1 /* ctl_mboxlist.c -- do DB related operations on mboxlist
2 *
3 * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The name "Carnegie Mellon University" must not be used to
18 * endorse or promote products derived from this software without
19 * prior written permission. For permission or any legal
20 * details, please contact
21 * Carnegie Mellon University
22 * Center for Technology Transfer and Enterprise Creation
23 * 4615 Forbes Avenue
24 * Suite 302
25 * Pittsburgh, PA 15213
26 * (412) 268-7393, fax: (412) 268-7395
27 * innovation@andrew.cmu.edu
28 *
29 * 4. Redistributions of any form whatsoever must retain the following
30 * acknowledgment:
31 * "This product includes software developed by Computing Services
32 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33 *
34 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41 */
42
43 /* currently doesn't catch signals; probably SHOULD */
44
45 #include <config.h>
46
47 #if HAVE_DIRENT_H
48 # include <dirent.h>
49 # define NAMLEN(dirent) strlen((dirent)->d_name)
50 #else
51 # define dirent direct
52 # define NAMLEN(dirent) (dirent)->d_namlen
53 # if HAVE_SYS_NDIR_H
54 # include <sys/ndir.h>
55 # endif
56 # if HAVE_SYS_DIR_H
57 # include <sys/dir.h>
58 # endif
59 # if HAVE_NDIR_H
60 # include <ndir.h>
61 # endif
62 #endif
63
64 #include <sys/types.h>
65 #ifdef HAVE_UNISTD_H
66 #include <unistd.h>
67 #endif
68 #include <sysexits.h>
69 #include <syslog.h>
70 #include <stdlib.h>
71 #include <string.h>
72 #include <sasl/sasl.h>
73
74 #include "assert.h"
75 #include "annotate.h"
76 #include "dlist.h"
77 #include "global.h"
78 #include "libcyr_cfg.h"
79 #include "mboxlist.h"
80 #include "mupdate.h"
81 #include "user.h"
82 #include "util.h"
83 #include "xmalloc.h"
84 #include "xstrlcpy.h"
85
86 /* generated headers are not necessarily in current directory */
87 #include "imap/imap_err.h"
88 #include "imap/mupdate_err.h"
89
90 extern int optind;
91 extern char *optarg;
92
93 enum mboxop { DUMP,
94 M_POPULATE,
95 RECOVER,
96 CHECKPOINT,
97 UNDUMP,
98 VERIFY,
99 NONE };
100
101 struct dumprock {
102 enum mboxop op;
103
104 const char *partition;
105 int purge;
106
107 mupdate_handle *h;
108 };
109
110 struct mb_node
111 {
112 char mailbox[MAX_MAILBOX_BUFFER];
113 char location[MAX_MAILBOX_BUFFER];
114 char *acl;
115 struct mb_node *next;
116 };
117
118 static struct mb_node *act_head = NULL, **act_tail = &act_head;
119 static struct mb_node *del_head = NULL;
120 static struct mb_node *wipe_head = NULL, *unflag_head = NULL;
121
122 /* assume the local copy is authoritative and that it should just overwrite
123 * mupdate */
124 static int local_authoritative = 0;
125 static int warn_only = 0;
126 static int interactive = 0;
127
128 /* For each mailbox that this guy gets called for, check that
129 * it is a mailbox that:
130 * a) mupdate server thinks *we* host
131 * -> Because we were called, this is the case, provided we
132 * -> gave the prefix parameter to the remote.
133 * -> (And assuming bugs don't exist.)
134 * b) we do not actually host
135 *
136 * if that's the case, enqueue a delete
137 * otherwise, we both agree that it exists, but we still need
138 * to verify that its info is up to date.
139 */
mupdate_list_cb(struct mupdate_mailboxdata * mdata,const char * cmd,void * context)140 static int mupdate_list_cb(struct mupdate_mailboxdata *mdata,
141 const char *cmd,
142 void *context __attribute__((unused)))
143 {
144 int ret;
145
146 /* the server thinks we have it, do we think we have it? */
147 ret = mboxlist_lookup(mdata->mailbox, NULL, NULL);
148 if (ret) {
149 struct mb_node *next;
150
151 next = xzmalloc(sizeof(struct mb_node));
152 strlcpy(next->mailbox, mdata->mailbox, sizeof(next->mailbox));
153
154 next->next = del_head;
155 del_head = next;
156 } else {
157 /* we both agree that it exists */
158 /* throw it onto the back of the activate queue */
159 /* we may or may not need to send an update */
160 struct mb_node *next;
161
162 next = xzmalloc(sizeof(struct mb_node));
163 strlcpy(next->mailbox, mdata->mailbox, sizeof(next->mailbox));
164 strlcpy(next->location, mdata->location, sizeof(next->location));
165 if (!strncmp(cmd, "MAILBOX", 7))
166 next->acl = xstrdup(mdata->acl);
167
168 *act_tail = next;
169 act_tail = &(next->next);
170 }
171 return 0;
172 }
173
dump_cb(const mbentry_t * mbentry,void * rockp)174 static int dump_cb(const mbentry_t *mbentry, void *rockp)
175 {
176 struct dumprock *d = (struct dumprock *) rockp;
177 int r = 0;
178
179 switch (d->op) {
180 case DUMP:
181 if (!d->partition || !strcmpsafe(d->partition, mbentry->partition)) {
182 printf("%s\t%d ", mbentry->name, mbentry->mbtype);
183 if (mbentry->server) printf("%s!", mbentry->server);
184 printf("%s %s\n", mbentry->partition, mbentry->acl);
185 if (d->purge) {
186 mboxlist_delete(mbentry->name);
187 }
188 }
189 break;
190 case M_POPULATE:
191 {
192 if (mbentry->mbtype & MBTYPE_DELETED)
193 return 0;
194
195 /* realpart is 'hostname!partition' */
196 char *realpart =
197 strconcat(config_servername, "!", mbentry->partition, (char *)NULL);
198 int skip_flag = 0;
199
200 /* If it is marked MBTYPE_MOVING, and it DOES match the entry,
201 * we need to unmark it. If it does not match the entry in our
202 * list, then we assume that it successfully made the move and
203 * we delete it from the local disk */
204
205 /* If they match, then we should check that we actually need
206 * to update it. If they *don't* match, then we believe that we
207 * need to send fresh data. There will be no point at which something
208 * is in the act_head list that we do not have locally, because that
209 * is a condition of being in the act_head list */
210 if (act_head && !strcmp(mbentry->name, act_head->mailbox)) {
211 struct mb_node *tmp;
212
213 /* If this mailbox was moving, we want to unmark the movingness,
214 * since the MUPDATE server agreed that it lives here. */
215 /* (and later also force an mupdate push) */
216 if (mbentry->mbtype & MBTYPE_MOVING) {
217 struct mb_node *next;
218
219 syslog(LOG_WARNING, "Remove remote flag on: %s", mbentry->name);
220
221 if (warn_only) {
222 printf("Remove remote flag on: %s\n", mbentry->name);
223 } else {
224 next = xzmalloc(sizeof(struct mb_node));
225 strlcpy(next->mailbox, mbentry->name, sizeof(next->mailbox));
226 next->next = unflag_head;
227 unflag_head = next;
228 }
229
230 /* No need to update mupdate NOW, we'll get it when we
231 * untag the mailbox */
232 skip_flag = 1;
233 } else if (act_head->acl) {
234 if (
235 !strcmp(realpart, act_head->location) &&
236 !strcmp(mbentry->acl, act_head->acl)
237 ) {
238
239 /* Do not update if location does match, and there is an acl,
240 * and the acl matches */
241
242 skip_flag = 1;
243 }
244 }
245
246 /* in any case, free the node. */
247 if (act_head->acl) free(act_head->acl);
248 tmp = act_head;
249 act_head = act_head->next;
250 if (tmp) free(tmp);
251 } else {
252 /* if they do not match, do an explicit MUPDATE find on the
253 * mailbox, and if it is living somewhere else, delete the local
254 * data, if it is NOT living somewhere else, recreate it in
255 * mupdate */
256 struct mupdate_mailboxdata *mdata;
257
258 /* if this is okay, we found it (so it is on another host, since
259 * it wasn't in our list in this position) */
260 if (!local_authoritative &&
261 !mupdate_find(d->h, mbentry->name, &mdata)) {
262 /* since it lives on another server, schedule it for a wipe */
263 struct mb_node *next;
264
265 /*
266 * Verify that what we found points at another host,
267 * not back to this host. Good idea, since if our assumption
268 * if wrong, we'll end up removing the authoritative
269 * mailbox.
270 */
271 if (strcmp(realpart, mdata->location) == 0 ) {
272 if ( act_head ) {
273 fprintf( stderr, "mupdate said: %s %s %s\n",
274 act_head->mailbox, act_head->location, act_head->acl );
275 }
276 fprintf( stderr, "mailboxes.db said: %s %s %s\n",
277 mbentry->name, realpart, mbentry->acl );
278 fprintf( stderr, "mupdate says: %s %s %s\n",
279 mdata->mailbox, mdata->location, mdata->acl );
280 fatal("mupdate said not us before it said us", EX_SOFTWARE);
281 }
282
283 /*
284 * Where does "unified" murder fit into ctl_mboxlist?
285 * 1. Only check locally hosted mailboxes.
286 * 2. Check everything.
287 * Either way, this check is just wrong!
288 */
289 if (config_mupdate_config !=
290 IMAP_ENUM_MUPDATE_CONFIG_UNIFIED) {
291 /* But not for a unified configuration */
292
293 syslog(LOG_WARNING, "Remove Local Mailbox: %s", mbentry->name);
294
295 if (warn_only) {
296 printf("Remove Local Mailbox: %s\n", mbentry->name);
297 } else {
298 next = xzmalloc(sizeof(struct mb_node));
299 strlcpy(next->mailbox, mbentry->name, sizeof(next->mailbox));
300 next->next = wipe_head;
301 wipe_head = next;
302 }
303 }
304
305 skip_flag = 1;
306 } else {
307 /* Check that it isn't flagged moving */
308 if (mbentry->mbtype & MBTYPE_MOVING) {
309 /* it's flagged moving, we'll fix it later (and
310 * push it then too) */
311 struct mb_node *next;
312
313 syslog(LOG_WARNING, "Remove remote flag on: %s", mbentry->name);
314
315 if (warn_only) {
316 printf("Remove remote flag on: %s\n", mbentry->name);
317 } else {
318 next = xzmalloc(sizeof(struct mb_node));
319 strlcpy(next->mailbox, mbentry->name, sizeof(next->mailbox));
320 next->next = unflag_head;
321 unflag_head = next;
322 }
323
324 /* No need to update mupdate now, we'll get it when we
325 * untag the mailbox */
326 skip_flag = 1;
327 }
328 }
329 }
330
331 if (skip_flag) {
332 free(realpart);
333 break;
334 }
335
336 syslog(LOG_WARNING, "Force Activate: %s", mbentry->name);
337
338 if (warn_only) {
339 printf("Force Activate: %s\n", mbentry->name);
340 free(realpart);
341 break;
342 }
343
344 r = mupdate_activate(d->h, mbentry->name, realpart, mbentry->acl);
345
346 if (r == MUPDATE_NOCONN) {
347 fprintf(stderr, "permanent failure storing '%s'\n", mbentry->name);
348 r = IMAP_IOERROR;
349 } else if (r == MUPDATE_FAIL) {
350 fprintf(stderr,
351 "temporary failure storing '%s' (update continuing)\n",
352 mbentry->name);
353 r = 0;
354 } else if (r) {
355 fprintf(
356 stderr,
357 "error storing '%s' (update continuing): %s\n",
358 mbentry->name,
359 error_message(r)
360 );
361 r = 0;
362 }
363
364 free(realpart);
365
366 break;
367 }
368
369 default: /* yikes ! */
370 abort();
371 break;
372 }
373
374 return r;
375 }
376
377 /*
378 * True if user types Y\n or y\n. Anything else is false.
379 */
yes(void)380 static int yes(void)
381 {
382 int c, answer = 0;
383
384 c = getchar();
385 if (c == 'Y' || c == 'y') {
386 answer = 1;
387
388 while ((c = getchar()) != EOF) {
389 if (c == '\n') {
390 break;
391 } else {
392 answer = 0;
393 }
394 }
395 }
396
397 return(answer);
398 }
399
400 /* Resyncing with mupdate:
401 *
402 * If it is local and not present on mupdate at all, push to mupdate.
403 * If it is local and present on mupdate for another host, delete local mailbox
404 * If it is local and present on mupdate but with incorrect partition/acl,
405 * update mupdate.
406 * If it is not local and present on mupdate for this host, delete it from
407 * mupdate.
408 */
409
do_dump(enum mboxop op,const char * part,int purge,int intermediary)410 static void do_dump(enum mboxop op, const char *part, int purge, int intermediary)
411 {
412 struct dumprock d;
413 int ret;
414 char buf[8192];
415
416 assert(op == DUMP || op == M_POPULATE);
417 assert(op == DUMP || !purge);
418 assert(op == DUMP || !part);
419
420 d.op = op;
421 d.partition = part;
422 d.purge = purge;
423
424 if (op == M_POPULATE) {
425 ret = mupdate_connect(NULL, NULL, &(d.h), NULL);
426 if (ret) {
427 fprintf(stderr, "couldn't connect to mupdate server\n");
428 exit(1);
429 }
430
431 /* now we need a list of what the remote thinks we have
432 * To generate it, ask for a prefix of '<our hostname>!',
433 * (to ensure we get exactly our hostname) */
434 snprintf(buf, sizeof(buf), "%s!", config_servername);
435 ret = mupdate_list(d.h, mupdate_list_cb, buf, NULL);
436 if (ret) {
437 fprintf(stderr, "couldn't do LIST command on mupdate server\n");
438 exit(1);
439 }
440
441 /* Run pending mupdate deletes */
442 while (del_head) {
443 struct mb_node *me = del_head;
444 del_head = del_head->next;
445
446 syslog(LOG_WARNING, "Remove from MUPDATE: %s", me->mailbox);
447
448 if (warn_only) {
449 printf("Remove from MUPDATE: %s\n", me->mailbox);
450 } else {
451 ret = mupdate_delete(d.h, me->mailbox);
452 if (ret) {
453 fprintf(stderr,
454 "couldn't mupdate delete %s\n", me->mailbox);
455 exit(1);
456 }
457 }
458
459 free(me);
460 }
461 }
462
463 /* Dump Database */
464 int flags = MBOXTREE_TOMBSTONES;
465 if (intermediary) flags |= MBOXTREE_INTERMEDIATES;
466 mboxlist_allmbox("", &dump_cb, &d, flags);
467
468 if (op == M_POPULATE) {
469 /* Remove MBTYPE_MOVING flags (unflag_head) */
470 while (unflag_head) {
471 mbentry_t *mbentry = NULL;
472 struct mb_node *me = unflag_head;
473
474 unflag_head = unflag_head->next;
475
476 ret = mboxlist_lookup(me->mailbox, &mbentry, NULL);
477 if (ret) {
478 fprintf(stderr,
479 "couldn't perform lookup to un-remote-flag %s\n",
480 me->mailbox);
481 exit(1);
482 }
483
484 /* Reset the partition! */
485 free(mbentry->server);
486 mbentry->server = NULL;
487 mbentry->mbtype &= ~(MBTYPE_MOVING|MBTYPE_REMOTE);
488 ret = mboxlist_update(mbentry, 1);
489 if (ret) {
490 fprintf(stderr,
491 "couldn't perform update to un-remote-flag %s\n",
492 me->mailbox);
493 exit(1);
494 }
495
496 /* force a push to mupdate */
497 snprintf(buf, sizeof(buf), "%s!%s", config_servername, mbentry->partition);
498 ret = mupdate_activate(d.h, me->mailbox, buf, mbentry->acl);
499 if (ret) {
500 fprintf(stderr,
501 "couldn't perform mupdatepush to un-remote-flag %s\n",
502 me->mailbox);
503 exit(1);
504 }
505
506 mboxlist_entry_free(&mbentry);
507 free(me);
508 }
509
510 /* Delete local mailboxes where needed (wipe_head) */
511 if (interactive) {
512 int count = 0;
513 struct mb_node *me;
514
515 for (me = wipe_head; me != NULL; me = me->next) count++;
516
517 if ( count > 0 ) {
518 fprintf(stderr, "OK to delete %d local mailboxes? ", count);
519 if (!yes()) {
520 fprintf(stderr, "Cancelled!\n");
521 exit(1);
522 }
523 }
524 }
525
526 while (wipe_head) {
527 struct mb_node *me = wipe_head;
528 wipe_head = wipe_head->next;
529
530 struct mboxlock *namespacelock = mboxname_usernamespacelock(me->mailbox);
531
532 if (!mboxlist_delayed_delete_isenabled()) {
533 ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, NULL,
534 MBOXLIST_DELETE_LOCALONLY|MBOXLIST_DELETE_FORCE);
535 } else if (mboxname_isdeletedmailbox(me->mailbox, NULL)) {
536 ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, NULL,
537 MBOXLIST_DELETE_LOCALONLY|MBOXLIST_DELETE_FORCE);
538
539 } else {
540 ret = mboxlist_delayed_deletemailbox(me->mailbox, 1, "", NULL, NULL,
541 MBOXLIST_DELETE_LOCALONLY|MBOXLIST_DELETE_FORCE);
542 }
543
544 mboxname_release(&namespacelock);
545
546 if (ret) {
547 fprintf(stderr, "couldn't delete defunct mailbox %s\n",
548 me->mailbox);
549 exit(1);
550 }
551
552 free(me);
553 }
554
555 /* Done with mupdate */
556 mupdate_disconnect(&(d.h));
557 sasl_done();
558 }
559
560 return;
561 }
562
do_undump(void)563 static void do_undump(void)
564 {
565 int r = 0;
566 char buf[16384];
567 int line = 0;
568 const char *name, *partition, *acl;
569 int mbtype;
570 char *p;
571
572 while (fgets(buf, sizeof(buf), stdin)) {
573 mbentry_t *newmbentry = NULL;
574 const char *server = NULL;
575
576 line++;
577
578 name = buf;
579 for (p = buf; *p && *p != '\t'; p++) ;
580 if (!*p) {
581 fprintf(stderr, "line %d: no partition found\n", line);
582 continue;
583 }
584 *p++ = '\0';
585 if (Uisdigit(*p)) {
586 /* new style dump */
587 mbtype = strtol(p, &p, 10);
588 /* skip trailing space */
589 if (*p == ' ') p++;
590 }
591 else mbtype = 0;
592
593 partition = p;
594 for (; *p && (*p != ' ') && (*p != '\t'); p++) {
595 if (*p == '!') {
596 *p++ = '\0';
597 server = partition;
598 partition = p;
599 }
600 }
601 if (!*p) {
602 fprintf(stderr, "line %d: no acl found\n", line);
603 continue;
604 }
605 *p++ = '\0';
606 acl = p;
607 /* chop off the newline */
608 for (; *p && *p != '\r' && *p != '\n'; p++) ;
609 *p++ = '\0';
610
611 if (strlen(name) >= MAX_MAILBOX_BUFFER) {
612 fprintf(stderr, "line %d: mailbox name too long\n", line);
613 continue;
614 }
615 if (strlen(partition) >= MAX_PARTITION_LEN) {
616 fprintf(stderr, "line %d: partition name too long\n", line);
617 continue;
618 }
619
620 /* generate a new entry */
621 newmbentry = mboxlist_entry_create();
622 newmbentry->name = xstrdup(name);
623 newmbentry->mbtype = mbtype;
624 newmbentry->server = xstrdupnull(server);
625 newmbentry->partition = xstrdupnull(partition);
626 newmbentry->acl = xstrdupnull(acl);
627 /* XXX - still missing all the new fields */
628
629 r = mboxlist_update(newmbentry, /*localonly*/1);
630 mboxlist_entry_free(&newmbentry);
631
632 if (r) break;
633 }
634
635 return;
636 }
637
638 enum {
639 ROOT = (1<<0),
640 DOMAIN = (1<<1),
641 MBOX = (1<<2)
642 };
643
644 struct found_data {
645 int type;
646 char mboxname[MAX_MAILBOX_BUFFER];
647 char partition[MAX_MAILBOX_BUFFER];
648 char path[MAX_MAILBOX_PATH+1];
649 };
650
651 struct found_list {
652 int idx;
653 int size;
654 int alloc;
655 struct found_data *data;
656 };
657
add_path(struct found_list * found,int type,const char * name,const char * part,const char * path)658 static void add_path(struct found_list *found, int type,
659 const char *name, const char *part, const char *path)
660 {
661 struct found_data *new;
662
663 if (found->size == found->alloc) {
664 /* reached the end of our allocated array, double it */
665 found->alloc *= 2;
666 found->data = xrealloc(found->data,
667 found->alloc * sizeof(struct found_data));
668 }
669
670 /* add our new node to the end of the array */
671 new = &found->data[found->size++];
672 new->type = type;
673 strcpy(new->mboxname, name);
674 strcpy(new->partition, part);
675 strcpy(new->path, path);
676 }
677
add_part(struct found_list * found,const char * part,const char * path,int override)678 static void add_part(struct found_list *found,
679 const char *part, const char *path, int override)
680 {
681 int i;
682
683 /* see if we already added a partition having this name */
684 for (i = 0; i < found->size; i++){
685 if (!strcmp(found->data[i].partition, part)) {
686 /* found it */
687 if (override) {
688 /* replace the path with the one containing cyrus.header */
689 strcpy(found->data[i].path, path);
690 }
691
692 /* we already have the proper path, so we're done */
693 return;
694 }
695 }
696
697 /* add the new partition path */
698 add_path(found, ROOT, "", part, path);
699 }
700
get_partitions(const char * key,const char * value,void * rock)701 static void get_partitions(const char *key, const char *value, void *rock)
702 {
703 static int check_meta = -1;
704 struct found_list *found = (struct found_list *) rock;
705
706 if (check_meta == -1) {
707 /* see if cyrus.header might be contained in a metapartition */
708 check_meta = (config_metapartition_files &
709 IMAP_ENUM_METAPARTITION_FILES_HEADER);
710 }
711
712 if (!strncmp(key, "partition-", 10)) {
713 add_part(found, key+10, value, 0);
714 }
715 else if (check_meta && !strncmp(key, "metapartition-", 14)) {
716 add_part(found, key+14, value, 1);
717 }
718 /* skip any other overflow strings */
719 }
720
compar_mbox(const void * v1,const void * v2)721 static int compar_mbox(const void *v1, const void *v2)
722 {
723 struct found_data *d1 = (struct found_data *) v1;
724 struct found_data *d2 = (struct found_data *) v2;
725
726 /* non-mailboxes get pushed to the end of the array,
727 otherwise we do an ASCII sort */
728 if (d1->type & MBOX) {
729 if (d2->type & MBOX) return strcmp(d1->mboxname, d2->mboxname);
730 else return -1;
731 }
732 else if (d2->type & MBOX) return 1;
733 else return 0;
734 }
735
verify_cb(const mbentry_t * mbentry,void * rockp)736 static int verify_cb(const mbentry_t *mbentry, void *rockp)
737 {
738 // This function is called for every entry in the database,
739 // and supplied an inventory in &found. *data however does
740 // not pass dlist_parsemap() unlike is the case with dump_db().
741
742 struct found_list *found = (struct found_list *) rockp;
743 int r = 0;
744
745 if (r) {
746 printf("'%s' has a directory '%s' but no DB entry\n",
747 found->data[found->idx].mboxname,
748 found->data[found->idx].path
749 );
750 } else {
751 // Walk the directories to see if the mailbox from data does have
752 // paths on the filesystem.
753 do {
754 r = -1;
755 if (
756 (found->idx >= found->size) || /* end of array */
757 !(found->data[found->idx].type & MBOX) || /* end of mailboxes */
758 (r = strcmp(mbentry->name, found->data[found->idx].mboxname)) < 0
759 ) {
760 printf("'%s' has a DB entry but no directory on partition '%s'\n",
761 mbentry->name, mbentry->partition);
762
763 }
764 else if (r > 0) {
765 printf("'%s' has a directory '%s' but no DB entry\n",
766 found->data[found->idx].mboxname,
767 found->data[found->idx].path
768 );
769
770 found->idx++;
771 }
772 else found->idx++;
773 } while (r > 0);
774
775 }
776
777 return 0;
778 }
779
do_verify(void)780 static void do_verify(void)
781 {
782 struct found_list found;
783 int i;
784
785 found.idx = 0;
786 found.size = 0;
787 found.alloc = 10;
788 found.data = xmalloc(found.alloc * sizeof(struct found_data));
789
790 /* gather a list of partition paths to search */
791 config_foreachoverflowstring(get_partitions, &found);
792
793 /* scan all paths in our list, tagging valid mailboxes,
794 and adding paths as we find them */
795 for (i = 0; i < found.size; i++) {
796 DIR *dirp;
797 struct dirent *dirent;
798 char name[MAX_MAILBOX_BUFFER];
799 char part[MAX_MAILBOX_BUFFER];
800 char path[MAX_MAILBOX_PATH+1];
801 int type;
802
803 if (config_hashimapspool && (found.data[i].type & ROOT)) {
804 /* need to add hashed directories */
805 int config_fulldirhash = libcyrus_config_getswitch(CYRUSOPT_FULLDIRHASH);
806 char *tail;
807 int j, c;
808
809 /* make the toplevel partition /a */
810 if (config_fulldirhash) {
811 strcat(found.data[i].path, "/A");
812 c = 'B';
813 } else {
814 strcat(found.data[i].path, "/a");
815 c = 'b';
816 }
817 type = (found.data[i].type &= ~ROOT);
818
819 /* make a template path for /b - /z */
820 strcpy(name, found.data[i].mboxname);
821 strcpy(part, found.data[i].partition);
822 strcpy(path, found.data[i].path);
823 tail = path + strlen(path) - 1;
824
825 for (j = 1; j < 26; j++, c++) {
826 *tail = c;
827 add_path(&found, type, name, part, path);
828 }
829
830 if (config_virtdomains && !type) {
831 /* need to add root domain directory */
832 strcpy(tail, "domain");
833 add_path(&found, DOMAIN | ROOT, name, part, path);
834 }
835 }
836
837 if (!(dirp = opendir(found.data[i].path))) continue;
838 while ((dirent = readdir(dirp))) {
839 const char *fname = FNAME_HEADER;
840 if (dirent->d_name[0] == '.') continue;
841 else if (!strcmp(dirent->d_name, fname+1)) {
842 /* XXX - check that it can be opened */
843 found.data[i].type |= MBOX;
844 }
845 else if (!strchr(dirent->d_name, '.') ||
846 (found.data[i].type & DOMAIN)) {
847 /* probably a directory, add it to the array */
848 type = 0;
849 strcpy(name, found.data[i].mboxname);
850
851 if (config_virtdomains &&
852 (found.data[i].type == ROOT) &&
853 !strcmp(dirent->d_name, "domain")) {
854 /* root domain directory */
855 type = DOMAIN | ROOT;
856 }
857 else if (!name[0] && found.data[i].type & DOMAIN) {
858 /* toplevel domain directory */
859 strcat(name, dirent->d_name);
860 strcat(name, "!");
861 type = DOMAIN | ROOT;
862 }
863 else {
864 /* possibly a mailbox directory */
865 if (name[0] && !(found.data[i].type & DOMAIN)) strcat(name, ".");
866 strcat(name, dirent->d_name);
867 }
868
869 strcpy(part, found.data[i].partition);
870 strcpy(path, found.data[i].path);
871 strcat(path, "/");
872 strcat(path, dirent->d_name);
873 add_path(&found, type, name, part, path);
874 }
875 }
876
877 closedir(dirp);
878 }
879
880 qsort(found.data, found.size, sizeof(struct found_data), compar_mbox);
881
882 mboxlist_allmbox("", &verify_cb, &found, MBOXTREE_TOMBSTONES);
883 }
884
usage(void)885 static void usage(void)
886 {
887 fprintf(stderr, "DUMP:\n");
888 fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -d [-x] [-y] [-p partition] [-f filename]\n");
889 fprintf(stderr, "UNDUMP:\n");
890 fprintf(stderr,
891 " ctl_mboxlist [-C <alt_config>] -u [-f filename]"
892 " [< mboxlist.dump]\n");
893 fprintf(stderr, "MUPDATE populate:\n");
894 fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -m [-a] [-w] [-i] [-f filename]\n");
895 fprintf(stderr, "VERIFY:\n");
896 fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -v [-f filename]\n");
897 exit(1);
898 }
899
main(int argc,char * argv[])900 int main(int argc, char *argv[])
901 {
902 const char *partition = NULL;
903 char *mboxdb_fname = NULL;
904 int dopurge = 0;
905 int opt;
906 enum mboxop op = NONE;
907 char *alt_config = NULL;
908 int dointermediary = 0;
909
910 while ((opt = getopt(argc, argv, "C:awmdurcxf:p:viy")) != EOF) {
911 switch (opt) {
912 case 'C': /* alt config file */
913 alt_config = optarg;
914 break;
915
916 case 'r':
917 /* deprecated, but we still support it */
918 fprintf(stderr, "ctl_mboxlist -r is deprecated: "
919 "use ctl_cyrusdb -r instead\n");
920 syslog(LOG_WARNING, "ctl_mboxlist -r is deprecated: "
921 "use ctl_cyrusdb -r instead");
922 if (op == NONE) op = RECOVER;
923 else usage();
924 break;
925
926 case 'c':
927 /* deprecated, but we still support it */
928 fprintf(stderr, "ctl_mboxlist -c is deprecated: "
929 "use ctl_cyrusdb -c instead\n");
930 syslog(LOG_WARNING, "ctl_mboxlist -c is deprecated: "
931 "use ctl_cyrusdb -c instead");
932 if (op == NONE) op = CHECKPOINT;
933 else usage();
934 break;
935
936 case 'f':
937 if (!mboxdb_fname) {
938 mboxdb_fname = optarg;
939 } else {
940 usage();
941 }
942 break;
943
944 case 'd':
945 if (op == NONE) op = DUMP;
946 else usage();
947 break;
948
949 case 'u':
950 if (op == NONE) op = UNDUMP;
951 else usage();
952 break;
953
954 case 'm':
955 if (op == NONE) op = M_POPULATE;
956 else usage();
957 break;
958
959 case 'p':
960 partition = optarg;
961 break;
962
963 case 'x':
964 dopurge = 1;
965 break;
966
967 case 'a':
968 local_authoritative = 1;
969 break;
970
971 case 'w':
972 warn_only = 1;
973 break;
974
975 case 'v':
976 if (op == NONE) op = VERIFY;
977 else usage();
978 break;
979
980 case 'i':
981 interactive = 1;
982 break;
983
984 case 'y':
985 dointermediary = 1;
986 break;
987
988 default:
989 usage();
990 break;
991 }
992 }
993
994 if (op != M_POPULATE && (local_authoritative || warn_only)) usage();
995 if (op != DUMP && partition) usage();
996 if (op != DUMP && dopurge) usage();
997 if (op != DUMP && dointermediary) usage();
998
999 if (op == RECOVER) {
1000 syslog(LOG_NOTICE, "running mboxlist recovery");
1001 libcyrus_config_setint(CYRUSOPT_DB_INIT_FLAGS, CYRUSDB_RECOVER);
1002 }
1003
1004 cyrus_init(alt_config, "ctl_mboxlist", 0, 0);
1005 global_sasl_init(1,0,NULL);
1006
1007 switch (op) {
1008 case RECOVER:
1009 /* this was done by the call to cyrus_init via libcyrus */
1010 syslog(LOG_NOTICE, "done running mboxlist recovery");
1011 break;
1012
1013 case CHECKPOINT:
1014 syslog(LOG_NOTICE, "checkpointing mboxlist");
1015 mboxlist_init(MBOXLIST_SYNC);
1016 mboxlist_open(NULL);
1017 mboxlist_close();
1018 mboxlist_done();
1019 syslog(LOG_NOTICE, "done checkpointing mboxlist");
1020 break;
1021
1022 case M_POPULATE:
1023 syslog(LOG_NOTICE, "%spopulating mupdate", warn_only ? "test " : "");
1024 GCC_FALLTHROUGH
1025
1026 case DUMP:
1027 mboxlist_init(0);
1028 mboxlist_open(mboxdb_fname);
1029
1030 do_dump(op, partition, dopurge, dointermediary);
1031
1032 mboxlist_close();
1033 mboxlist_done();
1034
1035 if (op == M_POPULATE) {
1036 syslog(LOG_NOTICE,
1037 "done %spopulating mupdate", warn_only ? "test " : "");
1038 }
1039 break;
1040
1041 case UNDUMP:
1042 mboxlist_init(0);
1043 mboxlist_open(mboxdb_fname);
1044
1045 do_undump();
1046
1047 mboxlist_close();
1048 mboxlist_done();
1049 break;
1050
1051 case VERIFY:
1052 mboxlist_init(0);
1053 mboxlist_open(mboxdb_fname);
1054
1055 do_verify();
1056
1057 mboxlist_close();
1058 mboxlist_done();
1059 break;
1060
1061 default:
1062 usage();
1063 cyrus_done();
1064 return 1;
1065 }
1066
1067 cyrus_done();
1068 return 0;
1069 }
1070