forked from pool/fetchmail
d3007028df
- Update to 6.4.32: * Use configure to find rst2html, some systems install it only with .py suffix, others only without, and some install both. * Update README.maintainer * Translations updated. - Reapplied patches - Add 44-uncorrupt_runfetchmail.patch to clean up some contrib/ scripts (gl#fetchmail/fetchmail#44). OBS-URL: https://build.opensuse.org/request/show/991826 OBS-URL: https://build.opensuse.org/package/show/server:mail/fetchmail?expand=0&rev=137
412 lines
12 KiB
Diff
412 lines
12 KiB
Diff
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 | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
|
|
6 files changed, 202 insertions(+), 55 deletions(-)
|
|
create mode 100644 oauth2.c
|
|
create mode 100644 oauth2.h
|
|
|
|
--- a/Makefile.am
|
|
+++ b/Makefile.am
|
|
@@ -74,7 +74,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 \
|
|
--- a/fetchmail.man
|
|
+++ b/fetchmail.man
|
|
@@ -1006,7 +1006,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 do not require a password (EXTERNAL, GSSAPI, KERBEROS\ IV,
|
|
KERBEROS\ 5); then it looks for methods that mask your password
|
|
@@ -2342,8 +2342,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);
|
|
--- a/imap.c
|
|
+++ b/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;
|
|
}
|
|
--- /dev/null
|
|
+++ b/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;
|
|
+}
|
|
--- /dev/null
|
|
+++ b/oauth2.h
|
|
@@ -0,0 +1,6 @@
|
|
+#ifndef OAUTH2_H
|
|
+#define OAUTH2_H
|
|
+
|
|
+char *get_oauth2_string(struct query *ctl,flag xoauth2);
|
|
+
|
|
+#endif /*OAUTH2_H*/
|
|
--- a/pop3.c
|
|
+++ b/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
|