LDAP: a Gentle Introduction

The perception of LDAP (Lightweight Directory Access Protocol) is ambivalent. On the one hand, it is widely supported as a common authentication backend. On the other hand, there’s very little and poor documentation that is mostly targeted towards a special case (e.g. replacing NIS by LDAP).

Although being mainly a developer, I’ve been lately heavily exposed to LDAP and would like to give a very gentle introduction into this field, to make the first step easier to others who have to grok this technology.

Introduction

So what is LDAP? I’m going to spare you the details of its history, which is a fundamental part of each LDAP-covering text I’ve been criticizing. So right ahead:

An LDAP server is a database.

A database with some special attributes which make it a directory. One of the most fundamental is: It is (or should be) highly optimized for reading. Writing is also needed, but essentially it’s about reading. Therefore it’s perfect for any kind of “white pages” or configurations. Accordingly, it’s mostly known for the usage as a centralized address book or for authenticating.

So far everything should be clear: We have a read-optimized database. This database consists of objects, which have attributes. The next important feature are predefined schemes which should make it easy to adopt LDAP by having conventions for object types, most software shall obey. For example, for address books, inetOrgPerson is fine. It contains attributes for most information about people, you’ll ever need. These schemes are also specified using attributes of the object called objectClass – so the objects are self-describing which helps parsing them. Necessary to say, that a object can be of arbitrary number of classes. That makes it for example possible to authenticate Windows and UNIX users against the same object.

Addressing

So, how do you access the data you fill in? LDAP databases are hierarchical, the addressing works from right to left. First of all, entries have usually a common name attribute, called “cn”. So for example “cn=hynek”. Now, you’re probably going to divide the (in this case) persons into groups, called organizational units. Assuming our group is “users”, we’ve got “cn=hynek,ou=users”. These “ou” can be as deep and branched as you like. Finally, you define using domain components (dc) your top directory: cn=hynek,ou=users,dc=ox,dc=cx. This would be the absolute “address” of an entry, these addresses are called distinguished names (in short “dn”) in LDAP. These abusive uses of abbreviations (cn, dn, ou, dc…) is also one of the reasons, why LDAP appears so confusing at first.

Accessing

Okay, we know what LDAP is and how to address specific entries…how do you actually access them? A neat tool for browsing and editing LDAP directories are Apache Directory Studio and JXplorer. When really in use, you’ll probably use one of the APIs (e.g. for Python, Perl or Java) or the LDIF format which allows manipulation using text files. The best way to understand it, is to tinker a bit with all of them.

For querying the directory, there is a whole language including logical ands and ors. If you just search a specific cn, the expression would be: (cn=foobar). If you search someone with the cn “foo” or the cn “bar”, you might just say (| (cn=foo) (cn=bar)) . Isn’t that hard, is it? One last thing you might encounter pretty often is the so called search base. With it, you define under which hierarchy the specified expression is supposed to be searched. In the example above, such a search base might be ou=users,dc=ox,dc=cx.

Putting It All Together

To add an example how such query might look, let’s search for the address book entry “hynek” inside ou=users,dc=ox,dc=cx:

$ ldapsearch -x '(cn=hynek)' -b 'ou=users,dc=ox,dc=cx'
# extended LDIF
#
# LDAPv3
# base ou=users,dc=ox,dc=cx> with scope sub
# filter: (cn=hynek)
# requesting: ALL
#
# hynek, users, ox.cx
dn: cn=hynek,ou=users,dc=ox,dc=cx
cn: hynek
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
sn: Schlawack
givenName: Hynek
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1

The output looks confusing but is harmless. The essential data begins after # hynek, users, ox.cx and ends with the givenName attribute. As I didn’t fill many attributes (just “givenName” and “sn” (aka surname)), there isn’t much to see. Interesting are the objectClass attributes which describe the object though.

A little pain for the end…

Now let’s go for the harder parts. LDAP, I’d say, suffers a bit from its heritage. Everything is built around numerical IDs which require you to have an OID before defining valid own schemes. Making it even more complicated: every object (e.g. “Person”) you define and every attribute (e.g. “Name”) has an own ID, beginning with the OID. The nice thing about this is that you can rename painlessly attributes. The bad thing is, that it looks awful.

One last OID pain: The datatypes (e.g. “Unicode String”) are IDs too. So for a 16-character sized Unicode string, you have to write:

1.3.6.1.4.1.1466.115.121.1.15{16}

Here’s an example from the shipped schemas from OpenLDAP for an attribute:

attributetype ( 2.5.4.9 NAME ( 'street' 'streetAddress' )
	DESC 'RFC2256: street address of this object'
    EQUALITY caseIgnoreMatch
	SUBSTR caseIgnoreSubstringsMatch
    SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )

A very fine tutorial for writing own schemes has been released in the Linux Gazette.

Epilogue

After reading this, you’re far away from fully understanding LDAP. But I feel, that diving through other – less gentle – docs should be a lot easier now.