Ubuntu LDAP Addressbook Thunderbird Squirrelmail iPhone

=openLDAP/slapd overview=

Hopefully this will explain how to use multi user openLDAP Address book with phpldapadmin/Thunderbird/Squirrelmail/iPhone(iOS)

This is on a Ubuntu 12.10 amd64 server

(Assuming you are already using Squirrelmail/Thunderbird)

There is a who load of outdated info on the interwebs, hopefully this will help someone not waste all the time I did!

Get everything sudo apt-get install slapd ldap-utils gnutls-bin jxplorer phpldapadmin


 * The password slapd asks you to set on install (via the ncurses popup) is your (effectively root) 'admin' account in openldap, corresponding to the login cn=admin,dc=yourdomain,dc=com

=openLDAP TSL/SSL securing=

openLDAP slapd modify /etc/default/slapd
Enable slapd daemon to listen on port 636 for TLS/SSL;

sudo nano -w /etc/default/slapd

Append SLAPD_SERVICES with ldaps:///

SLAPD_SERVICES="ldap:/// ldapi:/// ldaps:///"


 * Note using StartTLS instead of TLS/SSL uses port 389 so this wouldn't be needed, however couldn't find anything that used StartTLS, everything uses TLS/SSL so you will need this.

openLDAP modifying the cn=config
Add you SSL certificates, I am using the ones I use with dovecot and most everything else on the server.

Make sure they both have read access! The .pem should already, you will need to add openldap account [created when you install openLDAP] into the ssl-cert group so it has read access to the .key also.

sudo adduser openldap ssl-cert

It should end up like, -rw-r- 1 root ssl-cert 1675 Jun 27 2011 ssl-cert-snakeoil.key

cert.ldif
(create just as a temporary text file somewhere)

dn: cn=config changetype: modify replace: olcTLSCertificateFile olcTLSCertificateFile: /etc/ssl/certs/ssl-cert-snakeoil.pem - replace: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: /etc/ssl/private/ssl-cert-snakeoil.key

Add it

sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f cert.ldif -v

Check it

sudo cat "/etc/ldap/slapd.d/cn=config.ldif"

 creatorsName: cn=config createTimestamp: 20130103145038Z olcTLSCertificateFile: /etc/ssl/certs/ssl-cert-snakeoil.pem olcTLSCertificateKeyFile: /etc/ssl/private/ssl-cert-snakeoil.key entryCSN: 20130106140522.309069Z#000000#000#000000 

Restart slapd

sudo service slapd restart

Deleting mistakes
How to delete stuff you may have accidently added after reading other things on the internet.

delete.ldif

dn: cn=config changetype: modify delete: olcTLSCipherSuite - delete: olcTLSCRLCheck

Delete it

sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f delete.ldif -v

Check it is gone by ;

sudo cat "/etc/ldap/slapd.d/cn=config.ldif"

openLDAP Testing TLS SSL
Once slapd is restarted, it should be now listening on port 636 with your snakeoil certs (which I use with other things on the server. Note these were generated with openSSL you dont need to use gnutls thing)

I suggest using JXplorer for testing. So long as the server has ports 636 and 389 listening you can do this from a second client machine.

Host: yourdomain.com, port 636 Protocol: LDAP V3 Base DN: or dc=yourdomain,dc=com Security Level, Level : SSL + User + Password User DN: cn=admin,dc=yourdomain,dc=com Password: 

Suggest saving this as a template.

This should prompt for a cert, select session only! (this ascertains its listening and connecting on 636) then it should show you the dir, if this works you admin login is ok.

If you cannot get in, change it to port 389 , change Level to User + Password and that should let you in, this means cert problem is issue.

If that doesnt work leave it on 389 and change it to level Anonymous, if this doesn't work you have probably have done something daft.

