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