From 0d181b64d4d433a5ec88c4bfd55cd5a1d5f9a1da Mon Sep 17 00:00:00 2001 From: Eduardo Chappa Date: Tue, 28 Jul 2020 20:52:28 -0600 Subject: * XOAUTH2: automatic renew of access token and connection to a server within 60 seconds of expiration of the access token. --- alpine/imap.c | 2 +- imap/src/c-client/imap4r1.c | 26 ++++++++++++++++++++++-- imap/src/c-client/mail.c | 11 +++++++++++ imap/src/c-client/mail.h | 8 ++++++++ imap/src/c-client/nntp.c | 3 ++- imap/src/c-client/oauth2_aux.c | 45 ++++++++++++++++++++++++++++++++++++++++++ imap/src/c-client/pop3.c | 3 ++- imap/src/osdep/nt/dummynt.c | 3 ++- imap/src/osdep/nt/mbxnt.c | 3 ++- imap/src/osdep/nt/mtxnt.c | 3 ++- imap/src/osdep/nt/tenexnt.c | 3 ++- imap/src/osdep/nt/unixnt.c | 3 ++- imap/src/osdep/unix/dummy.c | 3 ++- imap/src/osdep/unix/mbx.c | 3 ++- imap/src/osdep/unix/mh.c | 3 ++- imap/src/osdep/unix/mix.c | 3 ++- imap/src/osdep/unix/mmdf.c | 3 ++- imap/src/osdep/unix/mtx.c | 3 ++- imap/src/osdep/unix/mx.c | 3 ++- imap/src/osdep/unix/news.c | 3 ++- imap/src/osdep/unix/phile.c | 3 ++- imap/src/osdep/unix/tenex.c | 3 ++- imap/src/osdep/unix/unix.c | 3 ++- pith/newmail.c | 25 +++++++++++++++++++++++ pith/pine.hlp | 5 ++++- 25 files changed, 154 insertions(+), 22 deletions(-) diff --git a/alpine/imap.c b/alpine/imap.c index 49e4da0d..7c518cf6 100644 --- a/alpine/imap.c +++ b/alpine/imap.c @@ -888,7 +888,7 @@ mm_login_oauth2(NETMBX *mb, char *user, char *method, ps_global->no_newmail_check_from_optionally_enter = 1; /* make sure errors are seen */ - if(ps_global->ttyo) + if(ps_global->ttyo && !ps_global->noshow_error) flush_status_messages(0); token = NULL; /* start from scratch */ diff --git a/imap/src/c-client/imap4r1.c b/imap/src/c-client/imap4r1.c index db3f1190..7343122c 100644 --- a/imap/src/c-client/imap4r1.c +++ b/imap/src/c-client/imap4r1.c @@ -170,6 +170,7 @@ long imap_delete (MAILSTREAM *stream,char *mailbox); long imap_rename (MAILSTREAM *stream,char *old,char *newname); long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2); long imap_status (MAILSTREAM *stream,char *mbx,long flags); +long imap_renew (MAILSTREAM *stream,MAILSTREAM *m); MAILSTREAM *imap_open (MAILSTREAM *stream); IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb, char *usr,char *tmp); @@ -316,7 +317,8 @@ DRIVER imapdriver = { imap_expunge, /* expunge deleted messages */ imap_copy, /* copy messages to another mailbox */ imap_append, /* append string message to mailbox */ - imap_gc /* garbage collect stream */ + imap_gc, /* garbage collect stream */ + imap_renew /* renew stream */ }; /* prototype stream */ @@ -793,6 +795,22 @@ long imap_status (MAILSTREAM *stream,char *mbx,long flags) return ret; /* success */ } +/* IMAP renew + * Accepts: stream to renew + * returns 0 if success, 1 if failure + */ +long imap_renew (MAILSTREAM *stream, MAILSTREAM *m) +{ + IMAPLOCAL *MLOCAL = (IMAPLOCAL *) m->local; + NETSTREAM *xnetstream; + + xnetstream = LOCAL->netstream; + LOCAL->netstream = MLOCAL->netstream; + MLOCAL->netstream = xnetstream; + + return 0L; +} + /* IMAP open * Accepts: stream to open * Returns: stream to use on success, NIL on failure @@ -1210,7 +1228,11 @@ long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr) while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag)) imap_soutr (stream,"*"); /* good if SASL ok and success response */ - if (ok && imap_OK (stream,reply)) return T; + if (ok && imap_OK (stream,reply)){ + if(stream->auth.name) fs_give((void **) &stream->auth.name); + stream->auth.name = cpystr(at->name); /* save method name */ + return T; + } if (!trial) { /* if main program requested cancellation */ mm_log ("IMAP Authentication cancelled",ERROR); return NIL; diff --git a/imap/src/c-client/mail.c b/imap/src/c-client/mail.c index a9688d4b..b52d0626 100644 --- a/imap/src/c-client/mail.c +++ b/imap/src/c-client/mail.c @@ -1238,6 +1238,17 @@ long mail_status_default (MAILSTREAM *stream,char *mbx,long flags) return T; /* success */ } +/* Mail renew stream + * Accepts: stream to renew + * returns: 0 for success, 1 for failure + */ +long mail_renew_stream (MAILSTREAM *stream) +{ + MAILSTREAM *m = mail_open(NIL, stream->original_mailbox, OP_SILENT); + long rv = stream && m ? (stream->dtb->renew)(stream, m) : 1; + mail_close(m); + return rv; +} /* Mail open * Accepts: candidate stream for recycling * mailbox name diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h index adcad4d9..9a02c5f9 100644 --- a/imap/src/c-client/mail.h +++ b/imap/src/c-client/mail.h @@ -1154,6 +1154,10 @@ typedef struct mail_stream { void *local; /* pointer to driver local data */ char *mailbox; /* mailbox name (canonicalized) */ char *original_mailbox; /* mailbox name (non-canonicalized) */ + struct { + char *name; /* AUTHENTICATE method */ + unsigned long expiration; /* expiration time for authentication */ + } auth; unsigned short use; /* stream use count */ unsigned short sequence; /* stream sequence */ unsigned int inbox : 1; /* stream open on an INBOX */ @@ -1582,6 +1586,8 @@ DRIVER { long (*append) (MAILSTREAM *stream,char *mailbox,append_t af,void *data); /* garbage collect stream */ void (*gc) (MAILSTREAM *stream,long gcflags); + /* renew stream */ + long (*renew) (MAILSTREAM *stream, MAILSTREAM *m); }; @@ -1706,6 +1712,7 @@ long mail_rename (MAILSTREAM *stream,char *old,char *newname); char *mail_utf7_valid (char *mailbox); long mail_status (MAILSTREAM *stream,char *mbx,long flags); long mail_status_default (MAILSTREAM *stream,char *mbx,long flags); +long mail_renew_stream (MAILSTREAM *stream); MAILSTREAM *mail_open (MAILSTREAM *stream,char *name,long options); MAILSTREAM *mail_open_work (DRIVER *d,MAILSTREAM *stream,char *name, long options); @@ -2011,3 +2018,4 @@ XOAUTH2_INFO_S *new_xoauth2_info(void); void free_xoauth2_info(XOAUTH2_INFO_S **); XOAUTH2_INFO_S *copy_xoauth2_info(XOAUTH2_INFO_S *); char *oauth2_generate_state(void); +void renew_accesstoken(MAILSTREAM *); diff --git a/imap/src/c-client/nntp.c b/imap/src/c-client/nntp.c index a46a5bd6..b8d89893 100644 --- a/imap/src/c-client/nntp.c +++ b/imap/src/c-client/nntp.c @@ -202,7 +202,8 @@ DRIVER nntpdriver = { nntp_expunge, /* expunge deleted messages */ nntp_copy, /* copy messages to another mailbox */ nntp_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/c-client/oauth2_aux.c b/imap/src/c-client/oauth2_aux.c index 24827e70..6fab2917 100644 --- a/imap/src/c-client/oauth2_aux.c +++ b/imap/src/c-client/oauth2_aux.c @@ -405,3 +405,48 @@ XOAUTH2_INFO_S *copy_xoauth2_info(XOAUTH2_INFO_S *x) if(x->users) y->users = cpystr(x->users); return y; } + +/* This function does not create a refresh token and and + * an access token, but uses an already known refresh token + * to generate a refresh token on an ALREADY OPEN stream. + * The assumption is that the user has already unlocked all + * passwords and the app can access them from some source + * (key chain/credentials/memory) to go through this + * process seamlessly. + */ +void renew_accesstoken(MAILSTREAM *stream) +{ + OAUTH2_S oauth2; + NETMBX mb; + char user[MAILTMPLEN]; + int tryanother; + unsigned long trial = 0; + + memset((void *) &oauth2, 0, sizeof(OAUTH2_S)); + mail_valid_net_parse(stream->original_mailbox, &mb); + user[0] = '\0'; + mm_login_method (&mb, user, (void *) &oauth2, trial, stream->auth.name); + + if(oauth2.param[OA2_State].value) + fs_give((void **) &oauth2.param[OA2_State].value); + + if(stream->auth.expiration == 0){ + stream->auth.expiration = oauth2.expiration; + return; + } + + if(oauth2.access_token) + fs_give((void **) &oauth2.access_token); + + oauth2.param[OA2_State].value = oauth2_generate_state(); + + mm_login_oauth2_c_client_method (&mb, user, stream->auth.name, &oauth2, trial, &tryanother); + + if(oauth2.access_token) + mm_login_method (&mb, user, (void *) &oauth2, trial, stream->auth.name); + + stream->auth.expiration = oauth2.expiration; + if(oauth2.param[OA2_Id].value) fs_give((void **) &oauth2.param[OA2_Id].value); + if(oauth2.param[OA2_Secret].value) fs_give((void **) &oauth2.param[OA2_Secret].value); + if(oauth2.param[OA2_Tenant].value) fs_give((void **) &oauth2.param[OA2_Tenant].value); +} diff --git a/imap/src/c-client/pop3.c b/imap/src/c-client/pop3.c index b5b51486..d91b3ccd 100644 --- a/imap/src/c-client/pop3.c +++ b/imap/src/c-client/pop3.c @@ -154,7 +154,8 @@ DRIVER pop3driver = { pop3_expunge, /* expunge deleted messages */ pop3_copy, /* copy messages to another mailbox */ pop3_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/nt/dummynt.c b/imap/src/osdep/nt/dummynt.c index 09a72f7a..7654313c 100644 --- a/imap/src/osdep/nt/dummynt.c +++ b/imap/src/osdep/nt/dummynt.c @@ -94,7 +94,8 @@ DRIVER dummydriver = { dummy_expunge, /* expunge deleted messages */ dummy_copy, /* copy messages to another mailbox */ dummy_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; diff --git a/imap/src/osdep/nt/mbxnt.c b/imap/src/osdep/nt/mbxnt.c index 5ea3b57e..f35bfa6c 100644 --- a/imap/src/osdep/nt/mbxnt.c +++ b/imap/src/osdep/nt/mbxnt.c @@ -157,7 +157,8 @@ DRIVER mbxdriver = { mbx_expunge, /* expunge deleted messages */ mbx_copy, /* copy messages to another mailbox */ mbx_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/nt/mtxnt.c b/imap/src/osdep/nt/mtxnt.c index 0ca88fb7..fa80afde 100644 --- a/imap/src/osdep/nt/mtxnt.c +++ b/imap/src/osdep/nt/mtxnt.c @@ -144,7 +144,8 @@ DRIVER mtxdriver = { mtx_expunge, /* expunge deleted messages */ mtx_copy, /* copy messages to another mailbox */ mtx_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/nt/tenexnt.c b/imap/src/osdep/nt/tenexnt.c index 4c1d0a3b..054ce73a 100644 --- a/imap/src/osdep/nt/tenexnt.c +++ b/imap/src/osdep/nt/tenexnt.c @@ -152,7 +152,8 @@ DRIVER tenexdriver = { tenex_expunge, /* expunge deleted messages */ tenex_copy, /* copy messages to another mailbox */ tenex_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/nt/unixnt.c b/imap/src/osdep/nt/unixnt.c index 2e92d39a..8c147985 100644 --- a/imap/src/osdep/nt/unixnt.c +++ b/imap/src/osdep/nt/unixnt.c @@ -177,7 +177,8 @@ DRIVER unixdriver = { unix_expunge, /* expunge deleted messages */ unix_copy, /* copy messages to another mailbox */ unix_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/dummy.c b/imap/src/osdep/unix/dummy.c index 07ed4618..660c916a 100644 --- a/imap/src/osdep/unix/dummy.c +++ b/imap/src/osdep/unix/dummy.c @@ -93,7 +93,8 @@ DRIVER dummydriver = { dummy_expunge, /* expunge deleted messages */ dummy_copy, /* copy messages to another mailbox */ dummy_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/mbx.c b/imap/src/osdep/unix/mbx.c index 1f41efce..ddb8ae89 100644 --- a/imap/src/osdep/unix/mbx.c +++ b/imap/src/osdep/unix/mbx.c @@ -165,7 +165,8 @@ DRIVER mbxdriver = { mbx_expunge, /* expunge deleted messages */ mbx_copy, /* copy messages to another mailbox */ mbx_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/mh.c b/imap/src/osdep/unix/mh.c index ebc930e3..ab4449f4 100644 --- a/imap/src/osdep/unix/mh.c +++ b/imap/src/osdep/unix/mh.c @@ -148,7 +148,8 @@ DRIVER mhdriver = { mh_expunge, /* expunge deleted messages */ mh_copy, /* copy messages to another mailbox */ mh_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/mix.c b/imap/src/osdep/unix/mix.c index 20d2b012..d2b5c71b 100644 --- a/imap/src/osdep/unix/mix.c +++ b/imap/src/osdep/unix/mix.c @@ -207,7 +207,8 @@ DRIVER mixdriver = { mix_expunge, /* expunge deleted messages */ mix_copy, /* copy messages to another mailbox */ mix_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/mmdf.c b/imap/src/osdep/unix/mmdf.c index 994c34a3..4a9e9934 100644 --- a/imap/src/osdep/unix/mmdf.c +++ b/imap/src/osdep/unix/mmdf.c @@ -334,7 +334,8 @@ DRIVER mmdfdriver = { mmdf_expunge, /* expunge deleted messages */ mmdf_copy, /* copy messages to another mailbox */ mmdf_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/mtx.c b/imap/src/osdep/unix/mtx.c index bc7f296e..0afa3376 100644 --- a/imap/src/osdep/unix/mtx.c +++ b/imap/src/osdep/unix/mtx.c @@ -143,7 +143,8 @@ DRIVER mtxdriver = { mtx_expunge, /* expunge deleted messages */ mtx_copy, /* copy messages to another mailbox */ mtx_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/mx.c b/imap/src/osdep/unix/mx.c index 3f90ab9e..54ea6ecb 100644 --- a/imap/src/osdep/unix/mx.c +++ b/imap/src/osdep/unix/mx.c @@ -148,7 +148,8 @@ DRIVER mxdriver = { mx_expunge, /* expunge deleted messages */ mx_copy, /* copy messages to another mailbox */ mx_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/news.c b/imap/src/osdep/unix/news.c index 51eb2e61..d80ef003 100644 --- a/imap/src/osdep/unix/news.c +++ b/imap/src/osdep/unix/news.c @@ -134,7 +134,8 @@ DRIVER newsdriver = { news_expunge, /* expunge deleted messages */ news_copy, /* copy messages to another mailbox */ news_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/phile.c b/imap/src/osdep/unix/phile.c index 85aa8cbb..e930841b 100644 --- a/imap/src/osdep/unix/phile.c +++ b/imap/src/osdep/unix/phile.c @@ -134,7 +134,8 @@ DRIVER philedriver = { phile_expunge, /* expunge deleted messages */ phile_copy, /* copy messages to another mailbox */ phile_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/tenex.c b/imap/src/osdep/unix/tenex.c index 2e1a5790..6d8ec011 100644 --- a/imap/src/osdep/unix/tenex.c +++ b/imap/src/osdep/unix/tenex.c @@ -150,7 +150,8 @@ DRIVER tenexdriver = { tenex_expunge, /* expunge deleted messages */ tenex_copy, /* copy messages to another mailbox */ tenex_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/imap/src/osdep/unix/unix.c b/imap/src/osdep/unix/unix.c index 74a1ffa9..68f64269 100644 --- a/imap/src/osdep/unix/unix.c +++ b/imap/src/osdep/unix/unix.c @@ -194,7 +194,8 @@ DRIVER unixdriver = { unix_expunge, /* expunge deleted messages */ unix_copy, /* copy messages to another mailbox */ unix_append, /* append string message to mailbox */ - NIL /* garbage collect stream */ + NIL, /* garbage collect stream */ + NIL /* renew stream */ }; /* prototype stream */ diff --git a/pith/newmail.c b/pith/newmail.c index 1f026eeb..bdbb2a58 100644 --- a/pith/newmail.c +++ b/pith/newmail.c @@ -118,6 +118,31 @@ new_mail(int force_arg, CheckPointTime time_for_check_point, int flags) if(!force && sp_unsorted_newmail(ps_global->mail_stream)) force = !(flags & NM_DEFER_SORT); + /* Some servers prefer to close the connection when the access token expires, + * while some others prefer to keep the connection alive. This means we + * need to check if the access token is about to expire, and if so renew + * the access token and the stream. Under normal circumstances this is done + * invisible to the user. Hide error messages here, in case there are any. + * The worst thing that could happen is that the user will SEE the error + * later, when the connection is closed by the server (and the error will + * be seen then.) Sending error messages at this time will confuse users. + * Avoid it now. + */ + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && m->auth.name + && (!strucmp(m->auth.name, OA2NAME) || !strucmp(m->auth.name, BEARERNAME)) + && now + 60 > m->auth.expiration){ /* procastinate doing this */ + int skip = m->auth.expiration == 0 ? 1 : 0; + dprint((9, "renew_accesstoken: %s: now = %lu, auth = %s, expiration = %lu\n", STREAMNAME(m), now, m->auth.name, m->auth.expiration)); + ps_global->noshow_error = 1; /* make this invisible to the user */ + renew_accesstoken(m); + if(skip == 0) mail_renew_stream(m); + ps_global->noshow_error = 0; /* return to normal status */ + dprint((9, "renew_accesstoken: %s: result: expiration = %lu\n", STREAMNAME(m), m->auth.expiration)); + } + } + if(!ps_global->mail_stream || !(timeo || force || sp_a_locked_stream_changed())) return(-1); diff --git a/pith/pine.hlp b/pith/pine.hlp index 428792b5..e215ec9b 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 496 2020-07-19 02:52:27 +Alpine Commit 498 2020-07-28 20:52:25 ============= h_news ================= @@ -194,6 +194,9 @@ problems you find with this release.
  • Expansion of the configuration screen for XOAUTH2 to include username, authorization flow, and tenant. +
  • XOAUTH2: automatic renew of access token and connection to a server + within 60 seconds of expiration of the access token. +
  • If a user has more than one client-id for a service, Alpine tries to asks the user which client-id to use and associates that client-id to the credentials in the XOAUTH2 configuration screen. -- cgit v1.2.3-70-g09d2