Novell is now a part of Micro Focus

How to Use Perl, Python, and PHP to Access NDS eDirectory 8.5 via LDAP

Articles and Tips: article

Mike Richichi
Assistant Director of Academic Technology
Drew University
mrichich@drew.edu

01 Aug 2001


This article outlines the considerations for developing directory-enabled applications with a scripting language. We also consider the concepts of LDAP that are important to working with any API, but with relevance to the methodology of accessing LDAP via a script language.


Topics

NDS eDirectory, Perl, PHP, Python, LDAP, scripting languages

Products

NDS eDirectory

Audience

developers

Level

intermediate

Prerequisite Skills

familiarity with LDAP, and NDS eDirectory

Operating System

Linux, win32

Tools

systems configured to run Perl, Python, and PHP scripts

Sample Code

yes

Introduction

This article gives an overview of configuring and using Perl, PHP, and Python to access NDS eDirectory via LDAP, and how to authenticate to LDAP, perform queries, and do meaningful things with the results in both command-line and CGI programs. Although Linux will be the reference platform in this article, the concepts are extensible to other Unix systems and any platform that can use the scripting languages above.

You may have web servers running on non-NetWare platforms that require access to the directory for Web-based applications. Also, your environment may include administrators or users that are used to programming with scripting languages on Unix or Linux-based systems. The popularity of NDS eDirectory means that many organizations now have a powerful directory service at their disposal, to be used to provide a common information store across the enterprise that is flexible, scalable, replicated, and secure. NDS eDirectory 8.5's support of LDAP is well-integrated and exposes nearly all NDS functionality via the LDAPv3 specification, thus allowing any LDAP-enabled program to utilize NDS eDirectory. This allows unprecedented flexibility in using NDS in your environment.

A perfect example is the university where I work. We have multiple servers running NDS eDirectory 8.5, with all faculty, staff, and students having an NDS eDirectory account. As befits the university environment, we have multiple platforms for servers (NetWare, Linux, Solaris, Tru64, OpenVMS, NT/2000) to provide a comprehensive set of network services for all people on campus. NDS eDirectory not only stores all the people on campus, but has objects for every course offered in a term, and students and faculty have assignments to these objects. We provide many services based on that information, some of it through Web-based interfaces. In addition, we are required to create hundreds of new accounts every year by interfacing with a legacy administrative interface that provides flat files for this purpose. We have found that using LDAP-based solutions through common scripting languages is an ideal solution.

This article outlines the considerations for developing directory-enabled applications with a scripting language. We also consider the concepts of LDAP that are important to working with any API, but with relevance to the methodology of accessing LDAP via a script language. Furthermore, you will see a brief overview of installation and configuration of LDAP libraries in Perl, PHP, and Python, and sample code in each of these languages to do simple directory operations. Finally, I include one additional Perl program that can be used to send email warnings of upcoming password expirations to your users.

Why Use a Scripting Language?

There are distinct advantages and disadvantages to scripting versus compiled programs:

Rapid Development

Scripting languages are popular because there's no compilation overhead in the development cycle, and because they usually have simple interfaces that would require many lines of code in a compiled language. For instance, PHP builds its LDAP support on a C SDK, and its LDAP calls abstract much of the buffer and connection handle management that you would have to do yourself if you were programming to the C SDK directly. Perl and Python behave similarly-the access methods to LDAP are closer to the native data access methods of the language-although understanding of the LDAP structures is important.

Web Integration

Perl, PHP, and Python are all very popular CGI scripting languages. There are other available modules in these languages for many common CGI operations-most notably, database access. The combination of LDAP access to NDS eDirectory and database access to an enterprise database for dynamic Web content is an extremely powerful one. It can provide rich dynamic content backed up by the directory, and allow for things like restricting access to database data by LDAP/eDirectory authentication, as well as even some data interchange and synchronization between LDAP and the database. And of course, designing Web interfaces to NDS eDirectory data means that data is available across operating system platforms from anywhere in the world.

Language Acceptance

