[Commits] [SCM] claws branch, gtk2, updated. 3.19.0-26-g0eef11f92

wwp at claws-mail.org wwp at claws-mail.org
Fri May 20 22:45:31 CEST 2022


The branch, gtk2 has been updated
       via  0eef11f9279815558a6049afa7bd49b70f97fbb0 (commit)
      from  b1af4da2238359b033dcac3f5991c132bef20bd6 (commit)

Summary of changes:
 src/oauth2.c        |  59 ++++++---------
 src/prefs_account.c | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 237 insertions(+), 36 deletions(-)


- Log -----------------------------------------------------------------
commit 0eef11f9279815558a6049afa7bd49b70f97fbb0
Author: Michael Rasmussen <mir at datanom.net>
Date:   Fri May 20 21:31:35 2022 +0200

    New method for OAuth2 authentication and receiving of access token from David Fletcher applied including my fix for Microsoft OAuth2 functionality
    
    Signed-off-by: Michael Rasmussen <mir at datanom.net>

diff --git a/src/oauth2.c b/src/oauth2.c
index e87ca68ce..5cde88ff0 100644
--- a/src/oauth2.c
+++ b/src/oauth2.c
@@ -47,7 +47,7 @@ static gchar *OAUTH2info[4][17]={
   {"accounts.google.com",
    "",
    ".",
-   "urn:ietf:wg:oauth:2.0:oob",
+   "http://127.0.0.1:8888",
    "/o/oauth2/auth",
    "/o/oauth2/token",
    "/o/oauth2/token",
@@ -64,7 +64,7 @@ static gchar *OAUTH2info[4][17]={
   {"login.microsoftonline.com",
    "",
    "",
-   "https://login.microsoftonline.com/common/oauth2/nativeclient",
+   "http://127.0.0.1:8888",
    "/common/oauth2/v2.0/authorize",
    "/common/oauth2/v2.0/token",
    "/common/oauth2/v2.0/token",
@@ -76,12 +76,12 @@ static gchar *OAUTH2info[4][17]={
    "",
    "offline",
    "wl.imap offline_access",
-   "fragment",
+   "query",
    ""},
   {"login.microsoftonline.com",
    "",
    "",
-   "https://login.microsoftonline.com/common/oauth2/nativeclient",
+   "http://127.0.0.1:8888",
    "/common/oauth2/v2.0/authorize",
    "/common/oauth2/v2.0/token",
    "/common/oauth2/v2.0/token",
@@ -93,7 +93,7 @@ static gchar *OAUTH2info[4][17]={
    "",
    "offline",
    "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send",
-   "fragment",
+   "query",
    ""},
   {"api.login.yahoo.com",
    "",
@@ -116,9 +116,9 @@ static gchar *OAUTH2info[4][17]={
 
 static gchar *OAUTH2CodeMarker[5][2] = {
     {"",""},
-    {"google_begin_mark","google_end_mark"}, /* Not used since token avalable to user to copy in browser window */
-    {"#code=","&session_state"},
-    {"#code=","&session_state"},
+    {"code=","&scope="},
+    {"code="," HTTP"},
+    {"code="," HTTP"},
     {"yahoo_begin_mark","yahoo_end_mark"} /* Not used since token avalable to user to copy in browser window */
 };
 
@@ -191,9 +191,9 @@ static gint oauth2_filter_refresh (gchar *json, gchar *refresh_token)
 
 static gchar* oauth2_get_token_from_response(Oauth2Service provider, const gchar* response) {
 	gchar* token = NULL;
-
+	
         debug_print("Auth response: %s\n", response);
-        if (provider == OAUTH2AUTH_YAHOO || provider == OAUTH2AUTH_GOOGLE) {
+        if (provider == OAUTH2AUTH_YAHOO) {
                 /* Providers which display auth token in browser for users to copy */
                 token = g_strdup(response);
         } else {
@@ -206,7 +206,7 @@ static gchar* oauth2_get_token_from_response(Oauth2Service provider, const gchar
                         return NULL;
                 token = g_strndup(start, stop - start);
         }
-
+	
 	return token;
 }
 
@@ -225,8 +225,8 @@ int oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const
 	SockInfo *sock;
 	gchar *client_id;
 	gchar *client_secret;
-    gchar *token = NULL;
-    gchar *tmp;
+        gchar *token = NULL;
+        gchar *tmp;
 	gint i;
 
 	i = (int)provider - 1;
@@ -239,7 +239,7 @@ int oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const
                 log_message(LOG_PROTOCOL, _("OAuth2 missing authorization code\n"));
                 return (1);
         }
-
+        debug_print("Connect: %s:443\n", OAUTH2info[i][OA2_BASE_URL]);
 	sock = sock_connect(OAUTH2info[i][OA2_BASE_URL], 443);
 	if (sock == NULL) {
                 log_message(LOG_PROTOCOL, _("OAuth2 connection error\n"));
@@ -267,12 +267,9 @@ int oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const
 	else
 	  client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]);
 
-	uri = g_uri_escape_string (client_id, NULL, FALSE);
-	uri2 = g_uri_escape_string (token, NULL, FALSE);
-	body = g_strconcat ("client_id=", uri, "&code=", uri2, NULL);
-    g_free(uri2);
-    g_free(uri);
-    g_free(token);
+        body = g_strconcat ("client_id=", client_id, "&code=", token, NULL);
+        debug_print("Body: %s\n", body);
+        g_free(token);
 
 	if(OAUTH2info[i][OA2_CLIENT_SECRET][0]){
 	  //Only allow custom client secret if the service provider would usually expect a client secret
@@ -283,45 +280,35 @@ int oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const
 	  uri = g_uri_escape_string (client_secret, NULL, FALSE);
 	  tmp = g_strconcat (body, "&client_secret=", uri, NULL);
 	  g_free(body);
-      g_free(uri);
+          g_free(uri);
 	  body = tmp;
 	}else{
 	  client_secret = g_strconcat ("", NULL);
 	}
 
 	if(OAUTH2info[i][OA2_REDIRECT_URI][0]) {
-	  uri = g_uri_escape_string (OAUTH2info[i][OA2_REDIRECT_URI], NULL, FALSE);
-	  tmp = g_strconcat (body, "&redirect_uri=", uri, NULL);
+          tmp = g_strconcat(body, "&redirect_uri=", OAUTH2info[i][OA2_REDIRECT_URI], NULL);
 	  g_free(body);
-	  g_free(uri);
 	  body = tmp;
 	}
 	if(OAUTH2info[i][OA2_GRANT_TYPE_ACCESS][0]) {
-	  uri = g_uri_escape_string (OAUTH2info[i][OA2_GRANT_TYPE_ACCESS], NULL, FALSE);
-	  tmp = g_strconcat (body, "&grant_type=", uri, NULL);
+          tmp = g_strconcat(body, "&grant_type=", OAUTH2info[i][OA2_GRANT_TYPE_ACCESS], NULL);
 	  g_free(body);
-	  g_free(uri);
 	  body = tmp;
 	}
 	if(OAUTH2info[i][OA2_TENANT][0]) {
-	  uri = g_uri_escape_string (OAUTH2info[i][OA2_TENANT], NULL, FALSE);
-	  tmp = g_strconcat (body, "&tenant=", uri, NULL);
+          tmp = g_strconcat(body, "&tenant=", OAUTH2info[i][OA2_TENANT], NULL);
 	  g_free(body);
-	  g_free(uri);
 	  body = tmp;
 	}
 	if(OAUTH2info[i][OA2_SCOPE_FOR_ACCESS][0]) {
-	  uri = g_uri_escape_string (OAUTH2info[i][OA2_SCOPE_FOR_ACCESS], NULL, FALSE);
-	  tmp = g_strconcat (body, "&scope=", uri, NULL);
+          tmp = g_strconcat(body, "&scope=", OAUTH2info[i][OA2_SCOPE_FOR_ACCESS], NULL);
 	  g_free(body);
-	  g_free(uri);
 	  body = tmp;
 	}
 	if(OAUTH2info[i][OA2_STATE][0]) {
-	  uri = g_uri_escape_string (OAUTH2info[i][OA2_STATE], NULL, FALSE);
-	  tmp = g_strconcat (body, "&state=", uri, NULL);
+          tmp = g_strconcat(body, "&state=", OAUTH2info[i][OA2_STATE], NULL);
 	  g_free(body);
-	  g_free(uri);
 	  body = tmp;
 	}
 
diff --git a/src/prefs_account.c b/src/prefs_account.c
index 172371490..f117446bc 100644
--- a/src/prefs_account.c
+++ b/src/prefs_account.c
@@ -69,6 +69,7 @@
 #ifdef USE_GNUTLS
 #include <gnutls/gnutls.h>
 #endif
+#include <pthread.h> 
 
 static gboolean cancelled;
 static gboolean new_account;
@@ -96,6 +97,9 @@ struct AutocheckWidgets {
 
 static GSList *prefs_pages = NULL;
 
+static pthread_t oauth2_listener_tid;
+static int oauth2_listener_cancel = 0;
+
 typedef struct BasicPage
 {
     PrefsPage page;
@@ -423,6 +427,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_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);
@@ -3826,6 +3832,13 @@ static void send_destroy_widget_func(PrefsPage *_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)
@@ -4155,6 +4168,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_listener_tid = (pthread_t)NULL;
+
 	prefs_account_register_page((PrefsPage *) &oauth2_page);
 }
 
@@ -5155,6 +5170,22 @@ static void prefs_account_oauth2_copy_url(GtkButton *button, gpointer data)
 		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)
@@ -6114,3 +6145,186 @@ static void prefs_account_receive_itv_spinbutton_value_changed_cb(GtkWidget *w,
 				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);
+}

-----------------------------------------------------------------------


hooks/post-receive
-- 
Claws Mail


More information about the Commits mailing list