+--------+ +--------+ | LDAP |------------->| LDAP | | Client | | Server | +--------+ +--------+The client opens a connection using TCP, by default to port 389 [1]. It may then log in - this is known as "binding" [2]. If it has no credentials with which to authenticate, it can bind as "anonymous".
In this respect, you can consider that LDAP is very like FTP. In fact, the similarities don't end there:
Unlike FTP and many other Internet protocols, LDAP is too complicated to send queries by hand using 'telnet'. For example, simply binding as anonymous requires the following binary data to be sent:
30 0c 02 01 01 60 07 02 01 02 04 00 80 00I don't have a clue how you get that stream of bytes from the LDAP specification, which refers you to ITU-T X.690 for details. I found it out using strace:
# strace -s100 -xx ldapsearch -b "dc=wibble,dc=org" "uid=root" 2>dumpand you can confirm it works by sending the same data [4].
Suffice to say, just don't bother trying to drive ldap manually; use someone else's tools instead.
dc=wibble,dc=org | | +-----------------+------------------+ | | | cn=Fred Bloggs, cn=Joe Smith, ou=printers, dc=wibble,dc=org dc=wibble,dc=org dc=wibble,dc=org | | +---------------------------+ | | cn=laserjet,ou=printers, cn=epson,ou=printers, dc=wibble,dc=org dc=wibble,dc=orgDistinguished names are read from right to left, starting at the top of the hierarchy and going downwards. This is the same as the DNS, where for example in "andrew.cmu.edu" the top-level domain is "edu", the next level down is "cmu" and the next level is "andrew".
LDAP itself doesn't force a particular naming hierarchy; it's all data-driven and you can design your own if you really want. However there are two in common use:
Existing DNS domain names can be mapped directly into the LDAP namespace [5], which saves you having to invent new names. As long as you have a "real" DNS domain name, which is globally unique, it ensures that your LDAP distinguished names are globally unique as well.
You use "dc=" (domain component) for each part of the domain name. For example, if your domain is "wibble.org" it would have a DN of
dc=wibble, dc=orgThe person called Fred Bloggs in your organisation could then be
cn=Fred Bloggs, dc=wibble, dc=orgcn stands for "Common Name". Note that it is quite OK for these identifiers to include spaces. Actually, LDAP is 8-bit clean and will happily store binary data such as JPEG photos [6].
To identify a login account at that domain, you can use "uid=" (user id). So, the user "root@wibble.org" would have a DN of
uid=root, dc=wibble, dc=org
X500, from which LDAP draws on heavily, has its own hierarchy [7]:
cn=Fred Blogs, ou=Accounts, o=Wibble Widgets Ltd, c=GBou=Organisational Unit, o=Organisation, c=Country. It can get much more complicated than this (e.g. you can add State/Province and Locality into the DN). The advantage of this approach is that you can present information to the world as a sort of LDAP "telephone book".
For me, the main disadvantage is that I don't know where you go to get an Organisation name allocated which is guaranteed to be unique within a particular Country. For this reason, using a DN which is derived from your domain name is probably a safer bet.
cn=Fred Bloggs,dc=wibble,dc=org Distinguished Name (DN) cn=Fred Bloggs Relative Distinguished Name (RDN)Note that there are a few rules when setting up a hierarchy of DNs:
Space at beginning or end of dn | \space |
# at beginning of dn | \# |
, | \, |
+ | \+ |
" | \" |
\ | \\ |
< | \< |
> | \> |
; | \; |
o=Incredible Widgets\, Ltd.,c=GB
For example, suppose you are using LDAP to emulate the /etc/hosts file. When you lookup a host using an LDAP query, this is what you might get back:
objectclass: ipHost +-------------------------+ cn=localhost,dc=wibble,dc=org --------> | iphostnumber: 127.0.0.1 | +-------------------------+Here, the DN we are using points to an entry of class "ipHost". This class contains one field, namely, "iphostnumber" which is the IP address we want. iphostnumber is a required field in an "ipHost" object, because it wouldn't make sense to leave it out.
Here is another example:
objectclass: person +-------------------------------+ cn=Fred Bloggs,dc=wibble,dc=org ------> | cn: Fred Bloggs | | sn: Bloggs | | telephoneNumber: 800 123 4567 | | description: blue eyes | +-------------------------------+This object class, "person", contains fields which are relevant to a human being. In this particular class, "cn" and "sn" are required fields, whilst telephoneNumber and description are optional.
For example, the "person" class defined above has very few fields in it. So there is also another class called "organizationalPerson" which has more detail in it, specific to a person working at a company.
objectclass: organizationalPerson +-------------------------------+ cn=Fred Bloggs,dc=wibble,dc=org ------> | cn: Fred Bloggs | | sn: Bloggs | | telephoneNumber: 800 123 4567 | | description: blue eyes | | title: Head of Accounts | | postalAddress: .... | +-------------------------------+This class "organizationalPerson" is a subclass of "person". The advantage of building a class hierarchy like this is that a client which only understands a "person" record can still process data from an "organizationalPerson" record.
All classes ultimately derive from the top-level class "top". So the class hierarchy of what we have seen so far is:
top | +-----------+----------+ | | person ipHost | | organizationalPersonThe objectclass hierarchy is completely separate from the distinguished name hierarchy.
Using the OpenLDAP server, you can find definitions of the fields required and allowed for each objectclass in /etc/openldap/schema/
The standard classes like "person" and "organizationalPerson" are defined in RFC 2256; an extended class called inetOrgPerson is defined in RFC 2798.
[1] RFC 2251 (LDAP v3) section 5.2.1
[2] RFC 2251 (LDAP v3) section 4.2
[4] Simple perl program to send that LDAP bind in binary:
#!/usr/bin/perl -w use strict; use Socket; my($remote,$port,$iaddr,$paddr,$proto,$line); $remote = 'localhost'; $port = 389; $iaddr = inet_aton($remote) or die "no host: $remote"; $paddr = sockaddr_in($port, $iaddr); $proto = getprotobyname('tcp'); socket(SOCK, PF_INET, SOCK_STREAM, $proto) or die "socket: $!"; connect(SOCK, $paddr) or die "connect: $!"; $line = pack("H28", "300c020101600702010204008000"); syswrite (SOCK, $line, 14); sysread (SOCK, $line, 14); print STDERR "Response: ", unpack("H28", $line), "\n"; close (SOCK); exit;Of course, you'd never do it that way in real life. Rather, you would use an API (Application Programming Interface) to do the encoding and decoding of LDAP strings. For example, in perl you'd use perldap, available from www.mozilla.org or as a port in FreeBSD ('ldapsdk' and 'perldap'). So this becomes:
#!/usr/bin/perl use Mozilla::LDAP::Conn; $who=""; $passwd=""; $ld = new Mozilla::LDAP::Conn("ldap.itd.umich.edu", "389", $who, $passwd); die "Bind failed\n" unless $ld; print "Bind succeeded\n"; $ld->close;Warning: I found that ldapsdk and openldap have conflicting "libldap.so" libraries, which meant that the above example did not work if openldap was installed at the same time. I'm not sure about the right way to fix this, it probably involves fiddling with the library path.
[5] RFC 2247 (Using Domains in LDAP/X.500 Distinguished Names) and RFC 2377 (Naming Plan for Internet Directory-Enabled Applications)
[6] The OpenLDAP server uses base64 encoding when exporting binary data in textual form. The second of these two examples includes a newline in the data:
# echo -n "testing" | ldif cn cn: testing # echo "testing" | ldif cn cn:: dGVzdGluZwo=
[7] RFC 2256 (A Summary of the X.500(96) User Schema for use with LDAPv3)