[balsa/oauth2-support] OAuth2 support for outlook.com accounts (see also issue #40)



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]