#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <ctype.h>
#include <syslog.h>
#include "acl.h"
#include "assert.h"
#include "cyrusdb.h"
#include "exitcodes.h"
#include "glob.h"
#include "hash.h"
#include "imapd.h"
#include "global.h"
#include "imap_err.h"
#include "mboxlist.h"
#include "util.h"
#include "xmalloc.h"
#include "annotate.h"
#define DB config_annotation_db
extern void printstring(const char *s);
extern void appendattvalue(struct attvaluelist **l, char *attrib,
const char *value);
extern void freeattvalues(struct attvaluelist *l);
struct db *anndb;
static int annotate_dbopen = 0;
int (*proxy_fetch_func)(const char *server, const char *mbox_pat,
struct strlist *entry_pat,
struct strlist *attribute_pat) = NULL;
int (*proxy_store_func)(const char *server, const char *mbox_pat,
struct entryattlist *entryatts) = NULL;
void appendstrlist_withdata(struct strlist **l, char *s, void *d, size_t size)
{
struct strlist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct strlist *)xmalloc(sizeof(struct strlist));
(*tail)->s = xstrdup(s);
(*tail)->p = 0;
if(d && size) {
(*tail)->rock = xmalloc(size);
memcpy((*tail)->rock, d, size);
} else {
(*tail)->rock = NULL;
}
(*tail)->next = 0;
}
void appendstrlist(struct strlist **l, char *s)
{
appendstrlist_withdata(l, s, NULL, 0);
}
void appendstrlistpat(struct strlist **l, char *s)
{
struct strlist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct strlist *)xmalloc(sizeof(struct strlist));
(*tail)->s = s;
(*tail)->rock = NULL;
(*tail)->p = charset_compilepat(s);
(*tail)->next = 0;
}
void freestrlist(struct strlist *l)
{
struct strlist *n;
while (l) {
n = l->next;
free(l->s);
if (l->p) charset_freepat(l->p);
if (l->rock) free(l->rock);
free((char *)l);
l = n;
}
}
void appendattvalue(struct attvaluelist **l, char *attrib, const char *value)
{
struct attvaluelist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct attvaluelist *)xmalloc(sizeof(struct attvaluelist));
(*tail)->attrib = xstrdup(attrib);
(*tail)->value = xstrdup(value);
(*tail)->next = 0;
}
void freeattvalues(struct attvaluelist *l)
{
struct attvaluelist *n;
while (l) {
n = l->next;
free(l->attrib);
free(l->value);
l = n;
}
}
void appendentryatt(struct entryattlist **l, char *entry,
struct attvaluelist *attvalues)
{
struct entryattlist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct entryattlist *)xmalloc(sizeof(struct entryattlist));
(*tail)->entry = xstrdup(entry);
(*tail)->attvalues = attvalues;
(*tail)->next = NULL;
}
void freeentryatts(struct entryattlist *l)
{
struct entryattlist *n;
while (l) {
n = l->next;
free(l->entry);
if (l->attvalues) freeattvalues(l->attvalues);
l = n;
}
}
void annotatemore_init(int myflags,
int (*fetch_func)(const char *, const char *,
struct strlist *, struct strlist *),
int (*store_func)(const char *, const char *,
struct entryattlist *))
{
int r;
if (myflags & ANNOTATE_SYNC) {
r = DB->sync();
}
if (fetch_func) {
proxy_fetch_func = fetch_func;
}
if (store_func) {
proxy_store_func = store_func;
}
}
void annotatemore_open(char *fname)
{
int ret;
char *tofree = NULL;
if (!fname) {
fname = xmalloc(strlen(config_dir)+sizeof(FNAME_ANNOTATIONS));
tofree = fname;
strcpy(fname, config_dir);
strcat(fname, FNAME_ANNOTATIONS);
}
ret = DB->open(fname, CYRUSDB_CREATE, &anndb);
if (ret != 0) {
syslog(LOG_ERR, "DBERROR: opening %s: %s", fname,
cyrusdb_strerror(ret));
fatal("can't read annotations file", EC_TEMPFAIL);
}
if (tofree) free(tofree);
annotate_dbopen = 1;
}
void annotatemore_close(void)
{
int r;
if (annotate_dbopen) {
r = DB->close(anndb);
if (r) {
syslog(LOG_ERR, "DBERROR: error closing annotations: %s",
cyrusdb_strerror(r));
}
annotate_dbopen = 0;
}
}
void annotatemore_done(void)
{
}
static int make_key(const char *mboxname, const char *entry,
const char *userid, char *key, size_t keysize)
{
int keylen = 0;
strlcpy(key+keylen, mboxname, keysize-keylen);
keylen += strlen(mboxname) + 1;
strlcpy(key+keylen, entry, keysize-keylen);
keylen += strlen(entry);
if (userid) {
keylen++;
strlcpy(key+keylen, userid, keysize-keylen);
keylen += strlen(userid) + 1;
}
return keylen;
}
static int split_attribs(const char *data, int datalen __attribute__((unused)),
struct annotation_data *attrib)
{
unsigned long tmp;
attrib->size = (size_t) ntohl(*(unsigned long *) data);
data += sizeof(unsigned long);
attrib->value = data;
data += strlen(data) + 1;
attrib->contenttype = data;
data += strlen(data) + 1;
memcpy(&tmp, data, sizeof(unsigned long));
attrib->modifiedsince = (size_t) ntohl(tmp);
data += sizeof(unsigned long);
return 0;
}
struct find_rock {
struct glob *mglob;
struct glob *eglob;
int (*proc)();
void *rock;
};
static int find_p(void *rock, const char *key,
int keylen __attribute__((unused)),
const char *data __attribute__((unused)),
int datalen __attribute__((unused)))
{
struct find_rock *frock = (struct find_rock *) rock;
const char *mboxname, *entry, *userid;
mboxname = key;
entry = mboxname + strlen(mboxname) + 1;
userid = entry + strlen(entry) + 1;
return ((GLOB_TEST(frock->mglob, mboxname) != -1) &&
(GLOB_TEST(frock->eglob, entry) != -1));
}
static int find_cb(void *rock, const char *key,
int keylen __attribute__((unused)),
const char *data, int datalen)
{
struct find_rock *frock = (struct find_rock *) rock;
const char *mboxname, *entry, *userid;
struct annotation_data attrib;
int r;
mboxname = key;
entry = mboxname + strlen(mboxname) + 1;
userid = entry + strlen(entry) + 1;
r = split_attribs(data, datalen, &attrib);
if (!r) r = frock->proc(mboxname, entry, userid, &attrib, frock->rock);
return r;
}
int annotatemore_findall(const char *mailbox, const char *entry,
int (*proc)(), void *rock, struct txn **tid)
{
char key[MAX_MAILBOX_PATH+1], *p;
int keylen, r;
struct find_rock frock;
frock.mglob = glob_init(mailbox, GLOB_HIERARCHY);
frock.eglob = glob_init(entry, GLOB_HIERARCHY);
GLOB_SET_SEPARATOR(frock.eglob, '/');
frock.proc = proc;
frock.rock = rock;
keylen = make_key(mailbox, entry, NULL, key, sizeof(key));
for (p = key; keylen; p++, keylen--) {
if (*p == '*' || *p == '%') break;
}
keylen = p - key;
r = DB->foreach(anndb, key, keylen, &find_p, &find_cb, &frock, tid);
glob_free(&frock.mglob);
glob_free(&frock.eglob);
return r;
}
enum {
ATTRIB_VALUE_SHARED = (1<<0),
ATTRIB_VALUE_PRIV = (1<<1),
ATTRIB_SIZE_SHARED = (1<<2),
ATTRIB_SIZE_PRIV = (1<<3),
ATTRIB_MODIFIEDSINCE_SHARED = (1<<4),
ATTRIB_MODIFIEDSINCE_PRIV = (1<<5),
ATTRIB_CONTENTTYPE_SHARED = (1<<6),
ATTRIB_CONTENTTYPE_PRIV = (1<<7)
};
typedef enum {
ANNOTATION_PROXY_T_INVALID = 0,
PROXY_ONLY = 1,
BACKEND_ONLY = 2,
PROXY_AND_BACKEND = 3
} annotation_proxy_t;
struct mailbox_annotation_rock
{
char *server, *partition, *acl, *path;
};
static void cleanup_mbrock(struct mailbox_annotation_rock *mbrock __attribute__((unused)))
{
return;
}
static void get_mb_data(const char *mboxname,
struct mailbox_annotation_rock *mbrock)
{
if(!mbrock->server && !mbrock->partition) {
int r = mboxlist_detail(mboxname, NULL, &(mbrock->path),
&(mbrock->server), &(mbrock->acl), NULL);
if (r) return;
mbrock->partition = strchr(mbrock->server, '!');
if (mbrock->partition) {
*(mbrock->partition)++ = '\0';
mbrock->path = NULL;
} else {
mbrock->partition = mbrock->server;
mbrock->server = NULL;
}
}
}
struct fetchdata {
struct namespace *namespace;
struct protstream *pout;
char *userid;
int isadmin;
struct auth_state *auth_state;
struct annotate_f_entry_list *entry_list;
unsigned attribs;
struct entryattlist **entryatts;
struct hash_table entry_table;
struct hash_table server_table;
const char *orig_mailbox;
struct strlist *orig_entry;
struct strlist *orig_attribute;
};
static void output_attlist(struct protstream *pout, struct attvaluelist *l)
{
int flag = 0;
assert(l);
prot_putc('(',pout);
while(l) {
if(flag) prot_putc(' ',pout);
else flag = 1;
printstring(l->attrib);
prot_putc(' ',pout);
printstring(l->value);
l = l->next;
}
prot_putc(')',pout);
}
static void output_entryatt(const char *mboxname, const char *entry,
const char *userid, struct annotation_data *attrib,
struct fetchdata *fdata)
{
static struct attvaluelist *attvalues = NULL;
static char lastname[MAX_MAILBOX_NAME+1];
static char lastentry[MAX_MAILBOX_NAME+1];
char key[MAX_MAILBOX_PATH+1];
char buf[100];
if (!mboxname || !entry) {
attvalues = NULL;
lastname[0] = '\0';
lastentry[0] = '\0';
return;
}
if ((!attrib || strcmp(mboxname, lastname) || strcmp(entry, lastentry))
&& attvalues) {
prot_printf(fdata->pout, "* ANNOTATION \"%s\" \"%s\" ",
lastname, lastentry);
output_attlist(fdata->pout, attvalues);
prot_printf(fdata->pout, "\r\n");
freeattvalues(attvalues);
attvalues = NULL;
}
if (!attrib) return;
strlcpy(lastname, mboxname, sizeof(lastname));
strlcpy(lastentry, entry, sizeof(lastentry));
strlcpy(key, mboxname, sizeof(key));
strlcat(key, entry, sizeof(key));
strlcat(key, userid, sizeof(key));
if (hash_lookup(key, &(fdata->entry_table))) return;
hash_insert(key, (void *)0xDEADBEEF, &(fdata->entry_table));
if (!userid[0]) {
if ((fdata->attribs & ATTRIB_VALUE_SHARED) && attrib->value) {
appendattvalue(&attvalues, "value.shared", attrib->value);
}
if ((fdata->attribs & ATTRIB_CONTENTTYPE_SHARED)
&& attrib->value && attrib->contenttype) {
appendattvalue(&attvalues, "content-type.shared",
attrib->contenttype);
}
if ((fdata->attribs & ATTRIB_SIZE_SHARED) && attrib->value) {
snprintf(buf, sizeof(buf), "%u", attrib->size);
appendattvalue(&attvalues, "size.shared", buf);
}
if ((fdata->attribs & ATTRIB_MODIFIEDSINCE_SHARED)
&& attrib->value && attrib->modifiedsince) {
snprintf(buf, sizeof(buf), "%ld", attrib->modifiedsince);
appendattvalue(&attvalues, "modifiedsince.shared", buf);
}
}
else {
if ((fdata->attribs & ATTRIB_VALUE_PRIV) && attrib->value) {
appendattvalue(&attvalues, "value.priv", attrib->value);
}
if ((fdata->attribs & ATTRIB_CONTENTTYPE_PRIV)
&& attrib->value && attrib->contenttype) {
appendattvalue(&attvalues, "content-type.priv",
attrib->contenttype);
}
if ((fdata->attribs & ATTRIB_SIZE_PRIV) && attrib->value) {
snprintf(buf, sizeof(buf), "%u", attrib->size);
appendattvalue(&attvalues, "size.priv", buf);
}
if ((fdata->attribs & ATTRIB_MODIFIEDSINCE_PRIV)
&& attrib->value && attrib->modifiedsince) {
snprintf(buf, sizeof(buf), "%ld", attrib->modifiedsince);
appendattvalue(&attvalues, "modifiedsince.priv", buf);
}
}
}
static void annotation_get_fromfile(const char *int_mboxname __attribute__((unused)),
const char *ext_mboxname,
const char *entry,
struct fetchdata *fdata,
struct mailbox_annotation_rock *mbrock __attribute__((unused)),
void *rock)
{
const char *filename = (const char *) rock;
char path[1024], buf[1024], *p;
FILE *f;
struct stat statbuf;
struct annotation_data attrib;
snprintf(path, sizeof(path), "%s/msg/%s", config_dir, filename);
if ((f = fopen(path, "r")) && fgets(buf, sizeof(buf), f)) {
if ((p = strchr(buf, '\r'))!=NULL) *p = 0;
if ((p = strchr(buf, '\n'))!=NULL) *p = 0;
memset(&attrib, 0, sizeof(attrib));
attrib.value = buf;
attrib.size = strlen(buf);
attrib.contenttype = "text/plain";
if (!fstat(fileno(f), &statbuf))
attrib.modifiedsince = statbuf.st_mtime;
output_entryatt(ext_mboxname, entry, "", &attrib, fdata);
}
if (f) fclose(f);
}
static void annotation_get_server(const char *int_mboxname,
const char *ext_mboxname,
const char *entry,
struct fetchdata *fdata,
struct mailbox_annotation_rock *mbrock,
void *rock __attribute__((unused)))
{
struct annotation_data attrib;
if(!int_mboxname || !ext_mboxname || !fdata || !mbrock)
fatal("annotation_get_server called with bad parameters", EC_TEMPFAIL);
get_mb_data(int_mboxname, mbrock);
if(!fdata->isadmin &&
(!mbrock->acl ||
!(cyrus_acl_myrights(fdata->auth_state, mbrock->acl) & ACL_LOOKUP)))
return;
memset(&attrib, 0, sizeof(attrib));
attrib.value = mbrock->server;
if(mbrock->server) {
attrib.size = strlen(mbrock->server);
attrib.contenttype = "text/plain";
}
output_entryatt(ext_mboxname, entry, "", &attrib, fdata);
}
static void annotation_get_partition(const char *int_mboxname,
const char *ext_mboxname,
const char *entry,
struct fetchdata *fdata,
struct mailbox_annotation_rock *mbrock,
void *rock __attribute__((unused)))
{
struct annotation_data attrib;
if(!int_mboxname || !ext_mboxname || !fdata || !mbrock)
fatal("annotation_get_partition called with bad parameters",
EC_TEMPFAIL);
get_mb_data(int_mboxname, mbrock);
if(!fdata->isadmin &&
(!mbrock->acl ||
!(cyrus_acl_myrights(fdata->auth_state, mbrock->acl) & ACL_LOOKUP)))
return;
memset(&attrib, 0, sizeof(attrib));
attrib.value = mbrock->partition;
if(mbrock->partition) {
attrib.size = strlen(mbrock->partition);
attrib.contenttype = "text/plain";
}
output_entryatt(ext_mboxname, entry, "", &attrib, fdata);
}
static void annotation_get_size(const char *int_mboxname,
const char *ext_mboxname,
const char *entry,
struct fetchdata *fdata,
struct mailbox_annotation_rock *mbrock,
void *rock __attribute__((unused)))
{
struct mailbox mailbox;
struct index_record record;
int r = 0, msg;
unsigned long totsize = 0;
char value[21];
struct annotation_data attrib;
if(!int_mboxname || !ext_mboxname || !fdata || !mbrock)
fatal("annotation_get_size called with bad parameters",
EC_TEMPFAIL);
get_mb_data(int_mboxname, mbrock);
if(!fdata->isadmin &&
(!mbrock->acl ||
!(cyrus_acl_myrights(fdata->auth_state, mbrock->acl) & ACL_LOOKUP) ||
!(cyrus_acl_myrights(fdata->auth_state, mbrock->acl) & ACL_READ)))
return;
if (mailbox_open_header(int_mboxname, 0, &mailbox) != 0)
return;
if (!r) r = mailbox_open_index(&mailbox);
if (!r) {
for (msg = 1; msg <= mailbox.exists; msg++) {
if ((r = mailbox_read_index_record(&mailbox, msg, &record))!=0)
break;
totsize += record.size;
}
}
mailbox_close(&mailbox);
if (r || snprintf(value, sizeof(value), "%lu", totsize) == -1)
return;
memset(&attrib, 0, sizeof(attrib));
attrib.value = value;
attrib.size = strlen(value);
attrib.contenttype = "text/plain";
output_entryatt(ext_mboxname, entry, "", &attrib, fdata);
}
static void annotation_get_lastupdate(const char *int_mboxname,
const char *ext_mboxname,
const char *entry,
struct fetchdata *fdata,
struct mailbox_annotation_rock *mbrock,
void *rock __attribute__((unused)))
{
time_t modtime = 0;
struct stat header, index;
char valuebuf[128];
struct annotation_data attrib;
if(!int_mboxname || !ext_mboxname || !fdata || !mbrock)
fatal("annotation_get_lastupdate called with bad parameters",
EC_TEMPFAIL);
get_mb_data(int_mboxname, mbrock);
if(!fdata->isadmin &&
(!mbrock->acl ||
!(cyrus_acl_myrights(fdata->auth_state, mbrock->acl) & ACL_LOOKUP) ||
!(cyrus_acl_myrights(fdata->auth_state, mbrock->acl) & ACL_READ)))
return;
if (!mbrock->path) return;
if (mailbox_stat(mbrock->path, &header, &index, NULL)) return;
modtime = (header.st_mtime > index.st_mtime) ?
header.st_mtime : index.st_mtime;
cyrus_ctime(modtime, valuebuf);
memset(&attrib, 0, sizeof(attrib));
attrib.value = valuebuf;
attrib.size = strlen(valuebuf);
attrib.contenttype = "text/plain";
output_entryatt(ext_mboxname, entry, "", &attrib, fdata);
}
struct rw_rock {
const char *ext_mboxname;
struct fetchdata *fdata;
};
static int rw_cb(const char *mailbox __attribute__((unused)),
const char *entry, const char *userid,
struct annotation_data *attrib, void *rock)
{
struct rw_rock *rw_rock = (struct rw_rock *) rock;
if (!userid[0] || !strcmp(userid, rw_rock->fdata->userid)) {
output_entryatt(rw_rock->ext_mboxname, entry, userid, attrib,
rw_rock->fdata);
}
return 0;
}
static void annotation_get_fromdb(const char *int_mboxname,
const char *ext_mboxname,
const char *entry __attribute__((unused)),
struct fetchdata *fdata,
struct mailbox_annotation_rock *mbrock,
void *rock)
{
struct rw_rock rw_rock;
const char *entrypat = (const char *) rock;
if(!int_mboxname || !ext_mboxname || !entrypat || !fdata ||
(int_mboxname[0] && !mbrock)) {
fatal("annotation_get_fromdb called with bad parameters", EC_TEMPFAIL);
}
if (!int_mboxname[0]) {
}
else {
get_mb_data(int_mboxname, mbrock);
if(!fdata->isadmin &&
(!mbrock->acl ||
!(cyrus_acl_myrights(fdata->auth_state, mbrock->acl) & ACL_LOOKUP) ||
!(cyrus_acl_myrights(fdata->auth_state, mbrock->acl) & ACL_READ)))
return;
}
rw_rock.ext_mboxname = ext_mboxname;
rw_rock.fdata = fdata;
annotatemore_findall(int_mboxname, entrypat, &rw_cb, &rw_rock, NULL);
}
struct annotate_f_entry
{
const char *name;
annotation_proxy_t proxytype;
void (*get)(const char *int_mboxname, const char *ext_mboxname,
const char *name, struct fetchdata *fdata,
struct mailbox_annotation_rock *mbrock,
void *rock);
void *rock;
};
struct annotate_f_entry_list
{
const struct annotate_f_entry *entry;
const char *entrypat;
struct annotate_f_entry_list *next;
};
const struct annotate_f_entry mailbox_ro_entries[] =
{
{ "/vendor/cmu/cyrus-imapd/partition", BACKEND_ONLY,
annotation_get_partition, NULL },
{ "/vendor/cmu/cyrus-imapd/server", PROXY_ONLY,
annotation_get_server, NULL },
{ "/vendor/cmu/cyrus-imapd/size", BACKEND_ONLY,
annotation_get_size, NULL },
{ "/vendor/cmu/cyrus-imapd/lastupdate", BACKEND_ONLY,
annotation_get_lastupdate, NULL },
{ NULL, ANNOTATION_PROXY_T_INVALID, NULL, NULL }
};
const struct annotate_f_entry mailbox_rw_entry =
{ NULL, BACKEND_ONLY, annotation_get_fromdb, NULL };
const struct annotate_f_entry server_legacy_entries[] =
{
{ "/motd", PROXY_AND_BACKEND, annotation_get_fromfile, "motd" },
{ "/vendor/cmu/cyrus-imapd/shutdown", PROXY_AND_BACKEND,
annotation_get_fromfile, "shutdown" },
{ NULL, ANNOTATION_PROXY_T_INVALID, NULL, NULL }
};
const struct annotate_f_entry server_entry =
{ NULL, PROXY_AND_BACKEND, annotation_get_fromdb, NULL };
struct annotate_attrib
{
const char *name;
int entry;
};
const struct annotate_attrib annotation_attributes[] =
{
{ "value", ATTRIB_VALUE_SHARED | ATTRIB_VALUE_PRIV },
{ "value.shared", ATTRIB_VALUE_SHARED },
{ "value.priv", ATTRIB_VALUE_PRIV },
{ "size", ATTRIB_SIZE_SHARED | ATTRIB_SIZE_PRIV },
{ "size.shared", ATTRIB_SIZE_SHARED },
{ "size.priv", ATTRIB_SIZE_PRIV },
{ "modifiedsince", ATTRIB_MODIFIEDSINCE_SHARED | ATTRIB_MODIFIEDSINCE_PRIV },
{ "modifiedsince.shared", ATTRIB_MODIFIEDSINCE_SHARED },
{ "modifiedsince.priv", ATTRIB_MODIFIEDSINCE_PRIV },
{ "content-type", ATTRIB_CONTENTTYPE_SHARED | ATTRIB_CONTENTTYPE_PRIV },
{ "content-type.shared", ATTRIB_CONTENTTYPE_SHARED },
{ "content-type.priv", ATTRIB_CONTENTTYPE_PRIV },
{ NULL, 0 }
};
static int fetch_cb(char *name, int matchlen,
int maycreate __attribute__((unused)), void* rock)
{
struct fetchdata *fdata = (struct fetchdata *) rock;
struct annotate_f_entry_list *entries_ptr;
static char lastname[MAX_MAILBOX_PATH+1];
static int sawuser = 0;
int c;
char int_mboxname[MAX_MAILBOX_PATH+1], ext_mboxname[MAX_MAILBOX_PATH+1];
struct mailbox_annotation_rock mbrock;
if (name == NULL) {
sawuser = 0;
lastname[0] = '\0';
return 0;
}
if (name[matchlen] && strncmp(lastname, name, matchlen) == 0) {
return 0;
}
if (matchlen == 4 && strncasecmp(name, "user", 4) == 0) {
if (sawuser) return 0;
sawuser = 1;
}
strlcpy(lastname, name, sizeof(lastname));
lastname[matchlen] = '\0';
if (!strncasecmp(lastname, "INBOX", 5)) {
(*fdata->namespace->mboxname_tointernal)(fdata->namespace, "INBOX",
fdata->userid, int_mboxname);
strlcat(int_mboxname, lastname+5, sizeof(int_mboxname));
}
else
strlcpy(int_mboxname, lastname, sizeof(int_mboxname));
c = name[matchlen];
if (c) name[matchlen] = '\0';
(*fdata->namespace->mboxname_toexternal)(fdata->namespace, name,
fdata->userid, ext_mboxname);
if (c) name[matchlen] = c;
memset(&mbrock, 0, sizeof(struct mailbox_annotation_rock));
if (proxy_fetch_func && fdata->orig_entry) {
get_mb_data(int_mboxname, &mbrock);
}
for (entries_ptr = fdata->entry_list;
entries_ptr;
entries_ptr = entries_ptr->next) {
entries_ptr->entry->get(int_mboxname, ext_mboxname,
entries_ptr->entry->name, fdata,
&mbrock, (entries_ptr->entry->rock ?
entries_ptr->entry->rock :
(void*) entries_ptr->entrypat));
}
if (proxy_fetch_func && fdata->orig_entry
&& !hash_lookup(mbrock.server, &(fdata->server_table))) {
proxy_fetch_func(mbrock.server, fdata->orig_mailbox,
fdata->orig_entry, fdata->orig_attribute);
hash_insert(mbrock.server, (void *)0xDEADBEEF, &(fdata->server_table));
}
cleanup_mbrock(&mbrock);
return 0;
}
int annotatemore_fetch(char *mailbox,
struct strlist *entries, struct strlist *attribs,
struct namespace *namespace, int isadmin, char *userid,
struct auth_state *auth_state, struct protstream *pout)
{
struct strlist *e = entries;
struct strlist *a = attribs;
struct fetchdata fdata;
struct glob *g;
const struct annotate_f_entry *non_db_entries;
const struct annotate_f_entry *db_entry;
memset(&fdata, 0, sizeof(struct fetchdata));
fdata.pout = pout;
fdata.namespace = namespace;
fdata.userid = userid;
fdata.isadmin = isadmin;
fdata.auth_state = auth_state;
output_entryatt(NULL, NULL, NULL, NULL, NULL);
while (a) {
int attribcount;
g = glob_init(a->s, GLOB_HIERARCHY);
for (attribcount = 0;
annotation_attributes[attribcount].name;
attribcount++) {
if (GLOB_TEST(g, annotation_attributes[attribcount].name) != -1) {
fdata.attribs |= annotation_attributes[attribcount].entry;
}
}
glob_free(&g);
a = a->next;
}
if (!fdata.attribs) return 0;
if (!mailbox[0]) {
non_db_entries = server_legacy_entries;
db_entry = &server_entry;
}
else {
non_db_entries = mailbox_ro_entries;
db_entry = &mailbox_rw_entry;
}
while (e) {
int entrycount;
int check_db = 0;
g = glob_init(e->s, GLOB_HIERARCHY);
GLOB_SET_SEPARATOR(g, '/');
for (entrycount = 0;
non_db_entries[entrycount].name;
entrycount++) {
if (GLOB_TEST(g, non_db_entries[entrycount].name) != -1) {
if (strcmp(e->s, non_db_entries[entrycount].name)) {
fdata.orig_entry = entries;
check_db = 1;
}
if (non_db_entries[entrycount].proxytype == PROXY_AND_BACKEND
|| (proxy_fetch_func &&
non_db_entries[entrycount].proxytype == PROXY_ONLY)
|| (!proxy_fetch_func &&
non_db_entries[entrycount].proxytype == BACKEND_ONLY)) {
struct annotate_f_entry_list *nentry =
xmalloc(sizeof(struct annotate_f_entry_list));
nentry->next = fdata.entry_list;
nentry->entry = &(non_db_entries[entrycount]);
fdata.entry_list = nentry;
}
}
else {
fdata.orig_entry = entries;
check_db = 1;
}
}
if (check_db &&
(db_entry->proxytype == PROXY_AND_BACKEND
|| (proxy_fetch_func && db_entry->proxytype == PROXY_ONLY)
|| (!proxy_fetch_func && db_entry->proxytype == BACKEND_ONLY))) {
struct annotate_f_entry_list *nentry =
xmalloc(sizeof(struct annotate_f_entry_list));
nentry->next = fdata.entry_list;
nentry->entry = db_entry;
nentry->entrypat = e->s;
fdata.entry_list = nentry;
}
glob_free(&g);
e = e->next;
}
if (!mailbox[0]) {
if (fdata.entry_list) {
struct annotate_f_entry_list *entries_ptr;
construct_hash_table(&fdata.entry_table, 100, 1);
for (entries_ptr = fdata.entry_list;
entries_ptr;
entries_ptr = entries_ptr->next) {
entries_ptr->entry->get("", "", entries_ptr->entry->name,
&fdata, NULL,
(entries_ptr->entry->rock ?
entries_ptr->entry->rock :
(void*) entries_ptr->entrypat));
}
free_hash_table(&fdata.entry_table, NULL);
}
}
else {
if (fdata.entry_list || proxy_fetch_func) {
char mboxpat[MAX_MAILBOX_NAME+1];
fetch_cb(NULL, 0, 0, 0);
construct_hash_table(&fdata.entry_table, 100, 1);
if(proxy_fetch_func && fdata.orig_entry) {
fdata.orig_mailbox = mailbox;
fdata.orig_attribute = attribs;
construct_hash_table(&fdata.server_table, 10, 1);
}
strlcpy(mboxpat, mailbox, sizeof(mboxpat));
mboxname_hiersep_tointernal(namespace, mboxpat,
config_virtdomains ?
strcspn(mboxpat, "@") : 0);
(*namespace->mboxlist_findall)(namespace, mboxpat,
isadmin, userid,
auth_state, fetch_cb,
&fdata);
free_hash_table(&fdata.entry_table, NULL);
if(proxy_fetch_func && fdata.orig_entry) {
free_hash_table(&fdata.server_table, NULL);
}
}
}
output_entryatt("", "", "", NULL, &fdata);
while(fdata.entry_list) {
struct annotate_f_entry_list *freeme = fdata.entry_list;
fdata.entry_list = fdata.entry_list->next;
free(freeme);
}
return 0;
}
int annotatemore_lookup(const char *mboxname, const char *entry,
const char *userid, struct annotation_data *attrib)
{
char key[MAX_MAILBOX_PATH+1];
int keylen, datalen, r;
const char *data;
memset(attrib, 0, sizeof(struct annotation_data));
keylen = make_key(mboxname, entry, userid, key, sizeof(key));
do {
r = DB->fetch(anndb, key, keylen, &data, &datalen, NULL);
} while (r == CYRUSDB_AGAIN);
if (!r && data) {
r = split_attribs(data, datalen, attrib);
}
else if (r == CYRUSDB_NOTFOUND) r = 0;
return r;
}
static int write_entry(const char *mboxname, const char *entry,
const char *userid, struct annotation_data *attrib,
struct txn **tid)
{
char key[MAX_MAILBOX_PATH+1];
int keylen, r;
keylen = make_key(mboxname, entry, userid, key, sizeof(key));
if (!strcmp(attrib->value, "NIL")) {
do {
r = DB->delete(anndb, key, keylen, tid, 0);
} while (r == CYRUSDB_AGAIN);
}
else {
char data[MAX_MAILBOX_PATH+1];
int datalen = 0;
unsigned long l;
l = htonl(strlen(attrib->value));
memcpy(data+datalen, &l, sizeof(l));
datalen += sizeof(l);
strlcpy(data+datalen, attrib->value, sizeof(data)-datalen);
datalen += strlen(attrib->value) + 1;
if (!attrib->contenttype || !strcmp(attrib->contenttype, "NIL")) {
attrib->contenttype = "text/plain";
}
strlcpy(data+datalen, attrib->contenttype, sizeof(data)-datalen);
datalen += strlen(attrib->contenttype) + 1;
l = htonl(attrib->modifiedsince);
memcpy(data+datalen, &l, sizeof(l));
datalen += sizeof(l);
do {
r = DB->store(anndb, key, keylen, data, datalen, tid);
} while (r == CYRUSDB_AGAIN);
}
return r;
}
int annotatemore_write_entry(const char *mboxname, const char *entry,
const char *userid,
const char *value, const char *contenttype,
size_t size, time_t modifiedsince,
struct txn **tid)
{
struct annotation_data theentry;
theentry.size = size;
theentry.modifiedsince = modifiedsince ? modifiedsince : time(NULL);
theentry.contenttype = contenttype ? contenttype : "text/plain";
theentry.value = value ? value : "NIL";
return write_entry(mboxname, entry, userid, &theentry, tid);
}
struct storedata {
struct namespace *namespace;
char *userid;
int isadmin;
struct auth_state *auth_state;
struct annotate_st_entry_list *entry_list;
unsigned count;
struct txn *tid;
struct hash_table server_table;
};
enum {
ATTRIB_TYPE_CONTENTTYPE,
ATTRIB_TYPE_STRING,
ATTRIB_TYPE_BOOLEAN,
ATTRIB_TYPE_UINT,
ATTRIB_TYPE_INT
};
struct annotate_st_entry {
const char *name;
int type;
annotation_proxy_t proxytype;
int attribs;
int acl;
int (*set)(const char *int_mboxname, struct annotate_st_entry_list *entry,
struct storedata *sdata, struct mailbox_annotation_rock *mbrock,
void *rock);
void *rock;
};
struct annotate_st_entry_list
{
const struct annotate_st_entry *entry;
struct annotation_data shared;
struct annotation_data priv;
struct annotate_st_entry_list *next;
};
static const char *annotate_canon_value(const char *value, int type)
{
char *p = NULL;
if (!strcasecmp(value, "NIL")) return "NIL";
switch (type) {
case ATTRIB_TYPE_CONTENTTYPE:
break;
case ATTRIB_TYPE_STRING:
break;
case ATTRIB_TYPE_BOOLEAN:
if (!strcasecmp(value, "true")) return "true";
else if (!strcasecmp(value, "false")) return "false";
else return NULL;
break;
case ATTRIB_TYPE_UINT:
errno = 0;
strtoul(value, &p, 10);
if ((p == value)
|| (*p != '\0')
|| errno
|| strchr(value, '-')) {
return NULL;
}
break;
case ATTRIB_TYPE_INT:
errno = 0;
strtol(value, &p, 10);
if ((p == value)
|| (*p != '\0')
|| errno) {
return NULL;
}
break;
default:
return NULL;
break;
}
return value;
}
static int store_cb(char *name, int matchlen,
int maycreate __attribute__((unused)), void *rock)
{
struct storedata *sdata = (struct storedata *) rock;
struct annotate_st_entry_list *entries_ptr;
static char lastname[MAX_MAILBOX_PATH+1];
static int sawuser = 0;
char int_mboxname[MAX_MAILBOX_PATH+1];
struct mailbox_annotation_rock mbrock;
int r = 0;
if (name == NULL) {
sawuser = 0;
lastname[0] = '\0';
return 0;
}
if (name[matchlen] && strncmp(lastname, name, matchlen) == 0) {
return 0;
}
if (matchlen == 4 && strncasecmp(name, "user", 4) == 0) {
if (sawuser) return 0;
sawuser = 1;
}
strlcpy(lastname, name, sizeof(lastname));
lastname[matchlen] = '\0';
if (!strncasecmp(lastname, "INBOX", 5)) {
(*sdata->namespace->mboxname_tointernal)(sdata->namespace, "INBOX",
sdata->userid, int_mboxname);
strlcat(int_mboxname, lastname+5, sizeof(int_mboxname));
}
else
strlcpy(int_mboxname, lastname, sizeof(int_mboxname));
memset(&mbrock, 0, sizeof(struct mailbox_annotation_rock));
get_mb_data(int_mboxname, &mbrock);
for (entries_ptr = sdata->entry_list;
entries_ptr;
entries_ptr = entries_ptr->next) {
r = entries_ptr->entry->set(int_mboxname, entries_ptr, sdata, &mbrock,
entries_ptr->entry->rock);
if (r) goto cleanup;
}
sdata->count++;
if (proxy_store_func &&
!hash_lookup(mbrock.server, &(sdata->server_table))) {
hash_insert(mbrock.server, (void *)0xDEADBEEF, &(sdata->server_table));
}
cleanup:
cleanup_mbrock(&mbrock);
return r;
}
struct proxy_rock {
char *mbox_pat;
struct entryattlist *entryatts;
};
static void store_proxy(char *server, void *data __attribute__((unused)),
void *rock)
{
struct proxy_rock *prock = (struct proxy_rock *) rock;
proxy_store_func(server, prock->mbox_pat, prock->entryatts);
}
static int annotation_set_tofile(const char *int_mboxname __attribute__((unused)),
struct annotate_st_entry_list *entry,
struct storedata *sdata,
struct mailbox_annotation_rock *mbrock __attribute__((unused)),
void *rock)
{
const char *filename = (const char *) rock;
char path[1024];
FILE *f;
if (!sdata->isadmin) return IMAP_PERMISSION_DENIED;
snprintf(path, sizeof(path), "%s/msg/%s", config_dir, filename);
if (!strcmp(entry->shared.value, "NIL"))
return unlink(path);
else if ((f = fopen(path, "w"))) {
fprintf(f, "%s\n", entry->shared.value);
return fclose(f);
}
return IMAP_IOERROR;
}
static int annotation_set_todb(const char *int_mboxname,
struct annotate_st_entry_list *entry,
struct storedata *sdata,
struct mailbox_annotation_rock *mbrock,
void *rock __attribute__((unused)))
{
int r = 0;
if (entry->shared.value || entry->shared.contenttype) {
int acl = ACL_READ | ACL_WRITE | entry->entry->acl;
if (!sdata->isadmin &&
(!int_mboxname[0] || !mbrock->acl ||
((cyrus_acl_myrights(sdata->auth_state,
mbrock->acl) & acl) != acl))) {
return IMAP_PERMISSION_DENIED;
}
if (!int_mboxname[0] || !proxy_store_func) {
if (!entry->shared.value) {
struct annotation_data shared;
r = annotatemore_lookup(int_mboxname, entry->entry->name,
"", &shared);
if (r) return r;
entry->shared.value = shared.value;
}
r = write_entry(int_mboxname, entry->entry->name, "",
&(entry->shared), &(sdata->tid));
}
}
if (entry->priv.value || entry->priv.contenttype) {
if (!int_mboxname[0] || !proxy_store_func) {
if (!entry->priv.value) {
struct annotation_data priv;
r = annotatemore_lookup(int_mboxname, entry->entry->name,
sdata->userid, &priv);
if (r) return r;
entry->priv.value = priv.value;
}
r = write_entry(int_mboxname, entry->entry->name, sdata->userid,
&(entry->priv), &(sdata->tid));
}
}
return r;
}
const struct annotate_st_entry server_entries[] =
{
{ "/comment", ATTRIB_TYPE_STRING, PROXY_AND_BACKEND,
ATTRIB_VALUE_SHARED | ATTRIB_VALUE_PRIV
| ATTRIB_CONTENTTYPE_SHARED | ATTRIB_CONTENTTYPE_PRIV,
ACL_ADMIN, annotation_set_todb, NULL },
{ "/motd", ATTRIB_TYPE_STRING, PROXY_AND_BACKEND,
ATTRIB_VALUE_SHARED | ATTRIB_CONTENTTYPE_SHARED,
ACL_ADMIN, annotation_set_tofile, "motd" },
{ "/admin", ATTRIB_TYPE_STRING, PROXY_AND_BACKEND,
ATTRIB_VALUE_SHARED | ATTRIB_CONTENTTYPE_SHARED,
ACL_ADMIN, annotation_set_todb, NULL },
{ "/vendor/cmu/cyrus-imapd/shutdown", ATTRIB_TYPE_STRING, PROXY_AND_BACKEND,
ATTRIB_VALUE_SHARED | ATTRIB_CONTENTTYPE_SHARED,
ACL_ADMIN, annotation_set_tofile, "shutdown" },
{ "/vendor/cmu/cyrus-imapd/squat", ATTRIB_TYPE_BOOLEAN, PROXY_AND_BACKEND,
ATTRIB_VALUE_SHARED | ATTRIB_CONTENTTYPE_SHARED,
ACL_ADMIN, annotation_set_todb, NULL },
{ "/vendor/cmu/cyrus-imapd/expire", ATTRIB_TYPE_UINT, PROXY_AND_BACKEND,
ATTRIB_VALUE_SHARED | ATTRIB_CONTENTTYPE_SHARED,
ACL_ADMIN, annotation_set_todb, NULL },
{ NULL, 0, ANNOTATION_PROXY_T_INVALID, 0, 0, NULL, NULL }
};
const struct annotate_st_entry mailbox_rw_entries[] =
{
{ "/comment", ATTRIB_TYPE_STRING, BACKEND_ONLY,
ATTRIB_VALUE_SHARED | ATTRIB_VALUE_PRIV
| ATTRIB_CONTENTTYPE_SHARED | ATTRIB_CONTENTTYPE_PRIV,
0, annotation_set_todb, NULL },
{ "/sort", ATTRIB_TYPE_STRING, BACKEND_ONLY,
ATTRIB_VALUE_SHARED | ATTRIB_VALUE_PRIV
| ATTRIB_CONTENTTYPE_SHARED | ATTRIB_CONTENTTYPE_PRIV,
0, annotation_set_todb, NULL },
{ "/thread", ATTRIB_TYPE_STRING, BACKEND_ONLY,
ATTRIB_VALUE_SHARED | ATTRIB_VALUE_PRIV
| ATTRIB_CONTENTTYPE_SHARED | ATTRIB_CONTENTTYPE_PRIV,
0, annotation_set_todb, NULL },
{ "/check", ATTRIB_TYPE_BOOLEAN, BACKEND_ONLY,
ATTRIB_VALUE_SHARED | ATTRIB_VALUE_PRIV
| ATTRIB_CONTENTTYPE_SHARED | ATTRIB_CONTENTTYPE_PRIV,
0, annotation_set_todb, NULL },
{ "/checkperiod", ATTRIB_TYPE_UINT, BACKEND_ONLY,
ATTRIB_VALUE_SHARED | ATTRIB_VALUE_PRIV
| ATTRIB_CONTENTTYPE_SHARED | ATTRIB_CONTENTTYPE_PRIV,
0, annotation_set_todb, NULL },
{ "/vendor/cmu/cyrus-imapd/squat", ATTRIB_TYPE_BOOLEAN, BACKEND_ONLY,
ATTRIB_VALUE_SHARED | ATTRIB_CONTENTTYPE_SHARED,
ACL_ADMIN, annotation_set_todb, NULL },
{ "/vendor/cmu/cyrus-imapd/expire", ATTRIB_TYPE_UINT, BACKEND_ONLY,
ATTRIB_VALUE_SHARED | ATTRIB_CONTENTTYPE_SHARED,
ACL_ADMIN, annotation_set_todb, NULL },
{ "/vendor/cmu/cyrus-imapd/news2mail", ATTRIB_TYPE_STRING, BACKEND_ONLY,
ATTRIB_VALUE_SHARED | ATTRIB_CONTENTTYPE_SHARED,
ACL_ADMIN, annotation_set_todb, NULL },
{ NULL, 0, ANNOTATION_PROXY_T_INVALID, 0, 0, NULL, NULL }
};
int annotatemore_store(char *mailbox,
struct entryattlist *l,
struct namespace *namespace,
int isadmin,
char *userid,
struct auth_state *auth_state)
{
int r = 0;
struct entryattlist *e = l;
struct attvaluelist *av;
struct storedata sdata;
const struct annotate_st_entry *entries;
time_t now = time(0);
memset(&sdata, 0, sizeof(struct storedata));
sdata.namespace = namespace;
sdata.userid = userid;
sdata.isadmin = isadmin;
sdata.auth_state = auth_state;
if (!mailbox[0]) {
entries = server_entries;
}
else {
entries = mailbox_rw_entries;
}
while (e) {
int entrycount, attribs;
struct annotate_st_entry_list *nentry = NULL;
for (entrycount = 0;
entries[entrycount].name;
entrycount++) {
if (!strcmp(e->entry, entries[entrycount].name)) {
break;
}
}
if (!entries[entrycount].name) {
return IMAP_PERMISSION_DENIED;
}
if (entries[entrycount].proxytype == PROXY_AND_BACKEND
|| (proxy_store_func &&
entries[entrycount].proxytype == PROXY_ONLY)
|| (!proxy_store_func &&
entries[entrycount].proxytype == BACKEND_ONLY)) {
nentry = xzmalloc(sizeof(struct annotate_st_entry_list));
nentry->next = sdata.entry_list;
nentry->entry = &(entries[entrycount]);
nentry->shared.modifiedsince = now;
nentry->priv.modifiedsince = now;
sdata.entry_list = nentry;
}
attribs = entries[entrycount].attribs;
av = e->attvalues;
while (av) {
const char *value;
if (!strcmp(av->attrib, "value.shared")) {
if (!(attribs & ATTRIB_VALUE_SHARED)) {
r = IMAP_PERMISSION_DENIED;
goto cleanup;
}
value = annotate_canon_value(av->value,
entries[entrycount].type);
if (!value) {
r = IMAP_ANNOTATION_BADVALUE;
goto cleanup;
}
if (nentry) nentry->shared.value = value;
}
else if (!strcmp(av->attrib, "content-type.shared")) {
if (!(attribs & ATTRIB_CONTENTTYPE_SHARED)) {
r = IMAP_PERMISSION_DENIED;
goto cleanup;
}
value = annotate_canon_value(av->value,
ATTRIB_TYPE_CONTENTTYPE);
if (!value) {
r = IMAP_ANNOTATION_BADVALUE;
goto cleanup;
}
if (nentry) nentry->shared.contenttype = value;
}
else if (!strcmp(av->attrib, "value.priv")) {
if (!(attribs & ATTRIB_VALUE_PRIV)) {
r = IMAP_PERMISSION_DENIED;
goto cleanup;
}
value = annotate_canon_value(av->value,
entries[entrycount].type);
if (!value) {
r = IMAP_ANNOTATION_BADVALUE;
goto cleanup;
}
if (nentry) nentry->priv.value = value;
}
else if (!strcmp(av->attrib, "content-type.priv")) {
if (!(attribs & ATTRIB_CONTENTTYPE_PRIV)) {
r = IMAP_PERMISSION_DENIED;
goto cleanup;
}
value = annotate_canon_value(av->value,
ATTRIB_TYPE_CONTENTTYPE);
if (!value) {
r = IMAP_ANNOTATION_BADVALUE;
goto cleanup;
}
if (nentry) nentry->priv.contenttype = value;
}
else {
r = IMAP_PERMISSION_DENIED;
goto cleanup;
}
av = av->next;
}
e = e->next;
}
if (!mailbox[0]) {
if (sdata.entry_list) {
struct annotate_st_entry_list *entries_ptr;
for (entries_ptr = sdata.entry_list;
entries_ptr;
entries_ptr = entries_ptr->next) {
r = entries_ptr->entry->set("", entries_ptr, &sdata, NULL,
entries_ptr->entry->rock);
if (r) break;
}
}
}
else {
char mboxpat[MAX_MAILBOX_NAME+1];
store_cb(NULL, 0, 0, 0);
if (proxy_store_func) {
construct_hash_table(&sdata.server_table, 10, 1);
}
strlcpy(mboxpat, mailbox, sizeof(mboxpat));
mboxname_hiersep_tointernal(namespace, mboxpat,
config_virtdomains ?
strcspn(mboxpat, "@") : 0);
r = (*namespace->mboxlist_findall)(namespace, mboxpat,
isadmin, userid,
auth_state, store_cb,
&sdata);
if (!r && !sdata.count) r = IMAP_MAILBOX_NONEXISTENT;
if (proxy_store_func) {
if (!r) {
struct proxy_rock prock = { NULL, NULL };
prock.mbox_pat = mailbox;
prock.entryatts = l;
hash_enumerate(&sdata.server_table, store_proxy, &prock);
}
free_hash_table(&sdata.server_table, NULL);
}
}
if (sdata.tid) {
if (!r) {
DB->commit(anndb, sdata.tid);
}
else {
DB->abort(anndb, sdata.tid);
}
}
cleanup:
while (sdata.entry_list) {
struct annotate_st_entry_list *freeme = sdata.entry_list;
sdata.entry_list = sdata.entry_list->next;
free(freeme);
}
return r;
}
struct rename_rock {
const char *newmboxname;
const char *olduserid;
const char *newuserid;
struct txn *tid;
};
static int rename_cb(const char *mailbox, const char *entry,
const char *userid, struct annotation_data *attrib,
void *rock)
{
struct rename_rock *rrock = (struct rename_rock *) rock;
int r = 0;
if (rrock->newmboxname) {
if (rrock->olduserid && rrock->newuserid &&
!strcmp(rrock->olduserid, userid)) {
r = write_entry(rrock->newmboxname, entry, rrock->newuserid,
attrib, &rrock->tid);
}
else {
r = write_entry(rrock->newmboxname, entry, userid,
attrib, &rrock->tid);
}
}
if (!r) {
attrib->value = "NIL";
r = write_entry(mailbox, entry, userid, attrib, &rrock->tid);
}
return r;
}
int annotatemore_rename(const char *oldmboxname, const char *newmboxname,
const char *olduserid, const char *newuserid)
{
struct rename_rock rrock;
int r;
rrock.newmboxname = newmboxname;
rrock.olduserid = olduserid;
rrock.newuserid = newuserid;
rrock.tid = NULL;
r = annotatemore_findall(oldmboxname, "*", &rename_cb, &rrock, &rrock.tid);
if (rrock.tid) {
if (!r) {
DB->commit(anndb, rrock.tid);
}
else {
DB->abort(anndb, rrock.tid);
}
}
return r;
}
int annotatemore_delete(const char *mboxname)
{
return annotatemore_rename(mboxname, NULL, NULL, NULL);
}