It may sound strange to think of using LDAP (Lightweight Directory Access Protocol) for “fun and profit” – indeed, even strange perhaps to see these words combined in the same sentence. But if the title captured your attention (else you wouldn’t be reading this), then it served its intended purpose.
Network administrators who oversee small organizations (fewer than 100 users) may not see a need for learning about or using LDAP, unless they find themselves having to manage a particular application that is LDAP-centric. But administrators over larger – and especially enterprise – networks will quickly find that the Lightweight Directory Access Protocol is an essential tool for managing network identity and other directory-related information – not so much for “fun”, but certainly for “profit”.
What is LDAP, you ask? Most of you reading this already know something about it. LDAP is sort of a universal location/identification standard that started from the X.500 standard some years ago.
Created in 1995 by the University of Michigan to replace the overly-complicated DAP (Directory Access Protocol), it was found that LDAP could be used for a wide variety of directory based applications. For the UNIX world, LDAP addressed many of the shortcomings of the commonly-used NIS (Network Information Service), by using well-known defined ports for communications, providing data encryption via SSL/TLS, and providing search mechanisms to allow searches on any field.
So as it stands today, while many applications use LDAP specifically for directory access and searches, the LDAP protocol has also become a kind of “universal language.” It can be employed to communicate information between disparate proprietary directories, such as Novell’s eDirectory and Microsoft’s Active Directory (and countless others).
Novell began to include an implementation of LDAP in its NetWare 5 operating system, using the NLDAP.NLM module on the server, and the ICE (Import/Convert/Export) utility, implemented as a Win32 executable program, ICE.EXE. LDAP was also integrated into the ConsoleOne graphical management utility. This LDAP implementation into NetWare allowed direct authenticated access for third-party LDAP applications into Novell Directory Services (later named eDirectory). No longer are there any dependencies for applications to be written to communicate with NDS using NetWare Core Protocol (NCP).
Just as eDirectory is now relatively platform-independent, able to run equally well on NetWare, Windows, Linux and UNIX operating systems, so also the communication with Novell’s directory has become independent of any NetWare client, or proprietary protocols such as NCP.
But let’s get down to the heart of the matter: how is LDAP useful in day-to-day network management? Here we will go through a few practical examples of using LDAP for common directory management tasks.
Let’s say we manage a computer network with about 1000 user accounts, stored in eDirectory. Perhaps one of the most common tasks would be pulling a current list of all of our users from eDirectory, with particular attributes that are required for a certain application. Obviously, we don’t want to have to manually look up and record this information (what are computers for, anyway?), so we would like to automate this as much as possible.
To keep this example simple, we will just use the tools that come built-in. In this case, we’ll assume that we have a NetWare 6.5 server, and we can use the ICE utility that is provided with NetWare (it is also provided with ConsoleOne and eDirectory for Linux).
ConsoleOne and the ICE utility are contained in SYS:\PUBLIC\mgmt\ConsoleOne\1.2\bin folder on the NetWare server. However, I prefer to copy the folder to my local workstation, to C:\Novell\ConsoleOne, so as to be able to access it without mapping any network drives or using any UNC paths.
So let’s say we wish to export our user account information, with particular attributes, from eDirectory. We will select for our example the costCenter and costCenterDescription attributes. To accomplish our goal, we could issue this command:
C:\novell\consoleone\1.2\bin\ice.exe -S LDAP -s NWSERVER -p 389 -d cn=admin,o=main -w password -b "o=MAIN" -F objectClass=user -c sub -a costCenter,costCenterDescription -D LDIF -f C:\DATA\exportCC.ldif
To the uninitiated, this command may seem like a jumble of confusing syntax. So let’s break it down, so we can understand what we are requesting from eDirectory:
C:\novell\consoleone\1.2\bin\ice.exe -S LDAP -s NWSERVER -p 389 -d cn=admin,o=MAIN -w password -b "o=MAIN" -F objectClass=user -c sub -a costCenter,costCenterDescription -D LDIF -f C:\DATA\exportCC.ldif
Obviously, the first element is the ICE utility itself. The “-S LDAP” denotes that the source of information (“source handler”) will be from an LDAP server, and the “-s NWSERVER” (note that -S and -s are different: the command is case-sensitive) defines that the particular server we are querying is identified as “NWSERVER”. (This would need to be resolved to an IP address, either via DNS or a hosts file; alternatively, an IP address could be used). The TCP communications port is specified with “-p 389”.
The parameters work as follows:
“-d cn=admin,o=MAIN” – specifies the distinguished name in LDAP format (note the use of commas as delimiter) used to bind to the server for authentication for this operation.
“-w password” – supplies the admin password.
“-b o=MAIN” specifies the base distinguished name (baseDN), or starting point of our search.
“-c sub” specifies that the scope of the search is “subtree”, or all levels below the baseDN.
“-a costCenter,costCenterDescription” denotes the attributes (comma-delimited with no spaces) that we want to include in the output.
“-D LDIF” (also case-sensitive) specifies that the output (“destination handler”) is to an LDIF file,
“-f C:\DATA\exportCC.ldif” provides the name of the output (LDIF) file.
LDIF stands for LDAP Data Interchange Format. It specifies the ASCII text file format used when importing and export information using LDAP.
If we entered all of these parameters correctly at a command prompt (from a Windows client), we should see a result like this:
Total entries processed: 1054 Total entries failed: 0 End time: Wednesday, December 26, 2007 Total Time: 0:00:10.540 Time per entry: 00:00.010
This indicates that it took about 10 seconds to search for and output 1054 records with the specified attribute information on my test server, and no entries failed. The output LDIF file, then, would look like this (only the first four records included):
#This LDIF file was generated by Novell's ICE and the LDIF destination handler. version: 1 dn: cn=admin,o=MAIN dn: cn=User1,ou=NewYork,o=MAIN changetype: add costCenterDescription: SALES costCenter: 444444 dn: cn=User2,ou=NewYork,o=MAIN changetype: add costCenterDescription: SALES costCenter: 444444 dn: cn=User3,ou=NewYork,o=MAIN changetype: add costCenterDescription: SALES costCenter: 444444
Here we see the resulting output LDIF file of the information we specified for our query, with records or entries separated by an extra linefeed. You will notice that each record also contains “changetype: add”. Because the most common purpose for exporting records is to import them into another system, the ICE utility by default automatically adds this to each entry. All lines, such as the first line in the LDIF file that are preceded by the “#” character are comments. The “version: 1” denotes that this file is in LDIF version 1 format and thus compatible with all versions (1, 2 and 3) of LDAP applications.
It quickly becomes apparent that having to type in the full command, with all of the correct syntax for the ICE utility, each time we wish to perform an LDAP operation would be quite onerous. Obviously, we would want to capture the command in a script file, such as a .bat or .cmd file, and then test it to ensure that we have all the parameters entered correctly.
So the script or command file could look just like the command itself:
:: LDIFexportCC.cmd :: Script used to export user accounts with costCenter attributes C:\novell\consoleone\1.2\bin\ice.exe -S LDAP -s NWSERVER -p 389 -d cn=admin,o=main -w password -b "o=MAIN" -F objectClass=user -c sub -a costCenter,costCenterDescription -D LDIF -f C:\DATA\exportCC.ldif pause
Note that the wrapping of text in this example does not indicate any linefeeds. The command line itself (as opposed to the comment lines beginning with “::”) is a single line.
One of the major purposes of using scripts is not only to repeat the same operation but to re-use the script for a similar, but alternate purpose. For example, we may want to search against a different server, or to search for different attributes, or to search in a different baseDN, or use a different export file (or all of the above).
The simplest method would be to just copy the script above and just modify the desired parameters. For example, if the attribute we wanted to export was telephoneNumber, then the export command script might look like this:
:: LDIFexportTel.cmd :: Script used to export user accounts with telephoneNumber attribute C:\novell\consoleone\1.2\bin\ice.exe -S LDAP -s NWSERVER -p 389 -d cn=admin,o=main -w password -b "o=MAIN" -F objectClass=user -c sub -a telephoneNumber -D LDIF -f C:\DATA\exportTel.ldif pause
So the primary difference between this script and its predecessor is that we simply changed the attribute list to “-a telephoneNumber”, and we changed the name of the output file.
Or, if we needed to perform the LDAP operation securely (encrypting network traffic via SSL), we could change the first script to this:
:: LDIFexportCC-SSL.cmd :: Script used to export user accounts with costCenter attributes C:\novell\consoleone\1.2\bin\ice.exe -S LDAP -s NWSERVER -p 636 -L c:\certs\NWSERVER.der -d cn=admin,o=main -w password -b "o=MAIN" -F objectClass=user -c sub -a costCenter,costCenterDescription -D LDIF -f C:\DATA\exportCC.ldif pause
The main problem with this approach is that we quickly develop a plethora of “mini scripts” that we have to keep track of, one for each variation of operation we would perform. For the administrator who needs to perform only occasional LDAP operations, this may be acceptable. But for the “scripting pro”, who may have this routine embedded into a larger script that performs many other functions, this could be undesirable.
A more robust approach to this type of scripting involves replacing the actual parameters of the commands with variables, which can be fed into the script with input parameters, or supplied by functions elsewhere within a larger script. An example of this type of scripting would be the following, which performs the identical operation as our above script:
:: Script to export objects and specific attributes using ICE to LDIF :: Set required variables ## NOTE: attributes should be comma delimited @echo off set utility="C:\novell\consoleone\1.2\bin\ice.exe" set server=NWSERVER set port=636 set sldap=-L set cert=c:\certs\%server%.der set authuser=cn=admin,o=main set passwd=password set baseDN="o=MAIN" set objclass=user set scope=sub set attrs=costCenter,costCenterDescription set datadir=C:\data set outputfile=exportCC.ldif :: Export objects from eDirectory using ICE %utility% -S LDAP -s %server% -p %port% %sldap% %cert% -d %authuser% -w %passwd% -b %baseDN% -F objectClass=%objclass% -c %scope% -a %attrs% -D LDIF -f %datadir%\%outputfile% pause
The only difference between this script and the previous one is the setting and replacing of each of the required parameters using variables. In this example we used simple set commands, but in a real-world application the information may come from other sources.
But the glaring problem with this (and all of the above) scripts is the inadvisable practice of putting the admin password, in clear text, inside the script.
With the Linux/UNIX version of the ICE utility, we have the -W (capital W) option, which would stop and prompt us for the password. But alas, this option is not yet available in the Windows command-line version.
To alleviate this issue, we could replace the simple “set” commands with commands that prompt the user to input the admin name and password, such as:
echo Enter the authorized LDAP username and password set /p authuser=Enter admin LDAP name: set /p passwd=Enter password:
This would prompt us to enter the admin distinguished name (DN) in LDAP format, such as: “cn=admin,o=MAIN”, and then supply the password when prompted. The only problem here is that the password is now typed on the screen in clear text. To overcome this issue, some batch file scripters add this little piece of code in their scripts:
:: Prompt for username and prompt for password using assembler code @echo off echo Enter the authorized LDAP username and password ... set /p authuser=Enter admin LDAP name: echo hP1X500P[PZBBBfh#b##fXf-V@`$fPf]f3/f1/5++u5x>in.com set /p passwd=Enter password:<nul for /f "tokens=*" %%i in ('in.com') do set passwd=%%i del in.com
This weird little command:
actually writes a little scrap of assembler code into an executable file in the current directory called “in.com”. The following commands use it to capture the next keyboard input (the password) into the “passwd” variable without printing any characters to the screen. Then it deletes the scrap of code (del in.com) to tidy up.
This is a good time to insert a little caveat about scripting: scripts are designed to perform tasks automatically, repetitively and quickly. Correctly written scripts, therefore, can do a lot of good, saving us a lot of time. Poorly written scripts can do a lot of bad in a hurry. So always test, test, test (and then re-test) your scripts in a lab environment before employing them in the production environment.
With that said, I’d like to share a few scripts that perform different LDAP operations. So far, all we’ve done is export user accounts with selected attributes. But sometimes we also want to import data into the directory. Here is an example that adds user accounts from an LDIF file:
:: Script to import eDirectory users from LDIF :: Set required variables @echo off set util="C:\novell\consoleone\1.2\bin\ice.exe" set datadir=C:\data set inputfile=importusers.ldif set server=NWSERVER set port=389 set authuser=cn=admin,o=MAIN set /p passwd=Enter admin password: :: Import objects to eDirectory using ICE %util% -S LDIF -f %datadir%\%inputfile% -D LDAP -s %server% -p %port% %sldap% %cert% -d %authuser% -w %passwd% pause
When specified, the parameters are filled in from the variables. The active command that is given to the system is this:
"C:\novell\consoleone\1.2\bin\ice.exe" -S LDIF -f C:\data\importusers.ldif -D LDAP -s NWSERVER -p 389 -d cn=admin,o=MAIN -w password
In this example, the script simply runs ICE with the source (“source handler”) specified to be an LDIF file (C:\data\importusers.ldif), and the destination (“destination handler”) as the LDAP server, specified as “NWSERVER”. It uses the (non-secure) port 389, binding as cn=admin,o=MAIN, and the password of “password” (obviously, highly insecure here). But notice: because we set no values for %sldap% and %cert%, those parameters were not inserted into the command.
The LDIF input file (importusers.ldif) might look like this:
dn: cn=TestUser1,ou=Atlanta,o=MAIN changetype: add cn: TestUser1 company: ACME Corporation description: Test User fullName: Test User1 givenName: Test groupMembership: cn=Atlanta Users,ou=Atlanta,o=MAIN l: Atlanta loginGraceRemaining: 3 loginGraceLimit: 5 objectClass: inetOrgPerson objectClass: User ou: Executive Management passwordAllowChange: TRUE passwordExpirationTime: 19920102000000Z passwordExpirationInterval: 7776000 passwordMinimumLength: 6 passwordRequired: TRUE passwordUniqueRequired: TRUE sn: User1 title: Manager userPassword: pa$$word dn: cn=TestUser2,ou=Atlanta,o=MAIN changetype: add cn: TestUser2 company: ACME Corporation description: Test User fullName: Test User2 givenName: Test groupMembership: cn=Atlanta Users,ou=Atlanta,o=MAIN l: Atlanta loginGraceRemaining: 3 loginGraceLimit: 5 objectClass: inetOrgPerson objectClass: User passwordAllowChange: TRUE passwordExpirationTime: 19920102000000Z passwordExpirationInterval: 7776000 passwordMinimumLength: 6 passwordRequired: TRUE passwordUniqueRequired: TRUE sn: User2 title: Manager userPassword: pa$$word
As you can see, the information in the LDIF file sets some basic user information for the newly created users, including password requirements, and it even sets the initial password for the user accounts. The “passwordExpirationTime” attribute is set so that the password is already expired, with only three grace logins (loginGraceRemaining) left, so users are prompted to change their passwords upon login. The passwordExpirationInterval attribute sets the password expiration interval to 90 days (7776000 seconds).
One way to see which LDAP attributes are currently set for a particular object is merely to export that object to LDIF. When no attributes are specified for the LDAP export operation, then all (non-blank) attributes are returned. Here’s an example:
:: Script to export single eDirectory user with all attributes :: Set required variables @echo off set util="C:\novell\consoleone\1.2\bin\ice.exe" set server=NWSERVER set port=389 set authuser=cn=admin,o=main set /p passwd=Enter admin password: echo Enter the full LDAP DN of user to export (cn=user,ou=ou,o=org) ... set /p baseDN=Enter full LDAP DN: set objclass=user set scope=base set attrs=* set datadir=c:\data set outputfile=export1user.ldif :: Export Object from eDirectory using ICE %util% -S LDAP -s %server% -p %port% %sldap% %cert% -d %authuser% -w %passwd% -b %baseDN% -F objectClass=%objclass% -c %scope% -a %attrs% -D LDIF -f %datadir%\%outputfile% pause
Once again, the variables for this example are simply supplied by using “set” commands. Assuming that we entered an admin password of “password”, and an LDAP DN (BaseDN) of cn=TestUser1,ou=Atlanta,o=main, this yields a command line that specifies:
“C:\novell\consoleone\1.2\bin\ice.exe” -S LDAP -s NWSERVER -p 389 -d cn=admin,o=main -w password -b cn=TestUser1,ou=Atlanta,o=main -F objectClass=user -c base -a * -D LDIF -f c:\data\export1user.ldif
The parameter “-c base” denotes that the scope will be that single object, rather than any child objects (if any). The parameter “-a *” denotes all (non-blank) attributes, and is treated the same if no attributes are specified. That is, if the set command for “set attrs=*” was missing, or otherwise specified “set attrs=” (with no argument), the effect is the same: all (non-blank) attributes are returned with the ICE command.
A common administrative operation any network administrator may need to accomplish is to change particular attribute values on existing objects.
Let’s go back to our first example, where we exported user accounts with the attributes of “costCenter” and “costCenterDescription”. Let’s say that HR has made a change for those users, and now they need to be modified with new values: the costCenter has changed to “444440”, and the costCenterDescription has changed to “Inside Sales”.
We can take the LDIF file that we previously exported, and using the search-and-replace utility of our choice, modify all the entries which have the desired values, changing them to the new values, such as this:
version: 1 dn: cn=TestUser1,ou=Atlanta,o=MAIN changetype: modify replace: costCenterDescription costCenterDescription: Inside Sales dn: cn=TestUser1,ou=Atlanta,o=MAIN changetype: modify replace: costCenter costCenter: 444440 dn: cn=TestUser2,ou=Atlanta,o=MAIN changetype: modify replace: costCenterDescription costCenterDescription: Inside Sales dn: cn=TestUser2,ou=Atlanta,o=MAIN changetype: modify replace: costCenter costCenter: 444440 dn: cn=TestUser3,ou=Atlanta,o=MAIN changetype: modify replace: costCenterDescription costCenterDescription: Inside Sales dn: cn=TestUser3,ou=Atlanta,o=MAIN changetype: modify replace: costCenter costCenter: 444440
Let’s say we saved the modified file as “changeCC.ldif”. This modified LDIF file then becomes the input data file for the next LDAP operation using ICE. We import the new values, replacing the attribute values for costCenter and costCenterDescription for the selected entries.
The script used to input this new data is almost exactly the same as the one we used above to import users. The only change here is that we are using a different input data file, which we just modified.
:: Script to import eDirectory attributes from LDIF :: Set required variables @echo off set util="C:\novell\consoleone\1.2\bin\ice.exe" set datadir=C:\data set inputfile=changeCC.ldif set server=NWSERVER set port=389 set authuser=cn=admin,o=MAIN set /p passwd=Enter admin password: :: Import objects to eDirectory using ICE %util% -S LDIF -f %datadir%\%inputfile% -D LDAP -s %server% -p %port% %sldap% %cert% -d %authuser% -w %passwd% pause
Once the variables are replaced with their specified values (and again assuming we input the admin password of “password”), the resulting command would look like this:
"C:\novell\consoleone\1.2\bin\ice.exe" -S LDIF -f C:\data\changeCC.ldif -D LDAP -s NWSERVER -p 389 -d cn=admin,o=MAIN -w password
So we see the difference is entirely in the LDIF data file that is fed into the ICE command. Since the changetype is now specified as “modify”, ICE will look for existing user accounts that match the specified “dn:” entries in the LDIF file. For each entry located, it will look for the “costCenter” and “costCenterDescription” attributes and replace their values with the values specified in each entry (which, in our example, are the same for each entry).
You will notice, however, that for each attribute value change that we make (here we are making two changes per user) we have to make one entry in the LDIF file. This is why there are two separate entries (each beginning with “dn: “) for each user account in the example.
Another common task that can be accomplished with LDAP is to add users to groups. In eDirectory, the attribute which tells us to which group a user belongs is the groupMembership attribute.
Here is a same script that sets the groupMembership attribute for user objects. Once again, for the sake of simplicity, we are just using simple set commands in a batch file to simulate what you would probably accomplish in a real-world application with some more complicated scripting.
:: Script to apply groupMembership attributes from LDIF set util="C:\novell\consoleone\1.2\bin\ice.exe" set datadir=data set inputfile=GroupAdd.ldif set server=NWSERVER set port=389 set authuser=cn=admin,o=main set /p passwd=Enter admin password: :: Import the new attributes to objects in eDirectory using ICE echo Importing new data ... %util% -S LDIF -f %datadir%\%inputfile% -D LDAP -s %server% -p %port% %sldap% %cert% -d %authuser% -w %passwd% pause
Here is an example of what the input LDIF file (“GroupAdd.ldif”) might look like:
#GroupAdd.ldif version: 1 dn: cn=testuser1,ou=atlanta,o=MAIN changetype: modify add: groupMembership groupMembership: cn=attest,ou=atlanta,o=MAIN dn: cn=testuser2,ou=atlanta,o=MAIN changetype: modify add: groupMembership groupMembership: cn=attest,ou=atlanta,o=MAIN dn: cn=testuser3,ou=atlanta,o=MAIN changetype: modify add: groupMembership groupMembership: cn=attest,ou=atlanta,o=MAIN
We can see that the command script is essentially the same as we have been using all along, with only the input parameters being changed. And we can see from the input LDIF file that what we are accomplishing here is merely to add the value “cn=attest,ou=atlanta,o=MAIN” to the groupMembership attribute of our three test users.
Also, we see that the operation in our previous example, which was “replace” (to replace the attribute value), is now specified as “add” (to add an attribute value to a multi-valued attribute). If we had used “replace”, then any existing groupMembership attribute values would have been cleared and replaced with the new one.
While this method of adding group membership is certainly effective, it can also leave something to be desired. For example, when we add an eDirectory user to a group – or add a group to a user object – using iManager or ConsoleOne, then these utilities perform what we call “referential integrity” for the objects in question. That is, if a user (attribute value) is added to a group object, then the utility ensures that the groupMembership (attribute value) is also added to the user object, and vice versa, so that the two objects are “in sync”, or have integrity in reference to each other.
But when we merely add the groupMembership attribute value directly to the user object using LDAP, there is no mechanism employed to ensure that we have referential integrity between the user object and the group object. For example, in using the script above, we would see this information, looking at the user from ConsoleOne:
But when we look at the corresponding group object in ConsoleOne, we don’t see that the user was added, since the “member” attribute of the group object was never changed:
So if we choose to use LDAP to add users to groups (or groups to users), then we have to perform our own referential integrity check from within our scripts. Primarily, whatever method we choose to employ to build the input LDIF file needs to add entries, not only to modify the user objects, but to modify the corresponding group objects as well. For example:
#GroupAdd.ldif version: 1 dn: cn=testuser1,ou=atlanta,o=MAIN changetype: modify add: groupMembership groupMembership: cn=attest,ou=atlanta,o=MAIN dn: cn=attest,ou=atlanta,o=MAIN changetype: modify add: Member Member: cn=testuser1,ou=atlanta,o=MAIN
Once we have implemented this (let’s assume we included the other two users, as well), then we can check our results and see that now the group object has been also appropriately modified:
So now we have achieved referential integrity – the groupMembership attributes are set correctly for the user objects, and the member attributes are set properly for the group object.
These examples may be “sandbox” (basic) to those who are already actively using LDAP, in all its simplicity and in all its complexity, to manage their network environment. This article is intended to be just a starting point to introduce the uninitiated to the power and effectiveness of the relatively simple LDAP protocol.
Do have your own examples of how to use LDAP to help manage our networks (especially Novell – but not necessarily NetWare networks)? Perhaps you would like to share them with the rest of us. We are all always on the lookout for “cool” solutions.
Disclaimer: As with everything else at NetIQ Cool Solutions, this content is definitely not supported by NetIQ, so Customer Support will not be able to help you if it has any adverse effect on your environment. It just worked for at least one person, and perhaps it will be useful for you too. Be sure to test in a non-production environment.