I am a n00b to perl scripting and need help to start building my own. I am
currently working on a project where the LDAP(389-ds) and Active Directory
are always in sync. I have a very minimal set of attributes and conditions
to keep them in sync.
Can anyone share their code, so that I can build around it? Here¹s my
requirement:
* Sync New users from AD to LDAP with attributes: sAMAccountName, sn,
givenName, description, userAccountControl(disable/enable),
* Delete LDAP accounts which are not present in AD and vice versa.
* Generate the next available uidnumber by parsing thru ldap, so new users
can be created
* Check memberOf for 2 groups and if true add them to corresponding groups
in LDAP
If you have any of these modules written already, that would be great help.
I am digging through the archive looking for related code.
Thanks,
Prashanth
Net::LDAP::Class should make most if not all of those requirements pretty easy
to implement. It was written to ease keeping a rdbms, LDAP and AD all in sync.
--
Peter Karman . http://peknet.com/ . pe...@peknet.com
I suppose I should back that up with some actual code.
I would set it up like this. These files:
lib/MyLDAP/User.pm
lib/MyLDAP/Group.pm
lib/MyAD/User.pm
lib/MyAD/Group.pm
Containing code like this. (NOTE the code is *NOT* tested).
lib/MyLDAP/User.pm:
package MyLDAP::User;
use base qw( Net::LDAP::Class::User::POSIX );
__PACKAGE__->metadata->setup(
base_dn => 'dc=yourcompany,dc=com',
attributes => __PACKAGE__->POSIX_attributes,
unique_attributes => __PACKAGE__->POSIX_unique_attributes,
);
sub init_group_class { 'MyLDAP::Group' }
1;
lib/MyLDAP/Group.pm:
package MyLDAP::User;
use base qw( Net::LDAP::Class::Group::POSIX );
__PACKAGE__->metadata->setup(
base_dn => 'dc=yourcompany,dc=com',
attributes => __PACKAGE__->POSIX_attributes,
unique_attributes => __PACKAGE__->POSIX_unique_attributes,
);
sub init_user_class { 'MyLDAP::User' }
1;
lib/MyAD/User.pm:
package MyAD::User;
use base qw( Net::LDAP::Class::User::AD );
__PACKAGE__->metadata->setup(
base_dn => 'dc=yourcompany,dc=com',
attributes => __PACKAGE__->AD_attributes,
unique_attributes => __PACKAGE__->AD_unique_attributes,
);
sub init_group_class { 'MyAD::Group' }
1;
lib/MyAD/Group.pm:
package MyAD::Group;
use base qw( Net::LDAP::Class::Group::AD );
__PACKAGE__->metadata->setup(
base_dn => 'dc=yourcompany,dc=com',
attributes => __PACKAGE__->AD_attributes,
unique_attributes => __PACKAGE__->AD_unique_attributes,
);
sub init_user_class { 'MyAD::User' }
1;
Now you have your .pm class files set up, and you can write a script (or more
likely, multiple scripts, one per action) to use them to handle all your
requirements:
# example to sync between AD and LDAP based on uid/sAMAccountName (username)
use strict;
use warnings;
use lib 'lib';
use Net::LDAP;
use MyLDAP::User;
use MyLDAP::Group;
use MyAD::User;
use MyAD::Group;
# create all the ldap connections and bind
my $ldap_dn = 'your-authenticated-user-dn';
my $ldap_pass = 'your-authenticated-user-password';
my $ldap_host = 'ldap://yourldapserver';
my $ldap = Net::LDAP->new($ldap_host);
my $ad_dn = 'your-authenticated-user-dn';
my $ad_pass = 'your-authenticated-user-password';
my $ad_host = 'ldap://youradserver';
my $ad = Net::LDAP->new($ad_host);
# could use ssl cert here too instead of password
my $mesg = $ldap->bind( $ldap_dn, password => $ldap_pass );
$mesg->code and die Net::LDAP::Class->get_ldap_error($mesg);
$mesg = $ad->bind( $ad_dn, password => $ad_pass );
$mesg->code and die Net::LDAP::Class->get_ldap_error($mesg);
# iterate over all LDAP users, checking if they are in AD
my $num_ldap_checked = MyLDAP::User->act_on_all(
\&check_ldap_users,
{ ldap => $ldap }
);
sub check_ldap_users {
my $ldap_user = shift;
my $ad_user = MyAD::User->new( ldap => $ad, username => "$ldap_user" );
if (!$ad_user->read) {
warn "$ldap_user is not in AD!";
# decide what action should be taken. Delete from ldap?
$ldap_user->delete;
# or create a AD user?
# add them with whatever attributes you need
# see the perldoc for Net::LDAP::Class::User::AD
# ...
# then save to AD
$ad_user->create or die "can't create AD user $ad_user";
}
}
# now do the reverse, checking AD
my $num_ad_checked = MyAD::User->new(
\&check_ad_users,
{ ldap => $ad }
);
sub check_ad_users {
my $ad_user = shift;
# similar to above in check_ldap_users.
}
print "Checked $num_ldap_checked LDAP users and $num_ad_checked AD users\n";
exit(0);
The basic idea is that you can set up .pm classes to handle all the dirty work
of managing user/group relationships, setting attributes, reading/writing from
the server, etc, and then use those classes over and over in your management
scripts.
However, but for uidnumber I use a object in AD named CN=nextUIDNumber where I store the next uid in the description attribute. Rather than troll through AD and LDAP I just get/set that for the uidnumber attribute.
Hi,
you can maybe have a look at www.lsc-project.org. It is not Perl, but
allows you to create a connector between AD and OpenLDAP easily.
For example, you can follow this tutorial:
http://lsc-project.org/wiki/documentation/tutorials/openldaptoactivedirectory
Cl�ment.
I want only one way sync from AD --> LDAP(389-ds). The other way has to only
generate a list of users and the AD will approve/reject those. There is no
password sync as it is handled by PAM. I tried the LDAP connector, but it
still needs more development and there are lot of bugs.
I am planning to script this one out and run as a crontab. I was not able to
achieve all my requirements but here it is.
PS: I know this is the worst code you might have ever come across but I am
in the process of breaking down to modules.
Peter: Your idea is great, but I am so new to this that it will take me some
time to implement that.
===========================================================
$user_ldbase = 'ou=people,dc=LDAPDomain,dc=net';
$user_ad="cn=administrator,cn=Users,dc=ADDomain,dc=net";
$user_ld="cn=Directory Manager";
$objectclasses = [ 'person', 'posixAccount', 'top', 'inetorgperson',
'organizationalPerson'];
# attribute defaults
$loginshell='/bin/bash';
# Bind and Initialization
sub init {
system("/bin/stty -echo");
print STDERR "Enter Active Directory password: ";
$passwd_ad=<STDIN>; chop $passwd_ad;
print STDERR "\nEnter Directory Manager password: ";
$passwd_ld=<STDIN>; chop $passwd_ld;
print STDERR "\n";
system("/bin/stty echo");
$ldap_ad = Net::LDAP->new('ADDomain2-svr.ADDomain.net') or die "$@";
$ldap_ld = Net::LDAP->new('centos-lin.LDAPDomain.net', port=>389) or
die "$@";
$ldap_ad->bind( dn => $user_ad, password =>$passwd_ad );
$ldap_ld->bind( dn => $user_ld, password =>$passwd_ld );
$lastuid = 2000;
}
# Get the list of uids to be synced(next available uid for new user)
sub getUid {
my($mesg_ld,$entry, $uid, @uids, $uidnumber_av);
$mesg_ld = $ldap_ld->search ( base => $user_ldbase,filter =>
"(objectclass=posixAccount)",attrs => ['uidNumber'] );
print $mesg_ld->error if $mesg_ld->code;
@entry = $mesg_ld->entries;
for (@entry) {
my $uid = $_->get_value("uidNumber");
if ($uid)
{ $uids[$uid] = 1; }
}
while (1) {
if ($uids[$lastuid])
{ $lastuid++; }
else {
if (($ldap_ld->search(base=>$user_ldbase,filter =>
"(uidNumber=$lastuid)" ))->entry())
{
$uids[$lastuid++] = 1;
}
else
{
$uids[$lastuid] = 1;
$uidnumber_av = $lastuid++;
return $uidnumber_av;
}
}
}
}
# post the AD user to LDAP
sub disableacct {
if (($userAccountControl & 0x0002) != 0x00002)
{ $result = $ldap_ld->modify($entry_pf->dn,changes =>
[delete => ['nsAccountLock' => 'true'],add => ['nsAccountLock' =>
'false'],]);
print "ENABLED account account for $ad_account !\n";
}
else
{ $result = $ldap_ld->modify($entry_pf->dn,changes =>
[delete => ['nsAccountLock' => 'false'],add => ['nsAccountLock' =>
'true'],]);
print "DISABLED account account for $ad_account !\n";
}
if($$result{'resultCode'})
{
print $ad_account, $$result{'errorMessage'},"!\n";
}
print "\n";
}
#Create the AD user with LDAP schema
sub createADUser4LDAP {
my($uidNumber) = @_;
my($entry,$dn,$mesg, $str,$type, $ad_proxyaddress);
$entry = Net::LDAP::Entry->new;
$dn = $ad_account;
$dn = "uid=" . $ad_account . "," . $user_ldbase;
$entry->dn($dn);
$entry->add('objectclass' => $objectclasses);
$entry->add('uid' => $ad_account);
$entry->add('uidNumber' => $uidNumber);
$entry->add('gidNumber' => $uidNumber);
$entry->add('homedirectory' => "/home/$ad_account");
$entry->add('cn' => $ad_name );
$entry->add('gecos' => $ad_account);
$entry->add('sn' => $sn );
$entry->add('givenName' => $givenName);
#$entry->add('description' => $description);
$entry->add('userPassword' => &get_password);
$entry->add('loginShell' => $loginshell);
$entry->add('mail' => $ad_mail);
if ($userAccountControl & 0x0002 != 0x00002)
{ $entry->add('nsAccountLock' => "false"); }
else
{ $entry->add('nsAccountLock' => "true"); }
$mesg = $entry->update( $ldap_ld );
print $mesg->error,"\n" if $mesg->code;
print "create $dn\n";
return $mesg->code;
}
# Perform a search
sub process {
$mesg_ad = $ldap_ad->search ( base =>
"OU=Users,OU=Corporate,DC=ADDomain,DC=net",filter =>
"(objectClass=person)");
$entries = $mesg_ad->count;
if ($entries lt 1)
{
print "entries=0 \n";
exit 1;
}
foreach my $entry ( $mesg_ad->entries )
{
$dn = $entry->dn;
$ad_account= $entry->get_value( "sAMAccountName" );
@ad_proxyAddresses = $entry->get_value( "proxyAddresses" );
$ad_name = $entry->get_value( "name" );
$sn = $entry->get_value( "sn" );
$givenName = $entry->get_value( "givenName" );
$description = $entry->get_value( "description" );
$userAccountControl = $entry->get_value("userAccountControl");
$ad_mail = $entry->get_value( "mail" );
next if(grep(/$ad_account/i, @ignore_users));
$mesg_ld = $ldap_ld->search ( base => $user_ldbase,filter =>
"(&(objectClass=posixAccount)(uid=$ad_account))" );
print "working on user: ",$ad_account,"\n";
if( $mesg_ld->count)
{
print "found user $ad_account on ldap no changes made\n";
$entry_pf = ($mesg_ld->entries)[0];
&disableacct($userAccountControl, $ad_account);
}
elsif ($map_user_list{$ad_account})
{
$mesg_ld = $ldap_ld->search
(base=>$user_ldbase,filter=>"(&(objectClass=posixAccount)(uid=$map_user_list
{$ad_account}))");
if( $mesg_ld->count)
{
$entry_pf = ($mesg_ld->entries)[0];
print "found user to ADD to LDAP
$ad_account/$map_user_list{$ad_account}\n";
&disableacct($userAccountControl, $map_user_list{$ad_account});
}
}
else
{
my($newuidnum);
$newuidnum = &getUid;
print "new userid#: ", $newuidnum, "\n";
&createADUser4LDAP($newuidnum);
}
}
}
#Set password for new user(random pass)
sub get_password {
my ($passname) = @_;
@chars = ('a' .. 'k','m' .. 'z',2..9);
$u{$passname} = "";
for (1 .. 8)
{
$u{$passname} .= $chars[rand @chars];
}
$u{$passname} = enc_passwd($u{$passname});
}
#Encrypted password to md5
sub enc_passwd {
my $password = shift;
my @chars = ( 'A' .. 'Z', 'a' .. 'z', '0' .. '9', '/', '.' );
my $salt = "";
for (1..8)
{
$salt .= $chars[rand @chars];
}
return ("{md5}" . unix_md5_crypt($password,$salt));
}
# Terminate routine
sub termiante {
$ldap_ad->unbind;
$ldap_ld->unbind;
}
&init;
&process;
&termiante;
=================================================