File fetchmail-support-oauthbearer-xoauth2-with-pop3.patch of Package fetchmail.22690

From: Matthew Ogilvie <mmogilvi+fml@zoho.com>
Date: Fri, 30 Jun 2017 02:35:12 -0600
Subject: support oauthbearer/xoauth2 with pop3
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
Git-commit: 7b5c56f0fa3acb4c5589a4747c1921a311d8a464

(Also factor out some common imap/pop3 oauth2 code.)
---
 Makefile.am   |   2 +-
 fetchmail.man |   5 +--
 imap.c        |  53 +++-------------------
 oauth2.c      |  61 +++++++++++++++++++++++++
 oauth2.h      |   6 +++
 pop3.c        | 122 ++++++++++++++++++++++++++++++++++++++++++++++++--
 6 files changed, 196 insertions(+), 53 deletions(-)
 create mode 100644 oauth2.c
 create mode 100644 oauth2.h

Index: fetchmail-6.4.22/Makefile.am
===================================================================
--- fetchmail-6.4.22.orig/Makefile.am
+++ fetchmail-6.4.22/Makefile.am
@@ -68,7 +68,7 @@ fetchmail_SOURCES=	fetchmail.h getopt.h
 		fetchmail.c env.c idle.c options.c daemon.c \
 		driver.c transact.c sink.c smtp.c \
 		idlist.c uid.c mxget.c md5ify.c cram.c gssapi.c \
-		opie.c interface.c netrc.c \
+		oauth2.c opie.c interface.c netrc.c \
 		unmime.c conf.c checkalias.c uid_db.h uid_db.c\
 		lock.h lock.c \
 		rcfile_l.l rcfile_y.y \
Index: fetchmail-6.4.22/fetchmail.man
===================================================================
--- fetchmail-6.4.22.orig/fetchmail.man
+++ fetchmail-6.4.22/fetchmail.man
@@ -1007,7 +1007,7 @@ AUTHENTICATION below for details).  The
 \&\fBpassword\fP, \fBkerberos_v5\fP, \fBkerberos\fP (or, for
 excruciating exactness, \fBkerberos_v4\fP), \fBgssapi\fP,
 \fBcram\-md5\fP, \fBotp\fP, \fBntlm\fP, \fBmsn\fP (only for POP3),
-\fBexternal\fP (only IMAP), \fBssh\fP and \fBoauthbearer\fP (only IMAP).
+\fBexternal\fP (only IMAP), \fBssh\fP and \fBoauthbearer\fP (requires token).
 When \fBany\fP (the default) is specified, fetchmail tries
 first methods that don't require a password (EXTERNAL, GSSAPI, KERBEROS\ IV,
 KERBEROS\ 5); then it looks for methods that mask your password
@@ -2351,8 +2351,7 @@ Legal protocol identifiers for use with
 Legal authentication types are 'any', 'password', 'kerberos',
 \&'kerberos_v4', 'kerberos_v5' and 'gssapi', 'cram\-md5', 'otp', 'msn'
 (only for POP3), 'ntlm', 'ssh', 'external' (only IMAP),
-'oauthbearer' (only for IMAP; requires authentication token in
-place of password).
+'oauthbearer' (requires authentication token in place of password).
 The 'password' type specifies
 authentication by normal transmission of a password (the password may be
 plain text or subject to protocol-specific encryption as in CRAM-MD5);
Index: fetchmail-6.4.22/imap.c
===================================================================
--- fetchmail-6.4.22.orig/imap.c
+++ fetchmail-6.4.22/imap.c
@@ -17,6 +17,7 @@
 #include  <limits.h>
 #include  <errno.h>
 #endif
+#include  "oauth2.h"
 #include  "socket.h"
 
 #include  "i18n.h"
