aboutsummaryrefslogtreecommitdiffstats
path: root/docs/MAIL-SERVER.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/MAIL-SERVER.md')
-rw-r--r--docs/MAIL-SERVER.md209
1 files changed, 175 insertions, 34 deletions
diff --git a/docs/MAIL-SERVER.md b/docs/MAIL-SERVER.md
index c5a0d17..cbad53d 100644
--- a/docs/MAIL-SERVER.md
+++ b/docs/MAIL-SERVER.md
@@ -1,6 +1,7 @@
# Mail Server — Postfix send-only with SPF/DKIM/DMARC
-> Target: meshbay.org (Ubuntu 26.04 LTS, OVH VPS 164.132.246.44)
+> Target: meshbay.org (Ubuntu 26.04 LTS, OVH VPS 164.132.246.44 /
+> 2001:41d0:20a:900::2389)
>
> Purpose: send confirmation codes to validate user email addresses at
> registration. The hub sends mail; it does not receive it. OVH MX servers
@@ -8,16 +9,18 @@
---
-## 1. Current DNS state (before setup)
+## 1. DNS state
-| Record | Value | Status |
-|---|---|---|
-| A | `164.132.246.44` | OK |
-| MX | `1 mx1.mail.ovh.net` / `5 mx2` / `100 mx3` | OK — receiving stays OVH |
-| SPF | `v=spf1 include:mx.ovh.com -all` | Must add VPS IP |
-| DKIM | (none) | Must create |
-| DMARC | (none) | Must create |
-| PTR (rDNS) | `vps-7148538e.vps.ovh.net` | Must change to `meshbay.org` |
+| Record | Value |
+|---|---|
+| A / AAAA | `164.132.246.44` / `2001:41d0:20a:900::2389` |
+| MX | `1 mx1.mail.ovh.net` / `5 mx2` / `100 mx3` — inbound mail and redirections stay at OVH |
+| SPF | `v=spf1 ip4:164.132.246.44 ip6:2001:41d0:20a:900::2389 include:mx.ovh.com -all` |
+| DKIM | `meshbay._domainkey` — RSA 2048, `h=sha256` |
+| DMARC | `v=DMARC1; p=quarantine; sp=quarantine; adkim=s; aspf=s; pct=100; rua=mailto:postmaster@meshbay.org` |
+| PTR (rDNS) | `meshbay.org.` for both the IPv4 and the IPv6 address |
+
+DMARC is at the intermediate `quarantine` step; `reject` is the target (§7).
## 2. What must happen
@@ -28,25 +31,28 @@ Set via the **OVH control panel** (not DNS zone):
1. OVH Manager → Bare Metal Cloud → VPS → IP
2. Click the gear icon next to `164.132.246.44` → Modify reverse DNS
3. Set to: `meshbay.org.` (trailing dot)
+4. Same for `2001:41d0:20a:900::2389` — Postfix sends over IPv6 whenever the
+ receiver has an AAAA MX (Gmail does)
Most receiving MTAs reject or score down mail from an IP whose PTR does not
match the HELO/EHLO hostname.
### 2.2 DNS zone records (OVH DNS zone editor)
-**Update SPF** — add the VPS IP alongside the existing OVH include:
+**Update SPF** — add both VPS addresses alongside the existing OVH include:
```
-meshbay.org. TXT "v=spf1 ip4:164.132.246.44 include:mx.ovh.com -all"
+meshbay.org. TXT "v=spf1 ip4:164.132.246.44 ip6:2001:41d0:20a:900::2389 include:mx.ovh.com -all"
```
-**Add DKIM** — after running the install script which generates the key:
+**Add DKIM** — once the key is generated (§3.3):
```
meshbay._domainkey.meshbay.org. TXT "v=DKIM1; h=sha256; k=rsa; p=<PUBLIC_KEY_BASE64>"
```
-The script prints the exact record content. The selector is `meshbay`.
+The `p=` value is in `/etc/opendkim/keys/meshbay.org/meshbay.txt`, written by
+`opendkim-genkey`. The selector is `meshbay`.
**Add DMARC** — start with `none` policy (monitoring), tighten to `quarantine`
then `reject` after confirming deliverability:
@@ -60,12 +66,13 @@ Target (after validation):
_dmarc.meshbay.org. TXT "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; rua=mailto:postmaster@meshbay.org"
```
-### 2.3 Server-side (automated by the install script)
+### 2.3 Server-side
| Component | Role |
|---|---|
| **Postfix** | MTA — sends mail directly to recipient MX servers |
| **OpenDKIM** | Signs outgoing mail with the domain's DKIM private key |
+| **systemd-resolved drop-in** | Lets the domain's MX resolve on a host named after the domain (§9.2) |
Postfix is configured as **send-only** (no listening on port 25 from outside).
@@ -73,27 +80,112 @@ Postfix is configured as **send-only** (no listening on port 25 from outside).
## 3. Installation
-Run the provided script on the server:
+### 3.1 Packages
```bash
-# From the local machine:
-scp QE/deploy/setup-mailserver.sh cbesson@meshbay.org:/tmp/
-ssh cbesson@meshbay.org 'sudo bash /tmp/setup-mailserver.sh'
+# Ubuntu — answer "Internet Site", mail name meshbay.org
+sudo apt install postfix opendkim opendkim-tools mailutils
+# Fedora
+sudo dnf install postfix opendkim opendkim-tools mailx
+```
+
+### 3.2 Postfix — `/etc/postfix/main.cf`
+
+```
+smtpd_banner = $myhostname ESMTP
+biff = no
+append_dot_mydomain = no
+
+smtp_tls_security_level = may
+smtp_tls_CApath = /etc/ssl/certs
+smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
+smtp_tls_loglevel = 1
+
+myhostname = meshbay.org
+myorigin = meshbay.org
+mydestination = localhost.localdomain, localhost
+mynetworks = 127.0.0.0/8 [::1]/128
+
+inet_interfaces = loopback-only
+inet_protocols = all
+
+relayhost =
+smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination
+
+mailbox_size_limit = 0
+recipient_delimiter = +
+
+milter_protocol = 6
+milter_default_action = accept
+smtpd_milters = local:opendkim/opendkim.sock
+non_smtpd_milters = $smtpd_milters
+
+compatibility_level = 3.9
```
-Or directly on the server:
+`inet_interfaces = loopback-only` makes the server send-only; `meshbay.org` is
+absent from `mydestination` so that mail to its OVH mailboxes and redirections
+goes through the MX lookup (§9.1).
+
+### 3.3 DKIM key
+
+```bash
+sudo mkdir -p /etc/opendkim/keys/meshbay.org
+sudo opendkim-genkey -b 2048 -d meshbay.org -s meshbay -D /etc/opendkim/keys/meshbay.org/
+sudo chown -R opendkim:opendkim /etc/opendkim
+sudo chmod 600 /etc/opendkim/keys/meshbay.org/meshbay.private
+```
+
+### 3.4 OpenDKIM
+
+`/etc/opendkim.conf`:
+
+```
+Syslog yes
+SyslogSuccess yes
+LogWhy yes
+Canonicalization relaxed/simple
+Mode s
+SubDomains no
+KeyTable /etc/opendkim/KeyTable
+SigningTable refile:/etc/opendkim/SigningTable
+InternalHosts /etc/opendkim/TrustedHosts
+OversignHeaders From
+Socket local:/var/spool/postfix/opendkim/opendkim.sock
+PidFile /run/opendkim/opendkim.pid
+UMask 007
+UserID opendkim
+```
+
+| File | Content |
+|---|---|
+| `/etc/opendkim/KeyTable` | `meshbay._domainkey.meshbay.org meshbay.org:meshbay:/etc/opendkim/keys/meshbay.org/meshbay.private` |
+| `/etc/opendkim/SigningTable` | `*@meshbay.org meshbay._domainkey.meshbay.org` |
+| `/etc/opendkim/TrustedHosts` | `127.0.0.1`, `::1`, `localhost`, `meshbay.org` — one per line |
+
+The socket lives inside the Postfix spool so that Postfix reaches it:
```bash
-sudo bash setup-mailserver.sh
+sudo mkdir -p /var/spool/postfix/opendkim
+sudo chown opendkim:postfix /var/spool/postfix/opendkim
+sudo chmod 750 /var/spool/postfix/opendkim
+sudo usermod -aG opendkim postfix
```
-The script:
-1. Installs Postfix + OpenDKIM
-2. Configures Postfix as send-only (inet_interfaces = loopback-only)
-3. Generates a 2048-bit RSA DKIM key with selector `meshbay`
-4. Wires OpenDKIM into Postfix via milter
-5. Prints the DKIM TXT record to add to DNS
-6. Sends a test email (if an address is given as argument)
+### 3.5 systemd-resolved
+
+Install the drop-in described in §9.2, then
+`sudo systemctl daemon-reload && sudo systemctl restart systemd-resolved`.
+
+### 3.6 Start
+
+```bash
+sudo systemctl enable --now opendkim postfix
+sudo systemctl restart opendkim postfix
+dig +short MX meshbay.org # must list the OVH MX (§9.2)
+```
+
+Then add the DNS records of §2.2 and set the PTR (§2.1).
## 4. Verification
@@ -210,9 +302,58 @@ Once deliverability is confirmed (SPF pass, DKIM pass on test emails):
## 8. Fedora 44 notes
-The install script auto-detects Fedora and uses `dnf` instead of `apt`.
-OpenDKIM is in the `opendkim` package, same as Ubuntu. The main difference is
-the Postfix service name (`postfix` on both) and that SELinux may need a policy
-for the OpenDKIM socket. The script handles this with `setsebool -P
-dkim_milter_enable on` if SELinux is enforcing, and falls back to a custom
-module if the boolean does not exist.
+Packages come from `dnf` (§3.1); OpenDKIM is in the `opendkim` package and the
+Postfix service is `postfix`, as on Ubuntu. When SELinux is enforcing, Postfix
+needs permission to connect to the OpenDKIM socket:
+`sudo setsebool -P dkim_milter_enable on`. If that boolean does not exist, a
+local module granting `postfix_smtpd_t` `connectto` on `opendkim_t`
+`unix_stream_socket` does the same.
+
+## 9. Hostname equal to the mail domain
+
+The server's hostname is `meshbay.org`, which is also the domain whose mailboxes
+and redirections live at OVH. Two settings keep mail addressed to `@meshbay.org`
+(for example `devel@meshbay.org`, an OVH redirection) going to OVH's MX instead
+of being handled on the server.
+
+### 9.1 `mydestination` does not list the domain
+
+```
+mydestination = localhost.localdomain, localhost
+```
+
+Postfix delivers locally every domain listed in `mydestination`, without an MX
+lookup. With `meshbay.org` in the list — which is also what the default
+`$myhostname` expands to — a message to `devel@meshbay.org` is refused with
+`550 5.1.1 User unknown in local recipient table`, or bounced by `postfix/local`
+into the local mailbox of the sender, and never reaches OVH.
+
+Consequence: mail for `root` and `cbesson` (cron output, bounces) is qualified
+with `myorigin = meshbay.org` and goes to OVH too. It is delivered only if the
+address exists there as a mailbox or a redirection.
+
+### 9.2 systemd-resolved does not answer for the hostname
+
+```
+# /etc/systemd/system/systemd-resolved.service.d/no-synthesize-hostname.conf
+[Service]
+Environment=SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME=0
+```
+
+systemd-resolved answers queries for the local hostname itself: A/AAAA with the
+machine's addresses, and an authoritative empty answer (`aa`, `NOERROR`, no
+records) for every other type, MX included. Postfix then falls back to the A
+record and connects to its own port 25, which is loopback-only:
+`status=deferred (connect to meshbay.org[164.132.246.44]:25: Connection refused)`.
+The drop-in turns the synthesis off, so the query goes to the upstream resolver
+and returns OVH's MX. Hostname resolution for `sudo` and other local tools is
+unaffected: it goes through the NSS `myhostname` module, not through resolved.
+
+After `systemctl daemon-reload && systemctl restart systemd-resolved`:
+
+```bash
+dig +short MX meshbay.org # must list mx1/mx2/mx3.mail.ovh.net
+echo test | mailx -s test devel@meshbay.org
+sudo journalctl -u postfix@- --since "1 min ago" | grep status=
+# Expected: relay=mx1.mail.ovh.net[...]:25, status=sent
+```