summaryrefslogtreecommitdiff
path: root/pith
diff options
context:
space:
mode:
authorEduardo Chappa <chappa@washington.edu>2019-05-04 12:41:11 -0600
committerEduardo Chappa <chappa@washington.edu>2019-05-04 12:41:11 -0600
commitc024a78dbaa9b42db7f18b0fea1894c41e2b0d67 (patch)
tree441e7308e4577ac8766c44edda682704aa432262 /pith
parent19cde66486e27063a9af8cfd79c6eb7f106b9111 (diff)
downloadalpine-c024a78dbaa9b42db7f18b0fea1894c41e2b0d67.tar.xz
* Initial release of XOAUTH2 authentication support in Alpine for
Gmail.
Diffstat (limited to 'pith')
-rw-r--r--pith/imap.c101
-rw-r--r--pith/imap.h14
-rw-r--r--pith/ldap.c6
-rw-r--r--pith/pine.hlp142
-rw-r--r--pith/string.c18
-rw-r--r--pith/string.h1
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.
&lt;End of help&gt;
</BODY>
</HTML>
+====== h_xoauth2 ======
+<HTML>
+<HEAD>
+<TITLE>XOAUTH2 Authenticator Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>XOAUTH2 Authenticator Explained</H1>
+
+The XOAUTH2 authenticator method is a way in which you can sign in to your
+account to read and send email. This method is different from the traditional
+username/password that users are accostumed to, and it needs to be set up
+by the user. This text will help you understand this authentication method.
+
+<P>
+The most important difference between this method and other autentication
+methods is what happens if someone steals your credentials. This method is
+attached to three components: Your username, your password and your email
+program.
+
+<P>
+At the beginning of this process, the developer of the email program
+registers the email program with the email service provider (Gmail,
+Outlook, etc.) In return, the email service provider creates an id and
+secret for the email program, which the email program will use in the
+future. Since Alpine is an open source program, these values are part
+of the source code, and are known to everyone, and cannot be obfuscated.
+
+<P>
+After a program has been registered with an email service provider, the
+user must authorize the email program access to their email account in
+that service. Alpine helps you do that, but it cannot do all the process
+for you, and you will need to use an external web browser to authorize
+Alpine to read and send email.
+
+<P>
+This part of the process is simple. Alpine creates a URL based on the id
+and secret created by the email service, which the user opens. Once the
+URL has been opened, the user signs in to the server, and once signed into
+the account, the user is asked to authorize Alpine access to the email
+account. If the user agrees, an &quot;<I>Access Code</I>&quot; will be
+generated.
+
+<P>
+An Access Code is a very long string, and so the recommendation is to copy
+and paste this code back into Alpine, at the time Alpine is helping you
+set up this authenticator. This part of the process is done only once per
+email program (If you run Alpine in several computers, you would do this
+once per computer. Each computer gets its own Access Code.)
+
+<P>
+Once Alpine has an Access Code, it goes back internally to the email
+service and asks it to generate two strings, called the &quot;<I>Refresh
+Token</I>&quot; and the &quot;<I>Access Token</I>&quot;. This part is
+invisible to the user, and they will never see it, but in order for you to
+understand what to do in case you lose them, you need to understand their
+use.
+
+<P>
+The <I>Access Token</I> is the equivalent of a temporary password. It
+allows the user to access (and reaccess as many times as they would like)
+their email account for a limited amount of time, typically 1 hour after
+it was issued. After that time expires, users need a new Access Token.
+
+<P>
+The <I>Refresh Token</I> is the equivalent of a password generator. With
+this token, Alpine can go to the email service provider and request a new
+Access Token. This part of the process does not need user intervention,
+anyone can do this, as long as they have the Refresh Token, program id and
+program secret, so it is important that users keep their Refresh Token as
+secure as they can.
+
+<P>
+Given that the Refresh Token and the Access Token are long strings, users
+are not supposed to be able to memorize them and recall them later. Alpine
+will store them in the password file, should a user have one. Otherwise,
+the user will have to create one each time they run Alpine, starting by
+creating the Access Code all over each time they try to sign in to their
+email account. When Alpine is compiled with SSL and password file support,
+it will default to saving this information using encryption and under a
+master password, so unless they have made changes to this process, their
+Refresh and Access Tokens are saved securely and persist from one session
+to the next.
+
+<P>
+Should any person steal your Refresh Token, you must login to your account
+with your username and password and revoke Alpine authorization to your
+account. This is enough to stop the person who stole your Refresh Token
+from accessing your email service. In the case of Gmail, changing your
+password will invalidate your Refresh Token, and this will be enough to
+prevent others from using a stolen Refresh Token. Consult with your email
+service provider to learn what you can do if someone steals your Refresh
+Token.
+
+<P>
+If, for any reason, Alpine cannot sign in to your email service for two
+consecutive times with Access Codes generated by your Refresh Token, it
+will remove the Refresh Token from your password file, forcing you to get
+a new one, by getting an Access Code first, and granting authorization to
+Alpine to access your email once again.
+
+<P>
+This implementation of XOAUTH2 knows the list of servers that it can
+access using the same credentials, so Alpine will be able to read and send
+emails using the same Access Token. You will not have to create
+Access and Refresh Tokens for the IMAP and SMTP servers separately.
+
+<P>
+&lt;End of help&gt;
+</BODY>
+</HTML>
====== h_tls_failure_details ======
<HTML>
<HEAD>
@@ -1566,6 +1678,34 @@ you will get no warning if you do this.
&lt;End of Cert Validation Failures help&gt;
</BODY>
</HTML>
+====== h_oauth2_start ======
+<HTML>
+<HEAD>
+<TITLE>Setting up XOAUTH2 Authentication</TITLE>
+</HEAD>
+<BODY>
+<H1>Setting up XOAUTH2 Authentication</H1>
+
+You are trying to connect to a server that uses the XOAUTH2 method of
+authentication.
+
+<P>
+In order to set this up, you need to authenticate in the target server
+and authorize Alpine to access your email account.
+
+<P>
+After you have authorized Alpine, the server will generate an
+&quot;access code.&quot; In order to use this code, press 'C'
+and copy and paste this code back into Alpine.
+
+<P>
+After you have input the code, Alpine will conclude logging you into your
+account.
+
+<P>
+&lt;End of setting up XOAUTH2 Authentication help&gt;
+</BODY>
+</HTML>
====== h_release_tlscerts ======
<HTML>
<HEAD>
diff --git a/pith/string.c b/pith/string.c
index 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 **);