{
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;
+}
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_ */
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
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
#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;
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 */
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;
** .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
** 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
** 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
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"));
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;
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);
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
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)
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)