aboutsummaryrefslogtreecommitdiffstats
path: root/cloud_mdir_sync/credsrv.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloud_mdir_sync/credsrv.py')
-rw-r--r--cloud_mdir_sync/credsrv.py65
1 files changed, 65 insertions, 0 deletions
diff --git a/cloud_mdir_sync/credsrv.py b/cloud_mdir_sync/credsrv.py
new file mode 100644
index 0000000..97357bc
--- /dev/null
+++ b/cloud_mdir_sync/credsrv.py
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: GPL-2.0+
+import asyncio
+import contextlib
+import os
+import re
+from typing import List
+
+from . import config, oauth
+
+
+class CredentialServer(object):
+ """Serve XOAUTH2 bearer tokens over a unix domain socket. The client
+ writes the user to obtain a token for and the server responds with the
+ token"""
+ def __init__(self,
+ cfg: config.Config,
+ path: str,
+ accounts: List[oauth.Account],
+ umask=0o600):
+ self.cfg = cfg
+ self.path = os.path.abspath(os.path.expanduser(path))
+ self.umask = umask
+ self.accounts = {}
+ for I in accounts:
+ I.oauth_smtp = True
+ self.accounts[I.user] = I
+
+ async def go(self):
+ old_umask = os.umask(self.umask)
+ try:
+ self.server = await asyncio.start_unix_server(
+ self.handle_client, self.path)
+ finally:
+ os.umask(old_umask)
+ os.chmod(self.path, self.umask)
+
+ async def close(self):
+ pass
+
+ async def handle_client(self, reader: asyncio.StreamReader,
+ writer: asyncio.StreamWriter) -> None:
+ with contextlib.closing(writer):
+ req = await reader.read()
+ g = re.match(r"([^ ,]+)(?:,(\S+))? (\S+@\S+)", req.decode())
+ if g is None:
+ self.cfg.logger.error(f"Invalid credential request {req!r}")
+ return
+ proto, opts_str, user = g.groups()
+ if opts_str:
+ opts = opts_str.split(',')
+ else:
+ opts = []
+
+ self.cfg.logger.debug(
+ f"Credential request {proto!r} {opts} {user!r}")
+
+ account = self.accounts.get(user)
+ if account is None:
+ return
+
+ xoauth2 = await account.get_xoauth2_bytes("SMTP")
+ if xoauth2 is None:
+ return
+ writer.write(xoauth2)
+ await writer.drain()