Compare commits

...

64 Commits

Author SHA1 Message Date
link2xt
290525f0fc Remove DKIM-Signature from incoming mail after checking 2025-03-25 02:21:14 +00:00
bjoern
e004a5e2f6 Merge pull request #528 from chatmail/r10s/update-readme
update some links
2025-03-23 23:54:34 +01:00
B. Petersen
acf6e862d0 update some links 2025-03-23 20:42:25 +01:00
holger krekel
31faf2c78e remove Delta Chat mentionings 2025-03-21 21:47:24 +01:00
holger krekel
f8c28d8b9f clarify client/server setup for deploying a chatmail server 2025-03-21 21:41:51 +01:00
holger krekel
f69a2355f6 better prerequisites 2025-03-21 21:40:05 +01:00
holger krekel
388c01105c streamline intro/getting started 2025-03-21 21:36:26 +01:00
holger krekel
f8996e1d7d Update README.md
Co-authored-by: bjoern <r10s@b44t.com>
2025-03-21 20:32:38 +01:00
holger krekel
6b3d5025d9 Update README.md
Co-authored-by: bjoern <r10s@b44t.com>
2025-03-21 20:32:38 +01:00
holger krekel
ed271189d2 trim the feature list 2025-03-21 20:32:38 +01:00
holger krekel
65f8a9a652 provide more summary info/perequisites in the README 2025-03-21 20:32:38 +01:00
holger krekel
6c5b9fde1f Update README.md
Co-authored-by: l <link2xt@testrun.org>
2025-03-21 06:50:17 +01:00
holger krekel
258436442f rework intro to read when coming from chatmail.at 2025-03-21 06:50:17 +01:00
link2xt
05a32efa50 fix: send SNI when connecting to outside servers
Otherwise email providers which allow to bring your own domain
and use the same IP addresses for all customers
send wildcard certificate instead of the correct one
and Postfix refuses to connect with an error

    server certificate verification failed for example.org[A.B.C.D]:25: num=62:hostname mismatch
