Compare commits

...

37 Commits

Author SHA1 Message Date
link2xt
54ad9551d1 Move MX record to its own subdomain 2024-11-22 13:50:38 +00:00
link2xt
95f8c4b269 Update iroh and remove iroh. subdomain 2024-11-09 01:02:20 +00:00
missytake
12217437e3 cmdeploy: install curl for downloading iroh 2024-11-02 15:54:11 +00:00
missytake
35a254fc1c acmetool: only request iroh certificate if it's required 2024-10-31 18:10:58 +01:00
missytake
2c0b659893 dns: add iroh CNAME to zonefile 2024-10-31 18:10:58 +01:00
holger krekel
fe51dbd844 streamline 2024-10-31 17:30:09 +01:00
holger krekel
99fbe1d4c4 Apply suggestions from code review
Co-authored-by: missytake <missytake@systemli.org>
2024-10-31 17:30:09 +01:00
holger krekel
d3e71aa394 streamline intro, mention IP addresses 2024-10-31 17:30:09 +01:00
holger krekel
72df078d02 add support for specifying whole domains for passthrough 2024-10-30 17:17:08 +01:00
missytake
8ea96e505e dovecot: fix syntax error 2024-10-30 16:34:53 +01:00
missytake
a5fd5cfb55 dovecot: disable anvil authentication penalty
fix #441
2024-10-30 16:34:53 +01:00
missytake
3098afb342 CI: fix accepting ns.testrun.org SSH Host Key 2024-10-30 13:30:44 +01:00
missytake
dfc1042a3f CI: fix #422 nested acme&dkimkeys folders 2024-10-30 13:30:44 +01:00
holger krekel
af17b459ba also change privacy policy to circumscribe iroh-relay services 2024-10-30 13:30:44 +01:00
missytake
aae05ac832 CI: set necessary DNS records before cmdeploy run, so it doesn't fail 2024-10-30 13:30:44 +01:00
link2xt
5048bde6d0 Deploy iroh relay 2024-10-30 13:30:44 +01:00
missytake
b92d9c889b doc: use ssh+tar to transfer vmail + dkimkeys as well 2024-10-29 17:17:17 +01:00
link2xt
c35c44ad8d Replace rsync with tar 2024-10-29 17:17:17 +01:00
missytake
a9779d7e7c add changelog 2024-10-29 17:17:17 +01:00
missytake
70f77a93ea doc: fix step 9 -> step 6
Co-authored-by: holger krekel  <holger@merlinux.eu>
2024-10-29 17:17:17 +01:00
missytake
ebed7ebf5e doc: migration guide should use new --ssh-host command 2024-10-29 17:17:17 +01:00
missytake
648bf53e83 Guide on how to migrate chatmail to a new host
This guide doesn't require knowing about firewalls,
but utilizes the `cmdeploy run --disable-mail` command from #428.

supercedes #417
2024-10-29 17:17:17 +01:00
missytake
75f11e68de updated privacy policy to testrun UG 2024-10-29 16:53:33 +01:00
missytake
579e6fd1cd added changelog 2024-10-29 16:53:04 +01:00
missytake
30392df901 cmdeploy: add argument to specify different SSH host than mail_domain 2024-10-29 16:53:04 +01:00
link2xt
7f3f69fa72 fix: increase request_queue_size for UNIX sockets to 1000
Default value is 5.
This setting was lost during refactoring in commit bf0f6e2303
2024-10-27 14:20:42 +00:00
missytake
3e646efee9 add PR link to CHANGELOG.md 2024-10-27 12:23:03 +01:00
Mark Felder
8fe173439d Dovecot quota_max_mail_size to use the Chatmail max_message_size value 2024-10-27 12:23:03 +01:00
holger krekel
48fdff6700 fix wrong ref in changelog 2024-10-23 13:49:46 +02:00
link2xt
5055434e48 Fix OpenPGP payload check
Replace \r\r\n in literal.eml test with \r\n
to make `test_filtermail_no_literal_packets`
actually reach `check_openpgp_payload()`
and make `check_openpgp_payload()` more strict.
2024-10-22 18:41:27 +00:00
missytake
bbf508d95e docs: nicer linebreaks 2024-10-16 16:45:06 +02:00
missytake
80cbdda772 docs: mention the chatmail.ini in the cmdeploy description 2024-10-16 16:45:06 +02:00
missytake
babdff361c docs: more details for the repo overview #419 2024-10-16 16:45:06 +02:00
missytake
15f30d8841 cmdeploy: flag to disable postfix + dovecot for migration 2024-10-16 12:15:59 +02:00
link2xt
737ab54bf2 ci: test cmdeploy dns only once
It should be reliable.
2024-10-16 12:06:55 +02:00
link2xt
20fa5d9656 Query autoritative nameserver directly to bypass DNS cache
unbound-control is not installed out of the box
and even once installed `flush_zone` does not seem
to work reliably.

Instead of trying to flush the cache from unbound,
we now query authoritative nameserver directly using `dig`.
2024-10-15 22:19:47 +00:00
link2xt
a2f2e04ff9 fix: set acme_account_url even if some DNS records are not set
perform_initial_checks may exit early
and not add `acme_account_url` if required DNS
records are not found.
In this case other `cmdeploy run` fails
with KeyError.

To avoid this, `acme_account_url` should always be set.

Unlike DNS checks, running acmetool
may not fail due to network errors,
so it is more reliable and should be checked first.
2024-10-15 16:10:36 +00:00
24 changed files with 489 additions and 122 deletions

View File