Perl, Python, and PHP are well-understood, widely-accepted languages with large and loyal followings. There is a broad talent pool of programmers well-versed in one or even all of these scripting languages. Many of these programmers may not be familiar with the traditional NetWare environment and may have no desire to learn it for whatever reason (we won't judge their rationality in this article.) Allowing these programmers to work in an environment they're comfortable in (say, a Perl programmer on a Linux system), using LDAP (which they may have experience with, say, using OpenLDAP), means they can be utilizing NDS eDirectory without having to learn NDS eDirectory programming on a NetWare server or in the Win32 environment. This may mean more efficient development of applications that leverage the power of the directory-and that is the important part.

Platform Independence

You may be running NDS eDirectory in a pure Unix environment, with no NetWare whatsoever. Thus using standard NetWare development solutions for CGI programming like Novell Script or Java Server Pages may not be possible. You may decide you want to devote the resources of your NetWare boxes to file, print, and NDS, and not implement application programs on them. You can use Unix or Linux systems, for instance, to do that work, but there is limited availability of NDS APIs on these platforms. LDAP provides an access method into the Directory that is easily available on these systems. Again, the advantages of NDS eDirectory with the benefit of working on the platforms you already have.

Of course it's not all wine and roses with scripting languages. You will have to deal with performance and compatibility concerns, as well as the dynamic state of the codebase.

Performance

In general, scripting languages may not be as fast at runtime as compiled languages. Since you will have some overhead due to LDAP communications itself and execution time of LDAP searches and other work on the NDS eDirectory server, the overhead of scripting may be small compared to the work LDAP itself is doing. Not to mention, the performance of scripting languages is increasing all the time (most are now precompiling into an intermediate bytecode that is actually quite fast). If, however, your application requires the fastest performance, you will need to at least consider a compiled language like C or C++.

SDK Issues and LDAP v2 vs. v3

Also, you will possibly have issues with SDKs and levels of LDAP compatibility. It's possible that the API you're using in a script language won't support all the features of LDAPv3. It, therefore, may not be able to work with LDAP extensions that NDS uses to expose certain features of NDS, may not be able to support SSL binds (more on that later), and may not be able to follow LDAP referrals (which could have implications in a distributed tree design). It's important to note that although NDS eDirectory 8.5 is fully LDAPv3 compliant, it also supports full LDAPv2 functionality as a subset of that and interoperates fully with any v2 client. All the implementations in this article allow complete ability to modify, search, update, and delete all attributes and values exposed by LDAP on any NDS object in your directory tree, via authenticated access controlled by NDS access rights. You may not, however, be able to do things like schema manipulation, partitioning, or other NDS-specific operations from your selected scripting language. I will try and point out these limitations where they apply. I believe that specifically for CGI programming, it's unlikely you'll want to modify the schema, or partition the NDS eDirectory tree anyway, so these limitations may not be relevant to you.

Dynamic Nature of the Codebase

One last consideration about the LDAP APIs available to scripting languages is that they are open source projects developed by programmers who are contributing their time and resources to the project. These APIs may be considered by their authors to be alpha or beta quality. In practice, I have found them to be as reliable as many commercial packages, but a bit more sensitive to changing specifications as the products mature and develop. It's possible that some of the code I present here may not work as written in the near future-that's also true with commercial APIs from many vendors as well. Also, remember there is no official Novell DeveloperNet support for these products. The volunteer sysops in the developer forums, however, may be able to answer questions. There is also a plethora of resources available on the Web to look for help, but opening a developer support incident is not an available option at this time.

With all that said, I'll assume I haven't scared you away and that you actually want to develop applications utilizing one of these languages. This article is going to treat Perl, Python, and PHP as different dialects of LDAP itself, so let's start with some LDAP fundamentals.

What You Need to Know About LDAP

I will not be providing a complete technical introduction to LDAP-for that, you really need to read the LDAP Developer's Guide published by Novell Press. It would be folly to try and redo what they've already accomplished. Seriously, go read that whole book right now. I'll wait.

There. Good book, wasn't it? Okay, since you've already properly read the book, and are just reviewing for the exam, it's okay to read my crib notes of it.

LDAP is about Objects, Attributes, Values, and Schema. An object is the fundamental unit of storage in an LDAP tree. Some objects are "container" objects-these contain other objects, possibly other container objects. The hierarchy of container objects and their layout defines the structure of the directory tree. All objects have attributes, which are fields that store information about the objects. Attributes can have one or more values, which are data about the object. What attributes go with what objects, what classes of objects there are, what structure the values can take in an attribute, what objects can contain other objects-basically, all characteristics of the directory itself-are controlled by the schema. My friend Gary Porter likes to refer to the schema as a big cookbook-it's the reference for creating any NDS object, it will tell you exactly how to do it. (He would also say, "How cool is that?" but I digress.)

While schema is important, it's also important to consider the sociopolitical and geographic implications of your directory tree design-considerations that are far beyond the scope of this article, but will make a tremendous difference to the usability of your tree and its data. However you do your NDS tree design, try to do it so that you can leverage the hierarchical structure to maximize the efficiency of searching for specific things. Also, consistency in data is important. For instance, we try to make sure all user objects have values in the attributes givenName, surname, and fullName, as well as a valid address in the mail attribute. It's also important to us that the names be properly capitalized (no ALL CAPS) so that software doesn't have to waste time making sure the values are pretty when displayed. In addition, we need to ensure that email addresses are in a valid format so you know that you can always email a user by reading the value from the mail attribute of the user object. Such consistency will make your life easier in the long run-and is easy to enforce at the data entry level with scripting languages.

The current version of the LDAP protocol that NDS eDirectory supports is LDAP v3. LDAP v3 is defined in RFC 2251, and also RFCs 2252-2256 which detail definitions for data types and for filters. Now, reading RFCs may not be your idea of a fun thing to do on a Friday night, but they are really worth reading, because you'll understand the fundamentals of what you're doing, and everything else will build from that foundation. Anyway, a juicy paragraph from RFC2251 states:

The general model adopted by this protocol is one of clients performing protocol operations against servers. In this model, a client transmits a protocol request describing the operation to be performed to a server. The server is then responsible for performing the necessary operation(s) in the directory. Upon completion of the operation(s), the server returns a response containing any results or errors to the requesting client.

And that's what you'll be doing the whole time you're using LDAP. You'll send a request to a server (say, a search request or an object change request). The server will take that request, do things with it, and return a response, which will either be a confirmation of an action, the data you requested, or an error because something went wrong. Your program will take that result and do things with it, possibly using the response to formulate more requests, and so on, until you've accomplished whatever it is you're trying to do.

Before you talk to an LDAP server, you should introduce yourself. Actually, as of LDAPv3, you can just start making requests, and the server won't get offended, but it will treat you like it doesn't know you. In technical terms, a session with an LDAP server is started with a bind operation, where you present credentials (usually a username and password, but LDAP v3 allows for other credentials) to the server, and it authenticates you as that user with appropriate access rights. If you don't bind first, LDAPv3 will automatically perform an anonymous bind, which means the LDAP server will treat you as an unprivileged user with whatever public access to the directory. Actually, NDS eDirectory allows anonymous binds to occur as an LDAP Proxy user in NDS, which can have more or fewer rights than [Root] or [Public] as appropriate.

I suggest you do anonymous binds to your tree and see what kinds of data are being exposed. You may have confidential items like Employee ID numbers, SSNs, home addresses, and the like, that should not be public information. Universities have to comply with FERPA, which limits what information can be public about students and allows those students to opt out of sharing directory information. Luckily, once such things are discovered, NDS eDirectory 8.5 makes it easy to limit access with careful application of ACLs, especially inherited ACLs on specific attributes, while allowing users who need the data access to it (I think there's an AppNote in that). Again, these are considerations you must think through for any information system and have policy and legal implications beyond what I'm prepared to write about here.

So, we've bound to an LDAP server, and now want to do "stuff." LDAPv3 defines 9 operations: bind and unbind, search, modify, add, delete, modify DN, compare, and abandon; as well as an extended operation that can do additional things (in the NDS eDirectory case, things like partitioning) in a standards-based, extensible fashion. In this article, we'll mostly do searches, modifies, adds, and deletes (besides binding and unbinding). The other operations are important as well, however.

When a request is made to an LDAP server, data is formatted in a way defined by the LDAPv3 RFCs, then sent to the server where it is parsed, and then data is returned in RFC-defined formats. All LDAP APIs and script language implementations do most of this dirty work for you, so you don't have to worry about things like allocating memory to create request and result structures and parsing them. Still, being aware of how it works will help you understand LDAP in any language you use to access it. For instance, here's the RFC 2251 definition of an LDAP v3 search operation, which you will be using frequently to perform operations on the tree:

SearchRequest ::= [APPLICATION 3] SEQUENCE {
      baseObject      LDAPDN,
      scope           ENUMERATED {
         baseObject              (0),
         singleLevel             (1),
         wholeSubtree            (2) },
      derefAliases    ENUMERATED {
         neverDerefAliases       (0),
         derefInSearching        (1),
         derefFindingBaseObj     (2),

         derefAlways             (3) },
      sizeLimit       INTEGER (0 .. maxInt),
      timeLimit       INTEGER (0 .. maxInt),
      typesOnly       BOOLEAN,
      filter          Filter,
      attributes      AttributeDescriptionList }

   Filter ::= CHOICE {
      and             [0] SET OF Filter,
      or              [1] SET OF Filter,
      not             [2] Filter,                           
      equalityMatch   [3] AttributeValueAssertion,
      substrings      [4] SubstringFilter,
      greaterOrEqual  [5] AttributeValueAssertion,
      lessOrEqual     [6] AttributeValueAssertion,
      present         [7] AttributeDescription,
      approxMatch     [8] AttributeValueAssertion,
      extensibleMatch [9] MatchingRuleAssertion }

   SubstringFilter ::= SEQUENCE {
      type            AttributeDescription,
      -- at least one must be present
      substrings      SEQUENCE OF CHOICE {
         initial [0] LDAPString,
         any     [1] LDAPString,
         final   [2] LDAPString } }

   MatchingRuleAssertion ::= SEQUENCE {
      matchingRule    [1] MatchingRuleId OPTIONAL,
      type            [2] AttributeDescription OPTIONAL,
      matchValue      [3] AssertionValue,
      dnAttributes    [4] BOOLEAN DEFAULT FALSE }

Well, it's not that bad, really. What this is telling us is that a search operation is defined by a bunch of things: First, a base object, which defines the starting level of the search (this is usually a container object like "o=yoyodyne", or "ou=propulsion,o=yoyodyne"). Second, a scope (base means the object itself, onelevel means everything at the level of the object, and subtree means the level of the object and every level below it). Next, whether and/or how we should dereference aliases. Also, limits on the number of objects returned and the amount of time a search can take. Next, whether you actually want the values of attributes or just a list of attributes the object(s) have. And, finally, a list of attributes you want returned from the object(s). The reason I point this out here is that you'll see that each language will call its LDAP search operation with parameters that reflect the RFCs structure for an LDAP search object. So, while part of an API's job is to abstract the dirty work of the protocol, it's impossible to separate working in LDAP from some understanding of the protocol. Therefore, it helps to understand these definitions at the protocol level.

Another important consideration is security. Novell's LDAP implementation, as a v3 standard, allows SSL encryption of bind requests (over a normal TCP connection). The scripting platforms involved, however, have various states of readiness for SSL-enabled LDAP, and often take multiple steps to get the code and tools for SSL encryption. You also may be able to use the publicly available stunnel package to create an SSL tunnel to the NDS eDirectory server, and do a normal unauthenticated bind to a LDAP port on the host where you're running the script. If you will not be able to SSL-enable connections, you will have to enable the LDAP server to accept cleartext passwords and accept the risks inherent there. This risk can be minimized, but not eliminated, by having an LDAP server on the same host as the web server. That may, however, create other risks as well. At the very least, if you're doing CGI programming that requires users to supply a username and password over the Web, that dialog should be secured by an SSL-enabled web page. The rest you may have do deal with by having links between the CGI server and LDAP server that you can secure physically and logically by limiting user access to machines, keeping up on security patches, and following good system administration practices. In a high-security environment, however, any risk of plaintext authentication may be unacceptable, and thus such solutions may not be available to you.

LDAP SDKs and You

Even though we're dealing with scripting languages, both Python and PHP's LDAP implementations are built against a C SDK. So, a brief discussion of the available SDKs is relevant here.

OpenLDAP

OpenLDAP (http://www.openldap.org) is a reference implementation of an LDAP server, clients, and libraries. When you go to their website you will see a "Stable" version (2.0.11) and an "historical" (1.2.12) version. The current version is the LDAPv3 compliant one. The 1.2.x versions are only LDAPv2 compliant. 2.0.11 can be built with SSL support. When you do this, assuming you have already built and installed OpenSSL, you'll need to run the configure with the following options:

env CPPFLAGS="-I/usr/local/ssl/include" LDFLAGS="-L/usr/local/ssl/lib"
    ./configure --with-tls --enable-crypt

This will ensure that OpenLDAP will build with SSL support and find the correct libraries.

Mozilla

Mozilla's (formerly Netscape's) directory SDK lives at http://www.mozilla.org/directory. It supports V3 as well. It uses Mozilla's Network Security Services (NSS) component for SSL support. I personally do not use this SDK on a regular basis and won't be focusing on it here. PHP supports either the OpenLDAP or Mozilla libraries.

Novell

Novell currently has a port of the LDAP SDK for Linux and Solaris (as well as Windows) available on their website as a Leading Edge component. These libraries come prebuilt with SSL support, and sample code; and in fact are based on the OpenLDAP libraries. PHP's LDAP support can be built against the Novell libraries; but since they are still considered Leading Edge components, there is no formal support from Novell, and the interfaces could change over time. Using Python with the Novell libraries may be possible with some modifications to the Python library. Note as well that, building Python or PHP to the Novell libraries won't expose any more functionality than the routines available in those languages. Therefore, while the libraries are useful for developing your own C programs to access the directory, there's no significant advantage to using them over OpenLDAP to build Python or PHP's LDAP support.

With that, let's look at implementations of the LDAP protocol available in Perl, Python and PHP.

Perl

Larry Wall, the creator of Perl, is fond of saying, "There's More Than One Way To Do It!" And that's certainly true with LDAP access from Perl. As far as I can tell, there are at least 3 different LDAP implementations:


Name of interface
Available at
Comments

Net::LDAPapi

http://www.cpan.org/authors/id/CDONLEY/Net-LDAPapi-1.42.tar.gz

Hasn't been updated since 1998. Doesn't use SSL. LDAP v2 only. The author is no longer supporting this module.

PerLDAP

http://www.perldap.org/

Provided by the Mozilla project. Is an interface to the Mozilla C LDAP SDK. Can do SSL binds (if the C library supports it.) Has v3 support in some modules.

Perl-LDAP

http://perl-ldap.sourceforge.net/

Native Perl interface to LDAP (no C SDK required.) v3 support. SSL binds supported (with IO::Socket::SSL installed.)

I have used all three, but I am most familiar with Perl-LDAP and particularly like the fact that it doesn't need a C SDK behind it. Thus it's portable to any systems with a current Perl implementation.

Note: As of this writing, Perl for NetWare is based on Perl 5.003, and Perl-LDAP requires 5.004. Novell is working on a Perl 5.6 port, which should support Perl-LDAP.)

Here's some sample code showing a bind with Perl-LDAP:

#!/usr/bin/perl
   
use NET::LDAP;
   
$ldap=Net::LDAP->new('ldap');
#$ldap defines an LDAP connection handle, and 'ldap' is the name
# of your LDAP server
   
$ldap->bind(dn=>'cn=admin,o=yoyodyne', password=>'plaintext');
# By the way, did I mention that you may have scripts where the
# password is hardcoded in them?  Sure, you can prompt as well, 
# but if you're doing batch processing, you'll need to be aware
# of this.
$result=$ldap->search(basedn=>'o=yoyodyne',
   scope=>'one',filter=>'cn=admin',
   attrs=>['surname','mail','groupMembership']);
$result->code && warn $result->error;
foreach $entry ($result->all_entries) {
   @surname=$entry->get('surname');
   @mail=$entry->get('mail');
   @groupMembership=$entry->get('groupMembership');
   $dn=$entry->dn;
   print ("Info for $dn:\n");
   print ("Surname: ");
   foreach $surnamevalue (@surname) {print "\"$surnamevalue\" ");}
   print ("\n");
   print ("email: ");
   foreach $mailvalue (@mail) {print "\"$mailvalue\" ");}
   print ("\n");
   foreach $groupmembershipvalue (@groupmembership)
       {print "\"$groupmembershipvalue\" ");}
   print ("\n\n");
   }
