]> granicus.if.org Git - mutt/commitdiff
Improve OAUTHBEARER support.
authorBrandon Long <blong@fiction.net>
Tue, 26 Jun 2018 22:42:08 +0000 (15:42 -0700)
committerKevin McCarthy <kevin@8t8.us>
Tue, 17 Jul 2018 02:21:45 +0000 (19:21 -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.

account.c
account.h
globals.h
imap/auth_oauth.c
init.h
pop_auth.c
smtp.c

index ce71180d5392f29d49b3b4b10e526a45e55ccfce..9e7f915416ac5ac437ed243fd5e58cf46780be9a 100644 (file)
--- 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;
+}
index f4d2e4ed299433506b4fae74371896fa3a5f51cc..dd9683e21d4ac122f42357b2a223c57b5a98db26 100644 (file)
--- 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_ */
index 116ba60b0a5c38291d539a06f6efb149b9cc2319..cecb46d619e5356de34187b0afc633067edc89e3 100644 (file)
--- 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;
index 0bb5d2c281105bca74de1fc8e5e7326ebb65a1c6..14e843a54d85b0c8c8c94b9a6ed64efb4d32ab2b 100644 (file)
@@ -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 e339dc4e8c80b55339a4f01a4dc3a2a279b8577b..c1724c13ebb332cb5e22a5c2ad06688c4fa986bf 100644 (file)
--- a/init.h
+++ b/init.h
@@ -1320,6 +1320,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
@@ -2385,6 +2393,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
@@ -3355,6 +3371,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
index e3fb51dd36285721d5d550049d3c4717f0179a38..b2d94b2c3b11da935b0b62c2ed0e11b3b41e543c 100644 (file)
@@ -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 0948af0afc55fdefa6a46f6ea8d2c85ce5f38c8d..fb4fd1256f7b51a98b866e94e7de7bff6d020a7b 100644 (file)
--- 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)