@@ -17,4 +17,6 @@ $TTL 300
;; DNS records.
@ IN A 37.27.95.249
mta-sts.staging-ipv4.testrun.org. CNAME staging-ipv4.testrun.org.
iroh.staging-ipv4.testrun.org. CNAME staging-ipv4.testrun.org.
www.staging-ipv4.testrun.org. CNAME staging-ipv4.testrun.org.
mx.staging-ipv4.testrun.org. CNAME staging-ipv4.testrun.org.

View File

@@ -17,5 +17,7 @@ $TTL 300
;; DNS records.
@ IN A 37.27.24.139
mta-sts.staging2.testrun.org. CNAME staging2.testrun.org.
iroh.staging2.testrun.org. CNAME staging2.testrun.org.
www.staging2.testrun.org. CNAME staging2.testrun.org.
mx.staging2.testrun.org. CNAME staging2.testrun.org.

View File

@@ -38,7 +38,9 @@ jobs:
if [ -f dkimkeys-ipv4/dkimkeys/opendkim.private ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" dkimkeys-ipv4 root@ns.testrun.org:/tmp/ || true; fi
if [ "$(ls -A acme-ipv4/acme/certs)" ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" acme-ipv4 root@ns.testrun.org:/tmp/ || true; fi
# make sure CAA record isn't set
scp .github/workflows/staging-ipv4.testrun.org-default.zone root@ns.testrun.org:/etc/nsd/staging-ipv4.testrun.org.zone
ssh -o StrictHostKeyChecking=accept-new root@ns.testrun.org sed -i '/CAA/d' /etc/nsd/staging-ipv4.testrun.org.zone
ssh root@ns.testrun.org nsd-checkzone staging-ipv4.testrun.org /etc/nsd/staging-ipv4.testrun.org.zone
ssh root@ns.testrun.org systemctl reload nsd
- name: rebuild staging-ipv4.testrun.org to have a clean VPS
@@ -64,8 +66,8 @@ jobs:
rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/acme-ipv4 acme-restore || true
rsync -avz root@ns.testrun.org:/tmp/dkimkeys-ipv4 dkimkeys-restore || true
# restore acme & dkim state to staging2.testrun.org
rsync -avz acme-restore/acme-ipv4/acme root@staging-ipv4.testrun.org:/var/lib/acme || true
rsync -avz dkimkeys-restore/dkimkeys-ipv4/dkimkeys root@staging-ipv4.testrun.org:/etc/dkimkeys || true
rsync -avz acme-restore/acme-ipv4/acme root@staging-ipv4.testrun.org:/var/lib/ || true
rsync -avz dkimkeys-restore/dkimkeys-ipv4/dkimkeys root@staging-ipv4.testrun.org:/etc/ || true
ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org chown root:root -R /var/lib/acme || true
- name: run formatting checks
@@ -93,6 +95,6 @@ jobs:
- name: cmdeploy test
run: CHATMAIL_DOMAIN2=nine.testrun.org cmdeploy test --slow
- name: cmdeploy dns (try 3 times)
run: cmdeploy dns || cmdeploy dns || cmdeploy dns
- name: cmdeploy dns
run: cmdeploy dns -v

View File

@@ -38,7 +38,9 @@ jobs:
if [ -f dkimkeys/opendkim.private ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" dkimkeys root@ns.testrun.org:/tmp/ || true; fi
if [ "$(ls -A acme/certs)" ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" acme root@ns.testrun.org:/tmp/ || true; fi
# make sure CAA record isn't set
ssh -o StrictHostKeyChecking=accept-new root@ns.testrun.org sed -i '/CAA/d' /etc/nsd/staging2.testrun.org.zone
scp -o StrictHostKeyChecking=accept-new .github/workflows/staging.testrun.org-default.zone root@ns.testrun.org:/etc/nsd/staging2.testrun.org.zone
ssh root@ns.testrun.org sed -i '/CAA/d' /etc/nsd/staging2.testrun.org.zone
ssh root@ns.testrun.org nsd-checkzone staging2.testrun.org /etc/nsd/staging2.testrun.org.zone
ssh root@ns.testrun.org systemctl reload nsd
- name: rebuild staging2.testrun.org to have a clean VPS
@@ -64,8 +66,8 @@ jobs:
rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/acme acme-restore || true
rsync -avz root@ns.testrun.org:/tmp/dkimkeys dkimkeys-restore || true
# restore acme & dkim state to staging2.testrun.org
rsync -avz acme-restore/acme/ root@staging2.testrun.org:/var/lib/acme || true
rsync -avz dkimkeys-restore/dkimkeys/ root@staging2.testrun.org:/etc/dkimkeys || true
rsync -avz acme-restore/acme root@staging2.testrun.org:/var/lib/ || true
rsync -avz dkimkeys-restore/dkimkeys root@staging2.testrun.org:/etc/ || true
ssh -o StrictHostKeyChecking=accept-new -v root@staging2.testrun.org chown root:root -R /var/lib/acme || true
- name: run formatting checks
@@ -91,6 +93,6 @@ jobs:
- name: cmdeploy test
run: CHATMAIL_DOMAIN2=nine.testrun.org cmdeploy test --slow
- name: cmdeploy dns (try 3 times)
run: cmdeploy dns -v || cmdeploy dns -v || cmdeploy dns -v
- name: cmdeploy dns
run: cmdeploy dns -v

View File

@@ -2,7 +2,27 @@
## untagged
- add mtail support (new optional `mail_address` ini value)
- deploy `iroh-relay` and also update "realtime relay services" in privacy policy.
([#434](https://github.com/deltachat/chatmail/pull/434))
([#451](https://github.com/deltachat/chatmail/pull/451))
- add guide to migrate chatmail to a new server
([#429](https://github.com/deltachat/chatmail/pull/429))
- disable anvil authentication penalty
([#414](https://github.com/deltachat/chatmail/pull/444)
- increase `request_queue_size` for UNIX sockets to 1000.
([#437](https://github.com/deltachat/chatmail/pull/437))
- add argument to `cmdeploy run` for specifying
a different SSH host than `mail_domain`
([#439](https://github.com/deltachat/chatmail/pull/439))
- query autoritative nameserver to bypass DNS cache
([#424](https://github.com/deltachat/chatmail/pull/424))
- add mtail support (new optional `mtail_address` ini value)
This defines the address on which [`mtail`](https://google.github.io/mtail/)
exposes its metrics collected from the logs.
If you want to collect the metrics with Prometheus,
@@ -15,6 +35,9 @@
- fix checking for required DNS records
([#412](https://github.com/deltachat/chatmail/pull/412))
- add support for specifying whole domains for recipient passthrough list
([#408](https://github.com/deltachat/chatmail/pull/408))
- add a paragraph about "account deletion" to info page
([#405](https://github.com/deltachat/chatmail/pull/405))
@@ -36,6 +59,14 @@
- add IMAP capabilities instead of overwriting them
([#413](https://github.com/deltachat/chatmail/pull/413))
- fix OpenPGP payload check
([#435](https://github.com/deltachat/chatmail/pull/435))
- fix Dovecot quota_max_mail_size to use max_message_size config value
([#438](https://github.com/deltachat/chatmail/pull/438))
- Move MX record to its own subdomain
([#403](https://github.com/deltachat/chatmail/pull/403))
## 1.4.1 2024-07-31

157
README.md
View File

@@ -80,31 +80,99 @@ scripts/cmdeploy bench
## Overview of this repository
This repository drives the development of chatmail services,
comprised of minimal setups of
This repository has four directories:
- [postfix smtp server](https://www.postfix.org)
- [dovecot imap server](https://www.dovecot.org)
- [cmdeploy](https://github.com/deltachat/chatmail/tree/main/cmdeploy)
is a collection of configuration files
and a [pyinfra](https://pyinfra.com)-based deployment script.
as well as custom services that are integrated with these two:
- [chatmaild](https://github.com/deltachat/chatmail/tree/main/chatmaild)
is a python package containing several small services
which handle authentication,
trigger push notifications on new messages,
ensure that outbound mails are encrypted,
delete inactive users,
and some other minor things.
chatmaild can also be installed as a stand-alone python package.
- `chatmaild/src/chatmaild/doveauth.py` implements
- [www](https://github.com/deltachat/chatmail/tree/main/www)
contains the html, css, and markdown files
which make up a chatmail server's web page.
Edit them before deploying to make your chatmail server stand out.
- [scripts](https://github.com/deltachat/chatmail/tree/main/scripts)
offers two convenience tools for beginners;
`initenv.sh` installs the necessary dependencies to a local virtual environment,
and the `scripts/cmdeploy` script enables you
to run the `cmdeploy` command line tool in the local virtual environment.
### cmdeploy
The `cmdeploy/src/cmdeploy/cmdeploy.py` command line tool
helps with setting up and managing the chatmail service.
`cmdeploy init` creates the `chatmail.ini` config file.
`cmdeploy run` uses a [pyinfra](https://pyinfra.com/)-based [script](`cmdeploy/src/cmdeploy/__init__.py`)
to automatically install or upgrade all chatmail components on a server,
according to the `chatmail.ini` config.
The components of chatmail are:
- [postfix smtp server](https://www.postfix.org) accepts sent messages (both from your users and from other servers)
- [dovecot imap server](https://www.dovecot.org) stores messages for your users until they download them
- [nginx](https://nginx.org/) shows the web page with your privacy policy and additional information
- [acmetool](https://hlandau.github.io/acmetool/) manages TLS certificates for dovecot, postfix, and nginx
- [opendkim](http://www.opendkim.org/) for signing messages with DKIM and rejecting inbound messages without DKIM
- [mtail](https://google.github.io/mtail/) for collecting anonymized metrics in case you have monitoring
- and the chatmaild services, explained in the next section:
### chatmaild
chatmaild offers several commands
which differentiate a *chatmail* server from a classic mail server.
If you deploy them with cmdeploy,
they are run by systemd services in the background.
A short overview:
- [`doveauth`](https://github.com/deltachat/chatmail/blob/main/chatmaild/src/chatmaild/doveauth.py) implements
create-on-login account creation semantics and is used
by Dovecot during login authentication and by Postfix
which in turn uses [Dovecot SASL](https://doc.dovecot.org/configuration_manual/authentication/dict/#complete-example-for-authenticating-via-a-unix-socket)
to authenticate users
to send mails for them.
- `chatmaild/src/chatmaild/filtermail.py` prevents
- [`filtermail`](https://github.com/deltachat/chatmail/blob/main/chatmaild/src/chatmaild/filtermail.py) prevents
unencrypted e-mail from leaving the chatmail service
and is integrated into postfix's outbound mail pipelines.
There is also the `cmdeploy/src/cmdeploy/cmdeploy.py` command line tool
which helps with setting up and managing the chatmail service.
`cmdeploy run` uses [pyinfra-based scripting](https://pyinfra.com/)
in `cmdeploy/src/cmdeploy/__init__.py`
to automatically install all chatmail components on a server.
- [`chatmail-metadata`](https://github.com/deltachat/chatmail/blob/main/chatmaild/src/chatmaild/metadata.py) is contacted by a
[dovecot lua script](https://github.com/deltachat/chatmail/blob/main/cmdeploy/src/cmdeploy/dovecot/push_notification.lua)
to store user-specific server-side config.
On new messages,
it [passes the user's push notification token](https://github.com/deltachat/chatmail/blob/main/chatmaild/src/chatmaild/notifier.py)
to [notifications.delta.chat](https://delta.chat/help#instant-delivery)
so the push notifications on the user's phone can be triggered
by Apple/Google.
- [`delete_inactive_users`](https://github.com/deltachat/chatmail/blob/main/chatmaild/src/chatmaild/delete_inactive_users.py)
deletes users if they have not logged in for a very long time.
The timeframe can be configured in `chatmail.ini`.
- [`lastlogin`](https://github.com/deltachat/chatmail/blob/main/chatmaild/src/chatmaild/lastlogin.py)
is contacted by dovecot when a user logs in
and stores the date of the login.
- [`echobot`](https://github.com/deltachat/chatmail/blob/main/chatmaild/src/chatmaild/echo.py)
is a small bot for test purposes.
It simply echoes back messages from users.
- [`chatmail-metrics`](https://github.com/deltachat/chatmail/blob/main/chatmaild/src/chatmaild/metrics.py)
collects some metrics and displays them at `https://example.org/metrics`.
### Home page and getting started for users
@@ -188,6 +256,71 @@ and rejects incorrectly authenticated emails with [`reject_sender_login_mismatch
`From:` header must correspond to envelope MAIL FROM,
this is ensured by `filtermail` proxy.
## Migrating chatmail server to a new host
If you want to migrate chatmail from an old machine
to a new machine,
you can use these steps.
They were tested with a linux laptop;
you might need to adjust some of the steps to your environment.
Let's assume that your `mail_domain` is `mail.example.org`,
all involved machines run Debian 12,
your old server's IP address is `13.37.13.37`,
and your new server's IP address is `13.12.23.42`.
During the guide, you might get a warning about changed SSH Host keys;
in this case, just run `ssh-keygen -R "mail.example.org"` as recommended
to make sure you can connect with SSH.
1. First, copy `/var/lib/acme` to the new server with
`ssh root@13.37.13.37 tar c /var/lib/acme | ssh root@13.12.23.42 tar x -C /var/lib/`.
This transfers your TLS certificate.
2. You should also copy `/etc/dkimkeys` to the new server with
`ssh root@13.37.13.37 tar c /etc/dkimkeys | ssh root@13.12.23.42 tar x -C /etc/`
so the DKIM DNS record stays correct.
3. On the new server, run `chown root: -R /var/lib/acme` and `chown root: -R /etc/dkimkeys` to make sure the permissions are correct.
4. Run `cmdeploy run --disable-mail --ssh-host 13.12.23.42` to install chatmail on the new machine.
postfix and dovecot are disabled for now,
we will enable them later.
5. Now, point DNS to the new IP addresses.
You can already remove the old IP addresses from DNS.
Existing Delta Chat users will still be able to connect
to the old server, send and receive messages,
but new users will fail to create new profiles
with your chatmail server.
If other servers try to deliver messages to your new server they will fail,
but normally email servers will retry delivering messages
for at least a week, so messages will not be lost.
6. Now you can run `cmdeploy run --disable-mail --ssh-host 13.37.13.37` to disable your old server.
Now your users will notice the migration
and will not be able to send or receive messages
until the migration is completed.
7. After everything is stopped,
you can copy the `/home/vmail/mail` directory to the new server.
It includes all user data, messages, password hashes, etc.
Just run: `ssh root@13.37.13.37 tar c /home/vmail/mail | ssh root@13.12.23.42 tar x -C /home/vmail/`
After this, your new server has all the necessary files to start operating :)
8. To be sure the permissions are still fine,
run `chown vmail: -R /home/vmail` on the new server.
9. Finally, you can run `cmdeploy run` to turn on chatmail on the new server.
Your users can continue using the chatmail server,
and messages which were sent after step 6. should arrive now.
Voilà!
## Setting up a reverse proxy
A chatmail server does not depend on the client IP address

View File

@@ -33,7 +33,12 @@ class Config:
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"
self.iroh_relay = params.get("iroh_relay")
if "iroh_relay" not in params:
self.iroh_relay = "https://" + params["mail_domain"]
self.enable_iroh_relay = True
else:
self.iroh_relay = params["iroh_relay"].strip()
self.enable_iroh_relay = False
self.privacy_postal = params.get("privacy_postal")
self.privacy_mail = params.get("privacy_mail")
self.privacy_pdo = params.get("privacy_pdo")

View File

@@ -87,8 +87,12 @@ class DictProxy:
except FileNotFoundError:
pass
with ThreadingUnixStreamServer(socket, Handler) as server:
with CustomThreadingUnixStreamServer(socket, Handler) as server:
try:
server.serve_forever()
except KeyboardInterrupt:
pass
class CustomThreadingUnixStreamServer(ThreadingUnixStreamServer):
request_queue_size = 1000

View File

@@ -60,10 +60,11 @@ def check_openpgp_payload(payload: bytes):
i += body_len
if i == len(payload):
if packet_type_id == 18:
# Last packet should be
# Symmetrically Encrypted and Integrity Protected Data Packet (SEIPD)
return True
# Last packet should be
# Symmetrically Encrypted and Integrity Protected Data Packet (SEIPD)
#
# This is the only place where this function may return `True`.
return packet_type_id == 18
elif packet_type_id not in [1, 3]:
# All packets except the last one must be either
# Public-Key Encrypted Session Key Packet (PKESK)
@@ -71,13 +72,7 @@ def check_openpgp_payload(payload: bytes):
# Symmetric-Key Encrypted Session Key Packet (SKESK)
return False
if i == 0:
return False
if i > len(payload):
# Payload is truncated.
return False
return True
return False
def check_armored_payload(payload: str):
@@ -147,6 +142,15 @@ async def asyncmain_beforequeue(config):
Controller(BeforeQueueHandler(config), hostname="127.0.0.1", port=port).start()
def recipient_matches_passthrough(recipient, passthrough_recipients):
for addr in passthrough_recipients:
if recipient == addr:
return True
if addr[0] == "@" and recipient.endswith(addr):
return True
return False
class BeforeQueueHandler:
def __init__(self, config):
self.config = config
@@ -210,7 +214,7 @@ class BeforeQueueHandler:
if envelope.mail_from == recipient:
# Always allow sending emails to self.
continue
if recipient in passthrough_recipients:
if recipient_matches_passthrough(recipient, passthrough_recipients):
continue
res = recipient.split("@")
if len(res) != 2:

View File

@@ -39,7 +39,7 @@ password_min_length = 9
passthrough_senders =
# list of e-mail recipients for which to accept outbound un-encrypted mails
# (space-separated)
# (space-separated, item may start with "@" to whitelist whole recipient domains)
passthrough_recipients = xstore@testrun.org
#
@@ -55,6 +55,13 @@ postfix_reinject_port = 10025
# if set to "True" IPv6 is disabled
disable_ipv6 = False
# Defaults to https://iroh.{{mail_domain}} and running `iroh-relay` on the chatmail
# service.
# If you set it to anything else, the service will be disabled
# and users will be directed to use the given iroh relay URL.
# Set it to empty string if you want users to use their default iroh relay.
# iroh_relay =
# Address on which `mtail` listens,
# e.g. 127.0.0.1 or some private network
# address like 192.168.10.1.

View File

@@ -1,44 +1,44 @@
From: {from_addr}
To: {to_addr}
Subject: ...
Date: Sun, 15 Oct 2023 16:43:21 +0000
Message-ID: <Mr.UVyJWZmkCKM.hGzNc6glBE_@c2.testrun.org>
In-Reply-To: <Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>
References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>
<Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>
Chat-Version: 1.0
Autocrypt: addr={from_addr}; prefer-encrypt=mutual;
keydata=xjMEZSwWjhYJKwYBBAHaRw8BAQdAQBEhqeJh0GueHB6kF/DUQqYCxARNBVokg/AzT+7LqH
rNFzxiYXJiYXpAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUsFo4CGwMECwkIBwYVCAkKCwID
FgIBFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX9A4AEAnHWHp49eBCMHK5t66gYPiW
XQuB1mwUjzGfYWB+0RXUoA/0xcQ3FbUNlGKW7Blp6eMFfViv6Mv2d3kNSXACB6nmcMzjgEZSwWjhIK
KwYBBAGXVQEFAQEHQBpY5L2M1XHo0uxf8SX1wNLBp/OVvidoWHQF2Jz+kJsUAwEIB8J4BBgWCAAgBQ
JlLBaOAhsMFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX/INgEA37AJaNvruYsJVanP
IXnYw4CKd55UAwl8Zcy+M2diAbkA/0fHHcGV4r78hpbbL1Os52DPOdqYQRauIeJUeG+G6bQO
MIME-Version: 1.0
Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
boundary="YFrteb74qSXmggbOxZL9dRnhymywAi"
From: {from_addr}
To: {to_addr}
Subject: ...
Date: Sun, 15 Oct 2023 16:43:21 +0000
Message-ID: <Mr.UVyJWZmkCKM.hGzNc6glBE_@c2.testrun.org>
In-Reply-To: <Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>
References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>
<Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>
Chat-Version: 1.0
Autocrypt: addr={from_addr}; prefer-encrypt=mutual;
keydata=xjMEZSwWjhYJKwYBBAHaRw8BAQdAQBEhqeJh0GueHB6kF/DUQqYCxARNBVokg/AzT+7LqH
rNFzxiYXJiYXpAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUsFo4CGwMECwkIBwYVCAkKCwID
FgIBFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX9A4AEAnHWHp49eBCMHK5t66gYPiW
XQuB1mwUjzGfYWB+0RXUoA/0xcQ3FbUNlGKW7Blp6eMFfViv6Mv2d3kNSXACB6nmcMzjgEZSwWjhIK
KwYBBAGXVQEFAQEHQBpY5L2M1XHo0uxf8SX1wNLBp/OVvidoWHQF2Jz+kJsUAwEIB8J4BBgWCAAgBQ
JlLBaOAhsMFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX/INgEA37AJaNvruYsJVanP
IXnYw4CKd55UAwl8Zcy+M2diAbkA/0fHHcGV4r78hpbbL1Os52DPOdqYQRauIeJUeG+G6bQO
MIME-Version: 1.0
Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
boundary="YFrteb74qSXmggbOxZL9dRnhymywAi"
--YFrteb74qSXmggbOxZL9dRnhymywAi
Content-Description: PGP/MIME version identification
Content-Type: application/pgp-encrypted
Version: 1
--YFrteb74qSXmggbOxZL9dRnhymywAi
Content-Description: OpenPGP encrypted message
Content-Disposition: inline; filename="encrypted.asc";
Content-Type: application/octet-stream; name="encrypted.asc"
-----BEGIN PGP MESSAGE-----
yxJiAAAAAABIZWxsbyB3b3JsZCE=
=1I/B
-----END PGP MESSAGE-----
--YFrteb74qSXmggbOxZL9dRnhymywAi--

View File

@@ -121,6 +121,30 @@ def test_excempt_privacy(maildata, gencreds, handler):
assert "500" in handler.check_DATA(envelope=env2)
def test_passthrough_domains(maildata, gencreds, handler):
from_addr = gencreds()[0]
to_addr = "privacy@x.y.z"
handler.config.passthrough_recipients = ["@x.y.z"]
false_to = "something@x.y"
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()
# assert that None/no error is returned
assert not handler.check_DATA(envelope=env)
class env2:
mail_from = from_addr
rcpt_tos = [to_addr, false_to]
content = msg.as_bytes()
assert "500" in handler.check_DATA(envelope=env2)
def test_passthrough_senders(gencreds, handler, maildata):
acc1 = gencreds()[0]
to_addr = "recipient@something.org"

View File

@@ -10,7 +10,7 @@ import sys
from pathlib import Path
from chatmaild.config import Config, read_config
from pyinfra import host
from pyinfra import host, facts
from pyinfra.facts.files import File
from pyinfra.facts.systemd import SystemdEnabled
from pyinfra.operations import apt, files, pip, server, systemd
@@ -479,10 +479,67 @@ def deploy_mtail(config):
)
def deploy_chatmail(config_path: Path) -> None:
def deploy_iroh_relay(config) -> None:
(url, sha256sum) = {
"x86_64": (
"https://github.com/n0-computer/iroh/releases/download/v0.28.1/iroh-relay-v0.28.1-x86_64-unknown-linux-musl.tar.gz",
"2ffacf7c0622c26b67a5895ee8e07388769599f60e5f52a3bd40a3258db89b2c",
),
"aarch64": (
"https://github.com/n0-computer/iroh/releases/download/v0.28.1/iroh-relay-v0.28.1-aarch64-unknown-linux-musl.tar.gz",
"b915037bcc1ff1110cc9fcb5de4a17c00ff576fd2f568cd339b3b2d54c420dc4",
),
}[host.get_fact(facts.server.Arch)]
apt.packages(
name="Install curl",
packages=["curl"],
)
server.shell(
name="Download iroh-relay",
commands=[
f"(echo '{sha256sum} /usr/local/bin/iroh-relay' | sha256sum -c) || (curl -L {url} | gunzip | tar -x -f - ./iroh-relay -O >/usr/local/bin/iroh-relay.new && mv /usr/local/bin/iroh-relay.new /usr/local/bin/iroh-relay)",
"chmod 755 /usr/local/bin/iroh-relay",
],
)
need_restart = False
systemd_unit = files.put(
name="Upload iroh-relay systemd unit",
src=importlib.resources.files(__package__).joinpath("iroh-relay.service"),
dest="/etc/systemd/system/iroh-relay.service",
user="root",
group="root",
mode="644",
)
need_restart |= systemd_unit.changed
iroh_config = files.put(
name=f"Upload iroh-relay config",
src=importlib.resources.files(__package__).joinpath("iroh-relay.toml"),
dest=f"/etc/iroh-relay.toml",
user="root",
group="root",
mode="644",
)
need_restart |= iroh_config.changed
systemd.service(
name="Start and enable iroh-relay",
service="iroh-relay.service",
running=True,
enabled=config.enable_iroh_relay,
restarted=need_restart,
)
def deploy_chatmail(config_path: Path, disable_mail: bool) -> None:
"""Deploy a chat-mail instance.
:param config_path: path to chatmail.ini
:param disable_mail: whether to disable postfix & dovecot
"""
config = read_config(config_path)
check_config(config)
@@ -507,6 +564,7 @@ def deploy_chatmail(config_path: Path) -> None:
system=True,
)
server.user(name="Create echobot user", user="echobot", system=True)
server.user(name="Create iroh user", user="iroh", system=True)
# Add our OBS repository for dovecot_no_delay
files.put(
@@ -555,9 +613,17 @@ def deploy_chatmail(config_path: Path) -> None:
enabled=True,
)
deploy_iroh_relay(config)
# Deploy acmetool to have TLS certificates.
tls_domains = [
mail_domain,
f"mta-sts.{mail_domain}",
f"www.{mail_domain}",
f"mx.{mail_domain}",
]
deploy_acmetool(
domains=[mail_domain, f"mta-sts.{mail_domain}", f"www.{mail_domain}"],
domains=tls_domains,
)
apt.packages(
@@ -624,19 +690,19 @@ def deploy_chatmail(config_path: Path) -> None:
# because it creates authentication socket
# required by Postfix.
systemd.service(
name="Start and enable Dovecot",
name="disable dovecot for now" if disable_mail else "Start and enable Dovecot",
service="dovecot.service",
running=True,
enabled=True,
restarted=dovecot_need_restart,
running=False if disable_mail else True,
enabled=False if disable_mail else True,
restarted=dovecot_need_restart if not disable_mail else False,
)
systemd.service(
name="Start and enable Postfix",
name="disable postfix for now" if disable_mail else "Start and enable Postfix",
service="postfix.service",
running=True,
enabled=True,
restarted=postfix_need_restart,
running=False if disable_mail else True,
enabled=False if disable_mail else True,
restarted=postfix_need_restart if not disable_mail else False,
)
systemd.service(

View File

@@ -7,10 +7,11 @@
{% if AAAA %}
{{ mail_domain }}. AAAA {{ AAAA }}
{% endif %}
{{ mail_domain }}. MX 10 {{ mail_domain }}.
{{ mail_domain }}. MX 10 mx.{{ mail_domain }}.
_mta-sts.{{ mail_domain }}. TXT "v=STSv1; id={{ sts_id }}"
mta-sts.{{ mail_domain }}. CNAME {{ mail_domain }}.
www.{{ mail_domain }}. CNAME {{ mail_domain }}.
mx.{{ mail_domain }}. CNAME {{ mail_domain }}.
{{ dkim_entry }}
;

View File

@@ -52,21 +52,36 @@ def run_cmd_options(parser):
action="store_true",
help="don't actually modify the server",
)
parser.add_argument(
"--disable-mail",
dest="disable_mail",
action="store_true",
help="install/upgrade the server, but disable postfix & dovecot for now"
)
parser.add_argument(
"--ssh-host",
dest="ssh_host",
help="specify an SSH host to deploy to; uses mail_domain from chatmail.ini by default"
)
def run_cmd(args, out):
"""Deploy chatmail services on the remote server."""
sshexec = args.get_sshexec()
require_iroh = args.config.enable_iroh_relay
remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain)
if not dns.check_initial_remote_data(remote_data, print=out.red):
return 1
env = os.environ.copy()
env["CHATMAIL_INI"] = args.inipath
env["CHATMAIL_DISABLE_MAIL"] = "True" if args.disable_mail else ""
env["CHATMAIL_REQUIRE_IROH"] = "True" if require_iroh else ""
deploy_path = importlib.resources.files(__package__).joinpath("deploy.py").resolve()
pyinf = "pyinfra --dry" if args.dry_run else "pyinfra"
cmd = f"{pyinf} --ssh-user root {args.config.mail_domain} {deploy_path} -y"
ssh_host = args.config.mail_domain if not args.ssh_host else args.ssh_host
cmd = f"{pyinf} --ssh-user root {ssh_host} {deploy_path} -y"
if version.parse(pyinfra.__version__) < version.parse("3"):
out.red("Please re-run scripts/initenv.sh to update pyinfra to version 3.")
return 1

View File

@@ -11,8 +11,9 @@ def main():
"CHATMAIL_INI",
importlib.resources.files("cmdeploy").joinpath("../../../chatmail.ini"),
)
disable_mail = bool(os.environ.get('CHATMAIL_DISABLE_MAIL'))
deploy_chatmail(config_path)
deploy_chatmail(config_path, disable_mail)
if pyinfra.is_cli:

View File

@@ -12,7 +12,7 @@ def get_initial_remote_data(sshexec, mail_domain):
)
def check_initial_remote_data(remote_data, print=print):
def check_initial_remote_data(remote_data, *, print=print):
mail_domain = remote_data["mail_domain"]
if not remote_data["A"] and not remote_data["AAAA"]:
print(f"Missing A and/or AAAA DNS records for {mail_domain}!")

View File

@@ -141,7 +141,7 @@ plugin {
# for now we define static quota-rules for all users
quota = maildir:User quota
quota_rule = *:storage={{ config.max_mailbox_size }}
quota_max_mail_size=30M
quota_max_mail_size={{ config.max_message_size }}
quota_grace = 0
# quota_over_flag_value = TRUE
}
@@ -194,6 +194,17 @@ service imap-login {
process_min_avail = 10
}
service anvil {
# We are disabling anvil penalty on failed login attempts
# because it can only detect brute forcing by IP address
# not by username. As the correct IP address is not handed
# to dovecot anyway, it is more of hindrance than of use.
# See <https://www.dovecot.org/list/dovecot/2012-May/135485.html> for details.
unix_listener anvil-auth-penalty {
mode = 0
}
}
ssl = required
ssl_cert = </var/lib/acme/live/{{ config.mail_domain }}/fullchain
ssl_key = </var/lib/acme/live/{{ config.mail_domain }}/privkey

View File

@@ -0,0 +1,12 @@
[Unit]
Description=Iroh relay
[Service]
ExecStart=/usr/local/bin/iroh-relay --config-path /etc/iroh-relay.toml
Restart=on-failure
RestartSec=5s
User=iroh
Group=iroh
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,5 @@
enable_relay = true
http_bind_addr = "[::]:3340"
enable_stun = true
enable_metrics = false
metrics_bind_addr = "127.0.0.1:9092"

View File

@@ -1,4 +1,4 @@
version: STSv1
mode: enforce
mx: {{ config.domain_name }}
mx: mx.{{ config.domain_name }}
max_age: 2419200

View File

@@ -96,6 +96,26 @@ http {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/newemail.py;
}
# Proxy to iroh-relay service.
location /relay {
proxy_pass http://127.0.0.1:3340;
proxy_http_version 1.1;
# Upgrade header is normally set to "iroh derp http" or "websocket".
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /relay/probe {
proxy_pass http://127.0.0.1:3340;
proxy_http_version 1.1;
}
location /generate_204 {
proxy_pass http://127.0.0.1:3340;
proxy_http_version 1.1;
}
}
# Redirect www. to non-www

View File

@@ -20,19 +20,18 @@ def perform_initial_checks(mail_domain):
assert mail_domain
if not shell("dig", fail_ok=True):
shell("apt-get install -y dnsutils")
shell(f"unbound-control flush_zone {mail_domain}", fail_ok=True)
A = query_dns("A", mail_domain)
AAAA = query_dns("AAAA", mail_domain)
MTA_STS = query_dns("CNAME", f"mta-sts.{mail_domain}")
WWW = query_dns("CNAME", f"www.{mail_domain}")
res = dict(mail_domain=mail_domain, A=A, AAAA=AAAA, MTA_STS=MTA_STS, WWW=WWW)
if not MTA_STS or not WWW or (not A and not AAAA):
return res
res["acme_account_url"] = shell("acmetool account-url", fail_ok=True)
res["dkim_entry"] = get_dkim_entry(mail_domain, dkim_selector="opendkim")
if not MTA_STS or not WWW or (not A and not AAAA):
return res
# parse out sts-id if exists, example: "v=STSv1; id=2090123"
parts = query_dns("TXT", f"_mta-sts.{mail_domain}").split("id=")
res["sts_id"] = parts[1].rstrip('"') if len(parts) == 2 else ""
@@ -53,8 +52,20 @@ def get_dkim_entry(mail_domain, dkim_selector):
def query_dns(typ, domain):
res = shell(f"dig -r -q {domain} -t {typ} +short")
print(res)
# Get autoritative nameserver from the SOA record.
soa_answers = [
x.split()
for x in shell(f"dig -r -q {domain} -t SOA +noall +authority +answer").split(
"\n"
)
]
soa = [a for a in soa_answers if len(a) >= 3 and a[3] == "SOA"]
if not soa:
return
ns = soa[0][4]
# Query authoritative nameserver directly to bypass DNS cache.
res = shell(f"dig @{ns} -r -q {domain} -t {typ} +short")
if res:
return res.split("\n")[0]
return ""
@@ -62,7 +73,6 @@ def query_dns(typ, domain):
def check_zonefile(zonefile, mail_domain):
"""Check expected zone file entries."""
shell(f"unbound-control flush_zone {mail_domain}", fail_ok=True)
required = True
required_diff = []
recommended_diff = []

View File

@@ -3,8 +3,8 @@
{% if config.mail_domain == "nine.testrun.org" %}
Welcome to `{{config.mail_domain}}`, the default chatmail onboarding server for Delta Chat users.
It is operated on the side by a small sysops team employed by [merlinux](https://merlinux.eu),
an open-source R&D company also acting as the fiscal sponsor of Delta Chat app developments.
It is operated on the side by a small sysops team
on a voluntary basis.
See [other chatmail servers](https://delta.chat/en/chatmail) for alternative server operators.
{% endif %}
@@ -23,18 +23,22 @@ A chatmail server behaves more like the Signal messaging server
but does not know about phone numbers and securely and automatically interoperates
with other chatmail and classic e-mail servers.
In particular, this chatmail server
Unlike classic e-mail servers, this chatmail server
- unconditionally removes messages after {{ config.delete_mails_after }} days,
- prohibits sending out un-encrypted messages,
- only has temporary log files used for debugging purposes.
- does not store Internet addresses ("IP addresses"),
- does not process IP addresses in relation to email addresses.
Due to the resulting lack of personal data processing
this chatmail server may not require a privacy policy.
Nevertheless, we provide legal details below to make life easier
for data protection specialists and lawyers scrutinizing chatmail operations.
Legally, authorities might still regard chatmail as a "classic e-mail" server
which collects and retains personal data.
We do not agree on this interpretation. Nevertheless, we provide more legal details below
to make life easier for data protection specialists and lawyers scrutinizing chatmail operations.
## 1. Name and contact information
@@ -54,18 +58,18 @@ We have appointed a data protection officer:
## 2. Processing when using chat e-mail services
We provide e-mail services optimized for the use from [Delta Chat](https://delta.chat) apps
We provide services optimized for the use from [Delta Chat](https://delta.chat) apps
and process only the data necessary
for the setup and technical execution of the e-mail dispatch.
The purpose of the processing is to
read, write, manage, delete, send, and receive emails.
for the setup and technical execution of message delivery.
The purpose of the processing is that users can
read, write, manage, delete, send, and receive chat messages.
For this purpose,
we operate server-side software
that enables us to send and receive e-mail messages.
Allowing the use of the e-mail service,
we process the following data and details:
that enables us to send and receive messages.
- Outgoing and incoming messages (SMTP) are stored for transit
We process the following data and details:
- Outgoing and incoming messages (SMTP) are stored for transit
on behalf of their users until the message can be delivered.
- E-Mail-Messages are stored for the recipient and made accessible via IMAP protocols,
@@ -74,9 +78,15 @@ we process the following data and details:
- IMAP and SMTP protocols are password protected with unique credentials for each account.
- Users can retrieve or delete all stored messages
- Users can retrieve or delete all stored messages
without intervention from the operators using standard IMAP client tools.
- Users can connect to a "realtime relay service"
to establish Peer-to-Peer connection between user devices,
allowing them to send and retrieve ephemeral messages
which are never stored on the chatmail server, also not in encrypted form.
### 2.1 Account setup
Creating an account happens in one of two ways on our mail servers:
@@ -253,7 +263,7 @@ is the `{{ config.privacy_supervisor }}`.
## 6. Validity of this privacy policy
This data protection declaration is valid
as of *December 2023*.
as of *October 2024*.
Due to the further development of our service and offers
or due to changed legal or official requirements,
it may become necessary to revise this data protection declaration from time to time.