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, 0, 1, 1, 0);
534 } else if (mboxname_isdeletedmailbox(me->mailbox, NULL)) {
535 ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, NULL, 0, 1, 1, 0);
536 } else {
537 ret = mboxlist_delayed_deletemailbox(me->mailbox, 1, "", NULL, NULL, 0, 1, 1, 0);
538 }
539
540 mboxname_release(&namespacelock);
541
542 if (ret) {
543 fprintf(stderr, "couldn't delete defunct mailbox %s\n",
544 me->mailbox);
545 exit(1);
546 }
547
548 free(me);
549 }
550
551 /* Done with mupdate */
552 mupdate_disconnect(&(d.h));
553 sasl_done();
554 }
555
556 return;
557 }
558
do_undump(void)559 static void do_undump(void)
560 {
561 int r = 0;
562 char buf[16384];
563 int line = 0;
564 const char *name, *partition, *acl;
565 int mbtype;
566 char *p;
567
568 while (fgets(buf, sizeof(buf), stdin)) {
569 mbentry_t *newmbentry = NULL;
570 const char *server = NULL;
571
572 line++;
573
574 name = buf;
575 for (p = buf; *p && *p != '\t'; p++) ;
576 if (!*p) {
577 fprintf(stderr, "line %d: no partition found\n", line);
578 continue;
579 }
580 *p++ = '\0';
581 if (Uisdigit(*p)) {
582 /* new style dump */
583 mbtype = strtol(p, &p, 10);
584 /* skip trailing space */
585 if (*p == ' ') p++;
586 }
587 else mbtype = 0;
588
589 partition = p;
590 for (; *p && (*p != ' ') && (*p != '\t'); p++) {
591 if (*p == '!') {
592 *p++ = '\0';
593 server = partition;
594 partition = p;
595 }
596 }
597 if (!*p) {
598 fprintf(stderr, "line %d: no acl found\n", line);
599 continue;
600 }
601 *p++ = '\0';
602 acl = p;
603 /* chop off the newline */
604 for (; *p && *p != '\r' && *p != '\n'; p++) ;
605 *p++ = '\0';
606
607 if (strlen(name) >= MAX_MAILBOX_BUFFER) {
608 fprintf(stderr, "line %d: mailbox name too long\n", line);
609 continue;
610 }
611 if (strlen(partition) >= MAX_PARTITION_LEN) {
612 fprintf(stderr, "line %d: partition name too long\n", line);
613 continue;
614 }
615
616 /* generate a new entry */
617 newmbentry = mboxlist_entry_create();
618 newmbentry->name = xstrdup(name);
619 newmbentry->mbtype = mbtype;
620 newmbentry->server = xstrdupnull(server);
621 newmbentry->partition = xstrdupnull(partition);
622 newmbentry->acl = xstrdupnull(acl);
623 /* XXX - still missing all the new fields */
624
625 r = mboxlist_update(newmbentry, /*localonly*/1);
626 mboxlist_entry_free(&newmbentry);
627
628 if (r) break;
629 }
630
631 return;
632 }
633
634 enum {
635 ROOT = (1<<0),
636 DOMAIN = (1<<1),
637 MBOX = (1<<2)
638 };
639
640 struct found_data {
641 int type;
642 char mboxname[MAX_MAILBOX_BUFFER];
643 char partition[MAX_MAILBOX_BUFFER];
644 char path[MAX_MAILBOX_PATH+1];
645 };
646
647 struct found_list {
648 int idx;
649 int size;
650 int alloc;
651 struct found_data *data;
652 };
653
add_path(struct found_list * found,int type,const char * name,const char * part,const char * path)654 static void add_path(struct found_list *found, int type,
655 const char *name, const char *part, const char *path)
656 {
657 struct found_data *new;
658
659 if (found->size == found->alloc) {
660 /* reached the end of our allocated array, double it */
661 found->alloc *= 2;
662 found->data = xrealloc(found->data,
663 found->alloc * sizeof(struct found_data));
664 }
665
666 /* add our new node to the end of the array */
667 new = &found->data[found->size++];
668 new->type = type;
669 strcpy(new->mboxname, name);
670 strcpy(new->partition, part);
671 strcpy(new->path, path);
672 }
673
add_part(struct found_list * found,const char * part,const char * path,int override)674 static void add_part(struct found_list *found,
675 const char *part, const char *path, int override)
676 {
677 int i;
678
679 /* see if we already added a partition having this name */
680 for (i = 0; i < found->size; i++){
681 if (!strcmp(found->data[i].partition, part)) {
682 /* found it */
683 if (override) {
684 /* replace the path with the one containing cyrus.header */
685 strcpy(found->data[i].path, path);
686 }
687
688 /* we already have the proper path, so we're done */
689 return;
690 }
691 }
692
693 /* add the new partition path */
694 add_path(found, ROOT, "", part, path);
695 }
696
get_partitions(const char * key,const char * value,void * rock)697 static void get_partitions(const char *key, const char *value, void *rock)
698 {
699 static int check_meta = -1;
700 struct found_list *found = (struct found_list *) rock;
701
702 if (check_meta == -1) {
703 /* see if cyrus.header might be contained in a metapartition */
704 check_meta = (config_metapartition_files &
705 IMAP_ENUM_METAPARTITION_FILES_HEADER);
706 }
707
708 if (!strncmp(key, "partition-", 10)) {
709 add_part(found, key+10, value, 0);
710 }
711 else if (check_meta && !strncmp(key, "metapartition-", 14)) {
712 add_part(found, key+14, value, 1);
713 }
714 /* skip any other overflow strings */
715 }
716
compar_mbox(const void * v1,const void * v2)717 static int compar_mbox(const void *v1, const void *v2)
718 {
719 struct found_data *d1 = (struct found_data *) v1;
720 struct found_data *d2 = (struct found_data *) v2;
721
722 /* non-mailboxes get pushed to the end of the array,
723 otherwise we do an ASCII sort */
724 if (d1->type & MBOX) {
725 if (d2->type & MBOX) return strcmp(d1->mboxname, d2->mboxname);
726 else return -1;
727 }
728 else if (d2->type & MBOX) return 1;
729 else return 0;
730 }
731
verify_cb(const mbentry_t * mbentry,void * rockp)732 static int verify_cb(const mbentry_t *mbentry, void *rockp)
733 {
734 // This function is called for every entry in the database,
735 // and supplied an inventory in &found. *data however does
736 // not pass dlist_parsemap() unlike is the case with dump_db().
737
738 struct found_list *found = (struct found_list *) rockp;
739 int r = 0;
740
741 if (r) {
742 printf("'%s' has a directory '%s' but no DB entry\n",
743 found->data[found->idx].mboxname,
744 found->data[found->idx].path
745 );
746 } else {
747 // Walk the directories to see if the mailbox from data does have
748 // paths on the filesystem.
749 do {
750 r = -1;
751 if (
752 (found->idx >= found->size) || /* end of array */
753 !(found->data[found->idx].type & MBOX) || /* end of mailboxes */
754 (r = strcmp(mbentry->name, found->data[found->idx].mboxname)) < 0
755 ) {
756 printf("'%s' has a DB entry but no directory on partition '%s'\n",
757 mbentry->name, mbentry->partition);
758
759 }
760 else if (r > 0) {
761 printf("'%s' has a directory '%s' but no DB entry\n",
762 found->data[found->idx].mboxname,
763 found->data[found->idx].path
764 );
765
766 found->idx++;
767 }
768 else found->idx++;
769 } while (r > 0);
770
771 }
772
773 return 0;
774 }
775
do_verify(void)776 static void do_verify(void)
777 {
778 struct found_list found;
779 int i;
780
781 found.idx = 0;
782 found.size = 0;
783 found.alloc = 10;
784 found.data = xmalloc(found.alloc * sizeof(struct found_data));
785
786 /* gather a list of partition paths to search */
787 config_foreachoverflowstring(get_partitions, &found);
788
789 /* scan all paths in our list, tagging valid mailboxes,
790 and adding paths as we find them */
791 for (i = 0; i < found.size; i++) {
792 DIR *dirp;
793 struct dirent *dirent;
794 char name[MAX_MAILBOX_BUFFER];
795 char part[MAX_MAILBOX_BUFFER];
796 char path[MAX_MAILBOX_PATH+1];
797 int type;
798
799 if (config_hashimapspool && (found.data[i].type & ROOT)) {
800 /* need to add hashed directories */
801 int config_fulldirhash = libcyrus_config_getswitch(CYRUSOPT_FULLDIRHASH);
802 char *tail;
803 int j, c;
804
805 /* make the toplevel partition /a */
806 if (config_fulldirhash) {
807 strcat(found.data[i].path, "/A");
808 c = 'B';
809 } else {
810 strcat(found.data[i].path, "/a");
811 c = 'b';
812 }
813 type = (found.data[i].type &= ~ROOT);
814
815 /* make a template path for /b - /z */
816 strcpy(name, found.data[i].mboxname);
817 strcpy(part, found.data[i].partition);
818 strcpy(path, found.data[i].path);
819 tail = path + strlen(path) - 1;
820
821 for (j = 1; j < 26; j++, c++) {
822 *tail = c;
823 add_path(&found, type, name, part, path);
824 }
825
826 if (config_virtdomains && !type) {
827 /* need to add root domain directory */
828 strcpy(tail, "domain");
829 add_path(&found, DOMAIN | ROOT, name, part, path);
830 }
831 }
832
833 if (!(dirp = opendir(found.data[i].path))) continue;
834 while ((dirent = readdir(dirp))) {
835 const char *fname = FNAME_HEADER;
836 if (dirent->d_name[0] == '.') continue;
837 else if (!strcmp(dirent->d_name, fname+1)) {
838 /* XXX - check that it can be opened */
839 found.data[i].type |= MBOX;
840 }
841 else if (!strchr(dirent->d_name, '.') ||
842 (found.data[i].type & DOMAIN)) {
843 /* probably a directory, add it to the array */
844 type = 0;
845 strcpy(name, found.data[i].mboxname);
846
847 if (config_virtdomains &&
848 (found.data[i].type == ROOT) &&
849 !strcmp(dirent->d_name, "domain")) {
850 /* root domain directory */
851 type = DOMAIN | ROOT;
852 }
853 else if (!name[0] && found.data[i].type & DOMAIN) {
854 /* toplevel domain directory */
855 strcat(name, dirent->d_name);
856 strcat(name, "!");
857 type = DOMAIN | ROOT;
858 }
859 else {
860 /* possibly a mailbox directory */
861 if (name[0] && !(found.data[i].type & DOMAIN)) strcat(name, ".");
862 strcat(name, dirent->d_name);
863 }
864
865 strcpy(part, found.data[i].partition);
866 strcpy(path, found.data[i].path);
867 strcat(path, "/");
868 strcat(path, dirent->d_name);
869 add_path(&found, type, name, part, path);
870 }
871 }
872
873 closedir(dirp);
874 }
875
876 qsort(found.data, found.size, sizeof(struct found_data), compar_mbox);
877
878 mboxlist_allmbox("", &verify_cb, &found, MBOXTREE_TOMBSTONES);
879 }
880
usage(void)881 static void usage(void)
882 {
883 fprintf(stderr, "DUMP:\n");
884 fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -d [-x] [-y] [-p partition] [-f filename]\n");
885 fprintf(stderr, "UNDUMP:\n");
886 fprintf(stderr,
887 " ctl_mboxlist [-C <alt_config>] -u [-f filename]"
888 " [< mboxlist.dump]\n");
889 fprintf(stderr, "MUPDATE populate:\n");
890 fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -m [-a] [-w] [-i] [-f filename]\n");
891 fprintf(stderr, "VERIFY:\n");
892 fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -v [-f filename]\n");
893 exit(1);
894 }
895
main(int argc,char * argv[])896 int main(int argc, char *argv[])
897 {
898 const char *partition = NULL;
899 char *mboxdb_fname = NULL;
900 int dopurge = 0;
901 int opt;
902 enum mboxop op = NONE;
903 char *alt_config = NULL;
904 int dointermediary = 0;
905
906 while ((opt = getopt(argc, argv, "C:awmdurcxf:p:viy")) != EOF) {
907 switch (opt) {
908 case 'C': /* alt config file */
909 alt_config = optarg;
910 break;
911
912 case 'r':
913 /* deprecated, but we still support it */
914 fprintf(stderr, "ctl_mboxlist -r is deprecated: "
915 "use ctl_cyrusdb -r instead\n");
916 syslog(LOG_WARNING, "ctl_mboxlist -r is deprecated: "
917 "use ctl_cyrusdb -r instead");
918 if (op == NONE) op = RECOVER;
919 else usage();
920 break;
921
922 case 'c':
923 /* deprecated, but we still support it */
924 fprintf(stderr, "ctl_mboxlist -c is deprecated: "
925 "use ctl_cyrusdb -c instead\n");
926 syslog(LOG_WARNING, "ctl_mboxlist -c is deprecated: "
927 "use ctl_cyrusdb -c instead");
928 if (op == NONE) op = CHECKPOINT;
929 else usage();
930 break;
931
932 case 'f':
933 if (!mboxdb_fname) {
934 mboxdb_fname = optarg;
935 } else {
936 usage();
937 }
938 break;
939
940 case 'd':
941 if (op == NONE) op = DUMP;
942 else usage();
943 break;
944
945 case 'u':
946 if (op == NONE) op = UNDUMP;
947 else usage();
948 break;
949
950 case 'm':
951 if (op == NONE) op = M_POPULATE;
952 else usage();
953 break;
954
955 case 'p':
956 partition = optarg;
957 break;
958
959 case 'x':
960 dopurge = 1;
961 break;
962
963 case 'a':
964 local_authoritative = 1;
965 break;
966
967 case 'w':
968 warn_only = 1;
969 break;
970
971 case 'v':
972 if (op == NONE) op = VERIFY;
973 else usage();
974 break;
975
976 case 'i':
977 interactive = 1;
978 break;
979
980 case 'y':
981 dointermediary = 1;
982 break;
983
984 default:
985 usage();
986 break;
987 }
988 }
989
990 if (op != M_POPULATE && (local_authoritative || warn_only)) usage();
991 if (op != DUMP && partition) usage();
992 if (op != DUMP && dopurge) usage();
993 if (op != DUMP && dointermediary) usage();
994
995 if (op == RECOVER) {
996 syslog(LOG_NOTICE, "running mboxlist recovery");
997 libcyrus_config_setint(CYRUSOPT_DB_INIT_FLAGS, CYRUSDB_RECOVER);
998 }
999
1000 cyrus_init(alt_config, "ctl_mboxlist", 0, 0);
1001 global_sasl_init(1,0,NULL);
1002
1003 switch (op) {
1004 case RECOVER:
1005 /* this was done by the call to cyrus_init via libcyrus */
1006 syslog(LOG_NOTICE, "done running mboxlist recovery");
1007 break;
1008
1009 case CHECKPOINT:
1010 syslog(LOG_NOTICE, "checkpointing mboxlist");
1011 mboxlist_init(MBOXLIST_SYNC);
1012 mboxlist_open(NULL);
1013 mboxlist_close();
1014 mboxlist_done();
1015 syslog(LOG_NOTICE, "done checkpointing mboxlist");
1016 break;
1017
1018 case M_POPULATE:
1019 syslog(LOG_NOTICE, "%spopulating mupdate", warn_only ? "test " : "");
1020 GCC_FALLTHROUGH
1021
1022 case DUMP:
1023 mboxlist_init(0);
1024 mboxlist_open(mboxdb_fname);
1025
1026 do_dump(op, partition, dopurge, dointermediary);
1027
1028 mboxlist_close();
1029 mboxlist_done();
1030
1031 if (op == M_POPULATE) {
1032 syslog(LOG_NOTICE,
1033 "done %spopulating mupdate", warn_only ? "test " : "");
1034 }
1035 break;
1036
1037 case UNDUMP:
1038 mboxlist_init(0);
1039 mboxlist_open(mboxdb_fname);
1040
1041 do_undump();
1042
1043 mboxlist_close();
1044 mboxlist_done();
1045 break;
1046
1047 case VERIFY:
1048 mboxlist_init(0);
1049 mboxlist_open(mboxdb_fname);
1050
1051 do_verify();
1052
1053 mboxlist_close();
1054 mboxlist_done();
1055 break;
1056
1057 default:
1058 usage();
1059 cyrus_done();
1060 return 1;
1061 }
1062
1063 cyrus_done();
1064 return 0;
1065 }
1066