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