Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F120838313
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
153 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline