Das Problem
Szenario: Du migrierst den LDAP-Bind-User für grommunio auf einen neuen Service-Account. Der Account ist frisch erstellt, das Passwort ist gesetzt, alles sieht gut aus.
Testlauf mit ldapwhoami:
ldapwhoami -H ldap://dc-obiwan.darkside.lan:7389 \
-D "uid=grommunio-ldap,cn=users,dc=darkside,dc=lan" \
-w 'MeinSicheresP@ssw0rt!'
Ausgabe:
dn:uid=grommunio-ldap,cn=users,dc=darkside,dc=lan
✅ Funktioniert. Also schnell in grommunio eingetragen und gecheckt:
grommunio-admin ldap check
Ausgabe:
invalidCredentials
Nanu. Gleiche Credentials, gleicher Server, gleicher Port. Nur ein anderer Client.
Die Diagnose
Erster Verdacht: falsche Config-Datei (dazu mehr in einem anderen Post). Nach Verifikation der korrekten Config (/etc/gromox/ldap_adaptor.cfg) bleibt das Problem.
Python-Test direkt auf dem Mail-Server:
import ldap3
srv = ldap3.Server('ldap://dc-obiwan.darkside.lan:7389', get_info=ldap3.NONE)
conn = ldap3.Connection(srv,
user='uid=grommunio-ldap,cn=users,dc=darkside,dc=lan',
password='MeinSicheresP@ssw0rt!',
authentication=ldap3.SIMPLE,
raise_exceptions=False)
print(conn.bind()) # False
print(conn.result) # {'description': 'invalidCredentials', ...}
Zum Vergleich — Admin-Account funktioniert:
conn = ldap3.Connection(srv,
user='cn=admin,dc=darkside,dc=lan',
password='AdminPasswortOhneSonderzeichen',
authentication=ldap3.SIMPLE,
raise_exceptions=False)
print(conn.bind()) # True ✅
Das grenzt es ein: Python ldap3 selbst läuft, die Verbindung steht — aber spezifisch für diesen Account schlägt der Bind fehl.
Die Ursache
Das Passwort des Service-Accounts enthielt @ und !.
ldapwhoami (und ldapsearch) nutzen libldap — die C-Bibliothek von OpenLDAP. Sie überträgt das Passwort als rohe Bytes direkt in den LDAP Simple Bind Request.
Python ldap3 ist eine pure-Python-Implementierung ohne C-Abhängigkeiten. Sie serialisiert die Credentials selbst in ASN.1/BER. Bei bestimmten Sonderzeichen — konkret @ und ! — kommt es dabei zu einem Encoding-Unterschied: Das Passwort kommt auf dem Server anders an als erwartet, und der gespeicherte Hash stimmt nicht mehr überein.
Das ist kein UCS-Bug und kein grommunio-Bug. Es ist ein Verhalten von ldap3 das sich von libldap unterscheidet.
Betroffene Zeichen: @, ! (möglicherweise weitere Sonderzeichen je nach ldap3-Version)
Nicht betroffen: -, _, Ziffern, normale Buchstaben, #, $ (nach eigenen Tests)
Die Lösung
Passwort des LDAP-Service-Accounts auf eines ohne die problematischen Sonderzeichen setzen.
Auf dem UCS-DC:
# Auf dc-obiwan
udm users/ldap modify \
--dn 'uid=grommunio-ldap,cn=users,dc=darkside,dc=lan' \
--set password='MeinSicheresPasswort-2026'
In der grommunio-Config aktualisieren (/etc/gromox/ldap_adaptor.cfg):
ldap_bind_pass=MeinSicheresPasswort-2026
Dienste neu starten:
systemctl restart gromox-http gromox-zcore
Verifikation
# Python ldap3 Test
python3 -c "
import ldap3
srv = ldap3.Server('ldap://dc-obiwan.darkside.lan:7389', get_info=ldap3.NONE)
conn = ldap3.Connection(srv,
user='uid=grommunio-ldap,cn=users,dc=darkside,dc=lan',
password='MeinSicheresPasswort-2026',
authentication=ldap3.SIMPLE,
raise_exceptions=False)
print('OK' if conn.bind() else 'FEHLER', conn.result)
"
# grommunio LDAP Check
grommunio-admin ldap check
Erwartete Ausgabe:
OK {'result': 0, 'description': 'success', ...}
Checking 16 users...
Everything is ok
Lessons Learned
1. ldap3 ≠ libldap
Wenn du mit ldapsearch oder ldapwhoami testest, testest du libldap. Wenn deine Applikation Python ldap3 nutzt, sind das zwei verschiedene Dinge. Ein erfolgreicher ldapwhoami sagt nichts darüber aus, ob ldap3 denselben Bind hinbekommt.
2. Passwörter für LDAP-Service-Accounts
Für Service-Accounts die von Applikationen genutzt werden (kein interaktiver Login), reicht ein langer alphanumerischer String mit - und _. Die üblichen Sonderzeichen-Anforderungen existieren um Dictionary-Attacks zu erschweren — bei einem 32-Zeichen-Zufallspasswort ohne @! ist das kein Thema.
3. Schnell-Diagnose bei invalidCredentials
# Schritt 1: libldap testen
ldapwhoami -H ldap://<DC>:7389 -D "<DN>" -w '<PASSWORT>'
# Schritt 2: ldap3 testen (falls Schritt 1 OK)
python3 -c "
import ldap3
srv = ldap3.Server('ldap://<DC>:7389', get_info=ldap3.NONE)
conn = ldap3.Connection(srv, user='<DN>', password='<PASSWORT>',
authentication=ldap3.SIMPLE, raise_exceptions=False)
print(conn.bind(), conn.result)
"
# Wenn Schritt 1 OK, Schritt 2 FEHLER → ldap3/Encoding-Problem
# Passwort ohne @ und ! neu setzen