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