LDAP

Exim4-LDAP 0.0.7 released

Exim4-LDAP 0.0.7 is a development release, which fix a lot of hidden bugs, more comprehensive clone of Qmail-LDAP individual account features, better documentation and coding syntax, and even more. I am confident that the code is stable enough for wider testing by the community, download now.

Spotlight changes include:

  • Add support with Qmail-LDAP accountStatus attribute.
  • Add support with Qmail-LDAP deliveryProgramPath attribute.
  • Activate home_directory in transport with relative patch support.
  • Add LDAP_MAILROOT support if homeDirectory is not absolute.
  • Debug program pipe transport.
  • Add sample LDIF for demo user demo@example.com.
  • Document installation guideline in INSTALL.

Please refer to /usr/share/doc/exim4-ldap/changelog.gz for complete changelog. Changes since 0.0.5:


Exim4-LDAP 0.0.5 initial released

Exim4-LDAP is my latest development progress. It is a Debian package that depends on exim4-heavy-daemon and slapd, which utilize Qmail-LDAP schema as backend database schema for seamless migration. Unlink Qmail-LDAP which required for manual source patching and compile, Exim4-LDAP just define additional authentication/router/transfer rules within configuration files. The ultimate goal of this research project is going to integrate with Samba-LDAP-PAM (for both Windows/Linux Single-Sign-On), RADIUS (for other services SSO) and even other services such as webmail. The project is now hosting in SourceForge.net with .deb available, download now.

Here I will draft some simple installation guideline which not yet included in source package document. I am now testing this with a newly installed Debian Lenny sandbox.


Configurate OpenLDAP in mirror mode replication

I am going to share some user account & directory with NFS among 2~3 servers. Traditionally we can use NFS+NIS solution, but I would like to try out NFS+LDAP+PAM/NSS plus OpenLDAP mirror mode replication.

For LDAP + PAM/NSS, please refer to my other article: LDAP + Samba PDC + PAM/NSS on Debian Lenny HOWTO

Add OpenLDAP mirror mode replication to existing server

Refer to OpenLDAP Software 2.4 Administrator's Guide: Replication, just add the following EXTRA setup to /etc/ldap/slapd.conf:

# Global section
serverID        1
loglevel        sync stats
moduleload      syncprov
rootdn          "cn=admin,dc=example,dc=com"
rootpw          CHANGE

# syncprov specific indexing
index entryCSN                          eq
index entryUUID                         eq

# syncrepl Provider for primary db
overlay syncprov
syncprov-checkpoint 100 10
syncprov-sessionlog 100

# syncrepl directive
syncrepl      rid=001
              provider=ldap://server2.example.com
              bindmethod=simple
              binddn="cn=admin,dc=example,dc=com"
              credentials=CHANGE
              searchbase="dc=dc=example,dc=com"
              schemachecking=on
              type=refreshAndPersist
              retry="60 +"

mirrormode on

Copy & paste this setup to your server2, change the serverID and provider accordingly.

