RE: How To Set AD Password

"Young, Darren" <[email protected]>
Newsgroups gmane.comp.lang.perl.modules.ldap
Message-ID <07A371D457B501478C1DB3C3DE8372D705357992@GSBHEX2V.gsb.uchicago.edu>
AD won't allow a user password to be set over a non-ssl LDAP connection.
Get a "real" cert from a third party for the DC or go self-signed.

I'll try to attach my entire AD support module to this, not sure if the
list will allow attachments though. If you want it directly, let me
know.

Here's my worker function to set the AD password for a given user DN
(note that adConnect() goes over a secure channel):

#
========================================================================
======
# NAME        : _setADPasswordForUser
# DESCRIPTION : Set the password for a given AD account
# ARGUMENTS   : string(dn), string(password)
# RETURN      : TRUE or FALSE
# NOTES       : None
#
========================================================================
======
sub _setADPasswordForUser {
    my ($dn, $pass) = @_; 
    my $name = "_setADPasswordForUser";
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename,
line->$line");    

    my $retval = 0;
    my $npass;

    my $ad = _adConnect();

    # add quotes and convert to uniCode
    map { $npass .= "$_\000" } split(//, "\"$pass\"");
    debug("$name: unicodePwd => $npass");
                
    my $rtn = $ad->modify($dn, replace => { "unicodePwd" => $npass }); 
        
    if ( $rtn->code ) { 
        logmsg("$name: FAILED to change password for $login");
        logmsg("$name: LDAP error code is: " . $rtn->code);
        logmsg("$name: LDAP error text is: " . $rtn->error);
        $retval = 0;
    } else {
        $retval = 1;
    }   

    if ( _adClose($ad) ) { 
        return($retval);
    } else {
        return($retval);
    }   

}



> -----Original Message-----
> From: Sheahan, John [mailto:[email protected]]
> Sent: Thursday, December 31, 2009 9:37 AM
> To: [email protected]
> Subject: How To Set AD Password
> 
> My script is successfully creating AD and Exchange accounts but I'm
> having trouble setting the password.
> 
> I am setting these attributes:
> 
>                                   'userPassword' => 'password',
>                                   'userAccountControl' => '66048',  #
> activates the account and sets the password to never expire
> 
> 
> For some reason, the password is not being set but my script does not
> throw an error.
> 
> I have two questions:
> 
> 
> 1.       Does anyone know what the default password is for a newly
> created user in AD if none is set?
> 
> 2.       What is the best way to set the password when creating a new
> user?
> 
> 
> Thanks a lot
> 
> John
adtools.pm (application/octet-stream, 22.2 KB)
# ==============================================================================
#
# adtools.pm
#
# Account maint Active Directory functions
#
# $Id: adtools.pm,v 1.12 2008/06/18 21:13:33 dyoung2 Exp $
#
# Darren Young [[email protected]]
#
# ==============================================================================
#
# ChangeLog
#
# $Log: adtools.pm,v $
# Revision 1.12  2008/06/18 21:13:33  dyoung2
#   * Ready for testing
#
# Revision 1.11  2008/06/10 19:44:54  dyoung2
#   * First WORKING version to test with.
#
# Revision 1.10  2008/06/10 18:54:27  dyoung2
#   * Attempted at using the groups to enumerate users, didn't work
#     have to use a paging control and go at the memberOf attribute
#
# Revision 1.9  2008/06/10 16:31:44  dyoung2
#   * New updates
#
# Revision 1.8  2008/06/10 15:54:05  dyoung2
#   * Started to add _getADGroupMembers
#
# Revision 1.7  2008/04/04 21:53:16  dyoung2
#   * Changes for registrar in testing.
#
# Revision 1.6  2006/08/09 21:44:58  dyoung2
#   * Added _deleteADUser function
#
# Revision 1.5  2005/10/10 23:18:35  dyoung2
#   * Added _adConnect and setADPassword().
#
# Revision 1.4  2005/09/22 17:57:43  dyoung2
#   * Added _getADDNForUser and _setADPasswordForUSer
#
# Revision 1.3  2005/08/25 22:38:26  dyoung2
#   * Standardized BEGIN headers.
#
# Revision 1.2  2005/06/20 20:57:51  dyoung2
#   * Added _getADGroupsForUser().
#
# Revision 1.1  2005/04/11 23:08:04  dyoung2
#   * Initial version of module.
#   * Added isValidADAccount().
#
# ==============================================================================

my $cvsid = '$Id: adtools.pm,v 1.12 2008/06/18 21:13:33 dyoung2 Exp $';
my @cvsinfo = split( ' ', $cvsid );
our $NAME    = File::Basename::basename($0);
our $VERSION = $cvsinfo[2];


# ------------------------------------------------------------------------------
#                             B E G I N
# ------------------------------------------------------------------------------
BEGIN {

    # Pragmas
    use strict;

    # "Standard" modules we use
    use FindBin;
    use Config::Simple;
    use lib "$FindBin::Bin/../lib";

    # Standard account maint modules to use
    use logging;
    use errcodes;
    use utils;
    
    # Modules that this module uses
    use Net::LDAP;
    use Net::LDAPS;
    use Net::LDAP::Control::Paged;
    use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED );

}


