You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Three roadmap rows the fork was best known for not having
Authentication-Results (RFC 8601) and Received-SPF (RFC 7208 9.1). Until now
the SPF, DKIM and DMARC verdicts existed only as X-hMailServer scoring headers,
so nothing downstream could act on them and our own ARC seals reported
spf=none - they read it from a Received-SPF header this server never emitted.
The verdicts had to be plumbed, not recomputed. SpamTestResult carries only a
test name, a pass/fail and a score, so the things the header needs - each
signature's d=, the SPF identity, the DMARC policy that applied - were known
only inside the tests and thrown away on return. There is now a carrier the
connection owns and both phases fill: SPF is a pre-transmission test while DKIM
and DMARC are post-transmission, so anything scoped to one phase could only ever
describe half the answer.
Both switches default off, because the field is a claim other systems act on and
it is worth something only if the reader trusts this server's name.
RFC 8601 section 5 stripping is implemented. A field arriving with our own
authserv-id was not written by us - this runs before ours is added - so it is a
forgery, and a downstream filter trusting our name would otherwise act on a
dkim=pass the sender wrote for themselves. Fields bearing anyone else's
authserv-id survive byte for byte, which is why the rewrite walks raw lines
instead of reparsing: the message may carry a DKIM signature over those exact
bytes. Two things nearly broke that. SplitString discards the trailing empty
substring, so reconstructing from "is this the last element" would have eaten
the header's final newline and left \r\r\n where the header/body boundary
belongs - silent corruption on the one path meant to remove a forgery. And
Message::SetSize takes an int where the size is a long, which /WX rejects.
Every value that reaches these headers is attacker-supplied - envelope sender,
HELO string, a signature's d= - so they are dropped to a conservative character
set rather than escaped, and capped. There is no escaping that is safe in every
position the text can land in: inside a comment, beside a tag, at the start of a
folded line. The Received-SPF explanation is generated locally rather than
quoted from the policy's exp=, which is a TXT record the sending domain
controls. There is deliberately no auth= method: this writes nothing for
authenticated submissions, so an auth= slot would be a field nothing could ever
populate, which is the exists-but-inert shape this tree has been bitten by.
Client certificates (mutual TLS) for inbound sessions. Per-port off / request /
require with a per-port CA bundle. The inbound path is a separate branch and a
separate verifier class, so outbound verification, MTA-STS enforcement and
DANE-EE are untouched - and the RFC 7435 opportunistic forgiveness in
OverrideResult_, which is right for outbound, can never apply inbound.
verify_fail_if_no_peer_cert is what rejects a certificate-less client, because
the callback is never invoked in that case. Save-time validation refuses the
three configurations that would look enforced and enforce nothing: a policy on
a plaintext port, a policy with no CA file, and require on STARTTLSOptional,
where a client that never issues STARTTLS is never asked for a certificate. A
require port with an unusable CA bundle does not start, rather than accepting
connections it can only reject.
DKIM dual-selector rotation. A second selector and key per domain so the new
selector can be published and confirmed in DNS before it signs anything. The
primary signs and the secondary never does: it is by definition waiting for
propagation, so a second signature would fail at every receiver until
propagation completed, and while RFC 6376 6.1 says one passing signature
suffices, real filters score a broken one against you. Promote refuses when
either staged value is empty - it would otherwise blank the primary and stop
signing silently - and refuses when the staged key is not on disk, checked
before the old key is cleared rather than after.
Schema 6006 -> 6007 -> 6008 across all four dialects, both create-table paths
and the DBUpdater chain. Two probes per step, not one: SQL CE commits each
ALTER implicitly and cannot roll back, so a two-column step can half-apply and
each column has to be proven separately. Every default is today's behaviour -
empty selectors, policy 0 - so an upgraded database behaves exactly as it did.
README and Roadmap audited against the code rather than against each other,
which found claims that were simply false. "Full RFC 4013 SASLprep of non-ASCII
credentials" was true of PLAIN and LOGIN and not of SCRAM, which does none. The
metrics paragraph named an uptime metric and a database-up metric that no longer
exist under those names, described a histogram as a summary, and said /readyz
checks pool state when it now requires a real database round trip. Four rows
were marked not-started that are implemented, and nine Details described gaps
that have since been closed.
Full suite: 1411 passed, 0 failed, 0 skipped, crash oracle clean.
Copy file name to clipboardExpand all lines: README.md
+27-13Lines changed: 27 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,44 +29,45 @@ Mail protocols
29
29
--------------
30
30
31
31
***SMTP** with PIPELINING, ENHANCEDSTATUSCODES, 8BITMIME, SIZE, CHUNKING/BDAT (RFC 3030), DSN delivery status notifications (RFC 3461/3464) and SMTPUTF8/EAI for internationalised addresses.
32
-
***IMAP4rev1 and IMAP4rev2**, with IDLE, MOVE (RFC 6851), UIDPLUS (RFC 4315), CONDSTORE/QRESYNC (RFC 7162), SEARCHRES (RFC 5182), ESEARCH (RFC 4731), SORT, ACL, NAMESPACE, ID (RFC 2971), SPECIAL-USE (RFC 6154) and QUOTA.
32
+
***IMAP4rev1**, plus **IMAP4rev2** (RFC 9051) advertised with its behavioural deltas implemented — a few of the extensions rev2 folds in (LIST-STATUS, non-synchronising literals, BINARY) are still missing — with IDLE, MOVE (RFC 6851), UIDPLUS (RFC 4315), CONDSTORE/QRESYNC (RFC 7162), SEARCHRES (RFC 5182), ESEARCH (RFC 4731), SORT, ACL, NAMESPACE, ID (RFC 2971), SPECIAL-USE (RFC 6154, including explicit designation via `CREATE ... (USE (\Sent))`) and QUOTA.
33
33
***POP3**, including retrieval from external POP3 accounts on a schedule.
34
34
***Public folders**, shared across accounts with per-user ACLs.
35
35
36
36
Transport security
37
37
------------------
38
38
39
-
***TLS 1.2 and 1.3** by default, on implicit-TLS and STARTTLS ports, with SNI and configurable cipher suites.
39
+
***TLS 1.2 and 1.3** by default, on implicit-TLS and STARTTLS ports, with SNI, configurable cipher suites (separately for TLS ≤ 1.2 and TLS 1.3) and configurable key-exchange groups — **hybrid post-quantum key exchange** (X25519MLKEM768) is preferred by default, on every TLS context inbound and outbound.
40
40
***MTA-STS** (RFC 8461) policy discovery and enforcement for outbound mail, and optional hosting of your own policy at `mta-sts.<domain>`.
41
41
***DANE** (RFC 7672) with full in-process **DNSSEC validation** (RFC 4033–4035) — a bogus chain blocks delivery to that host rather than silently downgrading. The **MX RRset is validated too**, not just the TLSA record (RFC 7672 §2.2): DANE is applied only to a host the recipient domain provably published, so a forged MX answer cannot redirect delivery to a host whose own TLSA record then validates.
42
42
* DNSSEC validation also protects SPF, DKIM and DMARC record lookups.
43
-
***TLS-RPT** (RFC 8460) daily aggregate reports to recipient domains.
43
+
***TLS-RPT** (RFC 8460) daily aggregate reports to recipient domains — off until `TlsRptFromAddress` is set (the server notes this in the application log while statistics are collected unsent).
44
44
***ACME v2 (Let's Encrypt)** built in: certificates are issued, renewed, assigned to TLS ports and hot-reloaded without a restart. The private key is reused across renewals, so published DANE TLSA records stay valid.
45
45
46
46
Sender authentication and anti-abuse
47
47
------------------------------------
48
48
49
49
***SPF**, **DKIM** signing and verification (including Ed25519, RFC 8463) and **DMARC** evaluation with alignment.
50
-
***ARC** sealing (RFC 8617) so forwarded mail keeps a verifiable authentication chain.
51
-
***SRS** sender rewriting for forwarded mail, and optional **BATV** (`prvs`) backscatter protection.
50
+
***DKIM signature timestamps**: every signature carries `t=`; an `x=` expiry is added when `DKIMSignatureValiditySeconds` is set, and expired signatures are refused on verification (on by default, with a configurable clock-skew allowance). Optional **oversigning** (`DkimOversignHeaders`, off by default) stops a second `From:` being prepended to signed mail.
51
+
***ARC** sealing (RFC 8617) so forwarded mail keeps a verifiable authentication chain — off by default (`ArcSealingEnabled`).
52
+
* Optional **SRS** sender rewriting for forwarded mail (`SRSEnabled`, off by default), and optional **BATV** (`prvs`) backscatter protection.
52
53
***SpamAssassin** integration, **DNSBL** and **SURBL** lookups, greylisting, HELO/PTR/MX sanity checks and a weighted scoring pipeline.
53
54
***Virus scanning** via ClamAV (clamd or clamscan) or any command-line scanner.
54
55
* Attachment blocking, IP ranges with per-range policy, and connection auto-banning after repeated authentication failures.
55
56
56
57
Account security and authentication
57
58
-----------------------------------
58
59
59
-
***SCRAM-SHA-256** SASL across IMAP, SMTP submission and POP3, plus **SCRAM-SHA-256-PLUS** channel binding on all three, with deterministic anti-enumeration salts.
60
+
***SCRAM-SHA-256** SASL across IMAP, SMTP submission and POP3, plus **SCRAM-SHA-256-PLUS** channel binding on all three, with deterministic anti-enumeration salts. SMTP and POP3 offer SCRAM whenever AUTH is available; on IMAP, SASL (PLAIN and SCRAM alike) sits behind one setting whose shipped default is off.
60
61
***OAuth2 / OpenID Connect** bearer tokens — SASL XOAUTH2 and OAUTHBEARER (RFC 7628) — validated against an external identity provider's signing key.
61
62
***LDAP directory authentication** against Active Directory or any LDAP directory, so accounts authenticate with their domain password. Simple bind and SASL Negotiate; LDAPS and StartTLS; certificate validation on by default, and a password is never sent over an unprotected connection unless that is explicitly permitted. Unlike the Windows-logon path it needs no domain-joined host, which is the usual situation for a mail server in a DMZ. Infrastructure failures are reported separately from wrong passwords, so a directory outage does not read as a hundred users mistyping. Off by default: the whole `[LDAP]` ini section is absent until you add it.
62
63
***Argon2id** and **PBKDF2-HMAC-SHA256** password hashing, with transparent upgrade on login, a minimum-accepted-hash policy, and an optional server-side pepper.
63
-
* Full RFC 4013 SASLprep of non-ASCII credentials.
64
+
* Full RFC 4013 SASLprep of non-ASCII credentials on the PLAIN and LOGIN paths (SCRAM usernames are matched as sent).
64
65
* Optional **TOTP two-factor authentication** for administrative logon.
65
66
66
67
Mail filtering and routing
67
68
--------------------------
68
69
69
-
***Sieve** (RFC 5228) — a standards-based interpreter runs each account's active script during delivery (`keep`, `fileinto`, `discard`, `redirect`, implicit keep), with an optional **ManageSieve** (RFC 5804) listener so clients can manage scripts over TCP.
70
+
***Sieve** (RFC 5228) — a standards-based interpreter runs each account's active script during delivery (`keep`, `fileinto`, `discard`, `redirect`, implicit keep, plus the `copy`, `relational`, `subaddress`, `imap4flags` and `vacation` extensions), with an optional **ManageSieve** (RFC 5804) listener so clients can manage scripts over TCP.
70
71
* The original rules engine, with global and per-account rules, regular-expression criteria and scripted actions.
71
72
* Server-side **event scripts** (VBScript/JScript) on connection, HELO, DATA, accept and delivery events.
72
73
* Routes, aliases, distribution lists, catch-all addresses and plus-addressing.
@@ -93,7 +94,7 @@ Technology
93
94
| MySQL/MariaDB client | MariaDB Connector/C, shipped as `libmysql.dll` with auth plugins — works with MySQL 8 `caching_sha2_password` and MariaDB `ed25519`/`gssapi` out of the box |
| Extensibility | COM/IDispatch API, plus a REST administration API |
96
-
| Schema | Database version 6005, upgradeable from every earlier hMailServer release |
97
+
| Schema | Database version 6008, upgradeable from every earlier hMailServer release |
97
98
98
99
**Quality gates.** Every release ships SPDX and CycloneDX SBOMs (Syft). The repository runs CodeQL analysis, Dependabot CVE alerts with grouped update pull requests, a dependency-review gate on pull requests, an installer smoke test that installs the built installer on a clean machine and verifies the service comes up, and a monthly comparison against the original upstream repository so nothing landing there is missed.
99
100
@@ -298,6 +299,17 @@ Transport security and authentication:
298
299
DaneEnforcementEnabled=1 ; honor recipient DANE/TLSA records when sending
299
300
DnssecValidationEnabled=1 ; validate DNSSEC for DANE and SPF/DKIM/DMARC lookups
DkimOversignHeaders= ; DKIM oversigning (RFC 6376 5.4): header names listed in h= once more
308
+
; than the message carries them (empty = off; From is always included
309
+
; when the feature is on)
310
+
DKIMSignatureValiditySeconds=0 ; add an x= expiry to outgoing DKIM signatures (0 = no x= tag)
311
+
DKIMEnforceSignatureExpiry=1 ; refuse expired DKIM signatures when verifying
312
+
DKIMExpiryClockSkewSeconds=300 ; clock-drift allowance when enforcing x=
301
313
ArcSealingEnabled=0 ; add ARC seals when forwarding (uses the domain's DKIM key)
302
314
TlsRptFromAddress= ; sender for daily TLS-RPT reports (empty = disabled)
303
315
TlsRptOrganizationName=hMailServer
@@ -364,15 +376,17 @@ Administration and monitoring:
364
376
; SEARCH BODY/TEXT reads every message in the mailbox, so these bound what a single
365
377
; authenticated command can cost; raise them for mailboxes of several hundred thousand messages
366
378
ManageSieveServerPort=0 ; ManageSieve (RFC 5804) script-management service (0 = disabled, standard port 4190)
367
-
ManageSieveServerBindAddress=127.0.0.1 ; SASL PLAIN over plaintext; bind to localhost unless TLS-fronted
379
+
ManageSieveServerBindAddress=127.0.0.1 ; STARTTLS is offered when a TLS certificate is configured, and an
380
+
; IP range can require TLS before authentication; otherwise SASL PLAIN
381
+
; travels in the clear, so keep the bind on localhost
368
382
JsonLogging=0 ; write logs as JSON lines
369
383
</pre>
370
384
371
-
**Mail filtering (Sieve, RFC 5228).** Each account can have an active Sieve script that runs during local delivery, supporting `keep`, `fileinto`, `discard`and `redirect` with the core tests (`header`, `address`, `exists`, `size`, `allof`/`anyof`/`not`) and`:is`/`:contains`/`:matches` match types. Scripts are edited from the Control Panel account **Sieve** tab (or the COM `Account.SieveScript` property) and stored as files under the data directory. With `ManageSieveServerPort` set, mail clients can upload and manage multiple named scripts over **ManageSieve (RFC 5804)** (`CAPABILITY`, SASL `PLAIN``AUTHENTICATE`, `PUTSCRIPT`/`CHECKSCRIPT`, `LISTSCRIPTS`, `GETSCRIPT`, `SETACTIVE`, `DELETESCRIPT`).
385
+
**Mail filtering (Sieve, RFC 5228).** Each account can have an active Sieve script that runs during local delivery, supporting `keep`, `fileinto`, `discard`, `redirect`and `stop` with the core tests (`header`, `address`, `envelope`, `exists`, `size`, `allof`/`anyof`/`not`), the`:is`/`:contains`/`:matches` match types, and the extensions **copy** (RFC 3894), **relational** (RFC 5231), **subaddress** (RFC 5233), **imap4flags** (RFC 5232) and **vacation** (RFC 5230, including `:seconds`). Scripts are edited from the Control Panel account **Sieve** tab (or the COM `Account.SieveScript` property) and stored as files under the data directory. With `ManageSieveServerPort` set, mail clients can upload and manage multiple named scripts over **ManageSieve (RFC 5804)** (`CAPABILITY`, `STARTTLS`, SASL `PLAIN``AUTHENTICATE`, `PUTSCRIPT`/`CHECKSCRIPT`, `LISTSCRIPTS`, `GETSCRIPT`, `SETACTIVE`, `DELETESCRIPT`, `HAVESPACE`).
372
386
373
-
The metrics listener also serves Kubernetes-style health probes: `/livez` (process liveness), `/readyz` (200 when `StateRunning` and the database pool is connected, else 503 — and 503 while the server is draining/stopping) and `/healthz` (JSON: status, server state, database). `/metrics` exposes counters and gauges for processed/spam/virus messages, TLS handshakes (success/failure), authentication (success/failure), sessions per protocol, uptime, database up/pool, the SMTP delivery-queue depth, delivery outcomes (`hmailserver_messages_delivered_total`/`_deferred_total`/`_bounced_total`), the message-store consistency result (`hmailserver_messagestore_missing_files`), and aggregate per-command processing latency (`hmailserver_command_processing_seconds` summary).
387
+
The metrics listener also serves Kubernetes-style health probes: `/livez` (process liveness), `/readyz` (200 only when `StateRunning` and the database has answered a real round trip within the last 20 seconds, else 503 — and 503 while the server is draining/stopping) and `/healthz` (JSON: status, server state, database, uptime). `/metrics` exposes counters and gauges for processed/spam/virus messages, TLS handshakes (success/failure), authentication (success/failure), sessions per protocol, the start time (`hmailserver_start_time_seconds`), database connectivity (`hmailserver_database_connected`, proved by a round trip, plus pool gauges), the SMTP delivery-queue depth and oldest-message age, certificate expiry, work-queue depth, delivery outcomes (`hmailserver_messages_delivered_total`/`_deferred_total`/`_bounced_total`), the message-store consistency result (`hmailserver_messagestore_missing_files`), and per-command and per-query latency histograms (`hmailserver_command_processing_seconds`, `hmailserver_db_query_seconds`).
0 commit comments