commit 28b2c38a9b25f611f844202ba785fa4c9588768e
parent 41cbf87342cba1333deb76b1fa9443604a88a83a
Author: Michael Rasmussen <mir@datanom.net>
Date: Thu, 31 Jul 2025 18:58:27 +0200
Add oauth2 patch-2025-07-30 from David
Signed-off-by: Michael Rasmussen <mir@datanom.net>
Diffstat:
9 files changed, 445 insertions(+), 246 deletions(-)
diff --git a/src/account.c b/src/account.c
@@ -55,6 +55,9 @@
#include "hooks.h"
#include "passwordstore.h"
#include "file-utils.h"
+#ifdef USE_OAUTH2
+#include "oauth2.h"
+#endif
enum {
ACCOUNT_IS_DEFAULT,
diff --git a/src/common/defs.h b/src/common/defs.h
@@ -51,6 +51,7 @@
#define COMMON_RC "clawsrc"
#define OLD_COMMON_RC "sylpheedrc"
#define ACCOUNT_RC "accountrc"
+#define OAUTH2_RC "oauth2rc"
#define CUSTOM_HEADER_RC "customheaderrc"
#define DISPLAY_HEADER_RC "dispheaderrc"
#define FOLDERITEM_RC "folderitemrc"
diff --git a/src/main.c b/src/main.c
@@ -136,6 +136,10 @@
#include "passwordstore.h"
#include "file-utils.h"
+#ifdef USE_OAUTH2
+#include "oauth2.h"
+#endif
+
#ifdef HAVE_LIBETPAN
#include "imap-thread.h"
#include "nntp-thread.h"
@@ -1279,6 +1283,9 @@ int main(int argc, char *argv[])
prefs_account_init();
account_read_config_all();
+#ifdef USE_OAUTH2
+ account_read_oauth2_all();
+#endif
if (prefs_update_config_version_accounts() < 0) {
debug_print("Accounts configuration file version upgrade failed, exiting\n");
diff --git a/src/oauth2.c b/src/oauth2.c
@@ -24,6 +24,7 @@
#ifdef USE_OAUTH2
+#include "defs.h"
#include <glib.h>
#ifdef ENABLE_NLS
#include <glib/gi18n.h>
@@ -42,118 +43,278 @@
#include "log.h"
#include "time.h"
#include "common/passcrypt.h"
+#include "common/version.h"
+#include "file-utils.h"
#include "prefs_common.h"
#define GNUTLS_PRIORITY "NORMAL:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.1"
//Yahoo requires token requests to send POST header Authorization: Basic
//where the password is Base64 encoding of client_id:client_secret
-static gchar *OAUTH2info[5][17]={
- {"accounts.google.com",
- "",
- ".",
- "http://127.0.0.1:8888",
- "/o/oauth2/auth",
- "/o/oauth2/token",
- "/o/oauth2/token",
- "code",
- "https://mail.google.com",
- "authorization_code",
- "refresh_token",
- "",
- "",
- "",
- "",
- "",
- ""},
- {"login.microsoftonline.com",
- "",
- "",
- "http://127.0.0.1:8888",
- "/common/oauth2/v2.0/authorize",
- "/common/oauth2/v2.0/token",
- "/common/oauth2/v2.0/token",
- "code",
- "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send",
- "authorization_code",
- "refresh_token",
- "common",
- "",
- "offline",
- "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send",
- "query",
- ""},
- {"login.microsoftonline.com",
- "",
- "",
- "http://127.0.0.1:8888",
- "/common/oauth2/v2.0/authorize",
- "/common/oauth2/v2.0/token",
- "/common/oauth2/v2.0/token",
- "code",
- "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send",
- "authorization_code",
- "refresh_token",
- "common",
- "",
- "offline",
- "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send",
- "query",
- ""},
- {"login.microsoftonline.us",
- "",
- "",
- "http://127.0.0.1:8888",
- "/common/oauth2/v2.0/authorize",
- "/common/oauth2/v2.0/token",
- "/common/oauth2/v2.0/token",
- "code",
- "offline_access https://outlook.office365.us/IMAP.AccessAsUser.All https://outlook.office365.us/POP.AccessAsUser.All https://outlook.office365.us/SMTP.Send",
- "authorization_code",
- "refresh_token",
- "common",
- "",
- "offline",
- "offline_access https://outlook.office365.us/IMAP.AccessAsUser.All https://outlook.office365.us/POP.AccessAsUser.All https://outlook.office365.us/SMTP.Send",
- "query",
- ""},
- {"api.login.yahoo.com",
- "",
- ".",
- "oob",
- "/oauth2/request_auth",
- "/oauth2/get_token",
- "/oauth2/get_token",
- "code",
- "",
- "authorization_code",
- "refresh_token",
- "",
- "",
- "",
- "",
- "",
- "1"}
-};
-
-static gchar *OAUTH2CodeMarker[6][2] = {
- {"",""},
- {"code=","&scope="},
- {"code="," HTTP"},
- {"code=","&session_state="},
- {"code=","&session_state="},
- {"yahoo_begin_mark","yahoo_end_mark"} /* Not used since token avalable to user to copy in browser window */
-};
-
static gint oauth2_post_request (gchar *buf, gchar *host, gchar *resource, gchar *header, gchar *body);
static gint oauth2_filter_refresh (gchar *json, gchar *refresh_token);
static gint oauth2_filter_access (gchar *json, gchar *access_token, gint *expiry);
+static GList *oauth2_providers_list = NULL;
+static Oauth2Info tmp_oa2_info;
+
+static PrefParam oauth2_info[] = {
+ {"oa2_name", NULL, &tmp_oa2_info.oa2_name, P_STRING, NULL, NULL, NULL},
+ {"oa2_base_url", NULL, &tmp_oa2_info.oa2_base_url, P_STRING, NULL, NULL, NULL},
+ {"oa2_client_id", NULL, &tmp_oa2_info.oa2_client_id, P_STRING, NULL, NULL, NULL},
+ {"oa2_client_secret", NULL, &tmp_oa2_info.oa2_client_secret, P_STRING, NULL, NULL, NULL},
+ {"oa2_redirect_uri", NULL, &tmp_oa2_info.oa2_redirect_uri, P_STRING, NULL, NULL, NULL},
+ {"oa2_auth_resource", NULL, &tmp_oa2_info.oa2_auth_resource, P_STRING, NULL, NULL, NULL},
+ {"oa2_access_resource", NULL, &tmp_oa2_info.oa2_access_resource, P_STRING, NULL, NULL, NULL},
+ {"oa2_refresh_resource", NULL, &tmp_oa2_info.oa2_refresh_resource, P_STRING, NULL, NULL, NULL},
+ {"oa2_response_type", NULL, &tmp_oa2_info.oa2_response_type, P_STRING, NULL, NULL, NULL},
+ {"oa2_scope_for_auth", NULL, &tmp_oa2_info.oa2_scope_for_auth, P_STRING, NULL, NULL, NULL},
+ {"oa2_grant_type_access", NULL, &tmp_oa2_info.oa2_grant_type_access, P_STRING, NULL, NULL, NULL},
+ {"oa2_grant_type_refresh", NULL, &tmp_oa2_info.oa2_grant_type_refresh, P_STRING, NULL, NULL, NULL},
+ {"oa2_tenant", NULL, &tmp_oa2_info.oa2_tenant, P_STRING, NULL, NULL, NULL},
+ {"oa2_state", NULL, &tmp_oa2_info.oa2_state, P_STRING, NULL, NULL, NULL},
+ {"oa2_access_type", NULL, &tmp_oa2_info.oa2_access_type, P_STRING, NULL, NULL, NULL},
+ {"oa2_scope_for_access", NULL, &tmp_oa2_info.oa2_scope_for_access, P_STRING, NULL, NULL, NULL},
+ {"oa2_response_mode", NULL, &tmp_oa2_info.oa2_response_mode, P_STRING, NULL, NULL, NULL},
+ {"oa2_header_auth_basic", NULL, &tmp_oa2_info.oa2_header_auth_basic, P_STRING, NULL, NULL, NULL},
+ {"oa2_two_stage_pop", NULL, &tmp_oa2_info.oa2_two_stage_pop, P_INT, NULL, NULL, NULL},
+ {"oa2_codemarker_start", NULL, &tmp_oa2_info.oa2_codemarker_start, P_STRING, NULL, NULL, NULL},
+ {"oa2_codemarker_stop", NULL, &tmp_oa2_info.oa2_codemarker_stop, P_STRING, NULL, NULL, NULL},
+ {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
+};
+
+static gchar *oauth2_tmpl =
+ "protected=0\n\n"
+ "[Oauth2: 1]\n"
+ "oa2_name=Google\n"
+ "oa2_base_url=accounts.google.com\n"
+ "oa2_client_id=\n"
+ "oa2_client_secret=.\n"
+ "oa2_redirect_uri=http://127.0.0.1:8888\n"
+ "oa2_auth_resource=/o/oauth2/auth\n"
+ "oa2_access_resource=/o/oauth2/token\n"
+ "oa2_refresh_resource=/o/oauth2/token\n"
+ "oa2_response_type=code\n"
+ "oa2_scope_for_auth=https://mail.google.com\n"
+ "oa2_grant_type_access=authorization_code\n"
+ "oa2_grant_type_refresh=refresh_token\n"
+ "oa2_tenant=\n"
+ "oa2_state=\n"
+ "oa2_access_type=\n"
+ "oa2_scope_for_access=\n"
+ "oa2_response_mode=\n"
+ "oa2_header_auth_basic=\n"
+ "oa2_two_stage_pop=0\n"
+ "oa2_codemarker_start=code=\n"
+ "oa2_codemarker_stop=&scope=\n\n"
+ "[Oauth2: 2]\n"
+ "oa2_name=Outlook\n"
+ "oa2_base_url=login.microsoftonline.com\n"
+ "oa2_client_id=\n"
+ "oa2_client_secret=\n"
+ "oa2_redirect_uri=http://127.0.0.1:8888\n"
+ "oa2_auth_resource=/common/oauth2/v2.0/authorize\n"
+ "oa2_access_resource=/common/oauth2/v2.0/token\n"
+ "oa2_refresh_resource=/common/oauth2/v2.0/token\n"
+ "oa2_response_type=code\n"
+ "oa2_scope_for_auth=offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send\n"
+ "oa2_grant_type_access=authorization_code\n"
+ "oa2_grant_type_refresh=refresh_token\n"
+ "oa2_tenant=common\n"
+ "oa2_state=\n"
+ "oa2_access_type=offline\n"
+ "oa2_scope_for_access=offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send\n"
+ "oa2_response_mode=query\n"
+ "oa2_header_auth_basic=\n"
+ "oa2_two_stage_pop=1\n"
+ "oa2_codemarker_start=code=\n"
+ "oa2_codemarker_stop= HTTP\n\n"
+ "[Oauth2: 3]\n"
+ "oa2_name=Exchange\n"
+ "oa2_base_url=login.microsoftonline.com\n"
+ "oa2_client_id=\n"
+ "oa2_client_secret=\n"
+ "oa2_redirect_uri=http://127.0.0.1:8888\n"
+ "oa2_auth_resource=/common/oauth2/v2.0/authorize\n"
+ "oa2_access_resource=/common/oauth2/v2.0/token\n"
+ "oa2_refresh_resource=/common/oauth2/v2.0/token\n"
+ "oa2_response_type=code\n"
+ "oa2_scope_for_auth=offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send\n"
+ "oa2_grant_type_access=authorization_code\n"
+ "oa2_grant_type_refresh=refresh_token\n"
+ "oa2_tenant=common\n"
+ "oa2_state=\n"
+ "oa2_access_type=offline\n"
+ "oa2_scope_for_access=offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send\n"
+ "oa2_response_mode=query\n"
+ "oa2_header_auth_basic=\n"
+ "oa2_two_stage_pop=1\n"
+ "oa2_codemarker_start=code=\n"
+ "oa2_codemarker_stop=&session_state=\n\n"
+ "[Oauth2: 4]\n"
+ "oa2_name=Microsoft_gcchigh\n"
+ "oa2_base_url=login.microsoftonline.us\n"
+ "oa2_client_id=\n"
+ "oa2_client_secret=\n"
+ "oa2_redirect_uri=http://127.0.0.1:8888\n"
+ "oa2_auth_resource=/common/oauth2/v2.0/authorize\n"
+ "oa2_access_resource=/common/oauth2/v2.0/token\n"
+ "oa2_refresh_resource=/common/oauth2/v2.0/token\n"
+ "oa2_response_type=code\n"
+ "oa2_scope_for_auth=offline_access https://outlook.office365.us/IMAP.AccessAsUser.All https://outlook.office365.us/POP.AccessAsUser.All https://outlook.office365.us/SMTP.Send\n"
+ "oa2_grant_type_access=authorization_code\n"
+ "oa2_grant_type_refresh=refresh_token\n"
+ "oa2_tenant=common\n"
+ "oa2_state=\n"
+ "oa2_access_type=offline\n"
+ "oa2_scope_for_access=offline_access https://outlook.office365.us/IMAP.AccessAsUser.All https://outlook.office365.us/POP.AccessAsUser.All https://outlook.office365.us/SMTP.Send\n"
+ "oa2_response_mode=query\n"
+ "oa2_header_auth_basic=\n"
+ "oa2_two_stage_pop=1\n"
+ "oa2_codemarker_start=code=\n"
+ "oa2_codemarker_stop=&session_state=\n\n"
+ "[Oauth2: 5]\n"
+ "oa2_name=Yahoo\n"
+ "oa2_base_url=api.login.yahoo.com\n"
+ "oa2_client_id=\n"
+ "oa2_client_secret=.\n"
+ "oa2_redirect_uri=oob\n"
+ "oa2_auth_resource=/oauth2/request_auth\n"
+ "oa2_access_resource=/oauth2/get_token\n"
+ "oa2_refresh_resource=/oauth2/get_token\n"
+ "oa2_response_type=code\n"
+ "oa2_scope_for_auth=\n"
+ "oa2_grant_type_access=authorization_code\n"
+ "oa2_grant_type_refresh=refresh_token\n"
+ "oa2_tenant=\n"
+ "oa2_state=\n"
+ "oa2_access_type=\n"
+ "oa2_scope_for_access=\n"
+ "oa2_response_mode=\n"
+ "oa2_header_auth_basic=1\n"
+ "oa2_two_stage_pop=0\n"
+ "oa2_codemarker_start=\n"
+ "oa2_codemarker_stop=\n"
+ ;
+
+static Oauth2Info *oauth2_new_from_config(const gchar *label)
+{
+ gchar *rcpath;
+ Oauth2Info *oa2_info;
+
+ cm_return_val_if_fail(label != NULL, NULL);
+
+ oa2_info = g_new0(Oauth2Info, 1);
+
+ /* Load default values to tmp_oa2_info first, ... */
+ memset(&tmp_oa2_info, 0, sizeof(Oauth2Info));
+ prefs_set_default(oauth2_info);
+
+ /* ... overriding them with values from stored config file. */
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, OAUTH2_RC, NULL);
+ prefs_read_config(oauth2_info, label, rcpath, NULL);
+ g_free(rcpath);
+
+ *oa2_info = tmp_oa2_info;
+
+ return oa2_info;
+}
+
+void account_read_oauth2_all(void)
+{
+ GSList *oauth2_label_list = NULL, *cur;
+ Oauth2Info *oauth2_prefs;
+ gchar *rcpath;
+ gchar *oauth2_text, *version_text;
+ FILE *fp;
+ gchar buf[PREFSBUFSIZE];
+ gint protected = 1;
+ gint matchedversion = 0;
+
+ debug_print("Reading oauth2rc file\n");
+
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, OAUTH2_RC, NULL);
+ if ((fp = claws_fopen(rcpath, "rb")) == NULL) {
+ //No oauth2rc file exists
+ oauth2_text = g_strconcat("[Version: ", VERSION, "]\n", oauth2_tmpl, NULL);
+ str_write_to_file(oauth2_text, rcpath, TRUE);
+ g_free(oauth2_text);
+ debug_print("No oauth2rc file found, new one created\n");
+
+ if ((fp = claws_fopen(rcpath, "rb")) == NULL) {
+ if (ENOENT != errno) FILE_OP_ERROR(rcpath, "claws_fopen");
+ g_free(rcpath);
+ return;
+ }
+ }else{
+ //oauth2rc file exists. Check version and whether protected from update
+ version_text = g_strconcat("[Version: ", VERSION, "]\n", NULL);
+ while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
+ if (!strncmp(buf, "protected=0", 11)) {
+ protected = 0;
+ debug_print("oauth2rc file is unprotected from updates\n");
+ }
+
+ if (!strcmp(buf, version_text)) {
+ matchedversion = 1;
+ debug_print("oauth2rc file matches Claws version\n");
+ }
+ }
+ g_free(version_text);
+ rewind(fp);
+
+ if(!protected && !matchedversion){
+ //oauth2rc not protected from updates and does not match this version of Claws
+ //Update it to the latest template version.
+ claws_fclose(fp);
+ oauth2_text = g_strconcat("[Version: ", VERSION, "]\n", oauth2_tmpl, NULL);
+ str_write_to_file(oauth2_text, rcpath, TRUE);
+ g_free(oauth2_text);
+ debug_print("Replacement oauth2rc file created to match this Claws version\n");
+
+ if ((fp = claws_fopen(rcpath, "rb")) == NULL) {
+ if (ENOENT != errno) FILE_OP_ERROR(rcpath, "claws_fopen");
+ g_free(rcpath);
+ return;
+ }
+ }
+ }
+ g_free(rcpath);
+
+ while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
+ if (!strncmp(buf, "[Oauth2: ", 9)) {
+ strretchomp(buf);
+ memmove(buf, buf + 1, sizeof(buf) - 1);
+ buf[strlen(buf) - 1] = '\0';
+ debug_print("Found configuration: %s\n", buf);
+ oauth2_label_list = g_slist_append(oauth2_label_list,
+ g_strdup(buf));
+ }
+ }
+ claws_fclose(fp);
+ /* read config data from file */
+ for (cur = oauth2_label_list; cur != NULL; cur = cur->next) {
+ debug_print("Extracting oauth2 data\n");
+ oauth2_prefs = oauth2_new_from_config((gchar *)cur->data);
+ oauth2_providers_list = g_list_append(oauth2_providers_list, oauth2_prefs);
+ }
+
+ while (oauth2_label_list) {
+ g_free(oauth2_label_list->data);
+ oauth2_label_list = g_slist_remove(oauth2_label_list,
+ oauth2_label_list->data);
+ }
+}
+
+GList *oauth2_providers_get_list(void)
+{
+ return oauth2_providers_list;
+}
static gint oauth2_post_request (gchar *buf, gchar *host, gchar *resource, gchar *header, gchar *body)
{
gint len;
-
+
debug_print("Complete body: %s\n", body);
len = strlen(body);
if (header[0])
@@ -214,17 +375,30 @@ 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;
+ gint i;
+ Oauth2Info *oa2;
+
+ //Retrieve oauth2 configuration information
+ if(provider > g_list_length(oauth2_providers_list)){
+ debug_print("Configured OAUTH2 provider is not present in the oauth2rc config file\n");
+ return NULL;
+ }
+
+ i = (int)provider - 1;
+
+ oa2 = g_list_nth_data (oauth2_providers_list, i);
debug_print("Auth response: %s\n", response);
- if (provider == OAUTH2AUTH_YAHOO) {
- /* Providers which display auth token in browser for users to copy */
+ if (!oa2->oa2_codemarker_start || !oa2->oa2_codemarker_stop ||
+ !oa2->oa2_codemarker_start[0] || !oa2->oa2_codemarker_stop[0]) {
+ /* Providers which display auth token in browser for users to copy */
token = g_strdup(response);
} else {
- gchar* start = g_strstr_len(response, strlen(response), OAUTH2CodeMarker[provider][0]);
+ gchar* start = g_strstr_len(response, strlen(response), oa2->oa2_codemarker_start);
if (start == NULL)
return NULL;
- start += strlen(OAUTH2CodeMarker[provider][0]);
- gchar* stop = g_strstr_len(response, strlen(response), OAUTH2CodeMarker[provider][1]);
+ start += strlen(oa2->oa2_codemarker_start);
+ gchar* stop = g_strstr_len(response, strlen(response), oa2->oa2_codemarker_stop);
if (stop == NULL)
return NULL;
token = g_strndup(start, stop - start);
@@ -296,19 +470,25 @@ int oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const
gchar *token = NULL;
gchar *tmp;
gint i;
-
- i = (int)provider - 1;
- if (i < 0 || i > (OAUTH2AUTH_LAST-1))
+ Oauth2Info *oa2;
+
+ //Retrieve oauth2 configuration information
+ if(provider > g_list_length(oauth2_providers_list)){
+ debug_print("Configured OAUTH2 provider is not present in the oauth2rc config file\n");
return (1);
-
+ }
+
+ i = (int)provider - 1;
+ oa2 = g_list_nth_data (oauth2_providers_list, i);
+
token = oauth2_get_token_from_response(provider, authcode);
debug_print("Auth token: %s\n", token);
if (token == NULL) {
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);
+ debug_print("Connect: %s:443\n", oa2->oa2_base_url);
+ sock = sock_connect(oa2->oa2_base_url, 443);
if (sock == NULL) {
log_message(LOG_PROTOCOL, _("OAuth2 connection error\n"));
g_free(token);
@@ -331,21 +511,21 @@ int oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const
access_token = g_malloc(OAUTH2BUFSIZE+1);
request = g_malloc(OAUTH2BUFSIZE+1);
- if(OAUTH2Data->custom_client_id)
+ if(OAUTH2Data->custom_client_id[0])
client_id = g_strdup(OAUTH2Data->custom_client_id);
else
- client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]);
+ client_id = g_strdup(oa2->oa2_client_id);
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]){
+ if(oa2->oa2_client_secret[0]){
//Only allow custom client secret if the service provider would usually expect a client secret
- if(OAUTH2Data->custom_client_secret)
+ if(OAUTH2Data->custom_client_secret[0])
client_secret = g_strdup(OAUTH2Data->custom_client_secret);
else
- client_secret = oauth2_decode(OAUTH2info[i][OA2_CLIENT_SECRET]);
+ client_secret = g_strdup(oa2->oa2_client_secret);
uri = g_uri_escape_string (client_secret, NULL, FALSE);
tmp = g_strconcat (body, "&client_secret=", uri, NULL);
g_free(body);
@@ -355,33 +535,33 @@ int oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const
client_secret = g_strconcat ("", NULL);
}
- if(OAUTH2info[i][OA2_REDIRECT_URI][0]) {
- tmp = g_strconcat(body, "&redirect_uri=", OAUTH2info[i][OA2_REDIRECT_URI], NULL);
+ if(oa2->oa2_redirect_uri[0]) {
+ tmp = g_strconcat(body, "&redirect_uri=", oa2->oa2_redirect_uri, NULL);
g_free(body);
body = tmp;
}
- if(OAUTH2info[i][OA2_GRANT_TYPE_ACCESS][0]) {
- tmp = g_strconcat(body, "&grant_type=", OAUTH2info[i][OA2_GRANT_TYPE_ACCESS], NULL);
+ if(oa2->oa2_grant_type_access[0]) {
+ tmp = g_strconcat(body, "&grant_type=", oa2->oa2_grant_type_access, NULL);
g_free(body);
body = tmp;
}
- if(OAUTH2info[i][OA2_TENANT][0]) {
- tmp = g_strconcat(body, "&tenant=", OAUTH2info[i][OA2_TENANT], NULL);
+ if(oa2->oa2_tenant[0]) {
+ tmp = g_strconcat(body, "&tenant=", oa2->oa2_tenant, NULL);
g_free(body);
body = tmp;
}
- if(OAUTH2info[i][OA2_SCOPE_FOR_ACCESS][0]) {
- tmp = g_strconcat(body, "&scope=", OAUTH2info[i][OA2_SCOPE_FOR_ACCESS], NULL);
+ if(oa2->oa2_scope_for_access[0]) {
+ tmp = g_strconcat(body, "&scope=", oa2->oa2_scope_for_access, NULL);
g_free(body);
body = tmp;
}
- if(OAUTH2info[i][OA2_STATE][0]) {
- tmp = g_strconcat(body, "&state=", OAUTH2info[i][OA2_STATE], NULL);
+ if(oa2->oa2_state[0]) {
+ tmp = g_strconcat(body, "&state=", oa2->oa2_state, NULL);
g_free(body);
body = tmp;
}
- if(OAUTH2info[i][OA2_HEADER_AUTH_BASIC][0]){
+ if(oa2->oa2_header_auth_basic[0]){
tmp_hd = g_strconcat(client_id, ":", client_secret, NULL);
tmp_hd_encoded = g_base64_encode (tmp_hd, strlen(tmp_hd));
header = g_strconcat ("Authorization: Basic ", tmp_hd_encoded, NULL);
@@ -391,7 +571,7 @@ int oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const
header = g_strconcat ("", NULL);
}
- oauth2_post_request (request, OAUTH2info[i][OA2_BASE_URL], OAUTH2info[i][OA2_ACCESS_RESOURCE], header, body);
+ oauth2_post_request (request, oa2->oa2_base_url, oa2->oa2_access_resource, header, body);
response = oauth2_contact_server (sock, request);
debug_print("Response from server: %s\n", response);
@@ -445,12 +625,18 @@ gint oauth2_use_refresh_token (Oauth2Service provider, OAUTH2Data *OAUTH2Data)
gchar *client_secret;
gchar *tmp;
gint i;
-
- i = (int)provider - 1;
- if (i < 0 || i > (OAUTH2AUTH_LAST-1))
+ Oauth2Info *oa2;
+
+ //Retrieve oauth2 configuration information
+ if(provider > g_list_length(oauth2_providers_list)){
+ debug_print("Configured OAUTH2 provider is not present in the oauth2rc config file\n");
return (1);
+ }
+
+ i = (int)provider - 1;
+ oa2 = g_list_nth_data (oauth2_providers_list, i);
- sock = sock_connect(OAUTH2info[i][OA2_BASE_URL], 443);
+ sock = sock_connect(oa2->oa2_base_url, 443);
if (sock == NULL) {
log_message(LOG_PROTOCOL, _("OAuth2 connection error\n"));
return (1);
@@ -471,21 +657,21 @@ gint oauth2_use_refresh_token (Oauth2Service provider, OAUTH2Data *OAUTH2Data)
refresh_token = g_malloc(OAUTH2BUFSIZE+1);
request = g_malloc(OAUTH2BUFSIZE+1);
- if(OAUTH2Data->custom_client_id)
+ if(OAUTH2Data->custom_client_id[0])
client_id = g_strdup(OAUTH2Data->custom_client_id);
else
- client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]);
+ client_id = g_strdup(oa2->oa2_client_id);
uri = g_uri_escape_string (client_id, NULL, FALSE);
body = g_strconcat ("client_id=", uri, "&refresh_token=", OAUTH2Data->refresh_token, NULL);
g_free(uri);
- if(OAUTH2info[i][OA2_CLIENT_SECRET][0]){
+ if(oa2->oa2_client_secret[0]){
//Only allow custom client secret if the service provider would usually expect a client secret
- if(OAUTH2Data->custom_client_secret)
+ if(OAUTH2Data->custom_client_secret[0])
client_secret = g_strdup(OAUTH2Data->custom_client_secret);
else
- client_secret = oauth2_decode(OAUTH2info[i][OA2_CLIENT_SECRET]);
+ client_secret = g_strdup(oa2->oa2_client_secret);
uri = g_uri_escape_string (client_secret, NULL, FALSE);
tmp = g_strconcat (body, "&client_secret=", uri, NULL);
g_free(body);
@@ -495,29 +681,29 @@ gint oauth2_use_refresh_token (Oauth2Service provider, OAUTH2Data *OAUTH2Data)
client_secret = g_strconcat ("", NULL);
}
- if(OAUTH2info[i][OA2_GRANT_TYPE_REFRESH][0]) {
- uri = g_uri_escape_string (OAUTH2info[i][OA2_GRANT_TYPE_REFRESH], NULL, FALSE);
+ if(oa2->oa2_grant_type_refresh[0]) {
+ uri = g_uri_escape_string (oa2->oa2_grant_type_refresh, NULL, FALSE);
tmp = g_strconcat (body, "&grant_type=", uri, 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);
+ if(oa2->oa2_scope_for_access[0]) {
+ uri = g_uri_escape_string (oa2->oa2_scope_for_access, NULL, FALSE);
tmp = g_strconcat (body, "&scope=", uri, 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);
+ if(oa2->oa2_state[0]) {
+ uri = g_uri_escape_string (oa2->oa2_state, NULL, FALSE);
tmp = g_strconcat (body, "&state=", uri, NULL);
g_free(body);
g_free(uri);
body = tmp;
}
- if(OAUTH2info[i][OA2_HEADER_AUTH_BASIC][0]){
+ if(oa2->oa2_header_auth_basic[0]){
tmp_hd = g_strconcat(client_id, ":", client_secret, NULL);
tmp_hd_encoded = g_base64_encode (tmp_hd, strlen(tmp_hd));
header = g_strconcat ("Authorization: Basic ", tmp_hd_encoded, NULL);
@@ -527,7 +713,7 @@ gint oauth2_use_refresh_token (Oauth2Service provider, OAUTH2Data *OAUTH2Data)
header = g_strconcat ("", NULL);
}
- oauth2_post_request (request, OAUTH2info[i][OA2_BASE_URL], OAUTH2info[i][OA2_REFRESH_RESOURCE], header, body);
+ oauth2_post_request (request, oa2->oa2_base_url, oa2->oa2_refresh_resource, header, body);
debug_print("Request: %s\n", request);
response = oauth2_contact_server (sock, request);
debug_print("Response from server: %s\n", response);
@@ -573,59 +759,68 @@ gint oauth2_authorisation_url (Oauth2Service provider, gchar **url, const gchar
gchar *client_id = NULL;
gchar *tmp;
gchar *uri;
-
- i = (int)provider - 1;
- if (i < 0 || i > (OAUTH2AUTH_LAST-1))
+ Oauth2Info *oa2;
+
+ //Retrieve oauth2 configuration information
+ if(provider > g_list_length(oauth2_providers_list)){
+ debug_print("Configured OAUTH2 provider is not present in the oauth2rc config file\n");
return (1);
+ }
- if(!custom_client_id)
- client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]);
+ i = (int)provider - 1;
+ oa2 = g_list_nth_data (oauth2_providers_list, i);
+
+ debug_print("FROM OAUTH2.C Oauth2 name: %s\n", oa2->oa2_name);
+ debug_print("FROM OAUTH2.C Oauth2 URL: %s\n", oa2->oa2_redirect_uri);
+
+ if(!custom_client_id[0])
+ client_id = g_strdup(oa2->oa2_client_id);
- uri = g_uri_escape_string (custom_client_id ? custom_client_id : client_id, NULL, FALSE);
- *url = g_strconcat ("https://", OAUTH2info[i][OA2_BASE_URL],OAUTH2info[i][OA2_AUTH_RESOURCE], "?client_id=",
+ uri = g_uri_escape_string (custom_client_id[0] ? custom_client_id : client_id, NULL, FALSE);
+ *url = g_strconcat ("https://", oa2->oa2_base_url, oa2->oa2_auth_resource, "?client_id=",
uri, NULL);
g_free(uri);
if (client_id)
g_free(client_id);
- if(OAUTH2info[i][OA2_REDIRECT_URI][0]) {
- uri = g_uri_escape_string (OAUTH2info[i][OA2_REDIRECT_URI], NULL, FALSE);
+ if(oa2->oa2_redirect_uri[0]) {
+ uri = g_uri_escape_string (oa2->oa2_redirect_uri, NULL, FALSE);
tmp = g_strconcat (*url, "&redirect_uri=", uri, NULL);
g_free(*url);
*url = tmp;
g_free(uri);
}
- if(OAUTH2info[i][OA2_RESPONSE_TYPE][0]) {
- uri = g_uri_escape_string (OAUTH2info[i][OA2_RESPONSE_TYPE], NULL, FALSE);
+ if(oa2->oa2_response_type[0]) {
+ uri = g_uri_escape_string (oa2->oa2_response_type, NULL, FALSE);
tmp = g_strconcat (*url, "&response_type=", uri, NULL);
g_free(*url);
*url = tmp;
g_free(uri);
}
- if(OAUTH2info[i][OA2_SCOPE_FOR_AUTH][0]) {
- uri = g_uri_escape_string (OAUTH2info[i][OA2_SCOPE_FOR_AUTH], NULL, FALSE);
+ if(oa2->oa2_scope_for_auth[0]) {
+ uri = g_uri_escape_string (oa2->oa2_scope_for_auth, NULL, FALSE);
tmp = g_strconcat (*url, "&scope=", uri, NULL);
g_free(*url);
*url = tmp;
g_free(uri);
}
- if(OAUTH2info[i][OA2_TENANT][0]) {
- uri = g_uri_escape_string (OAUTH2info[i][OA2_TENANT], NULL, FALSE);
+ if(oa2->oa2_tenant[0]) {
+ uri = g_uri_escape_string (oa2->oa2_tenant, NULL, FALSE);
tmp = g_strconcat (*url, "&tenant=", uri, NULL);
g_free(*url);
*url = tmp;
g_free(uri);
}
- if(OAUTH2info[i][OA2_RESPONSE_MODE][0]) {
- uri = g_uri_escape_string (OAUTH2info[i][OA2_RESPONSE_MODE], NULL, FALSE);
+ if(oa2->oa2_response_mode[0]) {
+ uri = g_uri_escape_string (oa2->oa2_response_mode, NULL, FALSE);
tmp = g_strconcat (*url, "&response_mode=", uri, NULL);
g_free(*url);
*url = tmp;
g_free(uri);
}
- if(OAUTH2info[i][OA2_STATE][0]) {
- uri = g_uri_escape_string (OAUTH2info[i][OA2_STATE], NULL, FALSE);
+ if(oa2->oa2_state[0]) {
+ uri = g_uri_escape_string (oa2->oa2_state, NULL, FALSE);
tmp = g_strconcat (*url, "&state=", uri, NULL);
g_free(*url);
*url = tmp;
@@ -697,38 +892,6 @@ gint oauth2_check_passwds (PrefsAccount *ac_prefs)
return (ret);
}
-/* returns allocated string which must be freed */
-guchar* oauth2_decode(const gchar *in)
-{
- guchar *tmp;
- gsize len;
-
- tmp = g_base64_decode(in, &len);
- passcrypt_decrypt(tmp, len);
- return tmp;
-}
-
-/* For testing */
-void oauth2_encode(const gchar *in)
-{
- guchar *tmp = g_strdup(in);
- guchar *tmp2 = g_strdup(in);
- gchar *result;
- gsize len = strlen(in);
-
- passcrypt_encrypt(tmp, len);
- result = g_base64_encode(tmp, len);
- tmp2 = oauth2_decode(result);
-
- log_message(LOG_PROTOCOL, _("OAuth2 original: %s\n"), in);
- log_message(LOG_PROTOCOL, _("OAuth2 encoded: %s\n"), result);
- log_message(LOG_PROTOCOL, _("OAuth2 decoded: %s\n\n"), tmp2);
-
- g_free(tmp);
- g_free(tmp2);
- g_free(result);
-}
-
gint oauth2_init (OAUTH2Data *OAUTH2Data)
{
OAUTH2Data->refresh_token = NULL;
@@ -741,4 +904,4 @@ gint oauth2_init (OAUTH2Data *OAUTH2Data)
return (0);
}
-#endif /* USE_GNUTLS */
+#endif /* USE_OAUTH2 */
diff --git a/src/oauth2.h b/src/oauth2.h
@@ -29,42 +29,17 @@
#include "passwordstore.h"
#include "smtp.h"
#include "prefs_account.h"
+#include "prefs_gtk.h"
#define OAUTH2BUFSIZE 8192
+#define OAUTH2AUTH_NONE 0
-typedef enum
-{
- OA2_BASE_URL,
- OA2_CLIENT_ID,
- OA2_CLIENT_SECRET,
- OA2_REDIRECT_URI,
- OA2_AUTH_RESOURCE,
- OA2_ACCESS_RESOURCE,
- OA2_REFRESH_RESOURCE,
- OA2_RESPONSE_TYPE,
- OA2_SCOPE_FOR_AUTH,
- OA2_GRANT_TYPE_ACCESS,
- OA2_GRANT_TYPE_REFRESH,
- OA2_TENANT,
- OA2_STATE,
- OA2_ACCESS_TYPE,
- OA2_SCOPE_FOR_ACCESS,
- OA2_RESPONSE_MODE,
- OA2_HEADER_AUTH_BASIC
-} Oauth2Params;
+GList *oauth2_providers_get_list (void);
-typedef enum
-{
- OAUTH2AUTH_NONE,
- OAUTH2AUTH_GOOGLE,
- OAUTH2AUTH_OUTLOOK,
- OAUTH2AUTH_EXCHANGE,
- OAUTH2AUTH_MICROSOFT_GCCHIGH,
- OAUTH2AUTH_YAHOO,
- OAUTH2AUTH_LAST = OAUTH2AUTH_YAHOO
-} Oauth2Service;
+typedef int Oauth2Service;
typedef struct _OAUTH2Data OAUTH2Data;
+
struct _OAUTH2Data
{
gchar *refresh_token;
@@ -80,7 +55,34 @@ gint oauth2_check_passwds (PrefsAccount *ac_prefs);
gint oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const gchar *authcode);
gint oauth2_authorisation_url (Oauth2Service provider, gchar **url, const gchar *custom_client_id);
gint oauth2_use_refresh_token (Oauth2Service provider, OAUTH2Data *OAUTH2Data);
-guchar* oauth2_decode(const gchar *in);
-void oauth2_encode(const gchar *in);
-#endif /* USE_GNUTLS */
+struct _Oauth2Info
+{
+ gchar *oa2_name;
+ gchar *oa2_base_url;
+ gchar *oa2_client_id;
+ gchar *oa2_client_secret;
+ gchar *oa2_redirect_uri;
+ gchar *oa2_auth_resource;
+ gchar *oa2_access_resource;
+ gchar *oa2_refresh_resource;
+ gchar *oa2_response_type;
+ gchar *oa2_scope_for_auth;
+ gchar *oa2_grant_type_access;
+ gchar *oa2_grant_type_refresh;
+ gchar *oa2_tenant;
+ gchar *oa2_state;
+ gchar *oa2_access_type;
+ gchar *oa2_scope_for_access;
+ gchar *oa2_response_mode;
+ gchar *oa2_header_auth_basic;
+ gint oa2_two_stage_pop;
+ gchar *oa2_codemarker_start;
+ gchar *oa2_codemarker_stop;
+};
+
+typedef struct _Oauth2Info Oauth2Info;
+
+void account_read_oauth2_all (void);
+
+#endif /* USE_OAUTH2 */
diff --git a/src/pop.c b/src/pop.c
@@ -42,6 +42,10 @@
#include "file-utils.h"
#include "oauth2.h"
+#ifdef USE_OAUTH2
+#include "defs.h"
+#endif
+
static gint pop3_greeting_recv (Pop3Session *session,
const gchar *msg);
static gint pop3_getauth_user_send (Pop3Session *session);
@@ -243,13 +247,11 @@ static gint pop3_getauth_oauth2_send_microsoft_2(Pop3Session *session)
static gint pop3_getauth_oauth2_send(Pop3Session *session)
{
- gint oauth2_provider = session->ac_prefs->oauth2_provider;
- return ( oauth2_provider == OAUTH2AUTH_OUTLOOK ||
- oauth2_provider == OAUTH2AUTH_EXCHANGE ||
- oauth2_provider == OAUTH2AUTH_MICROSOFT_GCCHIGH
- ? pop3_getauth_oauth2_send_microsoft_1(session)
- : pop3_getauth_oauth2_send_generic(session)
- );
+ gint two_stage_pop = session->two_stage_pop;
+ return ( two_stage_pop ?
+ pop3_getauth_oauth2_send_microsoft_1(session)
+ : pop3_getauth_oauth2_send_generic(session)
+ );
}
#endif
@@ -463,7 +465,6 @@ static gint pop3_retr_recv(Pop3Session *session, const gchar *data, guint len)
g_free(old_file);
}
}
-
/* drop_ok: 0: success 1: don't receive -1: error */
drop_ok = session->drop_message(session, file);
@@ -633,6 +634,18 @@ Session *pop3_session_new(PrefsAccount *account)
session->error_val = PS_SUCCESS;
session->error_msg = NULL;
+#ifdef USE_OAUTH2
+ if(session->ac_prefs->use_pop_auth && session->ac_prefs->pop_auth_type == POPAUTH_OAUTH2){
+ //Set up for two stage sessions - link provider selected in ac_prefs to the config file
+ GList *oauth2_providers_list = oauth2_providers_get_list();
+ Oauth2Info *oa2;
+
+ oa2 = g_list_nth_data (oauth2_providers_list, session->ac_prefs->oauth2_provider - 1);
+ debug_print("POP - Oauth2 name: %s Two stage POP: %i\n", oa2->oa2_name, oa2->oa2_two_stage_pop);
+ session->two_stage_pop = oa2->oa2_two_stage_pop;
+ }
+#endif
+
return SESSION(session);
}
diff --git a/src/pop.h b/src/pop.h
@@ -131,6 +131,9 @@ struct _Pop3Session
gint cur_total_bytes;
gint cur_total_recv_bytes;
+#ifdef USE_OAUTH2
+ gint two_stage_pop;
+#endif
Pop3MsgInfo *msg;
GHashTable *uidl_table;
diff --git a/src/prefs_account.c b/src/prefs_account.c
@@ -2282,14 +2282,20 @@ static void oauth2_create_widget_func(PrefsPage * _page,
menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(oauth2_auth_optmenu)));
gtk_widget_show (oauth2_auth_optmenu);
gtk_box_pack_start (GTK_BOX (hbox), oauth2_auth_optmenu, FALSE, FALSE, 0);
-
+
COMBOBOX_ADD (menu, _("Select"), NULL);
COMBOBOX_ADD (menu, NULL, 0);
- COMBOBOX_ADD (menu, "Google/Gmail", OAUTH2AUTH_GOOGLE);
- COMBOBOX_ADD (menu, "MS Outlook", OAUTH2AUTH_OUTLOOK);
- COMBOBOX_ADD (menu, "MS Exchange", OAUTH2AUTH_EXCHANGE);
- COMBOBOX_ADD (menu, "MS 365 GCC High", OAUTH2AUTH_MICROSOFT_GCCHIGH);
- COMBOBOX_ADD (menu, "Yahoo", OAUTH2AUTH_YAHOO);
+
+ gint j = 1;
+ GList *oauth2_providers_list = oauth2_providers_get_list();
+ GList *cur;
+ Oauth2Info *oa2;
+ for (cur = oauth2_providers_list; cur != NULL; cur = cur->next) {
+ oa2 = (Oauth2Info *)cur->data;
+ debug_print("Building menu Oauth2 name: %s number: %i\n", oa2->oa2_name, j);
+ COMBOBOX_ADD (menu, oa2->oa2_name, j);
+ j++;
+ }
protocol_optmenu = g_new(struct BasicProtocol, 1);
protocol_optmenu->combobox = oauth2_auth_optmenu;
@@ -2438,9 +2444,6 @@ static void oauth2_create_widget_func(PrefsPage * _page,
}
}
- /* For testing */
- /* oauth2_encode(OAUTH2info[0][OA2_CLIENT_ID]); */
-
}
#endif
diff --git a/src/prefs_gtk.c b/src/prefs_gtk.c
@@ -1009,6 +1009,7 @@ void prefs_prepare_cache(void)
gchar *clawsrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
gchar *folderitemrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FOLDERITEM_RC, NULL);
gchar *accountrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL);
+ gchar *oauth2rc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, OAUTH2_RC, NULL);
if (whole_cache == NULL) {
whole_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
@@ -1018,16 +1019,19 @@ void prefs_prepare_cache(void)
g_free(clawsrc);
g_free(folderitemrc);
g_free(accountrc);
+ g_free(oauth2rc);
return;
}
if (prefs_cache(clawsrc) < 0 ||
prefs_cache(folderitemrc) < 0 ||
- prefs_cache(accountrc) < 0)
+ prefs_cache(accountrc) < 0 ||
+ prefs_cache(oauth2rc) < 0)
prefs_destroy_cache();
g_free(clawsrc);
g_free(folderitemrc);
g_free(accountrc);
+ g_free(oauth2rc);
}
void prefs_destroy_cache(void)