Part I - Griping My goal in this project was, I thought, fairly simple. I am running a mail server that serves about 10 users, plus a mailing list or two. I wanted to remove the system accounts for all but the admin user, and move them all over to LDAP. The setup is fairly simple: Debian woody with some of Adrian Bunk's backports, exim-tls and spamassassin, and courier handling pop3/pop3-ssl and webmail (sqwebmail). OK, LDAP documentation sucks ass, as I found out fairly quickly. To ease administration, I stole a couple of scripts and wrote one of my own. They are attached at the end of this. The scripts got me around having to type in the long -D and -b options every time, and made it easier to check my work. The problem with most of the LDAP documentation out there, as far as I can tell, is that it's either written at a level that assumes you already know the structure of LDAP, and just need to learn this implementation, or it is written by people who have no idea how LDAP works, and have just stumbled across magic incantations to make it work. I'm hoping to change that, but I may just fall into the second category :) Part II - Installing packages Since this is done on a Debian woody box, I am going to address it from that point of view. The packages you'll need are: ii slapd 2.0.27-3.bunk OpenLDAP server (slapd). ii ldap-utils 2.0.27-3.bunk OpenLDAP utilities. ii libldap2 2.0.27-3.bunk OpenLDAP libraries (without TLS support). ii libldap2-tls 2.0.27-3.bunk OpenLDAP libraries (with TLS support). ii libnss-ldap 186-1 NSS module for using LDAP as a naming servic ii libpam-ldap 140-1 Pluggable Authentication Module allowing LDA ii exim-tls 3.35-3woody1 Exim Mailer - with TLS (SSL) support ii courier-authdaemon 0.37.3-2.3 Courier Mail Server authentication daemon ii courier-base 0.37.3-2.3 Courier Mail Server Base System ii courier-ldap 0.37.3-2.3 LDAP support for Courier Mail Server ii courier-maildrop 0.37.3-2.3 Mail delivery agent with filtering abilities ii courier-pcp 0.37.3-2.3 Courier PCP server ii courier-pop 0.37.3-2.3 POP3 daemon with PAM and Maildir support ii courier-pop-ssl 0.37.3-3.3 POP3 daemon with SSL, PAM and Maildir suppor ii courier-ssl 0.37.3-3.3 Courier Mail Server SSL Package ii courier-webadmin 0.37.3-2.3 Web-based administration tool for the Courie The version numbers of what you're using probably won't match unless you're also using Adrian Bunk's backports - they'll work the same, however. If you want to use his backports, the sources.list entry is: deb http://www.fs.tum.de/~bunk/debian woody/bunk-1 main contrib non-free Part III - Basics of LDAP Now comes the part where most things fall down. People often skip steps in describing the necessary configuration for the system to work. I will try to describe exactly what I did before it all fades from memory. Some things to understand first. LDAP is a tree-like directory structure, with a base and branches. In each branch, there are individual entries. Mine looks roughly like this: mail.lobefin.net | | - People | - Group | - admin The 'base' is mail.lobefin.net, and each of the branches are an 'objectClass'. If you're familiar at all with databases or programming, LDAP is what's called an object oriented database, rather than a relational one. Each of the things in the database are objects. If you don't understand what that means, don't worry too much about it - just remember to think of each entry as distinct entities. The admin branch does not have any entries in it - it is just the branch for the LDAP administrator, and the password and name are stored there. This keeps it clear that the admin is distinct from either People or Group affiliation (it is a distinct object). This object will be created with apropriate values by the maintainer scripts when you install slapd. The People and Group branches have entries below them, one for each user. Creating a Group objectClass is optional, but it allowed me to manage regular Unix group permissions with LDAP, so I went ahead and did so. If you only need user permission, you don't need to create this objectClass. The top level ou (Organizational Unit, or branch) People will be created by the maintainer scripts in slapd, so you won't need to make that by hand. You will need to create ldif files to add users to your LDAP database, though. A sample one is like so: dn: uid=foo,ou=People,dc=mail,dc=lobefin,dc=net uid: foo cn: Mr. Foo objectClass: account objectClass: posixAccount objectClass: top objectClass: shadowAccount userPassword: {crypt}(password changed to protect Mr. Foo) loginShell: /bin/false uidNumber: 1006 gidNumber: 1006 homeDirectory: /home/foo gecos: Mr. Foo,,, To explain: dn (Distinguished Name) is the name of this object, since all objects are unique and have unique names. uid is the User ID, and is the same as an account name. ou and dc we've covered already. cn is Common Name, and is just the users real name, as opposed to their account name. The various objectClass entries tell us that this is a top level structure, with type posixAccount (regular user account) and shadowAccount (they have an entry in /etc/shadow and use crypted passwords) The uidNumber and gidNumber are for unix number -> uid/gid mapping, and the homeDirectory should be self explanatory. To create the Group objectClass, you'll need to create an ldif file like so: dn: ou=Group,dc=mail,dc=lobefin,dc=net objectClass: posixGroup objectClass: top That's it. That tells LDAP, that we are creating a new top level branch with objectClass type posixGroup (posixGroup being the Unix gid to group name mapping). Populate this with entries like: dn: cn=foo,ou=Group,dc=mail,dc=lobefin,dc=net objectClass: posixGroup objectClass: top cn: foo userPassword: {crypt}x gidNumber: 1006 This tells LDAP that within the ou Group, we are creating a new entry, with cn (Common Name) foo. This entry will have gidNumber 1006. If you are moving from regular unix groups to LDAP groups, you can use the script /usr/share/migrationtools/migrate_group.pl, found in the migration-tools package. It will automatically convert your /etc/group file to an ldif format file like the entry above. There is also a script to migrate users, /usr/share/migrationtools/migrate_passwd.pl, which does the same for /etc/passwd. Part IV - System Configuration slapd is the main LDAP server. The important configuration is in /etc/ldap/slapd.conf, although if you answer the debconf questions carefully, you shouldn't need to touch this file. The important thing to remember here is that whatever you decide is going to be the base of your domain (mine is dc=mail,dc=lobefin,dc=net), it has to be consistent across all programs. If you drop the dc=mail in any one of them, it will all fall apart horribly. PAM. This is messy, but actually more straight forward than I had thought before I jumped into it. In libpam-ldap, there are a number of example files, found under /usr/share/doc/libpam-ldap/examples. There is a sample /etc/pam.conf, and sample files for each of the files under /etc/pam.d/ (one file for each service that needs authentication, essentially). Before you do anything else, make sure you back up the stock pam conffiles! I copied pam.conf and all of the files under pam.d to my home directory, but `cp pam.conf pam.conf.no-ldap` works just as well. Then cp /usr/share/doc/libpam-ldap/examples/pam.conf to /etc/pam.conf. Copy the files for the services you want into /etc/pam.d/ (I only wanted email, pop3, webmail, and so forth, so I didn't copy over ssh or telnet, but that's up to you). Some editing of the new version under /etc/pam.d/ is unfortunately necessary. Many of the files have a bad line, presumably left from an older distribution of PAM. You will probably have to comment out the line: password required /lib/security/pam_pwdb.so use_first_pass as there is no /lib/security/pam_pwdb.so. A judicious use of grep will help you here. Many of them also refer to /lib/security/pam_cracklib.so, which is not installed by default. It's up to you whether or not to install it (it checks password strength, and is not a bad thing, in my humble opinion) or just comment out the line. Again, grep is your friend. /etc/pam_ldap.conf should have been set up for you by the maintainer scripts, and you should not need to edit it manually. /etc/nsswitch.conf will need some manual editing, but not a huge amount. Just this: + passwd: compat files ldap + group: compat files ldap + shadow: compat files ldap - passwd: compat files - group: compat files - shadow: compat files For those of you not familiar with `diff` style output, that means change the lines beginning with '-' to be like the lines beginning with '+'. Pretty simple. This is telling the basic Name Service routines to also consult LDAP for passwd, group and shadow information. /etc/libnss-ldap.conf will need some manual editing as well, at least to get the group information correct. Before editing this file, I was getting things like: mercury:/home/foo# ls -l total 4 drwx------ 12 foo 1006 4096 Sep 5 16:07 Maildir Annoying, but not the end of the world. OK, the fix is to make sure /etc/libnss-ldap.conf contains the following: # Your LDAP server. Must be resolvable without using LDAP. host 127.0.0.1 # The distinguished name of the search base. base dc=mail,dc=lobefin,dc=net # The LDAP version to use ldap_version 3 # The distinguished name to bind to the server with # if the effective user ID is root. Password is # stored in /etc/ldap.secret (mode 600) rootbinddn cn=admin,dc=mail,dc=lobefin,dc=net # And these are the really important lines. nss_base_passwd ou=People,dc=mail,dc=lobefin,dc=net?one nss_base_shadow ou=People,dc=mail,dc=lobefin,dc=net?one nss_base_group ou=Group,dc=mail,dc=lobefin,dc=net?one The last three lines map Name Service calls to the corresponding objects in LDAP. Now I get: mercury:/home/foo# ls -l total 4 drwx------ 12 foo foo 4096 Sep 5 16:07 Maildir Much better! OK, on to actually making the various email programs use LDAP. Part V - Program Configuration Since this is only for email, webmail, and pop3, I don't need to change much. I need to tell exim to do LDAP lookups, and I need Courier's authdaemon to do LDAP lookups. Sqwebmail installs a file /etc/pam.d/webmail, which I edited to make pam-ldap work for it, like so: -auth required pam_unix.so nullok -account required pam_unix.so -password required pam_unix.so +auth sufficient /lib/security/pam_ldap.so +auth required pam_unix.so nullok try_first_pass +account sufficient /lib/security/pam_ldap.so +account required pam_unix.so try_first_pass +password sufficient /lib/security/pam_ldap.so +password required pam_unix.so try_first_pass So before any editing, it only allowed users with unix accounts to authenticate. Now ldap is 'sufficient' to allow authentication, but if that fails, it falls back to system accounts. 'try_first_pass' means prompt for a password only once, instead of once for each authentication method. Much nicer for the user, and would probably make the web interface go kablooey without it. Changes to exim are relatively straight forward. We need to add a director and a transport for LDAP users, so that exim knows how to verify that these are users, and once they're verified, exim knows how to deliver their mail. Main: (these are just variables, you don't need to do it this way) ldap_default_servers = localhost LDAPSERVER = localhost LDAPBASE = dc=mail,dc=lobefin,dc=net director (order matters, so I put this one last, so exim only does ldap lookups once every other method fails. Cuts out some unnecessary traffic) ldapuser: driver = aliasfile search_type = ldap query = "ldap://LDAPSERVER/ou=People,LDAPBASE?uid?one?uid=${local_part}" transport = ldap_delivery This tells exim to do an LDAP lookup on the server specified, and try to match the $local_part (that is, the part of their email address before the '@' against the uid field in their LDAP record). If this lookup succeeds, then it calls the transport ldap_delivery. It is as follows: # This transport is for ldap users ldap_delivery: driver = appendfile maildir_format group = mail create_directory = true directory = /home/${local_part}/Maildir/ mode = 0660 user = ${local_part} mode_fail_narrower = false delivery_date_add envelope_to_add return_path_add This is almost identical to the local_delivery transport that is set up by default, but it explicitly sets the user to deliver as. This will fail if the nsswitch stuff, above, is not set up yet, as I found out. Note that I am using Maildir style mailboxes, and delivering to the user's home directory, rather than /var/spool/mail/, as is exim's default. This is because I use Courier to handle pop3 and webmail, and this how Courier likes it. As for authentication for relaying, you can do it several ways. I have found that the simplest method, although also the most tedious, is to have exim lookup user entries in a flat file, and check their passwords there. If not found or they don't match, relaying is denied. To create this file, do `getent shadow` > /etc/exim/passwd, and edit it to remove the entries that don't need to be there (www-data and other sytem accounts probably don't need to relay through your mail server). Then add these lines to exim.conf, in authentication configuration section at the bottom: login: driver = plaintext public_name = LOGIN server_prompts = "Username:: : Password::" server_condition = "${if crypteq{$2}{${extract{1}{:}{${lookup{$1}lsearch{/etc/exim/passwd}{$value}{*:*}}}}}{1}{0}}" server_set_id = $1 This allows authentication of type login. You can do similar things for other authentication methods. To get Courier working, you'll need to do a little editing as well. First we're going to edit /etc/courier/authldaprc like so: # Location of your LDAP server: LDAP_SERVER localhost LDAP_PORT 389 ##NAME: LDAP_BASEDN:0 # # Look for authentication here: LDAP_BASEDN ou=People, dc=mail, dc=lobefin, dc=net # You may or may not need to specify the following. Because you've got # a password here, authldaprc should not be world-readable!!! LDAP_BINDDN cn=admin, dc=mail, dc=lobefin, dc=net LDAP_BINDPW secret_password # Here's the field on which we query LDAP_MAIL uid # The HOMEDIR attribute MUST exist, and we MUST be able to chdir to it LDAP_HOMEDIR homeDirectory LDAP_FULLNAME cn # Uncomment the following, and modify as appropriate, if your LDAP database # stores individual userids and groupids. Otherwise, you must uncomment # LDAP_GLOB_UID and LDAP_GLOB_GID above. LDAP_GLOB_UID and LDAP_GLOB_GID # specify a uid/gid for everyone. Otherwise, LDAP_UID and LDAP_GID must # be defined as attributes for everyone. This assumes working nsswitch. # LDAP_UID uidNumber LDAP_GID gidNumber Leave the Maildir commented out - we didn't use that field in our LDAP entries, and Courier will use $HOME/Maildir unless told to do otherwise, so that part just works. Next, we edit /etc/courier/authdaemonrc like so: authmodulelist="authpam authldap" version="authdaemond.ldap" We leave all of the other settings alone. This tells Courier's authdaemon to use the LDAP-aware version, and the settings in authldaprc tell it who to query and for what. That's it. We're done the configuration of all the email related stuff. Now resart your daemons (/etc/init.d/... restart - you should at least restart courier-authdaemon, and perhaps exim if you have it running as a daemon instead of from inetd) and you're done! Any problems, feel free to lambast me and publicly humiliate me - any time my advice works, make sure to not say anything at all, as that way I won't know I've helped anyone. Ok, that's it for now. Scripts follow here. Part VI - Scripts Scripts (passwords are, kind of obviously, not correct): getldapuserlist (Just returns a list of users and email addresses): #!/bin/sh password=secret ldapsearch -w $password -x -D \ 'cn=admin,dc=mail,dc=lobefin,dc=net' -b \ 'ou=People,dc=mail,dc=lobefin,dc=net' '(uid=*)' | \ grep '^uid: ' | sed -e 's/^uid: //' | \ awk -F@ '{print $1 "\t" $1 "@lobefin.net"}' searchldap (Returns a list of LDAP entries for uid specified as commandline arg): #!/bin/sh password=secret HOST=localhost; for arg in $@ ; do if [ "$HOST" = "next" ] ; then HOST=$arg; continue fi case $arg in -host|-h|--host) HOST=next; ;; -*) echo "Unknown argument: $arg" >&2 echo "Usage: $0 [-host ] username " >&2 exit 1; ;; *) SEARCHES="${SEARCHES} $arg"; ;; esac done if [ "$SEARCHES" = "" ] ; then echo "Usage: $0 [-host ] username " >&2 exit 1; fi if [ "${PAGER}" = "" ] ; then PAGER=less; fi for name in ${SEARCHES}; do ldapsearch -w $password -L -x -h $HOST -D \ 'cn=admin,dc=mail,dc=lobefin,dc=net' -b \ 'ou=People,dc=mail,dc=lobefin,dc=net' "(uid=$name)" done | ${PAGER} addldap (Adds an ldif file to the LDAP server): #!/bin/sh password=secret HOST=localhost; for arg in $@ ; do if [ "$HOST" = "next" ] ; then HOST=$arg; continue fi case $arg in -host|-h|--host) HOST=next; ;; -*) echo "Unknown argument: $arg" >&2 echo "Usage: $0 [-host ] file " >&2 exit 1; ;; *) FILES="${FILES} $arg" ;; esac done if [ "$FILES" = "" ] ; then echo "Usage: $0 [-host ] username " >&2 exit 1; fi for file in $FILES; do ldapadd -w $password -x -h $HOST -D \ 'cn=admin,dc=mail,dc=lobefin,dc=net' < $file done delete_ldap_user (Strangely, this deletes an entry from the LDAP database): #!/bin/bash HOST=localhost pass=secret base='ou=People,dc=mail,dc=lobefin,dc=net' binddn='cn=admin,dc=mail,dc=lobefin,dc=net' for arg in $@ ; do if [ "$HOST" = "next" ] ; then HOST=$arg; continue fi case $arg in -host|-h|--host) HOST=next; ;; -*) echo "Unknown argument: $arg" >&2 echo "Usage: $0 [-host ] file " >&2 exit 1; ;; *) NAMES="${NAMES} $arg" ;; esac done if [ "$NAMES" = "" ] ; then echo "Usage: $0 [-host ] username " >&2 exit 1; fi for name in $NAMES; do ldapdelete -v -x -h $HOST -D $binddn -w $pass uid=$name,$base done