experimental mail transport

This commit is contained in:
j3d1 2023-11-23 23:17:20 +01:00
parent e43d4837c3
commit 7cee8a3cc2
6 changed files with 141 additions and 18 deletions

View file

@ -31,6 +31,8 @@ DEBUG = True
ALLOWED_HOSTS = [os.getenv('HTTP_HOST', 'localhost')] ALLOWED_HOSTS = [os.getenv('HTTP_HOST', 'localhost')]
MAIL_DOMAIN = os.getenv('MAIL_DOMAIN', 'localhost')
SYSTEM3_VERSION = "0.0.0-dev.0" SYSTEM3_VERSION = "0.0.0-dev.0"
# Application definition # Application definition

View file

@ -1,9 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import asyncio import asyncio
import logging import logging
import os
from abc import ABCMeta
import uvicorn import uvicorn
from aiosmtpd.controller import Controller, UnixSocketController from aiosmtpd.controller import Controller, UnixSocketController, BaseController, UnixSocketMixin
from aiosmtpd.lmtp import LMTP from aiosmtpd.lmtp import LMTP
@ -26,7 +28,8 @@ async def handle_echo(reader, writer):
class ExampleHandler: class ExampleHandler:
async def handle_RCPT(self, server, session, envelope, address, rcpt_options): async def handle_RCPT(self, server, session, envelope, address, rcpt_options):
if not address.endswith('@example.com'): from core.settings import MAIL_DOMAIN
if not address.endswith('@' + MAIL_DOMAIN):
return '550 not relaying to that domain' return '550 not relaying to that domain'
envelope.rcpt_tos.append(address) envelope.rcpt_tos.append(address)
return '250 OK' return '250 OK'
@ -47,17 +50,39 @@ class LTMPController(Controller):
return LMTP(self.handler) return LMTP(self.handler)
class UnixSocketLMTPController(UnixSocketController): class BaseAsyncController(BaseController, metaclass=ABCMeta):
def __init__(
self,
handler,
loop,
**SMTP_parameters,
):
super().__init__(
handler,
loop,
**SMTP_parameters,
)
def serve(self):
return self._create_server()
class UnixSocketLMTPController(UnixSocketMixin, BaseAsyncController):
def factory(self): def factory(self):
return LMTP(self.handler) return LMTP(self.handler)
def _trigger_server(self): # pragma: no-unixsock
# Prevent confusion on which _trigger_server() to invoke.
# Or so LGTM.com claimed
UnixSocketMixin._trigger_server(self)
class UvicornServer(uvicorn.Server): class UvicornServer(uvicorn.Server):
def install_signal_handlers(self): def install_signal_handlers(self):
pass pass
async def web(): async def web(loop):
log_config = uvicorn.config.LOGGING_CONFIG log_config = uvicorn.config.LOGGING_CONFIG
log_config["handlers"]["default"] = {"class": "logging.FileHandler", "filename": "web.log", "formatter": "default"} log_config["handlers"]["default"] = {"class": "logging.FileHandler", "filename": "web.log", "formatter": "default"}
log_config["handlers"]["access"] = {"class": "logging.FileHandler", "filename": "web-access.log", log_config["handlers"]["access"] = {"class": "logging.FileHandler", "filename": "web-access.log",
@ -67,8 +92,10 @@ async def web():
await server.serve() await server.serve()
async def tcp(): async def tcp(loop):
log = logging.getLogger('test') log = logging.getLogger('test.log')
log.addHandler(logging.FileHandler('test.log'))
log.setLevel(logging.DEBUG)
log.info("Starting TCP server") log.info("Starting TCP server")
server = await asyncio.start_unix_server(handle_echo, path='test.sock') server = await asyncio.start_unix_server(handle_echo, path='test.sock')
@ -80,11 +107,24 @@ async def tcp():
log.info("TCP done") log.info("TCP done")
async def lmtp(): async def lmtp(loop):
log = logging.getLogger('lmtp') import grp
log = logging.getLogger('mail.log')
log.addHandler(logging.FileHandler('mail.log'))
log.setLevel(logging.WARNING)
log.info("Starting LMTP server") log.info("Starting LMTP server")
cont = UnixSocketLMTPController(ExampleHandler(), unix_socket='lmtp.sock') server = await UnixSocketLMTPController(ExampleHandler(), unix_socket='lmtp.sock', loop=loop).serve()
cont.start()
addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets)
log.info(f'Serving on {addrs}')
os.chmod('lmtp.sock', 0o775)
current_uid = os.getuid()
posix_gid = grp.getgrnam('postfix').gr_gid
os.chown('lmtp.sock', current_uid, posix_gid)
async with server:
await server.serve_forever()
log.info("LMTP done") log.info("LMTP done")
@ -104,18 +144,18 @@ def main():
import sdnotify import sdnotify
import signal import signal
import setproctitle import setproctitle
import os
setproctitle.setproctitle("c3lf-sys3") setproctitle.setproctitle("c3lf-sys3")
logging.basicConfig(filename='server.log', level=logging.DEBUG, encoding='utf-8') log = logging.getLogger('server.log')
logging.basicConfig(filename='test.log', level=logging.DEBUG, encoding='utf-8') log.addHandler(logging.FileHandler('server.log'))
logging.basicConfig(filename='lmtp.log', level=logging.DEBUG, encoding='utf-8') log.setLevel(logging.DEBUG)
log = logging.getLogger()
log.info("Starting server") log.info("Starting server")
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.create_task(shutdown(signal.SIGTERM, loop))) loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.create_task(shutdown(signal.SIGTERM, loop)))
loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(shutdown(signal.SIGINT, loop))) loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(shutdown(signal.SIGINT, loop)))
loop.create_task(web()) loop.create_task(web(loop))
loop.create_task(tcp()) loop.create_task(tcp(loop))
loop.create_task(lmtp()) loop.create_task(lmtp(loop))
n = sdnotify.SystemdNotifier() n = sdnotify.SystemdNotifier()
n.notify("READY=1") n.notify("READY=1")
log.info("Server ready") log.info("Server ready")
@ -123,8 +163,13 @@ def main():
loop.run_forever() loop.run_forever()
finally: finally:
loop.close() loop.close()
os.remove("lmtp.sock")
os.remove("test.sock")
os.remove("web.sock")
logging.info("Server stopped") logging.info("Server stopped")
logging.shutdown()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View file

@ -8,6 +8,7 @@ c3lf-nodes:
git_branch: master git_branch: master
git_repo: <git_repo_url> git_repo: <git_repo_url>
db_password: <db_password> db_password: <db_password>
mail_domain: <mail_domain>
main_email: <main_email> main_email: <main_email>
legacy_api_user: <legacy_api_user> legacy_api_user: <legacy_api_user>
legacy_api_password: <legacy_api_password> legacy_api_password: <legacy_api_password>

View file

@ -275,3 +275,27 @@
name: c3lf-sys3 name: c3lf-sys3
state: started state: started
enabled: yes enabled: yes
- name: add postfix to www-data group
user:
name: postfix
groups: www-data
append: yes
notify:
- restart postfix
- name: add custom transport config
lineinfile:
path: /etc/postfix/master.cf
line: "c3lf-sys3 unix - n n - - lmtp"
state: present
create: yes
notify:
- restart postfix
- name: configure postfix
template:
src: templates/postfix.cf.j2
dest: /etc/postfix/main.cf
notify:
- restart postfix

View file

@ -4,6 +4,7 @@ DB_NAME=c3lf_sys3
DB_USER=c3lf_sys3 DB_USER=c3lf_sys3
DB_PASSWORD={{ db_password }} DB_PASSWORD={{ db_password }}
HTTP_HOST={{ web_domain }} HTTP_HOST={{ web_domain }}
MAIL_DOMAIN={{ mail_domain }}
LEGACY_API_USER={{ legacy_api_user }} LEGACY_API_USER={{ legacy_api_user }}
LEGACY_API_PASSWORD={{ legacy_api_password }} LEGACY_API_PASSWORD={{ legacy_api_password }}
MEDIA_ROOT=/var/www/c3lf-sys3/userfiles MEDIA_ROOT=/var/www/c3lf-sys3/userfiles

View file

@ -0,0 +1,50 @@
# See /usr/share/postfix/main.cf.dist for a commented, more complete version
# Debian specific: Specifying a file name will cause the first
# line of that file to be used as the name. The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
readme_directory = no
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 3.6 on
# fresh installs.
compatibility_level = 3.6
# TLS parameters
smtp_use_tls = yes
smtp_force_tls = yes
smtpd_use_tls = yes
smtpd_tls_cert_file=/etc/letsencrypt/live/{{ web_domain }}/fullchain.pem;
smtpd_tls_key_file=/etc/letsencrypt/live/{{ web_domain }}/privkey.pem;
smtpd_tls_security_level=may
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = polaris.c3lf.de
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = $myhostname, , localhost
relayhost = firefly.lab.or.it
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
maillog_file = /var/log/mail.log
virtual_mailbox_domains = {{ mail_domain }}
virtual_transport=c3lf-sys3:unix:/var/www/c3lf-sys3/lmtp.sock