diff options
author | Eduardo Chappa <chappa@washington.edu> | 2020-06-12 02:21:59 -0600 |
---|---|---|
committer | Eduardo Chappa <chappa@washington.edu> | 2020-06-12 02:21:59 -0600 |
commit | 6c120b9e3730f997af56fbbe19229915b6380b2d (patch) | |
tree | 2acef9df0ffa1991a90cc1ded54400d1e0c1b35f /alpine/imap.c | |
parent | d600da07926e1ec3243c2a96cd988c61d6a97614 (diff) | |
download | alpine-6c120b9e3730f997af56fbbe19229915b6380b2d.tar.xz |
* Initial implementation of XOAUTH2 authentication support for Outlook.
Based on documentation suggested by Andrew C Aitchison.
Diffstat (limited to 'alpine/imap.c')
-rw-r--r-- | alpine/imap.c | 259 |
1 files changed, 238 insertions, 21 deletions
diff --git a/alpine/imap.c b/alpine/imap.c index f86587e6..54a58c68 100644 --- a/alpine/imap.c +++ b/alpine/imap.c @@ -153,45 +153,48 @@ OAUTH2_S alpine_oauth2_list[] = {"grant_type", "refresh_token"}, {"response_type", "code"}, {"state", NULL}, - {"prompt", NULL} + {"prompt", NULL}, + {"device_code", NULL} }, {{"GET", "https://accounts.google.com/o/oauth2/auth", {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}}, {"POST", "https://accounts.google.com/o/oauth2/token", {OA2_Id, OA2_Secret, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Code, OA2_End, OA2_End}}, {"POST", "https://accounts.google.com/o/oauth2/token", - {OA2_Id, OA2_Secret, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}} + {OA2_Id, OA2_Secret, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}, + {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}} }, - NULL, 0 + {NULL, NULL, NULL, 0, 0, NULL}, /* devicecode info */ + NULL, 0, 0 }, -#if 0 {"Outlook", - {"outlook.office365.com", "smtp.gmail.com", NULL, NULL}, -// {{"client_id", "2d681b88-9675-4ff0-b033-4de97dcb7a04"}, -// {"client_secret", "FHLY770;@%fmrzxbnEKG44!"}, + {"outlook.office365.com", "smtp.office365.com", NULL, NULL}, {{"client_id", NULL}, {"client_secret", NULL}, {"code", NULL}, {"refresh_token", NULL}, - {"scope", "openid offline_access profile https://outlook.office.com/mail.readwrite https://outlook.office.com/mail.readwrite.shared https://outlook.office.com/mail.send https://outlook.office.com/mail.send.shared https://outlook.office.com/calendars.readwrite https://outlook.office.com/calendars.readwrite.shared https://outlook.office.com/contacts.readwrite https://outlook.office.com/contacts.readwrite.shared https://outlook.office.com/tasks.readwrite https://outlook.office.com/tasks.readwrite.shared https://outlook.office.com/mailboxsettings.readwrite https://outlook.office.com/people.read https://outlook.office.com/user.readbasic.all"}, - {"redirect_uri", "https://login.microsoftonline.com/common/oauth2/nativeclient"}, - {"grant_type", "authorization_code"}, + {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"}, + {"grant_type", "urn:ietf:params:oauth:grant-type:device_code"}, + {"scope", "https://graph.microsoft.com/mail.read"}, {"grant_type", "refresh_token"}, {"response_type", "code"}, {"state", NULL}, - {"prompt", "login"} + {"prompt", "login"}, + {"device_code", NULL} }, {{"GET", "https://login.microsoftonline.com/common/oauth2/authorize", {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_State, OA2_Prompt, OA2_End}}, - {"POST", "https://login.microsoftonline.com/common/oauth2/token", - {OA2_Id, OA2_Secret, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Code, OA2_Scope, OA2_End}}, - {"POST", "https://login.microsoftonline.com/common/oauth2/token", - {OA2_Id, OA2_Secret, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}} + {"POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", + {OA2_Id, OA2_Redirect, OA2_DeviceCode, OA2_End, OA2_End, OA2_End}}, + {"POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", + {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}, + {"POST", "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode", + {OA2_Id, OA2_Scope, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}} }, - NULL, 0 + {NULL, NULL, NULL, 0, 0, NULL}, + NULL, 0, 0 }, -#endif - { NULL, NULL, NULL, NULL, NULL, 0}, + { NULL, NULL, NULL, NULL, NULL, NULL, 0, 0}, }; typedef struct auth_code_s { @@ -199,6 +202,219 @@ typedef struct auth_code_s { int answer; } AUTH_CODE_S; +UCS +oauth2device_decode_reply(void *datap, void *replyp) +{ + OAUTH2_DEVICEPROC_S *av = (OAUTH2_DEVICEPROC_S *)datap; + int reply = *(int *) replyp; + + return reply < 0 ? av->code_failure : (reply == 0 ? av->code_success : av->code_wait); +} + +int +oauth2_elapsed_done(void *aux_valuep) +{ + OAUTH2_S *oauth2 = aux_valuep ? ((OAUTH2_DEVICEPROC_S *) aux_valuep)->xoauth2 : NULL; + static time_t savedt = 0, now; + int rv = 0; + + if(aux_valuep == NULL) savedt = 0; /* reset last time we approved */ + else{ + now = time(0); + if(oauth2->devicecode.interval + now >= savedt) + savedt = now; + else + rv = -1; + } + return rv; +} + +void +oauth2_set_device_info(OAUTH2_S *oa2, char *method) +{ + char tmp[MAILTMPLEN]; + char *code; + char *name = oa2->name; + int aux_rv_value; + OAUTH2_DEVICECODE_S *deviceinfo = &oa2->devicecode; + OAUTH2_DEVICEPROC_S aux_value; + + if(ps_global->ttyo){ + SCROLL_S sargs; + STORE_S *in_store, *out_store; + gf_io_t pc, gc; + HANDLE_S *handles = NULL; + AUTH_CODE_S user_input; + + if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) || + !(out_store = so_get(CharStar, NULL, EDIT_ACCESS))) + goto try_wantto; + + aux_value.xoauth2 = oa2; + aux_value.code_success = 'e'; + aux_value.code_failure = 'e'; + aux_value.code_wait = NO_OP_COMMAND; + + so_puts(in_store, "<HTML><P>"); + sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name); + so_puts(in_store, tmp); + sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name, method), + so_puts(in_store, tmp); + + if(deviceinfo->verification_uri && deviceinfo->user_code){ + sprintf(tmp, + _("</P><P>To sign in, use a web browser to open the page <A HREF=\"%s\">%s</A> and enter the code \"%s\" without the quotes."), + deviceinfo->verification_uri, deviceinfo->verification_uri, deviceinfo->user_code); + so_puts(in_store, tmp); + } + else{ + so_puts(in_store, "</P><P>"); + so_puts(in_store, deviceinfo->message); + } + so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL.")); + sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), name); + so_puts(in_store, tmp); + so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice.")); + + so_puts(in_store, _("</P><P> After you open the previous link, please enter the code above, and then you will be asked to authenticate and later ")); + so_puts(in_store, _("to grant access to Alpine to your data. ")); + so_puts(in_store, _("</P><P> Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. If you do ")); + so_puts(in_store, _("not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection to the server will still be ")); + so_puts(in_store, _("alive at the end of this process, and your connection will proceed from there.")); + so_puts(in_store, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit")); + so_puts(in_store, _("</P></HTML>")); + + so_seek(in_store, 0L, 0); + init_handles(&handles); + gf_filter_init(); + gf_link_filter(gf_html2plain, + gf_html2plain_opt(NULL, + ps_global->ttyo->screen_cols, non_messageview_margin(), + &handles, NULL, GFHP_LOCAL_HANDLES)); + gf_set_so_readc(&gc, in_store); + gf_set_so_writec(&pc, out_store); + gf_pipe(gc, pc); + gf_clear_so_writec(out_store); + gf_clear_so_readc(in_store); + + memset(&sargs, 0, sizeof(SCROLL_S)); + sargs.text.handles = handles; + sargs.text.text = so_text(out_store); + sargs.text.src = CharStar; + sargs.text.desc = _("help text"); + sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION"); + sargs.proc.tool = oauth2_auth_answer; + sargs.proc.data.p = (void *)&user_input; + sargs.keys.menu = &oauth2_device_auth_keymenu; + /* don't want to re-enter c-client */ + sargs.quell_newmail = 1; + setbitmap(sargs.keys.bitmap); + sargs.help.text = h_oauth2_start; + sargs.help.title = _("HELP FOR SETTING UP XOAUTH2"); + sargs.aux_function = oauth2deviceinfo_get_accesscode; + sargs.aux_value = (void *) &aux_value; + sargs.aux_condition = oauth2_elapsed_done; + sargs.decode_aux_rv_value = oauth2device_decode_reply; + sargs.aux_rv_value = (void *) &aux_rv_value; + + do { + scrolltool(&sargs); + ps_global->mangled_screen = 1; + ps_global->painted_body_on_startup = 0; + ps_global->painted_footer_on_startup = 0; + } while (user_input.answer != 'e'); + + so_give(&in_store); + so_give(&out_store); + free_handles(&handles); + oauth2_elapsed_done(NULL); + } + else{ + int flags, rc, q_line; + /* TRANSLATORS: user needs to input an access code from the server */ + char prompt[MAILTMPLEN], token[MAILTMPLEN]; + /* + * If screen hasn't been initialized yet, use want_to. + */ +try_wantto: + + tmp_20k_buf[0] = '\0'; + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + _("Authorizing Alpine Access to %s Email Services\n\n"), name); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + _("Alpine is attempting to log you into your %s account, using the %s method. "), name, method), + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + if(deviceinfo->verification_uri && deviceinfo->user_code){ + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"), + deviceinfo->verification_uri, deviceinfo->user_code); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + } + else{ + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + "%s\n\n", deviceinfo->message); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + } + + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + _("Copy and paste the previous URL into a web browser that supports javascript, to take you to %s's servers to complete this process.\n\n"), name); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later ")); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + "%s", _("to grant access to Alpine to your data.\n\n")); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + "%s", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. ")); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection ")); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n")); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + display_init_err(tmp_20k_buf, 0); + memset((void *)tmp, 0, sizeof(tmp)); + strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + + if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){ + int rv; + UCS ch; + q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3); + flags = OE_APPEND_CURRENT; + + snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), + "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n")); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + aux_value.xoauth2 = oa2; + aux_value.code_success = 'y'; + aux_value.code_failure = 'n'; + aux_value.code_wait = 'w'; + + strncpy(tmp, _("Continue waiting"), sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + do { + if(oauth2_elapsed_done((void *) &aux_value) == 0) + oauth2deviceinfo_get_accesscode((void *) &aux_value, (void *) &rv); + ch = oauth2device_decode_reply((void *) &aux_value, (void *) &rv); + } while (ch == 'w' || want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'); + oauth2_elapsed_done(NULL); + } + } +} + char * oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int *tryanother) { @@ -238,7 +454,7 @@ oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int * so_puts(in_store, _("</P><P> If you completed these steps successfully, you are ready to move to the second part, where you will authorize Gmail to give access to Alpine to access your email.")); } - so_puts(in_store, _("</P><P><A NAME=\"secondpart\">In order</A> to authrorize Alpine to access your email, Alpine needs to open the following URL:")); + so_puts(in_store, _("</P><P><A NAME=\"secondpart\">In order</A> to authorize Alpine to access your email, Alpine needs to open the following URL:")); so_puts(in_store,"</P><P>"); sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url); so_puts(in_store, tmp_20k_buf); @@ -362,9 +578,8 @@ try_wantto: display_init_err(tmp_20k_buf, 0); memset((void *)tmp, 0, sizeof(tmp)); - strncpy(tmp, _("Alpine would like to get authorization to access your email: "), sizeof(tmp)); + strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp)); tmp[sizeof(tmp)-1] = '\0'; - strncat(tmp, _(": Proceed "), sizeof(tmp)-strlen(tmp)-1); if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){ q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3); @@ -623,6 +838,7 @@ mm_login_oauth2(NETMBX *mb, char *user, char *method, if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken)) fs_give((void **) &NewAccessToken); } + else login->first_time++; /* Default to saving what we already had saved */ @@ -685,6 +901,7 @@ mm_login_oauth2(NETMBX *mb, char *user, char *method, oa2list->param[OA2_RefreshToken].value = SaveRefreshToken; oa2list->access_token = SaveAccessToken; oa2list->expiration = SaveExpirationTime; + oa2list->first_time = login->first_time; *login = *oa2list; /* load login pointer */ } |