fetchmail/fetchmail-add-imap-oauthbearer-support.patch
Angel Yankov 9f1d1d27ed Accepting request 1227336 from home:ayankov:branches:server:mail
- Upgrade to 6.5.1 
  * Drop two wolfSSL compile-time checks that were for older 6.4 or for future
    7.0 releases and broke compilation with wolfSSL 5.7.4. 
    Fixes https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=282413#c4
  * Use %p instead of non-portable %#p for one wolfSSL-related diagnostic message
    (FreeBSD defines %#p to be %p, on many other platforms it's undefined 
    behavior).
  * Add regex_helper.c to list of files that contain translatable strings,
    which contains two strings we missed to translate.
  * Simplify EVP_MD_fetch API detection ("like OpenSSL 3" vs. "like OpenSSL 1") 
    for version switch and base it on the claimed OpenSSL version of the crypto 
    SSL, which works for LibreSSL (claims OpenSSL 2) and wolfSSL alike.
  * Several translations added
  - Rebased fetchmail-6.3.8-smtp_errors.patch
  - Rebased fetchmail-FAQ-list-gmail-options-including-oauthbearer-and-app.patch
  - Rebased fetchmail-add-contrib-fetchnmail-oauth2.py-token-acquisition-u.patch
  - Rebased fetchmail-add-imap-oauthbearer-support.patch
  - Rebased fetchmail-add-passwordfile-and-passwordfd-options.patch
  - Rebased fetchmail-add-query_to64_outsize-utility-function.patch
  - Rebased fetchmail-bump-max-passwordlen-to-1bytes.patch
  - Rebased fetchmail-give-each-ctl-it-s-own-copy-of-password.patch
  - Rebased fetchmail-increase-max-password-length-to-handle-oauth-tokens.patch
  - Rebased fetchmail-re-read-passwordfile-on-every-poll.patch
  - Rebased fetchmail-support-oauthbearer-xoauth2-with-pop3.patch
  - Rebased fetchmailconf-no-more-future.patch

OBS-URL: https://build.opensuse.org/request/show/1227336
OBS-URL: https://build.opensuse.org/package/show/server:mail/fetchmail?expand=0&rev=159
2024-11-29 14:58:57 +00:00

301 lines
12 KiB
Diff