# ------------------------------------------------------------------------------ 
#                             V A R I A B L E S
# ------------------------------------------------------------------------------ 
our $LOGFILE    = "$FindBin::Bin/../log/acctmgr.log" unless($LOGFILE); 
our $CONFIGFILE = "$FindBin::Bin/../etc/acctmgr.cfg" unless($CONFIGFILE); 


# ------------------------------------------------------------------------------
#                             C O N F I G   F I L E
# ------------------------------------------------------------------------------
our $CFG = new Config::Simple( filename => $CONFIGFILE ) or die "$!\n" unless($CFG);


# ==============================================================================
# NAME        : _adConnect
# DESCRIPTION : Establish and bind an AD LDAP session
# ARGUMENTS   : None
# RETURN      : string(reference(ldap))
# NOTES       : None
# ==============================================================================
sub _adConnect {
    my $name = "_adConnect";
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");
    
    my @adhosts = $CFG->param("ad.hosts"); ### list of hosts to use for searches
    my $timeout = $CFG->param("ad.timeout");
    my $ldapver = $CFG->param("ad.version");
    my $ldap;


    # create anonymous hashes for each host we're configured to look at
    # and populate their settings from the config file
    foreach $host (@adhosts) {
        
        $$host{"hostname"} = $CFG->param("$host.hostname");
        $$host{"type"}     = $CFG->param("$host.type");
        $$host{"binddn"}   = $CFG->param("$host.binddn");
        $$host{"bindpw"}   = $CFG->param("$host.bindpw");

    }


    # flip through all the AD servers and connect/bind
    foreach $host (@adhosts) {

        # Attempt to establish a connection to the designated host
        # if we fail, move to the next one
        debug("$name: Attempting to create an $$host{'type'} connection to $host");
        
        # if it's a plain old LDAP server
        if ( $$host{'type'} eq "LDAP" ) {
            if ( $ldap = Net::LDAP->new( $$host{'hostname'}, timeout=>$timeout, version=>$ldapver )) {
                debug("$name: Established $$host{'type'} connection to $$host{'hostname'}");
            } else {
                logmsg("$name: FAILED to establish $$host{'type'} connection to $$host{'hostname'}");
                next;
            }
        
        # if it has SSL enabled
        } elsif ( $$host{'type'} eq "LDAPS" ) {
            if ( $ldap = Net::LDAPS->new( $$host{'hostname'}, timeout=>$timeout, version=>$ldapver )) {
                debug("$name: Established $$host{'type'} connection to $$host{'hostname'}");
            } else {
                logmsg("$name: FAILED to establish $$host{'type'} connection to $$host{'hostname'}");
                next;
            }

        # and there are other ldap types ?
        } else {
            logmsg("$name: Invalid comm type: $$host{'type'} for $host");
            next;
        }


        # if we got a connection, attempt a bind to that sserver
        if ( $ldap->bind( $$host{'binddn'}, password => $$host{'bindpw'} ) ) {
            debug("$name: Successful bind to $host");
            return($ldap);
        } else {
            logmsg("$name: FAILED to bind to LDAP server $host");
            logmsg("$name: LDAP error code is " . $ldap->code);
            logmsg("$name: LDAP error text is " . $ldap->error);
            next;
        }

    } ### end foreach $host loop
    
    # if we made it here, all the connect() and bind() calls failed
    debug("$name: FAILED to contact ANY Active Directory servers");
    return(ERR_CRITICAL);

}


