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