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 /pith | |
parent | 19cde66486e27063a9af8cfd79c6eb7f106b9111 (diff) | |
download | alpine-c024a78dbaa9b42db7f18b0fea1894c41e2b0d67.tar.xz |
* Initial release of XOAUTH2 authentication support in Alpine for
Gmail.
Diffstat (limited to 'pith')
-rw-r--r-- | pith/imap.c | 101 | ||||
-rw-r--r-- | pith/imap.h | 14 | ||||
-rw-r--r-- | pith/ldap.c | 6 | ||||
-rw-r--r-- | pith/pine.hlp | 142 | ||||
-rw-r--r-- | pith/string.c | 18 | ||||
-rw-r--r-- | pith/string.h | 1 |
6 files changed, 253 insertions, 29 deletions
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 **); |