LDAP with ver0.16.10

Your question

Hello all,

I’m trying to use Active Directory as LDAP backend for a single domain using the Community edition, version 0.16.10, installed using this method: Linux / MacOS | Stalwart

I keep bouncing my head against the wall and it is not the 1st time I did AD/LDAP backend integration with various applications. Most of the search results refer to the old config.toml which are not applicable in the newer version post 0.16 AFAIK.

The initial install wizzard was followed using the defaults and only the LDAP section was customized.

I tried to map the settings described here in the old format: Active Directory Integration · stalwartlabs/stalwart · Discussion #374 · GitHub and others similar. No luck.

Also, in the logs (cli of the web) there is nothing related to any of the login failures. I’m practically blind.

Did anyone manage to configure this ? I’m sure the issue relies on the mapping of the attributes to the correct ones in AD and the LDAP filters.

My domain controllers are Win 2016 and other opensource apps can login against it via the same LDAP(s) method.

Many thanks in advance

Let me read the full ldap.md doc directly for a complete picture of config and troubleshooting.

For Active Directory you need to override the LDAP defaults, since Stalwart’s built-in filters/attributes target OpenLDAP schemas (inetOrgPerson, groupOfNames, pwdChangeTime), which don’t exist in AD. Configure the LDAP directory (Settings › Authentication › Directories, @type: "Ldap") roughly like this:

{
  "@type": "Ldap",
  "description": "Active Directory",
  "url": "ldaps://dc1.example.org:636",
  "useTls": true,
  "baseDn": "dc=example,dc=org",
  "bindDn": "cn=svc-stalwart,cn=Users,dc=example,dc=org",
  "bindSecret": { "@type": "Value", "secret": "..." },
  "bindAuthentication": true,
  "filterLogin": "(&(objectClass=user)(objectCategory=person)(|(userPrincipalName=?)(sAMAccountName=?)(mail=?)))",
  "filterMailbox": "(&(objectClass=user)(objectCategory=person)(|(mail=?)(proxyAddresses=smtp:?)))",
  "attrMemberOf": ["memberOf"],
  "attrEmail": ["mail"],
  "attrEmailAlias": ["proxyAddresses"],
  "attrSecretChanged": ["pwdLastSet"],
  "groupClass": "group"
}

Key points for AD:

  • Set bindAuthentication: true (the default). AD normally will not return password hashes, so hash comparison (attrSecret) will not work; the server must bind directly as the user via filterLogin.
  • attrSecretChanged should be pwdLastSet (AD’s equivalent of pwdChangeTime) so OAuth tokens get invalidated on password change.
  • groupClass should be "group" (AD group object class), not groupOfNames.
  • Your filterLogin/filterMailbox must reference AD attributes: userPrincipalName, sAMAccountName, mail, proxyAddresses, not inetOrgPerson/mailAlias.
  • Use ldaps:// with useTls: true (AD often rejects StartTLS or plaintext binds for user auth), and make sure the service account (bindDn/bindSecret) has read rights over the base DN.

To actually see what’s failing, raise log verbosity: the default level won’t show auth.failed events (they’re debug). Set the tracer level to debug or trace under Settings › Logging so failed bind/search attempts show up.

Docs used: LDAP Server, Directory object reference, Troubleshooting authentication.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

I understand this is an automated response. Can you please elaborate on how to insert the json formatted configuration you provided into my Stalwart Web admin page ?

In v0.16 the LDAP config lives in the web UI Directory object (Settings > Authentication > Directories), not config.toml, so the old TOML examples map field-by-field onto that form rather than being pasted in. Second, Active Directory doesn’t hand back password hashes, so use bind authentication (the default), where Stalwart binds as the user to verify the password.

For AD specifically: object class is user (not inetOrgPerson) and groups are group (not groupOfNames). A working starting point is a login filter like (&(objectClass=user)(|(sAMAccountName=?)(userPrincipalName=?)(mail=?))), a mailbox filter like (&(objectClass=user)(|(mail=?)(proxyAddresses=?))), primary email attribute mail, alias attribute proxyAddresses, member-of attribute memberOf, group object class group, and a service-account bind DN plus your domain base DN. To see why a login fails, raise the log level to trace and watch for store.ldap-query events; that shows the exact filter and the attributes AD returns. See: LDAP Server | Stalwart.

