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