If everything worked you have done the hardest bit (appart from understanding the ACL's, later!)

openLDAP Testing SSL/TLS way 2
gnutls-cli-debug -p 636 yourdomain.com

This should print a page full of things with 'yes' next to most of them.

Checking for SSL 3.0 support... yes Checking whether %COMPAT is required... no Checking for TLS 1.0 support... yes Checking for TLS 1.1 support... yes

openLDAP Logging - logging.ldif
If problems occur you can enable logging.

Add multiple logging options with olcLogLevel:, the logs are quite cryptic and unusually didn't help me much. stats is default level.

logging.ldif

dn: cn=config changetype: modify replace: olcLogLevel olcLogLevel: stats conns config ACL

Import it

sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// -f logging.ldif -v

Check its now present in

sudo cat "/etc/ldap/slapd.d/cn=config.ldif"

 creatorsName: cn=config createTimestamp: 20130103145038Z olcTLSCertificateFile: /etc/ssl/certs/ssl-cert-snakeoil.pem olcTLSCertificateKeyFile: /etc/ssl/private/ssl-cert-snakeoil.key olcLogLevel: stats conns config ACL entryCSN: 20130106140522.309069Z#000000#000#000000 


 * Note put it back to just 'stats' at end otherwise you will get massive log entries!!

openLDAP modify ldap.conf [not needed]
This doesn't seem to stop anything working except ldapsearch test. (Relates to self signed certs)


 * I haven't modified this file at all on my live system.

sudo nano -w /etc/ldap/ldap.conf

Add this line TLS_REQCERT never

ldapsearch -d 9 -D cn=admin,dc=yourdomain,dc=com -w "xxxxxxx" -b dc=yourdomain,dc=com -H "ldaps://yourdomain.com" "cn=*"

error if 'TLS_REQCERT never' isnt set.

ldap_url_parse_ext(ldaps://yourdomain.com) ldap_create ldap_url_parse_ext(ldaps://yourdomain.com:636/??base) ldap_sasl_bind ldap_send_initial_request ldap_new_connection 1 1 0 ldap_int_open_connection ldap_connect_to_host: TCP yourdomain.com:636 ldap_new_socket: 3 ldap_prepare_socket: 3 ldap_connect_to_host: Trying 92.234.13.94:636 ldap_pvt_connect: fd: 3 tm: -1 async: 0 TLS: peer cert untrusted or revoked (0x42) TLS: can't connect: (unknown error code). ldap_err2string ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)

=Setting up phpldapadmin=

Lighttpd config /etc/lighttpd/lighttpd.conf
Suggest setting it up in this fashion so everything you do is secure. This is a sample entry in the conf. This will redirect everything to go via SSL.

$HTTP["host"] =~ "^phpldapadmin.yourdomain.com$" { server.document-root = "/usr/share/phpldapadmin/"

$SERVER["socket"] == ":80" { url.redirect = ( ".*" => "https://phpldapadmin.yourdomain.com$0" ) }       $SERVER["socket"] == ":443" { ssl.engine = "enable" ssl.pemfile = "/etc/lighttpd/ssl/ssl-cert-snakeoil.concat.pem" } }


 * Note, again you can use the same snakeoil self signed cert - with Lighty the user .pem and .key are concatenated to make one .pem - this is just how it rolls.

(The concatted one should obviously be someplace where only lighty can read it and not everyone as it has key file in it)

phpldapadmin config - /etc/phpldapadmin/config.php
sudo nano -w /etc/phpldapadmin/config.php

$servers->setValue('server','base',array('dc=yourdomain,dc=com'));
 * 1) Set Base DN (for cn=admin login)

$servers->setValue('login','bind_id','cn=admin,dc=yourdomain,dc=com');
 * 1) Set default User DN (for cn=admin login)

Now you should be able to login with http://phpldapadmin.yourdomain.com which should forward to https://phpldapadmin.yourdomain.com

At this point you should login ok!

UserDN: "cn=admin,dc=yourdomain,dc=com" & your admin password you set when installed it

phpldapadmin - Importing Site Structure
Now go to import, and import the large conf.ldif which sets main system structure.

By far the easiest way is understand is copy and paste below and edit it to your site, import it and fiddle - it will mean more that studying the code first. (I know because I did it that way and regretted it)

In a nutshell, there are several groups in ou=grp, each containing members from ou=usr. Each group has access permission on a single address boot OF THE SAME NAME in ou=adr.

