Allow users to reset their Active Directory passwords via a web form.

September 23rd, 2006

This is the functioning post that allowed us to be able to reset user’s Active Directory passwords via a php page hosted on a linux box. Combine this with the password resetting delegation of a previous post for a bit more security. We’re also going to set this up to figure out what group someone is in and let teachers reset thier students passwords.

Before you do this you need to generate an ssl certificate on the windows box and import it into the linux box, see a previous post.

This information was mostly gleaned from here:
———————————————————
The post: http://forums.devshed.com/ldap-programming-76/modifying-active-directory-passwords-through-php-and-iis-74683-7.html
———————————————————

Here is the final script I am using for a user to change his or her password.
The form to submit to the script:
<form method="post" action="change_password.php">
username: <input type="text" name="uid" />
<br />
password: <input type="password" name="password" />
<br />
new password: <input type="password" name="newpass1" />
<br />
confirm new password: <input type="password" name="newpass2" />
<br />
<input type="submit" name="submit" value="Change Password" />
</form>

Config File:
< ?PHP
/*** Variable Settings ***/
// administrative bind user
// Admin account with permission to reset passwords
$adminUID = 'adminusername';
$adminPass = 'AdministrativePassword';
// ldap server info, moved to config file
$ldapserver = 'ldapserver.mydomain.com';
$baseDN = 'DC=mydomain,DC=com';
?>

Script:
< ?PHP
require_once('/var/www_config_files/secure/change_password.inc.php');
/*** Variable Settings ***/
$uid = $_POST['uid']; // Should be something like jsmith
$userbindDN = $uid . '@yourdomain.com'; // jsmith@yourdomain.com
//existing password
$userbindPass = $_POST['password'];
// new password
$passwd1 = $_POST['newpass1'];
$passwd2 = $_POST['newpass2'];
// administrative bind user
// Admin account with permission to reset passwords
$authbindDN = $adminUID . '@yourdomain.com';
$authbindPass = $adminPass;

// ldap server info, moved to config file
//$ldapserver = 'ldapserver.yourdomain.com';
//$baseDN = 'DC=mydomain,DC=com';
/**************************/

/************* Main Script Code ***************/
/** Connect SSL to Ldap Server **/

$ldap = ldap_connect('ldaps://'.$ldapserver,686);
//$ldap = ldap_connect($ldapserver);
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);

echo "Verifying old password ...<br>";

ldap_bind($ldap, $userbindDN, $userbindPass);

if (ldap_errno($ldap) !== 0)
{
exit('ERROR: Username or Password Invalid - Please Try again');
// exit('ERROR: User ID/Password Invalid - '.ldap_error($ldap));
}
/** We got this far, let's bind with an admin user **/
echo "Authenticated, changing password ...<br />";

ldap_bind($ldap, $authbindDN, $authbindPass);
if (ldap_errno($ldap) !== 0)
{
exit('ERROR: Unable to bind with admin user info - '.ldap_error
($ldap));
}

// Searching for the user
$filter = "(|(samaccountname=$uid))";
$justthese = array("cn");

$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);
// no matching records
$info = ldap_get_entries($ldap, $searchResults);
if ($searchResults === false)
{
exit('User ($uid) not found.');
}
if (!is_resource($searchResults))
{
exit('Error in search results.');
}

//echo "<pre>";
//print_r($info);
//echo "</pre>";

$entry = ldap_first_entry($ldap, $searchResults);
if (!is_resource($entry))
{
exit('Couldn\'t get entry');
}
$userDn = ldap_get_dn($ldap, $entry);

