Sharing an Address Book
via an LDAP Server
using
Mozilla Thunderbird
Versions 1.0 and 1.5
Traduzione Italiana Traduzione Italiana Traduzione Italiana

Introduction

There are many guides to help you connect to an existing Address Book Server so you can use someone else's already shared address book. This guide takes a different tack:  it assumes that you want to share your own address book amongst several computers. To do this, you need to learn how to

This will take us into the world of LDAP (Lightweight Directory Access Protocol) and LDIF (LDAP Data Interchange Format), as well as other equally obscure acronyms and abbreviations. As an example, I'll use Mozilla Thunderbird, a free open source email program. As of this writing, Thunderbird allows reading from its Address Book Server, but not writing to it.

Along the way, I'll describe some of the problems Tricky Part I ran into and the solutions (or workarounds) I came up with, as well as some of the choices A Choice Was Made available to you, and notes Notes.

LDAP Servers

What I referred to above as an Address Book Server is actually a more general (and more complicated) object called an LDAP server. To learn more about LDAP, search Google (where you'll get nearly 7 million hits), or browse through ZyTrax's excellent Open Source Guide - LDAP for Rocket Scientists.

Getting Started

Follow these steps to setup your own LDAP server:

  1. Download a free LDAP server from somewhere (OpenLDAP, etc.) and unpack it on your machine. I won't go into detail on this step as there are so many such servers and systems on which to install the server. Windows users can get an executable version from Lucas Bergman.

  2. Download a schema for the address book you are using. A schema is a description of the elements of your address book such as given name, surname, home telephone number, etc., in a format understood by the LDAP server.

    Thunderbird users might think that they can get a schema for that address book from the list of schema mentioned in Bugzilla. As you'll see from that link, their address book schema is still in flux.

    There are many choices of schema, all of which will involve some editing of either the schema file or your address book entries or both. The following discussion assumes that you chose none of the schema mentioned in Bugzilla, and instead chose (of all things) an obsolete one (for TB 1.0) or an Alpha one (for TB 1.5). Save whichever file you choose as (say) mozillaAbPerson.schema in the same directory as the other schema that came with your LDAP server.

    When copying and pasting the alpha schema from above for use in OpenLDAP, the leading space on every line has to be removed or the LDAP server will complain about the schema having undefined values.

  3. Edit the server's configuration file (which is almost always named slapd.conf) to include the following lines:

    include  core.schema
    include  cosine.schema
    include  inetorgperson.schema
    include  mozillaAbPerson.schema

    The above lines are necessary to define the schema on which the address book will rely. Schema rely upon other schema in a tree structure which is why you need three extra schema besides the one for Thunderbird. The first three schema almost certainly are included with your LDAP server.

    Some LDAP servers place the schema in a separate directory, so you might need to use a path name before the schema name.

    The configuration file includes lines beginning with suffix and rootdn. Replace those lines with the following:

    suffix "dc=YourDomain,dc=com"
    rootdn "cn=Manager,dc=YourDomain,dc=com"

    The value in the suffix entry is what distinguishes one database from another. Although I don't discuss this any more, LDAP servers support multiple databases. They are defined in slapd.conf where each group of entries which forms a database begins with the entry database, followed by, for example, suffix, rootdn, rootpw, directory, index, etc.

    The value in the suffix entry is the suffix to the Base DN (see below). The value in rootdn is the entry with full access to the database.

    Replace dc=YourDomain,dc=com with something appropriate to your location. Beyond that, the suffix and rootdn entries can take many different forms, so if you know what you are doing, feel free to change the above lines, but beware that I'll refer to the above values elsewhere so you'll need to change them there, too. Make up whatever name you like in place of Manager.

    Quite likely, the next line in the configuration file specifies the root password:

    rootpw secret

    The above line should be changed to something unique to you and your LDAP server. However, the following description uses this password, so change it where it occurs below to your choice. Also, see the comments which came with your slapd.conf about securing this password with strong authentication.

  4. Start the LDAP server (usually by typing slapd).

Initalizing the Server

  1. Initialize your suffix in the server as follows (a sample init.ldif file can be downloaded from here):

    Define a text file (say, init.ldif) starting with the following contents:

    dn: dc=YourDomain,dc=com
    objectClass: top
    objectClass: organization
    objectClass: dcObject
    description: Description of your domain
    o: Something else about your domain
    dc: YourDomain

    The argument to both the lefthand dc= and the dc: comes from the suffix entry defined in slapd.conf.

    Make up whatever you like for the values of o: and description: — you won't be using either of them again.

    When appending additional sections (beginning with dn:) to this file be sure to separate them from each other by a blank line so the LDAP server can tell them apart.

  2. To separate address books into their own hierarchy, append to the init.ldif file something like the following section:

    dn: ou=AddressBooks,dc=YourDomain,dc=com
    objectClass: top
    objectClass: organizationalUnit
    description: The address books
    ou: AddressBooks

    To define multiple address books (two in the example below), append the following sections:

    dn: o=Home,ou=AddressBooks,dc=YourDomain,dc=com
    objectClass: top
    objectClass: organization
    description: Home sweet home
    o: Home

    dn: o=Work,ou=AddressBooks,dc=YourDomain,dc=com
    objectClass: top
    objectClass: organization
    description: Work sweet work
    o: Work

    The individual address book's Base DNs are

    o=Home,ou=AddressBooks,dc=YourDomain,dc=com    and
    o=Work,ou=AddressBooks,dc=YourDomain,dc=com

    Use these DNs to identify which address book you wish to access. If you want to retrieve addresses from all address books to which you have access, use

    ou=AddressBooks,dc=YourDomain,dc=com

  3. To separate users into their own hierarchy, append to the init.ldif file something like the following section:

    dn: ou=Users,dc=YourDomain,dc=com
    objectClass: top
    objectClass: organizationalUnit
    description: Users who have access to at least one address book
    ou: Users

    To add individual users, append as many of the following sections as there are address book users:

    dn: cn=me,ou=Users,dc=YourDomain,dc=com
    objectClass: top
    objectClass: person
    cn: me
    sn: Smith
    description: Bob Smith
    userPassword: mysecret

    dn: cn=you,ou=Users,dc=YourDomain,dc=com
    objectClass: top
    objectClass: person
    cn: you
    sn: Jones
    description: Stan Jones
    userPassword: yoursecret

    The individual user's Bind DNs (also called User DNs) are

    cn=me,ou=Users,dc=YourDomain,dc=com    and
    cn=you,ou=Users,dc=YourDomain,dc=com

    Each user presents a DN like the above (in conjunction with the appropriate password) to log into the LDAP server.

    By the way, each user's password must be non-empty, or the LDAP server will reject attempts to login, instead responding with the cryptic message

    Error 53: DSA is unwilling to perform

    Curiously, the LDAP server will accept an empty password in a userPassword attribute — it just won't let you login with it.

    You do not need to define an entry for cn=Manager,dc=YourDomain,dc=com — it's enough for that name to be in the rootdn field in slapd.conf (and along with the password in rootpw) to allow it to be used for unrestricted access to your LDAP server.

  4. To handle groups of users, append to the init.ldif file the something like following section:

    dn: ou=Groups,dc=YourDomain,dc=com
    objectClass: top
    objectClass: organizationalUnit
    description: Groups of members for each address book
    ou: Groups

    Then, to group your users into separate address books, append these sections:

    dn: cn=Home,ou=Groups,dc=YourDomain,dc=com
    objectClass: top
    objectClass: groupOfNames
    description: Members of the Home Address Book group
    cn: Home
    member: cn=me,ou=Users,dc=YourDomain,dc=com
    member: cn=mine,ou=Users,dc=YourDomain,dc=com

    dn: cn=Work,ou=Groups,dc=YourDomain,dc=com
    objectClass: top
    objectClass: groupOfNames
    description: Members of the Work Address Book group
    cn: Work
    member: cn=me,ou=Users,dc=YourDomain,dc=com
    member: cn=you,ou=Users,dc=YourDomain,dc=com

    and include as many member: statements as you like as long as each has a corresponding entry in the ou=Users,dc=YourDomain,dc=com hierarchy.

    The hierarchical names used above such as Users and Groups are just my choices; substitute your own preferences as you like. Even the choice of structural classes such as ou are my choices; choose your own as you prefer.

  5. Now pass all of the above data to the LDAP server with a line similar to the following:

    ldapadd -x -D "cn=Manager,dc=YourDomain,dc=com" -w secret -v -f init.ldif

    The argument to -D is the Bind DN, and the argument to -w is the password, both defined in slapd.conf. The argument to -f is the file (prehaps preceded by a path) on which ldapadd is to operate. The -x switch says to use simple authentication instead of SASL (for simplicity, I won't use strong encryption), and the -v switch turns on verbose mode so if something goes wrong, you'll have more information. To display a list of all the command line options, type ldapadd ?.

    The program ldapadd supports reading the password from a file via the -y switch (e.g., -y Manager.pass) instead of an explicitly stated password using the -w switch. If you try this, and there is a trailing CR and/or LF appended to the end of the line with the password (as most text editors are wont to do), the operation will fail with the message ldap_bind: Invalid credentials (49) — remove the trailing CR and/or LF and all is well.

    If things work as expected, you should get a response similar to the following:

    ldap_initialize( <DEFAULT> )
    add objectClass:
           top
           organization
           dcObject
    add description:
           Description of your domain
    add o:
           Something else about your domain
    add dc:
           YourDomain
    adding new entry "dc=YourDomain,dc=com"
    modify complete

    followed by many more lines if you added users, groups, and/or groups of users. As mentioned above, a sample init.ldif file can be downloaded from here.

Security Considerations

The above section's suggestion that you might want to define individual users needs additional explanation. In particular, there's more to the issue than defining the user and group records (e.g., dn: cn=me,ou=Users,dc=YourDomain,dc=com, and dn: cn=Home,ou=Groups,dc=YourDomain,dc=com).

In particular, you need to also define ACPs (Access Control Policies) in your slapd.conf file. Suppose you want to limit different users to particular address books (e.g., Home and Work) as the above group entries suggest. There are many ways to accomplish this goal, such as the following:

# Allow users to write their own password; however, the
# principal use of this policy is to authenticate users.
access to attrs=userPassword
        by self write
        by anonymous auth

# Allow authenticated users, by group, access to their
# own address book and only their own address book.
access to dn.regex="o=(.+),ou=AddressBooks,dc=YourDomain,dc=com"
        by group.expand="cn=$1,ou=Groups,dc=YourDomain,dc=com" write

# Allow authenticated users to read the resources to which
# they have access by logging in with an empty Bind DN.
access to dn.base="ou=AddressBooks,dc=YourDomain,dc=com" by * read
access to dn.base="" by * read

# Allow authenticated users to read the Subschema of the
# resources to which they have access.
access to dn.base="cn=Subschema" by * read

# Disallow anonymous access (binds).
# With this policy in effect, unauthenticated users receive a response of
#   Error 48: Inappropriate authentication
# and otherwise can't see anything in or about your address books.
disallow bind_anon

With the above policies in effect and with the previous section's user and group entries, then users me and mine have access to address book Home, and users me and you have access to address book Work. Adding or deleting entries from the cn=Home,ou=Groups,dc=YourDomain,dc=com and cn=Work,ou=Groups,dc=YourDomain,dc=com entries control who has access to which address book; thus, in conjunction with the above policies, the database entries control access.

After putting the above ACPs into effect and then restarting your LDAP server, be sure to test the settings by attempting to login as different users to see how the policies are enforced.

Exporting Your Address Book

Export your address book from Thunderbird by opening up the address book and then selecting Tools and Export. Save the file (as, say, abook.ldif) some place where you can reference it via your LDAP utilities such as ldapadd.

Editing Your Address Book

You must edit the abook.ldif file to change entries to conform to certain conventions. Unfortunately, as of Thunderbird 1.0 as well as 1.5, there is no version of the published schema which matches what it exports as well as what LDAP servers find acceptable, so something has to be changed. Subsequent versions of Thunderbird likely will change the schema as well as what it exports, and thus the items you need to edit, so don't get too attached to your changes. For an automatic way to effect the following changes, download and run this PHP script.

In fact, if you use the above script, you can skip this section entirely.

  1. Escape reserved symbols in Distinguished Names (lines beginning with either dn: or member:). The seven reserved symbols are comma, plus sign, double-quote, backslash, left caret, right caret, and semicolon, i.e., ,+"\<>;. That is, occurrences of any of these seven reserved symbols in the content (as opposed to the syntax) of a distinguished name must be preceded with a backslash. For example, the line

    dn: cn=Stan Jones, Jr.,mail="Stan Jones, Jr." <stan@example.com>    must be changed to
    dn: cn=Stan Jones\, Jr.,mail=\"Stan Jones\, Jr.\" \<stan@example.com\>

    The comma preceding mail= is not escaped as it is part of the syntax of distinguished names.

  2. Escape non-ASCII characters in Base64-encoded UTF-8-encoded Distinguished Names (lines which begin with dn:: or member::). That is, occurrences of characters above ASCII(127) must be written as their UTF-8-encoded hexadecimal values with each byte preceded by a backslash. For example, had you entered a Display Name into Thunderbird's address book of Mary Gutiérrez, it would be exported as

    dn:: Y249TWFyeSBHdXRpw6lycmV6    (the double colon indicates a line in Base64-encoded UTF-8-encoded format)

    which Base64 decodes to

    dn: cn=Mary Guti+¬rrez    (that is, the original single non-ASCII character é was encoded as the two bytes 0xC3 and 0xA9 () in UTF-8 encoded format before being encoded in Base64)

    Finally, the above line must be changed to

    dn: cn=Mary Guti\C3\A9rrez

    which is a format acceptable to the LDAP server (and certainly more readble by humans).

    Only the dn:: and member:: lines need to be changed, not the cn:: line (or, for that matter, any other lines which start with an attribute name followed by a double colon).

    The whole process for both this and the preceding change is explained in UTF-8 String Representation of Distinguished Names.

  3. Each Distinguished Name with both a cn= and mail= part, must be changed to a format acceptable to the LDAP server. In particular, the two parts must be joined with a plus sign (instead of separated by a comma) so as to create a multi-valued RDN (Relative Distinguished Name). The format exported by Thunderbird is similar to the form

    dn: cn=First Last,mail=email@example.com    which must be changed to
    dn: cn=First Last+mail=email@example.com

    There are other ways to accomplish something similar such as omitting the mail= portion on the dn: line, but the change I suggest allows you to have entries with the same name and different email addresses.

  4. Each Distinguished Name must have appended to it the base DN for the address book (e.g., o=Home,ou=AddressBooks,dc=YourDomain,dc=com). This change is necessary so the LDAP server knows where to place the entry in the hierarchy.

    For example, the distinguished name

    dn: cn=First Last+mail=email@example.com    must be changed to something like
    dn: cn=First Last+mail=email@example.com,o=Home,ou=AddressBooks,dc=YourDomain,dc=com

  5. The attributes xmozillausehtmlmail and mozillaUseHtmlMail take a Boolean value (TRUE or FALSE) and must be in uppercase. Unfortunately, Thunderbird exports that attribute value in lowercase — change them all to uppercase.

  6. I could not get the modifytimestamp: 0Z entry to produce anything other than an error, so I turned each of them into a comment (by preceding each line with a pound sign). If you understand why this doesn't work (or, better yet, how to get it to work — I've already read about NO-USER-MODIFICATION), please .

  7. If you enter a nickname for a List (Address Book > New List, or right-click > Properties on an existing List), Thunderbird exports this value using the mozillaNickname attribute — for example:

    dn: cn=Poker
    objectclass: top
    objectclass: groupOfNames
    cn: Poker
    mozillaNickname: fish
    description: Poker friends
    member: cn=Stan Jones,mail=stan@example.com

    However, that attribute is not allowed within objectclass: groupOfNames, so it must be commented out.

  8. The attribute mozilla_AimScreenName exported by Thunderbird is not in its own schema, however nsAIMid is, so all occurrences of the former must be changed to the latter. In a similar fashion, Thunderbird 1.5's LDIF export and the mozillaAbPersonAlpha schema differ as follows:

    Thunderbird 1.5 Export    Closest Schema Match
    company:    o:
    department:    ou:
    homeStreet:    mozillaHomeStreet:

    so those changes must be made as well if you are using Thunderbird 1.5.

  9. Because one of the objectclasses is person and that objectclass requires certain attributes to be defined, all entries must have both a common name (cn) and a surname (sn) attribute. If you find address book entries which do not, make one up.

  10. If you have any duplicate entries (i.e., with the same common name (cn) and email address (mail)), remove or merge the duplicates or alter them to have different cns or mails.

Although Thunderbird generally accepts any text for any field in its address book, LDAP servers are much pickier. Some fields (e.g., those that accept a telephone number), are required to be in a particular format. This script doesn't attempt to ensure that those fields conform to any such requirements so you might find that ldapadd complains about some of your entries. To fix this, you'll need to consult the documentation for that entry for the proper format. To do that, you'll need to find the name of the field (from the schema) and possibly its Object Identifier (also from the schema), and then look up its definition via some site such as Alvestrand's Object Identifier Registry.

There may be other editing changes necessary to get this file up and running, but these are the ones I ran into when attempting to import my address book. If you find other necessary changes, please so I can add them to the above list. Again, these changes can be made automatically by downloading and running this PHP script.

Uploading Your Address Book to Your LDAP Server

After all that editing, you should have a clean enough address book to conform to what the LDAP shema require. Add your address book entries to the LDAP server as follows:

ldapadd -x -D "cn=Manager,dc=YourDomain,dc=com" -w secret -v -f abook.ldif

I found at this stage (before figuring out all of the above things that needed to be edited), that some errors would occur and stop the call to ldapadd from completing successfully. I would then

  1. Correct the errors (by editing the abook.ldif file)
  2. Stop the LDAP server
  3. Delete its database
  4. Restart the LDAP server
  5. Re-initialize the dc=YourDomain,dc=com and following entries (using init.ldif)
  6. and try again with the above ldapadd statement.

If you run into some error(s) I haven't encountered, I suggest you take the same approach (and then ) if you find something I've missed.

If there are no errors from the call to ldapadd, you have successfully imported your address book into your LDAP server.

Telling Thunderbird About Your LDAP Server

At this point, you have exported your address book from Thunderbird 1.0/1.5 to an LDAP server from which you can access your address book by telling Thunderbird about your LDAP server as follows:

In doing so, Thunderbird will now use your LDAP server to get email addresses when you type in a partial address while composing a message, and you can download the entire address book for local use.

Author and Acknowledgments

This page was created by Bob Smith — please any questions or comments about it to me.

I'd like to thank