LDAP validation fails when "User must change password on next log on". Any solution?

4

I'm having trouble with a user validation when the "User must change password on next log on" is set.

Here's how I validate the user:

Boolean ValidateUser(String userName, String password)
{
    try
    {
        var userOk = new DirectoryEntry("LDAP://<my LDAP server>", 
                                        userName, 
                                        password, 
                                        AuthenticationTypes.Secure 
                                      | AuthenticationTypes.ServerBind);
        return true;
    }
    catch (COMException ex)
    {
        if (ex.ErrorCode == -2147023570) // 0x8007052E -- Wrong user or password
            return false;
        else
            throw;
    }
}

When the "must change password" is set the COMException is catched as expected, however, the ErrorCode is the same as if the password was wrong.

Does anyone know how to fix this?

I need a return code that tells that the password is correct AND that the user must change the password.

I don't want to implement Kerberos in C# just to check for a damn flag when the user must change the password.

c#
ldap
asked on Stack Overflow Apr 14, 2011 by Paulo Santos

3 Answers

4

After a long search on the Internet, some empirical work with error messages and some spelunking through Win32API, I've came up with a solution that, so far works.

Boolean ValidateUser(String userName, String password)
{
  try
  {
    var user = new DirectoryEntry("LDAP://<my LDAP server>", 
                    userName, 
                    password);
    var obj = user.NativeObject;
    return true;
  }
  catch (DirectoryServicesCOMException ex)
  {
    /*
     * The string " 773," was discovered empirically and it is related to the
     * ERROR_PASSWORD_MUST_CHANGE = 0x773 that is returned by the LogonUser API.
     * 
     * However this error code is not in any value field of the 
     * error message, therefore we need to check for the existence of 
     * the string in the error message.
     */
     if (ex.ExtendedErrorMessage.Contains(" 773,"))
        throw new UserMustChangePasswordException();

     return false;
  }
  catch
  {
     throw;
  }
}
answered on Stack Overflow Apr 14, 2011 by Paulo Santos
2

Unfortunately, using an error message is not a fool-proof way to accomplish verify the reasons an account login was rejected. For this reason, it is important to understand how your LDAP environment manages user accounts. In Microsoft Active Directory, the userAccountControl field is used to handle most account statuses. Here is a list of common userAccountControl bits:

LDAP_UF_ACCOUNT_DISABLE = 2
LDAP_UF_HOMEDIR_REQUIRED = 8
LDAP_UF_LOCKOUT = 16
LDAP_UF_PASSWD_NOTREQD = 32
LDAP_UF_PASSWD_CANT_CHANGE = 64
LDAP_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 128
LDAP_UF_NORMAL_ACCOUNT = 512
LDAP_UF_INTERDOMAIN_TRUST_ACCOUNT = 2048
LDAP_UF_WORKSTATION_TRUST_ACCOUNT = 4096
LDAP_UF_SERVER_TRUST_ACCOUNT = 8192
LDAP_UF_DONT_EXPIRE_PASSWD = 65536
LDAP_UF_MNS_LOGON_ACCOUNT = 131072
LDAP_UF_SMARTCARD_REQUIRED = 262144
LDAP_UF_TRUSTED_FOR_DELEGATION = 524288
LDAP_UF_NOT_DELEGATED = 1048576
LDAP_UF_USE_DES_KEY_ONLY = 2097152
LDAP_UF_DONT_REQUIRE_PREAUTH = 4194304
LDAP_UF_PASSWORD_EXPIRED = 8388608
LDAP_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 16777216
LDAP_UF_NO_AUTH_DATA_REQUIRED = 33554432
LDAP_UF_PARTIAL_SECRETS_ACCOUNT = 67108864

Keep in mind that these are often combined. So, for instance, if a user with a normal account (LDAP_UF_NORMAL_ACCOUNT) is also disabled (LDAP_UF_ACCOUNT_DISABLE), the LDAP field userAccountControl would be set to 514 (Because 512 + 2 = 514)

Now, to answer the original question: When a user account is set for "Password must be changed", AD simply adds LDAP_UF_PASSWORD_EXPIRED to the userAccountControl field. So: Normal Account: LDAP_UF_NORMAL_ACCOUNT + LDAP_UF_PASSWORD_EXPIRED = 8389120

For this specific instance (Password Expired), that is by far the most common value, but it isn't the only one. You need to consider all possible options when evaluating if the account is set for password expired.

Of course, this isn't the easiest way to verify settings, but it is the most reliable way.

answered on Stack Overflow Mar 1, 2018 by Cypheros
1

Thank you Paulo. This works for me. Using this link I've expanded response after exception occur, something like this:

Catch ex As DirectoryServicesCOMException
        Dim msg As String = Nothing
        Select Case True
            Case ex.ExtendedErrorMessage.Contains("773")
                msg = "Error 773. User must change password at next logon is set.​ Please contact support."
            Case ex.ExtendedErrorMessage.Contains("525")
                msg = "User not found"
            Case ex.ExtendedErrorMessage.Contains("52e")
                msg = "Invalid credentials"
            Case ex.ExtendedErrorMessage.Contains("530")
                msg = "Not permitted to logon at this time​"
            Case ex.ExtendedErrorMessage.Contains("531")
                msg = "Not permitted to logon at this workstation​"
            Case ex.ExtendedErrorMessage.Contains("532")
                msg = "Password expired"
            Case ex.ExtendedErrorMessage.Contains("533")
                msg = "Account disabled"
            Case ex.ExtendedErrorMessage.Contains("701")
                msg = "Account expired"
            Case ex.ExtendedErrorMessage.Contains("775")
                msg = "User account is locked"
        End Select
        If msg IsNot Nothing Then
            errorLabel.Text = ex.Message & " " & msg
        Else
            errorLabel.Text = ex.Message
        End If
    End Try
answered on Stack Overflow Sep 4, 2013 by Aleks

User contributions licensed under CC BY-SA 3.0