# ==============================================================================
# NAME        : _adClose
# DESCRIPTION : Unbind and disconnect and AD LDAP session
# ARGUMENTS   : string(reference(ldap))
# RETURN      : TRUE or FALSE
# NOTES       : None
# ==============================================================================
sub _adClose {
    my ($ldap) = @_;
    my $name = "_adClose()";
    my ( $package, $filename, $line ) = caller;
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");
    
    # Make sure they gave us a valid Net::LDAP or Net::LDAPS object
    if ( $ldap->isa("Net::LDAP") || $ldap->isa("Net::LDAPS")) {
    
        # unbind from the server
        if ( $ldap->unbind ) {
            debug("$name: unbound from ldap server");
        } else {
            logmsg("$name: FAILED to unbind from ldap server");
            return(0);
        }
    
        # Disconnect from the server
        if ( $ldap->disconnect ) {
            debug("$name: disconnected from ldap server");
        } else {
            logmsg("$name: FAILED to disconnect from ldap server");
            return(0);
        }
    } else {
        logmsg("$name: object passed ($ldap) isn't an ldap object");
        return(0);
    }

    debug("$name: returning with val: $retval");
    return(1);
}


# ==============================================================================
# NAME        : isValidADAccount
# DESCRIPTION : checks to see if an account is valid in AD
# ARGUMENTS   : string(login)
# RETURN      : TRUE or FALSE 
# NOTES       : sends back ERR_CRITICAL on no AD servers available
# ==============================================================================
sub isValidADAccount {
    my ($login) = @_;
    my $name = "isValidADAccount";
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");

    my $filter;
    my $ad;
    my $basedn  = $CFG->param("ad.basedn");
    
    if ( $ad = _adConnect() ) {
        
        $filter = '(sAMAccountName=' . $login . ')';
        debug("$name: search based: " . $basedn );
        debug("$name: search filter: $filter");
        $mesg = $ad->search ( base   => $basedn, scope  => 'sub', filter => $filter );

        if ( $mesg->code ) {
            debug("$name: failed search on $ad->{net_ldap_uri}");
            debug("$name: LDAP error code: " . $mesg->code);
            debug("$name: LDAP error text: " . $mesg->error);
            return(0);
        } else {
            debug("$name: found " . $mesg->count . " entries");
            if ( $mesg->count == 0 ) {
                debug("$name: login not found in AD");
                if ( $ad ) {
                    _adClose($ad);
                    return(0);
                }
            } elsif ( $mesg->count == 1 ) {
                debug("$name: found login in AD");
                if ( $ad ) {
                    _adClose($ad);
                    return(1);
                }
            } else {
                debug("$name: too many entries returned");
                return(0);
            }
        }
    } else {
        logmsg("$name: LDAP search FAILED");
        return(0);
    }
}


