4.2. Database structure

LDAP stores its data as objects (entries), each characterized by a certain number of attributes. Each attribute has a well-defined ID.

Objects are mantained hierarchically along their distinguished name (dn in short). Children objects inherit the dn of their parent as a suffix to their own dn. The ensuing structure is therefore a tree structure.

With a tree like the following:

			o=Anarchy
			 \-- dc=org
			     \-- dc=infra
      
the object dn would be:
	dc=infra, dc=org, o=Anarchy
      

Each object can have different attributes depending on the classes it belongs to. The inheritance in LDAP can be multiple: this means an object can belong to several classes at the same time. Each class defines mandatory and optional attributes that each object of that class needs to (or could) have. This structure is described in the so-called LDAP schemas.

4.2.1. Database Content

In the structure we have adopted, we can isolate our data from other virtual organizations data, collecting all our system data under the object dc=infra, dc=org, o=Anarchy (this is a standard naming convention).

A database includes several kinds of users and objects, which are stored in different branches of the LDAP tree under dc=infra, dc=org, o=Anarchy. For example, these are the main branches we use:

4.2.2. Virtual Users

Throughout the following chapter we will mean "contact" by the word "user", that is an entity with access to various services, be it an organization, a person, or anything else. We introduce here this concept to better abstract the administration tasks and therefore the database structure.

Each user is represented in the database by an object identified through the uid key. We can choose whatever naming system for these objects, but in our case we have decided to name them after their main email addess (e.g. uid=phasa@domain.org). If we want to group the services of a whole organization, instead of a single user, we can choose to create an object called after a domain name as uid=organizzazione.it or anything else we deem meaningful (once a scheme has been laid out and chosen).

Each of these objects can be associated to different services, that will be stored under the "user" object. The type of these objects will correspond to the type of service. For example, these are some possible structures:


* a single user with a single mailbox

  ou=People
  \-- uid=user@domain.org                    shadowAccount
      \-- mail=user@domain.org               virtualMailUser

* a user with a mailbox and a website (therefore an ftp account)

  ou=People
  \-- uid=user@domain.org                    shadowAccount              
      +-- mail=user@domain.org               virtualMailUser
      +-- alias=usersite                     subSite
      \-- ftpname=user                       ftpAccount

* an organization with more accounts, a website and a domain of its own

  ou=People
  \-- uid=organiz.org                        shadowAccount
      +-- mail=user1@organiz.org             virtualMailUser
      +-- mail=user2@organiz.org             virtualMailUser
      +-- cn=www.organiz.org                 virtualHost
      \-- ftpname=organiz                    ftpAccount
On the right of each object you can see the class it belongs to. Particularly, the user object belongs to the class shadowAccount (a POSIX standard) that will allow to resolve NSS lookups and quota management (allowing to specify a uid name and not only a numeric id for each user), even if it will never be used in authentication.

4.2.3. Authentication

In the structure described above, each service requiring authentication matches the requested credentials directly against the child object of the relative service. So the authentication credentials of the shadowAccount object identifying the user, even if present as requested by the scheme, are never directly used.

By using appropriate query filters, we can then make sure that for example the IMAP daemon matches the authentication of users against a virtualMailUser object, with its own password. This allows, in case, to have different passwords for the different services. This possibility is included in the policy that needs to be implemented through the use of administration tools: in fact, LDAP does not have an internal mechanism to specify particular relationships between objects (apart from the hierarchical relationship), a thing most other relational database can do natively. This is the reason why it is impossible to tell LDAP that an object needs to have the same password of the parent object. This of course implies a certain redundancy of the data in the scheme (but not too much).

We will now see how the different services have been configured (relatively to the LDAP authentication). For each service we specify the configuration files concerning the authentication and what is the query that they are set to do on the LDAP server, composed of a base limiting the scope of the query and a filter that selects one or more specific objects:

ssh

ssh authenticates through PAM with its own configuration file, selecting only users in the ou=Admins branch of the LDAP database

query: &(uid=*)(objectClass=posixAccount)

base: ou=Admins, dc=infra, dc=org, o=Anarchy

