Page MenuHomePhorge

mboxlist.c
No OneTemporary

Authored By
Unknown
Size
87 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.
*
* $Id: mboxlist.c,v 1.275 2010/07/23 19:19:03 wescraig Exp $
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "acl.h"
#include "annotate.h"
#include "auth.h"
#include "glob.h"
#include "assert.h"
#include "global.h"
#include "cyrusdb.h"
#include "util.h"
#include "mailbox.h"
#include "exitcodes.h"
#include "imap/imap_err.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "user.h"
#include "mboxname.h"
#include "mupdate-client.h"
#include "mboxlist.h"
#include "quota.h"
#include "sync_log.h"
#define DB config_mboxlist_db
#define SUBDB config_subscription_db
cyrus_acl_canonproc_t mboxlist_ensureOwnerRights;
struct db *mbdb;
static int mboxlist_dbopen = 0;
static int mboxlist_opensubs(const char *userid, struct db **ret);
static void mboxlist_closesubs(struct db *sub);
static int mboxlist_rmquota(const char *name, int matchlen, int maycreate,
void *rock);
static int mboxlist_changequota(const char *name, int matchlen, int maycreate,
void *rock);
static int mboxlist_count_inferiors(const char *mboxname);
struct mboxlist_entry *mboxlist_entry_create(void)
{
struct mboxlist_entry *ret = xzmalloc(sizeof(struct mboxlist_entry));
/* xxx - initialiser functions here? */
return ret;
}
void mboxlist_entry_free(struct mboxlist_entry **mbentryptr)
{
struct mboxlist_entry *mbentry = *mbentryptr;
/* idempotent */
if (!mbentry) return;
/* check if we have allocated a buffer */
if (mbentry->_alloc)
free(mbentry->_alloc);
free(mbentry);
*mbentryptr = NULL;
}
char *mboxlist_entry_cstring(struct mboxlist_entry *mbentry)
{
struct buf ebuf;
char sep = '(';
char nbuf[30];
char *ret;
memset(&ebuf, 0, sizeof(struct buf));
if (mbentry->specialuse) {
buf_putc(&ebuf, sep);
buf_appendcstr(&ebuf, "specialuse");
buf_putc(&ebuf, ' ');
buf_appendcstr(&ebuf, mbentry->specialuse);
sep = ' ';
}
if (mbentry->uniqueid) {
buf_putc(&ebuf, sep);
buf_appendcstr(&ebuf, "uniqueid");
buf_putc(&ebuf, ' ');
buf_appendcstr(&ebuf, mbentry->uniqueid);
sep = ' ';
}
if (sep == ' ') {
buf_appendcstr(&ebuf, ") ");
}
sprintf(nbuf, "%d", mbentry->mbtype);
buf_appendcstr(&ebuf, nbuf);
buf_putc(&ebuf, ' ');
if (mbentry->server) {
buf_appendcstr(&ebuf, mbentry->server);
if (mbentry->partition)
buf_putc(&ebuf, '!');
}
if (mbentry->partition)
buf_appendcstr(&ebuf, mbentry->partition);
buf_putc(&ebuf, ' ');
buf_appendcstr(&ebuf, mbentry->acl);
ret = buf_release(&ebuf);
buf_free(&ebuf);
return ret;
}
/*
* read a single record from the mailboxes.db and return a pointer to it
*/
static int mboxlist_read(const char *name, const char **dataptr, size_t *datalenptr,
struct txn **tid, int wrlock)
{
int namelen = strlen(name);
int r;
if (!namelen)
return IMAP_MAILBOX_NONEXISTENT;
if (wrlock) {
r = cyrusdb_fetchlock(mbdb, name, namelen, dataptr, datalenptr, tid);
} else {
r = cyrusdb_fetch(mbdb, name, namelen, dataptr, datalenptr, tid);
}
switch (r) {
case CYRUSDB_OK:
/* no entry required, just checking if it exists */
return 0;
break;
case CYRUSDB_AGAIN:
return IMAP_AGAIN;
break;
case CYRUSDB_NOTFOUND:
return IMAP_MAILBOX_NONEXISTENT;
break;
default:
syslog(LOG_ERR, "DBERROR: error fetching mboxlist %s: %s",
name, cyrusdb_strerror(r));
return IMAP_IOERROR;
break;
}
/* never get here */
}
/*
* parse a record read from the mailboxes.db into its parts.
*/
/* NOTE: this needs to be made fully compatible with multiple different
* formats at some stage, when we switch to fully dlist entries */
static int mboxlist_parse_entry(struct mboxlist_entry **mbentryptr,
const char *name,
const char *data, size_t datalen)
{
char *p, *q;
const char **target;
struct mboxlist_entry *mbentry;
int namelen = strlen(name);
mbentry = mboxlist_entry_create();
mbentry->_alloc = p = xmalloc(namelen + datalen + 2);
/* copy name */
memcpy(p, name, namelen);
p[namelen] = '\0';
mbentry->name = p;
p += namelen + 1;
/* copy data */
memcpy(p, data, datalen);
p[datalen] = '\0';
/* 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, "specialuse")) target = &mbentry->specialuse;
if (!strcmp(p, "uniqueid")) target = &mbentry->uniqueid;
p = q;
while (*q && *q != ' ' && *q != ')') q++;
if (*q != ' ') last = 1;
if (*q) *q++ = '\0';
if (target) *target = 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 = p;
p = q;
while (*q && *q != ' ') q++;
}
if (*q) *q++ = '\0';
mbentry->partition = p;
mbentry->acl = q;
if (mbentryptr) *mbentryptr = mbentry;
else mboxlist_entry_free(&mbentry);
return 0;
}
/* read a record and parse into parts */
static int mboxlist_mylookup(const char *name,
struct mboxlist_entry **mbentryptr,
struct txn **tid, int wrlock)
{
int r;
const char *data;
size_t datalen;
r = mboxlist_read(name, &data, &datalen, tid, wrlock);
if (r) return r;
return mboxlist_parse_entry(mbentryptr, name, data, datalen);
}
/*
* Lookup 'name' in the mailbox list, ignoring reserved records
*/
int mboxlist_lookup(const char *name, struct mboxlist_entry **entryptr,
struct txn **tid)
{
struct mboxlist_entry *entry = NULL;
int r;
r = mboxlist_mylookup(name, &entry, tid, 0);
if (r) return r;
/* Ignore "reserved" entries, like they aren't there */
if (entry->mbtype & MBTYPE_RESERVE) {
mboxlist_entry_free(&entry);
return IMAP_MAILBOX_RESERVED;
}
if (entryptr) *entryptr = entry;
return 0;
}
/* now this is just silly, but hey */
int mboxlist_lookup_allow_reserved(const char *name,
struct mboxlist_entry **entryptr,
struct txn **tid)
{
return mboxlist_mylookup(name, entryptr, tid, 0);
}
/* given a mailbox name, find the staging directory. XXX - this should
* require more locking, and staging directories should be by pid */
int mboxlist_findstage(const char *name, char *stagedir, size_t sd_len)
{
const char *root;
struct mboxlist_entry *mbentry = NULL;
int r;
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;
}
int mboxlist_update(struct mboxlist_entry *mbentry, int localonly)
{
int r = 0, r2 = 0;
char *mboxent = NULL;
struct txn *tid = NULL;
mboxent = mboxlist_entry_cstring(mbentry);
r = cyrusdb_store(mbdb, mbentry->name, strlen(mbentry->name),
mboxent, strlen(mboxent), &tid);
free(mboxent);
mboxent = NULL;
/* 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);
} else {
r2 = cyrusdb_commit(mbdb, tid);
}
}
if (r2) {
syslog(LOG_ERR, "DBERROR: error %s txn in mboxlist_update: %s",
r ? "aborting" : "commiting", cyrusdb_strerror(r2));
}
return r;
}
static int mboxlist_findparent(const char *mboxname,
struct mboxlist_entry **mbentryp)
{
struct mboxlist_entry *mbentry = NULL;
char *parent = xstrdup(mboxname);
int parentlen = 0;
char *p;
int r = IMAP_MAILBOX_NONEXISTENT;
while ((parentlen==0) && (p = strrchr(parent, '.')) && !strchr(p, '!')) {
*p = '\0';
mboxlist_entry_free(&mbentry);
r = mboxlist_mylookup(parent, &mbentry, NULL, 0);
if (r != IMAP_MAILBOX_NONEXISTENT)
break;
}
free(parent);
if (r)
mboxlist_entry_free(&mbentry);
else
*mbentryp = mbentry;
return r;
}
static int mboxlist_create_partition(const char *mboxname,
const char *part,
char **out)
{
struct mboxlist_entry *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 partition with free space */
if (!part)
part = find_free_partition(NULL);
/* Configuration error */
if (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,
struct auth_state *auth_state,
int isadmin, int force_subdirs)
{
int user_folder_limit = config_getint(IMAPOPT_USER_FOLDER_LIMIT);
const char *p;
struct mboxlist_entry *mbentry = NULL;
int r = 0;
/* policy first */
r = mboxname_policycheck(mboxname);
if (r) return r;
/* 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 the folder limit */
if (user_folder_limit) {
const char *inbox = mboxname_user_inbox(userid);
int num = mboxlist_count_inferiors(inbox);
if (num + 1 > user_folder_limit) {
syslog(LOG_NOTICE, "LIMIT: refused to create "
"%s for %s because of limit %d",
mboxname, userid, user_folder_limit);
return IMAP_PERMISSION_DENIED;
}
}
}
/* Check to see if mailbox already exists */
r = mboxlist_mylookup(mboxname, &mbentry, NULL, 0);
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;
}
}
mboxlist_entry_free(&mbentry);
return r;
}
/* look for a parent mailbox */
r = mboxlist_findparent(mboxname, &mbentry);
if (r == 0) {
/* found a parent */
/* check acl */
if (!isadmin &&
!(cyrus_acl_myrights(auth_state, mbentry->acl) & ACL_CREATE)) {
mboxlist_entry_free(&mbentry);
return IMAP_PERMISSION_DENIED;
}
}
else if (r == IMAP_MAILBOX_NONEXISTENT) {
/* no parent mailbox */
if (!isadmin)
return IMAP_PERMISSION_DENIED;
p = mboxname_isusermailbox(mboxname, 0);
if (p) {
char *firstdot = strchr(p, '.');
if (!force_subdirs && firstdot) {
/* Disallow creating user.X.* when no user.X */
return IMAP_PERMISSION_DENIED;
}
}
}
else {
return r;
}
mboxlist_entry_free(&mbentry);
return 0;
}
static int mboxlist_create_acl(const char *mboxname, char **out)
{
struct mboxlist_entry *mbentry = NULL;
const char *owner;
int r;
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("");
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);
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_set(out, identifier, ACL_MODE_SET, cyrus_acl_strtomask(rights),
(cyrus_acl_canonproc_t *)0, (void *)0);
identifier = p;
}
free(defaultacl);
return 0;
}
/* and this API just plain sucks */
int mboxlist_createmailboxcheck(const char *name, int mbtype __attribute__((unused)),
const char *partition,
int isadmin, const char *userid,
struct auth_state *auth_state,
char **newacl, char **newpartition,
int forceuser)
{
char *part = NULL;
char *acl = NULL;
int r = 0;
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;
}
/*
* 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 inconsistant)
*
*/
int mboxlist_createmailbox_full(const char *mboxname, int mbtype,
const char *partition,
int isadmin, const char *userid,
struct auth_state *auth_state,
int options, unsigned uidvalidity,
const char *copyacl, const char *uniqueid,
int localonly, int forceuser, int dbonly,
struct mailbox **mboxptr, struct dlist *extargs)
{
int r;
char *newpartition = NULL;
char *mboxent = NULL;
char *acl = NULL;
struct mailbox *newmailbox = NULL;
int isremote = mbtype & MBTYPE_REMOTE;
struct mboxlist_entry *newmbentry = NULL;
struct dlist *item;
const char *useptr = NULL;
/* XXX - better DLIST API - handle multi-value? */
if (dlist_getlist(extargs, "USE", &item))
dlist_getatom(item, "USE", &useptr);
r = mboxlist_create_namecheck(mboxname, userid, auth_state,
isadmin, forceuser);
if (r) goto done;
if (copyacl) {
acl = xstrdup(copyacl);
}
else {
r = mboxlist_create_acl(mboxname, &acl);
if (r) goto done;
}
r = mboxlist_create_partition(mboxname, partition, &newpartition);
if (r) goto done;
if (!dbonly && !isremote) {
/* Filesystem Operations */
r = mailbox_create(mboxname, newpartition, acl, uniqueid,
useptr, options, uidvalidity, &newmailbox);
if (r) goto done; /* CREATE failed */
}
/* all is well - activate the mailbox */
newmbentry = mboxlist_entry_create();
newmbentry->acl = acl;
newmbentry->mbtype = mbtype;
newmbentry->partition = newpartition;
newmbentry->uniqueid = newmailbox ? newmailbox->uniqueid : uniqueid;
newmbentry->specialuse = useptr;
mboxent = mboxlist_entry_cstring(newmbentry);
r = cyrusdb_store(mbdb, mboxname, strlen(mboxname),
mboxent, strlen(mboxent), NULL);
if (r) {
syslog(LOG_ERR, "DBERROR: failed to insert to mailboxes list %s: %s",
mboxname, cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
/* 9. set MUPDATE entry as commited (CRASH: commited) */
if (!r && config_mupdate_server && !localonly) {
mupdate_handle *mupdate_h = NULL;
char *loc = strconcat(config_servername, "!", newpartition, (char *)NULL);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if (!r) r = mupdate_reserve(mupdate_h, mboxname, loc);
if (!r) r = mupdate_activate(mupdate_h, mboxname, loc, acl);
if (r) {
syslog(LOG_ERR, "MUPDATE: can't commit mailbox entry for '%s'",
mboxname);
cyrusdb_delete(mbdb, mboxname, strlen(mboxname), NULL, 0);
}
if (mupdate_h) mupdate_disconnect(&mupdate_h);
free(loc);
}
done:
if (newmailbox) {
if (r) mailbox_delete(&newmailbox);
else if (mboxptr) *mboxptr = newmailbox;
else mailbox_close(&newmailbox);
}
free(acl);
free(newpartition);
free(mboxent);
mboxlist_entry_free(&newmbentry);
return r;
}
int mboxlist_createmailbox(const char *name, int mbtype,
const char *partition,
int isadmin, const char *userid,
struct auth_state *auth_state,
int localonly, int forceuser, int dbonly,
struct dlist *extargs)
{
int options = config_getint(IMAPOPT_MAILBOX_DEFAULT_OPTIONS)
| OPT_POP3_NEW_UIDL;
return mboxlist_createmailbox_full(name, mbtype, partition,
isadmin, userid, auth_state,
options, 0, NULL, NULL, localonly,
forceuser, dbonly, NULL, extargs);
}
int mboxlist_createsync(const char *name, int mbtype,
const char *partition,
const char *userid, struct auth_state *auth_state,
int options, unsigned uidvalidity,
const char *acl, const char *uniqueid,
struct mailbox **mboxptr)
{
return mboxlist_createmailbox_full(name, mbtype, partition,
1, userid, auth_state,
options, uidvalidity, acl, uniqueid,
0, 1, 0, mboxptr, NULL);
}
/* insert an entry for the proxy */
int mboxlist_insertremote(struct mboxlist_entry *mbentry,
struct txn **tid)
{
char *mboxent;
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;
}
}
mboxent = mboxlist_entry_cstring(mbentry);
/* database put */
r = cyrusdb_store(mbdb, mbentry->name, strlen(mbentry->name),
mboxent, strlen(mboxent), tid);
switch (r) {
case CYRUSDB_OK:
break;
case CYRUSDB_AGAIN:
abort(); /* shouldn't happen ! */
break;
default:
syslog(LOG_ERR, "DBERROR: error updating database %s: %s",
mbentry->name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
break;
}
free(mboxent);
return r;
}
/* Special function to delete a remote mailbox.
* Only affects mboxlist.
* Assumes admin powers. */
int mboxlist_deleteremote(const char *name, struct txn **in_tid)
{
int r;
struct txn **tid;
struct txn *lcl_tid = NULL;
struct mboxlist_entry *mbentry = NULL;
if(in_tid) {
tid = in_tid;
} else {
tid = &lcl_tid;
}
retry:
r = mboxlist_mylookup(name, &mbentry, tid, 1);
switch (r) {
case 0:
break;
case IMAP_AGAIN:
goto retry;
break;
default:
goto done;
}
if ((mbentry->mbtype & MBTYPE_REMOTE) && !mbentry->server) {
syslog(LOG_ERR,
"mboxlist_deleteremote called on non-remote mailbox: %s",
name);
goto done;
}
retry_del:
/* delete entry */
r = cyrusdb_delete(mbdb, name, strlen(name), tid, 0);
switch (r) {
case CYRUSDB_OK: /* success */
break;
case CYRUSDB_AGAIN:
goto retry_del;
default:
syslog(LOG_ERR, "DBERROR: error deleting %s: %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) {
syslog(LOG_ERR, "DBERROR: failed on commit: %s",
cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
tid = NULL;
}
done:
if (r && !in_tid && tid) {
/* Abort the transaction if it is still in progress */
cyrusdb_abort(mbdb, *tid);
}
return r;
}
/*
* Delayed Delete a mailbox: translate delete into rename
*/
int
mboxlist_delayed_deletemailbox(const char *name, int isadmin,
const char *userid,
struct auth_state *auth_state, int checkacl,
int force)
{
struct mboxlist_entry *mbentry = NULL;
char newname[MAX_MAILBOX_BUFFER];
int r;
long myrights;
char *p;
if (!isadmin && force) return IMAP_PERMISSION_DENIED;
/* Check for request to delete a user:
user.<x> with no dots after it */
if ((p = mboxname_isusermailbox(name, 1))) {
/* Can't DELETE INBOX (your own inbox) */
if (userid) {
size_t len = config_virtdomains ?
strcspn(userid, "@") : strlen(userid);
if ((len == strlen(p)) && !strncmp(p, userid, len)) {
return(IMAP_MAILBOX_NOTSUPPORTED);
}
}
/* Only admins may delete user */
if (!isadmin) return(IMAP_PERMISSION_DENIED);
}
r = mboxlist_mylookup(name, &mbentry, NULL, 1);
if (r) return r;
/* 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;
mboxlist_entry_free(&mbentry);
return r;
}
}
/* get the deleted name */
mboxname_todeleted(name, newname, 1);
/* Get mboxlist_renamemailbox to do the hard work. No ACL checks needed */
r = mboxlist_renamemailbox((char *)name, newname, mbentry->partition,
0 /* uidvalidity */,
1 /* isadmin */, userid,
auth_state, force, 1);
mboxlist_entry_free(&mbentry);
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
*
*/
int mboxlist_deletemailbox(const char *name, int isadmin,
const char *userid,
struct auth_state *auth_state, int checkacl,
int local_only, int force)
{
struct mboxlist_entry *mbentry = NULL;
int r;
long myrights;
struct mailbox *mailbox = NULL;
int isremote = 0;
const char *p;
mupdate_handle *mupdate_h = NULL;
if (!isadmin && force) return IMAP_PERMISSION_DENIED;
/* Check for request to delete a user:
user.<x> with no dots after it */
if ((p = mboxname_isusermailbox(name, 1))) {
/* Can't DELETE INBOX (your own inbox) */
if (userid) {
size_t len = config_virtdomains ? strcspn(userid, "@") : strlen(userid);
if ((len == strlen(p)) && !strncmp(p, userid, len)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
}
/* Only admins may delete user */
if (!isadmin) { r = IMAP_PERMISSION_DENIED; goto done; }
}
r = mboxlist_mylookup(name, &mbentry, NULL, 1);
if (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 && !force) goto done;
/* remove from mupdate */
if (!isremote && !local_only && 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;
/* delete entry */
r = cyrusdb_delete(mbdb, name, strlen(name), NULL, 0);
if (r) {
syslog(LOG_ERR, "DBERROR: error deleting %s: %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);
r = mailbox_delete(&mailbox);
}
done:
mailbox_close(&mailbox);
mboxlist_entry_free(&mbentry);
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
*/
int mboxlist_renamemailbox(const char *oldname, const char *newname,
const char *partition, unsigned uidvalidity,
int isadmin, const char *userid,
struct auth_state *auth_state,
int forceuser, int ignorequota)
{
int r;
long myrights;
int isusermbox = 0; /* Are we renaming someone's inbox */
int partitionmove = 0;
struct mailbox *oldmailbox = NULL;
struct mailbox *newmailbox = NULL;
struct txn *tid = NULL;
const char *root = NULL;
char *newpartition = NULL;
char *mboxent = NULL;
mupdate_handle *mupdate_h = NULL;
struct mboxlist_entry *newmbentry = NULL;
/* 1. open mailbox */
r = mailbox_open_iwl(oldname, &oldmailbox);
if (r) return r;
myrights = cyrus_acl_myrights(auth_state, oldmailbox->acl);
/* check the ACLs up-front */
if (!isadmin) {
if (!(myrights & ACL_DELETEMBOX)) {
r = (myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
/* 2. verify valid move */
/* XXX - handle remote mailbox */
/* special case: same mailbox, must be a partition move */
if (!strcmp(oldname, newname)) {
char *oldpath = mailbox_datapath(oldmailbox);
/* 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);
if (r) goto done;
newmbentry = mboxlist_entry_create();
newmbentry->mbtype = oldmailbox->mbtype;
newmbentry->partition = newpartition;
newmbentry->acl = oldmailbox->acl;
mboxent = mboxlist_entry_cstring(newmbentry);
r = cyrusdb_store(mbdb, newname, strlen(newname),
mboxent, strlen(mboxent), &tid);
mboxlist_entry_free(&newmbentry);
if (r) goto done;
/* skip ahead to the commit */
goto dbdone;
}
/* RENAME of some user's INBOX */
if (mboxname_isusermailbox(oldname, 1)) {
if (mboxname_isdeletedmailbox(newname, NULL)) {
/* delete user is OK */
}
else if (mboxname_isusermailbox(newname, 1)) {
/* user rename is depends on config */
if (!config_getswitch(IMAPOPT_ALLOWUSERMOVES)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
}
else if (mboxname_userownsmailbox(userid, oldname) &&
mboxname_userownsmailbox(userid, newname)) {
/* Special case of renaming inbox */
isusermbox = 1;
}
else {
/* Everything else is bogus */
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
}
r = mboxlist_create_namecheck(newname, userid, auth_state,
isadmin, forceuser);
if (r) goto done;
r = mboxlist_create_partition(newname, partition, &newpartition);
if (r) goto done;
if (!newpartition) newpartition = xstrdup(config_defpartition);
/* Rename the actual mailbox */
r = mailbox_rename_copy(oldmailbox, newname, newpartition, uidvalidity,
isusermbox ? userid : NULL, ignorequota,
&newmailbox);
if (r) goto done;
syslog(LOG_INFO, "Rename: %s -> %s", oldname, newname);
/* create new entry */
newmbentry = mboxlist_entry_create();
newmbentry->mbtype = newmailbox->mbtype;
newmbentry->partition = newmailbox->part;
newmbentry->acl = newmailbox->acl;
mboxent = mboxlist_entry_cstring(newmbentry);
do {
r = 0;
/* delete the old entry */
if (!isusermbox)
r = cyrusdb_delete(mbdb, oldname, strlen(oldname), &tid, 0);
/* create a new entry */
if (!r)
r = cyrusdb_store(mbdb, newname, strlen(newname),
mboxent, strlen(mboxent), &tid);
switch (r) {
case 0: /* success */
break;
case CYRUSDB_AGAIN:
tid = NULL;
break;
default:
syslog(LOG_ERR, "DBERROR: error renaming %s %s: %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) {
syslog(LOG_ERR, "DBERROR: failed on commit %s %s: %s",
oldname, newname, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
}
if (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);
}
if (mupdate_h) mupdate_disconnect(&mupdate_h);
free(loc);
}
done: /* Commit or cleanup */
if (!r && newmailbox)
r = mailbox_commit(newmailbox);
if (r) {
/* XXX - rollback DB changes if it was an mupdate failure */
if (newmailbox) mailbox_delete(&newmailbox);
if (partitionmove && newpartition)
mailbox_delete_cleanup(newpartition, newname);
mailbox_close(&oldmailbox);
} else {
if (newmailbox) {
mailbox_rename_cleanup(&oldmailbox, isusermbox);
mailbox_close(&newmailbox);
}
else if (partitionmove) {
char *oldpartition = xstrdup(oldmailbox->part);
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: partitionmove sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s> oldpart=<%s> newpart=<%s>",
session_id(),
oldmailbox->name, oldmailbox->uniqueid,
oldpartition, partition);
mailbox_close(&oldmailbox);
mailbox_delete_cleanup(oldpartition, oldname);
free(oldpartition);
}
else
abort(); /* impossible, in theory */
/* log the rename */
sync_log_mailbox_double(oldname, newname);
}
/* free memory */
free(newpartition);
free(mboxent);
mboxlist_entry_free(&newmbentry);
return r;
}
/*
* Verify if the 'user' is the mailbox 'name' owner.
*/
static int mboxlist_is_owner(const char *name, int domainlen,
const char *user, int userlen)
{
char *dot_position = NULL;
/* is_user_mbox */
if(strncmp(name+domainlen, "user.", 5))
return 0;
dot_position = strchr(user, '.');
if (dot_position && (dot_position - user) <= userlen)
return 0;
/* starts_with_user */
if(strncmp(name+domainlen+5, user, userlen))
return 0;
/* is_exactly_user */
if (!(name[domainlen+5+userlen] == '\0' ||
name[domainlen+5+userlen] == '.'))
return 0;
return 1;
}
/*
* Check if the admin rights are present in the 'rights'
*/
static int mboxlist_have_admin_rights(const char* rights) {
int access = cyrus_acl_strtomask(rights);
int 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
*
*/
int mboxlist_setacl(const char *name, const char *identifier,
const char *rights,
int isadmin, const char *userid,
struct auth_state *auth_state)
{
struct mboxlist_entry *mbentry = NULL;
int useridlen = strlen(userid);
int domainlen = 0;
int identifierlen = strlen(identifier);
char *cp, ident[256];
const char *domain = NULL;
int r;
int myrights;
int mode = ACL_MODE_SET;
int isusermbox = 0;
int isidentifiermbox = 0;
int anyoneuseracl = 1;
int ensure_owner_rights = 0;
const char *mailbox_owner = NULL;
struct mailbox *mailbox = NULL;
char *newacl = NULL;
char *mboxent = NULL;
struct txn *tid = NULL;
if (config_virtdomains) {
if ((cp = strchr(userid, '@'))) {
useridlen = cp - userid;
}
if ((cp = strchr(name, '!'))) {
domain = name;
domainlen = cp - name + 1;
}
/* canonify identifier so it is fully qualified,
except for "anonymous", "anyone", the global admin
and users in the default domain */
if ((cp = strchr(identifier, '@'))) {
identifierlen = cp - identifier;
if (rights &&
((domain && strncasecmp(cp+1, domain, strlen(cp+1))) ||
(!domain && (!config_defdomain ||
strcasecmp(config_defdomain, cp+1))))) {
/* can't set cross-domain ACLs */
return IMAP_INVALID_IDENTIFIER;
}
if ((config_defdomain && !strcasecmp(config_defdomain, cp+1)) ||
!strcmp(identifier, "anonymous") ||
!strcmp(identifier, "anyone")) {
snprintf(ident, sizeof(ident),
"%.*s", (int) (cp - identifier), identifier);
} else {
strlcpy(ident, identifier, sizeof(ident));
}
} else {
strlcpy(ident, identifier, sizeof(ident));
if (domain && !isadmin &&
strcmp(ident, "anonymous") && strcmp(ident, "anyone")) {
snprintf(ident+strlen(ident), sizeof(ident)-strlen(ident),
"@%.*s",
domainlen ? domainlen-1 : (int) strlen(domain), domain);
}
}
identifier = ident;
}
/* checks if the mailbox belongs to the user who is trying to change the
access rights */
if (mboxlist_is_owner(name, domainlen, userid, useridlen)) {
isusermbox = 1;
}
anyoneuseracl = config_getswitch(IMAPOPT_ANYONEUSERACL);
/* checks if the identifier is the mailbox owner */
if (mboxlist_is_owner(name, domainlen, identifier, identifierlen)) {
isidentifiermbox = 1;
}
/* who is the mailbox owner? */
if (isusermbox) {
mailbox_owner = userid;
}
else if (isidentifiermbox) {
mailbox_owner = identifier;
}
/* ensure the access rights if the folder owner is the current user or
the identifier */
ensure_owner_rights = isusermbox || isidentifiermbox;
/* 1. Start Transaction */
/* lookup the mailbox to make sure it exists and get its acl */
do {
r = mboxlist_mylookup(name, &mbentry, &tid, 1);
} while(r == IMAP_AGAIN);
/* Can't do this to an in-transit or reserved mailbox */
if (!r && mbentry->mbtype & (MBTYPE_MOVING | MBTYPE_RESERVE)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
}
/* if it is not a remote mailbox, we need to unlock the mailbox list,
* lock the mailbox, and re-lock the mailboxes list */
/* we must do this to obey our locking rules */
if (!r && !(mbentry->mbtype & MBTYPE_REMOTE)) {
cyrusdb_abort(mbdb, tid);
tid = NULL;
mboxlist_entry_free(&mbentry);
/* open & lock mailbox header */
r = mailbox_open_iwl(name, &mailbox);
if (!r) {
do {
/* lookup the mailbox to make sure it exists and get its acl */
r = mboxlist_mylookup(name, &mbentry, &tid, 1);
} while (r == IMAP_AGAIN);
}
if(r) goto done;
}
/* 2. Check Rights */
if (!r && !isadmin) {
myrights = cyrus_acl_myrights(auth_state, mbentry->acl);
if (!(myrights & ACL_ADMIN)) {
r = (myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
/* 2.1 Only admin user can set 'anyone' rights if config says so */
if (!r && !isadmin && !anyoneuseracl && !strncmp(identifier, "anyone", 6)) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
/* 3. Set DB Entry */
if(!r) {
/* Make change to ACL */
newacl = xstrdup(mbentry->acl);
if (rights && *rights) {
/* rights are present and non-empty */
mode = ACL_MODE_SET;
if (*rights == '+') {
rights++;
mode = ACL_MODE_ADD;
}
else if (*rights == '-') {
rights++;
mode = ACL_MODE_REMOVE;
}
/* do not allow non-admin user to remove the admin rights from mailbox owner */
if (!isadmin && isidentifiermbox && (mode != ACL_MODE_ADD) &&
!mboxlist_have_admin_rights(rights)) {
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_set(&newacl, identifier, mode,
cyrus_acl_strtomask(rights),
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 */
mbentry->acl = newacl;
mboxent = mboxlist_entry_cstring(mbentry);
do {
r = cyrusdb_store(mbdb, name, strlen(name),
mboxent, strlen(mboxent), &tid);
} while(r == CYRUSDB_AGAIN);
if(r) {
syslog(LOG_ERR, "DBERROR: error updating acl %s: %s",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
}
/* 4. Change backup copy (cyrus.header) */
/* we already have it locked from above */
if (!r && !(mbentry->mbtype & MBTYPE_REMOTE)) {
mailbox_set_acl(mailbox, newacl, 1);
/* want to commit immediately to ensure ordering */
r = mailbox_commit(mailbox);
}
/* 5. Commit transaction */
if (!r) {
if((r = cyrusdb_commit(mbdb, tid)) != 0) {
syslog(LOG_ERR, "DBERROR: failed on commit: %s",
cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
tid = NULL;
}
/* 6. Change mupdate entry */
if (!r && config_mupdate_server) {
mupdate_handle *mupdate_h = NULL;
/* commit the update to MUPDATE */
char buf[MAX_PARTITION_LEN + HOSTNAME_SIZE + 2];
snprintf(buf, sizeof(buf), "%s!%s", config_servername, mbentry->partition);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if(r) {
syslog(LOG_ERR,
"cannot connect to mupdate server for setacl on '%s'",
name);
} else {
r = mupdate_activate(mupdate_h, name, buf, newacl);
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't update mailbox entry for '%s'",
name);
}
}
mupdate_disconnect(&mupdate_h);
}
done:
if (r && tid) {
/* if we are mid-transaction, abort it! */
int r2 = cyrusdb_abort(mbdb, tid);
if (r2) {
syslog(LOG_ERR,
"DBERROR: error aborting txn in mboxlist_setacl: %s",
cyrusdb_strerror(r2));
}
}
mailbox_close(&mailbox);
free(mboxent);
free(newacl);
mboxlist_entry_free(&mbentry);
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
*
*/
int
mboxlist_sync_setacls(const char *name, const char *newacl)
{
struct mboxlist_entry *mbentry = NULL;
int r;
struct txn *tid = NULL;
/* 1. Start Transaction */
/* lookup the mailbox to make sure it exists and get its acl */
do {
r = mboxlist_mylookup(name, &mbentry, &tid, 1);
} while(r == IMAP_AGAIN);
/* Can't do this to an in-transit or reserved mailbox */
if (!r && mbentry->mbtype & (MBTYPE_MOVING | MBTYPE_RESERVE)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
}
/* 2. Set DB Entry */
if (!r) {
/* ok, change the database */
mbentry->acl = newacl;
char *mboxent = mboxlist_entry_cstring(mbentry);
do {
r = cyrusdb_store(mbdb, name, strlen(name),
mboxent, strlen(mboxent), &tid);
} while (r == CYRUSDB_AGAIN);
if (r) {
syslog(LOG_ERR, "DBERROR: error updating acl %s: %s",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
free(mboxent);
}
/* 3. Commit transaction */
if (!r) {
r = cyrusdb_commit(mbdb, tid);
if (r) {
syslog(LOG_ERR, "DBERROR: failed on commit %s: %s",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
tid = NULL;
}
/* 4. 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];
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);
}
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 sync_setacls %s: %s",
name, cyrusdb_strerror(r2));
}
}
mboxlist_entry_free(&mbentry);
return r;
}
/* set the XLIST flag for a mailbox. Note: no mupdate changes required
* yet because mupdate protocol doesn't support xlist flags */
int mboxlist_setspecialuse(struct mailbox *mailbox, const char *specialuse)
{
const char *name = mailbox->name;
struct mboxlist_entry *mbentry = NULL;
struct txn *tid = NULL;
char *mboxent = NULL;
int r;
assert(mailbox->index_locktype == LOCK_EXCLUSIVE);
/* 1. Start Transaction */
/* lookup the mailbox to make sure it exists and get its acl */
do {
r = mboxlist_mylookup(name, &mbentry, &tid, 1);
} while (r == IMAP_AGAIN);
/* both empty? */
if (!mbentry->specialuse && !specialuse)
goto done;
/* both the same? */
if (mbentry->specialuse && specialuse &&
!strcmp(mbentry->specialuse, specialuse))
goto done;
/* Can't do this to anything other than a normal mailbox */
if (!r && mbentry->mbtype) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
/* update the entry */
mbentry->specialuse = specialuse;
mboxent = mboxlist_entry_cstring(mbentry);
do {
r = cyrusdb_store(mbdb, name, strlen(name),
mboxent, strlen(mboxent), &tid);
} while (r == CYRUSDB_AGAIN);
if (r) {
syslog(LOG_ERR, "DBERROR: error updating acl %s: %s",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
}
/* update the mailbox and commit */
r = mailbox_set_specialuse(mailbox, specialuse);
if (r) goto done;
r = mailbox_commit(mailbox);
if (r) goto done;
/* 3. Commit transaction */
r = cyrusdb_commit(mbdb, tid);
if (r) {
syslog(LOG_ERR, "DBERROR: failed on commit, header inconsistent %s: %s",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
}
tid = NULL;
done:
if (tid) {
int r2 = cyrusdb_abort(mbdb, tid);
if (r2) {
syslog(LOG_ERR, "DBERROR: failed on abort %s: %s",
name, cyrusdb_strerror(r2));
}
}
mboxlist_entry_free(&mbentry);
free(mboxent);
return r;
}
struct find_rock {
struct glob *g;
struct namespace *namespace;
int find_namespace;
int domainlen;
int inboxoffset;
const char *inboxcase;
const char *usermboxname;
size_t usermboxnamelen;
int checkmboxlist;
int checkshared;
struct db *db;
int isadmin;
struct auth_state *auth_state;
char *prev;
int prevlen;
int (*proc)(char *, int, int, void *rock);
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;
long minmatch;
struct glob *g = rock->g;
long matchlen;
/* don't list mailboxes outside of the default domain */
if (!rock->domainlen && !rock->isadmin && memchr(key, '!', keylen)) return 0;
minmatch = 0;
if (rock->inboxoffset) {
char namebuf[MAX_MAILBOX_BUFFER];
if(keylen >= (int) sizeof(namebuf)) {
syslog(LOG_ERR, "oversize keylen in mboxlist.c:find_p()");
return 0;
}
memcpy(namebuf, key, keylen);
namebuf[keylen] = '\0';
if (rock->inboxoffset) {
namebuf[rock->inboxoffset] = rock->inboxcase[0];
namebuf[rock->inboxoffset+1] = rock->inboxcase[1];
namebuf[rock->inboxoffset+2] = rock->inboxcase[2];
namebuf[rock->inboxoffset+3] = rock->inboxcase[3];
namebuf[rock->inboxoffset+4] = rock->inboxcase[4];
}
matchlen = glob_test(g, namebuf+rock->inboxoffset,
keylen-rock->inboxoffset, &minmatch);
} else {
matchlen = glob_test(g, key, keylen, &minmatch);
}
/* If its not a match, skip it -- partial matches are ok. */
if(matchlen == -1) return 0;
if (rock->find_namespace != NAMESPACE_INBOX &&
rock->usermboxname &&
keylen >= rock->usermboxnamelen &&
(keylen == rock->usermboxnamelen ||
key[rock->usermboxnamelen] == '.') &&
!strncmp(key, rock->usermboxname, rock->usermboxnamelen)) {
/* this would've been output with the inbox stuff, so skip it */
return 0;
}
if (rock->find_namespace == NAMESPACE_SHARED &&
rock->namespace && rock->namespace->isalt &&
!strncmp(key+rock->domainlen, "user", 4) &&
(key[rock->domainlen+4] == '\0' || key[rock->domainlen+4] == '.')) {
/* this would've been output with the user stuff, so skip it */
return 0;
}
/* Suppress deleted hierarchy unless admin: overrides ACL_LOOKUP test */
if (!rock->isadmin) {
char namebuf[MAX_MAILBOX_BUFFER];
memcpy(namebuf, key, keylen);
namebuf[keylen] = '\0';
if (mboxname_isdeletedmailbox(namebuf, NULL))
return 0;
}
/* check acl */
if (!rock->isadmin) {
int rights;
struct mboxlist_entry *mbentry = NULL;
if (mboxlist_parse_entry(&mbentry, "", data, datalen))
return 0;
/* check the acls */
rights = cyrus_acl_myrights(rock->auth_state, mbentry->acl);
mboxlist_entry_free(&mbentry);
if (!(rights & ACL_LOOKUP)) {
return 0;
}
}
/* if we get here, close enough for us to spend the time
acting interested */
return 1;
}
static int check_name(struct find_rock *rock,
const char *base, int len)
{
if (rock->prev) {
if (cyrusdb_compar(rock->db, base, len, rock->prev, rock->prevlen) < 0)
return 0; /* previous name, skip it */
free(rock->prev);
}
rock->prev = xstrndup(base, len);
rock->prevlen = len;
return 1;
}
static int find_cb(void *rockp,
const char *key, size_t keylen,
const char *data __attribute__((unused)),
size_t datalen __attribute__((unused)))
{
char namebuf[MAX_MAILBOX_BUFFER];
struct find_rock *rock = (struct find_rock *) rockp;
int r = 0;
long minmatch;
struct glob *g = rock->g;
/* foreach match, do this test */
minmatch = 0;
while (minmatch >= 0) {
long matchlen;
if(keylen >= (int) sizeof(namebuf)) {
syslog(LOG_ERR, "oversize keylen in mboxlist.c:find_cb()");
return 0;
}
memcpy(namebuf, key, keylen);
namebuf[keylen] = '\0';
if (rock->find_namespace != NAMESPACE_INBOX &&
rock->usermboxname &&
!strncmp(namebuf, rock->usermboxname, rock->usermboxnamelen)
&& (keylen == rock->usermboxnamelen ||
namebuf[rock->usermboxnamelen] == '.')) {
/* this would've been output with the inbox stuff, so skip it */
return 0;
}
/* make sure it's in the mailboxes db */
if (rock->checkmboxlist) {
r = mboxlist_lookup(namebuf, NULL, NULL);
} else {
r = 0; /* don't bother checking */
}
if (!r && rock->inboxoffset) {
namebuf[rock->inboxoffset] = rock->inboxcase[0];
namebuf[rock->inboxoffset+1] = rock->inboxcase[1];
namebuf[rock->inboxoffset+2] = rock->inboxcase[2];
namebuf[rock->inboxoffset+3] = rock->inboxcase[3];
namebuf[rock->inboxoffset+4] = rock->inboxcase[4];
}
matchlen = glob_test(g, namebuf+rock->inboxoffset,
keylen-rock->inboxoffset, &minmatch);
if (matchlen == -1) {
r = 0;
break;
}
switch (r) {
case 0:
/* found the entry; output it */
if (rock->find_namespace == NAMESPACE_SHARED &&
rock->checkshared && rock->namespace) {
/* special case: LIST "" *% -- output prefix */
r = (*rock->proc)(rock->namespace->prefix[NAMESPACE_SHARED],
strlen(rock->namespace->prefix[NAMESPACE_SHARED])-1,
1, rock->procrock);
if (rock->checkshared > 1) {
/* special case: LIST "" % -- output prefix only */
/* short-circuit the foreach - one mailbox is sufficient */
return CYRUSDB_DONE;
}
}
rock->checkshared = 0;
if (check_name(rock, namebuf+rock->inboxoffset, matchlen))
r = (*rock->proc)(namebuf+rock->inboxoffset, matchlen,
1, rock->procrock);
break;
case IMAP_MAILBOX_NONEXISTENT:
/* didn't find the entry */
r = 0;
break;
default:
break;
}
if (r) break;
}
return r;
}
int mboxlist_allmbox(const char *prefix, foreach_cb *proc, void *rock)
{
int r;
char *search = prefix ? (char *)prefix : "";
r = cyrusdb_foreach(mbdb, search, strlen(search), NULL, proc, rock, 0);
return r;
}
/*
* Find all mailboxes that match 'pattern'.
* 'isadmin' is nonzero if user is a mailbox admin. 'userid'
* is the user's login id. For each matching mailbox, calls
* 'proc' with the name of the mailbox. If 'proc' ever returns
* a nonzero value, mboxlist_findall immediately stops searching
* and returns that value. 'rock' is passed along as an argument to proc in
* case it wants some persistant storage or extra data.
*/
/* Find all mailboxes that match 'pattern'. */
int mboxlist_findall(struct namespace *namespace,
const char *pattern, int isadmin, const char *userid,
struct auth_state *auth_state, int (*proc)(), void *rock)
{
struct find_rock cbrock;
char usermboxname[MAX_MAILBOX_BUFFER];
size_t usermboxnamelen = 0;
const char *data;
size_t datalen;
int r = 0;
char *p;
size_t prefixlen;
int userlen = userid ? strlen(userid) : 0, domainlen = 0;
char domainpat[MAX_MAILBOX_BUFFER] = ""; /* do intra-domain fetches only */
char *pat = NULL;
if (!namespace) namespace = mboxname_get_adminnamespace();
if (config_virtdomains) {
char *domain;
if (userid && (domain = strrchr(userid, '@'))) {
userlen = domain - userid;
domainlen = strlen(domain); /* includes separator */
if ((p = strchr(pattern , '!'))) {
if ((p-pattern != domainlen-1) ||
strncmp(pattern, domain+1, domainlen-1)) {
/* don't allow cross-domain access */
return IMAP_MAILBOX_BADNAME;
}
pattern = p+1;
}
snprintf(domainpat, sizeof(domainpat), "%s!%s", domain+1, pattern);
}
if ((p = strrchr(pattern, '@'))) {
/* global admin specified mbox@domain */
if (domainlen) {
/* can't do both user@domain and mbox@domain */
return IMAP_MAILBOX_BADNAME;
}
/* don't prepend default domain */
if (!(config_defdomain && !strcasecmp(config_defdomain, p+1))) {
snprintf(domainpat, sizeof(domainpat), "%s!", p+1);
domainlen = strlen(p);
}
snprintf(domainpat+domainlen, sizeof(domainpat)-domainlen,
"%.*s", (int) (p - pattern), pattern);
}
}
if (domainpat[0] == '\0')
strlcpy(domainpat, pattern, sizeof(domainpat));
cbrock.g = glob_init(pattern, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.namespace = NULL;
cbrock.domainlen = domainlen;
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.isadmin = isadmin;
cbrock.auth_state = auth_state;
cbrock.checkmboxlist = 0; /* don't duplicate work */
cbrock.checkshared = 0;
cbrock.proc = proc;
cbrock.procrock = rock;
cbrock.prev = NULL;
cbrock.prevlen = 0;
cbrock.db = mbdb;
/* Build usermboxname */
if (userid && (!(p = strchr(userid, '.')) || ((p - userid) > userlen)) &&
strlen(userid)+5 < MAX_MAILBOX_BUFFER) {
if (domainlen)
snprintf(usermboxname, sizeof(usermboxname),
"%s!", userid+userlen+1);
snprintf(usermboxname+domainlen, sizeof(usermboxname)-domainlen,
"user.%.*s", userlen, userid);
usermboxnamelen = strlen(usermboxname);
}
else {
userid = NULL;
}
/* Check for INBOX first of all */
if (userid) {
if (GLOB_TEST(cbrock.g, "INBOX") != -1) {
r = cyrusdb_fetch(mbdb, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (r == CYRUSDB_NOTFOUND) r = 0;
else if (!r)
r = (*proc)(cbrock.inboxcase, 5, 1, rock);
}
else if (!strncmp(pattern,
usermboxname+domainlen, usermboxnamelen-domainlen) &&
GLOB_TEST(cbrock.g, usermboxname+domainlen) != -1) {
r = cyrusdb_fetch(mbdb, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (r == CYRUSDB_NOTFOUND) r = 0;
else if (!r)
r = (*proc)(usermboxname, usermboxnamelen, 1, rock);
}
strlcat(usermboxname, ".", sizeof(usermboxname));
usermboxnamelen++;
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
} else {
cbrock.usermboxname = NULL;
cbrock.usermboxnamelen = 0;
}
if (r) goto done;
/* Make a working copy of pattern */
pattern = pat = xstrdup(pattern);
/* Find fixed-string pattern prefix */
for (p = pat; *p; p++) {
if (*p == '*' || *p == '%' || *p == '?' || *p == '@') break;
}
prefixlen = p - pattern;
*p = '\0';
/*
* If user.X.* or INBOX.* can match pattern,
* search for those mailboxes next
*/
if (userid &&
(!strncmp(usermboxname+domainlen, pattern, usermboxnamelen-domainlen-1) ||
!strncasecmp("inbox.", pattern, prefixlen < 6 ? prefixlen : 6))) {
if (!strncmp(usermboxname+domainlen, pattern, usermboxnamelen-domainlen-1)) {
/* switch to pattern with domain prepended */
glob_free(&cbrock.g);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
cbrock.inboxoffset = 0;
}
else {
cbrock.inboxoffset = domainlen + userlen;
}
cbrock.find_namespace = NAMESPACE_INBOX;
/* iterate through prefixes matching usermboxname */
r = cyrusdb_foreach(mbdb,
usermboxname, usermboxnamelen,
&find_p, &find_cb, &cbrock,
NULL);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
}
if (!r && (isadmin || namespace->accessible[NAMESPACE_USER])) {
cbrock.find_namespace = NAMESPACE_USER;
/* switch to pattern with domain prepended */
glob_free(&cbrock.g);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
cbrock.inboxoffset = 0;
if (usermboxnamelen) {
usermboxname[--usermboxnamelen] = '\0';
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
/* search for all remaining mailboxes.
just bother looking at the ones that have the same pattern
prefix. */
r = cyrusdb_foreach(mbdb,
domainpat, domainlen + prefixlen,
&find_p, &find_cb, &cbrock,
NULL);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
}
done:
glob_free(&cbrock.g);
if (pat) free(pat);
return r;
}
int mboxlist_findall_alt(struct namespace *namespace,
const char *pattern, int isadmin, const char *userid,
struct auth_state *auth_state, int (*proc)(),
void *rock)
{
struct find_rock cbrock;
char usermboxname[MAX_MAILBOX_BUFFER], patbuf[MAX_MAILBOX_BUFFER];
size_t usermboxnamelen = 0;
const char *data;
size_t datalen;
int r = 0;
char *p;
size_t prefixlen, len;
size_t userlen = userid ? strlen(userid) : 0, domainlen = 0;
char domainpat[MAX_MAILBOX_BUFFER]; /* do intra-domain fetches only */
char *pat = NULL;
if (!namespace) namespace = mboxname_get_adminnamespace();
if (config_virtdomains && userid && (p = strchr(userid, '@'))) {
userlen = p - userid;
domainlen = strlen(p); /* includes separator */
snprintf(domainpat, sizeof(domainpat), "%s!", p+1);
}
else
domainpat[0] = '\0';
cbrock.g = glob_init(pattern, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.namespace = namespace;
cbrock.domainlen = domainlen;
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.isadmin = isadmin;
cbrock.auth_state = auth_state;
cbrock.checkmboxlist = 0; /* don't duplicate work */
cbrock.checkshared = 0;
cbrock.proc = proc;
cbrock.procrock = rock;
cbrock.prev = NULL;
cbrock.prevlen = 0;
cbrock.db = mbdb;
/* Build usermboxname */
if (userid && (!(p = strchr(userid, '.')) || ((p - userid) > (int)userlen)) &&
strlen(userid)+5 < MAX_MAILBOX_BUFFER) {
if (domainlen)
snprintf(usermboxname, sizeof(usermboxname),
"%s!", userid+userlen+1);
snprintf(usermboxname+domainlen, sizeof(usermboxname)-domainlen,
"user.%.*s", (int)userlen, userid);
usermboxnamelen = strlen(usermboxname);
}
else {
userid = 0;
}
/* Check for INBOX first of all */
if (userid) {
if (GLOB_TEST(cbrock.g, "INBOX") != -1) {
r = cyrusdb_fetch(mbdb, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (r == CYRUSDB_NOTFOUND) r = 0;
else if (!r)
r = (*proc)(cbrock.inboxcase, 5, 0, rock);
}
strlcat(usermboxname, ".", sizeof(usermboxname));
usermboxnamelen++;
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
} else {
cbrock.usermboxname = NULL;
cbrock.usermboxnamelen = 0;
}
if (r) goto done;
glob_free(&cbrock.g);
/* Make a working copy of pattern */
pattern = pat = xstrdup(pattern);
/* Find fixed-string pattern prefix */
for (p = pat; *p; p++) {
if (*p == '*' || *p == '%' || *p == '?' || *p == '@') break;
}
prefixlen = p - pattern;
/*
* Personal (INBOX) namespace
*
* Append pattern to "INBOX.", search for those mailboxes next
*/
if (userid) {
strlcpy(patbuf, usermboxname, sizeof(patbuf));
strlcat(patbuf, pattern, sizeof(patbuf));
cbrock.g = glob_init(patbuf, GLOB_HIERARCHY);
cbrock.inboxoffset = 0;
cbrock.find_namespace = NAMESPACE_INBOX;
/* iterate through prefixes matching usermboxname */
cyrusdb_foreach(mbdb,
usermboxname, usermboxnamelen,
&find_p, &find_cb, &cbrock,
NULL);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
glob_free(&cbrock.g);
}
if (usermboxnamelen) {
usermboxname[--usermboxnamelen] = '\0';
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
/*
* Other Users namespace
*
* If "Other Users*" can match pattern, search for those mailboxes next
*/
if (isadmin || namespace->accessible[NAMESPACE_USER]) {
len = strlen(namespace->prefix[NAMESPACE_USER]);
if(len>0) len--;
if (!strncmp(namespace->prefix[NAMESPACE_USER], pattern,
prefixlen < len ? prefixlen : len)) {
if (prefixlen < len) {
strlcpy(domainpat+domainlen, pattern+prefixlen,
sizeof(domainpat)-domainlen);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
}
else {
strlcpy(domainpat+domainlen, "user", sizeof(domainpat)-domainlen);
strlcat(domainpat, pattern+len, sizeof(domainpat));
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
}
cbrock.find_namespace = NAMESPACE_USER;
cbrock.inboxoffset = 0;
/* iterate through prefixes matching usermboxname */
strlcpy(domainpat+domainlen, "user", sizeof(domainpat)-domainlen);
cyrusdb_foreach(mbdb,
domainpat, strlen(domainpat),
&find_p, &find_cb, &cbrock,
NULL);
glob_free(&cbrock.g);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
}
}
/*
* Shared namespace
*
* search for all remaining mailboxes.
* just bother looking at the ones that have the same pattern prefix.
*/
if (isadmin || namespace->accessible[NAMESPACE_SHARED]) {
len = strlen(namespace->prefix[NAMESPACE_SHARED]);
if(len>0) len--;
if (!strncmp(namespace->prefix[NAMESPACE_SHARED], pattern,
prefixlen < len ? prefixlen : len)) {
cbrock.find_namespace = NAMESPACE_SHARED;
cbrock.inboxoffset = 0;
if (prefixlen <= len) {
/* Skip pattern which matches shared namespace prefix */
for (p = pat+prefixlen; *p; p++) {
if (*p == '%') continue;
else if (*p == '.') p++;
break;
}
if (*pattern && !strchr(pattern, '.') &&
pattern[strlen(pattern)-1] == '%')
/* special case: LIST "" *% -- output prefix */
cbrock.checkshared = 1;
if ((cbrock.checkshared || prefixlen == len) && !*p) {
/* special case: LIST "" % -- output prefix
(if we have a shared mbox) and quit */
strlcpy(domainpat+domainlen, "*", sizeof(domainpat)-domainlen);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
cbrock.checkshared = 2;
}
else {
strlcpy(domainpat+domainlen, p, sizeof(domainpat)-domainlen);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
}
domainpat[domainlen] = '\0';
cyrusdb_foreach(mbdb,
domainpat, domainlen,
&find_p, &find_cb, &cbrock,
NULL);
}
else if (pattern[len] == '.') {
strlcpy(domainpat+domainlen, pattern+len+1,
sizeof(domainpat)-domainlen);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
cyrusdb_foreach(mbdb,
domainpat, domainlen+prefixlen-(len+1),
&find_p, &find_cb, &cbrock,
NULL);
}
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
}
}
done:
glob_free(&cbrock.g);
if (pat) free(pat);
return r;
}
static int child_cb(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
if (!name) return 0;
return (*((int *) rock) = 1);
}
/*
* Set all the resource quotas on, or create a quota root.
*/
int mboxlist_setquotas(const char *root,
int newquotas[QUOTA_NUMRESOURCES], int force)
{
char pattern[MAX_MAILBOX_PATH+1];
struct quota q;
int have_mailbox = 1;
int r;
int res;
struct txn *tid = NULL;
if (!root[0] || root[0] == '.' || strchr(root, '/')
|| strchr(root, '*') || strchr(root, '%') || strchr(root, '?')) {
return IMAP_MAILBOX_BADNAME;
}
q.root = root;
r = quota_read(&q, &tid, 1);
if (!r) {
int changed = 0;
/* has it changed? */
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
if (q.limits[res] != newquotas[res]) {
q.limits[res] = newquotas[res];
changed++;
}
}
if (changed)
r = quota_write(&q, &tid);
if (!r)
quota_commit(&tid);
goto done;
}
if (r != IMAP_QUOTAROOT_NONEXISTENT)
goto done;
/*
* Have to create a new quota root
*/
strlcpy(pattern, root, sizeof(pattern));
if (config_virtdomains && root[strlen(root)-1] == '!') {
/* domain quota */
have_mailbox = 0;
strlcat(pattern, "*", sizeof(pattern));
}
else {
struct mboxlist_entry *mbentry = NULL;
strlcat(pattern, ".*", sizeof(pattern));
/* look for a top-level mailbox in the proposed quotaroot */
r = mboxlist_lookup(root, &mbentry, NULL);
if (r) {
if (!force && r == IMAP_MAILBOX_NONEXISTENT) {
/* look for a child mailbox in the proposed quotaroot */
mboxlist_findall(NULL, pattern, 1, NULL, NULL,
child_cb, (void *) &force);
}
/* are we going to force the create anyway? */
if (force) {
have_mailbox = 0;
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 */
quota_init(&q);
memcpy(q.limits, newquotas, sizeof(q.limits));
r = quota_write(&q, &tid);
if (r) goto done;
quota_commit(&tid);
/* recurse through mailboxes, setting the quota and finding
* out the usage */
/* top level mailbox */
if (have_mailbox)
mboxlist_changequota(root, 0, 0, (void *)root);
/* submailboxes - we're using internal names here */
mboxlist_findall(NULL, pattern, 1, 0, 0, mboxlist_changequota, (void *)root);
quota_changelockrelease();
done:
if (r && tid) quota_abort(&tid);
if (!r) sync_log_quota(root);
return r;
}
/*
* Remove a quota root
*/
int mboxlist_unsetquota(const char *root)
{
char pattern[MAX_MAILBOX_PATH+1];
struct quota q;
int r=0;
if (!root[0] || root[0] == '.' || strchr(root, '/')
|| strchr(root, '*') || strchr(root, '%') || strchr(root, '?')) {
return IMAP_MAILBOX_BADNAME;
}
q.root = root;
r = quota_read(&q, NULL, 0);
/* already unset */
if (r == IMAP_QUOTAROOT_NONEXISTENT) return 0;
if (r) return r;
r = quota_changelock();
/*
* Have to remove it from all affected mailboxes
*/
strlcpy(pattern, root, sizeof(pattern));
if (config_virtdomains && root[strlen(root)-1] == '!') {
/* domain quota */
strlcat(pattern, "*", sizeof(pattern));
}
else
strlcat(pattern, ".*", sizeof(pattern));
/* top level mailbox */
mboxlist_rmquota(root, 0, 0, (void *)root);
/* submailboxes - we're using internal names here */
mboxlist_findall(NULL, pattern, 1, 0, 0, mboxlist_rmquota, (void *)root);
r = quota_deleteroot(root);
quota_changelockrelease();
if (!r) sync_log_quota(root);
return r;
}
/*
* ACL access canonicalization routine which ensures that 'owner'
* retains lookup, administer, and create rights over a mailbox.
*/
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 char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
int r = 0;
struct mailbox *mailbox = NULL;
const char *oldroot = (const char *) rock;
assert(oldroot != NULL);
r = mailbox_open_iwl(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, name, error_message(r));
}
/* not a huge tragedy if we failed, so always return success */
return 0;
}
/*
* Helper function to change the quota root for 'name' to that pointed
* to by the static global struct pointer 'mboxlist_newquota'.
*/
static int mboxlist_changequota(const char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
int r = 0;
struct mailbox *mailbox = NULL;
const char *root = (const char *) rock;
int res;
quota_t quota_usage[QUOTA_NUMRESOURCES];
assert(root);
r = mailbox_open_iwl(name, &mailbox);
if (r) goto done;
mailbox_get_usage(mailbox, quota_usage);
if (mailbox->quotaroot) {
quota_t quota_diff[QUOTA_NUMRESOURCES];
if (strlen(mailbox->quotaroot) >= strlen(root)) {
/* Part of a child quota root - skip */
goto done;
}
/* remove usage from the old quotaroot */
for (res = 0; res < QUOTA_NUMRESOURCES ; res++) {
quota_diff[res] = -quota_usage[res];
}
r = quota_update_useds(mailbox->quotaroot, quota_diff,
mailbox->name);
}
/* update (or set) the quotaroot */
r = mailbox_set_quotaroot(mailbox, root);
if (r) goto done;
/* update the new quota root */
r = quota_update_useds(root, quota_usage, mailbox->name);
done:
mailbox_close(&mailbox);
if (r) {
syslog(LOG_ERR, "LOSTQUOTA: unable to change quota root for %s to %s: %s",
name, 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;
}
/* must be called after cyrus_init */
void mboxlist_init(int myflags)
{
if (myflags & MBOXLIST_SYNC) {
cyrusdb_sync(DB);
}
}
void mboxlist_open(const char *fname)
{
int ret, flags;
char *tofree = NULL;
if (!fname)
fname = config_getstring(IMAPOPT_MBOXLIST_DB_PATH);
/* create db file name */
if (!fname) {
tofree = strconcat(config_dir, FNAME_MBOXLIST, (char *)NULL);
fname = tofree;
}
flags = CYRUSDB_CREATE;
if (config_getswitch(IMAPOPT_IMPROVED_MBOXLIST_SORT)) {
flags |= CYRUSDB_MBOXSORT;
}
ret = cyrusdb_open(DB, fname, flags, &mbdb);
if (ret != 0) {
syslog(LOG_ERR, "DBERROR: opening %s: %s", fname,
cyrusdb_strerror(ret));
/* Exiting TEMPFAIL because Sendmail thinks this
EC_OSFILE == permanent failure. */
fatal("can't read mailboxes file", EC_TEMPFAIL);
}
free(tofree);
mboxlist_dbopen = 1;
}
void mboxlist_close(void)
{
int r;
if (mboxlist_dbopen) {
r = cyrusdb_close(mbdb);
if (r) {
syslog(LOG_ERR, "DBERROR: error closing mailboxes: %s",
cyrusdb_strerror(r));
}
mboxlist_dbopen = 0;
}
}
void mboxlist_done(void)
{
/* DB->done() handled by cyrus_done() */
}
/*
* Open the subscription list for 'userid'.
*
* On success, returns zero.
* On failure, returns an error code.
*/
static int
mboxlist_opensubs(const char *userid,
struct db **ret)
{
int r = 0, flags;
char *subsfname;
/* Build subscription list filename */
subsfname = user_hash_subs(userid);
flags = CYRUSDB_CREATE;
if (config_getswitch(IMAPOPT_IMPROVED_MBOXLIST_SORT)) {
flags |= CYRUSDB_MBOXSORT;
}
r = cyrusdb_open(SUBDB, subsfname, flags, ret);
if (r != CYRUSDB_OK) {
r = IMAP_IOERROR;
}
free(subsfname);
return r;
}
/*
* Close a subscription file
*/
static void mboxlist_closesubs(struct db *sub)
{
cyrusdb_close(sub);
}
/*
* Find subscribed mailboxes that match 'pattern'.
* 'isadmin' is nonzero if user is a mailbox admin. 'userid'
* is the user's login id. For each matching mailbox, calls
* 'proc' with the name of the mailbox.
*/
int mboxlist_findsub(struct namespace *namespace,
const char *pattern, int isadmin __attribute__((unused)),
const char *userid, struct auth_state *auth_state,
int (*proc)(), void *rock, int force)
{
struct db *subs = NULL;
struct find_rock cbrock;
char usermboxname[MAX_MAILBOX_BUFFER];
size_t usermboxnamelen = 0;
const char *data;
size_t datalen;
int r = 0;
char *p;
size_t prefixlen;
size_t userlen = userid ? strlen(userid) : 0, domainlen = 0;
char domainpat[MAX_MAILBOX_BUFFER]; /* do intra-domain fetches only */
char *pat = NULL;
if (!namespace) namespace = mboxname_get_adminnamespace();
if (config_virtdomains && userid && (p = strchr(userid, '@'))) {
userlen = p - userid;
domainlen = strlen(p); /* includes separator */
snprintf(domainpat, sizeof(domainpat), "%s!%s", p+1, pattern);
}
else
strncpy(domainpat, pattern, sizeof(domainpat));
cbrock.g = glob_init(pattern, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.namespace = NULL;
cbrock.domainlen = domainlen;
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.isadmin = 1; /* user can always see their subs */
cbrock.auth_state = auth_state;
cbrock.checkmboxlist = !force;
cbrock.checkshared = 0;
cbrock.proc = proc;
cbrock.procrock = rock;
cbrock.prev = NULL;
cbrock.prevlen = 0;
/* open the subscription file that contains the mailboxes the
user is subscribed to */
if ((r = mboxlist_opensubs(userid, &subs)) != 0) {
goto done;
}
cbrock.db = subs;
/* Build usermboxname */
if (userid && (!(p = strchr(userid, '.')) || ((p - userid) > (int)userlen)) &&
strlen(userid)+5 < MAX_MAILBOX_BUFFER) {
if (domainlen)
snprintf(usermboxname, sizeof(usermboxname),
"%s!", userid+userlen+1);
snprintf(usermboxname+domainlen, sizeof(usermboxname)-domainlen,
"user.%.*s", (int)userlen, userid);
usermboxnamelen = strlen(usermboxname);
}
else {
userid = 0;
}
/* Check for INBOX first of all */
if (userid) {
if (GLOB_TEST(cbrock.g, "INBOX") != -1) {
r = cyrusdb_fetch(subs, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (r == CYRUSDB_NOTFOUND) r = 0;
else if (!r)
r = (*proc)(cbrock.inboxcase, 5, 1, rock);
}
else if (!strncmp(pattern,
usermboxname+domainlen, usermboxnamelen-domainlen) &&
GLOB_TEST(cbrock.g, usermboxname+domainlen) != -1) {
r = cyrusdb_fetch(subs, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (r == CYRUSDB_NOTFOUND) r = 0;
else if (!r)
r = (*proc)(usermboxname, usermboxnamelen, 1, rock);
}
strlcat(usermboxname, ".", sizeof(usermboxname));
usermboxnamelen++;
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
if (r) goto done;
/* Make a working copy of pattern */
pattern = pat = xstrdup(pattern);
/* Find fixed-string pattern prefix */
for (p = pat; *p; p++) {
if (*p == '*' || *p == '%' || *p == '?' || *p == '@') break;
}
prefixlen = p - pattern;
*p = '\0';
/*
* If user.X.* or INBOX.* can match pattern,
* search for those mailboxes next
*/
if (userid &&
(!strncmp(usermboxname+domainlen, pattern, usermboxnamelen-domainlen-1) ||
!strncasecmp("inbox.", pattern, prefixlen < 6 ? prefixlen : 6))) {
if (!strncmp(usermboxname+domainlen, pattern, usermboxnamelen-domainlen-1)) {
/* switch to pattern with domain prepended */
glob_free(&cbrock.g);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
cbrock.inboxoffset = 0;
}
else {
cbrock.inboxoffset = strlen(userid);
}
cbrock.find_namespace = NAMESPACE_INBOX;
/* iterate through prefixes matching usermboxname */
cyrusdb_foreach(subs,
usermboxname, usermboxnamelen,
&find_p, &find_cb, &cbrock,
NULL);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
} else {
cbrock.usermboxname = NULL;
cbrock.usermboxnamelen = 0;
}
if (isadmin || namespace->accessible[NAMESPACE_USER]) {
cbrock.find_namespace = NAMESPACE_USER;
/* switch to pattern with domain prepended */
glob_free(&cbrock.g);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
cbrock.inboxoffset = 0;
if (usermboxnamelen) {
usermboxname[--usermboxnamelen] = '\0';
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
/* search for all remaining mailboxes.
just bother looking at the ones that have the same pattern prefix. */
cyrusdb_foreach(subs, domainpat, domainlen + prefixlen,
&find_p, &find_cb, &cbrock, NULL);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
}
done:
if (subs) mboxlist_closesubs(subs);
glob_free(&cbrock.g);
if (pat) free(pat);
return r;
}
int mboxlist_allsubs(const char *userid, foreach_cb *proc, void *rock)
{
struct db *subs = NULL;
int r;
/* open subs DB */
r = mboxlist_opensubs(userid, &subs);
if (r) return r;
r = cyrusdb_foreach(subs, "", 0, NULL, proc, rock, 0);
mboxlist_closesubs(subs);
return r;
}
int mboxlist_findsub_alt(struct namespace *namespace,
const char *pattern, int isadmin __attribute__((unused)),
const char *userid, struct auth_state *auth_state,
int (*proc)(), void *rock, int force)
{
struct db *subs = NULL;
struct find_rock cbrock;
char usermboxname[MAX_MAILBOX_BUFFER], patbuf[MAX_MAILBOX_BUFFER];
size_t usermboxnamelen = 0;
const char *data;
size_t datalen;
int r = 0;
char *p;
size_t prefixlen, len;
size_t userlen = userid ? strlen(userid) : 0, domainlen = 0;
char domainpat[MAX_MAILBOX_BUFFER]; /* do intra-domain fetches only */
char *pat = NULL;
if (!namespace) namespace = mboxname_get_adminnamespace();
if (config_virtdomains && userid && (p = strchr(userid, '@'))) {
userlen = p - userid;
domainlen = strlen(p); /* includes separator */
snprintf(domainpat, sizeof(domainpat), "%s!", p+1);
}
else
domainpat[0] = '\0';
cbrock.g = glob_init(pattern, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.namespace = namespace;
cbrock.domainlen = domainlen;
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.isadmin = 1; /* user can always see their subs */
cbrock.auth_state = auth_state;
cbrock.checkmboxlist = !force;
cbrock.checkshared = 0;
cbrock.proc = proc;
cbrock.procrock = rock;
cbrock.prev = NULL;
cbrock.prevlen = 0;
/* open the subscription file that contains the mailboxes the
user is subscribed to */
if ((r = mboxlist_opensubs(userid, &subs)) != 0) {
goto done;
}
cbrock.db = subs;
/* Build usermboxname */
if (userid && (!(p = strchr(userid, '.')) || ((p - userid) > (int)userlen)) &&
strlen(userid)+5 < MAX_MAILBOX_BUFFER) {
if (domainlen)
snprintf(usermboxname, sizeof(usermboxname),
"%s!", userid+userlen+1);
snprintf(usermboxname+domainlen, sizeof(usermboxname)-domainlen,
"user.%.*s", (int)userlen, userid);
usermboxnamelen = strlen(usermboxname);
}
else {
userid = 0;
}
/* Check for INBOX first of all */
if (userid) {
if (GLOB_TEST(cbrock.g, "INBOX") != -1) {
r = cyrusdb_fetch(subs, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (r == CYRUSDB_NOTFOUND) r = 0;
else if (!r)
r = (*proc)(cbrock.inboxcase, 5, 0, rock);
}
strlcat(usermboxname, ".", sizeof(usermboxname));
usermboxnamelen++;
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
if (r) goto done;
glob_free(&cbrock.g);
/* Make a working copy of pattern */
pattern = pat = xstrdup(pattern);
/* Find fixed-string pattern prefix */
for (p = pat; *p; p++) {
if (*p == '*' || *p == '%' || *p == '?' || *p == '@') break;
}
prefixlen = p - pattern;
/*
* Personal (INBOX) namespace
*
* Append pattern to "INBOX.", search for those subscriptions next
*/
if (userid) {
strlcpy(patbuf, usermboxname, sizeof(patbuf));
strlcat(patbuf, pattern, sizeof(patbuf));
cbrock.g = glob_init(patbuf, GLOB_HIERARCHY);
cbrock.inboxoffset = 0;
cbrock.find_namespace = NAMESPACE_INBOX;
/* iterate through prefixes matching usermboxname */
cyrusdb_foreach(subs,
usermboxname, usermboxnamelen,
&find_p, &find_cb, &cbrock,
NULL);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
glob_free(&cbrock.g);
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
} else {
cbrock.usermboxname = NULL;
cbrock.usermboxnamelen = 0;
}
if (usermboxnamelen) {
usermboxname[--usermboxnamelen] = '\0';
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
/*
* Other Users namespace
*
* If "Other Users*" can match pattern, search for those subscriptions next
*/
if (isadmin || namespace->accessible[NAMESPACE_USER]) {
len = strlen(namespace->prefix[NAMESPACE_USER]);
if(len>0) len--; /* Remove Separator */
if (!strncmp(namespace->prefix[NAMESPACE_USER], pattern,
prefixlen < len ? prefixlen : len)) {
if (prefixlen < len) {
strlcpy(domainpat+domainlen, pattern+prefixlen,
sizeof(domainpat)-domainlen);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
}
else {
strlcpy(domainpat+domainlen, "user",
sizeof(domainpat)-domainlen);
strlcat(domainpat, pattern+len, sizeof(domainpat));
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
}
cbrock.find_namespace = NAMESPACE_USER;
cbrock.inboxoffset = 0;
/* iterate through prefixes matching usermboxname */
strlcpy(domainpat+domainlen, "user", sizeof(domainpat)-domainlen);
cyrusdb_foreach(subs,
domainpat, strlen(domainpat),
&find_p, &find_cb, &cbrock,
NULL);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
glob_free(&cbrock.g);
}
}
/*
* Shared namespace
*
* search for all remaining subscriptions.
* just bother looking at the ones that have the same pattern prefix.
*/
if (isadmin || namespace->accessible[NAMESPACE_SHARED]) {
len = strlen(namespace->prefix[NAMESPACE_SHARED]);
if(len>0) len--; /* Remove Separator */
if (!strncmp(namespace->prefix[NAMESPACE_SHARED], pattern,
prefixlen < len ? prefixlen : len)) {
cbrock.find_namespace = NAMESPACE_SHARED;
cbrock.inboxoffset = 0;
if (prefixlen <= len) {
/* Skip pattern which matches shared namespace prefix */
for (p = pat+prefixlen; *p; p++) {
if (*p == '%') continue;
else if (*p == '.') p++;
break;
}
if (*pattern && !strchr(pattern, '.') &&
pattern[strlen(pattern)-1] == '%') {
/* special case: LSUB "" *% -- output prefix */
cbrock.checkshared = 1;
}
if ((cbrock.checkshared || prefixlen == len) && !*p) {
/* special case: LSUB "" % -- output prefix
(if we have a shared mbox) and quit */
strlcpy(domainpat+domainlen, "*", sizeof(domainpat)-domainlen);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
cbrock.checkshared = 2;
}
else {
strlcpy(domainpat+domainlen, p, sizeof(domainpat)-domainlen);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
}
domainpat[domainlen] = '\0';
cyrusdb_foreach(subs,
domainpat, domainlen,
&find_p, &find_cb, &cbrock,
NULL);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
}
else if (pattern[len] == '.') {
strlcpy(domainpat+domainlen, pattern+len+1,
sizeof(domainpat)-domainlen);
cbrock.g = glob_init(domainpat, GLOB_HIERARCHY);
cyrusdb_foreach(subs,
domainpat, domainlen+prefixlen-(len+1),
&find_p, &find_cb, &cbrock,
NULL);
free(cbrock.prev);
cbrock.prev = NULL;
cbrock.prevlen = 0;
}
}
}
done:
if (subs) mboxlist_closesubs(subs);
glob_free(&cbrock.g);
if (pat) free(pat);
return r;
}
/* returns CYRUSDB_NOTFOUND if the folder doesn't exist, and 0 if it does! */
int mboxlist_checksub(const char *name, const char *userid)
{
int r;
struct db *subs;
const char *val;
size_t vallen;
r = mboxlist_opensubs(userid, &subs);
if (!r) r = cyrusdb_fetch(subs, name, strlen(name), &val, &vallen, NULL);
mboxlist_closesubs(subs);
return r;
}
/*
* Change 'user's subscription status for mailbox 'name'.
* Subscribes if 'add' is nonzero, unsubscribes otherwise.
* if 'force' is set, force the subscription through even if
* we don't know about 'name'.
*/
int mboxlist_changesub(const char *name, const char *userid,
struct auth_state *auth_state, int add, int force)
{
struct mboxlist_entry *mbentry = NULL;
int r;
struct db *subs;
if ((r = mboxlist_opensubs(userid, &subs)) != 0) {
return r;
}
if (add && !force) {
/* Ensure mailbox exists and can be seen by user */
if ((r = mboxlist_lookup(name, &mbentry, NULL))!=0) {
mboxlist_closesubs(subs);
return r;
}
if ((cyrus_acl_myrights(auth_state, mbentry->acl) & ACL_LOOKUP) == 0) {
mboxlist_closesubs(subs);
mboxlist_entry_free(&mbentry);
return IMAP_MAILBOX_NONEXISTENT;
}
}
if (add) {
r = cyrusdb_store(subs, name, strlen(name), "", 0, NULL);
} else {
r = cyrusdb_delete(subs, name, strlen(name), NULL, 0);
/* if it didn't exist, that's ok */
if (r == CYRUSDB_EXISTS) r = CYRUSDB_OK;
}
switch (r) {
case CYRUSDB_OK:
r = 0;
break;
default:
r = IMAP_IOERROR;
break;
}
sync_log_subscribe(userid, name);
mboxlist_closesubs(subs);
mboxlist_entry_free(&mbentry);
return r;
}
/* Transaction Handlers */
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);
}
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);
}
/* Callback used by mboxlist_count_inferiors below */
static int _countmbox(void *rock,
const char *key __attribute__((unused)),
size_t keylen __attribute__((unused)),
const char *val __attribute__((unused)),
size_t vallen __attribute__((unused)))
{
int *count = (int *)rock;
(*count)++;
return 0;
}
/* Count how many children a mailbox has */
static int mboxlist_count_inferiors(const char *mboxname)
{
int count = 0;
char *prefix = strconcat(mboxname, ".", (char *)NULL);
mboxlist_allmbox(prefix, _countmbox, &count);
free(prefix);
return(count);
}

File Metadata

Mime Type
text/x-c
Expires
Fri, Apr 24, 1:59 PM (6 d, 11 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18749406
Default Alt Text
mboxlist.c (87 KB)

Event Timeline