diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c68ed23..aa18c8ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## untagged +- Enforce end-to-end encryption for incoming messages. + New user address mailboxes now get a `enforceE2EEincoming` file + which prohibits incoming cleartext messages from other domains. + An outside MTA trying to submit a cleartext message will + get a "523 Encryption Needed" response, see RFC5248. + If the file does not exist (as it the case for all existing accounts) + incoming cleartext messages are accepted. + ([#538](https://github.com/chatmail/server/pull/538)) + - Enforce end-to-end encryption between local addresses ([#535](https://github.com/chatmail/server/pull/535)) diff --git a/README.md b/README.md index 6da94473..e13b79bc 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,23 @@ -# Chatmail servers for secure instant messaging +# Chatmail servers for end-to-end encrypted instant messaging -Chatmail servers are interoperable email routing machines optimized for: +Chatmail servers are interoperable e-mail routing machines optimized for - **Convenience:** Low friction instant onboarding - **Privacy:** No name, phone numbers, email required or collected + +- **End-to-End Encryption enforced**: only OpenPGP messages with metadata minimization allowed + - **Instant:** Privacy-preserving push notifications for Apple, Google, and Huawei - **Speed:** Message delivery in well under a second. -- **Security:** Strict TLS, DKIM and OpenPGP with metadata-minimization rules enforced. +- **Transport Security:** Strict TLS and DKIM enforced. -- **Reliability:** No spam or IP reputation checks, rate-limits suitable for realtime chats. +- **Reliability:** No spam or IP reputation checks; rate-limits are suitable for realtime chats. - **Efficiency:** Messages are only stored for transit and removed automatically. @@ -191,9 +194,9 @@ A short overview: to authenticate users to send mails for them. -- [`filtermail`](https://github.com/chatmail/server/blob/main/chatmaild/src/chatmaild/filtermail.py) prevents - unencrypted email from leaving the chatmail service - and is integrated into Postfix's outbound mail pipelines. +- [`filtermail`](https://github.com/chatmail/server/blob/main/chatmaild/src/chatmaild/filtermail.py) + prevents unencrypted email from leaving or entering the chatmail service + and is integrated into Postfix's outbound and inbound mail pipelines. - [`chatmail-metadata`](https://github.com/chatmail/server/blob/main/chatmaild/src/chatmaild/metadata.py) is contacted by a [dovecot lua script](https://github.com/chatmail/server/blob/main/cmdeploy/src/cmdeploy/dovecot/push_notification.lua) @@ -237,7 +240,6 @@ by the according markdown `.md` file in the `www/src` directory. ### Refining the web pages - ``` scripts/cmdeploy webdev ``` @@ -252,6 +254,23 @@ This starts a local live development cycle for chatmail web pages: - Starts a browser window automatically where you can "refresh" as needed. +## Mailbox directory layout + +Fresh chatmail server addresses have a mailbox directory that contains: + +- a `password` file with the salted password required for authenticating + whether a login may use the address to send/receive messages. + If you modify the password file manually, you effectively block the user. + +- `enforceE2EEincoming` is a default-created file with each address. + If present the file indicates that this chatmail address rejects incoming cleartext messages. + If absent the address accepts incoming cleartext messages. + +- `dovecot*`, `cur`, `new` and `tmp` represent IMAP/mailbox state. + If the address is only used by one device, the Maildir directories + will typically be empty unless the user of that address hasn't been online + for a while. + ## Emergency Commands to disable automatic account creation diff --git a/chatmaild/src/chatmaild/config.py b/chatmaild/src/chatmaild/config.py index 670b2042..e5e797a1 100644 --- a/chatmaild/src/chatmaild/config.py +++ b/chatmaild/src/chatmaild/config.py @@ -29,7 +29,13 @@ class Config: self.passthrough_senders = params["passthrough_senders"].split() self.passthrough_recipients = params["passthrough_recipients"].split() self.filtermail_smtp_port = int(params["filtermail_smtp_port"]) + self.filtermail_smtp_port_incoming = int( + params["filtermail_smtp_port_incoming"] + ) self.postfix_reinject_port = int(params["postfix_reinject_port"]) + self.postfix_reinject_port_incoming = int( + params["postfix_reinject_port_incoming"] + ) self.mtail_address = params.get("mtail_address") self.disable_ipv6 = params.get("disable_ipv6", "false").lower() == "true" self.imap_rawlog = params.get("imap_rawlog", "false").lower() == "true" diff --git a/chatmaild/src/chatmaild/filtermail.py b/chatmaild/src/chatmaild/filtermail.py index 25d92487..f66188e5 100644 --- a/chatmaild/src/chatmaild/filtermail.py +++ b/chatmaild/src/chatmaild/filtermail.py @@ -11,9 +11,12 @@ from email.utils import parseaddr from smtplib import SMTP as SMTPClient from aiosmtpd.controller import Controller +from aiosmtpd.smtp import SMTP from .config import read_config +ENCRYPTION_NEEDED_523 = "523 Encryption Needed: Invalid Unencrypted Mail" + def check_openpgp_payload(payload: bytes): """Checks the OpenPGP payload. @@ -157,9 +160,14 @@ def check_encrypted(message): return True -async def asyncmain_beforequeue(config): - port = config.filtermail_smtp_port - Controller(BeforeQueueHandler(config), hostname="127.0.0.1", port=port).start() +async def asyncmain_beforequeue(config, mode): + if mode == "outgoing": + port = config.filtermail_smtp_port + handler = OutgoingBeforeQueueHandler(config) + else: + port = config.filtermail_smtp_port_incoming + handler = IncomingBeforeQueueHandler(config) + HackedController(handler, hostname="127.0.0.1", port=port).start() def recipient_matches_passthrough(recipient, passthrough_recipients): @@ -171,7 +179,21 @@ def recipient_matches_passthrough(recipient, passthrough_recipients): return False -class BeforeQueueHandler: +class HackedController(Controller): + def factory(self): + return SMTPDiscardRCPTO_options(self.handler, **self.SMTP_kwargs) + + +class SMTPDiscardRCPTO_options(SMTP): + def _getparams(self, params): + # aiosmtpd's SMTP daemon fails to handle a request if there are RCPT TO options + # We just ignore them for our incoming filtermail purposes + if len(params) == 1 and params[0].startswith("ORCPT"): + return {} + return super()._getparams(params) + + +class OutgoingBeforeQueueHandler: def __init__(self, config): self.config = config self.send_rate_limiter = SendRateLimiter() @@ -210,28 +232,85 @@ class BeforeQueueHandler: _, from_addr = parseaddr(message.get("from").strip()) - logging.info(f"mime-from: {from_addr} envelope-from: {envelope.mail_from!r}") if envelope.mail_from.lower() != from_addr.lower(): return f"500 Invalid FROM <{from_addr!r}> for <{envelope.mail_from!r}>" - if mail_encrypted: - print("Filtering encrypted mail.", file=sys.stderr) - else: - print("Filtering unencrypted mail.", file=sys.stderr) + if mail_encrypted or is_securejoin(message): + print("Outgoing: Filtering encrypted mail.", file=sys.stderr) + return + + print("Outgoing: Filtering unencrypted mail.", file=sys.stderr) if envelope.mail_from in self.config.passthrough_senders: return - if mail_encrypted or is_securejoin(message): - return + # allow self-sent Autocrypt Setup Message + if envelope.rcpt_tos == [from_addr]: + if message.get("subject") == "Autocrypt Setup Message": + if message.get_content_type() == "multipart/mixed": + return passthrough_recipients = self.config.passthrough_recipients for recipient in envelope.rcpt_tos: if recipient_matches_passthrough(recipient, passthrough_recipients): continue + print("Rejected unencrypted mail.", file=sys.stderr) - return "500 Invalid unencrypted mail" + return ENCRYPTION_NEEDED_523 + + +class IncomingBeforeQueueHandler: + def __init__(self, config): + self.config = config + + async def handle_DATA(self, server, session, envelope): + logging.info("handle_DATA before-queue") + error = self.check_DATA(envelope) + if error: + return error + logging.info("re-injecting the mail that passed checks") + + # the smtp daemon on reinject_port_incoming gives it to dkim milter + # which looks at source address to determine whether to verify or sign + client = SMTPClient( + "localhost", + self.config.postfix_reinject_port_incoming, + source_address=("127.0.0.2", 0), + ) + client.sendmail( + envelope.mail_from, envelope.rcpt_tos, envelope.original_content + ) + return "250 OK" + + def check_DATA(self, envelope): + """the central filtering function for e-mails.""" + logging.info(f"Processing DATA message from {envelope.mail_from}") + + message = BytesParser(policy=policy.default).parsebytes(envelope.content) + mail_encrypted = check_encrypted(message) + + if mail_encrypted or is_securejoin(message): + print("Incoming: Filtering encrypted mail.", file=sys.stderr) + return + + print("Incoming: Filtering unencrypted mail.", file=sys.stderr) + + # we want cleartext mailer-daemon messages to pass through + # chatmail core will typically not display them as normal messages + if message.get("auto-submitted"): + _, from_addr = parseaddr(message.get("from").strip()) + if from_addr.lower().startswith("mailer-daemon@"): + if message.get_content_type() == "multipart/report": + return + + for recipient in envelope.rcpt_tos: + user = self.config.get_user(recipient) + if user is None or user.is_incoming_cleartext_ok(): + continue + + print("Rejected unencrypted mail.", file=sys.stderr) + return ENCRYPTION_NEEDED_523 class SendRateLimiter: @@ -250,11 +329,14 @@ class SendRateLimiter: def main(): args = sys.argv[1:] - assert len(args) == 1 + assert len(args) == 2 config = read_config(args[0]) + mode = args[1] logging.basicConfig(level=logging.WARN) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - task = asyncmain_beforequeue(config) + assert mode in ["incoming", "outgoing"] + task = asyncmain_beforequeue(config, mode) loop.create_task(task) + logging.info("entering serving loop") loop.run_forever() diff --git a/chatmaild/src/chatmaild/ini/chatmail.ini.f b/chatmaild/src/chatmaild/ini/chatmail.ini.f index 60fe1dcd..d9e5261a 100644 --- a/chatmaild/src/chatmaild/ini/chatmail.ini.f +++ b/chatmaild/src/chatmaild/ini/chatmail.ini.f @@ -46,12 +46,14 @@ passthrough_recipients = xstore@testrun.org # Deployment Details # -# where the filtermail SMTP service listens +# SMTP outgoing filtermail and reinjection filtermail_smtp_port = 10080 - -# postfix accepts on the localhost reinject SMTP port postfix_reinject_port = 10025 +# SMTP incoming filtermail and reinjection +filtermail_smtp_port_incoming = 10081 +postfix_reinject_port_incoming = 10026 + # if set to "True" IPv6 is disabled disable_ipv6 = False diff --git a/chatmaild/src/chatmaild/tests/mail-data/asm.eml b/chatmaild/src/chatmaild/tests/mail-data/asm.eml new file mode 100644 index 00000000..d7bf6baa --- /dev/null +++ b/chatmaild/src/chatmaild/tests/mail-data/asm.eml @@ -0,0 +1,56 @@ +From: {from_addr} +To: {to_addr} +Autocrypt-Setup-Message: v1 +Subject: Autocrypt Setup Message +Date: Tue, 22 Jan 2019 12:56:29 +0100 +Content-type: multipart/mixed; boundary="Y6fyGi9SoGeH8WwRaEdC6bbBcYOedDzrQ" + +--Y6fyGi9SoGeH8WwRaEdC6bbBcYOedDzrQ +Content-Type: text/plain + +This message contains all information to transfer your Autocrypt +settings along with your secret key securely from your original +device. + +To set up your new device for Autocrypt, please follow the +instuctions that should be presented by your new device. + +You can keep this message and use it as a backup for your secret +key. If you want to do this, you should write down the Setup Code +and store it securely. +--Y6fyGi9SoGeH8WwRaEdC6bbBcYOedDzrQ +Content-Type: application/autocrypt-setup +Content-Disposition: attachment; filename="autocrypt-setup-message.html" + + +

