For automating and programmatically accessing Active Directory (AD), the typical API of choice is Microsoft Active Directory Service Interfaces. ADSI is an easy-to-use, robust interface that lets you easily manipulate objects in AD. When you combine ADSI with ADO for query purposes, you can perform nearly any function necessary to automate AD.
However, because ADSI is COM based—and COM is a proprietary Microsoft technology—you can't easily use ADSI from other platforms. In other words, ADSI is tied to Windows OSs. Using ADSI to develop scripts or applications on a non-Windows platform or to develop cross-platform scripts or applications is difficult. Fortunately, an alternative exists: You can use a Lightweight Directory Access Protocol (LDAP) API.
Microsoft's track record for supporting standards hasn't always been that great, but with AD, Microsoft made significant improvements in that regard. AD supports not only LDAP but also other standards, such as DNS, Simple Network Time Protocol (SNTP), Secure Sockets Layer (SSL), Transport Layer Security (TLS), and Kerberos. One of the great benefits of AD supporting LDAP is that LDAP makes AD much more platform neutral from a client perspective. Because LDAP is a standard, you aren't limited to Windows clients and platforms. LDAP has been around for many years and has clients for nearly all platforms, which means you can write LDAP-enabled clients or scripts to query and update AD from your platform of choice. Having that capability is a boon for administrators who need to develop scripts or applications on a non-Windows platform or write cross-platform applications or scripts that use AD.
Many people think of LDAP only as a protocol, but unlike most protocol standards, it's also associated with an Internet Engineering Task Force (IETF) Request for Comments (RFC)defined API. Defined in RFC 1823 (http://www.ietf.org/rfc/rfc1823.txt) and typically referred to as the C-style LDAP API, the API defines a basic set of functions necessary to query and update an LDAP-based directory. Microsoft and Netscape both have an LDAP software development kit (SDK) that implements the C-style LDAP API.
Although having a de facto standard API to use with LDAP was helpful at first, the C-style LDAP API wasn't object-oriented (OO) and didn't map well to some languages, such as Java. For that reason, Sun Microsystems developed its own LDAP API, known as the Java Naming and Directory Interface (JNDI—http://java.sun.com/products/jndi). JNDI is more than just an LDAP API. JNDI is also an interface to DNS and is similar in concept to ADSI as a generic directory interface.
In the Perl world, some Netscape developers created a set of LDAP Perl modules based on the Netscape LDAP SDK, commonly referred to as PerLDAP. The PerLDAP module names are prefixed with Mozilla::LDAP. Unfortunately, those Perl modules require that you install the Netscape LDAP SDK to use them. Another group of developers, led by Graham Barr, developed a pure Perl implementation—a set of Perl modules referred to as perl-ldap, which should not be confused with Netscape's PerLDAP. The perl-ldap module names are prefixed with Net::LDAP. The big advantage of the Net::LDAP modules over the Netscape modules is that you can install the Net::LDAP modules on virtually any machine that supports Perl and can write LDAP-based clients without the need for an external SDK or other software. The Net::LDAP modules use many of the C-style API function names but are OO for easier use.
In this article, I explain how to install and use the Net::LDAP modules to query AD. (Because this article is targeted toward experienced Perl users, I don't go into how to install and use Perl.) In Part 2, I'll discuss how to use the Net::LDAP modules to create and update objects in AD.
For the Perl aficionados, installing modules is probably second nature. To use the Comprehensive Perl Archive Network (CPAN) shell to install Net:: LDAP, you can run the following command from the command line:
cpan> install Net::LDAP
If you've never used the CPAN shell, learning how to use it is worthwhile because it makes installing and updating modules easy. The CPAN.pm module comes with most Perl installations; when you run this module for the first time, it walks you through how to set up the CPAN shell. You can also find instructions about how to install CPAN modules for a particular platform on the CPAN Web site (http://www.cpan.org/modules/INSTALL.html).
You can find links to the latest version of the perl-ldap library along with its online documentation on the perl-ldap home page (http://perl-ldap.sourceforge.net). The version used in this article's code is perl-ldap-0.26.
Getting Started with Net::LDAP
Let's start with a simple example of how to use the Net::LDAP modules. Listing 1 contains code that retrieves basic information from a directory. In this code, I use the new() method to create a new connection to a domain controller (DC) named dc1.
Next, I call the root_dse method to retrieve the desired attributes from the Root DSE, which is a repository of configuration information about the DC. In this case, I want to retrieve the attribute that specifies the domain naming context (NC), so I set the attrs parameter to defaultNamingContext. If you want to retrieve all the attributes in the Root DSE, you set the attrs parameter to an asterisk (*). I then use the get_value() method to return the attribute's value. The value returned for the mycorp.com domain, for example, would be dc=mycorp,dc=com.
Note that Listing 1 doesn't include any authentication code. The Root DSE is available through anonymous access so that applications can have a starting point to dynamically determine some basic information about a directory. To perform more-sophisticated queries, though, you need to include authentication code similar to the code that Web Listing 1 (http://www.winscriptingsolutions.com, InstantDoc ID 27569) shows. In this code, I call the bind() method and specify the distinguished name (DN) and password of the user that I want to authenticate as. The bind() method returns a Net::LDAP::Message object, which I use to determine whether a logon error occurred. If an error occurred, the code() method will return the error code, and the error() method returns the textual error message. To terminate the authenticated session, I call the unbind() method.
If you're familiar with the parameters used for LDAP searching, searching with Net::LDAP is straightforward. You typically use three parameters to perform an LDAP search: base DN, scope, and filter. The base DN parameter specifies the location from which to start the search. The scope parameter specifies the range of the search. You can use one of the following values:
- base—match only the object that the base DN specifies
- onelevel or one—match objects one level down from the base DN (i.e., direct children of the parent), not including the base DN
- subtree or sub—match any object below the base DN, not including the base DN
The filter parameter is a prefix notation string that specifies the criteria against which to match objects. RFC 2254 (http://www.ietf.org/rfc/rfc2254.txt) defines the filter syntax. Web Table 1 contains a few sample search filters. For information and tips about how to use filters to search AD, check out "Understanding ADO Search Filters in LDAP Queries," May 1999, http://www.winscriptingsolutions.com, InstantDoc ID 5282, and the Searching Active Directory Web page (http://msdn.microsoft.com/library/en-us/netdir/adsi/searching_active_directory.asp).
Listing 2 contains code that performs an AD query to search for all users with the last name of Allen. Note the $user variable in the code that callout A highlights. Instead of specifying a user DN, I specify a user principal name (UPN), which is an email-style identifier that users can use to log on. Having users' UPNs match their email addresses is common practice. If your forest DNS name doesn't match the DNS suffix used in your email addresses, you can create additional UPN suffixes by following the steps outlined in the Microsoft article "HOW TO: Add UPN Suffixes to a Forest" (http://support.microsoft.com/?kbid=243629). If you use a UPN, you don't need to specify the entire DN for the user in the code.
As the code at callout B in Listing 2 shows, I use the search() method to perform the query. I pass in the three parameters (i.e., base DN, scope, and filter) that I discussed earlier. I could also pass in a fourth parameter, attrs, to specify the array of attributes to return from the search. If you don't specify an attrs parameter, the search returns all the attributes that have values. If you're performing searches that will return a lot of attributes, you'll want to use the attrs parameter to reduce the amount of data the search returns.
As the code at callout C in Listing 2 shows, I use the entries() method to iterate through the returned search data. This method returns an array of Net::LDAP::Entry objects. For each object, I use the dump() method to print all returned attributes. If you want to access specific attributes, you can use the get_value() method, as I did in Listing 1.
Creating an LDAP Search Utility
Now that you know the basics of how to use the Net::LDAP modules to perform searches, let's look at a simple command-line tool—ldapsearch.pl, which Listing 3, page 4, shows—that you can use to query AD. Ldapsearch.pl is loosely based on the widely used LDAP search utility (ldapsearch) available from most commercial LDAP SDKs and LDAP directory servers (not including AD).
The basic code in ldapsearch.pl isn't much different from the code in Listing 2 with the exception of the command-line options and the code that processes them. As the code at callout D in Listing 3 shows, ldapsearch.pl has eight command-line options, seven of which are mandatory. You're already familiar with some of these options: The -b, -s, and -f options are the base DN, scope, and filter parameters, respectively, for the search, whereas the -D and -w options are the user's DN and password, respectively, for authentication purposes. You use the -h option to specify the LDAP server's name. You use the -a option to specify any of the attributes you want to retrieve. You need to specify the attributes as a comma-separated list. The last option, -p, is the only optional parameter. I discuss that parameter shortly.
To perform the command-line processing of the options, I use Perl's GetOpt::Std module, which provides basic functionality for processing options. Because most of the options are mandatory, ldapsearch.pl includes option-validation code, as the code at callout A in Listing 3 shows.
Callout B in Listing 3 highlights the code that performs the connect and bind operations. The one difference between this code and the connect and bind code in Listing 2 is the use of the port (-p) option. On the command line, you can use the -p option to specify an alternative port, such as port 3268 for the Global Catalog (GC). If you don't use the -p option, the script defaults to port 389, which is the standard LDAP port.
The code at callout C in Listing 3 executes the search and prints the matching entries. This code is similar to that in Listing 2, except for the use of the attrs parameter in the search method. Because I'm requiring the -a command-line option, I have to pass the attribute list to the search method. I use the split function to turn the comma-separated list into an array.
Web Figure 1 shows sample output from running ldapsearch.pl. The first three lines contain the invocation used to launch the script from the command line. As the invocation shows, I ran the script against host dc1 (-h option). The script performed a search against the cn=computers,dc=mycorp,dc=com container (-b option) and any subcontainers (-s option) for all computer objects that have a name that starts with app (-f option). The results from the query appear after the invocation. Because I specified to return only the cn attribute for each object (-a option), the results show the DN of the matching object and its cn attribute.
Stay Tuned for Part 2
If you need to create scripts or applications on a non-Windows platform or create cross-platform applications or scripts that use AD, you're better off using the Net::LDAP modules rather than ADSI. In Part 2, I'll show you how to go a step further and use the Net::LDAP modules to add and update information in AD.