aboutsummaryrefslogtreecommitdiffstats
path: root/ldap
diff options
context:
space:
mode:
Diffstat (limited to 'ldap')
-rw-r--r--ldap/Makefile88
-rw-r--r--ldap/README30
-rw-r--r--ldap/acl.ldif293
-rw-r--r--ldap/database.ldif60
-rw-r--r--ldap/fripost.ldif140
-rw-r--r--ldap/index.ldif44
-rw-r--r--ldap/modules.ldif16
-rw-r--r--ldap/populate.ldif199
-rw-r--r--ldap/syncprov.ldif25
-rw-r--r--ldap/syncrepl.ldif33
-rwxr-xr-xldap/test-user-acl.sh906
11 files changed, 1834 insertions, 0 deletions
diff --git a/ldap/Makefile b/ldap/Makefile
new file mode 100644
index 0000000..8d6868f
--- /dev/null
+++ b/ldap/Makefile
@@ -0,0 +1,88 @@
+DIR := $(shell grep -i '^olcDbDirectory: ' database.ldif | sed -e 's/^olcDbDirectory: //')
+SUFFIX := $(shell grep -i '^olcSuffix: ' database.ldif | sed -e 's/^olcSuffix: //')
+TMPSLAPD := /tmp/$(shell mktemp -u slapd.d-XXXXXX)
+BACKUPDB := /tmp/$(shell mktemp -u db-XXXXXX.ldif)
+BACKUPCONFIG := /tmp/$(shell mktemp -u config-XXXXXX.ldif)
+NUM := $(shell ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "cn=config" "olcSuffix=$(SUFFIX)" dn | sed -e '/^$$/d' -e 's/^dn: //')
+SCHEMA := $(shell grep -i '^dn: ' fripost.ldif | sed -re 's/^dn: cn=([^,]+),.*/\1/')
+
+all:
+ @echo "make install-schema: install the LDAP schema"
+ @echo "make install-db: install the database"
+ @echo "make install-mx: "
+
+
+init:
+ @echo "Suffix: \`$(SUFFIX)\'"
+ @echo "Location: \`$(DIR)\'"
+ @test -e "$(DIR)" && (echo "Error: \`$(DIR)' exists." ; false); true
+ @mkdir -m 0700 "$(DIR)"
+ @chown openldap:openldap "$(DIR)"
+ @ldapadd -Q -Y EXTERNAL -H ldapi:/// -f database.ldif
+# @if test -d "$(SUFFIX)" ; then echo nop ; fi
+
+
+install-mx:
+
+ rm -f -- "$(BACKUPDB)" "$(BACKUPCONFIG)"
+ @mkdir -m 0700 "$(BACKUPSLAPD)"
+
+ @rmdir "$(BACKUPSLAPD)"
+
+
+install-schema:
+ @ldapadd -Q -Y EXTERNAL -H ldapi:/// -f fripost.ldif
+
+
+install-acl:
+ @sed "s/^dn: olcDatabase={.*}hdb,cn=config$$/dn: $(NUM)/" acl.ldif | ldapmodify -Q -Y EXTERNAL -H ldapi:///
+
+
+uninstall:
+ @echo "The database - suffix \"$(SUFFIX)\" - will be saved into \`$(BACKUPDB)' (if non-empty), and then *cleared* (but not deleted)."
+ @echo "Also, its ACLs and indexes will be cleared as well, if there are any."
+ @echo "Global configuration will be saved into \`$(BACKUPCONFIG)'."
+ @echo "slapd will be offline for a moment - at best."
+ @/bin/echo -n "Are you sure you want to continue [y/N] "
+ @read A && if [ "x$$A" = "xY" -o "x$$A" = "xy" ]; then true; else echo Aborted; false; fi
+#
+ @if slapcat -b "$(SUFFIX)" 2>/dev/null | grep -q '.'; then \
+ echo "Saving database" && slapcat -b "$(SUFFIX)" -l "$(BACKUPDB)" && echo "Clearing database" && ldapdelete -Q -Y EXTERNAL -H ldapi:/// -r "$(SUFFIX)" \
+ ;else \
+ echo "Empty database, not saving anything." \
+ ;fi
+#
+ @echo "Saving configuration"; slapcat -n0 -l "$(BACKUPCONFIG)"
+#
+ @if (ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -s base -b "$(NUM)" "(olcAccess=*)" | grep -q '^dn: '); then \
+ echo "Removing ACLs" && /bin/echo -e "dn: $(NUM)\nchangetype: modify\ndelete: olcAccess" | ldapmodify -Q -Y EXTERNAL -H ldapi:/// >/dev/null \
+ ;else \
+ echo "No ACLs to remove" \
+ ;fi
+#
+ @if (ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -s base -b "$(NUM)" "(olcDbIndex=*)" | grep -q '^dn: '); then \
+ echo "Removing indexes" && /bin/echo -e "dn: $(NUM)\nchangetype: modify\ndelete: olcDbIndex" | ldapmodify -Q -Y EXTERNAL -H ldapi:/// >/dev/null \
+ ;else \
+ echo "No indexes to remove" \
+ ;fi
+
+ @echo "Making a new configuration directory at \`$(TMPSLAPD)'"
+ @mkdir -m0700 "$(TMPSLAPD)" && slapcat -n0 | slapadd -F "$(TMPSLAPD)" -n0 && chown -R 'openldap:openldap' "$(TMPSLAPD)"
+
+ @echo "Deleting schema \"cn=$(SCHEMA),cn=config\"" && find "$(TMPSLAPD)" -name "cn={*}$(SCHEMA).ldif" -delete
+#
+ @/etc/init.d/slapd stop
+#
+ @echo "Replacing the old \`slapd.d'"
+ rm -rf /etc/ldap/slapd.d/ && mv "$(TMPSLAPD)" /etc/ldap/slapd.d/
+#
+ @/etc/init.d/slapd start
+#
+ @echo "Don't forget to repopulate the database (if non-empty) from \`$(BACKUPDB)'"
+
+
+
+
+
+
+.PHONY: all backup-db backup-config
diff --git a/ldap/README b/ldap/README
new file mode 100644
index 0000000..037ae65
--- /dev/null
+++ b/ldap/README
@@ -0,0 +1,30 @@
+/!\ This work is still in developpement, DO NOT run/install that on a
+production server!
+
+
+Since the user now have (partial) write access to the LDAP directory, it
+is of crucial importance to configure the ACL properly.
+
+ * "populate.ldif" is meant to provide at least an example of every
+single situation we may encounter in our directory.
+
+ * "test-user-acl.sh" checks the database against the ACLs.
+
+
+/!\ Every modification to the schema or the ACLs should be made to
+"populate.ldif" and "test-user-acl.sh" too!
+
+
+Usage:
+
+ * Load the ACLs:
+
+ ldapmodify -Y EXTERNAL -H ldapi:/// -f acl.ldif
+
+ * Repopulate the database (will clear it out first!):
+
+ ldapdelete -Y EXTERNAL -H ldapi:/// -r "ou=virtual,o=mailHosting,dc=fripost,dc=dev" ; ldapadd -Y EXTERNAL -H ldapi:/// -f populate.ldif
+
+ * Running the test suite:
+
+ sudo ./test-user-acl.sh
diff --git a/ldap/acl.ldif b/ldap/acl.ldif
new file mode 100644
index 0000000..5af52aa
--- /dev/null
+++ b/ldap/acl.ldif
@@ -0,0 +1,293 @@
+# Load this file with
+#
+# ldapmodify -Y EXTERNAL -H ldapi:/// -f acl.ldif
+#
+# It will remove existing ACLs, and add the following instead. Ensure
+# that it's indeed the database #1 that you want to amend:
+#
+# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "cn=config" "olcSuffix=o=mailHosting,dc=fripost,dc=dev" dn
+#
+#
+# /!\ ATTENTION! Every modification made to this file should be
+# /!\ implemented in the test suite as well!
+#
+#
+# References:
+# - http://www.openldap.org/doc/admin24/access-control.html
+# - http://www.openldap.org/faq/data/cache/189.html
+# - http://www.openldap.org/faq/data/cache/1140.html
+# - http://www.openldap.org/faq/data/cache/1133.html
+# - man 5 slapd.access
+
+
+dn: olcDatabase={1}hdb,cn=config
+changetype: modify
+replace: olcAccess
+## Managers have read/write access to the "virtual" subtree.
+#olcAccess: to dn.subtree="ou=virtual,o=mailHosting,dc=fripost,dc=dev"
+# by dn.onelevel="ou=managers,o=mailHosting,dc=fripost,dc=org" write
+# by * break
+#-
+## 1. Users/Services/Managers can change their password (but not read it).
+## 2. Anonymous users/services/managers can bind.
+## 3. Else, we inspect the 2 following ACLs.
+#add: olcAccess
+olcAccess: to dn.subtree="o=mailHosting,dc=fripost,dc=dev"
+ attrs=userPassword
+ by self =w
+ by anonymous auth
+ by users none break
+-
+# The postmaster of a domain can change (replace) his/her users' password.
+add: olcAccess
+olcAccess: to dn.regex="^fvu=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualMailbox)
+ attrs=userPassword
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" =w
+-
+# No permission on the userPassword attribute for other users.
+# (That's a catch-all, just to be sure that services, etc. cannot read the passwords).
+add: olcAccess
+olcAccess: to dn.subtree="o=mailHosting,dc=fripost,dc=dev"
+ attrs=userPassword
+ by * none
+#-
+## Services can read the whole subtree (minus the userPassword attributes).
+#add: olcAccess
+#olcAccess: to dn.subtree="o=mailHosting,dc=fripost,dc=dev"
+# attrs=entry,creatorsName,@fripostVirtualDomain,@fripostVirtualMailbox,@fripostVirtualAlias,@fripostVirtualML
+# by dn.onelevel="ou=services,o=mailHosting,dc=fripost,dc=org" read
+# by users * break
+-
+# Users can search (e.g., to list the entries they have created).
+# Additional permissions may be added later on.
+add: olcAccess
+olcAccess: to dn.subtree="ou=virtual,o=mailHosting,dc=fripost,dc=dev"
+ attrs=entry,creatorsName,fripostOwner,fripostPostmaster,fripostCanCreateAlias,fripostCanCreateML
+ by users =s break
+-
+# Everyone can delete domains. (Provided he has +d access to the "entry"
+# attribute of the domains he wants to delete.)
+add: olcAccess
+olcAccess: to dn.base="ou=virtual,o=mailHosting,dc=fripost,dc=dev"
+ attrs=children
+ by users =z
+-
+# 1. The postmaster of a domain can give (or take back) people the right to create
+# aliases.
+# 2,3. People that can create aliases can list the members of the group.
+add: olcAccess
+olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev$"
+ filter=(objectClass=fripostVirtualDomain)
+ attrs=fripostCanCreateAlias
+ by dnattr=fripostPostmaster write
+ by dnattr=fripostOwner read
+ by set.exact="this/fripostCanCreateAlias & (user | user/-1)" read
+-
+# 1. The postmaster of a domain can give (or take back) people the right to create
+# mailing lists.
+# 2,3. People that can create mailing lists can list the members of the group.
+add: olcAccess
+olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev$"
+ filter=(objectClass=fripostVirtualDomain)
+ attrs=fripostCanCreateML
+ by dnattr=fripostPostmaster write
+ by dnattr=fripostOwner read
+ by set.exact="this/fripostCanCreateML & (user | user/-1)" read
+-
+# 1-3. Noone (but the managers) can appoint domain Owners or Postmasters.
+# But people that can create aliases and mailing lists can list the members of their group.
+add: olcAccess
+olcAccess: to dn.regex="^(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualDomain)
+ attrs=fripostOwner,fripostPostmaster
+ by dnattr=fripostOwner read
+ by dnattr=fripostPostmaster read
+ by set.exact="(this/fripostCanCreateAlias | this/fripostCanCreateML)& (user | user/-1)" read
+ by dn.onelevel,expand="$1" +d
+ by users +0
+-
+# Every one can add or delete children, but we will be carefull with the
+# kid's "entry" attribute, which require +a and +z to add and delete
+# respectively.
+add: olcAccess
+olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev$"
+ filter=(objectClass=fripostVirtualDomain)
+ attrs=children
+ by users +w
+-
+# 1. Domain owners can edit their entry's attributes.
+# 2. So can domain postmasters.
+# 3. Domain users can read the public domain attributes.
+# 4. So can users with "canCreateAlias" or "canCreateML" access.
+add: olcAccess
+olcAccess: to dn.regex="^(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualDomain)
+ attrs=fvd,fripostIsStatusActive,description
+ by dnattr=fripostOwner write
+ by dnattr=fripostPostmaster write
+ by dn.onelevel,expand="$1" read
+ by set.exact="(this/fripostCanCreateAlias | this/fripostCanCreateML) & (user | user/-1)" read
+-
+# 1. Domain owners can edit their entry's attributes.
+# 2. So can domain postmasters.
+add: olcAccess
+olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev$"
+ filter=(objectClass=fripostVirtualDomain)
+ attrs=@fripostVirtualDomain
+ by dnattr=fripostOwner write
+ by dnattr=fripostPostmaster write
+ by users +0
+-
+# 1. Domain owners can delete the domain (and read the entry).
+# 2. So can domain postmasters.
+# 3. Domain users can read the domain entry (but not delete it).
+# 4. So can users with "canCreateAlias" or "canCreateML" rights.
+add: olcAccess
+olcAccess: to dn.regex="^(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualDomain)
+ attrs=entry
+ by dnattr=fripostOwner +zrd
+ by dnattr=fripostPostmaster +zrd
+ by dn.onelevel,expand="$1" +rd
+ by set.exact="(this/fripostCanCreateAlias | this/fripostCanCreateML) & (user | user/-1)" +rd
+ by users +0
+-
+# Noone (but the managers) can change quotas.
+add: olcAccess
+olcAccess: to dn.regex="^fvu=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualMailbox)
+ attrs=fripostMailboxQuota
+ by self read
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" read
+-
+# 1. Users can modify their own entry.
+# 2. So can their postmasters.
+add: olcAccess
+olcAccess: to dn.regex="^fvu=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualMailbox)
+ attrs=@FripostVirtualMailbox
+ by self write
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" write
+-
+# 1. Postmasters can create mailboxes (but not delete them).
+# (Provided that they have +a access to the parent's "children" attribute.)
+# 2. Users can read their entry (but not delete it).
+add: olcAccess
+olcAccess: to dn.regex="^fvu=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualMailbox)
+ attrs=entry
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" +ard
+ by self +rd
+-
+# Reserved aliases cannot be deactivated. (But the alias definition may be changed by the
+# domain owner.)
+add: olcAccess
+olcAccess: to dn.regex="^fva=(abuse|postmaster),(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualAlias)
+ attrs=fripostIsStatusActive,fripostOwner,fva
+ by group/fripostVirtualDomain/fripostOwner.expand="$2" read
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$2" read
+ by users +0
+-
+# Reserved aliases cannot be deleted.
+add: olcAccess
+olcAccess: to dn.regex="^fva=(abuse|postmaster),(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualAlias)
+ attrs=entry
+ by group/fripostVirtualDomain/fripostOwner.expand="$2" +ard
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$2" +ard
+ by set.exact="this/-1/fripostCanCreateAlias & (user | user/-1)" +a
+ by users +0
+-
+# 1. The alias owner can list the ownership of the entry.
+# 2. The domain owner can add/delete/change the ownership of the entry.
+# 3. So can the domain postmasters.
+add: olcAccess
+olcAccess: to dn.regex="^fva=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualAlias)
+ attrs=fripostOwner
+ by dnattr=fripostOwner read continue
+ by group/fripostVirtualDomain/fripostOwner.expand="$1" write
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" write
+ by users +0
+-
+# 1. The alias owners can edit the rest of their entry's attributes.
+# 2. So can the domain owners.
+# 3. So can the domain postmasters.
+add: olcAccess
+olcAccess: to dn.regex="^fva=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualAlias)
+ attrs=@FripostVirtualAlias
+ by dnattr=fripostOwner write
+ by group/fripostVirtualDomain/fripostOwner.expand="$1" write
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" write
+-
+# 1. The alias owners can read and delete the entry.
+# 2. So can the domain owner.
+# 3. So can the domain postmaster.
+# 4. Users with "canCreateAlias" access (either explicitely, or as a wildcard) for the domain can create aliases for that domain.
+# (But *not* delete them, unless also owner.)
+add: olcAccess
+olcAccess: to dn.regex="^fva=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualAlias)
+ attrs=entry
+ by dnattr=fripostOwner +zrd continue
+ by group/fripostVirtualDomain/fripostOwner.expand="$1" +wrd
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" +wrd
+ by set.exact="this/-1/fripostCanCreateAlias & (user | user/-1)" +a
+ by users +0
+-
+# 1. The mailing list owner can list the ownership of the entry.
+# 2. The domain owner can add/delete/change the ownership of the entry.
+# 3. So can the domain postmasters.
+add: olcAccess
+olcAccess: to dn.regex="^fvml=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualML)
+ attrs=fripostOwner
+ by dnattr=fripostOwner read continue
+ by group/fripostVirtualDomain/fripostOwner.expand="$1" write
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" write
+ by users +0
+-
+# 1. The mailing list owner read (but not edit) the transport-related attributes.
+# 2. So can the domain ower.
+# 3. So can the domain postmaster.
+add: olcAccess
+olcAccess: to dn.regex="^fvml=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualML)
+ attrs=fripostMLManager,fripostMLCommand
+ by dnattr=fripostOwner read
+ by group/fripostVirtualDomain/fripostOwner.expand="$1" read
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" read
+-
+# 1. The mailing list owners can edit their entry's attributes.
+# 2. So can the domain owners.
+# 3. So can the domain postmasters.
+add: olcAccess
+olcAccess: to dn.regex="^fvml=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualML)
+ attrs=@FripostVirtualML
+ by dnattr=fripostOwner write
+ by group/fripostVirtualDomain/fripostOwner.expand="$1" write
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" write
+-
+# 1. The mailing list owners can read and delete the entry.
+# 2. So can the domain's Owner.
+# 3. So can the domain's Postmaster.
+# 4. Users with "canCreateML" capability (either explicitely, or as a wildcard) for the domain can create mailing lists for that domain.
+# (But *not* delete them, unless also owner.)
+add: olcAccess
+olcAccess: to dn.regex="^fvml=[^,]+,(fvd=[^,]+,ou=virtual,o=mailHosting,dc=fripost,dc=dev)$"
+ filter=(objectClass=fripostVirtualML)
+ attrs=entry
+ by dnattr=fripostOwner +rzd continue
+ by group/fripostVirtualDomain/fripostOwner.expand="$1" +rwd
+ by group/fripostVirtualDomain/fripostPostmaster.expand="$1" +rwd
+ by set.exact="this/-1/fripostCanCreateML & (user | user/-1)" +a
+ by users +0
+-
+# Catch the "break" control above.
+add: olcAccess
+olcAccess: to dn.subtree="ou=virtual,o=mailHosting,dc=fripost,dc=dev"
+ by users +0
diff --git a/ldap/database.ldif b/ldap/database.ldif
new file mode 100644
index 0000000..300d933
--- /dev/null
+++ b/ldap/database.ldif
@@ -0,0 +1,60 @@
+# Load this file with
+#
+# ldapadd -Y EXTERNAL -H ldapi:/// -f database.ldif
+#
+# It will create a new database under `/var/lib/ldap/dev', which has to
+# be an existing directory:
+#
+# mkdir -m 0700 /var/lib/ldap/dev && chown openldap:openldap /var/lib/ldap/dev
+
+
+dn: olcDatabase=hdb,cn=config
+objectClass: olcDatabaseConfig
+objectClass: olcHdbConfig
+olcDbDirectory: /var/lib/ldap/dev
+olcSuffix: o=mailHosting,dc=fripost,dc=dev
+olcLastMod: TRUE
+olcDbCheckpoint: 512 30
+# Require LDAPv3 protocol and authentication prior to directory
+# operations.
+olcRequires: LDAPv3 authc
+# We don't want to give "canCreate{Alias,ML}" write access to alias/ml
+# attributes.
+olcAddContentAcl: FALSE
+# The root user has all rights on the whole database (when SASL-binding
+# on a UNIX socket).
+olcRootDN: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
+
+
+#
+# Performance considerations
+#
+# References:
+# - https://wiki.zimbra.com/wiki/OpenLDAP_Performance_Tuning_5.0
+# - http://www.openldap.org/doc/admin24/tuning.html
+#
+#
+# 1. On single- and dual-core systems, change the maximum number of
+# threads to 8. (The default, 16, is fine for 4- and 8-core systems.)
+#
+# dn: cn=config
+# changetype: modify
+# add: olcThreads
+# olcThreads: 8
+#
+#
+# 2. It may be a good idea to modify DB_CONFIG, depending on the output
+# of
+#
+# db4.8_stat -m -h /var/lib/ldap/ | head -16
+#
+# (For optimal performance, the Requested pages found in the cache
+# should be above 95%, and the pages forced from the cache should be 0.)
+#
+# and
+#
+# db4.8_stat -m -h /var/lib/ldap/ | head -16
+#
+# (For optimal performance, usage should be within 85% of the configured
+# values.)
+#
diff --git a/ldap/fripost.ldif b/ldap/fripost.ldif
new file mode 100644
index 0000000..e0052a3
--- /dev/null
+++ b/ldap/fripost.ldif
@@ -0,0 +1,140 @@
+# Load this file with
+#
+# ldapadd -Y EXTERNAL -H ldapi:/// -f fripost.ldif
+#
+# It will load the schema. To perform modifications, the easiest way is to
+#
+# * Save the database: slapcat -b 'o=mailHosting,dc=fripost,dc=dev' > /tmp/db.ldif
+# * Save the configuration: slapcat -n0 > /tmp/config.ldif
+# * Backup slap.d: cp -a /etc/ldap/slapd.d/ /tmp/slap.d_back
+# * Edit the schema in /tmp/config.ldif
+# * Load the new config: mkdir -m 0700 /tmp/slapd.d_new && slapadd -F /tmp/slapd.d_new -n0 -l /tmp/config.ldif
+# * Stop slapd: /etc/init.d/slapd stop
+# * Load the new config: rm -rf /etc/ldap/slapd.d/ && mv /tmp/slapd.d_new /etc/ldap/slapd.d && chown -R openldap:openldap /etc/ldap/slapd.d
+# * Create indexes: su openldap -c "slapindex -b 'o=mailHosting,dc=fripost,dc=dev'"
+# * Start slapd: /etc/init.d/slapd start
+# If it fails, remove the existing database and see what's wrong
+# rm -rf /var/lib/ldap/dev/* && su openldap -c "slapadd -b 'o=mailHosting,dc=fripost,dc=org' -l /tmp/db.ldif"
+#
+#
+# /!\ ATTENTION! Every modification made to this file should be
+# /!\ implemented in the test suite as well!
+#
+#
+# References:
+# - http://courier.svn.sourceforge.net/svnroot/courier/trunk/courier-authlib/authldap.schema
+# - http://www.qmail-ldap.org/wiki/index.php/Qmail.schema
+# - http://www.wanderingbarque.com/howtos/mailserver/mailserver.html
+
+
+# OID prefix: 1.3.6.1.4.1.40011
+
+# This schema depends on:
+# - core.schema
+# - cosine.schema
+# - nis.schema
+
+
+dn: cn=fripost-master,cn=schema,cn=config
+objectClass: olcSchemaConfig
+#
+# Attributes: 1.3.6.1.4.1.40011.1.1
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.1 NAME 'fvd'
+ DESC 'A virtual mail domain'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} SINGLE-VALUE )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.2 NAME 'fvu'
+ DESC 'The local part of a virtual user'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.3 NAME 'fva'
+ DESC 'The local part of a virtual mail alias'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.4 NAME 'fvml'
+ DESC 'The local part of a virtual mailing list'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.5 NAME 'fripostMLCommand'
+ DESC 'The local part of a command associated with a mailing list'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.6 NAME 'fripostMaildrop'
+ DESC 'An email address the virtual alias should be mapped to'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.7 NAME 'fripostIsStatusActive'
+ DESC 'Is the entry active?'
+ EQUALITY booleanMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.8 NAME 'fripostMailboxQuota'
+ DESC 'The quota on a mailbox e.g., "50MB"'
+ EQUALITY caseExactMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32} SINGLE-VALUE )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.9 NAME 'fripostCanCreateAlias'
+ DESC 'A user/domain that can create aliases for the parent domain'
+ SUP distinguishedName )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.10 NAME 'fripostCanCreateML'
+ DESC 'A user/domain that can create mailing lists for the parent domain'
+ SUP distinguishedName )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.11 NAME 'fripostOwner'
+ DESC 'A user that owns the parent domain'
+ SUP distinguishedName )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.12 NAME 'fripostPostmaster'
+ DESC 'A user that is a postmaster of the parent domain'
+ SUP distinguishedName )
+#
+olcAttributeTypes: ( 1.3.6.1.4.1.40011.1.2.1.13 NAME 'fripostMLManager'
+ DESC 'A mailing list manager'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{64} SINGLE-VALUE )
+#
+#
+# Objects: 1.3.6.1.4.1.40011.1.2
+#
+olcObjectclasses: ( 1.3.6.1.4.1.40011.1.2.1 NAME 'FripostVirtualDomain'
+ SUP top STRUCTURAL
+ DESC 'Virtual domain'
+ MUST ( fvd $ fripostIsStatusActive )
+ MAY ( fripostCanCreateAlias $ fripostCanCreateML $
+ fripostOwner $ fripostPostmaster $
+ fripostMaildrop $ description ) )
+#
+# | TODO: add limits here
+olcObjectclasses: ( 1.3.6.1.4.1.40011.1.2.2 NAME 'FripostVirtualMailbox'
+ SUP top STRUCTURAL
+ DESC 'Virtual mailbox'
+ MUST ( fvu $ userPassword $ fripostIsStatusActive )
+ MAY ( fripostMailboxQuota $ fripostMaildrop $ cn $ description) )
+#
+olcObjectclasses: ( 1.3.6.1.4.1.40011.1.2.3 NAME 'FripostVirtualAlias'
+ SUP top STRUCTURAL
+ DESC 'Virtual alias'
+ MUST ( fva $ fripostMaildrop $ fripostIsStatusActive )
+ MAY ( fripostOwner $ description ) )
+#
+olcObjectclasses: ( 1.3.6.1.4.1.40011.1.2.4 NAME 'FripostVirtualML'
+ SUP top STRUCTURAL
+ DESC 'Mailing List'
+ MUST ( fvml $ fripostMLManager $ fripostIsStatusActive )
+ MAY ( fripostMLCommand $ fripostOwner $ description ) )
+
diff --git a/ldap/index.ldif b/ldap/index.ldif
new file mode 100644
index 0000000..d5f31a5
--- /dev/null
+++ b/ldap/index.ldif
@@ -0,0 +1,44 @@
+# Load this file with
+#
+# ldapmodify -Y EXTERNAL -H ldapi:/// -f index.ldif
+#
+# It will remove existing indexes, and add the following instead. Ensure
+# that it's indeed the database #1 that you want to amend:
+#
+# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "cn=config" "olcSuffix=o=mailHosting,dc=fripost,dc=dev" dn
+#
+#
+# To reindex an existing database, you have to
+# * Stop slapd /etc/init.d/slapd stop
+# * Reindex su openldap -c "slapindex -b 'o=mailHosting,dc=fripost,dc=dev'"
+# * Restart slapd /etc/init.d/slapd start
+#
+#
+# References
+# - http://www.openldap.org/doc/admin24/tuning.html
+# - http://www.openldap.org/faq/data/cache/42.html
+# - http://www.openldap.org/faq/data/cache/136.html
+# - http://www.zytrax.com/books/ldap/apa/indeces.html
+
+
+dn: olcDatabase={1}hdb,cn=config
+changetype: modify
+replace: olcDbIndex
+olcDbIndex: objectClass eq
+-
+add: olcDbIndex
+olcDbIndex: fripostIsStatusActive eq
+-
+add: olcDbIndex
+olcDbIndex: fvd,fvu,fva,fvml,fripostMLCommand,fripostMLManager eq
+-
+add: olcDbIndex
+olcDbIndex: fripostMaildrop pres
+# ^ TODO: a presence index on fripostMaildrop is not optimal, as the
+# attribute is not very rare...
+# Having a different attribute for the virtualMailbox object class would
+# be better.
+-
+# synprov specific indexing (provider side)
+add: olcDbIndex
+olcDbIndex: entryCSN,entryUUID eq
diff --git a/ldap/modules.ldif b/ldap/modules.ldif
new file mode 100644
index 0000000..0e63819
--- /dev/null
+++ b/ldap/modules.ldif
@@ -0,0 +1,16 @@
+# Load this file (on the provider) with
+#
+# ldapmodify -Y EXTERNAL -H ldapi:/// -f modules.ldif
+#
+# It will load the "syncprov" module.
+#
+#
+# References:
+# - http://www.openldap.org/doc/admin24/replication.html#Syncrepl
+# - http://www.zytrax.com/books/ldap/ch7/#ol-syncrepl-rap
+
+
+dn: cn=module{0}, cn=config
+changetype: modify
+add: olcModuleLoad
+olcModuleLoad: syncprov.la
diff --git a/ldap/populate.ldif b/ldap/populate.ldif
new file mode 100644
index 0000000..04d5177
--- /dev/null
+++ b/ldap/populate.ldif
@@ -0,0 +1,199 @@
+# Load this file with
+#
+# ldapadd -Y EXTERNAL -H ldapi:/// -f populate.ldif
+#
+# It will populate the directory for testing purposes.
+# If "o=mailHosting,dc=fripost,dc=dev" exists, you can delete it with
+#
+# ldapdelete -Y EXTERNAL -H ldapi:/// -r "ou=virtual,o=mailHosting,dc=fripost,dc=dev"
+
+# ou=quotas,o=mailHosting,dc=fripost,dc=dev
+# |- fvd=fripost.org
+# | maxAccounts: 20 fripost.org
+# | maxAlias: 5 user@fripost.org
+# |-
+
+
+dn: o=mailHosting,dc=fripost,dc=dev
+objectClass: organization
+description: Mail hosting
+
+
+dn: ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: organizationalUnit
+description: Virtual mail hosting
+
+
+# An independent domain, not self managed
+dn: fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualDomain
+fripostCanCreateAlias: fvu=fake,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostCanCreateML: fvu=fake,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostIsStatusActive: TRUE
+
+dn: fvu=user1,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualMailbox
+userPassword: user1
+fripostIsStatusActive: TRUE
+
+dn: fvu=user2,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualMailbox
+userPassword: user2
+fripostIsStatusActive: TRUE
+
+# A owned alias
+dn: fva=alias1,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualAlias
+fripostIsStatusActive: TRUE
+fripostOwner: fvu=user1,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostMaildrop: user1@fripost.org
+fripostMaildrop: user1@example.org
+# Buggy owner
+fripostOwner: fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+
+# An independent alias
+dn: fva=alias2,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualAlias
+fripostIsStatusActive: TRUE
+fripostMaildrop: user2@fripost.org
+
+# A owned mailing list
+dn: fvml=ml1,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualML
+fripostMLManager: mailman
+fripostIsStatusActive: TRUE
+fripostMLCommand: ml1-request
+fripostMLCommand: ml1-bounces
+fripostOwner: fvu=user1,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+
+# An independent mailing list (for user1)
+dn: fvml=ml2,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualML
+fripostMLManager: schleuder
+fripostIsStatusActive: TRUE
+fripostOwner: fvu=user2,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+# Buggy owner
+fripostOwner: fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+
+
+# An independent domain, with canCreateAlias options
+dn: fvd=example.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualDomain
+fripostCanCreateAlias: fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostIsStatusActive: TRUE
+
+# A owned alias
+dn: fva=alias1,fvd=example.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualAlias
+fripostIsStatusActive: TRUE
+fripostOwner: fvu=user1,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostMaildrop: user1@fripost.org
+
+
+# An independent domain, with canCreateML options
+dn: fvd=example2.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualDomain
+fripostCanCreateML: fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostIsStatusActive: TRUE
+
+# A owned mailing list
+dn: fvml=ml1,fvd=example2.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualML
+fripostIsStatusActive: TRUE
+fripostMLManager: schleuder
+fripostOwner: fvu=user2,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+
+
+# An independent domain, with both can createAlias and canCreateML options
+dn: fvd=example3.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualDomain
+fripostCanCreateAlias: fvu=user2,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostCanCreateML: fvu=user2,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostIsStatusActive: TRUE
+
+# A owned mailing list
+dn: fvml=ml,fvd=example3.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualML
+fripostIsStatusActive: TRUE
+fripostMLManager: mailman
+fripostOwner: fvu=user1,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+
+
+# A owned domain
+dn: fvd=owned.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualDomain
+fripostIsStatusActive: TRUE
+fripostOwner: fvu=user1,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+# Buggy owner
+fripostOwner: fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+
+dn: fva=alias,fvd=owned.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualAlias
+fripostIsStatusActive: TRUE
+fripostMaildrop: user1@fripost.org
+
+dn: fva=abuse,fvd=owned.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualAlias
+fripostIsStatusActive: TRUE
+fripostMaildrop: abuse@fripost.org
+
+dn: fva=postmaster,fvd=owned.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualAlias
+fripostIsStatusActive: TRUE
+fripostMaildrop: postmaster@fripost.org
+
+dn: fvml=ml,fvd=owned.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualML
+fripostMLManager: mailman
+fripostIsStatusActive: TRUE
+fripostMLCommand: ml-request
+fripostMLCommand: ml-bounces
+
+dn: fvu=user,fvd=owned.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualMailbox
+userPassword: user
+fripostIsStatusActive: TRUE
+
+
+# A postmastered domain
+dn: fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualDomain
+fripostIsStatusActive: TRUE
+fripostPostmaster: fvu=user1,fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostPostmaster: fvu=postmaster,fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+# Buggy owner
+fripostPostmaster: fvd=fripost.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+fripostCanCreateAlias: fvu=user,fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+
+dn: fva=alias,fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualAlias
+fripostIsStatusActive: TRUE
+fripostMaildrop: user1@fripost.org
+
+dn: fva=abuse,fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualAlias
+fripostIsStatusActive: TRUE
+fripostMaildrop: abuse@fripost.org
+fripostOwner: fvu=postmaster,fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+
+dn: fva=postmaster,fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualAlias
+fripostIsStatusActive: TRUE
+fripostMaildrop: postmaster@fripost.org
+
+dn: fvml=ml,fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualML
+fripostMLManager: mailman
+fripostIsStatusActive: TRUE
+fripostMLCommand: ml-request
+fripostMLCommand: ml-bounces
+
+dn: fvu=user,fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualMailbox
+userPassword: user
+fripostIsStatusActive: TRUE
+
+dn: fvu=postmaster,fvd=postmastered.org,ou=virtual,o=mailHosting,dc=fripost,dc=dev
+objectClass: fripostVirtualMailbox
+userPassword: postmaster
+fripostIsStatusActive: TRUE
diff --git a/ldap/syncprov.ldif b/ldap/syncprov.ldif
new file mode 100644
index 0000000..66ce154
--- /dev/null
+++ b/ldap/syncprov.ldif
@@ -0,0 +1,25 @@
+# Load this file (on the provider) with
+#
+# ldapadd -Y EXTERNAL -H ldapi:/// -f syncprov.ldif
+#
+# It will load the "syncprov" overlay configuration for the database #1.
+# Ensure that it's indeed the database #1 that you want to configure:
+#
+# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "cn=config" "olcSuffix=o=mailHosting,dc=fripost,dc=dev" dn
+#
+#
+# References:
+# - http://www.openldap.org/doc/admin24/replication.html#Syncrepl
+# - http://www.zytrax.com/books/ldap/ch7/#ol-syncrepl-rap
+# - man 5 slapo-syncprov
+
+
+dn: olcOverlay=syncprov,olcDatabase={1}hdb,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcSyncProvConfig
+olcOverlay: syncprov
+# contextCSN saved to database every 50 updates or 5
+# minutes
+olcSpCheckpoint: 50 5
+syncprov-reloadhint: TRUE
+
diff --git a/ldap/syncrepl.ldif b/ldap/syncrepl.ldif
new file mode 100644
index 0000000..6b9c378
--- /dev/null
+++ b/ldap/syncrepl.ldif
@@ -0,0 +1,33 @@
+# Load this file (on the consumer) with
+#
+# ldapmodify -Y EXTERNAL -H ldapi:/// -f syncrepl.ldif
+#
+# It will load the "syncRepl" directive for the database #1. Ensure
+# that it's indeed the database #1 that you want to configure:
+#
+# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "cn=config" "olcSuffix=o=mailHosting,dc=fripost,dc=dev" dn
+#
+#
+# References:
+# - http://www.openldap.org/doc/admin24/replication.html#Syncrepl
+# - http://www.zytrax.com/books/ldap/ch7/#ol-syncrepl-rap
+
+# TODO: Delta-syncrepl configuration? http://www.openldap.org/doc/admin24/replication.html#Delta-syncrepl
+
+dn: olcDatabase={1}hdb,cn=config
+changetype: modify
+replace: olcSyncRepl
+# Increase the rid for the different consumers
+olcSyncRepl: rid=000
+provider=ldap://127.0.0.1:3890
+bindmethod=simple
+binddn="cn=SMTP,ou=services,o=mailHosting,dc=fripost,dc=org"
+credentials="xxxxxx"
+type=refreshAndPersist
+retry="5 5 300 +"
+searchbase="ou=virtual,o=mailHosting,dc=fripost,dc=org"
+filter="(&(|(objectClass=FripostVirtualDomain)(objectClass=FripostVirtualMailbox)(objectClass=FripostVirtualAlias)(objectClass=FripostVirtualML))(fripostIsStatusActive=TRUE))"
+attrs="fripostIsStatusActive,fripostMaildrop,fvd,fvu,fva,fvml,fripostMLCommand,fripostMLManager"
+scope=sub
+schemachecking=off
+
diff --git a/ldap/test-user-acl.sh b/ldap/test-user-acl.sh
new file mode 100755
index 0000000..4b233ef
--- /dev/null
+++ b/ldap/test-user-acl.sh
@@ -0,0 +1,906 @@
+#!/bin/sh
+
+# This a test suite for the user ACLs.
+# *Every* modification to the schema and/or to the ACLs should be made
+# here too! It is crucial that the test suite doesn't break.
+#
+# Requires a populated database with every possible test case, like
+# `populate.ldif'.
+# The database remains unchanged, but slapacl needs to have read access,
+# so the test suite may have to be run by root.
+
+
+SLAPACL=/usr/sbin/slapacl
+SUFFIX="ou=virtual,o=mailHosting,dc=fripost,dc=dev"
+
+RES=$(tempfile) || exit 1
+
+checkACL () {
+ CMD=${SLAPACL}
+ BIND="${1},${SUFFIX}"
+ if [ -n "${1}" ]; then CMD="${CMD} -D ${BIND}"; fi
+ if [ -n "${2}" ]; then BASE="${2},${SUFFIX}"; else BASE="${SUFFIX}"; fi
+ shift; shift
+
+ ${CMD} -b "${BASE}" "$@" 2>&1 | grep -vixF -e "authcDN: \"${BIND}\""
+ N=1
+}
+
+msg () {
+ /bin/echo -n " "
+ /bin/echo -n "$@"
+ /bin/echo -n "... "
+}
+
+isOK () {
+ # Ensures that every line in "$RES" satisfies the regexp "$1".
+ # The total count is the number of "$2" attributes, or the number of lines in "$RES".
+ [ -n "${1}" ] || exit 1
+ cat > "${RES}"
+ if [ -n "${2}" ]; then
+ if [ -n "${3}" ]; then
+ TOT=$(grep -Pic "^${3} access to ${2}: " < "${RES}")
+ else
+ TOT=$(grep -Pic "^((write|add|delete|read|search|compare|auth|disclose) access to )?${2}: " < "${RES}")
+ fi
+ else
+ TOT=$(wc -l < "${RES}")
+ fi
+ FAILS=$(grep -vc "${1}" < "${RES}")
+ if [ ${FAILS} -eq 0 ]; then
+ /bin/echo -n "OK"
+ else
+ echo "Fail! (x${FAILS})"; exit 1
+ fi
+ /bin/echo -n " (${TOT}/${TOT})"
+ echo
+ # We need a complete test suite
+ ([ -n "${TOT}" ] && [ ${TOT} -ne 0 ]) || (echo "Add more test data."; exit 1)
+}
+
+search () {
+ ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:// "$@"
+}
+
+
+########################################################################
+
+
+DOMAINS=$(search -u -b "${SUFFIX}" "objectClass=FripostVirtualDomain" dn | \
+ grep -i '^ufn: ' | sed -re 's/^ufn: ([^,]+),.*/fvd=\1/')
+USERS=$(search -u -b "${SUFFIX}" "objectClass=FripostVirtualMailbox" dn | \
+ grep -i '^ufn: ' | sed -re 's/^ufn: ([^,]+), *([^,]+),.*/fvu=\1,fvd=\2/')
+ALIASES=$(search -u -b "${SUFFIX}" "objectClass=FripostVirtualAlias" dn | \
+ grep -i '^ufn: ' | sed -re 's/^ufn: ([^,]+), *([^,]+),.*/fva=\1,fvd=\2/')
+MLS=$(search -u -b "${SUFFIX}" "objectClass=FripostVirtualML" dn | \
+ grep -i '^ufn: ' | sed -re 's/^ufn: ([^,]+), *([^,]+),.*/fvml=\1,fvd=\2/')
+
+
+########################################################################
+
+
+echo "Anonymous users:"
+
+
+# Anonymous need to bind to any fvu
+msg "Have =xd access to \"userPassword\" attributes"
+for U in ${USERS}; do
+ checkACL "" "${U}" userPassword
+done | isOK 'auth(=xd)$'
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have =0 access to the rest of user entries"
+for U in ${USERS}; do
+ checkACL "" "${U}"
+done | grep -v '^userPassword=.*: auth(=xd)$' | isOK '=0$' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have =0 access to alias entries"
+for A in ${ALIASES}; do
+ checkACL "" "${A}"
+done | isOK '=0' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have =0 access to mailing lists entries"
+for ML in ${MLS}; do
+ checkACL "" "${ML}"
+done | isOK '=0' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have =0 access to domain entries"
+for D in ${DOMAINS}; do
+ checkACL "" "${D}"
+done | isOK '=0' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have =0 access to the base"
+checkACL "" "" | isOK '=0' entry
+[ $? -eq 0 ] || exit $?
+
+
+###########################################################################
+
+
+echo
+echo "Authenticated users, access to the base"
+
+
+usersB () {
+ for U in ${USERS}; do
+ checkACL "${U}" "" "$@"
+ done
+}
+
+
+msg "Have =s access on the base's \"entry\" attribute"
+usersB entry | isOK '=s' entry
+[ $? -eq 0 ] || exit $?
+
+
+# Needed to delete domains. They cannot create domains though, as they
+# would need =a on the "children" attribute.
+msg "Have =z access on the base's \"children\" attribute"
+usersB children | isOK '=z$' children
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have =0 access to the operational attributes"
+usersB structuralObjectClass entryUUID createTimestamp entryCSN modifiersName modifyTimestamp | isOK '=0$' entryUUID
+[ $? -eq 0 ] || exit $?
+
+
+###########################################################################
+
+
+echo
+echo "Authenticated users, access to domain entries"
+
+# * entry:
+# =s-a for all
+# +rd if children, canCreate{Alias,ML}, owner or postmaster
+# +z if owner or postmaster
+# * children:
+# =w for all
+# * fvd:
+# =rscd if children, canCreate{Alias,ML}, owner or postmaster
+# +w if owner or postmaster
+# * fripostIsStatusActive
+# =rscd if children, canCreate{Alias,ML}, owner or postmaster
+# +w if owner or postmaster
+# * fripostCanCreateAlias
+# =rscd if canCreateAlias, owner or postmaster
+# +w if postmaster
+# * fripostCanCreateML
+# =rscd if canCreateML, owner or postmaster
+# +w if postmaster
+# * fripostOwner
+# =s for all
+# +d if children
+# +rc if canCreate{Alias,ML}, owner or postmaster
+# * fripostPostmaster
+# =s for all
+# +d if children
+# +rc if canCreate{Alias,ML}, owner or postmaster
+# * fripostMaildrop
+# =wrscd if owner or postmaster
+# * description
+# =rscd if children, canCreate{Alias,ML}, owner or postmaster
+# +w if owner or postmaster
+
+usersD () {
+ for U in ${USERS}; do
+ for D in ${DOMAINS}; do
+ checkACL "${U}" "${D}" "$@"
+ done
+ done
+}
+
+msg "Cannot appoint domain Owners or Postmasters; Cannot add a domain"
+usersD fripostOwner/add fripostOwner/delete \
+ fripostPostmaster/add fripostPostmaster/delete \
+ entry/add \
+ | isOK 'DENIED$' entry
+[ $? -eq 0 ] || exit $?
+
+# We ensure not to give +a/+z access to the \"entry\" attribute of the
+# children, unless justified (required to add/delete a child).
+msg "Have =w access to \"children\""
+usersD children | isOK '=w$' children
+[ $? -eq 0 ] || exit $?
+
+msg "Have >=s access on \"entry\", \"fripostOwner\" and \"fripostPostmaster\""
+usersD entry/search fripostOwner/search fripostPostmaster/search | isOK 'ALLOWED$' entry
+[ $? -eq 0 ] || exit $?
+
+msg "Have =0 access to the operational attributes"
+usersD structuralObjectClass entryUUID createTimestamp entryCSN modifiersName modifyTimestamp | isOK '=0$' entryUUID
+[ $? -eq 0 ] || exit $?
+
+
+# We check the following permissions:
+# 0. Simple user
+# 1. canCreateAlias (exact,wildcard)
+# 2. canCreateML (exact,wildcard)
+# 3. Owner
+# 4. Postmaster
+
+# 0
+ATTRS0="entry/read entry/disclose
+ fvd/read fvd/search fvd/compare fvd/disclose
+ fripostIsStatusActive/read fripostIsStatusActive/search fripostIsStatusActive/compare fripostIsStatusActive/disclose
+ fripostOwner/disclose
+ fripostPostmaster/disclose
+ description/read description/search description/compare description/disclose"
+msg "Have >=rscd access to the public attributes of their parent"
+for U in ${USERS}; do
+ D="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ checkACL "${U}" "${D}" ${ATTRS0}
+done | isOK 'ALLOWED$' entry read
+[ $? -eq 0 ] || exit $?
+
+
+# 1
+ATTRSA="fripostOwner/read fripostOwner/compare
+ fripostPostmaster/read fripostPostmaster/compare
+ fripostCanCreateAlias/read fripostCanCreateAlias/search fripostCanCreateAlias/compare fripostCanCreateAlias/disclose"
+msg "Have >=rscd access to the public attributes and >=a to \"children\" (if CanCreateAlias, exact)"
+for U in ${USERS}; do
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "fripostCanCreateAlias=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${D}" children/add ${ATTRS0} ${ATTRSA}
+ done
+done | isOK 'ALLOWED$' children
+[ $? -eq 0 ] || exit $?
+
+
+# 1
+msg "Have >=rscd to the public attributes and >=a to \"children\" (if CanCreateAlias, wildcard)"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "fripostCanCreateAlias=${DU},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${D}" children/add ${ATTRS0} ${ATTRSA}
+ done
+done | isOK 'ALLOWED$' children
+[ $? -eq 0 ] || exit $?
+
+
+# 2
+ATTRSML="fripostOwner/read fripostOwner/compare
+ fripostPostmaster/read fripostPostmaster/compare
+ fripostCanCreateML/read fripostCanCreateML/search fripostCanCreateML/compare fripostCanCreateML/disclose"
+msg "Have >=rscd access to the public attributes and >=a to \"children\" (if CanCreateML, exact)"
+for U in ${USERS}; do
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "fripostCanCreateML=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${D}" children/add ${ATTRS0} ${ATTRSML}
+ done
+done | isOK 'ALLOWED$' children
+[ $? -eq 0 ] || exit $?
+
+
+# 2
+msg "Have >=rscd access to the public attributes and >=a to \"children\" (if CanCreateML, wildcard)"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "fripostCanCreateML=${DU},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${D}" children/add ${ATTRS0} ${ATTRSML}
+ done
+done | isOK 'ALLOWED$' children
+[ $? -eq 0 ] || exit $?
+
+
+# 3
+# >=w to "children", =zrscd to "entry", >=rscd to "fripostCanCreateAlias" and
+# "fripostCanCreateML", and =wrscd to the rest (other than "Owner" and
+# Postmaster")
+msg "Have =wrscd to the domain attributes (other than \"canCreate\"), and >=w to \"children\" (if Owner)"
+ATTRSO="entry/delete
+ fvd/write
+ fripostIsStatusActive/write
+ fripostMaildrop/delete fripostMaildrop/add fripostMaildrop/read fripostMaildrop/search fripostMaildrop/compare fripostMaildrop/disclose
+ description/add description/delete"
+for U in ${USERS}; do
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "fripostOwner=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${D}" children/write ${ATTRS0} ${ATTRSA} ${ATTRSML} ${ATTRSO}
+ done
+done | isOK 'ALLOWED$' children
+[ $? -eq 0 ] || exit $?
+
+
+# 4
+# >=w to "children", =zrscd to "entry", >=rscd to "fripostCanCreateAlias" and
+# "fripostCanCreateML", and =wrscd to the rest (other than "Owner" and
+# Postmaster")
+msg "Have =wrscd to the domain attributes, and >=w to \"children\" (if Postmaster)"
+ATTRSP="fripostCanCreateAlias/add fripostCanCreateAlias/delete
+ fripostCanCreateML/add fripostCanCreateML/delete"
+for U in ${USERS}; do
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "fripostPostmaster=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${D}" children/write ${ATTRS0} ${ATTRSA} ${ATTRSML} ${ATTRSO} ${ATTRSP}
+ done
+done | isOK 'ALLOWED$' children
+[ $? -eq 0 ] || exit $?
+
+
+# not (0 or 1 or 2 or 3 or 4)
+msg "Have no access to the public attributes of irrelevant domains"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for D in ${DOMAINS}; do
+ [ "x${DU}" = "x${D}" ] || \
+ search -s base -b "${D},${SUFFIX}" "(|(fripostCanCreateAlias=${U},${SUFFIX})
+ (fripostCanCreateAlias=${DU},${SUFFIX})
+ (fripostCanCreateML=${U},${SUFFIX})
+ (fripostCanCreateML=${DU},${SUFFIX})
+ (fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${D}" ${ATTRS0}
+ done
+done | isOK 'DENIED$' entry read
+[ $? -eq 0 ] || exit $?
+
+
+# not (1 or 2 or 3 or 4)
+msg "Do not have >=rc access to \"canCreate{Alias,ML}\", \"Owner\", \"Postmaster\" (unless member)"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "(|(fripostCanCreateAlias=${U},${SUFFIX})
+ (fripostCanCreateAlias=${DU},${SUFFIX})
+ (fripostCanCreateML=${U},${SUFFIX})
+ (fripostCanCreateML=${DU},${SUFFIX})
+ (fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${D}" ${ATTRSA} ${ATTRSML} entry/add
+ done
+done | isOK 'DENIED$' entry # "entry" here is useless, but it's just to get the count
+[ $? -eq 0 ] || exit $?
+
+
+# not (1 or 3 or 4)
+msg "Have =0 access to \"canCreateAlias\" (unless member, Owner, or Postmaster)"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "(|(fripostCanCreateAlias=${U},${SUFFIX})
+ (fripostCanCreateAlias=${DU},${SUFFIX})
+ (fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${D}" fripostCanCreateAlias entry/add
+ done
+done | isOK '\(=0\|DENIED\)$' entry # "entry" here is useless, but it's just to get the count
+[ $? -eq 0 ] || exit $?
+
+
+# not (2 or 3 or 4)
+msg "Have =0 access to \"canCreateML\" (unless member, Owner, or Postmaster)"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "(|(fripostCanCreateML=${U},${SUFFIX})
+ (fripostCanCreateML=${DU},${SUFFIX})
+ (fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${D}" fripostCanCreateML entry/add
+ done
+done | isOK '\(=0\|DENIED\)$' entry # "entry" here is useless, but it's just to get the count
+[ $? -eq 0 ] || exit $?
+
+
+# not (3 or 4)
+msg "Have =0 access to \"fripostMaildrop\" (unless Owner or Postmaster)"
+for U in ${USERS}; do
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "(|(fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${D}" ${ATTRSO}
+ done
+done | isOK 'DENIED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+# not 4
+msg "Do not have >=w access to \"canCreate{Alias,ML}\" (unless Postmaster)"
+for U in ${USERS}; do
+ for D in ${DOMAINS}; do
+ search -s base -b "${D},${SUFFIX}" "fripostPostmaster=${U},${SUFFIX}" | grep -q '^dn: ' || \
+ checkACL "${U}" "${D}" ${ATTRSP} entry/add
+ done
+done | isOK 'DENIED$' entry # "entry" here is useless, but it's just to get the count
+[ $? -eq 0 ] || exit $?
+
+
+
+###########################################################################
+
+
+echo
+echo "Authenticated users, access to user entries"
+
+# * entry:
+# =rsd if account owner
+# +a if domain postmaster
+# * children:
+# =0 for all
+# * fvu:
+# =wrscd if account owner or domain postmaster
+# * userPassword:
+# =w if account owner or domain postmaster
+# * fripostIsStatusActive:
+# =wrscd if account owner or domain postmaster
+# * fripostMailboxQuota:
+# =rscd if account owner or domain postmaster
+# * fripostMaildrop:
+# =wrscd if account owner or domain postmaster
+# * cn:
+# =wrscd if account owner or domain postmaster
+# * description:
+# =wrscd if account owner or domain postmaster
+
+usersU () {
+ for U in ${USERS}; do
+ checkACL "${U}" "${U}" "$@"
+ done
+}
+
+# They would need write access to their fripostMailboxQuota.
+# In practice they can't write fvu either, since it's single valued.
+msg "Have =rscxd access to their \"fripostMailboxQuota\""
+usersU fripostMailboxQuota | isOK 'read(=rscxd)$'
+[ $? -eq 0 ] || exit $?
+
+msg "Have =wd access to their own \"userPassword\""
+usersU userPassword | isOK '=w$'
+[ $? -eq 0 ] || exit $?
+
+msg "Have =wrscxd access to the other attributes of their own entry"
+usersU fvu fripostIsStatusActive fripostMaildrop cn description | isOK 'write(=wrscxd)$' fvu
+[ $? -eq 0 ] || exit $?
+
+msg "Have >=rsd access to the \"entry\" attribute of their own entry"
+usersU entry/read entry/search entry/disclose fvu/read \
+ | isOK 'ALLOWED$' fvu # fvu is useless here, but it's just to get the count
+[ $? -eq 0 ] || exit $?
+
+msg "Have =0 access to their \"children\" and operational attributes"
+usersU children structuralObjectClass entryUUID createTimestamp entryCSN modifiersName modifyTimestamp | isOK '=0$' children
+[ $? -eq 0 ] || exit $?
+
+msg "Have =0 access to other user entries (unless Postmaster)"
+for U1 in ${USERS}; do
+ for U2 in ${USERS}; do
+ D2="$(echo "${U2}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ [ "x${U1}" = "x${U2}" ] || \
+ search -s base -b "${D2},${SUFFIX}" "(fripostPostmaster=${U1},${SUFFIX})" | grep -q '^dn: ' || \
+ checkACL "${U1}" "${U2}" entry children \
+ fvu userPassword \
+ fripostIsStatusActive \
+ fripostMailboxQuota \
+ fripostMaildrop \
+ cn description
+ done
+done | isOK '=0$' entry
+[ $? -eq 0 ] || exit $?
+
+
+usersP () {
+ for P in ${USERS}; do
+ for U in ${USERS}; do
+ D="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ [ "x${U}" != "x${P}" ] && \
+ search -s base -b "${D},${SUFFIX}" "(fripostPostmaster=${P},${SUFFIX})" | grep -q '^dn: ' && \
+ checkACL "${P}" "${U}" "$@"
+ done
+ done
+}
+
+msg "Have =rscxd access to their user's \"fripostMailboxQuota\" (if Postmaster)"
+usersP fripostMailboxQuota | isOK 'read(=rscxd)$'
+[ $? -eq 0 ] || exit $?
+
+msg "Have =wd access to their user's \"userPassword\" (if Postmaster)"
+usersP userPassword | isOK '=w$'
+[ $? -eq 0 ] || exit $?
+
+msg "Have =wrscxd access to the other attributes of their users' entry (if Postmaster)"
+usersP fvu fripostIsStatusActive fripostMaildrop cn description | isOK 'write(=wrscxd)$' fvu
+[ $? -eq 0 ] || exit $?
+
+# "+a" is needed to create new accounts. "+z" would be required to
+# delete such accounts.
+msg "Have =arsd access to the \"entry\" attribute of their users' entry (if Postmaster)"
+usersP entry | isOK '=arsd$' entry
+[ $? -eq 0 ] || exit $?
+
+msg "Have =0 access to their users' \"children\" and operational attributes (if Postmaster)"
+usersP children structuralObjectClass entryUUID createTimestamp entryCSN modifiersName modifyTimestamp | isOK '=0$' children
+[ $? -eq 0 ] || exit $?
+
+
+###########################################################################
+
+
+echo
+echo "Authenticated users, access to alias entries"
+
+# * entry:
+# =s for all
+# +a if canCreateAlias
+# +rd if alias owner, domain owner or domain postmaster
+# +z (regular alias) if alias owner
+# +w (regular alias) if domain owner or domain postmaster
+# * children:
+# =0 for all
+# * fva:
+# =rscd (reserved alias) if domain owner or domain postmaster
+# =wrscd (regular alias) if alias owner, domain owner or domain postmaster
+# * fripostMaildrop:
+# =wrscd if alias owner, domain owner or domain postmaster
+# * fripostIsStatusActive:
+# =rscd (reserved alias) if domain owner or domain postmaster
+# =wrscd (regular alias) if alias owner, domain owner or domain postmaster
+# * fripostOwner:
+# =d for all
+# +rsc (reserved alias) if domain owner or domain postmaster
+# +rsc (regular alias) if alias owner, domain owner or domain postmaster
+# +w (regular alias) if domain owner or domain postmaster
+# * description:
+# =wrscd if alias owner, domain owner or domain postmaster
+
+usersA () {
+ for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ checkACL "${U}" "${A}" "$@"
+ done
+ done
+}
+
+
+msg "Have >=s access to \"entry\" and \"fripostOwner\""
+usersA fripostOwner/search entry/search | isOK 'ALLOWED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have =0 access to the \"children\" and operational attributes"
+usersA children structuralObjectClass entryUUID createTimestamp entryCSN modifiersName modifyTimestamp | isOK '=0$' children
+[ $? -eq 0 ] || exit $?
+
+RESERVED_ATTRS="entry/delete
+ fva/write
+ fripostIsStatusActive/write"
+RESERVED_ATTRS2="fripostOwner/add fripostOwner/delete"
+
+ATTRS="entry/read entry/disclose
+ fva/read fva/search fva/compare fva/disclose
+ fripostMaildrop/add fripostMaildrop/delete fripostMaildrop/read fripostMaildrop/search fripostMaildrop/compare fripostMaildrop/disclose
+ fripostIsStatusActive/read fripostIsStatusActive/search fripostIsStatusActive/compare fripostIsStatusActive/disclose
+ fripostOwner/read fripostOwner/compare fripostOwner/disclose
+ description/add description/delete description/read description/search description/compare description/disclose"
+
+msg "Cannot delete/deactivate/change ownership of reserved aliases"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ LA="$(echo "${A}" | sed -re 's/^fva=(.*),fvd=[^,]+$/\1/')"
+ [ "x${LA}" = "xabuse" -o "x${LA}" = "xpostmaster" ] && \
+ checkACL "${U}" "${A}" ${RESERVED_ATTRS}
+ done
+done | isOK 'DENIED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Can delete/deactivate/change ownership of regular aliases (if alias Owner)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ LA="$(echo "${A}" | sed -re 's/^fva=(.*),fvd=[^,]+$/\1/')"
+ [ "x${LA}" != "xabuse" -a "x${LA}" != "xpostmaster" ] && \
+ search -s base -b "${A},${SUFFIX}" "fripostOwner=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${A}" ${RESERVED_ATTRS}
+ done
+done | isOK 'ALLOWED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Can delete/deactivate/change ownership of regular aliases (if domain Owner)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ LA="$(echo "${A}" | sed -re 's/^fva=(.*),fvd=[^,]+$/\1/')"
+ [ "x${LA}" != "xabuse" -a "x${LA}" != "xpostmaster" ] && \
+ search -s base -b "${DA},${SUFFIX}" "fripostOwner=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${A}" ${RESERVED_ATTRS} ${RESERVED_ATTRS2}
+ done
+done | isOK 'ALLOWED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Can delete/deactivate/change ownership of regular aliases (if domain Postmaster)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ LA="$(echo "${A}" | sed -re 's/^fva=(.*),fvd=[^,]+$/\1/')"
+ [ "x${LA}" != "xabuse" -a "x${LA}" != "xpostmaster" ] && \
+ search -s base -b "${DA},${SUFFIX}" "fripostPostmaster=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${A}" ${RESERVED_ATTRS} ${RESERVED_ATTRS2}
+ done
+done | isOK 'ALLOWED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Can change destination (if alias Owner)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ search -s base -b "${A},${SUFFIX}" "fripostOwner=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${A}" ${ATTRS}
+ done
+done | isOK 'ALLOWED$' entry read
+[ $? -eq 0 ] || exit $?
+
+
+msg "Can change destination and create new aliases (if domain Owner)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DA},${SUFFIX}" "fripostOwner=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${A}" entry/add ${ATTRS}
+ done
+done | isOK 'ALLOWED$' entry add
+[ $? -eq 0 ] || exit $?
+
+
+msg "Can change destination and create new aliases (if domain Postmaster)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DA},${SUFFIX}" "fripostPostmaster=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${A}" entry/add ${ATTRS}
+ done
+done | isOK 'ALLOWED$' entry add
+[ $? -eq 0 ] || exit $?
+
+
+# Needed to create new entries. ("+z" is required to delete, btw.)
+msg "Have >=a access to \"entry\" (if CanCreateAlias, exact)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DA},${SUFFIX}" "fripostCanCreateAlias=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${A}" entry/add
+ done
+done | isOK 'ALLOWED$' entry add
+[ $? -eq 0 ] || exit $?
+
+
+# Needed to create new entries. ("+z" is required to delete, btw.)
+msg "Have >=a access to \"entry\" (if CanCreateAlias, wildcard)"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DA},${SUFFIX}" "fripostCanCreateAlias=${DU},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${A}" entry/add
+ done
+done | isOK 'ALLOWED$' entry add
+[ $? -eq 0 ] || exit $?
+
+
+msg "Do not have >=a access to \"entry\" (unless canCreateAlias)"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DA},${SUFFIX}" "(|(fripostCanCreateAlias=${U},${SUFFIX})
+ (fripostCanCreateAlias=${DU},${SUFFIX})
+ (fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${A}" entry/add
+ done
+done | isOK 'DENIED$' entry add
+[ $? -eq 0 ] || exit $?
+
+
+msg "Cannot manage ownership (unless domain owner/domain postmaster)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DA},${SUFFIX}" "(|(fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${A}" ${RESERVED_ATTRS2}
+ done
+done | isOK 'DENIED$' fripostOwner add
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have no access to aliases entries (unless alias owner/domain owner/domain postmaster)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${A},${SUFFIX}" "fripostOwner=${U},${SUFFIX}" | grep -q '^dn: ' || \
+ search -s base -b "${DA},${SUFFIX}" "(|(fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${A}" ${RESERVED_ATTRS} ${ATTRS}
+ done
+done | isOK 'DENIED$' entry delete
+[ $? -eq 0 ] || exit $?
+
+
+
+###########################################################################
+
+
+echo
+echo "Authenticated users, access to mailing list entries"
+
+# * entry:
+# =s for all
+# +a if canCreateML, domain owner or domain postmaster
+# +zrd if mailing list owner, domain owner or domain postmaster
+# * children:
+# =0 for all
+# * fvml:
+# =wrscd if mailing list owner, domain owner or domain postmaster
+# * fripostMLManager:
+# =rscd if mailing list owner, domain owner or domain postmaster
+# * fripostIsStatusActive:
+# =wrscd if mailing list owner, domain owner or domain postmaster
+# * fripostMLCommand:
+# =rscd if mailing list owner, domain owner or domain postmaster
+# * fripostOwner:
+# =d for all
+# +rsc if mailing list owner, domain owner or domain postmaster
+# +w if domain owner or domain postmaster
+# * description:
+# =wrscd if mailing list owner, domain owner or domain postmaster
+
+usersML () {
+ for U in ${USERS}; do
+ for ML in ${MLS}; do
+ checkACL "${U}" "${ML}" "$@"
+ done
+ done
+}
+
+
+msg "Have >=s access on \"entry\" and \"fripostOwner\""
+usersML fripostOwner/search entry/search | isOK 'ALLOWED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have =0 access the \"children\" and operational attributes"
+usersML children structuralObjectClass entryUUID createTimestamp entryCSN modifiersName modifyTimestamp | isOK '=0$' children
+[ $? -eq 0 ] || exit $?
+
+
+msg "Cannot change transport-related attributes"
+for U in ${USERS}; do
+ for ML in ${MLS}; do
+ checkACL "${U}" "${ML}" fripostMLCommand/add fripostMLCommand/delete \
+ fripostMLManager/write
+ done
+done | isOK 'DENIED$' fripostMLManager
+[ $? -eq 0 ] || exit $?
+
+
+ATTRS="entry/read entry/disclose entry/delete
+ fvml/write fvml/read fvml/search fvml/compare fvml/disclose
+ fripostMLManager/read fripostMLManager/search fripostMLManager/compare fripostMLManager/disclose
+ fripostIsStatusActive/write fripostIsStatusActive/read fripostIsStatusActive/search fripostIsStatusActive/compare fripostIsStatusActive/disclose
+ fripostMLCommand/read fripostMLCommand/search fripostMLCommand/compare fripostMLCommand/disclose
+ fripostOwner/read fripostOwner/compare fripostOwner/disclose
+ description/add description/delete description/read description/compare description/disclose"
+ATTRS2="fripostOwner/add fripostOwner/delete"
+
+msg "Can edit/delete mailing list (if mailing list Owner)"
+for U in ${USERS}; do
+ for ML in ${MLS}; do
+ search -s base -b "${ML},${SUFFIX}" "fripostOwner=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${ML}" ${ATTRS}
+ done
+done | isOK 'ALLOWED$' entry delete
+[ $? -eq 0 ] || exit $?
+
+
+msg "Can edit/create/delete mailing list (if domain Owner)"
+[ $? -eq 0 ] || exit $?
+for U in ${USERS}; do
+ for ML in ${MLS}; do
+ DML="$(echo "${ML}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DML},${SUFFIX}" "fripostOwner=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${ML}" ${ATTRS} ${ATTRS2} entry/add
+ done
+done | isOK 'ALLOWED$' entry add
+[ $? -eq 0 ] || exit $?
+
+
+msg "Can edit/create/delete mailing list (if domain Postmaster)"
+[ $? -eq 0 ] || exit $?
+for U in ${USERS}; do
+ for ML in ${MLS}; do
+ DML="$(echo "${ML}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DML},${SUFFIX}" "fripostPostmaster=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${ML}" ${ATTRS} ${ATTRS2} entry/add
+ done
+done | isOK 'ALLOWED$' entry add
+[ $? -eq 0 ] || exit $?
+
+
+# Needed to create new entries. ("+z" is required to delete, btw.)
+msg "Have >=a access to \"entry\" (if CanCreateML, exact)"
+for U in ${USERS}; do
+ for ML in ${MLS}; do
+ DML="$(echo "${ML}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DML},${SUFFIX}" "fripostCanCreateML=${U},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${ML}" entry/add
+ done
+done | isOK 'ALLOWED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+# Needed to create new entries. ("+z" is required to delete, btw.)
+msg "Have >=a access to \"entry\" (if CanCreateML, wildcard)"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for ML in ${MLS}; do
+ DML="$(echo "${ML}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DML},${SUFFIX}" "fripostCanCreateML=${DU},${SUFFIX}" | grep -q '^dn: ' && \
+ checkACL "${U}" "${ML}" entry/add
+ done
+done | isOK 'ALLOWED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Do not have >=a access to \"entry\" (unless canCreateML)"
+for U in ${USERS}; do
+ DU="$(echo "${U}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ for ML in ${MLS}; do
+ DML="$(echo "${ML}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DML},${SUFFIX}" "(|(fripostCanCreateML=${U},${SUFFIX})
+ (fripostCanCreateML=${DU},${SUFFIX})
+ (fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${ML}" entry/add
+ done
+done | isOK 'DENIED$' entry
+[ $? -eq 0 ] || exit $?
+
+
+msg "Cannot manage ownership (unless domain owner/domain postmaster)"
+for U in ${USERS}; do
+ for A in ${ALIASES}; do
+ DA="$(echo "${A}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${DA},${SUFFIX}" "(|(fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${A}" ${ATTRS2}
+ done
+done | isOK 'DENIED$' fripostOwner add
+[ $? -eq 0 ] || exit $?
+
+
+msg "Have no access to mailing list entries (unless mailing list owner/domain owner/domain postmaster)"
+for U in ${USERS}; do
+ for ML in ${MLS}; do
+ DML="$(echo "${ML}" | sed -re 's/.*,(fvd=[^,]+)$/\1/')"
+ search -s base -b "${ML},${SUFFIX}" "fripostOwner=${U},${SUFFIX}" | grep -q '^dn: ' || \
+ search -s base -b "${DML},${SUFFIX}" "(|(fripostOwner=${U},${SUFFIX})
+ (fripostPostmaster=${U},${SUFFIX}))" | grep -q '^dn: ' || \
+ checkACL "${U}" "${ML}" ${ATTRS} entry/delete
+ done
+done | isOK 'DENIED$' entry delete
+[ $? -eq 0 ] || exit $?
+
+
+
+###########################################################################
+
+
+rm "${RES}"