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