Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
153 KB
Referenced Files
None
Subscribers
None
diff --git a/imap/mboxlist.c b/imap/mboxlist.c
index 7121a8f2f..06f51338a 100644
--- a/imap/mboxlist.c
+++ b/imap/mboxlist.c
@@ -1,4852 +1,4862 @@
/* mboxlist.c -- Mailbox list manipulation routines
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <sysexits.h>
#include <syslog.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "acl.h"
#include "annotate.h"
#include "bsearch.h"
#include "glob.h"
#include "assert.h"
#include "global.h"
#include "cyrusdb.h"
#include "util.h"
#include "mailbox.h"
#include "mboxevent.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "partlist.h"
#include "xstrlcat.h"
#include "user.h"
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
#include "mboxname.h"
#include "mupdate-client.h"
#include "mboxlist.h"
#include "quota.h"
#include "sync_log.h"
#define DB config_mboxlist_db
#define SUBDB config_subscription_db
cyrus_acl_canonproc_t mboxlist_ensureOwnerRights;
static struct db *mbdb;
static int mboxlist_dbopen = 0;
static int mboxlist_initialized = 0;
#define RACL_VERSION 2
static int have_racl = 0;
#define RUNQ_VERSION 1
static int have_runq = 0;
static int mboxlist_opensubs(const char *userid, struct db **ret);
static void mboxlist_closesubs(struct db *sub);
static int mboxlist_rmquota(const mbentry_t *mbentry, void *rock);
static int mboxlist_changequota(const mbentry_t *mbentry, void *rock);
static void init_internal();
EXPORTED mbentry_t *mboxlist_entry_create(void)
{
mbentry_t *ret = xzmalloc(sizeof(mbentry_t));
/* xxx - initialiser functions here? */
return ret;
}
EXPORTED mbentry_t *mboxlist_entry_copy(const mbentry_t *src)
{
mbentry_t *copy = mboxlist_entry_create();
copy->name = xstrdupnull(src->name);
copy->ext_name = xstrdupnull(src->ext_name);
copy->mtime = src->mtime;
copy->uidvalidity = src->uidvalidity;
copy->mbtype = src->mbtype;
copy->createdmodseq = src->createdmodseq;
copy->foldermodseq = src->foldermodseq;
copy->partition = xstrdupnull(src->partition);
copy->server = xstrdupnull(src->server);
copy->acl = xstrdupnull(src->acl);
copy->uniqueid = xstrdupnull(src->uniqueid);
copy->legacy_specialuse = xstrdupnull(src->legacy_specialuse);
return copy;
}
EXPORTED void mboxlist_entry_free(mbentry_t **mbentryptr)
{
mbentry_t *mbentry = *mbentryptr;
/* idempotent */
if (!mbentry) return;
free(mbentry->name);
free(mbentry->ext_name);
free(mbentry->partition);
free(mbentry->server);
free(mbentry->acl);
free(mbentry->uniqueid);
free(mbentry->legacy_specialuse);
free(mbentry);
*mbentryptr = NULL;
}
static void _write_acl(struct dlist *dl, const char *aclstr)
{
const char *p, *q;
struct dlist *al = dlist_newkvlist(dl, "A");
p = aclstr;
while (p && *p) {
char *name,*val;
q = strchr(p, '\t');
if (!q) break;
name = xstrndup(p, q-p);
q++;
p = strchr(q, '\t');
if (p) {
val = xstrndup(q, p-q);
p++;
}
else
val = xstrdup(q);
dlist_setatom(al, name, val);
free(name);
free(val);
}
}
EXPORTED const char *mboxlist_mbtype_to_string(uint32_t mbtype)
{
static struct buf buf = BUF_INITIALIZER;
buf_reset(&buf);
if (mbtype & MBTYPE_DELETED)
buf_putc(&buf, 'd');
if (mbtype & MBTYPE_MOVING)
buf_putc(&buf, 'm');
if (mbtype & MBTYPE_NETNEWS)
buf_putc(&buf, 'n');
if (mbtype & MBTYPE_REMOTE)
buf_putc(&buf, 'r');
if (mbtype & MBTYPE_RESERVE)
buf_putc(&buf, 'z');
if (mbtype & MBTYPE_CALENDAR)
buf_putc(&buf, 'c');
if (mbtype & MBTYPE_COLLECTION)
buf_putc(&buf, 'b');
if (mbtype & MBTYPE_ADDRESSBOOK)
buf_putc(&buf, 'a');
if (mbtype & MBTYPE_INTERMEDIATE)
buf_putc(&buf, 'i');
if (mbtype & MBTYPE_SUBMISSION)
buf_putc(&buf, 's');
if (mbtype & MBTYPE_PUSHSUBSCRIPTION)
buf_putc(&buf, 'p');
if (mbtype & MBTYPE_JMAPNOTIFICATION)
buf_putc(&buf, 'j');
/* make sure we didn't forget to set a character for every interesting bit */
if (mbtype) assert(buf_len(&buf));
return buf_cstring(&buf);
}
static char *mboxlist_entry_cstring(const mbentry_t *mbentry)
{
struct buf buf = BUF_INITIALIZER;
struct dlist *dl = dlist_newkvlist(NULL, mbentry->name);
if (mbentry->acl)
_write_acl(dl, mbentry->acl);
if (mbentry->uniqueid)
dlist_setatom(dl, "I", mbentry->uniqueid);
if (mbentry->partition)
dlist_setatom(dl, "P", mbentry->partition);
if (mbentry->server)
dlist_setatom(dl, "S", mbentry->server);
if (mbentry->mbtype)
dlist_setatom(dl, "T", mboxlist_mbtype_to_string(mbentry->mbtype));
if (mbentry->uidvalidity)
dlist_setnum32(dl, "V", mbentry->uidvalidity);
if (mbentry->createdmodseq)
dlist_setnum64(dl, "C", mbentry->createdmodseq);
if (mbentry->foldermodseq)
dlist_setnum64(dl, "F", mbentry->foldermodseq);
dlist_setdate(dl, "M", time(NULL));
dlist_printbuf(dl, 0, &buf);
dlist_free(&dl);
return buf_release(&buf);
}
EXPORTED char *mbentry_metapath(const struct mboxlist_entry *mbentry, int metatype, int isnew)
{
return mboxname_metapath(mbentry->partition,
mbentry->name,
mbentry->uniqueid,
metatype,
isnew);
}
EXPORTED char *mbentry_datapath(const struct mboxlist_entry *mbentry, uint32_t uid)
{
return mboxname_datapath(mbentry->partition,
mbentry->name,
mbentry->uniqueid,
uid);
}
/*
* read a single record from the mailboxes.db and return a pointer to it
*/
static int mboxlist_read(const char *name, const char **dataptr, size_t *datalenptr,
struct txn **tid, int wrlock)
{
int namelen = strlen(name);
int r;
if (!namelen)
return IMAP_MAILBOX_NONEXISTENT;
if (wrlock) {
r = cyrusdb_fetchlock(mbdb, name, namelen, dataptr, datalenptr, tid);
} else {
r = cyrusdb_fetch(mbdb, name, namelen, dataptr, datalenptr, tid);
}
switch (r) {
case CYRUSDB_OK:
/* no entry required, just checking if it exists */
return 0;
break;
case CYRUSDB_AGAIN:
return IMAP_AGAIN;
break;
case CYRUSDB_NOTFOUND:
return IMAP_MAILBOX_NONEXISTENT;
break;
default:
xsyslog(LOG_ERR, "DBERROR: error fetching mboxlist",
"mailbox=<%s> error=<%s>",
name, cyrusdb_strerror(r));
return IMAP_IOERROR;
break;
}
/* never get here */
}
EXPORTED uint32_t mboxlist_string_to_mbtype(const char *string)
{
uint32_t mbtype = 0;
if (!string) return 0; /* null just means default */
for (; *string; string++) {
switch (*string) {
case 'a':
mbtype |= MBTYPE_ADDRESSBOOK;
break;
case 'b':
mbtype |= MBTYPE_COLLECTION;
break;
case 'c':
mbtype |= MBTYPE_CALENDAR;
break;
case 'd':
mbtype |= MBTYPE_DELETED;
break;
case 'i':
mbtype |= MBTYPE_INTERMEDIATE;
break;
case 'j':
mbtype |= MBTYPE_JMAPNOTIFICATION;
break;
case 'm':
mbtype |= MBTYPE_MOVING;
break;
case 'n':
mbtype |= MBTYPE_NETNEWS;
break;
case 'r':
mbtype |= MBTYPE_REMOTE;
break;
case 'p':
mbtype |= MBTYPE_PUSHSUBSCRIPTION;
break;
case 's':
mbtype |= MBTYPE_SUBMISSION;
break;
case 'z':
mbtype |= MBTYPE_RESERVE;
break;
}
}
/* make sure we didn't forget to set a character for every interesting bit */
assert(mbtype);
return mbtype;
}
struct parseentry_rock {
struct mboxlist_entry *mbentry;
struct buf *aclbuf;
int doingacl;
};
int parseentry_cb(int type, struct dlistsax_data *d)
{
struct parseentry_rock *rock = (struct parseentry_rock *)d->rock;
switch(type) {
case DLISTSAX_KVLISTSTART:
if (!strcmp(buf_cstring(&d->kbuf), "A")) {
rock->doingacl = 1;
}
break;
case DLISTSAX_KVLISTEND:
rock->doingacl = 0;
break;
case DLISTSAX_STRING:
if (rock->doingacl) {
buf_append(rock->aclbuf, &d->kbuf);
buf_putc(rock->aclbuf, '\t');
buf_appendcstr(rock->aclbuf, d->data);
buf_putc(rock->aclbuf, '\t');
}
else {
const char *key = buf_cstring(&d->kbuf);
if (!strcmp(key, "C")) {
rock->mbentry->createdmodseq = atomodseq_t(d->data);
}
else if (!strcmp(key, "F")) {
rock->mbentry->foldermodseq = atomodseq_t(d->data);
}
else if (!strcmp(key, "I")) {
rock->mbentry->uniqueid = xstrdupnull(d->data);
}
else if (!strcmp(key, "M")) {
rock->mbentry->mtime = atoi(d->data);
}
else if (!strcmp(key, "P")) {
rock->mbentry->partition = xstrdupnull(d->data);
}
else if (!strcmp(key, "S")) {
rock->mbentry->server = xstrdupnull(d->data);
}
else if (!strcmp(key, "T")) {
rock->mbentry->mbtype = mboxlist_string_to_mbtype(d->data);
}
else if (!strcmp(key, "V")) {
rock->mbentry->uidvalidity = atol(d->data);
}
}
}
return 0;
}
/*
* parse a record read from the mailboxes.db into its parts.
*
* full dlist format is:
* A: _a_cl
* C _c_reatedmodseq
* F: _f_oldermodseq
* I: unique_i_d
* M: _m_time
* P: _p_artition
* S: _s_erver
* T: _t_ype
* V: uid_v_alidity
*/
EXPORTED int mboxlist_parse_entry(mbentry_t **mbentryptr,
const char *name, size_t namelen,
const char *data, size_t datalen)
{
static struct buf aclbuf;
int r = IMAP_MAILBOX_BADFORMAT;
char *freeme = NULL;
char **target;
char *p, *q;
mbentry_t *mbentry = mboxlist_entry_create();
if (!datalen)
goto done;
/* copy name */
if (namelen)
mbentry->name = xstrndup(name, namelen);
else
mbentry->name = xstrdup(name);
/* check for DLIST mboxlist */
if (*data == '%') {
struct parseentry_rock rock;
memset(&rock, 0, sizeof(struct parseentry_rock));
rock.mbentry = mbentry;
rock.aclbuf = &aclbuf;
aclbuf.len = 0;
r = dlist_parsesax(data, datalen, 0, parseentry_cb, &rock);
if (!r) mbentry->acl = buf_newcstring(&aclbuf);
goto done;
}
/* copy data */
freeme = p = xstrndup(data, datalen);
/* check for extended mboxlist entry */
if (*p == '(') {
int last = 0;
p++; /* past leading '(' */
while (!last) {
target = NULL;
q = p;
while (*q && *q != ' ' && *q != ')') q++;
if (*q != ' ') break;
*q++ = '\0';
if (!strcmp(p, "uniqueid")) target = &mbentry->uniqueid;
if (!strcmp(p, "specialuse")) target = &mbentry->legacy_specialuse;
p = q;
while (*q && *q != ' ' && *q != ')') q++;
if (*q != ' ') last = 1;
if (*q) *q++ = '\0';
if (target) *target = xstrdup(p);
p = q;
}
if (*p == ' ') p++; /* past trailing ' ' */
}
/* copy out interesting parts */
mbentry->mbtype = strtol(p, &p, 10);
if (*p == ' ') p++;
q = p;
while (*q && *q != ' ' && *q != '!') q++;
if (*q == '!') {
*q++ = '\0';
mbentry->server = xstrdup(p);
p = q;
while (*q && *q != ' ') q++;
}
if (*q) *q++ = '\0';
mbentry->partition = xstrdup(p);
mbentry->acl = xstrdup(q);
r = 0;
done:
if (!r && mbentryptr)
*mbentryptr = mbentry;
else mboxlist_entry_free(&mbentry);
free(freeme);
return r;
}
/* read a record and parse into parts */
static int mboxlist_mylookup(const char *name,
mbentry_t **mbentryptr,
struct txn **tid, int wrlock)
{
int r;
const char *data;
size_t datalen;
init_internal();
r = mboxlist_read(name, &data, &datalen, tid, wrlock);
if (r) return r;
return mboxlist_parse_entry(mbentryptr, name, 0, data, datalen);
}
/*
* Lookup 'name' in the mailbox list, ignoring reserved records
*/
EXPORTED int mboxlist_lookup(const char *name, mbentry_t **entryptr,
struct txn **tid)
{
mbentry_t *entry = NULL;
int r;
init_internal();
r = mboxlist_mylookup(name, &entry, tid, 0);
if (r) return r;
/* Ignore "reserved" entries, like they aren't there */
if (entry->mbtype & MBTYPE_RESERVE) {
mboxlist_entry_free(&entry);
return IMAP_MAILBOX_RESERVED;
}
/* Ignore "deleted" entries, like they aren't there */
if (entry->mbtype & MBTYPE_DELETED) {
mboxlist_entry_free(&entry);
return IMAP_MAILBOX_NONEXISTENT;
}
/* Ignore "intermediate" entries, like they aren't there */
if (entry->mbtype & MBTYPE_INTERMEDIATE) {
mboxlist_entry_free(&entry);
return IMAP_MAILBOX_NONEXISTENT;
}
if (entryptr) *entryptr = entry;
else mboxlist_entry_free(&entry);
return 0;
}
EXPORTED int mboxlist_lookup_allow_all(const char *name,
mbentry_t **entryptr,
struct txn **tid)
{
return mboxlist_mylookup(name, entryptr, tid, 0);
}
struct _find_specialuse_data {
const char *use;
const char *userid;
char *mboxname;
};
static int _find_specialuse(const mbentry_t *mbentry, void *rock)
{
struct _find_specialuse_data *d = (struct _find_specialuse_data *)rock;
struct buf attrib = BUF_INITIALIZER;
annotatemore_lookup(mbentry->name, "/specialuse", d->userid, &attrib);
if (attrib.len) {
strarray_t *uses = strarray_split(buf_cstring(&attrib), NULL, 0);
if (strarray_find_case(uses, d->use, 0) >= 0)
d->mboxname = xstrdup(mbentry->name);
strarray_free(uses);
}
buf_free(&attrib);
if (d->mboxname) return CYRUSDB_DONE;
return 0;
}
EXPORTED char *mboxlist_find_specialuse(const char *use, const char *userid)
{
init_internal();
assert(userid);
/* \\Inbox is magical */
if (!strcasecmp(use, "\\Inbox"))
return mboxname_user_mbox(userid, NULL);
struct _find_specialuse_data rock = { use, userid, NULL };
mboxlist_usermboxtree(userid, NULL, _find_specialuse, &rock, MBOXTREE_SKIP_ROOT);
return rock.mboxname;
}
struct _find_uniqueid_data {
const char *uniqueid;
mbentry_t **entryptr;
};
static int _find_uniqueid(const mbentry_t *mbentry, void *rock) {
struct _find_uniqueid_data *d = (struct _find_uniqueid_data *) rock;
int r = 0;
if (!strcmpsafe(d->uniqueid, mbentry->uniqueid)) {
*(d->entryptr) = mboxlist_entry_copy(mbentry);
r = CYRUSDB_DONE;
}
return r;
}
// calling this function without a userid is fine, it will scan the entire server!
static int _mboxlist_find_uniqueid(const char *uniqueid, const char *userid,
const struct auth_state *auth_state,
mbentry_t **entryptr)
{
struct _find_uniqueid_data rock = { uniqueid, entryptr };
int flags = MBOXTREE_INTERMEDIATES|MBOXTREE_PLUS_RACL;
init_internal();
if (userid && !have_runq)
return mboxlist_usermboxtree(userid, auth_state, _find_uniqueid, &rock, flags);
else
return mboxlist_foreach_uniqueid(uniqueid, _find_uniqueid, &rock, flags);
}
EXPORTED char *mboxlist_find_uniqueid(const char *uniqueid, const char *userid,
const struct auth_state *auth_state)
{
mbentry_t *mbentry = NULL;
char *mboxname = NULL;
_mboxlist_find_uniqueid(uniqueid, userid, auth_state, &mbentry);
if (mbentry) {
mboxname = xstrdup(mbentry->name);
mboxlist_entry_free(&mbentry);
}
return mboxname;
}
EXPORTED int mboxlist_lookup_by_uniqueid(const char *uniqueid, mbentry_t **entryptr)
{
return _mboxlist_find_uniqueid(uniqueid, NULL, NULL, entryptr);
}
/* given a mailbox name, find the staging directory. XXX - this should
* require more locking, and staging directories should be by pid */
HIDDEN int mboxlist_findstage(const char *name, char *stagedir, size_t sd_len)
{
const char *root;
mbentry_t *mbentry = NULL;
int r;
init_internal();
assert(stagedir != NULL);
/* Find mailbox */
r = mboxlist_lookup(name, &mbentry, NULL);
if (r) return r;
root = config_partitiondir(mbentry->partition);
mboxlist_entry_free(&mbentry);
if (!root) return IMAP_PARTITION_UNKNOWN;
snprintf(stagedir, sd_len, "%s/stage./", root);
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_can_read(const strarray_t *aclbits, const char *user)
{
int i;
if (!aclbits) return 0;
for (i = 0; i+1 < strarray_size(aclbits); i+=2) {
// skip ACLs without read bit
if (!strchr(strarray_nth(aclbits, i+1), 'r')) continue;
if (!strcmp(strarray_nth(aclbits, i), user)) return 1;
}
return 0;
}
static int mboxlist_update_raclmodseq(const char *acluser)
{
char *aclusermbox = mboxname_user_mbox(acluser, NULL);
mbentry_t *raclmbentry = NULL;
if (mboxlist_lookup(aclusermbox, &raclmbentry, NULL) == 0) {
mboxname_nextraclmodseq(aclusermbox, 0);
sync_log_mailbox(aclusermbox);
}
mboxlist_entry_free(&raclmbentry);
free(aclusermbox);
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);
if (!strchr(strarray_nth(oldusers, i+1), 'r')) continue;
if (!strcmpsafe(userid, acluser)) continue;
if (strarray_find(admins, acluser, 0) >= 0) continue;
if (user_can_read(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;
mboxlist_update_raclmodseq(acluser);
}
}
if (newusers) {
for (i = 0; i+1 < strarray_size(newusers); i+=2) {
const char *acluser = strarray_nth(newusers, i);
if (!strchr(strarray_nth(newusers, i+1), 'r')) continue;
if (!strcmpsafe(userid, acluser)) continue;
if (strarray_find(admins, acluser, 0) >= 0) continue;
if (user_can_read(oldusers, acluser)) continue;
mboxlist_racl_key(!!userid, acluser, name, &buf);
r = cyrusdb_store(mbdb, buf.s, buf.len, "", 0, txn);
if (r) goto done;
mboxlist_update_raclmodseq(acluser);
}
}
done:
strarray_free(oldusers);
strarray_free(newusers);
free(userid);
buf_free(&buf);
return r;
}
static void mboxlist_runiqueid_key(const char *uniqueid, const char *mbname, struct buf *buf)
{
buf_setcstr(buf, "$RUNQ$");
buf_appendcstr(buf, uniqueid);
buf_putc(buf, '$');
if (mbname) {
buf_appendcstr(buf, mbname);
}
}
static int mboxlist_update_runiqueid(const char *name, const mbentry_t *oldmbentry, const mbentry_t *newmbentry, struct txn **txn)
{
struct buf buf = BUF_INITIALIZER;
int r = 0;
if (oldmbentry && oldmbentry->uniqueid) {
mboxlist_runiqueid_key(oldmbentry->uniqueid, name, &buf);
r = cyrusdb_delete(mbdb, buf.s, buf.len, txn, /*force*/1);
if (r) goto done;
}
if (newmbentry && newmbentry->uniqueid) {
mboxlist_runiqueid_key(newmbentry->uniqueid, name, &buf);
r = cyrusdb_store(mbdb, buf.s, buf.len, "", 0, txn);
if (r) goto done;
}
done:
buf_free(&buf);
return r;
}
static int mboxlist_update_entry(const char *name, const mbentry_t *mbentry, struct txn **txn)
{
mbentry_t *old = NULL;
int r = 0;
mboxlist_mylookup(name, &old, txn, 0); // ignore errors, it will be NULL
if (have_racl) {
r = mboxlist_update_racl(name, old, mbentry, txn);
if (r) goto done;
}
if (have_runq) {
r = mboxlist_update_runiqueid(name, old, mbentry, txn);
if (r) goto done;
}
if (mbentry) {
char *mboxent = mboxlist_entry_cstring(mbentry);
r = cyrusdb_store(mbdb, name, strlen(name), mboxent, strlen(mboxent), txn);
free(mboxent);
if (!r && config_auditlog) {
/* XXX is there a difference between "" and NULL? */
xsyslog(LOG_NOTICE, "auditlog: acl",
"sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s> mbtype=<%s> "
"oldacl=<%s> acl=<%s> foldermodseq=<%llu>",
session_id(),
name, mbentry->uniqueid, mboxlist_mbtype_to_string(mbentry->mbtype),
old ? old->acl : "NONE", mbentry->acl, mbentry->foldermodseq);
}
}
else {
r = cyrusdb_delete(mbdb, name, strlen(name), txn, /*force*/1);
}
done:
mboxlist_entry_free(&old);
return r;
}
EXPORTED int mboxlist_delete(const char *name)
{
return mboxlist_update_entry(name, NULL, NULL);
}
EXPORTED int mboxlist_update(mbentry_t *mbentry, int localonly)
{
int r = 0, r2 = 0;
struct txn *tid = NULL;
init_internal();
r = mboxlist_update_entry(mbentry->name, mbentry, &tid);
if (!r)
mboxname_setmodseq(mbentry->name, mbentry->foldermodseq, mbentry->mbtype,
MBOXMODSEQ_ISFOLDER);
/* commit the change to mupdate */
if (!r && !localonly && config_mupdate_server) {
mupdate_handle *mupdate_h = NULL;
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if (r) {
syslog(LOG_ERR,
"cannot connect to mupdate server for update of '%s'",
mbentry->name);
} else {
char *location = strconcat(config_servername, "!",
mbentry->partition, (char *)NULL);
r = mupdate_activate(mupdate_h, mbentry->name,
location, mbentry->acl);
free(location);
if (r) {
syslog(LOG_ERR,
"MUPDATE: can't update mailbox entry for '%s'",
mbentry->name);
}
}
mupdate_disconnect(&mupdate_h);
}
if (tid) {
if (r) {
r2 = cyrusdb_abort(mbdb, tid);
if (r2)
xsyslog(LOG_ERR, "DBERROR: error aborting transaction",
"error=<%s>", cyrusdb_strerror(r2));
} else {
r2 = cyrusdb_commit(mbdb, tid);
if (r2)
xsyslog(LOG_ERR, "DBERROR: error committing transaction",
"error=<%s>", cyrusdb_strerror(r2));
}
}
return r;
}
static void assert_namespacelocked(const char *mboxname)
{
char *userid = mboxname_to_userid(mboxname);
assert(user_isnamespacelocked(userid));
free(userid);
}
static int _findparent(const char *mboxname, mbentry_t **mbentryp, int allow_all)
{
mbentry_t *mbentry = NULL;
mbname_t *mbname = mbname_from_intname(mboxname);
int r = IMAP_MAILBOX_NONEXISTENT;
init_internal();
while (strarray_size(mbname_boxes(mbname))) {
free(mbname_pop_boxes(mbname));
/* skip exactly INBOX, since it's not a real intermediate folder,
* and the parent of INBOX.INBOX.foo is INBOX */
if (strarray_size(mbname_boxes(mbname)) == 1 &&
!strcmp(strarray_nth(mbname_boxes(mbname), 0), "INBOX")) {
free(mbname_pop_boxes(mbname));
}
mboxlist_entry_free(&mbentry);
if (allow_all)
r = mboxlist_lookup_allow_all(mbname_intname(mbname), &mbentry, NULL);
else
r = mboxlist_lookup(mbname_intname(mbname), &mbentry, NULL);
if (r != IMAP_MAILBOX_NONEXISTENT)
break;
}
if (r)
mboxlist_entry_free(&mbentry);
else
*mbentryp = mbentry;
mbname_free(&mbname);
return r;
}
EXPORTED int mboxlist_findparent(const char *mboxname,
mbentry_t **mbentryp)
{
return _findparent(mboxname, mbentryp, 0);
}
EXPORTED int mboxlist_findparent_allow_all(const char *mboxname,
mbentry_t **mbentryp)
{
return _findparent(mboxname, mbentryp, 1);
}
static int mboxlist_create_partition(const char *mboxname,
const char *part,
char **out)
{
mbentry_t *parent = NULL;
if (!part) {
int r = mboxlist_findparent(mboxname, &parent);
if (!r) part = parent->partition;
}
/* use defaultpartition if specified */
if (!part && config_defpartition)
part = config_defpartition;
/* look for most fitting partition */
if (!part)
part = partlist_local_select();
/* Configuration error */
if (!part || (strlen(part) > MAX_PARTITION_LEN))
goto err;
if (!config_partitiondir(part))
goto err;
*out = xstrdupnull(part);
mboxlist_entry_free(&parent);
return 0;
err:
mboxlist_entry_free(&parent);
return IMAP_PARTITION_UNKNOWN;
}
/*
* Check if a mailbox can be created. There is no other setup at this
* stage, just the check!
*/
static int mboxlist_create_namecheck(const char *mboxname,
const char *userid,
const struct auth_state *auth_state,
int isadmin, int force_subdirs)
{
mbentry_t *mbentry = NULL;
int r = 0;
/* policy first */
r = mboxname_policycheck(mboxname);
if (r) goto done;
/* is this the user's INBOX namespace? */
if (!isadmin && mboxname_userownsmailbox(userid, mboxname)) {
/* User has admin rights over their own mailbox namespace */
if (config_implicitrights & ACL_ADMIN)
isadmin = 1;
}
/* Check to see if mailbox already exists */
r = mboxlist_lookup(mboxname, &mbentry, NULL);
if (r != IMAP_MAILBOX_NONEXISTENT) {
if (!r) {
r = IMAP_MAILBOX_EXISTS;
/* Lie about error if privacy demands */
if (!isadmin &&
!(cyrus_acl_myrights(auth_state, mbentry->acl) & ACL_LOOKUP)) {
r = IMAP_PERMISSION_DENIED;
}
}
goto done;
}
mboxlist_entry_free(&mbentry);
/* look for a parent mailbox */
r = mboxlist_findparent(mboxname, &mbentry);
if (r == 0) {
/* found a parent */
char root[MAX_MAILBOX_NAME+1];
/* check acl */
if (!isadmin &&
!(cyrus_acl_myrights(auth_state, mbentry->acl) & ACL_CREATE)) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
/* check quota */
if (quota_findroot(root, sizeof(root), mboxname)) {
quota_t qdiffs[QUOTA_NUMRESOURCES] = QUOTA_DIFFS_DONTCARE_INITIALIZER;
qdiffs[QUOTA_NUMFOLDERS] = 1;
r = quota_check_useds(root, qdiffs);
if (r) goto done;
}
/* make sure parent isn't forbidden from containing children */
if ((!isadmin || mboxname_userownsmailbox(userid, mboxname))
&& config_getstring(IMAPOPT_SPECIALUSE_NOCHILDREN))
{
struct buf attrib = BUF_INITIALIZER;
mbname_t *mbname;
mbname = mbname_from_intname(mbentry->name);
annotatemore_lookup(mbentry->name, "/specialuse",
mbname_userid(mbname), &attrib);
mbname_free(&mbname);
if (buf_len(&attrib)) {
strarray_t *uses = strarray_split(buf_cstring(&attrib), NULL, 0);
strarray_t *forbidden = strarray_split(
config_getstring(IMAPOPT_SPECIALUSE_NOCHILDREN),
NULL,
STRARRAY_TRIM
);
if (strarray_intersect(uses, forbidden))
r = IMAP_PERMISSION_DENIED;
strarray_free(forbidden);
strarray_free(uses);
}
buf_free(&attrib);
if (r) goto done;
}
}
else if (r == IMAP_MAILBOX_NONEXISTENT) {
/* no parent mailbox */
if (!isadmin) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
if (!force_subdirs) {
mbname_t *mbname = mbname_from_intname(mboxname);
if (!mbname_isdeleted(mbname) && mbname_userid(mbname) && strarray_size(mbname_boxes(mbname))) {
/* Disallow creating user.X.* when no user.X */
r = IMAP_PERMISSION_DENIED;
goto done;
}
mbname_free(&mbname);
}
/* otherwise no parent is OK */
r = 0;
}
done:
mboxlist_entry_free(&mbentry);
return r;
}
static int mboxlist_create_acl(const char *mboxname, char **out)
{
mbentry_t *mbentry = NULL;
int r;
int mask;
char *defaultacl;
char *identifier;
char *rights;
char *p;
r = mboxlist_findparent(mboxname, &mbentry);
if (!r) {
*out = xstrdup(mbentry->acl);
mboxlist_entry_free(&mbentry);
return 0;
}
*out = xstrdup("");
char *owner = mboxname_to_userid(mboxname);
if (owner) {
/* owner gets full permission on own mailbox by default */
cyrus_acl_set(out, owner, ACL_MODE_SET, ACL_ALL,
(cyrus_acl_canonproc_t *)0, (void *)0);
free(owner);
return 0;
}
defaultacl = identifier = xstrdup(config_getstring(IMAPOPT_DEFAULTACL));
for (;;) {
while (*identifier && Uisspace(*identifier)) identifier++;
rights = identifier;
while (*rights && !Uisspace(*rights)) rights++;
if (!*rights) break;
*rights++ = '\0';
while (*rights && Uisspace(*rights)) rights++;
if (!*rights) break;
p = rights;
while (*p && !Uisspace(*p)) p++;
if (*p) *p++ = '\0';
cyrus_acl_strtomask(rights, &mask);
/* XXX and if strtomask fails? */
cyrus_acl_set(out, identifier, ACL_MODE_SET, mask,
(cyrus_acl_canonproc_t *)0, (void *)0);
identifier = p;
}
free(defaultacl);
return 0;
}
/* and this API just plain sucks */
EXPORTED int mboxlist_createmailboxcheck(const char *name, int mbtype __attribute__((unused)),
const char *partition,
int isadmin, const char *userid,
const struct auth_state *auth_state,
char **newacl, char **newpartition,
int forceuser)
{
char *part = NULL;
char *acl = NULL;
int r = 0;
init_internal();
r = mboxlist_create_namecheck(name, userid, auth_state,
isadmin, forceuser);
if (r) goto done;
if (newacl) {
r = mboxlist_create_acl(name, &acl);
if (r) goto done;
}
if (newpartition) {
r = mboxlist_create_partition(name, partition, &part);
if (r) goto done;
}
done:
if (r || !newacl) free(acl);
else *newacl = acl;
if (r || !newpartition) free(part);
else *newpartition = part;
return r;
}
/* PLEASE NOTE - ALWAYS CALL AFTER MAKING THE CHANGES, as this function
* will check for children when deciding whether to create or remove
* intermediate folders */
EXPORTED int mboxlist_update_intermediaries(const char *frommboxname,
int mbtype, modseq_t modseq)
{
mbentry_t *mbentry = NULL;
mbname_t *mbname = mbname_from_intname(frommboxname);
int r = 0;
/* only use intermediates for user mailboxes */
if (!mbname_userid(mbname))
goto out;
for (; strarray_size(mbname_boxes(mbname)); free(mbname_pop_boxes(mbname))) {
/* check for magic INBOX */
if (strarray_size(mbname_boxes(mbname)) == 1 &&
!strcmp(strarray_nth(mbname_boxes(mbname), 0), "INBOX")) {
/* don't generate magic INBOX intermediate, JMAP doesn't use it */
goto out;
}
const char *mboxname = mbname_intname(mbname);
mboxlist_entry_free(&mbentry);
r = mboxlist_mylookup(mboxname, &mbentry, NULL, 0);
if (r == IMAP_MAILBOX_NONEXISTENT) r = 0;
if (r) goto out;
if (mbentry) {
if (mbentry->mbtype & MBTYPE_DELETED) {
/* fall through to create a new intermediate */
}
else if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
/* existing intermediate - delete unless it still has children */
if (mboxlist_haschildren(mboxname))
continue;
/* bump modseq, we're removing a thing that can be seen */
if (!modseq)
modseq = mboxname_nextmodseq(mboxname, mbentry->foldermodseq,
mbtype, MBOXMODSEQ_ISFOLDER);
mbentry_t *newmbentry = mboxlist_entry_copy(mbentry);
newmbentry->mbtype = MBTYPE_DELETED;
newmbentry->foldermodseq = modseq;
syslog(LOG_NOTICE,
"mboxlist: deleting intermediate with no children: %s (%s)",
mboxname, mbentry->uniqueid);
r = mboxlist_update_entry(mboxname, newmbentry, NULL);
mboxlist_entry_free(&newmbentry);
if (r) goto out;
sync_log_mailbox(mboxname);
/* we've changed the type, we're done at this level */
continue;
}
else {
/* real mailbox, we're done at this level */
continue;
}
}
/* if there's no children, there's no need for intermediates */
if (!mboxlist_haschildren(mboxname))
continue;
/* bump modseq, we're adding a thing that can be seen */
if (!modseq)
modseq = mboxname_nextmodseq(mboxname,
mbentry ? mbentry->foldermodseq : 0,
mbtype, MBOXMODSEQ_ISFOLDER);
mbentry_t *newmbentry = mboxlist_entry_create();
newmbentry->uniqueid = xstrdupnull(makeuuid());
newmbentry->createdmodseq = modseq;
newmbentry->foldermodseq = modseq;
newmbentry->mbtype = MBTYPE_INTERMEDIATE;
newmbentry->foldermodseq = modseq;
syslog(LOG_NOTICE,
"mboxlist: creating intermediate with children: %s (%s)",
mboxname, newmbentry->uniqueid);
r = mboxlist_update_entry(mboxname, newmbentry, NULL);
mboxlist_entry_free(&newmbentry);
if (r) goto out;
sync_log_mailbox(mboxname);
}
out:
mboxlist_entry_free(&mbentry);
mbname_free(&mbname);
return r;
}
EXPORTED int mboxlist_promote_intermediary(const char *mboxname)
{
mbentry_t *mbentry = NULL, *parent = NULL;
struct mailbox *mailbox = NULL;
int r = 0;
struct txn *tid = NULL;
r = mboxlist_lookup_allow_all(mboxname, &mbentry, &tid);
if (r || !(mbentry->mbtype & MBTYPE_INTERMEDIATE)) goto done;
r = mboxlist_findparent(mboxname, &parent);
if (r) goto done;
r = mboxlist_create_partition(mboxname, parent->partition,
&mbentry->partition);
if (r) goto done;
mbentry->mbtype = 0; // intermediaries are always standard
free(mbentry->acl);
mbentry->acl = xstrdupnull(parent->acl);
r = mailbox_create(mboxname, mbentry->mbtype,
mbentry->partition, parent->acl,
mbentry->uniqueid, 0 /* options */,
mbentry->uidvalidity,
mbentry->createdmodseq,
mbentry->foldermodseq, &mailbox);
if (r) goto done;
r = mailbox_add_conversations(mailbox, /*silent*/1);
if (r) goto done;
r = mboxlist_update_entry(mboxname, mbentry, &tid);
if (r) goto done;
done:
mailbox_close(&mailbox);
if (tid) {
if (r) cyrusdb_abort(mbdb, tid);
else {
r = cyrusdb_commit(mbdb, tid);
}
}
mboxlist_entry_free(&mbentry);
mboxlist_entry_free(&parent);
return r;
}
/*
* Create a mailbox
*
* 1. verify ACL's to best of ability (CRASH: abort)
* 2. verify parent ACL's if need to
* 3. create the local mailbox locally (exclusive lock) and keep it locked
* 4. open mupdate connection if necessary
* 5. create mupdate entry (CRASH: mupdate inconsistent)
*
*/
static int mboxlist_createmailbox_full(const char *mboxname, int mbtype,
const char *partition,
int isadmin, const char *userid,
const struct auth_state *auth_state,
int options, unsigned uidvalidity,
modseq_t createdmodseq,
modseq_t highestmodseq,
modseq_t foldermodseq,
const char *copyacl, const char *uniqueid,
int localonly, int forceuser, int dbonly,
int keep_intermediaries,
struct mailbox **mboxptr)
{
int r;
char *newpartition = NULL;
char *acl = NULL;
struct mailbox *newmailbox = NULL;
int isremote = mbtype & MBTYPE_REMOTE;
mbentry_t *newmbentry = NULL;
r = mboxlist_create_namecheck(mboxname, userid, auth_state,
isadmin, forceuser);
if (r) goto done;
assert_namespacelocked(mboxname);
if (copyacl) {
acl = xstrdup(copyacl);
}
else {
r = mboxlist_create_acl(mboxname, &acl);
if (r) goto done;
}
r = mboxlist_create_partition(mboxname, partition, &newpartition);
if (r) goto done;
if (!dbonly && !isremote) {
/* Filesystem Operations */
r = mailbox_create(mboxname, mbtype, newpartition, acl, uniqueid,
options, uidvalidity, createdmodseq, highestmodseq, &newmailbox);
if (r) goto done; /* CREATE failed */
r = mailbox_add_conversations(newmailbox, /*silent*/0);
if (r) goto done;
}
/* all is well - activate the mailbox */
newmbentry = mboxlist_entry_create();
newmbentry->acl = xstrdupnull(acl);
newmbentry->mbtype = mbtype;
newmbentry->partition = xstrdupnull(newpartition);
if (newmailbox) {
newmbentry->uniqueid = xstrdupnull(newmailbox->uniqueid);
newmbentry->uidvalidity = newmailbox->i.uidvalidity;
newmbentry->createdmodseq = newmailbox->i.createdmodseq;
newmbentry->foldermodseq = foldermodseq ? foldermodseq : newmailbox->i.highestmodseq;
}
r = mboxlist_update_entry(mboxname, newmbentry, NULL);
if (!r && !keep_intermediaries) {
/* create any missing intermediaries */
r = mboxlist_update_intermediaries(mboxname, mbtype, newmbentry->foldermodseq);
}
if (r) {
xsyslog(LOG_ERR, "DBERROR: failed to insert to mailboxes list",
"mailbox=<%s> error=<%s>",
mboxname, cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
/* 9. set MUPDATE entry as commited (CRASH: commited) */
if (!r && config_mupdate_server && !localonly) {
mupdate_handle *mupdate_h = NULL;
char *loc = strconcat(config_servername, "!", newpartition, (char *)NULL);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if (!r) r = mupdate_reserve(mupdate_h, mboxname, loc);
if (!r) r = mupdate_activate(mupdate_h, mboxname, loc, acl);
if (r) {
syslog(LOG_ERR, "MUPDATE: can't commit mailbox entry for '%s'",
mboxname);
mboxlist_update_entry(mboxname, NULL, 0);
}
if (mupdate_h) mupdate_disconnect(&mupdate_h);
free(loc);
}
done:
if (newmailbox) {
if (r) mailbox_delete(&newmailbox);
else if (mboxptr) *mboxptr = newmailbox;
else mailbox_close(&newmailbox);
}
free(acl);
free(newpartition);
mboxlist_entry_free(&newmbentry);
return r;
}
EXPORTED int mboxlist_createmailboxlock(const char *name, int mbtype,
const char *partition,
int isadmin, const char *userid,
const struct auth_state *auth_state,
int localonly, int forceuser, int dbonly,
int notify, struct mailbox **mailboxptr)
{
struct mboxlock *namespacelock = mboxname_usernamespacelock(name);
int r = mboxlist_createmailbox_unq(name, mbtype, partition, isadmin,
userid, auth_state, localonly,
forceuser, dbonly, notify, NULL,
mailboxptr);
mboxname_release(&namespacelock);
return r;
}
EXPORTED int mboxlist_createmailbox(const char *name, int mbtype,
const char *partition,
int isadmin, const char *userid,
const struct auth_state *auth_state,
int localonly, int forceuser, int dbonly,
int notify, struct mailbox **mailboxptr)
{
return mboxlist_createmailbox_unq(name, mbtype, partition, isadmin,
userid, auth_state, localonly,
forceuser, dbonly, notify, NULL,
mailboxptr);
}
EXPORTED int mboxlist_createmailbox_unq(const char *name, int mbtype,
const char *partition,
int isadmin, const char *userid,
const struct auth_state *auth_state,
int localonly, int forceuser, int dbonly,
int notify, const char *uniqueid,
struct mailbox **mailboxptr)
{
int options = config_getint(IMAPOPT_MAILBOX_DEFAULT_OPTIONS)
| OPT_POP3_NEW_UIDL;
return mboxlist_createmailbox_opts(name, mbtype, partition, isadmin, userid,
auth_state, options, localonly, forceuser,
dbonly, notify, uniqueid, mailboxptr);
}
EXPORTED int mboxlist_createmailbox_opts(const char *name, int mbtype,
const char *partition,
int isadmin, const char *userid,
const struct auth_state *auth_state,
int options, int localonly,
int forceuser, int dbonly,
int notify, const char *uniqueid,
struct mailbox **mailboxptr)
{
int r;
struct mailbox *mailbox = NULL;
uint32_t uidvalidity = 0;
modseq_t createdmodseq = 0;
mbentry_t *oldmbentry = NULL;
init_internal();
/* check if a mailbox tombstone or intermediate record exists */
r = mboxlist_lookup_allow_all(name, &oldmbentry, NULL);
if (!r) {
if (oldmbentry->mbtype & MBTYPE_DELETED) {
/* then the UIDVALIDITY must be higher than before */
if (uidvalidity <= oldmbentry->uidvalidity)
uidvalidity = oldmbentry->uidvalidity+1;
}
else if (oldmbentry->mbtype & MBTYPE_INTERMEDIATE) {
/* then use the existing mailbox ID and createdmodseq */
if (!uniqueid) uniqueid = oldmbentry->uniqueid;
createdmodseq = oldmbentry->createdmodseq;
}
}
r = mboxlist_createmailbox_full(name, mbtype, partition,
isadmin, userid, auth_state,
options, uidvalidity, createdmodseq, 0, 0, NULL,
uniqueid, localonly,
forceuser, dbonly, 0, &mailbox);
if (notify && !r) {
/* send a MailboxCreate event notification */
struct mboxevent *mboxevent = mboxevent_new(EVENT_MAILBOX_CREATE);
mboxevent_extract_mailbox(mboxevent, mailbox);
mboxevent_set_access(mboxevent, NULL, NULL, userid, mailbox->name, 1);
mboxevent_notify(&mboxevent);
mboxevent_free(&mboxevent);
}
if (mailboxptr && !r) *mailboxptr = mailbox;
else mailbox_close(&mailbox);
mboxlist_entry_free(&oldmbentry);
return r;
}
EXPORTED int mboxlist_createsync(const char *name, int mbtype,
const char *partition,
const char *userid, const struct auth_state *auth_state,
int options, unsigned uidvalidity,
modseq_t createdmodseq,
modseq_t highestmodseq,
modseq_t foldermodseq,
const char *acl, const char *uniqueid,
int local_only, int keep_intermediaries,
struct mailbox **mboxptr)
{
return mboxlist_createmailbox_full(name, mbtype, partition,
1, userid, auth_state,
options, uidvalidity,
createdmodseq, highestmodseq,
foldermodseq, acl, uniqueid,
local_only, 1, 0,
keep_intermediaries, mboxptr);
}
/* insert an entry for the proxy */
EXPORTED int mboxlist_insertremote(mbentry_t *mbentry,
struct txn **txn)
{
int r = 0;
if (mbentry->server) {
/* remote mailbox */
if (config_mupdate_config == IMAP_ENUM_MUPDATE_CONFIG_UNIFIED &&
!strcasecmp(mbentry->server, config_servername)) {
/* its on our server, make it a local mailbox */
mbentry->mbtype &= ~MBTYPE_REMOTE;
mbentry->server = NULL;
}
else {
/* make sure it's a remote mailbox */
mbentry->mbtype |= MBTYPE_REMOTE;
}
}
/* database put */
r = mboxlist_update_entry(mbentry->name, mbentry, txn);
switch (r) {
case CYRUSDB_OK:
break;
case CYRUSDB_AGAIN:
abort(); /* shouldn't happen ! */
break;
default:
xsyslog(LOG_ERR, "DBERROR: error updating database",
"mailbox=<%s> error=<%s>",
mbentry->name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
break;
}
return r;
}
/* Special function to delete a remote mailbox.
* Only affects mboxlist.
* Assumes admin powers. */
EXPORTED int mboxlist_deleteremote(const char *name, struct txn **in_tid)
{
int r;
struct txn **tid;
struct txn *lcl_tid = NULL;
mbentry_t *mbentry = NULL;
if(in_tid) {
tid = in_tid;
} else {
tid = &lcl_tid;
}
retry:
r = mboxlist_mylookup(name, &mbentry, tid, 1);
switch (r) {
case 0:
break;
case IMAP_MAILBOX_NONEXISTENT:
r = 0;
break;
case IMAP_AGAIN:
goto retry;
break;
default:
goto done;
}
if (mbentry && (mbentry->mbtype & MBTYPE_REMOTE) && !mbentry->server) {
syslog(LOG_ERR,
"mboxlist_deleteremote called on non-remote mailbox: %s",
name);
goto done;
}
r = mboxlist_update_entry(name, NULL, tid);
if (r) {
xsyslog(LOG_ERR, "DBERROR: error deleting entry",
"mailbox=<%s> error=<%s>",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
/* commit db operations, but only if we weren't passed a transaction */
if (!in_tid) {
r = cyrusdb_commit(mbdb, *tid);
if (r) {
xsyslog(LOG_ERR, "DBERROR: failed on commit",
"error=<%s>",
cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
tid = NULL;
}
done:
if (r && !in_tid && tid) {
/* Abort the transaction if it is still in progress */
cyrusdb_abort(mbdb, *tid);
}
mboxlist_entry_free(&mbentry);
return r;
}
/*
* Delayed Delete a mailbox: translate delete into rename
*/
EXPORTED int
mboxlist_delayed_deletemailbox(const char *name, int isadmin,
const char *userid,
const struct auth_state *auth_state,
struct mboxevent *mboxevent,
int flags)
{
mbentry_t *mbentry = NULL;
mbentry_t *newmbentry = NULL;
strarray_t existing = STRARRAY_INITIALIZER;
char newname[MAX_MAILBOX_BUFFER];
int r = 0;
long myrights;
int checkacl = flags & MBOXLIST_DELETE_CHECKACL;
int localonly = flags & MBOXLIST_DELETE_LOCALONLY;
int force = flags & MBOXLIST_DELETE_FORCE;
int keep_intermediaries = flags & MBOXLIST_DELETE_KEEP_INTERMEDIARIES;
int unprotect_specialuse = flags & MBOXLIST_DELETE_UNPROTECT_SPECIALUSE;
init_internal();
if (!isadmin && force) return IMAP_PERMISSION_DENIED;
/* delete of a user.X folder */
mbname_t *mbname = mbname_from_intname(name);
if (mbname_userid(mbname) && !strarray_size(mbname_boxes(mbname))) {
/* Can't DELETE INBOX (your own inbox) */
if (!strcmpsafe(mbname_userid(mbname), userid)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
/* Only admins may delete user */
if (!isadmin) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
}
if (!isadmin && mbname_userid(mbname) && !unprotect_specialuse) {
const char *protect = config_getstring(IMAPOPT_SPECIALUSE_PROTECT);
if (protect) {
struct buf attrib = BUF_INITIALIZER;
annotatemore_lookup(mbname_intname(mbname), "/specialuse", mbname_userid(mbname), &attrib);
if (attrib.len) {
strarray_t *check = strarray_split(protect, NULL, STRARRAY_TRIM);
strarray_t *uses = strarray_split(buf_cstring(&attrib), NULL, 0);
if (strarray_intersect_case(uses, check))
r = IMAP_MAILBOX_SPECIALUSE;
strarray_free(uses);
strarray_free(check);
}
buf_free(&attrib);
}
if (r) goto done;
}
r = mboxlist_lookup_allow_all(name, &mbentry, NULL);
if (r) goto done;
/* check if user has Delete right (we've already excluded non-admins
* from deleting a user mailbox) */
if (checkacl && !(mbentry->mbtype & MBTYPE_INTERMEDIATE)) {
myrights = cyrus_acl_myrights(auth_state, mbentry->acl);
if (!(myrights & ACL_DELETEMBOX)) {
/* User has admin rights over their own mailbox namespace */
if (mboxname_userownsmailbox(userid, name) &&
(config_implicitrights & ACL_ADMIN)) {
isadmin = 1;
}
/* Lie about error if privacy demands */
r = (isadmin || (myrights & ACL_LOOKUP)) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
/* get the deleted name */
mboxname_todeleted(name, newname, 1);
/* Get mboxlist_renamemailbox to do the hard work. No ACL checks needed */
r = mboxlist_renamemailbox(mbentry, newname, mbentry->partition,
0 /* uidvalidity */,
1 /* isadmin */, userid,
auth_state,
mboxevent,
localonly /* local_only */,
force, 1,
keep_intermediaries,
0 /* move_subscription */, 0 /* silent */);
if (r) goto done;
/* Bump the deletedmodseq of the entries of mbtype. Do not
* bump the folderdeletedmodseq, yet. We'll take care of
* that in mboxlist_deletemailbox. */
r = mboxlist_lookup_allow_all(newname, &newmbentry, NULL);
if (!r) mboxname_setmodseq(newname, newmbentry->foldermodseq,
newmbentry->mbtype, MBOXMODSEQ_ISDELETE);
done:
strarray_fini(&existing);
mboxlist_entry_free(&newmbentry);
mboxlist_entry_free(&mbentry);
mbname_free(&mbname);
return r;
}
/*
* Delete a mailbox.
* Deleting the mailbox user.FOO may only be performed by an admin.
*
* 1. Begin transaction
* 2. Verify ACL's
* 3. remove from database
* 4. remove from disk
* 5. commit transaction
* 6. Open mupdate connection if necessary
* 7. delete from mupdate
*
*/
EXPORTED int mboxlist_deletemailbox(const char *name, int isadmin,
const char *userid,
const struct auth_state *auth_state,
struct mboxevent *mboxevent,
int flags)
{
mbentry_t *mbentry = NULL;
int r = 0;
long myrights;
struct mailbox *mailbox = NULL;
int isremote = 0;
mupdate_handle *mupdate_h = NULL;
int checkacl = flags & MBOXLIST_DELETE_CHECKACL;
int localonly = flags & MBOXLIST_DELETE_LOCALONLY;
int force = flags & MBOXLIST_DELETE_FORCE;
int keep_intermediaries = flags & MBOXLIST_DELETE_KEEP_INTERMEDIARIES;
int silent = flags & MBOXLIST_DELETE_SILENT;
int unprotect_specialuse = flags & MBOXLIST_DELETE_UNPROTECT_SPECIALUSE;
init_internal();
if (!isadmin && force) return IMAP_PERMISSION_DENIED;
assert_namespacelocked(name);
/* delete of a user.X folder */
mbname_t *mbname = mbname_from_intname(name);
if (mbname_userid(mbname) && !strarray_size(mbname_boxes(mbname))) {
/* Can't DELETE INBOX (your own inbox) */
if (!strcmpsafe(mbname_userid(mbname), userid)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
/* Only admins may delete user */
if (!isadmin) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
}
if (!isadmin && mbname_userid(mbname) && !unprotect_specialuse) {
const char *protect = config_getstring(IMAPOPT_SPECIALUSE_PROTECT);
if (protect) {
struct buf attrib = BUF_INITIALIZER;
annotatemore_lookup(mbname_intname(mbname), "/specialuse", mbname_userid(mbname), &attrib);
if (attrib.len) {
strarray_t *check = strarray_split(protect, NULL, STRARRAY_TRIM);
strarray_t *uses = strarray_split(buf_cstring(&attrib), NULL, 0);
if (strarray_intersect_case(uses, check))
r = IMAP_MAILBOX_SPECIALUSE;
strarray_free(uses);
strarray_free(check);
}
buf_free(&attrib);
}
if (r) goto done;
}
r = mboxlist_lookup_allow_all(name, &mbentry, NULL);
if (r) goto done;
if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
// make it deleted and mark it done!
if (!mboxname_isdeletedmailbox(name, NULL)) {
mbentry_t *newmbentry = mboxlist_entry_copy(mbentry);
newmbentry->mbtype = MBTYPE_DELETED;
if (!silent) {
newmbentry->foldermodseq = mboxname_nextmodseq(newmbentry->name, newmbentry->foldermodseq,
newmbentry->mbtype,
MBOXMODSEQ_ISFOLDER|MBOXMODSEQ_ISDELETE);
}
r = mboxlist_update(newmbentry, /*localonly*/1);
if (r) {
xsyslog(LOG_ERR, "DBERROR: error marking deleted",
"mailbox=<%s> error=<%s>",
name, cyrusdb_strerror(r));
}
mboxlist_entry_free(&newmbentry);
}
else {
r = mboxlist_update_entry(name, NULL, 0);
if (r) {
xsyslog(LOG_ERR, "DBERROR: error deleting",
"mailbox=<%s> error=<%s>",
name, cyrusdb_strerror(r));
}
}
goto done;
}
isremote = mbentry->mbtype & MBTYPE_REMOTE;
/* check if user has Delete right (we've already excluded non-admins
* from deleting a user mailbox) */
if (checkacl) {
myrights = cyrus_acl_myrights(auth_state, mbentry->acl);
if(!(myrights & ACL_DELETEMBOX)) {
/* User has admin rights over their own mailbox namespace */
if (mboxname_userownsmailbox(userid, name) &&
(config_implicitrights & ACL_ADMIN)) {
isadmin = 1;
}
/* Lie about error if privacy demands */
r = (isadmin || (myrights & ACL_LOOKUP)) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
/* Lock the mailbox if it isn't a remote mailbox */
if (!isremote) {
r = mailbox_open_iwl(name, &mailbox);
if (!r) mailbox->silentchanges = silent;
}
if (r && !force) goto done;
/* remove from mupdate */
if (!isremote && !localonly && config_mupdate_server) {
/* delete the mailbox in MUPDATE */
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if (r) {
syslog(LOG_ERR,
"cannot connect to mupdate server for delete of '%s'",
name);
goto done;
}
r = mupdate_delete(mupdate_h, name);
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't delete mailbox entry '%s'", name);
}
if (mupdate_h) mupdate_disconnect(&mupdate_h);
}
if (r && !force) goto done;
if (!isremote && !mboxname_isdeletedmailbox(name, NULL)) {
/* store a DELETED marker */
int haschildren = mboxlist_haschildren(name);
mbentry_t *newmbentry = mboxlist_entry_create();
newmbentry->name = xstrdupnull(name);
newmbentry->mbtype = haschildren ? MBTYPE_INTERMEDIATE : MBTYPE_DELETED;
if (mailbox) {
newmbentry->uniqueid = xstrdupnull(mailbox->uniqueid);
newmbentry->uidvalidity = mailbox->i.uidvalidity;
newmbentry->createdmodseq = mailbox->i.createdmodseq;
newmbentry->foldermodseq = mailbox_modseq_dirty(mailbox);
}
r = mboxlist_update(newmbentry, /*localonly*/1);
/* any other updated intermediates get the same modseq */
if (!r && !keep_intermediaries) {
r = mboxlist_update_intermediaries(mbentry->name, mbentry->mbtype, newmbentry->foldermodseq);
}
/* Bump the modseq of entries of mbtype. There's still a tombstone
* for this mailbox, so don't bump the folderdeletedmodseq, yet. */
if (!r) {
mboxname_setmodseq(mbentry->name, newmbentry->foldermodseq,
mbentry->mbtype, MBOXMODSEQ_ISDELETE);
}
mboxlist_entry_free(&newmbentry);
}
else {
/* delete entry (including DELETED.* mailboxes, no need
* to keep that rubbish around) */
r = mboxlist_update_entry(name, NULL, 0);
if (r) {
xsyslog(LOG_ERR, "DBERROR: error deleting",
"mailbox=<%s> error=<%s>",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
if (!force) goto done;
}
/* This mailbox is gone completely, so mark its modseq */
if (!r && !isremote && mboxname_isdeletedmailbox(name, NULL)) {
mboxname_setmodseq(name, mailbox->foldermodseq, mbentry->mbtype,
MBOXMODSEQ_ISFOLDER|MBOXMODSEQ_ISDELETE);
}
if (r && !force) goto done;
}
/* delete underlying mailbox */
if (!isremote && mailbox) {
/* only on a real delete do we delete from the remote end as well */
sync_log_unmailbox(mailbox->name);
mboxevent_extract_mailbox(mboxevent, mailbox);
mboxevent_set_access(mboxevent, NULL, NULL, userid, mailbox->name, 1);
r = mailbox_delete(&mailbox);
/* abort event notification */
if (r && mboxevent)
mboxevent_free(&mboxevent);
}
done:
mailbox_close(&mailbox);
mboxlist_entry_free(&mbentry);
mbname_free(&mbname);
return r;
}
EXPORTED int mboxlist_deletemailboxlock(const char *name, int isadmin,
const char *userid,
const struct auth_state *auth_state,
struct mboxevent *mboxevent,
int flags)
{
struct mboxlock *namespacelock = mboxname_usernamespacelock(name);
int r = mboxlist_deletemailbox(name, isadmin, userid, auth_state, mboxevent, flags);
mboxname_release(&namespacelock);
return r;
}
static int _rename_check_specialuse(const char *oldname, const char *newname)
{
const char *protect = config_getstring(IMAPOPT_SPECIALUSE_PROTECT);
if (!protect) return 0;
mbname_t *old = mbname_from_intname(oldname);
mbname_t *new = mbname_from_intname(newname);
struct buf attrib = BUF_INITIALIZER;
int r = 0;
if (mbname_userid(old))
annotatemore_lookup(oldname, "/specialuse", mbname_userid(old), &attrib);
/* we have specialuse? */
if (attrib.len) {
strarray_t *check = strarray_split(protect, NULL, STRARRAY_TRIM);
strarray_t *uses = strarray_split(buf_cstring(&attrib), NULL, 0);
if (strarray_intersect_case(uses, check)) {
/* then target must be a single-depth mailbox too */
if (strarray_size(mbname_boxes(new)) != 1)
r = IMAP_MAILBOX_SPECIALUSE;
/* and have a userid as well */
if (!mbname_userid(new))
r = IMAP_MAILBOX_SPECIALUSE;
/* and not be deleted */
if (mbname_isdeleted(new))
r = IMAP_MAILBOX_SPECIALUSE;
}
strarray_free(uses);
strarray_free(check);
}
mbname_free(&new);
mbname_free(&old);
buf_free(&attrib);
return r;
}
struct renmboxdata {
size_t ol;
size_t nl;
char newname[MAX_MAILBOX_NAME+1];
const struct auth_state *authstate;
const char *partition;
const char *userid;
int local_only;
int ignorequota;
int found;
int keep_intermediaries;
int move_subscription;
};
static int renamecheck(const mbentry_t *mbentry, void *rock)
{
struct renmboxdata *text = (struct renmboxdata *)rock;
int r;
text->found++;
if((text->nl + strlen(mbentry->name + text->ol)) >= MAX_MAILBOX_NAME)
return IMAP_MAILBOX_BADNAME;
strcpy(text->newname + text->nl, mbentry->name + text->ol);
/* force create, but don't ignore policy. This is a filthy hack that
will go away when we refactor this code */
r = mboxlist_createmailboxcheck(text->newname, 0, text->partition, 1,
text->userid, text->authstate, NULL, NULL, 2);
return r;
}
static int dorename(const mbentry_t *mbentry, void *rock)
{
struct renmboxdata *text = (struct renmboxdata *)rock;
int r;
if((text->nl + strlen(mbentry->name + text->ol)) >= MAX_MAILBOX_NAME)
return IMAP_MAILBOX_BADNAME;
strcpy(text->newname + text->nl, mbentry->name + text->ol);
r = mboxlist_renamemailbox(mbentry, text->newname,
text->partition, /*uidvalidity*/0,
/*isadmin*/1, text->userid,
text->authstate,
/*mboxevent*/NULL,
text->local_only, /*forceuser*/1, text->ignorequota,
text->keep_intermediaries,
text->move_subscription, /*silent*/0);
return r;
}
EXPORTED int mboxlist_renametree(const char *oldname, const char *newname,
const char *partition, unsigned uidvalidity,
int isadmin, const char *userid,
const struct auth_state *auth_state,
struct mboxevent *mboxevent,
int local_only, int forceuser, int ignorequota,
int keep_intermediaries, int move_subscription)
{
struct renmboxdata rock;
memset(&rock, 0, sizeof(struct renmboxdata));
rock.ol = strlen(oldname);
rock.nl = strlen(newname);
memcpy(rock.newname, newname, rock.nl);
rock.partition = partition;
rock.authstate = auth_state;
rock.userid = userid;
rock.local_only = local_only;
rock.ignorequota = ignorequota;
rock.keep_intermediaries = keep_intermediaries;
rock.move_subscription = move_subscription;
mbentry_t *mbentry = NULL;
int r;
/* first check that we can rename safely */
r = mboxlist_mboxtree(oldname, renamecheck, &rock, 0);
if (r) return r;
r = mboxlist_lookup_allow_all(oldname, &mbentry, 0);
if (r) return r;
if (mbentry->mbtype & (MBTYPE_RESERVE | MBTYPE_DELETED)) {
mboxlist_entry_free(&mbentry);
return IMAP_MAILBOX_NONEXISTENT;
}
// rename the root mailbox
r = mboxlist_renamemailbox(mbentry, newname,
partition, uidvalidity,
isadmin, userid,
auth_state,
mboxevent,
local_only, forceuser, ignorequota,
keep_intermediaries, move_subscription, /*silent*/0);
mboxlist_entry_free(&mbentry);
// special-case only children exist
if (r == IMAP_MAILBOX_NONEXISTENT && rock.found) r = 0;
if (r) return r;
// now the children
r = mboxlist_mboxtree(oldname, dorename, &rock, MBOXTREE_SKIP_ROOT);
return r;
}
/*
* Rename/move a single mailbox (recursive renames are handled at a
* higher level). This only supports local mailboxes. Remote
* mailboxes are handled up in imapd.c
*/
EXPORTED int mboxlist_renamemailbox(const mbentry_t *mbentry,
const char *newname,
const char *partition, unsigned uidvalidity,
int isadmin, const char *userid,
const struct auth_state *auth_state,
struct mboxevent *mboxevent,
int local_only, int forceuser,
int ignorequota, int keep_intermediaries,
int move_subscription, int silent)
{
int r;
const char *oldname = mbentry->name;
int mupdatecommiterror = 0;
long myrights;
int isusermbox = 0; /* Are we renaming someone's inbox */
int partitionmove = 0;
struct mailbox *oldmailbox = NULL;
struct mailbox *newmailbox = NULL;
strarray_t inter = STRARRAY_INITIALIZER;
struct txn *tid = NULL;
const char *root = NULL;
char *newpartition = NULL;
mupdate_handle *mupdate_h = NULL;
mbentry_t *newmbentry = NULL;
int modseqflags = MBOXMODSEQ_ISFOLDER;
if (mboxname_isdeletedmailbox(newname, NULL))
modseqflags |= MBOXMODSEQ_ISDELETE;
init_internal();
assert_namespacelocked(mbentry->name);
assert_namespacelocked(newname);
/* special-case: intermediate mailbox */
if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
r = mboxlist_create_namecheck(newname, userid, auth_state,
isadmin, forceuser);
if (r) goto done;
newmbentry = mboxlist_entry_copy(mbentry);
free(newmbentry->name);
newmbentry->name = xstrdupnull(newname);
if (!silent) {
newmbentry->foldermodseq = mboxname_nextmodseq(newname, newmbentry->foldermodseq,
newmbentry->mbtype, modseqflags);
}
/* skip ahead to the database update */
goto dbupdate;
}
myrights = cyrus_acl_myrights(auth_state, mbentry->acl);
/* check the ACLs up-front */
if (!isadmin) {
if (!(myrights & ACL_DELETEMBOX)) {
r = (myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
return r;
}
}
/* 1. open mailbox */
r = mailbox_open_iwl(oldname, &oldmailbox);
if (r) return r;
oldmailbox->silentchanges = silent;
/* 2. verify valid move */
/* XXX - handle remote mailbox */
/* special case: same mailbox, must be a partition move */
if (!strcmp(oldname, newname)) {
const char *oldpath = mailbox_datapath(oldmailbox, 0);
/* Only admin can move mailboxes between partitions */
if (!isadmin) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
/* No partition, we're definitely not moving anywhere */
if (!partition) {
r = IMAP_MAILBOX_EXISTS;
goto done;
}
/* let mupdate code below know it was a partition move */
partitionmove = 1;
/* this is OK because it uses a different static buffer */
root = config_partitiondir(partition);
if (!root) {
r = IMAP_PARTITION_UNKNOWN;
goto done;
}
if (!strncmp(root, oldpath, strlen(root)) &&
oldpath[strlen(root)] == '/') {
/* partitions are the same or share common prefix */
r = IMAP_MAILBOX_EXISTS;
goto done;
}
/* NOTE: this is a rename to the same mailbox name on a
* different partition. This is a pretty filthy hack,
* which should be handled by having four totally different
* codepaths: INBOX -> INBOX.foo, user rename, regular rename
* and of course this one, partition move */
newpartition = xstrdup(partition);
r = mailbox_copy_files(oldmailbox, newpartition, newname, oldmailbox->uniqueid);
if (r) goto done;
newmbentry = mboxlist_entry_create();
newmbentry->mbtype = oldmailbox->mbtype;
newmbentry->partition = xstrdupnull(newpartition);
newmbentry->acl = xstrdupnull(oldmailbox->acl);
newmbentry->uidvalidity = oldmailbox->i.uidvalidity;
newmbentry->uniqueid = xstrdupnull(oldmailbox->uniqueid);
newmbentry->createdmodseq = oldmailbox->i.createdmodseq;
newmbentry->foldermodseq = silent ? oldmailbox->foldermodseq
: mboxname_nextmodseq(newname, oldmailbox->foldermodseq,
oldmailbox->mbtype, modseqflags);
r = mboxlist_update_entry(newname, newmbentry, &tid);
if (r) goto done;
/* skip ahead to the commit */
goto dbdone;
}
if (!isadmin) {
r = _rename_check_specialuse(oldname, newname);
if (r) goto done;
}
/* RENAME of some user's INBOX */
if (mboxname_isusermailbox(oldname, 1)) {
if (mboxname_isdeletedmailbox(newname, NULL)) {
/* delete user is OK */
}
else if (mboxname_isusermailbox(newname, 1)) {
/* user rename is depends on config */
if (!config_getswitch(IMAPOPT_ALLOWUSERMOVES)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
}
else if (mboxname_userownsmailbox(userid, oldname) &&
mboxname_userownsmailbox(userid, newname)) {
/* Special case of renaming inbox */
isusermbox = 1;
}
else {
/* Everything else is bogus */
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
}
r = mboxlist_create_namecheck(newname, userid, auth_state,
isadmin, forceuser);
if (r) goto done;
r = mboxlist_create_partition(newname, partition, &newpartition);
if (r) goto done;
if (!newpartition) newpartition = xstrdup(config_defpartition);
/* keep uidvalidity on rename unless specified */
if (!uidvalidity)
uidvalidity = oldmailbox->i.uidvalidity;
/* Rename the actual mailbox */
r = mailbox_rename_copy(oldmailbox, newname, newpartition, uidvalidity,
isusermbox ? userid : NULL, ignorequota,
silent, &newmailbox);
if (r) goto done;
syslog(LOG_INFO, "Rename: %s -> %s", oldname, newname);
/* create new entry */
newmbentry = mboxlist_entry_create();
newmbentry->name = xstrdupnull(newmailbox->name);
newmbentry->mbtype = newmailbox->mbtype;
newmbentry->partition = xstrdupnull(newmailbox->part);
newmbentry->acl = xstrdupnull(newmailbox->acl);
newmbentry->uidvalidity = newmailbox->i.uidvalidity;
newmbentry->uniqueid = xstrdupnull(newmailbox->uniqueid);
newmbentry->createdmodseq = newmailbox->i.createdmodseq;
newmbentry->foldermodseq = newmailbox->i.highestmodseq;
dbupdate:
do {
r = 0;
/* delete the old entry */
if (!isusermbox) {
/* store a DELETED marker */
mbentry_t *oldmbentry = mboxlist_entry_create();
oldmbentry->name = xstrdupnull(mbentry->name);
oldmbentry->mbtype = MBTYPE_DELETED;
oldmbentry->uidvalidity = mbentry->uidvalidity;
oldmbentry->uniqueid = xstrdupnull(mbentry->uniqueid);
oldmbentry->createdmodseq = mbentry->createdmodseq;
oldmbentry->foldermodseq = newmbentry->foldermodseq;
r = mboxlist_update_entry(oldname, oldmbentry, &tid);
mboxlist_entry_free(&oldmbentry);
}
/* create a new entry */
if (!r) {
r = mboxlist_update_entry(newname, newmbentry, &tid);
}
switch (r) {
case 0: /* success */
break;
case CYRUSDB_AGAIN:
tid = NULL;
break;
default:
xsyslog(LOG_ERR, "DBERROR: rename failed on store",
"oldname=<%s> newname=<%s> error=<%s>",
oldname, newname, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
break;
}
} while (r == CYRUSDB_AGAIN);
dbdone:
/* 3. Commit transaction */
r = cyrusdb_commit(mbdb, tid);
tid = NULL;
if (r) {
xsyslog(LOG_ERR, "DBERROR: rename failed on commit",
"oldname=<%s> newname=<%s> error=<%s>",
oldname, newname, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
}
/* Move subscription */
if (move_subscription) {
int is_subscribed = mboxlist_checksub(oldname, userid) == 0;
int r2 = mboxlist_changesub(oldname, userid, auth_state, 0, 0, 0);
if (r2) {
syslog(LOG_ERR, "CHANGESUB: can't unsubscribe %s: %s",
oldname, error_message(r2));
}
if (is_subscribed) {
r2 = mboxlist_changesub(newname, userid, auth_state, 1, 0, 0);
if (r2) {
syslog(LOG_ERR, "CHANGESUB: can't subscribe %s: %s",
newname, error_message(r2));
}
}
}
if (!local_only && config_mupdate_server) {
/* commit the mailbox in MUPDATE */
char *loc = strconcat(config_servername, "!", newpartition, (char *)NULL);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if (!partitionmove) {
if (!r && !isusermbox)
r = mupdate_delete(mupdate_h, oldname);
if (!r) r = mupdate_reserve(mupdate_h, newname, loc);
}
if (!r) r = mupdate_activate(mupdate_h, newname, loc, newmbentry->acl);
if (r) {
syslog(LOG_ERR,
"MUPDATE: can't commit mailbox entry for '%s'",
newname);
mupdatecommiterror = r;
}
if (mupdate_h) mupdate_disconnect(&mupdate_h);
free(loc);
}
done: /* Commit or cleanup */
if (!r && newmailbox)
r = mailbox_commit(newmailbox);
if (!keep_intermediaries) {
if (!r) r = mboxlist_update_intermediaries(oldname, newmbentry->mbtype, newmbentry->foldermodseq);
if (!r) r = mboxlist_update_intermediaries(newname, newmbentry->mbtype, newmbentry->foldermodseq);
}
if (r) {
/* rollback DB changes if it was an mupdate failure */
if (mupdatecommiterror) {
r = 0;
/* delete the new entry */
if (!isusermbox)
r = mboxlist_update_entry(newname, NULL, &tid);
/* recreate an old entry */
if (!r)
r = mboxlist_update_entry(oldname, newmbentry, &tid);
/* Commit transaction */
if (!r)
r = cyrusdb_commit(mbdb, tid);
tid = NULL;
if (r) {
/* XXX HOWTO repair this mess! */
xsyslog(LOG_ERR, "DBERROR: failed DB rollback on mailboxrename",
"oldname=<%s> newname=<%s> error=<%s>",
oldname, newname, cyrusdb_strerror(r));
xsyslog(LOG_ERR, "DBERROR: mailboxdb on mupdate and backend"
" ARE NOT CONSISTENT",
"mupdate_entry=<%s> backend_entry=<%s>",
oldname, newname);
r = IMAP_IOERROR;
} else {
r = mupdatecommiterror;
}
}
if (newmailbox) mailbox_delete(&newmailbox);
if (partitionmove && newpartition)
mailbox_delete_cleanup(NULL, newpartition, newname, oldmailbox->uniqueid);
mailbox_close(&oldmailbox);
} else {
/* log the rename before we close either mailbox, so that
* we never nuke the mailbox from the replica before realising
* that it has been renamed. This can be moved later again when
* we sync mailboxes by uniqueid rather than name... */
sync_log_rename(oldname, newname);
if (newmailbox) {
/* prepare the event notification */
if (mboxevent) {
/* case of delayed delete */
if (mboxevent->type == EVENT_MAILBOX_DELETE)
mboxevent_extract_mailbox(mboxevent, oldmailbox);
else {
mboxevent_extract_mailbox(mboxevent, newmailbox);
mboxevent_extract_old_mailbox(mboxevent, oldmailbox);
}
mboxevent_set_access(mboxevent, NULL, NULL, userid, newmailbox->name, 1);
}
mailbox_rename_cleanup(&oldmailbox, isusermbox);
#ifdef WITH_DAV
mailbox_add_dav(newmailbox);
#endif
mailbox_close(&newmailbox);
/* and log an append so that squatter indexes it */
sync_log_append(newname);
}
else if (partitionmove) {
char *oldpartition = xstrdup(oldmailbox->part);
char *olduniqueid = xstrdup(oldmailbox->uniqueid);
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: partitionmove sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s> oldpart=<%s> newpart=<%s>",
session_id(),
oldmailbox->name, oldmailbox->uniqueid,
oldpartition, partition);
/* this will sync-log the name anyway */
mailbox_close(&oldmailbox);
mailbox_delete_cleanup(NULL, oldpartition, oldname, olduniqueid);
free(olduniqueid);
free(oldpartition);
}
else if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
/* no event notification */
if (mboxevent) mboxevent->type = EVENT_CANCELLED;
}
else
abort(); /* impossible, in theory */
}
/* free memory */
strarray_fini(&inter);
free(newpartition);
mboxlist_entry_free(&newmbentry);
return r;
}
/*
* Check if the admin rights are present in the 'rights'
*/
static int mboxlist_have_admin_rights(const char *rights) {
int access, have_admin_access;
cyrus_acl_strtomask(rights, &access);
have_admin_access = access & ACL_ADMIN;
return have_admin_access;
}
/*
* Change the ACL for mailbox 'name' so that 'identifier' has the
* rights enumerated in the string 'rights'. If 'rights' is the null
* pointer, removes the ACL entry for 'identifier'. 'isadmin' is
* nonzero if user is a mailbox admin. 'userid' is the user's login id.
*
* 1. Start transaction
* 2. Check rights
* 3. Set db entry
* 4. Change backup copy (cyrus.header)
* 5. Commit transaction
* 6. Change mupdate entry
*
*/
EXPORTED int mboxlist_setacl(const struct namespace *namespace __attribute__((unused)),
const char *name,
const char *identifier, const char *rights,
int isadmin, const char *userid,
const struct auth_state *auth_state)
{
mbentry_t *mbentry = NULL;
int r;
int myrights;
int mode = ACL_MODE_SET;
int isusermbox = 0;
int isidentifiermbox = 0;
int anyoneuseracl = 1;
int ensure_owner_rights = 0;
int mask;
const char *mailbox_owner = NULL;
struct mailbox *mailbox = NULL;
char *newacl = NULL;
struct txn *tid = NULL;
init_internal();
/* round trip identifier to potentially strip domain */
mbname_t *mbname = mbname_from_userid(identifier);
/* XXX - enforce cross domain restrictions */
identifier = mbname_userid(mbname);
/* checks if the mailbox belongs to the user who is trying to change the
access rights */
if (mboxname_userownsmailbox(userid, name))
isusermbox = 1;
anyoneuseracl = config_getswitch(IMAPOPT_ANYONEUSERACL);
/* checks if the identifier is the mailbox owner */
if (mboxname_userownsmailbox(identifier, name))
isidentifiermbox = 1;
/* who is the mailbox owner? */
if (isusermbox) {
mailbox_owner = userid;
}
else if (isidentifiermbox) {
mailbox_owner = identifier;
}
/* ensure the access rights if the folder owner is the current user or
the identifier */
ensure_owner_rights = isusermbox || isidentifiermbox;
/* 1. Start Transaction */
/* lookup the mailbox to make sure it exists and get its acl */
do {
r = mboxlist_mylookup(name, &mbentry, &tid, 1);
} while(r == IMAP_AGAIN);
/* Can't do this to an in-transit or reserved mailbox */
if (!r && mbentry->mbtype & (MBTYPE_MOVING | MBTYPE_RESERVE | MBTYPE_DELETED)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
}
/* if it is not a remote mailbox, we need to unlock the mailbox list,
* lock the mailbox, and re-lock the mailboxes list */
/* we must do this to obey our locking rules */
if (!r && !(mbentry->mbtype & MBTYPE_REMOTE)) {
cyrusdb_abort(mbdb, tid);
tid = NULL;
mboxlist_entry_free(&mbentry);
/* open & lock mailbox header */
r = mailbox_open_iwl(name, &mailbox);
if (!r) {
do {
/* lookup the mailbox to make sure it exists and get its acl */
r = mboxlist_mylookup(name, &mbentry, &tid, 1);
} while (r == IMAP_AGAIN);
}
if(r) goto done;
}
/* 2. Check Rights */
if (!r && !isadmin) {
myrights = cyrus_acl_myrights(auth_state, mbentry->acl);
if (!(myrights & ACL_ADMIN)) {
r = (myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
/* 2.1 Only admin user can set 'anyone' rights if config says so */
if (!r && !isadmin && !anyoneuseracl && !strncmp(identifier, "anyone", 6)) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
/* 3. Set DB Entry */
if(!r) {
/* Make change to ACL */
newacl = xstrdup(mbentry->acl);
if (rights && *rights) {
/* rights are present and non-empty */
mode = ACL_MODE_SET;
if (*rights == '+') {
rights++;
mode = ACL_MODE_ADD;
}
else if (*rights == '-') {
rights++;
mode = ACL_MODE_REMOVE;
}
/* do not allow non-admin user to remove the admin rights from mailbox owner */
if (!isadmin && isidentifiermbox && mode != ACL_MODE_ADD) {
int has_admin_rights = mboxlist_have_admin_rights(rights);
if ((has_admin_rights && mode == ACL_MODE_REMOVE) ||
(!has_admin_rights && mode != ACL_MODE_REMOVE)) {
syslog(LOG_ERR, "Denied removal of admin rights on "
"folder \"%s\" (owner: %s) by user \"%s\"", name,
mailbox_owner, userid);
r = IMAP_PERMISSION_DENIED;
goto done;
}
}
r = cyrus_acl_strtomask(rights, &mask);
if (!r && cyrus_acl_set(&newacl, identifier, mode, mask,
ensure_owner_rights ? mboxlist_ensureOwnerRights : 0,
(void *)mailbox_owner)) {
r = IMAP_INVALID_IDENTIFIER;
}
} else {
/* do not allow to remove the admin rights from mailbox owner */
if (!isadmin && isidentifiermbox) {
syslog(LOG_ERR, "Denied removal of admin rights on "
"folder \"%s\" (owner: %s) by user \"%s\"", name,
mailbox_owner, userid);
r = IMAP_PERMISSION_DENIED;
goto done;
}
if (cyrus_acl_remove(&newacl, identifier,
ensure_owner_rights ? mboxlist_ensureOwnerRights : 0,
(void *)mailbox_owner)) {
r = IMAP_INVALID_IDENTIFIER;
}
}
}
if (!r) {
/* ok, change the database */
free(mbentry->acl);
mbentry->acl = xstrdupnull(newacl);
mbentry->foldermodseq = mailbox_modseq_dirty(mailbox);
r = mboxlist_update_entry(name, mbentry, &tid);
if (r) {
xsyslog(LOG_ERR, "DBERROR: error updating acl",
"mailbox=<%s> error=<%s>",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
/* send a AclChange event notification */
struct mboxevent *mboxevent = mboxevent_new(EVENT_ACL_CHANGE);
mboxevent_extract_mailbox(mboxevent, mailbox);
mboxevent_set_acl(mboxevent, identifier, rights);
mboxevent_set_access(mboxevent, NULL, NULL, userid, mailbox->name, 0);
mboxevent_notify(&mboxevent);
mboxevent_free(&mboxevent);
}
/* 4. Change backup copy (cyrus.header) */
/* we already have it locked from above */
if (!r && !(mbentry->mbtype & MBTYPE_REMOTE)) {
mailbox_set_acl(mailbox, newacl);
/* want to commit immediately to ensure ordering */
r = mailbox_commit(mailbox);
}
/* 5. Commit transaction */
if (!r) {
if((r = cyrusdb_commit(mbdb, tid)) != 0) {
xsyslog(LOG_ERR, "DBERROR: failed on commit",
"error=<%s>",
cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
tid = NULL;
}
/* 6. Change mupdate entry */
if (!r && config_mupdate_server) {
mupdate_handle *mupdate_h = NULL;
/* commit the update to MUPDATE */
char buf[MAX_PARTITION_LEN + HOSTNAME_SIZE + 2];
snprintf(buf, sizeof(buf), "%s!%s", config_servername, mbentry->partition);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if(r) {
syslog(LOG_ERR,
"cannot connect to mupdate server for setacl on '%s'",
name);
} else {
r = mupdate_activate(mupdate_h, name, buf, newacl);
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't update mailbox entry for '%s'",
name);
}
}
mupdate_disconnect(&mupdate_h);
}
done:
if (r && tid) {
/* if we are mid-transaction, abort it! */
int r2 = cyrusdb_abort(mbdb, tid);
if (r2) {
syslog(LOG_ERR,
"DBERROR: error aborting txn in mboxlist_setacl: %s",
cyrusdb_strerror(r2));
}
}
mailbox_close(&mailbox);
free(newacl);
mboxlist_entry_free(&mbentry);
mbname_free(&mbname);
return r;
}
/* change the ACL for mailbox 'name' when we have nothing but the name and the new value */
EXPORTED int mboxlist_updateacl_raw(const char *name, const char *newacl)
{
struct mailbox *mailbox = NULL;
int r = mailbox_open_iwl(name, &mailbox);
if (!r) r = mboxlist_sync_setacls(name, newacl, mailbox_modseq_dirty(mailbox));
if (!r) r = mailbox_set_acl(mailbox, newacl);
if (!r) r = mailbox_commit(mailbox);
mailbox_close(&mailbox);
return r;
}
/*
* Change the ACL for mailbox 'name'. We already have it locked
* and have written the backup copy to the header, so there's
* nothing left but to write the mailboxes.db.
*
* 1. Start transaction
* 2. Set db entry
* 3. Commit transaction
* 4. Change mupdate entry
*
*/
EXPORTED int
mboxlist_sync_setacls(const char *name, const char *newacl, modseq_t foldermodseq)
{
mbentry_t *mbentry = NULL;
int r;
struct txn *tid = NULL;
init_internal();
/* 1. Start Transaction */
/* lookup the mailbox to make sure it exists and get its acl */
do {
r = mboxlist_mylookup(name, &mbentry, &tid, 1);
} while(r == IMAP_AGAIN);
if (r) goto done;
// nothing to change, great
if (!strcmpsafe(mbentry->acl, newacl) && mbentry->foldermodseq >= foldermodseq)
goto done;
/* Can't do this to an in-transit or reserved mailbox */
if (mbentry->mbtype & (MBTYPE_MOVING | MBTYPE_RESERVE | MBTYPE_DELETED)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
/* 2. Set DB Entry */
free(mbentry->acl);
mbentry->acl = xstrdupnull(newacl);
if (mbentry->foldermodseq < foldermodseq)
mbentry->foldermodseq = foldermodseq;
r = mboxlist_update_entry(name, mbentry, &tid);
if (r) {
xsyslog(LOG_ERR, "DBERROR: error updating acl",
"mailbox=<%s> error=<%s>",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
}
/* 3. Commit transaction */
r = cyrusdb_commit(mbdb, tid);
if (r) {
xsyslog(LOG_ERR, "DBERROR: failed on commit",
"mailbox=<%s> error=<%s>",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
}
tid = NULL;
/* 4. Change mupdate entry */
if (config_mupdate_server) {
mupdate_handle *mupdate_h = NULL;
/* commit the update to MUPDATE */
char buf[MAX_PARTITION_LEN + HOSTNAME_SIZE + 2];
sprintf(buf, "%s!%s", config_servername, mbentry->partition);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if (r) {
syslog(LOG_ERR,
"cannot connect to mupdate server for syncacl on '%s'",
name);
} else {
r = mupdate_activate(mupdate_h, name, buf, newacl);
if (r) {
syslog(LOG_ERR,
"MUPDATE: can't update mailbox entry for '%s'",
name);
}
}
mupdate_disconnect(&mupdate_h);
}
done:
if (tid) {
/* if we are mid-transaction, abort it! */
int r2 = cyrusdb_abort(mbdb, tid);
if (r2) {
syslog(LOG_ERR,
"DBERROR: error aborting txn in sync_setacls %s: %s",
name, cyrusdb_strerror(r2));
}
}
mboxlist_entry_free(&mbentry);
return r;
}
struct find_rock {
ptrarray_t globs;
struct namespace *namespace;
const char *userid;
const char *domain;
int mb_category;
int checkmboxlist;
int issubs;
int singlepercent;
struct db *db;
int isadmin;
const struct auth_state *auth_state;
mbname_t *mbname;
mbentry_t *mbentry;
int matchlen;
findall_p *p;
findall_cb *cb;
void *procrock;
};
/* return non-zero if we like this one */
static int find_p(void *rockp,
const char *key, size_t keylen,
const char *data, size_t datalen)
{
struct find_rock *rock = (struct find_rock *) 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;
assert(!rock->mbname);
rock->mbname = mbname_from_intname(intname);
if (!rock->isadmin && !config_getswitch(IMAPOPT_CROSSDOMAINS)) {
/* don't list mailboxes outside of the default domain */
if (strcmpsafe(rock->domain, mbname_domain(rock->mbname)))
goto nomatch;
}
if (rock->mb_category && mbname_category(rock->mbname, rock->namespace, rock->userid) != rock->mb_category)
goto nomatch;
/* NOTE: this will all be cleaned up to be much more efficient sooner or later, with
* a mbname_t being kept inside the mbentry, and the extname cached all the way to
* final use. For now, we pay the cost of re-calculating for simplicity of the
* changes to mbname_t itself */
const char *extname = mbname_extname(rock->mbname, rock->namespace, rock->userid);
if (!extname) goto nomatch;
int matchlen = 0;
for (i = 0; i < rock->globs.count; i++) {
glob *g = ptrarray_nth(&rock->globs, i);
int thismatch = glob_test(g, extname);
if (thismatch > matchlen) matchlen = thismatch;
}
/* If its not a match, skip it -- partial matches are ok. */
if (!matchlen) goto nomatch;
rock->matchlen = matchlen;
/* subs DB has empty keys */
if (rock->issubs)
goto good;
/* ignore entirely deleted records */
if (mboxlist_parse_entry(&rock->mbentry, key, keylen, data, datalen))
goto nomatch;
/* nobody sees tombstones */
if (rock->mbentry->mbtype & MBTYPE_DELETED)
goto nomatch;
/* only admins and mailbox owners see intermediates */
if (rock->mbentry->mbtype & MBTYPE_INTERMEDIATE) {
if (rock->isadmin ||
mboxname_userownsmailbox(rock->userid, intname)) goto good;
else goto nomatch;
}
/* check acl */
if (!rock->isadmin) {
if (!(cyrus_acl_myrights(rock->auth_state, rock->mbentry->acl) & ACL_LOOKUP)) goto nomatch;
}
good:
if (rock->p) {
struct findall_data fdata = { extname, 0, rock->mbentry, rock->mbname, 0 };
/* mbname confirms that it's an exact match */
if (rock->matchlen == (int) strlen(extname))
fdata.is_exactmatch = 1;
if (!rock->p(&fdata, rock->procrock)) goto nomatch;
return 1;
}
else {
return 1;
}
nomatch:
mboxlist_entry_free(&rock->mbentry);
mbname_free(&rock->mbname);
return 0;
}
static int find_cb(void *rockp,
/* XXX - confirm these are the same? - nah */
const char *key __attribute__((unused)),
size_t keylen __attribute__((unused)),
const char *data __attribute__((unused)),
size_t datalen __attribute__((unused)))
{
struct find_rock *rock = (struct find_rock *) rockp;
char *testname = NULL;
int r = 0;
int i;
if (rock->checkmboxlist && !rock->mbentry) {
r = mboxlist_lookup(mbname_intname(rock->mbname), &rock->mbentry, NULL);
if (r) {
if (r == IMAP_MAILBOX_NONEXISTENT) r = 0;
goto done;
}
}
const char *extname = mbname_extname(rock->mbname, rock->namespace, rock->userid);
testname = xstrndup(extname, rock->matchlen);
struct findall_data fdata = { testname, rock->mb_category, rock->mbentry, rock->mbname, 0 };
if (rock->singlepercent) {
char sep = rock->namespace->hier_sep;
char *p = testname;
/* we need to try all the previous names in order */
while ((p = strchr(p, sep)) != NULL) {
*p = '\0';
/* only if this expression could fully match */
int matchlen = 0;
for (i = 0; i < rock->globs.count; i++) {
glob *g = ptrarray_nth(&rock->globs, i);
int thismatch = glob_test(g, testname);
if (thismatch > matchlen) matchlen = thismatch;
}
if (matchlen == (int)strlen(testname)) {
r = (*rock->cb)(&fdata, rock->procrock);
if (r) goto done;
}
/* replace the separator for the next longest name */
*p++ = sep;
}
}
/* mbname confirms that it's an exact match */
if (rock->matchlen == (int)strlen(extname))
fdata.is_exactmatch = 1;
r = (*rock->cb)(&fdata, rock->procrock);
done:
free(testname);
mboxlist_entry_free(&rock->mbentry);
mbname_free(&rock->mbname);
return r;
}
struct allmb_rock {
struct mboxlist_entry *mbentry;
mboxlist_cb *proc;
void *rock;
int flags;
};
static int allmbox_cb(void *rock,
const char *key,
size_t keylen,
const char *data,
size_t datalen)
{
struct allmb_rock *mbrock = (struct allmb_rock *)rock;
if (!mbrock->mbentry) {
int r = mboxlist_parse_entry(&mbrock->mbentry, key, keylen, data, datalen);
if (r) return r;
}
return mbrock->proc(mbrock->mbentry, mbrock->rock);
}
static int allmbox_p(void *rock,
const char *key,
size_t keylen,
const char *data,
size_t datalen)
{
struct allmb_rock *mbrock = (struct allmb_rock *)rock;
int r;
/* skip any dollar keys */
if (keylen && key[0] == '$') return 0;
/* free previous record */
mboxlist_entry_free(&mbrock->mbentry);
r = mboxlist_parse_entry(&mbrock->mbentry, key, keylen, data, datalen);
if (r) return 0;
if (!(mbrock->flags & MBOXTREE_TOMBSTONES) && (mbrock->mbentry->mbtype & MBTYPE_DELETED))
return 0;
if (!(mbrock->flags & MBOXTREE_INTERMEDIATES) && (mbrock->mbentry->mbtype & MBTYPE_INTERMEDIATE))
return 0;
return 1; /* process this record */
}
EXPORTED int mboxlist_allmbox(const char *prefix, mboxlist_cb *proc, void *rock, int flags)
{
struct allmb_rock mbrock = { NULL, proc, rock, flags };
int r = 0;
init_internal();
if (!prefix) prefix = "";
r = cyrusdb_foreach(mbdb, prefix, strlen(prefix),
allmbox_p, allmbox_cb, &mbrock, 0);
mboxlist_entry_free(&mbrock.mbentry);
return r;
}
struct runqrock {
int prefixlen;
strarray_t *list;
};
static int runq_cb(void *rock,
const char *key, size_t keylen,
const char *data __attribute__((unused)),
size_t datalen __attribute__((unused)))
{
struct runqrock *runqrock = (struct runqrock *)rock;
strarray_appendm(runqrock->list, xstrndup(key + runqrock->prefixlen, keylen - runqrock->prefixlen));
return 0;
}
static int mboxlist_runiqueid_matches(struct db *db,
const char *uniqueid,
strarray_t *matches)
{
struct buf prefix = BUF_INITIALIZER;
struct runqrock runqrock = { 0, matches };
/* direct access by userid */
mboxlist_runiqueid_key(uniqueid, NULL, &prefix);
/* this is the prefix */
runqrock.prefixlen = buf_len(&prefix);
cyrusdb_foreach(db,
buf_cstring(&prefix),
buf_len(&prefix),
NULL, runq_cb, &runqrock, NULL);
buf_free(&prefix);
return 0;
}
struct _foreach_uniqueid_data {
const char *uniqueid;
mboxlist_cb *proc;
void *rock;
int flags;
};
static int _foreach_uniqueid(const mbentry_t *mbentry, void *rock) {
struct _foreach_uniqueid_data *d = rock;
if (!strcmpsafe(d->uniqueid, mbentry->uniqueid)) {
return d->proc(mbentry, d->rock);
}
return 0;
}
EXPORTED int mboxlist_foreach_uniqueid(const char *uniqueid, mboxlist_cb *proc,
void *rock, int flags)
{
struct _foreach_uniqueid_data crock = { uniqueid, proc, rock, flags };
struct allmb_rock mbrock = { NULL, _foreach_uniqueid, &crock, flags };
int r = 0;
init_internal();
if (have_runq) {
strarray_t matches = STRARRAY_INITIALIZER;
r = mboxlist_runiqueid_matches(mbdb, uniqueid, &matches);
int i;
for (i = 0; i < strarray_size(&matches); i++) {
const char *mboxname = strarray_nth(&matches, i);
r = cyrusdb_forone(mbdb, mboxname, strlen(mboxname),
allmbox_p, allmbox_cb, &mbrock, 0);
if (r) break;
}
mboxlist_entry_free(&mbrock.mbentry);
strarray_fini(&matches);
}
else {
r = mboxlist_allmbox("", _foreach_uniqueid, &crock, flags);
}
return r;
}
EXPORTED int mboxlist_mboxtree(const char *mboxname, mboxlist_cb *proc, void *rock, int flags)
{
struct allmb_rock mbrock = { NULL, proc, rock, flags };
int r = 0;
init_internal();
if (!(flags & MBOXTREE_SKIP_ROOT)) {
r = cyrusdb_forone(mbdb, mboxname, strlen(mboxname), allmbox_p, allmbox_cb, &mbrock, 0);
if (r) goto done;
}
if (!(flags & MBOXTREE_SKIP_CHILDREN)) {
char *prefix = strconcat(mboxname, ".", (char *)NULL);
r = cyrusdb_foreach(mbdb, prefix, strlen(prefix), allmbox_p, allmbox_cb, &mbrock, 0);
free(prefix);
if (r) goto done;
}
if ((flags & MBOXTREE_DELETED)) {
struct buf buf = BUF_INITIALIZER;
const char *p = strchr(mboxname, '!');
const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX);
if (p) {
buf_printf(&buf, "%.*s!%s.%s.", (int)(p-mboxname), mboxname, dp, p+1);
}
else {
buf_printf(&buf, "%s.%s.", dp, mboxname);
}
const char *prefix = buf_cstring(&buf);
r = cyrusdb_foreach(mbdb, prefix, strlen(prefix), allmbox_p, allmbox_cb, &mbrock, 0);
buf_free(&buf);
if (r) goto done;
}
done:
mboxlist_entry_free(&mbrock.mbentry);
return r;
}
static int 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 modified_mbdb = 0;
init_internal();
if (have_racl && (!enabled || have_racl != RACL_VERSION)) {
syslog(LOG_NOTICE, "removing reverse acl support");
/* remove */
r = cyrusdb_foreach(mbdb, "$RACL", 5, NULL, del_cb, &tid, &tid);
if (!r) have_racl = 0;
+ modified_mbdb = 1;
}
if (enabled && !have_racl) {
/* add */
struct allmb_rock mbrock = { NULL, racls_add_cb, &tid, 0 };
struct buf vbuf = BUF_INITIALIZER;
buf_printf(&vbuf, "%d", RACL_VERSION);
/* 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));
}
+ modified_mbdb = 1;
mboxlist_entry_free(&mbrock.mbentry);
if (!r) r = cyrusdb_store(mbdb, "$RACL", 5, vbuf.s, vbuf.len, &tid);
if (!r) have_racl = RACL_VERSION;
buf_free(&vbuf);
}
+ if (!modified_mbdb || !tid) return r;
+
if (r)
cyrusdb_abort(mbdb, tid);
else
cyrusdb_commit(mbdb, tid);
return r;
}
static int runiqueid_add_cb(const mbentry_t *mbentry, void *rock)
{
struct txn **txn = (struct txn **)rock;
return mboxlist_update_runiqueid(mbentry->name, NULL, mbentry, txn);
}
EXPORTED int mboxlist_set_runiqueid(int enabled)
{
struct txn *tid = NULL;
int r = 0;
+ int modified_mbdb = 0;
init_internal();
if (have_runq && (!enabled || have_runq != RUNQ_VERSION)) {
syslog(LOG_NOTICE, "removing reverse unique id support");
/* remove */
r = cyrusdb_foreach(mbdb, "$RUNQ", 5, NULL, del_cb, &tid, &tid);
if (!r) have_runq = 0;
+ modified_mbdb = 1;
}
if (enabled && !have_runq) {
/* add */
struct allmb_rock mbrock = { NULL, runiqueid_add_cb, &tid, 0 };
struct buf vbuf = BUF_INITIALIZER;
buf_printf(&vbuf, "%d", RUNQ_VERSION);
/* we can't use mboxlist_allmbox because it doesn't do transactions */
syslog(LOG_NOTICE, "adding reverse uniqueid support");
r = cyrusdb_foreach(mbdb, "", 0, allmbox_p, allmbox_cb, &mbrock, &tid);
if (r) {
syslog(LOG_ERR, "ERROR: failed to add reverse uniqueid support %s", error_message(r));
}
+ modified_mbdb = 1;
mboxlist_entry_free(&mbrock.mbentry);
if (!r) r = cyrusdb_store(mbdb, "$RUNQ", 5, vbuf.s, vbuf.len, &tid);
if (!r) have_runq = RUNQ_VERSION;
buf_free(&vbuf);
}
+ if (!modified_mbdb || !tid) return r;
+
if (r)
cyrusdb_abort(mbdb, tid);
else
cyrusdb_commit(mbdb, tid);
return r;
}
struct alluser_rock {
char *prev;
user_cb *proc;
void *rock;
};
static int alluser_cb(const mbentry_t *mbentry, void *rock)
{
struct alluser_rock *urock = (struct alluser_rock *)rock;
char *userid = mboxname_to_userid(mbentry->name);
int r = 0;
if (userid) {
if (strcmpsafe(urock->prev, userid)) {
r = urock->proc(userid, urock->rock);
free(urock->prev);
urock->prev = userid;
} else
free(userid);
}
return r;
}
EXPORTED int mboxlist_alluser(user_cb *proc, void *rock)
{
struct alluser_rock urock;
int r = 0;
init_internal();
urock.prev = NULL;
urock.proc = proc;
urock.rock = rock;
r = mboxlist_allmbox(NULL, alluser_cb, &urock, /*flags*/0);
free(urock.prev);
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_racl_matches(struct db *db,
int isuser, const char *userid,
const struct auth_state *auth_state,
const char *mboxprefix, size_t len,
strarray_t *matches)
{
struct buf raclprefix = BUF_INITIALIZER;
strarray_t *groups = NULL;
struct raclrock raclrock = { 0, matches };
int i;
/* direct access by userid */
mboxlist_racl_key(isuser, userid, NULL, &raclprefix);
/* this is the prefix */
raclrock.prefixlen = buf_len(&raclprefix);
/* 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(&raclprefix, mboxprefix, len);
cyrusdb_foreach(db,
buf_cstring(&raclprefix),
buf_len(&raclprefix),
NULL, racl_cb, &raclrock, NULL);
/* indirect access via group membership: same logic as userid, but per group */
if (auth_state)
groups = auth_groups(auth_state);
if (groups) {
for (i = 0; i < strarray_size(groups); i++) {
mboxlist_racl_key(isuser, strarray_nth(groups, i), NULL, &raclprefix);
raclrock.prefixlen = buf_len(&raclprefix);
if (len) buf_appendmap(&raclprefix, mboxprefix, len);
cyrusdb_foreach(db,
buf_cstring(&raclprefix),
buf_len(&raclprefix),
NULL, racl_cb, &raclrock, NULL);
}
strarray_free(groups);
}
// can "anyone" access this?
mboxlist_racl_key(isuser, "anyone", NULL, &raclprefix);
raclrock.prefixlen = buf_len(&raclprefix);
if (len) buf_appendmap(&raclprefix, mboxprefix, len);
cyrusdb_foreach(db,
buf_cstring(&raclprefix),
buf_len(&raclprefix),
NULL, racl_cb, &raclrock, NULL);
strarray_sort(matches, cmpstringp_mbox);
strarray_uniq(matches);
buf_free(&raclprefix);
return 0;
}
/* auth_state parameter is optional, but is needed for proper expansion
* of group RACLs if flags contains MBOXTREE_PLUS_RACL */
EXPORTED int mboxlist_usermboxtree(const char *userid,
const struct auth_state *auth_state,
mboxlist_cb *proc, void *rock, int flags)
{
char *inbox = mboxname_user_mbox(userid, 0);
int r = mboxlist_mboxtree(inbox, proc, rock, flags);
if (flags & MBOXTREE_PLUS_RACL) {
/* we're using reverse ACLs */
struct allmb_rock mbrock = { NULL, proc, rock, flags };
int i;
strarray_t matches = STRARRAY_INITIALIZER;
/* user items */
mboxlist_racl_matches(mbdb, 1, userid, auth_state, NULL, 0, &matches);
for (i = 0; !r && i < strarray_size(&matches); i++) {
const char *mboxname = strarray_nth(&matches, i);
r = cyrusdb_forone(mbdb, mboxname, strlen(mboxname),
allmbox_p, allmbox_cb, &mbrock, 0);
}
/* shared items */
strarray_fini(&matches);
strarray_init(&matches);
mboxlist_racl_matches(mbdb, 0, userid, auth_state, NULL, 0, &matches);
for (i = 0; !r && i < strarray_size(&matches); i++) {
const char *mboxname = strarray_nth(&matches, i);
r = cyrusdb_forone(mbdb, mboxname, strlen(mboxname),
allmbox_p, allmbox_cb, &mbrock, 0);
}
strarray_fini(&matches);
mboxlist_entry_free(&mbrock.mbentry);
}
free(inbox);
return r;
}
static int mboxlist_find_category(struct find_rock *rock, const char *prefix, size_t len)
{
int r = 0;
init_internal();
if (!rock->issubs && !rock->isadmin && have_racl) {
/* we're using reverse ACLs */
strarray_t matches = STRARRAY_INITIALIZER;
int i;
mboxlist_racl_matches(rock->db,
(rock->mb_category == MBNAME_OTHERUSER),
rock->userid,
rock->auth_state,
prefix, len,
&matches);
/* now call the callbacks */
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;
}
/*
* Find all mailboxes that match 'pattern'.
* 'isadmin' is nonzero if user is a mailbox admin. 'userid'
* is the user's login id. For each matching mailbox, calls
* 'proc' with the name of the mailbox. If 'proc' ever returns
* a nonzero value, mboxlist_findall immediately stops searching
* and returns that value. 'rock' is passed along as an argument to proc in
* case it wants some persistent storage or extra data.
*/
/* Find all mailboxes that match 'pattern'. */
static int mboxlist_do_find(struct find_rock *rock, const strarray_t *patterns)
{
const char *userid = rock->userid;
int isadmin = rock->isadmin;
int crossdomains = config_getswitch(IMAPOPT_CROSSDOMAINS);
int allowdeleted = config_getswitch(IMAPOPT_ALLOWDELETED);
char inbox[MAX_MAILBOX_BUFFER];
size_t inboxlen = 0;
size_t prefixlen, len;
size_t domainlen = 0;
size_t userlen = userid ? strlen(userid) : 0;
char domainpat[MAX_MAILBOX_BUFFER]; /* do intra-domain fetches only */
char commonpat[MAX_MAILBOX_BUFFER];
int r = 0;
int i;
const char *p;
if (patterns->count < 1) return 0; /* nothing to do */
for (i = 0; i < patterns->count; i++) {
glob *g = glob_init(strarray_nth(patterns, i), rock->namespace->hier_sep);
ptrarray_append(&rock->globs, g);
}
if (config_virtdomains && userid && (p = strchr(userid, '@'))) {
userlen = p - userid;
domainlen = strlen(p); /* includes separator */
snprintf(domainpat, sizeof(domainpat), "%s!", p+1);
}
else
domainpat[0] = '\0';
/* calculate the inbox (with trailing .INBOX. for later use) */
if (userid && (!(p = strchr(userid, rock->namespace->hier_sep)) ||
((p - userid) > (int)userlen)) &&
strlen(userid)+7 < MAX_MAILBOX_BUFFER) {
char *t, *tmpuser = NULL;
const char *inboxuser;
if (domainlen)
snprintf(inbox, sizeof(inbox), "%s!", userid+userlen+1);
if (rock->namespace->hier_sep == '/' && (p = strchr(userid, '.'))) {
tmpuser = xmalloc(userlen);
memcpy(tmpuser, userid, userlen);
t = tmpuser + (p - userid);
while(t < (tmpuser + userlen)) {
if (*t == '.')
*t = '^';
t++;
}
inboxuser = tmpuser;
} else
inboxuser = userid;
snprintf(inbox+domainlen, sizeof(inbox)-domainlen,
"user.%.*s.INBOX.", (int)userlen, inboxuser);
free(tmpuser);
inboxlen = strlen(inbox) - 7;
}
else {
userid = 0;
}
/* Find the common search prefix of all patterns */
const char *firstpat = strarray_nth(patterns, 0);
for (prefixlen = 0; firstpat[prefixlen]; prefixlen++) {
if (prefixlen >= MAX_MAILBOX_NAME) {
r = IMAP_MAILBOX_BADNAME;
goto done;
}
char c = firstpat[prefixlen];
for (i = 1; i < patterns->count; i++) {
const char *pat = strarray_nth(patterns, i);
if (pat[prefixlen] != c) break;
}
if (rock->namespace->hier_sep == '/') {
if (c == '/') c = '.';
else if (c == '.') c = DOTCHAR;
}
if (i < patterns->count) break;
if (c == '*' || c == '%' || c == '?') break;
commonpat[prefixlen] = c;
}
commonpat[prefixlen] = '\0';
if (patterns->count == 1) {
/* Skip pattern which matches shared namespace prefix */
if (!strcmp(firstpat+prefixlen, "%"))
rock->singlepercent = 2;
/* output prefix regardless */
if (!strcmp(firstpat+prefixlen, "*%"))
rock->singlepercent = 1;
}
/*
* Personal (INBOX) namespace (only if not admin)
*/
if (userid && !isadmin) {
/* first the INBOX */
rock->mb_category = MBNAME_INBOX;
r = cyrusdb_forone(rock->db, inbox, inboxlen, &find_p, &find_cb, rock, NULL);
if (r == CYRUSDB_DONE) r = 0;
if (r) goto done;
if (rock->namespace->isalt) {
/* do exact INBOX subs before resetting the namebuffer */
rock->mb_category = MBNAME_INBOXSUB;
r = cyrusdb_foreach(rock->db, inbox, inboxlen+7, &find_p, &find_cb, rock, NULL);
if (r == CYRUSDB_DONE) r = 0;
if (r) goto done;
/* reset the namebuffer */
if (rock->cb)
r = (*rock->cb)(NULL, rock->procrock);
if (r) goto done;
}
/* iterate through all the mailboxes under the user's inbox */
rock->mb_category = MBNAME_OWNER;
r = cyrusdb_foreach(rock->db, inbox, inboxlen+1, &find_p, &find_cb, rock, NULL);
if (r == CYRUSDB_DONE) r = 0;
if (r) goto done;
/* "Alt Prefix" folders */
if (rock->namespace->isalt) {
/* reset the namebuffer */
if (rock->cb)
r = (*rock->cb)(NULL, rock->procrock);
if (r) goto done;
rock->mb_category = MBNAME_ALTINBOX;
/* special case user.foo.INBOX. If we're singlepercent == 2, this could
return DONE, in which case we don't need to foreach the rest of the
altprefix space */
r = cyrusdb_forone(rock->db, inbox, inboxlen+6, &find_p, &find_cb, rock, NULL);
if (r == CYRUSDB_DONE) goto skipalt;
if (r) goto done;
/* special case any other altprefix stuff */
rock->mb_category = MBNAME_ALTPREFIX;
r = cyrusdb_foreach(rock->db, inbox, inboxlen+1, &find_p, &find_cb, rock, NULL);
skipalt: /* we got a done, so skip out of the foreach early */
if (r == CYRUSDB_DONE) r = 0;
if (r) goto done;
}
}
/*
* Other Users namespace
*
* If "Other Users*" can match pattern, search for those mailboxes next
*/
if (isadmin || rock->namespace->accessible[NAMESPACE_USER]) {
len = strlen(rock->namespace->prefix[NAMESPACE_USER]);
if (len) len--; // trailing separator
if (!strncmp(rock->namespace->prefix[NAMESPACE_USER], commonpat, MIN(len, prefixlen))) {
if (prefixlen <= len) {
/* we match all users */
strlcpy(domainpat+domainlen, "user.", sizeof(domainpat)-domainlen);
}
else {
/* just those in this prefix */
strlcpy(domainpat+domainlen, "user.", sizeof(domainpat)-domainlen);
strlcpy(domainpat+domainlen+5, commonpat+len+1, sizeof(domainpat)-domainlen-5);
}
rock->mb_category = MBNAME_OTHERUSER;
/* because of how domains work, with crossdomains or admin you can't prefix at all :( */
size_t thislen = (isadmin || crossdomains) ? 0 : strlen(domainpat);
/* reset the namebuffer */
if (rock->cb)
r = (*rock->cb)(NULL, rock->procrock);
if (r) goto done;
r = mboxlist_find_category(rock, domainpat, thislen);
if (r) goto done;
}
}
/*
* Shared namespace
*
* search for all remaining mailboxes.
* just bother looking at the ones that have the same pattern prefix.
*/
if (isadmin || rock->namespace->accessible[NAMESPACE_SHARED]) {
len = strlen(rock->namespace->prefix[NAMESPACE_SHARED]);
if (len) len--; // trailing separator
if (!strncmp(rock->namespace->prefix[NAMESPACE_SHARED], commonpat, MIN(len, prefixlen))) {
rock->mb_category = MBNAME_SHARED;
/* reset the namebuffer */
if (rock->cb)
r = (*rock->cb)(NULL, rock->procrock);
if (r) goto done;
/* iterate through all the non-user folders on the server */
r = mboxlist_find_category(rock, domainpat, domainlen);
if (r) goto done;
}
}
/* finally deleted namespaces - first the owner */
if (!isadmin && allowdeleted && userid) {
char prefix[MAX_MAILBOX_BUFFER];
char *inboxcopy = xstrndup(inbox, inboxlen);
mboxname_todeleted(inboxcopy, prefix, /*withtime*/0);
free(inboxcopy);
size_t prefixlen = strlen(prefix);
prefix[prefixlen] = '.';
rock->mb_category = MBNAME_OWNERDELETED;
/* reset the namebuffer */
if (rock->cb)
r = (*rock->cb)(NULL, rock->procrock);
if (r) goto done;
r = cyrusdb_foreach(rock->db, prefix, prefixlen+1, &find_p, &find_cb, rock, NULL);
if (r) goto done;
}
/* and everything else */
if (isadmin || (allowdeleted && rock->namespace->accessible[NAMESPACE_SHARED])) {
rock->mb_category = MBNAME_OTHERDELETED;
/* reset the namebuffer */
if (rock->cb)
r = (*rock->cb)(NULL, rock->procrock);
if (r) goto done;
/* iterate through all the non-user folders on the server */
r = mboxlist_find_category(rock, domainpat, domainlen);
if (r) goto done;
}
/* finish with a reset call always */
if (rock->cb)
r = (*rock->cb)(NULL, rock->procrock);
done:
for (i = 0; i < rock->globs.count; i++) {
glob *g = ptrarray_nth(&rock->globs, i);
glob_free(&g);
}
ptrarray_fini(&rock->globs);
return r;
}
EXPORTED int mboxlist_findallmulti(struct namespace *namespace,
const strarray_t *patterns, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_cb *proc, void *rock)
{
return mboxlist_findallmulti_withp(namespace, patterns, isadmin,
userid, auth_state,
NULL, proc, rock);
}
EXPORTED int mboxlist_findallmulti_withp(struct namespace *namespace,
const strarray_t *patterns, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_p *p, findall_cb *cb, void *rock)
{
int r = 0;
init_internal();
if (!namespace) namespace = mboxname_get_adminnamespace();
struct find_rock cbrock;
memset(&cbrock, 0, sizeof(struct find_rock));
cbrock.auth_state = auth_state;
cbrock.db = mbdb;
cbrock.isadmin = isadmin;
cbrock.namespace = namespace;
cbrock.p = p;
cbrock.cb = cb;
cbrock.procrock = rock;
cbrock.userid = userid;
if (userid) {
const char *domp = strchr(userid, '@');
if (domp) cbrock.domain = domp + 1;
}
r = mboxlist_do_find(&cbrock, patterns);
return r;
}
EXPORTED int mboxlist_findall(struct namespace *namespace,
const char *pattern, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_cb *proc, void *rock)
{
return mboxlist_findall_withp(namespace, pattern, isadmin,
userid, auth_state,
NULL, proc, rock);
}
EXPORTED int mboxlist_findall_withp(struct namespace *namespace,
const char *pattern, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_p *p, findall_cb *cb, void *rock)
{
strarray_t patterns = STRARRAY_INITIALIZER;
strarray_append(&patterns, pattern);
init_internal();
int r = mboxlist_findallmulti_withp(namespace, &patterns, isadmin, userid, auth_state,
p, cb, rock);
strarray_fini(&patterns);
return r;
}
EXPORTED int mboxlist_findone(struct namespace *namespace,
const char *intname, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_cb *proc, void *rock)
{
return mboxlist_findone_withp(namespace, intname, isadmin,
userid, auth_state,
NULL, proc, rock);
}
EXPORTED int mboxlist_findone_withp(struct namespace *namespace,
const char *intname, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_p *p, findall_cb *cb, void *rock)
{
int r = 0;
if (!namespace) namespace = mboxname_get_adminnamespace();
struct find_rock cbrock;
memset(&cbrock, 0, sizeof(struct find_rock));
init_internal();
cbrock.auth_state = auth_state;
cbrock.db = mbdb;
cbrock.isadmin = isadmin;
cbrock.namespace = namespace;
cbrock.p = p;
cbrock.cb = cb;
cbrock.procrock = rock;
cbrock.userid = userid;
if (userid) {
const char *domp = strchr(userid, '@');
if (domp) cbrock.domain = domp + 1;
}
mbname_t *mbname = mbname_from_intname(intname);
glob *g = glob_init(mbname_extname(mbname, namespace, userid),
namespace->hier_sep);
ptrarray_append(&cbrock.globs, g);
mbname_free(&mbname);
r = cyrusdb_forone(cbrock.db, intname, strlen(intname), &find_p, &find_cb, &cbrock, NULL);
glob_free(&g);
ptrarray_fini(&cbrock.globs);
return r;
}
static int exists_cb(const mbentry_t *mbentry __attribute__((unused)), void *rock)
{
int *exists = (int *)rock;
*exists = 1;
return CYRUSDB_DONE; /* one is enough */
}
struct changequota_rock {
const char *root;
int silent;
};
/*
* Set all the resource quotas on, or create a quota root.
*/
EXPORTED int mboxlist_setquotas(const char *root,
quota_t newquotas[QUOTA_NUMRESOURCES],
modseq_t quotamodseq, int force)
{
struct quota q;
int r;
int res;
struct txn *tid = NULL;
struct mboxevent *mboxevents = NULL;
struct mboxevent *quotachange_event = NULL;
struct mboxevent *quotawithin_event = NULL;
int silent = quotamodseq ? 1 : 0;
init_internal();
if (!root[0] || root[0] == '.' || strchr(root, '/')
|| strchr(root, '*') || strchr(root, '%') || strchr(root, '?')) {
return IMAP_MAILBOX_BADNAME;
}
quota_init(&q, root);
r = quota_read(&q, &tid, 1);
if (!r) {
int changed = 0;
int underquota;
quota_t oldquotas[QUOTA_NUMRESOURCES];
/* has it changed? */
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
oldquotas[res] = q.limits[res];
if (q.limits[res] != newquotas[res]) {
underquota = 0;
/* Prepare a QuotaChange event notification *now*.
*
* This is to ensure the QuotaChange is emitted before the
* subsequent QuotaWithin (if the latter becomes applicable).
*/
if (quotachange_event == NULL) {
quotachange_event = mboxevent_enqueue(EVENT_QUOTA_CHANGE,
&mboxevents);
}
/* prepare a QuotaWithin event notification if now under quota */
if (quota_is_overquota(&q, res, NULL) &&
(!quota_is_overquota(&q, res, newquotas) || newquotas[res] == -1)) {
if (quotawithin_event == NULL)
quotawithin_event = mboxevent_enqueue(EVENT_QUOTA_WITHIN,
&mboxevents);
underquota++;
}
q.limits[res] = newquotas[res];
changed++;
mboxevent_extract_quota(quotachange_event, &q, res);
if (underquota)
mboxevent_extract_quota(quotawithin_event, &q, res);
}
}
if (changed) {
if (quotamodseq)
q.modseq = quotamodseq;
r = quota_write(&q, silent, &tid);
if (quotachange_event == NULL) {
quotachange_event = mboxevent_enqueue(EVENT_QUOTA_CHANGE, &mboxevents);
}
for (res = 0; res < QUOTA_NUMRESOURCES; res++) {
mboxevent_extract_quota(quotachange_event, &q, res);
}
if (config_auditlog) {
struct buf item = BUF_INITIALIZER;
for (res = 0; res < QUOTA_NUMRESOURCES; res++) {
buf_printf(&item, " old%s=<%lld> new%s=<%lld>",
quota_names[res], oldquotas[res],
quota_names[res], newquotas[res]);
}
syslog(LOG_NOTICE, "auditlog: setquota root=<%s>%s", root, buf_cstring(&item));
buf_free(&item);
}
}
if (!r)
quota_commit(&tid);
goto done;
}
if (r != IMAP_QUOTAROOT_NONEXISTENT)
goto done;
if (config_virtdomains && root[strlen(root)-1] == '!') {
/* domain quota */
}
else {
mbentry_t *mbentry = NULL;
/* look for a top-level mailbox in the proposed quotaroot */
r = mboxlist_lookup(root, &mbentry, NULL);
if (r) {
if (!force && r == IMAP_MAILBOX_NONEXISTENT) {
mboxlist_mboxtree(root, exists_cb, &force, MBOXTREE_SKIP_ROOT);
}
/* are we going to force the create anyway? */
if (force) {
r = 0;
}
}
else if (mbentry->mbtype & (MBTYPE_REMOTE | MBTYPE_MOVING)) {
/* Can't set quota on a remote mailbox */
r = IMAP_MAILBOX_NOTSUPPORTED;
}
mboxlist_entry_free(&mbentry);
if (r) goto done;
}
/* safe against quota -f and other root change races */
r = quota_changelock();
if (r) goto done;
/* initialise the quota */
memcpy(q.limits, newquotas, sizeof(q.limits));
if (quotamodseq)
q.modseq = quotamodseq;
r = quota_write(&q, silent, &tid);
if (r) goto done;
/* prepare a QuotaChange event notification */
if (quotachange_event == NULL)
quotachange_event = mboxevent_enqueue(EVENT_QUOTA_CHANGE, &mboxevents);
for (res = 0; res < QUOTA_NUMRESOURCES; res++) {
mboxevent_extract_quota(quotachange_event, &q, res);
}
quota_commit(&tid);
if (config_auditlog) {
struct buf item = BUF_INITIALIZER;
for (res = 0; res < QUOTA_NUMRESOURCES; res++) {
buf_printf(&item, " new%s=<%lld>",
quota_names[res], newquotas[res]);
}
syslog(LOG_NOTICE, "auditlog: newquota root=<%s>%s", root, buf_cstring(&item));
buf_free(&item);
}
/* recurse through mailboxes, setting the quota and finding
* out the usage */
struct changequota_rock crock = { root, silent };
mboxlist_mboxtree(root, mboxlist_changequota, &crock, 0);
quota_changelockrelease();
done:
quota_free(&q);
if (r && tid) quota_abort(&tid);
if (!r) {
sync_log_quota(root);
/* send QuotaChange and QuotaWithin event notifications */
mboxevent_notify(&mboxevents);
}
mboxevent_freequeue(&mboxevents);
return r;
}
/*
* Remove a quota root
*/
EXPORTED int mboxlist_unsetquota(const char *root)
{
struct quota q;
int r=0;
init_internal();
if (!root[0] || root[0] == '.' || strchr(root, '/')
|| strchr(root, '*') || strchr(root, '%') || strchr(root, '?')) {
return IMAP_MAILBOX_BADNAME;
}
quota_init(&q, root);
r = quota_read(&q, NULL, 0);
/* already unset */
if (r == IMAP_QUOTAROOT_NONEXISTENT) {
r = 0;
goto done;
}
if (r) goto done;
r = quota_changelock();
/*
* Have to remove it from all affected mailboxes
*/
mboxlist_mboxtree(root, mboxlist_rmquota, (void *)root, /*flags*/0);
if (config_auditlog) {
struct buf item = BUF_INITIALIZER;
int res;
for (res = 0; res < QUOTA_NUMRESOURCES; res++) {
buf_printf(&item, " old%s=<%lld>", quota_names[res], q.limits[res]);
}
syslog(LOG_NOTICE, "auditlog: rmquota root=<%s>%s", root, buf_cstring(&item));
buf_free(&item);
}
r = quota_deleteroot(root, 0);
quota_changelockrelease();
if (!r) sync_log_quota(root);
done:
quota_free(&q);
return r;
}
EXPORTED int mboxlist_update_foldermodseq(const char *name, modseq_t foldermodseq)
{
mbentry_t *mbentry = NULL;
init_internal();
int r = mboxlist_mylookup(name, &mbentry, NULL, 0);
if (r) return r;
if (mbentry->foldermodseq < foldermodseq) {
mbentry->foldermodseq = foldermodseq;
r = mboxlist_update(mbentry, 0);
}
mboxlist_entry_free(&mbentry);
return r;
}
/*
* ACL access canonicalization routine which ensures that 'owner'
* retains lookup, administer, and create rights over a mailbox.
*/
EXPORTED int mboxlist_ensureOwnerRights(void *rock, const char *identifier,
int myrights)
{
char *owner = (char *)rock;
if (strcmp(identifier, owner) != 0) return myrights;
return myrights|config_implicitrights;
}
/*
* Helper function to remove the quota root for 'name'
*/
static int mboxlist_rmquota(const mbentry_t *mbentry, void *rock)
{
int r = 0;
struct mailbox *mailbox = NULL;
const char *oldroot = (const char *) rock;
assert(oldroot != NULL);
r = mailbox_open_iwl(mbentry->name, &mailbox);
if (r) goto done;
if (mailbox->quotaroot) {
if (strcmp(mailbox->quotaroot, oldroot)) {
/* Part of a different quota root */
goto done;
}
r = mailbox_set_quotaroot(mailbox, NULL);
}
done:
mailbox_close(&mailbox);
if (r) {
syslog(LOG_ERR, "LOSTQUOTA: unable to remove quota root %s for %s: %s",
oldroot, mbentry->name, error_message(r));
}
/* not a huge tragedy if we failed, so always return success */
return 0;
}
/*
* Helper function to change the quota root for 'name' to that pointed
* to by the static global struct pointer 'mboxlist_newquota'.
*/
static int mboxlist_changequota(const mbentry_t *mbentry, void *rock)
{
int r = 0;
struct mailbox *mailbox = NULL;
struct changequota_rock *crock = rock;
int res;
quota_t quota_usage[QUOTA_NUMRESOURCES];
assert(crock->root);
r = mailbox_open_iwl(mbentry->name, &mailbox);
if (r) goto done;
mailbox_get_usage(mailbox, quota_usage);
if (mailbox->quotaroot) {
quota_t quota_diff[QUOTA_NUMRESOURCES];
if (strlen(mailbox->quotaroot) >= strlen(crock->root)) {
/* Part of a child quota root - skip */
goto done;
}
/* remove usage from the old quotaroot */
for (res = 0; res < QUOTA_NUMRESOURCES ; res++) {
quota_diff[res] = -quota_usage[res];
}
r = quota_update_useds(mailbox->quotaroot, quota_diff,
mailbox->name, crock->silent);
}
/* update (or set) the quotaroot */
r = mailbox_set_quotaroot(mailbox, crock->root);
if (r) goto done;
/* update the new quota root */
r = quota_update_useds(crock->root, quota_usage, mailbox->name, crock->silent);
done:
mailbox_close(&mailbox);
if (r) {
syslog(LOG_ERR, "LOSTQUOTA: unable to change quota root for %s to %s: %s",
mbentry->name, crock->root, error_message(r));
}
/* Note, we're a callback, and it's not a huge tragedy if we
* fail, so we don't ever return a failure */
return 0;
}
EXPORTED int mboxlist_haschildren(const char *mboxname)
{
int exists = 0;
mboxlist_mboxtree(mboxname, exists_cb, &exists, MBOXTREE_SKIP_ROOT);
return exists;
}
EXPORTED void mboxlist_done(void)
{
/* DB->done() handled by cyrus_done() */
}
static void done_cb(void*rock __attribute__((unused)))
{
if (mboxlist_dbopen) {
mboxlist_close();
}
mboxlist_done();
}
static void init_internal()
{
if (!mboxlist_initialized) {
mboxlist_init(0);
}
if (!mboxlist_dbopen) {
mboxlist_open(NULL);
}
}
/* must be called after cyrus_init */
EXPORTED void mboxlist_init(int myflags)
{
if (myflags & MBOXLIST_SYNC) {
cyrusdb_sync(DB);
}
cyrus_modules_add(done_cb, NULL);
mboxlist_initialized = 1;
}
static int read_version(const char *key)
{
const char *data = NULL;
size_t len = 0;
int r = cyrusdb_fetch(mbdb, key, strlen(key), &data, &len, NULL);
if (r == CYRUSDB_NOTFOUND) return 0; // missing == not enabled == 0
assert(!r); // crash on rubbish
if (len == 0) return 1; // no value == version 1
int val = 0;
size_t i = 0;
for (i = 0; i < len; i++) {
assert(cyrus_isdigit(data[i])); // crash on rubbish
val *= 10;
val += data[i] - '0';
}
return val;
}
EXPORTED void mboxlist_open(const char *fname)
{
int ret, flags;
char *tofree = NULL;
if (!fname)
fname = config_getstring(IMAPOPT_MBOXLIST_DB_PATH);
/* create db file name */
if (!fname) {
tofree = strconcat(config_dir, FNAME_MBOXLIST, (char *)NULL);
fname = tofree;
}
mboxlist_init(0);
flags = CYRUSDB_CREATE;
if (config_getswitch(IMAPOPT_IMPROVED_MBOXLIST_SORT)) {
flags |= CYRUSDB_MBOXSORT;
}
ret = cyrusdb_open(DB, fname, flags, &mbdb);
if (ret != 0) {
xsyslog(LOG_ERR, "DBERROR: error opening mailboxes list",
"fname=<%s> error=<%s>",
fname, cyrusdb_strerror(ret));
/* Exiting TEMPFAIL because Sendmail thinks this
EX_OSFILE == permanent failure. */
fatal("can't read mailboxes file", EX_TEMPFAIL);
}
free(tofree);
mboxlist_dbopen = 1;
have_racl = read_version("$RACL");
have_runq = read_version("$RUNQ");
}
EXPORTED void mboxlist_close(void)
{
int r;
if (mboxlist_dbopen) {
r = cyrusdb_close(mbdb);
if (r) {
xsyslog(LOG_ERR, "DBERROR: error closing mailboxes",
"error=<%s>",
cyrusdb_strerror(r));
}
mboxlist_dbopen = 0;
}
}
/*
* Open the subscription list for 'userid'.
*
* On success, returns zero.
* On failure, returns an error code.
*/
static int
mboxlist_opensubs(const char *userid,
struct db **ret)
{
int r = 0, flags;
char *subsfname;
/* Build subscription list filename */
subsfname = user_hash_subs(userid);
flags = CYRUSDB_CREATE;
if (config_getswitch(IMAPOPT_IMPROVED_MBOXLIST_SORT)) {
flags |= CYRUSDB_MBOXSORT;
}
r = cyrusdb_open(SUBDB, subsfname, flags, ret);
if (r != CYRUSDB_OK) {
r = IMAP_IOERROR;
}
free(subsfname);
return r;
}
/*
* Close a subscription file
*/
static void mboxlist_closesubs(struct db *sub)
{
cyrusdb_close(sub);
}
/*
* Find subscribed mailboxes that match 'pattern'.
* 'isadmin' is nonzero if user is a mailbox admin. 'userid'
* is the user's login id. For each matching mailbox, calls
* 'proc' with the name of the mailbox.
*/
EXPORTED int mboxlist_findsubmulti(struct namespace *namespace,
const strarray_t *patterns, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_cb *proc, void *rock,
int force)
{
return mboxlist_findsubmulti_withp(namespace, patterns, isadmin,
userid, auth_state,
NULL, proc, rock,
force);
}
EXPORTED int mboxlist_findsubmulti_withp(struct namespace *namespace,
const strarray_t *patterns, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_p *p, findall_cb *cb, void *rock,
int force)
{
int r = 0;
init_internal();
if (!namespace) namespace = mboxname_get_adminnamespace();
struct find_rock cbrock;
memset(&cbrock, 0, sizeof(struct find_rock));
/* open the subscription file that contains the mailboxes the
user is subscribed to */
struct db *subs = NULL;
r = mboxlist_opensubs(userid, &subs);
if (r) return r;
cbrock.auth_state = auth_state;
cbrock.checkmboxlist = !force;
cbrock.db = subs;
cbrock.isadmin = isadmin;
cbrock.issubs = 1;
cbrock.namespace = namespace;
cbrock.p = p;
cbrock.cb = cb;
cbrock.procrock = rock;
cbrock.userid = userid;
if (userid) {
const char *domp = strchr(userid, '@');
if (domp) cbrock.domain = domp + 1;
}
r = mboxlist_do_find(&cbrock, patterns);
mboxlist_closesubs(subs);
return r;
}
EXPORTED int mboxlist_findsub(struct namespace *namespace,
const char *pattern, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_cb *proc, void *rock,
int force)
{
return mboxlist_findsub_withp(namespace, pattern, isadmin,
userid, auth_state,
NULL, proc, rock,
force);
}
EXPORTED int mboxlist_findsub_withp(struct namespace *namespace,
const char *pattern, int isadmin,
const char *userid, const struct auth_state *auth_state,
findall_p *p, findall_cb *cb, void *rock,
int force)
{
strarray_t patterns = STRARRAY_INITIALIZER;
strarray_append(&patterns, pattern);
init_internal();
int r = mboxlist_findsubmulti_withp(namespace, &patterns, isadmin, userid, auth_state,
p, cb, rock, force);
strarray_fini(&patterns);
return r;
}
static int subsadd_cb(void *rock, const char *key, size_t keylen,
const char *val __attribute__((unused)),
size_t vallen __attribute__((unused)))
{
strarray_t *list = (strarray_t *)rock;
strarray_appendm(list, xstrndup(key, keylen));
return 0;
}
EXPORTED strarray_t *mboxlist_sublist(const char *userid)
{
struct db *subs = NULL;
strarray_t *list = strarray_new();
int r;
init_internal();
/* open subs DB */
r = mboxlist_opensubs(userid, &subs);
if (r) goto done;
r = cyrusdb_foreach(subs, "", 0, NULL, subsadd_cb, list, 0);
mboxlist_closesubs(subs);
done:
if (r) {
strarray_free(list);
return NULL;
}
return list;
}
struct submb_rock {
struct mboxlist_entry *mbentry;
const char *userid;
int flags;
mboxlist_cb *proc;
void *rock;
};
static int usersubs_cb(void *rock, const char *key, size_t keylen,
const char *data __attribute__((unused)),
size_t datalen __attribute__((unused)))
{
struct submb_rock *mbrock = (struct submb_rock *) rock;
char mboxname[MAX_MAILBOX_NAME+1];
int r;
/* free previous record */
mboxlist_entry_free(&mbrock->mbentry);
snprintf(mboxname, MAX_MAILBOX_NAME, "%.*s", (int) keylen, key);
if ((mbrock->flags & MBOXTREE_SKIP_PERSONAL) &&
mboxname_userownsmailbox(mbrock->userid, mboxname)) return 0;
r = mboxlist_lookup(mboxname, &mbrock->mbentry, NULL);
if (r == IMAP_MAILBOX_NONEXISTENT) return 0;
if (r) {
syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s",
mboxname, error_message(r));
return r;
}
return mbrock->proc(mbrock->mbentry, mbrock->rock);
}
EXPORTED int mboxlist_usersubs(const char *userid, mboxlist_cb *proc,
void *rock, int flags)
{
struct db *subs = NULL;
struct submb_rock mbrock = { NULL, userid, flags, proc, rock };
int r = 0;
init_internal();
/* open subs DB */
r = mboxlist_opensubs(userid, &subs);
if (r) return r;
/* faster to do it all in a single slurp! */
r = cyrusdb_foreach(subs, "", 0, NULL, usersubs_cb, &mbrock, 0);
mboxlist_entry_free(&mbrock.mbentry);
mboxlist_closesubs(subs);
return r;
}
/* returns CYRUSDB_NOTFOUND if the folder doesn't exist, and 0 if it does! */
EXPORTED int mboxlist_checksub(const char *name, const char *userid)
{
int r;
struct db *subs;
const char *val;
size_t vallen;
init_internal();
r = mboxlist_opensubs(userid, &subs);
if (!r) r = cyrusdb_fetch(subs, name, strlen(name), &val, &vallen, NULL);
mboxlist_closesubs(subs);
return r;
}
/*
* Change 'user's subscription status for mailbox 'name'.
* Subscribes if 'add' is nonzero, unsubscribes otherwise.
* if 'force' is set, force the subscription through even if
* we don't know about 'name'.
*/
EXPORTED int mboxlist_changesub(const char *name, const char *userid,
const struct auth_state *auth_state,
int add, int force, int notify)
{
mbentry_t *mbentry = NULL;
int r;
struct db *subs;
struct mboxevent *mboxevent;
init_internal();
if ((r = mboxlist_opensubs(userid, &subs)) != 0) {
return r;
}
if (add && !force) {
/* Ensure mailbox exists and can be seen by user */
if ((r = mboxlist_lookup(name, &mbentry, NULL))!=0) {
mboxlist_closesubs(subs);
return r;
}
if ((cyrus_acl_myrights(auth_state, mbentry->acl) & ACL_LOOKUP) == 0) {
mboxlist_closesubs(subs);
mboxlist_entry_free(&mbentry);
return IMAP_MAILBOX_NONEXISTENT;
}
}
if (add) {
r = cyrusdb_store(subs, name, strlen(name), "", 0, NULL);
} else {
r = cyrusdb_delete(subs, name, strlen(name), NULL, 0);
/* if it didn't exist, that's ok */
if (r == CYRUSDB_EXISTS) r = CYRUSDB_OK;
}
switch (r) {
case CYRUSDB_OK:
r = 0;
break;
default:
r = IMAP_IOERROR;
break;
}
sync_log_subscribe(userid, name);
mboxlist_closesubs(subs);
mboxlist_entry_free(&mbentry);
/* prepare a MailboxSubscribe or MailboxUnSubscribe event notification */
if (notify && r == 0) {
mboxevent = mboxevent_new(add ? EVENT_MAILBOX_SUBSCRIBE :
EVENT_MAILBOX_UNSUBSCRIBE);
mboxevent_set_access(mboxevent, NULL, NULL, userid, name, 1);
mboxevent_notify(&mboxevent);
mboxevent_free(&mboxevent);
}
return r;
}
/* Transaction Handlers */
EXPORTED int mboxlist_commit(struct txn *tid)
{
assert(tid);
return cyrusdb_commit(mbdb, tid);
}
int mboxlist_abort(struct txn *tid)
{
assert(tid);
return cyrusdb_abort(mbdb, tid);
}
EXPORTED int mboxlist_delayed_delete_isenabled(void)
{
enum enum_value config_delete_mode = config_getenum(IMAPOPT_DELETE_MODE);
return(config_delete_mode == IMAP_ENUM_DELETE_MODE_DELAYED);
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Apr 24, 1:57 PM (6 d, 8 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18896751
Default Alt Text
(153 KB)

Event Timeline