summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--debian/control1
-rwxr-xr-ximap-dl62
-rw-r--r--imap-dl.1.pod4
3 files changed, 65 insertions, 2 deletions
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<retriever.username> is the username of the IMAP account.
B<retriever.password> is the password for the IMAP account when using
plaintext passwords.
+B<retriever.use_kerberos> (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<retriever.ssl_ciphers> is an OpenSSL cipher string to use instead of the
defaults. (The defaults are good; this should be avoided except to work
around bugs.)