# ==============================================================================
# NAME        : _getADGroupsForUser
# DESCRIPTION : Retrieve all AD groups a user is in
# ARGUMENTS   : string(login)
# RETURN      : array(groups)
# NOTES       : Need to rework this to use adConnect(), etc.
# ==============================================================================
sub _getADGroupsForUser {
    my ($login) = @_;
    my $name = "_getADGroupsForUser";
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");      

    my $filter;
    my $found = 0;
    my $retval = 0;
    my @memberGroups;

    my @adhosts = $CFG->param("ad.hosts"); ### list of hosts to use for searches
    my $timeout = $CFG->param("ad.timeout");
    my $ldapver = $CFG->param("ad.version");

    foreach $host (@adhosts) {

        # create anonymous hashes for each host we're configured to look at
        # and populate their settings from the config file
        $$host{"hostname"} = $CFG->param("$host.hostname");
        $$host{"port"}     = $CFG->param("$host.port");
        $$host{"binddn"}   = $CFG->param("$host.binddn");
        $$host{"bindpw"}   = $CFG->param("$host.bindpw");
        $$host{"basedn"}   = $CFG->param("$host.basedn");
    }

    foreach $host (@adhosts) {

        # attempt a connect then bind to the current host in this loop
        debug("$name: attepting to create connection to $$host{'hostname'}");
        $ldap = Net::LDAP->new($$host{'hostname'}, timeout=>$timeout, version=>$ldapver);
        $mesg = $ldap->bind($$host{'binddn'}, password=>$$host{'bindpw'});

        # if we fail the bind, move on to the next server in the array
        if ( $mesg->code ) {
            debug("$name: failed bind to $$host{'hostname'}");
            debug("$name: LDAP error is: " . $mesg->error);
            next; ### move on to next host in the foreach loop
        } else {
            debug("$name: successful bind to $$host{'hostname'}");

            $filter = '(sAMAccountName=' . $login . ')';
            debug("$name: search based: " . $$host{'basedn'});
            debug("$name: search filter: $filter");
            $mesg = $ldap->search ( base   => $$host{'basedn'},
                                    scope  => 'sub',
                                    filter => $filter
                                  );
            if ( $mesg->code ) {
                debug("$name: failed search on $$host{'hostname'}");
                debug("$name: LDAP error is: " . $mesg->error);
                next; ### move on to next host in the foreach loop
            } else {
                debug("$name: found " . $mesg->count . " entries");
                if ( $mesg->count == 0 ) {
                    debug("$name: login not found in AD");
                    if ( $ldap ) {
                        $ldap->unbind;
                        return(0);
                    }
                } elsif ( $mesg->count == 1 ) {
                    debug("$name: found login in AD");
                    my $entry = $mesg->entry(0);
                    my @ldapGroups = $entry->get_value("memberOf"); 
                    foreach my $group (@ldapGroups) {
                        debug("$name: isMemberOf -> $group");
                        push(@memberGroups, $group);
                    }

                    if ( $ldap ) {
                        $ldap->unbind;
                        return(@memberGroups);
                    }
                } else {
                    debug("$name: too many entries returned");
                }
            }

        }  ### end mesg->code check

        if ( $ldap ) {
            $ldap->unbind();
        }
        
    } ### end foreach $host loop

    # if we're still here, something is REALLY wrong with AD
    if ( ! $found ) {
        debug("$name: FAILED to contact ANY Active Directory servers");
        return(0);
    } 

}


# ==============================================================================
# NAME        : _getADDNForUser
# DESCRIPTION : Get the DN for an AD user
# ARGUMENTS   : string(login)
# RETURN      : string(dn)
# NOTES       : None
# ==============================================================================
sub _getADDNForUser {
    my ($login) = @_;
    my $name = "_getADDNForUser";
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");      

    my $filter;
    my $ad;
    my $basedn  = $CFG->param("ad.basedn");
    
    if ( $ad = _adConnect() ) {
        
        $filter = '(sAMAccountName=' . $login . ')';
        debug("$name: search based: " . $basedn );
        debug("$name: search filter: $filter");
        $mesg = $ad->search ( base   => $basedn, scope  => 'sub', filter => $filter );

        if ( $mesg->code ) {
        
        debug("$name: failed search on $ad->{net_ldap_uri}");
            debug("$name: LDAP error code: " . $mesg->code);
            debug("$name: LDAP error text: " . $mesg->error);
            return(0);
        
        } else {
            debug("$name: found " . $mesg->count . " entries");
        
        if ( $mesg->count == 0 ) {
                debug("$name: login not found in AD");
                
                if ( $ad ) {
                    _adClose($ad);
                    return(0);
                }
            
            } elsif ( $mesg->count == 1 ) {
                debug("$name: found login in AD");
                
                # found it, get the dn for the entry
                my $dn = $mesg->entry(0)->dn();                
                
                
                # if we had a good connection, close and return the dn.
                if ( $ad ) {
                    _adClose($ad);
                    return(qq($dn));
                }
            } else {
                debug("$name: too many entries returned");
                return(0);
            }
        }
    } else {
        logmsg("$name: FAILED to create AD/LDAP connection");
        return(0);
    }

}