My suggested procedure for deploy

  1. Shutdown both master/slave OpenLDAP server with:
    /etc/init.d/slapd stop
  2. Backup existing LDAP database under home directory:
    slapcat > ~/master.ldif
  3. Transfer the master.ldif to slave server.
  4. Transfer the master /etc/ldap/slapd.conf to slave server, and update it accordingly.
  5. Flush and import existing LDAP database in slave server:
    rm -rf /var/lib/ldap/*
    slapadd -l ~/master.ldif
    slapindex
    chown -Rf openldap:openldap /var/lib/ldap
  6. Start master server, and then slave server with:
    /etc/init.d/slapd start

My main tips: Always BACKUP before critical action :D

Debug the replication

As we turn on the syslog of slapd, we can keep trace it with:

tail -f -n 50 /var/log/syslog | grep slapd

Try to update some record from either master/slave server, check the log, and also browse the replication result in other server. Done :D


Courier LDAP authentication with qmail.schema

Need to include qmail.schema into your slapd.conf, and add object class qmailUser with mail attribute to user object. phpldapadmin can assist most of this setup.

For login from external MUA, e.g. Thunderbird:

  • username: postmaster (NOTE: not postmaster@example.com, we have no virtual domain support right now)
  • password: your_password

Some code snippet for Courier LDAP authentication with qmail.schema.
/etc/courier/authldaprc:

LDAP_URI ldap://127.0.0.1
LDAP_PROTOCOL_VERSION 3
LDAP_BASEDN dc=example,dc=com
LDAP_TIMEOUT 15
LDAP_AUTHBIND 1
LDAP_MAIL uid
LDAP_HOMEDIR homeDirectory
LDAP_MAILDIR mailMessageStore
LDAP_DEFAULTDELIVERY defaultDelivery
LDAP_MAILDIRQUOTA mailQuotaSize
LDAP_FULLNAME cn
LDAP_CRYPTPW userPassword
LDAP_UID uidNumber
LDAP_GID gidNumber
LDAP_DEREF never
LDAP_TLS 0

P.S. Courier LDAP authenticate seems support mailmap with extra lookup and attributes. I will need to study more if virtual domain support is required.

Other references:
http://wiki.debian.org.hk/w/Authenticate_user_for_Courier_imapd_and_pop3...


LDAP for Rocket Scientists

Still reading...
http://www.zytrax.com/books/ldap/

P.S. Already able to authenticate Courier and Exim4 with LDAP, but now thinging about multiple virtual domain implementation. Need to consider both Samba, Exim4, Courier and eGroupWare (WOW!) requirement... E.g. Multiple DN is not allowed in Courier ldapauth... May be clone eGroupWare design? From egroupware/addressbook/doc/README:

LDAP layout used for the eGroupWare addressbook
-----------------------------------------------

dc=domain,dc=com    base DN of your LDAP server
|
+-o=default         base DN for the addressbook of eGroupWare domain / DB instance "default"
| |                 (specified in Admin >> Addressbook >> Site config)
| |
| +-ou=accounts     base DN for accounts (specified in Setup >> Configuration)
| | +-uid=ralf      entry for user ralf
| | +-uid=lars      entry for user lars
| | +-uid=...       other users
| |
| +-ou=groups       base DN for groups (specified in Setup >> Configuration)
| | +-cn=Default    entry for the group Default
| | +-cn=...        other groups
| |
| +ou=contacts
|   |
|   +-ou=shared     shared addressbooks of the groups
|   | +-cn=default  addressbook of group Default
|   | +-cn=...
|   |
|   +-ou=personal   personal addressbooks of the users
|     +-cn=ralf     addressbook of user ralf
|     +-cn=lars     addressbook of user lars
|     +-cn=...
|
+-o=other           other eGroupWare domain / DB instance
  +-...


Temperary Exim4 SASL AUTH solution with LDAP-Samba-PAM/NSS + Courier authpam module

Recall to my previous Exim4 + Courier + SSL on Debian etch mini-HOWTO, Exim4 authentication should work fine without enable plain_courier_authdaemon and login_courier_authdaemon support. But case will become a bit more complicated when using LDAP-Samba-PAM/NSS setup: LDAP user will now not able to be authenticated. Why and what's up!?

Why not functioning?

By default Debian's Exim4 already coming with AUTH PLAIN and AUTH LOGIN setup with direct query on /etc/passwd or /etc/shadow as follow (Beware! This is completely not equal as authenticate with PAM!):

plain:
  driver = plaintext
  public_name = PLAIN
.ifndef AUTH_CLIENT_ALLOW_NOTLS_PASSWORDS
  client_send = "<; ${if !eq{$tls_cipher}{}\
                    {^${extract{1}{:}{PASSWDLINE}}\
                     ^${sg{PASSWDLINE}{\\N([^:]+:)(.*)\\N}{\\$2}}\
                   }fail}"
.else
  client_send = "<; ^${extract{1}{:}{PASSWDLINE}}\
                    ^${sg{PASSWDLINE}{\\N([^:]+:)(.*)\\N}{\\$2}}"
.endif

login:
  driver = plaintext
  public_name = LOGIN
.ifndef AUTH_CLIENT_ALLOW_NOTLS_PASSWORDS
  # Return empty string if not non-TLS AND looking up $host in passwd-file
  # yields a non-empty string; fail otherwise.
  client_send = "<; ${if and{\
                          {!eq{$tls_cipher}{}}\
                          {!eq{PASSWDLINE}{}}\
                         }\
                      {}fail}\
                 ; ${extract{1}{::}{PASSWDLINE}}\
                 ; ${sg{PASSWDLINE}{\\N([^:]+:)(.*)\\N}{\\$2}}"
.else
  # Return empty string if looking up $host in passwd-file yields a
  # non-empty string; fail otherwise.
  client_send = "<; ${if !eq{PASSWDLINE}{}\
                      {}fail}\
                 ; ${extract{1}{::}{PASSWDLINE}}\
                 ; ${sg{PASSWDLINE}{\\N([^:]+:)(.*)\\N}{\\$2}}"
.endif

As LDAP users information are now NOT stored within /etc/passwd and /etc/shadow, for sure that above setup will not function (because the PASSWDLINE don't contain such information).

Possible solutions

exim4-auth-001.pngexim4-auth-001.png

Some possible solutions:

  1. Direct authenticate with LDAP backend (reference: http://www.wlug.org.nz/EximSmtpAuth)
  2. Manual setup Exim4 with PAM authentication, as LDAP + PAM/NSS function correctly (reference: http://www.wlug.org.nz/EximSmtpAuth)
  3. Enable Exim4's plain_courier_authdaemon and login_courier_authdaemon support, as Courier's authpam module function correctly (which also means LDAP + PAM/NSS function correctly)

Each setup come with different PROS/CONS:

  1. Direct LDAP authentication: We can even store more information within LDAP, e.g. quota, vocation message, redirect, alias and so on, therefore enrich Exim4 functionality; BTW, this method is the most ideal but complicated in setup.
  2. Manual PAM authentication: A bit simple than above but only able to query authenticate information, and nothing else. There is no default Debian's reference setup, too.
  3. Authenticate though Courier: Most simple as Debian already handle most reference setup, what we only need to do is enable it.

Quick-and-dirty solution

Well... Long story short, as a quick and dirty solution, just enable Exim4's Courier authenticate section as below:

# Authenticate against courier authdaemon

# This is now the (working!) example from
# http://www.exim.org/eximwiki/FAQ/Policy_controls/Q0730
#
Possible pitfall: access rights on /var/run/courier/authdaemon/socket.
plain_courier_authdaemon:
  driver = plaintext
  public_name = PLAIN
  server_condition = \
    ${extract {ADDRESS} \
              {${readsocket{/var/run/courier/authdaemon/socket} \
              {AUTH ${strlen:exim\nlogin\n$auth2\n$auth3\n}\nexim\nlogin\n$auth2\n$auth3\n} }} \
              {yes} \
              fail}
  server_set_id = $auth2
  .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS
  server_advertise_condition = ${if eq{$tls_cipher}{}{}{*}}
  .endif

login_courier_authdaemon:
  driver = plaintext
  public_name = LOGIN
  server_prompts = Username:: : Password::
  server_condition = \
    ${extract {ADDRESS} \
              {${readsocket{/var/run/courier/authdaemon/socket} \
              {AUTH ${strlen:exim\nlogin\n$auth1\n$auth2\n}\nexim\nlogin\n$auth1\n$auth2\n} }} \
              {yes} \
              fail}
  server_set_id = $auth1
  .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS
  server_advertise_condition = ${if eq{$tls_cipher}{}{}{*}}
  .endif

And then ensure Courier is now authenticate with authpam:

authmodulelist="authpam"

Finally give access to Exim4 in order to query Courier authdaemon socket:

chmod 755 /var/run/courier/authdaemon

That's all :D


smbldap-populate hack with rfc2307bis.schema support

By default, smbldap_tools only support nis.schema and so not working with rfc2307bis.schema. By apply this patch your smbldap-populate can now install default schema with rfc2307bis.schema.

NOTE! This patch is far from complete: it only handle default schema populatation and hardcode with rfc2307bis.schema support. The ideal solution should be a clone to eGroupWare's implementation which can switch support between nis.schema and rfc2307bis.schema dynamically, and also add support for both group add/modify/delete actions.


eGroupWare + smbldap-tools integration with SQL hack

By default, smbldap-populate will add groups 'Domain User' with gidnumber 513, and 'Administrators' with gidnumber 544. This can map with eGroupWare's default groups 'Default' and 'Admins'. On the other hand, smbldap-populate will add default administrator account 'root' with uidnumber 0, where it is invalid for eGroupWare (eGroupWare count account id with AUTO_INCREMENT, where starting from 1 and so 0 is invalid). We need some tricks in order to integrate them:


Main difference between nis.schema and rfc2307bis.schema

I give some indeed study for eGroupWare + LDAP, compare its use between nis.schema and rfc2307bis.schema, slapcat and diff their result:

@@ -65,13 +65,15 @@ modifyTimestamp: 20090220182044Z
dn: cn=Default,ou=Group,dc=example,dc=com
objectClass: top
objectClass: posixGroup
+objectClass: groupOfNames
gidNumber: 1
cn: Default
-structuralObjectClass: posixGroup
+structuralObjectClass: groupOfNames
entryUUID: f627d08e-93c6-102d-9537-29575a5ac953
creatorsName: cn=admin,dc=example,dc=com
createTimestamp: 20090220182056Z
memberUid: postmaster
memberUid: tester
+member: uid=postmaster,ou=People,dc=example,dc=com
+member: uid=tester,ou=People,dc=example,dc=com
entryCSN: 20090220182056.968007Z#000000#000#000000
modifiersName: cn=admin,dc=example,dc=com
modifyTimestamp: 20090220182056Z

We can find that:

  1. Need to add the new objectClass: groupOfNames for each group.
  2. Need to replace all structuralObjectClass: posixGroup as structuralObjectClass: groupOfNames.
  3. Need to add required member attribute, e.g. member: uid=postmaster,ou=People,dc=example,dc=com.
  4. (HIDDEN RULE!) We need at least ONE member for each group!. eGroupWare web GUI will not allow group without any member, e.g. you can't create group without member, or delete the last member from a group.

So it is time to study how to patch smbldap-tools with rfc2307bis.schema support. May need some backtrace from eGroupWare logic, e.g. how to support both schema and dynamically switch the use of different syntax :D

Some code snippet from eGroupWare (/var/www/egroupware/phpgwapi/inc/class.accounts_ldap.inc.php):

                        // read the entry first, to check if the dn (account_lid) has changed
                        $sri = $is_group ? ldap_search($this->ds,$this->group_context,'gidnumber='.abs($data['account_id'])) :
                                ldap_search($this->ds,$this->user_context,'uidnumber='.$data['account_id']);
                        $old = ldap_get_entries($this->ds, $sri);

                        if (!$old['count'])
                        {
                                unset($old);
                        }
                        else
                        {
                                $old = $this->_ldap2array($old[0]);
                                foreach($old['objectclass'] as $n => $class)
                                {
                                        $old['objectclass'][$n] = strtolower($class);
                                }

                        $groupOfNames = in_array('groupofnames',$old ? $old['objectclass'] : $to_write['objectclass']);
                        if (!$old && $groupOfNames || $members)
                        {
                                $to_write = array_merge($to_write,$this->set_members($members,
                                        $data['account_id'],$groupOfNames,$dn));
                        }

        function set_members($members,$gid,$groupOfNames=null,$use_cn=null)
        {
                //echo "<p>accounts_ldap::set_members(".print_r($members,true).",$gid)</p>\n";
                if (!($cn = $use_cn) && !($cn = $this->id2name($gid))) return false;

                // do that group is a groupOfNames?
                if (is_null($groupOfNames)) $groupOfNames = $this->id2name($gid,'groupOfNames');

                $to_write = array('memberuid' => array());
                foreach((array)$members as $key => $member)
                {
                        if (is_numeric($member)) $member = $this->id2name($member);

                        if ($member)
                        {
                                $to_write['memberuid'][] = $member;
                                if ($groupOfNames) $to_write['member'][] = 'uid='.$member.','.$this->user_context;
                        }
                }
                if ($groupOfNames && !$to_write['member'])
                {
                        // hack as groupOfNames requires the member attribute
                        $to_write['member'][] = 'uid=dummy'.','.$this->user_context;
                }
                if ($use_cn) return $to_write;

                // set the member email addresses as forwards
                if ($this->id2name($gid,'account_email') &&     ($objectclass = $this->id2name($gid,'mailAllowed')))
                {
                        $forward = $this->group_mail_classes[$objectclass];

                        $to_write[$forward] = array();
                        foreach($members as $key => $member)
                        {
                                if (($email = $this->id2name($member,'account_email'))) $to_write[$forward][] = $email;
                        }
                }
                if (!ldap_modify($this->ds,'cn='.ldap::quote($cn).','.$this->group_context,$to_write))
                {
                        echo "ldap_modify(,'cn=$cn,$this->group_context',".print_r($to_write,true)."))\n";
                        return false;
                }
                return true;
        }


LDAP + Samba PDC + PAM/NSS on Debian Lenny HOWTO

Using LDAP is one of a good solution for single user database within networking hybrid system, e.g. integrate both user login for Windows and Linux, for email services, for web logon, and so on. This HOWTO will guide you though a basic system setup, including Samba PDC and PAM/NSS with LDAP on Debian Lenny. You can further more extend the use of LDAP to other system/platform with this setup.


Syndicate content