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>

Delegate Password Resetting Control in Active Directory

September 23rd, 2006

This allows you to have a user who can reset passwords for other users, but not necessarily administer the domain. We are using it so people can reset their passwords from a PHP page and so teachers will be able to reset their students’ passwords.
See:http://support.microsoft.com/default.aspx?scid=KB;en-us;296999

  1. Create a group that you want to be able to reset passwords
  2. Add the users you want to give the password resetting ability to to that group.
  3. Right Click on an OU that you want the resetters to be able to reset and choose “Delegate Control”
  4. Click Next and then add the Group you just created to the empty box.
  5. Click Next and then Check the Reset Password Box.
  6. Click Finish

Network Printers Assigned by Computer via a script run by a GPO

September 23rd, 2006

To assign a network printer via GPO you can either assign them to the user or to a computer. Scripting Printer installation for a user is fairly simple and straight forward.

  1. Create a script to map the appropriate printer:
    Option Explicit
    Dim objNetwork

    Set ObjNetwork=CreateObject("Wscript.Network")
    objNetwork.addWindowsPrinterConnection"\\serveripaddress\printername"
    Wscript.Quit

  2. In windows explorer copy the file. Open GPMC.
  3. Create a GPO for the appropriate user OU. Edit the settings and navigate to:
    User Configuration->Windows Settings->Scripts->Logon
  4. Add a script, just type the name of the file, not the path.
  5. Click the Show Files button and Paste your script into the Folder that opens.

If you login as a user within this OU you should now see the printer is available to you.

To assign a printer by computer, such as in a lab situation where anyone who logs in should have access to the printer follow the previous steps, but make the GPO on the OU that contains the computers. Note that you are making a GPO that targets users, but applying it to computers. To make this work you need to add one more setting to your GPO.

  1. Navigate to:
    Computer Configuration->Administrative Templates->System->Group Policy
  2. Set the value of “User Group Policy loopback processing mode” to Enabled and use the “Merge” option.

If you now login to a computer in the OU that you just applied the policy to you will have access to that printer, but if you move to a computer outside the OU it won’t be available to you.

The Group Policy Loopback processing mode of merge essentially means that policies should be applied to the computer at startup, the user at login, and then the user section of the computer policy afterward (still at login.)

Here is a relatively concise set of information on GPO loopback.