2025-03-16 11:21:16 +00:00
Mark Felder
1142d06fdb Limit the bind for the HTTPS server on 8443 to 127.0.0.1 2025-03-15 07:42:09 +00:00
link2xt
35fe189be7 Pass through original_content instead of content in filtermail
This avoids unnecessary UTF-8 recoding and passes bytestring through.
2025-03-11 13:27:16 +00:00
missytake
a78e8e10d2 Merge pull request #517 from chatmail/opendkim-path
opendkim: add absolute path to opendkim-genkey
2025-03-11 12:20:17 +01:00
missytake
9af37ccfbf opendkim: add absolute path to opendkim-genkey 2025-03-11 11:56:07 +01:00
l
803f3e6181 Merge pull request #514 from chatmail/link2xt/readme-tls
Document TLS requirements in the readme
2025-03-10 22:47:43 +00:00
link2xt
f188aef11e Document TLS requirements in the readme 2025-03-09 15:52:44 +00:00
link2xt
76d7e60018 Remove cleanup service from submission ports
It does not work because of `smtpd_proxy_filter`
forwarding the message to filtermail
and we cleanup the message once
filtermail reinjects it on port 10025.
2025-03-09 10:26:53 +00:00
link2xt
fe749159e4 Document that authclean cleans up the Subject 2025-03-08 02:42:35 +00:00
adbenitez
3c3532a292 update links in CHANGELOG.md 2025-03-06 22:10:15 +01:00
adb
710ca0070f Merge pull request #504 from chatmail/adb/delete-big-messages
delete big messages after 7 days
2025-03-04 17:40:44 +01:00
adbenitez
4038fefefd add changelog entry 2025-03-04 17:37:58 +01:00
Timotheus Pokorra
cdcdc0b724 update Let's encrypt Subscriber Agreement 2025-03-04 16:00:28 +01:00
adbenitez
2313093b55 delete big messages after 7 days 2025-03-03 17:19:15 +01:00
missytake
3f2ec54725 mtail: fix getting logs from STDIN 2025-02-25 16:23:13 +01:00
missytake
e928a33f95 opendkim: restart once every day (#498)
fix #495
2025-02-19 21:50:48 +01:00
missytake
2780f53d3b CI: accept ns.testrun.org host key (#499) 2025-02-19 21:24:23 +01:00
missytake
c3f1bdca52 filtermail: strip any empty lines at the end (#496) 2025-02-19 16:38:01 +01:00
missytake
f4e371676b chatmaild: fix umask for doveauth + metadata (#494)
* chatmaild: fix umask for doveauth + metadata

fix #453
2025-02-17 19:10:26 +01:00
link2xt
8ec6e6e985 opendkim: use su instead of sudo 2025-02-17 19:09:50 +01:00
missytake
f4fc1a3f93 CI: stop nested acme directories on staging-ipv4 2025-02-17 01:17:11 +01:00
missytake
42bfb9f22f journald: remove old logs from disk. (#490)
fix #486
2025-02-17 00:27:04 +01:00
link2xt
1a35cdc7a9 Require TLS 1.3 on client-facing ports
I tested with -tls1_2 option
of openssl s_client
that TLS 1.2 connections
are no longer possible
on any ports except port 25.

Port 25 requires at least TLS 1.2
for encrypted connections.
2025-02-16 23:01:56 +00:00
link2xt
2daac76574 Replace subject with [...] for outgoing mail
`authclean` cleanup server is used by
reinjecting smtpd running on localhost:10025 by default.
It runs after filtermail
and currently removes `Received` header
to avoid leaking IP address.
Can as well be used to replace `Subject` lines
with `Subject: [...]`.
If there are multiple `Subject` lines,
all of them should be replaced.

This allows us to avoid dealing with
localized subjects, including SecureJoin
messages `vc-request` and `vg-request`
which can have Subject lines like
Subject: =?utf-8?q?Nachricht_von_nrn178fi4=40nine=2Etestrun=2Eorg?=
2025-02-16 22:35:51 +00:00
link2xt
5633582d31 Add changelog entry for MTA-STS daemon removal 2025-02-16 21:27:15 +00:00
link2xt
667a987dfc Remove MTA-STS daemon 2025-02-16 20:31:07 +00:00
link2xt
49907c78a3 Add changelog entry for crypt compatibility 2025-02-16 15:15:31 +00:00
adb
5cfdb0698f use old crypt lib in python < 3.11 (#483) 2025-02-16 12:18:42 +00:00
link2xt
7e6f8ddfba Simplify SPF record
There is no need to explicitly specify domain for `a` rule.
2025-02-15 03:51:49 +00:00
adb
4d915f9800 improve secure-join message detection (#473) 2025-01-28 04:48:07 +00:00
l
9e6ba1a164 fix: install gcc and python3-dev (#477)
These are needed to build crypt-r
2025-01-27 14:38:18 +00:00
adbenitez
20f76c83f8 replace deprecated crypt package with crypt-r 2025-01-26 19:48:46 +00:00
link2xt
b2995551a2 ci: remove iroh relay from zonefiles
iroh subdomain is not needed
since 95f8c4b269
2025-01-26 19:22:45 +00:00
link2xt
c8f46147e0 chore: ruff 0.9.2 fixes and formatting 2025-01-24 20:57:13 +01:00
missytake
9f6ea8121c added changelog 2025-01-08 17:21:18 +01:00
missytake
9c08cbfbec DNS: recommend DKIM record without space in between for some DNS web interfaces 2025-01-08 17:21:18 +01:00
missytake
c3190dd51a doc: fix migration guide
fix #464
2025-01-08 16:55:10 +01:00
missytake
5b8de76c22 fix tests 2024-12-21 00:04:40 +01:00
missytake
d6205d9a04 add changelog 2024-12-21 00:04:40 +01:00
missytake
6a32192e50 Revert rest of #462
This reverts commit 88a8dc905b.
2024-12-21 00:04:40 +01:00
missytake
5c78619750 DNS: make --all non-optional for cmdeploy dns 2024-12-21 00:04:40 +01:00
missytake
a7b808ebaf Release 1.5.0 2024-12-20 10:53:36 +01:00
missytake
d11038b7b3 DNS: out() instead of print() 2024-12-20 10:46:42 +01:00
missytake
88a8dc905b DNS: recommend cmdeploy dns --all in the README 2024-12-20 10:46:42 +01:00
missytake
a2fbb5dc37 add changelog 2024-12-20 10:46:42 +01:00
missytake
97c31e3820 fix tests 2024-12-20 10:46:42 +01:00
missytake
08c88caa46 CI: test all DNS records 2024-12-20 10:46:42 +01:00
missytake
8e5174ae44 DNS: add -all to cmdeploy dns 2024-12-20 10:46:42 +01:00
missytake
69fe5eac2b DNS: more elegant solution to fix mta-sts record 2024-12-17 18:27:56 +01:00
missytake
46f6a07239 Revert "DNS: fix _mta-sts TXT record on initial setup"
This reverts commit 6d4af3cf0c.
2024-12-17 18:27:56 +01:00
missytake
b268efbc6e DNS: fix _mta-sts TXT record on initial setup 2024-12-17 18:27:56 +01:00
35 changed files with 480 additions and 275 deletions

View File

@@ -17,5 +17,4 @@ $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.

View File

@@ -17,6 +17,5 @@ $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.

View File

@@ -38,8 +38,8 @@ 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
scp -o StrictHostKeyChecking=accept-new .github/workflows/staging-ipv4.testrun.org-default.zone root@ns.testrun.org:/etc/nsd/staging-ipv4.testrun.org.zone
ssh 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
@@ -49,7 +49,7 @@ jobs:
-H "Authorization: Bearer ${{ secrets.HETZNER_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"image":"debian-12"}' \
"https://api.hetzner.cloud/v1/servers/${{ secrets.STAGING_SERVER_ID }}/actions/rebuild"
"https://api.hetzner.cloud/v1/servers/${{ secrets.STAGING_IPV4_SERVER_ID }}/actions/rebuild"
- run: scripts/initenv.sh
@@ -63,11 +63,11 @@ jobs:
while ! ssh -o ConnectTimeout=180 -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org id -u ; do sleep 1 ; done
ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org id -u
# download acme & dkim state from ns.testrun.org
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
rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/acme-ipv4/acme acme-restore || true
rsync -avz root@ns.testrun.org:/tmp/dkimkeys-ipv4/dkimkeys 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/ || true
rsync -avz dkimkeys-restore/dkimkeys-ipv4/dkimkeys root@staging-ipv4.testrun.org:/etc/ || true
rsync -avz acme-restore/acme root@staging-ipv4.testrun.org:/var/lib/ || true
rsync -avz dkimkeys-restore/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

View File

@@ -2,25 +2,90 @@
## untagged
- Remove `DKIM-Signature` from incoming mails after verifying
([#530](https://github.com/chatmail/server/pull/530))
- Send SNI when connecting to outside servers
([#524](https://github.com/chatmail/server/pull/524))
- Pass through `original_content` instead of `content` in filtermail
([#509](https://github.com/chatmail/server/pull/509))
- Document TLS requirements in the readme
([#514](https://github.com/chatmail/server/pull/514))
- Remove cleanup service from submission ports
([#512](https://github.com/chatmail/server/pull/512))
- cmdeploy dovecot: delete big messages after 7 days
([#504](https://github.com/chatmail/server/pull/504))
- mtail: fix getting logs from STDIN
([#502](https://github.com/chatmail/server/pull/502))
- filtermail: don't require exactly 2 lines after openPGP payload
([#497](https://github.com/chatmail/server/pull/497))
- cmdeploy dns: offer alternative DKIM record format for some web interfaces
([#470](https://github.com/chatmail/server/pull/470))
- journald: remove old logs from disk
([#490](https://github.com/chatmail/server/pull/490))
- opendkim: restart once every day to mend RAM leaks
([#498](https://github.com/chatmail/server/pull/498)
- migration guide: let opendkim own the DKIM keys directory
([#468](https://github.com/chatmail/server/pull/468))
- improve secure-join message detection
([#473](https://github.com/chatmail/server/pull/473))
- use old crypt lib in python < 3.11
([#483](https://github.com/chatmail/server/pull/483))
- chatmaild: set umask to 0700 for doveauth + metadata
([#490](https://github.com/chatmail/server/pull/492))
- remove MTA-STS daemon
([#488](https://github.com/chatmail/server/pull/488))
- replace `Subject` with `[...]` for all outgoing mails.
([#481](https://github.com/chatmail/server/pull/481))
- opendkim: use su instead of sudo
([#491](https://github.com/chatmail/server/pull/491))
## 1.5.0 2024-12-20
- cmdeploy dns: always show recommended DNS records
([#463](https://github.com/chatmail/server/pull/463))
- add `--all` to `cmdeploy dns`
([#462](https://github.com/chatmail/server/pull/462))
- fix `_mta-sts` TXT DNS record
([#461](https://github.com/chatmail/server/pull/461)
- 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))
([#434](https://github.com/chatmail/server/pull/434))
([#451](https://github.com/chatmail/server/pull/451))
- add guide to migrate chatmail to a new server
([#429](https://github.com/deltachat/chatmail/pull/429))
([#429](https://github.com/chatmail/server/pull/429))
- disable anvil authentication penalty
([#414](https://github.com/deltachat/chatmail/pull/444)
([#414](https://github.com/chatmail/server/pull/444)
- increase `request_queue_size` for UNIX sockets to 1000.
([#437](https://github.com/deltachat/chatmail/pull/437))
([#437](https://github.com/chatmail/server/pull/437))
- add argument to `cmdeploy run` for specifying
a different SSH host than `mail_domain`
([#439](https://github.com/deltachat/chatmail/pull/439))
([#439](https://github.com/chatmail/server/pull/439))
- query autoritative nameserver to bypass DNS cache
([#424](https://github.com/deltachat/chatmail/pull/424))
([#424](https://github.com/chatmail/server/pull/424))
- add mtail support (new optional `mtail_address` ini value)
This defines the address on which [`mtail`](https://google.github.io/mtail/)
@@ -30,195 +95,195 @@
and assign an IP address from this network to the host.
If you do not plan to collect metrics,
keep this setting unset.
([#388](https://github.com/deltachat/chatmail/pull/388))
([#388](https://github.com/chatmail/server/pull/388))
- fix checking for required DNS records
([#412](https://github.com/deltachat/chatmail/pull/412))
([#412](https://github.com/chatmail/server/pull/412))
- add support for specifying whole domains for recipient passthrough list
([#408](https://github.com/deltachat/chatmail/pull/408))
([#408](https://github.com/chatmail/server/pull/408))
- add a paragraph about "account deletion" to info page
([#405](https://github.com/deltachat/chatmail/pull/405))
([#405](https://github.com/chatmail/server/pull/405))
- avoid nginx listening on ipv6 if v6 is dsiabled
([#402](https://github.com/deltachat/chatmail/pull/402))
([#402](https://github.com/chatmail/server/pull/402))
- refactor ssh-based execution to allow organizing remote functions in
modules.
([#396](https://github.com/deltachat/chatmail/pull/396))
([#396](https://github.com/chatmail/server/pull/396))
- trigger "apt upgrade" during "cmdeploy run"
([#398](https://github.com/deltachat/chatmail/pull/398))
([#398](https://github.com/chatmail/server/pull/398))
- drop hispanilandia passthrough address
([#401](https://github.com/deltachat/chatmail/pull/401))
([#401](https://github.com/chatmail/server/pull/401))
- set CAA record flags to 0
- add IMAP capabilities instead of overwriting them
([#413](https://github.com/deltachat/chatmail/pull/413))
([#413](https://github.com/chatmail/server/pull/413))
- fix OpenPGP payload check
([#435](https://github.com/deltachat/chatmail/pull/435))
([#435](https://github.com/chatmail/server/pull/435))
- fix Dovecot quota_max_mail_size to use max_message_size config value
([#438](https://github.com/deltachat/chatmail/pull/438))
([#438](https://github.com/chatmail/server/pull/438))
## 1.4.1 2024-07-31
- fix metadata dictproxy which would confuse transactions
resulting in missed notifications and other issues.
([#393](https://github.com/deltachat/chatmail/pull/393))
([#394](https://github.com/deltachat/chatmail/pull/394))
([#393](https://github.com/chatmail/server/pull/393))
([#394](https://github.com/chatmail/server/pull/394))
- add optional "imap_rawlog" config option. If true,
.in/.out files are created in user home dirs
containing the imap protocol messages.
([#389](https://github.com/deltachat/chatmail/pull/389))
([#389](https://github.com/chatmail/server/pull/389))
## 1.4.0 2024-07-28
- Add `disable_ipv6` config option to chatmail.ini.
Required if the server doesn't have IPv6 connectivity.
([#312](https://github.com/deltachat/chatmail/pull/312))
([#312](https://github.com/chatmail/server/pull/312))
- allow current K9/Thunderbird-mail releases to send encrypted messages
outside by accepting their localized "encrypted subject" strings.
([#370](https://github.com/deltachat/chatmail/pull/370))
([#370](https://github.com/chatmail/server/pull/370))
- Migrate and remove sqlite database in favor of password/lastlogin tracking
in a user's maildir.
([#379](https://github.com/deltachat/chatmail/pull/379))
([#379](https://github.com/chatmail/server/pull/379))
- Require pyinfra V3 installed on the client side,
run `./scripts/initenv.sh` to upgrade locally.
([#378](https://github.com/deltachat/chatmail/pull/378))
([#378](https://github.com/chatmail/server/pull/378))
- don't hardcode "/home/vmail" paths but rather set them
once in the config object and use it everywhere else,
thereby also improving testability.
([#351](https://github.com/deltachat/chatmail/pull/351))
([#351](https://github.com/chatmail/server/pull/351))
temporarily introduced obligatory "passdb_path" and "mailboxes_dir"
settings but they were removed/obsoleted in
([#380](https://github.com/deltachat/chatmail/pull/380))
([#380](https://github.com/chatmail/server/pull/380))
- BREAKING: new required chatmail.ini value 'delete_inactive_users_after = 100'
which removes users from database and mails after 100 days without any login.
([#350](https://github.com/deltachat/chatmail/pull/350))
([#350](https://github.com/chatmail/server/pull/350))
- Refine DNS checking to distinguish between "required" and "recommended" settings
([#372](https://github.com/deltachat/chatmail/pull/372))
([#372](https://github.com/chatmail/server/pull/372))
- reload nginx in the acmetool cronjob
([#360](https://github.com/deltachat/chatmail/pull/360))
([#360](https://github.com/chatmail/server/pull/360))
- remove checking of reverse-DNS PTR records. Chatmail-servers don't
depend on it and even in the wider e-mail system it's not common anymore.
If it's an issue, a chatmail operator can still care to properly set reverse DNS.
([#348](https://github.com/deltachat/chatmail/pull/348))
([#348](https://github.com/chatmail/server/pull/348))
- Make DNS-checking faster and more interactive, run it fully during "cmdeploy run",
also introducing a generic mechanism for rapid remote ssh-based python function execution.
([#346](https://github.com/deltachat/chatmail/pull/346))
([#346](https://github.com/chatmail/server/pull/346))
- Don't fix file owner ship of /home/vmail
([#345](https://github.com/deltachat/chatmail/pull/345))
([#345](https://github.com/chatmail/server/pull/345))
- Support iterating over all users with doveadm commands
([#344](https://github.com/deltachat/chatmail/pull/344))
([#344](https://github.com/chatmail/server/pull/344))
- Test and fix for attempts to create inadmissible accounts
([#333](https://github.com/deltachat/chatmail/pull/321))
([#333](https://github.com/chatmail/server/pull/321))
- check that OpenPGP has only PKESK, SKESK and SEIPD packets
([#323](https://github.com/deltachat/chatmail/pull/323),
[#324](https://github.com/deltachat/chatmail/pull/324))
([#323](https://github.com/chatmail/server/pull/323),
[#324](https://github.com/chatmail/server/pull/324))
- improve filtermail checks for encrypted messages and drop support for unencrypted MDNs
([#320](https://github.com/deltachat/chatmail/pull/320))
([#320](https://github.com/chatmail/server/pull/320))
- replace `bash` with `/bin/sh`
([#334](https://github.com/deltachat/chatmail/pull/334))
([#334](https://github.com/chatmail/server/pull/334))
- Increase number of logged in IMAP sessions to 50000
([#335](https://github.com/deltachat/chatmail/pull/335))
([#335](https://github.com/chatmail/server/pull/335))
- filtermail: do not allow ASCII armor without actual payload
([#325](https://github.com/deltachat/chatmail/pull/325))
([#325](https://github.com/chatmail/server/pull/325))
- Remove sieve to enable hardlink deduplication in LMTP
([#343](https://github.com/deltachat/chatmail/pull/343))
([#343](https://github.com/chatmail/server/pull/343))
- dovecot: enable gzip compression on disk
([#341](https://github.com/deltachat/chatmail/pull/341))
([#341](https://github.com/chatmail/server/pull/341))
- DKIM-sign Content-Type and oversign all signed headers
([#296](https://github.com/deltachat/chatmail/pull/296))
([#296](https://github.com/chatmail/server/pull/296))
- Add nonci_accounts metric
([#347](https://github.com/deltachat/chatmail/pull/347))
([#347](https://github.com/chatmail/server/pull/347))
- doveauth: log when a new account is created
([#349](https://github.com/deltachat/chatmail/pull/349))
([#349](https://github.com/chatmail/server/pull/349))
- Multiplex HTTPS, IMAP and SMTP on port 443
([#357](https://github.com/deltachat/chatmail/pull/357))
([#357](https://github.com/chatmail/server/pull/357))
## 1.3.0 - 2024-06-06
- don't check necessary DNS records on cmdeploy init anymore
([#316](https://github.com/deltachat/chatmail/pull/316))
([#316](https://github.com/chatmail/server/pull/316))
- ensure cron and acl are installed
([#293](https://github.com/deltachat/chatmail/pull/293),
[#310](https://github.com/deltachat/chatmail/pull/310))
([#293](https://github.com/chatmail/server/pull/293),
[#310](https://github.com/chatmail/server/pull/310))
- change default for delete_mails_after from 40 to 20 days
([#300](https://github.com/deltachat/chatmail/pull/300))
([#300](https://github.com/chatmail/server/pull/300))
- save journald logs only to memory and save nginx logs to journald instead of file
([#299](https://github.com/deltachat/chatmail/pull/299))
([#299](https://github.com/chatmail/server/pull/299))
- fix writing of multiple obs repositories in `/etc/apt/sources.list`
([#290](https://github.com/deltachat/chatmail/pull/290))
([#290](https://github.com/chatmail/server/pull/290))
- metadata: add support for `/shared/vendor/deltachat/irohrelay`
([#284](https://github.com/deltachat/chatmail/pull/284))
([#284](https://github.com/chatmail/server/pull/284))
- Emit "XCHATMAIL" capability from IMAP server
([#278](https://github.com/deltachat/chatmail/pull/278))
([#278](https://github.com/chatmail/server/pull/278))
- Move echobot `into /var/lib/echobot`
([#281](https://github.com/deltachat/chatmail/pull/281))
([#281](https://github.com/chatmail/server/pull/281))
- Accept Let's Encrypt's new Terms of Services
([#275](https://github.com/deltachat/chatmail/pull/276))
([#275](https://github.com/chatmail/server/pull/276))
- Reload Dovecot and Postfix when TLS certificate updates
([#271](https://github.com/deltachat/chatmail/pull/271))
([#271](https://github.com/chatmail/server/pull/271))
- Use forked version of dovecot without hardcoded delays
([#270](https://github.com/deltachat/chatmail/pull/270))
([#270](https://github.com/chatmail/server/pull/270))
## 1.2.0 - 2024-04-04
- Install dig on the server to resolve DNS records
([#267](https://github.com/deltachat/chatmail/pull/267))
([#267](https://github.com/chatmail/server/pull/267))
- preserve notification order and exponentially backoff with
retries for tokens where we didn't get a successful return
([#265](https://github.com/deltachat/chatmail/pull/263))
([#265](https://github.com/chatmail/server/pull/263))
- Run chatmail-metadata and doveauth as vmail
([#261](https://github.com/deltachat/chatmail/pull/261))
([#261](https://github.com/chatmail/server/pull/261))
- Apply systemd restrictions to echobot
([#259](https://github.com/deltachat/chatmail/pull/259))
([#259](https://github.com/chatmail/server/pull/259))
- re-enable running the CI in pull requests, but not concurrently
([#258](https://github.com/deltachat/chatmail/pull/258))
([#258](https://github.com/chatmail/server/pull/258))
## 1.1.0 - 2024-03-28
@@ -226,27 +291,27 @@
### The changelog starts to record changes from March 15th, 2024
- Move systemd unit templates to cmdeploy package
([#255](https://github.com/deltachat/chatmail/pull/255))
([#255](https://github.com/chatmail/server/pull/255))
- Persist push tokens and support multiple device per address
([#254](https://github.com/deltachat/chatmail/pull/254))
([#254](https://github.com/chatmail/server/pull/254))
- Avoid warning for regular doveauth protocol's hello message.
([#250](https://github.com/deltachat/chatmail/pull/250))
([#250](https://github.com/chatmail/server/pull/250))
- Fix various tests to pass again with "cmdeploy test".
([#245](https://github.com/deltachat/chatmail/pull/245),
[#242](https://github.com/deltachat/chatmail/pull/242)
([#245](https://github.com/chatmail/server/pull/245),
[#242](https://github.com/chatmail/server/pull/242)
- Ensure lets-encrypt certificates are reloaded after renewal
([#244]) https://github.com/deltachat/chatmail/pull/244
([#244]) https://github.com/chatmail/server/pull/244
- Persist tokens to avoid iOS users loosing push-notifications when the
chatmail metadata service is restarted (happens regularly during deploys)
([#238](https://github.com/deltachat/chatmail/pull/239)
([#238](https://github.com/chatmail/server/pull/239)
- Fix failing sieve-script compile errors on incoming messages
([#237](https://github.com/deltachat/chatmail/pull/239)
([#237](https://github.com/chatmail/server/pull/239)
- Fix quota reporting after expunging of old mails
([#233](https://github.com/deltachat/chatmail/pull/239)
([#233](https://github.com/chatmail/server/pull/239)

114
README.md
View File

@@ -1,21 +1,55 @@
<img width="800px" src="www/src/collage-top.png"/>
# Chatmail services optimized for Delta Chat apps
# Chatmail servers for secure instant messaging
This repository helps to setup a ready-to-use chatmail server
Chatmail servers are minimal interoperable e-mail routing machines designed for:
- **Convenience:** Instant onboarding, with optional Google/Apple/Huawei push notifications
- **Privacy:** Just login, no questions asked, no name, numbers or e-mail needed
- **Speed:** End-to-End Message delivery in well under a second
- **Security:** Strict TLS, DKIM and OpenPGP with metadata-minimization enforced.
- **Relaxation:** No annoying spam-checking, IP reputation or rate limits
- **Efficiency:** messages are only stored for transit and removed automatically.
This repository contains everything needed to setup a ready-to-use chatmail server
comprised of a minimal setup of the battle-tested
[postfix smtp](https://www.postfix.org) and [dovecot imap](https://www.dovecot.org) services.
The setup is designed and optimized for providing chatmail accounts
for use by [Delta Chat apps](https://delta.chat).
The automated setup is designed and optimized for providing chatmail addresses
for immediate permission-free onboarding through chat apps and bots.
Chatmail addresses are automatically created by a first login,
after which the initially specified password is required
for sending and receiving messages through them.
Chatmail accounts are automatically created by a first login,
after which the initially specified password is required for using them.
Please see [this list of known apps and client projects](https://chatmail.at/apps.html) which offer instant onboarding on chatmail servers,
and [this list of known public 3rd party chatmail servers](https://delta.chat/en/chatmail).
## Deploying your own chatmail server
To deploy chatmail on your own server, you must have set-up ssh authentication and need to use an ed25519 key, due to an [upstream bug in paramiko](https://github.com/paramiko/paramiko/issues/2191). You also need to add your private key to the local ssh-agent, because you can't type in your password during deployment.
## Minimal requirements, Prerequisites
You will need the following:
- control over a domain through a DNS provider of your choice,
- a remote Debian 12 machine with IPV4 and preferably also IPV6 addresses and
reachable SMTP/SUBMISSIONS/IMAPS/HTTPS ports.
Machine needs 1GB RAM, one slow CPU and maybe 10GB storage for a
few thousand active chatmail addresses,
- a terminal window with password-less ssh root login to the remote machine;
you must have set up ssh authentication and need to use an ed25519 key,
due to an [upstream bug in paramiko](https://github.com/paramiko/paramiko/issues/2191);
you also need to add your private key to the local ssh-agent,
because you can't type in your password during deployment.
## Getting started
We use `chat.example.org` as the chatmail domain in the following steps.
Please substitute it with your own domain.
@@ -23,7 +57,7 @@ Please substitute it with your own domain.
1. Install the `cmdeploy` command in a virtualenv
```
git clone https://github.com/deltachat/chatmail
git clone https://github.com/chatmail/server
cd chatmail
scripts/initenv.sh
```
@@ -82,11 +116,11 @@ scripts/cmdeploy bench
This repository has four directories:
- [cmdeploy](https://github.com/deltachat/chatmail/tree/main/cmdeploy)
- [cmdeploy](https://github.com/chatmail/server/tree/main/cmdeploy)
is a collection of configuration files
and a [pyinfra](https://pyinfra.com)-based deployment script.
- [chatmaild](https://github.com/deltachat/chatmail/tree/main/chatmaild)
- [chatmaild](https://github.com/chatmail/server/tree/main/chatmaild)
is a python package containing several small services
which handle authentication,
trigger push notifications on new messages,
@@ -95,12 +129,12 @@ This repository has four directories:
and some other minor things.
chatmaild can also be installed as a stand-alone python package.
- [www](https://github.com/deltachat/chatmail/tree/main/www)
- [www](https://github.com/chatmail/server/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)
- [scripts](https://github.com/chatmail/server/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
@@ -139,39 +173,39 @@ 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
- [`doveauth`](https://github.com/chatmail/server/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.
- [`filtermail`](https://github.com/deltachat/chatmail/blob/main/chatmaild/src/chatmaild/filtermail.py) prevents
- [`filtermail`](https://github.com/chatmail/server/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.
- [`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)
- [`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)
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)
it [passes the user's push notification token](https://github.com/chatmail/server/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)
- [`delete_inactive_users`](https://github.com/chatmail/server/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)
- [`lastlogin`](https://github.com/chatmail/server/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)
- [`echobot`](https://github.com/chatmail/server/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)
- [`chatmail-metrics`](https://github.com/chatmail/server/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
@@ -228,8 +262,8 @@ While this file is present, account creation will be blocked.
Port 443 multiplexes HTTPS, IMAP and SMTP using ALPN to redirect connections to ports 8443, 465 or 993.
[acmetool](https://hlandau.github.io/acmetool/) listens on port 80 (http).
Delta Chat apps will, however, discover all ports and configurations
automatically by reading the [autoconfig XML file](https://www.ietf.org/archive/id/draft-bucksch-autoconfig-00.html) from the chatmail service.
Chatmail-core based apps will, however, discover all ports and configurations
automatically by reading the [autoconfig XML file](https://www.ietf.org/archive/id/draft-bucksch-autoconfig-00.html) from the chatmail server.
## Email authentication
@@ -256,6 +290,32 @@ and rejects incorrectly authenticated emails with [`reject_sender_login_mismatch
`From:` header must correspond to envelope MAIL FROM,
this is ensured by `filtermail` proxy.
## TLS requirements
Postfix is configured to require valid TLS
by setting [`smtp_tls_security_level`](https://www.postfix.org/postconf.5.html#smtp_tls_security_level) to `verify`.
If emails don't arrive from a chatmail server to your server,
the problem is likely that your server does not have a valid TLS certificate.
You can test it by resolving `MX` records of your server domain
and then connecting to MX servers (e.g `mx.example.org`) with
`openssl s_client -connect mx.example.org:25 -verify_hostname mx.example.org -verify_return_error -starttls smtp`
from the host that has open port 25 to verify that certificate is valid.
When providing a TLS certificate to your server,
make sure to provide the full certificate chain
and not just the last certificate.
If you are running Exim server and don't see incoming connections
from a chatmail server in the logs,
make sure `smtp_no_mail` log item is enabled in the config
with `log_selector = +smtp_no_mail`.
By default Exim does not log sessions that are closed
before sending the `MAIL` command.
This happens if certificate is not recognized as valid by Postfix,
so you might think that connection is not established
while actually it is a problem with your TLS certificate.
## Migrating chatmail server to a new host
If you want to migrate chatmail from an old machine
@@ -281,7 +341,7 @@ to make sure you can connect with SSH.
`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.
3. On the new server, run `chown root: -R /var/lib/acme` and `chown opendkim: -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,
@@ -290,9 +350,9 @@ to make sure you can connect with SSH.
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
Existing Chatmail app users or bots will still be able to connect
to the old server, send and receive messages,
but new users will fail to create new profiles
but new ones will fail to create new profiles
with your chatmail server.
If other servers try to deliver messages to your new server they will fail,

View File

@@ -12,6 +12,7 @@ dependencies = [
"deltachat-rpc-client",
"filelock",
"requests",
"crypt-r >= 3.13.1 ; python_version >= '3.11'",
]
[tool.setuptools]

View File

@@ -1,59 +0,0 @@
"""Generated from deltachat, draft-ietf-lamps-header-protection, and
encrypted_subject localizations in
https://github.com/thunderbird/thunderbird-android/
"""
common_encrypted_subjects = {
"...",
"[...]",
"암호화된 메시지",
"Ĉifrita mesaĝo",
"Courriel chiffré",
"Dulrituð skilaboð",
"Encrypted Message",
"Fersifere berjocht",
"Kemennadenn enrineget",
"Krüptitud kiri",
"Krypterat meddelande",
"Krypteret besked",
"Kryptert melding",
"Mensagem criptografada",
"Mensagem encriptada",
"Mensaje cifrado",
"Mensaxe cifrada",
"Mesaj Criptat",
"Mesazh i Fshehtëzuar",
"Messaggio criptato",
"Messaghju cifratu",
"Missatge encriptat",
"Neges wedi'i Hamgryptio",
"Pesan terenkripsi",
"Salattu viesti",
"Şifreli İleti",
"Šifrēta ziņa",
"Šifrirana poruka",
"Šifrirano sporočilo",
"Šifruotas laiškas",
"Tin nhắn được mã hóa",
"Titkosított üzenet",
"Verschlüsselte Nachricht",
"Versleuteld bericht",
"Zašifrovaná zpráva",
"Zaszyfrowana wiadomość",
"Zifratu mezua",
"Κρυπτογραφημένο μήνυμα",
"Зашифроване повідомлення",
"Зашифрованное сообщение",
"Зашыфраваны ліст",
"Криптирано съобщение",
"Шифрована порука",
"დაშიფრული წერილი",
"הודעה מוצפנת",
"پیام رمزنگاری‌شده",
"رسالة مشفّرة",
"എൻക്രിപ്റ്റുചെയ്‌ത സന്ദേശം",
"加密邮件",
"已加密的訊息",
"暗号化されたメッセージ",
}

View File

@@ -1,9 +1,13 @@
import crypt
import json
import logging
import os
import sys
try:
import crypt_r
except ImportError:
import crypt as crypt_r
from .config import Config, read_config
from .dictproxy import DictProxy
from .migrate_db import migrate_from_db_to_maildir
@@ -13,7 +17,7 @@ NOCREATE_FILE = "/etc/chatmail-nocreate"
def encrypt_password(password: str):
# https://doc.dovecot.org/configuration_manual/authentication/password_schemes/
passhash = crypt.crypt(password, crypt.METHOD_SHA512)
passhash = crypt_r.crypt(password, crypt_r.METHOD_SHA512)
return "{SHA512-CRYPT}" + passhash

View File

@@ -12,7 +12,6 @@ from smtplib import SMTP as SMTPClient
from aiosmtpd.controller import Controller
from .common_encrypted_subjects import common_encrypted_subjects
from .config import read_config
@@ -81,7 +80,9 @@ def check_armored_payload(payload: str):
return False
payload = payload.removeprefix(prefix)
suffix = "-----END PGP MESSAGE-----\r\n\r\n"
while payload.endswith("\r\n"):
payload = payload.removesuffix("\r\n")
suffix = "-----END PGP MESSAGE-----"
if not payload.endswith(suffix):
return False
payload = payload.removesuffix(suffix)
@@ -100,6 +101,27 @@ def check_armored_payload(payload: str):
return False
def is_securejoin(message):
if message.get("secure-join") not in ["vc-request", "vg-request"]:
return False
if not message.is_multipart():
return False
parts_count = 0
for part in message.iter_parts():
parts_count += 1
if parts_count > 1:
return False
if part.is_multipart():
return False
if part.get_content_type() != "text/plain":
return False
payload = part.get_payload().strip().lower()
if payload not in ("secure-join: vc-request", "secure-join: vg-request"):
return False
return True
def check_encrypted(message):
"""Check that the message is an OpenPGP-encrypted message.
@@ -107,8 +129,6 @@ def check_encrypted(message):
"""
if not message.is_multipart():
return False
if message.get("subject") not in common_encrypted_subjects:
return False
if message.get_content_type() != "multipart/encrypted":
return False
parts_count = 0
@@ -176,7 +196,9 @@ class BeforeQueueHandler:
return error
logging.info("re-injecting the mail that passed checks")
client = SMTPClient("localhost", self.config.postfix_reinject_port)
client.sendmail(envelope.mail_from, envelope.rcpt_tos, envelope.content)
client.sendmail(
envelope.mail_from, envelope.rcpt_tos, envelope.original_content
)
return "250 OK"
def check_DATA(self, envelope):
@@ -203,11 +225,7 @@ class BeforeQueueHandler:
passthrough_recipients = self.config.passthrough_recipients
is_securejoin = message.get("secure-join") in [
"vc-request",
"vg-request",
]
if is_securejoin:
if mail_encrypted or is_securejoin(message):
return
for recipient in envelope.rcpt_tos:
@@ -222,7 +240,7 @@ class BeforeQueueHandler:
_recipient_addr, recipient_domain = res
is_outgoing = recipient_domain != envelope_from_domain
if is_outgoing and not mail_encrypted:
if is_outgoing:
print("Rejected unencrypted mail.", file=sys.stderr)
return f"500 Invalid unencrypted mail to <{recipient}>"

View File

@@ -32,7 +32,7 @@ def migrate_from_db_to_maildir(config, chunking=10000):
# don't transfer special/CI accounts
rows = [row for row in all_rows if row[0][:3] not in ("ci-", "ac_")]
logging.info(f"ignoring {len(all_rows)-len(rows)} CI accounts")
logging.info(f"ignoring {len(all_rows) - len(rows)} CI accounts")
logging.info(f"migrating {len(rows)} sqlite database passwords to user dirs")
for i, row in enumerate(rows):

View File

@@ -0,0 +1,21 @@
Subject: Message from {from_addr}
From: <{from_addr}>
To: <{to_addr}>
Date: Sun, 15 Oct 2023 16:43:25 +0000
Message-ID: <Mr.78MWtlV7RAi.goCFzBhCYfy@c2.testrun.org>
Chat-Version: 1.0
Secure-Join: vc-request
Secure-Join-Invitenumber: RANDOM-TOKEN
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi"
--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi
Content-Type: text/plain; charset=utf-8
Buy viagra!
--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi--

View File

@@ -0,0 +1,21 @@
Subject: Message from {from_addr}
From: <{from_addr}>
To: <{to_addr}>
Date: Sun, 15 Oct 2023 16:43:25 +0000
Message-ID: <Mr.78MWtlV7RAi.goCFzBhCYfy@c2.testrun.org>
Chat-Version: 1.0
Secure-Join: vc-request
Secure-Join-Invitenumber: RANDOM-TOKEN
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi"
--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi
Content-Type: text/plain; charset=utf-8
Secure-Join: vc-request
--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi--

View File

@@ -69,7 +69,7 @@ def maildata(request):
assert datadir.exists(), datadir
def maildata(name, from_addr, to_addr, subject="..."):
def maildata(name, from_addr, to_addr, subject="[...]"):
# Using `.read_bytes().decode()` instead of `.read_text()` to preserve newlines.
data = datadir.joinpath(name).read_bytes().decode()

View File

@@ -5,7 +5,7 @@ from chatmaild.filtermail import (
SendRateLimiter,
check_armored_payload,
check_encrypted,
common_encrypted_subjects,
is_securejoin,
)
@@ -55,19 +55,28 @@ def test_filtermail_no_encryption_detection(maildata):
assert not check_encrypted(msg)
def test_filtermail_encryption_detection(maildata):
for subject in common_encrypted_subjects:
msg = maildata(
"encrypted.eml",
from_addr="1@example.org",
to_addr="2@example.org",
subject=subject,
)
assert check_encrypted(msg)
def test_filtermail_securejoin_detection(maildata):
msg = maildata(
"securejoin-vc.eml", from_addr="some@example.org", to_addr="other@example.org"
)
assert is_securejoin(msg)
# if the subject is not a known encrypted subject value, it is not considered ac-encrypted
msg.replace_header("Subject", "Click this link")
assert not check_encrypted(msg)
msg = maildata(
"securejoin-vc-fake.eml",
from_addr="some@example.org",
to_addr="other@example.org",
)
assert not is_securejoin(msg)
def test_filtermail_encryption_detection(maildata):
msg = maildata(
"encrypted.eml",
from_addr="1@example.org",
to_addr="2@example.org",
subject="Subject does not matter, will be replaced anyway",
)
assert check_encrypted(msg)
def test_filtermail_no_literal_packets(maildata):
@@ -196,10 +205,20 @@ UN4fiB0KR9JyG2ayUdNJVkXZSZLnHyRgiaadlpUo16LVvw==\r
=b5Kp\r
-----END PGP MESSAGE-----\r
\r
\r
"""
assert check_armored_payload(payload) == True
payload = payload.removesuffix("\r\n")
assert check_armored_payload(payload) == True
payload = payload.removesuffix("\r\n")
assert check_armored_payload(payload) == True
payload = payload.removesuffix("\r\n")
assert check_armored_payload(payload) == True
payload = """-----BEGIN PGP MESSAGE-----\r
\r
HELLOWORLD

View File

@@ -10,7 +10,7 @@ import sys
from pathlib import Path
from chatmaild.config import Config, read_config
from pyinfra import host, facts
from pyinfra import facts, host
from pyinfra.facts.files import File
from pyinfra.facts.systemd import SystemdEnabled
from pyinfra.operations import apt, files, pip, server, systemd
@@ -78,6 +78,11 @@ def _install_remote_venv_with_chatmaild(config) -> None:
always_copy=True,
)
apt.packages(
name="install gcc and headers to build crypt_r source package",
packages=["gcc", "python3-dev"],
)
server.shell(
name=f"forced pip-install {dist_file.name}",
commands=[
@@ -210,51 +215,37 @@ def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> bool:
server.shell(
name="Generate OpenDKIM domain keys",
commands=[
f"opendkim-genkey -D /etc/dkimkeys -d {domain} -s {dkim_selector}"
f"/usr/sbin/opendkim-genkey -D /etc/dkimkeys -d {domain} -s {dkim_selector}"
],
_sudo=True,
_sudo_user="opendkim",
_use_su_login=True,
_su_user="opendkim",
)
service_file = files.put(
name="Configure opendkim to restart once a day",
src=importlib.resources.files(__package__).joinpath("opendkim/systemd.conf"),
dest="/etc/systemd/system/opendkim.service.d/10-prevent-memory-leak.conf",
)
need_restart |= service_file.changed
return need_restart
def _install_mta_sts_daemon() -> bool:
need_restart = False
def _uninstall_mta_sts_daemon() -> None:
# Remove configuration.
files.file("/etc/mta-sts-daemon.yml", present=False)
config = files.put(
name="upload postfix-mta-sts-resolver config",
src=importlib.resources.files(__package__).joinpath(
"postfix/mta-sts-daemon.yml"
),
dest="/etc/mta-sts-daemon.yml",
user="root",
group="root",
mode="644",
files.directory("/usr/local/lib/postfix-mta-sts-resolver", present=False)
files.file("/etc/systemd/system/mta-sts-daemon.service", present=False)
systemd.service(
name="Stop MTA-STS daemon",
service="mta-sts-daemon.service",
daemon_reload=True,
running=False,
enabled=False,
)
need_restart |= config.changed
server.shell(
name="install postfix-mta-sts-resolver with pip",
commands=[
"python3 -m virtualenv /usr/local/lib/postfix-mta-sts-resolver",
"/usr/local/lib/postfix-mta-sts-resolver/bin/pip install postfix-mta-sts-resolver",
],
)
systemd_unit = files.put(
name="upload mta-sts-daemon systemd unit",
src=importlib.resources.files(__package__).joinpath(
"postfix/mta-sts-daemon.service"
),
dest="/etc/systemd/system/mta-sts-daemon.service",
user="root",
group="root",
mode="644",
)
need_restart |= systemd_unit.changed
return need_restart
def _configure_postfix(config: Config, debug: bool = False) -> bool:
@@ -283,7 +274,18 @@ def _configure_postfix(config: Config, debug: bool = False) -> bool:
)
need_restart |= master_config.changed
header_cleanup = files.put(
incoming_header_cleanup = files.put(
src=importlib.resources.files(__package__).joinpath(
"postfix/incoming_header_cleanup"
),
dest="/etc/postfix/incoming_header_cleanup",
user="root",
group="root",
mode="644",
)
need_restart |= incoming_header_cleanup.changed
submission_header_cleanup = files.put(
src=importlib.resources.files(__package__).joinpath(
"postfix/submission_header_cleanup"
),
@@ -292,7 +294,7 @@ def _configure_postfix(config: Config, debug: bool = False) -> bool:
group="root",
mode="644",
)
need_restart |= header_cleanup.changed
need_restart |= submission_header_cleanup.changed
# Login map that 1:1 maps email address to login.
login_map = files.put(
@@ -517,9 +519,9 @@ def deploy_iroh_relay(config) -> None:
need_restart |= systemd_unit.changed
iroh_config = files.put(
name=f"Upload iroh-relay config",
name="Upload iroh-relay config",
src=importlib.resources.files(__package__).joinpath("iroh-relay.toml"),
dest=f"/etc/iroh-relay.toml",
dest="/etc/iroh-relay.toml",
user="root",
group="root",
mode="644",
@@ -658,8 +660,8 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None:
debug = False
dovecot_need_restart = _configure_dovecot(config, debug=debug)
postfix_need_restart = _configure_postfix(config, debug=debug)
mta_sts_need_restart = _install_mta_sts_daemon()
nginx_need_restart = _configure_nginx(config)
_uninstall_mta_sts_daemon()
_remove_rspamd()
opendkim_need_restart = _configure_opendkim(mail_domain, "opendkim")
@@ -669,18 +671,10 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None:
service="opendkim.service",
running=True,
enabled=True,
daemon_reload=opendkim_need_restart,
restarted=opendkim_need_restart,
)
systemd.service(
name="Start and enable MTA-STS daemon",
service="mta-sts-daemon.service",
daemon_reload=True,
running=True,
enabled=True,
restarted=mta_sts_need_restart,
)
# Dovecot should be started before Postfix
# because it creates authentication socket
# required by Postfix.
@@ -730,6 +724,11 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None:
enabled=True,
restarted=journald_conf.changed,
)
files.directory(
name="Ensure old logs on disk are deleted",
path="/var/log/journal/",
present=False,
)
apt.packages(
name="Ensure cron is installed",

View File

@@ -70,6 +70,6 @@ def deploy_acmetool(email="", domains=[]):
)
server.shell(
name=f"Request certificate for: { ', '.join(domains) }",
commands=[f"acmetool want --xlog.severity=debug { ' '.join(domains)}"],
name=f"Request certificate for: {', '.join(domains)}",
commands=[f"acmetool want --xlog.severity=debug {' '.join(domains)}"],
)

View File

@@ -1,2 +1,2 @@
"acme-enter-email": "{{ email }}"
"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.4-April-3-2024.pdf": true
"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf": true

View File

@@ -16,7 +16,7 @@ www.{{ mail_domain }}. CNAME {{ mail_domain }}.
;
; Recommended DNS entries for interoperability and security-hardening
;
{{ mail_domain }}. TXT "v=spf1 a:{{ mail_domain }} ~all"
{{ mail_domain }}. TXT "v=spf1 a ~all"
_dmarc.{{ mail_domain }}. TXT "v=DMARC1;p=reject;adkim=s;aspf=s"
{% if acme_account_url %}

View File

@@ -56,12 +56,12 @@ def run_cmd_options(parser):
"--disable-mail",
dest="disable_mail",
action="store_true",
help="install/upgrade the server, but disable postfix & dovecot for now"
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"
help="specify an SSH host to deploy to; uses mail_domain from chatmail.ini by default",
)

View File

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

View File

@@ -29,7 +29,7 @@ def check_initial_remote_data(remote_data, *, print=print):
def get_filled_zone_file(remote_data):
sts_id = remote_data.get("sts_id")
if not sts_id:
sts_id = datetime.datetime.now().strftime("%Y%m%d%H%M")
remote_data["sts_id"] = datetime.datetime.now().strftime("%Y%m%d%H%M")
template = importlib.resources.files(__package__).joinpath("chatmail.zone.j2")
content = template.read_text()
@@ -49,16 +49,23 @@ def check_full_zone(sshexec, remote_data, out, zonefile) -> int:
kwargs=dict(zonefile=zonefile, mail_domain=remote_data["mail_domain"]),
)
returncode = 0
if required_diff:
out.red("Please set required DNS entries at your DNS provider:\n")
for line in required_diff:
out(line)
return 1
elif recommended_diff:
out("")
returncode = 1
if remote_data.get("dkim_entry") in required_diff:
out(
"If the DKIM entry above does not work with your DNS provider, you can try this one:\n"
)
out(remote_data.get("web_dkim_entry") + "\n")
if recommended_diff:
out("WARNING: these recommended DNS entries are not set:\n")
for line in recommended_diff:
out(line)
return 0
out.green("Great! All your DNS entries are verified and correct.")
return 0
if not (recommended_diff or required_diff):
out.green("Great! All your DNS entries are verified and correct.")
return returncode

View File

@@ -209,7 +209,7 @@ ssl = required
ssl_cert = </var/lib/acme/live/{{ config.mail_domain }}/fullchain
ssl_key = </var/lib/acme/live/{{ config.mail_domain }}/privkey
ssl_dh = </usr/share/dovecot/dh.pem
ssl_min_protocol = TLSv1.2
ssl_min_protocol = TLSv1.3
ssl_prefer_server_ciphers = yes

View File

@@ -1,3 +1,5 @@
# delete already seen big mails after 7 days, in the INBOX
2 0 * * * vmail find {{ config.mailboxes_dir }} -path '*/cur/*' -mtime +7 -size +200k -type f -delete
# delete all mails after {{ config.delete_mails_after }} days, in the Inbox
2 0 * * * vmail find {{ config.mailboxes_dir }} -path '*/cur/*' -mtime +{{ config.delete_mails_after }} -type f -delete
# or in any IMAP subfolder

View File

@@ -3,7 +3,7 @@ Description=mtail
[Service]
Type=simple
ExecStart=/bin/sh -c "journalctl -f -o short-iso -n 0 | /usr/bin/mtail --address={{ address }} --port={{ port }} --progs /etc/mtail --logtostderr --logs -"
ExecStart=/bin/sh -c "journalctl -f -o short-iso -n 0 | /usr/bin/mtail --address={{ address }} --port={{ port }} --progs /etc/mtail --logtostderr --logs /dev/stdin"
Restart=on-failure
[Install]

View File

@@ -46,10 +46,7 @@ http {
server {
listen 8443 ssl default_server;
{% if not disable_ipv6 %}
listen [::]:8443 ssl default_server;
{% endif %}
listen 127.0.0.1:8443 ssl default_server;
root /var/www/html;

View File

@@ -0,0 +1,3 @@
[Service]
Restart=always
RuntimeMaxSec=1d

View File

@@ -0,0 +1 @@
/^DKIM-Signature:/ IGNORE

View File

@@ -20,9 +20,12 @@ smtpd_tls_key_file=/var/lib/acme/live/{{ config.mail_domain }}/privkey
smtpd_tls_security_level=may
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_security_level=verify
# Send SNI extension when connecting to other servers.
# <https://www.postfix.org/postconf.5.html#smtp_tls_servername>
smtp_tls_servername = hostname
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtp_tls_policy_maps = socketmap:inet:127.0.0.1:8461:postfix
smtp_tls_policy_maps = inline:{nauta.cu=may}
smtpd_tls_protocols = >=TLSv1.2
# Disable anonymous cipher suites

View File

@@ -18,6 +18,7 @@ smtp inet n - y - - smtpd
submission inet n - y - 5000 smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_mandatory_protocols=>=TLSv1.3
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
@@ -31,11 +32,11 @@ submission inet n - y - 5000 smtpd
-o milter_macro_daemon_name=ORIGINATING
-o smtpd_client_connection_count_limit=1000
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port }}
-o cleanup_service_name=authclean
smtps inet n - y - 5000 smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_mandatory_protocols=>=TLSv1.3
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
@@ -48,10 +49,10 @@ smtps inet n - y - 5000 smtpd
-o smtpd_client_connection_count_limit=1000
-o milter_macro_daemon_name=ORIGINATING
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port }}
-o cleanup_service_name=authclean
#628 inet n - y - - qmqpd
pickup unix n - y 60 1 pickup
cleanup unix n - y - 0 cleanup
-o header_checks=regexp:/etc/postfix/incoming_header_cleanup
qmgr unix n - n 300 1 qmgr
#qmgr unix n - n 300 1 oqmgr
tlsmgr unix - - y 1000? 1 tlsmgr
@@ -88,5 +89,10 @@ localhost:{{ config.postfix_reinject_port }} inet n - n -
# We do not do this for received mails
# as this will break DKIM signatures
# if `Received` header is signed.
#
# This service also rewrites
# Subject with `[...]`
# to make sure the users
# cannot send unprotected Subject.
authclean unix n - - - 0 cleanup
-o header_checks=regexp:/etc/postfix/submission_header_cleanup

View File

@@ -2,3 +2,4 @@
/^X-Originating-IP:/ IGNORE
/^X-Mailer:/ IGNORE
/^User-Agent:/ IGNORE
/^Subject:/ REPLACE Subject: [...]

View File

@@ -27,7 +27,9 @@ def perform_initial_checks(mail_domain):
res = dict(mail_domain=mail_domain, A=A, AAAA=AAAA, MTA_STS=MTA_STS, WWW=WWW)
res["acme_account_url"] = shell("acmetool account-url", fail_ok=True)
res["dkim_entry"] = get_dkim_entry(mail_domain, dkim_selector="opendkim")
res["dkim_entry"], res["web_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
@@ -48,7 +50,11 @@ def get_dkim_entry(mail_domain, dkim_selector):
return
dkim_value_raw = f"v=DKIM1;k=rsa;p={dkim_pubkey};s=email;t=s"
dkim_value = '" "'.join(re.findall(".{1,255}", dkim_value_raw))
return f'{dkim_selector}._domainkey.{mail_domain}. TXT "{dkim_value}"'
web_dkim_value = "".join(re.findall(".{1,255}", dkim_value_raw))
return (
f'{dkim_selector}._domainkey.{mail_domain}. TXT "{dkim_value}"',
f'{dkim_selector}._domainkey.{mail_domain}. TXT "{web_dkim_value}"',
)
def query_dns(typ, domain):

View File

@@ -7,6 +7,7 @@ Restart=always
RestartSec=30
User=vmail
RuntimeDirectory=chatmail-metadata
UMask=0077
[Install]
WantedBy=multi-user.target

View File

@@ -7,6 +7,7 @@ Restart=always
RestartSec=30
User=vmail
RuntimeDirectory=doveauth
UMask=0077
[Install]
WantedBy=multi-user.target

View File

@@ -1,3 +1,4 @@
import datetime
import smtplib
import pytest
@@ -52,6 +53,14 @@ class TestSSHExecutor:
else:
pytest.fail("didn't raise exception")
def test_opendkim_restarted(self, sshexec):
"""check that opendkim is not running for longer than a day."""
out = sshexec(call=remote.rshell.shell, kwargs=dict(command="systemctl status opendkim"))
assert type(out) == str
since_date_str = out.split("since ")[1].split(";")[0]
since_date = datetime.datetime.strptime(since_date_str, "%a %Y-%m-%d %H:%M:%S %Z")
assert (datetime.datetime.now() - since_date).total_seconds() < 60 * 60 * 24
def test_remote(remote, imap_or_smtp):
lineproducer = remote.iter_output(imap_or_smtp.logcmd)
@@ -115,6 +124,27 @@ def test_reject_missing_dkim(cmsetup, maildata, from_addr):
s.sendmail(from_addr=from_addr, to_addrs=recipient.addr, msg=msg)
def test_rewrite_subject(cmsetup, maildata):
"""Test that subject gets replaced with [...]."""
user1, user2 = cmsetup.gen_users(2)
sent_msg = maildata(
"encrypted.eml",
from_addr=user1.addr,
to_addr=user2.addr,
subject="Unencrypted subject",
).as_string()
user1.smtp.sendmail(from_addr=user1.addr, to_addrs=[user2.addr], msg=sent_msg)
messages = user2.imap.fetch_all_messages()
assert len(messages) == 1
rcvd_msg = messages[0]
assert "Subject: [...]" not in sent_msg
assert "Subject: [...]" in rcvd_msg
assert "Subject: Unencrypted subject" in sent_msg
assert "Subject: Unencrypted subject" not in rcvd_msg
@pytest.mark.slow
def test_exceed_rate_limit(cmsetup, gencreds, maildata, chatmail_config):
"""Test that the per-account send-mail limit is exceeded."""

View File

@@ -85,7 +85,7 @@ class TestEndToEndDeltaChat:
attachsize = 1 * 1024 * 1024
num_to_send = quota // attachsize + 2
lp.sec(f"ac1: send {num_to_send} large files to ac2")
lp.indent(f"per-user quota is assumed to be: {quota/(1024*1024)}MB")
lp.indent(f"per-user quota is assumed to be: {quota / (1024 * 1024)}MB")
alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890"
msgs = []
for i in range(num_to_send):
@@ -97,7 +97,7 @@ class TestEndToEndDeltaChat:
msg = chat.send_file(str(attachment))
msgs.append(msg)
lp.indent(f"Sent out msg {i}, size {attachsize/(1024*1024)}MB")
lp.indent(f"Sent out msg {i}, size {attachsize / (1024 * 1024)}MB")
lp.sec("ac2: check messages are arriving until quota is reached")