From 0f89ad88df81df9d2ca7eafa276fecf8206fb598 Mon Sep 17 00:00:00 2001 From: Eduardo Chappa Date: Thu, 9 Jul 2020 00:16:36 -0600 Subject: * 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. --- alpine/conftype.h | 4 + alpine/imap.c | 227 ++++++++++++++++++++++++++++++++++++++--- alpine/keymenu.c | 15 +++ alpine/keymenu.h | 1 + alpine/xoauth2.h | 2 +- alpine/xoauth2conf.c | 98 +++++++++++++++--- imap/src/c-client/mail.h | 1 + imap/src/c-client/oauth2_aux.c | 2 + pith/pine.hlp | 36 ++++++- 9 files changed, 352 insertions(+), 34 deletions(-) diff --git a/alpine/conftype.h b/alpine/conftype.h index 9c25ad6b..21c7e456 100644 --- a/alpine/conftype.h +++ b/alpine/conftype.h @@ -104,6 +104,10 @@ typedef struct conf_line { XOAUTH2_INFO_S *pat; XOAUTH2_INFO_S **selected; } x; + struct xoauth2_flow { + OAUTH2_S *pat; + OAUTH2_S **selected; /* of type XOAUTH2_S */ + } xf; } d; } CONF_S; 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 @@ -741,6 +911,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)) @@ -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; diff --git a/alpine/keymenu.c b/alpine/keymenu.c index 8d94e878..47350d69 100644 --- a/alpine/keymenu.c +++ b/alpine/keymenu.c @@ -1797,6 +1797,21 @@ struct key role_select_keys[] = WHEREIS_MENU}; INST_KEY_MENU(role_select_km, role_select_keys); +struct key xoauth2_flow_select_keys[] = + {HELP_MENU, + NULL_MENU, + {"E", N_("Exit"), {MC_EXIT,1,{'e'}}, KS_EXITMODE}, + {"S", "[" N_("Select") "]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE}, + {"P", N_("PrevFlow"), {MC_PREVITEM, 1, {'p'}}, KS_NONE}, + {"N", N_("NextFlow"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE}, + PREVPAGE_MENU, + NEXTPAGE_MENU, + HOMEKEY_MENU, + ENDKEY_MENU, + NULL_MENU, + WHEREIS_MENU}; +INST_KEY_MENU(xoauth2_flow_select_km, xoauth2_flow_select_keys); + struct key xoauth2_id_select_keys[] = {HELP_MENU, NULL_MENU, diff --git a/alpine/keymenu.h b/alpine/keymenu.h index 3cf4e2cc..4664ed90 100644 --- a/alpine/keymenu.h +++ b/alpine/keymenu.h @@ -651,6 +651,7 @@ extern struct key_menu cancel_keymenu, config_checkbox_keymenu, config_text_keymenu, xoauth2_id_select_km, + xoauth2_flow_select_km, config_xoauth2_text_keymenu, config_text_to_charsets_keymenu, config_radiobutton_keymenu, diff --git a/alpine/xoauth2.h b/alpine/xoauth2.h index a50a1e8f..739a3b71 100644 --- a/alpine/xoauth2.h +++ b/alpine/xoauth2.h @@ -22,7 +22,7 @@ #define OUTLOOK_NAME "Outlook" #define OUTLOOK_ID "f21dcaf2-8020-469b-8135-343bfc35d046" -#define OUTLOOK_SECRET NULL +#define OUTLOOK_SECRET "Tk-DAcEi13-FeSsY_Ja4Y.-MyL66I.wIPt" #define OUTLOOK_TENANT "common" #endif /* ALPINE_XOAUTH2_INCLUDED */ diff --git a/alpine/xoauth2conf.c b/alpine/xoauth2conf.c index 7af36796..f930be6b 100644 --- a/alpine/xoauth2conf.c +++ b/alpine/xoauth2conf.c @@ -26,12 +26,12 @@ extern OAUTH2_S alpine_oauth2_list[]; XOAUTH2_INFO_S xoauth_default[] = { - { GMAIL_NAME, GMAIL_ID, GMAIL_SECRET, GMAIL_TENANT, NULL}, - { OUTLOOK_NAME, OUTLOOK_ID, OUTLOOK_SECRET, OUTLOOK_TENANT, NULL}, - { NULL, NULL, NULL, NULL, NULL} + { GMAIL_NAME, GMAIL_ID, GMAIL_SECRET, GMAIL_TENANT, NULL, NULL}, + { OUTLOOK_NAME, OUTLOOK_ID, OUTLOOK_SECRET, OUTLOOK_TENANT, NULL, NULL}, + { NULL, NULL, NULL, NULL, NULL, NULL} }; -typedef enum {Xname = 0, Xid, Xsecret, Xtenant, Xuser, Xend} XTYPES; +typedef enum {Xname = 0, Xid, Xsecret, Xtenant, Xuser, XFlow, Xend} XTYPES; typedef struct xoauh2_info_val_s { char *screen_name; @@ -45,6 +45,7 @@ XOAUTH2_INFO_VAL_S x_default[] = { {"Client-Secret", "/SECRET="}, {"Tenant", "/TENANT="}, {"Username", "/USER="}, + {"Auth Flow", "/Flow="}, {NULL, NULL} }; @@ -53,11 +54,13 @@ XOAUTH2_INFO_VAL_S x_default[] = { #define XSECRET x_default[Xsecret].pinerc_name #define XTENANT x_default[Xtenant].pinerc_name #define XUSER x_default[Xuser].pinerc_name +#define XFLOW x_default[XFlow].pinerc_name #define XOAUTH2_CLIENT_ID x_default[Xid].screen_name #define XOAUTH2_CLIENT_SECRET x_default[Xsecret].screen_name #define XOAUTH2_TENANT x_default[Xtenant].screen_name #define XOAUTH2_USERS x_default[Xuser].screen_name +#define XOAUTH2_FLOW x_default[XFlow].screen_name char *list_to_array(char **); char **array_to_list(char *); @@ -128,7 +131,9 @@ xoauth_config_line(XOAUTH2_INFO_S *x) + strlen(x->client_secret ? XSECRET : "") + strlen(x->client_secret ? x->client_secret : "") + strlen(x->tenant ? XTENANT : "") + strlen(x->tenant ? x->tenant : "") + strlen(XUSER) + strlen(x->users ? x->users : "") - + 2 + 3 + (x->client_secret ? 3 : 0) + (x->tenant ? 3 : 0) + 3 + 1; + + strlen(XFLOW) + strlen(x->flow ? x->flow : "") + + 2 + 3 + (x->client_secret ? 3 : 0) + (x->tenant ? 3 : 0) + + 3 + (x->flow ? 3 : 0) + 1; rv = fs_get(n*sizeof(char)); sprintf(rv, "%s\"%s\" %s\"%s\"", XNAME, x->name, XID, x->client_id); if(x->client_secret) @@ -136,6 +141,8 @@ xoauth_config_line(XOAUTH2_INFO_S *x) if(x->tenant) sprintf(rv + strlen(rv), " %s\"%s\"", XTENANT, x->tenant); sprintf(rv + strlen(rv), " %s\"%s\"", XUSER, x->users ? x->users : ""); + if(x->flow) + sprintf(rv + strlen(rv), " %s\"%s\"", XFLOW, x->flow ? x->flow : ""); return rv; } @@ -419,6 +426,13 @@ write_xoauth_configuration(struct variable *v, struct variable **vlist, EditWhi x->tenant = cpystr(p); continue; } + if (x->flow == NULL && !strcmp(vlist[i]->name, XOAUTH2_FLOW)){ + p = PVAL(vlist[i], ew); + if (p == NULL) p = vlist[i]->current_val.p; + if(p != NULL) + x->flow = cpystr(p); + continue; + } if (x->users == NULL && !strcmp(vlist[i]->name, XOAUTH2_USERS)){ l = LVAL(vlist[i], ew); x->users = list_to_array(l); @@ -492,6 +506,16 @@ xoauth_parse_client_info(char *lvalp) *t = c; } else x->client_secret = NULL; + if((s = strstr(lvalp, XFLOW)) != NULL){ + s += strlen(XFLOW); + if(*s == '"') s++; + for(t = s; *t && *t != '"' && *t != ' '; t++); + c = *t; + *t = '\0'; + if(*s) x->flow = cpystr(s); + *t = c; + } else x->flow = NULL; + if((s = strstr(lvalp, XUSER)) != NULL){ s += strlen(XUSER); if(*s == '"') s++; @@ -674,6 +698,33 @@ write_xoauth_conf_entry(XOAUTH2_INFO_S *x, XOAUTH2_INFO_S *y, CONF_S **cl, CONF_ (*cl)->varnamep = ctmpb; } + /* Set up flow variable */ + if(x->flow){ + varlist[p] = fs_get(sizeof(struct variable)); + memset((void *) varlist[p], 0, sizeof(struct variable)); + varlist[p]->name = cpystr(XOAUTH2_FLOW); + varlist[p]->is_used = 1; + varlist[p]->is_user = 1; + varlist[p]->main_user_val.p = cpystr(x->flow); + varlist[p]->global_val.p = cpystr(x->flow); + varlist[p]->dname = cpystr(tmp2); /* hack, but makes life easier! */ + varlist[p]->descrip = cpystr(x->name); /* hack, but makes life easier! */ + set_current_val(varlist[p], FALSE, FALSE); + + /* Write client-secret variable */ + new_confline(cl)->var = varlist[p]; + utf8_snprintf(tmp, sizeof(tmp), " %-*.100w =", ln, XOAUTH2_FLOW); + tmp[sizeof(tmp)-1] = '\0'; + (*cl)->varname = cpystr(tmp); + (*cl)->varmem = p++; + (*cl)->valoffset = ln + 3 + 3; + (*cl)->value = pretty_value(ps_global, *cl); + (*cl)->keymenu = &config_xoauth2_text_keymenu; + (*cl)->help = h_config_xoauth2_flow; + (*cl)->tool = text_tool; + (*cl)->varnamep = ctmpb; + } + /* Setup users variable */ varlist[p] = fs_get(sizeof(struct variable)); memset((void *) varlist[p], 0, sizeof(struct variable)); @@ -738,6 +789,7 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions) char *name_lval, *id_lval, *tenant_lval, *secret_lval, *user_lval, *id_def, *tenant_def, *secret_def; int i, j, k, l, p, q, ln = 0, readonly_warning = 0, pos, count_vars; + XTYPES m; CONF_S *ctmpa = NULL, *ctmpb, *first_line; FEATURE_S *feature; PINERC_S *prc = NULL; @@ -773,13 +825,10 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions) mailcap_free(); /* free resources we won't be using for a while */ pos = -1; - ln = strlen(XOAUTH2_CLIENT_ID); - i = strlen(XOAUTH2_CLIENT_SECRET); - if(ln < i) ln = i; - i = strlen(XOAUTH2_TENANT); - if(ln < i) ln = i; - i = strlen(XOAUTH2_USERS); - if(ln < i) ln = i; + for(ln = 0, m = Xid; m < Xend; m++){ + i = strlen(x_default[m].screen_name); + if(ln < i) ln = i; + } alval = ALVAL(&ps->vars[V_XOAUTH2_INFO], ew); lval = *alval = xoauth2_conf_dedup_and_merge(alval); @@ -798,7 +847,7 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions) free_xoauth2_info(&y); } if(lval == NULL || lval[k] == NULL){ - count_vars += 2; + count_vars += 3; if(xoauth_default[i].client_secret) count_vars++; if(xoauth_default[i].tenant) count_vars++; } @@ -808,7 +857,7 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions) free_xoauth2_info(&y); continue; } - count_vars += 2; + count_vars += 3; if(xoauth_default[i].client_secret != NULL) count_vars++; if(xoauth_default[i].tenant != NULL) count_vars++; free_xoauth2_info(&y); @@ -834,10 +883,22 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions) break; free_xoauth2_info(&y); } - if(lval == NULL || lval[k] == NULL) - write_xoauth_conf_entry(&xoauth_default[i], &xoauth_default[i], &ctmpa, &ctmpb, + if(lval == NULL || lval[k] == NULL){ + OAUTH2_S *oa2list; + for(oa2list = alpine_oauth2_list; oa2list; oa2list++){ + if(!strcmp(oa2list->name,xoauth_default[i].name)){ + xoauth_default[i].flow = cpystr(oa2list->server_mthd[0].name ? "Authorize" + : (oa2list->server_mthd[1].name ? "Device" : "Unknown")); + write_xoauth_conf_entry(&xoauth_default[i], &xoauth_default[i], &ctmpa, &ctmpb, &first_line, &varlist, &p, ln, -i-1); + fs_give((void **) &xoauth_default[i].flow); + break; /* just one entry, set the default to the first entry */ + } + } + } for(k = 0, q = 0; lval && lval[k]; k++){ + OAUTH2_S *oa2list, *oa2; + y = xoauth_parse_client_info(lval[k]); if(y && (!y->name || strcmp(y->name, xoauth_default[i].name))){ free_xoauth2_info(&y); @@ -849,6 +910,11 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions) y->client_secret = cpystr(xoauth_default[i].client_secret); if(y->tenant == NULL && xoauth_default[i].tenant != NULL) y->tenant = cpystr(xoauth_default[i].tenant); + for(oa2 = NULL, oa2list = alpine_oauth2_list; oa2 == NULL && oa2list; oa2list++) + if(!strcmp(oa2list->name, y->name)) oa2 = oa2list; + if(y->flow == NULL) + y->flow = cpystr(oa2->server_mthd[0].name ? "Authorize" + : (oa2->server_mthd[1].name ? "Device" : "Unknown")); write_xoauth_conf_entry(y, &xoauth_default[i], &ctmpa, &ctmpb, &first_line, &varlist, &p, ln, k); free_xoauth2_info(&y); } diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h index 65e02288..32df37ba 100644 --- a/imap/src/c-client/mail.h +++ b/imap/src/c-client/mail.h @@ -1993,6 +1993,7 @@ typedef struct xoauth_default_s { char *client_secret; char *tenant; char *users; + char *flow; } XOAUTH2_INFO_S; /* Supporting external functions for XOAUTH2 and OAUTHBEARER */ diff --git a/imap/src/c-client/oauth2_aux.c b/imap/src/c-client/oauth2_aux.c index e7b14c69..24827e70 100644 --- a/imap/src/c-client/oauth2_aux.c +++ b/imap/src/c-client/oauth2_aux.c @@ -386,6 +386,7 @@ void free_xoauth2_info(XOAUTH2_INFO_S **xp) if((*xp)->client_id) fs_give((void **) &(*xp)->client_id); if((*xp)->client_secret) fs_give((void **) &(*xp)->client_secret); if((*xp)->tenant) fs_give((void **) &(*xp)->tenant); + if((*xp)->flow) fs_give((void **) &(*xp)->flow); if((*xp)->users) fs_give((void **) &(*xp)->users); fs_give((void **) xp); } @@ -400,6 +401,7 @@ XOAUTH2_INFO_S *copy_xoauth2_info(XOAUTH2_INFO_S *x) if(x->client_id) y->client_id = cpystr(x->client_id); if(x->client_secret) y->client_secret = cpystr(x->client_secret); if(x->tenant) y->tenant = cpystr(x->tenant); + if(x->flow) y->flow = cpystr(x->flow); if(x->users) y->users = cpystr(x->users); return y; } diff --git a/pith/pine.hlp b/pith/pine.hlp index b751666b..903873c2 100644 --- a/pith/pine.hlp +++ b/pith/pine.hlp @@ -140,7 +140,7 @@ with help text for the config screen and the composer that didn't have any reasonable place to be called from. Dummy change to get revision in pine.hlp ============= h_revision ================= -Alpine Commit 486 2020-07-07 14:25:41 +Alpine Commit 488 2020-07-09 00:16:30 ============= h_news ================= @@ -16229,6 +16229,40 @@ personal), and the client-id and tenant that it uses would allow you access to only your work email, say, and not your personal email, because the app is trusted only at work, and so the client-id and tenant are good for that organization only. +

+<End of help on this topic> + + +======= h_config_xoauth2_flow ======= + + +Flow Explained + + +

Flow Explained

+ +The first time you connect to a service to authorize Alpine access to your +email, you will have to do a certain number of steps, which typically involve +to login to your account using a browser, and agreeing to give Alpine certain +rights to access your account. + +

+How this process is going to be done depends on the service. Some services +allow you to give access to Alpine and later generate a code that you +input into Alpine, while others give you a code you have to use before you +approve access to Alpine to access your email. + +

An example of a service that gives you a code after you authorize +Alpine is Gmail, and this process is called internally as "Authorize". +An example of a service that gives you a code before you authorize Alpine +is Outlook, and this process is called "Device". However, some services, +like Outlook, offer both services, and you can choose which flow you would like +to use. You +can choose between the "Authorize&qupt; and "Device" in these servers. +If you forget to configure +this, Alpine will ask you in these situations which method to use before it starts +to setup the process to get your authrization. +

<End of help on this topic> -- cgit v1.2.3-70-g09d2