[Commits] [SCM] claws branch, gtk3, updated. 3.99.0-75-g1f8ea5f92
miras at claws-mail.org
miras at claws-mail.org
Wed Apr 14 22:12:04 CEST 2021
The branch, gtk3 has been updated
via 1f8ea5f92eaf74b4bdbd3a836df69b9fef25160a (commit)
via 9e346e79923edcfef95565c70a930aec1e47161d (commit)
from 31d6518f57b8a5a42e2c0c6d2381e589658a64c2 (commit)
Summary of changes:
AUTHORS | 1 +
src/Makefile.am | 2 +
src/account.c | 3 +-
src/common/smtp.c | 40 ++++
src/common/smtp.h | 4 +-
src/etpan/imap-thread.c | 4 +-
src/imap.c | 11 +
src/imap.h | 3 +-
src/inc.c | 5 +
src/oauth2.c | 604 ++++++++++++++++++++++++++++++++++++++++++++++++
src/oauth2.h | 81 +++++++
src/passwordstore.h | 4 +-
src/pop.c | 36 ++-
src/pop.h | 1 +
src/prefs_account.c | 563 ++++++++++++++++++++++++++++++++++++++++++--
src/prefs_account.h | 18 +-
src/send_message.c | 4 +
17 files changed, 1363 insertions(+), 21 deletions(-)
create mode 100644 src/oauth2.c
create mode 100644 src/oauth2.h
- Log -----------------------------------------------------------------
commit 1f8ea5f92eaf74b4bdbd3a836df69b9fef25160a
Author: Michael Rasmussen <mir at datanom.net>
Date: Wed Apr 14 22:10:52 2021 +0200
fix widget sizing
Signed-off-by: Michael Rasmussen <mir at datanom.net>
diff --git a/src/prefs_account.c b/src/prefs_account.c
index c1f679172..f0b818992 100644
--- a/src/prefs_account.c
+++ b/src/prefs_account.c
@@ -1599,15 +1599,16 @@ static void receive_create_widget_func(PrefsPage * _page,
PACK_CHECK_BUTTON (vbox2, pop_auth_checkbtn,
_("Use secure POP authentication"));
- vbox5 = gtk_vbox_new (FALSE, 0);
+ //vbox5 = gtk_vbox_new (FALSE, 0);
+ vbox5 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
gtk_widget_show (vbox5);
gtk_box_pack_start (GTK_BOX (vbox2), vbox5, FALSE, FALSE, 0);
- hbox3 = gtk_hbox_new (FALSE, 8);
+ hbox3 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_show (hbox3);
gtk_box_pack_start (GTK_BOX (vbox5), hbox3, FALSE, FALSE, 0);
- hbox_spc = gtk_hbox_new (FALSE, 0);
+ hbox_spc = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 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);
@@ -2190,25 +2191,25 @@ static void oauth2_create_widget_func(PrefsPage * _page,
char *buf;
struct BasicProtocol *protocol_optmenu;
- vbox1 = gtk_vbox_new (FALSE, VSPACING);
+ vbox1 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 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);
+ _("Authorization"));
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_show (hbox);
gtk_box_pack_start (GTK_BOX (auth_vbox), hbox, FALSE, FALSE, 0);
- hbox_spc = gtk_hbox_new (FALSE, 0);
+ hbox_spc = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 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);
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_show (hbox);
gtk_box_pack_start (GTK_BOX (auth_vbox), hbox, FALSE, FALSE, 0);
@@ -2233,18 +2234,18 @@ static void oauth2_create_widget_func(PrefsPage * _page,
protocol_optmenu->label = label;
protocol_optmenu->descrlabel = label;
- hbox = gtk_hbox_new (FALSE, 8);
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_show (hbox);
gtk_box_pack_start (GTK_BOX (auth_vbox), hbox, FALSE, FALSE, 0);
- vbox3 = gtk_vbox_new (FALSE, 0);
+ vbox3 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 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);
+ vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_show (vbox2);
gtk_box_pack_start (GTK_BOX (vbox3), vbox2, FALSE, FALSE, 0);
@@ -2279,56 +2280,57 @@ static void oauth2_create_widget_func(PrefsPage * _page,
GTK_EXPAND | GTK_SHRINK | GTK_FILL,
GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
- hbox_spc = gtk_hbox_new (FALSE, 0);
+ hbox_spc = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 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);
+ //gtk_widget_set_size_request (hbox_spc, 12, -1);
SET_TOGGLE_SENSITIVITY (oauth2_customid_checkbtn, vbox2);
- hbox_spc = gtk_hbox_new (FALSE, 0);
+ hbox_spc = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 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);
+ //gtk_widget_set_size_request (hbox_spc, 12, 10);
- hbox = gtk_hbox_new (FALSE, 8);
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 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"));
+ label = gtk_label_new (_("Obtain authorization 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"));
+ oauth2_link_button = gtk_button_new_with_label(_("Click to open default browser with request"));
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);
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_show (hbox);
gtk_box_pack_start (GTK_BOX (vbox3), hbox, FALSE, FALSE, 0);
- gtk_widget_set_size_request (hbox, -1, 50);
+ //gtk_widget_set_size_request (hbox, -1, 50);
- label = gtk_label_new (_("Authorisation code"));
+ label = gtk_label_new (_("Authorization 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_widget_set_size_request (oauth2_authcode_entry, DEFAULT_ENTRY_WIDTH, -1);
+ gtk_widget_set_tooltip_text(oauth2_authcode_entry, _("Paste complete URL from browser or the provided auth token"));
gtk_box_pack_start (GTK_BOX (hbox), oauth2_authcode_entry, TRUE, TRUE, 0);
- hbox = gtk_hbox_new (FALSE, 8);
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_show (hbox);
gtk_box_pack_start (GTK_BOX (vbox3), hbox, FALSE, FALSE, 0);
- label = gtk_label_new (_("Complete authorisation "));
+ label = gtk_label_new (_("Complete authorization "));
gtk_widget_show (label);
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
- oauth2_authorise_btn = gtk_button_new_with_label(_("Authorise"));
+ oauth2_authorise_btn = gtk_button_new_with_label(_("Authorize"));
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);
commit 9e346e79923edcfef95565c70a930aec1e47161d
Author: Michael Rasmussen <mir at datanom.net>
Date: Wed Apr 14 19:35:03 2021 +0200
Add oauth2 authentication - mostly by David Fletcher
Signed-off-by: Michael Rasmussen <mir at datanom.net>
diff --git a/AUTHORS b/AUTHORS
index 6e1720885..5eee7ea45 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -337,3 +337,4 @@ contributors (in addition to the above; based on Changelog)
Steve Randall
Ramin Yaghoubzadeh Torky
Manuel Stoeckl
+ David Fletcher
diff --git a/src/Makefile.am b/src/Makefile.am
index d7929b259..de7efd86f 100644
--- 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
index 1fda175aa..47f031d1a 100644
--- a/src/account.c
+++ b/src/account.c
@@ -952,7 +952,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
index 2460f7e04..b0d91c0cc 100644
--- 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
index 706a0978a..f20bf711c 100644
--- 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
index eeccac793..6586f26bf 100644
--- 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
index 2b6bf3605..c486c471a 100644
--- 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
index 50569e0cf..f100dcb6b 100644
--- 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
index f559ca39c..2e5e1b2b4 100644
--- 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
new file mode 100644
index 000000000..a70eb4538
--- /dev/null
+++ b/src/oauth2.c
@@ -0,0 +1,604 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2021 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"
+
+//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"}
+};
+
+static gchar *OAUTH2CodeMarker[5][2] = {
+ {"",""},
+ {"google_begin_mark","google_end_mark"}, /* Not used since token avalable to user to copy in browser window */
+ {"#code=","&session_state"},
+ {"#code=","&session_state"},
+ {"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 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);
+}
+
+static gchar* oauth2_get_token_from_response(Oauth2Service provider, const gchar* response) {
+ gchar* token = NULL;
+
+ debug_print("Auth response: %s\n", response);
+ if (provider == OAUTH2AUTH_YAHOO || provider == OAUTH2AUTH_GOOGLE) {
+ /* 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]);
+ start += strlen(OAUTH2CodeMarker[provider][0]);
+ if (start == NULL)
+ return NULL;
+ gchar* stop = g_strstr_len(response, strlen(response), OAUTH2CodeMarker[provider][1]);
+ if (stop == NULL)
+ return NULL;
+ token = g_strndup(start, stop - start);
+ }
+
+ return token;
+}
+
+int 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;
+ gchar *token = NULL;
+
+ gint i;
+
+ i = (int)provider - 1;
+ if (i < 0 || i > (OAUTH2AUTH_LAST-1))
+ return (1);
+
+ token = oauth2_get_token_from_response(provider, authcode);
+ debug_print("Auth token: %s\n", token);
+ if (token == NULL) {
+ log_message(LOG_PROTOCOL, "OAUTH2 missing authentication code\n");
+ return (1);
+ }
+
+ sock = sock_connect(OAUTH2info[i][OA2_BASE_URL], 443);
+ if (sock == NULL) {
+ log_message(LOG_PROTOCOL, "OAUTH2 connecion error\n");
+ g_free(token);
+ return (1);
+ }
+ sock->ssl_cert_auto_accept = TRUE;
+ sock_set_nonblocking_mode(sock, FALSE);
+ sock_set_io_timeout(10);
+ sock->gnutls_priority = "NORMAL:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.1";
+ if (ssl_init_socket(sock) == FALSE) {
+ log_message(LOG_PROTOCOL, "OAUTH2 SSL connecion error\n");
+ g_free(token);
+ return (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 (token, NULL, FALSE), NULL);
+ g_free(token);
+
+ if(OAUTH2info[i][OA2_CLIENT_SECRET][0]){
+ //Only allow custom client secret if the service provider would usually expect a client secret
+ 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) {
+ log_message(LOG_PROTOCOL, "OAUTH2 connecion error\n");
+ return (1);
+ }
+ sock->ssl_cert_auto_accept = TRUE;
+ sock_set_nonblocking_mode(sock, FALSE);
+ sock_set_io_timeout(10);
+ sock->gnutls_priority = "NORMAL:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.1";
+ if (ssl_init_socket(sock) == FALSE) {
+ log_message(LOG_PROTOCOL, "OAUTH2 SSL connecion error\n");
+ return (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);
+ if (ac_prefs->use_smtp_auth && ac_prefs->smtp_auth_type == SMTPAUTH_OAUTH2)
+ 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
new file mode 100644
index 000000000..ef910d0e9
--- /dev/null
+++ b/src/oauth2.h
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+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
index b9198207e..125e65773 100644
--- 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
index 2b8c70a6d..0954df85c 100644
--- 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
index ba13e86e3..4fc5257d4 100644
--- 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
index a5fc88a39..c1f679172 100644
--- a/src/prefs_account.c
+++ b/src/prefs_account.c
@@ -53,6 +53,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"
@@ -63,6 +65,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;
@@ -136,7 +141,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;
@@ -194,6 +200,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;
@@ -342,6 +373,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;
@@ -381,7 +413,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);
@@ -473,10 +516,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},
@@ -600,6 +648,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,
@@ -1445,10 +1518,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;
@@ -1482,8 +1555,8 @@ static void receive_create_widget_func(PrefsPage * _page,
GtkAdjustment *adj;
struct AutocheckWidgets *autochk_widgets;
- GtkWidget *optmenu;
- GtkListStore *menu;
+ GtkWidget *optmenu, *optmenu2;
+ GtkListStore *menu, *menu2;
GtkTreeIter iter;
GtkWidget *recvatgetall_checkbtn;
@@ -1523,8 +1596,37 @@ static void receive_create_widget_func(PrefsPage * _page,
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"));
@@ -1660,6 +1762,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_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_show (hbox1);
@@ -1770,7 +1873,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;
@@ -1915,6 +2019,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);
@@ -2059,7 +2164,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)
@@ -3286,7 +3601,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",
@@ -3327,9 +3645,25 @@ 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;
+}
+
static gint prefs_compose_apply(void)
{
prefs_set_data_from_dialog(compose_param);
@@ -3427,6 +3761,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; */
@@ -3489,6 +3828,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;
@@ -3584,6 +3933,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;
@@ -3718,6 +4078,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];
@@ -3917,6 +4295,7 @@ void prefs_account_init()
#endif
register_proxy_page();
register_advanced_page();
+ register_oauth2_page();
}
PrefsAccount *prefs_account_new(void)
@@ -3928,6 +4307,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);
@@ -3960,6 +4340,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);
@@ -3971,6 +4352,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);
@@ -4107,6 +4489,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)
@@ -4151,6 +4534,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);
@@ -4638,6 +5022,157 @@ static void prefs_account_set_autochk_interval_from_widgets(PrefParam *pparam)
GTK_SPIN_BUTTON(autochk_widgets->autochk_sec_spinbtn));
}
+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_to_widgets(PrefParam *pparam)
{
gint val = *((gint *)pparam->data);
diff --git a/src/prefs_account.h b/src/prefs_account.h
index 4c53fea07..88c922eb5 100644
--- 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
index 364fd83df..84a058584 100644
--- 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;
-----------------------------------------------------------------------
hooks/post-receive
--
Claws Mail
More information about the Commits
mailing list