@@ -419,63 +420,23 @@ static int do_imap_ntlm(int sock, struct
 
 static int do_imap_oauthbearer(int sock, struct query *ctl,flag xoauth2)
 {
-    /* Implements relevant parts of RFC-7628, RFC-6750, and
-     * https://developers.google.com/gmail/imap/xoauth2-protocol
-     *
-     * This assumes something external manages obtaining an up-to-date
-     * authentication/bearer token and arranging for it to be in
-     * ctl->password.  This may involve renewing it ahead of time if
-     * necessary using a renewal token that fetchmail knows nothing about.
-     * See:
-     * https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough
-     */
-    const char *name;
-    char *oauth2str;
-    int oauth2len;
-    int saved_suppress_tags = suppress_tags;
-
-    char *oauth2b64;
-
+    char *oauth2str = get_oauth2_string(ctl, xoauth2);
+    const char *name = xoauth2 ? "XOAUTH2" : "OAUTHBEARER";
     int ok;
 
-    oauth2len = strlen(ctl->remotename) + strlen(ctl->password) + 32;
-    oauth2str = (char *)xmalloc(oauth2len);
-    if (xoauth2)
-    {
-	snprintf(oauth2str, oauth2len,
-	         "user=%s\1auth=Bearer %s\1\1",
-	         ctl->remotename,
-	         ctl->password);
-	name = "XOAUTH2";
-    }
-    else
-    {
-	snprintf(oauth2str, oauth2len,
-	         "n,a=%s,\1auth=Bearer %s\1\1",
-	         ctl->remotename,
-	         ctl->password);
-	name = "OAUTHBEARER";
-    }
-
-    oauth2b64 = (char *)xmalloc(2*strlen(oauth2str)+8);
-    to64frombits(oauth2b64, oauth2str, strlen(oauth2str));
-
-    memset(oauth2str, 0x55, strlen(oauth2str));
-    free(oauth2str);
-
     /* Protect the access token like a password in logs, despite the
      * usually-short expiration time and base64 encoding:
      */
-    strlcpy(shroud, oauth2b64, sizeof(shroud));
+    strlcpy(shroud, oauth2str, sizeof(shroud));
 
     plus_cont_context = IPLUS_OAUTHBEARER;
-    ok = gen_transact(sock, "AUTHENTICATE %s %s", name, oauth2b64);
+    ok = gen_transact(sock, "AUTHENTICATE %s %s", name, oauth2str);
     plus_cont_context = IPLUS_NONE;
 
     memset(shroud, 0x55, sizeof(shroud));
     shroud[0] = '\0';
-    memset(oauth2b64, 0x55, strlen(oauth2b64));
-    free(oauth2b64);
+    memset(oauth2str, 0x55, strlen(oauth2str));
+    free(oauth2str);
 
     return ok;
 }
Index: fetchmail-6.4.22/oauth2.c
===================================================================
--- /dev/null
+++ fetchmail-6.4.22/oauth2.c
@@ -0,0 +1,61 @@
+/*
+ * oauth2.c -- oauthbearer and xoauth2 support
+ *
+ * Copyright 2017 by Matthew Ogilvie
+ * For license terms, see the file COPYING in this directory.
+ */
+
+#include "config.h"
+#include "fetchmail.h"
+#include "oauth2.h"
+
+#include <stdio.h>
+#include <string.h>
+
+char *get_oauth2_string(struct query *ctl,flag xoauth2)
+{
+    /* Implements the bearer token string based for a
+     * combination of RFC-7628 (ouath sasl, with
+     * examples for imap only), RFC-6750 (oauth2), and
+     * RFC-5034 (pop sasl), as implemented by gmail and others.
+     *
+     * Also supports xoauth2, which is just a couple of minor variariations.
+     * https://developers.google.com/gmail/imap/xoauth2-protocol
+     *
+     * This assumes something external manages obtaining an up-to-date
+     * authentication/bearer token and arranging for it to be in
+     * ctl->password.  This may involve renewing it ahead of time if
+     * necessary using a renewal token that fetchmail knows nothing about.
+     * See:
+     * https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough
+     */
+    char *oauth2str;
+    int oauth2len;
+
+    char *oauth2b64;
+
+    oauth2len = strlen(ctl->remotename) + strlen(ctl->password) + 32;
+    oauth2str = (char *)xmalloc(oauth2len);
+    if (xoauth2)
+    {
+	snprintf(oauth2str, oauth2len,
+		 "user=%s\1auth=Bearer %s\1\1",
+		 ctl->remotename,
+		 ctl->password);
+    }
+    else
+    {
+	snprintf(oauth2str, oauth2len,
+		 "n,a=%s,\1auth=Bearer %s\1\1",
+		 ctl->remotename,
+		 ctl->password);
+    }
+
+    oauth2b64 = (char *)xmalloc(2*strlen(oauth2str)+8);
+    to64frombits(oauth2b64, oauth2str, strlen(oauth2str));
+
+    memset(oauth2str, 0x55, strlen(oauth2str));
+    free(oauth2str);
+
+    return oauth2b64;
+}
Index: fetchmail-6.4.22/oauth2.h
===================================================================
--- /dev/null
+++ fetchmail-6.4.22/oauth2.h
@@ -0,0 +1,6 @@
+#ifndef OAUTH2_H
+#define OAUTH2_H
+
+char *get_oauth2_string(struct query *ctl,flag xoauth2);
+
+#endif /*OAUTH2_H*/
Index: fetchmail-6.4.22/pop3.c
===================================================================
--- fetchmail-6.4.22.orig/pop3.c
+++ fetchmail-6.4.22/pop3.c
@@ -20,6 +20,7 @@
 #include  <errno.h>
 
 #include  "fetchmail.h"
+#include  "oauth2.h"
 #include  "socket.h"
 #include  "i18n.h"
 #include  "uid_db.h"
@@ -52,6 +53,10 @@ static flag has_cram = FALSE;
 static flag has_otp = FALSE;
 static flag has_ntlm = FALSE;
 static flag has_stls = FALSE;
+static flag has_oauthbearer = FALSE;
+static flag has_xoauth2 = FALSE;
+
+static const char *next_sasl_resp = NULL;
 
 static void clear_sessiondata(void) {
     /* must match defaults above */
@@ -135,12 +140,65 @@ static int pop3_ok (int sock, char *argb
     char buf [POPBUFSIZE+1];
     char *bufp;
 
-    if ((ok = gen_recv(sock, buf, sizeof(buf))) == 0)
+    while ((ok = gen_recv(sock, buf, sizeof(buf))) == 0)
     {	bufp = buf;
-	if (*bufp == '+' || *bufp == '-')
-	    bufp++;
-	else
+	if (*bufp == '+')
+	{
+	   bufp++;
+       if (*bufp == ' ' && next_sasl_resp != NULL)
+       {
+       /* Currently only used for OAUTHBEARER/XOAUTH2, and only
+        * rarely even then.
+        *
+        * This is the only case where the top while() actually
+        * loops.
+        *
+        * For OAUTHBEARER, data aftetr '+ ' is probably
+        * base64-encoded JSON with some HTTP-related error details.
+        */
+       if (*next_sasl_resp != '\0')
+           SockWrite(sock, next_sasl_resp, strlen(next_sasl_resp));
+       SockWrite(sock, "\r\n", 2);
+       if (outlevel >= O_MONITOR)
+       {
+           const char *found;
+           if (shroud[0] && (found = strstr(next_sasl_resp, shroud)))
+           {
+           /* enshroud() without copies, and avoid
+            * confusing with a genuine "*" (cancel).
+            */
+           report(stdout, "POP3> %.*s[SHROUDED]%s\n",
+                  (int)(found-next_sasl_resp), next_sasl_resp,
+                  found+strlen(shroud));
+           }
+           else
+           {
+           report(stdout, "POP3> %s\n", next_sasl_resp);
+           }
+       }
+
+       if (*next_sasl_resp == '\0' || *next_sasl_resp == '*')
+       {
+           /* No more responses expected, cancel AUTH command if
+            * more responses requested.
+            */
+           next_sasl_resp = "*";
+       }
+       else
+       {
+           next_sasl_resp = "";
+       }
+       continue;
+       }
+   }
+   else if (*bufp == '-')
+   {
+       bufp++;
+   }
+   else
+   {
 	    return(PS_PROTOCOL);
+   }
 
 	while (isalpha((unsigned char)*bufp))
 	    bufp++;
@@ -209,6 +267,8 @@ static int pop3_ok (int sock, char *argb
 #endif
 	if (argbuf != NULL)
 	    strcpy(argbuf,bufp);
+
+	break;
     }
 
     return(ok);
@@ -237,11 +297,13 @@ static int capa_probe(int sock)
 #ifdef NTLM_ENABLE
     has_ntlm = FALSE;
 #endif /* NTLM_ENABLE */
+	has_oauthbearer = FALSE;
+	has_xoauth2 = FALSE;
 
     ok = gen_transact(sock, "CAPA");
     if (ok == PS_SUCCESS)
     {
-	char buffer[64];
+	char buffer[128];
 	char *cp;
 
 	/* determine what authentication methods we have available */
@@ -256,6 +318,10 @@ static int capa_probe(int sock)
 	    if (strstr(buffer, "STLS"))
 		has_stls = TRUE;
 #endif /* SSL_ENABLE */
+static flag has_oauthbearer = FALSE;
+static flag has_xoauth2 = FALSE;
+
+static const char *next_sasl_resp = NULL;
 
 #if defined(GSSAPI)
 	    if (strstr(buffer, "GSSAPI"))
@@ -279,6 +345,12 @@ static int capa_probe(int sock)
 
 	    if (strstr(buffer, "CRAM-MD5"))
 		has_cram = TRUE;
+
+	    if (strstr(buffer, "OAUTHBEARER"))
+		has_oauthbearer = TRUE;
+
+	    if (strstr(buffer, "XOAUTH2"))
+		has_xoauth2 = TRUE;
 	}
     }
     done_capa = TRUE;
@@ -295,6 +367,40 @@ static void set_peek_capable(struct quer
     peek_capable = !ctl->fetchall && (!ctl->keep || ctl->server.uidl);
 }
 
+static int do_oauthbearer(int sock, struct query *ctl, flag xoauth2)
+{
+    char *oauth2str = get_oauth2_string(ctl, xoauth2);
+    const char *name = xoauth2 ? "XOAUTH2" : "OAUTHBEARER";
+    int ok;
+
+    /* Protect the access token like a password in logs, despite the
+     * usually-short expiration time and base64 encoding:
+     */
+    strlcpy(shroud, oauth2str, sizeof(shroud));
+
+    if (4+1+1+2+strlen(name)+strlen(oauth2str) <= 255)
+    {
+	next_sasl_resp = "";
+	ok = gen_transact(sock, "AUTH %s %s", name, oauth2str);
+    }
+    else
+    {
+	/* Too long to use "initial client response" (RFC-5034 section 4,
+	 * referencing RFC-4422 section 4).
+	 */
+	next_sasl_resp = oauth2str;
+	ok = gen_transact(sock, "AUTH %s", name);
+    }
+    next_sasl_resp = NULL;
+
+    memset(shroud, 0x55, sizeof(shroud));
+    shroud[0] = '\0';
+    memset(oauth2str, 0x55, strlen(oauth2str));
+    free(oauth2str);
+
+    return ok;
+}
+
 static int pop3_getauth(int sock, struct query *ctl, char *greeting)
 /* apply for connection authorization */
 {
@@ -374,6 +480,7 @@ static int pop3_getauth(int sock, struct
 		(ctl->server.authenticate == A_KERBEROS_V5) ||
 		(ctl->server.authenticate == A_OTP) ||
 		(ctl->server.authenticate == A_CRAM_MD5) ||
+		(ctl->server.authenticate == A_OAUTHBEARER) ||
 		maybe_starttls(ctl))
 	{
 	    if ((ok = capa_probe(sock)) != PS_SUCCESS)
@@ -523,6 +630,19 @@ static int pop3_getauth(int sock, struct
 	/*
 	 * OK, we have an authentication type now.
 	 */
+	if (ctl->server.authenticate == A_OAUTHBEARER)
+	{
+	    if (has_oauthbearer || !has_xoauth2)
+	    {
+		ok = do_oauthbearer(sock, ctl, FALSE); /* OAUTHBEARER */
+	    }
+	    if (ok != PS_SUCCESS && has_xoauth2)
+	    {
+		ok = do_oauthbearer(sock, ctl, TRUE); /* XOAUTH2 */
+	    }
+	    break;
+	}
+
 #if defined(KERBEROS_V4)
 	/* 
 	 * Servers doing KPOP have to go through a dummy login sequence
openSUSE Build Service is sponsored by