1 /* mboxname.c -- Mailbox list manipulation routines
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 #include <config.h>
44 
45 #include <errno.h>
46 #include <stdio.h>
47 #include <string.h>
48 #include <sysexits.h>
49 #include <syslog.h>
50 #ifdef HAVE_UNISTD_H
51 #include <unistd.h>
52 #endif
53 
54 #include "assert.h"
55 #include "byteorder64.h"
56 #include "crc32.h"
57 #include "glob.h"
58 #include "global.h"
59 #include "mailbox.h"
60 #include "map.h"
61 #include "retry.h"
62 #include "user.h"
63 #include "util.h"
64 #include "xmalloc.h"
65 
66 /* generated headers are not necessarily in current directory */
67 #include "imap/imap_err.h"
68 
69 #include "mboxname.h"
70 #include "mboxlist.h"
71 #include "cyr_lock.h"
72 
73 struct mboxlocklist {
74     struct mboxlocklist *next;
75     struct mboxlock l;
76     int nopen;
77 };
78 
79 static struct mboxlocklist *open_mboxlocks = NULL;
80 
81 static struct namespace *admin_namespace;
82 
83 struct mbname_parts {
84     /* master data */
85     strarray_t *boxes;
86     time_t is_deleted;
87     char *localpart;
88     char *domain;
89 
90     /* actual namespace */
91     const struct namespace *extns;
92     char *extuserid;
93 
94     /* cache data */
95     char *userid;
96     char *intname;
97     char *extname;
98     char *recipient;
99 };
100 
101 #define XX 127
102 /*
103  * Table for decoding modified base64 for IMAP UTF-7 mailbox names
104  */
105 static const char index_mod64[256] = {
106     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
107     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
108     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, 63,XX,XX,XX,
109     52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
110     XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
111     15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
112     XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
113     41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
114     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
115     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
116     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
117     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
118     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
119     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
120     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
121     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
122 };
123 #define CHARMOD64(c)  (index_mod64[(unsigned char)(c)])
124 
125 #define FNAME_SHAREDPREFIX "shared"
126 
127 
create_lockitem(const char * name)128 static struct mboxlocklist *create_lockitem(const char *name)
129 {
130     struct mboxlocklist *item = xmalloc(sizeof(struct mboxlocklist));
131     item->next = open_mboxlocks;
132     open_mboxlocks = item;
133 
134     item->nopen = 1;
135     item->l.name = xstrdup(name);
136     item->l.lock_fd = -1;
137     item->l.locktype = 0;
138 
139     return item;
140 }
141 
find_lockitem(const char * name)142 static struct mboxlocklist *find_lockitem(const char *name)
143 {
144     struct mboxlocklist *item;
145 
146     for (item = open_mboxlocks; item; item = item->next) {
147         if (!strcmp(name, item->l.name))
148             return item;
149     }
150 
151     return NULL;
152 }
153 
remove_lockitem(struct mboxlocklist * remitem)154 static void remove_lockitem(struct mboxlocklist *remitem)
155 {
156     struct mboxlocklist *item;
157     struct mboxlocklist *previtem = NULL;
158 
159     for (item = open_mboxlocks; item; item = item->next) {
160         if (item == remitem) {
161             if (previtem)
162                 previtem->next = item->next;
163             else
164                 open_mboxlocks = item->next;
165             if (item->l.lock_fd != -1) {
166                 if (item->l.locktype)
167                     lock_unlock(item->l.lock_fd, item->l.name);
168                 close(item->l.lock_fd);
169             }
170             free(item->l.name);
171             free(item);
172             return;
173         }
174         previtem = item;
175     }
176 
177     fatal("didn't find item in list", EX_SOFTWARE);
178 }
179 
180 /* name locking support */
181 
mboxname_lock(const char * mboxname,struct mboxlock ** mboxlockptr,int locktype_and_flags)182 EXPORTED int mboxname_lock(const char *mboxname, struct mboxlock **mboxlockptr,
183                   int locktype_and_flags)
184 {
185     const char *fname;
186     int r = 0;
187     struct mboxlocklist *lockitem;
188     int nonblock;
189     int locktype;
190 
191     nonblock = !!(locktype_and_flags & LOCK_NONBLOCK);
192     locktype = (locktype_and_flags & ~LOCK_NONBLOCK);
193 
194     fname = mboxname_lockpath(mboxname);
195     if (!fname)
196         return IMAP_MAILBOX_BADNAME;
197 
198     lockitem = find_lockitem(mboxname);
199 
200     /* already open?  just use this one */
201     if (lockitem) {
202         /* can't change locktype! */
203         if (lockitem->l.locktype != locktype)
204             return IMAP_MAILBOX_LOCKED;
205 
206         lockitem->nopen++;
207         goto done;
208     }
209 
210     lockitem = create_lockitem(mboxname);
211 
212     /* assume success, and only create directory on failure.
213      * More efficient on a common codepath */
214     lockitem->l.lock_fd = open(fname, O_CREAT | O_TRUNC | O_RDWR, 0666);
215     if (lockitem->l.lock_fd == -1) {
216         if (cyrus_mkdir(fname, 0755) == -1) {
217             r = IMAP_IOERROR;
218             goto done;
219         }
220         lockitem->l.lock_fd = open(fname, O_CREAT | O_TRUNC | O_RDWR, 0666);
221     }
222     /* but if it still didn't succeed, we have problems */
223     if (lockitem->l.lock_fd == -1) {
224         r = IMAP_IOERROR;
225         goto done;
226     }
227 
228     r = lock_setlock(lockitem->l.lock_fd,
229                      locktype == LOCK_EXCLUSIVE,
230                      nonblock, fname);
231     if (!r) lockitem->l.locktype = locktype;
232     else if (errno == EWOULDBLOCK) r = IMAP_MAILBOX_LOCKED;
233     else r = errno;
234 
235 done:
236     if (r) remove_lockitem(lockitem);
237     else *mboxlockptr = &lockitem->l;
238 
239     return r;
240 }
241 
mboxname_release(struct mboxlock ** mboxlockptr)242 EXPORTED void mboxname_release(struct mboxlock **mboxlockptr)
243 {
244     if (!*mboxlockptr) return;
245 
246     struct mboxlocklist *lockitem;
247     struct mboxlock *lock = *mboxlockptr;
248 
249     lockitem = find_lockitem(lock->name);
250     assert(lockitem && &lockitem->l == lock);
251 
252     *mboxlockptr = NULL;
253 
254     if (lockitem->nopen > 1) {
255         lockitem->nopen--;
256         return;
257     }
258 
259     remove_lockitem(lockitem);
260 }
261 
mboxname_islocked(const char * mboxname)262 EXPORTED int mboxname_islocked(const char *mboxname)
263 {
264     return find_lockitem(mboxname) ? 1 : 0;
265 }
266 
mboxname_usernamespacelock(const char * mboxname)267 EXPORTED struct mboxlock *mboxname_usernamespacelock(const char *mboxname)
268 {
269     mbname_t *mbname = mbname_from_intname(mboxname);
270     struct mboxlock *lock = user_namespacelock(mbname_userid(mbname));
271     mbname_free(&mbname);
272     return lock;
273 }
274 
275 /******************** mbname stuff **********************/
276 
_mbdirty(mbname_t * mbname)277 static void _mbdirty(mbname_t *mbname)
278 {
279     free(mbname->userid);
280     free(mbname->intname);
281     free(mbname->extname);
282     free(mbname->recipient);
283 
284     mbname->userid = NULL;
285     mbname->intname = NULL;
286     mbname->extname = NULL;
287     mbname->recipient = NULL;
288 }
289 
mbname_downcaseuser(mbname_t * mbname)290 EXPORTED void mbname_downcaseuser(mbname_t *mbname)
291 {
292     _mbdirty(mbname);
293     if (mbname->localpart) lcase(mbname->localpart);
294     if (mbname->domain) lcase(mbname->domain);
295 }
296 
mbname_set_localpart(mbname_t * mbname,const char * localpart)297 EXPORTED void mbname_set_localpart(mbname_t *mbname, const char *localpart)
298 {
299     _mbdirty(mbname);
300     free(mbname->localpart);
301     mbname->localpart = xstrdupnull(localpart);
302 }
303 
mbname_set_domain(mbname_t * mbname,const char * domain)304 EXPORTED void mbname_set_domain(mbname_t *mbname, const char *domain)
305 {
306     _mbdirty(mbname);
307     free(mbname->domain);
308     mbname->domain = strcmpsafe(domain, config_defdomain) ? xstrdupnull(domain) : NULL;
309 }
310 
mbname_set_boxes(mbname_t * mbname,const strarray_t * boxes)311 EXPORTED void mbname_set_boxes(mbname_t *mbname, const strarray_t *boxes)
312 {
313     _mbdirty(mbname);
314     strarray_free(mbname->boxes);
315     if (boxes)
316         mbname->boxes = strarray_dup(boxes);
317     else
318         mbname->boxes = NULL;
319 }
320 
mbname_push_boxes(mbname_t * mbname,const char * item)321 EXPORTED void mbname_push_boxes(mbname_t *mbname, const char *item)
322 {
323     _mbdirty(mbname);
324     if (!mbname->boxes) mbname->boxes = strarray_new();
325     strarray_push(mbname->boxes, item);
326 }
327 
mbname_pop_boxes(mbname_t * mbname)328 EXPORTED char *mbname_pop_boxes(mbname_t *mbname)
329 {
330     _mbdirty(mbname);
331     if (!mbname->boxes) mbname->boxes = strarray_new();
332     return strarray_pop(mbname->boxes);
333 }
334 
mbname_truncate_boxes(mbname_t * mbname,size_t len)335 EXPORTED void mbname_truncate_boxes(mbname_t *mbname, size_t len)
336 {
337     _mbdirty(mbname);
338     if (!mbname->boxes) mbname->boxes = strarray_new();
339     strarray_truncate(mbname->boxes, len);
340 }
341 
mbname_set_isdeleted(mbname_t * mbname,time_t isdel)342 EXPORTED void mbname_set_isdeleted(mbname_t *mbname, time_t isdel)
343 {
344     _mbdirty(mbname);
345     mbname->is_deleted = isdel;
346 }
347 
mbname_from_userid(const char * userid)348 EXPORTED mbname_t *mbname_from_userid(const char *userid)
349 {
350     mbname_t *mbname = xzmalloc(sizeof(mbname_t));
351     const char *p;
352 
353     if (!userid)
354         return mbname;
355 
356     if (!*userid)
357         return mbname; // empty string, *sigh*
358 
359     mbname->userid = xstrdup(userid); // may as well cache it
360 
361     p = strrchr(userid, '@');
362     if (p) {
363         mbname->localpart = xstrndup(userid, p - userid);
364         const char *domain = p+1;
365         mbname->domain = strcmpsafe(domain, config_defdomain) ? xstrdupnull(domain) : NULL;
366     }
367     else {
368         mbname->localpart = xstrdup(userid);
369     }
370 
371     return mbname;
372 }
373 
mbname_from_recipient(const char * recipient,const struct namespace * ns)374 EXPORTED mbname_t *mbname_from_recipient(const char *recipient, const struct namespace *ns)
375 {
376     mbname_t *mbname = xzmalloc(sizeof(mbname_t));
377 
378     if (!recipient)
379         return mbname;
380 
381     mbname->recipient = xstrdup(recipient); // may as well cache it
382     mbname->extns = ns;
383 
384     const char *at = strrchr(recipient, '@');
385     if (at) {
386         mbname->localpart = xstrndup(recipient, at - recipient);
387         const char *domain = at+1;
388         if (config_virtdomains && strcmpsafe(domain, config_defdomain))
389             mbname->domain = xstrdupnull(domain);
390         /* otherwise we ignore domain entirely */
391     }
392     else {
393         mbname->localpart = xstrdup(recipient);
394     }
395 
396     char *plus = strchr(mbname->localpart, '+');
397     if (plus) {
398         char sep[2];
399         sep[0] = ns->hier_sep;
400         sep[1] = '\0';
401         *plus = '\0';
402         mbname->boxes = strarray_split(plus+1, sep, /*flags*/0);
403     }
404     else
405         mbname->boxes = strarray_new();
406 
407     return mbname;
408 }
409 
mbname_from_extsub(const char * subfolder,const struct namespace * ns,const char * userid)410 EXPORTED mbname_t *mbname_from_extsub(const char *subfolder, const struct namespace *ns, const char *userid)
411 {
412     mbname_t *mbname = mbname_from_userid(userid);
413 
414     if (!subfolder)
415         return mbname;
416 
417     /* we know boxes isn't set already */
418     assert(!mbname->boxes);
419     char sep[2];
420     sep[0] = ns->hier_sep;
421     sep[1] = '\0';
422     mbname->boxes = strarray_split(subfolder, sep, /*flags*/0);
423 
424     return mbname;
425 }
426 
mbname_dup(const mbname_t * orig)427 EXPORTED mbname_t *mbname_dup(const mbname_t *orig)
428 {
429     mbname_t *mbname = xzmalloc(sizeof(mbname_t));
430 
431     mbname->localpart = xstrdupnull(orig->localpart);
432     mbname->domain = xstrdupnull(orig->domain);
433     mbname->is_deleted = orig->is_deleted;
434     if (orig->boxes) mbname->boxes = strarray_dup(orig->boxes);
435 
436     return mbname;
437 }
438 
_append_intbuf(struct buf * buf,const char * val)439 static void _append_intbuf(struct buf *buf, const char *val)
440 {
441     const char *p;
442     for (p = val; *p; p++) {
443         switch (*p) {
444         case '.':
445             buf_putc(buf, '^');
446             break;
447         default:
448             buf_putc(buf, *p);
449             break;
450         }
451     }
452 }
453 
_array_from_intname(strarray_t * a)454 static strarray_t *_array_from_intname(strarray_t *a)
455 {
456     int i;
457     for (i = 0; i < strarray_size(a); i++) {
458         char *p;
459         for (p = a->data[i]; *p; p++) {
460             switch (*p) {
461             case '^':
462                 *p = '.';
463                 break;
464             default:
465                 break;
466             }
467         }
468     }
469     return a;
470 }
471 
_append_extbuf(const struct namespace * ns,struct buf * buf,const char * val)472 static void _append_extbuf(const struct namespace *ns, struct buf *buf, const char *val)
473 {
474     const char *p;
475     int isuhs = (ns->hier_sep == '/');
476     for (p = val; *p; p++) {
477         switch (*p) {
478         case '.':
479             if (isuhs) buf_putc(buf, '.');
480             else buf_putc(buf, '^');
481             break;
482         default:
483             buf_putc(buf, *p);
484             break;
485         }
486     }
487 }
488 
_array_from_extname(const struct namespace * ns,strarray_t * a)489 static strarray_t *_array_from_extname(const struct namespace *ns, strarray_t *a)
490 {
491     int i;
492     int isuhs = (ns->hier_sep == '/');
493     for (i = 0; i < strarray_size(a); i++) {
494         char *p;
495         for (p = a->data[i]; *p; p++) {
496             switch (*p) {
497             case '^':
498                 if (isuhs) goto err;
499                 else *p = '.';
500                 break;
501             case '/':
502                 goto err;
503             default:
504                 break;
505             }
506         }
507     }
508     return a;
509 
510 err:
511     strarray_free(a);
512     return NULL;
513 }
514 
515 
mbname_from_intname(const char * intname)516 EXPORTED mbname_t *mbname_from_intname(const char *intname)
517 {
518     mbname_t *mbname = xzmalloc(sizeof(mbname_t));
519     const char *p;
520 
521     if (!intname)
522         return mbname;
523 
524     if (!*intname)
525         return mbname; // empty string, *sigh*
526 
527     const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX);
528 
529     mbname->intname = xstrdup(intname); // may as well cache it
530 
531     p = strchr(intname, '!');
532     if (p) {
533         mbname->domain = xstrndup(intname, p - intname);
534         if (!strcmpsafe(mbname->domain, config_defdomain)) {
535             free(mbname->domain);
536             mbname->domain = NULL;
537         }
538         intname = p+1;
539     }
540 
541     mbname->boxes = _array_from_intname(strarray_split(intname, ".", 0));
542 
543     if (!strarray_size(mbname->boxes))
544         return mbname;
545 
546     if (strarray_size(mbname->boxes) > 2 && !strcmpsafe(strarray_nth(mbname->boxes, 0), dp)) {
547         free(strarray_shift(mbname->boxes));
548         char *delval = strarray_pop(mbname->boxes);
549         mbname->is_deleted = strtoul(delval, NULL, 16);
550         free(delval);
551     }
552 
553     if (strarray_size(mbname->boxes) > 1 && !strcmpsafe(strarray_nth(mbname->boxes, 0), "user")) {
554         free(strarray_shift(mbname->boxes));
555         mbname->localpart = strarray_shift(mbname->boxes);
556     }
557 
558     return mbname;
559 }
560 
mbname_from_extname(const char * extname,const struct namespace * ns,const char * userid)561 EXPORTED mbname_t *mbname_from_extname(const char *extname, const struct namespace *ns, const char *userid)
562 {
563     int crossdomains = config_getswitch(IMAPOPT_CROSSDOMAINS) && !ns->isadmin;
564     int cdother = config_getswitch(IMAPOPT_CROSSDOMAINS_ONLYOTHER);
565     /* old-school virtdomains requires admin to be a different domain than the userid */
566     int admindomains = config_virtdomains && ns->isadmin;
567 
568     /* specialuse magic */
569     if (extname && extname[0] == '\\') {
570         char *intname = mboxlist_find_specialuse(extname, userid);
571         mbname_t *mbname = mbname_from_intname(intname);
572         free(intname);
573         return mbname;
574     }
575 
576     mbname_t *mbname = xzmalloc(sizeof(mbname_t));
577     char sepstr[2];
578     char *p = NULL;
579 
580     if (!extname)
581         return mbname;
582 
583     if (!*extname)
584         return mbname; // empty string, *sigh*
585 
586     sepstr[0] = ns->hier_sep;
587     sepstr[1] = '\0';
588 
589     mbname->extname = xstrdup(extname); // may as well cache it
590 
591     mbname_t *userparts = mbname_from_userid(userid);
592 
593     if (admindomains) {
594         p = strrchr(mbname->extname, '@');
595         if (p) {
596             *p = '\0';
597             if (strcmpsafe(p+1, config_defdomain))
598                 mbname->domain = xstrdup(p+1);
599         }
600         else {
601             // domain admin?
602             mbname->domain = xstrdupnull(mbname_domain(userparts));
603         }
604     }
605     else if (!crossdomains) {
606         // non-crossdomains, we're always in the user's domain
607         mbname->domain = xstrdupnull(mbname_domain(userparts));
608     }
609 
610     mbname->boxes = _array_from_extname(ns, strarray_split(mbname->extname, sepstr, 0));
611 
612     if (p) *p = '@'; // rebuild extname for later use
613 
614     if (!mbname->boxes)
615         goto done;
616 
617     if (!strarray_size(mbname->boxes))
618         goto done;
619 
620     if (ns->isalt) {
621         /* admin can't be in here, so we can ignore that :) - and hence also
622          * the DELETED namespace */
623         assert(!ns->isadmin);
624 
625         const char *toplevel = strarray_nth(mbname->boxes, 0);
626 
627         const char *up = config_getstring(IMAPOPT_USERPREFIX);
628         const char *sp = config_getstring(IMAPOPT_SHAREDPREFIX);
629         const char *ap = config_getstring(IMAPOPT_ALTPREFIX);
630 
631         if (!strcmpsafe(toplevel, ap)) {
632             free(strarray_shift(mbname->boxes));
633 
634             /* everything belongs to the userid */
635             mbname->localpart = xstrdupnull(mbname_localpart(userparts));
636             /* otherwise it was done above */
637             if (crossdomains) mbname->domain = xstrdupnull(mbname_domain(userparts));
638 
639             goto done;
640         }
641 
642         else if (!strcmpsafe(toplevel, up)) {
643             /* other user namespace */
644             free(strarray_shift(mbname->boxes));
645             mbname->localpart = strarray_shift(mbname->boxes);
646             if (crossdomains && mbname->localpart) {
647                 char *p = strrchr(mbname->localpart, '@');
648                 if (p) {
649                     *p = '\0';
650                     if (strcmpsafe(p+1, config_defdomain))
651                         mbname->domain = xstrdup(p+1);
652                 }
653                 else if (cdother) {
654                     mbname->domain = xstrdupnull(mbname_domain(userparts));
655                 }
656                 /* otherwise it must be in defdomain.  Domains are
657                  * always specified in crossdomains */
658             }
659             goto done;
660         }
661 
662         else if (!strcmpsafe(toplevel, sp)) {
663             /* shared namespace, no user */
664             free(strarray_shift(mbname->boxes));
665             if (crossdomains) {
666                 const char *toplevel = strarray_nth(mbname->boxes, 0);
667                 if (toplevel && strrchr(toplevel, '@')) {
668                     char *p = (char *)strrchr(toplevel, '@');
669                     *p = '\0';
670                     if (strcmpsafe(p+1, config_defdomain))
671                         mbname->domain = xstrdup(p+1);
672                 }
673                 else if (cdother) {
674                     mbname->domain = xstrdupnull(mbname_domain(userparts));
675                 }
676             }
677             goto done;
678         }
679 
680         /* everything else belongs to the userid */
681         mbname->localpart = xstrdupnull(mbname_localpart(userparts));
682         /* otherwise it was done above */
683         if (crossdomains) mbname->domain = xstrdupnull(mbname_domain(userparts));
684         /* special case INBOX case, because horrible */
685         if (!strcasecmpsafe(toplevel, "INBOX")) {
686             if (strarray_size(mbname->boxes) == 1) {
687                 free(strarray_shift(mbname->boxes));
688             }
689             else {
690                 /* force to upper case */
691                 char *p = (char *)toplevel;
692                 for (; *p; ++p)
693                     *p = toupper(*p);
694             }
695         }
696 
697         goto done;
698     }
699 
700     const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX);
701 
702     /* special inbox with insensitivity still, because horrible */
703     if (!strcasecmpsafe(strarray_nth(mbname->boxes, 0), "INBOX")) {
704         free(strarray_shift(mbname->boxes));
705         mbname->localpart = xstrdupnull(mbname_localpart(userparts));
706         /* otherwise it was done above */
707         if (crossdomains) mbname->domain = xstrdupnull(mbname_domain(userparts));
708         goto done;
709     }
710 
711     /* deleted prefix first */
712     if (ns->isadmin && !strcmpsafe(strarray_nth(mbname->boxes, 0), dp)) {
713         free(strarray_shift(mbname->boxes));
714         char *delval = strarray_pop(mbname->boxes);
715         if (!delval)
716             goto done;
717         mbname->is_deleted = strtoul(delval, NULL, 16);
718         free(delval);
719     }
720 
721     if (!strarray_size(mbname->boxes))
722         goto done;
723 
724     /* now look for user */
725     if (!strcmpsafe(strarray_nth(mbname->boxes, 0), "user")) {
726         free(strarray_shift(mbname->boxes));
727         mbname->localpart = strarray_shift(mbname->boxes);
728         if (crossdomains && mbname->localpart) {
729             char *p = strrchr(mbname->localpart, '@');
730             if (p) {
731                 *p = '\0';
732                 if (strcmpsafe(p+1, config_defdomain))
733                     mbname->domain = xstrdup(p+1);
734             }
735             else if (cdother) {
736                 mbname->domain = xstrdupnull(mbname_domain(userparts));
737             }
738         }
739         goto done;
740     }
741 
742     /* shared folders: are in user's domain unless admin */
743     if ((config_virtdomains && !ns->isadmin) || crossdomains) {
744         free(mbname->domain);
745         mbname->domain = xstrdupnull(mbname_domain(userparts));
746     }
747 
748  done:
749     mbname_free(&userparts);
750 
751     return mbname;
752 }
753 
mbname_free(mbname_t ** mbnamep)754 EXPORTED void mbname_free(mbname_t **mbnamep)
755 {
756     mbname_t *mbname = *mbnamep;
757     if (!mbname) return;
758 
759     *mbnamep = NULL;
760 
761     strarray_free(mbname->boxes);
762     free(mbname->localpart);
763     free(mbname->domain);
764 
765     /* cached values */
766     free(mbname->userid);
767     free(mbname->intname);
768     free(mbname->extname);
769     free(mbname->extuserid);
770     free(mbname->recipient);
771 
772     /* thing itself */
773     free(mbname);
774 }
775 
mboxname_to_userid(const char * intname)776 EXPORTED char *mboxname_to_userid(const char *intname)
777 {
778     mbname_t *mbname = mbname_from_intname(intname);
779     char *res = xstrdupnull(mbname_userid(mbname));
780     mbname_free(&mbname);
781     return res;
782 }
783 
mboxname_from_external(const char * extname,const struct namespace * ns,const char * userid)784 EXPORTED char *mboxname_from_external(const char *extname, const struct namespace *ns, const char *userid)
785 {
786     mbname_t *mbname = mbname_from_extname(extname, ns, userid);
787     char *res = xstrdupnull(mbname_intname(mbname));
788     mbname_free(&mbname);
789     return res;
790 }
791 
mboxname_to_external(const char * intname,const struct namespace * ns,const char * userid)792 EXPORTED char *mboxname_to_external(const char *intname, const struct namespace *ns, const char *userid)
793 {
794     mbname_t *mbname = mbname_from_intname(intname);
795     char *res = xstrdupnull(mbname_extname(mbname, ns, userid));
796     mbname_free(&mbname);
797     return res;
798 }
799 
800 /* all mailboxes have an internal name representation, so this
801  * function should never return a NULL.
802  */
mbname_intname(const mbname_t * mbname)803 EXPORTED const char *mbname_intname(const mbname_t *mbname)
804 {
805     if (mbname->intname)
806         return mbname->intname;
807 
808     struct buf buf = BUF_INITIALIZER;
809     const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX);
810     int sep = 0;
811     int i;
812 
813     strarray_t *boxes = strarray_dup(mbname_boxes(mbname));
814 
815     if (mbname->domain) {
816         buf_appendcstr(&buf, mbname->domain);
817         buf_putc(&buf, '!');
818     }
819 
820     if (mbname->is_deleted) {
821         buf_appendcstr(&buf, dp);
822         sep = 1;
823     }
824 
825     if (mbname->localpart) {
826         if (sep) buf_putc(&buf, '.');
827         buf_appendcstr(&buf, "user.");
828         _append_intbuf(&buf, mbname->localpart);
829         sep = 1;
830     }
831 
832     for (i = 0; i < strarray_size(boxes); i++) {
833         if (sep) buf_putc(&buf, '.');
834         _append_intbuf(&buf, strarray_nth(boxes, i));
835         sep = 1;
836     }
837 
838     if (mbname->is_deleted) {
839         if (sep) buf_putc(&buf, '.');
840         buf_printf(&buf, "%X", (unsigned)mbname->is_deleted);
841         sep = 1;
842     }
843 
844     mbname_t *backdoor = (mbname_t *)mbname;
845     backdoor->intname = buf_release(&buf);
846 
847     buf_free(&buf);
848     strarray_free(boxes);
849 
850     return mbname->intname;
851 }
852 
853 /* A userid may or may not have a domain - it's just localpart if the
854  * domain is unspecified or config_defdomain.  It totally ignores any parts.
855  * It's always NULL if there's no localpart
856  */
mbname_userid(const mbname_t * mbname)857 EXPORTED const char *mbname_userid(const mbname_t *mbname)
858 {
859     if (!mbname->localpart)
860         return NULL;
861 
862     if (mbname->userid)
863         return mbname->userid;
864 
865     struct buf buf = BUF_INITIALIZER;
866 
867     buf_appendcstr(&buf, mbname->localpart);
868 
869     if (mbname->domain) {
870         buf_putc(&buf, '@');
871         buf_appendcstr(&buf, mbname->domain);
872     }
873 
874     mbname_t *backdoor = (mbname_t *)mbname;
875     backdoor->userid = buf_release(&buf);
876 
877     buf_free(&buf);
878 
879     return mbname->userid;
880 }
881 
882 /* A "recipient" is a full username in external form (including domain) with an optional
883  * +addressed mailbox in external form, no INBOX prefix (since they can only be mailboxes
884  * owned by the user.
885  *
886  * shared folders (no user) are prefixed with a +, i.e. +shared@domain.com
887  *
888  * DELETED folders have no recipient, ever.
889  */
mbname_recipient(const mbname_t * mbname,const struct namespace * ns)890 EXPORTED const char *mbname_recipient(const mbname_t *mbname, const struct namespace *ns)
891 {
892     if (mbname->is_deleted) return NULL;
893 
894     /* gotta match up! */
895     if (mbname->recipient && ns == mbname->extns)
896         return mbname->recipient;
897 
898     struct buf buf = BUF_INITIALIZER;
899 
900     if (mbname->localpart) {
901         /* user mailbox */
902         buf_appendcstr(&buf, mbname->localpart);
903     }
904     else {
905         /* shared mailbox */
906         buf_appendcstr(&buf, config_getstring(IMAPOPT_POSTUSER));
907     }
908 
909     int i;
910     for (i = 0; i < strarray_size(mbname->boxes); i++) {
911         buf_putc(&buf, i ? ns->hier_sep : '+');
912         buf_appendcstr(&buf, strarray_nth(mbname->boxes, i));
913     }
914 
915     buf_putc(&buf, '@');
916     buf_appendcstr(&buf, mbname->domain ? mbname->domain : config_defdomain);
917 
918     mbname_t *backdoor = (mbname_t *)mbname;
919     free(backdoor->recipient);
920     backdoor->recipient = buf_release(&buf);
921     backdoor->extns = ns;
922 
923     buf_free(&buf);
924 
925     return mbname->recipient;
926 }
927 
928 /* This is one of the most complex parts of the code - generating an external
929  * name based on the namespace, the 'isadmin' status, and of course the current
930  * user.  There are some interesting things to look out for:
931  *
932  * Due to ambiguity, some names won't be representable in the external namespace,
933  * so this function can return a NULL in those cases.
934  */
mbname_category(const mbname_t * mbname,const struct namespace * ns,const char * userid)935 EXPORTED int mbname_category(const mbname_t *mbname, const struct namespace *ns, const char *userid)
936 {
937     if (!mbname_localpart(mbname)) return MBNAME_SHARED;
938     if (mbname_isdeleted(mbname)) {
939         if (strcmpsafe(mbname_userid(mbname), userid)) return MBNAME_OTHERDELETED;
940         return MBNAME_OWNERDELETED;
941     }
942 
943     if (strcmpsafe(mbname_userid(mbname), userid)) return MBNAME_OTHERUSER;
944 
945     const strarray_t *boxes = mbname_boxes(mbname);
946 
947     if (!strarray_size(boxes)) return MBNAME_INBOX;
948 
949     if (ns->isalt) {
950         const char *toplevel = strarray_nth(boxes, 0);
951 
952         /* exact "INBOX" */
953         if (!strcmpsafe(toplevel, "INBOX")) {
954             if (strarray_size(boxes) == 1) return MBNAME_ALTINBOX;
955             return MBNAME_INBOXSUB;
956         }
957 
958         /* other "INBOX" spellings */
959         if (!strcasecmpsafe(toplevel, "INBOX")) return MBNAME_ALTPREFIX;
960 
961         /* other prefixes that are special */
962         if (!strcmpsafe(toplevel, config_getstring(IMAPOPT_USERPREFIX))) return MBNAME_ALTPREFIX;
963         if (!strcmpsafe(toplevel, config_getstring(IMAPOPT_SHAREDPREFIX))) return MBNAME_ALTPREFIX;
964         if (!strcmpsafe(toplevel, config_getstring(IMAPOPT_ALTPREFIX))) return MBNAME_ALTPREFIX;
965     }
966 
967     /* everything else is owner */
968 
969     return MBNAME_OWNER;
970 }
971 
mbname_category_prefix(int category,const struct namespace * ns)972 EXPORTED const char *mbname_category_prefix(int category, const struct namespace *ns)
973 {
974     if (ns->isalt) {
975         switch (category) {
976             case MBNAME_ALTINBOX:
977                 return config_getstring(IMAPOPT_ALTPREFIX);
978             case MBNAME_OTHERUSER:
979                 return config_getstring(IMAPOPT_USERPREFIX);
980             case MBNAME_SHARED:
981                 return config_getstring(IMAPOPT_SHAREDPREFIX);
982             default:
983                 return NULL;
984         }
985     }
986     else {
987         if (category == MBNAME_OTHERUSER) return "user";
988     }
989 
990     return NULL;
991 }
992 
mbname_extname(const mbname_t * mbname,const struct namespace * ns,const char * userid)993 EXPORTED const char *mbname_extname(const mbname_t *mbname, const struct namespace *ns, const char *userid)
994 {
995     int crossdomains = config_getswitch(IMAPOPT_CROSSDOMAINS) && !ns->isadmin;
996     int cdother = config_getswitch(IMAPOPT_CROSSDOMAINS_ONLYOTHER);
997     /* old-school virtdomains requires admin to be a different domain than the userid */
998     int admindomains = config_virtdomains && ns->isadmin;
999 
1000     /* gotta match up! */
1001     if (mbname->extname && ns == mbname->extns && !strcmpsafe(userid, mbname->extuserid))
1002         return mbname->extname;
1003 
1004     struct buf buf = BUF_INITIALIZER;
1005 
1006     /* have to zero out any existing value just in case we drop through */
1007     mbname_t *backdoor = (mbname_t *)mbname;
1008     if (backdoor->extname) {
1009         free(backdoor->extname);
1010         backdoor->extname = NULL;
1011         backdoor->extns = ns;
1012         free(backdoor->extuserid);
1013         backdoor->extuserid = xstrdupnull(userid);
1014     }
1015 
1016     mbname_t *userparts = mbname_from_userid(userid);
1017     strarray_t *boxes = strarray_dup(mbname_boxes(mbname));
1018 
1019     if (ns->isalt) {
1020         assert(!ns->isadmin);
1021 
1022         const char *up = config_getstring(IMAPOPT_USERPREFIX);
1023         const char *sp = config_getstring(IMAPOPT_SHAREDPREFIX);
1024         const char *ap = config_getstring(IMAPOPT_ALTPREFIX);
1025 
1026         /* DELETED mailboxes have no extname in alt namespace.
1027          * There's also no need to display domains unless in crossdomains,
1028          * because admins are never in altnamespace, and only admins can
1029          * see domains in the admindomains space */
1030         if (mbname->is_deleted)
1031             goto done;
1032 
1033         /* shared */
1034         if (!mbname_localpart(mbname)) {
1035             /* can't represent an empty mailbox */
1036             if (!strarray_size(boxes))
1037                 goto done;
1038 
1039             const char *toplevel = strarray_nth(boxes, 0);
1040 
1041             if (strarray_size(boxes) == 1 && !strcmpsafe(toplevel, "user")) {
1042                 /* special case user all by itself */
1043                 buf_appendcstr(&buf, up);
1044                 goto end;
1045             }
1046             buf_appendcstr(&buf, sp);
1047             buf_putc(&buf, ns->hier_sep);
1048             _append_extbuf(ns, &buf, toplevel);
1049             /* domains go on the top level folder */
1050             if (crossdomains) {
1051                 const char *domain = mbname_domain(mbname);
1052                 if (!cdother || strcmpsafe(domain, mbname_domain(userparts))) {
1053                     if (!domain) domain = config_defdomain;
1054                     buf_putc(&buf, '@');
1055                     _append_extbuf(ns, &buf, domain);
1056                 }
1057             }
1058             int i;
1059             for (i = 1; i < strarray_size(boxes); i++) {
1060                 buf_putc(&buf, ns->hier_sep);
1061                 _append_extbuf(ns, &buf, strarray_nth(boxes, i));
1062             }
1063             goto end;
1064         }
1065 
1066         /* other users */
1067         if (strcmpsafe(mbname_userid(mbname), userid)) {
1068             buf_appendcstr(&buf, up);
1069             buf_putc(&buf, ns->hier_sep);
1070             _append_extbuf(ns, &buf, mbname_localpart(mbname));
1071             if (crossdomains) {
1072                 const char *domain = mbname_domain(mbname);
1073                 if (!cdother || strcmpsafe(domain, mbname_domain(userparts))) {
1074                     if (!domain) domain = config_defdomain;
1075                     buf_putc(&buf, '@');
1076                     _append_extbuf(ns, &buf, domain);
1077                 }
1078             }
1079             int i;
1080             for (i = 0; i < strarray_size(boxes); i++) {
1081                 buf_putc(&buf, ns->hier_sep);
1082                 _append_extbuf(ns, &buf, strarray_nth(boxes, i));
1083             }
1084             goto end;
1085         }
1086 
1087         /* own user */
1088         if (!strarray_size(boxes)) {
1089             buf_appendcstr(&buf, "INBOX");
1090             goto end;
1091         }
1092 
1093         const char *toplevel = strarray_nth(boxes, 0);
1094         /* INBOX is very special, because it can only be represented with exact case,
1095          * and it skips a level. Everything else including allcaps INBOX goes into
1096          * the Alt Prefix */
1097         if (!strcasecmpsafe(toplevel, "INBOX")) {
1098             if (strarray_size(boxes) == 1 || strcmpsafe(toplevel, "INBOX")) {
1099                 buf_appendcstr(&buf, ap);
1100                 buf_putc(&buf, ns->hier_sep);
1101             }
1102         }
1103         /* likewise anything exactly matching the user, alt or shared prefixes, both top level
1104          * or with children goes into alt prefix */
1105         else if (!strcmpsafe(toplevel, up) || !strcmpsafe(toplevel, sp) || !strcmpsafe(toplevel, ap)) {
1106             buf_appendcstr(&buf, ap);
1107             buf_putc(&buf, ns->hier_sep);
1108         }
1109 
1110          _append_extbuf(ns, &buf, toplevel);
1111 
1112         int i;
1113         for (i = 1; i < strarray_size(boxes); i++) {
1114            buf_putc(&buf, ns->hier_sep);
1115             _append_extbuf(ns, &buf, strarray_nth(boxes, i));
1116         }
1117 
1118         goto end;
1119     }
1120 
1121     if (mbname->is_deleted) {
1122         buf_appendcstr(&buf, config_getstring(IMAPOPT_DELETEDPREFIX));
1123         buf_putc(&buf, ns->hier_sep);
1124     }
1125 
1126     /* shared */
1127     if (!mbname_localpart(mbname)) {
1128         /* invalid names - not sure it's even possible, but hey */
1129         if (!strarray_size(boxes))
1130             goto done;
1131         if (!strcasecmpsafe(strarray_nth(boxes, 0), "INBOX"))
1132             goto done;
1133 
1134         /* shared folders can ONLY be in the same domain except for admin */
1135         if (!admindomains && strcmpsafe(mbname_domain(mbname), mbname_domain(userparts)))
1136             goto done;
1137 
1138         /* note "user" precisely appears here, but no need to special case it
1139          * since the output is the same */
1140         int i;
1141         for (i = 0; i < strarray_size(boxes); i++) {
1142             if (i) buf_putc(&buf, ns->hier_sep);
1143             _append_extbuf(ns, &buf, strarray_nth(boxes, i));
1144         }
1145 
1146         goto end;
1147     }
1148 
1149     /* other users or DELETED */
1150     if (mbname->is_deleted || strcmpsafe(mbname_userid(mbname), userid)) {
1151         buf_appendcstr(&buf, "user");
1152         buf_putc(&buf, ns->hier_sep);
1153         _append_extbuf(ns, &buf, mbname_localpart(mbname));
1154         if (crossdomains) {
1155             const char *domain = mbname_domain(mbname);
1156             if (!cdother || strcmpsafe(domain, mbname_domain(userparts))) {
1157                 if (!domain) domain = config_defdomain;
1158                 buf_putc(&buf, '@');
1159                 _append_extbuf(ns, &buf, domain);
1160             }
1161         }
1162         /* shared folders can ONLY be in the same domain except for admin */
1163         else if (!admindomains && strcmpsafe(mbname_domain(mbname), mbname_domain(userparts)))
1164             goto done;
1165         int i;
1166         for (i = 0; i < strarray_size(boxes); i++) {
1167             buf_putc(&buf, ns->hier_sep);
1168             _append_extbuf(ns, &buf, strarray_nth(boxes, i));
1169         }
1170         goto end;
1171     }
1172 
1173     buf_appendcstr(&buf, "INBOX");
1174     int i;
1175     for (i = 0; i < strarray_size(boxes); i++) {
1176        buf_putc(&buf, ns->hier_sep);
1177        _append_extbuf(ns, &buf, strarray_nth(boxes, i));
1178     }
1179 
1180  end:
1181 
1182     /* note: kinda bogus in altnamespace, meh */
1183     if (mbname->is_deleted) {
1184         buf_putc(&buf, ns->hier_sep);
1185         buf_printf(&buf, "%X", (unsigned)mbname->is_deleted);
1186     }
1187 
1188     if (admindomains && mbname_domain(mbname)) {
1189         buf_putc(&buf, '@');
1190         buf_appendcstr(&buf, mbname_domain(mbname));
1191     }
1192 
1193     backdoor->extname = buf_release(&buf);
1194 
1195  done:
1196 
1197     buf_free(&buf);
1198     mbname_free(&userparts);
1199     strarray_free(boxes);
1200 
1201     return mbname->extname;
1202 }
1203 
mbname_domain(const mbname_t * mbname)1204 EXPORTED const char *mbname_domain(const mbname_t *mbname)
1205 {
1206     return mbname->domain;
1207 }
1208 
mbname_localpart(const mbname_t * mbname)1209 EXPORTED const char *mbname_localpart(const mbname_t *mbname)
1210 {
1211     return mbname->localpart;
1212 }
1213 
mbname_isdeleted(const mbname_t * mbname)1214 EXPORTED time_t mbname_isdeleted(const mbname_t *mbname)
1215 {
1216     return mbname->is_deleted;
1217 }
1218 
mbname_boxes(const mbname_t * mbname)1219 EXPORTED const strarray_t *mbname_boxes(const mbname_t *mbname)
1220 {
1221     if (!mbname->boxes) {
1222         mbname_t *backdoor = (mbname_t *)mbname;
1223         backdoor->boxes = strarray_new();
1224     }
1225     return mbname->boxes;
1226 }
1227 
1228 /*
1229  * Create namespace based on config options.
1230  */
mboxname_init_namespace(struct namespace * namespace,int isadmin)1231 EXPORTED int mboxname_init_namespace(struct namespace *namespace, int isadmin)
1232 {
1233     const char *prefix;
1234 
1235     assert(namespace != NULL);
1236 
1237     namespace->isadmin = isadmin;
1238 
1239     namespace->hier_sep =
1240         config_getswitch(IMAPOPT_UNIXHIERARCHYSEP) ? '/' : '.';
1241     namespace->isalt = !isadmin && config_getswitch(IMAPOPT_ALTNAMESPACE);
1242 
1243     namespace->accessible[NAMESPACE_INBOX] = 1;
1244     namespace->accessible[NAMESPACE_USER] = !config_getswitch(IMAPOPT_DISABLE_USER_NAMESPACE);
1245     namespace->accessible[NAMESPACE_SHARED] = !config_getswitch(IMAPOPT_DISABLE_SHARED_NAMESPACE);
1246 
1247     if (namespace->isalt) {
1248         /* alternate namespace */
1249         strcpy(namespace->prefix[NAMESPACE_INBOX], "");
1250 
1251         prefix = config_getstring(IMAPOPT_USERPREFIX);
1252         if (!prefix || strlen(prefix) == 0 ||
1253             strlen(prefix) >= MAX_NAMESPACE_PREFIX ||
1254             strchr(prefix,namespace->hier_sep) != NULL)
1255             return IMAP_NAMESPACE_BADPREFIX;
1256         sprintf(namespace->prefix[NAMESPACE_USER], "%.*s%c",
1257                 MAX_NAMESPACE_PREFIX-1, prefix, namespace->hier_sep);
1258 
1259         prefix = config_getstring(IMAPOPT_SHAREDPREFIX);
1260         if (!prefix || strlen(prefix) == 0 ||
1261             strlen(prefix) >= MAX_NAMESPACE_PREFIX ||
1262             strchr(prefix, namespace->hier_sep) != NULL ||
1263             !strncmp(namespace->prefix[NAMESPACE_USER], prefix, strlen(prefix)))
1264             return IMAP_NAMESPACE_BADPREFIX;
1265 
1266         if (!isadmin) {
1267             sprintf(namespace->prefix[NAMESPACE_SHARED], "%.*s%c",
1268                 MAX_NAMESPACE_PREFIX-1, prefix, namespace->hier_sep);
1269         }
1270     }
1271 
1272     else {
1273         /* standard namespace */
1274         sprintf(namespace->prefix[NAMESPACE_INBOX], "%s%c",
1275                 "INBOX", namespace->hier_sep);
1276         sprintf(namespace->prefix[NAMESPACE_USER], "%s%c",
1277                 "user", namespace->hier_sep);
1278         strcpy(namespace->prefix[NAMESPACE_SHARED], "");
1279     }
1280 
1281     return 0;
1282 }
1283 
mboxname_get_adminnamespace()1284 EXPORTED struct namespace *mboxname_get_adminnamespace()
1285 {
1286     static struct namespace ns;
1287     if (!admin_namespace) {
1288         mboxname_init_namespace(&ns, /*isadmin*/1);
1289         admin_namespace = &ns;
1290     }
1291     return admin_namespace;
1292 }
1293 
1294 /*
1295  * Return nonzero if 'userid' owns the (internal) mailbox 'name'.
1296  */
mboxname_userownsmailbox(const char * userid,const char * name)1297 EXPORTED int mboxname_userownsmailbox(const char *userid, const char *name)
1298 {
1299     mbname_t *mbname = mbname_from_intname(name);
1300     int res = !strcmpsafe(mbname_userid(mbname), userid);
1301     mbname_free(&mbname);
1302 
1303     return res;
1304 }
1305 
1306 /*
1307  * If (internal) mailbox 'name' is a user's mailbox (optionally INBOX),
1308  * returns 1, otherwise returns 0.
1309  */
mboxname_isusermailbox(const char * name,int isinbox)1310 EXPORTED int mboxname_isusermailbox(const char *name, int isinbox)
1311 {
1312     mbname_t *mbname = mbname_from_intname(name);
1313     int res = 0;
1314 
1315     if (mbname_localpart(mbname) && !mbname_isdeleted(mbname)) {
1316         if (!isinbox || !strarray_size(mbname_boxes(mbname)))
1317             res = 1;
1318     }
1319 
1320     mbname_free(&mbname);
1321     return res;
1322 }
1323 
1324 /*
1325  * If (internal) mailbox 'name' is a user's Trash folder returns 1,
1326  * otherwise returns 0
1327  * XXX: use roles rather than hard coded name?
1328  */
mboxname_isusertrash(const char * name)1329 EXPORTED int mboxname_isusertrash(const char *name)
1330 {
1331     mbname_t *mbname = mbname_from_intname(name);
1332     int res = 0;
1333 
1334     if (mbname_localpart(mbname) && !mbname_isdeleted(mbname)) {
1335         const strarray_t *boxes = mbname_boxes(mbname);
1336         if (strarray_size(boxes) == 1 && !strcmpsafe(strarray_nth(boxes, 0), "Trash"))
1337             res = 1;
1338     }
1339 
1340     mbname_free(&mbname);
1341     return res;
1342 }
1343 
1344 /*
1345  * If (internal) mailbox 'name' is a DELETED mailbox
1346  * returns boolean
1347  */
mboxname_isdeletedmailbox(const char * name,time_t * timestampp)1348 EXPORTED int mboxname_isdeletedmailbox(const char *name, time_t *timestampp)
1349 {
1350     mbname_t *mbname = mbname_from_intname(name);
1351     time_t res = mbname_isdeleted(mbname);
1352     mbname_free(&mbname);
1353 
1354     if (timestampp)
1355         *timestampp = res;
1356 
1357     return res ? 1 : 0;
1358 }
1359 
1360 /*
1361  * If (internal) mailbox 'name' is a CALENDAR mailbox
1362  * returns boolean
1363  */
mboxname_iscalendarmailbox(const char * name,int mbtype)1364 EXPORTED int mboxname_iscalendarmailbox(const char *name, int mbtype)
1365 {
1366     if (mbtype & MBTYPE_CALENDAR) return 1;  /* Only works on backends */
1367     int res = 0;
1368 
1369     mbname_t *mbname = mbname_from_intname(name);
1370     const strarray_t *boxes = mbname_boxes(mbname);
1371     const char *prefix = config_getstring(IMAPOPT_CALENDARPREFIX);
1372     if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
1373         res = 1;
1374 
1375     mbname_free(&mbname);
1376     return res;
1377 }
1378 
1379 /*
1380  * If (internal) mailbox 'name' is a ADDRESSBOOK mailbox
1381  * returns boolean
1382  */
mboxname_isaddressbookmailbox(const char * name,int mbtype)1383 EXPORTED int mboxname_isaddressbookmailbox(const char *name, int mbtype)
1384 {
1385     if (mbtype & MBTYPE_ADDRESSBOOK) return 1;  /* Only works on backends */
1386     int res = 0;
1387 
1388     mbname_t *mbname = mbname_from_intname(name);
1389     const strarray_t *boxes = mbname_boxes(mbname);
1390     const char *prefix = config_getstring(IMAPOPT_ADDRESSBOOKPREFIX);
1391     if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
1392         res = 1;
1393 
1394     mbname_free(&mbname);
1395     return res;
1396 }
1397 
1398 /*
1399  * If (internal) mailbox 'name' is a DAVDRIVE mailbox
1400  * returns boolean
1401  */
mboxname_isdavdrivemailbox(const char * name,int mbtype)1402 EXPORTED int mboxname_isdavdrivemailbox(const char *name, int mbtype)
1403 {
1404     if (mbtype & MBTYPE_COLLECTION) return 1;  /* Only works on backends */
1405     int res = 0;
1406 
1407     mbname_t *mbname = mbname_from_intname(name);
1408     const strarray_t *boxes = mbname_boxes(mbname);
1409     const char *prefix = config_getstring(IMAPOPT_DAVDRIVEPREFIX);
1410     if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
1411         res = 1;
1412 
1413     mbname_free(&mbname);
1414     return res;
1415 }
1416 
1417 /*
1418  * If (internal) mailbox 'name' is a DAVNOTIFICATIONS mailbox
1419  * returns boolean
1420  */
mboxname_isdavnotificationsmailbox(const char * name,int mbtype)1421 EXPORTED int mboxname_isdavnotificationsmailbox(const char *name, int mbtype)
1422 {
1423     if (mbtype & MBTYPE_COLLECTION) return 1;  /* Only works on backends */
1424     int res = 0;
1425 
1426     mbname_t *mbname = mbname_from_intname(name);
1427     const strarray_t *boxes = mbname_boxes(mbname);
1428     const char *prefix = config_getstring(IMAPOPT_DAVNOTIFICATIONSPREFIX);
1429     if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
1430         res = 1;
1431 
1432     mbname_free(&mbname);
1433     return res;
1434 }
1435 
1436 /*
1437  * If (internal) mailbox 'name' is a user's "Notes" mailbox
1438  * returns boolean
1439  */
mboxname_isnotesmailbox(const char * name,int mbtype)1440 EXPORTED int mboxname_isnotesmailbox(const char *name, int mbtype __attribute__((unused)))
1441 {
1442     int res = 0;
1443 
1444     mbname_t *mbname = mbname_from_intname(name);
1445     const strarray_t *boxes = mbname_boxes(mbname);
1446     const char *prefix = config_getstring(IMAPOPT_NOTESMAILBOX);
1447     if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
1448         res = 1;
1449 
1450     mbname_free(&mbname);
1451     return res;
1452 }
1453 
1454 /*
1455  * If (internal) mailbox 'name' is a user's #jmapsubmission mailbox
1456  * returns boolean
1457  */
mboxname_issubmissionmailbox(const char * name,int mbtype)1458 EXPORTED int mboxname_issubmissionmailbox(const char *name, int mbtype)
1459 {
1460     if (mbtype & MBTYPE_SUBMISSION) return 1;  /* Only works on backends */
1461     int res = 0;
1462 
1463     mbname_t *mbname = mbname_from_intname(name);
1464     const strarray_t *boxes = mbname_boxes(mbname);
1465     const char *prefix = config_getstring(IMAPOPT_JMAPSUBMISSIONFOLDER);
1466     if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
1467         res = 1;
1468 
1469     mbname_free(&mbname);
1470     return res;
1471 }
1472 
1473 /*
1474  * If (internal) mailbox 'name' is a user's #jmappushsubscription mailbox
1475  * returns boolean
1476  */
mboxname_ispushsubscriptionmailbox(const char * name,int mbtype)1477 EXPORTED int mboxname_ispushsubscriptionmailbox(const char *name, int mbtype)
1478 {
1479     if (mbtype & MBTYPE_PUSHSUBSCRIPTION) return 1;  /* Only works on backends */
1480     int res = 0;
1481 
1482     mbname_t *mbname = mbname_from_intname(name);
1483     const strarray_t *boxes = mbname_boxes(mbname);
1484     const char *prefix = config_getstring(IMAPOPT_JMAPPUSHSUBSCRIPTIONFOLDER);
1485     if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
1486         res = 1;
1487 
1488     mbname_free(&mbname);
1489     return res;
1490 }
1491 
1492 /*
1493  * If (internal) mailbox 'name' is a user's #jmap upload mailbox
1494  * returns boolean
1495  */
mboxname_isjmapuploadmailbox(const char * name,int mbtype)1496 EXPORTED int mboxname_isjmapuploadmailbox(const char *name, int mbtype __attribute__((unused)))
1497 {
1498     int res = 0;
1499 
1500     mbname_t *mbname = mbname_from_intname(name);
1501     const strarray_t *boxes = mbname_boxes(mbname);
1502     const char *prefix = config_getstring(IMAPOPT_JMAPUPLOADFOLDER);
1503 
1504     if (strarray_size(boxes) && !strcmpsafe(prefix, strarray_nth(boxes, 0)))
1505         res = 1;
1506 
1507     mbname_free(&mbname);
1508     return res;
1509 }
1510 
mboxname_user_mbox(const char * userid,const char * subfolder)1511 EXPORTED char *mboxname_user_mbox(const char *userid, const char *subfolder)
1512 {
1513     if (!userid) return NULL;
1514 
1515     mbname_t *mbname = mbname_from_userid(userid);
1516 
1517     if (subfolder) {
1518         strarray_t *bits = strarray_split(subfolder, ".", 0);
1519         mbname_set_boxes(mbname, bits);
1520         strarray_free(bits);
1521     }
1522 
1523     char *res = xstrdup(mbname_intname(mbname));
1524     mbname_free(&mbname);
1525 
1526     return res;
1527 }
1528 
mboxname_abook(const char * userid,const char * collection)1529 EXPORTED char *mboxname_abook(const char *userid, const char *collection)
1530 {
1531     mbname_t *mbname = mbname_from_userid(userid);
1532 
1533     mbname_push_boxes(mbname, config_getstring(IMAPOPT_ADDRESSBOOKPREFIX));
1534     if (collection) mbname_push_boxes(mbname, collection);
1535 
1536     char *res = xstrdup(mbname_intname(mbname));
1537     mbname_free(&mbname);
1538 
1539     return res;
1540 }
1541 
mboxname_cal(const char * userid,const char * collection)1542 EXPORTED char *mboxname_cal(const char *userid, const char *collection)
1543 {
1544     mbname_t *mbname = mbname_from_userid(userid);
1545 
1546     mbname_push_boxes(mbname, config_getstring(IMAPOPT_CALENDARPREFIX));
1547     if (collection) mbname_push_boxes(mbname, collection);
1548 
1549     char *res = xstrdup(mbname_intname(mbname));
1550     mbname_free(&mbname);
1551 
1552     return res;
1553 }
1554 
1555 /*
1556  * Check whether two parts have the same userid.
1557  * Returns: 1 if the userids are the same, 0 if not.
1558  */
mbname_same_userid(const mbname_t * a,const mbname_t * b)1559 EXPORTED int mbname_same_userid(const mbname_t *a, const mbname_t *b)
1560 {
1561     int r;
1562 
1563     r = strcmpsafe(a->domain, b->domain);
1564     if (!r)
1565         r = strcmpsafe(a->localpart, b->localpart);
1566     return !r;
1567 }
1568 
1569 /*
1570  * Check whether two mboxnames have the same userid.
1571  * Needed for some corner cases in the COPY command.
1572  * Returns: 1 if the userids are the same, 0 if not,
1573  *          or negative error.
1574  */
mboxname_same_userid(const char * name1,const char * name2)1575 EXPORTED int mboxname_same_userid(const char *name1, const char *name2)
1576 {
1577     int r;
1578     mbname_t *p1 = mbname_from_intname(name1);
1579     mbname_t *p2 = mbname_from_intname(name2);
1580 
1581     r = mbname_same_userid(p1, p2);
1582 
1583     mbname_free(&p1);
1584     mbname_free(&p2);
1585 
1586     return r;
1587 }
1588 
1589 /*
1590  * Apply site policy restrictions on mailbox names.
1591  * Restrictions are hardwired for now.
1592  * NOTE: '^' is '.' externally in unixhs, and invalid in unixhs
1593  *
1594  * The set of printable chars that are not in GOODCHARS are:
1595  *    !"%&/;<>\`{|}
1596  */
1597 #define GOODCHARS " #$'()*+,-.0123456789:=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz~"
mboxname_policycheck(const char * name)1598 HIDDEN int mboxname_policycheck(const char *name)
1599 {
1600     const char *p;
1601     int sawutf7 = 0;
1602     unsigned c1, c2, c3, c4, c5, c6, c7, c8;
1603     int ucs4;
1604     int namelen = strlen(name);
1605     int hasdom = 0;
1606 
1607     /* We reserve mailboxes.db keys beginning with $ for internal use
1608      * (e.g. $RACL), so don't allow a real mailbox to sneak in there.
1609      *
1610      * N.B This is only forbidden at the absolute top of the internal
1611      * namespace: stuff like "user.foo.$bar", "domain!user.foo.$bar",
1612      * "domain!$bar", and even "user.$bar" are all still valid here,
1613      * because none of those names start with $, and won't conflict.
1614      */
1615     if (name[0] == '$')
1616         return IMAP_MAILBOX_BADNAME;
1617 
1618     /* Skip policy check on mailbox created in delayed delete namespace
1619      * assuming the mailbox existed before and was OK then.
1620      * This should allow mailboxes that are extremely long to be
1621      * deleted when delayed_delete is enabled.
1622      * A thorough fix might remove the prefix and timestamp
1623      * then continue with the check
1624      */
1625     if (mboxname_isdeletedmailbox(name, NULL))
1626         return 0;
1627 
1628     if (namelen > MAX_MAILBOX_NAME)
1629         return IMAP_MAILBOX_BADNAME;
1630 
1631     /* find the virtual domain, if any.  We don't sanity check domain
1632        names yet - maybe we should */
1633     p = strchr(name, '!');
1634     if (p) {
1635         if (config_virtdomains) {
1636             name = p + 1;
1637             namelen = strlen(name);
1638             hasdom = 1;
1639         }
1640         else
1641             return IMAP_MAILBOX_BADNAME;
1642     }
1643 
1644     /* bad mbox patterns */
1645     // empty name
1646     if (!name[0]) return IMAP_MAILBOX_BADNAME;
1647     // leading dot
1648     if (name[0] == '.') return IMAP_MAILBOX_BADNAME;
1649     // leading ~
1650     if (name[0] == '~') return IMAP_MAILBOX_BADNAME;
1651     // trailing dot
1652     if (name[namelen-1] == '.') return IMAP_MAILBOX_BADNAME;
1653     // double dot (zero length path item)
1654     if (strstr(name, "..")) return IMAP_MAILBOX_BADNAME;
1655     // non-" " whitespace
1656     if (strchr(name, '\r')) return IMAP_MAILBOX_BADNAME;
1657     if (strchr(name, '\n')) return IMAP_MAILBOX_BADNAME;
1658     if (strchr(name, '\t')) return IMAP_MAILBOX_BADNAME;
1659     // top level user
1660     if (!strcmp(name, "user")) return IMAP_MAILBOX_BADNAME;
1661     // special users
1662     if (!strcmp(name, "user.anyone")) return IMAP_MAILBOX_BADNAME;
1663     if (!strcmp(name, "user.anonymous")) return IMAP_MAILBOX_BADNAME;
1664     // redundant but explicit ban on userids starting with '%'
1665     // (would conflict with backups of shared mailboxes)
1666     if (!strncmp(name, "user.%", 6)) return IMAP_MAILBOX_BADNAME;
1667 
1668     while (*name) {
1669         if (*name == '&') {
1670             /* Modified UTF-7 */
1671             name++;
1672             while (*name != '-') {
1673                 if (sawutf7) {
1674                     /* Two adjacent utf7 sequences */
1675                     return IMAP_MAILBOX_BADNAME;
1676                 }
1677 
1678                 if ((c1 = CHARMOD64(*name++)) == XX ||
1679                     (c2 = CHARMOD64(*name++)) == XX ||
1680                     (c3 = CHARMOD64(*name++)) == XX) {
1681                     /* Non-base64 character */
1682                     return IMAP_MAILBOX_BADNAME;
1683                 }
1684                 ucs4 = (c1 << 10) | (c2 << 4) | (c3 >> 2);
1685                 if ((ucs4 & 0xff80) == 0) {
1686                     /* US-ASCII character */
1687                     return IMAP_MAILBOX_BADNAME;
1688                 }
1689                 if (*name == '-') {
1690                     /* Trailing bits not zero */
1691                     if (c3 & 0x03) return IMAP_MAILBOX_BADNAME;
1692 
1693                     /* End of UTF-7 sequence */
1694                     break;
1695                 }
1696 
1697                 if ((c4 = CHARMOD64(*name++)) == XX ||
1698                     (c5 = CHARMOD64(*name++)) == XX ||
1699                     (c6 = CHARMOD64(*name++)) == XX) {
1700                     /* Non-base64 character */
1701                     return IMAP_MAILBOX_BADNAME;
1702                 }
1703                 ucs4 = ((c3 & 0x03) << 14) | (c4 << 8) | (c5 << 2) | (c6 >> 4);
1704                 if ((ucs4 & 0xff80) == 0) {
1705                     /* US-ASCII character */
1706                     return IMAP_MAILBOX_BADNAME;
1707                 }
1708                 if (*name == '-') {
1709                     /* Trailing bits not zero */
1710                     if (c6 & 0x0f) return IMAP_MAILBOX_BADNAME;
1711 
1712                     /* End of UTF-7 sequence */
1713                     break;
1714                 }
1715 
1716                 if ((c7 = CHARMOD64(*name++)) == XX ||
1717                     (c8 = CHARMOD64(*name++)) == XX) {
1718                     /* Non-base64 character */
1719                     return IMAP_MAILBOX_BADNAME;
1720                 }
1721                 ucs4 = ((c6 & 0x0f) << 12) | (c7 << 6) | c8;
1722                 if ((ucs4 & 0xff80) == 0) {
1723                     /* US-ASCII character */
1724                     return IMAP_MAILBOX_BADNAME;
1725                 }
1726             }
1727 
1728             if (name[-1] == '&') sawutf7 = 0; /* '&-' is sequence for '&' */
1729             else sawutf7 = 1;
1730 
1731             name++;             /* Skip over terminating '-' */
1732         }
1733         else {
1734             if (!(strchr(GOODCHARS, *name) || (hasdom && *name == '!')))
1735                 return IMAP_MAILBOX_BADNAME;
1736             name++;
1737             sawutf7 = 0;
1738         }
1739     }
1740     return 0;
1741 }
1742 
mboxname_is_prefix(const char * longstr,const char * shortstr)1743 EXPORTED int mboxname_is_prefix(const char *longstr, const char *shortstr)
1744 {
1745     int longlen = strlen(longstr);
1746     int shortlen = strlen(shortstr);
1747 
1748     /* can't be a child */
1749     if (longlen < shortlen)
1750         return 0;
1751 
1752     /* don't match along same length */
1753     if (strncmp(longstr, shortstr, shortlen))
1754         return 0;
1755 
1756     /* longer, and not a separator */
1757     if (longlen > shortlen && longstr[shortlen] != '.')
1758         return 0;
1759 
1760     /* it's a match! */
1761     return 1;
1762 }
1763 
1764 
mboxname_hash(char * dest,size_t destlen,const char * root,const char * name)1765 EXPORTED void mboxname_hash(char *dest, size_t destlen,
1766                             const char *root,
1767                             const char *name)
1768 {
1769     mbname_t *mbname = mbname_from_intname(name);
1770     struct buf buf = BUF_INITIALIZER;
1771 
1772     buf_setcstr(&buf, root);
1773 
1774     const char *domain = mbname_domain(mbname);
1775     strarray_t *boxes = strarray_dup(mbname_boxes(mbname));
1776 
1777     if (domain) {
1778         if (config_hashimapspool) {
1779             char c = dir_hash_c(domain, config_fulldirhash);
1780             buf_printf(&buf, "%s%c/%s", FNAME_DOMAINDIR, c, domain);
1781         }
1782         else {
1783             buf_printf(&buf, "%s%s", FNAME_DOMAINDIR, domain);
1784         }
1785     }
1786 
1787     if (mbname_localpart(mbname)) {
1788         strarray_unshift(boxes, mbname_localpart(mbname));
1789         strarray_unshift(boxes, "user");
1790     }
1791     if (mbname_isdeleted(mbname)) {
1792         struct buf dbuf = BUF_INITIALIZER;
1793         buf_printf(&dbuf, "%X", (unsigned)mbname_isdeleted(mbname));
1794         strarray_unshift(boxes, config_getstring(IMAPOPT_DELETEDPREFIX));
1795         strarray_push(boxes, buf_cstring(&dbuf));
1796         buf_free(&dbuf);
1797     }
1798 
1799     if (config_hashimapspool && strarray_size(boxes)) {
1800         const char *idx = strarray_size(boxes) > 1 ? strarray_nth(boxes, 1) : strarray_nth(boxes, 0);
1801         char c = dir_hash_c(idx, config_fulldirhash);
1802         buf_printf(&buf, "/%c", c);
1803     }
1804 
1805     int i;
1806     for (i = 0; i < strarray_size(boxes); i++) {
1807         buf_putc(&buf, '/');
1808         _append_intbuf(&buf, strarray_nth(boxes, i));
1809     }
1810 
1811     /* for now, keep API even though we're doing a buffer inside here */
1812     strncpy(dest, buf_cstring(&buf), destlen);
1813 
1814     buf_free(&buf);
1815     strarray_free(boxes);
1816     mbname_free(&mbname);
1817 }
1818 
1819 /* note: mboxname must be internal */
mboxname_datapath(const char * partition,const char * mboxname,const char * uniqueid,unsigned long uid)1820 EXPORTED char *mboxname_datapath(const char *partition,
1821                                  const char *mboxname,
1822                                  const char *uniqueid __attribute__((unused)),
1823                                  unsigned long uid)
1824 {
1825     static char pathresult[MAX_MAILBOX_PATH+1];
1826     const char *root;
1827 
1828     if (!partition) return NULL;
1829 
1830     root = config_partitiondir(partition);
1831     if (!root) return NULL;
1832 
1833     if (!mboxname) {
1834         xstrncpy(pathresult, root, MAX_MAILBOX_PATH);
1835         return pathresult;
1836     }
1837 
1838     mboxname_hash(pathresult, MAX_MAILBOX_PATH, root, mboxname);
1839 
1840     if (uid) {
1841         int len = strlen(pathresult);
1842         snprintf(pathresult + len, MAX_MAILBOX_PATH - len, "/%lu.", uid);
1843     }
1844     pathresult[MAX_MAILBOX_PATH] = '\0';
1845 
1846     if (strlen(pathresult) == MAX_MAILBOX_PATH)
1847         return NULL;
1848 
1849     return pathresult;
1850 }
1851 
1852 /* note: mboxname must be internal */
mboxname_archivepath(const char * partition,const char * mboxname,const char * uniqueid,unsigned long uid)1853 EXPORTED char *mboxname_archivepath(const char *partition,
1854                                     const char *mboxname,
1855                                     const char *uniqueid __attribute__((unused)),
1856                                     unsigned long uid)
1857 {
1858     static char pathresult[MAX_MAILBOX_PATH+1];
1859     const char *root;
1860 
1861     if (!partition) return NULL;
1862 
1863     root = config_archivepartitiondir(partition);
1864     if (!root) root = config_partitiondir(partition);
1865     if (!root) return NULL;
1866 
1867     /* XXX - dedup with datapath above - but make sure to keep the results
1868      * in separate buffers and/or audit the callers */
1869     if (!mboxname) {
1870         xstrncpy(pathresult, root, MAX_MAILBOX_PATH);
1871         return pathresult;
1872     }
1873 
1874     mboxname_hash(pathresult, MAX_MAILBOX_PATH, root, mboxname);
1875 
1876     if (uid) {
1877         int len = strlen(pathresult);
1878         snprintf(pathresult + len, MAX_MAILBOX_PATH - len, "/%lu.", uid);
1879     }
1880     pathresult[MAX_MAILBOX_PATH] = '\0';
1881 
1882     if (strlen(pathresult) == MAX_MAILBOX_PATH)
1883         return NULL;
1884 
1885     return pathresult;
1886 }
1887 
mboxname_lockpath(const char * mboxname)1888 char *mboxname_lockpath(const char *mboxname)
1889 {
1890     return mboxname_lockpath_suffix(mboxname, ".lock");
1891 }
1892 
mboxname_lockpath_suffix(const char * mboxname,const char * suffix)1893 char *mboxname_lockpath_suffix(const char *mboxname,
1894                                const char *suffix)
1895 {
1896     static char lockresult[MAX_MAILBOX_PATH+1];
1897     char basepath[MAX_MAILBOX_PATH+1];
1898     const char *root = config_getstring(IMAPOPT_MBOXNAME_LOCKPATH);
1899     int len;
1900 
1901     if (!root) {
1902         snprintf(basepath, MAX_MAILBOX_PATH, "%s/lock", config_dir);
1903         root = basepath;
1904     }
1905 
1906     mboxname_hash(lockresult, MAX_MAILBOX_PATH, root, mboxname);
1907 
1908     len = strlen(lockresult);
1909     snprintf(lockresult + len, MAX_MAILBOX_PATH - len, "%s", suffix);
1910     lockresult[MAX_MAILBOX_PATH] = '\0';
1911 
1912     if (strlen(lockresult) == MAX_MAILBOX_PATH)
1913         return NULL;
1914 
1915     return lockresult;
1916 }
1917 
mboxname_metapath(const char * partition,const char * mboxname,const char * uniqueid,int metafile,int isnew)1918 EXPORTED char *mboxname_metapath(const char *partition,
1919                                  const char *mboxname,
1920                                  const char *uniqueid __attribute__((unused)),
1921                                  int metafile,
1922                                  int isnew)
1923 {
1924     static char metaresult[MAX_MAILBOX_PATH];
1925     int metaflag = 0;
1926     int archiveflag = 0;
1927     const char *root = NULL;
1928     const char *filename = NULL;
1929     char confkey[256];
1930 
1931     if (!partition) return NULL;
1932 
1933     *confkey = '\0';
1934 
1935     switch (metafile) {
1936     case META_HEADER:
1937         snprintf(confkey, 256, "metadir-header-%s", partition);
1938         metaflag = IMAP_ENUM_METAPARTITION_FILES_HEADER;
1939         filename = FNAME_HEADER;
1940         break;
1941     case META_INDEX:
1942         snprintf(confkey, 256, "metadir-index-%s", partition);
1943         metaflag = IMAP_ENUM_METAPARTITION_FILES_INDEX;
1944         filename = FNAME_INDEX;
1945         break;
1946     case META_CACHE:
1947         snprintf(confkey, 256, "metadir-cache-%s", partition);
1948         metaflag = IMAP_ENUM_METAPARTITION_FILES_CACHE;
1949         filename = FNAME_CACHE;
1950         break;
1951     case META_EXPUNGE:
1952         /* not movable, it's only old */
1953         metaflag = IMAP_ENUM_METAPARTITION_FILES_EXPUNGE;
1954         filename = FNAME_EXPUNGE;
1955         break;
1956     case META_SQUAT:
1957         snprintf(confkey, 256, "metadir-squat-%s", partition);
1958         metaflag = IMAP_ENUM_METAPARTITION_FILES_SQUAT;
1959         filename = FNAME_SQUAT;
1960         break;
1961     case META_ANNOTATIONS:
1962         snprintf(confkey, 256, "metadir-index-%s", partition);
1963         metaflag = IMAP_ENUM_METAPARTITION_FILES_ANNOTATIONS;
1964         filename = FNAME_ANNOTATIONS;
1965         break;
1966 #ifdef WITH_DAV
1967     case META_DAV:
1968         snprintf(confkey, 256, "metadir-dav-%s", partition);
1969         metaflag = IMAP_ENUM_METAPARTITION_FILES_DAV;
1970         filename = FNAME_DAV;
1971         break;
1972 #endif
1973     case META_ARCHIVECACHE:
1974         snprintf(confkey, 256, "metadir-archivecache-%s", partition);
1975         metaflag = IMAP_ENUM_METAPARTITION_FILES_ARCHIVECACHE;
1976         filename = FNAME_CACHE;
1977         archiveflag = 1;
1978         break;
1979     case 0:
1980         break;
1981     default:
1982         fatal("Unknown meta file requested", EX_SOFTWARE);
1983     }
1984 
1985     if (*confkey)
1986         root = config_getoverflowstring(confkey, NULL);
1987 
1988     if (!root && (!metaflag || (config_metapartition_files & metaflag)))
1989         root = config_metapartitiondir(partition);
1990 
1991     if (!root && archiveflag)
1992         root = config_archivepartitiondir(partition);
1993 
1994     if (!root)
1995         root = config_partitiondir(partition);
1996 
1997     if (!root)
1998         return NULL;
1999 
2000     if (!mboxname) {
2001         xstrncpy(metaresult, root, MAX_MAILBOX_PATH);
2002         return metaresult;
2003     }
2004 
2005     mboxname_hash(metaresult, MAX_MAILBOX_PATH, root, mboxname);
2006 
2007     if (filename) {
2008         int len = strlen(metaresult);
2009         if (isnew)
2010             snprintf(metaresult + len, MAX_MAILBOX_PATH - len, "%s.NEW", filename);
2011         else
2012             snprintf(metaresult + len, MAX_MAILBOX_PATH - len, "%s", filename);
2013     }
2014 
2015     if (strlen(metaresult) >= MAX_MAILBOX_PATH)
2016         return NULL;
2017 
2018     return metaresult;
2019 }
2020 
mboxname_todeleted(const char * name,char * result,int withtime)2021 EXPORTED void mboxname_todeleted(const char *name, char *result, int withtime)
2022 {
2023     int domainlen = 0;
2024     char *p;
2025     const char *deletedprefix = config_getstring(IMAPOPT_DELETEDPREFIX);
2026 
2027     xstrncpy(result, name, MAX_MAILBOX_BUFFER);
2028 
2029     if (config_virtdomains && (p = strchr(name, '!')))
2030         domainlen = p - name + 1;
2031 
2032     if (withtime) {
2033         struct timeval tv;
2034         gettimeofday( &tv, NULL );
2035         snprintf(result+domainlen, MAX_MAILBOX_BUFFER-domainlen, "%s.%s.%X",
2036                  deletedprefix, name+domainlen, (unsigned) tv.tv_sec);
2037     } else {
2038         snprintf(result+domainlen, MAX_MAILBOX_BUFFER-domainlen, "%s.%s",
2039                  deletedprefix, name+domainlen);
2040     }
2041 }
2042 
mboxname_make_parent(char * name)2043 EXPORTED int mboxname_make_parent(char *name)
2044 {
2045     int domainlen = 0;
2046     char *p;
2047 
2048     if (config_virtdomains && (p = strchr(name, '!')))
2049         domainlen = p - name + 1;
2050 
2051     if (!name[0] || !strcmp(name+domainlen, "user"))
2052         return 0;                               /* stop now */
2053 
2054     p = strrchr(name, '.');
2055 
2056     if (p && (p - name > domainlen))            /* don't split subdomain */
2057         *p = '\0';
2058     else if (!name[domainlen])                  /* server entry */
2059         name[0] = '\0';
2060     else                                        /* domain entry */
2061         name[domainlen] = '\0';
2062 
2063     return 1;
2064 }
2065 
mboxname_contains_parent(const char * mboxname,const char * prev)2066 EXPORTED int mboxname_contains_parent(const char *mboxname, const char *prev)
2067 {
2068     /* no names, definitely can't be parent! */
2069     if (!mboxname) return 0;
2070     if (!prev) return 0;
2071 
2072     char *parent = xstrdup(mboxname);
2073 
2074     /* this mailbox is just "user"? prev will always contain that */
2075     if (!mboxname_make_parent(parent)) {
2076         free(parent);
2077         return 1;
2078     }
2079 
2080     if (mboxname_is_prefix(prev, parent)) {
2081         /* it's not different?  Great - there's no missing intermediate */
2082         free(parent);
2083         return 1;
2084     }
2085 
2086     /* OK, it doesn't contain the parent for sure */
2087     free(parent);
2088     return 0;
2089 }
2090 
2091 /* NOTE: caller must free, which is different from almost every
2092  * other interface in the whole codebase.  Grr */
mboxname_conf_getpath(const mbname_t * mbname,const char * suffix)2093 EXPORTED char *mboxname_conf_getpath(const mbname_t *mbname, const char *suffix)
2094 {
2095     char *fname = NULL;
2096     char c[2], d[2];
2097 
2098     if (mbname->domain) {
2099         if (mbname->localpart) {
2100             if (suffix) {
2101                 fname = strconcat(config_dir,
2102                                   FNAME_DOMAINDIR,
2103                                   dir_hash_b(mbname->domain, config_fulldirhash, d),
2104                                   "/", mbname->domain,
2105                                   FNAME_USERDIR,
2106                                   dir_hash_b(mbname->localpart, config_fulldirhash, c),
2107                                   "/", mbname->localpart, ".", suffix,
2108                                   (char *)NULL);
2109             }
2110             else {
2111                 fname = strconcat(config_dir,
2112                                   FNAME_DOMAINDIR,
2113                                   dir_hash_b(mbname->domain, config_fulldirhash, d),
2114                                   "/", mbname->domain,
2115                                   FNAME_USERDIR,
2116                                   dir_hash_b(mbname->localpart, config_fulldirhash, c),
2117                                   (char *)NULL);
2118             }
2119         }
2120         else {
2121             if (suffix) {
2122                 fname = strconcat(config_dir,
2123                                   FNAME_DOMAINDIR,
2124                                   dir_hash_b(mbname->domain, config_fulldirhash, d),
2125                                   "/", mbname->domain,
2126                                   "/", FNAME_SHAREDPREFIX, ".", suffix,
2127                                   (char *)NULL);
2128             }
2129             else {
2130                 fname = strconcat(config_dir,
2131                                   FNAME_DOMAINDIR,
2132                                   dir_hash_b(mbname->domain, config_fulldirhash, d),
2133                                   "/", mbname->domain,
2134                                   (char *)NULL);
2135             }
2136         }
2137     }
2138     else {
2139         if (mbname->localpart) {
2140             if (suffix) {
2141                 fname = strconcat(config_dir,
2142                                   FNAME_USERDIR,
2143                                   dir_hash_b(mbname->localpart, config_fulldirhash, c),
2144                                   "/", mbname->localpart, ".", suffix,
2145                                   (char *)NULL);
2146             }
2147             else {
2148                 fname = strconcat(config_dir,
2149                                   FNAME_USERDIR,
2150                                   dir_hash_b(mbname->localpart, config_fulldirhash, c),
2151                                   (char *)NULL);
2152             }
2153         }
2154         else {
2155             if (suffix) {
2156                 fname = strconcat(config_dir,
2157                                   "/", FNAME_SHAREDPREFIX, ".", suffix,
2158                                   (char *)NULL);
2159             }
2160             else {
2161                 fname = xstrdup(config_dir);
2162             }
2163         }
2164     }
2165 
2166     return fname;
2167 }
2168 
2169 /* ========================= COUNTERS ============================ */
2170 
mboxname_readval_old(const char * mboxname,const char * metaname)2171 static bit64 mboxname_readval_old(const char *mboxname, const char *metaname)
2172 {
2173     bit64 fileval = 0;
2174     mbname_t *mbname = NULL;
2175     char *fname = NULL;
2176     const char *base = NULL;
2177     size_t len = 0;
2178     int fd = -1;
2179 
2180     mbname = mbname_from_intname(mboxname);
2181 
2182     fname = mboxname_conf_getpath(mbname, metaname);
2183     if (!fname) goto done;
2184 
2185     fd = open(fname, O_RDONLY);
2186 
2187     /* read the value - note: we don't care if it's being rewritten,
2188      * we'll still get a consistent read on either the old or new
2189      * value */
2190     if (fd != -1) {
2191         struct stat sbuf;
2192         if (fstat(fd, &sbuf)) {
2193             syslog(LOG_ERR, "IOERROR: failed to stat fd %s: %m", fname);
2194             goto done;
2195         }
2196         if (sbuf.st_size) {
2197             map_refresh(fd, 1, &base, &len, sbuf.st_size, metaname, mboxname);
2198             parsenum(base, NULL, sbuf.st_size, &fileval);
2199             map_free(&base, &len);
2200         }
2201     }
2202 
2203  done:
2204     if (fd != -1) close(fd);
2205     mbname_free(&mbname);
2206     free(fname);
2207     return fileval;
2208 }
2209 
2210 #define MV_VERSION 5
2211 
2212 #define MV_OFF_GENERATION 0
2213 #define MV_OFF_VERSION 4
2214 #define MV_OFF_HIGHESTMODSEQ 8
2215 #define MV_OFF_MAILMODSEQ 16
2216 #define MV_OFF_CALDAVMODSEQ 24
2217 #define MV_OFF_CARDDAVMODSEQ 32
2218 #define MV_OFF_NOTESMODSEQ 40
2219 #define MV_OFF_MAILFOLDERSMODSEQ 48
2220 #define MV_OFF_CALDAVFOLDERSMODSEQ 56
2221 #define MV_OFF_CARDDAVFOLDERSMODSEQ 64
2222 #define MV_OFF_NOTESFOLDERSMODSEQ 72
2223 #define MV_OFF_QUOTAMODSEQ 80
2224 #define MV_OFF_RACLMODSEQ 88
2225 #define MV_OFF_SUBMISSIONMODSEQ 96
2226 #define MV_OFF_SUBMISSIONFOLDERSMODSEQ 104
2227 #define MV_OFF_UIDVALIDITY 112
2228 #define MV_OFF_CRC 116
2229 #define MV_LENGTH 120
2230 
2231 /* NOTE: you need a MV_LENGTH byte base here */
mboxname_buf_to_counters(const char * base,size_t len,struct mboxname_counters * vals)2232 static int mboxname_buf_to_counters(const char *base, size_t len, struct mboxname_counters *vals)
2233 {
2234     vals->generation = ntohl(*((uint32_t *)(base)));
2235     vals->version = ntohl(*((uint32_t *)(base+4)));
2236 
2237     /* dodgy broken version storage in v0 code, it could be anything */
2238     if (len == 48) vals->version = 0;
2239 
2240     switch (vals->version) {
2241     case 0:
2242         if (len != 48) return IMAP_MAILBOX_CHECKSUM;
2243         if (crc32_map(base, 44) != ntohl(*((uint32_t *)(base+44))))
2244             return IMAP_MAILBOX_CHECKSUM;
2245 
2246         vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
2247         vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
2248         vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
2249         vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
2250         vals->notesmodseq = 0;
2251         vals->mailfoldersmodseq = 0;
2252         vals->caldavfoldersmodseq = 0;
2253         vals->carddavfoldersmodseq = 0;
2254         vals->notesfoldersmodseq = 0;
2255         vals->uidvalidity = ntohl(*((uint32_t *)(base+40)));
2256         break;
2257 
2258     case 1:
2259         if (len != 56) return IMAP_MAILBOX_CHECKSUM;
2260         if (crc32_map(base, 52) != ntohl(*((uint32_t *)(base+52))))
2261             return IMAP_MAILBOX_CHECKSUM;
2262 
2263         vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
2264         vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
2265         vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
2266         vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
2267         vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
2268         vals->mailfoldersmodseq = 0;
2269         vals->caldavfoldersmodseq = 0;
2270         vals->carddavfoldersmodseq = 0;
2271         vals->notesfoldersmodseq = 0;
2272         vals->uidvalidity = ntohl(*((uint32_t *)(base+48)));
2273         break;
2274 
2275     case 2:
2276         if (len != 64) return IMAP_MAILBOX_CHECKSUM;
2277         if (crc32_map(base, 60) != ntohl(*((uint32_t *)(base+60))))
2278             return IMAP_MAILBOX_CHECKSUM;
2279 
2280         vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
2281         vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
2282         vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
2283         vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
2284         vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
2285         vals->mailfoldersmodseq = ntohll(*((uint32_t *)(base+48)));
2286         vals->caldavfoldersmodseq = 0;
2287         vals->carddavfoldersmodseq = 0;
2288         vals->notesfoldersmodseq = 0;
2289         vals->uidvalidity = ntohl(*((uint32_t *)(base+56)));
2290         break;
2291 
2292     case 3:
2293         if (len != 88) return IMAP_MAILBOX_CHECKSUM;
2294         if (crc32_map(base, 84) != ntohl(*((uint32_t *)(base+84))))
2295             return IMAP_MAILBOX_CHECKSUM;
2296 
2297         vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
2298         vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
2299         vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
2300         vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
2301         vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
2302         vals->mailfoldersmodseq = ntohll(*((uint64_t *)(base+48)));
2303         vals->caldavfoldersmodseq = ntohll(*((uint64_t *)(base+56)));
2304         vals->carddavfoldersmodseq = ntohll(*((uint64_t *)(base+64)));
2305         vals->notesfoldersmodseq = ntohll(*((uint64_t *)(base+72)));
2306         vals->uidvalidity = ntohl(*((uint32_t *)(base+80)));
2307         break;
2308 
2309     case 4:
2310         if (len != 104) return IMAP_MAILBOX_CHECKSUM;
2311         if (crc32_map(base, 100) != ntohl(*((uint32_t *)(base+100))))
2312             return IMAP_MAILBOX_CHECKSUM;
2313 
2314         vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
2315         vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
2316         vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
2317         vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
2318         vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
2319         vals->mailfoldersmodseq = ntohll(*((uint64_t *)(base+48)));
2320         vals->caldavfoldersmodseq = ntohll(*((uint64_t *)(base+56)));
2321         vals->carddavfoldersmodseq = ntohll(*((uint64_t *)(base+64)));
2322         vals->notesfoldersmodseq = ntohll(*((uint64_t *)(base+72)));
2323         vals->quotamodseq = ntohll(*((uint64_t *)(base+80)));
2324         vals->raclmodseq = ntohll(*((uint64_t *)(base+88)));
2325         vals->uidvalidity = ntohl(*((uint32_t *)(base+96)));
2326         break;
2327 
2328     case 5:
2329         if (len != 120) return IMAP_MAILBOX_CHECKSUM;
2330         if (crc32_map(base, 116) != ntohl(*((uint32_t *)(base+116))))
2331             return IMAP_MAILBOX_CHECKSUM;
2332 
2333         vals->highestmodseq = ntohll(*((uint64_t *)(base+8)));
2334         vals->mailmodseq = ntohll(*((uint64_t *)(base+16)));
2335         vals->caldavmodseq = ntohll(*((uint64_t *)(base+24)));
2336         vals->carddavmodseq = ntohll(*((uint64_t *)(base+32)));
2337         vals->notesmodseq = ntohll(*((uint64_t *)(base+40)));
2338         vals->mailfoldersmodseq = ntohll(*((uint64_t *)(base+48)));
2339         vals->caldavfoldersmodseq = ntohll(*((uint64_t *)(base+56)));
2340         vals->carddavfoldersmodseq = ntohll(*((uint64_t *)(base+64)));
2341         vals->notesfoldersmodseq = ntohll(*((uint64_t *)(base+72)));
2342         vals->quotamodseq = ntohll(*((uint64_t *)(base+80)));
2343         vals->raclmodseq = ntohll(*((uint64_t *)(base+88)));
2344         vals->submissionmodseq = ntohll(*((uint64_t *)(base+96)));
2345         vals->submissionfoldersmodseq = ntohll(*((uint64_t *)(base+104)));
2346         vals->uidvalidity = ntohl(*((uint32_t *)(base+112)));
2347         break;
2348 
2349     default:
2350         return IMAP_MAILBOX_BADFORMAT;
2351     }
2352 
2353     return 0;
2354 }
2355 
2356 /* NOTE: you need a MV_LENGTH buffer to write into, aligned on 8 byte boundaries */
mboxname_counters_to_buf(const struct mboxname_counters * vals,char * base)2357 static void mboxname_counters_to_buf(const struct mboxname_counters *vals, char *base)
2358 {
2359     *((uint32_t *)(base+MV_OFF_GENERATION)) = htonl(vals->generation);
2360     *((uint32_t *)(base+MV_OFF_VERSION)) = htonl(MV_VERSION);
2361     align_htonll(base+MV_OFF_HIGHESTMODSEQ, vals->highestmodseq);
2362     align_htonll(base+MV_OFF_MAILMODSEQ, vals->mailmodseq);
2363     align_htonll(base+MV_OFF_CALDAVMODSEQ, vals->caldavmodseq);
2364     align_htonll(base+MV_OFF_CARDDAVMODSEQ, vals->carddavmodseq);
2365     align_htonll(base+MV_OFF_NOTESMODSEQ, vals->notesmodseq);
2366     align_htonll(base+MV_OFF_MAILFOLDERSMODSEQ, vals->mailfoldersmodseq);
2367     align_htonll(base+MV_OFF_CALDAVFOLDERSMODSEQ, vals->caldavfoldersmodseq);
2368     align_htonll(base+MV_OFF_CARDDAVFOLDERSMODSEQ, vals->carddavfoldersmodseq);
2369     align_htonll(base+MV_OFF_NOTESFOLDERSMODSEQ, vals->notesfoldersmodseq);
2370     *((uint32_t *)(base+MV_OFF_UIDVALIDITY)) = htonl(vals->uidvalidity);
2371     align_htonll(base+MV_OFF_QUOTAMODSEQ, vals->quotamodseq);
2372     align_htonll(base+MV_OFF_RACLMODSEQ, vals->raclmodseq);
2373     align_htonll(base+MV_OFF_SUBMISSIONMODSEQ, vals->submissionmodseq);
2374     align_htonll(base+MV_OFF_SUBMISSIONFOLDERSMODSEQ, vals->submissionfoldersmodseq);
2375     *((uint32_t *)(base+MV_OFF_CRC)) = htonl(crc32_map(base, MV_OFF_CRC));
2376 }
2377 
2378 /* XXX - inform about errors?  Any error causes the value of at least
2379    last+1 to be returned.  An error only on writing causes
2380    max(last, fileval) + 1 to still be returned */
mboxname_load_counters(const char * mboxname,struct mboxname_counters * vals,int * fdp)2381 static int mboxname_load_counters(const char *mboxname, struct mboxname_counters *vals, int *fdp)
2382 {
2383     int fd = -1;
2384     char *fname = NULL;
2385     struct stat sbuf, fbuf;
2386     const char *base = NULL;
2387     size_t len = 0;
2388     mbname_t *mbname = NULL;
2389     int r = 0;
2390 
2391     memset(vals, 0, sizeof(struct mboxname_counters));
2392 
2393     mbname = mbname_from_intname(mboxname);
2394 
2395     fname = mboxname_conf_getpath(mbname, "counters");
2396     if (!fname) {
2397         r = IMAP_MAILBOX_BADNAME;
2398         goto done;
2399     }
2400 
2401     /* get a blocking lock on fd */
2402     for (;;) {
2403         fd = open(fname, O_RDWR | O_CREAT, 0644);
2404         if (fd == -1) {
2405             /* OK to not exist - try creating the directory first */
2406             if (cyrus_mkdir(fname, 0755)) goto done;
2407             fd = open(fname, O_RDWR | O_CREAT, 0644);
2408         }
2409         if (fd == -1) {
2410             syslog(LOG_ERR, "IOERROR: failed to create %s: %m", fname);
2411             goto done;
2412         }
2413         if (lock_blocking(fd, fname)) {
2414             syslog(LOG_ERR, "IOERROR: failed to lock %s: %m", fname);
2415             goto done;
2416         }
2417         if (fstat(fd, &sbuf)) {
2418             syslog(LOG_ERR, "IOERROR: failed to stat fd %s: %m", fname);
2419             goto done;
2420         }
2421         if (stat(fname, &fbuf)) {
2422             syslog(LOG_ERR, "IOERROR: failed to stat file %s: %m", fname);
2423             goto done;
2424         }
2425         if (sbuf.st_ino == fbuf.st_ino) break;
2426         lock_unlock(fd, fname);
2427         close(fd);
2428         fd = -1;
2429     }
2430 
2431     if (fd < 0) {
2432         r = IMAP_IOERROR;
2433         goto done;
2434     }
2435 
2436     if (sbuf.st_size >= 8) {
2437         /* read the old value */
2438         map_refresh(fd, 1, &base, &len, sbuf.st_size, "counters", mboxname);
2439         if (len >= 8) {
2440             r = mboxname_buf_to_counters(base, len, vals);
2441         }
2442         map_free(&base, &len);
2443     }
2444     else {
2445         /* going to have to read the old files */
2446         vals->mailmodseq = vals->caldavmodseq = vals->carddavmodseq =
2447             vals->highestmodseq = mboxname_readval_old(mboxname, "modseq");
2448         vals->uidvalidity = mboxname_readval_old(mboxname, "uidvalidity");
2449     }
2450 
2451 done:
2452     if (r) {
2453         if (fd != -1) {
2454             lock_unlock(fd, fname);
2455             close(fd);
2456         }
2457     }
2458     else {
2459         /* maintain the lock until we're done */
2460         *fdp = fd;
2461     }
2462     mbname_free(&mbname);
2463     free(fname);
2464     return r;
2465 }
2466 
mboxname_set_counters(const char * mboxname,struct mboxname_counters * vals,int fd)2467 static int mboxname_set_counters(const char *mboxname, struct mboxname_counters *vals, int fd)
2468 {
2469     char *fname = NULL;
2470     mbname_t *mbname = NULL;
2471     char buf[MV_LENGTH];
2472     char newfname[MAX_MAILBOX_PATH];
2473     int newfd = -1;
2474     int n = 0;
2475     int r = 0;
2476 
2477     mbname = mbname_from_intname(mboxname);
2478 
2479     fname = mboxname_conf_getpath(mbname, "counters");
2480     if (!fname) {
2481         r = IMAP_MAILBOX_BADNAME;
2482         goto done;
2483     }
2484 
2485     snprintf(newfname, MAX_MAILBOX_PATH, "%s.NEW", fname);
2486     newfd = open(newfname, O_CREAT | O_TRUNC | O_WRONLY, 0644);
2487     if (newfd == -1) {
2488         r = IMAP_IOERROR;
2489         syslog(LOG_ERR, "IOERROR: failed to open for write %s: %m", newfname);
2490         goto done;
2491     }
2492 
2493     /* it's a new generation! */
2494     vals->generation++;
2495 
2496     mboxname_counters_to_buf(vals, buf);
2497     n = retry_write(newfd, buf, MV_LENGTH);
2498     if (n < 0) {
2499         r = IMAP_IOERROR;
2500         syslog(LOG_ERR, "IOERROR: failed to write %s: %m", newfname);
2501         goto done;
2502     }
2503 
2504     if (fdatasync(newfd)) {
2505         r = IMAP_IOERROR;
2506         syslog(LOG_ERR, "IOERROR: failed to fdatasync %s: %m", newfname);
2507         goto done;
2508     }
2509 
2510     close(newfd);
2511     newfd = -1;
2512 
2513     if (rename(newfname, fname)) {
2514         r = IMAP_IOERROR;
2515         syslog(LOG_ERR, "IOERROR: failed to rename %s: %m", newfname);
2516         goto done;
2517     }
2518 
2519  done:
2520     if (newfd != -1) close(newfd);
2521     if (fd != -1) {
2522         lock_unlock(fd, fname);
2523         close(fd);
2524     }
2525     mbname_free(&mbname);
2526     free(fname);
2527 
2528     return r;
2529 }
2530 
mboxname_unload_counters(int fd)2531 static int mboxname_unload_counters(int fd)
2532 {
2533     lock_unlock(fd, NULL);
2534     close(fd);
2535     return 0;
2536 }
2537 
mboxname_read_counters(const char * mboxname,struct mboxname_counters * vals)2538 EXPORTED int mboxname_read_counters(const char *mboxname, struct mboxname_counters *vals)
2539 {
2540     int r = 0;
2541     mbname_t *mbname = NULL;
2542     struct stat sbuf;
2543     char *fname = NULL;
2544     const char *base = NULL;
2545     size_t len = 0;
2546     int fd = -1;
2547 
2548     memset(vals, 0, sizeof(struct mboxname_counters));
2549 
2550     mbname = mbname_from_intname(mboxname);
2551 
2552     fname = mboxname_conf_getpath(mbname, "counters");
2553     if (!fname) {
2554         r = IMAP_MAILBOX_BADNAME;
2555         goto done;
2556     }
2557 
2558     fd = open(fname, O_RDONLY);
2559 
2560     /* if no file, import from the old files potentially, and write a file regardless */
2561     if (fd < 0) {
2562         /* race => multiple rewrites, won't hurt too much */
2563         r = mboxname_load_counters(mboxname, vals, &fd);
2564         if (r) goto done;
2565         r = mboxname_set_counters(mboxname, vals, fd);
2566         fd = -1;
2567         if (r) goto done;
2568         free(fname);
2569         fname = mboxname_conf_getpath(mbname, "modseq");
2570         if (fname) unlink(fname);
2571         free(fname);
2572         fname = mboxname_conf_getpath(mbname, "uidvalidity");
2573         if (fname) unlink(fname);
2574         goto done;
2575     }
2576 
2577     if (fstat(fd, &sbuf)) {
2578         syslog(LOG_ERR, "IOERROR: failed to stat fd %s: %m", fname);
2579         r = IMAP_IOERROR;
2580         goto done;
2581     }
2582 
2583     if (sbuf.st_size >= 8) {
2584         map_refresh(fd, 1, &base, &len, sbuf.st_size, "counters", mboxname);
2585         if (len >= 8)
2586             r = mboxname_buf_to_counters(base, len, vals);
2587         map_free(&base, &len);
2588     }
2589 
2590  done:
2591     if (fd != -1) close(fd);
2592     mbname_free(&mbname);
2593     free(fname);
2594     return r;
2595 }
2596 
2597 enum domodseq { MAILBOXMODSEQ, QUOTAMODSEQ, RACLMODSEQ };
2598 
mboxname_domodseq(const char * mboxname,modseq_t last,enum domodseq domodseq,int mbtype,int dofolder,modseq_t add)2599 static modseq_t mboxname_domodseq(const char *mboxname,
2600                                   modseq_t last,
2601                                   enum domodseq domodseq,
2602                                   int mbtype,   // for MAILBOXMODSEQ
2603                                   int dofolder, // for MAILBOXMODSEQ
2604                                   modseq_t add)
2605 {
2606     struct mboxname_counters counters;
2607     struct mboxname_counters oldcounters;
2608     modseq_t *typemodseqp = NULL;
2609     modseq_t *foldersmodseqp = NULL;
2610     int fd = -1;
2611 
2612     if (!config_getswitch(IMAPOPT_CONVERSATIONS))
2613         return last + add;
2614 
2615     /* XXX error handling */
2616     if (mboxname_load_counters(mboxname, &counters, &fd))
2617         return last + add;
2618 
2619     oldcounters = counters;
2620 
2621     if (domodseq == MAILBOXMODSEQ) {
2622         if (mboxname_isaddressbookmailbox(mboxname, mbtype)) {
2623             typemodseqp = &counters.carddavmodseq;
2624             foldersmodseqp = &counters.carddavfoldersmodseq;
2625         }
2626         else if (mboxname_iscalendarmailbox(mboxname, mbtype)) {
2627             typemodseqp = &counters.caldavmodseq;
2628             foldersmodseqp = &counters.caldavfoldersmodseq;
2629         }
2630         else if (mboxname_isnotesmailbox(mboxname, mbtype)) {
2631             typemodseqp = &counters.notesmodseq;
2632             foldersmodseqp = &counters.notesfoldersmodseq;
2633         }
2634         else if (mboxname_issubmissionmailbox(mboxname, mbtype)) {
2635             typemodseqp = &counters.submissionmodseq;
2636             foldersmodseqp = &counters.submissionfoldersmodseq;
2637         }
2638         else {
2639             typemodseqp = &counters.mailmodseq;
2640             foldersmodseqp = &counters.mailfoldersmodseq;
2641         }
2642     }
2643     else if (domodseq == QUOTAMODSEQ) {
2644         typemodseqp = &counters.quotamodseq;
2645         dofolder = 0;
2646     }
2647     else if (domodseq == RACLMODSEQ) {
2648         typemodseqp = &counters.raclmodseq;
2649         dofolder = 0;
2650     }
2651 
2652     /* make sure all counters are at least the old value */
2653     if (counters.highestmodseq < last)
2654         counters.highestmodseq = last;
2655     if (*typemodseqp < last)
2656         *typemodseqp = last;
2657     if (dofolder && *foldersmodseqp < last)
2658         *foldersmodseqp = last;
2659 
2660     /* if adding, bring all counters up to the overall highest modseq */
2661     if (add) {
2662         counters.highestmodseq += add;
2663         *typemodseqp = counters.highestmodseq;
2664         if (dofolder) *foldersmodseqp = counters.highestmodseq;
2665     }
2666 
2667     if (memcmp(&counters, &oldcounters, sizeof(struct mboxname_counters)))
2668         mboxname_set_counters(mboxname, &counters, fd);
2669     else
2670         mboxname_unload_counters(fd);
2671 
2672     return counters.highestmodseq;
2673 }
2674 
mboxname_nextmodseq(const char * mboxname,modseq_t last,int mbtype,int dofolder)2675 EXPORTED modseq_t mboxname_nextmodseq(const char *mboxname, modseq_t last, int mbtype, int dofolder)
2676 {
2677     return mboxname_domodseq(mboxname, last, MAILBOXMODSEQ, mbtype, dofolder, 1);
2678 }
2679 
mboxname_setmodseq(const char * mboxname,modseq_t last,int mbtype,int dofolder)2680 EXPORTED modseq_t mboxname_setmodseq(const char *mboxname, modseq_t last, int mbtype, int dofolder)
2681 {
2682     return mboxname_domodseq(mboxname, last, MAILBOXMODSEQ, mbtype, dofolder, 0);
2683 }
2684 
mboxname_readquotamodseq(const char * mboxname)2685 EXPORTED modseq_t mboxname_readquotamodseq(const char *mboxname)
2686 {
2687     struct mboxname_counters counters;
2688 
2689     if (!config_getswitch(IMAPOPT_CONVERSATIONS))
2690         return 0;
2691 
2692     if (mboxname_read_counters(mboxname, &counters))
2693         return 0;
2694 
2695     return counters.quotamodseq;
2696 }
2697 
mboxname_nextquotamodseq(const char * mboxname,modseq_t last)2698 EXPORTED modseq_t mboxname_nextquotamodseq(const char *mboxname, modseq_t last)
2699 {
2700     return mboxname_domodseq(mboxname, last, QUOTAMODSEQ, 0, 0, 1);
2701 }
2702 
mboxname_setquotamodseq(const char * mboxname,modseq_t last)2703 EXPORTED modseq_t mboxname_setquotamodseq(const char *mboxname, modseq_t last)
2704 {
2705     return mboxname_domodseq(mboxname, last, QUOTAMODSEQ, 0, 0, 0);
2706 }
2707 
mboxname_readraclmodseq(const char * mboxname)2708 EXPORTED modseq_t mboxname_readraclmodseq(const char *mboxname)
2709 {
2710     struct mboxname_counters counters;
2711 
2712     if (!config_getswitch(IMAPOPT_CONVERSATIONS))
2713         return 0;
2714 
2715     if (!mboxname_isusermailbox(mboxname, /*isinbox*/1))
2716         return 0;  // raclmodseq is only defined on user inboxes
2717 
2718     if (mboxname_read_counters(mboxname, &counters))
2719         return 0;
2720 
2721     return counters.raclmodseq;
2722 }
2723 
mboxname_nextraclmodseq(const char * mboxname,modseq_t last)2724 EXPORTED modseq_t mboxname_nextraclmodseq(const char *mboxname, modseq_t last)
2725 {
2726     return mboxname_domodseq(mboxname, last, RACLMODSEQ, 0, 0, 1);
2727 }
2728 
mboxname_setraclmodseq(const char * mboxname,modseq_t last)2729 EXPORTED modseq_t mboxname_setraclmodseq(const char *mboxname, modseq_t last)
2730 {
2731     return mboxname_domodseq(mboxname, last, RACLMODSEQ, 0, 0, 0);
2732 }
2733 
mboxname_readuidvalidity(const char * mboxname)2734 EXPORTED uint32_t mboxname_readuidvalidity(const char *mboxname)
2735 {
2736     struct mboxname_counters counters;
2737 
2738     if (!config_getswitch(IMAPOPT_CONVERSATIONS))
2739         return 0;
2740 
2741     if (mboxname_read_counters(mboxname, &counters))
2742         return 0;
2743 
2744     return counters.uidvalidity;
2745 }
2746 
mboxname_nextuidvalidity(const char * mboxname,uint32_t last)2747 EXPORTED uint32_t mboxname_nextuidvalidity(const char *mboxname, uint32_t last)
2748 {
2749     struct mboxname_counters counters;
2750     int fd = -1;
2751 
2752     if (!config_getswitch(IMAPOPT_CONVERSATIONS))
2753         return last + 1;
2754 
2755     /* XXX error handling */
2756     if (mboxname_load_counters(mboxname, &counters, &fd))
2757         return last + 1;
2758 
2759     if (counters.uidvalidity < last)
2760         counters.uidvalidity = last;
2761 
2762     counters.uidvalidity++;
2763 
2764     /* always set, because we always increased */
2765     mboxname_set_counters(mboxname, &counters, fd);
2766 
2767     return counters.uidvalidity;
2768 }
2769 
mboxname_setuidvalidity(const char * mboxname,uint32_t val)2770 EXPORTED uint32_t mboxname_setuidvalidity(const char *mboxname, uint32_t val)
2771 {
2772     struct mboxname_counters counters;
2773     int fd = -1;
2774     int dirty = 0;
2775 
2776     if (!config_getswitch(IMAPOPT_CONVERSATIONS))
2777         return val;
2778 
2779     /* XXX error handling */
2780     if (mboxname_load_counters(mboxname, &counters, &fd))
2781         return val;
2782 
2783     if (counters.uidvalidity < val) {
2784         counters.uidvalidity = val;
2785         dirty = 1;
2786     }
2787 
2788     if (dirty)
2789         mboxname_set_counters(mboxname, &counters, fd);
2790     else
2791         mboxname_unload_counters(fd);
2792 
2793     return val;
2794 }
2795 
mboxname_common_ancestor(const char * mboxname1,const char * mboxname2)2796 EXPORTED char *mboxname_common_ancestor(const char *mboxname1, const char *mboxname2)
2797 {
2798     mbname_t *mbname1 = mbname_from_intname(mboxname1);
2799     mbname_t *mbname2 = mbname_from_intname(mboxname2);
2800     char *ancestor = NULL;
2801 
2802     if (!mbname_same_userid(mbname1, mbname2))
2803         goto done;
2804 
2805     const strarray_t *boxes1 = mbname_boxes(mbname1);
2806     const strarray_t *boxes2 = mbname_boxes(mbname2);
2807     int len = boxes1->count < boxes2->count ? boxes1->count : boxes2->count;
2808     int i;
2809     for (i = 0; i < len - 1; i++) {
2810         if (strcmp(strarray_nth(boxes1, i), strarray_nth(boxes2, i)))
2811             break;
2812     }
2813     if (i > 0) {
2814         mbname_truncate_boxes(mbname1, i);
2815         ancestor = xstrdup(mbname_intname(mbname1));
2816     }
2817 
2818 done:
2819     mbname_free(&mbname1);
2820     mbname_free(&mbname2);
2821     return ancestor;
2822 }
2823