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 and jump right in:

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 optimized for reading. You’ll need writing to for it to be useful, 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 we have a read-optimized database and 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 that software agrees on. 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. Importantly an object can be of arbitrary number of classes. That makes it possible to authenticate Windows and UNIX users against the same object for example.

Addressing

So, how do you access the data you fill in? LDAP databases are hierarchical and 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 your top directory using domain components (dc): 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. The abusive use of abbreviations (cn, dn, ou, dc…) is also one of the reasons why LDAP appears so confusing at first.

Accessing

Now that we know what LDAP is and how to address specific entries…how do you actually access them? There’s neat GUI tools for browsing and editing LDAP directories like Apache Directory Studio or JXplorer. In production, you’ll probably use one of the language bindings (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

Let’s go for the harder parts next. 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’ll 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.