]> granicus.if.org Git - neomutt/commitdiff
Improve OAUTHBEARER support.
authorBrandon Long <blong@fiction.net>
Tue, 26 Jun 2018 22:42:08 +0000 (15:42 -0700)
committerRichard Russon <rich@flatcap.org>
Tue, 26 Jun 2018 22:42:08 +0000 (15:42 -0700)
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.

imap/auth_oauth.c
init.h
mutt_account.c
mutt_account.h
pop/pop.c
pop/pop.h
pop/pop_auth.c
smtp.c

index f343d45baee20a17c214ada86553f8ca24f40ae3..dde34ae88ed28a8d22a3d6183db13b52f2f63556 100644 (file)
@@ -34,8 +34,8 @@
 enum ImapAuthRes imap_auth_oauth(struct ImapData *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 */
@@ -45,58 +45,38 @@ enum ImapAuthRes imap_auth_oauth(struct ImapData *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 = mutt_str_strlen(idata->conn->account.user) +
-          mutt_str_strlen(idata->conn->account.host) +
-          mutt_str_strlen(idata->conn->account.pass) + 50;
-  oauth_buf = mutt_mem_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 = mutt_str_strlen(oauth_buf) * 2 + 30;
+  ilen = mutt_str_strlen(oauthbearer) + 30;
   ibuf = mutt_mem_malloc(ilen);
-  ibuf[0] = '\0';
-
-  mutt_str_strcat(ibuf, ilen, "AUTHENTICATE OAUTHBEARER ");
-  len = mutt_str_strlen(ibuf);
-
-  mutt_b64_encode(oauth_buf, mutt_str_strlen(oauth_buf), (ibuf + len), 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_send(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_send(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 696946b4e25113a3e0eea9182f42127ef6256ce4..688010043c2e0ebe85cfd6400be3b8f87724a063 100644 (file)
--- a/init.h
+++ b/init.h
@@ -1476,6 +1476,14 @@ struct ConfigDef MuttVars[] = {
   ** .pp
   ** This variable defaults to the value of $$imap_user.
   */
+  { "imap_oauth_refresh_command", DT_STRING, R_NONE, &ImapOauthRefreshCmd, 0 },
+  /*
+  ** .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_STRING,  R_NONE|F_SENSITIVE, &ImapPass, 0 },
   /*
   ** .pp
@@ -2764,6 +2772,14 @@ struct ConfigDef MuttVars[] = {
   ** for retrieving only unread messages from the POP server when using
   ** the \fC$<fetch-mail>\fP function.
   */
+  { "pop_oauth_refresh_command", DT_STRING, R_NONE, &PopOauthRefreshCmd, 0 },
+  /*
+  ** .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_STRING,  R_NONE|F_SENSITIVE, &PopPass, 0 },
   /*
   ** .pp
@@ -3810,6 +3826,14 @@ struct ConfigDef MuttVars[] = {
   ** set smtp_authenticators="digest-md5:cram-md5"
   ** .te
   */
+  { "smtp_oauth_refresh_command", DT_STRING, R_NONE, &SmtpOauthRefreshCmd, 0 },
+  /*
+  ** .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_STRING,  R_NONE|F_SENSITIVE, &SmtpPass, 0 },
   /*
   ** .pp
index fac54c05468413566cfe23dbbe3bfa4ab6f55233..aa5b43636601f34f9ac8c1ea4f02564ed1933184 100644 (file)
 #include "conn/conn.h"
 #include "mutt_account.h"
 #include "curs_lib.h"
+#include "filter.h"
 #include "globals.h"
 #include "options.h"
+#include "pop/pop.h"
 
 /* These Config Variables are only used in mutt_account.c */
 char *ImapLogin; ///< Config: (imap) Login name for the IMAP server (defaults to ImapUser)
+char *ImapOauthRefreshCmd;
 char *ImapPass; ///< Config: (imap) Password for the IMAP server
 char *NntpPass; ///< Config: (nntp) Password for the news server
 char *NntpUser; ///< Config: (nntp) Username for the news server
 char *PopPass;  ///< Config: (pop) Password of the POP server
 char *PopUser;  ///< Config: (pop) Username of the POP server
 char *SmtpPass; ///< Config: (smtp) Password for the SMTP server
+char *SmtpOauthRefreshCmd;
 
 /**
  * mutt_account_match - Compare account info (host/port/user)
@@ -330,3 +334,82 @@ void mutt_account_unsetpass(struct 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(struct 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_file_read_line(NULL, &token_size, fp, NULL, 0);
+  mutt_file_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 = mutt_mem_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 = mutt_mem_malloc(encoded_len);
+  mutt_b64_encode(oauthbearer, strlen(oauthbearer), encoded_token, encoded_len);
+  FREE(&oauthbearer);
+  return encoded_token;
+}
index 7a597eabff84d2d91c9c77ff776fbf30c5b2239a..492f2a14a76e6287f6f43fb547dd2b59e5cf1f61 100644 (file)
@@ -30,12 +30,14 @@ struct Url;
 
 /* These Config Variables are only used in mutt_account.c */
 extern char *ImapLogin;
+extern char *ImapOauthRefreshCmd;
 extern char *ImapPass;
 extern char *NntpPass;
 extern char *NntpUser;
 extern char *PopPass;
 extern char *PopUser;
 extern char *SmtpPass;
+extern char *SmtpOauthRefreshCmd;
 
 /**
  * enum AccountType - account types
@@ -63,5 +65,6 @@ int mutt_account_getuser(struct Account *account);
 int mutt_account_getlogin(struct Account *account);
 int mutt_account_getpass(struct Account *account);
 void mutt_account_unsetpass(struct Account *account);
+char *mutt_account_getoauthbearer(struct Account *account);
 
 #endif /* _MUTT_ACCOUNT_H */
index 62fc18f7c319990c6f647f38c04b0ba5642e3a0c..185711f9aaf1ed33bcec30bbaebf9c0435d2c8af 100644 (file)
--- a/pop/pop.c
+++ b/pop/pop.c
@@ -67,7 +67,8 @@ struct BodyCache;
 short PopCheckinterval; ///< Config: (pop) Interval between checks for new mail
 unsigned char PopDelete; ///< Config: (pop) After downloading POP messages, delete them on the server
 char *PopHost; ///< Config: (pop) Url of the POP server
-bool PopLast;  ///< Config: (pop) Use the 'LAST' command to fetch new mail
+char *PopOauthRefreshCmd;
+bool PopLast; ///< Config: (pop) Use the 'LAST' command to fetch new mail
 
 #ifdef USE_HCACHE
 #define HC_FNAME "neomutt" /* filename for hcache as POP lacks paths */
index 35d6a86eb92fe42afe8504bc9e7f9fb02b9fefb6..41163f9b1019559aa6fbff58968e139f49320317 100644 (file)
--- a/pop/pop.h
+++ b/pop/pop.h
@@ -42,6 +42,7 @@
 extern short         PopCheckinterval;
 extern unsigned char PopDelete;
 extern char *        PopHost;
+extern char *PopOauthRefreshCmd;
 extern bool          PopLast;
 
 /* These Config Variables are only used in pop/pop_auth.c */
index f92a14d36c89861b08629c120d28bfda30b757d3..56e88ca3efcba7ab0d1c28bea530ba3ce7d20056 100644 (file)
@@ -65,6 +65,9 @@ static enum PopAuthRes pop_auth_sasl(struct PopData *pop_data, const char *metho
   const char *pc = NULL;
   unsigned int len = 0, olen = 0, 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)
   {
     mutt_debug(1, "Error allocating SASL connection.\n");
@@ -233,6 +236,9 @@ static enum PopAuthRes pop_auth_apop(struct PopData *pop_data, const char *metho
   char hash[33];
   char buf[LONG_STRING];
 
+  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;
 
@@ -281,6 +287,9 @@ static enum PopAuthRes pop_auth_user(struct PopData *pop_data, const char *metho
   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);
@@ -326,13 +335,66 @@ static enum PopAuthRes pop_auth_user(struct PopData *pop_data, const char *metho
   return POP_A_FAILURE;
 }
 
+/* OAUTHBEARER authenticator */
+static enum PopAuthRes pop_auth_oauth(struct PopData *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 = mutt_mem_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_send(pop_data->conn, "\001");
+
+  err = pop_data->err_msg;
+  len = mutt_b64_decode(pop_data->err_msg, decoded_err, sizeof(decoded_err) - 1);
+  if (len >= 0)
+  {
+    decoded_err[len] = '\0';
+    err = decoded_err;
+  }
+  mutt_error("%s %s", _("Authentication failed."), err);
+
+  return POP_A_FAILURE;
+}
+
 static const struct PopAuth pop_authenticators[] = {
+  { pop_auth_oauth, "oauthbearer" },
 #ifdef USE_SASL
   { pop_auth_sasl, NULL },
 #endif
-  { pop_auth_apop, "apop" },
-  { pop_auth_user, "user" },
-  { NULL, NULL },
+  { pop_auth_apop, "apop" },         { pop_auth_user, "user" }, { NULL, NULL },
 };
 
 /**
@@ -354,8 +416,7 @@ int pop_authenticate(struct PopData *pop_data)
   int attempts = 0;
   int ret = POP_A_UNAVAIL;
 
-  if ((mutt_account_getuser(acct) < 0) || (acct->user[0] == '\0') ||
-      (mutt_account_getpass(acct) < 0) || (acct->pass[0] == '\0'))
+  if ((mutt_account_getuser(acct) < 0) || (acct->user[0] == '\0'))
   {
     return -3;
   }
diff --git a/smtp.c b/smtp.c
index 21977acecc87373a166e0ce20303233d2decf81d..3308924519490882444b57742d8c7e9e81d49c26 100644 (file)
--- a/smtp.c
+++ b/smtp.c
@@ -507,47 +507,23 @@ fail:
 static int smtp_auth_oauth(struct 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 = mutt_mem_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 = mutt_mem_malloc(ilen);
-  ibuf[0] = '\0';
-
-  mutt_str_strcat(ibuf, ilen, "AUTH OAUTHBEARER ");
-  len = strlen(ibuf);
-
-  mutt_b64_encode(oauth_buf, strlen(oauth_buf), (ibuf + len), ilen - len);
-  mutt_str_strcat(ibuf, ilen, "\r\n");
+  snprintf(ibuf, ilen, "AUTH OAUTHBEARER %s\r\n", oauthbearer);
 
   rc = mutt_socket_send(conn, ibuf);
-  FREE(&oauth_buf);
+  FREE(&oauthbearer);
   FREE(&ibuf);
 
   if (rc == -1)