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" <[email protected]> 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.