summaryrefslogtreecommitdiff
path: root/hosts/surtr/email/ccert-policy-server
diff options
context:
space:
mode:
Diffstat (limited to 'hosts/surtr/email/ccert-policy-server')
-rw-r--r--hosts/surtr/email/ccert-policy-server/ccert_policy_server/__init__.py0
-rw-r--r--hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py92
-rw-r--r--hosts/surtr/email/ccert-policy-server/setup.py12
3 files changed, 104 insertions, 0 deletions
diff --git a/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__init__.py b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__init__.py
diff --git a/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py
new file mode 100644
index 00000000..f481090c
--- /dev/null
+++ b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py
@@ -0,0 +1,92 @@
1from systemd.daemon import listen_fds
2from sdnotify import SystemdNotifier
3from socketserver import StreamRequestHandler, ThreadingMixIn
4from systemd_socketserver import SystemdSocketServer
5import sys
6from threading import Thread
7from psycopg_pool import ConnectionPool
8from psycopg.rows import namedtuple_row
9
10import logging
11
12
13class PolicyHandler(StreamRequestHandler):
14 def handle(self):
15 logger.debug('Handling new connection...')
16
17 self.args = dict()
18
19 line = None
20 while line := self.rfile.readline().removesuffix(b'\n'):
21 if b'=' not in line:
22 break
23
24 key, val = line.split(sep=b'=', maxsplit=1)
25 self.args[key.decode()] = val.decode()
26
27 logger.info('Connection parameters: %s', self.args)
28
29 allowed = False
30 with self.server.db_pool.connection() as conn:
31 local, domain = self.args['sender'].split(sep='@', maxsplit=1)
32 extension = None
33 if '+' in local:
34 local, extension = local.split(sep='+', maxsplit=1)
35
36 logger.debug('Parsed address: %s', {'local': local, 'extension': extension, 'domain': domain})
37
38 with conn.cursor() as cur:
39 cur.row_factory = namedtuple_row
40 cur.execute('SELECT "mailbox"."mailbox" as "user", "local", "extension", "domain" FROM "mailbox" INNER JOIN "mailbox_mapping" ON "mailbox".id = "mailbox_mapping"."mailbox" WHERE "mailbox"."mailbox" = %(user)s AND ("local" = %(local)s OR "local" IS NULL) AND ("extension" = %(extension)s OR "extension" IS NULL) AND "domain" = %(domain)s', params = {'user': self.args['ccert_subject'], 'local': local, 'extension': extension if extension is not None else '', 'domain': domain}, prepare=True)
41 for record in cur:
42 logger.debug('Received result: %s', record)
43 allowed = True
44
45 action = '550 5.7.0 Sender address not authorized for current user'
46 if allowed:
47 action = 'DUNNO'
48
49 logger.info('Reached verdict: %s', {'allowed': allowed, 'action': action})
50 self.wfile.write(f'action={action}\n\n'.encode())
51
52class ThreadedSystemdSocketServer(ThreadingMixIn, SystemdSocketServer):
53 def __init__(self, fd, RequestHandlerClass):
54 super().__init__(fd, RequestHandlerClass)
55
56 self.db_pool = ConnectionPool(min_size=1)
57 self.db_pool.wait()
58
59def main():
60 global logger
61 logger = logging.getLogger(__name__)
62 console_handler = logging.StreamHandler()
63 console_handler.setFormatter( logging.Formatter('[%(levelname)s](%(name)s): %(message)s') )
64 if sys.stderr.isatty():
65 console_handler.setFormatter( logging.Formatter('%(asctime)s [%(levelname)s](%(name)s): %(message)s') )
66 logger.addHandler(console_handler)
67 logger.setLevel(logging.DEBUG)
68
69 # log uncaught exceptions
70 def log_exceptions(type, value, tb):
71 global logger
72
73 logger.error(value)
74 sys.__excepthook__(type, value, tb) # calls default excepthook
75
76 sys.excepthook = log_exceptions
77
78 fds = listen_fds()
79 servers = [ThreadedSystemdSocketServer(fd, PolicyHandler) for fd in fds]
80
81 if servers:
82 for server in servers:
83 Thread(name=f'Server for fd{server.fileno()}', target=server.serve_forever).start()
84 else:
85 return 2
86
87 SystemdNotifier().notify('READY=1')
88
89 return 0
90
91if __name__ == '__main__':
92 sys.exit(main())
diff --git a/hosts/surtr/email/ccert-policy-server/setup.py b/hosts/surtr/email/ccert-policy-server/setup.py
new file mode 100644
index 00000000..d8eb415a
--- /dev/null
+++ b/hosts/surtr/email/ccert-policy-server/setup.py
@@ -0,0 +1,12 @@
1from setuptools import setup, find_packages
2
3setup(
4 name = 'ccert-policy-server',
5 version = '0.0.0',
6 packages = ['ccert_policy_server'],
7 entry_points = {
8 'console_scripts': [
9 'ccert-policy-server=ccert_policy_server.__main__:main'
10 ],
11 },
12)