if ($passwd1 == $passwd2){
// prepare data
$newPassword = $passwd1;
$newPassword = "\"" . $newPassword . "\"";
$len = strlen($newPassword);
for($i = 0; $i < $len; $i++)
{
$newPassw .= "{$newPassword{$i}}\000";
}
$newPassword = $newPassw;
$userdata['unicodePwd'] = $newPassword;

// echo "Changing Password<br /><br />";
echo "Username = ".$uid."<br />";
// echo "User login ID = ".$userbindDN."<br />";

$result = ldap_mod_replace($ldap, $userDn , $userdata);
if($result)
{
// echo "User modified!<br />" ;
}else{
echo "There was a problem!<br />";
echo ldap_error($ldap)."<br />";
}
/** Now try to bind with the username and new password to
ensure change**/
echo "Now testing new password to insure change<br />";
ldap_bind($ldap, $userbindDN, $passwd1);
if (ldap_errno($ldap) !== 0)
{
exit('ERROR: User ID/Password Invalid - '.ldap_error($ldap));
}else{
echo "Password Verified.<br />Password change complete.<br />";
echo "<p>You may now close this window, your password for
your computer, email, and web access has been updated.</p>";
}
}
?>

In addition I have modified this script to allow teachers to reset student passwords:
The page that requests information:
<html>
<head>
<title>Reset Student Password</title>
</head>
<body>
<h2>Reset a Student Password</h2>
<p>Teachers may use this form to reset the password of a student in their class. Simply fill out all of the fields and click the "Reset Student Password" button.</p>
<form method="post" action="change_password.php">
Teacher Username: <input type="text" name="uid" />
<br />
Teacher Password: <input type="password" name="password" />
<br />
Student Username: <input type="text" name="student_uid" />
<br />
New Student Password: <input type="password" name="newpass1" />
<br />
Confirm Student Password: <input type="password" name="newpass2" />
<br />
<input type="submit" name="submit" value="Reset Student Password" />
</form>
</body>
</html>

Uses the same config file from above.

The script:
< ?PHP
require_once('/var/www_config_files/secure/change_password.inc.php');
/*** Variable Settings ***/
$uid = $_POST['uid']; // Should be something like jsmith
$student_uid = $_POST['student_uid'];
$userbindDN = $uid . '@yourdomain.com'; // jsmith@yourdomain.com
//existing password
$userbindPass = $_POST['password'];
// new password
$passwd1 = $_POST['newpass1'];
$passwd2 = $_POST['newpass2'];
// administrative bind user
// Admin account with permission to reset passwords
$authbindDN = $adminUID . '@yourdomain.com';
$authbindPass = $adminPass;
// ldap server info, moved to config file
//$ldapserver = 'ldapserver.yourdomain.com';
//$baseDN = 'DC=yourdomain,DC=com';
/**************************/
/*
The theory: Bind as the teacher to verify password and get group membership
Bind as the admin
Grab student dn and group membership
if the teacher is a member of teachers and the student is a member of students
check to see that they are both a member of another group (other than Domain Users)
If they are: reset the student password

*/
/************* Main Script Code ***************/
/** Connect SSL to LDAP Server **/
?>
<html>
<head>
<title>Reset Student Password Results</title>
</head>
<body>
< ?
//echo "Connecting SSL to server<br>";
$ldap = ldap_connect('ldaps://'.$ldapserver,686);
//$ldap = ldap_connect($ldapserver);
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
/** Now try to bind with the username and password **/
echo "Verifying teacher credentials ...<br />";
ldap_bind($ldap, $userbindDN, $userbindPass);
if (ldap_errno($ldap) !== 0)
{
exit('ERROR: User Teacher username or password invalid');
//exit('ERROR: User ID/Password Invalid - '.ldap_error($ldap));
}
echo "Teacher credentials verified. <br />";

/** We got this far, let's bind with an admin user **/
ldap_bind($ldap, $authbindDN, $authbindPass);
if (ldap_errno($ldap) !== 0)
{
exit('ERROR: Unable to bind with admin user info - '.ldap_error
($ldap));
}

// Figure out who the teacher is so we can get their membership info
$filter = "(&(samaccountname=".$uid."))";
$justthese = array("dn");

$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);
// no matching records
$info = ldap_get_entries($ldap, $searchResults);
if ($searchResults === false)
{
exit('User ($uid) not found in AD');
}
if (!is_resource($searchResults))
{
exit('Error in search results.');
}