files:

  /etc/pam.d/common-auth
  auth  sufficient  pam_ldap.so config=/etc/pam_ldap_admin.conf
  auth  required    pam_unix.so use_first_pass
	      

  /etc/pam_ldap_admin.conf
  host 127.0.0.1
  base ou=Admins,dc=infra,dc=org,o=Anarchy
  ldap_version 3
  rootbinddn cn=manager,o=Anarchy
  pam_password crypt
	      

vsftpd

the FTP daemon authenticates using PAM but with another configuration file looking for objects, including the objectClass=ftpAccount attribute, and matching the host attribute with the name of the machine we are trying to authenticate on.

query: &(ftpname=*)(objectClass=ftpAccount)(host=server1)

base: ou=People, dc=infra, dc=org, o=Anarchy

files:

   /etc/pam.d/vsftpd
   auth  required  pam_ldap.so config=/etc/pam_ldap_ftp.conf
	      

   /etc/pam_ldap_ftp.conf
   host 127.0.0.1
   base ou=People,dc=infra,dc=org,o=Anarchy
   ldap_version 3
   rootbinddn cn=manager,o=Anarchy
   scope sub
   pam_login_attribute ftpname
   pam_filter &(host=server1)(status=active)
   pam_password crypt
	      

dovecot

Dovecot is the IMAP daemon and authenticates mail users. It does not use PAM, but its own mechanisms.

query: &(objectClass=virtualMailUser)(host=server1)

base: ou=People, dc=infra, dc=org, o=Anarchy

files:

   /etc/dovecot/dovecot-ldap.conf
   ldap_version = 3
   scope = subtree
   dn = cn=dovecot,ou=Operators,dc=infra,dc=org,o=Anarchy
   dnpass = blablabla
   base = ou=People,dc=infra,dc=org,o=Anarchy
   user_attrs = mail,mailMessageStore,mailMessageStore,,uidNumber,gidNumber
   user_filter = (&(objectClass=virtualMailUser)(status=active)(mail=%u))
   pass_attrs = mail,userPassword
   pass_filter = (&(objectClass=virtualMailUser)(status=active)(mail=%u))
	      

saslauthd

SASL authenticates users sending mail through SMTP. Saslauthd is the daemon actually carrying on the authentication. The configuration of this software is different from the others since it's possible to skip PAM and authenticate directly using an LDAP query. This unfortunately requires a different format for the configuration file. Furthermore we need to remember the -r option for the software since we authenticate users with both a name AND a domain (forgetting this option will cause the daemon to match only the user name and not the domain). We also need to remember to create the saslauthd socket in the Postfix chroot.

query: &(mail=*)(objectClass=virtualMailUser)

base: ou=People, dc=infra, dc=org, o=Anarchy

files:

   /etc/default/saslauthd
   START=yes
   MECHANISMS="ldap"
   PWDIR=/var/spool/postfix/var/run/saslauthd
   PIDFILE="$PWDIR/saslauthd.pid"
   PARAMS="-r -m $PWDIR"
	      

   /etc/saslauthd.conf
   ldap_servers: ldap://127.0.0.1/
   ldap_bind_dn: cn=ring0op,ou=Operators,dc=infra,dc=org,o=Anarchy
   ldap_password: blablabla
   ldap_search_base: ou=People,dc=infra,dc=org,o=Anarchy
   ldap_filter: (&(status=active)(objectClass=virtualMailUser)(mail=%u))
   ldap_auth_method: custom
	      

Note: all the queries above match the provided password with the one stored in the userPassword attribute. The passwords are stored in the LDAP database with different encoding mechanisms, detailed by a prefix that you can find in the attribute before the encoded password in brackets. E.g.:

	  {crypt}dlkj8h23dU9j1
	
Apart from crypt (which uses the crypt system function), it is possible to use the MD5 or the SSHA encoding systems, both of which simply use a hash of the password. Many common commands (as ldappasswd) set the password using one of this mechanisms, considering them more secure. This is relevant when we think that there is not a single way to verify authentication: some services use the bind LDAP system, i.e. try to access the LDAP database using the provided credential: this means that it is the LDAP server itself that manages the password verification. But in other cases (like dovecot) applications behave differently: they get the password from the LDAP server and verify it on their own. The problem is that generally this particular services are not able to correctly check passwords unless they are stored using the crypt mechanism! So be careful when using administration tools.