# Postfix LDAP Structure (schema & filter)



## Leander (Jan 20, 2015)

Hi,

I'm currently busy re-newing my mail server using LDAP instead of SQL based backend. Therwith I also want to implement LDAP based aliases. From my current point of understanding, it is a bad idea to provide aliases directly in the user object as e.g.:

```
# Bad Example User
dn: uid=username,ou=people,${DCs,,}
cn: Username
gidnumber: 2112
[...]
mail: My-01-Mail-Address@MyDomain.TLD
mail: My-02-Mail-Address@MyDomain.TLD
mailAlternateAddress: My-03-Mail-Address@OtherDomain.TLD
mailAlternateAddress: My-04-Mail-Address@OtherDomain.TLD
mailForwardingAddress: allMail@MyDomain.TLD
mailForwardingAddress: allMail@OtherDomain.TLD
[...]
objectclass: top
objectclass: person
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: shadowAccount
uid: username
uidnumber: 2112
[...]
```
Why? Because a user can have multiple mail[AlternateAddress] attributes - meaning the user has a couple of email addresses hosted. Setting up an alias within this object would then apply to all mail addresses of this user.

This was not an option for me neither did I feel comfortable puting sub containers under the user for each email address and potential aliases. The reason for this imply lies in password maintenance - or better said: user should have one master password. This is why I decided to have a separate alias OU in where I can store all aliases - more fine tuned - for each email address specifically on its own. So I was dreaming of something like this as alias leave:

```
dn: ou=My-02-Mail-Address@MyDomain.TLD,ou=aliases,ou=mail,${DCs,,}
mailDeliveryOption: forward
mailForwardingAddress: allMail@MyDomain.TLD
mailForwardingAddress: allMail@OtherDomain.TLD
```


I'm not too well oiled when it comes to know the potentials of LDAP's objectclasses. Now my question is what objectclasses to use best for each alias leave? Of course I could rape a regular fake user _(top person inetOrgPerson)_ for this but I wouldn't want, because it provides so many attributes which wouldn't be required - and an alias is not a person . For my virtual domains I found a simple suitable and bare minimum solution _(dnsDomain domainrelatedobject)_ - and similar I want to do with the aliases.

Is there *recommentable* objectclasses? Or a better solution? Personally I would prefer not to add any additional LDAP schemata - except if there is good reason for.

Here is the most important LDIFs to understand my LDAP DB structure:

```
# People Container
dn: ou=people,${DCs,,}
objectClass: top
objectClass: organizationalUnit
ou: people

# Example User
dn: uid=username,ou=people,${DCs,,}
cn: Username
displayname: Username
gidnumber: 2112
givenname: Username
homedirectory: /home/username
loginshell: /usr/local/bin/bash
mail: My-01-Mail-Address@MyDomain.TLD
o: MyCorporation
objectclass: top
objectclass: person
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: shadowAccount
sn: Surname
telephonenumber: +123456789
uid: username
uidnumber: 2112
userpassword: ${RANDOM}

# Mail Container
dn: ou=mail,${DCs,,}
objectClass: top
objectClass: organizationalUnit
ou: mail

# Aliases Container
dn: ou=aliases,ou=mail,${DCs,,}
objectClass: top
objectClass: organizationalUnit
ou: aliases

# Domains Container
dn: ou=domains,ou=mail,${DCs,,}
objectClass: top
objectClass: organizationalUnit
ou: domains

# Domain Example
dn: dc=Mydomain.TLD,ou=domains,ou=mail,${DCs,,}
objectClass: dnsDomain
objectClass: domainrelatedobject
dc: Mydomain.TLD
description: Xaver & Angelika
seeAlso: uid=User-01,ou=people,${DCs,,}
seeAlso: uid=User-02,ou=people,${DCs,,}
associateddomain: Mydomain.TLD
```


----------



## Leander (Jan 20, 2015)

OK, turned out that it might be best to create a individual schmata (MOD - scheme?). Since I am a total LDAP scheme noob, it would be awesome if someone could have a quick scan over it and tell me whether my copy & paste skills are proper to go.

Thanks a lot 

