| < Day Day Up > |
|
We were introduced to LDIF (LDAP data interchange format) in Chapter 2, when we played around with some of the LDAP command lines. We learned that LDIF is a way of representing the directory in a human-readable format. However, some questions remained open. For example, how do we use special characters required in some languages, such as the "umlaut" characters in German or binary data? In this section, we take a deeper look at the LDIF format.
The initial use of the LDIF format was for the University of Michigan Project, where LDIF was used to describe the entries held in the directory. Exhibit 18 shows a piece of our example directory dumped as an LDIF file. As you can see, it is the same format the command-line tool "ldapmodify -a" uses as input to load bulk data into the directory. The LDIF format is defined in RFC 2849, "The LDAP Data Interchange Format (LDIF) — Technical Specification." The file format now has two different purposes:
dn: o=LdapAbc.org objectClass: top objectClass: organization o: LdapAbc.org dn: ou=IT,o=LdapAbc.org objectClass: top objectClass: organizationalUnit ou: IT dn: ou=HR,o=LdapAbc.org objectClass: top objectClass: organizationalUnit ou: HR dn: ou=Marketing, o=LdapAbc.org objectClass: top objectClass: organizationalUnit ou: Marketing dn: ou=Research, o=LdapAbc.org objectClass: top objectClass: organizationalUnit ou: Research dn: uid=RVoglmaier,ou=IT,o=LdapAbc.org objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson cn: Reinhard Erich Voglmaier sn: Voglmaier givenName: Reinhard Erich ou: IT uid: RVoglmaier mail: RVoglmaier@LdapAbc.org
Description of directory entries
Update of directory entries
As the name suggests, LDIF is a data interchange format that provides a description of directory entries. Because LDIF is an ASCII format, the data can be exported easily from one directory and imported into another. With the data in ASCII format, the database can easily be transported over different architectures, e.g., from a directory running on Win2000 to UNIX. Furthermore, you can dump data from a legacy system or, indeed, from any repository, convert it into LDIF, and then import it into a directory.
The second purpose of LDIF is to describe the changes to be made to a directory. We used this capability in Chapter 2 when we applied the "ldapmodify" utility to change the directory. Exhibit 19 shows an example. Because there is a one-to-one relationship between the operations on the directory and the change-records defined in LDIF, you can use LDIF to describe the changes to be made to the directory.
dn: uid=JParker, ou=IT, o=LdapAbc.org changetype: modify replace: mail mail: Jparker1@LdapAbc.org dn: uid=MJohnson, ou=Marketing, o=LdapAbc.org changetype: delete
It is clear that the formats for the "description" and "update description" differ slightly. In the following, we will examine both formats in greater detail.
The LDIF file contains a dump of a number of entries. Each entry consists of a number of records, and each record is listed separately on a new line. Every entry consists of three parts:
The distinguished name that uniquely identifies the entry
The object class or object classes the entry belongs to
The attributes with their respective value or values
Exhibit 18 shows a real-life example of directory entries. Exhibit 20 is a formal representation of what an LDIF file describing directory entries should look like.
dn: <Distinguished Name> objectClass: <ObjectClass> objectClass: <ObjectClass> ?.. <attribute name>: <attribute value> ??.
There are a few exceptions to the described format of an LDIF file. For example, even though each record is separated by a new line, an attribute value could occupy more than one line. In this case, a white space at the beginning of a line indicates the continuation of the previous line. In LDAP parlance, this is called a "folded-attribute value." See Exhibit 21 for an entry containing a record with a folded-attribute value.
version: 1 dn:cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com objectclass:top objectclass:person objectclass:organizationalPerson cn:Barbara Jensen cn:Barbara J Jensen cn:Babs Jensen sn:Jensen uid:bjensen telephonenumber:+1 408 555 1212 description:Babs is a big sailing fan, and travels extensively in search of perfect sailing conditions. title:Product Manager, Rod and Reel Division
You also could have an entry containing binary data. Such data has to be encoded using base-64 encoding. There are libraries available for base-64 encoding for most programming and scripting languages. An entry containing a photo would look like the example in Exhibit 22. Instead of encoding the JPEG photo using base-64, you could also include an external file in the LDIF file. Exhibit 23 shows an example.
dn: uid=JMiller,ou=IT,o=LdapAbc.org objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson cn: Johann Miller sn: Miller givenName: Johann ou: IT jpegPhoto::V2hhdCBhIGNhcmVmdWwgcmVhZGVyIHlvdSBhcmUhICBUa GlzIGlzIGJhc2UtNjQtZW5jb2RIZCBiZWNhdXNlIGl0IGhhcyBhIGNvbnRl VyIGluIGl0IChhIENSKS4NICBCeSB0aGUgd2F5LCB5b3Ugc2hvdWxkIH JlYWxseSBnZXQgyb2wgY2hhcmFjdGb3V0IGlvcmUu uid: JMiller mail: JMiller@LdapAbc.org
dn: uid=JMiller,ou=IT,o=LdapAbc.org objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson cn: Johann Miller sn: Miller givenName: Johann ou: IT jpegPhoto: < file:///opt/directory/photos/jmiller.jpg uid: JMiller mail: JMiller@LdapAbc.org
You can also use attributes with language-specific values. These attribute values then have to be UTF-8 encoded. UTF-8 (Unicode Transformation Format-8) is an encoding that produces human-readable strings. It is a standard encoding system defined by the Unicode Consortium. Users in the United States will seldom be bothered with data in different character sets. Luckily, the ASCII characters in UTF-8 are represented with the same 7-bit code as in ASCII. If you need more information, see RFC 2253, "Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names." For more information about the LDIF syntax, refer to RFC 2849, "The LDAP Data Interchange Format (LDIF) — Technical Specification."
Now let us have a look at the update description, the second form of an LDIF file. This second form describes the modification applied or to be applied to a directory. It reflects the actions upon the directory described by the functional model (see the functional model in Chapter 3 for additional details). The syntax of this LDIF format is quite similar to the one previously described. The encoding rules are the same, so they will not be repeated here.
Recall that the functional model knows three types of functions:
Interrogation operations: search, compare
Update operations: add, delete, modify DN, modify
Authentication and control operations: bind, unbind, abandon
The functions used by the LDIF format are the update operations, i.e.,
The add function
The delete function
The modifyDN function
The modify function
The general syntax of these operations is:
dn: <Distinguished Name> changetype: <Type of Operation> <attribute name>: <attribute value> <attribute value>... ...
Let us see one example for each of these operations to get a better idea of what they look like. Here we use "Barbara Jensen" as an example, also called "Babs," which is also used for examples in the RFCs and in the LDAP literature.
The changeType "add" indicates that a new entry is to be added to the directory.
Syntax —
dn: <Distinguished Name> changetype: add <attribute name>: <attribute value> <attribute value>... ...
Example —
dn: uid=BJensen, ou=IT,o=LdapAbc.org changetype: add objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson cn: Barbara Jensen cn: Babs Jensen sn: Jensen givenName: Barbara ou: IT jpegPhoto: < file:///opt/ directory/photos/BJensen.jpg uid: BJensen mail: Bjensen@LdapAbc.org
The changeType "delete" indicates that the entry with the DN has to be deleted from the directory.
Syntax —
dn: <Distinguished Name> changetype: delete
Example —
dn: uid=CMiller, ou=IT,o=LdapAbc.org changetype: delete
The modifyDN function can:
Change only the RDN
Move the entry in the directory information tree
Both of the above
Note that in LDAP (v2), the moving of entries in the DIT is not allowed. You have to delete the old entry and create a new one instead of moving it.
Syntax —
dn: <Distinguished Name> changetype: moddn [newsuperior: <Distinguished Name of new superior>] [deleteoldrdn:] (0 | 1) [newrdn: <new RDN>]
Examples — Let us assume that Barbara Jensen moves from IT to Marketing. This calls for a change of the DN, a new superior entry, and removal of the old entry. The resultant changes in the LDIF file are as follows:
dn: uid=BJensen, ou=IT, o=LdapAbc.org changetype: moddn newrdn: uid=Bjensen newsuperior: ou=Marketing, o=LdapAbc.org deleteoldrdn: 1
The new DN of Barbara Jensen is now: uid = Bjensen, ou = Marketing, o = LdapAbc.org
Now let us see an example where the RDN changes. For example, we have a new person that would have a uid in conflict with an existing one. Let us assume there will be a new person — Joseph Smith, uid = JSmith — and that there is already a uid "JSmith" from a person "James Smith." Let us further assume that the IT department will avoid future conflicts by naming the new JSmith as JSmith1 and the original as JSmith0. To make the change in the directory, we would say:
dn: JSmith, ou=Marketing, o=LdapAbc.org changetype: moddn deleteoldrdn: 1 newrdn: JSmithl
One word about renaming an entry that has children. This action would require renaming an entire subtree. If you would like to do this via ldapmodify, you have to do this in three steps:
Create a new entry with the new DN
Change the RDN of all children
Delete the old DN
Again, an example makes this clear. Let us assume that Human Resources is to be changed to HR (perhaps to avoid typos).
dn: ou=HR, o=LdapAbc.org changetype: add objectclass: top objectclass: organizationalUnit ou: HR dn: PSmith, ou=Human Resources, o=LdapAbc.org changetype: modrdn newrdn: PSmith deleteoldrdn: 1 newsuperior: ou=HR, o=LdapAbc.org dn: ou=Human Resources, o=LdapAbc.org changetype: delete
The second step you have to do for all children. If the children have further children, you have to create down the hierarchy all entries having children, rename the children, and delete the old entries. If you have an extensive subtree, this can become painful. The alternative is to export the whole directory, rename all "ou = Human Resources" into "ou = HR," physically delete the old directory files, and import the new directory. We will see more about this in Chapter 7, "LDAP Directory Server Administration."
The modify function changes the attributes of an entry in the directory. Thus, there are three different types of modification:
Add an attribute
Delete an attribute
Replace an attribute
Syntax —
dn: <Distinguished Name> changetype: modify <modify type> <attribute> [<attribute name> <attribute value> ? ]
Examples — The following example shows the addition of a mobile attribute to the entry with distinguished name JSmith1, ou = Marketing, o = LdapAbc.org.
dn: JSmithl, ou=Marketing, o=LdapAbc.org changetype: modify add: mobile mobile: 0170 64738 8374 377
The following would change the new assigned number:
dn: JSmith1, ou=Marketing, o=LdapAbc.org changetype: modify replace: mobile mobile: 0170 64738 8374 477
The following would delete the number again:
dn: JSmithl, ou=Marketing, o=LdapAbc.org changetype: modify delete: mobile
One common task is synchronizing the directory with a database or some other data source. The data source provides a listing of all entries, and this list can be used to update the directory so that the LDAP database reflects the new data.
The first approach is to look up every entry to see if it exists and verify that the values are still the same. This entails sending all the data over the network to the directory so that the values can be compared with those held in the directory. If the number of employees in your enterprise is not too high (several hundred), this could be a reasonable solution. However, it can be problematic if there are any restrictions on the queries you can make against the directory.
Another alternative is to obtain a dump of the entries in a plain file, e.g., database.txt. When the sync request arrives, you rename the old file database.old.txt and produce, via the diff system call or a customized utility, an instruction list that updates the directory. This instruction list can then be printed out in the LDIF format. In order to do this you need three functions:
deleteEntry: Deletes an entry from the directory
addEntry: Adds a new entry to the directory
modEntry: Modifies an entry in the database
The Perl functions shown in Exhibit 24 generate an LDIF file that reflects these changes in the directory. The first function that we need is the addEntry function, which takes an associative array as a parameter. The array contains the attribute names as keys.
sub addEntry { my ($EntryPtr) = $_ ; my %Entry = %{EntryPtr} ; my @objectClasses = ("top,""organizationalPerson","inetOrgPerson"); printf("dn: uid = %s, ou=%s, o=%s\n",$Entry{uid}, $Entry{ou}, $Entry{o}) ; printf("changetype: add\n"); foreach $class (@objectClasses) { printf("objectClass: %s\n",$class); } printf("sn: %s\n",$Entry{sn}); printf("cn: %s %s\n",$Entry{givenName}, $Entry{sn}); printf("givenName: %s\n", $Entry{givenName}); printf("ou: %s\n",$Entry{ou}); printf("telephoneNumber: %s\n", $Entry{phone}); printf("mail: %s\n",$Entry{mail}); printf("uid: %s\n",$Entry{uid}) ; printf("\n\n"); }
The second function that we need is the delEntry function. The simplest of the three, it is as follows:
sub delEntry { my ($EntryPtr) = $_ ; %Entry = %{$EntryPtr} ; printf("dn: uid = %s, ou=%s, o=%s\n",$Entry{uid}, $Entry{ou}, $Entry{o}) ; printf("changetype: delete\n"): }
The third function, modEntry, is something different. To handle all possible situations while keeping the code simple, it first deletes the entry and then adds the entry with the new values:
sub modEntry { my ($EntryPtr) = $_ ; delEntry($EntryPtr); addEntry($EntryPtr) ; }
What is missing is a program uniting all of these operations: First, we define the values needed for all three functions, in this case only the name of the enterprise. The program then loads the ASCII file of the directory data in an associative array. Then we execute a function that looks to see whether the values of the attributes in the entries and the database are the same. For each "yes," it deletes the corresponding row in the associative array. If there is a discrepancy, it calls the function "modEntry" to resolve the discrepancy and then deletes the row in the associative array. If there is no line corresponding to the entry, it calls the function "addEntry." After processing all entries, the program looks to see whether there are any rows remaining in the associative array. If there are, then this indicates that some of the entries have been deleted in the data source. To deal with these lines, the program then calls the "delEntry" function. Exhibit 25 shows the code to perform all of these functions.
%DirectoryEntries = getDirectoryEntries(); %NewEntries = getNewEntries(); foreach $dn (keys %NewEntries)) { if (! $DirectoryEntries{$dn}) { addEntry($DirectoryEntries{$dn}) ; } else { if ( $DirectoryEntries{$dn} == $NewEntries{$dn}) { delete($DirectoryEntries{$dn}) , } else { modEntry($DirectoryEntries{$dn}) ; delete($DirectoryEntries{$dn}) ; } } } foreach $dn (keys % DirectoryEntries) { delEntry($NewEntries($dn)) ; } putDirectoryEntries(%NewEntries);
The functions still missing are getDirectoryEntries and its counterpart putDirectoryEntries. These could be functions that simply write down and read the entries in the form "key: value." The key is then used for the lookup. Another function that is missing is the one that produces the NewEntries hash or associative array, which should have the same syntax as the two previously mentioned functions. It could also be interesting to use objects instead of these hashes. In this case, the addEntry, delEntry, and modEntry functions could be implemented as methods.
The program logic also works if we simply install all entries new from scratch. In this case, the hash DirectoryEntries is empty, so the statement:
if (! $DirectoryEntries{$dn}
is always true and the entry is added.
The program produces a file in the LDIF format that can be used to update the directory with the command "ldapmodify."
Just for completeness, the Net::LDAP package has an object called Net::LDAP::LDIF that reads and writes a complete LDIF file in a single step. Have a look at the Web site dedicated to the perl package or consult the documentation delivered with the software distribution. Software and documention are available at the Net::LDAP project site: http://perl-ldap.sourceforge.net.
| < Day Day Up > |
|