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