summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrandon Long <blong@fiction.net>2018-06-26 15:42:08 -0700
committerKevin McCarthy <kevin@8t8.us>2018-07-16 19:21:45 -0700
commit98cc42365ac97b0dfeafadf5561043e06744fcf6 (patch)
tree9c82c0088a9cfe90ae6c578811eef6fbd7954fe2
parent363c3a95a8c5ed55a4ab8618363aa848453f3d19 (diff)
Improve OAUTHBEARER support.
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.
-rw-r--r--account.c80
-rw-r--r--account.h1
-rw-r--r--globals.h3
-rw-r--r--imap/auth_oauth.c57
-rw-r--r--init.h24
-rw-r--r--pop_auth.c71
-rw-r--r--smtp.c43
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 116ba60b..cecb46d6 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 e339dc4e..c1724c13 100644
--- 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
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)