diff options
author | Kevin McCarthy <kevin@8t8.us> | 2020-06-11 16:11:36 -0700 |
---|---|---|
committer | Kevin McCarthy <kevin@8t8.us> | 2020-06-13 14:44:17 -0700 |
commit | c7a872d1eeea39df148396869c1cbbc0fa26552f (patch) | |
tree | 0fa80c6f4e6e0a7f8693ee087f18b3870a16b718 | |
parent | 5b844328bb7d7fb0357328bed002e7672f9b9e2a (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.c | 56 | ||||
-rw-r--r-- | account.h | 2 | ||||
-rw-r--r-- | doc/manual.xml.head | 20 | ||||
-rw-r--r-- | imap/auth.c | 3 | ||||
-rw-r--r-- | imap/auth.h | 3 | ||||
-rw-r--r-- | imap/auth_oauth.c | 102 | ||||
-rw-r--r-- | imap/command.c | 1 | ||||
-rw-r--r-- | imap/imap_private.h | 1 | ||||
-rw-r--r-- | pop_auth.c | 77 | ||||
-rw-r--r-- | smtp.c | 62 |
10 files changed, 212 insertions, 115 deletions
@@ -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; } @@ -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 */ @@ -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 @@ -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; } |