Jonathan Nieder wrote: > I'll send a debdiff in a separate message.
Patch attached. Thoughts of all kinds welcome.
>From 31e72e18b9a9c97d67b685bbbe5b1278f5381835 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder <jrnie...@gmail.com> Date: Sun, 5 Aug 2018 17:32:40 -0700 Subject: Apply Brandon Long's oauthbearer patches --- debian/changelog | 11 + debian/patches/series | 3 + .../upstream/905551-oauthbearer-imap.patch | 237 +++++++++ .../upstream/905551-oauthbearer-refresh.patch | 467 ++++++++++++++++++ .../upstream/905551-oauthbearer-smtp.patch | 190 +++++++ 5 files changed, 908 insertions(+) create mode 100644 debian/patches/upstream/905551-oauthbearer-imap.patch create mode 100644 debian/patches/upstream/905551-oauthbearer-refresh.patch create mode 100644 debian/patches/upstream/905551-oauthbearer-smtp.patch diff --git a/debian/changelog b/debian/changelog index cc82620c..8dc191d7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +mutt (1.10.1-2) UNRELEASED; urgency=low + + * debian/patches: + + added upstream patches for OAUTHBEARER support by Brandon Long + (Closes: #905551). + + upstream/905551-oauthbearer-imap.patch + + upstream/905551-oauthbearer-smtp.patch + + upstream/905551-oauthbearer-refresh.patch + + -- Jonathan Nieder <jrnie...@gmail.com> Sun, 05 Aug 2018 17:31:32 -0700 + mutt (1.10.1-1) unstable; urgency=medium * New upstream release. diff --git a/debian/patches/series b/debian/patches/series index a19d2d26..12be8181 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -9,3 +9,6 @@ debian-specific/828751-pinentry-gpg2-support.patch misc/gpg.rc-paths.patch misc/smime.rc.patch upstream/528233-readonly-open.patch +upstream/905551-oauthbearer-imap.patch +upstream/905551-oauthbearer-smtp.patch +upstream/905551-oauthbearer-refresh.patch diff --git a/debian/patches/upstream/905551-oauthbearer-imap.patch b/debian/patches/upstream/905551-oauthbearer-imap.patch new file mode 100644 index 00000000..06159d1c --- /dev/null +++ b/debian/patches/upstream/905551-oauthbearer-imap.patch @@ -0,0 +1,237 @@ +From: Brandon Long <bl...@fiction.net> +Date: Mon, 11 Jun 2018 10:39:49 -0700 +Subject: Initial support for OAUTHBEARER for IMAP. + +commit 798f749eeeb98ed04028521a2eb3e505c1a83574 upstream. + +Gmail supports RFC 7628 for using OAUTH with IMAP, and they really don't +like you using password based auth. You can still enable "less secure +apps" and then generate an application specific password, but I figured it +was time to support it. + +Being mutt, I punted on some of the "hard" work to an external script, ie +getting/refreshing the OAUTH tokens. This avoids the issue of how do you +have a client-id and client-secret for an open source project, and the fact +that OAUTH discovery is still nascent, so you'd likely need separate things +for each of the providers. + +At least for Gmail, you can use the oauth2.py script from Google's +gmail-oauth2-tools: +https://github.com/google/gmail-oauth2-tools/blob/master/python/oauth2.py + +You'd need to get your own oauth client credentials for Gmail here: +https://console.developers.google.com/apis/credentials + +Then, you'd use oauth2.py with --generate_oauth2_token to get a refresh +token, and configure mutt with: + +set imap_authenticators="oauthbearer" +set imap_user="<email_address>" +set imap_pass=`/path/to/oauth2.py --quiet --user=<email_address> +--client_id=<client_id> --client_secret=<client_secret> +--refresh_token=<refresh_token>` + +For this patch, I didn't add any new configuration, but I'm open to +suggestions on that. + +The patch also only support SASL-IR to reduce round-trips to the server, +but it's certainly possible to change that if we think there are +OAUTHBEARER IMAP servers that don't support SASL-IR. It also requires the +connection to be encrypted as the access token is re-usable for an hour or +so. Again, Gmail only allows encrypted IMAP connections, not sure if any +OAUTHBEARER services allow non-encrypted. + +Turns out that auth failure leaves you in SASL mode, so I have a hack to +issue a noop command on error. Not sure if that's just OAUTHBEARER +oddness, or whether I should be using lower level mutt imap functions. +--- + imap/Makefile.am | 7 +-- + imap/auth.c | 1 + + imap/auth.h | 1 + + imap/auth_oauth.c | 104 ++++++++++++++++++++++++++++++++++++++++++++ + imap/command.c | 1 + + imap/imap_private.h | 1 + + 6 files changed, 112 insertions(+), 3 deletions(-) + create mode 100644 imap/auth_oauth.c + +diff --git a/imap/Makefile.am b/imap/Makefile.am +index 527b044f..199f6d6b 100644 +--- a/imap/Makefile.am ++++ b/imap/Makefile.am +@@ -13,12 +13,13 @@ else + AUTHENTICATORS = auth_anon.c auth_cram.c + endif + +-EXTRA_DIST = README TODO auth_anon.c auth_cram.c auth_gss.c auth_sasl.c ++EXTRA_DIST = README TODO auth_anon.c auth_cram.c auth_gss.c auth_oauth.c \ ++ auth_sasl.c + + AM_CPPFLAGS = -I$(top_srcdir) -I../intl + + noinst_LIBRARIES = libimap.a + noinst_HEADERS = auth.h imap_private.h message.h + +-libimap_a_SOURCES = auth.c auth_login.c browse.c command.c imap.c imap.h \ +- message.c utf7.c util.c $(AUTHENTICATORS) $(GSSSOURCES) ++libimap_a_SOURCES = auth.c auth_login.c auth_oauth.c browse.c command.c \ ++ imap.c imap.h message.c utf7.c util.c $(AUTHENTICATORS) $(GSSSOURCES) +diff --git a/imap/auth.c b/imap/auth.c +index 047531a5..1b26077a 100644 +--- a/imap/auth.c ++++ b/imap/auth.c +@@ -42,6 +42,7 @@ static const imap_auth_t imap_authenticators[] = { + { imap_auth_cram_md5, "cram-md5" }, + #endif + { imap_auth_login, "login" }, ++ { imap_auth_oauth, "oauthbearer" }, + + { NULL, NULL } + }; +diff --git a/imap/auth.h b/imap/auth.h +index 63107947..82ef2f4c 100644 +--- a/imap/auth.h ++++ b/imap/auth.h +@@ -51,5 +51,6 @@ 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); + + #endif /* _IMAP_AUTH_H */ +diff --git a/imap/auth_oauth.c b/imap/auth_oauth.c +new file mode 100644 +index 00000000..0bb5d2c2 +--- /dev/null ++++ b/imap/auth_oauth.c +@@ -0,0 +1,104 @@ ++/* ++ * Copyright (C) 1999-2001,2005 Brendan Cully <bren...@kublai.com> ++ * Copyright (C) 2018 Brandon Long <bl...@fiction.net> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++ */ ++ ++/* IMAP login/authentication code */ ++ ++#if HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include "mutt.h" ++#include "imap_private.h" ++#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) ++{ ++ char* ibuf = NULL; ++ char* oauth_buf = NULL; ++ int len, ilen, oalen; ++ int rc; ++ ++ /* 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; ++ ++ mutt_message _("Authenticating (OAUTHBEARER)..."); ++ ++ /* get auth info */ ++ if (mutt_account_getlogin (&idata->conn->account)) ++ return IMAP_AUTH_FAILURE; ++ ++ /* We get the access token from the "imap_pass" field */ ++ if (mutt_account_getpass (&idata->conn->account)) ++ return IMAP_AUTH_FAILURE; ++ ++ /* Determine the length of the keyed message digest, add 50 for ++ * overhead. ++ */ ++ oalen = strlen (idata->conn->account.user) + ++ strlen (idata->conn->account.host) + ++ strlen (idata->conn->account.pass) + 50; ++ oauth_buf = safe_malloc (oalen); ++ ++ snprintf (oauth_buf, oalen, ++ "n,a=%s,\001host=%s\001port=%d\001auth=Bearer %s\001\001", ++ idata->conn->account.user, idata->conn->account.host, ++ idata->conn->account.port, idata->conn->account.pass); ++ ++ /* ibuf must be long enough to store the base64 encoding of ++ * oauth_buf, plus the additional debris. ++ */ ++ ++ ilen = strlen (oauth_buf) * 2 + 30; ++ ibuf = safe_malloc (ilen); ++ ibuf[0] = '\0'; ++ ++ safe_strcat (ibuf, ilen, "AUTHENTICATE OAUTHBEARER "); ++ len = strlen(ibuf); ++ ++ mutt_to_base64 ((unsigned char*) (ibuf + len), ++ (unsigned char*) oauth_buf, strlen (oauth_buf), ++ ilen - len); ++ ++ /* 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 (&oauth_buf); ++ FREE (&ibuf); ++ ++ if (!rc) ++ { ++ mutt_clear_error(); ++ return IMAP_AUTH_SUCCESS; ++ } ++ ++ /* The error response was in SASL continuation, so "continue" the SASL ++ * to cause a failure and exit SASL input. ++ */ ++ mutt_socket_write (idata->conn, "an noop\r\n"); ++ ++ mutt_error _("OAUTHBEARER authentication failed."); ++ mutt_sleep (2); ++ return IMAP_AUTH_FAILURE; ++} +diff --git a/imap/command.c b/imap/command.c +index c8825981..0d8fcc8b 100644 +--- a/imap/command.c ++++ b/imap/command.c +@@ -64,6 +64,7 @@ static const char * const Capabilities[] = { + "AUTH=CRAM-MD5", + "AUTH=GSSAPI", + "AUTH=ANONYMOUS", ++ "AUTH=OAUTHBEARER", + "STARTTLS", + "LOGINDISABLED", + "IDLE", +diff --git a/imap/imap_private.h b/imap/imap_private.h +index 312fbfe4..d4337cbf 100644 +--- a/imap/imap_private.h ++++ b/imap/imap_private.h +@@ -112,6 +112,7 @@ enum + ACRAM_MD5, /* RFC 2195: CRAM-MD5 authentication */ + AGSSAPI, /* RFC 1731: GSSAPI authentication */ + AUTH_ANON, /* AUTH=ANONYMOUS */ ++ AUTH_OAUTHBEARER, /* RFC 7628: AUTH=OAUTHBEARER */ + STARTTLS, /* RFC 2595: STARTTLS */ + LOGINDISABLED, /* LOGINDISABLED */ + IDLE, /* RFC 2177: IDLE */ +-- +2.18.0.597.ga71716f1ad + diff --git a/debian/patches/upstream/905551-oauthbearer-refresh.patch b/debian/patches/upstream/905551-oauthbearer-refresh.patch new file mode 100644 index 00000000..58bd3fa0 --- /dev/null +++ b/debian/patches/upstream/905551-oauthbearer-refresh.patch @@ -0,0 +1,467 @@ +From: Brandon Long <bl...@fiction.net> +Date: Tue, 26 Jun 2018 15:42:08 -0700 +Subject: Improve OAUTHBEARER support. + +commit 98cc42365ac97b0dfeafadf5561043e06744fcf6 upstream. + +Move token refresh commands to their own config variables. Consolidate +code for refreshing tokens and generating the SASL OAUTHBEARER +argument in account.c. Add support for OAUTHBEARER to pop. + +Fix pop_auth_oauth() mutt_from_base64() call from 1.10.1 release. +--- + account.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++ + account.h | 1 + + globals.h | 3 ++ + imap/auth_oauth.c | 57 ++++++++++----------------------- + init.h | 24 ++++++++++++++ + pop_auth.c | 71 +++++++++++++++++++++++++++++++++++++++-- + smtp.c | 43 +++++-------------------- + 7 files changed, 202 insertions(+), 77 deletions(-) + +diff --git a/account.c b/account.c +index ce71180d..9e7f9154 100644 +--- a/account.c ++++ b/account.c +@@ -238,3 +238,83 @@ void mutt_account_unsetpass (ACCOUNT* account) + { + account->flags &= ~MUTT_ACCT_PASS; + } ++ ++/* 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. ++ */ ++char* mutt_account_getoauthbearer (ACCOUNT* account) ++{ ++ 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; ++ ++ /* The oauthbearer token includes the login */ ++ if (mutt_account_getlogin (account)) ++ return NULL; ++ ++#ifdef USE_IMAP ++ if ((account->type == MUTT_ACCT_TYPE_IMAP) && ImapOauthRefreshCmd) ++ cmd = ImapOauthRefreshCmd; ++#endif ++#ifdef USE_POP ++ else if ((account->type == MUTT_ACCT_TYPE_POP) && PopOauthRefreshCmd) ++ cmd = PopOauthRefreshCmd; ++#endif ++#ifdef USE_SMTP ++ else if ((account->type == MUTT_ACCT_TYPE_SMTP) && SmtpOauthRefreshCmd) ++ cmd = SmtpOauthRefreshCmd; ++#endif ++ ++ if (cmd == NULL) ++ { ++ mutt_error (_("mutt_account_getoauthbearer: No OAUTH refresh command defined")); ++ return NULL; ++ } ++ ++ if ((pid = mutt_create_filter (cmd, NULL, &fp, NULL)) < 0) ++ { ++ mutt_perror _("mutt_account_getoauthbearer: Unable to run refresh command"); ++ return NULL; ++ } ++ ++ /* read line */ ++ token = mutt_read_line (NULL, &token_size, fp, NULL, 0); ++ safe_fclose (&fp); ++ mutt_wait_filter (pid); ++ ++ if (token == NULL || *token == '\0') ++ { ++ mutt_error (_("mutt_account_getoauthbearer: Command returned empty string")); ++ FREE (&token); ++ return NULL; ++ } ++ ++ /* 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); ++ ++ snprintf (oauthbearer, oalen, ++ "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; ++} +diff --git a/account.h b/account.h +index f4d2e4ed..dd9683e2 100644 +--- a/account.h ++++ b/account.h +@@ -57,5 +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); + + #endif /* _MUTT_ACCOUNT_H_ */ +diff --git a/globals.h b/globals.h +index 2aa96f90..df490293 100644 +--- a/globals.h ++++ b/globals.h +@@ -64,6 +64,7 @@ WHERE char *ImapAuthenticators INITVAL (NULL); + WHERE char *ImapDelimChars INITVAL (NULL); + WHERE char *ImapHeaders; + WHERE char *ImapLogin INITVAL (NULL); ++WHERE char *ImapOauthRefreshCmd INITVAL (NULL); + WHERE char *ImapPass INITVAL (NULL); + WHERE char *ImapUser INITVAL (NULL); + #endif +@@ -107,6 +108,7 @@ WHERE char *PipeSep; + WHERE char *PopAuthenticators INITVAL (NULL); + WHERE short PopCheckTimeout; + WHERE char *PopHost; ++WHERE char *PopOauthRefreshCmd INITVAL (NULL); + WHERE char *PopPass INITVAL (NULL); + WHERE char *PopUser INITVAL (NULL); + #endif +@@ -134,6 +136,7 @@ WHERE char *SimpleSearch; + #if USE_SMTP + WHERE char *SmtpAuthenticators INITVAL (NULL); + WHERE char *SmtpPass INITVAL (NULL); ++WHERE char *SmtpOauthRefreshCmd INITVAL (NULL); + WHERE char *SmtpUrl INITVAL (NULL); + #endif /* USE_SMTP */ + WHERE char *Spoolfile; +diff --git a/imap/auth_oauth.c b/imap/auth_oauth.c +index 0bb5d2c2..14e843a5 100644 +--- a/imap/auth_oauth.c ++++ b/imap/auth_oauth.c +@@ -31,8 +31,8 @@ + imap_auth_res_t imap_auth_oauth (IMAP_DATA* idata, const char* method) + { + char* ibuf = NULL; +- char* oauth_buf = NULL; +- int len, ilen, oalen; ++ char* oauthbearer = NULL; ++ int ilen; + int rc; + + /* For now, we only support SASL_IR also and over TLS */ +@@ -43,61 +43,38 @@ imap_auth_res_t imap_auth_oauth (IMAP_DATA* idata, const char* method) + + mutt_message _("Authenticating (OAUTHBEARER)..."); + +- /* get auth info */ +- if (mutt_account_getlogin (&idata->conn->account)) ++ /* 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; + +- /* We get the access token from the "imap_pass" field */ +- if (mutt_account_getpass (&idata->conn->account)) +- return IMAP_AUTH_FAILURE; +- +- /* Determine the length of the keyed message digest, add 50 for +- * overhead. +- */ +- oalen = strlen (idata->conn->account.user) + +- strlen (idata->conn->account.host) + +- strlen (idata->conn->account.pass) + 50; +- oauth_buf = safe_malloc (oalen); +- +- snprintf (oauth_buf, oalen, +- "n,a=%s,\001host=%s\001port=%d\001auth=Bearer %s\001\001", +- idata->conn->account.user, idata->conn->account.host, +- idata->conn->account.port, idata->conn->account.pass); +- +- /* ibuf must be long enough to store the base64 encoding of +- * oauth_buf, plus the additional debris. +- */ +- +- ilen = strlen (oauth_buf) * 2 + 30; ++ ilen = strlen (oauthbearer) + 30; + ibuf = safe_malloc (ilen); +- ibuf[0] = '\0'; +- +- safe_strcat (ibuf, ilen, "AUTHENTICATE OAUTHBEARER "); +- len = strlen(ibuf); +- +- mutt_to_base64 ((unsigned char*) (ibuf + len), +- (unsigned char*) oauth_buf, strlen (oauth_buf), +- ilen - len); ++ snprintf (ibuf, ilen, "AUTHENTICATE OAUTHBEARER %s", oauthbearer); + + /* 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 (&oauth_buf); ++ FREE (&oauthbearer); + FREE (&ibuf); + ++ if (rc) ++ { ++ /* 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 ++ */ ++ mutt_socket_write (idata->conn, "\001"); ++ rc = imap_exec (idata, ibuf, IMAP_CMD_FAIL_OK); ++ } ++ + if (!rc) + { + mutt_clear_error(); + return IMAP_AUTH_SUCCESS; + } + +- /* The error response was in SASL continuation, so "continue" the SASL +- * to cause a failure and exit SASL input. +- */ +- mutt_socket_write (idata->conn, "an noop\r\n"); +- + mutt_error _("OAUTHBEARER authentication failed."); + mutt_sleep (2); + return IMAP_AUTH_FAILURE; +diff --git a/init.h b/init.h +index a5de4636..80ae7252 100644 +--- a/init.h ++++ b/init.h +@@ -1318,6 +1318,14 @@ struct option_t MuttVars[] = { + ** .pp + ** This variable defaults to the value of $$imap_user. + */ ++ { "imap_oauth_refresh_command", DT_STR, R_NONE, UL &ImapOauthRefreshCmd, UL "" }, ++ /* ++ ** .pp ++ ** The command to run to generate an OAUTH refresh token for ++ ** authorizing your connection to your IMAP server. This command will be ++ ** run on every connection attempt that uses the OAUTHBEARER authentication ++ ** mechanism. ++ */ + { "imap_pass", DT_STR, R_NONE, UL &ImapPass, UL 0 }, + /* + ** .pp +@@ -2379,6 +2387,14 @@ struct option_t MuttVars[] = { + ** for retrieving only unread messages from the POP server when using + ** the \fC$<fetch-mail>\fP function. + */ ++ { "pop_oauth_refresh_command", DT_STR, R_NONE, UL &PopOauthRefreshCmd, UL "" }, ++ /* ++ ** .pp ++ ** The command to run to generate an OAUTH refresh token for ++ ** authorizing your connection to your POP server. This command will be ++ ** run on every connection attempt that uses the OAUTHBEARER authentication ++ ** mechanism. ++ */ + { "pop_pass", DT_STR, R_NONE, UL &PopPass, UL "" }, + /* + ** .pp +@@ -3349,6 +3365,14 @@ struct option_t MuttVars[] = { + ** set smtp_authenticators="digest-md5:cram-md5" + ** .te + */ ++ { "smtp_oauth_refresh_command", DT_STR, R_NONE, UL &SmtpOauthRefreshCmd, UL "" }, ++ /* ++ ** .pp ++ ** The command to run to generate an OAUTH refresh token for ++ ** authorizing your connection to your SMTP server. This command will be ++ ** run on every connection attempt that uses the OAUTHBEARER authentication ++ ** mechanism. ++ */ + { "smtp_pass", DT_STR, R_NONE, UL &SmtpPass, UL 0 }, + /* + ** .pp +diff --git a/pop_auth.c b/pop_auth.c +index e3fb51dd..b2d94b2c 100644 +--- a/pop_auth.c ++++ b/pop_auth.c +@@ -49,6 +49,10 @@ static pop_auth_res_t pop_auth_sasl (POP_DATA *pop_data, const char *method) + const char *pc = NULL; + unsigned int len, olen, client_start; + ++ if (mutt_account_getpass (&pop_data->conn->account) || ++ !pop_data->conn->account.pass[0]) ++ return POP_A_FAILURE; ++ + if (mutt_sasl_client_new (pop_data->conn, &saslconn) < 0) + { + dprint (1, (debugfile, "pop_auth_sasl: Error allocating SASL connection.\n")); +@@ -207,6 +211,10 @@ static pop_auth_res_t pop_auth_apop (POP_DATA *pop_data, const char *method) + char buf[LONG_STRING]; + size_t i; + ++ if (mutt_account_getpass (&pop_data->conn->account) || ++ !pop_data->conn->account.pass[0]) ++ return POP_A_FAILURE; ++ + if (!pop_data->timestamp) + return POP_A_UNAVAIL; + +@@ -255,6 +263,10 @@ static pop_auth_res_t pop_auth_user (POP_DATA *pop_data, const char *method) + if (!pop_data->cmd_user) + return POP_A_UNAVAIL; + ++ if (mutt_account_getpass (&pop_data->conn->account) || ++ !pop_data->conn->account.pass[0]) ++ return POP_A_FAILURE; ++ + mutt_message _("Logging in..."); + + snprintf (buf, sizeof (buf), "USER %s\r\n", pop_data->conn->account.user); +@@ -304,7 +316,63 @@ 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) ++{ ++ char *oauthbearer = NULL; ++ char decoded_err[LONG_STRING]; ++ char *err = NULL; ++ char *auth_cmd = NULL; ++ size_t auth_cmd_len; ++ int ret, len; ++ ++ mutt_message _("Authenticating (OAUTHBEARER)..."); ++ ++ oauthbearer = mutt_account_getoauthbearer (&pop_data->conn->account); ++ if (oauthbearer == NULL) ++ return POP_A_FAILURE; ++ ++ 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); ++ ++ ret = pop_query_d (pop_data, auth_cmd, strlen (auth_cmd), ++#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" : ++#endif ++ NULL); ++ FREE (&auth_cmd); ++ ++ switch (ret) ++ { ++ case 0: ++ return POP_A_SUCCESS; ++ case -1: ++ return POP_A_SOCKET; ++ } ++ ++ /* The error response was a SASL continuation, so "continue" it. ++ * See RFC 7628 3.2.3 ++ */ ++ 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) ++ { ++ decoded_err[len] = '\0'; ++ err = decoded_err; ++ } ++ mutt_error ("%s %s", _("Authentication failed."), err); ++ mutt_sleep (2); ++ ++ return POP_A_FAILURE; ++} ++ + static const pop_auth_t pop_authenticators[] = { ++ { pop_auth_oauth, "oauthbearer" }, + #ifdef USE_SASL + { pop_auth_sasl, NULL }, + #endif +@@ -330,8 +398,7 @@ int pop_authenticate (POP_DATA* pop_data) + int attempts = 0; + int ret = POP_A_UNAVAIL; + +- if (mutt_account_getuser (acct) || !acct->user[0] || +- mutt_account_getpass (acct) || !acct->pass[0]) ++ if (mutt_account_getuser (acct) || !acct->user[0]) + return -3; + + if (PopAuthenticators && *PopAuthenticators) +diff --git a/smtp.c b/smtp.c +index 0948af0a..fb4fd125 100644 +--- a/smtp.c ++++ b/smtp.c +@@ -684,51 +684,24 @@ fail: + static int smtp_auth_oauth (CONNECTION* conn) + { + char* ibuf = NULL; +- char* oauth_buf = NULL; +- int len, ilen, oalen; ++ char* oauthbearer = NULL; ++ int ilen; + int rc; + + mutt_message _("Authenticating (OAUTHBEARER)..."); + +- /* get auth info */ +- if (mutt_account_getlogin (&conn->account)) ++ /* We get the access token from the smtp_oauth_refresh_command */ ++ oauthbearer = mutt_account_getoauthbearer (&conn->account); ++ if (oauthbearer == NULL) + return SMTP_AUTH_FAIL; + +- /* We get the access token from the "smtp_pass" field */ +- if (mutt_account_getpass (&conn->account)) +- return SMTP_AUTH_FAIL; +- +- /* Determine the length of the keyed message digest, add 50 for +- * overhead. +- */ +- oalen = strlen (conn->account.user) + +- strlen (conn->account.host) + +- strlen (conn->account.pass) + 50; +- oauth_buf = safe_malloc (oalen); +- +- snprintf (oauth_buf, oalen, +- "n,a=%s,\001host=%s\001port=%d\001auth=Bearer %s\001\001", +- conn->account.user, conn->account.host, conn->account.port, +- conn->account.pass); +- +- /* ibuf must be long enough to store the base64 encoding of +- * oauth_buf, plus the additional debris. +- */ +- +- ilen = strlen (oauth_buf) * 2 + 30; ++ ilen = strlen (oauthbearer) + 30; + ibuf = safe_malloc (ilen); +- ibuf[0] = '\0'; + +- safe_strcat (ibuf, ilen, "AUTH OAUTHBEARER "); +- len = strlen(ibuf); +- +- mutt_to_base64 ((unsigned char*) (ibuf + len), +- (unsigned char*) oauth_buf, strlen (oauth_buf), +- ilen - len); +- safe_strcat (ibuf, ilen, "\r\n"); ++ snprintf (ibuf, ilen, "AUTH OAUTHBEARER %s\r\n", oauthbearer); + + rc = mutt_socket_write (conn, ibuf); +- FREE (&oauth_buf); ++ FREE (&oauthbearer); + FREE (&ibuf); + + if (rc == -1) +-- +2.18.0.597.ga71716f1ad + diff --git a/debian/patches/upstream/905551-oauthbearer-smtp.patch b/debian/patches/upstream/905551-oauthbearer-smtp.patch new file mode 100644 index 00000000..2d4da0ff --- /dev/null +++ b/debian/patches/upstream/905551-oauthbearer-smtp.patch @@ -0,0 +1,190 @@ +From: Brandon Long <bl...@fiction.net> +Date: Tue, 12 Jun 2018 14:11:47 -0700 +Subject: Support for using OAUTHBEARER for smtp. + +commit fcd333986c0d15dec67870b7b74fef0e00e8c28b upstream. + +This also means a bunch of smtp auth stuff is now compiled in by +default (with --enable-smtp) without having sasl +--- + init.h | 2 -- + smtp.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++-------- + 2 files changed, 88 insertions(+), 15 deletions(-) + +diff --git a/init.h b/init.h +index 44d7fabd..983fde4b 100644 +--- a/init.h ++++ b/init.h +@@ -3333,7 +3333,6 @@ struct option_t MuttVars[] = { + ** (S/MIME only) + */ + #ifdef USE_SMTP +-# ifdef USE_SASL + { "smtp_authenticators", DT_STR, R_NONE, UL &SmtpAuthenticators, UL 0 }, + /* + ** .pp +@@ -3350,7 +3349,6 @@ struct option_t MuttVars[] = { + ** set smtp_authenticators="digest-md5:cram-md5" + ** .te + */ +-# endif /* USE_SASL */ + { "smtp_pass", DT_STR, R_NONE, UL &SmtpPass, UL 0 }, + /* + ** .pp +diff --git a/smtp.c b/smtp.c +index 1d147a5a..0948af0a 100644 +--- a/smtp.c ++++ b/smtp.c +@@ -66,8 +66,9 @@ enum { + CAPMAX + }; + +-#ifdef USE_SASL + static int smtp_auth (CONNECTION* conn); ++static int smtp_auth_oauth (CONNECTION* conn); ++#ifdef USE_SASL + static int smtp_auth_sasl (CONNECTION* conn, const char* mechanisms); + #endif + +@@ -495,19 +496,12 @@ static int smtp_open (CONNECTION* conn) + return -1; + } + +-#ifdef USE_SASL + return smtp_auth (conn); +-#else +- mutt_error (_("SMTP authentication requires SASL")); +- mutt_sleep (1); +- return -1; +-#endif /* USE_SASL */ + } + + return 0; + } + +-#ifdef USE_SASL + static int smtp_auth (CONNECTION* conn) + { + int r = SMTP_AUTH_UNAVAIL; +@@ -528,21 +522,41 @@ static int smtp_auth (CONNECTION* conn) + + dprint (2, (debugfile, "smtp_authenticate: Trying method %s\n", method)); + +- r = smtp_auth_sasl (conn, method); +- ++ if (!strcmp (method, "oauthbearer")) ++ { ++ r = smtp_auth_oauth (conn); ++ } ++ else ++ { ++#ifdef USE_SASL ++ r = smtp_auth_sasl (conn, method); ++#else ++ mutt_error (_("SMTP authentication method %s requires SASL"), method); ++ mutt_sleep (1); ++ continue; ++#endif ++ } + if (r == SMTP_AUTH_FAIL && delim) + { +- mutt_error (_("%s authentication failed, trying next method"), method); +- mutt_sleep (1); ++ mutt_error (_("%s authentication failed, trying next method"), method); ++ mutt_sleep (1); + } + else if (r != SMTP_AUTH_UNAVAIL) +- break; ++ break; + } + + FREE (&methods); + } + else ++ { ++#ifdef USE_SASL + r = smtp_auth_sasl (conn, AuthMechs); ++#else ++ mutt_error (_("SMTP authentication requires SASL")); ++ mutt_sleep (1); ++ r = SMTP_AUTH_UNAVAIL; ++#endif ++ } + + if (r != SMTP_AUTH_SUCCESS) + mutt_account_unsetpass (&conn->account); +@@ -561,6 +575,7 @@ static int smtp_auth (CONNECTION* conn) + return r == SMTP_AUTH_SUCCESS ? 0 : -1; + } + ++#ifdef USE_SASL + static int smtp_auth_sasl (CONNECTION* conn, const char* mechlist) + { + sasl_conn_t* saslconn; +@@ -663,3 +678,63 @@ fail: + return SMTP_AUTH_FAIL; + } + #endif /* USE_SASL */ ++ ++ ++/* smtp_auth_oauth: AUTH=OAUTHBEARER support. See RFC 7628 */ ++static int smtp_auth_oauth (CONNECTION* conn) ++{ ++ char* ibuf = NULL; ++ char* oauth_buf = NULL; ++ int len, ilen, oalen; ++ int rc; ++ ++ mutt_message _("Authenticating (OAUTHBEARER)..."); ++ ++ /* get auth info */ ++ if (mutt_account_getlogin (&conn->account)) ++ return SMTP_AUTH_FAIL; ++ ++ /* We get the access token from the "smtp_pass" field */ ++ if (mutt_account_getpass (&conn->account)) ++ return SMTP_AUTH_FAIL; ++ ++ /* Determine the length of the keyed message digest, add 50 for ++ * overhead. ++ */ ++ oalen = strlen (conn->account.user) + ++ strlen (conn->account.host) + ++ strlen (conn->account.pass) + 50; ++ oauth_buf = safe_malloc (oalen); ++ ++ snprintf (oauth_buf, oalen, ++ "n,a=%s,\001host=%s\001port=%d\001auth=Bearer %s\001\001", ++ conn->account.user, conn->account.host, conn->account.port, ++ conn->account.pass); ++ ++ /* ibuf must be long enough to store the base64 encoding of ++ * oauth_buf, plus the additional debris. ++ */ ++ ++ ilen = strlen (oauth_buf) * 2 + 30; ++ ibuf = safe_malloc (ilen); ++ ibuf[0] = '\0'; ++ ++ safe_strcat (ibuf, ilen, "AUTH OAUTHBEARER "); ++ len = strlen(ibuf); ++ ++ mutt_to_base64 ((unsigned char*) (ibuf + len), ++ (unsigned char*) oauth_buf, strlen (oauth_buf), ++ ilen - len); ++ safe_strcat (ibuf, ilen, "\r\n"); ++ ++ rc = mutt_socket_write (conn, ibuf); ++ FREE (&oauth_buf); ++ FREE (&ibuf); ++ ++ if (rc == -1) ++ return SMTP_AUTH_FAIL; ++ if (smtp_get_resp (conn) != 0) ++ return SMTP_AUTH_FAIL; ++ ++ return SMTP_AUTH_SUCCESS; ++} +-- +2.18.0.597.ga71716f1ad + -- 2.18.0.597.ga71716f1ad