Using Horde with a New LDAP Directory Written by Ben Chavet (ben [at] horde [dot] org) Using Horde with a New LDAP Directory Document Standards LDAP Directory Configuration Directory Structure Required Schemas Import LDAP Entries Set Passwords for New Accounts Directory Permissions Horde Configuration Authentication Groups Preferences Hooks Horde 5 See Also This document is intended to help administrators set up a new Horde 3 installation using a new LDAP directory. Installing and configuring an LDAP directory is outside the scope of this document. It is assumed that you have a working LDAP directory, and that we are adding a new branch to it. Please feel free to fill in any gaps or clarify any existing information presented here. For starters, this will be a running progress of what I am doing to set up a working Horde installation using LDAP wherever possible. Document Standards The following standards and assumptions are used throughout this document. Please adjust accordingly to your situation. The LDAP directory is on the same machine we are installing Horde on. The LDAP directory does not allow anonymous binding. The LDAP administrative account is cn=root,dc=example,dc=com. The LDAP directory security accounts will be stored in ou=DSA,dc=example,dc=com OpenLDAP 2.1.30-r4 running on a Gentoo Linux machine is used for the presented examples. LDAP Directory Configuration Directory Structure The following shows the LDAP directory structure we are using. dc=example,dc=com |-- ou=DSA | `-- cn=horde `-- ou=horde |-- ou=users | `--cn=admin `-- ou=groups This is certainly not the only, or even necessarily the "correct", directory structure to use. Horde is not picky, and can be configured to use any structure you choose. We chose this particular structure in order to leave our LDAP directory open for non-horde use in the future. If you choose not to use this structure, be sure to modify the examples to fit your directory structure. Required Schemas The following schemas must be included in your LDAP configuration. include /etc/openldap/schema/core.schema include /etc/openldap/schema/cosine.schema include /etc/openldap/schema/inetorgperson.schema include /etc/openldap/schema/nis.schema And the following schemas are optional. include /etc/openldap/schema/rfc2739.schema include /etc/openldap/schema/horde.schema rfc2739.schema is used by turba to store calendar information, and can be found in horde/turba/scripts/ldap/rfc2739.schema. horde.schema is used by horde to store user preferences, and can be found in horde/scripts/ldap/horde.schema. If you intend to use either of these features, copy the respective schema file to /etc/openldap/schema. Import LDAP Entries Put the following in a file named horde.ldif. Don't worry about the password values just yet, we'll be changing them in a minute. Also, make sure to adjust the dn values for your directory. dn: ou=DSA,dc=example,dc=com objectclass: organizationalUnit ou: DSA dn: cn=horde,ou=DSA,dc=example,dc=com objectclass: organizationalRole objectClass: top objectClass: simpleSecurityObject userPassword: superSecretPassword cn: horde dn: ou=horde,dc=example,dc=com objectclass: organizationalUnit ou: horde dn: ou=users,ou=horde,dc=example,dc=com objectclass: organizationalUnit ou: users dn: uid=admin,ou=users,ou=horde,dc=example,dc=com objectclass: shadowaccount objectclass: inetorgperson uid: admin cn: Administrator sn: Administrator userpassword: supersecretpassword dn: ou=groups,ou=horde,dc=example,dc=com objectclass: organizationalUnit ou: groups Then, run the following command to import the entries into the LDAP directory. You will be prompted for the LDAP root password. ldapadd -x -h localhost -D "cn=root,dc=example,dc=com" -f horde.ldif -W Set Passwords for New Accounts The new accounts that we just created have generic passwords, so we need to set them to something reasonable. Run the following two commands to set the passwords. Be sure to replace secretpassword with the real passwords you want to have set. Set the password for cn=horde,ou=DSA,dc=example,dc=com: ldappasswd -x -h localhost -D "cn=root,dc=example,dc=com" -s secretpassword -W cn=horde,ou=DSA,dc=example,dc=com Set the password for uid=admin,ou=users,ou=horde,dc=example,dc=com: ldappasswd -x -h localhost -D "cn=root,dc=example,dc=com" -s secretpassword -W uid=admin,ou=users,ou=horde,dc=example,dc=com Directory Permissions These are the minimum directory permissions required for horde to work properly. These go in your slapd.conf. ### ou=DSA Permissions ### access to dn.children="ou=DSA,dc=example,dc=com" attrs=userPassword by self write by anonymous auth by * none access to dn.children="ou=DSA,dc=example,dc=com" by self read by * none ### ou=horde Permissions ### access to dn.children="ou=horde,dc=example,dc=com" attrs=userPassword by dn="cn=horde,ou=DSA,dc=example,dc=com" write by self write by anonymous auth by * none access to dn="ou=horde,dc=example,dc=com" by dn="cn=horde,ou=DSA,dc=example,dc=com" write by self read by * none Horde Configuration Authentication Note: Image can not be downloaded, please fix The hostname of the LDAP server - This is the address of your LDAP server. If you have a master and one or more slave LDAP servers, you can provide failover here by entering all of your LDAP servers separated by a space. The base DN for the LDAP server - This is the subtree that horde will search through to find user information. The DN used to bind to the LDAP server - Because our LDAP directory does not allow anonymous binding, we must provide the binding account here. If your LDAP directory allows anonymous binding, this can be left blank. The password used to bind to the LDAP server - The password associated with the binding account. Leave this blank if binding anonymously. LDAP Protocol Version - This should almost always be LDAPv3. Is this an AD server? - Check this if you are querying an Active Directory server. The username search key (set to samaccountname for AD) - This is the field that stores the username. If you're using Active Directory, set this field to samaccountname What objectclasses should a new user account be member of? These objectclasses should cover the cn,sn,userPassword attributes as well as the username search key - Horde will use these objectclasses when creating a new account. This value is unused if we enable the authldap hook. How to specify a filter for the user lists - Unless you have to use some fancy filters to find users, One or more objectclass filters should work fine here. The objectclass filter used to search for users. Can be a single objectclass or a list - This is simply a list of objectClass values that represent valid users. Any directory entry below the base DN that have all of the listed objectclasses are considered to be a valid user. For Active Directory users, you should (at least) set this to use person. Enable the creating of accounts with expiring passwords? (Note: New users should have the shadowAccount objectclass) - Horde supports expiring passwords. If you select yes here, be sure that your user accounts have a shadowAccount objectclass. Groups [screenshot coming soon] The hostname of the LDAP server - This is the address of your LDAP server. If you have a master and one or more slave LDAP servers, you can provide failover here by entering all of your LDAP servers separated by a space. The base DN for the LDAP server - This is the subtree that horde will search through to find user information. The DN used to bind to the LDAP server - Because our LDAP directory does not allow anonymous binding, we must provide the binding account here. If your LDAP directory allows anonymous binding, this can be left blank. The password used to bind to the LDAP server - The password associated with the binding account. Leave this blank if binding anonymously. LDAP Protocol Version - This should almost always be LDAPv3. The group search key - This is the field that stores the group name. Group membership field - This is the field that stores which users are in this group. How to specify a filter for the group lists - Unless you have to use some fancy filters to find groups, One or more objectclass filters should work fine here. The objectclass filter used to search for groups. Can be a single objectclass or a list - This is simply a list of objectClass values that represent valid groups. Any directory entry below the base DN that have all of the listed objectclasses are considered to be a valid group. Preferences Storing Horde preferences in the LDAP directory adds a large number of attribute entries to every user DN. If this is something you do not want, you should look into using some other preference backend. Be sure to include the horde schema as described above. The hostname of the LDAP server - This is the address of your LDAP server. If you have a master and one or more slave LDAP servers, you can provide failover here by entering all of your LDAP servers separated by a space. The port of the LDAP server - This is the port that your LDAP server is listening on. Most commonly, this will be 389. LDAP Protocol Version - This should almost always be LDAPv3. The base DN for the LDAP server - This is the subtree that horde will search through to find user preference information. Should Horde bind as each user for that user's write operations? - If selected, the preferences will be written by binding as the user saving their preferences. This requires some modification to the LDAP directory permissions that we defined earlier. TODO: define user permissions to read/write preferences. If not, provide the DN of the root (administrative) account to bind for write operations - This is not actually asking for the LDAP root account, this is just the DN that horde uses to bind to the LDAP directory. This account should have write priveleges, which we configured above. The password of the root DN for bind authentication - The password associated with the binding account. Leave this blank if binding anonymously. The username search key - This is the field that stores the username. This should be the same value as defined in the Authentication section. Hooks Horde can do some basic user management based on the objectclass values we gave it in the Authentication configuration. However, in order to ensure that the LDAP entries are formed exactly how we want them, we must activate the _horde_hook_authldap function in horde/config/hooks.php. There is an example function in horde/config/hooks.php.dist which we will use as a base for our function. if (!function_exists('_horde_hook_authldap')) { function _horde_hook_authldap($userID, $credentials = null) { $entry['dn'] = 'uid=' . $userID . ',ou=horde,dc=example,dc=com'; if (isset($credentials) && isset($credentials['user_fullname'])) { $entry['cn'] = $credentials['user_fullname']; } else { $entry['cn'] = $userID; } $entry['sn'] = $userID; $entry['objectclass'][0] = 'shadowaccount'; $entry['objectclass'][1] = 'inetorgperson'; $entry['uid'] = $userID; // need to check for new users (password) and edited users (user_pass_2) if (isset($credentials) && isset($credentials['password'])) { $entry['userPassword'] = '{MD5}' . base64_encode(mHash(MHASH_MD5, $credentials['password'])); } else if (isset($credentials) && isset($credentials['user_pass_2'])) { $entry['userPassword'] = '{MD5}' . base64_encode(mHash(MHASH_MD5, $credentials['user_pass_2'])); } return $entry; } } This hook function also needs to be enabled in the horde configuration. Horde 5 I recently modified some examples to provide this H5 hook. This hook uses the default LDAP configuration for base DN and uid attribute name. It creates or updates an LDAP entry for a user via the Administration > Users page and does nothing from the registration form. It fills in the cn, sn, and givenName attributes from the 'user_fullname' signup field and creates the mail attribute from the uid and the default domain specified in the Horde IMAP configuration. class Horde_Hooks { public function signup_getextra() { return array( 'user_fullname' => array( 'label' => 'Full Name', 'type' => 'text', 'required' => true ), ); } public function signup_addextra($userId, $extra, $password) { // defined but empty, work around a small horde bug } public function preauthenticate($userId, $credentials) { if ($credentials['authMethod'] != 'admin' || !isset($credentials['password'])) return true; global $conf; $base = $conf['auth']['params']['basedn']; $attr = $conf['auth']['params']['uid']; $dn = "$attr=$userId,$base"; $default_domain = $conf['imap']['maildomain']; $cn = $userId; $sn = $userId; $given = ''; if (isset($credentials['user_fullname'])) { $cn = $credentials['user_fullname']; if (($pos = strrpos($cn, ' ')) !== false) { $sn = substr($cn, -$pos); $given = substr($cn, 0, $pos); } else { $sn = $cn; } } // Create credentials needed by the LDAP Horde_Auth // driver for adding/deleting/updating users. $entry = array( 'dn' => $dn, 'cn' => $cn, 'givenName' => $given, 'sn' => $sn, 'objectclass' => array_merge(array('top'), $conf['auth']['params']['newuser_objectclass']), 'mail' => "$userId@$default_domain", $attr => $userId, ); // Need to check for new users (password) and edited users (user_pass_2) if (isset($credentials['password'])) { $password = $credentials['password']; } elseif (isset($credentials['user_pass_2'])) { $password = $credentials['user_pass_2']; } if (isset($password)) { $entry['userPassword'] = Horde_Auth::getCryptedPassword( $password, '', $conf['auth']['params']['encryption'], true); } $credentials['ldap'] = $entry; return array( 'userId' => $userId, 'credentials' => $credentials ); } } TODO: Group Hooks See Also ExistingLDAPHowTo LdapPref General LDAP Authentication Info for Linux (Unix) and Windows (Samba)