SOGo, LDAP group and Dovecot shared IMAP folders

I've wasted a fair amount of time recently on shared IMAP folders.

IMAP shared folder ACLs and SOGo

Work has a company SOGo instance. This is backed by a LDAP directory running on OpenLDAP and a Dovecot IMAP server.

The SOGo web mail interfaces allows you to set sharing permissions on IMAP folders. It reads and sets these using the IMAP ACL extensions, activated in Dovecot by loading the necessary plugins:

protocol imap {
  mail_plugins = acl imap_acl
}

This works with individual users, and with anyone, but not with LDAP groups. I've submitted patches for SOGo which get it to do the right thing for groups, and which should appear in 1.3.8. In conjunction with Dovecot 2.0 (1.2 has problems with group permissions over IMAP), it sets up the right ACLs for groups.

The remaining problem is how to tell Dovecot about which LDAP groups the user belongs to. But first, a brief diversion on LDAP groups.

Groups in LDAP

Under the normal schemas, LDAP knows about two types of group. There is groupOfNames in core.schema, which is what you might call LDAP's idea of a group. Then in nis.schema there is a POSIX-type group, a posixGroup. These are both structural objects. Any LDAP object must possess one and only one structural object. The upshot here is that you can't have a groupOfNames which is also a posixGroup.

We don't want every group to be a POSIX group. They need to have a gid and I've had trouble with group names that don't consist of a single all-lowercase word. We have groups called All Staff and similar. LDAP directory maintenance tools usually have nice ways of dealing with groupOfNames.

So we're using the RFC2307bis schema instead. This is exactly the same as nis.schema but has posixGroup as an auxiliary class. So you can add posixGroup as an extra object type to a groupOfNames and everyone is happy.

Groups membership in OpenLDAP

The next stage of the puzzle is when a user logs in to the Dovecot IMAP server. You want to determine which groups that user is a member of. A groupOfNames being just that, a list of users, the only way to work out what groups a user is in is to scan every single group looking for that username.

OpenLDAP has an overlay memberOf which we have configured and activated. This adds and removes as appropriate memberOf attributes to user records. Each memberOf attribute gives the LDAP identity of a group the user belongs to.

Putting it together

Dovecot currently has no way of dealing with multi-value LDAP attributes. But it does have a post-login script to allow you to set up an environment variable ACL_GROUPS with a comma-separated list of groups that user belongs to.

So in Dovecot configuration I set up a post-login script:

service imap {
  executable = imap imap-postlogin
}

service imap-postlogin {
  # all post-login scripts are executed via script-login binary
  executable = script-login -d /etc/dovecot/acl_groups.py

  # the script process runs as the user specified here (v2.0.14+):
  user = $default_internal_user
  
  # this UNIX socket listener must use the same name as given to imap executable
  unix_listener imap-postlogin {
  }
}

We currently have Maildirs in the users home directory. script-login -d runs the after login imap process as the user. The script acl_groups.py fishes out the group memberships from LDAP, sets up ACL_GROUPS and chains to the rest of the IMAP session. Dovecot passes the location of the program to run for the rest of the session on the command line.

#!/usr/bin/env python
#
# A Dovecot post-login script for IMAP. This creates environment
# ACL_GROUPS with a comma-separated list of the user's LDAP group
# memberships and then execs the Dovecot IMAP handler.
#

import ldap, os, sys;

ldapUrl = "ldap://localhost"
bindAccount = "cn=access,dc=example,dc=com"
bindPw = "xxxxxx"

searchBase = "ou=People,dc=example,dc=com"
searchFilter = "(&(objectClass=posixAccount)(uid={0}))"

user = os.environ["USER"]
groups = []

l = ldap.initialize(ldapUrl)
l.bind(bindAccount, bindPw, ldap.AUTH_SIMPLE)
res = l.search_s(searchBase, ldap.SCOPE_SUBTREE,
                 searchFilter.format(os.environ["USER"]),
                 ['memberOf'])
for dn, entry in res:
    try:
        for g in entry['memberOf']:
            # Returns 'cn=All UK staff,ou=Groups,dc=example,dc=com' etc.
            # Fish out 'All UK staff' as group name.
            groups.append(g.split(',', 1)[0][3:])
    except KeyError:
        pass    # User in no groups.
        
os.environ["ACL_GROUPS"] = ",".join(groups)
try:
    os.environ["USERDB_KEYS"] += " acl_groups"
except KeyError:
    os.environ["USERDB_KEYS"] = "acl_groups"

os.execv(sys.argv[1], sys.argv[1:])
sys.exit(1) # In case above fails

File system permissions

The final stage is to make sure that the file system permissions allow the user to read from the shared mailbox. This is difficult with Maildir directories under the user's home directory. Right now I've set the Maildir directory group to mail and given the group r-s permission on the directory. But this has to be done by hand for each home directory created. I'm looking at moving all mail to under /var/mail instead.

 
sogodovecotldapandgroups.txt · Last modified: 2016/02/05 12:44 by jim
chimeric.de = chi`s home Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0