/* mboxname.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 <errno.h>
#include <stdio.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "assert.h"
#include "byteorder.h"
#include "crc32.h"
#include "glob.h"
#include "global.h"
#include "mailbox.h"
#include "map.h"
#include "retry.h"
#include "user.h"
#include "util.h"
#include "xmalloc.h"
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
#include "mboxname.h"
#include "mboxlist.h"
#include "cyr_lock.h"
struct mboxlocklist {
struct mboxlocklist *next;
struct mboxlock l;
int nopen;
};
static struct mboxlocklist *open_mboxlocks = NULL;
static struct namespace *admin_namespace;
struct mbname_parts {
/* master data */
strarray_t *boxes;
time_t is_deleted;
char *localpart;
char *domain;
/* actual namespace */
const struct namespace *extns;
char *extuserid;
/* cache data */
char *userid;
char *intname;
char *extname;
char *recipient;
};
#define XX 127
/*
* Table for decoding modified base64 for IMAP UTF-7 mailbox names
*/
static const char index_mod64[256] = {
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, 63,XX,XX,XX,
52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};
#define CHARMOD64(c) (index_mod64[(unsigned char)(c)])
#define FNAME_SHAREDPREFIX "shared"
EXPORTED int open_mboxlocks_exist(void)
{
return open_mboxlocks ? 1 : 0;
}
static struct mboxlocklist *create_lockitem(const char *name)
{
struct mboxlocklist *item = xmalloc(sizeof(struct mboxlocklist));
item->next = open_mboxlocks;
open_mboxlocks = item;
item->nopen = 1;
item->l.name = xstrdup(name);
item->l.lock_fd = -1;
item->l.locktype = 0;
return item;
}
static struct mboxlocklist *find_lockitem(const char *name)
{
struct mboxlocklist *item;
for (item = open_mboxlocks; item; item = item->next) {
if (!strcmp(name, item->l.name))
return item;
}
return NULL;
}
static void remove_lockitem(struct mboxlocklist *remitem)
{
struct mboxlocklist *item;
struct mboxlocklist *previtem = NULL;
for (item = open_mboxlocks; item; item = item->next) {
if (item == remitem) {
if (previtem)
previtem->next = item->next;
else
open_mboxlocks = item->next;
if (item->l.lock_fd != -1) {
if (item->l.locktype)
lock_unlock(item->l.lock_fd, item->l.name);
close(item->l.lock_fd);
}
free(item->l.name);
free(item);
return;
}
previtem = item;
}
fatal("didn't find item in list", EX_SOFTWARE);
}
/* name locking support */
EXPORTED int mboxname_lock(const char *mboxname, struct mboxlock **mboxlockptr,
int locktype_and_flags)
{
const char *fname;
int r = 0;
struct mboxlocklist *lockitem;
int nonblock;
int locktype;
nonblock = !!(locktype_and_flags & LOCK_NONBLOCK);
locktype = (locktype_and_flags & ~LOCK_NONBLOCK);
fname = mboxname_lockpath(mboxname);
if (!fname)
return IMAP_MAILBOX_BADNAME;
lockitem = find_lockitem(mboxname);
/* already open? just use this one */
if (lockitem) {
/* can't change locktype! */
if (lockitem->l.locktype != locktype)
return IMAP_MAILBOX_LOCKED;
lockitem->nopen++;
goto done;
}
lockitem = create_lockitem(mboxname);
/* assume success, and only create directory on failure.
* More efficient on a common codepath */
lockitem->l.lock_fd = open(fname, O_CREAT | O_TRUNC | O_RDWR, 0666);
if (lockitem->l.lock_fd == -1) {
if (cyrus_mkdir(fname, 0755) == -1) {
r = IMAP_IOERROR;
goto done;
}
lockitem->l.lock_fd = open(fname, O_CREAT | O_TRUNC | O_RDWR, 0666);
}
/* but if it still didn't succeed, we have problems */
if (lockitem->l.lock_fd == -1) {
r = IMAP_IOERROR;
goto done;
}
r = lock_setlock(lockitem->l.lock_fd,
locktype == LOCK_EXCLUSIVE,
nonblock, fname);
if (!r) lockitem->l.locktype = locktype;
else if (errno == EWOULDBLOCK) r = IMAP_MAILBOX_LOCKED;
else r = errno;
done:
if (r) remove_lockitem(lockitem);
else *mboxlockptr = &lockitem->l;
return r;
}
EXPORTED void mboxname_release(struct mboxlock **mboxlockptr)
{
if (!*mboxlockptr) return;
struct mboxlocklist *lockitem;
struct mboxlock *lock = *mboxlockptr;
lockitem = find_lockitem(lock->name);
assert(lockitem && &lockitem->l == lock);
*mboxlockptr = NULL;
if (lockitem->nopen > 1) {
lockitem->nopen--;
return;
}
remove_lockitem(lockitem);
}
EXPORTED int mboxname_islocked(const char *mboxname)
{
struct mboxlocklist *lockitem = find_lockitem(mboxname);
if (!lockitem) return 0;
return lockitem->l.locktype;
}
EXPORTED struct mboxlock *mboxname_usernamespacelock(const char *mboxname)
{
mbname_t *mbname = mbname_from_intname(mboxname);
struct mboxlock *lock = user_namespacelock(mbname_userid(mbname));
mbname_free(&mbname);
return lock;
}
/******************** mbname stuff **********************/
static void _mbdirty(mbname_t *mbname)
{
free(mbname->userid);
free(mbname->intname);
free(mbname->extname);
free(mbname->recipient);
mbname->userid = NULL;
mbname->intname = NULL;
mbname->extname = NULL;
mbname->recipient = NULL;
}
EXPORTED void mbname_downcaseuser(mbname_t *mbname)
{
_mbdirty(mbname);
if (mbname->localpart) lcase(mbname->localpart);
if (mbname->domain) lcase(mbname->domain);
}
EXPORTED void mbname_set_localpart(mbname_t *mbname, const char *localpart)
{
_mbdirty(mbname);
free(mbname->localpart);
mbname->localpart = xstrdupnull(localpart);
}
EXPORTED void mbname_set_domain(mbname_t *mbname, const char *domain)
{
_mbdirty(mbname);
free(mbname->domain);
mbname->domain = strcmpsafe(domain, config_defdomain) ? xstrdupnull(domain) : NULL;
}
EXPORTED void mbname_set_boxes(mbname_t *mbname, const strarray_t *boxes)
{
_mbdirty(mbname);
strarray_free(mbname->boxes);
if (boxes)
mbname->boxes = strarray_dup(boxes);
else
mbname->boxes = NULL;
}
EXPORTED void mbname_push_boxes(mbname_t *mbname, const char *item)
{
_mbdirty(mbname);
if (!mbname->boxes) mbname->boxes = strarray_new();
strarray_push(mbname->boxes, item);
}
EXPORTED char *mbname_pop_boxes(mbname_t *mbname)
{
_mbdirty(mbname);
if (!mbname->boxes) mbname->boxes = strarray_new();
return strarray_pop(mbname->boxes);
}
EXPORTED void mbname_truncate_boxes(mbname_t *mbname, size_t len)
{
_mbdirty(mbname);
if (!mbname->boxes) mbname->boxes = strarray_new();
strarray_truncate(mbname->boxes, len);
}
EXPORTED void mbname_set_isdeleted(mbname_t *mbname, time_t isdel)
{
_mbdirty(mbname);
mbname->is_deleted = isdel;
}
EXPORTED mbname_t *mbname_from_userid(const char *userid)
{
mbname_t *mbname = xzmalloc(sizeof(mbname_t));
const char *p;
if (!userid)
return mbname;
if (!*userid)
return mbname; // empty string, *sigh*
mbname->userid = xstrdup(userid); // may as well cache it
p = strrchr(userid, '@');
if (p) {
mbname->localpart = xstrndup(userid, p - userid);
const char *domain = p+1;
mbname->domain = strcmpsafe(domain, config_defdomain) ? xstrdupnull(domain) : NULL;
}
else {
mbname->localpart = xstrdup(userid);
}
return mbname;
}
EXPORTED mbname_t *mbname_from_recipient(const char *recipient, const struct namespace *ns)
{
mbname_t *mbname = xzmalloc(sizeof(mbname_t));
if (!recipient)
return mbname;
mbname->recipient = xstrdup(recipient); // may as well cache it
mbname->extns = ns;
const char *at = strrchr(recipient, '@');
if (at) {
mbname->localpart = xstrndup(recipient, at - recipient);
const char *domain = at+1;
if (config_virtdomains && strcmpsafe(domain, config_defdomain))
mbname->domain = xstrdupnull(domain);
/* otherwise we ignore domain entirely */
}
else {
mbname->localpart = xstrdup(recipient);
}
char *plus = strchr(mbname->localpart, '+');
if (plus) {
char sep[2];
sep[0] = ns->hier_sep;
sep[1] = '\0';
*plus = '\0';
/* Encode mailbox name in IMAP UTF-7 */
charset_t cs = charset_lookupname("utf-8");
char *detail =
charset_to_imaputf7(plus+1, strlen(plus+1), cs, ENCODING_NONE);
mbname->boxes = strarray_split(detail, sep, /*flags*/0);
charset_free(&cs);
free(detail);
}
else
mbname->boxes = strarray_new();
return mbname;
}
EXPORTED mbname_t *mbname_from_extsub(const char *subfolder, const struct namespace *ns, const char *userid)
{
mbname_t *mbname = mbname_from_userid(userid);
if (!subfolder)
return mbname;
/* we know boxes isn't set already */
assert(!mbname->boxes);
char sep[2];
sep[0] = ns->hier_sep;
sep[1] = '\0';
mbname->boxes = strarray_split(subfolder, sep, /*flags*/0);
return mbname;
}
EXPORTED mbname_t *mbname_dup(const mbname_t *orig)
{
mbname_t *mbname = xzmalloc(sizeof(mbname_t));
mbname->localpart = xstrdupnull(orig->localpart);
mbname->domain = xstrdupnull(orig->domain);
mbname->is_deleted = orig->is_deleted;
if (orig->boxes) mbname->boxes = strarray_dup(orig->boxes);
return mbname;
}
static void _append_intbuf(struct buf *buf, const char *val)
{
const char *p;
for (p = val; *p; p++) {
switch (*p) {
case '.':
buf_putc(buf, '^');
break;
default:
buf_putc(buf, *p);
break;
}
}
}
static strarray_t *_array_from_intname(strarray_t *a)
{
int i;
for (i = 0; i < strarray_size(a); i++) {
char *p;
for (p = a->data[i]; *p; p++) {
switch (*p) {
case '^':
*p = '.';
break;
default:
break;
}
}
}
return a;
}
static void _append_extbuf(const struct namespace *ns, struct buf *buf, const char *val)
{
const char *p;
int isuhs = (ns->hier_sep == '/');
for (p = val; *p; p++) {
switch (*p) {
case '.':
if (isuhs) buf_putc(buf, '.');
else buf_putc(buf, '^');
break;
default:
buf_putc(buf, *p);
break;
}
}
}
static strarray_t *_array_from_extname(const struct namespace *ns, strarray_t *a)
{
int i;
int isuhs = (ns->hier_sep == '/');
for (i = 0; i < strarray_size(a); i++) {
char *p;
for (p = a->data[i]; *p; p++) {
switch (*p) {
case '^':
if (isuhs) goto err;
else *p = '.';
break;
case '/':
goto err;
default:
break;
}
}
}
return a;
err:
strarray_free(a);
return NULL;
}
EXPORTED mbname_t *mbname_from_intname(const char *intname)
{
mbname_t *mbname = xzmalloc(sizeof(mbname_t));
const char *p;
if (!intname)
return mbname;
if (!*intname)
return mbname; // empty string, *sigh*
const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX);
mbname->intname = xstrdup(intname); // may as well cache it
p = strchr(intname, '!');
if (p) {
mbname->domain = xstrndup(intname, p - intname);
if (!strcmpsafe(mbname->domain, config_defdomain)) {
free(mbname->domain);
mbname->domain = NULL;
}
intname = p+1;
}
mbname->boxes = _array_from_intname(strarray_split(intname, ".", 0));
if (!strarray_size(mbname->boxes))
return mbname;
if (strarray_size(mbname->boxes) > 2 && !strcmpsafe(strarray_nth(mbname->boxes, 0), dp)) {
free(strarray_shift(mbname->boxes));
char *delval = strarray_pop(mbname->boxes);
mbname->is_deleted = strtoul(delval, NULL, 16);
free(delval);
}
if (strarray_size(mbname->boxes) > 1 && !strcmpsafe(strarray_nth(mbname->boxes, 0), "user")) {
free(strarray_shift(mbname->boxes));
mbname->localpart = strarray_shift(mbname->boxes);
}
return mbname;
}
EXPORTED mbname_t *mbname_from_extname(const char *extname, const struct namespace *ns, const char *userid)
{
int crossdomains = config_getswitch(IMAPOPT_CROSSDOMAINS) && !ns->isadmin;
int cdother = config_getswitch(IMAPOPT_CROSSDOMAINS_ONLYOTHER);
/* old-school virtdomains requires admin to be a different domain than the userid */
int admindomains = config_virtdomains && ns->isadmin;
/* specialuse magic */
if (extname && extname[0] == '\\') {
char *intname = mboxlist_find_specialuse(extname, userid);
mbname_t *mbname = mbname_from_intname(intname);
free(intname);
return mbname;
}
mbname_t *mbname = xzmalloc(sizeof(mbname_t));
char sepstr[2];
char *p = NULL;
if (!extname)
return mbname;
if (!*extname)
return mbname; // empty string, *sigh*
sepstr[0] = ns->hier_sep;
sepstr[1] = '\0';
mbname->extname = xstrdup(extname); // may as well cache it
mbname_t *userparts = mbname_from_userid(userid);
if (admindomains) {
p = strrchr(mbname->extname, '@');
if (p) {
*p = '\0';
if (strcmpsafe(p+1, config_defdomain))
mbname->domain = xstrdup(p+1);
}
else {
// domain admin?
mbname->domain = xstrdupnull(mbname_domain(userparts));
}
}
else if (!crossdomains) {
// non-crossdomains, we're always in the user's domain
mbname->domain = xstrdupnull(mbname_domain(userparts));
}
mbname->boxes = _array_from_extname(ns, strarray_split(mbname->extname, sepstr, 0));
if (p) *p = '@'; // rebuild extname for later use
if (!mbname->boxes)
goto done;
if (!strarray_size(mbname->boxes))
goto done;
if (ns->isalt) {
/* admin can't be in here, so we can ignore that :) - and hence also
* the DELETED namespace */
assert(!ns->isadmin);
const char *toplevel = strarray_nth(mbname->boxes, 0);
const char *up = config_getstring(IMAPOPT_USERPREFIX);
const char *sp = config_getstring(IMAPOPT_SHAREDPREFIX);
const char *ap = config_getstring(IMAPOPT_ALTPREFIX);
if (!strcmpsafe(toplevel, ap)) {
free(strarray_shift(mbname->boxes));
/* everything belongs to the userid */
mbname->localpart = xstrdupnull(mbname_localpart(userparts));
/* otherwise it was done above */
if (crossdomains) mbname->domain = xstrdupnull(mbname_domain(userparts));
goto done;
}
else if (!strcmpsafe(toplevel, up)) {
/* other user namespace */
free(strarray_shift(mbname->boxes));
mbname->localpart = strarray_shift(mbname->boxes);
if (crossdomains && mbname->localpart) {
char *p = strrchr(mbname->localpart, '@');
if (p) {
*p = '\0';
if (strcmpsafe(p+1, config_defdomain))
mbname->domain = xstrdup(p+1);
}
else if (cdother) {
mbname->domain = xstrdupnull(mbname_domain(userparts));
}
/* otherwise it must be in defdomain. Domains are
* always specified in crossdomains */
}
goto done;
}
else if (!strcmpsafe(toplevel, sp)) {
/* shared namespace, no user */
free(strarray_shift(mbname->boxes));
if (crossdomains) {
const char *toplevel = strarray_nth(mbname->boxes, 0);
if (toplevel && strrchr(toplevel, '@')) {
char *p = (char *)strrchr(toplevel, '@');
*p = '\0';
if (strcmpsafe(p+1, config_defdomain))
mbname->domain = xstrdup(p+1);
}
else if (cdother) {
mbname->domain = xstrdupnull(mbname_domain(userparts));
}
}
goto done;
}
/* everything else belongs to the userid */
mbname->localpart = xstrdupnull(mbname_localpart(userparts));
/* otherwise it was done above */
if (crossdomains) mbname->domain = xstrdupnull(mbname_domain(userparts));
/* special case INBOX case, because horrible */
if (!strcasecmpsafe(toplevel, "INBOX")) {
if (strarray_size(mbname->boxes) == 1) {
free(strarray_shift(mbname->boxes));
}
else {
/* force to upper case */
char *p = (char *)toplevel;
for (; *p; ++p)
*p = toupper(*p);
}
}
goto done;
}
const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX);
/* special inbox with insensitivity still, because horrible */
if (!strcasecmpsafe(strarray_nth(mbname->boxes, 0), "INBOX")) {
free(strarray_shift(mbname->boxes));
mbname->localpart = xstrdupnull(mbname_localpart(userparts));
/* otherwise it was done above */
if (crossdomains) mbname->domain = xstrdupnull(mbname_domain(userparts));
goto done;
}
/* deleted prefix first */
if (ns->isadmin && !strcmpsafe(strarray_nth(mbname->boxes, 0), dp)) {
free(strarray_shift(mbname->boxes));
char *delval = strarray_pop(mbname->boxes);
if (!delval)
goto done;
mbname->is_deleted = strtoul(delval, NULL, 16);
free(delval);
}
if (!strarray_size(mbname->boxes))
goto done;
/* now look for user */
if (!strcmpsafe(strarray_nth(mbname->boxes, 0), "user")) {
free(strarray_shift(mbname->boxes));
mbname->localpart = strarray_shift(mbname->boxes);
if (crossdomains && mbname->localpart) {
char *p = strrchr(mbname->localpart, '@');
if (p) {
*p = '\0';
if (strcmpsafe(p+1, config_defdomain))
mbname->domain = xstrdup(p+1);
}
else if (cdother) {
mbname->domain = xstrdupnull(mbname_domain(userparts));
}
}
goto done;
}
/* shared folders: are in user's domain unless admin */
if ((config_virtdomains && !ns->isadmin) || crossdomains) {
free(mbname->domain);
mbname->domain = xstrdupnull(mbname_domain(userparts));
}
done:
mbname_free(&userparts);
return mbname;
}
EXPORTED void mbname_free(mbname_t **mbnamep)
{
mbname_t *mbname = *mbnamep;
if (!mbname) return;
*mbnamep = NULL;
strarray_free(mbname->boxes);
free(mbname->localpart);
free(mbname->domain);
/* cached values */
free(mbname->userid);
free(mbname->intname);
free(mbname->extname);
free(mbname->extuserid);
free(mbname->recipient);
/* thing itself */
free(mbname);
}
EXPORTED char *mboxname_to_userid(const char *intname)
{
mbname_t *mbname = mbname_from_intname(intname);
char *res = xstrdupnull(mbname_userid(mbname));
mbname_free(&mbname);
return res;
}
EXPORTED char *mboxname_from_externalUTF8(const char *extname,
const struct namespace *ns,
const char *userid)
{
char *intname, *freeme = NULL;
if (config_getswitch(IMAPOPT_SIEVE_UTF8FILEINTO)) {
charset_t cs = charset_lookupname("utf-8");
if (cs == CHARSET_UNKNOWN_CHARSET) {
/* huh? */
syslog(LOG_INFO, "charset utf-8 is unknown");
return NULL;
}
/* Encode mailbox name in IMAP UTF-7 */
freeme = charset_to_imaputf7(extname, strlen(extname), cs, ENCODING_NONE);
charset_free(&cs);
if (!freeme) {
syslog(LOG_ERR, "Could not convert mailbox name to IMAP UTF-7.");
return NULL;
}
extname = freeme;
}
intname = mboxname_from_external(extname, ns, userid);
free(freeme);
return intname;
}
EXPORTED char *mboxname_from_external(const char *extname, const struct namespace *ns, const char *userid)
{
mbname_t *mbname = mbname_from_extname(extname, ns, userid);
char *res = xstrdupnull(mbname_intname(mbname));
mbname_free(&mbname);
return res;
}
EXPORTED char *mboxname_to_external(const char *intname, const struct namespace *ns, const char *userid)
{
mbname_t *mbname = mbname_from_intname(intname);
char *res = xstrdupnull(mbname_extname(mbname, ns, userid));
mbname_free(&mbname);
return res;
}
/* all mailboxes have an internal name representation, so this
* function should never return a NULL.
*/
EXPORTED const char *mbname_intname(const mbname_t *mbname)
{
if (mbname->intname)
return mbname->intname;
struct buf buf = BUF_INITIALIZER;
const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX);
int sep = 0;
int i;
strarray_t *boxes = strarray_dup(mbname_boxes(mbname));
if (mbname->domain) {
buf_appendcstr(&buf, mbname->domain);
buf_putc(&buf, '!');
}
if (mbname->is_deleted) {
buf_appendcstr(&buf, dp);
sep = 1;
}
if (mbname->localpart) {
if (sep) buf_putc(&buf, '.');
buf_appendcstr(&buf, "user.");
_append_intbuf(&buf, mbname->localpart);
sep = 1;
}
for (i = 0; i < strarray_size(boxes); i++) {
if (sep) buf_putc(&buf, '.');
_append_intbuf(&buf, strarray_nth(boxes, i));
sep = 1;
}
if (mbname->is_deleted) {
if (sep) buf_putc(&buf, '.');
buf_printf(&buf, "%X", (unsigned)mbname->is_deleted);
sep = 1;
}
mbname_t *backdoor = (mbname_t *)mbname;
backdoor->intname = buf_release(&buf);
strarray_free(boxes);
return mbname->intname;
}
/* A userid may or may not have a domain - it's just localpart if the
* domain is unspecified or config_defdomain. It totally ignores any parts.
* It's always NULL if there's no localpart
*/
EXPORTED const char *mbname_userid(const mbname_t *mbname)
{
if (!mbname->localpart)
return NULL;
if (mbname->userid)
return mbname->userid;
struct buf buf = BUF_INITIALIZER;
buf_appendcstr(&buf, mbname->localpart);
if (mbname->domain) {
buf_putc(&buf, '@');
buf_appendcstr(&buf, mbname->domain);
}
mbname_t *backdoor = (mbname_t *)mbname;
backdoor->userid = buf_release(&buf);
return mbname->userid;
}
/* A "recipient" is a full username in external form (including domain) with an optional
* +addressed mailbox in external form, no INBOX prefix (since they can only be mailboxes
* owned by the user.
*
* shared folders (no user) are prefixed with a +, i.e. +shared@domain.com
*
* DELETED folders have no recipient, ever.
*/
EXPORTED const char *mbname_recipient(const mbname_t *mbname, const struct namespace *ns)
{
if (mbname->is_deleted) return NULL;
/* gotta match up! */
if (mbname->recipient && ns == mbname->extns)
return mbname->recipient;
struct buf buf = BUF_INITIALIZER;
if (mbname->localpart) {
/* user mailbox */
buf_appendcstr(&buf, mbname->localpart);
}
else {
/* shared mailbox */
buf_appendcstr(&buf, config_getstring(IMAPOPT_POSTUSER));
}
int i;
for (i = 0; i < strarray_size(mbname->boxes); i++) {
buf_putc(&buf, i ? ns->hier_sep : '+');
buf_appendcstr(&buf, strarray_nth(mbname->boxes, i));
}
buf_putc(&buf, '@');
buf_appendcstr(&buf, mbname->domain ? mbname->domain : config_defdomain);
mbname_t *backdoor = (mbname_t *)mbname;
free(backdoor->recipient);
backdoor->recipient = buf_release(&buf);
backdoor->extns = ns;
return mbname->recipient;
}
/* This is one of the most complex parts of the code - generating an external
* name based on the namespace, the 'isadmin' status, and of course the current
* user. There are some interesting things to look out for:
*
* Due to ambiguity, some names won't be representable in the external namespace,
* so this function can return a NULL in those cases.
*/
EXPORTED int mbname_category(const mbname_t *mbname, const struct namespace *ns, const char *userid)
{
if (!mbname_localpart(mbname)) return MBNAME_SHARED;
if (mbname_isdeleted(mbname)) {
if (strcmpsafe(mbname_userid(mbname), userid)) return MBNAME_OTHERDELETED;
return MBNAME_OWNERDELETED;
}
if (strcmpsafe(mbname_userid(mbname), userid)) return MBNAME_OTHERUSER;
const strarray_t *boxes = mbname_boxes(mbname);
if (!strarray_size(boxes)) return MBNAME_INBOX;
if (ns->isalt) {
const char *toplevel = strarray_nth(boxes, 0);
/* exact "INBOX" */
if (!strcmpsafe(toplevel, "INBOX")) {
if (strarray_size(boxes) == 1) return MBNAME_ALTINBOX;
return MBNAME_INBOXSUB;
}
/* other "INBOX" spellings */
if (!strcasecmpsafe(toplevel, "INBOX")) return MBNAME_ALTPREFIX;
/* other prefixes that are special */
if (!strcmpsafe(toplevel, config_getstring(IMAPOPT_USERPREFIX))) return MBNAME_ALTPREFIX;
if (!strcmpsafe(toplevel, config_getstring(IMAPOPT_SHAREDPREFIX))) return MBNAME_ALTPREFIX;
if (!strcmpsafe(toplevel, config_getstring(IMAPOPT_ALTPREFIX))) return MBNAME_ALTPREFIX;
}
/* everything else is owner */
return MBNAME_OWNER;
}
EXPORTED const char *mbname_category_prefix(int category, const struct namespace *ns)
{
if (ns->isalt) {
switch (category) {
case MBNAME_ALTINBOX:
return config_getstring(IMAPOPT_ALTPREFIX);
case MBNAME_OTHERUSER:
return config_getstring(IMAPOPT_USERPREFIX);
case MBNAME_SHARED:
return config_getstring(IMAPOPT_SHAREDPREFIX);
default:
return NULL;
}
}
else {
if (category == MBNAME_OTHERUSER) return "user";
}
return NULL;
}
EXPORTED const char *mbname_extname(const mbname_t *mbname, const struct namespace *ns, const char *userid)
{
int crossdomains = config_getswitch(IMAPOPT_CROSSDOMAINS) && !ns->isadmin;
int cdother = config_getswitch(IMAPOPT_CROSSDOMAINS_ONLYOTHER);
/* old-school virtdomains requires admin to be a different domain than the userid */
int admindomains = config_virtdomains && ns->isadmin;
/* gotta match up! */
if (mbname->extname && ns == mbname->extns && !strcmpsafe(userid, mbname->extuserid))
return mbname->extname;
struct buf buf = BUF_INITIALIZER;
/* have to zero out any existing value just in case we drop through */
mbname_t *backdoor = (mbname_t *)mbname;
if (backdoor->extname) {
free(backdoor->extname);
backdoor->extname = NULL;
backdoor->extns = ns;
free(backdoor->extuserid);
backdoor->extuserid = xstrdupnull(userid);
}
mbname_t *userparts = mbname_from_userid(userid);
strarray_t *boxes = strarray_dup(mbname_boxes(mbname));
if (ns->isalt) {
assert(!ns->isadmin);
const char *up = config_getstring(IMAPOPT_USERPREFIX);
const char *sp = config_getstring(IMAPOPT_SHAREDPREFIX);
const char *ap = config_getstring(IMAPOPT_ALTPREFIX);
/* DELETED mailboxes have no extname in alt namespace.
* There's also no need to display domains unless in crossdomains,
* because admins are never in altnamespace, and only admins can
* see domains in the admindomains space */
if (mbname->is_deleted)
goto done;
/* shared */
if (!mbname_localpart(mbname)) {
/* can't represent an empty mailbox */
if (!strarray_size(boxes))
goto done;
const char *toplevel = strarray_nth(boxes, 0);
if (strarray_size(boxes) == 1 && !strcmpsafe(toplevel, "user")) {
/* special case user all by itself */
buf_appendcstr(&buf, up);
goto end;
}
buf_appendcstr(&buf, sp);
buf_putc(&buf, ns->hier_sep);
_append_extbuf(ns, &buf, toplevel);
/* domains go on the top level folder */
if (crossdomains) {
const char *domain = mbname_domain(mbname);
if (!cdother || strcmpsafe(domain, mbname_domain(userparts))) {
if (!domain) domain = config_defdomain;
buf_putc(&buf, '@');
_append_extbuf(ns, &buf, domain);
}
}
int i;
for (i = 1; i < strarray_size(boxes); i++) {
buf_putc(&buf, ns->hier_sep);
_append_extbuf(ns, &buf, strarray_nth(boxes, i));
}
goto end;
}
/* other users */
if (strcmpsafe(mbname_userid(mbname), userid)) {
buf_appendcstr(&buf, up);
buf_putc(&buf, ns->hier_sep);
_append_extbuf(ns, &buf, mbname_localpart(mbname));
if (crossdomains) {
const char *domain = mbname_domain(mbname);
if (!cdother || strcmpsafe(domain, mbname_domain(userparts))) {
if (!domain) domain = config_defdomain;
buf_putc(&buf, '@');
_append_extbuf(ns, &buf, domain);
}
}
int i;
for (i = 0; i < strarray_size(boxes); i++) {
buf_putc(&buf, ns->hier_sep);
_append_extbuf(ns, &buf, strarray_nth(boxes, i));
}
goto end;
}
/* own user */
if (!strarray_size(boxes)) {
buf_appendcstr(&buf, "INBOX");
goto end;
}
const char *toplevel = strarray_nth(boxes, 0);
/* INBOX is very special, because it can only be represented with exact case,
* and it skips a level. Everything else including allcaps INBOX goes into
* the Alt Prefix */
if (!strcasecmpsafe(toplevel, "INBOX")) {
if (strarray_size(boxes) == 1 || strcmpsafe(toplevel, "INBOX")) {
buf_appendcstr(&buf, ap);
buf_putc(&buf, ns->hier_sep);
}
}
/* likewise anything exactly matching the user, alt or shared prefixes, both top level
* or with children goes into alt prefix */
else if (!strcmpsafe(toplevel, up) || !strcmpsafe(toplevel, sp) || !strcmpsafe(toplevel, ap)) {
buf_appendcstr(&buf, ap);
buf_putc(&buf, ns->hier_sep);
}
_append_extbuf(ns, &buf, toplevel);
int i;
for (i = 1; i < strarray_size(boxes); i++) {
buf_putc(&buf, ns->hier_sep);
_append_extbuf(ns, &buf, strarray_nth(boxes, i));
}
goto end;
}
if (mbname->is_deleted) {
buf_appendcstr(&buf, config_getstring(IMAPOPT_DELETEDPREFIX));
buf_putc(&buf, ns->hier_sep);
}
/* shared */
if (!mbname_localpart(mbname)) {
/* invalid names - not sure it's even possible, but hey */
if (!strarray_size(boxes))
goto done;
if (!strcasecmpsafe(strarray_nth(boxes, 0), "INBOX"))
goto done;
/* shared folders can ONLY be in the same domain except for admin */
if (!admindomains && strcmpsafe(mbname_domain(mbname), mbname_domain(userparts)))
goto done;
/* note "user" precisely appears here, but no need to special case it
* since the output is the same */
int i;
for (i = 0; i < strarray_size(boxes); i++) {
if (i) buf_putc(&buf, ns->hier_sep);
_append_extbuf(ns, &buf, strarray_nth(boxes, i));
}
goto end;
}
/* other users or DELETED */
if (mbname->is_deleted || strcmpsafe(mbname_userid(mbname), userid)) {
buf_appendcstr(&buf, "user");
buf_putc(&buf, ns->hier_sep);
_append_extbuf(ns, &buf, mbname_localpart(mbname));
if (crossdomains) {
const char *domain = mbname_domain(mbname);
if (!cdother || strcmpsafe(domain, mbname_domain(userparts))) {
if (!domain) domain = config_defdomain;
buf_putc(&buf, '@');
_append_extbuf(ns, &buf, domain);
}
}
/* shared folders can ONLY be in the same domain except for admin */
else if (!admindomains && strcmpsafe(mbname_domain(mbname), mbname_domain(userparts)))
goto done;
int i;
for (i = 0; i < strarray_size(boxes); i++) {
buf_putc(&buf, ns->hier_sep);
_append_extbuf(ns, &buf, strarray_nth(boxes, i));
}
goto end;
}
buf_appendcstr(&buf, "INBOX");
int i;
for (i = 0; i < strarray_size(boxes); i++) {
buf_putc(&buf, ns->hier_sep);
_append_extbuf(ns, &buf, strarray_nth(boxes, i));
}
end:
/* note: kinda bogus in altnamespace, meh */
if (mbname->is_deleted) {
buf_putc(&buf, ns->hier_sep);
buf_printf(&buf, "%X", (unsigned)mbname->is_deleted);
}
if (admindomains && mbname_domain(mbname)) {
buf_putc(&buf, '@');
buf_appendcstr(&buf, mbname_domain(mbname));
}
backdoor->extname = buf_release(&buf);
done:
buf_free(&buf);
mbname_free(&userparts);
strarray_free(boxes);
return mbname->extname;
}
EXPORTED const char *mbname_domain(const mbname_t *mbname)
{
return mbname->domain;
}
EXPORTED const char *mbname_localpart(const mbname_t *mbname)
{
return mbname->localpart;
}
EXPORTED time_t mbname_isdeleted(const mbname_t *mbname)
{
return mbname->is_deleted;
}
EXPORTED const strarray_t *mbname_boxes(const mbname_t *mbname)
{
if (!mbname->boxes) {
mbname_t *backdoor = (mbname_t *)mbname;
backdoor->boxes = strarray_new();
}
return mbname->boxes;
}
/*
* Create namespace based on config options.
*/
EXPORTED int mboxname_init_namespace(struct namespace *namespace, int isadmin)
{
const char *prefix;
assert(namespace != NULL);
namespace->isadmin = isadmin;
namespace->hier_sep =
config_getswitch(IMAPOPT_UNIXHIERARCHYSEP) ? '/' : '.';
namespace->isalt = !isadmin && config_getswitch(IMAPOPT_ALTNAMESPACE);
namespace->accessible[NAMESPACE_INBOX] = 1;
namespace->accessible[NAMESPACE_USER] = !config_getswitch(IMAPOPT_DISABLE_USER_NAMESPACE);
namespace->accessible[NAMESPACE_SHARED] = !config_getswitch(IMAPOPT_DISABLE_SHARED_NAMESPACE);
if (namespace->isalt) {
/* alternate namespace */
strcpy(namespace->prefix[NAMESPACE_INBOX], "");
prefix = config_getstring(IMAPOPT_USERPREFIX);
if (!prefix || strlen(prefix) == 0 ||
strlen(prefix) >= MAX_NAMESPACE_PREFIX ||
strchr(prefix,namespace->hier_sep) != NULL)
return IMAP_NAMESPACE_BADPREFIX;
sprintf(namespace->prefix[NAMESPACE_USER], "%.*s%c",
MAX_NAMESPACE_PREFIX-1, prefix, namespace->hier_sep);
prefix = config_getstring(IMAPOPT_SHAREDPREFIX);
if (!prefix || strlen(prefix) == 0 ||
strlen(prefix) >= MAX_NAMESPACE_PREFIX ||
strchr(prefix, namespace->hier_sep) != NULL ||
!strncmp(namespace->prefix[NAMESPACE_USER], prefix, strlen(prefix)))
return IMAP_NAMESPACE_BADPREFIX;
if (!isadmin) {
sprintf(namespace->prefix[NAMESPACE_SHARED], "%.*s%c",
MAX_NAMESPACE_PREFIX-1, prefix, namespace->hier_sep);
}
}
else {
/* standard namespace */
sprintf(namespace->prefix[NAMESPACE_INBOX], "%s%c",
"INBOX", namespace->hier_sep);
sprintf(namespace->prefix[NAMESPACE_USER], "%s%c",
"user", namespace->hier_sep);
strcpy(namespace->prefix[NAMESPACE_SHARED], "");
}
return 0;
}
EXPORTED struct namespace *mboxname_get_adminnamespace()
{
static struct namespace ns;
if (!admin_namespace) {
mboxname_init_namespace(&ns, /*isadmin*/1);
admin_namespace = &ns;
}
return admin_namespace;
}
/*
* Return nonzero if 'userid' owns the (internal) mailbox 'name'.
*/
EXPORTED int mboxname_userownsmailbox(const char *userid, const char *name)
{
mbname_t *mbname = mbname_from_intname(name);
int res = !strcmpsafe(mbname_userid(mbname), userid);
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a user's mailbox (optionally INBOX),
* returns 1, otherwise returns 0.
*/
EXPORTED int mboxname_isusermailbox(const char *name, int isinbox)
{
mbname_t *mbname = mbname_from_intname(name);
int res = 0;
if (mbname_localpart(mbname) && !mbname_isdeleted(mbname)) {
if (!isinbox || !strarray_size(mbname_boxes(mbname)))
res = 1;
}
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a user's Trash folder returns 1,
* otherwise returns 0
* XXX: use roles rather than hard coded name?
*/
EXPORTED int mboxname_isusertrash(const char *name)
{
mbname_t *mbname = mbname_from_intname(name);
int res = 0;
if (mbname_localpart(mbname) && !mbname_isdeleted(mbname)) {
const strarray_t *boxes = mbname_boxes(mbname);
if (strarray_size(boxes) == 1 && !strcmpsafe(strarray_nth(boxes, 0), "Trash"))
res = 1;
}
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a DELETED mailbox
* returns boolean
*/
EXPORTED int mboxname_isdeletedmailbox(const char *name, time_t *timestampp)
{
mbname_t *mbname = mbname_from_intname(name);
time_t res = mbname_isdeleted(mbname);
mbname_free(&mbname);
if (timestampp)
*timestampp = res;
return res ? 1 : 0;
}
/*
* If (internal) mailbox 'name' is a CALENDAR mailbox
* returns boolean
*/
EXPORTED int mboxname_iscalendarmailbox(const char *name, int mbtype)
{
if (mbtype & MBTYPE_CALENDAR) return 1; /* Only works on backends */
int res = 0;
mbname_t *mbname = mbname_from_intname(name);
const strarray_t *boxes = mbname_boxes(mbname);
const char *prefix = config_getstring(IMAPOPT_CALENDARPREFIX);
if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
res = 1;
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a ADDRESSBOOK mailbox
* returns boolean
*/
EXPORTED int mboxname_isaddressbookmailbox(const char *name, int mbtype)
{
if (mbtype & MBTYPE_ADDRESSBOOK) return 1; /* Only works on backends */
int res = 0;
mbname_t *mbname = mbname_from_intname(name);
const strarray_t *boxes = mbname_boxes(mbname);
const char *prefix = config_getstring(IMAPOPT_ADDRESSBOOKPREFIX);
if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
res = 1;
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a DAVDRIVE mailbox
* returns boolean
*/
EXPORTED int mboxname_isdavdrivemailbox(const char *name, int mbtype)
{
if (mbtype & MBTYPE_COLLECTION) return 1; /* Only works on backends */
int res = 0;
mbname_t *mbname = mbname_from_intname(name);
const strarray_t *boxes = mbname_boxes(mbname);
const char *prefix = config_getstring(IMAPOPT_DAVDRIVEPREFIX);
if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
res = 1;
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a DAVNOTIFICATIONS mailbox
* returns boolean
*/
EXPORTED int mboxname_isdavnotificationsmailbox(const char *name, int mbtype)
{
if (mbtype & MBTYPE_COLLECTION) return 1; /* Only works on backends */
int res = 0;
mbname_t *mbname = mbname_from_intname(name);
const strarray_t *boxes = mbname_boxes(mbname);
const char *prefix = config_getstring(IMAPOPT_DAVNOTIFICATIONSPREFIX);
if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
res = 1;
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a user's "Notes" mailbox
* returns boolean
*/
EXPORTED int mboxname_isnotesmailbox(const char *name, int mbtype __attribute__((unused)))
{
int res = 0;
mbname_t *mbname = mbname_from_intname(name);
const strarray_t *boxes = mbname_boxes(mbname);
const char *prefix = config_getstring(IMAPOPT_NOTESMAILBOX);
if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
res = 1;
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a user's #jmapsubmission mailbox
* returns boolean
*/
EXPORTED int mboxname_issubmissionmailbox(const char *name, int mbtype)
{
if (mbtype & MBTYPE_SUBMISSION) return 1; /* Only works on backends */
int res = 0;
mbname_t *mbname = mbname_from_intname(name);
const strarray_t *boxes = mbname_boxes(mbname);
const char *prefix = config_getstring(IMAPOPT_JMAPSUBMISSIONFOLDER);
if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
res = 1;
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a user's #jmappushsubscription mailbox
* returns boolean
*/
EXPORTED int mboxname_ispushsubscriptionmailbox(const char *name, int mbtype)
{
if (mbtype & MBTYPE_PUSHSUBSCRIPTION) return 1; /* Only works on backends */
int res = 0;
mbname_t *mbname = mbname_from_intname(name);
const strarray_t *boxes = mbname_boxes(mbname);
const char *prefix = config_getstring(IMAPOPT_JMAPPUSHSUBSCRIPTIONFOLDER);
if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
res = 1;
mbname_free(&mbname);
return res;
}
/*
* If (internal) mailbox 'name' is a user's #jmap upload mailbox
* returns boolean
*/
EXPORTED int mboxname_isjmapuploadmailbox(const char *name, int mbtype __attribute__((unused)))
{
int res = 0;
mbname_t *mbname = mbname_from_intname(name);
const strarray_t *boxes = mbname_boxes(mbname);
const char *prefix = config_getstring(IMAPOPT_JMAPUPLOADFOLDER);
if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
res = 1;
mbname_free(&mbname);
return res;
}
EXPORTED char *mboxname_user_mbox(const char *userid, const char *subfolder)
{
if (!userid) return NULL;
mbname_t *mbname = mbname_from_userid(userid);
if (subfolder) {
strarray_t *bits = strarray_split(subfolder, ".", 0);
mbname_set_boxes(mbname, bits);
strarray_free(bits);
}
char *res = xstrdup(mbname_intname(mbname));
mbname_free(&mbname);
return res;
}
EXPORTED char *mboxname_abook(const char *userid, const char *collection)
{
mbname_t *mbname = mbname_from_userid(userid);
mbname_push_boxes(mbname, config_getstring(IMAPOPT_ADDRESSBOOKPREFIX));
if (collection) mbname_push_boxes(mbname, collection);
char *res = xstrdup(mbname_intname(mbname));
mbname_free(&mbname);
return res;
}
EXPORTED char *mboxname_cal(const char *userid, const char *collection)
{
mbname_t *mbname = mbname_from_userid(userid);
mbname_push_boxes(mbname, config_getstring(IMAPOPT_CALENDARPREFIX));
if (collection) mbname_push_boxes(mbname, collection);
char *res = xstrdup(mbname_intname(mbname));
mbname_free(&mbname);
return res;
}
/*
* Check whether two parts have the same userid.
* Returns: 1 if the userids are the same, 0 if not.
*/
EXPORTED int mbname_same_userid(const mbname_t *a, const mbname_t *b)
{
int r;
r = strcmpsafe(a->domain, b->domain);
if (!r)
r = strcmpsafe(a->localpart, b->localpart);
return !r;
}
/*
* Check whether two mboxnames have the same userid.
* Needed for some corner cases in the COPY command.
* Returns: 1 if the userids are the same, 0 if not,
* or negative error.
*/
EXPORTED int mboxname_same_userid(const char *name1, const char *name2)
{
int r;
mbname_t *p1 = mbname_from_intname(name1);
mbname_t *p2 = mbname_from_intname(name2);
r = mbname_same_userid(p1, p2);
mbname_free(&p1);
mbname_free(&p2);
return r;
}
/*
* Apply site policy restrictions on mailbox names.
* Restrictions are hardwired for now.
* NOTE: '^' is '.' externally in unixhs, and invalid in unixhs
*
* The set of printable chars that are not in GOODCHARS are:
* !"%&/;<>\`{|}
*/
#define GOODCHARS " #$'()*+,-.0123456789:=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz~"
EXPORTED int mboxname_policycheck(const char *name)
{
const char *p;
int sawutf7 = 0;
unsigned c1, c2, c3, c4, c5, c6, c7, c8;
int ucs4;
int namelen = strlen(name);
int hasdom = 0;
/* We reserve mailboxes.db keys beginning with $ for internal use
* (e.g. $RACL), so don't allow a real mailbox to sneak in there.
*
* N.B This is only forbidden at the absolute top of the internal
* namespace: stuff like "user.foo.$bar", "domain!user.foo.$bar",
* "domain!$bar", and even "user.$bar" are all still valid here,
* because none of those names start with $, and won't conflict.
*/
if (name[0] == '$')
return IMAP_MAILBOX_BADNAME;
/* Skip policy check on mailbox created in delayed delete namespace
* assuming the mailbox existed before and was OK then.
* This should allow mailboxes that are extremely long to be
* deleted when delayed_delete is enabled.
* A thorough fix might remove the prefix and timestamp
* then continue with the check
*/
if (mboxname_isdeletedmailbox(name, NULL))
return 0;
if (namelen > MAX_MAILBOX_NAME)
return IMAP_MAILBOX_BADNAME;
/* find the virtual domain, if any. We don't sanity check domain
names yet - maybe we should */
p = strchr(name, '!');
if (p) {
if (config_virtdomains) {
name = p + 1;
namelen = strlen(name);
hasdom = 1;
}
else
return IMAP_MAILBOX_BADNAME;
}
/* bad mbox patterns */
// empty name
if (!name[0]) return IMAP_MAILBOX_BADNAME;
// leading dot
if (name[0] == '.') return IMAP_MAILBOX_BADNAME;
// leading ~
if (name[0] == '~') return IMAP_MAILBOX_BADNAME;
// trailing dot
if (name[namelen-1] == '.') return IMAP_MAILBOX_BADNAME;
// double dot (zero length path item)
if (strstr(name, "..")) return IMAP_MAILBOX_BADNAME;
// non-" " whitespace
if (strchr(name, '\r')) return IMAP_MAILBOX_BADNAME;
if (strchr(name, '\n')) return IMAP_MAILBOX_BADNAME;
if (strchr(name, '\t')) return IMAP_MAILBOX_BADNAME;
// top level user
if (!strcmp(name, "user")) return IMAP_MAILBOX_BADNAME;
// special users
if (!strcmp(name, "user.anyone")) return IMAP_MAILBOX_BADNAME;
if (!strcmp(name, "user.anonymous")) return IMAP_MAILBOX_BADNAME;
// redundant but explicit ban on userids starting with '%'
// (would conflict with backups of shared mailboxes)
if (!strncmp(name, "user.%", 6)) return IMAP_MAILBOX_BADNAME;
while (*name) {
if (*name == '&') {
/* Modified UTF-7 */
name++;
while (*name != '-') {
if (sawutf7) {
/* Two adjacent utf7 sequences */
return IMAP_MAILBOX_BADNAME;
}
if ((c1 = CHARMOD64(*name++)) == XX ||
(c2 = CHARMOD64(*name++)) == XX ||
(c3 = CHARMOD64(*name++)) == XX) {
/* Non-base64 character */
return IMAP_MAILBOX_BADNAME;
}
ucs4 = (c1 << 10) | (c2 << 4) | (c3 >> 2);
if ((ucs4 & 0xff80) == 0) {
/* US-ASCII character */
return IMAP_MAILBOX_BADNAME;
}
if (*name == '-') {
/* Trailing bits not zero */
if (c3 & 0x03) return IMAP_MAILBOX_BADNAME;
/* End of UTF-7 sequence */
break;
}
if ((c4 = CHARMOD64(*name++)) == XX ||
(c5 = CHARMOD64(*name++)) == XX ||
(c6 = CHARMOD64(*name++)) == XX) {
/* Non-base64 character */
return IMAP_MAILBOX_BADNAME;
}
ucs4 = ((c3 & 0x03) << 14) | (c4 << 8) | (c5 << 2) | (c6 >> 4);
if ((ucs4 & 0xff80) == 0) {
/* US-ASCII character */
return IMAP_MAILBOX_BADNAME;
}
if (*name == '-') {
/* Trailing bits not zero */
if (c6 & 0x0f) return IMAP_MAILBOX_BADNAME;
/* End of UTF-7 sequence */
break;
}
if ((c7 = CHARMOD64(*name++)) == XX ||
(c8 = CHARMOD64(*name++)) == XX) {
/* Non-base64 character */
return IMAP_MAILBOX_BADNAME;
}
ucs4 = ((c6 & 0x0f) << 12) | (c7 << 6) | c8;
if ((ucs4 & 0xff80) == 0) {
/* US-ASCII character */
return IMAP_MAILBOX_BADNAME;
}
}
if (name[-1] == '&') sawutf7 = 0; /* '&-' is sequence for '&' */
else sawutf7 = 1;
name++; /* Skip over terminating '-' */
}
else {
if (!(strchr(GOODCHARS, *name) || (hasdom && *name == '!')))
return IMAP_MAILBOX_BADNAME;
name++;
sawutf7 = 0;
}
}
return 0;
}
EXPORTED int mboxname_is_prefix(const char *longstr, const char *shortstr)
{
int longlen = strlen(longstr);
int shortlen = strlen(shortstr);
/* can't be a child */
if (longlen < shortlen)
return 0;
/* don't match along same length */
if (strncmp(longstr, shortstr, shortlen))
return 0;
/* longer, and not a separator */
if (longlen > shortlen && longstr[shortlen] != '.')
return 0;
/* it's a match! */
return 1;
}
EXPORTED void mboxname_hash(char *dest, size_t destlen,
const char *root,
const char *name)
{
mbname_t *mbname = mbname_from_intname(name);
struct buf buf = BUF_INITIALIZER;
buf_setcstr(&buf, root);
const char *domain = mbname_domain(mbname);
strarray_t *boxes = strarray_dup(mbname_boxes(mbname));
if (domain) {
if (config_hashimapspool) {
char c = dir_hash_c(domain, config_fulldirhash);
buf_printf(&buf, "%s%c/%s", FNAME_DOMAINDIR, c, domain);
}
else {
buf_printf(&buf, "%s%s", FNAME_DOMAINDIR, domain);
}
}
if (mbname_localpart(mbname)) {
strarray_unshift(boxes, mbname_localpart(mbname));
strarray_unshift(boxes, "user");
}
if (mbname_isdeleted(mbname)) {
struct buf dbuf = BUF_INITIALIZER;
buf_printf(&dbuf, "%X", (unsigned)mbname_isdeleted(mbname));
strarray_unshift(boxes, config_getstring(IMAPOPT_DELETEDPREFIX));
strarray_push(boxes, buf_cstring(&dbuf));
buf_free(&dbuf);
}
if (config_hashimapspool && strarray_size(boxes)) {
const char *idx = strarray_size(boxes) > 1 ? strarray_nth(boxes, 1) : strarray_nth(boxes, 0);
char c = dir_hash_c(idx, config_fulldirhash);
buf_printf(&buf, "/%c", c);
}
int i;
for (i = 0; i < strarray_size(boxes); i++) {
buf_putc(&buf, '/');
_append_intbuf(&buf, strarray_nth(boxes, i));
}
/* for now, keep API even though we're doing a buffer inside here */
strncpy(dest, buf_cstring(&buf), destlen);
buf_free(&buf);
strarray_free(boxes);
mbname_free(&mbname);
}
/* note: mboxname must be internal */
EXPORTED char *mboxname_datapath(const char *partition,
const char *mboxname,
const char *uniqueid __attribute__((unused)),
unsigned long uid)
{
static char pathresult[MAX_MAILBOX_PATH+1];
const char *root;
if (!partition) return NULL;
root = config_partitiondir(partition);
if (!root) return NULL;
if (!mboxname) {
xstrncpy(pathresult, root, MAX_MAILBOX_PATH);
return pathresult;
}
mboxname_hash(pathresult, MAX_MAILBOX_PATH, root, mboxname);
if (uid) {
int len = strlen(pathresult);
snprintf(pathresult + len, MAX_MAILBOX_PATH - len, "/%lu.", uid);
}
pathresult[MAX_MAILBOX_PATH] = '\0';
if (strlen(pathresult) == MAX_MAILBOX_PATH)
return NULL;
return pathresult;
}
/* note: mboxname must be internal */
EXPORTED char *mboxname_archivepath(const char *partition,
const char *mboxname,
const char *uniqueid __attribute__((unused)),
unsigned long uid)
{
static char pathresult[MAX_MAILBOX_PATH+1];
const char *root;
if (!partition) return NULL;
root = config_archivepartitiondir(partition);
if (!root) root = config_partitiondir(partition);
if (!root) return NULL;
/* XXX - dedup with datapath above - but make sure to keep the results
* in separate buffers and/or audit the callers */
if (!mboxname) {
xstrncpy(pathresult, root, MAX_MAILBOX_PATH);
return pathresult;
}
mboxname_hash(pathresult, MAX_MAILBOX_PATH, root, mboxname);
if (uid) {
int len = strlen(pathresult);
snprintf(pathresult + len, MAX_MAILBOX_PATH - len, "/%lu.", uid);
}
pathresult[MAX_MAILBOX_PATH] = '\0';
if (strlen(pathresult) == MAX_MAILBOX_PATH)
return NULL;
return pathresult;
}
char *mboxname_lockpath(const char *mboxname)
{
return mboxname_lockpath_suffix(mboxname, ".lock");
}
char *mboxname_lockpath_suffix(const char *mboxname,
const char *suffix)
{
static char lockresult[MAX_MAILBOX_PATH+1];
char basepath[MAX_MAILBOX_PATH+1];
const char *root = config_getstring(IMAPOPT_MBOXNAME_LOCKPATH);
int len;
if (!root) {
snprintf(basepath, MAX_MAILBOX_PATH, "%s/lock", config_dir);
root = basepath;
}
mboxname_hash(lockresult, MAX_MAILBOX_PATH, root, mboxname);
len = strlen(lockresult);
snprintf(lockresult + len, MAX_MAILBOX_PATH - len, "%s", suffix);
lockresult[MAX_MAILBOX_PATH] = '\0';
if (strlen(lockresult) == MAX_MAILBOX_PATH)
return NULL;
return lockresult;
}
EXPORTED char *mboxname_metapath(const char *partition,
const char *mboxname,
const char *uniqueid __attribute__((unused)),
int metafile,
int isnew)
{
static char metaresult[MAX_MAILBOX_PATH];
int metaflag = 0;
int archiveflag = 0;
const char *root = NULL;
const char *filename = NULL;
char confkey[256];
if (!partition) return NULL;
*confkey = '\0';
switch (metafile) {
case META_HEADER:
snprintf(confkey, 256, "metadir-header-%s", partition);
metaflag = IMAP_ENUM_METAPARTITION_FILES_HEADER;
filename = FNAME_HEADER;
break;
case META_INDEX:
snprintf(confkey, 256, "metadir-index-%s", partition);
metaflag = IMAP_ENUM_METAPARTITION_FILES_INDEX;
filename = FNAME_INDEX;
break;
case META_CACHE:
snprintf(confkey, 256, "metadir-cache-%s", partition);
metaflag = IMAP_ENUM_METAPARTITION_FILES_CACHE;
filename = FNAME_CACHE;
break;
case META_EXPUNGE:
/* not movable, it's only old */
metaflag = IMAP_ENUM_METAPARTITION_FILES_EXPUNGE;
filename = FNAME_EXPUNGE;
break;
case META_SQUAT:
snprintf(confkey, 256, "metadir-squat-%s", partition);
metaflag = IMAP_ENUM_METAPARTITION_FILES_SQUAT;
filename = FNAME_SQUAT;
break;
case META_ANNOTATIONS:
snprintf(confkey, 256, "metadir-index-%s", partition);
metaflag = IMAP_ENUM_METAPARTITION_FILES_ANNOTATIONS;
filename = FNAME_ANNOTATIONS;
break;
#ifdef WITH_DAV
case META_DAV:
snprintf(confkey, 256, "metadir-dav-%s", partition);
metaflag = IMAP_ENUM_METAPARTITION_FILES_DAV;
filename = FNAME_DAV;
break;
#endif
case META_ARCHIVECACHE:
snprintf(confkey, 256, "metadir-archivecache-%s", partition);
metaflag = IMAP_ENUM_METAPARTITION_FILES_ARCHIVECACHE;
filename = FNAME_CACHE;
archiveflag = 1;
break;
case 0:
break;
default:
fatal("Unknown meta file requested", EX_SOFTWARE);
}
if (*confkey)
root = config_getoverflowstring(confkey, NULL);
if (!root && (!metaflag || (config_metapartition_files & metaflag)))
root = config_metapartitiondir(partition);
if (!root && archiveflag)
root = config_archivepartitiondir(partition);
if (!root)
root = config_partitiondir(partition);
if (!root)
return NULL;
if (!mboxname) {
xstrncpy(metaresult, root, MAX_MAILBOX_PATH);
return metaresult;
}
mboxname_hash(metaresult, MAX_MAILBOX_PATH, root, mboxname);
if (filename) {
int len = strlen(metaresult);
if (isnew)
snprintf(metaresult + len, MAX_MAILBOX_PATH - len, "%s.NEW", filename);
else
snprintf(metaresult + len, MAX_MAILBOX_PATH - len, "%s", filename);
}
if (strlen(metaresult) >= MAX_MAILBOX_PATH)
return NULL;
return metaresult;
}
EXPORTED void mboxname_todeleted(const char *name, char *result, int withtime)
{
int domainlen = 0;
char *p;
const char *deletedprefix = config_getstring(IMAPOPT_DELETEDPREFIX);
xstrncpy(result, name, MAX_MAILBOX_BUFFER);
if (config_virtdomains && (p = strchr(name, '!')))
domainlen = p - name + 1;
if (withtime) {
struct timeval tv;
gettimeofday( &tv, NULL );
snprintf(result+domainlen, MAX_MAILBOX_BUFFER-domainlen, "%s.%s.%X",
deletedprefix, name+domainlen, (unsigned) tv.tv_sec);
} else {
snprintf(result+domainlen, MAX_MAILBOX_BUFFER-domainlen, "%s.%s",
deletedprefix, name+domainlen);
}
}
EXPORTED int mboxname_make_parent(char *name)
{
int domainlen = 0;
char *p;
if (config_virtdomains && (p = strchr(name, '!')))
domainlen = p - name + 1;
if (!name[0] || !strcmp(name+domainlen, "user"))
return 0; /* stop now */
p = strrchr(name, '.');
if (p && (p - name > domainlen)) /* don't split subdomain */
*p = '\0';
else if (!name[domainlen]) /* server entry */
name[0] = '\0';
else /* domain entry */
name[domainlen] = '\0';
return 1;
}
EXPORTED int mboxname_contains_parent(const char *mboxname, const char *prev)
{
/* no names, definitely can't be parent! */
if (!mboxname) return 0;
if (!prev) return 0;
char *parent = xstrdup(mboxname);
/* this mailbox is just "user"? prev will always contain that */
if (!mboxname_make_parent(parent)) {
free(parent);
return 1;
}
if (mboxname_is_prefix(prev, parent)) {
/* it's not different? Great - there's no missing intermediate */
free(parent);
return 1;
}
/* OK, it doesn't contain the parent for sure */
free(parent);
return 0;
}
/* NOTE: caller must free, which is different from almost every
* other interface in the whole codebase. Grr */
EXPORTED char *mboxname_conf_getpath(const mbname_t *mbname, const char *suffix)
{
char *fname = NULL;
char c[2], d[2];
if (mbname->domain) {
if (mbname->localpart) {
if (suffix) {
fname = strconcat(config_dir,
FNAME_DOMAINDIR,
dir_hash_b(mbname->domain, config_fulldirhash, d),
"/", mbname->domain,
FNAME_USERDIR,
dir_hash_b(mbname->localpart, config_fulldirhash, c),
"/", mbname->localpart, ".", suffix,
(char *)NULL);
}
else {
fname = strconcat(config_dir,
FNAME_DOMAINDIR,
dir_hash_b(mbname->domain, config_fulldirhash, d),
"/", mbname->domain,
FNAME_USERDIR,
dir_hash_b(mbname->localpart, config_fulldirhash, c),
(char *)NULL);
}
}
else {
if (suffix) {
fname = strconcat(config_dir,
FNAME_DOMAINDIR,
dir_hash_b(mbname->domain, config_fulldirhash, d),
"/", mbname->domain,
"/", FNAME_SHAREDPREFIX, ".", suffix,
(char *)NULL);
}
else {
fname = strconcat(config_dir,
FNAME_DOMAINDIR,
dir_hash_b(mbname->domain, config_fulldirhash, d),
"/", mbname->domain,
(char *)NULL);
}
}
}
else {
if (mbname->localpart) {
if (suffix) {
fname = strconcat(config_dir,
FNAME_USERDIR,
dir_hash_b(mbname->localpart, config_fulldirhash, c),
"/", mbname->localpart, ".", suffix,
(char *)NULL);
}
else {
fname = strconcat(config_dir,
FNAME_USERDIR,
dir_hash_b(mbname->localpart, config_fulldirhash, c),
(char *)NULL);
}
}
else {
if (suffix) {
fname = strconcat(config_dir,
"/", FNAME_SHAREDPREFIX, ".", suffix,
(char *)NULL);
}
else {
fname = xstrdup(config_dir);
}
}
}
return fname;
}
/* ========================= COUNTERS ============================ */
static bit64 mboxname_readval_old(const char *mboxname, const char *metaname)
{
bit64 fileval = 0;
mbname_t *mbname = NULL;
char *fname = NULL;
const char *base = NULL;
size_t len = 0;
int fd = -1;
mbname = mbname_from_intname(mboxname);
fname = mboxname_conf_getpath(mbname, metaname);
if (!fname) goto done;
fd = open(fname, O_RDONLY);
/* read the value - note: we don't care if it's being rewritten,
* we'll still get a consistent read on either the old or new
* value */
if (fd != -1) {
struct stat sbuf;
if (fstat(fd, &sbuf)) {
syslog(LOG_ERR, "IOERROR: failed to stat fd %s: %m", fname);
goto done;
}
if (sbuf.st_size) {
map_refresh(fd, 1, &base, &len, sbuf.st_size, metaname, mboxname);
parsenum(base, NULL, sbuf.st_size, &fileval);
map_free(&base, &len);
}
}
done:
if (fd != -1) close(fd);
mbname_free(&mbname);
free(fname);
return fileval;
}
#define MV_VERSION 8
#define MV_OFF_GENERATION 0
#define MV_OFF_VERSION 4
#define MV_OFF_HIGHESTMODSEQ 8
#define MV_OFF_MAILMODSEQ 16
#define MV_OFF_CALDAVMODSEQ 24
#define MV_OFF_CARDDAVMODSEQ 32
#define MV_OFF_NOTESMODSEQ 40
#define MV_OFF_MAILFOLDERSMODSEQ 48
#define MV_OFF_CALDAVFOLDERSMODSEQ 56
#define MV_OFF_CARDDAVFOLDERSMODSEQ 64
#define MV_OFF_NOTESFOLDERSMODSEQ 72
#define MV_OFF_QUOTAMODSEQ 80
#define MV_OFF_RACLMODSEQ 88
#define MV_OFF_SUBMISSIONMODSEQ 96
#define MV_OFF_SUBMISSIONFOLDERSMODSEQ 104
#define MV_OFF_MAILDELETEDMODSEQ 112
#define MV_OFF_CALDAVDELETEDMODSEQ 120
#define MV_OFF_CARDDAVDELETEDMODSEQ 128
#define MV_OFF_NOTESDELETEDMODSEQ 136
#define MV_OFF_SUBMISSIONDELETEDMODSEQ 144
#define MV_OFF_MAILFOLDERSDELETEDMODSEQ 152
#define MV_OFF_CALDAVFOLDERSDELETEDMODSEQ 160
#define MV_OFF_CARDDAVFOLDERSDELETEDMODSEQ 168
#define MV_OFF_NOTESFOLDERSDELETEDMODSEQ 176
#define MV_OFF_SUBMISSIONFOLDERSDELETEDMODSEQ 184
#define MV_OFF_DAVNOTIFICATIONMODSEQ 192
#define MV_OFF_DAVNOTIFICATIONDELETEDMODSEQ 200
#define MV_OFF_DAVNOTIFICATIONFOLDERSMODSEQ 208
#define MV_OFF_DAVNOTIFICATIONFOLDERSDELETEDMODSEQ 216
#define MV_OFF_JMAPNOTIFICATIONMODSEQ 224
#define MV_OFF_JMAPNOTIFICATIONDELETEDMODSEQ 232
#define MV_OFF_JMAPNOTIFICATIONFOLDERSMODSEQ 240
#define MV_OFF_JMAPNOTIFICATIONFOLDERSDELETEDMODSEQ 248
#define MV_OFF_UIDVALIDITY 256
#define MV_OFF_CRC 260
#define MV_LENGTH 264
/* NOTE: you need a MV_LENGTH byte base here */
static int mboxname_buf_to_counters(const char *base, size_t len, struct mboxname_counters *vals)
{
memset(vals, 0, sizeof(struct mboxname_counters));
vals->generation = ntohl(*((uint32_t *)(base)));
vals->version = ntohl(*((uint32_t *)(base+4)));
/* dodgy broken version storage in v0 code, it could be anything */
if (len == 48) vals->version = 0;
switch (vals->version) {
case 0:
if (len != 48) return IMAP_MAILBOX_CHECKSUM;
if (crc32_map(base, 44) != ntohl(*((uint32_t *)(base+44))))
return IMAP_MAILBOX_CHECKSUM;
vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
vals->uidvalidity = ntohl(*((uint32_t *)(base+40)));
break;
case 1:
if (len != 56) return IMAP_MAILBOX_CHECKSUM;
if (crc32_map(base, 52) != ntohl(*((uint32_t *)(base+52))))
return IMAP_MAILBOX_CHECKSUM;
vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
vals->uidvalidity = ntohl(*((uint32_t *)(base+48)));
break;
case 2:
if (len != 64) return IMAP_MAILBOX_CHECKSUM;
if (crc32_map(base, 60) != ntohl(*((uint32_t *)(base+60))))
return IMAP_MAILBOX_CHECKSUM;
vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
vals->mailfoldersmodseq = ntohll(*((uint32_t *)(base+48)));
vals->uidvalidity = ntohl(*((uint32_t *)(base+56)));
break;
case 3:
if (len != 88) return IMAP_MAILBOX_CHECKSUM;
if (crc32_map(base, 84) != ntohl(*((uint32_t *)(base+84))))
return IMAP_MAILBOX_CHECKSUM;
vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
vals->mailfoldersmodseq = ntohll(*((uint64_t *)(base+48)));
vals->caldavfoldersmodseq = ntohll(*((uint64_t *)(base+56)));
vals->carddavfoldersmodseq = ntohll(*((uint64_t *)(base+64)));
vals->notesfoldersmodseq = ntohll(*((uint64_t *)(base+72)));
vals->uidvalidity = ntohl(*((uint32_t *)(base+80)));
break;
case 4:
if (len != 104) return IMAP_MAILBOX_CHECKSUM;
if (crc32_map(base, 100) != ntohl(*((uint32_t *)(base+100))))
return IMAP_MAILBOX_CHECKSUM;
vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
vals->mailfoldersmodseq = ntohll(*((uint64_t *)(base+48)));
vals->caldavfoldersmodseq = ntohll(*((uint64_t *)(base+56)));
vals->carddavfoldersmodseq = ntohll(*((uint64_t *)(base+64)));
vals->notesfoldersmodseq = ntohll(*((uint64_t *)(base+72)));
vals->quotamodseq = ntohll(*((uint64_t *)(base+80)));
vals->raclmodseq = ntohll(*((uint64_t *)(base+88)));
vals->uidvalidity = ntohl(*((uint32_t *)(base+96)));
break;
case 5:
if (len != 120) return IMAP_MAILBOX_CHECKSUM;
if (crc32_map(base, 116) != ntohl(*((uint32_t *)(base+116))))
return IMAP_MAILBOX_CHECKSUM;
vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
vals->mailfoldersmodseq = ntohll(*((uint64_t *)(base+48)));
vals->caldavfoldersmodseq = ntohll(*((uint64_t *)(base+56)));
vals->carddavfoldersmodseq = ntohll(*((uint64_t *)(base+64)));
vals->notesfoldersmodseq = ntohll(*((uint64_t *)(base+72)));
vals->quotamodseq = ntohll(*((uint64_t *)(base+80)));
vals->raclmodseq = ntohll(*((uint64_t *)(base+88)));
vals->submissionmodseq = ntohll(*((uint64_t *)(base+96)));
vals->submissionfoldersmodseq = ntohll(*((uint64_t *)(base+104)));
vals->uidvalidity = ntohl(*((uint32_t *)(base+112)));
break;
case 6:
if (len != 200) return IMAP_MAILBOX_CHECKSUM;
if (crc32_map(base, 196) != ntohl(*((uint32_t *)(base+196))))
return IMAP_MAILBOX_CHECKSUM;
vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
vals->mailfoldersmodseq = ntohll(*((uint64_t *)(base+48)));
vals->caldavfoldersmodseq = ntohll(*((uint64_t *)(base+56)));
vals->carddavfoldersmodseq = ntohll(*((uint64_t *)(base+64)));
vals->notesfoldersmodseq = ntohll(*((uint64_t *)(base+72)));
vals->quotamodseq = ntohll(*((uint64_t *)(base+80)));
vals->raclmodseq = ntohll(*((uint64_t *)(base+88)));
vals->submissionmodseq = ntohll(*((uint64_t *)(base+96)));
vals->submissionfoldersmodseq = ntohll(*((uint64_t *)(base+104)));
vals->maildeletedmodseq = ntohll(*((uint64_t *)(base+112)));
vals->caldavdeletedmodseq = ntohll(*((uint64_t *)(base+120)));
vals->carddavdeletedmodseq = ntohll(*((uint64_t *)(base+128)));
vals->notesdeletedmodseq = ntohll(*((uint64_t *)(base+136)));
vals->submissiondeletedmodseq = ntohll(*((uint64_t *)(base+144)));
vals->mailfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+152)));
vals->caldavfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+160)));
vals->carddavfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+168)));
vals->notesfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+176)));
vals->submissionfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+184)));
vals->uidvalidity = ntohl(*((uint32_t *)(base+192)));
break;
case 7:
if (len != 232) return IMAP_MAILBOX_CHECKSUM;
if (crc32_map(base, 228) != ntohl(*((uint32_t *)(base+228))))
return IMAP_MAILBOX_CHECKSUM;
vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
vals->mailfoldersmodseq = ntohll(*((uint64_t *)(base+48)));
vals->caldavfoldersmodseq = ntohll(*((uint64_t *)(base+56)));
vals->carddavfoldersmodseq = ntohll(*((uint64_t *)(base+64)));
vals->notesfoldersmodseq = ntohll(*((uint64_t *)(base+72)));
vals->quotamodseq = ntohll(*((uint64_t *)(base+80)));
vals->raclmodseq = ntohll(*((uint64_t *)(base+88)));
vals->submissionmodseq = ntohll(*((uint64_t *)(base+96)));
vals->submissionfoldersmodseq = ntohll(*((uint64_t *)(base+104)));
vals->maildeletedmodseq = ntohll(*((uint64_t *)(base+112)));
vals->caldavdeletedmodseq = ntohll(*((uint64_t *)(base+120)));
vals->carddavdeletedmodseq = ntohll(*((uint64_t *)(base+128)));
vals->notesdeletedmodseq = ntohll(*((uint64_t *)(base+136)));
vals->submissiondeletedmodseq = ntohll(*((uint64_t *)(base+144)));
vals->mailfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+152)));
vals->caldavfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+160)));
vals->carddavfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+168)));
vals->notesfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+176)));
vals->submissionfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+184)));
vals->davnotificationmodseq = ntohll(*((uint64_t *)(base+192)));
vals->davnotificationdeletedmodseq = ntohll(*((uint64_t *)(base+200)));
vals->davnotificationfoldersmodseq = ntohll(*((uint64_t *)(base+208)));
vals->davnotificationfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+216)));
vals->uidvalidity = ntohl(*((uint32_t *)(base+224)));
break;
case 8:
if (len != 264) return IMAP_MAILBOX_CHECKSUM;
if (crc32_map(base, 260) != ntohl(*((uint32_t *)(base+260))))
return IMAP_MAILBOX_CHECKSUM;
vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
vals->mailfoldersmodseq = ntohll(*((uint64_t *)(base+48)));
vals->caldavfoldersmodseq = ntohll(*((uint64_t *)(base+56)));
vals->carddavfoldersmodseq = ntohll(*((uint64_t *)(base+64)));
vals->notesfoldersmodseq = ntohll(*((uint64_t *)(base+72)));
vals->quotamodseq = ntohll(*((uint64_t *)(base+80)));
vals->raclmodseq = ntohll(*((uint64_t *)(base+88)));
vals->submissionmodseq = ntohll(*((uint64_t *)(base+96)));
vals->submissionfoldersmodseq = ntohll(*((uint64_t *)(base+104)));
vals->maildeletedmodseq = ntohll(*((uint64_t *)(base+112)));
vals->caldavdeletedmodseq = ntohll(*((uint64_t *)(base+120)));
vals->carddavdeletedmodseq = ntohll(*((uint64_t *)(base+128)));
vals->notesdeletedmodseq = ntohll(*((uint64_t *)(base+136)));
vals->submissiondeletedmodseq = ntohll(*((uint64_t *)(base+144)));
vals->mailfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+152)));
vals->caldavfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+160)));
vals->carddavfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+168)));
vals->notesfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+176)));
vals->submissionfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+184)));
vals->davnotificationmodseq = ntohll(*((uint64_t *)(base+192)));
vals->davnotificationdeletedmodseq = ntohll(*((uint64_t *)(base+200)));
vals->davnotificationfoldersmodseq = ntohll(*((uint64_t *)(base+208)));
vals->davnotificationfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+216)));
vals->jmapnotificationmodseq = ntohll(*((uint64_t *)(base+224)));
vals->jmapnotificationdeletedmodseq = ntohll(*((uint64_t *)(base+232)));
vals->jmapnotificationfoldersmodseq = ntohll(*((uint64_t *)(base+240)));
vals->jmapnotificationfoldersdeletedmodseq = ntohll(*((uint64_t *)(base+248)));
vals->uidvalidity = ntohl(*((uint32_t *)(base+256)));
break;
default:
return IMAP_MAILBOX_BADFORMAT;
}
return 0;
}
/* NOTE: you need a MV_LENGTH buffer to write into, aligned on 8 byte boundaries */
static void mboxname_counters_to_buf(const struct mboxname_counters *vals, char *base)
{
*((uint32_t *)(base+MV_OFF_GENERATION)) = htonl(vals->generation);
*((uint32_t *)(base+MV_OFF_VERSION)) = htonl(MV_VERSION);
align_htonll(base+MV_OFF_HIGHESTMODSEQ, vals->highestmodseq);
align_htonll(base+MV_OFF_MAILMODSEQ, vals->mailmodseq);
align_htonll(base+MV_OFF_CALDAVMODSEQ, vals->caldavmodseq);
align_htonll(base+MV_OFF_CARDDAVMODSEQ, vals->carddavmodseq);
align_htonll(base+MV_OFF_NOTESMODSEQ, vals->notesmodseq);
align_htonll(base+MV_OFF_MAILFOLDERSMODSEQ, vals->mailfoldersmodseq);
align_htonll(base+MV_OFF_CALDAVFOLDERSMODSEQ, vals->caldavfoldersmodseq);
align_htonll(base+MV_OFF_CARDDAVFOLDERSMODSEQ, vals->carddavfoldersmodseq);
align_htonll(base+MV_OFF_NOTESFOLDERSMODSEQ, vals->notesfoldersmodseq);
align_htonll(base+MV_OFF_QUOTAMODSEQ, vals->quotamodseq);
align_htonll(base+MV_OFF_RACLMODSEQ, vals->raclmodseq);
align_htonll(base+MV_OFF_SUBMISSIONMODSEQ, vals->submissionmodseq);
align_htonll(base+MV_OFF_SUBMISSIONFOLDERSMODSEQ, vals->submissionfoldersmodseq);
align_htonll(base+MV_OFF_MAILDELETEDMODSEQ, vals->maildeletedmodseq);
align_htonll(base+MV_OFF_CALDAVDELETEDMODSEQ, vals->caldavdeletedmodseq);
align_htonll(base+MV_OFF_CARDDAVDELETEDMODSEQ, vals->carddavdeletedmodseq);
align_htonll(base+MV_OFF_NOTESDELETEDMODSEQ, vals->notesdeletedmodseq);
align_htonll(base+MV_OFF_SUBMISSIONDELETEDMODSEQ,
vals->submissiondeletedmodseq);
align_htonll(base+MV_OFF_MAILFOLDERSDELETEDMODSEQ,
vals->mailfoldersdeletedmodseq);
align_htonll(base+MV_OFF_CALDAVFOLDERSDELETEDMODSEQ,
vals->caldavfoldersdeletedmodseq);
align_htonll(base+MV_OFF_CARDDAVFOLDERSDELETEDMODSEQ,
vals->carddavfoldersdeletedmodseq);
align_htonll(base+MV_OFF_NOTESFOLDERSDELETEDMODSEQ,
vals->notesfoldersdeletedmodseq);
align_htonll(base+MV_OFF_SUBMISSIONFOLDERSDELETEDMODSEQ,
vals->submissionfoldersdeletedmodseq);
align_htonll(base+MV_OFF_DAVNOTIFICATIONMODSEQ,
vals->davnotificationmodseq);
align_htonll(base+MV_OFF_DAVNOTIFICATIONDELETEDMODSEQ,
vals->davnotificationdeletedmodseq);
align_htonll(base+MV_OFF_DAVNOTIFICATIONFOLDERSMODSEQ,
vals->davnotificationfoldersmodseq);
align_htonll(base+MV_OFF_DAVNOTIFICATIONFOLDERSDELETEDMODSEQ,
vals->davnotificationfoldersdeletedmodseq);
align_htonll(base+MV_OFF_JMAPNOTIFICATIONMODSEQ,
vals->jmapnotificationmodseq);
align_htonll(base+MV_OFF_JMAPNOTIFICATIONDELETEDMODSEQ,
vals->jmapnotificationdeletedmodseq);
align_htonll(base+MV_OFF_JMAPNOTIFICATIONFOLDERSMODSEQ,
vals->jmapnotificationfoldersmodseq);
align_htonll(base+MV_OFF_JMAPNOTIFICATIONFOLDERSDELETEDMODSEQ,
vals->jmapnotificationfoldersdeletedmodseq);
*((uint32_t *)(base+MV_OFF_UIDVALIDITY)) = htonl(vals->uidvalidity);
*((uint32_t *)(base+MV_OFF_CRC)) = htonl(crc32_map(base, MV_OFF_CRC));
}
/* XXX - inform about errors? Any error causes the value of at least
last+1 to be returned. An error only on writing causes
max(last, fileval) + 1 to still be returned */
static int mboxname_load_counters(const char *mboxname, struct mboxname_counters *vals, int *fdp)
{
int fd = -1;
char *fname = NULL;
struct stat sbuf, fbuf;
const char *base = NULL;
size_t len = 0;
mbname_t *mbname = NULL;
int r = 0;
memset(vals, 0, sizeof(struct mboxname_counters));
mbname = mbname_from_intname(mboxname);
fname = mboxname_conf_getpath(mbname, "counters");
if (!fname) {
r = IMAP_MAILBOX_BADNAME;
goto done;
}
/* get a blocking lock on fd */
for (;;) {
fd = open(fname, O_RDWR | O_CREAT, 0644);
if (fd == -1) {
/* OK to not exist - try creating the directory first */
if (cyrus_mkdir(fname, 0755)) goto done;
fd = open(fname, O_RDWR | O_CREAT, 0644);
}
if (fd == -1) {
syslog(LOG_ERR, "IOERROR: failed to create %s: %m", fname);
goto done;
}
if (lock_blocking(fd, fname)) {
syslog(LOG_ERR, "IOERROR: failed to lock %s: %m", fname);
goto done;
}
if (fstat(fd, &sbuf)) {
syslog(LOG_ERR, "IOERROR: failed to stat fd %s: %m", fname);
goto done;
}
if (stat(fname, &fbuf)) {
syslog(LOG_ERR, "IOERROR: failed to stat file %s: %m", fname);
goto done;
}
if (sbuf.st_ino == fbuf.st_ino) break;
lock_unlock(fd, fname);
close(fd);
fd = -1;
}
if (fd < 0) {
r = IMAP_IOERROR;
goto done;
}
if (sbuf.st_size >= 8) {
/* read the old value */
map_refresh(fd, 1, &base, &len, sbuf.st_size, "counters", mboxname);
if (len >= 8) {
r = mboxname_buf_to_counters(base, len, vals);
}
map_free(&base, &len);
}
else {
/* going to have to read the old files */
vals->mailmodseq = vals->caldavmodseq = vals->carddavmodseq =
vals->highestmodseq = mboxname_readval_old(mboxname, "modseq");
vals->uidvalidity = mboxname_readval_old(mboxname, "uidvalidity");
}
done:
if (r) {
if (fd != -1) {
lock_unlock(fd, fname);
close(fd);
}
}
else {
/* maintain the lock until we're done */
*fdp = fd;
}
mbname_free(&mbname);
free(fname);
return r;
}
static int mboxname_set_counters(const char *mboxname, struct mboxname_counters *vals, int fd)
{
char *fname = NULL;
mbname_t *mbname = NULL;
char buf[MV_LENGTH];
char newfname[MAX_MAILBOX_PATH];
int newfd = -1;
int n = 0;
int r = 0;
mbname = mbname_from_intname(mboxname);
fname = mboxname_conf_getpath(mbname, "counters");
if (!fname) {
r = IMAP_MAILBOX_BADNAME;
goto done;
}
snprintf(newfname, MAX_MAILBOX_PATH, "%s.NEW", fname);
newfd = open(newfname, O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (newfd == -1) {
r = IMAP_IOERROR;
syslog(LOG_ERR, "IOERROR: failed to open for write %s: %m", newfname);
goto done;
}
/* it's a new generation! */
vals->generation++;
mboxname_counters_to_buf(vals, buf);
n = retry_write(newfd, buf, MV_LENGTH);
if (n < 0) {
r = IMAP_IOERROR;
syslog(LOG_ERR, "IOERROR: failed to write %s: %m", newfname);
goto done;
}
if (fdatasync(newfd)) {
r = IMAP_IOERROR;
syslog(LOG_ERR, "IOERROR: failed to fdatasync %s: %m", newfname);
goto done;
}
close(newfd);
newfd = -1;
if (rename(newfname, fname)) {
r = IMAP_IOERROR;
syslog(LOG_ERR, "IOERROR: failed to rename %s: %m", newfname);
goto done;
}
done:
if (newfd != -1) close(newfd);
if (fd != -1) {
lock_unlock(fd, fname);
close(fd);
}
mbname_free(&mbname);
free(fname);
return r;
}
static int mboxname_unload_counters(int fd)
{
lock_unlock(fd, NULL);
close(fd);
return 0;
}
EXPORTED int mboxname_read_counters(const char *mboxname, struct mboxname_counters *vals)
{
int r = 0;
mbname_t *mbname = NULL;
struct stat sbuf;
char *fname = NULL;
const char *base = NULL;
size_t len = 0;
int fd = -1;
memset(vals, 0, sizeof(struct mboxname_counters));
mbname = mbname_from_intname(mboxname);
fname = mboxname_conf_getpath(mbname, "counters");
if (!fname) {
r = IMAP_MAILBOX_BADNAME;
goto done;
}
fd = open(fname, O_RDONLY);
/* if no file, import from the old files potentially, and write a file regardless */
if (fd < 0) {
/* race => multiple rewrites, won't hurt too much */
r = mboxname_load_counters(mboxname, vals, &fd);
if (r) goto done;
r = mboxname_set_counters(mboxname, vals, fd);
fd = -1;
if (r) goto done;
free(fname);
fname = mboxname_conf_getpath(mbname, "modseq");
if (fname) unlink(fname);
free(fname);
fname = mboxname_conf_getpath(mbname, "uidvalidity");
if (fname) unlink(fname);
goto done;
}
if (fstat(fd, &sbuf)) {
syslog(LOG_ERR, "IOERROR: failed to stat fd %s: %m", fname);
r = IMAP_IOERROR;
goto done;
}
if (sbuf.st_size >= 8) {
map_refresh(fd, 1, &base, &len, sbuf.st_size, "counters", mboxname);
if (len >= 8)
r = mboxname_buf_to_counters(base, len, vals);
map_free(&base, &len);
}
done:
if (fd != -1) close(fd);
mbname_free(&mbname);
free(fname);
return r;
}
enum domodseq { MBOXMODSEQ, QUOTAMODSEQ, RACLMODSEQ };
static modseq_t mboxname_domodseq(const char *mboxname,
modseq_t last,
enum domodseq domodseq,
int mbtype, // for MBOXMODSEQ
int flags, // for MBOXMODSEQ
modseq_t add)
{
struct mboxname_counters counters;
struct mboxname_counters oldcounters;
modseq_t *typemodseqp = NULL;
modseq_t *foldersmodseqp = NULL;
int fd = -1;
int dofolder = flags & MBOXMODSEQ_ISFOLDER;
int isdelete = flags & MBOXMODSEQ_ISDELETE;
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return last + add;
/* XXX error handling */
if (mboxname_load_counters(mboxname, &counters, &fd))
return last + add;
oldcounters = counters;
if (domodseq == MBOXMODSEQ) {
if (mboxname_isaddressbookmailbox(mboxname, mbtype)) {
typemodseqp = isdelete ?
&counters.carddavdeletedmodseq :
&counters.carddavmodseq;
foldersmodseqp = isdelete ?
&counters.carddavfoldersdeletedmodseq :
&counters.carddavfoldersmodseq;
}
else if (mboxname_iscalendarmailbox(mboxname, mbtype)) {
typemodseqp = isdelete ?
&counters.caldavdeletedmodseq :
&counters.caldavmodseq;
foldersmodseqp = isdelete ?
&counters.caldavfoldersdeletedmodseq :
&counters.caldavfoldersmodseq;
}
else if (mboxname_isnotesmailbox(mboxname, mbtype)) {
typemodseqp = isdelete ?
&counters.notesdeletedmodseq :
&counters.notesmodseq;
foldersmodseqp = isdelete ?
&counters.notesfoldersdeletedmodseq :
&counters.notesfoldersmodseq;
}
else if (mboxname_issubmissionmailbox(mboxname, mbtype)) {
typemodseqp = isdelete ?
&counters.submissiondeletedmodseq :
&counters.submissionmodseq;
foldersmodseqp = isdelete ?
&counters.submissionfoldersdeletedmodseq :
&counters.submissionfoldersmodseq;
}
else if (mboxname_isdavnotificationsmailbox(mboxname, mbtype)) {
typemodseqp = isdelete ?
&counters.davnotificationdeletedmodseq :
&counters.davnotificationmodseq;
foldersmodseqp = isdelete ?
&counters.davnotificationfoldersdeletedmodseq :
&counters.davnotificationfoldersmodseq;
}
else {
typemodseqp = isdelete ?
&counters.maildeletedmodseq :
&counters.mailmodseq;
foldersmodseqp = isdelete ?
&counters.mailfoldersdeletedmodseq :
&counters.mailfoldersmodseq;
}
}
else if (domodseq == QUOTAMODSEQ) {
typemodseqp = &counters.quotamodseq;
dofolder = 0;
}
else if (domodseq == RACLMODSEQ) {
typemodseqp = &counters.raclmodseq;
dofolder = 0;
}
/* make sure all counters are at least the old value */
if (counters.highestmodseq < last)
counters.highestmodseq = last;
if (*typemodseqp < last)
*typemodseqp = last;
if (dofolder && *foldersmodseqp < last)
*foldersmodseqp = last;
/* if adding, bring all counters up to the overall highest modseq */
if (add) {
counters.highestmodseq += add;
*typemodseqp = counters.highestmodseq;
if (dofolder) *foldersmodseqp = counters.highestmodseq;
}
if (memcmp(&counters, &oldcounters, sizeof(struct mboxname_counters)))
mboxname_set_counters(mboxname, &counters, fd);
else
mboxname_unload_counters(fd);
return counters.highestmodseq;
}
EXPORTED modseq_t mboxname_nextmodseq(const char *mboxname, modseq_t last, int mbtype, int flags)
{
return mboxname_domodseq(mboxname, last, MBOXMODSEQ, mbtype, flags, 1);
}
EXPORTED modseq_t mboxname_setmodseq(const char *mboxname, modseq_t last, int mbtype, int flags)
{
return mboxname_domodseq(mboxname, last, MBOXMODSEQ, mbtype, flags, 0);
}
EXPORTED modseq_t mboxname_readquotamodseq(const char *mboxname)
{
struct mboxname_counters counters;
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return 0;
if (mboxname_read_counters(mboxname, &counters))
return 0;
return counters.quotamodseq;
}
EXPORTED modseq_t mboxname_nextquotamodseq(const char *mboxname, modseq_t last)
{
return mboxname_domodseq(mboxname, last, QUOTAMODSEQ, 0, 0, 1);
}
EXPORTED modseq_t mboxname_setquotamodseq(const char *mboxname, modseq_t last)
{
return mboxname_domodseq(mboxname, last, QUOTAMODSEQ, 0, 0, 0);
}
EXPORTED modseq_t mboxname_readraclmodseq(const char *mboxname)
{
struct mboxname_counters counters;
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return 0;
if (!mboxname_isusermailbox(mboxname, /*isinbox*/1))
return 0; // raclmodseq is only defined on user inboxes
if (mboxname_read_counters(mboxname, &counters))
return 0;
return counters.raclmodseq;
}
EXPORTED modseq_t mboxname_nextraclmodseq(const char *mboxname, modseq_t last)
{
return mboxname_domodseq(mboxname, last, RACLMODSEQ, 0, 0, 1);
}
EXPORTED modseq_t mboxname_setraclmodseq(const char *mboxname, modseq_t last)
{
return mboxname_domodseq(mboxname, last, RACLMODSEQ, 0, 0, 0);
}
EXPORTED uint32_t mboxname_readuidvalidity(const char *mboxname)
{
struct mboxname_counters counters;
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return 0;
if (mboxname_read_counters(mboxname, &counters))
return 0;
return counters.uidvalidity;
}
EXPORTED uint32_t mboxname_nextuidvalidity(const char *mboxname, uint32_t last)
{
struct mboxname_counters counters;
int fd = -1;
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return last + 1;
/* XXX error handling */
if (mboxname_load_counters(mboxname, &counters, &fd))
return last + 1;
if (counters.uidvalidity < last)
counters.uidvalidity = last;
counters.uidvalidity++;
/* always set, because we always increased */
mboxname_set_counters(mboxname, &counters, fd);
return counters.uidvalidity;
}
EXPORTED uint32_t mboxname_setuidvalidity(const char *mboxname, uint32_t val)
{
struct mboxname_counters counters;
int fd = -1;
int dirty = 0;
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return val;
/* XXX error handling */
if (mboxname_load_counters(mboxname, &counters, &fd))
return val;
if (counters.uidvalidity < val) {
counters.uidvalidity = val;
dirty = 1;
}
if (dirty)
mboxname_set_counters(mboxname, &counters, fd);
else
mboxname_unload_counters(fd);
return val;
}
EXPORTED char *mboxname_common_ancestor(const char *mboxname1, const char *mboxname2)
{
mbname_t *mbname1 = mbname_from_intname(mboxname1);
mbname_t *mbname2 = mbname_from_intname(mboxname2);
char *ancestor = NULL;
if (!mbname_same_userid(mbname1, mbname2))
goto done;
const strarray_t *boxes1 = mbname_boxes(mbname1);
const strarray_t *boxes2 = mbname_boxes(mbname2);
int len = boxes1->count < boxes2->count ? boxes1->count : boxes2->count;
int i;
for (i = 0; i < len - 1; i++) {
if (strcmp(strarray_nth(boxes1, i), strarray_nth(boxes2, i)))
break;
}
if (i > 0) {
mbname_truncate_boxes(mbname1, i);
ancestor = xstrdup(mbname_intname(mbname1));
}
done:
mbname_free(&mbname1);
mbname_free(&mbname2);
return ancestor;
}