Page MenuHomePhorge

ctl_mboxlist.c
No OneTemporary

Authored By
Unknown
Size
42 KB
Referenced Files
None
Subscribers
None

ctl_mboxlist.c

/* ctl_mboxlist.c -- do DB related operations on mboxlist
*
* 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.
*/
/* currently doesn't catch signals; probably SHOULD */
#include <config.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <inttypes.h>
#include <sysexits.h>
#include <syslog.h>
#include <stdlib.h>
#include <string.h>
#include <sasl/sasl.h>
#include "assert.h"
#include "annotate.h"
#include "dlist.h"
#include "global.h"
#include "json_support.h"
#include "libcyr_cfg.h"
#include "mboxlist.h"
#include "mupdate.h"
#include "user.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
#include "imap/mupdate_err.h"
#include "lib/ptrarray.h"
extern int optind;
extern char *optarg;
enum mboxop { DUMP,
M_POPULATE,
UNDUMP,
VERIFY,
NONE };
struct dumprock {
const char *partition;
int purge;
const char *sep;
};
struct popmupdaterock {
mupdate_handle *h;
};
struct mb_node
{
char mailbox[MAX_MAILBOX_BUFFER];
char location[MAX_MAILBOX_BUFFER];
char *acl;
struct mb_node *next;
};
static struct mb_node *act_head = NULL, **act_tail = &act_head;
static struct mb_node *del_head = NULL;
static struct mb_node *wipe_head = NULL, *unflag_head = NULL;
/* assume the local copy is authoritative and that it should just overwrite
* mupdate */
static int local_authoritative = 0;
static int warn_only = 0;
static int interactive = 0;
/* For each mailbox that this guy gets called for, check that
* it is a mailbox that:
* a) mupdate server thinks *we* host
* -> Because we were called, this is the case, provided we
* -> gave the prefix parameter to the remote.
* -> (And assuming bugs don't exist.)
* b) we do not actually host
*
* if that's the case, enqueue a delete
* otherwise, we both agree that it exists, but we still need
* to verify that its info is up to date.
*/
static int mupdate_list_cb(struct mupdate_mailboxdata *mdata,
const char *cmd,
void *context __attribute__((unused)))
{
int ret;
/* the server thinks we have it, do we think we have it? */
ret = mboxlist_lookup(mdata->mailbox, NULL, NULL);
if (ret) {
struct mb_node *next;
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, mdata->mailbox, sizeof(next->mailbox));
next->next = del_head;
del_head = next;
} else {
/* we both agree that it exists */
/* throw it onto the back of the activate queue */
/* we may or may not need to send an update */
struct mb_node *next;
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, mdata->mailbox, sizeof(next->mailbox));
strlcpy(next->location, mdata->location, sizeof(next->location));
if (!strncmp(cmd, "MAILBOX", 7))
next->acl = xstrdup(mdata->acl);
*act_tail = next;
act_tail = &(next->next);
}
return 0;
}
static int pop_mupdate_cb(const mbentry_t *mbentry, void *rockp)
{
struct popmupdaterock *rock = (struct popmupdaterock *) rockp;
int r = 0;
if (mbentry->mbtype & MBTYPE_DELETED)
return 0;
/* realpart is 'hostname!partition' */
char *realpart =
strconcat(config_servername, "!", mbentry->partition, (char *)NULL);
int skip_flag = 0;
/* If it is marked MBTYPE_MOVING, and it DOES match the entry,
* we need to unmark it. If it does not match the entry in our
* list, then we assume that it successfully made the move and
* we delete it from the local disk */
/* If they match, then we should check that we actually need
* to update it. If they *don't* match, then we believe that we
* need to send fresh data. There will be no point at which something
* is in the act_head list that we do not have locally, because that
* is a condition of being in the act_head list */
if (act_head && !strcmp(mbentry->name, act_head->mailbox)) {
struct mb_node *tmp;
/* If this mailbox was moving, we want to unmark the movingness,
* since the MUPDATE server agreed that it lives here. */
/* (and later also force an mupdate push) */
if (mbentry->mbtype & MBTYPE_MOVING) {
struct mb_node *next;
syslog(LOG_WARNING, "Remove remote flag on: %s", mbentry->name);
if (warn_only) {
printf("Remove remote flag on: %s\n", mbentry->name);
} else {
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, mbentry->name, sizeof(next->mailbox));
next->next = unflag_head;
unflag_head = next;
}
/* No need to update mupdate NOW, we'll get it when we
* untag the mailbox */
skip_flag = 1;
} else if (act_head->acl) {
if (
!strcmp(realpart, act_head->location) &&
!strcmp(mbentry->acl, act_head->acl)
) {
/* Do not update if location does match, and there is an acl,
* and the acl matches */
skip_flag = 1;
}
}
/* in any case, free the node. */
if (act_head->acl) free(act_head->acl);
tmp = act_head;
act_head = act_head->next;
if (tmp) free(tmp);
} else {
/* if they do not match, do an explicit MUPDATE find on the
* mailbox, and if it is living somewhere else, delete the local
* data, if it is NOT living somewhere else, recreate it in
* mupdate */
struct mupdate_mailboxdata *mdata;
/* if this is okay, we found it (so it is on another host, since
* it wasn't in our list in this position) */
if (!local_authoritative &&
!mupdate_find(rock->h, mbentry->name, &mdata)) {
/* since it lives on another server, schedule it for a wipe */
struct mb_node *next;
/*
* Verify that what we found points at another host,
* not back to this host. Good idea, since if our assumption
* if wrong, we'll end up removing the authoritative
* mailbox.
*/
if (strcmp(realpart, mdata->location) == 0 ) {
if ( act_head ) {
fprintf( stderr, "mupdate said: %s %s %s\n",
act_head->mailbox, act_head->location, act_head->acl );
}
fprintf( stderr, "mailboxes.db said: %s %s %s\n",
mbentry->name, realpart, mbentry->acl );
fprintf( stderr, "mupdate says: %s %s %s\n",
mdata->mailbox, mdata->location, mdata->acl );
fatal("mupdate said not us before it said us", EX_SOFTWARE);
}
/*
* Where does "unified" murder fit into ctl_mboxlist?
* 1. Only check locally hosted mailboxes.
* 2. Check everything.
* Either way, this check is just wrong!
*/
if (config_mupdate_config !=
IMAP_ENUM_MUPDATE_CONFIG_UNIFIED) {
/* But not for a unified configuration */
syslog(LOG_WARNING, "Remove Local Mailbox: %s", mbentry->name);
if (warn_only) {
printf("Remove Local Mailbox: %s\n", mbentry->name);
} else {
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, mbentry->name, sizeof(next->mailbox));
next->next = wipe_head;
wipe_head = next;
}
}
skip_flag = 1;
} else {
/* Check that it isn't flagged moving */
if (mbentry->mbtype & MBTYPE_MOVING) {
/* it's flagged moving, we'll fix it later (and
* push it then too) */
struct mb_node *next;
syslog(LOG_WARNING, "Remove remote flag on: %s", mbentry->name);
if (warn_only) {
printf("Remove remote flag on: %s\n", mbentry->name);
} else {
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, mbentry->name, sizeof(next->mailbox));
next->next = unflag_head;
unflag_head = next;
}
/* No need to update mupdate now, we'll get it when we
* untag the mailbox */
skip_flag = 1;
}
}
}
if (skip_flag) {
free(realpart);
return 0;
}
syslog(LOG_WARNING, "Force Activate: %s", mbentry->name);
if (warn_only) {
printf("Force Activate: %s\n", mbentry->name);
free(realpart);
return 0;
}
r = mupdate_activate(rock->h, mbentry->name, realpart, mbentry->acl);
if (r == MUPDATE_NOCONN) {
fprintf(stderr, "permanent failure storing '%s'\n", mbentry->name);
r = IMAP_IOERROR;
} else if (r == MUPDATE_FAIL) {
fprintf(stderr,
"temporary failure storing '%s' (update continuing)\n",
mbentry->name);
r = 0;
} else if (r) {
fprintf(
stderr,
"error storing '%s' (update continuing): %s\n",
mbentry->name,
error_message(r)
);
r = 0;
}
free(realpart);
return r;
}
/*
* True if user types Y\n or y\n. Anything else is false.
*/
static int yes(void)
{
int c, answer = 0;
c = getchar();
if (c == 'Y' || c == 'y') {
answer = 1;
while ((c = getchar()) != EOF) {
if (c == '\n') {
break;
} else {
answer = 0;
}
}
}
return(answer);
}
/* Resyncing with mupdate:
*
* If it is local and not present on mupdate at all, push to mupdate.
* If it is local and present on mupdate for another host, delete local mailbox
* If it is local and present on mupdate but with incorrect partition/acl,
* update mupdate.
* If it is not local and present on mupdate for this host, delete it from
* mupdate.
*/
static void do_pop_mupdate(void)
{
struct popmupdaterock popmupdaterock = {0};
int ret;
char buf[8192];
ret = mupdate_connect(NULL, NULL, &(popmupdaterock.h), NULL);
if (ret) {
fprintf(stderr, "couldn't connect to mupdate server\n");
exit(1);
}
/* now we need a list of what the remote thinks we have
* To generate it, ask for a prefix of '<our hostname>!',
* (to ensure we get exactly our hostname) */
snprintf(buf, sizeof(buf), "%s!", config_servername);
ret = mupdate_list(popmupdaterock.h, mupdate_list_cb, buf, NULL);
if (ret) {
fprintf(stderr, "couldn't do LIST command on mupdate server\n");
exit(1);
}
/* Run pending mupdate deletes */
while (del_head) {
struct mb_node *me = del_head;
del_head = del_head->next;
syslog(LOG_WARNING, "Remove from MUPDATE: %s", me->mailbox);
if (warn_only) {
printf("Remove from MUPDATE: %s\n", me->mailbox);
} else {
ret = mupdate_delete(popmupdaterock.h, me->mailbox);
if (ret) {
fprintf(stderr,
"couldn't mupdate delete %s\n", me->mailbox);
exit(1);
}
}
free(me);
}
/* Run callback for mailboxes */
int flags = MBOXTREE_TOMBSTONES;
mboxlist_allmbox("", &pop_mupdate_cb, &popmupdaterock, flags);
/* Remove MBTYPE_MOVING flags (unflag_head) */
while (unflag_head) {
mbentry_t *mbentry = NULL;
struct mb_node *me = unflag_head;
unflag_head = unflag_head->next;
ret = mboxlist_lookup(me->mailbox, &mbentry, NULL);
if (ret) {
fprintf(stderr,
"couldn't perform lookup to un-remote-flag %s\n",
me->mailbox);
exit(1);
}
/* Reset the partition! */
free(mbentry->server);
mbentry->server = NULL;
mbentry->mbtype &= ~(MBTYPE_MOVING|MBTYPE_REMOTE);
ret = mboxlist_updatelock(mbentry, 1);
if (ret) {
fprintf(stderr,
"couldn't perform update to un-remote-flag %s\n",
me->mailbox);
exit(1);
}
/* force a push to mupdate */
snprintf(buf, sizeof(buf), "%s!%s", config_servername, mbentry->partition);
ret = mupdate_activate(popmupdaterock.h, me->mailbox, buf, mbentry->acl);
if (ret) {
fprintf(stderr,
"couldn't perform mupdatepush to un-remote-flag %s\n",
me->mailbox);
exit(1);
}
mboxlist_entry_free(&mbentry);
free(me);
}
/* Delete local mailboxes where needed (wipe_head) */
if (interactive) {
int count = 0;
struct mb_node *me;
for (me = wipe_head; me != NULL; me = me->next) count++;
if ( count > 0 ) {
fprintf(stderr, "OK to delete %d local mailboxes? ", count);
if (!yes()) {
fprintf(stderr, "Cancelled!\n");
exit(1);
}
}
}
while (wipe_head) {
struct mb_node *me = wipe_head;
wipe_head = wipe_head->next;
struct mboxlock *namespacelock = mboxname_usernamespacelock(me->mailbox);
if (!mboxlist_delayed_delete_isenabled() ||
mboxname_isdeletedmailbox(me->mailbox, NULL)) {
ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, NULL,
MBOXLIST_DELETE_LOCALONLY|MBOXLIST_DELETE_FORCE);
} else {
ret = mboxlist_delayed_deletemailbox(me->mailbox, 1, "", NULL, NULL,
MBOXLIST_DELETE_LOCALONLY|MBOXLIST_DELETE_FORCE);
}
mboxname_release(&namespacelock);
if (ret) {
fprintf(stderr, "couldn't delete defunct mailbox %s\n",
me->mailbox);
exit(1);
}
free(me);
}
/* Done with mupdate */
mupdate_disconnect(&(popmupdaterock.h));
sasl_done();
}
/* XXX based on mailbox_acl_to_dlist. this should probably be in lib/acl.c! */
static json_t *acl_to_json(const char *aclstr)
{
const char *p, *q;
json_t *jacl = json_object();
p = aclstr;
while (p && *p) {
char *name, *val;
q = strchr(p, '\t');
if (!q) break;
name = xstrndup(p, q-p);
q++;
p = strchr(q, '\t');
if (p) {
val = xstrndup(q, p-q);
p++;
}
else
val = xstrdup(q);
json_object_set_new(jacl, name, json_string(val));
free(name);
free(val);
}
return jacl;
}
static int dump_cb(const mbentry_t *mbentry, void *rockp)
{
struct dumprock *d = (struct dumprock *) rockp;
int i, r = 0;
json_t *jparent, *jobj, *jname_history;
char *output = NULL;
static struct buf buf = BUF_INITIALIZER;
/* skip if we're limiting by partition and this one doesn't match */
if (d->partition && strcmpsafe(d->partition, mbentry->partition))
return 0;
jobj = json_object();
/* char *name; */
json_object_set_new(jobj, "name", json_string(mbentry->name));
/* char *ext_name
* this field is a place to cache a calculated value, not
* a real value in mailboxes.db, so don't output it.
*/
/* time_t mtime; */
buf_reset(&buf);
buf_printf(&buf, TIME_T_FMT, mbentry->mtime);
json_object_set_new(jobj, "mtime", json_string(buf_cstring(&buf)));
/* uint32_t uidvalidity; */
buf_reset(&buf);
buf_printf(&buf, "%" PRIu32, mbentry->uidvalidity);
json_object_set_new(jobj, "uidvalidity", json_string(buf_cstring(&buf)));
/* modseq_t createdmodseq; */
buf_reset(&buf);
buf_printf(&buf, MODSEQ_FMT, mbentry->createdmodseq);
json_object_set_new(jobj, "createdmodseq", json_string(buf_cstring(&buf)));
/* modseq_t foldermodseq; */
buf_reset(&buf);
buf_printf(&buf, MODSEQ_FMT, mbentry->foldermodseq);
json_object_set_new(jobj, "foldermodseq", json_string(buf_cstring(&buf)));
/* uint32_t mbtype; */
json_object_set_new(jobj, "mbtype",
json_string(mboxlist_mbtype_to_string(mbentry->mbtype)));
/* char *partition; */
json_object_set_new(jobj, "partition", json_string(mbentry->partition));
/* char *server; */
json_object_set_new(jobj, "server", json_string(mbentry->server));
/* char *acl; */
json_object_set_new(jobj, "acl", acl_to_json(mbentry->acl));
/* char *uniqueid; */
json_object_set_new(jobj, "uniqueid", json_string(mbentry->uniqueid));
/* char *legacy_specialuse; */
json_object_set_new(jobj, "legacy_specialuse",
json_string(mbentry->legacy_specialuse));
/* ptrarray_t name_history; */
jname_history = json_array();
for (i = 0; i < mbentry->name_history.count; i++) {
former_name_t *histitem = ptrarray_nth(&mbentry->name_history, i);
json_t *jhistitem = json_object();
json_object_set_new(jhistitem, "name", json_string(histitem->name));
buf_reset(&buf);
buf_printf(&buf, TIME_T_FMT, histitem->mtime);
json_object_set_new(jhistitem, "mtime",
json_string(buf_cstring(&buf)));
buf_reset(&buf);
buf_printf(&buf, "%" PRIu32, histitem->uidvalidity);
json_object_set_new(jhistitem, "uidvalidity",
json_string(buf_cstring(&buf)));
buf_reset(&buf);
buf_printf(&buf, MODSEQ_FMT, histitem->createdmodseq);
json_object_set_new(jhistitem, "createdmodseq",
json_string(buf_cstring(&buf)));
buf_reset(&buf);
buf_printf(&buf, MODSEQ_FMT, histitem->foldermodseq);
json_object_set_new(jhistitem, "foldermodseq",
json_string(buf_cstring(&buf)));
json_object_set_new(jhistitem, "mbtype",
json_string(mboxlist_mbtype_to_string(histitem->mbtype)));
json_object_set_new(jhistitem, "partition",
json_string(histitem->partition));
json_array_append_new(jname_history, jhistitem);
}
json_object_set_new(jobj, "name_history", jname_history);
jparent = json_object();
json_object_set_new(jparent, mbentry->name, jobj);
output = json_dumps(jparent, JSON_EMBED);
if (!output) {
xsyslog(LOG_ERR, "unable to stringify json object",
"mboxname=<%s>", mbentry->name);
return IMAP_INTERNAL;
}
printf("%s%s", d->sep, output);
if (d->sep && !*d->sep)
d->sep = ",\n";
free(output);
json_decref(jparent);
if (d->purge) {
mboxlist_deletelock(mbentry);
}
return r;
}
static void do_dump(const char *part, int purge, int intermediary)
{
struct dumprock d = { part, purge, "" };
/* Dump Database */
int flags = MBOXTREE_TOMBSTONES;
if (intermediary) flags |= MBOXTREE_INTERMEDIATES;
puts("{");
mboxlist_allmbox("", &dump_cb, &d, flags);
puts("\n}");
}
static void do_undump_legacy(void)
{
char buf[16384];
int line = 0;
while (fgets(buf, sizeof(buf), stdin)) {
mbentry_t *newmbentry = mboxlist_entry_create();
line++;
sscanf(buf, "%m[^\t]\t%d %ms %m[^>]>%ms " TIME_T_FMT " %" SCNu32
" %llu %llu %m[^\n]\n", &newmbentry->name, &newmbentry->mbtype,
&newmbentry->partition, &newmbentry->acl, &newmbentry->uniqueid,
&newmbentry->mtime, &newmbentry->uidvalidity, &newmbentry->foldermodseq,
&newmbentry->createdmodseq, &newmbentry->legacy_specialuse);
if (!newmbentry->acl) {
/*
* This can be valid, e.g. for folders created by
* 0000 CREATE #calendars (TYPE CALENDAR)
* 0001 CREATE #addressbooks (TYPE ADDRESSBOOK)
* 0002 CREATE #calendars/Shared (TYPE CALENDAR)
* 0003 CREATE #addressbooks/Shared (TYPE ADDRESSBOOK)
* For these read the uniqueid, mtime, etc.
*/
mboxlist_entry_free(&newmbentry);
newmbentry = mboxlist_entry_create();
sscanf(buf, "%m[^\t]\t%d %ms >%ms " TIME_T_FMT " %" SCNu32
" %llu %llu %m[^\n]\n", &newmbentry->name, &newmbentry->mbtype,
&newmbentry->partition, &newmbentry->uniqueid,
&newmbentry->mtime, &newmbentry->uidvalidity, &newmbentry->foldermodseq,
&newmbentry->createdmodseq, &newmbentry->legacy_specialuse);
}
if (!newmbentry->partition) {
fprintf(stderr, "line %d: no partition found\n", line);
mboxlist_entry_free(&newmbentry);
continue;
}
char *partition = strchr(newmbentry->partition, '!');
if (partition) {
newmbentry->server = newmbentry->partition;
newmbentry->partition = strdup(partition + 1);
*partition = '\0';
}
if (strlen(newmbentry->name) >= MAX_MAILBOX_BUFFER) {
/* XXX should be MAX_MAILBOX_NAME, not MAX_MAILBOX_BUFFER? */
fprintf(stderr, "line %d: mailbox name too long\n", line);
mboxlist_entry_free(&newmbentry);
continue;
}
if (strlen(newmbentry->partition) >= MAX_PARTITION_LEN) {
fprintf(stderr, "line %d: partition name too long\n", line);
mboxlist_entry_free(&newmbentry);
continue;
}
/* generate a new entry */
int r = mboxlist_updatelock(newmbentry, /*localonly*/1);
mboxlist_entry_free(&newmbentry);
if (r) break;
}
return;
}
static void undump_name_history(ptrarray_t *name_history,
const json_t *jname_history)
{
size_t index;
json_t *value;
/* XXX check lengths of mailbox and partition names */
json_array_foreach(jname_history, index, value) {
former_name_t *histitem;
const char *tmp;
histitem = xzmalloc(sizeof(*histitem));
/* char *name; */
if ((tmp = json_string_value(json_object_get(value, "name")))) {
histitem->name = xstrdup(tmp);
}
/* time_t mtime; */
if ((tmp = json_string_value(json_object_get(value, "mtime")))) {
histitem->mtime = atoi(tmp);
}
/* uint32_t uidvalidity; */
if ((tmp = json_string_value(json_object_get(value, "uidvalidity")))) {
histitem->uidvalidity = strtoul(tmp, NULL, 10);
}
/* modseq_t createdmodseq; */
if ((tmp = json_string_value(json_object_get(value, "createdmodseq")))) {
histitem->createdmodseq = atomodseq_t(tmp);
}
/* modseq_t foldermodseq; */
if ((tmp = json_string_value(json_object_get(value, "foldermodseq")))) {
histitem->foldermodseq = atomodseq_t(tmp);
}
/* uint32_t mbtype; */
if ((tmp = json_string_value(json_object_get(value, "mbtype")))) {
histitem->mbtype = mboxlist_string_to_mbtype(tmp);
}
/* char *partition; */
if ((tmp = json_string_value(json_object_get(value, "partition")))) {
histitem->partition = xstrdup(tmp);
}
ptrarray_append(name_history, histitem);
}
}
static int do_undump(void)
{
json_t *jmailboxes = NULL;
json_error_t jerr;
const char *key;
json_t *value;
jmailboxes = json_loadf(stdin, 0, &jerr);
if (!jmailboxes) {
fprintf(stderr, "parse error at line %d: %s\n", jerr.line, jerr.text);
return -1;
}
json_object_foreach(jmailboxes, key, value) {
mbentry_t *newmbentry = mboxlist_entry_create();
const char *tmp;
json_t *jtmp;
/* char *name; */
if (strlen(key) >= MAX_MAILBOX_NAME) {
fprintf(stderr, "mailbox name too long: %s\n", key);
goto skip;
}
newmbentry->name = xstrdup(key);
/* char *ext_name
* this field is a place to cache a calculated value, not
* a real value in mailboxes.db, so don't expect it.
*/
/* time_t mtime;
* this field is ignored, the new mbentry will always be created with
* the current time in its mtime field.
*/
/* uint32_t uidvalidity; */
if ((tmp = json_string_value(json_object_get(value, "uidvalidity")))) {
newmbentry->uidvalidity = strtoul(tmp, NULL, 10);
}
else {
fprintf(stderr, "missing uidvalidity for %s\n", key);
}
/* modseq_t createdmodseq; */
if ((tmp = json_string_value(json_object_get(value, "createdmodseq")))) {
newmbentry->createdmodseq = atomodseq_t(tmp);
}
else {
fprintf(stderr, "missing createdmodseq for %s\n", key);
}
/* modseq_t foldermodseq; */
if ((tmp = json_string_value(json_object_get(value, "foldermodseq")))) {
newmbentry->foldermodseq = atomodseq_t(tmp);
}
else {
fprintf(stderr, "missing foldermodseq for %s\n", key);
}
/* uint32_t mbtype; */
if ((tmp = json_string_value(json_object_get(value, "mbtype")))) {
newmbentry->mbtype = mboxlist_string_to_mbtype(tmp);
}
else {
// XXX possibly infer mbtype from name
// We might want to set/verify mbtype based on the name. For
// instance user.foo.#calendars* should be MBTYPE_CALENDAR,
// user.foo.#jmap should be MBTYPE_COLLECTION, etc.
fprintf(stderr, "missing mbtype for %s\n", key);
}
/* char *partition; */
if ((tmp = json_string_value(json_object_get(value, "partition")))) {
if (strlen(tmp) >= MAX_PARTITION_LEN) {
fprintf(stderr, "partition too long for %s\n", key);
goto skip;
}
newmbentry->partition = xstrdup(tmp);
}
else {
fprintf(stderr, "missing mbtype for %s\n", key);
goto skip;
}
/* char *server; */
if ((tmp = json_string_value(json_object_get(value, "server")))) {
newmbentry->server = xstrdup(tmp);
}
else {
// XXX detect whether this needs to be present, whinge if it's not
// Its mandatory for frontends and mupdate in a Murder. Backends
// shouldn't have this in a traditional (non-unified) Murder.
}
/* char *acl; */
if ((jtmp = json_object_get(value, "acl"))) {
const char *aclkey;
json_t *aclvalue;
struct buf buf = BUF_INITIALIZER;
json_object_foreach(jtmp, aclkey, aclvalue) {
buf_printf(&buf, "%s\t%s\t",
aclkey,
json_string_value(aclvalue));
}
newmbentry->acl = buf_release(&buf);
}
/* char *uniqueid; */
if ((tmp = json_string_value(json_object_get(value, "uniqueid")))) {
newmbentry->uniqueid = xstrdup(tmp);
}
else {
/* XXX could potentially infer this if the mailbox is on disk */
fprintf(stderr, "missing uniqueid for %s\n", key);
goto skip;
}
/* char *legacy_specialuse; */
if ((tmp = json_string_value(json_object_get(value, "legacy_specialuse")))) {
newmbentry->legacy_specialuse = xstrdup(tmp);
}
/* ptrarray_t name_history; */
if ((jtmp = json_object_get(value, "name_history"))) {
undump_name_history(&newmbentry->name_history, jtmp);
}
/* generate a new entry */
mboxlist_updatelock(newmbentry, /*localonly*/1);
/* XXX should we auditlog something here? */
skip:
mboxlist_entry_free(&newmbentry);
}
json_decref(jmailboxes);
return 0;
}
enum {
ROOT = (1<<0),
DOMAIN = (1<<1),
MBOX = (1<<2),
UUID = (1<<3),
MATCHED = (1<<4)
};
struct found_data {
int type;
char mboxname[MAX_MAILBOX_BUFFER];
char partition[MAX_MAILBOX_BUFFER];
char path[MAX_MAILBOX_PATH+1];
};
static void add_path(ptrarray_t *found, int type,
const char *name, const char *part, const char *path)
{
struct found_data *new;
new = malloc(sizeof(struct found_data));
new->type = type;
strcpy(new->mboxname, name);
strcpy(new->partition, part);
strcpy(new->path, path);
/* add our new node to the end of the list */
ptrarray_append(found, new);
}
static void add_part(ptrarray_t *found,
const char *part, const char *path, int override)
{
int i;
struct found_data *entry;
/* see if we already added a partition having this name */
for (i = 0; i < ptrarray_size(found); i++){
entry = ptrarray_nth(found, i);
if (!strcmp(entry->partition, part)) {
/* found it */
if (override) {
/* replace the path with the one containing cyrus.header */
strcpy(entry->path, path);
}
/* we already have the proper path, so we're done */
return;
}
}
/* add the new partition path */
add_path(found, ROOT, "", part, path);
}
static void get_partitions(const char *key, const char *value, void *rock)
{
static int check_meta = -1;
ptrarray_t *found = (ptrarray_t *) rock;
if (check_meta == -1) {
/* see if cyrus.header might be contained in a metapartition */
check_meta = (config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_HEADER);
}
if (!strncmp(key, "partition-", 10)) {
add_part(found, key+10, value, 0);
}
else if (check_meta && !strncmp(key, "metapartition-", 14)) {
add_part(found, key+14, value, 1);
}
/* skip any other overflow strings */
}
static int compar_mbox(const void **v1, const void **v2)
{
struct found_data *d1 = (struct found_data *) *v1;
struct found_data *d2 = (struct found_data *) *v2;
/* non-mailboxes get pushed to the end of the array,
otherwise we do an ASCII sort */
if (d1->type & MBOX) {
if (d2->type & MBOX) return strcmp(d1->mboxname, d2->mboxname);
else return -1;
}
else if (d2->type & MBOX) return 1;
else return 0;
}
static int add_mbox_cb(const mbentry_t *mbentry, void *rockp)
{
// This function is called for every entry in the database,
// and stores all mailboxes in &mboxes.
ptrarray_t *mboxes = (ptrarray_t *) rockp;
/* skip deleted mailboxes and mailboxes without partition
as they cannot have a path in the filesystem */
if (mbentry->partition == NULL ||
mbentry->mbtype & MBTYPE_DELETED)
return 0;
if (mbentry->mbtype & MBTYPE_LEGACY_DIRS)
add_path(mboxes, MBOX, mbentry->name, mbentry->partition, mbentry->uniqueid);
else
add_path(mboxes, MBOX | UUID, mbentry->uniqueid, mbentry->partition, mbentry->name);
return 0;
}
static void verify_mboxes(ptrarray_t *mboxes, ptrarray_t *found, int *idx)
{
int i;
int r;
char *mbname;
struct found_data *found_mailbox_entry;
struct found_data *found_path_entry;
for (i = 0; i < ptrarray_size(mboxes); i++) {
found_mailbox_entry = ptrarray_nth(mboxes, i);
if (found_mailbox_entry->type & UUID)
mbname = found_mailbox_entry->path;
else
mbname = found_mailbox_entry->mboxname;
// Walk the directories to see if the mailbox does have
// paths on the filesystem.
do {
r = -1;
found_path_entry = ptrarray_nth(found, *idx);
if (
!(found_path_entry->type & MBOX) || /* end of mailboxes */
(r = strcmp(found_mailbox_entry->mboxname, found_path_entry->mboxname)) < 0
) {
printf("'%s' has a DB entry but no directory on partition '%s'\n",
mbname, found_mailbox_entry->partition);
break;
}
else if (r == 0) {
if (found_path_entry->type & MATCHED) {
printf("'%s' has an additional match to DB entry of mailbox '%s' on partition '%s'\n",
found_path_entry->path, mbname, found_mailbox_entry->partition);
}
/* mark filesystem entry as matched */
found_path_entry->type |= MATCHED;
}
(*idx)++;
} while (r > 0);
}
/* now report all unmatched mailboxes found in filesystem */
for (i = 0; i < ptrarray_size(found); i++) {
found_path_entry = ptrarray_nth(found, i);
if (!(found_path_entry->type & MBOX)) break;
if (!(found_path_entry->type & MATCHED)) {
printf("'%s' has a directory '%s' but no DB entry\n",
found_path_entry->mboxname,
found_path_entry->path
);
}
}
}
static void do_verify(void)
{
ptrarray_t *found;
ptrarray_t *mboxes;
int i;
int idx = 0;
found = ptrarray_new();
ptrarray_init(found);
mboxes = ptrarray_new();
ptrarray_init(mboxes);
/* gather a list of partition paths to search */
config_foreachoverflowstring(get_partitions, found);
/* scan all paths in our list, tagging valid mailboxes,
and adding paths as we find them */
for (i = 0; i < ptrarray_size(found); i++) {
DIR *dirp;
struct dirent *dirent;
char name[MAX_MAILBOX_BUFFER];
char part[MAX_MAILBOX_BUFFER];
char path[MAX_MAILBOX_PATH+1];
int type;
struct found_data *entry = ptrarray_nth(found, i);
if (config_hashimapspool && (entry->type & ROOT)) {
/* need to add hashed directories */
int config_fulldirhash = libcyrus_config_getswitch(CYRUSOPT_FULLDIRHASH);
char *tail;
int j, c;
/* make the toplevel partition /a */
if (config_fulldirhash) {
strcat(entry->path, "/A");
c = 'B';
} else {
strcat(entry->path, "/a");
c = 'b';
}
type = (entry->type &= ~ROOT);
/* make a template path for /b - /z */
strcpy(name, entry->mboxname);
strcpy(part, entry->partition);
strcpy(path, entry->path);
tail = path + strlen(path) - 1;
for (j = 1; j < 26; j++, c++) {
*tail = c;
add_path(found, type, name, part, path);
}
if (config_virtdomains && !type) {
/* need to add root domain directory */
strcpy(tail, "domain");
add_path(found, DOMAIN | ROOT, name, part, path);
}
/* need to add uuid directory */
strcpy(tail, "uuid");
add_path(found, type | UUID, name, part, path);
}
if (!(dirp = opendir(entry->path))) continue;
while ((dirent = readdir(dirp))) {
const char *fname = FNAME_HEADER;
if (dirent->d_name[0] == '.') continue;
else if (!strcmp(dirent->d_name, fname+1)) {
/* XXX - check that it can be opened */
entry->type |= MBOX;
strcpy(name, entry->mboxname);
}
else if (!strchr(dirent->d_name, '.') ||
(entry->type & DOMAIN)) {
/* probably a directory, add it to the array */
type = 0;
strcpy(name, entry->mboxname);
if (config_virtdomains &&
(entry->type == ROOT) &&
!strcmp(dirent->d_name, "domain")) {
/* root domain directory */
type = DOMAIN | ROOT;
}
else if (!name[0] && entry->type & DOMAIN) {
/* toplevel domain directory */
strcat(name, dirent->d_name);
strcat(name, "!");
type = DOMAIN | ROOT;
}
else if (entry->type & UUID) {
/* possibly a mailbox directory, use directory name without ancestor information */
strcpy(name, dirent->d_name);
}
else {
/* possibly a mailbox directory */
if (name[0] && !(entry->type & DOMAIN)) strcat(name, ".");
strcat(name, dirent->d_name);
}
strcpy(part, entry->partition);
strcpy(path, entry->path);
strcat(path, "/");
strcat(path, dirent->d_name);
/* inherit UUID flag from parent entry */
type = entry->type & UUID;
add_path(found, type, name, part, path);
}
}
closedir(dirp);
}
ptrarray_sort(found, compar_mbox);
/* gather all mailboxes and sort them, so that UUID and non-UUID
mailboxes are sorted in the way we need them to be to avoid
full nested looping */
mboxlist_allmbox("", &add_mbox_cb, mboxes, MBOXTREE_TOMBSTONES);
ptrarray_sort(mboxes, compar_mbox);
verify_mboxes(mboxes, found, &idx);
}
static void usage(void)
{
fprintf(stderr, "DUMP:\n");
fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -d [-x] [-y] [-p partition] [-f filename]\n");
fprintf(stderr, "UNDUMP:\n");
fprintf(stderr,
" ctl_mboxlist [-C <alt_config>] -u [-f filename] [-L]"
" [< mboxlist.dump]\n");
fprintf(stderr, "MUPDATE populate:\n");
fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -m [-a] [-w] [-i] [-f filename]\n");
fprintf(stderr, "VERIFY:\n");
fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -v [-f filename]\n");
exit(1);
}
int main(int argc, char *argv[])
{
const char *partition = NULL;
char *mboxdb_fname = NULL;
int dopurge = 0;
int opt;
enum mboxop op = NONE;
char *alt_config = NULL;
int dointermediary = 0;
int undump_legacy = 0;
while ((opt = getopt(argc, argv, "C:Lawmdurcxf:p:viy")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'L':
undump_legacy = 1;
break;
case 'f':
if (!mboxdb_fname) {
mboxdb_fname = optarg;
} else {
usage();
}
break;
case 'd':
if (op == NONE) op = DUMP;
else usage();
break;
case 'u':
if (op == NONE) op = UNDUMP;
else usage();
break;
case 'm':
if (op == NONE) op = M_POPULATE;
else usage();
break;
case 'p':
partition = optarg;
break;
case 'x':
dopurge = 1;
break;
case 'a':
local_authoritative = 1;
break;
case 'w':
warn_only = 1;
break;
case 'v':
if (op == NONE) op = VERIFY;
else usage();
break;
case 'i':
interactive = 1;
break;
case 'y':
dointermediary = 1;
break;
default:
usage();
break;
}
}
if (op != M_POPULATE && (local_authoritative || warn_only)) usage();
if (op != DUMP && partition) usage();
if (op != DUMP && dopurge) usage();
if (op != DUMP && dointermediary) usage();
if (op != UNDUMP && undump_legacy) usage();
cyrus_init(alt_config, "ctl_mboxlist", 0, 0);
global_sasl_init(1,0,NULL);
switch (op) {
case M_POPULATE:
syslog(LOG_NOTICE, "%spopulating mupdate", warn_only ? "test " : "");
mboxlist_init(0);
mboxlist_open(mboxdb_fname);
do_pop_mupdate();
mboxlist_close();
mboxlist_done();
syslog(LOG_NOTICE, "done %spopulating mupdate", warn_only ? "test " : "");
break;
case DUMP:
mboxlist_init(0);
mboxlist_open(mboxdb_fname);
do_dump(partition, dopurge, dointermediary);
mboxlist_close();
mboxlist_done();
break;
case UNDUMP:
mboxlist_init(0);
mboxlist_open(mboxdb_fname);
if (undump_legacy) {
do_undump_legacy();
}
else {
do_undump();
}
mboxlist_close();
mboxlist_done();
break;
case VERIFY:
mboxlist_init(0);
mboxlist_open(mboxdb_fname);
do_verify();
mboxlist_close();
mboxlist_done();
break;
default:
usage();
cyrus_done();
return 1;
}
cyrus_done();
return 0;
}

File Metadata

Mime Type
text/x-c
Expires
Mon, Apr 6, 12:49 AM (5 d, 18 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831749
Default Alt Text
ctl_mboxlist.c (42 KB)

Event Timeline