Simple binds aren't always so simple
In my last posting, I showed how to call the low-level API exposed by WLDAP32.DLL to authenticate via an LDAP bind. The authentication function - ldap_simple_bind_s() - returns a 0 when the credentials supplied were successfully authenticated. I left out what happens when the authentication function returns an error code. It turns out that determining what caused your authentication call to fail can be a bit subtle – at least when the directory you’re binding is Active Directory.
PLEASE NOTE: I’m sharing this information because I don’t want any to have to figure this out by the means I did (quality time with ADSIEdit, a test domain controller loaded in a Virtual PC, and lots of VBScript fragments pulled from around the Internet.)
First, here’s what can go wrong when calling ldap_simple_bind_s() using a connection to an Active Directory LDAP server:
All the errors listed on http://windowssdk.msdn.microsoft.com/library/default.asp?url=/library/en-us/ldap/ldap/return_values.asp (other than LDAP_SUCCESS)
locked out account
user must change password at next login (and thus can’t just bind and continue)
password expired (as set by a policy applied to that user object)
probably something else that I’ve missed
And here’s how to tell them apart:
Any error other than LDAP_INVALID_CREDENTIALS implies that it’s not any of the other cases I’ve listed
If ldap_simple_bind_s() returns LDAP_INVALID_CREDENTIALS
Test for the disabled account condition by checking if the userAccountControl attribute for that user has ACCOUNTDISABLE (0x0000002) bit set. If it’s set, the account is disabled.
Test for the expired account condition by checking if the accountExpires attribute has an invalid value – this is a little bit tricky, as it’s a 64 bit integer field that may or may not be present (because account may or may not have an expiration date.) If it’s present, you’ll need to convert it to a date/time value and compare it to the current system date/time. It gets even more subtle than that, but that’s the subject of a future posting…
Test for the locked out account condition by checking the lockoutTime attribute. If it comes back with a non-zero value, the account is locked out.
Test for the must change password condition by checking the pwdLastSet attribute. If it comes back with a 0 value, the password must be reset at next login. There’s a flag in the userAccountControl attribute that looks like it corresponds to this condition, but in my experience, I’ve found that this is the only reliable way to tell.
Test for the expired password condition by checking the pwdLastSet attribute against domain policy. This also involves date arithmetic with 64 bit integer attributes, which I’ll cover in a future posting.
If all those tests fail, you can safely presume that you’ve been given a bad password.
As you’ve probably surmised, I had write an application that had a critical need to tell the difference between all this conditions and was surprised at how hard it was to do so. Hopefully, this will help someone else charged with the same task (or hopefully, no one else will ever have to do this.) At any rate, that’s how to tell why your LDAP bind to Active Directory didn’t work.
Somewhere, the system administrators of the world are laughing at me. J