New method for OAuth2 authentication and receiving of access token from David Fletche...
[claws.git] / src / prefs_account.c
index 7e84ec7a365f54840aa3ad47c9d7d5f581ad210c..0f94467460c7abf8296897d254ad5d513ff1dc61 100644 (file)
@@ -68,6 +68,7 @@
 #ifdef USE_GNUTLS
 #include <gnutls/gnutls.h>
 #endif
 #ifdef USE_GNUTLS
 #include <gnutls/gnutls.h>
 #endif
+#include <pthread.h> 
 
 static gboolean cancelled;
 static gboolean new_account;
 
 static gboolean cancelled;
 static gboolean new_account;
@@ -95,6 +96,9 @@ struct AutocheckWidgets {
 
 static GSList *prefs_pages = NULL;
 
 
 static GSList *prefs_pages = NULL;
 
+static pthread_t oauth2_listener_tid;
+static int oauth2_listener_cancel = 0;
+
 typedef struct BasicPage
 {
     PrefsPage page;
 typedef struct BasicPage
 {
     PrefsPage page;
@@ -422,6 +426,8 @@ static void prefs_account_oauth2_provider_set_data_from_optmenu
 static void prefs_account_oauth2_provider_set_optmenu  (PrefParam *pparam);
 static void prefs_account_oauth2_copy_url                       (GtkButton *button, 
                                                         gpointer data);
 static void prefs_account_oauth2_provider_set_optmenu  (PrefParam *pparam);
 static void prefs_account_oauth2_copy_url                       (GtkButton *button, 
                                                         gpointer data);
+static void * prefs_account_oauth2_listener(void *params);
+static int  prefs_account_oauth2_get_line(int sock, char *buf, int size);
 static void prefs_account_oauth2_set_sensitivity(void);
 static void prefs_account_oauth2_set_auth_sensitivity(void);
 static void prefs_account_oauth2_obtain_tokens(GtkButton *button, gpointer data);
 static void prefs_account_oauth2_set_sensitivity(void);
 static void prefs_account_oauth2_set_auth_sensitivity(void);
 static void prefs_account_oauth2_obtain_tokens(GtkButton *button, gpointer data);
@@ -3776,6 +3782,13 @@ static void send_destroy_widget_func(PrefsPage *_page)
 static void oauth2_destroy_widget_func(PrefsPage *_page)
 {
        /* Oauth2Page *page = (Oauth2Page *) _page; */
 static void oauth2_destroy_widget_func(PrefsPage *_page)
 {
        /* Oauth2Page *page = (Oauth2Page *) _page; */
+
+        if(oauth2_listener_tid){
+         debug_print("Closing oauth2 listener thread\n");
+         oauth2_listener_cancel = 1;
+         pthread_join(oauth2_listener_tid, NULL);
+         oauth2_listener_tid = (pthread_t)NULL;
+       }
 }
 
 static void compose_destroy_widget_func(PrefsPage *_page)
 }
 
 static void compose_destroy_widget_func(PrefsPage *_page)
@@ -4105,6 +4118,8 @@ static void register_oauth2_page(void)
        oauth2_page.page.save_page = oauth2_save_func;
        oauth2_page.page.can_close = oauth2_can_close_func;
 
        oauth2_page.page.save_page = oauth2_save_func;
        oauth2_page.page.can_close = oauth2_can_close_func;
 
+       oauth2_listener_tid = (pthread_t)NULL;
+
        prefs_account_register_page((PrefsPage *) &oauth2_page);
 }
 
        prefs_account_register_page((PrefsPage *) &oauth2_page);
 }
 
@@ -5113,6 +5128,22 @@ static void prefs_account_oauth2_copy_url(GtkButton *button, gpointer data)
                open_uri(url, prefs_common_get_uri_cmd());
 
        g_free(url);
                open_uri(url, prefs_common_get_uri_cmd());
 
        g_free(url);
+
+       //Start listener for authorisation reply
+       //Needs to be in a separate thread to avoid hanging while we wait, and to allow cancellation of the process
+       //Avoid starting multiple threads if someone clicks this button more than once. 
+
+       //if thead exists cancel it here
+       if(oauth2_listener_tid){
+         debug_print("Cancelling old oauth2 listener thread\n");
+         oauth2_listener_cancel = 1;
+         pthread_join(oauth2_listener_tid, NULL);
+         oauth2_listener_tid = (pthread_t)NULL;
+       }
+       debug_print("Starting oauth2 listener thread\n");
+       oauth2_listener_cancel = 0;
+       pthread_create(&oauth2_listener_tid, NULL, prefs_account_oauth2_listener, (void*)win);
+       
 }
 
 static void prefs_account_oauth2_obtain_tokens(GtkButton *button, gpointer data)
 }
 
 static void prefs_account_oauth2_obtain_tokens(GtkButton *button, gpointer data)
