[balsa/oauth2-support] OAuth2 support for outlook.com accounts (see also issue #40)
- From: Albrecht Dreß <albrecht src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa/oauth2-support] OAuth2 support for outlook.com accounts (see also issue #40)
- Date: Sun, 3 Jan 2021 15:51:49 +0000 (UTC)
commit ee75f4cb2f83ef610f597fd52923148a78e6ec9c
Author: Albrecht Dreß <albrecht dress arcor de>
Date: Sun Jan 3 16:52:01 2021 +0100
OAuth2 support for outlook.com accounts (see also issue #40)
Implement OAuth2 authentication for outlook.com accounts, which requires
re-enabling XOAUTH2 authentication (RFC 6749) as Microsoft does not
support OAUTHBEARER (RFC 7628). Further improvements:
- erase stored tokens if the auth server returns an error
- link Soup logging to G_MESSAGES_DEBUG setting
Details:
- libbalsa/imap:
- imap-auth.c, imap-handle.[ch]: support both XOAUTH2 and OAUTHBEARER
- libnetclient:
- net-client-pop.c, net-client-smtp.c, net-client-utils.[ch]: support
both XOAUTH2 and OAUTHBEARER
- libbalsa/oauth2:
add support for Outlook.com accounts; support NULL client secrets; add
methods for erasing stored tokens from Secret service and the private
config file; erase tokens if the auth server returns a 4xy status code;
enable Soup logging iff G_MESSAGES_DEBUG contains "oauth" or "all"
Signed-off-by: Albrecht Dreß <albrecht dress arcor de>
libbalsa/imap/imap-auth.c | 11 ++-
libbalsa/imap/imap-handle.c | 5 +-
libbalsa/imap/imap-handle.h | 3 +-
libbalsa/oauth2.c | 174 +++++++++++++++++++++++++++++++++-------
libnetclient/net-client-pop.c | 32 +++++---
libnetclient/net-client-smtp.c | 30 +++++--
libnetclient/net-client-utils.c | 13 ++-
libnetclient/net-client-utils.h | 10 ++-
8 files changed, 217 insertions(+), 61 deletions(-)
---
diff --git a/libbalsa/imap/imap-auth.c b/libbalsa/imap/imap-auth.c
index 4c55599f9..6dc14d0f8 100644
--- a/libbalsa/imap/imap-auth.c
+++ b/libbalsa/imap/imap-auth.c
@@ -224,7 +224,7 @@ imap_auth_anonymous(ImapMboxHandle* handle)
/* =================================================================== */
-/* SASL OAUTHBEARER (RFC 7628) */
+/* SASL XOAUTH2 (RFC 6749) or OAUTHBEARER (RFC 7628) */
/* =================================================================== */
#if defined(HAVE_OAUTH2)
@@ -238,7 +238,8 @@ getmsg_oauth2(ImapMboxHandle *h, char **retmsg, int *retmsglen)
if ((auth_data == NULL) || (auth_data[0] == NULL) || (auth_data[1] == NULL)) {
result = FALSE;
} else {
- *retmsg = net_client_auth_oauth2_calc(auth_data[0], NET_CLIENT(h->sio), auth_data[1]);
+ *retmsg = net_client_auth_oauth2_calc(auth_data[0], imap_mbox_handle_can_do(h,
IMCAP_AOAUTHBEARER) != 0,
+ NET_CLIENT(h->sio), auth_data[1]);
*retmsglen = strlen(*retmsg);
result = TRUE;
}
@@ -253,7 +254,11 @@ getmsg_oauth2(ImapMboxHandle *h, char **retmsg, int *retmsglen)
static ImapResult
imap_auth_oauth2(ImapMboxHandle* handle)
{
- return imap_auth_sasl(handle, IMCAP_AOAUTH2, "AUTHENTICATE OAUTHBEARER", getmsg_oauth2);
+ if (imap_mbox_handle_can_do(handle, IMCAP_AOAUTHBEARER) != 0) {
+ return imap_auth_sasl(handle, IMCAP_AOAUTHBEARER, "AUTHENTICATE OAUTHBEARER", getmsg_oauth2);
+ } else {
+ return imap_auth_sasl(handle, IMCAP_AXOAUTH2, "AUTHENTICATE XOAUTH2", getmsg_oauth2);
+ }
}
#else
diff --git a/libbalsa/imap/imap-handle.c b/libbalsa/imap/imap-handle.c
index 2f83d739a..71df168b8 100644
--- a/libbalsa/imap/imap-handle.c
+++ b/libbalsa/imap/imap-handle.c
@@ -2107,7 +2107,7 @@ ir_capability_data(ImapMboxHandle *handle)
/* ordered identically as ImapCapability constants */
static const char* capabilities[] = {
"IMAP4", "IMAP4rev1", "STATUS",
- "AUTH=ANONYMOUS", "AUTH=CRAM-MD5", "AUTH=GSSAPI", "AUTH=PLAIN", "AUTH=OAUTHBEARER",
+ "AUTH=ANONYMOUS", "AUTH=CRAM-MD5", "AUTH=GSSAPI", "AUTH=PLAIN", "AUTH=XOAUTH2", "AUTH=OAUTHBEARER",
"ACL", "RIGHTS=", "BINARY", "CHILDREN",
"COMPRESS=DEFLATE",
"ESEARCH", "IDLE", "LITERAL+",
@@ -4489,7 +4489,8 @@ imap_server_probe(const gchar *host, guint timeout_secs, NetClientProbeResult *r
if (imap_mbox_handle_can_do(handle, IMCAP_AGSSAPI) != 0) {
result->auth_mode |= NET_CLIENT_AUTH_KERBEROS;
}
- if (imap_mbox_handle_can_do(handle, IMCAP_AOAUTH2) != 0) {
+ if ((imap_mbox_handle_can_do(handle, IMCAP_AXOAUTH2) != 0) ||
+ (imap_mbox_handle_can_do(handle, IMCAP_AOAUTHBEARER) != 0)) {
result->auth_mode |= NET_CLIENT_AUTH_OAUTH2;
}
retval = TRUE;
diff --git a/libbalsa/imap/imap-handle.h b/libbalsa/imap/imap-handle.h
index 716fb4a1c..f0ec3407e 100644
--- a/libbalsa/imap/imap-handle.h
+++ b/libbalsa/imap/imap-handle.h
@@ -62,7 +62,8 @@ typedef enum
IMCAP_ACRAM_MD5, /* RFC 2195: CRAM-MD5 authentication */
IMCAP_AGSSAPI, /* RFC 1731: GSSAPI authentication */
IMCAP_APLAIN, /* RFC 2595: */
- IMCAP_AOAUTH2, /* RFC 7628: OAUTHBEARER authentication */
+ IMCAP_AXOAUTH2, /* RFC 6749: Google XOAUTH2 authentication */
+ IMCAP_AOAUTHBEARER, /* RFC 7628: OAUTHBEARER authentication */
IMCAP_ACL, /* RFC 2086: IMAP4 ACL extension */
IMCAP_RIGHTS, /* RFC 4314: IMAP4 RIGHTS= extension */
IMCAP_BINARY, /* RFC 3516 */
diff --git a/libbalsa/oauth2.c b/libbalsa/oauth2.c
index 83d60e7ee..334815262 100644
--- a/libbalsa/oauth2.c
+++ b/libbalsa/oauth2.c
@@ -41,10 +41,6 @@
#endif /* defined(HAVE_LIBSECRET) */
-/* define the following to enable verbose HTTP (soup) session logging */
-#define HTTP_VERBOSE 1
-
-
/** Web page template for displaying a message in the web browser that the authorisation code has been
received. */
#define OAUTH_AUTH_DONE_TEMPLATE
\
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n" \
@@ -126,13 +122,27 @@ static const oauth2_provider_t providers[] = {
// FIXME - Thunderbird client id & secret
.id = "yahoo.com",
.display_name = "Yahoo Mail",
- .email_re = "^.*@yahoo\\.[a-z]{2,3}$", //
FIXME - are there more?
+ .email_re = "^.*@yahoo\\.[a-z.]+$", //
FIXME - are there more?
.client_id =
"dj0yJmk9NUtCTWFMNVpTaVJmJmQ9WVdrOVJ6UjVTa2xJTXpRbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD0yYw--",
.client_secret = "f2de6a30ae123cdbc258c15e0812799010d589cc",
.auth_uri = "https://api.login.yahoo.com/oauth2/request_auth",
.token_uri = "https://api.login.yahoo.com/oauth2/get_token",
.scope = "mail-w",
- .oob_mode = TRUE }
+ .oob_mode = TRUE },
+ { /*
+ * Microsoft Accounts: see
+ *
https://docs.microsoft.com/en-us/Exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
+ */
+ .id = "outlook.com",
+ .display_name = "Microsoft",
+ .email_re = "^.*@(outlook|hotmail)(\\.com)?\\.[a-z]{2,3}$", // FIXME - are there
more?
+ .client_id = "04a18be9-b37c-43a4-a58e-9c251ba1eb5b",
+ .client_secret = NULL,
+ .auth_uri = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
+ .token_uri = "https://login.microsoftonline.com/common/oauth2/v2.0/token",
+ .scope = "https://outlook.office.com/IMAP.AccessAsUser.All
https://outlook.office.com/POP.AccessAsUser.All "
+ "https://outlook.office.com/SMTP.Send offline_access",
+ .oob_mode = FALSE }
};
@@ -166,9 +176,11 @@ static const oauth2_provider_t *libbalsa_oauth2_find_provider(const gchar *mailb
#if defined(HAVE_LIBSECRET)
static void load_oauth2_token_libsecret(LibBalsaOauth2 *oauth);
static void save_oauth2_token_libsecret(const LibBalsaOauth2 *oauth);
+static void erase_oauth2_token_libsecret(const LibBalsaOauth2 *oauth);
#endif /* defined(HAVE_LIBSECRET) */
static void load_oauth2_token_config(LibBalsaOauth2 *oauth);
static void save_oauth2_token_config(const LibBalsaOauth2 *oauth);
+static void erase_oauth2_token_config(const LibBalsaOauth2 *oauth);
static gboolean oauth2_refresh(LibBalsaOauth2 *oauth,
GError **error);
@@ -463,6 +475,30 @@ save_oauth2_token_libsecret(const LibBalsaOauth2 *oauth)
}
}
+
+/** @brief Remove OAuth2 tokens from the Secret Service
+ *
+ * @param[in] oauth OAuth2 context
+ *
+ * Remove all information of the passed OAuth2 object from the Secret Service.
+ */
+static void
+erase_oauth2_token_libsecret(const LibBalsaOauth2 *oauth)
+{
+ gboolean result;
+ GError *error = NULL;
+
+ result =
+ secret_password_clear_sync(&oauth2_schema, NULL, &error, "provider", oauth->provider->id,
"user", oauth->account, NULL);
+ if (!result) {
+ g_warning("%s: cannot remove OAuth2 token for %s/%s from secret service: %s", __func__,
oauth->account,
+ oauth->provider->id, (error != NULL) ? error->message : "unknown");
+ g_clear_error(&error);
+ } else {
+ g_debug("%s: removed token for %s/%s from secret service", __func__, oauth->account,
oauth->provider->id);
+ }
+}
+
#endif /* defined(HAVE_LIBSECRET) */
@@ -518,6 +554,27 @@ save_oauth2_token_config(const LibBalsaOauth2 *oauth)
}
+/** @brief Remove OAuth2 tokens from the private config file
+ *
+ * @param[in] oauth OAuth2 context
+ *
+ * Remove all information of the passed OAuth2 object from the local private configuration file.
+ */
+static void
+erase_oauth2_token_config(const LibBalsaOauth2 *oauth)
+{
+ GChecksum *hash;
+ const gchar *group_name;
+
+ hash = g_checksum_new(G_CHECKSUM_SHA256);
+ g_checksum_update(hash, (const guchar *) oauth->provider->id, strlen(oauth->provider->id));
+ g_checksum_update(hash, (const guchar *) oauth->account, strlen(oauth->account));
+ group_name = g_checksum_get_string(hash);
+ libbalsa_conf_private_remove_group(group_name);
+ g_checksum_free(hash);
+}
+
+
/* == OAuth2 token refresh related functions
==================================================================================== */
/** @brief Refresh a OAuth2 access token
@@ -543,12 +600,20 @@ oauth2_refresh(LibBalsaOauth2 *oauth, GError **error)
g_debug("%s: post refresh request for %s: uri=%s id=%s secret=%s token=%s", __func__, oauth->account,
oauth->provider->token_uri, oauth->provider->client_id, oauth->provider->client_secret,
oauth->refresh_token);
session = oauth_soup_session_new();
- message = soup_form_request_new("POST", oauth->provider->token_uri,
- "grant_type", "refresh_token",
- "client_id", oauth->provider->client_id,
- "client_secret", oauth->provider->client_secret,
- "refresh_token", oauth->refresh_token,
- NULL);
+ if (oauth->provider->client_secret != NULL) {
+ message = soup_form_request_new("POST", oauth->provider->token_uri,
+ "grant_type", "refresh_token",
+ "client_id", oauth->provider->client_id,
+ "client_secret", oauth->provider->client_secret,
+ "refresh_token", oauth->refresh_token,
+ NULL);
+ } else {
+ message = soup_form_request_new("POST", oauth->provider->token_uri,
+ "grant_type", "refresh_token",
+ "client_id", oauth->provider->client_id,
+ "refresh_token", oauth->refresh_token,
+ NULL);
+ }
status = soup_session_send_message(session, message);
g_debug("%s: status=%u, code=%u, reason=%s", __func__, status, message->status_code,
message->reason_phrase);
@@ -556,6 +621,17 @@ oauth2_refresh(LibBalsaOauth2 *oauth, GError **error)
oauth->access_token = NULL;
if ((status != SOUP_STATUS_OK) || (message->status_code != SOUP_STATUS_OK) || (message->response_body
== NULL)) {
eval_json_error(_("OAuth2 refresh token request failed"), message, error);
+ /* a 4xy status probably means that we must re-authorise, so erase the refresh token in this
case */
+ if ((status / 100U) == 4U) {
+ g_free(oauth->access_token);
+ oauth->access_token = NULL;
+ g_free(oauth->refresh_token);
+ oauth->refresh_token = NULL;
+#if defined(HAVE_LIBSECRET)
+ erase_oauth2_token_libsecret(oauth);
+#endif
+ erase_oauth2_token_config(oauth);
+ }
result = FALSE;
} else {
result = eval_json_auth_reply(oauth, message, FALSE, error);
@@ -908,6 +984,8 @@ oauth2_authorize_finish(oauth2_auth_ctx_t *auth_ctx, const gchar *auth_code)
SoupSession *session;
SoupMessage *message;
guint status;
+ gboolean success = FALSE;
+ GtkWidget *icon;
gtk_label_set_label(GTK_LABEL(auth_ctx->spinner_label), _("sending authorization code…"));
g_debug("%s: uri=%s, client_id=%s, client_secret=%s code=%s listen_uri=%s", __func__,
provider->token_uri, provider->client_id,
@@ -915,16 +993,26 @@ oauth2_authorize_finish(oauth2_auth_ctx_t *auth_ctx, const gchar *auth_code)
/* send the authorisation code */
session = oauth_soup_session_new();
- message = soup_form_request_new("POST", provider->token_uri,
- "grant_type", "authorization_code",
- "client_id", provider->client_id,
- "client_secret", provider->client_secret,
- "code", auth_code,
- "redirect_uri", auth_ctx->listen_uri,
- NULL);
+ if (provider->client_secret != NULL) {
+ message = soup_form_request_new("POST", provider->token_uri,
+ "grant_type", "authorization_code",
+ "client_id", provider->client_id,
+ "client_secret", provider->client_secret,
+ "code", auth_code,
+ "redirect_uri", auth_ctx->listen_uri,
+ NULL);
+ } else {
+ message = soup_form_request_new("POST", provider->token_uri,
+ "grant_type", "authorization_code",
+ "client_id", provider->client_id,
+ "code", auth_code,
+ "redirect_uri", auth_ctx->listen_uri,
+ NULL);
+ }
status = soup_session_send_message(session, message);
g_debug("%s: status=%u, code=%u, reason=%s", __func__, status, message->status_code,
message->reason_phrase);
+ gtk_spinner_stop(GTK_SPINNER(auth_ctx->spinner));
if ((status != SOUP_STATUS_OK) || (message->status_code != SOUP_STATUS_OK) || (message->response_body
== NULL)) {
eval_json_error(_("OAuth2 authorization request failed"), message, &auth_ctx->auth_err);
gtk_label_set_label(GTK_LABEL(auth_ctx->spinner_label), _("send error"));
@@ -932,11 +1020,16 @@ oauth2_authorize_finish(oauth2_auth_ctx_t *auth_ctx, const gchar *auth_code)
if (eval_json_auth_reply(auth_ctx->oauth, message, TRUE, &auth_ctx->auth_err)) {
gtk_label_set_label(GTK_LABEL(auth_ctx->spinner_label), _("authorization
successful"));
gtk_dialog_set_response_sensitive(GTK_DIALOG(auth_ctx->auth_dialog),
GTK_RESPONSE_ACCEPT, TRUE);
+ success = TRUE;
} else {
gtk_label_set_label(GTK_LABEL(auth_ctx->spinner_label), _("authorization failed"));
}
}
- gtk_spinner_stop(GTK_SPINNER(auth_ctx->spinner));
+ icon = gtk_image_new_from_icon_name(success ? "gtk-yes" : "gtk-no", GTK_ICON_SIZE_BUTTON);
+ gtk_box_pack_start(GTK_BOX(gtk_widget_get_parent(auth_ctx->spinner)), icon, FALSE, FALSE, 0U);
+ gtk_widget_hide(auth_ctx->spinner);
+ gtk_widget_destroy(auth_ctx->spinner);
+ gtk_widget_show(icon);
g_object_unref(message);
g_object_unref(session);
}
@@ -1044,7 +1137,7 @@ run_oauth2_dialog(oauth2_auth_ctx_t *auth_ctx, const oauth2_provider_t *provider
gtk_spinner_start(GTK_SPINNER(auth_ctx->spinner));
gtk_box_pack_start(GTK_BOX(hbox), auth_ctx->spinner, FALSE, FALSE, 0U);
auth_ctx->spinner_label = gtk_label_new(_("…waiting for authorization code"));
- gtk_box_pack_start(GTK_BOX(hbox), auth_ctx->spinner_label, FALSE, FALSE, 0U);
+ gtk_box_pack_end(GTK_BOX(hbox), auth_ctx->spinner_label, TRUE, TRUE, 0U);
gtk_widget_show_all(auth_ctx->auth_dialog);
result = gtk_dialog_run(GTK_DIALOG(auth_ctx->auth_dialog));
@@ -1058,26 +1151,45 @@ run_oauth2_dialog(oauth2_auth_ctx_t *auth_ctx, const oauth2_provider_t *provider
*
* @return a new SoupSession
*
- * @todo Check the environment variable @c G_MESSAGES_DEBUG for @c oauth instead of using the compile-time
@ref HTTP_VERBOSE define.
+ * The soup session includes a Soup logger, dumping the full body, iff the environment variable @c
G_MESSAGES_DEBUG contains either
+ * @c all or <c>oauth</c>. Note that this logger object is never freed, i.e. running Balsa with the
aforementioned configuration
+ * will produce a (harmless) leak.
*/
static SoupSession *
oauth_soup_session_new(void)
{
-#ifdef HTTP_VERBOSE
+ static gsize env_checked = 0UL;
static SoupLogger *logger = NULL;
-#endif
SoupSession *session;
+ /* check the environment once if we should add the soup logger (note: GLib 2.68 adds
g_log_writer_default_would_drop() which
+ * might be used instead of the following code...) */
+ if (g_once_init_enter(&env_checked)) {
+ const gchar *log_domains;
+
+ log_domains = g_getenv("G_MESSAGES_DEBUG");
+ if (log_domains != NULL) {
+ gchar **domlist;
+ guint n;
+
+ domlist = g_strsplit(log_domains, " ", -1);
+ for (n = 0U; (logger == NULL) && (domlist[n] != NULL); n++) {
+ if ((strcmp(domlist[n], "all") == 0) || (strcmp(domlist[n], G_LOG_DOMAIN) ==
0)) {
+ logger = soup_logger_new(SOUP_LOGGER_LOG_BODY, -1);
+ g_debug("%s: create Soup logger", __func__);
+ }
+ }
+ g_strfreev(domlist);
+ }
+ g_once_init_leave(&env_checked, 1UL);
+ }
+
+ /* create the new session */
session = soup_session_new();
g_object_set(session, "user-agent", PACKAGE "/" BALSA_VERSION " ", NULL);
-
-#ifdef HTTP_VERBOSE
- if (g_once_init_enter(&logger)) {
- SoupLogger *_logger = soup_logger_new(SOUP_LOGGER_LOG_BODY, -1);
- g_once_init_leave(&logger, _logger);
+ if (logger != NULL) {
+ soup_session_add_feature(session, SOUP_SESSION_FEATURE(logger));
}
- soup_session_add_feature(session, SOUP_SESSION_FEATURE(logger));
-#endif
return session;
}
diff --git a/libnetclient/net-client-pop.c b/libnetclient/net-client-pop.c
index 84b1ab1bb..2cff3f8a7 100644
--- a/libnetclient/net-client-pop.c
+++ b/libnetclient/net-client-pop.c
@@ -53,9 +53,11 @@ struct _NetClientPop {
/** RFC 4752 "GSSAPI" authentication method. */
#define NET_CLIENT_POP_AUTH_GSSAPI 0x040U
/** RFC 7628 "OAUTHBEARER" authentication method. */
-#define NET_CLIENT_POP_AUTH_OAUTH2 0x080U
+#define NET_CLIENT_POP_AUTH_OAUTHBEARER 0x080U
+/** RFC 6749 "XOAUTH2" authentication method. */
+#define NET_CLIENT_POP_AUTH_XOAUTH2 0x100U
/** RFC 4505 "ANONYMOUS" authentication method. */
-#define NET_CLIENT_POP_AUTH_ANONYMOUS 0x100U
+#define NET_CLIENT_POP_AUTH_ANONYMOUS 0x200U
/** Mask of all authentication methods requiring user name and password. */
@@ -63,6 +65,10 @@ struct _NetClientPop {
(NET_CLIENT_POP_AUTH_USER_PASS | NET_CLIENT_POP_AUTH_APOP | NET_CLIENT_POP_AUTH_LOGIN |
NET_CLIENT_POP_AUTH_PLAIN | \
NET_CLIENT_POP_AUTH_CRAM_MD5 | NET_CLIENT_POP_AUTH_CRAM_SHA1)
+/** Mask of OAuth2 authentication methods. */
+#define NET_CLIENT_POP_AUTH_OAUTH2 \
+ (NET_CLIENT_POP_AUTH_OAUTHBEARER | NET_CLIENT_POP_AUTH_XOAUTH2)
+
/** Mask of all authentication methods. */
#define NET_CLIENT_POP_AUTH_ALL \
(NET_CLIENT_POP_AUTH_PASSWORD + NET_CLIENT_POP_AUTH_GSSAPI + NET_CLIENT_POP_AUTH_OAUTH2 +
NET_CLIENT_POP_AUTH_ANONYMOUS)
@@ -103,9 +109,10 @@ static gboolean net_client_pop_auth_anonymous(NetClientPop *client, GError **err
static gboolean net_client_pop_auth_user_pass(NetClientPop *client, const gchar* user, const gchar* passwd,
GError** error);
static gboolean net_client_pop_auth_apop(NetClientPop *client, const gchar* user, const gchar* passwd,
GError** error);
static gboolean net_client_pop_auth_cram(NetClientPop *client, GChecksumType chksum_type, const gchar *user,
const gchar *passwd,
- GError **error);
+ GError **error);
static gboolean net_client_pop_auth_gssapi(NetClientPop *client, const gchar *user, GError **error);
-static gboolean net_client_pop_auth_oauth2(NetClientPop *client, const gchar *user, const gchar
*access_token, GError **error);
+static gboolean net_client_pop_auth_oauth2(NetClientPop *client, const gchar *user, const gchar
*access_token, gboolean oauthbearer,
+ GError **error);
static gboolean net_client_pop_retr_msg(NetClientPop *client, const NetClientPopMessageInfo *info,
NetClientPopMsgCb callback,
gpointer user_data, GError
**error);
@@ -649,7 +656,8 @@ net_client_pop_auth(NetClientPop *client, guint auth_supported, GError **error)
g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
_("user name and access token required"));
} else {
- result = net_client_pop_auth_oauth2(client, auth_data[0], auth_data[1], error);
+ result = net_client_pop_auth_oauth2(client, auth_data[0], auth_data[1],
+ (auth_mask & NET_CLIENT_POP_AUTH_OAUTHBEARER) ==
NET_CLIENT_POP_AUTH_OAUTHBEARER, error);
}
} else {
/* user name and password authentication methods */
@@ -875,14 +883,18 @@ net_client_pop_auth_gssapi(NetClientPop G_GNUC_UNUSED *client, const gchar G_GNU
#if defined(HAVE_OAUTH2)
static gboolean
-net_client_pop_auth_oauth2(NetClientPop *client, const gchar *user, const gchar *access_token, GError
**error)
+net_client_pop_auth_oauth2(NetClientPop *client, const gchar *user, const gchar *access_token, gboolean
oauthbearer, GError **error)
{
gboolean result ;
gchar *base64_buf;
- base64_buf = net_client_auth_oauth2_calc(user, NET_CLIENT(client), access_token);
+ base64_buf = net_client_auth_oauth2_calc(user, oauthbearer, NET_CLIENT(client), access_token);
if (base64_buf != NULL) {
- result = net_client_pop_execute_sasl(client, "AUTH OAUTHBEARER", NULL, error);
+ if (oauthbearer) {
+ result = net_client_pop_execute_sasl(client, "AUTH OAUTHBEARER", NULL, error);
+ } else {
+ result = net_client_pop_execute_sasl(client, "AUTH XOAUTH2", NULL, error);
+ }
if (result) {
result = net_client_pop_execute(client, "%s", NULL, error, base64_buf);
// FIXME - grab the JSON response on error
@@ -982,7 +994,9 @@ net_client_pop_get_capa(NetClientPop *client, guint *auth_supported)
#endif
#if defined(HAVE_OAUTH2)
} else if (strcmp(auth[n], "OAUTHBEARER") == 0) {
- *auth_supported |= NET_CLIENT_POP_AUTH_OAUTH2;
+ *auth_supported |= NET_CLIENT_POP_AUTH_OAUTHBEARER;
+ } else if (strcmp(auth[n], "XOAUTH2") == 0) {
+ *auth_supported |= NET_CLIENT_POP_AUTH_XOAUTH2;
#endif
} else {
/* other auth methods are ignored for the time being (see
MISRA C:2012, Rule 15.7) */
diff --git a/libnetclient/net-client-smtp.c b/libnetclient/net-client-smtp.c
index 6fcacc71e..8b8494ed3 100644
--- a/libnetclient/net-client-smtp.c
+++ b/libnetclient/net-client-smtp.c
@@ -62,12 +62,19 @@ typedef struct {
/** RFC 4752 "GSSAPI" authentication method. */
#define NET_CLIENT_SMTP_AUTH_GSSAPI 0x20U
/** RFC 7628 "OAUTHBEARER" authentication method. */
-#define NET_CLIENT_SMTP_AUTH_OAUTH2 0x40U
+#define NET_CLIENT_SMTP_AUTH_OAUTHBEARER 0x40U
+/** RFC 6749 "XOAUTH2" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_XOAUTH2 0x80U
+
/** Mask of all authentication methods requiring user name and password. */
#define NET_CLIENT_SMTP_AUTH_PASSWORD \
(NET_CLIENT_SMTP_AUTH_PLAIN | NET_CLIENT_SMTP_AUTH_LOGIN | NET_CLIENT_SMTP_AUTH_CRAM_MD5 |
NET_CLIENT_SMTP_AUTH_CRAM_SHA1)
+/** Mask of OAuth2 authentication methods. */
+#define NET_CLIENT_SMTP_AUTH_OAUTH2 \
+ (NET_CLIENT_SMTP_AUTH_OAUTHBEARER | NET_CLIENT_SMTP_AUTH_XOAUTH2)
+
/** Mask of all authentication methods. */
#define NET_CLIENT_SMTP_AUTH_ALL \
(NET_CLIENT_SMTP_AUTH_NONE | NET_CLIENT_SMTP_AUTH_PASSWORD | NET_CLIENT_SMTP_AUTH_GSSAPI |
NET_CLIENT_SMTP_AUTH_OAUTH2)
@@ -96,7 +103,8 @@ static gboolean net_client_smtp_auth_login(NetClientSmtp *client, const gchar *u
static gboolean net_client_smtp_auth_cram(NetClientSmtp *client, GChecksumType chksum_type, const gchar
*user, const gchar *passwd,
GError **error);
static gboolean net_client_smtp_auth_gssapi(NetClientSmtp *client, const gchar *user, GError **error);
-static gboolean net_client_smtp_auth_oauth2(NetClientSmtp *client, const gchar *user, const gchar
*access_token, GError **error);
+static gboolean net_client_smtp_auth_oauth2(NetClientSmtp *client, const gchar *user, const gchar
*access_token,
+ gboolean oauthbearer,
GError **error);
static gboolean net_client_smtp_read_reply(NetClientSmtp *client, gint expect_code, gchar **last_reply,
GError **error);
static gboolean net_client_smtp_eval_rescode(gint res_code, gint expect_code, const gchar *reply, GError
**error);
static gchar *net_client_smtp_dsn_to_string(const NetClientSmtp *client, NetClientSmtpDsnMode dsn_mode);
@@ -541,7 +549,8 @@ net_client_smtp_auth(NetClientSmtp *client, guint auth_supported, GError **error
g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
_("user name and access token required"));
} else {
- result = net_client_smtp_auth_oauth2(client, auth_data[0], auth_data[1], error);
+ result = net_client_smtp_auth_oauth2(client, auth_data[0], auth_data[1],
+ (auth_mask & NET_CLIENT_SMTP_AUTH_OAUTHBEARER) ==
NET_CLIENT_SMTP_AUTH_OAUTHBEARER, error);
}
} else {
/* user name and password authentication methods */
@@ -703,15 +712,20 @@ net_client_smtp_auth_gssapi(NetClientSmtp G_GNUC_UNUSED *client, const gchar G_G
#if defined(HAVE_OAUTH2)
static gboolean
-net_client_smtp_auth_oauth2(NetClientSmtp *client, const gchar *user, const gchar *access_token, GError
**error)
+net_client_smtp_auth_oauth2(NetClientSmtp *client, const gchar *user, const gchar *access_token, gboolean
oauthbearer,
+ GError **error)
{
gboolean result;
gchar *base64_buf;
- base64_buf = net_client_auth_oauth2_calc(user, NET_CLIENT(client), access_token);
+ base64_buf = net_client_auth_oauth2_calc(user, oauthbearer, NET_CLIENT(client), access_token);
if (base64_buf != NULL) {
/* RFC 4954, Sect. 6 requires status 235 */
- result = net_client_smtp_execute(client, "AUTH OAUTHBEARER %s", 235, NULL, error, base64_buf);
+ if (oauthbearer) {
+ result = net_client_smtp_execute(client, "AUTH OAUTHBEARER %s", 235, NULL, error,
base64_buf);
+ } else {
+ result = net_client_smtp_execute(client, "AUTH XOAUTH2 %s", 235, NULL, error,
base64_buf);
+ }
// FIXME - grab the JSON response on error?
net_client_free_authstr(base64_buf);
} else {
@@ -807,7 +821,9 @@ net_client_smtp_ehlo(NetClientSmtp *client, guint *auth_supported, gboolean *can
#endif
#if defined (HAVE_OAUTH2)
} else if (strcmp(auth[n], "OAUTHBEARER") == 0) {
- *auth_supported |= NET_CLIENT_SMTP_AUTH_OAUTH2;
+ *auth_supported |= NET_CLIENT_SMTP_AUTH_OAUTHBEARER;
+ } else if (strcmp(auth[n], "XOAUTH2") == 0) {
+ *auth_supported |= NET_CLIENT_SMTP_AUTH_XOAUTH2;
#endif
} else {
/* other auth methods are ignored for the time being
*/
diff --git a/libnetclient/net-client-utils.c b/libnetclient/net-client-utils.c
index d34c4ba20..46b914dc5 100644
--- a/libnetclient/net-client-utils.c
+++ b/libnetclient/net-client-utils.c
@@ -375,17 +375,22 @@ gss_error_string(OM_uint32 err_maj, OM_uint32 err_min)
#if defined(HAVE_OAUTH2)
gchar *
-net_client_auth_oauth2_calc(const gchar *user, NetClient *client, const gchar *access_token)
+net_client_auth_oauth2_calc(const gchar *user, gboolean rfc7628mode, NetClient *client, const gchar
*access_token)
{
gchar *buffer;
gchar *result;
GNetworkAddress *remote_address;
- g_return_val_if_fail((user != NULL) && (client != NULL) && (access_token != NULL), NULL);
+ g_return_val_if_fail((user != NULL) && (!rfc7628mode || (client != NULL)) && (access_token != NULL),
NULL);
remote_address = net_client_get_remote_address(client);
g_return_val_if_fail(remote_address != NULL, NULL);
- buffer = g_strdup_printf("n,a=%s,\001host=%s\001port=%hu\001auth=Bearer %s\001\001", user,
- g_network_address_get_hostname(remote_address), g_network_address_get_port(remote_address),
access_token);
+ if (rfc7628mode) {
+ buffer = g_strdup_printf("n,a=%s,\001host=%s\001port=%hu\001auth=Bearer %s\001\001", user,
+ g_network_address_get_hostname(remote_address),
g_network_address_get_port(remote_address), access_token);
+ } else {
+ buffer = g_strdup_printf("user=%s\001auth=Bearer %s\001\001", user, access_token);
+ }
+ g_debug("%s: '%s'", __func__, buffer);
result = g_base64_encode(buffer, strlen(buffer));
g_free(buffer);
return result;
diff --git a/libnetclient/net-client-utils.h b/libnetclient/net-client-utils.h
index 0db4333fb..d3a9e7339 100644
--- a/libnetclient/net-client-utils.h
+++ b/libnetclient/net-client-utils.h
@@ -183,14 +183,16 @@ void net_client_gss_ctx_free(NetClientGssCtx *gss_ctx);
/** @brief Calculate a OAuth2 authentication string
*
* @param user user name
- * @param client network client
+ * @param rfc7628mode indicates if RFC 7628 @c OAUTHBEARER instead of RFC 6749 XOAUTH2 mode shall be used
+ * @param client network client, required only if rfc7628mode is set
* @param access_token access token
* @return a newly allocated string containing the base64-encoded authentication
*
- * This helper function calculates the the base64-encoded authentication string from the user name, host and
port, and the access
- * token according to RFC 7628. The caller shall free the returned string when it is not needed any more.
+ * This helper function calculates the the base64-encoded authentication string from the user name, host and
port (RFC 7628
+ * @c OAUTHBEARER mode only), and the access token according to RFC 6749 or RFC 7628. The caller shall free
the returned string
+ * when it is not needed any more.
*/
-gchar *net_client_auth_oauth2_calc(const gchar *user, NetClient *client, const gchar *access_token)
+gchar *net_client_auth_oauth2_calc(const gchar *user, gboolean rfc7628mode, NetClient *client, const gchar
*access_token)
G_GNUC_MALLOC;
#endif /* HAVE_OAUTH2 */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]