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
    163 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.6 (6 October 2026) publishes the first container image, registry.gitlab.com/hmailserver/hmailserver:6.3.6, and a monitor that matches "The response carried no HTTP headers." needs the new sentence, which says who ended the connection, after how many bytes and seconds. See the 6.3.6 announcement. hMailServer 6.3.7 (17 October 2026) publishes registry.gitlab.com/hmailserver/hmailserver:6.3.7, tagged 6.3 and latest until 6.3.8; renames the Control Panel page that holds the metrics listener's settings from API & monitoring to Monitoring & alerts, with the alerts' settings, the outbound webhooks and the SNMP agent on it (Ctrl+K takes the old title to it); and refuses GET /api/v1/metrics/history, and the same history over COM, to a credential confined to named domains, such as a domain administrator, the auditor role given a list of domains or an API key issued for named domains. With IMAP and POP3 now taking the client's address from a PROXY protocol header as SMTP does, the High Availability Runbook says that a balancer's /readyz check must not carry that header: the metrics listener reads none and answers a request behind one 404. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) publishes registry.gitlab.com/hmailserver/hmailserver:6.3.8, also tagged 6.3 and latest. See the 6.3.8 announcement. This topic covers what hMailServer 6.3.4 offers a monitoring system: the metrics listener's Prometheus exposition and health probes, the repository's Grafana dashboard, the OpenTelemetry push, the server's own metric history, the new SNMPv3 agent and, briefly, the daily block-list check of the server's own addresses. The one thing to know first: the metrics listener is off until you give it a port, and once it is on, the health check to configure is /readyz, because it is proved by a real database round trip rather than by a flag. The metrics listener One small HTTP listener serves both the exposition and the probes. Its seven settings are [Settings] keys, set on the Control Panel's Monitoring & troubleshooting → API & monitoring page (Monitoring & alerts since 6.3.7; Windows only), with PUT /api/v1/settings/ini/{name}, with hmctl ini set, or, on a Linux server whose REST listener is not yet on, with hmailserver --set-setting NAME=VALUE; the topic on settings explains where they are stored and when a save applies. All seven are taken when the listener starts, the three credential settings included, so a saved change waits for a reinitialise (POST /api/v1/server/reinitialize, or Apply now in the Control Panel or the Deck) or a service restart. A token that has been saved and not applied leaves /metrics answering as it did before. Setting Default Effect When it is wrong MetricsServerPort 0 0 means the listener is never created No port, no probes, no metrics, and no complaint anywhere MetricsServerBindAddress 127.0.0.1 An IP literal only, parsed with inet_pton: 0.0.0.0, 127.0.0.1, :: or a specific IPv6 address A host name or localhost is rejected: the log says MetricsServer: Invalid bind address, the listener does not start and the probes go with it MetricsServerAuthToken empty Sent by the scraper as Authorization: Bearer <token> Absent on a non-loopback bind: /metrics answers 503, not 401, and the body names the settings that would open it. The probes keep answering MetricsServerAuthUsername and MetricsServerAuthPassword empty HTTP Basic; with both schemes configured, either satisfies the check As above MetricsServerCertificateFile and MetricsServerPrivateKeyFile empty Both set makes the whole port HTTPS, probes included Set but unusable: the listener still serves the probes in the clear and /metrics answers 503 :: serves both address families; a specific IPv6 literal serves IPv6 only. "Loopback" means the whole of 127.0.0.0/8 and ::1. The probes are answered before any branch that can refuse. A Kubernetes httpGet probe, an ELB or ALB health check and a Docker HEALTHCHECK have nowhere to keep a bearer token. Refused credentials on /metrics are counted in hmailserver_metrics_unauthorized_requests_total rather than logged. Nothing on the request path touches the database or the disk. A refresher thread probes the database every 5 s (every 30 s after a failure), refreshes the delivery-queue aggregates at most every 10 s and only when a scrape has asked for them, and re-reads the certificate files every 5 minutes. One exception since 6.3.4: the two message-store gauges are cached for an hour and recomputed by whichever caller finds them stale, so about once an hour a scrape can run select sum(messagesize), count(*) from hm_messages on the listener thread itself: on a large store, a slow answer to that scrape and to any probe queued behind it. It is a single accept loop serving one connection at a time. A request's header read is bounded at 5 s and a response write at 15 s. For a load balancer's check the High Availability Runbook gives an interval of 5 s and a timeout of 2 s; never point a sub-second health check at it. The three health probes No credential in any configuration; HTTP/1.0 responses that close the connection. Path 200 when Body Use it for /livez Always, if the listener answered; never 503 alive Liveness restarts. It checks nothing on purpose: a database outage must not get a healthy process killed /readyz State is Running and the database has proved within the last 20 s that it can answer; 503 otherwise ready, or not ready: <reason> Load-balancer and VIP routing /healthz The same conditions {"status":"ok\|unavailable","state":"...","database":"up\|down","uptime_seconds":N} Dashboards and human debugging Readiness is four conditions in order, each with its own reason: not ready: server not in running state; not ready: database not connected; not ready: database did not answer the last readiness probe; and not ready: database readiness probe has not completed recently. The last exists because a probe against a database that has stopped answering can hang for as long as DBConnectionAcquireTimeout allows, 60 s by default. Since 6.3.4 a server in maintenance mode answers 503 not ready: draining: POST /api/v1/server/drain enters it, GET reports {"draining","since","sessions","deliveries","drained"}, and DELETE leaves it. Start-up is a refused connection, not a 503: the listener is brought up only after the state has gone to Running. Shutdown is a 503 first, then a refusal: the state goes to Stopping before anything is torn down, so /readyz reports 503 while ShutdownDrainSeconds runs. The High Availability Runbook builds its active/passive design on this: both nodes have the listener on, with MetricsServerBindAddress=0.0.0.0 and a MetricsServerAuthToken, and the VIP is gated strictly on /readyz. The passive node's service is stopped, so its /readyz is refused and it is never routed to. Since the probes need no credential, a missing token shows as missing dashboards, not as a failed failover. Every metric family Thirty-eight metric families in 6.3.4 (thirty in 6.3.3). Counters are monotonic since server start; a restart resets them, which is what hmailserver_start_time_seconds is for. Mail flow Metric Type Meaning, and the trap in it hmailserver_processed_messages_total counter One per delivery pass started, not per message received: a message deferred three times and then delivered counts four hmailserver_messages_delivered_total counter Delivery passes that completed with no failed recipient hmailserver_messages_deferred_total counter Passes rescheduled: a temporary failure, greylisting, or a message the virus scanner could not examine (AVFailAction=1) hmailserver_messages_bounced_total counter Delivery-failure notifications queued to the sender; one can cover several recipients hmailserver_delivery_queue_messages gauge Rows in hm_messages in the delivering state hmailserver_delivery_queue_oldest_message_age_seconds gauge Age of the oldest queued message; 0 when the queue is empty. Alert on this, not on depth hmailserver_spam_messages_total, hmailserver_viruses_removed_total counter Messages classified as spam; viruses removed during delivery preprocessing hmailserver_store_bytes, hmailserver_store_messages gauge Since 6.3.4. The size and the number of the messages in hm_messages, recomputed at most once an hour hmailserver_domain_messages_received_total{domain}, hmailserver_domain_messages_sent_total{domain} counter Only present when MetricsPerDomainEnabled=1; only hosted domains are ever labelled Added in 6.3.4. hmailserver_country_blocked_total and hmailserver_country_blocked_by_country_total{country} count connections and sign-ins refused by a country policy, and hmailserver_spam_trap_hits_total counts hits on a declared spam trap. Three gauges cover the feedback loops' seven-day window: hmailserver_domain_complaints{domain}, hmailserver_domain_complaint_window_messages{domain} and hmailserver_domain_complaint_rate{domain}. They are present only when MetricsPerDomainEnabled is set and complaint processing is on. The rate gauge is a ratio, not parts per million: Complaints.md alerts on hmailserver_domain_complaint_rate > 0.001. Parts per million is the unit of the complaints.rate.high alert rule's threshold, so an expression written in parts per million against the gauge would never fire. Sessions, authentication and TLS Metric Type Meaning hmailserver_sessions{protocol} gauge Concurrent sessions; protocol is smtp, imap or pop3 hmailserver_auth_success_total, hmailserver_auth_failures_total counter A rising failure rate against a flat success rate is a password-guessing run hmailserver_tls_handshakes_total, hmailserver_tls_handshake_failures_total counter The mail protocols only; a failed handshake on the metrics port is never counted here hmailserver_tls_certificate_expiry_seconds{certificate} gauge notAfter as a Unix timestamp per configured certificate, re-read every 5 minutes; at most 128 series. Absent entirely when no certificate is configured. With no certificate row, the ACME certificate is labelled ACME (automatic) hmailserver_metrics_unauthorized_requests_total counter Always 0 when no credential is configured The server and the database Metric Type Meaning hmailserver_start_time_seconds gauge Uptime is time() - hmailserver_start_time_seconds hmailserver_build_info{version,architecture,database_schema_version} gauge Always 1; the information is in the labels hmailserver_state{state} gauge One series per state (unknown, stopped, starting, running, stopping and, since 6.3.4, draining), exactly one of which is 1 hmailserver_workqueue_depth{queue}, hmailserver_workqueue_blocking_tasks_waiting{queue} gauge Seven fixed queue values: Server queue, Maintenance queue, Asynchronous task queue, Name lookup queue, IOCPQueue, SMTP delivery queue, External fetch queue. Asynchronous task queue sends the final 250, so depth there is mail waiting to be acknowledged hmailserver_messagestore_missing_files gauge Always 0 unless MessageStoreConsistencyCheck=1 hmailserver_command_processing_seconds, hmailserver_db_query_seconds histogram Bucket bounds from 0.0001 s to 1.0 s, and from 0.001 s to 10.0 s; both emit the full _bucket, _sum and _count set hmailserver_database_connected gauge 1 when the database answered a real round trip recently hmailserver_database_probe_success_timestamp_seconds, hmailserver_database_probe_age_seconds gauge When it last answered, and how long ago on the server's own clock; /readyz reports 503 once the age passes 20 s hmailserver_db_connections{state} gauge busy and available; available pinned at zero is saturation hmailserver_db_slow_queries_total counter Statements slower than SlowQueryLogMilliseconds; 0 while that setting is 0 Scraping it scrape_configs: - job_name: hmailserver scrape_interval: 30s scrape_timeout: 10s metrics_path: /metrics authorization: credentials: "<the value of MetricsServerAuthToken>" static_configs: - targets: ["mail.example.com:9100"] Use scheme: https when the certificate pair is set, and keep scrape_timeout well under scrape_interval. The wiki gives nine alerting rules against these metrics, among them hmailserver_database_connected == 0 for 2m, hmailserver_delivery_queue_oldest_message_age_seconds > 3600 for 15m and hmailserver_tls_certificate_expiry_seconds - time() < 14 * 86400. The Grafana dashboard The repository ships hmailserver/docs/grafana-dashboard.json. Import it as it is and pick your Prometheus data source; the instance variable, from label_values(hmailserver_build_info, instance), selects the server. Its uid is hmailserver-overview; it refreshes every 1m and opens on now-24h. Its panels array has 19 entries: four row headers, five stat panels and ten time series. Every rate is over [5m] and every query is filtered by instance=~"$instance". Health: server state (hmailserver_state == 1), database, uptime, missing message files, and TLS certificate expiry, amber below 21 days and red below 7. Mail flow: delivery outcomes (delivered, deferred and bounced, stacked), queue depth with the oldest message's age on a right-hand axis, messages processed with spam and viruses removed, and active sessions by protocol. Authentication and TLS: authentication outcomes and TLS handshakes, as rates. Latency and database: command processing and database query latency (p50, p95 and p99 by histogram_quantile over the two histograms' _bucket series), the connection pool by state, and slow queries. What the dashboard does not draw: the two database probe gauges, the two work-queue gauges, hmailserver_metrics_unauthorized_requests_total, the per-domain counters, the two store gauges and the six families added in 6.3.4. hmailserver_build_info is read by the instance variable and by no panel. The metric history inside the server The server keeps a short history of its own, so the Control Panel's dashboard can show 24 hours, 7 days and 30 days without Prometheus. MetricsHistoryDays (default 7; 0 switches it off) is the days kept in hm_metricsamples: one row per metric per minute, plus a sample at service start. Fourteen metrics are stored in 6.3.3, named as the exposition's without the hmailserver_ prefix and with the label folded in, such as sessions_smtp and processed_messages_total. 6.3.4 adds store_bytes and store_messages, whose history begins the day the server first samples them, and country_blocked_total and spam_trap_hits_total. Read it with GET /api/v1/metrics/history?metric=…&range=24h|7d|30d, or on Windows over COM with Utilities.GetMetricHistory. It has no labels and no histograms and is not a replacement for Prometheus, but it survives a restart. Pushing to an OpenTelemetry collector Three OTLP/HTTP signals, each a no-op until its own endpoint is set. The pushed metric names are the Prometheus names verbatim. Setting Default Effect OtelMetricsEndpoint empty OTLP metrics; the default path is /v1/metrics OtelMetricsInterval 60 Seconds between exports, clamped to 5–3600 OtelEndpoint empty OTLP traces only: one span per delivery attempt, joined to the trace named by the message's own traceparent; default path /v1/traces OtelLogsEndpoint empty OTLP logs: the same lines, categories and mask the log files get; default path /v1/logs What is pushed: the twelve counters that predate 6.3.4 and, since 6.3.4, hmailserver_spam_trap_hits_total, sessions as one gauge with a protocol attribute, hmailserver_messagestore_missing_files, and the per-domain counters when they are enabled. The histograms, the database and queue gauges and certificate expiry are scrape-only. On Linux, in every release from 6.3.0 to 6.3.3, a collector that accepted the connection and never answered held the exporter, and the stop that waits for it, for good; 6.3.4 fixes that. The SNMPv3 agent (since 6.3.4) For a monitoring system that speaks SNMP and not Prometheus or OpenTelemetry, 6.3.4 adds a read-only agent answering the mail MIB (RFC 2789, MTA-MIB) and the standard SNMP groups from the same counters /metrics serves; an object /metrics has no counter for is answered noSuchObject, not made up. It speaks SNMPv3 with the User-based Security Model for one user at authPriv only (HMAC-SHA-256 and AES-128), and SNMPv2c as an option that stays off even when the agent is on. SNMPv1 is not answered, a Set is answered noAccess, and nothing is sent unasked: no traps, no informs. It is off as shipped, and an empty allow list answers nobody. Setting (hm_settings) REST key Default Meaning SnmpEnabled snmp_enabled off The whole agent; off, no socket is opened SnmpPort snmp_port 161 The UDP port SnmpBindAddress snmp_bind_address empty, every address 127.0.0.1 for a collector on the same machine; otherwise an address on a management network SnmpAllowedAddresses snmp_allowed_addresses empty Addresses and CIDR ranges, at most 64. Name the managers one by one: UDP does not prove a source address SnmpV3User snmp_v3_user empty The one SNMPv3 user SnmpV3AuthPassword, SnmpV3PrivPassword snmp_v3_auth_password, snmp_v3_priv_password (write-only) empty 8 to 64 ASCII characters each SnmpV2cEnabled, SnmpV2cCommunity snmp_v2c_enabled, snmp_v2c_community (write-only) off, empty The community crosses the network in the clear; an answer is never more than eight times its request SnmpContact, SnmpLocation snmp_contact, snmp_location empty sysContact and sysLocation They are on /api/v1/settings (the server group), on the Control Deck's Settings page under Server and in hmctl settings. There is no COM property for any of them, and in 6.3.4 no Control Panel page, so Windows and Linux are configured the same way; since 6.3.7 they are the SNMP tab of the Control Panel's Monitoring & alerts page on Windows, which 6.3.5 and 6.3.6 had on Logging. They can also be given as HM_PROPERTY_SnmpEnabled and so on in the environment, which reaches the database only when hmailserver --apply-environment is run (hMailServer.exe --apply-environment on Windows): the service does not read the environment itself. Each setting is read when the agent starts, at the next service start or Reinitialize. The two passwords and the community are stored protected, never read back, and not carried by a backup: enter them again after a restore. Snmp.md's example keeps every password off the command line. On the server, for a manager at 192.0.2.10: $ umask 077 $ cat > snmp.json {"snmp_allowed_addresses": "192.0.2.10", "snmp_v3_user": "monitor", "snmp_v3_auth_password": "...", "snmp_v3_priv_password": "...", "snmp_contact": "[email protected]", "snmp_location": "Rack 4, London", "snmp_enabled": true} ^D $ hmctl settings set --from-json snmp.json $ rm snmp.json $ hmctl api POST /api/v1/server/reinitialize On the manager, with Net-SNMP 5.9 or later, defVersion 3, the user (defSecurityName), both passphrases, defSecurityLevel authPriv, defAuthType SHA-256 and defPrivType AES go in ~/.snmp/snmp.conf, readable by you alone (chmod 600); snmpwalk mail.example.com 1.3.6.1.2.1.28 then walks the mail MIB. What it serves. In the MTA-MIB mtaGroupTable has one row per protocol the server runs: 1 SMTP, 2 POP3, 3 IMAP. What has a counter is answered: mtaStoredMessages is the delivery queue's depth, mtaTransmittedMessages is hmailserver_messages_delivered_total, mtaGroupOldestMessageStored is the oldest queued message's age, and mtaGroupInboundAssociations is hmailserver_sessions for the protocol. mtaReceivedMessages is noSuchObject, because no counter of messages received exists, and so are the volume, recipient and conversion columns. Sixteen scalars at 1.3.6.1.4.1.32473.1.2.<n>.0 carry /metrics series directly, among them hmailserver_state as a number at 14 (3 is running, 5 draining) and the two delivery-queue gauges at 15 and 16. The enterprise number is a placeholder. 1.3.6.1.4.1.32473 is the number RFC 5612 reserves for documentation and examples; it is not registered to this project with IANA. When a number is registered, sysObjectID, the scalars and the engine ID's prefix move to it in one release, and a manager configured for these OIDs will need them changed. There is no MIB module file yet, for the same reason. When it does not start. Error 7420: the port could not be bound, because another program holds it (the operating system's own SNMP service, on Windows or Linux, listens on 161 when it is installed) or, on Linux outside the packaged unit, the process lacks CAP_NET_BIND_SERVICE; the server runs on without the agent. 7421: the engine's seed or boot count in hm_settings could not be read or written. 7422: an exception left the agent's thread; a Reinitialize starts it again. On Linux the packaged hmailserver.service has CAP_NET_BIND_SERVICE for port 25, which is what lets it bind 161. No container image of 6.3.4 is published; a container built from the repository's Dockerfile publishes the port as UDP (-p 161:161/udp) and needs a host name of its own, since two started with one fixed hostname are one SNMP engine. The daily block-list check, briefly Since 6.3.4 the server looks the addresses it sends from up on DNS block lists once a day and raises the reputation.listed alert when one is listed. The watch is on in a new installation and off after an upgrade, and is read at GET /api/v1/reputation; the topic on DKIM rotation, reports and the reputation watch has the lists, the addresses checked and what a failed lookup means. Where to read more Wiki: Monitoring and Health and High Availability Runbook Documents at v6.3.4: Snmp.md, HighAvailabilityRunbook.md, ReputationWatch.md, Complaints.md and the dashboard itself, grafana-dashboard.json The public documentation: 16.3 Prometheus metrics and health probes On this forum: Alerts, the audit trail, syslog and the journal, and per-domain reports, Reading the logs, and Warm standby and high availability for the pair the probes gate. See also The Control Deck and hMailServer on Linux: what you get and what you do not
  • 0 Votes
    1 Posts
    137 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) holds a rule and a Sieve script to their owner: a domain's or an account's rule may no longer set From, Sender, Resent-From, Resent-Sender, DKIM-Signature or the loop count, and its Reply goes only from an address its owner may send as; a Sieve script may no longer add or delete those fields, Auto-Submitted or the loop count, and its notify goes from the account's own address unless its :from reaches the account. One stored before the upgrade still runs without that action or edit, and the application log names it. A loop count that is not digits alone, or is above the limit, reads as the limit, and RuleLoopLimit 0 now stops out-of-office replies too. See the 6.3.7 announcement. hMailServer filters mail with two systems that both run during delivery and do not know about each other. Rules are the server's own engine: criteria and actions, edited in the Control Panel, and the only one of the two that can run server-wide. Sieve (RFC 5228) is the standard mail filtering language, owned by the mailbox user and edited from a mail client over ManageSieve or on the Control Panel. This topic covers the order they run in, rules and the Run function action, Sieve and vacation, the out-of-office policy, and what both see on sealed mail. It describes 6.3.4 (27 September 2026, schema 6104). The one thing to know first: they run at different points, they see the message at different stages, and when they disagree about where a message goes, Sieve wins. The order they run in The filtering pipeline for one inbound message: During the SMTP conversation, while the sender is still connected. The pre-transmission spam tests (SPF, DNSBL, HELO, greylisting) run at MAIL FROM and RCPT TO; the script event OnSMTPData runs at the DATA command, before any of the body is sent; once the body is in come the post-transmission tests (SURBL, DKIM, ARC, SpamAssassin). A message over the delete threshold is refused, with 550 when the verdict came before the body and with 554 after it; with quarantine on, one judged after the body is held in quarantine instead and the sender answered 250. Otherwise the script event OnAcceptMessage runs and the message is queued. On the delivery thread, once per message and on every delivery attempt: OnDeliveryStart, blocked attachments and the virus scan, the global rules, OnDeliverMessage, then DKIM signing and mirroring (first attempt only). The message is split into local and external recipients. External recipients go to relays, routes and MX delivery. For each local recipient, on that recipient's own copy: per-account spam overrides, the account auto-reply, the account rules, account forwarding, trace headers, the Sieve script, the domain-wide auto-reply (only if the account said nothing), Sieve reject then discard, Sieve flags, the folder decision, and the save to the mailbox. Because It follows that Spam scoring happens during the SMTP conversation A rule cannot make a message spam; it can only read the flag, or a header the filter wrote Virus scanning happens before global rules A rule never sees an infected message Global rules run on the shared message, before the split Delete e-mail in a global rule destroys the message for every recipient; in an account rule, only that recipient's copy Global rules re-run on every delivery attempt Forward, Create copy and Reply can fire more than once for a deferred message. Write Delivery attempts equals 1 to prevent it Sieve runs after account rules A Sieve fileinto overrides the folder a rule chose. A header a rule set, or a copy it queued, has already happened and Sieve cannot undo it What a rule is Rules in the Control Panel sidebar is the server-wide set ("Global rules"); each account has its own on the Rules tab of its account dialog. A rule is a name, an enabled flag, a match mode - Match ALL criteria (AND) or Match ANY criterion (OR) - an ordered list of criteria and an ordered list of actions. Criterion What is compared Note From, To, CC The From:, To: and Cc: headers Not the envelope. Mail that reached you by Bcc, an alias or a list has no To: naming you Body The plain-text body concatenated with the HTML body A phrase inside an attachment is not matched Message size The file's size in bytes 25 MB is 26214400 Recipient list Every SMTP envelope recipient, joined with ; The field that sees a Bcc, an alias expansion or a list member Delivery attempts The retry count plus one The first attempt is 1 Custom header The named header's value Name it without the colon Match types. equals, does not equal, contains and does not contain are case-insensitive. is less than and is greater than parse both sides as numbers, and a non-numeric string parses as 0. matches wildcard and matches regex require the whole value to match; a regex is also case-sensitive, so to find a word anywhere write .*invoice.*, and (?i).*invoice.* to ignore case. does not equal and does not contain are true when the field is empty: Subject does not contain [ok] -> Delete e-mail deletes every message that has no Subject at all. Add a positive criterion under match mode ALL when the action is Delete. Action What it does Delete e-mail Global: the message is dropped and Message will be deleted. Action triggered by a global rule (<name>) is logged. Account: only this recipient's copy Forward e-mail Queues a second message to the named address, loop-count guarded Reply Queues a new message to the envelope sender, stamped Auto-Submitted; never answers auto-submitted mail Move to IMAP folder Happens at the end of local delivery. INBOX.Archive; #Public.Name for a public folder, subject to the insert permission; %YEAR%, %MONTH% and %DAY% come from the server's local date. A missing folder is created Run script function Calls a function in the event script - next section Stop rule processing Stops the rule set after this rule's remaining actions have run Set header value Rewrites the message file; %MACRO_ORIGINAL_HEADER% is the header's current value Create copy Queues a duplicate carrying X-CopyRule: <rule name> Send using route, Bind to address Force a named route, or the local address the connection leaves from. Absent from an account's rules The Run function action, since 6.3.4 The action the Control Panel calls Run script function calls the named function in the event script as <function>(HMAILSERVER_MESSAGE), then reloads the message from disk, so a script that edits and saves is seen by the rest of the rule set. It needs scripting enabled. Since 6.3.4 the name must be a plain identifier: ASCII letters, digits, _ and $, not starting with a digit. On Windows, in either script language (VBScript or JScript), any other name is refused as HM5018 each time the rule fires. Before 6.3.4 the name was written into the call unescaped, in either language, so whoever may edit a rule, a domain's own administrator included, could run code in the server's process: that is CVE-2026-100741, in every release from 6.0.0 to 6.3.3, and event scripts and the 6.3.4 security fix has the flaw and what to do until a server is upgraded. On Linux the action works since 6.3.4, where event scripts are JavaScript. The Linux engine looks a handler up by name instead of building a call, so such a name is never evaluated and finds no function: nothing is run, and the ERROR log has a Script Error line, not HM5018. Since 6.3.7 such a name is refused when the rule is saved, over COM and REST, for a rule of every owner; and the action on a domain's or an account's rule is the server administrator's to set, since it reaches a function of the server's event script: a credential confined to named domains - such as a domain administrator, or an API key issued for its domains - is refused it. Evaluation: a matching rule does not stop the set For each active rule, in order, the criteria are evaluated under the match mode and, if they matched, every action runs. Only a Stop rule processing action ends the set, so make that the last action of every rule that should be terminal. A rule with no criteria runs no actions, in either match mode. A catch-all rule needs a criterion that is always true. If the message file cannot be loaded, the whole set is skipped and the message is delivered unfiltered, with HM6040 in the log, rather than run against an empty message. When several choose a folder, a Sieve fileinto beats an account rule's folder, which beats a global rule's. The regular-expression breaker. A regex runs against a value the sender wrote, so it has a budget of 250 ms per evaluation. A pattern that will not compile is reported as HM6042; one that takes 250 ms or more as HM6043; either is suspended for 300 seconds, keyed on the pattern text, so a corrected pattern takes effect on the next message. A suspended pattern does not match. The same breaker covers Sieve's :regex, and suspension is shared. Loop protection. Every generated message carries X-hMailServer-LoopCount. Forward, Reply and Create copy check it against RuleLoopLimit (COM Settings.RuleLoopLimit; Mail flow & delivery → Delivery of e-mail → Rules, since 6.3.7 a card on the Rules page; default 5); at or above it nothing is generated and HM5064, HM5065, HM5201 or HM4404 is logged. An Auto-Submitted header stops Reply and bounce reports only; Forward deliberately ignores it. What else 6.3.4 changed for rules Three collections instead of two. A domain's rules (Domain.Rules, /api/v1/domains/{name}/rules) run after the server's. Account rules are on /api/v1/accounts/{address}/rules. Five more actions: Add a disclaimer, Reject with a text, Require TLS, Quarantine and Warn the sender. A legal hold keeps, among the account's deleted items, what an account rule's Delete or Reject, or a Sieve discard, reject or redirect that keeps no local copy, dropped. It does not reach the server's or a domain's rules: a Delete there drops the message for everybody, held accounts included. A domain's rules do not always act on the whole message. Mail flow rules and what a domain does to a message says when they act on all of it and when on the domain's own recipients only, what each new action does, and two 6.3.4 fixes to what a rule queues. Sieve: where a script lives and how it becomes active Each account has one active script, plus any number of named scripts. They are files, not database rows, under [Directories] DataFolder: <DataFolder>\ Sieve\ _global\ administrator-placed scripts, for include :global example.com\ alice\ active.sieve THE ACTIVE SCRIPT - this is what delivery runs vacation.responses who has already had a vacation reply, and until when duplicate.seen identifiers the duplicate test has seen scripts\ holiday.sieve named scripts, from ManageSieve PUTSCRIPT Four routes write that same active.sieve, and all four check the syntax first: Route Where Control Panel (Windows) Account dialog, Sieve tab COM (Windows) Account.SieveScript; no Save() is needed REST GET and PUT /api/v1/me/filters, as the account itself; 256 KB cap; an empty script removes the filter ManageSieve PUTSCRIPT then SETACTIVE Deactivating a script also clears that account's vacation-response records. ManageSieve is off by default: ManageSieveServerPort=0 means the listener is never created. Set the port (the wiki's example is 4190) and ManageSieveServerBindAddress on the ManageSieve (RFC 5804) card of the Control Panel's API & monitoring page on Windows (since 6.3.7 a ManageSieve tab of the Protocols page; Ctrl+K takes the old title to it), or on either platform with PUT /api/v1/settings/ini/{name} or hmctl ini set. Since 6.3.4 both wait for a reinitialise, not a restart: Apply now in the Control Panel or the Deck, or POST /api/v1/server/reinitialize. A script is at most 1 MB. With no certificate, STARTTLS is not advertised at all; with no certificate and no range policy, keep the listener on 127.0.0.1. What this server's Sieve accepts and refuses require accepts fileinto, envelope, copy, relational, subaddress, vacation, vacation-seconds, imap4flags, body, mailbox, regex, ihave, environment, date, index, spamtest, spamtestplus, duplicate, editheader, variables, reject, ereject, include and enotify, and the comparators comparator-i;ascii-casemap, comparator-i;octet and comparator-i;ascii-numeric. What matters in practice: fileinto :copy is refused at upload. This server stores exactly one local copy. Use :copy on redirect. redirect without :copy cancels the implicit keep, and the message leaves the mailbox entirely. imap4flags stores only the five system flags, \Seen \Answered \Flagged \Deleted \Draft. A keyword is accepted, logged, and stored nowhere. spamtest reads the server's internal flag, never a header a sender could forge: "0" when the message was not classified, "10" when it was. Any threshold from 1 to 10 behaves identically. duplicate keys on Message-ID over 7 days by default, and its store fails open, because a wrong "duplicate" destroys mail. reject and ereject send a non-delivery report with status 5.7.1 to whoever the envelope names. Against forged senders use discard. editheader refuses Received and Return-Path, and since 6.3.7 From, Sender, Resent-From, Resent-Sender, DKIM-Signature, Auto-Submitted and X-hMailServer-LoopCount, at upload and at delivery, where a script stored before the upgrade still runs without the edit; enotify accepts mailto: only, and since 6.3.7 a notify goes from the account's own address unless its :from is the account's own address or an active alias pointing at it; include nests at most three levels. The everyday script: require ["fileinto", "mailbox", "spamtest", "relational", "comparator-i;ascii-numeric"]; if spamtest :value "ge" :comparator "i;ascii-numeric" "5" { fileinto :create "INBOX.Junk"; stop; } if header :contains "List-Id" "announce.example.com" { fileinto :create "INBOX.Lists.announce"; stop; } Vacation, and the out-of-office policy require ["vacation"]; vacation :days 7 :subject "Out of the office until 22 September" :addresses ["[email protected]", "[email protected]"] "I am away until 22 September. For anything urgent please write to [email protected]."; :addresses is not decoration: a reply is sent only when one of the user's own addresses appears in the envelope or the headers, and that list is what "the user's own addresses" means. :days defaults to 7; vacation-seconds gives :seconds, and :seconds 0 means "reply every time". The window is per sender, in vacation.responses, and that store fails closed: if it cannot be read, no reply is sent. The account's Auto-reply tab and the domain's Out of office tab are the server's own reply, separate from Sieve. At most one reply answers any message. The account's own, when it is on and inside its dates, comes first; a domain's reply answers only for accounts that have none. If both an account-level auto-reply and a Sieve vacation are configured, the account-level one wins. Account Domain General text VacationSubject, VacationMessage VacationSubject, VacationMessage Text for colleagues VacationInternalSubject, VacationInternalMessage the same two Colleagues only VacationMessageInternalOnly leave the general text empty Strangers get the domain's text - VacationExternalOverride A sender is internal when the envelope sender resolves to an account hosted here or to an active alias. That is whatever the submitting client said, so an internal text must never carry anything that cannot be shown to a stranger. Each sender is answered once per absence; a window - VacationMessageDays on the account, VacationDays on the domain, 0 to 3650 - answers the same sender again after that many days. The memory is the table hm_autoreplies (schema 6060), so since 6.3.4 a restart no longer repeats every reply. Never answered: a null return path; Auto-Submitted other than no; Precedence: bulk, list or junk; anything with a List-* header; a sender that set X-Auto-Response-Suppress; postmaster and mailer-daemon; ourselves; and marked spam when VacationMessageAbortSpamFlagged says so; and since 6.3.7 a message whose X-hMailServer-LoopCount has reached RuleLoopLimit, so RuleLoopLimit 0 stops out-of-office replies as it stops every other generated message. Every reply goes out with a null return path and Auto-Submitted: auto-replied. What rules and Sieve see on sealed mail A message encrypted end to end (S/MIME or PGP/MIME) is opaque: the server holds no key to it. How the server decides that, and what the rest of the server does with such a message, is in end-to-end encryption and sealed mail. For rules and Sieve: A rule's body criterion on an opaque message does not match, whichever way it is phrased, and one line per rule per message in the log says so. Under AND, a rule with a body criterion does not fire; under OR, the other criteria are still evaluated. Before 6.3.4, does not contain matched every encrypted message. A multipart/signed message is read as it always was. To catch encrypted mail, test the Content-Type header for multipart/encrypted or application/pkcs7-mime with a custom-header criterion. Sieve's evaluation is unchanged; a log line is new. An encrypted message has no text part, so body :contains "x" is false and not body :contains "x" is true, and the delivery writes a log line saying so, once per message and recipient. body :raw is the undecoded body exactly as it arrived, so if body :raw :contains "BEGIN PGP MESSAGE" is the honest way a script can recognise a sealed message. What it does not do Sieve cannot run server-wide, force a route or bind an outbound address. A rule's Delete e-mail is silent. The sender was already answered 250. To tell the sender, use Sieve reject, or since 6.3.4 the Reject with a text action. A journal rule does not see a rule's forward, copy or reply, or a Sieve redirect: the server queues those itself. Where to read more Wiki: Rules and Sieve, The COM API and Scripting Documents at the 6.3.4 tag: OutOfOffice.md, SealedMail.md, MailFlowRules.md, EventScripts.md The public documentation: chapter 12 and the rules cookbook On this forum: the 6.3.4 release announcement, the Linux guide, mail flow rules and domain rules, event scripts, and when a saved setting takes effect for the ManageSieve keys
  • 0 Votes
    1 Posts
    115 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.6 (6 October 2026) carries the PowerShell module, HmailServer.psd1 and HmailServer.psm1, Authenticode-signed, as files of the release, and Linux packages for x86-64 and AArch64; hmctl as a PyPI package and the module on the PowerShell Gallery are built but not yet published. See the 6.3.6 announcement. hMailServer 6.3.7 (17 October 2026) carries the module the same way, and since 18 October 2026 the repository and its releases are also mirrored on GitHub, issues and merge requests staying on GitLab. 6.3.7's REST API refuses a state-changing request that carries a credential when a browser reports it as cross-site, which hmctl and the module, sending no such header, never meet, and it refuses a key restricted to named domains a domain's rename, a domain alias and a custom role's creation, change or delete. See the 6.3.7 announcement. Since 21 October 2026 hmctl is on PyPI as hmailserver-cli and the module on the PowerShell Gallery as HmailServer, each at 6.3.7. hMailServer 6.3.8 (2 November 2026) carries the module as files of the release, as before, and both are sent 6.3.8 by a job started by hand once the release is published. 6.3.8's server answers 400 to a body that is not one JSON object or names a member twice, so a body given to hmctl api or --from-json must be one JSON object that names each member once, and 6.3.8's hmctl refuses a body holding NaN or Infinity before sending it. See the 6.3.8 announcement. hMailServer 6.3.4 has two command-line clients: hmctl for any shell, and the HmailServer module for PowerShell. They share one vocabulary and one API, so both administer a Windows server, a Linux server or a server on another machine, and neither needs COM or the Control Panel. This guide covers where each comes from, the credentials they take, the verbs, the import of accounts from a CSV file with its dry run, the configuration as one document, and what neither client does. The one thing to know first: both speak the REST API, so the REST listener has to be on before either can do anything, and neither client can turn it on. Neither is a replacement for the administration programs. The Windows Control Panel is the place to configure a server on Windows, and the browser Control Deck is that place on Linux. A command line is for what a command line is better at: making fifty accounts from a spreadsheet, reading one setting in a script, watching the delivery queue, or doing the same thing to twenty servers. Where the clients come from File What it is hmailserver/source/Tools/Cli/hmctl One Python 3 file with no dependencies beyond the standard library. hmailserver/source/Tools/Cli/HmailServer.psm1 The PowerShell module: Get-HmDomain, New-HmAccount, Set-HmSetting and the rest, answering objects rather than text. build/check-cli.py Runs both against a recorded server in CI and asserts what each one sent, so the two cannot drift apart. On Linux, the packages install hmctl as /usr/bin/hmctl. The 6.3.4 packages are x86-64 only, and the changelog records Python 3 as one of the .deb's two Recommends, the other being 7-Zip. On Windows, the wiki says the installer carries neither client: take them from the repository directory hmailserver/source/Tools/Cli/. Both clients are in the repository since 6.3.4. build/hmconfig.ps1 is a different tool and stays. It exports the whole configuration as one JSON document and applies it to another machine over COM, on Windows. The two clients here are per-object verbs over HTTP. Before anything else: the listener On Linux, store the port on the server itself and restart the service. Since 6.3.4 the database is the settings store, so editing the [Settings] section of hMailServer.ini does not turn the listener on once the server has stored the key. sudo hmailserver --set-setting RestApiPort=8080 sudo systemctl restart hmailserver On Windows, the Control Panel does this over COM. On either platform, TLS is required unless the listener is bound to the loopback, so give it a certificate the client trusts if it is to be reached from another machine. The REST API guide has the listener's settings, the reinitialise they wait for and the three ways it refuses to start, and the hmailserver command line on Linux has the checks --set-setting makes and how it takes a secret. Credentials Either the administrator password, sent as HTTP Basic with the user name Administrator, or an API key, sent as a bearer token. An API key is the better answer for anything unattended: it can be read-only, it can be restricted to one domain, and it can be revoked without changing the administrator password. export HMAIL_URL=https://mail.example.com:8045 export HMAIL_API_KEY="$(cat /etc/hmailserver/api.key)" hmctl domain list Connect-HmServer -Url https://mail.example.com:8045 -ApiKey (Get-Content .\api.key) Get-HmDomain hmctl reads HMAIL_URL, HMAIL_API_KEY, HMAIL_PASSWORD and HMAIL_PASSWORD_FILE, and takes --url, --api-key, --password and --password-file before the verb or after it. With no credential at all it asks for one, but only when there is a console to answer. In a scheduled task or a pipeline it says what is missing and stops. --insecure is refused for anything but the loopback. A self-signed certificate on localhost is the ordinary case and nothing is on the wire. An administrator password sent to a remote host over a connection nobody verified is what the refusal prevents, and the remedy is a certificate the client trusts. Some commands take the administrator password and never an API key, because no key reaches the routes behind them: hmctl apikey, hmctl ini, hmctl config changed, hmctl webhook and hmctl webhooks. The command synopsis names no option for a one-time code. Since 6.3.4 a one-time code is taken once, and the changelog's advice to a script that sent the administrator's password and a code with every request is a session or an API key; the REST API guide has the detail. hmctl hmctl [--url URL] [--api-key KEY | --password PASSWORD | --password-file FILE] [--insecure] [--json] <resource> <verb> [arguments] The resources, grouped. CommandLine.md has a row for each. Area Resources Notes Domains and what is in them domain, account, alias, list, group, fetchaccount, app-password, device, mailbox An account is listed and created under --domain, and read and written at its address, which is how the API is shaped. mailbox renames or moves one. Server-wide objects rule, route, port, iprange, certificate, journalrule list, create, update, delete Mail in motion queue, quarantine queue has list, retry, delete; quarantine has list, release, delete Settings settings, ini, config A settings group whole or one setting; the keys that were the [Settings] section; the configuration as one document Deliverability dkim, dmarc, suppression, bounce, bounces, complaint, complaints, spamtrap, traphits, reputation, openpgp reputation exits 1 when an address is listed and when a list could not be asked Integrations apikey, webhook, webhooks All three with the administrator password only, never an API key Operations log, backup, report, sample-domain, match match says what a rule criterion would decide, without a rule Bulk accounts import and export, a CSV file, with a dry run Anything else api A method and a path: the escape hatch, for a route newer than the client A few whole commands: hmctl domain create --set name=example.com --set active=true hmctl account create --domain example.com --set [email protected] --set password=... hmctl account list --domain example.com hmctl settings get antispam use_spf hmctl settings set antispam --set use_spf=true --set spam_mark_threshold=5 hmctl device [email protected] list hmctl queue list --json | jq '.[] | select(.retry_count > 3)' hmctl match contains viagra "cheap viagra here" hmctl report traffic --from 2026-09-01 --to 2026-09-15 --domain example.com hmctl api POST /api/v1/server/reinitialize How values are read. --set name=value reads true, false, null, numbers and anything starting with { or [ as JSON, and everything else as a string. --set-string keeps a value that looks like one of those a string. --from-json FILE takes a whole object, and - reads it from standard input. Output and exit codes. Output is a table by default and the server's own JSON with --json. Exit code Meaning 0 Success 1 An error 2 A usage mistake 3 The server refused the credential 4 What was named does not exist A script can therefore tell "no such account" from "wrong password" without reading the text. The PowerShell module Import-Module .\hmailserver\source\Tools\Cli\HmailServer.psm1 Connect-HmServer -Url https://localhost:8045 -Insecure Get-HmDomain | Where-Object active -eq $false Get-HmAccount -Domain example.com | Sort-Object max_size_mb -Descending | Select-Object -First 10 New-HmAccount -Domain example.com -Address [email protected] -Password (Read-Host -AsSecureString) Set-HmAccount -Address [email protected] -Property @{ max_size_mb = 500 } Set-HmSetting -Group antispam -Property @{ use_spf = $true } Get-HmQueue | Where-Object retry_count -gt 3 | Start-HmQueueDelivery Get-HmChangedSetting | Format-Table store, name, value, default Every function answers objects, so Where-Object, Sort-Object and Export-Csv work on them, and nothing in the module formats anything. Everything that changes the server supports -WhatIf and -Confirm, and deleting a domain or an account asks before it acts unless told not to. A domain name or an address completes from the server once Connect-HmServer has run. Invoke-HmApi -Method GET -Path /api/v1/... is the escape hatch, and is what every other function is built on. Accounts from a CSV file hmctl accounts import new-people.csv --dry-run hmctl accounts import new-people.csv hmctl accounts export example.com accounts.csv Import-HmAccount -Path .\new-people.csv -WhatIf Import-HmAccount -Path .\new-people.csv Export-HmAccount -Domain example.com -Path .\accounts.csv The file is UTF-8 (in Excel, CSV UTF-8) and its header row names the columns. These are the columns, and there are no others. Column Required Notes address yes Created under the address's own domain. password yes Sent as text, so digits alone are still a password. Never trimmed: white space at either end is refused. Judged by the server's password policy and, when it is switched on, the breach check. first_name At most 60 characters, counted as UTF-16 code units. last_name As first_name. active true, yes, on or 1; false, no, off or 0. Default true. max_size_mb A whole number of megabytes; 0 is no limit, and is the default. admin_level user, domain or server. Default user. address,password,first_name,last_name,active,max_size_mb,admin_level [email protected],S0me-Long-Passphrase,Ann,Adams,true,500,user [email protected],An0ther-Long-Passphrase,Bob,"Brown, Jr",true,,domain The columns may come in any order and their names in any case. Every cell but the password is trimmed, and an empty cell is not sent, so the server's default applies. A value with a comma in it goes in quotes. A row with more cells than the header has columns is refused rather than shifted along. What is refused before anything is sent. Each row becomes one POST /api/v1/domains/<domain>/accounts, and that route refuses a field it does not know by name. So a column that is not in the table is refused before anything is sent, a name column included. The clients also refuse, in the words the server would use: a row with no password; an active that is not one of the eight words; a max_size_mb that is not a whole number; an admin_level that is not one of the three; a name past its 60 characters; a password with a space or a tab at its start or end, which is the one row the clients refuse and the server would take. What a dry run cannot foresee. The password policy, the domain's account limit and an address the server does not accept are judged by the server alone. They are refused per row when the import runs, and listed with the address and the server's reason; hmctl adds the line number. An address that already exists is left alone and counted, never overwritten. An import that reset a password without saying so would lock people out with one command. --dry-run makes no request that changes anything and prints what would happen, line by line. The exit code is 1 when any row was refused. Who may make an administrator. admin_level is decided by who is asking. The administrator password, or a key issued for every domain, may make a server administrator. A key restricted to named domains may make users and domain administrators, and a row asking it for server is refused with a 403. The export writes the same columns less password, because the server does not have the passwords to give. Add a password column and change the addresses, and the exported file imports into another domain or another server as the same accounts: names, sizes, levels and whether each is active. The configuration as one document hmctl config changed # every setting that is not the shipped default hmctl config export server.json # the whole configuration, as a document hmctl config diff server.json # what would change; exit 1 if anything would hmctl config apply server.json # the plan, and nothing else hmctl config apply server.json --force # the plan, applied The document holds the settings groups, the hMailServer.ini keys the server exposes, the domains with their accounts, aliases and distribution lists, the groups, the rules, the routes, the listeners and the IP ranges. It is sorted throughout and carries nothing the server allocated, so two exports of an unchanged server are the same bytes. apply is a plan unless --force is added, and a deletion needs --allow-delete on top of that. Without it, an entry the server has and the document does not is left alone and counted. Not carried Why Passwords The server does not give them out. An account in the document that does not exist on the server is refused rather than created with a password nobody chose; add a password field for the accounts to be created. The secrets among the ini keys Listed as (set) or empty, and left out of the document. hmctl ini get <name> reads one by name. Certificates and the directories They are files and paths on one machine's disk. The journaling rules An address chosen for one server is almost never the right one for another. hmctl journalrule edits them. What lives under an account Application passwords, fetch accounts and folder permissions are the account holder's. The selector and key files of a domain whose DKIM key rotation is on The rotation moves them on its own schedule. Settings the environment manages In a container the value is the deployment's. The server refuses a change to such a setting with 409. Remote domain policies and alert rules The wiki's own addition: they have routes of their own that config does not read. What this server has changed from stock. hmctl config changed, or Get-HmChangedSetting in PowerShell, lists every setting whose value is not the one this version ships with, with the shipped value beside it. Settings: what is not stock has the route behind it, the other places to read the same list, what "default" means and who may see it. $ hmctl config changed STORE NAME VALUE DEFAULT ini LogLevel 5 9 ini SMTPDMaxTimeout 600 1800 ini OAuth2HmacSecret (set) ini DnsServer 10.0.0.1 (forced from [SettingsOverride]) database smtpnoofretries 6 4 5 of 391 settings are not what this version ships with. A secret is named and never shown. A value that the file's [SettingsOverride] section is forcing says (forced from [SettingsOverride]), a value the environment sets says (set by the environment), and a name the server holds a value for and ships no default for says (none: this server does not read it). A difference is not an exit code here, unlike config diff, because every real server has changed something. Reports and country blocking from a shell hmctl report sections hmctl report summary --from 2026-09-01 --to 2026-09-15 hmctl report senders --domain example.com --top 25 hmctl report mailboxes --csv mailboxes.csv hmctl report traffic --csv - --csv fetches the server's own CSV with format=csv instead of building one from the JSON. Per-domain reports has the sections, their sources and parameters, and who may read them. Run hmctl report sections first. It says which sources are recording and what this server cannot answer. The four sections counted from the message trace are empty when the trace is switched off, which is the default, and that is not the same statement as "nothing happened". Without --csv, the section's own note is printed above the table. CountryBlocking.md shows the country policy, which the topic on allow and block lists and country blocking explains, set from both clients: hmctl settings get settings geoip_database_status hmctl settings set --set geoip_database_file=/var/lib/GeoIP/GeoLite2-Country.mmdb hmctl settings set --set country_policy_smtp=block:CN,RU hmctl domain update example.com --set active=true --set country_policy=allow:GB,IE Set-HmSetting -Property @{ geoip_database_file = '/var/lib/GeoIP/GeoLite2-Country.mmdb' country_policy_smtp = 'block:CN,RU' } (Get-HmSetting).geoip_database_status Set-HmDomain -Name example.com -Property @{ active = $true; country_policy = 'allow:GB,IE' } geoip_database_status is read-only. A settings change is all or nothing, so a policy the grammar refuses leaves every other key in the same request unapplied. What neither client does Neither reads or writes the message store. Mail is IMAP's business. Neither is a backup tool of its own. hmctl backup start, hmctl backup restore and hmctl backup passphrase ask the server; the archive is written, read and restored by the server. Neither sets the administrator password. hmailserver --set-admin-password on the server does that, on Windows hMailServer.exe --set-admin-password, with the password in HM_ADMIN_PASSWORD or on standard input and never as an argument. On Windows the Control Panel does it too, but only where the service may write hMailServer.ini, as under LocalSystem: under NT SERVICE\hMailServer, a new 6.3.4 installation's account, its Administrator password card is refused with a sentence. Neither makes or carries the stored-secrets key. That is done on the server itself, never over the API. Neither can turn the REST listener on, since both need it. Neither sets what the environment sets in a container. No container image of 6.3.4 is published. Neither invents a field. A field that was not given is one the server decides. hmctl has no pending verb. SettingsApply.md notes that GET /api/v1/server/settings/pending is one request; hmctl api takes a method and a path. Where to read more The operator document: CommandLine.md, with a row for every resource and sections on DKIM rotation, bounces, complaints, DMARC reports, OpenPGP keys, the mailbox move and setting profiles. The wiki page: Command Line. For the examples above: ChangedFromStock.md, Reports.md and CountryBlocking.md. The public documentation: chapter 24, The REST API, which both clients speak, and 35.10, Administering a Linux server. On this forum: the Linux guide, The Control Deck, the 6.3.4 announcement, the REST API guide and, for what hmctl webhook manages, the send API and webhooks.
  • 0 Votes
    1 Posts
    124 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes three things this guide relies on. A Control Panel signed in to a server on another computer no longer reads or writes its own computer's hMailServer.ini for the [LDAP] values, which it cannot read from the server, where it showed that file or the defaults as the server's; the sidebar is regrouped, and Ctrl+K takes every old page title to its page. With SMTP logging on, 6.3.6 and every earlier version that offered them wrote the bearer token of AUTH XOAUTH2 and AUTH OAUTHBEARER and the lines of SCRAM-SHA-256, OAUTHBEARER and GSSAPI into the SMTP log in the clear; 6.3.7 hides every line of a SASL exchange but the user name of AUTH LOGIN and the authentication identity of AUTH PLAIN. An account given the serveradmin role with a list of domains is, over COM as over REST, confined to those domains and no longer the server's administrator, so a script that runs the directory synchronisation's preview and apply as such an account needs the role given with no list. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) signs a name in to an account only when it is the account's stored address, compared in lower case after a domain alias and the default domain, for the name an OAuth2 bearer token carries as for a password (work item 104), so a 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, may no longer sign in. On Windows an OAuth2PublicKeyFile whose path holds characters outside ASCII may be refused with HM7536 (one outside the code page, which worked up to 6.3.7, is refused), and then the key verifies no token: give the file, and the folders above it, an ASCII name before upgrading. See the 6.3.8 announcement. This topic covers three ways an organisation's directory or identity provider vouches for somebody on hMailServer 6.3.4's mail protocols: checking a password against Active Directory or another LDAP directory, creating mailboxes from that directory, and accepting OAuth2 bearer tokens. Know this first: 6.3.4 keeps settings in the database, but the [LDAP] section stays in hMailServer.ini on each server, with no REST route and no environment name; the server re-reads it within two seconds of a change, with no restart. The OAuth2 keys are ordinary [Settings] keys, stored like any other. Three mechanisms, three questions Mechanism The question it answers Control Panel page (Windows) LDAP directory authentication Is this the password of the directory user the account is linked to? Access & abuse protection → Directory authentication (LDAP) Directory synchronisation Which mailboxes should exist, according to the directory? Access & abuse protection → Directory synchronisation OAuth2 bearer tokens Did the identity provider sign this token, and which mailbox does it name? Access & abuse protection → Authentication All three are off by default, and an upgrade switches none of them on: the [LDAP] section is absent until you add it, and OAuth2Enabled defaults to 0. Where the [LDAP] section lives Windows. The Control Panel's page writes the hMailServer.INI of its own machine. Connected to another machine, its Save is disabled, although "the connection test still works with the values typed here". Work on the server, over Remote Desktop. Linux. No Control Panel and no COM. The file is /etc/hmailserver/hMailServer.ini, 0640 root:hmailserver: sudoedit /etc/hmailserver/hMailServer.ini. Not REST or hmctl. The REST ini routes and hmctl ini set handle [Settings] keys only. Per server. On a warm standby each node has its own [LDAP]; keep the two alike by hand. ServicePassword is a line of that file. It is not among the [Settings] secrets 6.3.4 seals under the stored-secrets key; the Control Panel's field for it is write-only. The sign-in keys Key Default What it does Enabled 0 The master switch Server empty The directory host; required Port 0 0 means 636 for LDAPS, 389 otherwise Security 2 2 LDAPS, 1 StartTLS, 0 plain; anything else is LDAPS VerifyCertificate 1 Check the directory's certificate AllowUnprotectedPassword 0 Permit a simple bind over a plain connection BindMethod 0 0 simple; 1 Negotiate (Kerberos or NTLM through Windows SSPI) SearchBase empty Required unless UserDnTemplate is set or the bind is Negotiate UserSearchFilter (&(objectCategory=person)(objectClass=user)(sAMAccountName=%u)) How the user's entry is found UserDnTemplate empty Bind directly, without searching ServiceUsername, ServiceDomain, ServicePassword empty What the search binds with TimeoutSeconds 10 Bounds every directory operation; 1 to 90 FallbackToWindowsLogon 0 Also try Windows LogonUser; Windows only LDAPS with VerifyCertificate=1 is the default and the recommended configuration, proven end to end against a Windows Server 2025 domain controller. Server must be the name in the directory's certificate: the same setup pointed at the controller's IP address, with the right password, is refused, and HM5920 reports a failure "unrelated to the credentials supplied". On Windows, put the issuing CA in LocalMachine\Root; when this was proven the service ran as LocalSystem, and a CurrentUser\Root import served it nothing. The bind needs no extra privilege under NT SERVICE\hMailServer, which a new 6.3.4 installation runs as. On Linux the certificate must chain to the system trust store. StartTLS (Security=1) connects on 389 and upgrades before anything is sent. Plain LDAP is labelled Unprotected LDAP - cleartext (lab networks only): there a simple bind is refused (HM5921) rather than sent, unless AllowUnprotectedPassword is 1. Simple or Negotiate. A simple bind sends the password to the directory, so it belongs on LDAPS or StartTLS. Negotiate, Windows only, never sends it; it worked on 389 against a controller that had no LDAPS certificate yet, and suits a mail server not joined to the domain. Search or template. A search binds with the service credentials, finds the user under SearchBase with UserSearchFilter, then binds as the user. It asks for two entries to see ambiguity: more than one match is a configuration fault and authenticates nobody. UserDnTemplate skips the search, so no service credential need be stored at all; with a simple bind, that is the configuration the feature was proven with. Linking an account to the directory The directory is asked only about linked accounts; others keep their own password. The webmail's sign-in shares the IMAP and POP3 logon, so it takes the directory password too. Control Panel: the account dialog's Windows directory tab, with the domain (ADDomain) and user name (ADUsername); Browse Active Directory searches the forest to fill them. Since 6.3.4 the tab has that name; it was called Directory, and the new Directory entry tab is the organisation's address book, unrelated to Active Directory. REST: PUT /api/v1/accounts/{address} with ad_enabled, ad_domain and ad_username. COM (Windows): IsAD, ADDomain and ADUsername. What a link changes. The account's stored password is no fallback: in the proof run, a linked account's local password was refused. Linked accounts bypass local hashing, so they are exempt from the minimum-hash policy and from password expiry, and cannot sign in with SCRAM-SHA-256; their clients use PLAIN or LOGIN over TLS. App passwords work for them: the directory holds the password, but an app password is local, which is what makes a second factor possible for a directory account at all. The person cannot change the password in the webmail (POST /api/v1/me/password answers 409), and Forgotten your password? cannot reset it, because this server does not hold it. Without LDAP, on Windows, a linked account is checked by LogonUser, which needs the mail server's host joined to the domain. On a host that is not joined, Windows answers every domain name, real or misspelt, as a wrong password, so the account can never sign in; HM5911 says so, naming the domain and the computer. An empty domain, . or the computer's own name means a local Windows account. Never leave the domain empty by accident: with [LDAP] Enabled off, an account linked with an empty domain and the user name administrator would accept the server's local Administrator password. FallbackToWindowsLogon never applies to a wrong password: a directory's rejection deliberately does not fall through to LogonUser, because that would double every failed attempt and trip a lockout policy on a single wrong password. Leave it off on a host that is not domain-joined. Setting it up on Windows For LDAPS or StartTLS, put the directory's issuing CA in LocalMachine\Root on the mail server. On the mail server, open Directory authentication (LDAP). Switch directory authentication on (Enabled); until then a linked account is checked by Windows LogonUser, not with these settings. Fill in the server, the security, the bind, and either the search or UserDnTemplate; the first card asks Is this configuration complete? Test the connection with a directory user name, its AD domain and its password. It tries the values typed on the page. Save. The server re-reads [LDAP] within two seconds. Link one account and sign in with the right password and a wrong one. Over POP3 the proof run got +OK Mailbox locked and ready and -ERR, and the log showed the bind and the rejection separately. Link the others by hand, or with directory synchronisation. Setting it up on Linux The only directory path is an LDAP simple bind over LDAPS or StartTLS, proven against OpenLDAP's slapd. Make sure the directory's certificate chains to the system trust store, and use the name it carries. Add the section with sudoedit /etc/hmailserver/hMailServer.ini, filling in the angle brackets. Without a UserSearchFilter line the search uses Active Directory's filter, (&(objectCategory=person)(objectClass=user)(sAMAccountName=%u)), which finds nobody in another directory such as OpenLDAP; there, write a filter that matches its user entries, with %u for the name: [LDAP] Enabled=1 Server=<the host name the directory's certificate carries> Security=2 VerifyCertificate=1 BindMethod=0 SearchBase=<the DN the users are under> UserSearchFilter=<that filter, unless the directory is Active Directory> ServiceUsername=<the search account> ServicePassword=<its password> Two seconds later the server has it. Link an account over REST (ad_enabled, ad_domain, ad_username) and sign in with the right password and a wrong one. Linux has no Negotiate (BindMethod=1 is refused as a directory failure, not a wrong password: HM6420, reported once), no LogonUser (a linked account while [LDAP] Enabled is off is refused with HM6421, which names the settings to set; FallbackToWindowsLogon refuses with HM6404), and no GSSAPI bind. When the directory cannot answer So that an outage does not read as users mistyping, infrastructure failures are reported apart from credential rejections, throttled per error and endpoint, and TimeoutSeconds bounds the wait. Code Meaning HM5920 The directory could not be used (a certificate not matching Server, say). Reported at High: unrelated to the credentials, and every account on that directory is affected HM5921 A simple bind would have sent the password unprotected, so the logon was refused first HM5911 Windows LogonUser: the host is not joined to the domain; at most once a minute per problem Directory synchronisation: preview, then apply Synchronisation reads the directory, works out which mailboxes should exist, and only when asked creates and updates them; it was verified end to end against a Windows Server 2025 domain controller. The planner writes nothing and the applier decides nothing, so a preview cannot promise what the apply would not do. Per-domain opt-in. A domain takes part only if its Active Directory domain name is set (ADDomainName in the domain dialog, ad_domain_name over REST), so a search base one level too high cannot provision into unrelated hosted domains. Nothing is ever deleted, account or message. The most it does is clear Active, only when asked, and never on a truncated or empty enumeration or for a domain in which nothing was seen. No password. A made account is marked IsAD and signs in through the directory from the start. No existing account claimed. An account that is not linked, or is linked to another AD domain, is reported, not changed. Paged. Active Directory answers a search beyond MaxPageSize (1000) with the first page and a success status, so the enumeration pages; SyncMaxUsers is the ceiling. [LDAP] key Default SyncFilter (&(objectClass=user)(objectCategory=person)(mail=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2))) SyncMailAttribute mail; proxyAddresses works, preferring the capitalised SMTP: primary SyncUsernameAttribute, SyncDisplayNameAttribute sAMAccountName, displayName SyncMaxUsers 5000; at most 100000 SyncScheduleMinutes 0, off; 15 at least, 10080 (seven days) at most On Windows: Get authentication working first. Synchronisation binds as the service account, honouring BindMethod, so Negotiate works where there is no LDAPS certificate. Set the Active Directory domain name on each mail domain that takes part. Open Directory synchronisation, check the selection keys, choose the domain and whether absent accounts are marked inactive, and run Preview. It changes nothing. Read the report, including the existing accounts it reports and leaves alone. Apply. The button stays disabled until a preview of the same options has run. Behind the page are Settings.PreviewDirectorySync(DomainName, DisableMissing) and Settings.ApplyDirectorySync, for a server administrator and callable from a script. From a Control Panel on another machine the six selection boxes are read-only and, up to 6.3.6, show their defaults; since 6.3.7 the Control Panel no longer reads its own computer's file for the [LDAP] values, which it cannot read from the server, so it no longer shows them as the server's. Preview and Apply still run inside the server. The unattended schedule (SyncScheduleMinutes) runs at start and then every so many minutes. It creates and updates but never disables: unattended is where nobody reads the report that would show a mailbox about to be taken away. The schedule is one of the tasks the server registers as it starts, and only if SyncScheduleMinutes is set then, so restart the service once after first setting it. On Linux, with no COM, there is no preview or apply, and whether the schedule runs there is not documented: treat it as untested. OAuth2 bearer tokens on IMAP, POP3 and SMTP With OAuth2Enabled=1, SMTP, IMAP and POP3 offer XOAUTH2 and OAUTHBEARER (RFC 7628), by default only over TLS (OAuth2RequireTLS=1). The server verifies the token's signature itself, takes the login name from the claim OAuth2UsernameClaim names (email), and signs in to that mailbox; a user= the client asserts must match. The mailbox must already exist: a token never makes one. Key Default What it does OAuth2JwksUrl empty The provider's JWK Set; the token's kid picks the key. https, or plain http to loopback only OAuth2JwksCacheSeconds 3600 How long the set is kept OAuth2PublicKeyFile empty A PEM RSA or EC public key; the fallback OAuth2HmacSecret empty The HS256 key OAuth2AllowedAlgorithms RS256 HS256, RS256 and ES256 recognised; none never OAuth2Issuer, OAuth2Audience empty Expected iss and aud; empty turns the check off OAuth2IntrospectionUrl empty RFC 7662 endpoint asked whether a verified token is still active OAuth2IntrospectionClientId, OAuth2IntrospectionClientSecret empty Who this server introspects as, over HTTP Basic OAuth2IntrospectionCacheSeconds 300 Verdict reuse, never past the token's exp OAuth2IntrospectionFailOpen 0 1 accepts a token the endpoint cannot judge, logging each An unknown kid, which is what a rotation looks like, fetches the set again at once, at most once a minute; a failed fetch keeps the keys held. HttpProxy, when set, carries these fetches. Where to set them. Each applies when saved: on the Control Panel's Authentication page, with PUT /api/v1/settings/ini/{name} and the administrator password, with hmctl ini set, or with COM Settings.SetIniSetting. Not in hMailServer.ini: since 6.3.4 the database holds them, and a line edited in the file is not used; Settings: when a save takes effect explains the store. Choose the key source: OAuth2JwksUrl, so a rotation needs no copied file; or OAuth2PublicKeyFile; or OAuth2HmacSecret with HS256 allowed. Set OAuth2Issuer and OAuth2Audience to what the provider writes into its tokens. Left empty, neither is checked: any token the provider's keys signed is accepted, whatever client it was issued to. Optionally set introspection, so a revoked token stops working before it expires. Only then set OAuth2Enabled to 1. On Windows, read the OAuth2 / XOAUTH2 item of the Control Panel's External setup list. Why that order. Switched on without usable key material, the mechanisms are still advertised, every token fails, and failed bearer sign-ins feed the auto-ban, so a correct client can have its address banned. On Linux before the REST listener is up, hmailserver --set-setting stores a key; a secret is given by name alone and typed at a prompt. A value stored this way reaches the running server at its next save, reinitialise or restart. Set the issuer and audience too, as step 2 says, putting your provider's values in place of these examples: sudo hmailserver --set-setting OAuth2JwksUrl=https://idp.example.com/keys sudo hmailserver --set-setting OAuth2Issuer=https://idp.example.com sudo hmailserver --set-setting OAuth2Audience=hmailserver sudo hmailserver --set-setting OAuth2Enabled=1 sudo systemctl restart hmailserver Since 6.3.4: a bearer token meets the domain's and the account's country policy, as a password does (a stolen token used to sign in from a refused country); an SMTP session signed in with a token can send more than one message (the second used to be refused with 550 "Login credentials no longer valid"); and OAuth2HmacSecret and OAuth2IntrospectionClientSecret are sealed at rest once the installation has a stored-secrets key, which an upgrade does not make until an administrator runs --create-stored-secrets-key. Tested how. The regression fixtures play the identity provider on the loopback, to make it misbehave: RS256 and ES256, the JWK Set and its rotation, introspection and fail-open. A real provider's tokens are not yet part of them. This is inbound only. The OutboundOAuth2 keys and FetchOAuth2Hosts are the server's own token for relaying through and collecting from Microsoft 365; Routes, relays and smart hosts covers relaying with it. Where these end: SCIM, single sign-on and Kerberos The LDAP backend authenticates; SCIM provisions. Synchronisation reads the directory; SCIM is the other direction, Entra ID or Okta calling /scim/v2 under an API key whose role holds provision, and it removes accounts, which synchronisation never does. Single sign-on, OpenID Connect or SAML 2.0, opens the webmail and the Control Deck only, ending in POST /api/v1/session; the protocols' bearer tokens are a separate, older mechanism with the OAuth2 settings above, and single sign-on does not reach mail clients. On Windows, SASL GSSAPI (GssapiEnabled) lets a mail client sign in with a Kerberos ticket, mapped to the account linked to that directory user. SCIM provisioning and single sign-on sets those up; Signing in and Connecting a mail client show the person's side. What it does not do No Negotiate, Windows logon or GSSAPI bind on Linux. hMailServer on Linux lists the rest. No LDAP server here, and the webmail's organisation directory is typed in, not imported from Active Directory. Where to read more Wiki: Settings Reference sections 9.2 and 11, Capabilities and Configuration, Control Panel Reference, Installing on Linux, Security Hardening, Sign-in and Identity, Architecture Documents: SettingsApply.md, ServiceAccount.md, Scim.md, PasskeysAndSingleSignOn.md, TestLab.md The Roadmap rows on LDAP, which record how each part was proven On this forum: the stored-secrets key, auto-ban and the warm standby The public documentation's Creating accounts from Active Directory and Modern authentication Report a security flaw in any of this by SECURITY.md's routes: a confidential GitLab issue, or email to contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as one.
  • 0 Votes
    1 Posts
    135 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) refuses a rule's Run function action when the rule is saved unless it names a plain function, for a rule of every owner, over COM and REST, and setting one on a domain's or an account's rule is held to the server administrator alone, where up to 6.3.6 a credential confined to named domains, a domain administrator among them, could set it. An event script that goes on in a transaction after Database.ExecuteSQL has failed is refused every statement after it once the database has ended the transaction, and Database.CommitTransaction then fails: call Database.RollbackTransaction and run the whole transaction again, or stop. A ScriptTimeout above 4,294,967 seconds is taken as that, where it ended every Windows event script at once, and the VBScript calls of OnClientValidatePassword, OnExternalAccountDownload and OnDeliveryFailed are quoted as every other call is. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) adds a sixteenth event, OnIMAPMessageMoved, called once for each message an IMAP MOVE or UID MOVE moved between two folders of the account signed in, after the command and off the IMAP thread, with the account and thirteen values, in VBScript and JScript on Windows and JavaScript on Linux; it is off as shipped, and IMAPMessageMovedEvents (COM Settings.Scripting.IMAPMessageMovedEvents, imap_message_moved_events in PUT /api/v1/settings/scripting) switches it on (EventScripts.md). Work that outlasts a stop of the servers now writes its interruption to the debug log, not the ERROR log, so OnError is not called for it, and an event script's call that meets a stop fails with "The server is stopping. The request was not completed.", its script error going to the debug log. See the 6.3.8 announcement. This topic covers event scripts in hMailServer 6.3.4: the languages, switching scripting on, the fifteen events, the objects, the limits and errors, and the 6.3.4 security fix. If you run a Windows server older than 6.3.4 with event scripting on, that fix is the one thing to know first: a value written into a JScript handler's call, a password among them, could run code inside the server, and so could the name in a rule's Run function action, in either language. The section on CVE-2026-100741 below says what to do until the server is upgraded. What an event script is An event script is one file, EventHandlers.vbs or EventHandlers.js, in the server's Events directory, holding functions named after the events they handle. When scripting is on, the server calls the function for each event as it happens, hands it the objects the event is about (the client, the message, the account) and reads back what the function decided through the Result object. A rule's Run function action calls a function of the same file by name, and it too needs scripting on. Scripting is off as shipped, and with it off no handler is called. The file is loaded all the same: at start, at every reload, at Reload() and at POST /api/v1/settings/scripting/reload. Loading a file runs its code outside functions, once to compile it and once more for each of the fifteen handlers looked for, so keep the top level to declarations. A top level that throws, or runs past the timeout, is reported as a load that failed (HM5016, HM5710 or HM5021), whether scripting is on or not. A handler runs inside the server, on the thread that is doing the work, as the service's account: LocalSystem on a Windows installation that began before 6.3.4, NT SERVICE\hMailServer on a new one from 6.3.4, the hmailserver user on Linux. Everything behind a slow script waits, so try a script first on a server not carrying your mail. Which language runs where Language Windows Linux Engine File VBScript runs refused at load Windows Active Scripting (vbscript.dll) EventHandlers.vbs JScript runs refused at load Windows Active Scripting (jscript.dll), ECMAScript 3 EventHandlers.js JavaScript refused at load runs QuickJS 2025-04-26, built into the server, ES2023 EventHandlers.js Up to 6.3.3 a Linux server had no script engine and refused any script; since 6.3.4 it runs JavaScript in QuickJS, with the same fifteen events, function names and objects. Windows keeps VBScript and JScript unchanged. The language is one setting: Settings.Scripting.Language over COM (Windows only), "language" in PUT /api/v1/settings/scripting over REST, and the Language (VBScript, JScript or JavaScript) field on the Control Panel's Advanced page. Since 6.3.4 it accepts all three words on both platforms, in any case, and refuses any other word without storing anything. It is seeded VBScript on every platform, so a Linux server switching scripting on sets JavaScript first. Storing the language loads nothing. The script in force goes on running, in the language it was loaded in, until the next load. A language the build does not run is refused when the script is loaded: one line in the ERROR log, HM5016 at a start or HM5710 at a reload (the earlier script then stays in force), naming the language to use, once per load. While scripting is switched off this refusal is not reported, so switch scripting on and reload before reading the log for it. GET /api/v1/status says which languages a build runs, under build.script_languages: ["VBScript","JScript"] on Windows, ["JavaScript"] on Linux. Switching it on On Windows there are two Control Panel pages under Maintenance: Advanced, whose Scripting tab turns the engine on and chooses the language, and Event scripts, which edits the file. Save & reload writes a .bak first, then reloads and checks the syntax. Over COM the switch is Settings.Scripting.Enabled; Settings.Scripting.Reload() re-reads the file and CheckSyntax() returns the error text, where empty means clean. There is one file and no include mechanism. On Linux, from the command line: Set the language: hmctl settings set scripting --set-string language=JavaScript Write the file as the service's user: sudo -u hmailserver tee /var/lib/hmailserver/events/EventHandlers.js <<'EOF' function OnAcceptMessage(client, message) { if (message.Subject.includes('[URGENT]') && !client.Authenticated) { Result.Value = 2; Result.Message = 'Urgent mail comes from people who have logged on.'; } } EOF Switch scripting on: hmctl settings set scripting --set enabled=true Confirm which file the server reads: hmctl settings get scripting current_script_file Load it with POST /api/v1/settings/scripting/reload. POST /api/v1/settings/scripting/check answers {"result":""} when the file compiles and the error, with its line, when it does not. The fifteen events Every handler is optional. When the script is loaded the server checks which of these names the file declares and fires only those. Names are matched exactly: OnErrorLog is not a handler, the name is OnError. Handler Fires when Called with What Result.Value does OnClientConnect(client) any TCP connection is accepted, before a greeting the client 1 closes the connection at once OnHELO(client) the SMTP client sent HELO or EHLO the client 1 answers 554 5.7.1 Rejected; 2 answers 554 5.7.1 with Result.Message; 3 answers 453 4.7.0 with Result.Message OnClientLogon(client) an SMTP, POP3 or IMAP client finished a logon attempt, successful or not the client; client.Authenticated says how the logon went nothing: the logon has been decided OnClientValidatePassword(account, password) a password is being checked, before the built-in check the account and the password typed 0 lets the client in; 1 turns it away; 2 (as given) leaves it to the stored password OnSMTPData(client, message) the DATA command arrived, before the body the client and the message as OnHELO OnAcceptMessage(client, message) the body has been received and is about to be queued the client and the message as OnHELO OnRecipientUnknown(client, message) a RCPT TO named an address this server does not host the client and the message nothing OnTooManyInvalidCommands(client, message) an SMTP client sent too many invalid commands the client and the message nothing OnDeliveryStart(message) the first step of a delivery attempt, before virus scanning the message 1 deletes the message OnDeliverMessage(message) after virus scanning and global rules, before the recipient split the message 1 deletes the message OnDeliveryFailed(message, recipient, error) once per failed recipient; may fire several times per message the message, the recipient, the error text nothing OnExternalAccountDownload(fetchAccount, message, uid) a message was fetched from an external POP3 or IMAP account the fetch account, the message or null, its UID on the remote server 1 deletes it on the remote server now; 2 after Result.Parameter days; 3 never OnBackupCompleted() a backup finished nothing nothing OnBackupFailed(reason) a backup failed the reason nothing OnError(severity, code, source, description) anything is written to the ERROR log the error, as the ERROR log has it nothing OnError's severity is 1 Critical, 2 High, 3 Medium, 4 Low, and its code is the HM<nnnn> number without the prefix. OnClientValidatePassword setting 0 is a full authentication bypass for that account, on every protocol. A handler that bridges to another directory should fail closed: set 1 when the lookup errors, never 0. The objects a handler is given The objects an event has are also globals of the same names, HMAILSERVER_CLIENT, HMAILSERVER_MESSAGE, HMAILSERVER_ACCOUNT, HMAILSERVER_FETCHACCOUNT, Result and EventLog, and the parameter and the global are the same object. An object an event does not have is null as a parameter; as a global it may not be defined at all (the message of an external download that failed), and reading an undefined global throws a ReferenceError, so a handler reads its parameter. Do not use Result in the seven events that are notifications. EventScripts.md says it is not defined at all in OnClientLogon, OnRecipientUnknown, OnTooManyInvalidCommands, OnDeliveryFailed and OnError; the public documentation adds that the server makes no Result for OnBackupCompleted and OnBackupFailed. Object Members On JavaScript (Linux) Client Port, IPAddress, Username, HELO, Authenticated, EncryptedConnection, CipherVersion, CipherName, CipherBits, SessionID the same, all read-only Message ID, Filename, Subject, From, FromAddress, To, CC, Date, Body, HTMLBody, Charset, Recipients, AddRecipient(name, address), ClearRecipients(), HeaderValue(name), HasBodyType(type), Flag(flag), Size (KB), State, DeliveryAttempt, UID, InternalDate, Copy(folderId), RefreshContent(), Save(), Attachments, Headers Attachments and Headers are not there; SetHeaderValue(name, value) and SetFlag(flag, on) write; InternalDate is text Result Value, Message, Parameter Value and Parameter take a whole Number only EventLog Write(text) the same FetchAccount the COM FetchAccount readable, not assignable; Password, Save(), DownloadNow() and Delete() are not there Account the COM Account, freshly loaded the properties that describe the account are readable, not assignable; Password, the collections and every method are not there Save() is what writes a change to disk. Changes to the subject, the body and the headers are written to the message file by Save(), on both platforms; a handler that does not call it changes nothing on disk. Do not call it in OnSMTPData: the public documentation says the message is still being processed there and saving corrupts it, and that only the character set, the file name, the envelope sender and the recipients are reliable at that point. Change a message in OnAcceptMessage. Result is read after the handler returns. On JavaScript a string ('1' too), a boolean, null, undefined, NaN, a fraction or an object assigned to Value or Parameter throws a TypeError, and the event goes on with the Result it had. So in OnClientValidatePassword, Result.Value = decisions[user] for a user the table does not have leaves 2, the stored password. On Windows, COM turns a boolean, undefined, a fraction and a string of digits into numbers, and undefined into 0: the same line in JScript lets that client in. EventLog.Write is the debugger. It appends a line to hmailserver_events.log in the log directory, and there is no breakpoint. A rule's Run function A rule's Run script function action calls the named function as <function>(HMAILSERVER_MESSAGE), then reloads the message from disk, so anything the function saves is seen by the rest of the rule set. Since 6.3.4 it works on Linux, where the function is a JavaScript one in EventHandlers.js. Since 6.3.4 the action names the function alone, as a plain identifier: ASCII letters, digits, _ and $, not starting with a digit. StampReceivedDate qualifies; StampReceivedDate(x) does not. On Windows, in VBScript or JScript, any other name is refused as HM5018 each time the rule matches and the function is not run. On Linux such a name is never evaluated and finds no function: nothing is run, and the ERROR log has a Script Error line, not HM5018. A ported handler JScript and JavaScript share the file name, so a JScript file carried from Windows to Linux is the file the JavaScript engine is given. EventScripts.md's example starts from VBScript on Windows: Sub OnAcceptMessage(oClient, oMessage) If InStr(oMessage.HeaderValue("X-Spam-Flag"), "YES") > 0 And Not oClient.Authenticated Then oMessage.HeaderValue("X-Quarantine") = "yes" oMessage.Subject = "[SPAM] " & oMessage.Subject oMessage.Save EventLog.Write("Tagged spam from " & oClient.IPAddress) End If End Sub The same handler for a Linux server, in EventHandlers.js: function OnAcceptMessage(client, message) { if (message.HeaderValue('X-Spam-Flag').includes('YES') && !client.Authenticated) { message.SetHeaderValue('X-Quarantine', 'yes'); message.Subject = `[SPAM] ${message.Subject}`; message.Save(); EventLog.Write(`Tagged spam from ${client.IPAddress}`); } } What changes in a port: Parameterised properties have no assignment. message.HeaderValue('X') = 'v' becomes message.SetHeaderValue('X', 'v'), and message.Flag(n) = true becomes message.SetFlag(n, true). The reading forms are unchanged. Names are case-sensitive and spelled as the IDL spells them. JScript found message.FileName; JavaScript needs message.Filename. No CreateObject, no new ActiveXObject. Both exist on JavaScript only to throw a TypeError that says so. null, not Nothing, for an object an event does not have. The limits A fresh engine for every event, on Windows and Linux alike: the whole file is parsed and run, the handler is called, and the engine is freed before the event goes on, so nothing a script makes outlives the event. No file system, no network, no processes on JavaScript. QuickJS keeps these in quickjs-libc.c, and that file is not in the tree. No module loader is installed, so import() is refused and an import statement is a syntax error; there is no print and no console. What a handler can change is what its objects change: a message's file through Save(), a copy into a folder through Copy(), the event log through EventLog.Write. On Windows, ScriptAllowedObjects decides what a script may create with CreateObject or new ActiveXObject. Its shipped value is *, every class. Empty means no class beyond the server's own objects, and a comma-separated list of ProgIDs means only those. Up to 6.3.3 it applied at a service restart; since 6.3.4 a save applies it. It has nothing to apply to on Linux. ScriptTimeout is in seconds, with 0 for no limit. It is kept in the settings store: set it with PUT /api/v1/settings/ini/ScriptTimeout, COM Settings.SetIniSetting or the Control Panel, not by editing hMailServer.ini. Limit Platform Value When a handler reaches it ScriptTimeout Windows 60 seconds by default HM5019; the interrupt cannot release a handler blocked inside a COM call ScriptTimeout Linux 60 seconds by default HM7341; the event goes on with whatever Result the handler had set Memory Linux 64 MB for each run, a constant HM7342; the event goes on Stack Linux 512 KB of the calling thread's stack a runaway recursion throws an InternalError The Linux timeout does not stop one call into a built-in that loops in the engine's C code without polling, such as includes or indexOf on an object whose length is huge: do not call an array method on an object whose length the handler has not checked. Errors, and where they are written What Where A script that does not compile, or whose top level throws HM5016 at start (nothing loaded), HM5710 at a reload (the earlier script stays in force) A top level that runs past the timeout HM5021, and the load refused An exception a handler does not catch one Script Error line in the ERROR log; the event goes on The load is all or nothing on every engine, so a typo in a reload never switches a working OnClientValidatePassword off. The 6.3.4 security fix: CVE-2026-100741 Up to 6.3.3 a password could run code inside the server. On Windows, with event scripting on, the language JScript and an OnClientValidatePassword handler in EventHandlers.js, any client that could reach a sign-in and name an existing, active account could have JScript of its own run inside hMailServer.exe, with the rights of the service's account, before it had authenticated. The server built each call as source text and escaped the apostrophe but not the backslash before it. With ScriptAllowedObjects at its shipped *, that script could create any COM object, WScript.Shell among them. Path Who could use it Open in The password given to OnClientValidatePassword any client that can reach a sign-in JScript The UID given to OnExternalAccountDownload a remote POP3 server JScript The error text and failed recipient given to OnDeliveryFailed a remote mail server JScript The name in a rule's Run function action whoever may edit a rule, a domain's own administrator included JScript and VBScript Which servers are affected. The flaw is CVE-2026-100741; the record was not yet public when this was written. The flaw is in every release from 6.0.0 to 6.3.3: the escaping was checked at every one of those tags (GitLab issue 27). Treat any server older than 6.3.4 with event scripting on and JScript as its language as affected. A rule's Run function action wrote its function name into the call unescaped, in either language, so VBScript servers too. What 6.3.4 changes. Every JScript value is escaped in one helper, ScriptServer::EscapeForJScriptLiteral: every backslash first, then both quotes, CR, LF, U+2028, U+2029 and NUL; OnBackupFailed's reason, which neither language escaped and the table above does not list, is escaped in both. A rule's Run function name must be a plain identifier. On Linux the handler is called with values as values, so nothing is quoted into source text. A JScript handler now receives every value exactly as it was, backslashes included; one that undid the old half-escaping by hand must stop doing so. Until you can upgrade, switch event scripting off. That is the only measure that closes every path, in either language. A server that cannot do without its scripts can narrow what is open: Take the OnClientValidatePassword, OnExternalAccountDownload and OnDeliveryFailed handlers out of a JScript EventHandlers.js and reload the scripts. Check the reload: one that fails (HM5710 in the ERROR log) keeps the earlier script in force, those handlers included. In either language, let nobody but the server administrator make or edit a rule with a Run function action. Roles.md lets an account's rules be written over COM by the server administrator, an administrator of its domain, or the account itself, so "whoever may edit a rule" is wider than administrators. Narrow ScriptAllowedObjects from *, which limits what an injected script can create. The key exists from 6.2.25, and before 6.3.4 it applies only after a service restart. Moving a handler to VBScript closes the password, UID and error-text paths and leaves the rule path open. Before you upgrade, check your rules' Run function actions and put the function's bare name in any that does not qualify. After it, an HM5018 in the ERROR log (on Linux, a Script Error line naming the function) means one was missed. Upgrading to 6.3.4 covers the upgrade itself. Reporting a vulnerability goes through a confidential GitLab issue or the Service Desk address contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, and not through a public issue or this forum. What it does not do OnClientValidatePassword is not consulted for SCRAM, which has no password to give it. Where to read more EventScripts.md: the member-by-member contract of every object, and the engine. The COM API and Scripting, sections 23.6 to 23.10. Rules and Sieve for the Run script function action, and Security Hardening, section 17.11. The public documentation's event scripts chapter. On this forum: hMailServer on Linux: what you get and what you do not, the 6.3.4 security announcement, the COM API on Windows and rules and the Run function action.
  • 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
    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
    145 Views
    ProgressiverobotP
    Written at 6.3.4, when no image was published. hMailServer 6.3.6 (6 October 2026) publishes the first: registry.gitlab.com/hmailserver/hmailserver:6.3.6, also tagged 6.3 and latest, one index over linux/amd64 and linux/arm64, signed with cosign as the release's pipeline; the Helm chart is a file of the release, and the packaging Compose file now runs PostgreSQL 18. See the 6.3.6 announcement and ContainerImage.md at the v6.3.6 tag. hMailServer 6.3.7 (17 October 2026), a security release, is registry.gitlab.com/hmailserver/hmailserver:6.3.7, for linux/amd64 and linux/arm64; in the container the update check names the image to pull and installs nothing, and the schema stays at 6108, so a container recreated on it from 6.3.6 changes no table. Its IMAP and POP3 listeners take the client's address from a PROXY protocol header, as the SMTP listener does, each from the peers on a list of its own, both off by default. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026), a security release, is registry.gitlab.com/hmailserver/hmailserver:6.3.8, and 6.3 and latest now name it; the schema stays at 6108. Its image carries MariaDB Connector/C, so HM_DB_TYPE=MySQL reaches a MariaDB server, where the images of 6.3.7 and earlier stopped at the first start, and its entrypoint secures a new installation at its first start by itself, whatever the container's command, so the run by hand below is left for a database its --create-database made more than a day before. The Helm charts of 6.3.6 and 6.3.7 never started a pod; 6.3.8's runs it as 999:999, fills new volumes in an init container and leaves database.port to the type's own port. Move a release of the older chart with helm repo update, helm upgrade <release> hmailserver/hmailserver --reset-then-reuse-values (Helm 3.14 or later) and then kubectl delete pod <release>-hmailserver-0, since the StatefulSet never replaces the stuck pod by itself (--reuse-values would keep the old chart's fsGroup 1000 and database.port 5432); where a cluster's policy refuses every sysctl, set unprivilegedPortsSysctl: false. For a Compose deployment made from an earlier Compose file, keep its HM_REST_CERTIFICATE_FILE, HM_REST_PRIVATE_KEY_FILE and ./certs mount in the new file, the key readable by 999, or its REST API does not start. See the 6.3.8 announcement. The Linux server is also a container image: the x86-64 .deb installed on Ubuntu 24.04 and run as the hmailserver user, with the database connection, the REST listener, the administrator password and, since 6.3.4, every other setting taken from the environment at start. This topic describes the image, the Compose file and the Helm chart as they are in the tree at the v6.3.4 tag, and the HM_* variables the entrypoint reads. The one thing to know first, from the 6.3.4 changelog: no container image of 6.3.4 is published. The images published on ghcr.io/progressiverobot/hmailserver before GitHub's suspension answer 404, and registry.gitlab.com/progressiverobot/hmailserver, which the Compose file and the chart now name, holds nothing. Meanwhile, build the image from the release's .deb and use your own tag wherever the files say registry.gitlab.com/.... What a Linux server is and is not is in hMailServer on Linux. Building the image yourself Use the docker directory of the same tag as the package: 6.3.4's entrypoint runs verbs a 6.3.3 binary does not have. git clone https://gitlab.com/hmailserver/hmailserver.git cd hmailserver git checkout v6.3.4 cd hmailserver/source/Server/platform/packaging/docker mkdir packages curl -L -o packages/hmailserver_6.3.4_amd64.deb \ https://gitlab.com/hmailserver/hmailserver/-/releases/v6.3.4/downloads/hmailserver_amd64.deb docker build -t hmailserver:6.3.4 . The Dockerfile copies packages/hmailserver_*_amd64.deb (the HM_DEB build argument names another path) and pins its Ubuntu base by digest. 6.3.4's package carries no Sigstore bundle to check it against, as 6.3.3's did. The image is x86-64 only: the Dockerfile takes the amd64 package. What is in the image, and what is not In: /usr/bin/hmailserver, the packaged hMailServer.ini at /etc/hmailserver/, the database scripts, the Control Deck, the fail2ban filter and jail the package carries, ca-certificates for the TLS the server does as a client, and an entrypoint. About 40 MB. The process runs as hmailserver; ports below 1024 are bound through cap_net_bind_service on the binary, not through root. Since 6.3.4 the Dockerfile installs the package's two Recommends, 7zip and python3: without them a backup failed with "Could not add the backup settings to the archive", and hmctl had no Python 3. Not in: the database, and it never will be. Point the image at a PostgreSQL or MariaDB server, in another container or anywhere else; on first start the entrypoint creates the schema there, and on every later start upgrades it if it is behind. Also not in: curl, an editor, or a shell of any consequence beyond bash. There is no Control Panel and no COM; the REST API is the administration. The variables the entrypoint reads itself Sixteen of these are older than 6.3.4 and keep their names and meanings. Variable Writes Notes HM_DB_TYPE [Database] Type PostgreSQL or MySQL (MariaDB is MySQL). Setting this is what makes the section be written at all HM_DB_HOST, HM_DB_PORT Server, Port Defaults db, and the type's port (5432 or 3306) HM_DB_NAME, HM_DB_USER, HM_DB_PASSWORD Database, Username, Password Defaults hmailserver, hmailserver, empty. The user needs the right to create the database on first start HM_DB_TLS_MODE, HM_DB_TLS_CA TlsMode, TlsCaFile Since 6.3.4. disable, prefer (empty, the default), require or verify-full, and the path of a PEM file of CA certificates mounted into the container HM_ADMIN_PASSWORD [Security] AdministratorPassword Through the server's own --set-admin-password: the file holds a PBKDF2 hash, never the password. Without one the REST API refuses to start HM_REST_PORT RestApiPort The packaged file has 0, which is off; 8045 is the conventional port HM_REST_BIND_ADDRESS RestApiBindAddress The packaged file has 127.0.0.1, which inside a container is the container's own loopback HM_REST_CERTIFICATE_FILE, HM_REST_PRIVATE_KEY_FILE RestApiCertificateFile, RestApiPrivateKeyFile Paths inside the container, readable by the hmailserver user. Paths the server reads, not _FILE forms HM_DB_PASSWORD_FILE, HM_ADMIN_PASSWORD_FILE the same two secrets Read from a file: a Docker or Compose secret. Setting both a variable and its _FILE is refused HM_CREATE_DATABASE, HM_UPGRADE_DATABASE nothing 0 skips --create-database or --upgrade-database at start. Both are no-ops when there is nothing to do HM_INI which file is written Default /etc/hmailserver/hMailServer.ini HM_PROFILE, HM_PROFILE_CONFIRM nothing Since 6.3.4: the setting profile applied once, and its confirmation On 6.3.3 and earlier the entrypoint writes these into hMailServer.ini on every start, and every other setting is the packaged file in the /etc/hmailserver volume. Since 6.3.4 the database is the settings store, so the four HM_REST_* variables are aliases of HM_SETTING_RestApiPort and its three neighbours, stored like any other setting; [Database] is still written into the file at every start, because it says where the database is. The image's /etc/hmailserver is owned by the hmailserver user, so there, unlike on a package installation, the server keeps its copy of each stored value in the file, and a --create-database that creates the schema also makes the stored-secrets key. Every setting from the environment (since 6.3.4) After --create-database and --upgrade-database, and before the server, the entrypoint runs the server's own verb, hmailserver --apply-environment, whenever there is anything for it to do. HM_SETTING_<key> sets a [Settings] key and HM_PROPERTY_<name> a database setting (HM_SETTING_MaxSubmissionsPerIPPerMinute=30, HM_PROPERTY_maxmessagesize=20480), and either with _FILE reads the value from a file, a Compose secret or a Kubernetes Secret mounted as one. The environment wins, at every start. An administrator cannot change a setting the environment names until the variable is unset and the container restarted, and a ConfigMap change, too, is applied by a restart and not live. Everything is checked before anything is stored. When anything is refused, nothing is stored and the container does not start; docker compose logs or kubectl logs says which variable and why. The naming rule, every refusal, what the REST API and the Control Deck show for a setting the environment manages, and how a value from a file stays secret are in configuration from the environment. HM_PROFILE names one of the four setting profiles, office, gateway, relay or sender, which the entrypoint applies once, with --apply-profile NAME --once, after the environment's settings and before the server starts. HM_PROFILE=none records that none is wanted. A profile that closes a listener, trusts an address range, turns a filter or a check off or weakens it, or shortens a window of days needs HM_PROFILE_CONFIRM=yes, and without it the container does not start. A profile skips a setting the environment manages, naming its variable. Not yet run in a container. The verb has been run as hMailServer.exe --apply-environment on Windows, and its POSIX half compiles; the entrypoint as a whole, the POSIX verb against a database, the chart and the Compose file have been read and not executed. HM_PROFILE is in the same state. Try it on a scratch database first. Reaching the API The server requires TLS on a REST listener that is not bound to loopback. A listener left at the packaged bind address answers only processes inside the container, which is none, since the image carries no client. Two ways out; either will do: Bind it and give it a certificate. HM_REST_BIND_ADDRESS=0.0.0.0, with HM_REST_CERTIFICATE_FILE and HM_REST_PRIVATE_KEY_FILE pointing at a certificate and key mounted into the container, and the port published. Publish it on the host's loopback (127.0.0.1:8045:8045) unless the certificate is one clients will trust. Host networking (--network host). The container's 127.0.0.1 is the host's, the packaged bind address is right as it is, no certificate is needed, and the API is at http://127.0.0.1:8045 from the host and through an SSH tunnel from elsewhere. The mail ports are then the host's ports directly. First start, with Compose The file to use is in the repository, at hmailserver/source/Server/platform/packaging/docker/docker-compose.yml. It reads the two passwords as Compose secrets from files, mounts the REST certificate and key in, and carries the HM_SETTING_ and HM_PROPERTY_ examples commented out. A workflow resolved it with docker compose config on every change until 18 September 2026, so it is known to parse. Below is the mail service of the wiki's shorter example, which puts it under services: beside a postgres:16 service named db and declares the named volumes. It names the image built above, not the empty registry: mail: image: hmailserver:6.3.4 depends_on: - db environment: HM_DB_TYPE: PostgreSQL HM_DB_HOST: db HM_DB_NAME: hmailserver HM_DB_USER: hmailserver HM_DB_PASSWORD: change-me HM_ADMIN_PASSWORD: change-me-too HM_REST_PORT: 8045 HM_REST_BIND_ADDRESS: 0.0.0.0 HM_REST_CERTIFICATE_FILE: /certs/rest.pem HM_REST_PRIVATE_KEY_FILE: /certs/rest.key volumes: - ./certs:/certs:ro - hm-data:/var/lib/hmailserver - hm-logs:/var/log/hmailserver - hm-etc:/etc/hmailserver ports: - "25:25" - "587:587" - "993:993" - "127.0.0.1:8045:8045" restart: unless-stopped restart: unless-stopped matters on the first start: depends_on starts the database container first but does not wait for it to accept connections, so the entrypoint's --create-database fails and the container exits if the database is not there yet; the restart tries again a moment later. The first start logs the [Database] section written, the administrator password hashed and the database created at schema 6104, and also what --create-database did about the stored-secrets key and one line per HM_SETTING_ or HM_PROPERTY_ variable stored. The first domain is then one request away (First install on Linux). Passwords in a Compose file are passwords in a file: for anything beyond a first look, use HM_DB_PASSWORD_FILE and HM_ADMIN_PASSWORD_FILE with Compose secrets. The secure defaults of a new installation are not applied in a container by themselves. On a package installation the systemd unit's first start gives a database --create-database made a self-signed certificate, STARTTLS and TLS required for authentication on the Internet IP range. The image has no unit, so that step is run by hand, as the service's user, with the server stopped. SecureDefaults.md gives the verb and no container command for it, and lists its refusal of a running service (exit 4) for Windows only: hmailserver --apply-new-install-defaults A run by hand has no one-day deadline, so an image first booted days after its --create-database is secured this way. What it changes, its exit codes and the checklist item that says what is left are in Hardening a public mail server. Kubernetes, with the chart Since 6.3.4 the chart is at hmailserver/source/Server/platform/packaging/helm/hmailserver. It is not published to a chart repository; install it from a checkout. It renders a StatefulSet of one, because a message store is not something two pods may share: the three volumes the image declares become claim templates, and the database is pointed at, never bundled. The chart refuses to render without database.host. Its image.repository names the empty registry, so set it to the image you built, pushed to a registry the cluster's nodes can pull from: helm install mail ./hmailserver \ --set image.repository=<your registry>/hmailserver \ --set database.host=postgres.databases.svc.cluster.local \ --set database.password=... \ --set administrator.password=... \ --set rest.tlsSecretName=mail-tls Value What it is image.repository registry.gitlab.com/progressiverobot/hmailserver as shipped, which holds no image: set it to where you pushed the image you built database.host, .type, .name, .user The PostgreSQL or MariaDB the server uses; required. database.create and database.upgrade make the schema on first start and move it forward on an upgrade database.password, administrator.password Go into a Secret the chart makes. Point existingSecret at a Secret you made and the chart writes none; its two keys are named by existingSecretKeys rest.enabled, .port, .tlsSecretName The one listener that serves the REST API, the Control Deck and the webmail. Off loopback the server requires TLS, so without a kubernetes.io/tls Secret named here the listener will not start; the chart's notes say so on install settings, properties, settingsFrom Since 6.3.4: any [Settings] key (rendered as HM_SETTING_<key>), any database setting (HM_PROPERTY_<name>), and a key of a Secret you made, projected into /run/secrets/hmailserver-settings/<n> and named through _FILE, so a secret value is in no environment variable, rendered manifest or log line service.type, .ports, .externalTrafficPolicy A LoadBalancer with the mail ports, and the REST port when the listener is on. Local keeps the client's address, which auto-ban, greylisting and the Received header all need persistence.* The message store, the logs and the configuration, one claim each Probes Port 25, as the image's own health check is, or the metrics listener's /livez and /readyz when that listener is on settings, properties and settingsFrom look like this in a values file; a settingsFrom entry says setting: in place of property: for a [Settings] key: settings: MaxSubmissionsPerIPPerMinute: "30" properties: maxmessagesize: "20480" settingsFrom: - secretName: mail-relay key: password property: smtprelayerpassword A switch is 1 or 0 for a [Settings] key; true, false, yes, no, on and off are taken as well for a setting whose REST settings row is a switch, and only there: maxmessagesize=off is refused rather than stored as 0, which is "no limit". Volumes, ports and the health check Path What lives there /var/lib/hmailserver The mail store, and .hmailserver-secret-key, the key under which this machine's stored secrets are protected. The key travels with this directory: a database restored beside a fresh data volume cannot open the secrets it holds /var/log/hmailserver The logs. Bind-mount it to a host path if fail2ban on the host is to read it /etc/hmailserver hMailServer.ini; since 6.3.4 the only home of PasswordPepper and [Security] StoredSecretsKey, which no database and no backup holds The image exposes 25, 110, 143, 465, 587, 993, 995, 4190 (ManageSieve) and 8045; publish the ones you serve. The health check opens a connection to the container's own port 25 every 30 seconds after a 60-second start period, and three failures in a row mark the container unhealthy. Upgrades, backups and auto-ban Upgrading is building the image of the new tag, as no 6.3.4 image is published to pull, and recreating the container: the entrypoint runs --upgrade-database on every start, so a schema that is behind is brought forward before the server opens a port. On MySQL and MariaDB the 6102 step rebuilds every table as utf8mb4, which takes time on a large database and needs the database server version that Databases on Linux gives. An upgraded database gets no stored-secrets key until one is made with hmailserver --create-stored-secrets-key, and never the new installation's defaults. Take the backup first (Upgrading to 6.3.4). Backups are three things together: the database, the /var/lib/hmailserver volume, and /etc/hmailserver. Two things are only in /etc/hmailserver/hMailServer.ini: a PasswordPepper, if one is set, without which a restored database refuses the password of every account hashed with Argon2id or scrypt, and the stored-secrets key, without which a restored database opens no account's second factor and no [Settings] secret. Export the key once, and keep the file with the database backups, apart from them: docker exec <container> hmailserver --export-stored-secrets-key /var/lib/hmailserver/stored-secrets.key docker cp <container>:/var/lib/hmailserver/stored-secrets.key ./stored-secrets.key A replacement container takes it back with --import-stored-secrets-key FILE. Auto-ban works as everywhere: an address that fails to log on repeatedly is refused by the listeners. The hook that adds the address to the host's firewall cannot run from inside a container, so run fail2ban on the host instead: bind-mount /var/log/hmailserver, copy the filter and jail out of the image, point the jail's logpath at the mounted directory, and enable it. What was proven, and what has not been Until 18 September 2026 the image job of the GitHub workflow built the image on every release tag, pushed it, and smoke-tested the pushed image as the example above runs it: the container healthy, the SMTP banner and the API answering, and the administrator password in no file in clear. Nothing has run it since. GitLab CI's linux-image job is written to build the image from the .deb with kaniko and inspect it; it neither pushes nor runs the image, so the smoke test has no replacement yet. The chart-and-compose job is written to lint and render the chart and resolve the Compose file. Neither had run when ContinuousIntegration.md was written, on 25 September 2026. The changelog's 6.3.4 section records the first full pipeline the day after: it finished failed, the image job among those that passed, and no pipeline has run on the fixed tree since. 6.3.4 was made by hand. Where to read more Running in a Container and Profiles and Environment on the wiki. EnvironmentConfiguration.md for the naming rule and every refusal, Profiles.md for every entry of the four profiles, SecureDefaults.md for the verb run by hand, and DatabaseTls.md for HM_DB_TLS_MODE. The 6.3.4 section of CHANGELOG.md for the state of the image. On this forum: the 6.3.4 announcement, hMailServer on Linux, First install on Linux and Upgrading to 6.3.4; configuration from the environment for HM_SETTING_ and HM_PROPERTY_ in full, and the hmailserver command line on Linux for the verbs the entrypoint runs.
  • 0 Votes
    1 Posts
    145 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.6 (6 October 2026) carries .deb and .rpm packages for x86-64 and AArch64, one for each of Debian 12 and 13, Ubuntu 24.04, Fedora 43 and 44, Enterprise Linux 9 and 10, and openSUSE Leap 16.0 and Tumbleweed, both AppImages and the first container image. Its commands say what they did on their own output rather than in the error log, and the store commands run as root refuse a data directory that another account could have linked into. See the 6.3.6 announcement. From 6.3.6 the root helper applies hMailServer 6.3.7 (17 October 2026), a security release, by the live update, for the generic .deb and .rpm of either architecture and both AppImages; a server from one of 6.3.6's per-distribution packages is offered the generic package, which apt refuses on Debian 12 and dnf on Enterprise Linux 9, and is upgraded with its package manager and its release's own package, and from 6.3.4 or 6.3.5 install 6.3.7 with apt, dnf or zypper, not by the live update. A server whose UpdateSigningIdentity, UpdateSigningIssuer and UpdateSourceRepository still name the path the project began at refuses 6.3.7 and 6.3.8 until sudo hmailserver --set-setting <name>= clears each of the three and the service is restarted. See the 6.3.7 announcement. From 6.3.6 or 6.3.7 the root helper applies hMailServer 6.3.8 (2 November 2026), a security release, by the live update in the same way; a server from one of 6.3.6's or 6.3.7's per-distribution packages is upgraded with its package manager and its release's own package, and from 6.3.4 or 6.3.5 install 6.3.8 with apt, dnf or zypper rather than by the live update, or turn the live update's apply off first with systemctl disable --now hmailserver-update.path. 6.3.8 names files in UTF-8 whatever LANG says: on a server that ran under a LANG that is not UTF-8, rename each file and folder named outside ASCII to its UTF-8 name before upgrading, for example with convmv -f iso-8859-1 -t utf-8 -r --notest <folder>. The values --set-setting, --apply-profile, --account and --domain take, and --config, are read as UTF-8 and refused with exit 2 when they are not. See the 6.3.8 announcement. On Linux the server is one executable, /usr/bin/hmailserver, and what cannot be done over the REST API, from creating the schema to rewriting the message store, is done with its own options. This topic lists every verb 6.3.4's documents name and who runs it, where the package puts its files, what the systemd unit lets the service touch, and how the root helper applies a live update. What a Linux server has and lacks is in hMailServer on Linux, the first install in First install on Linux. The one thing to know first: on a package installation /etc/hmailserver/hMailServer.ini is 0640 root:hmailserver, and the service cannot write it. Where the package puts things Path What it is /usr/bin/hmailserver the server, one file /usr/bin/tlds.txt, /usr/bin/dh2048.pem read from beside the binary; the second is required by every TLS context /usr/share/hmailserver/DBScripts/*.sql the schema creation and upgrade scripts /usr/lib/systemd/system/hmailserver.service the unit /etc/hmailserver/hMailServer.ini the configuration, a conffile an upgrade never overwrites; since 6.3.4 its [Settings] section is a copy of the database /usr/bin/hmctl since 6.3.4, the command-line client over the REST API /etc/logrotate.d/hmailserver the log rotation rule /etc/fail2ban/filter.d/hmailserver.conf, /etc/fail2ban/jail.d/hmailserver.conf the fail2ban filter and jail, the jail disabled as shipped /usr/lib/hmailserver/hmailserver-update, /usr/lib/hmailserver/autoban-hook the root update helper (since 6.3.4) and the firewall hook /var/lib/hmailserver, with temp, events and database under it the mail store, one directory per domain and one per mailbox; scratch space; event scripts /var/log/hmailserver the logs What the maintainer scripts do. They create the hmailserver system user and group, and the last two rows above, all 0750 hmailserver:hmailserver; install the configuration; and enable hmailserver.service without starting it, on purpose, because there is no database yet. On an upgrade they run hmailserver --upgrade-database as the service user, only if a database is configured, then try-restart the service if it was running. On removal they stop and disable, and leave the mail store and the logs alone; a .deb purge removes the configuration as well, and /etc/hmailserver with it when nothing else is in it, and still leaves the store. Which packages exist. 6.3.4 carries two x86-64 packages, hmailserver_6.3.4_amd64.deb and hmailserver-6.3.4-1.x86_64.rpm, and no container image of 6.3.4 is published. The .deb installs on Ubuntu 26.04 and 24.04, not on Debian 12; the .rpm on Fedora 43 and 44 and Enterprise Linux 10, not on Enterprise Linux 9; the rest of the platform picture is in hMailServer on Linux. The packages carry no Sigstore bundle, so no live update installs them: 6.3.4 is installed by hand (Upgrading to 6.3.4). Who runs what As root (sudo), because the file is root's: --set-admin-password, --set-setting, and the four stored-secrets verbs. All but the export write /etc/hmailserver/hMailServer.ini, and the export a new file of its own (below); --set-setting keeps the file's owner and mode. As the service user (sudo -u hmailserver), because the verb opens the database or writes the data directory: the rest, from --create-database to the store walks. --apply-new-install-defaults refuses to run as root (exit 2), whose certificate key the service could not read, and --sample-domain run as root leaves today's log owned by root. Two things follow from the file being root's. The running server cannot write its copy of a stored value back into [Settings]: a value stored over the API is in effect and the file's line goes stale, which the server says once a start in the application log, not as an error. The stale line is never taken for an edit, so nothing needs changing by hand; only what reads the file itself, such as a server that cannot read its table, sees the older value (how the settings store works). The exception is a [Settings] secret sealed once the stored-secrets key exists (below): its line cannot be rewritten, and HM5803 names it at each start until sudo hmailserver --set-setting <name> writes it. And a server that verifies an older MD5 or SHA-256 administrator password hash cannot write it again as PBKDF2: it says so once a process (HM7494), and sudo hmailserver --set-admin-password does it. Every verb Every row from --check-database to the stored-secrets verbs is new since 6.3.4. Verb Runs as What it does --config <file> The configuration to read. Default: hMailServer.ini beside the executable, then /etc/hmailserver/hMailServer.ini --foreground the unit Run in the foreground and log to standard error as well as the log directory. It is the default --check-config service user Print the configuration path, the directories and the database type, and exit without opening a listener or the database. Database type: 0 means the Type key did not take --set-admin-password root Read a new administrator password from standard input with echo off, hash it with PBKDF2, and write it to the file --create-database service user Create the database [Database] names, or use an existing empty one, and run the create script for its type. Refuses SQL Server and SQL Server Compact by name. Since 6.3.4, says the wiki's Installing on Linux, a database that 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 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 --upgrade-database service user Run every upgrade script from the database's version to this build's, in order. Refuses a database from a newer build; stops rather than loops if a script runs and the version does not move. Safe to run by hand --check-database service user Connect once with TlsMode and TlsCaFile, say whether the connection is encrypted and the certificate checked: exit 0 connected, 1 not, 2 not configured. Changes nothing, and runs beside a running service --set-setting NAME=VALUE, --set-setting NAME root Store one [Settings] value in the database and write the server's copy into the file (below) --apply-environment service user Store every setting the environment names (HM_SETTING_<key>, HM_PROPERTY_<name>, their _FILE forms, the container's HM_REST_*), each checked first: exit 1, with nothing stored, if one is refused. It writes the file's copy too, so the file must be writable, as the container's is; where it is not, as for the service user on a package installation, the verb refuses before it stores anything. EnvironmentConfiguration.md says it has been run on Windows only, and on Linux compiled but not run: try it on a scratch database first --apply-profile NAME [--confirm] [--once] [--if-new], --revert-profile service user, server stopped Apply one of the four setting profiles (office, gateway, relay, sender; none records that none is wanted), or put back what the last one changed. Exit 3 means an entry needs --confirm, 5 that --if-new found ten accounts or more. On Windows the verb refuses while the service runs (exit 4); Profiles.md says that on Linux nothing tells it whether a server runs, and that the POSIX verbs have been written and read, and not run: try them on a scratch database first --apply-new-install-defaults [--if-marked [<file>]] service user, server stopped The secure defaults of a new installation (below). Exit 0 applied, 1 something could not be written (HM7510, HM7511), 2 no database or run as root, 5 not a database this installation created, or not as the create script left it --sample-domain create, --sample-domain remove service user Make or remove the throwaway example.test. Exit 0 done, 2 refused, 1 failed. Remove it before the server takes real mail --measure-store service user What compressing the store and holding its large parts once would save. Changes nothing, and runs beside a running server --compress-store [--decompress], --recount-store service user, server stopped Rewrite the store in place, or back to plain RFC 822; recount the parts held once. Refused while the server runs (exit 4): on Linux the server holds a lock in the log directory for as long as it runs --encrypt-store, --decrypt-store service user, server stopped Encrypt what a mailbox encrypted at rest stored before it was switched on, or put it back. --decrypt-store reads the domain's recovery passphrase from HMAILSERVER_RECOVERY_PASSPHRASE, never from the command line --copy-store-to-object-storage, --copy-store-from-object-storage service user Copy the message store into the object store the settings name, or back, each file verified and a stopped run resumed. They run beside the server; --remove-source removes what was copied from once its copy is verified, and only with the server stopped (exit 4 beside it) --verify-update <file> Check a downloaded package or AppImage against the Sigstore bundle beside it; exit 0 when a signer the server trusts made it --create-stored-secrets-key, --export-stored-secrets-key <file>, --import-stored-secrets-key <file>, --forget-sealed-secrets <key id> root Make the stored-secrets key, export it, import it on another node, or, with the server stopped (exit 4 beside it), clear what was sealed under a key lost for good (below) --version, --help --measure-store, --compress-store, --encrypt-store, --decrypt-store and the two copies take --account <address> for one mailbox or --domain <name> for one domain's, and neither for the whole store; --decrypt-store needs one of the two. Signals. SIGTERM and SIGINT stop the server. SIGHUP, which systemctl reload hmailserver sends, re-reads the configuration; it stops and restarts the listeners inside the running process, so do it when nothing is mid-delivery. Since 6.3.4 the server drains before it stops: the mail listeners close at the socket, so senders retry, and ShutdownDrainSeconds lets what is in flight finish. At its default of 0 nothing is waited for, and a session mid-DATA at the moment of the stop is cut: set it on a production server, where Upgrading.md calls 30 a reasonable figure. --set-setting: the way in before the API exists (since 6.3.4) From schema 6042 the database is the settings store. Every [Settings] key the file holds at the server's first start is taken into the database then, the packaged RestApiPort=0 among them. Afterwards a line edited in the file is reported in the error log as HM5804 and the stored value is used, so an edit to the file brings no listener up, and with the listener off nothing that speaks the API can change it either. sudo hmailserver --set-setting RestApiPort=8045 sudo hmailserver --set-setting RestApiBindAddress=127.0.0.1 sudo systemctl restart hmailserver The verb stores the value as the REST API does and prints what it stored. It refuses a name the server's settings loader does not read, a whole number that is not one, a port that is not 0 to 65535, and a RestApiBindAddress that is not an IP address or localhost: the checks --apply-environment makes, in the same words. A secret's value is refused on the command line, where the shell's history, the process list and sudo's log would keep it. Give the name alone, sudo hmailserver --set-setting SRSSecret, and type the value at a prompt that does not echo it, or pipe it in. Which sections and keys stay in the file rather than the database, and the file's [SettingsOverride] section, which forces a value over the stored one (HM5807, HM5808), are in Settings: what is not stock, when a save takes effect. The stored-secrets key on Linux (since 6.3.4) A Linux server has two secret stores. The machine's is the key file <DataFolder>/.hmailserver-secret-key, mode 0600, made by the server on first use: the database password in the configuration and the route and fetch-account passwords are kept under it as LINUX1: envelopes. It travels with the data directory. A secret moved to a machine without it cannot be opened (HM6414), and there is no re-keying command: replacing the file means re-entering every stored password. The installation's is the stored-secrets key, [Security] StoredSecretsKey, under which each account's TOTP second factor and the [Settings] secrets are sealed in the database as HMK1: envelopes, so that every node sharing the database can open them. The service never makes it. On a package installation --create-database runs as the service user, cannot write the root-owned file, makes no key and says what to run instead. An upgrade makes none. Until a key exists those secrets are kept as written, and the setup checklist's stored_secrets_key item offers the step. Make the key, export it and restart the service; nothing is sealed until that restart: sudo hmailserver --create-stored-secrets-key sudo hmailserver --export-stored-secrets-key /root/hmailserver-stored-secrets.key sudo systemctl restart hmailserver Keep the exported file with the database backups, apart from them: a database dump restored without it brings back every second factor and [Settings] secret unopenable. Carrying the key to a standby with --import-stored-secrets-key, what a node without it says (HM7495) and --forget-sealed-secrets for a key lost for good are in Hardening a public mail server. The unit, and what it lets the service touch The packaged unit runs the server with --foreground as hmailserver, and before every start: ExecStartPre=-/usr/bin/hmailserver --apply-new-install-defaults --if-marked TimeoutStartSec=300 The unit's hardening: ProtectSystem=strict, which leaves /etc read-only to the service; /usr read-only, which with the single capability below is why the service can neither run the package manager nor stop itself; ProtectHome=yes, which hides /home; ReadWritePaths of /var/lib/hmailserver and /var/log/hmailserver; RuntimeDirectory=hmailserver, which is /run/hmailserver; and one capability, CAP_NET_BIND_SERVICE, which binds the mail ports. CAP_NET_ADMIN is deliberately not granted, so the firewall hook behind AutoBanFirewall cannot act until a drop-in (systemctl edit hmailserver) adds it to AmbientCapabilities and CapabilityBoundingSet; sudo -u hmailserver /usr/lib/hmailserver/autoban-hook check says whether it can. A DataFolder outside ReadWritePaths needs a drop-in too, before the first start: sudo systemctl edit hmailserver # [Service] # ReadWritePaths=/srv/mail A memory-safety fault is recorded in one line of /var/log/hmailserver/crash-oracle.log; core dumps are off by default. The first start secures a new installation. On a database --create-database made, the ExecStartPre line gives the server a self-signed certificate, STARTTLS on 25, 587, 110 and 143, and TLS required for authentication on the Internet IP range; mail clients warn about the certificate until ACME (Let's Encrypt certificates) or your own replaces it. It acts only on a database marked within a day of its clock and still as the create script left it; an older mark is kept for a run by hand, sudo -u hmailserver hmailserver --apply-new-install-defaults with the server stopped, and an upgraded or attached database is never changed. The mark, the certificate and the checklist item that follows up are in Hardening a public mail server. The root helper hmailserver-update (since 6.3.4) A live update is applied outside the server's unit. The server writes a one-file request into /run/hmailserver; hmailserver-update.path, a root path unit the package enables, takes it and runs /usr/lib/hmailserver/hmailserver-update, a shell script readable end to end. The script: checks every line of the request against root's own configuration; verifies the package, and the rollback image, again as root against their Sigstore bundles, with hmailserver --verify-update: the check the server made, made by a process the server could not have influenced; refuses anything that is not a genuine upgrade: the package's own version is compared with what dpkg or rpm says is installed, so a signed but superseded release cannot be put back; stops the unit, installs with apt-get/dpkg or dnf/rpm, starts the unit, and waits for the server to stay up, and for /readyz when the metrics listener is on; reinstalls the previous version if it does not, and writes one line to <DataFolder>/Updates/last-apply.txt, which the server reports at its next check and the Control Deck shows. A refused request is explained in journalctl -u hmailserver-update and nowhere else, on purpose. sudo /usr/lib/hmailserver/hmailserver-update check names the installer it would use, whether the path unit is active and whether the running unit has the runtime directory the request needs. The Debian and RPM packages enable and start the path unit; Arch does not enable units from a package, so there sudo systemctl enable --now hmailserver-update.path is yours to run. Nothing happens unless the update check is on and the install is asked for, from the Control Deck or over the API, and only for a release signed by a signer the server trusts, which 6.3.4 is not. hmctl and the PowerShell module The other command line is hmctl (/usr/bin/hmctl, one Python 3 file), with the HmailServer PowerShell module beside it in the repository. Both speak the REST API, so both administer a Windows or a Linux server, here or on another machine. They cannot turn the REST listener on, set the administrator password, or make or carry the stored-secrets key. They have a topic of their own: hmctl and the PowerShell module. Logs and the journal The server's own logs are in /var/log/hmailserver. The journal (journalctl -u hmailserver -f) carries the startup line, standard error and the operational events: WindowsEventLogEnabled=1 is a Windows inheritance in name only, and on Linux means syslog. The logrotate rule uses copytruncate, because the server does not reopen its log on a signal. Where to read more On the wiki: Installing on Linux, and Command Line for hmctl. CommandLine.md, SecretsAtRest.md, SecureDefaults.md, Profiles.md and MessageStore.md. The documentation page: 35.2 Supported platforms and packages, 35.7 Command-line reference, which lists none of the verbs new in 6.3.4, and 35.8 The systemd service. On this forum: hMailServer on Linux, First install on Linux, Upgrading to 6.3.4 and The Control Deck; Databases on Linux for --create-database and the schema upgrade; the update check and the live update for what the root helper is asked to apply; and message storage for what the store verbs change.
  • 0 Votes
    1 Posts
    159 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.6 (6 October 2026) is signed by the GitLab pipeline at the group path and installs by the live update from 6.3.4 or 6.3.5; from 6.3.6 the Control Panel installs an update with one click where the service is not an administrator, and on Linux a 6.3.4 or 6.3.5 server installs 6.3.6 with its package manager rather than by the live update, whose apply step on those versions is the one 6.3.6 fixes. See the 6.3.6 announcement. hMailServer 6.3.7 (17 October 2026), a security release signed at the same group path, was what the feed offered before 6.3.8: from 6.3.6 the live update installs it, with 6.3.6's file as its rollback image and no schema step between them; on Windows a service running as LocalSystem installs it itself, and under NT SERVICE\hMailServer an administrator presses Install update on the Control Panel's Server status page on the server, one Windows prompt; on Linux 6.3.6's own helper applies it to the generic packages and both AppImages, while a server from a per-distribution package upgrades with its package manager. From 6.3.4 or 6.3.5 what 6.3.6's notes said still holds, and a server whose UpdateSigningIdentity, UpdateSigningIssuer and UpdateSourceRepository still name the project's first path refuses 6.3.7 until the three are cleared, on 6.3.6 by deleting their lines from [Settings] in hMailServer.ini with an editor run as an administrator (on Linux sudo hmailserver --set-setting <name>=) and restarting the service. In 6.3.7's Control Panel, Updates is a page of its own under Maintenance. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026), a security release signed at the same group path, with the project's id, 86730559, in the certificate, is what the feed now offers: from 6.3.7 or 6.3.6 the live update installs it, with the running version's own file as its rollback image and no schema step between them, a service running as LocalSystem by itself and one under NT SERVICE\hMailServer by Install update on the server; from 6.3.4 or 6.3.5 what 6.3.7's notes said still holds; a server from one of 6.3.6's or 6.3.7's per-distribution packages is offered the generic package, which apt refuses on Debian 12 and dnf on Enterprise Linux 9, so upgrade it with its release's own package of 6.3.8; the three settings that name the project's first path refuse 6.3.8 too until they are cleared; and a server from 6.2.28 to 6.3.3, or from 6.3.3's AArch64 packages or AppImages, installs 6.3.8 by hand, once. See the 6.3.8 announcement. This topic covers the update check and the live update as 6.3.4 has them. The one thing to know first: no live update installs 6.3.4. It carries an Authenticode signature and no Sigstore bundle, so a server of any version that is offered it says it cannot be verified and downloads nothing. Every server installs 6.3.4 by hand, as Upgrading to 6.3.4 describes. A server from 6.2.28 to 6.3.3 has a second reason to do it soon: its live update will refuse 6.3.5, the first release to be signed on GitLab. Four steps, and where each is done Each step is separate and opt-in, and only applying runs anything. Step Control Panel (Windows) REST COM (Windows) Check Check for updates on Server status POST /api/v1/update/check Status.CheckForUpdate Download and verify Download update POST /api/v1/update/download Status.DownloadUpdate Apply Install update POST /api/v1/update/install Status.InstallUpdate Read the verdict the Server card of Server status GET /api/v1/update Status.UpdateState, Status.AvailableVersion, Status.UpdateLastError, Status.UpdateApplyOutcome The Control Deck's Update view does all four on either platform; on Linux there is no Control Panel and no COM. The state is 0 not checked, 1 up to date, 2 a newer release exists, 3 downloaded and verified, 4 installing, or 5 the last step failed (lastError says why). An on-demand check reads the feed whether or not the scheduled check is on. Starting an install from the Control Panel closes it, because the installer replaces its files. Turning the check on UpdateCheckEnabled is 0 by default, because a server must not call out to anyone until its administrator says it may. At 1 the server reads the feed at startup and then every UpdateCheckHours (24; below 1 is taken as 1, and the server sets no ceiling although the Control Panel's label says "1 to 168"). UpdateChannel is stable by default; prerelease makes pre-releases eligible. What it sends. A plain GET of the feed with nothing but the User-Agent the server always sends: no identifier, no configuration, no counts. Since 6.3.4 it no longer sends GitHub's X-GitHub-Api-Version header. Where the result shows. The application log, the Control Panel's Server status page, the Deck's Update view, COM, and GET /api/v1/update, which adds the release's page and date, the installer's address, size and digest, and the last apply's outcome. Setting the keys since 6.3.4. Use the Updates card of the Control Panel's API & monitoring page (in 6.3.7 Updates is a page of its own under Maintenance, and Ctrl+K takes the old title to it), PUT /api/v1/settings/ini/{name} with {"value": "..."} (administrator password only), hmctl ini set, or on Linux hmailserver --set-setting; the Deck has no editor for these keys. Every key here applies when it is saved, but a value stored with --set-setting while the service runs reaches it only at its next save, reinitialise or restart. Do not edit the [Settings] lines in hMailServer.ini: the database is the settings store, and Settings: when a save takes effect explains what happens to a line edited in the file. curl -u Administrator:pw -X PUT -H "Content-Type: application/json" -d '{"value":"1"}' http://127.0.0.1:8443/api/v1/settings/ini/UpdateCheckEnabled curl -u Administrator:pw -X POST http://127.0.0.1:8443/api/v1/update/check The feed Since 6.3.4 a server reads https://updates.progressiverobot.com without being told: /releases/latest, the newest stable release, on the stable channel, and /releases.json, every release, on the pre-release channel. GitLab Pages serves it from the project's releases. For the rollback the server looks one release up at /releases/tags/v<version>. Every address a server builds is a file answered 200, and the server follows no redirect. A release has the shape of GitHub's Releases API, and each installer's Sigstore bundle is an asset of its own, named after its file with .cosign.bundle added. A download is held to the size and the digest the feed gives. Since 6.3.4, a version with a suffix (6.3.5-rc1, 6.3.5rc1) is a pre-release whatever the feed's prerelease flag says, so the stable channel passes over it. A mirror. UpdateFeedUrl points the server at another feed, for a network without Internet access; empty is the built-in feed. It must be https unless it is on this machine. It may serve one release or a list, and the rollback's lookup keeps everything up to /releases of the value and adds /tags/v<version>, so the mirror answers that path too. A mirror changes where the server looks, not whom it trusts: every installer is still verified against the signers compiled in. Through a proxy. HttpProxy=host:port (or [ipv6]:port) sends every web request the server makes as a client through a forward proxy: the feed, the installer and its bundle, and the JWKS and token-introspection fetches of OAuth 2. An https address is tunnelled with CONNECT, and the certificate is verified against the real host. There is no place for credentials; a proxy that demands them refuses. A refusal reads The proxy <host:port> refused CONNECT to <host:port>: <its status line>, and a value without a port fails with HttpProxy must be host:port (or [ipv6]:port)., never quietly going direct. ACME and the outbound-relay OAuth2 token request do not use it. Download and verification Download update, POST /api/v1/update/download, Status.DownloadUpdate or UpdateAutoDownload=1 fetches the installer and its bundle into the data directory's Updates folder (Data\Updates on Windows; on Linux the file is the .deb or .rpm this server was installed from) and checks them without cosign and without the network: the file's SHA-256 is the one signed and the one the transparency log recorded; the certificate chains to Sigstore's Fulcio authority at the moment the log recorded the signature; the certificate is one of this project's signers, whole; the signature verifies under the certificate's key, and the log vouches for the entry under its own key. A file that fails is deleted, and the reason is in Status.UpdateLastError and the log. Nothing is run. The two signers, since 6.3.4. A certificate must match one entry whole. The GitHub workflow The GitLab CI pipeline Signs Every release from 6.2.19 to 6.3.3 The releases from 6.3.5 on: 6.3.5 at the path the project began at, 6.3.6 and later at the group path hmailserver/hmailserver Identity begins https://github.com/Progressiverobot/hmailserver/.github/workflows/sign-release.yml@ https://gitlab.com/Progressiverobot/hmailserver//.gitlab-ci.yml@refs/tags/v for 6.3.5; from 6.3.6, https://gitlab.com/hmailserver/hmailserver//.gitlab-ci.yml@refs/tags/v, the path the project moved to on 29 September 2026 Issuer https://token.actions.githubusercontent.com https://gitlab.com Repository id 1267388815, required 86730559, checked when the certificate carries one Held to Certificates issued before 18 September 2026, and releases up to 6.3.3 The tag the certificate names The pipeline is also trusted at the path of the group hmailserver the project is to move to, where the id 86730559 is required whatever any setting says. A server from 6.2.28 to 6.3.3 trusts the GitHub workflow alone. Further checks since 6.3.4. A file signed for one tag is refused as any other release. On Windows the installer's version resource must name the release it is installed as; on Linux the update helper installs nothing that is not an upgrade. On Windows only SYSTEM, the Administrators and the service's account may change the Updates folder; one that is a link, owned by anyone else or beyond repair is refused with HM6894, and nothing is downloaded or applied: delete it, and the server makes it again. Authenticode. UpdateRequireAuthenticode=1 adds, on Windows only, a check that the installer carries an Authenticode signature Windows trusts (no revocation check, no publisher pin). The installers have been signed since 6.3.1; 1 refuses earlier ones. It is on top of the Sigstore check, which cannot be turned off. The trust settings UpdateTrustRootsFile, UpdateLogPublicKeyFile, UpdateSigningIdentity, UpdateSigningIssuer and UpdateSourceRepository are for a site that signs its own releases. Leave them empty; set, the last three replace their part of every signer. Applying an update On Windows, Install update, POST /api/v1/update/install, Status.InstallUpdate or the unattended window does this, in order: Verifies the downloaded installer once more. Fetches and verifies the running version's installer as a rollback image, where the feed still offers it. Issues a single-use token for the database upgrade: 32 random bytes in a file only the machine's administrators can read, good for one hour and one use, given to the installer as /upgradetoken=<hex> in place of the administrator password. Drains, since 6.3.4: the SMTP, IMAP, POP3 and ManageSieve listeners close at the socket, so senders retry, and the queue pauses. It refuses with already draining while somebody else's drain is in progress: leave it, or wait, and apply again. Hands the installer to hMailServer.Updater.exe, copied from Bin into Data\Updates because the installer replaces Bin. The helper runs it silently, and only with the SHA-256 the server read; waits UpdateServiceWaitSeconds (180) for the service; and if it does not come back, reinstalls the previous version. Reports the outcome: one line, in the log, Status.UpdateApplyOutcome, the status page and GET /api/v1/update; since 6.3.4, as soon as the service is back. If the helper finishes without stopping the service, the server leaves the drain itself after ten minutes at most (HM7151 when the helper wrote nothing); the helper's log is apply-<version>.log in the Updates folder. Set ShutdownDrainSeconds (30 is a reasonable figure) so the stop waits for what is in flight. What a rollback cannot do. It reinstalls the previous binaries; it cannot take the database back. When the new version had moved the schema and then failed to start, the previous version refuses the newer schema, and the outcome says so. Every release's notes say whether it moves the schema; before one that does, take a backup (Backup and restore). The rollback image is verified like any installer, and 6.3.4's has no bundle. UpdateFeed.md does not say what a 6.3.4 server's first live update does about that, so plan it as one without a rollback image, and take a backup first. In the one case UpdateFeed.md describes, an old server set to the GitLab signer (below), the install runs without a rollback image, the log says no rollback image ... could be had, and a new version that does not start has to be reinstalled by hand. Under NT SERVICE\hMailServer, the account a new 6.3.4 installation on Windows runs as, the apply is refused before anything happens, before the rollback image and the drain: the installer needs an administrator. The update is still checked, downloaded and verified. REST, COM and the Deck say so, and tell an administrator to download the installer from the release page, check its Authenticode signature (Get-AuthenticodeSignature reports it Valid) and run it; never the copy in Data\Updates, which the service can write. An open unattended window logs HM7463 once per process instead of taking a backup for nothing, and the setup checklist carries update_apply_by_administrator while the check is on. An upgraded installation that kept LocalSystem applies as before. Installing on Windows covers the account. On Linux, since 6.3.4, the service writes a request to /run/hmailserver and the root path unit hmailserver-update.path runs /usr/lib/hmailserver/hmailserver-update, which verifies the package and the rollback image again as root, installs, and rolls back when the service does not come back. No token is involved: the package's post-install step runs the schema upgrade. A server not under systemd is told so and left with the verified file to install by hand. The hmailserver command line on Linux has the helper in full. Linux builds from 6.3.0 to 6.3.3 answer This build cannot apply an update. Unattended updates Key Default What it does UpdateAutoDownload 0 1 downloads and verifies a newer release's installer as soon as a check finds it UpdateWindow empty, never When a verified update may be applied without a click UpdateBackupBeforeApply 1 An unattended apply backs up to the configured backup destination first, and does not go ahead without a destination or when the backup fails UpdateWindow takes 03:00 (every day from 03:00, for an hour), Sun 03:00 or Sat,Sun 02:00-05:00: English three-letter days, local 24-hour times, and a range that crosses midnight belongs to the day it starts on. An unparsable value is logged once per process and nothing is applied. The backup covers the unattended path only, so take one yourself before an apply you start. Under NT SERVICE\hMailServer a window applies nothing (HM7463). On Linux: sudo hmailserver --set-setting UpdateCheckEnabled=1 sudo hmailserver --set-setting UpdateAutoDownload=1 sudo hmailserver --set-setting "UpdateWindow=Sun 03:00" sudo systemctl restart hmailserver A server from 6.2.28 to 6.3.3 These versions read GitHub's Releases API, which has answered 404 since GitHub suspended the project's account on 18 September 2026, so their check says The update feed answered HTTP 404. Nothing else breaks. Pointed at the new feed, such a server trusts the re-hosted releases up to 6.3.3, which carry GitHub's bundles, but its check now offers 6.3.8, the newest stable release, signed by the GitLab pipeline, which it cannot verify either: its live update installs nothing until 6.3.4 or later is installed by hand. It will also refuse 6.3.5 (The certificate names https://gitlab.com/Progressiverobot/hmailserver//.gitlab-ci.yml@refs/tags/v..., not this repository's release workflow.). These versions read [Settings] from the file, and a change needs a service restart. Turn automatic download off. With UpdateAutoDownload=1 such a server would download a GitLab-signed installer, about 80 MB, at every check and refuse it every time, because the verification comes after the download. Set it to 0. Point it at the feed, with exactly this line under [Settings] in hMailServer.ini, then restart the service: [Settings] UpdateFeedUrl=https://updates.progressiverobot.com/releases.json Without .json the address is a directory, answered with a redirect, and the check says The update feed answered HTTP 302. On Windows set it on the Updates card and let the Control Panel restart the service; or add it to Bin\hMailServer.INI as an administrator and run net stop hMailServer and net start hMailServer from an elevated prompt. On Linux: sudoedit /etc/hmailserver/hMailServer.ini # add the line under [Settings] sudo systemctl restart hmailserver An AppImage keeps it in $HMAILSERVER_HOME/hMailServer.ini, or else $XDG_DATA_HOME/hmailserver/hMailServer.ini, normally ~/.local/share/hmailserver/hMailServer.ini; start it again after the edit. Check. The answer should be that the newest release, 6.3.8, is available. Still 404 means the service has not read the line: check the section, the spelling and the restart. The update feed could not be read: ... names what stopped it: no route out, a proxy (HttpProxy), or a certificate the machine does not trust. Install 6.3.4 by hand, following Upgrading to 6.3.4, with the backup Upgrading.md describes. The service must have been restarted after your last [Settings] change, or 6.3.4's first start treats that change as an edit (HM5804) and undoes it. 6.3.4 and 6.3.5 had no AppImage and no AArch64 package; 6.3.6, 6.3.7 and 6.3.8 have both again, and such an installation takes the newest release's, 6.3.8's, by hand, once. Afterwards, 6.3.4 reads the new feed without being told and trusts both signers. The UpdateFeedUrl line can go, although leaving it changes nothing: clear it in the Control Panel, with DELETE /api/v1/settings/ini/UpdateFeedUrl, or on Linux with hmailserver --set-setting UpdateFeedUrl=, not in the file. Turn UpdateAutoDownload back on if you want it. The other way, Windows only. UpdateFeed.md gives values for UpdateSigningIdentity, UpdateSigningIssuer and UpdateSourceRepository that let such a server install a GitLab-signed release. While set, they make it refuse every GitHub-signed release, its own rollback image included; they are proved only once a GitLab-signed release exists; and they must stay set until the new version runs, then be cleared as step 5 clears UpdateFeedUrl, or the new version refuses what is signed after the project's move. Never point them at the group's path: an old server checks no repository id. Installing 6.3.4 by hand avoids all of this. The download links, and checking a file by hand https://updates.progressiverobot.com/download/<name> always leads to a file of the newest stable release, never a pre-release, and /download/ to that release's page. GitLab's own /-/releases/permalink/latest follows the release published last, a pre-release included. A server does not read these links. <name> is a file's name without its version: hMailServer-x64.exe, hMailServer-arm64.exe, hmailserver_amd64.deb, hmailserver_arm64.deb, hmailserver.x86_64.rpm, hmailserver.aarch64.rpm, hMailServer-x86_64.AppImage and hMailServer-aarch64.AppImage, each with its bundle at the same name plus .cosign.bundle. A name the release does not carry answers 404, never another release's file. 6.3.4 carries the x64 installer, the amd64 .deb and the x86-64 .rpm, and no bundle. Checking 6.3.4. With no bundle to check, 6.3.4 is checked by hmailserver-6.3.4-SHA256SUMS.txt, whose signature hmailserver-6.3.4-SHA256SUMS.txt.sig is made with the key that signs the release tags, and on Windows by the installer's Authenticode signature, which Get-AuthenticodeSignature should answer Valid, signed by Progressive Robot Ltd. The 6.3.4 announcement has the commands and what each must answer. Other releases. Up to 6.3.3, cosign verify-blob checks a file against its bundle and the GitHub identity (Verifying a release in the Security Policy). From 6.3.5 a release has no .sig; its files are signed with Sigstore, and on Linux hmailserver --verify-update <file> checks a download the way the server does. What it does not do It does not install 6.3.4, and does not apply under NT SERVICE\hMailServer. A rollback does not take the database back; a backup does. It follows no redirect and sends no proxy credentials. It does not drain a warm-standby pair: the drain is per process, so a pair drains one node at a time (Warm standby and high availability). Where to read more UpdateFeed.md, ServiceAccount.md, Upgrading.md, SettingsApply.md, and the Update check section of the README. On the wiki: Upgrading hMailServer, Settings Reference, The REST API, Security Policy and Installing on Linux. The documentation page: Staying up to date and Self-update. On the forum: Upgrading to 6.3.4; Upgrading from 5.x, 6.2 or 6.3, for what 6.3.4 changes and rolling back by hand; and The Control Deck. Without a GitLab account, a question or a bug can go to the project's Service Desk, contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com. A suspected vulnerability goes only by the routes of SECURITY.md: a confidential GitLab issue or that same address, which GitLab files as a confidential issue.
  • 0 Votes
    1 Posts
    140 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.6 (6 October 2026) publishes the first container image, registry.gitlab.com/hmailserver/hmailserver:6.3.6; its installer exits 10 or 11 when a step after its files did not complete (it exited 0), and it makes the data, log, temp, database and event folders private to the administrators and the service. See the 6.3.6 announcement. hMailServer 6.3.7 (17 October 2026), a security release, publishes registry.gitlab.com/hmailserver/hmailserver:6.3.7 (also tagged 6.3 and latest); its Windows installers carry the .NET Desktop Runtime 10.0.13, installed only where no .NET 10 Desktop Runtime is installed at all, so an upgraded server keeps the one it had; at every installation and upgrade /ProtectFolders hardens the program folder, Bin and ControlPanel, since an installation in a folder that took its permissions from the root of a drive let any account that could sign in to the computer replace the programs (CVE-2026-107571): before upgrading one, follow ServiceAccount.md's steps, and where other accounts could sign in while it was so installed, reinstall into a new folder only the administrators may change, given to setup with /DIR=, since over an installation setup installs into the folder it finds; and setup waits ShutdownDrainSeconds plus 90 seconds for the service's stop, where it waited plus 30. Chocolatey and the apt and dnf repositories still publish nothing: no channel is live as 6.3.7 is made, and 6.3.7 is not on the Snap Store. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026), a security release, publishes registry.gitlab.com/hmailserver/hmailserver:6.3.8, now the image tagged 6.3 and latest, which reaches a MariaDB database, where the images of 6.3.7 and earlier carried PostgreSQL's client alone, and whose entrypoint takes the database and administrator passwords out of the environment before it runs any program; and its Helm chart starts a server, where the charts of 6.3.6 and 6.3.7 never started a pod. Its Windows setup ends with exit code 7 where another account than SYSTEM, the Administrators and TrustedInstaller controls the program folder, Bin or ControlPanel, or a folder or a link on the way to them, naming the icacls that deals with it; with exit code 12, and the service set not to start until setup runs again, where something is not as setup left it after the files; and with exit code 1 for an installation of the original hMailServer's 5.7, the reason in its /LOG and in hMailServer-setup-refused.txt in %TEMP%. Chocolatey's install now passes the installer /LOG, and that log records setup's command line, so give the administrator password in HM_ADMIN_PASSWORD, set in the elevated prompt, rather than with /adminpassword in --install-arguments. Once 6.3.8 is published, jobs started by hand send it to the Ubuntu PPA (add-apt-repository ppa:hmailserver/hmailserver), Fedora Copr (dnf copr enable progressiverobot/hmailserver), Docker Hub, Chocolatey (hmailserver-progressiverobot, listed once its moderators approve it), PyPI and the PowerShell Gallery; the signed apt and dnf repositories this topic describes are still not published (Channels.md). See the 6.3.8 announcement. This topic covers installing hMailServer 6.3.4 without a wizard: the Windows installer's silent switches and what a silent install does with the administrator password; where the winget and Chocolatey packages stand; the signed apt and dnf repositories and why they are not live; and, since 6.3.4, every setting from the environment and the four setting profiles, the two ways a deployment shapes a server before it starts. The one thing to know first: an unattended install leaves the administrator password unset unless you pass it, and 6.3.4 is installed by hand from the release page, because neither package manager carries it and no live update installs it (the 6.3.4 announcement). The Windows installer's switches The installer is Inno Setup, so it takes the standard switches and a few of its own. Switch Effect /SILENT No wizard; progress window only /VERYSILENT No wizard and no progress window /SUPPRESSMSGBOXES Suppress message boxes; some failure paths still raise one without it /LOG="<path>" Write an install log; it is the only record of what happened /DIR="<path>" Installation directory /COMPONENTS="server,admintools,controlpanel" server is the service; admintools registers the COM type library; controlpanel installs the Control Panel, which does not need admintools /adminpassword=<password> Custom: sets the administrator password (at least 5 characters) and hands it to the database tools /upgradetoken=<hex> Custom: the single-use token of a live update; not for people to type /useinternaldbms=true\|false Custom: true (the default) uses the bundled SQL Server Compact database /serviceaccount=virtual\|localsystem\|keep Custom: virtual is NT SERVICE\hMailServer, the default for a new installation; keep is the default for an upgrade /profile=NAME Custom, since 6.3.4: the setting profile a new installation applies, or none. Written and read, and not run (below) hMailServer-x.y.z-x64.exe /VERYSILENT /SUPPRESSMSGBOXES /LOG="C:\Temp\hmail-install.log" ^ /COMPONENTS="server,controlpanel" /useinternaldbms=true Treat a non-zero exit code as a failed install and read the log. The installer checks both that the database tool launched and its exit code, so a failed or cancelled schema step fails the install rather than passing silently. The log has the tool's code: from DBSetupQuick.exe, 0 is a database created or already up to date, 1 a failed create or a DBUpdater.exe that would not start, 3 a wrong or missing administrator password, and any other DBUpdater.exe's own code. A hm_dbversion still on the old value afterwards is the same failure. A silent installation does not approve folders or key files the configuration names outside the program folder, and leaves such a service on LocalSystem rather than moving it to the virtual account. The administrator password, silently A fresh silent install leaves the administrator password unset unless you pass /adminpassword=<password> or set HM_ADMIN_PASSWORD: without either, the AdministratorPassword key is left out of hMailServer.ini entirely. Set it afterwards with hMailServer.exe --set-admin-password, new in 6.3.4, from an elevated prompt, then restart the service. It takes the password from HM_ADMIN_PASSWORD or, when that is not set, from one line of standard input when that is a pipe or a file, and never from an argument. Prefer the variable: a pipe from Windows PowerShell 5.1 turns every non-ASCII character into ?, storing a password nobody typed. On Linux the command is hmailserver --set-admin-password. ServiceAccount.md and Upgrading.md write the Windows step as hMailServer.exe /SetAdministratorPassword. The Control Panel and COM are refused under the virtual account. A new 6.3.4 installation runs the service as NT SERVICE\hMailServer by default, and under that account hMailServer.ini is read-only to the service: the Control Panel's Administrator password card and COM's Settings.SetAdministratorPassword are each refused with a sentence. They set the password only where the service may write the file, as it may under LocalSystem, which an upgrade keeps unless it is answered otherwise. A silent upgrade of an installation that has a password still needs it for the database upgrade, and cannot prompt. Without it the database upgrade fails and the installer reports a non-zero exit code rather than waiting. With the password empty, a silent upgrade works. Since 6.3.4, HM_ADMIN_PASSWORD is the safer of the two. Inno Setup drops every double quote from its command line, so /adminpassword=ab"cd sets abcd, and anybody listing processes while setup runs can read the switch. The variable takes any character. Setup reads it only when neither /adminpassword= nor /upgradetoken= is given. Set the variable in an elevated prompt. Setup started without administrator rights elevates itself through the UAC prompt, and the variable does not cross it. Until 6.3.4 the password went unquoted on the database tools' command lines, so one with a space, a double quote or backslashes before one failed the database step with exit code 3. It now goes in HM_ADMIN_PASSWORD. winget and Chocolatey: not in the public repositories yet The manifests exist and are validated for each release, from the release's own Authenticode-signed installer. They are not in the public repositories: neither winget install ProgressiveRobot.hMailServer nor choco install hmailserver installs this fork until a first submission has been reviewed and merged. Do not run choco install hmailserver expecting this fork. PackageManagers.md records none on 15 September 2026, but on 27 September 2026 the community feed held an unlisted hmailserver package: the original project's 5.6.8, not this fork's. What the packages are built to do, once accepted: Both run the signed installer the release page offers with /VERYSILENT /SUPPRESSMSGBOXES /NORESTART. /SUPPRESSMSGBOXES is not optional: without it a database-setup failure shows a message box, silent mode answers it with the default button, and a broken install reports success. /TYPE=full is deliberately not passed, so an upgrade keeps the components already installed and does not put the Control Panel back on a server where it was left out. MinimumOSVersion: 10.0.14393.0 is Windows 10 1607, the floor of the .NET 10 Desktop Runtime the Control Panel needs. The administrator password is left unset, and neither package invents one. Set one before the server is reachable from anywhere but the machine itself. Uninstalling runs the installer's own uninstaller, which does not delete the message store, the database or hMailServer.ini. A pre-release is never submitted. apt and dnf on Linux: written, tested in a lab, not live The project keeps signed apt and dnf repositories on the update feed's site, https://updates.progressiverobot.com. They are not live. The job that makes them, package-repos, was tested on the lab machine on 26 September 2026 with a throwaway key and dummy packages, and publishes nothing until the owner has made the signing key. This is what a Debian or Ubuntu user will type once it is live; on a release with no suite, apt update answers 404 for its InRelease. sudo install -d -m 0755 /etc/apt/keyrings sudo curl -fsSLo /etc/apt/keyrings/hmailserver.asc https://updates.progressiverobot.com/hmailserver-repository-key.asc gpg --show-keys /etc/apt/keyrings/hmailserver.asc # compare the fingerprint with the one below echo "deb [signed-by=/etc/apt/keyrings/hmailserver.asc] https://updates.progressiverobot.com/apt $(. /etc/os-release && echo "$ID$VERSION_ID") main" \ | sudo tee /etc/apt/sources.list.d/hmailserver.list sudo apt update sudo apt install hmailserver The fingerprint does not exist yet: PackageManagers.md records it as (not made yet). There is no dnf repository at all until a stable release carries .rpm files built for each distribution, and 6.3.4 carries the generic one alone. Once one does, on Fedora and Enterprise Linux it is sudo curl -fsSLo /etc/yum.repos.d/hmailserver.repo https://updates.progressiverobot.com/rpm/fc44/hmailserver.repo and then sudo dnf install hmailserver, with fc44 replaced by the release's directory. What 6.3.4 carries limits what the repositories could hold. Its Linux packages are one generic x86-64 .deb and one generic .rpm, neither built for one distribution; hMailServer on Linux: what you get and what you do not says where each installs. In the repositories as PackageManagers.md describes them, the .deb belongs in the ubuntu26.04 suite, and the .rpm, built on Ubuntu 26.04, goes in no repository. On 26 September 2026 dnf install of the .rpm built from master that day resolved its dependencies on Fedora 43 and 44 and AlmaLinux 10, and was refused on AlmaLinux 9, whose glibc and libstdc++ are older than it asks for. Packages built for each distribution come from the release after 6.3.4. The packages carry no RPM signature of their own: the signed repository metadata carries every package's SHA-256. Not tried: the job on GitLab's runners, HTTPS end to end, arm64 and aarch64. Until it is live, First install on Linux is the walk. Every setting from the environment Since 6.3.4. Every [Settings] key and every hm_settings row the server reads has a name in the environment. Variable Sets HM_SETTING_<key> The [Settings] key, one of 241: HM_SETTING_MaxSubmissionsPerIPPerMinute=30 HM_PROPERTY_<name> The hm_settings row: HM_PROPERTY_maxmessagesize=20480 HM_SETTING_<key>_FILE, HM_PROPERTY_<name>_FILE The same, read from the file the variable names, such as a Kubernetes or Compose Secret; UTF-8, at most 64 KB, trailing line breaks stripped The prefixes and _FILE are written in capitals; the name after the prefix is matched without regard to case, and a key is stored in its own spelling. What is refused, before anything is stored: a name that is not a setting of the store its prefix names (MaxMessageSize after HM_SETTING_ is refused naming HM_PROPERTY_maxmessagesize); a misspelt prefix such as HM_SETTINGS_; a read-only property such as WebPushVapidPublicKey; the plain and the _FILE form of one setting together; a _FILE naming a directory, a missing path or a file over 64 KB, refused naming the path, never the content; the _FILE form of a setting a domain or an account may hold its own value of; and a value the setting's own REST check would refuse, in that check's words. maxmessagesize=off is refused rather than stored as 0, which is "no limit". The verb. hmailserver --config /etc/hmailserver/hMailServer.ini --apply-environment hMailServer.exe --apply-environment on Windows is the same verb from the same code. It opens the configuration and the database as the service does, so run it as the user the service runs as. It checks every value first and exits 1 having changed nothing when any is refused, then writes each through the setter PUT /api/v1/settings/... uses and prints a line per variable: set, unchanged or refused. A secret's value is never printed. Exit 2 is a configuration file that names no database. The verb's writes are not rows in the audit trail: its own output, in a container the container's log, says what it changed. Who runs it. No container image of 6.3.4 is published: the image is one you build yourself, from the Dockerfile in the tree. Its entrypoint runs the verb after --create-database and --upgrade-database and before the server. On Windows nothing runs it for you: a Windows service has no environment of its own that anything sets, so the verb is there for a script or a deployment tool. Run beside a running server, its values reach that server at the next reinitialise (Apply now), or the next start for a key marked restart. The environment wins, every start. The server records the managed names in the hm_settings row EnvironmentManagedSettings and refuses an administrator's change to one of them. Where What happens REST A PUT that would change a managed key is 409, with a sentence naming the variable; GET /api/v1/settings/environment lists every managed setting with no value COM (Windows) Every setter refuses with the same sentence Control Deck Set by the environment beside the key, with its value and no control Control Panel (Windows) The same mark, with the field disabled hmctl hmctl config changed prints (set by the environment); hmctl config export leaves managed settings out Unset the variable and restart to manage the setting in the server again. A value a _FILE supplied stays withheld after the variable is gone: REST answers (set), the Control Panel leaves the field empty, and it is never on a command line or in a log. The environment sets the server level only, and the two [SendingLimits] keys have no environment name. The container's own variables are not settings: the database's HM_DB_*, TLS included; HM_ADMIN_PASSWORD and HM_ADMIN_PASSWORD_FILE; HM_INI; HM_CREATE_DATABASE and HM_UPGRADE_DATABASE; HM_PROFILE and HM_PROFILE_CONFIRM. The four REST ones are aliases: HM_REST_PORT stands for HM_SETTING_RestApiPort. The Helm chart takes settings, properties and settingsFrom. In a container or on Kubernetes has the table, the chart, and the stored-secrets key a first start makes, to export at once. What has not been run. The verb has been run as hMailServer.exe --apply-environment on Windows only. Its POSIX half compiles; the image's entrypoint as a whole, the POSIX verb against a database, the chart and the Compose file have not been executed. There is no live reload from a ConfigMap change: a restart applies it. Profiles: the shape a new server starts in Since 6.3.4. A profile is the thirty or so settings that make a server the kind it is, each with a reason written from what the setting's reader does. Four ship as reviewed documents; there is no route that makes one of your own. Profile For What you would notice office One server that receives an office's mail and holds its mailboxes SPF, the MX check, DKIM and DMARC scoring; greylisting; the quarantine; auto-ban and lockout; a password policy and the breach check (31 entries) gateway Behind a filtering gateway that has already judged the mail Greylisting off; XCLIENT and an ARC seal trusted only from the gateway and sealer you name (17 entries) relay An outbound relay that holds no mailbox anybody reads IMAP and POP3 closed at the next restart; hard bounces suppressed; the trace kept three months (24 entries) sender A high-volume sender judged on its bounce and complaint rates The relay's shape, more deliveries at once, the feedback loops, the trace kept six months (32 entries) Every surface shows a diff first, then saves what it replaces, then writes, all or nothing. An entry that closes a listener, trusts an address range, turns a filter off or weakens a check, or shortens a window of days asks for a confirmation. A profile never touches a port, a path, a bind address, the database connection, a password or any secret, an IP range, Incoming relays, a DNS block list, a listener's TLS or a domain's own settings. What only you know, such as the gateway's addresses, is a step listed under the diff. Revert puts back each value the profile replaced and leaves alone one an administrator changed since. It puts back settings, not data: where a profile shortened a window of days, what the sweep deleted under the shorter window stays deleted, and a listener a profile closed opens again only at the next restart. There is one snapshot: a second apply waits for the first to be reverted or discarded. Where a profile is chosen at install time: The Windows installer offers the four on a page of their own, after the administrator password and before the service-account page, for a new installation only, with "None" chosen until another is. No diff is shown there: each profile's sentence names the listener, filter or trust it changes, and choosing it is the confirmation, so relay and sender close IMAP and POP3 with no further question. After the database step it runs hMailServer.exe --apply-profile NAME --once --if-new --confirm before it starts the service. Silently, that is /profile=NAME. Every failure is said, and the installation still succeeds with the shipped settings. The container applies HM_PROFILE once, before the server starts; HM_PROFILE_CONFIRM=yes confirms an entry that asks, and HM_PROFILE=none records that no profile is wanted. The verb itself: hmailserver --apply-profile NAME [--confirm] [--once] [--if-new] hmailserver --revert-profile Exit Meaning 0 Done, or nothing to do 1 Refused or failed 2 No such profile, or no database 3 An entry needs --confirm 4 The service is running 5 --if-new, and the server is in service On Windows the verbs refuse while the service runs (exit 4). On Linux nothing says whether a server is running, so run them with the server stopped, as the container's entrypoint does. --if-new does nothing on a server of ten accounts or more. A setting the environment manages is skipped, with its variable named. Afterwards a profile is chosen in the Control Panel's Maintenance, Setting profiles (Windows), the Deck's Checklist, COM's Settings.ApplyProfile (Windows), REST or hmctl. Read the diff at GET /api/v1/settings/profiles/{name}, or with hmctl settings profile NAME, which prints a diff_token. Apply with {"confirm": true, "diff_token": ...} on POST /api/v1/settings/profiles/{name}/apply, or hmctl settings apply-profile NAME with --confirm TOKEN: a diff that has moved since it was read is refused (409) and nothing is written. The way back is POST /api/v1/settings/profiles/revert; POST /api/v1/settings/profiles/discard drops the snapshot. The installer page with its /profile=NAME switch, the container's HM_PROFILE and the Linux verbs have been written and read, and not run: the installer was not built and the Linux server not compiled for this change, and no container was started. What it does not do No package configures the server: no password, no domain, no firewall port. No export of the configuration as an environment. Four profiles, no parameters, no profile per domain. Where to read more The wiki pages: Profiles and Environment and Running in a Container winget, Chocolatey, apt and dnf: PackageManagers.md The naming rule and every refusal: EnvironmentConfiguration.md Every entry of the four profiles: Profiles.md The silent upgrade: Upgrading.md The documentation page: Run the installer On this forum: Upgrading to 6.3.4, Installing on Windows for the wizard's pages, and Behind a gateway for the gateway profile's steps.
  • 0 Votes
    1 Posts
    171 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) lets the IMAP and POP3 listeners take the client's address from a PROXY protocol header, as the SMTP listener does, each from the peers on a list of its own; both are off by default, and in the Control Panel the PROXY protocol and XCLIENT are a card on Incoming relays, while API & monitoring is Monitoring & alerts. The ACME http-01 challenge responder serves up to 64 connections at once and closes one past that, where it served one at a time. The REST listener refuses a state-changing request that carries a credential when the browser reports it as cross-site (Sec-Fetch-Site, or an Origin that is not this server's); scripts, hmctl and the PowerShell module send no such header and are unaffected. 6.3.7's container image is registry.gitlab.com/hmailserver/hmailserver:6.3.7. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) has the web services listener close a connection, with no answer and before the TLS handshake, from an address whose IP range allows none of SMTP, POP3 and IMAP - an auto-ban's, or one of your own that allows nothing - while loopback and the addresses in WebServicesTrustedProxies are never refused by a range; so a range of your own that allows some addresses nothing now closes this listener to them as well, and with it ACME's http-01 validation when this listener answers it, MTA-STS, CalDAV, CardDAV, Autodiscover, autoconfiguration and the Web Key Directory: check that the certificate authority, the sending MTAs and these services' clients do not come from one. 6.3.8's container image is registry.gitlab.com/hmailserver/hmailserver:6.3.8, also tagged 6.3 and latest. See the 6.3.8 announcement. This topic lists every socket hMailServer 6.3.4 can open, inbound and outbound, and the firewall rules that follow. The one thing to know first: a new server listens on four mail ports and on nothing else. Every other listener is off until a setting gives it a port, three of them bind only to 127.0.0.1 when you do, and almost every outbound connection beyond DNS, SMTP delivery and the recipient's MTA-STS policy exists because an administrator turned a feature on. Write the firewall for what you turned on. Connection security per mail port, and what a new 6.3.4 installation sets on each, is in Connecting a mail client: IMAP, POP3, SMTP submission and the ports. Two kinds of listener, set in two places Mail-protocol listeners (SMTP, POP3, IMAP) are rows of the table hm_tcpipports, edited on Connections & protocols → TCP/IP ports in the Control Panel (Windows), on the Control Deck, over COM (Settings.TCPIPPorts, Windows) and over REST at /api/v1/ports, where a new row's address defaults to 0.0.0.0. A port you add, change or delete takes effect at a reinitialise (POST /api/v1/server/reinitialize, or the Deck's button that restarts the services in place) or a service restart. Service listeners are one key each, all off by default. Set their [Settings] keys in the Control Panel (Windows), over REST (PUT /api/v1/settings/ini/{name}), with hmctl ini set or hmailserver --set-setting NAME=VALUE; the Deck has no page for them. Since 6.3.4 the database holds these keys, so a line edited in hMailServer.ini turns no listener on (HM5804); Settings: what is not stock, when a save takes effect explains the store. Each listener's port, bind address and certificate files are among the 28 keys that wait for a reinitialise: Apply now in the Control Panel or the Deck, POST /api/v1/server/reinitialize, Application.Reinitialize over COM, or a restart. AcmeHttpPort applies when it is saved. The SNMP agent's settings are hm_settings rows instead, set on the Deck's Settings page under Server, over REST (/api/v1/settings) or with hmctl settings set, with no Control Panel page, and read when the agent starts, at a service start or a Reinitialize. The mail listeners Port Protocol Created by Default bind What it says on connect 25 SMTP Seeded in every new database The row's IP address (0.0.0.0 or :: is every address) 220 MAIL01 ESMTP: the Welcome banner from Connections & protocols → Protocols → SMTP, or the computer name, with ESMTP appended 587 SMTP submission Seeded The row's IP address The same banner 110 POP3 Seeded The row's IP address +OK POP3, or your welcome message 143 IMAP Seeded The row's IP address * OK IMAPrev1, or your welcome message 465, 993, 995 SMTP, IMAP, POP3 over implicit TLS You, once you have a certificate What you choose Nothing until the TLS handshake completes A listener bound to one internal address is the usual reason mail works from the LAN and not from outside. Two failures leave one port dead and the rest running: a TLS port with no certificate selected does not start (HM5113), and a port something else holds does not bind (HM4316). On Windows with WSL2 in mirrored networking mode, a socket bound inside WSL takes the port from Windows too, shows in neither Get-NetTCPConnection nor netstat -ano, and on the project's test bench only wsl --shutdown gave it back. The service listeners Listener Port key, default Bind key, default TLS How to tell it is up REST API, the Control Deck at /, the webmail at /portal, SCIM at /scim/v2 (since 6.3.4) RestApiPort, 0; 8045 is the conventional port RestApiBindAddress, 127.0.0.1 Required unless bound to 127.0.0.1, localhost or ::1 RestApi: Listening on <endpoint> (https) or (http, loopback only) in the application log Public web services WebServicesHttpPort, WebServicesHttpsPort, both 0; 80 and 443 to enable WebServicesBindAddress, 0.0.0.0 WebServicesCertificateFile and WebServicesPrivateKeyFile on the HTTPS port, or the ACME certificate on disk A URL it serves answers Prometheus metrics and health probes MetricsServerPort, 0 MetricsServerBindAddress, 127.0.0.1, an IP literal only MetricsServerCertificateFile + MetricsServerPrivateKeyFile; plain HTTP otherwise /livez answers 200 alive ManageSieve (RFC 5804) ManageSieveServerPort, 0; 4190 is the standard port ManageSieveServerBindAddress, 127.0.0.1 STARTTLS, only when an IMAP, POP3 or SMTP port has a certificate Its capability response ACME http-01 challenge AcmeHttpPort, 80 Every address Never Open only during an issuance SNMP agent, UDP, since 6.3.4 SnmpPort, 161, with SnmpEnabled off SnmpBindAddress, empty = every address SNMPv3 at authPriv instead of TLS A stranger gets no answer at all One port carries the REST API, the Deck and the webmail; there is no second port to open. It will not start without an administrator password, nor beyond loopback without a certificate, and with RestApiCertificateFile empty it takes the ACME certificate when one exists. For users to reach /portal, or an identity provider SCIM, it needs a certificate they trust, a reachable bind address and a firewall rule, or a TLS-terminating proxy in front (Behind a gateway, load balancer or reverse proxy). Turning it on is in The REST API: turning it on, credentials, roles, one-time codes. The web services listener binds every address by default. It serves the MTA-STS policy at /.well-known/mta-sts.txt (HTTPS only), ACME challenges, Thunderbird autoconfig, Outlook autodiscover and the Apple profile (HTTPS only), security.txt, CardDAV and CalDAV under /dav/ (HTTPS only; CalDAV since 6.3.4), and, since 6.3.4, the OpenPGP Web Key Directory (HTTPS only) and an S/MIME authority's root and CRL (plain HTTP). MtaStsHostingEnabled and AutoconfigEnabled default to 1 while both ports stay 0, which the server reports once at startup; the ports stay 0 on purpose, because port 80 may belong to IIS. The HTTPS port waits for a certificate, its own or ACME's, and says so in the log. Metrics. A host name or localhost as the bind address is refused (MetricsServer: Invalid bind address) and the probes go with the listener. Beyond loopback with no credential, /metrics answers 503 while the probes still answer. More, including the SNMP agent, is in Monitoring and health: Prometheus metrics, health probes, Grafana, OTLP and SNMPv3. ManageSieve has no certificate of its own and offers no STARTTLS, saying so in the log, until a mail port has one. The ACME challenge listener is not permanent. When WebServicesHttpPort equals AcmeHttpPort, the web services listener answers /.well-known/acme-challenge/; otherwise a temporary listener binds AcmeHttpPort for the issuance, and a failed bind logs Failed to start the http-01 challenge listener. Is the port in use? The client uses the http-01 challenge only, so port 80 must be reachable from the internet (Let's Encrypt certificates are automatic). The SNMP agent (since 6.3.4) answers nobody until SnmpAllowedAddresses names a manager; a datagram from any other address is dropped before it is read. Bind it to 127.0.0.1 or a management address, and name managers one by one: UDP does not prove a source address. Error 7420 is a port it could not bind, such as one the operating system's own SNMP service holds. Snmp.md lists the Linux bind to 161 under the packaged unit as not proved by any test. A Control Panel on another machine (Windows) reaches the COM API over DCOM, which must be permitted, after probing TCP port 135 (the RPC endpoint mapper). hmctl and the PowerShell module use the REST listener instead. Where Windows and Linux differ Where you set a listener. On Windows, the TCP/IP ports page, Connections & protocols → Web services & autoconfiguration, TLS & certificates → Certificates (ACME) and Monitoring & troubleshooting → API & monitoring (Monitoring & alerts since 6.3.7). Linux has no Control Panel and no COM: the Deck for the mail listeners and the SNMP agent, and REST, hmctl or hmailserver --set-setting for every listener's [Settings] key. The packaged RestApiPort=0 is stored at the first start, and a line edited afterwards turns nothing on; First install on Linux has the order. Low ports. The packaged hmailserver.service runs as its own user with CAP_NET_BIND_SERVICE, which it has for port 25 and which also lets the agent bind UDP 161. The database port. On Windows [Database] Port of 0 means the provider's own default. On Linux write it out (5432, 3306 it goes into the connection string as it stands. DNS. On Windows the custom servers under Connections & protocols → DNS resolver are given port 0, because the Windows DNS client supplies 53 itself and rejects an entry that names one; DNSServer is one IPv4 address with no port syntax. Containers. The image exposes 25, 110, 143, 465, 587, 993, 995, 4190 and 8045, and SNMP is published as UDP. No image of 6.3.4 is published; 6.3.8's is registry.gitlab.com/hmailserver/hmailserver:6.3.8, also tagged 6.3 and latest, and In a container or on Kubernetes has the rest. Proving what is listening On Windows, on the server and then from outside: Get-NetTCPConnection -State Listen -LocalPort 587 | Select-Object LocalAddress, LocalPort, OwningProcess Test-NetConnection 127.0.0.1 -Port 25 Get-NetFirewallRule -Enabled True | Where-Object DisplayName -like "*hMail*" Test-NetConnection mail.example.com -Port 25 openssl s_client -connect mail.example.com:465 -quiet On Linux, ss -ltnp lists listening sockets with their process, and the REST listener answers curl -u Administrator http://127.0.0.1:8045/api/v1/status. On Windows, Monitoring & troubleshooting → Server status lists the listeners the service brought up; senders timing out with nothing in the SMTP log means nothing reached a listener. What the server connects to On every server, with nothing turned on: Destination Port Setting If it is blocked DNS resolvers 53, UDP and TCP The machine's resolvers, or DNSServer Nothing is delivered externally, and DNSBL, SPF, DKIM, DMARC, MTA-STS and DANE stop at once Every recipient domain's MX host 25 None Messages stay queued and are retried, by default 4 times at 60-minute intervals, then bounced The recipient domain's MTA-STS policy, from mta-sts.<domain> 443 MtaStsEnabled, 1 Delivery to a domain with an enforcing policy falls back to what DANE and your TLS settings allow DANE and TLS-RPT need no port of their own: TLSA records come over DNS, and TLS-RPT reports are sent as ordinary mail. Delivery leaves from SMTPDeliveryBindToIP, or a rule's bind to address action; since 6.3.4 SMTPDeliveryBindToIP may name one address of each family, separated by a comma. Only when you configure it: Destination Port Setting, as shipped Smart host, or a route's host SMTPRelayerPort (25; 0 is read as 25), or the route's own Empty Database server The provider's port [Database]; since 6.3.4 MySQL and MariaDB use TLS on it when offered SpamAssassin spamd, ClamAV clamd 127.0.0.1:783, localhost:3310 Both off; no TLS An HTTP filter engine The URL's FilterHookUrl, empty The ACME directory 443 AcmeEnabled, 0; AcmeDirectoryUrl is Let's Encrypt The update feed, https://updates.progressiverobot.com, and the installer from the project's package registry on gitlab.com 443 UpdateCheckEnabled, 0 The password breach check, https://api.pwnedpasswords.com/range/ 443 BreachCheckEnabled, 0 (since 6.3.4) An object store: https://s3.<region>.amazonaws.com, https://<account>.blob.core.windows.net or MessageStoreObjectEndpoint 443, or the endpoint's MessageStoreObjectService, empty (since 6.3.4) OAuth2: a key set, an introspection endpoint, the outbound relay's token endpoint The URL's OAuth2JwksUrl, OAuth2IntrospectionUrl, OutboundOAuth2TokenUrl, empty An OpenID Connect issuer: <issuer>/.well-known/openid-configuration, then its token endpoint The issuer's oidc_issuer, empty (since 6.3.4) Outbound webhooks, alert webhooks, a sandbox service, a route of mail in to a webhook The URL's No subscription, rule or route; SandboxEnabled, 0 (all since 6.3.4) A syslog collector SyslogPort, 514 (UDP, TCP); 6514 is the registered TLS port SyslogEnabled, off (since 6.3.4) An OpenTelemetry collector The endpoint's OtelEndpoint, OtelMetricsEndpoint, OtelLogsEndpoint, empty The browser vendor's push service The service's WebPushEnabled, 0 (since 6.3.4) External accounts and migrations: another server's POP3 or IMAP; imap.gmail.com or outlook.office365.com on 993; graph.microsoft.com; CardDAV and CalDAV over https The account's; a person's own account 110, 995, 143 or 993 at public addresses unless reader_fetch_accounts_private_addresses is on Nothing until an account or a migration is made At a person's request in the webmail: since 6.3.4, a correspondent's OpenPGP key from their domain's Web Key Directory (lookup_wkd, on; never an address inside your network) or from https://keys.openpgp.org (lookup_keyserver, off), and a sender's BIMI logo (public addresses only, and only with AuthenticationResultsEnabled, which is 0); and, since 6.3.2, a one-click unsubscribe, posted to a public address only. Where an outbound request may go. Every outbound web request is https, or plain http to this machine only; https is verified against the machine's root store, host name checked, TLS 1.2 at least, so a private CA must be in that store (or in MessageStoreObjectCaFile for an object store). Outbound webhooks may reach a private address, on purpose; a route of mail in to a webhook never reaches a link-local or cloud metadata address, and one not made by the server administrator reaches public addresses only. Through a proxy. HttpProxy (host:port or [ipv6]:port, since 6.2.28) carries the update feed and its downloads, the JWKS and introspection fetches, the breach check and the object store. It sends no proxy credentials, and a value with no port fails with HttpProxy must be host:port (or [ipv6]:port). rather than going direct. ACME issuance, the outbound relay's token request, a mail-in-to-webhook route and the MTA-STS policy fetch never use it; the MTA-STS fetch is on by default, so a proxy-only egress stops it. Whether the other requests above use the proxy is not documented: prove a proxy-only egress before relying on it. UpdateFeed.md, written before the feed site's first deployment, says the address answers nothing usable until then. The firewall matrix Direction Port From or to Only when In TCP 25 Anywhere Always In TCP 587, and 465, 993, 995 Anywhere, or your users' networks 587 always; the others once you have created them In TCP 143, 110 Your users A client still uses them; deny them afterwards In TCP 80 Anywhere ACME, or web services over HTTP In TCP 443 Anywhere WebServicesHttpsPort is 443 In TCP REST port (8045) Your management network; users for /portal; the identity provider for SCIM The listener is bound beyond loopback, with TLS In TCP metrics port, ManageSieve 4190 Prometheus; your management network Bound beyond loopback (or keep 127.0.0.1 and a tunnel) In UDP 161 The SNMP managers only, on the bound address The agent is on In TCP 135 and DCOM The administrator's machine A remote Control Panel (Windows) Out TCP 25 Anywhere, or only the smart host on its port Always Out UDP and TCP 53 Your resolvers Always Out TCP 443 Anywhere, or the hosts in the tables above MTA-STS is on (the default) or a feature needs it Out Database, spamd, clamd, syslog, OTLP ports Those hosts They are not on this machine Out TCP 110, 995, 143, 993, or the account's port The servers mail is collected or migrated from An external account or a migration exists Windows. The installer opens no firewall port, so nothing reaches the server until you add the rules. In an elevated PowerShell: New-NetFirewallRule -DisplayName "hMailServer SMTP" -Direction Inbound -Protocol TCP -LocalPort 25 -Action Allow New-NetFirewallRule -DisplayName "hMailServer Submission" -Direction Inbound -Protocol TCP -LocalPort 587 -Action Allow New-NetFirewallRule -DisplayName "hMailServer IMAPS" -Direction Inbound -Protocol TCP -LocalPort 993 -Action Allow New-NetFirewallRule -DisplayName "hMailServer POP3S" -Direction Inbound -Protocol TCP -LocalPort 995 -Action Allow Linux. With firewalld, firewall-cmd --permanent --add-port={25,587,993,995}/tcp and firewall-cmd --reload; with ufw, ufw allow 25,587,993,995/tcp. The documentation page's chapter 4.4 also opens 110, 143 and 465; leave them out. On either system, add 143, 110, 465, 80 or 443 only when the matrix says so, and forward the same ports on the router or cloud firewall. The Linux package's own firewall hook (AutoBanFirewall=1) only blocks, and needs CAP_NET_ADMIN, which the unit does not grant by default; on Windows the same switch is refused under NT SERVICE\hMailServer (HM7460). Put the addresses you administer from in AutoBanNeverBan first; Reading the logs, and fail2ban has auto-ban in the firewall. What it does not do The SNMP agent sends no trap or inform and does not answer SNMPv1; nothing leaves it unasked. Country blocking downloads no geographic database; the .mmdb file is yours to fetch and keep fresh. No AI or machine-learning service is called by the server. Where to read more Wiki: Ports Reference, Before You Install, Installing hMailServer, Installing on Linux, Settings Reference, Troubleshooting, Encryption and Certificates. Documents at v6.3.4: EvidencePack.md (what leaves the server), SettingsApply.md, Snmp.md, UpdateFeed.md, InboundWebhooks.md, ObjectStorage.md, PasswordBreachCheck.md, Migration.md. The documentation page's ports reference and Open the firewall chapters. On the forum: Let's Encrypt certificates are automatic, First install on Linux, The Control Deck, Installing on Windows: wizard pages, service account, profile and the first hour, Your first domain and mailbox, and the DNS records your domain needs, TLS on the listeners: your own certificate, versions, ciphers and Troubleshooting by symptom: no mail in, no mail out.
  • 0 Votes
    1 Posts
    174 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes what an upgraded server does without anything being switched on, and some of it looks like a fault. A forward, a redirect or a rule's forward of mail from outside that did not pass DMARC for the forwarding domain's organisation keeps the envelope sender it came with, with no SRS address or rewritten sender; where the copy would still carry an identity of that organisation (an empty envelope sender under this server's host name, or an envelope sender at this server's host name or at a hosted domain in that organisation) it is not sent to an outside address at all: the message stays in the mailbox, and the application log names each recipient. An authenticated submission is signed as another hosted domain only when the account may send as the address its From names, and is otherwise sent unsigned as that domain. A domain's or an account's rule may no longer set From, Sender, Resent-From, Resent-Sender, DKIM-Signature or the loop count, and a Sieve script may no longer add or delete them: one stored before the upgrade still runs without that action, and the application log names it. RuleLoopLimit 0 stops out-of-office replies. A delivery whose mail servers' DNS lookup failed tells the sender that the DNS query failed (4.4.4) and retries. On Windows the x64 installer's MariaDB Connector/C 3.4.11 has seven authentication plugins where 6.3.6's had nine, so a database account that signs in with MySQL Enterprise's Windows authentication can no longer sign in and the server cannot open its database. An error log that was quiet may now name database failures that were hidden (HM5032), and the Control Panel's API & monitoring page is Monitoring & alerts. Fixed from reports: the Control Panel's IP ranges page that could show no list at all, DANE enforcement deferring mail to a DNSSEC-signed domain whose MX answer came compressed (live.com among them), and classic Outlook's POP3 downloads stopping at the UTF8 it sends after the login. See the 6.3.7 announcement. After the upgrade to hMailServer 6.3.8 (2 November 2026), TlsMode=verify-full on MySQL or MariaDB with no TlsCaFile set is refused with HM7535 on a connection to a database server on this machine, and the database is not reached; on Windows a TlsCaFile or PostgreSQLSslRootCert whose path holds characters outside ASCII may be refused with HM7534, with the same result, and a TLS certificate or key file so named with HM7536, so that the port it is for does not start; and a Linux server that ran under a LANG that is not UTF-8 no longer finds a file or folder named outside ASCII until it is renamed to its UTF-8 name. A 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. With the web services listener on, an address whose IP range allows none of SMTP, POP3 and IMAP - an auto-ban's, or one of your own that allows nothing - is closed there with no answer, and with it CalDAV, CardDAV, Autodiscover, autoconfiguration, MTA-STS and, when this listener answers it, ACME's http-01 validation. See the 6.3.8 announcement. This topic is the wiki's Troubleshooting chapter arranged by the sentence support threads open with: mail from outside does not arrive, a client cannot send, the Control Panel or the Deck will not connect, the service will not start. It also covers the server's replies, the Diagnostics self-test, the message trace and what only looks like a fault. The one thing to know first: on Windows the service tells the Service Control Manager it is running before it has opened its database or a single port, so "the service is running" proves nothing. Today's ERROR_hmailserver_<date>.log does: it almost always holds the reason a start failed. Start here Three pages, in this order, under Monitoring & troubleshooting in the Control Panel (Windows): Diagnostics, the built-in self-test (below). It names most problems outright. Live logs, while you reproduce the problem, with debug messages on if the answer is not obvious, and off afterwards. Delivery queue: is mail stuck, and what do NextTry and Tries say? On Linux the Deck's Diagnostics, Logs and queue views read the same, and three commands start every investigation: systemctl status hmailserver journalctl -u hmailserver -n 100 --no-pager sudo tail -n 100 /var/log/hmailserver/ERROR_hmailserver_$(date +%F).log Which problem is it? Not running, or nothing listening: The service will not start. A sender got an SMTP error: the reply names the cause. A sender timed out, or outbound mail sits in the queue: Diagnosing stalled mail. Inbound with nothing at all: I can't receive. Outbound that never reaches the queue: I can't send. "I can't receive mail from outside" Work down the table in order, cheapest check first. Check How (Windows) A failure means Is the service running? Get-Service hMailServer Start it, then read the error log for why it stopped Is anything listening on 25? Get-NetTCPConnection -LocalPort 25 -State Listen The port is not configured, is bound to one address only, or another program has it: Connections & protocols → TCP/IP ports Is 25 open in Windows Firewall? Get-NetFirewallRule -Enabled True \| Where-Object DisplayName -like "*hMail*" Open it: see the firewall paragraph below Does the machine itself answer on 25? Test-NetConnection 127.0.0.1 -Port 25 Then it is the server, not the network Is 25 forwarded by your router or cloud firewall? Its configuration The far end sees a refusal or a timeout Does your ISP block 25? Ask them Common on consumer lines: a relay host or a business line Is the MX record right? The MX query page, or Resolve-DnsName example.com -Type MX Fix DNS Can the world reach you? From outside: Test-NetConnection mail.example.com -Port 25 It is the path in Is the domain in hMailServer and Active? Accounts & domains → Domains An inactive domain is not accepted for Is the mailbox Active, and full? The account dialog A full mailbox gets a temporary failure, and the sender retries A port bound to one internal address answers only on the LAN. Server status lists the listeners the service actually brought up. The firewall. The installer opens no port, so nothing from outside reaches the server until you open them, on the machine and on the router or cloud firewall in front of it. Which ports, and the rules for Windows and Linux, are in Every port hMailServer listens on or connects to. "I can't send mail" The reply names most sending faults. In an ESMTP session an enhanced status code follows the number: 530 Must issue STARTTLS first. arrives as 530 5.7.0 Must issue STARTTLS first. Reply Likely cause What to do 550 Delivery is not allowed to this address. ("relay denied") The client is not authenticating, or uses 25 instead of 587 Port 587 with authentication; a printer that cannot authenticate gets its own range under Access & abuse protection → IP ranges 530 SMTP authentication is required. The same, said earlier As above 530 Must issue STARTTLS first. The port requires TLS first STARTTLS or SSL/TLS in the client 535 Authentication failed. Wrong password, not the full address as user name, or an inactive account Also check the auto-ban 535 Authentication failed. Too many invalid logon attempts. The auto-ban: 3 failures, 60 minutes, by default Below 550 Too many recipients. Too many recipients in one transaction Below 552 5.3.4 Message size exceeds fixed maximum message size. Size: N KB, Max size: M KB Over the server's maximum message size (20480 KB), or the domain's own Connections & protocols → Protocols, SMTP tab, or the domain dialog 452 4.3.1 Insufficient system storage. Free space on the store's volume below MinimumFreeDiskSpaceMB (100 MB) Free space; senders retry A banned client. The ban is an expiring IP range: Access & abuse protection → IP ranges lists it, and deleting it releases the client (DELETE /api/v1/ipranges/<id>, or the Deck's IP ranges view). 550 Too many recipients. Two wiki pages disagree. Troubleshooting §19.3 ties it to maxsmtprecipientsinbatch (100) and says split the batch or raise the limit; the Architecture page's RCPT TO chart refuses at 50,000 recipients and uses MaxSMTPRecipientsInBatch to batch outbound deliveries. Splitting the message is right either way; raising the setting also changes the outbound batch size. What the server's replies mean A 4xx is temporary and the sender retries; a 5xx is permanent and the sender bounces. Reply Meaning 421 4.7.0 Too many invalid commands. Bye! Disconnected for misbehaving: a scanner, or a broken client 451 4.3.1 Server temporarily overloaded while accepting the message; please retry. The accept pipeline exceeded FinalizationTimeout: Diagnosing stalled mail 451 4.3.2 Unable to verify the recipient at the moment. Please retry later. The database did not answer, so the mail is deferred, not bounced. Check /readyz 451 4.3.2 The server is shutting down and cannot accept the message. A graceful stop with ShutdownDrainSeconds set 452 4.2.2 Mailbox is full. At or over quota, refused during the conversation; the holder had a QUOTA_WARNING 550 Unknown user No such mailbox, alias or list 553 or 554 from a remote server The far end refused you; its text is the diagnosis, usually SPF, DKIM, PTR or a block list "My mail goes to spam" Almost always DNS, in this order: the PTR record (nothing compensates for a missing or generic one), SPF, DKIM, DMARC even at p=none, then the block lists. With split-horizon DNS, the outside view is the one that counts. Being trusted by the servers you send to covers it. The Control Panel, the Deck or the REST listener will not connect The Control Panel (Windows) talks to the running service over COM: no service, no connection. Use the administration password, not a mailbox's. After a restart it reconnects by itself; after you stop the service it deliberately does not. Remotely it needs port 135 and DCOM. Every page saying "Server unavailable" means the engine is paused: Server status → Resume. Since 6.3.4, under NT SERVICE\hMailServer, a change of the administrator password is refused because the service may not write the file: run hMailServer.exe /SetAdministratorPassword from an elevated prompt, then restart the service. More in The Control Panel on Windows. The Deck and the REST API share one listener, off until RestApiPort is set. It refuses to start without an administrator password, beyond loopback without a certificate, or with a certificate it cannot load (HM5113 names the file), each logged once as RestApi: Refusing to start; the three lines and their fixes are in The REST API: turning it on. Only a RestApi: Listening on line proves it is up. Since 6.3.4 the port waits for a reinitialise (Apply now, or POST /api/v1/server/reinitialize), and a line edited into hMailServer.ini after the first start loses to the stored value (HM5804). With the listener off, on Linux: sudo hmailserver --set-setting RestApiPort=8045 sudo systemctl restart hmailserver On Windows, the Control Panel's API & monitoring page (Monitoring & alerts since 6.3.7). "Web administration page not installed" means the WebAdmin files are missing, or ProgramFolder does not point at the directory holding them. Repeated bad passwords get the address refused for five minutes; loopback is exempt. The Deck itself is The Control Deck. The service will not start The start, in order; where it stops names the problem: Windows is told the service is running, before any of what follows. hMailServer.INI is read, alone, which is how the database is found. A [Database] Type it does not recognise abandons the start, with nothing logged about a database. On Linux, sudo -u hmailserver hmailserver --check-config then prints Database type: 0. HM5005: Server or Database is missing for the configured Type. The database is connected, retried ConnectionAttempts times (6), ConnectionAttemptsDelay seconds apart (5). HM4354: the database server is down or moved, or the credentials changed. On Windows the [Database] password is DPAPI-protected per machine and cannot be copied from another server. Since 6.3.4, HM7530 (an unknown TlsMode) and HM7531 (verify-full with a missing TlsCaFile) stop it too, and since 6.3.8 HM7535 (verify-full on MySQL or MariaDB with no TlsCaFile set, to a database server on this machine: name the CA that issued its certificate in TlsCaFile, or set TlsMode=require) and, on Windows, HM7534 (a TlsCaFile or PostgreSQLSslRootCert name the client cannot be handed as named: give the file and the folders above it ASCII names); --check-database makes one connection and says what it got. The schema version, compared both ways. HM5010: Database version could not be detected. HM5011 too old: DBUpdater.exe from Bin on Windows, sudo -u hmailserver hmailserver --upgrade-database on Linux. HM5011 too new: install the matching or a newer server; never downgrade the database. Since 6.3.4, [Database] UpgradeOnStart=1 makes the service upgrade the schema as it starts: off by default, no backup first, one node at a time. Settings are read again from the database, since 6.3.4 with any [SettingsOverride] keys over them. A configuration that will not load abandons the start. One listener per TCP/IP ports row. A row whose protocol is off on Protocols → Services is skipped in silence, the commonest reason for nothing on 143. HM4316: something else holds the port (IIS on 25 or 443, another mail server, a leftover instance); that port alone stays down. HM5113 with Error initializing SSL. Certificate not set.: that listener does not start. The optional listeners (metrics, REST, web services, ManageSieve), each only with a non-zero port. MetricsServer: Invalid bind address means MetricsServerBindAddress is not an IP literal (localhost is refused too); that listener stays down and the server runs. A failure in steps 2 to 6 stops the whole server, and one with a code writes it to the ERROR log; steps 7 and 8 cost one listener and go to the application log. Windows. A service that stops at once with nothing logged is the account's rights on Bin, Data, Logs and Database. Since 6.3.4 a new installation runs as NT SERVICE\hMailServer (an upgrade keeps its account), and a folder outside the program folder needs approving: run hMailServer.exe --confirm /GrantServiceAccess elevated in Bin, and approve only a folder you recognise, since the service may then write in it. Local SQL Server with integrated authentication needs a login for NT SERVICE\hMailServer. What the account may not do, and moving between it and LocalSystem, are in Installing on Windows. The installer's recovery settings restart a failed service after 1, 2 and 5 minutes (sc.exe qfailure hMailServer). Linux, under systemd. A start that fails before any listener opens puts hMailServer could not start: in the journal, with the reason on standard error in case the log directory is the fault. The unit runs as hmailserver, and ProtectSystem=strict makes anything outside its ReadWritePaths (/var/lib/hmailserver and /var/log/hmailserver) read-only to the service: a data, backup, archive or ACME directory elsewhere needs a drop-in (sudo systemctl edit hmailserver). HM5094: the MySQL or MariaDB client library is not installed. HM5603 on every TLS listener: /usr/bin/dh2048.pem is missing; reinstall the package. The Diagnostics self-test Monitoring & troubleshooting → Diagnostics in the Control Panel; since 6.3.4 also the Deck's Diagnostics view and POST /api/v1/diagnostics/run, on either platform. Ten tests: Test Fails when Server details Never; since 6.3.4 it also says whether the database connection is encrypted Installation paths (6.3.0) A configured path does not exist Test IPv6 Never Test outbound port Outbound 25 is blocked, or the smart host's name, port or TLS mode is wrong Test backup directory No destination, or the service cannot write there Test MX records Public DNS is wrong, or the resolver is not answering Test local connect Hairpin NAT fails, 25 is not forwarded, or the MX points elsewhere Test message file locations Files outside the data directory, or full paths stored, which stops the built-in backup Test IP range configuration 127.0.0.1 has been auto-banned Error logs Any ERROR* file exists Fill both domain boxes first: the MX and local-connect tests, and the outbound test without a smart host, are skipped rather than failed when they are empty. Copy puts the result on the clipboard for a post. "Did we ever get it?": the message trace The message trace records what the server did with each message, keyed on its queue id. It is off by default (MessageTraceEnabled), because it records who corresponds with whom, and nothing is recorded while it is off: turn it on before you need it. Turning it on. Since 6.3.4 it applies when saved: the Control Panel's Message trace card on Monitoring & troubleshooting → Logging, PUT /api/v1/settings/ini/{name}, or hmctl, both with the administrator password: hmctl ini set MessageTraceEnabled 1 On 6.3.3 and earlier it is MessageTraceEnabled=1 under [Settings] and a restart. Rows are kept MessageTraceRetentionDays (30): since 6.3.4 an hourly task deletes older ones; on 6.3.3 and earlier only Remove expired on the Message trace page, or MessageTrace.DeleteExpired over COM, did. Reading it. The Control Panel's Message trace page searches an address as sender or recipient, newest first, and Follow this message shows one queue id. Over REST since 6.3.4: GET /api/v1/message-trace, /api/v1/message-trace/address/{address} and /api/v1/message-trace/queue/{id}, which need the view_message_trace permission. hmctl api GET /api/v1/message-trace/address/[email protected] What a row says. One row per recipient per delivery attempt: when, the event, sender, recipient, source address and status code. Events include delivered and failed; on 6.3.4 also deferred (an attempt that will be retried) and suppressed (stopped by the suppression list). A refusal at RCPT carries queue id 0. A forward, a list or a rule that re-sends makes a new queue entry. Answering the question. A delivered row: you had it; look at the account's folders and rules. A row with queue id 0: refused at RCPT, and the status says why. No row: either it never reached you, or it is still queued or held, came while the trace was off, or is past the retention. The trace records delivery attempts only, with no acceptance, spam or virus event, so an empty trace does not prove the server never had the message. The per-domain reports' traffic, failures, senders and recipients are counted from it, and say so while it is off: Alerts, the audit trail and per-domain reports. Things that look like faults and are not What you see Why A new sender's first message is minutes late Greylisting doing its job (6.3.4) A value typed into [Settings] is back after a restart, HM5804 in the ERROR log The database is the settings store: set it in the Control Panel, over REST (PUT /api/v1/settings/ini/{name}) or with hmctl ini set (6.3.4) HM5807 at every start A [SettingsOverride] section is forcing keys; remove it once the stored value is right (6.3.4, Linux package) HM5803 at every start The service cannot write its copy into the root-owned file; the stored value is in effect 6.2.28 to 6.3.3: "The update feed answered HTTP 404." Set UpdateFeedUrl=https://updates.progressiverobot.com/releases.json and restart "Release 6.3.4 carries no Sigstore bundle for its installer, so it cannot be verified." Install 6.3.4 by hand: Upgrading to 6.3.4 Where a message waits, since 6.3.4. Neither delivered nor queued may mean held on purpose: by the sandbox, by compromised-account detection, in a back-off window, under an IP warm-up ceiling or behind a drain; Diagnosing stalled mail says where to look. And 550 5.7.1 Too many delivery attempts before the greylisting delay had passed is greylisting's abuse lock, lifted after GreyListingAbuseLockMinutes (60); a 5.7.10 bounce to a partner domain is gateway encryption refusing to send in the clear, the partner's key having expired. What it does not do It does not diagnose a mail client (Connecting a mail client), read logs line by line (Reading the logs), or work a stalled queue. The Control Panel and COM are Windows-only. When nothing here names the fault, Read this before you post says what a request needs. Without a GitLab account, email the Service Desk, contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as a confidential issue. Where to read more Wiki: Troubleshooting, Ports Reference, Monitoring and Health, Installing hMailServer, Installing on Linux, The Control Panel, The REST API, Upgrading hMailServer, Architecture. Documents: ServiceAccount.md, DatabaseTls.md, Reports.md, SettingsApply.md. On the forum: Settings: when a save takes effect (HM5804 and the reinitialise), IP ranges, auto-ban and lockout, Choosing a database and encrypting the link (TlsMode, HM7530 and HM7531) and The hmailserver command line on Linux (the unit and its drop-ins). Public documentation: Troubleshooting and Troubleshooting a Linux installation.
  • 0 Votes
    1 Posts
    149 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) fixes the SMTP log writing some credentials in the clear with SMTP logging on: an AUTH PLAIN sent before EHLO or not in its one exact form, the bearer token of AUTH XOAUTH2 and AUTH OAUTHBEARER, and the lines of SCRAM-SHA-256, OAUTHBEARER and GSSAPI went into the log as the client sent them, base64 and readable, in every version from 6.0.0 to 6.3.6. An SMTP log written by 6.3.6 or earlier may hold them: redact every line of a SASL exchange before posting one. A database write that failed is now reported (HM5032) where it was taken as done, so an error log that was quiet may name failures that were hidden; and the IMAP and POP3 listeners can take the client's address from a PROXY protocol header, as the SMTP listener does, both off by default. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) no longer reports a stop of the servers as errors of their own: work that outlasted a stop, a reinitialise among them, writes a line to the debug log where it wrote HM5136 or another code to the error log and ran an installed OnError script, so the error log of a server that reinitialises may be quieter; and an event script's call that meets a stop fails with "The server is stopping. The request was not completed.", its script error going to the debug log. A failed account sign-in at the REST listener from loopback, as through a reverse proxy on the same machine, is counted against the name alone and writes no Auto-ban: logon failure line for fail2ban to read. See the 6.3.8 announcement. This topic covers what hMailServer writes to disk and which file to open, the settings that decide how much is written and kept, the live views, following one connection (since 6.3.4), a copy to syslog or the journal, and the fail2ban filter the Linux package installs. The one thing to know first: open ERROR_hmailserver_<date>.log before anything else. It holds only the entries the server itself classed as an error, and its mere existence is a diagnostic, because the Control Panel's built-in self-test (Monitoring & troubleshooting → Diagnostics, so Windows) fails when any ERROR* file is present in the log directory. Where the logs are Platform Log directory Set by Windows C:\Program Files\hMailServer\Logs by default [Directories] LogFolder in hMailServer.INI Linux /var/log/hmailserver, created by the package as 0750 hmailserver:hmailserver and left in place when the package is removed [Directories] in /etc/hmailserver/hMailServer.ini The format is the same on both platforms. On Linux the journal (journalctl -u hmailserver) carries the startup line, anything written to standard error and the operational events; a memory-safety fault is recorded in /var/log/hmailserver/crash-oracle.log as one line (signal, address, thread, event number) before the process dies by the signal it caught. [Directories] is per machine and is never mirrored into the database, so do not copy another server's file over this one's. The files File Holds Rotates hmailserver_<date>.log Everything: application lines, and the protocol conversations unless they are split out Daily, by name ERROR_hmailserver_<date>.log Only entries the server classed as an error Daily, by name hmailserver_SMTP_<date>.log, hmailserver_IMAP_<date>.log, hmailserver_POP3_<date>.log The per-protocol conversations, when SepSvcLogs=1 Daily, by name hmailserver_backup.log Every backup and restore run, line by line Never: one growing file. LogDeleteDays matches it by name but deletes only a file nothing has written to for that many days hmailserver_events.log Event-script output Never, as above hmailserver_awstats.log The AWStats-format feed, when AWStats logging is on Never, as above hMailServer_messagestore_consistency.report The most recent message-store consistency scan: one line per message whose file is missing, as messageid<TAB>account<TAB>expected-path Overwritten each run The backup and event logs are among the files the server marks with a byte-order mark; they are UTF-16LE on both platforms, so a file written on one reads on the other. A log line looks like "IMAPD" 5220 41 "2026-09-17 09:00:00.120" "192.0.2.7" "SENT: A1 OK LOGIN completed", where IMAPD is the category and 41 is the session id that every line of that conversation carries. The conversation categories are SMTPD, SMTPC, POP3D, POP3C and IMAPD; APPLICATION, TCPIP, DEBUG and ERROR are the rest. The settings Setting Default What it does What it looks like when it is wrong LogLevel 9 Not the choice of log types, which is the Logging page's tick-boxes (or PUT /api/v1/settings/logging). The Settings Reference records two effects: long lines are cut only when it is 2 or lower with debug off, and IMAP SENT: lines for FETCH and STATUS are logged only when it is above 2 or debug is on — Debug messages (the Logging page) off Adds the LOG_DEBUG lines, including the accept-pipeline stage timings Left on, a busy server fills the disk in days, and a full disk stops mail SepSvcLogs 0 Splits SMTP, IMAP and POP3 into their own files Off, so a search for one protocol wades through all three LogDeleteDays 0 Deletes files named hmailserver_*.log or ERROR_hmailserver_*.log whose last write is older than this many days, and nothing else in the directory. 0 keeps everything for ever Left at 0 on a server with debug logging on: the usual cause of a full log volume JsonLogging 0 Writes JSON lines, for Elasticsearch, Loki or Splunk Overridden while Logging.LogFormat is NCSA: the pipeline gets NCSA lines and no error SlowQueryLogMilliseconds 0 (off) Logs, and counts in hmailserver_db_slow_queries_total, any database statement slower than this Off, so a database that has become slow leaves no trace but the latency histogram WindowsEventLogLevel 2 How much also goes to the Windows event log — Where they are set. On Windows, Monitoring & troubleshooting → Logging in the Control Panel. On either platform the table's [Settings] keys (all but Debug messages) are set with PUT /api/v1/settings/ini/{name} or hmctl ini set. On Linux there is no Control Panel, so the log categories, debug messages among them, are set with PUT /api/v1/settings/logging, hmctl settings set logging or the Deck's Settings page. There, WindowsEventLogEnabled=1 is a Windows inheritance in name only, because on Linux it means syslog, and the operational events reach the journal through it. Since 6.3.4 these keys are stored in the database, whose value is the one used: where the service can write hMailServer.INI, a line edited there is put back and reported as HM5804. How the settings store works has the rest, [SettingsOverride] (HM5807) among it. Rotation. On Windows, LogDeleteDays is the rotation. On Linux the package installs /etc/logrotate.d/hmailserver; the rule uses copytruncate because the server does not reopen its log on a signal, and the rule says so at length. Debug logging: on to diagnose, off afterwards. The stage-timing lines that diagnosing stalled mail depends on are debug lines; the slow ones (ten seconds or more) are written at application level as well. Leaving debug on matters because a full disk stops mail: the application log warns below DiskSpaceWarningThresholdMB (1024 MB), and below MinimumFreeDiskSpaceMB (100 MB) on the message store's volume the server answers 452 4.3.1 Insufficient system storage. and senders retry. The live views Windows: the Control Panel. Monitoring & troubleshooting → Live logs is a real-time, colour-coded tail of what the server is writing; every page in that group but Discovery reads the running service over COM (Discovery has no COM interface, so it reads the REST listener and needs RestApiPort set). Diagnosing stalled mail, in the same group, has a one-click debug-logging toggle. Both platforms: the Control Deck and hmctl. The browser Control Deck at / on the REST listener has a Logs page that lists the log files and shows any file's last 200 or 2,000 lines, without a share to the log directory. Since 6.3.4, hmctl log list and hmctl log tail <name> --lines N read the same over GET /api/v1/logs and GET /api/v1/logs/{name}, and work against a Windows or a Linux server because they go over HTTP rather than DCOM. The Linux packages install hmctl as /usr/bin/hmctl; the HmailServer PowerShell module is the same vocabulary for PowerShell. The files themselves, from a shell. On Windows: $log = "C:\Program Files\hMailServer\Logs\hmailserver_" + (Get-Date -Format "yyyy-MM-dd") + ".log" Get-Content $log -Tail 0 -Wait | Select-String "SMTPD|SMTPConnection" On Linux: tail -f /var/log/hmailserver/hmailserver_$(date +%F).log journalctl -u hmailserver -f Following one connection, since 6.3.4 Every SMTP, POP3 and IMAP conversation is a session with a number, and every line the log writes for it carries that number. Until 6.3.4 that meant searching the day's log for the number afterwards. Since 6.3.4 the Connections section at the top of the Deck's Logs page lists every connection the server has open now, newest first, and the last two hundred that ended: the id, the protocol, the address it came from, the port it came in on, the account once it has signed in, and when it started or ended. Follow beside any of them opens a pane holding every line the log has for that session and keeps adding lines as the server writes them; Copy puts the whole pane on the clipboard; Stop following ends it. A session that ended a second ago is still listed, so an SMTP conversation that lasted a quarter of a second can be picked and read. How it works, and what that means for you. The server keeps the last 4,096 log entries in memory, across every session; the ring is always on, and nothing survives a restart. A debug-logged IMAP client can fill it in minutes, so a follow whose cursor points before the oldest entry still held answers dropped: true and starts from what is left; the page says so. A line older than that is in the log files and nowhere else. The conversation lines (SMTPD, SMTPC, POP3D, POP3C, IMAPD) carry the session id in their own column. DEBUG and TCPIP lines carry none and are filed by the first number after the word session in their text (TCP connection started for session 41, Ending session 41); that is an inference, and the text is not always the server's own. A line that does neither is nobody's and is not shown. It shows what the log mask lets through, and no more. With IMAP logging off, an IMAP session is listed and its follow is empty; the answer's logged field says whether the log mask lets that protocol's lines through, and the page names the switch to turn on. Debug logging is what makes the TLS handshake, the timeouts and the disconnect appear beside the conversation. The connections the server opens itself are listed too, marked client: SMTP client, POP3 client, IMAP client, and the short-lived SpamAssassin and Diagnostics connections. Not listed: anything that is not a TCPConnection, so the REST API, the webmail, the DAV services, ManageSieve and the anti-virus scanner's socket to clamd are not sessions here. A delivery to a host that refuses the connection is not listed either, though with debug logging on its lines can still be followed by id, with known: false. A follow is a long poll, not a stream: GET /api/v1/logs/sessions/{id}?after=0&wait=10 answers the session's lines after sequence 0 and a cursor to ask after next time, waiting up to wait seconds for the next line. The wait is at most twenty seconds, and one waiting follow at a time is allowed, because a waiting follow holds one of the HTTP listener's four worker threads, which also serve the webmail; a second follow is answered at once with waited: false. An answer carries at most 500 lines, and the pane keeps the last 5,000. GET /api/v1/logs/sessions lists the sessions (the newest thousand when more are open, with truncated: true), with held_lines and logging_enabled. Both routes are under the view_logs permission, which the built-in read-only auditor role holds, and both are server-wide: a credential confined to some domains is refused them, as it is refused the log files. There is no Control Panel page and no hmctl verb for this; the Control Panel's Live logs is the older, whole-log form over COM. There is no filtering inside a session. A copy to syslog, or to the journal, since 6.3.4 Since 6.3.4 every entry the server logs can also be sent as RFC 5424 to a syslog collector or, on Linux, where the server runs as the packaged systemd unit with no collector host named, to the journal. It is a copy: the files above are written exactly as before, and nothing it does can stop a line reaching them, or delay or lose a message. Its settings are hm_settings rows, not [Settings] keys, read when the sink starts (the next service start or reinitialise), not when they are saved. With the journal on, journalctl -u hmailserver HMAILSERVER_SESSION=41 follows one session from the journal, across a restart. Alerts, the audit trail, syslog and the journal has the twelve settings, the transports, the rsyslog configurations and the journal's fields. Codes in the error log worth knowing since 6.3.4 Code What it says, and what to do HM5804 A value typed into hMailServer.INI [Settings] for a key the database holds was put back at the last start. Change it in the Control Panel, with PUT /api/v1/settings/ini/{name}, hmctl ini set or COM Settings.SetIniSetting HM7460 AutoBanFirewall is on and the Windows service account cannot write firewall rules (below) HM7495 The database records a stored-secrets key this server does not hold, and every account's second factor refuses every code: import the exported key with hMailServer.exe --import-stored-secrets-key FILE (elevated) or sudo hmailserver --import-stored-secrets-key FILE, then restart HM7540 A restore was refused because the archive holds an entry with an absolute path, a .. segment or a link with somewhere to point; nothing was extracted. Find out where the archive came from fail2ban on Linux, and the Windows contrast An auto-ban (repeated failed logons from one address) is enforced by the server's own listeners: the kernel accepts the connection and the server refuses it. Shipped in 6.3.2, the Linux package offers two ways to push the ban below the server, and one is enough. fail2ban needs no capability at all, because it runs as root and reads the application log. The package installs /etc/fail2ban/filter.d/hmailserver.conf and a jail at /etc/fail2ban/jail.d/hmailserver.conf, disabled as Debian asks of a shipped jail. The filter matches the two fixed-shape lines the server writes for every counted failure and every ban, whether or not protocol logging is on; auto-ban on logon failure (AutoBanOnLogonFailureEnabled, on as shipped; auto_ban_on_logon_failure over REST) is what writes them. Turn it on: printf '[hmailserver]\nenabled = true\n' > /etc/fail2ban/jail.d/hmailserver.local systemctl reload fail2ban fail2ban-client status hmailserver The server itself, with AutoBanFirewall=1, makes the firewall match its auto-ban ranges at start, on every ban, on every range deletion and once a minute. On Linux it runs /usr/lib/hmailserver/autoban-hook, which keeps an nftables set with a timeout (iptables where there is no nft) and drops TCP from the banned address to the ports the server listens on. Changing the firewall needs CAP_NET_ADMIN, which the unit deliberately does not grant a mail server by default. Grant it once with a drop-in, and the hook's check verb says whether it can act: systemctl edit hmailserver [Service] AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN systemctl restart hmailserver sudo -u hmailserver /usr/lib/hmailserver/autoban-hook check AutoBanCommand, on either platform, runs a program of yours instead of, or as well as, AutoBanFirewall (<command> ban <address> <minutes> <ports> and <command> unban <address>) for a cloud firewall or a router. Before turning either on, list the addresses you administer from in AutoBanNeverBan: a ban that reaches the firewall locks that address out of every mail port on the machine. Since 6.3.4 AutoBanFirewall, AutoBanCommand and AutoBanNeverBan are stored settings like the other [Settings] keys: set them over the API or with hmctl ini set, not in the file. The fail2ban filter and the hook are part of the Linux packages. On Windows, AutoBanFirewall writes one inbound block rule per banned address in Windows Defender Firewall, named hMailServer auto-ban <address> and scoped to the ports the server listens on, and since 6.3.4 a new installation's service account, NT SERVICE\hMailServer, cannot write them: HM7460 is logged once a start and no rules appear, while the listeners still refuse a banned address. Turn AutoBanFirewall off there, or use AutoBanCommand towards a router or cloud firewall; HM7461 names rules a LocalSystem run left behind, with the PowerShell that removes them. What to include in a report SUPPORT.md asks for, in order of value: the relevant log, not a description of it, with debug logging on and the window around the problem; the ERROR_hmailserver_<date>.log for the same window; the exact version, the platform and the database backend; and what you expected instead, and whether it is reproducible. Remove passwords, keys and real email addresses first, because a posted log is public. Read this before you post asks for the same log lines and adds the build number, the package, whether the server was upgraded and the Diagnostics output; Copy on the Diagnostics page puts the whole result on the clipboard. Without a GitLab account, email the report to the project's Service Desk address, contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as a confidential issue; the maintainers answer by email. Where to read more Wiki: Monitoring and Health, sections 16.2 and 16.2a; Troubleshooting, sections 19.10 and 19.11; Installing on Linux, for the journal and fail2ban. Repository: FollowConnection.md, Syslog.md, SUPPORT.md. The public documentation: Logs on disk, Logging and log rotation on Linux, Follow one message end to end in the logs, Codes in the error log. On this forum: hMailServer on Linux: what you get and what you do not, The Control Deck, Diagnosing stalled mail, and IP ranges, auto-ban and lockout for when a ban is made and how to lift one.
  • 0 Votes
    1 Posts
    129 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.8 (2 November 2026), a security release, is the release to install: as 6.3.7 (17 October 2026) did, beside the generic .deb and .rpm for x86-64 and AArch64 it carries a package for each of Debian 12 and 13, Ubuntu 24.04, Fedora 43 and 44, Enterprise Linux 9 and 10 and openSUSE Leap 16.0 and Tumbleweed, each installed in a clean container of its release by that release's own package manager before publication, so step 1 is apt, dnf or zypper with your release's own package; the AArch64 files are cross-compiled and tested under emulation. Check a download with sha256sum -c --ignore-missing hmailserver-linux-6.3.8-SHA256SUMS.txt first. See the 6.3.8 announcement. The order matters more than the commands. The package installs the server enabled and stopped, on purpose: it has no database yet, and nothing in a package can know which backend you have or its credentials. The full sequence, with the output each step prints, is §35.3. Take the exact file names from the downloads page, and note the RPM carries a -1 release field in its name. 1. Install the package. sudo apt install ./<file>.deb on Ubuntu 26.04 or 24.04, sudo dnf install ./<file>.rpm on Fedora 43 or 44 or Enterprise Linux 10. 6.3.4's .deb needs glibc 2.38 or later, so it does not install on Debian 12; its .rpm does not install on Enterprise Linux 9, and nothing has been tried with zypper on openSUSE. The maintainer script creates the hmailserver system user and group, creates the directories, and enables the unit without starting it. It deliberately does not walk or chown an existing store. 2. If your backend is MySQL or MariaDB, install the client library now. The server opens it at run time and no package manager will pull it in: libmariadb3 on Debian and Ubuntu, mariadb-connector-c on Fedora and RHEL, mariadb-libs on Arch. PostgreSQL needs nothing extra. 3. Create the database role. Either let the role create its own database, or create an empty one yourself and leave the role without CREATEDB. §35.4 has both, and the SSL keys for a database on another host. 4. Edit /etc/hmailserver/hMailServer.ini. Fill in [Database]. Two rules catch people. Only ; starts a comment, and only at the start of a line. Text after a value on the same line is part of the value, so Type=PostgreSQL ; the backend matches no backend name. Write the port out: PostgreSQL wants 5432, MySQL and MariaDB want 3306. Leave AdministratorPassword alone. 5. Set the administrator password. sudo hmailserver --set-admin-password reads it from standard input with echo off and writes the hash into the file. This is the one step that runs as root, because the file is 0640 root:hmailserver. 6. Create the schema. sudo -u hmailserver hmailserver --create-database. Then, as root, make the stored-secrets key that second factors and the [Settings] secrets are sealed under, and export it at once: sudo hmailserver --create-stored-secrets-key, then sudo hmailserver --export-stored-secrets-key /root/hmailserver-stored-secrets.key. Keep that file with the database backups, stored apart from them. Without a key those secrets are stored as written, as before 6.3.4. 7. Check it before starting anything. sudo -u hmailserver hmailserver --check-config, as the service user and without --config, so the server has to find the configuration on its own. Database type: 0 means the Type key did not take. It is a report and not a validator: it returns 0 unconditionally, so read the output rather than its exit status. 8. Start it. sudo systemctl start hmailserver, then journalctl -u hmailserver -f. A freshly created database already holds four listeners: SMTP on 25 and 587, POP3 on 110 and IMAP on 143, bound to every address. Since 6.3.4 the unit's first start, if it comes within a day of --create-database, gives them a self-signed certificate and optional STARTTLS, and makes the Internet IP range require TLS for authentication (SecureDefaults.md). Mail clients warn about a self-signed certificate, so replace it with ACME or your own before this faces the internet. The listeners and the certificate are database rows, not INI keys, so you change them over the API or the Control Deck's Ports and Certificates views. 9. Turn on administration. sudo hmailserver --set-setting RestApiPort=8080 (or another free port), leave RestApiBindAddress on loopback, and sudo systemctl restart hmailserver. Not by editing the file: since 6.3.4 the database is the settings store, and a [Settings] line edited after the first start is reported as HM5804 and not applied: the service cannot write the packaged file, so the line stays as you left it and the stored value is used. TLS is required unless the bind address is exactly 127.0.0.1, localhost or ::1, so tunnel over SSH rather than exposing it. §35.9 and §35.10 are the rest. Post what --check-config printed and the last lines of journalctl -u hmailserver if a step stops. §35.16 lists the Linux-specific failures by symptom.
  • 0 Votes
    1 Posts
    134 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.8 (2 November 2026) is the current release, a security release: its Linux packages are, as 6.3.7's (17 October 2026) were, the generic .deb and .rpm for x86-64 and AArch64, a package for each of Debian 12 and 13, Ubuntu 24.04, Fedora 43 and 44, Enterprise Linux 9 and 10 and openSUSE Leap 16.0 and Tumbleweed, and both AppImages, and its container image is registry.gitlab.com/hmailserver/hmailserver:6.3.8; the AArch64 files are cross-compiled and tested under emulation, and the Ubuntu PPA and Fedora Copr, which have published 6.3.7 since 21 October 2026, are sent it by a job started by hand. From 6.3.6 or 6.3.7 the live update installs it through the root helper, for the generic packages and the AppImages; a server from one of 6.3.6's or 6.3.7's per-distribution packages upgrades with its package manager and its release's own package, and from 6.3.4 or 6.3.5 install it with apt, dnf or zypper. 6.3.8 names files in UTF-8 whatever LANG says, so on a server that ran under a LANG that is not UTF-8, rename each file and folder named outside ASCII to its UTF-8 name before upgrading. See the 6.3.8 announcement. Since 6.3.0 the same source tree builds, installs and delivers mail on Linux, as a systemd service, from a .deb, an .rpm or an Arch PKGBUILD: on x86-64 and AArch64 up to 6.3.3, and on x86-64 only in 6.3.4's packages. That is why the release is numbered 6.3 and not 6.2.29. Read §35.1 before you install anything. Finding out about a missing capability after the mailboxes are populated is the wrong order. What crossed The SMTP, POP3 and IMAP engines, delivery, routing, rules, anti-spam, anti-virus, ACME, the backup manager, the REST API, the Control Deck and the self-service portal. The on-disk message format is unchanged: one directory per domain and per mailbox, one file per message. The database schema is the same version the Windows build of the same release needs. The packages 6.3.4's .deb installs with apt on Ubuntu 26.04, where it is built, and on Ubuntu 24.04. It needs glibc 2.38 or later, so it does not install on Debian 12. Its .rpm installs with dnf on Fedora 43 and 44 and Enterprise Linux 10, not on Enterprise Linux 9, whose glibc and libstdc++ are older than it asks for, and nothing has been tried with openSUSE's zypper. There is no apt or dnf repository yet. Arch is a PKGBUILD in the tree, built with makepkg -si from the git tag, which it clones from https://gitlab.com/hmailserver/hmailserver. 6.3.4's packages are x86-64 only: the AArch64 packages were last published with 6.3.3, built natively on the CI the project lost with its GitHub account. Nothing else is packaged. The AppImage is not for running mail. It runs as whoever started it, with no hmailserver user, no unit and none of the systemd hardening, it puts its store under the invoking user's home directory, and it cannot bind port 25. Use it to see the thing work on a laptop. 6.3.4 has none; the last AppImage is 6.3.3's. §35.2. Where things live Configuration is /etc/hmailserver/hMailServer.ini at 0640 root:hmailserver. The store is /var/lib/hmailserver, the logs /var/log/hmailserver, the unit /usr/lib/systemd/system/hmailserver.service. The service runs as hmailserver:hmailserver and never as root. Full table: §35.6. The backends are PostgreSQL through libpq, a link-time dependency, and MySQL or MariaDB through a client loaded with dlopen at run time, which nothing pulls in for you. SQL Server and SQL Server Compact are refused by name, so an INI carried across from Windows is repointed or it does not start. The gaps, stated plainly No Control Panel and no COM API. Both are Windows-only and are not compiled here. Every third-party COM script stops at that boundary. Event scripts in JavaScript only, since 6.3.4. EventHandlers.js runs in an embedded QuickJS engine, for the same events under the same function names; the scripting language must be set to JavaScript (hmctl settings set scripting --set-string language=JavaScript), and VBScript and JScript are refused. Scripting is off until you turn it on. A script has no file, process or network access and cannot create COM objects, so a Windows script is ported, not copied (EventScripts.md). Domain properties were not writable when Linux arrived, and are now. At 6.3.0 per-domain DKIM, per-domain size limits and the domain signature were readable over REST and not writable. 6.3.3 made PUT /api/v1/domains/{domain} take the whole domain - its DKIM signing, its limits, its signature and its relay host among the rest - and gave the Control Deck full domain editing. At 6.3.4 the project's census finds 458 of the 466 properties the Windows Control Panel writes writable over REST and reachable from a Deck view, and the eight it lists as missing, an account's directory fields, are written on a route of their own, PUT /api/v1/accounts/{address}/directory, which the Deck's account editor uses (DeckParity.md). Self-update from 6.3.4, but not to it. The packages now install hmailserver-update, a root helper outside the server's unit, and the live update, when an administrator asks it to install, applies a .deb, an .rpm or an AppImage through it, rolling back when the server does not come back. The update check is off until you turn it on. 6.3.4 itself carries no Sigstore bundle, so no live update installs it: every server reaches 6.3.4 with apt or dnf, and the helper has nothing to apply before a release signed on the project's GitLab CI. Arch upgrades with pacman. No tested path from Windows. The schema and the store format are shared, so the pieces are there. Nobody has run a Windows installation onto Linux and verified the result. §35.14 says what is known. Administration is therefore the Control Deck at / on the REST listener, and the REST API behind it. §35.10 covers the daily jobs.
  • 0 Votes
    1 Posts
    134 Views
    ProgressiverobotP
    Moving to new hardware is a restore onto fresh metal. Chapter 15 is the prerequisite, and the first time, rehearse onto a test machine. The plan (§33.2) Check the new machine against the chapter 3 checklist. Size the disk for the mail store you have now, not the one you had at installation. Install the same version, in the same path: a different version or directory is where these moves go wrong. Since hMailServer 5.4 a message row holds a bare file name and the path is rebuilt from the data directory; a database from before 5.4 can still hold full paths. Back up the old server. The built-in backup does not include messages unless you tick the option. For a large store, stop the service, copy Data\, dump the database natively, and take hMailServer.INI, Events\, your DKIM keys and, if one was made, an export of the stored-secrets key (--export-stored-secrets-key). Stop the old service for good and set it to Disabled. Anything it accepts after the backup is stranded. Restore the data directory, the database and hMailServer.INI, in chapter 15's order. Re-enter the protected secrets. Keep the host name if you can. Update the A record with a lowered TTL, get a PTR on the new IP, re-run the firewall rules, repoint your port forwards, and run Monitoring & troubleshooting, Diagnostics last. Leave the old machine powered off but intact for a week or two. It is the rollback. Budget for step 6. With ProtectStoredSecretsWithDPAPI=1, the default, stored passwords are encrypted with machine-scoped Windows DPAPI and cannot be decrypted on the new machine. Re-enter the database connection password with Bin\DBSetup.exe, then route, smart-host relayer and external-account passwords in the Control Panel. Forget this and you get a server that cannot reach its own database. Since 6.3.4 the stored-secrets key that seals second factors and the [Settings] secrets is kept in hMailServer.INI under the same DPAPI, so a copied INI does not carry it: import the exported file on the new machine with hMailServer.exe --import-stored-secrets-key <file> and restart the service. An upgraded server has no such key until an administrator makes one. Linux to Linux is easier: secrets sit under <DataFolder>/.hmailserver-secret-key, so copy that file too, preserving mode 0600 and its ownership. Windows to Linux is not a migration It is not supported and the project does not claim it is. The schema is the same on both, 6.3.4 needs 6104 either way, and the message store's on-disk format is the same. What is missing is a tested path: nobody has run a Windows installation onto Linux and verified the result (§35.14). What is known to break: SQL Server. An INI with Type=MSSQL or Type=MSSQLCE is refused by name. Move to PostgreSQL or MySQL on Windows first. Every DPAPI secret. Route, fetch-account and per-domain relay passwords, private-key passphrases and the administrator's TOTP secret. Each is reported once as HM6414 and answers empty. Event scripts and COM integrations. COM does not exist on Linux, and since 6.3.4 event scripts run there in JavaScript only: a VBScript file is rewritten and a JScript one ported, without COM objects. DKIM key paths. The per-domain key-file setting holds a Windows path such as C:\keys\mail.pem, which resolves to nothing on Linux. Copy the keys and re-point every signing domain. Directory case. The Linux build lower-cases directory names derived from an address; Windows leaves them as found. A store carrying Test.com is one this server looks for under test.com, and everything under it must end up owned by hmailserver. So treat it as a rebuild and a cutover. A new installation on Linux, accounts and mail brought over at mailbox level by mirroring folders over IMAP from the running Windows server. The Import Tool is not an option here: it is a Windows .NET program that drives the COM API, and Linux has neither (§33.8), verified against a still-running Windows server, DNS moved last.
  • 0 Votes
    1 Posts
    54 Views
    ProgressiverobotP
    hMailServer 6.3.0 runs on Linux. Every core translation unit compiles there, counted file by file by build/linux-tu-census.sh under clang on x86-64 and AArch64, with a separate job linking the core with GCC. The Windows build is the same MSVC project it was and behaves as it did. What the upgrade involved No schema change. The schema is 6031, as in 6.2.28, so the installer's database upgrade has nothing to do. On Windows, run the installer over the existing installation. Nothing this release adds is on by default: the REST API needs RestApiPort, and a server upgraded without touching its settings behaves exactly as 6.2.28 did. Per-domain DKIM cannot be configured on Linux. There is a read route and no write route, and a PUT answers 404. A domain that must sign its outbound mail with DKIM is not one to run on Linux today. Packaging. A .deb and an .rpm for both architectures, a PKGBUILD for Arch, and an AppImage: a systemd unit running the server as its own user, the configuration under /etc/hmailserver, and --create-database, --upgrade-database and --set-admin-password. Proven against PostgreSQL 18 and MariaDB 11.8, and against a real slapd over StartTLS and LDAPS. REST API. PUT /api/v1/settings and its anti-spam and logging groups write 108 settings, each through the same setter the Control Panel calls, applied only when every key in the request is accepted. Global rules, SMTP routes, aliases, accounts, certificates and listeners too, plus POST /api/v1/server/reinitialize, so a new listener takes effect without stopping the process. Control Deck and /portal. The administration page no longer stores the administrator password: POST /api/v1/session exchanges it once for an HttpOnly, SameSite=Strict cookie that ends when that password changes. /portal is a webmail now, polling GET /api/v1/me/changes every six seconds. Fixes. On Linux, IMAP's modified UTF-7 was broken in both directions, so non-ASCII folder names were not stored correctly, and CStdString stopped converting at the first byte above 127, cutting short IMAP SEARCH CHARSET UTF-8 and MAIL FROM under SMTPUTF8. A TLS key-exchange group list OpenSSL rejects is now reported once, not once per listener and per delivery: on the OpenSSL Debian and Ubuntu ship, hundreds of errors an hour. The Control Panel sign-in box no longer translates the administrator user name, which made a fresh installation in Chinese, German or Swedish refuse the credential it had just asked for. (GitHub issue 156, #177) Full release notes, checksums and signatures