A Unix NIS to AD account synchronization script
For shops where Unix NIS is still the authoritative source of accounts, and where AD is implemented, the following script synchronizes NIS accounts to AD.
This tip was submitted to the SearchWin2000.com tip exchange by member Kevin Crandall. Please let other users know...
Continue Reading This Article
Enjoy this article as well as all of our content, including E-Guides, news, tips and more.
how useful it is by rating it below.
For shops where Unix NIS is still the authoritative source of accounts, and where AD is implemented, we developed the following script to synchronize NIS accounts to Active Directory using the following prerequisite software:
The script runs as a Scheduled Task on an AD DC under a domain admin account. All scripts must reside in the same folder on the DC and be ACLed appropriately.
Exclude files are included for accounts in Unix and AD that are not human -- e.g. service, dbadmin, etc., to avoid constant announcements of lasting discrepancies.
Variables where [] exist must be specified for your configuration.
A team treated this task as a project and produced requirements, scope and effort.
Code:
: This is the NT batch file to schedule on the DC:
: Script #1, the one to be scheduled, is an NT batch file.
: go.cmd
: Pull NIS information from NIS slave to a text file on the AD DC.
SSH [unix yp master or slave hostname] -l ypadsync -i d:\winnt\ad_sync\id_dsa -C "ypcat passwd|unix2dos" > d:\winnt\ad_sync\passwd
: Invoke script that parses the NIS information into simple text file.
start /w yppasswd.pl
: Invoke the script that compares NIS account information with AD accounts. Add accounts where necessary, inform where there are inconsistencies and inform when AD contains an account that no longer exists in NIS, so a human can delete the AD account.
actual.pl
#Script 2 actual.pl
#!/usr/bin/perl
=Program overview
VERSION AS OF 9/5/2002, 1600
The object of this program is to keep the account status of accounts in AD and NIS the same. This program compares the state of accounts in AD and a light version of yppasswd. The program is divided into three parts. The first part established all the variables. This sets up the search object used to get the ADsPath of every account in AD and create a table with them in it.
The second part reads the account information from AD and NIS and places it in two arrays -- @YP and @AD.
The third part of the program compares the entries in the two arrays. It starts at the top of @AD and compares each entry agains every entry in @YP. It then goes the other way. The accounts found not to sync up are remembered to a sysadmin through an e-mail. If the account does not exist in AD, but it does in NIS, it is created in the Fresh_Users_from_NIS OU so that it can be dealt with later.
This program requires that the Bundle-libnet be installed. An easy way to install it is with PPM. The sript has only been tested using ActivePerl from ActiveState.
Structure of variables
@AD[x]=userprincipalname (AD username), fullname, locked, sAMAccountName (compatibility name, the one used by NT),account path (path to the account in AD)
:@YP[x]=username,fullname,locked
The exclusion file format is one username per line, that is all.
=cut use Win32::OLE; use Win32::OLE::Enum; use net::smtp; # Constants my $smtpserver="[ip address of smtp server]"; my $adminmail="[e-mail address]"; my $defaultpwd="[default AD password for account delivery]"; open(YPFILE,'<','d:\winnt\ad_sync\passwdlite'); open(EXCLUSION,'<','d:\winnt\ad_sync\exclusion.ad'); # SMTP connection. $smtp=Net::SMTP::->new($smtpserver); # Connect to AD. $userbucket = Win32::OLE->GetObject ("LDAP://jax.org/OU=Fresh_Users_from_NIS,dc=jax,dc=org") or die "Unable to connect to domain.\n"; # Create search object. $searchconn=Win32::OLE->new("ADODB.Connection") or die Win32::OLE->LastError(); $searchcont=Win32::OLE->new("ADODB.Command") or die Win32::OLE->LastError (); $searchconn->{Provider}="ADsDSOObject" or die "unable to set search provider"; $searchconn->{Open}="","dc=jax,dc=org",""; if ($searchconn->{state} ne 1) { die "search not active, unable to continue"; } $searchcont->LetProperty("ActiveConnection",$searchconn); $searchcont->SetProperty("Properties","Page Size",9999); $searchcont->{CommandText}="<LDAP://jax.org/dc=jax,dc=org>;(& (ObjectClass=User)(ObjectCategory=person));ADsPath;subtree"; # Execute search. $testasdftest=$searchcont->execute() or die Win32::OLE->LastError(); ###### Get information from AD. ###### $index=0; #start counter while (!$testasdftest->EOF) { $accountpath=$testasdftest->fields->item(ADsPath)->Value; $i=Win32::OLE::->GetObject($testasdftest->fields->item(ADsPath)- >Value); # Adjust for accounts that have their sAMAccountName (NT username) set and not the userprincipalname (AD username). if ($i->{userprincipalname} eq undef or $i->{userprincipalname} eq "") { $userprincipalname=$i->{sAMAccountName}; } else { $userprincipalname=$i->{userprincipalname}; } # Adjust for accounts that have no fullname entry. if ($i->{fullname} eq undef or $i->{fullname} eq "") { $fullname=$userprincipalname; } else { $fullname=$i->{fullname}; } # Check and see if account is disabled or not. Account is disabled if AccountDisabled is not equal to 0; we want a true or false. #print $i->{AccountDisabled}; if ($i->{AccountDisabled} eq 0) { $locked=0; } else { $locked=1; } # Add to array of users the entry for this user. $AD[$index]=["$userprincipalname","$fullname","$locked","$i-> {sAMAccountName}","$accountpath"]; # Advance counter and query results. #print $index," ",$userprincipalname,"\n"; $index ++; $testasdftest->movenext; } ###### Get information from YP file. ###### # Start counter. $index=0; # Set temp = to anything except undef. $temp=1; while ($temp ne undef) { $temp=<YPFILE>; chomp $temp; # Write user entry into YP array. $YP[$index]=[split(/:/,$temp)]; # Advance counter. $index ++; } ###### Get information from exclusion file. ###### $exclusion=1; $counterexc=0; while ($exclusion ne undef) { $exclusion=<EXCLUSION>; chomp $exclusion; @exclusion_array[$counterexc]=split(/:/,$exclusion); $counterexc ++; } #print @exclusion_array,"\n"; ###### ADD ALL OBJECTS IN THE FRESH USER BUCKET TO THE EXCLUSION LIST. ##### # Create search object. $searchconn1=Win32::OLE->new("ADODB.Connection") or die Win32::OLE->LastError(); $searchcont1=Win32::OLE->new("ADODB.Command") or die Win32::OLE-≶LastError(); $searchconn1->{Provider}="ADsDSOObject" or die "unable to set search provider"; $searchconn1->{Open}="","dc=jax,dc=org",""; if ($searchconn1->{state} ne 1) { die "search not active, unable to continue"; } $searchcont1->LetProperty("ActiveConnection",$searchconn1); $searchcont1->SetProperty("Properties","Page Size",9999); $searchcont1->{CommandText} ="<LDAP://jax.org/OU=Fresh_Users_from_NIS,dc=jax,dc=org>;(& (ObjectClass=User)(ObjectCategory=person));ADsPath"; # Execute search. my @thingtoadd=undef; $testasdftest1=$searchcont1->execute() or die Win32::OLE->LastError(); my $ad_exc_list=0; while (!$testasdftest1->EOF) { $accountpath=$testasdftest1->fields->item(ADsPath)->Value; $i1=Win32::OLE::->GetObject($testasdftest1->fields->item(ADsPath)- >Value); $userprincipalname1=$i1->{userprincipalname}; @thingtoadd=split('@',$userprincipalname1); @exclusion_array[$counterexc]=$thingtoadd[0]; $counterexc ++; $testasdftest1->movenext; $ad_exc_list ++; } ###### Active Directory information processing. ###### print "\n\nActive Directory Information\n\n"; # Loop through entire AD array. foreach $index (0..$#AD) { # If username is in the exclusion list, skip. if (excheck() eq 1) { next; } else { $notice=1; # Compare current entry in AD to every entry in YP foreach $index2 (0..$#YP) { if (join('@',$YP[$index2][0],"jax.org") eq $AD[$index][0] or $YP[$index2][0] eq $AD[$index][3]) { # If the account exists, avoid sending e-mail about it. $notice=0; if ($AD[$index][2] ne $YP[$index2][2]) { ## E-MAIL OUT SAYING THAT ACCOUNT STATE DOES NOT MATCH, actually, to avoid sending the same notice twice. Let the YP processing section take care of this. ## print "username $AD[$index][0] exists in both but has different locked states\n"; } else { #DEBUG print "$AD[$index][0] exists in both\n"; } } else { # Here just in case any action is needed here, there should not be any. } } # If account exists in AD but not YP send mail. if ($notice eq 1) { $smtp->mail(); $smtp->to("$adminmail"); $smtp->data(); $smtp->datasend("From: YP-AD Script <yp-ad@jax.org>\n"); $smtp->datasend("To: it-core-rotation\n"); $smtp->datasend("Subject: Account exists in AD but not NIS\n"); $smtp->datasend("$AD[$index][0] in $AD[$index][4] has no matching NIS account\n"); $smtp->datasend(); $smtp->dataend(); } } } ###### Yellow pages information processing. ###### # This is the same thing as for AD but reversed and with an account creation if account does not exist in AD. print "\n\nYellow Pages Information\n\n"; my $skipped=0; my $created=0; my $notcreated=0; my $excheckypcounter=0; foreach $index (0..$#YP) { if ($YP[$index][0] ne undef) { $create=1; if (excheckyp()) { $skipped ++; $create=0; } else { # print "$YP[$index][0]:$YP[$index][1]:$YP[$in dex][2]\n"; foreach $index2 (0..$#AD) { ##### Problem must be here!!!!!!!!! if (join('@',$YP[$index][0],"jax.org") eq $AD[$index2][0] or $YP[$index][0] eq $AD[$index2][3]) { $create=0; if ($YP[$index][2] ne $AD[$index2][2]) { ## E-MAIL OUT SAYING THAT ACCOUNT STATE DOES NOT MATCH. ## $smtp->mail(); $smtp->to($adminmail); $smtp->data(); $smtp->datasend("From: YP-AD Script <yp-ad@jax.org>\n"); $smtp->datasend("To: it-core-rotationn"); $smtp->datasend("Subject: Account status does not match\n"); $smtp->datasend("$YP[$index][0]\n"); if ($YP[$index][2] eq 0) { $smtp->datasend("NIS = Enabled\nAD = Disabled"); } else { $smtp->datasend("NIS = Disabled\nAD = Enabled"); } $smtp->datasend(); $smtp->datasend(); print "\nAccount status on $YP[$index][0] does not match\n"; } } else { } } if ($create eq 1) { $created ++; chomp $YP[$index][1]; @nameparts=split(/ /,$YP[$index][1]); # print length($YP[$index][1]); if (length($YP[$index][1]) > 50) { substr($YP[$index][1],50)=""; } # print length($YP[$index][1]); # print "Nameparts: $nameparts[0] $nameparts[$#nameparts]\n"; $newuser = $userbucket->Create("User",join('',"cn=",$YP[$index][1]," ",$YP[$index][0])); $newuser->{userprincipalname}=join ('@',$YP[$index][0],"jax.org"); $newuser->{sAMAccountNAme}=$YP[$index][0]; $newuser->SetInfo; # DEBUG print Win32::OLE->LastError(); $newuser->GetInfo; $newuser->{firstname}=$nameparts[0]; $newuser->{lastname}=$nameparts[$#nameparts]; $newuser->{fullname}=$YP[$index][1]; $newuser->{setpassword}=$defaultpwd; print "Account Created: $newuser->{Fullname}:$newuser-> {userprincipalname}:$YP[$index][0]\n"; $newuser->SetInfo; # DEBUG print Win32::OLE->LastError(); $smtp->mail(); $smtp->to($adminmail); $smtp->data(); $smtp->datasend("From: YP-AD Script <yp-ad@jax.org>\n"); $smtp->datasend("To: it-core-rotation\n"); $smtp->datasend("Subject: New Account Created\n"); $smtp->datasend("$YP[$index][1] was created in the Fresh_Users_from_NIS bucket\n"); $smtp->datasend(); $smtp->dataend(); } else { } } } } print "Summary of Actionsn"; print "Skipped, Created, Not Created, Num in AD, Num in AD exclude list, num in YP\n"; print "$skipped, $created, $notcreated, $#AD, $ad_exc_list, $excheckypcounter\n"; $smtp->mail(); $smtp->to("elijahmm@jax.org"); $smtp->data(); $smtp->datasend("From: YP-AD Script <yp-ad@jax.org>\n"); $smtp->datasend("To: it-core-rotation\n"); $smtp->datasend("Subject: ypad summary\n"); $smtp->datasend("Skipped, Created, Not Created, Num in AD, Num in AD exclude list, num in YP\n"); $smtp->datasend("$skipped, $created, $notcreated, $#AD, $ad_exc_list, $excheckypcounter\n"); $smtp->datasend(); $smtp->dataend(); # Close SMTP connection. $smtp->quit; sub excheck { foreach $excheckcounter (0..$#exclusion_array) { if (join('@',$exclusion_array[$excheckcounter],"jax.org") eq $AD[$index][0] or $exclusion_array[$excheckcounter] eq $AD[$index][3]) { # print "Excluded $exclusion_array[$excheckcounter]\n"; return 1; } } } sub excheckyp { foreach $excheckcounter (0..$#exclusion_array) { if ( $exclusion_array[$excheckcounter] eq $YP[$index][0]) { # print "Excluded $exclusion_array[$excheckcounter]\n"; $excheckypcounter ++; return 1; } } } sub debugexclusion { foreach $excheckcounter (0..$#exclusion_array) { if ( $exclusion_array[$excheckcounter] eq $YP[$index][0]) { print ":$exclusion_array[$excheckcounter]\n"; } else { print "Not found but should have been\n"; } } }
# Script #3
=program overview
Create a lite password file from yppasswd.
new file format
username:fullname:locked
The exclusion file format is one username per line, that is all.
=cut open(YPPASSWD,'<','d:\winnt\ad_sync\passwd'); open(PASSWDLIGHT,'>','d:\winnt\ad_sync\passwdlite'); open(EXCLUSION,'<','d:\winnt\ad_sync\exclusion'); $temp=1; $exclusion=1; $counter=0; while ($exclusion ne undef) { $exclusion=<EXCLUSION>; chomp $exclusion; @exclusion_array[$counter]=split(/:/,$exclusion); $counter ++; } while ($temp ne undef) { $temp=<YPPASSWD>; chomp $temp; @YP=split(/:/,$temp); @temp2=split(/-/,$YP[1]); if (excheck() eq 1) { next; } else { if ($temp2[0] eq "LOCKED") { $locked=1; } else { $locked=0; } if ($YP[4] eq undef) { $YP[4]=$YP[0]; } @comma_test=split(',',$YP[0]); $YP[0]=$comma_test[0]; foreach $comma_counter (1..$#comma_test) { $YP[0].=$comma_test[$comma_counter]; } @comma_test=split(',',$YP[4]); $YP[4]=$comma_test[0]; foreach $comma_counter (1..$#comma_test) { $YP[4].=$comma_test[$comma_counter]; } if ($YP[0] ne undef) { print PASSWDLIGHT "$YP[0]:$YP[4]:$locked\n"; # print "$YP[0]:$YP[4]:$locked\n"; } } } sub excheck { foreach $excheckcounter (0..$#exclusion_array) { if ($YP[0] eq $exclusion_array[$excheckcounter]) { return 1; } } }
Example of exclusion lists:
File exclusion.ad:
File exclusion:
Start the conversation
0 comments