Re: [Net::LDAP] Re: [Net::LDAP] LDAP and AD sync

Prashanth Sundaram <[email protected]>
Newsgroups gmane.comp.lang.perl.modules.ldap
Message-ID <C7482B7E.5E7C%[email protected]>
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.
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.