$ldap->unbind;

This uses standard Perl object oriented syntax. You start an LDAP connection by creating an $ldap variable to act as a connection handle by invoking the new method of Net::LDAP, with a parameter of the server name (there are other parameters you can throw at it, but you can get it started just with that.) You then bind to the LDAP server with the bind method, passing a DN of the user object and a password. If you don't prompt for the password, it will just be there in your file. Scripteat emptor, perhaps.

You then perform a search by invoking the search method and assigning the result to another variable; I called it $result. By the way, you can also get the result codes of the new and bind operations-it may, in fact, be a good idea because you can then test the error codes and do some error handling if something goes wrong. For instance, after the search, I look at $result->code, and if it's non-zero, I print the error result. It's pretty useful.

Anyway, you perform a search, and the $result->all_entries array has LDAP::Entry objects for each DN found. You then iterate over each entry object with the get method, which returns arrays containing all the values of an object. Remember, many fields in LDAP can be multivalued, and thus the API will always return an array, even for single-valued attributes like Surname. Here, I just print the values, but you may want to do more useful things with them.

Downloading and Installing Perl-LDAP

There's a number of ways to install Perl-LDAP (this is probably Larry Wall's influence again.) On a Unix or Linux system, you can go to http://sourceforge.net/projects/net-ldap/ , and download the latest gzipped tar file, unpack it, do a ‘perl Makefile.PL' then a ‘make' and a ‘make install.' Also, if you have CPAN.pm installed, you can do a ‘perl -MCPAN -eshell' and then a ‘install Bundle::Net::LDAP' and it will do it automatically, including installing SSL support. Note, however, that to install the IO::Socket::SSL that Bundle::Net::LDAP needs, you will also need the latest version of OpenSSL available at http://www.openssl.org/.

