talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

commit d0f3df42c68d9f68576666b61f746c891ff407af
parent 5d552a3e4677d8c92bc86d2d95dd84622afbf2c0
Author: Michael Rasmussen <mir@datanom.net>
Date:   Thu, 25 Feb 2021 23:42:58 +0100

Add oauth2 version 5

Signed-off-by: Michael Rasmussen <mir@datanom.net>

Diffstat:
MAUTHORS | 1+
Msrc/Makefile.am | 2++
Msrc/account.c | 3++-
Msrc/common/smtp.c | 40++++++++++++++++++++++++++++++++++++++++
Msrc/common/smtp.h | 4+++-
Msrc/etpan/imap-thread.c | 4+++-
Msrc/imap.c | 11+++++++++++
Msrc/imap.h | 3++-
Msrc/inc.c | 5+++++
Asrc/oauth2.c | 481+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/oauth2.h | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/passwordstore.h | 4+++-
Msrc/pop.c | 36++++++++++++++++++++++++++++++++++--
Msrc/pop.h | 1+
Msrc/prefs_account.c | 561+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/prefs_account.h | 18+++++++++++++++++-
Msrc/send_message.c | 4++++
17 files changed, 1314 insertions(+), 20 deletions(-)

diff --git a/AUTHORS b/AUTHORS @@ -333,3 +333,4 @@ contributors (in addition to the above; based on Changelog) Jean Delvare Damian Poddebniak Alvar Penning + David Fletcher diff --git a/src/Makefile.am b/src/Makefile.am @@ -178,6 +178,7 @@ claws_mail_SOURCES = \ news.c \ news_gtk.c \ noticeview.c \ + oauth2.c \ partial_download.c \ password.c \ password_gtk.c \ @@ -297,6 +298,7 @@ claws_mailinclude_HEADERS = \ news.h \ news_gtk.h \ noticeview.h \ + oauth2.h \ partial_download.h \ password.h \ password_gtk.h \ diff --git a/src/account.c b/src/account.c @@ -953,7 +953,8 @@ static void account_clone(GtkWidget *widget, gpointer data) ACP_FASSIGN(out_ssl_client_cert_pass); /* receive */ - ACP_FASSIGN(use_apop_auth); + ACP_FASSIGN(use_pop_auth); + ACP_FASSIGN(pop_auth_type); ACP_FASSIGN(rmmail); ACP_FASSIGN(msg_leave_time); ACP_FASSIGN(msg_leave_hour); diff --git a/src/common/smtp.c b/src/common/smtp.c @@ -46,6 +46,7 @@ static gint smtp_starttls(SMTPSession *session); static gint smtp_auth_cram_md5(SMTPSession *session); static gint smtp_auth_login(SMTPSession *session); static gint smtp_auth_plain(SMTPSession *session); +static gint smtp_auth_oauth2(SMTPSession *session); static gint smtp_ehlo(SMTPSession *session); static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg); @@ -175,6 +176,11 @@ static gint smtp_auth(SMTPSession *session) && (session->avail_auth_type & SMTPAUTH_PLAIN) != 0) smtp_auth_plain(session); + else if ((session->forced_auth_type == SMTPAUTH_OAUTH2 + || session->forced_auth_type == 0) + && + (session->avail_auth_type & SMTPAUTH_OAUTH2) != 0) + smtp_auth_oauth2(session); else if (session->forced_auth_type == 0) { log_warning(LOG_PROTOCOL, _("No SMTP AUTH method available\n")); return SM_AUTHFAIL; @@ -317,6 +323,8 @@ static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg) session->avail_auth_type |= SMTPAUTH_CRAM_MD5; if (strcasestr(p, "DIGEST-MD5")) session->avail_auth_type |= SMTPAUTH_DIGEST_MD5; + if (strcasestr(p, "XOAUTH2")) + session->avail_auth_type |= SMTPAUTH_OAUTH2; } if (g_ascii_strncasecmp(p, "SIZE", 4) == 0) { p += 5; @@ -391,6 +399,36 @@ static gint smtp_auth_plain(SMTPSession *session) return SM_OK; } + +static gint smtp_auth_oauth2(SMTPSession *session) +{ + gchar buf[MESSAGEBUFSIZE], *b64buf, *out; + gint len; + + session->state = SMTP_AUTH_OAUTH2; + session->auth_type = SMTPAUTH_OAUTH2; + + memset(buf, 0, sizeof buf); + + /* "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"*/ + /* session->pass contains the OAUTH2 Access Token*/ + len = sprintf(buf, "user=%s\1auth=Bearer %s\1\1", session->user, session->pass); + b64buf = g_base64_encode(buf, len); + out = g_strconcat("AUTH XOAUTH2 ", b64buf, NULL); + g_free(b64buf); + + if (session_send_msg(SESSION(session), out) < 0) { + g_free(out); + return SM_ERROR; + } + + g_free(out); + + log_print(LOG_PROTOCOL, "ESMTP> [AUTH XOAUTH2]\n"); + + return SM_OK; +} + static gint smtp_auth_login(SMTPSession *session) { session->state = SMTP_AUTH; @@ -509,6 +547,7 @@ static gint smtp_session_recv_msg(Session *session, const gchar *msg) case SMTP_AUTH_PLAIN: case SMTP_AUTH_LOGIN_USER: case SMTP_AUTH_LOGIN_PASS: + case SMTP_AUTH_OAUTH2: case SMTP_AUTH_CRAM_MD5: log_print(LOG_PROTOCOL, "ESMTP< %s\n", msg); break; @@ -631,6 +670,7 @@ static gint smtp_session_recv_msg(Session *session, const gchar *msg) break; case SMTP_AUTH_PLAIN: case SMTP_AUTH_LOGIN_PASS: + case SMTP_AUTH_OAUTH2: case SMTP_AUTH_CRAM_MD5: ret = smtp_from(smtp_session); break; diff --git a/src/common/smtp.h b/src/common/smtp.h @@ -55,7 +55,8 @@ typedef enum SMTPAUTH_CRAM_MD5 = 1 << 1, SMTPAUTH_DIGEST_MD5 = 1 << 2, SMTPAUTH_TLS_AVAILABLE = 1 << 3, - SMTPAUTH_PLAIN = 1 << 4 + SMTPAUTH_PLAIN = 1 << 4, + SMTPAUTH_OAUTH2 = 1 << 5 } SMTPAuthType; typedef enum @@ -70,6 +71,7 @@ typedef enum SMTP_AUTH_LOGIN_PASS, SMTP_AUTH_CRAM_MD5, SMTP_AUTH_PLAIN, + SMTP_AUTH_OAUTH2, SMTP_RCPT, SMTP_DATA, SMTP_SEND_DATA, diff --git a/src/etpan/imap-thread.c b/src/etpan/imap-thread.c @@ -999,7 +999,9 @@ static void login_run(struct etpan_thread_op * op) param->type, NULL, NULL, NULL, NULL, param->login, param->password, NULL); - else + else if (!strcmp(param->type, "XOAUTH2")) { + r = mailimap_oauth2_authenticate(param->imap, param->login, param->password); + } else r = mailimap_authenticate(param->imap, param->type, NULL, NULL, NULL, param->login, param->login, diff --git a/src/imap.c b/src/imap.c @@ -72,6 +72,7 @@ #include "main.h" #include "passwordstore.h" #include "file-utils.h" +#include "oauth2.h" typedef struct _IMAPFolder IMAPFolder; typedef struct _IMAPSession IMAPSession; @@ -912,6 +913,9 @@ static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass case IMAP_AUTH_PLAIN: ok = imap_cmd_login(session, user, pass, "PLAIN"); break; + case IMAP_AUTH_OAUTH2: + ok = imap_cmd_login(session, user, pass, "XOAUTH2"); + break; case IMAP_AUTH_LOGIN: ok = imap_cmd_login(session, user, pass, "LOGIN"); break; @@ -928,6 +932,7 @@ static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass "\t DIGEST-MD5 %d\n" "\t SCRAM-SHA-1 %d\n" "\t PLAIN %d\n" + "\t OAUTH2 %d\n" "\t LOGIN %d\n" "\t GSSAPI %d\n", imap_has_capability(session, "ANONYMOUS"), @@ -935,6 +940,7 @@ static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass imap_has_capability(session, "DIGEST-MD5"), imap_has_capability(session, "SCRAM-SHA-1"), imap_has_capability(session, "PLAIN"), + imap_has_capability(session, "XOAUTH2"), imap_has_capability(session, "LOGIN"), imap_has_capability(session, "GSSAPI")); if (imap_has_capability(session, "CRAM-MD5")) @@ -945,6 +951,8 @@ static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass ok = imap_cmd_login(session, user, pass, "SCRAM-SHA-1"); if (ok == MAILIMAP_ERROR_LOGIN && imap_has_capability(session, "PLAIN")) ok = imap_cmd_login(session, user, pass, "PLAIN"); + if (ok == MAILIMAP_ERROR_LOGIN && imap_has_capability(session, "XOAUTH2")) + ok = imap_cmd_login(session, user, pass, "XOAUTH2"); if (ok == MAILIMAP_ERROR_LOGIN && imap_has_capability(session, "LOGIN")) ok = imap_cmd_login(session, user, pass, "LOGIN"); if (ok == MAILIMAP_ERROR_LOGIN && imap_has_capability(session, "GSSAPI")) @@ -1308,6 +1316,9 @@ static gint imap_session_authenticate(IMAPSession *session, gint ok = MAILIMAP_NO_ERROR; g_return_val_if_fail(account->userid != NULL, MAILIMAP_ERROR_BAD_STATE); + if(account->imap_auth_type == IMAP_AUTH_OAUTH2) + oauth2_check_passwds (account); + if (!password_get(account->userid, account->recv_server, "imap", SESSION(session)->port, &acc_pass)) { acc_pass = passwd_store_get_account(account->account_id, diff --git a/src/imap.h b/src/imap.h @@ -31,7 +31,8 @@ typedef enum IMAP_AUTH_DIGEST_MD5 = 1 << 4, IMAP_AUTH_SCRAM_SHA1 = 1 << 5, IMAP_AUTH_PLAIN = 1 << 6, - IMAP_AUTH_LOGIN = 1 << 7 + IMAP_AUTH_LOGIN = 1 << 7, + IMAP_AUTH_OAUTH2 = 1 << 8 } IMAPAuthType; FolderClass *imap_get_class (void); diff --git a/src/inc.c b/src/inc.c @@ -61,6 +61,7 @@ #include "hooks.h" #include "logwindow.h" #include "passwordstore.h" +#include "oauth2.h" extern SessionStats session_stats; @@ -633,6 +634,10 @@ static gint inc_start(IncProgressDialog *inc_dialog) (inc_dialog->dialog->window, NULL, NULL); + if(pop3_session->ac_prefs->use_pop_auth && + pop3_session->ac_prefs->pop_auth_type == POPAUTH_OAUTH2) + oauth2_check_passwds (pop3_session->ac_prefs); + if (password_get(pop3_session->user, pop3_session->ac_prefs->recv_server, "pop3", pop3_get_port(pop3_session), diff --git a/src/oauth2.c b/src/oauth2.c @@ -0,0 +1,481 @@ +/* + * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2020 the Claws Mail team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#include "claws-features.h" +#endif + +#include <glib.h> +#ifdef ENABLE_NLS +#include <glib/gi18n.h> +#else +#define _(a) (a) +#define N_(a) (a) +#endif +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "oauth2.h" +#include "md5.h" +#include "utils.h" +#include "log.h" +#include "time.h" +#include "common/passcrypt.h" + +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 gint oauth2_contact_server (SockInfo *sock, gchar *request, gchar *response); + + +static gint oauth2_post_request (gchar *buf, gchar *host, gchar *resource, gchar *header, gchar *body) +{ + gint len; + + len = strlen(body); + if (header[0]) + return snprintf(buf, OAUTH2BUFSIZE, "POST %s HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: text/html,application/json\r\nContent-Length: %i\r\nHost: %s\r\nConnection: close\r\nUser-Agent: ClawsMail\r\n%s\r\n\r\n%s", resource, len, host, header, body); + else + return snprintf(buf, OAUTH2BUFSIZE, "POST %s HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: text/html,application/json\r\nContent-Length: %i\r\nHost: %s\r\nConnection: close\r\nUser-Agent: ClawsMail\r\n\r\n%s", resource, len, host, body); +} + +static gint oauth2_filter_access (gchar *json, gchar *access_token, gint *expiry) +{ + GMatchInfo *matchInfo; + GRegex *regex; + + regex = g_regex_new ("\"access_token\": ?\"(.*?)\",?", 0, 0, NULL); + g_regex_match (regex, json, 0, &matchInfo); + if (g_match_info_matches (matchInfo)) + g_stpcpy (access_token,g_match_info_fetch (matchInfo, 1)); + else{ + g_match_info_free (matchInfo); + return (-1); + } + + g_match_info_free (matchInfo); + + regex = g_regex_new ("\"expires_in\": ?([0-9]*),?", 0, 0, NULL); + g_regex_match (regex, json, 0, &matchInfo); + if (g_match_info_matches (matchInfo)){ + // Reduce available token life to avoid attempting connections with (near) expired tokens + *expiry = (g_get_real_time () / G_USEC_PER_SEC) + atoi(g_match_info_fetch (matchInfo, 1)) - 120; + }else{ + g_match_info_free (matchInfo); + return (-2); + } + + g_match_info_free (matchInfo); + + return(0); +} + +static gint oauth2_filter_refresh (gchar *json, gchar *refresh_token) +{ + GMatchInfo *matchInfo; + GRegex *regex; + + regex = g_regex_new ("\"refresh_token\": ?\"(.*?)\",?", 0, 0, NULL); + g_regex_match (regex, json, 0, &matchInfo); + if (g_match_info_matches (matchInfo)) + g_stpcpy (refresh_token,g_match_info_fetch (matchInfo, 1)); + else{ + g_match_info_free (matchInfo); + return (-1); + } + + g_match_info_free (matchInfo); + + return(0); +} + +gint oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const gchar *authcode) +{ + gchar *request; + gchar *response; + gchar *body; + gchar *header; + gchar *tmp_hd; + gchar *access_token; + gchar *refresh_token; + gint expiry = 0; + gint ret; + SockInfo *sock; + gchar *client_id; + gchar *client_secret; + + gint i; + + i = (int)provider - 1; + if (i < 0 || i > (OAUTH2AUTH_LAST-1)) + return (1); + + sock = sock_connect(OAUTH2info[i][OA2_BASE_URL], 443); + if(sock == NULL || ssl_init_socket(sock) == FALSE){ + log_message(LOG_PROTOCOL, "OAUTH2 SSL connecion error\n"); + return (1); + } + + sock_set_nonblocking_mode(sock, FALSE); + sock_set_io_timeout(10); + sock->gnutls_priority = "NORMAL:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.1"; + + refresh_token = g_malloc(OAUTH2BUFSIZE+1); + access_token = g_malloc(OAUTH2BUFSIZE+1); + request = g_malloc(OAUTH2BUFSIZE+1); + response = g_malloc(OAUTH2BUFSIZE+1); + body = g_malloc(OAUTH2BUFSIZE+1); + + if(OAUTH2Data->custom_client_id) + client_id = g_strdup(OAUTH2Data->custom_client_id); + else + client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]); + + body = g_strconcat ("client_id=", g_uri_escape_string (client_id, NULL, FALSE), + "&code=",g_uri_escape_string (authcode, NULL, FALSE), NULL); + + if(OAUTH2info[i][OA2_CLIENT_SECRET][0]){ + //Only allow custom client secret if the service provider would usually expect a client secret + if(OAUTH2Data->custom_client_secret) + client_secret = g_strdup(OAUTH2Data->custom_client_secret); + else + client_secret = oauth2_decode(OAUTH2info[i][OA2_CLIENT_SECRET]); + body = g_strconcat (body, "&client_secret=", g_uri_escape_string (client_secret, NULL, FALSE), NULL); + }else{ + client_secret = g_strconcat ("", NULL); + } + + if(OAUTH2info[i][OA2_REDIRECT_URI][0]) + body = g_strconcat (body, "&redirect_uri=",g_uri_escape_string (OAUTH2info[i][OA2_REDIRECT_URI], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_GRANT_TYPE_ACCESS][0]) + body = g_strconcat (body, "&grant_type=", g_uri_escape_string (OAUTH2info[i][OA2_GRANT_TYPE_ACCESS], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_TENANT][0]) + body = g_strconcat (body, "&tenant=", g_uri_escape_string (OAUTH2info[i][OA2_TENANT], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_SCOPE_FOR_ACCESS][0]) + body = g_strconcat (body, "&scope=", g_uri_escape_string (OAUTH2info[i][OA2_SCOPE_FOR_ACCESS], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_STATE][0]) + body = g_strconcat (body, "&state=", g_uri_escape_string (OAUTH2info[i][OA2_STATE], NULL, FALSE), NULL); + + if(OAUTH2info[i][OA2_HEADER_AUTH_BASIC][0]){ + tmp_hd = g_strconcat(client_id, ":", client_secret, NULL); + header = g_strconcat ("Authorization: Basic ", g_base64_encode (tmp_hd, strlen(tmp_hd)), NULL); + g_free(tmp_hd); + }else{ + header = g_strconcat ("", NULL); + } + + oauth2_post_request (request, OAUTH2info[i][OA2_BASE_URL], OAUTH2info[i][OA2_ACCESS_RESOURCE], header, body); + ret = oauth2_contact_server (sock, request, response); + + if(oauth2_filter_access (response, access_token, &expiry) == 0){ + OAUTH2Data->access_token = access_token; + OAUTH2Data->expiry = expiry; + OAUTH2Data->expiry_str = g_strdup_printf ("%i", expiry); + ret = 0; + log_message(LOG_PROTOCOL, "OAUTH2 access token obtained\n"); + }else{ + log_message(LOG_PROTOCOL, "OAUTH2 access token not obtained\n"); + debug_print("OAUTH2 - request: %s\n Response: %s", request, response); + ret = 1; + } + + if(oauth2_filter_refresh (response, refresh_token) == 0){ + OAUTH2Data->refresh_token = refresh_token; + log_message(LOG_PROTOCOL, "OAUTH2 refresh token obtained\n"); + }else{ + log_message(LOG_PROTOCOL, "OAUTH2 refresh token not obtained\n"); + } + + sock_close(sock, TRUE); + g_free(body); + g_free(header); + g_free(request); + g_free(response); + g_free(client_id); + g_free(client_secret); + + return (ret); +} + +gint oauth2_use_refresh_token (Oauth2Service provider, OAUTH2Data *OAUTH2Data) +{ + + gchar *request; + gchar *response; + gchar *body; + gchar *header; + gchar *tmp_hd; + gchar *access_token; + gint expiry = 0; + gint ret; + SockInfo *sock; + gchar *client_id; + gchar *client_secret; + + gint i; + + i = (int)provider - 1; + if (i < 0 || i > (OAUTH2AUTH_LAST-1)) + return (1); + + sock = sock_connect(OAUTH2info[i][OA2_BASE_URL], 443); + if(sock == NULL || ssl_init_socket(sock) == FALSE){ + log_message(LOG_PROTOCOL, "OAUTH2 SSL connecion error\n"); + return (1); + } + + sock_set_nonblocking_mode(sock, FALSE); + sock_set_io_timeout(10); + sock->gnutls_priority = "NORMAL:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.1"; + + access_token = g_malloc(OAUTH2BUFSIZE+1); + request = g_malloc(OAUTH2BUFSIZE+1); + response = g_malloc(OAUTH2BUFSIZE+1); + body = g_malloc(OAUTH2BUFSIZE+1); + + if(OAUTH2Data->custom_client_id) + client_id = g_strdup(OAUTH2Data->custom_client_id); + else + client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]); + + body = g_strconcat ("client_id=", g_uri_escape_string (client_id, NULL, FALSE), + "&refresh_token=",OAUTH2Data->refresh_token, NULL); + + if(OAUTH2info[i][OA2_CLIENT_SECRET][0]){ + //Only allow custom client secret if the service provider would usually expect a client secret + if(OAUTH2Data->custom_client_secret) + client_secret = g_strdup(OAUTH2Data->custom_client_secret); + else + client_secret = oauth2_decode(OAUTH2info[i][OA2_CLIENT_SECRET]); + body = g_strconcat (body, "&client_secret=", g_uri_escape_string (client_secret, NULL, FALSE), NULL); + }else{ + client_secret = g_strconcat ("", NULL); + } + + if(OAUTH2info[i][OA2_GRANT_TYPE_REFRESH][0]) + body = g_strconcat (body, "&grant_type=", g_uri_escape_string (OAUTH2info[i][OA2_GRANT_TYPE_REFRESH], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_SCOPE_FOR_ACCESS][0]) + body = g_strconcat (body, "&scope=", g_uri_escape_string (OAUTH2info[i][OA2_SCOPE_FOR_ACCESS], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_STATE][0]) + body = g_strconcat (body, "&state=", g_uri_escape_string (OAUTH2info[i][OA2_STATE], NULL, FALSE), NULL); + + if(OAUTH2info[i][OA2_HEADER_AUTH_BASIC][0]){ + tmp_hd = g_strconcat(client_id, ":", client_secret, NULL); + header = g_strconcat ("Authorization: Basic ", g_base64_encode (tmp_hd, strlen(tmp_hd)), NULL); + g_free(tmp_hd); + }else{ + header = g_strconcat ("", NULL); + } + + oauth2_post_request (request, OAUTH2info[i][OA2_BASE_URL], OAUTH2info[i][OA2_REFRESH_RESOURCE], header, body); + ret = oauth2_contact_server (sock, request, response); + + if(oauth2_filter_access (response, access_token, &expiry) == 0){ + OAUTH2Data->access_token = access_token; + OAUTH2Data->expiry = expiry; + OAUTH2Data->expiry_str = g_strdup_printf ("%i", expiry); + ret = 0; + log_message(LOG_PROTOCOL, "OAUTH2 access token obtained\n"); + }else{ + log_message(LOG_PROTOCOL, "OAUTH2 access token not obtained\n"); + debug_print("OAUTH2 - request: %s\n Response: %s", request, response); + ret = 1; + } + + debug_print("OAUTH2 - access token: %s\n", access_token); + debug_print("OAUTH2 - access token expiry: %i\n", expiry); + + sock_close(sock, TRUE); + g_free(body); + g_free(header); + g_free(request); + g_free(response); + g_free(client_id); + g_free(client_secret); + + return (ret); +} + +static gint oauth2_contact_server (SockInfo *sock, gchar *request, gchar *response) +{ + gint len; + gint ret; + gchar *token; + gint toread = OAUTH2BUFSIZE; + time_t startplus = time(NULL); + + len = strlen(request); + + startplus += 10; + + if (sock_write (sock, request, len+1) < 0) { + log_message(LOG_PROTOCOL, "OAUTH2 socket write error\n"); + return (1); + } + + token = g_strconcat ("", NULL); + do { + + ret = sock_read (sock, response, OAUTH2BUFSIZE); + if (ret < 0 && errno == EAGAIN) + continue; + if (ret < 0) + break; + if (ret == 0) + break; + + toread -= ret; + token = g_strconcat(token, response, NULL); + } while ((toread > 0) && (time(NULL) < startplus)); + + if(time(NULL) >= startplus) + log_message(LOG_PROTOCOL, "OAUTH2 socket timeout error \n"); + + g_free(token); + + return (0); +} + +gint oauth2_authorisation_url (Oauth2Service provider, gchar **url, const gchar *custom_client_id) +{ + gint i; + const gchar *client_id; + + i = (int)provider - 1; + if (i < 0 || i > (OAUTH2AUTH_LAST-1)) + return (1); + + if(custom_client_id) + client_id = custom_client_id; + else + client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]); + + *url = g_strconcat ("https://", OAUTH2info[i][OA2_BASE_URL],OAUTH2info[i][OA2_AUTH_RESOURCE], "?client_id=", + g_uri_escape_string (client_id, NULL, FALSE), NULL); + + if(OAUTH2info[i][OA2_REDIRECT_URI][0]) + *url = g_strconcat (*url, "&redirect_uri=", g_uri_escape_string (OAUTH2info[i][OA2_REDIRECT_URI], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_RESPONSE_TYPE][0]) + *url = g_strconcat (*url, "&response_type=",g_uri_escape_string (OAUTH2info[i][OA2_RESPONSE_TYPE], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_SCOPE_FOR_AUTH][0]) + *url = g_strconcat (*url, "&scope=", g_uri_escape_string (OAUTH2info[i][OA2_SCOPE_FOR_AUTH], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_TENANT][0]) + *url = g_strconcat (*url, "&tenant=", g_uri_escape_string (OAUTH2info[i][OA2_TENANT], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_RESPONSE_MODE][0]) + *url = g_strconcat (*url, "&response_mode=", g_uri_escape_string (OAUTH2info[i][OA2_RESPONSE_MODE], NULL, FALSE), NULL); + if(OAUTH2info[i][OA2_STATE][0]) + *url = g_strconcat (*url, "&state=", g_uri_escape_string (OAUTH2info[i][OA2_STATE], NULL, FALSE), NULL); + + return (0); +} + +gint oauth2_check_passwds (PrefsAccount *ac_prefs) +{ + gchar *uid = g_strdup_printf("%d", ac_prefs->account_id); + gint expiry; + OAUTH2Data *OAUTH2Data = g_malloc(sizeof(* OAUTH2Data)); + gint ret; + + oauth2_init (OAUTH2Data); + + if (ac_prefs->oauth2_use_custom_id) { + OAUTH2Data->custom_client_id = ac_prefs->oauth2_cust_client_id; + OAUTH2Data->custom_client_secret = ac_prefs->oauth2_cust_client_secret; + } + + if(passwd_store_has_password(PWS_ACCOUNT, uid, PWS_ACCOUNT_OAUTH2_EXPIRY)) { + expiry = atoi(passwd_store_get_account(ac_prefs->account_id, PWS_ACCOUNT_OAUTH2_EXPIRY)); + if (expiry > (g_get_real_time () / G_USEC_PER_SEC)){ + g_free(OAUTH2Data); + log_message(LOG_PROTOCOL, "OAUTH2 access token still fresh\n"); + return (0); + } + } + + if(passwd_store_has_password(PWS_ACCOUNT, uid, PWS_ACCOUNT_OAUTH2_REFRESH)) { + log_message(LOG_PROTOCOL, "OAUTH2 obtaining access token using refresh token\n"); + OAUTH2Data->refresh_token = passwd_store_get_account(ac_prefs->account_id, PWS_ACCOUNT_OAUTH2_REFRESH); + ret = oauth2_use_refresh_token (ac_prefs->oauth2_provider, OAUTH2Data); + }else if (passwd_store_has_password(PWS_ACCOUNT, uid, PWS_ACCOUNT_OAUTH2_AUTH)) { + log_message(LOG_PROTOCOL, "OAUTH2 trying for fresh access token with auth code\n"); + ret = oauth2_obtain_tokens (ac_prefs->oauth2_provider, OAUTH2Data, + passwd_store_get_account(ac_prefs->account_id, PWS_ACCOUNT_OAUTH2_AUTH)); + }else{ + ret = 1; + } + + if (ret){ + log_message(LOG_PROTOCOL, "OAUTH2 access token not obtained\n"); + }else{ + passwd_store_set_account(ac_prefs->account_id, PWS_ACCOUNT_RECV, OAUTH2Data->access_token, FALSE); + passwd_store_set_account(ac_prefs->account_id, PWS_ACCOUNT_SEND, OAUTH2Data->access_token, FALSE); + passwd_store_set_account(ac_prefs->account_id, PWS_ACCOUNT_OAUTH2_EXPIRY, OAUTH2Data->expiry_str, FALSE); + log_message(LOG_PROTOCOL, "OAUTH2 access token updated\n"); + } + + g_free(OAUTH2Data); + + 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; + OAUTH2Data->access_token = NULL; + OAUTH2Data->expiry_str = NULL; + OAUTH2Data->expiry = 0; + OAUTH2Data->custom_client_id = NULL; + OAUTH2Data->custom_client_secret = NULL; + + return (0); +} diff --git a/src/oauth2.h b/src/oauth2.h @@ -0,0 +1,156 @@ +/* + * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2020 the Claws Mail team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "claws-features.h" +#endif + +#include <glib.h> + +#include "socket.h" +#include "passwordstore.h" +#include "smtp.h" +#include "prefs_account.h" + +#define OAUTH2BUFSIZE 8192 + +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; + +//Yahoo requires token requests to send POST header Authorization: Basic +//where the password is Base64 encoding of client_id:client_secret + +static gchar *OAUTH2info[4][17]={ + {"accounts.google.com", + "G/jjil7/XHfv4mw90hhhFy5hRci8NeOF3w7QtX8hb9yljE+mU0/MvGk3G4RoUWK13phSIZ7+JSSg4R2f1RV2NbaT5DODMMt5", + "cABm8Lx5PgnrUOOwNJSamcG8Nlj8g8go", + "urn:ietf:wg:oauth:2.0:oob", + "/o/oauth2/auth", + "/o/oauth2/token", + "/o/oauth2/token", + "code", + "https://mail.google.com", + "authorization_code", + "refresh_token", + "", + "", + "", + "", + "", + ""}, + {"login.microsoftonline.com", + "Srm4tajDIHKiu25KIxOlaqei+AJ8q/DPT7PNOhskKrzIjlGT", + "", + "https://login.microsoftonline.com/common/oauth2/nativeclient", + "/common/oauth2/v2.0/authorize", + "/common/oauth2/v2.0/token", + "/common/oauth2/v2.0/token", + "code", + "wl.imap offline_access", + "authorization_code", + "refresh_token", + "common", + "", + "offline", + "wl.imap offline_access", + "fragment", + ""}, + {"login.microsoftonline.com", + "Srm4tajDIHKiu25KIxOlaqei+AJ8q/DPT7PNOhskKrzIjlGT", + "", + "https://login.microsoftonline.com/common/oauth2/nativeclient", + "/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", + "fragment", + ""}, + {"api.login.yahoo.com", + "TTzJciHB9+id6C5eZ1lhRQJVGy8GNYh+iXh8nhiD3cofx5zi4xHLN7Y/IWASKh4Oy7cghOQCs8Q1kmKB2xRWlKP8/fFNXSBFNYpni83PHGUUKgbTYJUz+3/nLLOJASYf", + "T/PyRkrw/ByaZ8mkn6aISpsXhci/fieo+ibj1aRkkqhUKqPKeeH7Xg==", + "oob", + "/oauth2/request_auth", + "/oauth2/get_token", + "/oauth2/get_token", + "code", + "", + "authorization_code", + "refresh_token", + "", + "", + "", + "", + "", + "1"} +}; + +typedef enum +{ + OAUTH2AUTH_NONE, + OAUTH2AUTH_GOOGLE, + OAUTH2AUTH_OUTLOOK, + OAUTH2AUTH_EXCHANGE, + OAUTH2AUTH_YAHOO, + OAUTH2AUTH_LAST = OAUTH2AUTH_YAHOO +} Oauth2Service; + + +typedef struct _OAUTH2Data OAUTH2Data; +struct _OAUTH2Data +{ + gchar *refresh_token; + gchar *access_token; + gint expiry; + gchar *expiry_str; + gchar *custom_client_id; + gchar *custom_client_secret; +}; + +gint oauth2_init (OAUTH2Data *OAUTH2Data); +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); diff --git a/src/passwordstore.h b/src/passwordstore.h @@ -93,7 +93,9 @@ gboolean passwd_store_has_password_account(gint account_id, #define PWS_ACCOUNT_RECV_CERT "recv_cert" #define PWS_ACCOUNT_SEND_CERT "send_cert" #define PWS_ACCOUNT_PROXY_PASS "proxy_pass" - +#define PWS_ACCOUNT_OAUTH2_AUTH "oauth2_auth" +#define PWS_ACCOUNT_OAUTH2_REFRESH "oauth2_refresh" +#define PWS_ACCOUNT_OAUTH2_EXPIRY "oauth2_access_expiry" #define PWS_CORE_PROXY "proxy" #define PWS_CORE_PROXY_PASS "proxy_pass" diff --git a/src/pop.c b/src/pop.c @@ -178,6 +178,31 @@ static gint pop3_getauth_apop_send(Pop3Session *session) return PS_SUCCESS; } +static gint pop3_getauth_oauth2_send(Pop3Session *session) +{ + gchar buf[MESSAGEBUFSIZE], *b64buf, *out; + gint len; + + cm_return_val_if_fail(session->user != NULL, -1); + cm_return_val_if_fail(session->pass != NULL, -1); + + session->state = POP3_GETAUTH_OAUTH2; + memset(buf, 0, sizeof buf); + + /* "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"*/ + /* session->pass contains the OAUTH2 Access Token*/ + len = sprintf(buf, "user=%s\1auth=Bearer %s\1\1", session->user, session->pass); + b64buf = g_base64_encode(buf, len); + out = g_strconcat("AUTH XOAUTH2 ", b64buf, NULL); + g_free(b64buf); + + pop3_gen_send(session, "%s", out); + /* Any error response contains base64({JSON-Body}) containing three values: status, schemes, and scope */ + /* This could be dealt with but is currently written to the log in a fairly graceful fail - not crucial */ + g_free(out); + return PS_SUCCESS; +} + static gint pop3_getrange_stat_send(Pop3Session *session) { session->state = POP3_GETRANGE_STAT; @@ -508,6 +533,8 @@ static void pop3_gen_send(Pop3Session *session, const gchar *format, ...) if (!g_ascii_strncasecmp(buf, "PASS ", 5)) log_print(LOG_PROTOCOL, "POP> PASS ********\n"); + else if (!g_ascii_strncasecmp(buf, "AUTH XOAUTH2 ", 13)) + log_print(LOG_PROTOCOL, "POP> AUTH XOAUTH2 ********\n"); else log_print(LOG_PROTOCOL, "POP> %s\n", buf); @@ -964,8 +991,10 @@ static gint pop3_session_recv_msg(Session *session, const gchar *msg) val = pop3_stls_send(pop3_session); else #endif - if (pop3_session->ac_prefs->use_apop_auth) + if (pop3_session->ac_prefs->use_pop_auth && pop3_session->ac_prefs->pop_auth_type == POPAUTH_APOP) val = pop3_getauth_apop_send(pop3_session); + else if (pop3_session->ac_prefs->use_pop_auth && pop3_session->ac_prefs->pop_auth_type == POPAUTH_OAUTH2) + val = pop3_getauth_oauth2_send(pop3_session); else val = pop3_getauth_user_send(pop3_session); break; @@ -973,8 +1002,10 @@ static gint pop3_session_recv_msg(Session *session, const gchar *msg) case POP3_STLS: if (pop3_stls_recv(pop3_session) != PS_SUCCESS) return -1; - if (pop3_session->ac_prefs->use_apop_auth) + if (pop3_session->ac_prefs->use_pop_auth && pop3_session->ac_prefs->pop_auth_type == POPAUTH_APOP) val = pop3_getauth_apop_send(pop3_session); + else if (pop3_session->ac_prefs->use_pop_auth && pop3_session->ac_prefs->pop_auth_type == POPAUTH_OAUTH2) + val = pop3_getauth_oauth2_send(pop3_session); else val = pop3_getauth_user_send(pop3_session); break; @@ -984,6 +1015,7 @@ static gint pop3_session_recv_msg(Session *session, const gchar *msg) break; case POP3_GETAUTH_PASS: case POP3_GETAUTH_APOP: + case POP3_GETAUTH_OAUTH2: if (!pop3_session->pop_before_smtp) val = pop3_getrange_stat_send(pop3_session); else diff --git a/src/pop.h b/src/pop.h @@ -53,6 +53,7 @@ typedef enum { POP3_GETAUTH_USER, POP3_GETAUTH_PASS, POP3_GETAUTH_APOP, + POP3_GETAUTH_OAUTH2, POP3_GETRANGE_STAT, POP3_GETRANGE_LAST, POP3_GETRANGE_UIDL, diff --git a/src/prefs_account.c b/src/prefs_account.c @@ -54,6 +54,8 @@ #include "colorlabel.h" #include "smtp.h" #include "imap.h" +#include "pop.h" +#include "oauth2.h" #include "remotefolder.h" #include "combobox.h" #include "setup.h" @@ -64,6 +66,9 @@ #include "ssl_certificate.h" #include "passwordstore.h" #include "file-utils.h" +#ifdef USE_GNUTLS +#include <gnutls/gnutls.h> +#endif static gboolean cancelled; static gboolean new_account; @@ -137,7 +142,8 @@ typedef struct ReceivePage GtkWidget *vbox; GtkWidget *pop3_frame; - GtkWidget *use_apop_checkbtn; + GtkWidget *pop_auth_checkbtn; + GtkWidget *pop_auth_type_optmenu; GtkWidget *rmmail_checkbtn; GtkWidget *leave_time_spinbtn; GtkWidget *leave_hour_spinbtn; @@ -195,6 +201,31 @@ typedef struct SendPage GtkWidget *pop_auth_minutes_lbl; } SendPage; +typedef struct Oauth2Page +{ + PrefsPage page; + + GtkWidget *vbox; + GtkWidget *oauth2_sensitive; + + GtkWidget *oauth2_authorise_btn; + GtkWidget *oauth2_deauthorise_btn; + GtkWidget *oauth2_authcode_entry; + GtkWidget *oauth2_auth_optmenu; + GtkWidget *oauth2_link_button; + gpointer *protocol_optmenu; + GtkWidget *oauth2_customid_checkbtn; + GtkWidget *oauth2_cust_client_id_entry; + GtkWidget *oauth2_cust_client_secret_entry; + +} Oauth2Page; + +typedef struct +{ + gchar *auth_uri; + GtkWidget *entry; +} AuthCodeQueryButtonData; + typedef struct ComposePage { PrefsPage page; @@ -343,6 +374,7 @@ typedef struct AdvancedPage static BasicPage basic_page; static ReceivePage receive_page; static SendPage send_page; +static Oauth2Page oauth2_page; static ComposePage compose_page; static TemplatesPage templates_page; static PrivacyPage privacy_page; @@ -382,7 +414,18 @@ static void prefs_account_imap_auth_type_set_optmenu (PrefParam *pparam); static void prefs_account_smtp_auth_type_set_data_from_optmenu (PrefParam *pparam); static void prefs_account_smtp_auth_type_set_optmenu (PrefParam *pparam); +static void prefs_account_pop_auth_type_set_data_from_optmenu + (PrefParam *pparam); +static void prefs_account_pop_auth_type_set_optmenu (PrefParam *pparam); +static void prefs_account_oauth2_provider_set_data_from_optmenu + (PrefParam *pparam); +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_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_set_autochk_interval_from_widgets(PrefParam *pparam); static void prefs_account_set_autochk_interval_to_widgets(PrefParam *pparam); @@ -474,10 +517,15 @@ static PrefParam basic_param[] = { }; static PrefParam receive_param[] = { - {"use_apop_auth", "FALSE", &tmp_ac_prefs.use_apop_auth, P_BOOL, - &receive_page.use_apop_checkbtn, + {"use_pop_auth", "FALSE", &tmp_ac_prefs.use_pop_auth, P_BOOL, + &receive_page.pop_auth_checkbtn, prefs_set_data_from_toggle, prefs_set_toggle}, + {"pop_auth_method", "0", &tmp_ac_prefs.pop_auth_type, P_ENUM, + &receive_page.pop_auth_type_optmenu, + prefs_account_pop_auth_type_set_data_from_optmenu, + prefs_account_pop_auth_type_set_optmenu}, + {"remove_mail", "TRUE", &tmp_ac_prefs.rmmail, P_BOOL, &receive_page.rmmail_checkbtn, prefs_set_data_from_toggle, prefs_set_toggle}, @@ -601,6 +649,31 @@ static PrefParam send_param[] = { {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL} }; +static PrefParam oauth2_param[] = { + {"oauth2_auth_provider", "0", &tmp_ac_prefs.oauth2_provider, P_ENUM, + &oauth2_page.oauth2_auth_optmenu, + prefs_account_oauth2_provider_set_data_from_optmenu, + prefs_account_oauth2_provider_set_optmenu}, + + {"oauth2_date", 0, &tmp_ac_prefs.oauth2_date, P_INT, + NULL, NULL, NULL}, + + {"oauth2_authcode", NULL, &tmp_ac_prefs.oauth2_authcode, P_PASSWORD, + NULL, NULL, NULL}, + + {"oauth2_use_custom_id", "FALSE", &tmp_ac_prefs.oauth2_use_custom_id, P_BOOL, + &oauth2_page.oauth2_customid_checkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"oauth2_cust_client_id", NULL, &tmp_ac_prefs.oauth2_cust_client_id, P_STRING, + &oauth2_page.oauth2_cust_client_id_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"oauth2_cust_client_secret", NULL, &tmp_ac_prefs.oauth2_cust_client_secret, P_STRING, + &oauth2_page.oauth2_cust_client_secret_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL} +}; + static PrefParam compose_param[] = { {"signature_type", "0", &tmp_ac_prefs.sig_type, P_ENUM, &compose_page.sigfile_radiobtn, @@ -1479,10 +1552,10 @@ static void receive_create_widget_func(PrefsPage * _page, ReceivePage *page = (ReceivePage *) _page; PrefsAccount *ac_prefs = (PrefsAccount *) data; - GtkWidget *vbox1, *vbox2, *vbox3, *vbox4; - GtkWidget *hbox1, *hbox2; + GtkWidget *vbox1, *vbox2, *vbox3, *vbox4, *vbox5; + GtkWidget *hbox1, *hbox2, *hbox3; GtkWidget *frame1; - GtkWidget *use_apop_checkbtn; + GtkWidget *pop_auth_checkbtn; GtkWidget *rmmail_checkbtn; GtkWidget *hbox_spc; GtkWidget *leave_time_label; @@ -1516,8 +1589,8 @@ static void receive_create_widget_func(PrefsPage * _page, GtkObject *adj; struct AutocheckWidgets *autochk_widgets; - GtkWidget *optmenu; - GtkListStore *menu; + GtkWidget *optmenu, *optmenu2; + GtkListStore *menu, *menu2; GtkTreeIter iter; GtkWidget *recvatgetall_checkbtn; @@ -1557,8 +1630,38 @@ static void receive_create_widget_func(PrefsPage * _page, local_inbox_entry); vbox2 = gtkut_get_options_frame(vbox1, &frame1, _("POP")); - PACK_CHECK_BUTTON (vbox2, use_apop_checkbtn, - _("Use secure authentication (APOP)")); + + PACK_CHECK_BUTTON (vbox2, pop_auth_checkbtn, + _("Use secure POP authentication")); + + vbox5 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox5); + gtk_box_pack_start (GTK_BOX (vbox2), vbox5, FALSE, FALSE, 0); + + hbox3 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox3); + gtk_box_pack_start (GTK_BOX (vbox5), hbox3, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox3), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, -1); + + label = gtk_label_new (_("Authentication method")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox3), label, FALSE, FALSE, 0); + + optmenu2 = gtkut_sc_combobox_create(NULL, FALSE); + menu2 = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu2))); + gtk_widget_show (optmenu2); + gtk_box_pack_start (GTK_BOX (hbox3), optmenu2, FALSE, FALSE, 0); + + COMBOBOX_ADD (menu2, _("Select"), 0); + COMBOBOX_ADD (menu2, NULL, 0); + COMBOBOX_ADD (menu2, "APOP", POPAUTH_APOP); + COMBOBOX_ADD (menu2, "OAUTH2", POPAUTH_OAUTH2); + + SET_TOGGLE_SENSITIVITY (pop_auth_checkbtn, vbox5); PACK_CHECK_BUTTON (vbox2, rmmail_checkbtn, _("Remove messages on server when received")); @@ -1695,6 +1798,7 @@ static void receive_create_widget_func(PrefsPage * _page, COMBOBOX_ADD (menu, "SCRAM-SHA-1", IMAP_AUTH_SCRAM_SHA1); COMBOBOX_ADD (menu, "PLAIN", IMAP_AUTH_PLAIN); COMBOBOX_ADD (menu, "LOGIN", IMAP_AUTH_LOGIN); + COMBOBOX_ADD (menu, "OAUTH2", IMAP_AUTH_OAUTH2); hbox1 = gtk_hbox_new (FALSE, 8); gtk_widget_show (hbox1); @@ -1805,7 +1909,8 @@ static void receive_create_widget_func(PrefsPage * _page, _("'Get Mail' checks for new messages on this account")); page->pop3_frame = frame1; - page->use_apop_checkbtn = use_apop_checkbtn; + page->pop_auth_checkbtn = pop_auth_checkbtn; + page->pop_auth_type_optmenu = optmenu2; page->rmmail_checkbtn = rmmail_checkbtn; page->leave_time_spinbtn = leave_time_spinbtn; page->leave_hour_spinbtn = leave_hour_spinbtn; @@ -1950,6 +2055,7 @@ static void send_create_widget_func(PrefsPage * _page, COMBOBOX_ADD (menu, "PLAIN", SMTPAUTH_PLAIN); COMBOBOX_ADD (menu, "LOGIN", SMTPAUTH_LOGIN); COMBOBOX_ADD (menu, "CRAM-MD5", SMTPAUTH_CRAM_MD5); + COMBOBOX_ADD (menu, "OAUTH2", SMTPAUTH_OAUTH2); COMBOBOX_ADD (menu, "DIGEST-MD5", SMTPAUTH_DIGEST_MD5); gtk_list_store_set(menu, &iter, COMBOBOX_SENS, FALSE, -1); @@ -2095,6 +2201,217 @@ static void send_create_widget_func(PrefsPage * _page, page->page.widget = vbox1; } +static void oauth2_create_widget_func(PrefsPage * _page, + GtkWindow * window, + gpointer data) +{ + Oauth2Page *page = (Oauth2Page *) _page; + PrefsAccount *ac_prefs = (PrefsAccount *) data; + + GtkWidget *vbox1, *vbox2, *vbox3; + GtkWidget *hbox; + GtkWidget *hbox_spc; + GtkWidget *oauth2_authorise_btn; + GtkWidget *auth_vbox, *auth_frame; + GtkWidget *label; + GtkWidget *oauth2_authcode_entry; + GtkWidget *oauth2_auth_optmenu; + GtkWidget *oauth2_link_button; + GtkWidget *oauth2_customid_checkbtn; + GtkWidget *oauth2_cust_client_id_entry; + GtkWidget *oauth2_cust_client_secret_entry; + GtkWidget *table1; + GtkListStore *menu; + GtkTreeIter iter; + char *buf; + struct BasicProtocol *protocol_optmenu; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + auth_vbox = gtkut_get_options_frame(vbox1, &auth_frame, + _("Authorisation")); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (auth_vbox), hbox, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_widget_set_size_request (hbox_spc, 12, -1); + gtk_box_pack_start (GTK_BOX (hbox), hbox_spc, FALSE, FALSE, 0); + + /* Email service provider */ + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (auth_vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (_("Select OAUTH2 Email Service Provider")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + oauth2_auth_optmenu = gtkut_sc_combobox_create(NULL, FALSE); + 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, "Yahoo", OAUTH2AUTH_YAHOO); + + protocol_optmenu = g_new(struct BasicProtocol, 1); + protocol_optmenu->combobox = oauth2_auth_optmenu; + protocol_optmenu->label = label; + protocol_optmenu->descrlabel = label; + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (auth_vbox), hbox, FALSE, FALSE, 0); + + vbox3 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox3); + gtk_box_pack_start (GTK_BOX (auth_vbox), vbox3, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (vbox3, oauth2_customid_checkbtn, + _("Use custom client details")); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox3), vbox2, FALSE, FALSE, 0); + + table1 = gtk_table_new (3, 2, FALSE); + gtk_widget_show (table1); + gtk_container_add (GTK_CONTAINER (vbox2), table1); + gtk_container_set_border_width (GTK_CONTAINER (table1), 8); + gtk_table_set_row_spacings (GTK_TABLE (table1), VSPACING_NARROW); + gtk_table_set_col_spacings (GTK_TABLE (table1), 8); + + label = gtk_label_new (_("Client ID")); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table1), label, 0, 1, 0, 1, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + + label = gtk_label_new (_("Client secret")); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table1), label, 0, 1, 1, 2, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + + oauth2_cust_client_id_entry = gtk_entry_new (); + gtk_widget_show (oauth2_cust_client_id_entry); + gtk_table_attach (GTK_TABLE (table1), oauth2_cust_client_id_entry, 1, 2, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + oauth2_cust_client_secret_entry = gtk_entry_new (); + gtk_widget_show (oauth2_cust_client_secret_entry); + gtk_table_attach (GTK_TABLE (table1), oauth2_cust_client_secret_entry, 1, 2, 1, 2, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, -1); + + SET_TOGGLE_SENSITIVITY (oauth2_customid_checkbtn, vbox2); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (vbox2), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, 10); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox3), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (_("Obtain authorisation code via a browser")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + oauth2_link_button = gtk_button_new_with_label(_("Click to copy code to clipboard")); + g_signal_connect(G_OBJECT(oauth2_link_button), "clicked", G_CALLBACK(prefs_account_oauth2_copy_url), NULL); + gtk_widget_set_sensitive(oauth2_link_button, TRUE); + gtk_widget_show (oauth2_link_button); + gtk_box_pack_start (GTK_BOX (hbox), oauth2_link_button, FALSE, FALSE, 0); + + /* Authorisation code */ + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox3), hbox, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox, -1, 50); + + label = gtk_label_new (_("Authorisation code")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + oauth2_authcode_entry = gtk_entry_new (); + gtk_widget_show (oauth2_authcode_entry); + gtk_widget_set_size_request (oauth2_authcode_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_box_pack_start (GTK_BOX (hbox), oauth2_authcode_entry, TRUE, TRUE, 0); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox3), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (_("Complete authorisation ")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + oauth2_authorise_btn = gtk_button_new_with_label(_("Authorise")); + gtk_box_pack_start(GTK_BOX(hbox), oauth2_authorise_btn, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(oauth2_authorise_btn), "clicked", + G_CALLBACK(prefs_account_oauth2_obtain_tokens), NULL); + gtk_widget_show(oauth2_authorise_btn); + + /* Add items to page struct */ + + page->oauth2_link_button = oauth2_link_button; + page->oauth2_authcode_entry = oauth2_authcode_entry; + page->oauth2_auth_optmenu = oauth2_auth_optmenu; + page->protocol_optmenu = (gpointer)protocol_optmenu; + page->oauth2_authorise_btn = oauth2_authorise_btn; + page->oauth2_customid_checkbtn = oauth2_customid_checkbtn; + page->oauth2_cust_client_id_entry = oauth2_cust_client_id_entry; + page->oauth2_cust_client_secret_entry = oauth2_cust_client_secret_entry; + page->vbox = vbox1; + page->page.widget = vbox1; + page->oauth2_sensitive = vbox3; + + tmp_ac_prefs = *ac_prefs; + + g_signal_connect(G_OBJECT(oauth2_auth_optmenu), "changed", + G_CALLBACK(prefs_account_oauth2_set_sensitivity), NULL); + g_signal_connect(G_OBJECT(oauth2_authcode_entry), "changed", + G_CALLBACK(prefs_account_oauth2_set_auth_sensitivity), NULL); + + if (new_account) { + prefs_set_dialog_to_default(oauth2_param); + } else { + prefs_set_dialog(oauth2_param); + + /* Passwords are handled outside of PrefParams. */ + buf = passwd_store_get_account(ac_prefs->account_id, + PWS_ACCOUNT_OAUTH2_AUTH); + gtk_entry_set_text(GTK_ENTRY(page->oauth2_authcode_entry), buf != NULL ? buf : ""); + if (buf != NULL) { + memset(buf, 0, strlen(buf)); + g_free(buf); + } + } + + /* For testing */ + /* oauth2_encode(OAUTH2info[0][OA2_CLIENT_ID]); */ + +} + + static void compose_create_widget_func(PrefsPage * _page, GtkWindow * window, gpointer data) @@ -3336,7 +3653,10 @@ static gint prefs_basic_apply(void) PWS_ACCOUNT_RECV, gtk_entry_get_text(GTK_ENTRY(basic_page.pass_entry)), FALSE); - + + /* Manual password change - reset expiry on OAUTH2 tokens*/ + passwd_store_set_account(tmp_ac_prefs.account_id, PWS_ACCOUNT_OAUTH2_EXPIRY, "0", FALSE); + if (protocol == A_IMAP4 || protocol == A_NNTP) { new_id = g_strdup_printf("#%s/%s", protocol == A_IMAP4 ? "imap":"news", @@ -3377,6 +3697,22 @@ static gint prefs_send_apply(void) gtk_entry_get_text(GTK_ENTRY(send_page.smtp_pass_entry)), FALSE); + /* Manual password change - reset expiry on OAUTH2 tokens*/ + passwd_store_set_account(tmp_ac_prefs.account_id, PWS_ACCOUNT_OAUTH2_EXPIRY, "0", FALSE); + + return 0; +} + +static gint prefs_oauth2_apply(void) +{ + prefs_set_data_from_dialog(oauth2_param); + + /* Passwords are stored outside of PrefParams. */ + passwd_store_set_account(tmp_ac_prefs.account_id, + PWS_ACCOUNT_OAUTH2_AUTH, + gtk_entry_get_text(GTK_ENTRY(oauth2_page.oauth2_authcode_entry)), + FALSE); + return 0; } @@ -3477,6 +3813,11 @@ static void send_destroy_widget_func(PrefsPage *_page) /* SendPage *page = (SendPage *) _page; */ } +static void oauth2_destroy_widget_func(PrefsPage *_page) +{ + /* Oauth2Page *page = (Oauth2Page *) _page; */ +} + static void compose_destroy_widget_func(PrefsPage *_page) { /* ComposePage *page = (ComposePage *) _page; */ @@ -3539,6 +3880,16 @@ static gboolean send_can_close_func(PrefsPage *_page) return prefs_send_apply() >= 0; } +static gboolean oauth2_can_close_func(PrefsPage *_page) +{ + Oauth2Page *page = (Oauth2Page *) _page; + + if (!page->page.page_open) + return TRUE; + + return prefs_oauth2_apply() >= 0; +} + static gboolean compose_can_close_func(PrefsPage *_page) { ComposePage *page = (ComposePage *) _page; @@ -3634,6 +3985,17 @@ static void send_save_func(PrefsPage *_page) cancelled = FALSE; } +static void oauth2_save_func(PrefsPage *_page) +{ + Oauth2Page *page = (Oauth2Page *) _page; + + if (!page->page.page_open) + return; + + if (prefs_oauth2_apply() >= 0) + cancelled = FALSE; +} + static void compose_save_func(PrefsPage *_page) { ComposePage *page = (ComposePage *) _page; @@ -3768,6 +4130,24 @@ static void register_send_page(void) prefs_account_register_page((PrefsPage *) &send_page); } +static void register_oauth2_page(void) +{ + static gchar *path[3]; + + path[0] = _("Account"); + path[1] = _("OAUTH2"); + path[2] = NULL; + + oauth2_page.page.path = path; + oauth2_page.page.weight = 1000.0; + oauth2_page.page.create_widget = oauth2_create_widget_func; + oauth2_page.page.destroy_widget = oauth2_destroy_widget_func; + oauth2_page.page.save_page = oauth2_save_func; + oauth2_page.page.can_close = oauth2_can_close_func; + + prefs_account_register_page((PrefsPage *) &oauth2_page); +} + static void register_compose_page(void) { static gchar *path[3]; @@ -3967,6 +4347,7 @@ void prefs_account_init() #endif register_proxy_page(); register_advanced_page(); + register_oauth2_page(); } PrefsAccount *prefs_account_new(void) @@ -3978,6 +4359,7 @@ PrefsAccount *prefs_account_new(void) prefs_set_default(basic_param); prefs_set_default(receive_param); prefs_set_default(send_param); + prefs_set_default(oauth2_param); prefs_set_default(compose_param); prefs_set_default(templates_param); prefs_set_default(privacy_param); @@ -4010,6 +4392,7 @@ PrefsAccount *prefs_account_new_from_config(const gchar *label) prefs_set_default(basic_param); prefs_set_default(receive_param); prefs_set_default(send_param); + prefs_set_default(oauth2_param); prefs_set_default(compose_param); prefs_set_default(templates_param); prefs_set_default(privacy_param); @@ -4021,6 +4404,7 @@ PrefsAccount *prefs_account_new_from_config(const gchar *label) prefs_read_config(basic_param, label, rcpath, NULL); prefs_read_config(receive_param, label, rcpath, NULL); prefs_read_config(send_param, label, rcpath, NULL); + prefs_read_config(oauth2_param, label, rcpath, NULL); prefs_read_config(compose_param, label, rcpath, NULL); prefs_read_config(templates_param, label, rcpath, NULL); prefs_read_config(privacy_param, label, rcpath, NULL); @@ -4157,6 +4541,7 @@ void prefs_account_write_config_all(GList *account_list) WRITE_PARAM(basic_param) WRITE_PARAM(receive_param) WRITE_PARAM(send_param) + WRITE_PARAM(oauth2_param) WRITE_PARAM(compose_param) WRITE_PARAM(templates_param) WRITE_PARAM(privacy_param) @@ -4201,6 +4586,7 @@ void prefs_account_free(PrefsAccount *ac_prefs) prefs_free(basic_param); prefs_free(receive_param); prefs_free(send_param); + prefs_free(oauth2_param); prefs_free(compose_param); prefs_free(templates_param); prefs_free(privacy_param); @@ -4681,6 +5067,157 @@ static void prefs_account_smtp_auth_type_set_optmenu(PrefParam *pparam) combobox_select_by_data(optmenu, type); } +static void prefs_account_pop_auth_type_set_data_from_optmenu(PrefParam *pparam) +{ + *((RecvProtocol *)pparam->data) = + combobox_get_active_data(GTK_COMBO_BOX(*pparam->widget)); +} + +static void prefs_account_pop_auth_type_set_optmenu(PrefParam *pparam) +{ + POPAuthType type = *((POPAuthType *)pparam->data); + GtkComboBox *optmenu = GTK_COMBO_BOX(*pparam->widget); + + combobox_select_by_data(optmenu, type); +} + +static void prefs_account_oauth2_provider_set_data_from_optmenu(PrefParam *pparam) +{ + *((Oauth2Service *)pparam->data) = + combobox_get_active_data(GTK_COMBO_BOX(*pparam->widget)); +} + +static void prefs_account_oauth2_provider_set_optmenu(PrefParam *pparam) +{ + Oauth2Service type = *((Oauth2Service *)pparam->data); + GtkComboBox *optmenu = GTK_COMBO_BOX(*pparam->widget); + + combobox_select_by_data(optmenu, type); +} + +static void prefs_account_oauth2_set_auth_sensitivity(void) +{ + const gchar *authcode = gtk_entry_get_text ((GtkEntry *)oauth2_page.oauth2_authcode_entry); + + if(strlen(authcode) == 0) + gtk_widget_set_sensitive(oauth2_page.oauth2_authorise_btn, FALSE); + else + gtk_widget_set_sensitive(oauth2_page.oauth2_authorise_btn, TRUE); +} + +static void prefs_account_oauth2_set_sensitivity(void) +{ + struct BasicProtocol *protocol_optmenu = (struct BasicProtocol *)oauth2_page.protocol_optmenu; + GtkWidget *optmenu = protocol_optmenu->combobox; + Oauth2Service service; + + service = combobox_get_active_data(GTK_COMBO_BOX(optmenu)); + + if(service == OAUTH2AUTH_NONE) + gtk_widget_set_sensitive(oauth2_page.oauth2_sensitive, FALSE); + else + gtk_widget_set_sensitive(oauth2_page.oauth2_sensitive, TRUE); +} + +static void prefs_account_oauth2_copy_url(GtkButton *button, gpointer data) +{ + struct BasicProtocol *protocol_optmenu = (struct BasicProtocol *)oauth2_page.protocol_optmenu; + + GtkWidget *optmenu = protocol_optmenu->combobox; + + GtkWidget *win; + GtkClipboard *clip, *clip2; + gint len; + gchar *url; + url = g_malloc(OAUTH2BUFSIZE+1); + Oauth2Service service; + const gchar * custom_client_id = NULL; + + service = combobox_get_active_data(GTK_COMBO_BOX(optmenu)); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(oauth2_page.oauth2_customid_checkbtn)) == TRUE) + custom_client_id = gtk_entry_get_text ((GtkEntry *)oauth2_page.oauth2_cust_client_id_entry); + + oauth2_authorisation_url(service, &url, custom_client_id); + + win = gtk_widget_get_toplevel (optmenu); + len = strlen(url); + + clip = gtk_widget_get_clipboard (win, GDK_SELECTION_PRIMARY); + clip2 = gtk_widget_get_clipboard (win, GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clip, url, len); + gtk_clipboard_set_text (clip2, url, len); + + open_uri(url, prefs_common_get_uri_cmd()); + + g_free(url); +} + +static void prefs_account_oauth2_obtain_tokens(GtkButton *button, gpointer data) +{ + struct BasicProtocol *protocol_optmenu = (struct BasicProtocol *)oauth2_page.protocol_optmenu; + + GtkWidget *optmenu = protocol_optmenu->combobox; + Oauth2Service service; + OAUTH2Data *OAUTH2Data = g_malloc(sizeof(* OAUTH2Data)); + const gchar *authcode = gtk_entry_get_text ((GtkEntry *)oauth2_page.oauth2_authcode_entry); + gint ret; + + oauth2_init (OAUTH2Data); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(oauth2_page.oauth2_customid_checkbtn)) == TRUE){ + OAUTH2Data->custom_client_secret = + g_strdup(gtk_entry_get_text ((GtkEntry *)oauth2_page.oauth2_cust_client_secret_entry)); + OAUTH2Data->custom_client_id = + g_strdup(gtk_entry_get_text ((GtkEntry *)oauth2_page.oauth2_cust_client_id_entry)); + } + + service = combobox_get_active_data(GTK_COMBO_BOX(optmenu)); + ret = oauth2_obtain_tokens (service, OAUTH2Data, authcode); + + if(ret){ + g_free(OAUTH2Data); + return; + } + + 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; + + g_free(OAUTH2Data); + +} + static void prefs_account_set_autochk_interval_from_widgets(PrefParam *pparam) { struct AutocheckWidgets *autochk_widgets = diff --git a/src/prefs_account.h b/src/prefs_account.h @@ -41,9 +41,16 @@ typedef enum { SIG_DIRECT } SigType; +typedef enum +{ + POPAUTH_APOP = 1 << 0, + POPAUTH_OAUTH2 = 1 << 1 +} POPAuthType; + #include <glib.h> #include "smtp.h" +#include "pop.h" #include "gtk/prefswindow.h" struct _Folder; @@ -89,7 +96,8 @@ struct _PrefsAccount gboolean use_tls_sni; /* Receive */ - gboolean use_apop_auth; + gboolean use_pop_auth; + POPAuthType pop_auth_type; gboolean rmmail; gint msg_leave_time; gint msg_leave_hour; @@ -127,6 +135,14 @@ struct _PrefsAccount GSList *customhdr_list; + /* OAUTH2 */ + gint oauth2_provider; + gint oauth2_date; + gchar *oauth2_authcode; + gboolean oauth2_use_custom_id; + gchar *oauth2_cust_client_id; + gchar *oauth2_cust_client_secret; + /* Compose */ SigType sig_type; gchar *sig_path; diff --git a/src/send_message.c b/src/send_message.c @@ -57,6 +57,7 @@ #include "log.h" #include "passwordstore.h" #include "file-utils.h" +#include "oauth2.h" typedef struct _SendProgressDialog SendProgressDialog; @@ -298,6 +299,9 @@ gint send_message_smtp_full(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp, g } port = ac_prefs->set_smtpport ? ac_prefs->smtpport : SMTP_PORT; #endif + + if(ac_prefs->use_smtp_auth && ac_prefs->smtp_auth_type == SMTPAUTH_OAUTH2) + oauth2_check_passwds (ac_prefs); if (ac_prefs->use_smtp_auth) { smtp_session->forced_auth_type = ac_prefs->smtp_auth_type;