Sync from SUSE:SLFO:Main fetchmail revision 167eafefba1b0c2f31793088483240b6
This commit is contained in:
commit
3e80a7d031
23
.gitattributes
vendored
Normal file
23
.gitattributes
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
## Default LFS
|
||||
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||
*.bsp filter=lfs diff=lfs merge=lfs -text
|
||||
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.gem filter=lfs diff=lfs merge=lfs -text
|
||||
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||
*.jar filter=lfs diff=lfs merge=lfs -text
|
||||
*.lz filter=lfs diff=lfs merge=lfs -text
|
||||
*.lzma filter=lfs diff=lfs merge=lfs -text
|
||||
*.obscpio filter=lfs diff=lfs merge=lfs -text
|
||||
*.oxt filter=lfs diff=lfs merge=lfs -text
|
||||
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.rpm filter=lfs diff=lfs merge=lfs -text
|
||||
*.tbz filter=lfs diff=lfs merge=lfs -text
|
||||
*.tbz2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||
*.txz filter=lfs diff=lfs merge=lfs -text
|
||||
*.whl filter=lfs diff=lfs merge=lfs -text
|
||||
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.zst filter=lfs diff=lfs merge=lfs -text
|
41
fetchmail-6.3.8-smtp_errors.patch
Normal file
41
fetchmail-6.3.8-smtp_errors.patch
Normal file
@ -0,0 +1,41 @@
|
||||
# Patches fetchmail's behaviour for SMTP servers which:
|
||||
# 1) insist on using TLS - we do not want messages which cannot
|
||||
# be sent due to server negotiation problems lost - rather we
|
||||
# keep them on the mailserver. As users should realize rather
|
||||
# quickly that something went wrong, the overhead shouldn't
|
||||
# be considered that important.
|
||||
# 2) use the 501 error code to mark spam - in such a case
|
||||
# the 'antispam' option should be used
|
||||
Index: fetchmail-6.5.1/sink.c
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/sink.c
|
||||
+++ fetchmail-6.5.1/sink.c
|
||||
@@ -526,6 +526,19 @@ static int handle_smtp_report(struct que
|
||||
free(responses[0]);
|
||||
return(PS_TRANSIENT);
|
||||
|
||||
+ case 501: /* Syntax error in parameters or arguments */
|
||||
+ /*
|
||||
+ * Some SMTP servers use this error code to refuse spam, however
|
||||
+ * as we don't want to delete message if e.g. the address is (possibly
|
||||
+ * by mistake) malformed, user has to use the 'antispam' option to
|
||||
+ * treat this as spam symptom
|
||||
+ */
|
||||
+ free(responses[0]);
|
||||
+ if (outlevel > O_SILENT)
|
||||
+ report_complete(stdout,
|
||||
+ GT_(" SMTP 501 error - if the server uses this code to report spam, include '501' in the 'antispam' option .\n"));
|
||||
+ return(PS_TRANSIENT);
|
||||
+
|
||||
default:
|
||||
/* bounce non-transient errors back to the sender */
|
||||
if (smtperr >= 500 && smtperr <= 599)
|
||||
@@ -591,7 +604,7 @@ static int handle_smtp_report_without_bo
|
||||
case 553: /* invalid sending domain */
|
||||
return(PS_REFUSED);
|
||||
|
||||
- default:
|
||||
+ default:
|
||||
/* bounce non-transient errors back to the sender */
|
||||
if (smtperr >= 500 && smtperr <= 599)
|
||||
return(PS_SUCCESS);
|
BIN
fetchmail-6.5.1.tar.xz
(Stored with Git LFS)
Normal file
BIN
fetchmail-6.5.1.tar.xz
(Stored with Git LFS)
Normal file
Binary file not shown.
16
fetchmail-6.5.1.tar.xz.asc
Normal file
16
fetchmail-6.5.1.tar.xz.asc
Normal file
@ -0,0 +1,16 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCgAdFiEE3EplW9mTzUhx+oIQ5BKxVu/zhVoFAmcz6JcACgkQ5BKxVu/z
|
||||
hVptQA//c7mklRvCJmSIhF91w0XntT5aOoWL19zLLZXBXgU+3YXmhu8sUufaZtVF
|
||||
ORJpJPvxEuK3g3Ja9rS4q959pM1rTDZ15GvpnF9oXUi62xLEEGzMweU8PIIMu7Ov
|
||||
AV6kJMJ0jmVEBwRhVnYn18+QgiB33hy6CiHbzKXQafElbSbR+7Cg7C4tPpv2hlfh
|
||||
BslV/zZIGwDfi6GrlZqfPyd/r8258fhsmUQa/y/HTYm9QCc1DKXfSPzqpb1yZMLe
|
||||
omOw0IMduE2xn2QsWz2CgdeNO7QPH6qFoM9SJTh3xtI4f7xVb6tLW4vvVpOQb85E
|
||||
LIUg+NWoqBmLBx2wkLttSld9kDJZFTVksZEa7XcvJGY62tIeLAn/0Y1Ytg6Xpm9H
|
||||
oihaNZ7vhsB6iwNWOuHxXtJOpvkMfEDmtSQDoT3ier+HA8/+ofiLUrctZ30uh0da
|
||||
R/AjFDwFD53lbdWmfMpL+ZUHcctSg+3MDaL2AF2eOkxlDf0+zESffncxLLUXnhgR
|
||||
5eSpxg/CclShPY1kkq6D3UTznwLu4UxsjL2YmQS0jefE4VrJOBFxpExMs2z/tMKn
|
||||
yRV8JnDWTAHtw1FmynXz0YClXVHSKlf2h4vTUf92hapwvcz2ExhjkLDFiMMi+/jX
|
||||
+Qlb4iYVUdg5rqvTCTgWoHC4nArbcSSlYrxSQZpjSzUzyJzsSX8=
|
||||
=fYQt
|
||||
-----END PGP SIGNATURE-----
|
@ -0,0 +1,48 @@
|
||||
From: Matthew Ogilvie <mmogilvi+fml@zoho.com>
|
||||
Date: Sat, 3 Jun 2017 17:57:22 -0600
|
||||
Subject: FAQ: list gmail options including oauthbearer and app password
|
||||
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
|
||||
Git-commit: dbeee6a0c0fc5392953f38d6f0dcffdeeb8ae141
|
||||
|
||||
---
|
||||
fetchmail-FAQ.html | 24 +++++++++++++++++++++---
|
||||
1 file changed, 21 insertions(+), 3 deletions(-)
|
||||
|
||||
Index: fetchmail-6.5.1/fetchmail-FAQ.html
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/fetchmail-FAQ.html
|
||||
+++ fetchmail-6.5.1/fetchmail-FAQ.html
|
||||
@@ -1956,12 +1956,28 @@ authentication schemes based on OAuth 2.
|
||||
users to jump through quite a few hoops, and use web browsers for
|
||||
signing in, and software vendors to hand in their software for
|
||||
sometimes paid reviews. Such is not going to happen for fetchmail.
|
||||
+
|
||||
+If this hinders access to your account through fetchmail, you have some
|
||||
+options:</p>
|
||||
+<ul>
|
||||
+ <li>You can generate and use an
|
||||
+ <a href="https://support.google.com/accounts/answer/185833">App Password</a>.
|
||||
+ This is probably best unless you are on a "G-Suite" account and the
|
||||
+ administrator has disabled this option.</li>
|
||||
+ <li>You can use separate tools to generate and renew oauth2 access
|
||||
+ tokens. Then configure fetchmail to use "auth oauthbearer" and use
|
||||
+ a current access token as the password. See comments and --help in
|
||||
+ contrib/fetchmail-oauth2.py from the fetchmail source tree
|
||||
+ for more information. This is derived from Google's
|
||||
+ <a href="https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough">OAuth2DotPyRunThrough</a>,
|
||||
+ associated code, RFC-7628, and RFC-6750.</li>
|
||||
+ <li>You may turn on access for "less secure apps" at
|
||||
+ <a href="https://www.google.com/settings/security/lesssecureapps">https://www.google.com/settings/security/lesssecureapps</a>,
|
||||
+ or see <a href="https://support.google.com/accounts/answer/6010255">https://support.google.com/accounts/answer/6010255</a>.
|
||||
+ But G-suite administrators are more likely to have disabled
|
||||
+ this option than "App Password"s.</li>
|
||||
+</ul>
|
||||
|
||||
-If this hinders access to your account through fetchmail, you may
|
||||
-need to turn on access for "less secure apps", or create an application or service specific password.
|
||||
-
|
||||
-For Google, this - at some point in time - used to live at <a
|
||||
- href="https://myaccount.google.com/lesssecureapps">https://myaccount.google.com/lesssecureapps</a>.<br/>
|
||||
It is disputable whether an application that does not include web
|
||||
browsing capabilities or heavy-weight libraries is "less secure" as
|
||||
Google claims.</p>
|
@ -0,0 +1,616 @@
|
||||
From: Matthew Ogilvie <mmogilvi+fml@zoho.com>
|
||||
Date: Thu, 1 Jun 2017 00:09:02 -0600
|
||||
Subject: add contrib/fetchnmail-oauth2.py token acquisition utility
|
||||
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
|
||||
Git-commit: c82625858682eb2396b6a49da79e403c6f2b018b
|
||||
|
||||
---
|
||||
contrib/README | 6
|
||||
contrib/fetchmail-oauth2.py | 567 ++++++++++++++++++++++++++++++++++++++++++++
|
||||
fetchmail.man | 3
|
||||
3 files changed, 575 insertions(+), 1 deletion(-)
|
||||
create mode 100755 contrib/fetchmail-oauth2.py
|
||||
|
||||
Index: fetchmail-6.5.1/contrib/README
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/contrib/README
|
||||
+++ fetchmail-6.5.1/contrib/README
|
||||
@@ -181,6 +181,12 @@ sendmail 8.11.0 with multidrop.
|
||||
|
||||
Watchdog script to check whether fetchmail is working in daemon mode.
|
||||
|
||||
+### fetchmail-oauth2.py
|
||||
+
|
||||
+Script to obtain oauth2 access tokens that "fetchmail --auth oauthbearer"
|
||||
+expects in place of the password. See --help and comments in the
|
||||
+script, as well as fetchmail --auth documentation.
|
||||
+
|
||||
### mold-remover.py
|
||||
|
||||
A short python script to remove old read mail from a pop3 mailserver.
|
||||
Index: fetchmail-6.5.1/contrib/fetchmail-oauth2.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ fetchmail-6.5.1/contrib/fetchmail-oauth2.py
|
||||
@@ -0,0 +1,567 @@
|
||||
+#!/usr/bin/python
|
||||
+#
|
||||
+# Updates: Copyright 2017 Matthew Ogilvie (mogilvie+fml at zoho.com)
|
||||
+# - Started with https://github.com/google/gmail-oauth2-tools.git
|
||||
+# commit 45c39795044c604ed126205806191a8473c0f671 dated
|
||||
+# 2015-06-09.
|
||||
+# - Add file interaction (--refresh, --auto_refresh,
|
||||
+# --obtain_refresh_token_file and related options).
|
||||
+# - Support both python 2 and 3.
|
||||
+# - Keeping the same license (below).
|
||||
+#
|
||||
+# Copyright 2012 Google Inc.
|
||||
+#
|
||||
+# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
+# you may not use this file except in compliance with the License.
|
||||
+# You may obtain a copy of the License at
|
||||
+#
|
||||
+ # http://www.apache.org/licenses/LICENSE-2.0
|
||||
+#
|
||||
+# Unless required by applicable law or agreed to in writing, software
|
||||
+# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
+# See the License for the specific language governing permissions and
|
||||
+# limitations under the License.
|
||||
+
|
||||
+###############
|
||||
+# POSSIBLE IMPROVEMENTS:
|
||||
+#
|
||||
+# FUTURE: Explicitly track expiration time of access tokens,
|
||||
+# and base --auto_refresh on actual expiration time instead of
|
||||
+# simple age.
|
||||
+# FUTURE: Add a mode that can print the access token by itself to
|
||||
+# stdout, presumably piped into fetchmail or similar (either both
|
||||
+# launched by a wrapper script, this launches fetchmail, or fetchmail
|
||||
+# launches this).
|
||||
+# FUTURE: Mix old and new interfaces (or get rid of old interface):
|
||||
+# Support using a config file to supply some of the details
|
||||
+# for the original google modes of operation (--generate_oauth2_token,
|
||||
+# --generate_oauth2_string, --refresh_token, and --test_*).
|
||||
+# Also support providing sensative data on the command line instead
|
||||
+# of files for the new modes of operation, despite the lack
|
||||
+# of security (process listings, .bash_history files, etc).
|
||||
+# FUTURE: Revise model for how to set permissions on updated files?
|
||||
+# Preserve existing? Somehow allow setting UID/GID? Warn if files
|
||||
+# are accessible by anyone but the current user?
|
||||
+
|
||||
+"""Performs client tasks for testing IMAP OAuth2 authentication.
|
||||
+
|
||||
+This documentation and examples is for gmail. For other providers,
|
||||
+you will likely need to track down appropriate non-default settings
|
||||
+for auth_url, token_url, and scope.
|
||||
+
|
||||
+To use this script, you'll need to have registered with Google as an OAuth
|
||||
+application and obtained an OAuth client ID and client secret.
|
||||
+See https://developers.google.com/identity/protocols/OAuth2 and
|
||||
+https://developers.google.com/identity/sign-in/web/devconsole-project
|
||||
+for instructions on registering and for documentation of the APIs
|
||||
+invoked by this code.
|
||||
+
|
||||
+This script has 2 main modes of operation.
|
||||
+
|
||||
+1. The first mode is used to generate and authorize an OAuth2 token, the
|
||||
+first step in logging in via OAuth2.
|
||||
+
|
||||
+First, after registering your "application" (above) you should setup a
|
||||
+configuration file. Use a text editor to do the command-line equivalent of:
|
||||
+
|
||||
+ sed 's/^ *//' > /path/to/oauth2Config.properties << EOF
|
||||
+ client_id=1038[...].apps.googleusercontent.com
|
||||
+ client_secret=VWFn8LIKAMC-MsjBMhJeOplZ
|
||||
+ refresh_token_file=/home/path/to/refresh_token_file
|
||||
+ access_token_file=/home/path/to/access_token_file
|
||||
+EOF
|
||||
+
|
||||
+ chmod 600 /path/to/oauth2Config.properties
|
||||
+
|
||||
+Then run the following, and repeat any time the refresh token stops
|
||||
+working, such as when you change your password. This is interactive
|
||||
+and requires a web browser to complete:
|
||||
+
|
||||
+ oauth2 -c /path/to/oauth2Config.properties --obtain_refresh_token_file
|
||||
+
|
||||
+The script will converse with Google and generate an oauth request
|
||||
+token, then present you with a URL you should visit in your browser to
|
||||
+authorize the token. Once you get the verification code from the Google
|
||||
+website, enter it into the script, which will then save access and referesh
|
||||
+tokens to the corresponding files for later use.
|
||||
+
|
||||
+Also, you'll usually need to configure fetchmail by
|
||||
+including a section like the following in your .fetchmailrc:
|
||||
+
|
||||
+ poll imap.gmail.com protocol imap
|
||||
+ auth oauthbearer username "USER@gmail.com"
|
||||
+ passwordfile "/home/path/to/access_token_file"
|
||||
+ is LOCALUSER here sslmode wrapped sslcertck
|
||||
+
|
||||
+Alternative for debugging: You can also use the original google
|
||||
+script interface to obtain these tokens without involving files:
|
||||
+
|
||||
+ oauth2 \
|
||||
+ --client_id=1038[...].apps.googleusercontent.com \
|
||||
+ --client_secret=VWFn8LIKAMC-MsjBMhJeOplZ \
|
||||
+ --generate_oauth2_token
|
||||
+
|
||||
+
|
||||
+-----
|
||||
+2. The script will generate new access tokens using a refresh token.
|
||||
+
|
||||
+This uses the same config file setup above.
|
||||
+
|
||||
+ oauth2 -c /path/to/oauth2Config.properties --auto_refresh
|
||||
+ # Or force refresh by using --refresh instead of --auto_refresh.
|
||||
+
|
||||
+ fetchmail -s # or other tools configured to use the access_token_file
|
||||
+ # And/or call something to update outgoing MTA relay configuration,
|
||||
+ # if necessary.
|
||||
+
|
||||
+You may put this sequence in a short shell script,
|
||||
+and configure cron to call it a few times per hour.
|
||||
+
|
||||
+Alternative for debugging: You can also use the original google
|
||||
+script interface to refresh the token without involving files:
|
||||
+
|
||||
+ oauth2 \
|
||||
+ --client_id=1038[...].apps.googleusercontent.com \
|
||||
+ --client_secret=VWFn8LIKAMC-MsjBMhJeOplZ \
|
||||
+ --refresh_token=1/Yzm6MRy4q1xi7Dx2DuWXNgT6s37OrP_DW_IoyTum4YA
|
||||
+
|
||||
+-----
|
||||
+Google's non-file script interface also supports a few other
|
||||
+testing modes; see --help.
|
||||
+"""
|
||||
+
|
||||
+from __future__ import print_function
|
||||
+import base64
|
||||
+import imaplib
|
||||
+import json
|
||||
+from optparse import OptionParser
|
||||
+import smtplib
|
||||
+import sys
|
||||
+import os
|
||||
+import time
|
||||
+
|
||||
+try:
|
||||
+ import urllib.request as urlopen
|
||||
+ import urllib.parse as urlparse
|
||||
+except ImportError:
|
||||
+ import urllib as urlopen
|
||||
+ import urllib as urlparse
|
||||
+
|
||||
+try: input = raw_input
|
||||
+except NameError: pass
|
||||
+
|
||||
+
|
||||
+def SetupOptionParser():
|
||||
+ # Usage message is the module's docstring.
|
||||
+ parser = OptionParser(usage=__doc__)
|
||||
+ parser.add_option('-c', '--config_file',
|
||||
+ default=None,
|
||||
+ help='Configuration file for --refresh '
|
||||
+ 'and --obtain_refresh_token_file.\n'
|
||||
+ 'The file should contain 4 (or more) settings, '
|
||||
+ 'one per line, or they can also be overridden '
|
||||
+ 'by the equivalent options:\n'
|
||||
+ ' client_id=...\n'
|
||||
+ ' client_secret=...\n'
|
||||
+ ' refresh_token_file=/path/to/...\n'
|
||||
+ ' access_token_file=/path/to/...\n'
|
||||
+ ' Also max_age_sec, scope, umask, auth_url, and'
|
||||
+ ' token_url have reasonable defaults for google.')
|
||||
+ parser.add_option('--auto_refresh',
|
||||
+ action='store_const',
|
||||
+ default=None,
|
||||
+ const=1,
|
||||
+ dest='refresh',
|
||||
+ help='Automatically refresh access_token_file, '
|
||||
+ 'if older than max_age_sec from '
|
||||
+ 'required -c /file/ info.');
|
||||
+ parser.add_option('--refresh',
|
||||
+ action='store_const',
|
||||
+ const=2,
|
||||
+ dest='refresh',
|
||||
+ help='Refresh access_token_file '
|
||||
+ 'unconditionally. Requires -c /file/ info.');
|
||||
+ parser.add_option('--obtain_refresh_token_file',
|
||||
+ action='store_true',
|
||||
+ dest='obtain_refresh_token_file',
|
||||
+ default=None,
|
||||
+ help='Update refresh token in file. This is '
|
||||
+ 'interactive, and requires '
|
||||
+ 'a web browser. Also requires -c /file/ info. '
|
||||
+ 'This also saves an initial access_token_file.');
|
||||
+ parser.add_option('--client_id',
|
||||
+ default=None,
|
||||
+ help='Client ID of the application that is authenticating. '
|
||||
+ 'See OAuth2 documentation for details.')
|
||||
+ parser.add_option('--client_secret',
|
||||
+ default=None,
|
||||
+ help='Client secret of the application that is '
|
||||
+ 'authenticating. See OAuth2 documentation for '
|
||||
+ 'details.')
|
||||
+ parser.add_option('--access_token_file',
|
||||
+ default=None,
|
||||
+ help='File name containing OAuth2 access token')
|
||||
+ parser.add_option('--refresh_token_file',
|
||||
+ default=None,
|
||||
+ help='File name containing OAuth2 refresh token')
|
||||
+ parser.add_option('--max_age_sec',
|
||||
+ default=None, # manual default 3000
|
||||
+ help='default max file age for --auto_refresh. '
|
||||
+ 'Defaults to 3000 (10 minutes short of '
|
||||
+ 'normal 3600 sec token expiration).')
|
||||
+ parser.add_option('--umask',
|
||||
+ default=None, # manual default 0077
|
||||
+ help='default umask for --auto_refresh and '
|
||||
+ '--obtain_refresh_token_file. Defaults to 0077.')
|
||||
+ parser.add_option('--scope',
|
||||
+ default=None, # manual default='https://mail.google.com/'
|
||||
+ help='scope for the access token. Multiple scopes can be '
|
||||
+ 'listed separated by spaces with the whole argument '
|
||||
+ 'quoted. Defaults to https://mail.google.com/')
|
||||
+ parser.add_option('--auth_url',
|
||||
+ default=None, # manual default...
|
||||
+ help='Permission URL for --obtain_refresh_token_file. '
|
||||
+ 'Defaults to https://accounts.google.com/o/oauth2/auth.')
|
||||
+ parser.add_option('--token_url',
|
||||
+ default=None, # manual default...
|
||||
+ help='Token URL for --obtain_refresh_token_file,'
|
||||
+ ' and --refresh. '
|
||||
+ 'Defaults to https://accounts.google.com/o/oauth2/token.')
|
||||
+ parser.add_option('--generate_oauth2_token',
|
||||
+ action='store_true',
|
||||
+ dest='generate_oauth2_token',
|
||||
+ help='(OLD/testing) generates an OAuth2 token for testing.'
|
||||
+ ' Ignores all files.')
|
||||
+ parser.add_option('--refresh_token',
|
||||
+ default=None,
|
||||
+ help='(OLD/testing) Generate a new access token using'
|
||||
+ ' this OAuth2 refresh token. Ignores all files.')
|
||||
+ parser.add_option('--user',
|
||||
+ default=None,
|
||||
+ help='(OLD/testing) email address of user whose account'
|
||||
+ ' is being accessed')
|
||||
+ parser.add_option('--access_token',
|
||||
+ default=None,
|
||||
+ help='(OLD/testing) OAuth2 access token.')
|
||||
+ parser.add_option('--generate_oauth2_string',
|
||||
+ action='store_true',
|
||||
+ dest='generate_oauth2_string',
|
||||
+ help='(OLD/testing) generates an initial client response'
|
||||
+ ' string for OAuth2. Ignores all files.')
|
||||
+ parser.add_option('--test_imap_authentication',
|
||||
+ action='store_true',
|
||||
+ dest='test_imap_authentication',
|
||||
+ help='(OLD/testing) attempts to authenticate to IMAP. '
|
||||
+ 'Ignores all files.')
|
||||
+ parser.add_option('--test_smtp_authentication',
|
||||
+ action='store_true',
|
||||
+ dest='test_smtp_authentication',
|
||||
+ help='(OLD/testing) attempts to authenticate to SMTP. '
|
||||
+ 'Ignores all files.')
|
||||
+ return parser
|
||||
+
|
||||
+
|
||||
+# Hardcoded dummy redirect URI for non-web apps.
|
||||
+REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
|
||||
+
|
||||
+
|
||||
+def UrlEscape(text):
|
||||
+ # See OAUTH 5.1 for a definition of which characters need to be escaped.
|
||||
+ return urlparse.quote(text, safe='~-._')
|
||||
+
|
||||
+
|
||||
+def UrlUnescape(text):
|
||||
+ # See OAUTH 5.1 for a definition of which characters need to be escaped.
|
||||
+ return urlparse.unquote(text)
|
||||
+
|
||||
+
|
||||
+def FormatUrlParams(params):
|
||||
+ """Formats parameters into a URL query string.
|
||||
+
|
||||
+ Args:
|
||||
+ params: A key-value map.
|
||||
+
|
||||
+ Returns:
|
||||
+ A URL query string version of the given parameters.
|
||||
+ """
|
||||
+ param_fragments = []
|
||||
+ for param in sorted(params.items(), key=lambda x: x[0]):
|
||||
+ param_fragments.append('%s=%s' % (param[0], UrlEscape(param[1])))
|
||||
+ return '&'.join(param_fragments)
|
||||
+
|
||||
+
|
||||
+def GeneratePermissionUrl(client_id, scope, auth_url):
|
||||
+ """Generates the URL for authorizing access.
|
||||
+
|
||||
+ This uses the "OAuth2 for Installed Applications" flow described at
|
||||
+ https://developers.google.com/accounts/docs/OAuth2InstalledApp
|
||||
+
|
||||
+ Args:
|
||||
+ client_id: Client ID obtained by registering your app.
|
||||
+ scope: scope for access token, e.g. 'https://mail.google.com'
|
||||
+ Returns:
|
||||
+ A URL that the user should visit in their browser.
|
||||
+ """
|
||||
+ if not scope:
|
||||
+ scope = 'https://mail.google.com/'
|
||||
+ if not auth_url:
|
||||
+ auth_url = 'https://accounts.google.com/o/oauth2/auth'
|
||||
+ params = {}
|
||||
+ params['client_id'] = client_id
|
||||
+ params['redirect_uri'] = REDIRECT_URI
|
||||
+ params['scope'] = scope
|
||||
+ params['response_type'] = 'code'
|
||||
+ return '%s?%s' % (auth_url, FormatUrlParams(params))
|
||||
+
|
||||
+
|
||||
+def AuthorizeTokens(client_id, client_secret, authorization_code, token_url):
|
||||
+ """Obtains OAuth access token and refresh token.
|
||||
+
|
||||
+ This uses the application portion of the "OAuth2 for Installed Applications"
|
||||
+ flow at https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse
|
||||
+
|
||||
+ Args:
|
||||
+ client_id: Client ID obtained by registering your app.
|
||||
+ client_secret: Client secret obtained by registering your app.
|
||||
+ authorization_code: code generated by Google Accounts after user grants
|
||||
+ permission.
|
||||
+ Returns:
|
||||
+ The decoded response from the Google Accounts server, as a dict. Expected
|
||||
+ fields include 'access_token', 'expires_in', and 'refresh_token'.
|
||||
+ """
|
||||
+ params = {}
|
||||
+ params['client_id'] = client_id
|
||||
+ params['client_secret'] = client_secret
|
||||
+ params['code'] = authorization_code
|
||||
+ params['redirect_uri'] = REDIRECT_URI
|
||||
+ params['grant_type'] = 'authorization_code'
|
||||
+ if not token_url:
|
||||
+ token_url = 'https://accounts.google.com/o/oauth2/token'
|
||||
+
|
||||
+ response = urlopen.urlopen(token_url,
|
||||
+ urlparse.urlencode(params).encode('ascii')).read()
|
||||
+ return json.loads(response.decode("utf-8"))
|
||||
+
|
||||
+
|
||||
+def RefreshToken(client_id, client_secret, refresh_token, token_url):
|
||||
+ """Obtains a new token given a refresh token.
|
||||
+
|
||||
+ See https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh
|
||||
+
|
||||
+ Args:
|
||||
+ client_id: Client ID obtained by registering your app.
|
||||
+ client_secret: Client secret obtained by registering your app.
|
||||
+ refresh_token: A previously-obtained refresh token.
|
||||
+ Returns:
|
||||
+ The decoded response from the Google Accounts server, as a dict. Expected
|
||||
+ fields include 'access_token', 'expires_in', and 'refresh_token'.
|
||||
+ """
|
||||
+ params = {}
|
||||
+ params['client_id'] = client_id
|
||||
+ params['client_secret'] = client_secret
|
||||
+ params['refresh_token'] = refresh_token
|
||||
+ params['grant_type'] = 'refresh_token'
|
||||
+ if not token_url:
|
||||
+ token_url = 'https://accounts.google.com/o/oauth2/token'
|
||||
+
|
||||
+ response = urlopen.urlopen(token_url,
|
||||
+ urlparse.urlencode(params).encode('ascii')).read()
|
||||
+ return json.loads(response.decode("utf-8"))
|
||||
+
|
||||
+
|
||||
+def GenerateOAuth2String(username, access_token, base64_encode=True):
|
||||
+ """Generates an IMAP OAuth2 authentication string.
|
||||
+
|
||||
+ See https://developers.google.com/google-apps/gmail/oauth2_overview
|
||||
+
|
||||
+ Args:
|
||||
+ username: the username (email address) of the account to authenticate
|
||||
+ access_token: An OAuth2 access token.
|
||||
+ base64_encode: Whether to base64-encode the output.
|
||||
+
|
||||
+ Returns:
|
||||
+ The SASL argument for the OAuth2 mechanism.
|
||||
+ """
|
||||
+ auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token)
|
||||
+ if base64_encode:
|
||||
+ auth_string = base64.b64encode(auth_string)
|
||||
+ return auth_string
|
||||
+
|
||||
+
|
||||
+def TestImapAuthentication(user, auth_string):
|
||||
+ """Authenticates to IMAP with the given auth_string.
|
||||
+
|
||||
+ Prints a debug trace of the attempted IMAP connection.
|
||||
+
|
||||
+ Args:
|
||||
+ user: The Gmail username (full email address)
|
||||
+ auth_string: A valid OAuth2 string, as returned by GenerateOAuth2String.
|
||||
+ Must not be base64-encoded, since imaplib does its own base64-encoding.
|
||||
+ """
|
||||
+ print()
|
||||
+ imap_conn = imaplib.IMAP4_SSL('imap.gmail.com')
|
||||
+ imap_conn.debug = 4
|
||||
+ imap_conn.authenticate('XOAUTH2', lambda x: auth_string)
|
||||
+ imap_conn.select('INBOX')
|
||||
+
|
||||
+
|
||||
+def TestSmtpAuthentication(user, auth_string):
|
||||
+ """Authenticates to SMTP with the given auth_string.
|
||||
+
|
||||
+ Args:
|
||||
+ user: The Gmail username (full email address)
|
||||
+ auth_string: A valid OAuth2 string, not base64-encoded, as returned by
|
||||
+ GenerateOAuth2String.
|
||||
+ """
|
||||
+ print()
|
||||
+ smtp_conn = smtplib.SMTP('smtp.gmail.com', 587)
|
||||
+ smtp_conn.set_debuglevel(True)
|
||||
+ smtp_conn.ehlo('test')
|
||||
+ smtp_conn.starttls()
|
||||
+ smtp_conn.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(auth_string))
|
||||
+
|
||||
+
|
||||
+def RequireOptions(options, *args):
|
||||
+ missing = [arg for arg in args if getattr(options, arg) is None]
|
||||
+ if missing:
|
||||
+ print('Missing options: %s' % ' '.join(missing))
|
||||
+ sys.exit(-1)
|
||||
+
|
||||
+def parseConfigFile(options):
|
||||
+ if options.config_file:
|
||||
+ cfg = dict(line.strip().split('=',1) for line in open(options.config_file))
|
||||
+ else:
|
||||
+ cfg = { }
|
||||
+ # defaults:
|
||||
+ if not 'scope' in cfg:
|
||||
+ cfg['scope'] = 'https://mail.google.com/'
|
||||
+ if not 'max_age_sec' in cfg:
|
||||
+ cfg['max_age_sec'] = '3000'
|
||||
+ if not 'umask' in cfg:
|
||||
+ cfg['umask'] = '0077'
|
||||
+ if not 'auth_url' in cfg:
|
||||
+ cfg['auth_url'] = 'https://accounts.google.com/o/oauth2/auth'
|
||||
+ if not 'token_url' in cfg:
|
||||
+ cfg['token_url'] = 'https://accounts.google.com/o/oauth2/token'
|
||||
+ # overrides (from command line):
|
||||
+ for arg in [ 'scope', 'client_id', 'client_secret', 'umask',
|
||||
+ 'max_age_sec', 'access_token_file', 'refresh_token_file',
|
||||
+ 'auth_url', 'token_url' ]:
|
||||
+ if getattr(options,arg):
|
||||
+ cfg[arg] = getattr(options,arg)
|
||||
+ return cfg
|
||||
+
|
||||
+def requireConfig(cfg, *args):
|
||||
+ missing = [arg for arg in args if not arg in cfg]
|
||||
+ if missing:
|
||||
+ print('Missing options: %s' % ' '.join(missing))
|
||||
+ sys.exit(-1)
|
||||
+
|
||||
+
|
||||
+def main(argv):
|
||||
+ options_parser = SetupOptionParser()
|
||||
+ (options, args) = options_parser.parse_args()
|
||||
+ if options.refresh:
|
||||
+ cfg = parseConfigFile(options)
|
||||
+ requireConfig(cfg, 'refresh_token_file', 'access_token_file',
|
||||
+ 'client_id', 'client_secret', 'umask')
|
||||
+ st = os.stat(cfg['access_token_file'])
|
||||
+ if options.refresh < 2:
|
||||
+ requireConfig(cfg, 'max_age_sec')
|
||||
+ if time.time()-st.st_mtime < int(cfg['max_age_sec']):
|
||||
+ return
|
||||
+ with open(cfg['refresh_token_file'],"r") as f:
|
||||
+ reftok = f.readline().rstrip()
|
||||
+ if len(reftok) == 0:
|
||||
+ print('refresh token is empty')
|
||||
+ sys.exit(-1)
|
||||
+ response = RefreshToken(cfg['client_id'],cfg['client_secret'],reftok,
|
||||
+ cfg['token_url'])
|
||||
+ newTok = response['access_token']
|
||||
+ if len(newTok) == 0:
|
||||
+ print('failed to obtain access token: it is empty')
|
||||
+ sys.exit(-1)
|
||||
+ savedUmask = os.umask(int(cfg['umask'],8))
|
||||
+ try:
|
||||
+ with open(cfg['access_token_file']+".tmp","w") as f:
|
||||
+ f.write(newTok)
|
||||
+ f.write('\n')
|
||||
+ os.rename(cfg['access_token_file']+".tmp",cfg['access_token_file'])
|
||||
+ finally:
|
||||
+ os.umask(savedUmask)
|
||||
+ elif options.obtain_refresh_token_file:
|
||||
+ cfg = parseConfigFile(options)
|
||||
+ requireConfig(cfg, 'refresh_token_file', 'access_token_file',
|
||||
+ 'client_id', 'client_secret', 'umask')
|
||||
+ print('To authorize token, visit this url and follow the directions:')
|
||||
+ print(' %s' % GeneratePermissionUrl(cfg['client_id'], cfg['scope'],
|
||||
+ cfg['auth_url']))
|
||||
+ authorization_code = input('Enter verification code: ')
|
||||
+ response = AuthorizeTokens(cfg['client_id'], cfg['client_secret'],
|
||||
+ authorization_code, cfg['token_url'])
|
||||
+ newRefTok = response['refresh_token']
|
||||
+ if len(newRefTok) == 0:
|
||||
+ print('failed to obtain refresh token: it is empty')
|
||||
+ sys.exit(-1)
|
||||
+ newTok = response['access_token']
|
||||
+ if len(newTok) == 0:
|
||||
+ print('failed to obtain corresponding access token: it is empty')
|
||||
+ sys.exit(-1)
|
||||
+ savedUmask = os.umask(int(cfg['umask'],8))
|
||||
+ try:
|
||||
+ with open(cfg['refresh_token_file']+".tmp","w") as f:
|
||||
+ f.write(newRefTok)
|
||||
+ f.write('\n')
|
||||
+ os.rename(cfg['refresh_token_file']+".tmp",cfg['refresh_token_file'])
|
||||
+ with open(cfg['access_token_file']+".tmp","w") as f:
|
||||
+ f.write(newTok)
|
||||
+ f.write('\n')
|
||||
+ print("Refresh token saved to '%s'" % cfg['refresh_token_file'])
|
||||
+ print("Initial access token saved to '%s'" % cfg['access_token_file'])
|
||||
+ print('Access Token Expiration Seconds: %s' % response['expires_in'])
|
||||
+ os.rename(cfg['access_token_file']+".tmp",cfg['access_token_file'])
|
||||
+ finally:
|
||||
+ os.umask(savedUmask)
|
||||
+
|
||||
+ ##### (OLD/testing options)
|
||||
+
|
||||
+ elif options.refresh_token:
|
||||
+ RequireOptions(options, 'client_id', 'client_secret')
|
||||
+ response = RefreshToken(options.client_id, options.client_secret,
|
||||
+ options.refresh_token, options.token_url)
|
||||
+ print('Access Token: %s' % response['access_token'])
|
||||
+ print('Access Token Expiration Seconds: %s' % response['expires_in'])
|
||||
+ elif options.generate_oauth2_string:
|
||||
+ RequireOptions(options, 'user', 'access_token')
|
||||
+ print ('OAuth2 argument:\n' +
|
||||
+ GenerateOAuth2String(options.user, options.access_token))
|
||||
+ elif options.generate_oauth2_token:
|
||||
+ RequireOptions(options, 'client_id', 'client_secret')
|
||||
+ print('To authorize token, visit this url and follow the directions:')
|
||||
+ print(' %s' % GeneratePermissionUrl(options.client_id, options.scope,
|
||||
+ options.auth_url))
|
||||
+ authorization_code = input('Enter verification code: ')
|
||||
+ response = AuthorizeTokens(options.client_id, options.client_secret,
|
||||
+ authorization_code, options.token_url)
|
||||
+ print('Refresh Token: %s' % response['refresh_token'])
|
||||
+ print('Access Token: %s' % response['access_token'])
|
||||
+ print('Access Token Expiration Seconds: %s' % response['expires_in'])
|
||||
+ elif options.test_imap_authentication:
|
||||
+ RequireOptions(options, 'user', 'access_token')
|
||||
+ TestImapAuthentication(options.user,
|
||||
+ GenerateOAuth2String(options.user, options.access_token,
|
||||
+ base64_encode=False))
|
||||
+ elif options.test_smtp_authentication:
|
||||
+ RequireOptions(options, 'user', 'access_token')
|
||||
+ TestSmtpAuthentication(options.user,
|
||||
+ GenerateOAuth2String(options.user, options.access_token,
|
||||
+ base64_encode=False))
|
||||
+ else:
|
||||
+ options_parser.print_help()
|
||||
+ print('Nothing to do, exiting.')
|
||||
+ return
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ main(sys.argv)
|
||||
Index: fetchmail-6.5.1/fetchmail.man
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/fetchmail.man
|
||||
+++ fetchmail-6.5.1/fetchmail.man
|
||||
@@ -1181,7 +1181,8 @@ 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, so the
|
||||
"passwordfile", "passwordfd", or "pwmd_*" options may be useful. See the
|
||||
-oauth2.py script from
|
||||
+contrib/fetchmail-oauth2.py script from the fetchmail source code, which
|
||||
+was derived from code associated with
|
||||
.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
|
300
fetchmail-add-imap-oauthbearer-support.patch
Normal file
300
fetchmail-add-imap-oauthbearer-support.patch
Normal file
@ -0,0 +1,300 @@
|
||||
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; }
|
314
fetchmail-add-passwordfile-and-passwordfd-options.patch
Normal file
314
fetchmail-add-passwordfile-and-passwordfd-options.patch
Normal file
@ -0,0 +1,314 @@
|
||||
From: Matthew Ogilvie <mmogilvi+fml@zoho.com>
|
||||
Date: Sun, 28 May 2017 00:01:02 -0600
|
||||
Subject: add passwordfile and passwordfd options
|
||||
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
|
||||
Git-commit: cdd7182f65734c97723ba5f282040e08d830e650
|
||||
|
||||
---
|
||||
fetchmail.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
|
||||
fetchmail.h | 2 +
|
||||
fetchmail.man | 40 +++++++++++++++++++++++++++-
|
||||
options.c | 16 +++++++++++
|
||||
rcfile_l.l | 2 +
|
||||
rcfile_y.y | 6 ++++
|
||||
6 files changed, 145 insertions(+), 3 deletions(-)
|
||||
|
||||
Index: fetchmail-6.5.1/fetchmail.c
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/fetchmail.c
|
||||
+++ fetchmail-6.5.1/fetchmail.c
|
||||
@@ -471,7 +471,7 @@ int main(int argc, char **argv)
|
||||
/* Server won't care what the password is, but there
|
||||
must be some non-null string here. */
|
||||
ctl->password = ctl->remotename;
|
||||
- else
|
||||
+ else if (!ctl->passwordfile && ctl->passwordfd==-1)
|
||||
{
|
||||
const netrc_entry *p;
|
||||
|
||||
@@ -649,8 +649,81 @@ int main(int argc, char **argv)
|
||||
if (ctl->active && !(implicitmode && ctl->server.skip)
|
||||
&& !NO_PASSWORD(ctl) && !ctl->password)
|
||||
{
|
||||
- if (!isatty(0))
|
||||
+ if (ctl->passwordfd != -1)
|
||||
{
|
||||
+ char msg[PASSWORDLEN+1];
|
||||
+ char *mi;
|
||||
+
|
||||
+ /* Read one character at a time to avoid reading too
|
||||
+ * much if more than one password sent in through this FD
|
||||
+ * (although that would be a questionable practice).
|
||||
+ */
|
||||
+ for (mi = msg; mi<msg+sizeof(msg)-1; ++mi) {
|
||||
+ int res = read(ctl->passwordfd, mi, 1);
|
||||
+ if(res == -1) {
|
||||
+ int saveErrno = errno;
|
||||
+ fprintf(stderr,
|
||||
+ GT_("fetchmail: unable to read password from fd=%d: %s\n"),
|
||||
+ ctl->passwordfd,
|
||||
+ strerror(saveErrno));
|
||||
+ memset(msg, 0x55, mi-msg);
|
||||
+ return PS_AUTHFAIL;
|
||||
+ }
|
||||
+ if (res == 0 || *mi == '\n')
|
||||
+ break;
|
||||
+ }
|
||||
+ *mi = '\0';
|
||||
+ if (mi == msg) {
|
||||
+ fprintf(stderr,
|
||||
+ GT_("fetchmail: empty password read from fd=%d\n"),
|
||||
+ ctl->passwordfd);
|
||||
+ return PS_AUTHFAIL;
|
||||
+ }
|
||||
+
|
||||
+ ctl->password = xstrdup(msg);
|
||||
+ memset(msg, 0x55, mi-msg);
|
||||
+ } else if (ctl->passwordfile) {
|
||||
+ int fd = open(ctl->passwordfile, O_RDONLY);
|
||||
+ char msg[PASSWORDLEN+1];
|
||||
+ char *newline;
|
||||
+ int res;
|
||||
+
|
||||
+ if (fd == -1) {
|
||||
+ int saveErrno = errno;
|
||||
+ fprintf(stderr,
|
||||
+ GT_("fetchmail: unable to open %s: %s\n"),
|
||||
+ ctl->passwordfile,
|
||||
+ strerror(saveErrno));
|
||||
+ return PS_AUTHFAIL;
|
||||
+ }
|
||||
+
|
||||
+ res = read(fd, msg, sizeof(msg)-1);
|
||||
+ if (res == -1 || close(fd) == -1) {
|
||||
+ int saveErrno = errno;
|
||||
+ fprintf(stderr,
|
||||
+ GT_("fetchmail: error reading %s: %s\n"),
|
||||
+ ctl->passwordfile,
|
||||
+ strerror(saveErrno));
|
||||
+ return PS_AUTHFAIL;
|
||||
+ }
|
||||
+ msg[res] = '\0';
|
||||
+
|
||||
+ newline = memchr(msg, '\n', res);
|
||||
+ if (newline != NULL) {
|
||||
+ *newline = '\0';
|
||||
+ }
|
||||
+
|
||||
+ if (strlen(msg) == 0) {
|
||||
+ fprintf(stderr,
|
||||
+ GT_("fetchmail: empty password read from %s\n"),
|
||||
+ ctl->passwordfile);
|
||||
+ memset(msg, 0x55, res);
|
||||
+ return PS_AUTHFAIL;
|
||||
+ }
|
||||
+
|
||||
+ ctl->password = xstrdup(msg);
|
||||
+ memset(msg, 0x55, res);
|
||||
+ } else if (!isatty(0)) {
|
||||
fprintf(stderr,
|
||||
GT_("fetchmail: can't find a password for %s@%s.\n"),
|
||||
ctl->remotename, ctl->server.pollname);
|
||||
@@ -1046,6 +1119,10 @@ static void optmerge(struct query *h2, s
|
||||
FLAG_MERGE(wildcard);
|
||||
STRING_MERGE(remotename);
|
||||
STRING_MERGE(password);
|
||||
+ FLAG_MERGE(passwordfile);
|
||||
+ if (force ? h1->passwordfd!=-1 : h2->passwordfd==-1) {
|
||||
+ h2->passwordfd = h1->passwordfd;
|
||||
+ }
|
||||
STRING_MERGE(mda);
|
||||
STRING_MERGE(bsmtp);
|
||||
FLAG_MERGE(listener);
|
||||
@@ -1112,6 +1189,7 @@ static int load_params(int argc, char **
|
||||
def_opts.smtp_socket = -1;
|
||||
def_opts.smtpaddress = (char *)0;
|
||||
def_opts.smtpname = (char *)0;
|
||||
+ def_opts.passwordfd = -1;
|
||||
def_opts.server.protocol = P_AUTO;
|
||||
def_opts.server.timeout = CLIENT_TIMEOUT;
|
||||
def_opts.server.idle_timeout = CLIENT_IDLE_TIMEOUT;
|
||||
Index: fetchmail-6.5.1/fetchmail.h
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/fetchmail.h
|
||||
+++ fetchmail-6.5.1/fetchmail.h
|
||||
@@ -312,6 +312,8 @@ struct query
|
||||
int wildcard; /* should unmatched names be passed through */
|
||||
char *remotename; /* remote login name to use */
|
||||
char *password; /* remote password to use */
|
||||
+ char *passwordfile; /* filename; first line contains password */
|
||||
+ int passwordfd; /* fileno that password will be piped into */
|
||||
struct idlist *mailboxes; /* list of mailboxes to check */
|
||||
|
||||
/* per-forwarding-target data */
|
||||
Index: fetchmail-6.5.1/fetchmail.man
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/fetchmail.man
|
||||
+++ fetchmail-6.5.1/fetchmail.man
|
||||
@@ -1056,6 +1056,37 @@ The default is your login name on the cl
|
||||
\fBfetchmail\fP.
|
||||
See USER AUTHENTICATION below for a complete description.
|
||||
.TP
|
||||
+.B \-\-passwordfile <filename>
|
||||
+(Keyword: passwordfile)
|
||||
+.br
|
||||
+Specifies a file name from which to read the first line to use as the password.
|
||||
+Useful if something changes the password/token often without regenerating a
|
||||
+long fetchmailrc file, such as with typical xoauth2 authentication tokens.
|
||||
+Protect the file with appropriate permissions to avoid leaking your password.
|
||||
+Fetchmail might not re-read the file in daemon mode (-d) unless the
|
||||
+fetchmailrc file also changes, so it might make sense to run it in
|
||||
+non-daemon mode from some other background process (cron and/or whatever
|
||||
+updates the password).
|
||||
+.TP
|
||||
+.B \-\-passwordfd <integer>
|
||||
+(Keyword: passwordfd)
|
||||
+.br
|
||||
+Specifies a file descriptor number inherited from the calling process,
|
||||
+from which fetchmail should read one line to use as the password.
|
||||
+The descriptor will usually be the receiving end of a pipe (equivalent
|
||||
+to "something | fetchmail \-\-passwordfd 5 5<\&0"),
|
||||
+although it could also be a redirected input file
|
||||
+(equivalent to "fetchmail \-\-passwordfd 5 5</path/to/file").
|
||||
+Useful if something wants to manage password ownership more securely
|
||||
+than files, or if the password/token changes often,
|
||||
+such as with typical xoauth2 authentication tokens. Normal interactive
|
||||
+mode passwords requires that standard input is a terminal and disables
|
||||
+echo, but passwordfd does not care. Do not do something
|
||||
+like "echo 'password' | fetchmail ...", since echo's arguments are
|
||||
+likely to (briefly) be publicly visible in process listings.
|
||||
+This probably doesn't interact well with deamon mode: when will it
|
||||
+re-read a new password?
|
||||
+.TP
|
||||
.B \-I <specification> | \-\-interface <specification>
|
||||
(Keyword: interface)
|
||||
.br
|
||||
@@ -1148,7 +1179,8 @@ setting also allows the non-standard "xo
|
||||
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
|
||||
+and new ones need to be generated from renewal tokens, so the
|
||||
+"passwordfile", "passwordfd", or "pwmd_*" options may be useful. 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"
|
||||
@@ -2100,6 +2132,12 @@ T}
|
||||
pass[word] \& \& T{
|
||||
Specify remote account password
|
||||
T}
|
||||
+passwordfile \-\-... \& T{
|
||||
+File name with password in first line.
|
||||
+T}
|
||||
+passwordfd \-\-... \& T{
|
||||
+Inherited file descriptor from which to read one line for the password.
|
||||
+T}
|
||||
ssl \& \& T{
|
||||
Connect to server over the specified base protocol using SSL encryption
|
||||
T}
|
||||
Index: fetchmail-6.5.1/options.c
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/options.c
|
||||
+++ fetchmail-6.5.1/options.c
|
||||
@@ -29,6 +29,8 @@ enum {
|
||||
LA_POSTMASTER,
|
||||
LA_NOBOUNCE,
|
||||
LA_AUTH,
|
||||
+ LA_PASSWORDFILE,
|
||||
+ LA_PASSWORDFD,
|
||||
LA_FETCHDOMAINS,
|
||||
LA_BSMTP,
|
||||
LA_LMTP,
|
||||
@@ -98,6 +100,8 @@ static const struct option longoptions[]
|
||||
{"port", required_argument, (int *) 0, 'P' },
|
||||
{"service", required_argument, (int *) 0, 'P' },
|
||||
{"auth", required_argument, (int *) 0, LA_AUTH},
|
||||
+ {"passwordfile", required_argument, (int *) 0, LA_PASSWORDFILE },
|
||||
+ {"passwordfd", required_argument, (int *) 0, LA_PASSWORDFD },
|
||||
{"timeout", required_argument, (int *) 0, 't' },
|
||||
{"envelope", required_argument, (int *) 0, 'E' },
|
||||
{"qvirtual", required_argument, (int *) 0, 'Q' },
|
||||
@@ -231,6 +235,7 @@ int parsecmdline (int argc /** argument
|
||||
|
||||
memset(ctl, '\0', sizeof(struct query)); /* start clean */
|
||||
ctl->smtp_socket = -1;
|
||||
+ ctl->passwordfd = -1;
|
||||
|
||||
while (!errflag &&
|
||||
(c = getopt_long(argc,argv,shortoptions,
|
||||
@@ -402,6 +407,17 @@ int parsecmdline (int argc /** argument
|
||||
errflag++;
|
||||
}
|
||||
break;
|
||||
+ case LA_PASSWORDFILE:
|
||||
+ ctl->passwordfile = optarg;
|
||||
+ break;
|
||||
+ case LA_PASSWORDFD:
|
||||
+ ctl->passwordfd = xatoi(optarg, &errflag);
|
||||
+ if (ctl->passwordfd < 0) {
|
||||
+ fprintf(stderr,GT_("Invalid file descriptor %d\n"),
|
||||
+ ctl->passwordfd);
|
||||
+ errflag++;
|
||||
+ }
|
||||
+ break;
|
||||
case 't':
|
||||
ctl->server.timeout = xatoi(optarg, &errflag);
|
||||
if (ctl->server.timeout == 0)
|
||||
Index: fetchmail-6.5.1/rcfile_l.l
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/rcfile_l.l
|
||||
+++ fetchmail-6.5.1/rcfile_l.l
|
||||
@@ -116,6 +116,8 @@ accept { return ACCEPT; }
|
||||
reject { return REJECT_; }
|
||||
|
||||
user(name)? {SETSTATE(NAME); return USERNAME; }
|
||||
+passwordfile { return PASSWORDFILE; }
|
||||
+passwordfd { return PASSWORDFD; }
|
||||
<INITIAL,NAME>pass(word)? {SETSTATE(NAME); return PASSWORD; }
|
||||
folder(s)? { return FOLDER; }
|
||||
smtp(host)? { return SMTPHOST; }
|
||||
Index: fetchmail-6.5.1/rcfile_y.y
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/rcfile_y.y
|
||||
+++ fetchmail-6.5.1/rcfile_y.y
|
||||
@@ -62,6 +62,7 @@ void yyerror (const char *s)
|
||||
%token DEFAULTS POLL SKIP VIA AKA LOCALDOMAINS PROTOCOL
|
||||
%token AUTHENTICATE TIMEOUT IDLETIMEOUT KPOP SDPS ENVELOPE QVIRTUAL
|
||||
%token USERNAME PASSWORD FOLDER SMTPHOST FETCHDOMAINS MDA BSMTP LMTP
|
||||
+%token PASSWORDFILE PASSWORDFD
|
||||
%token SMTPADDRESS SMTPNAME SPAMRESPONSE PRECONNECT POSTCONNECT LIMIT WARNINGS
|
||||
%token INTERFACE MONITOR PLUGIN PLUGOUT
|
||||
%token IS HERE THERE TO MAP
|
||||
@@ -314,6 +315,8 @@ user_option : TO mapping_list HERE
|
||||
|
||||
| IS STRING THERE {current.remotename = $2;}
|
||||
| PASSWORD STRING {current.password = $2;}
|
||||
+ | PASSWORDFILE STRING {current.passwordfile = $2;}
|
||||
+ | PASSWORDFD NUMBER {current.passwordfd = NUM_VALUE_IN($2);}
|
||||
| FOLDER folder_list
|
||||
| SMTPHOST smtp_list
|
||||
| FETCHDOMAINS fetch_list
|
||||
@@ -495,6 +498,7 @@ static void reset_server(const char *nam
|
||||
trailer = FALSE;
|
||||
memset(¤t,'\0',sizeof(current));
|
||||
current.smtp_socket = -1;
|
||||
+ current.passwordfd = -1;
|
||||
current.server.pollname = xstrdup(name);
|
||||
current.server.skip = skip;
|
||||
}
|
||||
@@ -515,6 +519,7 @@ static void user_reset(void)
|
||||
|
||||
memset(¤t, '\0', sizeof(current));
|
||||
current.smtp_socket = -1;
|
||||
+ current.passwordfd = -1;
|
||||
|
||||
current.server = save;
|
||||
}
|
||||
@@ -535,6 +540,7 @@ struct query *hostalloc(struct query *in
|
||||
{
|
||||
memset(node, '\0', sizeof(struct query));
|
||||
node->smtp_socket = -1;
|
||||
+ node->passwordfd = -1;
|
||||
}
|
||||
|
||||
/* append to end of list */
|
41
fetchmail-add-query_to64_outsize-utility-function.patch
Normal file
41
fetchmail-add-query_to64_outsize-utility-function.patch
Normal file
@ -0,0 +1,41 @@
|
||||
From: Matthew Ogilvie <mmogilvi+fml@zoho.com>
|
||||
Date: Fri, 21 Dec 2018 09:00:46 -0700
|
||||
Subject: add query_to64_outsize() utility function
|
||||
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
|
||||
Git-commit: cc6e146d516140df800da68976eb7c0aa1cef7c0
|
||||
|
||||
---
|
||||
base64.c | 7 +++++++
|
||||
fetchmail.h | 1 +
|
||||
2 files changed, 8 insertions(+)
|
||||
|
||||
Index: fetchmail-6.5.1/base64.c
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/base64.c
|
||||
+++ fetchmail-6.5.1/base64.c
|
||||
@@ -66,6 +66,13 @@ fail:
|
||||
return rc;
|
||||
}
|
||||
|
||||
+size_t query_to64_outsize(size_t inlen)
|
||||
+/* Returns how much space needs to be allocated to receive the output from
|
||||
+ * to64frombits(), including the '\0' terminator. */
|
||||
+{
|
||||
+ return ((inlen+2)/3)*4+1;
|
||||
+}
|
||||
+
|
||||
int from64tobits(void *out_, const char *in, int maxlen)
|
||||
/* base 64 to raw bytes in quasi-big-endian order, returning count of bytes */
|
||||
/* maxlen limits output buffer size, set to zero to ignore */
|
||||
Index: fetchmail-6.5.1/fetchmail.h
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/fetchmail.h
|
||||
+++ fetchmail-6.5.1/fetchmail.h
|
||||
@@ -611,6 +611,7 @@ int prc_filecheck(const char *, const fl
|
||||
/* base64.c */
|
||||
unsigned len64frombits(unsigned inlen); /** calculate length needed to encode inlen octets. warnings: 1. caller needs to add 1 for a trailing \0 byte himself. 2. returns 0 for inlen 0! */
|
||||
int to64frombits(char *, const void *, int inlen, size_t outlen);
|
||||
+size_t query_to64_outsize(size_t inlen);
|
||||
int from64tobits(void *, const char *, int mxoutlen);
|
||||
|
||||
/* unmime.c */
|
160
fetchmail-add-readme-oauth2-issue-27.patch
Normal file
160
fetchmail-add-readme-oauth2-issue-27.patch
Normal file
@ -0,0 +1,160 @@
|
||||
From: William Bader <william@newspapersystems.com>
|
||||
Date: Sun, 31 Jan 2021 06:42:46 +0000
|
||||
Subject: Add README.OAUTH2 issue #27
|
||||
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
|
||||
Git-commit: d52ba9652c9207358e0b9acc11403688f6f16b9e
|
||||
|
||||
---
|
||||
README.OAUTH2 | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 147 insertions(+)
|
||||
|
||||
--- /dev/null
|
||||
+++ b/README.OAUTH2
|
||||
@@ -0,0 +1,147 @@
|
||||
+OAUTH2 support for gmail
|
||||
+========================
|
||||
+
|
||||
+Preface
|
||||
+-------
|
||||
+
|
||||
+fetchmail 7 adds support for OAuth2.
|
||||
+You create a project in google that requests gmail access to request an OAuth2 client id and client secret.
|
||||
+Then you use the contributed fetchmail-oauth2.py to request a refresh token for gmail access to your gmail account.
|
||||
+Then you use the fetchmail-oauth2.py again to request temporary access tokens that fetchmail uses like a password.
|
||||
+
|
||||
+Create a Google project and request an OAuth2 client id and client secret
|
||||
+-------------------------------------------------------------------------
|
||||
+
|
||||
+* Open the Google API Dashboard: https://console.developers.google.com/apis/dashboard
|
||||
+* The first time that you enter the page, you will have to select your country and agree to terms of service.
|
||||
+* You should see a title bar with "Google APIs" and a menu down the left with "Dashboard, Library,
|
||||
+ Credentials, OAuth consent screen, Domain verification, Page usage agreements".
|
||||
+* Click to create a new project, possibly on a pull-down arrow to the right of "Google APIs" on the title bar.
|
||||
+* Click on "NEW PROJECT".
|
||||
+ + Enter a project name like "fetchmail".
|
||||
+ + You can leave "Location" as "No organization" for personal email.
|
||||
+ + If you are a G Suite administrator, you might be able to enter your G Suite organization.
|
||||
+ + When you enter the project name, you will get a message like "Project ID: fetchmail-123456. It cannot be changed later."
|
||||
+ + Make a note of the full project name.
|
||||
+ + Click on "CREATE".
|
||||
+ + Google will take a few seconds to create the project.
|
||||
+* Switch to the new project, either from "SELECT PROJECT" in the notification window or on the title bar pulldown after "Google APIs".
|
||||
+* Click on "Credentials" on the menu at the left.
|
||||
+* Click on "+ CREATE CREDENTIALS" at the top of the window.
|
||||
+* Select "OAuth client ID" from the list of credential types.
|
||||
+* Click on "CONFIGURE CONSENT SCREEN" at the right.
|
||||
+ + Select "External" from the list of user types. "Internal" is for organizations with G Suite.
|
||||
+ + Click "CREATE".
|
||||
+* Fill out the app registration form.
|
||||
+ + "App name" can be the full project name, like "fetchmail-123456". It has to be unique.
|
||||
+ + "User support email" can be your gmail email.
|
||||
+ + "App logo" can be empty. I used /usr/share/icons/Adwaita/256x256/legacy/emblem-mail.png
|
||||
+ + "Application home page" can be empty.
|
||||
+ + "Application privacy policy link" can be empty.
|
||||
+ + "Application terms of service link" can be empty.
|
||||
+ + "Authorized domain" can be empty.
|
||||
+ + "Developer contact email address" can be your gmail email.
|
||||
+ + Click "SAVE AND CONTINUE".
|
||||
+ + Click "ADD OR REMOVE SCOPES" on the "Edit app registration" screen.
|
||||
+ + Click on "Google API Library". This opens a new tab.
|
||||
+ + Filter for "Email" and click on "Gmail API".
|
||||
+ + Click on "ENABLE".
|
||||
+ + Return to the "Edit app registration" tab and refresh.
|
||||
+ + Click "ADD OR REMOVE SCOPES" on the "Edit app registration" screen.
|
||||
+ + I think that the scope ".../auth/gmail.modify" to "View and modify but not delete your email" is sufficient.
|
||||
+ + Click on "SAVE AND CONTINUE". This opens the "Test Users" window.
|
||||
+ + Click on "+ ADD USERS".
|
||||
+ + Enter you gmail address and click on "ADD".
|
||||
+ + Click on "SAVE AND CONTINUE".
|
||||
+ + This opens a "Summary" page.
|
||||
+ + If you need to change something, click on "OAuth consent screen" on the menu at the left and then "EDIT APP" to step through the screens again.
|
||||
+* Click on "Credentials" on the menu at the left to create client credentials.
|
||||
+ + Click on "+ CREATE CREDENTIALS" at the top of the window.
|
||||
+ + Select "OAuth client ID" from the list of credential types.
|
||||
+ + Select "Desktop app" from the list of "Application types".
|
||||
+ + "Name" can be "DesktopClient1" or whatever the screen suggests.
|
||||
+ + Click on "CREATE".
|
||||
+ + It will show a window with "Your Client ID" and "Your Client Secret". Copy them somewhere safe.
|
||||
+
|
||||
+Download and build fetchmail 7
|
||||
+------------------------------
|
||||
+```
|
||||
+git clone https://gitlab.com/fetchmail/fetchmail.git
|
||||
+cd fetchmail
|
||||
+git checkout -t origin/next
|
||||
+./autogen.sh
|
||||
+./configure
|
||||
+make
|
||||
+make check
|
||||
+sudo make install
|
||||
+```
|
||||
+
|
||||
+Configure fetchmail-oauth2.py
|
||||
+-----------------------------
|
||||
+* Create a file, for example /home/yourname/.fetchmail-oauth2
|
||||
+```
|
||||
+client_id=YOUR-CLIENT-ID
|
||||
+client_secret=YOUR-CLIENT-SECRET
|
||||
+refresh_token_file=/home/yourname/.fetchmail-refresh
|
||||
+access_token_file=/home/yourname/.fetchmail-token
|
||||
+max_age_sec=3000
|
||||
+```
|
||||
+* Replace YOUR-CLIENT-ID and YOUR-CLIENT-SECRET with the keys for "Your Client ID" and "Your Client Secret" from the previous step.
|
||||
+* The refresh and token files do not need to exist, but they have to be valid paths.
|
||||
+* Run `contrib/fetchmail-oauth2.py -c /home/yourname/.fetchmail-oauth2 --obtain_refresh_token_file`
|
||||
+ + The script will give you a URL.
|
||||
+ + Paste the URL into a web browser.
|
||||
+ + URL should open a google authentication page.
|
||||
+ + Select the email account.
|
||||
+ + Google will warn that the app isn't verified. Click on "Continue".
|
||||
+ + Google will warn that "fetchmail-123456 wants to access your Google Account `your.name@gmail.com`".
|
||||
+ + Click on "Allow".
|
||||
+ + The page will display the sign in key.
|
||||
+ + Paste the key into the script.
|
||||
+ + The script will report:
|
||||
+```
|
||||
+Refresh token saved to '/home/yourname/.fetchmail-refresh'
|
||||
+Initial access token saved to '/home/yourname/.fetchmail-token'
|
||||
+Access Token Expiration Seconds: 3599
|
||||
+```
|
||||
+ + Hopefully you will not need to do this again for months or years.
|
||||
+* Run `chmod 0600` on all of the files .fetchmail-oauth2 .fetchmail-refresh .fetchmail-token
|
||||
+
|
||||
+Configure fetchmail
|
||||
+---------------------
|
||||
+* Create an entry in your `.fetchmailrc`
|
||||
+```
|
||||
+poll imap.gmail.com protocol imap
|
||||
+ auth oauthbearer username "your.name@gmail.com"
|
||||
+ passwordfile "/home/yourname/.fetchmail-token"
|
||||
+ is yourname here
|
||||
+ fetchlimit 10 folder "Download"
|
||||
+ keep
|
||||
+ sslmode wrapped sslcertck
|
||||
+```
|
||||
+* Run `chmod 0400` on your `.fetchmailrc`
|
||||
+* The optional "fetchlimit #" limits the number of emails if you are testing.
|
||||
+* The optional "folder name" sets the folder to check.
|
||||
+* I made gmail filters that add a "Download" label to important emails.
|
||||
+* Fetchmail downloads unread emails. You can go into gmail and mark a few emails unread for testing.
|
||||
+* Try running fetchmail once at a command line.
|
||||
+
|
||||
+Script fetchmail
|
||||
+----------------
|
||||
+* Each access token expires after an hour.
|
||||
+* If you run fetchmail from cron, you should run `fetchmail-oauth2.py -c /home/yourname/.fetchmail-oauth2 --auto_refresh ; fetchmail`
|
||||
+* For example, `*/2 * * * * (fetchmail-oauth2.py -c /home/yourname/.fetchmail-oauth2 --auto_refresh ; fetchmail) > /home/yourname/fetchmail.log 2>&1`
|
||||
+* The `--auto_refresh` option checks the age of the key against the `max_age_sec` and renews it if necessary.
|
||||
+* `max_age_sec=3000` in `.fetchmail-oauth2` renews the key after 50 minutes, which should give a safe margin.
|
||||
+
|
||||
+Further reading
|
||||
+---------------
|
||||
+* Instructions by the author of OAuth2 support for Fetchmail and Postfix
|
||||
+ + Setting Up OAUTH2 Support for Fetchmail and Postfix http://mmogilvi.users.sourceforge.net/software/oauthbearer.html
|
||||
+ + Run `fetchmail-oauth2.py --help | less`
|
||||
+* Documents from Google
|
||||
+ + Using OAuth 2.0 to Access Google APIs https://developers.google.com/identity/protocols/oauth2
|
||||
+ + Integrating Google Sign-In into your web app https://developers.google.com/identity/sign-in/web/devconsole-project
|
||||
+* Google links
|
||||
+ + Google API Dashboard: https://console.developers.google.com/apis/dashboard
|
||||
+ + Google Cloud Resource Manager: https://console.developers.google.com/cloud-resource-manager
|
23
fetchmail-bump-max-passwordlen-to-1bytes.patch
Normal file
23
fetchmail-bump-max-passwordlen-to-1bytes.patch
Normal file
@ -0,0 +1,23 @@
|
||||
From: Matthias Andree <matthias.andree@gmx.de>
|
||||
Date: Sat, 24 Apr 2021 15:12:01 +0200
|
||||
Subject: Bump max. passwordlen to 10000 bytes.
|
||||
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
|
||||
Git-commit: 919fd787540c4a3fa4694566edce406df1e42001
|
||||
|
||||
---
|
||||
fetchmail.h | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Index: fetchmail-6.5.1/fetchmail.h
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/fetchmail.h
|
||||
+++ fetchmail-6.5.1/fetchmail.h
|
||||
@@ -101,7 +101,7 @@ struct addrinfo;
|
||||
|
||||
#define NAMELEN 64 /* max username length */
|
||||
/* oauth2 access tokens seem to be about 130 characters; make this longer: */
|
||||
-#define PASSWORDLEN 4096 /* max password length; oauth2 tokens have no maximum length */
|
||||
+#define PASSWORDLEN 10000 /* max password length; oauth2 tokens have no maximum length */
|
||||
#define DIGESTLEN 33 /* length of MD5 digest */
|
||||
|
||||
/* exit code values */
|
29
fetchmail-chase-and-integrate-interface-change.patch
Normal file
29
fetchmail-chase-and-integrate-interface-change.patch
Normal file
@ -0,0 +1,29 @@
|
||||
From: Matthias Andree <matthias.andree@gmx.de>
|
||||
Date: Sun, 25 Nov 2018 12:09:07 +0100
|
||||
Subject: Chase and integrate interface change.
|
||||
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
|
||||
Git-commit: d52b7b6859d46134b46e6de9b408739b18745d47
|
||||
|
||||
---
|
||||
oauth2.c | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/oauth2.c
|
||||
+++ b/oauth2.c
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "oauth2.h"
|
||||
|
||||
#include <stdio.h>
|
||||
+#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
char *get_oauth2_string(struct query *ctl,flag xoauth2)
|
||||
@@ -52,7 +53,7 @@ char *get_oauth2_string(struct query *ct
|
||||
}
|
||||
|
||||
oauth2b64 = (char *)xmalloc(2*strlen(oauth2str)+8);
|
||||
- to64frombits(oauth2b64, oauth2str, strlen(oauth2str));
|
||||
+ to64frombits(oauth2b64, oauth2str, strlen(oauth2str), oauth2len);
|
||||
|
||||
memset(oauth2str, 0x55, strlen(oauth2str));
|
||||
free(oauth2str);
|
45
fetchmail-give-each-ctl-it-s-own-copy-of-password.patch
Normal file
45
fetchmail-give-each-ctl-it-s-own-copy-of-password.patch
Normal file
@ -0,0 +1,45 @@
|
||||
From: Matthew Ogilvie <mmogilvi+fml@zoho.com>
|
||||
Date: Fri, 9 Jun 2017 19:31:17 -0600
|
||||
Subject: give each ctl it's own copy of password
|
||||
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
|
||||
Git-commit: 469b0a212e7f047ab16ef46a9158df5fb373e8c2
|
||||
|
||||
pwdb_* and passwordfile options may free and re-allocate password
|
||||
for each poll operation. Giving each context it's own copy of
|
||||
the password should prevent accessing freed memory in another copy.
|
||||
|
||||
I haven't tested pwmd, but these seem like obvious fixes.
|
||||
---
|
||||
fetchmail.c | 12 ++++++++++--
|
||||
1 file changed, 10 insertions(+), 2 deletions(-)
|
||||
|
||||
Index: fetchmail-6.5.1/fetchmail.c
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/fetchmail.c
|
||||
+++ fetchmail-6.5.1/fetchmail.c
|
||||
@@ -470,7 +470,7 @@ int main(int argc, char **argv)
|
||||
if (NO_PASSWORD(ctl))
|
||||
/* Server won't care what the password is, but there
|
||||
must be some non-null string here. */
|
||||
- ctl->password = ctl->remotename;
|
||||
+ ctl->password = xstrdup(ctl->remotename);
|
||||
else if (!ctl->passwordfile && ctl->passwordfd==-1)
|
||||
{
|
||||
const netrc_entry *p;
|
||||
@@ -1118,7 +1118,15 @@ static void optmerge(struct query *h2, s
|
||||
|
||||
FLAG_MERGE(wildcard);
|
||||
STRING_MERGE(remotename);
|
||||
- STRING_MERGE(password);
|
||||
+ if (force ? !!h1->password : !h2->password) {
|
||||
+ if (h2->password) {
|
||||
+ memset(h2->password, 0x55, strlen(h2->password));
|
||||
+ xfree(h2->password);
|
||||
+ }
|
||||
+ if (h1->password) {
|
||||
+ h2->password = xstrdup(h1->password);
|
||||
+ }
|
||||
+ }
|
||||
FLAG_MERGE(passwordfile);
|
||||
if (force ? h1->passwordfd!=-1 : h2->passwordfd==-1) {
|
||||
h2->passwordfd = h1->passwordfd;
|
@ -0,0 +1,32 @@
|
||||
From: =?utf-8?q?Martin_Sj=C3=B6lund_=3Cmartin=40sjoelund=2Ese=3E?=
|
||||
Date: Thu, 17 Dec 2020 09:09:44 +0100
|
||||
Subject: Increase max password length to handle oauth tokens
|
||||
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
|
||||
Git-commit: 6e877b5d92527ad501aaef46e37704b51db316fb
|
||||
|
||||
The maximum length of oauth2 access tokens is unbounded.
|
||||
* Google uses 2048 byte access tokens and "Google reserves the right
|
||||
to change token size within these limits, and your application
|
||||
must support variable token sizes accordingly."
|
||||
* My Office365 access token is 2108 bytes long.
|
||||
* Intuit says you must support 4096 byte access tokens.
|
||||
|
||||
This simply patches the hard-coded limit to 4096 bytes, but it might
|
||||
not be sufficient.
|
||||
---
|
||||
fetchmail.h | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Index: fetchmail-6.5.1/fetchmail.h
|
||||
===================================================================
|
||||
--- fetchmail-6.5.1.orig/fetchmail.h
|
||||
+++ fetchmail-6.5.1/fetchmail.h
|
||||