diff options
author | Brandon Long <blong@fiction.net> | 2018-06-26 15:42:08 -0700 |
---|---|---|
committer | Kevin McCarthy <kevin@8t8.us> | 2018-07-16 19:21:45 -0700 |
commit | 98cc42365ac97b0dfeafadf5561043e06744fcf6 (patch) | |
tree | 9c82c0088a9cfe90ae6c578811eef6fbd7954fe2 | |
parent | 363c3a95a8c5ed55a4ab8618363aa848453f3d19 (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.c | 80 | ||||
-rw-r--r-- | account.h | 1 | ||||
-rw-r--r-- | globals.h | 3 | ||||
-rw-r--r-- | imap/auth_oauth.c | 57 | ||||
-rw-r--r-- | init.h | 24 | ||||
-rw-r--r-- | pop_auth.c | 71 | ||||
-rw-r--r-- | smtp.c | 43 |
7 files changed, 202 insertions, 77 deletions
@@ -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; +} @@ -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_ */ @@ -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; @@ -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 @@ -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) @@ -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) |