Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
369 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/imap/mboxname.c b/imap/mboxname.c
index 83e7f5417..1f6b44f19 100644
--- a/imap/mboxname.c
+++ b/imap/mboxname.c
@@ -1,3277 +1,3277 @@
/* 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 mbname_t *mbname_from_extnameUTF8(const char *extname, const struct namespace *ns, const char *userid)
{
mbname_t *mbname;
char *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;
}
mbname = mbname_from_extname(extname, ns, userid);
free(freeme);
return mbname;
}
EXPORTED mbname_t *mbname_from_path(const char *path)
{
int absolute = 0;
mbname_t *mbname = NULL;
mbentry_t *mbentry = NULL;
const char *uid;
/* Is the mailbox argument absolute or relative to cwd? */
if (path[0] == '/') {
absolute = 1;
uid = strrchr(path, '/');
}
else {
/* Construct a mailbox relative to cwd */
char cwd[MAX_MAILBOX_PATH+1];
if (!getcwd(cwd, MAX_MAILBOX_PATH)) {
syslog(LOG_ERR, "IOERROR: failed to getcwd in mbname_from_path %s: %m", path);
}
uid = strrchr(cwd, '/');
}
if (uid) {
/* Lookup current mailbox by uniqueid */
int r = mboxlist_lookup_by_uniqueid(uid+1, &mbentry, NULL);
if (!r) {
/* Build current mailbox name */
mbname = mbname_from_intname(mbentry->name);
if (!absolute) {
/* Add submailbox(es) */
strarray_t *subs = subs = strarray_split(path, "/", 0);
int i;
for (i = 0; i < strarray_size(subs); i++) {
const char *sub = strarray_nth(subs, i);
if (!strcmp(sub, ".")) continue;
else if (!strcmp(sub, "..")) {
char *s = mbname_pop_boxes(mbname);
if (s) free(s);
else {
/* At top of hierarchy */
mbname_free(&mbname);
break;
}
}
else {
mbname_push_boxes(mbname, strarray_nth(subs, i));
}
}
strarray_free(subs);
/* If we end up at a magic user.foo.INBOX, revert to user.foo */
if (mbname_userid(mbname) != NULL &&
strarray_size(mbname_boxes(mbname)) == 1 &&
!strcmp(strarray_nth(mbname_boxes(mbname), 0), "INBOX")) {
free(mbname_pop_boxes(mbname));
}
}
}
}
mboxlist_entry_free(&mbentry);
return mbname ? mbname : xzmalloc(sizeof(mbname_t));
}
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_isa(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_isa(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_isa(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_isa(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 JMAPNOTIFICATIONS mailbox
* returns boolean
*/
EXPORTED int mboxname_isjmapnotificationsmailbox(const char *name, int mbtype)
{
if (mbtype_isa(mbtype) == MBTYPE_JMAPNOTIFY) 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_JMAPNOTIFICATIONFOLDER);
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_isa(mbtype) == MBTYPE_JMAPSUBMIT) 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_isa(mbtype) == MBTYPE_JMAPPUSHSUB) 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);
}
EXPORTED void mboxname_id_hash(char *dest, size_t destlen,
const char *root,
const char *id)
{
struct buf buf = BUF_INITIALIZER;
if (root) buf_printf(&buf, "%s/uuid/%c/%c/%s", root, id[0], id[1], id);
else buf_printf(&buf, "uuid/%c/%c/%s", id[0], id[1], id);
/* for now, keep API even though we're doing a buffer inside here */
strncpy(dest, buf_cstring(&buf), destlen);
buf_free(&buf);
}
/* note: mboxname must be internal */
EXPORTED char *mboxname_datapath(const char *partition,
const char *mboxname,
const char *uniqueid,
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;
}
if (uniqueid) {
/* Mailbox dir by uniqueid */
mboxname_id_hash(pathresult, MAX_MAILBOX_PATH, root, uniqueid);
}
else {
/* Legacy mailbox dir by mboxname */
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,
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;
}
if (uniqueid) {
/* Mailbox dir by uniqueid */
mboxname_id_hash(pathresult, MAX_MAILBOX_PATH, root, uniqueid);
}
else {
/* Legacy mailbox dir by mboxname */
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,
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;
}
if (uniqueid) {
/* Mailbox dir by uniqueid */
mboxname_id_hash(metaresult, MAX_MAILBOX_PATH, root, uniqueid);
}
else {
/* Legacy mailbox dir by mboxname */
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;
if (mbname->localpart) {
char *inboxname = mboxname_user_mbox(mbname_userid(mbname), NULL);
mbentry_t *mbentry = NULL;
int r = mboxlist_lookup_allow_all(inboxname, &mbentry, NULL);
free(inboxname);
if (r == IMAP_MAILBOX_NONEXISTENT) {
// look for the INBOX of previous names in order to see if we're mid-rename
mbentry_t *mbentry_byname = NULL;
mbentry_t *mbentry_byid = NULL;
r = mboxlist_lookup_allow_all(mbname_intname(mbname), &mbentry_byname, NULL);
if (!r) r = mboxlist_lookup_by_uniqueid(mbentry_byname->uniqueid, &mbentry_byid, NULL);
if (!r) {
int i;
for (i = 0; i < ptrarray_size(&mbentry_byid->name_history); i++) {
- const former_name_t *fname = ptrarray_nth(&mbentry_byid->name_history, i);
- char *olduserid = mboxname_to_userid(fname->name);
+ const former_name_t *histitem = ptrarray_nth(&mbentry_byid->name_history, i);
+ char *olduserid = mboxname_to_userid(histitem->name);
char *oldinboxname = mboxname_user_mbox(olduserid, NULL);
r = mboxlist_lookup_allow_all(oldinboxname, &mbentry, NULL);
free(oldinboxname);
free(olduserid);
if (r != IMAP_MAILBOX_NONEXISTENT) break;
}
}
mboxlist_entry_free(&mbentry_byid);
mboxlist_entry_free(&mbentry_byname);
}
if (!r && !(mbentry->mbtype & MBTYPE_LEGACY_DIRS)) {
fname = mboxid_conf_getpath(mbentry->uniqueid, suffix);
}
mboxlist_entry_free(&mbentry);
}
if (!fname) {
fname = mboxname_conf_getpath_legacy(mbname, suffix);
}
return fname;
}
EXPORTED char *mboxname_conf_getpath_legacy(const mbname_t *mbname,
const char *suffix)
{
char *fname = NULL;
char c[2], d[2];
if (mbname->localpart) {
if (mbname->domain) {
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_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 (mbname->domain) {
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 (suffix) {
fname = strconcat(config_dir,
"/", FNAME_SHAREDPREFIX, ".", suffix,
(char *)NULL);
}
else {
fname = xstrdup(config_dir);
}
}
return fname;
}
EXPORTED char *mboxid_conf_getpath(const char *mboxid, const char *suffix)
{
char *fname = NULL;
char path[MAX_MAILBOX_PATH+1];
mboxname_id_hash(path, MAX_MAILBOX_PATH, NULL, mboxid);
if (suffix) {
fname = strconcat(config_dir,
FNAME_USERDIR,
path, "/", suffix, ".db",
(char *)NULL);
}
else {
fname = strconcat(config_dir,
FNAME_USERDIR,
path,
(char *)NULL);
}
return fname;
}
/* ========================= COUNTERS ============================ */
#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_fcounters(const char *fname, struct mboxname_counters *vals, int *fdp)
{
int fd = -1;
struct stat sbuf, fbuf;
const char *base = NULL;
size_t len = 0;
int r = 0;
memset(vals, 0, sizeof(struct mboxname_counters));
/* 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) {
xsyslog(LOG_ERR, "IOERROR: create failed",
"filename=<%s>", fname);
goto done;
}
if (lock_blocking(fd, fname)) {
xsyslog(LOG_ERR, "IOERROR: lock_blocking failed",
"filename=<%s>", fname);
goto done;
}
if (fstat(fd, &sbuf)) {
xsyslog(LOG_ERR, "IOERROR: fstat failed",
"filename=<%s>", fname);
goto done;
}
if (stat(fname, &fbuf)) {
xsyslog(LOG_ERR, "IOERROR: stat failed",
"filename=<%s>", 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) {
/* read the old value */
map_refresh(fd, 1, &base, &len, sbuf.st_size, "counters", fname);
if (len >= 8) {
r = mboxname_buf_to_counters(base, len, vals);
}
map_free(&base, &len);
}
done:
if (r) {
if (fd != -1) {
lock_unlock(fd, fname);
close(fd);
}
}
else {
/* maintain the lock until we're done */
*fdp = fd;
}
return r;
}
static int mboxname_set_fcounters(const char *fname, struct mboxname_counters *vals, int fd)
{
char buf[MV_LENGTH];
char newfname[MAX_MAILBOX_PATH];
int newfd = -1;
int n = 0;
int r = 0;
snprintf(newfname, MAX_MAILBOX_PATH, "%s.NEW", fname);
newfd = open(newfname, O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (newfd == -1) {
r = IMAP_IOERROR;
xsyslog(LOG_ERR, "IOERROR: open failed",
"filename=<%s>", 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;
xsyslog(LOG_ERR, "IOERROR: retry_write failed",
"filename=<%s>", newfname);
goto done;
}
if (fdatasync(newfd)) {
r = IMAP_IOERROR;
xsyslog(LOG_ERR, "IOERROR: fdatasync failed",
"filename=<%s>", newfname);
goto done;
}
close(newfd);
newfd = -1;
if (rename(newfname, fname)) {
r = IMAP_IOERROR;
xsyslog(LOG_ERR, "IOERROR: rename failed",
"oldfname=<%s> newfname=<%s>",
newfname, fname);
goto done;
}
done:
if (newfd != -1) close(newfd);
if (fd != -1) {
lock_unlock(fd, fname);
close(fd);
}
return r;
}
static int mboxname_unload_counters(int fd)
{
lock_unlock(fd, NULL);
close(fd);
return 0;
}
static int mboxname_read_fcounters(const char *fname, struct mboxname_counters *vals)
{
int r = 0;
struct stat sbuf;
const char *base = NULL;
size_t len = 0;
int fd = -1;
memset(vals, 0, sizeof(struct mboxname_counters));
fd = open(fname, O_RDONLY);
if (fd < 0) goto done;
if (fstat(fd, &sbuf)) {
xsyslog(LOG_ERR, "IOERROR: fstat failed",
"filename=<%s>", fname);
r = IMAP_IOERROR;
goto done;
}
if (sbuf.st_size >= 8) {
map_refresh(fd, 1, &base, &len, sbuf.st_size, "counters", fname);
if (len >= 8)
r = mboxname_buf_to_counters(base, len, vals);
map_free(&base, &len);
}
done:
if (fd != -1) close(fd);
return r;
}
EXPORTED int mboxname_read_counters(const char *mboxname, struct mboxname_counters *vals)
{
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
int r = mboxname_read_fcounters(fname, vals);
free(fname);
mbname_free(&mbname);
return r;
}
enum domodseq { MBOXMODSEQ, QUOTAMODSEQ, RACLMODSEQ };
static modseq_t mboxname_domodseq(const char *fname,
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_fcounters(fname, &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 if (mboxname_isjmapnotificationsmailbox(mboxname, mbtype)) {
typemodseqp = isdelete ?
&counters.jmapnotificationdeletedmodseq :
&counters.jmapnotificationmodseq;
foldersmodseqp = isdelete ?
&counters.jmapnotificationfoldersdeletedmodseq :
&counters.jmapnotificationfoldersmodseq;
}
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_fcounters(fname, &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)
{
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
modseq_t modseq = mboxname_domodseq(fname, mboxname, last, MBOXMODSEQ, mbtype, flags, 1);
free(fname);
mbname_free(&mbname);
return modseq;
}
EXPORTED modseq_t mboxname_setmodseq(const char *mboxname, modseq_t last, int mbtype, int flags)
{
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
modseq_t modseq = mboxname_domodseq(fname, mboxname, last, MBOXMODSEQ, mbtype, flags, 0);
free(fname);
mbname_free(&mbname);
return modseq;
}
EXPORTED modseq_t mboxname_readquotamodseq(const char *mboxname)
{
struct mboxname_counters counters;
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return 0;
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
int r = mboxname_read_fcounters(fname, &counters);
free(fname);
mbname_free(&mbname);
if (r) return 0;
return counters.quotamodseq;
}
EXPORTED modseq_t mboxname_nextquotamodseq(const char *mboxname, modseq_t last)
{
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
modseq_t modseq = mboxname_domodseq(fname, mboxname, last, QUOTAMODSEQ, 0, 0, 1);
free(fname);
mbname_free(&mbname);
return modseq;
}
EXPORTED modseq_t mboxname_setquotamodseq(const char *mboxname, modseq_t last)
{
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
modseq_t modseq = mboxname_domodseq(fname, mboxname, last, QUOTAMODSEQ, 0, 0, 0);
free(fname);
mbname_free(&mbname);
return modseq;
}
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
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = fname = mboxname_conf_getpath(mbname, "counters");
int r = mboxname_read_fcounters(fname, &counters);
free(fname);
mbname_free(&mbname);
if (r) return 0;
return counters.raclmodseq;
}
EXPORTED modseq_t mboxname_nextraclmodseq(const char *mboxname, modseq_t last)
{
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
modseq_t modseq = mboxname_domodseq(fname, mboxname, last, RACLMODSEQ, 0, 0, 1);
free(fname);
mbname_free(&mbname);
return modseq;
}
EXPORTED modseq_t mboxname_setraclmodseq(const char *mboxname, modseq_t last)
{
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
modseq_t modseq = mboxname_domodseq(fname, mboxname, last, RACLMODSEQ, 0, 0, 0);
free(fname);
mbname_free(&mbname);
return modseq;
}
EXPORTED uint32_t mboxname_readuidvalidity(const char *mboxname)
{
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return 0;
struct mboxname_counters counters;
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
int r = mboxname_read_counters(mboxname, &counters);
free(fname);
mbname_free(&mbname);
if (r) return 0;
return counters.uidvalidity;
}
EXPORTED uint32_t mboxname_nextuidvalidity(const char *mboxname, uint32_t last)
{
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return last + 1;
struct mboxname_counters counters;
int fd = -1;
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
/* XXX error handling */
if (mboxname_load_fcounters(fname, &counters, &fd)) {
counters.uidvalidity = last + 1;
goto done;
}
if (counters.uidvalidity < last)
counters.uidvalidity = last;
counters.uidvalidity++;
/* always set, because we always increased */
mboxname_set_fcounters(fname, &counters, fd);
done:
free(fname);
mbname_free(&mbname);
return counters.uidvalidity;
}
EXPORTED uint32_t mboxname_setuidvalidity(const char *mboxname, uint32_t val)
{
if (!config_getswitch(IMAPOPT_CONVERSATIONS))
return val;
struct mboxname_counters counters;
int fd = -1;
int dirty = 0;
mbname_t *mbname = mbname_from_intname(mboxname);
char *fname = mboxname_conf_getpath(mbname, "counters");
if (mboxname_load_fcounters(fname, &counters, &fd)) {
counters.uidvalidity = val;
goto done;
}
if (counters.uidvalidity < val) {
counters.uidvalidity = val;
dirty = 1;
}
if (dirty)
mboxname_set_fcounters(fname, &counters, fd);
else
mboxname_unload_counters(fd);
done:
free(fname);
mbname_free(&mbname);
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;
}
diff --git a/imap/sync_support.c b/imap/sync_support.c
index 5ef5016dc..7145da1cd 100644
--- a/imap/sync_support.c
+++ b/imap/sync_support.c
@@ -1,7831 +1,7831 @@
/* sync_support.c -- Cyrus synchronization support functions
*
* 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>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sysexits.h>
#include <syslog.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <dirent.h>
#include <utime.h>
#include <limits.h>
#include "assert.h"
#include "bsearch.h"
#include "global.h"
#include "imap_proxy.h"
#include "mboxlist.h"
#include "mailbox.h"
#include "quota.h"
#include "xmalloc.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "imapd.h"
#include "message.h"
#include "util.h"
#include "user.h"
#include "prot.h"
#include "dlist.h"
#include "xstrlcat.h"
#include "strarray.h"
#include "ptrarray.h"
#include "sievedir.h"
#ifdef USE_CALALARMD
#include "caldav_alarm.h"
#endif
#ifdef USE_SIEVE
#include "sieve/sieve_interface.h"
#endif
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
#include "message_guid.h"
#include "sync_support.h"
#include "sync_log.h"
static int opt_force = 0; // FIXME
struct sync_client_state rightnow_sync_cs;
/* protocol definitions */
static char *imap_sasl_parsesuccess(char *str, const char **status);
static void imap_postcapability(struct backend *s);
struct protocol_t imap_csync_protocol =
{ "imap", "imap", TYPE_STD,
{ { { 1, NULL },
{ "C01 CAPABILITY", NULL, "C01 ", imap_postcapability,
CAPAF_MANY_PER_LINE,
{ { "AUTH", CAPA_AUTH },
{ "STARTTLS", CAPA_STARTTLS },
// FIXME doesn't work with compress at the moment for some reason
// { "COMPRESS=DEFLATE", CAPA_COMPRESS },
// FIXME do we need these ones?
// { "IDLE", CAPA_IDLE },
// { "MUPDATE", CAPA_MUPDATE },
// { "MULTIAPPEND", CAPA_MULTIAPPEND },
// { "RIGHTS=kxte", CAPA_ACLRIGHTS },
// { "LIST-EXTENDED", CAPA_LISTEXTENDED },
{ "SASL-IR", CAPA_SASL_IR },
{ "X-REPLICATION", CAPA_REPLICATION },
{ NULL, 0 } } },
{ "S01 STARTTLS", "S01 OK", "S01 NO", 0 },
{ "A01 AUTHENTICATE", 0, 0, "A01 OK", "A01 NO", "+ ", "*",
&imap_sasl_parsesuccess, AUTO_CAPA_AUTH_OK },
{ "Z01 COMPRESS DEFLATE", "* ", "Z01 OK" },
{ "N01 NOOP", "* ", "N01 OK" },
{ "Q01 LOGOUT", "* ", "Q01 " } } }
};
struct protocol_t csync_protocol =
{ "csync", "csync", TYPE_STD,
{ { { 1, "* OK" },
{ NULL, NULL, "* OK", NULL,
CAPAF_ONE_PER_LINE|CAPAF_SKIP_FIRST_WORD,
{ { "SASL", CAPA_AUTH },
{ "STARTTLS", CAPA_STARTTLS },
{ "COMPRESS=DEFLATE", CAPA_COMPRESS },
{ NULL, 0 } } },
{ "STARTTLS", "OK", "NO", 1 },
{ "AUTHENTICATE", USHRT_MAX, 0, "OK", "NO", "+ ", "*", NULL, 0 },
{ "COMPRESS DEFLATE", NULL, "OK" },
{ "NOOP", NULL, "OK" },
{ "EXIT", NULL, "OK" } } }
};
/* parse_success api is undocumented but my current understanding
* is that the caller expects it to return a pointer to the position
* within str at which base64 encoded "success data" can be found.
* status is for passing back other status data (if required) to
* the original caller.
*
* in the case of what we're doing here, there is no base64 encoded
* 'success data', but there is a capability string that we want to
* save. so we grab the capability string (including the []s) and
* chuck that in status, and then we return NULL to indicate the
* lack of base64 data.
*/
static char *imap_sasl_parsesuccess(char *str, const char **status)
{
syslog(LOG_DEBUG, "imap_sasl_parsesuccess(): input is: %s", str);
if (NULL == status) return NULL; /* nothing useful we can do */
const char *prelude = "A01 OK "; // FIXME don't hardcode this, get it from sasl_cmd->ok
const size_t prelude_len = strlen(prelude);
const char *capability = "[CAPABILITY ";
const size_t capability_len = strlen(capability);
char *start, *end;
if (strncmp(str, prelude, prelude_len)) {
/* this isn't the string we expected */
syslog(LOG_INFO, "imap_sasl_parsesuccess(): unexpected initial string contents: %s", str);
return NULL;
}
start = str + prelude_len;
if (strncmp(start, capability, capability_len)) {
/* this isn't a capability string */
syslog(LOG_INFO, "imap_sasl_parsesuccess(): str does not contain a capability string: %s", str);
return NULL;
}
end = start + capability_len;
while (*end != ']' && *end != '\0') {
end++;
}
if (*end == '\0') {
/* didn't find end of capability string */
syslog(LOG_INFO, "imap_sasl_parsesuccess(): did not find end of capability string: %s", str);
return NULL;
}
/* we want to keep the ], but crop the rest off */
*++end = '\0';
/* status gets the capability string */
syslog(LOG_DEBUG, "imap_sasl_parsesuccess(): found capability string: %s", start);
*status = start;
/* there's no base64 data, so return NULL */
return NULL;
}
static void imap_postcapability(struct backend *s)
{
if (CAPA(s, CAPA_SASL_IR)) {
/* server supports initial response in AUTHENTICATE command */
s->prot->u.std.sasl_cmd.maxlen = USHRT_MAX;
}
}
static const char *_synclock_name(const char *hostname, const char *userid)
{
const char *p;
static struct buf buf = BUF_INITIALIZER;
if (!userid) userid = ""; // no userid == global lock
buf_setcstr(&buf, "*S*");
for (p = hostname; *p; p++) {
switch(*p) {
case '.':
buf_putc(&buf, '^');
break;
default:
buf_putc(&buf, *p);
break;
}
}
buf_putc(&buf, '*');
for (p = userid; *p; p++) {
switch(*p) {
case '.':
buf_putc(&buf, '^');
break;
default:
buf_putc(&buf, *p);
break;
}
}
return buf_cstring(&buf);
}
static struct mboxlock *sync_lock(struct sync_client_state *sync_cs,
const char *userid)
{
const char *name = _synclock_name(sync_cs->servername, userid);
struct mboxlock *lock = NULL;
int r = mboxname_lock(name, &lock, LOCK_EXCLUSIVE);
return r ? NULL : lock;
}
/* channel-based configuration */
EXPORTED const char *sync_get_config(const char *channel, const char *val)
{
const char *response = NULL;
if (channel) {
char name[MAX_MAILBOX_NAME]; /* crazy long, but hey */
snprintf(name, MAX_MAILBOX_NAME, "%s_%s", channel, val);
response = config_getoverflowstring(name, NULL);
}
if (!response) {
/* get the core value */
if (!strcmp(val, "sync_host"))
response = config_getstring(IMAPOPT_SYNC_HOST);
else if (!strcmp(val, "sync_authname"))
response = config_getstring(IMAPOPT_SYNC_AUTHNAME);
else if (!strcmp(val, "sync_password"))
response = config_getstring(IMAPOPT_SYNC_PASSWORD);
else if (!strcmp(val, "sync_realm"))
response = config_getstring(IMAPOPT_SYNC_REALM);
else if (!strcmp(val, "sync_port"))
response = config_getstring(IMAPOPT_SYNC_PORT);
else if (!strcmp(val, "sync_shutdown_file"))
response = config_getstring(IMAPOPT_SYNC_SHUTDOWN_FILE);
else if (!strcmp(val, "sync_cache_db_path"))
response = config_getstring(IMAPOPT_SYNC_CACHE_DB_PATH);
else
fatal("unknown config variable requested", EX_SOFTWARE);
}
return response;
}
EXPORTED int sync_get_durationconfig(const char *channel, const char *val, int defunit)
{
int response = -1;
if (channel) {
const char *result = NULL;
char name[MAX_MAILBOX_NAME]; /* crazy long, but hey */
snprintf(name, MAX_MAILBOX_NAME, "%s_%s", channel, val);
result = config_getoverflowstring(name, NULL);
if (result)
config_parseduration(result, defunit, &response);
}
if (response == -1) {
if (!strcmp(val, "sync_repeat_interval"))
response = config_getduration(IMAPOPT_SYNC_REPEAT_INTERVAL, defunit);
}
return response;
}
EXPORTED int sync_get_switchconfig(const char *channel, const char *val)
{
int response = -1;
if (channel) {
const char *result = NULL;
char name[MAX_MAILBOX_NAME]; /* crazy long, but hey */
snprintf(name, sizeof(name), "%s_%s", channel, val);
result = config_getoverflowstring(name, NULL);
if (result) response = atoi(result);
}
if (response == -1) {
if (!strcmp(val, "sync_try_imap"))
response = config_getswitch(IMAPOPT_SYNC_TRY_IMAP);
}
return response;
}
/* Parse routines */
char *sync_encode_options(int options)
{
static char buf[4];
int i = 0;
if (options & OPT_POP3_NEW_UIDL)
buf[i++] = 'P';
if (options & OPT_IMAP_SHAREDSEEN)
buf[i++] = 'S';
if (options & OPT_IMAP_DUPDELIVER)
buf[i++] = 'D';
if (options & OPT_IMAP_HAS_ALARMS)
buf[i++] = 'A';
buf[i] = '\0';
return buf;
}
int sync_parse_options(const char *options)
{
int res = 0;
const char *p = options;
if (!options) return 0;
while (*p) {
switch(*p) {
case 'P':
res |= OPT_POP3_NEW_UIDL;
break;
case 'S':
res |= OPT_IMAP_SHAREDSEEN;
break;
case 'D':
res |= OPT_IMAP_DUPDELIVER;
break;
case 'A':
res |= OPT_IMAP_HAS_ALARMS;
break;
}
p++;
}
return res;
}
/* Get a simple line (typically error text) */
static int sync_getline(struct protstream *in, struct buf *buf)
{
int c;
buf_reset(buf);
for (;;) {
c = prot_getc(in);
if (c == EOF || (c == '\r') || (c == '\n')) {
/* Munch optional LF after CR */
if (c == '\r' && ((c = prot_getc(in)) != EOF && c != '\n')) {
prot_ungetc(c, in);
c = '\r';
}
buf_cstring(buf);
return c;
}
if (buf->len > config_maxword)
fatal("word too long", EX_IOERR);
buf_putc(buf, c);
}
return c;
}
/* ====================================================================== */
struct sync_msgid_list *sync_msgid_list_create(int hash_size)
{
struct sync_msgid_list *l = xzmalloc(sizeof (struct sync_msgid_list));
/* Pick a sensible default if no size given */
if (hash_size == 0)
hash_size = 256;
l->head = NULL;
l->tail = NULL;
l->hash_size = hash_size;
l->hash = xzmalloc(hash_size * sizeof(struct sync_msgid *));
l->count = 0;
l->toupload = 0;
return(l);
}
struct sync_msgid *sync_msgid_insert(struct sync_msgid_list *l,
const struct message_guid *guid)
{
struct sync_msgid *msgid;
int offset;
if (message_guid_isnull(guid))
return NULL;
offset = message_guid_hash(guid, l->hash_size);
/* do we already have it? Don't add it again */
for (msgid = l->hash[offset] ; msgid ; msgid = msgid->hash_next) {
if (message_guid_equal(&msgid->guid, guid))
return msgid;
}
msgid = xzmalloc(sizeof(struct sync_msgid));
msgid->need_upload = 1;
message_guid_copy(&msgid->guid, guid);
l->count++;
l->toupload++;
if (l->tail)
l->tail = l->tail->next = msgid;
else
l->head = l->tail = msgid;
/* Insert at start of list */
msgid->hash_next = l->hash[offset];
l->hash[offset] = msgid;
return msgid;
}
void sync_msgid_remove(struct sync_msgid_list *l,
const struct message_guid *guid)
{
int offset = message_guid_hash(guid, l->hash_size);
struct sync_msgid *msgid;
if (message_guid_isnull(guid)) return;
for (msgid = l->hash[offset] ; msgid ; msgid = msgid->hash_next) {
if (message_guid_equal(&msgid->guid, guid)) {
message_guid_set_null(&msgid->guid);
return;
}
}
}
void sync_msgid_list_free(struct sync_msgid_list **lp)
{
struct sync_msgid_list *l = *lp;
struct sync_msgid *current, *next;
current = l->head;
while (current) {
next = current->next;
free(current->fname);
free(current);
current = next;
}
free(l->hash);
free(l);
*lp = NULL;
}
struct sync_msgid *sync_msgid_lookup(const struct sync_msgid_list *l,
const struct message_guid *guid)
{
int offset = message_guid_hash(guid, l->hash_size);
struct sync_msgid *msgid;
if (message_guid_isnull(guid))
return(NULL);
for (msgid = l->hash[offset] ; msgid ; msgid = msgid->hash_next) {
if (message_guid_equal(&msgid->guid, guid))
return(msgid);
}
return(NULL);
}
struct sync_reserve_list *sync_reserve_list_create(int hash_size)
{
struct sync_reserve_list *l = xmalloc(sizeof(struct sync_reserve_list));
l->head = NULL;
l->tail = NULL;
l->hash_size = hash_size;
return l;
}
struct sync_msgid_list *sync_reserve_partlist(struct sync_reserve_list *l,
const char *part)
{
struct sync_reserve *item;
for (item = l->head; item; item = item->next) {
if (!strcmp(item->part, part))
return item->list;
}
/* not found, create it */
item = xmalloc(sizeof(struct sync_reserve));
item->part = xstrdup(part);
item->next = NULL;
item->list = sync_msgid_list_create(l->hash_size);
if (l->tail)
l->tail = l->tail->next = item;
else
l->tail = l->head = item;
return item->list;
}
void sync_reserve_list_free(struct sync_reserve_list **lp)
{
struct sync_reserve_list *l = *lp;
struct sync_reserve *current, *next;
current = l->head;
while (current) {
next = current->next;
sync_msgid_list_free(&current->list);
free(current->part);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
/* ====================================================================== */
struct sync_folder_list *sync_folder_list_create(void)
{
struct sync_folder_list *l = xzmalloc(sizeof (struct sync_folder_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return(l);
}
struct sync_folder *sync_folder_list_add(struct sync_folder_list *l,
const char *uniqueid, const char *name,
uint32_t mbtype,
const char *part, const char *acl,
uint32_t options,
uint32_t uidvalidity,
uint32_t last_uid,
modseq_t highestmodseq,
struct synccrcs synccrcs,
uint32_t recentuid,
time_t recenttime,
time_t pop3_last_login,
time_t pop3_show_after,
struct sync_annot_list *annots,
modseq_t xconvmodseq,
modseq_t raclmodseq,
modseq_t foldermodseq,
int ispartial)
{
struct sync_folder *result = xzmalloc(sizeof(struct sync_folder));
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
l->count++;
result->next = NULL;
result->mailbox = NULL;
result->uniqueid = xstrdupnull(uniqueid);
result->name = xstrdupnull(name);
result->mbtype = mbtype;
result->part = xstrdupnull(part);
result->acl = xstrdupnull(acl);
result->uidvalidity = uidvalidity;
result->last_uid = last_uid;
result->highestmodseq = highestmodseq;
result->options = options;
result->synccrcs = synccrcs;
result->recentuid = recentuid;
result->recenttime = recenttime;
result->pop3_last_login = pop3_last_login;
result->pop3_show_after = pop3_show_after;
result->annots = annots; /* NOTE: not a copy! */
result->xconvmodseq = xconvmodseq;
result->raclmodseq = raclmodseq;
result->foldermodseq = foldermodseq;
result->ispartial = ispartial;
result->mark = 0;
result->reserve = 0;
return(result);
}
struct sync_folder *sync_folder_lookup(struct sync_folder_list *l,
const char *uniqueid)
{
struct sync_folder *p;
for (p = l->head; p; p = p->next) {
if (!strcmp(p->uniqueid, uniqueid))
return p;
}
return NULL;
}
void sync_folder_list_free(struct sync_folder_list **lp)
{
struct sync_folder_list *l = *lp;
struct sync_folder *current, *next;
if (!l) return;
current = l->head;
while (current) {
next = current->next;
free(current->uniqueid);
free(current->name);
free(current->part);
free(current->acl);
sync_annot_list_free(&current->annots);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
/* ====================================================================== */
struct sync_rename_list *sync_rename_list_create(void)
{
struct sync_rename_list *l = xzmalloc(sizeof(struct sync_rename_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
l->done = 0;
return(l);
}
struct sync_rename *sync_rename_list_add(struct sync_rename_list *l,
const char *uniqueid, const char *oldname,
const char *newname, const char *partition,
unsigned uidvalidity)
{
struct sync_rename *result
= xzmalloc(sizeof(struct sync_rename));
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
l->count++;
result->next = NULL;
result->uniqueid = xstrdupnull(uniqueid);
result->oldname = xstrdupnull(oldname);
result->newname = xstrdupnull(newname);
result->part = xstrdupnull(partition);
result->uidvalidity = uidvalidity;
result->done = 0;
return result;
}
struct sync_rename *sync_rename_lookup(struct sync_rename_list *l,
const char *oldname)
{
struct sync_rename *p;
for (p = l->head; p; p = p->next) {
if (!strcmp(p->oldname, oldname))
return p;
}
return NULL;
}
void sync_rename_list_free(struct sync_rename_list **lp)
{
struct sync_rename_list *l = *lp;
struct sync_rename *current, *next;
if (!l) return;
current = l->head;
while (current) {
next = current->next;
free(current->uniqueid);
free(current->oldname);
free(current->newname);
free(current->part);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
/* ====================================================================== */
struct sync_quota_list *sync_quota_list_create(void)
{
struct sync_quota_list *l = xzmalloc(sizeof(struct sync_quota_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
l->done = 0;
return(l);
}
struct sync_quota *sync_quota_list_add(struct sync_quota_list *l,
const char *root)
{
struct sync_quota *result
= xzmalloc(sizeof(struct sync_quota));
int res;
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
l->count++;
result->next = NULL;
result->root = xstrdup(root);
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++)
result->limits[res] = QUOTA_UNLIMITED;
result->done = 0;
return result;
}
struct sync_quota *sync_quota_lookup(struct sync_quota_list *l,
const char *name)
{
struct sync_quota *p;
for (p = l->head; p; p = p->next) {
if (!strcmp(p->root, name))
return p;
}
return NULL;
}
void sync_quota_list_free(struct sync_quota_list **lp)
{
struct sync_quota_list *l = *lp;
struct sync_quota *current, *next;
if (!l) return;
current = l->head;
while (current) {
next = current->next;
free(current->root);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
void sync_encode_quota_limits(struct dlist *kl, const quota_t limits[QUOTA_NUMRESOURCES])
{
int res;
/*
* For backwards compatibility, we encode the STORAGE limit as LIMIT
* and we always report it even if it's QUOTA_UNLIMITED. This is
* kinda screwed up but should work. For QUOTA_UNLIMITED < 0, we
* send a very large unsigned number across the wire, and parse it
* back as QUOTA_UNLIMITED at the other end. Spit and string.
*/
dlist_setnum32(kl, "LIMIT", limits[QUOTA_STORAGE]);
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
if (limits[res] >= 0)
dlist_setnum32(kl, quota_names[res], limits[res]);
}
}
void sync_decode_quota_limits(/*const*/ struct dlist *kl, quota_t limits[QUOTA_NUMRESOURCES])
{
uint32_t limit = 0;
int res;
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++)
limits[res] = QUOTA_UNLIMITED;
/* For backwards compatibility */
if (dlist_getnum32(kl, "LIMIT", &limit)) {
if (limit == UINT_MAX)
limits[QUOTA_STORAGE] = -1;
else
limits[QUOTA_STORAGE] = limit;
}
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
if (dlist_getnum32(kl, quota_names[res], &limit))
limits[res] = limit;
}
}
/* ====================================================================== */
struct sync_sieve_list *sync_sieve_list_create(void)
{
struct sync_sieve_list *l = xzmalloc(sizeof (struct sync_sieve_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return l;
}
static struct sync_sieve *sync_sieve_list_add(
struct sync_sieve_list *l, const char *name,
time_t last_update, struct message_guid *guidp,
int active)
{
struct sync_sieve *item = xzmalloc(sizeof(struct sync_sieve));
item->name = xstrdup(name);
item->last_update = last_update;
item->active = active;
message_guid_copy(&item->guid, guidp);
item->mark = 0;
if (l->tail)
l->tail = l->tail->next = item;
else
l->head = l->tail = item;
l->count++;
return item;
}
struct sync_sieve *sync_sieve_lookup(struct sync_sieve_list *l, const char *name)
{
struct sync_sieve *p;
for (p = l->head; p; p = p->next) {
if (!strcmp(p->name, name))
return p;
}
return NULL;
}
void sync_sieve_list_free(struct sync_sieve_list **lp)
{
struct sync_sieve_list *l = *lp;
struct sync_sieve *current, *next;
current = l->head;
while (current) {
next = current->next;
free(current->name);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
static int list_cb(const char *sievedir,
const char *name, struct stat *sbuf,
const char *link_target __attribute__((unused)),
void *rock)
{
struct sync_sieve_list *list = (struct sync_sieve_list *) rock;
/* calculate the sha1 on the fly, relatively cheap */
struct buf *buf = sievedir_get_script(sievedir, name);
if (buf && buf_len(buf)) {
struct message_guid guid;
message_guid_generate(&guid, buf_base(buf), buf_len(buf));
sync_sieve_list_add(list, name, sbuf->st_mtime, &guid, 0);
buf_destroy(buf);
}
return SIEVEDIR_OK;
}
struct sync_sieve_list *sync_sieve_list_generate(const char *userid)
{
struct sync_sieve_list *list = sync_sieve_list_create();
const char *sieve_path = user_sieve_path(userid);
const char *active = sievedir_get_active(sieve_path);
sievedir_foreach(sieve_path, SIEVEDIR_SCRIPTS_ONLY, &list_cb, list);
if (active) {
char target[SIEVEDIR_MAX_NAME_LEN];
struct message_guid guid;
message_guid_set_null(&guid);
snprintf(target, sizeof(target), "%s%s", active, BYTECODE_SUFFIX);
sync_sieve_list_add(list, target, 0, &guid, 1);
}
return list;
}
char *sync_sieve_read(const char *userid, const char *name, uint32_t *sizep)
{
const char *sieve_path = user_sieve_path(userid);
struct buf *buf = sievedir_get_script(sieve_path, name);
char *result = NULL;
if (buf) {
if (sizep) *sizep = buf_len(buf);
result = buf_release(buf);
buf_destroy(buf);
}
else if (sizep)
*sizep = 0;
return result;
}
int sync_sieve_upload(const char *userid, const char *name,
time_t last_update, const char *content,
size_t len)
{
const char *sieve_path = user_sieve_path(userid);
char tmpname[2048];
char newname[2048];
char *ext;
FILE *file;
int r = 0;
struct stat sbuf;
struct utimbuf utimbuf;
ext = strrchr(name, '.');
if (ext && !strcmp(ext, ".bc")) {
/* silently ignore attempts to upload compiled bytecode */
return 0;
}
if (stat(sieve_path, &sbuf) == -1 && errno == ENOENT) {
if (cyrus_mkdir(sieve_path, 0755) == -1) return IMAP_IOERROR;
if (mkdir(sieve_path, 0755) == -1 && errno != EEXIST) {
syslog(LOG_ERR, "Failed to create %s:%m", sieve_path);
return IMAP_IOERROR;
}
}
snprintf(tmpname, sizeof(tmpname), "%s/sync_tmp-%lu",
sieve_path, (unsigned long)getpid());
snprintf(newname, sizeof(newname), "%s/%s", sieve_path, name);
if ((file=fopen(tmpname, "w")) == NULL) {
return IMAP_IOERROR;
}
/* XXX - error handling */
fwrite(content, 1, len, file);
if ((fflush(file) != 0) || (fsync(fileno(file)) < 0))
r = IMAP_IOERROR;
fclose(file);
utimbuf.actime = time(NULL);
utimbuf.modtime = last_update;
if (!r && (utime(tmpname, &utimbuf) < 0))
r = IMAP_IOERROR;
if (!r && (rename(tmpname, newname) < 0))
r = IMAP_IOERROR;
#ifdef USE_SIEVE
if (!r) {
r = sieve_rebuild(newname, NULL, /*force*/ 1, NULL);
if (r == SIEVE_PARSE_ERROR || r == SIEVE_FAIL)
r = IMAP_SYNC_BADSIEVE;
}
#endif
sync_log_sieve(userid);
return r;
}
int sync_sieve_activate(const char *userid, const char *bcname)
{
const char *sieve_path = user_sieve_path(userid);
char target[2048];
int r;
#ifdef USE_SIEVE
snprintf(target, sizeof(target), "%s/%s", sieve_path, bcname);
sieve_rebuild(NULL, target, 0, NULL);
#endif
snprintf(target, sizeof(target), "%.*s",
(int) strlen(bcname) - BYTECODE_SUFFIX_LEN, bcname);
r = sievedir_activate_script(sieve_path, target);
if (r) return r;
sync_log_sieve(userid);
return 0;
}
int sync_sieve_deactivate(const char *userid)
{
const char *sieve_path = user_sieve_path(userid);
int r = sievedir_deactivate_script(sieve_path);
if (r) return r;
sync_log_sieve(userid);
return(0);
}
int sync_sieve_delete(const char *userid, const char *script)
{
const char *sieve_path = user_sieve_path(userid);
char name[2048];
snprintf(name, sizeof(name), "%.*s",
(int) strlen(script) - SCRIPT_SUFFIX_LEN, script);
/* XXX Do we NOT care about errors? */
if (sievedir_script_isactive(sieve_path, name)) {
sievedir_deactivate_script(sieve_path);
}
sievedir_delete_script(sieve_path, name);
sync_log_sieve(userid);
return(0);
}
/* ====================================================================== */
struct sync_name_list *sync_name_list_create(void)
{
struct sync_name_list *l = xzmalloc(sizeof (struct sync_name_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
l->marked = 0;
return l;
}
struct sync_name *sync_name_list_add(struct sync_name_list *l,
const char *name)
{
struct sync_name * item = sync_name_lookup(l, name);
if (item) return item;
item = xzmalloc(sizeof(struct sync_name));
if (l->tail)
l->tail = l->tail->next = item;
else
l->head = l->tail = item;
l->count++;
item->next = NULL;
item->name = xstrdup(name);
item->mark = 0;
return item;
}
struct sync_name *sync_name_lookup(struct sync_name_list *l,
const char *name)
{
struct sync_name *p;
for (p = l->head; p; p = p->next)
if (!strcmp(p->name, name))
return p;
return NULL;
}
void sync_name_list_free(struct sync_name_list **lp)
{
struct sync_name *current, *next;
current = (*lp)->head;
while (current) {
next = current->next;
free(current->name);
free(current);
current = next;
}
free(*lp);
*lp = NULL;
}
/* ====================================================================== */
struct sync_seen_list *sync_seen_list_create(void)
{
struct sync_seen_list *l = xzmalloc(sizeof (struct sync_seen_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return l;
}
struct sync_seen *sync_seen_list_add(struct sync_seen_list *l,
const char *uniqueid, time_t lastread,
unsigned lastuid, time_t lastchange,
const char *seenuids)
{
struct sync_seen *item = xzmalloc(sizeof(struct sync_seen));
if (l->tail)
l->tail = l->tail->next = item;
else
l->head = l->tail = item;
l->count++;
item->next = NULL;
item->uniqueid = xstrdup(uniqueid);
item->sd.lastread = lastread;
item->sd.lastuid = lastuid;
item->sd.lastchange = lastchange;
item->sd.seenuids = xstrdup(seenuids);
item->mark = 0;
return item;
}
struct sync_seen *sync_seen_list_lookup(struct sync_seen_list *l,
const char *uniqueid)
{
struct sync_seen *p;
for (p = l->head; p; p = p->next)
if (!strcmp(p->uniqueid, uniqueid))
return p;
return NULL;
}
void sync_seen_list_free(struct sync_seen_list **lp)
{
struct sync_seen *current, *next;
current = (*lp)->head;
while (current) {
next = current->next;
free(current->uniqueid);
seen_freedata(&current->sd);
free(current);
current = next;
}
free(*lp);
*lp = NULL;
}
/* ====================================================================== */
struct sync_annot_list *sync_annot_list_create(void)
{
struct sync_annot_list *l = xzmalloc(sizeof (struct sync_annot_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return(l);
}
void sync_annot_list_add(struct sync_annot_list *l,
const char *entry, const char *userid,
const struct buf *value,
modseq_t modseq)
{
struct sync_annot *item = xzmalloc(sizeof(struct sync_annot));
item->entry = xstrdupnull(entry);
item->userid = xstrdupnull(userid);
buf_copy(&item->value, value);
item->mark = 0;
item->modseq = modseq;
if (l->tail)
l->tail = l->tail->next = item;
else
l->head = l->tail = item;
l->count++;
}
void sync_annot_list_free(struct sync_annot_list **lp)
{
struct sync_annot_list *l = *lp;
struct sync_annot *current, *next;
if (!l)
return;
current = l->head;
while (current) {
next = current->next;
free(current->entry);
free(current->userid);
buf_free(&current->value);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
/* ====================================================================== */
struct sync_action_list *sync_action_list_create(void)
{
struct sync_action_list *l = xzmalloc(sizeof (struct sync_action_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return(l);
}
void sync_action_list_add(struct sync_action_list *l,
const char *name, const char *user)
{
struct sync_action *current;
if (!name && !user) return;
for (current = l->head ; current ; current = current->next) {
if ((!name || (current->name && !strcmp(current->name, name))) &&
(!user || (current->user && !strcmp(current->user, user)))) {
current->active = 1; /* Make sure active */
return;
} else {
/* name and/or user don't match current: no match possible */
}
}
current = xzmalloc(sizeof(struct sync_action));
current->next = NULL;
current->name = xstrdupnull(name);
current->user = xstrdupnull(user);
current->active = 1;
if (l->tail)
l->tail = l->tail->next = current;
else
l->head = l->tail = current;
l->count++;
}
void sync_action_list_free(struct sync_action_list **lp)
{
struct sync_action_list *l = *lp;
struct sync_action *current, *next;
current = l->head;
while (current) {
next = current->next;
if (current->name) free(current->name);
if (current->user) free(current->user);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
/* ====================================================================== */
static int read_one_annot(const char *mailbox __attribute__((unused)),
uint32_t uid __attribute__((unused)),
const char *entry,
const char *userid,
const struct buf *value,
const struct annotate_metadata *mdata,
void *rock)
{
struct sync_annot_list **salp = (struct sync_annot_list **)rock;
if (!*salp)
*salp = sync_annot_list_create();
sync_annot_list_add(*salp, entry, userid, value, mdata->modseq);
return 0;
}
/*
* Read all the annotations in the local annotations database
* for the message given by @mailbox and @record, returning them
* as a new sync_annot_list. The caller should free the new
* list with sync_annot_list_free().
* If record is NULL, return the mailbox annotations
* If since_modseq is greated than zero, return annotations
* add or changed since modseq (exclusively since_modseq).
* If flags is set to ANNOTATE_TOMBSTONES, also return
* deleted annotations. Deleted annotations have a zero value.
*
* Returns: non-zero on error,
* resulting sync_annot_list in *@resp
*/
int read_annotations(const struct mailbox *mailbox,
const struct index_record *record,
struct sync_annot_list **resp,
modseq_t since_modseq __attribute__((unused)),
int flags)
{
*resp = NULL;
return annotatemore_findall_mailbox(mailbox, record ? record->uid : 0,
/* all entries*/"*", /*XXX since_modseq*/0,
read_one_annot, (void *)resp, flags);
}
/*
* Encode the given list of annotations @sal as a dlist
* structure with the given @parent.
*/
void encode_annotations(struct dlist *parent,
struct mailbox *mailbox,
const struct index_record *record,
const struct sync_annot_list *sal)
{
const struct sync_annot *sa;
struct dlist *annots = NULL;
struct dlist *aa;
if (sal) {
for (sa = sal->head ; sa ; sa = sa->next) {
if (!annots)
annots = dlist_newlist(parent, "ANNOTATIONS");
aa = dlist_newkvlist(annots, NULL);
dlist_setatom(aa, "ENTRY", sa->entry);
dlist_setatom(aa, "USERID", sa->userid);
dlist_setnum64(aa, "MODSEQ", sa->modseq);
dlist_setmap(aa, "VALUE", sa->value.s, sa->value.len);
}
}
if (record && record->cid && mailbox->i.minor_version >= 13) {
if (!annots)
annots = dlist_newlist(parent, "ANNOTATIONS");
aa = dlist_newkvlist(annots, NULL);
dlist_setatom(aa, "ENTRY", IMAP_ANNOT_NS "thrid");
dlist_setatom(aa, "USERID", "");
dlist_setnum64(aa, "MODSEQ", 0);
dlist_sethex64(aa, "VALUE", record->cid);
}
if (record && record->savedate && mailbox->i.minor_version >= 15) {
if (!annots)
annots = dlist_newlist(parent, "ANNOTATIONS");
aa = dlist_newkvlist(annots, NULL);
dlist_setatom(aa, "ENTRY", IMAP_ANNOT_NS "savedate");
dlist_setatom(aa, "USERID", "");
dlist_setnum64(aa, "MODSEQ", 0);
dlist_setnum32(aa, "VALUE", record->savedate);
}
if (record && record->createdmodseq && mailbox->i.minor_version >= 16) {
if (!annots)
annots = dlist_newlist(parent, "ANNOTATIONS");
aa = dlist_newkvlist(annots, NULL);
dlist_setatom(aa, "ENTRY", IMAP_ANNOT_NS "createdmodseq");
dlist_setatom(aa, "USERID", "");
dlist_setnum64(aa, "MODSEQ", 0);
dlist_setnum64(aa, "VALUE", record->createdmodseq);
}
}
/*
* Decode the given list of encoded annotations @annots and create
* a new sync_annot_list in *@salp, which the caller should free
* with sync_annot_list_free().
*
* Returns: zero on success or Cyrus error code.
*/
int decode_annotations(/*const*/struct dlist *annots,
struct sync_annot_list **salp,
struct mailbox *mailbox,
struct index_record *record)
{
struct dlist *aa;
const char *entry;
const char *userid;
modseq_t modseq;
*salp = NULL;
if (strcmp(annots->name, "ANNOTATIONS"))
return IMAP_PROTOCOL_BAD_PARAMETERS;
for (aa = annots->head ; aa ; aa = aa->next) {
struct buf value = BUF_INITIALIZER;
if (!*salp)
*salp = sync_annot_list_create();
if (!dlist_getatom(aa, "ENTRY", &entry))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(aa, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum64(aa, "MODSEQ", &modseq))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getbuf(aa, "VALUE", &value))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!strcmp(entry, IMAP_ANNOT_NS "thrid") &&
record && mailbox->i.minor_version >= 13) {
const char *p = buf_cstring(&value);
parsehex(p, &p, 16, &record->cid);
}
else if (!strcmp(entry, IMAP_ANNOT_NS "savedate") &&
record && mailbox->i.minor_version >= 15) {
const char *p = buf_cstring(&value);
bit64 newval;
parsenum(p, &p, 0, &newval);
record->savedate = newval;
}
else if (!strcmp(entry, IMAP_ANNOT_NS "createdmodseq") &&
record && mailbox->i.minor_version >= 16) {
const char *p = buf_cstring(&value);
bit64 newval;
parsenum(p, &p, 0, &newval);
record->createdmodseq = newval;
}
else if (!strcmp(entry, IMAP_ANNOT_NS "basethrid") && record) {
/* this might double-apply the annotation, but oh well. It does mean that
* basethrid is paired in here when we do a comparison against new values
* from the replica later! */
const char *p = buf_cstring(&value);
parsehex(p, &p, 16, &record->basecid);
/* XXX - check on p? */
/* "basethrid" is special, since it is written during mailbox
* appends and rewrites, using whatever modseq the index_record
* has at this moment. This might differ from the modseq we
* just parsed here, causing master and replica annotations
* to get out of sync.
* The fix is to set the basecid field both on the index
* record *and* adding the annotation to the annotation list.
* That way the local modseq of basethrid always gets over-
* written by whoever wins to be master of this annotation */
sync_annot_list_add(*salp, entry, userid, &value, modseq);
}
else {
sync_annot_list_add(*salp, entry, userid, &value, modseq);
}
buf_free(&value);
}
return 0;
}
/*
* Merge a local and remote list of annotations, and apply the resulting
* list of annotations to the local annotation database, storing new values
* or deleting old values as necessary. Manages its own annotations
* transaction.
* Record may be null, to process mailbox annotations.
*/
static int diff_annotation(const struct sync_annot *a,
const struct sync_annot *b,
int diff_value)
{
int diff = 0;
if (!a && !b) return 0;
if (a)
diff--;
if (b)
diff++;
if (!diff)
diff = strcmpnull(a->entry, b->entry);
if (!diff)
diff = strcmpnull(a->userid, b->userid);
if (!diff && diff_value)
diff = buf_cmp(&a->value, &b->value);
return diff;
}
int diff_annotations(const struct sync_annot_list *local_annots,
const struct sync_annot_list *remote_annots)
{
const struct sync_annot *local = (local_annots ? local_annots->head : NULL);
const struct sync_annot *remote = (remote_annots ? remote_annots->head : NULL);
while (local || remote) {
int r = diff_annotation(local, remote, 1);
if (r) return r;
if (local) local = local->next;
if (remote) remote = remote->next;
}
return 0;
}
int apply_annotations(struct mailbox *mailbox,
const struct index_record *record,
const struct sync_annot_list *local_annots,
const struct sync_annot_list *remote_annots,
int local_wins, int *hadsnoozed)
{
const struct sync_annot *local = (local_annots ? local_annots->head : NULL);
const struct sync_annot *remote = (remote_annots ? remote_annots->head : NULL);
const struct sync_annot *chosen;
static const struct buf novalue = BUF_INITIALIZER;
const struct buf *value;
int r = 0;
int diff;
annotate_state_t *astate = NULL;
if (record) {
r = mailbox_get_annotate_state(mailbox, record->uid, &astate);
}
else {
astate = annotate_state_new();
r = annotate_state_set_mailbox(astate, mailbox);
}
if (r) goto out;
/*
* We rely here on the database scan order resulting in lists
* of annotations that are ordered lexically on entry then userid.
* We walk over both lists at once, choosing an annotation from
* either the local list only (diff < 0), the remote list only
* (diff > 0), or both lists (diff == 0).
*/
while (local || remote) {
diff = diff_annotation(local, remote, 0);
chosen = 0;
if (diff < 0) {
chosen = local;
value = (local_wins ? &local->value : &novalue);
if (hadsnoozed && !strcmpsafe(chosen->entry, IMAP_ANNOT_NS "snoozed") && buf_len(value))
*hadsnoozed = 1;
local = local->next;
}
else if (diff > 0) {
chosen = remote;
value = (local_wins ? &novalue : &remote->value);
if (hadsnoozed && !strcmpsafe(chosen->entry, IMAP_ANNOT_NS "snoozed") && buf_len(value))
*hadsnoozed = 1;
remote = remote->next;
}
else {
chosen = remote;
value = (local_wins ? &local->value : &remote->value);
if (hadsnoozed && !strcmpsafe(chosen->entry, IMAP_ANNOT_NS "snoozed") && buf_len(value))
*hadsnoozed = 1;
diff = buf_cmp(&local->value, &remote->value);
local = local->next;
remote = remote->next;
if (!diff)
continue; /* same value, skip */
}
/* Replicate the modseq of this record from master */
struct annotate_metadata mdata = {
chosen->modseq, /* modseq */
0 /* flags - is determined by value */
};
r = annotate_state_writemdata(astate, chosen->entry,
chosen->userid, value, &mdata);
if (r)
break;
}
out:
if (record) {
#ifdef USE_CALALARMD
if (mbtype_isa(mailbox_mbtype(mailbox)) == MBTYPE_CALENDAR) {
// NOTE: this is because we don't pass the annotations through
// with the record as we create it, so we can't update the alarm
// database properly. Instead, we don't set anything when we append
// by checking for .silentupdate, and instead update the database by touching
// the alarm AFTER writing the record.
caldav_alarm_sync_nextcheck(mailbox, record);
}
#endif
}
else {
/* need to manage our own txn for the global db */
if (!r)
r = annotate_state_commit(&astate);
else
annotate_state_abort(&astate);
}
/* else, the struct mailbox manages it for us */
return r;
}
/* =========================================================================== */
void sync_print_flags(struct dlist *kl,
struct mailbox *mailbox,
const struct index_record *record)
{
int flag;
struct dlist *fl = dlist_newlist(kl, "FLAGS");
if (record->system_flags & FLAG_DELETED)
dlist_setflag(fl, "FLAG", "\\Deleted");
if (record->system_flags & FLAG_ANSWERED)
dlist_setflag(fl, "FLAG", "\\Answered");
if (record->system_flags & FLAG_FLAGGED)
dlist_setflag(fl, "FLAG", "\\Flagged");
if (record->system_flags & FLAG_DRAFT)
dlist_setflag(fl, "FLAG", "\\Draft");
if (record->internal_flags & FLAG_INTERNAL_EXPUNGED)
dlist_setflag(fl, "FLAG", "\\Expunged");
if (record->system_flags & FLAG_SEEN)
dlist_setflag(fl, "FLAG", "\\Seen");
/* print user flags in mailbox order */
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if (!mailbox->flagname[flag])
continue;
if (!(record->user_flags[flag/32] & (1<<(flag&31))))
continue;
dlist_setflag(fl, "FLAG", mailbox->flagname[flag]);
}
}
int sync_getflags(struct dlist *kl,
struct mailbox *mailbox,
struct index_record *record)
{
struct dlist *ki;
int userflag;
for (ki = kl->head; ki; ki = ki->next) {
char *s = xstrdup(ki->sval);
if (s[0] == '\\') {
/* System flags */
lcase(s);
if (!strcmp(s, "\\seen")) {
record->system_flags |= FLAG_SEEN;
} else if (!strcmp(s, "\\expunged")) {
record->internal_flags |= FLAG_INTERNAL_EXPUNGED;
} else if (!strcmp(s, "\\answered")) {
record->system_flags |= FLAG_ANSWERED;
} else if (!strcmp(s, "\\flagged")) {
record->system_flags |= FLAG_FLAGGED;
} else if (!strcmp(s, "\\deleted")) {
record->system_flags |= FLAG_DELETED;
} else if (!strcmp(s, "\\draft")) {
record->system_flags |= FLAG_DRAFT;
} else {
syslog(LOG_ERR, "Unknown system flag: %s", s);
}
}
else {
if (mailbox_user_flag(mailbox, s, &userflag, /*allow all*/2)) {
syslog(LOG_ERR, "Unable to record user flag: %s", s);
free(s);
return IMAP_IOERROR;
}
record->user_flags[userflag/32] |= 1<<(userflag&31);
}
free(s);
}
return 0;
}
int parse_upload(struct dlist *kr, struct mailbox *mailbox,
struct index_record *record,
struct sync_annot_list **salp)
{
struct dlist *fl;
struct message_guid *tmpguid;
int r;
memset(record, 0, sizeof(struct index_record));
if (!dlist_getnum32(kr, "UID", &record->uid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum64(kr, "MODSEQ", &record->modseq))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kr, "LAST_UPDATED", &record->last_updated))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getlist(kr, "FLAGS", &fl))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kr, "INTERNALDATE", &record->internaldate))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kr, "SIZE", &record->size))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getguid(kr, "GUID", &tmpguid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
record->guid = *tmpguid;
/* parse the flags */
r = sync_getflags(fl, mailbox, record);
if (r) return r;
/* the ANNOTATIONS list is optional too */
if (salp && dlist_getlist(kr, "ANNOTATIONS", &fl))
r = decode_annotations(fl, salp, mailbox, record);
return r;
}
/* NOTE - we don't prot_flush here, as we always send an OK at the
* end of a response anyway */
void sync_send_response(struct dlist *kl, struct protstream *out)
{
prot_printf(out, "* ");
dlist_print(kl, 1, out);
prot_printf(out, "\r\n");
}
static const char *sync_gentag(struct buf *tag)
{
static unsigned cmdcnt = 0;
buf_reset(tag);
buf_printf(tag, "S%d", cmdcnt++);
return buf_cstring(tag);
}
/* these are one-shot commands for get and apply, so flush the stream
* after sending */
void sync_send_apply(struct dlist *kl, struct protstream *out)
{
if (out->userdata) {
/* IMAP flavor (w/ tag) */
prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
}
prot_printf(out, "APPLY ");
dlist_print(kl, 1, out);
prot_printf(out, "\r\n");
prot_flush(out);
}
void sync_send_lookup(struct dlist *kl, struct protstream *out)
{
if (out->userdata) {
/* IMAP flavor (w/ tag) */
prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
}
prot_printf(out, "GET ");
dlist_print(kl, 1, out);
prot_printf(out, "\r\n");
prot_flush(out);
}
void sync_send_restart(struct protstream *out)
{
if (out->userdata) {
/* IMAP flavor (w/ tag) */
prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
}
prot_printf(out, "RESTART\r\n");
prot_flush(out);
}
void sync_send_restore(struct dlist *kl, struct protstream *out)
{
if (out->userdata) {
/* IMAP flavor (w/ tag) */
prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
}
prot_printf(out, "RESTORE ");
dlist_print(kl, 1, out);
prot_printf(out, "\r\n");
prot_flush(out);
}
/*
* Copied from imapparse.c:eatline(), and extended to also eat
* dlist file literals
* XXX potentially dedup back into original eatline
*/
void sync_eatline(struct protstream *pin, int c)
{
for (;;) {
if (c == '\n') return;
/* Several of the parser helper functions return EOF
even if an unexpected character (other than EOF) is received.
We need to confirm that the stream is actually at EOF. */
if (c == EOF && (prot_IS_EOF(pin) || prot_IS_ERROR(pin))) return;
/* see if it's a literal */
if (c == '{') {
c = prot_getc(pin);
uint64_t size = 0;
while (cyrus_isdigit(c)) {
if (size > 429496729 || (size == 429496729 && (c > '5')))
break; /* don't fatal, just drop out of literal parsing */
size = size * 10 + c - '0';
c = prot_getc(pin);
}
if (c != '+') continue;
c = prot_getc(pin);
if (c != '}') continue;
c = prot_getc(pin);
/* optional \r */
if (c == '\r') c = prot_getc(pin);
if (c != '\n') continue;
/* successful literal, consume it */
while (size--) {
c = prot_getc(pin);
if (c == EOF) return;
}
}
else if (c == '%') {
/* replication file literal */
static struct buf discard = BUF_INITIALIZER;
uint32_t size = 0;
c = prot_getc(pin);
if (c != '{') continue;
c = getastring(pin, NULL, &discard); /* partition */
if (c != ' ') continue;
c = getastring(pin, NULL, &discard); /* guid */
if (c != ' ') continue;
c = getuint32(pin, &size);
if (c != '}') continue;
c = prot_getc(pin);
/* optional \r */
if (c == '\r') c = prot_getc(pin);
if (c != '\n') continue;
/* successful file literal, consume it */
while (size--) {
c = prot_getc(pin);
if (c == EOF) return;
}
}
c = prot_getc(pin);
}
}
struct dlist *sync_parseline(struct protstream *in)
{
struct dlist *dl = NULL;
int c;
c = dlist_parse(&dl, 1, 0, in);
/* end line - or fail */
if (c == '\r') c = prot_getc(in);
if (c == '\n') return dl;
dlist_free(&dl);
sync_eatline(in, c);
return NULL;
}
static int sync_send_file(struct mailbox *mailbox,
const char *topart,
const struct index_record *record,
struct sync_msgid_list *part_list,
struct dlist *kupload)
{
struct sync_msgid *msgid;
const char *fname;
/* we'll trust that it exists - if not, we'll bail later,
* but right now we're under locks, so be fast */
fname = mailbox_record_fname(mailbox, record);
if (!fname) return IMAP_MAILBOX_BADNAME;
msgid = sync_msgid_insert(part_list, &record->guid);
/* already uploaded, great */
if (!msgid->need_upload)
return 0;
dlist_setfile(kupload, "MESSAGE", topart, &record->guid, record->size, fname);
/* note that we will be sending it, so it doesn't need to be
* sent again */
msgid->size = record->size;
if (!msgid->fname) msgid->fname = xstrdup(fname);
msgid->need_upload = 0;
msgid->is_archive = (record->internal_flags & FLAG_INTERNAL_ARCHIVED) ? 1 : 0;
part_list->toupload--;
return 0;
}
static int sync_prepare_dlists(struct mailbox *mailbox,
struct sync_folder *local,
struct sync_folder *remote,
const char *topart,
struct sync_msgid_list *part_list,
struct dlist *kl, struct dlist *kupload,
int printrecords, int fullannots, int sendsince)
{
struct sync_annot_list *annots = NULL;
struct mailbox_iter *iter = NULL;
modseq_t xconvmodseq = 0;
int r = 0;
int ispartial = local ? local->ispartial : 0;
if (!topart) topart = mailbox_partition(mailbox);
dlist_setatom(kl, "UNIQUEID", mailbox_uniqueid(mailbox));
dlist_setatom(kl, "MBOXNAME", mailbox_name(mailbox));
if (mbtypes_sync(mailbox_mbtype(mailbox)))
dlist_setatom(kl, "MBOXTYPE", mboxlist_mbtype_to_string(mbtypes_sync(mailbox_mbtype(mailbox))));
if (ispartial) {
/* send a zero to make older Cyrus happy */
dlist_setnum32(kl, "SYNC_CRC", 0);
/* calculated partial values */
dlist_setnum32(kl, "LAST_UID", local->last_uid);
dlist_setnum64(kl, "HIGHESTMODSEQ", local->highestmodseq);
/* create synthetic values for the other fields */
dlist_setnum32(kl, "RECENTUID", remote ? remote->recentuid : 0);
dlist_setdate(kl, "RECENTTIME", remote ? remote->recenttime : 0);
dlist_setdate(kl, "LAST_APPENDDATE", 0);
dlist_setdate(kl, "POP3_LAST_LOGIN", remote ? remote->pop3_last_login : 0);
dlist_setdate(kl, "POP3_SHOW_AFTER", remote ? remote->pop3_show_after : 0);
if (remote && remote->xconvmodseq)
dlist_setnum64(kl, "XCONVMODSEQ", remote->xconvmodseq);
if (remote && remote->raclmodseq)
dlist_setnum64(kl, "RACLMODSEQ", remote->raclmodseq);
}
else {
struct synccrcs synccrcs = mailbox_synccrcs(mailbox, /*force*/0);
dlist_setnum32(kl, "SYNC_CRC", synccrcs.basic);
dlist_setnum32(kl, "SYNC_CRC_ANNOT", synccrcs.annot);
dlist_setnum32(kl, "LAST_UID", mailbox->i.last_uid);
dlist_setnum64(kl, "HIGHESTMODSEQ", mailbox->i.highestmodseq);
dlist_setnum32(kl, "RECENTUID", mailbox->i.recentuid);
dlist_setdate(kl, "RECENTTIME", mailbox->i.recenttime);
dlist_setdate(kl, "LAST_APPENDDATE", mailbox->i.last_appenddate);
dlist_setdate(kl, "POP3_LAST_LOGIN", mailbox->i.pop3_last_login);
dlist_setdate(kl, "POP3_SHOW_AFTER", mailbox->i.pop3_show_after);
if (mailbox_has_conversations(mailbox)) {
r = mailbox_get_xconvmodseq(mailbox, &xconvmodseq);
if (!r && xconvmodseq)
dlist_setnum64(kl, "XCONVMODSEQ", xconvmodseq);
}
modseq_t raclmodseq = mboxname_readraclmodseq(mailbox_name(mailbox));
if (raclmodseq)
dlist_setnum64(kl, "RACLMODSEQ", raclmodseq);
}
dlist_setnum32(kl, "UIDVALIDITY", mailbox->i.uidvalidity);
dlist_setatom(kl, "PARTITION", topart);
dlist_setatom(kl, "ACL", mailbox_acl(mailbox));
dlist_setatom(kl, "OPTIONS", sync_encode_options(mailbox->i.options));
if (mailbox->quotaroot)
dlist_setatom(kl, "QUOTAROOT", mailbox->quotaroot);
if (mailbox->i.createdmodseq)
dlist_setnum64(kl, "CREATEDMODSEQ", mailbox->i.createdmodseq);
if (mailbox_foldermodseq(mailbox))
dlist_setnum64(kl, "FOLDERMODSEQ", mailbox_foldermodseq(mailbox));
/* always send mailbox annotations */
r = read_annotations(mailbox, NULL, &annots, 0, 0);
if (r) goto done;
encode_annotations(kl, mailbox, NULL, annots);
sync_annot_list_free(&annots);
if (sendsince && remote && remote->highestmodseq) {
dlist_setnum64(kl, "SINCE_MODSEQ", remote->highestmodseq);
dlist_setnum32(kl, "SINCE_CRC", remote->synccrcs.basic);
dlist_setnum32(kl, "SINCE_CRC_ANNOT", remote->synccrcs.annot);
}
if (printrecords) {
const message_t *msg;
struct dlist *rl = dlist_newlist(kl, "RECORD");
modseq_t modseq = remote ? remote->highestmodseq : 0;
iter = mailbox_iter_init(mailbox, modseq, 0);
while ((msg = mailbox_iter_step(iter))) {
const struct index_record *record = msg_record(msg);
modseq_t since_modseq = fullannots ? 0 : modseq;
/* stop early for partial sync */
modseq_t mymodseq = record->modseq;
if (ispartial) {
if (record->uid > local->last_uid)
break;
/* something from past the modseq that we're sending now */
if (mymodseq > local->highestmodseq) {
/* we will send this one later */
if (remote && record->uid <= remote->last_uid)
continue;
/* falsify modseq for now, we will resync this message later */
mymodseq = local->highestmodseq;
}
}
/* start off thinking we're sending the file too */
int send_file = 1;
/* does it exist at the other end? Don't send it */
if (remote && record->uid <= remote->last_uid)
send_file = 0;
/* if we're not uploading messages... don't send file */
if (!part_list || !kupload)
send_file = 0;
/* if we don't HAVE the file we can't send it */
if (record->internal_flags & FLAG_INTERNAL_UNLINKED)
send_file = 0;
if (send_file) {
r = sync_send_file(mailbox, topart,
record, part_list, kupload);
if (r) goto done;
}
struct dlist *il = dlist_newkvlist(rl, "RECORD");
dlist_setnum32(il, "UID", record->uid);
dlist_setnum64(il, "MODSEQ", mymodseq);
dlist_setdate(il, "LAST_UPDATED", record->last_updated);
sync_print_flags(il, mailbox, record);
dlist_setdate(il, "INTERNALDATE", record->internaldate);
dlist_setnum32(il, "SIZE", record->size);
dlist_setatom(il, "GUID", message_guid_encode(&record->guid));
r = read_annotations(mailbox, record, &annots, since_modseq,
/*XXX ANNOTATE_TOMBSTONES*/0);
if (r) goto done;
encode_annotations(il, mailbox, record, annots);
sync_annot_list_free(&annots);
}
}
done:
mailbox_iter_done(&iter);
return r;
}
int sync_parse_response(const char *cmd, struct protstream *in,
struct dlist **klp)
{
static struct buf response; /* BSS */
static struct buf errmsg;
struct dlist *kl = NULL;
int c;
if ((c = getword(in, &response)) == EOF) {
xsyslog(LOG_ERR, "IOERROR: zero length response",
"command=<%s> prot_error=<%s>",
cmd, prot_error(in));
return IMAP_PROTOCOL_ERROR;
}
if (c != ' ') goto parse_err;
kl = dlist_newlist(NULL, cmd);
while (!strcmp(response.s, "*")) {
struct dlist *item = sync_parseline(in);
if (!item) goto parse_err;
dlist_stitch(kl, item);
if ((c = getword(in, &response)) == EOF)
goto parse_err;
}
if (in->userdata) {
/* check IMAP response tag */
if (strcmp(response.s, buf_cstring((struct buf *) in->userdata)))
goto parse_err;
/* first word was IMAP response tag - get response token */
if ((c = getword(in, &response)) == EOF)
return IMAP_PROTOCOL_ERROR;
if (c != ' ') goto parse_err;
}
if (!strcmp(response.s, "OK")) {
if (klp) *klp = kl;
else dlist_free(&kl);
eatline(in, c);
return 0;
}
if (!strcmp(response.s, "BYE")) {
/* server is shutting down, don't be surprised by it */
syslog(LOG_DEBUG, "received BYE: replica was shut down");
dlist_free(&kl);
eatline(in, c);
return IMAP_BYE_LOGOUT;
}
if (!strcmp(response.s, "NO")) {
dlist_free(&kl);
sync_getline(in, &errmsg);
syslog(LOG_ERR, "%s received NO response: %s", cmd, errmsg.s);
/* Slight hack to transform certain error strings into equivalent
* imap_err value so that caller has some idea of cause. Match
* this to the logic at sync_response() */
if (!strncmp(errmsg.s, "IMAP_INVALID_USER ",
strlen("IMAP_INVALID_USER ")))
return IMAP_INVALID_USER;
else if (!strncmp(errmsg.s, "IMAP_MAILBOX_NONEXISTENT ",
strlen("IMAP_MAILBOX_NONEXISTENT ")))
return IMAP_MAILBOX_NONEXISTENT;
else if (!strncmp(errmsg.s, "IMAP_MAILBOX_LOCKED ",
strlen("IMAP_MAILBOX_LOCKED ")))
return IMAP_MAILBOX_LOCKED;
else if (!strncmp(errmsg.s, "IMAP_MAILBOX_MOVED ",
strlen("IMAP_MAILBOX_MOVED ")))
return IMAP_MAILBOX_MOVED;
else if (!strncmp(errmsg.s, "IMAP_MAILBOX_NOTSUPPORTED ",
strlen("IMAP_MAILBOX_NOTSUPPORTED ")))
return IMAP_MAILBOX_NOTSUPPORTED;
else if (!strncmp(errmsg.s, "IMAP_SYNC_CHECKSUM ",
strlen("IMAP_SYNC_CHECKSUM ")))
return IMAP_SYNC_CHECKSUM;
else if (!strncmp(errmsg.s, "IMAP_SYNC_CHANGED ",
strlen("IMAP_SYNC_CHANGED ")))
return IMAP_SYNC_CHANGED;
else if (!strncmp(errmsg.s, "IMAP_SYNC_BADSIEVE ",
strlen("IMAP_SYNC_BADSIEVE ")))
return IMAP_SYNC_BADSIEVE;
else if (!strncmp(errmsg.s, "IMAP_PROTOCOL_ERROR ",
strlen("IMAP_PROTOCOL_ERROR ")))
return IMAP_PROTOCOL_ERROR;
else if (!strncmp(errmsg.s, "IMAP_PROTOCOL_BAD_PARAMETERS ",
strlen("IMAP_PROTOCOL_BAD_PARAMETERS ")))
return IMAP_PROTOCOL_BAD_PARAMETERS;
else
return IMAP_REMOTE_DENIED;
}
parse_err:
dlist_free(&kl);
sync_getline(in, &errmsg);
xsyslog(LOG_ERR, "IOERROR: received bad response",
"command=<%s> response=<%s> errmsg=<%s>",
cmd, response.s, errmsg.s);
return IMAP_PROTOCOL_ERROR;
}
int sync_append_copyfile(struct mailbox *mailbox,
struct index_record *record,
const struct sync_annot_list *annots,
const struct sync_msgid_list *part_list)
{
const char *destname;
struct message_guid tmp_guid;
struct sync_msgid *item;
int r = 0;
message_guid_copy(&tmp_guid, &record->guid);
item = sync_msgid_lookup(part_list, &record->guid);
if (!item || !item->fname)
r = IMAP_IOERROR;
else
r = message_parse(item->fname, record);
if (r) {
/* deal with unlinked master records */
if (record->internal_flags & FLAG_INTERNAL_EXPUNGED) {
/* no need to set 'needs cleanup' here, it's already expunged */
record->internal_flags |= FLAG_INTERNAL_UNLINKED;
goto just_write;
}
xsyslog(LOG_ERR, "IOERROR: parse failed",
"guid=<%s> error=<%s>",
message_guid_encode(&record->guid),
error_message(r));
return r;
}
/* record->guid was rewritten in the parse, see if it changed */
if (!message_guid_equal(&tmp_guid, &record->guid)) {
xsyslog(LOG_ERR, "IOERROR: guid mismatch on parse",
"filename=<%s> guid=<%s>",
item->fname, message_guid_encode(&record->guid));
return IMAP_IOERROR;
}
/* put back to archive if original was archived, gain single instance store */
if (item->is_archive)
record->internal_flags |= FLAG_INTERNAL_ARCHIVED;
/* push it to archive if it should be archived now anyway */
if (mailbox_should_archive(mailbox, record, NULL))
record->internal_flags |= FLAG_INTERNAL_ARCHIVED;
destname = mailbox_record_fname(mailbox, record);
cyrus_mkdir(destname, 0755);
r = mailbox_copyfile(item->fname, destname, 0);
if (r) {
xsyslog(LOG_ERR, "IOERROR: copy file failed",
"filename=<%s> destination=<%s>",
item->fname, destname);
return r;
}
just_write:
/* Never apply GUID limits when replicating or repairing */
record->ignorelimits = 1;
r = mailbox_append_index_record(mailbox, record);
if (r) return r;
int hadsnoozed = 0;
/* apply the remote annotations */
r = apply_annotations(mailbox, record, NULL, annots, 0, &hadsnoozed);
if (r) {
syslog(LOG_ERR, "Failed to apply annotations: %s",
error_message(r));
}
if (!r && hadsnoozed) {
record->silentupdate = 1;
record->internal_flags |= FLAG_INTERNAL_SNOOZED;
r = mailbox_rewrite_index_record(mailbox, record);
}
return r;
}
/* ==================================================================== */
int sync_mailbox_version_check(struct mailbox **mailboxp)
{
int r = 0;
if ((*mailboxp)->i.minor_version < 10) {
/* index records will definitely not have guids! */
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
/* scan index records to ensure they have guids. version 10 index records
* have this field, but it might have never been initialised.
* XXX this might be overkill for versions > 10, but let's be cautious */
struct mailbox_iter *iter = mailbox_iter_init((*mailboxp), 0, 0);
const message_t *msg;
while ((msg = mailbox_iter_step(iter))) {
const struct index_record *record = msg_record(msg);
if (message_guid_isnull(&record->guid)) {
syslog(LOG_WARNING, "%s: missing guid for record %u -- needs 'reconstruct -G'?",
mailbox_name(*mailboxp), record->recno);
r = IMAP_MAILBOX_NOTSUPPORTED;
break;
}
}
mailbox_iter_done(&iter);
done:
if (r) {
syslog(LOG_DEBUG, "%s: %s failed version check: %s",
__func__, mailbox_name(*mailboxp), error_message(r));
mailbox_close(mailboxp);
}
return r;
}
/* ======================= server-side sync =========================== */
static void reserve_folder(const char *part, const char *mboxname,
struct sync_msgid_list *part_list)
{
struct mailbox *mailbox = NULL;
int r;
struct sync_msgid *item;
const char *mailbox_msg_path, *stage_msg_path;
int num_reserved;
redo:
num_reserved = 0;
/* Open and lock mailbox */
r = mailbox_open_irl(mboxname, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
if (r) return;
struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, ITER_SKIP_UNLINKED);
const message_t *msg;
while ((msg = mailbox_iter_step(iter))) {
const struct index_record *record = msg_record(msg);
/* do we need it? */
item = sync_msgid_lookup(part_list, &record->guid);
if (!item)
continue;
/* have we already found it? */
if (!item->need_upload)
continue;
/* Attempt to reserve this message */
mailbox_msg_path = mailbox_record_fname(mailbox, record);
stage_msg_path = dlist_reserve_path(part, record->internal_flags & FLAG_INTERNAL_ARCHIVED,
0, &record->guid);
/* check that the sha1 of the file on disk is correct */
struct index_record record2;
memset(&record2, 0, sizeof(struct index_record));
r = message_parse(mailbox_msg_path, &record2);
if (r) {
xsyslog(LOG_ERR, "IOERROR: parse failed",
"filename=<%s>",
mailbox_msg_path);
continue;
}
if (!message_guid_equal(&record->guid, &record2.guid)) {
xsyslog(LOG_ERR, "IOERROR: guid mismatch on parse",
"filename=<%s>",
mailbox_msg_path);
continue;
}
if (mailbox_copyfile(mailbox_msg_path, stage_msg_path, 0) != 0) {
xsyslog(LOG_ERR, "IOERROR: link failed",
"mailbox_msg_path=<%s> stage_msg_path=<%s>",
mailbox_msg_path, stage_msg_path);
continue;
}
item->size = record->size;
item->fname = xstrdup(stage_msg_path); /* track the correct location */
item->is_archive = (record->internal_flags & FLAG_INTERNAL_ARCHIVED) ? 1 : 0;
item->need_upload = 0;
part_list->toupload--;
num_reserved++;
/* already found everything, drop out */
if (!part_list->toupload) break;
/* arbitrary batch size */
if (num_reserved >= 1024) {
mailbox_iter_done(&iter);
mailbox_close(&mailbox);
goto redo;
}
}
mailbox_iter_done(&iter);
mailbox_close(&mailbox);
}
int sync_apply_reserve(struct dlist *kl,
struct sync_reserve_list *reserve_list,
struct sync_state *sstate)
{
struct message_guid *tmpguid;
struct sync_name_list *folder_names = sync_name_list_create();
struct sync_msgid_list *part_list;
struct sync_msgid *item;
struct sync_name *folder;
mbentry_t *mbentry = NULL;
const char *partition = NULL;
struct dlist *ml;
struct dlist *gl;
struct dlist *i;
struct dlist *kout = NULL;
if (!dlist_getatom(kl, "PARTITION", &partition)) goto parse_err;
if (!dlist_getlist(kl, "MBOXNAME", &ml)) goto parse_err;
if (!dlist_getlist(kl, "GUID", &gl)) goto parse_err;
part_list = sync_reserve_partlist(reserve_list, partition);
for (i = gl->head; i; i = i->next) {
if (!dlist_toguid(i, &tmpguid))
goto parse_err;
sync_msgid_insert(part_list, tmpguid);
}
/* need a list so we can mark items */
for (i = ml->head; i; i = i->next) {
sync_name_list_add(folder_names, i->sval);
}
for (folder = folder_names->head; folder; folder = folder->next) {
if (!part_list->toupload) break;
if (mboxlist_lookup(folder->name, &mbentry, 0))
continue;
if (strcmp(mbentry->partition, partition)) {
mboxlist_entry_free(&mbentry);
continue; /* try folders on the same partition first! */
}
mboxlist_entry_free(&mbentry);
reserve_folder(partition, folder->name, part_list);
folder->mark = 1;
}
/* if we have other folders, check them now */
for (folder = folder_names->head; folder; folder = folder->next) {
if (!part_list->toupload) break;
if (folder->mark)
continue;
reserve_folder(partition, folder->name, part_list);
folder->mark = 1;
}
/* check if we missed any */
kout = dlist_newlist(NULL, "MISSING");
for (i = gl->head; i; i = i->next) {
if (!dlist_toguid(i, &tmpguid))
goto parse_err;
item = sync_msgid_lookup(part_list, tmpguid);
if (item->need_upload)
dlist_setguid(kout, "GUID", tmpguid);
}
if (kout->head)
sync_send_response(kout, sstate->pout);
dlist_free(&kout);
sync_name_list_free(&folder_names);
mboxlist_entry_free(&mbentry);
return 0;
parse_err:
dlist_free(&kout);
sync_name_list_free(&folder_names);
mboxlist_entry_free(&mbentry);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
/* ====================================================================== */
int sync_apply_unquota(struct dlist *kin,
struct sync_state *sstate __attribute__((unused)))
{
return mboxlist_unsetquota(kin->sval);
}
int sync_apply_quota(struct dlist *kin,
struct sync_state *sstate __attribute__((unused)))
{
const char *root;
quota_t limits[QUOTA_NUMRESOURCES];
if (!dlist_getatom(kin, "ROOT", &root))
return IMAP_PROTOCOL_BAD_PARAMETERS;
sync_decode_quota_limits(kin, limits);
modseq_t modseq = 0;
dlist_getnum64(kin, "MODSEQ", &modseq);
return mboxlist_setquotas(root, limits, modseq, 1);
}
/* ====================================================================== */
static int sync_mailbox_compare_update(struct mailbox *mailbox,
struct dlist *kr, int doupdate,
struct sync_msgid_list *part_list)
{
struct index_record mrecord;
struct dlist *ki;
struct sync_annot_list *mannots = NULL;
struct sync_annot_list *rannots = NULL;
int r;
int i;
int has_append = 0;
struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, 0);
const message_t *msg = mailbox_iter_step(iter);
const struct index_record *rrecord = msg ? msg_record(msg) : NULL;
for (ki = kr->head; ki; ki = ki->next) {
sync_annot_list_free(&mannots);
sync_annot_list_free(&rannots);
r = parse_upload(ki, mailbox, &mrecord, &mannots);
if (r) {
xsyslog(LOG_ERR, "SYNCERROR: failed to parse uploaded record", NULL);
return IMAP_PROTOCOL_ERROR;
}
/* n.b. we assume the records in kr are in ascending uid order.
* stuff will probably fail in interesting ways if they're ever not.
*/
while (rrecord && rrecord->uid < mrecord.uid) {
/* read another record */
msg = mailbox_iter_step(iter);
rrecord = msg ? msg_record(msg) : NULL;
}
/* found a match, check for updates */
if (rrecord && rrecord->uid == mrecord.uid) {
/* if they're both EXPUNGED then ignore everything else */
if (mrecord.internal_flags & FLAG_INTERNAL_EXPUNGED &&
rrecord->internal_flags & FLAG_INTERNAL_EXPUNGED)
continue;
/* GUID mismatch is an error straight away, it only ever happens if we
* had a split brain - and it will take a full sync to sort out the mess */
if (!message_guid_equal(&mrecord.guid, &rrecord->guid)) {
xsyslog(LOG_ERR, "SYNCNOTICE: guid mismatch",
"mailbox=<%s> uid=<%u>",
mailbox_name(mailbox), mrecord.uid);
r = IMAP_SYNC_CHECKSUM;
goto out;
}
/* higher modseq on the replica is an error */
if (rrecord->modseq > mrecord.modseq) {
if (opt_force) {
syslog(LOG_NOTICE, "forcesync: higher modseq on replica %s %u (" MODSEQ_FMT " > " MODSEQ_FMT ")",
mailbox_name(mailbox), mrecord.uid, rrecord->modseq, mrecord.modseq);
}
else {
xsyslog(LOG_ERR, "SYNCNOTICE: higher modseq on replica",
"mailbox=<%s> uid=<%u>"
" replicamodseq=<" MODSEQ_FMT ">"
" mastermodseq=<" MODSEQ_FMT ">",
mailbox_name(mailbox), mrecord.uid,
rrecord->modseq, mrecord.modseq);
r = IMAP_SYNC_CHECKSUM;
goto out;
}
}
/* if it's already expunged on the replica, but alive on the master,
* that's bad */
if (!(mrecord.internal_flags & FLAG_INTERNAL_EXPUNGED) &&
(rrecord->internal_flags & FLAG_INTERNAL_EXPUNGED)) {
xsyslog(LOG_ERR, "SYNCNOTICE: expunged on replica",
"mailbox=<%s> uid=<%u>",
mailbox_name(mailbox), mrecord.uid);
r = IMAP_SYNC_CHECKSUM;
goto out;
}
/* skip out on the first pass */
if (!doupdate) continue;
struct index_record copy = *rrecord;
copy.cid = mrecord.cid;
copy.basecid = mrecord.basecid;
copy.modseq = mrecord.modseq;
copy.last_updated = mrecord.last_updated;
copy.internaldate = mrecord.internaldate;
copy.savedate = mrecord.savedate;
copy.createdmodseq = mrecord.createdmodseq;
copy.system_flags = mrecord.system_flags;
/* FLAG_INTERNAL_EXPUNGED is a syncable flag, but it's internal.
* The `internal_flags` contain replica's internal_flags for
* non-EXPUNGED, but master's internal_flags for EXPUNGED */
copy.internal_flags = rrecord->internal_flags & ~FLAG_INTERNAL_EXPUNGED;
copy.internal_flags |= mrecord.internal_flags & FLAG_INTERNAL_EXPUNGED;
for (i = 0; i < MAX_USER_FLAGS/32; i++)
copy.user_flags[i] = mrecord.user_flags[i];
r = read_annotations(mailbox, &copy, &rannots, rrecord->modseq,
/*XXX ANNOTATE_TOMBSTONES*/0);
if (r) {
syslog(LOG_ERR, "Failed to read local annotations %s %u: %s",
mailbox_name(mailbox), rrecord->recno, error_message(r));
goto out;
}
int hadsnoozed = 0;
r = apply_annotations(mailbox, &copy, rannots, mannots, 0, &hadsnoozed);
if (r) {
syslog(LOG_ERR, "Failed to write merged annotations %s %u: %s",
mailbox_name(mailbox), rrecord->recno, error_message(r));
goto out;
}
if (hadsnoozed) copy.internal_flags |= FLAG_INTERNAL_SNOOZED;
else copy.internal_flags &= ~FLAG_INTERNAL_SNOOZED;
copy.silentupdate = 1;
copy.ignorelimits = 1;
r = mailbox_rewrite_index_record(mailbox, &copy);
if (r) {
xsyslog(LOG_ERR, "IOERROR: rewrite record failed",
"mboxname=<%s> recno=<%u>",
mailbox_name(mailbox), rrecord->recno);
goto out;
}
}
/* not found and less than LAST_UID, bogus */
else if (mrecord.uid <= mailbox->i.last_uid) {
/* Expunged, just skip it */
if (!(mrecord.internal_flags & FLAG_INTERNAL_EXPUNGED)) {
r = IMAP_SYNC_CHECKSUM;
goto out;
}
}
/* after LAST_UID, it's an append, that's OK */
else {
/* skip out on the first pass */
if (!doupdate) continue;
mrecord.silentupdate = 1;
r = sync_append_copyfile(mailbox, &mrecord, mannots, part_list);
if (r) {
xsyslog(LOG_ERR, "IOERROR: append file failed",
"mboxname=<%s> uid=<%d>",
mailbox_name(mailbox), mrecord.uid);
goto out;
}
has_append = 1;
}
}
if (has_append)
sync_log_append(mailbox_name(mailbox));
r = 0;
out:
mailbox_iter_done(&iter);
sync_annot_list_free(&mannots);
sync_annot_list_free(&rannots);
return r;
}
int sync_apply_mailbox(struct dlist *kin,
struct sync_reserve_list *reserve_list,
struct sync_state *sstate)
{
struct sync_msgid_list *part_list;
/* fields from the request */
const char *uniqueid;
const char *partition;
const char *mboxname;
const char *mboxtype = NULL; /* optional */
uint32_t mbtype;
uint32_t last_uid;
modseq_t highestmodseq;
uint32_t recentuid;
time_t recenttime;
time_t last_appenddate;
time_t pop3_last_login;
time_t pop3_show_after = 0; /* optional */
uint32_t uidvalidity;
modseq_t foldermodseq = 0;
const char *acl;
const char *options_str;
struct synccrcs synccrcs = { 0, 0 };
uint32_t options;
/* optional fields */
modseq_t xconvmodseq = 0;
modseq_t raclmodseq = 0;
modseq_t createdmodseq = 0;
/* previous state markers */
modseq_t since_modseq = 0;
struct synccrcs since_crcs = { 0, 0 };
struct mailbox *mailbox = NULL;
struct dlist *kr;
struct dlist *ka = NULL;
int r;
struct sync_annot_list *mannots = NULL;
struct sync_annot_list *rannots = NULL;
annotate_state_t *astate = NULL;
if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum64(kin, "HIGHESTMODSEQ", &highestmodseq))
return IMAP_PROTOCOL_BAD_PARAMETERS;
dlist_getnum64(kin, "CREATEDMODSEQ", &createdmodseq);
dlist_getatom(kin, "MBOXTYPE", &mboxtype);
mbtype = mboxlist_string_to_mbtype(mboxtype);
if (mbtype & (MBTYPE_INTERMEDIATE|MBTYPE_DELETED)) {
// XXX - make sure what's already there is either nothing or compatible...
mbentry_t *newmbentry = NULL;
newmbentry = mboxlist_entry_create();
newmbentry->name = xstrdupnull(mboxname);
newmbentry->mbtype = mbtype;
newmbentry->uniqueid = xstrdupnull(uniqueid);
newmbentry->foldermodseq = highestmodseq;
newmbentry->createdmodseq = createdmodseq;
r = mboxlist_update(newmbentry, /*localonly*/1);
mboxlist_entry_free(&newmbentry);
return r;
}
if (!dlist_getatom(kin, "PARTITION", &partition))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kin, "LAST_UID", &last_uid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kin, "RECENTUID", &recentuid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "RECENTTIME", &recenttime))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "LAST_APPENDDATE", &last_appenddate))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "POP3_LAST_LOGIN", &pop3_last_login))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kin, "UIDVALIDITY", &uidvalidity))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "ACL", &acl))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "OPTIONS", &options_str))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getlist(kin, "RECORD", &kr))
return IMAP_PROTOCOL_BAD_PARAMETERS;
/* optional */
dlist_getlist(kin, "ANNOTATIONS", &ka);
dlist_getdate(kin, "POP3_SHOW_AFTER", &pop3_show_after);
dlist_getnum64(kin, "XCONVMODSEQ", &xconvmodseq);
dlist_getnum64(kin, "RACLMODSEQ", &raclmodseq);
dlist_getnum64(kin, "FOLDERMODSEQ", &foldermodseq);
/* Get the CRCs */
dlist_getnum32(kin, "SYNC_CRC", &synccrcs.basic);
dlist_getnum32(kin, "SYNC_CRC_ANNOT", &synccrcs.annot);
/* Get the previous state for this delta */
dlist_getnum64(kin, "SINCE_MODSEQ", &since_modseq);
dlist_getnum32(kin, "SINCE_CRC", &since_crcs.basic);
dlist_getnum32(kin, "SINCE_CRC_ANNOT", &since_crcs.annot);
options = sync_parse_options(options_str);
r = mailbox_open_iwl(mboxname, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
if (r == IMAP_MAILBOX_NONEXISTENT) {
struct mboxlock *namespacelock = mboxname_usernamespacelock(mboxname);
if (!namespacelock) {
r = IMAP_MAILBOX_LOCKED;
xsyslog(LOG_ERR, "IOERROR: failed to usernamespacelock",
"mailbox=<%s> uniqueid=<%s>",
mboxname, uniqueid);
goto done;
}
// try again under lock
r = mailbox_open_iwl(mboxname, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
if (r == IMAP_MAILBOX_NONEXISTENT) { // did we win a race?
char *oldname = mboxlist_find_uniqueid(uniqueid, NULL, NULL);
// if they have the same name it's probably an intermediate being
// promoted. Intermediates, the gift that keeps on giving.
if (oldname && strcmp(oldname, mboxname)) {
xsyslog(LOG_ERR, "SYNCNOTICE: mailbox uniqueid already in use",
"mailbox=<%s> uniqueid=<%s> usedby=<%s>",
mboxname, uniqueid, oldname);
free(oldname);
r = IMAP_MAILBOX_MOVED;
}
else {
mbentry_t mbentry = MBENTRY_INITIALIZER;
mbentry.name = (char *) mboxname;
mbentry.partition = (char *) partition;
mbentry.uniqueid = (char *) uniqueid;
mbentry.acl = (char *) acl;
mbentry.mbtype = mbtype;
mbentry.uidvalidity = uidvalidity;
mbentry.createdmodseq = createdmodseq;
mbentry.foldermodseq = foldermodseq;
unsigned flags = MBOXLIST_CREATE_SYNC;
if (sstate->local_only) flags |= MBOXLIST_CREATE_LOCALONLY;
r = mboxlist_createmailbox(&mbentry, options, highestmodseq,
1/*isadmin*/,
sstate->userid, sstate->authstate,
flags, &mailbox);
}
/* set a highestmodseq of 0 so ALL changes are future
* changes and get applied */
if (!r) mailbox->i.highestmodseq = 0;
}
mboxname_release(&namespacelock);
}
if (!r && strcmp(mailbox_uniqueid(mailbox), uniqueid)) {
if (opt_force || mailbox->i.last_uid == 0) {
xsyslog(LOG_NOTICE, "SYNCNOTICE: mailbox uniqueid changed - replacing",
"mailbox=<%s> origuniqueid=<%s> newuniqueid=<%s>",
mboxname, mailbox_uniqueid(mailbox), uniqueid);
// close first, because we'll be taking the lock and comparing and then away we go
mailbox_close(&mailbox);
struct mboxlock *namespacelock = mboxname_usernamespacelock(mboxname);
if (!namespacelock) {
r = IMAP_MAILBOX_LOCKED;
xsyslog(LOG_ERR, "IOERROR: failed to usernamespacelock",
"mailbox=<%s> uniqueid=<%s>",
mboxname, uniqueid);
goto done;
}
r = mailbox_open_iwl(mboxname, &mailbox);
if (r) goto unlock;
// lost the race? Keep this mailbox
if (!strcmp(mailbox_uniqueid(mailbox), uniqueid)) goto unlock;
// we need to create a new mailbox!
mailbox_close(&mailbox);
int delflags = MBOXLIST_DELETE_FORCE | MBOXLIST_DELETE_SILENT;
if (sstate->local_only) delflags |= MBOXLIST_DELETE_LOCALONLY;
r = mboxlist_deletemailbox(mboxname, sstate->userisadmin,
sstate->userid, sstate->authstate,
NULL, delflags);
if (r) goto unlock; // kinda bad, we've failed to nuke the old one
mbentry_t mbentry = MBENTRY_INITIALIZER;
mbentry.name = (char *) mboxname;
mbentry.partition = (char *) partition;
mbentry.uniqueid = (char *) uniqueid;
mbentry.acl = (char *) acl;
mbentry.mbtype = mbtype;
mbentry.uidvalidity = uidvalidity;
mbentry.createdmodseq = createdmodseq;
mbentry.foldermodseq = foldermodseq;
unsigned flags = MBOXLIST_CREATE_SYNC;
if (sstate->local_only) flags |= MBOXLIST_CREATE_LOCALONLY;
r = mboxlist_createmailbox(&mbentry, options, highestmodseq,
1/*isadmin*/,
sstate->userid, sstate->authstate,
flags, &mailbox);
/* set a highestmodseq of 0 so ALL changes are future
* changes and get applied */
if (!r) mailbox->i.highestmodseq = 0;
unlock:
mboxname_release(&namespacelock);
}
else {
xsyslog(LOG_ERR, "SYNCERROR: mailbox uniqueid changed - retry",
"mailbox=<%s> origuniqueid=<%s> newuniqueid=<%s>",
mboxname, mailbox_uniqueid(mailbox), uniqueid);
r = IMAP_MAILBOX_MOVED;
goto done;
}
}
if (r) {
syslog(LOG_ERR, "Failed to open mailbox %s to update: %s",
mboxname, error_message(r));
goto done;
}
// immediate bail if we have an old state to compare
if (since_modseq) {
struct synccrcs mycrcs = mailbox_synccrcs(mailbox, 0);
if (since_modseq != mailbox->i.highestmodseq ||
!mailbox_crceq(since_crcs, mycrcs)) {
xsyslog(LOG_ERR, "SYNCNOTICE: mailbox sync mismatch",
"mailbox=<%s>"
" hms_master=<" MODSEQ_FMT ">"
" hms_replica=<" MODSEQ_FMT ">"
" crcs_master=<%u/%u>"
" crcs_replica=<%u/%u>",
mailbox_name(mailbox),
since_modseq,
mailbox->i.highestmodseq,
since_crcs.basic, since_crcs.annot,
mycrcs.basic, mycrcs.annot);
r = IMAP_SYNC_CHANGED;
goto done;
}
}
if ((mbtypes_sync(mailbox_mbtype(mailbox))) != mbtype) {
syslog(LOG_ERR, "INVALID MAILBOX TYPE %s (%d, %d)", mailbox_name(mailbox), mailbox_mbtype(mailbox), mbtype);
/* is this even possible? */
r = IMAP_MAILBOX_BADTYPE;
goto done;
}
part_list = sync_reserve_partlist(reserve_list, mailbox_partition(mailbox));
/* hold the annotate state open */
r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
if (r) goto done;
/* and make it hold a transaction open */
annotate_state_begin(astate);
/* skip out now, it's going to mismatch for sure! */
if (highestmodseq < mailbox->i.highestmodseq) {
if (opt_force) {
syslog(LOG_NOTICE, "forcesync: higher modseq on replica %s - "
MODSEQ_FMT " < " MODSEQ_FMT,
mboxname, highestmodseq, mailbox->i.highestmodseq);
}
else {
syslog(LOG_ERR, "higher modseq on replica %s - "
MODSEQ_FMT " < " MODSEQ_FMT,
mboxname, highestmodseq, mailbox->i.highestmodseq);
r = IMAP_SYNC_CHECKSUM;
goto done;
}
}
/* skip out now, it's going to mismatch for sure! */
if (uidvalidity < mailbox->i.uidvalidity) {
if (opt_force) {
syslog(LOG_NOTICE, "forcesync: higher uidvalidity on replica %s - %u < %u",
mboxname, uidvalidity, mailbox->i.uidvalidity);
}
else {
syslog(LOG_ERR, "higher uidvalidity on replica %s - %u < %u",
mboxname, uidvalidity, mailbox->i.uidvalidity);
r = IMAP_SYNC_CHECKSUM;
goto done;
}
}
/* skip out now, it's going to mismatch for sure! */
if (last_uid < mailbox->i.last_uid) {
if (opt_force) {
syslog(LOG_NOTICE, "forcesync: higher last_uid on replica %s - %u < %u",
mboxname, last_uid, mailbox->i.last_uid);
}
else {
syslog(LOG_ERR, "higher last_uid on replica %s - %u < %u",
mboxname, last_uid, mailbox->i.last_uid);
r = IMAP_SYNC_CHECKSUM;
goto done;
}
}
/* skip out now, it's going to mismatch for sure! */
/* 0 is the default case, and should always be overwritten with the real value */
if (createdmodseq > mailbox->i.createdmodseq && mailbox->i.createdmodseq != 0) {
xsyslog(LOG_NOTICE, "SYNCNOTICE: lower createdmodseq on replica",
"mailbox=<%s> createdmodseq=<" MODSEQ_FMT ">"
" replica_createdmodseq=<" MODSEQ_FMT ">",
mboxname, createdmodseq, mailbox->i.createdmodseq);
}
/* NOTE - this is optional */
if (mailbox_has_conversations(mailbox) && xconvmodseq) {
modseq_t ourxconvmodseq = 0;
r = mailbox_get_xconvmodseq(mailbox, &ourxconvmodseq);
if (r) {
syslog(LOG_ERR, "Failed to read xconvmodseq for %s: %s",
mboxname, error_message(r));
goto done;
}
/* skip out now, it's going to mismatch for sure! */
if (xconvmodseq < ourxconvmodseq) {
if (opt_force) {
syslog(LOG_NOTICE, "forcesync: higher xconvmodseq on replica %s - %llu < %llu",
mboxname, xconvmodseq, ourxconvmodseq);
}
else {
syslog(LOG_ERR, "higher xconvmodseq on replica %s - %llu < %llu",
mboxname, xconvmodseq, ourxconvmodseq);
r = IMAP_SYNC_CHECKSUM;
goto done;
}
}
}
r = sync_mailbox_compare_update(mailbox, kr, 0, part_list);
if (r) goto done;
/* now we're committed to writing something no matter what happens! */
mailbox_index_dirty(mailbox);
mailbox->silentchanges = 1;
/* always take the ACL from the master, it's not versioned */
r = mboxlist_sync_setacls(mboxname, acl, foldermodseq ? foldermodseq : highestmodseq);
if (!r) r = mailbox_set_acl(mailbox, acl);
if (r) goto done;
/* take all mailbox (not message) annotations - aka metadata,
* they're not versioned either */
if (ka)
decode_annotations(ka, &mannots, mailbox, NULL);
r = read_annotations(mailbox, NULL, &rannots, 0, 0);
if (!r) r = apply_annotations(mailbox, NULL, rannots, mannots, 0, NULL);
if (r) {
syslog(LOG_ERR, "syncerror: annotations failed to apply to %s",
mailbox_name(mailbox));
goto done;
}
r = sync_mailbox_compare_update(mailbox, kr, 1, part_list);
if (r) {
abort();
return r;
}
if (!opt_force) {
assert(mailbox->i.last_uid <= last_uid);
}
mailbox->i.last_uid = last_uid;
mailbox->i.recentuid = recentuid;
mailbox->i.highestmodseq = highestmodseq;
mailbox->i.recenttime = recenttime;
mailbox->i.last_appenddate = last_appenddate;
mailbox->i.pop3_last_login = pop3_last_login;
mailbox->i.pop3_show_after = pop3_show_after;
mailbox->i.createdmodseq = createdmodseq;
/* only alter the syncable options */
mailbox->i.options = (options & MAILBOX_OPTIONS_MASK) |
(mailbox->i.options & ~MAILBOX_OPTIONS_MASK);
/* always set the highestmodseq */
mboxname_setmodseq(mailbox_name(mailbox), highestmodseq, mailbox_mbtype(mailbox), /*flags*/0);
/* this happens rarely, so let us know */
if (mailbox->i.uidvalidity != uidvalidity) {
syslog(LOG_NOTICE, "%s uidvalidity changed, updating %u => %u",
mailbox_name(mailbox), mailbox->i.uidvalidity, uidvalidity);
/* make sure nothing new gets created with a lower value */
mailbox->i.uidvalidity = mboxname_setuidvalidity(mailbox_name(mailbox), uidvalidity);
}
if (mailbox_has_conversations(mailbox)) {
r = mailbox_update_xconvmodseq(mailbox, xconvmodseq, opt_force);
}
if (config_getswitch(IMAPOPT_REVERSEACLS) && raclmodseq) {
mboxname_setraclmodseq(mailbox_name(mailbox), raclmodseq);
}
done:
sync_annot_list_free(&mannots);
sync_annot_list_free(&rannots);
/* check the CRC too */
if (!r && !mailbox_crceq(synccrcs, mailbox_synccrcs(mailbox, 0))) {
/* try forcing a recalculation */
if (!mailbox_crceq(synccrcs, mailbox_synccrcs(mailbox, 1)))
r = IMAP_SYNC_CHECKSUM;
}
mailbox_close(&mailbox);
return r;
}
/* ====================================================================== */
static int getannotation_cb(const char *mboxname,
uint32_t uid __attribute__((unused)),
const char *entry, const char *userid,
const struct buf *value,
const struct annotate_metadata *mdata __attribute__((unused)),
void *rock)
{
struct protstream *pout = (struct protstream *)rock;
struct dlist *kl;
kl = dlist_newkvlist(NULL, "ANNOTATION");
dlist_setatom(kl, "MBOXNAME", mboxname);
dlist_setatom(kl, "ENTRY", entry);
dlist_setatom(kl, "USERID", userid);
dlist_setmap(kl, "VALUE", value->s, value->len);
sync_send_response(kl, pout);
dlist_free(&kl);
return 0;
}
int sync_get_annotation(struct dlist *kin, struct sync_state *sstate)
{
const char *mboxname = kin->sval;
return annotatemore_findall_pattern(mboxname, 0, "*", /*modseq*/0,
&getannotation_cb, (void *) sstate->pout,
/*flags*/0);
}
static void print_quota(struct quota *q, struct protstream *pout)
{
struct dlist *kl;
kl = dlist_newkvlist(NULL, "QUOTA");
dlist_setatom(kl, "ROOT", q->root);
sync_encode_quota_limits(kl, q->limits);
dlist_setnum64(kl, "MODSEQ", q->modseq);
sync_send_response(kl, pout);
dlist_free(&kl);
}
static int quota_work(const char *root, struct protstream *pout)
{
struct quota q;
quota_init(&q, root);
if (!quota_read(&q, NULL, 0))
print_quota(&q, pout);
quota_free(&q);
return 0;
}
int sync_get_quota(struct dlist *kin, struct sync_state *sstate)
{
return quota_work(kin->sval, sstate->pout);
}
struct mbox_rock {
struct protstream *pout;
struct sync_name_list *qrl;
};
static int sync_mailbox_byentry(const mbentry_t *mbentry, void *rock)
{
struct mbox_rock *mrock = (struct mbox_rock *) rock;
struct sync_name_list *qrl = mrock->qrl;
struct mailbox *mailbox = NULL;
struct dlist *kl = dlist_newkvlist(NULL, "MAILBOX");
annotate_state_t *astate = NULL;
int r = 0;
if (!mbentry) goto out;
if (mbentry->mbtype & MBTYPE_DELETED) goto out;
if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
dlist_setatom(kl, "UNIQUEID", mbentry->uniqueid);
dlist_setatom(kl, "MBOXNAME", mbentry->name);
dlist_setatom(kl, "MBOXTYPE", mboxlist_mbtype_to_string(mbtypes_sync(mbentry->mbtype)));
dlist_setnum32(kl, "SYNC_CRC", 0);
// this stuff should be optional, but old sync_client will barf without it
dlist_setnum32(kl, "LAST_UID", 0);
dlist_setnum64(kl, "HIGHESTMODSEQ", 0);
dlist_setnum32(kl, "RECENTUID", 0);
dlist_setdate(kl, "RECENTTIME", 0);
dlist_setdate(kl, "LAST_APPENDDATE", 0);
dlist_setdate(kl, "POP3_LAST_LOGIN", 0);
dlist_setdate(kl, "POP3_SHOW_AFTER", 0);
// standard fields
dlist_setnum32(kl, "UIDVALIDITY", mbentry->uidvalidity);
dlist_setatom(kl, "PARTITION", mbentry->partition);
dlist_setatom(kl, "ACL", mbentry->acl);
dlist_setatom(kl, "OPTIONS", sync_encode_options(0));
dlist_setnum64(kl, "CREATEDMODSEQ", mbentry->createdmodseq);
dlist_setnum64(kl, "FOLDERMODSEQ", mbentry->foldermodseq);
// send the intermediate response
sync_send_response(kl, mrock->pout);
goto out;
}
r = mailbox_open_irl(mbentry->name, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
/* doesn't exist? Probably not finished creating or removing yet */
if (r == IMAP_MAILBOX_NONEXISTENT ||
r == IMAP_MAILBOX_RESERVED) {
r = 0;
goto out;
}
if (r) goto out;
/* hold the annotate state open */
r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
if (r) goto out;
/* and make it hold a transaction open */
annotate_state_begin(astate);
if (qrl && mailbox->quotaroot)
sync_name_list_add(qrl, mailbox->quotaroot);
r = sync_prepare_dlists(mailbox, NULL, NULL, NULL, NULL, kl, NULL, 0,
/*XXX fullannots*/1, 0);
if (!r) sync_send_response(kl, mrock->pout);
out:
mailbox_close(&mailbox);
dlist_free(&kl);
return r;
}
static int sync_mailbox_byuniqueid(const char *uniqueid, void *rock)
{
mbentry_t *mbentry = NULL;
int r = mboxlist_lookup_by_uniqueid(uniqueid, &mbentry, NULL);
if (!r) r = sync_mailbox_byentry(mbentry, rock);
mboxlist_entry_free(&mbentry);
return r;
}
int sync_get_fullmailbox(struct dlist *kin, struct sync_state *sstate)
{
struct mailbox *mailbox = NULL;
struct dlist *kl = dlist_newkvlist(NULL, "MAILBOX");
int r;
r = mailbox_open_irl(kin->sval, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
if (r) goto out;
r = sync_prepare_dlists(mailbox, NULL, NULL, NULL, NULL, kl, NULL, 1,
/*XXX fullannots*/1, 0);
if (r) goto out;
sync_send_response(kl, sstate->pout);
out:
dlist_free(&kl);
mailbox_close(&mailbox);
return r;
}
int sync_get_mailboxes(struct dlist *kin, struct sync_state *sstate)
{
struct dlist *ki;
struct mbox_rock mrock = { sstate->pout, NULL };
for (ki = kin->head; ki; ki = ki->next) {
mbentry_t *mbentry = NULL;
int r = mboxlist_lookup_allow_all(ki->sval, &mbentry, NULL);
if (!r) sync_mailbox_byentry(mbentry, &mrock);
mboxlist_entry_free(&mbentry);
}
return 0;
}
int sync_get_uniqueids(struct dlist *kin, struct sync_state *sstate)
{
struct dlist *ki;
struct mbox_rock mrock = { sstate->pout, NULL };
for (ki = kin->head; ki; ki = ki->next)
sync_mailbox_byuniqueid(ki->sval, &mrock);
return 0;
}
/* ====================================================================== */
static int print_seen(const char *uniqueid, struct seendata *sd, void *rock)
{
struct dlist *kl;
struct protstream *pout = (struct protstream *) rock;
kl = dlist_newkvlist(NULL, "SEEN");
dlist_setatom(kl, "UNIQUEID", uniqueid);
dlist_setdate(kl, "LASTREAD", sd->lastread);
dlist_setnum32(kl, "LASTUID", sd->lastuid);
dlist_setdate(kl, "LASTCHANGE", sd->lastchange);
dlist_setatom(kl, "SEENUIDS", sd->seenuids);
sync_send_response(kl, pout);
dlist_free(&kl);
return 0;
}
static int user_getseen(const char *userid, struct protstream *pout)
{
struct seen *seendb = NULL;
/* no SEEN DB is OK, just return */
if (seen_open(userid, SEEN_SILENT, &seendb))
return 0;
seen_foreach(seendb, print_seen, pout);
seen_close(&seendb);
return 0;
}
static int user_getsub(const char *userid, struct protstream *pout)
{
struct dlist *kl = dlist_newlist(NULL, "LSUB");
strarray_t *sublist = mboxlist_sublist(userid);
int i;
for (i = 0; i < sublist->count; i++) {
const char *name = strarray_nth(sublist, i);
dlist_setatom(kl, "MBOXNAME", name);
}
if (kl->head)
sync_send_response(kl, pout);
dlist_free(&kl);
strarray_free(sublist);
return 0;
}
static int user_getsieve(const char *userid, struct protstream *pout)
{
struct sync_sieve_list *sieve_list;
struct sync_sieve *sieve;
struct dlist *kl;
sieve_list = sync_sieve_list_generate(userid);
if (!sieve_list) return 0;
for (sieve = sieve_list->head; sieve; sieve = sieve->next) {
kl = dlist_newkvlist(NULL, "SIEVE");
dlist_setatom(kl, "FILENAME", sieve->name);
dlist_setdate(kl, "LAST_UPDATE", sieve->last_update);
dlist_setatom(kl, "GUID", message_guid_encode(&sieve->guid));
dlist_setnum32(kl, "ISACTIVE", sieve->active ? 1 : 0);
sync_send_response(kl, pout);
dlist_free(&kl);
}
sync_sieve_list_free(&sieve_list);
return 0;
}
static int user_meta(const char *userid, struct protstream *pout)
{
user_getseen(userid, pout);
user_getsub(userid, pout);
user_getsieve(userid, pout);
return 0;
}
int sync_get_meta(struct dlist *kin, struct sync_state *sstate)
{
return user_meta(kin->sval, sstate->pout);
}
int sync_get_user(struct dlist *kin, struct sync_state *sstate)
{
int r;
struct sync_name_list *quotaroots;
struct sync_name *qr;
const char *userid = kin->sval;
struct mbox_rock mrock;
quotaroots = sync_name_list_create();
mrock.qrl = quotaroots;
mrock.pout = sstate->pout;
r = mboxlist_usermboxtree(userid, NULL, sync_mailbox_byentry, &mrock, MBOXTREE_DELETED|MBOXTREE_INTERMEDIATES);
if (r) goto bail;
for (qr = quotaroots->head; qr; qr = qr->next) {
r = quota_work(qr->name, sstate->pout);
if (r) goto bail;
}
r = user_meta(userid, sstate->pout);
if (r) goto bail;
bail:
sync_name_list_free(&quotaroots);
return r;
}
/* ====================================================================== */
int sync_apply_unmailbox(struct dlist *kin, struct sync_state *sstate)
{
const char *mboxname = kin->sval;
struct mboxlock *namespacelock = mboxname_usernamespacelock(mboxname);
/* Delete with admin privileges */
int delflags = MBOXLIST_DELETE_FORCE | MBOXLIST_DELETE_SILENT;
if (sstate->local_only) delflags |= MBOXLIST_DELETE_LOCALONLY;
int r = mboxlist_deletemailbox(mboxname, sstate->userisadmin,
sstate->userid, sstate->authstate,
NULL, delflags);
mboxname_release(&namespacelock);
return r;
}
int sync_apply_rename(struct dlist *kin, struct sync_state *sstate)
{
const char *oldmboxname;
const char *newmboxname;
const char *partition;
uint32_t uidvalidity = 0;
mbentry_t *mbentry = NULL;
int r;
if (!dlist_getatom(kin, "OLDMBOXNAME", &oldmboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "NEWMBOXNAME", &newmboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "PARTITION", &partition))
return IMAP_PROTOCOL_BAD_PARAMETERS;
/* optional */
dlist_getnum32(kin, "UIDVALIDITY", &uidvalidity);
struct mboxlock *oldlock = NULL;
struct mboxlock *newlock = NULL;
/* make sure we grab these locks in a stable order! */
if (strcmpsafe(oldmboxname, newmboxname) < 0) {
oldlock = mboxname_usernamespacelock(oldmboxname);
newlock = mboxname_usernamespacelock(newmboxname);
}
else {
// doesn't hurt to double lock, it's refcounted
newlock = mboxname_usernamespacelock(newmboxname);
oldlock = mboxname_usernamespacelock(oldmboxname);
}
r = mboxlist_lookup_allow_all(oldmboxname, &mbentry, 0);
if (!r) r = mboxlist_renamemailbox(mbentry, newmboxname, partition,
uidvalidity, 1, sstate->userid,
sstate->authstate, NULL, sstate->local_only, 1, 1,
1/*keep_intermediaries*/,
0/*move_subscription*/,
1/*silent*/);
mboxlist_entry_free(&mbentry);
mboxname_release(&oldlock);
mboxname_release(&newlock);
return r;
}
int sync_apply_changesub(struct dlist *kin, struct sync_state *sstate)
{
const char *mboxname;
const char *userid;
int add;
/* SUB or UNSUB */
add = strcmp(kin->name, "SUB") ? 0 : 1;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return mboxlist_changesub(mboxname, userid, sstate->authstate, add, add, 0);
}
/* ====================================================================== */
int sync_apply_annotation(struct dlist *kin, struct sync_state *sstate)
{
struct entryattlist *entryatts = NULL;
struct attvaluelist *attvalues = NULL;
const char *mboxname = NULL;
const char *entry = NULL;
const char *mapval = NULL;
size_t maplen = 0;
struct buf value = BUF_INITIALIZER;
const char *userid = NULL;
char *name = NULL;
struct mailbox *mailbox = NULL;
annotate_state_t *astate = NULL;
int r;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "ENTRY", &entry))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getmap(kin, "VALUE", &mapval, &maplen))
return IMAP_PROTOCOL_BAD_PARAMETERS;
buf_init_ro(&value, mapval, maplen);
appendattvalue(&attvalues,
*userid ? "value.priv" : "value.shared",
&value);
appendentryatt(&entryatts, entry, attvalues);
astate = annotate_state_new();
if (*mboxname) {
r = mailbox_open_iwl(mboxname, &mailbox);
if (r) goto done;
r = sync_mailbox_version_check(&mailbox);
if (r) goto done;
r = annotate_state_set_mailbox(astate, mailbox);
if (r) goto done;
}
else {
r = annotate_state_set_server(astate);
if (r) goto done;
}
annotate_state_set_auth(astate,
sstate->userisadmin, userid, sstate->authstate);
r = annotate_state_store(astate, entryatts);
done:
if (!r)
r = annotate_state_commit(&astate);
else
annotate_state_abort(&astate);
mailbox_close(&mailbox);
buf_free(&value);
freeentryatts(entryatts);
free(name);
return r;
}
int sync_apply_unannotation(struct dlist *kin, struct sync_state *sstate)
{
struct entryattlist *entryatts = NULL;
struct attvaluelist *attvalues = NULL;
const char *mboxname = NULL;
const char *entry = NULL;
const char *userid = NULL;
struct buf empty = BUF_INITIALIZER;
char *name = NULL;
struct mailbox *mailbox = NULL;
annotate_state_t *astate = NULL;
int r;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "ENTRY", &entry))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
appendattvalue(&attvalues,
*userid ? "value.priv" : "value.shared",
&empty);
appendentryatt(&entryatts, entry, attvalues);
astate = annotate_state_new();
if (*mboxname) {
r = mailbox_open_iwl(mboxname, &mailbox);
if (r) goto done;
r = sync_mailbox_version_check(&mailbox);
if (r) goto done;
r = annotate_state_set_mailbox(astate, mailbox);
if (r) goto done;
}
else {
r = annotate_state_set_server(astate);
if (r) goto done;
}
annotate_state_set_auth(astate,
sstate->userisadmin, userid, sstate->authstate);
r = annotate_state_store(astate, entryatts);
done:
if (!r)
r = annotate_state_commit(&astate);
else
annotate_state_abort(&astate);
mailbox_close(&mailbox);
freeentryatts(entryatts);
free(name);
return r;
}
int sync_apply_sieve(struct dlist *kin,
struct sync_state *sstate __attribute__((unused)))
{
const char *userid;
const char *filename;
time_t last_update;
const char *content;
size_t len;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "FILENAME", &filename))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "LAST_UPDATE", &last_update))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getmap(kin, "CONTENT", &content, &len))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return sync_sieve_upload(userid, filename, last_update, content, len);
}
int sync_apply_unsieve(struct dlist *kin,
struct sync_state *sstate __attribute__((unused)))
{
const char *userid;
const char *filename;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "FILENAME", &filename))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return sync_sieve_delete(userid, filename);
}
int sync_apply_activate_sieve(struct dlist *kin,
struct sync_state *sstate __attribute((unused)))
{
const char *userid;
const char *filename;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "FILENAME", &filename))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return sync_sieve_activate(userid, filename);
}
int sync_apply_unactivate_sieve(struct dlist *kin,
struct sync_state *sstate __attribute__((unused)))
{
const char *userid;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return sync_sieve_deactivate(userid);
}
int sync_apply_seen(struct dlist *kin,
struct sync_state *sstate __attribute__((unused)))
{
int r;
struct seen *seendb = NULL;
struct seendata sd = SEENDATA_INITIALIZER;
const char *seenuids;
const char *userid;
const char *uniqueid;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "LASTREAD", &sd.lastread))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kin, "LASTUID", &sd.lastuid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "LASTCHANGE", &sd.lastchange))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "SEENUIDS", &seenuids))
return IMAP_PROTOCOL_BAD_PARAMETERS;
sd.seenuids = xstrdup(seenuids);
r = seen_open(userid, SEEN_CREATE, &seendb);
if (r) return r;
r = seen_write(seendb, uniqueid, &sd);
seen_close(&seendb);
seen_freedata(&sd);
return r;
}
EXPORTED int addmbox_cb(const mbentry_t *mbentry, void *rock)
{
strarray_t *list = (strarray_t *)rock;
strarray_append(list, mbentry->name);
return 0;
}
int sync_apply_unuser(struct dlist *kin, struct sync_state *sstate)
{
const char *userid = kin->sval;
int r = 0;
int i;
/* nothing to do if there's no userid */
if (!userid || !userid[0]) {
xsyslog(LOG_WARNING, "ignoring attempt to sync_apply_unuser() without userid",
NULL);
return 0;
}
struct mboxlock *namespacelock = user_namespacelock(userid);
/* Nuke subscriptions */
/* ignore failures here - the subs file gets deleted soon anyway */
strarray_t *list = mboxlist_sublist(userid);
for (i = 0; i < list->count; i++) {
const char *name = strarray_nth(list, i);
mboxlist_changesub(name, userid, sstate->authstate, 0, 0, 0);
}
mbentry_t *mbentry = NULL;
char *inbox = mboxname_user_mbox(userid, 0);
mboxlist_lookup_allow_all(inbox, &mbentry, NULL);
free(inbox);
strarray_truncate(list, 0);
mboxlist_usermboxtree(userid, NULL, addmbox_cb, list, 0);
/* delete in reverse so INBOX is last */
int delflags = MBOXLIST_DELETE_FORCE;
if (sstate->local_only) delflags |= MBOXLIST_DELETE_LOCALONLY;
for (i = list->count; i; i--) {
const char *name = strarray_nth(list, i-1);
r = mboxlist_deletemailbox(name, sstate->userisadmin,
sstate->userid, sstate->authstate,
NULL, delflags);
if (r) goto done;
}
if (mbentry && !(mbentry->mbtype & MBTYPE_DELETED)) {
r = user_deletedata(mbentry, 1);
}
else {
r = user_deletequotaroots(userid);
sync_log_unuser(userid);
}
done:
mboxname_release(&namespacelock);
mboxlist_entry_free(&mbentry);
strarray_free(list);
return r;
}
/* ====================================================================== */
int sync_get_sieve(struct dlist *kin, struct sync_state *sstate)
{
struct dlist *kl;
const char *userid;
const char *filename;
uint32_t size;
char *sieve;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "FILENAME", &filename))
return IMAP_PROTOCOL_BAD_PARAMETERS;
sieve = sync_sieve_read(userid, filename, &size);
if (!sieve)
return IMAP_MAILBOX_NONEXISTENT;
kl = dlist_newkvlist(NULL, "SIEVE");
dlist_setatom(kl, "USERID", userid);
dlist_setatom(kl, "FILENAME", filename);
dlist_setmap(kl, "CONTENT", sieve, size);
sync_send_response(kl, sstate->pout);
dlist_free(&kl);
free(sieve);
return 0;
}
/* NOTE - can't lock a mailbox here, because it could deadlock,
* so just pick the file out from under the hood */
int sync_get_message(struct dlist *kin, struct sync_state *sstate)
{
const char *mboxname;
const char *partition;
const char *uniqueid;
const char *guid;
uint32_t uid;
const char *fname;
struct dlist *kl;
struct message_guid tmp_guid;
struct stat sbuf;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "PARTITION", &partition))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "GUID", &guid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kin, "UID", &uid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!message_guid_decode(&tmp_guid, guid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
fname = mboxname_datapath(partition, mboxname, uniqueid, uid);
if (stat(fname, &sbuf) == -1) // try archive partition
fname = mboxname_archivepath(partition, mboxname, uniqueid, uid);
if (stat(fname, &sbuf) == -1) // try legacy data path
fname = mboxname_datapath(partition, mboxname, NULL, uid);
if (stat(fname, &sbuf) == -1) // try legacy archive partition
fname = mboxname_archivepath(partition, mboxname, NULL, uid);
if (stat(fname, &sbuf) == -1) // give up
return IMAP_MAILBOX_NONEXISTENT;
kl = dlist_setfile(NULL, "MESSAGE", partition, &tmp_guid, sbuf.st_size, fname);
sync_send_response(kl, sstate->pout);
dlist_free(&kl);
return 0;
}
int sync_apply_expunge(struct dlist *kin,
struct sync_state *sstate __attribute__((unused)))
{
const char *mboxname;
const char *uniqueid;
struct dlist *ul;
struct dlist *ui;
struct mailbox *mailbox = NULL;
int r = 0;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getlist(kin, "UID", &ul))
return IMAP_PROTOCOL_BAD_PARAMETERS;
r = mailbox_open_iwl(mboxname, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
if (r) goto done;
/* don't want to expunge the wrong mailbox! */
if (strcmp(mailbox_uniqueid(mailbox), uniqueid)) {
r = IMAP_MAILBOX_MOVED;
goto done;
}
for (ui = ul->head; ui; ui = ui->next) {
struct index_record oldrecord;
r = mailbox_find_index_record(mailbox, dlist_num(ui), &oldrecord);
if (r) continue; /* skip */
oldrecord.internal_flags |= FLAG_INTERNAL_EXPUNGED;
oldrecord.silentupdate = 1; /* so the next sync will succeed */
oldrecord.ignorelimits = 1;
r = mailbox_rewrite_index_record(mailbox, &oldrecord);
if (r) goto done;
}
done:
mailbox_close(&mailbox);
return r;
}
int sync_apply_message(struct dlist *kin,
struct sync_reserve_list *reserve_list,
struct sync_state *sstate __attribute((unused)))
{
struct sync_msgid_list *part_list;
struct dlist *ki;
struct sync_msgid *msgid;
for (ki = kin->head; ki; ki = ki->next) {
struct message_guid *guid;
const char *part;
size_t size;
const char *fname;
/* XXX - complain more? */
if (!dlist_tofile(ki, &part, &guid, (unsigned long *) &size, &fname))
continue;
part_list = sync_reserve_partlist(reserve_list, part);
msgid = sync_msgid_insert(part_list, guid);
if (!msgid->need_upload)
continue;
msgid->size = size;
if (!msgid->fname) msgid->fname = xstrdup(fname);
msgid->need_upload = 0;
part_list->toupload--;
}
return 0;
}
/* ====================================================================== */
int sync_restore_mailbox(struct dlist *kin,
struct sync_reserve_list *reserve_list,
struct sync_state *sstate)
{
/* fields from the request, all but mboxname are optional */
const char *mboxname;
const char *uniqueid = NULL;
const char *partition = NULL;
const char *mboxtype = NULL;
const char *acl = NULL;
const char *options_str = NULL;
modseq_t highestmodseq = 0;
uint32_t uidvalidity = 0;
struct dlist *kr = NULL;
struct dlist *ka = NULL;
modseq_t xconvmodseq = 0;
modseq_t createdmodseq = 0;
modseq_t foldermodseq = 0;
/* derived fields */
uint32_t options = 0;
uint32_t mbtype = 0;
struct mailbox *mailbox = NULL;
struct sync_msgid_list *part_list;
annotate_state_t *astate = NULL;
struct dlist *ki;
int has_append = 0;
int is_new_mailbox = 0;
int r;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname)) {
syslog(LOG_DEBUG, "%s: missing MBOXNAME", __func__);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
/* optional */
dlist_getatom(kin, "PARTITION", &partition);
dlist_getatom(kin, "ACL", &acl);
dlist_getatom(kin, "OPTIONS", &options_str);
dlist_getlist(kin, "RECORD", &kr);
dlist_getlist(kin, "ANNOTATIONS", &ka);
dlist_getatom(kin, "MBOXTYPE", &mboxtype);
dlist_getnum64(kin, "XCONVMODSEQ", &xconvmodseq);
/* derived */
options = sync_parse_options(options_str);
mbtype = mboxlist_string_to_mbtype(mboxtype);
/* XXX sanely handle deletedprefix mboxnames */
/* If the mboxname being restored already exists, then restored messages
* are appended to it. The UNIQUEID, HIGHESTMODSEQ, UIDVALIDITY and
* MBOXTYPE fields in the dlist, and the UID, MODSEQ and LAST_UPDATED fields
* in the restored message records, are ignored entirely.
*
* If the mboxname does not exist, we create it. If UNIQUEID, HIGHESTMODSEQ
* and UIDVALIDITY were provided, we try to preserve them, and if we can, we
* also try to preserve the UID, MODSEQ and LAST_UPDATED fields of the
* restored messages. This is useful when e.g. rebuilding a server from a
* backup, and wanting clients' IMAP states to match.
*
* If UNIQUEID, HIGHESTMODSEQ or UIDVALIDITY are not provided, we don't try
* to preserve them. We create the mailbox, but then append the restored
* messages to it as if it already existed (new UID et al).
*/
/* open/create mailbox */
r = mailbox_open_iwl(mboxname, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
syslog(LOG_DEBUG, "%s: mailbox_open_iwl %s: %s",
__func__, mboxname, error_message(r));
if (r == IMAP_MAILBOX_NONEXISTENT) {
dlist_getatom(kin, "UNIQUEID", &uniqueid);
dlist_getnum64(kin, "HIGHESTMODSEQ", &highestmodseq);
dlist_getnum32(kin, "UIDVALIDITY", &uidvalidity);
dlist_getnum64(kin, "CREATEDMODSEQ", &createdmodseq);
dlist_getnum64(kin, "FOLDERMODSEQ", &foldermodseq);
/* if any of these three weren't set, disregard the others too */
if (!uniqueid || !highestmodseq || !uidvalidity) {
uniqueid = NULL;
highestmodseq = 0;
uidvalidity = 0;
}
struct mboxlock *namespacelock = mboxname_usernamespacelock(mboxname);
// try again under lock
r = mailbox_open_iwl(mboxname, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
if (r == IMAP_MAILBOX_NONEXISTENT) { // did we win a race?
mbentry_t mbentry = MBENTRY_INITIALIZER;
mbentry.name = (char *) mboxname;
mbentry.partition = (char *) partition;
mbentry.uniqueid = (char *) uniqueid;
mbentry.acl = (char *) acl;
mbentry.mbtype = mbtype;
mbentry.uidvalidity = uidvalidity;
mbentry.createdmodseq = createdmodseq;
mbentry.foldermodseq = foldermodseq;
unsigned flags = MBOXLIST_CREATE_SYNC;
if (sstate->local_only) flags |= MBOXLIST_CREATE_LOCALONLY;
r = mboxlist_createmailbox(&mbentry, options, highestmodseq,
1/*isadmin*/,
sstate->userid, sstate->authstate,
flags, &mailbox);
syslog(LOG_DEBUG, "%s: mboxlist_createmailbox %s: %s",
__func__, mboxname, error_message(r));
is_new_mailbox = 1;
}
mboxname_release(&namespacelock);
}
if (r) {
syslog(LOG_ERR, "Failed to open mailbox %s to restore: %s",
mboxname, error_message(r));
return r;
}
/* XXX what if we've opened a deleted mailbox? */
/* XXX verify mailbox is suitable? */
/* make sure mailbox types match */
if (mbtypes_sync(mailbox_mbtype(mailbox)) != mbtype) {
syslog(LOG_ERR, "restore mailbox %s: mbtype mismatch (%d, %d)",
mailbox_name(mailbox), mailbox_mbtype(mailbox), mbtype);
r = IMAP_MAILBOX_BADTYPE;
goto bail;
}
part_list = sync_reserve_partlist(reserve_list, mailbox_partition(mailbox));
/* hold the annotate state open */
r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
syslog(LOG_DEBUG, "%s: mailbox_get_annotate_state %s: %s",
__func__, mailbox_name(mailbox), error_message(r));
if (r) goto bail;
/* and make it hold a transaction open */
annotate_state_begin(astate);
/* XXX do we need to hold the conversation state open? */
/* restore mailbox annotations */
if (ka) {
struct sync_annot_list *restore_annots = NULL;
struct sync_annot_list *mailbox_annots = NULL;
r = decode_annotations(ka, &restore_annots, mailbox, NULL);
if (!r) r = read_annotations(mailbox, NULL, &mailbox_annots, 0, 0);
if (!r) r = apply_annotations(mailbox, NULL,
mailbox_annots, restore_annots,
!is_new_mailbox, NULL);
if (r)
syslog(LOG_WARNING,
"restore mailbox %s: unable to apply mailbox annotations: %s",
mailbox_name(mailbox), error_message(r));
/* keep going on annotations failure*/
r = 0;
sync_annot_list_free(&restore_annots);
sync_annot_list_free(&mailbox_annots);
}
/* n.b. undocumented assumption here and in sync_apply_mailbox
* that records will be provided sorted by ascending uid */
for (ki = kr->head; ki; ki = ki->next) {
struct sync_annot_list *annots = NULL;
struct index_record record = {0};
/* XXX skip if the guid is already in this folder? */
r = parse_upload(ki, mailbox, &record, &annots);
syslog(LOG_DEBUG, "%s: parse_upload %s: %s",
__func__, mailbox_name(mailbox), error_message(r));
if (r) goto bail;
/* generate a uid if we can't reuse a provided one */
if (!uidvalidity || record.uid <= mailbox->i.last_uid)
record.uid = mailbox->i.last_uid + 1;
/* reuse a provided modseq/last_updated if safe */
if (highestmodseq && record.modseq && record.modseq <= mailbox->i.highestmodseq)
record.silentupdate = 1;
r = sync_append_copyfile(mailbox, &record, annots, part_list);
has_append = 1;
sync_annot_list_free(&annots);
if (r) goto bail;
}
r = mailbox_commit(mailbox);
syslog(LOG_DEBUG, "%s: mailbox_commit %s: %s",
__func__, mailbox_name(mailbox), error_message(r));
if (r) {
syslog(LOG_ERR, "%s: mailbox_commit(%s): %s",
__func__, mailbox_name(mailbox), error_message(r));
}
if (!r && has_append)
sync_log_append(mailbox_name(mailbox));
mailbox_close(&mailbox);
return r;
bail:
mailbox_abort(mailbox);
mailbox_close(&mailbox);
return r;
}
/* ====================================================================== */
static const char *sync_response(int r)
{
const char *resp;
switch (r) {
case 0:
resp = "OK success";
break;
case IMAP_INVALID_USER:
resp = "NO IMAP_INVALID_USER No Such User";
break;
case IMAP_MAILBOX_NONEXISTENT:
resp = "NO IMAP_MAILBOX_NONEXISTENT No Such Mailbox";
break;
case IMAP_MAILBOX_LOCKED:
resp = "NO IMAP_MAILBOX_LOCKED Mailbox locked";
break;
case IMAP_MAILBOX_MOVED:
resp = "NO IMAP_MAILBOX_MOVED Mailbox exists with another name or uniqueid";
break;
case IMAP_MAILBOX_NOTSUPPORTED:
resp = "NO IMAP_MAILBOX_NOTSUPPORTED Operation is not supported on mailbox";
break;
case IMAP_SYNC_CHECKSUM:
resp = "NO IMAP_SYNC_CHECKSUM Checksum Failure";
break;
case IMAP_SYNC_CHANGED:
resp = "NO IMAP_SYNC_CHANGED Changed since last sync";
break;
case IMAP_SYNC_BADSIEVE:
resp = "NO IMAP_SYNC_BADSIEVE Sieve script compilation failure";
break;
case IMAP_PROTOCOL_ERROR:
resp = "NO IMAP_PROTOCOL_ERROR Protocol error";
break;
case IMAP_PROTOCOL_BAD_PARAMETERS:
resp = "NO IMAP_PROTOCOL_BAD_PARAMETERS";
// XXX resp = "NO IMAP_PROTOCOL_BAD_PARAMETERS near %s\r\n", dlist_lastkey());
break;
default:
resp = "NO Unknown error";
}
return resp;
}
/* ======================= client-side sync =========================== */
/* Routines relevant to reserve operation */
/* Find the messages that we will want to upload from this mailbox,
* flag messages that are already available at the server end */
int sync_find_reserve_messages(struct mailbox *mailbox,
uint32_t fromuid,
uint32_t touid,
struct sync_msgid_list *part_list)
{
struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, ITER_SKIP_UNLINKED);
mailbox_iter_startuid(iter, fromuid+1); /* only read new records */
const message_t *msg;
while ((msg = mailbox_iter_step(iter))) {
const struct index_record *record = msg_record(msg);
sync_msgid_insert(part_list, &record->guid);
if (record->uid >= touid) break;
}
mailbox_iter_done(&iter);
return 0;
}
static int calculate_intermediate_state(struct mailbox *mailbox,
modseq_t frommodseq,
uint32_t fromuid,
uint32_t batchsize,
uint32_t *touidp,
modseq_t *tomodseqp)
{
modseq_t tomodseq = mailbox->i.highestmodseq;
uint32_t touid = fromuid;
uint32_t seen = 0;
struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, ITER_SKIP_UNLINKED);
mailbox_iter_startuid(iter, fromuid+1); /* only read new records */
const message_t *msg;
while ((msg = mailbox_iter_step(iter))) {
const struct index_record *record = msg_record(msg);
if (seen < batchsize) {
touid = record->uid;
}
else if (record->modseq <= tomodseq)
tomodseq = record->modseq - 1;
seen++;
}
mailbox_iter_done(&iter);
/* no need to batch if there are fewer than batchsize records */
if (seen <= batchsize)
return 0;
/* must have found at least one record past the end to do a partial batch,
* and we need a highestmodseq at least one less than that records so that
* it can successfully sync */
if (tomodseq > frommodseq && tomodseq < mailbox->i.highestmodseq) {
*tomodseqp = tomodseq;
*touidp = touid;
return 1;
}
/* can't find an intermediate modseq */
return 0;
}
static int find_reserve_all(struct sync_name_list *mboxname_list,
const char *topart,
struct sync_folder_list *master_folders,
struct sync_folder_list *replica_folders,
struct sync_reserve_list *reserve_list,
uint32_t batchsize)
{
struct sync_name *mbox;
struct sync_folder *rfolder;
struct sync_msgid_list *part_list;
struct mailbox *mailbox = NULL;
int r = 0;
/* Find messages we want to upload that are available on server */
for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
mbentry_t *mbentry = NULL;
if (mboxlist_lookup_allow_all(mbox->name, &mbentry, NULL))
continue;
if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
struct synccrcs synccrcs = {0, 0};
sync_folder_list_add(master_folders, mbentry->uniqueid, mbentry->name,
mbentry->mbtype,
mbentry->partition, mbentry->acl, 0,
mbentry->uidvalidity, 0,
0, synccrcs,
0, 0,
0, 0,
NULL, 0,
0, mbentry->foldermodseq, 0);
mboxlist_entry_free(&mbentry);
continue;
}
mboxlist_entry_free(&mbentry);
r = mailbox_open_irl(mbox->name, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
/* Quietly skip over folders which have been deleted since we
started working (but record fact in case caller cares) */
if (r == IMAP_MAILBOX_NONEXISTENT) {
r = 0;
continue;
}
if (r) {
xsyslog(LOG_ERR, "IOERROR: mailbox open failed",
"mboxname=<%s> error=<%s>",
mbox->name, error_message(r));
goto bail;
}
modseq_t xconvmodseq = 0;
if (mailbox_has_conversations(mailbox)) {
r = mailbox_get_xconvmodseq(mailbox, &xconvmodseq);
if (r) {
xsyslog(LOG_ERR, "IOERROR: get xconvmodseq failed",
"mboxname=<%s> error=<%s>",
mbox->name, error_message(r));
goto bail;
}
}
modseq_t raclmodseq = mboxname_readraclmodseq(mbox->name);
/* mailbox is open from here, no exiting without closing it! */
rfolder = sync_folder_lookup(replica_folders, mailbox_uniqueid(mailbox));
uint32_t fromuid = rfolder ? rfolder->last_uid : 0;
uint32_t touid = mailbox->i.last_uid;
modseq_t tomodseq = mailbox->i.highestmodseq;
int ispartial = 0;
if (batchsize && touid - fromuid > batchsize) {
/* see if we actually need to calculate an intermediate state */
modseq_t frommodseq = rfolder ? rfolder->highestmodseq : 0;
/* is there an intermediate modseq available and enough records to make a batch? */
ispartial = calculate_intermediate_state(mailbox, frommodseq, fromuid,
batchsize, &touid, &tomodseq);
if (ispartial) {
syslog(LOG_DEBUG, "doing partial sync: %s (%u/%u/%u) (%llu/%llu/%llu)",
mailbox_name(mailbox), fromuid, touid, mailbox->i.last_uid,
frommodseq, tomodseq, mailbox->i.highestmodseq);
}
}
sync_folder_list_add(master_folders, mailbox_uniqueid(mailbox), mailbox_name(mailbox),
mailbox_mbtype(mailbox),
mailbox_partition(mailbox), mailbox_acl(mailbox), mailbox->i.options,
mailbox->i.uidvalidity, touid,
tomodseq, mailbox->i.synccrcs,
mailbox->i.recentuid, mailbox->i.recenttime,
mailbox->i.pop3_last_login,
mailbox->i.pop3_show_after, NULL, xconvmodseq,
raclmodseq, mailbox_foldermodseq(mailbox), ispartial);
part_list = sync_reserve_partlist(reserve_list, topart ? topart : mailbox_partition(mailbox));
sync_find_reserve_messages(mailbox, fromuid, touid, part_list);
mailbox_close(&mailbox);
}
bail:
mailbox_close(&mailbox);
return r;
}
static int mark_missing (struct dlist *kin,
struct sync_msgid_list *part_list)
{
struct dlist *kl = kin->head;
struct dlist *ki;
struct message_guid tmp_guid;
struct sync_msgid *msgid;
/* no missing at all, good */
if (!kl) return 0;
if (strcmp(kl->name, "MISSING")) {
xsyslog(LOG_ERR, "SYNCERROR: Illegal response to RESERVE",
"name=<%s>", kl->name);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
/* unmark each missing item */
for (ki = kl->head; ki; ki = ki->next) {
if (!message_guid_decode(&tmp_guid, ki->sval)) {
xsyslog(LOG_ERR, "SYNCERROR: reserve: failed to parse GUID",
"sval=<%s>", ki->sval);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
/* afraid we will need this after all */
msgid = sync_msgid_lookup(part_list, &tmp_guid);
if (msgid && !msgid->need_upload) {
msgid->need_upload = 1;
part_list->toupload++;
}
}
return 0;
}
int sync_reserve_partition(struct sync_client_state *sync_cs, char *partition,
struct sync_folder_list *replica_folders,
struct sync_msgid_list *part_list)
{
const char *cmd = "RESERVE";
struct sync_msgid *msgid = part_list->head;
struct sync_folder *folder;
struct dlist *kl = NULL;
struct dlist *kin = NULL;
struct dlist *ki;
int r = 0;
if (!replica_folders->head)
return 0; /* nowhere to reserve */
while (msgid) {
int n = 0;
if (!part_list->toupload)
goto done; /* got them all */
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "PARTITION", partition);
ki = dlist_newlist(kl, "MBOXNAME");
for (folder = replica_folders->head; folder; folder = folder->next)
dlist_setatom(ki, "MBOXNAME", folder->name);
ki = dlist_newlist(kl, "GUID");
for (; msgid; msgid = msgid->next) {
if (!msgid->need_upload) continue;
if (n > 8192) break;
dlist_setatom(ki, "GUID", message_guid_encode(&msgid->guid));
/* we will re-add the "need upload" if we get a MISSING response */
msgid->need_upload = 0;
part_list->toupload--;
n++;
}
sync_send_apply(kl, sync_cs->backend->out);
r = sync_parse_response(cmd, sync_cs->backend->in, &kin);
if (r) goto done;
r = mark_missing(kin, part_list);
if (r) goto done;
dlist_free(&kl);
dlist_free(&kin);
}
done:
dlist_free(&kl);
dlist_free(&kin);
return r;
}
static int reserve_messages(struct sync_client_state *sync_cs,
struct sync_name_list *mboxname_list,
const char *topart,
struct sync_folder_list *master_folders,
struct sync_folder_list *replica_folders,
struct sync_reserve_list *reserve_list,
uint32_t batchsize)
{
struct sync_reserve *reserve;
int r;
r = find_reserve_all(mboxname_list, topart, master_folders,
replica_folders, reserve_list, batchsize);
if (r) return r;
for (reserve = reserve_list->head; reserve; reserve = reserve->next) {
r = sync_reserve_partition(sync_cs, reserve->part,
replica_folders, reserve->list);
if (r) return r;
}
return 0;
}
static struct db *sync_getcachedb(struct sync_client_state *sync_cs)
{
if (sync_cs->cachedb) return sync_cs->cachedb;
const char *dbtype = config_getstring(IMAPOPT_SYNC_CACHE_DB);
if (!dbtype) return NULL;
const char *dbpath = sync_get_config(sync_cs->channel, "sync_cache_db_path");
if (!dbpath) return NULL;
int flags = CYRUSDB_CREATE;
int r = cyrusdb_open(dbtype, dbpath, flags, &sync_cs->cachedb);
if (r) {
xsyslog(LOG_ERR, "DBERROR: failed to open sync cache db",
"dbpath=<%s> error=<%s>",
dbpath, cyrusdb_strerror(r));
}
return sync_cs->cachedb;
}
static int sync_readcache(struct sync_client_state *sync_cs, const char *mboxname,
struct dlist **klp)
{
struct db *db = sync_getcachedb(sync_cs);
if (!db) return 0;
const char *base;
size_t len;
int r = cyrusdb_fetch(db, mboxname, strlen(mboxname), &base, &len, /*tid*/NULL);
if (r) return r;
dlist_parsemap(klp, 0, 0, base, len);
// we need the name so the parser can parse it
if (*klp) (*klp)->name = xstrdup("MAILBOX");
return 0;
}
// NOTE: this is destructive of kl - it removes the RECORD section!
// this is always safe because of where we call it
static int sync_cache(struct sync_client_state *sync_cs, const char *mboxname,
struct dlist *kl)
{
struct db *db = sync_getcachedb(sync_cs);
if (!db) return 0;
struct dlist *ritem = dlist_getchild(kl, "RECORD");
if (ritem) {
dlist_unstitch(kl, ritem);
dlist_free(&ritem);
}
struct buf buf = BUF_INITIALIZER;
dlist_printbuf(kl, 0, &buf);
int r = cyrusdb_store(db, mboxname, strlen(mboxname),
buf_base(&buf), buf_len(&buf), /*tid*/NULL);
buf_free(&buf);
return r;
}
static void sync_uncache(struct sync_client_state *sync_cs, const char *mboxname)
{
struct db *db = sync_getcachedb(sync_cs);
if (!db) return;
cyrusdb_delete(db, mboxname, strlen(mboxname), /*tid*/NULL, /*force*/1);
}
static int sync_kl_parse(struct dlist *kin,
struct sync_folder_list *folder_list,
struct sync_name_list *sub_list,
struct sync_sieve_list *sieve_list,
struct sync_seen_list *seen_list,
struct sync_quota_list *quota_list)
{
struct dlist *kl;
for (kl = kin->head; kl; kl = kl->next) {
if (!strcmp(kl->name, "SIEVE")) {
struct message_guid guid;
const char *filename = NULL;
const char *guidstr = NULL;
time_t modtime = 0;
uint32_t active = 0;
if (!sieve_list) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kl, "FILENAME", &filename)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kl, "LAST_UPDATE", &modtime)) return IMAP_PROTOCOL_BAD_PARAMETERS;
dlist_getatom(kl, "GUID", &guidstr); /* optional */
if (guidstr) {
if (!message_guid_decode(&guid, guidstr)) return IMAP_PROTOCOL_BAD_PARAMETERS;
}
else {
message_guid_set_null(&guid);
}
dlist_getnum32(kl, "ISACTIVE", &active); /* optional */
sync_sieve_list_add(sieve_list, filename, modtime, &guid, active);
}
else if (!strcmp(kl->name, "QUOTA")) {
const char *root = NULL;
struct sync_quota *sq;
if (!quota_list) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kl, "ROOT", &root)) return IMAP_PROTOCOL_BAD_PARAMETERS;
sq = sync_quota_list_add(quota_list, root);
sync_decode_quota_limits(kl, sq->limits);
}
else if (!strcmp(kl->name, "LSUB")) {
struct dlist *i;
if (!sub_list) return IMAP_PROTOCOL_BAD_PARAMETERS;
for (i = kl->head; i; i = i->next) {
sync_name_list_add(sub_list, i->sval);
}
}
else if (!strcmp(kl->name, "SEEN")) {
const char *uniqueid = NULL;
time_t lastread = 0;
uint32_t lastuid = 0;
time_t lastchange = 0;
const char *seenuids = NULL;
if (!seen_list) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kl, "UNIQUEID", &uniqueid)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kl, "LASTREAD", &lastread)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kl, "LASTUID", &lastuid)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kl, "LASTCHANGE", &lastchange)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kl, "SEENUIDS", &seenuids)) return IMAP_PROTOCOL_BAD_PARAMETERS;
sync_seen_list_add(seen_list, uniqueid, lastread,
lastuid, lastchange, seenuids);
}
else if (!strcmp(kl->name, "MAILBOX")) {
const char *uniqueid = NULL;
const char *mboxname = NULL;
const char *mboxtype = NULL;
const char *part = NULL;
const char *acl = NULL;
const char *options = NULL;
modseq_t highestmodseq = 0;
uint32_t uidvalidity = 0;
uint32_t last_uid = 0;
struct synccrcs synccrcs = { 0, 0 };
uint32_t recentuid = 0;
time_t recenttime = 0;
time_t pop3_last_login = 0;
time_t pop3_show_after = 0;
struct dlist *al = NULL;
struct sync_annot_list *annots = NULL;
modseq_t xconvmodseq = 0;
modseq_t raclmodseq = 0;
modseq_t foldermodseq = 0;
if (!folder_list) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kl, "UNIQUEID", &uniqueid)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kl, "MBOXNAME", &mboxname)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kl, "PARTITION", &part)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kl, "ACL", &acl)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kl, "OPTIONS", &options)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum64(kl, "HIGHESTMODSEQ", &highestmodseq)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kl, "UIDVALIDITY", &uidvalidity)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kl, "LAST_UID", &last_uid)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum32(kl, "RECENTUID", &recentuid)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kl, "RECENTTIME", &recenttime)) return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kl, "POP3_LAST_LOGIN", &pop3_last_login)) return IMAP_PROTOCOL_BAD_PARAMETERS;
/* optional */
dlist_getdate(kl, "POP3_SHOW_AFTER", &pop3_show_after);
dlist_getatom(kl, "MBOXTYPE", &mboxtype);
dlist_getnum32(kl, "SYNC_CRC", &synccrcs.basic);
dlist_getnum32(kl, "SYNC_CRC_ANNOT", &synccrcs.annot);
dlist_getnum64(kl, "XCONVMODSEQ", &xconvmodseq);
dlist_getnum64(kl, "RACLMODSEQ", &raclmodseq);
dlist_getnum64(kl, "FOLDERMODSEQ", &foldermodseq);
if (dlist_getlist(kl, "ANNOTATIONS", &al))
decode_annotations(al, &annots, NULL, NULL);
sync_folder_list_add(folder_list, uniqueid, mboxname,
mboxlist_string_to_mbtype(mboxtype),
part, acl,
sync_parse_options(options),
uidvalidity, last_uid,
highestmodseq, synccrcs,
recentuid, recenttime,
pop3_last_login,
pop3_show_after, annots,
xconvmodseq, raclmodseq,
foldermodseq, /*ispartial*/0);
}
else {
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
}
return 0;
}
int sync_response_parse(struct sync_client_state *sync_cs, const char *cmd,
struct sync_folder_list *folder_list,
struct sync_name_list *sub_list,
struct sync_sieve_list *sieve_list,
struct sync_seen_list *seen_list,
struct sync_quota_list *quota_list)
{
struct dlist *kin = NULL;
int r;
r = sync_parse_response(cmd, sync_cs->backend->in, &kin);
/* Unpleasant: translate remote access error into "please reset me" */
if (r == IMAP_MAILBOX_NONEXISTENT)
return 0;
if (r) return r;
r = sync_kl_parse(kin, folder_list, sub_list,
sieve_list, seen_list, quota_list);
if (r)
xsyslog(LOG_ERR, "SYNCERROR: invalid response",
"command=<%s> response=<%s>",
cmd, dlist_lastkey());
else {
// do we have mailboxes to cache?
struct dlist *kl = NULL;
for (kl = kin->head; kl; kl = kl->next) {
if (strcmp(kl->name, "MAILBOX")) continue;
const char *mboxname = NULL;
if (!dlist_getatom(kl, "MBOXNAME", &mboxname)) continue;
sync_cache(sync_cs, mboxname, kl);
}
}
dlist_free(&kin);
return r;
}
static int folder_rename(struct sync_client_state *sync_cs,
const char *oldname, const char *newname,
const char *partition, unsigned uidvalidity)
{
const char *cmd = (sync_cs->flags & SYNC_FLAG_LOCALONLY) ? "LOCAL_RENAME" : "RENAME";
struct dlist *kl;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s -> %s (%s)\n", cmd, oldname, newname, partition);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s -> %s (%s)", cmd, oldname, newname, partition);
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "OLDMBOXNAME", oldname);
dlist_setatom(kl, "NEWMBOXNAME", newname);
dlist_setatom(kl, "PARTITION", partition);
dlist_setnum32(kl, "UIDVALIDITY", uidvalidity);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
int r = sync_parse_response(cmd, sync_cs->backend->in, NULL);
// this means that newname won't be cached, but we'll cache it next sync
sync_uncache(sync_cs, oldname);
return r;
}
int sync_do_folder_delete(struct sync_client_state *sync_cs, const char *mboxname)
{
const char *cmd =
(sync_cs->flags & SYNC_FLAG_LOCALONLY) ? "LOCAL_UNMAILBOX" :"UNMAILBOX";
struct dlist *kl;
int r;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s\n", cmd, mboxname);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s", cmd, mboxname);
kl = dlist_setatom(NULL, cmd, mboxname);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
r = sync_parse_response(cmd, sync_cs->backend->in, NULL);
if (r == IMAP_MAILBOX_NONEXISTENT)
r = 0;
sync_uncache(sync_cs, mboxname);
return r;
}
int sync_set_sub(struct sync_client_state *sync_cs,
const char *userid, const char *mboxname, int add)
{
const char *cmd = add ? "SUB" : "UNSUB";
struct dlist *kl;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s %s\n", cmd, userid, mboxname);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s %s", cmd, userid, mboxname);
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "USERID", userid);
dlist_setatom(kl, "MBOXNAME", mboxname);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
static int folder_setannotation(struct sync_client_state *sync_cs,
const char *mboxname, const char *entry,
const char *userid, const struct buf *value)
{
const char *cmd = "ANNOTATION";
struct dlist *kl;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s %s %s\n", cmd, mboxname, entry, userid);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s %s %s", cmd, mboxname, entry, userid);
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "MBOXNAME", mboxname);
dlist_setatom(kl, "ENTRY", entry);
dlist_setatom(kl, "USERID", userid);
dlist_setmap(kl, "VALUE", value->s, value->len);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
static int folder_unannotation(struct sync_client_state *sync_cs,
const char *mboxname, const char *entry,
const char *userid)
{
const char *cmd = "UNANNOTATION";
struct dlist *kl;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s %s %s\n", cmd, mboxname, entry, userid);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s %s %s", cmd, mboxname, entry, userid);
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "MBOXNAME", mboxname);
dlist_setatom(kl, "ENTRY", entry);
dlist_setatom(kl, "USERID", userid);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
/* ====================================================================== */
static int sieve_upload(struct sync_client_state *sync_cs,
const char *userid, const char *filename,
unsigned long last_update)
{
const char *cmd = "SIEVE";
struct dlist *kl;
char *sieve;
uint32_t size;
sieve = sync_sieve_read(userid, filename, &size);
if (!sieve) return IMAP_IOERROR;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s %s\n", cmd, userid, filename);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s %s", cmd, userid, filename);
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "USERID", userid);
dlist_setatom(kl, "FILENAME", filename);
dlist_setdate(kl, "LAST_UPDATE", last_update);
dlist_setmap(kl, "CONTENT", sieve, size);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
free(sieve);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
static int sieve_delete(struct sync_client_state *sync_cs,
const char *userid, const char *filename)
{
const char *cmd = "UNSIEVE";
struct dlist *kl;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s %s\n", cmd, userid, filename);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s %s", cmd, userid, filename);
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "USERID", userid);
dlist_setatom(kl, "FILENAME", filename);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
static int sieve_activate(struct sync_client_state *sync_cs,
const char *userid, const char *filename)
{
const char *cmd = "ACTIVATE_SIEVE";
struct dlist *kl;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s %s\n", cmd, userid, filename);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s %s", cmd, userid, filename);
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "USERID", userid);
dlist_setatom(kl, "FILENAME", filename);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
static int sieve_deactivate(struct sync_client_state *sync_cs,
const char *userid)
{
const char *cmd = "UNACTIVATE_SIEVE";
struct dlist *kl;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s\n", cmd, userid);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s", cmd, userid);
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "USERID", userid);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
/* ====================================================================== */
static int delete_quota(struct sync_client_state *sync_cs, const char *root)
{
const char *cmd = "UNQUOTA";
struct dlist *kl;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s\n", cmd, root);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s", cmd, root);
kl = dlist_setatom(NULL, cmd, root);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
static int update_quota_work(struct sync_client_state *sync_cs,
struct quota *client,
struct sync_quota *server)
{
const char *cmd = "QUOTA";
struct dlist *kl;
int r;
r = quota_read(client, NULL, 0);
/* disappeared? Delete it*/
if (r == IMAP_QUOTAROOT_NONEXISTENT)
return delete_quota(sync_cs, client->root);
if (r) {
syslog(LOG_INFO, "Warning: failed to read quotaroot %s: %s",
client->root, error_message(r));
return r;
}
if (server) {
int changed = 0;
int res;
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
if (client->limits[res] != server->limits[res])
changed++;
}
if (!changed)
return 0;
}
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("%s %s\n", cmd, client->root);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s", cmd, client->root);
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "ROOT", client->root);
sync_encode_quota_limits(kl, client->limits);
dlist_setnum64(kl, "MODSEQ", client->modseq);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
static int copy_local(struct mailbox *mailbox, unsigned uid)
{
char *oldfname, *newfname;
struct index_record newrecord;
struct index_record oldrecord;
int r;
annotate_state_t *astate = NULL;
if (mailbox_find_index_record(mailbox, uid, &oldrecord)) {
/* not finding the record is an error! (should never happen) */
xsyslog(LOG_ERR, "IOERROR: couldn't find index record",
"uid=<%u>",
uid);
return IMAP_MAILBOX_NONEXISTENT;
}
/* create the new record as a clone of the old record */
newrecord = oldrecord;
newrecord.uid = mailbox->i.last_uid + 1;
/* copy the file in to place */
oldfname = xstrdup(mailbox_record_fname(mailbox, &oldrecord));
newfname = xstrdup(mailbox_record_fname(mailbox, &newrecord));
r = mailbox_copyfile(oldfname, newfname, 0);
free(oldfname);
free(newfname);
if (r) return r;
/* append the new record */
r = mailbox_append_index_record(mailbox, &newrecord);
if (r) return r;
/* ensure we have an astate connected to the destination
* mailbox, so that the annotation txn will be committed
* when we close the mailbox */
r = mailbox_get_annotate_state(mailbox, newrecord.uid, &astate);
if (r) return r;
/* Copy across any per-message annotations */
r = annotate_msg_copy(mailbox, oldrecord.uid,
mailbox, newrecord.uid,
NULL);
if (r) return r;
/* and expunge the old record */
oldrecord.internal_flags |= FLAG_INTERNAL_EXPUNGED;
r = mailbox_rewrite_index_record(mailbox, &oldrecord);
/* done - return */
return r;
}
static int fetch_file(struct sync_client_state *sync_cs,
struct mailbox *mailbox, unsigned uid,
const struct index_record *rp,
struct sync_msgid_list *part_list)
{
const char *cmd = "FETCH";
struct dlist *kin = NULL;
struct dlist *kl;
int r = 0;
struct sync_msgid *msgid;
struct message_guid *guid = NULL;
size_t size = 0;
const char *fname = NULL;
msgid = sync_msgid_lookup(part_list, &rp->guid);
/* already reserved? great */
if (msgid && msgid->fname) {
syslog(LOG_NOTICE, "trying to get already uploaded %u: %s", uid, message_guid_encode(&rp->guid));
return 0;
}
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "MBOXNAME", mailbox_name(mailbox));
dlist_setatom(kl, "PARTITION", mailbox_partition(mailbox));
dlist_setatom(kl, "UNIQUEID", mailbox_uniqueid(mailbox));
dlist_setguid(kl, "GUID", &rp->guid);
dlist_setnum32(kl, "UID", uid);
sync_send_lookup(kl, sync_cs->backend->out);
dlist_free(&kl);
r = sync_parse_response(cmd, sync_cs->backend->in, &kin);
if (r) {
xsyslog(LOG_ERR, "IOERROR: parse response failed",
"error=<%s>",
error_message(r));
return r;
}
if (!dlist_tofile(kin->head, NULL, &guid, (unsigned long *) &size, &fname)) {
r = IMAP_MAILBOX_NONEXISTENT;
xsyslog(LOG_ERR, "IOERROR: dlist_tofile failed",
"error=<%s>",
error_message(r));
goto done;
}
/* well, we can copy it back or we can re-reserve... */
if (message_guid_equal(guid, &rp->guid) && (size == rp->size)) {
msgid = sync_msgid_insert(part_list, &rp->guid);
msgid->need_upload = 1;
msgid->size = size;
if (!msgid->fname) msgid->fname = xstrdup(fname);
}
else {
r = IMAP_MAILBOX_NONEXISTENT;
xsyslog(LOG_ERR, "IOERROR: GUID MISMATCH",
"error=<%s>",
error_message(r));
r = IMAP_IOERROR;
}
done:
dlist_free(&kin);
return r;
}
static int copy_remote(struct mailbox *mailbox, uint32_t uid,
struct dlist *kr, struct sync_msgid_list *part_list)
{
struct index_record record;
struct dlist *ki;
int r;
struct sync_annot_list *annots = NULL;
for (ki = kr->head; ki; ki = ki->next) {
r = parse_upload(ki, mailbox, &record, &annots);
if (r) {
xsyslog(LOG_ERR, "IOERROR: parse_upload failed",
"uid=<%u>",
uid);
return r;
}
if (record.uid == uid) {
/* choose the destination UID */
record.uid = mailbox->i.last_uid + 1;
/* append the file */
r = sync_append_copyfile(mailbox, &record, annots, part_list);
sync_annot_list_free(&annots);
return r;
}
sync_annot_list_free(&annots);
}
/* not finding the record is an error! (should never happen) */
xsyslog(LOG_ERR, "IOERROR: couldn't find index record",
"uid=<%u>",
uid);
return IMAP_MAILBOX_NONEXISTENT;
}
static int copyback_one_record(struct sync_client_state *sync_cs,
struct mailbox *mailbox,
struct index_record *rp,
const struct sync_annot_list *annots,
struct dlist *kaction,
struct sync_msgid_list *part_list)
{
int r;
/* don't want to copy back expunged records! */
if (rp->internal_flags & FLAG_INTERNAL_EXPUNGED)
return 0;
/* if the UID is lower than master's last_uid,
* we'll need to renumber */
if (rp->uid <= mailbox->i.last_uid) {
/* Ok, now we need to check if it's just really stale
* (has been cleaned out locally) or an error.
* In the error case we copy back, stale
* we remove from the replica */
if (rp->modseq < mailbox->i.deletedmodseq) {
if (kaction)
dlist_setnum32(kaction, "EXPUNGE", rp->uid);
}
else {
r = fetch_file(sync_cs, mailbox, rp->uid, rp, part_list);
if (r) return r;
if (kaction)
dlist_setnum32(kaction, "COPYBACK", rp->uid);
}
}
/* otherwise we can pull it in with the same UID,
* which saves causing renumbering on the replica
* end, so is preferable */
else {
/* grab the file */
r = fetch_file(sync_cs, mailbox, rp->uid, rp, part_list);
if (r) return r;
/* make sure we're actually making changes now */
if (!kaction) return 0;
/* append the file */
r = sync_append_copyfile(mailbox, rp, annots, part_list);
if (r) return r;
}
return 0;
}
static int renumber_one_record(const struct index_record *mp,
struct dlist *kaction)
{
/* don't want to renumber expunged records */
if (mp->internal_flags & FLAG_INTERNAL_EXPUNGED)
return 0;
if (kaction)
dlist_setnum32(kaction, "RENUMBER", mp->uid);
return 0;
}
static const char *make_flags(struct mailbox *mailbox, struct index_record *record)
{
static char buf[4096];
const char *sep = "";
int flag;
if (record->system_flags & FLAG_DELETED) {
snprintf(buf, 4096, "%s\\Deleted", sep);
sep = " ";
}
if (record->system_flags & FLAG_ANSWERED) {
snprintf(buf, 4096, "%s\\Answered", sep);
sep = " ";
}
if (record->system_flags & FLAG_FLAGGED) {
snprintf(buf, 4096, "%s\\Flagged", sep);
sep = " ";
}
if (record->system_flags & FLAG_DRAFT) {
snprintf(buf, 4096, "%s\\Draft", sep);
sep = " ";
}
if (record->internal_flags & FLAG_INTERNAL_EXPUNGED) {
snprintf(buf, 4096, "%s\\Expunged", sep);
sep = " ";
}
if (record->system_flags & FLAG_SEEN) {
snprintf(buf, 4096, "%s\\Seen", sep);
sep = " ";
}
/* print user flags in mailbox order */
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if (!mailbox->flagname[flag])
continue;
if (!(record->user_flags[flag/32] & (1<<(flag&31))))
continue;
snprintf(buf, 4096, "%s%s", sep, mailbox->flagname[flag]);
sep = " ";
}
return buf;
}
static void log_record(const char *name, struct mailbox *mailbox,
struct index_record *record)
{
syslog(LOG_NOTICE, "SYNCNOTICE: %s uid:%u modseq:" MODSEQ_FMT " "
"last_updated:" TIME_T_FMT " internaldate:" TIME_T_FMT " flags:(%s) cid:" CONV_FMT,
name, record->uid, record->modseq,
record->last_updated, record->internaldate,
make_flags(mailbox, record), record->cid);
}
static void log_mismatch(const char *reason, struct mailbox *mailbox,
struct index_record *mp,
struct index_record *rp)
{
syslog(LOG_NOTICE, "SYNCNOTICE: record mismatch with replica: %s %s",
mailbox_name(mailbox), reason);
log_record("master", mailbox, mp);
log_record("replica", mailbox, rp);
}
static int compare_one_record(struct sync_client_state *sync_cs,
struct mailbox *mailbox,
struct index_record *mp,
struct index_record *rp,
const struct sync_annot_list *mannots,
const struct sync_annot_list *rannots,
struct dlist *kaction,
struct sync_msgid_list *part_list)
{
int i;
int r;
/* if both ends are expunged, then we do no more processing. This
* allows a split brain cleanup to not break things forever. It
* does mean that an expunged message might not replicate in that
* case, but the only way to fix this is add ANOTHER special flag
* for BROKEN and only ignore GUID mismatches in that case, after
* moving the message up. I guess we could force UNLINK immediately
* too... hmm. Not today. */
if ((mp->internal_flags & FLAG_INTERNAL_EXPUNGED) &&
(rp->internal_flags & FLAG_INTERNAL_EXPUNGED))
return 0;
/* first of all, check that GUID matches. If not, we have had a split
* brain, so the messages both need to be fixed up to their new UIDs.
* After this function succeeds, both the local and remote copies of this
* current UID will be actually EXPUNGED, so the earlier return applies. */
if (!message_guid_equal(&mp->guid, &rp->guid)) {
char *mguid = xstrdup(message_guid_encode(&mp->guid));
char *rguid = xstrdup(message_guid_encode(&rp->guid));
xsyslog(LOG_ERR, "SYNCERROR: guid mismatch",
"mailbox=<%s> uid=<%u> rguid=<%s> mguid=<%s>",
mailbox_name(mailbox), mp->uid, rguid, mguid);
free(rguid);
free(mguid);
/* we will need to renumber both ends to get in sync */
/* ORDERING - always lower GUID first */
if (message_guid_cmp(&mp->guid, &rp->guid) > 0) {
r = copyback_one_record(sync_cs, mailbox, rp, rannots, kaction, part_list);
if (!r) r = renumber_one_record(mp, kaction);
}
else {
r = renumber_one_record(mp, kaction);
if (!r) r = copyback_one_record(sync_cs, mailbox, rp, rannots, kaction, part_list);
}
return r;
}
/* are there any differences? */
if (mp->modseq != rp->modseq)
goto diff;
if (mp->last_updated != rp->last_updated)
goto diff;
if (mp->internaldate != rp->internaldate)
goto diff;
if (mp->system_flags != rp->system_flags)
goto diff;
if ((mp->internal_flags & FLAG_INTERNAL_EXPUNGED) !=
(rp->internal_flags & FLAG_INTERNAL_EXPUNGED))
goto diff;
if (mp->cid != rp->cid)
goto diff;
if (mp->basecid != rp->basecid)
goto diff;
if (mp->savedate != rp->savedate)
goto diff;
if (mp->createdmodseq != rp->createdmodseq)
goto diff;
if (diff_annotations(mannots, rannots))
goto diff;
for (i = 0; i < MAX_USER_FLAGS/32; i++) {
if (mp->user_flags[i] != rp->user_flags[i])
goto diff;
}
/* no changes found, whoopee */
return 0;
diff:
/* if differences we'll have to rewrite to bump the modseq
* so that regular replication will cause an update */
/* interesting case - expunged locally */
if (mp->internal_flags & FLAG_INTERNAL_EXPUNGED) {
/* if expunged, fall through - the rewrite will lift
* the modseq to force the change to stick */
}
else if (rp->internal_flags & FLAG_INTERNAL_EXPUNGED) {
/* mark expunged - rewrite will cause both sides to agree
* again */
mp->internal_flags |= FLAG_INTERNAL_EXPUNGED;
}
/* otherwise, is the replica "newer"? Better grab those flags */
else {
if (rp->modseq > mp->modseq &&
rp->last_updated >= mp->last_updated) {
log_mismatch("more recent on replica", mailbox, mp, rp);
/* then copy all the flag data over from the replica */
mp->system_flags = rp->system_flags;
mp->internal_flags &= ~FLAG_INTERNAL_EXPUNGED;
mp->internal_flags |= rp->internal_flags & FLAG_INTERNAL_EXPUNGED;
mp->cid = rp->cid;
for (i = 0; i < MAX_USER_FLAGS/32; i++)
mp->user_flags[i] = rp->user_flags[i];
}
}
/* are we making changes yet? */
if (!kaction) return 0;
int hadsnoozed = 0;
/* even expunged messages get annotations synced */
r = apply_annotations(mailbox, mp, mannots, rannots, 0, &hadsnoozed);
if (r) return r;
if (hadsnoozed) mp->internal_flags |= FLAG_INTERNAL_SNOOZED;
else mp->internal_flags &= ~FLAG_INTERNAL_SNOOZED;
/* this will bump the modseq and force a resync either way :) */
return mailbox_rewrite_index_record(mailbox, mp);
}
static int mailbox_update_loop(struct sync_client_state *sync_cs,
struct mailbox *mailbox,
struct dlist *ki,
uint32_t last_uid,
modseq_t highestmodseq,
struct dlist *kaction,
struct sync_msgid_list *part_list)
{
struct index_record rrecord;
struct sync_annot_list *mannots = NULL;
struct sync_annot_list *rannots = NULL;
int r;
struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, 0);
const message_t *msg = mailbox_iter_step(iter);
const struct index_record *mrecord = msg ? msg_record(msg) : NULL;
/* while there are more records on either master OR replica,
* work out what to do with them */
while (ki || msg) {
sync_annot_list_free(&mannots);
sync_annot_list_free(&rannots);
/* most common case - both a master AND a replica record exist */
if (ki && mrecord) {
r = read_annotations(mailbox, mrecord, &mannots, 0, 0);
if (r) goto out;
r = parse_upload(ki, mailbox, &rrecord, &rannots);
if (r) goto out;
/* same UID - compare the records */
if (rrecord.uid == mrecord->uid) {
mailbox_read_basecid(mailbox, mrecord);
r = compare_one_record(sync_cs, mailbox,
(struct index_record *)mrecord, &rrecord,
mannots, rannots,
kaction, part_list);
if (r) goto out;
/* increment both */
msg = mailbox_iter_step(iter);
mrecord = msg ? msg_record(msg) : NULL;
ki = ki->next;
}
else if (rrecord.uid > mrecord->uid) {
/* record only exists on the master */
if (!(mrecord->internal_flags & FLAG_INTERNAL_EXPUNGED)) {
xsyslog(LOG_ERR, "SYNCNOTICE: record only exists on master",
"mailbox=<%s> uid=<%u> guid=<%s>",
mailbox_name(mailbox), mrecord->uid,
message_guid_encode(&mrecord->guid));
r = renumber_one_record(mrecord, kaction);
if (r) goto out;
}
/* only increment master */
msg = mailbox_iter_step(iter);
mrecord = msg ? msg_record(msg) : NULL;
}
else {
/* record only exists on the replica */
if (!(rrecord.internal_flags & FLAG_INTERNAL_EXPUNGED)) {
if (kaction)
xsyslog(LOG_ERR, "SYNCNOTICE: record only exists on replica",
"mailbox=<%s> uid=<%u> guid=<%s>",
mailbox_name(mailbox), rrecord.uid,
message_guid_encode(&rrecord.guid));
r = copyback_one_record(sync_cs, mailbox, &rrecord, rannots, kaction, part_list);
if (r) goto out;
}
/* only increment replica */
ki = ki->next;
}
}
/* no more replica records, but still master records */
else if (mrecord) {
/* if the replica has seen this UID, we need to renumber.
* Otherwise it will replicate fine as-is */
if (mrecord->uid <= last_uid) {
r = renumber_one_record(mrecord, kaction);
if (r) goto out;
}
else if (mrecord->modseq <= highestmodseq) {
if (kaction) {
/* bump our modseq so we sync */
xsyslog(LOG_NOTICE, "SYNCNOTICE: bumping modseq",
"mailbox=<%s> record=<%u>",
mailbox_name(mailbox), mrecord->uid);
r = mailbox_rewrite_index_record(mailbox, (struct index_record *)mrecord);
if (r) goto out;
}
}
msg = mailbox_iter_step(iter);
mrecord = msg ? msg_record(msg) : NULL;
}
/* record only exists on the replica */
else {
r = parse_upload(ki, mailbox, &rrecord, &rannots);
if (r) goto out;
if (kaction)
xsyslog(LOG_NOTICE, "SYNCNOTICE: record only exists on replica",
"mailbox=<%s> uid=<%u>",
mailbox_name(mailbox), rrecord.uid);
/* going to need this one */
r = copyback_one_record(sync_cs, mailbox, &rrecord, rannots, kaction, part_list);
if (r) goto out;
ki = ki->next;
}
}
r = 0;
out:
mailbox_iter_done(&iter);
sync_annot_list_free(&mannots);
sync_annot_list_free(&rannots);
return r;
}
static int mailbox_full_update(struct sync_client_state *sync_cs,
struct sync_folder *local,
struct sync_reserve_list *reserve_list,
unsigned flags)
{
const char *cmd = "FULLMAILBOX";
struct mailbox *mailbox = NULL;
int r;
struct dlist *kin = NULL;
struct dlist *kr = NULL;
struct dlist *ka = NULL;
struct dlist *kuids = NULL;
struct dlist *kl = NULL;
struct dlist *kaction = NULL;
struct dlist *kexpunge = NULL;
modseq_t highestmodseq;
modseq_t foldermodseq = 0;
uint32_t uidvalidity;
uint32_t last_uid;
struct sync_annot_list *mannots = NULL;
struct sync_annot_list *rannots = NULL;
int remote_modseq_was_higher = 0;
modseq_t xconvmodseq = 0;
struct sync_msgid_list *part_list;
annotate_state_t *astate = NULL;
if (flags & SYNC_FLAG_VERBOSE)
printf("%s %s\n", cmd, local->name);
if (flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s", cmd, local->name);
kl = dlist_setatom(NULL, cmd, local->name);
sync_send_lookup(kl, sync_cs->backend->out);
dlist_free(&kl);
r = sync_parse_response(cmd, sync_cs->backend->in, &kin);
if (r) return r;
// we know the remote state, so cache it
r = sync_cache(sync_cs, local->name, kin);
if (r) return r;
kl = kin->head;
if (!kl) {
r = IMAP_MAILBOX_NONEXISTENT;
goto done;
}
/* XXX - handle the header. I want to do some ordering on timestamps
* in particular here - if there's more recent data on the replica then
* it should be copied back. This depends on having a nice way to
* parse the mailbox structure back in to a struct index_header rather
* than the by hand stuff though, because that sucks. NOTE - this
* doesn't really matter too much, because we'll blat the replica's
* values anyway! */
if (!dlist_getnum64(kl, "HIGHESTMODSEQ", &highestmodseq)) {
r = IMAP_PROTOCOL_BAD_PARAMETERS;
goto done;
}
if (!dlist_getnum32(kl, "UIDVALIDITY", &uidvalidity)) {
r = IMAP_PROTOCOL_BAD_PARAMETERS;
goto done;
}
if (!dlist_getnum32(kl, "LAST_UID", &last_uid)) {
r = IMAP_PROTOCOL_BAD_PARAMETERS;
goto done;
}
if (!dlist_getlist(kl, "RECORD", &kr)) {
r = IMAP_PROTOCOL_BAD_PARAMETERS;
goto done;
}
/* optional */
dlist_getnum64(kl, "XCONVMODSEQ", &xconvmodseq);
dlist_getnum64(kl, "FOLDERMODSEQ", &foldermodseq);
/* we'll be updating it! */
if (local->mailbox) {
mailbox = local->mailbox;
}
else {
r = mailbox_open_iwl(local->name, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
}
if (r) goto done;
part_list = sync_reserve_partlist(reserve_list, mailbox_partition(mailbox));
/* if local UIDVALIDITY is lower, copy from remote, otherwise
* remote will copy ours when we sync */
if (mailbox->i.uidvalidity < uidvalidity) {
xsyslog(LOG_NOTICE, "SYNCNOTICE: uidvalidity higher on replica, updating",
"mailbox=<%s> olduidvalidity=<%u> newuidvalidity=<%u>",
mailbox_name(mailbox), mailbox->i.uidvalidity, uidvalidity);
mailbox_index_dirty(mailbox);
mailbox->i.uidvalidity = mboxname_setuidvalidity(mailbox_name(mailbox), uidvalidity);
}
if (mailbox->i.highestmodseq < highestmodseq) {
/* highestmodseq on replica is dirty - we must copy and then dirty
* so we go one higher! */
xsyslog(LOG_NOTICE, "SYNCNOTICE: highestmodseq higher on replica, updating",
"mailbox=<%s> oldhighestmodseq=<" MODSEQ_FMT ">"
" newhighestmodseq=<" MODSEQ_FMT ">",
mailbox_name(mailbox), mailbox->i.highestmodseq, highestmodseq+1);
mailbox->modseq_dirty = 0;
mailbox->i.highestmodseq = highestmodseq;
mailbox_modseq_dirty(mailbox);
remote_modseq_was_higher = 1;
}
/* hold the annotate state open */
r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
if (r) goto done;
annotate_state_begin(astate);
r = mailbox_update_loop(sync_cs, mailbox, kr->head, last_uid,
highestmodseq, NULL, part_list);
if (r) {
xsyslog(LOG_ERR, "SYNCNOTICE: failed to prepare update",
"mailbox=<%s> error=<%s>",
mailbox_name(mailbox), error_message(r));
goto done;
}
/* OK - now we're committed to make changes! */
/* this is safe because "larger than" logic is embedded
* inside update_xconvmodseq */
if (mailbox_has_conversations(mailbox)) {
r = mailbox_update_xconvmodseq(mailbox, xconvmodseq, /* force */0);
if (r) goto done;
}
if (foldermodseq) {
// by writing the same ACL with the updated foldermodseq, this will bounce it
// if needed
r = mboxlist_sync_setacls(mailbox_name(mailbox), mailbox_acl(mailbox), foldermodseq);
if (r) goto done;
}
kaction = dlist_newlist(NULL, "ACTION");
r = mailbox_update_loop(sync_cs, mailbox, kr->head, last_uid,
highestmodseq, kaction, part_list);
if (r) goto cleanup;
/* if replica still has a higher last_uid, bump our local
* number to match so future records don't clash */
if (mailbox->i.last_uid < last_uid) {
mailbox_index_dirty(mailbox);
mailbox->i.last_uid = last_uid;
}
/* ugly variable reuse */
dlist_getlist(kl, "ANNOTATIONS", &ka);
if (ka) decode_annotations(ka, &rannots, mailbox, NULL);
r = read_annotations(mailbox, NULL, &mannots, 0, 0);
if (r) goto cleanup;
r = apply_annotations(mailbox, NULL, mannots, rannots,
!remote_modseq_was_higher, NULL);
if (r) goto cleanup;
/* blatant reuse 'r' us */
kexpunge = dlist_newkvlist(NULL, "EXPUNGE");
dlist_setatom(kexpunge, "MBOXNAME", mailbox_name(mailbox));
dlist_setatom(kexpunge, "UNIQUEID", mailbox_uniqueid(mailbox)); /* just for safety */
kuids = dlist_newlist(kexpunge, "UID");
for (ka = kaction->head; ka; ka = ka->next) {
if (!strcmp(ka->name, "EXPUNGE")) {
dlist_setnum32(kuids, "UID", dlist_num(ka));
}
else if (!strcmp(ka->name, "COPYBACK")) {
r = copy_remote(mailbox, dlist_num(ka), kr, part_list);
if (r) goto cleanup;
dlist_setnum32(kuids, "UID", dlist_num(ka));
}
else if (!strcmp(ka->name, "RENUMBER")) {
r = copy_local(mailbox, dlist_num(ka));
if (r) goto cleanup;
}
}
/* we still need to do the EXPUNGEs */
cleanup:
sync_annot_list_free(&mannots);
sync_annot_list_free(&rannots);
/* close the mailbox before sending any expunges
* to avoid deadlocks */
if (!local->mailbox) mailbox_close(&mailbox);
/* only send expunge if we have some UIDs to expunge */
if (kuids && kuids->head) {
int r2;
sync_send_apply(kexpunge, sync_cs->backend->out);
r2 = sync_parse_response("EXPUNGE", sync_cs->backend->in, NULL);
if (r2) {
xsyslog(LOG_ERR, "SYNCERROR: failed to expunge in cleanup",
"name=<%s>",
local->name);
}
}
done:
if (r && mailbox)
annotate_state_abort(&mailbox->annot_state);
if (mailbox && !local->mailbox) mailbox_close(&mailbox);
dlist_free(&kin);
dlist_free(&kaction);
dlist_free(&kexpunge);
/* kuids points into the tree rooted at kexpunge
* so we don't need to free it explicitly here */
return r;
}
static int is_unchanged(struct mailbox *mailbox, struct sync_folder *remote)
{
/* look for any mismatches */
unsigned options = mailbox->i.options & MAILBOX_OPTIONS_MASK;
modseq_t xconvmodseq = 0;
if (!remote) return 0;
if (remote->mbtype != mbtypes_sync(mailbox_mbtype(mailbox))) return 0;
if (remote->last_uid != mailbox->i.last_uid) return 0;
if (remote->highestmodseq != mailbox->i.highestmodseq) return 0;
if (remote->uidvalidity != mailbox->i.uidvalidity) return 0;
if (remote->recentuid != mailbox->i.recentuid) return 0;
if (remote->recenttime != mailbox->i.recenttime) return 0;
if (remote->pop3_last_login != mailbox->i.pop3_last_login) return 0;
if (remote->pop3_show_after != mailbox->i.pop3_show_after) return 0;
if (remote->options != options) return 0;
if (remote->foldermodseq && remote->foldermodseq != mailbox_foldermodseq(mailbox)) return 0;
if (strcmp(remote->acl, mailbox_acl(mailbox))) return 0;
if (config_getswitch(IMAPOPT_REVERSEACLS)) {
modseq_t raclmodseq = mboxname_readraclmodseq(mailbox_name(mailbox));
// don't bail if either are zero, that could be version skew
if (raclmodseq && remote->raclmodseq && remote->raclmodseq != raclmodseq) return 0;
}
if (mailbox_has_conversations(mailbox)) {
int r = mailbox_get_xconvmodseq(mailbox, &xconvmodseq);
if (r) return 0;
if (remote->xconvmodseq != xconvmodseq) return 0;
}
/* compare annotations */
{
struct sync_annot_list *mannots = NULL;
int r = read_annotations(mailbox, NULL, &mannots, 0, 0);
if (r) return 0;
if (diff_annotations(mannots, remote->annots)) {
sync_annot_list_free(&mannots);
return 0;
}
sync_annot_list_free(&mannots);
}
/* if we got here then we should force check the CRCs */
if (!mailbox_crceq(remote->synccrcs, mailbox_synccrcs(mailbox, /*force*/0)))
if (!mailbox_crceq(remote->synccrcs, mailbox_synccrcs(mailbox, /*force*/1)))
return 0;
/* otherwise it's unchanged! */
return 1;
}
/* XXX kind of nasty having this here, but i think it probably
* shouldn't be in .h with the rest of them */
#define SYNC_FLAG_ISREPEAT (1<<15)
#define SYNC_FLAG_FULLANNOTS (1<<16)
static int update_mailbox_once(struct sync_client_state *sync_cs,
struct sync_folder *local,
struct sync_folder *remote,
const char *topart,
struct sync_reserve_list *reserve_list,
unsigned flags)
{
struct sync_msgid_list *part_list;
struct mailbox *mailbox = NULL;
int r = 0;
const char *cmd =
(flags & SYNC_FLAG_LOCALONLY) ? "LOCAL_MAILBOX" : "MAILBOX";
struct dlist *kl = dlist_newkvlist(NULL, cmd);
struct dlist *kupload = dlist_newlist(NULL, "MESSAGE");
annotate_state_t *astate = NULL;
struct sync_folder_list *myremotes = NULL;
if (flags & SYNC_FLAG_ISREPEAT) {
// we have to fetch the sync_folder again!
myremotes = sync_folder_list_create();
struct dlist *mbkl = dlist_newlist(NULL, "MAILBOXES");
dlist_setatom(mbkl, "MBOXNAME", local->name);
if (flags & SYNC_FLAG_VERBOSE)
printf("MAILBOXES %s\n", local->name);
sync_send_lookup(mbkl, sync_cs->backend->out);
dlist_free(&mbkl);
r = sync_response_parse(sync_cs, "MAILBOXES", myremotes,
NULL, NULL, NULL, NULL);
if (r) goto done;
remote = myremotes->head;
}
if (local->mailbox) {
mailbox = local->mailbox;
}
else {
r = mailbox_open_iwl(local->name, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
}
if (r == IMAP_MAILBOX_NONEXISTENT) {
/* been deleted in the meanwhile... it will get picked up by the
* delete call later */
r = 0;
goto done;
}
else if (r)
goto done;
if (!topart) topart = mailbox_partition(mailbox);
/* hold the annotate state open */
r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
if (r) goto done;
/* and force it to hold a transaction while it does stuff */
annotate_state_begin(astate);
/* definitely bad if these don't match! */
if (strcmp(mailbox_uniqueid(mailbox), local->uniqueid) ||
strcmp(mailbox_partition(mailbox), local->part)) {
r = IMAP_MAILBOX_MOVED;
goto done;
}
/* check that replication stands a chance of succeeding */
if (remote && !(flags & SYNC_FLAG_ISREPEAT)) {
if (mailbox->i.deletedmodseq > remote->highestmodseq) {
syslog(LOG_NOTICE, "inefficient replication ("
MODSEQ_FMT " > " MODSEQ_FMT ") %s",
mailbox->i.deletedmodseq, remote->highestmodseq,
local->name);
r = IMAP_AGAIN;
goto done;
}
}
/* if local UIDVALIDITY is lower, copy from remote, otherwise
* remote will copy ours when we sync */
if (remote && mailbox->i.uidvalidity < remote->uidvalidity) {
xsyslog(LOG_NOTICE, "SYNCNOTICE: uidvalidity higher on replica, updating",
"mailbox=<%s> olduidvalidity=<%u> newuidvalidity=<%u>",
mailbox_name(mailbox), mailbox->i.uidvalidity, remote->uidvalidity);
mailbox_index_dirty(mailbox);
mailbox->i.uidvalidity = mboxname_setuidvalidity(mailbox_name(mailbox), remote->uidvalidity);
}
/* make sure CRC is updated if we're retrying */
if (flags & SYNC_FLAG_ISREPEAT) {
r = mailbox_index_recalc(mailbox);
if (r) goto done;
}
/* bump the raclmodseq if it's higher on the replica */
if (remote && remote->raclmodseq) {
mboxname_setraclmodseq(mailbox_name(mailbox), remote->raclmodseq);
}
/* bump the foldermodseq if it's higher on the replica */
if (remote && remote->foldermodseq > mailbox_foldermodseq(mailbox)) {
mboxlist_sync_setacls(mailbox_name(mailbox), mailbox_acl(mailbox), remote->foldermodseq);
mailbox->foldermodseq = remote->foldermodseq;
}
/* nothing changed - nothing to send */
if (is_unchanged(mailbox, remote))
goto done;
if (!topart) topart = mailbox_partition(mailbox);
part_list = sync_reserve_partlist(reserve_list, topart);
r = sync_prepare_dlists(mailbox, local, remote, topart, part_list, kl,
kupload, 1, /*XXX flags & SYNC_FLAG_FULLANNOTS*/1, !(flags & SYNC_FLAG_ISREPEAT));
if (r) goto done;
/* keep the mailbox locked for shorter time! Unlock the index now
* but don't close it, because we need to guarantee that message
* files don't get deleted until we're finished with them... */
if (!local->mailbox) mailbox_unlock_index(mailbox, NULL);
if (flags & SYNC_FLAG_VERBOSE)
printf("%s %s\n", cmd, local->name);
if (flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "%s %s", cmd, local->name);
/* upload in small(ish) blocks to avoid timeouts */
while (kupload->head) {
struct dlist *kul1 = dlist_splice(kupload, 1024);
sync_send_apply(kul1, sync_cs->backend->out);
r = sync_parse_response("MESSAGE", sync_cs->backend->in, NULL);
dlist_free(&kul1);
if (r) goto done; /* abort earlier */
}
/* close before sending the apply - all data is already read */
if (!local->mailbox) mailbox_close(&mailbox);
/* update the mailbox */
sync_send_apply(kl, sync_cs->backend->out);
r = sync_parse_response("MAILBOX", sync_cs->backend->in, NULL);
// if we succeeded, cache!
if (!r) r = sync_cache(sync_cs, local->name, kl);
done:
if (mailbox && !local->mailbox) mailbox_close(&mailbox);
// any error, nuke our remote cache.
if (r) sync_uncache(sync_cs, local->name);
sync_folder_list_free(&myremotes);
dlist_free(&kupload);
dlist_free(&kl);
return r;
}
int sync_do_update_mailbox(struct sync_client_state *sync_cs,
struct sync_folder *local,
struct sync_folder *remote,
const char *topart,
struct sync_reserve_list *reserve_list)
{
mbentry_t *mbentry = NULL;
// it should exist! Guess we lost a race, force it to retry
int r = mboxlist_lookup_allow_all(local->name, &mbentry, NULL);
if (r) return r;
if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
struct dlist *kl = dlist_newkvlist(NULL, "MAILBOX");
dlist_setatom(kl, "UNIQUEID", mbentry->uniqueid);
dlist_setatom(kl, "MBOXNAME", mbentry->name);
dlist_setatom(kl, "MBOXTYPE",
mboxlist_mbtype_to_string(mbtypes_sync(mbentry->mbtype)));
dlist_setnum64(kl, "HIGHESTMODSEQ", mbentry->foldermodseq);
dlist_setnum64(kl, "CREATEDMODSEQ", mbentry->createdmodseq);
dlist_setnum64(kl, "FOLDERMODSEQ", mbentry->foldermodseq);
sync_send_apply(kl, sync_cs->backend->out);
r = sync_parse_response("MAILBOX", sync_cs->backend->in, NULL);
// on error, clear cache - otherwise cache this state
if (r) sync_uncache(sync_cs, mbentry->name);
else r = sync_cache(sync_cs, mbentry->name, kl);
dlist_free(&kl);
mboxlist_entry_free(&mbentry);
return 0;
}
mboxlist_entry_free(&mbentry);
int flags = sync_cs->flags;
r = update_mailbox_once(sync_cs, local, remote, topart, reserve_list, flags);
flags |= SYNC_FLAG_ISREPEAT;
if (r == IMAP_SYNC_CHECKSUM) {
syslog(LOG_NOTICE, "SYNC_NOTICE: CRC failure on sync %s, recalculating counts and trying again", local->name);
r = update_mailbox_once(sync_cs, local, remote, topart, reserve_list, flags);
}
/* never retry - other end should always sync cleanly */
if (flags & SYNC_FLAG_NO_COPYBACK) return r;
if (r == IMAP_AGAIN) {
local->ispartial = 0; /* don't batch the re-update, means sync to 2.4 will still work after fullsync */
r = mailbox_full_update(sync_cs, local, reserve_list, flags);
if (!r) r = update_mailbox_once(sync_cs, local, remote, topart,
reserve_list, flags);
}
else if (r == IMAP_SYNC_CHECKSUM) {
syslog(LOG_ERR, "CRC failure on sync for %s, trying full update",
local->name);
r = mailbox_full_update(sync_cs, local, reserve_list, flags);
if (!r) r = update_mailbox_once(sync_cs, local, remote, topart,
reserve_list, flags|SYNC_FLAG_FULLANNOTS);
}
return r;
}
/* ====================================================================== */
static int update_seen_work(struct sync_client_state *sync_cs,
const char *user, const char *uniqueid,
struct seendata *sd)
{
const char *cmd = "SEEN";
struct dlist *kl;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("SEEN %s %s\n", user, uniqueid);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "SEEN %s %s", user, uniqueid);
/* Update seen list */
kl = dlist_newkvlist(NULL, cmd);
dlist_setatom(kl, "USERID", user);
dlist_setatom(kl, "UNIQUEID", uniqueid);
dlist_setdate(kl, "LASTREAD", sd->lastread);
dlist_setnum32(kl, "LASTUID", sd->lastuid);
dlist_setdate(kl, "LASTCHANGE", sd->lastchange);
dlist_setatom(kl, "SEENUIDS", sd->seenuids);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
return sync_parse_response(cmd, sync_cs->backend->in, NULL);
}
int sync_do_seen(struct sync_client_state *sync_cs,
const char *userid, char *uniqueid)
{
int r = 0;
struct seen *seendb = NULL;
struct seendata sd = SEENDATA_INITIALIZER;
/* ignore read failures */
r = seen_open(userid, SEEN_SILENT, &seendb);
if (r) return 0;
// XXX: we should pipe the channel through to here
struct mboxlock *synclock = sync_lock(sync_cs, userid);
if (!synclock) {
r = IMAP_MAILBOX_LOCKED;
goto done;
}
r = seen_read(seendb, uniqueid, &sd);
if (!r) r = update_seen_work(sync_cs, userid, uniqueid, &sd);
done:
seen_close(&seendb);
seen_freedata(&sd);
mboxname_release(&synclock);
return r;
}
/* ====================================================================== */
int sync_do_quota(struct sync_client_state *sync_cs,
const char *root)
{
int r = 0;
char *userid = mboxname_to_userid(root);
struct mboxlock *synclock = sync_lock(sync_cs, userid);
if (!synclock) {
r = IMAP_MAILBOX_LOCKED;
goto done;
}
struct quota q;
quota_init(&q, root);
r = update_quota_work(sync_cs, &q, NULL);
quota_free(&q);
done:
mboxname_release(&synclock);
free(userid);
return r;
}
static int do_annotation_cb(const char *mailbox __attribute__((unused)),
uint32_t uid __attribute__((unused)),
const char *entry, const char *userid,
const struct buf *value,
const struct annotate_metadata *mdata,
void *rock)
{
struct sync_annot_list *l = (struct sync_annot_list *) rock;
sync_annot_list_add(l, entry, userid, value, mdata->modseq);
return 0;
}
static int parse_annotation(struct dlist *kin,
struct sync_annot_list *replica_annot)
{
struct dlist *kl;
const char *entry;
const char *userid = "";
const char *valmap = NULL;
size_t vallen = 0;
struct buf value = BUF_INITIALIZER;
modseq_t modseq = 0;
for (kl = kin->head; kl; kl = kl->next) {
if (!dlist_getatom(kl, "ENTRY", &entry))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getmap(kl, "VALUE", &valmap, &vallen))
return IMAP_PROTOCOL_BAD_PARAMETERS;
dlist_getatom(kl, "USERID", &userid); /* optional */
dlist_getnum64(kl, "MODSEQ", &modseq); /* optional */
buf_init_ro(&value, valmap, vallen);
sync_annot_list_add(replica_annot, entry, userid, &value, modseq);
buf_free(&value);
}
return 0;
}
static int do_getannotation(struct sync_client_state *sync_cs, const char *mboxname,
struct sync_annot_list *replica_annot)
{
const char *cmd = "ANNOTATION";
struct dlist *kl;
struct dlist *kin = NULL;
int r;
/* Update seen list */
kl = dlist_setatom(NULL, cmd, mboxname);
sync_send_lookup(kl, sync_cs->backend->out);
dlist_free(&kl);
r = sync_parse_response(cmd, sync_cs->backend->in, &kin);
if (r) return r;
r = parse_annotation(kin, replica_annot);
dlist_free(&kin);
return r;
}
int sync_do_annotation(struct sync_client_state *sync_cs, const char *mboxname)
{
int r;
struct sync_annot_list *replica_annot = sync_annot_list_create();
struct sync_annot_list *master_annot = sync_annot_list_create();
struct sync_annot *ma, *ra;
int n;
char *userid = mboxname_to_userid(mboxname);
// XXX: we should pipe the channel through to here
struct mboxlock *synclock = sync_lock(sync_cs, userid);
if (!synclock) {
r = IMAP_MAILBOX_LOCKED;
goto bail;
}
r = do_getannotation(sync_cs, mboxname, replica_annot);
if (r) goto bail;
r = annotatemore_findall_pattern(mboxname, 0, "*", /*modseq*/0, &do_annotation_cb,
master_annot, /*flags*/0);
if (r) {
xsyslog(LOG_ERR, "IOERROR: fetching annotations failed",
"mboxname=<%s>", mboxname);
r = IMAP_IOERROR;
goto bail;
}
/* both lists are sorted, so we work our way through the lists
top-to-bottom and determine what we need to do based on order */
ma = master_annot->head;
ra = replica_annot->head;
while (ma || ra) {
if (!ra) n = -1; /* add all master annotations */
else if (!ma) n = 1; /* remove all replica annotations */
else if ((n = strcmp(ma->entry, ra->entry)) == 0)
n = strcmp(ma->userid, ra->userid);
if (n > 0) {
/* remove replica annotation */
r = folder_unannotation(sync_cs, mboxname, ra->entry, ra->userid);
if (r) goto bail;
ra = ra->next;
continue;
}
if (n == 0) {
/* already have the annotation, but is the value different? */
if (!buf_cmp(&ra->value, &ma->value)) {
ra = ra->next;
ma = ma->next;
continue;
}
ra = ra->next;
}
/* add the current client annotation */
r = folder_setannotation(sync_cs, mboxname, ma->entry, ma->userid, &ma->value);
if (r) goto bail;
ma = ma->next;
}
bail:
sync_annot_list_free(&master_annot);
sync_annot_list_free(&replica_annot);
mboxname_release(&synclock);
free(userid);
return r;
}
/* ====================================================================== */
static int do_folders(struct sync_client_state *sync_cs,
struct sync_name_list *mboxname_list, const char *topart,
struct sync_folder_list *replica_folders,
int flags)
{
int r = 0;
struct sync_folder_list *master_folders;
struct sync_rename_list *rename_folders;
struct sync_reserve_list *reserve_list;
struct sync_folder *mfolder, *rfolder;
const char *part;
uint32_t batchsize = 0;
if (flags & SYNC_FLAG_BATCH) {
batchsize = config_getint(IMAPOPT_SYNC_BATCHSIZE);
}
master_folders = sync_folder_list_create();
rename_folders = sync_rename_list_create();
reserve_list = sync_reserve_list_create(SYNC_MSGID_LIST_HASH_SIZE);
r = reserve_messages(sync_cs, mboxname_list, topart, master_folders,
replica_folders, reserve_list, batchsize);
if (r) {
syslog(LOG_ERR, "reserve messages: failed: %s", error_message(r));
goto bail;
}
/* Tag folders on server which still exist on the client. Anything
* on the server which remains untagged can be deleted immediately */
for (mfolder = master_folders->head; mfolder; mfolder = mfolder->next) {
if (mfolder->mark) continue;
rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid);
if (!rfolder) continue;
if (rfolder->mark) continue;
rfolder->mark = 1;
/* does it need a rename? partition change is a rename too */
part = topart ? topart : mfolder->part;
if (strcmp(mfolder->name, rfolder->name) || (rfolder->part && strcmpsafe(part, rfolder->part))) {
sync_rename_list_add(rename_folders, mfolder->uniqueid, rfolder->name,
mfolder->name, part, mfolder->uidvalidity);
}
}
/* XXX - sync_log_channel_user on any issue here rather than trying to solve,
* and remove all entries related to that user from both lists */
/* Delete folders on server which no longer exist on client */
if (flags & SYNC_FLAG_DELETE_REMOTE) {
for (rfolder = replica_folders->head; rfolder; rfolder = rfolder->next) {
if (rfolder->mark) continue;
mbentry_t *tombstone = NULL;
r = mboxlist_lookup_allow_all(rfolder->name, &tombstone, NULL);
if (r == 0 && (tombstone->mbtype & MBTYPE_DELETED) == MBTYPE_DELETED) {
mboxlist_entry_free(&tombstone);
r = sync_do_folder_delete(sync_cs, rfolder->name);
if (r) {
syslog(LOG_ERR, "sync_do_folder_delete(): failed: %s '%s'",
rfolder->name, error_message(r));
goto bail;
}
}
else {
mboxlist_entry_free(&tombstone);
syslog(LOG_ERR, "%s: no tombstone for deleted mailbox %s (%s)",
__func__, rfolder->name, error_message(r));
/* XXX copy the missing local mailbox back from the replica? */
}
}
}
/* Need to rename folders in an order which avoids dependancy conflicts
* following isn't wildly efficient, but rename_folders will typically be
* short and contain few dependancies. Algorithm is to simply pick a
* rename operation which has no dependancy and repeat until done */
while (rename_folders->done < rename_folders->count) {
int rename_success = 0;
struct sync_rename *item, *item2 = NULL;
for (item = rename_folders->head; item; item = item->next) {
if (item->done) continue;
/* don't skip rename to different partition */
if (strcmp(item->oldname, item->newname)) {
item2 = sync_rename_lookup(rename_folders, item->newname);
if (item2 && !item2->done) continue;
}
/* Found unprocessed item which should rename cleanly */
r = folder_rename(sync_cs, item->oldname, item->newname, item->part,
item->uidvalidity);
if (r) {
syslog(LOG_ERR, "do_folders(): failed to rename: %s -> %s ",
item->oldname, item->newname);
goto bail;
}
rename_folders->done++;
item->done = 1;
rename_success = 1;
}
if (!rename_success) {
/* Scanned entire list without a match */
const char *name = "unknown";
if (item2) name = item2->oldname;
syslog(LOG_ERR,
"do_folders(): failed to order folders correctly at %s", name);
r = IMAP_AGAIN;
goto bail;
}
}
/* if we renamed anything, we want to resync the mailbox list before doing the
* mailbox contents */
if (rename_folders->count) {
syslog(LOG_DEBUG,
"do_folders(): did some renames, so retrying");
r = IMAP_AGAIN;
goto bail;
}
for (mfolder = master_folders->head; mfolder; mfolder = mfolder->next) {
if (mfolder->mark) continue;
/* NOTE: rfolder->name may now be wrong, but we're guaranteed that
* it was successfully renamed above, so just use mfolder->name for
* all commands */
rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid);
r = sync_do_update_mailbox(sync_cs, mfolder, rfolder, topart, reserve_list);
if (r) {
syslog(LOG_ERR, "do_folders(): update failed: %s '%s'",
mfolder->name, error_message(r));
goto bail;
}
if (sync_cs->channel && mfolder->ispartial) {
sync_log_channel_mailbox(sync_cs->channel, mfolder->name);
}
}
bail:
sync_folder_list_free(&master_folders);
sync_rename_list_free(&rename_folders);
sync_reserve_list_free(&reserve_list);
return r;
}
int sync_do_mailboxes(struct sync_client_state *sync_cs,
struct sync_name_list *mboxname_list,
const char *topart, int flags)
{
struct sync_name *mbox;
struct sync_folder_list *replica_folders = sync_folder_list_create();
struct buf buf = BUF_INITIALIZER;
int r;
strarray_t userids = STRARRAY_INITIALIZER;
ptrarray_t locks = PTRARRAY_INITIALIZER;
// what a pain, we need to lock all the users in order, so..
for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
char *userid = mboxname_to_userid(mbox->name);
strarray_add(&userids, userid ? userid : "");
free(userid);
}
strarray_sort(&userids, cmpstringp_raw);
int i;
for (i = 0; i < strarray_size(&userids); i++) {
const char *userid = strarray_nth(&userids, i);
struct mboxlock *lock = sync_lock(sync_cs, userid);
if (!lock) {
r = IMAP_MAILBOX_LOCKED;
goto done;
}
ptrarray_append(&locks, lock);
}
int tries = 0;
redo:
tries++;
if (tries > 3) {
syslog(LOG_ERR, "failed to settle renames after 3 tries!");
r = IMAP_SYNC_CHANGED;
goto done;
}
struct dlist *kl = NULL;
struct dlist *cachel = NULL;
for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
struct dlist *cl = NULL;
// check if it's in the cache, then we don't need to look it up
if (!sync_readcache(sync_cs, mbox->name, &cl) && cl) {
if (!cachel) cachel = dlist_newlist(NULL, "MAILBOXES");
dlist_stitch(cachel, cl);
if ((flags & SYNC_FLAG_VERBOSE) || (flags & SYNC_FLAG_LOGGING))
buf_printf(&buf, " (%s)", mbox->name);
}
// if it's not in the cache, then we need to ask for it
else {
if (!kl) kl = dlist_newlist(NULL, "MAILBOXES");
dlist_setatom(kl, "MBOXNAME", mbox->name);
if ((flags & SYNC_FLAG_VERBOSE) || (flags & SYNC_FLAG_LOGGING))
buf_printf(&buf, " %s", mbox->name);
}
}
if (flags & SYNC_FLAG_VERBOSE)
printf("MAILBOXES%s\n", buf_cstring(&buf));
if (flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "MAILBOXES%s", buf_cstring(&buf));
buf_free(&buf);
if (kl) {
sync_send_lookup(kl, sync_cs->backend->out);
dlist_free(&kl);
r = sync_response_parse(sync_cs, "MAILBOXES", replica_folders,
NULL, NULL, NULL, NULL);
if (r) goto done;
}
if (cachel) {
r = sync_kl_parse(cachel, replica_folders, NULL, NULL, NULL, NULL);
dlist_free(&cachel);
if (r) goto done;
}
/* we don't want to delete remote folders which weren't found locally,
* because we may be racing with a rename, and we don't want to lose
* the remote files. A real delete will always have inserted a
* UNMAILBOX anyway */
flags &= ~SYNC_FLAG_DELETE_REMOTE;
r = do_folders(sync_cs, mboxname_list, topart, replica_folders, flags);
if (r == IMAP_AGAIN) {
sync_folder_list_free(&replica_folders);
replica_folders = sync_folder_list_create();
goto redo;
}
done:
sync_folder_list_free(&replica_folders);
strarray_fini(&userids);
for (i = 0; i < ptrarray_size(&locks); i++) {
struct mboxlock *lock = ptrarray_nth(&locks, i);
mboxname_release(&lock);
}
ptrarray_fini(&locks);
return r;
}
/* ====================================================================== */
struct mboxinfo {
struct sync_name_list *mboxlist;
struct sync_name_list *quotalist;
};
static int do_mailbox_info(const mbentry_t *mbentry, void *rock)
{
struct mailbox *mailbox = NULL;
struct mboxinfo *info = (struct mboxinfo *)rock;
int r = 0;
/* XXX - check for deleted? */
if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
sync_name_list_add(info->mboxlist, mbentry->name);
return 0;
}
r = mailbox_open_irl(mbentry->name, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
/* doesn't exist? Probably not finished creating or removing yet */
if (r == IMAP_MAILBOX_NONEXISTENT) {
r = 0;
goto done;
}
if (r == IMAP_MAILBOX_RESERVED) {
r = 0;
goto done;
}
if (r) goto done;
if (info->quotalist && mailbox->quotaroot) {
sync_name_list_add(info->quotalist, mailbox->quotaroot);
}
sync_name_list_add(info->mboxlist, mbentry->name);
done:
mailbox_close(&mailbox);
return r;
}
int sync_do_user_quota(struct sync_client_state *sync_cs,
struct sync_name_list *master_quotaroots,
struct sync_quota_list *replica_quota)
{
int r;
struct sync_name *mitem;
struct sync_quota *rquota;
struct quota q;
/* set any new or changed quotas */
for (mitem = master_quotaroots->head; mitem; mitem = mitem->next) {
rquota = sync_quota_lookup(replica_quota, mitem->name);
if (rquota)
rquota->done = 1;
quota_init(&q, mitem->name);
r = update_quota_work(sync_cs, &q, rquota);
quota_free(&q);
if (r) return r;
}
/* delete any quotas no longer on the master */
for (rquota = replica_quota->head; rquota; rquota = rquota->next) {
if (rquota->done) continue;
r = delete_quota(sync_cs, rquota->root);
if (r) return r;
}
return 0;
}
static int do_user_main(struct sync_client_state *sync_cs,
const char *userid, const char *topart,
struct sync_folder_list *replica_folders,
struct sync_quota_list *replica_quota)
{
int r = 0;
struct mboxinfo info;
info.mboxlist = sync_name_list_create();
info.quotalist = sync_name_list_create();
r = mboxlist_usermboxtree(userid, NULL, do_mailbox_info, &info, MBOXTREE_DELETED);
/* we know all the folders present on the master, so it's safe to delete
* anything not mentioned here on the replica - at least until we get
* real tombstones */
int flags = sync_cs->flags;
flags |= SYNC_FLAG_DELETE_REMOTE;
if (!r) r = do_folders(sync_cs, info.mboxlist, topart, replica_folders, flags);
if (!r) r = sync_do_user_quota(sync_cs, info.quotalist, replica_quota);
sync_name_list_free(&info.mboxlist);
sync_name_list_free(&info.quotalist);
if (r && r != IMAP_AGAIN) {
xsyslog(LOG_ERR, "IOERROR: user replication failed",
"error=<%s> userid=<%s> channel=<%s> servername=<%s>",
error_message(r), userid,
sync_cs->channel, sync_cs->servername);
}
return r;
}
int sync_do_user_sub(struct sync_client_state *sync_cs, const char *userid,
struct sync_name_list *replica_subs)
{
struct sync_name *rsubs;
int r = 0;
int i;
/* Includes subsidiary nodes automatically */
strarray_t *msubs = mboxlist_sublist(userid);
if (!msubs) {
xsyslog(LOG_ERR, "IOERROR: fetching subscriptions failed",
"userid=<%s>", userid);
r = IMAP_IOERROR;
goto bail;
}
/* add any folders that need adding, and mark any which
* still exist */
for (i = 0; i < msubs->count; i++) {
const char *name = strarray_nth(msubs, i);
rsubs = sync_name_lookup(replica_subs, name);
if (rsubs) {
rsubs->mark = 1;
continue;
}
r = sync_set_sub(sync_cs, userid, name, 1);
if (r) goto bail;
}
/* remove any no-longer-subscribed folders */
for (rsubs = replica_subs->head; rsubs; rsubs = rsubs->next) {
if (rsubs->mark)
continue;
r = sync_set_sub(sync_cs, userid, rsubs->name, 0);
if (r) goto bail;
}
bail:
strarray_free(msubs);
return r;
}
static int get_seen(const char *uniqueid, struct seendata *sd, void *rock)
{
struct sync_seen_list *list = (struct sync_seen_list *)rock;
sync_seen_list_add(list, uniqueid, sd->lastread, sd->lastuid,
sd->lastchange, sd->seenuids);
return 0;
}
int sync_do_user_seen(struct sync_client_state *sync_cs, const char *userid,
struct sync_seen_list *replica_seen)
{
int r;
struct sync_seen *mseen, *rseen;
struct seen *seendb = NULL;
struct sync_seen_list *list;
/* silently ignore errors */
r = seen_open(userid, SEEN_SILENT, &seendb);
if (r) return 0;
list = sync_seen_list_create();
seen_foreach(seendb, get_seen, list);
seen_close(&seendb);
for (mseen = list->head; mseen; mseen = mseen->next) {
rseen = sync_seen_list_lookup(replica_seen, mseen->uniqueid);
if (rseen) {
rseen->mark = 1;
if (seen_compare(&rseen->sd, &mseen->sd))
continue; /* nothing changed */
}
r = update_seen_work(sync_cs, userid, mseen->uniqueid, &mseen->sd);
}
/* XXX - delete seen on the replica for records that don't exist? */
sync_seen_list_free(&list);
return 0;
}
int sync_do_user_sieve(struct sync_client_state *sync_cs, const char *userid,
struct sync_sieve_list *replica_sieve)
{
int r = 0;
struct sync_sieve_list *master_sieve;
struct sync_sieve *mitem, *ritem;
int master_active = 0;
int replica_active = 0;
char *ext;
master_sieve = sync_sieve_list_generate(userid);
if (!master_sieve) {
syslog(LOG_ERR, "Unable to list sieve scripts for %s", userid);
return IMAP_IOERROR;
}
/* Upload missing and out of date or mismatching scripts */
for (mitem = master_sieve->head; mitem; mitem = mitem->next) {
ritem = sync_sieve_lookup(replica_sieve, mitem->name);
if (ritem) {
ritem->mark = 1;
/* compare the GUID if known */
if (!message_guid_isnull(&ritem->guid)) {
if (message_guid_equal(&ritem->guid, &mitem->guid))
continue;
/* XXX: copyback support */
}
/* fallback to date comparison */
else if (ritem->last_update >= mitem->last_update)
continue; /* changed */
}
/* Don't upload compiled bytecode */
ext = strrchr(mitem->name, '.');
if (!ext || strcmp(ext, ".bc")) {
r = sieve_upload(sync_cs, userid, mitem->name, mitem->last_update);
if (r) goto bail;
}
/* but still log it as having been created, since it will be automatically */
if (!ritem) {
ritem = sync_sieve_list_add(replica_sieve, mitem->name,
mitem->last_update, &mitem->guid, 0);
ritem->mark = 1;
}
}
/* Delete scripts which no longer exist on the master */
replica_active = 0;
for (ritem = replica_sieve->head; ritem; ritem = ritem->next) {
if (ritem->mark) {
if (ritem->active)
replica_active = 1;
} else {
r = sieve_delete(sync_cs, userid, ritem->name);
if (r) goto bail;
ritem->mark = -1;
}
}
/* Change active script if necessary */
master_active = 0;
for (mitem = master_sieve->head; mitem; mitem = mitem->next) {
if (!mitem->active)
continue;
master_active = 1;
ritem = sync_sieve_lookup(replica_sieve, mitem->name);
if (ritem) {
if (ritem->active) break;
if (ritem->mark != -1) {
r = sieve_activate(sync_cs, userid, mitem->name);
if (r) goto bail;
replica_active = 1;
}
}
break;
}
if (!master_active && replica_active)
r = sieve_deactivate(sync_cs, userid);
bail:
sync_sieve_list_free(&master_sieve);
return(r);
}
int sync_do_user(struct sync_client_state *sync_cs,
const char *userid, const char *topart)
{
int r = 0;
struct sync_folder_list *replica_folders = sync_folder_list_create();
struct sync_name_list *replica_subs = sync_name_list_create();
struct sync_sieve_list *replica_sieve = sync_sieve_list_create();
struct sync_seen_list *replica_seen = sync_seen_list_create();
struct sync_quota_list *replica_quota = sync_quota_list_create();
struct dlist *kl = NULL;
struct mailbox *mailbox = NULL;
struct mboxlock *userlock = sync_lock(sync_cs, userid);
if (!userlock) {
r = IMAP_MAILBOX_LOCKED;
goto done;
}
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("USER %s\n", userid);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "USER %s", userid);
int tries = 0;
redo:
tries++;
if (tries > 3) {
syslog(LOG_ERR, "failed to sync user %s after 3 tries", userid);
r = IMAP_SYNC_CHANGED;
goto done;
}
kl = dlist_setatom(NULL, "USER", userid);
sync_send_lookup(kl, sync_cs->backend->out);
dlist_free(&kl);
r = sync_response_parse(sync_cs, "USER", replica_folders, replica_subs,
replica_sieve, replica_seen, replica_quota);
/* can happen! */
if (r == IMAP_MAILBOX_NONEXISTENT) r = 0;
if (r) goto done;
/* check that the inbox exists locally to be allowed to sync this user at all */
char *inbox = mboxname_user_mbox(userid, NULL);
r = mailbox_open_irl(inbox, &mailbox);
if (!r) r = sync_mailbox_version_check(&mailbox);
free(inbox);
if (r == IMAP_MAILBOX_NONEXISTENT) {
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("Does not exist locally %s\n", userid);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "Does not exist locally %s", userid);
// just skip this user. XXX - tombstone for user -> sync_reset?
r = 0;
goto done;
}
if (r) goto done;
/* we don't hold locks while sending commands */
mailbox_close(&mailbox);
r = do_user_main(sync_cs, userid, topart, replica_folders, replica_quota);
if (r == IMAP_AGAIN) {
// we've done a rename - have to try again!
sync_folder_list_free(&replica_folders);
sync_name_list_free(&replica_subs);
sync_sieve_list_free(&replica_sieve);
sync_seen_list_free(&replica_seen);
sync_quota_list_free(&replica_quota);
replica_folders = sync_folder_list_create();
replica_subs = sync_name_list_create();
replica_sieve = sync_sieve_list_create();
replica_seen = sync_seen_list_create();
replica_quota = sync_quota_list_create();
goto redo;
}
if (r) goto done;
r = sync_do_user_sub(sync_cs, userid, replica_subs);
if (r) goto done;
r = sync_do_user_sieve(sync_cs, userid, replica_sieve);
if (r) goto done;
r = sync_do_user_seen(sync_cs, userid, replica_seen);
done:
sync_folder_list_free(&replica_folders);
sync_name_list_free(&replica_subs);
sync_sieve_list_free(&replica_sieve);
sync_seen_list_free(&replica_seen);
sync_quota_list_free(&replica_quota);
mboxname_release(&userlock);
mailbox_close(&mailbox);
return r;
}
/* ====================================================================== */
int sync_do_meta(struct sync_client_state *sync_cs, const char *userid)
{
struct sync_name_list *replica_subs = sync_name_list_create();
struct sync_sieve_list *replica_sieve = sync_sieve_list_create();
struct sync_seen_list *replica_seen = sync_seen_list_create();
struct dlist *kl = NULL;
int r = 0;
if (sync_cs->flags & SYNC_FLAG_VERBOSE)
printf("META %s\n", userid);
if (sync_cs->flags & SYNC_FLAG_LOGGING)
syslog(LOG_INFO, "META %s", userid);
kl = dlist_setatom(NULL, "META", userid);
sync_send_lookup(kl, sync_cs->backend->out);
dlist_free(&kl);
r = sync_response_parse(sync_cs, "META", NULL,
replica_subs, replica_sieve, replica_seen, NULL);
if (!r) r = sync_do_user_seen(sync_cs, userid, replica_seen);
if (!r) r = sync_do_user_sub(sync_cs, userid, replica_subs);
if (!r) r = sync_do_user_sieve(sync_cs, userid, replica_sieve);
sync_seen_list_free(&replica_seen);
sync_name_list_free(&replica_subs);
sync_sieve_list_free(&replica_sieve);
return r;
}
/* ====================================================================== */
EXPORTED const char *sync_apply(struct dlist *kin, struct sync_reserve_list *reserve_list, struct sync_state *state)
{
int r = IMAP_PROTOCOL_ERROR;
ucase(kin->name);
if (!strcmp(kin->name, "MESSAGE"))
r = sync_apply_message(kin, reserve_list, state);
else if (!strcmp(kin->name, "EXPUNGE"))
r = sync_apply_expunge(kin, state);
/* dump protocol */
else if (!strcmp(kin->name, "ACTIVATE_SIEVE"))
r = sync_apply_activate_sieve(kin, state);
else if (!strcmp(kin->name, "ANNOTATION"))
r = sync_apply_annotation(kin, state);
else if (!strcmp(kin->name, "MAILBOX"))
r = sync_apply_mailbox(kin, reserve_list, state);
else if (!strcmp(kin->name, "LOCAL_MAILBOX")) {
state->local_only = 1;
r = sync_apply_mailbox(kin, reserve_list, state);
}
else if (!strcmp(kin->name, "QUOTA"))
r = sync_apply_quota(kin, state);
else if (!strcmp(kin->name, "SEEN"))
r = sync_apply_seen(kin, state);
else if (!strcmp(kin->name, "RENAME"))
r = sync_apply_rename(kin, state);
else if (!strcmp(kin->name, "LOCAL_RENAME")) {
state->local_only = 1;
r = sync_apply_rename(kin, state);
}
else if (!strcmp(kin->name, "RESERVE"))
r = sync_apply_reserve(kin, reserve_list, state);
else if (!strcmp(kin->name, "SIEVE"))
r = sync_apply_sieve(kin, state);
else if (!strcmp(kin->name, "SUB"))
r = sync_apply_changesub(kin, state);
/* "un"dump protocol ;) */
else if (!strcmp(kin->name, "UNACTIVATE_SIEVE"))
r = sync_apply_unactivate_sieve(kin, state);
else if (!strcmp(kin->name, "UNANNOTATION"))
r = sync_apply_unannotation(kin, state);
else if (!strcmp(kin->name, "UNMAILBOX"))
r = sync_apply_unmailbox(kin, state);
else if (!strcmp(kin->name, "LOCAL_UNMAILBOX")) {
state->local_only = 1;
r = sync_apply_unmailbox(kin, state);
}
else if (!strcmp(kin->name, "UNQUOTA"))
r = sync_apply_unquota(kin, state);
else if (!strcmp(kin->name, "UNSIEVE"))
r = sync_apply_unsieve(kin, state);
else if (!strcmp(kin->name, "UNSUB"))
r = sync_apply_changesub(kin, state);
/* user is a special case that's not paired, there's no "upload user"
* as such - we just call the individual commands with their items */
else if (!strcmp(kin->name, "UNUSER"))
r = sync_apply_unuser(kin, state);
else if (!strcmp(kin->name, "LOCAL_UNUSER")) {
state->local_only = 1;
r = sync_apply_unuser(kin, state);
}
else {
xsyslog(LOG_ERR, "SYNCERROR: unknown command",
"command=<%s>",
kin->name);
r = IMAP_PROTOCOL_ERROR;
}
return sync_response(r);
}
EXPORTED const char *sync_get(struct dlist *kin, struct sync_state *state)
{
int r = IMAP_PROTOCOL_ERROR;
ucase(kin->name);
if (!strcmp(kin->name, "ANNOTATION"))
r = sync_get_annotation(kin, state);
else if (!strcmp(kin->name, "FETCH"))
r = sync_get_message(kin, state);
else if (!strcmp(kin->name, "FETCH_SIEVE"))
r = sync_get_sieve(kin, state);
else if (!strcmp(kin->name, "FULLMAILBOX"))
r = sync_get_fullmailbox(kin, state);
else if (!strcmp(kin->name, "MAILBOXES"))
r = sync_get_mailboxes(kin, state);
else if (!strcmp(kin->name, "UNIQUEIDS"))
r = sync_get_uniqueids(kin, state);
else if (!strcmp(kin->name, "META"))
r = sync_get_meta(kin, state);
else if (!strcmp(kin->name, "QUOTA"))
r = sync_get_quota(kin, state);
else if (!strcmp(kin->name, "USER"))
r = sync_get_user(kin, state);
else
r = IMAP_PROTOCOL_ERROR;
return sync_response(r);
}
EXPORTED const char *sync_restore(struct dlist *kin,
struct sync_reserve_list *reserve_list,
struct sync_state *state)
{
int r = IMAP_PROTOCOL_ERROR;
ucase(kin->name);
if (!strcmp(kin->name, "MAILBOX"))
r = sync_restore_mailbox(kin, reserve_list, state);
else if (!strcmp(kin->name, "LOCAL_MAILBOX")) {
state->local_only = 1;
r = sync_restore_mailbox(kin, reserve_list, state);
}
else {
xsyslog(LOG_ERR, "SYNCERROR: unknown command",
"command=<%s>",
kin->name);
r = IMAP_PROTOCOL_ERROR;
}
return sync_response(r);
}
/* ====================================================================== */
static int do_unuser(struct sync_client_state *sync_cs, const char *userid)
{
const char *cmd = "UNUSER";
struct mailbox *mailbox = NULL;
struct dlist *kl;
int r;
/* nothing to do if there's no userid */
if (!userid || !userid[0]) {
syslog(LOG_WARNING, "ignoring attempt to %s() without userid", __func__);
return 0;
}
/* check local mailbox first */
char *inbox = mboxname_user_mbox(userid, NULL);
r = mailbox_open_irl(inbox, &mailbox);
/* only remove from server if there's no local mailbox */
if (r == IMAP_MAILBOX_NONEXISTENT) {
kl = dlist_setatom(NULL, cmd, userid);
sync_send_apply(kl, sync_cs->backend->out);
dlist_free(&kl);
r = sync_parse_response(cmd, sync_cs->backend->in, NULL);
if (r == IMAP_MAILBOX_NONEXISTENT) r = 0;
}
mailbox_close(&mailbox);
free(inbox);
return r;
}
/* ====================================================================== */
static int user_sub(struct sync_client_state *sync_cs,
const char *userid, const char *mboxname)
{
int r;
r = mboxlist_checksub(mboxname, userid);
switch (r) {
case CYRUSDB_OK:
return sync_set_sub(sync_cs, userid, mboxname, 1);
case CYRUSDB_NOTFOUND:
return sync_set_sub(sync_cs, userid, mboxname, 0);
default:
return r;
}
}
/* ====================================================================== */
static int do_unmailbox(struct sync_client_state *sync_cs, const char *mboxname)
{
struct mailbox *mailbox = NULL;
int r;
r = mailbox_open_irl(mboxname, &mailbox);
if (r == IMAP_MAILBOX_NONEXISTENT) {
/* make sure there's an explicit local tombstone */
mbentry_t *tombstone = NULL;
r = mboxlist_lookup_allow_all(mboxname, &tombstone, NULL);
if (r == IMAP_MAILBOX_NONEXISTENT) {
// otherwise we don't change anything on the replica
xsyslog(LOG_NOTICE, "SYNCNOTICE: attempt to UNMAILBOX without a tombstone",
"mailbox=<%s>", mboxname);
r = 0;
goto skip;
}
if (r) {
syslog(LOG_ERR, "%s: mboxlist_lookup() failed: %s '%s'",
__func__, mboxname, error_message(r));
}
else if ((tombstone->mbtype & MBTYPE_DELETED) == 0) {
syslog(LOG_ERR, "attempt to UNMAILBOX non-tombstone: \"%s\"",
mboxname);
}
else {
r = sync_do_folder_delete(sync_cs, mboxname);
if (r) {
syslog(LOG_ERR, "%s: sync_do_folder_delete(): failed: %s '%s'",
__func__, mboxname, error_message(r));
}
}
skip:
mboxlist_entry_free(&tombstone);
}
mailbox_close(&mailbox);
return r;
}
/* ====================================================================== */
static void remove_meta(char *user, struct sync_action_list *list)
{
struct sync_action *action;
for (action = list->head ; action ; action = action->next) {
if (!strcmp(user, action->user)) {
action->active = 0;
}
}
}
/* ====================================================================== */
#define report_verbose(...) syslog(LOG_INFO, __VA_ARGS__)
#define report_verbose_error(...) syslog(LOG_ERR, __VA_ARGS__)
static int do_mailboxes(struct sync_client_state *sync_cs,
struct sync_name_list *mboxname_list,
struct sync_action_list *user_list,
int flags)
{
struct sync_name *mbox;
int r = 0;
if (mboxname_list->count) {
r = sync_do_mailboxes(sync_cs, mboxname_list, NULL, flags);
if (sync_cs->channel && r == IMAP_MAILBOX_LOCKED) {
for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
if (mbox->mark) continue;
sync_log_channel_mailbox(sync_cs->channel, mbox->name);
report_verbose(" Deferred: MAILBOX %s\n", mbox->name);
}
r = 0;
}
else if (r && r != IMAP_BYE_LOGOUT) {
/* promote failed personal mailboxes to USER */
int nonuser = 0;
for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
/* done OK? Good :) */
if (mbox->mark)
continue;
char *userid = mboxname_to_userid(mbox->name);
if (userid) {
mbox->mark = 1;
sync_action_list_add(user_list, NULL, userid);
report_verbose(" Promoting: MAILBOX %s -> USER %s\n",
mbox->name, userid);
free(userid);
}
else
nonuser = 1; /* there was a non-user mailbox */
}
if (!nonuser) r = 0;
}
}
return r;
}
int sync_do_restart(struct sync_client_state *sync_cs)
{
sync_send_restart(sync_cs->backend->out);
return sync_parse_response("RESTART", sync_cs->backend->in, NULL);
}
struct split_user_mailboxes_rock {
struct sync_client_state *sync_cs;
struct sync_action_list *user_list;
int r;
};
static void split_user_mailboxes(const char *key __attribute__((unused)),
void *data,
void *rock)
{
struct split_user_mailboxes_rock *smrock =
(struct split_user_mailboxes_rock *) rock;
struct sync_action_list *mailbox_list = (struct sync_action_list *) data;
struct sync_name_list *mboxname_list = sync_name_list_create();;
struct sync_action *action;
for (action = mailbox_list->head; action; action = action->next) {
if (!action->active)
continue;
sync_name_list_add(mboxname_list, action->name);
mbentry_t *mbentry_byname = NULL;
mbentry_t *mbentry_byid = NULL;
int r = mboxlist_lookup_allow_all(action->name, &mbentry_byname, NULL);
if (!r) r = mboxlist_lookup_by_uniqueid(mbentry_byname->uniqueid, &mbentry_byid, NULL);
if (!r) {
int i;
for (i = 0; i < ptrarray_size(&mbentry_byid->name_history); i++) {
- const former_name_t *fname = ptrarray_nth(&mbentry_byid->name_history, i);
- sync_name_list_add(mboxname_list, fname->name);
+ const former_name_t *histitem = ptrarray_nth(&mbentry_byid->name_history, i);
+ sync_name_list_add(mboxname_list, histitem->name);
}
}
mboxlist_entry_free(&mbentry_byid);
mboxlist_entry_free(&mbentry_byname);
}
if (mboxname_list->count) {
syslog(LOG_DEBUG, "sync_mailboxes: doing %lu",
mboxname_list->count);
smrock->r = do_mailboxes(smrock->sync_cs, mboxname_list,
smrock->user_list, smrock->sync_cs->flags);
if (!smrock->r) smrock->r = sync_do_restart(smrock->sync_cs);
}
sync_name_list_free(&mboxname_list);
}
/* need this lil wrapper for free_hash_table callback */
static void sync_action_list_free_wrapper(void *p)
{
struct sync_action_list *l = (struct sync_action_list *) p;
sync_action_list_free(&l);
}
int sync_do_reader(struct sync_client_state *sync_cs, sync_log_reader_t *slr)
{
struct sync_action_list *user_list = sync_action_list_create();
struct sync_action_list *unuser_list = sync_action_list_create();
struct sync_action_list *meta_list = sync_action_list_create();
struct sync_action_list *unmailbox_list = sync_action_list_create();
struct sync_action_list *quota_list = sync_action_list_create();
struct sync_action_list *annot_list = sync_action_list_create();
struct sync_action_list *seen_list = sync_action_list_create();
struct sync_action_list *sub_list = sync_action_list_create();
hash_table user_mailboxes = HASH_TABLE_INITIALIZER;
const char *args[3];
struct sync_action *action;
int r = 0;
construct_hash_table(&user_mailboxes, 1024 /* XXX */, 0);
while (sync_log_reader_getitem(slr, args) != EOF) {
if (!strcmp(args[0], "USER"))
sync_action_list_add(user_list, NULL, args[1]);
else if (!strcmp(args[0], "UNUSER"))
sync_action_list_add(unuser_list, NULL, args[1]);
else if (!strcmp(args[0], "META"))
sync_action_list_add(meta_list, NULL, args[1]);
else if (!strcmp(args[0], "SIEVE"))
sync_action_list_add(meta_list, NULL, args[1]);
else if ((!strcmp(args[0], "APPEND")) /* just a mailbox event */
|| (!strcmp(args[0], "MAILBOX"))
|| (!strcmp(args[0], "DOUBLEMAILBOX"))) {
char *freeme = NULL;
const char *userid;
struct sync_action_list *mailbox_list;
userid = freeme = mboxname_to_userid(args[1]);
if (!userid) userid = ""; /* treat non-user mboxes as a single cohort */
mailbox_list = hash_lookup(userid, &user_mailboxes);
if (!mailbox_list) {
mailbox_list = sync_action_list_create();
hash_insert(userid, mailbox_list, &user_mailboxes);
}
sync_action_list_add(mailbox_list, args[1], NULL);
if (args[2]) {
/* if there's a second MAILBOX recorded (i.e. a copy or move), add
* it to the same user's mailbox_list (even if it's a diff user),
* so that the order doesn't get lost.
*/
sync_action_list_add(mailbox_list, args[2], NULL);
}
free(freeme);
}
else if (!strcmp(args[0], "RENAME")) {
char *freeme1 = NULL, *freeme2 = NULL;
const char *userid1, *userid2;
struct sync_action_list *mailbox_list;
userid1 = freeme1 = mboxname_to_userid(args[1]);
if (!userid1) userid1 = "";
userid2 = freeme2 = mboxname_to_userid(args[2]);
if (!userid2) userid2 = "";
/* add both mboxnames to the list for the first one's user */
mailbox_list = hash_lookup(userid1, &user_mailboxes);
if (!mailbox_list) {
mailbox_list = sync_action_list_create();
hash_insert(userid1, mailbox_list, &user_mailboxes);
}
sync_action_list_add(mailbox_list, args[1], NULL);
sync_action_list_add(mailbox_list, args[2], NULL);
/* if the second mboxname's user is different, add both names there too */
if (strcmp(userid1, userid2) != 0) {
mailbox_list = hash_lookup(userid2, &user_mailboxes);
if (!mailbox_list) {
mailbox_list = sync_action_list_create();
hash_insert(userid2, mailbox_list, &user_mailboxes);
}
sync_action_list_add(mailbox_list, args[1], NULL);
sync_action_list_add(mailbox_list, args[2], NULL);
}
free(freeme1);
free(freeme2);
}
else if (!strcmp(args[0], "UNMAILBOX"))
sync_action_list_add(unmailbox_list, args[1], NULL);
else if (!strcmp(args[0], "QUOTA"))
sync_action_list_add(quota_list, args[1], NULL);
else if (!strcmp(args[0], "ANNOTATION"))
sync_action_list_add(annot_list, args[1], NULL);
else if (!strcmp(args[0], "SEEN"))
sync_action_list_add(seen_list, args[2], args[1]);
else if (!strcmp(args[0], "SUB"))
sync_action_list_add(sub_list, args[2], args[1]);
else if (!strcmp(args[0], "UNSUB"))
sync_action_list_add(sub_list, args[2], args[1]);
else
syslog(LOG_ERR, "Unknown action type: %s", args[0]);
}
/* Optimise out redundant clauses */
for (action = user_list->head; action; action = action->next) {
/* remove per-user items */
remove_meta(action->user, meta_list);
remove_meta(action->user, seen_list);
remove_meta(action->user, sub_list);
}
/* duplicate removal for unuser - we also strip all the user events */
for (action = unuser_list->head; action; action = action->next) {
/* remove per-user items */
remove_meta(action->user, meta_list);
remove_meta(action->user, seen_list);
remove_meta(action->user, sub_list);
/* unuser trumps user */
remove_meta(action->user, user_list);
}
for (action = meta_list->head; action; action = action->next) {
/* META action overrides any user SEEN or SUB/UNSUB action
for same user */
remove_meta(action->user, seen_list);
remove_meta(action->user, sub_list);
}
/* And then run tasks. */
if (hash_numrecords(&user_mailboxes)) {
struct split_user_mailboxes_rock smrock;
smrock.sync_cs = sync_cs;
smrock.user_list = user_list;
smrock.r = 0;
/* process user_mailboxes in sets of ~1000, splitting only on
* user boundaries */
hash_enumerate(&user_mailboxes, split_user_mailboxes, &smrock);
r = smrock.r;
if (r) goto cleanup;
}
for (action = quota_list->head; action; action = action->next) {
if (!action->active)
continue;
r = sync_do_quota(sync_cs, action->name);
if (sync_cs->channel && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_quota(sync_cs->channel, action->name);
report_verbose(" Deferred: QUOTA %s\n", action->name);
}
else if (r == IMAP_BYE_LOGOUT) {
goto cleanup;
}
else if (r) {
sync_action_list_add(user_list, action->name, NULL);
report_verbose(" Promoting: QUOTA %s -> USER %s\n",
action->name, action->name);
}
}
for (action = annot_list->head; action; action = action->next) {
if (!action->active)
continue;
/* NOTE: ANNOTATION "" is a special case - it's a server
* annotation, hence the check for a character at the
* start of the name */
r = sync_do_annotation(sync_cs, action->name);
if (!*action->name) continue;
if (sync_cs->channel && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_annotation(sync_cs->channel, action->name);
report_verbose(" Deferred: ANNOTATION %s\n", action->name);
}
else if (r == IMAP_BYE_LOGOUT) {
goto cleanup;
}
else if (r) {
sync_action_list_add(user_list, action->name, NULL);
report_verbose(" Promoting: ANNOTATION %s -> USER %s\n",
action->name, action->name);
}
}
for (action = seen_list->head; action; action = action->next) {
if (!action->active)
continue;
r = sync_do_seen(sync_cs, action->user, action->name);
if (sync_cs->channel && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_seen(sync_cs->channel, action->user, action->name);
report_verbose(" Deferred: SEEN %s %s\n",
action->user, action->name);
}
else if (r == IMAP_BYE_LOGOUT) {
goto cleanup;
}
else if (r) {
char *userid = mboxname_to_userid(action->name);
if (userid && mboxname_isusermailbox(action->name, 1) && !strcmp(userid, action->user)) {
sync_action_list_add(user_list, NULL, action->user);
report_verbose(" Promoting: SEEN %s %s -> USER %s\n",
action->user, action->name, action->user);
} else {
sync_action_list_add(meta_list, NULL, action->user);
report_verbose(" Promoting: SEEN %s %s -> META %s\n",
action->user, action->name, action->user);
}
free(userid);
}
}
for (action = sub_list->head; action; action = action->next) {
if (!action->active)
continue;
r = user_sub(sync_cs, action->user, action->name);
if (sync_cs->channel && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_subscribe(sync_cs->channel, action->user, action->name);
report_verbose(" Deferred: SUB %s %s\n",
action->user, action->name);
}
else if (r == IMAP_BYE_LOGOUT) {
goto cleanup;
}
else if (r) {
sync_action_list_add(meta_list, NULL, action->user);
report_verbose(" Promoting: SUB %s %s -> META %s\n",
action->user, action->name, action->user);
}
}
/* XXX - is unmailbox used much anyway - we need to see if it's logged for a rename,
* e.g.
* RENAME A B:
* MAILBOX A
* MAILBOX B
* UNMAILBOX A
*
* suggestion: PROMOTE ALL UNMAILBOX on user accounts to USER foo
*/
for (action = unmailbox_list->head; action; action = action->next) {
if (!action->active)
continue;
r = do_unmailbox(sync_cs, action->name);
if (sync_cs->channel && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_unmailbox(sync_cs->channel, action->name);
report_verbose(" Deferred: UNMAILBOX %s\n", action->name);
}
else if (r) goto cleanup;
}
for (action = meta_list->head; action; action = action->next) {
if (!action->active)
continue;
r = sync_do_meta(sync_cs, action->user);
if (sync_cs->channel && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_sieve(sync_cs->channel, action->user);
report_verbose(" Deferred: META %s\n", action->user);
}
else if (r == IMAP_INVALID_USER || r == IMAP_BYE_LOGOUT) {
goto cleanup;
}
else if (r) {
sync_action_list_add(user_list, NULL, action->user);
report_verbose(" Promoting: META %s -> USER %s\n",
action->user, action->user);
}
}
for (action = user_list->head; action; action = action->next) {
if (!action->active)
continue;
r = sync_do_user(sync_cs, action->user, NULL);
if (sync_cs->channel && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_user(sync_cs->channel, action->user);
report_verbose(" Deferred: USER %s\n", action->user);
}
else if (r) goto cleanup;
r = sync_do_restart(sync_cs);
if (r) goto cleanup;
}
for (action = unuser_list->head; action; action = action->next) {
if (!action->active)
continue;
r = do_unuser(sync_cs, action->user);
if (sync_cs->channel && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_unuser(sync_cs->channel, action->user);
report_verbose(" Deferred: UNUSER %s\n", action->user);
}
else if (r) goto cleanup;
}
cleanup:
if (r && r != IMAP_BYE_LOGOUT) {
report_verbose_error("Error in do_sync(): bailing out! %s", error_message(r));
}
sync_action_list_free(&user_list);
sync_action_list_free(&unuser_list);
sync_action_list_free(&meta_list);
sync_action_list_free(&unmailbox_list);
sync_action_list_free(&quota_list);
sync_action_list_free(&annot_list);
sync_action_list_free(&seen_list);
sync_action_list_free(&sub_list);
free_hash_table(&user_mailboxes, sync_action_list_free_wrapper);
return r;
}
EXPORTED int sync_connect(struct sync_client_state *sync_cs)
{
sasl_callback_t *cb;
int timeout;
const char *port, *auth_status = NULL;
int try_imap;
struct backend *backend = sync_cs->backend;
int verbose = (sync_cs->flags & SYNC_FLAG_VERBOSE);
sync_cs->backend = NULL;
buf_free(&sync_cs->tagbuf);
cb = mysasl_callbacks(NULL,
sync_get_config(sync_cs->channel, "sync_authname"),
sync_get_config(sync_cs->channel, "sync_realm"),
sync_get_config(sync_cs->channel, "sync_password"));
/* get the right port */
port = sync_get_config(sync_cs->channel, "sync_port");
if (port) {
imap_csync_protocol.service = port;
csync_protocol.service = port;
}
try_imap = sync_get_switchconfig(sync_cs->channel, "sync_try_imap");
if (try_imap) {
backend = backend_connect(backend, sync_cs->servername,
&imap_csync_protocol, "", cb, &auth_status,
(verbose > 1 ? fileno(stderr) : -1));
if (backend) {
if (backend->capability & CAPA_REPLICATION) {
/* attach our IMAP tag buffer to our protstreams as userdata */
backend->in->userdata = backend->out->userdata = &sync_cs->tagbuf;
goto connected;
}
else {
backend_disconnect(backend);
backend = NULL;
}
}
}
backend = backend_connect(backend, sync_cs->servername,
&csync_protocol, "", cb, NULL,
(verbose > 1 ? fileno(stderr) : -1));
// auth_status means there was an error
if (!backend) {
free_callbacks(cb);
return IMAP_AGAIN;
}
connected:
free_callbacks(cb);
cb = NULL;
if (sync_cs->servername[0] != '/' && backend->sock >= 0) {
tcp_disable_nagle(backend->sock);
tcp_enable_keepalive(backend->sock);
}
#ifdef HAVE_ZLIB
/* Does the backend support compression? */
if (CAPA(backend, CAPA_COMPRESS)) {
prot_printf(backend->out, "%s\r\n",
backend->prot->u.std.compress_cmd.cmd);
prot_flush(backend->out);
if (sync_parse_response("COMPRESS", backend->in, NULL)) {
syslog(LOG_NOTICE, "Failed to enable compression, continuing uncompressed");
}
else {
prot_setcompress(backend->in);
prot_setcompress(backend->out);
}
}
#endif
/* Set inactivity timer */
timeout = config_getduration(IMAPOPT_SYNC_TIMEOUT, 's');
if (timeout < 3) timeout = 3;
prot_settimeout(backend->in, timeout);
/* Force use of LITERAL+ so we don't need two way communications */
prot_setisclient(backend->in, 1);
prot_setisclient(backend->out, 1);
sync_cs->backend = backend;
return 0;
}
EXPORTED void sync_disconnect(struct sync_client_state *sync_cs)
{
if (!sync_cs->backend) return;
if (sync_cs->backend->timeout)
prot_removewaitevent(sync_cs->clientin, sync_cs->backend->timeout);
sync_cs->clientin = NULL;
sync_cs->backend->timeout = NULL;
backend_disconnect(sync_cs->backend);
// backend may have put stuff here, free it so we don't leak memory
buf_free(&sync_cs->tagbuf);
// drop any cache database too
if (sync_cs->cachedb) {
cyrusdb_close(sync_cs->cachedb);
sync_cs->cachedb = NULL;
}
}
static struct prot_waitevent *
sync_rightnow_timeout(struct protstream *s __attribute__((unused)),
struct prot_waitevent *ev __attribute__((unused)),
void *rock __attribute__((unused)))
{
syslog(LOG_DEBUG, "sync_rightnow_timeout()");
/* too long since we last used the syncer - disconnect */
sync_disconnect(&rightnow_sync_cs);
free(rightnow_sync_cs.backend);
rightnow_sync_cs.backend = NULL;
return NULL;
}
EXPORTED int sync_checkpoint(struct protstream *clientin)
{
struct buf *buf = sync_log_rightnow_buf();
if (!buf) return 0;
time_t when = time(NULL) + 30;
if (rightnow_sync_cs.backend) {
if (rightnow_sync_cs.backend->timeout->mark) {
rightnow_sync_cs.backend->timeout->mark = when;
}
}
else {
const char *conf = config_getstring(IMAPOPT_SYNC_RIGHTNOW_CHANNEL);
if (conf && strcmp(conf, "\"\""))
rightnow_sync_cs.channel = conf;
rightnow_sync_cs.servername = sync_get_config(rightnow_sync_cs.channel, "sync_host");
rightnow_sync_cs.flags = SYNC_FLAG_LOGGING;
syslog(LOG_DEBUG, "sync_rightnow_connect(%s)", rightnow_sync_cs.servername);
sync_connect(&rightnow_sync_cs);
if (!rightnow_sync_cs.backend) {
syslog(LOG_ERR, "SYNCERROR sync_rightnow: failed to connect to server: %s",
rightnow_sync_cs.servername);
// dammit, but the show must go on
buf_reset(buf);
return 0;
}
rightnow_sync_cs.clientin = clientin;
rightnow_sync_cs.backend->timeout
= prot_addwaitevent(clientin, when, sync_rightnow_timeout, NULL);
}
sync_log_reader_t *slr = sync_log_reader_create_with_content(buf_cstring(buf));
int r = sync_log_reader_begin(slr);
if (!r) r = sync_do_reader(&rightnow_sync_cs, slr);
if (r) {
syslog(LOG_ERR, "SYNCERROR sync_rightnow: error syncing to: %s (%s)",
rightnow_sync_cs.servername, error_message(r));
}
sync_log_reader_end(slr);
sync_log_reader_free(slr);
// mark these items consumed!
buf_reset(buf);
return 0;
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Apr 24, 10:21 AM (6 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18879309
Default Alt Text
(369 KB)

Event Timeline