From: Matthew Ogilvie <mmogilvi+fml@zoho.com>
Date: Sat, 27 May 2017 15:32:28 -0600
Subject: add imap oauthbearer support
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
Git-commit: 5c44df6df70b90f06d3204c6fbdd1ff19e990ca0
This expects an oauth2 access token in place of password.
When configured, it will also fall back on trying xoauth2.
---
conf.c | 2 +
fetchmail.c | 3 +
fetchmail.h | 2 +
fetchmail.man | 26 +++++++++++--
fetchmailconf.py | 2 -
imap.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
options.c | 2 +
rcfile_l.l | 1
8 files changed, 137 insertions(+), 5 deletions(-)
Index: fetchmail-6.5.1/conf.c
===================================================================
--- fetchmail-6.5.1.orig/conf.c
+++ fetchmail-6.5.1/conf.c
@@ -284,6 +284,8 @@ void dump_config(struct runctl *runp, st
stringdump("auth", "otp");
else if (ctl->server.authenticate == A_MSN)
stringdump("auth", "msn");
+ else if (ctl->server.authenticate == A_OAUTHBEARER)
+ stringdump("auth", "oauthbearer");
#ifdef HAVE_RES_SEARCH
booldump("dns", ctl->server.dns);
Index: fetchmail-6.5.1/fetchmail.c
===================================================================
--- fetchmail-6.5.1.orig/fetchmail.c
+++ fetchmail-6.5.1/fetchmail.c
@@ -1804,6 +1804,9 @@ static void dump_params (struct runctl *
case A_IMPLICIT:
printf(GT_(" End-to-end encryption assumed.\n"));
break;
+ case A_OAUTHBEARER:
+ printf(GT_(" OAUTHBEARER will be forced; expecting password to really be OAUTH2 authentication token\n"));
+ break;
}
if (ctl->server.principal != (char *) NULL)
printf(GT_(" Mail service principal is: %s\n"), ctl->server.principal);
Index: fetchmail-6.5.1/fetchmail.h
===================================================================
--- fetchmail-6.5.1.orig/fetchmail.h
+++ fetchmail-6.5.1/fetchmail.h
@@ -64,6 +64,7 @@ struct addrinfo;
#define A_IMPLICIT 8 /* authentication at session level */
#define A_MSN 9 /* same as NTLM with keyword MSN */
#define A_EXTERNAL 10 /* external authentication (client cert) */
+#define A_OAUTHBEARER 11 /** oauth2 access token (not password) */
/* some protocols or authentication types (KERBEROS, GSSAPI, SSH) don't
* require a password */
@@ -99,6 +100,7 @@ struct addrinfo;
#define MSGBUFSIZE 8192
#define NAMELEN 64 /* max username length */
+/* oauth2 access tokens seem to be about 130 characters; make this longer: */
#define PASSWORDLEN 256 /* max password length */
#define DIGESTLEN 33 /* length of MD5 digest */
Index: fetchmail-6.5.1/fetchmail.man
===================================================================
--- fetchmail-6.5.1.orig/fetchmail.man
+++ fetchmail-6.5.1/fetchmail.man
@@ -1113,8 +1113,8 @@ 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) and \fBimplicit\fP (\fBssh\fP is understood
-as alias for \fBimplicit\fP).
+\fBexternal\fP (only IMAP), \fBimplicit\fP (\fBssh\fP is understood
+as alias for \fBimplicit\fP) and \fBoauthbearer\fP (only IMAP).
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
@@ -1139,8 +1139,24 @@ authentication. This option does not wo
in line with RFC-2743 and IANA registrations, see
.UR https://www.iana.org/assignments/gssapi-service-names/
Generic Security Service Application Program Interface (GSSAPI)/Kerberos/Simple
-Authentication and Security Layer (SASL) Service Names
-.UE .
+Authentication and Security Layer (SASL) Service Names .
+.sp
+\fBoauthbearer\fP expects the supplied password to be an oauth2 authentication
+token instead of a password, as used by services like gmail.
+See RFC 7628 and RFC 6750. The \fBoauthbearer\fP
+setting also allows the non-standard "xoauth2" SASL scheme (using
+the same token) if the server only claims to support "xoauth2".
+External tools are necessary to obtain
+such tokens. Access tokens often expire fairly quickly (e.g. 1 hour),
+and new ones need to be generated from renewal tokens. See the
+oauth2.py script from
+.URL https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough "Google's Oauth2 Run Through" ,
+and other oauth2 documentation. For services like gmail, an "App Password"
+is probably preferable if available, because it has roughly the same
+security risks, and is a whole lot simpler to get working. "App Password"
+and oauthbearer both need to protect secrets on the client machine (files) and
+over the network (SSL/TLS). But "App Password" is
+sometimes completely disabled by business "G-suite" administrators.
.SS Miscellaneous Options
.TP
.B \-f <pathname> | \-\-fetchmailrc <pathname>
@@ -2475,7 +2491,9 @@ Legal protocol identifiers for use with
.PP
Legal authentication types are 'any', 'password', 'kerberos',
\&'kerberos_v4', 'kerberos_v5' and 'gssapi', 'cram\-md5', 'otp', 'msn'
-(only for POP3), 'ntlm', 'implicit', 'external' (only IMAP).
+(only for POP3), 'ntlm', 'implicit', 'external' (only IMAP),
+'oauthbearer' (only for IMAP; 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.5.1/fetchmailconf.py
===================================================================
--- fetchmail-6.5.1.orig/fetchmailconf.py
+++ fetchmail-6.5.1/fetchmailconf.py
@@ -499,7 +499,7 @@ defaultports = {"auto":None,
"ODMR":"odmr"}
authlist = ("any", "password", "gssapi", "kerberos", "implicit", "otp",
- "msn", "ntlm")
+ "msn", "ntlm", "oauthbearer")
listboxhelp = {
'title' : 'List Selection Help',
Index: fetchmail-6.5.1/imap.c
===================================================================
--- fetchmail-6.5.1.orig/imap.c
+++ fetchmail-6.5.1/imap.c
@@ -24,6 +24,10 @@
#define IMAP4 0 /* IMAP4 rev 0, RFC1730 */
#define IMAP4rev1 1 /* IMAP4 rev 1, RFC2060 */
+/* imap_plus_cont_context values */
+#define IPLUS_NONE 0
+#define IPLUS_OAUTHBEARER 1 /* oauthbearer (for more error info) */
+
/* global variables: please reinitialize them explicitly for proper
* working in daemon mode */
@@ -49,6 +53,8 @@ static void clear_sessiondata(void) {
* a const initializer */
const char *const capa_begin = " [CAPABILITY "; const unsigned capa_len = 13;
+static int plus_cont_context = IPLUS_NONE;
+
/* mailbox variables initialized in imap_getrange() */
static int count = 0, oldcount = 0, recentcount = 0, unseen = 0, deletions = 0;
static unsigned int startcount = 1;
@@ -262,6 +268,21 @@ static int imap_response(int sock, char
if (ok != PS_SUCCESS)
return(ok);
+ if (buf[0] == '+' && buf[1] == ' ') {
+ if (plus_cont_context == IPLUS_OAUTHBEARER) {
+ /* future: Consider decoding the base64-encoded JSON
+ * error response info and logging it. But for now,
+ * ignore continuation data, send the expected blank
+ * line, and assume that the next response will be
+ * a tagged "NO" as documented.
+ */
+ SockWrite(sock, "\r\n", 2);
+ if (outlevel >= O_MONITOR)
+ report(stdout, "IMAP> \n");
+ continue;
+ }
+ }
+
/* all tokens in responses are caseblind */
for (cp = buf; *cp; cp++)
if (islower((unsigned char)*cp))
@@ -394,6 +415,69 @@ static int do_imap_ntlm(int sock, struct
}
#endif /* NTLM */
+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;
+
+ 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));
+
+ plus_cont_context = IPLUS_OAUTHBEARER;
+ ok = gen_transact(sock, "AUTHENTICATE %s %s", name, oauth2b64);
+ plus_cont_context = IPLUS_NONE;
+
+ memset(shroud, 0x55, sizeof(shroud));
+ shroud[0] = '\0';
+ memset(oauth2b64, 0x55, strlen(oauth2b64));
+ free(oauth2b64);
+
+ return ok;
+}
+
static void imap_canonicalize(char *result, char *raw, size_t maxlen)
/* encode an IMAP password as per RFC1730's quoting conventions */
{
@@ -584,6 +668,26 @@ static int imap_getauth(int sock, struct
for future maintenance */
(void)ok;
+ if (ctl->server.authenticate == A_OAUTHBEARER)
+ {
+ /* Fetchmail's oauthbearer and xoauth2 support expects the "password"
+ * to actually be an oauth2 authentication token, so only
+ * try these options if specifically enabled.
+ * (Generating a token using the complex https-based oauth2
+ * protocol is left as an exercise for the user.)
+ */
+ if (strstr(capabilities, "AUTH=OAUTHBEARER") ||
+ !strstr(capabilities, "AUTH=XOAUTH2"))
+ {
+ ok = do_imap_oauthbearer(sock, ctl, FALSE); /* OAUTHBEARER */
+ }
+ if (ok && strstr(capabilities, "AUTH=XOAUTH2"))
+ {
+ ok = do_imap_oauthbearer(sock, ctl, TRUE); /* XOAUTH2 */
+ }
+ return ok;
+ }
+
/* Yahoo hack - we'll just try ID if it was offered by the server,
* and IGNORE errors. */
{
Index: fetchmail-6.5.1/options.c
===================================================================
--- fetchmail-6.5.1.orig/options.c
+++ fetchmail-6.5.1/options.c
@@ -395,6 +395,8 @@ int parsecmdline (int argc /** argument
ctl->server.authenticate = A_ANY;
else if (strcmp(optarg, "msn") == 0)
ctl->server.authenticate = A_MSN;
+ else if (strcmp(optarg, "oauthbearer") == 0)
+ ctl->server.authenticate = A_OAUTHBEARER;
else {
fprintf(stderr,GT_("Invalid authentication `%s' specified.\n"), optarg);
errflag++;
Index: fetchmail-6.5.1/rcfile_l.l
===================================================================
--- fetchmail-6.5.1.orig/rcfile_l.l
+++ fetchmail-6.5.1/rcfile_l.l
@@ -103,6 +103,7 @@ cram(-md5)? { SETSTATE(0); yylval.proto
msn { SETSTATE(0); yylval.proto = A_MSN; return AUTHTYPE;}
ntlm { SETSTATE(0); yylval.proto = A_NTLM; return AUTHTYPE;}
<AUTH>password { SETSTATE(0); yylval.proto = A_PASSWORD; return AUTHTYPE;}
+oauthbearer { SETSTATE(0); yylval.proto = A_OAUTHBEARER; return AUTHTYPE;}
timeout { return TIMEOUT;}
idletimeout { return IDLETIMEOUT;}
envelope { return ENVELOPE; }