Stalwart: Difference between revisions
No edit summary |
→Sending from subaddresses: Add note on planned feature |
||
| (29 intermediate revisions by 3 users not shown) | |||
| Line 2: | Line 2: | ||
== Setup == | == Setup == | ||
The following example enables the Stalwart mail server for the domain ''example.org'', listening on mail delivery SMTP/Submission | The following example enables the Stalwart mail server for the domain ''example.org'', listening on mail delivery SMTP/Submission (<code>25, 465</code>), IMAPS (<code>993</code>) and JMAP ports (8080/443) for mail clients to connect to. Mailboxes for the accounts <code>postmaster@example.org</code> and <code>user1@example.org</code> get created if they don't exist yet. | ||
{{file|/etc/nixos/configuration.nix|nix|3=environment.etc = { | |||
"stalwart/mail-pw1".text = "foobar"; | |||
"stalwart/mail-pw2".text = "foobar"; | |||
"stalwart/admin-pw".text = "foobar"; | |||
"stalwart/acme-secret".text = "secret123"; | |||
}; | |||
services.stalwart-mail = { | |||
enable = true; | enable = true; | ||
openFirewall = true; | openFirewall = true; | ||
settings = { | settings = { | ||
server = { | server = { | ||
hostname = "example.org"; | hostname = "mx1.example.org"; | ||
tls = { | tls = { | ||
enable = true; | enable = true; | ||
| Line 21: | Line 29: | ||
bind = "[::]:465"; | bind = "[::]:465"; | ||
protocol = "smtp"; | protocol = "smtp"; | ||
tls.implicit = true | |||
}; | }; | ||
imaps = { | imaps = { | ||
bind = "[::]:993"; | bind = "[::]:993"; | ||
protocol = "imap"; | protocol = "imap"; | ||
tls.implicit = true | |||
}; | }; | ||
jmap = { | jmap = { | ||
bind = "[::]:8080"; | bind = "[::]:8080"; | ||
url = "https://mail. | url = "https://mail.example.org"; | ||
protocol = " | protocol = "http"; | ||
}; | }; | ||
management = { | management = { | ||
| Line 45: | Line 55: | ||
challenge = "dns-01"; | challenge = "dns-01"; | ||
contact = "user1@example.org"; | contact = "user1@example.org"; | ||
domains = [ "example.org" ]; | domains = [ "example.org" "mx1.example.org" ]; | ||
provider = "cloudflare"; | provider = "cloudflare"; | ||
secret = " | secret = "%{file:/etc/stalwart/acme-secret}%"; | ||
}; | }; | ||
session.auth = { | session.auth = { | ||
| Line 55: | Line 65: | ||
storage.directory = "in-memory"; | storage.directory = "in-memory"; | ||
session.rcpt.directory = "'in-memory'"; | session.rcpt.directory = "'in-memory'"; | ||
directory."imap".lookup.domains = [ "example.org" ]; | directory."imap".lookup.domains = [ "example.org" ]; | ||
directory."in-memory" = { | directory."in-memory" = { | ||
| Line 61: | Line 70: | ||
principals = [ | principals = [ | ||
{ | { | ||
class = " | class = "individual"; | ||
name = "User 1"; | name = "User 1"; | ||
secret = " | secret = "%{file:/etc/stalwart/mail-pw1}%"; | ||
email = [ "user1@example.org" ]; | email = [ "user1@example.org" ]; | ||
} | } | ||
| Line 69: | Line 78: | ||
class = "individual"; | class = "individual"; | ||
name = "postmaster"; | name = "postmaster"; | ||
secret = " | secret = "%{file:/etc/stalwart/mail-pw1}%"; | ||
email = [ "postmaster@example.org" ]; | email = [ "postmaster@example.org" ]; | ||
} | } | ||
]; | ]; | ||
}; | |||
authentication.fallback-admin = { | |||
user = "admin"; | |||
secret = "%{file:/etc/stalwart/admin-pw}%"; | |||
}; | }; | ||
}; | }; | ||
| Line 82: | Line 95: | ||
"webadmin.example.org" = { | "webadmin.example.org" = { | ||
extraConfig = '' | extraConfig = '' | ||
reverse_proxy http://127.0. | reverse_proxy http://127.0.0.1:8080 | ||
''; | ''; | ||
serverAliases = [ | serverAliases = [ | ||
| Line 88: | Line 101: | ||
"autoconfig.example.org" | "autoconfig.example.org" | ||
"autodiscover.example.org" | "autodiscover.example.org" | ||
"mail.example.org" | |||
]; | ]; | ||
}; | }; | ||
}; | }; | ||
};}} | };}} | ||
== | TLS key generation is done using DNS-01 challenge through Cloudflare domain provider, see dns-update library for [https://github.com/stalwartlabs/dns-update further providers] or configure [https://stalw.art/docs/server/tls/certificates manual certificates]. | ||
=== DNS records === | |||
Before adding required records to the example domain <code>example.org</code>, we need to register the domain on the Stalwart server.<syntaxhighlight lang="shell"> | |||
stalwart-cli --url https://webadmin.example.org domain create example.org | |||
</syntaxhighlight>Authenticate using the fallback-admin password. | |||
Review the list of which DNS records are required including their values for the mail server to work at https://webadmin.example.org/manage/directory/domains/tuxtux.com.co/view. Especially following records are essential: | |||
{| class="wikitable" | |||
! Record Type | |||
! Name | |||
! Value / Target | |||
! Notes | |||
|- | |||
| A | |||
| example.org | |||
| ''IPv4 address of the mail server'' | |||
| Required | |||
|- | |||
| AAAA | |||
| example.org | |||
| ''IPv6 address of the mail server'' | |||
| Required | |||
|- | |||
| CNAME | |||
| autoconfig | |||
| example.org | |||
| Mail client autoconfiguration | |||
|- | |||
| CNAME | |||
| autodiscover | |||
| example.org | |||
| Outlook / Exchange compatibility | |||
|- | |||
| CNAME | |||
| mail | |||
| example.org | |||
| Mail host | |||
|- | |||
| CNAME | |||
| mta-sts | |||
| example.org | |||
| MTA-STS | |||
|- | |||
| CNAME | |||
| webadmin | |||
| example.org | |||
| Stalwart web administration interface | |||
|- | |||
| MX | |||
| example.org | |||
| mx1.example.org | |||
| Mail delivery | |||
|- | |||
| SRV | |||
| _imaps._tcp | |||
| ''See Web Admin for exact values'' | |||
| IMAPS service | |||
|- | |||
| SRV | |||
| _submissions._tcp | |||
| ''See Web Admin for exact values'' | |||
| SMTP Submission service | |||
|- | |||
| TLSA | |||
| _25._tcp.example.org. | |||
| 3 1 1 … | |||
| Only the record starting with <code>3 1 1</code> is required | |||
|- | |||
| TLSA | |||
| _25._tcp.mx1.example.org. | |||
| 3 1 1 … | |||
| Only the record starting with <code>3 1 1</code> is required | |||
|- | |||
| TXT | |||
| 202409e._domainkey | |||
| ''DKIM public key'' | |||
| DKIM | |||
|- | |||
| TXT | |||
| 202409r._domainkey | |||
| ''DKIM public key'' | |||
| DKIM | |||
|- | |||
| TXT | |||
| _dmarc | |||
| ''DMARC policy'' | |||
| DMARC | |||
|- | |||
| TXT | |||
| mx1 | |||
| ''SPF or server information'' | |||
| Depends on configuration | |||
|- | |||
| TXT | |||
| _smtp._tls | |||
| ''MTA-STS policy'' | |||
| SMTP TLS reporting | |||
|- | |||
| TXT | |||
| example.org | |||
| ''SPF record'' | |||
| SPF | |||
|} | |||
=== DNSSEC === | === DNSSEC === | ||
| Line 103: | Line 221: | ||
_25._tcp.mx1.example.org. 10800 IN TLSA 3 1 1 7f59d873a70e224b184c95a4eb54caa9621e47d48b4a25d312d83d96 e3498238 | _25._tcp.mx1.example.org. 10800 IN TLSA 3 1 1 7f59d873a70e224b184c95a4eb54caa9621e47d48b4a25d312d83d96 e3498238 | ||
_25._tcp.mx1.example.org. 10800 IN RRSIG TLSA 13 5 10800 20230601000000 20230511000000 39688 example.org. He9VYZ35xTC3fNo8GJa6swPrZodSnjjIWPG6Th2YbsOEKTV1E8eGtJ2A +eyBd9jgG+B3cA/jw8EJHmpvy/buCw== | _25._tcp.mx1.example.org. 10800 IN RRSIG TLSA 13 5 10800 20230601000000 20230511000000 39688 example.org. He9VYZ35xTC3fNo8GJa6swPrZodSnjjIWPG6Th2YbsOEKTV1E8eGtJ2A +eyBd9jgG+B3cA/jw8EJHmpvy/buCw== | ||
== Configuration == | |||
=== Mail aliases === | |||
Considering the configuration above, we could add a mail alias for <code>user1@example.org</code> by simply adding further addresses to the <code>email</code>-array such as <code>user1real@example.org</code> | |||
{{file|/etc/nixos/configuration.nix|nix|3=services.stalwart-mail = { | |||
settings = { | |||
[...] | |||
directory."in-memory" = { | |||
type = "memory"; | |||
principals = [ | |||
{ | |||
class = "individual"; | |||
name = "User 1"; | |||
secret = "%{file:/etc/stalwart/mail-pw1}%"; | |||
email = [ "user1@example.org" "user1real@example.org ]; | |||
} | |||
]; | |||
}; | |||
}; | |||
};}} | |||
== Tips and tricks == | == Tips and tricks == | ||
=== Auto update TLSA records === | |||
Stalwart [https://github.com/stalwartlabs/stalwart/issues/1664 does not yet] automatically update the TLSA record if your ACME certificate changes. | |||
Following script is a possible workaounrd. It extracts the ACME cert every five minute, calculates the TLSA hash and compares it with the upstream record. If it doesn't match, it uses [https://github.com/Stenstromen/gotlsaflare gotlsaflare] to update the TLSA record on Cloudflare. | |||
<syntaxhighlight lang="nixos"> | |||
systemd.services.tlsa-cloudflare-update = { | |||
description = "Check and update TLSA/DANE record for mx1 from Stalwart ACME Cert"; | |||
after = [ | |||
"network-online.target" | |||
"stalwart-mail.service" | |||
]; | |||
wants = [ | |||
"network-online.target" | |||
"stalwart-mail.service" | |||
]; | |||
serviceConfig = { | |||
Type = "oneshot"; | |||
User = "stalwart-mail"; | |||
Group = "stalwart-mail"; | |||
EnvironmentFile = config.age.secrets.gotlsaflare-cloudflare-token.path; | |||
RuntimeDirectory = "stalwart-tlsa"; | |||
}; | |||
environment = { | |||
DOMAIN = "example.org"; | |||
SUBDOMAIN = "mail"; | |||
PORT = "25"; | |||
ACME_PROVIDER_ID = "cloudflare"; | |||
}; | |||
path = with pkgs; [ | |||
bash | |||
coreutils | |||
openssl | |||
dnsutils | |||
gotlsaflare | |||
rocksdb.tools | |||
gawk | |||
]; | |||
script = '' | |||
set -eu | |||
TLSA_RECORD="_$PORT._tcp.$SUBDOMAIN.$DOMAIN" | |||
DB_PATH="/var/lib/stalwart-mail/db" | |||
TEMP_RAW="/run/stalwart-tlsa/cert.bundle" | |||
TEMP_CRT="/run/stalwart-tlsa/cert.crt" | |||
echo "Starting TLSA update process for $DOMAIN" | |||
ldb --db="$DB_PATH" --column_family=s get "acme.$ACME_PROVIDER_ID.cert" | base64 -d > "$TEMP_RAW" | |||
if [ ! -s "$TEMP_RAW" ]; then | |||
echo "ERROR: ACME certificate extraction failed" | |||
exit 1 | |||
fi | |||
openssl x509 -in "$TEMP_RAW" -out "$TEMP_CRT" | |||
LOCAL_HASH=$(openssl x509 -in "$TEMP_CRT" -pubkey -noout | openssl pkey -pubin -outform DER | openssl sha256 | awk '{print tolower($2)}') | |||
echo "Local hash: $LOCAL_HASH" | |||
UPSTREAM_HASH=$(dig +nosplit +short TLSA "$TLSA_RECORD" | awk '{print tolower($4)}' | head -n1) | |||
echo "Upstream hash: $UPSTREAM_HASH" | |||
if [ "$LOCAL_HASH" = "$UPSTREAM_HASH" ]; then | |||
echo "Hashes match. DNS is up to date." | |||
exit 0 | |||
fi | |||
echo "Hashes differ! Updating Cloudflare..." | |||
gotlsaflare update \ | |||
--url "$DOMAIN" \ | |||
--subdomain "$SUBDOMAIN" \ | |||
--tcp"$PORT" \ | |||
--cert "$TEMP_CRT" | |||
echo "TLSA update completed successfully." | |||
''; | |||
}; | |||
systemd.timers.tlsa-cloudflare-update = { | |||
description = "Run TLSA check and update every 5 minutes"; | |||
wantedBy = [ "timers.target" ]; | |||
timerConfig = { | |||
OnBootSec = "2m"; | |||
OnUnitActiveSec = "5m"; | |||
Unit = "tlsa-cloudflare-update.service"; | |||
}; | |||
}; | |||
</syntaxhighlight> | |||
Adapt the variables <code>DOMAIN</code>, <code>SUBDOMAIN</code>, and <code>PORT</code> according to your needs. The variable <code>ACME_PROVIDER_ID</code> corresponds to the ACME profile name you've setup in the Stalwart webadmin interface. <code>EnvironmentFile</code> points to a file containing the secret Cloudflare api token in the format: TOKEN=12345678[...]. | |||
=== Sending from subaddresses === | |||
Receiving mails to subaddresses like <code>john+secondary@example.org</code> is enabled by default. Sending from subaddresses will fail with "You are not allowed to send from this address" as long as they are not an configured alias address. You can disable this check but it will allow any authenticated user to send from any other address. | |||
{{file|/etc/nixos/configuration.nix|nix|3=services.stalwart-mail = { | |||
settings = { | |||
[...] | |||
session.auth.must-match-sender = false; | |||
}; | |||
};}} | |||
A configuration option to customize the pattern of authorized sender addresses is a [https://github.com/stalwartlabs/stalwart/issues/394#issuecomment-3705990056 planned feature]. | |||
=== Test mail server === | |||
You can use several online tools to test your mail server configuration: | |||
* [https://en.internet.nl/test-mail en.internet.nl/test-mail]: Test your mail server configuration for validity and security. | |||
* [https://www.mail-tester.com mail-tester.com]: Send a mail to this service and get a rating about the "spaminess" of your mail server. | |||
* Send a mail to the echo server <code>echo@univie.ac.at</code>. You should receive a response containing your message in several seconds. | |||
=== Unsecure setup for testing environments === | === Unsecure setup for testing environments === | ||
The following minimal configuration example is unsecure and for testing purpose only. It will run the Stalwart mail server on <code>localhost</code>, listening on port <code>143</code> (IMAP) and <code>587</code> (Submission). Users <code>alice</code> and <code>bob</code> are configured with the password <code>foobar</code>.{{file|/etc/nixos/configuration.nix|nix|3=services.stalwart-mail = { | The following minimal configuration example is unsecure and for testing purpose only. It will run the Stalwart mail server on <code>localhost</code>, listening on port <code>143</code> (IMAP) and <code>587</code> (Submission). Users <code>alice</code> and <code>bob</code> are configured with the password <code>foobar</code>.{{file|/etc/nixos/configuration.nix|nix|3=services.stalwart-mail = { | ||
enable = true; | enable = true; | ||
settings = { | settings = { | ||
server = { | server = { | ||