A user can be a member of any group and thus have access to one or more address books. The 'gal' (global address book accessing) group ou=gal has added all the users from ou=usr.


 * Note this is a plain ldif format, i.e. doesn't have the extra header bits ldiffmodify needs such as 'changetype: modify replace: xxxxxxxx'

conf.ldif


 * 1) OU's of users, groups and address books
 * 1) OU's of users, groups and address books

dn: ou=usr,dc=yourdomain,dc=com objectClass: top objectClass: organizationalUnit description: OU for all the users who have access to LDAP server ou: usr

dn: ou=grp,dc=yourdomain,dc=com objectClass: top objectClass: organizationalUnit description: OU for all the groups, with each group inside only having access to address book of same name. ou: grp

dn: ou=adr,dc=yourdomain,dc=com objectClass: top objectClass: organizationalUnit description: OU containing all the separate address books, each one inside corresponding to unique group of same name. ou: adr


 * 1) OU's of address books under main address books OU (ou=adr)
 * 1) OU's of address books under main address books OU (ou=adr)

dn: ou=rma,ou=adr,dc=yourdomain,dc=com objectClass: top objectClass: organizationalUnit description: rm's address book, must be same name as the group controlling it. ou: rma

dn: ou=tma,ou=adr,dc=yourdomain,dc=com objectClass: top objectClass: organizationalUnit description: tm's address book, must be same name as the group controlling it. ou: tma

dn: ou=ama,ou=adr,dc=yourdomain,dc=com objectClass: top objectClass: organizationalUnit description: am's address book, must be same name as the group controlling it. ou: ama

dn: ou=gal,ou=adr,dc=yourdomain,dc=com objectClass: top objectClass: organizationalUnit description: Global address book, must be same name as the group controlling it. ou: gal


 * 1) Users with access, need to login with their User DN cn=xx,ou=usr,dc=yourdomain,dc=com
 * 1) Users with access, need to login with their User DN cn=xx,ou=usr,dc=yourdomain,dc=com

dn: cn=rm,ou=usr,dc=yourdomain,dc=com objectClass: top objectClass: person cn: rm sn: rm userPassword: xxxxxxxxxx

dn: cn=tm,ou=usr,dc=yourdomain,dc=com objectClass: top objectClass: person cn: tm sn: tm userPassword: xxxxxxxxxx

dn: cn=am,ou=usr,dc=yourdomain,dc=com objectClass: top objectClass: person cn: am sn: am userPassword: xxxxxxxxxx

dn: cn=hh,ou=usr,dc=yourdomain,dc=com objectClass: top objectClass: person cn: hh sn: hh userPassword: xxxxxxxxxx

dn: cn=gal,ou=usr,dc=yourdomain,dc=com objectClass: top objectClass: person cn: gal sn: gal userPassword: xxxxxxxxxx


 * 1) Groups that control address books of same name
 * 1) Groups that control address books of same name

dn: cn=rma,ou=grp,dc=yourdomain,dc=com objectClass: top objectClass: groupOfNames description: rm's address book controlling group cn: rma member: cn=rm,ou=usr,dc=yourdomain,dc=com member: cn=hh,ou=usr,dc=yourdomain,dc=com

dn: cn=tma,ou=grp,dc=yourdomain,dc=com objectClass: top objectClass: groupOfNames description: tm's address book controlling group cn: tma member: cn=tm,ou=usr,dc=yourdomain,dc=com

dn: cn=ama,ou=grp,dc=yourdomain,dc=com objectClass: top objectClass: groupOfNames description: am's address book controlling group cn: ama member: cn=am,ou=usr,dc=yourdomain,dc=com

dn: cn=gal,ou=grp,dc=yourdomain,dc=com objectClass: top objectClass: groupOfNames description: Global address book controlling group, everyone member of this cn: gal member: cn=rm,ou=usr,dc=yourdomain,dc=com member: cn=hh,ou=usr,dc=yourdomain,dc=com member: cn=am,ou=usr,dc=yourdomain,dc=com member: cn=tm,ou=usr,dc=yourdomain,dc=com

