summaryrefslogtreecommitdiff
path: root/alpine/imap.c
diff options
context:
space:
mode:
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;