Skip to content

hMailServer 6.3.8

Latest

Choose a tag to compare

@RainbowWizard-coder RainbowWizard-coder released this 02 Nov 17:03
· 5 commits to master since this release
Immutable release. Only release title and notes can be modified.

Download

Windows, x64 hMailServer-6.3.8-x64.exe: the installer, Authenticode-signed by Progressive Robot Ltd. It installs a new server, or upgrades an existing one in place: read the notes below and back up the database and the data folder first. SHA-256 4b9b9c3102cbed743f6c826289c54cec25c395d02208e55cdd3219187fbf8654.
Windows on ARM64 (preview) hMailServer-6.3.8-arm64.exe, signed the same way. A preview: it has not been run on ARM64 hardware. A new installation is set up against Microsoft SQL Server or PostgreSQL, since SQL Server Compact has no ARM64 build (WindowsOnArm.md). SHA-256 e5ce357b9ca4e013529898c5208d43895fec3a397b1a4defa4cd905ee9b7f27a.
Linux The .deb and .rpm packages for each distribution and the two AppImages, under Assets below, in Packages; hmailserver-linux-6.3.8-SHA256SUMS.txt lists them.
Container image registry.gitlab.com/hmailserver/hmailserver:6.3.8, for linux/amd64 and linux/arm64, pushed by this release's pipeline after the release is published. :6.3 and :latest are moved to it too, unless a later version has either already.
Checking a download Each file the release pipeline made is signed with Sigstore, its bundle beside it as <file>.cosign.bundle, and hmailserver-6.3.8.intoto.sigstore.json is the release's SLSA build provenance (hmailserver-6.3.8.intoto.jsonl is its signed envelope alone, as one line of JSON); Verification, at the end of these notes, gives the identity to check them against (SECURITY.md, "Verifying a release").

hMailServer 6.3.8

Everything that landed on master after the v6.3.7 tag, from 17 October 2026 (compare v6.3.7 with
v6.3.8
). This is Progressive Robot's fork of
hMailServer. 6.3.8 is a security release: it fixes vulnerabilities the project found in its own code, each a work item
with a CVE request through GitLab's CNA, and the four narrower cases of work item 78 that 6.3.7's notes left open.
Between them they reach back to 6.0.0's first beta: upgrade every server running 6.3.7 or earlier. It was first
decided, while releases pause, because the published Helm charts of 6.3.6 and 6.3.7 did not start a server,
which only a release puts right, and that reason stands beside the fixes; it also puts right the container image, which
could not reach a MariaDB database, and the Windows installer, which ran over an installation of the original
hMailServer's 5.7, and it carries the finished work master holds (Why there is a 6.3.8, below). The schema stays at
6108, as 6.3.6 and 6.3.7 have it: 6.3.8 adds no upgrade step. What changes on upgrade without a switch follows the
Security section.

What it fixes, and for whom. Each is in its section below, with what changed and how it is tested. The
vulnerabilities first; their work items are made public, and their advisories published, once 6.3.8 is on the update
feed:

  • The database layer's test for the marker that lets a statement's error be ignored, and the log's and the trace's
    redaction of a statement's literals, read a literal as each engine does

    (work item 100; Security).
  • The auto-ban saves the IP range it adds under a name of its own, so that it bans the address
    (work item 102; Security).
  • A sign-in's name is matched to its account as the per-name lockout and the compromised-account hold count it
    (work item 104; Security).
  • The web services listener (CardDAV, CalDAV, Autodiscover) refuses an address an auto-ban or an IP range bans
    (work item 105; Security).
  • A failed sign-in at the web services listener through a proxy is no longer charged to the proxy's address
    (work item 106; Security).
  • A failed account sign-in at the REST listener through a reverse proxy on the same machine is no longer charged to
    127.0.0.1
    (work item 107; Security).
  • The REST API, and the reader of a report mailed to a domain's report address, read a JSON body in time and memory
    that follow its bytes
    (work item 110;
    work item 111; Security).
  • TlsMode=verify-full on MySQL and MariaDB checks the database server's certificate against the CA file TlsCaFile
    names
    (work item 112; Security). Its entry corrects
    what 6.3.7's notes said of a named TlsCaFile as the mitigation for CVE-2026-107819, MariaDB Connector/C's own flaw.
    On Linux the client is the distribution's, which should be MariaDB Connector/C 3.4.11 or later; where it is older and
    not patched, name the CA in TlsCaFile (DatabaseTls.md).
  • The database client's TlsMode=verify-full check of a database server on this machine, on MySQL and MariaDB
    (work item 113; Security).
  • On Windows the database clients check the database server's certificate against the root certificate file named, or
    refuse its name
    (work item 114; Security).
  • On Windows the files the server hands to OpenSSL by name are read from the file configured, or refused
    (work item 115; Security).
  • On Windows the bundled MySQL and MariaDB client, MariaDB Connector/C, loads its authentication plugins from the
    installation's own plugin folder, or from none

    (work item 116; Security).
  • On Windows, setup makes or hardens the program folder, Bin and ControlPanel before it copies a file or runs a
    program of the installation, and refuses a folder or a link on the way to them, or a program, library or script in
    them, that another account controls
    - the four narrower cases of work item 78 that 6.3.7's notes listed as still
    open (work item 78; Security).
  • Published with it: the advisory for a flaw in the auto-ban on MySQL and MariaDB that 6.3.4 fixed
    (work item 103).

Each was found by the hMailServer project (Progressive Robot Ltd). The other fixes and changes an administrator meets:

  • The Helm chart starts a server (Downloads and the release, Correctness). Installed into a cluster, the charts
    of 6.3.6 and 6.3.7 never started a pod: the image names its user, which Kubernetes cannot hold to the chart's
    runAsNonRoot, and once given the user by number the server stopped at a configuration volume that hid the image's
    settings file. 6.3.8's chart runs the pod as the image's user by number, 999:999, fills new volumes in an init
    container, and secures a new installation at its first start, as the packaged systemd unit does; so does the Compose
    file. The pod sets net.ipv4.ip_unprivileged_port_start to 0 in its own security context, so that the server binds
    the mail ports under containerd 1.7 and CRI-O too. A release of the 6.3.6 or 6.3.7 chart is moved with helm upgrade --reset-then-reuse-values and then kubectl delete pod (Before you upgrade, below).
  • The container image reaches a MariaDB database (Downloads and the release). The images of 6.3.7 and earlier
    carried PostgreSQL's client alone, so a container told HM_DB_TYPE=MySQL stopped at its first start; 6.3.8's
    carries MariaDB Connector/C (libmariadb3), and the release's pipeline starts each platform's pushed image beside
    PostgreSQL and beside MariaDB and asks each for SMTP, the REST API and the schema it laid out. The chart passes a
    database port only when one is set, so a chart told MySQL no longer dials MariaDB on PostgreSQL's port. Found by
    the project's own audit of its listings. The image's entrypoint also secures a new installation by itself, whatever
    the container's command, and takes the database and administrator passwords out of the environment before it runs
    any program (Security).
  • The Windows installer refuses an installation of the original hMailServer's 5.7 (Correctness). Both projects'
    installers register as hMailServer_is1, so the installers of 6.3.7 and earlier took an installation of the
    original's 5.7 builds of August and September 2026 (schema 5709 to 5711, which this fork's upgrade chain has no step
    from) for their own and replaced its programs. 6.3.8's setup asks first, before a page is shown or anything is
    installed or stopped, refuses such an installation with a message that says nothing has been changed, and, silent,
    ends with exit code 1 and writes the reason to its log (/LOG) and to hMailServer-setup-refused.txt in %TEMP%.
    An installation of this fork, or of the original up to 5.6, is upgraded as before. The installers of 6.3.7 and
    earlier do not look: never run one over the original's 5.7.
  • A host added to AcmeDomains no longer waits for the renewal, and a check can be asked for
    (work item 98, ThiagoES84; Correctness,
    Administration, Control Panel). The hourly check never looked at a certificate's names, so an added host waited
    for the renewal, some sixty days. A certificate that does not name every host the list holds is now due at once, a
    save of AcmeDomains in the running server starts the check (a value hmailserver --set-setting stores is read at
    the next reinitialise or start), the certificate is issued once the CA has validated every host on the list, and
    GET /api/v1/acme and POST /api/v1/acme/run (COM Utilities.GetAcmeStatus and Utilities.RunAcmeCheck, the
    Control Deck's Certificates page) give the certificate's state and ask for a check, one at a time, with the CA's
    rate limits and a pause after a failed attempt kept whatever starts it. The certificate a CA sends is installed only
    when it is for the request's key, names every host asked for and has not expired.
  • An event after an IMAP move, for scripts
    (work item 109, ThiagoES84; Administration).
    OnIMAPMessageMoved is called once for each message an IMAP MOVE or UID MOVE moved between two folders of the
    account signed in, after the command has done all of its work and off the IMAP thread. Off as shipped:
    IMAPMessageMovedEvents switches it on.
  • Rspamd learns from the Junk folder, beside SpamAssassin
    (work item 108, ThiagoES84; Administration). A
    message a user moves into Junk is taught as spam and one moved out as ham to the filter MoveLearningBackend names:
    SpamAssassin, the default, taught as before, or Rspamd through its controller, for the classifier
    RspamdClassifier names. Nothing is sent to Rspamd until it is chosen and its controller and classifier are set
    (JunkFolderLearning.md).
  • A REST request body meant to be JSON must be one JSON object (Correctness). The server answers 400, saying
    why, before it reads one that is not, names a member twice or passes the bounds the entry gives, and every member
    reader reads the one parse, so that a key spelt inside a value is no longer read as the member it is not.
  • The database client's lines say what it checks (Correctness): --check-database and PostgreSQL's connection
    line name the root certificate libpq is handed and what it checks.
  • On Linux a file's name outside ASCII is one name, UTF-8, whatever LANG says (Correctness). It was two files,
    or a file and an exception, by the call that opened it, and a LANG naming a locale the machine does not have
    stopped the server at its start. spamd's User: header now carries a name outside ASCII, and the legacy Blowfish
    scheme refuses a secret it cannot carry, saying whose it was, where it wrote an empty one. A server that ran under a
    LANG that is not UTF-8 renames its folders first (Before you upgrade, below).
  • On Alpine, log lines and other text holding a character outside ASCII are written (Correctness), where each
    came out empty.
  • A stop of the servers is no longer reported as errors of their own (Correctness). Work that outlasted a stop -
    a reinitialise among them - met the stop's interruption and wrote it to the error log as a failure (HM5136 and the
    other codes the entry names), which also ran an installed OnError script; each now writes a line to the debug log
    and ends as the stop asked, and a database connection such a call held goes back to the pool. A session or a task
    that went on after a stop no longer reaches for what the stop let go of, which on Linux ended the process.
  • The Linux server reaches clamd over its Unix socket (Administration): ClamAVHost takes the socket's
    absolute path (ClamAV.md).
  • The command-line clients (Correctness): the PowerShell module says why a call got no answer under Windows
    PowerShell 5.1, and Set-HmOpenPgpKey -Path sends the key there; hmctl reads a file that begins with a byte
    order mark - a configuration document, --from-json, --password-file and --passphrase-file - and refuses a
    body holding NaN or Infinity before it is sent.
  • The Control Panel (Control Panel): the Logging page names the destination a new installation has as Files on
    disk
    ; the Anti-spam page opens and saves on a server whose refuse threshold is at or below its mark threshold,
    saying what that pair does, and the Anti-spam overview reads the quarantine switch; the ACME page and the Setup
    checklist read AcmeDomains as the server does.
  • Who made what. The packages, the image, the chart, the snap and the command-line clients say that they are
    Progressive Robot's fork of hMailServer and credit the original's author, Martin Knafve; his copyright notices are
    back on the original's files; the tree is REUSE-compliant, with a CI job that holds it; the third parties whose
    work the tree carries are credited under their licences - Feather's icons (Cole Bemis), the Contributor Covenant,
    commonmark.js (John MacFarlane), GitLab's secret detection rules and Microsoft's Fluent icons in the README's
    screenshots; and two pieces of third-party code that stated no licence - the installer's service routines and the
    tools' tab-order helper - are written anew (Correctness, Downloads and the release, Build and tests).
  • New ways to install (Downloads and the release): an Ansible collection, progressiverobot.hmailserver
    (Ansible.md); a Kubernetes operator for Operator Lifecycle Manager; and a container on
    Proxmox VE with one command on the host (Proxmox.md), whose scripts have not yet run
    on a Proxmox VE host (Known issues, below).
  • The release's files (Downloads and the release): the release carries hmailserver-6.3.8.intoto.jsonl, its
    SLSA build provenance as one line of JSON beside hmailserver-6.3.8.intoto.sigstore.json; the provenance is held to
    the pipeline that publishes it and to what GitLab certified of the run; and the documents say its SLSA level, Build
    L1 and no higher.