Assuming everything imported ok, log out.


 * Note, passwords set here do work and get encoded. These become 'live' to login with immediately the file is imported.

fixup config.php to final settings
Go back to config.php and change the two lines above to this (dont do it first as it will break due to a bug where if the server base array is set to something non existing you then cant import anything with the same name)

$servers->setValue('server','base',array('dc=yourdomain,dc=com','ou=adr,dc=yourdomain,dc=com'));
 * 1) Change Base DN to something users can get to (when ACL is set in a bit)

$servers->setValue('login','bind_id','cn=,ou=usr,dc=yourdomain,dc=com');
 * 1) Change the default login User DN to what most users would want to login with to save typing from now on (or leave it)

Now try logging in again as a user, the login being "cn=A-USER-YOU-ADDED,ou=usr,dc=yourdomain,dc=com" and password now their password from imported file.

You should again be able to see and do everything (as no ACL's set yet)

=openLDAP ACL Access Permissions acl.ldif=

Now lets setup some security as dont want everyone accessing all the address books.

This is a unnashamed rip from http://www.sudleyplace.com/LDAP/, but with some explanation of what you do with it and converted to import with new way of doing things (which took a lot of fiddling to work out).


 * The regex line is what ties the group (ou) to the address book (ou) for access rights, so must have same names!

The first 'replace:' blats existing entries and replaces with first olcAccess rule, the others are then added with 'add:', you cannot have comments between the replace/add and the line you are adding


 * Note line split ones (i.e. regex one in this example) need a space at line ending AND next line beginning otherwise you will get "ldap_modify: Other (e.g., implementation specific) error (80)"

'olcAccess: blah' on adjacent lines only need one add: / replace: line preceeding them if you wanted to remove all the comments etc.

dn: olcDatabase={1}hdb,cn=config changetype: modify replace: olcAccess olcAccess: to attrs=userPassword by self write by anonymous auth - add: olcAccess olcAccess: to dn.regex="ou=(.+),ou=adr,dc=yourdomain,dc=com" by group.expand="cn=$1,ou=grp,dc=yourdomain,dc=com" - add: olcAccess olcAccess: to dn.base="ou=adr,dc=yourdomain,dc=com" by * read olcAccess: to dn.base="" by * read - add: olcAccess olcAccess: to dn.base="cn=Subschema" by * read
 * 1) Allow users to write their own password; however, the
 * 2) principal use of this policy is to authenticate users.
 * 1) Allow authenticated users, by group, access to their
 * 2) own address book and only their own address book.
 * 1) Allow authenticated users to read the resources to which
 * 2) they have access by logging in with an empty Bind DN.
 * 1) Allow authenticated users to read the Subschema of the
 * 2) resources to which they have access.

sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// -f acl.ldif -v

Vitally, check it imported as it only reports an error on some errors, not all!

sudo cat "/etc/ldap/slapd.d/cn=config/olcDatabase={1}hdb.ldif"

result

 creatorsName: cn=config createTimestamp: 20130103145038Z olcAccess: {0}to attrs=userPassword by self write by anonymous auth olcAccess: {1}to dn.regex="ou=(.+),ou=adr,dc=yourdomain,dc=com" by group.expan d="cn=$1,ou=grp,dc=yourdomain,dc=com" write olcAccess: {2}to dn.base="ou=adr,dc=yourdomain,dc=com" by * read olcAccess: {3}to dn.base="" by * read olcAccess: {4}to dn.base="cn=Subschema" by * read entryCSN: 20130106133914.761252Z#000000#000#000000 

phpldapadmin - Testing ACL's
Now try logging in again as a user, the login being "cn=A-USER-YOU-ADDED,ou=usr,dc=yourdomain,dc=com" and password now their password from imported file.

You should now see a lot less. Something like this should greet you.

dc=yourdomain,dc=com This base cannot be created with PLA

Followed by

+ ou=adr,dc=yourdomain,dc=com

which you can expand and now see your address book and the gal one. If you can everything is working!


 * Note you will have 'Create new entry here' in ou=adr root, but if you try it will say 'insufficient access'

Now you can import your list of contacts in .ldif format from wherever!