```
#
# OID Macros (10001 should be IANA-registered)
#

objectidentifier nameSpace  1.3.6.1.4.1.10001
objectidentifier mail  nameSpace:1
objectidentifier objectClassAccount  mail:1


#
# Attributes: objectClass[NAME]:1.[SERIAL]
#

attributetype ( objectClassAccount:1.1
  NAME 'mailAddress'
  DESC 'The hosted mail addresses'
  EQUALITY caseIgnoreIA5Match
  SUBSTR caseIgnoreIA5SubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} SINGLE-VALUE )

attributetype ( objectClassAccount:1.2
  NAME 'MailPassword'
  DESC 'The hosted mail password'
  EQUALITY octetStringMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{128} )

attributetype ( objectClassAccount:1.3
  NAME 'MailAccountStatus'
  DESC 'The status of a user account: active, noaccess, disabled, deleted'
  EQUALITY caseIgnoreIA5Match
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )

attributetype ( objectClassAccount:1.4
  NAME 'mailStorageDirectory'
  DESC 'Path to the maildir/mbox on the mail system'
  EQUALITY caseExactIA5Match
  SUBSTR caseIgnoreIA5SubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} SINGLE-VALUE )

attributetype ( objectClassAccount:1.5
  NAME 'mailAlias'
  DESC 'RFC822 Mailbox - mail alias'
  EQUALITY caseIgnoreIA5Match
  SUBSTR caseIgnoreIA5SubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )

attributetype ( objectClassAccount:1.6
  NAME 'mailDelivery'
  DESC 'Program to execute for all incoming mails.'
  EQUALITY caseExactIA5Match
  SUBSTR caseIgnoreIA5SubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} SINGLE-VALUE )

attributetype ( objectClassAccount:1.7
  NAME 'mailSizeMax'
  DESC 'The maximum size of a single messages the user accepts.'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )

attributetype ( objectClassAccount:1.8
  NAME 'mailReplyText'
  DESC 'A reply text for every incoming message'
  EQUALITY caseIgnoreMatch
  SUBSTR caseIgnoreSubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{4096} SINGLE-VALUE )

attributetype ( objectClassAccount:1.9
  NAME 'mailQuotaSize'
  DESC 'The size of space the user can have until further messages get bounced.'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )

attributetype ( objectClassAccount:1.10
  NAME 'mailQuotaCount'
  DESC 'The number of messages the user can have until further messages get bounced.'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )

attributetype ( objectClassAccount:1.11
  NAME 'mailservice'
  DESC 'The status of a user account: smtp, imap, pop3, managesieve'
  EQUALITY caseIgnoreIA5Match
  SUBSTR caseIgnoreIA5SubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )


#
# Objects: objectClass[NAME]:2.[SERIAL]
#

objectclass ( objectClassAccount:2.1
  NAME 'mailAccount'
  SUP ( top )
  STRUCTURAL
  DESC 'Mail account'
  MUST ( mailAddress $ mailPassword $ mailAccountStatus $ mailStorageDirectory $ mailDelivery $ mailservice )
  MAY ( mailAlias $ mailSizeMax $ mailReplyText $ mailQuotaSize $ mailQuotaCount $ mailservice ) )
```


----------



## Leander (Jan 24, 2015)

The LDAP schema is working nicely. Here is an example LDIF if someone is looking for a similar solution:


```
cat << EOF | sed -E 's|^#.*||' | sed -E '/^$/d' > /tmp/insert.ldif
dn: mailAddress=${LDAP_EMAIL_USR},ou=mail,uid=username-01,ou=people,${DCs,,}
objectclass: mailAccount

mailAddress: ${LDAP_EMAIL_USR}
MailPassword: ${LDAP_EMAIL_PWD}
MailAccountStatus: active
mailStorageDirectory: /mail/
#mailAlias: NONE
mailDelivery: dovecot
mailSizeMax: 10240
mailReplyText: Nothing to reply here
mailQuotaSize: 10240
mailQuotaCount: 0
mailservice: smtp
mailservice: pop3
mailservice: imap
mailservice: managesieve
EOF
```
Now I'm trying to write an according LDAP filter. Unfortunately I struggle quite a lot getting the required attributes by a single query.
My goal is to e.g.: have a query which operates like the following pseudo code:

```
return uidNumber OF ( &(objectClass=posixAccount) ) IF they have a ou=mail AND the mailAddress in this ou=mail IS EQUAL to %s
```
Now I figured out how to use logical operators AND, OR, etc. yet I haven't figured out ho to get the result of e.g. uid IF a subcontainer attribute matched - like in the above examples.
Any help on a example query would be greatly appreciated

Best Regards


----------

