xref: /plan9-contrib/sys/src/cmd/ip/imap4d/mbox.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include "imap4d.h"
6 
7 static NamedInt	flagChars[NFlags] =
8 {
9 	{"s",	MSeen},
10 	{"a",	MAnswered},
11 	{"f",	MFlagged},
12 	{"D",	MDeleted},
13 	{"d",	MDraft},
14 	{"r",	MRecent},
15 };
16 
17 static	int	fsCtl = -1;
18 
19 static	void	boxFlags(Box *box);
20 static	int	createImp(Box *box, Qid *qid);
21 static	void	fsInit(void);
22 static	void	mboxGone(Box *box);
23 static	MbLock	*openImp(Box *box, int new);
24 static	int	parseImp(Biobuf *b, Box *box);
25 static	int	readBox(Box *box);
26 static	ulong	uidRenumber(Msg *m, ulong uid, int force);
27 static	int	impFlags(Box *box, Msg *m, char *flags);
28 
29 /*
30  * strategy:
31  * every mailbox file has an associated .imp file
32  * which maps upas/fs message digests to uids & message flags.
33  *
34  * the .imp files are locked by /mail/fs/usename/L.mbox.
35  * whenever the flags can be modified, the lock file
36  * should be opened, thereby locking the uid & flag state.
37  * for example, whenever new uids are assigned to messages,
38  * and whenever flags are changed internally, the lock file
39  * should be open and locked.  this means the file must be
40  * opened during store command, and when changing the \seen
41  * flag for the fetch command.
42  *
43  * if no .imp file exists, a null one must be created before
44  * assigning uids.
45  *
46  * the .imp file has the following format
47  * imp		: "imap internal mailbox description\n"
48  * 			uidvalidity " " uidnext "\n"
49  *			messageLines
50  *
51  * messageLines	:
52  *		| messageLines digest " " uid " " flags "\n"
53  *
54  * uid, uidnext, and uidvalidity are 32 bit decimal numbers
55  * printed right justified in a field NUid characters long.
56  * the 0 uid implies that no uid has been assigned to the message,
57  * but the flags are valid. note that message lines are in mailbox
58  * order, except possibly for 0 uid messages.
59  *
60  * digest is an ascii hex string NDigest characters long.
61  *
62  * flags has a character for each of NFlag flag fields.
63  * if the flag is clear, it is represented by a "-".
64  * set flags are represented as a unique single ascii character.
65  * the currently assigned flags are, in order:
66  *	MSeen		s
67  *	MAnswered	a
68  *	MFlagged	f
69  *	MDeleted	D
70  *	MDraft		d
71  */
72 Box*
73 openBox(char *name, char *fsname, int writable)
74 {
75 	Box *box;
76 	MbLock *ml;
77 	int n, new;
78 
79 	if(cistrcmp(name, "inbox") == 0)
80 		name = "mbox";
81 
82 	fsInit();
83 	if(fprint(fsCtl, "open /mail/box/%s/%s %s", username, name, fsname) < 0){
84 //ZZZ
85 char err[ERRMAX];
86 errstr(err, sizeof err);
87 if(strstr(err, "file does not exist") == nil)
88 	fprint(2, "imap4d at %lud: upas/fs open %s/%s as %s failed: '%s' %s\n", time(nil), username, name, fsname, err, ctime(time(nil)));
89 		fprint(fsCtl, "close %s", fsname);
90 		return nil;
91 	}
92 
93 	/*
94 	 * read box to find all messages
95 	 * each one has a directory, and is in numerical order
96 	 */
97 	box = MKZ(Box);
98 	box->writable = writable;
99 
100 	n = strlen(name) + 1;
101 	box->name = emalloc(n);
102 	strcpy(box->name, name);
103 
104 	n += STRLEN(".imp");
105 	box->imp = emalloc(n);
106 	snprint(box->imp, n, "%s.imp", name);
107 
108 	n = strlen(fsname) + 1;
109 	box->fs = emalloc(n);
110 	strcpy(box->fs, fsname);
111 
112 	n = STRLEN("/mail/fs/") + strlen(fsname) + 1;
113 	box->fsDir = emalloc(n);
114 	snprint(box->fsDir, n, "/mail/fs/%s", fsname);
115 
116 	box->uidnext = 1;
117 	new = readBox(box);
118 	if(new >= 0){
119 		ml = openImp(box, new);
120 		if(ml != nil){
121 			closeImp(box, ml);
122 			return box;
123 		}
124 	}
125 	closeBox(box, 0);
126 	return nil;
127 }
128 
129 /*
130  * check mailbox
131  * returns fd of open .imp file if imped.
132  * otherwise, return value is insignificant
133  *
134  * careful: called by idle polling proc
135  */
136 MbLock*
137 checkBox(Box *box, int imped)
138 {
139 	MbLock *ml;
140 	Dir *d;
141 	int new;
142 
143 	if(box == nil)
144 		return nil;
145 
146 	/*
147 	 * if stat fails, mailbox must be gone
148 	 */
149 	d = cdDirstat(box->fsDir, ".");
150 	if(d == nil){
151 		mboxGone(box);
152 		return nil;
153 	}
154 	new = 0;
155 	if(box->qid.path != d->qid.path || box->qid.vers != d->qid.vers
156 	|| box->mtime != d->mtime){
157 		new = readBox(box);
158 		if(new < 0){
159 			free(d);
160 			return nil;
161 		}
162 	}
163 	free(d);
164 	ml = openImp(box, new);
165 	if(ml == nil)
166 		box->writable = 0;
167 	else if(!imped){
168 		closeImp(box, ml);
169 		ml = nil;
170 	}
171 	return ml;
172 }
173 
174 /*
175  * mailbox is unreachable, so mark all messages expunged
176  * clean up .imp files as well.
177  */
178 static void
179 mboxGone(Box *box)
180 {
181 	Msg *m;
182 
183 	if(cdExists(mboxDir, box->name) < 0)
184 		cdRemove(mboxDir, box->imp);
185 	for(m = box->msgs; m != nil; m = m->next)
186 		m->expunged = 1;
187 	box->writable = 0;
188 }
189 
190 /*
191  * read messages in the mailbox
192  * mark message that no longer exist as expunged
193  * returns -1 for failure, 0 if no new messages, 1 if new messages.
194  */
195 static int
196 readBox(Box *box)
197 {
198 	Msg *msgs, *m, *last;
199 	Dir *d;
200 	char *s;
201 	long max, id;
202 	int i, nd, fd, new;
203 
204 	fd = cdOpen(box->fsDir, ".", OREAD);
205 	if(fd < 0){
206 		syslog(0, "mail", "imap4d at %lud: upas/fs stat of %s/%s aka %s failed: %r\n", time(nil),
207 			username, box->name, box->fsDir);
208 		mboxGone(box);
209 		return -1;
210 	}
211 
212 	/*
213 	 * read box to find all messages
214 	 * each one has a directory, and is in numerical order
215 	 */
216 	d = dirfstat(fd);
217 	if(d == nil){
218 		close(fd);
219 		return -1;
220 	}
221 	box->mtime = d->mtime;
222 	box->qid = d->qid;
223 	last = nil;
224 	msgs = box->msgs;
225 	max = 0;
226 	new = 0;
227 	free(d);
228 	while((nd = dirread(fd, &d)) > 0){
229 		for(i = 0; i < nd; i++){
230 			s = d[i].name;
231 			id = strtol(s, &s, 10);
232 			if(id <= max || *s != '\0'
233 			|| (d[i].mode & DMDIR) != DMDIR)
234 				continue;
235 
236 			max = id;
237 
238 			while(msgs != nil){
239 				last = msgs;
240 				msgs = msgs->next;
241 				if(last->id == id)
242 					goto continueDir;
243 				last->expunged = 1;
244 			}
245 
246 			new = 1;
247 			m = MKZ(Msg);
248 			m->id = id;
249 			m->fsDir = box->fsDir;
250 			m->fs = emalloc(2 * (MsgNameLen + 1));
251 			m->efs = seprint(m->fs, m->fs + (MsgNameLen + 1), "%lud/", id);
252 			m->size = ~0UL;
253 			m->lines = ~0UL;
254 			m->prev = last;
255 			m->flags = MRecent;
256 			if(!msgInfo(m))
257 				freeMsg(m);
258 			else{
259 				if(last == nil)
260 					box->msgs = m;
261 				else
262 					last->next = m;
263 				last = m;
264 			}
265 	continueDir:;
266 		}
267 		free(d);
268 	}
269 	close(fd);
270 	for(; msgs != nil; msgs = msgs->next)
271 		msgs->expunged = 1;
272 
273 	/*
274 	 * make up the imap message sequence numbers
275 	 */
276 	id = 1;
277 	for(m = box->msgs; m != nil; m = m->next){
278 		if(m->seq && m->seq != id)
279 			bye("internal error assigning message numbers");
280 		m->seq = id++;
281 	}
282 	box->max = id - 1;
283 
284 	return new;
285 }
286 
287 /*
288  * read in the .imp file, or make one if it doesn't exist.
289  * make sure all flags and uids are consistent.
290  * return the mailbox lock.
291  */
292 #define IMPMAGIC	"imap internal mailbox description\n"
293 static MbLock*
294 openImp(Box *box, int new)
295 {
296 	Qid qid;
297 	Biobuf b;
298 	MbLock *ml;
299 	int fd;
300 //ZZZZ
301 	int once;
302 
303 	ml = mbLock();
304 	if(ml == nil)
305 		return nil;
306 	fd = cdOpen(mboxDir, box->imp, OREAD);
307 	once = 0;
308 ZZZhack:
309 	if(fd < 0 || fqid(fd, &qid) < 0){
310 		if(fd < 0){
311 			char buf[ERRMAX];
312 
313 			errstr(buf, sizeof buf);
314 			if(cistrstr(buf, "does not exist") == nil)
315 				fprint(2, "imap4d at %lud: imp open failed: %s\n", time(nil), buf);
316 			if(!once && cistrstr(buf, "locked") != nil){
317 				once = 1;
318 				fprint(2, "imap4d at %lud: imp %s/%s %s locked when it shouldn't be; spinning\n", time(nil), username, box->name, box->imp);
319 				fd = openLocked(mboxDir, box->imp, OREAD);
320 				goto ZZZhack;
321 			}
322 		}
323 		if(fd >= 0)
324 			close(fd);
325 		fd = createImp(box, &qid);
326 		if(fd < 0){
327 			mbUnlock(ml);
328 			return nil;
329 		}
330 		box->dirtyImp = 1;
331 		if(box->uidvalidity == 0)
332 			box->uidvalidity = box->mtime;
333 		box->impQid = qid;
334 		new = 1;
335 	}else if(qid.path != box->impQid.path || qid.vers != box->impQid.vers){
336 		Binit(&b, fd, OREAD);
337 		if(!parseImp(&b, box)){
338 			box->dirtyImp = 1;
339 			if(box->uidvalidity == 0)
340 				box->uidvalidity = box->mtime;
341 		}
342 		Bterm(&b);
343 		box->impQid = qid;
344 		new = 1;
345 	}
346 	if(new)
347 		boxFlags(box);
348 	close(fd);
349 	return ml;
350 }
351 
352 /*
353  * close the .imp file, after writing out any changes
354  */
355 void
356 closeImp(Box *box, MbLock *ml)
357 {
358 	Msg *m;
359 	Qid qid;
360 	Biobuf b;
361 	char buf[NFlags+1];
362 	int fd;
363 
364 	if(ml == nil)
365 		return;
366 	if(!box->dirtyImp){
367 		mbUnlock(ml);
368 		return;
369 	}
370 
371 	fd = cdCreate(mboxDir, box->imp, OWRITE, 0664);
372 	if(fd < 0){
373 		mbUnlock(ml);
374 		return;
375 	}
376 	Binit(&b, fd, OWRITE);
377 
378 	box->dirtyImp = 0;
379 	Bprint(&b, "%s", IMPMAGIC);
380 	Bprint(&b, "%.*lud %.*lud\n", NUid, box->uidvalidity, NUid, box->uidnext);
381 	for(m = box->msgs; m != nil; m = m->next){
382 		if(m->expunged)
383 			continue;
384 		wrImpFlags(buf, m->flags, strcmp(box->fs, "imap") == 0);
385 		Bprint(&b, "%.*s %.*lud %s\n", NDigest, m->info[IDigest], NUid, m->uid, buf);
386 	}
387 	Bterm(&b);
388 
389 	if(fqid(fd, &qid) >= 0)
390 		box->impQid = qid;
391 	close(fd);
392 	mbUnlock(ml);
393 }
394 
395 void
396 wrImpFlags(char *buf, int flags, int killRecent)
397 {
398 	int i;
399 
400 	for(i = 0; i < NFlags; i++){
401 		if((flags & flagChars[i].v)
402 		&& (flagChars[i].v != MRecent || !killRecent))
403 			buf[i] = flagChars[i].name[0];
404 		else
405 			buf[i] = '-';
406 	}
407 	buf[i] = '\0';
408 }
409 
410 int
411 emptyImp(char *mbox)
412 {
413 	Dir *d;
414 	long mode;
415 	int fd;
416 
417 	fd = cdCreate(mboxDir, impName(mbox), OWRITE, 0664);
418 	if(fd < 0)
419 		return -1;
420 	d = cdDirstat(mboxDir, mbox);
421 	if(d == nil){
422 		close(fd);
423 		return -1;
424 	}
425 	fprint(fd, "%s%.*lud %.*lud\n", IMPMAGIC, NUid, d->mtime, NUid, 1UL);
426 	mode = d->mode & 0777;
427 	nulldir(d);
428 	d->mode = mode;
429 	dirfwstat(fd, d);
430 	free(d);
431 	return fd;
432 }
433 
434 /*
435  * try to match permissions with mbox
436  */
437 static int
438 createImp(Box *box, Qid *qid)
439 {
440 	Dir *d;
441 	long mode;
442 	int fd;
443 
444 	fd = cdCreate(mboxDir, box->imp, OREAD, 0664);
445 	if(fd < 0)
446 		return -1;
447 	d = cdDirstat(mboxDir, box->name);
448 	if(d != nil){
449 		mode = d->mode & 0777;
450 		nulldir(d);
451 		d->mode = mode;
452 		dirfwstat(fd, d);
453 		free(d);
454 	}
455 	if(fqid(fd, qid) < 0){
456 		close(fd);
457 		return -1;
458 	}
459 
460 	return fd;
461 }
462 
463 /*
464  * read or re-read a .imp file.
465  * this is tricky:
466  *	messages can be deleted by another agent
467  *	we might still have a Msg for an expunged message,
468  *		because we haven't told the client yet.
469  *	we can have a Msg without a .imp entry.
470  *	flag information is added at the end of the .imp by copy & append
471  *	there can be duplicate messages (same digests).
472  *
473  * look up existing messages based on uid.
474  * look up new messages based on in order digest matching.
475  *
476  * note: in the face of duplicate messages, one of which is deleted,
477  * two active servers may decide different ones are valid, and so return
478  * different uids for the messages.  this situation will stablize when the servers exit.
479  */
480 static int
481 parseImp(Biobuf *b, Box *box)
482 {
483 	Msg *m, *mm;
484 	char *s, *t, *toks[3];
485 	ulong uid, u;
486 	int match, n;
487 
488 	m = box->msgs;
489 	s = Brdline(b, '\n');
490 	if(s == nil || Blinelen(b) != STRLEN(IMPMAGIC)
491 	|| strncmp(s, IMPMAGIC, STRLEN(IMPMAGIC)) != 0)
492 		return 0;
493 
494 	s = Brdline(b, '\n');
495 	if(s == nil || Blinelen(b) != 2*NUid + 2)
496 		return 0;
497 	s[2*NUid + 1] = '\0';
498 	u = strtoul(s, &t, 10);
499 	if(u != box->uidvalidity && box->uidvalidity != 0)
500 		return 0;
501 	box->uidvalidity = u;
502 	if(*t != ' ' || t != s + NUid)
503 		return 0;
504 	t++;
505 	u = strtoul(t, &t, 10);
506 	if(box->uidnext > u)
507 		return 0;
508 	box->uidnext = u;
509 	if(t != s + 2*NUid+1 || box->uidnext == 0)
510 		return 0;
511 
512 	uid = ~0;
513 	while(m != nil){
514 		s = Brdline(b, '\n');
515 		if(s == nil)
516 			break;
517 		n = Blinelen(b) - 1;
518 		if(n != NDigest + NUid + NFlags + 2
519 		|| s[NDigest] != ' ' || s[NDigest + NUid + 1] != ' ')
520 			return 0;
521 		toks[0] = s;
522 		s[NDigest] = '\0';
523 		toks[1] = s + NDigest + 1;
524 		s[NDigest + NUid + 1] = '\0';
525 		toks[2] = s + NDigest + NUid + 2;
526 		s[n] = '\0';
527 		t = toks[1];
528 		u = strtoul(t, &t, 10);
529 		if(*t != '\0' || uid != ~0 && (uid >= u && u || u && !uid))
530 			return 0;
531 		uid = u;
532 
533 		/*
534 		 * zero uid => added by append or copy, only flags valid
535 		 * can only match messages without uids, but this message
536 		 * may not be the next one, and may have been deleted.
537 		 */
538 		if(!uid){
539 			for(; m != nil && m->uid; m = m->next)
540 				;
541 			for(mm = m; mm != nil; mm = mm->next){
542 				if(strcmp(mm->info[IDigest], toks[0]) == 0){
543 					if(!mm->uid)
544 						mm->flags = 0;
545 					if(!impFlags(box, mm, toks[2]))
546 						return 0;
547 					m = mm->next;
548 					break;
549 				}
550 			}
551 			continue;
552 		}
553 
554 		/*
555 		 * ignore expunged messages,
556 		 * and messages already assigned uids which don't match this uid.
557 		 * such messages must have been deleted by another imap server,
558 		 * which updated the mailbox and .imp file since we read the mailbox,
559 		 * or because upas/fs got confused by consecutive duplicate messages,
560 		 * the first of which was deleted by another imap server.
561 		 */
562 		for(; m != nil && (m->expunged || m->uid && m->uid < uid); m = m->next)
563 			;
564 		if(m == nil)
565 			break;
566 
567 		/*
568 		 * only check for digest match on the next message,
569 		 * since it comes before all other messages, and therefore
570 		 * must be in the .imp file if they should be.
571 		 */
572 		match = strcmp(m->info[IDigest], toks[0]) == 0;
573 		if(uid && (m->uid == uid || !m->uid && match)){
574 			if(!match)
575 				bye("inconsistent uid");
576 
577 			/*
578 			 * wipe out recent flag if some other server saw this new message.
579 			 * it will be read from the .imp file if is really should be set,
580 			 * ie the message was only seen by a status command.
581 			 */
582 			if(!m->uid)
583 				m->flags = 0;
584 
585 			if(!impFlags(box, m, toks[2]))
586 				return 0;
587 			m->uid = uid;
588 			m = m->next;
589 		}
590 	}
591 	return 1;
592 }
593 
594 /*
595  * parse .imp flags
596  */
597 static int
598 impFlags(Box *box, Msg *m, char *flags)
599 {
600 	int i, f;
601 
602 	f = 0;
603 	for(i = 0; i < NFlags; i++){
604 		if(flags[i] == '-')
605 			continue;
606 		if(flags[i] != flagChars[i].name[0])
607 			return 0;
608 		f |= flagChars[i].v;
609 	}
610 
611 	/*
612 	 * recent flags are set until the first time message's box is selected or examined.
613 	 * it may be stored in the file as a side effect of a status or subscribe command;
614 	 * if so, clear it out.
615 	 */
616 	if((f & MRecent) && strcmp(box->fs, "imap") == 0)
617 		box->dirtyImp = 1;
618 	f |= m->flags & MRecent;
619 
620 	/*
621 	 * all old messages with changed flags should be reported to the client
622 	 */
623 	if(m->uid && m->flags != f){
624 		box->sendFlags = 1;
625 		m->sendFlags = 1;
626 	}
627 	m->flags = f;
628 	return 1;
629 }
630 
631 /*
632  * assign uids to any new messages
633  * which aren't already in the .imp file.
634  * sum up totals for flag values.
635  */
636 static void
637 boxFlags(Box *box)
638 {
639 	Msg *m;
640 
641 	box->recent = 0;
642 	for(m = box->msgs; m != nil; m = m->next){
643 		if(m->uid == 0){
644 			box->dirtyImp = 1;
645 			box->uidnext = uidRenumber(m, box->uidnext, 0);
646 		}
647 		if(m->flags & MRecent)
648 			box->recent++;
649 	}
650 }
651 
652 static ulong
653 uidRenumber(Msg *m, ulong uid, int force)
654 {
655 	for(; m != nil; m = m->next){
656 		if(!force && m->uid != 0)
657 			bye("uid renumbering with a valid uid");
658 		m->uid = uid++;
659 	}
660 	return uid;
661 }
662 
663 void
664 closeBox(Box *box, int opened)
665 {
666 	Msg *m, *next;
667 
668 	/*
669 	 * make sure to leave the mailbox directory so upas/fs can close the mailbox
670 	 */
671 	myChdir(mboxDir);
672 
673 	if(box->writable){
674 		deleteMsgs(box);
675 		if(expungeMsgs(box, 0))
676 			closeImp(box, checkBox(box, 1));
677 	}
678 
679 	if(fprint(fsCtl, "close %s", box->fs) < 0 && opened)
680 		bye("can't talk to mail server");
681 	for(m = box->msgs; m != nil; m = next){
682 		next = m->next;
683 		freeMsg(m);
684 	}
685 	free(box->name);
686 	free(box->fs);
687 	free(box->fsDir);
688 	free(box->imp);
689 	free(box);
690 }
691 
692 int
693 deleteMsgs(Box *box)
694 {
695 	Msg *m;
696 	char buf[BufSize], *p, *start;
697 	int ok;
698 
699 	if(!box->writable)
700 		return 0;
701 
702 	/*
703 	 * first pass: delete messages; gang the writes together for speed.
704 	 */
705 	ok = 1;
706 	start = seprint(buf, buf + sizeof(buf), "delete %s", box->fs);
707 	p = start;
708 	for(m = box->msgs; m != nil; m = m->next){
709 		if((m->flags & MDeleted) && !m->expunged){
710 			m->expunged = 1;
711 			p = seprint(p, buf + sizeof(buf), " %lud", m->id);
712 			if(p + 32 >= buf + sizeof(buf)){
713 				if(write(fsCtl, buf, p - buf) < 0)
714 					bye("can't talk to mail server");
715 				p = start;
716 			}
717 		}
718 	}
719 	if(p != start && write(fsCtl, buf, p - buf) < 0)
720 		bye("can't talk to mail server");
721 
722 	return ok;
723 }
724 
725 /*
726  * second pass: remove the message structure,
727  * and renumber message sequence numbers.
728  * update messages counts in mailbox.
729  * returns true if anything changed.
730  */
731 int
732 expungeMsgs(Box *box, int send)
733 {
734 	Msg *m, *next, *last;
735 	ulong n;
736 
737 	n = 0;
738 	last = nil;
739 	for(m = box->msgs; m != nil; m = next){
740 		m->seq -= n;
741 		next = m->next;
742 		if(m->expunged){
743 			if(send)
744 				Bprint(&bout, "* %lud expunge\r\n", m->seq);
745 			if(m->flags & MRecent)
746 				box->recent--;
747 			n++;
748 			if(last == nil)
749 				box->msgs = next;
750 			else
751 				last->next = next;
752 			freeMsg(m);
753 		}else
754 			last = m;
755 	}
756 	if(n){
757 		box->max -= n;
758 		box->dirtyImp = 1;
759 	}
760 	return n;
761 }
762 
763 static void
764 fsInit(void)
765 {
766 	if(fsCtl >= 0)
767 		return;
768 	fsCtl = open("/mail/fs/ctl", ORDWR);
769 	if(fsCtl < 0)
770 		bye("can't open mail file system");
771 	if(fprint(fsCtl, "close mbox") < 0)
772 		bye("can't initialize mail file system");
773 }
774 
775 static char *stoplist[] =
776 {
777 	"mbox",
778 	"pipeto",
779 	"forward",
780 	"names",
781 	0
782 };
783 
784 /*
785  * reject bad mailboxes based on mailbox name
786  */
787 int
788 okMbox(char *path)
789 {
790 	char *name;
791 	int i;
792 
793 	name = strrchr(path, '/');
794 	if(name == nil)
795 		name = path;
796 	else
797 		name++;
798 	if(strlen(name) + STRLEN(".imp") >= MboxNameLen)
799 		return 0;
800 	for(i = 0; stoplist[i]; i++)
801 		if(strcmp(name, stoplist[i]) == 0)
802 			return 0;
803 	if(isprefix("L.", name) || isprefix("imap-tmp.", name)
804 	|| issuffix(".imp", name)
805 	|| strcmp("imap.subscribed", name) == 0
806 	|| isdotdot(name) || name[0] == '/')
807 		return 0;
808 
809 	return 1;
810 
811 }
812