phpldapadmin - Importing Contacts

 * Note this is a plain ldif format, i.e. doesn't have the extra header bits ldiffmodify needs such as 'changetype: modify replace: xxxxxxxx'

contacts.ldif

dn: cn=Eric Idle,ou=gal,ou=adr,dc=scaryscary,dc=com objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson cn: Eric Idle sn: Idle givenName: Eric mail: ericidle@gmail.com mail: eric.idle@ntlworld.com mobile: 07871630516

dn: cn=Sheldon Cooper,ou=rma,ou=adr,dc=scaryscary,dc=com objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson cn: Sheldon Cooper sn: Cooper givenName: Sheldon mail: sheldoncooper@gmail.com homePhone: 01262382556 mobile: 07713344656


 * Note, top one is in the gal address book and so everyone sees them logging using their account. Second one appears is in rma's address book only.
 * Note, multiple mail entries just use mail: as many times as you need. Squirrelmail will see all of them, Thunderbird sees the first only, iPhone sees the last only. Great :(

Assuming everything imported ok, log out.

=ThunderBird Client Config=

There is no certificate prompting at all like with mail (or anything at all to let you know the problem), so you need to get the certificate in (assuming you want contacts secured with SSL anyway). Ideally use the same one for dovecot/postfix or equivalent then you need only do this once to take care of imap/pop3/smtp/ldap connections in one go.

Config main settings
Name: Hostname: yourdomain.com Base DN: ou=adr,dc=yourdomain,dc=com Port number: Bind DN: cn=rm,ou=usr,dc=yourdomain,dc=com tick 'Use Secure connection'

SSL Certificate best way
Edit > Preferences > Advanced, View Certiticates, Import, select your locally copied version of /etc/ssl/certs/ssl-cert-snakeoil.pem. When imported, goto 'Edit Trust', tick 'This certificate can identify web sites'. It should appear under 'Authorities'.

SSL Certificate simplest way
Edit > Preferences > Advanced > Certificates > View Certificates > Servers > Add Exception, enter http://yourdomain.com:636, this will add it as an exception and everything should work.

Quick test
Adress Book > Properties > Offline tab > Download now should succeed if (cert)/settings ok. (you will need to always 'ok' any changes, shut and reopened ldap settings)

Set address auto completion
Edit > Preferences > Composition, set Adressing > Address autocompletion, add tick to 'directory server'. Then select add and enter under 'general' tab,

anon SSL access
Of course you can use a non SSL anonymous connection if you wish, generally you dont need to add anything other than the hostname as the whole directory is open.

Name: Hostname: yourdomain.com Base DN: Port number: Bind DN:

Testing
Now going into the address book, click your server, then search for someone and they should show up! (OR see quick test above.)

LDAP bugs in Thunderbird
It cant cope with a contact having more than one possible mail addresses. It only shows the first one.

If you have two LDAP servers it only will autofil from the top one in the list.

It wont display First and Last name fields, which matters if you are using LDAP connection to Active Directory and the 'Name' filed is just a cryptic login name.

=Squirelmail Config=

Squirrelmail can only bind to one fixed account.

Wont work generally without base being set! wont work without specifying protocol 3. (dont enable js address find as its crap.)

this should be in /usr/share/squirrelmail/config/config.php, (simpler than running conf.pl)

$default_use_javascript_addr_book = false; $ldap_server[0] = array(   'host' => 'localhost',    'base' => 'ou=adr,dc=yourdomain,dc=com',    'binddn' => 'cn=gal,ou=usr,dc=yourdomain,dc=com',    'bindpw' => 'xxxxxxxx',    'protocol' => 3 );

=iOS iPhone config=

Settings > Mail, Contacts, Calendars > (Accounts > Contacts Add LDAP) >

Server [LDAP host/IP]: yourdomain.com User Name [User DN] : cn=rm,ou=usr,dc=yourdomain,dc=com Search Settings > Base [Base DN] : ou=adr,dc=yourdomain,dc=com Search Scope : Subtree


 * Note SSL wont work if you are using a self signed certificate as you cannot import it. [I dont know how & not spent any time looking]