I often work in hybrid environments with lots of Linux/UNIX servers to do heavy lifting and a large Windows domain for workstations, directory services, and Exchange. Interoperability between the environments is important, and there is the occasional need for a web application or script that can change a user’s AD password. Here’s a breakdown of a rough script that does just that.
The script takes the following approach:
- Accept inputs from the command line (this is a rough script – no fancy usage flags or getopt argument parsing).
- Bind to the directory.
- Search for the user, confirm only one account matching the constraints exists, and pull the DN attribute.
- Create a double quoted unicode version of the passwords (Active Directory requires this for the account’s current and new passwords).
- Delete, then Add the unicodePwd attribute from the account. This is must be done in a single LDAP modify operation.
In the form of the script below, a user can change their own AD password, but a privileged account (such as a domain admin) cannot. Users change their own password with a delete, followed by an add. Admins change passwords with an LDAP replace. Uncomment the appropriate line in the script to adjust the functionality. Read on for the full script.
#!/usr/bin/perl -w
# ad_pwchange.pl - Ben Whaley - ben at atrust dot com
use strict;
use Net::LDAPS;
my ($binddn, $bindpw, $searchdn, $server, $sam, $newpass);
my ($ad, $uninewpass, $unibindpw, $sr, $samdn, $rtn);
$binddn = $ARGV[0]; # distinguished name for the LDAP bind
$bindpw = $ARGV[1]; # a password to match $binddn
$searchdn = $ARGV[2]; # The search base, usually the AD domain name
$server = $ARGV[3]; # The AD server to bind to (over LDAPS, or port 636)
$sam = $ARGV[4]; # The sAMAccountName, or username, of the account
$newpass = $ARGV[5]; # The new password value
# Bind to the AD server
$ad = Net::LDAPS->new($server, version => 3) or exit 1;
$ad->bind(dn => $binddn, password => $bindpw) or exit 1;
# Do an AD lookup to get the dn for this user.
# also, confirm only 1 account matches.
$sr = $ad->search(base => $searchdn, filter => "samaccountname=$sam");
die 1 if ($sr->count() !=1 );
$samdn = $sr->entry(0)->dn;
# Add quotes and uniCode to the passwords.
map { $uninewpass .= "$_\000" } split(//, "\"$newpass\"");
map { $unibindpw .= "$_\000" } split(//, "\"$bindpw\"");
# Uncomment this (and comment the next lines)
# to allow admin password changes
#$rtn = $ad->modify($samdn, replace => [ 'unicodePwd' => $uninewpass]);
# Replace the password in an LDAP modify operation
$rtn = $ad->modify($samdn, changes => [
delete => [ 'unicodePwd' => $unibindpw ],
add => [ 'unicodePwd' => $uninewpass]
]);
# Complain verbosely if there was an error
if($rtn->{'resultCode'} != 0) {
print $rtn->error . "\n";
exit 1;
}
exit 0;
An example usage for a user password change might be:
% ./ad_pwchange.pl 'CN=Whaley\, Benjamin,OU=Users,DC=example,DC=com' 'CurrentPass' 'DC=example,DC=com' 'adserver.example.com' 'bwhaley' 'NewPass'
Similarly, for an admin changing another user’s password:
% ./ad_pwchange.pl 'CN=Admin\, Super,OU=Admins,DC=example,DC=com' 'AdminPass' 'DC=example,DC=com' 'adserver.example.com' 'bwhaley' 'NewPass'
I typically call this code from other scripts (sometimes from PHP-based web apps), thus the lack of arguments, prompted inputs, dual password verification, and better error handling.
2 Responses
February 23rd, 2010 at 8:28 am
We had a need to set initial passwords in an LDS directory we were setting up, this was very helpful in understanding the unicodepwd and setting it in LDS.
Thanks,
February 23rd, 2010 at 10:02 am
Great, Roger, glad you found this useful!
- Ben
Leave a Comment