Repository navigation
The Service Account
Source of truth: hmailserver/docs/ServiceAccount.md, kept in the repository and copied here. Copied on 27 September 2026 from the document as the tag v6.3.4 (51e2dbda0) has it, and again on 5 October 2026 from the document as the tag v6.3.6 (e3c448aa6) has it (ServiceAccount.md at v6.3.6): the live update under this account and the administrator's install from the Control Panel (
AdminUpdate,HM7465toHM7469), the service's login on SQL Server (hMailServer.exe --grant-database-login), a new installation whose database step did not complete, what an administrator's command writes and which links it goes through,/GrantServiceAccess's exit code 7, the program folder's folders made private (hMailServer.exe /ProtectFolders, with--report-onlywritten before the verb), and a command run as root on Linux. Copied again on 18 October 2026 from the document as the tag v6.3.7 (31d656c0c), the release, tagged and published on 17 October 2026, has it (ServiceAccount.md at v6.3.7):/ProtectFolders, which setup runs at every installation and upgrade, hardens the program folder,BinandControlPaneltoo, which until 6.3.7 took their permissions from the folder they are in, so that on an installation at a drive's root any account that could sign in could change the programs - with the steps to take before upgrading such an installation, and what is left for aCHECKline to name; a command run as root on Linux writes no report but the stored-secrets code'sHM7496andHM7497, which go to standard error, where v6.3.6's text says every report goes there and this copy has said so since 5 October 2026; and a server on 6.3.4 or 6.3.5 under this account cannot install 6.3.6 or a later release by its live update (From 6.3.4 and 6.3.5), as this copy said from 17 October 2026. The document is new in 6.3.4, and for Windows: the Linux packages have always run the server as a user of its own. Security Hardening §17.3 is the short version. Re-copied on 20 October 2026 from the document asmasterhas it from batch50, 0c0b6bae7 (ServiceAccount.md at 0c0b6bae7): in Reinstalling into a new folder, step 2 changes the settings file's other paths into the old installation -PostgreSQLSslRootCertandTlsCaFilein[Database],UpdateTrustRootsFileandUpdateLogPublicKeyFilein[Settings], and a path in[SettingsOverride]- to copies where nobody but the administrators may change them, and deletes the lines the site did not set in[Directories], among the live update's five trust settings, in[SettingsOverride], in[LDAP]and in the sending limits, and step 4 keeps the old folder private to SYSTEM and the Administrators until it is deleted; and the live update's internal tests name the signed installers that must pass the publisher pin there as 6.3.1 and 6.3.3 to 6.3.5, where they said 6.3.1 to 6.3.5. Re-copied on 21 October 2026 from the document asmasterhas it from batch51, 5e49abf06 (ServiceAccount.md at 5e49abf06), which changes it in two places: Reinstalling into a new folder says that where nobody but the administrators could have changed the old folder's programs, Relocating an Installation moves the whole installation instead, its programs copied with it, its step 6 running/ProtectFolderson the copy and its step 11 running setup at the new folder; and the table of what writeshMailServer.INIunder the virtual account says that the application log's line on the file read-only to the service is written at a reinitialise where application logging is on, with the settings whose line there is older than the stored value counted and named, and dropped at the service's start, which reconciles the settings store before the server has read its logging settings, and that an edit is reported once, asHM5804, where it said that the line was written once per start and an edit named once. Its links to the repository's other documents point at them as 5e49abf06 has them, or at this wiki's copy where there is one.
The Windows service reads mail from anybody on the internet, in C++, and for as
long as this project has existed it has done that as LocalSystem, the most
privileged account on the machine. A flaw reachable through SMTP, IMAP or POP3
was a flaw with full control of the computer. Since 25 September 2026 a new
installation runs the service as NT SERVICE\hMailServer instead: the service's
own virtual account, which Windows makes from the service's name - no password,
no account to create - and which reaches only the folders it is granted. The
Linux packages of this same server already run as a user of their own
(hmailserver.service: User=hmailserver); this is the Windows half.
What this page covers: what the installer asks and does, what the account is granted
and by what, why nothing the service can write decides that, what the service may no
longer write - hMailServer.INI - and what became of each thing that wrote it, what
the other users of the computer may read of its folders, and change of its programs
(/ProtectFolders), the table of everything LocalSystem could do that the virtual
account cannot, the live update's answer, where the Control Panel, the Control Deck
and the checklist say which account the service runs as, and how to go back.
What it does not cover, because it stays declined: an installation under an
ordinary account for shared hosting (discussion #267). The secret store is
machine-scope DPAPI (DataProtector.cpp, CRYPTPROTECT_LOCAL_MACHINE), which
anyone with administrator rights on the machine can read; the COM registration is
machine-wide; and the message store is guarded by file permissions that an
ordinary user cannot set against the other users of the same machine - who, on
shared hosting, are the other tenants.
A service that can make an elevated program act on something it chose has given
back what the account took away. Three elevated programs act on this service's
behalf - the installer, hMailServer.exe /GrantServiceAccess and
hMailServer.exe /Register - so none of them takes a folder, a file or an
account from anywhere the service can write:
-
hMailServer.INIis not the service's to write. It holds[Database],[Directories]and[Security]- how the server reaches its database, where its folders are, and who may administer it - and the elevated programs read it. The service runs on it read-only (below). - The grant takes its folders from trusted places alone: where the program is, and a list only administrators may write. A folder the configuration names is reported, never granted, until an administrator approves it after a question that names it.
-
/Registertakes the account from its command line alone, never fromServiceAccountName.
The installer has a page for it, after the server profile, whenever the server component is installed. It says which case applies:
| The machine | The page offers | The default |
|---|---|---|
| No hMailServer service yet - a new installation; or the LocalSystem service a new installation left when its database step did not complete (below) |
NT SERVICE\hMailServer, or LocalSystem as earlier versions ran it |
NT SERVICE\hMailServer |
| A service running as LocalSystem - an upgrade | keep LocalSystem, or move it to NT SERVICE\hMailServer
|
keep LocalSystem |
| A service running as any other account - an upgrade | keep that account, or move it back to LocalSystem | keep the account |
An upgrade never changes the account unless the page is answered to. A silent
installation takes the same choice as /serviceaccount=virtual,
/serviceaccount=localsystem or /serviceaccount=keep; without it, a new
installation gets the virtual account and an upgrade keeps what it has. When the
database is SQL Server reached with Windows authentication, the move page says
that the moved service needs a login there, which setup gives it first (step 4).
The order is the point:
- The service is registered -
hMailServer.exe --service-account <word> /Register- as LocalSystem when it is to be moved (a new installation, or an upgrade of a LocalSystem service that chose the move), and as its chosen or kept account otherwise. - A service that already runs as an account of its own is granted its folders again, for anything this version added.
- The database step runs - DBSetupQuick for the built-in database, DBSetup for an
external one - and the chosen profile is applied. That step writes
[Database]intohMailServer.INIthrough the service, over COM, and sets the service's dependency on a local database server; LocalSystem may do both, and the virtual account may do neither. - For a move, when the database is SQL Server reached with Windows
authentication,
hMailServer.exe --grant-database-logingivesNT SERVICE\hMailServera login there, a user in the hMailServer database anddb_owner(below) - as the administrator running setup, on a connection of its own, while the service is still LocalSystem's. The database step made or opened the database as LocalSystem; the moved service signs in as the virtual account, and without these it cannot open the database. When they cannot be given on a database this installation set up, the service is not moved: it stays on LocalSystem, working, the message gives the statements a SQL Server administrator runs, and setup ends with 10 (Upgrading.md, "If the upgrade fails"). Then the service is stopped andhMailServer.exe /GrantServiceAccessruns - with--confirmin an attended installation. By now the database names every certificate and key file, so the grant can say which are outside the folders it grants. Only when it ends in 0 is the service registered asNT SERVICE\hMailServer. Anything else - a folder nobody approved in a silent installation, a folder that can never be granted, No to the question, a grant that failed - leaves the service on LocalSystem, working, and says why; the installation still succeeds. A database step that failed leaves it on LocalSystem as well, and a new installation's leaves the mark that lets it be finished later (below). - The service is started. A service this installation has just moved is then held
to opening its database as
NT SERVICE\hMailServer- the database's version is read through it, as the installer reads it for an upgrade - since a service reports itself running before it has opened anything. One that did not start, or did not open its database, is moved back to LocalSystem and started again, and setup says why and ends with 10 (11 when its database step changed the database): until 6.3.6 such a server, which listened on no port, ended setup with 0.
A grant that fails at step 2 fails the installation - that service may not start
- and says so in its own words, not the database's.
A new installation whose database step did not complete - DBSetup refused a
setting, the database server refused the sign-in, the wizard was closed - has its
files, a service registered as LocalSystem for the database step, and no database;
none of what follows that step has happened: the secure defaults
(Secure Defaults), the profile, the stored-secrets key
(Secrets at Rest) and the move. Setup ends with 10 and leaves
a mark, NewInstallationPending (the account chosen, virtual or localsystem)
and NewInstallationProfile (the profile, if one was) under
HKEY_LOCAL_MACHINE\SOFTWARE\hMailServer in the 32-bit view, writable by
administrators only. Either way the message offers then finishes it as a new
installation:
-
Run setup again, with the setting corrected. It finds the mark - or the
LocalSystem service beside a
hMailServer.INIthat names no database, which an earlier version's setup leaves - and sets it up as a new installation's: the pages offer, and a silent installation defaults to, the account and the profile the mark holds (NT SERVICE\hMailServerwithout one),/serviceaccount=and/profile=choosing otherwise, and the steps after the database run as for any new installation. A LocalSystem service whosehMailServer.INInames a database, or whose file is not found, is an upgrade's, and keeps LocalSystem unless the move is chosen. On the built-in database the same holds: setup run again finishes the installation, whereDBSetupQuick.exewould set the database up and nothing after it, so the message after a new installation's failed step names setup. -
Run
DBSetup.exeas an administrator from theBinfolder - its wizard, or/unattendedwith the settings in its environment (WindowsOnArm.md). With the mark there, once the database is set up it runs the installer's steps itself, in the installer's order, with hMailServer.exe's verbs: the service stopped,--apply-new-install-defaults, the profile (--apply-profile NAME --once --if-new --confirm),--create-stored-secrets-key, and for the virtual account--grant-database-login, the grant and--service-account virtual /Register; then the service started and, when moved, held to opening its database, moved back to LocalSystem when it does not. Each step is said as it goes, and the mark is removed - kept only when the service would not stop, so that none of them ran. The wizard asks Windows for elevation itself when the mark is there;/unattendedwithout elevation refuses, before it changes anything, with exit code 2.
A new installation whose administrator cannot open the database - SQL Server
with Windows authentication, where the database step made the database as
LocalSystem, a SQL Server administrator, and the person running Setup is none -
has its database, and --apply-new-install-defaults (exit 2) and
--create-stored-secrets-key (exit 1), run as that person, could not open it. Until
6.3.6 neither was ever applied: the recovery the message gives - the statements run
by a SQL Server administrator, then Setup again - runs as an upgrade. Now Setup,
and DBSetup finishing an installation, start the service with the argument that has
it apply them itself: sc start hMailServer --apply-new-install-defaults. The
service opens its database, applies a new installation's defaults with the verb's
own rules - the database's NewInstallDefaultsPending mark required, the shape of a
new database, the mark cleared once decided - and, for the new installation it
found and applied them to, makes the stored-secrets key when it runs as LocalSystem, which may write hMailServer.INI (the virtual account may
not, and says so, HM7498), before its listeners start. What it could not do is in the
error log (HM7498, HM7510, HM7511), each step in the application log when that is
on, and the Control Deck's setup checklist shows the outcome. An administrator can start it so at any time after; the mark is
acted on whatever its age, since the argument is the request. The profile chosen is
not applied this way: the Control Deck's checklist offers it.
Setup clears the mark before its own database step, so that the DBSetup it runs
leaves those steps to it, and writes it again only if that step does not complete
either; the uninstaller removes it. An update hMailServer.Updater runs - the live
update, or Install update in the Control Panel - leaves the mark alone and
upgrades the installation as it is: unattended, it keeps the account and changes
nothing a new installation would, and its database step is held to the version
before it, as any upgrade's is. Until 6.3.6 setup run again kept the service on
LocalSystem, and DBSetup set the database up and nothing else - no self-signed
certificate (the database kept its NewInstallDefaultsPending mark), no
stored-secrets key, LocalSystem (the installer smoke VM, 2 October 2026).
Every registration of an account other than LocalSystem also sets the service's
SID type to unrestricted, so that the service's token carries
NT SERVICE\hMailServer whatever account it logs on as - which is what lets one
set of grants serve the virtual account and a group managed service account alike.
A registration that leaves the service on LocalSystem leaves the SID type alone.
Run elevated - by the installer, by the Control Panel's buttons, or by hand from
an elevated Command Prompt in the Bin folder. It grants the service's SID,
NT SERVICE\hMailServer
(S-1-5-80-3504853920-2627644491-1723263464-2546768561-1700046809, derived from the
name as Windows derives it), exactly this:
| Where | What | Why |
|---|---|---|
The program folder's own Data, and everything in it |
Modify | The message store, and its ACME, Certificates, DKIM, Discovery, Files, Recoverable, Blobs, Lists, Updates and other folders |
The program folder's own Logs, and everything in it |
Modify | The logs, and the crash dumps and the crash record, which are written there |
The program folder's own Temp, and everything in it |
Modify | Messages being received and scanned, and the virus scanners' copies |
The program folder's own Database, and everything in it |
Modify | The built-in SQL Server Compact database |
Each folder approved in HKEY_LOCAL_MACHINE\SOFTWARE\hMailServer\ServiceAccessPaths, and everything in it |
Modify, or Read | A data, log or backup folder, an archive folder, a certificate folder elsewhere - what an administrator said the service may reach |
Each other folder in the program folder - Bin, DBScripts, Events, Languages, WebAdmin, Addons and the rest - and everything in it |
Read | The program, its scripts, its languages and the Deck. Not the program folder itself: every service holds bypass traverse checking, so the account reaches the folders in it without, and an entry on it would make Windows walk everything below it - the message store included, a tree the service writes in once it has moved |
| Each file with protected permissions in the DKIM key folder and the Updates folder, when those are in a folder granted Modify | Modify, on the file | The DKIM rotation's keys and the live update's token are written with permissions of their own (ProtectedFile) that inherit nothing, so a service moved off LocalSystem would otherwise lose the keys it signs with |
| A certificate or key file the configuration names inside a folder above, whose own permissions keep that folder's grant from it | Read, on the file | TLS and DKIM signing |
Bin\hMailServerApiKeys.ini |
Modify, on the file | The REST API's key store, which the service writes when a key is made or revoked. Made empty when it is not there yet - which the REST API reads as no keys - so that the service can write into it without being able to create files in Bin. It is the one file there the service may write, because nothing elevated takes a path, an account or a command from it: it holds key hashes the REST listener checks, and the service could mint a key through its own API anyway |
hMailServer.INI |
Nothing | Read-only to the service - see below |
The program folder is where the program is: the folder above the Bin
folder hMailServer.exe runs from, not [Directories] ProgramFolder. A binary
that is not in a folder named Bin grants nothing (exit 2).
What the configuration names is only reported. The folders of
[Directories] (DataFolder, LogFolder, TempFolder, DatabaseFolder for the
built-in database, EventFolder), the backup folder, ArchiveDir and
AcmeCertificateDirectory, and the certificate and key files - the SSL
certificates, the domains' DKIM keys, the REST API's, the metrics listener's and
the web services' - are read from the file and from the database (the settings
from hm_inisettings itself, not the file's copy), and each is checked against
the grants above. One the grants reach needs nothing. Otherwise:
- a folder nobody has approved is listed
NOT APPROVED; - a certificate or key file is listed
OUTSIDE, with the way to fix it: move it intoData\Certificates, which the data folder's grant reaches, or approve its folder for Read. It is never given Read where it is: the configuration could name any file on the machine; - a folder that may never be granted is listed
REFUSED, with why; - a backup folder on a network share is noted: the service reaches a share as the
computer account (
DOMAIN\COMPUTER$), and that is granted on the share.
Approving a folder. --approve-modify "<folder>" or --approve-read "<folder>" on the command line; or --confirm, with which the verb asks - in a
box of its own, on the desktop of the elevated process, which nothing running at
a lower integrity can answer - naming each folder not approved, each key file
outside and each refusal, before it changes anything. Yes approves the folders,
No changes nothing at all. An approval is recorded in
HKEY_LOCAL_MACHINE\SOFTWARE\hMailServer\ServiceAccessPaths (64-bit view), one
value per folder - its full path as the name, Modify or Read as the data - in
a key the verb makes owned by Administrators and writable by Administrators and
SYSTEM alone. If anybody else may change that key, or owns it, nothing in it is
granted and the run fails with the reason. Approve only a folder you recognise:
the service may then write in it.
Never granted, approved or not, a folder that:
- is not a full path on a fixed local drive - a share, a device path (
\\?\,\\.\), a relative path or a stream; - is a drive's root;
- is, or holds, the program folder; or is in it but outside its own
Data,Logs,TempandDatabase-Binabove all; - is, holds or is in the Windows folder, a Program Files folder, the user
profiles folder or
ProgramData\Microsoft(every user's Start menu and startup folder); - is or holds
ProgramData(a folder of its own in it may be approved); - is, or runs through, a junction, a symbolic link or a mount point that the administrators do not alone control - one the service's account, or anybody else who is not an administrator, made or could turn elsewhere. The refusal names who may change the link, or who owns it.
Every path is resolved first - .. taken out, short names made long - so none of
these can be named another way.
A link the administrators alone control is gone through, by the rule the store
commands follow ("What an administrator's command writes", below): keeping Data,
Logs, a folder above them or a folder of the program such as Events on another
drive behind a junction or a volume mount point an administrator made is an
ordinary layout. Such a link - owned by BUILTIN\Administrators, SYSTEM or
TrustedInstaller, which nobody else may change or remove from the folder it is
in (OnlyAdministratorsControl) - is followed, and the path it holds is held from
that volume's root in the same way - every link on it, the folder it ends at
included, asked about in its turn, so a junction the service's account put where
an administrator's link leads is refused - must be on a fixed local drive, and is
held to the refusals above as though it had been named itself: a junction from
Data to a drive's root, the Windows folder or Bin is refused there. A volume
mounted at Data or Logs with no drive letter of its own is gone through, its
root granted; one that also has a drive letter is that drive's root, and refused
as one - take the letter away (mountvol <letter>: /D), or make the link a
junction to a folder on that drive instead. The grant is written on the folder the
link leads to, by its volume's name (\\?\Volume{...}\), so that what it inherits
is that folder's own parent's, not the link's; the line says where the link leads.
A link to a share is refused as not on a fixed local drive, the line naming the
share it leads to. Until 6.3.6 every link on the way was refused, and the upgrade
of an installation whose data folder an administrator had moved behind a junction
failed its grant. The icacls that gives a link to the administrators alone is
under "What an administrator's command writes".
How a grant is written. One allow entry for the service's SID is added and
nothing is removed - no other entry is changed, an administrator's deny entry for
the service included; an explicit deny outranks any allow, so the line says the
grant FAILED and names it, and an inherited deny is named as the entry the new
one now comes before. While a folder is granted, it and every folder above it are
held open without sharing delete, so none of them can be renamed, removed or
swapped for a link while the permission is written through the path - a link the
administrators alone control included, and every folder from the root of the
volume it leads to down. A file is granted through a handle opened without
following links, and only when it has one name - a hard link elsewhere would
carry the grant to wherever else it is. A
second run finds the entries there and changes nothing: each line says
granted, already there or FAILED with the reason, and the last line counts
them. Windows hands a folder's entry down to everything below it as part of the
grant, which on a large message store takes minutes the first time and nothing
after that.
What it checks, and only says. When Everyone, Authenticated Users,
Users, SERVICE or the service itself may write the program folder, Bin or
hMailServer.INI - add, change or delete what is in it, or change who may - a
CHECK line names the entry. The grant removes nobody's entry. An installation
outside Program Files could inherit such an entry from a drive's root until 6.3.7;
hMailServer.exe /ProtectFolders, which setup runs at every installation and
upgrade before the grant, now takes away an entry any of the three inherits (below),
so what is left for the line to name is an entry of its own, which an administrator
gave, and the icacls ... /remove:g that takes it away.
--report-only says all of this - what would be approved and granted, and what
is not reached - and changes nothing.
Exit codes: 0 every grant made and everything the configuration names reached, or
confirmed; 1 a grant or an approval failed; 2 not in an installation's Bin
folder; 3 not run as an administrator; 4 folders not approved or key files
outside, not confirmed; 5 the configuration names a folder that is never granted;
6 the administrator answered No, and nothing was changed; 7 the grants were made,
but this account could not read the database, so the certificate and key files and
the backup and PST import folders it names are not known, and nobody confirmed. The
installer and the Control Panel move the service only on 0.
A database it cannot read. On SQL Server with Windows authentication, where
LocalSystem is a SQL Server administrator and the person running the grant is not,
the grant cannot read what the database names. It says so on a NOT READ line and
ends in 7 - until 6.3.6 it said so in a note and ended in 0, and the service was
moved without anybody knowing whether a key file it reads lay outside its folders.
With --confirm the question names the database it could not read, and Yes is a
0. A silent installation's move then waits, as for a folder nobody approved; an
upgrade of a service already moved goes on, since nothing is moved. A database not
set up yet names nothing, and is no 7.
Until 6.3.6 the program folder's own Data, Logs, Temp, Database and Events
folders, and Bin\hMailServer.INI, took their permissions from the folder they are
in. Under C:\Program Files that gives BUILTIN\Users - every account that can sign
in to the computer: a Remote Desktop server's users, a shared machine's, a service
running as an ordinary account - Read on all of them, and ALL APPLICATION PACKAGES
the same. Any of them could read every message; the logs, with their addresses,
subjects and whole SMTP, IMAP and POP3 conversations; the built-in database, with the
accounts' password hashes and the stored secrets' sealed values, whenever the service
that keeps its file open is stopped; a key file an administrator copied into
Data\Certificates; the event scripts; and hMailServer.INI - the database's
password, sealed with the machine's DPAPI key, which any account on the machine may
use to unseal it (PasswordEncryption=6), the administrator password's hash and its
second factor. The grant (above) adds the service's entries and removes nobody's. A
data folder moved under a folder at a drive's root took that folder's permissions
instead once anything rewrote its own - on Windows Server, Users' Read and the right
to make files and folders in it; on a Windows client, Authenticated Users' Modify.
On the installer smoke VM on 3 October 2026 a user in the Users group alone, signed
in interactively, listed the five folders of a 6.3.5 installation and read two
messages, a log naming seven addresses, the event script, an imported key file and
hMailServer.INI, and unsealed the database's password from it.
Since 6.3.6 the installer runs hMailServer.exe /ProtectFolders, elevated, at every
installation and upgrade of the server - whatever account the service runs as, before
the database step - and an administrator may run it from an elevated prompt in the
Bin folder at any time. Each of the six is made private:
| What | Who reaches it afterwards |
|---|---|
Data, Logs, Temp, Database, and everything in them |
SYSTEM and the Administrators, full control; NT SERVICE\hMailServer Modify, the grant's, when the service runs as anything but LocalSystem |
Events, and everything in it |
SYSTEM and the Administrators; the service Read, the grant's |
Bin\hMailServer.INI |
SYSTEM and the Administrators; NT SERVICE\hMailServer Read, under every account - the service reads its configuration whatever it runs as, and writes it under none but LocalSystem |
How. The permissions stop inheriting - a protected DACL - so that nothing the
folder above hands down reaches it. Every deny entry is kept, an inherited one as the
folder's own; every entry of the folder's own is kept, an administrator's and the
grant's; SYSTEM's and the Administrators' inherited entries become full control,
handed down; an inherited entry for the service's SID - the Read on hMailServer.INI
that Bin's grant hands down - is kept as the file's own; and every other inherited
allow entry is dropped and named on the line: BUILTIN\Users, ALL APPLICATION PACKAGES, ALL RESTRICTED APPLICATION PACKAGES, CREATOR OWNER, and on a moved data
folder Authenticated Users. Windows hands the new permissions down to everything in
the folder as part of the change, which on a large message store takes minutes the
first time; a second run finds them already private and changes nothing. A file or
folder below with permissions of its own - the DKIM keys and the update token
(ProtectedFile), the self-signed certificate's key - keeps them.
Where it acts, and where it only says. The six are found from where the program
is, as the grant's folders are, never from hMailServer.INI or the database; a
junction or a mount point among them is gone through only when the administrators
alone control it, and held from that volume's root while it is changed, as the grant
goes through one - so a data folder an administrator moved to C:\MailStore\Data
behind such a junction is made private there. Anything else is checked and said,
never changed: a folder elsewhere may be shared on purpose, and an elevated program
takes no path to change from the configuration.
- A folder
hMailServer.INInames outside the program folder ([Directories]), a folder approved for the service, the backup, archive and PST import folders, and the key and certificate files the database names: aCHECKline when Users, Authenticated Users, Everyone, INTERACTIVE or another group of ordinary accounts may read it, with the twoicaclsthat close it. - A key or certificate file in
Data\Certificates,Data\DKIMorData\ACMEwith permissions of its own that such a group may read - a file moved there keeps the permissions it had where it was: aCHECKline, andicacls "<file>" /reset, which gives it the data folder's. - An entry of one of the six's own that lets such a group read it, which only an
administrator can have given: kept, and a
CHECKline with theicacls /remove:gthat takes it away.
Bin\hMailServerApiKeys.ini is left as it is, readable as the rest of Bin is: it
holds each REST API key's label and the SHA-256 of its 32 random bytes, which tells
nobody the key, and the Control Panel lists the keys from it.
The program's own folders: what the other users may change. Until 6.3.7 the
program folder itself, Bin and ControlPanel took their permissions from the folder
they are in, as the six did. Under C:\Program Files that is right: SYSTEM, the
Administrators and TrustedInstaller may change them and every other account may read
and run them. In a folder at the root of a drive it is not. On a Windows client the
root hands every folder made below it Authenticated Users Modify, so every account
that can sign in to the computer could replace hMailServer.exe, or put a library in
Bin that Windows loads into it before its own (a program's own folder is searched
first), and the service - LocalSystem by default and after every upgrade that keeps
it, or NT SERVICE\hMailServer - ran it at its next start, as did the next
administrator to run DBSetup, the Control Panel's elevated steps or the uninstaller.
On Windows Server the root lets Users make files and folders in it, which is enough
for the library. Every release from 6.0.0 to 6.3.6 installed so at a drive's root
(work item 78); the default folder, Program Files, was never affected.
Since 6.3.7 /ProtectFolders hardens the three, after the six:
| What | Who reaches it afterwards |
|---|---|
The program folder itself - the uninstaller, and what the folders in it inherit (DBScripts, Languages, WebAdmin, Addons) |
SYSTEM and the Administrators, full control; BUILTIN\Users Read & execute where every user could read it before (Program Files, a drive's root), and no entry where no user could; the Administrators its owner |
Bin, and everything in it |
The same; NT SERVICE\hMailServer Read & execute, the grant's, and Modify on Bin\hMailServerApiKeys.ini alone, the grant's, an entry of the file's own that stays |
ControlPanel, and everything in it |
The same; NT SERVICE\hMailServer Read & execute, the grant's |
How. Each stops inheriting, as the six do; every deny entry and every entry of
its own is kept - the grant's Read for the service among them; SYSTEM and the
Administrators get full control and BUILTIN\Users Read & execute, each handed down,
the Users' a fresh entry whatever the folder above handed them, so that the Control
Panel and DBSetup still run for an administrator whose programs run unelevated -
but only where Everyone, Authenticated Users or Users could read the folder
before, as under Program Files and at a drive's root: in a folder made for the
purpose that only the administrators may open, the Users get no entry, so that what
the program folder holds besides the six (a backup, a key file, a data folder of
another name) does not become every user's to read; and every inherited allow entry
is dropped, the line naming those that gave a right to change it: Authenticated Users from a client's drive root, Users and CREATOR OWNER from a Windows
Server's, and under Program Files TrustedInstaller and CREATOR OWNER. A folder
somebody other than SYSTEM, the Administrators or TrustedInstaller owns (one a user
made at a drive's root before the installation, which any user can) is given to the
Administrators, and the line names the owner it had: an owner may change the
permissions whatever they say, and so could have written the folder's own entries
too - those that let anybody but SYSTEM, the Administrators and TrustedInstaller
change it go with that owner, and are named. A file in Bin or ControlPanel with
permissions of its own keeps them. A second run finds the three already hardened
and changes nothing; under Program Files the first run of 6.3.7 makes the three's
entries their own, and no account can then do anything there it could not before. The
program folder is not changed while one of the five folders in it could not be made
private, since Windows hands its change down through that folder; Bin and
ControlPanel are hardened all the same.
Then the rule of an administrator's install. After the three, the installation is
held to the rule an administrator's install of an update holds it to before it runs
anything from it ("The live update", below): the folders above the program folder,
the program folder, Bin and ControlPanel with every folder, program and library in
them, hMailServer.INI and AdminUpdate. What the hardening makes passes it, so a
drive-root installation that the live update refused before 6.3.7 passes once
/ProtectFolders has run. What it leaves - an entry of a file's or a folder's own that
an administrator gave, an owner that is not SYSTEM, the Administrators or
TrustedInstaller, or a folder above the program folder that others may delete or
rename (an installation in C:\Apps\hMailServer, where C:\Apps inherits
Authenticated Users Modify from the drive's root) - is a CHECK line with the
icacls that removes it, the first the rule finds, and is never changed here: a folder
above the program folder may hold other programs that rely on what it allows.
Setup. Before it copies anything, setup reads the permissions of the folder chosen
on its folder page - or, when that is not there yet, of the nearest folder above it
that is - and when Everyone, Authenticated Users, Users, INTERACTIVE or
another group of every user may change what an installation there would hold, it says
so, in a box and in the setup log (the log alone for a silent installation, and for an
upgrade, whose folder page is not shown). It never refuses the folder: one made for
the purpose off a drive's root is an administrator's choice, and the hardening is what
closes it. A program folder setup makes is given SYSTEM's and the Administrators' full
control and - where every user could read it as it was made - the Users' Read &
execute, inheriting nothing, by icacls on the SIDs,
before any other folder is made in it or any file copied - so the programs are never
there, even for the seconds before /ProtectFolders, with a weaker list.
The Control Panel alone. An installation of the Control Panel without the server
(the remote administration support with it or not) runs no /ProtectFolders: it has
no server. Into a folder setup makes, that changes nothing - the folder is given its
permissions before the files, as above. Into a folder that was there - one an
administrator made at a drive's root, or an earlier installation's - setup hardens it
in place before it copies anything: the program folder, then ControlPanel and Bin
where they are there, each as /ProtectFolders hardens a server's - SYSTEM and the
Administrators full control, the Users' Read & execute where every user could read it,
nothing inherited, the entries of its own kept while SYSTEM, the Administrators or
TrustedInstaller owns it, and otherwise the Administrators made its owner and no entry
of its own left. It leaves alone, and says in the setup log, a drive's root; a folder
that holds a server installation (Data or Bin\hMailServer.INI there), which that
installation's own /ProtectFolders hardens once its data folders are private; and a
folder that is a link - a junction, a symbolic link, a mount point - or is in one,
whose permissions would be written wherever it leads. Every folder from below the
drive's root down to the one it changes is held open, with nobody else allowed to
delete or rename it, from before it is looked at until after it is changed, so that no
user can put a link in its place in between.
Before upgrading an installation at a drive's root. From an elevated Command
Prompt, with the installation's own folder in place of C:\hMailServer, first see
who owns what is there. Of the two listings below, the first names the program
folder's owner on its line ., and the owner of each file and folder directly in
it - Bin, ControlPanel and the uninstaller among them - on theirs; the second
names the owner of everything in the folders that hold the programs and the event
script, hidden files included, and of nothing in the data folders:
dir /q /a "C:\hMailServer"
dir /q /s /a "C:\hMailServer\Bin" "C:\hMailServer\ControlPanel" "C:\hMailServer\Events" "C:\hMailServer\WebAdmin" "C:\hMailServer\DBScripts" "C:\hMailServer\Addons" "C:\hMailServer\Languages"
dir cuts an owner's name to 23 characters (NT SERVICE\TrustedInsta), shows
... for an owner it cannot read, and says File Not Found for a folder the
installation does not have - the Control Panel alone has no Events, WebAdmin,
DBScripts or Addons - and lists the others. Each icacls line below must say
that it processed one file - Successfully processed 1 files on an English
Windows: one that says 0 has changed nothing, and from /remove:g that is the only
sign that it found no account for a SID or a name it was given. Then, as
/ProtectFolders does, for the program folder, then Bin, then ControlPanel:
-
Make the Administrators its owner - an owner may change the permissions whatever they say. The folder alone:
/Twould reach the message store too.icacls "C:\hMailServer" /setowner *S-1-5-32-544 -
List its entries with
icacls "C:\hMailServer"; one without(I)is the folder's own. Take away each entry of its own but SYSTEM's and the Administrators'. The Users' and the service's go by their SIDs, a line each, whatever they give - steps 3 and 4 give back what they need:icacls "C:\hMailServer" /remove:g *S-1-5-32-545 icacls "C:\hMailServer" /remove:g *S-1-5-80-3504853920-2627644491-1723263464-2546768561-1700046809On an installation without the server - the Control Panel alone - leave out the second line, and step 4: there is no service, so its SID names no account, and
icaclsrefuses a SID that names no account, and the rest of its line with it (exit 1332). Anybody else's goes by the name the list gives, in quotes - but the two application package groups by their SIDs,*S-1-15-2-1and*S-1-15-2-2, sinceicaclsdoes not take them by the names it gives them,APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGESandAPPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES. A(DENY)entry only takes away, and stays.icacls "C:\hMailServer" /remove:g "<name>"Where the list gives a SID and no name, Windows found no account for it - one deleted, one of a domain it cannot reach, the service's where there is no service - and
icaclscannot take the entry away. PowerShell can, with the SID in place of<SID>; the line takes away that SID's allow entries of the folder's own, and nothing else:powershell -NoProfile -Command "$d = Get-Item -LiteralPath 'C:\hMailServer'; $a = $d.GetAccessControl('Access'); $a.RemoveAccessRuleAll((New-Object Security.AccessControl.FileSystemAccessRule ([Security.Principal.SecurityIdentifier]'<SID>'), 'FullControl', 'Allow')); $d.SetAccessControl($a)" -
Replace what it inherits, and give SYSTEM and the Administrators full control and the Users Read & execute:
icacls "C:\hMailServer" /inheritance:r /grant:r *S-1-5-18:(OI)(CI)F *S-1-5-32-544:(OI)(CI)F *S-1-5-32-545:(OI)(CI)RX -
On
BinandControlPanel, where the server is installed, give the service back its Read & execute, the grant's:icacls "C:\hMailServer\Bin" /grant *S-1-5-80-3504853920-2627644491-1723263464-2546768561-1700046809:(OI)(CI)RX
icacls on each of the three then lists SYSTEM's and the Administrators'
(OI)(CI)(F), the Users' (OI)(CI)(RX), on Bin and ControlPanel the service's
(OI)(CI)(RX) where the server is installed, and any deny entry, which stays -
nothing marked (I) and nobody else's. The SIDs are SYSTEM's, the Administrators',
the Users', the service's and the two application package groups', the same in
every language of Windows; where the folder above lets no user read the
installation, leave out the Users' grant at the end of step 3, as /ProtectFolders
does. On the program folder each change of its entries reaches everything in it,
the message store included where 6.3.5 or earlier left Data inheriting - minutes
there, and Data then readable by Users until the upgrade's /ProtectFolders makes
it private. The service's Modify on Bin\hMailServerApiKeys.ini is the file's own,
and stays.
The steps keep anybody else from changing the three folders themselves from then
on; they undo nothing changed before, and a file or folder in them keeps its owner
and any entry of its own. Where accounts that are not administrators could sign in
to the computer while the three took the root's permissions; where the listings
named an owner other than BUILTIN\Administrators, NT AUTHORITY\SYSTEM or
NT SERVICE\TrustedInstaller - ... among them - for the program folder, a file
directly in it, or Bin, ControlPanel, Events, WebAdmin, DBScripts,
Addons or Languages or anything in them; or where step 2 took away an entry that
gave anybody more than (RX), the Users' and the service's included, or could not
take one away: a program in them may have been changed already, and so may the
event script in Events, which the service runs. Reinstall the programs from the
release into a new folder that only the administrators may change - under Program
Files, say - not over this installation, rather than rely on the steps: setup leaves
in place every file it does not ship, a library put in Bin among them, and puts the
event script and the scripts in Addons only where none is there.
Reinstalling into a new folder. Run nothing from the old folder, its uninstaller
included, which runs the old Bin\hMailServer.exe as an administrator. Setup run
over an installation shows no folder page and installs into the folder it finds, so
it is given the new one with /DIR; and setup run into a folder without a settings
file sets up a new server there - an empty one, without this installation's mail,
accounts or settings - so the settings file and the data go into the new folder
first. Bring nothing else from the old folder, and read what you bring, as you would
read a program. (Where nobody but the administrators could have changed the old
folder's programs, Relocating an Installation moves
the whole installation instead, its programs copied with it: its step 6 runs
/ProtectFolders on the copy, which a copy by hand does not get otherwise, and its
step 11 runs setup at the new folder as step 3 below does.) From the elevated
prompt, with the new folder in place of C:\Program Files\hMailServer (the Control
Panel alone brings nothing: step 3 is all of it):
-
Stop the service. Make the new folder's
Bin,Data,DatabaseandEvents- the last three private to SYSTEM and the Administrators before anything is in them - and copy the mail, the built-in database's folder and the settings file, which is made private too. Copy, not move: a file moved within a drive keeps its owner and its permissions.net stop hMailServer mkdir "C:\Program Files\hMailServer\Bin" "C:\Program Files\hMailServer\Data" "C:\Program Files\hMailServer\Database" "C:\Program Files\hMailServer\Events" icacls "C:\Program Files\hMailServer\Data" /inheritance:r /grant:r *S-1-5-18:(OI)(CI)F *S-1-5-32-544:(OI)(CI)F icacls "C:\Program Files\hMailServer\Database" /inheritance:r /grant:r *S-1-5-18:(OI)(CI)F *S-1-5-32-544:(OI)(CI)F icacls "C:\Program Files\hMailServer\Events" /inheritance:r /grant:r *S-1-5-18:(OI)(CI)F *S-1-5-32-544:(OI)(CI)F robocopy "C:\hMailServer\Data" "C:\Program Files\hMailServer\Data" /E robocopy "C:\hMailServer\Database" "C:\Program Files\hMailServer\Database" /E copy "C:\hMailServer\Bin\hMailServer.INI" "C:\Program Files\hMailServer\Bin" icacls "C:\Program Files\hMailServer\Bin\hMailServer.INI" /inheritance:r /grant:r *S-1-5-18:F *S-1-5-32-544:F -
Read the copy of the settings file - in Notepad, say. It took
Bin's permissions until 6.3.6, and it decides what the service runs and who may administer it:-
[Directories]names the folders the service takes the database scripts and7za.exefrom (ProgramFolder) and the event scripts from (EventFolder), and those of its data, logs and temporary files. Delete each of its lines that names a folder in the old installation; setup writes the new folder's instead. Setup keeps every other line butProgramFolder, which it always writes, so delete as well a line that names a folder outside both installations and that you did not set - anEventFolderof somebody's own, say, which anybody who could changeBincould have written. -
AdministratorPassword, in[Security], is the administrator password's hash. Delete it, and setup asks for a new password; a silent setup takes it fromHM_ADMIN_PASSWORD. -
[Database]names the database the server opens, andAutoBanCommand, in[Settings]or[SettingsOverride], a program it runs. - Any other line with a path into the old installation still names it after
setup, and the new service goes on reading that file from the old folder:
PostgreSQLSslRootCertandTlsCaFile, in[Database], the CA files the database server's certificate is checked against;UpdateTrustRootsFileandUpdateLogPublicKeyFile, in[Settings], which are read from this file alone; and a path in[SettingsOverride]. Change each as step 4 of Relocating an Installation changes it, to a copy in the new folder of what it names - read before it is copied, as the event script is below, or taken again from where it came from. The two CA files and the two trust files go where nobody but the administrators may change them, the newBinsay, and not inData, which the service may change: an administrator's install of an update refuses a trust file anybody else may change ("The live update", below). The two trust files are the live update's trust withUpdateSigningIdentity,UpdateSigningIssuerandUpdateSourceRepositorybeside them in[Settings], and decide whose signature an update is installed on: delete any of the five your site did not set, and an update is checked against the project's own signers (The Update Feed, "Where the trust is read from"). - Delete as well any other line your site did not set in
[SettingsOverride], each of which takes the place of the database's value of its[Settings]key at every start, any key but those five; in[LDAP], which the server reads from this file alone, and which decides where and how the password of a directory-linked account is checked; and in[SendingLimits]and[SendingLimitsOverrides], read from it alone too, which limit what an account may send.
Read what is in the old
Events- the event script - as well, and only then copy it:robocopy "C:\hMailServer\Events" "C:\Program Files\hMailServer\Events" /E -
-
Where the first line below finds an entry - a 32-bit installation's - setup installs into the folder that entry names, whatever
/DIRsays: take it away with the second. Then run the release's setup with the new folder. For the server, it finds the settings file there and goes on as an upgrade of this installation, the service's account kept, with the programs its own;sc qc hMailServerthen names the newBin.reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\hMailServer_is1" /reg:32 reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\hMailServer_is1" /reg:32 /f hMailServer-<version>-x64.exe /DIR="C:\Program Files\hMailServer" -
The database comes across as it was - on the built-in database in a file that took the program folder's permissions until 6.3.6. In the new Control Panel, read the programs its settings name: ClamWin's and the custom scanner's under Anti-virus settings, and
AutoBanCommandunder Auto-ban. Make the REST API's keys again rather than copyBin\hMailServerApiKeys.ini, to which anybody who could changeBincould have added one. A path the database holds into the old folder - a certificate's, a DKIM key's, the backup folder - still names it: step 9 of Relocating an Installation moves each. Until nothing names it, keep the old folder private to SYSTEM and the Administrators: where 6.3.5 or earlier left itsData,Logs,DatabaseandBin\hMailServer.INItaking the permissions of the folder they are in, every user may still read the mail, the logs, the built-in database and the settings file there, and nothing runs/ProtectFolderson a folder no installation is in. As soon as step 1 has copied what it copies - for a service running asNT SERVICE\hMailServer, once the paths named at the end of this step are moved too - make the Administrators the old folder's owner and replace what it inherits. The folder alone has its owner changed, and on a large message store the second line takes minutes, as above:icacls "C:\hMailServer" /setowner *S-1-5-32-544 icacls "C:\hMailServer" /inheritance:r /grant:r *S-1-5-18:(OI)(CI)F *S-1-5-32-544:(OI)(CI)FThen take away each entry of its own but SYSTEM's and the Administrators', as step 2 of Before upgrading an installation at a drive's root does. Where those steps, or the
/ProtectFoldersof 6.3.7 or later, were run on the old folder,BinandControlPanelinherit nothing from it and keep any Read & execute the Users were given, and a settings file that 6.3.5 or earlier left takingBin's permissions takes it fromBin: take that entry away from both as well, fromControlPanelwhere the installation has one:icacls "C:\hMailServer\Bin" /remove:g *S-1-5-32-545 icacls "C:\hMailServer\ControlPanel" /remove:g *S-1-5-32-545Any other entry of its own that a folder in it has stays - the grant's for the service among them, on each folder that was in it when the old installation's grant last ran - so the service still reads what the database names in those, and writes what it names in Data, Logs, Temp and Database. A service running as
NT SERVICE\hMailServermay have reached what the database names directly in the old folder, or in a folder made in it since then, and a folder it writes in anywhere else in it - a backup or archive folder, which the grant gives no more than Read there ("Never granted, approved or not", above) - only through what the Users and Authenticated Users were given there, which this step takes away: move each first, by step 9 of Relocating an Installation. Then, once nothing names it, delete the old folder - by hand, never with its uninstaller.
Exit codes: 0 each made private or hardened, or so already (CHECK lines do not change
it); 1 one could not be, and its line says why - a link somebody else controls,
permissions that could not be written; 2 not in an installation's Bin folder; 3 not
run as an administrator. --report-only says what it would make private and who may
read each now, and what it would harden and who may change each now, and changes
nothing; it comes before the verb, as the mode of the other commands does:
hMailServer.exe --report-only /ProtectFolders (after the verb it is not read, and
nothing is changed or reported). A run that does not end in 0 does not fail the installation:
setup says so, with the command to run again, and what could not be changed is as it
was.
What an administrator notices. An account that is not an administrator - and an
administrator's own programs not run as administrator - can no longer open these
folders or hMailServer.INI:
- Explorer asks You don't currently have permission to access this folder, and Continue adds an entry for the administrator's own account to the folder, which later runs keep. Open the logs with an editor run as administrator instead, to leave the folder as it is.
- The Control Panel not run as administrator - on a Windows client, where an administrator's programs run unelevated - says Could not read the file on its live log page and its message-store check, where it would have said there was no log folder; what it does over COM is as before. On Windows Server the built-in Administrator account runs it elevated already.
- A script, a backup program or a monitoring agent that read the logs or the message
store as an ordinary account needs an entry of its own -
icacls "<folder>" /grant "<account>:(OI)(CI)RX"- which the next run keeps. - An editor that saves
hMailServer.INIby writing a new file and renaming it over the old one gives itBin's permissions again: runhMailServer.exe /ProtectFoldersafter, or the next upgrade does.
On SQL Server reached with Windows authentication - [Database] Type=MSSQL and no
Username - the server signs in as the account the service runs as. LocalSystem
is NT AUTHORITY\SYSTEM to a SQL Server on the same machine; the virtual account
is NT SERVICE\hMailServer, which has no login there until one is made, and no
user in the hMailServer database, which can only be made once the database
exists. The verb makes what is missing of the three, on a connection of its own,
as the account that runs it:
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = N'NT SERVICE\hMailServer') CREATE LOGIN [NT SERVICE\hMailServer] FROM WINDOWS;
USE [hMailServer];
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = N'NT SERVICE\hMailServer') CREATE USER [NT SERVICE\hMailServer] FOR LOGIN [NT SERVICE\hMailServer];
ALTER ROLE db_owner ADD MEMBER [NT SERVICE\hMailServer];
(hMailServer being the database [Database] Database names.) It looks first and
leaves what is there - a login, a user under another name for the same login, or
the login as the database's owner - and takes nothing away. Each step is one line:
made, or there already. It makes no user and changes no role in a database
that has DDL triggers of its own (sys.triggers, enabled, on the database): such
a trigger runs with the rights on SQL Server of whoever's statement fires it, so
anybody who was ever db_owner there - an earlier server's login, another
program's - could have left one to run as the administrator, server-wide. The
verb then says so, and a SQL Server administrator reads the triggers before
running the statements. Run it from an elevated prompt in the Bin folder as
an account that may make logins on that SQL Server (securityadmin or sysadmin)
and that can open the database; an administrator who is a SQL Server administrator
through the Administrators group is one only when elevated. The installer runs it
as the administrator running setup, before it moves the service (above), and so
does DBSetup when it finishes a new installation. When it cannot do a step, it says
which and why, and prints the statements above for a SQL Server administrator to
run, with the sqlcmd line that runs them. It says everything on its own output
and writes nothing to the error log or the Application log: a connection the
account running it cannot make would otherwise be reported there as the server's
own failure to reach its database (HM5028), and the log folder is the service's.
A SQL Server on another computer is left alone: there the virtual account signs
in as this computer's own account, DOMAIN\COMPUTER$, as LocalSystem does, so the
login that worked keeps working, and a login named NT SERVICE\hMailServer could
not even be made there. The verb compares SQL Server's MachineName and
ComputerNamePhysicalNetBIOS with this computer's name, and says which it found.
Exit codes: 0 the service's account can open the database - its login, user and
db_owner made now or there already - or needs nothing here, which the lines say
(not SQL Server with Windows authentication, or SQL Server on another computer); 1
one of them could not be made; 2 hMailServer.INI names no database, or the
database could not be opened as the account that ran it. Run before
--service-account virtual /Register with &&, it moves only a service that will
open its database.
The file keeps four kinds of thing, and under NT SERVICE\hMailServer the service
writes none of them. What each writer does instead:
| What wrote the file | Under the virtual account | How |
|---|---|---|
The [Settings] copy of the settings store, at every start, reinitialise and save (IniSettingStore) |
Not written. The database is the settings store and its values are in use, as ever | Said in the application log, not as an error, with the settings whose line there is older than the stored value counted and named, at a reinitialise where application logging is on. At the service's start the line is dropped: the settings store is reconciled before the server has read its logging settings (Application::InitInstance runs IniSettingStore::Synchronize before Configuration::Load). A setting removed while the file still has its line is kept stored as its default - which the server treats the same - because a dropped row would be put back from the line at the next start; a key this build does not read is refused with a sentence. Each row records what the file really holds, so an old copy is never taken for an edit, and an edit is reported once, as HM5804. A backup restored stores its settings in the database |
PasswordPepper, kept in the file only |
Refused with a sentence | An administrator sets it in the file with an elevated editor and restarts the service |
The live update's trust - UpdateSigningIdentity, UpdateSigningIssuer, UpdateSourceRepository, UpdateTrustRootsFile and UpdateLogPublicKeyFile - read from the file alone from 6.3.6 |
Refused with a sentence, under every account: the process an update is checked for may not choose what it is checked against (The Update Feed, "Where the trust is read from") | An administrator sets them in the file with an elevated editor and restarts the service |
[Directories] - COM Directories.*
|
Refused with a sentence | An administrator changes the file and approves a new folder for the service |
[Database] - COM Database.CreateInternalDatabase, CreateExternalDatabase, SetDefaultDatabase, which DBSetup and DBSetupQuick call |
Refused before anything is made | The installer does its database step under LocalSystem (above). To change the database later, move the service to LocalSystem for the change and back, or edit [Database] and restart |
[Security] AdministratorPassword - COM Settings.SetAdministratorPassword, the Control Panel's Administrator password card |
Refused with a sentence |
hMailServer.exe /SetAdministratorPassword, from an elevated prompt, reads the password from standard input and writes the hash the card writes; restart the service. From PowerShell: $p = Read-Host -AsSecureString 'New administrator password'; [Net.NetworkCredential]::new('', $p).Password | & '.\hMailServer.exe' /SetAdministratorPassword
|
[Security] AdministratorTotpSecret - COM Settings.EnrolAdministratorTOTP and DisableAdministratorTOTP, POST and DELETE /api/v1/administrator/second-factor
|
Refused with a sentence (409 over REST) | To turn it off, delete the line with an elevated editor and restart. Enrolling needs an account that may write the file: the server makes the secret and hands it out once, so move the service to LocalSystem for the enrolment and back |
Bin\hMailServerApiKeys.ini, a file of its own, is still written (above).
The server decides by asking the file - opened for writing and closed at once -
not by the account's name, so a service that may write it (LocalSystem, an
account an administrator granted) behaves as before. The Linux packages already
install /etc/hmailserver/hMailServer.ini as 0640 root:hmailserver; the same
handling applies there, and a setting saved is no longer an error at every start.
The [Settings] copy is stale for anything that reads the file itself rather
than the database: the server when its table cannot be read, the installer (for
ShutdownDrainSeconds), the Control Panel when it has no COM connection, and a
person opening the file.
hMailServer.exe --service-account virtual|localsystem|keep|DOMAIN\name$ /Register.
Without --service-account it keeps the account a service has, and makes a new
one LocalSystem. DOMAIN\name$ is a group managed service account, which has no
password. Any other account has one, and is refused: it is set in services.msc
(the service's Log on tab), and every upgrade keeps it.
ServiceAccountName and ServiceAccountPassword are no longer used: the server still
loads them with its other settings (IniFileSettings.cpp), and nothing reads them. They were
what /Register used, and they are written by others: they are rows of the
settings store, which hMailServer administrators - who need not be Windows
administrators - set over REST and COM, and which the service writes; and the file
holding their copy is written by whichever account last could. A registration
that read them would let any of those make the next elevated /Register give the
service LocalSystem back. Asking whether the file is writable by administrators
alone would answer for one of the two places the value comes from, so the value is
not read at all. The Control Panel no longer edits them; its Setup checklist page
asks for a stored password to be deleted.
hMailServer.exe is the service, and also the commands an administrator runs:
Setup, DBSetup and the Control Panel run /Register, /RegisterTypeLib,
/Unregister, /GrantServiceAccess, /ProtectFolders, --grant-database-login,
--apply-new-install-defaults, --apply-profile, --create-stored-secrets-key,
--set-admin-password and the live update's two elevated, and an administrator
runs the rest - --upgrade-database, the stored-secrets commands, the store and
object-store commands, --apply-environment, --check-database - from an
elevated prompt. Each reads [Database] or [Security] from hMailServer.INI, or
changes the service, so each runs with the administrator's rights; and the folders
the service is granted Modify on - Logs, Data, Temp, Database - are folders
a program running as NT SERVICE\hMailServer can put a junction or a mount point
in. A file an administrator's process wrote there by its path could be written
wherever that leads.
So, since 6.3.6:
-
No log, no event, no crash record. Every command line but
RunAsService,/Debugand/Testforbids the logger's writes as the first thing it does (ForbidServiceFolderWritesinhMailServer.cpp,Logger:: ForbidWritesForThisProcess): no line reaches the error log or any other log, the Application log, theOnErrorevent script or the SQL log device; and it arms no crash oracle, whose record and dump go to the log folder. What each command did, and why it failed, is on its own standard output and standard error, where it already said it; Setup, DBSetup and the Control Panel keep that in their logs./Registersays there why a registration failed. Until 6.3.6 only the live update's verbs, the grant and--grant-database-logindid this; the others wrote their errors to the error log, and every one armed the crash oracle. -
No junction followed where Windows can refuse it. On Windows 11 22H2 and
Windows Server 2025 and later the same commands follow no junction that a user
who is not an administrator made (
ProcessRedirectionTrustPolicy); an older Windows refuses the request, and the next two rules hold there alone. -
The new installation's certificate is made through held folders.
--apply-new-install-defaultswrites the self-signed certificate and its private key into the data folder'sCertificatesfolder. It holds every folder of that path open from the drive's root down - none renamed or removed while held, and none a link but one the administrators alone control (below) - makesCertificatesand each file relative to the folder above, never through a link of its name, and checks each file it made to be in that folder before writing a byte to it. The key's permissions - SYSTEM, the Administrators, the account running it and Read for the service's account - are given when it is made, not set by its path later. -
A store command finds no link in the data folder that somebody else controls.
--compress-store(and--decompress),--encrypt-store,--decrypt-store,--recount-store,--measure-store,--copy-store-from-object-storageand--copy-store-to-object-storagereach each message by its path. Before anything is read or changed the data folder is held from its drive's root down and every folder and file in it listed once; a junction, a mount point or a symbolic link on the way to it or in it that the administrators do not alone control stops the command, exit 1, naming it and why. A file that is only tiered or deduplicated is not a link. A copy to the object store that removes nothing is held to it too: it reads with an administrator's rights and hands what it read to a store the service's account reads. So is--measure-storesince the re-check of 6.3.6, although it changes nothing and says what it read to the administrator alone: it reads every file with an administrator's rights, and one rule for every store command leaves no exception to reason about. -
A link the administrators alone control is gone through. Keeping the data on
another drive through a junction or a volume mount point an administrator made -
Dataitself, a folder above it, or a domain's folder in it - is an ordinary layout. Such a link is gone through when it is owned byBUILTIN\Administrators,SYSTEMorTrustedInstaller- an account without an administrator's rights cannot make one so owned - nobody else may change it (write its data or attributes, which is how a link is pointed elsewhere, add to it, delete it, or change its permissions or owner), and no entry in the permissions of the folder it is in lets anybody else remove it from there or change that folder's permissions or owner (OnlyAdministratorsControl,ProtectedFile.h). The path it holds - where it says it leads, read from the link - is then held in the same way from that volume's own root and checked to be the folder the link opened, so a link somebody else made further along is refused there, the folder the link leads to itself included: until 6.3.6's re-check the folder an open through the link ended at was walked instead, and that open follows a second link unasked. A link to a file is never gone through. What a refusal says names who may change the link, or who owns it. A link made in the data folder inherits the write rights setup grants the service's account there, and one made in a folder at a drive's root inherits them forUsersorAuthenticated Users, the service's account among them: either way the service could turn it elsewhere, and it is refused until it is given permissions only the administrators may change. An elevated prompt on Windows Server makes a link owned byBUILTIN\Administratorsalready; where the owner is the administrator's own account instead (a Windows client without UAC's split token, for one), give it to the Administrators too. For the link (and, for a mount point, the folder it is mounted on) -/Lacts on a symbolic link itself rather than where it leads:icacls <link> /setowner *S-1-5-32-544 /Landicacls <link> /inheritance:r /grant:r *S-1-5-32-544:(F) *S-1-5-18:(F) *S-1-5-32-545:(RX) /L- or make it below a folder only they may write, such as the program folder. The
service still reaches the mail through it: what is below the link is reached with
the permissions of the folder it leads to; only the link itself, which the service
then may not remove or change, is held to the link's.
A folder no administrator may list -
System Volume Information, when the data folder is a drive's root - is left unlisted when the administrators alone control it in the same way: nobody else can have put anything in it. Until the re-check of 6.3.6 every link and every such folder stopped the command.
- or make it below a folder only they may write, such as the program folder. The
service still reaches the mail through it: what is below the link is reached with
the permissions of the folder it leads to; only the link itself, which the service
then may not remove or change, is held to the link's.
A folder no administrator may list -
-
A store command reaches no file outside the data folder. The names it
rewrites, removes and copies over are the database's - each message's file name,
each deleted item's, each held part's - and, on an object store, its keys: both
are the service's to write. A row whose file is outside the data folder - a full
path elsewhere, or a part that is
..- stops every store command, naming the message, before it reads or changes a file; a key whose name would lead out of it is not listed; and a held part's name is its SHA-256 in hex or names no file. A full path below the folder a junction or mount point of the data folder leads to- written when the data was there by that name - is in it. A full path elsewhere
is usually one a server before 5.4 wrote: put
DataFolderback to the folder it names, run the Data Directory Synchronizer there (Addons\DataDirectorySynchronizer; the diagnostic Test message file locations says whether any are left), which turns each into a name below the data folder, and move the data as Relocating an Installation says; or delete the message.
- written when the data was there by that name - is in it. A full path elsewhere
is usually one a server before 5.4 wrote: put
What is left: a program that still runs as the service's account after the service
has stopped could make a link while a long store command runs, after the folder was
listed - or, owning a folder in the data folder, as it owns those it made, give
itself the right to remove an administrator's link from it and put one of its own
there; on Windows 11 22H2, Windows Server 2025 and later the command does not follow
it, and the next command's listing refuses it everywhere. An account holding the right to restore files (SeRestorePrivilege, Backup
Operators) can give a file any owner; the service's account holds none, and one
configured to would be an administrator in all but name. The SQL Server Compact database file in Database is opened by its path by the
commands that open the database; that engine opens only a file that is a database,
and creates none.
build/check-elevated-verbs.py holds the commands to these rules and lists them:
it refuses a command line added to the three that may log, a dispatch before the
decision, the crash oracle armed for any other, the certificate written by its path,
and a store command - --measure-store included - or a copy dispatched without the
check or behind a condition. --self-test breaks each rule in a copy and proves the
check reports it.
The package runs the service as hmailserver (the unit's User=), which owns the
data directory /var/lib/hmailserver and the log directory /var/log/hmailserver
and so can put a symbolic link anywhere in them. Since the re-check of 6.3.6:
-
The commands that work in those folders run as the service's account. The
store commands (
--measure-store,--compress-storeand--decompress,--recount-store,--encrypt-store,--decrypt-store), the copies to and from an object store,--sample-domain,--apply-profile,--revert-profileand--apply-environmentare run as that account, as their help says. Run as root (sudo hmailserver --compress-store), each now becomes it - the data directory's owner, with that account's groups - before it opens anything, says so on standard error, and so reaches only what the service reaches, through any link, and leaves no file behind that the service cannot open. A data directory that cannot be examined refuses, exit 2. So does one whose way is not root's and its owner's alone - a folder above it, or above where a symbolic link on the way points, that belongs to another account or that another account or group may write - since an account able to put a link there could choose which folder, and so which owner, the path leads to; the refusal names the folder. The package's/var/lib/hmailserveris below folders only root writes. Until then they ran as root, held only to name no file outside the data directory, and followed a symbolic link the service's account put in it. -
Over a data directory that belongs to root, they run as root where nothing but
root could have put a link. A service run as root - a source build or a
container without
User=, or the Linux regression suite - has a data directory of root's, and a command run as root there crosses no boundary, provided no other account could have put a symbolic link where it goes. So it runs on as root, and says so on standard error ("Run as root: ... runs as root, the data directory ... and everything under it being root's alone ..."), when every one of these is root's alone: the data directory, every folder under it, and every folder a symbolic link under it leads to; the log, temporary, event and database folders ([Directories]LogFolder,TempFolder,EventFolder,DatabaseFolder, each that is set - every command opens the database through the server's start, which can write into any of them, and the store commands take their lock in the log directory), and everything under each; the way to each, as above with root for the owner; and the configuration that names them all ---config's file, or the one found - and the way to it, since the command reads it again as it starts and writes its settings back to it, and an account that could change it could point the folders at a tree of root's of its choosing. Root's alone means each folder (and the configuration) belongs to root and no other account may write it: no write for others, and for its group only when that is root's group and no access control list stands behind the group's bits; no folder carries a default access control list that gives another account - or, on a set-group-ID folder, a group other than root's - a write to what is made in it; and each symbolic link belongs to root and leads where the way is root's alone. The folders are walked withlstat, following only links that pass; a file is not judged, since in such a folder no other account can put one there or replace one with a link. A folder that is not there yet is judged by the folder that would hold it. What the command then makes is root's alone too: it runs with at least every other account's write taken away by its umask (022), whatever umask it was started under. Anything else - a folder of another account's in the store, one a group other than root's or every account may write, a link another account owns, a log directory of the service's account's beside a data directory of root's, a configuration another account owns or may write, a log directory below Ubuntu's/var/log, which thesysloggroup may write - refuses, exit 2, naming the first folder, file or link that fails and what to do: give it to root and take the other accounts' write from it (chown root:root,chmod go-w,setfacl -bk), name folders that are root's alone in the configuration, or give the data directory to the service's account, as the package does, so that the command runs as that account. Until the re-check of 6.3.6 a data directory of root's refused every one of these commands, which stopped administrators of such a service and kept nobody out. -
Every other command line run as root writes nothing in the log directory.
--set-setting, the stored-secrets commands,--create-database,--upgrade-database,--set-admin-password,--check-database,--verify-updateand the rest need root for the configuration or say only what they read; each says what it did, and why it failed, on its own output. What would have gone to the error log is not written, but for the stored-secrets code'sHM7496andHM7497, which go to standard error (Secrets at Rest). The server itself, and a command run as the service's account, log as before. -
--create-databasemakes.new-installationnew. Anything at that name in the data directory is removed first - a symbolic link itself, not what it names - and the file is then created exclusively, never through a link (WriteNewFileNoFollow); the stored-secrets key file was made so already (DataProtector::CreateKeyFileFor).
Every right the service used that only LocalSystem - or an administrator - has, and what changed:
| What | Under LocalSystem | Under NT SERVICE\hMailServer
|
How |
|---|---|---|---|
| Its own folders | Anything on the machine | The grants above | /GrantServiceAccess |
hMailServer.INI |
Written | Read only | The section above |
| A file you name that is outside them - a certificate, a key, a DKIM key file, a message to import, a backup to restore | Read wherever it is | Read only where the account may | The grant lists the configured ones as OUTSIDE; move them into Data\Certificates, or approve their folder for Read |
The Windows event log's source (HKLM\SYSTEM\...\EventLog\Application\hMailServer) |
Registered at the first error forwarded | Cannot write HKLM |
/Register, which runs elevated, registers it; the running service only reads it (WindowsEventLog::RegisterSource). Writing to the Application log itself is allowed to every service |
The COM AppID (HKCR\AppID) |
Re-written at every start | That write fails, silently and harmlessly |
/Register writes it, as it always has |
| The DCOM access and launch permissions | Unchanged | Unchanged | They are about the callers - the Control Panel, scripts - who stay the administrators |
Changing its own service entry (COM Utilities.MakeDependent, which DBSetup uses to depend on a local database service) |
Worked | Refused | The installer sets the database up while the service is LocalSystem; DBSetup run later falls back to sc.exe config hMailServer depend= RPCSS/<service> itself, as an administrator. The right is deliberately not granted: a service that may rewrite its own entry may make itself LocalSystem again |
| The live update | The helper runs the installer | The service cannot run it | The update is still checked, downloaded and verified; an administrator installs it with Install update in the Control Panel on the server, after Windows' prompt (below) |
The auto-ban's Windows Defender Firewall rules (AutoBanFirewall) |
Written | Refused | Writing a firewall rule takes an administrator and there is no narrower right to grant. Said once in the error log (HM7460) and in the checklist; banned addresses are still refused by the server. AutoBanCommand still runs - as the service account, so a command that asks a router or a cloud firewall works and one that runs netsh locally does not. Rules a LocalSystem run left behind cannot be removed by the new account, and are named once (HM7461) with the PowerShell that removes them |
| A port | Any | Any | Windows lets any account bind any port, and the firewall's rule is per program: nothing to grant |
| The secret store | Machine-scope DPAPI | The same | Any account on the machine can decrypt it - which is why shared hosting stays declined |
| SQL Server with integrated authentication, on the same machine | The login NT AUTHORITY\SYSTEM
|
The login NT SERVICE\hMailServer
|
hMailServer.exe --grant-database-login (above) makes it, a user for it in the hMailServer database and db_owner - which the installer runs before the move, as the administrator running setup; where that account may not, the service stays on LocalSystem and setup gives the statements for a SQL Server administrator |
| SQL Server with integrated authentication, on another machine | The computer account, DOMAIN\COMPUTER$
|
The computer account, as before | A virtual account is the computer account on the network, so the login that worked keeps working. For an identity of the server's own rather than the whole computer's, run the service as a group managed service account (--service-account DOMAIN\hmail$ /Register) and give that a login |
| MySQL, MariaDB, PostgreSQL | A user and password | The same | Nothing changes |
| A backup written to a network share | The computer account | The computer account | Grant DOMAIN\COMPUTER$ on the share and its folder, as for LocalSystem |
| A backup or archive folder on this machine, set after the move | Written | Written once approved | Grant access lists it as not approved and asks |
| A custom virus scanner, ClamWin | Ran as LocalSystem | Runs as the service account | It reads the message in the temp folder, which is granted, and its own program and signatures, which the Users group can normally read. A scanner that needs more has to be granted it |
ClamAV's clamd, SpamAssassin's spamd
|
Over TCP | Over TCP | Nothing changes: the message is streamed, not named by path |
| Event scripts | Ran with LocalSystem's rights | Run with the service account's | A script that writes files, starts programs or reaches a database with integrated authentication can do only what the account can |
Active Directory sign-in (LogonUser, LDAP bind) |
Worked | The same | No privilege is needed for a network logon. Kerberos acceptance with no explicit account (GssapiAcceptor) uses the computer account's keys under both; this is expected and not yet exercised on a domain-joined bench |
| Crash dumps | Written to the log folder | The same |
hMailServer.Minidump.exe runs as the service account and writes to the granted log folder |
| "Log on as a service" | Implicit | Held through NT SERVICE\ALL SERVICES
|
A domain policy that replaces the right has to keep that group, or the service reports error 1069 and does not start |
The helper that applies an update (hMailServer.Updater.exe) is started by the
service and runs as the service's account, and the installer it runs needs an
administrator. On LocalSystem - an upgraded installation that kept it - nothing
about the live update changes: the service hands the installer to the helper, in
the update window or when Install update is clicked, as
The Update Feed describes.
Under an account that is not an administrator the service still checks for the update, downloads it and verifies it, and from 6.3.6 an administrator installs it with one click: Install update on the Updates page of the Control Panel on the server (on Server status in 6.3.6). Windows asks for permission once; everything after that happens by itself, and the Control Panel comes back signed in, on the new version, when the service is running. The service never runs anything as an administrator, and nothing it can write decides what the administrator's side runs.
-
Windows' prompt. The Control Panel starts
Bin\hMailServer.exe /InstallUpdateof its own installation elevated (runas), for the release the Status page shows. Only a Control Panel on the server does this: one connected to another computer says to open the Control Panel there, and so do the Control Deck andPOST /api/v1/update/install(409, codeupdate_install_by_administrator). A Control Panel that is not the installation's own - a copy run from elsewhere - says to open the installation's. If the prompt is declined, nothing has changed anywhere. -
The elevated stage checks the installation. It finds the installation from the
service's registration and
hMailServer.INI, which only administrators can write, never from what the service says, and checks that nobody but SYSTEM, the Administrators and TrustedInstaller can change what an administrator's process will run or read there: the folders above the program folder and the program folder itself (deleting in them, deleting them, or changing their permissions or owner), andBinandControlPanelwith every folder, program and library in them,hMailServer.INI,AdminUpdateand the trust fileshMailServer.INInames (any write). Something others can change stops the install before anything has changed, and the Control Panel names the folder, the account, the right and theicaclscommand that removes it: a program run from there as an administrator could be one that account put there.hMailServer.exe /ProtectFoldersgives the program folder,BinandControlPanelthe permissions this passes, and says the rest of what it finds ("The program's own folders", above). The same rule is checked by the Control Panel before the prompt, from a table of cases both sides are tested against (build/testdata/install-permissions-cases.json). -
It copies the installer out of the service's reach.
AdminUpdate\<id>in the program folder - owned by the Administrators, SYSTEM and Administrators alone may change it, the administrator who clicked may read it - is made new for each install, and the installer and its Sigstore bundle are copied into it fromData\Updates, which the service writes.AdminUpdateitself lets anyone read its permissions, and nothing else of it, so that the Control Panel of every later install can hold it to the rule before the prompt. A name that leads through a link or a junction, a folder where a file should be, or a copy bigger than 1 GB is refused. The copies are held open, so that nothing can change them between the check and the install, and everything below is checked on the held bytes. -
It checks the installer itself, trusting nothing the service said about it:
- the Authenticode signature, the primary one, with Windows' own
verification, and the publisher pinned: the certificate names exactly
CN=Progressive Robot Ltd, O=Progressive Robot Ltd, L=Chester, S=Cheshire West and Chester, C=GB, its chain ends, by SHA-256, in Microsoft ID Verified Code Signing PCA 2021 and Microsoft Identity Verification Root Certificate Authority 2020, it is issued for code signing, and the signature carries a timestamp. A file that Windows trusts but another publisher signed is refused. Where the installedhMailServer.exeis signed by somebody else - a site that builds and signs its own - the installer is held to that signer instead, the same name through the same root, and where the installed one is not signed at all there is nothing to hold it to, and the one-click install is refused. A name is compared whole: one with a NUL in it is refused, never read up to the NUL; -
revocation, online, of the whole chain, within 30 seconds: a certificate
that is revoked is refused, and so is one whose revocation could not be
checked - said as the revocation server not being reachable, never as revoked.
Windows' own answer is not enough for that: its Authenticode policy takes a
revocation server that does not answer as good, so the stage reads the
revocation status of each certificate in the chain Windows built, the root
aside, and refuses where one is unknown or was checked offline (on the
installer smoke VM before 6.3.6 was released, a revocation server held
unanswered let an installer through as "revocation checked online"; with
its outbound HTTP blocked, Windows answered 0 for the real 6.3.5 while each
certificate of its chain below the root said its revocation was unknown and
checked offline, and with HTTP open and nothing cached the same check
fetched Microsoft's lists and passed). The chain read is the signer's; the
timestamp's own chain is left to Windows' answer. So whoever can keep this
computer from the revocation server -
www.microsoft.com, where Microsoft publishes the lists for the project's certificates - can stop the one-click install: a firewall that lets out only the download, a proxy the check cannot pass, or somebody on the network between. That is a denial, refused before anything is changed, never an install of anything else; the installer can still be run by hand, where Windows' own check, which takes an unreachable revocation server as good, is what applies; - the version in its version resource is the release offered, the
architecture in its description (
hMailServer Setup (x64)or(ARM64)) is this installation's, and the release is newer than the running server, whose version is compiled into the stage; - the Sigstore bundle - the pipeline for that release's tag, and the
transparency log - checked by a second copy of
hMailServer.exethat the stage starts with no privilege but change-notify, the Administrators group and every other group UAC takes from an administrator (Backup Operators, Domain Admins and the rest) made deny-only, at low integrity, in a job that holds it to one process and 512 MB, and that is given the bundle and the installer's SHA-256 through a pipe and nothing else. The bundle is parsed there, not in the elevated process, and a child that a crafted bundle took over could write nothing of the administrator's: at medium integrity, the administrator's own unelevated level, it could still have written their profile and registry, which their programs and Windows' auto-elevating ones read. Its answer is not what the install rests on - the Authenticode pin is.
- the Authenticode signature, the primary one, with Windows' own
verification, and the publisher pinned: the certificate names exactly
-
The service prepares, on the administrator's request (COM
Status.PrepareUpdateForAdministrator, asked from this computer only): it checks its own download again, takes the backup (UpdateBackupBeforeApply, to the destination Backup, restore & migration sets) and fetches the running version's installer as the rollback image. The stage copies the rollback image out and checks it as it checked the installer, held to the running version. A backup that fails asks the administrator whether to install without one; with no destination set, the window says the rollback puts the program back and not the database. Nothing elevated takes anything from the service's answer: the service's part is the backup, the drain and the token. -
The install begins (
Status.BeginUpdateForAdministrator): the service issues the database upgrade's single-use token, closes its listeners and lets the mail in progress finish (the drain), and the stage hands over to a copy ofhMailServer.Updater.exein the work folder, checked by its SHA-256. The Control Panel closes, so that the installer can replace it. -
The Updater installs as the administrator: it ends every program still
running from the installation, runs the held installer silently - the token in
its environment, not on its command line - with its temporary files in the work
folder, waits for the service to come back, runs the held rollback image if it
does not, or if the installer ends with 10, but never once the installer's
database step has changed the database (exit code 11,
upgraded-incomplete; Upgrading Guide, "If the upgrade fails"), and asks Windows to hold off a sign-out or a shutdown while it works (the reason shows on the sign-out screen). It writes the outcome into the work folder, which only administrators can write, puts an entry in the Application log (HM7466 when it starts, HM7467 with the outcome), and opens the Control Panel again as the administrator who clicked, not elevated: as a child of that user's desktop shell,explorer.exein the same session, with the shell's own token. -
The Control Panel comes back signed in, from a handoff it wrote before it
closed - DPAPI for the Windows user who clicked, bound to a key only that user
and the Administrators can read in the work folder, good for 30 minutes and
deleted when it is read - and shows the outcome. Where the administrator
password has a second factor it asks for the sign-in instead. The service
reports the outcome at its start, in the application log and the audit trail
(object type
update, interfaceWindows, the Windows account that approved it; AlertsAndAuditTrail.md).
The whole of the elevated side writes only into the work folder, never into
Data, Logs or anything else the service can change, and hMailServer.exe loads
the libraries it links from Bin and System32 only (/DEPENDENTLOADFLAG). A
stage that finds another administrator's install under way on the server says so
and does nothing. {app}\AdminUpdate is removed by the uninstaller.
The token the installer's database upgrade presents is in the Updates folder, in a
file the service can write (ProtectedFile: SYSTEM, the Administrators and the
service), and a token lets its holder sign in over COM as the server's
administrator. That is not a way for the service to reach an administrator: a
service that could forge it holds everything the server's administrator can do
already, and redeeming it runs nothing elevated. In an administrator's install a
service that rewrote it could at worst make the genuine installer's database
upgrade fail, and the Updater put the previous version back - a denial, not a
compromise. The Updater reads the file only if it is no link, sits where it is
expected and holds exactly 64 lowercase hexadecimal characters, and gives it to a
6.3.6 or later installer in its environment (HM_UPGRADE_TOKEN), so that it never
appears on a command line - nor in the installer's log, which Inno Setup begins with
the command line it was started with; the installer reads it and removes it from its
own environment at once. The live update of a service running as LocalSystem gives it
the same way from 6.3.6; up to 6.3.5 it was on the installer's command line, and so
in Updates\install-<version>.log - a single-use token, spent once the installer's
database step had run, in a folder only SYSTEM, the Administrators and the service
can read.
A scheduled task running as SYSTEM, which the service could trigger, was considered and not built. The only file it could run is one in a folder the service writes, so to be safe it would have to copy the installer somewhere the service cannot reach, verify it there itself - not trusting the service's word, which is the whole point - pin the publisher, refuse an older version, and write its log and outcome nowhere the service could turn into a link to a system file. That is a second updater running as SYSTEM, and a service that can make SYSTEM run a file has given back what the account took away.
The administrator's install does every one of those checks, and differs from the task in what starts it: an administrator's click at the server, and Windows' prompt, which the service can neither cause, show nor answer. No elevated program waits for the service or listens to it; the elevated stage starts from the click or not at all, and what it runs is what it checked itself.
A server on 6.3.4 or 6.3.5 under this account cannot install 6.3.6 or a later
release this way: the refusal is compiled into those versions, and their
Install update asks the service alone. It is installed by hand once - download
the installer from the release page, check that Get-AuthenticodeSignature
reports it Valid and signed by CN=Progressive Robot Ltd, and run it as an
administrator, keeping the service account on the installer's page. Windows
SmartScreen may warn about a file downloaded with a browser that it has not yet
seen often; the signature is what to check. From 6.3.6 on, each update is the one
click above, and never meets SmartScreen: the installer is not downloaded by a
browser, and the Updater starts it directly.
The unattended update window does not install under this account: when the window is open and an installer is verified, the error log says once per process that an administrator installs it with Install update in the Control Panel on this server (HM7463), and no backup is taken for an install that cannot happen.
-
The checklist (
GET /api/v1/setup/checklist, SetupChecklist.md): the tenth item,service_account.service_account_local_system(on Linuxservice_account_root) is action needed while the server runs as the most privileged account - a recommendation, as the missing profile is, never an error;service_account_ownnames any other account and is done. Under an account that is not an administrator,autoban_firewall_not_permittedis added whileAutoBanFirewallis on, andupdate_apply_by_administratorwhile the update check is. The server judges from its own token, not from what the service manager was asked for. - The Control Deck's dashboard shows the same finding, as information, while the server runs as LocalSystem or root, with a way to the checklist.
-
The Control Panel, Server limits & expert settings, card Windows service
account: what the service runs as, and three buttons, each run after the
elevation prompt with what it said shown afterwards - Grant access
(
--confirm /GrantServiceAccess), Move to NT SERVICE\hMailServer (the same grant, then--service-account virtual /Registeronly if it ended in 0) and Move to LocalSystem (--service-account localsystem /Register). External setup carries the same item, and asks for a storedServiceAccountPasswordto be deleted.
To move a service that runs as LocalSystem to the virtual account: run the
installer again and choose it, use the Control Panel's button, or from an elevated
Command Prompt in the Bin folder
hMailServer.exe --grant-database-login
hMailServer.exe --confirm /GrantServiceAccess
hMailServer.exe --service-account virtual /Register
net stop hMailServer && net start hMailServer
running each line only when the one before it ended in 0. The first matters only
for SQL Server with Windows authentication, and says "Nothing to do" otherwise;
the Control Panel's button does not run it, so on such a database run it, or the
statements it gives, before the button. To go back to LocalSystem:
run the installer again and choose Move it back to LocalSystem
(/serviceaccount=localsystem silently), use the Control Panel's button, or
hMailServer.exe --service-account localsystem /Register
net stop hMailServer && net start hMailServer
The grants and approvals stay behind and do nothing under LocalSystem;
icacls <folder> /remove "NT SERVICE\hMailServer" /T takes the grants away, and
deleting the ServiceAccessPaths key the approvals, if you want them gone.
| Code | Severity | Said when |
|---|---|---|
| HM7460 | Medium, once per process |
AutoBanFirewall is on and the account cannot write firewall rules |
| HM7461 | High, once per process | Auto-ban rules a LocalSystem run left in the firewall cannot be removed by this account |
| HM7462 | Medium |
/Register could not set the service's SID type to unrestricted |
| HM7463 | Medium, once per process | The update window is open and the update cannot be applied unattended by this account: an administrator installs it with Install update in the Control Panel on this server |
| HM7464 | Medium |
/Register was given an account it does not set - one with a password - and registered nothing |
| HM7465 | Medium | An administrator's install did not stop the service within 10 minutes of its start: the drain is left, the token revoked, and the server accepts mail again. The Updater's log in AdminUpdate says how far it got |
| HM7466 | Information, in the Windows Application log | An administrator's install has started: who, which version over which, and the work folder. Written by hMailServer.Updater.exe, with the administrator's account as the entry's user |
| HM7467 | Information, a warning or an error, in the Windows Application log | An administrator's install ended: installed, cancelled or refused (a warning), or not installed, rolled back, with its rollback failed, or installed with its database upgraded and a step not complete - upgraded-incomplete, not rolled back - (an error), with the reason |
| HM7468 | Medium | The service's preparation of an administrator's install failed: the step (the check of its download, the backup, the rollback image) and the reason |
| HM7469 | Medium, once per process | The outcome of the last administrator's install could not be read, or an install that began more than an hour ago wrote none - the Windows session it ran in may have been signed out |
None of HM7460 to HM7464 fires on a server as it is shipped: the firewall half of the auto-ban and the unattended update window are both off until they are switched on. HM7465 to HM7469 come only from an administrator's install. An administrator's install that did not succeed is also reported as HM5480, as the unattended apply's is - and one whose database step changed the database before a step did not complete, which nothing puts back, as HM5481 (Upgrading Guide, "If the upgrade fails").
-
ServiceAccountTester(Common/Util/ServiceAccount.cpp), in the internal suiteUtilities.RunTestSuiteruns: the service SID against the one Windows publishes for TrustedInstaller; which folders a grant may reach, on a machine made up for the purpose -[Directories] DataFolderpointed atBin, at the program folder and atData\..\Bin, the Windows folder, Program Files, a profile, a share, a device path, a stream, a drive's root, all refused; the program's own four and a folder of its own on another drive, not - and the plan made from them: a malicious data folder refused and not granted, an unapproved backup folder listed, an approved one granted, an approval of the Windows folder refused, a key file outside listed and one inside read on the file alone; the logon/Registerpasses for each--service-account, an account with a password and LocalSystem under another name refused; a grant on a directory of its own under the temp folder, made, made again with nothing changed, the one explicit entry it leaves, handed down to a file that was there before and one made after, an administrator's own entry kept; a file with protected permissions granted without losing its protection or its other entries; a hard-linked file refused; a path through a symbolic link refused where one can be made; a path through a junction only the administrators control held to where it leads, the junction and that folder not removable while held and the grant's path the volume's name of it, one the service's account may change refused naming that account, also where the administrators' junction leads to it, one leading to a folder no grant may reach refused, a folder missing beyond one found missing, and a grant through one made on the folder it leads to, handed down, that folder keeping what it inherits from its own folder and the junction still the administrators' alone (skipped only where the tester is no administrator); a held folder that cannot be removed; and/ProtectFolders(TestProtect_) on folders of its own that hand down what Program Files hands down - BUILTIN\Users' Read - beside a deny entry and Read for the service's SID: Users no longer reach the folder, a file already in it or one made after; the deny kept as the folder's own and first; an administrator's entry and the service's grant kept; SYSTEM and the Administrators given full control handed down; a file keeping the service's Read as its own, or given it where it had none; a second run changing nothing; a file with two names refused; and an entry of the folder's own for Users, given after, kept and still seen. And the program's own folders it hardens: in memory (TestHardenedShape_, which needs no administrator), the permissions built from what a Windows client's drive root, a Windows Server's, Program Files, an inherited Modify or Read for the service, a deny entry beside an administrator's entry, no list at all and a folder only the administrators may open hand a folder down, each held to the rule of an administrator's install of an update and passing it, nobody but SYSTEM, the Administrators and TrustedInstaller left to write, the Users' Read & execute given where every user could read before and no entry for them where none could, the dropped entries that gave a right to write named and the Users' Read not, the grant's Read, the deny (first) and the administrator's entry kept, and taken for hardened only when protected and the Administrators' to own; a folder a user made with entries of its own for itself and Everyone keeping them while the Administrators own it and losing them, named, when its owner is not trusted; and on the disk (TestHarden_), an installation of the tester's own under a folder that hands down what a client's drive root does - the program folder with the uninstaller,Binwith the server, a library,hMailServer.INIprivate and the key store,ControlPanel- which Authenticated Users may change and the rule refuses before, hardened: nobody but SYSTEM and the Administrators may change a folder, a file in it or one made after, Users still read and run them, the grant's entries and the key store's Modify kept, the rule passing every folder, program and library, a second run changing nothing, an entry an administrator gives Users afterwards kept and named by the rule, and aBina user owns given to the Administrators, the Modify that user gave itself taken away (where the tester runs as an administrator's own account). -
Installation/InstallerPackaging.cs: the page's three forms and its defaults,/serviceaccount=, the word/Registeris given and thathMailServer.exeknows it, that nothing readsServiceAccountNameany more, the registration before the database step and the move after it, the move only on a grant that ended in 0,--confirmin an attended installation, and a failed grant's own message; and/ProtectFoldersrun at every installation and upgrade, under no condition, after the registration and before the grant and the database step, its output in the setup log and its message suppressible, the verb taking the program folder's own six from where it is, holding each from the drive's root, and giving the service Read onhMailServer.INI; and the verb hardening the program folder,BinandControlPanelafter the six, through the pin, the program folder not while one of the six failed, with a fresh Read & execute for Users where every user could read before, nothing inherited and the Administrators as owner, then holding the installation to the rule of an administrator's install; setup's message naming them, the program folder the first[Dirs]makes and given its permissions by SID before anything else, and the folder page saying, in a suppressible box and the log, and never refusing, when the chosen folder lets every user change what is in it. -
API/RestApiSetupChecklist.cs: the tenth item, judged from the server's own token and, beside a running service, checked against the accountsc.exe qcreports. - The administrator's install:
AdminUpdateTester(Common/Util/AdminUpdateTester.cpp) holds every check of the elevated stage to its negative - the permission rule against the table of cases the Control Panel'sInstallationPermissionsTestsreads too,Binat a client's and at a Windows Server's drive root refused andBinand a program in it as/ProtectFoldershardens them passing among them; the work folder made new and an old one, one with others' rights and a junction refused; the copy through a junction, a link and a stream refused; the architecture marker; the pin alone, a subject with an attribute more or less, another chain, no code-signing use; and the real signed installers of 6.3.1 and 6.3.3 to 6.3.5 (put on the bench bybuild/fetch-installer-fixtures.ps1, checked by their SHA-256) passing, with 6.3.0 (unsigned), a byte changed, a file cut short or grown, the .NET runtime's installer (another publisher, trusted by Windows) and a catalogue-signed system file refused, the site-signer path held to the installed signer's name and root (the same name through another root refused), a name with a NUL in it refused, a revocation check with no time to run refused as not reachable, and a chain whose revocation status is unknown or offline below its root refused while one with only its root unchecked passes; and the lowered child's token read back - the Administrators and every group UAC filters deny-only, no privilege but change-notify, low integrity - its job's limit, its timeout and its output cap.AdminUpdateStageTesterholds the stage's request to its child (a round trip, and every malformed frame refused), the child's answers, the Updater's outcome file, and the real 6.3.5 bundle verified through the child as the stage runs it. The Control Panel'sAdministratorUpdateTests,AfterUpdateHandoffTests(a handoff without its key, another user's, an old one and a second read refused) andUpdateStatusTextTests;API/UpdateApply.cs, which on the bench's LocalSystem service holds the administrator's path shut, and where the service is another account holds the 409 and the state; andbuild/check-elevated-verbs.py, which fails when a verb that runs elevated reaches the log or the crash handler. The whole install, with Windows' prompt, is run by hand on the installer smoke VM. - The live registration - a new installation under the virtual account, an upgrade that keeps LocalSystem, a move each way, the question the grant asks - is exercised by hand on a throwaway machine; nothing here registers a service on the bench.
hMailServer 6.3.7, the latest release (17 October 2026, schema 6108) · AGPL-3.0-or-later · Repository · Mirror on GitHub · Report a documentation error (a GitLab issue naming the page)
Home — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- Profiles and Environment
- The Control Panel
- The Control Deck
- Your First Domain and Mailbox
- Connecting a Mail Client
- The Webmail
- DNS for Your Domain
- Migration
2. Secure it
- Encryption and Certificates
- Database TLS
- Encryption for People
- Stopping Spam
- Stopping Viruses
- Security Hardening
- Secure Defaults
- The Service Account
- Secrets at Rest
- Sign-in and Identity
- Security Policy
3. Operate it
- Monitoring and Health
- The SNMP Agent
- Backup and Restore
- Message Storage
- Compliance and eDiscovery
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- The Update Feed
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Rooms and Equipment
- Mailing Lists
- Routes and Relays
- Mail In to a Webhook
- Outbound Shaping
- The COM API and Scripting
- Event Scripts
- The REST API
- Command Line
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey
- Settings Reference
- Settings Overview
- Capabilities and Configuration
- Control Panel Reference
- Control Panel Pages
- Ports Reference
- Changes-Since-6210
- Version-History-to-6210
- Glossary
About this wiki
- hMailServer 6.3.7 is the latest release (17 October 2026, schema 6108)
- Pages say since 6.3.4 where a feature is new in it
- AGPL-3.0-or-later
- Repository, the one place the project is worked on
- Its mirror on GitHub, since 18 October 2026, with no Issues
- Questions: a GitLab issue with the Question template, GitHub Discussions Q&A, or the forum
- Report a documentation error: a GitLab issue naming the page