summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEduardo Chappa <chappa@washington.edu>2019-05-04 12:41:11 -0600
committerEduardo Chappa <chappa@washington.edu>2019-05-04 12:41:11 -0600
commitc024a78dbaa9b42db7f18b0fea1894c41e2b0d67 (patch)
tree441e7308e4577ac8766c44edda682704aa432262
parent19cde66486e27063a9af8cfd79c6eb7f106b9111 (diff)
downloadalpine-c024a78dbaa9b42db7f18b0fea1894c41e2b0d67.tar.xz
* Initial release of XOAUTH2 authentication support in Alpine for
Gmail.
-rw-r--r--.gitignore1
-rw-r--r--alpine/alpine.c1
-rw-r--r--alpine/imap.c739
-rw-r--r--alpine/imap.h5
-rw-r--r--alpine/keymenu.c15
-rw-r--r--alpine/keymenu.h1
-rw-r--r--alpine/mailview.c39
-rw-r--r--alpine/mailview.h2
-rw-r--r--alpine/rpdump.c26
-rw-r--r--alpine/rpload.c25
-rw-r--r--alpine/xoauth2.h77
-rw-r--r--imap/src/c-client/auth_log.c10
-rw-r--r--imap/src/c-client/auth_md5.c10
-rw-r--r--imap/src/c-client/auth_ntl.c13
-rw-r--r--imap/src/c-client/auth_oa2.c359
-rw-r--r--imap/src/c-client/auth_pla.c10
-rw-r--r--imap/src/c-client/http.c1215
-rw-r--r--imap/src/c-client/http.h116
-rw-r--r--imap/src/c-client/imap4r1.c30
-rw-r--r--imap/src/c-client/json.c497
-rw-r--r--imap/src/c-client/json.h32
-rw-r--r--imap/src/c-client/mail.c18
-rw-r--r--imap/src/c-client/mail.h57
-rw-r--r--imap/src/c-client/nntp.c10
-rw-r--r--imap/src/c-client/pop3.c10
-rw-r--r--imap/src/c-client/rfc822.c2
-rw-r--r--imap/src/c-client/sslio.h2
-rw-r--r--imap/src/c-client/tcp.h1
-rw-r--r--imap/src/dmail/dmail.c6
-rw-r--r--imap/src/imapd/imapd.c9
-rw-r--r--imap/src/ipopd/ipop2d.c11
-rw-r--r--imap/src/ipopd/ipop3d.c12
-rw-r--r--imap/src/mailutil/mailutil.c36
-rw-r--r--imap/src/mtest/mtest.c11
-rw-r--r--imap/src/osdep/unix/Makefile12
-rw-r--r--imap/src/osdep/unix/ssl_unix.c26
-rw-r--r--imap/src/osdep/unix/tcp_unix.c22
-rw-r--r--imap/src/tmail/tmail.c6
-rw-r--r--pith/imap.c101
-rw-r--r--pith/imap.h14
-rw-r--r--pith/ldap.c6
-rw-r--r--pith/pine.hlp142
-rw-r--r--pith/string.c18
-rw-r--r--pith/string.h1
-rw-r--r--web/src/alpined.d/imap.c14
45 files changed, 3583 insertions, 187 deletions
diff --git a/.gitignore b/.gitignore
index a0033a3..9667b16 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,6 @@ an
alpine/alpine
alpine/rpdump
alpine/rpload
-c-client
imap/OSTYPE
imap/SPECIALS
imap/c-client/
diff --git a/alpine/alpine.c b/alpine/alpine.c
index 3b7df35..973fa2c 100644
--- a/alpine/alpine.c
+++ b/alpine/alpine.c
@@ -340,6 +340,7 @@ main(int argc, char **argv)
mail_parameters(NULL, SET_FREESTREAMSPAREP, (void *) sp_free_callback);
mail_parameters(NULL, SET_FREEELTSPAREP, (void *) free_pine_elt);
mail_parameters(NULL, SET_FREEBODYSPAREP, (void *) free_body_sparep);
+ mail_parameters(NULL, SET_OA2CLIENTGETACCESSCODE, (void *) oauth2_get_access_code);
init_pinerc(pine_state, &init_pinerc_debugging);
diff --git a/alpine/imap.c b/alpine/imap.c
index 1fc6c76..c6da3e6 100644
--- a/alpine/imap.c
+++ b/alpine/imap.c
@@ -39,6 +39,7 @@ static char rcsid[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington
#include "arg.h"
#include "busy.h"
#include "titlebar.h"
+#include "xoauth2.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/msgno.h"
@@ -100,12 +101,15 @@ static int storepassprompt = -1;
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 *);
#ifdef LOCAL_PASSWD_CACHE
int read_passfile(char *, MMLOGIN_S **);
void write_passfile(char *, MMLOGIN_S *);
int preserve_prompt(char *);
+int preserve_prompt_auth(char *, char *authtype);
void update_passfile_hostlist(char *, char *, STRLIST_S *, int);
+void update_passfile_hostlist_auth(char *, char *, STRLIST_S *, int, char *);
void free_passfile_cache_work(MMLOGIN_S **);
static MMLOGIN_S *passfile_cache = NULL;
@@ -127,6 +131,477 @@ int init_wincred_funcs(void);
static char *details_cert, *details_host, *details_reason;
+typedef struct auth_code_s {
+ char *code;
+ int answer;
+} AUTH_CODE_S;
+
+char *
+oauth2_get_access_code(char *url, OAUTH2_S *oauth2, int *tryanother)
+{
+ char tmp[MAILTMPLEN];
+ char *code;
+
+ 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;
+
+ so_puts(in_store, "<HTML><P>");
+ sprintf(tmp, _("<CENTER>Auhtorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
+ so_puts(in_store, tmp);
+ sprintf(tmp, _("</P><P>Alpine is attempting to log you into your %s account, using the XOAUTH2 method."), oauth2->name),
+ so_puts(in_store, tmp);
+
+ so_puts(in_store, _(" In order to do that, 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);
+
+ 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."), oauth2->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, you will be asked to authenticate and later to authorize access to Alpine. "));
+ so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
+ so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
+ so_puts(in_store, _(" If you do not see a code, copy the url of the page you were redirected to and again press 'C' and copy and paste it into the prompt. "));
+ so_puts(in_store, _(" Once you have completed this process, Alpine will proceed with authentication."));
+ so_puts(in_store, _(" If you do not wish to proceed, cancel 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_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");
+
+ 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');
+
+ if(!struncmp(user_input.code, "https://", 8)){
+ char *s, *t;
+ s = strstr(user_input.code, "code=");
+ if(s != NULL){
+ t = strchr(s, '&');
+ if(t) *t = '\0';
+ code = cpystr(s+5);
+ if(t) *t = '&';
+ }
+ }
+ else code = user_input.code ? cpystr(user_input.code) : NULL;
+ if(user_input.code) fs_give((void **) &user_input.code);
+
+ if(code == NULL) *tryanother = 1;
+
+ so_give(&in_store);
+ so_give(&out_store);
+ free_handles(&handles);
+ }
+ else{
+ int flags, rc, q_line;
+ /* TRANSLATORS: user needs to input an access code from the server */
+ char *accesscodelabel = _("Copy and Paste Access Code");
+ char prompt[MAILTMPLEN], token[MAILTMPLEN];
+ /*
+ * If screen hasn't been initialized yet, use want_to.
+ */
+try_wantto:
+ memset((void *)tmp, 0, sizeof(tmp));
+ strncpy(tmp, _("Alpine would like to get authorization to access your email: "), 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);
+ flags = OE_APPEND_CURRENT;
+ sprintf(prompt, "%s: ", accesscodelabel);
+ do {
+ rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
+ prompt, NULL, NO_HELP, &flags);
+ } while (rc != 0 && rc != 1);
+ if(!struncmp(token, "https://", 8)){
+ char *s, *t;
+ s = strstr(token, "code=");
+ if(s != NULL){
+ t = strchr(s, '&');
+ if(t) *t = '\0';
+ code = cpystr(s+5);
+ if(t) *t = '&';
+ }
+ }
+ else code = token[0] ? cpystr(token) : NULL;
+ }
+ }
+
+ return code;
+}
+
+void mm_login_oauth2(NETMBX *, char *, OAUTH2_S *, long int, char *, char *);
+
+/* The purpose of this function is to report to c-client the values of the
+ * different tokens and codes so that c-client can try to log in the user
+ * to the server. This function DOES NOT attempt to get these values for
+ * the user. That is attempted in the c-client side (as best as it can be
+ * done given our circumstances: no http support, no javascript support,
+ * etc.). This is the best we can do:
+ *
+ * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
+ * as best as we can. Unloaded means that there is no server known to
+ * connect, no access token, etc. Pretty much nothing is known about
+ * how to get access code, access token, etc. We ask the user to fill
+ * it up for us, if they can. If the user fills it up we save those
+ * values.
+ *
+ * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
+ * save the information that c-client got for us.
+ *
+ * 3. When saving this information we use the password caching facilities,
+ * but we must do it in a different format so that old information and
+ * new information are not mixed. In order to accomodate this for new
+ * authentication methods, we save the information in the same fields,
+ * but this time we modify it slightly, so that old functions fail to
+ * understand the new information and so not modify it nor use it. The
+ * modification is simple: Every piece of information that was saved
+ * before is prepended XOAUTH2\001 to indicate the authentication
+ * method for which it works. The character \001 is a separator. New
+ * Alpine will know how to deal with this, but old versions, will not
+ * strip this prefix from the information and fail to get the
+ * information or modify it when needed. Only new versions of Alpine will
+ * know how to process this information.
+ * new_value = authenticator_method separator old_value
+ * authenticator_method = "XOAUTH2_S"
+ * separator = "U+1"
+ * example: if old value is imap.gmail.com, the new value for the XOAUTH2
+ * authenticator is "XOAUTH2\001imap.gmail.com".
+ * In addition, the password field is not used to encode the password
+ * anymore, it is used to save login information needed in a format that
+ * the caller function chooses, but that must be preceeded by the
+ * "authenticator_method separator" code as above.
+ */
+void
+mm_login_oauth2(NETMBX *mb, char *user, OAUTH2_S *login, long int trial,
+ char *usethisprompt, char *altuserforcache)
+{
+ char *token, tmp[MAILTMPLEN];
+ char prompt[4*MAILTMPLEN], value[4*MAILTMPLEN], *last;
+ char defuser[NETMAXUSER];
+ char hostleadin[80], hostname[200], defubuf[200];
+ char logleadin[80], pwleadin[50];
+ char *url_oauth2;
+ char *tool = NULL;
+ char *OldRefreshToken, *OldAccessToken;
+ char *NewRefreshToken, *NewAccessToken;
+ char *SaveRefreshToken, *SaveAccessToken;
+ /* TRANSLATORS: A label for the hostname that the user is logging in on */
+ char *hostlabel = _("HOST");
+ /* TRANSLATORS: user is logging in as a particular user (a particular
+ login name), this is just labelling that user name. */
+ char *userlabel = _("USER");
+ STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
+ HelpType help ;
+ int len, rc, q_line, flags, i, j;
+ int oespace, avail, need, save_dont_use;
+ int save_in_init;
+ int registered;
+ int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
+ OAUTH2_S *oa2list;
+ unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
+#if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
+ int preserve_password = -1;
+#endif
+
+ dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
+ trial, mb->user ? mb->user : "(null)",
+ mb->service ? mb->service : "(null)",
+ mb->port ? " port=" : "",
+ mb->port ? comatose(mb->port) : "",
+ altuserforcache ? " altuserforcache =" : "",
+ altuserforcache ? altuserforcache : ""));
+
+ q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
+
+ save_in_init = ps_global->in_init_seq;
+ ps_global->in_init_seq = 0;
+ ps_global->no_newmail_check_from_optionally_enter = 1;
+
+ /* make sure errors are seen */
+ if(ps_global->ttyo)
+ flush_status_messages(0);
+
+ token = NULL; /* start from scratch */
+
+ hostlist[0].name = mb->host;
+ if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
+ hostlist[0].next = &hostlist[1];
+ hostlist[1].name = mb->orighost;
+ hostlist[1].next = NULL;
+ }
+ else
+ hostlist[0].next = NULL;
+
+ if(hostlist[0].name){
+ dprint((9, "mm_login_oauth2: host=%s\n",
+ hostlist[0].name ? hostlist[0].name : "?"));
+ if(hostlist[0].next && hostlist[1].name){
+ dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
+ }
+ }
+
+ /*
+ * 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;
+ 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){
+ registered++;
+ break;
+ }
+ }
+
+ if(registered){
+ hostlist2[i = 0].name = mb->host;
+ if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
+ hostlist2[++i].name = mb->orighost;
+
+ for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
+ int k;
+ for(k = 0; k <= i && hostlist2[k].name
+ && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
+ if(k == i + 1)
+ hostlist2[++i].name = oa2list->host[j];
+ }
+ hostlist2[i+1].name = NULL;
+ hostlist2[i+1].next = NULL;
+ for(j = i; j >= 0; j--)
+ hostlist2[j].next = &hostlist2[j+1];
+ }
+
+ /*
+ * We check if we have a refresh token saved somewhere, if so
+ * we use it to get a new access token, otherwise we need to
+ * get an access code so we can get (and save) a refresh token
+ * 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,
+ (mb->sslflag||mb->tlsflag), OA2NAME)){
+ dprint((9, "mm_login_oauth2: found a refresh token\n"));
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ }
+#ifdef LOCAL_PASSWD_CACHE
+ /* or see if we have saved one in the local password cache and load it */
+ else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
+ user, registered ? hostlist2 : hostlist,
+ (mb->sslflag||mb->tlsflag), OA2NAME)){
+ imap_set_passwd_auth(&mm_login_list, token, user,
+ hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
+ update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
+ (mb->sslflag||mb->tlsflag), OA2NAME);
+ dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ }
+ if(token && *token) preserve_password = 1; /* resave it, no need to ask */
+#endif /* LOCAL_PASSWD_CACHE */
+ }
+ user[NETMAXUSER-1] = '\0';
+
+ /* The Old* variables is what c_client knows */
+ OldRefreshToken = login->param[OA2_RefreshToken].value;
+ OldAccessToken = login->access_token;
+ OldExpirationTime = login->expiration;
+
+ /* The New* variables is what Alpine knows */
+ NewRefreshToken = NewAccessToken = NULL;
+ NewExpirationTime = 0L;
+ ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
+
+ if(token && *token){
+ char *s, *t;
+
+ s = token;
+ t = strchr(s, PWDAUTHSEP);
+ if(t == NULL)
+ NewRefreshToken = cpystr(s);
+ else {
+ *t++ = '\0';
+ NewRefreshToken = cpystr(s);
+ s = t;
+ t = strchr(s, PWDAUTHSEP);
+ if(t == NULL)
+ NewAccessToken = cpystr(s);
+ else {
+ *t++ = '\0';
+ NewAccessToken = cpystr(s);
+ s = t;
+ NewExpirationTime = strtol(s, &s, 10);
+ if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
+ NewExpirationTime = 0L;
+ }
+ }
+ /* check we got good information, and send good information below */
+ if(NewRefreshToken && !*NewRefreshToken)
+ fs_give((void **) &NewRefreshToken);
+ if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
+ fs_give((void **) &NewAccessToken);
+ }
+
+ /* Default to saving what we already had saved */
+
+ SaveRefreshToken = NewRefreshToken;
+ SaveAccessToken = NewAccessToken;
+ SaveExpirationTime = NewExpirationTime;
+
+ /* Translation of the logic below:
+ * if (c-client has a refresh token
+ and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
+ forget the Alpine refresh token;
+ make the Alpine Refresh token = c-client refresh token.;
+ signal that we changed the refresh token;
+ In this situation we do not need to clear up the Alpine access token. This will
+ expire, so we can use it until it expires. We can save the c-client refresh token
+ together with the Alpine Access Token and the expiration date of the Alpine Access
+ Token.
+ } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
+ forget the Alpine refresh token;
+ if Alpine has an access token, forget it;
+ reset the expiration time
+ signal that we changed the refresh token; (because the service expired it)
+ }
+ */
+
+ if(OldRefreshToken != NULL
+ && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
+ if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
+ NewRefreshToken = cpystr(OldRefreshToken);
+ ChangeRefreshToken++;
+ SaveRefreshToken = OldRefreshToken;
+ SaveAccessToken = NewAccessToken;
+ SaveExpirationTime = NewExpirationTime;
+ } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
+ fs_give((void **) &NewRefreshToken);
+ if(NewAccessToken) fs_give((void **) &NewAccessToken);
+ NewExpirationTime = 0L;
+ ChangeRefreshToken++;
+ SaveRefreshToken = NULL;
+ SaveAccessToken = NULL;
+ SaveExpirationTime = 0L;
+ }
+
+ if(OldAccessToken != NULL
+ && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
+ if(NewAccessToken) fs_give((void **) &NewAccessToken);
+ NewAccessToken = cpystr(OldAccessToken);
+ ChangeAccessToken++;
+ NewExpirationTime = OldExpirationTime;
+ SaveRefreshToken = NewRefreshToken;
+ SaveAccessToken = NewAccessToken;
+ SaveExpirationTime = NewExpirationTime;
+ }
+
+ if(!registered){
+ login->param[OA2_RefreshToken].value = SaveRefreshToken;
+ login->access_token = SaveAccessToken;
+ login->expiration = SaveExpirationTime;
+ } else {
+ oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
+ oa2list->access_token = SaveAccessToken;
+ oa2list->expiration = SaveExpirationTime;
+ *login = *oa2list; /* load login pointer */
+ }
+
+ if(!ChangeAccessToken && !ChangeRefreshToken)
+ return;
+
+ /* get ready to save this information. The format will be
+ * RefreshToken \001 LastAccessToken \001 ExpirationTime
+ * (spaces added for clarity, \001 is PWDAUTHSEP)
+ */
+ if(token) fs_give((void **) &token);
+ sprintf(tmp, "%lu", SaveExpirationTime);
+ tmp[sizeof(tmp) - 1] = '\0';
+ len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
+ + strlen(SaveAccessToken ? SaveAccessToken : "")
+ + strlen(tmp) + 2;
+ token = fs_get(len + 1);
+ sprintf(token, "%s%c%s%c%lu",
+ SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
+ SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
+ SaveExpirationTime);
+
+ /* remember the access information for next time */
+ if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
+ imap_set_passwd_auth(&mm_login_list, token,
+ altuserforcache ? altuserforcache : user, hostlist,
+ (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
+#ifdef LOCAL_PASSWD_CACHE
+ /* if requested, remember it on disk for next session */
+ if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
+ set_passfile_passwd_auth(ps_global->pinerc, token,
+ altuserforcache ? altuserforcache : user, hostlist,
+ (mb->sslflag||mb->tlsflag),
+ (preserve_password == -1 ? 0
+ : (preserve_password == 0 ? 2 :1)), OA2NAME);
+#endif /* LOCAL_PASSWD_CACHE */
+
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+}
/*----------------------------------------------------------------------
recieve notification from IMAP
@@ -328,11 +803,21 @@ mm_log(char *string, long int errflg)
ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
}
+void
+mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
+ char *method, char *usethisprompt, char *altuserforcache)
+{
+ if(method == NULL)
+ return;
+ if(strucmp(method, OA2NAME) == 0)
+ mm_login_oauth2(mb, user, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
+}
void
-mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
+mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
char *usethisprompt, char *altuserforcache)
{
+ char tmp[MAILTMPLEN];
char prompt[1000], *last;
char port[20], non_def_port[20], insecure[20];
char defuser[NETMAXUSER];
@@ -371,6 +856,11 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
altuserforcache ? altuserforcache : ""));
q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
+ if(pwd && *pwd){
+ char *s = *pwd;
+ fs_give((void **) &s);
+ *pwd = NULL;
+ }
save_in_init = ps_global->in_init_seq;
ps_global->in_init_seq = 0;
ps_global->no_newmail_check_from_optionally_enter = 1;
@@ -460,7 +950,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
/* check to see if there's a password left over from last session */
if(get_passfile_passwd(ps_global->pinerc, pwd,
user, hostlist, (mb->sslflag||mb->tlsflag))){
- imap_set_passwd(&mm_login_list, pwd, user,
+ imap_set_passwd(&mm_login_list, *pwd, user,
hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
update_passfile_hostlist(ps_global->pinerc, user, hostlist,
(mb->sslflag||mb->tlsflag));
@@ -503,7 +993,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
/* check to see if there's a password left over from last session */
if(get_passfile_passwd(ps_global->pinerc, pwd,
user, hostlist, (mb->sslflag||mb->tlsflag))){
- imap_set_passwd(&mm_login_list, pwd, user,
+ imap_set_passwd(&mm_login_list, *pwd, user,
hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
update_passfile_hostlist(ps_global->pinerc, user, hostlist,
(mb->sslflag||mb->tlsflag));
@@ -540,7 +1030,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
/* check to see if there's a password left over from last session */
if(get_passfile_passwd(ps_global->pinerc, pwd,
user, hostlist, (mb->sslflag||mb->tlsflag))){
- imap_set_passwd(&mm_login_list, pwd, user,
+ imap_set_passwd(&mm_login_list, *pwd, user,
hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
update_passfile_hostlist(ps_global->pinerc, user, hostlist,
(mb->sslflag||mb->tlsflag));
@@ -785,7 +1275,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
#ifdef LOCAL_PASSWD_CACHE
if(get_passfile_passwd(ps_global->pinerc, pwd,
user, hostlist, (mb->sslflag||mb->tlsflag))){
- imap_set_passwd(&mm_login_list, pwd, user,
+ imap_set_passwd(&mm_login_list, *pwd, user,
hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
ps_global->no_newmail_check_from_optionally_enter = 0;
ps_global->in_init_seq = save_in_init;
@@ -804,7 +1294,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
#ifdef LOCAL_PASSWD_CACHE
if(get_passfile_passwd(ps_global->pinerc, pwd,
altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
- imap_set_passwd(&mm_login_list, pwd, altuserforcache,
+ imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
ps_global->no_newmail_check_from_optionally_enter = 0;
ps_global->in_init_seq = save_in_init;
@@ -966,7 +1456,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
prompt[sizeof(prompt)-1] = '\0';
- *pwd = '\0';
+ tmp[0] = '\0';
while(1) {
if(ps_global->ttyo)
mm_login_alt_cue(mb);
@@ -975,12 +1465,13 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
ps_global->dont_use_init_cmds = 1;
flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
#ifdef _WINDOWS
- rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD, 0, 1,
+ rc = os_login_dialog(mb, user, NETMAXUSER, tmp, NETMAXPASSWD, 0, 1,
&preserve_password);
#else /* !_WINDOWS */
- rc = optionally_enter(pwd, q_line, 0, NETMAXPASSWD,
+ rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
prompt, NULL, help, &flags);
#endif /* !_WINDOWS */
+ *pwd = cpystr(tmp);
ps_global->dont_use_init_cmds = save_dont_use;
if(rc == 3) {
@@ -992,9 +1483,9 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
break;
}
- if(rc == 1 || !pwd[0]) {
+ if(rc == 1 || !tmp[0]) {
ps_global->user_says_cancel = (rc == 1);
- user[0] = pwd[0] = '\0';
+ user[0] = '\0';
ps_global->no_newmail_check_from_optionally_enter = 0;
ps_global->in_init_seq = save_in_init;
return;
@@ -1005,13 +1496,13 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
#endif
/* remember the password for next time */
if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
- imap_set_passwd(&mm_login_list, pwd,
+ imap_set_passwd(&mm_login_list, *pwd,
altuserforcache ? altuserforcache : user, hostlist,
(mb->sslflag||mb->tlsflag), 0, 0);
#ifdef LOCAL_PASSWD_CACHE
/* if requested, remember it on disk for next session */
if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
- set_passfile_passwd(ps_global->pinerc, pwd,
+ set_passfile_passwd(ps_global->pinerc, *pwd,
altuserforcache ? altuserforcache : user, hostlist,
(mb->sslflag||mb->tlsflag),
(preserve_password == -1 ? 0
@@ -1860,6 +2351,47 @@ answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
}
+int
+oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 1, rc;
+ AUTH_CODE_S user;
+ int q_line, flags;
+ /* TRANSLATORS: user needs to input an access code from the server */
+ char *accesscodelabel = _("Copy and Paste Access Code");
+ char token[MAILTMPLEN], prompt[MAILTMPLEN];
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ token[0] = '\0';
+ switch(cmd){
+ case MC_YES :
+ q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
+ flags = OE_APPEND_CURRENT;
+ sprintf(prompt, "%s: ", accesscodelabel);
+ do {
+ rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
+ prompt, NULL, NO_HELP, &flags);
+ } while (rc != 0 && rc != 1);
+ user.code = rc == 0 ? cpystr(token) : NULL;
+ user.answer = 'e';
+ rv = rc == 1 ? 0 : 1;
+ break;
+
+ case MC_NO :
+ user.code = NULL;
+ user.answer = 'e';
+ break;
+
+ default:
+ alpine_panic("Unexpected command in oauth2_auth_answer");
+ break;
+ }
+ *(AUTH_CODE_S *) sparms->proc.data.p = user;
+ return(rv);
+}
+
+
/*----------------------------------------------------------------------
This can be used to prevent the flickering of the check_cue char
caused by numerous (5000+) fetches by c-client. Right now, the only
@@ -2117,8 +2649,10 @@ read_passfile(pinerc, l)
if(passwd && host && user){ /* valid field? */
STRLIST_S hostlist[2];
- int flags = sflags ? atoi(sflags) : 0;
+ int flags;
+ tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
+ flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
hostlist[0].name = host;
if(orighost){
hostlist[0].next = &hostlist[1];
@@ -2267,8 +2801,10 @@ read_passfile(pinerc, l)
if(passwd && host && user){ /* valid field? */
STRLIST_S hostlist[2];
- int flags = sflags ? atoi(sflags) : 0;
+ int flags;
+ tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
+ flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
hostlist[0].name = host;
if(orighost){
hostlist[0].next = &hostlist[1];
@@ -2314,9 +2850,10 @@ read_passfile(pinerc, l)
char tmp[MAILTMPLEN], *ui[5];
int i, j, n, rv = 0;
+ size_t len;
#ifdef SMIME
char tmp2[MAILTMPLEN];
- char *text = NULL, *text2 = NULL;
+ char *tmptext, *text = NULL, *text2 = NULL;
int encrypted = 0;
#endif /* SMIME */
FILE *fp;
@@ -2401,6 +2938,7 @@ read_passfile(pinerc, l)
*/
if(encrypted){
text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
+ len = text2 ? strlen(text2) : 0;
switch(i){
case -2: using_passfile = 0;
break;
@@ -2416,8 +2954,14 @@ read_passfile(pinerc, l)
default: break;
}
}
- else
+ else{
+ struct stat sbuf;
+ if(our_stat(tmp, &sbuf) == 0)
+ len = sbuf.st_size;
+ else
+ len = 0;
fp = our_fopen(tmp, "rb"); /* reopen to read data */
+ }
#endif /* SMIME */
if(using_passfile == 0){
@@ -2427,34 +2971,36 @@ read_passfile(pinerc, l)
return using_passfile;
}
+ tmptext = fs_get(len + 1);
#ifdef SMIME
- for(n = 0; encrypted ? line_get(tmp, sizeof(tmp), &text2)
- : (fgets(tmp, sizeof(tmp), fp) != NULL); n++){
+ for(n = 0; len > 0 && (encrypted ? line_get(tmptext, len + 1, &text2)
+ : (fgets(tmptext, len+1, fp) != NULL)); n++){
#else /* SMIME */
- for(n = 0; fgets(tmp, sizeof(tmp), fp); n++){
+ for(n = 0; fgets(tmptext, len+1, fp); n++){
#endif /* SMIME */
/*** do any necessary DEcryption here ***/
xlate_key = n;
- for(i = 0; tmp[i]; i++)
- tmp[i] = xlate_out(tmp[i]);
+ for(i = 0; tmptext[i]; i++)
+ tmptext[i] = xlate_out(tmptext[i]);
- if(i && tmp[i-1] == '\n')
- tmp[i-1] = '\0'; /* blast '\n' */
+ if(i && tmptext[i-1] == '\n')
+ tmptext[i-1] = '\0'; /* blast '\n' */
dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
- for(i = 0, j = 0; tmp[i] && j < 5; j++){
- for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
+ for(i = 0, j = 0; tmptext[i] && j < 5; j++){
+ for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
; /* find end of data */
- if(tmp[i])
- tmp[i++] = '\0'; /* tie off data */
+ if(tmptext[i])
+ tmptext[i++] = '\0'; /* tie off data */
}
dprint((10, "read_passfile: calling imap_set_passwd\n"));
if(ui[0] && ui[1] && ui[2]){ /* valid field? */
STRLIST_S hostlist[2];
- int flags = ui[3] ? atoi(ui[3]) : 0;
+ char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
+ int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
hostlist[0].name = ui[2];
if(ui[4]){
@@ -2470,6 +3016,7 @@ read_passfile(pinerc, l)
}
}
+ if (tmptext) fs_give((void **) &tmptext);
#ifdef SMIME
if (text) fs_give((void **)&text);
#else /* SMIME */
@@ -2486,6 +3033,7 @@ write_passfile(pinerc, l)
char *pinerc;
MMLOGIN_S *l;
{
+ char *authend, *authtype;
#ifdef WINCRED
# if (WINCRED > 0)
char target[MAILTMPLEN];
@@ -2499,11 +3047,21 @@ write_passfile(pinerc, l)
dprint((9, "write_passfile\n"));
for(; l; l = l->next){
- snprintf(target, sizeof(target), "%s%s\t%s\t%d",
+ authtype = l->passwd;
+ authend = strchr(l->passwd, PWDAUTHSEP);
+ if(authend != NULL){
+ *authend = '\0';
+ sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
+ *authend = PWDAUTHSEP;
+ }
+ else
+ sprintf(blob, "%d", l->altflag);
+
+ snprintf(target, sizeof(target), "%s%s\t%s\t%s",
TNAME,
(l->hosts && l->hosts->name) ? l->hosts->name : "",
l->user ? l->user : "",
- l->altflag);
+ blob);
ltarget = utf8_to_lptstr((LPSTR) target);
if(ltarget){
@@ -2528,7 +3086,6 @@ write_passfile(pinerc, l)
#endif /* WINCRED > 0 */
#elif APPLEKEYCHAIN
-
int rc;
char target[MAILTMPLEN];
char blob[MAILTMPLEN];
@@ -2540,10 +3097,20 @@ write_passfile(pinerc, l)
dprint((9, "write_passfile\n"));
for(; l; l = l->next){
- snprintf(target, sizeof(target), "%s\t%s\t%d",
+ authtype = l->passwd;
+ authend = strchr(l->passwd, PWDAUTHSEP);
+ if(authend != NULL){
+ *authend = '\0';
+ sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
+ *authend = PWDAUTHSEP;
+ }
+ else
+ sprintf(blob, "%d", l->altflag);
+
+ snprintf(target, sizeof(target), "%s\t%s\t%s",
(l->hosts && l->hosts->name) ? l->hosts->name : "",
l->user ? l->user : "",
- l->altflag);
+ blob);
snprintf(blob, sizeof(blob), "%s%s%s",
l->passwd ? l->passwd : "",
@@ -2588,12 +3155,11 @@ write_passfile(pinerc, l)
}
#else /* PASSFILE */
-
- char tmp[MAILTMPLEN];
+ char tmp[4*MAILTMPLEN], blob[4*MAILTMPLEN];
int i, n;
FILE *fp;
#ifdef SMIME
- char *text = NULL, tmp2[MAILTMPLEN];
+ char *text = NULL, tmp2[4*MAILTMPLEN];
int len = 0;
#endif
@@ -2614,9 +3180,19 @@ write_passfile(pinerc, l)
#endif /* SMIME */
for(n = 0; l; l = l->next, n++){
+ authtype = l->passwd;
+ authend = strchr(l->passwd, PWDAUTHSEP);
+ if(authend != NULL){
+ *authend = '\0';
+ sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
+ *authend = PWDAUTHSEP;
+ }
+ else
+ sprintf(blob, "%d", l->altflag);
+
/*** do any necessary ENcryption here ***/
- snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%d%s%s\n", l->passwd, l->user,
- l->hosts->name, l->altflag,
+ snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
+ l->hosts->name, blob,
(l->hosts->next && l->hosts->next->name) ? "\t" : "",
(l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
: "");
@@ -2705,22 +3281,31 @@ ask_erase_credentials(void)
#ifdef LOCAL_PASSWD_CACHE
+int
+get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
+ char *pinerc, **passwd, *user;
+ STRLIST_S *hostlist;
+ int altflag;
+{
+ return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
+}
/*
- * get_passfile_passwd - return the password contained in the special passord
+ * get_passfile_passwd_auth - return the password contained in the special passord
* cache. The file is assumed to be in the same directory
* as the pinerc with the name defined above.
*/
int
-get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
- char *pinerc, *passwd, *user;
+get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
+ char *pinerc, **passwd, *user;
STRLIST_S *hostlist;
int altflag;
+ char *authtype;
{
- dprint((10, "get_passfile_passwd\n"));
+ dprint((10, "get_passfile_passwd_auth\n"));
return((passfile_cache || read_passfile(pinerc, &passfile_cache))
- ? imap_get_passwd(passfile_cache, passwd,
- user, hostlist, altflag)
+ ? imap_get_passwd_auth(passfile_cache, passwd,
+ user, hostlist, altflag, authtype)
: 0);
}
@@ -2769,8 +3354,16 @@ get_passfile_user(pinerc, hostlist)
int
preserve_prompt(char *pinerc)
{
+ return preserve_prompt_auth(pinerc, NULL);
+}
+
+int
+preserve_prompt_auth(char *pinerc, char *authtype)
+{
#ifdef WINCRED
# if (WINCRED > 0)
+#define PROMPT_PWD _("Preserve password for next login")
+#define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
/*
* This prompt was going to be able to be turned on and off via a registry
* setting controlled from the config menu. We decided to always use the
@@ -2779,7 +3372,9 @@ preserve_prompt(char *pinerc)
* OS X somewhat uses the behavior just described.
*/
if(mswin_store_pass_prompt()
- && (want_to(_("Preserve password for next login"),
+ && (want_to(authtype
+ ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
+ : PROMPT_PWD,
'y', 'x', NO_HELP, WT_NORM)
== 'y'))
return(1);
@@ -2790,11 +3385,14 @@ preserve_prompt(char *pinerc)
# endif
#elif APPLEKEYCHAIN
+#define PROMPT_PWD _("Preserve password for next login")
+#define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
int rc;
if((rc = macos_store_pass_prompt()) != 0){
- if(want_to(_("Preserve password for next login"),
- 'y', 'x', NO_HELP, WT_NORM)
+ if(want_to(authtype
+ ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
+ : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
== 'y'){
if(rc == -1){
macos_set_store_pass_prompt(1);
@@ -2812,6 +3410,9 @@ preserve_prompt(char *pinerc)
}
return(0);
#else /* PASSFILE */
+#define PROMPT_PWD _("Preserve password on DISK for next login")
+#define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
+
char tmp[MAILTMPLEN];
struct stat sbuf;
@@ -2819,8 +3420,9 @@ preserve_prompt(char *pinerc)
return 0;
if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
- return(want_to(_("Preserve password on DISK for next login"),
- 'y', 'x', NO_HELP, WT_NORM)
+ return(want_to(authtype
+ ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
+ : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
== 'y');
return(0);
#endif /* PASSFILE */
@@ -2947,6 +3549,14 @@ macos_erase_keychain(void)
#ifdef LOCAL_PASSWD_CACHE
+void
+set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
+ char *pinerc, *passwd, *user;
+ STRLIST_S *hostlist;
+ int altflag, already_prompted;
+{
+ set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
+}
/*
* set_passfile_passwd - set the password file entry associated with
* cache. The file is assumed to be in the same directory
@@ -2956,21 +3566,31 @@ macos_erase_keychain(void)
* 2 prompted, answered no
*/
void
-set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
+set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
char *pinerc, *passwd, *user;
STRLIST_S *hostlist;
int altflag, already_prompted;
+ char *authtype;
{
- dprint((10, "set_passfile_passwd\n"));
- if(((already_prompted == 0 && preserve_prompt(pinerc))
+ dprint((10, "set_passfile_passwd_auth\n"));
+ if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
|| already_prompted == 1)
&& !ps_global->nowrite_password_cache
&& (passfile_cache || read_passfile(pinerc, &passfile_cache))){
- imap_set_passwd(&passfile_cache, passwd, user, hostlist, altflag, 0, 0);
+ imap_set_passwd_auth(&passfile_cache, passwd, user, hostlist, altflag, 0, 0, authtype);
write_passfile(pinerc, passfile_cache);
}
}
+void
+update_passfile_hostlist(pinerc, user, hostlist, altflag)
+ char *pinerc;
+ char *user;
+ STRLIST_S *hostlist;
+ int altflag;
+{
+ update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
+}
/*
* Passfile lines are
@@ -2981,21 +3601,24 @@ set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
* This routine attempts to repair that.
*/
void
-update_passfile_hostlist(pinerc, user, hostlist, altflag)
+update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
char *pinerc;
char *user;
STRLIST_S *hostlist;
int altflag;
+ char *authtype;
{
#ifdef WINCRED
return;
#else /* !WINCRED */
MMLOGIN_S *l;
+ size_t len = authtype ? strlen(authtype) : 0;
+ size_t offset = authtype ? 1 : 0;
for(l = passfile_cache; l; l = l->next)
- if(imap_same_host(l->hosts, hostlist)
+ if(imap_same_host_auth(l->hosts, hostlist, authtype)
&& *user
- && !strcmp(user, l->user)
+ && !strcmp(user, l->user + len + offset)
&& l->altflag == altflag){
break;
}
@@ -3003,7 +3626,7 @@ update_passfile_hostlist(pinerc, user, hostlist, altflag)
if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
&& hostlist->next->name
&& !ps_global->nowrite_password_cache){
- l->hosts->next = new_strlist(hostlist->next->name);
+ l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
write_passfile(pinerc, passfile_cache);
}
#endif /* !WINCRED */
diff --git a/alpine/imap.h b/alpine/imap.h
index 08f51ce..cc56e2d 100644
--- a/alpine/imap.h
+++ b/alpine/imap.h
@@ -31,11 +31,14 @@ char *pine_newsrcquery(MAILSTREAM *, char *, char *);
int url_local_certdetails(char *);
void pine_sslfailure(char *, char *, unsigned long);
void mm_expunged_current(long unsigned int);
+char *oauth2_get_access_code(char *, OAUTH2_S *, int *);
#ifdef LOCAL_PASSWD_CACHE
-int get_passfile_passwd(char *, char *, char *, STRLIST_S *, int);
+int get_passfile_passwd(char *, char **, char *, STRLIST_S *, int);
+int get_passfile_passwd_auth(char *, char **, char *, STRLIST_S *, int, char *);
int is_using_passfile(void);
void set_passfile_passwd(char *, char *, char *, STRLIST_S *, int, int);
+void set_passfile_passwd_auth(char *, char *, char *, STRLIST_S *, int, int, char *);
char *get_passfile_user(char *, STRLIST_S *);
void free_passfile_cache(void);
#endif /* LOCAL_PASSWD_CACHE */
diff --git a/alpine/keymenu.c b/alpine/keymenu.c
index 7796932..016bd37 100644
--- a/alpine/keymenu.c
+++ b/alpine/keymenu.c
@@ -562,6 +562,21 @@ struct key ans_certquery_keys[] =
{"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE}};
INST_KEY_MENU(ans_certquery_keymenu, ans_certquery_keys);
+struct key oauth2_alpine_auth_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"C",N_("Enter Code"),{MC_YES,1,{'c'}},KS_NONE},
+ {"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_auth_keymenu, oauth2_alpine_auth_keys);
+
struct key forge_keys[] =
{HELP_MENU,
diff --git a/alpine/keymenu.h b/alpine/keymenu.h
index 7e3a7f7..47c0eea 100644
--- a/alpine/keymenu.h
+++ b/alpine/keymenu.h
@@ -582,6 +582,7 @@ extern struct key_menu cancel_keymenu,
rev_msg_keymenu,
ans_certfail_keymenu,
ans_certquery_keymenu,
+ oauth2_auth_keymenu,
forge_keymenu,
listmgr_keymenu,
index_keymenu,
diff --git a/alpine/mailview.c b/alpine/mailview.c
index 0738c37..4e7b79a 100644
--- a/alpine/mailview.c
+++ b/alpine/mailview.c
@@ -1389,6 +1389,11 @@ dot_on_handle(long int line, int goal)
return(key);
}
+int
+url_launch(HANDLE_S *handle)
+{
+ return do_url_launch(handle->h.url.tool, handle->h.url.path);
+}
/*
* url_launch - Sniff the given url, see if we can do anything with
@@ -1396,19 +1401,17 @@ dot_on_handle(long int line, int goal)
*
*/
int
-url_launch(HANDLE_S *handle)
+do_url_launch(char *toolp, char *url)
{
int rv = 0;
url_tool_t f;
#define URL_MAX_LAUNCH (2 * MAILTMPLEN)
- if(handle->h.url.tool){
- char *toolp, *cmdp, *p, cmd[URL_MAX_LAUNCH + 4];
+ if(toolp){
+ char *cmdp, *p, cmd[URL_MAX_LAUNCH + 4];
int mode, copied = 0;
PIPE_S *syspipe;
- toolp = handle->h.url.tool;
-
/* This code used to quote a URL to prevent arbitrary command execution
* through a URL. The plan was to quote the URL with single quotes,
* and this used to work. BUT some shells do not care about quoting
@@ -1434,8 +1437,7 @@ url_launch(HANDLE_S *handle)
cmdp--;
copied = 1;
- for(p = handle->h.url.path;
- p && *p && cmdp-cmd < URL_MAX_LAUNCH; p++)
+ for(p = url; p && *p && cmdp-cmd < URL_MAX_LAUNCH; p++)
*cmdp++ = *p;
*cmdp = '\0';
@@ -1462,14 +1464,13 @@ url_launch(HANDLE_S *handle)
/* TRANSLATORS: Cannot start command : <command name> */
_("Cannot start command : %s"), cmd);
}
- else if((f = url_local_handler(handle->h.url.path)) != NULL){
- if((*f)(handle->h.url.path) > 1)
+ else if((f = url_local_handler(url)) != NULL){
+ if((*f)(url) > 1)
rv = 1; /* done! */
}
else
q_status_message1(SM_ORDER, 2, 2,
- _("\"URL-Viewer\" not defined: Can't open %s"),
- handle->h.url.path);
+ _("\"URL-Viewer\" not defined: Can't open %s"), url);
return(rv);
}
@@ -1487,6 +1488,12 @@ url_launch_too_long(int return_value)
char *
url_external_handler(HANDLE_S *handle, int specific)
{
+ return get_url_external_handler(handle->h.url.path, specific);
+}
+
+char *
+get_url_external_handler(char *url, int specific)
+{
char **l, *test, *cmd, *p, *q, *ep;
int i, specific_match;
@@ -1524,11 +1531,9 @@ url_external_handler(HANDLE_S *handle, int specific)
else
q = ep;
while(!((i = strlen(p))
- && ((p[i-1] == ':'
- && handle->h.url.path[i - 1] == ':')
- || (p[i-1] != ':'
- && handle->h.url.path[i] == ':'))
- && !struncmp(handle->h.url.path, p, i))
+ && ((p[i-1] == ':' && url[i - 1] == ':')
+ || (p[i-1] != ':' && url[i] == ':'))
+ && !struncmp(url, p, i))
&& *(p = q));
if(*p){
@@ -1571,7 +1576,7 @@ url_external_handler(HANDLE_S *handle, int specific)
cmd = NULL;
if(!specific){
- cmd = url_os_specified_browser(handle->h.url.path);
+ cmd = url_os_specified_browser(url);
/*
* Last chance, anything handling "text/html" in mailcap...
*/
diff --git a/alpine/mailview.h b/alpine/mailview.h
index 2cd3f64..f8962c6 100644
--- a/alpine/mailview.h
+++ b/alpine/mailview.h
@@ -110,6 +110,8 @@ typedef struct scrolltool_s {
/* exported protoypes */
+char *get_url_external_handler(char *, int);
+int do_url_launch(char *, char *);
void mail_view_screen(struct pine *);
url_tool_t url_local_handler(char *);
int url_local_mailto(char *);
diff --git a/alpine/rpdump.c b/alpine/rpdump.c
index 2c677d9..6dfbfee 100644
--- a/alpine/rpdump.c
+++ b/alpine/rpdump.c
@@ -536,14 +536,23 @@ void mm_log(string, errflg)
}
}
+void mm_login_method(mb, user, pwd, trial, method)
+ NETMBX *mb;
+ char *user;
+ void *pwd;
+ long trial;
+ char *method;
+{
+ mm_login(mb, user, (char **) pwd, trial);
+}
void mm_login(mb, user, pwd, trial)
NETMBX *mb;
char *user;
- char *pwd;
+ char **pwd;
long trial;
{
- char prompt[100], *last;
+ char prompt[100], *last, tmp[MAILTMPLEN];
int i, j, goal, ugoal, len, rc, flags = 0;
#define NETMAXPASSWD 100
@@ -595,14 +604,14 @@ void mm_login(mb, user, pwd, trial)
if(rc == 1 || !user[0]) {
user[0] = '\0';
- pwd[0] = '\0';
+ *pwd = NULL;
}
}
else
strncpy(user, mb->user, NETMAXUSER);
user[NETMAXUSER-1] = '\0';
- pwd[NETMAXPASSWD-1] = '\0';
+// pwd[NETMAXPASSWD-1] = '\0';
if(!user[0])
return;
@@ -662,16 +671,17 @@ void mm_login(mb, user, pwd, trial)
strncpy(&prompt[i], &" ENTER PASSWORD: "[i ? 0 : 8], sizeof(prompt)-i);
prompt[sizeof(prompt)-1] = '\0';
- *pwd = '\0';
+ tmp[0] = '\0';
while(1) {
flags = OE_PASSWD;
- rc = opt_enter(pwd, NETMAXPASSWD, prompt, &flags);
+ rc = opt_enter(tmp, NETMAXPASSWD, prompt, &flags);
if(rc != 4)
break;
}
- if(rc == 1 || !pwd[0]) {
- user[0] = pwd[0] = '\0';
+ if(tmp[0]) *pwd = cpystr(tmp);
+ if(rc == 1 || !tmp[0]) {
+ user[0] = '\0';
return;
}
}
diff --git a/alpine/rpload.c b/alpine/rpload.c
index cd74e7f..a497d3c 100644
--- a/alpine/rpload.c
+++ b/alpine/rpload.c
@@ -775,14 +775,23 @@ void mm_log(string, errflg)
}
}
+void mm_login_method(mb, user, pwd, trial, method)
+ NETMBX *mb;
+ char *user;
+ void *pwd;
+ long trial;
+ char *method;
+{
+ mm_login(mb, user, (char **) pwd, trial);
+}
void mm_login(mb, user, pwd, trial)
NETMBX *mb;
char *user;
- char *pwd;
+ char **pwd;
long trial;
{
- char prompt[100], *last;
+ char prompt[100], *last, tmp[MAILTMPLEN];
int i, j, goal, ugoal, len, rc, flags = 0;
#define NETMAXPASSWD 100
@@ -834,14 +843,13 @@ void mm_login(mb, user, pwd, trial)
if(rc == 1 || !user[0]) {
user[0] = '\0';
- pwd[0] = '\0';
}
}
else
strncpy(user, mb->user, NETMAXUSER);
user[NETMAXUSER-1] = '\0';
- pwd[NETMAXPASSWD-1] = '\0';
+// pwd[NETMAXPASSWD-1] = '\0';
if(!user[0])
return;
@@ -901,16 +909,17 @@ void mm_login(mb, user, pwd, trial)
strncpy(&prompt[i], &" ENTER PASSWORD: "[i ? 0 : 8], sizeof(prompt)-i);
prompt[sizeof(prompt)-1] = '\0';
- *pwd = '\0';
+ tmp[0] = '\0';
while(1) {
flags = OE_PASSWD;
- rc = opt_enter(pwd, NETMAXPASSWD, prompt, &flags);
+ rc = opt_enter(tmp, NETMAXPASSWD, prompt, &flags);
if(rc != 4)
break;
}
- if(rc == 1 || !pwd[0]) {
- user[0] = pwd[0] = '\0';
+ if(tmp[0]) *pwd = cpystr(tmp);
+ if(rc == 1 || !tmp[0]) {
+ user[0] = '\0';
return;
}
}
diff --git a/alpine/xoauth2.h b/alpine/xoauth2.h
new file mode 100644
index 0000000..c5a65dd
--- /dev/null
+++ b/alpine/xoauth2.h
@@ -0,0 +1,77 @@
+/*
+ * ========================================================================
+ * Copyright 2018 Eduardo Chappa
+ * Copyright 2006-2009 University of Washington
+ *
+ * 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 ALPINE_XOAUTH2_INCLUDED
+#define ALPINE_XOAUTH2_INCLUDED
+
+/*
+ * This is the private information of the client, which is passed to
+ * c-client for processing. Every c-client application must have its
+ * own.
+ */
+OAUTH2_S alpine_oauth2_list[] =
+{
+ {"Gmail",
+ {"imap.gmail.com", "smtp.gmail.com", NULL, NULL},
+ {{"client_id", "624395471329-0qee3goofj7kbl7hsukou3rqq0igntv1.apps.googleusercontent.com"},
+ {"client_secret", "vwnqVJQrJZpR6JilCfAN5nY7"},
+ {"code", NULL},
+ {"refresh_token", NULL},
+ {"scope", "https://mail.google.com/"},
+ {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
+ {"grant_type", "authorization_code"},
+ {"grant_type", "refresh_token"},
+ {"response_type", "code"},
+ {"state", NULL},
+ {"prompt", 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}}
+ },
+ NULL, 0
+ },
+#if 0
+ {"Outlook",
+ {"outlook.office365.com", "smtp.gmail.com", NULL, NULL},
+// {{"client_id", "2d681b88-9675-4ff0-b033-4de97dcb7a04"},
+// {"client_secret", "FHLY770;@%fmrzxbnEKG44!"},
+ {{"client_id", "c8df0dbf-4750-4bb9-98e9-562b10caa26a"},
+ {"client_secret", "ijrmPVDYP4yxbNL3442;!!_"},
+ {"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"},
+ {"grant_type", "refresh_token"},
+ {"response_type", "code"},
+ {"state", NULL},
+ {"prompt", "login"}
+ },
+ {{"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}}
+ },
+ NULL, 0
+ },
+#endif
+ { NULL, NULL, NULL, NULL, NULL, 0},
+};
+#endif /* ALPINE_XOAUTH2_INCLUDED */
diff --git a/imap/src/c-client/auth_log.c b/imap/src/c-client/auth_log.c
index 6615744..a54d636 100644
--- a/imap/src/c-client/auth_log.c
+++ b/imap/src/c-client/auth_log.c
@@ -58,16 +58,15 @@ long auth_login_client (authchallenge_t challenger,authrespond_t responder,
char *service,NETMBX *mb,void *stream,
unsigned long *trial,char *user)
{
- char pwd[MAILTMPLEN];
+ char *pwd = NIL;
void *challenge;
unsigned long clen;
long ret = NIL;
/* get user name prompt */
if ((challenge = (*challenger) (stream,&clen)) != NULL) {
fs_give ((void **) &challenge);
- pwd[0] = NIL; /* prompt user */
- mm_login (mb,user,pwd,*trial);
- if (!pwd[0]) { /* user requested abort */
+ mm_login (mb,user, &pwd,*trial);
+ if (!pwd) { /* user requested abort */
(*responder) (stream,NIL,0);
*trial = 0; /* cancel subsequent attempts */
ret = LONGT; /* will get a BAD response back */
@@ -85,9 +84,10 @@ long auth_login_client (authchallenge_t challenger,authrespond_t responder,
ret = LONGT; /* check the authentication */
}
}
+ fs_give((void **) &pwd);
}
}
- memset (pwd,0,MAILTMPLEN); /* erase password */
+ if(pwd) fs_give((void **) &pwd);
if (!ret) *trial = 65535; /* don't retry if bad protocol */
return ret;
}
diff --git a/imap/src/c-client/auth_md5.c b/imap/src/c-client/auth_md5.c
index 8c98976..d4e7024 100644
--- a/imap/src/c-client/auth_md5.c
+++ b/imap/src/c-client/auth_md5.c
@@ -99,15 +99,14 @@ long auth_md5_client (authchallenge_t challenger,authrespond_t responder,
char *service,NETMBX *mb,void *stream,
unsigned long *trial,char *user)
{
- char pwd[MAILTMPLEN],hshbuf[2*MD5DIGLEN + 1];
+ char *pwd = NIL,hshbuf[2*MD5DIGLEN + 1];
void *challenge;
unsigned long clen;
long ret = NIL;
/* get challenge */
if ((challenge = (*challenger) (stream,&clen)) != NULL) {
- pwd[0] = NIL; /* prompt user */
- mm_login (mb,user,pwd,*trial);
- if (!pwd[0]) { /* user requested abort */
+ mm_login (mb,user, &pwd,*trial);
+ if (!pwd) { /* user requested abort */
fs_give ((void **) &challenge);
(*responder) (stream,NIL,0);
*trial = 0; /* cancel subsequent attempts */
@@ -126,9 +125,10 @@ long auth_md5_client (authchallenge_t challenger,authrespond_t responder,
ret = LONGT; /* check the authentication */
}
}
+ fs_give((void **) &pwd);
}
}
- memset (pwd,0,MAILTMPLEN); /* erase password in case not overwritten */
+ if(pwd) fs_give((void **) &pwd);
if (!ret) *trial = 65535; /* don't retry if bad protocol */
return ret;
}
diff --git a/imap/src/c-client/auth_ntl.c b/imap/src/c-client/auth_ntl.c
index 0afe99f..52ae904 100644
--- a/imap/src/c-client/auth_ntl.c
+++ b/imap/src/c-client/auth_ntl.c
@@ -1,6 +1,7 @@
/* ========================================================================
- * Copyright 1988-2008 University of Washington
+ * Copyright 2018 Eduardo Chappa
* Copyright 2015 Imagination Technologies
+ * Copyright 1988-2008 University of Washington
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -56,7 +57,7 @@ long auth_ntlm_client (authchallenge_t challenger, authrespond_t responder,
tSmbNtlmAuthRequest request;
char tbuf[MAILTMPLEN];
char ubuf[MAILTMPLEN];
- char pass[MAILTMPLEN];
+ char *pass = NIL;
unsigned long clen;
unsigned long ulen;
unsigned long dlen;
@@ -66,9 +67,8 @@ long auth_ntlm_client (authchallenge_t challenger, authrespond_t responder,
/* get initial (empty) challenge */
if (challenge = (*challenger) (stream, &clen)) {
fs_give ((void **) &challenge);
- pass[0] = NIL; /* prompt user */
- mm_login (mb, user, pass, *trial);
- if (!pass[0]) { /* user requested abort */
+ mm_login (mb, user, &pass, *trial);
+ if (!pass) { /* user requested abort */
(*responder) (stream, NIL, 0);
*trial = 0; /* cancel subsequent attempts */
ret = LONGT; /* will get a BAD response back */
@@ -104,9 +104,10 @@ long auth_ntlm_client (authchallenge_t challenger, authrespond_t responder,
}
}
}
+ if(pass) fs_give((void **) &pass);
}
}
- memset (pass,0,MAILTMPLEN); /* erase password */
+ if(pass) fs_give((void **) &pass);
if (!ret) *trial = 65535; /* don't retry if bad protocol */
return ret;
}
diff --git a/imap/src/c-client/auth_oa2.c b/imap/src/c-client/auth_oa2.c
new file mode 100644
index 0000000..d77b010
--- /dev/null
+++ b/imap/src/c-client/auth_oa2.c
@@ -0,0 +1,359 @@
+/* ========================================================================
+ * Copyright 2018 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
+ *
+ *
+ * ========================================================================
+ */
+
+long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder,
+ char *service,NETMBX *mb,void *stream,
+ unsigned long *trial,char *user);
+
+void mm_login_oauth2_c_client_method (NETMBX *, char *, OAUTH2_S *, unsigned long, int *);
+
+char *oauth2_generate_state(void);
+
+AUTHENTICATOR auth_oa2 = {
+ AU_HIDE, /* hidden */
+ OA2NAME, /* authenticator name */
+ NIL, /* always valid */
+ auth_oauth2_client, /* client method */
+ NIL, /* server method */
+ NIL /* next authenticator */
+};
+
+#define OAUTH2_USER "user="
+#define OAUTH2_BEARER "auth=Bearer "
+
+/* we generate something like a guid, but not care about
+ * anything, but that it is really random.
+ */
+char *oauth2_generate_state(void)
+{
+ char rv[36];
+ int i;
+
+ rv[0] = '\0';
+ for(i = 0; i < 4; i++)
+ sprintf(rv + strlen(rv), "%x", random() % 256);
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 2; i++)
+ sprintf(rv + strlen(rv), "%x", random() % 256);
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 2; i++)
+ sprintf(rv + strlen(rv), "%x", random() % 256);
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 2; i++)
+ sprintf(rv + strlen(rv), "%x", random() % 256);
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 6; i++)
+ sprintf(rv + strlen(rv), "%x", random() % 256);
+ rv[36] = '\0';
+ return cpystr(rv);
+}
+
+
+/* Client authenticator
+ * Accepts: challenger function
+ * responder function
+ * SASL service name
+ * parsed network mailbox structure
+ * stream argument for functions
+ * pointer to current trial count
+ * returned user name
+ * Returns: T if success, NIL otherwise, number of trials incremented if retry
+ */
+
+long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder,
+ char *service,NETMBX *mb,void *stream,
+ unsigned long *trial,char *user)
+{
+ char *u;
+ void *challenge;
+ unsigned long clen;
+ long ret = NIL;
+ OAUTH2_S oauth2;
+ int tryanother = 0; /* try another authentication method */
+
+ memset((void *) &oauth2, 0, sizeof(OAUTH2_S));
+ /* snarl if not SSL/TLS session */
+ if (!mb->sslflag && !mb->tlsflag)
+ mm_log ("SECURITY PROBLEM: insecure server advertised AUTH=XOAUTH2",WARN);
+
+ /* get initial (empty) challenge */
+ if ((challenge = (*challenger) (stream,&clen)) != NULL) {
+ fs_give ((void **) &challenge);
+ if (clen) { /* abort if challenge non-empty */
+ mm_log ("Server bug: non-empty initial XOAUTH2 challenge",WARN);
+ (*responder) (stream,NIL,0);
+ ret = LONGT; /* will get a BAD response back */
+ }
+
+ /*
+ * the call to mm_login_method is supposed to return the username
+ * and access token. If this is not known by the application, then
+ * we call our internal functions to get a refresh token, access token
+ * and expiration time.
+ *
+ * Programmers note: We always call mm_login_method at least once.
+ * The first call is done with empty parameters and it indicates
+ * we are asking the application to load it the best it can. Then
+ * the application returns the loaded value. If we get it fully loaded
+ * we use the value, but if we don't get it fully loaded, we call
+ * our internal functions to try to fully load it.
+ *
+ * If in the internal call we get it loaded, then we use these values
+ * to log in. At this time we call the app to send back the loaded values
+ * so it can save them for the next time we call. This is done in a
+ * second call to mm_login_method. If we do not get oauth2 back with
+ * fully loaded values we cancel authentication completely. If the
+ * user cannot load this variable, then the user, through the client,
+ * should disable XOAUTH2 as an authentication method and try a new one.
+ *
+ * If we make our internal mm_login_oauth2_c_client_method call,
+ * we might still need to call the client to get the access token,
+ * this is done through a callback declared by the client. If we need
+ * that information, but the callback is not declared, this process
+ * will fail, so we will check if that call is declared as soon as we
+ * know we should start it, and we will only start it if this callback
+ * is declared.
+ *
+ * We start this process by calling the client and loading oauth2
+ * with the required information as best as we can.
+ */
+
+ mm_login_method (mb, user, (void *) &oauth2, *trial, OA2NAME);
+
+ if(oauth2.param[OA2_State].value)
+ fs_give((void **) &oauth2.param[OA2_State].value);
+
+ oauth2.param[OA2_State].value = oauth2_generate_state();
+
+ /*
+ * If we did not get an access token, try to get one through
+ * our internal functions
+ */
+ if(oauth2.name && oauth2.access_token == NIL){
+ char *RefreshToken = NIL;
+
+ if(oauth2.param[OA2_RefreshToken].value)
+ RefreshToken = cpystr(oauth2.param[OA2_RefreshToken].value);
+
+ mm_login_oauth2_c_client_method (mb, user, &oauth2, *trial, &tryanother);
+
+ /*
+ * if we got an access token from the c_client_method call,
+ * or somehow there was a change in the refresh token, return
+ * it to the client so that it will save it.
+ */
+
+ if(!tryanother
+ && (oauth2.access_token
+ || (!RefreshToken && oauth2.param[OA2_RefreshToken].value)
+ || (RefreshToken && oauth2.param[OA2_RefreshToken].value
+ && strcmp(RefreshToken, oauth2.param[OA2_RefreshToken].value))))
+ mm_login_method (mb, user, (void *) &oauth2, *trial, OA2NAME);
+ }
+
+ /* empty challenge or user requested abort or client does not have info */
+ if(!oauth2.access_token) {
+ (*responder) (stream,NIL,0);
+ *trial = 0; /* cancel subsequent attempts */
+ ret = LONGT; /* will get a BAD response back */
+ }
+ else {
+ unsigned long rlen = strlen(OAUTH2_USER) + strlen(user)
+ + strlen(OAUTH2_BEARER) + strlen(oauth2.access_token) + 1 + 2;
+ char *response = (char *) fs_get (rlen);
+ char *t = response; /* copy authorization id */
+ for (u = OAUTH2_USER; *u; *t++ = *u++);
+ for (u = user; *u; *t++ = *u++);
+ *t++ = '\001'; /* delimiting ^A */
+ for (u = OAUTH2_BEARER; *u; *t++ = *u++);
+ for (u = oauth2.access_token; *u; *t++ = *u++);
+ *t++ = '\001'; /* delimiting ^A */
+ *t++ = '\001'; /* delimiting ^A */
+ if ((*responder) (stream,response,rlen)) {
+ if ((challenge = (*challenger) (stream,&clen)) != NULL)
+ fs_give ((void **) &challenge);
+ else {
+ ++*trial; /* can try again if necessary */
+ ret = *trial < 3 ? LONGT : NIL; /* check the authentication */
+ /* When the Access Token expires we fail once, but after we get
+ * a new one, we should succeed at the second attempt. If the
+ * Refresh Token has expired somehow, we invalidate it if we
+ * reach *trial to 3. This forces the process to restart later on.
+ */
+ if(*trial == 3){
+ if(oauth2.param[OA2_State].value)
+ fs_give((void **) &oauth2.param[OA2_State].value);
+ fs_give((void **) &oauth2.param[OA2_RefreshToken].value);
+ fs_give((void **) &oauth2.access_token);
+ oauth2.expiration = 0L;
+ }
+ }
+ }
+ fs_give ((void **) &response);
+ }
+ }
+ if (!ret || !oauth2.name || tryanother)
+ *trial = 65535; /* don't retry if bad protocol */
+ return ret;
+}
+
+/*
+ * 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,
+ 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;
+
+ if(oauth2->param[OA2_Id].value == NULL
+ || oauth2->param[OA2_Secret].value == NULL){
+ /*
+ * We need to implement client-side entering client_id and
+ * client_secret, and other parameters. In the mean time, bail out.
+ */
+ 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);
+ else if(strcmp(RefreshMethod.name, "POST2") == 0)
+ s = http_post_param2(RefreshMethod.urlserver, params);
+
+ if(s){
+ unsigned char *t, *u;
+ if((t = strstr(s, "\r\n\r\n")) && (u = strchr(t, '{')))
+ 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){
+ if(jx->jtype == JString){
+ unsigned long *l = fs_get(sizeof(unsigned long));
+ *l = atol((char *) jx->value);
+ fs_give(&jx->value);
+ jx->value = (void *) l;
+ jx->jtype = JLong;
+ }
+ if(jx->jtype == JLong)
+ oauth2->expiration = time(0) + *(unsigned long *) 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, 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);
+ else if(strcmp(RefreshMethod.name, "POST2") == 0)
+ s = http_post_param2(RefreshMethod.urlserver, params);
+
+ if(s){
+ unsigned char *t, *u;
+ if((t = strstr(s, "\r\n\r\n")) && (u = strchr(t, '{')))
+ 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){
+ if(jx->jtype == JString){
+ unsigned long *l = fs_get(sizeof(unsigned long));
+ *l = atol((char *) jx->value);
+ fs_give(&jx->value);
+ jx->value = (void *) l;
+ jx->jtype = JLong;
+ }
+ if(jx->jtype == JLong)
+ oauth2->expiration = time(0) + *(unsigned long *) jx->value;
+ }
+ json_free(&json);
+ }
+ }
+ return;
+ }
+}
diff --git a/imap/src/c-client/auth_pla.c b/imap/src/c-client/auth_pla.c
index ef991f1..6cc26c7 100644
--- a/imap/src/c-client/auth_pla.c
+++ b/imap/src/c-client/auth_pla.c
@@ -55,7 +55,7 @@ long auth_plain_client (authchallenge_t challenger,authrespond_t responder,
char *service,NETMBX *mb,void *stream,
unsigned long *trial,char *user)
{
- char *u,pwd[MAILTMPLEN];
+ char *u, *pwd = NIL;
void *challenge;
unsigned long clen;
long ret = NIL;
@@ -70,9 +70,8 @@ long auth_plain_client (authchallenge_t challenger,authrespond_t responder,
(*responder) (stream,NIL,0);
ret = LONGT; /* will get a BAD response back */
}
- pwd[0] = NIL; /* prompt user if empty challenge */
- mm_login (mb,user,pwd,*trial);
- if (!pwd[0]) { /* empty challenge or user requested abort */
+ mm_login (mb,user, &pwd,*trial);
+ if (!pwd) { /* empty challenge or user requested abort */
(*responder) (stream,NIL,0);
*trial = 0; /* cancel subsequent attempts */
ret = LONGT; /* will get a BAD response back */
@@ -100,9 +99,10 @@ long auth_plain_client (authchallenge_t challenger,authrespond_t responder,
}
memset (response,0,rlen); /* erase credentials */
fs_give ((void **) &response);
+ fs_give ((void **) &pwd);
}
}
- memset (pwd,0,MAILTMPLEN); /* erase password */
+ if(pwd) fs_give ((void **) &pwd);
if (!ret) *trial = 65535; /* don't retry if bad protocol */
return ret;
}
diff --git a/imap/src/c-client/http.c b/imap/src/c-client/http.c
new file mode 100644
index 0000000..1e580c6
--- /dev/null
+++ b/imap/src/c-client/http.c
@@ -0,0 +1,1215 @@
+/*
+ * Copyright 2018 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
+ *
+ */
+#include <ctype.h>
+#include <stdio.h>
+#include <time.h>
+#include "c-client.h"
+#include "flstring.h"
+#include "netmsg.h"
+#include "http.h"
+
+//char t[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~";
+static char http_notok[] = "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\42\50\51\54\57\72\73\74\75\76\77\100\133\134\135\173\175\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377";
+static char http_noparam_val[] = "\1\2\3\4\5\6\7\10\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\42\134\177";
+
+#define HTTPTCPPORT (long) 80 /* assigned TCP contact port */
+#define HTTPSSLPORT (long) 443 /* assigned SSL TCP contact port */
+
+typedef struct http_request_s {
+ char *request;
+ char *header;
+ char *body;
+} HTTP_REQUEST_S;
+
+#define HTP_NOVAL 0x001 /* the header accepts parameters without value */
+
+#define HTP_UNLIMITED (-1) /* parse and infinite list */
+
+#if 0
+typedef struct http_val_param_s {
+ char *value;
+ PARAMETER *plist;
+} HTTP_VAL_PARAM_S;
+
+typedef struct http_param_list_s {
+ HTTP_VAL_PARAM_S *vp;
+ struct http_param_list_s *next;
+} HTTP_PARAM_LIST_S;
+
+typedef struct http_header_value_s {
+ char *data;
+ HTTP_PARAM_LIST_S *p;
+} HTTP_HEADER_S;
+
+typedef struct http_header_data_s {
+ HTTP_HEADER_S *accept, /* RFC 7231, Section 5.3.2 */
+ *accept_charset, /* RFC 7231, Section 5.3.3 */
+ *accept_encoding, /* RFC 7231, Section 5.3.4 */
+ *accept_language, /* RFC 7231, Section 5.3.5 */
+ *accept_ranges, /* RFC 7233, Section 2.3 */
+ *age, /* RFC 7234, Section 5.1 */
+ *allow, /* RFC 7231, Section 7.4.1 */
+ *cache_control, /* RFC 7234, Section 5.2 */
+ *connection, /* RFC 7230, Section 6.1 */
+ *content_encoding, /* RFC 7231, Section 3.1.2.2 */
+ *content_disposition, /* RFC 6266 */
+ *content_language, /* RFC 7231, Section 3.1.3.2 */
+ *content_length, /* RFC 7230, Section 3.3.2 */
+ *content_location, /* RFC 7231, Section 3.1.4.2 */
+ *content_type, /* RFC 7231, Section 3.1.1.5 */
+ *date, /* RFC 7231, Section 7.1.1.2 */
+ *etag, /* RFC 7232, Section 2.3 */
+ *expect, /* RFC 7231, Section 5.1.1 */
+ *expires, /* RFC 7234, Section 5.3 */
+ *from, /* RFC 7231, Section 5.5.1 */
+ *host, /* RFC 7230, Section 5.4 */
+ *last_modified, /* RFC 7232, Section 2.2 */
+ *location, /* RFC 7231, Section 7.1.2 */
+ *max_forwards, /* RFC 7231, Section 5.1.2 */
+ *mime_version, /* RFC 7231, Appendix A.1 */
+ *pragma, /* RFC 7234, Section 5.4 */
+ *proxy_authenticate, /* RFC 7235, Section 4.3 */
+ *referer, /* RFC 7231, Section 5.5.2 */
+ *retry_after, /* RFC 7231, Section 7.1.3 */
+ *server, /* RFC 7231, Section 7.4.2 */
+ *te, /* RFC 7230, Section 4.3 */
+ *trailer, /* RFC 7230, Section 4.4 */
+ *transfer_encoding, /* RFC 7230, Section 3.3.1 */
+ *upgrade, /* RFC 7230, Section 6.7 */
+ *user_agent, /* RFC 7231, Section 5.5.3 */
+ *via, /* RFC 7230, Section 5.7.1 */
+ *vary, /* RFC 7231, Section 7.1.4 */
+ *warning, /* RFC 7234, Section 5.5 */
+ *www_authenticate; /* RFC 7235, Section 4.1 */
+} HTTP_HEADER_DATA_S;
+#endif
+
+/* helper functions */
+HTTP_STATUS_S *http_status_line_get(char *);
+void http_status_line_free(HTTP_STATUS_S **);
+HTTP_REQUEST_S *http_request_get(void);
+void http_request_free(HTTP_REQUEST_S **);
+char *http_request_line(char *, char *, char *);
+void http_add_header(HTTP_REQUEST_S **, char *, char *);
+void http_add_body(HTTP_REQUEST_S **, char *);
+void buffer_add(char **, char *);
+char *hex_escape_url_part(char *, char *);
+char *encode_url_body_part(char *, char *);
+
+
+/* HTTP function prototypes */
+int http_valid_net_parse (char *, NETMBX *);
+void *http_parameters (long function,void *value);
+
+long http_send (HTTPSTREAM *stream, HTTP_REQUEST_S *);
+long http_reply (HTTPSTREAM *stream);
+long http_fake (HTTPSTREAM *stream, char *text);
+
+void http_skipows(char **);
+void http_remove_trailing_ows(char *);
+
+int valid_dquote_text(char *);
+#define valid_token_name(X) (strpbrk((X), http_notok) ? 0 : 1)
+#define valid_parameter_value(X) \
+ ((valid_token_name((X)) || valid_dquote_text((X))) ? 1 : 0)
+
+/* HTTP HEADER FUNCTIONS */
+void http_add_header_data(HTTPSTREAM *, char *);
+void http_add_data_to_header(HTTP_HEADER_S **, char *);
+
+HTTP_PARAM_LIST_S *http_parse_token_parameter(char *, int);
+HTTP_PARAM_LIST_S *http_parse_token_list(char *, int);
+PARAMETER *http_parse_parameter(char *, int);
+
+void http_parse_headers(HTTPSTREAM *);
+
+void
+http_parse_headers(HTTPSTREAM *stream)
+{
+ HTTP_HEADER_DATA_S *hd;
+ HTTP_HEADER_S *h;
+
+ if(!stream || !stream->header) return;
+
+ hd = stream->header;
+
+ if(((h = hd->accept)) && h->data){ /* RFC 7231, Section 5.3.2 */
+ h->p = http_parse_token_parameter(h->data, HTP_NOVAL);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->accept_charset)) && h->data){ /* RFC 7231, Section 5.3.3 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->accept_encoding)) && h->data){ /* RFC 7231, Section 5.3.4 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->accept_language)) && h->data){ /* RFC 7231, Section 5.3.5 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->accept_ranges)) && h->data){ /* RFC 7233, Section 2.3 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->age)) && h->data){ /* RFC 7234, Section 5.1 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->allow)) && h->data){ /* RFC 7231, Section 7.4.1 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->cache_control)) && h->data){ /* RFC 7234, Section 5.2 */
+ h->p = http_parse_token_parameter(h->data, HTP_NOVAL);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->connection)) && h->data){ /* RFC 7230, Section 6.1 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_encoding)) && h->data){ /* RFC 7231, Section 3.1.2.2 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_disposition)) && h->data){ /* RFC 6266 */
+ h->p = http_parse_token_parameter(h->data, HTP_NOVAL);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_language)) && h->data){ /* RFC 7231, Section 3.1.3.2 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_length)) && h->data){ /* RFC 7230, Section 3.3.2 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_location)) && h->data){ /* RFC 7231, Section 3.1.4.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_type)) && h->data){ /* RFC 7231, Section 3.1.1.5 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->date)) && h->data){ /* RFC 7231, Section 7.1.1.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->etag)) && h->data){ /* Rewrite this. RFC 7232, Section 2.3 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->expect)) && h->data){ /* Rewrite this. RFC 7231, Section 5.1.1 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->expires)) && h->data){ /* Rewrite this. RFC 7234, Section 5.3 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->from)) && h->data){ /* Rewrite this. RFC 7231, Section 5.5.1 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->host)) && h->data){ /* Rewrite this. RFC 7230, Section 5.4 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->last_modified)) && h->data){ /* Rewrite this. RFC 7232, Section 2.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->location)) && h->data){ /* Rewrite this. RFC 7231, Section 7.1.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->max_forwards)) && h->data){ /* RFC 7231, Section 5.1.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->mime_version)) && h->data){ /* Rewrite this. RFC 7231, Appendix A.1 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->pragma)) && h->data){ /* RFC 7234, Section 5.4 */
+ h->p = http_parse_token_parameter(h->data, HTP_NOVAL);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->proxy_authenticate)) && h->data){ /* Rewrite this. RFC 7235, Section 4.3 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->referer)) && h->data){ /* Rewrite this. RFC 7231, Section 5.5.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->retry_after)) && h->data){ /* Rewrite this. RFC 7231, Section 7.1.3 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->server)) && h->data){ /* Rewrite this. RFC 7231, Section 7.4.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->te)) && h->data){ /* Rewrite this. RFC 7230, Section 4.3 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->trailer)) && h->data){ /* RFC 7230, Section 4.4 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->transfer_encoding)) && h->data){ /* Rewrite this. RFC 7230, Section 3.3.1 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->upgrade)) && h->data){ /* Rewrite this. RFC 7230, Section 6.7 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->user_agent)) && h->data){ /* Rewrite this. RFC 7231, Section 5.5.3 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->via)) && h->data){ /* Rewrite this. RFC 7230, Section 5.7.1 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->vary)) && h->data){ /* Rewrite this. RFC 7231, Section 7.1.4 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->warning)) && h->data){ /* Rewrite this. RFC 7234, Section 5.5 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->www_authenticate)) && h->data){ /* Rewrite this. RFC 7235, Section 4.1 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+}
+
+
+void
+http_add_data_to_header(HTTP_HEADER_S **headerp, char *data)
+{
+ HTTP_HEADER_S *h = *headerp;
+
+ if(!h){
+ h = fs_get(sizeof(HTTP_HEADER_S));
+ memset((void *)h, 0, sizeof(HTTP_HEADER_S));
+ }
+
+ if(h->data) buffer_add(&h->data, ", ");
+ buffer_add(&h->data, data);
+ *headerp = h;
+}
+
+void
+http_add_header_data(HTTPSTREAM *stream, char *hdata)
+{
+ char *hname, *h;
+ size_t hlen;
+ int found = 1;
+
+ if(!stream || !hdata || !*hdata) return;
+
+ if(!stream->header){
+ stream->header = fs_get(sizeof(HTTP_HEADER_DATA_S));
+ memset((void *) stream->header, 0, sizeof(HTTP_HEADER_DATA_S));
+ }
+
+
+ /* extract header name first */
+ if((h = strchr(hdata, ':'))){
+ *h = '\0';
+ hname = fs_get(h-hdata+1);
+ strncpy(hname, hdata, h-hdata);
+ hname[h-hdata] = '\0';
+ if(!valid_token_name(hname))
+ return;
+ hname[h-hdata] = ':';
+ hname[h-hdata+1] = '\0';
+ *h++ = ':';
+ }
+ else return;
+
+ switch(*hname){
+ case 'A':
+ case 'a': if(!compare_cstring(hname+1, "ccept:")) /* RFC 7231, Section 5.3.2 */
+ http_add_data_to_header(&stream->header->accept, h);
+ else if(!compare_cstring(hname+1, "ccept-charset:")) /* RFC 7231, Section 5.3.3 */
+ http_add_data_to_header(&stream->header->accept_charset, h);
+ else if(!compare_cstring(hname+1, "ccept-encoding:")) /* RFC 7231, Section 5.3.4 */
+ http_add_data_to_header(&stream->header->accept_encoding, h);
+ else if(!compare_cstring(hname+1, "ccept-language:")) /* RFC 7231, Section 5.3.5 */
+ http_add_data_to_header(&stream->header->accept_language, h);
+ else if(!compare_cstring(hname+1, "ccept-ranges:")) /* RFC 7233, Section 2.3 */
+ http_add_data_to_header(&stream->header->accept_ranges, h);
+ else if(!compare_cstring(hname+1, "ge:")) /* RFC 7234, Section 5.1 */
+ http_add_data_to_header(&stream->header->age, h);
+ else if(!compare_cstring(hname+1, "llow:")) /* RFC 7231, Section 7.4.1 */
+ http_add_data_to_header(&stream->header->allow, h);
+ else found = 0;
+ break;
+
+ case 'C':
+ case 'c': if(!compare_cstring(hname+1, "ache-control:")) /* RFC 7234, Section 5.2 */
+ http_add_data_to_header(&stream->header->cache_control, h);
+ else if(!compare_cstring(hname+1, "onnection:")) /* RFC 7230, Section 6.1 */
+ http_add_data_to_header(&stream->header->connection, h);
+ else if(!compare_cstring(hname+1, "ontent-disposition:")) /* RFC 6266 */
+ http_add_data_to_header(&stream->header->content_disposition, h);
+ else if(!compare_cstring(hname+1, "ontent-encoding:")) /* RFC 7231, Section 3.1.2.2 */
+ http_add_data_to_header(&stream->header->content_encoding, h);
+ else if(!compare_cstring(hname+1, "ontent-language:")) /* RFC 7231, Section 3.1.3.2 */
+/* rewrite this */ http_add_data_to_header(&stream->header->content_language, h);
+ else if(!compare_cstring(hname+1, "ontent-length:")) /* RFC 7230, Section 3.3.2 */
+ http_add_data_to_header(&stream->header->content_length, h);
+ else if(!compare_cstring(hname+1, "ontent-location:")) /* RFC 7231, Section 3.1.4.2 */
+/* rewrite this */ http_add_data_to_header(&stream->header->content_location, h);
+ else if(!compare_cstring(hname+1, "ontent-type:")) /* RFC 7231, Section 3.1.1.5 */
+ http_add_data_to_header(&stream->header->content_type, h);
+ else found = 0;
+ break;
+
+ case 'D':
+ case 'd': if(!compare_cstring(hname+1, "ate:")) /* RFC 7231, Section 7.1.1.2 */
+/* revise this */ http_add_data_to_header(&stream->header->date, h);
+ else found = 0;
+ break;
+
+ case 'E':
+ case 'e': if(!compare_cstring(hname+1, "tag:")) /* RFC 7232, Section 2.3 */
+/* rewrite this */ http_add_data_to_header(&stream->header->etag, h);
+ else if(!compare_cstring(hname+1, "xpect:")) /* RFC 7231, Section 5.1.1 */
+/* rewrite this */ http_add_data_to_header(&stream->header->expect, h);
+ else if(!compare_cstring(hname+1, "xpires:")) /* RFC 7234, Section 5.3 */
+/* rewrite this */ http_add_data_to_header(&stream->header->expires, h);
+ else found = 0;
+ break;
+
+ case 'F':
+ case 'f': if(!compare_cstring(hname+1, "rom:")) /* RFC 7231, Section 5.5.1 */
+/* rewrite this */ http_add_data_to_header(&stream->header->from, h);
+ else found = 0;
+ break;
+
+ case 'H':
+ case 'h': if(!compare_cstring(hname+1, "ost:")) /* RFC 7230, Section 5.4 */
+ http_add_data_to_header(&stream->header->host, h);
+ else found = 0;
+ break;
+
+ case 'L':
+ case 'l': if(!compare_cstring(hname+1, "ast-modified:")) /* RFC 7232, Section 2.2 */
+ http_add_data_to_header(&stream->header->last_modified, h);
+ else if(!compare_cstring(hname+1, "ocation:")) /* RFC 7231, Section 7.1.2 */
+ http_add_data_to_header(&stream->header->location, h);
+ else found = 0;
+ break;
+
+ case 'M':
+ case 'm': if(!compare_cstring(hname+1, "ax-forwards:")) /* RFC 7231, Section 5.1.2 */
+ http_add_data_to_header(&stream->header->max_forwards, h);
+ else if(!compare_cstring(hname+1, "ime-version:")) /* RFC 7231, Appendix A.1 */
+ http_add_data_to_header(&stream->header->mime_version, h);
+ else found = 0;
+ break;
+
+ case 'P':
+ case 'p': if(!compare_cstring(hname+1, "ragma:")) /* RFC 7234, Section 5.4 */
+ http_add_data_to_header(&stream->header->pragma, h);
+ else if(!compare_cstring(hname+1, "roxy-authenticate:")) /* RFC 7235, Section 4.3 */
+ http_add_data_to_header(&stream->header->proxy_authenticate, h);
+ else found = 0;
+ break;
+
+ case 'R':
+ case 'r': if(!compare_cstring(hname+1, "eferer:")) /* RFC 7231, Section 5.5.2 */
+ http_add_data_to_header(&stream->header->referer, h);
+ else if(!compare_cstring(hname+1, "etry-after:")) /* RFC 7231, Section 7.1.3 */
+ http_add_data_to_header(&stream->header->retry_after, h);
+ else found = 0;
+ break;
+
+ case 'S':
+ case 's': if(!compare_cstring(hname+1, "erver:")) /* RFC 7231, Section 7.4.2 */
+ http_add_data_to_header(&stream->header->server, h);
+ else found = 0;
+ break;
+
+ case 'T':
+ case 't': if(!compare_cstring(hname+1, "e:")) /* RFC 7230, Section 4.3 */
+ http_add_data_to_header(&stream->header->te, h);
+ else if(!compare_cstring(hname+1, "railer:")) /* RFC 7230, Section 4.4 */
+ http_add_data_to_header(&stream->header->trailer, h);
+ else if(!compare_cstring(hname+1, "ransfer-encoding:")) /* RFC 7230, Section 3.3.1 */
+ http_add_data_to_header(&stream->header->transfer_encoding, h);
+ else found = 0;
+ break;
+ break;
+
+ case 'U':
+ case 'u': if(!compare_cstring(hname+1, "pgrade:")) /* RFC 7230, Section 6.7 */
+ http_add_data_to_header(&stream->header->upgrade, h);
+ else if(!compare_cstring(hname+1, "ser-agent:")) /* RFC 7231, Section 5.5.3 */
+ http_add_data_to_header(&stream->header->user_agent, h);
+ else found = 0;
+ break;
+
+ case 'V':
+ case 'v': if(!compare_cstring(hname+1, "ia:")) /* RFC 7230, Section 5.7.1 */
+ http_add_data_to_header(&stream->header->via, h);
+ else if(!compare_cstring(hname+1, "ary:")) /* RFC 7231, Section 7.1.4 */
+ http_add_data_to_header(&stream->header->vary, h);
+ else found = 0;
+ break;
+
+ case 'W':
+ case 'w': if(!compare_cstring(hname+1, "arning:")) /* RFC 7234, Section 5.5 */
+ http_add_data_to_header(&stream->header->warning, h);
+ else if(!compare_cstring(hname+1, "ww-authenticate:")) /* RFC 7235, Section 4.1 */
+ http_add_data_to_header(&stream->header->www_authenticate, h);
+ else found = 0;
+ break;
+
+ default: break;
+ }
+}
+
+
+/* parse a list of tokens. If num is positive, parse at most
+ * num members in the list. Set num to HTP_UNLIMITED for a list
+ * without bounds
+ */
+HTTP_PARAM_LIST_S *
+http_parse_token_list(char *h, int num)
+{
+ char *s = h, *t, *u, c, d;
+ HTTP_PARAM_LIST_S *rv = NIL;
+
+ if(!s || !*s || num == 0) return NIL;
+ http_skipows(&s);
+ if(!*s) return NIL;
+ for(t = s; *t != '\0' && *t != ','; t++);
+ c = *t; *t = '\0';
+ http_remove_trailing_ows(s);
+
+ if(!valid_token_name(s))
+ return c == ',' ? http_parse_token_list(t+1, num) : NIL;
+
+ if(num > 0) num--; /* this one counts! */
+ rv = fs_get(sizeof(HTTP_PARAM_LIST_S));
+ memset((void *) rv, 0, sizeof(HTTP_PARAM_LIST_S));
+ rv->vp = fs_get(sizeof(HTTP_VAL_PARAM_S));
+ memset((void *) rv->vp, 0, sizeof(HTTP_VAL_PARAM_S));
+ rv->vp->value = cpystr(s);
+ *t = c;
+ if(c == ',')
+ rv->next = http_parse_token_list(t+1, num);
+
+ return rv;
+}
+
+
+/*
+ * parse a list of tokens with optional parameters
+ * into a HEADER_DATA structure. Do not parse into
+ * it anything invalid.
+ */
+HTTP_PARAM_LIST_S *
+http_parse_token_parameter(char *h, int flag)
+{
+ char *s = h, *t, *u, c, d;
+ HTTP_PARAM_LIST_S *rv = NIL;
+
+ /*
+ * Step 1:
+ * isolate first list element from list and remove
+ * leading and trailing white space.
+ */
+ if(!s) return NIL;
+ http_skipows(&s);
+ if(!*s) return NIL;
+ for(t = s; *t != '\0' && *t != ','; t++);
+ c = *t; *t = '\0';
+ http_remove_trailing_ows(s);
+
+ /*
+ * Step 2:
+ * isolate token name from its parameters. Remove
+ * any trailing spaces. If not valid token, move
+ * to the next entry in the list.
+ */
+ for(u = s; *u != '\0' && *u != ';'; u++);
+ d = *u; *u = '\0';
+ http_remove_trailing_ows(s);
+ if(!valid_token_name(s))
+ return c == ',' ? http_parse_token_parameter(t+1, flag) : NIL;
+
+ /*
+ * Step 3:
+ * If we make it this far, create a non-null reply
+ * and parse the token and parameters into a
+ * HTTP_HEADER_DATA_S structure
+ */
+ rv = fs_get(sizeof(HTTP_PARAM_LIST_S));
+ memset((void *) rv, 0, sizeof(HTTP_PARAM_LIST_S));
+ rv->vp = fs_get(sizeof(HTTP_VAL_PARAM_S));
+ memset((void *) rv->vp, 0, sizeof(HTTP_VAL_PARAM_S));
+ rv->vp->value = cpystr(s);
+ if(d == ';')
+ rv->vp->plist = http_parse_parameter(u+1, flag);
+ *u = d;
+ *t = c;
+ if(c == ',')
+ rv->next = http_parse_token_parameter(t+1, flag);
+
+ return rv;
+}
+
+int
+valid_dquote_text(char *s)
+{
+ char *t;
+
+ if(!s || *s != '\"') return 0;
+
+ t = strchr(s+1, '\"');
+ return (t && !t[1]) ? 1 : 0;
+}
+
+
+void
+http_skipows(char **sp)
+{
+ char *s = *sp;
+ for(; *s == ' ' || *s == '\t'; s++);
+ *sp = s;
+}
+
+void
+http_remove_trailing_ows(char *s)
+{
+ char *t;
+ for(t = s; strlen(t) > 0 ;)
+ if(t[strlen(t)-1] == ' ' || t[strlen(t)-1] == '\t')
+ t[strlen(t)-1] = '\0';
+ else
+ break;
+}
+
+PARAMETER *
+http_parse_parameter(char *s, int flag)
+{
+ PARAMETER *p;
+ char *t, *u, c, d;
+
+ /* Step 1:
+ * separate the parameters into a list separated by ";"
+ */
+ if(!s || !*s) return NIL;
+ http_skipows(&s);
+ if(!*s) return NIL;
+ for(t = s; *t != '\0' && *t != ';'; t++);
+ c = *t; *t = '\0';
+
+ /* Now we look for separation of attribute and value */
+ u = strchr(s, '=');
+
+ if(u){
+ *u = '\0';
+ http_remove_trailing_ows(s); http_remove_trailing_ows(u+1);
+ if(!valid_token_name(s) || !valid_parameter_value(u+1))
+ return c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+ p = mail_newbody_parameter();
+ p->attribute = cpystr(s);
+ p->value = cpystr(u+1);
+ p->next = c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+ *u = '=';
+ }
+ else if(flag & HTP_NOVAL){
+ /* this is a parameter with attribute but no value. RFC 7231
+ * section 5.3.2 allows this.
+ */
+ http_remove_trailing_ows(s);
+ if(!valid_token_name(s))
+ return c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+ p = mail_newbody_parameter();
+ p->attribute = cpystr(s);
+ p->next = c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+ } else
+ p = c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+
+ return p;
+}
+
+char *
+http_get_param_url(char *url, HTTP_PARAM_S *param)
+{
+ int i;
+ char *rv = NULL;
+ HTTP_PARAM_S enc_param;
+
+ buffer_add(&rv, url);
+ for(i = 0; param[i].name != NULL; i++){
+ enc_param.name = hex_escape_url_part(param[i].name, NULL);
+ enc_param.value = hex_escape_url_part(param[i].value, NULL);
+ buffer_add(&rv, i == 0 ? "?" : "&");
+ buffer_add(&rv, enc_param.name);
+ buffer_add(&rv, "=");
+ buffer_add(&rv, enc_param.value);
+ fs_give((void **) &enc_param.name);
+ fs_give((void **) &enc_param.value);
+ }
+
+ return rv;
+}
+
+HTTP_REQUEST_S *
+http_request_get(void)
+{
+ HTTP_REQUEST_S *rv = fs_get(sizeof(HTTP_REQUEST_S));
+ memset((void *) rv, 0, sizeof(HTTP_REQUEST_S));
+
+ return rv;
+}
+
+void
+http_request_free(HTTP_REQUEST_S **hr)
+{
+ if(!hr) return;
+
+ if((*hr)->request) fs_give((void **) &(*hr)->request);
+ if((*hr)->header) fs_give((void **) &(*hr)->header);
+ if((*hr)->body) fs_give((void **) &(*hr)->body);
+ fs_give((void **) hr);
+}
+
+char *
+http_request_line(char *method, char *target, char *version)
+{
+ int len = strlen(method) + strlen(target) + strlen(version) + 2 + 1;
+ char *line = fs_get(len);
+
+ sprintf(line, "%s %s %s", method, target, version);
+ return line;
+}
+
+void
+http_add_header(HTTP_REQUEST_S **reqp, char *name, char *value)
+{
+ int len, hlen;
+
+ if(!reqp) return;
+
+ if(!*reqp) *reqp = http_request_get();
+
+ len = strlen(name) + 2 + strlen(value) + 2 + 1;
+ hlen = (*reqp)->header ? strlen((*reqp)->header) : 0;
+ len += hlen;
+ fs_resize((void **) &(*reqp)->header, len);
+ sprintf((*reqp)->header + hlen, "%s: %s\015\012", name, value);
+}
+
+void
+buffer_add(char **bufp, char *text)
+{
+ int len;
+
+ if(!bufp || !text || !*text) return;
+
+ len = *bufp ? strlen(*bufp) : 0;
+ fs_resize((void **) bufp, len + strlen(text) + 1);
+ (*bufp)[len] = '\0';
+ strcat(*bufp, text);
+}
+
+void
+http_add_body(HTTP_REQUEST_S **reqp, char *text)
+{
+ if(!reqp) return;
+
+ if(!*reqp) *reqp = http_request_get();
+
+ buffer_add(&(*reqp)->body, text);
+}
+
+
+/* NULL terminated list of HTTP_PARAM_S objects.
+ * If caller needs "x" parameters, call this function
+ * with argument "x+1".
+ */
+HTTP_PARAM_S *
+http_param_get(int len)
+{
+ HTTP_PARAM_S *http_params;
+
+ http_params = fs_get(len*sizeof(HTTP_PARAM_S));
+ memset((void *) http_params, 0, len*sizeof(HTTP_PARAM_S));
+ return http_params;
+}
+
+void
+http_param_free(HTTP_PARAM_S **param)
+{
+ int i;
+
+ if(param == NULL) return;
+
+ for(i = 0; (*param)[i].name != NULL; i++)
+ fs_give((void **) &(*param)[i].name);
+
+ for(i = 0; (*param)[i].value != NULL; i++)
+ fs_give((void **) &(*param)[i].value);
+
+ fs_give((void **) param);
+}
+
+
+/* This encodes for a GET request */
+char *
+hex_escape_url_part(char *text, char *addsafe)
+{
+ char *safechars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-";
+ char *s = fs_get(3*strlen(text) + 1), *t;
+
+ *s = '\0';
+ for(t = text; *t != '\0'; t++)
+ if(strchr(safechars, *t) != NULL
+ || (addsafe != NULL && strchr(addsafe, *t) != NULL))
+ sprintf(s + strlen(s), "%c", *t);
+ else
+ sprintf(s + strlen(s), "%%%X", *t);
+ fs_resize((void **) &s, strlen(s)+1);
+ return s;
+}
+
+/* this encodes for a POST request */
+char *
+encode_url_body_part(char *text, char *addsafe)
+{
+ char *safechars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-";
+ char *s = fs_get(3*strlen(text) + 1), *t;
+
+ *s = '\0';
+ for(t = text; *t != '\0'; t++)
+ if(*t == ' ') /* ASCII 32 is never safe, must always be encoded */
+ sprintf(s + strlen(s), "%c", '+');
+ else if(strchr(safechars, *t) != NULL
+ || (addsafe != NULL && strchr(addsafe, *t) != NULL))
+ sprintf(s + strlen(s), "%c", *t);
+ else
+ sprintf(s + strlen(s), "%%%X", *t);
+ fs_resize((void **) &s, strlen(s)+1);
+ return s;
+}
+
+int
+http_valid_net_parse (char *url, NETMBX *mb)
+{
+ int i, len;
+ char *s, *p;
+
+ if((url == NIL)
+ || (url[0] != 'h' && url[0] != 'H')
+ || (url[1] == 't' && url[1] == 'T')
+ || (url[2] == 't' && url[2] == 'T')
+ || (url[3] == 'p' && url[3] == 'P'))
+ return 0;
+
+ if(url[i = 4] == 's' || url[i] == 'S')
+ mb->sslflag = mb->notlsflag = T;
+ else i = 3;
+
+ if(url[++i] != ':' || url[++i] != '/' || url[++i] != '/')
+ return 0;
+
+ strcpy(mb->service, "http");
+ s = strchr(url+i+1, '/');
+ len = s ? s - url - i - 1 : strlen(url+i+1);
+ strncpy(mb->orighost, url+i+1, len);
+ mb->orighost[len] = '\0';
+ if((p = strchr(mb->orighost, ':')) != NULL){
+ *p++ = '\0';
+ mb->port = strtoul(p, &p, 10);
+ if(mb->port == 0L || *p != '\0')
+ return NIL;
+ }
+ strcpy(mb->host, mb->orighost);
+ return T;
+}
+
+HTTPSTREAM *
+http_open (char *url)
+{
+ HTTPSTREAM *stream;
+ NETMBX mb;
+ char *s;
+
+ memset((void *) &mb, 0, sizeof(NETMBX));
+ if(http_valid_net_parse (url,&mb) == 0)
+ return NIL;
+
+ stream = fs_get(sizeof(HTTPSTREAM));
+ memset((void *) stream, 0, sizeof(HTTPSTREAM));
+
+ s = strchr(url + 7 + (mb.trysslflag ? 1 : 0) + 1, '/'); /* 7 = strlen("http://") + 1 */
+ stream->url = cpystr(url);
+ stream->urlhost = cpystr(mb.orighost);
+ stream->urltail = cpystr(s ? s : "/");
+ stream->netstream = net_open (&mb, NIL, mb.port ? mb.port : HTTPTCPPORT,
+ (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL),
+ "*https", mb.port ? mb.port : HTTPSSLPORT);
+ if(!stream->netstream) http_close(stream);
+ return stream;
+}
+
+char *
+http_post_param(char *url, HTTP_PARAM_S *param)
+{
+ HTTPSTREAM *stream;
+ HTTP_PARAM_S enc_param;
+ HTTP_REQUEST_S *http_request;
+ char *reply;
+ int i;
+
+ if(url == NULL || param == NULL || (stream = http_open(url)) == NULL)
+ return NULL;
+
+ http_request = http_request_get();
+ http_request->request = http_request_line("POST", stream->urltail, HTTP_1_1_VERSION);
+ http_add_header(&http_request, "Host", stream->urlhost);
+ http_add_header(&http_request, "Content-Type", HTTP_MIME_URLENCODED);
+
+ for(i = 0; param[i].name != NULL; i++){
+ enc_param.name = encode_url_body_part(param[i].name, NULL);
+ enc_param.value = encode_url_body_part(param[i].value, NULL);
+ if(i > 0)
+ http_add_body(&http_request, "&");
+ http_add_body(&http_request, enc_param.name);
+ http_add_body(&http_request, "=");
+ http_add_body(&http_request, enc_param.value);
+ fs_give((void **) &enc_param.name);
+ fs_give((void **) &enc_param.value);
+ }
+
+ if(http_send(stream, http_request)){
+ reply = cpystr(stream->reply ? stream->reply : "");
+ http_close(stream);
+ }
+
+ http_request_free(&http_request);
+
+ return reply;
+}
+
+char *
+http_post_param2(char *url, HTTP_PARAM_S *param)
+{
+ HTTPSTREAM *stream;
+ HTTP_PARAM_S enc_param;
+ HTTP_REQUEST_S *http_request;
+ char *reply;
+ int i;
+
+ if(url == NULL || param == NULL || (stream = http_open(url)) == NULL)
+ return NULL;
+
+ http_request = http_request_get();
+ http_request->request = http_request_line("POST", stream->urltail, HTTP_1_1_VERSION);
+ http_add_header(&http_request, "Host", stream->urlhost);
+ http_add_header(&http_request, "User-Agent", "Alpine");
+ http_add_header(&http_request, "Content-Type", HTTP_MIME_URLENCODED);
+
+ for(i = 0; param[i].name != NULL; i++){
+ enc_param.name = encode_url_body_part(param[i].name, NULL);
+ enc_param.value = encode_url_body_part(param[i].value, NULL);
+ if(i > 0)
+ http_add_body(&http_request, "&");
+ http_add_body(&http_request, enc_param.name);
+ http_add_body(&http_request, "=");
+ http_add_body(&http_request, enc_param.value);
+ fs_give((void **) &enc_param.name);
+ fs_give((void **) &enc_param.value);
+ }
+
+ if(http_send(stream, http_request)){
+ reply = cpystr(stream->reply ? stream->reply : "");
+ http_close(stream);
+ }
+
+ http_request_free(&http_request);
+
+ return reply;
+}
+
+char *
+http_get_param(char *base_url, HTTP_PARAM_S *param)
+{
+ HTTPSTREAM *stream;
+ char *url, *reply;
+
+ url = http_get_param_url(base_url, param);
+ if(url){
+ reply = http_get(url);
+ fs_give((void **) &url);
+ }
+ return reply;
+}
+
+char *
+http_get(char *url)
+{
+ HTTP_REQUEST_S *http_request;
+ char *reply;
+ HTTPSTREAM *stream;
+
+ if(!url) return NIL;
+ stream = http_open(url);
+ if(!stream){
+ fs_give((void **) &url);
+ return NIL;
+ }
+
+ http_request = http_request_get();
+ http_request->request = http_request_line("GET", stream->urltail, HTTP_1_1_VERSION);
+ http_add_header(&http_request, "Host", stream->urlhost);
+
+ if(http_send(stream, http_request)){
+ reply = cpystr(stream->reply ? stream->reply : "");
+ http_close(stream);
+ }
+
+ http_request_free(&http_request);
+
+ return reply;
+}
+
+void
+http_close (HTTPSTREAM *stream)
+{
+ if(stream){
+ if (stream->netstream) net_close (stream->netstream);
+ stream->netstream = NIL;
+ if (stream->url) fs_give ((void **) &stream->url);
+ if (stream->urlhost) fs_give ((void **) &stream->urlhost);
+ if (stream->urltail) fs_give ((void **) &stream->urltail);
+ if (stream->response) fs_give ((void **) &stream->response);
+ if (stream->reply) fs_give ((void **) &stream->reply);
+ fs_give((void **) &stream);
+ }
+}
+
+long
+http_send (HTTPSTREAM *stream, HTTP_REQUEST_S *req)
+{
+ long ret;
+ int len;
+ char *s = NULL;
+
+ if (!stream->netstream)
+ ret = http_fake (stream,"http connection lost");
+ else {
+ if(req->body){
+ char length[20];
+
+ sprintf(length, "%lu", strlen(req->body));
+ http_add_header(&req, "Content-Length", length);
+ }
+
+ buffer_add(&s, req->request); buffer_add(&s, "\015\012");
+ buffer_add(&s, req->header); buffer_add(&s, "\015\012");
+ buffer_add(&s, req->body); buffer_add(&s, "\015\012");
+ mm_log(s, TCPDEBUG);
+ ret = net_soutr (stream->netstream,s)
+ ? http_reply (stream)
+ : http_fake (stream,"http connection broken in command");
+ fs_give ((void **) &s);
+ }
+ return ret;
+}
+
+HTTP_STATUS_S *
+http_status_line_get(char *status_line)
+{
+ HTTP_STATUS_S *rv = NULL;
+ char *version, *s;
+ int status;
+ int len;
+
+ if(!status_line) return NIL;
+
+ if((s = strchr(status_line, ' ')) != NIL){
+ *s = '\0';
+ version = cpystr(status_line);
+ *s++ = ' ';
+ status = strtoul(s, &s, 10);
+ if(s && *s == ' ' && status >= 100 && status < 600){
+ rv = fs_get(sizeof(HTTP_STATUS_S));
+ rv->version = version;
+ rv->status = status;
+ rv->text = cpystr(++s);
+ }
+ else
+ fs_give((void **) &version);
+ }
+ return rv;
+}
+
+void
+http_status_line_free(HTTP_STATUS_S **status)
+{
+ if(status == NULL) return;
+
+ if((*status)->version) fs_give((void **) &(*status)->version);
+ if((*status)->text) fs_give((void **) &(*status)->text);
+ fs_give((void **) status);
+}
+
+
+long
+http_reply (HTTPSTREAM *stream)
+{
+ int in_header = 1;
+ HTTP_STATUS_S reply_status;
+ unsigned long size;
+
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getline(stream->netstream);
+
+ if(stream->response){
+ buffer_add(&stream->reply, stream->response);
+ buffer_add(&stream->reply, "\015\012");
+ }
+
+ if(stream->status) http_status_line_free(&stream->status);
+ stream->status = http_status_line_get(stream->response);
+
+ if(!stream->status){
+ http_fake(stream, "Invalid status line received. Closing connection");
+ return NIL;
+ }
+
+ while (in_header > 0){
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getline (stream->netstream);
+ if(stream->response){
+ buffer_add(&stream->reply, stream->response);
+ http_add_header_data(stream, stream->response);
+ }
+ buffer_add(&stream->reply, "\015\012");
+// save_header(stream->headers, stream->response);
+ if(!stream->response || *stream->response == '\0')
+ in_header--;
+ }
+
+ http_parse_headers(stream);
+ if(stream->header->content_length){
+ size = atol(stream->header->content_length->p->vp->value);
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getsize (stream->netstream, size);
+ if(stream->response) buffer_add(&stream->reply, stream->response);
+ }
+ else if (stream->header->transfer_encoding){
+ HTTP_PARAM_LIST_S *p = stream->header->transfer_encoding->p;
+ for(; p ; p = p->next){
+ if(!compare_cstring(p->vp->value, "chunked"))
+ break;
+ }
+ if(p && p->vp->value){ /* chunked transfer */
+ int done = 0;
+ size = 0L;
+ while(!done){
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getline (stream->netstream);
+ if(stream->response){
+ buffer_add(&stream->reply, stream->response);
+ buffer_add(&stream->reply, "\015\012");
+ size = strtol(stream->response, NIL, 16);
+ fs_give ((void **) &stream->response);
+ stream->response = net_getsize (stream->netstream, size);
+ buffer_add(&stream->reply, stream->response);
+ }
+ if(size == 0L) done++;
+ }
+ }
+ }
+
+#if 0
+ while(in_header == 0){
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getline (stream->netstream);
+ if(stream->response){
+ buffer_add(&stream->reply, stream->response);
+ buffer_add(&stream->reply, stream->response);
+ }
+ buffer_add(&stream->reply, "\015\012");
+ if(!stream->response || *stream->response == '\0')
+ in_header--;
+ }
+#endif
+
+ if(!stream->netstream)
+ http_fake(stream, "Connection to HTTP server closed");
+ return stream->netstream ? T : NIL;
+}
+
+long
+http_fake (HTTPSTREAM *stream, char *text)
+{
+ if (stream->netstream) net_close (stream->netstream);
+ stream->netstream = NIL;
+ if (stream->response) fs_give ((void **) &stream->response);
+ /* add *text to the log, to pass this to the client */
+ return NIL;
+}
diff --git a/imap/src/c-client/http.h b/imap/src/c-client/http.h
new file mode 100644
index 0000000..59b7e1d
--- /dev/null
+++ b/imap/src/c-client/http.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2018 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
+ *
+ * Last Edited: July 23, 2018 Eduardo Chappa <chappa@washington.edu>
+ *
+ */
+
+typedef struct http_val_param_s {
+ char *value;
+ PARAMETER *plist;
+} HTTP_VAL_PARAM_S;
+
+typedef struct http_param_list_s {
+ HTTP_VAL_PARAM_S *vp;
+ struct http_param_list_s *next;
+} HTTP_PARAM_LIST_S;
+
+typedef struct http_header_value_s {
+ char *data;
+ HTTP_PARAM_LIST_S *p;
+} HTTP_HEADER_S;
+
+typedef struct http_header_data_s {
+ HTTP_HEADER_S *accept, /* RFC 7231, Section 5.3.2 */
+ *accept_charset, /* RFC 7231, Section 5.3.3 */
+ *accept_encoding, /* RFC 7231, Section 5.3.4 */
+ *accept_language, /* RFC 7231, Section 5.3.5 */
+ *accept_ranges, /* RFC 7233, Section 2.3 */
+ *age, /* RFC 7234, Section 5.1 */
+ *allow, /* RFC 7231, Section 7.4.1 */
+ *cache_control, /* RFC 7234, Section 5.2 */
+ *connection, /* RFC 7230, Section 6.1 */
+ *content_disposition, /* RFC 6266 */
+ *content_encoding, /* RFC 7231, Section 3.1.2.2 */
+ *content_language, /* RFC 7231, Section 3.1.3.2 */
+ *content_length, /* RFC 7230, Section 3.3.2 */
+ *content_location, /* RFC 7231, Section 3.1.4.2 */
+ *content_type, /* RFC 7231, Section 3.1.1.5 */
+ *date, /* RFC 7231, Section 7.1.1.2 */
+ *etag, /* RFC 7232, Section 2.3 */
+ *expect, /* RFC 7231, Section 5.1.1 */
+ *expires, /* RFC 7234, Section 5.3 */
+ *from, /* RFC 7231, Section 5.5.1 */
+ *host, /* RFC 7230, Section 5.4 */
+ *last_modified, /* RFC 7232, Section 2.2 */
+ *location, /* RFC 7231, Section 7.1.2 */
+ *max_forwards, /* RFC 7231, Section 5.1.2 */
+ *mime_version, /* RFC 7231, Appendix A.1 */
+ *pragma, /* RFC 7234, Section 5.4 */
+ *proxy_authenticate, /* RFC 7235, Section 4.3 */
+ *referer, /* RFC 7231, Section 5.5.2 */
+ *retry_after, /* RFC 7231, Section 7.1.3 */
+ *server, /* RFC 7231, Section 7.4.2 */
+ *user_agent, /* RFC 7231, Section 5.5.3 */
+ *te, /* RFC 7230, Section 4.3 */
+ *trailer, /* RFC 7230, Section 4.4 */
+ *transfer_encoding, /* RFC 7230, Section 3.3.1 */
+ *upgrade, /* RFC 7230, Section 6.7 */
+ *via, /* RFC 7230, Section 5.7.1 */
+ *vary, /* RFC 7231, Section 7.1.4 */
+ *warning, /* RFC 7234, Section 5.5 */
+ *www_authenticate; /* RFC 7235, Section 4.1 */
+} HTTP_HEADER_DATA_S;
+
+#define HTTP_MIME_URLENCODED "application/x-www-form-urlencoded"
+
+#define HTTP_1_1_VERSION "HTTP/1.1"
+
+#define GET_HTTPPORT (long) 456
+#define SET_HTTPPORT (long) 457
+#define GET_SSLHTTPPORT (long) 458
+#define SET_SSLHTTPPORT (long) 459
+
+typedef struct http_status_s {
+ char *version;
+ int status;
+ char *text;
+} HTTP_STATUS_S;
+
+
+typedef struct http_stream {
+ NETSTREAM *netstream;
+ HTTP_HEADER_DATA_S *header; /* headers sent by the server */
+ char *url; /* original url */
+ char *urlhost; /* get original host */
+ char *urltail; /* the part of the URL after the original host */
+ HTTP_STATUS_S *status;/* parsed status line from server */
+ char *response; /* last reply line from server */
+ char *reply; /* the full reply from the server */
+} HTTPSTREAM;
+
+/* parameters for a get or post call */
+typedef struct http_param_s {
+ char *name;
+ char *value;
+} HTTP_PARAM_S;
+
+/* exported prototypes */
+HTTPSTREAM *http_open (char *);
+char *http_post_param(char *, HTTP_PARAM_S *);
+char *http_post_param2(char *, HTTP_PARAM_S *);
+char *http_get_param(char *, HTTP_PARAM_S *);
+char *http_get(char *);
+void http_close (HTTPSTREAM *stream);
+
+HTTP_PARAM_S *http_param_get(int);
+void http_param_free(HTTP_PARAM_S **);
+
+/* Ugghh.... just construct the URL for a get request */
+char *http_get_param_url(char *, HTTP_PARAM_S *);
diff --git a/imap/src/c-client/imap4r1.c b/imap/src/c-client/imap4r1.c
index b742305..aaaa66e 100644
--- a/imap/src/c-client/imap4r1.c
+++ b/imap/src/c-client/imap4r1.c
@@ -1142,14 +1142,22 @@ long imap_anon (MAILSTREAM *stream,char *tmp)
long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
{
- unsigned long trial,ua;
+ unsigned long trial,ua,uasaved;
int ok;
char tag[16];
char *lsterr = NIL;
- AUTHENTICATOR *at;
+ AUTHENTICATOR *at, *atsaved;
IMAPPARSEDREPLY *reply;
for (ua = LOCAL->cap.auth, LOCAL->saslcancel = NIL; LOCAL->netstream && ua &&
(at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) {
+ if(mb && *mb->auth){
+ if(!compare_cstring(at->name, mb->auth))
+ atsaved = at;
+ else{
+ uasaved = ua;
+ continue;
+ }
+ }
if (lsterr) { /* previous authenticator failed? */
sprintf (tmp,"Retrying using %s authentication after %.80s",
at->name,lsterr);
@@ -1201,6 +1209,11 @@ long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
}
fs_give ((void **) &lsterr);
}
+ if(mb && *mb->auth){
+ if(!uasaved) sprintf (tmp,"Client does not support AUTH=%.80s authenticator",mb->auth);
+ else if (!atsaved) sprintf (tmp,"IMAP server does not support AUTH=%.80s authenticator",mb->auth);
+ if (!uasaved || !atsaved) mm_log (tmp,ERROR);
+ }
return NIL; /* ran out of authenticators */
}
@@ -1219,6 +1232,7 @@ long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
IMAPARG *args[3];
IMAPARG ausr,apwd;
long ret = NIL;
+ char *app_pwd = NIL;
if (stream->secure) /* never do LOGIN if want security */
mm_log ("Can't do secure authentication with this server",ERROR);
/* never do LOGIN if server disabled it */
@@ -1232,8 +1246,13 @@ long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
apwd.text = (void *) pwd;
args[0] = &ausr; args[1] = &apwd; args[2] = NIL;
do {
- pwd[0] = 0; /* prompt user for password */
- mm_login (mb,usr,pwd,trial++);
+ if(app_pwd) fs_give((void **) &app_pwd);
+ pwd[0] = '\0';
+ mm_login (mb,usr, &app_pwd,trial++);
+ if(app_pwd){
+ strncpy(pwd, app_pwd, MAILTMPLEN);
+ pwd[MAILTMPLEN-1] = '\0';
+ }
if (pwd[0]) { /* send login command if have password */
LOCAL->sensitive = T; /* hide this command */
/* send "LOGIN usr pwd" */
@@ -1251,7 +1270,8 @@ long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
} while (!ret && pwd[0] && (trial < imap_maxlogintrials) &&
LOCAL->netstream && !LOCAL->byeseen && !LOCAL->referral);
}
- memset (pwd,0,MAILTMPLEN); /* erase password */
+ if(app_pwd) fs_give((void **) &app_pwd);
+ memset((void *) pwd, 0, MAILTMPLEN);
return ret;
}
diff --git a/imap/src/c-client/json.c b/imap/src/c-client/json.c
new file mode 100644
index 0000000..f6d0573
--- /dev/null
+++ b/imap/src/c-client/json.c
@@ -0,0 +1,497 @@
+/*
+ * Copyright 2018 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
+ *
+ */
+#ifdef STANDALONE
+#include "headers.h"
+#include "mem.h"
+#include "readfile.h"
+#include "json.h"
+#else
+#include <ctype.h>
+#include <stdio.h>
+#include <time.h>
+#include "c-client.h"
+#include "json.h"
+#endif /* STANDALONE */
+
+#define NIL 0
+
+#define json_ws(X) (((X) == ' ' || (X) == '\t' || (X) == '\n' || (X) == '\r') ? 1 : 0)
+
+#define json_skipws(X) for(; json_ws(*(X)); (X)++)
+
+#define json_skipchar(X) { \
+ (X)++; \
+ json_skipws((X)); \
+}
+
+#define json_last_char(X) (((X) == ',' || (X) == ']' || (X) == '}') ? 1 : 0)
+
+JObjType json_number_type(unsigned char *);
+unsigned char *json_strchr(unsigned char *, unsigned char);
+JSON_S *json_parse_work(unsigned char **);
+JSON_S *json_parse_pairs(unsigned char **);
+
+JSON_X *json_value_parse(unsigned char **);
+void json_value_free(JSON_X **);
+
+/* An array is a JSON object where the name is null */
+JSON_S *json_array_parse_work(unsigned char **);
+JSON_S *json_array_parse(unsigned char **);
+void json_array_free(JSON_S **);
+
+#ifdef STANDALONE
+int compare_cstring (unsigned char *,unsigned char *);
+int compare_uchar (unsigned char,unsigned char);
+int compare_ulong (unsigned long,unsigned long);
+
+int compare_ulong (unsigned long l1,unsigned long l2)
+{
+ if (l1 < l2) return -1;
+ if (l1 > l2) return 1;
+ return 0;
+}
+
+int compare_uchar (unsigned char c1,unsigned char c2)
+{
+ return compare_ulong (((c1 >= 'a') && (c1 <= 'z')) ? c1 - ('a' - 'A') : c1,
+ ((c2 >= 'a') && (c2 <= 'z')) ? c2 - ('a' - 'A') : c2);
+}
+
+int compare_cstring (unsigned char *s1,unsigned char *s2)
+{
+ int i;
+ if (!s1) return s2 ? -1 : 0; /* empty string cases */
+ else if (!s2) return 1;
+ for (; *s1 && *s2; s1++,s2++) if ((i = (compare_uchar (*s1,*s2))) != 0) return i;
+ if (*s1) return 1; /* first string is longer */
+ return *s2 ? -1 : 0; /* second string longer : strings identical */
+}
+#endif /* STANDALONE */
+
+/* we are parsing from the text of the json object, so it is
+ * never possible to have a null character, unless something
+ * is corrupt, in whose case we return JNumberError.
+ */
+JObjType json_number_type(unsigned char *s)
+{
+ unsigned char *w = s, *u;
+ int PossibleDecimal, PossibleExponential;
+
+ PossibleDecimal = PossibleExponential = 0;
+
+ if(*w == '+' || *w == '-')
+ w++;
+
+ if (*w == '0'){
+ u = w + 1;
+ if(json_ws(*u)) json_skipws(u);
+ if(json_last_char(*u)) return JLong;
+ else if(*(w+1) == '.'){
+ for(u = w+2; *u >= '0' && *u <= '9'; u++);
+ if(json_ws(*u)) json_skipws(u);
+ if(json_last_char(*u)) return JDecimal;
+ else return JNumberError;
+ }
+ else return JNumberError;
+ }
+
+ if(*w < '1' || *w > '9')
+ return JNumberError;
+
+ u = w + 1;
+ if(json_ws(*u)) json_skipws(u);
+ if(json_last_char(*u)) return JLong;
+ else if (*(w+1) == 'e' || *(w+1) == 'E'){
+ PossibleExponential++;
+ w += 2;
+ }
+ else if(*(w+1) == '.'){
+ PossibleDecimal++;
+ PossibleExponential++;
+ w += 2;
+ } else {
+ for(; *w >= '0' && *w <= '9'; w++);
+ if(*w == '.'){
+ PossibleDecimal++;
+ w++;
+ }
+ else {
+ if(json_ws(*w)) json_skipws(w);
+ if(json_last_char(*w)) return JLong;
+ else return JNumberError;
+ }
+ }
+
+ if(PossibleDecimal){
+ for(; *w >= '0' && *w <= '9'; w++);
+ u = w;
+ if(json_ws(*u)) json_skipws(u);
+ if(json_last_char(*u)) return JDecimal;
+ else if (*w == 'e' || *w == 'E'){
+ PossibleExponential++;
+ w++;
+ }
+ else return JNumberError;
+ }
+
+ if(PossibleExponential){
+ if(*w == '+' || *w == '-')
+ w++;
+ if(*w == '0'){
+ u = w + 1;
+ if(json_ws(*u)) json_skipws(u);
+ if(json_last_char(*u)) return JExponential;
+ else return JNumberError;
+ }
+ if(*w < '1' || *w > '9')
+ return JNumberError;
+ for(; *w >= '0' && *w <= '9'; w++);
+ if(json_ws(*w)) json_skipws(w);
+ if(json_last_char(*w)) return JExponential;
+ else return JNumberError;
+ }
+ return JNumberError; /* never reached */
+}
+
+
+JSON_X *
+json_body_value(JSON_S *j, unsigned char *s)
+{
+ JSON_S *js;
+
+ if(!j || !j->value|| j->value->jtype != JObject) return NIL;
+
+ for(js = (JSON_S *) j->value->value; js ; js = js->next)
+ if(js->name && !compare_cstring(js->name, s))
+ break;
+
+ return js ? js->value : NIL;
+}
+
+unsigned char *
+json_strchr(unsigned char *s, unsigned char c)
+{
+ unsigned char *t, d;
+ int escaped, extended;
+
+ if(c == '\0'){
+ t = s + strlen((char *) s);
+ return t;
+ }
+
+ escaped = extended = 0;
+ for(t = s; (d = *t) != '\0';){
+ if(escaped){
+ if (d == '\"' || d == '\\' || d == '/' || d == 'b'
+ || d == 'f' || d == 'n' || d == 'r' || d == 't'){
+ escaped = 0;
+ t++;
+ continue;
+ }
+ else{
+ if(d == 'u'){
+ escaped = 0;
+ extended = 1;
+ t++;
+ }
+ else
+ return NIL;
+ }
+ }
+ else {
+ if(d == '\\'){
+ escaped = 1;
+ t++;
+ continue;
+ }
+ else if(extended){
+ int i;
+ static char HEX[] = "abcdefABCDEF0123456789";
+
+ if(strlen((char *) t) < 4)
+ return NIL;
+
+ for(i = 0; i < 4; i++){
+ if(!strchr(HEX, *t))
+ return NIL;
+ else
+ t++;
+ }
+ extended = 0;
+ }
+ else if (d == c)
+ break;
+ else t++;
+ }
+ }
+ return (*t == '\0') ? NULL : t;
+}
+
+JSON_X *
+json_value_parse(unsigned char **s)
+{
+ JSON_X *jx;
+ unsigned char *u, *w;
+ unsigned long *l;
+
+ w = *s;
+ json_skipws(w);
+ jx = fs_get(sizeof(JSON_X));
+ memset((void **) jx, 0, sizeof(JSON_X));
+ jx->jtype = JEnd;
+ switch(*w){
+ case '\"': jx->jtype = JString; break;
+ case '{' : jx->jtype = JObject; break;
+ case '[' : jx->jtype = JArray; break;
+ case 'f' : if(strlen((char *) w) > 5
+ && !strncmp((char *) w, "false", 5)){
+ u = w+5;
+ json_skipws(u);
+ if(json_last_char(*u))
+ jx->jtype = JBoolean;
+ }
+ break;
+ case 'n' : if(strlen((char *) w) > 4
+ && !strncmp((char *) w, "null", 4)){
+ u = w+4;
+ json_skipws(u);
+ if(json_last_char(*u))
+ jx->jtype = JNull;
+ }
+ break;
+ case 't' : if(strlen((char *) w) > 4
+ && !strncmp((char *) w, "true", 4)){
+ u = w+4;
+ json_skipws(u);
+ if(json_last_char(*u))
+ jx->jtype = JBoolean;
+ }
+ break;
+ default: jx->jtype = json_number_type(w);
+ break;
+ }
+
+ switch(jx->jtype){
+ case JString: u = json_strchr(w+1, '\"');
+ if(u != NULL){
+ *u = '\0';
+ jx->value = (void *) cpystr((char *) w+1);
+ *u = '\"';
+ json_skipchar(u);
+ w = u;
+ }
+ break;
+
+ case JObject: jx->value = (void *) json_parse(&w);
+ break;
+
+ case JLong : l = fs_get(sizeof(unsigned long));
+ *l = strtoul((char *) w, (char **) &w, 10);
+ jx->value = (void *) l;
+ json_skipws(w);
+ break;
+
+ case JDecimal :
+ case JExponential: l = fs_get(sizeof(double));
+ *l = strtod((char *) w, (char **) &w);
+ jx->value = (void *) l;
+ json_skipws(w);
+ break;
+
+ case JBoolean: if(*w == 't'){
+ jx->value = (void *) cpystr("true");
+ w += 4;
+ }
+ else{
+ jx->value = (void *) cpystr("false");
+ w += 5;
+ }
+ json_skipws(w);
+ break;
+
+ case JNull: jx->value = (void *) cpystr("null");
+ w += 4;
+ json_skipws(w);
+ break;
+
+ case JArray: jx->value = json_array_parse(&w);
+ json_skipchar(w);
+ break;
+
+ default: break; /* let caller handle this */
+ }
+ *s = w;
+ return jx;
+}
+
+JSON_S *
+json_array_parse(unsigned char **s)
+{
+ JSON_S *j;
+ unsigned char *w = *s;
+
+ json_skipws(w);
+ if(*w == '['){
+ json_skipchar(w);
+ j = json_array_parse_work(&w);
+ }
+ *s = w;
+ return j;
+}
+
+JSON_S *
+json_array_parse_work(unsigned char **s)
+{
+ unsigned char *w = *s;
+ JSON_S *j;
+
+ json_skipws(w);
+ j = fs_get(sizeof(JSON_S));
+ memset((void *) j, 0, sizeof(JSON_S));
+ j->value = json_value_parse(&w);
+ json_skipws(w);
+ switch(*w){
+ case ',' : json_skipchar(w);
+ j->next = json_array_parse_work(&w);
+ break;
+
+ case ']' : break;
+ default : json_free(&j);
+ }
+ *s = w;
+ return j;
+}
+
+void
+json_array_free(JSON_S **j)
+{
+ json_free(j);
+}
+
+JSON_S *
+json_parse_pairs(unsigned char **s)
+{
+ JSON_S *j;
+ unsigned char *u, *w = *s;
+
+ json_skipws(w);
+ if(*w++ != '\"')
+ return NIL;
+
+ u = json_strchr(w, '\"');
+ if(!u)
+ return NIL;
+
+ *u = '\0';
+ j = fs_get(sizeof(JSON_S));
+ memset((void *) j, 0, sizeof(JSON_S));
+ j->name = (unsigned char *) cpystr((char *) w);
+
+ *u = '\"';
+ json_skipchar(u);
+ if(*u != ':')
+ return j;
+ json_skipchar(u);
+
+ j->value = json_value_parse(&u);
+ json_skipws(u);
+ if (*u == ','){
+ json_skipchar(u);
+ j->next = json_parse_pairs(&u);
+ }
+ *s = u;
+ return j;
+}
+
+void
+json_value_free(JSON_X **jxp)
+{
+ if(!jxp || !*jxp)
+ return;
+
+ switch((*jxp)->jtype){
+ case JString:
+ case JLong :
+ case JDecimal:
+ case JExponential:
+ case JBoolean:
+ case JNull: fs_give((void **) &(*jxp)->value);
+ break;
+
+ case JArray: json_array_free((JSON_S **) &(*jxp)->value);
+ break;
+
+ case JObject: json_free((JSON_S **) &(*jxp)->value);
+ break;
+
+ default: printf("Unhandled case in json_value_free");
+ exit(1);
+ }
+}
+
+void
+json_free(JSON_S **jp)
+{
+
+ if(jp == NULL || *jp == NULL) return;
+
+ if((*jp)->name) fs_give((void **) &(*jp)->name);
+ if((*jp)->value) json_value_free(&(*jp)->value);
+ if((*jp)->next) json_free(&(*jp)->next);
+ fs_give((void **) jp);
+}
+
+JSON_S *
+json_parse(unsigned char **s)
+{
+ JSON_S *j = NULL;
+ JSON_X *jx;
+ unsigned char *w = *s;
+
+ json_skipws(w);
+ if(*w == '{'){
+ json_skipchar(w);
+ jx = fs_get(sizeof(JSON_X));
+ memset((void *) jx, 0, sizeof(JSON_X));
+ jx->jtype = JObject;
+ jx->value = (void *) json_parse_pairs(&w);
+ j = fs_get(sizeof(JSON_S));
+ memset((void *) j, 0, sizeof(JSON_S));
+ j->value = jx;
+ json_skipws(w);
+ if(*w == '}'){
+ json_skipchar(w);
+ }
+ else
+ json_free(&j);
+ }
+ *s = w;
+ return j;
+}
+
+#ifdef STANDALONE
+int main (int argc, char *argv[])
+{
+ unsigned char *s, *t;
+ JSON_S *json;
+ JSON_X *jx;
+
+ t = NULL;
+ readfile(argv[1], (char **) &t, NULL);
+ s = t;
+ json = json_parse(&s);
+ if(!*s) printf("Success!\n");
+// jx = json_body_value(json, (unsigned char *) "subject");
+// if(jx) printf("subject = %s\n", (char *) jx->value);
+ if(t) fs_give((void **)&t);
+ exit(0);
+}
+#endif /* STANDALONE */
diff --git a/imap/src/c-client/json.h b/imap/src/c-client/json.h
new file mode 100644
index 0000000..8858517
--- /dev/null
+++ b/imap/src/c-client/json.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 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 JSON_H_INCLUDED
+#define JSON_H_INCLUDED
+
+typedef enum {JValue, JString, JLong, JDecimal, JExponential, JNumberError,
+ JObject, JArray, JBoolean, JNull, JEnd} JObjType;
+
+typedef struct json_x {
+ JObjType jtype;
+ void *value;
+} JSON_X;
+
+typedef struct json_s {
+ unsigned char *name;
+ JSON_X *value;
+ struct json_s *next;
+} JSON_S;
+
+JSON_S *json_parse(unsigned char **);
+JSON_X *json_body_value(JSON_S *, unsigned char *);
+void json_free(JSON_S **);
+
+#endif /* JSON_H_INCLUDED */
diff --git a/imap/src/c-client/mail.c b/imap/src/c-client/mail.c
index 8f0373e..49444eb 100644
--- a/imap/src/c-client/mail.c
+++ b/imap/src/c-client/mail.c
@@ -89,6 +89,8 @@ static threadresults_t mailthreadresults = NIL;
static copyuid_t mailcopyuid = NIL;
/* APPEND UID results */
static appenduid_t mailappenduid = NIL;
+
+static oauth2getaccesscode_t oauth2getaccesscode = NIL;
/* free elt extra stuff callback */
static freeeltsparep_t mailfreeeltsparep = NIL;
/* free envelope extra stuff callback */
@@ -665,6 +667,12 @@ void *mail_parameters (MAILSTREAM *stream,long function,void *value)
idapp = (IDLIST *) value;
case GET_IDPARAMS:
ret = (void *) idapp;
+ break;
+ case SET_OA2CLIENTGETACCESSCODE:
+ oauth2getaccesscode = (oauth2getaccesscode_t) value;
+ case GET_OA2CLIENTGETACCESSCODE:
+ ret = (void *) oauth2getaccesscode;
+ break;
default:
if ((r = smtp_parameters (function,value)) != NULL) ret = r;
if ((r = env_parameters (function,value)) != NULL) ret = r;
@@ -6211,7 +6219,8 @@ static NETDRIVER tcpdriver = {
tcp_host, /* return host name */
tcp_remotehost, /* return remote host name */
tcp_port, /* return port number */
- tcp_localhost /* return local host name */
+ tcp_localhost, /* return local host name */
+ tcp_getsize /* read a specific number of bytes */
};
@@ -6325,6 +6334,13 @@ char *net_getline (NETSTREAM *stream)
}
+char *net_getsize (NETSTREAM *stream, unsigned long size)
+{
+ return (*stream->dtb->getsize) (stream->stream, size);
+}
+
+
+
/* Network receive buffer
* Accepts: Network stream (must be void * for use as readfn_t)
* size in bytes
diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h
index 58d2979..b5cd65d 100644
--- a/imap/src/c-client/mail.h
+++ b/imap/src/c-client/mail.h
@@ -1,7 +1,7 @@
/*
- * Copyright 2016 Eduardo Chappa
+ * Copyright 2016-2018 Eduardo Chappa
*
- * Last Edited: February 6, 2015 Eduardo Chappa <chappa@gmx.com>
+ * Last Edited: July 21, 2018 Eduardo Chappa <chappa@washington.edu>
*
*/
/* ========================================================================
@@ -154,6 +154,8 @@
#define SET_IDPARAMS (long) 166
#define GET_IDSTREAM (long) 167
#define SET_IDSTREAM (long) 168
+#define GET_OA2CLIENTGETACCESSCODE (long) 169
+#define SET_OA2CLIENTGETACCESSCODE (long) 170
/* 2xx: environment */
#define GET_USERNAME (long) 201
@@ -396,7 +398,7 @@
#define DR_HALFOPEN (long) 0x10000
#define DR_DIRFMT (long) 0x20000/* driver is a directory-format */
#define DR_MODSEQ (long) 0x40000/* driver supports modseqs */
-
+#define DR_SHORTLIVED (long) 0x80000/* assume server disconnects after sending command */
/* Cache management function codes */
@@ -769,7 +771,8 @@ typedef struct mail_envelope {
#define TYPEIMAGE 5 /* static image */
#define TYPEVIDEO 6 /* video */
#define TYPEMODEL 7 /* model */
-#define TYPEOTHER 8 /* unknown */
+#define TYPEALL 8 /* type "*" */
+#define TYPEOTHER 9 /* unknown */
#define TYPEMAX 15 /* maximum type code */
@@ -1253,6 +1256,7 @@ NETDRIVER {
char *(*remotehost) (void *stream);
unsigned long (*port) (void *stream);
char *(*localhost) (void *stream);
+ char *(*getsize) (void *stream, unsigned long size);
};
@@ -1662,7 +1666,8 @@ void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes);
void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status);
void mm_log (char *string,long errflg);
void mm_dlog (char *string);
-void mm_login (NETMBX *mb,char *user,char *pwd,long trial);
+void mm_login (NETMBX *mb,char *user,char **pwd,long trial);
+void mm_login_method (NETMBX *mb,char *user, void *info,long trial, char *method);
void mm_critical (MAILSTREAM *stream);
void mm_nocritical (MAILSTREAM *stream);
long mm_diskerror (MAILSTREAM *stream,long errcode,long serious);
@@ -1882,6 +1887,7 @@ char *net_host (NETSTREAM *stream);
char *net_remotehost (NETSTREAM *stream);
unsigned long net_port (NETSTREAM *stream);
char *net_localhost (NETSTREAM *stream);
+char *net_getsize(NETSTREAM *stream, unsigned long size);
long sm_subscribe (char *mailbox);
long sm_unsubscribe (char *mailbox);
@@ -1902,3 +1908,44 @@ long INWAIT (long seconds);
int PSOUT (char *s);
int PSOUTR (SIZEDTEXT *s);
int PFLUSH (void);
+
+/* XOAUTH Client-Side Support */
+
+#define OA2NAME "XOAUTH2"
+
+#define OAUTH2_MAX_EQUIV (2)
+#define OAUTH2_TOT_EQUIV (OAUTH2_MAX_EQUIV + 2)
+#define OAUTH2_PARAM_NUMBER (7)
+
+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;
+
+typedef enum {OA2_GetAccessCode = 0,
+ OA2_GetAccessTokenFromAccessCode,
+ OA2_GetAccessTokenFromRefreshToken,
+ OA2_GetEnd} OA2_function;
+
+typedef struct OA2_param_s {
+ char *name;
+ char *value;
+} OAUTH2_PARAM_S;
+
+typedef struct OA2_serverparam_s {
+ char *name; /* method name: GET or POST */
+ char *urlserver;
+ OA2_type params[OAUTH2_PARAM_NUMBER];
+} OAUTH2_SERVER_METHOD_S;
+
+typedef struct oauth2_s {
+ 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];
+ char *access_token;
+ unsigned long expiration;
+} OAUTH2_S;
+
+typedef char *(*oauth2getaccesscode_t) (char *url, OAUTH2_S *, int *);
diff --git a/imap/src/c-client/nntp.c b/imap/src/c-client/nntp.c
index b1d0802..53b5145 100644
--- a/imap/src/c-client/nntp.c
+++ b/imap/src/c-client/nntp.c
@@ -2039,7 +2039,7 @@ long nntp_send_auth (SENDSTREAM *stream,long flags)
long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags)
{
unsigned long trial,auths;
- char tmp[MAILTMPLEN],usr[MAILTMPLEN];
+ char tmp[MAILTMPLEN],usr[MAILTMPLEN], *pwd2 = NIL;
AUTHENTICATOR *at;
char *lsterr = NIL;
long ret = NIL;
@@ -2096,10 +2096,10 @@ long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags)
else for (trial = 0, pwd[0] = 'x';
!ret && pwd[0] && (trial < nntp_maxlogintrials) &&
stream->netstream; ) {
- pwd[0] = NIL; /* get user name and password */
- mm_login (mb,usr,pwd,trial++);
+ mm_login (mb,usr, &pwd2,trial++);
+ pwd[0] = pwd2 ? pwd2[0] : '\0';
/* do the authentication */
- if (pwd[0]) switch ((int) nntp_send_work (stream,"AUTHINFO USER",usr)) {
+ if (pwd2 && *pwd2) switch ((int) nntp_send_work (stream,"AUTHINFO USER",usr)) {
case NNTPBADCMD: /* give up if unrecognized command */
mm_log (NNTP.ext.authuser ? stream->reply :
"Can't do AUTHINFO USER to this server",ERROR);
@@ -2110,7 +2110,7 @@ long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags)
break;
case NNTPWANTPASS: /* wants password */
stream->sensitive = T; /* hide this command */
- if (nntp_send_work (stream,"AUTHINFO PASS",pwd) == NNTPAUTHED)
+ if (nntp_send_work (stream,"AUTHINFO PASS",pwd2) == NNTPAUTHED)
ret = LONGT; /* password OK */
stream->sensitive = NIL; /* unhide */
if (ret) break; /* OK if successful */
diff --git a/imap/src/c-client/pop3.c b/imap/src/c-client/pop3.c
index dfc4f92..7ca07da 100644
--- a/imap/src/c-client/pop3.c
+++ b/imap/src/c-client/pop3.c
@@ -554,7 +554,7 @@ long pop3_capa (MAILSTREAM *stream,long flags)
long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
{
unsigned long i,trial,auths = 0, authsaved;
- char *t;
+ char *t, *app_pwd = NIL;
AUTHENTICATOR *at, *atsaved;
long ret = NIL;
long flags = (stream->secure ? AU_SECURE : NIL) |
@@ -675,7 +675,12 @@ long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
trial = 0; /* initial trial count */
do {
pwd[0] = 0; /* prompt user for password */
- mm_login (mb,usr,pwd,trial++);
+ if(app_pwd) fs_give((void **) &app_pwd);
+ mm_login (mb,usr, &app_pwd,trial++);
+ if(app_pwd){
+ strncpy(pwd, app_pwd, MAILTMPLEN);
+ pwd[MAILTMPLEN-1] = '\0';
+ }
if (pwd[0]) { /* send login sequence if have password */
if (pop3_send (stream,"USER",usr)) {
LOCAL->sensitive = T; /* hide this command */
@@ -693,6 +698,7 @@ long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
} while (!ret && pwd[0] && (trial < pop3_maxlogintrials) &&
LOCAL->netstream);
}
+ if(app_pwd) fs_give((void **) &app_pwd);
memset (pwd,0,MAILTMPLEN); /* erase password */
/* get capabilities if logged in */
if (ret && capaok) pop3_capa (stream,flags);
diff --git a/imap/src/c-client/rfc822.c b/imap/src/c-client/rfc822.c
index b4c72a3..a0195b9 100644
--- a/imap/src/c-client/rfc822.c
+++ b/imap/src/c-client/rfc822.c
@@ -65,7 +65,7 @@ static const char *errhst = ERRHOST;
char *body_types[TYPEMAX+1] = {
"TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
- "MODEL", "X-UNKNOWN"
+ "MODEL", "*", "X-UNKNOWN"
};
diff --git a/imap/src/c-client/sslio.h b/imap/src/c-client/sslio.h
index 8063f3f..ab163d6 100644
--- a/imap/src/c-client/sslio.h
+++ b/imap/src/c-client/sslio.h
@@ -40,6 +40,7 @@ struct ssl_driver { /* must parallel NETDRIVER in mail.h */
char *(*remotehost) (SSLSTREAM *stream);
unsigned long (*port) (SSLSTREAM *stream);
char *(*localhost) (SSLSTREAM *stream);
+ char *(*getsize) (SSLSTREAM *stream, unsigned long size);
};
@@ -58,6 +59,7 @@ typedef struct ssl_stdiostream {
SSLSTREAM *ssl_open (char *host,char *service,unsigned long port);
SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf);
char *ssl_getline (SSLSTREAM *stream);
+char *ssl_getsize (SSLSTREAM *stream, unsigned long size);
long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer);
long ssl_getdata (SSLSTREAM *stream);
long ssl_soutr (SSLSTREAM *stream,char *string);
diff --git a/imap/src/c-client/tcp.h b/imap/src/c-client/tcp.h
index ecf39be..48883e2 100644
--- a/imap/src/c-client/tcp.h
+++ b/imap/src/c-client/tcp.h
@@ -40,6 +40,7 @@ void *tcp_parameters (long function,void *value);
TCPSTREAM *tcp_open (char *host,char *service,unsigned long port);
TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf);
char *tcp_getline (TCPSTREAM *stream);
+char *tcp_getsize (TCPSTREAM *stream, unsigned long size);
long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer);
long tcp_getdata (TCPSTREAM *stream);
long tcp_soutr (TCPSTREAM *stream,char *string);
diff --git a/imap/src/dmail/dmail.c b/imap/src/dmail/dmail.c
index cf9bb73..31c7135 100644
--- a/imap/src/dmail/dmail.c
+++ b/imap/src/dmail/dmail.c
@@ -612,11 +612,15 @@ void mm_dlog (char *string)
* trial count
*/
-void mm_login (NETMBX *mb,char *username,char *password,long trial)
+void mm_login (NETMBX *mb,char *username,char **password,long trial)
{
fatal ("mm_login() call");
}
+void mm_login_method(NETMBX *mb, char *user, void *login, long trial, char *method)
+{
+ fatal ("mm_login_method() call");
+}
/* About to enter critical code
* Accepts: stream
diff --git a/imap/src/imapd/imapd.c b/imap/src/imapd/imapd.c
index 1c1d401..f53dca8 100644
--- a/imap/src/imapd/imapd.c
+++ b/imap/src/imapd/imapd.c
@@ -4642,11 +4642,16 @@ void mm_dlog (char *string)
* trial count
*/
-void mm_login (NETMBX *mb,char *username,char *password,long trial)
+void mm_login (NETMBX *mb,char *username,char **password,long trial)
{
/* set user name */
strncpy (username,*mb->user ? mb->user : (char *) user,NETMAXUSER);
- strncpy (password,pass,256); /* and password */
+ *password = cpystr(pass); /* and password */
+}
+
+void mm_login_method (NETMBX *mb,char *username,void *password,long trial, char *method)
+{
+ password = NULL;
}
diff --git a/imap/src/ipopd/ipop2d.c b/imap/src/ipopd/ipop2d.c
index 58d4d2d..48844b3 100644
--- a/imap/src/ipopd/ipop2d.c
+++ b/imap/src/ipopd/ipop2d.c
@@ -646,12 +646,17 @@ void mm_dlog (char *string)
* trial count
*/
-void mm_login (NETMBX *mb,char *username,char *password,long trial)
+void mm_login (NETMBX *mb,char *username,char **password,long trial)
{
/* set user name */
strncpy (username,*mb->user ? mb->user : user,NETMAXUSER-1);
- strncpy (password,pass,255); /* and password */
- username[NETMAXUSER] = password[255] = '\0';
+ *password = cpystr(pass); /* and password */
+ username[NETMAXUSER] = (*password)[255] = '\0';
+}
+
+void mm_login_method (NETMBX *mb,char *username,void *password,long trial, char *method)
+{
+ password = NULL;
}
/* About to enter critical code
diff --git a/imap/src/ipopd/ipop3d.c b/imap/src/ipopd/ipop3d.c
index 9cb8bff..2be17af 100644
--- a/imap/src/ipopd/ipop3d.c
+++ b/imap/src/ipopd/ipop3d.c
@@ -1013,18 +1013,22 @@ void mm_dlog (char *string)
* trial count
*/
-void mm_login (NETMBX *mb,char *username,char *password,long trial)
+void mm_login (NETMBX *mb,char *username,char **password,long trial)
{
/* set user name */
strncpy (username,*mb->user ? mb->user : user,NETMAXUSER-1);
if (pass) {
- strncpy (password,pass,255);/* and password */
+ *password = cpystr(pass);/* and password */
+ (*password)[255] = '\0';
fs_give ((void **) &pass);
}
- else memset (password,0,256); /* no password to send, abort login */
- username[NETMAXUSER] = password[255] = '\0';
+ username[NETMAXUSER] = '\0';
}
+void mm_login_method (NETMBX *mb,char *username,void *password,long trial, char *method)
+{
+ password = NULL;
+}
/* About to enter critical code
* Accepts: stream
*/
diff --git a/imap/src/mailutil/mailutil.c b/imap/src/mailutil/mailutil.c
index d8faefa..979416b 100644
--- a/imap/src/mailutil/mailutil.c
+++ b/imap/src/mailutil/mailutil.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 Eduardo Chappa
+ * Copyright 2016-2018 Eduardo Chappa
*/
/* ========================================================================
@@ -1035,7 +1035,7 @@ void mm_dlog (char *string)
* trial count
*/
-void mm_login (NETMBX *mb,char *username,char *password,long trial)
+void mm_login (NETMBX *mb,char *username,char **password,long trial)
{
char *s,tmp[MAILTMPLEN];
sprintf (s = tmp,"{%s/%s",mb->host,mb->service);
@@ -1050,7 +1050,37 @@ void mm_login (NETMBX *mb,char *username,char *password,long trial)
if ((s = strchr (username,'\n')) != NULL) *s = '\0';
s = "password: ";
}
- if(strlen (s = getpass (s)) < MAILTMPLEN) strcpy (password,s);
+ if(strlen (s = getpass (s)) < MAILTMPLEN) *password = cpystr(s);
+}
+
+void mm_login_method (NETMBX *mb,char *username, void *login,long trial, char *method)
+{
+ if(method == NULL) return;
+ if(strcmp(method, "XOAUTH2") == 0){
+ OAUTH2_S *muinfo = NULL; /* mail util info */
+ char *s,tmp[MAILTMPLEN];
+ sprintf (s = tmp,"{%s/%s",mb->host,mb->service);
+ if (*mb->user) sprintf (tmp+strlen (tmp),"/user=%s",
+ strcpy (username,mb->user));
+ if (*mb->user) strcat (s = tmp,"} access token: ");
+ else {
+ printf ("%s} username: ",tmp);
+ fgets (username,NETMAXUSER-1,stdin);
+ username[NETMAXUSER-1] = '\0';
+ if ((s = strchr (username,'\n')) != NULL) *s = '\0';
+ printf ("%s} access token: ", tmp);
+ }
+ fgets (s,MAILTMPLEN/2-1,stdin);
+ tmp[MAILTMPLEN/2-1] = '\0';
+ if ((s = strchr (tmp,'\n')) != NULL) *s = '\0';
+ if(tmp[0]){
+ muinfo = fs_get(sizeof(OAUTH2_S));
+ memset((void *) muinfo, 0, sizeof(OAUTH2_S));
+ muinfo->access_token = cpystr(tmp);
+ /* STILL MISSING: get expires in info */
+ }
+ login = (void *) muinfo;
+ }
}
diff --git a/imap/src/mtest/mtest.c b/imap/src/mtest/mtest.c
index 018d1bc..e433ba8 100644
--- a/imap/src/mtest/mtest.c
+++ b/imap/src/mtest/mtest.c
@@ -705,7 +705,7 @@ void mm_dlog (char *string)
}
-void mm_login (NETMBX *mb,char *user,char *pwd,long trial)
+void mm_login (NETMBX *mb,char *user,char **pwd,long trial)
{
char *s,tmp[MAILTMPLEN];
if (curhst) fs_give ((void **) &curhst);
@@ -724,9 +724,16 @@ void mm_login (NETMBX *mb,char *user,char *pwd,long trial)
}
if (curusr) fs_give ((void **) &curusr);
curusr = cpystr (user);
- strcpy (pwd,getpass (s));
+ if(pwd) *pwd = cpystr(getpass (s));
}
+/* IMPLEMENT THIS LATER */
+void mm_login_method (NETMBX *mb,char *user,void *pwd,long trial, char *method)
+{
+ mm_login(mb, user, (char **) pwd, trial);
+}
+
+
void mm_critical (MAILSTREAM *stream)
{
diff --git a/imap/src/osdep/unix/Makefile b/imap/src/osdep/unix/Makefile
index 8d740bb..a94040c 100644
--- a/imap/src/osdep/unix/Makefile
+++ b/imap/src/osdep/unix/Makefile
@@ -140,7 +140,7 @@ RANLIB=ranlib
# Standard distribution build parameters
-DEFAULTAUTHENTICATORS=ext md5 pla log
+DEFAULTAUTHENTICATORS=ext md5 pla oa2 log
#
# mh needs to be after any other directory format drivers (such as mx or mix)
# since otherwise mh will seize any directory that is under the mh path.
@@ -155,7 +155,7 @@ CHUNKSIZE=65536
ARCHIVE=c-client.a
BINARIES=osdep.o mail.o misc.o newsrc.o smanager.o utf8.o utf8aux.o siglocal.o \
dummy.o pseudo.o netmsg.o flstring.o fdstring.o \
- rfc822.o nntp.o smtp.o imap4r1.o pop3.o \
+ rfc822.o nntp.o smtp.o imap4r1.o http.o json.o pop3.o \
unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o
CFLAGS=-g
@@ -889,7 +889,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
+mail.o: mail.h misc.h osdep.h rfc822.h linkage.h json.h
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
@@ -911,7 +911,8 @@ tenex.o: mail.h misc.h osdep.h dummy.h
unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h
utf8.o: mail.h misc.h osdep.h utf8.h tmap.c widths.c
utf8aux.o: mail.h misc.h osdep.h utf8.h
-
+json.o: mail.h misc.h osdep.h utf8.h json.h
+http.o: mail.h misc.h osdep.h utf8.h http.h json.h
# OS-dependent
@@ -922,7 +923,8 @@ osdep.o:mail.h misc.h env.h fs.h ftl.h nl.h tcp.h \
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_pla.c \
+ auth_ext.c auth_gss.c auth_log.c auth_md5.c auth_ntl.c \
+ auth_oa2.c auth_pla.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/imap/src/osdep/unix/ssl_unix.c b/imap/src/osdep/unix/ssl_unix.c
index 53c5d05..21bf55e 100644
--- a/imap/src/osdep/unix/ssl_unix.c
+++ b/imap/src/osdep/unix/ssl_unix.c
@@ -107,7 +107,8 @@ static struct ssl_driver ssldriver = {
ssl_host, /* return host name */
ssl_remotehost, /* return remote host name */
ssl_port, /* return port number */
- ssl_localhost /* return local host name */
+ ssl_localhost, /* return local host name */
+ ssl_getsize /* return needed number of bytes */
};
/* non-NIL if doing SSL primary I/O */
static SSLSTDIOSTREAM *sslstdio = NIL;
@@ -546,7 +547,28 @@ char *ssl_getline (SSLSTREAM *stream)
}
return ret;
}
-
+
+char *ssl_getsize (SSLSTREAM *stream, unsigned long size)
+{
+ char *ret = NIL;
+ unsigned long got = 0L, need = size, n;
+ int done = 0;
+
+ while(!done){
+ if(!ssl_getdata (stream)) return ret; /* return what we have */
+ n = stream->ictr < need ? stream->ictr : need;
+ fs_resize((void **) &ret, got + n + 1);
+ memcpy(ret + got, stream->iptr, n);
+ ret[got+n] = '\0';
+ got += n;
+ need -= n;
+ stream->iptr += n;
+ stream->ictr -= n;
+ if(need == 0L) done++;
+ }
+
+ return ret;
+}
/* SSL receive line or partial line
* Accepts: SSL stream
* pointer to return size
diff --git a/imap/src/osdep/unix/tcp_unix.c b/imap/src/osdep/unix/tcp_unix.c
index b076ce4..73791f6 100644
--- a/imap/src/osdep/unix/tcp_unix.c
+++ b/imap/src/osdep/unix/tcp_unix.c
@@ -1063,6 +1063,28 @@ long tcp_isclienthost (char *host)
return ret;
}
+char *tcp_getsize (TCPSTREAM *stream, unsigned long size)
+{
+ char *ret = NIL;
+ unsigned long got = 0L, need = size, n;
+ int done = 0;
+
+ while(!done){
+ if(!tcp_getdata (stream)) return ret; /* return what we have */
+ n = stream->ictr < need ? stream->ictr : need;
+ fs_resize((void **) &ret, got + n + 1);
+ memcpy(ret + got, stream->iptr, n);
+ ret[got+n] = '\0';
+ got += n;
+ need -= n;
+ stream->iptr += n;
+ stream->ictr -= n;
+ if(need == 0L) done++;
+ }
+
+ return ret;
+}
+
/* Following statement must be at end of this module */
#undef fork /* undo any use of vfork() */
diff --git a/imap/src/tmail/tmail.c b/imap/src/tmail/tmail.c
index 273db40..1e4a4e4 100644
--- a/imap/src/tmail/tmail.c
+++ b/imap/src/tmail/tmail.c
@@ -752,11 +752,15 @@ void mm_dlog (char *string)
* trial count
*/
-void mm_login (NETMBX *mb,char *username,char *password,long trial)
+void mm_login (NETMBX *mb,char *username,char **password,long trial)
{
fatal ("mm_login() call");
}
+void mm_login_method(NETMBX *mb, char *user, void *login, long trial, char *method)
+{
+ fatal ("mm_login_method() call");
+}
/* About to enter critical code
* Accepts: stream
diff --git a/pith/imap.c b/pith/imap.c
index 5d9a789..f9e5162 100644
--- a/pith/imap.c
+++ b/pith/imap.c
@@ -855,11 +855,17 @@ imap_seq_exec_append(MAILSTREAM *stream, long int msgno, void *args)
Result: username and password passed back to imap
----*/
void
-mm_login(NETMBX *mb, char *user, char *pwd, long int trial)
+mm_login(NETMBX *mb, char *user, char **pwd, long int trial)
{
mm_login_work(mb, user, pwd, trial, NULL, NULL);
}
+void
+mm_login_method(NETMBX *mb, char *user, void *login, long int trial, char *method)
+{
+ mm_login_method_work(mb, user, login, trial, method, NULL, NULL);
+}
+
/*----------------------------------------------------------------------
Exported method to retrieve logged in user name associated with stream
@@ -888,11 +894,35 @@ cached_user_name(char *host)
int
imap_same_host(STRLIST_S *hl1, STRLIST_S *hl2)
{
+ return imap_same_host_auth(hl1, hl2, NULL);
+}
+
+/* An explanation about this function. The way this is used in the
+ * new code for XOAUTH2, makes it so that when we are checking if
+ * the hosts and orighosts are the same, we are given the mb->host
+ * and mb->orighost pointers, and we cannot transform them in any
+ * way to be sure that increasing their size will not overflow the
+ * fixed width buffer that contain them, so we cannot change that.
+ * For purposes of this function, these values come in the hl2 variable.
+ * However, for purposes of this function, the values in hl1 are the ones
+ * that come straight from the cache, and here no transformation is made,
+ * that is, we save them as we read them, so when we compare the values
+ * read from the cache, with those that we want to save, we need to make
+ * sure we "shift" the hl1 variable, but not the hl2 variable.
+ */
+int
+imap_same_host_auth(STRLIST_S *hl1, STRLIST_S *hl2, char *authtype)
+{
STRLIST_S *lp;
+ size_t len, offset;
+ len = authtype ? strlen(authtype) : 0;
+ offset = authtype ? 1 : 0;
for( ; hl1; hl1 = hl1->next)
for(lp = hl2; lp; lp = lp->next)
- if(!strucmp(hl1->name, lp->name))
+ if((len == 0 || (!struncmp(hl1->name, authtype, len)
+ && hl1->name[len] == PWDAUTHSEP))
+ && !strucmp(hl1->name + len + offset, lp->name))
return(TRUE);
return(FALSE);
@@ -949,9 +979,16 @@ imap_get_user(MMLOGIN_S *m_list, STRLIST_S *hostlist)
* attempt to login with the password from the cache.
*/
int
-imap_get_passwd(MMLOGIN_S *m_list, char *passwd, char *user, STRLIST_S *hostlist, int altflag)
+imap_get_passwd(MMLOGIN_S *m_list, char **passwd, char *user, STRLIST_S *hostlist, int altflag)
+{
+ return imap_get_passwd_auth(m_list, passwd, user, hostlist, altflag, NULL);
+}
+
+int
+imap_get_passwd_auth(MMLOGIN_S *m_list, char **passwd, char *user, STRLIST_S *hostlist, int altflag, char *authtype)
{
MMLOGIN_S *l;
+ int len, offset;
dprint((9,
"imap_get_passwd: checking user=%s alt=%d host=%s%s%s\n",
@@ -961,18 +998,22 @@ imap_get_passwd(MMLOGIN_S *m_list, char *passwd, char *user, STRLIST_S *hostlist
(hostlist->next && hostlist->next->name) ? ", " : "",
(hostlist->next && hostlist->next->name) ? hostlist->next->name
: ""));
+ len = authtype ? strlen(authtype) : 0;
+ offset = authtype ? 1 : 0;
for(l = m_list; l; l = l->next)
- if(imap_same_host(l->hosts, hostlist)
+ if(imap_same_host_auth(l->hosts, hostlist, authtype)
&& *user
- && !strcmp(user, l->user)
+ && (len == 0 || (!struncmp(l->user, authtype, len)
+ && l->user[len] == PWDAUTHSEP))
+ && !strcmp(user, l->user + len + offset)
&& l->altflag == altflag){
if(passwd){
- strncpy(passwd, l->passwd, NETMAXPASSWD);
- passwd[NETMAXPASSWD-1] = '\0';
+ fs_resize((void **) passwd, strlen(l->passwd + len + offset) + 1);
+ strcpy(*passwd, l->passwd + len + offset);
}
dprint((9, "imap_get_passwd: match\n"));
dprint((10, "imap_get_passwd: trying passwd=\"%s\"\n",
- passwd ? passwd : "?"));
+ passwd && *passwd ? *passwd : "?"));
return(TRUE);
}
@@ -986,15 +1027,28 @@ void
imap_set_passwd(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist,
int altflag, int ok_novalidate, int warned)
{
+ imap_set_passwd_auth(l, passwd, user, hostlist, altflag, ok_novalidate,
+ warned, NULL);
+}
+
+void
+imap_set_passwd_auth(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist,
+ int altflag, int ok_novalidate, int warned, char *authtype)
+{
STRLIST_S **listp;
size_t len;
+ size_t authlen, offset;
+ authlen = authtype ? strlen(authtype) : 0;
+ offset = authtype ? 1 : 0;
dprint((9, "imap_set_passwd\n"));
for(; *l; l = &(*l)->next)
- if(imap_same_host((*l)->hosts, hostlist)
- && !strcmp(user, (*l)->user)
+ if((authlen == 0 || (!struncmp((*l)->user, authtype, authlen)
+ && (*l)->user[authlen] == PWDAUTHSEP))
+ && !strcmp(user, (*l)->user + authlen + offset)
+ && imap_same_host_auth((*l)->hosts, hostlist, authtype)
&& altflag == (*l)->altflag){
- if(strcmp(passwd, (*l)->passwd) ||
+ if(strcmp(passwd, (*l)->passwd + authlen + offset) ||
(*l)->ok_novalidate != ok_novalidate ||
(*l)->warned != warned)
break;
@@ -1008,17 +1062,24 @@ imap_set_passwd(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist,
}
len = strlen(passwd);
- if(!(*l)->passwd || strlen((*l)->passwd) < len)
- (*l)->passwd = ps_get(len+1);
+ if(!(*l)->passwd || strlen((*l)->passwd) < len + authlen + offset)
+ (*l)->passwd = ps_get(len + authlen + offset + 1);
- strncpy((*l)->passwd, passwd, len+1);
+ if(authtype)
+ sprintf((*l)->passwd, "%s%c%s", authtype, PWDAUTHSEP, passwd);
+ else
+ strncpy((*l)->passwd, passwd, len+1);
- (*l)->altflag = altflag;
- (*l)->ok_novalidate = ok_novalidate;
- (*l)->warned = warned;
+ (*l)->altflag = altflag; (*l)->ok_novalidate = ok_novalidate; (*l)->warned = warned;
- if(!(*l)->user)
- (*l)->user = cpystr(user);
+ if(!(*l)->user){
+ if(authlen > 0){
+ (*l)->user = fs_get(strlen(user) + authlen + offset + 1);
+ sprintf((*l)->user, "%s%c%s", authtype, PWDAUTHSEP, user);
+ }
+ else
+ (*l)->user = cpystr(user);
+ }
dprint((9, "imap_set_passwd: user=%s altflag=%d\n",
(*l)->user ? (*l)->user : "?",
@@ -1031,7 +1092,7 @@ imap_set_passwd(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist,
;
if(!*listp){
- *listp = new_strlist(hostlist->name);
+ *listp = new_strlist_auth(hostlist->name, authtype, PWDAUTHSEP);
dprint((9, "imap_set_passwd: host=%s\n",
(*listp)->name ? (*listp)->name : "?"));
}
diff --git a/pith/imap.h b/pith/imap.h
index d58f4e3..9ed7062 100644
--- a/pith/imap.h
+++ b/pith/imap.h
@@ -21,7 +21,9 @@
#include "../pith/string.h"
-#define NETMAXPASSWD 100
+#define NETMAXPASSWD 512 /* increased from 100 due to token lengths.
+ * must be less than MAILTMPLEN
+ */
/*
@@ -69,6 +71,8 @@ typedef struct mm_list_s {
#define URL_IMAP_IMBXLSTLSUB 0x0010
#define URL_IMAP_ISERVERONLY 0x0020
+/* Marker for Separator of Authentication Method */
+#define PWDAUTHSEP '\001'
/*
* Exported globals setup by searching functions to tell mm_searched
@@ -119,10 +123,13 @@ char *imap_referral(MAILSTREAM *, char *, long);
long imap_proxycopy(MAILSTREAM *, char *, char *, long);
char *cached_user_name(char *);
int imap_same_host(STRLIST_S *, STRLIST_S *);
+int imap_same_host_auth(STRLIST_S *, STRLIST_S *, char *);
int imap_get_ssl(MMLOGIN_S *, STRLIST_S *, int *, int *);
char *imap_get_user(MMLOGIN_S *, STRLIST_S *);
-int imap_get_passwd(MMLOGIN_S *, char *, char *, STRLIST_S *, int);
+int imap_get_passwd(MMLOGIN_S *, char **, char *, STRLIST_S *, int);
+int imap_get_passwd_auth (MMLOGIN_S *, char **, char *, STRLIST_S *, int, char *);
void imap_set_passwd(MMLOGIN_S **, char *, char *, STRLIST_S *, int, int, int);
+void imap_set_passwd_auth(MMLOGIN_S **, char *, char *, STRLIST_S *, int, int, int, char *);
void imap_flush_passwd_cache(int);
@@ -130,7 +137,8 @@ void imap_flush_passwd_cache(int);
/* called by build_folder_list(), ok if it does nothing */
void set_read_predicted(int);
-void mm_login_work (NETMBX *mb,char *user,char *pwd,long trial,char *usethisprompt, char *altuserforcache);
+void mm_login_work (NETMBX *mb,char *user,char **pwd,long trial,char *usethisprompt, char *altuserforcache);
+void mm_login_method_work (NETMBX *mb,char *user,void *login,long trial, char *method, char *usethisprompt, char *altuserforcache);
/* this is necessary to figure out the name of the password file of the application. */
#ifdef PASSFILE
diff --git a/pith/ldap.c b/pith/ldap.c
index acf592d..f56592d 100644
--- a/pith/ldap.c
+++ b/pith/ldap.c
@@ -556,7 +556,7 @@ ldap_lookup(LDAP_SERV_S *info, char *string, CUSTOM_FILT_S *cust,
}
else if(!ps_global->intr_pending){
int proto = 3, tlsmustbail = 0;
- char pwd[NETMAXPASSWD], user[NETMAXUSER];
+ char *pwd = NULL, user[NETMAXUSER];
#ifdef _WINDOWS
char *passwd = NULL;
#else
@@ -634,7 +634,7 @@ try_password_again:
if(!tlsmustbail){
snprintf(pmt, sizeof(pmt), " %s", (info->nick && *info->nick) ? info->nick : serv);
- mm_login_work(&mb, user, pwd, pwdtrial, pmt, info->binddn);
+ mm_login_work(&mb, user, &pwd, pwdtrial, pmt, info->binddn);
if(pwd && pwd[0])
#ifdef _WINDOWS
passwd = pwd;
@@ -1172,6 +1172,8 @@ try_password_again:
}
}
}
+ if(pwd)
+ fs_give((void **) &pwd);
}
if(we_cancel)
diff --git a/pith/pine.hlp b/pith/pine.hlp
index 16efc9c..af37f7a 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 335 2019-04-28 16:01:07
+Alpine Commit 336 2019-05-04 12:40:47
============= h_news =================
<HTML>
<HEAD>
@@ -180,6 +180,8 @@ addresses bugs found in previous releases and has a few additions as well.
Additions include:
<UL>
+<LI> Support for <A HREF="h_xoauth2">XOAUTH2</A> authentication method in Gmail.
+
<LI> PC-Alpine builds with LibreSSL and supports S/MIME.
<LI> NTLM authentication support with the ntlm library, in Unix systems.
@@ -1459,6 +1461,116 @@ modifier to the server definition to create a secure encrypted connection.
&lt;End of help&gt;
</BODY>
</HTML>
+====== h_xoauth2 ======
+<HTML>
+<HEAD>
+<TITLE>XOAUTH2 Authenticator Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>XOAUTH2 Authenticator Explained</H1>
+
+The XOAUTH2 authenticator method is a way in which you can sign in to your
+account to read and send email. This method is different from the traditional
+username/password that users are accostumed to, and it needs to be set up
+by the user. This text will help you understand this authentication method.
+
+<P>
+The most important difference between this method and other autentication
+methods is what happens if someone steals your credentials. This method is
+attached to three components: Your username, your password and your email
+program.
+
+<P>
+At the beginning of this process, the developer of the email program
+registers the email program with the email service provider (Gmail,
+Outlook, etc.) In return, the email service provider creates an id and
+secret for the email program, which the email program will use in the
+future. Since Alpine is an open source program, these values are part
+of the source code, and are known to everyone, and cannot be obfuscated.
+
+<P>
+After a program has been registered with an email service provider, the
+user must authorize the email program access to their email account in
+that service. Alpine helps you do that, but it cannot do all the process
+for you, and you will need to use an external web browser to authorize
+Alpine to read and send email.
+
+<P>
+This part of the process is simple. Alpine creates a URL based on the id
+and secret created by the email service, which the user opens. Once the
+URL has been opened, the user signs in to the server, and once signed into
+the account, the user is asked to authorize Alpine access to the email
+account. If the user agrees, an &quot;<I>Access Code</I>&quot; will be
+generated.
+
+<P>
+An Access Code is a very long string, and so the recommendation is to copy
+and paste this code back into Alpine, at the time Alpine is helping you
+set up this authenticator. This part of the process is done only once per
+email program (If you run Alpine in several computers, you would do this
+once per computer. Each computer gets its own Access Code.)
+
+<P>
+Once Alpine has an Access Code, it goes back internally to the email
+service and asks it to generate two strings, called the &quot;<I>Refresh
+Token</I>&quot; and the &quot;<I>Access Token</I>&quot;. This part is
+invisible to the user, and they will never see it, but in order for you to
+understand what to do in case you lose them, you need to understand their
+use.
+
+<P>
+The <I>Access Token</I> is the equivalent of a temporary password. It
+allows the user to access (and reaccess as many times as they would like)
+their email account for a limited amount of time, typically 1 hour after
+it was issued. After that time expires, users need a new Access Token.
+
+<P>
+The <I>Refresh Token</I> is the equivalent of a password generator. With
+this token, Alpine can go to the email service provider and request a new
+Access Token. This part of the process does not need user intervention,
+anyone can do this, as long as they have the Refresh Token, program id and
+program secret, so it is important that users keep their Refresh Token as
+secure as they can.
+
+<P>
+Given that the Refresh Token and the Access Token are long strings, users
+are not supposed to be able to memorize them and recall them later. Alpine
+will store them in the password file, should a user have one. Otherwise,
+the user will have to create one each time they run Alpine, starting by
+creating the Access Code all over each time they try to sign in to their
+email account. When Alpine is compiled with SSL and password file support,
+it will default to saving this information using encryption and under a
+master password, so unless they have made changes to this process, their
+Refresh and Access Tokens are saved securely and persist from one session
+to the next.
+
+<P>
+Should any person steal your Refresh Token, you must login to your account
+with your username and password and revoke Alpine authorization to your
+account. This is enough to stop the person who stole your Refresh Token
+from accessing your email service. In the case of Gmail, changing your
+password will invalidate your Refresh Token, and this will be enough to
+prevent others from using a stolen Refresh Token. Consult with your email
+service provider to learn what you can do if someone steals your Refresh
+Token.
+
+<P>
+If, for any reason, Alpine cannot sign in to your email service for two
+consecutive times with Access Codes generated by your Refresh Token, it
+will remove the Refresh Token from your password file, forcing you to get
+a new one, by getting an Access Code first, and granting authorization to
+Alpine to access your email once again.
+
+<P>
+This implementation of XOAUTH2 knows the list of servers that it can
+access using the same credentials, so Alpine will be able to read and send
+emails using the same Access Token. You will not have to create
+Access and Refresh Tokens for the IMAP and SMTP servers separately.
+
+<P>
+&lt;End of help&gt;
+</BODY>
+</HTML>
====== h_tls_failure_details ======
<HTML>
<HEAD>
@@ -1566,6 +1678,34 @@ you will get no warning if you do this.
&lt;End of Cert Validation Failures help&gt;
</BODY>
</HTML>
+====== h_oauth2_start ======
+<HTML>
+<HEAD>
+<TITLE>Setting up XOAUTH2 Authentication</TITLE>
+</HEAD>
+<BODY>
+<H1>Setting up XOAUTH2 Authentication</H1>
+
+You are trying to connect to a server that uses the XOAUTH2 method of
+authentication.
+
+<P>
+In order to set this up, you need to authenticate in the target server
+and authorize Alpine to access your email account.
+
+<P>
+After you have authorized Alpine, the server will generate an
+&quot;access code.&quot; In order to use this code, press 'C'
+and copy and paste this code back into Alpine.
+
+<P>
+After you have input the code, Alpine will conclude logging you into your
+account.
+
+<P>
+&lt;End of setting up XOAUTH2 Authentication help&gt;
+</BODY>
+</HTML>
====== h_release_tlscerts ======
<HTML>
<HEAD>
diff --git a/pith/string.c b/pith/string.c
index 11a57fc..50833af 100644
--- a/pith/string.c
+++ b/pith/string.c
@@ -2839,11 +2839,23 @@ isxpair(char *s)
STRLIST_S *
new_strlist(char *name)
{
+ return new_strlist_auth(name, NULL, '\0');
+}
+
+STRLIST_S *
+new_strlist_auth(char *name, char *authtype, char sep)
+{
STRLIST_S *sp = (STRLIST_S *) fs_get(sizeof(STRLIST_S));
- memset(sp, 0, sizeof(STRLIST_S));
- if(name)
- sp->name = cpystr(name);
+ int len = authtype ? strlen(authtype) : 0;
+ int offset = authtype ? 1 : 0;
+ memset(sp, 0, sizeof(STRLIST_S));
+ if(name){
+ sp->name = fs_get(strlen(name) + len + offset + 1);
+ sprintf(sp->name, "%s%s%s", authtype ? authtype : "",
+ authtype ? " " : "", name);
+ if(authtype != NULL) sp->name[len] = sep;
+ }
return(sp);
}
diff --git a/pith/string.h b/pith/string.h
index 13e2560..d3f1ee5 100644
--- a/pith/string.h
+++ b/pith/string.h
@@ -144,6 +144,7 @@ char *add_escapes(char *, char *, int, char *, char *);
char *copy_quoted_string_asis(char *);
int isxpair(char *);
STRLIST_S *new_strlist(char *);
+STRLIST_S *new_strlist_auth(char *, char *, char);
STRLIST_S *copy_strlist(STRLIST_S *);
void combine_strlists(STRLIST_S **, STRLIST_S *);
void free_strlist(STRLIST_S **);
diff --git a/web/src/alpined.d/imap.c b/web/src/alpined.d/imap.c
index d9512e8..a57e769 100644
--- a/web/src/alpined.d/imap.c
+++ b/web/src/alpined.d/imap.c
@@ -235,7 +235,11 @@ mm_notify(MAILSTREAM *stream, char *string, long errflag)
3, 6, ps_global->last_error);
}
+void
+mm_login_method_work (NETMBX *mb,char *user,void *login,long trial, char *method, char *usethisprompt, char *altuserforcache)
+{
+}
/*----------------------------------------------------------------------
Do work of getting login and password from user for IMAP login
@@ -248,11 +252,12 @@ mm_notify(MAILSTREAM *stream, char *string, long errflag)
Result: username and password passed back to imap
----*/
void
-mm_login_work(NETMBX *mb, char *user, char *pwd, long trial, char *usethisprompt, char *altuserforcache)
+mm_login_work(NETMBX *mb, char *user, char **passwd, long trial, char *usethisprompt, char *altuserforcache)
{
STRLIST_S hostlist[2];
NETMBX cmb;
int l;
+ char *pwd = *passwd;
pwd[0] = '\0';
@@ -262,6 +267,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long trial, char *usethisprompt
if(trial){ /* one shot only! */
user[0] = '\0';
peCredentialError = 1;
+ *passwd = pwd;
return;
}
@@ -276,6 +282,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long trial, char *usethisprompt
else
user[0] = pwd[0] = '\0';
+ *passwd = pwd;
return;
}
#endif
@@ -284,6 +291,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long trial, char *usethisprompt
/* we *require* secure authentication */
if(!(mb->sslflag || mb->tlsflag) && strcmp("localhost",mb->host)){
user[0] = pwd[0] = '\0';
+ *passwd = pwd;
return;
}
#endif
@@ -307,6 +315,7 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long trial, char *usethisprompt
* don't blindly offer user/pass
*/
user[0] = pwd[0] = '\0';
+ *passwd = pwd;
return;
}
@@ -323,9 +332,10 @@ mm_login_work(NETMBX *mb, char *user, char *pwd, long trial, char *usethisprompt
hostlist[0].next = NULL;
/* try last working password associated with this host. */
- if(!imap_get_passwd(mm_login_list, pwd, user, hostlist, (mb->sslflag || mb->tlsflag))){
+ if(!imap_get_passwd(mm_login_list, passwd, user, hostlist, (mb->sslflag || mb->tlsflag))){
peNoPassword = 1;
user[0] = pwd[0] = '\0';
+ *passwd = pwd;
}
}