xref: /plan9-contrib/sys/src/cmd/upas/fs/mbox.c (revision d46c239f8612929b7dbade67d0d071633df3a15d)
1 #include "common.h"
2 #include <ctype.h>
3 #include <plumb.h>
4 #include <libsec.h>
5 #include "dat.h"
6 
7 typedef struct Header Header;
8 
9 struct Header {
10 	char *type;
11 	void (*f)(Message*, Header*, char*);
12 	int len;
13 };
14 
15 /* headers */
16 static	void	ctype(Message*, Header*, char*);
17 static	void	cencoding(Message*, Header*, char*);
18 static	void	cdisposition(Message*, Header*, char*);
19 static	void	date822(Message*, Header*, char*);
20 static	void	from822(Message*, Header*, char*);
21 static	void	to822(Message*, Header*, char*);
22 static	void	sender822(Message*, Header*, char*);
23 static	void	replyto822(Message*, Header*, char*);
24 static	void	subject822(Message*, Header*, char*);
25 static	void	inreplyto822(Message*, Header*, char*);
26 static	void	cc822(Message*, Header*, char*);
27 static	void	bcc822(Message*, Header*, char*);
28 static	void	messageid822(Message*, Header*, char*);
29 static	void	mimeversion(Message*, Header*, char*);
30 static	void	nullsqueeze(Message*);
31 enum
32 {
33 	Mhead=	11,	/* offset of first mime header */
34 };
35 
36 Header head[] =
37 {
38 	{ "date:", date822, },
39 	{ "from:", from822, },
40 	{ "to:", to822, },
41 	{ "sender:", sender822, },
42 	{ "reply-to:", replyto822, },
43 	{ "subject:", subject822, },
44 	{ "cc:", cc822, },
45 	{ "bcc:", bcc822, },
46 	{ "in-reply-to:", inreplyto822, },
47 	{ "mime-version:", mimeversion, },
48 	{ "message-id:", messageid822, },
49 
50 [Mhead]	{ "content-type:", ctype, },
51 	{ "content-transfer-encoding:", cencoding, },
52 	{ "content-disposition:", cdisposition, },
53 	{ 0, },
54 };
55 
56 static	void	fatal(char *fmt, ...);
57 static	void	initquoted(void);
58 static	void	startheader(Message*);
59 static	void	startbody(Message*);
60 static	char*	skipwhite(char*);
61 static	char*	skiptosemi(char*);
62 static	char*	getstring(char*, String*, int);
63 static	void	setfilename(Message*, char*);
64 static	char*	lowercase(char*);
65 static	int	is8bit(Message*);
66 static	int	headerline(char**, String*);
67 static	void	initheaders(void);
68 static void	parseattachments(Message*, Mailbox*);
69 
70 int		debug;
71 char		stdmbox[Pathlen];
72 
73 char *Enotme = "path not served by this file server";
74 
75 enum
76 {
77 	Chunksize = 1024,
78 };
79 
80 Mailboxinit *boxinit[] = {
81 	imap4mbox,
82 	pop3mbox,
83 	plan9mbox,
84 };
85 
86 char*
87 syncmbox(Mailbox *mb, int doplumb)
88 {
89 	return (*mb->sync)(mb, doplumb);
90 }
91 
92 /* create a new mailbox */
93 char*
94 newmbox(char *path, char *name, int std)
95 {
96 	Mailbox *mb, **l;
97 	char *p, *rv;
98 	int i;
99 
100 	initheaders();
101 
102 	if(stdmbox[0] == 0)
103 		snprint(stdmbox, sizeof(stdmbox), "/mail/box/%s/mbox", user);
104 
105 	mb = emalloc(sizeof(*mb));
106 	strncpy(mb->path, path, sizeof(mb->path)-1);
107 	if(name == nil){
108 		p = strrchr(path, '/');
109 		if(p == nil)
110 			p = path;
111 		else
112 			p++;
113 		if(*p == 0){
114 			free(mb);
115 			return "bad mbox name";
116 		}
117 		strncpy(mb->name, p, sizeof(mb->name)-1);
118 	} else {
119 		strncpy(mb->name, name, sizeof(mb->name)-1);
120 	}
121 
122 	rv = nil;
123 	// check for a mailbox type
124 	for(i=0; i<nelem(boxinit); i++)
125 		if((rv = (*boxinit[i])(mb, path)) != Enotme)
126 			break;
127 	if(i == nelem(boxinit)){
128 		free(mb);
129 		return "bad path";
130 	}
131 
132 	// on error, give up
133 	if(rv){
134 		free(mb);
135 		return rv;
136 	}
137 
138 	// make sure name isn't taken
139 	qlock(&mbllock);
140 	for(l = &mbl; *l != nil; l = &(*l)->next){
141 		if(strcmp((*l)->name, mb->name) == 0){
142 			if(strcmp(path, (*l)->path) == 0)
143 				rv = nil;
144 			else
145 				rv = "mbox name in use";
146 			if(mb->close)
147 				(*mb->close)(mb);
148 			free(mb);
149 			qunlock(&mbllock);
150 			return rv;
151 		}
152 	}
153 
154 	// all mailboxes in /mail/box/$user are locked using /mail/box/$user/mbox
155 	p = strrchr(stdmbox, '/');
156 	mb->dolock = strncmp(mb->path, stdmbox, p - stdmbox) == 0;
157 
158 	mb->refs = 1;
159 	mb->next = nil;
160 	mb->id = newid();
161 	mb->root = newmessage(nil);
162 	mb->std = std;
163 	*l = mb;
164 	qunlock(&mbllock);
165 
166 	qlock(mb);
167 	if(mb->ctl){
168 		henter(PATH(mb->id, Qmbox), "ctl",
169 			(Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
170 	}
171 	rv = syncmbox(mb, 0);
172 	qunlock(mb);
173 
174 	return rv;
175 }
176 
177 // close the named mailbox
178 void
179 freembox(char *name)
180 {
181 	Mailbox **l, *mb;
182 
183 	qlock(&mbllock);
184 	for(l=&mbl; *l != nil; l=&(*l)->next){
185 		if(strcmp(name, (*l)->name) == 0){
186 			mb = *l;
187 			*l = mb->next;
188 			mboxdecref(mb);
189 			break;
190 		}
191 	}
192 	hfree(PATH(0, Qtop), name);
193 	qunlock(&mbllock);
194 }
195 
196 static void
197 initheaders(void)
198 {
199 	Header *h;
200 	static int already;
201 
202 	if(already)
203 		return;
204 	already = 1;
205 
206 	for(h = head; h->type != nil; h++)
207 		h->len = strlen(h->type);
208 }
209 
210 /*
211  *  parse a Unix style header
212  */
213 void
214 parseunix(Message *m)
215 {
216 	char *p;
217 	String *h;
218 
219 	h = s_new();
220 	for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
221 		s_putc(h, *p);
222 	s_terminate(h);
223 	s_restart(h);
224 
225 	m->unixfrom = s_parse(h, s_reset(m->unixfrom));
226 	m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
227 
228 	s_free(h);
229 }
230 
231 /*
232  *  parse a message
233  */
234 void
235 parseheaders(Message *m, int justmime, Mailbox *mb)
236 {
237 	String *hl;
238 	Header *h;
239 	char *p, *q;
240 	int i;
241 
242 	if(m->whole == m->whole->whole){
243 		henter(PATH(mb->id, Qmbox), m->name,
244 			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
245 	} else {
246 		henter(PATH(m->whole->id, Qdir), m->name,
247 			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
248 	}
249 	for(i = 0; i < Qmax; i++)
250 		henter(PATH(m->id, Qdir), dirtab[i],
251 			(Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
252 
253 	// parse mime headers
254 	p = m->header;
255 	hl = s_new();
256 	while(headerline(&p, hl)){
257 		if(justmime)
258 			h = &head[Mhead];
259 		else
260 			h = head;
261 		for(; h->type; h++){
262 			if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
263 				(*h->f)(m, h, s_to_c(hl));
264 				break;
265 			}
266 		}
267 		s_reset(hl);
268 	}
269 	s_free(hl);
270 
271 	// the blank line isn't really part of the body or header
272 	if(justmime){
273 		m->mhend = p;
274 		m->hend = m->header;
275 	} else {
276 		m->hend = p;
277 	}
278 	if(*p == '\n')
279 		p++;
280 	m->rbody = m->body = p;
281 
282 	// if type is text, get any nulls out of the body.  This is
283 	// for the two seans and imap clients that get confused.
284 	if(strncmp(s_to_c(m->type), "text/", 5) == 0)
285 		nullsqueeze(m);
286 
287 	//
288 	// cobble together Unix-style from line
289 	// for local mailbox messages, we end up recreating the
290 	// original header.
291 	// for pop3 messages, the best we can do is
292 	// use the From: information and the RFC822 date.
293 	//
294 	if(m->unixdate == nil){
295 		// look for the date in the first Received: line.
296 		// it's likely to be the right time zone (it's
297 	 	// the local system) and in a convenient format.
298 		if(cistrncmp(m->header, "received:", 9)==0){
299 			if((q = strchr(m->header, ';')) != nil){
300 				p = q;
301 				while((p = strchr(p, '\n')) != nil){
302 					if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
303 						break;
304 					p++;
305 				}
306 				if(p){
307 					*p = '\0';
308 					m->unixdate = date822tounix(q+1);
309 					*p = '\n';
310 				}
311 			}
312 		}
313 
314 		// fall back on the rfc822 date
315 		if(m->unixdate==nil && m->date822)
316 			m->unixdate = date822tounix(s_to_c(m->date822));
317 	}
318 
319 	if(m->unixheader != nil)
320 		s_free(m->unixheader);
321 	m->unixheader = s_copy("From ");
322 	if(m->unixfrom)
323 		s_append(m->unixheader, s_to_c(m->unixfrom));
324 	else if(m->from822)
325 		s_append(m->unixheader, s_to_c(m->from822));
326 	else
327 		s_append(m->unixheader, "???");
328 
329 	s_append(m->unixheader, " ");
330 	if(m->unixdate)
331 		s_append(m->unixheader, s_to_c(m->unixdate));
332 	else
333 		s_append(m->unixheader, "Thu Jan  1 00:00:00 EST 1970");
334 
335 	s_append(m->unixheader, "\n");
336 }
337 
338 String*
339 promote(String **sp)
340 {
341 	String *s;
342 
343 	if(*sp != nil)
344 		s = s_clone(*sp);
345 	else
346 		s = nil;
347 	return s;
348 }
349 
350 void
351 parsebody(Message *m, Mailbox *mb)
352 {
353 	Message *nm;
354 
355 	// recurse
356 	if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
357 		parseattachments(m, mb);
358 	} else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
359 		decode(m);
360 		parseattachments(m, mb);
361 		nm = m->part;
362 
363 		// promote headers
364 		if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
365 			m->from822 = promote(&nm->from822);
366 			m->to822 = promote(&nm->to822);
367 			m->date822 = promote(&nm->date822);
368 			m->sender822 = promote(&nm->sender822);
369 			m->replyto822 = promote(&nm->replyto822);
370 			m->subject822 = promote(&nm->subject822);
371 			m->unixdate = promote(&nm->unixdate);
372 		}
373 	}
374 }
375 
376 void
377 parse(Message *m, int justmime, Mailbox *mb)
378 {
379 	parseheaders(m, justmime, mb);
380 	parsebody(m, mb);
381 }
382 
383 static void
384 parseattachments(Message *m, Mailbox *mb)
385 {
386 	Message *nm, **l;
387 	char *p, *x;
388 
389 	// if there's a boundary, recurse...
390 	if(m->boundary != nil){
391 		p = m->body;
392 		nm = nil;
393 		l = &m->part;
394 		for(;;){
395 			x = strstr(p, s_to_c(m->boundary));
396 
397 			/* no boundary, we're done */
398 			if(x == nil){
399 				if(nm != nil)
400 					nm->rbend = nm->bend = nm->end = m->bend;
401 				break;
402 			}
403 
404 			/* boundary must be at the start of a line */
405 			if(x != m->body && *(x-1) != '\n'){
406 				p = x+1;
407 				continue;
408 			}
409 
410 			if(nm != nil)
411 				nm->rbend = nm->bend = nm->end = x;
412 			x += strlen(s_to_c(m->boundary));
413 
414 			/* is this the last part? ignore anything after it */
415 			if(strncmp(x, "--", 2) == 0)
416 				break;
417 
418 			p = strchr(x, '\n');
419 			if(p == nil)
420 				break;
421 			nm = newmessage(m);
422 			nm->start = nm->header = nm->body = nm->rbody = ++p;
423 			nm->mheader = nm->header;
424 			*l = nm;
425 			l = &nm->next;
426 		}
427 		for(nm = m->part; nm != nil; nm = nm->next)
428 			parse(nm, 1, mb);
429 		return;
430 	}
431 
432 	// if we've got an rfc822 message, recurse...
433 	if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
434 		nm = newmessage(m);
435 		m->part = nm;
436 		nm->start = nm->header = nm->body = nm->rbody = m->body;
437 		nm->end = nm->bend = nm->rbend = m->bend;
438 		parse(nm, 0, mb);
439 	}
440 }
441 
442 /*
443  *  pick up a header line
444  */
445 static int
446 headerline(char **pp, String *hl)
447 {
448 	char *p, *x;
449 
450 	s_reset(hl);
451 	p = *pp;
452 	x = strpbrk(p, ":\n");
453 	if(x == nil || *x == '\n')
454 		return 0;
455 	for(;;){
456 		x = strchr(p, '\n');
457 		if(x == nil)
458 			x = p + strlen(p);
459 		s_nappend(hl, p, x-p);
460 		p = x;
461 		if(*p != '\n' || *++p != ' ' && *p != '\t')
462 			break;
463 		while(*p == ' ' || *p == '\t')
464 			p++;
465 		s_putc(hl, ' ');
466 	}
467 	*pp = p;
468 	return 1;
469 }
470 
471 static String*
472 addr822(char *p)
473 {
474 	String *s, *list;
475 	int incomment, addrdone, inanticomment, quoted;
476 	int n;
477 	int c;
478 
479 	list = s_new();
480 	s = s_new();
481 	quoted = incomment = addrdone = inanticomment = 0;
482 	n = 0;
483 	for(; *p; p++){
484 		c = *p;
485 
486 		// whitespace is ignored
487 		if(!quoted && isspace(c) || c == '\r')
488 			continue;
489 
490 		// strings are always treated as atoms
491 		if(!quoted && c == '"'){
492 			if(!addrdone && !incomment)
493 				s_putc(s, c);
494 			for(p++; *p; p++){
495 				if(!addrdone && !incomment)
496 					s_putc(s, *p);
497 				if(!quoted && *p == '"')
498 					break;
499 				if(*p == '\\')
500 					quoted = 1;
501 				else
502 					quoted = 0;
503 			}
504 			if(*p == 0)
505 				break;
506 			quoted = 0;
507 			continue;
508 		}
509 
510 		// ignore everything in an expicit comment
511 		if(!quoted && c == '('){
512 			incomment = 1;
513 			continue;
514 		}
515 		if(incomment){
516 			if(!quoted && c == ')')
517 				incomment = 0;
518 			quoted = 0;
519 			continue;
520 		}
521 
522 		// anticomments makes everything outside of them comments
523 		if(!quoted && c == '<' && !inanticomment){
524 			inanticomment = 1;
525 			s = s_reset(s);
526 			continue;
527 		}
528 		if(!quoted && c == '>' && inanticomment){
529 			addrdone = 1;
530 			inanticomment = 0;
531 			continue;
532 		}
533 
534 		// commas separate addresses
535 		if(!quoted && c == ',' && !inanticomment){
536 			s_terminate(s);
537 			addrdone = 0;
538 			if(n++ != 0)
539 				s_append(list, " ");
540 			s_append(list, s_to_c(s));
541 			s = s_reset(s);
542 			continue;
543 		}
544 
545 		// what's left is part of the address
546 		s_putc(s, c);
547 
548 		// quoted characters are recognized only as characters
549 		if(c == '\\')
550 			quoted = 1;
551 		else
552 			quoted = 0;
553 
554 	}
555 
556 	if(*s_to_c(s) != 0){
557 		s_terminate(s);
558 		if(n++ != 0)
559 			s_append(list, " ");
560 		s_append(list, s_to_c(s));
561 	}
562 	s_free(s);
563 
564 	if(n == 0){
565 		s_free(list);
566 		return nil;
567 	}
568 	return list;
569 }
570 
571 static void
572 to822(Message *m, Header *h, char *p)
573 {
574 	p += strlen(h->type);
575 	s_free(m->to822);
576 	m->to822 = addr822(p);
577 }
578 
579 static void
580 cc822(Message *m, Header *h, char *p)
581 {
582 	p += strlen(h->type);
583 	s_free(m->cc822);
584 	m->cc822 = addr822(p);
585 }
586 
587 static void
588 bcc822(Message *m, Header *h, char *p)
589 {
590 	p += strlen(h->type);
591 	s_free(m->bcc822);
592 	m->bcc822 = addr822(p);
593 }
594 
595 static void
596 from822(Message *m, Header *h, char *p)
597 {
598 	p += strlen(h->type);
599 	s_free(m->from822);
600 	m->from822 = addr822(p);
601 }
602 
603 static void
604 sender822(Message *m, Header *h, char *p)
605 {
606 	p += strlen(h->type);
607 	s_free(m->sender822);
608 	m->sender822 = addr822(p);
609 }
610 
611 static void
612 replyto822(Message *m, Header *h, char *p)
613 {
614 	p += strlen(h->type);
615 	s_free(m->replyto822);
616 	m->replyto822 = addr822(p);
617 }
618 
619 static void
620 mimeversion(Message *m, Header *h, char *p)
621 {
622 	p += strlen(h->type);
623 	s_free(m->mimeversion);
624 	m->mimeversion = addr822(p);
625 }
626 
627 static void
628 killtrailingwhite(char *p)
629 {
630 	char *e;
631 
632 	e = p + strlen(p) - 1;
633 	while(e > p && isspace(*e))
634 		*e-- = 0;
635 }
636 
637 static void
638 date822(Message *m, Header *h, char *p)
639 {
640 	p += strlen(h->type);
641 	p = skipwhite(p);
642 	s_free(m->date822);
643 	m->date822 = s_copy(p);
644 	p = s_to_c(m->date822);
645 	killtrailingwhite(p);
646 }
647 
648 static void
649 subject822(Message *m, Header *h, char *p)
650 {
651 	p += strlen(h->type);
652 	p = skipwhite(p);
653 	s_free(m->subject822);
654 	m->subject822 = s_copy(p);
655 	p = s_to_c(m->subject822);
656 	killtrailingwhite(p);
657 }
658 
659 static void
660 inreplyto822(Message *m, Header *h, char *p)
661 {
662 	p += strlen(h->type);
663 	p = skipwhite(p);
664 	s_free(m->inreplyto822);
665 	m->inreplyto822 = s_copy(p);
666 	p = s_to_c(m->inreplyto822);
667 	killtrailingwhite(p);
668 }
669 
670 static void
671 messageid822(Message *m, Header *h, char *p)
672 {
673 	p += strlen(h->type);
674 	p = skipwhite(p);
675 	s_free(m->messageid822);
676 	m->messageid822 = s_copy(p);
677 	p = s_to_c(m->messageid822);
678 	killtrailingwhite(p);
679 }
680 
681 static int
682 isattribute(char **pp, char *attr)
683 {
684 	char *p;
685 	int n;
686 
687 	n = strlen(attr);
688 	p = *pp;
689 	if(cistrncmp(p, attr, n) != 0)
690 		return 0;
691 	p += n;
692 	while(*p == ' ')
693 		p++;
694 	if(*p++ != '=')
695 		return 0;
696 	while(*p == ' ')
697 		p++;
698 	*pp = p;
699 	return 1;
700 }
701 
702 static void
703 ctype(Message *m, Header *h, char *p)
704 {
705 	String *s;
706 
707 	p += h->len;
708 	p = skipwhite(p);
709 
710 	p = getstring(p, m->type, 1);
711 
712 	while(*p){
713 		if(isattribute(&p, "boundary")){
714 			s = s_new();
715 			p = getstring(p, s, 0);
716 			m->boundary = s_reset(m->boundary);
717 			s_append(m->boundary, "--");
718 			s_append(m->boundary, s_to_c(s));
719 			s_free(s);
720 		} else if(cistrncmp(p, "multipart", 9) == 0){
721 			/*
722 			 *  the first unbounded part of a multipart message,
723 			 *  the preamble, is not displayed or saved
724 			 */
725 		} else if(isattribute(&p, "name")){
726 			if(m->filename == nil)
727 				setfilename(m, p);
728 		} else if(isattribute(&p, "charset")){
729 			p = getstring(p, s_reset(m->charset), 0);
730 		}
731 
732 		p = skiptosemi(p);
733 	}
734 }
735 
736 static void
737 cencoding(Message *m, Header *h, char *p)
738 {
739 	p += h->len;
740 	p = skipwhite(p);
741 	if(cistrncmp(p, "base64", 6) == 0)
742 		m->encoding = Ebase64;
743 	else if(cistrncmp(p, "quoted-printable", 16) == 0)
744 		m->encoding = Equoted;
745 }
746 
747 static void
748 cdisposition(Message *m, Header *h, char *p)
749 {
750 	p += h->len;
751 	p = skipwhite(p);
752 	while(*p){
753 		if(cistrncmp(p, "inline", 6) == 0){
754 			m->disposition = Dinline;
755 		} else if(cistrncmp(p, "attachment", 10) == 0){
756 			m->disposition = Dfile;
757 		} else if(cistrncmp(p, "filename=", 9) == 0){
758 			p += 9;
759 			setfilename(m, p);
760 		}
761 		p = skiptosemi(p);
762 	}
763 
764 }
765 
766 ulong msgallocd, msgfreed;
767 
768 Message*
769 newmessage(Message *parent)
770 {
771 	static int id;
772 	Message *m;
773 
774 	msgallocd++;
775 
776 	m = emalloc(sizeof(*m));
777 	memset(m, 0, sizeof(*m));
778 	m->disposition = Dnone;
779 	m->type = s_copy("text/plain");
780 	m->charset = s_copy("iso-8859-1");
781 	m->id = newid();
782 	if(parent)
783 		sprint(m->name, "%d", ++(parent->subname));
784 	if(parent == nil)
785 		parent = m;
786 	m->whole = parent;
787 	m->hlen = -1;
788 	return m;
789 }
790 
791 // delete a message from a mailbox
792 void
793 delmessage(Mailbox *mb, Message *m)
794 {
795 	Message **l;
796 	int i;
797 
798 	mb->vers++;
799 	msgfreed++;
800 
801 	if(m->whole != m){
802 		// unchain from parent
803 		for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
804 			;
805 		if(*l != nil)
806 			*l = m->next;
807 
808 		// clear out of name lookup hash table
809 		if(m->whole->whole == m->whole)
810 			hfree(PATH(mb->id, Qmbox), m->name);
811 		else
812 			hfree(PATH(m->whole->id, Qdir), m->name);
813 		for(i = 0; i < Qmax; i++)
814 			hfree(PATH(m->id, Qdir), dirtab[i]);
815 	}
816 
817 	/* recurse through sub-parts */
818 	while(m->part)
819 		delmessage(mb, m->part);
820 
821 	/* free memory */
822 	if(m->mallocd)
823 		free(m->start);
824 	if(m->hallocd)
825 		free(m->header);
826 	if(m->ballocd)
827 		free(m->body);
828 	s_free(m->unixfrom);
829 	s_free(m->unixdate);
830 	s_free(m->unixheader);
831 	s_free(m->from822);
832 	s_free(m->sender822);
833 	s_free(m->to822);
834 	s_free(m->bcc822);
835 	s_free(m->cc822);
836 	s_free(m->replyto822);
837 	s_free(m->date822);
838 	s_free(m->inreplyto822);
839 	s_free(m->subject822);
840 	s_free(m->messageid822);
841 	s_free(m->addrs);
842 	s_free(m->mimeversion);
843 	s_free(m->sdigest);
844 	s_free(m->boundary);
845 	s_free(m->type);
846 	s_free(m->charset);
847 	s_free(m->filename);
848 
849 	free(m);
850 }
851 
852 // mark messages (identified by path) for deletion
853 void
854 delmessages(int ac, char **av)
855 {
856 	Mailbox *mb;
857 	Message *m;
858 	int i, needwrite;
859 
860 	qlock(&mbllock);
861 	for(mb = mbl; mb != nil; mb = mb->next)
862 		if(strcmp(av[0], mb->name) == 0){
863 			qlock(mb);
864 			break;
865 		}
866 	qunlock(&mbllock);
867 	if(mb == nil)
868 		return;
869 
870 	needwrite = 0;
871 	for(i = 1; i < ac; i++){
872 		for(m = mb->root->part; m != nil; m = m->next)
873 			if(strcmp(m->name, av[i]) == 0){
874 				if(!m->deleted){
875 					mailplumb(mb, m, 1);
876 					needwrite = 1;
877 					m->deleted = 1;
878 					logmsg("deleting", m);
879 				}
880 				break;
881 			}
882 	}
883 	if(needwrite)
884 		syncmbox(mb, 1);
885 	qunlock(mb);
886 }
887 
888 /*
889  *  the following are called with the mailbox qlocked
890  */
891 void
892 msgincref(Message *m)
893 {
894 	m->refs++;
895 }
896 void
897 msgdecref(Mailbox *mb, Message *m)
898 {
899 	m->refs--;
900 	if(m->refs == 0 && m->deleted)
901 		syncmbox(mb, 1);
902 }
903 
904 /*
905  *  the following are called with mbllock'd
906  */
907 void
908 mboxincref(Mailbox *mb)
909 {
910 	assert(mb->refs > 0);
911 	mb->refs++;
912 }
913 void
914 mboxdecref(Mailbox *mb)
915 {
916 	assert(mb->refs > 0);
917 	qlock(mb);
918 	mb->refs--;
919 	if(mb->refs == 0){
920 		delmessage(mb, mb->root);
921 		if(mb->ctl)
922 			hfree(PATH(mb->id, Qmbox), "ctl");
923 		if(mb->close)
924 			(*mb->close)(mb);
925 		free(mb);
926 	} else
927 		qunlock(mb);
928 }
929 
930 int
931 cistrncmp(char *a, char *b, int n)
932 {
933 	while(n-- > 0){
934 		if(tolower(*a++) != tolower(*b++))
935 			return -1;
936 	}
937 	return 0;
938 }
939 
940 int
941 cistrcmp(char *a, char *b)
942 {
943 	for(;;){
944 		if(tolower(*a) != tolower(*b++))
945 			return -1;
946 		if(*a++ == 0)
947 			break;
948 	}
949 	return 0;
950 }
951 
952 static char*
953 skipwhite(char *p)
954 {
955 	while(isspace(*p))
956 		p++;
957 	return p;
958 }
959 
960 static char*
961 skiptosemi(char *p)
962 {
963 	while(*p && *p != ';')
964 		p++;
965 	while(*p == ';' || isspace(*p))
966 		p++;
967 	return p;
968 }
969 
970 static char*
971 getstring(char *p, String *s, int dolower)
972 {
973 	s = s_reset(s);
974 	p = skipwhite(p);
975 	if(*p == '"'){
976 		p++;
977 		for(;*p && *p != '"'; p++)
978 			if(dolower)
979 				s_putc(s, tolower(*p));
980 			else
981 				s_putc(s, *p);
982 		if(*p == '"')
983 			p++;
984 		s_terminate(s);
985 
986 		return p;
987 	}
988 
989 	for(; *p && !isspace(*p) && *p != ';'; p++)
990 		if(dolower)
991 			s_putc(s, tolower(*p));
992 		else
993 			s_putc(s, *p);
994 	s_terminate(s);
995 
996 	return p;
997 }
998 
999 static void
1000 setfilename(Message *m, char *p)
1001 {
1002 	m->filename = s_reset(m->filename);
1003 	getstring(p, m->filename, 0);
1004 	for(p = s_to_c(m->filename); *p; p++)
1005 		if(*p == ' ' || *p == '\t' || *p == ';')
1006 			*p = '_';
1007 }
1008 
1009 //
1010 // undecode message body
1011 //
1012 void
1013 decode(Message *m)
1014 {
1015 	int i, len;
1016 	char *x;
1017 
1018 	if(m->decoded)
1019 		return;
1020 	switch(m->encoding){
1021 	case Ebase64:
1022 		len = m->bend - m->body;
1023 		i = (len*3)/4+1;	// room for max chars + null
1024 		x = emalloc(i);
1025 		len = dec64((uchar*)x, i, m->body, len);
1026 		if(m->ballocd)
1027 			free(m->body);
1028 		m->body = x;
1029 		m->bend = x + len;
1030 		m->ballocd = 1;
1031 		break;
1032 	case Equoted:
1033 		len = m->bend - m->body;
1034 		x = emalloc(len+2);	// room for null and possible extra nl
1035 		len = decquoted(x, m->body, m->bend);
1036 		if(m->ballocd)
1037 			free(m->body);
1038 		m->body = x;
1039 		m->bend = x + len;
1040 		m->ballocd = 1;
1041 		break;
1042 	default:
1043 		break;
1044 	}
1045 	m->decoded = 1;
1046 }
1047 
1048 // convert latin1 to utf
1049 void
1050 convert(Message *m)
1051 {
1052 	int len;
1053 	char *x;
1054 
1055 	// don't convert if we're not a leaf, not text, or already converted
1056 	if(m->converted)
1057 		return;
1058 	if(m->part != nil)
1059 		return;
1060 	if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
1061 		return;
1062 
1063 	if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 ||
1064 	   cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){
1065 		len = is8bit(m);
1066 		if(len > 0){
1067 			len = 2*len + m->bend - m->body + 1;
1068 			x = emalloc(len);
1069 			len = latin1toutf(x, m->body, m->bend);
1070 			if(m->ballocd)
1071 				free(m->body);
1072 			m->body = x;
1073 			m->bend = x + len;
1074 			m->ballocd = 1;
1075 		}
1076 	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){
1077 		len = xtoutf("8859-2", &x, m->body, m->bend);
1078 		if(len != 0){
1079 			if(m->ballocd)
1080 				free(m->body);
1081 			m->body = x;
1082 			m->bend = x + len;
1083 			m->ballocd = 1;
1084 		}
1085 	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){
1086 		len = xtoutf("8859-15", &x, m->body, m->bend);
1087 		if(len != 0){
1088 			if(m->ballocd)
1089 				free(m->body);
1090 			m->body = x;
1091 			m->bend = x + len;
1092 			m->ballocd = 1;
1093 		}
1094 	} else if(cistrcmp(s_to_c(m->charset), "big5") == 0){
1095 		len = xtoutf("big5", &x, m->body, m->bend);
1096 		if(len != 0){
1097 			if(m->ballocd)
1098 				free(m->body);
1099 			m->body = x;
1100 			m->bend = x + len;
1101 			m->ballocd = 1;
1102 		}
1103 	} else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0
1104 			|| cistrcmp(s_to_c(m->charset), "windows-1252") == 0){
1105 		len = is8bit(m);
1106 		if(len > 0){
1107 			len = 2*len + m->bend - m->body + 1;
1108 			x = emalloc(len);
1109 			len = windows1257toutf(x, m->body, m->bend);
1110 			if(m->ballocd)
1111 				free(m->body);
1112 			m->body = x;
1113 			m->bend = x + len;
1114 			m->ballocd = 1;
1115 		}
1116 	}
1117 
1118 	m->converted = 1;
1119 }
1120 
1121 enum
1122 {
1123 	Self=	1,
1124 	Hex=	2,
1125 };
1126 uchar	tableqp[256];
1127 
1128 static void
1129 initquoted(void)
1130 {
1131 	int c;
1132 
1133 	memset(tableqp, 0, 256);
1134 	for(c = ' '; c <= '<'; c++)
1135 		tableqp[c] = Self;
1136 	for(c = '>'; c <= '~'; c++)
1137 		tableqp[c] = Self;
1138 	tableqp['\t'] = Self;
1139 	tableqp['='] = Hex;
1140 }
1141 
1142 static int
1143 hex2int(int x)
1144 {
1145 	if(x >= '0' && x <= '9')
1146 		return x - '0';
1147 	if(x >= 'A' && x <= 'F')
1148 		return (x - 'A') + 10;
1149 	if(x >= 'a' && x <= 'f')
1150 		return (x - 'a') + 10;
1151 	return 0;
1152 }
1153 
1154 static char*
1155 decquotedline(char *out, char *in, char *e)
1156 {
1157 	int c, soft;
1158 
1159 	/* dump trailing white space */
1160 	while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
1161 		e--;
1162 
1163 	/* trailing '=' means no newline */
1164 	if(*e == '='){
1165 		soft = 1;
1166 		e--;
1167 	} else
1168 		soft = 0;
1169 
1170 	while(in <= e){
1171 		c = (*in++) & 0xff;
1172 		switch(tableqp[c]){
1173 		case Self:
1174 			*out++ = c;
1175 			break;
1176 		case Hex:
1177 			c = hex2int(*in++)<<4;
1178 			c |= hex2int(*in++);
1179 			*out++ = c;
1180 			break;
1181 		}
1182 	}
1183 	if(!soft)
1184 		*out++ = '\n';
1185 	*out = 0;
1186 
1187 	return out;
1188 }
1189 
1190 int
1191 decquoted(char *out, char *in, char *e)
1192 {
1193 	char *p, *nl;
1194 
1195 	if(tableqp[' '] == 0)
1196 		initquoted();
1197 
1198 	p = out;
1199 	while((nl = strchr(in, '\n')) != nil && nl < e){
1200 		p = decquotedline(p, in, nl);
1201 		in = nl + 1;
1202 	}
1203 	if(in < e)
1204 		p = decquotedline(p, in, e-1);
1205 
1206 	// make sure we end with a new line
1207 	if(*(p-1) != '\n'){
1208 		*p++ = '\n';
1209 		*p = 0;
1210 	}
1211 
1212 	return p - out;
1213 }
1214 
1215 static char*
1216 lowercase(char *p)
1217 {
1218 	char *op;
1219 	int c;
1220 
1221 	for(op = p; c = *p; p++)
1222 		if(isupper(c))
1223 			*p = tolower(c);
1224 	return op;
1225 }
1226 
1227 /*
1228  *  return number of 8 bit characters
1229  */
1230 static int
1231 is8bit(Message *m)
1232 {
1233 	int count = 0;
1234 	char *p;
1235 
1236 	for(p = m->body; p < m->bend; p++)
1237 		if(*p & 0x80)
1238 			count++;
1239 	return count;
1240 }
1241 
1242 // translate latin1 directly since it fits neatly in utf
1243 int
1244 latin1toutf(char *out, char *in, char *e)
1245 {
1246 	Rune r;
1247 	char *p;
1248 
1249 	p = out;
1250 	for(; in < e; in++){
1251 		r = (*in) & 0xff;
1252 		p += runetochar(p, &r);
1253 	}
1254 	*p = 0;
1255 	return p - out;
1256 }
1257 
1258 // translate any thing else using the tcs program
1259 int
1260 xtoutf(char *charset, char **out, char *in, char *e)
1261 {
1262 	char *av[4];
1263 	int totcs[2];
1264 	int fromtcs[2];
1265 	int n, len, sofar;
1266 	char *p;
1267 
1268 	len = e-in+1;
1269 	sofar = 0;
1270 	*out = p = malloc(len+1);
1271 	if(p == nil)
1272 		return 0;
1273 
1274 	av[0] = charset;
1275 	av[1] = "-f";
1276 	av[2] = charset;
1277 	av[3] = 0;
1278 	if(pipe(totcs) < 0)
1279 		return 0;
1280 	if(pipe(fromtcs) < 0){
1281 		close(totcs[0]); close(totcs[1]);
1282 		return 0;
1283 	}
1284 	switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
1285 	case -1:
1286 		close(fromtcs[0]); close(fromtcs[1]);
1287 		close(totcs[0]); close(totcs[1]);
1288 		return 0;
1289 	case 0:
1290 		close(fromtcs[0]); close(totcs[1]);
1291 		dup(fromtcs[1], 1);
1292 		dup(totcs[0], 0);
1293 		close(fromtcs[1]); close(totcs[0]);
1294 		dup(open("/dev/null", OWRITE), 2);
1295 		exec("/bin/tcs", av);
1296 		_exits(0);
1297 	default:
1298 		close(fromtcs[1]); close(totcs[0]);
1299 		switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
1300 		case -1:
1301 			close(fromtcs[0]); close(totcs[1]);
1302 			return 0;
1303 		case 0:
1304 			close(fromtcs[0]);
1305 			while(in < e){
1306 				n = write(totcs[1], in, e-in);
1307 				if(n <= 0)
1308 					break;
1309 				in += n;
1310 			}
1311 			close(totcs[1]);
1312 			_exits(0);
1313 		default:
1314 			close(totcs[1]);
1315 			for(;;){
1316 				n = read(fromtcs[0], &p[sofar], len-sofar);
1317 				if(n <= 0)
1318 					break;
1319 				sofar += n;
1320 				p[sofar] = 0;
1321 				if(sofar == len){
1322 					len += 1024;
1323 					*out = p = realloc(p, len+1);
1324 					if(p == nil)
1325 						return 0;
1326 				}
1327 			}
1328 			close(fromtcs[0]);
1329 			break;
1330 		}
1331 		break;
1332 	}
1333 	return sofar;
1334 }
1335 
1336 enum {
1337 	Winstart= 0x7f,
1338 	Winend= 0x9f,
1339 };
1340 
1341 Rune winchars[] = {
1342 	L'•',
1343 	L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡',
1344 	L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•',
1345 	L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—',
1346 	L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ',
1347 };
1348 
1349 int
1350 windows1257toutf(char *out, char *in, char *e)
1351 {
1352 	Rune r;
1353 	char *p;
1354 
1355 	p = out;
1356 	for(; in < e; in++){
1357 		r = (*in) & 0xff;
1358 		if(r >= 0x7f && r <= 0x9f)
1359 			r = winchars[r-0x7f];
1360 		p += runetochar(p, &r);
1361 	}
1362 	*p = 0;
1363 	return p - out;
1364 }
1365 
1366 void *
1367 emalloc(ulong n)
1368 {
1369 	void *p;
1370 
1371 	p = mallocz(n, 1);
1372 	if(!p){
1373 		fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
1374 		exits("out of memory");
1375 	}
1376 	setmalloctag(p, getcallerpc(&n));
1377 	return p;
1378 }
1379 
1380 void *
1381 erealloc(void *p, ulong n)
1382 {
1383 	if(n == 0)
1384 		n = 1;
1385 	p = realloc(p, n);
1386 	if(!p){
1387 		fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
1388 		exits("out of memory");
1389 	}
1390 	setrealloctag(p, getcallerpc(&p));
1391 	return p;
1392 }
1393 
1394 void
1395 mailplumb(Mailbox *mb, Message *m, int delete)
1396 {
1397 	Plumbmsg p;
1398 	Plumbattr a[7];
1399 	char buf[256];
1400 	int ai;
1401 	char lenstr[10], *from, *subject, *date;
1402 	static int fd = -1;
1403 
1404 	if(m->subject822 == nil)
1405 		subject = "";
1406 	else
1407 		subject = s_to_c(m->subject822);
1408 
1409 	if(m->from822 != nil)
1410 		from = s_to_c(m->from822);
1411 	else if(m->unixfrom != nil)
1412 		from = s_to_c(m->unixfrom);
1413 	else
1414 		from = "";
1415 
1416 	if(m->unixdate != nil)
1417 		date = s_to_c(m->unixdate);
1418 	else
1419 		date = "";
1420 
1421 	sprint(lenstr, "%ld", m->end-m->start);
1422 
1423 	if(biffing && !delete)
1424 		print("[ %s / %s / %s ]\n", from, subject, lenstr);
1425 
1426 	if(!plumbing)
1427 		return;
1428 
1429 	if(fd < 0)
1430 		fd = plumbopen("send", OWRITE);
1431 	if(fd < 0)
1432 		return;
1433 
1434 	p.src = "mailfs";
1435 	p.dst = "seemail";
1436 	p.wdir = "/mail/fs";
1437 	p.type = "text";
1438 
1439 	ai = 0;
1440 	a[ai].name = "filetype";
1441 	a[ai].value = "mail";
1442 
1443 	a[++ai].name = "sender";
1444 	a[ai].value = from;
1445 	a[ai-1].next = &a[ai];
1446 
1447 	a[++ai].name = "length";
1448 	a[ai].value = lenstr;
1449 	a[ai-1].next = &a[ai];
1450 
1451 	a[++ai].name = "mailtype";
1452 	a[ai].value = delete?"delete":"new";
1453 	a[ai-1].next = &a[ai];
1454 
1455 	a[++ai].name = "date";
1456 	a[ai].value = date;
1457 	a[ai-1].next = &a[ai];
1458 
1459 	if(m->sdigest){
1460 		a[++ai].name = "digest";
1461 		a[ai].value = s_to_c(m->sdigest);
1462 		a[ai-1].next = &a[ai];
1463 	}
1464 
1465 	a[ai].next = nil;
1466 
1467 	p.attr = a;
1468 	snprint(buf, sizeof(buf), "%s/%s/%s",
1469 		mntpt, mb->name, m->name);
1470 	p.ndata = strlen(buf);
1471 	p.data = buf;
1472 
1473 	plumbsend(fd, &p);
1474 }
1475 
1476 //
1477 // count the number of lines in the body (for imap4)
1478 //
1479 void
1480 countlines(Message *m)
1481 {
1482 	int i;
1483 	char *p;
1484 
1485 	i = 0;
1486 	for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
1487 		i++;
1488 	sprint(m->lines, "%d", i);
1489 }
1490 
1491 char *LOG = "fs";
1492 
1493 void
1494 logmsg(char *s, Message *m)
1495 {
1496 	int pid;
1497 
1498 	if(!logging)
1499 		return;
1500 	pid = getpid();
1501 	if(m == nil)
1502 		syslog(0, LOG, "%s.%d: %s", user, pid, s);
1503 	else
1504 		syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
1505 			user, pid, s,
1506 			m->from822 ? s_to_c(m->from822) : "?",
1507 			s_to_c(m->sdigest));
1508 }
1509 
1510 /*
1511  *  squeeze nulls out of the body
1512  */
1513 static void
1514 nullsqueeze(Message *m)
1515 {
1516 	char *p, *q;
1517 
1518 	for(p = m->body; p < m->end; p = q){
1519 		q = memchr(p, 0, m->end-p);
1520 		if(q == nil)
1521 			break;
1522 		memmove(q, q+1, m->end - q - 1);
1523 		m->end--;
1524 	}
1525 	m->bend = m->rbend = m->bend;
1526 }
1527 
1528 
1529 //
1530 // convert an RFC822 date into a Unix style date
1531 // for when the Unix From line isn't there (e.g. POP3).
1532 // enough client programs depend on having a Unix date
1533 // that it's easiest to write this conversion code once, right here.
1534 //
1535 // people don't follow RFC822 particularly closely,
1536 // so we use strtotm, which is a bunch of heuristics.
1537 //
1538 
1539 extern int strtotm(char*, Tm*);
1540 String*
1541 date822tounix(char *s)
1542 {
1543 	char *p, *q;
1544 	Tm tm;
1545 
1546 	if(strtotm(s, &tm) < 0)
1547 		return nil;
1548 
1549 	p = asctime(&tm);
1550 	if(q = strchr(p, '\n'))
1551 		*q = '\0';
1552 	return s_copy(p);
1553 }
1554 
1555