@@ -5977,3 +6008,186 @@ static void prefs_account_receive_itv_spinbutton_value_changed_cb(GtkWidget *w,
                                PREFS_RECV_AUTOCHECK_MIN_INTERVAL);
        }
 }
                                PREFS_RECV_AUTOCHECK_MIN_INTERVAL);
        }
 }
+
+//Automation of the oauth2 authorisation process to receive loopback callback generated by redirect in browser
+static void * prefs_account_oauth2_listener(void * param)
+{ 
+        int socket_desc, client_sock, c;
+       struct sockaddr_in server , client;
+       char client_message[2000];
+       char reply[600];
+       char reply_message[400];
+       gchar *trim_text = NULL;
+       fd_set rfds;
+       gint ret;
+       struct timeval timeout;
+       struct BasicProtocol *protocol_optmenu = (struct BasicProtocol *)oauth2_page.protocol_optmenu;
+       GtkWidget *optmenu = protocol_optmenu->combobox;
+       Oauth2Service service;
+        OAUTH2Data *OAUTH2Data = g_malloc(sizeof(* OAUTH2Data));
+
+       
+       //pthread_detach(pthread_self());
+       debug_print("oauth2 listener thread running\n");
+     
+       //Create socket
+       socket_desc = socket(AF_INET , SOCK_STREAM , 0);
+       if (socket_desc == -1)
+       {
+               debug_print("oauth2 listener could not create socket\n");
+               return NULL;
+       }
+       debug_print("oauth2 listener socket created\n");
+       
+       //Prepare the sockaddr_in structure
+       server.sin_family = AF_INET;
+       server.sin_addr.s_addr = INADDR_ANY;
+       server.sin_port = htons( 8888 );
+       
+       //Bind
+       if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
+       {
+               debug_print("oauth2 listener bind failed\n");
+               return NULL;
+       }
+       debug_print("oauth2 listener bind done\n");
+
+       listen(socket_desc , 1);
+       
+       //Accept and incoming connection
+       debug_print("oauth2 listener waiting for incoming connections...\n");
+       c = sizeof(struct sockaddr_in);
+       
+       do{
+            FD_ZERO(&rfds);
+            FD_SET(socket_desc, &rfds);
+            timeout.tv_sec = 1;
+            timeout.tv_usec = 0;
+
+            select(socket_desc+1, &rfds, NULL, NULL, &timeout);
+            
+            //select woke up, maybe accept connection from an incoming client
+            if(FD_ISSET(socket_desc, &rfds)){
+              
+                 client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c);
+                 if (client_sock < 0){
+                   debug_print("oauth2 listener accept failed\n");
+                   return NULL;
+                 }
+                 debug_print("oauth2 listener connection accepted\n");
+                 
+                 //Receive message sent to the loopback address by the authorisation page
+                 prefs_account_oauth2_get_line(client_sock, client_message, sizeof(client_message));
+                 trim_text = g_strdup(client_message);
+                 g_strstrip(trim_text);
+
+                 gtk_entry_set_text(GTK_ENTRY(oauth2_page.oauth2_authcode_entry), trim_text != NULL ? trim_text : "");
+                 gtk_widget_set_sensitive(oauth2_page.oauth2_authcode_entry, FALSE);
+                 gtk_widget_set_sensitive(oauth2_page.oauth2_authorise_btn, FALSE);
+                 
+                 oauth2_init (OAUTH2Data);
+                 
+                 OAUTH2Data->custom_client_secret = 
+                   g_strdup(gtk_entry_get_text((GtkEntry *)oauth2_page.oauth2_client_secret_entry));
+                 OAUTH2Data->custom_client_id = 
+                   g_strdup(gtk_entry_get_text((GtkEntry *)oauth2_page.oauth2_client_id_entry));
+                 
+                 service = combobox_get_active_data(GTK_COMBO_BOX(optmenu));
+                 ret = oauth2_obtain_tokens (service, OAUTH2Data, trim_text);
+                 
+                 if(!ret){
+                   if(OAUTH2Data->refresh_token != NULL){
+                     passwd_store_set_account(tmp_ac_prefs.account_id,
+                                              PWS_ACCOUNT_OAUTH2_REFRESH,
+                                              OAUTH2Data->refresh_token,
+                                              FALSE);
+                     log_message(LOG_PROTOCOL, "OAuth2 refresh token stored\n");
+                   }
+                   
+                   if(OAUTH2Data->access_token != NULL){
+                     passwd_store_set_account(tmp_ac_prefs.account_id,
+                                              PWS_ACCOUNT_RECV,
+                                              OAUTH2Data->access_token,
+                                              FALSE);
+                     
+                     passwd_store_set_account(tmp_ac_prefs.account_id,
+                                              PWS_ACCOUNT_SEND,
+                                              OAUTH2Data->access_token,
+                                              FALSE);
+                     log_message(LOG_PROTOCOL, "OAuth2 access token stored\n");
+                     
+                     gtk_entry_set_text(GTK_ENTRY(basic_page.pass_entry), OAUTH2Data->access_token);
+                     gtk_entry_set_text(GTK_ENTRY(send_page.smtp_pass_entry), OAUTH2Data->access_token);
+                   }
+                   
+                   if(OAUTH2Data->expiry_str != NULL){
+                     passwd_store_set_account(tmp_ac_prefs.account_id,
+                                              PWS_ACCOUNT_OAUTH2_EXPIRY,
+                                              OAUTH2Data->expiry_str,
+                                              FALSE);
+                     log_message(LOG_PROTOCOL, "OAuth2 access token expiry stored\n");
+                   }
+                   
+                   tmp_ac_prefs.oauth2_date = g_get_real_time () / G_USEC_PER_SEC;
+                   
+                   sprintf(reply_message, "<html><body><h1>Authorisation complete</h1><p>Your oauth2 authorisation code has been received by Claws Mail</p></body></html>");
+                 }else{
+                   //Something went wrong
+                   log_message(LOG_PROTOCOL, "oauth2 authorisation code not received\n");
+                   sprintf(reply_message, "<html><body><h1>Authorisation NOT completed</h1><p>Your authorisation code was not received by Claws Mail</p></body></html>");
+                 }
+                 
+                 sprintf(reply, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nContent-Length: %lu\r\n\r\n%s", strlen(reply_message), reply_message);
+                 write(client_sock, reply, strlen(reply));
+                 close(client_sock);
+            }
+            
+       }while(ret && !oauth2_listener_cancel);
+
+       close(socket_desc);
+       g_free(trim_text);
+       g_free(OAUTH2Data);
+       
+       //To Do
+       //(3) Work out how to give an indicataion in the main window that authentication has happened
+       //(6) Clean up, test, copy over to git version
+       //(1) Work out why extracting code from this causes segfault in oauth2_get_token_from_response
+       //http://127.0.0.1:8888/?code=M.R3_BAY.09a02423-7bf8-8e51-c4a3-5565f8e7d56b HTTP/1.1
+       return NULL;
+}
+
+
+
+
+
+static int prefs_account_oauth2_get_line(int sock, char *buf, int size)
+{
+       int i = 0;
+       char c = '\0';
+       int n;
+       
+       while ((i < size - 1) && (c != '\n'))
+         {
+           n = recv(sock, &c, 1, 0);
+           //printf("%02X\n", c);
+           if (n > 0)
+             {
+               if (c == '\r')
+                 {
+                   n = recv(sock, &c, 1, MSG_PEEK);
+                   //printf("%02X\n", c);
+                   if ((n > 0) && (c == '\n'))
+                     recv(sock, &c, 1, 0);
+                   else
+                     c = '\n';
+                 }
+               buf[i] = c;
+               i++;
+             }
+           else
+             c = '\n';
+         }
+       buf[i] = '\0';
+       
+       return(i);
+}