# ==============================================================================
# NAME        : _setADPasswordForUser
# DESCRIPTION : Set the password for a given AD account
# ARGUMENTS   : string(dn), string(password)
# RETURN      : TRUE or FALSE
# NOTES       : None
# ==============================================================================
sub _setADPasswordForUser {
    my ($dn, $pass) = @_;
    my $name = "_setADPasswordForUser";
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");      

    my $retval = 0;
    my $npass;

    my $ad = _adConnect();

    # add quotes and convert to uniCode
    map { $npass .= "$_\000" } split(//, "\"$pass\"");
    debug("$name: unicodePwd => $npass");
            
    my $rtn = $ad->modify($dn, replace => { "unicodePwd" => $npass });
    
    if ( $rtn->code ) {
        logmsg("$name: FAILED to change password for $login");
        logmsg("$name: LDAP error code is: " . $rtn->code);
        logmsg("$name: LDAP error text is: " . $rtn->error);
        $retval = 0;
    } else {
        $retval = 1;
    }

    if ( _adClose($ad) ) {
        return($retval);
    } else {
        return($retval);
    }

}


# ==============================================================================
# NAME        : 
# DESCRIPTION : 
# ARGUMENTS   : 
# RETURN      : 
# NOTES       : 
# ==============================================================================
sub _deleteADUser {
    my ($login) = @_;
    my $name = "_deleteADUser";
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");      

    my $filter;
    my $ad;
    my $basedn  = $CFG->param("ad.basedn");
    my $retval = 1; # default to true (good)
    
    if ( $ad = _adConnect() ) {
        
        $filter = '(sAMAccountName=' . $login . ')';
        debug("$name: search based: " . $basedn );
        debug("$name: search filter: $filter");
        $mesg = $ad->search ( base   => $basedn, scope  => 'sub', filter => $filter );

        if ( $mesg->code ) {
        
        debug("$name: failed search on $ad->{net_ldap_uri}");
            debug("$name: LDAP error code: " . $mesg->code);
            debug("$name: LDAP error text: " . $mesg->error);
            return(0);
        
        } else {
            debug("$name: found " . $mesg->count . " entries");
        
        if ( $mesg->count == 0 ) {
                debug("$name: login not found in AD");
                
                if ( $ad ) {
                    _adClose($ad);
                    return(0);
                }
            
            } elsif ( $mesg->count == 1 ) {
                debug("$name: found login in AD");
                
                # found it, get the dn for the entry
                my $dn = $mesg->entry(0)->dn();                
                debug("$name: dn => $dn");

                my $r = $ad->delete($dn);

                if ( $r->code ) {
                    logmsg("$name: failed to delete account, code => " . $r->code);
                } else {
                    logmsg("$name: deleted AD account for $login");
                }
                
                # if we had a good connection, close and return the dn.
                if ( $ad ) {
                    _adClose($ad);
                    return(1);
                }
            } else {
                debug("$name: too many entries returned");
                return(0);
            }
        }
    } else {
        logmsg("$name: FAILED to create AD/LDAP connection");
        return(0);
    }

    # fall through to here?
    return($retval);

}


# ==============================================================================
# NAME        : _getADGroupMembers
# DESCRIPTION : Return members of an AD group
# ARGUMENTS   : string(group)
# RETURN      : array(members) or FALSE
# NOTES       : Uses an LDAP paged result set
#             : The group name is the DN of the group
# ==============================================================================
sub _getADGroupMembers {
    my ($group) = @_;
    my $name = "_getADGroupMembers";
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");

    my $filter = "(memberOf=$group)";
    my $ad;
    my $basedn  = $CFG->param("ad.basedn");
    my @members;
    my $page;
    my @args;
    my $cookie;
    my $pagesize = 100;

    $| = 1;

    debug("$name: filter => $filter");

    if ( $ad = _adConnect() ) {

        debug("$name: search based: " . $basedn );
        debug("$name: search filter: $filter");
        debug("$name: page size: $pagesize");

        $page = Net::LDAP::Control::Paged->new( size => $pagesize );

        @args = ( base     => $basedn,
                  scope    => "subtree",
                  filter   => $filter,
                  control  => [ $page ],
                );

        while(1) {
            # Perform search
            debug("Searching next $pagesize...");
            my $mesg = $ad->search( @args );

            # gather the members and shove them in the array
            while (my $entry = $mesg->shift_entry()) {
 
                my $name = $entry->get_value('samAccountName');
                push (@members, $name);

            } # while


            my ($resp) = $mesg->control( LDAP_CONTROL_PAGED ) or last;
            $cookie    = $resp->cookie or last;
            $page->cookie($cookie);

        } # while (1)

        # Close the search if we're out of results
        if ($cookie) {
            $page->cookie($cookie);
            $page->size(0);
            $ad->search( @args );
        }

        # if the connection is still good, close it out
        if ( $ad ) {
            _adClose($ad);
        }

        # Return a sorted array
        debug("$name: found " . scalar(@members) . " group members");
        @members = sort(@members);
        return(@members);

    } else {
        logmsg("$name: FAILED to create AD/LDAP connection");
        return(0);
    }
}

1;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.