Page MenuHomePhorge

mboxlist.c
No OneTemporary

Authored By
Unknown
Size
94 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 <syslog.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "acl.h"
#include "annotate.h"
#include "glob.h"
#include "assert.h"
#include "global.h"
#include "cyrusdb.h"
#include "util.h"
#include "mailbox.h"
#include "mboxevent.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;
EXPORTED 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);
EXPORTED mbentry_t *mboxlist_entry_create(void)
{
mbentry_t *ret = xzmalloc(sizeof(mbentry_t));
/* xxx - initialiser functions here? */
return ret;
}
EXPORTED void mboxlist_entry_free(mbentry_t **mbentryptr)
{
mbentry_t *mbentry = *mbentryptr;
/* idempotent */
if (!mbentry) return;
free(mbentry->name);
free(mbentry->partition);
free(mbentry->server);
free(mbentry->acl);
free(mbentry->uniqueid);
free(mbentry->legacy_specialuse);
free(mbentry);
*mbentryptr = NULL;
}
static void _write_acl(struct dlist *dl, const char *aclstr)
{
const char *p, *q;
struct dlist *al = dlist_newkvlist(dl, "A");
p = aclstr;
while (p && *p) {
char *name,*val;
q = strchr(p, '\t');
if (!q) break;
name = xstrndup(p, q-p);
q++;
p = strchr(q, '\t');
if (p) {
val = xstrndup(q, p-q);
p++;
}
else
val = xstrdup(q);
dlist_setatom(al, name, val);
free(name);
free(val);
}
}
EXPORTED const char *mboxlist_mbtype_to_string(uint32_t mbtype)
{
static struct buf buf = BUF_INITIALIZER;
buf_reset(&buf);
if (mbtype & MBTYPE_DELETED)
buf_putc(&buf, 'd');
if (mbtype & MBTYPE_MOVING)
buf_putc(&buf, 'm');
if (mbtype & MBTYPE_NETNEWS)
buf_putc(&buf, 'n');
if (mbtype & MBTYPE_REMOTE)
buf_putc(&buf, 'r');
if (mbtype & MBTYPE_RESERVE)
buf_putc(&buf, 'z');
return buf_cstring(&buf);
}
EXPORTED char *mboxlist_entry_cstring(mbentry_t *mbentry)
{
struct buf buf = BUF_INITIALIZER;
struct dlist *dl = dlist_newkvlist(NULL, mbentry->name);
if (mbentry->acl)
_write_acl(dl, mbentry->acl);
if (mbentry->uniqueid)
dlist_setatom(dl, "I", mbentry->uniqueid);
if (mbentry->partition)
dlist_setatom(dl, "P", mbentry->partition);
if (mbentry->server)
dlist_setatom(dl, "S", mbentry->server);
if (mbentry->mbtype)
dlist_setatom(dl, "T", mboxlist_mbtype_to_string(mbentry->mbtype));
if (mbentry->uidvalidity)
dlist_setnum32(dl, "V", mbentry->uidvalidity);
dlist_setdate(dl, "M", time(NULL));
dlist_printbuf(dl, 0, &buf);
dlist_free(&dl);
return buf_release(&buf);
}
/*
* 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 */
}
static char *_parse_acl(struct dlist *di)
{
struct buf buf = BUF_INITIALIZER;
struct dlist *ai;
/* gotta make it all tabby again */
for (ai = di->head; ai; ai = ai->next) {
buf_printf(&buf, "%s\t", ai->name);
buf_printf(&buf, "%s\t", dlist_cstring(ai));
}
return buf_release(&buf);
}
EXPORTED uint32_t mboxlist_string_to_mbtype(const char *string)
{
uint32_t mbtype = 0;
if (!string) return 0; /* null just means default */
for (; *string; string++) {
switch (*string) {
case 'd':
mbtype |= MBTYPE_DELETED;
break;
case 'm':
mbtype |= MBTYPE_MOVING;
break;
case 'n':
mbtype |= MBTYPE_NETNEWS;
break;
case 'r':
mbtype |= MBTYPE_REMOTE;
break;
case 'z':
mbtype |= MBTYPE_RESERVE;
break;
}
}
return mbtype;
}
/*
* parse a record read from the mailboxes.db into its parts.
*
* full dlist format is:
* A: _a_cl
* I: unique_i_d
* M: _m_time
* 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)
{
int r = IMAP_MAILBOX_BADFORMAT;
char *freeme = NULL;
char **target;
char *p, *q;
mbentry_t *mbentry = mboxlist_entry_create();
if (!datalen)
goto done;
/* copy name */
if (namelen)
mbentry->name = xstrndup(name, namelen);
else
mbentry->name = xstrdup(name);
/* check for DLIST mboxlist */
if (*data == '%') {
struct dlist *dl = NULL;
struct dlist *di;
r = dlist_parsemap(&dl, 0, data, datalen);
if (r) goto done;
if (!dl) goto done;
for (di = dl->head; di; di = di->next) {
switch(di->name[0]) {
case 'A':
mbentry->acl = _parse_acl(di);
break;
case 'I':
mbentry->uniqueid = xstrdupnull(dlist_cstring(di));
break;
case 'M':
mbentry->mtime = dlist_num(di);
break;
case 'P':
mbentry->partition = xstrdupnull(dlist_cstring(di));
break;
case 'S':
mbentry->server = xstrdupnull(dlist_cstring(di));
break;
case 'T':
mbentry->mbtype = mboxlist_string_to_mbtype(dlist_cstring(di));
break;
case 'V':
mbentry->uidvalidity = dlist_num(di);
break;
}
}
dlist_free(&dl);
r = 0;
goto done;
}
/* copy data */
freeme = p = xstrndup(data, datalen);
/* check for extended mboxlist entry */
if (*p == '(') {
int last = 0;
p++; /* past leading '(' */
while (!last) {
target = NULL;
q = p;
while (*q && *q != ' ' && *q != ')') q++;
if (*q != ' ') break;
*q++ = '\0';
if (!strcmp(p, "uniqueid")) target = &mbentry->uniqueid;
if (!strcmp(p, "specialuse")) target = &mbentry->legacy_specialuse;
p = q;
while (*q && *q != ' ' && *q != ')') q++;
if (*q != ' ') last = 1;
if (*q) *q++ = '\0';
if (target) *target = xstrdup(p);
p = q;
}
if (*p == ' ') p++; /* past trailing ' ' */
}
/* copy out interesting parts */
mbentry->mbtype = strtol(p, &p, 10);
if (*p == ' ') p++;
q = p;
while (*q && *q != ' ' && *q != '!') q++;
if (*q == '!') {
*q++ = '\0';
mbentry->server = xstrdup(p);
p = q;
while (*q && *q != ' ') q++;
}
if (*q) *q++ = '\0';
mbentry->partition = xstrdup(p);
mbentry->acl = xstrdup(q);
r = 0;
done:
if (!r && mbentryptr)
*mbentryptr = mbentry;
else mboxlist_entry_free(&mbentry);
free(freeme);
return r;
}
/* read a record and parse into parts */
static int mboxlist_mylookup(const char *name,
mbentry_t **mbentryptr,
struct txn **tid, int wrlock)
{
int r;
const char *data;
size_t datalen;
r = mboxlist_read(name, &data, &datalen, tid, wrlock);
if (r) return r;
return mboxlist_parse_entry(mbentryptr, name, 0, data, datalen);
}
/*
* Lookup 'name' in the mailbox list, ignoring reserved records
*/
EXPORTED int mboxlist_lookup(const char *name, mbentry_t **entryptr,
struct txn **tid)
{
mbentry_t *entry = NULL;
int r;
r = mboxlist_mylookup(name, &entry, tid, 0);
if (r) return r;
/* Ignore "reserved" entries, like they aren't there */
if (entry->mbtype & MBTYPE_RESERVE) {
mboxlist_entry_free(&entry);
return IMAP_MAILBOX_RESERVED;
}
/* Ignore "deleted" entries, like they aren't there */
if (entry->mbtype & MBTYPE_DELETED) {
mboxlist_entry_free(&entry);
return IMAP_MAILBOX_NONEXISTENT;
}
if (entryptr) *entryptr = entry;
else mboxlist_entry_free(&entry);
return 0;
}
/* now this is just silly, but hey */
EXPORTED int mboxlist_lookup_allow_reserved(const char *name,
mbentry_t **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 */
HIDDEN int mboxlist_findstage(const char *name, char *stagedir, size_t sd_len)
{
const char *root;
mbentry_t *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;
}
EXPORTED int mboxlist_update(mbentry_t *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;
}
EXPORTED int mboxlist_findparent(const char *mboxname,
mbentry_t **mbentryp)
{
mbentry_t *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_lookup(parent, &mbentry, NULL);
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)
{
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 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)
{
mbentry_t *mbentry = NULL;
const char *p;
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;
}
}
else if (r == IMAP_MAILBOX_NONEXISTENT) {
/* no parent mailbox */
if (!isadmin) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
p = mboxname_isusermailbox(mboxname, 0);
if (p) {
char *firstdot = strchr(p, '.');
if (!force_subdirs && firstdot) {
/* Disallow creating user.X.* when no user.X */
r = IMAP_PERMISSION_DENIED;
goto done;
}
}
/* 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;
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 */
if (config_getswitch(IMAPOPT_UNIXHIERARCHYSEP)) {
/*
* The mailboxname is now in the internal format,
* so we we need to change DOTCHARs back to '.'
* in the identifier in order to have the correct ACL.
*/
for (p = (char *)owner; *p; p++) {
if (*p == DOTCHAR) *p = '.';
}
}
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 */
EXPORTED 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)
*
*/
static 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)
{
int r;
char *newpartition = NULL;
char *mboxent = NULL;
char *acl = NULL;
struct mailbox *newmailbox = NULL;
int isremote = mbtype & MBTYPE_REMOTE;
mbentry_t *newmbentry = NULL;
mbentry_t *mbentry = NULL;
r = mboxlist_create_namecheck(mboxname, userid, auth_state,
isadmin, forceuser);
if (r) goto done;
/* check if a previous deleted mailbox existed */
r = mboxlist_mylookup(mboxname, &mbentry, NULL, 0);
if (!r && mbentry->mbtype == MBTYPE_DELETED) {
/* changing the unique id since last time? */
if (strcmpsafe(uniqueid, mbentry->uniqueid)) {
/* then the UIDVALIDITY must be higher than before */
if (uidvalidity <= mbentry->uidvalidity)
uidvalidity = mbentry->uidvalidity+1;
}
}
if (copyacl) {
acl = xstrdup(copyacl);
}
else {
r = mboxlist_create_acl(mboxname, &acl);
if (r) goto done;
}
r = mboxlist_create_partition(mboxname, partition, &newpartition);
if (r) goto done;
if (!dbonly && !isremote) {
/* Filesystem Operations */
r = mailbox_create(mboxname, mbtype, newpartition, acl, uniqueid,
options, uidvalidity, &newmailbox);
if (r) goto done; /* CREATE failed */
}
/* all is well - activate the mailbox */
newmbentry = mboxlist_entry_create();
newmbentry->acl = xstrdupnull(acl);
newmbentry->mbtype = mbtype;
newmbentry->partition = xstrdupnull(newpartition);
if (newmailbox) {
newmbentry->uniqueid = xstrdupnull(newmailbox->uniqueid);
newmbentry->uidvalidity = newmailbox->i.uidvalidity;
}
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(&mbentry);
mboxlist_entry_free(&newmbentry);
return r;
}
EXPORTED 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,
int notify, struct mailbox **mailboxptr)
{
int options = config_getint(IMAPOPT_MAILBOX_DEFAULT_OPTIONS)
| OPT_POP3_NEW_UIDL;
int r;
struct mailbox *mailbox = NULL;
r = mboxlist_createmailbox_full(name, mbtype, partition,
isadmin, userid, auth_state,
options, 0, NULL, NULL, localonly,
forceuser, dbonly, &mailbox);
if (notify && !r) {
/* send a MailboxCreate event notification */
struct mboxevent *mboxevent = mboxevent_new(EVENT_MAILBOX_CREATE);
mboxevent_extract_mailbox(mboxevent, mailbox);
mboxevent_notify(mboxevent);
mboxevent_free(&mboxevent);
}
if (mailboxptr && !r) *mailboxptr = mailbox;
else mailbox_close(&mailbox);
return r;
}
EXPORTED 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);
}
/* insert an entry for the proxy */
EXPORTED int mboxlist_insertremote(mbentry_t *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;
}
else {
/* make sure it's a remote mailbox */
mbentry->mbtype |= MBTYPE_REMOTE;
}
}
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. */
EXPORTED int mboxlist_deleteremote(const char *name, struct txn **in_tid)
{
int r;
struct txn **tid;
struct txn *lcl_tid = NULL;
mbentry_t *mbentry = NULL;
if(in_tid) {
tid = in_tid;
} else {
tid = &lcl_tid;
}
retry:
r = mboxlist_mylookup(name, &mbentry, tid, 1);
switch (r) {
case 0:
break;
case IMAP_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
*/
EXPORTED int
mboxlist_delayed_deletemailbox(const char *name, int isadmin,
const char *userid,
struct auth_state *auth_state,
struct mboxevent *mboxevent,
int checkacl,
int force)
{
mbentry_t *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_lookup(name, &mbentry, NULL);
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,
mboxevent,
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
*
*/
EXPORTED int mboxlist_deletemailbox(const char *name, int isadmin,
const char *userid,
struct auth_state *auth_state,
struct mboxevent *mboxevent,
int checkacl,
int local_only, int force)
{
mbentry_t *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_lookup(name, &mbentry, NULL);
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;
if (!isremote && !mboxname_isdeletedmailbox(name, NULL)) {
/* store a DELETED marker */
mbentry_t *newmbentry = mboxlist_entry_create();
newmbentry->name = xstrdupnull(name);
newmbentry->mbtype = MBTYPE_DELETED;
if (mailbox) {
newmbentry->uidvalidity = mailbox->i.uidvalidity;
newmbentry->uniqueid = xstrdupnull(mailbox->uniqueid);
}
r = mboxlist_update(newmbentry, /*localonly*/1);
mboxlist_entry_free(&newmbentry);
}
else {
/* delete entry (including DELETED.* mailboxes, no need
* to keep that rubbish around) */
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);
mboxevent_extract_mailbox(mboxevent, mailbox);
r = mailbox_delete(&mailbox);
/* abort event notification */
if (r && mboxevent)
mboxevent_free(&mboxevent);
}
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
*/
EXPORTED int mboxlist_renamemailbox(const char *oldname, const char *newname,
const char *partition, unsigned uidvalidity,
int isadmin, const char *userid,
struct auth_state *auth_state,
struct mboxevent *mboxevent,
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;
mbentry_t *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 = xstrdupnull(newpartition);
newmbentry->acl = xstrdupnull(oldmailbox->acl);
newmbentry->uidvalidity = oldmailbox->i.uidvalidity;
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->name = xstrdupnull(newmailbox->name);
newmbentry->mbtype = newmailbox->mbtype;
newmbentry->partition = xstrdupnull(newmailbox->part);
newmbentry->acl = xstrdupnull(newmailbox->acl);
newmbentry->uidvalidity = newmailbox->i.uidvalidity;
newmbentry->uniqueid = xstrdupnull(newmailbox->uniqueid);
mboxent = mboxlist_entry_cstring(newmbentry);
do {
r = 0;
/* delete the old entry */
if (!isusermbox) {
/* store a DELETED marker */
char *oldmboxent = NULL;
mbentry_t *oldmbentry = mboxlist_entry_create();
oldmbentry->name = xstrdupnull(oldmailbox->name);
oldmbentry->mbtype = MBTYPE_DELETED;
oldmbentry->uidvalidity = oldmailbox->i.uidvalidity;
oldmbentry->uniqueid = xstrdupnull(oldmailbox->uniqueid);
oldmboxent = mboxlist_entry_cstring(oldmbentry);
r = cyrusdb_store(mbdb, oldname, strlen(oldname),
oldmboxent, strlen(oldmboxent), &tid);
mboxlist_entry_free(&oldmbentry);
free(oldmboxent);
}
/* 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: rename failed on store %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: rename 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) {
/* 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);
}
}
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(struct namespace *namespace,
const char *name, int domainlen,
const char *user, int userlen)
{
struct buf extname = BUF_INITIALIZER;
const char *username = NULL;
const char *dot_position = NULL;
/* is_user_mbox */
if (strncmp(name+domainlen, "user.", 5))
return 0;
/* get external representation of owner to check against given user */
username = name + domainlen + 5;
dot_position = strchr(username, '.');
buf_setmap(&extname, username, dot_position ?
dot_position - username : (int)strlen(username));
mboxname_hiersep_toexternal(namespace,
(char *)buf_cstring(&extname), buf_len(&extname));
/* starts_with_user */
if (strncmp(buf_cstring(&extname), user, userlen)) {
buf_free(&extname);
return 0;
}
buf_free(&extname);
/* is_exactly_user */
if (!(username[userlen] == '\0' || username[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
*
*/
EXPORTED int mboxlist_setacl(struct namespace *namespace, const char *name,
const char *identifier, const char *rights,
int isadmin, const char *userid,
struct auth_state *auth_state)
{
mbentry_t *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(namespace, name, domainlen, userid, useridlen)) {
isusermbox = 1;
}
anyoneuseracl = config_getswitch(IMAPOPT_ANYONEUSERACL);
/* checks if the identifier is the mailbox owner */
if (mboxlist_is_owner(namespace, 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 | MBTYPE_DELETED)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
}
/* if it is not a remote mailbox, we need to unlock the mailbox list,
* lock the mailbox, and re-lock the mailboxes list */
/* we must do this to obey our locking rules */
if (!r && !(mbentry->mbtype & MBTYPE_REMOTE)) {
cyrusdb_abort(mbdb, tid);
tid = NULL;
mboxlist_entry_free(&mbentry);
/* open & lock mailbox header */
r = mailbox_open_iwl(name, &mailbox);
if (!r) {
do {
/* lookup the mailbox to make sure it exists and get its acl */
r = mboxlist_mylookup(name, &mbentry, &tid, 1);
} while (r == IMAP_AGAIN);
}
if(r) goto done;
}
/* 2. Check Rights */
if (!r && !isadmin) {
myrights = cyrus_acl_myrights(auth_state, mbentry->acl);
if (!(myrights & ACL_ADMIN)) {
r = (myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
/* 2.1 Only admin user can set 'anyone' rights if config says so */
if (!r && !isadmin && !anyoneuseracl && !strncmp(identifier, "anyone", 6)) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
/* 3. Set DB Entry */
if(!r) {
/* Make change to ACL */
newacl = xstrdup(mbentry->acl);
if (rights && *rights) {
/* rights are present and non-empty */
mode = ACL_MODE_SET;
if (*rights == '+') {
rights++;
mode = ACL_MODE_ADD;
}
else if (*rights == '-') {
rights++;
mode = ACL_MODE_REMOVE;
}
/* do not allow non-admin user to remove the admin rights from mailbox owner */
if (!isadmin && isidentifiermbox) {
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;
}
}
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 */
free(mbentry->acl);
mbentry->acl = xstrdupnull(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;
}
/* 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_notify(mboxevent);
mboxevent_free(&mboxevent);
}
/* 4. Change backup copy (cyrus.header) */
/* we already have it locked from above */
if (!r && !(mbentry->mbtype & MBTYPE_REMOTE)) {
mailbox_set_acl(mailbox, newacl, 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
*
*/
EXPORTED int
mboxlist_sync_setacls(const char *name, const char *newacl)
{
mbentry_t *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 | MBTYPE_DELETED)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
}
/* 2. Set DB Entry */
if (!r) {
/* ok, change the database */
free(mbentry->acl);
mbentry->acl = xstrdupnull(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;
}
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 issubs;
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;
mbentry_t *mbentry = NULL;
/* 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;
}
/* subs DB has empty keys */
if (rock->issubs)
return 1;
/* ignore entirely deleted records */
if (mboxlist_parse_entry(&mbentry, key, keylen, data, datalen))
return 0;
if (mbentry->mbtype & MBTYPE_DELETED) {
mboxlist_entry_free(&mbentry);
return 0;
}
/* check acl */
if (!rock->isadmin) {
int rights = cyrus_acl_myrights(rock->auth_state, mbentry->acl);
if (!(rights & ACL_LOOKUP)) {
mboxlist_entry_free(&mbentry);
return 0;
}
}
mboxlist_entry_free(&mbentry);
/* 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;
}
static int skipdel_cb(void *rock __attribute__((unused)),
const char *key,
size_t keylen,
const char *data,
size_t datalen)
{
mbentry_t *mbentry = NULL;
int r;
int res = 1;
r = mboxlist_parse_entry(&mbentry, key, keylen, data, datalen);
if (r) return 0;
if (mbentry->mbtype & MBTYPE_DELETED)
res = 0;
mboxlist_entry_free(&mbentry);
return res;
}
EXPORTED int mboxlist_allmbox(const char *prefix, foreach_cb *proc, void *rock,
int incdel)
{
int r;
char *search = prefix ? (char *)prefix : "";
if (incdel)
r = cyrusdb_foreach(mbdb, search, strlen(search), NULL, proc, rock, 0);
else
r = cyrusdb_foreach(mbdb, search, strlen(search), skipdel_cb, proc, rock, 0);
return r;
}
EXPORTED int mboxlist_allusermbox(const char *userid, foreach_cb *proc,
void *rock, int include_deleted)
{
char *inbox = mboxname_user_mbox(userid, 0);
size_t inboxlen = strlen(inbox);
char *search = strconcat(inbox, ".", (char *)NULL);
const char *data = NULL;
size_t datalen = 0;
int r;
r = cyrusdb_fetch(mbdb, inbox, inboxlen, &data, &datalen, NULL);
if (r) goto done;
/* process inbox first */
r = proc(rock, inbox, inboxlen, data, datalen);
if (r) goto done;
/* process all the sub folders */
r = cyrusdb_foreach(mbdb, search, strlen(search), NULL, proc, rock, 0);
if (r) goto done;
/* don't check if delayed delete is enabled, maybe the caller wants to
* clean up deleted stuff after it's been turned off */
if (include_deleted) {
const char *prefix = config_getstring(IMAPOPT_DELETEDPREFIX);
char *name = strconcat(prefix, ".", inbox, ".", (char *)NULL);
r = cyrusdb_foreach(mbdb, name, strlen(name), NULL, proc, rock, 0);
free(name);
}
done:
free(search);
free(inbox);
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'. */
EXPORTED 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.issubs = 0;
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;
}
HIDDEN 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.issubs = 0;
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.
*/
EXPORTED 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;
struct mboxevent *mboxevents = NULL;
struct mboxevent *quotachange_event = NULL;
struct mboxevent *quotawithin_event = NULL;
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;
/* has it changed? */
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
if (q.limits[res] != newquotas[res]) {
underquota = 0;
/* prepare a QuotaChange event notification */
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)
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 {
mbentry_t *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 */
memcpy(q.limits, newquotas, sizeof(q.limits));
r = quota_write(&q, &tid);
if (r) goto done;
/* prepare a QuotaChange event notification */
quotachange_event = mboxevent_enqueue(EVENT_QUOTA_CHANGE, &mboxevents);
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
if (q.limits[res] != QUOTA_UNLIMITED)
mboxevent_extract_quota(quotachange_event, &q, res);
}
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:
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)
{
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;
}
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
*/
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);
done:
quota_free(&q);
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 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 */
EXPORTED void mboxlist_init(int myflags)
{
if (myflags & MBOXLIST_SYNC) {
cyrusdb_sync(DB);
}
}
EXPORTED void mboxlist_open(const char *fname)
{
int ret, flags;
char *tofree = NULL;
if (!fname)
fname = config_getstring(IMAPOPT_MBOXLIST_DB_PATH);
/* create db file name */
if (!fname) {
tofree = strconcat(config_dir, FNAME_MBOXLIST, (char *)NULL);
fname = tofree;
}
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;
}
EXPORTED 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;
}
}
EXPORTED 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.
*/
EXPORTED 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
xstrncpy(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.issubs = 1;
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;
}
EXPORTED 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;
}
HIDDEN 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.issubs = 1;
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! */
EXPORTED 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'.
*/
EXPORTED int mboxlist_changesub(const char *name, const char *userid,
struct auth_state *auth_state,
int add, int force, int notify)
{
mbentry_t *mbentry = NULL;
int r;
struct db *subs;
struct mboxevent *mboxevent;
if ((r = mboxlist_opensubs(userid, &subs)) != 0) {
return r;
}
if (add && !force) {
/* Ensure mailbox exists and can be seen by user */
if ((r = mboxlist_lookup(name, &mbentry, NULL))!=0) {
mboxlist_closesubs(subs);
return r;
}
if ((cyrus_acl_myrights(auth_state, mbentry->acl) & ACL_LOOKUP) == 0) {
mboxlist_closesubs(subs);
mboxlist_entry_free(&mbentry);
return IMAP_MAILBOX_NONEXISTENT;
}
}
if (add) {
r = cyrusdb_store(subs, name, strlen(name), "", 0, NULL);
} else {
r = cyrusdb_delete(subs, name, strlen(name), NULL, 0);
/* if it didn't exist, that's ok */
if (r == CYRUSDB_EXISTS) r = CYRUSDB_OK;
}
switch (r) {
case CYRUSDB_OK:
r = 0;
break;
default:
r = IMAP_IOERROR;
break;
}
sync_log_subscribe(userid, name);
mboxlist_closesubs(subs);
mboxlist_entry_free(&mbentry);
/* prepare a MailboxSubscribe or MailboxUnSubscribe event notification */
if (notify && r == 0) {
mboxevent = mboxevent_new(add ? EVENT_MAILBOX_SUBSCRIBE :
EVENT_MAILBOX_UNSUBSCRIBE);
mboxevent_set_access(mboxevent, NULL, NULL, userid, name);
mboxevent_notify(mboxevent);
mboxevent_free(&mboxevent);
}
return r;
}
/* Transaction Handlers */
EXPORTED int mboxlist_commit(struct txn *tid)
{
assert(tid);
return cyrusdb_commit(mbdb, tid);
}
int mboxlist_abort(struct txn *tid)
{
assert(tid);
return cyrusdb_abort(mbdb, tid);
}
EXPORTED int mboxlist_delayed_delete_isenabled(void)
{
enum enum_value config_delete_mode = config_getenum(IMAPOPT_DELETE_MODE);
return(config_delete_mode == IMAP_ENUM_DELETE_MODE_DELAYED);
}

File Metadata

Mime Type
text/x-c
Expires
Fri, Apr 24, 2:00 PM (6 d, 15 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18896764
Default Alt Text
mboxlist.c (94 KB)

Event Timeline