summaryrefslogtreecommitdiff
path: root/alpine/imap.c
diff options
context:
space:
mode:
authorEduardo Chappa <chappa@washington.edu>2020-07-09 00:16:36 -0600
committerEduardo Chappa <chappa@washington.edu>2020-07-09 00:16:36 -0600
commit0f89ad88df81df9d2ca7eafa276fecf8206fb598 (patch)
treeb8c24c61a7b9959241c5bfc0d95b7fe2f595c323 /alpine/imap.c
parent560dd4993146181912c3bb98f6b26f7ed7f4de5d (diff)
downloadalpine-0f89ad88df81df9d2ca7eafa276fecf8206fb598.tar.xz
* Add choice of Authorization flow to Alpine. Alpine supports two ways to get
authorization to read email. One is called "Authorize" and the other "Device". Some servers support both, some only one. For servers that support both, Alpine will ask if it does not know which method to choose. Inspired by a conversation with Pieter Jacques.
Diffstat (limited to 'alpine/imap.c')
-rw-r--r--alpine/imap.c227
1 files changed, 211 insertions, 16 deletions
diff --git a/alpine/imap.c b/alpine/imap.c
index 03685707..fe4fdcec 100644
--- a/alpine/imap.c
+++ b/alpine/imap.c
@@ -40,6 +40,8 @@ static char rcsid[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington
#include "busy.h"
#include "titlebar.h"
#include "xoauth2.h"
+#include "xoauth2conf.h"
+#include "confscroll.h"
#include "init.h"
#include "../pith/state.h"
#include "../pith/conf.h"
@@ -103,6 +105,8 @@ void mm_login_alt_cue(NETMBX *);
long pine_tcptimeout_noscreen(long, long, char *);
int answer_cert_failure(int, MSGNO_S *, SCROLL_S *);
int oauth2_auth_answer(int, MSGNO_S *, SCROLL_S *);
+OAUTH2_S *oauth2_select_flow(char *);
+int xoauth2_flow_tool(struct pine *, int, CONF_S **, unsigned int);
#ifdef LOCAL_PASSWD_CACHE
int read_passfile(char *, MMLOGIN_S **);
@@ -199,9 +203,159 @@ OAUTH2_S alpine_oauth2_list[] =
0, /* first time indicator */
0 /* client secret required */
},
+ {"Outlook",
+ {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
+ {{"client_id", NULL},
+ {"client_secret", NULL}, /* not used, but needed */
+ {"tenant", NULL}, /* used */
+ {"code", NULL}, /* used during authorization */
+ {"refresh_token", NULL},
+ {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
+ {"redirect_uri", "http://localhost"},
+ {"grant_type", "authorization_code"},
+ {"grant_type", "refresh_token"},
+ {"response_type", "code"},
+ {"state", NULL}, /* not used */
+ {"device_code", NULL} /* not used */
+ },
+ {{"GET", "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
+ {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
+ {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
+ {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
+ {OA2_Id, OA2_Redirect, OA2_Scope, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End}},
+ {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
+ {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End}}
+ },
+ {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
+ NULL, /* access token */
+ 0, /* expiration time */
+ 0, /* first time indicator */
+ 1 /* client secret required */
+ },
{ NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0},
};
+int
+xoauth2_flow_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0;
+
+ switch(cmd){
+ case MC_CHOICE:
+ *((*cl)->d.xf.selected) = (*cl)->d.xf.pat;
+ rv = simple_exit_cmd(flags);
+
+ case MC_EXIT:
+ rv = simple_exit_cmd(flags);
+ break;
+
+ default:
+ rv = -1;
+ }
+
+ if(rv > 0)
+ ps->mangled_body = 1;
+
+ return rv;
+}
+
+OAUTH2_S *
+oauth2_select_flow(char *host)
+{
+ OAUTH2_S *oa2list, *oa2;
+ int i, n, rv;
+ char *method;
+
+ if(ps_global->ttyo){
+ CONF_S *ctmp = NULL, *first_line = NULL;
+ OAUTH2_S *x_sel = NULL;
+ OPT_SCREEN_S screen;
+ char tmp[1024];
+
+ dprint((9, "xoauth2 select flow"));
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ memset(&screen, 0, sizeof(screen));
+
+ for(i = 0; i < sizeof(tmp) && i < ps_global->ttyo->screen_cols; i++)
+ tmp[i] = '-';
+ tmp[i] = '\0';
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(tmp);
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(_("Please select below the authorization flow you would like to follow:"));
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(tmp);
+
+ for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
+ for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
+ if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
+ new_confline(&ctmp);
+ if(!first_line)
+ first_line = ctmp;
+ method = oa2list->server_mthd[0].name ? "Authorize"
+ : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
+ sprintf(tmp, "%s (%s)", oa2list->name, method);
+ ctmp->value = cpystr(tmp);
+ ctmp->d.xf.selected = &x_sel;
+ ctmp->d.xf.pat = oa2list;
+ ctmp->keymenu = &xoauth2_id_select_km;
+ ctmp->help = NO_HELP;
+ ctmp->help_title = NULL;
+ ctmp->tool = xoauth2_flow_tool;
+ ctmp->flags = CF_STARTITEM;
+ ctmp->valoffset = 4;
+ }
+ }
+ (void)conf_scroll_screen(ps_global, &screen, first_line, _("SELECT AUTHORIZATION FLOW"),
+ _("xoauth2"), 0, NULL);
+ oa2 = x_sel;
+ }
+ else{
+ char *s;
+ char prompt[1024];
+ char reply[1024];
+ int sel, j;
+
+ for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++)
+ n += strlen(oa2list->name); + 5; /* number, parenthesis, space */
+ n += 1024; /* large enough to display to lines of 80 characters in UTF-8 */
+ s = fs_get(n*sizeof(char));
+ strcpy(s, _("Please select below the authorization flow you would like to follow:"));
+ sprintf(s + strlen(s), _("Please select the client-id to use from the following list.\n\n"));
+ for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
+ for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
+ if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i])
+ sprintf(s + strlen(s), " %d) %.70s\n", j++, oa2list->name);
+ }
+ display_init_err(s, 0);
+
+ strncpy(prompt, _("Enter your selection number: "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ do{
+ rv = optionally_enter(reply, 0, 0, sizeof(reply), prompt, NULL, NO_HELP, 0);
+ sel = atoi(reply);
+ rv = (sel >= 0 && sel < i) ? 0 : -1;
+ } while (rv != 0);
+
+ for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
+ for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
+ if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
+ if(j == sel) break;
+ else j++;
+ }
+ }
+ oa2 = oa2list;
+ }
+ return oa2;
+}
+
typedef struct auth_code_s {
char *code;
int answer;
@@ -512,7 +666,8 @@ oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int *
ps_global->painted_footer_on_startup = 0;
} while (user_input.answer != 'e');
- if(!struncmp(user_input.code, "https://", 8)){
+ if(!struncmp(user_input.code, "http://", 7)
+ || !struncmp(user_input.code, "https://", 8)){
char *s, *t;
s = strstr(user_input.code, "code=");
if(s != NULL){
@@ -594,7 +749,8 @@ try_wantto:
rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
prompt, NULL, NO_HELP, &flags);
} while (rc != 0 && rc != 1);
- if(!struncmp(token, "https://", 8)){
+ if(!struncmp(token, "http://", 7)
+ || !struncmp(token, "https://", 8)){
char *s, *t;
s = strstr(token, "code=");
if(s != NULL){
@@ -680,7 +836,9 @@ mm_login_oauth2(NETMBX *mb, char *user, char *method,
int save_in_init;
int registered;
int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
- OAUTH2_S *oa2list;
+ OAUTH2_S *oa2list, *oa2;
+ XOAUTH2_INFO_S *x;
+
unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
#if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
int preserve_password = -1;
@@ -723,13 +881,25 @@ mm_login_oauth2(NETMBX *mb, char *user, char *method,
}
}
+ if(trial == 0L && !altuserforcache){
+ if(*mb->user != '\0')
+ strncpy(user, mb->user, NETMAXUSER);
+ else{
+ flags = OE_APPEND_CURRENT;
+ sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
+ rc = optionally_enter(user, q_line, 0, NETMAXUSER,
+ prompt, NULL, NO_HELP, &flags);
+ }
+ user[NETMAXUSER-1] = '\0';
+ }
+
/*
* We check to see if the server we are going to log in to is already
* registered. This gives us a list of servers with the same
* credentials, so we use the same credentials for all of them.
*/
- for(registered = 0, oa2list = alpine_oauth2_list;
+ for(registered = 0, oa2list = alpine_oauth2_list;
oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
oa2list++){
for(i = 0; i < OAUTH2_TOT_EQUIV
@@ -742,6 +912,25 @@ mm_login_oauth2(NETMBX *mb, char *user, char *method,
}
if(registered){
+ x = oauth2_get_client_info(oa2list->name, user);
+ if(x && x->flow){
+ for(oa2list = alpine_oauth2_list;
+ oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
+ oa2list++){
+ for(i = 0; i < OAUTH2_TOT_EQUIV
+ && oa2list->host[i] != NULL
+ && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
+ if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
+ char *flow = oa2list->server_mthd[0].name ? "Authorize"
+ : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
+ if(!strucmp(x->flow, flow)) break; /* found it */
+ }
+ }
+ }
+ /* else use the one we found earlier, the user has to configure this better */
+ }
+
+ if(registered){
hostlist2[i = 0].name = mb->host;
if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
hostlist2[++i].name = mb->orighost;
@@ -766,18 +955,6 @@ mm_login_oauth2(NETMBX *mb, char *user, char *method,
* and use the access token.
*/
if(trial == 0L && !altuserforcache){
- int code;
-
- if(*mb->user != '\0')
- strncpy(user, mb->user, NETMAXUSER);
- else{
- flags = OE_APPEND_CURRENT;
- sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
- rc = optionally_enter(user, q_line, 0, NETMAXUSER,
- prompt, NULL, NO_HELP, &flags);
- }
- user[NETMAXUSER-1] = '\0';
-
/* Search for a refresh token that is already loaded ... */
if(imap_get_passwd_auth(mm_login_list, &token, user,
registered ? hostlist2 : hostlist,
@@ -845,6 +1022,24 @@ mm_login_oauth2(NETMBX *mb, char *user, char *method,
}
else login->first_time++;
+ if(login->first_time){ /* count how many authorization methods we support */
+ int nmethods, i, j;
+
+ for(nmethods = 0, oa2 = alpine_oauth2_list; oa2 && oa2->name ; oa2++){
+ for(j = 0; j < OAUTH2_TOT_EQUIV
+ && oa2
+ && oa2->host[j] != NULL
+ && strucmp(oa2->host[j], mb->orighost) != 0; j++);
+ if(oa2 && oa2->host && j < OAUTH2_TOT_EQUIV && oa2->host[j])
+ nmethods++;
+ }
+
+ if(nmethods > 1)
+ oa2list = oauth2_select_flow(mb->orighost);
+
+ if(!oa2list) registered = 0;
+ }
+
/* Default to saving what we already had saved */
SaveRefreshToken = NewRefreshToken;