+This is the Autocrypt setup file used to transfer settings and +keys between clients. You can decrypt it using the Setup Code +presented on your old device, and then import the contained key +into your keyring. +

+ +
+-----BEGIN PGP MESSAGE-----
+Passphrase-Format: numeric9x4
+Passphrase-Begin: 17
+
+jA0EBwMCFAxADoCdzeX/0ukBlqI5+pfpKb751qd/7nLNbkpy3gVcaf1QwRPZYt40
+Ynp08UqRQ2g48ZlnzHLSwlTGOPTuv2Jt8ka+pgZ45xzvJSG2gau03xP4VsC271kR
+VmCjdb0Y6Rk96mAwfGzrkbaRQ9Z7fIoL866GOv6h9neiVIkp+JYlTV6ISD0ZQJ4Q
+I6dOQkB/TWZyVjtiJDOQHdfNWliA6NtqaLq19wlu9L5xXjuNpY95KwR8EJXWe0+o
+Y3d2U/KxOAkXKghP2Qg1GtlPVeGC5T4p03TGI6pzKT+kHX6Rrm9wK6sM9aTquMmF
+Vok84Jg1DFnwivWC2RILR81rXi7k/+Y6MUbveFgJ9cQduqpxnmD7TjOblYu7M6zp
+YGAUxh8DRKlIMn2QsA++DBYQ6ACZvwuY8qTDLkqPDo4WqM313dsMJbyGjDdVE7EM
+PESS+RlABETpZXz8g/ycr6DIUNdlbPcmYlsBfHWDOuR2GFFTwmlv5slWS39dJv38
+E0eIe1CwdxI801Se7t7dUUS/ZF8wb6GlmxOcqGbF8eko1Z0S64IAm7/h13MRQCxI
+geQnHfGYVJ2FOimoCMEKwfa9x++RFTDW0u7spDC2uWvK/1viV8OfRppFhLr/kmKb
+18lWXuAz80DAjUDUsVqEq2MvJBJGoCJUEyjuRsLkHYRM5jYk4v50LyyR0Om73nWF
+nZBqmqNzdr7Xb9PHHdFhnEc0VvoYbrcM0RVYcEMW3YbmejM891j1d6Iv+/n/qND/
+NdebGrfWJMmFLf/iEkzTZ3/v5inW9LpWoRc94ioCjJTaEo8Rib6ARRFaJVIsmNXi
+YicFGO98D+zX+a2t9Yz6IpPajVslnOp6ScpmXgts/2XWD7oE+JgxSAqo/dLVsHgP
+Ufo=
+=pulM
+-----END PGP MESSAGE-----
+
+--Y6fyGi9SoGeH8WwRaEdC6bbBcYOedDzrQ-- diff --git a/chatmaild/src/chatmaild/tests/mail-data/mailer-daemon.eml b/chatmaild/src/chatmaild/tests/mail-data/mailer-daemon.eml new file mode 100644 index 00000000..66e7c7c2 --- /dev/null +++ b/chatmaild/src/chatmaild/tests/mail-data/mailer-daemon.eml @@ -0,0 +1,46 @@ +Date: Fri, 8 Jul 1994 09:21:47 -0400 +From: Mail Delivery Subsystem +Subject: Returned mail: User unknown +To: +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="JAA13167.773673707/CS.UTK.EDU" + +--JAA13167.773673707/CS.UTK.EDU +content-type: text/plain; charset=us-ascii + + ----- The following addresses had delivery problems ----- + (unrecoverable error) + (unrecoverable error) + +--JAA13167.773673707/CS.UTK.EDU +content-type: message/delivery-status + +Reporting-MTA: dns; cs.utk.edu + +Original-Recipient: rfc822;arathib@vnet.ibm.com +Final-Recipient: rfc822;arathib@vnet.ibm.com +Action: failed +Status: 5.0.0 (permanent failure) +Diagnostic-Code: smtp; + 550 'arathib@vnet.IBM.COM' is not a registered gateway user +Remote-MTA: dns; vnet.ibm.com + +Original-Recipient: rfc822;johnh@hpnjld.njd.hp.com +Final-Recipient: rfc822;johnh@hpnjld.njd.hp.com +Action: delayed +Status: 4.0.0 (hpnjld.njd.jp.com: host name lookup failure) + +Original-Recipient: rfc822;wsnell@sdcc13.ucsd.edu +Final-Recipient: rfc822;wsnell@sdcc13.ucsd.edu +Action: failed +Status: 5.0.0 +Diagnostic-Code: smtp; 550 user unknown +Remote-MTA: dns; sdcc13.ucsd.edu + +--JAA13167.773673707/CS.UTK.EDU +content-type: message/rfc822 + +[original message goes here] +--JAA13167.773673707/CS.UTK.EDU-- diff --git a/chatmaild/src/chatmaild/tests/test_filtermail.py b/chatmaild/src/chatmaild/tests/test_filtermail.py index efbfc17e..284dbef1 100644 --- a/chatmaild/src/chatmaild/tests/test_filtermail.py +++ b/chatmaild/src/chatmaild/tests/test_filtermail.py @@ -1,7 +1,8 @@ import pytest from chatmaild.filtermail import ( - BeforeQueueHandler, + IncomingBeforeQueueHandler, + OutgoingBeforeQueueHandler, SendRateLimiter, check_armored_payload, check_encrypted, @@ -18,7 +19,13 @@ def maildomain(): @pytest.fixture def handler(make_config, maildomain): config = make_config(maildomain) - return BeforeQueueHandler(config) + return OutgoingBeforeQueueHandler(config) + + +@pytest.fixture +def inhandler(make_config, maildomain): + config = make_config(maildomain) + return IncomingBeforeQueueHandler(config) def test_reject_forged_from(maildata, gencreds, handler): @@ -127,13 +134,27 @@ def test_cleartext_excempt_privacy(maildata, gencreds, handler): rcpt_tos = [to_addr, false_to] content = msg.as_bytes() - assert "500" in handler.check_DATA(envelope=env2) + assert "523" in handler.check_DATA(envelope=env2) -def test_cleartext_self_send_fails(maildata, gencreds, handler): +def test_cleartext_self_send_autocrypt_setup_message(maildata, gencreds, handler): from_addr = gencreds()[0] to_addr = from_addr + msg = maildata("asm.eml", from_addr=from_addr, to_addr=to_addr) + + class env: + mail_from = from_addr + rcpt_tos = [to_addr] + content = msg.as_bytes() + + assert not handler.check_DATA(envelope=env) + + +def test_cleartext_send_fails(maildata, gencreds, handler): + from_addr = gencreds()[0] + to_addr = gencreds()[0] + msg = maildata("plain.eml", from_addr=from_addr, to_addr=to_addr) class env: @@ -142,7 +163,41 @@ def test_cleartext_self_send_fails(maildata, gencreds, handler): content = msg.as_bytes() res = handler.check_DATA(envelope=env) - assert "500 Invalid unencrypted" in res + assert "523 Encryption Needed" in res + + +def test_cleartext_incoming_fails(maildata, gencreds, inhandler): + from_addr = gencreds()[0] + to_addr, password = gencreds() + + msg = maildata("plain.eml", from_addr=from_addr, to_addr=to_addr) + + class env: + mail_from = from_addr + rcpt_tos = [to_addr] + content = msg.as_bytes() + + user = inhandler.config.get_user(to_addr) + user.set_password(password) + res = inhandler.check_DATA(envelope=env) + assert "523 Encryption Needed" in res + + user.allow_incoming_cleartext() + assert not inhandler.check_DATA(envelope=env) + + +def test_cleartext_incoming_mailer_daemon(maildata, gencreds, inhandler): + from_addr = "mailer-daemon@example.org" + to_addr = gencreds()[0] + + msg = maildata("mailer-daemon.eml", from_addr=from_addr, to_addr=to_addr) + + class env: + mail_from = from_addr + rcpt_tos = [to_addr] + content = msg.as_bytes() + + assert not inhandler.check_DATA(envelope=env) def test_cleartext_passthrough_domains(maildata, gencreds, handler): @@ -166,7 +221,7 @@ def test_cleartext_passthrough_domains(maildata, gencreds, handler): rcpt_tos = [to_addr, false_to] content = msg.as_bytes() - assert "500" in handler.check_DATA(envelope=env2) + assert "523" in handler.check_DATA(envelope=env2) def test_cleartext_passthrough_senders(gencreds, handler, maildata): diff --git a/chatmaild/src/chatmaild/tests/test_user.py b/chatmaild/src/chatmaild/tests/test_user.py index e6f944f0..3e7b5063 100644 --- a/chatmaild/src/chatmaild/tests/test_user.py +++ b/chatmaild/src/chatmaild/tests/test_user.py @@ -40,3 +40,17 @@ def test_no_mailboxes_dir(testaddr, example_config, tmp_path): user.set_password("someeqkjwelkqwjleqwe") user.set_last_login_timestamp(100000) assert user.get_last_login_timestamp() == 86400 + + +def test_set_get_cleartext_flag(testaddr, example_config, tmp_path): + p = tmp_path.joinpath("a", "mailboxes") + example_config.mailboxes_dir = p + + user = example_config.get_user(testaddr) + user.set_password("someeqkjwelkqwjleqwe") + user.set_last_login_timestamp(100000) + assert user.get_last_login_timestamp() == 86400 + + assert not user.is_incoming_cleartext_ok() + user.allow_incoming_cleartext() + assert user.is_incoming_cleartext_ok() diff --git a/chatmaild/src/chatmaild/user.py b/chatmaild/src/chatmaild/user.py index e9030e40..3ef4fc81 100644 --- a/chatmaild/src/chatmaild/user.py +++ b/chatmaild/src/chatmaild/user.py @@ -13,6 +13,7 @@ class User: self.maildir = maildir self.addr = addr self.password_path = password_path + self.enforce_E2EE_path = maildir.joinpath("enforceE2EEincoming") self.uid = uid self.gid = gid @@ -35,6 +36,13 @@ class User: home = str(self.maildir) return dict(addr=self.addr, home=home, uid=self.uid, gid=self.gid, password=pw) + def is_incoming_cleartext_ok(self): + return not self.enforce_E2EE_path.exists() + + def allow_incoming_cleartext(self): + if self.enforce_E2EE_path.exists(): + self.enforce_E2EE_path.unlink() + def set_password(self, enc_password): """Set the specified password for this user. @@ -50,6 +58,7 @@ class User: if not self.addr.startswith("echo@"): logging.error(f"could not write password for: {self.addr}") raise + self.enforce_E2EE_path.touch() def set_last_login_timestamp(self, timestamp): """Track login time with daily granularity diff --git a/cmdeploy/src/cmdeploy/__init__.py b/cmdeploy/src/cmdeploy/__init__.py index a80db7c2..e12c53cc 100644 --- a/cmdeploy/src/cmdeploy/__init__.py +++ b/cmdeploy/src/cmdeploy/__init__.py @@ -106,12 +106,14 @@ def _install_remote_venv_with_chatmaild(config) -> None: for fn in ( "doveauth", "filtermail", + "filtermail-incoming", "echobot", "chatmail-metadata", "lastlogin", ): + execpath = fn if fn != "filtermail-incoming" else "filtermail" params = dict( - execpath=f"{remote_venv_dir}/bin/{fn}", + execpath=f"{remote_venv_dir}/bin/{execpath}", config_path=remote_chatmail_inipath, remote_venv_dir=remote_venv_dir, mail_domain=config.mail_domain, @@ -541,7 +543,6 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: server.group(name="Create vmail group", group="vmail", system=True) server.user(name="Create vmail user", user="vmail", group="vmail", system=True) - server.user(name="Create filtermail user", user="filtermail", system=True) server.group(name="Create opendkim group", group="opendkim", system=True) server.user( name="Create opendkim user", diff --git a/cmdeploy/src/cmdeploy/postfix/master.cf.j2 b/cmdeploy/src/cmdeploy/postfix/master.cf.j2 index 494cd0ce..3573da7b 100644 --- a/cmdeploy/src/cmdeploy/postfix/master.cf.j2 +++ b/cmdeploy/src/cmdeploy/postfix/master.cf.j2 @@ -14,7 +14,7 @@ smtp inet n - y - - smtpd -v {%- else %} smtp inet n - y - - smtpd {%- endif %} - -o smtpd_milters=unix:opendkim/opendkim.sock + -o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port_incoming }} submission inet n - y - 5000 smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt @@ -76,12 +76,17 @@ anvil unix - - y - 1 anvil scache unix - - y - 1 scache postlog unix-dgram n - n - 1 postlogd filter unix - n n - - lmtp -# Local SMTP server for reinjecting filered mail. +# Local SMTP server for reinjecting outgoing filtered mail. localhost:{{ config.postfix_reinject_port }} inet n - n - 10 smtpd -o syslog_name=postfix/reinject -o smtpd_milters=unix:opendkim/opendkim.sock -o cleanup_service_name=authclean +# Local SMTP server for reinjecting incoming filtered mail +localhost:{{ config.postfix_reinject_port_incoming }} inet n - n - 10 smtpd + -o syslog_name=postfix/reinject_incoming + -o smtpd_milters=unix:opendkim/opendkim.sock + # Cleanup `Received` headers for authenticated mail # to avoid leaking client IP. # diff --git a/cmdeploy/src/cmdeploy/service/filtermail-incoming.service.f b/cmdeploy/src/cmdeploy/service/filtermail-incoming.service.f new file mode 100644 index 00000000..844843e4 --- /dev/null +++ b/cmdeploy/src/cmdeploy/service/filtermail-incoming.service.f @@ -0,0 +1,12 @@ +[Unit] +Description=Incoming Chatmail Postfix before queue filter + +[Service] +ExecStart={execpath} {config_path} incoming +Restart=always +RestartSec=30 +User=vmail + +[Install] +WantedBy=multi-user.target + diff --git a/cmdeploy/src/cmdeploy/service/filtermail.service.f b/cmdeploy/src/cmdeploy/service/filtermail.service.f index d0f9d3f5..398d5a89 100644 --- a/cmdeploy/src/cmdeploy/service/filtermail.service.f +++ b/cmdeploy/src/cmdeploy/service/filtermail.service.f @@ -1,11 +1,11 @@ [Unit] -Description=Chatmail Postfix before queue filter +Description=Outgoing Chatmail Postfix before queue filter [Service] -ExecStart={execpath} {config_path} +ExecStart={execpath} {config_path} outgoing Restart=always RestartSec=30 -User=filtermail +User=vmail [Install] WantedBy=multi-user.target diff --git a/cmdeploy/src/cmdeploy/tests/online/benchmark.py b/cmdeploy/src/cmdeploy/tests/online/benchmark.py index 01de017b..ab015011 100644 --- a/cmdeploy/src/cmdeploy/tests/online/benchmark.py +++ b/cmdeploy/src/cmdeploy/tests/online/benchmark.py @@ -37,7 +37,7 @@ class TestDC: def test_ping_pong(self, benchmark, cmfactory): ac1, ac2 = cmfactory.get_online_accounts(2) - chat = cmfactory.get_accepted_chat(ac1, ac2) + chat = cmfactory.get_protected_chat(ac1, ac2) def dc_ping_pong(): chat.send_text("ping") @@ -49,7 +49,7 @@ class TestDC: def test_send_10_receive_10(self, benchmark, cmfactory, lp): ac1, ac2 = cmfactory.get_online_accounts(2) - chat = cmfactory.get_accepted_chat(ac1, ac2) + chat = cmfactory.get_protected_chat(ac1, ac2) def dc_send_10_receive_10(): for i in range(10): diff --git a/cmdeploy/src/cmdeploy/tests/online/test_2_deltachat.py b/cmdeploy/src/cmdeploy/tests/online/test_2_deltachat.py index cb31a471..b50dc5b6 100644 --- a/cmdeploy/src/cmdeploy/tests/online/test_2_deltachat.py +++ b/cmdeploy/src/cmdeploy/tests/online/test_2_deltachat.py @@ -173,7 +173,7 @@ def test_echobot(cmfactory, chatmail_config, lp, sshdomain): ac._evtracker.wait_securejoin_joiner_progress(1000) # send message and check it gets replied back - lp.sec(f"Send message to echobot") + lp.sec("Send message to echobot") text = "hi, I hope you text me back" chat.send_text(text) lp.sec("Wait for reply from echobot") diff --git a/cmdeploy/src/cmdeploy/tests/plugin.py b/cmdeploy/src/cmdeploy/tests/plugin.py index b4f17fa7..4ffd7a1e 100644 --- a/cmdeploy/src/cmdeploy/tests/plugin.py +++ b/cmdeploy/src/cmdeploy/tests/plugin.py @@ -62,7 +62,7 @@ def sshdomain(maildomain): def maildomain2(): domain = os.environ.get("CHATMAIL_DOMAIN2") if not domain: - pytest.skip("set CHATMAIL_DOMAIN2 to a ssh-reachable chatmail instance") + pytest.skip("set CHATMAIL_DOMAIN2 to a second chatmail server") return domain