#include <config.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <syslog.h>
#include <stdlib.h>
#include <string.h>
#include <sasl/sasl.h>
#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"
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;
static int local_authoritative = 0;
static int warn_only = 0;
static int mupdate_list_cb(struct mupdate_mailboxdata *mdata,
const char *cmd,
void *context __attribute__((unused)))
{
int ret;
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 {
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;
name = xstrndup(key, keylen);
mbtype = strtol(data, &p, 10);
p = strchr(data, ' ');
if (p == NULL) {
abort();
}
p++;
acl = strchr(p, ' ');
if (acl == NULL) {
abort();
}
part = xstrndup(p, acl - p);
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;
sprintf(realpart, "%s!%s", config_servername, part);
if(act_head && !strcmp(name, act_head->mailbox)) {
struct mb_node *tmp;
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;
}
skip_flag = 1;
} else if(act_head->acl &&
!strcmp(realpart, act_head->server) &&
!strcmp(acl, act_head->acl)) {
skip_flag = 1;
} else {
skip_flag = 0;
}
if(act_head->acl) free(act_head->acl);
tmp = act_head;
act_head = act_head->next;
free(tmp);
} else {
struct mupdate_mailboxdata *unused_mbdata;
if(!local_authoritative &&
!mupdate_find(d->h, name, &unused_mbdata)) {
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 {
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;
}
skip_flag = 1;
} else {
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:
abort();
break;
}
free(name);
free(part);
free(acl);
return 0;
}
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);
}
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);
}
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);
}
}
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) {
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);
}
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);
}
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);
}
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);
}
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;
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) {
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) {
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 <alt_config>] -d [-x] [-f filename] [-p partition]\n");
fprintf(stderr, "UNDUMP:\n");
fprintf(stderr,
" ctl_mboxlist [-C <alt_config>] -u [-f filename]"
" [< mboxlist.dump]\n");
fprintf(stderr, "MUPDATE populate:\n");
fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -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 = optarg;
break;
case 'r':
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':
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:
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;
}