[Commits] [SCM] claws branch, master, updated. 4.1.0-18-ge3a613af5
miras at claws-mail.org
miras at claws-mail.org
Fri May 20 21:31:53 CEST 2022
The branch, master has been updated
via e3a613af5ac6ed643b53f3b95f48cb4cb0b5c4e0 (commit)
from c23d02b23105ab05d0b0e1a037da7596e6c61777 (commit)
Summary of changes:
src/oauth2.c | 59 ++++++---------
src/prefs_account.c | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 237 insertions(+), 36 deletions(-)
- Log -----------------------------------------------------------------
commit e3a613af5ac6ed643b53f3b95f48cb4cb0b5c4e0
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 e760eccfa..1f7f59959 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 7e84ec7a3..0f9446746 100644
--- a/src/prefs_account.c
+++ b/src/prefs_account.c
@@ -68,6 +68,7 @@
#ifdef USE_GNUTLS
#include <gnutls/gnutls.h>
#endif
+#include <pthread.h>
static gboolean cancelled;
static gboolean new_account;
@@ -95,6 +96,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;
@@ -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_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);
@@ -3776,6 +3782,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)
@@ -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_listener_tid = (pthread_t)NULL;
+
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);
+
+ //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)
@@ -5977,3 +6008,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