summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--alpine/alpine.c1
-rw-r--r--alpine/imap.c259
-rw-r--r--alpine/imap.h3
-rw-r--r--alpine/keymenu.c14
-rw-r--r--alpine/keymenu.h1
-rw-r--r--alpine/mailview.c16
-rw-r--r--alpine/mailview.h5
-rw-r--r--alpine/xoauth2.h4
-rw-r--r--alpine/xoauth2conf.c6
-rw-r--r--imap/src/c-client/auth_bea.c181
-rw-r--r--imap/src/c-client/auth_oa2.c181
-rw-r--r--imap/src/c-client/http.h10
-rw-r--r--imap/src/c-client/mail.c17
-rw-r--r--imap/src/c-client/mail.h29
-rw-r--r--imap/src/c-client/oauth2_aux.c314
-rw-r--r--imap/src/c-client/oauth2_aux.h25
-rw-r--r--imap/src/osdep/unix/Makefile6
-rw-r--r--pith/pine.hlp12
18 files changed, 681 insertions, 403 deletions
diff --git a/alpine/alpine.c b/alpine/alpine.c
index 8155f54a..d72c60cf 100644
--- a/alpine/alpine.c
+++ b/alpine/alpine.c
@@ -344,6 +344,7 @@ main(int argc, char **argv)
mail_parameters(NULL, SET_FREEBODYSPAREP, (void *) free_body_sparep);
mail_parameters(NULL, SET_OA2CLIENTGETACCESSCODE, (void *) oauth2_get_access_code);
mail_parameters(NULL, SET_OA2CLIENTINFO, (void *) oauth2_get_client_info);
+ mail_parameters(NULL, SET_OA2DEVICEINFO, (void *) oauth2_set_device_info);
init_pinerc(pine_state, &init_pinerc_debugging);
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 */
}
diff --git a/alpine/imap.h b/alpine/imap.h
index c4195fba..e595e196 100644
--- a/alpine/imap.h
+++ b/alpine/imap.h
@@ -32,6 +32,9 @@ int url_local_certdetails(char *);
void pine_sslfailure(char *, char *, unsigned long);
void mm_expunged_current(long unsigned int);
char *oauth2_get_access_code(unsigned char *, char *, OAUTH2_S *, int *);
+void oauth2_set_device_info(OAUTH2_S *, char *);
+int oauth2_elapsed_done(void *);
+UCS oauth2device_decode_reply(void *, void *);
#ifdef LOCAL_PASSWD_CACHE
int get_passfile_passwd(char *, char **, char *, STRLIST_S *, int);
diff --git a/alpine/keymenu.c b/alpine/keymenu.c
index 65ba6f88..519622a6 100644
--- a/alpine/keymenu.c
+++ b/alpine/keymenu.c
@@ -577,6 +577,20 @@ struct key oauth2_alpine_auth_keys[] =
{"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE}};
INST_KEY_MENU(oauth2_auth_keymenu, oauth2_alpine_auth_keys);
+struct key oauth2_device_alpine_auth_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"V","[" N_("View URL") "]",{MC_VIEW_HANDLE,3,{'v',ctrl('M'),ctrl('J')}},KS_NONE},
+ {"E",N_("Exit"),{MC_NO,1,{'e'}},KS_NONE},
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ FWDEMAIL_MENU,
+ {"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE}};
+INST_KEY_MENU(oauth2_device_auth_keymenu, oauth2_alpine_auth_keys);
struct key forge_keys[] =
{HELP_MENU,
diff --git a/alpine/keymenu.h b/alpine/keymenu.h
index e29ec201..87d1151d 100644
--- a/alpine/keymenu.h
+++ b/alpine/keymenu.h
@@ -585,6 +585,7 @@ extern struct key_menu cancel_keymenu,
ans_certfail_keymenu,
ans_certquery_keymenu,
oauth2_auth_keymenu,
+ oauth2_device_auth_keymenu,
forge_keymenu,
listmgr_keymenu,
index_keymenu,
diff --git a/alpine/mailview.c b/alpine/mailview.c
index a6531ea3..d1ace2f2 100644
--- a/alpine/mailview.c
+++ b/alpine/mailview.c
@@ -2382,7 +2382,7 @@ scrolltool(SCROLL_S *sparms)
UCS ch;
int result, done, cmd, found_on, found_on_index,
first_view, force, scroll_lines, km_size,
- cursor_row, cursor_col, km_popped,
+ cursor_row, cursor_col, km_popped, rv,
nrows, ncols;
char *utf8str;
long jn;
@@ -2712,11 +2712,21 @@ scrolltool(SCROLL_S *sparms)
if(ps_global->prev_screen == mail_view_screen)
mswin_setviewinwindcallback(view_in_new_window);
#endif
- if(cmd == MC_RESIZE
+ rv = -1;
+ if(sparms->aux_function
+ && sparms->aux_condition
+ && ((rv = (sparms->aux_condition)(sparms->aux_value)) == 0))
+ (sparms->aux_function)(sparms->aux_value, sparms->aux_rv_value);
+
+ if(rv == 0){
+ ch = (sparms->decode_aux_rv_value)(sparms->aux_value, sparms->aux_rv_value);
+ if (ch == NO_OP_COMMAND) ch = read_command(&utf8str);
+ }
+ else if(cmd == MC_RESIZE
|| ps_global->ttyo == NULL
|| (ps_global->ttyo->screen_cols == ncols
&& ps_global->ttyo->screen_rows == nrows))
- ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
+ ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
#ifdef MOUSE
#ifndef WIN32
if(sparms->text.handles)
diff --git a/alpine/mailview.h b/alpine/mailview.h
index 9df7aeb6..35d4ac4b 100644
--- a/alpine/mailview.h
+++ b/alpine/mailview.h
@@ -63,6 +63,11 @@ typedef struct scrolltool_s {
HelpType text; /* help text */
char *title; /* title for help screen */
} help;
+ UCS (*decode_aux_rv_value)(void *, void *); /* transforms a reply to a command */
+ int (*aux_condition)(void *); /* has the condition been met to execute aux_function, 0 = success */
+ void (*aux_function)(void *, void *); /* auxiliary function to help us decide what to do */
+ void *aux_value; /* argument of aux_function */
+ void *aux_rv_value; /* the return value of aux_function in (void *) */
struct {
int (*click)(struct scrolltool_s *);
int (*clickclick)(struct scrolltool_s *);
diff --git a/alpine/xoauth2.h b/alpine/xoauth2.h
index c9c69974..8a3fc3f7 100644
--- a/alpine/xoauth2.h
+++ b/alpine/xoauth2.h
@@ -26,7 +26,7 @@ typedef struct xoauth_default_s {
#define GMAIL_SECRET "vwnqVJQrJZpR6JilCfAN5nY7"
#define OUTLOOK_NAME "Outlook"
-#define OUTLOOK_ID "c8df0dbf-4750-4bb9-98e9-562b10caa26a"
-#define OUTLOOK_SECRET "ijrmPVDYP4yxbNL3442;!!_"
+#define OUTLOOK_ID "f21dcaf2-8020-469b-8135-343bfc35d046"
+#define OUTLOOK_SECRET "lIE42T4kZ2ZrN-2-AVNYSZ~8i_Co2WG4m."
#endif /* ALPINE_XOAUTH2_INCLUDED */
diff --git a/alpine/xoauth2conf.c b/alpine/xoauth2conf.c
index 97900d25..4a69eb9c 100644
--- a/alpine/xoauth2conf.c
+++ b/alpine/xoauth2conf.c
@@ -26,9 +26,7 @@ extern OAUTH2_S alpine_oauth2_list[];
XOAUTH2_INFO_S xoauth_default[] = {
{ GMAIL_NAME, GMAIL_ID, GMAIL_SECRET},
-#if 0
{ OUTLOOK_NAME, OUTLOOK_ID, OUTLOOK_SECRET},
-#endif
{ NULL, NULL, NULL}
};
@@ -202,9 +200,7 @@ void
alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions)
{
struct variable gmail_client_id_var, gmail_client_secret_var;
-#if 0
struct variable outlook_client_id_var, outlook_client_secret_var;
-#endif
struct variable *varlist[2*NXSERVERS + 1];
char tmp[MAXPATH+1], *pval, **lval;
char *id, *secret;
@@ -245,10 +241,8 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions)
varlist[0] = &gmail_client_id_var;
varlist[1] = &gmail_client_secret_var;
-#if 0
varlist[2] = &outlook_client_id_var;
varlist[3] = &outlook_client_secret_var;
-#endif
varlist[2*NXSERVERS] = NULL;
for(i = 0; i < 2*NXSERVERS; i++)
diff --git a/imap/src/c-client/auth_bea.c b/imap/src/c-client/auth_bea.c
index 6079fb7b..bf5c9c1e 100644
--- a/imap/src/c-client/auth_bea.c
+++ b/imap/src/c-client/auth_bea.c
@@ -11,12 +11,11 @@
* ========================================================================
*/
+#include "oauth2_aux.h"
+
long auth_oauthbearer_client (authchallenge_t challenger,authrespond_t responder, char *base,
char *service,NETMBX *mb,void *stream, unsigned long port,
unsigned long *trial,char *user);
-#ifndef HTTP_OAUTH2_INCLUDED
-void mm_login_oauth2_c_client_method (NETMBX *, char *, char *, OAUTH2_S *, unsigned long, int *);
-#endif /* HTTP_OAUTH2_INCLUDED */
AUTHENTICATOR auth_bea = {
AU_HIDE | AU_SINGLE, /* hidden, single trip */
@@ -34,38 +33,6 @@ AUTHENTICATOR auth_bea = {
#define BEARER_HOST "host="
#define BEARER_PORT "port="
-#ifndef OAUTH2_GENERATE_STATE
-#define OAUTH2_GENERATE_STATE
-char *oauth2_generate_state(void);
-
-/* we generate something like a guid, but not care about
- * anything, but that it is really random.
- */
-char *oauth2_generate_state(void)
-{
- char rv[37];
- int i;
-
- rv[0] = '\0';
- for(i = 0; i < 4; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 6; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- rv[36] = '\0';
- return cpystr(rv);
-}
-#endif /* OAUTH2_GENERATE_STATE */
-
/* Client authenticator
* Accepts: challenger function
* responder function
@@ -196,147 +163,3 @@ long auth_oauthbearer_client (authchallenge_t challenger,authrespond_t responder
*trial = 65535; /* don't retry if bad protocol */
return ret;
}
-
-#ifndef HTTP_OAUTH2_INCLUDED
-#define HTTP_OAUTH2_INCLUDED
-/*
- * The code above is enough to implement OAUTHBEARER, all one needs is the username
- * and access token and give it to the function above. However, normal users cannot
- * be expected to get the access token, so we ask the client to help with getting
- * the access token, refresh token and expire values, so the code below is written
- * to help with that.
- */
-
-#include "http.h"
-#include "json.h"
-
-void
-mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
- OAUTH2_S *oauth2, unsigned long trial, int *tryanother)
-{
- int i;
- HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
- OAUTH2_SERVER_METHOD_S RefreshMethod;
- unsigned char *s = NULL;
- JSON_S *json = NULL;
- int status = 0;
-
- if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL){
- oauth2clientinfo_t ogci =
- (oauth2clientinfo_t) mail_parameters (NIL, GET_OA2CLIENTINFO, NIL);
-
- if(ogci) (*ogci)(oauth2->name, &oauth2->param[OA2_Id].value,
- &oauth2->param[OA2_Secret].value);
- }
-
- if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL)
- return;
-
- /* first check if we have a refresh token, and in that case use it */
- if(oauth2->param[OA2_RefreshToken].value){
-
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessTokenFromRefreshToken];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "POST") == 0)
- s = http_post_param(RefreshMethod.urlserver, params, &status);
- else if(strcmp(RefreshMethod.name, "POST2") == 0)
- s = http_post_param2(RefreshMethod.urlserver, params, &status);
-
- if(status != 200 && s)
- fs_give((void **) &s); /* at this moment ignore the reply text */
-
- if(s){
- unsigned char *u = s;
- json = json_parse(&u);
- fs_give((void **) &s);
- }
-
- if(json != NULL){
- JSON_X *jx;
-
- jx = json_body_value(json, "access_token");
- if(jx && jx->jtype == JString)
- oauth2->access_token = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "expires_in");
- if(jx && jx->jtype == JString)
- oauth2->expiration = time(0) + atol((char *) jx->value);
-
- json_free(&json);
- }
- return;
- }
- /*
- * else, we do not have a refresh token, nor an access token.
- * We need to start the process to get an access code. We use this
- * to get an access token and refresh token.
- */
- {
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessCode];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "GET") == 0){
- unsigned char *url = http_get_param_url(RefreshMethod.urlserver, params);
- oauth2getaccesscode_t ogac =
- (oauth2getaccesscode_t) mail_parameters (NIL, GET_OA2CLIENTGETACCESSCODE, NIL);
-
- if(ogac)
- oauth2->param[OA2_Code].value = (*ogac)(url, method, oauth2, tryanother);
- }
-
- if(oauth2->param[OA2_Code].value){
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "POST") == 0)
- s = http_post_param(RefreshMethod.urlserver, params, &status);
- else if(strcmp(RefreshMethod.name, "POST2") == 0)
- s = http_post_param2(RefreshMethod.urlserver, params, &status);
-
- if(status != 200 && s)
- fs_give((void **) &s); /* at this moment ignore the error */
-
- if(s){
- unsigned char *u = s;
- json = json_parse(&u);
- fs_give((void **) &s);
- }
-
- if(json != NULL){
- JSON_X *jx;
-
- jx = json_body_value(json, "refresh_token");
- if(jx && jx->jtype == JString)
- oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "access_token");
- if(jx && jx->jtype == JString)
- oauth2->access_token = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "expires_in");
- if(jx && jx->jtype == JString)
- oauth2->expiration = time(0) + atol((char *) jx->value);
-
- json_free(&json);
- }
- }
- return;
- }
-}
-#endif /* HTTP_OAUTH2_INCLUDED */
diff --git a/imap/src/c-client/auth_oa2.c b/imap/src/c-client/auth_oa2.c
index 6de4395e..69987559 100644
--- a/imap/src/c-client/auth_oa2.c
+++ b/imap/src/c-client/auth_oa2.c
@@ -15,10 +15,6 @@ long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder, cha
char *service,NETMBX *mb,void *stream, unsigned long port,
unsigned long *trial,char *user);
-#ifndef HTTP_OAUTH2_INCLUDED
-void mm_login_oauth2_c_client_method (NETMBX *, char *, char *, OAUTH2_S *, unsigned long, int *);
-#endif /* HTTP_OAUTH2_INCLUDED */
-
AUTHENTICATOR auth_oa2 = {
AU_HIDE | AU_SINGLE, /* hidden */
OA2NAME, /* authenticator name */
@@ -31,39 +27,6 @@ AUTHENTICATOR auth_oa2 = {
#define OAUTH2_USER "user="
#define OAUTH2_BEARER "auth=Bearer "
-#ifndef OAUTH2_GENERATE_STATE
-#define OAUTH2_GENERATE_STATE
-char *oauth2_generate_state(void);
-/* we generate something like a guid, but not care about
- * anything, but that it is really random.
- */
-char *oauth2_generate_state(void)
-{
- char rv[37];
- int i;
-
- rv[0] = '\0';
- for(i = 0; i < 4; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 6; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- rv[36] = '\0';
- return cpystr(rv);
-}
-#endif /* OAUTH2_GENERATE_STATE */
-
-
-
/* Client authenticator
* Accepts: challenger function
* responder function
@@ -213,147 +176,3 @@ long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder, cha
*trial = 65535; /* don't retry if bad protocol */
return ret;
}
-
-#ifndef HTTP_OAUTH2_INCLUDED
-#define HTTP_OAUTH2_INCLUDED
-/*
- * The code above is enough to implement XOAUTH2, all one needs is the username
- * and access token and give it to the function above. However, normal users cannot
- * be expected to get the access token, so we ask the client to help with getting
- * the access token, refresh token and expire values, so the code below is written
- * to help with that.
- */
-
-#include "http.h"
-#include "json.h"
-
-void
-mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
- OAUTH2_S *oauth2, unsigned long trial, int *tryanother)
-{
- int i;
- HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
- OAUTH2_SERVER_METHOD_S RefreshMethod;
- char *s = NULL;
- JSON_S *json = NULL;
- int status = 0;
-
- if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL){
- oauth2clientinfo_t ogci =
- (oauth2clientinfo_t) mail_parameters (NIL, GET_OA2CLIENTINFO, NIL);
-
- if(ogci) (*ogci)(oauth2->name, &oauth2->param[OA2_Id].value,
- &oauth2->param[OA2_Secret].value);
- }
-
- if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL)
- return;
-
- /* first check if we have a refresh token, and in that case use it */
- if(oauth2->param[OA2_RefreshToken].value){
-
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessTokenFromRefreshToken];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "POST") == 0)
- s = http_post_param(RefreshMethod.urlserver, params, &status);
- else if(strcmp(RefreshMethod.name, "POST2") == 0)
- s = http_post_param2(RefreshMethod.urlserver, params, &status);
-
- if(status != 200 && s)
- fs_give((void **) &s); /* at this moment, ignore the error */
-
- if(s){
- unsigned char *u = s;
- json = json_parse(&u);
- fs_give((void **) &s);
- }
-
- if(json != NULL){
- JSON_X *jx;
-
- jx = json_body_value(json, "access_token");
- if(jx && jx->jtype == JString)
- oauth2->access_token = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "expires_in");
- if(jx && jx->jtype == JString)
- oauth2->expiration = time(0) + atol((char *) jx->value);
-
- json_free(&json);
- }
- return;
- }
- /*
- * else, we do not have a refresh token, nor an access token.
- * We need to start the process to get an access code. We use this
- * to get an access token and refresh token.
- */
- {
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessCode];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "GET") == 0){
- char *url = http_get_param_url(RefreshMethod.urlserver, params);
- oauth2getaccesscode_t ogac =
- (oauth2getaccesscode_t) mail_parameters (NIL, GET_OA2CLIENTGETACCESSCODE, NIL);
-
- if(ogac)
- oauth2->param[OA2_Code].value = (*ogac)(url, method, oauth2, tryanother);
- }
-
- if(oauth2->param[OA2_Code].value){
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "POST") == 0)
- s = http_post_param(RefreshMethod.urlserver, params, &status);
- else if(strcmp(RefreshMethod.name, "POST2") == 0)
- s = http_post_param2(RefreshMethod.urlserver, paramsm &status);
-
- if(status != 200 && s)
- fs_give((void **) &s); /* at this moment, ignore the error */
-
- if(s){
- unsigned char *u = s;
- json = json_parse(&u);
- fs_give((void **) &s);
- }
-
- if(json != NULL){
- JSON_X *jx;
-
- jx = json_body_value(json, "refresh_token");
- if(jx && jx->jtype == JString)
- oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "access_token");
- if(jx && jx->jtype == JString)
- oauth2->access_token = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "expires_in");
- if(jx && jx->jtype == JString)
- oauth2->expiration = time(0) + atol((char *) jx->value);
-
- json_free(&json);
- }
- }
- return;
- }
-}
-#endif /* HTTP_OAUTH2_INCLUDED */
diff --git a/imap/src/c-client/http.h b/imap/src/c-client/http.h
index 84607707..621e7c48 100644
--- a/imap/src/c-client/http.h
+++ b/imap/src/c-client/http.h
@@ -71,11 +71,13 @@ typedef struct http_header_data_s {
#define HTTP_MIME_URLENCODED "application/x-www-form-urlencoded"
#define HTTP_1_1_VERSION "HTTP/1.1"
+#define HTTP_OK 200
+#define HTTP_BAD 400
-#define GET_HTTPPORT (long) 456
-#define SET_HTTPPORT (long) 457
-#define GET_SSLHTTPPORT (long) 458
-#define SET_SSLHTTPPORT (long) 459
+#define GET_HTTPPORT (long) 490
+#define SET_HTTPPORT (long) 491
+#define GET_SSLHTTPPORT (long) 492
+#define SET_SSLHTTPPORT (long) 493
typedef struct http_status_s {
char *version;
diff --git a/imap/src/c-client/mail.c b/imap/src/c-client/mail.c
index baf636a1..a9688d4b 100644
--- a/imap/src/c-client/mail.c
+++ b/imap/src/c-client/mail.c
@@ -93,6 +93,8 @@ static appenduid_t mailappenduid = NIL;
static oauth2getaccesscode_t oauth2getaccesscode = NIL;
static oauth2clientinfo_t oauth2clientinfo = NIL;
+
+static oauth2deviceinfo_t oauth2deviceinfo = NIL;
/* free elt extra stuff callback */
static freeeltsparep_t mailfreeeltsparep = NIL;
/* free envelope extra stuff callback */
@@ -680,6 +682,11 @@ void *mail_parameters (MAILSTREAM *stream,long function,void *value)
case GET_OA2CLIENTINFO:
ret = (void *) oauth2clientinfo;
break;
+ case SET_OA2DEVICEINFO:
+ oauth2deviceinfo = (oauth2deviceinfo_t) value;
+ case GET_OA2DEVICEINFO:
+ ret = (void *) oauth2deviceinfo;
+ break;
default:
if ((r = smtp_parameters (function,value)) != NULL) ret = r;
if ((r = env_parameters (function,value)) != NULL) ret = r;
@@ -6444,3 +6451,13 @@ void free_c_client_module_globals(void)
env_end();
tcp_end();
}
+
+/* OAUTH2 support code goes here. This is necessary because
+ * 1. it helps to coordinate two different methods, such as XOAUTH2 and
+ * OAUTHBEARER, which use the same code, so it can all go in one place
+ *
+ * 2. It helps with coordinating with the client when the server requires
+ * the deviceinfo method.
+ */
+
+#include "oauth2_aux.c"
diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h
index 39c7e4f8..174fb9e3 100644
--- a/imap/src/c-client/mail.h
+++ b/imap/src/c-client/mail.h
@@ -158,6 +158,8 @@
#define SET_OA2CLIENTGETACCESSCODE (long) 170
#define GET_OA2CLIENTINFO (long) 171
#define SET_OA2CLIENTINFO (long) 172
+#define GET_OA2DEVICEINFO (long) 173
+#define SET_OA2DEVICEINFO (long) 174
/* 2xx: environment */
#define GET_USERNAME (long) 201
@@ -298,6 +300,7 @@
#define SET_IDLETIMEOUT (long) 453
#define GET_FETCHLOOKAHEADLIMIT (long) 454
#define SET_FETCHLOOKAHEADLIMIT (long) 455
+/* HTTP SUPPORT DEFINES THEIR OWN SET_ AND GET_ CONSTANTS (490..493). See http.h */
/* 5xx: local file drivers */
#define GET_MBXPROTECTION (long) 500
@@ -1927,11 +1930,12 @@ int PFLUSH (void);
typedef enum {OA2_Id = 0, OA2_Secret, OA2_Code, OA2_RefreshToken,
OA2_Scope, OA2_Redirect,
OA2_GrantTypeforAccessToken, OA2_GrantTypefromRefreshToken,
- OA2_Response, OA2_State, OA2_Prompt, OA2_End} OA2_type;
+ OA2_Response, OA2_State, OA2_Prompt, OA2_DeviceCode, OA2_End} OA2_type;
typedef enum {OA2_GetAccessCode = 0,
OA2_GetAccessTokenFromAccessCode,
OA2_GetAccessTokenFromRefreshToken,
+ OA2_GetDeviceCode,
OA2_GetEnd} OA2_function;
typedef struct OA2_param_s {
@@ -1945,15 +1949,38 @@ typedef struct OA2_serverparam_s {
OA2_type params[OAUTH2_PARAM_NUMBER];
} OAUTH2_SERVER_METHOD_S;
+typedef struct device_code_s {
+ unsigned char *device_code;
+ unsigned char *user_code;
+ char *verification_uri;
+ int expires_in;
+ int interval;
+ unsigned char *message;
+} OAUTH2_DEVICECODE_S;
+
typedef struct oauth2_s {
unsigned char *name; /* provider name */
char *host[OAUTH2_TOT_EQUIV]; /* servers for which this data applies */
OAUTH2_PARAM_S param[OA2_End]; /* parameters name and values for this server */
/* servers, methods and parameters to retrieve access code and tokens */
OAUTH2_SERVER_METHOD_S server_mthd[OA2_GetEnd];
+ OAUTH2_DEVICECODE_S devicecode;
char *access_token;
unsigned long expiration;
+ unsigned int first_time:1; /* this is the first time we get credentials for this account */
} OAUTH2_S;
+typedef struct deviceproc_s {
+ OAUTH2_S *xoauth2; /* the full OAUTH2_S structure we need to update */
+ char code_success; /* code to say we succeeded */
+ char code_failure; /* code to say we failed */
+ char code_wait; /* code to say keep waiting */
+} OAUTH2_DEVICEPROC_S;
+
+/* Supporting external functions for XOAUTH2 and OAUTHBEARER */
typedef char *(*oauth2getaccesscode_t) (unsigned char *, char *, OAUTH2_S *, int *);
typedef void (*oauth2clientinfo_t)(unsigned char *name, char **id, char **secret);
+typedef void (*oauth2deviceinfo_t)(OAUTH2_S *, char *method);
+void mm_login_oauth2_c_client_method (NETMBX *, char *, char *, OAUTH2_S *, unsigned long, int *);
+char *oauth2_generate_state(void);
+void oauth2deviceinfo_get_accesscode(void *, void *);
diff --git a/imap/src/c-client/oauth2_aux.c b/imap/src/c-client/oauth2_aux.c
new file mode 100644
index 00000000..9ff14174
--- /dev/null
+++ b/imap/src/c-client/oauth2_aux.c
@@ -0,0 +1,314 @@
+/*
+ * ========================================================================
+ * Copyright 2013-2020 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+
+/* OAUTH2 support code goes here. This is necessary because
+ * 1. it helps to coordinate two different methods, such as XOAUTH2 and
+ * OAUTHBEARER, which use the same code, so it can all go in one place
+ *
+ * 2. It helps with coordinating with the client when the server requires
+ * the deviceinfo method.
+ */
+
+#include "http.h"
+#include "json.h"
+#include "oauth2_aux.h"
+
+/* we generate something like a guid, but not care about
+ * anything, but that it is really random.
+ */
+char *oauth2_generate_state(void)
+{
+ char rv[37];
+ int i;
+
+ rv[0] = '\0';
+ for(i = 0; i < 4; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 2; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 2; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 2; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 6; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ rv[36] = '\0';
+ return cpystr(rv);
+}
+
+
+JSON_S *oauth2_json_reply(OAUTH2_SERVER_METHOD_S, OAUTH2_S *, int *);
+
+#define LOAD_HTTP_PARAMS(X, Y) { \
+ int i; \
+ for(i = 0; (X).params[i] != OA2_End; i++){ \
+ OA2_type j = (X).params[i]; \
+ (Y)[i].name = oauth2->param[j].name; \
+ (Y)[i].value = oauth2->param[j].value; \
+ } \
+ (Y)[i].name = (Y)[i].value = NULL; \
+}
+
+JSON_S *oauth2_json_reply(OAUTH2_SERVER_METHOD_S RefreshMethod, OAUTH2_S *oauth2, int *status)
+{
+ JSON_S *json = NULL;
+ HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
+ unsigned char *s;
+
+ LOAD_HTTP_PARAMS(RefreshMethod, params);
+ *status = 0;
+ if(strcmp(RefreshMethod.name, "POST") == 0
+ && ((s = http_post_param(RefreshMethod.urlserver, params, status)) != NULL)){
+ unsigned char *u = s;
+ json = json_parse(&u);
+ fs_give((void **) &s);
+ }
+ return json;
+}
+
+
+void
+mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
+ OAUTH2_S *oauth2, unsigned long trial, int *tryanother)
+{
+ int i, status;
+ char *s = NULL;
+ JSON_S *json = NULL;
+
+ if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL){
+ oauth2clientinfo_t ogci =
+ (oauth2clientinfo_t) mail_parameters (NIL, GET_OA2CLIENTINFO, NIL);
+
+ if(ogci) (*ogci)(oauth2->name, &oauth2->param[OA2_Id].value,
+ &oauth2->param[OA2_Secret].value);
+ }
+
+ if (oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL)
+ return;
+
+ /* Do we have a method to execute? */
+ if (oauth2->first_time && oauth2->server_mthd[OA2_GetDeviceCode].name){
+ oauth2deviceinfo_t ogdi;
+
+ json = oauth2_json_reply(oauth2->server_mthd[OA2_GetDeviceCode], oauth2, &status);
+
+ if(json != NULL){
+ JSON_X *jx;
+
+ jx = json_body_value(json, "device_code");
+ if(jx && jx->jtype == JString)
+ oauth2->devicecode.device_code = cpystr((char *) jx->value);
+
+ jx = json_body_value(json, "user_code");
+ if(jx && jx->jtype == JString)
+ oauth2->devicecode.user_code = cpystr((char *) jx->value);
+
+ jx = json_body_value(json, "verification_uri");
+ if(jx && jx->jtype == JString)
+ oauth2->devicecode.verification_uri = cpystr((char *) jx->value);
+
+ if((jx = json_body_value(json, "expires_in")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->devicecode.expires_in = atoi((char *) jx->value);
+ break;
+ case JLong : oauth2->devicecode.expires_in = *(long *) jx->value;
+ break;
+ }
+
+ if((jx = json_body_value(json, "interval")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->devicecode.interval = atoi((char *) jx->value);
+ break;
+ case JLong : oauth2->devicecode.interval = *(long *) jx->value;
+ break;
+ }
+
+ jx = json_body_value(json, "message");
+ if(jx && jx->jtype == JString)
+ oauth2->devicecode.message = cpystr((char *) jx->value);
+
+ json_free(&json);
+
+ if(oauth2->devicecode.verification_uri && oauth2->devicecode.user_code){
+ ogdi = (oauth2deviceinfo_t) mail_parameters (NIL, GET_OA2DEVICEINFO, NIL);
+ if(ogdi) (*ogdi)(oauth2, method);
+ }
+ }
+ return;
+ }
+
+ /* else check if we have a refresh token, and in that case use it */
+
+ if(oauth2->param[OA2_RefreshToken].value){
+
+ json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromRefreshToken], oauth2, &status);
+
+ if(json != NULL){
+ JSON_X *jx;
+
+ jx = json_body_value(json, "access_token");
+ if(jx && jx->jtype == JString)
+ oauth2->access_token = cpystr((char *) jx->value);
+
+ if((jx = json_body_value(json, "expires_in")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
+ break;
+ case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
+ break;
+ }
+
+ json_free(&json);
+ }
+ return;
+ }
+ /*
+ * else, we do not have a refresh token, nor an access token.
+ * We need to start the process to get an access code. We use this
+ * to get an access token and refresh token.
+ */
+ { OAUTH2_SERVER_METHOD_S RefreshMethod = oauth2->server_mthd[OA2_GetAccessCode];
+ HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
+
+ LOAD_HTTP_PARAMS(RefreshMethod, params);
+
+ if(strcmp(RefreshMethod.name, "GET") == 0){
+ char *url = http_get_param_url(RefreshMethod.urlserver, params);
+ oauth2getaccesscode_t ogac =
+ (oauth2getaccesscode_t) mail_parameters (NIL, GET_OA2CLIENTGETACCESSCODE, NIL);
+
+ if(ogac)
+ oauth2->param[OA2_Code].value = (*ogac)(url, method, oauth2, tryanother);
+ }
+
+ if(oauth2->param[OA2_Code].value){
+ json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode], oauth2, &status);
+
+ if(json != NULL){
+ JSON_X *jx;
+
+ switch(status){
+ case HTTP_OK : jx = json_body_value(json, "refresh_token");
+ if(jx && jx->jtype == JString)
+ oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
+
+ jx = json_body_value(json, "access_token");
+ if(jx && jx->jtype == JString)
+ oauth2->access_token = cpystr((char *) jx->value);
+
+ if((jx = json_body_value(json, "expires_in")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
+ break;
+ case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
+ break;
+ }
+
+ jx = json_body_value(json, "expires_in");
+ if(jx && jx->jtype == JString)
+ oauth2->expiration = time(0) + atol((char *) jx->value);
+
+ break;
+
+ case HTTP_BAD : break;
+
+ default : { char tmp[100];
+ sprintf(tmp, "Oauth Client Received Code %d", status);
+ fatal (tmp);
+ }
+ }
+
+ json_free(&json);
+ }
+ }
+ return;
+ }
+
+ /* Else, does this server use the /devicecode method? */
+}
+
+void oauth2deviceinfo_get_accesscode(void *inp, void *outp)
+{
+ OAUTH2_DEVICEPROC_S *oad = (OAUTH2_DEVICEPROC_S *) inp;
+ OAUTH2_S *oauth2 = oad->xoauth2;
+ OAUTH2_DEVICECODE_S *dcode = &oauth2->devicecode;
+ int done = 0, status, rv;
+ HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
+ JSON_S *json;
+
+ if(dcode->device_code && oauth2->param[OA2_DeviceCode].value == NULL)
+ oauth2->param[OA2_DeviceCode].value = cpystr(dcode->device_code);
+
+ rv = OA2_CODE_WAIT; /* wait by default */
+ json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode], oauth2, &status);
+
+ if(json != NULL){
+ JSON_X *jx;
+ char *error;
+
+ switch(status){
+ case HTTP_BAD : jx = json_body_value(json, "error");
+ if(jx && jx->jtype == JString)
+ error = cpystr((char *) jx->value);
+ else
+ break;
+
+ if(compare_cstring(error, "authorization_pending") == 0)
+ rv = OA2_CODE_WAIT;
+ else if(compare_cstring(error, "authorization_declined") == 0)
+ rv = OA2_CODE_FAIL;
+ else if(compare_cstring(error, "bad_verification_code") == 0)
+ rv = OA2_CODE_FAIL;
+ else if(compare_cstring(error, "expired_token") == 0)
+ rv = OA2_CODE_FAIL;
+ else /* keep waiting? */
+ rv = OA2_CODE_WAIT;
+
+ break;
+
+ case HTTP_OK : jx = json_body_value(json, "refresh_token");
+ if(jx && jx->jtype == JString)
+ oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
+
+ jx = json_body_value(json, "access_token");
+ if(jx && jx->jtype == JString)
+ oauth2->access_token = cpystr((char *) jx->value);
+
+ if((jx = json_body_value(json, "expires_in")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
+ break;
+ case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
+ break;
+ }
+
+ rv = OA2_CODE_SUCCESS;
+
+ break;
+
+ default : { char tmp[100];
+ sprintf(tmp, "Oauth device Received Code %d", status);
+ fatal (tmp);
+ }
+ }
+
+ json_free(&json);
+ }
+
+ *(int *)outp = rv;
+}
diff --git a/imap/src/c-client/oauth2_aux.h b/imap/src/c-client/oauth2_aux.h
new file mode 100644
index 00000000..b076da3c
--- /dev/null
+++ b/imap/src/c-client/oauth2_aux.h
@@ -0,0 +1,25 @@
+/*
+ * ========================================================================
+ * Copyright 2013-2020 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+#ifndef C_CLIENT_OAUTH2_AUX_INCLUDED
+#define C_CLIENT_OAUTH2_AUX_INCLUDED
+
+#define OA2_CODE_WAIT 1
+#define OA2_CODE_FAIL -1
+#define OA2_CODE_SUCCESS 0
+
+void mm_login_oauth2_c_client_method (NETMBX *, char *, char *, OAUTH2_S *, unsigned long, int *);
+char *oauth2_generate_state(void);
+void oauth2deviceinfo_get_accesscode(void *, void *);
+
+#endif /* C_CLIENT_OAUTH2_AUX_INCLUDED */
+
diff --git a/imap/src/osdep/unix/Makefile b/imap/src/osdep/unix/Makefile
index cdcbf315..5c9c6051 100644
--- a/imap/src/osdep/unix/Makefile
+++ b/imap/src/osdep/unix/Makefile
@@ -897,7 +897,7 @@ dummy.o: mail.h misc.h osdep.h dummy.h
fdstring.o: mail.h misc.h osdep.h fdstring.h
flstring.o: mail.h misc.h osdep.h flstring.h
imap4r1.o: mail.h misc.h osdep.h imap4r1.h rfc822.h
-mail.o: mail.h misc.h osdep.h rfc822.h linkage.h json.h
+mail.o: mail.h misc.h osdep.h rfc822.h linkage.h http.h json.h oauth2_aux.h oauth2_aux.c
mbx.o: mail.h misc.h osdep.h dummy.h
mh.o: mail.h misc.h osdep.h dummy.h
mix.o: mail.h misc.h osdep.h dummy.h
@@ -925,14 +925,14 @@ http.o: mail.h misc.h osdep.h utf8.h http.h json.h
# OS-dependent
osdep.o:mail.h misc.h env.h fs.h ftl.h nl.h tcp.h \
- osdep.h env_unix.h tcp_unix.h \
+ osdep.h env_unix.h tcp_unix.h oauth2_aux.h \
osdep.c env_unix.c fs_unix.c ftl_unix.c nl_unix.c tcp_unix.c ip_unix.c\
auths.c crexcl.c flockcyg.c flocklnx.c flocksim.c fsync.c \
gethstid.c getspnam.c \
gr_wait.c gr_wait4.c gr_waitp.c \
kerb_mit.c \
auth_ext.c auth_gss.c auth_log.c auth_md5.c auth_ntl.c \
- auth_bea.c auth_oa2.c auth_pla.c \
+ auth_bea.c auth_oa2.c auth_pla.c oauth2_aux.c \
pmatch.c scandir.c setpgrp.c strerror.c truncate.c write.c \
memmove.c memmove2.c memset.c \
tz_bsd.c tz_nul.c tz_sv4.c \
diff --git a/pith/pine.hlp b/pith/pine.hlp
index c6ec53a4..9b3e55d3 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 442 2020-06-11 01:39:17
+Alpine Commit 443 2020-06-12 02:21:54
============= h_news =================
<HTML>
<HEAD>
@@ -173,9 +173,15 @@ Apache web server.
<H2>New in Alpine <!--#echo var="ALPINE_VERSION"--> (<!--#echo var="ALPINE_REVISION"-->)</H2>
-<!--This is a development version of Alpine. These enhancements are
+This is a development version of Alpine. These enhancements are
released after the release of version 2.22. Please report any
-problems you find with this release. -->
+problems you find with this release.
+
+<P> Additions include:
+<UL>
+<LI> Implementation of XOAUTH2 authentication support for Outlook.
+ Based on documentation suggested by Andrew C Aitchison.
+</UL>
<P>
Version <!--#echo var="ALPINE_VERSION"--> addresses bugs found in previous releases and