. Since I want you to be secure, I'll outline these steps using CPAN:

  1. Download and install Open SSL. This is a case of exploding the newest .tar.gz file, changing to that directory, running "./Configure", then a "make" and "make install" as root. Then do "perl -MCPAN -eshell" again and enter "install IO::Socket::SSL." If anything goes wrong, you'll have do dig a little deeper, but this should work.

  2. Then, to actually do an SSL bind to a server, you replace "use Net::LDAP" with "use Net::LDAPS" in your script. Optionally, if you've exported the server certificate and have it in a local file, you can verify it with optional parameters to the bind: "verify=>‘require'" and "capath=‘/usr/local/certs'." This will verify that the server is who it says it is (at least, who you think you say it is.)

So, what do you do with all this cool stuff? Well, I decided to solve a problem, which was this: We have setups where people can use their NDS password to authenticate without being notified their grace logins are being used. Thus, password problems. I have written a simple Perl script that scans the tree for people whose passwords are less than one week from expiring and sends them an email. I run this script as a cron job on one of our Linux servers, and thus people get notifications before their password expires and are encouraged to change them. We also have ways to change your password over the Web, and the email has links to all of that. Here's the script:

#!/usr/bin/perl
   
use Net::LDAP;
use Time::Local;
use Net::SMTP;
   
