Victory! Change Active Directory Password via LDAP through browser


I had to give up on PHP and go to Perl, but it turned out not to be so bad. Users can now change their Active Directory passwords via a self-service web page that doesn’t require admin credentials. The Perl code is below.  Authentication to the script is done via .htaccess LDAP authentication, so the REMOTE_USER env variable is assumed to contain the user’s username (sAMAccountName) by the time this script is called.  There is a simple check for $ENV{HTTPS} to ensure the script is called via SSL, and AD requires password changes to be done via ldaps, so the whole thing should be encrypted end to end.

#!/usr/bin/perl
#
# Evan Hoffman (evanhoffman@evanhoffman.com)
# 13 January, 2010
#
# Base functionality from: http://search.cpan.org/~gbarr/perl-ldap/lib/Net/LDAP/FAQ.pod#..._in_MS_Active_Directory_?

print "Content-Type: text/html\n\n";

use Net::LDAP;
use Unicode::Map8;
use Unicode::String qw(utf16);
use MIME::Base64;
use CGI::Lite;

# build the conversion map from your local character set to Unicode
my $charmap = Unicode::Map8->new('latin1')  or  die;

my $host = 'ldaps://activedirectory.example.com';	

my $bind_dn = 'CN=ldapuser,OU=Utility,OU=Users,DC=example,DC=com';
my $bind_pw = 'secret';
my $base_dn = 'DC=example,DC=com';
my $search_filter = '(!(userAccountControl:1.2.840.113556.1.4.803:=2))';

my $ldap = Net::LDAP->new($host)  or  die "$@";

print '<html><title>Active Directory Password Changer $Id$</title><body>';

die "REMOTE_USER not set.\n" unless $ENV{'REMOTE_USER'};

unless ( $ENV{'HTTPS'} eq 'on' ) {
	print "<b>This script requires HTTPS.</b>\n";
	die;

}

print "Logged in as <b>$ENV{REMOTE_USER}</b>.  LDAP URI is <b>$host</b>.<br>\n";

$cgi = new CGI::Lite;

%form = $cgi->parse_form_data;

if ($form{'op'} eq 'submit') {
	my $user_dn = $form{'dn'};
	my $user_pw = $form{'oldpassword'};
	my $newpw1 = $form{'newpw1'};
	my $newpw2 = $form{'newpw2'};

	die 'Password mismatch.'  unless ( $newpw1 eq $newpw2 );
	die 'No DN specified.' unless $user_dn;

	print "Attempting to bind as <b>$user_dn</b> ... ";

	my $mesg = $ldap->bind($user_dn,
			password => $user_pw);
	if ($mesg->is_error) {
		my $err = "Error attempting to bind as $user_dn: ". $mesg->error;
		print "<pre>$err</pre>";
		die $err;
	}

	print " OK.<br>\n";

	my $oldUniPW = $charmap->tou('"'.$user_pw.'"')->byteswap()->utf16();
	my $newUniPW = $charmap->tou('"'.$newpw1.'"')->byteswap()->utf16();

	print "Attempting to modify password for <b>$user_dn</b> ... ";

	$mesg = $ldap->modify($user_dn,
			changes => [
			delete => [ unicodePwd => $oldUniPW ],
			add    => [ unicodePwd => $newUniPW ] ]);

	print "OK<br>\n";

#	$mesg->is_error && die ('Modify error: '. $mesg->error);
	if ($mesg->is_error) {
		my $err = 'Modify error: '. $mesg->error;
		print "<pre>$err</pre>\n";
		die $err;
	}

	$ldap->unbind();
	print "<blink>VICTORY!!</blink> Successfully changed password for user $ENV{REMOTE_USER}, DN <b>$user_dn</b>, msg ID: ".$mesg->mesg_id ."\n";

} else {

# bind as dummy to lookup the user's DN
	my $mesg = $ldap->bind($bind_dn, password => $bind_pw );
	$mesg->is_error && die ('Bind error: '. $mesg->error);

	$mesg = $ldap->search(
			base => $base_dn,
			scope => 'sub',
			attrs => ['dn','cn','mail'],
			filter => "(&(samaccountname=$ENV{REMOTE_USER})$search_filter)"
			);
	$mesg->is_error && die ( 'Search failed: '. $mesg->error );

	$mesg->count || die ("Search for user '$ENV{REMOTE_USER}' returned no results.");

	my $entry = $mesg->entry( 0 );

	$ldap->unbind();

	print "LDAP DN for user '$ENV{REMOTE_USER}': <b>".$entry->dn.'</b>';

	print "<form action='$ENV{REQUEST_URI}' METHOD='POST'>\n";
	print "<input type='hidden' name='op' value='submit'>\n";
	print "<input type='hidden' name='dn' value='".$entry->dn."'>\n";

	print "<table border='0'>\n";
	print "<tr><td>Current password for <b>$ENV{REMOTE_USER}</b>:</td><td><input type='password' name='oldpassword' size='30'></td></tr>\n";
	print "<tr><td>New password for <b>$ENV{REMOTE_USER}</b>:</td><td><input type='password' name='newpw1' size='30'></td></tr>\n";
	print "<tr><td>New password for <b>$ENV{REMOTE_USER}</b> <i>again</i>:</td><td><input type='password' name='newpw2' size='30'></td></tr>\n";
	print "<tr><td colspan='2'><input type='submit' name='submit' value='Change Password'><input type='reset' value='Reset Form'></td></tr>\n";
	print "</form>\n";
	print "</table>\n";

}

print '</body></html>';

, , , , , , , , , , ,

  1. No comments yet.
(will not be published)
  1. No trackbacks yet.