Thanks everyone for the feedback. I am new to scripting and unix world, and learning my way through.
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; ================================================= On 12/10/09 7:18 PM, "Peter Karman" <pe...@peknet.com> wrote: > Peter Karman wrote on 12/10/09 5:48 PM: >> Prashanth Sundaram wrote on 12/10/09 4:59 PM: >>> Folks, >>> >>> 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. >> >> 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. >> >> > > 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.