#set up some constants we'll use, and be able to configure
   
$ldapuser="cn=admin,o=yoyodyne";
$ldappassword="thisisabadpassword";
$advance_warning=86400*7;
   
$ldap_filter="(\&(!(networkAddressRestriction=\\0A\\00\\15\\55\\
    33\\66))(cn=*)(objectclass=inetOrgPerson))"
@attribs=["passwordExpirationTime","mail"];
$baseDN="o=yoyodyne";
$passadmin="changeyourpassword@yoyodyne.not";
   
#create an object
   
$ldap=Net::LDAP->new('ldap');
   
#bind
   
$ldap->bind(dn=>$ldapuser, password=>$ldappassword);
   
$smtp=Net::SMTP->new('email');
   
print scalar localtime, "\n";
$result=$ldap->search(base=>$basedn,
               filter=>$ldapfilter,
               attrs=> @attribs);
foreach $entry ($result->all_entries) {
      $userentry = $result->pop_entry;
      @passexp = $userentry->get('passwordExpirationTime');
      @email = $userentry->get('mail');
      $mail = $email[0];
      $dn= $userentry->dn;
      $dn =~ s/,?(cn|ou|o)=/\./g;
      foreach $pwexp (@passexp) {
      ($yr,$mo,$dt,$hr,$min,$sec) = ($pwexp =~ /(\d{4})(\d{2})
          (\d{2})(\d{2})(\d{2})(\d{2})/);
      $mo2=$mo-1; $yr2=$yr-1900;
      $exptime=timegm($sec,$min,$hr,$dt,$mo2,$yr2) || warn "timegm:
          $!\n";
      if ($exptime < time + $advance_warning) {
      if ($exptime > time) {
         $secsleft = $exptime -time;
         $daysleft = int($secsleft/86400) + 1;
         $smtp->mail("$passadmin");
         $smtp->to("$mail");
            $smtp->data();
            $smtp->datasend("Subject:  $dn: Your NDS password expires
                within $daysleft days");
            $smtp->datasend("\n");
   
            $smtp->datasend("The network password for account $dn
                expires on\n $mo/$dt/$yr at
$hr:$min:$sec.\n");
$smtp->datasend('cat passinfo.txt');
$smtp->dataend();

print "$dn $dt/$mo/$yr $hr:$min:$sec -- $daysleft days\n";
}
else {print STDERR "dn: $dn, password expired ", time- $exptime, "
      seconds ago\n";}
}            
      }
}
print "\n\n";
$ldap->unbind;
$smtp->quit;

Keep in mind that this wants a few things-namely, that every user object (in LDAP, you identify user objects by object that have the objectClass of inetOrgPerson, by the way,) should have a fullName and mail attribute, with the latter being a valid email address. Also, note my search filter. LDAP search filters are incredibly powerful, and this complicated one:

(&(!(networkAddressRestriction=\\0A\\00\\15\\55\\33\\66))
    (objectclass=inetOrgPerson))

basically says, "Give me all the objects of class inetOrgPerson, that do not have a Network Address Restriction of \\0A\\00\\15\\55\\33\\66 set." This is necessary in our environment to exclude certain utility accounts that aren't really users. The following would actually be a binary value, which is how address restrictions are stored. You may have all kinds of other restrictions-you may wish to run different scripts and do users whose last names start with A first (surname=A*) or who have outie bellybuttons (bellybuttonType="outie", if you've extended your schema appropriately). Search filters can be constructed with multiple and, or and not statements as well.

One more thing before we leave the world of Perl. How do you change things? Well, here's an add statement:

$result=$ldap->add (
      dn => $dn,
      attr => [  'sn' => $lname,
         'cn' => $user,
         'objectClass' => ['top','person','inetOrgPerson',
               'organizationalPerson'],
            'givenName' => $fname,
            'fullname' => "$fname $lname",
            'mail' => "$user\@yoyodyne.not",
            'description' => $description,
            'messageServer' => $ms,
            'ou' => [$department, "STU"],
            'telephoneNumber' => $ext,
            'LoginScript' => "write \"Hello.\"",
   
            'passwordAllowChange' => 'TRUE',
            'passwordRequired' => 'TRUE',
            'passwordMinimumLength' => '8',
            'passwordExpirationInterval' => '10368000',
            'passwordUniqueRequired' => 'TRUE',
            'loginGraceLimit' => '12',
            'loginGraceRemaining' => '12',
            'ndsHomeDirectory' => "$home$user",
            'userPassword' => $pass
            ]
         );
$result->code && warn "Create failed: ", $result->code, $result->error;

Notice that you use the attribute names as field names in the add statement, and you must explicitly define the relevant objectClass values for the user object. Also note the special userPassword attribute. This is an attribute that can only be written to or deleted, never read from. You can change a password with LDAP by first doing a delete on the userPassword with a value of the old password, and then an add with the new password-something like this:

$result=$ldap->modify($dn
      changes => [
            delete => [ userPassword => $oldpass],
            add => [userPassword => $newpass],
      ]
      );

These must occur as one operation to work correctly to ensure the user changing the password knows it, unless the bound user has supervisory rights to $dn.

Python

In stark contrast to Larry Wall's laissez-faire style of program design, Guido van Rossum's Python is a language that enforces structure and readability. Python has become very popular both as a CGI provider and a command-line interpreted language.

The currently available LDAP interface for Python is python-ldap 1.10. It is an RFC 1823-compliant LDAP implementation. That means LDAP v2, which precludes such things as SSL authentication, and v3 extended operations, but is perfectly acceptable for modifying and changing attributes and values of directory objects, as long as you remember the security considerations I outlined above. Also, the current version of python-ldap cannot compile under OpenLDAP 2.0.11, and needs the 1.2.x OpenLDAP libraries. However, it's highly recommended you go to the python-ldap website (http://python-ldap.sourceforge.net) and see what's up-the software is sort of in flux right now, with new developers coming onto the scene, and there may be upgrades and patches to support things like OpenLDAP 2.0.x and SSL support by the time you read this.

Here's the first example I wrote in the Perl section, in python:

#!/usr/bin/env python
   
import ldap
   
l=ldap.open('ldap')
l.simple_bind_s('cn=admin,o=yoyodyne','plaintext')
res = l.search_s('o=yoyodyne', ldap.SCOPE_ONELEVEL, "cn=admin",
    ["surname", "mail", "groupMembership"])
for entry in res:
   attrs=entry[1]
   surname=attrs["surname"]
   mail=attrs["mail"]
   groupmembership=attrs["groupMembership"]
   dn=entry[0]
   print "Info for %s:\n" % (dn,)
   for surnamevalue in surname:
   
   print "\"%s\" " % (surnamevalue,)
   print "\n"
   for mailvalue in mail:
      print "\"%s\" " % (mailvalue,)
   print "\n"
   for groupmembershipvalue in groupmembership:
      print "\"%s\" " % (groupmembershipvalue,)
   
   print "\n"
   
l.unbind()

Notice the similarities and differences. They're similar because we open a connection, bind to it, perform a search operation, and then iterate over the results. The parameters passed to the search are identical to the Perl case (base DN, scope, search filter, attribute list), and the results are a list of values. Each of these values contain a DN and then a dictionary (similar to a Perl hash) of the attributes, which are themselves lists (Python for array) which we can iterate over to get individual attribute values. Notice that if you look beyond the language-specific syntax issues, the Perl and Python versions of this script are nearly identical.

Getting and Installing Python-LDAP

Python-LDAP lives on SourceForge at http://python-ldap.sourceforge.net/. The current version as of this writing is 1.10alpha 3 and is available both as source and as a RPM package for Linux. It supports Python version 1.5.2 and later. The source requires an LDAP C library, such as OpenLDAP, Mozilla, or Novell's. Full documentation for the product is also available there.

You use the module by doing an "import ldap" at the beginning of your program, along with whatever other LDAP modules you wish to use. Again, python-ldap supports all LDAP v2 functionality, and can do adds, modifies, and deletes to any object in the tree, based on rights. For more info on python-ldap, visit the above web page, in particular, the documentation.

PHP

PHP was created to be a simple, embedded, CGI scripting language with features of Perl, ASP, and Basic. Its syntax has elements of all of these. Unlike Perl and Python, it is rarely used from the command line, and in fact PHP will generate its own HTTP headers, unlike Perl or Python where they must be generated manually.

The current version of PHP is 4.0.6, and its LDAP support is built on the Netscape or OpenLDAP libraries. Again, however, its LDAP support is mostly v2 based (although LDAP controls are supported as of 4.0.4 with the OpenLDAP libraries) and thus will not support SSL sessions and other v3 functionality, even if the C library supports them. You'll want to look at the PHP web page (http://www.php.net) for details on the LDAP calls themselves. But since it's been working well for me, I'm going to give you the same code in Perl, and Python, now in PHP:

<?php
   
$ldap = ldap_connect("ldap");
   
ldap_bind($ldap, "cn=admin,o=yoyodyne", "plaintext");
   
   
$result = ldap_search($ldap, "o=yoyodyne", "cn=admin",
    array( "surname", "mail" ,"groupMembership"));
   
   
   
foreach (ldap_get_values($ldap) as $entry) {
   $dn=$entry["dn"];
   $surname=$entry["surname"];
   $mail=$entry["mail"];
   $groupmembership=$entry["groupmembership"];
   print "Info for $dn\n";
   foreach ($surname as $surnamevalue) {
      print "\"$surnamevalue\" ";
      }
   print "\n";
   foreach ($mail as $mailvalue) {
      print "\"$mailvalue\" ";
      }
   print "\n";
   foreach ($groupmembership as $groupmembershipvalue) {
      print "\"$groupmembershipvalue\" ";
      }
   print "\n";
   }
ldap_unbind($ldap);
   
?>

This code will only work with PHP 4. A lot of Linux systems may have PHP 3 on them. If you do I strongly recommend upgrading to PHP 4. The language has much more functionality and is significantly faster.

Getting and installing PHP 4

PHP 4 lives at http://www.php.net/. You can download the latest .tar.gz file, unpack it, do a configure, and make. When you configure, you'll want to make sure you add the "with-ldap" parameter to supply LDAP support. This should work with installed OpenLDAP as is. With another LDAP library you may need to specify the directory like "with-ldap=/usr/local/ldapcsdk/sdk/usr/lib" or some such to get it installed. Also, it's likely you'll have other configure options for PHP-check the INSTALL doc in the PHP build directory.

Additionally, many Linux distribution vendors (Red Hat for instance) provide precompiled PHP modules with support for LDAP built in. Check their distribution or update sites. You may find these packages easier to configure and support in your environment. It's your call as to what you're comfortable with.

Conclusion

Well, hopefully this has been enough of an introduction to the concepts of LDAP in scripting languages, and some information about how to get and build these languages with LDAP support to get you started in your own explorations. You'll hopefully find that working with LDAP in your favorite scripting language is easy, flexible, and fun. Keep in mind we've really just scratched the surface here, and that there's a lot to learn about LDAP and the scripting platforms in this document.

I leave you with links to some help for using LDAP with each of these languages:

And some more useful references:

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates