LDAP Books
About the best introductory book I've found is also one of the more recent, Gerry Carter's LDAP Administration(O'Reilly), which covers alot of ground for a book that's supposed to focus mostly on OpenLDAP (here's a secret, while OpenLDAP is used as an example LDAP directory through the book, Gerry actually provides a pretty balanced presentation of generic LDAP, including some space on Active Directory). For IT managers, the recently updated Understanding and Deploying LDAP Directory Services (Addison-Wesley) provides the best resource for architectural issues. Those managing a Sun Directory environment will find the Sun Blueprints series title LDAP in the Solaris Operating Environment somewhat helpful, although most of the material there appears to be a rehash of the manuals (which are actually pretty good as software manuals go, the Deployment Guide and Command Reference, are must-reads).
For a directory administrator like myself, unfortunately, there is currently no advanced book to help with the practical day to day problems of synchronizing (which is really just a function of "transforming" and then "diffing" entries) directories, writing connectors to non-LDAP systems and best practices for data management. While all the books currently out there provide examples of code using the Netscape SDKs and PerLDAP, these tools are now pretty long in the tooth, and don't have the price/performance punch of the current Sun Java/JNDI and the pure Perl Net::LDAP (a/k/a perl-ldap) offerings. To Gerry Carter's credit Net::LDAP gets good coverage in his book (as it does in Robbie Allen's older book, only marketing forces can explain why he went pure VBScript in his newer writings). For OpenLDAP administrators there still is no book with coverage of tuning the Berkeley DB backend, which as I've pointed out elsewhere on this blog is a critical task if that product is to be used in an enterprise environment. What is missing, and sorely needed, is an "LDAP Administrator's Cookbook" and a companion "Advanced LDAP Directory Management" to cover best practices.
I am, of course, open to any offers.
Using Spreadsheet::ParseExcel
My problem was that the doc for Spreadsheet::ParseExcel was opaque to me, as are most man pages on UNIX systems. Maybe it's just that I'm dense, or don't have enough patience. Anyway, after much experimentation, I finally "cracked the code" and was able to come up with the following little script to convert an Excel spreadsheet into a delimited text file.
#!/usr/bin/perl
# parse_excel.pl Read an Excel spreadsheet and print its contents to
# a bar delimited file
# Created 10/19/04 by P Lembo
use strict;
use Spreadsheet::ParseExcel;
my $HOME = "/u01/home/ldapcon";
my $xlsFile = "$HOME/data/admin/ArrowOIDRegistry.xls";
my $txtFile = "$HOME/data/admin/oid-registry.txt";
my $wkBook = Spreadsheet::ParseExcel::Workbook->Parse($xlsFile);
open FH, ">$txtFile" or die $!;
my $nuSheets = $wkBook->{SheetCount};
print "There are $nuSheets sheets in this workbook\n";
# Iterate through worksheets
for ( my $iSheet=0; $iSheet < $wkBook->{SheetCount}; $iSheet++ ) {
my $wkSheet = $wkBook->Worksheet($iSheet);
my $wksName = $wkSheet->{Name};
print "Name of worksheet is $wksName\n";
# Now iterate through a worksheet by row
for (my $iRow=0; $iRow < $wkSheet->{MaxRow}; $iRow++ ) {
my $colNu = $wkSheet->{MaxCol};
# Loop through the columns in the row
for (my $iCol=0; $iCol < $wkSheet->{MaxCol}; $iCol++ ) {
# Pull out the cell value
my $wksCell = $wkSheet->{Cells}[$iRow][$iCol];
# Print each value
print FH $wksCell->Value, if ($wksCell);
# Print a delimiter after each value except the last in the row
print FH "\|", if ($iCol < ($colNu - 1));
}
# Print a newline after each row
print FH "\n";
}
}
print FH "\n":
close FH;
__END__;
Mixing Metaphors: LDAP and ADSI at work
So, without further ado, here is the code:
#!perl -w
# adpassreset.cgi Reset Active Directory passwords
# 10/1/04 by P Lembo
# Give choice of unlocking, forcing or both (default)
# version 0.01
use strict;
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use URI::URL;
use Net::LDAP;
use Net::LDAP::Entry;
use Win32::OLE;
$Win32::OLE::Warn = 3;
my $q = CGI->new;
require "../etc/ldapapp.conf";
our ($adsTestHost,$adsQualHost,$adsProdHost,$adsTestPath,
$adsQualPath,$adsProdPath,$adsUsr,$adsPass);
my @adshosts = ("$adsTestHost","$adsQualHost","$adsProdHost");
my @adsbases = ("$adsTestPath","$adsQualPath","$adsProdPath");
my $newpass = 'yellow';
my $webhost = "localhost";
my $webport = "80";
if ($q->param('Review')) {
give_status($q);
}
elsif ($q->param('Reset Account')) {
reset_user($q);
}
else {
show_main($q);
}
sub show_main {
print $q->header;
print $q->start_html(-title=>'Active Directory Account Reset',
-style=>{'src'=>'/styles/ldapadmin.css'}
);
print $q->h1("Active Directory Account Reset");
my $action = $q->url;
print $q->start_form(-method=>'POST',
-action=>$action,
);
print $q->h4("Select Server");
print $q->popup_menu(-name=>'adshost',
-values=>\@adshosts
);
print $q->h4("Choose Domain");
print $q->popup_menu(-name=>'adsbase',
-values=>\@adsbases
);
print $q->h4("Enter User ID");
print $q->textfield(-name=>'userid',-size=>5);
print $q->p();
print $q->submit('Review');
print $q->reset("Cancel");
print $q->end_form;
print $q->p();
print $q->a( { -href => "/acctadmin/index.html" },
"Back To Active Directory Tools");
print $q->end_html();
} # main
sub give_status {
print $q->header(-charset=>'UTF-8');
print $q->start_html(-title=>'Active Directory Account Reset',
-style=>{'src'=>'/styles/ldapadmin.css'}
);
print $q->h1("Active Directory Account Reset");
print $q->h3("Confirm User Information");
my $action = $q->url;
print $q->start_form(-method=>'POST',
-action=>$action,
);
my $adshost = $q->param('adshost');
my $adsbase = $q->param('adsbase');
my $userid = $q->param('userid');
print $q->hidden(-name=>"adshost",value=>"$adshost");
print $q->hidden(-name=>"adsbase",value=>"$adsbase");
print $q->hidden(-name=>"userid",value=>"$userid");
print $q->p("Working on $adshost");
print $q->p("$adsbase");
$adsUsr = "$adsUsr,$adsbase";
my $basedn = $adsbase;
my @attrs = qw(displayname cn userprincipalname samaccountname);
my $filter = "(|(samaccountname=$userid)(cn=$userid))";
my $ldap = Net::LDAP->new($adshost, version =>'3');
my $mesg = $ldap->bind($adsUsr, password => $adsPass);
$mesg = $ldap->search(base => $basedn,
scope => 'sub',
filter => $filter,
attrs => \@attrs
);
if ( $mesg->count <1 ) {
print $q->h4("No results returned!");
} # if
elsif ( $mesg->count >1 ) {
print $q->p("Please choose from one of the entries listed and retry");
while (my $entry = $mesg->shift_entry()) {
my $userdn = $entry->dn;
my $cn = $entry->get_value('cn');
my $displayname = $entry->get_value('displayname');
print $q->p("$userdn");
print $q->p("UserID: $userid",$q->br, "Full Name: $displayname");
}
}
else {
my $entry = $mesg->shift_entry();
my $userdn = $entry->dn;
print $q->hidden(-name=>"userdn",value=>"$userdn");
my $cn = $entry->get_value('cn');
print $q->hidden(-name=>"cn",value=>"$cn");
my $displayname = $entry->get_value('displayname');
print $q->hidden(-name=>"displayname",value=>"$displayname");
my $userprincipalname = $entry->get_value('userprincipalname');
print $q->hidden(-name=>"userprincipalname",value=>"$userprincipalname");
print $q->p("$userdn");
print $q->p("UserID: $userid",$q->br, "Full Name: $displayname");
# Bind using ADSI to check flags
my $ldapObj = Win32::OLE->GetObject('LDAP:');
my $usrObj = $ldapObj->OpenDSObject("LDAP://$adshost/$userdn","$adsUsr", "$adsPass", 1);
if ($usrObj->AccountDisabled == 1) {
print $q->p("Account is disabled, contact System Administrator");
}
if ($usrObj->{IsAccountLocked} == 1) {
print $q->p("Account is locked, reset will unlock");
}
else {
print $q->p("Account not locked");
}
print $q->p();
print $q->submit('Reset Account');
print $q->reset("Cancel");
}
$ldap->unbind;
print $q->end_form;
print $q->p();
print $q->a( { -href => '/acctadmin/adacctreset.cgi' }, 'Try Again');
print $q->p();
print $q->a( { -href => "/acctadmin/index.html" }, "Back To Help Desk Tools");
print $q->end_html;
} # status
sub reset_user {
print $q->header(-charset=>'UTF-8');
print $q->start_html(-title=>'Active Directory Account Reset',
-style=>{'src'=>'/styles/ldapadmin.css'}
);
print $q->h1("Active Directory Account Reset");
print $q->h3("Confirming Changes");
my $adshost = $q->param('adshost');
my $adsbase = $q->param('adsbase');
my $userdn = $q->param('userdn');
my $cn = $q->param('cn');
my $displayname = $q->param('displayname');
my $userprincipalname = $q->param('userprincipalname');
print $q->p("Working on $adshost");
print "$userdn\n";
print "$displayname
\n";
$adsUsr = "$adsUsr,$adsbase";
# Bind using ADSI to reset password
my $ldapObj = Win32::OLE->GetObject('LDAP:');
my $usrObj = $ldapObj->OpenDSObject("LDAP://$adshost/$userdn","$adsUsr", "$adsPass", 1);
# Check for disabled account
if ($usrObj->AccountDisabled == 1) {
print $q->p("Account is still disabled, contact System Administrator");
}
# Unlock account
if ($usrObj->{IsAccountLocked} == 1) {
$usrObj->{IsAccountLocked} = 0;
$usrObj->SetInfo;
print $q->p("Account unlocked");
}
else {
print$q->p( "Account not locked");
}
# Reset password
$usrObj->SetPassword($newpass);
$usrObj->SetInfo;
print $q->p("Password reset to $newpass");
# Forces change password on next logon
$usrObj->Put('pwdLastSet', 0);
$usrObj->SetInfo;
print $q->p("User must change password at next logon");
print $q->p();
print $q->a( { -href => '/acctadmin/adacctreset.cgi' }, 'Try Again');
print $q->p();
print $q->a( { -href => "/acctadmin/index.html" }, "Back To Active Directory Tools");
print $q->end_html;
} # reset
__END__;
Red Hat buying Netscape Directory, Certificate Servers
As a fairly satisfied user of Sun's rebranded version of the Netscape Directory server (which they acquired after the dissolution of the iPlanet partnership), I've kept a careful eye on the Netscape version (which has sometimes been hard to find, buried somewhere among a pile of broken links to technologies Netscape no longer supports). It's been pretty clear for a long time that AOL wasn't even trying to market these Netscape properties, and at least from a "maintaining balance in the universe" standpoint its good to see they will be going to a home where they won't be wasted.
The other good part is that these products will soon be open sourced, and made available for deployment on advanced Linux systems like the upcoming Red Hat Enterprise 4. AOL/Netscape did not support Linux on its latest versions, and Sun has only paid lipservice to the open source O/S (They claim their latest version, rebranded "Java System Enterprise Directory", works on the now antiquated Red Hat Enterprise 2.1. Right.). At work this has been an issue because we're now engineering a probable move of most of our web tier to Linux on Intel. Up until now it looked like the directories would have to stay on Solaris/Sparc.
I'll take a careful look at Red Hat's next release of the Netscape Directory, especially because the schemae and access controls in the Sun and Netscape products are almost identical and as a result the transition in our enterprise would be fairly painless. Interesting contrast here. Some companies. like Oracle, AOL and Novell (remmember WordPerfect? Or UNIXWare? DRDOS?), gobble up others and run them into the ground (beware, PeopleSoft and SuSE customers), others, like Red Hat, give them new life (think Cygwin here).