//echo "<pre>";
//print_r($info);
for ($i=0; $i < $info["count"]; $i++) {
//print "LDAP DN: " . $info[$i]['dn'];
//echo "<br /><br />";
$userDN = $info[$i]['dn'];
}
//echo "</pre>";

$filter = "(&(samaccountname=".$student_uid."))";
$justthese = array("dn");

$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);
// no matching records
$info = ldap_get_entries($ldap, $searchResults);
if ($searchResults === false)
{
exit('User ($uid) not found in AD');
}
if (!is_resource($searchResults))
{
exit('Error in search results.');
}

for ($i=0; $i < $info["count"]; $i++) {
//print "LDAP DN: " . $info[$i]['dn'];
//echo "<br /><br />";
$student_userDN = $info[$i]['dn'];
}

// Searching for the teacher
$filter = "(&(objectCategory=group)(member=".$userDN."))";
$justthese = array("dn");

$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);
// no matching records
$info = ldap_get_entries($ldap, $searchResults);
if ($searchResults === false)
{
exit('User ($uid) not found in AD');
}
if (!is_resource($searchResults))
{
exit('Error in search results.');
}

//echo "<pre>";
for ($i=0; $i < $info["count"]; $i++) {
$teacher_groups[] = $info[$i]['dn'];
}
//print_r($teacher_groups);
//echo "</pre>";

// Searching for the student
$filter = "(&(objectCategory=group)(member=".$student_userDN."))";
$justthese = array("dn");

$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);
// no matching records
$info = ldap_get_entries($ldap, $searchResults);
if ($searchResults === false)
{
exit('User ($student_uid) not found in AD');
}
if (!is_resource($searchResults))
{
exit('Error in search results.');
}

//echo "</pre><pre>";
for ($i=0; $i < $info["count"]; $i++) {
$student_groups[] = $info[$i]['dn'];
}
//print_r($student_groups);
//echo "</pre>";

foreach ($teacher_groups as $teacher_group) {
$is_teacher = stristr($teacher_group, 'Teacher');
}
//echo "$is_teacher <br />";
if($is_teacher !== 'FALSE') {
echo "Verifying group membership...";
// In this case the teacher must be a member of the same group as the student
// This was added to allow fifth grade teachers to reset fifth grades student
// passwords, but not sixth grade student passwords.
foreach ($student_groups as $student_group) {
$same_group = array_search($student_group, $teacher_groups);
//echo $same_group;

}
if($same_group !== 'FALSE') {
echo "Group membership verified";
echo "<br />";
echo "Resetting student password...";

//---------------------------------------------//
// Check Passwords to make sure they match.
if ($passwd1 == $passwd2){
// prepare data
$newPassword = $passwd1;
$newPassword = "\"" . $newPassword . "\"";
$len = strlen($newPassword);
for($i = 0; $i < $len; $i++) {
$newPassw .= "{$newPassword{$i}}\000";
}
$newPassword = $newPassw;
$userdata['unicodePwd'] = $newPassword;

echo "for username = ".$student_uid."<br />";

$result = ldap_mod_replace($ldap, $student_userDN, $userdata);
if($result)
{
echo "User modified!<br />" ;
}else{
echo "There was a problem!<br />";
echo ldap_error($ldap)."<br />";
}
/** Now try to bind with the username and new password to
insure change**/
echo "Now testing new password to ensure change<br />";
ldap_bind($ldap, $student_userDN, $passwd1);
if (ldap_errno($ldap) !== 0)
{
exit('ERROR: User ID/Password Invalid - '.ldap_error($ldap));
}else{
echo "Password Verified.<br />Password change complete.<br />";
echo "<p>You may now close this window, the student password has been modified.</p><p>";
}
}

//--------------------------------------------------//

}
}
else {
echo "Sorry we could not verity that you are a teacher.";
}
?>
</p></pre></body>
</html>