Ubuntu LDAP Addressbook Thunderbird Squirrelmail iPhone

From richud.com
Jump to navigation Jump to search


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"
<SNIP>
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
<SNIP>

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: <blank> or dc=yourdomain,dc=com Security Level,

Level : SSL + User + Password
User DN: cn=admin,dc=yourdomain,dc=com
Password: <your admin password you entered when setting it up>

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"
<SNIP>
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
<SNIP>
  • 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
#Set Base DN (for cn=admin login) 
$servers->setValue('server','base',array('dc=yourdomain,dc=com'));

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

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

########################################
# 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

###########################################################
# 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



########################################################################################
# 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

################################################
# 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)

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

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

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
# Allow users to write their own password; however, the
# principal use of this policy is to authenticate users.
replace: olcAccess
olcAccess: 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.
add: olcAccess
olcAccess:  to dn.regex="ou=(.+),ou=adr,dc=yourdomain,dc=com" 
 by group.expand="cn=$1,ou=grp,dc=yourdomain,dc=com"
-
# Allow authenticated users to read the resources to which
# they have access by logging in with an empty Bind DN.
add: olcAccess
olcAccess:  to dn.base="ou=adr,dc=yourdomain,dc=com" by * read
olcAccess:  to dn.base="" by * read
-
# Allow authenticated users to read the Subschema of the
# resources to which they have access.
add: olcAccess
olcAccess:  to dn.base="cn=Subschema" by * read
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

<SNIP>
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
<SNIP>

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: <anything you want>
Hostname: yourdomain.com
Base DN: ou=adr,dc=yourdomain,dc=com
Port number: <will auto set>
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: <anything you want> Hostname: yourdomain.com Base DN: <blank> Port number: <will auto set> Bind DN: <blank>

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]


Comments

blog comments powered by Disqus