summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin McCarthy <kevin@8t8.us>2020-06-11 16:11:36 -0700
committerKevin McCarthy <kevin@8t8.us>2020-06-13 14:44:17 -0700
commitc7a872d1eeea39df148396869c1cbbc0fa26552f (patch)
tree0fa80c6f4e6e0a7f8693ee087f18b3870a16b718
parent5b844328bb7d7fb0357328bed002e7672f9b9e2a (diff)
Add basic XOAUTH2 support.
This still relies on an external script to obtain the resource access token. Since XOAUTH2 should be slowly going away, use the same refresh_commands as with OAUTHBEARER. Unlike OAUTHBEARER, XOAUTH2 must be explicitly added to the $imap/smtp/pop_authenticators list. To keep the shared functions simpler, convert them to use buffers. RFC 7628 indicates that upon authentication failure the clients should be sending an BASE 64 encoded '^a' ("AQ=="), to terminate the SASL session, so change all the handlers to do that and read the following response. The RFC doesn't comment about a line terminator being required, but I assume it is, so add that too.
-rw-r--r--account.c56
-rw-r--r--account.h2
-rw-r--r--doc/manual.xml.head20
-rw-r--r--imap/auth.c3
-rw-r--r--imap/auth.h3
-rw-r--r--imap/auth_oauth.c102
-rw-r--r--imap/command.c1
-rw-r--r--imap/imap_private.h1
-rw-r--r--pop_auth.c77
-rw-r--r--smtp.c62
10 files changed, 212 insertions, 115 deletions
diff --git a/account.c b/account.c
index a005fa86..28c0749b 100644
--- a/account.c
+++ b/account.c
@@ -241,25 +241,26 @@ void mutt_account_unsetpass (ACCOUNT* account)
/* mutt_account_getoauthbearer: call external command to generate the
* oauth refresh token for this ACCOUNT, then create and encode the
- * OAUTHBEARER token based on RFC 7628. Returns NULL on failure.
- * Resulting token is dynamically allocated and should be FREE'd by the
- * caller.
+ * OAUTHBEARER token based on RFC 7628.
+ *
+ * Returns 0 on success, -1 on failure.
+ *
+ * If xoauth2 is set, a deprecated XOAUTH2 token will be generated instead.
*/
-char* mutt_account_getoauthbearer (ACCOUNT* account)
+int mutt_account_getoauthbearer (ACCOUNT* account, BUFFER *authbearer, int xoauth2)
{
FILE *fp;
char *cmd = NULL;
char *token = NULL;
size_t token_size = 0;
- char *oauthbearer = NULL;
- size_t oalen;
- char *encoded_token = NULL;
- size_t encoded_len;
pid_t pid;
+ BUFFER *unencoded_bearertoken = NULL;
+
+ mutt_buffer_clear (authbearer);
/* The oauthbearer token includes the login */
if (mutt_account_getlogin (account))
- return NULL;
+ return -1;
#ifdef USE_IMAP
if ((account->type == MUTT_ACCT_TYPE_IMAP) && ImapOauthRefreshCmd)
@@ -282,13 +283,13 @@ char* mutt_account_getoauthbearer (ACCOUNT* account)
your $*_oauth_refresh_command's are defined."
*/
mutt_error (_("mutt_account_getoauthbearer: No OAUTH refresh command defined"));
- return NULL;
+ return -1;
}
if ((pid = mutt_create_filter (cmd, NULL, &fp, NULL)) < 0)
{
mutt_perror _("mutt_account_getoauthbearer: Unable to run refresh command");
- return NULL;
+ return -1;
}
/* read line */
@@ -300,26 +301,27 @@ char* mutt_account_getoauthbearer (ACCOUNT* account)
{
mutt_error (_("mutt_account_getoauthbearer: Command returned empty string"));
FREE (&token);
- return NULL;
+ return -1;
}
- /* Determine the length of the keyed message digest, add 50 for
- * overhead.
- */
- oalen = strlen (account->login) + strlen (account->host) + strlen (token) + 50;
- oauthbearer = safe_malloc (oalen);
+ unencoded_bearertoken = mutt_buffer_pool_get ();
- snprintf (oauthbearer, oalen,
- "n,a=%s,\001host=%s\001port=%d\001auth=Bearer %s\001\001",
- account->login, account->host, account->port, token);
+ if (xoauth2)
+ mutt_buffer_printf (unencoded_bearertoken,
+ "user=%s\001auth=Bearer %s\001\001",
+ account->login, token);
+ else
+ mutt_buffer_printf (unencoded_bearertoken,
+ "n,a=%s,\001host=%s\001port=%d\001auth=Bearer %s\001\001",
+ account->login, account->host, account->port, token);
FREE (&token);
- encoded_len = strlen (oauthbearer) * 4 / 3 + 10;
- encoded_token = safe_malloc (encoded_len);
- mutt_to_base64 ((unsigned char*) encoded_token,
- (unsigned char*) oauthbearer, strlen (oauthbearer),
- encoded_len);
- FREE (&oauthbearer);
- return encoded_token;
+ mutt_buffer_to_base64 (authbearer,
+ (const unsigned char *) mutt_b2s (unencoded_bearertoken),
+ mutt_buffer_len (unencoded_bearertoken));
+
+ mutt_buffer_pool_release (&unencoded_bearertoken);
+
+ return 0;
}
diff --git a/account.h b/account.h
index 943da839..2eccb7f4 100644
--- a/account.h
+++ b/account.h
@@ -57,6 +57,6 @@ int mutt_account_getuser (ACCOUNT* account);
int mutt_account_getlogin (ACCOUNT* account);
int mutt_account_getpass (ACCOUNT* account);
void mutt_account_unsetpass (ACCOUNT* account);
-char* mutt_account_getoauthbearer (ACCOUNT* account);
+int mutt_account_getoauthbearer (ACCOUNT* account, BUFFER *authbearer, int xoauth2);
#endif /* _MUTT_ACCOUNT_H_ */
diff --git a/doc/manual.xml.head b/doc/manual.xml.head
index e6414c0f..354932a4 100644
--- a/doc/manual.xml.head
+++ b/doc/manual.xml.head
@@ -9026,6 +9026,26 @@ set imap_oauth_refresh_command="/path/to/oauth2.py --quiet --user=[email_address
<para>
Substitute pop or smtp for imap in the above example to configure for those.
</para>
+
+<sect2 id="xoauth2">
+ <title>XOAUTH2 Support</title>
+
+ <para>
+ Support for the deprecated XOAUTH2 protocol is also available. To
+ enable this, add <quote>xoauth2</quote> to the
+ <link linkend="imap-authenticators">$imap_authenticators</link>,
+ <link linkend="pop-authenticators">$pop_authenticators</link>, or
+ <link linkend="smtp-authenticators">$smtp_authenticators</link> config
+ variables. XOAUTH2 uses the same refresh command configuration variables
+ as OAUTHBEARER:
+ <link linkend="imap-oauth-refresh-command">$imap_oauth_refresh_command</link>,
+ <link linkend="pop-oauth-refresh-command">$pop_oauth_refresh_command</link>, and
+ <link linkend="smtp-oauth-refresh-command">$smtp_oauth_refresh_command</link>.
+ Those will need to be set to a script to generate the appropriate XOAUTH2
+ token.
+ </para>
+</sect2>
+
</sect1>
<sect1 id="account-hook">
diff --git a/imap/auth.c b/imap/auth.c
index 48909cd8..83c87f01 100644
--- a/imap/auth.c
+++ b/imap/auth.c
@@ -29,7 +29,8 @@
#include "auth.h"
static const imap_auth_t imap_authenticators[] = {
- { imap_auth_oauth, "oauthbearer" },
+ { imap_auth_oauthbearer, "oauthbearer" },
+ { imap_auth_xoauth2, "xoauth2" },
#ifdef USE_SASL
{ imap_auth_sasl, NULL },
#else
diff --git a/imap/auth.h b/imap/auth.h
index 6ae33fc6..f022beae 100644
--- a/imap/auth.h
+++ b/imap/auth.h
@@ -51,6 +51,7 @@ imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method);
#ifdef USE_SASL
imap_auth_res_t imap_auth_sasl (IMAP_DATA* idata, const char* method);
#endif
-imap_auth_res_t imap_auth_oauth (IMAP_DATA* idata, const char* method);
+imap_auth_res_t imap_auth_oauthbearer (IMAP_DATA* idata, const char* method);
+imap_auth_res_t imap_auth_xoauth2 (IMAP_DATA* idata, const char* method);
#endif /* _IMAP_AUTH_H */
diff --git a/imap/auth_oauth.c b/imap/auth_oauth.c
index a301e166..b7f01e4f 100644
--- a/imap/auth_oauth.c
+++ b/imap/auth_oauth.c
@@ -28,58 +28,88 @@
#include "auth.h"
/* imap_auth_oauth: AUTH=OAUTHBEARER support. See RFC 7628 */
-imap_auth_res_t imap_auth_oauth (IMAP_DATA* idata, const char* method)
+static imap_auth_res_t imap_auth_oauth (IMAP_DATA* idata, int xoauth2)
{
- char* ibuf = NULL;
- char* oauthbearer = NULL;
- int ilen;
- int rc;
+ int rc = IMAP_AUTH_FAILURE, steprc;
+ BUFFER *bearertoken = NULL, *authline = NULL;
+ const char *authtype;
- /* For now, we only support SASL_IR also and over TLS */
- if (!mutt_bit_isset (idata->capabilities, AUTH_OAUTHBEARER) ||
- !mutt_bit_isset (idata->capabilities, SASL_IR) ||
- !idata->conn->ssf)
- return IMAP_AUTH_UNAVAIL;
+ authtype = xoauth2 ? "XOAUTH2" : "OAUTHBEARER";
- /* If they did not explicitly request or configure oauth then fail quietly */
- if (!(method || ImapOauthRefreshCmd))
- return IMAP_AUTH_UNAVAIL;
+ mutt_message (_("Authenticating (%s)..."), authtype);
- mutt_message _("Authenticating (OAUTHBEARER)...");
+ bearertoken = mutt_buffer_pool_get ();
+ authline = mutt_buffer_pool_get ();
/* We get the access token from the imap_oauth_refresh_command */
- oauthbearer = mutt_account_getoauthbearer (&idata->conn->account);
- if (oauthbearer == NULL)
- return IMAP_AUTH_FAILURE;
+ if (mutt_account_getoauthbearer (&idata->conn->account, bearertoken, xoauth2))
+ goto cleanup;
- ilen = strlen (oauthbearer) + 30;
- ibuf = safe_malloc (ilen);
- snprintf (ibuf, ilen, "AUTHENTICATE OAUTHBEARER %s", oauthbearer);
+ mutt_buffer_printf (authline, "AUTHENTICATE %s %s",
+ authtype, mutt_b2s (bearertoken));
/* This doesn't really contain a password, but the token is good for
* an hour, so suppress it anyways.
*/
- rc = imap_exec (idata, ibuf, IMAP_CMD_FAIL_OK | IMAP_CMD_PASS);
-
- FREE (&oauthbearer);
- FREE (&ibuf);
-
- if (rc)
+ if (imap_exec (idata, mutt_b2s (authline), IMAP_CMD_FAIL_OK | IMAP_CMD_PASS))
{
/* The error response was in SASL continuation, so continue the SASL
* to cause a failure and exit SASL input. See RFC 7628 3.2.3
+ * "AQ==" is Base64 encoded ^A (0x01) .
*/
- mutt_socket_write (idata->conn, "\001");
- rc = imap_exec (idata, ibuf, IMAP_CMD_FAIL_OK);
- }
+ mutt_socket_write (idata->conn, "AQ==\r\n");
+ do
+ steprc = imap_cmd_step (idata);
+ while (steprc == IMAP_CMD_CONTINUE);
- if (!rc)
- {
- mutt_clear_error();
- return IMAP_AUTH_SUCCESS;
+ /* L10N:
+ %s is the authentication type, for example OAUTHBEARER
+ */
+ mutt_error (_("%s authentication failed."), authtype);
+ mutt_sleep (2);
+ goto cleanup;
}
- mutt_error _("OAUTHBEARER authentication failed.");
- mutt_sleep (2);
- return IMAP_AUTH_FAILURE;
+ mutt_clear_error();
+ rc = IMAP_AUTH_SUCCESS;
+
+cleanup:
+ mutt_buffer_pool_release (&bearertoken);
+ mutt_buffer_pool_release (&authline);
+ return rc;
+}
+
+/* AUTH=OAUTHBEARER support. See RFC 7628 */
+imap_auth_res_t imap_auth_oauthbearer (IMAP_DATA* idata, const char* method)
+{
+ /* For now, we only support SASL_IR also and over TLS */
+ if (!mutt_bit_isset (idata->capabilities, SASL_IR) ||
+ !idata->conn->ssf)
+ return IMAP_AUTH_UNAVAIL;
+
+ if (!mutt_bit_isset (idata->capabilities, AUTH_OAUTHBEARER))
+ return IMAP_AUTH_UNAVAIL;
+
+ if (!ImapOauthRefreshCmd)
+ return IMAP_AUTH_UNAVAIL;
+
+ return imap_auth_oauth (idata, 0);
+}
+
+/* AUTH=XOAUTH2 support. */
+imap_auth_res_t imap_auth_xoauth2 (IMAP_DATA* idata, const char* method)
+{
+ /* For now, we only support SASL_IR also and over TLS */
+ if (!mutt_bit_isset (idata->capabilities, SASL_IR) ||
+ !idata->conn->ssf)
+ return IMAP_AUTH_UNAVAIL;
+
+ if (!mutt_bit_isset (idata->capabilities, AUTH_XOAUTH2))
+ return IMAP_AUTH_UNAVAIL;
+
+ /* If they did not explicitly request XOAUTH2 then fail quietly */
+ if (!(method && ImapOauthRefreshCmd))
+ return IMAP_AUTH_UNAVAIL;
+
+ return imap_auth_oauth (idata, 1);
}
diff --git a/imap/command.c b/imap/command.c
index 239eb1fc..1cf02257 100644
--- a/imap/command.c
+++ b/imap/command.c
@@ -66,6 +66,7 @@ static const char * const Capabilities[] = {
"AUTH=GSSAPI",
"AUTH=ANONYMOUS",
"AUTH=OAUTHBEARER",
+ "AUTH=XOAUTH2",
"STARTTLS",
"LOGINDISABLED",
"IDLE",
diff --git a/imap/imap_private.h b/imap/imap_private.h
index eb073efc..42078349 100644
--- a/imap/imap_private.h
+++ b/imap/imap_private.h
@@ -113,6 +113,7 @@ enum
AGSSAPI, /* RFC 1731: GSSAPI authentication */
AUTH_ANON, /* AUTH=ANONYMOUS */
AUTH_OAUTHBEARER, /* RFC 7628: AUTH=OAUTHBEARER */
+ AUTH_XOAUTH2, /* Deprecated precursor to OAUTHBEARER */
STARTTLS, /* RFC 2595: STARTTLS */
LOGINDISABLED, /* LOGINDISABLED */
IDLE, /* RFC 2177: IDLE */
diff --git a/pop_auth.c b/pop_auth.c
index fd866ae0..f1b58c41 100644
--- a/pop_auth.c
+++ b/pop_auth.c
@@ -317,52 +317,50 @@ static pop_auth_res_t pop_auth_user (POP_DATA *pop_data, const char *method)
return POP_A_FAILURE;
}
-/* OAUTHBEARER authenticator */
-static pop_auth_res_t pop_auth_oauth (POP_DATA *pop_data, const char *method)
+/* OAUTHBEARER/XOAUTH2 authenticator */
+static pop_auth_res_t pop_auth_oauth (POP_DATA *pop_data, int xoauth2)
{
- char *oauthbearer = NULL;
+ int rc = POP_A_FAILURE;
+ BUFFER *bearertoken = NULL, *authline = NULL;
+ const char *authtype;
char decoded_err[LONG_STRING];
char *err = NULL;
- char *auth_cmd = NULL;
- size_t auth_cmd_len;
int ret, len;
- /* If they did not explicitly request or configure oauth then fail quietly */
- if (!(method || PopOauthRefreshCmd))
- return POP_A_UNAVAIL;
+ authtype = xoauth2 ? "XOAUTH2" : "OAUTHBEARER";
- mutt_message _("Authenticating (OAUTHBEARER)...");
+ mutt_message (_("Authenticating (%s)..."), authtype);
- oauthbearer = mutt_account_getoauthbearer (&pop_data->conn->account);
- if (oauthbearer == NULL)
- return POP_A_FAILURE;
+ bearertoken = mutt_buffer_pool_get ();
+ authline = mutt_buffer_pool_get ();
+
+ if (mutt_account_getoauthbearer (&pop_data->conn->account, bearertoken, xoauth2))
+ goto cleanup;
- auth_cmd_len = strlen (oauthbearer) + 30;
- auth_cmd = safe_malloc (auth_cmd_len);
- snprintf (auth_cmd, auth_cmd_len, "AUTH OAUTHBEARER %s\r\n", oauthbearer);
- FREE (&oauthbearer);
+ mutt_buffer_printf (authline, "AUTH %s %s\r\n", authtype, mutt_b2s (bearertoken));
- ret = pop_query_d (pop_data, auth_cmd, strlen (auth_cmd),
+ ret = pop_query_d (pop_data, authline->data, authline->dsize,
#ifdef DEBUG
/* don't print the bearer token unless we're at the ungodly debugging level */
- debuglevel < MUTT_SOCK_LOG_FULL ? "AUTH OAUTHBEARER *\r\n" :
+ debuglevel < MUTT_SOCK_LOG_FULL ?
+ (xoauth2 ? "AUTH XOAUTH2 *\r\n" : "AUTH OAUTHBEARER *\r\n")
+ :
#endif
NULL);
- FREE (&auth_cmd);
switch (ret)
{
case 0:
- return POP_A_SUCCESS;
+ rc = POP_A_SUCCESS;
+ goto cleanup;
case -1:
- return POP_A_SOCKET;
+ rc = POP_A_SOCKET;
+ goto cleanup;
}
/* The error response was a SASL continuation, so "continue" it.
- * See RFC 7628 3.2.3
+ * See RFC 7628 3.2.3. "AQ==" is Base64 encoded ^A (0x01) .
*/
- mutt_socket_write (pop_data->conn, "\001");
-
err = pop_data->err_msg;
len = mutt_from_base64 (decoded_err, pop_data->err_msg, sizeof(decoded_err) - 1);
if (len >= 0)
@@ -370,14 +368,41 @@ static pop_auth_res_t pop_auth_oauth (POP_DATA *pop_data, const char *method)
decoded_err[len] = '\0';
err = decoded_err;
}
+ mutt_buffer_strcpy (authline, "AQ==\r\n");
+ pop_query_d (pop_data, authline->data, authline->dsize, NULL);
mutt_error ("%s %s", _("Authentication failed."), err);
mutt_sleep (2);
- return POP_A_FAILURE;
+cleanup:
+ mutt_buffer_pool_release (&bearertoken);
+ mutt_buffer_pool_release (&authline);
+ return rc;
+}
+
+
+/* OAUTHBEARER/XOAUTH2 authenticator */
+static pop_auth_res_t pop_auth_oauthbearer (POP_DATA *pop_data, const char *method)
+{
+ if (!PopOauthRefreshCmd)
+ return POP_A_UNAVAIL;
+
+ return pop_auth_oauth (pop_data, 0);
}
+/* OAUTHBEARER/XOAUTH2 authenticator */
+static pop_auth_res_t pop_auth_xoauth2 (POP_DATA *pop_data, const char *method)
+{
+ /* If they did not explicitly request XOAUTH2 then fail quietly */
+ if (!(method && PopOauthRefreshCmd))
+ return POP_A_UNAVAIL;
+
+ return pop_auth_oauth (pop_data, 1);
+}
+
+
static const pop_auth_t pop_authenticators[] = {
- { pop_auth_oauth, "oauthbearer" },
+ { pop_auth_oauthbearer, "oauthbearer" },
+ { pop_auth_xoauth2, "xoauth2" },
#ifdef USE_SASL
{ pop_auth_sasl, NULL },
#endif
diff --git a/smtp.c b/smtp.c
index 1e80d5e6..942b7464 100644
--- a/smtp.c
+++ b/smtp.c
@@ -67,7 +67,7 @@ enum {
};
static int smtp_auth (CONNECTION* conn);
-static int smtp_auth_oauth (CONNECTION* conn);
+static int smtp_auth_oauth (CONNECTION* conn, int xoauth2);
#ifdef USE_SASL
static int smtp_auth_sasl (CONNECTION* conn, const char* mechanisms);
#endif
@@ -524,9 +524,13 @@ static int smtp_auth (CONNECTION* conn)
dprint (2, (debugfile, "smtp_authenticate: Trying method %s\n", method));
- if (!strcmp (method, "oauthbearer"))
+ if (!ascii_strcasecmp (method, "oauthbearer"))
{
- r = smtp_auth_oauth (conn);
+ r = smtp_auth_oauth (conn, 0);
+ }
+ else if (!ascii_strcasecmp (method, "xoauth2"))
+ {
+ r = smtp_auth_oauth (conn, 1);
}
else
{
@@ -684,33 +688,45 @@ fail:
/* smtp_auth_oauth: AUTH=OAUTHBEARER support. See RFC 7628 */
-static int smtp_auth_oauth (CONNECTION* conn)
+static int smtp_auth_oauth (CONNECTION* conn, int xoauth2)
{
- char* ibuf = NULL;
- char* oauthbearer = NULL;
- int ilen;
- int rc;
+ int rc = SMTP_AUTH_FAIL;
+ BUFFER *bearertoken = NULL, *authline = NULL;
+ const char *authtype;
- mutt_message _("Authenticating (OAUTHBEARER)...");
+ authtype = xoauth2 ? "XOAUTH2" : "OAUTHBEARER";
- /* We get the access token from the smtp_oauth_refresh_command */
- oauthbearer = mutt_account_getoauthbearer (&conn->account);
- if (oauthbearer == NULL)
- return SMTP_AUTH_FAIL;
+ /* L10N:
+ %s is the authentication type, such as XOAUTH2 or OAUTHBEARER
+ */
+ mutt_message (_("Authenticating (%s)..."), authtype);;
- ilen = strlen (oauthbearer) + 30;
- ibuf = safe_malloc (ilen);
+ bearertoken = mutt_buffer_pool_get ();
+ authline = mutt_buffer_pool_get ();
- snprintf (ibuf, ilen, "AUTH OAUTHBEARER %s\r\n", oauthbearer);
+ /* We get the access token from the smtp_oauth_refresh_command */
+ if (mutt_account_getoauthbearer (&conn->account, bearertoken, xoauth2))
+ goto cleanup;
- rc = mutt_socket_write (conn, ibuf);
- FREE (&oauthbearer);
- FREE (&ibuf);
+ mutt_buffer_printf (authline, "AUTH %s %s\r\n", authtype, mutt_b2s (bearertoken));
- if (rc == -1)
- return SMTP_AUTH_FAIL;
+ if (mutt_socket_write (conn, mutt_b2s (authline)) == -1)
+ goto cleanup;
if (smtp_get_resp (conn) != 0)
- return SMTP_AUTH_FAIL;
+ {
+ /* The error response was in SASL continuation, so continue the SASL
+ * to cause a failure and exit SASL input. See RFC 7628 3.2.3
+ * "AQ==" is Base64 encoded ^A (0x01) .
+ */
+ mutt_socket_write (conn, "AQ==\r\n");
+ smtp_get_resp (conn);
+ goto cleanup;
+ }
+
+ rc = SMTP_AUTH_SUCCESS;
- return SMTP_AUTH_SUCCESS;
+cleanup:
+ mutt_buffer_pool_release (&bearertoken);
+ mutt_buffer_pool_release (&authline);
+ return rc;
}