xref: /plan9/sys/src/cmd/upas/fs/mbox.c (revision 4d44ba9b9ee4246ddbd96c7fcaf0918ab92ab35a)
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);
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 	if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 ||
1074 	   cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){
1075 		len = is8bit(m);
1076 		if(len > 0){
1077 			len = 2*len + m->bend - m->body + 1;
1078 			x = emalloc(len);
1079 			len = latin1toutf(x, m->body, m->bend);
1080 			if(m->ballocd)
1081 				free(m->body);
1082 			m->body = x;
1083 			m->bend = x + len;
1084 			m->ballocd = 1;
1085 		}
1086 	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){
1087 		len = xtoutf("8859-2", &x, m->body, m->bend);
1088 		if(len != 0){
1089 			if(m->ballocd)
1090 				free(m->body);
1091 			m->body = x;
1092 			m->bend = x + len;
1093 			m->ballocd = 1;
1094 		}
1095 	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){
1096 		len = xtoutf("8859-15", &x, m->body, m->bend);
1097 		if(len != 0){
1098 			if(m->ballocd)
1099 				free(m->body);
1100 			m->body = x;
1101 			m->bend = x + len;
1102 			m->ballocd = 1;
1103 		}
1104 	} else if(cistrcmp(s_to_c(m->charset), "big5") == 0){
1105 		len = xtoutf("big5", &x, m->body, m->bend);
1106 		if(len != 0){
1107 			if(m->ballocd)
1108 				free(m->body);
1109 			m->body = x;
1110 			m->bend = x + len;
1111 			m->ballocd = 1;
1112 		}
1113 	} else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){
1114 		len = xtoutf("jis", &x, m->body, m->bend);
1115 		if(len != 0){
1116 			if(m->ballocd)
1117 				free(m->body);
1118 			m->body = x;
1119 			m->bend = x + len;
1120 			m->ballocd = 1;
1121 		}
1122 	} else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0
1123 			|| cistrcmp(s_to_c(m->charset), "windows-1252") == 0){
1124 		len = is8bit(m);
1125 		if(len > 0){
1126 			len = 2*len + m->bend - m->body + 1;
1127 			x = emalloc(len);
1128 			len = windows1257toutf(x, m->body, m->bend);
1129 			if(m->ballocd)
1130 				free(m->body);
1131 			m->body = x;
1132 			m->bend = x + len;
1133 			m->ballocd = 1;
1134 		}
1135 	} else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){
1136 		len = xtoutf("cp1251", &x, m->body, m->bend);
1137 		if(len != 0){
1138 			if(m->ballocd)
1139 				free(m->body);
1140 			m->body = x;
1141 			m->bend = x + len;
1142 			m->ballocd = 1;
1143 		}
1144 	} else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){
1145 		len = xtoutf("koi8", &x, m->body, m->bend);
1146 		if(len != 0){
1147 			if(m->ballocd)
1148 				free(m->body);
1149 			m->body = x;
1150 			m->bend = x + len;
1151 			m->ballocd = 1;
1152 		}
1153 	}
1154 
1155 	m->converted = 1;
1156 }
1157 
1158 enum
1159 {
1160 	Self=	1,
1161 	Hex=	2,
1162 };
1163 uchar	tableqp[256];
1164 
1165 static void
1166 initquoted(void)
1167 {
1168 	int c;
1169 
1170 	memset(tableqp, 0, 256);
1171 	for(c = ' '; c <= '<'; c++)
1172 		tableqp[c] = Self;
1173 	for(c = '>'; c <= '~'; c++)
1174 		tableqp[c] = Self;
1175 	tableqp['\t'] = Self;
1176 	tableqp['='] = Hex;
1177 }
1178 
1179 static int
1180 hex2int(int x)
1181 {
1182 	if(x >= '0' && x <= '9')
1183 		return x - '0';
1184 	if(x >= 'A' && x <= 'F')
1185 		return (x - 'A') + 10;
1186 	if(x >= 'a' && x <= 'f')
1187 		return (x - 'a') + 10;
1188 	return 0;
1189 }
1190 
1191 static char*
1192 decquotedline(char *out, char *in, char *e)
1193 {
1194 	int c, soft;
1195 
1196 	/* dump trailing white space */
1197 	while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
1198 		e--;
1199 
1200 	/* trailing '=' means no newline */
1201 	if(*e == '='){
1202 		soft = 1;
1203 		e--;
1204 	} else
1205 		soft = 0;
1206 
1207 	while(in <= e){
1208 		c = (*in++) & 0xff;
1209 		switch(tableqp[c]){
1210 		case Self:
1211 			*out++ = c;
1212 			break;
1213 		case Hex:
1214 			c = hex2int(*in++)<<4;
1215 			c |= hex2int(*in++);
1216 			*out++ = c;
1217 			break;
1218 		}
1219 	}
1220 	if(!soft)
1221 		*out++ = '\n';
1222 	*out = 0;
1223 
1224 	return out;
1225 }
1226 
1227 int
1228 decquoted(char *out, char *in, char *e)
1229 {
1230 	char *p, *nl;
1231 
1232 	if(tableqp[' '] == 0)
1233 		initquoted();
1234 
1235 	p = out;
1236 	while((nl = strchr(in, '\n')) != nil && nl < e){
1237 		p = decquotedline(p, in, nl);
1238 		in = nl + 1;
1239 	}
1240 	if(in < e)
1241 		p = decquotedline(p, in, e-1);
1242 
1243 	// make sure we end with a new line
1244 	if(*(p-1) != '\n'){
1245 		*p++ = '\n';
1246 		*p = 0;
1247 	}
1248 
1249 	return p - out;
1250 }
1251 
1252 static char*
1253 lowercase(char *p)
1254 {
1255 	char *op;
1256 	int c;
1257 
1258 	for(op = p; c = *p; p++)
1259 		if(isupper(c))
1260 			*p = tolower(c);
1261 	return op;
1262 }
1263 
1264 /*
1265  *  return number of 8 bit characters
1266  */
1267 static int
1268 is8bit(Message *m)
1269 {
1270 	int count = 0;
1271 	char *p;
1272 
1273 	for(p = m->body; p < m->bend; p++)
1274 		if(*p & 0x80)
1275 			count++;
1276 	return count;
1277 }
1278 
1279 // translate latin1 directly since it fits neatly in utf
1280 int
1281 latin1toutf(char *out, char *in, char *e)
1282 {
1283 	Rune r;
1284 	char *p;
1285 
1286 	p = out;
1287 	for(; in < e; in++){
1288 		r = (*in) & 0xff;
1289 		p += runetochar(p, &r);
1290 	}
1291 	*p = 0;
1292 	return p - out;
1293 }
1294 
1295 // translate any thing else using the tcs program
1296 int
1297 xtoutf(char *charset, char **out, char *in, char *e)
1298 {
1299 	char *av[4];
1300 	int totcs[2];
1301 	int fromtcs[2];
1302 	int n, len, sofar;
1303 	char *p;
1304 
1305 	len = e-in+1;
1306 	sofar = 0;
1307 	*out = p = malloc(len+1);
1308 	if(p == nil)
1309 		return 0;
1310 
1311 	av[0] = charset;
1312 	av[1] = "-f";
1313 	av[2] = charset;
1314 	av[3] = 0;
1315 	if(pipe(totcs) < 0)
1316 		return 0;
1317 	if(pipe(fromtcs) < 0){
1318 		close(totcs[0]); close(totcs[1]);
1319 		return 0;
1320 	}
1321 	switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
1322 	case -1:
1323 		close(fromtcs[0]); close(fromtcs[1]);
1324 		close(totcs[0]); close(totcs[1]);
1325 		return 0;
1326 	case 0:
1327 		close(fromtcs[0]); close(totcs[1]);
1328 		dup(fromtcs[1], 1);
1329 		dup(totcs[0], 0);
1330 		close(fromtcs[1]); close(totcs[0]);
1331 		dup(open("/dev/null", OWRITE), 2);
1332 		exec("/bin/tcs", av);
1333 		_exits(0);
1334 	default:
1335 		close(fromtcs[1]); close(totcs[0]);
1336 		switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
1337 		case -1:
1338 			close(fromtcs[0]); close(totcs[1]);
1339 			return 0;
1340 		case 0:
1341 			close(fromtcs[0]);
1342 			while(in < e){
1343 				n = write(totcs[1], in, e-in);
1344 				if(n <= 0)
1345 					break;
1346 				in += n;
1347 			}
1348 			close(totcs[1]);
1349 			_exits(0);
1350 		default:
1351 			close(totcs[1]);
1352 			for(;;){
1353 				n = read(fromtcs[0], &p[sofar], len-sofar);
1354 				if(n <= 0)
1355 					break;
1356 				sofar += n;
1357 				p[sofar] = 0;
1358 				if(sofar == len){
1359 					len += 1024;
1360 					*out = p = realloc(p, len+1);
1361 					if(p == nil)
1362 						return 0;
1363 				}
1364 			}
1365 			close(fromtcs[0]);
1366 			break;
1367 		}
1368 		break;
1369 	}
1370 	return sofar;
1371 }
1372 
1373 enum {
1374 	Winstart= 0x7f,
1375 	Winend= 0x9f,
1376 };
1377 
1378 Rune winchars[] = {
1379 	L'•',
1380 	L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡',
1381 	L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•',
1382 	L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—',
1383 	L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ',
1384 };
1385 
1386 int
1387 windows1257toutf(char *out, char *in, char *e)
1388 {
1389 	Rune r;
1390 	char *p;
1391 
1392 	p = out;
1393 	for(; in < e; in++){
1394 		r = (*in) & 0xff;
1395 		if(r >= 0x7f && r <= 0x9f)
1396 			r = winchars[r-0x7f];
1397 		p += runetochar(p, &r);
1398 	}
1399 	*p = 0;
1400 	return p - out;
1401 }
1402 
1403 void *
1404 emalloc(ulong n)
1405 {
1406 	void *p;
1407 
1408 	p = mallocz(n, 1);
1409 	if(!p){
1410 		fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
1411 		exits("out of memory");
1412 	}
1413 	setmalloctag(p, getcallerpc(&n));
1414 	return p;
1415 }
1416 
1417 void *
1418 erealloc(void *p, ulong n)
1419 {
1420 	if(n == 0)
1421 		n = 1;
1422 	p = realloc(p, n);
1423 	if(!p){
1424 		fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
1425 		exits("out of memory");
1426 	}
1427 	setrealloctag(p, getcallerpc(&p));
1428 	return p;
1429 }
1430 
1431 void
1432 mailplumb(Mailbox *mb, Message *m, int delete)
1433 {
1434 	Plumbmsg p;
1435 	Plumbattr a[7];
1436 	char buf[256];
1437 	int ai;
1438 	char lenstr[10], *from, *subject, *date;
1439 	static int fd = -1;
1440 
1441 	if(m->subject822 == nil)
1442 		subject = "";
1443 	else
1444 		subject = s_to_c(m->subject822);
1445 
1446 	if(m->from822 != nil)
1447 		from = s_to_c(m->from822);
1448 	else if(m->unixfrom != nil)
1449 		from = s_to_c(m->unixfrom);
1450 	else
1451 		from = "";
1452 
1453 	if(m->unixdate != nil)
1454 		date = s_to_c(m->unixdate);
1455 	else
1456 		date = "";
1457 
1458 	sprint(lenstr, "%ld", m->end-m->start);
1459 
1460 	if(biffing && !delete)
1461 		print("[ %s / %s / %s ]\n", from, subject, lenstr);
1462 
1463 	if(!plumbing)
1464 		return;
1465 
1466 	if(fd < 0)
1467 		fd = plumbopen("send", OWRITE);
1468 	if(fd < 0)
1469 		return;
1470 
1471 	p.src = "mailfs";
1472 	p.dst = "seemail";
1473 	p.wdir = "/mail/fs";
1474 	p.type = "text";
1475 
1476 	ai = 0;
1477 	a[ai].name = "filetype";
1478 	a[ai].value = "mail";
1479 
1480 	a[++ai].name = "sender";
1481 	a[ai].value = from;
1482 	a[ai-1].next = &a[ai];
1483 
1484 	a[++ai].name = "length";
1485 	a[ai].value = lenstr;
1486 	a[ai-1].next = &a[ai];
1487 
1488 	a[++ai].name = "mailtype";
1489 	a[ai].value = delete?"delete":"new";
1490 	a[ai-1].next = &a[ai];
1491 
1492 	a[++ai].name = "date";
1493 	a[ai].value = date;
1494 	a[ai-1].next = &a[ai];
1495 
1496 	if(m->sdigest){
1497 		a[++ai].name = "digest";
1498 		a[ai].value = s_to_c(m->sdigest);
1499 		a[ai-1].next = &a[ai];
1500 	}
1501 
1502 	a[ai].next = nil;
1503 
1504 	p.attr = a;
1505 	snprint(buf, sizeof(buf), "%s/%s/%s",
1506 		mntpt, mb->name, m->name);
1507 	p.ndata = strlen(buf);
1508 	p.data = buf;
1509 
1510 	plumbsend(fd, &p);
1511 }
1512 
1513 //
1514 // count the number of lines in the body (for imap4)
1515 //
1516 void
1517 countlines(Message *m)
1518 {
1519 	int i;
1520 	char *p;
1521 
1522 	i = 0;
1523 	for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
1524 		i++;
1525 	sprint(m->lines, "%d", i);
1526 }
1527 
1528 char *LOG = "fs";
1529 
1530 void
1531 logmsg(char *s, Message *m)
1532 {
1533 	int pid;
1534 
1535 	if(!logging)
1536 		return;
1537 	pid = getpid();
1538 	if(m == nil)
1539 		syslog(0, LOG, "%s.%d: %s", user, pid, s);
1540 	else
1541 		syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
1542 			user, pid, s,
1543 			m->from822 ? s_to_c(m->from822) : "?",
1544 			s_to_c(m->sdigest));
1545 }
1546 
1547 /*
1548  *  squeeze nulls out of the body
1549  */
1550 static void
1551 nullsqueeze(Message *m)
1552 {
1553 	char *p, *q;
1554 
1555 	q = memchr(m->body, 0, m->end-m->body);
1556 	if(q == nil)
1557 		return;
1558 
1559 	for(p = m->body; q < m->end; q++){
1560 		if(*q == 0)
1561 			continue;
1562 		*p++ = *q;
1563 	}
1564 	m->bend = m->rbend = m->end = p;
1565 }
1566 
1567 
1568 //
1569 // convert an RFC822 date into a Unix style date
1570 // for when the Unix From line isn't there (e.g. POP3).
1571 // enough client programs depend on having a Unix date
1572 // that it's easiest to write this conversion code once, right here.
1573 //
1574 // people don't follow RFC822 particularly closely,
1575 // so we use strtotm, which is a bunch of heuristics.
1576 //
1577 
1578 extern int strtotm(char*, Tm*);
1579 String*
1580 date822tounix(char *s)
1581 {
1582 	char *p, *q;
1583 	Tm tm;
1584 
1585 	if(strtotm(s, &tm) < 0)
1586 		return nil;
1587 
1588 	p = asctime(&tm);
1589 	if(q = strchr(p, '\n'))
1590 		*q = '\0';
1591 	return s_copy(p);
1592 }
1593 
1594