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

Reply via email to