Skip to content
  • Categories
  • Recent
  • Popular
  • Tags
  • Users
  • Groups
  • Documentation
  • Downloads
  • Report a bug
  • GitHub mirror
  • Paid support
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
hMailServer Community Forum
hMailServer 6.3.8 is the current release, signed 2 November 2026. Windows and Linux, and an in-place upgrade from 5.3 or later, but for the original hMailServer's 5.7.Download 6.3.8 Documentation FAQ
  1. Support and discussion for hMailServer
  2. Automation & integration
  3. REST API & integrations
  4. Active Directory and LDAP sign-in, directory synchronisation and OAuth2 bearer tokens

Active Directory and LDAP sign-in, directory synchronisation and OAuth2 bearer tokens

Scheduled Pinned Locked Moved REST API & integrations
securityadministrationwindowslinuxcontrol-panel
1 Posts 1 Posters 121 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • ProgressiverobotP
    ProgressiverobotP
    Progressiverobot
    hMailServer Team
    wrote on last edited by chris
    #1

    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes three things this guide relies on. A Control Panel signed in to a server on another computer no longer reads or writes its own computer's hMailServer.ini for the [LDAP] values, which it cannot read from the server, where it showed that file or the defaults as the server's; the sidebar is regrouped, and Ctrl+K takes every old page title to its page. With SMTP logging on, 6.3.6 and every earlier version that offered them wrote the bearer token of AUTH XOAUTH2 and AUTH OAUTHBEARER and the lines of SCRAM-SHA-256, OAUTHBEARER and GSSAPI into the SMTP log in the clear; 6.3.7 hides every line of a SASL exchange but the user name of AUTH LOGIN and the authentication identity of AUTH PLAIN. An account given the serveradmin role with a list of domains is, over COM as over REST, confined to those domains and no longer the server's administrator, so a script that runs the directory synchronisation's preview and apply as such an account needs the role given with no list. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) signs a name in to an account only when it is the account's stored address, compared in lower case after a domain alias and the default domain, for the name an OAuth2 bearer token carries as for a password (work item 104), so a name that differs from the stored address in more than the case of the letters A to Z, or has white space at either end, may no longer sign in. On Windows an OAuth2PublicKeyFile whose path holds characters outside ASCII may be refused with HM7536 (one outside the code page, which worked up to 6.3.7, is refused), and then the key verifies no token: give the file, and the folders above it, an ASCII name before upgrading. See the 6.3.8 announcement.

    This topic covers three ways an organisation's directory or identity provider vouches for somebody on hMailServer 6.3.4's mail protocols: checking a password against Active Directory or another LDAP directory, creating mailboxes from that directory, and accepting OAuth2 bearer tokens. Know this first: 6.3.4 keeps settings in the database, but the [LDAP] section stays in hMailServer.ini on each server, with no REST route and no environment name; the server re-reads it within two seconds of a change, with no restart. The OAuth2 keys are ordinary [Settings] keys, stored like any other.

    Three mechanisms, three questions

    Mechanism The question it answers Control Panel page (Windows)
    LDAP directory authentication Is this the password of the directory user the account is linked to? Access & abuse protection → Directory authentication (LDAP)
    Directory synchronisation Which mailboxes should exist, according to the directory? Access & abuse protection → Directory synchronisation
    OAuth2 bearer tokens Did the identity provider sign this token, and which mailbox does it name? Access & abuse protection → Authentication

    All three are off by default, and an upgrade switches none of them on: the [LDAP] section is absent until you add it, and OAuth2Enabled defaults to 0.

    Where the [LDAP] section lives

    • Windows. The Control Panel's page writes the hMailServer.INI of its own machine. Connected to another machine, its Save is disabled, although "the connection test still works with the values typed here". Work on the server, over Remote Desktop.
    • Linux. No Control Panel and no COM. The file is /etc/hmailserver/hMailServer.ini, 0640 root:hmailserver: sudoedit /etc/hmailserver/hMailServer.ini.
    • Not REST or hmctl. The REST ini routes and hmctl ini set handle [Settings] keys only.
    • Per server. On a warm standby each node has its own [LDAP]; keep the two alike by hand.
    • ServicePassword is a line of that file. It is not among the [Settings] secrets 6.3.4 seals under the stored-secrets key; the Control Panel's field for it is write-only.

    The sign-in keys

    Key Default What it does
    Enabled 0 The master switch
    Server empty The directory host; required
    Port 0 0 means 636 for LDAPS, 389 otherwise
    Security 2 2 LDAPS, 1 StartTLS, 0 plain; anything else is LDAPS
    VerifyCertificate 1 Check the directory's certificate
    AllowUnprotectedPassword 0 Permit a simple bind over a plain connection
    BindMethod 0 0 simple; 1 Negotiate (Kerberos or NTLM through Windows SSPI)
    SearchBase empty Required unless UserDnTemplate is set or the bind is Negotiate
    UserSearchFilter (&(objectCategory=person)(objectClass=user)(sAMAccountName=%u)) How the user's entry is found
    UserDnTemplate empty Bind directly, without searching
    ServiceUsername, ServiceDomain, ServicePassword empty What the search binds with
    TimeoutSeconds 10 Bounds every directory operation; 1 to 90
    FallbackToWindowsLogon 0 Also try Windows LogonUser; Windows only

    LDAPS with VerifyCertificate=1 is the default and the recommended configuration, proven end to end against a Windows Server 2025 domain controller. Server must be the name in the directory's certificate: the same setup pointed at the controller's IP address, with the right password, is refused, and HM5920 reports a failure "unrelated to the credentials supplied". On Windows, put the issuing CA in LocalMachine\Root; when this was proven the service ran as LocalSystem, and a CurrentUser\Root import served it nothing. The bind needs no extra privilege under NT SERVICE\hMailServer, which a new 6.3.4 installation runs as. On Linux the certificate must chain to the system trust store.

    StartTLS (Security=1) connects on 389 and upgrades before anything is sent. Plain LDAP is labelled Unprotected LDAP - cleartext (lab networks only): there a simple bind is refused (HM5921) rather than sent, unless AllowUnprotectedPassword is 1.

    Simple or Negotiate. A simple bind sends the password to the directory, so it belongs on LDAPS or StartTLS. Negotiate, Windows only, never sends it; it worked on 389 against a controller that had no LDAPS certificate yet, and suits a mail server not joined to the domain.

    Search or template. A search binds with the service credentials, finds the user under SearchBase with UserSearchFilter, then binds as the user. It asks for two entries to see ambiguity: more than one match is a configuration fault and authenticates nobody. UserDnTemplate skips the search, so no service credential need be stored at all; with a simple bind, that is the configuration the feature was proven with.

    Linking an account to the directory

    The directory is asked only about linked accounts; others keep their own password. The webmail's sign-in shares the IMAP and POP3 logon, so it takes the directory password too.

    • Control Panel: the account dialog's Windows directory tab, with the domain (ADDomain) and user name (ADUsername); Browse Active Directory searches the forest to fill them. Since 6.3.4 the tab has that name; it was called Directory, and the new Directory entry tab is the organisation's address book, unrelated to Active Directory.
    • REST: PUT /api/v1/accounts/{address} with ad_enabled, ad_domain and ad_username.
    • COM (Windows): IsAD, ADDomain and ADUsername.

    What a link changes. The account's stored password is no fallback: in the proof run, a linked account's local password was refused. Linked accounts bypass local hashing, so they are exempt from the minimum-hash policy and from password expiry, and cannot sign in with SCRAM-SHA-256; their clients use PLAIN or LOGIN over TLS. App passwords work for them: the directory holds the password, but an app password is local, which is what makes a second factor possible for a directory account at all. The person cannot change the password in the webmail (POST /api/v1/me/password answers 409), and Forgotten your password? cannot reset it, because this server does not hold it.

    Without LDAP, on Windows, a linked account is checked by LogonUser, which needs the mail server's host joined to the domain. On a host that is not joined, Windows answers every domain name, real or misspelt, as a wrong password, so the account can never sign in; HM5911 says so, naming the domain and the computer. An empty domain, . or the computer's own name means a local Windows account. Never leave the domain empty by accident: with [LDAP] Enabled off, an account linked with an empty domain and the user name administrator would accept the server's local Administrator password.

    FallbackToWindowsLogon never applies to a wrong password: a directory's rejection deliberately does not fall through to LogonUser, because that would double every failed attempt and trip a lockout policy on a single wrong password. Leave it off on a host that is not domain-joined.

    Setting it up on Windows

    1. For LDAPS or StartTLS, put the directory's issuing CA in LocalMachine\Root on the mail server.
    2. On the mail server, open Directory authentication (LDAP). Switch directory authentication on (Enabled); until then a linked account is checked by Windows LogonUser, not with these settings. Fill in the server, the security, the bind, and either the search or UserDnTemplate; the first card asks Is this configuration complete?
    3. Test the connection with a directory user name, its AD domain and its password. It tries the values typed on the page.
    4. Save. The server re-reads [LDAP] within two seconds.
    5. Link one account and sign in with the right password and a wrong one. Over POP3 the proof run got +OK Mailbox locked and ready and -ERR, and the log showed the bind and the rejection separately.
    6. Link the others by hand, or with directory synchronisation.

    Setting it up on Linux

    The only directory path is an LDAP simple bind over LDAPS or StartTLS, proven against OpenLDAP's slapd.

    1. Make sure the directory's certificate chains to the system trust store, and use the name it carries.
    2. Add the section with sudoedit /etc/hmailserver/hMailServer.ini, filling in the angle brackets. Without a UserSearchFilter line the search uses Active Directory's filter, (&(objectCategory=person)(objectClass=user)(sAMAccountName=%u)), which finds nobody in another directory such as OpenLDAP; there, write a filter that matches its user entries, with %u for the name:
    [LDAP]
    Enabled=1
    Server=<the host name the directory's certificate carries>
    Security=2
    VerifyCertificate=1
    BindMethod=0
    SearchBase=<the DN the users are under>
    UserSearchFilter=<that filter, unless the directory is Active Directory>
    ServiceUsername=<the search account>
    ServicePassword=<its password>
    
    1. Two seconds later the server has it. Link an account over REST (ad_enabled, ad_domain, ad_username) and sign in with the right password and a wrong one.

    Linux has no Negotiate (BindMethod=1 is refused as a directory failure, not a wrong password: HM6420, reported once), no LogonUser (a linked account while [LDAP] Enabled is off is refused with HM6421, which names the settings to set; FallbackToWindowsLogon refuses with HM6404), and no GSSAPI bind.

    When the directory cannot answer

    So that an outage does not read as users mistyping, infrastructure failures are reported apart from credential rejections, throttled per error and endpoint, and TimeoutSeconds bounds the wait.

    Code Meaning
    HM5920 The directory could not be used (a certificate not matching Server, say). Reported at High: unrelated to the credentials, and every account on that directory is affected
    HM5921 A simple bind would have sent the password unprotected, so the logon was refused first
    HM5911 Windows LogonUser: the host is not joined to the domain; at most once a minute per problem

    Directory synchronisation: preview, then apply

    Synchronisation reads the directory, works out which mailboxes should exist, and only when asked creates and updates them; it was verified end to end against a Windows Server 2025 domain controller. The planner writes nothing and the applier decides nothing, so a preview cannot promise what the apply would not do.

    • Per-domain opt-in. A domain takes part only if its Active Directory domain name is set (ADDomainName in the domain dialog, ad_domain_name over REST), so a search base one level too high cannot provision into unrelated hosted domains.
    • Nothing is ever deleted, account or message. The most it does is clear Active, only when asked, and never on a truncated or empty enumeration or for a domain in which nothing was seen.
    • No password. A made account is marked IsAD and signs in through the directory from the start.
    • No existing account claimed. An account that is not linked, or is linked to another AD domain, is reported, not changed.
    • Paged. Active Directory answers a search beyond MaxPageSize (1000) with the first page and a success status, so the enumeration pages; SyncMaxUsers is the ceiling.
    [LDAP] key Default
    SyncFilter (&(objectClass=user)(objectCategory=person)(mail=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
    SyncMailAttribute mail; proxyAddresses works, preferring the capitalised SMTP: primary
    SyncUsernameAttribute, SyncDisplayNameAttribute sAMAccountName, displayName
    SyncMaxUsers 5000; at most 100000
    SyncScheduleMinutes 0, off; 15 at least, 10080 (seven days) at most

    On Windows:

    1. Get authentication working first. Synchronisation binds as the service account, honouring BindMethod, so Negotiate works where there is no LDAPS certificate.
    2. Set the Active Directory domain name on each mail domain that takes part.
    3. Open Directory synchronisation, check the selection keys, choose the domain and whether absent accounts are marked inactive, and run Preview. It changes nothing.
    4. Read the report, including the existing accounts it reports and leaves alone.
    5. Apply. The button stays disabled until a preview of the same options has run.

    Behind the page are Settings.PreviewDirectorySync(DomainName, DisableMissing) and Settings.ApplyDirectorySync, for a server administrator and callable from a script. From a Control Panel on another machine the six selection boxes are read-only and, up to 6.3.6, show their defaults; since 6.3.7 the Control Panel no longer reads its own computer's file for the [LDAP] values, which it cannot read from the server, so it no longer shows them as the server's. Preview and Apply still run inside the server.

    The unattended schedule (SyncScheduleMinutes) runs at start and then every so many minutes. It creates and updates but never disables: unattended is where nobody reads the report that would show a mailbox about to be taken away. The schedule is one of the tasks the server registers as it starts, and only if SyncScheduleMinutes is set then, so restart the service once after first setting it. On Linux, with no COM, there is no preview or apply, and whether the schedule runs there is not documented: treat it as untested.

    OAuth2 bearer tokens on IMAP, POP3 and SMTP

    With OAuth2Enabled=1, SMTP, IMAP and POP3 offer XOAUTH2 and OAUTHBEARER (RFC 7628), by default only over TLS (OAuth2RequireTLS=1). The server verifies the token's signature itself, takes the login name from the claim OAuth2UsernameClaim names (email), and signs in to that mailbox; a user= the client asserts must match. The mailbox must already exist: a token never makes one.

    Key Default What it does
    OAuth2JwksUrl empty The provider's JWK Set; the token's kid picks the key. https, or plain http to loopback only
    OAuth2JwksCacheSeconds 3600 How long the set is kept
    OAuth2PublicKeyFile empty A PEM RSA or EC public key; the fallback
    OAuth2HmacSecret empty The HS256 key
    OAuth2AllowedAlgorithms RS256 HS256, RS256 and ES256 recognised; none never
    OAuth2Issuer, OAuth2Audience empty Expected iss and aud; empty turns the check off
    OAuth2IntrospectionUrl empty RFC 7662 endpoint asked whether a verified token is still active
    OAuth2IntrospectionClientId, OAuth2IntrospectionClientSecret empty Who this server introspects as, over HTTP Basic
    OAuth2IntrospectionCacheSeconds 300 Verdict reuse, never past the token's exp
    OAuth2IntrospectionFailOpen 0 1 accepts a token the endpoint cannot judge, logging each

    An unknown kid, which is what a rotation looks like, fetches the set again at once, at most once a minute; a failed fetch keeps the keys held. HttpProxy, when set, carries these fetches.

    Where to set them. Each applies when saved: on the Control Panel's Authentication page, with PUT /api/v1/settings/ini/{name} and the administrator password, with hmctl ini set, or with COM Settings.SetIniSetting. Not in hMailServer.ini: since 6.3.4 the database holds them, and a line edited in the file is not used; Settings: when a save takes effect explains the store.

    1. Choose the key source: OAuth2JwksUrl, so a rotation needs no copied file; or OAuth2PublicKeyFile; or OAuth2HmacSecret with HS256 allowed.
    2. Set OAuth2Issuer and OAuth2Audience to what the provider writes into its tokens. Left empty, neither is checked: any token the provider's keys signed is accepted, whatever client it was issued to.
    3. Optionally set introspection, so a revoked token stops working before it expires.
    4. Only then set OAuth2Enabled to 1.
    5. On Windows, read the OAuth2 / XOAUTH2 item of the Control Panel's External setup list.

    Why that order. Switched on without usable key material, the mechanisms are still advertised, every token fails, and failed bearer sign-ins feed the auto-ban, so a correct client can have its address banned.

    On Linux before the REST listener is up, hmailserver --set-setting stores a key; a secret is given by name alone and typed at a prompt. A value stored this way reaches the running server at its next save, reinitialise or restart. Set the issuer and audience too, as step 2 says, putting your provider's values in place of these examples:

    sudo hmailserver --set-setting OAuth2JwksUrl=https://idp.example.com/keys
    sudo hmailserver --set-setting OAuth2Issuer=https://idp.example.com
    sudo hmailserver --set-setting OAuth2Audience=hmailserver
    sudo hmailserver --set-setting OAuth2Enabled=1
    sudo systemctl restart hmailserver
    

    Since 6.3.4: a bearer token meets the domain's and the account's country policy, as a password does (a stolen token used to sign in from a refused country); an SMTP session signed in with a token can send more than one message (the second used to be refused with 550 "Login credentials no longer valid"); and OAuth2HmacSecret and OAuth2IntrospectionClientSecret are sealed at rest once the installation has a stored-secrets key, which an upgrade does not make until an administrator runs --create-stored-secrets-key.

    Tested how. The regression fixtures play the identity provider on the loopback, to make it misbehave: RS256 and ES256, the JWK Set and its rotation, introspection and fail-open. A real provider's tokens are not yet part of them.

    This is inbound only. The OutboundOAuth2 keys and FetchOAuth2Hosts are the server's own token for relaying through and collecting from Microsoft 365; Routes, relays and smart hosts covers relaying with it.

    Where these end: SCIM, single sign-on and Kerberos

    The LDAP backend authenticates; SCIM provisions. Synchronisation reads the directory; SCIM is the other direction, Entra ID or Okta calling /scim/v2 under an API key whose role holds provision, and it removes accounts, which synchronisation never does. Single sign-on, OpenID Connect or SAML 2.0, opens the webmail and the Control Deck only, ending in POST /api/v1/session; the protocols' bearer tokens are a separate, older mechanism with the OAuth2 settings above, and single sign-on does not reach mail clients. On Windows, SASL GSSAPI (GssapiEnabled) lets a mail client sign in with a Kerberos ticket, mapped to the account linked to that directory user. SCIM provisioning and single sign-on sets those up; Signing in and Connecting a mail client show the person's side.

    What it does not do

    • No Negotiate, Windows logon or GSSAPI bind on Linux. hMailServer on Linux lists the rest.
    • No LDAP server here, and the webmail's organisation directory is typed in, not imported from Active Directory.

    Where to read more

    • Wiki: Settings Reference sections 9.2 and 11, Capabilities and Configuration, Control Panel Reference, Installing on Linux, Security Hardening, Sign-in and Identity, Architecture
    • Documents: SettingsApply.md, ServiceAccount.md, Scim.md, PasskeysAndSingleSignOn.md, TestLab.md
    • The Roadmap rows on LDAP, which record how each part was proven
    • On this forum: the stored-secrets key, auto-ban and the warm standby
    • The public documentation's Creating accounts from Active Directory and Modern authentication

    Report a security flaw in any of this by SECURITY.md's routes: a confidential GitLab issue, or email to contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as one.

    1 Reply Last reply
    0

    Hello! It looks like you're interested in this conversation, but you don't have an account yet.

    Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

    With your input, this post could be even better 💗

    Register Login
    Reply
    • Reply as topic
    Log in to reply
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes


    hMailServer Community

    The community support forum for hMailServer — the free, open-source mail server for Windows and Linux — maintained by Progressive Robot Ltd. The software is AGPL-3.0 and stays that way; this is where it is discussed, debugged and documented.

    The software

    • Documentation
    • Downloads
    • hMailServer 6.3
    • Source on GitLab

    Getting help

    • Forum rules
    • About this forum
    • Support plans
    • Pay monthly support
    • Talk to Progressive Robot

    Legal

    • Privacy policy
    • Terms and conditions
    • Cookie policy

    Progressive Robot Ltd · 220 Chester Business Park, Heronsway, Chester, Cheshire CH4 9GB, United Kingdom · +44 (0)1244 911212 · [email protected] · VAT 506152326

    Powered by NodeBB

    • Login

    • Don't have an account? Register

    • Login or register to search.
    • First post
      Last post
    0
    • Categories
    • Recent
    • Popular
    • Tags
    • Users
    • Groups
    • Documentation
    • Downloads
    • Report a bug
    • GitHub mirror
    • Paid support