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
    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
    126 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) bears on relocating a Windows installation to another drive. hMailServer.exe /ProtectFolders, which setup runs at every installation and upgrade, now hardens the program folder, Bin and ControlPanel: where hMailServer was installed in a folder that took its permissions from the root of a drive, any account that could sign in to the computer could replace the programs the service runs (CVE-2026-107571); the default folder under Program Files was never affected. The procedure below copies the tree and re-registers it without running setup, and a folder below a drive's root takes the permissions the root hands down, so an installation relocated there is hardened by the upgrade to 6.3.7, or on 6.3.7 by running hMailServer.exe /ProtectFolders as an administrator, not by the move (on 6.3.6 or earlier follow the icacls steps in ServiceAccount.md, since those versions' /ProtectFolders leaves the three program folders as they are); before upgrading an installation at a drive's root, follow the steps "Before upgrading an installation at a drive's root" in ServiceAccount.md. Where accounts that are not administrators could sign in to the computer while the installation was in such a folder, the release says to reinstall the programs into a new folder that only the administrators may change, not over the installation, with setup given the new folder by /DIR=: over an installation setup installs into the folder it finds, and a 32-bit installation's uninstall entry overrides /DIR= until it is taken away, which the "re-run the installer" repair below meets. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) has setup make or harden the program folder, Bin and ControlPanel before it copies a file or runs a program of the installation. One of them that another account could change, as a folder below a drive's root can be, setup hardens and then refuses, with exit code 7, until Windows has restarted; and a folder or a link on the way to them, or a program, library, script or settings file in them, that another account than SYSTEM, the Administrators and TrustedInstaller controls, it refuses with exit code 7, naming the icacls that deals with it and a restart of Windows before setup runs again. On Windows 6.3.8 may also refuse, with HM7534 or HM7536, TlsCaFile, PostgreSQLSslRootCert, a TLS certificate or private key file, OAuth2PublicKeyFile or AcmeCertificateDirectory where its path holds characters outside ASCII, so give the new root, and the folders above it, an ASCII name. See the 6.3.8 announcement. This guide covers two different moves. The first is relocating a configured installation to another directory or drive on the same machine, for example from C:\Program Files (x86)\hMailServer to C:\Program Files\hMailServer. The second is renaming a mailbox or moving it to another domain, which is an operation of its own since 6.3.4. Moving to new hardware is a third thing, and Moving to new hardware, and why Windows to Linux is a rebuild covers it. The one thing to know first: relocating is not one change. The installation path is recorded in the registry, in the service, in the COM registration, in hMailServer.ini and in a handful of database columns, and since 6.3.4 a [Settings] path edited in the file is ignored in favour of the value stored in the database. The relocation runbook is written in Windows terms: the registry, the service and the COM registration. The documents used here give no separate procedure for a Linux installation. Installing somewhere new is a different, solved problem: /DIR="<path>" on the installer puts a fresh installation wherever you want. The good news first The mail store itself is already relocatable. Message rows hold a bare {guid}.eml with no directory, and the path is rebuilt at every read against the data directory. Moving the data directory and pointing DataFolder at its new home moves every message with it. The exception is a database that predates hMailServer 5.4, where the column can hold a full path. Before you touch anything Take a backup and verify it restores (Backup and restore). Everything below is reversible only while the old tree and a good backup both exist. Ask the server whether its message rows are relative. In the Control Panel, Monitoring & troubleshooting → Diagnostics → Test message file locations. If it reports ERROR: Full paths are stored in the database, the move is still possible, but converting the paths becomes mandatory rather than optional. Write down what you are about to strand. The same Diagnostics page has an Installation paths section that lists the registry root, every [Directories] key and every path-shaped setting the running server holds, with the ones that do not exist marked. It changes nothing. The inventory Where What Repaired by Bin\hMailServer.ini, [Directories] ProgramFolder, DataFolder, DatabaseFolder, LogFolder, TempFolder, EventFolder Editing the file Bin\hMailServer.ini, [Database] PostgreSQLSslRootCert and TlsCaFile Editing the file. Leave the connection keys alone: they name a server, not a path The settings store (hm_inisettings) ArchiveDir, AcmeCertificateDirectory, OAuth2PublicKeyFile, UpdateTrustRootsFile, UpdateLogPublicKeyFile, and the certificate and private key files of the REST API, metrics and web services listeners Storing the new value, not editing the file (since 6.3.4) Registry, HKLM\SOFTWARE\hMailServer in the 32-bit view InstallLocation By hand Registry: the service's ImagePath, LocalServer32 (79 keys), InprocServer32 (15), the TypeLib values hMailServer.exe /Register Registry, HKLM\...\Uninstall\hMailServer_is1 InstallLocation, UninstallString, DisplayIcon Nothing; re-run the installer The database Certificate and key files, DKIM key files, the client-certificate CA bundle, backupdestination, the ClamWin and custom scanner paths, rule actions Each on its own page What breaks when a key is left behind. With a stale DataFolder the store appears empty. With a stale LogFolder logs are written to the old tree, silently. With a stale EventFolder event scripts are not found and events stop running. The 32-bit view is not optional. The server reads InstallLocation through KEY_WOW64_32KEY, and the installer writes it there. Writing the 64-bit view changes nothing and looks like it worked. The procedure Stop the service, and confirm it is stopped. Nothing here may run against a live server. Copy the tree. Copy, do not move: the old tree is the rollback. Include Bin\hMailServerApiKeys.ini if it exists, since the REST API key store lives there and no installer manifest mentions it. Grant the service account access to the new tree as 6.3.8's RelocatingAnInstallation.md does: a copy takes the permissions of the folder it lands in (under Program Files every user's Read on the new Data, Logs, Temp, Database, Events and hMailServer.INI) and not the old tree's entries for NT SERVICE\hMailServer, so the document makes the new root with the permissions setup gives a program folder before the copy, runs hMailServer.exe /ProtectFolders from the new Bin after step 5's /Register, and grants a service that runs as anything but LocalSystem its folders again with /GrantServiceAccess. A server that cannot write its log directory starts and then says nothing. Rewrite the file keys in <new root>\Bin\hMailServer.ini whose value was under the old root: the [Directories] folders, and [Database] PostgreSQLSslRootCert and TlsCaFile, the second named beside the first by 6.3.8's document, since with TlsMode=verify-full a CA file left in the old tree stops the server reaching its database once that tree is deleted (HM7531). Nothing else, and not a folder that was outside the old tree and has not moved: a DataFolder pointed at an empty directory makes the store appear empty. Repoint the registry root, from an elevated prompt: reg add "HKLM\SOFTWARE\hMailServer" /v InstallLocation /t REG_SZ /d "<new root>" /reg:32 /f. /reg:32 is the whole of the point. Re-register the server, from an elevated prompt, from the new Bin: hMailServer.exe /Register. It registers the path of the executable that runs it. Start it, and check what it read. Read the application log, and since 6.3.4 the ERROR log too. Confirm that the Diagnostics page's Installation paths shows the new paths (6.3.8's document names it; the Control Panel has no Settings → Directories page), that your domains are listed, and that the log file you are reading is under the new log directory. Convert full message paths, only if the diagnostic found any: run the Data Directory Synchronizer, in its import mode, on the old installation before step 2 copies anything, as step 0 of 6.3.8's RelocatingAnInstallation.md has it, not against the new data directory after the move: it converts only a row whose full path is where the file is found, so after the move it would import every such message a second time, and its other mode would delete the copies. A hand-written UPDATE hm_messages SET ... is not a supported converter. Store the [Settings] paths and fix the database-borne paths (both below). Verify before deleting anything: send and receive a message; complete a TLS handshake on every port that has a certificate; run a backup to completion; run an event script, if you use them; check the log directory is being written. Keep the old tree, unreachable by the service, for at least one full backup cycle. Do not make it unreachable until the [Settings] paths are stored and a reinitialise has picked up the new certificate and ACME paths. The failure this prevents. A server whose anchors are stale reports Running and serves an empty configuration. That failure is silent. The [Settings] paths: 6.3.3 and earlier, and since 6.3.4 On 6.3.3 and earlier these are [Settings] keys of hMailServer.ini like any other, and editing the file moves them. Since 6.3.4 it does not: the stored value is used, the edit is named in the error log as HM5804, and, where the service can write the file, the line is put back to the old path. RelocatingAnInstallation.md and the wiki page between them give three ways to store the new values. The first is the document's own, and the one to use: When How Where it is described After the moved server is running In the Control Panel under Settings, with Settings.SetIniSetting over COM, PUT /api/v1/settings/ini/{name}, or hmctl ini set <name> <value> RelocatingAnInstallation.md, step 9, and the wiki Before stopping the old server The same four ways, as the last thing before the service is stopped. A save applies ArchiveDir, OAuth2PublicKeyFile, UpdateTrustRootsFile and UpdateLogPublicKeyFile at once, not at the next start as the wiki page has it, so the running old server turns to a tree not yet copied: archiving stops, and token validation and update verification refuse. The database then holds the new paths, so going back to the old tree means storing the old values again The wiki, corrected by SettingsApply.md At the first start, if the service is already stopped A [SettingsOverride] section in the new hMailServer.ini, named in the error log as HM5807; then store the same values and remove the section before the next restart The wiki A save applies every one of these paths at once except the listeners' certificate paths and the ACME certificate directory, which are bound when the servers start and wait for a reinitialise (Apply now, or POST /api/v1/server/reinitialize). Until then the server runs on the old paths, which still resolve because the tree was copied rather than moved. PasswordPepper is the exception to all of this: it lives in the file only, is not a path, and comes across with the copied file. The database-borne paths SSL certificates. Since 6.3.7 the Control Panel's SSL certificates page has Edit…, which changes a certificate's name and files and keeps its id, so every port that presents it keeps it and reads the new files at the next reinitialise; deleting a certificate and adding it again, as the document had it until 6.3.8, leaves each port that presented it with none, and such a port does not start (HM5113). It can also be set over COM. DKIM keys. Domain → DKIM, the primary and, if used, the secondary. A domain whose key rotation manages its keys refuses that edit. Client-certificate CA bundle, per port. A port set to require a client certificate refuses every client when its bundle cannot be loaded. Archive index. hm_archiveindex.archivepath is matched on the exact string by retention, legal hold and the address eraser. Do not bulk-rewrite it. If the archive directory moved, move the archive back, or accept that the rows written before the move point at the old location; there is no supported converter for this column. What stays broken, and why Add/Remove Programs still points at the old unins000.exe. Do not edit unins000.dat. The supported repair is to re-run the installer at the new root. Start-menu shortcuts hold absolute targets and nothing repairs them. Message content. The old path appears inside messages the server itself wrote, such as a delivery report. Never rewrite message content. Renaming a mailbox, or moving it to another domain New in 6.3.4 (schema 6056, whose table hm_mailboxmoves is the move's journal). new_address carries the domain, so renaming and moving are the same operation: [email protected] to [email protected] is a rename, [email protected] to [email protected] is a move. A move keeps the account's row, so everything keyed on the account id follows without being touched: rules, folders with their subscriptions and access-control entries, application passwords, the second factor, contacts, calendars and the role assignment. Copying the mail into a second account loses all of it. Interface How Control Deck The Move or rename card in the account editor hmctl hmctl mailbox status [email protected], then hmctl mailbox move [email protected] [email protected] REST POST /api/v1/accounts/ann%40example.com/move with {"new_address": "[email protected]", "keep_alias": true}; GET on the same path says whether anything holds the mailbox PowerShell Move-HmMailbox -Address [email protected] -NewAddress [email protected] Control Panel (Windows) Move or rename… on the account's General tab COM (Windows) Account.MoveTo(NewAddress, KeepOldAddressAsAlias) or Utilities.MoveMailbox(Address, NewAddress, KeepOldAddressAsAlias) The old address is kept as an alias by default, pointing at the new address. Turn it off (keep_alias false, --drop-alias) only when the address is to stop working: mail to it is then refused at RCPT TO. What moves: the message store directory <data>/<domain>/<local part>; the Sieve tree; aliases in any domain that point at the account; distribution list memberships; recipients of mail still in the delivery queue; and quarantined mail, up to a ceiling of 200,000 rows. What does not move, on purpose. The archive and the message trace stay under the old address, as a record of the past. Erasure works by address, so erasing the new address will not reach the archive written under the old one: erase both (Compliance: personal data and erasure). The domains a role reaches are not rewritten either, so check a role after a cross-domain move (Administrator roles end to end). What is refused, before the first byte is moved: a domain this server does not hold, or one that is not active; a destination domain at its maximum number of accounts, or that would exceed its maximum total size; an account, an alias or a distribution list already at the new address; a mailbox encrypted at rest moving to another domain (409); a rename inside the domain is not refused; an installation whose message file names are still full paths; a credential that may not administer accounts in both domains; a mailbox in use. In use means a POP3 session, or an IMAP session with one of the account's folders selected. What is not counted: an IMAP session that has selected nothing, a webmail request in flight, a ManageSieve session and an SMTP delivery to the mailbox in flight. A message that arrives during the copy is written under the old directory, and the old directory is swept once afterwards; anything that could not be carried is HM6948. That is why a move of a large busy mailbox belongs out of hours. When a move fails or is interrupted There is no transaction across a filesystem and four database backends, so the order is chosen to make each step undoable: the store, then the account row, then the references, then the alias. A journal row is opened before the first step and closed after the last, and an interrupted move is said once per start as HM6947 in the application log, naming both addresses and the stage it stopped at. Stage What it means started Nothing has been changed moving the store The copy was under way; look at both directories. The database has not been touched store moved The directories are at the new address. While the account row says the old one, the account authenticates and its mail is unreadable account written The store and the account row agree; the references have not all been rewritten references rewritten Everything but the alias complete The move finished rolled back A step failed and everything before it was put back rollback failed Something could not be put back; HM6940 is in the application log To finish a move interrupted at moving the store or store moved, ask for the move again, naming the address the directories now have. Read the journal with hmctl mailbox status or GET /api/v1/accounts/<address>/move. The journal is not in the backup. Code Meaning HM6941 The move succeeded but the alias for the old address could not be created HM6942 Aliases pointing at the account could not be re-pointed HM6943 Distribution list memberships could not be moved HM6944 Messages in the delivery queue still name the old address HM6945 Quarantined messages still name the old address HM6946 The move could not be journalled. It still happened, and is on the audit trail Who may. manage_accounts, in both domains. The server administrator and a domain administrator of both domains have it; the help-desk role and the auditor do not. view_accounts is enough to read the journal. A move is one entry on the audit trail: object type account, action moved. What it does not do The relocation runbook does not repair the uninstall entry or the shortcuts, and it has no converter for the archive index. A mailbox move does not wait for a delivery in flight; nothing in the server records one. Setting Account.Address and saving goes through the same implementation, but leaves no alias, does not refuse while the mailbox is in use, and cannot cross domains. A domain rename (Domain.Name and Save()) is a different thing again, and keeps the path it has always taken. Where to read more Wiki: Relocating an Installation. Documents: RelocatingAnInstallation.md and MailboxMove.md. The public documentation: 33.1, the installation on disk. On this forum: Moving to new hardware, and why Windows to Linux is a rebuild, Upgrading to 6.3.4, and how the settings store works and when a save takes effect in Settings: what is not stock, when a save takes effect, and the three levels.
  • 0 Votes
    1 Posts
    130 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) regroups the Control Panel's sidebar: the audit trail, journaling and eDiscovery are a Compliance group of their own rather than the last rows of Monitoring & troubleshooting, as the table below has it, and Ctrl+K takes every old title and route to its page. See the 6.3.7 announcement. This guide covers what an operator is asked for when a lawyer, a regulator or a person with a data request arrives: eDiscovery (cases, searches, legal holds and exports), a person's own data (the export and the erasure), and the audit trail that records who did what. eDiscovery, the person's data as one archive, the erasure of log lines, the erasure's reach into the stores beside the mailbox, and the audit trail are new since 6.3.4. The one thing to know first: cases, their files and their holds are not in the backup. A server rebuilt from a backup has no cases, no exports and no holds, so place the holds again before its mail can be deleted. Where it is Where What The Control Deck, Discovery view The whole of it: cases, the search form, holds with Lift the hold, exports with Download, Open and Close REST, /api/v1/discovery/cases The same ten routes the Deck calls, in the OpenAPI document The Control Panel (Windows only), Monitoring & troubleshooting → Discovery (eDiscovery, in the Compliance group, since 6.3.7) Read-only: the cases and their lines of the audit trail, read over the REST listener, which must be on (RestApiPort). It changes nothing COM, hmctl Neither On Linux, then, discovery is worked from the Deck or over REST. Discovery and the erasure do nothing until somebody opens a case or asks for an erasure; the audit trail is on from the first start. Two neighbouring tools are not this one. The archive has had a legal hold of its own since before 6.3.4 (/api/v1/archive/{id}/hold), which keeps one archived copy from the archive's retention sweep (Quotas, retention and the archive). Journaling, a copy of a domain's or an account's mail sent elsewhere as it passes, has its own topic: Journaling and dual delivery. Cases A case has a name (up to 255 characters), a description (up to 1,000), who opened it and when, and a state: open or closed. Its searches, holds and exports belong to it. Closing a case releases every hold of it that is still live, each release on the audit trail; a closed case runs no search, places no hold and makes no export, and what it made stays readable and downloadable. A case is never deleted, and an address's erasure does not reach it. The files a case makes are under the data directory, in Discovery\case-<id>\: search-<id>.json for each search and export-<id>.zip for each export. Nothing removes an export; delete its zip by hand when it is no longer needed. Searching POST /api/v1/discovery/cases/{id}/searches takes: Part What it means custodians (required, at most 100) An address, a domain, or * for every account on the server and the whole archive from, to (optional, YYYY-MM-DD) The day a message is dated by its Date field, read as UTC; else the day the server stored it words (optional, at most 50) Every word must be found, a quoted phrase as one: a case-insensitive substring of what IMAP SEARCH TEXT reads attachment (optional) name, type and min_kb; {} is any attachment at all scope mailboxes, archive or both, the default There is no sender or recipient criterion: a word is looked for in the whole decoded header, and a person is named as a custodian. A query the server cannot take is refused with 400 and the reason; 202 answers with the search's id, and the search is queued. Where it looks. For each custodian account: its folders, its deleted items (what a hold kept, and what RecoverDeletedDays keeps), and the archive. A search copies nothing; it lists each hit, and lists separately what it could not read, with the reason. What it cannot see. 100,000 hits. A search stops there and says truncated. Narrow it and run it again. Text inside a PDF, a Word document or a spreadsheet. Only text/* attachments are read, up to 1 MB of characters a message. An attachment criterion still finds the PDF by name, type and size. More than 80 MB a message. It is listed as not searched when the query asks about its body, and never dropped as a non-match. Zero-access mailboxes and messages encrypted end to end are matched on their headers alone and marked headers_only. The stores beside the mailbox, <data directory>\Confidential and \Protected, and archived copies with no index row (a bounce, a delivery report). The full-text index is a filter, never the answer. It is off by default (IndexerFullText). When it is on, a message the index proves cannot contain a word is ruled out; every other message is read. One at a time. Searches and exports are queued and run by one runner, the server's own. Only one hMailServer service may run against a database at a time (Warm standby and high availability), so a standby started after a failover takes over the queue rather than sharing it. Holds POST /api/v1/discovery/cases/{id}/holds takes one of: Body Holds {"scope":"account","target":"[email protected]"} Every message of the account, and its archived copies {"scope":"domain","target":"example.com"} Every message of every account in the domain, and their archived copies {"scope":"archive","search":12} The archived copies search 12 of this case matched, at most 10,000 at once A hold is live until it is released or its case is closed, and what it keeps is kept by any live hold of any case. Path Under a hold The person's delete (IMAP EXPUNGE, POP3 DELE, the webmail's delete, a folder deleted) and an administrator's delete of a message The message leaves the folder, and a copy is kept among the account's deleted items whatever RecoverDeletedDays says (0 by default). If the copy cannot be made, the delete is refused (HM7333) An IMAP MOVE out of the account The original is kept among the held account's deleted items Every message of the account deleted at once Refused, 409 over REST The account's own filters at delivery: a rule's Delete or Reject, a Sieve discard, reject or redirect, a forward that keeps no copy The copy is kept among the deleted items instead of dropped; if it cannot be kept, the message is delivered Retention, the recover-deleted sweep, deleting a deleted item for good Nothing of the held account is deleted A mailbox restore with mode replace, a full restore with the domains option Refused (the full restore as HM5014) Moving the account to another domain, removing its mailbox key, deleting the account or its domain Refused, over REST, SCIM and COM alike The archive's retention sweep and an address's erasure A held copy keeps its file and its index row What a hold does not reach: the server's and a domain's rules, which decide before a message is split between its recipients; spam and viruses refused or deleted before a message is the account's, and what the quarantine discards; the delivery queue; the confidential and password-protected stores; archived copies with no index row. If ArchiveDir was put inside the data directory, a full restore with the messages option replaces held archived copies too, so keep the archive outside it. The hold is not hidden from the custodian. The account's own GET /api/v1/me/recoverable answers held: true and lists what the hold kept, and the webmail's Recover deleted items page offers no delete for good. It fails closed. When the holds cannot be read, every question is answered held, and the error log says so once a minute (HM7332). A hold is a row in the database. The server reads the holds again as soon as one is placed or released, and no more than five seconds after a change made elsewhere. Exports POST .../searches/{search}/export queues an export of a finished search's hits. The zip holds: Each hit as EML, the message's own bytes as the server stores them, under <custodian>/<folder>/<n>-<message id>.eml, and an mbox per custodian folder beside them, in parts of at most 256 MB. SHA256SUMS, so that sha256sum -c SHA256SUMS checks the unpacked export. manifest.json: the case, the search as it was run, who ran it, who asked for the export, every item with its path, SHA-256 and size, and every item withheld, with the reason. The zip's own SHA-256 is kept with the export, and the case answers it (exports[].sha256). Withheld: a zero-access mailbox's messages, and anything deleted, moved or swept since the search ran. Search again to find where a message is now. Limits. The zip written is the 32-bit format, so no export passes 4 GB: narrow the search and export each part. A download past 500 MB answers 413 with where the file is on the server's disk and its SHA-256. An export that would leave the data directory's volume with less than a gigabyte free fails. A discovery export is never a PST; an examiner who needs one converts the EML files with a third-party converter. Who may Permission Allows Held by view_discovery Reading the cases, their searches and hits, holds and exports The server administrator and the auditor role manage_discovery Opening and closing a case, running a search, placing and releasing a hold, making an export The server administrator; a custom role may be given it Downloading an export needs view_mailbox as well, since it hands over the mail itself, the custodians' deleted items among it. A search that reads the archive needs view_archive as well. A credential confined to named domains names no custodian outside them, never *, and is answered 404 for a case that reaches outside them. A person's data: the export One .zip of everything the server holds for an account. The person downloads it from the webmail's Your data card (GET /api/v1/me/export.zip), with no administrator involved; that download is refused (403) to a request authenticated with an app password. The administrator has the Deck's "Everything (.zip)" on the account row, GET /api/v1/accounts/{address}/export.zip (the administrator password's alone), POST /api/v1/accounts/{address}/export with archive, hmctl privacy export <address> --archive <file> and, on Windows, COM Account.ExportArchive(file). Entry What it is manifest.json The address, the moment, and the counts of what follows mail/<folder path>.mbox Every folder with mail, one mbox each account.xml What the backup carries for the account, less the credentials contacts.vcf The address book as vCard 3.0 calendars/<calendar>/<object> Every calendar object as the client sent it preferences.json, filters.sieve The webmail's preferences, and the active Sieve script logs.txt The lines of the server's own log files that name the address, up to 50 MB A download past 500 MB is refused with 413, and the operator writes the archive to disk instead; the disk form has no ceiling but the format's 4 GB. A person's data: the erasure POST /api/v1/addresses/erase, hmctl privacy erase, the Deck's "Erase traces" and, on Windows, the Control Panel's "Erase traces..." remove what deleting the account deliberately leaves: quarantine entries, message-trace rows, greylisting triplets, distribution-list memberships, aliases on either side, the bounce and complaint records, and the rows naming the address in other people's address books and first-contact memory. With the archive included, the person's archive tree is swept and every index row naming the address goes. The log lines are asked for by name (include_logs on the REST body, --logs every occurrence becomes [erased], and a log past 256 MB is reported instead of rewritten. Since 6.3.4 it also reaches the stores beside the mailbox. A message held for review that the address sent or would receive is discarded. The address is taken out of the recipients of a message that expires, and one it sent is ended. A password-protected message to it is ended. The recipients an account remembers writing to, the out-of-office memory, booking policies and correspondents' S/MIME certificates and OpenPGP keys lose it. Every list of addresses is matched whole. Erasing [email protected] leaves [email protected] and [email protected] as they are. A legal hold outranks it. An erasure never deletes a message from a mailbox, so a hold meets it only in the stores it deletes from, and there the hold wins. The REST answer's held counts what a hold kept; run the erasure again once the case is closed. When the holds themselves cannot be read, the erasure fails (HM6250; 500 over REST) and says nothing about a legal hold. What it leaves, by design, among others: mail in mailboxes, the person's own and everybody else's, and a mailbox's deleted items; a list's archive of its posts; other people's calendars; configuration somebody made that names the address (a forward, a rule, a list's moderator); the audit trail, and the alert events, which nothing prunes yet; the spam trap record; the delivery queue and the webhook queue; the contacts in DMARC and TLS reports; the archive's Inbound folder; and backups, which are outside the running server's reach. PersonalData.md has the full list, with each reason. The audit trail, for a compliance reader The audit trail is on by default. It keeps one row in hm_audit for each administrative change made over COM, REST or the Deck, a mailbox's move included, with a few exceptions such as an API key made or revoked or a quarantined message released or deleted; it records changes, never reads. A password, key, token or other secret is recorded as (changed), never as a value. Each row carries the SHA-256 of the row before it and its own, so GET /api/v1/audit/verify tells an edited row from a removed one: tampering is detectable, not impossible, since anyone who can write the table can recompute the whole chain. audit_retention_days is 0 by default, which keeps everything. Three things on it matter here. Every discovery search is recorded with its whole query; a download of a discovery export is recorded on the trail alone, so with the trail off a download is recorded nowhere; and an erasure is recorded with the address it erased wherever it deletes from recorded tables (aliases, list recipients, the archive index): those rows name the person until audit_retention_days ages them out. Alerts, the audit trail, syslog and the journal has the columns, reading and putting a change back, who may read it, and what it does not record. Regulatory scope, in one paragraph The project's own determination, reviewed 12 August 2026, is that the EU Cyber Resilience Act and the revised Product Liability Directive do not place a manufacturer's obligations on it, because it is supplied outside a commercial activity. The document calls itself a self-assessment and not legal advice, and lists the triggers that would re-open it. Under GDPR and UK GDPR the project is neither controller nor processor: the operator of an installation is. The export, the erasure and the log retention limits exist so that operators can meet their own obligations. What it does not do No PST export, and no text read from attachments but text/*. No silent hold: the custodian can see what a hold kept. No export past 4 GB and no download past 500 MB. No saved queries, review tags, redaction or de-duplication across custodians: a message sent to two custodians is two hits. No search of backups, and no case carried by a backup. Where to read more Wiki: Compliance and eDiscovery. Documents: Discovery.md, PersonalData.md, AlertsAndAuditTrail.md and RegulatoryScope.md. On this forum: The Control Deck, Journaling and dual delivery, and what a backup carries and what it leaves in Backup and restore.
  • 0 Votes
    1 Posts
    128 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) is a security release that bears on this guide. On Windows, hMailServer.exe /ProtectFolders, which setup runs at every installation and upgrade, now hardens the program folder, Bin and ControlPanel: where hMailServer was installed in a folder that took its permissions from the root of a drive, any account that could sign in to the computer could replace the programs the service runs (CVE-2026-107571); the default folder under Program Files was never affected, and an installation at a drive's root is hardened by the upgrade itself. The serveradmin role given with a list of domains is confined to those domains over COM as over REST, a credential confined to named domains is refused the changes that reached beyond its domains over REST and COM, and a state-changing REST request that a browser reports as cross-site is refused. SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account, and the stored-secrets key is no longer left in memory freed without being written over. An upgraded Windows server keeps the .NET 10 Desktop Runtime it had, 10.0.12 where 6.3.4 to 6.3.6 installed it, against which Microsoft published advisories on 13 October 2026: bring it to 10.0.13 through Microsoft Update or Microsoft's installer. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) is a security release too: upgrade every server running 6.3.7 or earlier. On Windows setup now makes or hardens the program folder, Bin and ControlPanel before it copies a file or runs a program of the installation (the four narrower cases of work item 78 that 6.3.7's notes left open), and a program folder, Bin or ControlPanel, a folder or a link on the way to them, or a program, library, script or settings file in them, that another account than SYSTEM, the Administrators and TrustedInstaller controls ends the installation with exit code 7, naming the icacls that deals with it and, where it says so, a restart of Windows before setup runs again. The auto-ban saves the IP range it adds under a name of its own, so that it bans the address; the web services listener (CardDAV, CalDAV, Autodiscover) refuses an address an auto-ban or an IP range bans; and a sign-in's name is matched to its account as the per-name lockout and the compromised-account hold count it, so a mail client that signs in with another spelling of the account's address than the stored one, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in: correct the name in the client. See the 6.3.8 announcement. This guide covers what stands between a mail server on the public internet and the people who guess passwords at it. All of it is new in 6.3.4 except auto-ban, account lockout, the administrator's second factor and the IP range option Require TLS for auth, which are older; 6.3.4 sets that option on a new installation and judges it in the checklist. The one thing to know first: the secure defaults are applied only to a database the installation itself created, and an upgraded server is never changed. After an upgrade your listeners, IP ranges and service account are as they were and nothing is sealed until you make a key; the checklist says what is left. What a new installation starts with, and what an upgrade keeps In 6.3.3 and earlier a new installation listened on ports 25, 587, 110 and 143 with no TLS, and neither shipped IP range required TLS for authentication. The create scripts still make that database; the Windows installer, and the Linux packages' first start, now change it before the server first listens. A new installation since 6.3.4 An upgraded server Ports 25 and 587 (SMTP), 110 (POP3), 143 (IMAP) STARTTLS offered, optional as it was IP range Internet (every IPv4 address) TLS required for authentication as it was; the checklist flags it IP range My computer (127.0.0.1) unchanged as it was Certificates Self-signed (made at installation) as they were Windows service account NT SERVICE\hMailServer the account it had (LocalSystem, if it began before 6.3.4) What a client sees. From another machine, AUTH on SMTP is not offered before STARTTLS and is refused with 530 A SSL/TLS-connection is required for authentication. if it is sent; IMAP LOGIN and AUTHENTICATE answer BAD A SSL/TLS-connection is required for authentication.; POP3 USER and AUTH answer -ERR. After STARTTLS each works as before. From the server itself, on 127.0.0.1, nothing has changed, and mail from other servers on port 25 is taken with or without TLS. Why STARTTLS is optional. A port's requirement applies to every address: on port 25 it would refuse every server that cannot start TLS, on the others this computer's own clients. The range's rule protects the password. The certificate. self-signed-certificate.pem and its key self-signed-key.pem are in <data directory>/Certificates: C:\Program Files\hMailServer\Data\Certificates on a default Windows installation, /var/lib/hmailserver/Certificates from the Linux packages, where the key is mode 0600 and owned by the service's user. It is RSA 2048, runs 825 days, names the host and gives the organisation hMailServer, self-signed at installation. It encrypts, but nothing vouches for it, so mail clients warn. Replace it with ACME (set AcmeEnabled, AcmeContactEmail and AcmeDomains; the first certificate goes to every listener that holds the self-signed one) or with your own, set on each port. See Let's Encrypt certificates are automatic. Where the defaults are applied The create step marks its database with the hm_settings row NewInstallDefaultsPending; the verb --apply-new-install-defaults requires the mark and removes it once it has decided. Platform When it runs What to know Windows The installer, after the database step of a new installation, with the service stopped A database DBSetup attached, rather than created, carries no mark: exit 5, logged, nothing changed Linux (.deb, .rpm) The unit's first start after sudo -u hmailserver hmailserver --create-database, which marks the database and leaves a one-line file, .new-installation, in the DataFolder The unit runs ExecStartPre=-/usr/bin/hmailserver --apply-new-install-defaults --if-marked: nothing unless that file is there, then one try at the database, given room by TimeoutStartSec=300 (the leading - forgives a failure, not a timeout). It ignores a mark made more than a day from its clock; a run by hand has no such limit A container (since 6.3.8) The image's entrypoint, before whatever command the container runs; the Compose file and the Helm chart run the same step before the server hmailserver --apply-new-install-defaults --if-marked secures a database the entrypoint's --create-database made a moment before and opens nothing at any other start; a failure (run as root, a database not reached) is said and forgiven, and the server starts all the same By hand A container from an image before 6.3.8, a server started by hand rather than by the unit, or defaults left for later With the service stopped; on Linux as the service's user, because the verb refuses to run as root By hand, on Windows and then on Linux: Stop-Service hMailServer & "C:\Program Files\hMailServer\Bin\hMailServer.exe" --apply-new-install-defaults Start-Service hMailServer sudo systemctl stop hmailserver sudo -u hmailserver hmailserver --apply-new-install-defaults sudo systemctl start hmailserver Exit codes: 0 applied (with --if-marked, also nothing to do); 1 something could not be made or written (HM7510, HM7511 in the error log); 2 no database named, or not readable enough to judge, or on Linux run as root; 4 the Windows service is running; 5 not a database this installation created, or not as the create script left it: nothing changed. On Linux, mind the data directory. A DataFolder outside the unit's ReadWritePaths (/var/lib/hmailserver and /var/log/hmailserver) is read-only to the service, so the first start can neither write the certificate nor store mail there. Add the directory with a drop-in (sudo systemctl edit hmailserver, then ReadWritePaths=/srv/mail under [Service]) before that first start. Not yet proved on a clean machine. SecureDefaults.md's checks that the installer applies the defaults are not yet written, and those for the .deb ran only on GitHub Actions, so nothing runs them today. The setup checklist The checklist judges every server, new or upgraded, in twelve items. Key Done when profile a setting profile has been applied; asked for on a server with fewer than ten accounts domain an active domain exists, and none has a name the server reserves for its own directories account at least one account exists submission every SMTP listener other than port 25 has STARTTLS or implicit TLS, and a certificate other than the self-signed one sign_in_tls every IP range that lets a client connect from another address requires TLS for authentication, or reaches only listeners that start TLS before a password spf, dkim, dmarc every active domain has a TXT record beginning v=spf1, signs with a key whose record is published at <selector>._domainkey.<domain>, and has a TXT record beginning v=DMARC1 at _dmarc.<domain> backup the destination is set and the folder exists second_factor the administrator password is enrolled with a one-time code stored_secrets_key this server holds the key the database records, and an export of it has been recorded service_account it is not LocalSystem on Windows, not root on Linux Each item is done, action_needed, cannot_tell or not_needed. Port 25 is never the submission port, and a plain submission port beside a secured one still needs action. Where it is. GET /api/v1/setup/checklist (reading needs view_configuration; ?dns=0 skips the lookups), the Deck's Checklist view and, on Windows, COM's Utilities.GetSetupChecklist(LookUpDns) and the Control Panel's External setup page and dashboard. Passwords only over TLS, on a server you upgraded "Require TLS for auth" is an option of an IP range, not of a port, and covers SMTP, POP3, IMAP and ManageSieve alike. Read the sign_in_tls item: it names each range, and the listeners where a password from there would cross the network in the clear. If the finding is sign_in_no_tls, a listener has no TLS to start, and requiring TLS would refuse every client on it: give those listeners a certificate and STARTTLS first. This finding carries no fix. Set every mail client that signs in from another machine to STARTTLS first. Once the range requires TLS, a client that signs in without TLS is refused; that is why an upgrade is never changed by itself. When the finding is sign_in_plain, apply its fix: the Deck's button Require TLS for sign-in from this range, or PUT /api/v1/ipranges/{id} with {"require_tls_for_auth": true}, which needs manage_settings. On Windows the Control Panel opens its IP ranges page, where the setting is Require TLS for auth. Replace the self-signed certificate if you have one, so that submission_self_signed clears. Secrets at rest and the stored-secrets key Until 6.3.4 the administrator password (unsalted MD5, from the Windows installer), each account's second factor and the [Settings] secrets were kept as written. The administrator password is now written as PBKDF2. An older MD5 or SHA-256 value is rewritten as PBKDF2 at the first successful administrator sign-in where the service can write the file. Where it cannot (NT SERVICE\hMailServer, or the Linux package's root-owned file) the old value stays, HM7494 says so once, and the verb rewrites it: on Windows hMailServer.exe --set-admin-password from an elevated prompt, with the password in HM_ADMIN_PASSWORD, or on one line of standard input from a pipe or a file for a password in plain ASCII (Windows PowerShell 5.1 turns every other character into ?), never as an argument; on Linux sudo hmailserver --set-admin-password, which reads standard input. Second factors and ten [Settings] secrets (SRSSecret, BATVSecret, OAuth2HmacSecret and the rest) are sealed with AES-256-GCM under the installation's stored-secrets key, in the form HMK1:<key id>:<base64>, once the installation has a key. The key is kept in each server's hMailServer.ini as [Security] StoredSecretsKey, and never in the database or a backup archive. Who gets a key without asking. The Windows installer makes one on a new installation. On Linux, hmailserver --create-database makes one when it creates the schema and can write the configuration: in a container, whose /etc/hmailserver the service owns, it does (no image of 6.3.4 is published); run by the package as the service's user, it cannot. An upgraded installation, and a package installation on Linux, seal nothing until an administrator makes the key: Make the key on one node. Windows, from an elevated prompt beside hMailServer.exe: .\hMailServer.exe --create-stored-secrets-key. Linux: sudo hmailserver --create-stored-secrets-key. Export it at once: .\hMailServer.exe --export-stored-secrets-key D:\Escrow\hmailserver-stored-secrets.key, or sudo hmailserver --export-stored-secrets-key FILE. The export writes a new file, never over one that exists, readable only by SYSTEM and the administrators on Windows and mode 0600 on Linux, and prints the key id, never the key. Restart the service on that node (Restart-Service hMailServer, or sudo systemctl restart hmailserver). Its next start seals what is stored as written; until then nothing is sealed. On every other node that shares the database, --import-stored-secrets-key FILE, then restart each. An import is refused while the node holds another key that opens: a standby or new node whose installer made a key of its own removes its [Security] StoredSecretsKey line first, and until then the shared database refuses it (HM7495). Keep the exported file with the database backups, stored apart from them. Why the export matters. The product's own backup archive carries these secrets opened, and a restore seals them under the restoring server's key, so set a backup passphrase. A database-level backup (a dump, a copy of the .sdf) restored without the key brings back every second factor and [Settings] secret unopenable. A copy of hMailServer.ini is no copy of the key on Windows, since its line is sealed under that machine's DPAPI; never copy that line from one machine to another. When a node lacks the key it says HM7495 at every start and makes no key of its own; until the key is imported, a [Settings] secret sealed under it runs as though unset (HM7492) and a second factor refuses every code (HM7493). A key lost for good is recovered from with --forget-sealed-secrets <key id>, with the service stopped (exit 4 otherwise), which clears only what that key sealed: every second factor is enrolled again, every [Settings] secret entered again. ProtectStoredSecretsWithDPAPI=0 (default 1) has a cost. The next start opens what was sealed and stores it as written, readable by anyone who can read the file or the database, and the machine-bound secrets are written with the legacy Blowfish scheme, whose key is in the source code: the way back, not a setting for a new server. Two things to do after an upgrade. Since 6.3.4 PasswordPepper stays in hMailServer.ini only and is left out of every backup, so keep a copy of that value with the backups. And a password hash stored before 6.3.4 from a password with a character above U+00FF is refused, to its own password too: an administrator sets such passwords again, and the application log names each account as its user tries to sign in. Checking new passwords against published breaches Off by default, because it sends something off the server. When on, a new password found in the published corpus of breached passwords is refused. Setting (REST key) Default What it means breach_check_enabled off The whole feature breach_check_url https://api.pwnedpasswords.com/range/ Where the five characters are sent breach_check_minimum_count 1 Refuse a password seen this many times or more breach_check_unreachable_action allow allow lets a change through unchecked; refuse refuses it What leaves the server is the first five characters of the password's SHA-1 and nothing else. The default endpoint is a third-party service, Have I Been Pwned's Pwned Passwords, and an organisation that cannot send it those five characters, its address and the time runs its own mirror. When it runs. When a password is chosen, by the person or by an administrator, and never at logon. An endpoint that gives no complete answer within eight seconds counts as unusable, and the error log records HM6600 (let through) or HM6601 (refused). Under refuse, an endpoint that is down refuses every password change on the server. Bulk imports make one request per account, so run them against a mirror or with the check off. Where it is set. The four settings are hm_settings rows, not hMailServer.ini lines, and a change applies from the next password chosen. They are on the Control Panel's Administrative access → Password page (the Passwords found in published breaches card), in the Deck's Settings view, over REST and in hmctl settings set: hmctl settings set --set breach_check_enabled=true Compromised-account detection This judges every authenticated submission (SMTP, the webmail's send, a scheduled draft) against the account's own history of the last 30 days, without reading the message. It starts in watch on every installation, new or upgraded: the baseline is built, the account.compromised alert says what enforcing would have done, and nothing else happens. Four signals score: volume (2 points), the last hour's messages reaching compromise_volume_floor (30) and exceeding compromise_volume_factor (4) times the busiest baseline hour; recipients (2), the last hour's distinct recipients reaching compromise_recipient_floor (20), at least 80 per cent of them new to the account; hour (1), an hour of the day (UTC) the account has not submitted at in the baseline; address (1), a client network (the /24 of an IPv4 address, the /64 of an IPv6 one) not seen for this account. compromise_score_to_lock (3) is a takeover. In enforce, the message is held, the account is locked until an administrator unlocks it, and the alert and an account.locked webhook are raised. The client is still answered 250 Queued. Held mail is reviewed on the Deck's Held for review page and under /api/v1/review; a held message nobody has looked at in compromise_held_days days (7) is discarded. Watch it before enforcing: the false positive is a person locked out of their own mail. There is no Control Panel page for it. Roles instead of the administrator password Rather than share the administrator password, give each administrator a role in the domains they look after, on the Deck's Roles view, over REST, or on Windows in the Control Panel or over COM. Nothing changes on the day you upgrade: an account with no assignment holds the authority its admin level has always given it, and a narrow role cannot take over a wider one, so a help desk is refused a domain administrator's password reset. Administrator roles end to end has the permissions, how to give a role, and the two stores only the administrator password opens. The rest of the list Auto-ban is on by default: 3 failures within 30 minutes, a 60-minute ban. MaxInvalidLogonAttempts at 0 disables it, and AutoBanMinutes at 0 disconnects the client but bans nothing. Account lockout (AccountLockoutThreshold) is off until set, and counts by the name being guessed at. IP ranges, auto-ban, lockout and the rest has both in full. The server-enforced administrator second factor refuses the password alone everywhere, the REST API included. On Windows, move a service still on LocalSystem with the installer, the Control Panel's button, or from an elevated Command Prompt in Bin: hMailServer.exe --confirm /GrantServiceAccess, then, only if that ended in 0, hMailServer.exe --service-account virtual /Register, then net stop hMailServer && net start hMailServer. Check first what the virtual account cannot do, listed in Installing on Windows. The event-script flaw fixed in 6.3.4 (CVE-2026-100741; its record was not yet public when this was written) is in every release from 6.0.0 to 6.3.3: upgrade, and until you can, switch event scripting off. See Event scripts and the 6.3.4 security fix and the 6.3.4 security announcement. What it does not do Passkeys, single sign-on and the session policies do not reach IMAP, POP3 or SMTP, which still take the password or an app password. The breach check does not cache, and does not expire a password that enters the corpus later. Compromised-account detection does not drop a session that is already up. The stored-secrets key protects against a copy of the database that leaves without the file, not against a local account on the server. Where to read more Wiki: Security Hardening. Documents: SecureDefaults.md, SetupChecklist.md, SecretsAtRest.md, PasswordBreachCheck.md, CompromisedAccounts.md and Roles.md. The public documentation: chapter 17, Security hardening. On this forum: Upgrading to 6.3.4, The Control Deck and hMailServer on Linux. A suspected vulnerability is reported privately, never in a forum post: by a confidential GitLab issue, or by email to the project's Service Desk address, contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as a confidential issue (SECURITY.md).
  • 0 Votes
    1 Posts
    122 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) is a security release that bears on the send-as and sign-in sections here. An authenticated submission is now DKIM-signed as another domain hosted here only when its account may send as the address its From names (its own address, an alias pointing at it, a mailbox that granted it the post right, an address it proved, or a masked address of its own, the addresses the send-as table below lists); otherwise the mail is still accepted and sent, unsigned as that domain, so give such an account the alias or the grant before upgrading. A domain's or an account's rule replies only from an address its owner may send as. SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account; with SRS on, mail to an SRS address this server handed out is held to the relay rules unless it is a bounce; the IMAP and POP3 listeners can take the client's address from a PROXY protocol header as the SMTP listener does, each from a list of peers of its own, both off by default; and the Control Panel's IP ranges page, which could show no list of ranges at all, is fixed. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) is a security release that bears on the auto-ban, the lockout and the IP ranges here. The auto-ban saves the IP range it adds under a name of its own, the user name cut so that the name and its suffix fit in the 100 characters a range's name holds, so that it bans the address: up to 6.3.7 a second or later ban for a long user name could be refused (HM6099) and the address given a whole new set of attempts, and now an address's counted failures are cleared only once its range is saved. A sign-in's name is matched to its account as the per-name lockout counts it, so a mail client that signs in with another spelling of the account's address than the stored one, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in, and each such try is counted as a failure: correct the name in the client. 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, is now refused there too, before the TLS handshake, loopback and the addresses in WebServicesTrustedProxies apart; such a range of your own closes to its addresses ACME's http-01 validation when this listener answers it, MTA-STS, CalDAV, CardDAV, Autodiscover, autoconfiguration and the Web Key Directory, so before upgrading check that the certificate authority, the sending MTAs and these services' clients do not come from one. See the 6.3.8 announcement. This topic covers who may use this server and how hard it is to abuse it. Know this first: only the IP ranges and auto-ban work out of the box; every other brake here is off until you set it. Until you do, nothing else stops a botnet spending one guess per address, or a stolen password sending thousands of messages. The secure defaults of a new installation, TLS for sign-in and compromised-account detection are in Hardening a public mail server. Where each one is set Since 6.3.4 the database is the settings store, and for a key it holds, a [Settings] line edited in hMailServer.ini changes nothing; on 6.3.3 these keys were lines of the file, applied at the next start. The store, and when a save takes effect, are in the settings topic. What Kept in Set with Takes effect IP ranges the database Control Panel Access & abuse protection → IP ranges; the Control Deck's IP ranges view; /api/v1/ipranges; hmctl iprange; COM Settings.SecurityRanges at once Auto-ban's four settings the database Control Panel Access & abuse protection → Auto-ban; the Deck's settings; PUT /api/v1/settings; COM at once Lockout, tarpits, rate limit, send-as check, hashing, policy the settings store, under their [Settings] names Control Panel; PUT /api/v1/settings/ini/{name}; hmctl ini set <name> <value>; COM Settings.SetIniSetting; on Linux hmailserver --set-setting NAME=VALUE when saved [SendingLimits], [SendingLimitsOverrides], PasswordPepper hMailServer.ini only the file, by hand or through the Control Panel (below) sending limits within seconds; the pepper after a restart The Control Panel and COM are Windows-only. Of these [Settings] keys the Deck edits only SmtpTarpitCount and SmtpTarpitDelaySeconds. A value set with hmailserver --set-setting while the service runs waits for the next save, reinitialise or restart. IP ranges: priority and the relay grid When a client connects, every range containing its address is found and the highest priority wins. The shipped ranges are Internet (priority 10, 0.0.0.0–255.255.255.255) and My computer (priority 30, 127.0.0.1); a range added on the Control Panel's page starts at 15, so a narrow range sits in front of Internet without editing it. At every RCPT TO two decisions are made, in order: may this combination be delivered at all (if not, 550 Delivery is not allowed to this address.), and must the client authenticate first (if so, 530 SMTP authentication is required.). "Local" and "remote" describe the addresses, not the client: an address is local when its domain is a hosted active domain or a route says so. Combination REST fields: allowed / auth required Internet My computer Local → local deliver_local_to_local / require_auth_local_to_local allowed, auth required allowed, no auth Local → remote deliver_local_to_remote / require_auth_local_to_remote allowed, auth required allowed, no auth Remote → local deliver_remote_to_local / require_auth_remote_to_local allowed, no auth allowed, no auth Remote → remote (relay) deliver_remote_to_remote / require_auth_remote_to_remote allowed, auth required allowed, auth required Anti-spam, anti-virus spam_protection, virus_protection on on Require TLS for auth require_tls_for_auth on in a new 6.3.4 installation; otherwise as the server had it off Remote → remote is the open-relay box. Both shipped ranges allow it with authentication required, even from 127.0.0.1, so a client that has not signed in gets 530; a range that does not allow it at all gets 550. The Control Panel's Server status page raises a Critical warning for any range that allows it without authentication. Remote → local is inbound mail and must need no authentication. Local → local needs it on Internet so that nobody outside can forge mail from one of your addresses to another. The protection switches reach further than their names. Clearing Enable anti-spam turns off every spam test, greylisting and the recipient tarpit for the range; clearing Enable anti-virus means its mail is never flagged for scanning. Since 6.3.4 SPF gives loopback no pass, so a content filter that re-injects mail should authenticate or use a range with anti-spam off. Require TLS for auth belongs to the range, not the port. With it set, AUTH is not advertised on a cleartext connection from that range, on SMTP, POP3, IMAP and ManageSieve. A device that cannot sign in gets a range of its own above Internet. For a scanner at 192.0.2.50 that sends from your domains to your own mailboxes only: hmctl iprange create --set name=scanner --set lower=192.0.2.50 --set upper=192.0.2.50 \ --set priority=20 --set allow_smtp=true --set allow_imap=false --set allow_pop3=false \ --set deliver_local_to_local=true --set require_auth_local_to_local=false \ --set deliver_local_to_remote=false --set deliver_remote_to_local=false \ --set deliver_remote_to_remote=false --set spam_protection=true --set virus_protection=true hmctl iprange update <id> and PUT /api/v1/ipranges/{id} (since 6.3.3) change any subset of the fields. The routes refuse a domain-restricted API key; a role needs manage_settings. The Control Panel's Restore defaults (SecurityRanges.SetDefault()) discards every range, your own and the auto-ban ones, and writes back the two shipped ones as the create scripts make them, without Require TLS for auth. Auto-ban, and why its ranges sit at priority 100 Auto-ban counts failed logons per connecting address; SMTP AUTH, POP3, IMAP, ManageSieve, the REST API and the web services listener's CardDAV, CalDAV and Autodiscover sign-ins feed one counter, but since 6.3.8 neither REST, for a failure from loopback (all of 127.0.0.0/8, and ::1), nor the web services listener, for one from loopback or an address in WebServicesTrustedProxies, counts it towards a ban of the address: an account's failed sign-in from there is counted against the name alone. Up to 6.3.7 an account's failed sign-in at REST from loopback was counted towards a ban of 127.0.0.1, which could then be refused at the SMTP, POP3, IMAP and ManageSieve listeners; on 6.3.2 to 6.3.7 AutoBanNeverBan with 127.0.0.1, ::1 avoids it. Behind a reverse proxy on the same machine (the Linux package binds REST to 127.0.0.1), the webmail's failed sign-ins are never auto-banned; account lockout, below, is what limits them there, and it is off until you set it. At the limit auto-ban clears that address's counted failures, creates an expiring range named Auto-ban: <user> at priority 100, and disconnects; SMTP says 535 Authentication failed. Too many invalid logon attempts. Priority 100, above both shipped ranges, is what makes a ban effective; AutoBanNeverBan, below, exempts an address. Setting (COM name) Default Trap AutoBanOnLogonFailure on — MaxInvalidLogonAttempts 3 0 disables auto-ban entirely, whatever the switch says MaxInvalidLogonAttemptsWithin 30 minutes Not a sliding window: how long a failure record survives, deleted by a pass every minute that runs only while auto-ban is on AutoBanMinutes 60 0 disconnects the client but creates no range, so nothing is banned Their REST keys include auto_ban_on_logon_failure and max_invalid_logon_attempts. The office profile (since 6.3.4) raises the limit to 5. AutoBanNeverBan (since 6.3.2): addresses and CIDR blocks whose failures are never counted; the per-connection disconnect still applies, and a bad entry is reported once as HM6524. Server status warns "Localhost is currently banned in the IP ranges." when a ban covers 127.0.0.1. Control Panel sign-in over COM is outside auto-ban and lockout, so guessing an administrator's mailbox name cannot lock them out of the tool. Spam traps and the multi-recipient null-sender check ban through the same ranges (Auto-ban: spam trap <address>, Auto-ban: multi-recipient null sender <address> see Allow and block lists and the anti-spam stack. Firewall bans (AutoBanFirewall, AutoBanCommand, fail2ban) are in Reading the logs. Releasing a banned address. Clear logon-failure list (POST /api/v1/settings/logon-failures/clear, COM Settings.ClearLogonFailureList()) clears counted failures only, for every address at once; a ban stays until its range expires. To release one now: Have the person correct the password in their client first: the counted failures were cleared when the ban was made, so a client still retrying the old one reaches the limit again as soon as the range is gone. Find the range: the IP ranges page, the Deck's IP ranges view, hmctl iprange list or GET /api/v1/ipranges. Its name begins Auto-ban: and its priority is 100. Delete it: on the page, hmctl iprange delete <id>, or DELETE /api/v1/ipranges/<id>. With AutoBanFirewall on, the firewall is reconciled on every range deletion, so its rule goes too. Five error-log codes mean the control has quietly stopped working: Code Meaning HM6114 A failed logon could not be recorded, so no ban will trigger while this persists HM6099 The auto-ban range could not be saved: the address has not been blocked HM6113 Spent failures could not be cleared, so the address may be banned again as soon as its ban expires HM6112 Expired IP ranges could not be removed, so an expired ban keeps blocking HM6115 Expired failures could not be cleared, so occasional failures may add up to a ban Account lockout, by name A distributed attack never crosses any one address's threshold. Lockout counts the name being guessed at, across SMTP AUTH, POP3, IMAP, ManageSieve, REST and the webmail's sign-in. It is off until set; the office profile sets 10. AccountLockoutThreshold=10 ; failed logons per username before a lockout. 0 = off AccountLockoutWindowMinutes=30 ; the window they must fall inside AccountLockoutMinutes=30 ; how long the lock lasts It counts names, not accounts, so a nonexistent name locks as a real one would, and reveals nothing. A locked name is refused before the password is checked, with the ordinary invalid-credentials reply; only its timing differs, deliberately. A lockout refusal is not charged to the connecting address, so an owner retrying from an office NAT does not get everyone behind it auto-banned. Passkeys and single sign-on are not counted, being no guesses. To lift one early: POST /api/v1/accounts/{address}/unlock, which a help desk scoped to the account's domain may call, COM Application.UnlockAccount, or Unlock on the Deck's Accounts row. A password reset clears it too. Tarpits and the submission rate limit All 0, off, until set; these values are a starting point. LogonTarpitSeconds=2 ; per failure on this connection, capped at 30 s. 0 = off SmtpTarpitCount=10 ; recipients an unauthenticated session may name before... SmtpTarpitDelaySeconds=5 ; ...each further RCPT TO waits this long, capped at 30 s MaxSubmissionsPerIPPerMinute=30 ; MAIL FROM commands per source IP per minute. 0 = off The logon tarpit (Auto-ban page) makes a failed logon's refusal wait LogonTarpitSeconds times the failures so far on that connection, on SMTP, POP3 and IMAP. A correct password never waits. The recipient tarpit (Anti-spam settings page; COM AntiSpam.TarpitCount and AntiSpam.TarpitDelay; REST tarpit_count and tarpit_delay on /api/v1/settings/antispam) spares authenticated sessions and ranges with anti-spam off. MaxSubmissionsPerIPPerMinute counts every MAIL FROM, authenticated or not, and refuses the excess with 421 Too many messages from your IP address, which contains a compromised account. The office profile sets 60; gateway keeps 0, since all inbound mail comes from the gateway's few addresses. Since 6.3.4 a save restarts its one-minute window. Behind a load balancer, give the SMTP listener SMTPProxyProtocolEnabled with SMTPProxyProtocolTrustedIPs (or XCLIENT), or auto-ban, the rate limit and the ranges all reason about the proxy's address: see Behind a gateway or reverse proxy. Per-account sending limits The brake on a compromised account, counted against the authenticated user name (never MAIL FROM) over a rolling period. The server level stays in the file: [SendingLimits] MaxMessagesPerAccountPerPeriod=500 MaxRecipientsPerAccountPerPeriod=2000 PeriodHours=24 ; clamped to 1-168 StateSaveIntervalSeconds=10 ; clamped to 1-3600 [SendingLimitsOverrides] [email protected]=5000:50000 [email protected]=0:0 Both limits default to 0, no limit; recipients are the stricter measure. An override replaces the global pair for that address, 0:0 exempts it, and an optional third field sets its period in hours; a malformed line is ignored with a log entry and the global limit applies. On Windows edit C:\Program Files\hMailServer\Bin\hMailServer.INI with an elevated editor, or use the Control Panel's Maintenance → Server limits & expert settings on the server itself; on Linux, sudoedit /etc/hmailserver/hMailServer.ini. They are not in the settings store, so no environment variable or setting profile writes them. What the sender sees: 452 4.7.0 Sending limit for this account has been reached. Please try again later. at MAIL FROM, or 452 4.5.3 Recipient sending limit for this account has been reached. Please try again later. at RCPT TO. The session stays open, the refusal spends no budget, and the application log, not the error log, says SendingLimit refused submission. Counters survive a restart in hmailserver_sendinglimits.dat in the data directory. It fails open: an unreadable file, a limiter exception or a full table of tracked accounts (error 5813) lets the message through. Since 6.3.4 there are three levels. A domain and an account may hold sending_max_messages and sending_max_recipients, which tighten the level above and never loosen it; an address's override line is its server level. Set them on the Levels tab of the domain and account dialogs, in the Deck's editors, with PUT /api/v1/accounts/{address}, or hmctl account set <address> sending_max_messages 100 (inherit returns to the level above); they apply at once. Also since 6.3.4 the webmail's send and the send API consume the same quota, answering 429; a scheduled draft is not counted when it goes. Who may send as whom SmtpAuthenticatedSenderCheck (Control Panel Connections & protocols → Protocols, SMTP tab) came in 6.2.25 and is off by default, since an installation may depend on what off allows: any account sending as anyone. The office and gateway profiles set it to 1. Turned on, it applies to every authenticated session, whose MAIL FROM must then be: Kind Given by The account's own address always An alias resolving to it, up to five hops the administrator A mailbox whose owner granted the post (p) right on their INBOX that owner, with any IMAP client's SETACL An address elsewhere proved by a code mailed to it (since 6.3.4) the person, in the webmail's Settings, Other accounts, Send mail as A masked address the account made, while it is on (since 6.3.4) the person, in the webmail's Settings, Masked addresses Anything else is refused 550 5.7.1 before the message is accepted. The webmail's From picker lists exactly these, so what it offers is what the server takes. The null sender is always allowed; unauthenticated sessions are unaffected. The post right counts only while IMAP ACL enforcement is on (enableimapacl, on by default). Before turning the check on, grant p to every delegate who sends as a shared mailbox. Mail sent as a proved outside address is not aligned with that address's domain, so a receiver honouring its DMARC policy may quarantine or refuse it. How passwords are stored In the Control Panel, Access & abuse protection → Authentication, card Password storage. Schemes: 0 none, 1 Blowfish, 2 MD5, 3 SHA-256, 4 PBKDF2-HMAC-SHA256 (the default), 5 Argon2id, 6 DPAPI (stored secrets only), 7 scrypt. PreferredHashAlgorithm=4 ; 3, 4, 5 or 7. Anything else is refused (HM5528) and read as 4 MinimumAcceptedHashAlgorithm=0 ; refuse a logon whose stored hash is weaker. 0 = off PasswordPepper= ; a server-wide secret mixed into Argon2id and scrypt hashes PasswordHashIterations=0 ; PBKDF2. 0 = 210,000. Range 10,000 to 10,000,000 PasswordHashMemoryKB=0 ; Argon2id. 0 = 19,456 KiB. Range 4,096 to 1,048,576 PasswordHashTimeCost=0 ; Argon2id passes. 0 = 2. Range 1 to 20 Hashes upgrade at a successful logon, only upward. Raising the scheme or a work factor re-derives each hash when its owner next signs in; lowering either affects new hashes only. MinimumAcceptedHashAlgorithm set too high is a lockout. The refusal follows a correct comparison and the upgrade needs a successful logon, so an account below the floor never climbs out: its password must be reset. Raise the preference first, let accounts sign in, then set the floor. Argon2id and scrypt are peers, so 5 accepts either. A work factor out of range is reported as HM5561 and read as 0; HM5607 means Argon2id was asked for on a build whose OpenSSL has none, and another scheme is used. Argon2id allocates PasswordHashMemoryKB on a connection thread at every logon. PasswordPepper must be set before accounts exist: changing it invalidates every Argon2id and scrypt password. PBKDF2 is not peppered, so it stays usable for SCRAM. Since 6.3.4 it lives in hMailServer.ini only and no backup carries it, so keep a copy of the value with the backups: a database or archive restored onto a machine whose file has no pepper, or a different one, refuses every Argon2id and scrypt password, and a warm standby needs the same value in its own file. Where the service cannot write the file (a new Windows installation running as NT SERVICE\hMailServer, or a Linux package installation), COM and REST refuse it: set it with an elevated editor (sudoedit on Linux) and restart the service. The password policy All off until set, applied where a password is chosen, never at logon; on Windows under Access & abuse protection → Administrative access, Password tab. PasswordPolicyMinimumLength=12 PasswordPolicyRequireMixedCase=1 PasswordPolicyRequireDigit=1 PasswordPolicyRequireNonAlphanumeric=1 PasswordPolicyRejectCommon=1 ; refuse passwords from a built-in common list PasswordPolicyHistoryCount=5 ; refuse the last N passwords PasswordPolicyMaximumAgeDays=0 ; 0 = passwords never expire The office profile sets a minimum of 10 and the common list. Expiry needs care: IMAP, POP3 and SMTP cannot change a password and /portal refuses an expired one, so an administrator renews it, or its owner through an app password, which still signs in to /portal. Active Directory accounts are exempt. The breach check is in Hardening a public mail server; the rest of sign-in is in Signing in. What it does not do None of it spam-tests your own users: an authenticated session never is. A setting profile never writes an IP range. Where to read more Wiki: Security Hardening sections 17.1, 17.2, 17.5, 17.7 and 17.9; Settings Reference sections 9.1, 9.9 and 10; Stopping Spam section 10.1; Troubleshooting sections 19.3 and 19.7. Documents: SettingsApply.md, SettingLevels.md, SendAs.md, Profiles.md, Roles.md, ServiceAccount.md. Public documentation: SecurityRange and SecurityRanges, for the COM names of the range flags. On this forum: Being trusted by the servers you send to, The Control Deck, Upgrading to 6.3.4, hMailServer on Linux.
  • 0 Votes
    1 Posts
    121 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes three things this guide touches. On MySQL and MariaDB a lost database connection is reconnected and the statement run again, so the statement after a database restart, a failover or wait_timeout no longer fails, and an insert that forgives a duplicate key forgives that alone, so an error log that was quiet may now name failures that were hidden. On Windows a stop or a reinitialise no longer waits past a work queue's ten seconds for a thread held by a host or a database that has stopped answering, and setup, the live update's silent install and the uninstaller wait ShutdownDrainSeconds plus 90 seconds for the stop, where they waited plus 30. The ACME HTTP-01 challenge responder, which served one connection at a time, serves up to 64 at once. See the 6.3.7 announcement. This topic describes a running hMailServer 6.3.4 service: its parts and pools, the connection ceilings in front of them, the scheduled work behind them, what the machine needs, and what a folder of a hundred thousand messages costs the webmail. The one thing to know first: nothing in the server is unbounded. There is no thread per connection and no pool that grows. Every piece of work runs on a pool of a set size, and a dependency that stops answering uses up a pool long before it uses up the processor, the memory or the disk. What it cannot give you is a throughput figure: none has been measured yet. The parts of the running service Everything runs in one process: the Windows service hMailServer, or the systemd unit hmailserver on Linux. Part What it is Notes Mail listeners SMTP, POP3 and IMAP on IOService + TCPServer Boost.Asio HttpServer The engine under two listeners: REST (/api/v1, the webmail at /portal, the Control Deck) and web services (MTA-STS policy, autoconfig, ACME http-01) Its own io_context, so a request storm cannot starve SMTP accept. Since 6.2.28 Metrics listener /metrics and the health probes Raw sockets, one worker thread ManageSieve Sieve script management Raw sockets, a thread of its own SNMP agent Read-only, UDP 161 Since 6.3.4; off, and answering nobody until SnmpAllowedAddresses names a manager Background Scheduler, SMTPDeliveryManager, ExternalFetchManager Long-lived tasks on the main server queue State The database (accounts, domains, message metadata and settings, since 6.3.4 including the [Settings] keys), one file per message, and hMailServer.ini as the bootstrap The database stays small next to the store The handover that matters for capacity: an SMTP session never delivers anything. The final 250 is sent from a worker on the asynchronous task queue once the message is written, and delivery happens later on a delivery thread, so the accepting side and the delivering side run out separately. Diagnosing stalled mail takes each half in turn; How email works follows one message through both. The pools, their sizes, and what a full one looks like Seven work queues are created at startup, and the two HTTP listeners have workers of their own. The first column is the queue label of hmailserver_workqueue_depth{queue} on the metrics endpoint, which counts tasks accepted and not yet started; the last row is not a work queue and has no label. Queue Threads Sized by What runs on it When it is full IOCPQueue 15 tcpipthreads Every socket read, write and TLS handshake for SMTP, POP3 and IMAP New connections are accepted but nothing progresses; clients time out mid-command Asynchronous task queue 15 MaxNumberOfAsynchronousTasks Message finalization, which sends the final 250 Senders sit after CRLF.CRLF with no reply Name lookup queue 15 the same setting Blocking reverse-DNS lookups for live sessions Reverse names become "Unknown", so a dead reverse zone cannot stall acknowledgements SMTP delivery queue 10 maxdelivertythreads The delivery of one queued message The queue grows and mail is late; nothing is lost, since the sender already has its 250 External fetch queue 15 MaxNumberOfExternalFetchThreads One run per fetch account Fetch accounts fall behind their schedule Maintenance queue 5, fixed nothing One-shot scheduled tasks; a backup started over REST A slow sweep delays the other one-shot sweeps Server queue 4, fixed nothing Scheduler, IOService, SMTPDeliveryManager, ExternalFetchManager Cannot fill in normal operation: each task holds one slot for the life of the server HttpServer workers 4 per listener, fixed nothing One REST or web-services request each Requests queue behind the four; SMTP is unaffected Where the four sizes are set. On Windows, the Control Panel's Maintenance → Performance page, Threads tab, holds the COM Settings properties TCPIPThreads, MaxAsynchronousThreads and MaxDeliveryThreads, and the [Settings] key MaxNumberOfExternalFetchThreads. On either platform the first three are in the server settings group: the Control Deck's Settings view, PUT /api/v1/settings with tcpip_threads, max_asynchronous_threads and max_delivery_threads, or hmctl settings set. The fourth goes through /api/v1/settings/ini/{name} (administrator password only), hmctl ini set, or on Linux hmailserver --set-setting as root. Since 6.3.4 the database is the settings store, so do not edit the [Settings] line in hMailServer.ini: a line edited there is ignored in favour of the stored value. Settings: when a save takes effect covers the store, what becomes of the file's line (HM5804) and when each key applies. The tab also shows WorkerThreadPriority, read-only: the server stores that value and never reads it. When a new size takes effect. Since 6.3.4 a save applies most settings at once, but MaxNumberOfExternalFetchThreads waits for a reinitialise, because the queue's size is set when the fetch manager is built; GET /api/v1/server/settings/pending lists it until then. For the other three the documents leave a gap: the Control Panel reports a save of its COM settings as "applied immediately", while the Architecture page has the pools created when the servers start, and no document says when a changed count reaches a running pool. Until one does, treat all four alike: after saving, reinitialise (the Control Panel's Reinitialize or Apply now, POST /api/v1/server/reinitialize, or hmctl api POST /api/v1/server/reinitialize) or restart the service. A reinitialise stops and starts every service in place, ends the Control Deck's session, and ends a drain. # The REST listener on this machine: read the server settings group, # then give delivery 25 threads (or: hmctl settings set --set max_delivery_threads=25) curl -u Administrator:pw http://127.0.0.1:8443/api/v1/settings curl -u Administrator:pw -X PUT -H "Content-Type: application/json" \ -d '{"max_delivery_threads":25}' http://127.0.0.1:8443/api/v1/settings # The fetch pool is a [Settings] key; it waits for a reinitialise hmctl ini set MaxNumberOfExternalFetchThreads 20 hmctl api POST /api/v1/server/reinitialize Two asynchronous threads are kept back. Finalization runs the spam tests and event scripts, which wait on things outside the process, so AsyncQueueReservedThreads (2) keeps that many threads free of it: with the defaults at most 13 finalizations run at once. A task waiting for a slot holds no thread and is counted by hmailserver_workqueue_blocking_tasks_waiting{queue}; a count that stays above zero means something those tasks depend on is slow. On Windows the key is on Server limits & expert settings, card Timeouts and queue bounds; anywhere, hmctl ini set. Only two queues report a stall: the asynchronous and name lookup queues write the "every thread busy" report that Diagnosing stalled mail reads. The others are left out deliberately, since their tasks are long-lived by design, so a full IOCPQueue, SMTP delivery queue or External fetch queue shows only in hmailserver_workqueue_depth{queue} and in its symptoms. The database connections every pool shares The deliveries, the sign-ins and the rest of the server share one pool of database connections, NumberOfConnections in the [Database] section of hMailServer.ini, 5 as shipped. A wait for one longer than DBConnectionAcquireTimeout (60 seconds) fails. In hmailserver_db_connections{state}, available pinned at zero with busy at the pool size is saturation, and every mail path is then waiting out that timeout. Raise it with the delivery threads. The relay profile sets maxdelivertythreads to 25 and the sender profile to 50, and each tells you to raise NumberOfConnections to about the delivery thread count. The built-in database has one connection. On Windows, NumberOfConnections is forced to 1 for MSSQLCE. Before You Install calls the built-in engine adequate for anything up to a few dozen mailboxes, and its single connection the first thing to change if the server feels slow under concurrency. Linux has no built-in database; it uses PostgreSQL or MariaDB. Choosing a database covers the choice. It is a bootstrap key. [Database] stays in the file, never in the database, and its keys wait for a restart. On Windows the Performance page's Database tab edits it, on the server machine only. On Linux: sudoedit /etc/hmailserver/hMailServer.ini # [Database] NumberOfConnections=25 sudo systemctl restart hmailserver Connections and requests: the ceilings in front of the pools Where Limit As shipped Set on SMTP sessions MaxSMTPConnections 0, no cap Connections & protocols → Protocols, SMTP tab; max_smtp_connections IMAP sessions MaxIMAPConnections 0, no cap The IMAP tab; max_imap_connections POP3 sessions MaxPOP3Connections not given in the documents; GET /api/v1/settings shows it The POP3 tab; max_pop3_connections Recipients in one message 50,000 50,000 No setting is named; beyond it, 550 Too many recipients REST listener, with the webmail and the Deck 64 connections, closed at accept before any TLS handshake; 4 workers; 64 KB a request (16 MB, with 300 seconds, on the two attachment routes); 30 seconds a request; 300 seconds a connection fixed no key Web-services listener 128 connections; 4 workers; 64 KB a request; 15 seconds a request; 120 seconds a connection fixed no key The webmail shares the REST listener: its users, the Control Deck and every script that calls the API meet the same 64 connections and four workers. Its request rate is in the topic on the REST API. Outbound, since 6.3.4, MaxOutboundConnsPerDestination and MaxOutboundConnsPerTenant (both 0, off) cap sessions per destination and per sending domain; a tenant share larger than the delivery thread count is no share at all (Outbound shaping and IP warm-up). Scheduled work The Scheduler polls its recurring tasks once a minute; one-shot tasks go through the Maintenance queue at once. External fetch is not one of them: the ExternalFetchManager runs its own one-minute loop. Roadmap.md recorded in August 2026 that a due task runs on the scheduler's own thread, so one long task delays the others, and that the real period is the interval plus the run time. The tasks, grouped by cadence: Task Cadence Registered GreyListCleanerTask every GreylistingRecordExpirationInterval minutes (240 as shipped) when SMTP is on RemoveExpiredRecords, WorkQueueHealthTask every minute always; the second writes the stall report TlsRptReporterTask, DmarcRptReporterTask hourly always; each sends only when its from-address is set LogRetentionTask, MailboxRetentionTask start + 6 hours always ArchiveRetentionTask, IMAPExpungeRetentionTask start + 12 hours always MetricsHistoryTask start + every minute always UpdateCheckTask start + every 15 minutes always; does nothing until UpdateCheckEnabled=1 MessageStoreConsistencyTask, DiskSpaceMonitorTask start + hourly always BackupScheduleTask a one-minute tick against the wall clock only when a schedule is set DirectorySyncScheduleTask start + every [LDAP] SyncScheduleMinutes only when that is set AcmeRenewalTask start + hourly only when AcmeEnabled=1 Sizing the machine Memory. Before You Install asks for 2 GB as a minimum on Windows, more for a busy server. It is guidance; nothing enforces it, and memory per session has not been measured. Disk. Size it for how much mail you will keep, not how much you have today. The store is Data\ under the program folder on Windows and /var/lib/hmailserver on Linux, one file per message. For scale, the webmail bench's hundred thousand messages, 5 % of them with an attachment of 20 to 512 KB, are 1,323,987,827 bytes. Below MinimumFreeDiskSpaceMB (100) new mail is refused temporarily, 452 4.3.1 over SMTP and NO [UNAVAILABLE] to an IMAP APPEND, so senders retry; below DiskSpaceWarningThresholdMB (1024) the server logs a warning and keeps accepting. Antivirus. On Windows, exclude Data\, Database\ and Temp\ from real-time scanning. A scanner holding a message file open is a documented cause of delivery that silently does nothing; the server detects one shape of it and logs error 5760. Before You Install names no exclusions for Linux, where the store is /var/lib/hmailserver with its temp and database subdirectories. Durability. MessageStoreFsync (0 as shipped) flushes the spool file to disk before the 250, and a failed flush refuses the message. The published-numbers method runs with it on and labels the shipped default "not durable". Set it on Windows on Server limits & expert settings (card Message store durability), or anywhere with hmctl ini set. A folder of a hundred thousand messages in the webmail Every webmail path reads the folder's shared message collection, the one IMAP, POP3 and delivery use: one small object per message. Until 6.3.4 opening a folder copied the whole of it, under the lock IMAP sessions and delivery wait on, for every page of 200. Since 6.3.4: Operation What it does now What still grows with the folder Open, GET /api/v1/me/folders/{id}/messages One pass under the lock counts the folder and copies only the page of 200; each row's file head is read twice, not three times That pass; and the first open after a start, or after thirty idle minutes in the cache, reads every row of the folder from the database, as an IMAP SELECT does Scroll Older and Newer by before_uid; from three pages up, Oldest in one request (oldest_before_uid) Every page costs what the first costs, wherever it is in the folder: the one pass that counts the folder Search At most 2,000 message files and about two and a half seconds a request, then answers where to go on from; across every folder it walks the mailbox newest first and answers next_before_id; with IndexerFullText and message indexing on (off as shipped), a message the index rules out is passed without opening its file Performance.md does not say Select-all and one action The selection goes to POST /api/v1/me/folders/{id}/bulk: at most 1,000 messages a call for a flag, 500 for a move or a delete, nothing new taken on after about two and a half seconds A changed flag is a row written, a move is a file copied, and a selection narrowed to an inbox tab reads the head of every message Junk over a whole folder is a hundred thousand lessons. With SpamAssassin on, each message moved into or out of Junk queues one lesson, as the same move over IMAP does. In the Settings Reference the lesson on a move is SpamAssassinLearnOnMove (off as shipped), run as a task on the asynchronous work queue. A move holds the mail twice, briefly: original and copy, both counted against the quota, for no longer than one slice of the work. Not changed. The Storage, Subscriptions and Scheduled views still copy each folder they look at; emptying Junk or Trash copies the folder once per 500 messages removed; a quick step over a whole-folder selection still reads at most five thousand messages. For other callers of GET /api/v1/me/search, it now answers complete: false, more: true with next_before_id whenever it is not the end, and its hits come newest first across every folder, where they came folder by folder. Over IMAP, one SEARCH or SORT is bounded by IMAPSearchTimeout (60 seconds) and IMAPSearchMaxMegabytes (2048 MB examined). The webmail portal at /portal covers the rest of the client. What has not been measured The webmail at a hundred thousand. Performance.md has the method and a results table for the Windows bench (SQL Server Compact, 100,000 messages in the inbox, the full-text index off and on). Every cell reads to be measured on the bench; nothing was estimated. Its tools, build/perf/seed-folder.py and build/perf/webmail-timing.py, are in the repository and also run against a Linux server. Record the machine, database, message store, index state and build with any numbers. How much the server carries. PublishedNumbers.md defines messages in and out per second, latency percentiles, IMAP sessions per core with every operation's 95th percentile under 200 ms, memory per session and Dovecot's imaptest, measured against Postfix and Dovecot on the same machine under ten fairness rules. Its results say Not measured yet, and until they are filled the first line of Roadmap2.md is a goal, not a claim. The load generator, in the repository since 6.3.4, is build/perf/load/loadgen.py; a run in which the generator was busy for more than 85 per cent of it gives only a floor and is not published. The Windows bench gives hMailServer's own numbers only, not to be compared with the Linux table. python build/perf/load/loadgen.py --help python build/perf/load/loadgen.py self-test # proves the generator, says nothing about any server Size from the ceilings above, watch the queue depths and the database pool, and treat any throughput figure quoted elsewhere as unmeasured by the project. What it does not do No document gives a rule for sizing the pools to a machine; the only sized examples are the relay and sender profiles' 25 and 50 delivery threads. The main server queue, the maintenance queue and the HTTP listeners' workers, connection caps and deadlines are fixed; no key changes them. Where to read more Wiki: Architecture, Before You Install, Control Panel Reference section 7.8, Settings Reference, The REST API section 8, APIs Reference section 1 and Monitoring and Health. Documents: Performance.md, PublishedNumbers.md, SettingsApply.md, Profiles.md and OutboundShaping.md. The public documentation: Before you install, A database and Prometheus metrics and health probes. On this forum: Diagnosing stalled mail, Reading the logs, Monitoring and health, The webmail in depth, The Control Deck and hMailServer on Linux. Without a GitLab account, a bug or a question can go to the Service Desk address contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as a confidential issue; the replies arrive by email.
  • 0 Votes
    1 Posts
    156 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) is a security release that closes gaps in what this guide describes. Over COM an account given serveradmin with a list of domains was the server's administrator, its list aside, and one given domainadmin with a list that left out its own domain administered that domain's ordinary accounts; both are now confined as over REST, so before upgrading give serveradmin with no list to any account that is to administer the whole server over COM, a script's or a Control Panel's account among them. A credential confined to named domains is refused a domain's rename, a domain alias, a custom role's creation, change or deletion, a rule action that reaches the server's own addresses, a route's stored credentials or the event script, a relay host that is not public, and the server-wide metric history, over REST and COM alike; and a role is given or taken away only by a credential that takes in what the account holds now and afterwards, so manage_roles in named domains can no longer take a role from an account whose admin level is the server administrator's. A state-changing REST request that a browser reports as cross-site is refused. In the Control Panel, API & monitoring is Monitoring & alerts and REST API keys is under Sign-in & access; Ctrl+K takes every old title to its page. See the 6.3.7 announcement. This guide puts administrator roles in one place. Roles are new since 6.3.4 (schema 6047). The one thing to know first: an upgrade changes nobody's authority. The upgrade writes no assignment, and an account with no assignment holds exactly what its admin level has always given it. Nothing narrows until you give somebody a role. Permissions, roles and assignments A permission is one thing a credential may do, a role is a named set of permissions, and an assignment gives one account one role in named domains. An account holds at most one role. Role For Reaches when no domain is named The same as serveradmin Everything, including the secrets no other role reads Every domain The administrator password, admin level Server, an API key with Scope=full and no role domainadmin The accounts, aliases, lists and mailboxes of a domain The account's own domain Admin level Domain helpdesk Finding an account, resetting its password, unlocking it, and reading where its mail went, and nothing else The account's own domain Nothing before 6.3.4 auditor Reading everything the administration exposes, the audit trail included, and changing nothing Every domain An API key with Scope=readonly and no role The built-in roles cannot be edited or deleted. Their permissions are in the server's code, not the hm_roles table, so a change to the table cannot hand anybody a permission. The 27 permissions and who holds them A permission marked server-wide is reached only by a credential with no list of domains. The queue, the quarantine, the archive, the logs and the settings carry every domain's mail, so a role confined to named domains is refused them whatever it holds. Permission What it reaches serveradmin domainadmin helpdesk auditor administer_server Everything, secrets such as password hashes and private keys included; COM's "is this the server administrator?" yes administer_domain COM's "is this a domain administrator?" yes yes view_configuration Status, settings, listeners, certificates, rules, routes, anti-spam lists, the roles and who holds them, alert rules and events, bounce and complaint readings; never a secret yes yes yes view_accounts Domains, accounts, aliases, lists, groups (server-wide), fetch accounts yes yes yes yes view_mailbox Another account's folders and messages yes yes yes view_queue The delivery queue (server-wide) yes yes view_quarantine The quarantine (server-wide) yes yes yes view_archive The archive (server-wide) yes yes view_logs The logs (server-wide) yes yes view_message_trace The message trace; with view_configuration, the reports and a domain's suppression list yes yes yes yes view_audit The audit trail (server-wide) yes yes manage_accounts Creating, changing and deleting accounts, aliases, lists, groups, fetch accounts and app passwords; a domain's spam traps, DKIM rotation and suppression list; moving a mailbox (needs both domains) yes yes reset_password POST /api/v1/accounts/{address}/password, Application.ResetPassword yes yes yes unlock_account POST /api/v1/accounts/{address}/unlock, Application.UnlockAccount, Account.UnlockMailbox; clearing the logon-failure list (server-wide) yes yes yes manage_mailbox Another account's folders, messages and folder rights, a support session, a PST migration job (with manage_accounts) yes yes manage_queue Retrying or deleting a queued message (server-wide) yes manage_quarantine Releasing or deleting held mail (server-wide) yes yes manage_archive Holding and releasing archived messages (server-wide) yes manage_settings Every setting, listener, certificate, route, rule and anti-spam list, the alert rules (server-wide); the webmail's branding and file-link policy, a confined credential only its own domains' yes manage_domains Which domains exist (server-wide) yes manage_server Reinitialise, back up, reload scripts, clear caches, install an update, run a pass now (server-wide) yes manage_roles Roles and who holds them (server-wide) yes send_through_api Sending through POST /api/v1/send; only an API key reaches it yes provision SCIM 2.0 at /scim/v2, reads and writes; only an API key reaches it yes view_discovery eDiscovery's cases, searches, holds and exports yes yes manage_discovery Opening and closing a case, searching, legal holds, exports yes manage_inbound_webhooks Routing an address or a domain to a URL, as much as reading every message the route takes yes Four rules sit beside the table: The auditor reads mail. It holds view_mailbox; the help desk does not. The reports need two permissions. GET /api/v1/reports needs view_configuration and view_message_trace, because it says who mails whom. domainadmin and auditor read them; helpdesk is refused, and the Deck leaves the Reports view out for any role without both. The trace follows the role's reach. A confined help desk traces one whole address in a domain it reaches and sees only events naming that address exactly: [email protected] does not also show [email protected]. Clearing the logon-failure list is server-wide. hm_logon_failures records only the address a failure came from, with no account or domain, so a confined help desk is refused POST /api/v1/settings/logon-failures/clear, and Settings.ClearLogonFailureList answers the server administrator alone. The help desk unlocks one account with POST /api/v1/accounts/{address}/unlock instead; a password reset clears the lockout too. A ban already placed is an IP range under /api/v1/ipranges, which needs manage_settings. What only serveradmin has Reading a secret. No view_ permission covers a password hash or a private key: an auditor that could read one would be an offline attack away from being every role. Handing out authority. No custom role may hold administer_server, administer_domain or manage_roles, and a credential may never give a role holding a permission, or reaching a domain, that it does not hold itself. Taking over a wider account. A credential may not reset the password of, or change, an account whose whole authority it does not take in. A help desk is refused a domain administrator's or an auditor's password, even in its own domain, because otherwise it could sign in as them; a domain administrator is refused an auditor's. Where the server administrator's own mailbox is in a domain somebody else administers, that domain's administrator is refused all of these on it, over REST and COM. Over COM this is asked where the write is made, not where the object was reached: Account.Save, Account.Delete, Account.MoveTo, Application.ResetPassword, the app passwords and every write to what is in an account (FetchAccount.Save, Rule.Save, IMAPFolder.Save, Message.Save and the rest) ask that the credential reach the account and take in its authority. An event script's objects carry no credential and are left as they were. Roles.md lists every route and COM member that asks this. Which domains a role reaches Credential No domains named * A list An assignment of serveradmin or auditor Every domain Every domain Only those An assignment of any other role, custom ones included The account's own domain Every domain Only those An API key's Domains= line, whatever its role Every domain Matches no domain Only those A custom role holding a server-wide permission needs * before an account can use it at all. The listings answer every_domain over REST, and a fourth field over COM. A credential confined to named domains sees only the assignments of accounts in those domains. Giving a role, surface by surface Control Panel (Windows only): Accounts & domains → Administrator roles lists the roles and who holds them. Type an address, choose a role, optionally name the domains, and press Give the role; select a row and press Take the role away. It applies at once. Control Deck (Windows and Linux): the Roles view does the same. REST: giving or taking away a role needs manage_roles, which only serveradmin holds and which is server-wide: the administrator password, or a serveradmin credential reaching every domain. PUT /api/v1/role-assignments/[email protected] {"role":"helpdesk","domains":"example.test,example.org"} DELETE /api/v1/role-assignments/[email protected] GET /api/v1/role-assignments GET /api/v1/roles COM (Windows only): an empty role name takes the role away, and Account.RoleName, RoleDomains and RolePermissions read it back: $app = New-Object -ComObject 'hMailServer.Application' $account = $app.Authenticate('Administrator', 'your-admin-password') if (-not $account) { throw 'Authentication failed.' } $app.AssignRole('[email protected]', 'helpdesk', 'example.test') $helen = $app.Domains.ItemByName('example.test').Accounts.ItemByAddress('[email protected]') $helen.RoleName; $helen.RoleDomains; $helen.RolePermissions With a second factor on the administrator, Authenticate refuses and the script uses AuthenticateWithCode. Application.CurrentRole and CurrentPermissions say what a session holds. An API key: POST /api/v1/apikeys with "role":"helpdesk" writes a Role= line to hMailServerApiKeys.ini. The key store answers to the administrator password alone. The Deck's API keys view, signed in with that password, also makes a key with a role and its domains, showing the token once. curl -u Administrator:your-admin-password \ -H "Content-Type: application/json" \ -d '{"label":"help desk tool","role":"helpdesk","domains":"example.test"}' \ https://<host>:8045/api/v1/apikeys Use the address and port your listener answers on; bound to loopback with no certificate, it is http://127.0.0.1:8045 (https:// once a certificate, or an ACME one, is in place). A key's role decides what it reaches. The Control Panel's REST API keys page, as its reference describes it, names no role; give a key its role over REST or in the Deck. No API key signs in to the Deck. Custom roles are made over REST from a name, a description and a comma-separated permission list; both editors list them and give them out. A role that releases held mail holds server-wide permissions, so it is given with * and reaches every domain's held mail: POST /api/v1/roles {"name":"quarantine-desk","description":"releases held mail","permissions":"view_quarantine,manage_quarantine"} PUT /api/v1/role-assignments/[email protected] {"role":"quarantine-desk","domains":"*"} A permission the server does not know is dropped, not refused, so a custom role ends with less authority than it asked for, never more. A custom role somebody holds cannot be deleted: DELETE /api/v1/roles/{name} answers 409 saying how many accounts hold it, and the Deck's Delete shows that sentence. Give each holder another role or take this one away first; assignments are never removed for you, because an account with none falls back to its admin level. A built-in role answers 400, and COM cannot delete a role. An assignment decides even when its role holds nothing: holders of a custom role whose permissions name nothing this version knows are ordinary accounts, not their admin level. Where each role works Role Control Deck REST API Control Panel (COM) serveradmin Every view Every route Every page domainadmin Dashboard, DANE / TLSA, domains, fetch accounts, lists, roles and reports for its domains Its domains' accounts, aliases, lists and mailboxes, the status, who holds a role there Domains and Administrator roles helpdesk Domains, fetch accounts and lists, with Reset password and Unlock on every account The reset, the unlock, the account listings, the trace of an address in its domains Welcome and About only; Application.ResetPassword, UnlockAccount and GlobalObjects.MessageTrace answer a script auditor Every view that reads, the audit trail and reports included Every read, the audit trail and alerts included; every write refused Welcome, About and Administrator roles The help desk and the auditor work in the Deck. COM's settings objects answer only the server administrator, so an auditor reads the configuration through the Deck or REST. A domain administrator is not shown the settings, rules, routes, remote domains, certificates, ports, IP ranges, groups, backup, server messages, queue, logs or audit trail, which are server-wide. The quarantine is server-wide as well, so a domain administrator confined to its domain is refused it. Hiding a page is a courtesy, not the boundary. The server refuses the request whatever a page draws, and a Control Panel connected to a server that predates roles shows every page. A role holder signs in with its own address and password, and its second factor where it has one. An app password never carries the role: over REST it is an ordinary account's request, and COM's Application.Authenticate and AuthenticateWithCode treat it as a wrong password. A support session into a mailbox does not carry the person's role either. A role holder's own mail and settings under /api/v1/me stay its own. Setting up a help desk Turn the REST listener on if it is off. It needs RestApiPort set, RestApiBindAddress set to an address staff can reach (the default, 127.0.0.1, serves this machine alone), a certificate unless it is bound to loopback, and a name staff can reach. On Windows set them on the Control Panel's API & monitoring page (Monitoring & alerts since 6.3.7) and press Apply now, since they wait for a reinitialise. On Linux run sudo hmailserver --set-setting RestApiPort=8045 and the same for RestApiBindAddress and, for any address but loopback, RestApiCertificateFile and RestApiPrivateKeyFile (or let an ACME certificate stand in), then sudo systemctl restart hmailserver. The Control Deck topic has the details. Give each person a mailbox of their own; the role goes to that account. Decide the domains. With none named, helpdesk reaches only the account's own domain; name several, comma separated, for a desk serving more. Give the role on any surface above and check it with GET /api/v1/role-assignments or the Roles view. The person signs in to the Deck at https://<host>:8045/ as themselves, then uses Reset password or Unlock on an account. Tell them the limits: no mail reading, no reports, no domain administrator's or auditor's password, no removing a ban, and, when confined, no clearing of the logon-failure list. Each change the help desk makes is on the audit trail as its own, over COM and REST, with actor type account and a detail ending [role: helpdesk]. What a refusal says A permission the role lacks: 403 naming both, for example the role 'helpdesk' does not hold the permission 'manage_settings'. A domain it does not reach: the role 'helpdesk' does not reach that domain, whether or not the account exists, so nobody can list another domain's mailboxes one request at a time. Over COM, Application.ResetPassword and UnlockAccount answer No such account. for both. An account wider than the credential: that account holds authority this credential does not. Over COM, since 6.3.4 a refusal carries the server's sentence rather than a bare Exception from HRESULT: 0x800403E9. An API key on the key store, the [Settings] routes, the audit trail or the alerts: 401 with {"error":"authentication failed"}, the same as "not a key". The two stores only the administrator password opens The key store (/api/v1/apikeys) and the [Settings] section of hMailServer.ini (/api/v1/settings/ini) answer to the administrator password and nothing else: not an account holding serveradmin, not a key of any role or scope. A key that could mint keys could issue itself an unrestricted replacement, and the file holds the server's secrets. An account is refused 403 with the reason, a key 401. The audit trail (/api/v1/audit) and the alerts (/api/v1/alerts/...) are refused to every API key with the same 401, so a stolen key cannot read what it did or silence the alerting. An account whose role reaches every domain reaches them through view_audit, view_configuration, manage_settings or manage_server; a confined role is refused them. Upgrades, the audit trail and backups The upgrade. Schema step 6047 adds hm_roles, seeded with the four built-in rows, and hm_roleassignments, and writes no assignment. Roles are data, not settings: nothing goes into hm_settings or hMailServer.ini. The audit trail. Every role created, changed or deleted, and every role given or taken away, is a row naming the credential that did it; an assignment's row reads like role: helpdesk -> auditor; domains: the account's own domain -> every domain. The actor type follows the role: the administrator password and holders of serveradmin or domainadmin are administrator, every other account is account. Backup and restore. An account's assignment travels with it, by role name, and the custom roles travel whenever the domains or the settings do, restored first. A role the restoring server already has is kept as it is, with any difference named in the backup log. The admin level waits until the assignment is written, so a restore that stops part-way leaves no account wider than its role. An assignment naming a role neither side has is dropped, the level stays withheld, and HM6129 names the account. No archive carries the API key store. What it does not do No mapping from an identity provider. There is no claims mapping; an identity provider saying somebody is an administrator is not obeyed. The server's roles and oidc_administrators or saml_administrators decide. No role on an app password or a support session. No Control Panel or COM on Linux: roles are given there in the Deck and over REST. Where to read more Roles.md, the authority on all of this. Sign-in and Identity §7, The Control Deck, The REST API §2 and The COM API and Scripting §23.2. AlertsAndAuditTrail.md, Backup.md and Reports.md. On this forum: The Control Deck: browser administration at /, The REST API: turning it on, credentials and roles, The COM API on Windows, Hardening a public mail server, Alerts and the audit trail and IP ranges, auto-ban and lockout.
  • 0 Votes
    1 Posts
    118 Views
    ProgressiverobotP
    This topic covers how much mail an hMailServer 6.3.4 server keeps, and for how long: each mailbox's limit and the quota policy 6.3.4 adds around it; retention, the administrator's and each person's, with the recover-deleted window; and the server archive under ArchiveDir. The one thing to know first: out of the box nothing is deleted because of its age. A new account has no limit (0), no domain has a retention policy, the archive is off, and once it is on, ArchiveRetentionDays is 0, keep for ever. The limit, and what a full mailbox does Where it is set. Account.MaxSize over COM, max_size_mb over REST, in megabytes; 0, the default, is no limit. Set it on the Control Panel's account dialog (Quota (MB), Windows only), in the Control Deck's account editor, with PUT /api/v1/accounts/{address}, in the max_size_mb column of an accounts CSV file (since 6.3.4), or with Set-HmAccount. A domain's Limits tab caps the whole domain (Maximum domain size (MB), 0 for none; once reached, delivery to its mailboxes fails) and sets the quota new accounts get. What counts is each message's original size: compression does not lower it, and a mailbox encrypted at rest keeps its sizes in the clear. The kept copy of a message the person sent to expire counts against their quota (413, quota_exceeded). Where At the limit SMTP RCPT TO, with RejectFullMailboxAtRcpt on (1, the default) A mailbox already at or over its limit, and past any grace (below), is answered 452 4.2.2 Mailbox is full., a temporary refusal: the sending server keeps the message and retries A message that arrives while there is room and would overflow it Caught at delivery, with a non-delivery report to the envelope sender RejectFullMailboxAtRcpt at 0 Accepted, then bounced to an envelope sender that spam has usually forged. Leave it on IMAP APPEND, COPY, MOVE Refused with [OVERQUOTA], so a client can tell a full mailbox from any other refusal APPEND into a folder shared from another mailbox Since 6.3.4 charged to the folder's owner, as COPY and MOVE were A migration or PST import job Pauses with the account has no room left under its quota and tries again; raise the limit or make room A full disk is a different refusal. Below MinimumFreeDiskSpaceMB (100) free on the store's volume, MAIL FROM gets 452 4.3.1 Insufficient system storage. and IMAP APPEND NO [UNAVAILABLE], for every mailbox. The log warns first, below DiskSpaceWarningThresholdMB (1024). What the person sees. The webmail's Storage page (since 6.3.2, GET /api/v1/me/storage the share used, every folder by size, the twenty largest messages, and a clean-up of Trash and Junk older than 7, 30 or 90 days, or everything. See The webmail portal at /portal. GET /api/v1/me answers quota with limit_mb and used_bytes. IMAP QUOTA: QUOTA=RES-STORAGE is advertised; GETQUOTA and GETQUOTAROOT answer one unnamed root for the whole account with STORAGE in kilobytes, and an empty list for an account with no limit. SETQUOTA is refused: the limit is the administrator's. It is on as seeded; the switch is imap_quota_enabled on PUT /api/v1/settings (IMAPQuotaEnabled over COM). The quota policy (since 6.3.4) The server-wide warning at QuotaWarningPercent (90) has been there since 6.2.22-pre6. Schema 6061 adds a domain's own warning share and words, a grace and a send-stop, each a share of the limit; schema 6088 lets an account hold its own shares too. Point Server Domain (-1 takes the server's) What it does Warning QuotaWarningPercent, 90; 0 warns nobody quota_warning_percent The delivery that takes the mailbox across this share sends its owner one notice, from the domain's postmaster Grace quota_grace_percent, 0 quota_grace_percent How far past the limit mail from other people is still accepted Send-stop quota_send_stop_percent, 0 (never) quota_send_stop_percent The share at which the account can no longer send Over COM (Windows): Settings.QuotaGracePercent, Settings.QuotaSendStopPercent, and Domain properties of the same names. The warning is sent once, by the delivery that crosses the threshold; nothing records who was warned, so a mailbox emptied and filled again is warned again. The grace is for mail other people send, the mail otherwise lost. A 1 GB mailbox with a 50% grace receives until it holds 1.5 GB; the owner's own saves and copies over IMAP and the webmail stop at the limit. The send-stop refuses the account, not the address. An authenticated MAIL FROM is refused with 552 5.2.2 and the reason, keyed on the account that signed in, even under a domain alias, not the address it named. Receiving goes on. QuotaPolicy.md describes the refusal at SMTP only; test the webmail's send and the send API before relying on it there. The words are the QUOTA_WARNING_SUBJECT and QUOTA_WARNING server messages, with %MACRO_MAILBOX%, %MACRO_QUOTA% (MB), %MACRO_USED% (MB) and %MACRO_PERCENT%. Server messages are server-wide (the Control Panel's Mail flow & delivery → Server messages, the Deck's Server messages page, PUT /api/v1/settings/messages/{name}). A domain sets its own subject and text, quota_warning_subject and quota_warning_text (Domain.QuotaWarningSubject, Domain.QuotaWarningText), so its people can be warned in their own language. They are chosen apart: a domain that sets only the text keeps the server's subject. Levels. The warning is a preference: the deepest level holding a value wins. The grace and the send-stop are limits: an account's own value may tighten its domain's, never loosen it, while a domain's may sit either side of the server's. The domain's shares are the domain administrator's, and a domain-restricted key's. <name>_effective and <name>_source on the domain and account routes say why a mailbox was not warned. An account's own quota shares are tested only through what the routes answer, and only on Windows. The mechanism is in Settings: what is not stock, when a save takes effect, and the three levels. Where it is set. Control Deck: the domain editor's Quota policy group; the server's grace and send-stop on the Settings page. Control Panel (Windows): the domain dialog's Limits tab; the server's grace and send-stop under SMTP, beside QuotaWarningPercent. REST: the five domain keys on PUT /api/v1/domains/{name}; the server's two on PUT /api/v1/settings. An account's own value: the Levels tab of the Control Panel's account dialog, the Deck account editor's levels card, hmctl account set, or PUT /api/v1/accounts/{address}. QuotaWarningPercent is a key of the settings store: set it in the Control Panel (Windows) on Connections & protocols → Protocols, SMTP tab, with PUT /api/v1/settings/ini/{name} or hmctl ini set, which answer to the administrator password alone (never an API key, nor an account holding a role), or, on the Linux server itself, hmailserver --set-setting, not by editing hMailServer.ini. Every value here applies when it is saved through the server, at every level; when a save takes effect explains why a line edited in the file does not hold and when a value stored by --set-setting arrives. A backup taken before the policy restores with the server's values. One domain warned at 85%, stopped from sending at the limit and given a 20% grace, with a German subject; then one account put back on its domain's grace: hmctl domain set example.com quota_warning_percent 85 hmctl domain set example.com quota_send_stop_percent 100 hmctl domain set example.com quota_grace_percent 20 hmctl domain update example.com --set "quota_warning_subject=Ihr Postfach ist fast voll" hmctl account set [email protected] quota_grace_percent inherit Who is nearest their limit GET /api/v1/reports/quota lists every mailbox with a limit and at least one message, nearest first: megabytes, quota_megabytes, percent, the warns_at and sending_stops_at points that apply to it, and a state of ok, warned, stopped or over. It takes domain, top (1 to 200, default 10) and format=csv, and ignores the date window. Its test has run on the Windows bench only. The Deck's Reports page shows it as Near their quota. Other than the administrator password, a credential needs view_configuration and view_message_trace. The sections hmctl report documents do not include it: curl -u Administrator:your-admin-password \ "http://127.0.0.1:8443/api/v1/reports/quota?domain=example.com&top=200&format=csv" Nothing records what a mailbox held yesterday; per-domain storage by day is the storage section, in Alerts, the audit trail, syslog and the journal, and per-domain reports. Retention: the administrator's policy MessageRetentionDays (since 6.2.25) is set per domain and per account; there is no server value. On a domain, 0 (the default) is no policy and a number deletes mail older than that many days. On an account, 0 (the default) takes the domain's, a number is its own, and -1 keeps for ever whatever the domain says. The sweep. MailboxRetentionTask runs at start-up and every six hours. It removes delivered messages whose stored creation time is older than the window, in every folder of the mailbox, through the same deletion an IMAP EXPUNGE performs, so the quota, the QRESYNC record and an open IMAP session all see it. Utilities.RunMessageRetention (COM, Windows only) runs it now and returns the count; no document names a REST route or hmctl verb for it, so on Linux wait for the next pass. Where it is set. On Windows the Control Panel's domain dialog (Limits tab) and account dialog (General tab); the Deck's domain and account editors; message_retention_days on the domain and account routes; hmctl domain set and hmctl account set. A domain's retention is the server administrator's (COM refuses a domain administrator, the domain route a restricted key); an account's own value is the domain administrator's, so an account's -1 can switch its domain's policy off for that account. Before switching a domain's policy on: Give -1 to each account that must keep everything. Decide whether what the sweep removes should be recoverable for a while (recover_deleted_days, below). Off, a removed message is gone. Set the domain's days. It applies when saved; the next pass, at most six hours away, deletes. Easy to miss. A restore of the built-in backup does not carry a domain's retention days: the wiki's Backup and Restore page lists them as silently reset to 0, so re-enter them. And a legal hold outranks retention: a held account is left alone by the policy and by the person's own choices, and the log says so. Each person's own retention (since 6.3.4) Three choices on the webmail's preferences page: Empty Trash after, Empty Junk after and Archive Inbox mail after, each never, 7, 30 or 90 days, or a year, kept as retention_trash_days, retention_junk_days and retention_archive_days. The same six-hourly sweep runs them after the administrator's policy. Trash and Junk (the folders designated \Trash and \Junk, or named as clients name them) are emptied of what is older; Inbox mail older than the choice is copied to the \Archive folder, made as a top-level Archive if there is none, then removed from the Inbox. That is a folder of the person's mailbox, not the server archive. A value that is not a whole number of days from 1 to 3650 is off. The recover-deleted window (since 6.3.4) recover_deleted_days on PUT /api/v1/settings or the Deck's Settings view (0 to 365; 0, off, by default) keeps a copy of what a person deletes, and of what either retention removes, for the person to put back from the webmail's Recover deleted items. A domain or an account may keep copies for fewer days than the level above, never more. The copies, under <data directory>/Recoverable/<account id>/, count against no quota and are not in the backup, so size the window with the disk in mind; the six-hourly task ends those past the window, and all of them at 0. The person's side is in The webmail in depth: search, offline mail, expiring messages, send-as and recovery. The server archive With ArchiveDir set, the server writes a copy of every message received by SMTP into a tree of its own, as received and in the clear: encryption at rest does not cover it, and the mailbox retention policy does not reach it. The layout, as the Settings Reference writes it (Windows separators, names lower-cased): Message Where the copy goes From a local sender ArchiveDir\<senderdomain>\<sendername>\Sent-<file> From anybody else ArchiveDir\Inbound\<file> For each local recipient ArchiveDir\<recipientdomain>\<recipientname>\<file>, made from the first copy when there is one A bounce or report with a null sender The archive's Error folder, with no index row The archive's settings: Key Default What it does ArchiveDir empty (off) The folder the copies go to ArchiveDomains empty (every message) Comma-separated: a message is archived only when its local sender or a recipient is in a listed domain, and only the listed domains' copies are made ArchiveHardLinks 0 Hard-link instead of copy: a promise that nothing outside the server edits a message file in place ArchiveRetentionDays 0 (keep for ever) Deletes archived copies older than this many days Its sweep runs at start-up and every twelve hours. It removes only .eml files, follows no symbolic link or junction, is depth-capped, and asks the index first: a held copy stays however old. The index (since 6.2.25, schema 6029): a row per copy in hm_archiveindex, with who, when, what, where and whether it is held; copies made before schema 6029 have none. Search it in the Deck's Archive view (since 6.3.4), with GET /api/v1/archive (domain, mailbox, sender, recipient, subject, since, until, hold=1, limit 1 to 1000, default 200, newest first), or with Utilities.SearchArchive over COM (Windows). A role needs view_archive; a key restricted to named domains must name one of its own, and the Inbound copies are the administrator's alone. Searching the words of archived mail is an eDiscovery case's work. A hold on one copy, POST /api/v1/archive/{id}/hold or Utilities.SetArchiveHold, keeps its file and row from the sweep and from an address's erasure; DELETE lifts it. Holds on whole accounts and domains are eDiscovery's: see Compliance: eDiscovery, legal holds, personal data, erasure and the audit trail. curl -u Administrator:your-admin-password \ "http://127.0.0.1:8443/api/v1/archive?domain=example.com&[email protected]&since=2026-09-01&limit=50" curl -u Administrator:your-admin-password -X POST http://127.0.0.1:8443/api/v1/archive/4471/hold Where it is set. On Windows, the Control Panel's Maintenance → Advanced, Copies of mail tab (ArchiveDir, ArchiveHardLinks, ArchiveDomains), and ArchiveRetentionDays on the SMTP tab of Connections & protocols → Protocols. The Deck has no page for these keys: use PUT /api/v1/settings/ini/{name} or hmctl ini set <name> <value> (the administrator password only), which take effect when saved, or hmailserver --set-setting NAME=VALUE (with sudo) on the Linux server itself, which, while the service runs, waits for the next save, a reinitialise (POST /api/v1/server/reinitialize) or a restart. Setting one up: Choose a folder outside the data directory: a full restore with the messages option replaces that directory, and an archive inside it too, held copies included. On Windows, a new installation runs the service as NT SERVICE\hMailServer, which writes only where it is granted: approve the folder by running hMailServer.exe /GrantServiceAccess with --approve-modify "<folder>" from an elevated Command Prompt in the installation's Bin folder. It must be a full path on a fixed local drive, outside the program folder, Program Files, the Windows folder and the profiles folder: never a share, a drive's root, or a path through a junction or link. On Linux the service runs as User=hmailserver, and the packaged unit lets it write only under /var/lib/hmailserver, the data directory itself, and /var/log/hmailserver. Every other folder is read-only to it (/home is not even visible), and an ArchiveDir it cannot write stops archiving. Make the folder writable by the hmailserver user, add it to the unit with a drop-in (sudo systemctl edit hmailserver, then under [Service] a ReadWritePaths= line naming the folder) and restart the service. SecureDefaults.md gives this drop-in for a data folder; no document gives it for the archive. Set ArchiveDomains first if only some domains are archived, then ArchiveDir. Send a test message and find its row in the Deck's Archive view. Before relying on it. The backup does not carry it. The backup copies the data directory, and hm_archiveindex is not carried: back up the archive folder, and the database with its own tool for the index. Moving it strands the index. hm_archiveindex.archivepath is matched as an exact string by the sweep, holds and erasure; do not rewrite it in bulk. If the folder moves, move it back or accept that older rows point at the old place. A mailbox move leaves the archive under the old address, and an address's erasure with the archive included leaves the copies in other people's trees and in Inbound until ArchiveRetentionDays ages them out. It is not journaling, which sends a copy elsewhere and keeps nothing here: Journaling and dual delivery. SMTP, as documented. No 6.3.4 document says whether mail the server queues itself, such as the webmail's send, or mail a fetch account collects, is archived. What it does not do No message-count quota: storage is the only quota enforced. No server-wide retention policy, and no documented way to run the retention sweep on demand on Linux. No write-once archive: a hold is a flag an administrator can lift. The archive only grows while ArchiveRetentionDays is 0, and the recover-deleted store is bounded by its window alone: watch the disk they are on. Where to read more Documents at v6.3.4: QuotaPolicy.md, SettingLevels.md, Reports.md, RecoverDeleted.md, Discovery.md, Backup.md, ServiceAccount.md, RelocatingAnInstallation.md, PersonalData.md, Journaling.md, SecureDefaults.md, SettingsApply.md. Wiki: Settings Reference (section 9.13), Your First Domain and Mailbox, Compliance and eDiscovery, Backup and Restore, APIs Reference. The documentation page: Storage, retention and archive, Queue, quarantine and archive. On this forum: Backup and restore, for what a restore resets; Relocating an installation, and moving or renaming a mailbox, for the archive index; the service account on Windows and the Linux unit, for the folders the service may write; The Control Deck.
  • 0 Votes
    1 Posts
    137 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.8 (2 November 2026), a security release, reads on Windows the CA file MessageStoreObjectCaFile names from the file configured, or refuses its name: a name it cannot hand to OpenSSL as it is named, as a path holding characters outside ASCII may be, is refused with HM7536 in the error log, and then no request to the object store is allowed, so give the file, and the folders above it, an ASCII name before upgrading. See the 6.3.8 announcement. This topic covers the three things 6.3.4 can do to a stored message that 6.3.3 could not: store it compressed with its large attachments held once, keep it in an S3-compatible or Azure Blob store instead of under the data directory, and store it encrypted to a key of the mailbox's own. All three are new in 6.3.4, and all three are off until an administrator turns them on: a server that turns none of them on stores every message exactly as 6.3.3 did. The one thing to know first: the delivery queue is never compressed, encrypted or put in the store. A queued message is plain RFC 822 on the local disk, so the event scripts, the virus scanners, SpamAssassin, the sandbox and the filter hook read what they always read. One door, and what a client sees Every place that reads, sizes, copies, moves or deletes a stored message now goes through one seam, MessageStore. Everything that hands a message out reads the original bytes back through it, so a client sees nothing different: IMAP FETCH BODY[], RFC822.SIZE, BODYSTRUCTURE, SEARCH, SORT and THREAD; POP3 RETR, TOP, LIST and STAT; the webmail and the REST API; the rules, Sieve, the quota (which counts the original sizes), and a DKIM signature checked against the stored copy. What changes is the file itself. Message.Filename over COM still answers the stored file's path. With compression on, a COM client or a script that opens that path reads a container, not RFC 822; with an object store, once the message is committed there is no file there. Read the message through the API instead. Compression and attachments held once Schema 6098. With MessageStoreCompression on, a message delivered to a mailbox, or put there by IMAP APPEND, is stored as a container: the message compressed with deflate, and each large MIME part, an attachment of at least MessageStoreDedupMinKB once decoded, cut out and held once as a blob that every message carrying the same bytes shares. A mail sent to forty people on this server with a 10 MB presentation keeps the presentation once. Where What Control Panel (Windows) Settings > Maintenance > Performance, the Message store tab Control Deck The two settings on the server settings page, and a Message store section on the dashboard REST message_store_compression and message_store_dedup_min_kb in /api/v1/settings; GET /api/v1/store; POST /api/v1/store/measure COM (Windows) Settings.MessageStoreCompression, Settings.MessageStoreDedupMinKB, Settings.MessageStoreStatus Both are rows of hm_settings and take effect for the next message stored. MessageStoreDedupMinKB is 256 unless changed, 0 to 65536, and 0 holds nothing once and compresses only. The switch rewrites nothing. The reader never asks the setting what a file is: it reads either form, per file, by the file's first eight bytes. Turning it off leaves every stored message readable. Only delivery and IMAP APPEND write the new form. A webmail draft, a message saved through COM, the importers and the IMAP mirror write plain. A message larger than 64 MB is stored as it came, because a container is decoded whole in memory. Every container is checked before a byte is served: its structure, each blob against its hash, and the SHA-256 of the whole. A damaged one is refused and named in the error log (HM7350), never served as something else. Two messages share a blob when the part's encoded bytes are identical. The same file attached twice by a client that wraps its base64 differently is two blobs. The command line, run as the user the server runs as (hMailServer.exe takes the same switches on Windows): hmailserver --measure-store [--account <address> | --domain <name>] hmailserver --compress-store [--account <address> | --domain <name>] [--decompress] hmailserver --recount-store Verb What it does Server --measure-store Changes nothing; prints what the store takes now, what it would take, and how much more it would save May be running --compress-store Rewrites every stored file in place, each written beside itself, read back and compared byte for byte, then renamed over its name Stopped --compress-store --decompress Puts the files back as plain RFC 822 Stopped --recount-store Sets every blob's count to what the stored files reference Stopped Exit 0 is a finished walk, 1 a file that could not be read or written, 2 no database or no such account or domain, and 4 a running server. How much it saves has not been measured on a real mailbox. MessageStore.md holds a placeholder where the figures are to go, and the wiki claims none. Run --measure-store on your own store and read the answer. The document also records that its regression fixture had not yet run against a bench when it was written. Object storage: S3 and Azure Blob The messages in the mailboxes, the blobs and the recover-deleted store's kept copies can be kept in an S3-compatible store (AWS S3, MinIO, Wasabi, Backblaze B2's S3 API, Ceph RGW), signed with AWS Signature Version 4, or in Azure Blob Storage, with Shared Key or a SAS. The database stays the index, and an object is named by the path its file had. There is no schema step. Nothing changes until a store is named. Every write is a local one. A message is placed on the local disk exactly as without a store, and a committer puts it into the store afterwards, and only then removes the local file. It runs every minute; what it has been handed is journalled in <data directory>\_ObjectStore\pending.txt and taken up again after a restart. So a delivery never waits for the store and never fails for it, and every read looks at the local disk first. Property REST key Default MessageStoreBackend message_store_backend file; the other value is object MessageStoreObjectService message_store_object_service empty; s3 or azure MessageStoreObjectEndpoint message_store_object_endpoint empty, which is the service's own MessageStoreObjectRegion message_store_object_region us-east-1 MessageStoreObjectBucket message_store_object_bucket empty MessageStoreObjectAddressing message_store_object_addressing auto; also path, virtual MessageStoreObjectPrefix message_store_object_prefix empty MessageStoreObjectAccessKey message_store_object_access_key empty MessageStoreObjectSecret message_store_object_secret empty MessageStoreObjectSasToken message_store_object_sas_token empty MessageStoreObjectCaFile message_store_object_ca_file empty MessageStoreObjectCacheMB message_store_object_cache_mb 32 These are rows of hm_settings, set over /api/v1/settings or on the Control Deck's Settings > Server form. There is no COM member and no Control Panel page for them. The secret and the SAS are protected, never emitted, and left out of a backup, so after a restore onto another server they are entered again. TLS verification is always on, and no setting turns it off. With MessageStoreObjectCaFile set, the certificate is verified against that file: beside the system's bundle on Linux, and alone on Windows. Turning it on: Make the bucket or container. This server makes none. For S3, an access key allowed s3:GetObject, s3:PutObject, s3:DeleteObject, s3:ListBucket and the multipart upload calls, and a lifecycle rule that aborts incomplete multipart uploads after a day. Name it, with message_store_backend still file. Copy the store into it: hmailserver --copy-store-to-object-storage. It runs beside the server, verifies every object, and goes on where it stopped when run again. Switch new mail to it: message_store_backend to object, taken within five seconds. Remove the local copies when you are ready: stop the server and run the copy again with --remove-source. Going back is the same the other way: message_store_backend to file, then hmailserver --copy-store-from-object-storage, then, once it says nothing failed, message_store_object_service to empty. When the store cannot be reached, mail is not lost. Door Answer for a message only the store holds A delivery Placed on the local disk and done; committed when the store answers again IMAP FETCH NO [UNAVAILABLE] POP3 RETR and TOP -ERR [SYS/TEMP] The webmail and REST 503 HM7473 says, once an hour at most, how many files wait on the local disk. HM7471 is the store refusing this server's credentials (401, 403), which is never taken for an absent message. HM7476 names a message that is neither on the disk nor in the store; no placeholder is written for it while a store is named. What is thinner than it could be: one connection per request; one committer, a pass at a time; credentials are the two the settings hold, with no instance role, no workload identity and no Azure AD token; the Import Tool and the data directory synchronizer walk the local disk only; a changed prefix is not a move. The requests were proved against MinIO and Azurite on the project's lab on 25 September 2026. The server's fixture needs that lab, is skipped on a bench without one, and has not run in the Linux suite. Mailbox encryption at rest Schema 6099. A mailbox whose messages are stored encrypted to an X25519 key of the account's own, with AES-256-GCM, as the container's last stage, after compression. The key opens when the person signs in with their password and closes MailboxKeyIdleMinutes after the owner's last use (15 by default, 1 to 1440). Mail that arrives while nobody is signed in is encrypted all the same, because encrypting needs only the key's public half. When a password is lost, the domain's recovery key opens the key again. It is off for every account until an administrator switches it on for that account. What it protects: a disk taken out of the machine, a snapshot of the volume, a backup archive, the data directory copied somewhere, the database dumped, once the account has signed in with its password after the switch. What it does not protect. It does not protect the mail from the running server, which reads it while the key is open, nor from whoever runs the server, who can capture a password at sign-in. A person who needs that wants a zero-access mailbox, and the two are never on one mailbox. Until the first sign-in the key is pending, held under this machine's own secret: the machine's DPAPI key on Windows, and the key file .hmailserver-secret-key inside the data directory on Linux. On Linux a copy of the data directory together with the database therefore opens every pending key. An account that only receives mail and never signs in with its password stays pending. Switching it on, on the Control Deck or over REST: The domain's recovery key first: the domain page's Mailbox recovery key card, or PUT /api/v1/domains/{domain}/mailbox-recovery with {"passphrase": ..., "hint": ...}. The passphrase is at least 12 characters, and this server never stores it. Keep it where this server is not. Then the account: the Encryption at rest card, or PUT /api/v1/accounts/{address}/mailbox-encryption with {"enabled": true}. The switch-on ends every session the account had. A mail client sees its connection drop; the person signs in again with the password. Then what is already there: hmailserver --encrypt-store --account <address>, with the server stopped. Refused, with a sentence: an account whose domain has no recovery key; a zero-access mailbox; an account that signs in against a Windows directory; and an account with a TOTP second factor enrolled. The last runs both ways: a second factor cannot be enrolled on an account that has a mailbox key. Sign-in. IMAP and POP3 sign a keyed mailbox in only with its own password. An app password, an OAuth2 token, a client certificate or a Kerberos ticket is answered NO [UNAVAILABLE] (POP3: -ERR [SYS/PERM]), and the IMAP master user is refused whether or not the owner's key is open. The webmail refuses a passkey, single sign-on or an app password (403, mailbox_key). SMTP, ManageSieve and DAV are not refused, since they do not read the mailbox. Passwords. The person's own change wraps the key by the new password. An administrator's reset does so while the key is open or pending, and is refused while it is closed, with a sentence naming the recovery: POST /api/v1/accounts/{address}/mailbox-encryption/recover with the domain's passphrase and the new password. A lost password and a lost recovery passphrase together are the mail lost. Nobody can open it. DELETE /api/v1/accounts/{address}/mailbox-encryption?confirm=forfeit removes the key, and with it every message still encrypted to it, for ever. It is refused while a legal hold covers the account. Switching it off stores new mail as it comes. What was stored encrypted stays encrypted until hmailserver --decrypt-store, run with the server stopped and the domain's passphrase in the environment variable HMAILSERVER_RECOVERY_PASSPHRASE, never on the command line. A keyed mailbox stays in its domain. A move to another domain is refused (409); a rename inside the domain goes ahead. What stays in the clear: the queue and the copy a delivery works on, which is overwritten with zeros once placed, though a copy-on-write filesystem, a snapshot or an SSD may not honour that; the calendar and the address book; the quarantine, the archive and journal copies, and a forward; each message's row in the database, with its size, flags, folder and dates; logs and the trace. An encrypted mailbox holds no part once. A blob shared between accounts cannot be under one account's key, so every part stays inside the message's own container. The cost is the de-duplication, for these mailboxes only. Backups A backup with messages copies the data directory as always, with the containers, the Blobs directory and encrypted files as they are. It copies file by file while the server runs, so an archive taken while mail is delivered and deleted can hold a container whose part was written after the Blobs directory was copied: take the backup when the server is quiet. With an object store it also reads every message and blob that is only in the store into the archive; a store that cannot be read fails the backup. A full restore rebuilds the blobs' counts from the stored files. hm_storeblobs is not in the archive. An encrypted mailbox's key is carried with its account, and the domain's recovery key with its domain. A pending key on Windows opens only on the machine that wrote it; elsewhere the mailbox is opened with the recovery. On Linux it opens wherever the key file goes, and the archive does not carry that file. A restore onto a server that never turned compression on reads the containers the archive brought. Known limits in 6.3.4 Limit Where it is stated Encryption at rest is on REST and the Control Deck, with no webmail card and no Control Panel page. The person's own view is GET /api/v1/me/mailbox-encryption CHANGELOG, EncryptionAtRest.md No COM for the switch, the recovery or MailboxKeyIdleMinutes EncryptionAtRest.md A search over an encrypted mailbox reads every message, and works only while its key is open. The full-text index holds none of its words EncryptionAtRest.md With compression on, Message.Filename over COM names the stored form; with an object store it names a path that holds no file once committed CHANGELOG, MessageStore.md, ObjectStorage.md An account's key is never rotated; the domain's recovery key can be replaced EncryptionAtRest.md The encryption fixture runs on Windows and is not in the Linux suite EncryptionAtRest.md No zstd: deflate is the one stage MessageStore.md Where to read more The wiki page: Message Storage The documents: MessageStore.md, ObjectStorage.md and EncryptionAtRest.md, each with its error codes and what proves each claim Zero-access mailboxes: the wiki's Encryption for People, and on this forum End-to-end encryption The documentation page: Backup and restore On this forum: The Control Deck, where the cards and the settings form are, and Backup and restore, for what the archive holds and what it leaves out
  • 0 Votes
    1 Posts
    146 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) adds no schema step, so a 6.3.6 node and a 6.3.7 node both require schema 6108 and the version check at start does not tell them apart: upgrade both nodes all the same. On MySQL and MariaDB a lost database connection is reconnected and the statement run again, so the statement after a database restart or a failover no longer fails; and the first units of the active-active work are in the tree, and it is still not built. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026), a security release, adds no schema step either, so a node of 6.3.6, 6.3.7 or 6.3.8 requires 6108 and the check at start does not tell them apart: upgrade both nodes all the same. See the 6.3.8 announcement. This topic covers what hMailServer 6.3.4 offers for a second machine. Two operator documents describe one topology from two angles: WarmStandby.md, on what has to reach the standby and the constraints that decide the design, and HighAvailabilityRunbook.md, the active/passive procedure with a virtual IP and a health check. The one thing to know before anything else: exactly one hMailServer service may run against the database at a time. There is no clustering code and no active/active mode. Failover is starting a service on the other machine, which takes minutes, and a second service started against the same database while the first still runs delivers mail twice, with no error anywhere. Why exactly one service may run The code makes two servers destructive to each other in three ways: Startup unlocks the whole queue. PersistentMessage::UnlockAll runs update hm_messages set messagelocked = 0 for every queued message when the service starts, because locks held by a crashed server would otherwise stall those messages for ever. A second server started against the same database releases every message the first is delivering, and both then deliver them. Delivery selection is not coordinated. The queue is driven by select ... where messagelocked = 0 and messagenexttrytime <= now, and the lock is taken after selection, so two servers race that window even after both have started cleanly. External fetch has the same shape. PersistentFetchAccount::UnlockAll runs at fetch-manager start, and a POP3 account fetched twice with delete-after-download is mail split between two stores. The rehearsal of 9 September 2026 measured the first. With the primary holding a queued message locked, the standby was started on a second machine: it bound the same ports, because the listeners are rows in hm_tcpipports and both servers read them, and the lock went to 0. Nothing warned, on either side. So the standby is warm: installed, configured, patched along with the primary, and its service set to Manual start and left stopped. WarmStandby.md says Manual rather than Disabled, because a disabled service cannot be started by the runbook without an extra step. The HA runbook accepts either in its section 4 and its check-list, though the wiki's copy of it lists Disabled among the ways the pair is got wrong, for the same reason. Use Manual. What lives where State Lives in Reaches the standby by Domains, accounts, aliases, lists, rules, routes, IP ranges The database Sharing the database Message metadata, folders, IMAP state The database Sharing the database The delivery queue hm_messages rows and spool files on disk The database and the data directory Greylisting triplets, auto-ban ranges, login-failure state The database Sharing the database Message files (.eml) The data directory Replication or shared storage, below The [Settings] section Since 6.3.4 the database (hm_inisettings, schema 6042); the INI section is a cache Sharing the database [Directories], [Database], [Security], [GUILanguages] Each machine's own hMailServer.ini Configured per machine, deliberately [SettingsOverride] Each machine's own INI Not shared, on purpose PasswordPepper Each machine's own INI; no database and no backup holds it Copied by hand TLS certificates and keys, DKIM keys Files on disk; the database stores their paths The same files at the same paths Route, external-fetch and per-domain-relay passwords The database, DPAPI-protected They do not travel An account's second factor and the [Settings] secrets The database, sealed under the stored-secrets key (schema 6103) Sharing the database, once the standby holds the key The settings store, since 6.3.4. A standby that shares the database inherits the primary's server settings at its next start, without anything copying INI files. Each setting the primary changed since the standby's file was written is named in the standby's error log as HM5804 and its line rewritten: that is the copy catching up, not a fault, and it appears once. A stale file can still do two things. A setting deleted on the primary whose line the standby's file still carries is adopted back into the shared store when the standby starts, so delete it from the standby's file as well. And a key the standby's file holds that the primary never had is adopted the same way. Do not copy the primary's INI over the standby's. On Windows [Database] Password is sealed with the machine's own DPAPI key, and the copy breaks the standby's database connection outright. Treat "no [SettingsOverride] section on either machine" as part of the check-list: a key in that section is applied over whatever the database holds. On 6.3.3 it was different. The wiki records that the [Settings] section was not carried by the shared database there, and had to be copied into the other machine's INI after every settings change. The wiki also notes that [LDAP], [SendingLimits] and [SendingLimitsOverrides] stay in each node's file in 6.3.4, to be kept the same by hand. Paths must resolve on both machines. If the primary's row says C:\certs\mail.pem, the standby needs that file at that path. Rotated DKIM keys are written into <DataDirectory>\DKIM; a standby promoted with an out-of-date copy of that folder signs with a key file it does not have. What does not travel Passwords the server presents to somebody else. Route smart-host credentials, external fetch accounts, the per-domain relay password and SSL private-key passphrases are stored protected by Windows DPAPI with CRYPTPROTECT_LOCAL_MACHINE, and a blob written by one machine cannot be decrypted by another. After a failover the server keeps running, with local delivery, IMAP, POP3 and unauthenticated relaying untouched, but each such credential fails until it is re-entered on the standby. Failback breaks them the other way. Keep the list of credentialed routes, fetch accounts and relay-configured domains, with the passwords in a password manager. It is between zero and five entries on a typical installation. The wiki has a PowerShell script over COM that lists them; none of its getters returns a password. On Linux the server seals these with a key file, <DataFolder>/.hmailserver-secret-key. In the rehearsal the route arrived without its password, the standby reported HM6412, and the delivery attempt went out unauthenticated. A replication that copies the data directory wholesale copies that file too, and the standby then reads every secret the primary could. Decide which you want; the rehearsal excluded it. The administrator password. It lives in hMailServer.ini, not in the database. Until it is set on the standby, the standby has no REST API and no COM authentication. On Linux the command is sudo hmailserver --set-admin-password. On Windows WarmStandby.md names the Control Panel, but a standby installed with 6.3.4 is a new installation, whose service runs as NT SERVICE\hMailServer by default, and under that account the Control Panel's password card is refused: use hMailServer.exe --set-admin-password from an elevated prompt, which is new in 6.3.4. The rehearsal found this when the standby came up mute. PasswordPepper. With a different value on the standby, or none, no account whose password is an Argon2id or scrypt hash signs in there. The stored-secrets key, since 6.3.4. It is kept in each server's INI as [Security] StoredSecretsKey, never in the database, so the standby needs it carried: export it on the primary, elevated, with hMailServer.exe --export-stored-secrets-key FILE (sudo hmailserver ... on Linux), and import it on the standby with --import-stored-secrets-key FILE. If the standby's installer made a key of its own, remove its [Security] StoredSecretsKey line first, or the import is refused. Never copy the line itself; it is sealed under the primary's own DPAPI. Without the key every second factor refuses every code on the standby, and SRS, BATV, HS256 tokens and /metrics stop working there; the standby says so as HM7495 at each start. Hardening a public mail server has what the key seals, and what to do on an upgraded server, which has none until you make one. The message store: the two documents differ WarmStandby.md says replicate, and do not share. It argues against putting the data directory on an SMB share or a Cluster Shared Volume: The accept path flushes the spool file to disk before answering 250. Over SMB that guarantee depends on the file server and on the network between the machines, which is the failure the topology exists for. Local delivery copies the spool file once per local recipient, and the delivery loop rewrites message files; each becomes network I/O. Handles and oplocks held by a crashed primary take time to expire, so the standby can find its files locked by a machine that no longer exists. Cluster Shared Volumes are built for large block-oriented I/O, not millions of small files. It recommends a one-way scheduled robocopy /MIR of the data directory every few minutes, or DFS-R or storage-level replication, stopped as part of failover, so that a stale mirror pass from a half-dead primary cannot delete newly accepted mail. Messages accepted after the last sync are rows whose files are missing on the standby. The server treats a missing file as an error on access, not a crash, so a five-minute interval bounds the loss to five minutes of message bodies; metadata, folders and flags lose nothing. The document calls the SMB and CSV reasoning operational judgement built on code facts, not itself a code fact. HighAvailabilityRunbook.md says share. Both nodes must see the same [Directories] DataFolder on shared storage (SAN, NAS, a clustered file system or a cloud file share), with identical read and write access for both service accounts, and the database and the store on the same failover boundary. Its validation check-list begins with that. Neither document withdraws the other. Choose knowing what each claims. The database Use a server backend, SQL Server, MySQL or MariaDB, or PostgreSQL, with its own availability. SQL Server Compact is excluded: it is a single local file. Both machines must run the same build: REQUIRED_DB_VERSION is checked at every start and refuses a mismatch in both directions, so a half-upgraded pair fails safe at startup where the two builds require different schemas; 6.3.6, 6.3.7 and 6.3.8 all require 6108, so that check does not catch a pair split between them. Health probes and the load balancer The probes are on the metrics listener, enabled on both nodes. Since 6.3.4 these are stored settings: set them in the Control Panel (Windows), over /api/v1/settings/ini, with hmctl, or on a Linux node whose REST listener is not yet on with hmailserver --set-setting NAME=VALUE. Then reinitialise (Apply now, or POST /api/v1/server/reinitialize) or restart. [Settings] MetricsServerPort=8080 MetricsServerBindAddress=0.0.0.0 MetricsServerAuthToken=<32+ random characters> ShutdownDrainSeconds=30 Point the health check at /readyz. It answers 200 only when the server is Running and the database is connected, and 503 while stopping, while draining, or when the database is lost. The passive node's service is stopped, so its connection is refused and it is never routed to. /metrics answers 503 on a non-loopback bind until MetricsServerAuthToken is set; the probes need no credential. ShutdownDrainSeconds defaults to 0, which waits for nothing. Set it. The drain is /api/v1/server/drain, on the REST listener: GET its state, POST to enter, DELETE to leave. Monitoring and health has the three probes and the listener's rules. Two matter here: MetricsServerBindAddress takes an IP literal and nothing else, since a host name keeps the listener, and so the probes, from starting; and the listener serves one connection at a time, so no sub-second health check. Building the standby Install the same build as the primary, with the same drive letter and directory layout. Point its [Database] section at the same database server, verify that it connects, then stop the service and set it to Manual. Set up the store: replication from primary to standby, or the shared directory. Record the credential list somewhere that survives the primary. Set the administrator password, copy PasswordPepper, and import the stored-secrets key. Rehearse the failover at least once before it is real. Planned failover The drain as a state of its own and UpgradeOnStart are new in 6.3.4. On 6.3.3, step 1 is a plain stop with ShutdownDrainSeconds set. Drain A: POST /api/v1/server/drain (manage_server), or the Drain button in the Deck's update view. A closes its SMTP, IMAP, POP3 and ManageSieve listeners, pauses its delivery queue, and reports /readyz 503 draining. A new connection is refused at the socket, which every MTA retries. REST, metrics and web services stay up. Watch GET /api/v1/server/drain until drained is true. Stop A. Verify A is down: /readyz on A is refused. Stop the replication job, in whichever direction it runs, where the store is replicated rather than shared. Do it before B is started. The runbook has no such step, because it assumes a shared store; WarmStandby.md's failover has it. In a planned failover A's machine stays up, so a scheduled mirror pass from it would delete the mail B has just accepted. Move the VIP to B. Start B and wait for /readyz 200. If B's binaries are newer than the shared schema, B refuses to start (HM5011) unless its INI says [Database] UpgradeOnStart=1. Take the database backup before this step. One node at a time. Check the key on B: no HM7495, HM7492 or HM7493 in its error log. /readyz is 200 either way, so this is the check that finds a missing key. Confirm: a test message through the VIP, and /healthz reporting database: up and state: running. Then re-enter the route, fetch and relay passwords on B, as after an unplanned failover. Reverse the steps to fail back, the check of the key included; with a replicated store, follow Failback below, which resynchronises the store first. To take A off the air without a failover, do step 1 and then DELETE /api/v1/server/drain, which reopens the same ports. Unplanned failover The health check on /readyz fails; stop routing to A. Fence A: power it off, or isolate it from the storage and database network. Unreachable is not the same as dead. Stop the replication job, where the store is replicated. Move the VIP, or the DNS record or port forward, to B. Start the service on B. Its startup unlock now works for you: messages the dead primary held mid-delivery are released and retried. A message the primary had delivered but not yet deleted from the queue may be delivered again, which is the known, bounded cost. Re-enter the route, fetch and relay passwords on B. When A is repaired, bring it back stopped, as the new passive node. The two documents order the middle differently: the runbook moves the VIP and then starts B, and WarmStandby.md starts the standby and then moves the endpoint. Both put the fence first. Keep MX TTLs modest, 300 to 3600 s, as a standing decision. Sending servers retry for days, so mail queued remotely during the gap is delayed, not lost. Failback is the same with the roles swapped, plus one step first where the store is replicated: resynchronise the data directory backwards, because the standby has been accepting mail and the primary's store is now the stale one. Then stop the standby's service, replicate from standby to primary once more for the final delta, start the primary, move the endpoint back, re-enter the route, fetch and relay passwords on the primary, and set up the replication from primary to standby again. Verify, after either direction GET /readyz answers 200: the service is running and the database has answered a real round trip within 20 s. A message sent in from outside, read back over IMAP or POP3. A message sent out through each credentialed route. No ERROR entries in the application log since the failover. TLS on 25, 143, 110, 587, 993 and 995 presents the right certificate. What it does not do, and what has not been rehearsed No active/active, no sharding, no clustering code. The runbook puts them out of scope. Not seconds. Failover is minutes, and the timings on the wiki pages are the shape of a rehearsed failover, not a promise. No rolling restart on one node. A single node refuses connections for the length of an install. The drain's state is per process, so a pair drains one node at a time. The rehearsal was a Linux pair: an Ubuntu 26.04 standby, one PostgreSQL 18 database, an SSH tunnel standing in for a LAN route. Not rehearsed: a Windows-to-Windows pair, DPAPI itself, a file-replication product rather than a copy at the moment of failure, DNS or IP takeover, and failback. Where to read more The wiki pages: Warm Standby, with the worked commands and the recovery-window table, and High Availability Runbook, with the probe-lifecycle table and load-balancer configurations The documents: WarmStandby.md and HighAvailabilityRunbook.md The documentation page: A warm standby, active/passive high availability and Prometheus metrics and health probes On this forum: Upgrading to 6.3.4, Moving to new hardware, and Settings: what is not stock and when a save takes effect, for the settings store and [SettingsOverride]
  • 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
    136 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes some of what a client gets. IMAP COPY and MOVE by message number read their set in the client's numbering, and a set that names a message another session has just expunged is refused NO [EXPUNGEISSUED] for the client to send again; a COPY or MOVE that fails part way leaves the destination as it was; SEARCH, SORT and THREAD ask a criterion named again beside itself once; and FETCH BODY[N.MIME], and BODY[N] of a multipart, are the stored octets. POP3 accepts the UTF8 classic Outlook for Windows (2016 to 2024) sends after the login, until the session's first STAT, LIST, UIDL, RETR, TOP, DELE or RSET, so its downloads work again. SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account. The IMAP and POP3 listeners can 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. The send-as grant now bears on DKIM too: an authenticated submission is signed as another hosted domain only when its account may send as the address its From names - its own, an alias pointing at it, a mailbox that granted it the post right, an address it proved, or a masked address of its own - and is otherwise accepted and sent unsigned as that domain. 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, on IMAP, POP3 and SMTP alike, SCRAM-SHA-256 among them: a client that signs in with another spelling of the address, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in, since such a name fails as a name that does not exist fails and is counted as a failure. Correct the name in the client. See the 6.3.8 announcement. This topic is the server side of IMAP, POP3 and SMTP in hMailServer 6.3.4: what each listener advertises and which switch gates it, search limits and the full-text index, QRESYNC's expunge history, connection limits, shared mailboxes, and what 6.3.4 fixed that a client may notice. Know this first: a capability missing from an answer is nearly always a switch or the session's state, not a fault. Two IMAP switches ship off, and since 6.3.4 IMAP offers STARTTLS only before TLS and sign-in, and COMPRESS=DEFLATE only after sign-in. What a mail client needs is in Connecting a mail client; the ACL rights table is in Aliases, distribution lists, mailing lists and public folders. Seeing what the server advertises Ask the server, from any machine with OpenSSL. For SMTP type EHLO test.example.org; for IMAP, CAPABILITY: openssl s_client -connect mail.yourcompany.com:25 -starttls smtp -quiet openssl s_client -connect mail.yourcompany.com:993 -quiet On 6.3.4 ask IMAP before LOGIN and after: the answers differ by design. For POP3, connect to 995 the same way and send CAPA. Ports 993 and 995 exist only once you have created them (Connections & protocols → TCP/IP ports in the Windows Control Panel, or the Control Deck). IMAP: what is always there On every connection, as in 6.3.3: IMAP4 IMAP4rev1 IMAP4rev2 CHILDREN THREAD=ORDEREDSUBJECT THREAD=REFERENCES NAMESPACE RIGHTS=texk MOVE ID SPECIAL-USE CREATE-SPECIAL-USE UNSELECT UIDPLUS ENABLE STATUS=SIZE ESEARCH CONDSTORE QRESYNC LIST-EXTENDED SEARCHRES WITHIN UTF8=ACCEPT LIST-STATUS PREVIEW MULTIAPPEND REPLACE SAVEDATE METADATA OBJECTID BINARY LITERAL- APPENDLIMIT APPENDLIMIT is bare before authentication and APPENDLIMIT=<bytes> after. LITERAL- caps non-synchronising literals at 4096 bytes, so an oversized APPEND can still be refused with TOOBIG. A client opts in to IMAP4rev2 per session with ENABLE IMAP4rev2, which implies UTF8=ACCEPT. ENABLE recognises exactly four names: QRESYNC (implies CONDSTORE), CONDSTORE, UTF8=ACCEPT and IMAP4rev2. After ENABLE IMAP4rev2 the session drops the RECENT response and the [UNSEEN] code on SELECT and EXAMINE, drops RECENT as a STATUS item, and answers a SEARCH with no RETURN options as ESEARCH. IMAP: what is gated, and by what Capability Advertised when Shipped IDLE enableimapidle is on On QUOTA QUOTA=RES-STORAGE enableimapquota is on On SORT enableimapsort is on On ACL enableimapacl is on; it also switches the #Users namespace On AUTH=PLAIN, AUTH=SCRAM-SHA-256 EnableImapSASLPlain is on and authentication is available Off SASL-IR EnableImapSASLInitialResponse is on Off COMPRESS=DEFLATE IMAPCompressionEnabled=1, the client has signed in (since 6.3.4), and the session is not yet compressed On STARTTLS The port is STARTTLS-optional or -required, TLS is not active, and (since 6.3.4) the client has not signed in Per port LOGINDISABLED Authentication is refused on this cleartext connection (STARTTLS-required port, or RequireTLSForAuth on the IP range) Per range The rest depend on the connection: AUTH=SCRAM-SHA-256-PLUS only on TLS (and behind the same SASL switch), AUTH=XOAUTH2 AUTH=OAUTHBEARER with OAuth2Enabled and, unless OAuth2RequireTLS=0, only over TLS, AUTH=EXTERNAL on a verified client certificate that names an address, and UNAUTHENTICATE once signed in. IDLE reports changes in the selected folder (EXISTS, RECENT, EXPUNGE, FLAGS) as they happen. A folder another client has just created is seen at the next folder-list refresh, as there is no IMAP NOTIFY; that is IMAP, not a fault. QUOTA answers with one unnamed root, "", and a STORAGE resource in kilobytes from the account's maximum size; an account with no limit gets an empty list. SETQUOTA is refused, because the quota is the administrator's. Refusals of APPEND, COPY and MOVE for a full mailbox carry OVERQUOTA. SORT: with message indexing off, as it ships, SORT reads each message's header from disk. EnableImapSASLPlain gates SCRAM as well as PLAIN, despite its name. Roadmap.md says it gates the whole AUTHENTICATE command, while the Capabilities wiki gives OAuth2 and EXTERNAL conditions of their own: with it off, test XOAUTH2, OAUTHBEARER and EXTERNAL over IMAP before relying on them. COMPRESS DEFLATE (RFC 4978) compresses both directions of the rest of the session; a client that does not ask is unaffected. A second one is NO [COMPRESSIONACTIVE], STARTTLS under compression is BAD, and IMAPCompressionEnabled=0 withdraws it, the command then answering BAD like an unknown one. POP3 and SMTP: the same question POP3 CAPA always carries UIDL, TOP, RESP-CODES, AUTH-RESP-CODE, IMPLEMENTATION hMailServer, PIPELINING, UTF8 and EXPIRE NEVER. The SASL mechanisms depend on TLS, STLS on the port, and LOGIN-DELAY <n> appears only when Pop3LoginDelaySeconds is above 0 (0 ships). That key advertises and enforces a minimum interval between sign-ins, refusing an early one with -ERR [LOGIN-DELAY] …; on Windows it is the POP3 tab's card How often a client may check for mail. SMTP EHLO always carries SIZE <n> (the maximum message size in KB times 1024; bare SIZE when 0), 8BITMIME, PIPELINING, CHUNKING, BINARYMIME, SMTPUTF8, ENHANCEDSTATUSCODES, DSN and HELP. BINARYMIME is for local delivery: a recipient needing onward relay is refused at RCPT TO with 554 5.6.3. Conditionally: XCLIENT only when SMTPXClientEnabled=1 and the real peer is in SMTPXClientTrustedIPs; an untrusted peer that tries it gets 550. STARTTLS on a cleartext connection to a STARTTLS-optional or -required port. AUTH only when authentication is enabled, withheld on cleartext when the port requires STARTTLS or the range sets RequireTLSForAuth, and never on the ports in DisableAUTHList. Not a keyword, but decided the same way: the HAProxy PROXY protocol is accepted only with SMTPProxyProtocolEnabled=1 and from a peer in SMTPProxyProtocolTrustedIPs, and nothing in EHLO announces it. On SMTP and POP3 the AUTH line and its SASL responses take up to 12,288 octets; every other line keeps 510 (SMTP) or 500 (POP3). What 6.3.4 added to SMTP is refusals, not capabilities. Where the switches are set, and when a change applies What Windows Control Panel Elsewhere Applies IDLE, QUOTA, SORT, ACL, the two SASL switches, the connection limits Connections & protocols → Protocols, IMAP, SMTP and POP3 tabs COM Settings (IMAPIdleEnabled and its neighbours); REST PUT /api/v1/settings (imap_idle_enabled, imap_quota_enabled, imap_sort_enabled, imap_acl_enabled, imap_sasl_plain_enabled, imap_sasl_initial_response_enabled); the Deck's Settings view; hmctl settings set At once Message indexing Maintenance → Performance, Indexing tab COM Settings.MessageIndexing; PUT /api/v1/settings/indexing (enabled); hmctl settings, group indexing At once IMAPCompressionEnabled, IMAPSearchTimeout, IMAPSearchMaxMegabytes, IMAPExpungeRetentionRecords, Pop3LoginDelaySeconds, IndexerFullText and its tuning keys Cards on the IMAP and POP3 tabs; the Indexing tab COM Settings.SetIniSetting; PUT /api/v1/settings/ini/{name} (administrator password only); hmctl ini; on Linux hmailserver --set-setting NAME=VALUE. No Deck page When saved The Control Panel and COM are Windows-only. The last row's keys were the [Settings] section of hMailServer.ini; since 6.3.4 the database holds them, none of them waits for a reinitialise or a restart (docs/SettingsApply.md), and the server does not take a line edited in the file, so set them through an interface. Settings: when a save takes effect covers the store, HM5804, and a value stored by --set-setting while the service runs. The REST lines assume the listener on 127.0.0.1:8443, as the REST API wiki's examples do; RestApiPort ships at 0 (off) and waits for a reinitialise. # Switch IDLE off; in effect when the answer comes back curl -u Administrator:your-admin-password -X PUT -H "Content-Type: application/json" \ -d '{"imap_idle_enabled":false}' http://127.0.0.1:8443/api/v1/settings # A [Settings] key: the administrator password only, never an API key curl -u Administrator:your-admin-password -X PUT -H "Content-Type: application/json" \ -d '{"value":"120"}' http://127.0.0.1:8443/api/v1/settings/ini/IMAPSearchTimeout # On Linux, then a restart so the running server takes it sudo hmailserver --set-setting IMAPSearchTimeout=120 sudo systemctl restart hmailserver Search: two ceilings and the full-text index Without the full-text index, SEARCH BODY and TEXT read every message in the mailbox, attachments whose media type is text included (decoded, bounded at 1 MB). Two ceilings, measured from the moment a command starts and never re-armed by progress, bound SEARCH, SORT and THREAD: IMAPSearchTimeout (60 seconds) and IMAPSearchMaxMegabytes (2048 MB of message content read); 0 turns either off. They are the IMAP tab's card IMAP search limits, and README.md says to raise them for mailboxes of several hundred thousand messages. What a client gets at a ceiling is stated two ways. Roadmap.md records NO with RFC 9051 LIMIT and no untagged result, so a cut-short list cannot pass for a complete one; README.md's comment beside IMAPSearchTimeout says the search returns the matches found so far. Until that is settled, treat a search that hit a ceiling as incomplete, and let nothing act on its result in bulk. The full-text index is off, and needs two switches: IndexerFullText=1 indexes nothing unless message indexing (Settings.MessageIndexing) is on too. It costs a term table of a few kilobytes per message and a backfill pass over every message already delivered, which is why no upgrade turns it on. The index can only prove that a message cannot match; everything else is read and scanned as before, so results are identical on or off. The webmail's search and eDiscovery use the same filter. Key Default What it does IndexerFullTextBatchSize 250 (1 to 100000) Messages one backfill pass reads before the cursor is saved and the thread pauses IndexerFullTextMinTokenLength 3 (3 to 64) The shortest search string the index answers for IndexerFullTextMaxTokensPerMessage 2048 (64 to 1000000) Distinct terms before a message is marked always-scanned instead of indexed The Indexing tab's Index now, and show how far behind it is and Discard the index and rebuild it are POST /api/v1/settings/indexing/index and /clear over REST, and Index() and Clear() over COM, where TotalIndexedCount against TotalMessageCount shows a rebuild's progress. With mailboxes encrypted at rest, the indexed count stays short by their messages for good: those are never indexed. QRESYNC: how much expunge history is kept IMAPExpungeRetentionRecords keeps the newest 5000 expunge records per mailbox (0 keeps all), pruned every twelve hours and once at service start; on Windows it is the card Change history for synchronising clients. The newest is never removed. A client whose last sync is older than what is kept gets a full resync, never a wrong one: every UID in its range that the mailbox does not hold. An installation upgraded from a release older than 6.2.23-alpha1 prunes the table in one pass at its first start. README.md says 0 defers that prune, and an IMAPExpungeRetentionRecords line of 0 added to [Settings] before the upgrade is taken: 6.3.4's first start adopts from the file a key the database has never held, and before 6.2.23-alpha1 the database held this one only if the file had the line. Restart the old service once after adding or changing it (Upgrading hMailServer wiki, 18.3, step 6): CHANGELOG.md's note that a line edited to prepare an upgrade is put back concerns a key the database already holds, and that restart settles it. Set it back to 5000 afterwards through an interface; Upgrading to 6.3.4 has the rest of the order. Connection limits and what guards them MaxSMTPConnections, MaxIMAPConnections and MaxPOP3Connections are on the Protocols page (REST max_smtp_connections, max_imap_connections, max_pop3_connections). None has a limit by default; SMTP and IMAP ship at 0, no cap. The Services tab turns a protocol off altogether (ServiceSMTP, ServiceIMAP, ServicePOP3). Watch hmailserver_sessions{protocol} on /metrics (off until MetricsServerPort is set, which waits for a reinitialise) or sessions in GET /api/v1/status. Around the limits: auto-ban counts failed sign-ins per IP across SMTP, IMAP, POP3, ManageSieve and REST (on: 3 in 30 minutes, a 60-minute ban); LogonTarpitSeconds (off) delays each failed sign-in's refusal; a second POP3 session on one mailbox gets -ERR [IN-USE]; and since 6.3.4 an IMAP LIST or LSUB pattern over 8,192 characters, or more than 32 patterns in one LIST, is answered BAD. The submission rate limit and the tarpits are in IP ranges, auto-ban, lockout and tarpits; refusing a connection by country is in allow and block lists and country blocking. Opening a mailbox to a colleague Shared and delegated mailboxes are IMAP ACL (RFC 4314). The owner opens a folder to another account, and it appears to that account under the Other Users namespace as #[email protected]. No administrator step is needed: any account whose client speaks SETACL can do it, and the audit trail records no row for it. Leave ACL on. With it off, #Users stops existing (NAMESPACE advertises NIL), public folders are open to everyone, and the send-as grant is not honoured. The owner grants rights from an IMAP client with ACL support. An administrator can do it over REST: GET on /api/v1/accounts/{address}/folders gives the folder's id, and /api/v1/accounts/{address}/folders/{id}/permissions lists, adds, changes and removes entries (since 6.3.3; writes need a full key). On Windows, COM has IMAPFolderPermissions.Add. The grantee is an account, a group (/api/v1/groups, since 6.3.4), or Anyone, which is every authenticated account on the server. Grant l, or the folder does not exist for the colleague. r reads, s changes read state, i, t and e file and delete, and p on the INBOX is the send-as grant. Grant on each top-level folder needed. A right is decided at the nearest folder up the owner's tree that has any entries, and entries are not merged: a grant on INBOX reaches its sub-folders that have none of their own, not the Sent folder, and one entry on a sub-folder cuts it off from the parent's. Over SMTP the p grant counts only while SmtpAuthenticatedSenderCheck is on, and it ships off: until then nothing constrains an authenticated MAIL FROM, so any account can send as the mailbox, or as anyone else. Turned on, it applies to every authenticated session on the server; the addresses it then allows are in who may send as whom. In the colleague's client, a pane of subscribed folders shows only the ones the owner subscribed to; the webmail shows them under Shared by <owner>. What follows from sharing: \Seen is stored on the message, not per person: with s, a delegate marks a message read for everyone; with lr alone they leave no trace, and their client cannot track what they have read. Since 6.3.4 an APPEND into a #Users folder is charged to the owner's quota, as COPY and MOVE already were. A delegate reads a folder of a mailbox encrypted at rest only while the owner's key is open; otherwise a FETCH that reads the message answers NO [UNAVAILABLE], and copying out is refused. In the webmail a read-only share refuses a flag or a delete with 403, a message cannot move between one's own mailbox and a shared one, and the owner cannot grant access from the page. Since 6.3.4 a restore puts each folder's access grants back. Every LIST runs one more query while ACL is on. What 6.3.4 fixed that a client may notice Where Dovecot's imaptest found IMAP answering other than the RFCs say (\Seen without .PEEK, APPEND byte for byte, SORT and THREAD order, among others), Connecting a mail client has the list. The rest: What a client did What it gets since 6.3.4 Sent STARTTLS after LOGIN, or inside TLS BAD, and no STARTTLS in CAPABILITY Sent COMPRESS DEFLATE before signing in NO Appended over a compressed connection, data inflating past the 250,000-octet receive buffer The whole message, where it was stored cut short or never finished Read ENVELOPE with a backslash in Date, or BODYSTRUCTURE with a quote or backslash in a part's parameters A structure that parses: the date's backslashes left out; quotes and backslashes escaped, a control character sent as a space Fetched a message the server cannot read NO [UNAVAILABLE] for a closed key; a damaged one (HM7350) left out, the rest fetched and the command ending NO - not OK with an empty ENVELOPE Changed one message's flags from two sessions at once Both changes kept; STORE ... (UNCHANGEDSINCE n) tested in the same step Went on sending a MULTIAPPEND's later literals after a refusal, one over the maximum message size (and 1 MB) The connection closed with BAD [TOOBIG], where it was read whatever its size Fetched a message only an unreachable object store holds IMAP NO [UNAVAILABLE], POP3 -ERR [SYS/TEMP] (object storage is new in 6.3.4, and off) Two heap-corruption aborts are gone as well (two sessions on one message's keywords; LIST beside folder changes). Most of these were in 6.3.3 too. What it does not do No APOP or CRAM-MD5 on POP3, and THREADID is answered NIL. Delegation is not measured in Thunderbird or Outlook, and an Outlook IMAP profile never acts on the Autodiscover answer's delegated mailboxes; the #Users namespace is what works. Where to read more Wiki: Capabilities and Configuration, Settings Reference (9.12), Connecting a Mail Client, Aliases, Lists and Public Folders (13.3), Control Panel Reference (7.5), Security Hardening (17.5, 17.7), Changes Since 6.2.10 (4.3, 4.4). At the 6.3.4 tag: SettingsApply.md, SendAs.md, EncryptionAtRest.md, DeckParity.md, README.md, CHANGELOG.md (6.3.4, IMAP, POP3 and the webmail), Roadmap.md. The public documentation: 34.3 IMAP details worth knowing, 30.3 IMAP folders and permissions. On this forum: Upgrading to 6.3.4, The Control Deck, The REST API: turning it on, Message storage: object storage and encryption at rest, What runs out first: pools, connections, large folders and Monitoring and health. A client that does not get on with these answers is a bug: a GitLab issue or, without a GitLab account, an email to contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, filed as a confidential issue (SUPPORT.md). A vulnerability goes only by SECURITY.md's routes.
  • 0 Votes
    1 Posts
    140 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) moves the Control Panel's pages: Updates is a page of its own under Maintenance, the sidebar has Compliance and Virus scanning groups, API & monitoring is Monitoring & alerts, the PROXY protocol and XCLIENT are a card on Incoming relays, and Ctrl+K takes every old title and route to its page. A Control Panel signed in to a server on another computer now shows and saves the server's hMailServer.ini [Settings] values. The anti-virus action named Delete infected attachments only in 6.3.6's Control Panel is Remove all attachments and deliver the text in 6.3.7's. The installers carry the .NET Desktop Runtime 10.0.13, Microsoft's security release of 13 October 2026, on which the Control Panel runs, but install it only where no .NET 10 Desktop Runtime is installed at all: an upgraded server keeps the 10.0.12 that 6.3.4, 6.3.5 or 6.3.6 installed, so bring it to 10.0.13 or later through Microsoft Update or Microsoft's installer. See the 6.3.7 announcement. The hMailServer Control Panel (hMailCP.exe) is the Windows desktop administration application: a .NET 10 WPF program, x64 only, that talks to the server over the COM API. That is why it can administer a server on another machine over DCOM, and why there is none on Linux, where the Control Deck, the REST API and hmctl are the administration surfaces (hMailServer on Linux: what you get and what you do not). This topic covers what it writes and where, connecting, the 65 pages with the store behind each, and, since 6.3.4, the mark under each field that says when a saved value applies, the pending bar, the Levels tab and the help pane. The one thing to know first: since 6.3.4 the Control Panel writes every [Settings] key over COM into the database, even when it runs on the server itself, and a Control Panel with no connection says it cannot change such a setting rather than writing to the file, where a stored key's line is ignored in favour of the database's value. What it holds, and what it writes where The Control Panel holds no configuration of its own. Its per-user state (window bounds, theme, language) is under HKCU\Software\hMailServer\ControlPanel. The server is reached over COM. Files on the server (hMailServer.INI, hMailServerApiKeys.ini, the log folder, the event script, certificate and key files) are reachable only when the Control Panel runs on the server itself. Since 6.3.4 the database is the store of the [Settings] keys, and the file holds a copy (where each setting is kept, and what stays in the file). The Control Panel reads those keys over COM, falls back to the file only when it has no session, and writes them over COM only; a [Settings] line edited by hand in the file changes nothing, and the error log names it as HM5804. [Database], [Directories] and [LDAP] are still read from the file only, and [Database] and [Directories] are deliberately not reachable over COM. Since 6.3.4 a new installation runs the service as NT SERVICE\hMailServer; an upgrade keeps the account it had (the service account, and moving to or from it). hMailServer.INI is read-only to that account on purpose, so its copy of the settings goes stale there. For the Control Panel that changes four things, each refused with a sentence rather than failing quietly: What Under the virtual account Settings saved on any page Unchanged, since they are kept in the database; only PasswordPepper, kept in the file only, is refused The Administrator password card, and enrolling or removing the administrator's second factor Refused. For the password the sentence names hMailServer.exe /SetAdministratorPassword from an elevated prompt The data, log and temporary directories Refused: an administrator changes the file and approves the new folder for the service Creating or switching the database Refused before anything is made Maintenance → Server limits & expert settings carries a Windows service account card: what the service actually runs as, and three buttons, each run after the Windows elevation prompt: Grant access (hMailServer.exe --confirm /GrantServiceAccess), Move to NT SERVICE\hMailServer and Move to LocalSystem. The live update's apply and the auto-ban's firewall rules need LocalSystem, and are refused under the virtual account with a sentence. From 6.3.6 a service running as NT SERVICE\hMailServer downloads and verifies an update, and an administrator installs it with Install update on the Server status page of the Control Panel on the server, with one Windows prompt; on 6.3.4 and 6.3.5 Install update cannot do it, and the administrator runs the downloaded installer by hand. Connecting Field What to enter When it is wrong Server localhost, or the remote server's name or IP; an empty box means localhost A name that does not resolve, or does not answer on TCP 135, gets a sentence saying so. The check has a four-second budget, so a mistyped host is a pause, not a hang User name Administrator. A mailbox address also authenticates, but only a server administrator can read the settings "Authentication failed. Check the user name and password." The server never says which of the two was wrong Password The administration password set during installation The same message "hMailServer COM API is not registered on the target machine." means you reached the machine but hMailServer is not installed there. "Unable to reach the server (RPC unavailable)." is HRESULT 0x800706BA: firewall, DCOM permissions, or the service is not running. Two second factors, and they are not the same thing. The server-enforced one, whose secret is [Security] AdministratorTotpSecret in hMailServer.ini, protects every client of the administrator credential: this Control Panel, COM scripts, and the REST API through its X-hMailServer-OTP header. The older one, AdminTotpSecret under HKLM\SOFTWARE\hMailServer, protects this installation of the Control Panel on this machine and nothing else. The code is asked for once, never in a loop, because a wrong password and a missing code look identical from outside. hMailCP.exe /connect <host> <user> <password> signs in as the window loads; the password is on the command line, where anything that can list processes can read it. Connecting opens the Dashboard, not Welcome. COM sign-in is deliberately outside the auto-ban and the per-name lockout, so guessing an administrator's name over IMAP cannot lock that administrator out. The window and the tree Since 6.3.4 the Control Panel is drawn on one design system. The window has three rows: the title bar; a command bar holding the sidebar toggle, a search field that opens the Ctrl+K palette, a server status pill (Not connected, Connected, Reconnecting…, Connection lost), the theme toggle and the administrator's menu; then the sidebar beside the content. Below 1,000 pixels of window width the sidebar collapses to a rail of one glyph per group. What did not change is the part you may have scripted: the navigation map, every page title, tab header, setting caption and automation id (nav-<key>) are what they were. The sidebar holds 65 pages in eight groups in 6.3.4 (55 in 6.3.3), never more than two levels deep: Monitoring & troubleshooting, Accounts & domains, Mail flow & delivery, Spam & virus filtering, Connections & protocols, TLS & certificates, Access & abuse protection and Maintenance, with Welcome, Dashboard and About outside them. The ten pages new in 6.3.4 are Suppression list, Journaling and dual delivery, Audit trail, Discovery, Administrator roles, Remote domains, Allow and block list, Greylisting bypass list, Domain white and black list and Setting profiles. Search (Ctrl+K) answers three questions in one ranked list: a page name or any alias; what you are trying to do, from 97 outcome phrases such as "stop spam"; and a setting name or key, from an index of 448 settings in 6.3.4 (393 in 6.3.3). It also resolves 54 old navigation paths, so Settings > Security > Auto-ban & SSL/TLS opens Auto-ban. Welcome. Since 6.3.4 it offers a throwaway sample domain, example.test, and a first-run tour of seven stops that never acts and is never modal. Every page, by what it writes The wiki lists each page with its navigation key, which never changes when a page is renamed. Grouped by the store behind them, the 65 are: What the page writes Pages Applies Nothing: no settings behind the page (6) Welcome, Dashboard, Live logs, Diagnostics, MX query, About not applicable Read-only, or actions only (12) Server status, Delivery queue, Message trace, Audit trail, Discovery, External setup, DNS records, Server sendout, Spam filtering overview, Quarantine, Virus scanning overview, Transport encryption overview Actions at once The COM settings tree, a COM collection, or the database through COM (25) Diagnosing stalled mail, Suppression list, Journaling and dual delivery, Domains, Public folders, Groups, Administrator roles, Routes, Remote domains, Incoming relays, Rules, Server messages, SURBL servers, DNS blacklists, White list, Blocked senders, Allow and block list, Greylisting white list, Greylisting bypass list, Domain white and black list, Blocked attachments, TCP/IP ports, SSL certificates, IP ranges, Setting profiles At once; a profile's entries as each setting's own mark says [Settings] keys, in the database since 6.3.4 (7) API & monitoring, Web services & autoconfiguration, DNS resolver, Certificates (ACME), Transport security, Authentication, Server limits & expert settings As each field's mark says COM and [Settings] keys together (11) Logging, Delivery of e-mail, Anti-spam settings, Anti-virus settings, Protocols, SSL/TLS, Administrative access, Auto-ban, Backup & restore, Performance, Advanced COM at once, except the syslog card on Logging (since 6.3.4), whose values the sink takes when it starts, at a reinitialise or a service start; the keys as each field's mark says A file on the server (4) REST API keys (hMailServerApiKeys.ini, re-read on every request), Directory authentication (LDAP) and Directory synchronisation ([LDAP], re-read within two seconds), Event scripts (the script file; Save & reload reloads the engine) Without a restart What the table cannot say: Three overview pages are deliberately read-only: Spam filtering overview, Virus scanning overview and Transport encryption overview. They show what no single editor can: a virus scanner that is switched on but cannot run is treated as no virus found, so infected mail is delivered as though examined; a TLS port with no certificate fails to start rather than falling back to plaintext. Discovery (since 6.3.4) changes nothing and is the one page that reads the REST listener, because discovery has no COM interface. It needs RestApiPort set, a certificate it can check for an address beyond loopback, and an administrator without a second factor. A case is opened, searched, held and exported on the Control Deck (eDiscovery and legal holds). Journaling and dual delivery and Audit trail are the server administrator's only. Message trace and Quarantine are inert unless MessageTraceEnabled and QuarantineEnabled are set, and Blocked attachments unless attachment blocking is on in Anti-virus settings. Server limits & expert settings also edits [SendingLimits] and [SendingLimitsOverrides], which stay in the file and are re-read within seconds. Performance also edits the local [Database] rows, and since 6.3.4 has a Message store tab: MessageStoreCompression, off by default, and MessageStoreDedupMinKB, 256. The dialogs. Since 6.3.4 the domain dialog has fourteen tabs (seven in 6.3.3) and the account dialog fifteen (twelve), each ending in Levels. The mark under each field, and the pending bar Since 6.3.4. On 6.3.3 a save of a page whose settings live in hMailServer.INI offers to restart the service. Against a 6.3.4 server a save publishes, and under every field kept in the settings store a small line says when the value takes effect (which keys carry each mark, and what a reinitialise takes The line Meaning Applies on save In effect when the save returns After reinitialize A listener's port or bind address, a TLS context, a work queue's size: waits for Apply now After a restart ServiceAccountName and ServiceAccountPassword, which are retired. The [Database] rows on Performance carry the same words Every key's mark comes in one COM call when such a page is opened, Settings.IniSettingAppliesAll. A page's save writes only the fields whose value changed, because every write publishes, and it says how many of its keys still wait. The pending bar sits at the foot of the window: "N saved setting(s) wait for a reinitialize", with Apply now. The list behind it is Settings.PendingIniSettings, recomputed by the server at every request, so a value put back before anything applied it is not pending. Apply now is Application.Reinitialize over COM: every service stops and starts again in place, with no service restart and no UAC prompt. It asks first. While the server is draining for maintenance the question says that the listeners open again and delivery resumes, because a reinitialise ends the drain. It takes every reinitialize key and not a restart key. When a restart key waits too, what it says afterwards is how many still wait for a service restart, not that the saved settings are in effect. The Control Panel's COM session survives it. The Control Deck's session does not. Three things the pages will not do. Return a key to its default. Clearing a box stores an empty value, which a number reads as 0. The default comes back through DELETE /api/v1/settings/ini/{name}, hmctl ini delete <name> or COM Settings.DeleteIniSetting(name). Change a setting the environment names. A setting set by HM_SETTING_<key> or HM_PROPERTY_<name> (configuration from the environment) is shown disabled, with Set by the environment in place of the mark. Claim more than an older server does. Against a 6.3.3 server, which lacks IniSettingAppliesAll and PendingIniSettings, every field kept in the store is marked After a restart, a save says the service must be restarted, and no pending bar is shown. The Levels tab. Since 6.3.4 the domain and account dialogs list the twenty-seven settings a domain or an account may hold its own value of, ticked where this level holds one, with "In effect: ..., from the ..." beside each. Save levels now writes what changed over COM at once, reads every line back, and shows the server's sentence when it refuses one. Against a server older than schema 6088 the tab says the server has no levels. The help pane Since 6.3.4. Every page has a ? button in the command bar, and F1 does the same: a right-hand pane opens beside the page with what the operator documentation says about it, scrolled to the right heading. It has the document's headings as a list, a search box over the open document, and two links at the foot: Open on the wiki and All documentation. The text is built in. It is the repository's hmailserver/docs/*.md, embedded in the Control Panel as resources (each named Help.<Name>.md, with README.md and DeckParity.md excluded). The Control Panel must work with no REST listener, so it carries its own copy and reads it over no route at all. What is thinner than it sounds. A page whose subject has no document of its own (the TCP/IP ports, the certificates, the groups) opens the nearest one. The search is within the open document only, not across all of them. Open on the wiki is HelpWikiBaseUrl followed by the document's name. The wiki's pages are not named after the documents, so for most documents that link finds no page yet; the pane's own text is the complete one. HelpWikiBaseUrl is a row of hm_settings, shipped as https://gitlab.com/hmailserver/hmailserver/-/wikis/ and edited on the Advanced page under General. A value that is not empty and not an absolute http:// or https:// URL is refused, and empty hides the link. The Control Deck and the webmail have the same pane, served by the server from GET /api/v1/help/{page}. Administering a server on another machine Type the server's name into the Server box and the Control Panel connects over DCOM. That covers everything the COM API can reach. What degrades is anything that means reading a file on the server: On a remote session What you see Live logs "Log folder not found on this machine (live logs need a local server)." REST API keys The key store is a file beside hMailServer.INI; the page needs it The "Listening" badge on TCP/IP ports Every port reads "Unknown": the TCP table probed is this machine's Certificate and private-key inspection "cannot be checked from here" Event scripts The script is loaded from and saved to local disk The [LDAP] and [Database] editors Those sections are read from the file only Editing the [Settings] pages from another machine is not confirmed for 6.3.4. The wiki's Control Panel page says, in section 5.5 on remote administration, that such a page answers "hMailServer.INI was not found on this machine. These settings can only be edited on the server itself." Its 6.3.4 note in section 5.0 says that these keys are now read and written over COM, and that section 5.5 describes 6.3.3 and was not re-checked against the change. Test it on your own installation before relying on it; if a remote page refuses, use Remote Desktop or the REST API. Since 6.3.7 a Control Panel signed in to a server on another computer shows and saves the server's hMailServer.ini [Settings] values, and no longer reads or writes this computer's file for the [Database], [SendingLimits] and [LDAP] values, which it cannot read from there. When the Control Panel itself is the problem Symptom What it means What to do It does not reconnect after you stop the service Deliberate: it rebuilds only a link that broke while the service was up, because activating the COM class would start a service you stopped on purpose Start the service, then use any page An unexpected-error dialog An unhandled UI fault See %LOCALAPPDATA%\hMailServer\ControlPanel\control-panel-errors.log Signing in freezes the window The host accepted TCP 135 and then stalled inside DCOM Check DCOM permissions and the firewall on the server With a server-enforced second factor, a silent reconnect cannot produce a fresh code, so after a service restart you sign in again through the prompt. What it does not do, and where to read more There is no Control Panel on Linux and no COM there; the Control Deck, the REST API and the hmctl client are the surfaces. The Control Panel never returns a key to its default, and it has no page for an account's kind (a person, a meeting room or equipment), encryption at rest, object storage or working an eDiscovery case: those are the Control Deck's and REST's. The public documentation's chapter 20 lists all 65 pages of 6.3.4 by group, and the wiki's Control Panel Pages is the source it follows. Connecting, the shell, the tour and the help pane: https://gitlab.com/hmailserver/hmailserver/-/wikis/The-Control-Panel Every page with its key and its store: https://gitlab.com/hmailserver/hmailserver/-/wikis/Control-Panel-Pages Every page, dialog and field with the setting behind it: https://gitlab.com/hmailserver/hmailserver/-/wikis/Control-Panel-Reference When a saved setting takes effect: https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SettingsApply.md The help pane: https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Help.md The public documentation: https://www.progressiverobot.com/hmailserver-documentation/#5-the-control-panel-your-first-look and https://www.progressiverobot.com/hmailserver-documentation/#20-every-control-panel-page
  • 0 Votes
    1 Posts
    137 Views
    ProgressiverobotP
    This topic is about the settings themselves, whichever interface you use: where a setting is stored, which of yours are not what 6.3.4 ships with, when a saved value is in effect, and how the twenty-seven settings a domain or an account may hold its own value of combine with the server's. The one thing to know first: since 6.3.4 the database is the settings store. A [Settings] line edited by hand in hMailServer.ini changes nothing; at the next start the server uses the stored value, names the key in the error log as HM5804, and, where the service may write the file, puts the line back. Everything below describes 6.3.4 unless it says otherwise; a server on 6.3.3 still reads the file and applies a change at the next start. Two stores, and how a setting is changed The table hm_settings. Everything the COM Settings object has always carried, such as the anti-spam thresholds and the delivery options. Since 6.3.4 every new setting goes here, and no key is added to [Settings]. 6.3.4 added 193 rows for its features. The [Settings] keys. 241 keys read by the settings loader, plus UseLanguage, read per call. On 6.3.3 they live in the file, mirrored into hm_inisettings. Since 6.3.4 (schema 6042) hm_inisettings is the store and the file's section is a copy, which the server keeps current where the service may write the file. Where the service cannot write the file, the copy goes stale. That is a new Windows installation, whose service runs as NT SERVICE\hMailServer, and a Linux package installation, where /etc/hmailserver/hMailServer.ini is 0640 root:hmailserver. There the copy is not refreshed and an edited line is not put back; the database's values are still the ones used. The server reports this once a start in the application log, not as an error, and ServiceAccount.md says the same of the Linux packages; the wiki's Installing on Linux page names HM5806 at a save and HM5803 at every start for them instead. On such a server read a setting from the store, not from the file. The migration is the first start. A key the table has never seen is adopted with the file's value, and there is nothing to run by hand. A line edited after the old server last started, including one edited to prepare the upgrade, is taken for an edit and put back, so make such a change after upgrading. A 6.3.3 server refuses a database at schema 6042 or later with HM5011; see Upgrading to 6.3.4. What stays in the file. [Database], [Directories], [Security] and [GUILanguages] are the bootstrap, read before the database is open and never in the store. [LDAP], [SendingLimits] and [SendingLimitsOverrides] stay in the file. PasswordPepper is kept in the file only, never in the database and never in a backup. [SettingsOverride] is the one way to make the file win, for a database that is unreachable or holds a value the server will not start on: a key written there is applied over whatever is stored and named in the error log as HM5807 at every start, because no page shows it and no backup carries it. Storing a value for a key an override shadows is accepted and reported as HM5808. Surface Set a [Settings] key Return it to the default Control Panel (Windows only) the key's editor, written over COM even on the server itself No. Clearing a box stores an empty value, which a number reads as 0 REST PUT /api/v1/settings/ini/{name} with {"value": "..."} DELETE /api/v1/settings/ini/{name} hmctl, over REST hmctl ini set <name> <value> hmctl ini delete <name> PowerShell module Set-HmIniSetting none COM (Windows only) Settings.SetIniSetting(name, value) Settings.DeleteIniSetting(name) Linux command line, as root hmailserver --set-setting NAME=VALUE, with no listener running. A secret's value is refused on the command line: give the name alone and type the value at the prompt none Control Deck only SmtpTarpitCount, SmtpTarpitDelaySeconds, UseLanguage and RewriteEnvelopeFromWhenForwarding none The REST ini routes take the administrator password only; an API key of any scope is refused. Since 6.3.4 a setting the environment names, as HM_SETTING_<key> or HM_PROPERTY_<name> (configuration from the environment), refuses an administrator's change, 409 over REST, until a start without the variable. What this server has changed from stock Since 6.3.4. One list of every setting whose value is not the one this version ships with, with the shipped value beside it. The example in ChangedFromStock.md: STORE NAME VALUE DEFAULT ini LogLevel 5 9 ini MaxLogLineLen 2000 500 ini SMTPDMaxTimeout 600 1800 ini OAuth2HmacSecret (set) ini DnsServer 10.0.0.1 (forced from [SettingsOverride]) database smtpnoofretries 6 4 database maxsmtpconnections 200 0 7 of 391 settings are not what this version ships with. Where What to ask Control Deck Changed from stock, in the navigation beside Settings REST GET /api/v1/settings/changed Command line hmctl config changed PowerShell Get-HmChangedSetting COM Settings.GetChangedFromDefault The REST route and the COM call answer the same document. store is ini (hm_inisettings) or database (hm_settings). What "default" means. The value the server behaves as though the setting has when nothing has been stored for it, not what a fresh installation's files contain. hMailServer.ini ships with lines the packaging wrote, such as RestApiBindAddress=127.0.0.1, and a line holding the shipped value is not a change; one holding another value is listed. An upgraded installation lists more than a new one. database defaults are what a database created by this version holds. An installation upgraded from an old schema got its values from the upgrade scripts of the time, and where those differ (maxmessagesize, usecache, protocolimap, smtpallowincorrectlineendings and half a dozen more) the older installation is genuinely different and is listed. A row nothing reads any more (useordb, usespamhaus, tarpitcount, filters, imapport, relaymode) is not reported at all. The marks on an entry. secret: a setting whose name says its value is a secret is listed by name, with (set) or empty, and never shown. A key file, such as RestApiPrivateKeyFile, is a path and is shown. known false: the server holds a value and ships no default for it, such as a key stored for a script of your own. hmctl prints (none: this server does not read it). overridden: [SettingsOverride] is forcing the value. Such a key is listed even when neither store holds anything for it; hmctl prints (forced from [SettingsOverride]). environment is variable or file, and hmctl prints (set by the environment). profile names the setting profile whose apply wrote the value, while the setting still holds it. Which values these are. The value stored, not the value the server settled on where the two differ: a PasswordHashIterations outside the range the server accepts is reported as HM5561 and the built-in work factor used; an unusable PreferredHashAlgorithm is reported as HM5528 (or HM5607, for Argon2id on a build whose OpenSSL has none). Who may see it. The administrator password, and nothing else. An API key of any scope is refused with a 401, and an account holding an administrative role, an auditor included, with a 403, because the list includes commands the server runs: AutoBanCommand runs as the service account. The feature changes nothing. It does not compare two servers (two hmctl config changed --json answers and diff do), and it does not say when or by whom a setting was changed; the audit trail does (GET /api/v1/audit?object_type=setting). When a saved setting takes effect Since 6.3.4. Until then every [Settings] key was latched when the service started. Now a save publishes: the store takes the value, the server rebuilds its settings from the rows, and every key whose readers can take a new value is in effect when the save returns, whichever door saved it. Every key carries one of three marks: Mark Keys What it means now 211 A save applies it reinitialize 28 Bound into something the servers build when they start, so it waits for a reinitialise: Apply now in the Control Panel or the Deck, POST /api/v1/server/reinitialize, or Application.Reinitialize over COM restart 31 Read once per process: 2 [Settings] keys and the 29 bootstrap keys (20 [Database], 5 [Directories], 3 [Security], 1 [GUILanguages]) The 28 that wait for a reinitialise. What is rebuilt Keys The REST listener RestApiBindAddress, RestApiPort, RestApiCertificateFile, RestApiPrivateKeyFile The metrics listener MetricsServerBindAddress, MetricsServerPort, MetricsServerCertificateFile, MetricsServerPrivateKeyFile, MetricsServerAuthPassword, MetricsServerAuthToken, MetricsServerAuthUsername The web-services listener WebServicesBindAddress, WebServicesHttpPort, WebServicesHttpsPort, WebServicesCertificateFile, WebServicesPrivateKeyFile The ManageSieve listener ManageSieveServerBindAddress, ManageSieveServerPort The SSL contexts of the SMTP, IMAP, POP3 and ManageSieve listeners and the outbound client TlsCipherSuites13, TlsKeyExchangeGroups, TlsSessionCacheSize, TlsSessionTicketsEnabled, TlsSessionTimeoutSeconds, TlsTicketKeyRotationSeconds ACME, the database pool, the fetch queue AcmeEnabled, AcmeCertificateDirectory, DatabaseStatementTimeout, MaxNumberOfExternalFetchThreads The 2 that wait for a restart are ServiceAccountName and ServiceAccountPassword, which are retired: since 6.3.4 hMailServer.exe /Register takes the account from its command line alone, and a stored value is shown as pending until the next start. The bootstrap keys are never in the store, so none is ever pending. Some keys are now because a subscriber re-latches them. MaxSubmissionsPerIPPerMinute and MaxOutboundPerDestinationPerMinute start each limit's one-minute window again, so a saved limit counts from when it was saved. A cache of data is not a setting: a changed DNSServer is used for the next lookup, and an answer already cached stays until its TTL runs out. Pending. A reinitialize or restart key whose stored value is not the value the server is running on is pending. The list is recomputed at every request and never remembered, so a value put back before anything applied it is not pending. Where What REST GET /api/v1/server/settings/pending answers [{name, stored, running, applies, secret}], and applies and pending come on every /api/v1/settings/ini/{name} answer. The administrator password's route COM Settings.IniSettingApplies(name), Settings.IniSettingAppliesAll, Settings.PendingIniSettings Control Panel Applies on save, After reinitialize or After a restart under each field kept in the store, and a pending bar with Apply now Control Deck The same words as a badge, and Apply now above its Settings view A name the loader does not read (UseLanguage, a key a script of your own reads, a misspelling) has no mark: COM's IniSettingApplies refuses it and REST answers applies: null. Apply now is a reinitialise. Every service stops and starts again in place, which takes every reinitialize key and not a restart key. It asks first. A reinitialise ends maintenance mode, and POST /api/v1/server/reinitialize answers {"reinitializing": true, "drained": true} when it ended a drain. The Deck's session ends with the REST listener it was using; the Control Panel's COM session survives. What a save does not do. It does not re-read the bootstrap sections or [SettingsOverride]; a reinitialise or a start re-reads the override section and announces it as HM5807. It does not reconcile the file against the table; that is a start's and a reinitialise's only. And a value stored by another process (another node on the same database, or hmailserver --set-setting while the service runs) reaches this server at the next save here, a reinitialise or a restart, because nothing watches the table. hmctl has no pending verb. On Linux the regression fixture alone proves the marks, with the three COM members answered over REST; the internal tester that swaps settings under load does not run there. One value, three levels Since 6.3.4 (schema 6088). Twenty-seven settings may be set for the server, for a domain and for an account, and every place that shows one shows what is in effect and where it came from. For an account: its own value, else its domain's, else the server's. Every setting on the list is exactly one kind: Kind How many The rule Who sets a level below the server Limit 15 A level below may tighten the level above and never loosen it. A looser value is refused when it is written; a value the level above has since tightened past is capped when it is read Anybody who administers that level: a domain administrator, a key restricted to the domain Gate 4 The deepest level that holds a value wins, either way The server administrator alone; a domain-restricted key is refused with 403 Preference 8 The deepest level that holds a value wins The domain's administrators, and a domain-restricted key The last column is the rule for the levels held in hm_settingoverrides. A field the domain already had keeps the authority it had, whatever its kind: the domain's maximum message size and the domain's retention are the server administrator's. Kind Settings Limits max_message_size_kb, session_idle_seconds, session_lifetime_seconds, session_one_per_device, session_bind_to_address, quota_grace_percent, quota_send_stop_percent, recall_days, recover_deleted_days, sending_max_messages, sending_max_recipients, reader_fetch_accounts_max, reader_sender_addresses_max, reader_masked_addresses_max, reader_offline_days_max Gates plus_addressing_enabled, openpgp_portal_enabled, zero_access_allowed, directory_user_editable Preferences quarantine_digest, quarantine_digest_hour, external_tag_subject, external_tag_header, impersonation_action, quota_warning_percent, country_policy, message_retention_days A server setting not on the list is the server's alone: its name is refused on the domain and account routes (400, unknown field) and by COM's SettingOverride. Of the [Settings] keys only QuotaWarningPercent has levels. Where each level is held. A field the domain or the account already had, such as the domain's MaxMessageSize or MessageRetentionDays, stays where it was, with the meaning it always had; its 0 or -1 is the level holding nothing. The rest are rows of hm_settingoverrides, where no row means "take the level above". An owner's rows are cached for sixty seconds; every write through the interfaces drops the cache at once, and so does POST /api/v1/settings/cache/clear or COM Cache.Clear. A row put in by hand that the interfaces would refuse is treated as holding nothing and reported as HM7311. Nothing changes for a domain or an account that holds no value of its own. Where What REST GET /api/v1/settings/levels lists the twenty-seven. GET /api/v1/domains/{domain} and GET /api/v1/accounts/{address} answer <name>_effective and <name>_source, and <name> where the level is held in the overlay. For a level held in the overlay a PUT takes a value of the setting's own JSON type, and null makes it inherit; for a level held in a field of its own null leaves it unchanged, and the field's own 0 or -1 is "take the level above" COM (Windows only) Domain.SettingOverride(name) and Account.SettingOverride(name), an empty string for "take the level above", written at once and not by Save; EffectiveSetting(name) and EffectiveSettingSource(name) hmctl hmctl domain set <domain> <name> <value\|inherit> and hmctl account set <address> <name> <value\|inherit> Control Panel (Windows only) A Levels tab on the domain and account dialogs, with "In effect: ..., from the ..." beside each setting and Save levels now Control Deck The same card in its domain and account editors A saved level applies at once, at every level and from every door, because the resolver is asked when the value is used (at MAIL FROM, when a session is checked) and never latched. Deleting, backing up and moving. Deleting an account or a domain deletes its rows; a delete that cannot remove them is reported as HM7310 for a domain and HM6109 for an account. The XML backup carries the rows, and on restore a row the build refuses is left out and reported as HM7312. A mailbox moved to another domain keeps its own values. What is thinner than it sounds. Attachment blocking, web push, passkeys and account recovery have no levels. The domain's session seconds and switches and its quota grace and send-stop shares could always be set looser than the server's and still can; the ceiling binds the account below them. The maximum message size is resolved at MAIL FROM and at IMAP APPEND; the EHLO SIZE advertisement, the webmail's own send and the send API stay the server's. The session policies are the browser sessions' (the webmail, the portal, the Deck); IMAP, POP3 and SMTP sessions have none. country_policy cannot be set to empty over COM, where an empty string means "take the level above"; REST's "" does it. Twelve of the twenty-seven are proved by what the account and its domain answer rather than by driving the behaviour per account, and the levels' own regression fixture is Windows-only. When a setting appears to do nothing Is it pending? A reinitialize key waits for Apply now and a restart key for a service restart. Was it typed into hMailServer.ini? The stored value wins and HM5804 names the key. Store it instead. Is the key spelled exactly as the code reads it, and is a boolean literally 1? An unknown key is the default, and only 1 is true: 2, yes and true all read as false. Does it need a companion? SRSEnabled needs SRSSecret, ACME needs AcmeDomains, autoconfig needs WebServicesHttpPort or WebServicesHttpsPort, and MTA-STS hosting needs the HTTPS one. Does the error log show HM5807 or HM5808? A [SettingsOverride] section is forcing a value. Where to read more The tour: https://gitlab.com/hmailserver/hmailserver/-/wikis/Settings-Overview. Every key with its default, validation and mark: https://gitlab.com/hmailserver/hmailserver/-/wikis/Settings-Reference. Where the two disagree, the reference is generated from the code and is right. The three documents behind this topic: https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/ChangedFromStock.md, https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SettingsApply.md and https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SettingLevels.md The public documentation's settings chapter: https://www.progressiverobot.com/hmailserver-documentation/#21-settings-reference-hmailserverini On this forum: the Control Panel on Windows, hmctl and the PowerShell module, the hmailserver command line, the Control Deck, Upgrading to 6.3.4 and hMailServer on Linux.
  • 0 Votes
    1 Posts
    136 Views
    ProgressiverobotP
    This topic covers the built-in backup, what its archive holds and leaves out, and restoring a server, one mailbox or one message. Know this first: a restore is destructive and has no transaction. It deletes every domain, every account and every public folder before it puts the archive's back, so whatever an account has that is not in the archive is deleted and not replaced. And the archive does not include your messages unless you tick the box. What one run writes One 7-Zip archive per run, named HMBackup YYYY-MM-DD HHMMSS.7z, in the configured destination: an XML index and, with Back up messages on, the whole data directory beside it. The data directory is C:\Program Files\hMailServer\Data by default on Windows and /var/lib/hmailserver on Linux, and what lives inside it travels with the messages: Sieve scripts and their state (<data directory>\Sieve), and ACME certificates and keys (<data directory>\ACME, unless AcmeCertificateDirectory moved them). Since 6.3.4, messages held in an object store (MessageStoreBackend) are read into the archive too: a store that cannot be read fails the backup, a restore puts the files back on the local disk, and the store's credentials are not carried. What it cannot hold: files the database only points at. Manually placed TLS certificates, private keys and DKIM keys are stored as paths. Restored onto a machine where those paths hold nothing, the server starts, serves no TLS and signs nothing. Keep them inside the data directory, or copy them yourself. What is in the archive Area Carried Domains Aliases, distribution lists and members, signatures, DKIM selectors and key paths; since 6.3.4 the domain's allow and block lists, its spam traps, the domain transforms, the S/MIME authority and the recovery key of a domain encrypted at rest Accounts Address, password hash and its algorithm, forwarding, vacation, signature, quota, admin level, anti-spam thresholds, TOTP secret Per account, every release Rules, fetch accounts with their passwords, application passwords as hashes and, with Back up messages, folders and messages with UID and flags Per account, since 6.3.4 Message keywords (the webmail's labels, second and third stars, follow-up flags, mute and pin), the address book, webmail preferences, scheduled sends and snoozes, file-link records, S/MIME keys, the password history, calendars with their events and tasks, the first-contact memory, safe and blocked senders, the mailbox key of an account encrypted at rest, the role assignment Public folders With the permissions on them. Backup.md states no condition; section 15.2 of the wiki lists them under Back up settings. A restore deletes every public folder first Settings, with Back up settings The hm_settings property set and the [Settings] rows of hm_inisettings; routes, IP ranges, global rules, TCP/IP ports, SSL certificate records, incoming relays, groups, blocked attachments, the DNSBL, SURBL and white lists; since 6.3.4 the remote domain policies and IP warm-up schedules Custom administrator roles With domains or settings; restored first, so the assignments accounts carry find their role Nothing identity-shaped is written. A restore gives every row a new id, which makes the archive portable between database backends; a reference that cannot be resolved drops that one row and names the account in hmailserver_backup.log. Secrets travel opened. Route, fetch-account and SSL private-key passwords are carried in a portable encoding and protected again on the restoring machine (since 6.3.8 one the encoding cannot carry, holding a character above U+00FF or longer than 248 characters, as an external account's OAuth token often is, is left out of the archive and named in the error log and the backup log as HM7280, to be entered again after a restore, where 6.3.7 and earlier wrote it empty); since 6.3.4 each TOTP secret and the [Settings] secrets (SRSSecret, OAuth2HmacSecret and the rest) are carried opened and sealed again under the restoring server's stored-secrets key, so a restore onto new hardware keeps every second factor. An unencrypted archive therefore hands those secrets to whoever can read the destination folder. In 6.3.3 and earlier the per-account rows marked since 6.3.4 are in no archive, and a restore deletes them. Until you upgrade, back up the database itself with its own tool (pg_dump, mysqldump, SQL Server's backup, or a copy of hMailServer.sdf with the service stopped), restore from that when recovering a whole server, and tell users before they find their address book and labels gone. An archive written by 6.3.3 or earlier restores on a newer server as it always did. What is not in the archive, deliberately Not carried Consequence of a restore [Database] and [Directories] in hMailServer.INI; the administrator password and its second factor Per machine. Keep a copy of the file, and never copy one machine's file over another's PasswordPepper (since 6.3.4) Kept in the file only. A restore onto new hardware needs the same pepper in that machine's file before an Argon2id or scrypt password can sign in; PBKDF2, SHA-256 and MD5 hashes, and the administrator password, do not use it The stored-secrets key (since 6.3.4) The archive does not need it. A database-level backup does: restored without the exported key, every second factor and [Settings] secret in it is unopenable. Export it once (hMailServer.exe --export-stored-secrets-key FILE, elevated, or sudo hmailserver --export-stored-secrets-key FILE) and keep it with the database backups, stored apart from them On Linux, the stored-secret key file <DataFolder>/.hmailserver-secret-key The archive does not carry it. Its passwords travel in a portable encoding and do not need it, but a domain's S/MIME authority key, and the mailbox key of an account encrypted at rest that has not yet signed in, are carried as envelopes that open only where this file is: restored without it, the authority must be rotated before it signs. A database-level backup needs it as well: back it up with the database, because a secret moved to a machine without it cannot be opened (HM6412) The delivery queue Queued files are copied with the data directory, but nothing re-queues them. Let the queue drain before a migration Per-domain relay settings, and per-domain message retention days Silently lost, and silently reset to 0 (keep for ever). Re-enter them Greylisting triplets; auto-ban and login-failure state Senders are greylisted afresh; bans lapse. Permanent IP ranges are carried Quarantine, message trace, metric history; since 6.3.4 the audit trail, alert rules and events Operational records. Back the database up with its own tool if you need them Deleted items kept for recovery, eDiscovery cases and holds, messages that expire, password-protected messages The Recoverable, Discovery, Confidential and Protected folders are left out of the copy and left in place by a restore. A restore with domains is refused while an account or a domain is under a legal hold. Archives taken by earlier 6.3.4 builds, before Confidential and Protected were left out, do hold both: a message that has expired or been withdrawn since is still in them, so apply the mail's own retention to those archives, or delete them The full-text index, IMAP annotations, the QRESYNC expunge log Rebuilt, or keyed on ids a restore reassigns. IMAP clients resynchronise Passkeys; calendar and address-book shares; the backup passphrase, the SNMP passwords, the object store's secret, the sandbox token Enrol, share or enter them again A domain named recoverable, discovery, confidential or protected keeps its mail in a folder the backup leaves out. Such names are refused for a new domain since 6.3.4, and an existing one is reported as HM7597; rename it before relying on the backup. The options, and the combinations that fail Maintenance → Backup & restore in the Control Panel (Windows-only), the Control Deck's Backup view, PUT /api/v1/settings/backup and hmctl settings set backup set the destination and what goes in. A fresh install seeds backupoptions as 0: nothing is ticked. On a packaged Linux server the destination has to be inside the unit's ReadWritePaths, or the write fails; add it with a drop-in. Option What it changes Back up domains The domain tree goes into the index. Required for messages to mean anything Back up messages Copies the whole data directory. Only with Back up domains as well: messages without domains stages nothing and then fails Message metadata only (BackupMessagesDBOnly=1) Records which messages exist, not their contents; a restore expects the files still to be in the data directory. It governs the restore as well as the backup, and must be the same for both Back up settings The settings row above and, per the wiki, public folders Compress destination files Puts the message files inside the archive. Off, every run overwrites one DataBackup folder beside the archives: exactly one generation of mail, whatever retention says Verify each backup (BackupVerifyRestore=1, the default) The next section Tick domains and messages together, always, and Back up settings as well if the installation has public folders. The index is written to hMailServerBackup.xml, added to the archive and deleted, so what you restore from is the .7z; the Control Panel's file picker defaults to *.xml, so choose All files. How a run is verified Every refusal happens before anything in the destination is written, or deletes only the archive this run created. A run refuses when a message file lies outside the data directory, when the destination is unreachable, and when the database is down or any read did not run, so an outage cannot produce a hollow archive. Then the archive is read back: The index opens, through the same code a restore uses, and describes this run. If not, the backup fails and the archive is deleted. The per-account rows are all there (since 6.3.4), counted per table. A difference fails the backup. The message store extracts to exactly the files and bytes that were staged. Fewer is a truncated archive, whatever 7-Zip's exit code said. Every message row has a file. Reported, never failed: mail flow does not stop for a backup. The log names the first ten. Steps 3 and 4 run only when domains and messages with their files are included and BackupVerifyRestore is 1. The wiki puts step 2 under the same conditions; Backup.md says only that BackupVerifyRestore=0 turns off steps 3 and 4. Step 3 costs disk: the store exists twice in the temp directory during the check, and a temp volume that cannot hold it skips the step with an explanation rather than failing a good backup. A successful run ends Backup completed successfully.; a failed one has a BACKUP ERROR line. Schedule and retention Since 6.2.25. Nothing is configured on a fresh install. Setting Default Meaning ScheduledBackupTime empty (off) HH:MM, local time; at most one backup per calendar day ScheduledBackupIntervalMinutes 0 (off) Used only when no daily time is set; measured against the newest archive in the destination ScheduledBackupKeepCount 0 (keep all) How many of this server's own archives to keep ScheduledBackupMaxAgeDays 0 (keep all) Delete archives older than this UpdateBackupBeforeApply 1 An unattended live-update apply (in UpdateWindow) backs up to the destination first and is cancelled without a successful backup; an apply you start yourself takes none Since 6.3.4 these are stored in the database: set them on the Backup & restore page, with PUT /api/v1/settings/ini/{name} or hmctl ini set, not in the file (how the settings store works). Retention has four rules: nothing is deleted until the replacement has completed successfully; the newest archive is never deleted; the age rule always leaves the two newest alone; and only files whose names the server generated are considered, the timestamp read from the name. A scheduled run certain to fail (no destination, nothing selected, database unavailable, destination unreachable) is refused before it starts and backs off 5, 10, 20, 40 and then 60 minutes. Test backup directory on the Control Panel's Diagnostics page catches a schedule that has been failing silently. Encrypted archives, since 6.3.4 Off until a passphrase is set; then every archive is 7-Zip AES-256 with its headers encrypted (-mhe=on), so it cannot even be listed without the passphrase. Set it in the Control Panel (Backup & restore → Encryption), the Deck's Backup view, encryption_passphrase in PUT /api/v1/settings/backup, or hmctl backup passphrase set. It is 12 to 256 printable ASCII characters, kept in the database under the machine's secret store (DPAPI on Windows, the key file on Linux), never written into the archive, a command line or a log, and never read back. If it is lost, everything in every archive written with it is lost. Changing it does not touch archives already written, so write the passphrase and its date down together, away from the backups. What it changes about a run. Compression must be on when messages are backed up, or the run is refused. Staging moves into a directory under TempDirectory, so the temp volume needs room for the message store plus a little, and the run is refused with both numbers if it is not there. A stored passphrase this machine cannot open fails the backup; an unencrypted copy is never written in its place. 7zr, the reduced p7zip build, cannot encrypt: a Linux server needs 7-Zip (7zz) or p7zip-full. The archive's file name, its size and the fact that a backup ran are not hidden. Restoring the whole server Phase one decides and changes nothing. Since 6.3.4 the archive is listed first (7za l -slt, the headers only): an entry with an absolute path, a .. segment, or a link with somewhere to point refuses the whole archive as HM7540, because no backup the server writes has such an entry. Then the archive must be one of ours, written by an older or equal version (a newer one is refused, with no override), and hold what you asked to restore; then the store is extracted to a scratch directory. Phase two acts: delete every domain, delete every public folder, empty the data directory and copy the staged store in, load domains and accounts, load settings, reinitialise. A failure here leaves a half-restored server. The listing check works whichever 7-Zip is installed; the Windows installer's is 26.03 in 6.3.4, replacing 19.00. Where. The Control Panel's Restore card and COM (BackupManager.LoadBackup, LoadEncryptedBackup) on Windows; POST /api/v1/backup/restore with file, passphrase and the three parts, which is the only restore a Linux server has; the Deck's Backup view; hmctl backup restore FILE --domains --messages --passphrase-file FILE. The REST route's check is bounded at two minutes; past that the route answers 504 and starts nothing. Install the same version, at the same drive letter and directory layout. Restore or recreate [Database] in hMailServer.INI and confirm you can connect. Choose the .7z, tick the categories, start, and watch hmailserver_backup.log. Copy back what the archive could not hold: certificate, key and DKIM files, and PasswordPepper. Re-enter the per-domain relay settings. Check Server status, then Diagnostics. Send a message in from outside, read it over IMAP, send one out. Rehearse this yearly on a scratch machine against a new, empty database, never production, and keep the list of what you fixed by hand with the backup. Restoring one mailbox, or one message, since 6.3.4 An archive in the backup destination can give back one account's mailbox, every folder and message it held when the backup was taken, or one message of it, and touch nothing else: not the account's settings, rules, contacts or calendars, and no other account. Where How REST GET /api/v1/backup/archives, GET /api/v1/backup/{archive}/mailboxes/{address}, POST .../restore with mode, or message and folder, GET /api/v1/backup/restore-mailbox/{job} Control Deck, and Control Panel (Windows) The Restore a mailbox and Restore one message cards COM (Windows) BackupManager.RestoreMailbox, RestoreMessage, DescribeMailbox, MailboxRestoreStatus hmctl hmctl backup archives, hmctl backup mailbox ARCHIVE ADDRESS, hmctl backup restore-mailbox ARCHIVE ADDRESS --merge\|--replace [--message ID [--folder F]], hmctl backup restore-status JOB Which archives. Only one inside the destination, named as the listing names it and never by an arbitrary path. It must have been taken with domains and messages; an archive taken with BackupMessagesDBOnly holds rows and no files, and the job fails. An encrypted one needs its passphrase. Merge creates each archived folder that is missing and adds each archived message whose file is not in the live mailbox; nothing is removed, and merging twice adds nothing. Replace empties every live folder first, so the mailbox is the archive's and mail that arrived since the backup is gone; what it removes is kept only for RecoverDeletedDays, which is off by default. Replace is refused with 400 when the archive holds no messages for the account. The identity rule. Every message file is named once, with a GUID, and the same name anywhere in the live mailbox is the same message: a message the person moved is not brought back to its old folder, and one they deleted is. A restored message keeps its flags, keywords and dates, gets a fresh UID, and is charged to the account as a delivery is. Refused, and counted. The account must exist on this server and the archive must hold it (404); restoring into a different account is not offered, and neither is a public folder. A second restore while a backup or restore runs is 409. The outcome (folders, added, skipped, failed) is in the job's status and the backup log. Restoring needs manage_server; listing an account's messages out of an archive needs view_mailbox. Where to read more Wiki: Backup and Restore, with COM commands in section 15.9; Installing on Linux for the Linux paths and key file. Repository: Backup.md; SecretsAtRest.md. The public documentation: Backup and restore, Backups on Linux. On this forum: Moving to new hardware, Upgrading to 6.3.4, and Hardening a public mail server for exporting the stored-secrets key.