From: Brandon Long Date: Tue, 26 Jun 2018 22:42:08 +0000 (-0700) Subject: Improve OAUTHBEARER support. X-Git-Tag: 2019-10-25~666^2~2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=96da7f5c68b80ab4afbeeb856ebdf2d81a29e62c;p=neomutt 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. --- diff --git a/imap/auth_oauth.c b/imap/auth_oauth.c index f343d45ba..dde34ae88 100644 --- a/imap/auth_oauth.c +++ b/imap/auth_oauth.c @@ -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 696946b4e..688010043 100644 --- 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$\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 diff --git a/mutt_account.c b/mutt_account.c index fac54c054..aa5b43636 100644 --- a/mutt_account.c +++ b/mutt_account.c @@ -34,17 +34,21 @@ #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; +} diff --git a/mutt_account.h b/mutt_account.h index 7a597eabf..492f2a14a 100644 --- a/mutt_account.h +++ b/mutt_account.h @@ -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 */ diff --git a/pop/pop.c b/pop/pop.c index 62fc18f7c..185711f9a 100644 --- 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 */ diff --git a/pop/pop.h b/pop/pop.h index 35d6a86eb..41163f9b1 100644 --- 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 */ diff --git a/pop/pop_auth.c b/pop/pop_auth.c index f92a14d36..56e88ca3e 100644 --- a/pop/pop_auth.c +++ b/pop/pop_auth.c @@ -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 21977acec..330892451 100644 --- 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)