Did you have any success connecting to AD? I’ve been struggling with this for hours and not even seeing a single LDAP log entry when the log level is set to TRACE. I definitely have the General>Authentication>Directory set to the AD Directory, and if I try to create a user account it warns me that it can’t create them in external directories, so I know it at least “thinks” it should be using AD…

There seem to be several comments out there about the lack of log entries when there’s not a successful connection, however I’m connecting with the suggested settings (except I’m not using TLS, just unencrypted on port 389…) Still, I’m able to connect to AD with the same settings using an LDAP browser utility.

I’ve just installed Stalwart 0.16.16 and it’s a 2022 server that I’m tying to connect to.

Thanks-

UPDATE: I’m seeing log entries now that I’m attempting to login to the Web GUI. (I was attempting to connect via Thunderbird IMAP.) Not successful yet, but now that I can see some log entries I at least stand a chance of figuring out my mistakes! :slight_smile:

Yes, I’ve solved it. I’ll dig my notes. I even managed to have it configured with stalwart-cli. Let me come back to you

@TTx, the missing log entries are a clue. The directory is only consulted after the domain part of the login name resolves to a Domain object that exists in Stalwart, and that check runs first and aborts the attempt on its own. So no LDAP query is ever issued, and no store.ldap-query event can appear. That’s also why the web GUI produced entries and Thunderbird didn’t: you almost certainly logged into the GUI with a different form of the username.

Filter your trace log on auth.failed and auth.warning rather than on store.ldap-query, and look for Reason = "Domain not found" or Reason = "No domain in username". My guess is that Thunderbird is sending a bare username, or DOMAIN\user, or the AD UPN suffix rather than your mail domain. The fix is to make sure the domain exists in Stalwart and to have the client authenticate as the full user@domain that matches it.

Thanks for the response - I was able to successfully login just before the weekend and hadn’t replied yet. You’re spot-on about it being related to the domain, but in more than one way.

I expect when I was trying to login from Thunderbird, it may have been leaving the domain part out (as you say). When logging into the Stalwart web UI, my issue ended up being that I have several email aliases, and one of them was for a domain that I hadn’t added to Stalwart yet.

Thanks!

Hello,

Sorry for the late response. Here’s what I got for LDAP:

Type: LDAP Directory

Directory
  Description: MyDomain

Connection
  Server URL:                 ldaps://domain.tld:636
  Connection Timeout:         30 s
  Enable TLS:                 Yes
  Allow Invalid Certificates: Yes

Bind
  Base DN:                 DC=domain,DC=tld
  Bind DN:                 CN=LdapService,OU=serviceAccounts,OU=Mail,OU=Applications,OU=xxx,DC=domain,DC=tld
  Bind Secret:
    Type: Secret value
    Secret
      Secret: ****
  Use Bind Authentication: Yes

Filters
  Login Filter:     (&(objectClass=user)(memberOf=CN=someDomainNAme,OU=userDomains,OU=Mail,OU=Applications,OU=xxx,DC=domain,DC=tld)(!(objectClass=computer))(|(userPrincipalName=?)(mail=?)))
  Mailbox Filter:   (&(objectClass=user)(|(userPrincipalName=?)(mail=?)(proxyAddresses=smtp:?)(proxyAddresses=SMTP:?)))
  Member Of Filter: <none>

Attributes
  Account Type Attribute:     userPrincipalName
  Description Attribute:      description
  Primary E-mail Attribute:   mail
  E-mail Alias Attribute:     proxyAddresses
  Member Of Attribute:        memberOf
  Password Attribute:         userPassword
  Password Changed Attribute: pwdChangeTime
  Group Object Class:         group

Pool
  Max Connections: 10
  Create Timeout:  30 s
  Recycle Timeout: 30 s
  Wait Timeout:    30 s
  Tenant:          <none>


This allows me to login using the [email protected] format.

Thanks for posting your config, @Mircevski - I’ll compare mine and if I missed anything else. :slight_smile: