Skip to content
Permalink
Browse files

mboxlist: implement reverse ACLs

To enable, set 'reverseacls: yes' in imapd.conf and restart the
server.  Since it requires a complete table sweep in a transaction,
this will only be done by ctl_cyrusdb -r at startup.  You can also
remove the reverse ACL support by removing the config key - at next
startup it will remove all keys starting with $RACL.

To enable reverse ACLs, it creates a key called '$RACL'  If this key
is present, then the reverse ACLs are used for looking up records in
NAMESPACE_USER and NAMESPACE_SHARED.

NOTE: reverse ACLs are not used by admin connections at all, since
admins tend to have read access to everything, and it would be
pointless and create bloat.  Likewise, reverse ACLs aren't use for
the user's own folders (INBOX and friends), because they are always
accessible to their owner.

When the $RACL key is present, all updates from mboxlist_update or
mboxlist_delete will cause the ACL to be examined, and for each
user who:

a) is not the mailbox owner
b) is not an admin (NOTE: making an existing user not an admin will break things)
c) has the 'l' right

A record will be created or removed.  If the folder is in NAMESPACE_USER
then the record will have key:

$RACL$U$<acluserid>$<intname>

If it's NAMESPACE_SHARED, then it will be:

$RACL$S$<acluserid>$<intname>

The record has a zero-length value.  This works in all decent DB types, because
it's also the method used by the subscriptions database.

When a LIST (mboxlist_findall) command is run, then if it's a non-admin
request, and $RACL is present in the mailboxes.db, the reverse ACLs will
be read into a strarray_t, and each record will be called with cyrusdb_forone.
  • Loading branch information
brong committed Nov 16, 2015
1 parent d2f2d49 commit 1e0c2778f38a07668909e2eb3d3656be710ebc69
Showing with 187 additions and 4 deletions.
  1. +6 −3 imap/ctl_cyrusdb.c
  2. +175 −1 imap/mboxlist.c
  3. +2 −0 imap/mboxlist.h
  4. +4 −0 lib/imapoptions
@@ -118,7 +118,7 @@ static void usage(void)
exit(-1);
}

/* Callback for use by recover_reserved */
/* Callback for use by process_mboxlist */
static int fixmbox(const mbentry_t *mbentry,
void *rock __attribute__((unused)))
{
@@ -159,7 +159,7 @@ static int fixmbox(const mbentry_t *mbentry,
return 0;
}

static void recover_reserved(void)
static void process_mboxlist(void)
{
mboxlist_init(0);
mboxlist_open(NULL);
@@ -176,6 +176,9 @@ static void recover_reserved(void)
/* build a list of mailboxes - we're using internal names here */
mboxlist_allmbox(NULL, fixmbox, NULL, 0);

/* enable or disable RACLs per config */
mboxlist_set_racls(config_getswitch(IMAPOPT_REVERSEACLS));

quotadb_close();
quotadb_done();

@@ -377,7 +380,7 @@ int main(int argc, char *argv[])
strarray_fini(&files);

if(op == RECOVER && reserve_flag)
recover_reserved();
process_mboxlist();

free(dirname);
free(backup1);
@@ -605,10 +605,95 @@ HIDDEN int mboxlist_findstage(const char *name, char *stagedir, size_t sd_len)
return 0;
}

static void mboxlist_racl_key(int isuser, const char *keyuser, const char *mbname, struct buf *buf)
{
buf_setcstr(buf, "$RACL$");
buf_putc(buf, isuser ? 'U' : 'S');
buf_putc(buf, '$');
if (keyuser) {
buf_appendcstr(buf, keyuser);
buf_putc(buf, '$');
}
if (mbname) {
buf_appendcstr(buf, mbname);
}
}

static int user_is_in(const strarray_t *aclbits, const char *user)
{
int i;
if (!aclbits) return 0;
for (i = 0; i+1 < strarray_size(aclbits); i+=2) {
if (!strcmp(strarray_nth(aclbits, i), user)) return 1;
}
return 0;
}

static int mboxlist_update_racl(const char *name, const mbentry_t *oldmbentry, const mbentry_t *newmbentry, struct txn **txn)
{
static strarray_t *admins = NULL;
struct buf buf = BUF_INITIALIZER;
char *userid = mboxname_to_userid(name);
strarray_t *oldusers = NULL;
strarray_t *newusers = NULL;
int i;
int r = 0;

if (!admins) admins = strarray_split(config_getstring(IMAPOPT_ADMINS), NULL, 0);

if (oldmbentry && oldmbentry->mbtype != MBTYPE_DELETED)
oldusers = strarray_split(oldmbentry->acl, "\t", 0);

if (newmbentry && newmbentry->mbtype != MBTYPE_DELETED)
newusers = strarray_split(newmbentry->acl, "\t", 0);

if (oldusers) {
for (i = 0; i+1 < strarray_size(oldusers); i+=2) {
const char *acluser = strarray_nth(oldusers, i);
const char *aclval = strarray_nth(oldusers, i+1);
if (!strchr(aclval, 'l')) continue; /* non-lookup ACLs can be skipped */
if (!strcmpsafe(userid, acluser)) continue;
if (strarray_find(admins, acluser, 0) >= 0) continue;
if (user_is_in(newusers, acluser)) continue;
mboxlist_racl_key(!!userid, acluser, name, &buf);
r = cyrusdb_delete(mbdb, buf.s, buf.len, txn, /*force*/1);
if (r) goto done;
}
}

if (newusers) {
for (i = 0; i+1 < strarray_size(newusers); i+=2) {
const char *acluser = strarray_nth(newusers, i);
const char *aclval = strarray_nth(newusers, i+1);
if (!strchr(aclval, 'l')) continue; /* non-lookup ACLs can be skipped */
if (!strcmpsafe(userid, acluser)) continue;
if (strarray_find(admins, acluser, 0) >= 0) continue;
if (user_is_in(oldusers, acluser)) continue;
mboxlist_racl_key(!!userid, acluser, name, &buf);
r = cyrusdb_store(mbdb, buf.s, buf.len, "", 0, txn);
if (r) goto done;
}
}

done:
strarray_free(oldusers);
strarray_free(newusers);
free(userid);
buf_free(&buf);
return r;
}

static int mboxlist_update_entry(const char *name, const mbentry_t *mbentry, struct txn **txn)
{
int r = 0;

if (!cyrusdb_fetch(mbdb, "$RACL", 5, NULL, NULL, txn)) {
mbentry_t *old = NULL;
mboxlist_mylookup(name, &old, txn, 0); // ignore errors, it will be NULL
r = mboxlist_update_racl(name, old, mbentry, txn);
mboxlist_entry_free(&old);
}

if (mbentry) {
char *mboxent = mboxlist_entry_cstring(mbentry);
r = cyrusdb_store(mbdb, name, strlen(name), mboxent, strlen(mboxent), txn);
@@ -2133,6 +2218,9 @@ static int find_p(void *rockp,
char intname[MAX_MAILBOX_PATH+1];
int i;

/* skip any $RACL or future $ space keys */
if (key[0] == '$') return 0;

memcpy(intname, key, keylen);
intname[keylen] = 0;

@@ -2371,6 +2459,53 @@ EXPORTED int mboxlist_mboxtree(const char *mboxname, mboxlist_cb *proc, void *ro
return r;
}

static int racls_del_cb(void *rock,
const char *key, size_t keylen,
const char *data __attribute__((unused)),
size_t datalen __attribute__((unused)))
{
struct txn **txn = (struct txn **)rock;
return cyrusdb_delete(mbdb, key, keylen, txn, /*force*/0);
}

static int racls_add_cb(const mbentry_t *mbentry, void *rock)
{
struct txn **txn = (struct txn **)rock;
return mboxlist_update_racl(mbentry->name, NULL, mbentry, txn);
}

EXPORTED int mboxlist_set_racls(int enabled)
{
struct txn *tid = NULL;
int r = 0;
int now = !cyrusdb_fetch(mbdb, "$RACL", 5, NULL, NULL, &tid);

if (now && !enabled) {
syslog(LOG_NOTICE, "removing reverse acl support");
/* remove */
r = cyrusdb_foreach(mbdb, "$RACL", 5, NULL, racls_del_cb, &tid, &tid);
}
else if (enabled && !now) {
/* add */
struct allmb_rock mbrock = { NULL, 0, racls_add_cb, &tid };
/* we can't use mboxlist_allmbox because it doesn't do transactions */
syslog(LOG_NOTICE, "adding reverse acl support");
r = cyrusdb_foreach(mbdb, "", 0, allmbox_p, allmbox_cb, &mbrock, &tid);
if (r) {
syslog(LOG_ERR, "ERROR: failed to add reverse acl support %s", error_message(r));
}
mboxlist_entry_free(&mbrock.mbentry);
if (!r) r = cyrusdb_store(mbdb, "$RACL", 5, "", 0, &tid);
}

if (r)
cyrusdb_abort(mbdb, tid);
else
cyrusdb_commit(mbdb, tid);

return r;
}


struct alluser_rock {
char *prev;
@@ -2417,9 +2552,48 @@ EXPORTED int mboxlist_usermboxtree(const char *userid, mboxlist_cb *proc,
return r;
}

struct raclrock {
int prefixlen;
strarray_t *list;
};

static int racl_cb(void *rock,
const char *key, size_t keylen,
const char *data __attribute__((unused)),
size_t datalen __attribute__((unused)))
{
struct raclrock *raclrock = (struct raclrock *)rock;
strarray_appendm(raclrock->list, xstrndup(key + raclrock->prefixlen, keylen - raclrock->prefixlen));
return 0;
}

static int mboxlist_find_namespace(struct find_rock *rock, const char *prefix, size_t len)
{
int r = cyrusdb_foreach(rock->db, prefix, len, &find_p, &find_cb, rock, NULL);
int r = 0;
if (!rock->issubs && !rock->isadmin && !cyrusdb_fetch(rock->db, "$RACL", 5, NULL, NULL, NULL)) {
/* we're using reverse ACLs */
struct buf buf = BUF_INITIALIZER;
strarray_t matches = STRARRAY_INITIALIZER;
mboxlist_racl_key(rock->find_namespace == NAMESPACE_USER, rock->userid, NULL, &buf);
/* this is the prefix */
struct raclrock raclrock = { buf.len, &matches };
/* we only need to look inside the prefix still, but we keep the length
* in raclrock pointing to the start of the mboxname part of the key so
* we get correct names in matches */
if (len) buf_appendmap(&buf, prefix, len);
r = cyrusdb_foreach(rock->db, buf.s, buf.len, NULL, racl_cb, &raclrock, NULL);
/* XXX - later we need to sort the array when we've added groups */
int i;
for (i = 0; !r && i < strarray_size(&matches); i++) {
const char *key = strarray_nth(&matches, i);
r = cyrusdb_forone(rock->db, key, strlen(key), &find_p, &find_cb, rock, NULL);
}
strarray_fini(&matches);
}
else {
r = cyrusdb_foreach(rock->db, prefix, len, &find_p, &find_cb, rock, NULL);
}

if (r == CYRUSDB_DONE) r = 0;
return r;
}
@@ -204,6 +204,8 @@ int mboxlist_setacl(const struct namespace *namespace, const char *name,
/* Change all ACLs on mailbox */
int mboxlist_sync_setacls(const char *name, const char *acl);

int mboxlist_set_racls(int enabled);

modseq_t mboxlist_foldermodseq_dirty(struct mailbox *mailbox);

typedef int findall_cb(const char *name, int matchlen, int maycreate, void *rock);
@@ -1550,6 +1550,10 @@ If all partitions are over that limit, this feature is not used anymore.
/* If enabled, lmtpd rejects messages with 8-bit characters in the
headers. */

{ "reverseacls", 0, SWITCH }
/* At startup time, ctl_cyrusdb -r will check this value and it
will either add or remove reverse ACL pointers from mailboxes.db */

{ "rfc2046_strict", 0, SWITCH }
/* If enabled, imapd will be strict (per RFC 2046) when matching MIME
boundary strings. This means that boundaries containing other

0 comments on commit 1e0c277

Please sign in to comment.
You can’t perform that action at this time.