Skip to content
hMailServer 6.3.8 is the current release, signed 2 November 2026. Windows and Linux, and an in-place upgrade from 5.3 or later, but for the original hMailServer's 5.7.Download 6.3.8 Documentation FAQ
  • 0 Votes
    1 Posts
    131 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) holds a credential confined to named domains - such as a domain administrator, or an API key issued for its domains - to a public address when it changes a domain's relay host, so it cannot point the server's delivery at the server's own network, and makes a domain's or an account's rule's Send using route and Bind to address actions the server administrator's to set; an account given the serveradmin role with a list of domains is, over COM as over REST, confined to those domains, so a script's account that edits the routes or the relayer needs the role with no list. See the 6.3.7 announcement. There are four ways this server can decide where an outbound message goes, and they are tried in a fixed order. Almost every "it went to the wrong server" question is answered by knowing that order, so this topic starts with it. It then covers the smart host and its failover, per-domain relays, routes and what they do to an unknown recipient, sending an OAuth2 token to a relay and what 6.3.4 changed about it, the COM change that is in effect only after Save(), and three things new in 6.3.4 (27 September 2026, schema 6104) that are not about where mail goes: remote domain policies (one paragraph here; the fuller treatment is Requiring TLS: MTA-STS, DANE and remote domain policies) and restricted delivery, which decide whether mail may go, and message recall, which withdraws unread copies already delivered on this server. The one thing to know first: a route is a statement about the destination and beats everything; a per-domain relay is a statement about the sender; the server-wide relayer is the fallback for mail nothing else has an opinion about. The order of decision When a message is ready to leave, its recipients are grouped by domain and the server asks: Did a global rule apply the Send using route action? Then every recipient goes to that route's target host and nothing else is consulted. Is there a route whose domain matches the recipient domain? Wildcards are allowed. The route's host, port, connection security and credentials are used. Does the sender's local domain have a relay host of its own? That domain's relay host, port, security and credentials. Port 0 means 25. Is a server-wide SMTP relayer configured? The relayer's host, port, security and credentials. Port 0 means 25. Otherwise, an ordinary MX lookup for the recipient domain, then A or AAAA for each MX host. Targets that turn out to be identical are merged: a message for four domains that all relay through one smart host opens one connection, not four. A bounce never picks up a per-domain relay, because a bounce has no envelope sender, so there is no sending domain to consult. Since 6.3.4 one question comes before all of these for inbound mail: an address or a hosted domain routed to a webhook has each message posted to an application, instead of being delivered (deliver off) or as well (deliver on). That is a different thing from the routes below, which send a domain's mail to another mail server; mail in to a webhook has the webhook routes. Sending through a smart host On Windows, configure it in the Control Panel at Mail flow & delivery → Delivery of e-mail → Relayer. The Control Panel and the COM API are Windows-only; over REST, which Linux has too, the relayer's settings are keys of PUT /api/v1/settings (smtp_relayer_password is write-only). Setting COM name Default When it is wrong SMTP relayer SMTPRelayer empty: MX lookup A host that does not resolve gives the bounce text "The host specified as SMTP relay server could not be found" with 4.3.5, a temporary failure, so the message is retried Port SMTPRelayerPort 25 Looks like a connection timeout in the SMTP log Connection security SMTPRelayerConnectionSecurity None Required against a host without STARTTLS defers every message Relayer requires authentication SMTPRelayerRequiresAuthentication off Off against a host that requires it gives 530/535 and the mail stays queued Number of delivery retries SMTPNoOfTries 4 0 means one attempt and then a bounce Minutes between retries SMTPMinutesBetweenTry 60 Outbound delivery security SMTPConnectionSecurity STARTTLS (optional) Applies to MX-resolved delivery, not to the relayer Several smart hosts. Separate the names with a vertical bar: smtp1.example.net|smtp2.example.net Each name is resolved in turn and every address it resolves to is appended, giving one ordered candidate list tried top to bottom. A single name with several A records already gives that failover. All hosts in the list share the port, security and credentials. A permanent 5xx rejection is a real delivery failure and the next host is not tried: trying it would deliver the message twice if the first server had in fact accepted it. Relaying everything through an ISP: Open Mail flow & delivery → Delivery of e-mail → Relayer. SMTP relayer: smtp.isp.example. Port: 587. Connection security: STARTTLS (required) - required, not optional, so a downgrade cannot send your credentials in clear. Tick Relayer requires authentication and fill in the user name and password. Save changes, send one message, and read Monitoring & troubleshooting → Live logs. The line that proves the relayer was used is: "SMTPDeliverer - Message 41: Relaying to host smtp.isp.example." If the message went to an MX host instead, the relayer field is empty, or a route or a per-domain relay took precedence. Per-domain relay. Accounts & domains → Domains → Properties → Relay sets where mail from that domain leaves through. It is for a server hosting several independent domains where each has bought its own delivery provider. An empty host means the domain has no opinion. Since 6.3.7 a credential confined to named domains - such as a domain administrator, or an API key issued for its domains - may set the host to a public address only, over REST and over COM; a host on the server's own network is the server administrator's to set, and one the server administrator set stays the domain administrator's to save, since only a change of the host is judged. Routes A route overrides delivery for a specific recipient domain. Mail flow & delivery → Routes: type the domain and target host, press Add, then Properties for everything else. Over REST the routes are /api/v1/routes. Field Default Note Domain name - Wildcards are matched, so *.partner.example works. The first matching route in name order wins; a wildcard broader than you meant silently captures other domains Target SMTP host, port -, 25 A name or a literal IP address; the host1\|host2 failover applies here too Number of delivery retries 3 Replaces SMTPNoOfTries. The delivery path applies it as tries − 1, so a route set to 3 schedules 2 further attempts Minutes between retries 10 Where one message has recipients on several routes, the largest count and the largest interval win Deliver to all addresses on Off: only the addresses listed are routed. Off with an incomplete list is the single most common route mistake Connection security None None, SSL/TLS, STARTTLS optional, STARTTLS required Treat sender domain as local off Turn it on only for a domain you actually control Treat recipient domain as local off On, unauthenticated senders may send to it from the Internet range What a route does to an unknown recipient: Route setting Domain has a catch-all? Address in the route's list? Result Deliver to all addresses - - Routed Specific addresses only no no 550 Recipient not in route list. Specific addresses only yes no Delivered to the catch-all, not routed Specific addresses only yes yes Routed; the catch-all does not intercept it Routes also make ETRN work: when a remote site issues ETRN <domain> and a route matches, the messages held for it are released for immediate delivery. OAuth2 to a relay A route or the SMTP relay whose host is on OutboundOAuth2Hosts (default smtp.office365.com) is sent AUTH XOAUTH2 with the server's own token: a client-credentials token fetched from OutboundOAuth2TokenUrl with OutboundOAuth2ClientId, OutboundOAuth2ClientSecret and OutboundOAuth2Scope (default https://outlook.office365.com/.default), or OutboundOAuth2FixedToken used verbatim instead of fetching. The default host list is inert on its own: with OutboundOAuth2TokenUrl and OutboundOAuth2FixedToken both empty, no bearer is set and the delivery proceeds with LOGIN and the password, with no log line. Since 6.3.4 the token is sent over TLS only. A route or the SMTP relay whose host is on OutboundOAuth2Hosts and whose connection security is none - or STARTTLS optional against a server that does not offer it - used to send AUTH XOAUTH2 with the token in the clear. Such a delivery now sends nothing in its place, fails with a temporary 450 4.7.0, and the error log says to set the security to TLS or STARTTLS required (HM5912); the message waits for the retries the route allows. Exchange Online offers STARTTLS on 587, so a route set up as Microsoft documents it is not affected. Also since 6.3.4, a relay that refuses the login no longer gets the token into the bounce, which now reads hMailServer sent: AUTH XOAUTH2 <Token removed>; the logs write AUTH XOAUTH2 ***. These are [Settings] keys, and since 6.3.4 the database is the settings store: a line edited in the file changes nothing, the stored value is used and named as HM5804, and the line is put back where the service can write the file (when a saved setting takes effect). Change them in the Control Panel, with PUT /api/v1/settings/ini/{name} or with hmctl ini set. A COM change is in effect only after Save() Since 6.3.4 a COM change to a route or an incoming relay is in effect only after the object's Save(), and a property set and never saved changes nothing; the list of objects this applies to does not name the server-wide SMTP relayer's settings. COM is Windows-only, so this concerns scripts on Windows: the COM API topic has the whole list, what an object read before the save shows, and error 7390. Remote domain policies, in one paragraph Since 6.3.4 (schema 6045). A route says where mail for a domain goes; a remote domain policy says what this server will and will not do when it talks to one domain it does not host, however that domain's server was found, and it applies to a routed delivery exactly as to an MX one. One record per name or pattern (bank.example, *.example, *), the most specific active one governing, holds the outbound TLS the domain demands (none, encrypted, verified or dane; a delivery that cannot meet it is deferred with 4.7.0, never sent in the clear), TLS required of its senders (530 5.7.0), a largest message (5.3.4), connection and rate ceilings (deferred with 4.4.5), whether automatic replies and forwards may go there, a recipient callout for a domain this server is a backup MX for, and gateway encryption to the partner's own key. It is server-administrator only, and GET /api/v1/remote-domains/effective?domain=mail.bank.example answers which record a delivery will use. Restricted delivery, since 6.3.4 Schema 6084. Accounts, or a whole domain, that may only exchange mail with named domains: a school whose pupils write to the school and to nobody else. It is not a route: it decides whether mail may go, not where. The list is the domain's: Domain.RestrictedDomains over COM, restricted_domains on GET/PUT /api/v1/domains/{domain}, the Restricted delivery tab of the Control Panel's domain dialog, the same group in the Deck's domain editor. At most 2,000 characters, names separated by commas or spaces, ASCII only, an internationalised name in its xn--... form. Written as Covers partner.example that domain, and nothing beneath it .example example and everything beneath it, anchored at a label boundary An account follows its domain unless it says otherwise (Account.RestrictedDelivery, restricted_delivery on the account routes): Value Word Meaning 0 domain Follows the domain. The default 1 restricted Restricted even when the domain names nothing; the account is then confined to its own domain 2 exempt May exchange mail with anyone An empty list, the default, means nothing runs, so an upgraded server delivers exactly as it did. Always allowed: the account's own domain under its own name or any alias domain, the domain's postmaster, the null sender MAIL FROM:<>, and mail this server writes itself. Other domains this server hosts are not allowed unless named. It is enforced in three places: Inbound, at RCPT TO. A restricted recipient whose envelope sender is not allowed is answered 550 5.7.1 <address>: this mailbox accepts mail from named domains only., per recipient, so one refused RCPT does not end the transaction. Every session is judged, authenticated or not. Outbound, at RCPT TO. A restricted submitting account is answered 550 5.7.1 <address>: this account may send to named domains only. for a recipient outside. The webmail's POST /api/v1/me/messages answers 400 with the sentence and queues nothing. At local delivery, for a recipient reached by expansion: a distribution list member, an alias target, a catch-all. The restricted recipient is dropped, and a non-delivery report goes to the sender only when the sender is local. A restricted account's forward - the account forward, an account rule's forward, a Sieve redirect - to a domain not allowed is not made, and the message is still delivered to the mailbox. Its out-of-office reply is not sent to a sender who is not allowed. Every refusal is a DEBUG log line; nothing goes to the error log, since a refusal is the feature working. Message recall within the server, since 6.3.4 Schema 6085. An unread message sent to accounts on this server can be withdrawn by its sender. Nothing does this across the Internet: a message that has left for another server is that server's. No recall notice reaches the recipients; the Sent copy's own mark is the record. What is recorded. A message accepted from one of the server's own accounts - an authenticated SMTP submission, credited to the account that signed in, or the webmail's send - gets one row in hm_recall per recipient that resolved to a local account. Nothing is recorded for: mail from outside, and mail this server writes itself; an unauthenticated submission from a local IP range in a local account's name, because a recall keyed on a claim would let anybody delete mail from other people's mailboxes; a message with a Message-ID longer than 255 characters, and a message that expires; the 201st and later recipient rows of one sender in a rolling day. The recall. In the webmail, Recall in the menu of a message in the Sent folder. For a program, POST /api/v1/me/recall with {"message_id": "<the Message-ID>", "sent_id": n}; it is refused with 403 on an app password. Each recipient is judged on their own: Outcome Meaning read Some live copy of theirs carries \Seen. Nothing was done: whoever has read it keeps it recalled No copy was read; every unread copy has been deleted the way the webmail deletes a message gone No copy of theirs is there any more: deleted by the reader, never delivered, or still in the queue failed The database did not answer; a later call decides them again The answer also counts elsewhere, the To and Cc addresses whose domain this server does not host, and says in sent_marked whether the Sent copy carries the keyword $Recalled. Read is \Seen and nothing more: a POP3 client, or an IMAP client that peeks, has the message and the recall cannot know. RecallDays (recall_days on PUT /api/v1/settings) is 14 by default, 1 to 365; a domain or an account may hold a shorter window, never a longer one. There is no COM property and no Control Panel page. What it does not do A route does not decide whether mail may go, and restricted delivery and a remote domain policy do not decide where. A per-policy SMTP port or credential is a route's job. Restricted delivery does not check the From header, does not filter what is already in the mailbox, and does not gate a journal rule's copy or an alias or list in the domain that reaches outside. Recall does not reach another server, does not touch the queue, and does not recall a message submitted without authentication. A 5xx from one smart host is not retried on the next. Where to read more Wiki: Routes and Relays, Settings Reference for the OutboundOAuth2* keys, APIs Reference, Diagnosing Stalled Mail Documents at the 6.3.4 tag: RemoteDomains.md, RestrictedDelivery.md, MessageRecall.md, Upgrading.md The public documentation: chapter 14, Routes and relays On this forum: Being trusted by the servers you send to, the 6.3.4 release announcement, requiring TLS and remote domain policies, and diagnosing stalled mail for a message that will not leave the queue
  • 0 Votes
    1 Posts
    139 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes how the listener judges some requests. A state-changing request that carries a credential, HTTP Basic included, is refused when the browser reports it as cross-site (Sec-Fetch-Site, or an Origin that is not this server's); scripts (the curl examples below among them), hmctl and the PowerShell module send no such header and are unaffected. A credential confined to named domains (a domain administrator, a key issued for named domains, or serveradmin given with a list of domains) is refused a domain's rename, a domain alias, a custom role's creation, change or delete, the bind_to_address, send_using_route and script_function actions on a domain's or an account's rule, a relay host changed to an address that is not public, and GET /api/v1/metrics/history; and a role is given or taken away only by a credential that holds manage_roles in the account's domain and takes in what the account holds now and afterwards, so such a credential cannot take a role away from an account whose admin level is the server administrator's. The OpenAPI document describes each path once, with every method it answers. In the Control Panel, API & monitoring is Monitoring & alerts and REST API keys is under Sign-in & access; Ctrl+K takes every old title to its page. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) reads a JSON body in time and memory that follow its bytes (work item 110), and answers 400, saying why, before a route reads a member, to a body that is not one JSON object, names a member twice, holds an escaped U+0000 or passes 100,000 values or 32 levels, and to a member whose absence is the wider setting written as another type than its route reads, such as domains, allowed_from, role or expires on POST /api/v1/apikeys as anything but a string or null: correct a client of your own that sends such a body. An account's failed sign-in from loopback, as through a reverse proxy on the same machine, is counted against the name alone, AccountLockoutThreshold (off by default) being the limit, and never towards a ban of the address (work item 107); the listener's own accounting spares all of 127.0.0.0/8. See the 6.3.8 announcement. The REST API is the alternative to COM for automating hMailServer, and the only administration interface that works from a machine that is not running Windows. This guide covers how to turn the listener on, the three credentials it takes and the roles that narrow them, how one-time codes behave since 6.3.4, the OpenAPI document the server emits, and a set of curl examples that use only routes the project's own documents name. The one thing to know first: the listener is off as shipped (RestApiPort=0), and since 6.3.4 RestApiPort is a setting stored in the database. Editing the [Settings] section of hMailServer.ini does not turn the API on once the server has stored the key. Turning the listener on Four settings shape the listener. Setting Shipped value What it is RestApiPort 0 The port. 0 means there is no listener. RestApiBindAddress 127.0.0.1 The address it binds. TLS is required unless this is 127.0.0.1, localhost or ::1. RestApiCertificateFile empty The certificate, in PEM. Left empty, the listener falls back to the ACME certificate. RestApiPrivateKeyFile empty Its private key, in PEM. Since 6.3.4 the database is the settings store (schema 6042) and the [Settings] section of hMailServer.ini is its copy. A key the server has never stored is taken in from the file at the next start, so on a fresh installation an edit made before the first start still works; once the key is stored, the database's value is the one used. Settings: what is not stock, when a save takes effect covers the store itself, why an edited line is put back (HM5804) and [SettingsOverride]. Where the stored value is changed depends on the platform. Platform How RestApiPort is set Windows The Control Panel page Monitoring & troubleshooting → API & monitoring, or Settings.SetIniSetting over COM. The Control Panel and the COM API are Windows-only. Linux hmailserver --set-setting on the server itself, then a restart of the service. Either, once the API answers PUT /api/v1/settings/ini/{name} with the administrator password. sudo hmailserver --set-setting RestApiPort=8443 sudo systemctl restart hmailserver --set-setting checks what it is given: a port must be a number from 0 to 65535 and RestApiBindAddress an IP address or localhost. On Linux this matters most, because there the REST API is the administration interface. The packaged /etc/hmailserver/hMailServer.ini ships RestApiPort=0 and RestApiBindAddress=127.0.0.1 under [Settings], so the first start stores both, and changing those lines afterwards turns nothing on. The one part of the file that still decides a stored key is a [SettingsOverride] section, applied over the store at every start and reported as HM5807 each time; the wiki gives it as the route where neither the Control Panel nor COM is available. The new value waits for a reinitialise. Since 6.3.4 most settings apply when they are saved, but RestApiPort, RestApiBindAddress, RestApiCertificateFile and RestApiPrivateKeyFile are among the 28 keys marked reinitialize, because the listener binds and its TLS context is built when the servers start. They take effect at Apply now in the Control Panel (Windows) or the Control Deck, at Application.Reinitialize over COM (Windows), at POST /api/v1/server/reinitialize, or at a restart of the service. The listener can refuse to start, and each refusal is written once to the application log. What is wrong Log line Fix The administrator password is empty RestApi: Refusing to start - the administrator password is not set. Set an administrator password The bind address is not loopback and there is no certificate RestApi: Refusing to start - TLS certificate is required unless bound to 127.0.0.1 or ::1. Set RestApiCertificateFile and RestApiPrivateKeyFile. Set both files, enable ACME, or bind loopback The certificate cannot be loaded, or its key does not match RestApi: Refusing to start - the shared TLS configuration could not be applied to the configured certificate., preceded by HM5113 naming the certificate or key file Correct the PEM paths and permissions Nothing RestApi: Listening on 127.0.0.1:8443 (http, loopback only). or ... (https). None needed The Listening on line is the only proof that the listener is up. The loopback exemption is a string comparison against 127.0.0.1, localhost and ::1; 127.0.0.2 and ::ffff:127.0.0.1 do not qualify. The TLS configuration is the one the mail protocols use (cipher list, protocol switches, TlsKeyExchangeGroups), with a TLS 1.2 minimum applied on top, so opening an older protocol for a mail client does not open it for the management API. The three credentials Credential Header Reaches Administrator password Authorization: Basic with the user name Administrator (case-insensitive) Every administrative route. It is never read-only and never confined to domains. API key Authorization: Bearer hmapi_<64 hex> What its scope, role and domains allow, less the routes listed below. An account's own Authorization: Basic with the address and the mailbox password or an app password, or the hmailsession cookie /api/v1/me and /api/v1/session; since 6.3.4 also the administrative routes a role it holds allows. The two Basic forms are told apart before either password is tried: any user name but administrator is treated as a mailbox address and goes down the path an IMAP logon takes, with the same lockout and auto-ban. An account is never tested against the administrator password. API keys are the credential for scripts. A key is read-only unless it is made with "scope":"full", expires after 90 days unless the request names an expires (there is no "never expires"), and may be narrowed to named domains and to a source address, range or CIDR. Only the SHA-256 of a key is stored, in hMailServerApiKeys.ini beside hMailServer.ini, and that file is read again at every authentication attempt, so removing a key's section revokes it on the next request. # Administrator password only: a key can never make or revoke keys curl -u Administrator:your-admin-password \ -H "Content-Type: application/json" \ -d '{"label":"backup monitor","scope":"readonly","expires":"2027-01-01 00:00:00"}' \ http://127.0.0.1:8443/api/v1/apikeys {"id":"3f9a1c4b5d6e7f80","label":"backup monitor","scope":"readonly","domains":"", "expires":"2027-01-01 00:00:00","allowed_from":"","key":"hmapi_7c1f...c92a"} key is the clear text. It is returned once and cannot be recovered, so store it then. GET /api/v1/apikeys lists the keys with an expired flag and never a hash; DELETE /api/v1/apikeys/<id> revokes one. The create request takes label (required, at most 64 characters), scope, domains, expires and allowed_from, and since 6.3.4 role and the send API's send_from, send_per_minute and send_per_day. What no API key reaches. Whatever its scope or role, a key is answered 401 (not 403, so that it learns nothing) on the key store /api/v1/apikeys and on /api/v1/settings/ini, and since 6.3.4 also on /api/v1/audit, /api/v1/alerts, /api/v1/webhooks, the compromised-account review, /api/v1/settings/changed, /api/v1/server/settings/pending, an account's export, the address erasure and the second factor's enrolment. In the other direction, since 6.3.4 a key is the only credential POST /api/v1/send takes (a key naming a sender) and the only one /scim/v2 takes (a key whose role holds provision). Roles, since 6.3.4 A role is a named set of permissions, and an assignment gives an account a role in named domains (schema 6047). Nothing changes on upgrade: an account with no assignment holds what its admin level has always given it, and a key without a role is what it always was: Scope=full is serveradmin and Scope=readonly is auditor. A key is given a role with role on POST /api/v1/apikeys; an account is given one at /api/v1/role-assignments by a credential holding manage_roles. What a refusal says. A route a role may not call answers 403 naming the role and the permission, for example the role 'helpdesk' does not hold the permission 'manage_settings'. An address in a domain the role does not reach answers the role 'helpdesk' does not reach that domain whether or not the account exists. A request authenticated with an app password never carries the account's role. Administrator roles end to end has the built-in roles and their permissions, giving a role on each surface, custom roles, and the two stores, /api/v1/apikeys and /api/v1/settings/ini, that answer to the administrator password alone, not even to an account holding serveradmin. One-time codes When a second factor is enrolled on the administrator credential, the code travels in an extra header: curl -u Administrator:your-admin-password \ -H "X-hMailServer-OTP: 123456" \ http://127.0.0.1:8443/api/v1/status Without it the answer is the one 401 on this listener that says what is missing: HTTP/1.1 401 Unauthorized WWW-Authenticate: Basic realm="hMailServer" X-hMailServer-OTP: required Cache-Control: no-store {"error":"authentication failed","second_factor":"required"} A missing code is not counted towards the auto-ban; a wrong code is. Since 6.3.4 a one-time code is taken once, over COM and over REST, for the administrator and for an account alike. A code the server has accepted is refused if it comes again. The consequence for automation is direct: a script that sent the administrator's password and a code with every request now has only its first request in each half minute accepted. The others are refused with 401 and are not counted as wrong codes, so such a script does not lock the administrator out. The changelog names the two remedies: sign in once with POST /api/v1/session and use the hmailsession cookie from there, or use an API key. # Sign in once; the password and the code travel on this request alone curl -c cookies.txt -u Administrator:your-admin-password \ -H "X-hMailServer-OTP: 123456" \ -X POST http://127.0.0.1:8443/api/v1/session # Reads carry the cookie curl -b cookies.txt http://127.0.0.1:8443/api/v1/status # Anything that changes something also carries the CSRF header curl -b cookies.txt -X POST -H "X-Requested-With: hMailServer" \ http://127.0.0.1:8443/api/v1/queue/81234/retry # Sign out curl -b cookies.txt -X DELETE -H "X-Requested-With: hMailServer" \ http://127.0.0.1:8443/api/v1/session The session rules are enforced by the server: 30 minutes idle and 12 hours from sign-in until a session policy sets others (since 6.3.4, session_idle_seconds and session_lifetime_seconds), at most 1000 sessions, and every session dropped when the listener stops. A cookie-authenticated request that is not GET or HEAD without X-Requested-With: hMailServer is refused with 403. Somebody who is asked for a code twice within half a minute, to sign in to the Control Deck and then to register a passkey for example, waits for the authenticator's next code. Two further changes in 6.3.4 belong here. A wrong code is counted against a budget of five in fifteen minutes, after which no code is taken for fifteen minutes; on the administrator's Basic sign-in the answer is then 429 with code second_factor_locked and Retry-After. And for an account, one of its one-time recovery codes, in X-hMailServer-Recovery-Code, is taken wherever the authenticator's code is asked for. The administrator signs in at POST /api/v1/session with HTTP Basic, and with the code in X-hMailServer-OTP when a second factor is enrolled: AccountRecovery.md names the header for this route, the 6.3.4 changelog gives the session as the remedy above, and the Control Deck signs in that way. The wiki's credentials table, which lists /api/v1/session among the routes the administrator password does not reach, is out of date on this. How a request is judged The server tries Bearer, then Basic, then the session cookie. It then applies the CSRF check for cookie-authenticated writes (and, since 6.3.7, refuses any state-changing request that carries a credential when the browser reports it as cross-site, by Sec-Fetch-Site or an Origin that is not this server's) and the rate budget of 200 requests in a fixed 10-second window per credential, turns the method and path into a route kind, and decides on that kind in one place before any handler runs. Read-only is decided by route kind, not by HTTP method. A read-only key on a mutating route is answered 403 with this api key is read-only. A key with a domain list is refused server-wide resources outright: the queue, the quarantine, IP ranges, certificates, rules, logs, the backup and the settings among them. On a route that names a domain or an address outside its list the answer is 403 with this api key is not permitted for that domain. Routing is exact. Method and path must match after the query string is removed, a trailing slash is a different path, and an id in a queue, quarantine, IP-range, archive or /api/v1/me/... path must be 1 to 18 decimal digits and greater than zero, or the answer is 404. The OpenAPI document GET /api/v1/openapi.json answers the API's description to the administrator password or to any key, a read-only one included. The wiki's advice is that this document is the count to trust: in 6.3.4 it names 441 paths and 621 path-and-method operations, 413 and 585 of them under /api/, against 146 and 219 in 6.3.3. The rest are /scim/v2, the sign-in, reset and reading pages, the list manager's pages and the static catalogues and scripts. The Control Deck draws its settings groups from this document, and since 6.3.4 the document gives the ini-backed rows of the settings groups an x-applies mark saying when a save takes effect. The APIs Reference describes it as a static OpenAPI 3.0.3 document and records where its text is not accurate: the summary of /api/v1/status mentions uptime, and the handler returns no uptime field; the text for /api/v1/settings/ini still describes the file as the store, where since 6.3.4 the value goes to the database first. Settings saved and not yet running Since 6.3.4 a key marked reinitialize or restart whose stored value is not the value the server is running on is pending. The list is recomputed at every request. Route Answer GET /api/v1/server/settings/pending [{name, stored, running, applies, secret}]; a secret's two values are (set) or empty GET /api/v1/settings/ini The keys as {name, value, secret}, an applies map and a pending array of names GET, PUT, DELETE /api/v1/settings/ini/{name} One key, with applies and pending POST /api/v1/server/reinitialize 202 before it happens; {"reinitializing": true, "drained": true} when it ended a drain All but the last answer to the administrator password alone. A DELETE is now the only way to return a stored key to its default, because deleting a line from the file no longer does. A reinitialise restarts the REST listener with the other services, so it ends a Control Deck session; poll /api/v1/status until it answers again. Worked examples These use the loopback, plain-HTTP shape that RestApiPort=8443 and RestApiBindAddress=127.0.0.1 give; put the port that was stored in place of 8443. With a certificate configured, change http:// to https://. They present the administrator password on every request, which works while no second factor is enrolled; with one enrolled, use the session above or an API key. # Status: state is 0 Unknown, 1 Stopped, 2 Starting, 3 Running, 4 Stopping curl -u Administrator:pw http://127.0.0.1:8443/api/v1/status # Domains and accounts curl -u Administrator:pw http://127.0.0.1:8443/api/v1/domains curl -u Administrator:pw http://127.0.0.1:8443/api/v1/domains/example.com/accounts curl -u Administrator:pw -H "Content-Type: application/json" \ -d '{"address":"[email protected]","password":"S0me-Long-Passphrase"}' \ http://127.0.0.1:8443/api/v1/domains/example.com/accounts curl -u Administrator:pw -X DELETE \ http://127.0.0.1:8443/api/v1/accounts/[email protected] # The delivery queue curl -u Administrator:pw http://127.0.0.1:8443/api/v1/queue curl -u Administrator:pw -X POST http://127.0.0.1:8443/api/v1/queue/81234/retry curl -u Administrator:pw -X DELETE http://127.0.0.1:8443/api/v1/queue/81234 # Logs and the backup curl -u Administrator:pw "http://127.0.0.1:8443/api/v1/logs/hmailserver_2026-09-08.log?lines=50" curl -u Administrator:pw -X POST http://127.0.0.1:8443/api/v1/backup curl -u Administrator:pw http://127.0.0.1:8443/api/v1/backup # With a key curl -H "Authorization: Bearer hmapi_7c1f...c92a" http://127.0.0.1:8443/api/v1/status # Store another port: it waits for a reinitialise, and the pending list shows both values curl -u Administrator:pw -X PUT -H "Content-Type: application/json" \ -d '{"value":"8444"}' http://127.0.0.1:8443/api/v1/settings/ini/RestApiPort curl -u Administrator:pw http://127.0.0.1:8443/api/v1/server/settings/pending # Put the running port back; a value put back before anything applied it is no longer pending curl -u Administrator:pw -X PUT -H "Content-Type: application/json" \ -d '{"value":"8443"}' http://127.0.0.1:8443/api/v1/settings/ini/RestApiPort Creating an account answers {"address":"[email protected]","created":true}, or 400 for a missing field, 404 {"error":"domain not found"} or 409 {"error":"account already exists"}. In 6.3.4 the create route takes address and password and the other columns of the CSV import as well (first_name, last_name, active, max_size_mb and admin_level), and refuses a field it does not know by name (CommandLine.md). The older statement in the wiki and the APIs Reference, that only address and password are read, no longer holds. For a log, lines defaults to 200 and is capped at 2000. Limits and status codes Limit Value What the client sees Request size 64 KB, head and body 413, connection closed Large requests 16 MB and 300 s on POST /api/v1/me/messages and POST /api/v1/me/drafts; a few other routes are granted more than 64 KB as well, among them PUT /api/v1/me/settings and the SAML ACS (256 KB) 413 above that Request deadline 30 s, the TLS handshake included Connection closed with no response Connection lifetime 300 s, and 1000 requests Connection closed Concurrent connections 64 Connection closed Rate 200 requests per 10 s per credential 429 with Retry-After: 10 Auto-ban Shared with SMTP, IMAP and POP3; loopback exempt The address is refused at accept for 5 minutes A 401 is deliberately the same for no credential, a wrong password and an unknown, expired or out-of-network key; the application log says which. A request with Transfer-Encoding is answered 411, because the server does not de-chunk. An API response is application/json with Cache-Control: no-store, with exceptions: the static pages and the attachment download serve their own media types, a report asked for with format=csv is text/csv, and every answer under /scim/ is application/scim+json. Where to read more The wiki: The REST API and, for a row per route, APIs Reference section 4. The operator documents: Roles.md and SettingsApply.md. The public documentation: chapter 24, The REST API, with API keys and the OpenAPI document. On this forum: The Control Deck, which is the browser front end for this API; the Linux guide; the 6.3.4 announcement; hmctl and the PowerShell module; the send API and webhooks; and SCIM provisioning and single sign-on.
  • 0 Votes
    1 Posts
    123 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) fixes DANE enforcement deferring mail to a DNSSEC-signed domain whose MX answer came compressed, live.com among them (work item 80), and treats a DNS lookup that fails as a failure rather than a name with no addresses: a delivery whose mail servers' lookup failed tells the sender that the DNS query failed (4.4.4) and retries, a DNSSEC validation that fails says where and why in the debug log, and the application log's lines for a skipped host end with the reason in brackets. The ACME HTTP-01 challenge responder, which served one connection at a time so that idle clients could make a certificate's issuance or renewal fail, now serves up to 64 connections at once (work item 97). See the 6.3.7 announcement. In hMailServer 6.3.8 (2 November 2026) the web services listener refuses an address an auto-ban or an IP range bans (work item 105), so an IP range of your own that allows some addresses none of SMTP, POP3 and IMAP now closes that listener to them, and with it the MTA-STS policy this topic publishes and ACME's http-01 validation where that listener answers it: check that the sending MTAs and the certificate authority do not come from such a range. See the 6.3.8 announcement. This topic is about mail travelling encrypted between servers, in both directions, and about being able to show that it did: honouring the MTA-STS and DANE policies other domains publish, publishing your own, adding a requirement of your own for a partner that publishes nothing, and sending the TLS-RPT and DMARC reports that tell other operators what became of their mail. It assumes a certificate is already installed and the listeners already use it; that is Let's Encrypt certificates are automatic, and the basics of SPF, DKIM and DMARC are in Being trusted by the servers you send to. The one thing to know first: the three outbound policies - MtaStsEnabled, DaneEnforcementEnabled and DnssecValidationEnabled - are on by default, and every outbound path described here that cannot get the encryption it requires defers the message with 4.7.0. Where encryption is required, nothing falls back to sending in the clear. What is on already, and what is yours to do Mechanism Which mail State as shipped What you do Honouring other domains' MTA-STS policies mail you send on, MtaStsEnabled=1 nothing Honouring other domains' DANE records mail you send on, DaneEnforcementEnabled=1 and DnssecValidationEnabled=1 nothing Publishing your own MTA-STS policy mail you receive MtaStsHostingEnabled=1, but inert until WebServicesHttpsPort is set set the port, have a certificate for mta-sts.<domain>, publish the _mta-sts TXT record Publishing your own TLSA record mail you receive nothing to switch on publish the record the ACME log prints, in a DNSSEC-signed zone Remote domain policies (since 6.3.4) both directions none exist create one per partner TLS-RPT and DMARC aggregate reports you send reports to other operators inert until a sender address is set set TlsRptFromAddress and DmarcRptFromAddress The three outbound policies, the authentication-results headers and the two reporters are on the Control Panel's TLS & certificates → Transport security page (Windows-only); on either platform, PUT /api/v1/settings/ini/{name} and hmctl ini set write them. Of the keys in this topic, WebServicesHttpsPort and AcmeEnabled wait for a reinitialise (Apply now) and the rest apply when saved. Do not edit hMailServer.ini for them: since 6.3.4 the database holds these keys, and a line edited in the file is ignored and named in the error log as HM5804. Settings: when a save takes effect explains the store and [SettingsOverride]. Honouring another domain's MTA-STS policy It applies to an MX delivery, not to a fixed relay. For each message due for a domain, the server looks up the TXT record at _mta-sts under that domain. With no record, "no policy" is cached for 30 minutes and the message is delivered as usual. With a record, the policy is fetched over HTTPS from mta-sts.<domain> at /.well-known/mta-sts.txt, and its mode decides: mode What the server does none Delivers as usual testing Writes a debug log line only; nothing is enforced enforce Drops every MX host the policy does not name. If none is left, the message is deferred with 4.7.0. Otherwise TLS with certificate verification is required to the hosts that remain How long a policy is believed. A policy is cached until its own max_age and revalidated by re-reading the DNS record's policy id at most once an hour. A DNS failure or a failed fetch during revalidation keeps the cached policy (RFC 8461 §5.1). The mx: patterns match either exactly or with a leading *. that matches exactly one label. What you see in the log. SMTPDeliverer - Message 4711: MTA-STS policy mode 'enforce' active for example.com. TLS with certificate validation is required. SMTPDeliverer - Message 4711: Skipping MX host mx9.example.com - not permitted by the MTA-STS policy of example.com. SMTPDeliverer - Message 4711: Delivery to example.com deferred. No MX host matches the domain's MTA-STS policy. Honouring DANE DANE applies to an MX delivery when DaneEnforcementEnabled=1. The MX lookup for the recipient domain is DNSSEC-validated, and the chain status of the MX RRset decides what happens next. Step Status What happens The MX RRset insecure Delivered as usual The MX RRset bogus The host is skipped entirely The MX RRset secure A TLSA lookup is made per host, at _25._tcp.<mx-host> The TLSA lookup bogus The host is skipped The TLSA lookup no records Delivered as usual The TLSA lookup validated TLS is required and the certificate is pinned to the TLSA records If no host could be attempted, the message is deferred with 4.7.0. Three points that matter when you read a log: The MX RRset must be DNSSEC-validated too, not only the TLSA record (RFC 7672 §2.2). A TLSA record found under a host name learned from a forged MX answer proves nothing, so the server validates the MX RRset and checks that the host it is about to contact is one of the names that RRset published. Only DANE-EE, usage 3, is honoured. A record of any other usage is skipped, issuer certificates are ignored, and the leaf is matched against selector 0 (whole certificate) or 1 (SubjectPublicKeyInfo) with matching type 0 (exact), 1 (SHA-256) or 2 (SHA-512). It fails open except for one value. Bogus - a published DNSSEC chain that does not validate - is the only status that can stop a delivery. Every other outcome lands on the ordinary delivery path. An empty DnssecTrustAnchors means the built-in IANA root anchors. Setting DnssecValidationEnabled=0 reverts to opportunistic, unvalidated TLSA use. With enforcement on, a recipient whose DNSSEC chain is bogus will not receive your mail rather than receive it unencrypted. SMTPDeliverer - Message 4712: DNSSEC-validated DANE-EE TLSA records found for mx1.example.com. TLS with TLSA certificate matching is required. SMTPDeliverer - Message 4712: TLSA records for mx1.example.com failed DNSSEC validation. Skipping this host (RFC 7672). SMTPDeliverer - Message 4712: Delivery to example.com deferred. TLSA records of all MX hosts failed DNSSEC validation. And in the TCP/IP log, per handshake: DANE: Session 812: Certificate matched DANE-EE TLSA record (selector 1, matching type 1). DANE: Session 813: Certificate did not match any DANE-EE TLSA record. Failing TLS handshake. Both deferral paths use 4.7.0, "other or undefined security status" (RFC 3463). Publishing your own MTA-STS policy MtaStsHostingEnabled defaults to 1. The built-in web services server serves /.well-known/mta-sts.txt for any hosted, active domain when the request's Host is mta-sts.<domain>, and generates the body from your live MX records unless MtaStsPolicyMx overrides them: version: STSv1 mode: enforce mx: mail.yourcompany.com max_age: 604800 Setting Default Notes MtaStsHostingEnabled 1 On, but inert until the HTTPS listener exists MtaStsPolicyMode enforce enforce, testing or none; anything else is read as enforce MtaStsPolicyMaxAge 604800 (7 days) Clamped to 86400 … 31557600 MtaStsPolicyMx empty Comma-separated override; empty derives the list from live MX records WebServicesHttpsPort 0 Must be set. RFC 8461 §3.3 requires the policy to be fetched over HTTPS WebServicesHttpsPort is what makes it live. While it is 0 the server logs that the policy cannot be served and that sending servers will treat the domain as having no policy. Set WebServicesHttpsPort. Add mta-sts.yourcompany.com to AcmeDomains, so that the HTTPS listener has a certificate for the name it will be asked for. Every name in AcmeDomains becomes a SAN on one certificate, and each must already resolve to this server (Let's Encrypt certificates are automatic). Decide MtaStsPolicyMode. The default is enforce, under which a sender that cannot verify the certificate on your MX host defers its mail to you. The wiki's DNS page says to start at testing, which, once you publish a _smtp._tls TXT record (v=TLSRPTv1; rua=mailto:...), gives you TLS-RPT reports without losing mail while you find out whether every sender can reach you over TLS; then move to enforce. Publish _mta-sts.yourcompany.com. IN TXT "v=STSv1; id=20260908000000Z". Bump the id whenever you change the policy. DANE for your own server, with ACME Every successful ACME issuance writes the exact record to publish into the log: ACME: DANE TLSA record for this certificate: _25._tcp.<mx-host>. IN TLSA 3 1 1 3b1f… That is 3 1 1: DANE-EE, SubjectPublicKeyInfo, SHA-256. The digest is over the key, so the record stays valid across a renewal only when the key is kept, which is what AcmeReuseKey (default 1) does. When it is on and a privkey.pem already exists, the log says ACME: Reusing the existing certificate key (keeps published TLSA records valid). Otherwise a fresh 2048-bit RSA key is generated. Leave it on if you publish TLSA records. The wiki's DNS page adds three things. Your zone must be DNSSEC-signed for the record to mean anything. Publish the record for every host named in your MX records. And re-publish before you rotate a certificate, never after. The Control Panel's DNS records page does not generate the TLSA value. A requirement of your own: remote domain policies Since 6.3.4 (schema 6045). In 6.3.3 the only ways to insist on TLS to a destination are the server-wide delivery security setting, a route's own connection security, and the MTA-STS or DANE policy the domain publishes itself. A remote domain policy is you saying the same for a domain that publishes nothing: a partner, a bank. Where it lives. Policies are rows of hm_remotedomainpolicies, take effect for the next message without a restart, are part of the configuration backup, and have nothing in hMailServer.ini. Four surfaces write the same records: Where How Control Panel (Windows) Mail flow & delivery → Remote domains, beside Routes Control Deck Remote domains in the navigation REST API GET/POST /api/v1/remote-domains, PUT/DELETE /api/v1/remote-domains/{id}, GET /api/v1/remote-domains/effective?domain=... COM (Windows) Application.Settings.RemoteDomainPolicies All are server-administrator only. An API key restricted to named domains is refused, because a policy names somebody else's domain and decides this server's posture. A policy is not a route. A route says where mail for a domain goes and replaces the MX lookup. A policy says what this server will and will not do when it gets there, however the server was found. A domain may have both, and the policy applies to a routed delivery exactly as it applies to an MX one. Which policy applies. A policy names a domain (bank.example) or a pattern (*.example, *), and the most specific active one governs: an exact name beats any pattern, however long the pattern; between two patterns, the one with more literal characters wins; an inactive policy is skipped, and the next most specific active one governs in its place. Switching off the entry for one domain beneath a * that requires TLS removes an exception; it does not exempt that domain. GET /api/v1/remote-domains/effective?domain=mail.bank.example (and PolicyForDomain over COM) answers which record a delivery will use, before a message has to find out. When one connection carries recipients of several domains, the strictest combination applies: the highest TLS requirement, and the smallest of each non-zero limit. Outbound TLS. Value What delivery to the domain requires none (default) Nothing beyond what the server does anyway: the delivery security setting, a route's security, and MTA-STS or DANE if the domain publishes them encrypted STARTTLS must be offered and the handshake must succeed. The certificate is not judged - the level for a partner with a self-signed or internal certificate verified Encrypted, and the certificate must chain to a trusted root and name the host connected to - what MTA-STS enforce asks dane Encrypted, and the certificate must match a DNSSEC-validated TLSA record. The TLSA lookup is made even with DANE enforcement off and even for a routed delivery; a host with no validated record is skipped, so with DnssecValidationEnabled=0 every delivery under the policy is deferred It can only raise the requirement. The policy, MTA-STS enforce and a usable TLSA record combine as the strictest of the three, never the newest. A domain that publishes MTA-STS enforce and is named here at encrypted still gets certificate verification. No value here weakens what a domain published for itself. A delivery it cannot satisfy is deferred, never sent in the clear. The recipient's status is 4.7.0, the message stays queued and is retried on the usual schedule, and the log line - and the bounce, if the retries run out - names the rule: Delivery deferred: the remote domain policy for bank.example requires TLS with a certificate that verifies to this destination, and it could not be established. The message has NOT been sent in the clear. It is a deferral rather than a bounce on purpose: the far end may fix its STARTTLS or its certificate before the queue gives up, which is how RFC 8461 §5 reasons about MTA-STS. Inbound. With Require TLS on mail from this domain set, an unencrypted session whose envelope sender is at the domain is answered 530 5.7.0 Must issue STARTTLS first at MAIL FROM. It is not applied to an authenticated submission, nor to a connection from a configured incoming relay: behind a front-end filter the hop the policy is about happened before that machine, and TLS has to be required there. The envelope sender is whatever the connecting server claims, so this keeps honest partners encrypted and is not an anti-spoofing control. The rest of the record. The same row carries the largest message this server will send to the domain (beyond it no connection is opened and the message is bounced at once with 5.3.4), the simultaneous connections and the messages a minute (beyond either, deferred with 4.4.5 for one to five minutes), whether automatic replies and forwards may go there, a backup-MX recipient callout, and since 6.3.4 (schema 6094) Encrypt mail to this domain at this server. The ceilings are in Outbound shaping and IP warm-up, gateway encryption in End-to-end encryption. Telling other operators what happened: the reports you send Two aggregate reporters, both scheduled hourly and both inert until a sender address is set. They flush their statistics either way, so switching one on does not start with an empty history. TLS-RPT (RFC 8460) DMARC aggregate (RFC 7489) Address setting TlsRptFromAddress DmarcRptFromAddress Organisation name TlsRptOrganizationName, default hMailServer DmarcRptOrganizationName, default hMailServer Recorded by ExternalDelivery, per delivery attempt, classified tlsa, sts or no-policy-found SpamTestDMARC, for every message evaluated against a published policy, passes included Schema - DmarcRptSchemaVersion, default 1 = RFC 7489 Appendix C; 2 = RFC 9990 DMARC reports are sent to an address outside the policy domain only when that address's own DNS says it wants them, so a domain cannot use its DMARC record to aim this server's reports at somebody else. Recording what this server concluded. Three related settings, the first two off by default: Setting Default What it writes AuthenticationResultsEnabled off One Authentication-Results header per accepted inbound message (RFC 8601), with the SPF, DKIM and DMARC verdicts this server reached. Only checks that ran are reported ReceivedSpfHeaderEnabled off A Received-SPF header (RFC 7208 §9.1), written only when the SPF check ran AuthenticationResultsIdentity empty = this computer's name The authserv-id, always lower-cased An arriving message that carries an Authentication-Results header claiming this server's identity has that header removed before the server writes its own; a header naming any other identity is left untouched. The reports other servers send about your domains go to the rua= address in your _dmarc and _smtp._tls records. Since 6.3.4 a domain's report mailbox reads them: see DKIM rotation, DMARC and TLS reports. A checklist AcmeEnabled=1, AcmeDomains naming mail., mta-sts. and autoconfig., and port 80 reachable (the ACME topic). Check TLS & certificates → Transport encryption overview, which says whether anything still carries a password in the clear. Leave MtaStsEnabled, DaneEnforcementEnabled and DnssecValidationEnabled at 1. Set WebServicesHttpsPort, start MtaStsPolicyMode at testing, and publish _mta-sts and _smtp._tls in DNS. Publish the TLSA record the ACME log prints, if your zone is signed. Since 6.3.4, name the partners that must never get mail in the clear as remote domain policies at encrypted or verified. Turn on AuthenticationResultsEnabled, and set TlsRptFromAddress and DmarcRptFromAddress. Diary the first renewal: it falls two-thirds of the way through the certificate's life, and the log line to look for is ACME: Certificate issued successfully. What this topic does not cover The connection to the database ([Database] TlsMode and TlsCaFile, since 6.3.4): Choosing a database and encrypting the link. Protocol versions, cipher suites, session resumption and client certificates: TLS on the listeners. Encrypting the message rather than the connection. TLS ends at the next server; End-to-end encryption is about what happens after that. Where to read more Wiki: Encryption and Certificates, §9.6 to §9.10; DNS for Your Domain, for the records themselves; Routes and Relays, §14.2a, for the whole remote domain record. Operator document at the v6.3.4 tag: RemoteDomains.md. Public documentation: 9.4 Outbound transport security and 8.7 MTA-STS. On this forum: Let's Encrypt certificates are automatic, Being trusted by the servers you send to, and Upgrading to 6.3.4 for what changes about hMailServer.ini.
  • 0 Votes
    1 Posts
    125 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) renames two Control Panel pages this topic names: SSL certificates is TLS certificates and SSL/TLS is TLS versions & ciphers; Ctrl+K takes every old title and route to its page. See the 6.3.7 announcement. On Windows, hMailServer 6.3.8 (2 November 2026) reads each certificate and private key file, dh2048.pem and a port's client-certificate CA file from the file configured, and refuses with HM7536 a name it cannot hand over as it is named (work item 115): a port whose certificate or key such a name gives does not start, a client-certificate CA file verifies no certificate, and dh2048.pem is left out as a missing one is. Before upgrading, give each such file whose path holds characters outside ASCII, and the folders above it, an ASCII name; the Control Panel shows a certificate or key file whose name is refused as Name refused. On Linux every name goes over in UTF-8, so on a server that ran under a LANG that is not UTF-8, rename such a file to its UTF-8 name before upgrading. See the 6.3.8 announcement. This topic is the manual half of TLS on the SMTP, IMAP and POP3 listeners: your own certificate and where the Windows service may read it, then versions, ciphers, key exchange, session resumption, client certificates with SASL EXTERNAL, and certificates for IP addresses. Know this first: a port set to use TLS whose certificate cannot be loaded does not fall back to plain text. It does not start. The application log says HM5113, and the only other symptom is a listener missing. Where each thing is set, and when it applies What Set in Applies Certificates, and which port uses which Control Panel: TLS & certificates → SSL certificates and Connections & protocols → TCP/IP ports; the Deck's certificates and listeners views; /api/v1/certificates and /api/v1/ports; hmctl's certificate and port resources; COM At a reinitialise, or a service restart TLS 1.0 to 1.3, the cipher list for TLS 1.2 and below, the two cipher-order switches Control Panel: TLS & certificates → SSL/TLS; PUT /api/v1/settings; hmctl settings set; COM Settings See the note below TlsCipherSuites13, TlsKeyExchangeGroups and the four session settings The same page; PUT /api/v1/settings/ini/{name}; hmctl ini set; on Linux hmailserver --set-setting At a reinitialise A reinitialise is Apply now on the Control Panel's pending bar or in the Deck's Settings view, POST /api/v1/server/reinitialize, or Application.Reinitialize over COM. Every service stops and starts again in the same process; it ends the Deck's session and any drain in progress. GET /api/v1/server/settings/pending lists the [Settings] keys saved and not yet running, such as TlsKeyExchangeGroups; a port, a certificate, the versions and the cipher list are never in it. The Control Panel and COM are Windows-only; on Linux use the Deck, REST or hmctl. Do not edit hMailServer.ini. Since 6.3.4 the database holds the [Settings] keys, so a line changed in the file is put back (HM5804) or left unused; Settings: when a save takes effect explains the store and the reinitialise. When the versions and the cipher list apply. They and the two order switches are database settings (hm_settings rows), not [Settings] keys, so SettingsApply.md gives them no mark. Roadmap.md says they are applied in the call that builds each listener's TLS context, and SettingsApply.md says those contexts are built when the servers start; the wiki's Control Panel Pages page, written against 6.2.28, says the SSL/TLS page's COM settings apply at once. Press Apply now after changing any of them, and check from outside. Adding your own certificate Two PEM files. The certificate file must hold the leaf and the issuer chain: the server loads it twice, once as the certificate and once as the chain, and reports each failure separately. The private key is the second file. A passphrase-protected key is supported, per certificate. Give it in the add dialog, on the SSL certificates page's Private key passphrase card, in the Deck, or as private_key_password in POST /api/v1/certificates. It is write-only and kept under the machine's own secret store. The built-in backup and restore carries it to a new machine; a copied or shared database does not, so on a warm standby enter it again after a failover, and again on the primary after failing back, or the key does not load there and the TLS ports that use it do not start. An encrypted key with no passphrase is HM6170, and its port does not start. Where the files go. On Windows, a new 6.3.4 installation runs the service as NT SERVICE\hMailServer, which reads only what it has been granted (next section); put the files in Data\Certificates (C:\Program Files\hMailServer\Data\Certificates on a default installation), where a new 6.3.4 installation's self-signed pair is. An upgraded Windows server keeps the account it had; LocalSystem can read a file anywhere. On Linux the service runs as User=hmailserver under a unit with ProtectHome=yes, so nothing under /home is visible to it. The self-signed pair of a new installation is in /var/lib/hmailserver/Certificates, its key mode 0600 owned by the service's user; keep yours beside it, readable by that user. Copy the certificate file and the key into that folder. Add the certificate: on Windows, TLS & certificates → SSL certificates → Add certificate (a name, the PEM certificate file, the PEM key file, an optional passphrase); or the Deck's certificates view; or POST /api/v1/certificates with name, certificate_file and private_key_file. A file that is not there is refused (400). The Control Panel's grid then shows whether the certificate parses, when it expires, and whether the key is encrypted and matches. On Windows under the virtual account, run Grant access (next section), so it reads the new paths. Set the certificate on every TLS port: TCP/IP ports → Edit, the certificate list. Over REST, PUT /api/v1/ports/{id} is the whole record and fields left out take their defaults, so send protocol, address, port, connection security, certificate_id and the two client-certificate fields every time. Reinitialise. Check the application log for the codes below and, on Windows, Monitoring & troubleshooting → Server status for the listeners actually running and TLS & certificates → Transport encryption overview, which judges ports, certificates, versions and ciphers together. From a shell over the REST listener: add the certificate, then, once the ports are set, reinitialise and read the banner over implicit TLS. --from-json with - reads the body from standard input, which keeps a passphrase off the process list; the JSON holds the fields of step 2 and, for an encrypted key, private_key_password. hmctl certificate create --from-json - < certificate.json hmctl api POST /api/v1/server/reinitialize openssl s_client -connect mail.example.com:465 -quiet Code Message Meaning HM5113 Failed to load certificate file / …certificate chain… / …private key file The path, the format or the passphrase is wrong, the file cannot be read, or the key does not match. The port does not start HM5113 Error initializing SSL. Certificate not set. A TLS port has no certificate. The port does not start HM6170 — An encrypted key with no passphrase stored. The port does not start HM5991 The TLS certificate … expired on … / … is not valid until … Reported at startup; the port starts and offers it anyway, and clients refuse it. A future date usually means the server's clock is wrong HM5603 Unable to enable Diffie-Hellman key agreement dh2048.pem is missing: from Bin on Windows, from beside the binary in /usr/bin on Linux Replacing it later. When ACME renews, it logs ACME: Restarting servers to load the new certificate.; after replacing a file by hand, reinitialise likewise. If you publish DANE records, publish the new TLSA record before you rotate the certificate, never after; GET /api/v1/tlsa gives one 3 1 1 record per configured certificate, for _25._tcp only. Since 6.3.4 the alert certificate.expiring is on as shipped (7 days ahead), but nothing is mailed until AlertRecipient and AlertSenderAddress are set; the metrics listener exports hmailserver_tls_certificate_expiry_seconds. Since 6.3.4 a COM script's change to an SSL certificate is not in effect until Save(), and one never saved changes nothing, so a script that relied on an unsaved change must now save; the listeners still load the certificate at a reinitialise. ACME alongside. The built-in client gives its certificate to TLS ports that have none or hold the installation's self-signed one, and leaves alone a port that names a certificate somebody chose. The HTTPS listeners take files, not a certificate record: RestApiCertificateFile, WebServicesCertificateFile and MetricsServerCertificateFile, each with its private-key partner, all waiting for a reinitialise. ManageSieve reuses the certificate configured on an IMAP, POP3 or SMTP port. The service account's folder rules (Windows, since 6.3.4) hMailServer.exe /GrantServiceAccess, run elevated, grants NT SERVICE\hMailServer Modify on the program folder's own Data, Logs, Temp and Database, Read on its other folders, and what an administrator approved. It is the Grant access… button on Maintenance → Server limits & expert settings, card Windows service account, or, from an elevated Command Prompt in Bin: hMailServer.exe --confirm /GrantServiceAccess It checks every certificate and key file the configuration names (the SSL certificates, DKIM keys, and the REST, metrics and web-services files) against those grants. A file in a folder they reach needs nothing; where the file's own permissions keep the folder's grant from it, the grant gives Read on that file alone. A file they do not reach is listed OUTSIDE and is never given Read where it is, because the configuration could name any file on the machine. Move it into Data\Certificates, or approve its folder for Read with --approve-read "<folder>" or Yes at the question --confirm asks; approve only a folder you recognise. Some folders are refused even when approved, a share and a drive's root among them. ServiceAccount.md does not list the client-certificate CA bundle among the files the grant checks, so keep that bundle in Data\Certificates too. The account's rules in full, from what is never granted to the exit codes, are in Installing on Windows. Protocol versions and the legacy switches Version Bit in SslVersions COM REST, in PUT /api/v1/settings TLS 1.0 (legacy) 2 TlsVersion10Enabled tls_version_10_enabled TLS 1.1 (legacy) 4 TlsVersion11Enabled tls_version_11_enabled TLS 1.2 8 TlsVersion12Enabled tls_version_12_enabled TLS 1.3 16 TlsVersion13Enabled tls_version_13_enabled The shipped value is 24, TLS 1.2 and 1.3 only; SSLv2 and SSLv3 cannot be switched on at all. Do not re-enable TLS 1.0 or 1.1 unless a specific old client forces you to, and plan to replace that client. The versions and the cipher list also govern the connections this server makes when it delivers. The REST, web-services HTTPS and metrics listeners add a TLS 1.2 minimum of their own, so TLS 1.0 switched on for an old mail client stays off for the management API, and a server left with only TLS 1.0 and 1.1 leaves those three listeners with no version at all. Every version switched off is HM5990 and falls back to TLS 1.2 and 1.3; turning TLS off is done per port. Cipher suites: SslCipherList, AEAD-ONLY and TlsCipherSuites13 SslCipherList is an OpenSSL cipher string for TLS 1.2 and below only; it does not touch TLS 1.3. The shipped value is the long-standing list, which also admits CBC-mode suites and static-RSA key exchange. A string OpenSSL rejects is HM5511, and the previous list stays. AEAD-ONLY is a named preset, not an OpenSSL string: set SslCipherList to that word and the server applies a vetted list of forward-secret AES-GCM and ChaCha20-Poly1305 suites (ECDHE and DHE), excluding every CBC-mode suite and static-RSA key exchange. The cost: TLS 1.2 clients that speak only CBC cannot connect, and TLS 1.0 and 1.1 are left with no usable suite, so never enable them alongside it; the Transport encryption overview flags that combination. A misspelled preset goes to OpenSSL, which rejects it, and HM5511 names the string. TlsCipherSuites13 takes RFC 8446 names, colon separated, for example TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256. Empty, the shipped value, leaves OpenSSL's defaults. A list rejected outright is HM6120 and the defaults stand; names this build does not know are named in HM6121 and the rest of the list is applied. Prefer server cipher order and Prioritize ChaCha20-Poly1305 (TlsOptionPreferServerCiphersEnabled, TlsOptionPrioritizeChaChaEnabled; over REST tls_prefer_server_ciphers, tls_prioritize_chacha) are both off as shipped. The second works only with the first on and TLS 1.2 or 1.3 enabled. A compliance baseline from a shell, then a reinitialise: hmctl settings set --set ssl_cipher_list=AEAD-ONLY --set tls_prefer_server_ciphers=true hmctl ini set TlsCipherSuites13 TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 hmctl api POST /api/v1/server/reinitialize The ini line takes the administrator password (--password-file or HMAIL_PASSWORD); an API key is refused there with 401. Key exchange: the hybrid post-quantum groups TlsKeyExchangeGroups ships as X25519MLKEM768:SecP256r1MLKEM768:X25519:secp384r1:secp256r1: two ML-KEM hybrids first, then the classical curves, so a modern peer agrees a hybrid and an older one falls back. The list reaches every TLS context in the server: the mail listeners, ManageSieve, the three HTTPS listeners and the outbound client, so mail this server sends uses it too. A list OpenSSL rejects, an empty one, or one that leaves no group, is HM5720 and falls back to secp384r1:x25519:secp256r1, without the two hybrids; if even that is refused, HM5721 and OpenSSL's own defaults. Session caches, tickets and ticket-key rotation All four are on the SSL/TLS page's Session resumption tab and wait for a reinitialise. At their defaults three of them make no OpenSSL call at all. Setting Default A non-default value TlsSessionTicketsEnabled on Off: no tickets on any version. TLS 1.2 clients fall back to the in-process cache; TLS 1.3 clients get no ticket TlsSessionCacheSize 0, OpenSSL's cap of 20480 A positive value replaces the cap; -1 turns the server-side cache off, leaving tickets alone TlsSessionTimeoutSeconds 0, OpenSSL's 300 Bounds how long a leaked resumption secret stays useful TlsTicketKeyRotationSeconds 0, one key for the life of the process A captured ticket can be decrypted for at most two intervals; 86400 is a day Rotation is the one worth turning on for forward secrecy. Without it every ticket is sealed under one key made at startup, and that key, recovered later from a memory dump or a hibernation file, opens every ticket ever recorded. Rotation is process-wide. Each listener keeps its own session-ID context, so a session made on a port that asks for no client certificate is never resumed on one that requires it. Client certificates (mutual TLS) and SASL EXTERNAL The policy is per port, on the TCP/IP port dialog (COM ClientCertificatePolicy and ClientCertificateCAFile; REST client_certificate_policy and client_certificate_ca_file). Policy Behaviour CA bundle cannot be loaded Off (0) Nothing is asked. The default — Request (1) Asked, verified if offered, the outcome logged; the handshake never fails over it HM6140, and the port starts anyway Require (2) The handshake fails unless the client presents a certificate chaining to the port's CA bundle The listener is not started The bundle is the only trust anchor for that port, not the Windows certificate store. A policy on a port without TLS is refused when you save it; Roadmap.md adds a policy with no CA file, and Require on a STARTTLS (optional) port, where a client that never issues STARTTLS is never asked. A hand-edited database that has one anyway is HM6142 (require stops the listener, request only logs). Start with Request to see which clients could survive Require; Require on the port ordinary mail clients use would lock out every one without a certificate. EXTERNAL. A verified client certificate that names an address makes AUTH EXTERNAL (RFC 4422) available on that connection, on SMTP, IMAP and POP3, and to nobody else. It signs in as the mailbox the certificate names: a subjectAltName rfc822Name, the subject's emailAddress, or an address-shaped common name; a certificate for one user is never a credential for another. Since 6.3.4 such a sign-in also meets the domain's and the account's country policy, and an SMTP session signed in with it is no longer refused its second message with 550. Since 6.3.4 a mailbox encrypted at rest refuses EXTERNAL on IMAP and POP3, opening with its own password only. A certificate for an IP address (Linux, since 6.3.4) This is about certificates this server checks when it connects out, not the one it serves. An endpoint given by its address (an object store, a webhook, the update feed, an SMTP route, the syslog sink) presents a certificate with the address in an iPAddress entry, where RFC 5280 puts it. The Linux check read only dNSName entries and the common name, so such a certificate was refused; it is accepted now, and the HTTPS client no longer sends an address as the server name (SNI), as the IMAP migration client already did not. Newly refused on Linux: a wildcard taken to cover an address, and the common name of a certificate whose iPAddress entries name other addresses. Windows always accepted such certificates. What it does not do It does not obtain or renew certificates: that is ACME, in Let's Encrypt certificates are automatic. Connection security per port, and what a mail client is told: Connecting a mail client. MTA-STS, DANE, TLS-RPT and remote domain policies: Requiring TLS. The hardening checklist: Hardening a public mail server. TLS to the database is [Database] TlsMode and TlsCaFile, with no Control Panel or Deck editor: Choosing a database and encrypting the link. Where to read more Wiki: Encryption and Certificates §9.2 to §9.4 and §9.12, Ports Reference §3.4, Settings Reference, Security Hardening §17.3. Documents: ServiceAccount.md, SettingsApply.md, SecureDefaults.md, SecretsAtRest.md, DatabaseTls.md, AlertsAndAuditTrail.md. On the forum, for the certificate-expiry alert and where it is mailed: Alerts, the audit trail, syslog and the journal. The documentation page, 9.2 The manual way.
  • 0 Votes
    1 Posts
    147 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) accepts the UTF8 that classic Outlook for Windows (Outlook 2016 to 2024) sends after the POP3 login, at which its downloads had stopped; SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account, and IMAP takes an authzid naming the client itself as none; IMAP's COPY and MOVE by message number read their set in the client's numbering and refuse one that names a message another session has just expunged (NO [EXPUNGEISSUED]), FETCH BODY[N.MIME] and BODY[N] of a multipart are the stored octets, and a refusal by the access control is NO [NOPERM]; and the IMAP and POP3 listeners can take the client's address from a PROXY protocol header, as the SMTP listener does, off by default. An authenticated submission is DKIM-signed as another hosted domain only when the account may send as the address its From names. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) signs a client in only when its user name is the account's stored address, compared in lower case after a domain alias and the default domain. A user name that differs from it in more than the case of the letters A to Z, or has white space at either end, may no longer sign in: such a name fails as a name that does not exist fails, so correct the user name in the client. See the 6.3.8 announcement. This topic covers connecting a mail client to hMailServer 6.3.4: ports, authentication, folders, automatic configuration, app passwords and devices. The one thing to know first: a new installation of 6.3.4 refuses a password from any machine but itself until the session is encrypted, and an upgraded server is not changed. On either, the TLS ports 465, 993 and 995 exist only once you create them. The settings your users need Setting Value Why, and what goes wrong Incoming server (IMAP) mail.yourcompany.com, port 993, SSL/TLS Implicit TLS: the handshake comes before the greeting. On a port not configured as SSL/TLS the client hangs, then times out Incoming server (POP3) mail.yourcompany.com, port 995, SSL/TLS The same. Only use POP3 if you have a reason to Outgoing server (SMTP) mail.yourcompany.com, port 587, STARTTLS Port 25 is for other servers, not for your users; many ISPs block outbound 25 from residential lines User name The full email address, [email protected] A bare name works only with Maintenance → Advanced → Default domain (for unqualified logons) set Password The account's password, or an app password if the account is enrolled in TOTP See the app passwords section below Encryption Never "none", never "auto-detect" "Auto-detect" is how a client ends up sending a password in the clear The ports, and what 6.3.4 changes on a new installation A new database is seeded with four rows - SMTP 25, SMTP 587, POP3 110 and IMAP 143 - and up to 6.3.3 all four were without encryption. Add 465, 993 and 995 yourself under Connections & protocols → TCP/IP ports in the Windows Control Panel, or on the Control Deck, once you have a certificate of your own. Connection security What the server does None (0) Plain text; STARTTLS is not advertised. Everything, including the password, is in the clear SSL/TLS (1) Handshake first, banner after. Correct for 465, 993 and 995 STARTTLS optional (2) Advertises STARTTLS and accepts both. The right setting for port 25 only: on a submission port it lets a misconfigured client send its password in the clear STARTTLS required (3) Answers 530 5.7.0 Must issue STARTTLS first. to every command except NOOP, EHLO, STARTTLS and QUIT. Right for 587, and for 143 and 110 if you keep them; on port 25 it silently refuses mail from every server that cannot do TLS With TLS chosen and no certificate selected the listener does not start at all: HM5113, Error initializing SSL. Certificate not set., in the application log, connection refused, nothing in the SMTP log; pick a certificate on the port. A port already in use is HM4316, and the rest of the server starts normally. A new installation of 6.3.4 has all four seeded listeners at STARTTLS optional with a self-signed certificate, which a mail client warns about until it is replaced, and the Internet IP range requires TLS for authentication; My computer (127.0.0.1) is left as it was. So from another machine, until the client sends STARTTLS: SMTP AUTH is not offered, and is refused with 530 A SSL/TLS-connection is required for authentication. if it is sent; IMAP advertises LOGINDISABLED, and LOGIN and AUTHENTICATE answer BAD A SSL/TLS-connection is required for authentication.; POP3 USER and AUTH answer -ERR. After STARTTLS every one of them works: the range's rule, not the port's, is what protects the password. When ACME's first certificate arrives it is given to every listener holding the self-signed one: see Let's Encrypt certificates are automatic. An upgraded or attached database is never changed. On Linux the defaults are applied at the unit's first start only when it comes within a day of --create-database. Which databases get them, the verb that applies them by hand and the checklist item that judges an upgraded server are in Hardening a public mail server. The IP range, not the port. RequireTLSForAuth lives on the IP range (Access & abuse protection → IP ranges, the Require auth tab). A range that requires TLS is offered no authentication mechanism at all on a cleartext connection, and IMAP additionally advertises LOGINDISABLED, so a client learns before it sends the password. DisableAUTHList is a comma-separated list of local ports on which SMTP AUTH is neither advertised nor accepted; use it to keep authentication off port 25. Since 6.3.4 such [Settings] keys are kept in the database: change them in the Control Panel, over REST (PUT /api/v1/settings/ini/{name}) or on Linux with hmailserver --set-setting NAME=VALUE. The database's value is the one used; where the service can write hMailServer.ini, a line edited there is put back (HM5804). Which port, decided once. For sending, 587 with STARTTLS required, or 465 if the client does not do STARTTLS properly; for receiving, 993, or 995 for POP3 on exactly one device. Never 143 or 110 without STARTTLS required, and deny 143 and 110 at the firewall once no client is left on them. What the server will actually accept Protocol Mechanism Advertised when Default on a fresh install SMTP AUTH LOGIN AUTH is enabled on the port Yes SMTP AUTH PLAIN Allow plain-text authentication is on No - authallowplaintext is seeded 0 SMTP AUTH SCRAM-SHA-256 Always, when AUTH is enabled Yes SMTP AUTH SCRAM-SHA-256-PLUS On a TLS connection Yes, on a TLS connection SMTP AUTH XOAUTH2 / OAUTHBEARER OAuth2 is enabled, and by default over TLS No SMTP AUTH EXTERNAL The handshake verified a client certificate on that port No IMAP LOGIN command Unless the port or the IP range requires TLS on a cleartext connection Yes IMAP AUTH=PLAIN, AUTH=SCRAM-SHA-256 Allow SASL PLAIN authentication is on No - EnableImapSASLPlain is seeded 0 IMAP SASL-IR Allow SASL initial client response is on No POP3 USER/PASS, SASL PLAIN, SASL SCRAM-SHA-256 Unless authentication is refused on this cleartext connection Yes The switches are on Connections & protocols → Protocols, the SMTP and IMAP tabs. Two consequences look like client bugs when they are not. On a default install, an IMAP client that insists on AUTHENTICATE PLAIN finds no AUTH= capability at all and must fall back to LOGIN. And SCRAM is always the account's own password, so an account with a second factor enrolled, or whose password has passed PasswordPolicyMaximumAgeDays, cannot sign in over SCRAM at all. Outlook on the desktop will not use SCRAM: it needs LOGIN or PLAIN. What the conversation looks like An extract of IMAP on a default install: * OK IMAPrev1 a002 LOGIN alice secret a002 NO Invalid user name or password. Please use full email address as user name. a003 LOGIN [email protected] secret a003 OK LOGIN completed The hint is given because the user name has no @. SMTP submission on 587, STARTTLS required: 220 MAIL01 ESMTP EHLO laptop.example.net 250-MAIL01 250-SIZE 20971520 250-8BITMIME 250-PIPELINING 250-CHUNKING 250-BINARYMIME 250-SMTPUTF8 250-ENHANCEDSTATUSCODES 250-DSN 250-STARTTLS 250 HELP MAIL FROM:<[email protected]> 530 5.7.0 Must issue STARTTLS first. STARTTLS 220 Ready to start TLS There is no AUTH line in that first answer, because the port requires TLS. After the handshake the client must send EHLO again, since the session starts afresh (RFC 3207 section 4.2). The second answer carries 250-AUTH LOGIN SCRAM-SHA-256 SCRAM-SHA-256-PLUS. Folders your users will look for A brand-new mailbox contains INBOX and nothing else. Drafts, Sent, Trash and Junk are created only when Create Drafts, Sent, Trash and Junk for new accounts is switched on (Connections & protocols → Protocols → IMAP), and the setting is seeded off. That is the usual explanation for "my sent mail is not on the server": the client fell back to a local folder because it found nothing designated \Sent. Fact Value Hierarchy delimiter ., so a subfolder of Work is Work.Invoices Public folders namespace #Public Other users' folders namespace #Users, only when ACL (shared folder permissions) is on - it is on by default Special-use designations \Drafts \Sent \Trash \Junk, advertised as SPECIAL-USE and CREATE-SPECIAL-USE IMAP or POP3. hMailServer advertises EXPIRE NEVER, so "leave mail on server" means for good, and a second POP3 session on the same mailbox gets -ERR [IN-USE]. What 6.3.4 changed in IMAP's answers Dovecot's imaptest was first run against this server on 25 September 2026: 31 of its 71 scripts failed, and 4 after 6.3.4 made the server answer as the RFCs say. The capability list is unchanged. A client that relied on the old answers may notice these: When the client 6.3.4 answers Names a mailbox that does not exist, or needs a right the account lacks NO, where it used to answer BAD; a failed SELECT or EXAMINE leaves no mailbox selected Sends an unknown LIST selection or return option BAD Fetches any BODY[<section>] without .PEEK Sets \Seen, BODY[HEADER] and BODY[1.MIME] included Appends a message Stores the literal byte for byte, where it used to add a CRLF, so RFC822.SIZE of a newly appended message is two octets smaller Pipelines a command behind an APPEND with a non-synchronising literal Answers it; it used to be thrown away, which stopped mbsync after one message Sorts, or threads Uses every criterion and builds the RFC's trees, so either can order messages differently Is the first session to be told of new messages Counts them as \Recent, where it said * 0 RECENT Automatic client configuration The server can configure clients, so users type only their address and password. AutoconfigEnabled is already 1; giving the web-services listener a port is what turns it on (WebServicesHttpPort=80, WebServicesHttpsPort=443), at the next reinitialise or restart, since the listener binds when it starts. Both port keys default to 0. Then point autoconfig.yourcompany.com (Thunderbird) and autodiscover.yourcompany.com (Outlook) at the server; both are ordinary host records. URL path Format Fetched by /mail/config-v1.1.xml, /.well-known/autoconfig/mail/config-v1.1.xml Thunderbird autoconfig XML Thunderbird and its relatives /autodiscover/autodiscover.xml Outlook POX autodiscover XML Outlook /email.mobileconfig, /mail/config.mobileconfig Apple configuration profile iOS and macOS Mail Which port ends up in the answer. The server reads your actual port rows and picks the best one per protocol: Rank Port configuration socketType written 100 SSL/TLS (implicit) SSL 90, 80 STARTTLS required on port 587; on any other port STARTTLS 70, 60 STARTTLS optional on port 587; on any other port STARTTLS 10 No encryption plain A protocol with no port at all is omitted. The host name is AutoconfigClientHost if set, otherwise the server's configured host name; with both empty the endpoint answers 404. Delegated mailboxes (since 6.3.4). The Outlook answer also names an AlternativeMailbox for every account that has opened a folder to the address asked about, but only to a request with HTTP Basic credentials, over HTTPS, for that very address. An Outlook IMAP profile never sees it: Outlook reads the element only for an Exchange account, and sends credentials only after a 401, which this server never returns here. What puts a delegated mailbox into a client's folder tree is the IMAP #Users namespace. None of this has been measured in Thunderbird or Outlook. Accounts with a second factor: app passwords A mail client has nowhere to type a six-digit code. Once a TOTP secret exists on an account, the account password is no longer a mailbox credential, and an app password is the only one. The order matters, and the Windows Control Panel puts the tabs in that order on purpose: Open the account: Accounts & domains → Domains → your domain → Accounts → the account. App passwords tab: create one per device, and write down the generated value; it is shown once. Two-factor tab: enrol the secret. Do it the other way round and every configured client stops working with no way back in. App passwords work for Active Directory accounts too, and are unaffected by password expiry, so enabling PasswordPolicyMaximumAgeDays does not silently break configured clients. A person makes their own on the webmail's Security page, which asks for the account's password again. Passkeys and single sign-on do not reach a mail client (since 6.3.4). They sign a person in to the webmail and the Control Deck and nothing else. An account that single sign-on or SCIM made has a random password nobody was told, so it needs a password set by an administrator before its person can make an app password for a phone. A recovery code is not a mail-client credential either. A mailbox encrypted at rest takes its own password only (since 6.3.4). It cannot enrol a TOTP second factor; on IMAP and POP3 its app passwords, an OAuth2 token, a client certificate and a Kerberos ticket are refused, and SCRAM does not open a key that is closed, so the client signs in with IMAP LOGIN or AUTHENTICATE PLAIN, or POP3 USER/PASS or AUTH PLAIN; and a client that was signed in when the switch was made is disconnected once. Devices: what revoking does Every client that has authenticated is one entry on the account's Devices list. Revoking a mail client's entry drops a live IMAP, POP3 or SMTP connection where it stands; a CalDAV, CardDAV or ManageSieve client has no connection to drop, so revoking one means revoking its app password. A revoke is not a ban: a client whose credential still works signs in again, so turn off its app password too, or change the account's password, which drops every connection of the account. Where the list is and what revoking does exactly are in Signing in. When it does not work What the user sees What the server said Cause Fix "Invalid user name or password", with a hint NO Invalid user name or password. Please use full email address as user name. Bare user name, no default domain Use the full address "Invalid user name or password", no hint NO Invalid user name or password. Wrong password, inactive account or domain, a TOTP-enrolled account given its account password, or, since 6.3.8, a user name that differs from the stored address in more than the case of the letters A to Z or has white space at either end Check both are active; issue an app password; give the address as stored "Authentication required" on sending 530 5.7.0 SMTP authentication is required. The client is not logged in for outgoing mail, or is on port 25 Tick "my server requires authentication"; move to 587 "Must issue STARTTLS" 530 5.7.0 Must issue STARTTLS first. Client encryption set to none on a STARTTLS-required port Set the client to STARTTLS A new 6.3.4 installation refuses the password from another machine 530 A SSL/TLS-connection is required for authentication. The Internet range requires TLS for authentication Set the client to STARTTLS, or use 465, 993, 995 once created Client hangs on connect, then times out Nothing: no session is created Implicit TLS expected on a STARTTLS port, or the reverse Match the port's connection security Repeated logon failures, then silence NO Too many invalid logon attempts. and a BYE Ten failures on one connection, the per-name lockout, or the per-IP auto-ban Access & abuse protection → Auto-ban Large attachment rejected 552 5.3.4 Message size exceeds fixed maximum message size The maximum message size, default 20480 KB Max message size, or the domain's own limit While diagnosing on Windows, look at Monitoring & troubleshooting → Live logs and Server status, which lists the listeners the service actually brought up. Per client: if Thunderbird offers "no encryption" it found a plain port, so fix the port, not the client. Where to read more Wiki: Connecting a Mail Client, Ports Reference, Encryption and Certificates, Sign-in and Identity. Documents: SecureDefaults.md, DevicesAndSessions.md. The public documentation: 7. Connecting a mail client, 7.2 Automatic client configuration, 22. Ports reference. On this forum: Let's Encrypt certificates are automatic, IMAP and POP3 on the server for shared mailboxes, when a saved setting takes effect, First install on Linux, Upgrading to 6.3.4 and the 6.3.4 announcement.
  • 0 Votes
    1 Posts
    176 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026), a security release, keeps the schema at 6108: an upgrade from 6.3.6 changes no table, from 6.3.5 or older the upgrade runs 6.3.6's three one-way steps to 6108, and from 6.3.3 or older 6.3.4's steps come first. On MySQL and MariaDB an insert that forgives a duplicate key now forgives that alone and any other failure is reported (HM5032), where it was taken as done with no row written, and a lost connection is reconnected and the statement run again, so the statement after a database restart, a failover or wait_timeout no longer fails; an error log that was quiet may now name failures that were hidden. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026), a security release, adds no schema step, so an upgrade from 6.3.6 or 6.3.7 changes no table. With TlsMode=verify-full on MySQL and MariaDB it checks the database server's certificate against the CA file TlsCaFile names, where in 6.3.4 to 6.3.7 a name holding a character outside ASCII reached the client as no CA file (work item 112); and through MariaDB Connector/C it refuses verify-full without TlsCaFile to a database server on this machine (127.0.0.1, ::1, localhost, an empty Server= or a Server= list that holds one) with HM7535, where Connector/C 3.4.1 and later checked neither that certificate's issuer nor its name (work item 113): before upgrading, name in TlsCaFile a CA that issues nothing but the database's certificates, the one that issued its certificate, or set TlsMode=require. The distribution's client should be MariaDB Connector/C 3.4.11 or later; where it is older and not patched, name the CA in TlsCaFile. On a server that ran under a LANG that is not UTF-8, rename a TlsCaFile named outside ASCII to its UTF-8 name before upgrading. The container image now carries MariaDB's client, so a container told MySQL reaches MariaDB. See the 6.3.8 announcement. A Linux server keeps its accounts, domains, settings and message metadata in PostgreSQL or in MySQL/MariaDB, and in nothing else. There is no built-in database on Linux, hmailserver --create-database refuses SQL Server and SQL Server Compact by name, and SQL Server is refused on Linux as HM6390, because ADO is Windows. This topic covers the choice between the two, the client library each needs, the [Database] section, each backend's set-up step by step, TLS to the database, and what the schema upgrade asks of MySQL and MariaDB. The one thing to know first: since 6.3.4 every table on MySQL and MariaDB is utf8mb4, which needs MySQL 5.7.9 or MariaDB 10.2.2, or later. On an older server the create script stops at its first table, and an upgrade from 6.3.3 stops at its first step, 6040 to 6041, with error 1071, having changed nothing. Check the version before anything else. The first install as a whole is First install on Linux. Choosing between the two PostgreSQL MySQL / MariaDB [Database] Type PostgreSQL MySQL (MariaDB is MySQL) Port 5432 3306 Proven on PostgreSQL 18 for the first Linux proof; PostgreSQL 17 in the TLS lab MySQL 5.7.44 and 8.4.11, MariaDB 10.6.28, 11.4.13 and 11.8.9, on 25 September 2026 Client library libpq, pulled in by the package's own dependencies libmariadb.so.3, opened at run time, installed by you Upgrade steps that ship every step from 5001 onwards every step, back to schema 0 Transactions DDL is transactional, so a failed schema step rolls back cleanly used only if every table reports InnoDB at connect time TLS with nothing set libpq's own prefer prefer since 6.3.4; never encrypted through MariaDB Connector/C before MigratingDatabaseBackend.md calls PostgreSQL the best-behaved backend in this codebase, and gives "you already run it" as the reason for MySQL or MariaDB. Both are proven live on Linux: schema created, account made, message delivered and read back. That the PostgreSQL steps begin at 5001 is not a gap, because PostgreSQL was not a backend before hMailServer 5. Moving between the two later is not an upgrade, and the chain does not do it: it is a new installation against the new backend and a migration. The client library to install The .deb and the .rpm declare their library dependencies automatically (dpkg-shlibdeps for the .deb, rpm's own auto-requires), so libpq is pulled in with the package. MySQL and MariaDB are different: the server opens libmariadb.so.3 at run time rather than linking it, so the wiki tells you to install the client library yourself. Distribution Package Debian, Ubuntu libmariadb3 Fedora, RHEL mariadb-connector-c Arch mariadb-libs Where there is no libmariadb, the server loads Oracle's libmysqlclient. Only Oracle's 8.x client is given TLS options: with any other, require and verify-full are refused with the version it reports, rather than claimed. Oracle's libmysqlclient.so.24, MySQL 8.4's client as Ubuntu 26.04 packages it, is not among the names the Linux server loads. On Windows the client is bundled, as libmysql.dll; it is the same MariaDB Connector/C: 3.4.9 in the x64 installers of 6.2.3 to 6.3.6, and 3.4.11 in 6.3.7's, built from MariaDB's source, with seven authentication plugins where 6.3.6's had nine. The Linux server loads the distribution's libmariadb.so.3, which the distribution patches. The [Database] section The file is root's, so edit it with sudoedit /etc/hmailserver/hMailServer.ini. Key What to write Type PostgreSQL or MySQL Server, Database, Username, Password Where the database is and who the server connects as. The Linux package's template reads a password typed with Passwordencryption=0 as plain text Port Written out, 5432 or 3306: it goes into the connection string as it stands, and there is no useful default TlsMode, TlsCaFile Since 6.3.4, optional (below) UpgradeOnStart Since 6.3.4: 1 lets the service bring its own schema forward as it starts. Off by default, and not needed on a package installation, whose post-install step runs the upgrade NumberOfConnections The pool that deliveries, sign-ins and the rest share; five as shipped These keys are read at the service's start, and are in the file rather than the settings store because they say how the store is reached. sudo -u hmailserver hmailserver --check-config prints the database type it made of what you wrote, without opening the database; Database type: 0 means the Type key did not take. PostgreSQL, step by step Create the role. With --createdb the server creates the database itself in step 3: sudo -u postgres createuser --pwprompt --createdb hmailserver If you would rather the service role could not create databases, which the wiki calls the tidier policy, leave the flag off and create the database yourself: sudo -u postgres createuser --pwprompt hmailserver sudo -u postgres createdb --owner hmailserver hmailserver Write [Database]: Type=PostgreSQL, the server, the database, the role, its password, and Port=5432. Create the schema, as the service user: sudo -u hmailserver hmailserver --create-database If TLS is to be required, that is the database server's to say: ssl=on and a certificate in postgresql.conf, and hostssl lines in pg_hba.conf. MySQL or MariaDB, step by step Check the version against the floor. The reason is key size: a key over a varchar(255), an account's address or a folder's name, is 1020 bytes at four bytes a character, more than an InnoDB key part may be in the older COMPACT rows (767 bytes). MySQL 5.7.9 and MariaDB 10.2.2 are the first releases whose InnoDB, as it ships, takes a key part of up to 3072 bytes and makes its tables DYNAMIC. On a MySQL 5.7 set up as 5.6 shipped, the error is 1071, "Specified key was too long; max key length is 767 bytes". Install the client library from the table above. Create the database, or let the verb create it, and give the account the server connects with every privilege on it: CREATE DATABASE hmailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; GRANT ALL PRIVILEGES ON hmailserver.* TO 'hmailserver'@'%'; Every table names its own character set, so a database made with another default still works. But the upgrade steps create, alter and index tables, and the 6102 step also changes the database's own default character set, which needs the ALTER privilege on the database itself; ALL PRIVILEGES ON <database>.* has it. A database the server creates itself is made utf8mb4 already. Make sure every table ends up InnoDB: the server issues transactions only if they all report it at connect time, and silently does not if any does not. Write [Database]: Type=MySQL, the server, Port=3306, the database, the account and its password. Create the schema: sudo -u hmailserver hmailserver --create-database. Why utf8mb4. MySQL's utf8 is utf8mb3, three bytes a character, so nothing outside the Basic Multilingual Plane fits in it: no emoji. Before schema 6102 such a character in a contact, a folder or a signature was refused with error 1366 under strict mode, and made a question mark where strict mode is off. utf8mb4_general_ci is the twin of utf8_general_ci, the collation the tables had, so an address or a name compares as it did. What it does not do is tell characters outside the Basic Multilingual Plane apart: two address books of one account whose names differ only in an emoji are one name to its unique index. What --create-database does The package installs the server enabled and stopped, on purpose: there is no database yet, and the schema is made afterwards with this verb. If the database is not there, the verb connects to the server [Database] names with no database selected, creates it in the backend's own dialect and reconnects; if it is there and empty, it uses it. Either way it then runs the create script for the backend's type, which is what DBSetupQuick does over COM on Windows, and a database 6.3.4 creates is stamped 6104. Since 6.3.4, says the wiki's Installing on Linux, a database that already holds this build's schema, or an older one, is left as it is with exit 0, and a newer one exits 1; 6.3.3 refused any database that already held a schema. The documentation page (35.4, 35.7) still says a schema is refused, exit 1: test it on a scratch database first. Two things follow a create, both since 6.3.4. The database, an empty one you created first included, is marked for a new installation's secure defaults, which the systemd unit's first start applies if it comes within a day; a database that already held a schema is not marked. And run as the service user on a package installation, the verb cannot write the root-owned configuration, so it makes no stored-secrets key and says what to run instead, sudo hmailserver --create-stored-secrets-key. Export that key and keep the file with the database dumps, apart from them: a dump restored without it brings back every second factor and [Settings] secret unopenable. Both are covered in full in Hardening a public mail server. TLS to the database (since 6.3.4) Before 6.3.4 the connection to MySQL and MariaDB through MariaDB Connector/C, the client Ubuntu packages, was never encrypted, and a database account made with REQUIRE SSL refused the server. Since 6.3.4 two [Database] keys choose the behaviour on every backend (on PostgreSQL the older PostgreSQLSslMode and PostgreSQLSslRootCert win when they are set), and are read at the service's start: TlsMode, one of disable, prefer, require or verify-full (empty is prefer: TLS when the server offers it, the certificate unchecked), and TlsCaFile, a PEM file of CA certificates that verify-full checks against: [Database] TlsMode=verify-full TlsCaFile=/etc/hmailserver/db-ca.pem Only verify-full checks the certificate, and only it protects against someone on the path who can alter the traffic. On Linux, MySQL and MariaDB take the CA file or the system's store, /etc/ssl/certs, and which client library is loaded matters: of Oracle's clients only 8.x is given TLS options (above). On PostgreSQL, verify-full with no TlsCaFile is sslrootcert=system on Linux, with libpq 16 and later. What each mode does, the refusals (HM7530, HM7531), what each backend did before 6.3.4 and does now, and what is thinner than it could be are in Choosing a database, creating it, encrypting the link. The server cannot make the database server refuse plaintext: that is REQUIRE SSL on the MySQL account, or hostssl lines only in pg_hba.conf. Try it before the service is given it. --check-database makes one connection with the TLS settings in the configuration, prints the result, and exits 0 connected, 1 not, 2 not configured. It changes nothing, so it runs beside a running service: $ sudo -u hmailserver hmailserver --check-database Configuration: /etc/hmailserver/hMailServer.ini Database: MySQL/MariaDB on 192.0.2.20:3306, database hmailserver, as hmailserver TLS: TlsMode verify-full, the certificate checked against /etc/hmailserver/db-ca.pem Connected. MySQL/MariaDB: encrypted with TLS (TLS_AES_256_GCM_SHA384), the server's certificate checked (TlsMode verify-full) The application log says the same of the running service's connection, in a line that begins Database connection:. In a container (no image of 6.3.4 is published, so one you build from the .deb) the entrypoint writes [Database] from the environment at every start, so there HM_DB_TLS_MODE and HM_DB_TLS_CA set these, not a line in the file. Upgrading the schema, and the floor On a package upgrade the maintainer scripts run hmailserver --upgrade-database as the service user, only when a database is configured, then try-restart the service if it was running. A failure of the schema upgrade is reported and does not fail the package, so read what it printed. The verb is safe to run by hand, and says "nothing to do" when done: sudo -u hmailserver hmailserver --upgrade-database The chain is applied one step at a time from whatever version the database reports, which select value from hm_dbversion answers. A database from a newer build is refused, and a server refuses to start on a database that is too old or too new for it. Take the dump first, with pg_dump or mysqldump, and the data directory with it: the upgrade is one-way, and rolling back is restoring that dump and reinstalling the older package (Upgrading from 5.x, 6.2 or 6.3: rolling back). From 6.3.3 to 6.3.4 the schema moves from 6040 to 6104 in 64 steps. On PostgreSQL step 6102 only moves the version. On MySQL and MariaDB it converts every table to utf8mb4 under utf8mb4_general_ci, InnoDB and ROW_FORMAT=DYNAMIC: What Detail Time and disk It takes as long as copying the database. Each table is rebuilt in turn, and hm_messages, hm_message_metadata, hm_messageindexterms, hm_messagerecipients and hm_audit take the time: one to five seconds on an empty schema, minutes or more on a large installation. Have free disk space of the order of the largest table The floor, error 1071 Steps 6041, 6101 and 6102 each open with a statement that needs a long key; 6041 and 6101 make and drop a probe table, hm_upgradeprobe. On a server older than MySQL 5.7.9 or MariaDB 10.2.2 the upgrade stops at 6040 to 6041 with error 1071, and nothing has been changed. Upgrade the database server, then the schema The privilege, error 1044 The step changes the database's default character set, which needs ALTER on the database itself, not only on its tables A duplicate, error 1062 Two rows that the old collation told apart and the new one does not, under one unique index, stop the step; the error names the index and the value. The wiki's examples are straße and strase in one address book on MariaDB 11.8, and on MySQL 8.4 two names that differ only in an emoji. Rename or remove one and run the upgrade again: every statement of the step can run twice Afterwards A text column may read mediumtext, and a mediumtext one longtext. The server reads both alike Until 26 September 2026 steps 6041 and 6101 made their first tables before the index that failed, so an unreleased build from before that day could leave a database half done at 6040 or 6100. No upgrade path repairs that; Upgrading.md gives the DROP TABLE statements that do. Two nodes on one database are serialised step by step if they start together: on PostgreSQL by the version row, which each step locks for the length of its transaction, and on MySQL and MariaDB by a named lock, GET_LOCK('hmailserver_schema_upgrade'), held until the step is over and waited on for up to four hours. A server that refuses GET_LOCK altogether, MariaDB Galera with wsrep on, still upgrades with the row lock alone, and the output says so for each step: there, upgrade from one node at a time. Where to read more Installing on Linux and Before You Install, section 3.5, on the wiki. DatabaseTls.md, with the Linux lab run of 25 September 2026 line by line; Upgrading.md for the 6102 step and the repair of a half-done one; MigratingDatabaseBackend.md for why utf8mb4. The documentation page: 3.5 A database, 35.4 Creating the database and 35.13 Upgrading and rollback. On this forum: First install on Linux, Upgrading to 6.3.4 and Moving to new hardware; choosing a database and encrypting the link for TLS on every backend, and the hmailserver command line on Linux for the other verbs.
  • 0 Votes
    1 Posts
    128 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) is a security release that bears on this guide. On Windows, hMailServer.exe /ProtectFolders, which setup runs at every installation and upgrade, now hardens the program folder, Bin and ControlPanel: where hMailServer was installed in a folder that took its permissions from the root of a drive, any account that could sign in to the computer could replace the programs the service runs (CVE-2026-107571); the default folder under Program Files was never affected, and an installation at a drive's root is hardened by the upgrade itself. The serveradmin role given with a list of domains is confined to those domains over COM as over REST, a credential confined to named domains is refused the changes that reached beyond its domains over REST and COM, and a state-changing REST request that a browser reports as cross-site is refused. SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account, and the stored-secrets key is no longer left in memory freed without being written over. An upgraded Windows server keeps the .NET 10 Desktop Runtime it had, 10.0.12 where 6.3.4 to 6.3.6 installed it, against which Microsoft published advisories on 13 October 2026: bring it to 10.0.13 through Microsoft Update or Microsoft's installer. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) is a security release too: upgrade every server running 6.3.7 or earlier. On Windows setup now makes or hardens the program folder, Bin and ControlPanel before it copies a file or runs a program of the installation (the four narrower cases of work item 78 that 6.3.7's notes left open), and a program folder, Bin or ControlPanel, a folder or a link on the way to them, or a program, library, script or settings file in them, that another account than SYSTEM, the Administrators and TrustedInstaller controls ends the installation with exit code 7, naming the icacls that deals with it and, where it says so, a restart of Windows before setup runs again. The auto-ban saves the IP range it adds under a name of its own, so that it bans the address; the web services listener (CardDAV, CalDAV, Autodiscover) refuses an address an auto-ban or an IP range bans; and a sign-in's name is matched to its account as the per-name lockout and the compromised-account hold count it, so a mail client that signs in with another spelling of the account's address than the stored one, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in: correct the name in the client. See the 6.3.8 announcement. This guide covers what stands between a mail server on the public internet and the people who guess passwords at it. All of it is new in 6.3.4 except auto-ban, account lockout, the administrator's second factor and the IP range option Require TLS for auth, which are older; 6.3.4 sets that option on a new installation and judges it in the checklist. The one thing to know first: the secure defaults are applied only to a database the installation itself created, and an upgraded server is never changed. After an upgrade your listeners, IP ranges and service account are as they were and nothing is sealed until you make a key; the checklist says what is left. What a new installation starts with, and what an upgrade keeps In 6.3.3 and earlier a new installation listened on ports 25, 587, 110 and 143 with no TLS, and neither shipped IP range required TLS for authentication. The create scripts still make that database; the Windows installer, and the Linux packages' first start, now change it before the server first listens. A new installation since 6.3.4 An upgraded server Ports 25 and 587 (SMTP), 110 (POP3), 143 (IMAP) STARTTLS offered, optional as it was IP range Internet (every IPv4 address) TLS required for authentication as it was; the checklist flags it IP range My computer (127.0.0.1) unchanged as it was Certificates Self-signed (made at installation) as they were Windows service account NT SERVICE\hMailServer the account it had (LocalSystem, if it began before 6.3.4) What a client sees. From another machine, AUTH on SMTP is not offered before STARTTLS and is refused with 530 A SSL/TLS-connection is required for authentication. if it is sent; IMAP LOGIN and AUTHENTICATE answer BAD A SSL/TLS-connection is required for authentication.; POP3 USER and AUTH answer -ERR. After STARTTLS each works as before. From the server itself, on 127.0.0.1, nothing has changed, and mail from other servers on port 25 is taken with or without TLS. Why STARTTLS is optional. A port's requirement applies to every address: on port 25 it would refuse every server that cannot start TLS, on the others this computer's own clients. The range's rule protects the password. The certificate. self-signed-certificate.pem and its key self-signed-key.pem are in <data directory>/Certificates: C:\Program Files\hMailServer\Data\Certificates on a default Windows installation, /var/lib/hmailserver/Certificates from the Linux packages, where the key is mode 0600 and owned by the service's user. It is RSA 2048, runs 825 days, names the host and gives the organisation hMailServer, self-signed at installation. It encrypts, but nothing vouches for it, so mail clients warn. Replace it with ACME (set AcmeEnabled, AcmeContactEmail and AcmeDomains; the first certificate goes to every listener that holds the self-signed one) or with your own, set on each port. See Let's Encrypt certificates are automatic. Where the defaults are applied The create step marks its database with the hm_settings row NewInstallDefaultsPending; the verb --apply-new-install-defaults requires the mark and removes it once it has decided. Platform When it runs What to know Windows The installer, after the database step of a new installation, with the service stopped A database DBSetup attached, rather than created, carries no mark: exit 5, logged, nothing changed Linux (.deb, .rpm) The unit's first start after sudo -u hmailserver hmailserver --create-database, which marks the database and leaves a one-line file, .new-installation, in the DataFolder The unit runs ExecStartPre=-/usr/bin/hmailserver --apply-new-install-defaults --if-marked: nothing unless that file is there, then one try at the database, given room by TimeoutStartSec=300 (the leading - forgives a failure, not a timeout). It ignores a mark made more than a day from its clock; a run by hand has no such limit A container (since 6.3.8) The image's entrypoint, before whatever command the container runs; the Compose file and the Helm chart run the same step before the server hmailserver --apply-new-install-defaults --if-marked secures a database the entrypoint's --create-database made a moment before and opens nothing at any other start; a failure (run as root, a database not reached) is said and forgiven, and the server starts all the same By hand A container from an image before 6.3.8, a server started by hand rather than by the unit, or defaults left for later With the service stopped; on Linux as the service's user, because the verb refuses to run as root By hand, on Windows and then on Linux: Stop-Service hMailServer & "C:\Program Files\hMailServer\Bin\hMailServer.exe" --apply-new-install-defaults Start-Service hMailServer sudo systemctl stop hmailserver sudo -u hmailserver hmailserver --apply-new-install-defaults sudo systemctl start hmailserver Exit codes: 0 applied (with --if-marked, also nothing to do); 1 something could not be made or written (HM7510, HM7511 in the error log); 2 no database named, or not readable enough to judge, or on Linux run as root; 4 the Windows service is running; 5 not a database this installation created, or not as the create script left it: nothing changed. On Linux, mind the data directory. A DataFolder outside the unit's ReadWritePaths (/var/lib/hmailserver and /var/log/hmailserver) is read-only to the service, so the first start can neither write the certificate nor store mail there. Add the directory with a drop-in (sudo systemctl edit hmailserver, then ReadWritePaths=/srv/mail under [Service]) before that first start. Not yet proved on a clean machine. SecureDefaults.md's checks that the installer applies the defaults are not yet written, and those for the .deb ran only on GitHub Actions, so nothing runs them today. The setup checklist The checklist judges every server, new or upgraded, in twelve items. Key Done when profile a setting profile has been applied; asked for on a server with fewer than ten accounts domain an active domain exists, and none has a name the server reserves for its own directories account at least one account exists submission every SMTP listener other than port 25 has STARTTLS or implicit TLS, and a certificate other than the self-signed one sign_in_tls every IP range that lets a client connect from another address requires TLS for authentication, or reaches only listeners that start TLS before a password spf, dkim, dmarc every active domain has a TXT record beginning v=spf1, signs with a key whose record is published at <selector>._domainkey.<domain>, and has a TXT record beginning v=DMARC1 at _dmarc.<domain> backup the destination is set and the folder exists second_factor the administrator password is enrolled with a one-time code stored_secrets_key this server holds the key the database records, and an export of it has been recorded service_account it is not LocalSystem on Windows, not root on Linux Each item is done, action_needed, cannot_tell or not_needed. Port 25 is never the submission port, and a plain submission port beside a secured one still needs action. Where it is. GET /api/v1/setup/checklist (reading needs view_configuration; ?dns=0 skips the lookups), the Deck's Checklist view and, on Windows, COM's Utilities.GetSetupChecklist(LookUpDns) and the Control Panel's External setup page and dashboard. Passwords only over TLS, on a server you upgraded "Require TLS for auth" is an option of an IP range, not of a port, and covers SMTP, POP3, IMAP and ManageSieve alike. Read the sign_in_tls item: it names each range, and the listeners where a password from there would cross the network in the clear. If the finding is sign_in_no_tls, a listener has no TLS to start, and requiring TLS would refuse every client on it: give those listeners a certificate and STARTTLS first. This finding carries no fix. Set every mail client that signs in from another machine to STARTTLS first. Once the range requires TLS, a client that signs in without TLS is refused; that is why an upgrade is never changed by itself. When the finding is sign_in_plain, apply its fix: the Deck's button Require TLS for sign-in from this range, or PUT /api/v1/ipranges/{id} with {"require_tls_for_auth": true}, which needs manage_settings. On Windows the Control Panel opens its IP ranges page, where the setting is Require TLS for auth. Replace the self-signed certificate if you have one, so that submission_self_signed clears. Secrets at rest and the stored-secrets key Until 6.3.4 the administrator password (unsalted MD5, from the Windows installer), each account's second factor and the [Settings] secrets were kept as written. The administrator password is now written as PBKDF2. An older MD5 or SHA-256 value is rewritten as PBKDF2 at the first successful administrator sign-in where the service can write the file. Where it cannot (NT SERVICE\hMailServer, or the Linux package's root-owned file) the old value stays, HM7494 says so once, and the verb rewrites it: on Windows hMailServer.exe --set-admin-password from an elevated prompt, with the password in HM_ADMIN_PASSWORD, or on one line of standard input from a pipe or a file for a password in plain ASCII (Windows PowerShell 5.1 turns every other character into ?), never as an argument; on Linux sudo hmailserver --set-admin-password, which reads standard input. Second factors and ten [Settings] secrets (SRSSecret, BATVSecret, OAuth2HmacSecret and the rest) are sealed with AES-256-GCM under the installation's stored-secrets key, in the form HMK1:<key id>:<base64>, once the installation has a key. The key is kept in each server's hMailServer.ini as [Security] StoredSecretsKey, and never in the database or a backup archive. Who gets a key without asking. The Windows installer makes one on a new installation. On Linux, hmailserver --create-database makes one when it creates the schema and can write the configuration: in a container, whose /etc/hmailserver the service owns, it does (no image of 6.3.4 is published); run by the package as the service's user, it cannot. An upgraded installation, and a package installation on Linux, seal nothing until an administrator makes the key: Make the key on one node. Windows, from an elevated prompt beside hMailServer.exe: .\hMailServer.exe --create-stored-secrets-key. Linux: sudo hmailserver --create-stored-secrets-key. Export it at once: .\hMailServer.exe --export-stored-secrets-key D:\Escrow\hmailserver-stored-secrets.key, or sudo hmailserver --export-stored-secrets-key FILE. The export writes a new file, never over one that exists, readable only by SYSTEM and the administrators on Windows and mode 0600 on Linux, and prints the key id, never the key. Restart the service on that node (Restart-Service hMailServer, or sudo systemctl restart hmailserver). Its next start seals what is stored as written; until then nothing is sealed. On every other node that shares the database, --import-stored-secrets-key FILE, then restart each. An import is refused while the node holds another key that opens: a standby or new node whose installer made a key of its own removes its [Security] StoredSecretsKey line first, and until then the shared database refuses it (HM7495). Keep the exported file with the database backups, stored apart from them. Why the export matters. The product's own backup archive carries these secrets opened, and a restore seals them under the restoring server's key, so set a backup passphrase. A database-level backup (a dump, a copy of the .sdf) restored without the key brings back every second factor and [Settings] secret unopenable. A copy of hMailServer.ini is no copy of the key on Windows, since its line is sealed under that machine's DPAPI; never copy that line from one machine to another. When a node lacks the key it says HM7495 at every start and makes no key of its own; until the key is imported, a [Settings] secret sealed under it runs as though unset (HM7492) and a second factor refuses every code (HM7493). A key lost for good is recovered from with --forget-sealed-secrets <key id>, with the service stopped (exit 4 otherwise), which clears only what that key sealed: every second factor is enrolled again, every [Settings] secret entered again. ProtectStoredSecretsWithDPAPI=0 (default 1) has a cost. The next start opens what was sealed and stores it as written, readable by anyone who can read the file or the database, and the machine-bound secrets are written with the legacy Blowfish scheme, whose key is in the source code: the way back, not a setting for a new server. Two things to do after an upgrade. Since 6.3.4 PasswordPepper stays in hMailServer.ini only and is left out of every backup, so keep a copy of that value with the backups. And a password hash stored before 6.3.4 from a password with a character above U+00FF is refused, to its own password too: an administrator sets such passwords again, and the application log names each account as its user tries to sign in. Checking new passwords against published breaches Off by default, because it sends something off the server. When on, a new password found in the published corpus of breached passwords is refused. Setting (REST key) Default What it means breach_check_enabled off The whole feature breach_check_url https://api.pwnedpasswords.com/range/ Where the five characters are sent breach_check_minimum_count 1 Refuse a password seen this many times or more breach_check_unreachable_action allow allow lets a change through unchecked; refuse refuses it What leaves the server is the first five characters of the password's SHA-1 and nothing else. The default endpoint is a third-party service, Have I Been Pwned's Pwned Passwords, and an organisation that cannot send it those five characters, its address and the time runs its own mirror. When it runs. When a password is chosen, by the person or by an administrator, and never at logon. An endpoint that gives no complete answer within eight seconds counts as unusable, and the error log records HM6600 (let through) or HM6601 (refused). Under refuse, an endpoint that is down refuses every password change on the server. Bulk imports make one request per account, so run them against a mirror or with the check off. Where it is set. The four settings are hm_settings rows, not hMailServer.ini lines, and a change applies from the next password chosen. They are on the Control Panel's Administrative access → Password page (the Passwords found in published breaches card), in the Deck's Settings view, over REST and in hmctl settings set: hmctl settings set --set breach_check_enabled=true Compromised-account detection This judges every authenticated submission (SMTP, the webmail's send, a scheduled draft) against the account's own history of the last 30 days, without reading the message. It starts in watch on every installation, new or upgraded: the baseline is built, the account.compromised alert says what enforcing would have done, and nothing else happens. Four signals score: volume (2 points), the last hour's messages reaching compromise_volume_floor (30) and exceeding compromise_volume_factor (4) times the busiest baseline hour; recipients (2), the last hour's distinct recipients reaching compromise_recipient_floor (20), at least 80 per cent of them new to the account; hour (1), an hour of the day (UTC) the account has not submitted at in the baseline; address (1), a client network (the /24 of an IPv4 address, the /64 of an IPv6 one) not seen for this account. compromise_score_to_lock (3) is a takeover. In enforce, the message is held, the account is locked until an administrator unlocks it, and the alert and an account.locked webhook are raised. The client is still answered 250 Queued. Held mail is reviewed on the Deck's Held for review page and under /api/v1/review; a held message nobody has looked at in compromise_held_days days (7) is discarded. Watch it before enforcing: the false positive is a person locked out of their own mail. There is no Control Panel page for it. Roles instead of the administrator password Rather than share the administrator password, give each administrator a role in the domains they look after, on the Deck's Roles view, over REST, or on Windows in the Control Panel or over COM. Nothing changes on the day you upgrade: an account with no assignment holds the authority its admin level has always given it, and a narrow role cannot take over a wider one, so a help desk is refused a domain administrator's password reset. Administrator roles end to end has the permissions, how to give a role, and the two stores only the administrator password opens. The rest of the list Auto-ban is on by default: 3 failures within 30 minutes, a 60-minute ban. MaxInvalidLogonAttempts at 0 disables it, and AutoBanMinutes at 0 disconnects the client but bans nothing. Account lockout (AccountLockoutThreshold) is off until set, and counts by the name being guessed at. IP ranges, auto-ban, lockout and the rest has both in full. The server-enforced administrator second factor refuses the password alone everywhere, the REST API included. On Windows, move a service still on LocalSystem with the installer, the Control Panel's button, or from an elevated Command Prompt in Bin: hMailServer.exe --confirm /GrantServiceAccess, then, only if that ended in 0, hMailServer.exe --service-account virtual /Register, then net stop hMailServer && net start hMailServer. Check first what the virtual account cannot do, listed in Installing on Windows. The event-script flaw fixed in 6.3.4 (CVE-2026-100741; its record was not yet public when this was written) is in every release from 6.0.0 to 6.3.3: upgrade, and until you can, switch event scripting off. See Event scripts and the 6.3.4 security fix and the 6.3.4 security announcement. What it does not do Passkeys, single sign-on and the session policies do not reach IMAP, POP3 or SMTP, which still take the password or an app password. The breach check does not cache, and does not expire a password that enters the corpus later. Compromised-account detection does not drop a session that is already up. The stored-secrets key protects against a copy of the database that leaves without the file, not against a local account on the server. Where to read more Wiki: Security Hardening. Documents: SecureDefaults.md, SetupChecklist.md, SecretsAtRest.md, PasswordBreachCheck.md, CompromisedAccounts.md and Roles.md. The public documentation: chapter 17, Security hardening. On this forum: Upgrading to 6.3.4, The Control Deck and hMailServer on Linux. A suspected vulnerability is reported privately, never in a forum post: by a confidential GitLab issue, or by email to the project's Service Desk address, contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as a confidential issue (SECURITY.md).
  • 0 Votes
    1 Posts
    157 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) adds no schema step (6108, as 6.3.6). Its x64 installer's MySQL and MariaDB client is MariaDB Connector/C 3.4.11, built from MariaDB's source, in place of the 3.4.9 that 6.2.3 to 6.3.6 carried (CVE-2026-107819), with seven authentication plugins where 6.3.6's had nine: a database account that signs in with MySQL Enterprise's Windows authentication can no longer sign in, and the server then cannot open its database; the ARM64 installer carries no MySQL or MariaDB client. On MySQL, MariaDB, SQL Server and SQL Server Compact an insert that forgives a duplicate key now forgives that alone and any other failure is reported (HM5032), and on MySQL and MariaDB a lost connection is reconnected and the statement run again, so an error log that was quiet may now name failures that were hidden. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026), a security release, adds no schema step either. Through MariaDB Connector/C, the client the Windows installer bundles and the Linux server loads, TlsMode=verify-full without TlsCaFile to a MySQL or MariaDB server on this machine (by a loopback address, localhost, a Unix socket or a named pipe, or through a Server= list that holds one) is refused with HM7535, since in 6.3.4 to 6.3.7, with Connector/C 3.4.1 or later, neither the certificate's issuer nor its name was checked there: before upgrading, name in TlsCaFile a CA that issues nothing but the database's certificates, the one that issued its certificate, or set TlsMode=require. A TlsCaFile whose name holds a character outside ASCII, which in 6.3.4 to 6.3.7 could reach the client as no CA file at all, is now handed to it as named; on Windows a TlsCaFile or PostgreSQLSslRootCert name the client cannot be handed as named is refused with HM7534, so give the file, and the folders above it, an ASCII name; and on Linux, where the distribution's client is older than Connector/C 3.4.11 and not patched, name the CA in TlsCaFile. See the 6.3.8 announcement. This topic covers the four databases hMailServer 6.3.4 can keep its data in, what each needs before the schema can be made, how the connection to the database server is encrypted since 6.3.4, and how to move an installation off the built-in engine. The one thing to know first: on MySQL and MariaDB, 6.3.4 needs MySQL 5.7.9 or MariaDB 10.2.2 or later, because since schema 6102 every table is utf8mb4. On an older server the create script stops at its first table, and an upgrade from 6.3.3 stops at its first step, 6040 to 6041, with error 1071 and nothing changed. What the database holds, and what it does not hMailServer stores accounts, domains, message metadata and settings in the database. The message bodies are files on disk, one per message, at <DataFolder>\<domain>\<local part>\<xx>\{guid}.eml, so the database stays small relative to Data\. Since 6.3.4 the database is also the settings store: the [Settings] keys of hMailServer.ini are rows from schema 6042, the file's section is the server's copy, and a line edited there for a key the database holds is named in the error log as HM5804 and does not take effect. Where the service may write the file the line is put back; where it may not (a new Windows installation under NT SERVICE\hMailServer, a Linux package installation) the copy is not refreshed, the edited line stays, and the database's value is used. The [Database] section itself stays in the file, because it says how the store is reached. The four choices Database [Database] Type What is bundled What you must have first Built-in (SQL Server Compact 4.0 SP1) MSSQLCE SSCERuntime_x64-ENU.msi, installed by setup Nothing. This is the default MySQL / MariaDB MYSQL MariaDB Connector/C 3.4.9 as Bin\libmysql.dll (3.4.11 since 6.3.7, with seven authentication plugins where 6.3.6's had nine) A reachable server, MySQL 5.7.9 or MariaDB 10.2.2 or later, an empty database, and a user that may create tables Microsoft SQL Server MSSQL Nothing; the OS's own data stack The same, including Express edition PostgreSQL PostgreSQL libpq from PostgreSQL 18.3 The same On Linux there is no built-in database: a Linux server keeps its data in PostgreSQL or MySQL/MariaDB, and hmailserver --create-database refuses SQL Server and SQL Server Compact by name. For MySQL or MariaDB install the client library yourself, because the server opens libmariadb.so.3 at run time: libmariadb3 on Debian and Ubuntu, mariadb-connector-c on Fedora and RHEL. Databases on Linux has the steps for each backend, and First install on Linux the order of the whole first install. Two things about the built-in engine. It runs on a single database connection: NumberOfConnections is forced to 1 for MSSQLCE. And Microsoft has withdrawn the SQL Server Compact download; the copy in installation/SQLCE/ is the archive of record, and it still installs. MigratingDatabaseBackend.md calls it "the one real dependency liability in this tree". For anything up to a few dozen mailboxes it is adequate, and the last section of this topic is the way off it. How the four behave differently MS SQL Server PostgreSQL MySQL / MariaDB SQL Server Compact DDL in a transaction Mostly Yes, the only one of the four No, commits as it goes No, commits as it goes A failed schema step leaves Normally nothing applied Nothing applied A partly built database A partly built database Isolation the server uses READ UNCOMMITTED its default its default its default Transactions used at all yes yes only if every table reports InnoDB at connect time asked for, but a no-op: SQLCEConnection::BeginTransaction returns true without starting one (Roadmap.md) Choose it when You already run SQL Server You want a failed schema step to roll back cleanly You already run it As a new installation's default, up to a few dozen mailboxes; as the target of a migration, only for a test rig MySQL and MariaDB since 6.3.4 MySQL 5.7.9 or MariaDB 10.2.2, or later. Since schema 6102 every table is utf8mb4 under utf8mb4_general_ci, InnoDB, with ROW_FORMAT=DYNAMIC, and a key over a varchar(255) is then 1020 bytes: more than the 767 bytes an InnoDB key part may be in the older COMPACT rows, and within the 3072 bytes these releases, as they ship, take. Why. MySQL's utf8 is three bytes a character, so an emoji in a contact, a folder or a signature was refused with error 1366 under strict mode and made a question mark without it. utf8mb4_general_ci is the twin of utf8_general_ci, so an address or a folder compares as it did, but it does not tell characters outside the Basic Multilingual Plane apart: two address books called Family 👪 and Family 🎉 are one name to its unique index. Creating the database yourself: CREATE DATABASE hmailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; GRANT ALL PRIVILEGES ON hmailserver.* TO 'hmailserver'@'%'; The 6102 step also changes the database's own default character set, which needs the ALTER privilege on the database, not only on its tables. Every table must end up InnoDB: the server issues transactions only if all of them report it at connect time, and silently does not otherwise. A database the server creates itself is made utf8mb4 already. The 6.3.4 upgrade on these two rebuilds every table, and on a server older than 5.7.9 or 10.2.2 it stops at 6041 with error 1071, having changed nothing. Databases on Linux covers that step in full, its time, disk and duplicates (error 1062) included, and Upgrading to 6.3.4 the rest of the upgrade. Creating the database on Windows The wizard asks only "built-in or external". For the built-in engine, DBSetupQuick.exe creates the database at the current schema version, 6104. For an external engine, DBSetup.exe runs after the file copy, asks for the connection, and either creates the database or attaches one that exists; the secure defaults of a new 6.3.4 installation are applied only to one it created. Since 6.3.4 the administrator password reaches both tools in the environment variable HM_ADMIN_PASSWORD, never on a command line. The tools write [Database] in Bin\hMailServer.INI: Key Meaning Type MSSQL, MYSQL, PostgreSQL or MSSQLCE, matched case-insensitively Server, Database, Username, Password The connection. Server is empty for the internal engine PasswordEncryption Written by the server; says how Password is protected Port 0 uses the backend's default Internal 1 only for the embedded engine NumberOfConnections Pool size; default 5 The password in this file may be machine-bound. With ProtectStoredSecretsWithDPAPI=1, the default, the server writes Password through machine-scoped DPAPI (PasswordEncryption=6), and it cannot be decrypted on another machine. If you are also moving hosts, set the password on the new machine through DBSetup.exe rather than copying the encrypted one across. Under NT SERVICE\hMailServer, the account a new 6.3.4 installation runs as, hMailServer.INI is read-only to the service, and the COM methods the two tools call to write [Database] are refused before anything is made. The installer therefore runs its database step while the service is still LocalSystem. To change the database later, move the service to LocalSystem for the change and back (Installing on Windows has the commands), or edit [Database] by hand and restart. Encrypting the connection (since 6.3.4) The test lab measured on 25 September 2026 that the connection to MySQL and MariaDB through MariaDB Connector/C, the client bundled on Windows and the one Ubuntu packages, was never encrypted: every account was reached in plaintext, password exchange, queries and rows alike, and one made with REQUIRE SSL refused the server. Since 6.3.4 two keys under [Database] choose the behaviour: [Database] TlsMode=verify-full TlsCaFile=C:\hMailServer\certs\database-ca.pem TlsMode What the connection does (empty, the default) prefer: TLS whenever the server offers it, and the connection without it when the server offers none, or when the TLS it offers cannot be agreed on (one attempt with TLS and one without, for that failure only). The certificate is not checked prefer As the default, said explicitly disable No TLS. SQL Server's providers still encrypt the login itself require TLS or no connection; the certificate is not checked verify-full TLS or no connection, the certificate checked: its chain to a CA in TlsCaFile (or the system's trust store when that is empty; since 6.3.8 an empty one is refused, HM7535, on MySQL and MariaDB through MariaDB Connector/C to a database server on this machine), and its name or address against Server= TlsCaFile is a PEM file of CA certificates, read by verify-full and by nothing else, so that a CA file left beside require cannot quietly make it stronger or weaker than it says. A TlsCaFile that verify-full would read and that is not there refuses the connection (HM7531) rather than falling back to the system's store. Any other word in TlsMode is refused without an attempt (HM7530); verify-ca is refused by name, and PostgreSQL's own PostgreSQLSslMode=verify-ca remains for PostgreSQL. The keys are read at the service's start. Backend Before 6.3.4 Since 6.3.4, with nothing set MySQL / MariaDB, Windows and Linux Plaintext, always TLS whenever offered, unchecked; plaintext when not PostgreSQL, both platforms libpq's default, prefer Unchanged: libpq's prefer, or a PGSSLMODE in the service's environment, stands. TlsMode and TlsCaFile become sslmode and sslrootcert unless PostgreSQLSslMode and PostgreSQLSslRootCert are set, which win. verify-full with no TlsCaFile is sslrootcert=system on Linux (libpq 16 and later), and libpq's own default file on Windows, %APPDATA%\postgresql\root.crt of the service's account SQL Server, Windows The login encrypted and nothing after it, unless the server forced encryption The whole session encrypted through MSOLEDBSQL, when version 18 or later is installed, and through SQLNCLI for a failover partner; through SQLOLEDB only the login, unless TlsMode is require or verify-full SQL Server, Linux Refused: ADO is Windows (HM6390) Unchanged SQL Server Compact A file on this machine Unchanged On MySQL and MariaDB the old AllowUnencryptedConnection is still read and changes nothing: what it described, TLS preferred and plaintext allowed, is the default now. On SQL Server verify-full checks against the Windows certificate store; TlsCaFile is not read (HM7532), and ConnectionStringOptions naming encryption beside a TlsMode is refused (HM7533). Seeing it. The application log says how the connection is protected, once the service has started and again whenever it changes, for example Database connection: PostgreSQL: NOT encrypted: the server offered no TLS (TlsMode prefer, the default); the diagnostics run on the Diagnostics pages carries the same line under Server details. At the service's start a connection refused for TLS is the connect failure it always was, HM4354, with the database client's own reason and a sentence naming the setting to change. --check-database makes one connection with the TLS settings in the configuration, says what it got, and exits 0 connected, 1 not, 2 not configured. It changes nothing, so it runs beside a running service: hmailserver --config /etc/hmailserver/hMailServer.ini --check-database hMailServer.exe --check-database --config C:\hMailServer\try.ini What the server does not do for you. It cannot switch TLS on at the database server or give it a certificate. Under verify-full a MariaDB 11.4 server's own self-signed certificate is accepted without a CA file when the account's password proves it (MariaDB's zero-configuration TLS, for mysql_native_password, ed25519 and parsec accounts), and not once a CA file is named. A database that must refuse a plaintext connection should say so itself: REQUIRE SSL on the MySQL account, hostssl only in pg_hba.conf, Force Encryption on SQL Server. What is thinner than it could be. There is no editor for these keys in the Control Panel or the Deck. require on the MariaDB client is checked after the login, and an unencrypted connection is closed before a statement crosses it: by then a challenge and a response have crossed in the clear for mysql_native_password, caching_sha2_password and ed25519 accounts, and the password itself for an account that signs in through PAM or the clear-text plugin. verify-full refuses before the login. require protects against someone who can listen and not against someone who can alter the traffic, and prefer connects without TLS to a server that seems to offer none; only verify-full protects against someone on the path. And only a login with VIEW SERVER STATE lets SQL Server say whether its session is encrypted. Moving off the built-in database There is no migration tool. Upgrading.md records that as a gap; MigratingDatabaseBackend.md says it is not an omission, because backup and restore are already backend-agnostic: back up, repoint, restore. It works because the archive's XML carries no identity values, and the message store is addressed by domain name, local part and the file's own guid, never by id. Your mail files never move. Before you start. Write down how many domains, accounts, aliases and distribution lists there are, to verify against afterwards. Take a filesystem-level backup of the whole installation. Backup refuses to run if any message row points outside DataFolder ("All messages are not located in the data folder"). The restore refuses an archive taken by a newer hMailServer, so migrate on one version, then upgrade. Keep a copy of hMailServer.ini; the administrator password lives in it, so it carries over. And any mail delivered between the backup and the cutover is lost to the new database: hold inbound traffic, or accept the gap knowingly. Stop the service (Stop-Service hMailServer). Restart it for the backup itself, which runs through the running server, but stop accepting new mail first if you can. Take the backup on Maintenance → Backup & restore with all three of Back up domains, Back up messages and Back up settings. The archive lands as HMBackup <local time>.7z. All three are required: the restore refuses a combination that would delete something and put nothing back, and says so before it changes anything. Set BackupMessagesDBOnly to 1 before both the backup and the restore. Since 6.3.4 it is a stored setting that applies when it is saved, with no restart: set it in the Control Panel, over REST (PUT /api/v1/settings/ini/BackupMessagesDBOnly with 1) or with hmctl ini set BackupMessagesDBOnly 1. On 6.3.3 it was a [Settings] line in the file, read at start, so restart there. Without it, a normal restore empties the live data directory and copies the staged message store into it; with it that step is skipped and your .eml files are never touched. Remove it afterwards. Stop the service again (Stop-Service hMailServer) and wait for it to stop. Create the new database: empty, with a login that may create tables; on MySQL or MariaDB, utf8mb4. Edit [Database] to point at it, with Internal=0. If the service runs as NT SERVICE\hMailServer, move it to LocalSystem first (hMailServer.exe --service-account localsystem /Register) and back once the migration is verified: under the virtual account the COM methods DBSetupQuick.exe calls are refused before anything is made. Then: & "C:\Program Files\hMailServer\Bin\DBSetupQuick.exe" if ($LASTEXITCODE -ne 0) { throw "Database setup failed with exit code $LASTEXITCODE" } On MySQL and SQL Server Compact a creation that fails halfway leaves a partly built database: drop it and start this step again. Restore. Start the service (Start-Service hMailServer). At its first start against the new database the server adopts the file's [Settings] section into the empty store. Before you restore, check BackupMessagesDBOnly on the new database (hmctl ini get BackupMessagesDBOnly, or the Control Panel). The setting has to be identical for the backup and the restore, and the restore runs against the new database, which has a settings store of its own. If it is not 1 there, set it again, which applies when it is saved. That is the case when the old server could not write its line into the file, which it reported as HM5806 or HM5803, and under NT SERVICE\hMailServer, which does not write the file's copy. A restore without it is a normal one, which empties the data directory. Then: $app = New-Object -ComObject hMailServer.Application $app.Authenticate("Administrator", "<password>") $backup = $app.BackupManager.LoadBackup("D:\hmail-migration\HMBackup 2026-08-13 220511.7z") $backup.RestoreDomains = $true $backup.RestoreMessages = $true $backup.RestoreSettings = $true $backup.StartRestore() The server restarts itself at the end; that is expected. Verify all of these, because a partial restore looks like a working server: the domain, account, alias and list counts, against what you wrote down before you started; the folder list over IMAP as a real account; one whole message body; certificates, ports, IP ranges, routes and rules; one message in and one out; and an empty ERROR_hmailserver_<date>.log. Rolling back is exact until you delete the old database, which was only ever read: stop the service, put the original [Database] section back, start the service. Since 6.3.4 the settings come back with it, and a setting changed since the cutover is named as HM5804 on that start, which is the rollback working, not a fault. Ids change, so a script that recorded hm_accounts.accountid will break; key on the address instead. What it does not cover, and where to read more Moving the data folder to different storage is a separate operation, Relocating an installation to another drive; the path is [Directories] DataFolder. Moving to a new machine is Moving to new hardware, and why Windows to Linux is a rebuild. What the backup archive holds, and what it does not, is in Backup and restore. Sources: the wiki's Before You Install, Migrating the Database Backend and Installing on Linux; MigratingDatabaseBackend.md, DatabaseTls.md and Upgrading.md; and sections 3.5 and 33.3 of the public documentation.
  • 0 Votes
    1 Posts
    103 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) signs a message as a hosted domain only when the server is entitled to: mail from outside whose From names a domain hosted here is no longer signed with that domain's DKIM key, and an authenticated submission is signed as another hosted domain only when its account may send as the address its From names (CVE-2026-107567); the upgrade stops the signing, and only rotating the domain's key ends the signatures already made. A forward of outside mail whose From names the forwarding domain's own organisation is given an SRS or rewritten envelope sender only when the message passed DMARC for it on arrival; with SRS on, mail to an SRS address this server handed out is held to the relay rules unless it is a bounce; and an ARC seal this server makes attests its own SPF result and names the domains its results are for. See the 6.3.7 announcement. Getting mail accepted is mostly DNS. hMailServer does the cryptography and the protocol work. It cannot publish records for you. This is the split. What the server does on its own DKIM signing. Open the domain's DKIM tab in the Control Panel and click Generate key. It creates an RSA-2048 key, saves the private key, fills in the path and shows the exact TXT record with a Copy button. Once the record is published it signs the domain's outgoing mail - a signed-in account's submission and every copy of it, a message the server makes itself, and mail an IP range lets in as one of this server's domains - but since 6.3.7 not mail from outside whose From names the domain: such mail leaves with the signature it came with. Since 6.3.7 an authenticated submission is signed as another hosted domain only when its account may send as the address its From names (CVE-2026-107567). Ed25519 DKIM (RFC 8463) is supported alongside RSA, for shorter keys. Since 6.3.4 a domain's DKIM keys can also rotate on a policy, off by default. DkimOversignHeaders over-signs the header fields you name. DkimAcceptSha1=0 refuses rsa-sha1 signatures on inbound mail. SPF and DMARC inbound. SPF is checked as part of anti-spam scoring, with SpfVoidLookupLimit=2. Since 6.3.4 it follows RFC 7208: mail from 127.0.0.0/8 and ::1 no longer passes unconditionally, so a content filter that hands mail back should authenticate, or connect from an IP range with anti-spam off. DMARC policy is evaluated on inbound mail and the DMARCbis tree walk is on by default (DmarcTreeWalkEnabled=1). Aggregate reporting stays off until you set DmarcRptFromAddress. Outbound transport security. MtaStsEnabled=1, DaneEnforcementEnabled=1 and DnssecValidationEnabled=1 are all on by default, under TLS & certificates → Transport security. A recipient whose DNSSEC chain is bogus does not receive your mail rather than receiving it unencrypted. That is the point of enforcement, so read the logs before you reach for the switch. TLS-RPT. Off until you set TlsRptFromAddress. After that the server sends daily reports to recipient domains about TLS failures it hit delivering to them. SRS and BATV. Off by default. If you forward mail, set SRSEnabled=1 and a SRSSecret to keep forwarded mail SPF-aligned. The secret must be stable. Outstanding SRS addresses remain valid for 21 days, so changing it loses bounces for mail you forwarded recently. BATV (prvs) signs your envelope sender so you can recognise and drop backscatter. Neither ever became an RFC. Both follow the widely deployed drafts, as does everything that interoperates with them. What you must publish yourself PTR, and your ISP sets that one, not you. Missing PTR is the single biggest cause of spam-foldering. Then SPF, the DKIM TXT record, and DMARC. For MTA-STS on the inbound side, point mta-sts.<domain> at the server, set MtaStsHostingEnabled=1 and WebServicesHttpsPort=443, and publish the _mta-sts.<domain> TXT record. If you publish TLSA records for inbound DANE, keep AcmeReuseKey=1 so renewals do not invalidate them. Start DMARC at p=none. Read the reports for a few weeks and confirm all your legitimate mail passes SPF and DKIM before moving to p=quarantine, then p=reject. Starting at p=reject silently loses mail from the sender you had forgotten about. Still incomplete ARC sealing (RFC 8617) covers mail from hosted DKIM-enabled domains. Relayed third-party mail is not sealed yet. Chapter 8, the DNS records with example syntax: https://www.progressiverobot.com/hmailserver-documentation/#8-making-the-internet-trust-you-dns Chapter 9.4, outbound transport security: https://www.progressiverobot.com/hmailserver-documentation/#94-outbound-transport-security Chapter 17.6, SRS and BATV: https://www.progressiverobot.com/hmailserver-documentation/#176-srs-and-batv
  • 0 Votes
    1 Posts
    111 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) fixes the ACME HTTP-01 challenge responder, which served one connection at a time, so that idle clients could make a certificate's issuance or renewal fail in every earlier version (work item 97): it now serves up to 64 connections at once and closes one past that, though a client that holds 64 idle connections open through a whole validation can still make it fail. In 6.3.7's Control Panel the SSL certificates page is TLS certificates, and Ctrl+K takes the old title to it. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) no longer leaves a host added to AcmeDomains waiting for the renewal (work item 98): a certificate that does not name every host on the list is due at once, a save of the list in the running server starts the check (a value hmailserver --set-setting stores is read at the next reinitialise or start), and GET /api/v1/acme, POST /api/v1/acme/run and the Control Deck's Certificates page give the certificate's state and ask for a check, the CA's rate limits kept. Where the list holds a host the certificate lacks, the first check after the upgrade orders a certificate for every host on it, and a host the CA cannot validate fails the whole order, at most three times an hour, and holds the renewal for the other hosts: take such a host off the list before upgrading. A wildcard, an IP address or another name no certificate can carry is left out of the check and the order and named in the application log. An IP range of your own that allows some addresses none of SMTP, POP3 and IMAP now closes the web services listener to them, and with it ACME's http-01 validation where that listener answers it, so check that the certificate authority does not come from one; and on Windows an AcmeCertificateDirectory whose path holds characters outside ASCII may be refused with HM7536, and then the client reads and writes nothing there and orders no certificate: give it, and the folders above it, an ASCII name before upgrading. See the 6.3.8 announcement. hMailServer has an ACME v2 client built in (RFC 8555). It obtains a free certificate, installs it, assigns it to your TLS ports, renews it before expiry and reloads it. No restart, no scheduled task, no external client. Configure it under TLS & certificates → Certificates (ACME), over REST with PUT /api/v1/settings/ini/{name}, or on Linux with sudo hmailserver --set-setting NAME=VALUE. Since 6.3.4 these are database settings, not lines to edit in hMailServer.INI: a line edited there is named in the error log (HM5804) and not used, and is put back where the service may write the file. The values: AcmeEnabled=1 [email protected] AcmeDomains=mail.yourcompany.com,mta-sts.yourcompany.com,autoconfig.yourcompany.com AcmeHttpPort=80 AcmeReuseKey=1 AcmeEnabled ships as 0, and a change to it takes effect at a reinitialise (Apply now), not at the save. AcmeDirectoryUrl defaults to https://acme-v02.api.letsencrypt.org/directory. Two requirements. Port 80 must be reachable from the internet, because that is how the http-01 challenge proves you control the name. Every name in AcmeDomains must already resolve to this server before you switch it on. If you also host MTA-STS or client autoconfiguration, put mta-sts.<domain>, autoconfig.<domain> and autodiscover.<domain> in AcmeDomains so one certificate covers them. After issuance. The server creates or updates a certificate record named ACME (automatic) and assigns it to every TLS-enabled listener that has none of its own, and since 6.3.4 to every listener still holding the self-signed certificate a new installation is given, logging each assignment. A listener the assignment could not be saved for is reported as a High 6100 rather than logged as a success, because a port left without a certificate accepts no TLS at all. That is the error to alert on. Renewal. The renewal window is two thirds of the certificate lifetime, plus ARI. That is why there is nothing left to schedule. Files live in Data\ACME on Windows and /var/lib/hmailserver/ACME/ on Linux: the account key, fullchain.pem and privkey.pem. Back that directory up with the rest of Data\. If a restore misses it the client re-issues, but the new key invalidates any TLSA records you have published. Leave AcmeReuseKey=1. Keeping the same private key across renewals is what keeps published TLSA records valid. Linux. Port 80 works without root. CAP_NET_BIND_SERVICE on the unit is what allows the bind. The default certificate directory is <DataFolder>/ACME, which is inside ReadWritePaths. Point AcmeCertificateDirectory elsewhere and you must add that path to ReadWritePaths with a drop-in, or issuance fails on a read-only filesystem. If it never worked for you. The built-in client had never once succeeded against real Let's Encrypt until 6.2.24 (GitHub issue 34). Boulder pretty-prints its JSON and the challenge locator searched for the compact spelling. 6.2.25 then fixed issuance and renewal ending the process (GitHub issue 93). If you gave up on this on an older build, it is worth another look. Another CA. Leave AcmeEnabled=0, add the PEM certificate and key under TLS & certificates → SSL certificates, then assign it to ports under Connections & protocols → TCP/IP ports. On Linux, an external client that replaces the PEM files in place needs POST /api/v1/server/reinitialize or a restart before the new files are served. Chapter 9, encryption and certificates: https://www.progressiverobot.com/hmailserver-documentation/#9-encryption-and-certificates Report problems: https://gitlab.com/hmailserver/hmailserver, or without a GitLab account by email to the project's Service Desk address, contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com
  • 0 Votes
    1 Posts
    52 Views
    ProgressiverobotP
    ACME issuance and renewal ended the hMailServer process (GitHub issue 93). Anyone running automatic certificates on 6.2.24 lost the service on every issuance and every renewal. Two calls in the ACME client handed the OpenSSL DLL a FILE* opened by the server's own C runtime: the DANE TLSA line logged straight after issuance, and the re-read of the existing private key at the start of every renewal (the default, AcmeReuseKey). With no OPENSSL_Applink export in the executable, OpenSSL does not return an error. It writes OPENSSL_Uplink(...): no OPENSSL_Applink to the Windows Application log under the source "OpenSSL" and calls TerminateProcess. The symptoms: an OpenSSL event whose message looks blank, a 7031 from the service control manager in the same second, no crash dump, and no "ACME (automatic)" certificate record. Both calls now go through OpenSSL's own file I/O, and the deployment runs before the TLSA line. If 6.2.24 issued you a certificate before it died, the files under Data\ACME are valid. 6.2.25 deploys them at its first ACME check after start-up and logs "issued but never deployed". What the upgrade involved The schema moves from 6025 to 6030 in five steps, one way. DBUpdater runs them in order and resumes from a partial upgrade; there is no downgrade. The 6029 to 6030 step adds seventeen FOREIGN KEY constraints with ON DELETE CASCADE and removes the orphan rows they would refuse. On a large database it reads every child table once, so plan for it like an index build. If you are on 6.2.21 or a 6.2.22/6.2.23 pre-release, read the 6.2.24 notes first. Everything there still applies. Behaviour that changes without a switch The Apple .mobileconfig profile is served over HTTPS only. Plain HTTP gets a 301 to the WebServicesHttpsPort listener, or a 403 when none is configured. A TLS-terminating proxy must send X-Forwarded-Proto: https. A Message-ID is added only to submissions (upstream #552). Relayed mail keeps its headers, so a filter counting on the header will now see messages without one. IMAP sequence numbers are stable within a session (upstream #602). Another session's expunge no longer renumbers a client's messages under it. Also fixed: two restarts at once, one over COM and one from an ACME deployment or backup restore, rebuilt the same queues under each other and could end in an access violation. Restarts now run in sequence. Full release notes, checksums and signatures
  • 0 Votes
    1 Posts
    58 Views
    ProgressiverobotP
    This was the first pre-release in the 6.2.22 line. Everything in it shipped in 6.2.24. Nothing here should be installed today. The built-in ACME client has never completed an order against a real Let's Encrypt. If you issued certificates through hMailServer rather than an external client, it failed every time. That is fixed here (GitHub issue 34). What the upgrade involved This is a pre-release. Take a backup before installing it. Databases upgrade automatically from schema 6012 to 6014 via the bundled DBUpdater. ACME (GitHub issue 34) Boulder, the software behind Let's Encrypt staging and production, pretty-prints its JSON: "type": "http-01", with a space after the colon. The challenge locator searched for the compact form, "type":"http-01", which can never match a real response. Every issuance failed at the first authorization with "Authorization offers no http-01 challenge". The locator now tolerates any whitespace and checks that the string is genuinely a type key's value, and the real CA response shape is pinned by self-tests. Failure messages now name the domain they concern, and the no-challenge refusal includes the CA's actual response. Log lines going missing (GitHub issue 33) With Keep log files open enabled, each line sat in a ~4 KB write buffer until later lines pushed it out. On a quiet server the tail of a session stayed invisible; the reporter measured forty minutes. The logger now hands every line to the operating system as it is written. Installations without the setting were never affected. Microsoft 365 Microsoft shuts off Basic authentication for SMTP relay in December 2026. XOAUTH2 bearer login is implemented for outbound relay and for POP3 collection from external accounts. Tokens use the client-credentials flow and are cached to 80% of their lifetime. Bearer auth applies only to destinations on the configured OAuth host list; unlisted relays keep password login unchanged. POP3 collection has no password fallback, deliberately. Sieve and IMAP Sieve now implements variables (RFC 5229), editheader (5293), duplicate (7352), reject/ereject (5429), include (6609), enotify (5435), date/currentdate (5260), spamtest (5235), mailboxexists and :regex. ManageSieve gains RENAMESCRIPT and machine-readable response codes. Eleven IMAP extensions, including APPENDLIMIT (7889), LITERAL- (7888), LIST-STATUS (5819), UNAUTHENTICATE (8437), QUOTA=RES-STORAGE (9208), PREVIEW (8970), MULTIAPPEND (3502), REPLACE (8508), SAVEDATE (8514, schema 6013) and METADATA (5464, schema 6014). The delivery client now opens with EHLO on every delivery, not just routes needing TLS or authentication, and declares SIZE (RFC 1870). Still unimplemented: BINARYMIME, IMAP BINARY, CATENATE/URLAUTH, COMPRESS=DEFLATE, OBJECTID and outbound PIPELINING/CHUNKING. Equal-preference MX records are still not randomised (RFC 5321 5.1). Full release notes, checksums and signatures
  • 0 Votes
    1 Posts
    55 Views
    ProgressiverobotP
    6.0.0 is the first modernised release of the fork: 64-bit only, built with the VS2026 toolchain, with OpenSSL 4 and TLS 1.2 and 1.3 underneath. Build 4 is the production build, and it fixes the installer defect that made build 3 unusable on fresh installations. What the upgrade involved 6.0 was 64-bit only. The installer dropped the obsolete 2015-era UCRT, which had been included in Windows since Server 2016 / Windows 10. Upgrade notes and the full feature list are in the README. The installer fix Build 3 bundled VS2019-era VC++ runtime DLLs. The service loaded those instead of the current ones and crashed at startup with an access violation in msvcp140.dll on fresh installations. Build 4 ships the matching v145 (14.51) runtimes. Build 3 also fixed MySQL/MariaDB database creation, which was a script line-ending problem, and bundles libmysql.dll. What 6.0 brought OpenSSL 4, TLS 1.2 and 1.3 MTA-STS and DANE, with in-process DNSSEC validation ARC sealing and Ed25519 DKIM ACME (Let's Encrypt) built in REST admin API and Prometheus metrics IMAP MOVE, ID and SPECIAL-USE PBKDF2 password hashing The release was validated by the full regression suite: 898 of 898 tests passing, zero failures, run against a MariaDB (MySQL backend) instance, including live SpamAssassin, ClamAV with real EICAR detection, DMARC against live DNS, and TLS 1.2 and 1.3 end to end. Full release notes, checksums and signatures
  • 0 Votes
    1 Posts
    53 Views
    ProgressiverobotP
    This was a beta build of the 6.0 line. 6.0.0 was the production release. Nothing here should be installed today. This is the first build of the modernised 6.0 line to clear the full regression suite: 898 tests, 895 passed, 0 code failures. What the upgrade involved Database version was 6001. Upgrade from 5.7 was supported via DBUpdater. Toolchain for this build: VS2026 (v145), OpenSSL 4.0.1, Boost 1.91, PostgreSQL 18.3 libpq, .NET Framework 4.8.1. What 6.0 brings in: MTA-STS and DANE/DNSSEC outbound TLS policy, TLS-RPT reporting, TLS 1.2/1.3 defaults. ARC sealing, DMARC, Ed25519 DKIM. ACME (Let's Encrypt) with auto-apply. REST admin API, and an Administrator dashboard with TOTP 2FA. MTA-STS, autoconfig and autodiscover hosting. Prometheus metrics and JSON logging. PBKDF2 password hashing with transparent rehash. IMAP MOVE, ID and SPECIAL-USE, plus 8BITMIME. Fixed in this build: a latent CStdStr::FormatV buffer-count bug that crashed the service. Validation ran with live SpamAssassin (JAM Windows build), ClamAV 1.5.2 and all optional features enabled. The three non-passing tests are environment-bound rather than code defects. Two require an unfiltered DNS resolver for SURBL test queries, and one requires the service-based SpamAssassin variant. Installation is via the installer, against the internal database (SQL CE 4.0) or an external MySQL, MSSQL or PostgreSQL instance. IMPLEMENTATION-NOTES.md carries the feature and INI reference. Licensed AGPLv3. Based on hMailServer by Martin Knafve. Full release notes, checksums and signatures