Page MenuHomePhorge

mboxlist.c
No OneTemporary

Authored By
Unknown
Size
168 KB
Referenced Files
None
Subscribers
None

mboxlist.c

/* 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
#define KEY_TYPE_NAME 'N'
#define KEY_TYPE_ID 'I'
#define KEY_TYPE_ACL 'A'
#define DB_DOMAINSEP_STR "\x1D" /* group separator (GS) */
#define DB_DOMAINSEP_CHAR DB_DOMAINSEP_STR[0]
#define DB_HIERSEP_STR "\x1F" /* unit separator (US) */
#define DB_HIERSEP_CHAR DB_HIERSEP_STR[0]
#define DB_USER_PREFIX "user" DB_HIERSEP_STR
#define DB_VERSION_KEY DB_HIERSEP_STR "VER" DB_HIERSEP_STR
#define DB_VERSION_STR "2"
static mbname_t *mbname_from_dbname(const char *dbname);
static char *mbname_dbname(const mbname_t *mbname);
static char *mboxname_from_dbname(const char *dbname);
static char *mboxname_to_dbname(const char *intname);
cyrus_acl_canonproc_t mboxlist_ensureOwnerRights;
static struct db *mbdb;
static int mboxlist_dbopen = 0;
static int mboxlist_initialized = 0;
static int have_racl = 0;
static int mboxlist_opensubs(const char *userid, int create, struct db **ret);
static void mboxlist_closesubs(struct db *sub);
static int mboxlist_upgrade_subs(const char *userid, const char *subsfname, struct db **ret);
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);
former_name_t *histitem;
while ((histitem = ptrarray_pop(&mbentry->name_history))) {
free(histitem->name);
free(histitem);
}
ptrarray_fini(&mbentry->name_history);
free(mbentry);
*mbentryptr = NULL;
}
EXPORTED const char *mboxlist_mbtype_to_string(uint32_t mbtype)
{
static struct buf buf = BUF_INITIALIZER;
buf_reset(&buf);
/* mailbox types */
switch (mbtype_isa(mbtype)) {
case MBTYPE_EMAIL:
buf_putc(&buf, 'e');
break;
case MBTYPE_NETNEWS:
buf_putc(&buf, 'n');
break;
case MBTYPE_COLLECTION:
buf_putc(&buf, 'b');
break;
case MBTYPE_CALENDAR:
buf_putc(&buf, 'c');
break;
case MBTYPE_ADDRESSBOOK:
buf_putc(&buf, 'a');
break;
case MBTYPE_JMAPNOTIFY:
buf_putc(&buf, 'j');
break;
case MBTYPE_JMAPSUBMIT:
buf_putc(&buf, 's');
break;
case MBTYPE_JMAPPUSHSUB:
buf_putc(&buf, 'p');
break;
}
/* mailbox flags */
if (mbtype & MBTYPE_DELETED)
buf_putc(&buf, 'd');
if (mbtype & MBTYPE_MOVING)
buf_putc(&buf, 'm');
if (mbtype & MBTYPE_REMOTE)
buf_putc(&buf, 'r');
if (mbtype & MBTYPE_RESERVE)
buf_putc(&buf, 'z');
if (mbtype & MBTYPE_INTERMEDIATE)
buf_putc(&buf, 'i');
if (mbtype & MBTYPE_LEGACY_DIRS)
buf_putc(&buf, 'l');
/* make sure we didn't forget to set a character for every interesting bit */
assert(buf_len(&buf));
return buf_cstring(&buf);
}
static struct dlist *mboxlist_entry_dlist(const char *dbname,
const mbentry_t *mbentry)
{
struct dlist *dl = dlist_newkvlist(NULL, dbname);
dlist_setatom(dl, "T", mboxlist_mbtype_to_string(mbentry->mbtype));
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->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));
if (mbentry->acl)
dlist_stitch(dl, mailbox_acl_to_dlist(mbentry->acl));
return dl;
}
EXPORTED char *mbentry_metapath(const struct mboxlist_entry *mbentry, int metatype, int isnew)
{
uint32_t legacy_dirs = (mbentry->mbtype & MBTYPE_LEGACY_DIRS);
return mboxname_metapath(mbentry->partition,
mbentry->name,
legacy_dirs ? NULL : mbentry->uniqueid,
metatype,
isnew);
}
EXPORTED char *mbentry_datapath(const struct mboxlist_entry *mbentry, uint32_t uid)
{
uint32_t legacy_dirs = (mbentry->mbtype & MBTYPE_LEGACY_DIRS);
return mboxname_datapath(mbentry->partition,
mbentry->name,
legacy_dirs ? NULL : mbentry->uniqueid,
uid);
}
EXPORTED char *mbentry_archivepath(const struct mboxlist_entry *mbentry, uint32_t uid)
{
uint32_t legacy_dirs = (mbentry->mbtype & MBTYPE_LEGACY_DIRS);
return mboxname_archivepath(mbentry->partition,
mbentry->name,
legacy_dirs ? NULL : mbentry->uniqueid,
uid);
}
static void mboxlist_dbname_to_key(const char *dbname, size_t len,
const char *userid, struct buf *key)
{
buf_reset(key);
buf_putc(key, KEY_TYPE_NAME);
if (userid) {
mbname_t *mbname = mbname_from_userid(userid);
char *inbox = mbname_dbname(mbname);
size_t inboxlen = strlen(inbox);
if (len >= inboxlen && !strncmp(dbname, inbox, inboxlen)) {
buf_appendcstr(key, "INBOX");
dbname += inboxlen;
len -= inboxlen;
}
mbname_free(&mbname);
free(inbox);
}
buf_appendmap(key, dbname, len);
}
static void mboxlist_dbname_from_key(const char *key, size_t len,
const char *userid, struct buf *dbname)
{
if (userid && len >= 6 && !strncmp(key+1, "INBOX", 5)) {
mbname_t *mbname = mbname_from_userid(userid);
char *inbox = mbname_dbname(mbname);
buf_setcstr(dbname, inbox);
buf_appendmap(dbname, key+6, len-6);
mbname_free(&mbname);
free(inbox);
return;
}
buf_init_ro(dbname, key+1, len-1);
}
static void mboxlist_id_to_key(const char *id, struct buf *key)
{
buf_reset(key);
buf_putc(key, KEY_TYPE_ID);
buf_appendcstr(key, id);
}
/*
* read a single _N_ame record from the mailboxes.db and return a pointer to it
*/
static int mboxlist_read_name(const char *dbname,
const char **dataptr, size_t *datalenptr,
struct txn **tid, int wrlock)
{
struct buf key = BUF_INITIALIZER;
int namelen = strlen(dbname);
int r;
if (!namelen)
return IMAP_MAILBOX_NONEXISTENT;
mboxlist_dbname_to_key(dbname, namelen, NULL, &key);
if (wrlock) {
r = cyrusdb_fetchlock(mbdb, buf_base(&key), buf_len(&key),
dataptr, datalenptr, tid);
} else {
r = cyrusdb_fetch(mbdb, buf_base(&key), buf_len(&key),
dataptr, datalenptr, tid);
}
switch (r) {
case CYRUSDB_OK:
/* no entry required, just checking if it exists */
r = 0;
break;
case CYRUSDB_AGAIN:
r = IMAP_AGAIN;
break;
case CYRUSDB_NOTFOUND:
r = IMAP_MAILBOX_NONEXISTENT;
break;
default:
{
char *intname = mboxname_from_dbname(dbname);
xsyslog(LOG_ERR, "DBERROR: error fetching mboxlist",
"mailbox=<%s> error=<%s>",
intname, cyrusdb_strerror(r));
free(intname);
r = IMAP_IOERROR;
break;
}
}
buf_free(&key);
return r;
}
EXPORTED uint32_t mboxlist_string_to_mbtype(const char *string)
{
uint32_t mbtype = 0;
if (!string) return 0; /* null just means default */
/* mailbox type - ALWAYS first character */
switch (*string++) {
case 'a':
mbtype = MBTYPE_ADDRESSBOOK;
break;
case 'b':
mbtype = MBTYPE_COLLECTION;
break;
case 'c':
mbtype = MBTYPE_CALENDAR;
break;
case 'e':
mbtype = MBTYPE_EMAIL;
break;
case 'j':
mbtype = MBTYPE_JMAPNOTIFY;
break;
case 'n':
mbtype = MBTYPE_NETNEWS;
break;
case 'p':
mbtype = MBTYPE_JMAPPUSHSUB;
break;
case 's':
mbtype = MBTYPE_JMAPSUBMIT;
break;
default:
/* Assume this is a mailbox flag .
This should only happen for a legacy email entry with no 'e' */
string--;
break;
}
for (; *string; string++) {
/* mailbox flags */
switch (*string) {
case 'd':
mbtype |= MBTYPE_DELETED;
break;
case 'i':
mbtype |= MBTYPE_INTERMEDIATE;
break;
case 'l':
mbtype |= MBTYPE_LEGACY_DIRS;
break;
case 'm':
mbtype |= MBTYPE_MOVING;
break;
case 'r':
mbtype |= MBTYPE_REMOTE;
break;
case 'z':
mbtype |= MBTYPE_RESERVE;
break;
default:
/* make sure we didn't forget to handle every expected character */
assert(0);
break;
}
}
return mbtype;
}
struct parseentry_rock {
struct mboxlist_entry *mbentry;
struct buf *aclbuf;
int doingacl;
int doinghistory;
};
static int parseentry_cb(int type, struct dlistsax_data *d)
{
struct parseentry_rock *rock = (struct parseentry_rock *)d->rock;
const char *key = buf_cstring(&d->kbuf);
switch(type) {
case DLISTSAX_LISTSTART:
if (!strcmp(key, "H")) rock->doinghistory = 1;
break;
case DLISTSAX_LISTEND:
if (rock->doinghistory) rock->doinghistory = 0;
break;
case DLISTSAX_KVLISTSTART:
if (!strcmp(key, "A")) {
rock->doingacl = 1;
}
else if (rock->doinghistory) {
ptrarray_append(&rock->mbentry->name_history,
xzmalloc(sizeof(former_name_t)));
}
break;
case DLISTSAX_KVLISTEND:
if (rock->doingacl) 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 if (rock->doinghistory) {
former_name_t *histitem = ptrarray_tail(&rock->mbentry->name_history);
if (!strcmp(key, "F")) {
histitem->foldermodseq = atomodseq_t(d->data);
}
else if (!strcmp(key, "M")) {
histitem->mtime = atoi(d->data);
}
else if (!strcmp(key, "N")) {
histitem->name = mboxname_from_dbname(d->data);
}
}
else {
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, "N")) {
if (!rock->mbentry->name)
rock->mbentry->name = mboxname_from_dbname(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
* H: name_h_istory
* I: unique_i_d
* M: _m_time
* N: _n_ame
* P: _p_artition
* S: _s_erver
* T: _t_ype
* V: uid_v_alidity
*/
static 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();
char mboxname[MAX_MAILBOX_NAME+1];
if (!datalen)
goto done;
if (name) {
/* copy name */
snprintf(mboxname, sizeof(mboxname), "%.*s",
(int) (namelen ? namelen : strlen(name)), name);
mbentry->name = mboxname_from_dbname(mboxname);
}
/* 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 *dbname,
mbentry_t **mbentryptr,
struct txn **tid, int wrlock, int allow_all)
{
int r;
const char *data;
size_t datalen;
mbentry_t *entry = NULL;
init_internal();
r = mboxlist_read_name(dbname, &data, &datalen, tid, wrlock);
if (r) return r;
r = mboxlist_parse_entry(&entry, dbname, 0, data, datalen);
if (r) return r;
if (!allow_all) {
/* Ignore "reserved" entries, like they aren't there */
if (entry->mbtype & MBTYPE_RESERVE) {
r = IMAP_MAILBOX_RESERVED;
}
/* Ignore "deleted" entries, like they aren't there */
else if (entry->mbtype & MBTYPE_DELETED) {
r = IMAP_MAILBOX_NONEXISTENT;
}
/* Ignore "intermediate" entries, like they aren't there */
else if (entry->mbtype & MBTYPE_INTERMEDIATE) {
r = IMAP_MAILBOX_NONEXISTENT;
}
}
if (!r && mbentryptr) *mbentryptr = entry;
else mboxlist_entry_free(&entry);
return r;
}
/*
* Lookup 'name' in the mailbox list, ignoring reserved records
*/
EXPORTED int mboxlist_lookup(const char *name,
mbentry_t **entryptr, struct txn **tid)
{
char *dbname = mboxname_to_dbname(name);
int r = mboxlist_mylookup(dbname, entryptr, tid,
0/*wrlock*/, 0/*allow_all*/);
free(dbname);
return r;
}
EXPORTED int mboxlist_lookup_allow_all(const char *name,
mbentry_t **entryptr,
struct txn **tid)
{
char *dbname = mboxname_to_dbname(name);
int r = mboxlist_mylookup(dbname, entryptr, tid,
0/*wrlock*/, 1/*allow_all*/);
free(dbname);
return r;
}
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_mbe(mbentry, "/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;
}
/*
* read a single unique_I_d record from the mailboxes.db and return a pointer to it
*/
static int mboxlist_read_uniqueid(const char *uniqueid,
const char **dataptr, size_t *datalenptr,
struct txn **tid, int wrlock)
{
struct buf key = BUF_INITIALIZER;
int r;
if (!uniqueid)
return IMAP_MAILBOX_NONEXISTENT;
mboxlist_id_to_key(uniqueid, &key);
if (wrlock) {
r = cyrusdb_fetchlock(mbdb, buf_base(&key), buf_len(&key),
dataptr, datalenptr, tid);
} else {
r = cyrusdb_fetch(mbdb, buf_base(&key), buf_len(&key),
dataptr, datalenptr, tid);
}
switch (r) {
case CYRUSDB_OK:
/* no entry required, just checking if it exists */
r = 0;
break;
case CYRUSDB_AGAIN:
r = IMAP_AGAIN;
break;
case CYRUSDB_NOTFOUND:
r = IMAP_MAILBOX_NONEXISTENT;
break;
default:
syslog(LOG_ERR, "DBERROR: error fetching mboxlist %s: %s",
uniqueid, cyrusdb_strerror(r));
r = IMAP_IOERROR;
break;
}
buf_free(&key);
return r;
}
EXPORTED char *mboxlist_find_uniqueid(const char *uniqueid,
const char *userid __attribute__((unused)),
const struct auth_state *auth_state __attribute__((unused)))
{
int r;
const char *data;
size_t datalen;
mbentry_t *mbentry = NULL;
char *mbname = NULL;
init_internal();
r = mboxlist_read_uniqueid(uniqueid, &data, &datalen, NULL, 0);
if (r) return NULL;
r = mboxlist_parse_entry(&mbentry, NULL, 0, data, datalen);
if (r) return NULL;
// only note the name down if it's not deleted
if (!(mbentry->mbtype & MBTYPE_DELETED)) {
mbname = mbentry->name;
mbentry->name = NULL;
}
mboxlist_entry_free(&mbentry);
return mbname;
}
/*
* Lookup 'uniqueid' in the mailbox list, ignoring reserved records
*/
EXPORTED int mboxlist_lookup_by_uniqueid(const char *uniqueid,
mbentry_t **entryptr, struct txn **tid)
{
mbentry_t *entry = NULL;
const char *data;
size_t datalen;
int r;
init_internal();
r = mboxlist_read_uniqueid(uniqueid, &data, &datalen, tid, 0);
if (r) return r;
r = mboxlist_parse_entry(&entry, NULL, 0, data, datalen);
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;
}
if (entryptr) {
entry->uniqueid = xstrdup(uniqueid);
*entryptr = entry;
}
else mboxlist_entry_free(&entry);
return 0;
}
/* 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;
}
#define ACL_RECORDSEP_CHAR '\x1E' /* record separator (RS) */
static void mboxlist_racl_key(int isuser, const char *keyuser,
const char *dbname, struct buf *buf)
{
buf_reset(buf);
buf_putc(buf, KEY_TYPE_ACL);
buf_putc(buf, isuser ? 'U' : 'S');
buf_putc(buf, ACL_RECORDSEP_CHAR);
if (keyuser) {
buf_appendcstr(buf, keyuser);
buf_putc(buf, ACL_RECORDSEP_CHAR);
}
if (dbname) {
buf_appendcstr(buf, dbname);
}
}
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 with neither read nor lookup bit
if (!strpbrk(strarray_nth(aclbits, i+1), "lr")) 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 *dbname, const mbentry_t *oldmbentry,
const mbentry_t *newmbentry, struct txn **txn)
{
static strarray_t *admins = NULL;
struct buf buf = BUF_INITIALIZER;
strarray_t *oldusers = NULL;
strarray_t *newusers = NULL;
int i;
int r = 0;
mbname_t *mbname = mbname_from_dbname(dbname);
char *userid = xstrdupnull(mbname_userid(mbname));
mbname_free(&mbname);
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 (!strpbrk(strarray_nth(oldusers, i+1), "lr")) 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, dbname, &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 (!strpbrk(strarray_nth(newusers, i+1), "lr")) 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, dbname, &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 add_former_name(struct dlist *name_history, const char *name,
time_t mtime, modseq_t foldermodseq)
{
struct dlist *histitem = dlist_newkvlist(NULL, "");
char *dbname = mboxname_to_dbname(name);
dlist_setatom(histitem, "N", dbname);
dlist_setnum64(histitem, "F", foldermodseq);
dlist_setdate(histitem, "M", mtime);
dlist_push(name_history, histitem);
free(dbname);
}
static int mboxlist_update_entry(const char *name,
const mbentry_t *mbentry, struct txn **txn)
{
char *dbname = mboxname_to_dbname(name);
struct buf key = BUF_INITIALIZER;
mbentry_t *old = NULL;
int r = 0;
mboxlist_mylookup(dbname, &old, txn, 0, 1); // ignore errors, it will be NULL
if (have_racl) {
r = mboxlist_update_racl(dbname, old, mbentry, txn);
if (r) goto done;
}
mboxlist_dbname_to_key(dbname, strlen(dbname), NULL, &key);
if (mbentry) {
/* Create N record value */
struct dlist *dl = mboxlist_entry_dlist(dbname, mbentry);
struct buf mboxent = BUF_INITIALIZER;
dlist_printbuf(dl, 0, &mboxent);
r = cyrusdb_store(mbdb, buf_base(&key), buf_len(&key),
buf_cstring(&mboxent), buf_len(&mboxent), txn);
if (!r && mbentry->uniqueid &&
!(old && (old->mbtype & mbentry->mbtype & MBTYPE_DELETED))) {
mbentry_t *oldid = NULL;
struct dlist *name_history = dlist_newlist(NULL, "H");
/* Rebrand I field as N field for I record value */
struct dlist *c = dlist_getchild(dl, "I");
dlist_rename(c, "N");
dlist_makeatom(c, dbname);
mboxlist_lookup_by_uniqueid(mbentry->uniqueid, &oldid, txn);
if (oldid) {
/* Existing mailbox */
while (oldid->name_history.count) {
former_name_t *histitem = ptrarray_shift(&oldid->name_history);
add_former_name(name_history, histitem->name,
histitem->mtime, histitem->foldermodseq);
free(histitem->name);
free(histitem);
}
if (strcmp(name, oldid->name)) {
/* Renamed mailbox */
add_former_name(name_history, oldid->name,
time(NULL), oldid->foldermodseq);
}
mboxlist_entry_free(&oldid);
}
/* Add H field for I record value */
dlist_stitch(dl, name_history);
buf_reset(&mboxent);
dlist_printbuf(dl, 0, &mboxent);
mboxlist_id_to_key(mbentry->uniqueid, &key);
r = cyrusdb_store(mbdb, buf_base(&key), buf_len(&key),
buf_cstring(&mboxent), buf_len(&mboxent), txn);
}
dlist_free(&dl);
buf_free(&mboxent);
if (!r && config_auditlog && (!old || strcmpsafe(old->acl, mbentry->acl))) {
/* 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, buf_base(&key), buf_len(&key), txn, /*force*/1);
if (!r && old && old->uniqueid) {
mbentry_t *mbentry = NULL;
int r1 = mboxlist_lookup_by_uniqueid(old->uniqueid, &mbentry, txn);
if (!r1 && !strcmp(old->name, mbentry->name)) {
mboxlist_id_to_key(old->uniqueid, &key);
r = cyrusdb_delete(mbdb, buf_base(&key), buf_len(&key),
txn, /*force*/1);
}
mboxlist_entry_free(&mbentry);
}
}
done:
mboxlist_entry_free(&old);
buf_free(&key);
free(dbname);
return r;
}
EXPORTED int mboxlist_delete(const char *name)
{
return mboxlist_update_entry(name, NULL, NULL);
}
EXPORTED int mboxlist_update(const mbentry_t *mbentry, int localonly)
{
int r = 0, r2 = 0;
struct txn *tid = NULL;
init_internal();
r = mboxlist_update_entry(mbentry->name, mbentry, &tid);
/* 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));
}
if (!r)
mboxname_setmodseq(mbentry->name, mbentry->foldermodseq, mbentry->mbtype,
MBOXMODSEQ_ISFOLDER);
}
return r;
}
static void assert_namespacelocked(const char *mboxname)
{
char *userid = mboxname_to_userid(mboxname);
assert(user_isnamespacelocked(userid));
free(userid);
}
static int _findparent(mbname_t *mbname, mbentry_t **mbentryp, int allow_all)
{
mbentry_t *mbentry = NULL;
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;
return r;
}
EXPORTED int mboxlist_findparent(const char *mboxname,
mbentry_t **mbentryp)
{
mbname_t *mbname = mbname_from_intname(mboxname);
int r = _findparent(mbname, mbentryp, 0);
mbname_free(&mbname);
return r;
}
static int mboxlist_findusermbentry(const char *mboxname,
mbentry_t **mbentryp)
{
mbname_t *mbname = mbname_from_intname(mboxname);
int r = 0;
if (!mbname_userid(mbname)) {
// fall back to findparent if no user
r = _findparent(mbname, mbentryp, 0);
}
else {
// get the INBOX!
mbname_set_isdeleted(mbname, 0);
mbname_set_boxes(mbname, NULL);
r = mboxlist_lookup(mbname_intname(mbname), mbentryp, NULL);
}
mbname_free(&mbname);
return r;
}
EXPORTED int mboxlist_findparent_allow_all(const char *mboxname,
mbentry_t **mbentryp)
{
mbname_t *mbname = mbname_from_intname(mboxname);
int r = _findparent(mbname, mbentryp, 1);
mbname_free(&mbname);
return r;
}
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);
char *partition = NULL;
int r = 0;
/* not for deleted namespace */
if (mbname_isdeleted(mbname))
goto out;
/* 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);
char *dbname = mbname_dbname(mbname);
mboxlist_entry_free(&mbentry);
r = mboxlist_mylookup(dbname, &mbentry, NULL, 0, 1);
free(dbname);
if (r == IMAP_MAILBOX_NONEXISTENT) r = 0;
if (r) goto out;
/* we don't remove parents any more, so skip out immediately if we find an entry */
if (mbentry && !(mbentry->mbtype & MBTYPE_DELETED)) continue;
/* if there's no children, there's no need for intermediates */
if (!mboxlist_haschildren(mboxname))
continue;
syslog(LOG_NOTICE, "mboxlist: intermediate fill-in mailbox: %s", mboxname);
if (!partition) {
mboxlist_entry_free(&mbentry);
mboxlist_findparent_allow_all(mboxname, &mbentry);
partition = xstrdupnull(mbentry->partition);
}
mbentry_t newmbentry = MBENTRY_INITIALIZER;
newmbentry.name = (char *)mboxname;
newmbentry.partition = partition;
newmbentry.mbtype = mbtype;
newmbentry.createdmodseq = modseq;
newmbentry.foldermodseq = modseq;
int flags = MBOXLIST_CREATE_KEEP_INTERMEDIARIES; // avoid infinite looping!
r = mboxlist_createmailbox(&newmbentry, 0/*options*/, 0/*highestmodseq*/,
1/*isadmin*/, NULL/*userid*/, NULL/*authstate*/,
flags, NULL/*mailboxptr*/);
if (r) goto out;
}
out:
mboxlist_entry_free(&mbentry);
mbname_free(&mbname);
free(partition);
return r;
}
EXPORTED int mboxlist_promote_intermediary(const char *mboxname)
{
mbentry_t *mbentry = NULL, *parent = NULL;
struct mailbox *mailbox = NULL;
int r = 0;
assert_namespacelocked(mboxname);
r = mboxlist_lookup_allow_all(mboxname, &mbentry, NULL);
if (r || !(mbentry->mbtype & MBTYPE_INTERMEDIATE)) goto done;
r = mboxlist_findparent(mboxname, &parent);
if (r) goto done;
mbentry->mbtype |= (parent->mbtype & MBTYPE_LEGACY_DIRS);
xfree(mbentry->partition);
r = mboxlist_create_partition(mboxname, parent->partition,
&mbentry->partition);
if (r) goto done;
mbentry->mbtype &= ~MBTYPE_INTERMEDIATE;
xfree(mbentry->acl);
mbentry->acl = xstrdupnull(parent->acl);
r = mailbox_create(mboxname, mbentry->mbtype,
mbentry->partition, mbentry->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;
// make sure all the fields are up-to-date
xfree(mbentry->uniqueid);
mbentry->uniqueid = xstrdupnull(mailbox_uniqueid(mailbox));
mbentry->uidvalidity = mailbox->i.uidvalidity;
mbentry->createdmodseq = mailbox->i.createdmodseq;
mbentry->foldermodseq = mailbox->i.highestmodseq;
r = mboxlist_update_entry(mboxname, mbentry, NULL);
if (r) goto done;
done:
// XXX - cleanup on error?
mailbox_close(&mailbox);
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)
*
*/
EXPORTED int mboxlist_createmailbox(const mbentry_t *mbentry,
unsigned options, modseq_t highestmodseq,
unsigned isadmin, const char *userid,
const struct auth_state *auth_state,
unsigned flags, struct mailbox **mboxptr)
{
const char *mboxname = mbentry->name;
char *uniqueid = xstrdupnull(mbentry->uniqueid);
uint32_t mbtype = mbentry->mbtype;
uint32_t uidvalidity = mbentry->uidvalidity;
modseq_t createdmodseq = mbentry->createdmodseq;
modseq_t foldermodseq = mbentry->foldermodseq;
int r;
char *newpartition = NULL;
char *acl = NULL;
struct mailbox *newmailbox = NULL;
int isremote = mbtype & MBTYPE_REMOTE;
mbentry_t *usermbentry = NULL, *newmbentry = NULL;
r = mboxlist_create_namecheck(mboxname, userid, auth_state,
isadmin, (flags & MBOXLIST_CREATE_FORCEUSER));
if (r) goto done;
assert_namespacelocked(mboxname);
init_internal();
if (!(flags & MBOXLIST_CREATE_SYNC)) {
options |= config_getint(IMAPOPT_MAILBOX_DEFAULT_OPTIONS)
| OPT_POP3_NEW_UIDL;
/* check if a mailbox tombstone or intermediate record exists */
mbentry_t *oldmbentry = NULL;
r = mboxlist_lookup_allow_all(mboxname, &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 = xstrdupnull(oldmbentry->uniqueid);
createdmodseq = oldmbentry->createdmodseq;
}
}
mboxlist_entry_free(&oldmbentry);
}
if (mbentry->acl) {
acl = xstrdup(mbentry->acl);
}
else {
r = mboxlist_create_acl(mboxname, &acl);
if (r) goto done;
}
r = mboxlist_create_partition(mboxname, mbentry->partition, &newpartition);
if (r) goto done;
r = mboxlist_findusermbentry(mboxname, &usermbentry);
if (!r) {
mbtype |= (usermbentry->mbtype & MBTYPE_LEGACY_DIRS);
}
else if (r != IMAP_MAILBOX_NONEXISTENT) goto done;
else if (config_getswitch(IMAPOPT_MAILBOX_LEGACY_DIRS))
mbtype |= MBTYPE_LEGACY_DIRS;
if (!(flags & MBOXLIST_CREATE_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(mailbox_uniqueid(newmailbox));
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 && !(flags & MBOXLIST_CREATE_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 && !(flags & MBOXLIST_CREATE_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);
}
if (!r && (flags & MBOXLIST_CREATE_NOTIFY)) {
/* send a MailboxCreate event notification */
struct mboxevent *mboxevent = mboxevent_new(EVENT_MAILBOX_CREATE);
mboxevent_extract_mailbox(mboxevent, newmailbox);
mboxevent_set_access(mboxevent, NULL, NULL, userid, mailbox_name(newmailbox), 1);
mboxevent_notify(&mboxevent);
mboxevent_free(&mboxevent);
}
done:
if (newmailbox) {
if (r) mailbox_delete(&newmailbox);
else if (mboxptr) *mboxptr = newmailbox;
else mailbox_close(&newmailbox);
}
free(acl);
free(newpartition);
free(uniqueid);
mboxlist_entry_free(&newmbentry);
mboxlist_entry_free(&usermbentry);
return r;
}
EXPORTED int mboxlist_createmailboxlock(const mbentry_t *mbentry,
unsigned options, modseq_t highestmodseq,
unsigned isadmin, const char *userid,
const struct auth_state *auth_state,
unsigned flags, struct mailbox **mboxptr)
{
struct mboxlock *namespacelock = mboxname_usernamespacelock(mbentry->name);
int r = mboxlist_createmailbox(mbentry, options, highestmodseq,
isadmin, userid, auth_state,
flags, mboxptr);
mboxname_release(&namespacelock);
return r;
}
/* 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;
char *dbname = mboxname_to_dbname(name);
if(in_tid) {
tid = in_tid;
} else {
tid = &lcl_tid;
}
retry:
r = mboxlist_mylookup(dbname, &mbentry, tid, 1, 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:
free(dbname);
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(mailbox));
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;
}
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(mailbox));
mboxevent_extract_mailbox(mboxevent, mailbox);
mboxevent_set_access(mboxevent, NULL, NULL, userid, mailbox_name(mailbox), 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->mbtype & MBTYPE_LEGACY_DIRS ? NULL : mailbox_uniqueid(oldmailbox));
if (r) goto done;
newmbentry = mboxlist_entry_create();
newmbentry->mbtype = mailbox_mbtype(oldmailbox);
newmbentry->partition = xstrdupnull(newpartition);
newmbentry->acl = xstrdupnull(mailbox_acl(oldmailbox));
newmbentry->uidvalidity = oldmailbox->i.uidvalidity;
newmbentry->uniqueid = xstrdupnull(mailbox_uniqueid(oldmailbox));
newmbentry->createdmodseq = oldmailbox->i.createdmodseq;
newmbentry->foldermodseq = silent ? mailbox_foldermodseq(oldmailbox)
: mboxname_nextmodseq(newname, mailbox_foldermodseq(oldmailbox),
mailbox_mbtype(oldmailbox), 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;
if (isusermbox || (mailbox_mbtype(oldmailbox) & MBTYPE_LEGACY_DIRS)) {
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;
/* create new entry */
newmbentry = mboxlist_entry_create();
newmbentry->name = xstrdupnull(mailbox_name(newmailbox));
newmbentry->mbtype = mailbox_mbtype(newmailbox);
newmbentry->partition = xstrdupnull(mailbox_partition(newmailbox));
newmbentry->acl = xstrdupnull(mailbox_acl(newmailbox));
newmbentry->uidvalidity = newmailbox->i.uidvalidity;
newmbentry->uniqueid = xstrdupnull(mailbox_uniqueid(newmailbox));
newmbentry->createdmodseq = newmailbox->i.createdmodseq;
newmbentry->foldermodseq = newmailbox->i.highestmodseq;
}
else {
/* Rename the mailbox metadata */
r = mailbox_rename_nocopy(oldmailbox, newname, silent);
if (r) goto done;
/* rewrite entry with new name */
newmbentry = mboxlist_entry_create();
newmbentry->name = xstrdupnull(newname);
newmbentry->mbtype = mailbox_mbtype(oldmailbox);
newmbentry->partition = xstrdupnull(mailbox_partition(oldmailbox));
newmbentry->acl = xstrdupnull(mailbox_acl(oldmailbox));
newmbentry->uidvalidity = oldmailbox->i.uidvalidity;
newmbentry->uniqueid = xstrdupnull(mailbox_uniqueid(oldmailbox));
newmbentry->createdmodseq = oldmailbox->i.createdmodseq;
newmbentry->foldermodseq = oldmailbox->i.highestmodseq;
}
syslog(LOG_INFO, "Rename: %s -> %s", oldname, newname);
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 = mbentry->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,
(mailbox_mbtype(oldmailbox) & MBTYPE_LEGACY_DIRS) ?
NULL : mailbox_uniqueid(oldmailbox));
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, mailbox_name(newmailbox), 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 = xstrdupnull(mailbox_partition(oldmailbox));
char *olduniqueid = (mailbox_mbtype(oldmailbox) & MBTYPE_LEGACY_DIRS) ?
NULL : xstrdup(mailbox_uniqueid(oldmailbox));
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: partitionmove sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s> oldpart=<%s> newpart=<%s>",
session_id(),
mailbox_name(oldmailbox), mailbox_uniqueid(oldmailbox),
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 {
/* simple rename */
/* prepare the event notification */
if (mboxevent) {
/* case of delayed delete */
if (mboxevent->type == EVENT_MAILBOX_DELETE)
mboxevent_extract_mailbox(mboxevent, oldmailbox);
else {
/* New maibox is the same as old, except for the name */
oldname = mailbox_name(oldmailbox);
oldmailbox->name = (char *) newname;
mboxevent_extract_mailbox(mboxevent, oldmailbox);
oldmailbox->name = (char *) oldname;
mboxevent_extract_old_mailbox(mboxevent, oldmailbox);
}
mboxevent_set_access(mboxevent, NULL, NULL, userid, newname, 1);
}
#ifdef WITH_DAV
/* Remove DAV DB records for a delayed delete mailbox */
if (mboxname_isdeletedmailbox(newname, NULL)) {
mailbox_delete_dav(oldmailbox);
}
#endif
/* 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);
mailbox_close(&oldmailbox);
}
}
/* 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 *idname = mbname_from_userid(identifier);
/* XXX - enforce cross domain restrictions */
identifier = mbname_userid(idname);
char *dbname = mboxname_to_dbname(name);
/* 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(dbname, &mbentry, &tid, 1, 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(dbname, &mbentry, &tid, 1, 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;
}
}
/* 4. 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;
}
/* 5. 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);
/* 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(mailbox), 0);
mboxevent_notify(&mboxevent);
mboxevent_free(&mboxevent);
}
/* 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(&idname);
free(dbname);
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;
char *dbname = mboxname_to_dbname(name);
init_internal();
/* 1. Start Transaction */
/* lookup the mailbox to make sure it exists and get its acl */
do {
r = mboxlist_mylookup(dbname, &mbentry, &tid, 1, 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:
free(dbname);
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;
struct buf dbname = BUF_INITIALIZER;
int i;
/* skip any non-name keys */
if (key[0] != KEY_TYPE_NAME) return 0;
mboxlist_dbname_from_key(key, keylen,
rock->issubs ? rock->userid : NULL, &dbname);
assert(!rock->mbname);
rock->mbname = mbname_from_dbname(buf_cstring(&dbname));
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,
buf_cstring(&dbname), buf_len(&dbname),
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 ||
!strcmpsafe(rock->userid, mbname_userid(rock->mbname))) 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:
buf_free(&dbname);
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);
buf_free(&dbname);
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) {
char *dbname = mbname_dbname(rock->mbname);
r = mboxlist_mylookup(dbname, &rock->mbentry, NULL, 0, 0);
free(dbname);
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) {
struct buf dbname = BUF_INITIALIZER;
mboxlist_dbname_from_key(key, keylen, NULL, &dbname);
int r = mboxlist_parse_entry(&mbrock->mbentry,
buf_base(&dbname), buf_len(&dbname),
data, datalen);
buf_free(&dbname);
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;
struct buf dbname = BUF_INITIALIZER;
int r;
/* skip any non-name keys */
if (!(keylen && key[0] == KEY_TYPE_NAME)) return 0;
/* free previous record */
mboxlist_entry_free(&mbrock->mbentry);
mboxlist_dbname_from_key(key, keylen, NULL, &dbname);
r = mboxlist_parse_entry(&mbrock->mbentry,
buf_base(&dbname), buf_len(&dbname),
data, datalen);
buf_free(&dbname);
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 };
struct buf key = BUF_INITIALIZER;
char *freeme = NULL;
int r = 0;
init_internal();
if (!prefix || !*prefix) prefix = "";
else {
mbname_t *mbname = mbname_from_intname(prefix);
if (prefix[strlen(prefix)-1] == '.') {
/* A mailbox pattern ending in the hierarchy separator */
mbname_push_boxes(mbname, "");
}
prefix = freeme = mbname_dbname(mbname);
mbname_free(&mbname);
}
mboxlist_dbname_to_key(prefix, strlen(prefix), NULL, &key);
r = cyrusdb_foreach(mbdb, buf_base(&key), buf_len(&key),
allmbox_p, allmbox_cb, &mbrock, 0);
mboxlist_entry_free(&mbrock.mbentry);
buf_free(&key);
free(freeme);
return r;
}
EXPORTED int mboxlist_mboxtree(const char *mboxname, mboxlist_cb *proc, void *rock, int flags)
{
struct allmb_rock mbrock = { NULL, proc, rock, flags };
char *dbname = mboxname_to_dbname(mboxname);
struct buf key = BUF_INITIALIZER;
int r = 0;
init_internal();
if (!(flags & MBOXTREE_SKIP_ROOT)) {
mboxlist_dbname_to_key(dbname, strlen(dbname), NULL, &key);
r = cyrusdb_forone(mbdb, buf_base(&key), buf_len(&key),
allmbox_p, allmbox_cb, &mbrock, 0);
if (r) goto done;
}
if (!(flags & MBOXTREE_SKIP_CHILDREN)) {
char *prefix = strconcat(dbname, DB_HIERSEP_STR, (char *)NULL);
mboxlist_dbname_to_key(prefix, strlen(prefix), NULL, &key);
r = cyrusdb_foreach(mbdb, buf_base(&key), buf_len(&key),
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(dbname, DB_DOMAINSEP_CHAR);
const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX);
if (p) {
buf_printf(&buf, "%.*s%c%s%c%s%c",
(int)(p-dbname), dbname, DB_DOMAINSEP_CHAR,
dp, DB_HIERSEP_CHAR, p+1, DB_HIERSEP_CHAR);
}
else {
buf_printf(&buf, "%s%c%s%c",
dp, DB_HIERSEP_CHAR, dbname, DB_HIERSEP_CHAR);
}
const char *prefix = buf_cstring(&buf);
mboxlist_dbname_to_key(prefix, strlen(prefix), NULL, &key);
r = cyrusdb_foreach(mbdb, buf_base(&key), buf_len(&key),
allmbox_p, allmbox_cb, &mbrock, 0);
buf_free(&buf);
if (r) goto done;
}
done:
mboxlist_entry_free(&mbrock.mbentry);
buf_free(&key);
free(dbname);
return r;
}
static int racls_del_cb(void *rock,
const char *key, size_t keylen,
const char *data __attribute__((unused)),
size_t datalen __attribute__((unused)))
{
struct txn **txn = (struct txn **)rock;
return cyrusdb_delete(mbdb, key, keylen, txn, /*force*/0);
}
static int racls_add_cb(const mbentry_t *mbentry, void *rock)
{
struct txn **txn = (struct txn **)rock;
char *dbname = mboxname_to_dbname(mbentry->name);
int r = mboxlist_update_racl(dbname, NULL, mbentry, txn);
free(dbname);
return r;
}
EXPORTED int mboxlist_set_racls(int enabled)
{
struct buf key = BUF_INITIALIZER;
struct txn *tid = NULL;
int r = 0;
int modified_mbdb = 0;
mboxlist_racl_key(0, NULL, NULL, &key);
init_internal();
if (have_racl && !enabled) {
syslog(LOG_NOTICE, "removing reverse acl support");
/* remove */
r = cyrusdb_foreach(mbdb, buf_base(&key), buf_len(&key),
NULL, racls_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 };
/* 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, buf_base(&key), buf_len(&key), "", 0, &tid);
if (!r) have_racl = 1;
}
buf_free(&key);
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_raw);
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 };
struct buf key = BUF_INITIALIZER;
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);
mboxlist_dbname_to_key(mboxname, strlen(mboxname), NULL, &key);
r = cyrusdb_forone(mbdb, buf_base(&key), buf_len(&key),
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);
mboxlist_dbname_to_key(mboxname, strlen(mboxname), NULL, &key);
r = cyrusdb_forone(mbdb, buf_base(&key), buf_len(&key),
allmbox_p, allmbox_cb, &mbrock, 0);
}
buf_free(&key);
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)
{
struct buf key = BUF_INITIALIZER;
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 *dbname = strarray_nth(&matches, i);
mboxlist_dbname_to_key(dbname, strlen(dbname), NULL, &key);
r = cyrusdb_forone(rock->db, buf_base(&key), buf_len(&key),
&find_p, &find_cb, rock, NULL);
}
strarray_fini(&matches);
}
else {
mboxlist_dbname_to_key(prefix, len,
rock->issubs ? rock->userid : NULL, &key);
r = cyrusdb_foreach(rock->db, buf_base(&key), buf_len(&key),
&find_p, &find_cb, rock, NULL);
}
if (r == CYRUSDB_DONE) r = 0;
buf_free(&key);
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;
struct buf key = BUF_INITIALIZER;
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%c", p+1, DB_DOMAINSEP_CHAR);
}
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) {
if (domainlen)
snprintf(inbox, sizeof(inbox), "%s%c",
userid+userlen+1, DB_DOMAINSEP_CHAR);
snprintf(inbox+domainlen, sizeof(inbox)-domainlen,
"%s%.*s%cINBOX%c", DB_USER_PREFIX,
(int)userlen, userid, DB_HIERSEP_CHAR, DB_HIERSEP_CHAR);
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 (c == rock->namespace->hier_sep) c = DB_HIERSEP_CHAR;
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;
mboxlist_dbname_to_key(inbox, inboxlen,
rock->issubs ? userid : NULL, &key);
r = cyrusdb_forone(rock->db, buf_base(&key), buf_len(&key),
&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;
mboxlist_dbname_to_key(inbox, inboxlen+7,
rock->issubs ? userid : NULL, &key);
r = cyrusdb_foreach(rock->db, buf_base(&key), buf_len(&key),
&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;
mboxlist_dbname_to_key(inbox, inboxlen+1,
rock->issubs ? userid : NULL, &key);
r = cyrusdb_foreach(rock->db, buf_base(&key), buf_len(&key),
&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 */
mboxlist_dbname_to_key(inbox, inboxlen+6,
rock->issubs ? userid : NULL, &key);
r = cyrusdb_forone(rock->db, buf_base(&key), buf_len(&key),
&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;
mboxlist_dbname_to_key(inbox, inboxlen+1,
rock->issubs ? userid : NULL, &key);
r = cyrusdb_foreach(rock->db, buf_base(&key), buf_len(&key),
&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, DB_USER_PREFIX, sizeof(domainpat)-domainlen);
}
else {
/* just those in this prefix */
strlcpy(domainpat+domainlen, DB_USER_PREFIX, 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) {
/* inboxname to deleted */
char prefix[MAX_MAILBOX_BUFFER];
const char *deletedprefix = config_getstring(IMAPOPT_DELETEDPREFIX);
snprintf(prefix, MAX_MAILBOX_BUFFER, "%.*s%s%c%.*s",
(int) domainlen, inbox, deletedprefix, DB_HIERSEP_CHAR,
(int) (inboxlen - domainlen), inbox+domainlen);
size_t prefixlen = strlen(prefix);
prefix[prefixlen] = DB_HIERSEP_CHAR;
rock->mb_category = MBNAME_OWNERDELETED;
/* reset the namebuffer */
if (rock->cb)
r = (*rock->cb)(NULL, rock->procrock);
if (r) goto done;
mboxlist_dbname_to_key(prefix, prefixlen+1,
rock->issubs ? userid : NULL, &key);
r = cyrusdb_foreach(rock->db, buf_base(&key), buf_len(&key),
&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);
buf_free(&key);
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;
}
struct buf key = BUF_INITIALIZER;
mbname_t *mbname = mbname_from_intname(intname);
char *dbname = mbname_dbname(mbname);
glob *g = glob_init(mbname_extname(mbname, namespace, userid),
namespace->hier_sep);
ptrarray_append(&cbrock.globs, g);
mbname_free(&mbname);
mboxlist_dbname_to_key(dbname, strlen(dbname), NULL, &key);
r = cyrusdb_forone(cbrock.db, buf_base(&key), buf_len(&key),
&find_p, &find_cb, &cbrock, NULL);
buf_free(&key);
free(dbname);
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_lookup_allow_all(name, &mbentry, NULL);
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 'rock'
*/
static int mboxlist_changequota(const mbentry_t *mbentry, void *rock)
{
int r = 0;
struct mailbox *mailbox = NULL;
struct changequota_rock *crock = rock;
assert(crock->root);
r = mailbox_open_iwl(mbentry->name, &mailbox);
if (!r) r = mailbox_changequotaroot(mailbox, crock->root, crock->silent);
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 char *mboxlist_fname(void)
{
const char *fname = config_getstring(IMAPOPT_MBOXLIST_DB_PATH);
if (fname) return xstrdup(fname);
return strconcat(config_dir, FNAME_MBOXLIST, (char *)NULL);
}
EXPORTED void mboxlist_open(const char *fname)
{
int ret, flags;
char *tofree = NULL;
/* create db file name */
if (!fname) {
tofree = mboxlist_fname();
fname = tofree;
}
mboxlist_init(0);
flags = CYRUSDB_CREATE;
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;
struct buf key = BUF_INITIALIZER;
mboxlist_racl_key(0, NULL, NULL, &key);
have_racl = !cyrusdb_fetch(mbdb, buf_base(&key), buf_len(&key), NULL, NULL, NULL);
buf_free(&key);
}
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,
int create,
struct db **ret)
{
int r = 0;
char *subsfname = user_hash_subs(userid);
int db_r = cyrusdb_open(SUBDB, subsfname, /*flags*/0, ret);
if (db_r == CYRUSDB_OK) {
r = mboxlist_upgrade_subs(userid, subsfname, ret);
}
else if (create) {
db_r = cyrusdb_open(SUBDB, subsfname, CYRUSDB_CREATE, ret);
if (db_r == CYRUSDB_OK) {
// set the version key
const char *key = DB_VERSION_KEY;
size_t keylen = strlen(key);
const char *data = DB_VERSION_STR;
size_t datalen = strlen(data);
db_r = cyrusdb_store(*ret, key, keylen, data, datalen, NULL);
}
if (db_r != CYRUSDB_OK)
r = IMAP_IOERROR;
}
else {
r = IMAP_NOTFOUND;
}
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, /*create*/0, &subs);
if (r) return (r == IMAP_NOTFOUND ? 0 : 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;
}
struct subsadd_rock {
const char *userid;
strarray_t *list;
};
static int subsadd_cb(void *rock, const char *key, size_t keylen,
const char *val __attribute__((unused)),
size_t vallen __attribute__((unused)))
{
struct subsadd_rock *srock = (struct subsadd_rock *) rock;
struct buf dbname = BUF_INITIALIZER;
mboxlist_dbname_from_key(key, keylen, srock->userid, &dbname);
strarray_appendm(srock->list, mboxname_from_dbname(buf_cstring(&dbname)));
buf_free(&dbname);
return 0;
}
EXPORTED strarray_t *mboxlist_sublist(const char *userid)
{
struct buf key = BUF_INITIALIZER;
struct db *subs = NULL;
strarray_t *list = strarray_new();
struct subsadd_rock rock = { userid, list };
int r;
init_internal();
/* open subs DB */
r = mboxlist_opensubs(userid, /*create*/0, &subs);
if (r) goto done;
/* faster to do it all in a single slurp! */
mboxlist_dbname_to_key("", 0, NULL, &key);
r = cyrusdb_foreach(subs, buf_base(&key), buf_len(&key),
NULL, subsadd_cb, &rock, 0);
mboxlist_closesubs(subs);
done:
buf_free(&key);
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;
struct buf dbname = BUF_INITIALIZER;
mbname_t *mbname = NULL;
int r;
/* free previous record */
mboxlist_entry_free(&mbrock->mbentry);
mboxlist_dbname_from_key(key, keylen, mbrock->userid, &dbname);
mbname = mbname_from_dbname(buf_cstring(&dbname));
if ((mbrock->flags & MBOXTREE_SKIP_PERSONAL) &&
!strcmpsafe(mbrock->userid, mbname_userid(mbname))) {
r = 0;
goto done;
}
r = mboxlist_mylookup(buf_cstring(&dbname), &mbrock->mbentry, NULL, 0, 0);
if (r == IMAP_MAILBOX_NONEXISTENT) {
r = 0;
goto done;
}
if (r) {
syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s",
mbname_intname(mbname), error_message(r));
goto done;
}
r = mbrock->proc(mbrock->mbentry, mbrock->rock);
done:
mbname_free(&mbname);
buf_free(&dbname);
return r;
}
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 };
struct buf key = BUF_INITIALIZER;
int r = 0;
init_internal();
/* open subs DB */
r = mboxlist_opensubs(userid, /*create*/0, &subs);
if (r) return (r == IMAP_NOTFOUND ? 0 : r);
/* faster to do it all in a single slurp! */
mboxlist_dbname_to_key("", 0, NULL, &key);
r = cyrusdb_foreach(subs, buf_base(&key), buf_len(&key),
NULL, usersubs_cb, &mbrock, 0);
mboxlist_entry_free(&mbrock.mbentry);
mboxlist_closesubs(subs);
buf_free(&key);
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, /*create*/0, &subs);
if (r) return (r == IMAP_NOTFOUND ? CYRUSDB_NOTFOUND : r);
if (!r) {
struct buf key = BUF_INITIALIZER;
char *dbname = mboxname_to_dbname(name);
mboxlist_dbname_to_key(dbname, strlen(dbname), userid, &key);
free(dbname);
r = cyrusdb_fetch(subs, buf_base(&key), buf_len(&key),
&val, &vallen, NULL);
buf_free(&key);
}
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)
{
struct buf key = BUF_INITIALIZER;
mbentry_t *mbentry = NULL;
int r;
struct db *subs;
struct mboxevent *mboxevent;
init_internal();
if ((r = mboxlist_opensubs(userid, add, &subs)) != 0) {
return (add || r != IMAP_NOTFOUND) ? r : 0;
}
char *dbname = mboxname_to_dbname(name);
if (add && !force) {
/* Ensure mailbox exists and can be seen by user */
if ((r = mboxlist_mylookup(dbname, &mbentry, NULL, 0, 0))!=0) {
mboxlist_closesubs(subs);
goto done;
}
if ((cyrus_acl_myrights(auth_state, mbentry->acl) & ACL_LOOKUP) == 0) {
mboxlist_closesubs(subs);
mboxlist_entry_free(&mbentry);
r = IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
mboxlist_dbname_to_key(dbname, strlen(dbname), userid, &key);
if (add) {
r = cyrusdb_store(subs, buf_base(&key), buf_len(&key), "", 0, NULL);
} else {
r = cyrusdb_delete(subs, buf_base(&key), buf_len(&key), 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);
buf_free(&key);
/* 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);
}
done:
free(dbname);
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);
}
/* Handlers for mailboxes.db names */
static mbname_t *mbname_from_dbname(const char *dbname)
{
mbname_t *mbname = mbname_from_userid(NULL); // allocate empty mbname
const char *p;
if (!dbname || !*dbname) return mbname;
const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX);
p = strchr(dbname, DB_DOMAINSEP_CHAR);
if (p) {
char domain[MAX_MAILBOX_NAME];
snprintf(domain, sizeof(domain), "%.*s", (int) (p - dbname), dbname);
mbname_set_domain(mbname, domain);
dbname = p+1;
}
strarray_t *boxes = strarray_split(dbname, DB_HIERSEP_STR, 0);
if (strarray_size(boxes) > 2 && !strcmpsafe(strarray_nth(boxes, 0), dp)) {
free(strarray_shift(boxes));
char *delval = strarray_pop(boxes);
mbname_set_isdeleted(mbname, strtoul(delval, NULL, 16));
free(delval);
}
if (strarray_size(boxes) > 1 && !strcmpsafe(strarray_nth(boxes, 0), "user")) {
free(strarray_shift(boxes));
char *localpart = strarray_shift(boxes);
mbname_set_localpart(mbname, localpart);
free(localpart);
}
mbname_set_boxes(mbname, boxes);
strarray_free(boxes);
return mbname;
}
/* all mailboxes have a database name representation, so this
* function should never return a NULL.
*/
static char *mbname_dbname(const mbname_t *mbname)
{
struct buf buf = BUF_INITIALIZER;
int sep = 0;
int i;
const char *domain = mbname_domain(mbname);
if (domain) {
buf_appendcstr(&buf, domain);
buf_putc(&buf, DB_DOMAINSEP_CHAR);
}
time_t is_deleted = mbname_isdeleted(mbname);
if (is_deleted) {
buf_appendcstr(&buf, config_getstring(IMAPOPT_DELETEDPREFIX));
sep = 1;
}
const char *localpart = mbname_localpart(mbname);
if (localpart) {
if (sep) buf_putc(&buf, DB_HIERSEP_CHAR);
buf_appendcstr(&buf, DB_USER_PREFIX);
buf_appendcstr(&buf, localpart);
sep = 1;
}
const strarray_t *boxes = mbname_boxes(mbname);
for (i = 0; i < strarray_size(boxes); i++) {
if (sep) buf_putc(&buf, DB_HIERSEP_CHAR);
buf_appendcstr(&buf, strarray_nth(boxes, i));
sep = 1;
}
if (is_deleted) {
if (sep) buf_putc(&buf, DB_HIERSEP_CHAR);
buf_printf(&buf, "%X", (unsigned) is_deleted);
sep = 1;
}
return buf_release(&buf);
}
static char *mboxname_from_dbname(const char *dbname)
{
mbname_t *mbname = mbname_from_dbname(dbname);
char *res = xstrdupnull(mbname_intname(mbname));
mbname_free(&mbname);
return res;
}
static char *mboxname_to_dbname(const char *intname)
{
mbname_t *mbname = mbname_from_intname(intname);
char *res = mbname_dbname(mbname);
mbname_free(&mbname);
return res;
}
static int _check_rec_cb(void *rock,
const char *key, size_t keylen,
const char *data, size_t datalen)
{
int *do_upgrade = (int *) rock;
int r = CYRUSDB_OK;
if (!keylen) return r;
switch (key[0]) {
case '$':
/* Verify that we have a $RACL$ or $RUNQ$ record */
if (keylen >= 6 &&
(!strncmp(key, "$RACL$", 6) || !strncmp(key, "$RUNQ$", 6))) {
*do_upgrade = 1;
r = CYRUSDB_DONE;
}
break;
case KEY_TYPE_ACL: {
/* Verify that we have a valid A record */
struct buf aclkey = BUF_INITIALIZER;
mboxlist_racl_key(0, NULL, NULL, &aclkey);
if (keylen >= buf_len(&aclkey) &&
!strncmp(key, buf_cstring(&aclkey), buf_len(&aclkey))) {
*do_upgrade = 0;
r = CYRUSDB_DONE;
}
break;
}
case KEY_TYPE_ID: {
/* Verify that we have a valid I record */
mbentry_t *mbentry = NULL;
r = mboxlist_parse_entry(&mbentry, NULL, 0, data, datalen);
if (!r) {
*do_upgrade = (mbentry->name == NULL);
mboxlist_entry_free(&mbentry);
r = CYRUSDB_DONE;
}
break;
}
}
return r;
}
struct upgrade_rock {
const char *userid;
struct buf *namebuf;
struct db *db;
struct txn **tid;
hash_table *ids;
int *r;
};
static int _foreach_cb(void *rock,
const char *key, size_t keylen,
const char *data, size_t datalen)
{
struct upgrade_rock *urock = (struct upgrade_rock *) rock;
mbentry_t *mbentry = NULL;
int r;
/* skip $RACL and $RUNQ keys */
if (keylen >= 5 &&
(!strncmp(key, "$RACL", 5) || !strncmp(key, "$RUNQ", 5))) {
return CYRUSDB_OK;
}
r = mboxlist_parse_entry(&mbentry, NULL, 0, data, datalen);
if (r) return r;
mbentry->name = xstrndup(key, keylen);
mbentry->mbtype |= MBTYPE_LEGACY_DIRS;
int idx = 0;
ptrarray_t *pa = hash_lookup(mbentry->uniqueid, urock->ids);
if (!pa) {
pa = ptrarray_new();
hash_insert(mbentry->uniqueid, pa, urock->ids);
}
else if (!(mbentry->mbtype & MBTYPE_DELETED)) {
idx = ptrarray_size(pa);
}
else {
/* Determine where to insert this entry in the list (sorted by modseq) */
int n = ptrarray_size(pa);
mbentry_t *this;
do {
this = (mbentry_t *) ptrarray_nth(pa, idx);
} while ((mbentry->foldermodseq > this->foldermodseq) && (++idx < n));
}
ptrarray_insert(pa, idx, mbentry);
return r;
}
static void _upgrade_cb(const char *key __attribute__((unused)),
void *data, void *rock)
{
struct upgrade_rock *urock = (struct upgrade_rock *) rock;
ptrarray_t *pa = (ptrarray_t *) data;
int idx, n = ptrarray_size(pa);
for (idx = 0; idx < n; idx++) {
mbentry_t *mbentry = (mbentry_t *) ptrarray_nth(pa, idx);
if (!*urock->r)
*urock->r = mboxlist_update_entry(mbentry->name, mbentry, urock->tid);
mboxlist_entry_free(&mbentry);
}
ptrarray_free(pa);
}
EXPORTED int mboxlist_upgrade(int *upgraded)
{
int r, r2 = 0, do_upgrade = 0;
struct buf buf = BUF_INITIALIZER;
struct db *backup = NULL;
struct txn *tid = NULL;
hash_table ids = HASH_TABLE_INITIALIZER;
struct upgrade_rock urock = { NULL, NULL, NULL, &tid, &ids, &r };
char *fname;
if (upgraded) *upgraded = 0;
/* check if we need to upgrade */
mboxlist_open(NULL);
r = cyrusdb_foreach(mbdb, "", 0, NULL, _check_rec_cb, &do_upgrade, NULL);
mboxlist_close();
if (r != CYRUSDB_DONE) return r;
else if (!do_upgrade) return 0;
/* create db file names */
fname = mboxlist_fname();
buf_setcstr(&buf, fname);
buf_appendcstr(&buf, ".OLD");
/* rename db file to backup */
r = rename(fname, buf_cstring(&buf));
free(fname);
if (r) goto done;
/* open backup db file */
r = cyrusdb_open(DB, buf_cstring(&buf), 0, &backup);
if (r) {
syslog(LOG_ERR, "DBERROR: opening %s: %s", buf_cstring(&buf),
cyrusdb_strerror(r));
fatal("can't open mailboxes file", EX_TEMPFAIL);
}
/* open a new db file */
mboxlist_open(NULL);
/* perform upgrade from backup to new db */
construct_hash_table(&ids, 4096, 0);
r = cyrusdb_foreach(backup, "", 0, NULL, _foreach_cb, &urock, NULL);
r2 = cyrusdb_close(backup);
if (r2) {
syslog(LOG_ERR, "DBERROR: error closing %s: %s", buf_cstring(&buf),
cyrusdb_strerror(r2));
}
hash_enumerate(&ids, &_upgrade_cb, &urock);
free_hash_table(&ids, NULL);
/* complete txn on new db */
if (tid) {
if (r) {
r2 = mboxlist_abort(tid);
} else {
r2 = mboxlist_commit(tid);
}
if (r2) {
syslog(LOG_ERR, "DBERROR: error %s txn in mboxlist_upgrade: %s",
r ? "aborting" : "committing", cyrusdb_strerror(r2));
}
}
mboxlist_close();
if (!r && upgraded) *upgraded = 1;
done:
buf_free(&buf);
return r;
}
static int _upgrade_subs_cb(void *rock, const char *key, size_t keylen,
const char *data, size_t datalen)
{
struct upgrade_rock *urock = (struct upgrade_rock *) rock;
struct buf *namebuf = urock->namebuf;
char *dbname = NULL;
buf_setmap(namebuf, key, keylen);
dbname = mboxname_to_dbname(buf_cstring(namebuf));
mboxlist_dbname_to_key(dbname, strlen(dbname), urock->userid, namebuf);
free(dbname);
const char *newkey = buf_base(namebuf);
size_t newkeylen = buf_len(namebuf);
return cyrusdb_store(urock->db, newkey, newkeylen, data, datalen, urock->tid);
}
static int mboxlist_upgrade_subs_work(const char *userid, const char *subsfname, struct db **subs)
{
int db_r = 0;
int r2 = 0;
char *newsubsfname = NULL;
struct buf buf = BUF_INITIALIZER;
struct db *oldsubs = *subs;
struct db *newsubs = NULL;
struct txn *oldtid = NULL;
struct txn *newtid = NULL;
/* create new db file name */
buf_setcstr(&buf, subsfname);
buf_appendcstr(&buf, ".NEW");
newsubsfname = buf_release(&buf);
/* open new db file */
db_r = cyrusdb_open(SUBDB, newsubsfname, CYRUSDB_CREATE, &newsubs);
if (!db_r) {
/* add version record */
const char *key = DB_VERSION_KEY;
size_t keylen = strlen(key);
const char *data = DB_VERSION_STR;
size_t datalen = strlen(data);
db_r = cyrusdb_store(newsubs, key, keylen, data, datalen, &newtid);
}
if (db_r) {
syslog(LOG_ERR, "DBERROR: opening %s: %s", newsubsfname,
cyrusdb_strerror(db_r));
fatal("can't open new subscriptions file", EX_TEMPFAIL);
}
/* perform upgrade from old to new db */
struct upgrade_rock urock = { userid, &buf, newsubs, &newtid, NULL, NULL };
db_r = cyrusdb_foreach(oldsubs, "", 0, NULL, _upgrade_subs_cb, &urock, &oldtid);
r2 = cyrusdb_abort(oldsubs, oldtid);
if (!r2) r2 = cyrusdb_close(oldsubs);
if (r2) {
syslog(LOG_ERR, "DBERROR: error closing %s: %s", subsfname,
cyrusdb_strerror(r2));
if (!db_r) db_r = r2;
}
*subs = NULL;
/* complete txn on new db */
if (newtid) {
if (db_r) {
r2 = cyrusdb_abort(newsubs, newtid);
} else {
r2 = cyrusdb_commit(newsubs, newtid);
}
if (r2) {
syslog(LOG_ERR, "DBERROR: error %s txn in mboxlist_upgrade_subs: %s",
db_r ? "aborting" : "committing", cyrusdb_strerror(r2));
}
}
r2 = cyrusdb_close(newsubs);
if (r2) {
syslog(LOG_ERR, "DBERROR: error closing %s: %s", newsubsfname,
cyrusdb_strerror(r2));
if (!db_r) db_r = r2;
}
if (!db_r) {
/* rename new db file */
if (rename(newsubsfname, subsfname) < 0) {
syslog(LOG_ERR, "DBERROR: renaming %s: %m", newsubsfname);
fatal("can't rename subscriptions file", EX_TEMPFAIL);
}
/* reopen upgraded db under regular name (not-create, we're sure it will
* be there due to locks! */
db_r = cyrusdb_open(SUBDB, subsfname, 0, subs);
}
unlink(newsubsfname);
free(newsubsfname);
buf_free(&buf);
return db_r ? IMAP_IOERROR : 0;
}
static int mboxlist_upgrade_subs(const char *userid, const char *subsfname, struct db **subs)
{
// if we have the DB key already in the DB, nothing to do!
const char *key = DB_VERSION_KEY;
size_t keylen = strlen(DB_VERSION_KEY);
const char *data = NULL;
size_t datalen = 0;
int db_r = cyrusdb_fetch(*subs, key, keylen, &data, &datalen, NULL);
// XXX: check version?
if (db_r == CYRUSDB_OK) return 0;
int r = 0;
// lock the user namespace - we'll hold this lock while we upgrade.
struct mboxlock *namespacelock = user_namespacelock(userid);
/* if we find it this time, we lost the race and someone else already
* upgraded the DB. Bonus. */
db_r = cyrusdb_fetch(*subs, key, keylen, &data, &datalen, NULL);
if (db_r != CYRUSDB_OK) {
syslog(LOG_NOTICE, "mboxlist_upgrade_subs(): %s", userid);
r = mboxlist_upgrade_subs_work(userid, subsfname, subs);
}
mboxname_release(&namespacelock);
return r;
}

File Metadata

Mime Type
text/x-c
Expires
Sat, Apr 4, 3:22 AM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822390
Default Alt Text
mboxlist.c (168 KB)

Event Timeline