From 66a987a52a66aa36b3dfdb4ab8fa6af1c0d5ccf2 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 10 Mar 2020 17:16:10 -0400 Subject: imap-dl: update documentation to be less explicitly "getmail"y The fact that imap-dl came out of getmail and borrows a bit of configuration shouldn't be relevant for new users. Make the documentation more agnostic about where they're coming from. Signed-off-by: Daniel Kahn Gillmor --- imap-dl | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) (limited to 'imap-dl') diff --git a/imap-dl b/imap-dl index 469b81d..de6be60 100755 --- a/imap-dl +++ b/imap-dl @@ -17,22 +17,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -DESCRIPTION = '''A simple replacement for a minimalist use of getmail. +DESCRIPTION = '''Fetch messages from an IMAP inbox into a maildir -In particular, if you use getmail to reach an IMAP server as though it -were POP (retrieving from the server and optionally deleting), you can -point this script to the getmail config and it should do the same -thing. - -It tries to ensure that the configuration file is of the expected -type, and will terminate raising an exception, and it should not lose -messages. - -If there's any interest in supporting other similarly simple use cases -for getmail, patches are welcome. - -If you've never used getmail, you can make the simplest possible -config file like so: +Example config file: ---------- [retriever] @@ -46,6 +33,8 @@ path = /home/foo/Maildir [options] delete = True ---------- + +Run "man imap-dl" for more details. ''' import re -- cgit v1.2.3 From 6e26cfb34ae96f2dbef3660afd1857b31bd6ea5c Mon Sep 17 00:00:00 2001 From: Robbie Harwood Date: Tue, 10 Mar 2020 12:54:50 -0400 Subject: imap-dl: Allow specifying ssl_ciphers In my case, this allows supporting servers which have unacceptably (for systemwide OpenSSL) small DH sizes by requesting non-DH ciphers. Specifically, hardcoding AES256-GCM-SHA384 prevents this traceback: Traceback (most recent call last): File "/usr/bin/imap-dl", line 273, in scan_msgs(confname, args.verbose) File "/usr/bin/imap-dl", line 133, in scan_msgs ssl_context=ctx) as imap: File "/usr/lib/python3.7/imaplib.py", line 1288, in __init__ IMAP4.__init__(self, host, port) File "/usr/lib/python3.7/imaplib.py", line 198, in __init__ self.open(host, port) File "/usr/lib/python3.7/imaplib.py", line 1301, in open IMAP4.open(self, host, port) File "/usr/lib/python3.7/imaplib.py", line 299, in open self.sock = self._create_socket() File "/usr/lib/python3.7/imaplib.py", line 1293, in _create_socket server_hostname=self.host) File "/usr/lib/python3.7/ssl.py", line 423, in wrap_socket session=session File "/usr/lib/python3.7/ssl.py", line 870, in _create self.do_handshake() File "/usr/lib/python3.7/ssl.py", line 1139, in do_handshake self._sslobj.do_handshake() ssl.SSLError: [SSL: DH_KEY_TOO_SMALL] dh key too small (_ssl.c:1076) Signed-off-by: Robbie Harwood Acked-by: Daniel Kahn Gillmor --- imap-dl | 4 ++++ imap-dl.1.pod | 4 ++++ 2 files changed, 8 insertions(+) (limited to 'imap-dl') diff --git a/imap-dl b/imap-dl index de6be60..a1c2329 100755 --- a/imap-dl +++ b/imap-dl @@ -116,6 +116,10 @@ def scan_msgs(configfile:str, verbose:bool) -> None: '(found "{on_size_mismatch_str}")') ctx = ssl.create_default_context(cafile=ca_certs) + ssl_ciphers = conf.get('retriever', 'ssl_ciphers', fallback=None) + if ssl_ciphers: + ctx.set_ciphers(ssl_ciphers) + server:str = conf.get('retriever', 'server') with imaplib.IMAP4_SSL(host=server, #type: ignore port=int(conf.get('retriever', 'port', fallback=993)), diff --git a/imap-dl.1.pod b/imap-dl.1.pod index 901c7e8..7998d3a 100644 --- a/imap-dl.1.pod +++ b/imap-dl.1.pod @@ -48,6 +48,10 @@ B is the username of the IMAP account. B is the password for the IMAP account when using plaintext passwords. +B is an OpenSSL cipher string to use instead of the +defaults. (The defaults are good; this should be avoided except to work +around bugs.) + B is the location of the target maildir. B is a boolean, whether to delete the messages that -- cgit v1.2.3 From 1011755b266f69f1fc90d2447737133cbba754de Mon Sep 17 00:00:00 2001 From: Robbie Harwood Date: Tue, 10 Mar 2020 11:06:52 -0400 Subject: imap-dl: Factor password auth into separate function Signed-off-by: Robbie Harwood Acked-by: Daniel Kahn Gillmor --- imap-dl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'imap-dl') diff --git a/imap-dl b/imap-dl index a1c2329..f1259da 100755 --- a/imap-dl +++ b/imap-dl @@ -80,6 +80,14 @@ summary_splitter = Splitter('summary', _summary_re) _fetch_re = rb'^(?P[0-9]+) \(UID (?P[0-9]+) (FLAGS \([\\A-Za-z ]*\) )?BODY\[\] \{(?P[0-9]+)\}$' fetch_splitter = Splitter('fetch', _fetch_re) +def auth_builtin(username:str, imap:imaplib.IMAP4_SSL, + conf:configparser.ConfigParser, server:str) -> None: + logging.info('Logging in as %s', username) + resp:Tuple[str, List[Union[bytes,Tuple[bytes,bytes]]]] + resp = imap.login(username, conf.get('retriever', 'password')) + if resp[0] != 'OK': + raise Exception(f'login failed with {resp} as user {username} on {server}') + def scan_msgs(configfile:str, verbose:bool) -> None: conf = configparser.ConfigParser() conf.read_file(open(configfile, 'r')) @@ -125,11 +133,7 @@ def scan_msgs(configfile:str, verbose:bool) -> None: port=int(conf.get('retriever', 'port', fallback=993)), ssl_context=ctx) as imap: username:str = conf.get('retriever', 'username') - logging.info('Logging in as %s', username) - resp:Tuple[str, List[Union[bytes,Tuple[bytes,bytes]]]] - resp = imap.login(username, conf.get('retriever', 'password')) - if resp[0] != 'OK': - raise Exception(f'login failed with {resp} as user {username} on {server}') + auth_builtin(username, imap, conf, server) if verbose: # only enable debugging after login to avoid leaking credentials in the log imap.debug = 4 logging.info("capabilities reported: %s", ', '.join(imap.capabilities)) -- cgit v1.2.3 From 51e0c8a488b7a8d195d32d0bc58e5ef24b38e626 Mon Sep 17 00:00:00 2001 From: Robbie Harwood Date: Tue, 10 Mar 2020 12:49:36 -0400 Subject: imap-dl: Kerberos support using python3-gssapi This is based off offlineimap's code rather than getmail's. getmail relied on pykerberos, which is considered deprecated in Fedora/RHEL/CentOS; offlineimap relied on python-gssapi, which is considered its replacement there. python3-gssapi doesn't yet have type annotations, but this is planned to change in the future. Signed-off-by: Robbie Harwood Acked-by: Daniel Kahn Gillmor --- debian/control | 1 + imap-dl | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- imap-dl.1.pod | 4 ++++ 3 files changed, 65 insertions(+), 2 deletions(-) (limited to 'imap-dl') diff --git a/debian/control b/debian/control index 3ce1ac4..ffe889d 100644 --- a/debian/control +++ b/debian/control @@ -51,6 +51,7 @@ Recommends: git, notmuch, python3-argcomplete, + python3-gssapi, python3-pgpy, Suggests: gnutls-bin, diff --git a/imap-dl b/imap-dl index f1259da..83ce84f 100755 --- a/imap-dl +++ b/imap-dl @@ -50,13 +50,19 @@ import argparse import statistics import configparser -from typing import Dict, List, Union, Tuple +from typing import Dict, List, Optional, Tuple, Union try: import argcomplete #type: ignore except ImportError: argcomplete = None +try: + import gssapi # type: ignore +except ModuleNotFoundError: + gssapi = None + + class Splitter(object): def __init__(self, name:str, match:bytes): self._splitter = re.compile(match) @@ -88,6 +94,52 @@ def auth_builtin(username:str, imap:imaplib.IMAP4_SSL, if resp[0] != 'OK': raise Exception(f'login failed with {resp} as user {username} on {server}') +# imaplib auth methods need to be in the form of callables, and they all +# requre both additional parameters and storage beyond what the function +# interface provides. +class GSSAPI_handler(): + gss_vc:gssapi.SecurityContext + username:str + + def __init__(self, server:str, username:str) -> None: + name = gssapi.Name(f'imap@{server}', gssapi.NameType.hostbased_service) + self.gss_vc = gssapi.SecurityContext(usage="initiate", name=name) + self.username = username + + def __call__(self, token:Optional[bytes]) -> bytes: + if token == b"": + token = None + if not self.gss_vc.complete: + response = self.gss_vc.step(token) + return response if response else b"" # type: ignore + elif token is None: + return b"" + + response = self.gss_vc.unwrap(token) + + # Preserve the "length" of the message we received, and set the first + # byte to one. If username is provided, it's next. + reply:List[int] = [] + reply[0:4] = response.message[0:4] + reply[0] = 1 + if self.username: + reply[5:] = self.username.encode("utf-8") + + response = self.gss_vc.wrap(bytes(reply), response.encrypted) + return response.message if response.message else b"" # type: ignore + +def auth_gssapi(username:str, imap:imaplib.IMAP4_SSL, + conf:configparser.ConfigParser, server:str) -> None: + if not gssapi: + raise Exception('Kerberos requested, but python3-gssapi not found') + + logging.info(f'Logging in as {username} with GSSAPI') + + callback = GSSAPI_handler(server, username) + resp = imap.authenticate("GSSAPI", callback) + if resp[0] != 'OK': + raise Exception(f'GSSAPI login failed with {resp} as user {username} on {server}') + def scan_msgs(configfile:str, verbose:bool) -> None: conf = configparser.ConfigParser() conf.read_file(open(configfile, 'r')) @@ -133,7 +185,13 @@ def scan_msgs(configfile:str, verbose:bool) -> None: port=int(conf.get('retriever', 'port', fallback=993)), ssl_context=ctx) as imap: username:str = conf.get('retriever', 'username') - auth_builtin(username, imap, conf, server) + use_kerberos = conf.getboolean('retriever', 'use_kerberos', + fallback=False) + if use_kerberos: + auth_gssapi(username, imap, conf, server) + else: + auth_builtin(username, imap, conf, server) + if verbose: # only enable debugging after login to avoid leaking credentials in the log imap.debug = 4 logging.info("capabilities reported: %s", ', '.join(imap.capabilities)) diff --git a/imap-dl.1.pod b/imap-dl.1.pod index 7998d3a..5864267 100644 --- a/imap-dl.1.pod +++ b/imap-dl.1.pod @@ -48,6 +48,10 @@ B is the username of the IMAP account. B is the password for the IMAP account when using plaintext passwords. +B (boolean) requests that Kerberos (through GSSAPI) is +to be used instead of password-based auth. There is no need to specify +password when using Kerberos. This requires the python3-gssapi module. + B is an OpenSSL cipher string to use instead of the defaults. (The defaults are good; this should be avoided except to work around bugs.) -- cgit v1.2.3 From ddfda64826800a7b737fa161fd9d793fa6b42f06 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 18 Mar 2020 22:07:33 -0400 Subject: imap-dl: use retriever.authentication configuration After discussion with Sean Whitton and Robbie Harwood, I think makes more sense to have a straight "retriever.authentication" configuration setting rather than a rather odd boolean "use_kerberos". This is a divergence from getmail, but that seems OK at this point. The implementation now also makes it pretty straightforward to add new authentication mechanisms if someone wants to add them. One additional thing that would be nice would be for imap-dl to be able to dynamically choose the "best" available authentication method. Signed-off-by: Daniel Kahn Gillmor --- imap-dl | 17 +++++++++++++---- imap-dl.1.pod | 10 +++++----- 2 files changed, 18 insertions(+), 9 deletions(-) (limited to 'imap-dl') diff --git a/imap-dl b/imap-dl index 83ce84f..4f5abbb 100755 --- a/imap-dl +++ b/imap-dl @@ -185,12 +185,21 @@ def scan_msgs(configfile:str, verbose:bool) -> None: port=int(conf.get('retriever', 'port', fallback=993)), ssl_context=ctx) as imap: username:str = conf.get('retriever', 'username') - use_kerberos = conf.getboolean('retriever', 'use_kerberos', - fallback=False) - if use_kerberos: + authentication:str = conf.get('retriever', 'authentication', + fallback='basic') + # FIXME: have the default automatically choose an opinionated + # best authentication method. e.g., if the gssapi module is + # installed and the user has a reasonable identity in their + # local credential cache, choose kerberos, otherwise, choose + # "basic". + if authentication == 'kerberos': auth_gssapi(username, imap, conf, server) - else: + elif authentication == 'basic': auth_builtin(username, imap, conf, server) + else: + # FIXME: implement other authentication mechanisms + raise Exception(f'retriever.authentication should be one of:\n' + '"basic" or "kerberos", got "{authentication}"') if verbose: # only enable debugging after login to avoid leaking credentials in the log imap.debug = 4 diff --git a/imap-dl.1.pod b/imap-dl.1.pod index 5864267..88e3129 100644 --- a/imap-dl.1.pod +++ b/imap-dl.1.pod @@ -43,14 +43,14 @@ options: B is the dns name of the mailserver. +B is either "basic" (the default, using the +IMAP LOGIN verb) or "kerberos" (IMAP AUTHENTICATE with GSSAPI, +requires the python3-gssapi module). + B is the username of the IMAP account. B is the password for the IMAP account when using -plaintext passwords. - -B (boolean) requests that Kerberos (through GSSAPI) is -to be used instead of password-based auth. There is no need to specify -password when using Kerberos. This requires the python3-gssapi module. +B is set to "basic". B is an OpenSSL cipher string to use instead of the defaults. (The defaults are good; this should be avoided except to work -- cgit v1.2.3 From af0bd7d1033244e86ce460183d5197bad7ce2ef8 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 20 Mar 2020 11:28:43 -0700 Subject: copyright notices for Robbie Harwood's recent commits Verified with the author. Signed-off-by: Sean Whitton --- debian/copyright | 1 + imap-dl | 1 + 2 files changed, 2 insertions(+) (limited to 'imap-dl') diff --git a/debian/copyright b/debian/copyright index 17997a8..db97f3d 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,6 +3,7 @@ Collection of scripts for manipulating e-mail on Debian Copyright (C)2017-2020 Sean Whitton Copyright (C)2019-2020 Daniel Kahn Gillmor +Copyright (C)2020 Red Hat, Inc. These programs are free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as diff --git a/imap-dl b/imap-dl index 4f5abbb..c98f575 100755 --- a/imap-dl +++ b/imap-dl @@ -3,6 +3,7 @@ # -*- coding: utf-8 -*- # Copyright (C) 2019-2020 Daniel Kahn Gillmor +# Copyright (C) 2020 Red Hat, Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -- cgit v1.2.3 From 855b1a65f34e266a7e972f053e73087798f92a7e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 20 Mar 2020 11:25:52 -0400 Subject: imap-dl: Use "gssapi" as a synonym for "kerberos" Robbie Harwood says that "gssapi" is more accurate. I regularly get perplexed by the layers of SASL, GSSAPI, and Kerberos, so i'm happy to have this alias Just Work in any case. Suggested-by: Robbie Harwood Signed-off-by: Daniel Kahn Gillmor --- imap-dl | 4 ++-- imap-dl.1.pod | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'imap-dl') diff --git a/imap-dl b/imap-dl index c98f575..5a8494c 100755 --- a/imap-dl +++ b/imap-dl @@ -193,14 +193,14 @@ def scan_msgs(configfile:str, verbose:bool) -> None: # installed and the user has a reasonable identity in their # local credential cache, choose kerberos, otherwise, choose # "basic". - if authentication == 'kerberos': + if authentication in ['kerberos', 'gssapi']: auth_gssapi(username, imap, conf, server) elif authentication == 'basic': auth_builtin(username, imap, conf, server) else: # FIXME: implement other authentication mechanisms raise Exception(f'retriever.authentication should be one of:\n' - '"basic" or "kerberos", got "{authentication}"') + '"basic" or "gssapi" (or "kerberos"). Got "{authentication}"') if verbose: # only enable debugging after login to avoid leaking credentials in the log imap.debug = 4 diff --git a/imap-dl.1.pod b/imap-dl.1.pod index 204b430..402a167 100644 --- a/imap-dl.1.pod +++ b/imap-dl.1.pod @@ -44,8 +44,8 @@ options: B is the dns name of the mailserver. B is either "basic" (the default, using the -IMAP LOGIN verb) or "kerberos" (IMAP AUTHENTICATE with GSSAPI, -requires the python3-gssapi module). +IMAP LOGIN verb) or "gssapi" (IMAP AUTHENTICATE with GSSAPI, requires +the python3-gssapi module). "kerberos" is an alias for "gssapi". B is the username of the IMAP account. -- cgit v1.2.3