diff options
author | Eduardo Chappa <chappa@washington.edu> | 2019-05-04 12:41:11 -0600 |
---|---|---|
committer | Eduardo Chappa <chappa@washington.edu> | 2019-05-04 12:41:11 -0600 |
commit | c024a78dbaa9b42db7f18b0fea1894c41e2b0d67 (patch) | |
tree | 441e7308e4577ac8766c44edda682704aa432262 | |
parent | 19cde66486e27063a9af8cfd79c6eb7f106b9111 (diff) | |
download | alpine-c024a78dbaa9b42db7f18b0fea1894c41e2b0d67.tar.xz |
* Initial release of XOAUTH2 authentication support in Alpine for
Gmail.
45 files changed, 3583 insertions, 187 deletions
@@ -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 3b7df35f..973fa2c6 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 1fc6c76f..c6da3e61 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 08f51ce5..cc56e2d1 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 77969321..016bd375 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 7e3a7f76..47c0eea3 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 0738c376..4e7b79a5 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 2cd3f643..f8962c6d 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 2c677d93..6dfbfee2 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 cd74e7fb..a497d3c3 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 00000000..c5a65ddb --- /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 6615744f..a54d6365 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 8c989769..d4e7024b 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 0afe99fc..52ae9048 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 00000000..d77b0102 --- /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 ef991f1d..6cc26c71 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 00000000..1e580c6a --- /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 00000000..59b7e1d0 --- /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 b7423056..aaaa66e4 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 00000000..f6d05736 --- /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 00000000..88585170 --- /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 8f0373ed..49444ebe 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 58d2979c..b5cd65d2 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 b1d08024..53b51454 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 dfc4f925..7ca07da8 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 b4c72a36..a0195b97 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 8063f3fc..ab163d6c 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 ecf39bef..48883e2a 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 cf9bb732..31c71355 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 1c1d4011..f53dca82 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 58d4d2d5..48844b3c 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 9cb8bffc..2be17afb 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 d8faefa4..979416bd 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 018d1bcd..e433ba8d 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 8d740bb4..a94040c8 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 53c5d05a..21bf55ee 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 b076ce44..73791f60 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 273db40a..1e4a4e47 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 5d9a789c..f9e5162f 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 d58f4e3c..9ed70621 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 acf592db..f56592d2 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 16efc9c2..af37f7ab 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. <End of help> </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 "<I>Access Code</I>" 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 "<I>Refresh +Token</I>" and the "<I>Access Token</I>". 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> +<End of help> +</BODY> +</HTML> ====== h_tls_failure_details ====== <HTML> <HEAD> @@ -1566,6 +1678,34 @@ you will get no warning if you do this. <End of Cert Validation Failures help> </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 +"access code." 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> +<End of setting up XOAUTH2 Authentication help> +</BODY> +</HTML> ====== h_release_tlscerts ====== <HTML> <HEAD> diff --git a/pith/string.c b/pith/string.c index 11a57fc0..50833afa 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 13e25604..d3f1ee5c 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 d9512e86..a57e769d 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; } } |