summaryrefslogtreecommitdiff
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
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.
-rw-r--r--alpine/conftype.h4
-rw-r--r--alpine/imap.c227
-rw-r--r--alpine/keymenu.c15
-rw-r--r--alpine/keymenu.h1
-rw-r--r--alpine/xoauth2.h2
-rw-r--r--alpine/xoauth2conf.c98
-rw-r--r--imap/src/c-client/mail.h1
-rw-r--r--imap/src/c-client/oauth2_aux.c2
-rw-r--r--pith/pine.hlp36
9 files changed, 352 insertions, 34 deletions
diff --git a/alpine/conftype.h b/alpine/conftype.h
index 9c25ad6..21c7e45 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 0368570..fe4fdce 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;
diff --git a/alpine/keymenu.c b/alpine/keymenu.c
index 8d94e87..47350d6 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 3cf4e2c..4664ed9 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 a50a1e8..739a3b7 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 7af3679..f930be6 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 65e0228..32df37b 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 e7b14c6..24827e7 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 b751666..903873c 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 =================
<HTML>
<HEAD>
@@ -16233,6 +16233,40 @@ organization only.
&lt;End of help on this topic&gt;
</BODY>
</HTML>
+======= h_config_xoauth2_flow =======
+<HTML>
+<HEAD>
+<TITLE>Flow Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Flow Explained</H1>
+
+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.
+
+<P>
+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.
+
+<P> An example of a service that gives you a code after you authorize
+Alpine is Gmail, and this process is called internally as &quot;Authorize&quot;.
+An example of a service that gives you a code before you authorize Alpine
+is Outlook, and this process is called &quot;Device&quot;. However, some services,
+like Outlook, offer both services, and you can choose which flow you would like
+to use. You
+can choose between the &quot;Authorize&qupt; and &quot;Device&quot; 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.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
======= h_config_xoauth2_username =======
<HTML>
<HEAD>