Reported from outside and answered besides: on-demand ACME issuance (work item 98), Rspamd learning from the Junk
folder (work item 108) and an event after an IMAP move (work item 109), each asked for by ThiagoES84. Thank you.

Why there is a 6.3.8. While releases pause, a release is made for a confirmed vulnerability, and other defects
ship with the next one (SECURITY.md, "What gets a release"). 6.3.8 was first decided on 24 October 2026
under the owner's delegation for another reason: the published charts of 6.3.6 and 6.3.7 start no server, and neither
the chart nor the image of a release can be put right but by a release. The project's own work since found the
vulnerabilities above, each with a CVE request, and 6.3.8 is made as a security release (SECURITY.md,
"How a security release is made"), the chart's reason standing beside them. One of its fixes, work item 100's, was
public on GitLab from 19 October 2026 and on master from 20 October 2026, a week before it was judged a
vulnerability, as SECURITY.md says; the others were pushed with the release. Work item 108 and work item 109, asked
for on 29 October 2026, were put into it on 30 October 2026, each off until it is switched on. Like every release
during the pause it is cut from master with the finished work master holds then, and so it carries everything that
landed after 6.3.7: the work above, the channels' packages and texts, and the documents.

Signing, and who installs this release by the live update. Every file is signed with Sigstore as this release's
own pipeline at the group's path, https://gitlab.com/hmailserver/hmailserver//.gitlab-ci.yml@refs/tags/v6.3.8,
issuer https://gitlab.com, with the project's id, 86730559, in the certificate, as 6.3.6 and 6.3.7 were. Every
server from 6.3.4 on has that signer compiled in and requires the id for it. The update is applied by the version
already installed, so:

  • From 6.3.7 or 6.3.6 the live update installs 6.3.8, with the running version's own file as its rollback image.
    A Windows service running as LocalSystem installs it itself; one running as NT SERVICE\hMailServer downloads and
    verifies it, and an administrator installs it with Install update on the Server status page of the Control Panel
    on the server: one Windows prompt, and the installer is checked again, elevated, before it runs. On Linux the
    installed version's update helper applies it, for the generic .deb and .rpm of either architecture and for both
    AppImages; an ARM64 Windows server finds the ARM64 installer by name. A server installed from one of 6.3.6's or
    6.3.7's per-distribution packages is offered the generic package, which apt refuses on Debian 12 and dnf on
    Enterprise Linux 9: upgrade it with its package manager and its own release's package of 6.3.8. No live update is
    known to have installed a release signed at the group's path when these notes were written.
  • From 6.3.4 or 6.3.5 what 6.3.7's notes said of those two versions still holds: a Windows service running as
    LocalSystem installs 6.3.8 by its live update, from 6.3.5 with 6.3.5's file as its rollback image, which is no way
    back once the upgrade's schema steps have run, and from 6.3.4 without one; one running as NT SERVICE\hMailServer
    leaves the install to an administrator, whose Install update on those versions cannot do it, so install 6.3.8 by
    hand, once; on Linux install it with apt, dnf or zypper rather than by the live update, whose apply step on 6.3.4 and
    6.3.5 is the one 6.3.6 fixed, or turn the apply off first with systemctl disable --now hmailserver-update.path.
  • A server whose UpdateSigningIdentity, UpdateSigningIssuer and UpdateSourceRepository still name the path the
    project began at
    refuses every release signed after the project moved, 6.3.8 included: clear the three first, as
    UpdateFeed.md says.
  • From 6.2.28 to 6.3.3, and from 6.3.3's AArch64 packages or AppImages: by hand, once, after the backup
    Upgrading.md describes (UpdateFeed.md, "A server
    from 6.2.28 to 6.3.3"). Older than 6.2.28: run the installer, as always; the upgrade chain runs from every
    earlier version of this fork, and from the original hMailServer up to 5.6 and its 5.7 development builds up to
    schema 5708, and not from the original's 5.7 releases, which 6.3.8's installer refuses
    (Migration.md, "From the original hMailServer", says how such a server's mail moves).
  • The container image, registry.gitlab.com/hmailserver/hmailserver:6.3.8, also tagged 6.3 and latest, is
    pushed from this release's own pipeline once the release is published, by a job started by hand, as 6.3.6's and
    6.3.7's were: in the container the update check names the image to pull and installs nothing
    (ContainerImage.md).

By hand on Windows: download hMailServer-6.3.8-x64.exe from this release, 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 Service account page. On a machine that has not seen the file before, Windows
SmartScreen may say that it "prevented an unrecognized app from starting": More info names the publisher,
Progressive Robot Ltd, and with the signature checked, Run anyway installs it.

Back up first; no schema step. Take the backup Upgrading.md describes - the
database and the data directory, each with its own tools - before installing. 6.3.8 requires schema 6108, as 6.3.7
and 6.3.6 do, and adds no upgrade step, so an upgrade from either changes no table, and the live update's rollback,
should 6.3.8 not start, meets no newer schema. From 6.3.5 or older the upgrade runs 6.3.6's three steps to 6108,
which are one-way (6.3.6's notes; from 6.3.3 or older 6.3.4's steps come first), and the backup is the only way back.

The .NET 10 Desktop Runtime. The installers carry 10.0.13, as 6.3.7's did, and install it only on a machine that
has no .NET 10 Desktop Runtime at all: an upgraded server keeps the runtime it had. Bring it up to date through
Microsoft Update, or with Microsoft's installer (https://dotnet.microsoft.com/download/dotnet/10.0);
dotnet --list-runtimes lists what a machine has (Upgrading.md, "The .NET 10
runtime").

Before you upgrade. What an upgraded server does differently with no setting turned is in What changes on upgrade
without a switch
, below. A server upgraded from 6.3.6 or earlier meets as well what 6.3.7's section says under its
Before you upgrade and in its list, and one from 6.3.5 or earlier what 6.3.6's list says. The changes most likely to
need something done:

  • A release of the Helm chart of 6.3.6 or 6.3.7 (its pod never started): helm repo update, helm upgrade <release> hmailserver/hmailserver --reset-then-reuse-values (Helm 3.14 or later), then kubectl delete pod <release>-hmailserver-0, since the StatefulSet replaces no pod while one is not Running and Ready. With
    --reuse-values instead the release keeps the old chart's fsGroup 1000 and database.port 5432; the pod still
    runs as 999. In a cluster whose policy refuses every sysctl, set unprivilegedPortsSysctl: false.
  • A Compose deployment made from an earlier Compose file: keep its HM_REST_CERTIFICATE_FILE,
    HM_REST_PRIVATE_KEY_FILE and ./certs mount in the new file, the key readable by 999, or its REST API, and with
    it the Control Deck, the webmail and hmctl, does not start (ContainerImage.md,
    "Updates").
  • AcmeDomains listing a host the certificate lacks: the first check orders a certificate for every host on the
    list. A host the CA cannot validate - it does not resolve to the server, or its port 80 is not reached - fails the
    whole order, at most three times an hour, and holds the renewal for the other hosts with it: take such a host off
    the list before upgrading.
  • TlsMode=verify-full on MySQL or MariaDB through MariaDB Connector/C without TlsCaFile, to a server on this
    machine
    (127.0.0.1, ::1, localhost, an empty Server=, . for a named pipe on Windows, or a Server= list
    that holds one; Connector/C is the client the Windows installer bundles and the one the Linux server loads,
    libmariadb.so.3): 6.3.8 refuses the connection with HM7535. Name in TlsCaFile a CA that issues nothing but the
    database's certificates, the one that issued its certificate (DatabaseTls.md), or
    set TlsMode=require, before upgrading.
  • On Windows, a TlsCaFile (with TlsMode=verify-full) or a PostgreSQLSslRootCert whose path holds characters
    outside ASCII:
    6.3.8 may refuse the name with HM7534, and then does not attempt the connection to the database.
    Give the file, and the folders above it, an ASCII name before upgrading
    (DatabaseTls.md).
  • On Windows, a TLS certificate or private key file, dh2048.pem, a client-certificate CA file,
    MessageStoreObjectCaFile, OAuth2PublicKeyFile or AcmeCertificateDirectory, whose path holds characters outside
    ASCII:
    6.3.8 may refuse the name with HM7536, and then what the file is for does not work - a port whose
    certificate or key it names does not start. Give each such file, and the folders above it, an ASCII name before
    upgrading.
  • A Linux server that ran under a LANG that is not UTF-8, with a data directory, a domain or account folder,
    LogFolder, TlsCaFile or a TLS certificate or key file named outside ASCII: 6.3.8 names files in UTF-8 whatever
    LANG says, so rename each to its UTF-8 name before upgrading, for example with
    convmv -f iso-8859-1 -t utf-8 -r --notest <folder>. Under a UTF-8 LANG, the packages', nothing changes.
  • IP ranges of your own that allow some addresses none of SMTP, POP3 and IMAP, with the web services listener on:
    such a range now closes that listener to those addresses as well - ACME's http-01 validation when this listener
    answers it, MTA-STS, CalDAV, CardDAV, Autodiscover, autoconfiguration and the Web Key Directory - so check that the
    certificate authority, the sending MTAs and these services' clients do not come from one.
  • A REST client of your own that sends a body that is not one JSON object, names a member twice, holds an escaped
    U+0000 or passes the bounds (100,000 values, 32 levels deep), or that writes a member whose absence is the wider
    setting as another type than its route reads - a quoted flag, an array for a text: the server answers it with 400
    (REST request bodies, in the list below).
  • A mail client that signs in with another spelling of the account's address than the stored one, in more than
    the case of the letters A to Z, or with white space at either end: it may no longer sign in. Correct the name in the
    client (Sign-in names, in the list below).
  • Installing with Chocolatey: its install now passes the installer /LOG, and the log records setup's command
    line, so give the administrator password in HM_ADMIN_PASSWORD, set in the elevated prompt, rather than with
    /adminpassword in --install-arguments (PackageManagers.md).
  • On Windows, a program folder, Bin or ControlPanel, or a folder or a link on the way to them, that another
    account than SYSTEM, the Administrators and TrustedInstaller controls:
    setup refuses it with exit code 7, naming
    the icacls that deals with it, and asks for a restart of Windows before it runs again
    (ServiceAccount.md).
  • Anything that counts the error log or relies on an OnError script during a stop: a stop's interruption is no
    longer written there; an event script's call that meets a stop fails with "The server is stopping. The request was
    not completed." and its script error goes to the debug log.
  • On Windows, an installation of the original hMailServer's 5.7 is refused by 6.3.8's setup before it changes
    anything; move its mail over IMAP instead.

What 6.3.8 fixes of what 6.3.7 left open, and what it does not. The four narrower cases of work item 78 that
6.3.7's notes listed as still open are fixed in this release
(work item 78, above). The original hMailServer's 5.7
releases are still not on the upgrade chain: 6.3.8's installer refuses an installation of them, and its mail moves
over IMAP (Migration.md, "From the original hMailServer").

Known issues.

  • The ARM64 installer has never run on ARM64 hardware. It is cross-compiled on the project's x64 Windows runner and
    remains a preview (WindowsOnArm.md); the AArch64 Linux files are cross-compiled,
    and installed - the generic .deb and .rpm started as well - in the release's pipeline under emulation: no AArch64
    machine has run them. If you install either, please say how it went, working or not, in
    an issue.
  • The channels are published to by jobs started by hand. The Ubuntu PPA, Fedora Copr, Docker Hub, Chocolatey,
    PyPI and the PowerShell Gallery are sent 6.3.8 from this release, verified against its bundles, by a job started by
    hand on master once the release is published; a Chocolatey version is listed once its moderators have approved
    it. The openSUSE Build Service's job runs once its package holds this tree's new _service, which a maintainer puts
    in after the release is published, since 6.3.7's tarball lacks a file it takes; until then it keeps 6.3.7. A job
    started the same way uploads the snaps to the Snap Store's edge and candidate channels, stable being promoted
    by hand; 6.3.8's are the first there, since 6.3.7's snaps could not read their configuration and were never uploaded
    (Channels.md, Snap.md). The Ansible collection is not on
    Ansible Galaxy yet (Ansible.md): the collection in the tree pins 6.3.7's .deb files,
    and installs that release unless it is given another's SHA-256, until the collection built for Galaxy pins 6.3.8's.
    The operator's pull request to OperatorHub.io is made by hand.
  • The Proxmox VE scripts have not run on a Proxmox VE host. None was available: the container's creation is shown
    by a dry run and by stand-ins of Proxmox VE's commands, and the install ran in a container made from Proxmox VE's own
    debian-13-standard template (Proxmox.md, "What has run"). If you run them, please
    say how it went in an issue.
  • The image's OpenSSL is Ubuntu 24.04's 3.0.13, which has neither the hybrid post-quantum key exchange groups
    (OpenSSL 3.5) nor Argon2id (3.2): its server logs HM5720 at its start and takes the classical groups, makes with
    scrypt the hashes it would make with Argon2id, and GET /api/v1/status now says false for argon2id and
    post_quantum_key_exchange, from the library it runs with (ContainerImage.md,
    "What is not shown yet").
  • An AppImage on Fedora, Enterprise Linux or openSUSE runs on the host's glibc and cannot require its extra
    character-set converters: install glibc-gconv-extra (Fedora, EL) or glibc-gconv-modules-extra (openSUSE) by
    hand; the server says when they are missing.

Verification. hMailServer.exe 6.3.8.47, built from this release's source by build\build.ps1 -Configuration Release with the toolchain RELEASE.md step 8 names, has the SHA-256
6baa2c2348cea4f83cbffae33e0f02f60b1ee843c9315852eb95d2a0ecd9e881, the same from clean and from a second checkout at
another path (ReproducibleBuilds.md).

Security

  • Fixed: TlsMode=verify-full on MySQL and MariaDB checks the database server's certificate against the file
    TlsCaFile names when that name holds a character outside ASCII, where the client was given no CA file at all.

    In 6.3.4 to 6.3.7 such a name - on Linux any character outside ASCII, on Windows one above U+00FF - reached the
    MySQL client as no CA file while certificate verification stayed on, so that the certificate was checked against
    the system's trust store in place of the named CA, and with some versions of MariaDB Connector/C less than that:
    on a connection the client takes as one to this machine, 3.4.1 and later checked neither the certificate's issuer
    nor its name; and over the network, with 3.4.2 to 3.4.10 a certificate whose issuer the client could not find was
    taken unchecked (MariaDB's CONC-853, fixed in Connector/C 3.4.11), and with 3.4.1 to 3.4.9 such a connection was
    open to CVE-2026-107819. The Windows
    builds of 6.3.4 to 6.3.6 bundle Connector/C 3.4.9; on Linux the distribution's client is used. The settings line of
    --check-database named the configured file all the same, and the application log and the diagnostics said the
    certificate had been checked. So 6.3.7's notes, which called a named TlsCaFile the mitigation for CVE-2026-107819,
    did not hold for such a name. On Linux with no locale set, and on Alpine, such a name failed the connection instead.
    From 6.3.6 PostgreSQL's root certificate was handed to libpq, in UTF-8 on Linux and in the code page on Windows (in
    6.3.4 and 6.3.5 its whole connection string went through the same conversion, which 6.3.6 fixed), and SQL Server
    reads no file. Now the name is handed to the
    client in the encoding it opens the file in - on Linux UTF-8, on Windows the ANSI code page - and a name that cannot
    be written so is refused before anything is attempted, with HM7534 (new) in the error log, and never handed over as
    nothing. DALConnection::FileNameBytes is the one conversion, which PostgreSQL's root certificate goes through
    too. On Windows the authentication-plugin folder the bundled client is handed is always one that only
    administrators control, and never empty: Bin\plugin, in the code page it loads a plugin in, or by the folder's
    short name (MySQLConnection::PluginDirectoryBytes), and where neither can be written, a folder under the Windows
    system folder that holds no plugin, so that an account whose plugin is only in Bin\plugin is refused with the
    client's own error; where even that cannot be written, the connection is refused before anything is attempted,
    with HM7537 (new) in the error log (MySQLConnection::PluginFolderOption).
    hmailserver/docs/DatabaseTls.md says what the client is given, with HM7537 in its table, no
    longer asks for the path in ASCII, and asks on Linux for MariaDB Connector/C 3.4.11 or later, and
    hmailserver/docs/ThirdPartyBinaries.md says which plugin folder the client is handed. connection-string-tests'
    MySQLConnectionTester, which ClassTester runs on Windows, holds the name's bytes for both clients, the refusals,
    the plugin folder's name and the folder handed where that name is refused.
    build/check-mysql-plugin-folder.py, run by the checkers job with its self-test, holds that the connection hands
    the client that folder and no other: the option is set in one place, from the folder
    MySQLConnection::PluginFolderOption gave, and PluginDirectoryBytes is called by PluginFolderOption and the
    tester alone.
    Found by the project's own review.

  • Changed: a command run as root on Linux examines and opens the folders it will work in by the names the server
    uses for them, through one function.
    The store commands, the object-store copies, --sample-domain,
    --apply-profile, --revert-profile and --apply-environment, run as root, decide whether to become the data
    directory's owner or to go on as root by examining the data directory, the log, temporary, event and database folders
    and the configuration that names them (RootCommand, docs/ServiceAccount.md). In 6.3.6 and 6.3.7 they examined
    those folders by the Latin-1 bytes of their names, while the server reaches them by the UTF-8 ones, and their own
    file operations in the log, temporary, event and database folders went by the same bytes, so that for a name with a
    character outside ASCII the commands missed the folder the server uses. Now the examination and
    the commands' own file operations both go by the name the server uses, through one function
    (FileUtilities::NarrowPath, below), so that they stay on the same folder.

  • Fixed: TlsMode=verify-full on MySQL and MariaDB is refused without TlsCaFile on a connection the client takes
    as one to this machine, where neither the database server's certificate's issuer nor its name was checked.
    In
    6.3.4 to 6.3.7, with MariaDB Connector/C 3.4.1 or later as the client - the one this server bundles on Windows, and
    the one the Linux packages suggest and the server loads - such a connection, to a database server on this machine by
    a loopback address, localhost, a Unix socket or a named pipe, or through a Server= list that holds such a server,
    was made with neither the certificate's issuer nor its name checked, while the settings line of --check-database
    said it had been checked against the system's trust store, and the application log and the diagnostics that it had
    been checked. It is now refused before anything is attempted, with HM7535 (new) in the error log, which says to name
    the CA that issued the database server's certificate in TlsCaFile, or to set TlsMode=require. The lines that
    say what was checked now say what the client checks (MySQLConnection::VerifyFullCheck). Connector/C 3.3 and 3.4.0
    and Oracle's MySQL client were not affected. hmailserver/docs/DatabaseTls.md says what each client checks, with
    HM7535 in its table. connection-string-tests' MySQLConnectionTester, which ClassTester runs on Windows, holds which
    servers the client takes as local, the refusal and each line. Found by the project's own review.

  • Fixed: on Windows a root certificate's name whose bytes in the ANSI code page outside ASCII also read as UTF-8 is
    refused, where the database's client could check the server's certificate against another file than the one
    named.
    In 6.2.25 to 6.3.7 on Windows the file PostgreSQLSslRootCert names (from 6.2.25) or, with
    TlsMode=verify-full, TlsCaFile (from 6.3.4) was handed to libpq - and TlsCaFile to Oracle's MySQL client where
    one is put in place of the bundled MariaDB Connector/C - by its bytes in the code page (up to 6.3.5, and to Oracle's
    client up to 6.3.7, by its Latin-1 bytes), which the client's OpenSSL could open as a file at another name. Such a
    name is now refused before anything is attempted, with HM7534 in the error log, whichever client is loaded, as one
    the code page cannot carry is: TlsCaFile by DatabaseTls::FromConfiguration, PostgreSQLSslRootCert by
    PGConnection::Connect, where 6.3.6 and 6.3.7 refused only a character the code page lacks. The bundled MariaDB
    Connector/C and the Linux builds were not affected, and a Windows whose code page is UTF-8 is handed the name in
    UTF-8 (6.3.6 and 6.3.7 refused every PostgreSQL root certificate name there). DALConnection::FileNameBytes says
    so, and connection-string-tests'
    PGConnectionTester, which ClassTester runs on Windows, holds the refusal. Found by the project's own review.

  • Fixed: on Windows the files the server hands to OpenSSL by name are read from the file configured, or refused.
    The certificate and private key files of the TLS certificates - the REST API's, the metrics listener's and the web
    services' among them - dh2048.pem, a TCP/IP port's client-certificate CA file, MessageStoreObjectCaFile and the
    files in AcmeCertificateDirectory were handed to OpenSSL in an encoding in which a name with some characters
    outside ASCII could name another file than the one configured, in every release up to and including 6.3.7. Each name
    now goes through one conversion, FileUtilities::OpenSslPath, which holds to the rule the database's CA files
    follow (FileUtilities::FileNameBytes, which DALConnection::FileNameBytes now calls), and a name it cannot hand
    over as it is named is refused, with HM7536 (new) in the error log, together with what the file was for: a port
    whose certificate or key it names does not start, a client-certificate CA file verifies no certificate, the object
    store's CA file allows no request, the public key verifies no token, dh2048.pem is left out as a missing one is,
    and the ACME client reads and writes nothing there and orders no certificate. A name in ASCII is handed over as
    before, and on Linux every name in UTF-8, the bytes FileUtilities::NarrowPath gives every other call there (under
    Correctness), which OpenSSL opens as they are. The Control Panel holds the same rule where it reads these files on
    the server's own computer: the SSL certificates page shows such a certificate or key file as Name refused, at
    Critical, and the setup checklist's items on the TLS private keys, the client-certificate CA files and
    OAuth2PublicKeyFile say the server does not read it (error 7536), since they read the file by its wide name, which
    works whatever the name. OAuth2PublicKeyFile was handed over in UTF-8 up to 6.3.7, so a name outside the code page
    that worked then is refused now. build/check-openssl-file-names.py, run by the checkers job with its self-test,
    holds every call in the server that opens a file through OpenSSL by its name to a name OpenSslPath gave.
    DatabaseTls.md says which names are refused and what to do, and Acme.md, ObjectStorage.md and
    RelocatingAnInstallation.md say so beside the settings. Found by the project's own review.

  • Fixed: the container image secures a new installation by itself, whatever the container's command, as the
    packaged systemd unit's and OpenRC service's first starts do.
    Its entrypoint now runs hmailserver --apply-new-install-defaults --if-marked last, before the command: a database the entrypoint's --create-database
    made a moment before gets a self-signed certificate, STARTTLS on 25, 587, 110 and 143, AUTH on 25 a honeypot and a
    password taken from another address only over TLS, and at every other start the step opens nothing; a failure (run as
    root, a database not reached) is said and forgiven, the server starting all the same, as the unit's ExecStartPre=-
    forgives it. Until now only the Compose file's and the Helm chart's commands ran it, so a docker run of the image
    with its own command took passwords in the clear on every port, and --create-database told such a container's
    administrator to secure it by hand - and told the Compose file's and the chart's the same, whose next step did it. The
    entrypoint gives its --create-database HM_ENTRYPOINT_SECURES_NEW_DATABASE=1, which makes it say that the
    entrypoint secures the database next; run anywhere else, it says what it said before. The Compose file and the chart
    keep their own step, for an image published before, whose entrypoint does not run it; with this image it finds no
    marker and does nothing. SecureDefaults.md, ContainerImage.md and CommandLine.md say so.

  • Fixed: the redaction of a statement's literals in the log and the trace, and the test for the marker that lets a
    statement's error be ignored, now read a literal as each engine does.
    SqlLiteralScanTester holds both to each
    engine's reading, inside the server on Windows (ClassTester) and in connection-string-tests on Linux. Found by
    the project's own review.

  • Changed: the container image's two passwords no longer reach the server's environment, nor that of anything it
    starts.
    The entrypoint read HM_DB_PASSWORD and HM_ADMIN_PASSWORD (or their _FILE forms, whose contents it
    exported into the plain variables), used them, and then ran the server - the container's PID 1 - with its whole
    environment, so that the server's verbs it ran, the server itself and every program the server starts, 7-Zip, the
    auto-ban hook, event scripts, a virus scanner and the update helper among them, held both. A program the server
    starts holds no capability, and its /proc/<pid>/environ showed them to any program running as the image's user
    while it ran (6.3.7's image: the auto-ban hook the server runs at its start held both, plain or from a _FILE). The
    server's own /proc/1/environ is kept from the image's user only where the kernel grants the binary its file
    capability, as it does under Docker's defaults. Under no_new_privs, which the Helm chart's
    allowPrivilegeEscalation: false sets, as Docker's --security-opt no-new-privileges does, the kernel never grants
    it, and any program running as the image's user can read the server's own environment for as long as the server
    runs: under the chart's own security context 6.3.7's server held both passwords there, read from their _FILE
    secrets, for its whole life. Root in the container reads none of these environments, since it lacks
    CAP_SYS_PTRACE. The server reads neither password from its environment on Linux. Both now leave the environment,
    in whichever form they came, before the entrypoint runs any program - the cat that reads a _FILE among them,
    which held the password read before it - and stay its own shell variables for the [Database] section and
    --set-admin-password's standard input. The settings' variables (HM_SETTING_*, HM_PROPERTY_*, their _FILE
    forms and the four HM_REST_*), which the entrypoint's own --apply-environment still sees and stores in the
    database before the server starts, and which the running server does not read, leave it too when the command is the
    server itself (hmailserver with nothing but --foreground and --config, the image's own command); any other
    command keeps them, since one that runs --apply-environment would otherwise give back every setting they name, and
    the entrypoint cannot read a shell's command line. So the Compose file's and the Helm chart's commands, which are
    shell command lines (--apply-new-install-defaults and then the server), still pass the settings' variables to the
    server and to what it starts - under the chart's security context, to its own /proc/1/environ too - and a setting
    that is a secret belongs in a _FILE there, as their examples give it. HM_INSTALL_KIND stays, for the update
    check, and any other variable reaches the command as it was given, as in 6.3.7, but for HM_DB_TYPE, which reaches
    it in the entrypoint's own spelling (PostgreSQL, MySQL or MSSQL) when given another it accepts, and two of the
    entrypoint's own names: a variable named INI reaches it holding the configuration file's path, and one named
    default_port the database's default port when HM_DB_TYPE is given. Hardening, not a flaw: a program running as
    the image's user can read hMailServer.ini, which holds the database password, all the same.
    build/test-docker-entrypoint.sh holds it, with 6.3.7's entrypoint as its negative control
    (hmailserver/docs/ContainerImage.md).

  • Changed: the container image's HMAILSERVER_RECOVERY_PASSPHRASE no longer reaches the server's environment when
    the container runs the image's own command.

    The variable is read by one verb, hmailserver --decrypt-store, and by nothing the running server does; given in a
    container's environment it stayed in the server's, and that of every program the server starts, for the server's
    whole life, which under no_new_privs - the Helm chart's allowPrivilegeEscalation: false - any program running
    as the image's user could read from /proc/1/environ. The entrypoint now takes it out of the environment with the
    two passwords, before it runs any program of its own, and gives it back to the command unless the command is the
    server itself, as it keeps the settings' variables: a container whose command is --decrypt-store has it, and the
    Compose file's and the chart's shell command lines still pass it to the server they start, so it belongs only in
    the environment of a container started to run the verb. ContainerImage.md and EncryptionAtRest.md say so, and
    build/test-docker-entrypoint.sh holds it, with a case of its own for --decrypt-store as the command.

  • Fixed: a run of failed logons under a user name could fail to ban the address, when the auto-ban could not save
    the range it is written as under a name of its own.
    The IP range a ban is written as is named
    Auto-ban: <user name>, with (1) or
    another suffix after it when a range of that name exists, in a column of 100 characters that no two ranges may
    share. Up to 6.3.7 the suffix went after the whole user name and the name was cut to 100 characters only after the
    check for a name in use, so that for a long user name a suffix could be cut and a later ban for the user name could
    take a name in use: the second ban for it or a later one. Such a ban was refused and the address was not banned
    (reported as HM6099 from 6.2.19) - and its counted failures were cleared all the same, so it was given a whole new
    set of attempts. On SQL Server the check for a name in use compared the name in the database's code page, so it
    could also miss a name in use. A ban for a user name holding a NUL could fail too. Now the
    user name (or the spam trap) ends before a NUL and is shortened so that the name, its suffix included, fits the
    column, never in the middle of a character, and the check asks about the name exactly as it will be stored,
    compared as the database compares it; a range refused even so - a name another server sharing the database wrote
    in between - is written again once under a GUID name before HM6099 is reported; and an address's counted failures
    are cleared only once its range is saved, so while it cannot be each later failed logon within
    MaxInvalidLogonAttemptsWithin tries again (with AutoBanMinutes 0, which writes no range, as before). Sessions
    from one address that were open before it was banned, and that each cross the threshold, write no second range
    while the ban in force lasts at least as long as a new one would. A ban for a long user name is named with the
    user name cut, and the Auto-ban page shows it so; a range name over 100 characters given over COM or the REST API
    is stored as its first 100, as before, and now reads back as stored, and one holding a NUL given over COM as the
    part before it, while over the REST API a body that carries one, as an escaped U+0000, is answered 400 before the
    route reads it; and POST /api/v1/ipranges reads the name in UTF-8, as PUT does, where it stored a name with
    characters outside ASCII as other characters, one for each of its bytes.
    AccountLogonRangeNameTester holds the names (inside the server on Windows, with a half against the database, and
    in autoban-widen-tests on Linux), and the regression fixtures Security.AutoBanRangeName and
    Infrastructure.DatabaseFailureHandling the bans (AutoBan.md). Found by the
    hMailServer project (Progressive Robot Ltd).

  • Fixed: an address an auto-ban or an administrator's IP range banned was not refused by the web services
    listener, so it could go on guessing passwords at the CardDAV, CalDAV and Autodiscover sign-in.
    The listener,
    off by default (WebServicesHttpPort and WebServicesHttpsPort 0), asked the country policy at most when it
    accepted a connection (6.3.3 nothing at all). A failed sign-in there counted towards the auto-ban as on every other protocol, and the
    ban it wrote refused the address at the SMTP, POP3, IMAP and ManageSieve listeners, but not at this one.
    Now the listener asks, once a connection and before the TLS handshake, for the IP range that decides for the
    address, and closes the connection with no answer when that range allows none of SMTP, POP3 and IMAP - an
    auto-ban's, or an administrator's that allows nothing - or when the ranges cannot be read; no range lets the
    address in. A range that allows any of the three does not refuse, since the listener also serves MTA-STS
    policies, ACME challenges, the Web Key Directory and autoconfiguration to the world, and a server that lets the
    Internet in for SMTP alone must still serve them. Loopback and the addresses in WebServicesTrustedProxies are
    never refused by a range: behind such a proxy the listener sees the proxy's address for every client, and a ban
    that held it would refuse every client behind it; through a proxy the per-name lockout is what limits the guesses.
    Every failed sign-in on the listener still closes its connection, so one opened before the ban has one guess at
    most after it. An administrator's range that allows none of the three now closes this listener to the addresses it
    holds as well - the ACME http-01 validation when this listener answers it, MTA-STS, CalDAV, CardDAV, Autodiscover,
    autoconfiguration and the Web Key Directory - so before upgrading a server whose own ranges allow some addresses
    nothing (an Internet range that allows nothing because mail comes through a relay or a VPN, say), check that the
    certificate authority, the sending MTAs and the clients of these services do not come from one; each such refusal
    is said in the application log, naming the range, once an hour for each address while fewer than 1,024 are
    recorded. WebServicesRangeTester holds the rule (inside the server on Windows, and in autoban-widen-tests on
    Linux), and the regression fixture Security.WebServicesBan the listener, from the machine's own address that is
    not loopback, with the ranges unreadable among its cases (AutoBan.md). Found by
    the hMailServer project (Progressive Robot Ltd).

  • Fixed: a failed CardDAV, CalDAV or Autodiscover sign-in through a proxy in front of the web services listener
    counted towards an auto-ban of the proxy's own address.
    The listener sees a TLS-terminating proxy's
    address for every client behind it (X-Forwarded-For is not believed), and a failed sign-in through it was
    counted towards the auto-ban against that address, so a ban written for that address could shut the proxy, and
    every client behind it, out of the SMTP, POP3, IMAP and ManageSieve listeners. Since
    6.3.3 for CardDAV and 6.3.4 for CalDAV and Autodiscover. A failed sign-in from loopback or a
    WebServicesTrustedProxies entry - the peers this listener takes X-Forwarded-Proto from and never refuses by a
    range - is now counted against the name alone (AccountLockoutThreshold, off by default, which is then the limit
    on the guesses through a proxy), never towards a ban of the address; a failure from any other address counts as
    before; the comments on Autodiscover's sign-in and on WebServicesServer::IsForwarder say so, where the first
    still said that each failure was charged to the proxy's address. The Control Panel's note under the trusted
    proxies says what a listed address is spared, and that only proxies belong there, in English and in the 17
    languages. Security.WebServicesBan holds it: three failed sign-ins from loopback and through a trusted proxy
    write no ban and SMTP still greets each, and from loopback they lock the name
    (AutoBan.md). Found by the hMailServer project (Progressive Robot Ltd).

  • Fixed: an account's failed sign-in at the REST listener through a reverse proxy on the same machine counted
    towards an auto-ban of 127.0.0.1.
    Every browser behind such a proxy reaches the listener from loopback, the one
    peer whose X-Forwarded-Proto it believes. The listener's own accounting of a refused credential spared 127.0.0.1
    and ::1, but an account's failed HTTP Basic sign-in - on any route, POST /api/v1/session, the webmail's and the portal's
    sign-in, among them - had already been counted towards the auto-ban against the connection's address by the logon
    the mail protocols share, so a ban could be written that refused 127.0.0.1 at the SMTP, POP3, IMAP and ManageSieve
    listeners, while the REST listener, which asks no range, went on answering. Since 6.2.28, where accounts
    first signed in there. A failed account sign-in from loopback is now counted against the name alone
    (AccountLockoutThreshold, off by default, which is the limit on the guesses at one account through such a
    proxy, as it already was, since the REST listener never refused 127.0.0.1 or ::1), never towards a ban of the address, and
    no Auto-ban: logon failure line is written for it; a failure from any other address counts as before. The
    listener's own accounting - of the administrator password, API keys, passkeys and the codes and passwords it
    checks itself - now spares all of 127.0.0.0/8, the peers it takes X-Forwarded-Proto from, where it spared
    127.0.0.1 and ::1 alone. On 6.3.2 to 6.3.7, AutoBanNeverBan with 127.0.0.1, ::1 avoids it; 6.2.28 to 6.3.1
    have no AutoBanNeverBan, and there only the auto-ban on failed logons switched off (AutoBanOnLogonFailure
    false, or MaxInvalidLogonAttempts 0) or AutoBanMinutes 0, which disconnects without writing a range, avoids
    it, each for every address. The regression fixture Security.RestApiLoopbackBan holds it: three failed sign-ins at GET /api/v1/me and at
    POST /api/v1/session from loopback write no ban and no such line, SMTP and IMAP still greet loopback, and the
    name is locked; failed account sign-ins, a wrong administrator password and a wrong API key from 127.0.0.2, and on
    the Windows suite over ::1, are charged to nothing and the listener still answers; on the Windows suite, three
    from another address are still charged to it
    (AutoBan.md, DevicesAndSessions.md).
    Found by the hMailServer project (Progressive Robot Ltd).

  • Fixed: a sign-in took for an account every spelling of its name that the database's own comparison took for it,
    so the per-name lockout and the compromised-account hold could be got round with another spelling of the name.

    The per-name lockout (AccountLockoutThreshold, from 6.2.22-pre2) and the hold the compromised-account detection
    puts on an account (from 6.3.4) are asked of the name the client gives - trimmed, in lower case, with a domain
    alias or the default domain applied - before the password is checked. The account the name then signed in to was
    whichever one the database found under it, and the databases find an account under spellings that key counts
    apart: they differ by database and collation. Each such
    spelling had a lockout threshold of its own, so the lockout limited the guesses at an account only up to the
    number of its spellings, and on some databases not at all; and an account held for review could
    be signed into under another spelling of its name with its password. Now a name signs in to an account only when
    the account's stored address is that name, compared in lower case after the alias and the default domain, by the
    function the lockout's key is made with: any other spelling names no account, fails as a name that does not
    exist fails, with the same reply and no password verified, and is counted as a failure; the country policy asked
    before the password finds the account by that rule too, so such a spelling meets its domain's policy, as a name
    that does not exist does, where it had met the account's own and a refusal there was counted as nothing. The
    same rule finds the account for every sign-in by a name a client or another party gives: the password sign-ins
    of SMTP, IMAP, POP3,
    ManageSieve, the DAV services, the webmail, the REST API and COM, the IMAP master user's authorisation identity,
    SCRAM-SHA-256, the name of an OAuth2 bearer token, a client certificate and a Kerberos ticket (for an account linked
    to a directory user, the directory user name and domain the account holds, where the database's comparison had
    chosen among the linked accounts), and the address an OpenID Connect or SAML identity provider signs a person in
    with (for which no mailbox is made either). A name that holds a NUL names no account and is refused before anything
    is looked up with it: up to 6.3.7 such a name reached the account and domain lookups, where MySQL, MariaDB and
    PostgreSQL failed the statement and the server reported a database error for each. A password
    sign-in also asks the hold, the country policy and, for the account that authenticated, the lockout of the
    account's own address once the password has found it; when the IMAP master user signs in as another account, the
    master user's own hold, country policy and lockout are asked of its own address too, besides the other account's
    hold and country policy, where up to 6.3.7 they were asked only of the name it gave, so that a held or locked
    master user opens no mailbox under any spelling of its name; and the account.locked webhook is raised only for a
    locked name that is an account's own. A name with white space at either end no longer signs in, whatever the
    domain aliases hold (it
    is still counted against the account, or as a name of no account where a domain alias's own stored name ends in
    white space; the server now takes the white space off an alias's name when COM or the Control Panel saves one, as
    the REST API did, where up to 6.3.7 a name under such an alias could be counted apart from the account; and the
    default domain is saved and read without white space at either end, where up to 6.3.7 COM and the REST API saved it
    as given, and with white space after it a bare local part could be counted apart from the account), nor does one
    that
    differs from the stored address in the case of a letter the C library does not lower in the C locale: with glibc
    every letter but A to Z; on Windows Latin-1's own capitals (U+00C0 to U+00DE) and some letters from U+0100 up -
    title-case letters, letters added in recent versions of Unicode and any outside the Basic Multilingual Plane among
    them - while it lowers most of the others; and with musl (Alpine) hardly any, since it lowers nearly every letter,
    so that there such a spelling signs in to the account and is counted under the
    account's own key. LogonNameTester holds the rule
    (inside the server on Windows, and in sign-in-budget-tests on Linux), and the regression fixtures
    Security.LogonNameVariants (over IMAP, over the web services' HTTP Basic, which carries a name as UTF-8, and over
    SCRAM-SHA-256 on SMTP, IMAP and POP3, and a name holding a NUL over IMAP LOGIN and in a request for a password
    reset, an alias whose stored name ends in a space, a default domain saved with white space after it, an
    account's own country policy and a locked IMAP master user) and
    API.RestApiCompromisedAccount (a held account, over IMAP and over SMTP AUTH PLAIN, which carries a name as UTF-8
    too, and a held IMAP master user) and Security.CountryBlocking (the IMAP master user's own country policy) the
    sign-ins
    (CompromisedAccounts.md). Found by the hMailServer project (Progressive
    Robot Ltd).

  • Fixed: a mailbox's recovery address could be sent more password-reset links than the per-address limit allows.
    POST /api/v1/password-reset, which takes no credential, allows each address one request a minute and six an
    hour, which is what keeps a link from being mailed at somebody over and over. From 6.3.4 that limit was counted
    under the address as it was given, and the mailbox was then found under another reading of it: some other forms of
    the address each had a limit of their own, though each was looked up as the address itself; each sent the mailbox's
    link. Now the limit counts the address as a sign-in compares
    a name with an account's address - in lower case, with the alias and the default domain applied and the white space
    at either end taken off - so every spelling that names a mailbox shares its one limit, with no lookup made for the
    count; a request under the address itself is counted as before; the address is lowered once, and what is looked up
    is what was counted; and an address that holds a NUL character, which names no mailbox, is counted against nothing
    and looks nothing up, because a body that carries one, as an escaped U+0000, is now answered 400 before the route
    reads it, as every REST body that carries one is. The regression fixtures
    Security.LogonNameVariants.APasswordResetForAnAddressHoldingANulSendsNoMoreThanOneForTheAddress and
    Security.LogonNameVariants.APasswordResetUnderADomainAliasOrTheDefaultDomainSharesTheAddressBudget hold it
    (AccountRecovery.md). Found by the hMailServer project (Progressive Robot
    Ltd).

  • Fixed: the REST API, and the reader of a report mailed to a domain's report address, read a JSON body in time and
    memory that follow its bytes.
    From 6.3.2 the REST API could take far more time and memory to read a JSON body than
    the body's size, in the service that also serves SMTP, IMAP and POP3, and an account's own password was enough to send
    one. From 6.3.4 the same was true of the reply to a password-protected message. The parse now moves each value into
    its place rather than copying it; it keeps the names an object has already seen in an ordered set; and a body of more
    than 100,000 values, an object of more than 10,000 members, or more than 32 levels is answered 400 at the first value,
    member or level past the bound, before any route reads a member. The reply to a password-protected message is now held
    to 2 MB, room for its one armoured message, and is read by that one parse rather than a second. A TLS-RPT report
    mailed to a domain's report address is now read through the bounded parse too, within bounds of its own. The
    RestApiRequestBodies fixtures hold the bounds, with the account's password and on the
    reply with no credential, and JsonValueTester and the DomainReports tester hold the parse. Found by the
    hMailServer project (Progressive Robot Ltd).

  • Fixed: on Windows, setup makes a new program folder, and a Bin and a ControlPanel that are not there, with
    permissions of their own from the moment they exist
    (work item
    78
    , CVE-2026-107571, R1). The last step of
    PrepareToInstall, before Inno Setup makes a folder or copies a file, makes the program folder, and every folder
    above it that is not there, each by its one name relative to the handle of the folder above it (NtCreateFile), so
    that no link put on the way since setup looked is followed and nothing is made where one leads, with a security
    descriptor: SYSTEM and the Administrators full control, BUILTIN\Users Read & execute where every user could read a
    folder made there, nothing inherited, the Administrators the owner. It makes exactly the names below the deepest
    folder it found and holds, never deciding what is there by a path again: it reads that folder again through the
    handle that holds it before it makes anything in it, and holds each folder the moment it is made and reads through
    the two handles where it is, so that a name somebody else takes in the meantime, a folder or a link, and a folder
    held above it made a link in the meantime, are refused rather than taken. In a program folder it makes, it makes
    Bin, and ControlPanel when the Control Panel is installed, the same way, so that a cancel or a failure during
    the copy removes them with the folders setup made - the program folder stays only when Inno Setup's rollback leaves
    in it the files it never uninstalls, of Addons and Events, the Administrators' with setup's permissions and
    nothing else in it; in a program folder that is there it makes them the same way when they are not there, and when
    they are there and held nothing when setup first looked, before anything changed (R2). An entry the folder above
    hands down with generic rights counts as the rights they mean for a folder, as the server counts it, so that
    C:\Program Files\hMailServer, where Program Files hands Users (GR,GE) down, gets its Users entry;
    InstallerPackaging.cs and build/check-installer-scripts.py hold every mask setup tests an entry's rights against
    to the generic rights that mean one of its own. A program folder typed on the directory page with a name that ends in
    a dot or a space, which Windows drops from a path, or with . or .. in it, is refused before anything is looked
    at, with the path Windows reads - Inno Setup takes such a path as typed and installs by the path Windows reads, so
    that setup would make the folder the name names, which no path reaches, and Inno Setup make and install into its own
    with what the folder above hands down - and setup makes no folder whose name ends in a dot or a space. In 6.3.7 Inno Setup made the folder with the permissions the folder
    above hands down, and icacls replaced them just after, and made a missing Bin or ControlPanel with what the
    program folder handed down. The uninstaller is given each folder setup made (section_dirs.iss) and removes it when
    it is empty, and those still empty are removed when setup is cancelled or fails before its files: each recorded only
    once it was found where it was named, with where that is, and removed only while it is still that folder, read
    through a handle opened on its path as itself while the folders above it are still held; a folder found made
    elsewhere is left as it is. An empty Bin or ControlPanel that was there, which setup removes and makes again, is
    removed too when setup is cancelled or fails before its files: such an installation does not put back the empty
    Bin or ControlPanel that was there. An empty folder on the program folder's own level that was there, which
    setup's last look removes (R2), is made again by setup at once, and a cancel or a failure leaves it in place.
    InstallerPackaging.cs, build/check-installer-scripts.py and the installer smoke
    test, which reads each folder's permissions the moment it appears, hold it
    (ServiceAccount.md, "Before the files"). Found by the hMailServer project
    (Progressive Robot Ltd).

  • Fixed: on Windows, setup hardens a program folder, Bin and ControlPanel that are already there before it
    copies a file or runs a program of the installation, and does not install into one it cannot harden
    (work item
    78
    , CVE-2026-107571, R2): a server installed into a
    folder made beforehand, an upgrade, a repair, and the Control Panel alone, whatever the folder holds. A program
    folder that is the root of a volume, which a mount point or a junction to a volume's root leads to, is refused,
    since its permissions are the volume's and are handed down to what is made there, the uninstaller among it: the
    refusal says to install into a folder on that volume; so is a Bin or a ControlPanel that leads to the root of a
    volume. The program folder is left as it is only when it is the earlier installation's and its Data, Logs,
    Temp, Database or Events still inherit from it, as an installation before 6.3.6 leaves them, which
    /ProtectFolders hardens after the files, and no other account may change it - an inherited entry or a group of
    every user's own that lets another account write it counts whatever it holds, one emptied of all but empty
    folders among them, so that setup hardens it and refuses until a restart, and an entry of its own for a named
    account is said; its Bin and ControlPanel are hardened or made before them all the same. Whether it is left so
    is decided once, before anything changes, and the restart rule goes by the same answer: asked again before the
    files, an answer that differs - a data folder made, renamed or removed since setup looked - ends the installation
    as a folder changed while setup prepared it, exit code 7. Each folder is read through the handle that holds it, and read again
    once hardened. An entry of its own that lets a group of every user change it is taken away - each group the server
    counts as one, CONSOLE LOGON, NETWORK, BATCH, SERVICE, ANONYMOUS LOGON, the two groups of application
    packages, This Organization, Local account, Authentication authority asserted identity, ALL SERVICES and a
    domain's Domain Users and Domain Guests among them, which /ProtectFolders and its CHECK lines count too; one
    for another account is kept and said (R4), but not on a folder whose permissions another account could change, as
    below. A folder of the
    program that is there and that another account could change before this installation - one it owns, one an entry
    lets it change the permissions or the owner of (Everyone Full control on a folder made beforehand, say), or one
    that holds anything but empty folders and that it may write, by an entry setup's hardening takes away, an inherited
    one or a group of every user's own - a named account's own, which setup keeps and says, is not counted, and so does
    not refuse every run - or one that holds a program, a library, a script or a settings file whose inherited entry,
    kept from where it was made and not handed down by the folder it is in, lets another account change it, or a folder
    moved so into Bin, ControlPanel or a folder that holds what is run, which keeps its entries as a file does and
    hands them down to every file in it - keeps that
    right in a handle a program the account started then, whatever setup does to the permissions after. Setup hardens
    each before it stops the service - one whose permissions or owner another account could change rebuilt as the
    earlier installation's folder another account owned is, since through that right the account could have written an
    entry of the folder's own for itself, which setup cannot tell from one an administrator gave: every entry of its own
    that lets another account write is dropped and named in the setup log - and refuses the installation, exit code 7,
    until Windows has restarted, which
    closes every such handle, and records that in a volatile key of HKEY_LOCAL_MACHINE,
    SOFTWARE\hMailServerSetupRestart, which Windows does not keep past a restart, by {app} and by where the program
    folder, Bin and ControlPanel are, read through the handles that hold them; setup run again before it, by that
    name or another for the same folder - a junction or a mount point to it, a short name, a second drive letter - is
    refused by the record, and a refusal whose remedy ends in a restart is recorded the same way. A data folder of the
    program folder, Data, Logs, Temp or Database, and the folder a data folder's link leads to, is held to a
    rule of its own before anything changes, since /ProtectFolders keeps a data folder's own entries and its owner
    after the files: its owner SYSTEM, the Administrators, TrustedInstaller or the service, and no entry of its own
    that lets another account but the service write it; one that is not is refused, exit code 7, with icacls /setowner *S-1-5-32-544 or /remove:g and a restart of Windows, but for the earlier installation's that another account
    owns, which setup takes before a restart as it takes Bin, its permissions rebuilt with the service's entries kept
    and none for Users; and the files of the Database are held to the rule of a settings file by their owners and
    their own entries. A folder another account owns is taken so only when it is the earlier installation's, whose
    folders an administrator's own account may own: it is given to the Administrators with takeown /A, and its
    entries that give no right to write are kept, the service's Read & execute on Bin among them, as
    /ProtectFolders keeps them. Any other such folder is refused, since its owner may hold it open with the right to
    change its permissions: the refusal says to remove or rename it, or to install elsewhere. So that nothing another
    account opened while it could write a folder decides where the files go, a Bin or a ControlPanel that held
    nothing when setup first looked, before anything changed, is removed and made again by setup whoever could write
    it, one that holds something is refused when it has been made a link since, read through the handle that holds it,
    before it is hardened, the program folder, Bin and ControlPanel are read again through their handles for a link
    just before setup's last look at what is in them, an empty folder in Bin, ControlPanel and the folders that
    hold what is run, and, on the program folder's own level, an empty folder setup installs into or makes again - one
    of those five, or a data folder when the server is installed - is removed before the files through a handle read as
    that folder, the last made again at once by setup - with the list of entries it had when it inherited nothing, so
    that an earlier installation's private Logs and Temp stay private and a cancel leaves them in place, and
    otherwise with what the program folder hands down now and the entries of its own it had, so that an entry it kept,
    still marked inherited, from a folder it was moved in from is not carried over onto it and the files copied into
    it - and another empty folder there, which nothing installs into or runs from, is left and said, a folder
    hardened in place keeps a file of setup's until the files are in, so that none can be made a junction, and after
    the files, before setup runs anything of the installation, the three are read again through the handles that hold
    them and looked through for a link or an empty folder setup did not leave - only what was found as setup left it,
    each folder opened as itself where the one above it says - which is named and not removed; anything not as setup
    left it ends the installation with exit code 12, runs nothing of it - the Control Panel the last page offers
    included - sets the service not to start, until setup run again with the folders as it leaves them sets it to start
    again as it did - Automatic, Automatic (Delayed Start) or Manual, the start type it read and recorded first; a
    service that was Disabled already, as a standby kept from starting is, is left so, and one an administrator has set
    to start otherwise since is left as they set it - and keeps setup from running again before Windows has restarted. A refusal after setup stopped the service
    that found a folder changed sets the service not to start too, and hMailServer.Updater puts no earlier version
    back over such folders, on 12 or on that 7. A folder setup cannot harden ends the installation with exit code 7 and
    what to do. In 6.3.7 a server installation into a folder that was there, and every upgrade, had the three hardened
    only at the end of setup, by /ProtectFolders, and a folder another account owned, which only an installation of
    the Control Panel alone hardened before its files, was left with the entries of SYSTEM, the Administrators and
    Users alone. Found by the hMailServer project (Progressive Robot Ltd).

  • Fixed: hMailServer.exe /ProtectFolders says a key or certificate file that a group of every user may read by
    an entry it inherits as inheriting it
    (work item 78,
    R4). A file in Data\Certificates, Data\DKIM or Data\ACME that inherited such a group's Read from an entry of
    the data folder's own, which the line on the data folder names, was said to have permissions of its own, as a
    file moved there keeps them, with icacls "<file>" /reset as the remedy, which changes nothing; it is now said
    as inheriting the entry from the folder whose own entry it is, with the icacls "<folder>" /remove:g that takes
    it away there, and an entry of the file's own as before. A key file the configuration names in one of the
    program's own folders is said the same way, and --report-only no longer says one as outside the program's own
    folders: it says a key file whose own entry lets such a group read it, in those three folders or named by the
    configuration in one of the program's own, as /ProtectFolders does, and what a file inherits by the line on its
    folder, which says who may read the folder now. Since the release after 6.3.7 counts This Organization,
    Local account, Authentication authority asserted identity, ALL SERVICES and a domain's Domain Users and Domain
    Guests among the groups of every user, both lines could be written of a data folder an administrator had given one
    of them. Found by the hMailServer project (Progressive Robot Ltd).

  • Fixed: on Windows, setup refuses a junction, a symbolic link or a mount point on the way to the program folder,
    Bin or ControlPanel that anybody but SYSTEM, the Administrators and TrustedInstaller controls, and a folder on
    the way that another account may rename or empty
    (work item
    78
    , CVE-2026-107571, R3). Every folder from the root of
    the volume down is held open as itself, not shared for deleting, from the page before the installation to the end of
    setup, and a link is read through the handle that holds it: refused when another account owns it, has a right on it
    that changes it or what it leads to, or may remove it from the folder it is in: every right the server's
    OnlyAdministratorsControl counts on a link. A link an administrator made is refused as well when the rights on it,
    those it inherits among them, let another account change it. The refusal says how to make the link
    the administrators' alone (icacls <link> /L /setowner *S-1-5-32-544, then /L /inheritance:r /grant:r with SYSTEM
    and the Administrators full control and Users Read & execute) and to restart Windows before setup runs again; setup
    then goes through it and on from where it leads, a mount point to a volume's root included, and a relative
    symbolic link taken as Windows takes it: from the root of its volume when its name starts with a separator, from
    the folder it is in otherwise, and refused when its name goes above that root. A folder on the way that
    is not a link, the root of its volume included, is held to the rule the live update holds the folders above the
    program folder to: owned by SYSTEM, the Administrators or TrustedInstaller, and nobody else may rename it, remove
    what is in it or change who may; the refusal names the account and the icacls that takes the right away, and says
    to restart Windows before setup runs again, which setup keeps it from doing until then. Refused too, before anything
    changes: a program folder on a share or a network drive; a program folder, Bin or ControlPanel on a volume that
    keeps no permissions (FAT, exFAT), or whose kind cannot be told, each asked of its own volume; and walks of the
    three that disagree, as when a folder or a link is made while setup looks. The three are looked at, changing
    nothing, before setup stops the service, so that a refusal leaves a running server running; a refusal after setup
    stopped it says so, and how to start it again, also when an earlier click on the page found the stop slow. The
    refusal is PrepareToInstall's, so a silent installation ends with exit code 7. In 6.3.7 setup installed through
    any link and said in its log that it had not hardened it, and refused no folder above the program folder: it read
    the one a new program folder would be made in only to warn, and /ProtectFolders named one others may rename on a
    CHECK line after the files. Found by the hMailServer project (Progressive Robot Ltd).

  • Fixed: on Windows, setup does not install where the service and the administrators would run a program, a library
    or a script another account controls, and says what an administrator is to look at in a program folder that is
    already there
    (work item 78, CVE-2026-107571, R4).
    Everything in Bin and ControlPanel at any depth, on the program folder's own level, and in Addons, Events,
    DBScripts, WebAdmin and Languages at any depth - where the service runs the event script, the administrators
    the tools, and DBSetup the SQL - and in MySQL, which holds the bundled MySQL's programs, is looked at before
    setup stops the service and again once the folders are
    hardened, each item but a link, which is known by what the folder's listing says of it, through a handle opened on
    it as itself: a link there - on the program folder's own level one only the administrators control is taken for
    Data, Logs, Temp and Database alone, where it leads then held to the rules of the program folder's own way,
    every link and every folder above it, its volume and the folder being there, and any other there is refused
    whoever controls it, Addons, Events, DBScripts, WebAdmin and Languages, which setup installs into,
    MySQL and the uninstaller's unins000.dat among them - or a program, a
    library, a script (in those five folders, MySQL and on the program folder's own level), a file that says what
    they run or where (Bin's
    hMailServer.INI, the REST API's key store hMailServerApiKeys.ini, which may also be the service's, with the
    Modify /GrantServiceAccess gives it, a .config or a .json beside a program, and the uninstaller's
    unins000.dat) or a folder whose owner is not SYSTEM, the Administrators or TrustedInstaller, or cannot be read, or
    an entry of whose own lets another account change it, ends the installation with exit code 7, each named with the
    icacls that deals with it and with a restart of Windows before setup runs again - as the live update's check of an
    installation refuses them in Bin and ControlPanel. Where a data folder's link leads is held as well to what
    /GrantServiceAccess refuses after the files - a drive's root, the Windows folder, a Program Files folder, the
    profiles folder, ProgramData\Microsoft, ProgramData itself or a folder that holds it, and the program folder
    outside its data folders, Bin above all - and a Database link's target, and every file in it, to the rule of a
    settings file: its owner SYSTEM, the Administrators, TrustedInstaller or the service, and no group of every user
    able to change it, since the built-in database there names the program the service runs to scan for viruses. The
    folder a data folder's link leads to is held to the rule of a data folder too (R2). Data, Logs and Temp, which
    the service writes in, are not looked inside, and Database only for its files. Another file such an account owns,
    such as a note, and an entry of a folder's own that lets a named account or group change it, which setup keeps on a
    folder the administrators own as /ProtectFolders does, are said in the setup log with the icacls that deals with
    each, and an attended installation shows them in one box. Setup replaces the files it installs, and leaves one it
    does not install as it is. In 6.3.7 setup looked at none of it before the files; after them /ProtectFolders held
    every folder, program and library in Bin and ControlPanel, and Bin\hMailServer.INI, to the live update's rule,
    and said the first it found on a CHECK line. Found by the hMailServer project (Progressive Robot Ltd).

What changes on upgrade without a switch

  1. ACME: a host the certificate lacks is ordered at the first check. A server whose AcmeDomains lists a host its
    certificate does not name orders a certificate for every host at the first check after the upgrade, where it waited
    for the renewal; if the CA cannot validate that host, the attempt fails from then on, at most three times an hour
    (no order within twenty minutes of a failed one), and since each order names every host, no certificate is issued
    and the one the other hosts use is not renewed while it stays on the list. A name no certificate can carry - a
    wildcard, an IP address, a name past ASCII not in its xn-- form, one holding a character no host name can - is left
    out of the check and the order and named in the application log at each check, where it failed every order. A CA's
    rateLimited answer holds every order until its Retry-After (an hour without one, at most seven days). An ACME URL
    that is not https is refused unless it is to this machine. The certificate a CA sends is installed only when it is
    a PEM certificate for the request's key, naming every host asked for and not expired; otherwise the certificate in
    use stays, and the log says why. Installing one reinitialises the server, as before. (Correctness, "a host added to
    AcmeDomains")
  2. A stop is no longer an error. Work that outlasted a stop of the servers - a reinitialise among them - writes a
    line to the debug log where it wrote HM5136 or one of the other codes the entries name to the error log, which also
    ran an installed OnError script; the error log of a server that reinitialises may be quieter.
    An event script's call that meets a stop fails with "The server is stopping. The request was not completed.", its
    script error goes to the debug log, and the event ends there; Scripting.Reload, CheckSyntax and Enabled met by
    a stop keep the scripts loaded before. A database connection such a call held goes back to the pool, where on SQL
    Server Compact every later statement waited a minute and failed (HM5180). (Correctness, "a session whose
    command outlasted a stop" and "work that outlasted a stop")
  3. With the servers stopped, or after a stop that could not end a thread: a delivery going on after the stop
    defers its recipients (4.3.0) and is retried after the start with the try not counted; a message being finalised
    goes on without a SpamAssassin verdict; an external account's DownloadNow succeeds and the account is fetched when
    the servers start; a clear of the delivery queue reports HM4210 and clears nothing; a change made through COM, a
    message saved into an IMAP folder among them, succeeds where it was answered with an error after being made; and a
    session that lived through a stop gives back its own folder subscription and nobody else's. (Correctness, "an
    IMAP session that ended after a stop" and "a task or a session that a stop of the servers could not end")
  4. On Windows, a stop of the REST API's or the web services' HTTP listener gives the connections it closed five
    seconds to drain, where it gave about six and a quarter. (Correctness)
  5. On Windows, setup refuses an installation of the original hMailServer's 5.7 (5.7.0 and its pre-release builds
    2788 and 2843, 5.7.1 and 5.7.2) before it changes anything; silent, it ends with exit code 1, and writes the reason
    to its log with /LOG and to hMailServer-setup-refused.txt in its %TEMP%, which a later setup that is not
    refused deletes. Installations of this fork, and of the original up to 5.6 and its 5.7 development builds up to
    schema 5708, are upgraded as before. (Correctness)
  6. On Windows, setup and the uninstaller find and stop the service through routines written anew from Microsoft's
    documentation: the service's name reaches Windows through its Unicode functions, the Service Control Manager is
    opened with SC_MANAGER_CONNECT alone; where Windows will not open it, setup, or the uninstaller, says so with
    Windows' reason in its log and in a message, and a stop that Windows refuses is written to the setup log with
    Windows' reason. (Correctness, "the Windows installer finds, reads and stops")
  7. On Windows, setup makes or hardens the program folder, Bin and ControlPanel before it copies a file or runs a
    program of the installation.
    A folder, a link on the way to them, or a program, library, script or settings file
    in them, that another account than SYSTEM, the Administrators and TrustedInstaller controls, and a program folder
    on a share or on a volume that keeps no permissions, end the installation with exit code 7, each named with the
    icacls that deals with it and, where it says so, a restart of Windows before setup runs again; anything not as
    setup left it after the files ends it with exit code 12 and sets the service not to start until setup runs again.
    An entry of a folder's own that lets a named account or group change it is kept and said in the setup log.
    hMailServer.exe /ProtectFolders says a key or certificate file that inherits a group of every user's Read as
    inheriting it, and hMailServer.Updater puts no earlier version back over folders setup found changed. (Security,
    work item 78's entries)
  8. The log's redaction of literals: the slow-query line and the trace's db.statement read a literal as the
    configured database does, so a line may differ from before. (Security)
  9. The auto-ban's ranges: a ban for a long user name is named with the user name cut so that the name and its
    suffix fit the column, and the Auto-ban page shows it so; a range name over 100 characters given over COM or the
    REST API is stored as its first 100, as before, and now reads back as stored; and POST /api/v1/ipranges reads a
    name in UTF-8, as PUT does. (Security, "a run of failed logons")
  10. Sign-in names: a name signs in to an account only when it is the account's stored address, compared in lower
    case, as the C library lowers it in the C locale (with glibc, A to Z alone), after a domain alias and the default
    domain; any other spelling the database would have found the account under, and a name with
    white space at either end, fails as a name that does not exist fails and is counted as a failure, for every
    protocol and for an OpenID Connect or SAML sign-in (Before you upgrade, above). The IMAP master user's own hold,
    country policy and lockout are asked of its own address as well; the account.locked webhook is raised only for a
    locked name that is an account's own; and COM and the Control Panel save a domain alias's name, and COM and the
    REST API the default domain, without white space at either end. (Security, "a sign-in took for an account")
  11. Password reset: POST /api/v1/password-reset counts its per-address limit under the address as a sign-in
    compares it, so every spelling that names a mailbox shares its one limit. (Security, "a mailbox's recovery
    address")
  12. The web services listener and IP ranges: with the listener on, an address whose IP range allows none of SMTP,
    POP3 and IMAP - an auto-ban's, or an administrator's that allows nothing - or one met while the ranges cannot be
    read, is closed with no answer before the TLS handshake; loopback and the addresses in WebServicesTrustedProxies
    are never refused by a range. Such an administrator's range now closes this listener to the addresses it holds -
    ACME's http-01 validation when this listener answers it, MTA-STS, CalDAV, CardDAV, Autodiscover, autoconfiguration
    and the Web Key Directory among them - and each refusal is said in the application log, naming the range, once an
    hour for each address. (Security, "an address an auto-ban or an administrator's IP range banned")
  13. Failed sign-ins through a proxy: a failed sign-in at the web services listener from loopback or an address in
    WebServicesTrustedProxies, and a failed account sign-in at the REST listener from loopback, is counted against
    the name alone (AccountLockoutThreshold, off by default, is then the limit on the guesses at one account through
    the proxy), never towards a ban of the address, and the REST listener writes no Auto-ban: logon failure line for
    it; a failure from any other address counts as before. The REST listener's own accounting spares all of
    127.0.0.0/8, where it spared 127.0.0.1 and ::1. (Security, "a failed CardDAV, CalDAV or Autodiscover sign-in
    through a proxy" and "an account's failed sign-in at the REST listener")
  14. REST request bodies: a body meant to be JSON, on every route under /api/ and /scim/ that takes one, must be
    one JSON object within the bounds the entries give - no text after the value, no member named twice - or it is
    answered 400 saying why (on /scim/, RFC 7644's invalidSyntax), before the route reads a member; every member
    reader reads the one parse, and a member whose absence is the wider setting, written as another type than its
    route reads, is refused too, where it was read as absent (the entry lists them); the reply to a password-protected
    message is held to 2 MB, room for its one armoured message; and a TLS-RPT report mailed to a domain's report
    address is read through the bounded parse, within bounds of its own. (Correctness, "a REST request body meant to
    be JSON"; Security, "the REST API, and the reader of a report")
  15. The database client's TLS: TlsMode=verify-full on MySQL and MariaDB through MariaDB Connector/C without
    TlsCaFile is refused with HM7535 (new) on a connection to this machine (Before you upgrade, above);
    TlsCaFile, and on Windows PostgreSQLSslRootCert, reach the client as named, and a name the client cannot be
    handed as named is refused before anything is attempted, with HM7534 (new) (Before you upgrade, above); and the
    lines that say what was checked - --check-database's settings line, the application log's and the diagnostics'
    connection line - say what the client checks, for PostgreSQL the root certificate libpq is handed.
    (Security, the entries on verify-full and on a root certificate's name; Correctness, "--check-database
    names the root certificate" and "PostgreSQL's connection line")
  16. On Windows, the files the server hands to OpenSSL by name - the TLS certificates' certificate and private key
    files, dh2048.pem, a TCP/IP port's client-certificate CA file, MessageStoreObjectCaFile, OAuth2PublicKeyFile
    and the files in AcmeCertificateDirectory - are read from the file configured, and a name the server cannot hand
    over as it is named is refused with HM7536 (new) in the error log, with what the file was for: a port whose
    certificate or key it names does not start, a client-certificate CA file verifies no certificate, the object
    store's CA file allows no request, the public key verifies no token, dh2048.pem is left out as a missing one is,
    and the ACME client reads and writes nothing there and orders no certificate (Before you upgrade, above).
    OAuth2PublicKeyFile was handed over in UTF-8 up to 6.3.7, so a name outside the code page that worked then is
    refused now. A name in ASCII is handed over as before; on Linux every name goes over in UTF-8, as every other file
    name there does (On Linux, file names outside ASCII, below). The Control Panel's SSL certificates page shows a
    certificate or key file whose name is refused as Name refused, at Critical, and the setup checklist says that
    the server does not read it (error 7536). (Security, "on Windows the files the server hands to OpenSSL by name")
  17. On Linux, file names outside ASCII are UTF-8 whatever LANG says. A server that ran under a LANG that is not
    UTF-8 named such files in that locale's bytes and no longer finds them: rename each folder to its UTF-8 name before
    upgrading (Before you upgrade, above). A LANG, LC_ALL or LC_CTYPE naming a locale the machine does not have
    no longer stops the server at its start; the values --set-setting, --apply-profile, --account and --domain
    take, and --config, are read as UTF-8 and refused with exit 2 when they are not; and what hmailserver prints to
    a terminal is UTF-8 throughout. (Correctness, "on Linux a file's name outside ASCII")
  18. spamd's User: header carries a name outside ASCII, where it was left out and the message scanned, or the
    lesson told, as spamd's default user: an address that arrived as SMTPUTF8 bytes goes as those bytes, any other name
    as UTF-8, and on Windows a name within Latin-1 as before; a name the header cannot carry is sent as no header with
    HM5514 (new). (Correctness, "spamd's User: header")
  19. The legacy Blowfish scheme refuses a secret it cannot carry (HM7278, new), where it wrote an empty one: a
    backup's XML leaves such a secret out and names it in the error log and the backup log (HM7280, new), to be entered
    again after a restore; COM's database calls answer an error for such a password; and on Linux a text within
    Latin-1 is written as Windows writes it. (Correctness, "the legacy Blowfish scheme")
  20. On Alpine, a log line or other formatted text holding a character outside ASCII is written, where it came out
    empty; on every Linux build a formatted text longer than 65,535 characters is made whole, where it came out empty.
    (Correctness, "on Alpine, every log line")
  21. Argon2id where the OpenSSL the server runs with has none: GET /api/v1/status's argon2id and
    post_quantum_key_exchange say what the library the server runs with does; HM5606 and HM5607 name that library;
    and an app password a MinimumAcceptedHashAlgorithm of 5 raises to Argon2id is made with scrypt and said once
    (HM5607), where it was an empty hash the store refused. (Correctness, "GET /api/v1/status said" and "the
    reports that a server has no Argon2id")
  22. Moves between IMAP folders: with SpamAssassinLearnOnMove on, the lessons of a COPY or a MOVE are handed on
    after the command, a MOVE's after its expunge, and the plain copy spamd is sent is made when the work queue runs
    the lesson; spamd is sent the same bytes and taught as before. OnIMAPMessageMoved (IMAPMessageMovedEvents) and
    Rspamd's learning (MoveLearningBackend) are off until they are switched on. (Administration,
    "OnIMAPMessageMoved" and "Rspamd as the filter that learns from the Junk folder")
  23. The container image: it carries MariaDB Connector/C (libmariadb3, with mariadb-common and mysql-common),
    so HM_DB_TYPE=MySQL reaches a MariaDB server; its hmailserver account is made as 999:999, the numbers it had.
    Its entrypoint runs hmailserver --apply-new-install-defaults --if-marked before the command, which secures a
    database its --create-database made and opens nothing at any other start; HM_DB_PASSWORD, HM_ADMIN_PASSWORD,
    their _FILE forms and HMAILSERVER_RECOVERY_PASSPHRASE leave the environment before the entrypoint runs any
    program, and when the command is the server itself the settings' variables (HM_SETTING_*, HM_PROPERTY_*, their
    _FILE forms and the four HM_REST_*) leave it too, so that the server, and what it starts, no longer has them.
    The Compose file's and the Helm chart's commands are shell command lines and still pass them to the server, so a
    setting that is a secret belongs in a _FILE variable there, as their examples give it.
    (Downloads and the release, "the container image reaches a MariaDB database"; Security, "the container image
    secures a new installation by itself" and the two entries on the image's environment)
  24. On Linux, hmailserver --create-database asks first whether the schema's version table is there, so a new
    installation, a container image's first start among them, no longer logs HM5033 as its first error; a database
    that cannot answer that question is still HM5033 and is refused, and one that holds the table with no version
    that can be read is refused too, where the create script ran before. (Correctness, "every new installation's
    create step logged HM5033")
  25. The Helm chart runs the pod as 999:999 with fsGroup 999 (it was 1000), whichever of the three its values leave
    out; an init container puts the image's hMailServer.ini on an empty configuration volume and makes the data
    volume's temp, events and database; the server's container runs hmailserver --apply-new-install-defaults --if-marked before the server, so a new installation is secured at its first start; database.port is empty by
    default, the type's own port; and the pod sets net.ipv4.ip_unprivileged_port_start to 0 in its own
    securityContext.sysctls unless unprivilegedPortsSysctl is false, a release upgraded with --reuse-values from
    a chart that does not name it included. The Compose file runs the same step, and gives its REST listener the pair a
    new installation makes; a pair in ./certs must be readable by 999. (Downloads and the release, "the Helm chart
    starts a server"; Correctness, "the Helm chart's server binds the mail ports")
  26. The Control Panel: the Logging page shows a destination never chosen (0) as Files on disk, and a save that
    leaves the list alone writes 0 back; the Anti-spam page shows a refuse threshold at or below the mark threshold with
    a Warning and saves it, where it opened in error and saved nothing; the Anti-spam overview reads the quarantine
    switch for its verdict; the ACME page and the Setup checklist leave out the names the server leaves out, and say
    so. (Control Panel)
  27. The PowerShell module and hmctl: the module's error for no answer gives the system's reason, and an answer
    that is not JSON is printed as text; hmctl config diff, config apply, --from-json, --password-file and
    --passphrase-file take a file with a byte order mark, where they refused it or took the mark for the password's
    first character; and hmctl refuses a body holding NaN or Infinity, --set name=NaN sending the string NaN.
    (Correctness, the module's two entries; "a REST request body meant to be JSON")
  28. The Linux packages name their maintainer as Christopher Holloway chris.holloway@progressiverobot.com, where
    they gave another address, and their summaries and descriptions, the snap's and the image's labels say Progressive
    Robot's fork. (Downloads and the release)
  29. The release carries hmailserver-6.3.8.intoto.jsonl beside the provenance's Sigstore bundle, 161 links in all.
    (Downloads and the release)

Correctness

  • Fixed: on Alpine, every log line and other formatted text holding a character outside ASCII was empty.
    String::Format on Linux made its text with the C library's vswprintf, and musl's writes each wide character
    through the C locale's multibyte form, which the server runs in and which carries nothing above U+007F but the
    characters 0xDF80 to 0xDFFF, so a format holding any other such character - in its text or in a %s or %c
    argument - failed whole and gave nothing; glibc's copies wide characters as they are. Every line of the default log
    is made by Format: on Alpine no line held a character outside ASCII and a line that would have was not written -
    an IMAP LOGIN with such a name lost its RECEIVED line, and the auto-ban's
    Auto-ban: logon failure ... for <name>, which fail2ban reads, was never written for such a name, though the server
    counted the failure - and protocol replies, REST messages and the other formatted text holding such a character were
    empty too. platform/hm_platform.h now makes the text itself, as glibc's vswprintf makes it, for every value a
    wchar_t holds - the format's text, %s and %c copied with their flags, width and precision, a narrow string
    through the C library's own conversion as before, the numbers by its snprintf, which writes ASCII - and changes no
    locale; a locale of the call's own would not have done, since musl's C.UTF-8 refuses 0xDF80 to 0xDFFF, of
    which a Boost.Filesystem path's wstring() is made there for a name holding a byte above 0x7F. musl's vswprintf
    has no %p either, and ends the process on a null %s, where glibc's writes (null). The layer writes a null %s
    as (null), as glibc's does, and makes a %p with the C library's snprintf, as it makes the numbers, so that a
    null pointer is 0 on musl, where glibc writes (nil); no format of the Linux build uses %p. With glibc 2.37 and
    later the text is what vswprintf made, but for one thing: a text longer than 65,535 characters, which the old
    measure in a buffer that doubled up to 64 KB gave as nothing, is now made whole, as on Windows (2.36 and older,
    Debian 12's and Enterprise Linux 9's, also refused a %c of WEOF, which the layer writes, as 2.37 does). It costs
    more there: a short format, a log line's among them, takes 1.4 to 2.0 times as long as glibc's vswprintf took
    (0.04 to 0.27 microseconds more, clang -O3), and a long text less; on musl every format measured takes less time
    than before. A new tester program, string-format-tests, which linux-build, the package builds, the APKBUILD's
    check(), debian/rules and the RPM spec's %check run, asks for U+00E0, U+0130, U+20AC, U+1F600, 0xDF80 to
    0xDFFF, the other surrogates, U+FFFE, U+10FFFF and the values above it through %s, %c and the format's own
    text, holds the formatter to the C library's own vswprintf over a table of formats, and logs lines holding those
    characters through the default format, the event log and the backup log and reads them back.
    Alpine.md, "What musl changed", says so. And
    LogonNameVariants.OverHttpBasicAVariantSpellingFailsAndIsCountedAsAFailure expects of the dotted capital I what
    the server's C library does, as LogonName.h states the rule: on a musl server that spelling is the account's own
    name and signs in, and elsewhere it fails as a name that does not exist fails. And both of the layer's format
    rewriters stop at a format's end: one that ended inside a conversion - a % at its end, or flags, a width or a
    length with nothing after them - made them step past its terminating NUL and read on into what lay after it, on
    glibc and musl alike, which AddressSanitizer reported of the new tester.

  • Fixed: Unicode::ToANSI gives on Linux the bytes it gives on Windows: each character up to U+00FF as its byte,
    and nothing at all for a text with a character above it.
    Up to 6.3.7 Linux ran wcstombs in the C locale, which
    the server never changes, and gave nothing for any character outside ASCII, where Windows' wcstombs_s in its own C
    locale gives Latin-1 (measured with MSVC 19.51 on 30 October 2026: U+00E0 is e0, U+0080 80, U+00FF ff, and U+0130 or
    U+20AC anywhere in the text empties it). What reached it on Linux: the legacy Blowfish scheme, spamd's User:
    header and Formatter::FormatAsAnsi, whose exception texts name files (the entries below), and a 503's text from
    the CRL endpoint, which is ASCII. unicode-tests (new, run by linux-build's jobs, the APKBUILD's check() on musl and
    the source packages' builds - debian/rules' TESTERS and the spec's loop, which build/check-source-tarball.py holds
    to CMakeLists.txt) runs FormatterTester, which ClassTester runs on Windows, over each case, with LANG=C.UTF-8 and
    unset.

  • Fixed: Formatter::FormatAsAnsi with five arguments puts each in its place. It passed the first, the second and
    the fifth to the three-argument Format, so that {0}|{1}|{2}|{3}|{4} gave one|two|five|{3}|{4}. Nothing called
    it with five; FormatterTester now holds every count of arguments.

  • Fixed: the legacy Blowfish scheme refuses a secret it cannot carry, and its writers say whose it was and why,
    where it was written as an empty secret without a word.
    LegacyBlowfish::EncryptToString takes a text as
    Unicode::ToANSI's bytes, which for a text with a character above U+00FF are none, and encrypted those: the value
    written read back as no secret at all - on Linux for any character outside ASCII. It now refuses such a text, HM7278
    (new) in the error log, and answers nothing, as it does for one longer than 248 characters (HM7277). A backup's XML,
    which carries secrets in that scheme, says in the error log and the backup log, in one line, which secret it was
    written without and which of the two limits refused it - a character above U+00FF, or more characters than the
    scheme holds, as an external account's OAuth refresh token often has (HM7280, new; LegacyBlowfish::Refusal,
    Crypt::BackupRefusal): an external account's password and OAuth token, a route's relayer password, a certificate's
    private key password - so that it is entered again after a restore (Crypt::EnCryptForXml). A person export, whose
    file carries no credential, encrypts none and reports nothing to the backup log (Backup::BOLeaveOutCredentials).
    IniFileSettings::SetPassword leaves the database password in hMailServer.ini, and the running process's copy, as
    they were, with HM7279 (new) naming the limit, where it wrote an empty one, and answers false; COM's
    Database.CreateInternalDatabase, CreateExternalDatabase and SetDefaultDatabase answer an error for it, where
    they answered success, and the last two refuse a password the scheme would refuse before anything is created, when
    the machine's own store is not used. COM's Utilities.BlowfishEncrypt answers an error for such a text; and
    Crypt::ProtectSecret's fall-back to the scheme on Windows answers empty with HM7278, which its callers already
    read as not stored. A text within Latin-1 is written on Linux as Windows writes it, and a value Windows wrote reads
    back on Linux as the text it was: legacy-blowfish-tests' table of what 6.3.4 wrote on Windows now holds on both
    platforms, where at the commit before this Linux wrote dbd2989fb2ae94d1, an empty text, for each of its four texts
    outside ASCII; its LegacyBlowfishTester, which ClassTester runs on Windows, holds each refusal's reason and HM7280's
    words; file-utilities-tests holds that a password above U+00FF and a token of 249 characters are each left out of a
    backup with one HM7280 naming whose it was and why, and no HM7277 or HM7278, where at the commit before this change
    each was reported a second time, by HM7278 or HM7277; and the regression suite's BlowfishRefusesATextItCannotCarry
    holds COM's error and HM7278 on Windows.

  • Fixed: spamd's User: header carries a name outside ASCII, where it was left out without a word and the message
    was scanned, or the lesson told, as spamd's default user.
    SpamAssassinUser, and a recipient's address with
    SpamAssassinUserFromRecipient=1, went through Unicode::ToANSI, which gave nothing for a name with a character
    outside ASCII on Linux and above U+00FF on Windows, and an empty user sends no header. spamd reads the header's
    bytes as they are and refuses a request whose User: holds a control character (got_user_header in spamd,
    PROTOCOL: the meaning of the name is spamd's own). Now an address held as the bytes it arrived in - SMTPUTF8's -
    goes as those bytes, any other name as UTF-8, and on Windows a name within Latin-1 as its Latin-1 bytes, as before
    (SpamAssassinClient::UserBytes); a name the header cannot carry, a control character or half of a surrogate pair,
    is sent as no header with HM5514 (new) in the error log. unicode-tests' SpamAssassinUserTester, which ClassTester
    runs on Windows, holds each, where at the commit before this the first name outside ASCII went as nothing.
    README.md's lines on the two settings say so.

  • Fixed: on Linux a file's name outside ASCII is one name to every call that opens the file, UTF-8, whatever LANG
    says.
    Boost.Filesystem converted a wide name with the locale the process started in: UTF-8 under a UTF-8 LANG on
    glibc, and an uncaught exception (boost::filesystem::path codecvt to string) with LANG unset - as the container
    image leaves it - and on Alpine whatever LANG says, since libstdc++ on musl has no UTF-8 locale. File::Open and
    the other narrow calls - stat, open, link, rename, access, chmod, statvfs, libstdc++'s streams, the
    listeners' and ACME's certificate and key files, the client-certificate CA, OpenSSL's BIO_new_file, execvp's
    command line, working directory and output file - were handed AnsiString(String)'s Latin-1 bytes, '?' above U+00FF.
    So one name outside ASCII was two files, or a file and an exception. Now Boost.Filesystem is given a UTF-8 conversion
    of its own before main (FileUtilities::UseUtf8FileNames, Boost's own facet, the same on glibc and musl), every
    narrow call the bytes of FileUtilities::NarrowPath, UTF-8, beside IniFile, DataProtector, OAuth's key file and
    libpq's root certificate, which wrote UTF-8 already, and a name the system gives as bytes is read as UTF-8
    (FileUtilities::WidePath): --config and --if-marked, refused with exit 2 when they are not UTF-8,
    /proc/self/exe, from which the program folder is made, a directory of PATH where 7-Zip is looked for, APPIMAGE,
    the image an update replaces, and APPDIR, where the AppImage runtime mounts that image under TMPDIR and from where
    the update helper is copied - each of the two refusing the update, with the reason, when it is not UTF-8, where under
    a TMPDIR outside ASCII the helper's name was its UTF-8 encoded a second time, the copy found nothing (HM6891) and
    the image could not update itself - and RUNTIME_DIRECTORY, where the update's request is put, which systemd names in
    ASCII for the packaged unit. Functions that held a file's name narrow take it as it is: MimeBody's LoadFromFile,
    LoadHeaderFromFile and SaveAllToFile and MessageStore::LoadMime, so that a message under a folder named above
    U+00FF, which was written, is read - by IMAP's FETCH of a part or of the structure, the rules, scripts, signatures and
    the indexer, the virus scan's pass over each attachment, attachment stripping and a Sieve script's header edits -
    where it was looked for under '?', and iMIP's look for an invitation, which handed LoadMime the name's UTF-8 to be
    encoded again and read nothing under any name outside ASCII; and DKIM's signer, which looked for a key file under such
    a folder with '?' in its name and signed nothing (HM5306). On Windows these narrowings put '?' for a character outside
    the code page, and they take the name there too. The crash record is written under a LogFolder outside ASCII, where
    its name was in no directory there is; the update helper is made executable by its name, and a failure to is refused
    (HM6891), where chmod named another file and nothing was said; and the update's request names the package, the
    rollback image, the outcome and the log in UTF-8, as the helper is given the Updates directory, where under a
    DataFolder outside ASCII the helper found them outside it and refused the update. The values --set-setting takes,
    on the command line or from standard input, and --apply-profile, --account and --domain are read as UTF-8 too
    and refused with exit 2 when they are not, where each byte was widened to a character and a value outside ASCII was
    stored, or looked for, as other text - for a path, its UTF-8 encoded a second time, the name of no file.
    --set-admin-password still widens its bytes, which is safe: the hash is made over Unicode::ToUtf8's bytes, which
    for UTF-8 widened so are the bytes typed, the ones a sign-in's UTF-8 gives. What hmailserver prints to a terminal is
    UTF-8 throughout, as --check-database's and --apply-environment's lines were, where platform/main.cpp printed
    some paths in UTF-8 and every other text, the configuration file's name among them, in Latin-1 with '?' above U+00FF.
    That covers the [Directories] folders, TlsCaFile, the domain and account folders of the data directory and the
    messages in them, the backup's and the archive's files, DKIM key files, the logs and the crash record, the message
    store's links and stamps, the GeoIP database, the update's Sigstore bundles, helper and request, the stored-secrets
    key and the files the REST API serves and keeps. On Windows the other narrow calls were and are given the code page's
    bytes, and the wide ones the name. And a LANG, LC_ALL or LC_CTYPE naming a locale the machine does not have -
    LC_CTYPE=UTF-8 from a Mac's SSH session, which sudo keeps, or a container's LANG with no such locale generated -
    changes nothing, and no longer stops the server: up to 6.3.7 its start ended at the first name Boost.Filesystem
    converted (exit 134, locale::facet::_S_create_c_locale name not valid), Boost asking the C++ library for the locale
    named there, which glibc's refuses for one it does not have; Boost.Filesystem now converts with a UTF-8 conversion of
    its own and never asks for that locale, and the one answer Boost 1.90's path::imbue still makes from it, after the
    conversion is in place, is caught. Upgrading a Linux server that ran under a LANG that is not UTF-8, a Latin-1
    locale say: it named its files in that locale's bytes - Boost.Filesystem by the locale, File::Open in Latin-1 - and
    from this release names them in UTF-8 whatever LANG says, so that a data directory, a domain or account folder,
    LogFolder or TlsCaFile named outside ASCII under such a locale is not found after the upgrade, and new folders are
    made beside the old ones: rename each to its UTF-8 name before upgrading, for example with convmv -f iso-8859-1 -t utf-8 -r --notest <folder>. Under a UTF-8 LANG, the packages', a folder named outside ASCII was Boost's, in UTF-8,
    and is found as it was. file-utilities-tests holds it (FileUtilitiesTester::TestFileNameEncoding and
    MessageFileNameTester, which ClassTester runs on Windows too): a file named with U+00E0, U+0130 and U+20AC, made by
    File::Open, is found by FileUtilities::Exists, Boost.Filesystem and stat and listed under its name, and a
    message under folders named in CJK, Cyrillic, a character beyond U+FFFF, a combining accent and Latin-1 is read by
    MimeBody, LoadMime and MessageData and written back by SaveAllToFile, with LANG=C.UTF-8 and unset; before
    this change NarrowPath's stand-in, AnsiString(String), gave 63 e0 2d 3f 2d 3f for the name's first six characters,
    and at the commit before this change the message under the folder named in CJK was not read by
    MimeBody::LoadFromFile; and the regression suite's Signing.TestSigningWithAKeyFileNamedOutsideLatin1 signs with a
    key file in a folder named in CJK and Cyrillic. file-utilities-tests also runs itself again under
    LC_ALL=xx_YY.UTF-8, which reaches main and converts a name to UTF-8, and names the AppImage's update helper from
    an APPDIR in UTF-8 and refuses one that is not, in a build with the live update; with the exception left uncaught
    the run again ended by signal 6 before main, and with APPDIR widened as before the helper's name was other
    characters, on glibc under each LANG. Measured on 31 October 2026 with hmailserver --check-database built in the
    CI image, under LC_ALL=xx_YY.UTF-8, against MariaDB 13.0.2 and MySQL 8.4.11 (each by its image's digest): at the
    commit before this change all 24 cases ended with exit 134, and with it each connected or was refused as under
    LANG=C.UTF-8.

  • **Fixed: --check-database names the root certificate libpq is handed for PostgreSQL, where it named TlsCaFile


These notes are cut here for GitHub's limit. The whole notes are on the release on GitLab, https://gitlab.com/hmailserver/hmailserver/-/releases/v6.3.8, and attached here as release-notes-v6.3.8.md.