/* ctl_mboxlist.c -- do DB related operations on mboxlist * * Copyright (c) 1998-2003 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 other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@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. * */ /* $Id: ctl_mboxlist.c,v 1.5 2005/03/05 00:36:47 dasenbro Exp $ */ /* currently doesn't catch signals; probably SHOULD */ #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include "assert.h" #include "exitcodes.h" #include "imap_err.h" #include "global.h" #include "libcyr_cfg.h" #include "mboxlist.h" #include "mupdate-client.h" #include "xmalloc.h" /* config.c stuff */ const int config_need_data = 0; extern int optind; extern char *optarg; const int PER_COMMIT = 1000; enum mboxop { DUMP, M_POPULATE, RECOVER, CHECKPOINT, UNDUMP, NONE }; struct dumprock { enum mboxop op; struct txn *tid; const char *partition; int purge; mupdate_handle *h; }; struct mb_node { char mailbox[MAX_MAILBOX_NAME+1]; char server[MAX_MAILBOX_NAME+1]; 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; /* 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. * 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, 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->server, mdata->server, sizeof(next->server)); if(!strncmp(cmd, "MAILBOX", 7)) next->acl = xstrdup(mdata->acl); *act_tail = next; act_tail = &(next->next); } return 0; } static int dump_cb(void *rockp, const char *key, int keylen, const char *data, int datalen) { struct dumprock *d = (struct dumprock *) rockp; int r; char *p; char *name, *part, *acl; int mbtype; /* \0 terminate 'name' */ name = xstrndup(key, keylen); /* Get mailbox type */ mbtype = strtol(data, &p, 10); p = strchr(data, ' '); if (p == NULL) { abort(); } p++; acl = strchr(p, ' '); if (acl == NULL) { abort(); } /* grab 'part', \0 terminate */ part = xstrndup(p, acl - p); /* \0 terminate 'acl' */ p = acl + 1; acl = xstrndup(p, datalen - (p - data)); switch (d->op) { case DUMP: if(!d->partition || !strcmp(d->partition, part)) { printf("%s\t%s\t%s\n", name, part, acl); if(d->purge) { config_mboxlist_db->delete(mbdb, key, keylen, &(d->tid), 0); } } break; case M_POPULATE: { char *realpart = xmalloc(strlen(config_servername) + 1 + strlen(part) + 1); int skip_flag; /* 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 */ /* realpart is 'hostname!partition' */ sprintf(realpart, "%s!%s", config_servername, part); /* 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(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(mbtype & MBTYPE_MOVING) { struct mb_node *next; if(warn_only) { printf("Remove remote flag on: %s\n", name); } else { next = xzmalloc(sizeof(struct mb_node)); strlcpy(next->mailbox, 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 && !strcmp(realpart, act_head->server) && !strcmp(acl, act_head->acl)) { /* Do not update if location does match, and there is an acl, * and the acl matches */ skip_flag = 1; } else { skip_flag = 0; } /* in any case, free the node. */ if(act_head->acl) free(act_head->acl); tmp = act_head; act_head = act_head->next; 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 *unused_mbdata; /* 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(d->h, name, &unused_mbdata)) { /* since it lives on another server, schedule it for a wipe */ struct mb_node *next; if(warn_only) { printf("Remove Local Mailbox: %s\n", name); } else { next = xzmalloc(sizeof(struct mb_node)); strlcpy(next->mailbox, name, sizeof(next->mailbox)); next->next = wipe_head; wipe_head = next; } skip_flag = 1; } else { /* Check that it isn't flagged moving */ if(mbtype & MBTYPE_MOVING) { /* it's flagged moving, we'll fix it later (and * push it then too) */ struct mb_node *next; if(warn_only) { printf("Remove remote flag on: %s\n", name); } else { next = xzmalloc(sizeof(struct mb_node)); strlcpy(next->mailbox, 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 { /* we should just push the change to mupdate now */ skip_flag = 0; } } } if(skip_flag) { free(realpart); break; } if(warn_only) { printf("Force Activate: %s\n", name); free(realpart); break; } r = mupdate_activate(d->h,name,realpart,acl); free(realpart); if(r == MUPDATE_NOCONN) { fprintf(stderr, "permanant failure storing '%s'\n", name); return IMAP_IOERROR; } else if (r == MUPDATE_FAIL) { fprintf(stderr, "temporary failure storing '%s' (update continuing)", name); } break; } default: /* yikes ! */ abort(); break; } free(name); free(part); free(acl); return 0; } /* 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. */ void do_dump(enum mboxop op, const char *part, int purge) { struct dumprock d; int ret; char buf[8192]; assert(op == DUMP || op == M_POPULATE); assert(op == DUMP || !purge); assert(op == DUMP || !part); d.op = op; d.partition = part; d.purge = purge; d.tid = NULL; if(op == M_POPULATE) { ret = mupdate_connect(NULL, NULL, &(d.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 '!', * (to ensure we get exactly our hostname) */ snprintf(buf, sizeof(buf), "%s!", config_servername); ret = mupdate_list(d.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; if(warn_only) { printf("Remove from MUPDATE: %s\n", me->mailbox); } else { ret = mupdate_delete(d.h, me->mailbox); if(ret) { fprintf(stderr, "couldn't mupdate delete %s\n", me->mailbox); exit(1); } } free(me); } } /* Dump Database */ config_mboxlist_db->foreach(mbdb, "", 0, NULL, &dump_cb, &d, NULL); if(d.tid) { config_mboxlist_db->commit(mbdb, d.tid); d.tid = NULL; } if(op == M_POPULATE) { /* Remove MBTYPE_MOVING flags (unflag_head) */ while(unflag_head) { struct mb_node *me = unflag_head; int type; char *part, *acl, *newpart; unflag_head = unflag_head->next; ret = mboxlist_detail(me->mailbox, &type, NULL, &part, &acl, NULL); if(ret) { fprintf(stderr, "couldn't perform lookup to un-remote-flag %s\n", me->mailbox); exit(1); } /* Reset the partition! */ newpart = strchr(part, '!'); if(!newpart) newpart = part; else newpart++; ret = mboxlist_update(me->mailbox, type & ~MBTYPE_MOVING, newpart, acl, 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, part); ret = mupdate_activate(d.h, me->mailbox, buf, acl); if(ret) { fprintf(stderr, "couldn't perform mupdatepush to un-remote-flag %s\n", me->mailbox); exit(1); } free(me); } /* Delete local mailboxes where needed (wipe_head) */ while(wipe_head) { struct mb_node *me = wipe_head; wipe_head = wipe_head->next; ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, 0, 1, 1); if(ret) { fprintf(stderr, "couldn't delete defunct mailbox %s\n", me->mailbox); exit(1); } free(me); } /* Done with mupdate */ mupdate_disconnect(&(d.h)); sasl_done(); } return; } void do_undump(void) { int r = 0; char buf[16384]; int line = 0; char last_commit[MAX_MAILBOX_NAME]; char *key=NULL, *data=NULL; int keylen, datalen; int untilCommit = PER_COMMIT; struct txn *tid = NULL; last_commit[0] = '\0'; while (fgets(buf, sizeof(buf), stdin)) { char *name, *partition, *acl; char *p; int tries = 0; line++; name = buf; for (p = buf; *p && *p != '\t'; p++) ; if (!*p) { fprintf(stderr, "line %d: no partition found\n", line); continue; } *p++ = '\0'; partition = p; for (; *p && *p != '\t'; p++) ; if (!*p) { fprintf(stderr, "line %d: no acl found\n", line); continue; } *p++ = '\0'; acl = p; /* chop off the newline */ for (; *p && *p != '\r' && *p != '\n'; p++) ; *p++ = '\0'; if (strlen(name) > MAX_MAILBOX_NAME) { fprintf(stderr, "line %d: mailbox name too long\n", line); continue; } if (strlen(partition) >= MAX_PARTITION_LEN) { fprintf(stderr, "line %d: partition name too long\n", line); continue; } key = name; keylen = strlen(key); data = mboxlist_makeentry(0, partition, acl); datalen = strlen(data); tries = 0; retry: r = config_mboxlist_db->store(mbdb, key, keylen, data, datalen, &tid); switch (r) { case 0: break; case CYRUSDB_AGAIN: if (tries++ < 5) { fprintf(stderr, "warning: DB_LOCK_DEADLOCK; retrying\n"); goto retry; } fprintf(stderr, "error: too many deadlocks, aborting\n"); break; default: r = IMAP_IOERROR; break; } free(data); if(--untilCommit == 0) { /* commit */ r = config_mboxlist_db->commit(mbdb, tid); if(r) break; tid = NULL; untilCommit = PER_COMMIT; strlcpy(last_commit,key,sizeof(last_commit)); } if (r) break; } if(!r && tid) { /* commit the last transaction */ r=config_mboxlist_db->commit(mbdb, tid); } if (r) { if(tid) config_mboxlist_db->abort(mbdb, tid); fprintf(stderr, "db error: %s\n", cyrusdb_strerror(r)); if(key) fprintf(stderr, "was processing mailbox: %s\n", key); if(last_commit[0]) fprintf(stderr, "last commit was at: %s\n", last_commit); else fprintf(stderr, "no commits\n"); } return; } void usage(void) { fprintf(stderr, "DUMP:\n"); fprintf(stderr, " ctl_mboxlist [-C ] -d [-x] [-f filename] [-p partition]\n"); fprintf(stderr, "UNDUMP:\n"); fprintf(stderr, " ctl_mboxlist [-C ] -u [-f filename]" " [< mboxlist.dump]\n"); fprintf(stderr, "MUPDATE populate:\n"); fprintf(stderr, " ctl_mboxlist [-C ] -m [-a] [-w] [-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; if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE); while ((opt = getopt(argc, argv, "C:awmdurcxf:p:")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; break; case 'r': /* deprecated, but we still support it */ fprintf(stderr, "ctl_mboxlist -r is deprecated: " "use ctl_cyrusdb -r instead\n"); syslog(LOG_WARNING, "ctl_mboxlist -r is deprecated: " "use ctl_cyrusdb -r instead"); if (op == NONE) op = RECOVER; else usage(); break; case 'c': /* deprecated, but we still support it */ fprintf(stderr, "ctl_mboxlist -c is deprecated: " "use ctl_cyrusdb -c instead\n"); syslog(LOG_WARNING, "ctl_mboxlist -c is deprecated: " "use ctl_cyrusdb -c instead"); if (op == NONE) op = CHECKPOINT; else usage(); 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; default: usage(); break; } } if(op != M_POPULATE && (local_authoritative || warn_only)) usage(); if(op != DUMP && partition) usage(); if(op != DUMP && dopurge) usage(); if(op == RECOVER) { syslog(LOG_NOTICE, "running mboxlist recovery"); libcyrus_config_setint(CYRUSOPT_DB_INIT_FLAGS, CYRUSDB_RECOVER); } cyrus_init(alt_config, "ctl_mboxlist", 0); global_sasl_init(1,0,NULL); switch (op) { case RECOVER: /* this was done by the call to cyrus_init via libcyrus */ syslog(LOG_NOTICE, "done running mboxlist recovery"); break; case CHECKPOINT: syslog(LOG_NOTICE, "checkpointing mboxlist"); mboxlist_init(MBOXLIST_SYNC); mboxlist_done(); break; case DUMP: case M_POPULATE: mboxlist_init(0); mboxlist_open(mboxdb_fname); quotadb_init(0); quotadb_open(NULL); do_dump(op, partition, dopurge); quotadb_close(); quotadb_done(); mboxlist_close(); mboxlist_done(); break; case UNDUMP: mboxlist_init(0); mboxlist_open(mboxdb_fname); quotadb_init(0); quotadb_open(NULL); do_undump(); quotadb_close(); quotadb_done(); mboxlist_close(); mboxlist_done(); break; default: usage(); cyrus_done(); return 1; } cyrus_done(); return 0; }