GavinC
GavinC

Reputation: 53

PHP LDAP Check for Password_Expire and Account_Lock

I was wondering if someone will be able to help with PHP LDAP Active Directory.

Currently using a PHP LDAP class created by Scott Barnett & Richard Hyland (http://adldap.sourceforge.net/) version 4.0.4.

Now logging in is no problem, and if a user has entered their password in incorrectly too many times, their account is locked as set by Active Directory.

The problem I am having is now trying to determine when a user has locked their account and when their password has expired after 30 days, each scenario needs to redirect the user to a different page as it will be for external users.

Now when a user logs in normally the following code is executed:

include (dirname(__FILE__) . "./adLDAP/src/adLDAP.php");
try 
{
   $adldap = new adLDAP();
}
catch (adLDAPException $e) 
{
   echo $e;
   exit();
}
if ($adldap->authenticate($username, $password))
{
   //establish your session and redirect
   session_start();
   $_SESSION["username"] = $username;
   $_SESSION["userinfo"] = $adldap->user()->info($username);
   $redir = "Location: https://" . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . "user_page.php";
   header($redir);
   exit;
}

This code links to adLDAP.php, creates a connection to AD and then Authenticates the user and logs them in.

Now within the adLDAPUsers.php Class, there is a public function "passwordExpiry($username, $isGUID = false)" that is supposed to be used to determine if a user's password has expired, but I am struggling to get this function to work, as when it executes the following line:

if (!$this->adldap->getLdapBind()) { return false; }

It always returns FALSE. It does not allow a connection to AD with the user's correct credentials, and the rest of the code is never executed to determine password expiry.

From my main login page I have the following code:

if (!$adldap->checkAccPassExp($username))
{
   $redir = "Location: https://" . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . "changepassword.php";
   header($redir);
   exit;
}

It then links to adLDAP.php:

public function checkAccPassExp($username, $password)
{
    $PassExpire = $this->user()->passwordExpiry($username, $isGUID);
}

Which in turn then links to adLDAPUsers.php Class and executes the code.

As for determining if a user's account has been locked, there isn't really an indepth function determine that, just a protected function within the class:

protected function accountControl($options)
{
$val=0;
if (is_array($options)) {
if (in_array("SCRIPT",$options)){ $val=$val+1; }
if (in_array("ACCOUNTDISABLE",$options)){ $val=$val+2; }
if (in_array("HOMEDIR_REQUIRED",$options)){ $val=$val+8; }
if (in_array("LOCKOUT",$options)){ $val=$val+16; }
if (in_array("PASSWD_NOTREQD",$options)){ $val=$val+32; }
//PASSWD_CANT_CHANGE Note You cannot assign this permission by directly modifying the UserAccountControl attribute.
//For information about how to set the permission programmatically, see the "Property flag descriptions" section.
if (in_array("ENCRYPTED_TEXT_PWD_ALLOWED",$options)){ $val=$val+128; }
if (in_array("TEMP_DUPLICATE_ACCOUNT",$options)){ $val=$val+256; }
if (in_array("NORMAL_ACCOUNT",$options)){ $val=$val+512; }
if (in_array("INTERDOMAIN_TRUST_ACCOUNT",$options)){ $val=$val+2048; }
if (in_array("WORKSTATION_TRUST_ACCOUNT",$options)){ $val=$val+4096; }
if (in_array("SERVER_TRUST_ACCOUNT",$options)){ $val=$val+8192; }
if (in_array("DONT_EXPIRE_PASSWORD",$options)){ $val=$val+65536; }
if (in_array("MNS_LOGON_ACCOUNT",$options)){ $val=$val+131072; }
if (in_array("SMARTCARD_REQUIRED",$options)){ $val=$val+262144; }
if (in_array("TRUSTED_FOR_DELEGATION",$options)){ $val=$val+524288; }
if (in_array("NOT_DELEGATED",$options)){ $val=$val+1048576; }
if (in_array("USE_DES_KEY_ONLY",$options)){ $val=$val+2097152; }
if (in_array("DONT_REQ_PREAUTH",$options)){ $val=$val+4194304; } 
if (in_array("PASSWORD_EXPIRED",$options)){ $val=$val+8388608; }
if (in_array("TRUSTED_TO_AUTH_FOR_DELEGATION",$options)){val=$val+16777216; }
}
return $val;
}

Now this code I had to copy from the class to the adLDAP.php file and call like this as it is a protected function and would not allow me to call it any other way:

public function checkAccLocked()
{
   $options = array("LOCKOUT");
   $userlocked[0] = $this->accountControl($options);
   if ($userlocked[0] == 16)
   {
       $ret = false;
   }
   else
   {
       $ret = true;
   }
   return $ret;
}

Now I have determined, if Anything is wrong with a user's account, you can send any value through to $options and it will return the corresponding value i.e. my user's password could be expired, but I can call any variable from the function and it will return a value, instead of just the password_expire value.

I have found on the LDAPWiki (https://ldapwiki.com/wiki/Common%20Active%20Directory%20Bind%20Errors) that LDAP code 49 returns quite a lot of values, so the LDAP code is useless to me. I was hoping that maybe the code would allow me to retrieve the error number and then I can point the user in the correct direction, but I would need the Decimal Code:

Code - 1330 for ERROR_PASSWORD_EXPIRED or Code - 1907 for ERROR_PASSWORD_MUST_CHANGE and Code - 1909 for ERROR_ACCOUNT_LOCKED_OUT

Has anyone ever used this code before? Could someone please provide assistance as to what code I am to use where and how to get this working? If not, is there maybe some other code that I need to use?

Upvotes: 1

Views: 3828

Answers (2)

ChadSikorra
ChadSikorra

Reputation: 2869

You could get the extended error code you're looking for after a failed authentication/bind by using a function like this:

function getExtendedErrorNumber($adLdap)
{
    $errorNumber = 0;

    ldap_get_option($adLdap->getLdapConnection(), LDAP_OPT_ERROR_STRING, $extendedError);
    if (!empty($extendedError) && preg_match('/, data (\d+),?/', $extendedError, $matches)) {
        $errorNumber = hexdec(intval($matches[1]));
    }

    return $errorNumber;
}

Use it like:

const PWD_EXPIRED = 1330;
const PWD_MUST_CHANGE = 1907;
const ACCOUNT_LOCKED = 1909;
const ACCOUNT_DISABLED = 1331;

$errorNumber = getExtendedErrorNumber($adldap);


// The account password has expired 
if ($errorNumber === PWD_EXPIRED) {

// The user must change their password before they can login
} elseif ($errorNumber === PWD_MUST_CHANGE) {

// The user's account is locked
} elseif ($errorNumber === ACCOUNT_LOCKED) {

// The user's account is disabled
} elseif ($errorNumber === ACCOUNT_DISABLED) {

} 

Side note: The adLdap library you're using is no longer maintained. I'd suggest upgrading to AdLdap2 or LdapTools.

Upvotes: 2

jwilleke
jwilleke

Reputation: 11026

You have two conditions that MUST be determined from at least two separate methods. Password_Expired is a condition where the Password has expired. For Microsoft Active Directory we have a normative description.

Account_lock is where the Account has been either Administratively locked or the account has be locked by Intruder Detection.

Upvotes: 0

Related Questions