Security
The COM API reported success on calls it had refused (0d01a55)
Fifteen COM methods rejected an unauthorized caller with return false. These functions return HRESULT, where false is 0 — which is S_OK. Every one of them reported success for a call it had just refused.
- InterfaceCache's five getters return before writing *pVal, so a caller without server-admin rights received S_OK and read whatever happened to be in the out-parameter — uninitialized memory handed to any COM client.
- InterfaceSettings::SetAdministratorPassword and five siblings skipped the write and reported success, so a caller was told the administrator password had changed when it had not.
- InterfaceMessageIndexing's four methods behaved the same way.
This was the file's own convention being broken rather than a design choice: InterfaceSettings.cpp already returned authentication_->GetAccessDenied() in twelve other places, and each of the fifteen sites called GetAccessDenied() one line above for its null-config check. They now do the same for the authorization check.
Found by running CodeQL's C++ suite locally. The workflow analysed C# only, so 4.65 MB of network-facing code — the largest language in the repository and the entire protocol surface — had never been scanned. The scan returned 16 high-severity findings, 15 of them these. Re-running it after the fix returns 1, a verified false positive in FileUtilities.cpp (the cast binds before the + 1, so it skips a UTF-16 BOM rather than a byte).
The PHP WebAdmin is removed (53f52eb)
It shipped by default under "Administrative tools" and stored the mail server's administrator password in plaintext in a PHP session:
$_SESSION['session_password'] = $password;
replaying it to Authenticate() on every request. It also required DCOM permissions to be opened up for the web server account, and it was unmaintained 2008-era code. The Control Panel replaces it and already connects to a remote host, so nothing is lost — 145 files, and the installer dropped from 400 payload files to 255.
Removals
The retired Administrator is gone from the repository (acde61a)
hMailAdmin.exe was retired in 6.2 when the Control Panel became the sole bundled GUI, and the installer has not shipped it since. The project stayed in the tree, still built by the Tools solution, and was the single largest contributor to the repository's code-quality findings: 333 of 958, including 71 of the 132 that indicate a real defect rather than style — all of it in a GUI no user runs.
The one thing still needed from it was Interop.hMailServer.dll, which the installer took from its build output. That was incidental: the file is a tlbimp wrapper around the COM type library, and all six tools declare the same COMReference and generate an equivalent assembly. The installer now takes it from Shared, so the packaged output is unchanged.
"Administrative tools" says what it actually does (79d1134)
With both front-ends gone the component delivered libraries and no application — anyone ticking it in a custom install got the VC runtime, OpenSSL, libpq and the translations, and nothing to run. What it actually does, and always did, is register the hMailServer type library on a machine that is not the server so scripts and COM clients can administer a remote instance. It is now called "Remote administration support (registers the COM API for scripts)". The Control Panel never needed it — it binds late through IDispatch — and has its own component.
Also drops a dnsapi.dll line gated OnlyBelowVersion: 0,6 (pre-Vista) in an installer whose MinVersion has been 6.1sp1 since the move to Inno Setup 6. It could never install.
Quality
Code-quality findings in shipped, hand-written code: 958 → 0 (d1c35ec, dbdf15a)
The Code Quality grade read "Poor" off 733 findings. Running CodeQL's own csharp-code-quality suite locally over every C# solution gave 958 and showed why: 333 in the retired Administrator, 297 in test harnesses, 227 in the Control Panel, 101 in the legacy setup tools. Two thirds sat in code no user runs.
Real defects fixed
- DBUpdater rethrew with throw ex, resetting the stack trace on the database-upgrade failure path — precisely where the original trace matters.
- Three as casts dereferenced without a null check (the wizard's page handling in Shared, the upgrade-script loop in DBUpdater).
- The error dialog's inner-exception branch had + Environment.NewLine inside the format string, so it printed that text instead of a line break.
- PngByteQRCode was built per render in the two-factor dialog and never disposed.
- DBSetup looked keys up twice (ContainsKey then the indexer).
Then 57 more across the Control Panel and setup tools: 16 fields made readonly, 9 if/else branches collapsed to a conditional, 6 x == false comparisons, 10 loops expressed as LINQ where genuinely clearer, 4 redundant ToString() calls.
Excluded rather than "fixed", with the reasoning recorded in .github/codeql/codeql-config.yml: *.Designer.cs (Visual Studio rewrites those files on the next designer save), cs/path-combine (10 hits, every one a false positive — the later argument is always a literal or a filename the code just built), and the catch-all/empty-catch pair across a COM-interop GUI that must not crash the window on one failed call.
Worth recording, since it is not obvious: the C++ server core contributes nothing to these numbers. CodeQL ships no quality-tagged queries for C++ at all (C# has 108), so the largest language in the repository is not graded by Code Quality in either direction. That is why the security scan above had to be run by hand.
A real defect in the regression suite
OpenTelemetryTracing bound its OTLP collector to port 9099 — the same port DeliveryMetrics, HealthProbes and DatabaseMetrics point the server's own metrics listener at. With NUnit running 32 workers in parallel the collector could not bind. It now uses 9096, and the fixture passes.
One build path, one output folder
Building ControlPanel.sln (x64) and running dotnet build ControlPanel.csproj (AnyCPU) wrote to two different bin folders, while CI and the installer both read one fixed path — so a solution build followed by an installer build would have shipped whichever binary the last csproj-level build happened to leave behind. The output path is now pinned.
Upgrading
Drop-in over 6.2.9. No database change (schema version 6005), no configuration change.
If you had "Administrative tools" ticked purely to get the WebAdmin or the Administrator, there is nothing to migrate to — use the Control Panel, which connects to a remote server directly. If you had it ticked to run COM scripts against a remote instance, that still works and the component still does exactly that.