xref: /plan9/sys/src/cmd/upas/marshal/marshal.c (revision 41dd6b4775bcffc7275c15aee7294944759a2ea7)
1 /*
2  * marshal - gather mail message for transmission
3  */
4 #include "common.h"
5 #include <ctype.h>
6 
7 typedef struct Attach Attach;
8 typedef struct Alias Alias;
9 typedef struct Addr Addr;
10 typedef struct Ctype Ctype;
11 
12 struct Attach {
13 	Attach	*next;
14 	char	*path;
15 	char	*type;
16 	int	ainline;
17 	Ctype	*ctype;
18 };
19 
20 struct Alias
21 {
22 	Alias	*next;
23 	int	n;
24 	Addr	*addr;
25 };
26 
27 struct Addr
28 {
29 	Addr	*next;
30 	char	*v;
31 };
32 
33 enum {
34 	Hfrom,
35 	Hto,
36 	Hcc,
37 	Hbcc,
38 	Hsender,
39 	Hreplyto,
40 	Hinreplyto,
41 	Hdate,
42 	Hsubject,
43 	Hmime,
44 	Hpriority,
45 	Hmsgid,
46 	Hcontent,
47 	Hx,
48 	Hprecedence,
49 	Nhdr,
50 };
51 
52 enum {
53 	PGPsign = 1,
54 	PGPencrypt = 2,
55 };
56 
57 char *hdrs[Nhdr] = {
58 [Hfrom]		"from:",
59 [Hto]		"to:",
60 [Hcc]		"cc:",
61 [Hbcc]		"bcc:",
62 [Hreplyto]	"reply-to:",
63 [Hinreplyto]	"in-reply-to:",
64 [Hsender]	"sender:",
65 [Hdate]		"date:",
66 [Hsubject]	"subject:",
67 [Hpriority]	"priority:",
68 [Hmsgid]	"message-id:",
69 [Hmime]		"mime-",
70 [Hcontent]	"content-",
71 [Hx]		"x-",
72 [Hprecedence]	"precedence",
73 };
74 
75 struct Ctype {
76 	char	*type;
77 	char 	*ext;
78 	int	display;
79 };
80 
81 Ctype ctype[] = {
82 	{ "text/plain",			"txt",	1,	},
83 	{ "text/html",			"html",	1,	},
84 	{ "text/html",			"htm",	1,	},
85 	{ "text/tab-separated-values",	"tsv",	1,	},
86 	{ "text/richtext",		"rtx",	1,	},
87 	{ "message/rfc822",		"txt",	1,	},
88 	{ "", 				0,	0,	},
89 };
90 
91 Ctype *mimetypes;
92 
93 int pid = -1;
94 int pgppid = -1;
95 
96 void	Bdrain(Biobuf*);
97 void	attachment(Attach*, Biobuf*);
98 void	body(Biobuf*, Biobuf*, int);
99 int	cistrcmp(char*, char*);
100 int	cistrncmp(char*, char*, int);
101 int	doublequote(Fmt*);
102 void*	emalloc(int);
103 int	enc64(char*, int, uchar*, int);
104 void*	erealloc(void*, int);
105 char*	estrdup(char*);
106 Addr*	expand(int, char**);
107 Addr*	expandline(String**, Addr*);
108 void	freeaddr(Addr*);
109 void	freeaddr(Addr *);
110 void	freeaddrs(Addr*);
111 void	freealias(Alias*);
112 void	freealiases(Alias*);
113 Attach*	mkattach(char*, char*, int);
114 char*	mkboundary(void);
115 char*	mksubject(char*);
116 int	pgpfilter(int*, int, int);
117 int	pgpopts(char*);
118 int	printcc(Biobuf*, Addr*);
119 int	printdate(Biobuf*);
120 int	printfrom(Biobuf*);
121 int	printinreplyto(Biobuf*, char*);
122 int	printsubject(Biobuf*, char*);
123 int	printto(Biobuf*, Addr*);
124 Alias*	readaliases(void);
125 int	readheaders(Biobuf*, int*, String**, Addr**, int);
126 void	readmimetypes(void);
127 int	rfc2047fmt(Fmt*);
128 int	sendmail(Addr*, Addr*, int*, char*);
129 char*	waitforsubprocs(void);
130 
131 int rflag, lbflag, xflag, holding, nflag, Fflag, eightflag, dflag;
132 int pgpflag = 0;
133 char *user;
134 char *login;
135 Alias *aliases;
136 int rfc822syntaxerror;
137 char lastchar;
138 char *replymsg;
139 
140 enum
141 {
142 	Ok = 0,
143 	Nomessage = 1,
144 	Nobody = 2,
145 	Error = -1,
146 };
147 
148 #pragma varargck	type	"Z"	char*
149 #pragma varargck	type	"U"	char*
150 
151 void
usage(void)152 usage(void)
153 {
154 	fprint(2, "usage: %s [-Fr#xn] [-s subject] [-c ccrecipient] [-t type]"
155 	    " [-aA attachment] [-p[es]] [-R replymsg] -8 | recipient-list\n",
156 		argv0);
157 	exits("usage");
158 }
159 
160 void
fatal(char * fmt,...)161 fatal(char *fmt, ...)
162 {
163 	char buf[1024];
164 	va_list arg;
165 
166 	if(pid >= 0)
167 		postnote(PNPROC, pid, "die");
168 	if(pgppid >= 0)
169 		postnote(PNPROC, pgppid, "die");
170 
171 	va_start(arg, fmt);
172 	vseprint(buf, buf+sizeof(buf), fmt, arg);
173 	va_end(arg);
174 	fprint(2, "%s: %s\n", argv0, buf);
175 	holdoff(holding);
176 	exits(buf);
177 }
178 
179 static void
bwritesfree(Biobuf * bp,String ** str)180 bwritesfree(Biobuf *bp, String **str)
181 {
182 	if(Bwrite(bp, s_to_c(*str), s_len(*str)) != s_len(*str))
183 		fatal("write error");
184 	s_free(*str);
185 	*str = nil;
186 }
187 
188 void
main(int argc,char ** argv)189 main(int argc, char **argv)
190 {
191 	int ccargc, flags, fd, noinput, headersrv;
192 	char *subject, *type, *boundary;
193 	char *ccargv[32];
194 	Addr *cc, *to;
195 	Attach *first, **l, *a;
196 	Biobuf in, out, *b;
197 	String *file, *hdrstring;
198 
199 	noinput = 0;
200 	subject = nil;
201 	first = nil;
202 	l = &first;
203 	type = nil;
204 	hdrstring = nil;
205 	ccargc = 0;
206 
207 	quotefmtinstall();
208 	fmtinstall('Z', doublequote);
209 	fmtinstall('U', rfc2047fmt);
210 
211 	ARGBEGIN{
212 	case 'a':
213 		flags = 0;
214 		goto aflag;
215 	case 'A':
216 		flags = 1;
217 	aflag:
218 		a = mkattach(EARGF(usage()), type, flags);
219 		if(a == nil)
220 			exits("bad args");
221 		type = nil;
222 		*l = a;
223 		l = &a->next;
224 		break;
225 	case 'C':
226 		if(ccargc >= nelem(ccargv)-1)
227 			sysfatal("too many cc's");
228 		ccargv[ccargc++] = EARGF(usage());
229 		break;
230 	case 'd':
231 		dflag = 1;		/* for sendmail */
232 		break;
233 	case 'F':
234 		Fflag = 1;		/* file message */
235 		break;
236 	case 'n':			/* no standard input */
237 		nflag = 1;
238 		break;
239 	case 'p':			/* pgp flag: encrypt, sign, or both */
240 		if(pgpopts(EARGF(usage())) < 0)
241 			sysfatal("bad pgp options");
242 		break;
243 	case 'r':
244 		rflag = 1;		/* for sendmail */
245 		break;
246 	case 'R':
247 		replymsg = EARGF(usage());
248 		break;
249 	case 's':
250 		subject = EARGF(usage());
251 		break;
252 	case 't':
253 		type = EARGF(usage());
254 		break;
255 	case 'x':
256 		xflag = 1;		/* for sendmail */
257 		break;
258 	case '8':			/* read recipients from rfc822 header */
259 		eightflag = 1;
260 		break;
261 	case '#':
262 		lbflag = 1;		/* for sendmail */
263 		break;
264 	default:
265 		usage();
266 		break;
267 	}ARGEND;
268 
269 	login = getlog();
270 	user = getenv("upasname");
271 	if(user == nil || *user == 0)
272 		user = login;
273 	if(user == nil || *user == 0)
274 		sysfatal("can't read user name");
275 
276 	if(Binit(&in, 0, OREAD) < 0)
277 		sysfatal("can't Binit 0: %r");
278 
279 	if(nflag && eightflag)
280 		sysfatal("can't use both -n and -8");
281 	if(eightflag && argc >= 1)
282 		usage();
283 	else if(!eightflag && argc < 1)
284 		usage();
285 
286 	aliases = readaliases();
287 	if(!eightflag){
288 		to = expand(argc, argv);
289 		cc = expand(ccargc, ccargv);
290 	} else
291 		to = cc = nil;
292 
293 	flags = 0;
294 	headersrv = Nomessage;
295 	if(!nflag && !xflag && !lbflag &&!dflag) {
296 		/*
297 		 * pass through headers, keeping track of which we've seen,
298 		 * perhaps building to list.
299 		 */
300 		holding = holdon();
301 		headersrv = readheaders(&in, &flags, &hdrstring,
302 			eightflag? &to: nil, 1);
303 		if(rfc822syntaxerror){
304 			Bdrain(&in);
305 			fatal("rfc822 syntax error, message not sent");
306 		}
307 		if(to == nil){
308 			Bdrain(&in);
309 			fatal("no addresses found, message not sent");
310 		}
311 
312 		switch(headersrv){
313 		case Error:			/* error */
314 			fatal("reading");
315 			break;
316 		case Nomessage:	/* no message, just exit mimicking old behavior */
317 			noinput = 1;
318 			if(first == nil)
319 				exits(0);
320 			break;
321 		}
322 	}
323 
324 	fd = sendmail(to, cc, &pid, Fflag ? argv[0] : nil);
325 	if(fd < 0)
326 		sysfatal("execing sendmail: %r\n:");
327 	if(xflag || lbflag || dflag){
328 		close(fd);
329 		exits(waitforsubprocs());
330 	}
331 
332 	if(Binit(&out, fd, OWRITE) < 0)
333 		fatal("can't Binit 1: %r");
334 
335 	if(!nflag)
336 		bwritesfree(&out, &hdrstring);
337 
338 	/* read user's standard headers */
339 	file = s_new();
340 	mboxpath("headers", user, file, 0);
341 	b = Bopen(s_to_c(file), OREAD);
342 	if(b != nil){
343 		if (readheaders(b, &flags, &hdrstring, nil, 0) == Error)
344 			fatal("reading");
345 		Bterm(b);
346 		bwritesfree(&out, &hdrstring);
347 	}
348 
349 	/* add any headers we need */
350 	if((flags & (1<<Hdate)) == 0)
351 		if(printdate(&out) < 0)
352 			fatal("writing");
353 	if((flags & (1<<Hfrom)) == 0)
354 		if(printfrom(&out) < 0)
355 			fatal("writing");
356 	if((flags & (1<<Hto)) == 0)
357 		if(printto(&out, to) < 0)
358 			fatal("writing");
359 	if((flags & (1<<Hcc)) == 0)
360 		if(printcc(&out, cc) < 0)
361 			fatal("writing");
362 	if((flags & (1<<Hsubject)) == 0 && subject != nil)
363 		if(printsubject(&out, subject) < 0)
364 			fatal("writing");
365 	if(replymsg != nil)
366 		if(printinreplyto(&out, replymsg) < 0)
367 			fatal("writing");
368 	Bprint(&out, "MIME-Version: 1.0\n");
369 
370 	if(pgpflag){
371 		/* interpose pgp process between us and sendmail to handle body */
372 		Bflush(&out);
373 		Bterm(&out);
374 		fd = pgpfilter(&pgppid, fd, pgpflag);
375 		if(Binit(&out, fd, OWRITE) < 0)
376 			fatal("can't Binit 1: %r");
377 	}
378 
379 	/* if attachments, stick in multipart headers */
380 	boundary = nil;
381 	if(first != nil){
382 		boundary = mkboundary();
383 		Bprint(&out, "Content-Type: multipart/mixed;\n");
384 		Bprint(&out, "\tboundary=\"%s\"\n\n", boundary);
385 		Bprint(&out, "This is a multi-part message in MIME format.\n");
386 		Bprint(&out, "--%s\n", boundary);
387 		Bprint(&out, "Content-Disposition: inline\n");
388 	}
389 
390 	if(!nflag){
391 		if(!noinput && headersrv == Ok)
392 			body(&in, &out, 1);
393 	} else
394 		Bprint(&out, "\n");
395 	holdoff(holding);
396 
397 	Bflush(&out);
398 	for(a = first; a != nil; a = a->next){
399 		if(lastchar != '\n')
400 			Bprint(&out, "\n");
401 		Bprint(&out, "--%s\n", boundary);
402 		attachment(a, &out);
403 	}
404 
405 	if(first != nil){
406 		if(lastchar != '\n')
407 			Bprint(&out, "\n");
408 		Bprint(&out, "--%s--\n", boundary);
409 	}
410 
411 	Bterm(&out);
412 	close(fd);
413 	exits(waitforsubprocs());
414 }
415 
416 /* evaluate pgp option string */
417 int
pgpopts(char * s)418 pgpopts(char *s)
419 {
420 	if(s == nil || s[0] == '\0')
421 		return -1;
422 	while(*s){
423 		switch(*s++){
424 		case 's':  case 'S':
425 			pgpflag |= PGPsign;
426 			break;
427 		case 'e': case 'E':
428 			pgpflag |= PGPencrypt;
429 			break;
430 		default:
431 			return -1;
432 		}
433 	}
434 	return 0;
435 }
436 
437 /*
438  * read headers from stdin into a String, expanding local aliases,
439  * keep track of which headers are there, which addresses we have
440  * remove Bcc: line.
441  */
442 int
readheaders(Biobuf * in,int * fp,String ** sp,Addr ** top,int strict)443 readheaders(Biobuf *in, int *fp, String **sp, Addr **top, int strict)
444 {
445 	int i, seen, hdrtype;
446 	char *p;
447 	Addr *to;
448 	String *s, *sline;
449 
450 	s = s_new();
451 	sline = nil;
452 	to = nil;
453 	hdrtype = -1;
454 	seen = 0;
455 	for(;;) {
456 		if((p = Brdline(in, '\n')) != nil) {
457 			seen = 1;
458 			p[Blinelen(in)-1] = 0;
459 
460 			/* coalesce multiline headers */
461 			if((*p == ' ' || *p == '\t') && sline){
462 				s_append(sline, "\n");
463 				s_append(sline, p);
464 				p[Blinelen(in)-1] = '\n';
465 				continue;
466 			}
467 		}
468 
469 		/* process the current header, it's all been read */
470 		if(sline) {
471 			assert(hdrtype != -1);
472 			if(top){
473 				switch(hdrtype){
474 				case Hto:
475 				case Hcc:
476 				case Hbcc:
477 					to = expandline(&sline, to);
478 					break;
479 				}
480 			}
481 			if(hdrtype == Hsubject){
482 				s_append(s, mksubject(s_to_c(sline)));
483 				s_append(s, "\n");
484 			}else if(top==nil || hdrtype!=Hbcc){
485 				s_append(s, s_to_c(sline));
486 				s_append(s, "\n");
487 			}
488 			s_free(sline);
489 			sline = nil;
490 		}
491 
492 		if(p == nil)
493 			break;
494 
495 		/* if no :, it's not a header, seek back and break */
496 		if(strchr(p, ':') == nil){
497 			p[Blinelen(in)-1] = '\n';
498 			Bseek(in, -Blinelen(in), 1);
499 			break;
500 		}
501 
502 		sline = s_copy(p);
503 
504 		/*
505 		 * classify the header.  If we don't recognize it, break.
506 		 * This is to take care of users who start messages with
507 		 * lines that contain ':'s but that aren't headers.
508 		 * This is a bit hokey.  Since I decided to let users type
509 		 * headers, I need some way to distinguish.  Therefore,
510 		 * marshal tries to know all likely headers and will indeed
511 		 * screw up if the user types an unlikely one.  -- presotto
512 		 */
513 		hdrtype = -1;
514 		for(i = 0; i < nelem(hdrs); i++){
515 			if(cistrncmp(hdrs[i], p, strlen(hdrs[i])) == 0){
516 				*fp |= 1<<i;
517 				hdrtype = i;
518 				break;
519 			}
520 		}
521 		if(strict){
522 			if(hdrtype == -1){
523 				p[Blinelen(in)-1] = '\n';
524 				Bseek(in, -Blinelen(in), 1);
525 				break;
526 			}
527 		} else
528 			hdrtype = 0;
529 		p[Blinelen(in)-1] = '\n';
530 	}
531 
532 	*sp = s;
533 	if(top)
534 		*top = to;
535 
536 	if(seen == 0){
537 		if(Blinelen(in) == 0)
538 			return Nomessage;
539 		else
540 			return Ok;
541 	}
542 	if(p == nil)
543 		return Nobody;
544 	return Ok;
545 }
546 
547 /* pass the body to sendmail, make sure body starts and ends with a newline */
548 void
body(Biobuf * in,Biobuf * out,int docontenttype)549 body(Biobuf *in, Biobuf *out, int docontenttype)
550 {
551 	char *buf, *p;
552 	int i, n, len;
553 
554 	n = 0;
555 	len = 16*1024;
556 	buf = emalloc(len);
557 
558 	/* first char must be newline */
559 	i = Bgetc(in);
560 	if(i > 0){
561 		if(i != '\n')
562 			buf[n++] = '\n';
563 		buf[n++] = i;
564 	} else
565 		buf[n++] = '\n';
566 
567 	/* read into memory */
568 	if(docontenttype){
569 		while(docontenttype){
570 			if(n == len){
571 				len += len >> 2;
572 				buf = realloc(buf, len);
573 				if(buf == nil)
574 					sysfatal("%r");
575 			}
576 			p = buf+n;
577 			i = Bread(in, p, len - n);
578 			if(i < 0)
579 				fatal("input error2");
580 			if(i == 0)
581 				break;
582 			n += i;
583 			for(; i > 0; i--)
584 				if((*p++ & 0x80) && docontenttype){
585 					Bprint(out, "Content-Type: text/plain; charset=\"UTF-8\"\n");
586 					Bprint(out, "Content-Transfer-Encoding: 8bit\n");
587 					docontenttype = 0;
588 					break;
589 				}
590 		}
591 		if(docontenttype){
592 			Bprint(out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
593 			Bprint(out, "Content-Transfer-Encoding: 7bit\n");
594 		}
595 	}
596 
597 	/* write what we already read */
598 	if(Bwrite(out, buf, n) < 0)
599 		fatal("output error");
600 	if(n > 0)
601 		lastchar = buf[n-1];
602 	else
603 		lastchar = '\n';
604 
605 
606 	/* pass the rest */
607 	for(;;){
608 		n = Bread(in, buf, len);
609 		if(n < 0)
610 			fatal("input error2");
611 		if(n == 0)
612 			break;
613 		if(Bwrite(out, buf, n) < 0)
614 			fatal("output error");
615 		lastchar = buf[n-1];
616 	}
617 }
618 
619 /*
620  * pass the body to sendmail encoding with base64
621  *
622  *  the size of buf is very important to enc64.  Anything other than
623  *  a multiple of 3 will cause enc64 to output a termination sequence.
624  *  To ensure that a full buf corresponds to a multiple of complete lines,
625  *  we make buf a multiple of 3*18 since that's how many enc64 sticks on
626  *  a single line.  This avoids short lines in the output which is pleasing
627  *  but not necessary.
628  */
629 void
body64(Biobuf * in,Biobuf * out)630 body64(Biobuf *in, Biobuf *out)
631 {
632 	int m, n;
633 	uchar buf[3*18*54];
634 	char obuf[3*18*54*2];
635 
636 	Bprint(out, "\n");
637 	for(;;){
638 		n = Bread(in, buf, sizeof(buf));
639 		if(n < 0)
640 			fatal("input error");
641 		if(n == 0)
642 			break;
643 		m = enc64(obuf, sizeof(obuf), buf, n);
644 		if(Bwrite(out, obuf, m) < 0)
645 			fatal("output error");
646 	}
647 	lastchar = '\n';
648 }
649 
650 /* pass message to sendmail, make sure body starts with a newline */
651 void
copy(Biobuf * in,Biobuf * out)652 copy(Biobuf *in, Biobuf *out)
653 {
654 	int n;
655 	char buf[4*1024];
656 
657 	for(;;){
658 		n = Bread(in, buf, sizeof(buf));
659 		if(n < 0)
660 			fatal("input error");
661 		if(n == 0)
662 			break;
663 		if(Bwrite(out, buf, n) < 0)
664 			fatal("output error");
665 	}
666 }
667 
668 void
attachment(Attach * a,Biobuf * out)669 attachment(Attach *a, Biobuf *out)
670 {
671 	Biobuf *f;
672 	char *p;
673 
674 	/* if it's already mime encoded, just copy */
675 	if(strcmp(a->type, "mime") == 0){
676 		f = Bopen(a->path, OREAD);
677 		if(f == nil){
678 			/*
679 			 * hack: give marshal time to stdin, before we kill it
680 			 * (for dead.letter)
681 			 */
682 			sleep(500);
683 			postnote(PNPROC, pid, "interrupt");
684 			sysfatal("opening %s: %r", a->path);
685 		}
686 		copy(f, out);
687 		Bterm(f);
688 	}
689 
690 	/* if it's not already mime encoded ... */
691 	if(strcmp(a->type, "text/plain") != 0)
692 		Bprint(out, "Content-Type: %s\n", a->type);
693 
694 	if(a->ainline)
695 		Bprint(out, "Content-Disposition: inline\n");
696 	else {
697 		p = strrchr(a->path, '/');
698 		if(p == nil)
699 			p = a->path;
700 		else
701 			p++;
702 		Bprint(out, "Content-Disposition: attachment; filename=%Z\n", p);
703 	}
704 
705 	f = Bopen(a->path, OREAD);
706 	if(f == nil){
707 		/*
708 		 * hack: give marshal time to stdin, before we kill it
709 		 * (for dead.letter)
710 		 */
711 		sleep(500);
712 		postnote(PNPROC, pid, "interrupt");
713 		sysfatal("opening %s: %r", a->path);
714 	}
715 
716 	/* dump our local 'From ' line when passing along mail messages */
717 	if(strcmp(a->type, "message/rfc822") == 0){
718 		p = Brdline(f, '\n');
719 		if(strncmp(p, "From ", 5) != 0)
720 			Bseek(f, 0, 0);
721 	}
722 	if(a->ctype->display)
723 		body(f, out, strcmp(a->type, "text/plain") == 0);
724 	else {
725 		Bprint(out, "Content-Transfer-Encoding: base64\n");
726 		body64(f, out);
727 	}
728 	Bterm(f);
729 }
730 
731 char *ascwday[] =
732 {
733 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
734 };
735 
736 char *ascmon[] =
737 {
738 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
739 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
740 };
741 
742 int
printdate(Biobuf * b)743 printdate(Biobuf *b)
744 {
745 	int tz;
746 	Tm *tm;
747 
748 	tm = localtime(time(0));
749 	tz = (tm->tzoff/3600)*100 + (tm->tzoff/60)%60;
750 
751 	return Bprint(b, "Date: %s, %d %s %d %2.2d:%2.2d:%2.2d %s%.4d\n",
752 		ascwday[tm->wday], tm->mday, ascmon[tm->mon], 1900 + tm->year,
753 		tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz);
754 }
755 
756 int
printfrom(Biobuf * b)757 printfrom(Biobuf *b)
758 {
759 	return Bprint(b, "From: %s\n", user);
760 }
761 
762 int
printto(Biobuf * b,Addr * a)763 printto(Biobuf *b, Addr *a)
764 {
765 	int i;
766 
767 	if(Bprint(b, "To: %s", a->v) < 0)
768 		return -1;
769 	i = 0;
770 	for(a = a->next; a != nil; a = a->next)
771 		if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
772 			return -1;
773 	if(Bprint(b, "\n") < 0)
774 		return -1;
775 	return 0;
776 }
777 
778 int
printcc(Biobuf * b,Addr * a)779 printcc(Biobuf *b, Addr *a)
780 {
781 	int i;
782 
783 	if(a == nil)
784 		return 0;
785 	if(Bprint(b, "CC: %s", a->v) < 0)
786 		return -1;
787 	i = 0;
788 	for(a = a->next; a != nil; a = a->next)
789 		if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
790 			return -1;
791 	if(Bprint(b, "\n") < 0)
792 		return -1;
793 	return 0;
794 }
795 
796 int
printsubject(Biobuf * b,char * subject)797 printsubject(Biobuf *b, char *subject)
798 {
799 	return Bprint(b, "Subject: %U\n", subject);
800 }
801 
802 int
printinreplyto(Biobuf * out,char * dir)803 printinreplyto(Biobuf *out, char *dir)
804 {
805 	int fd, n;
806 	char buf[256];
807 	String *s = s_copy(dir);
808 
809 	s_append(s, "/messageid");
810 	fd = open(s_to_c(s), OREAD);
811 	s_free(s);
812 	if(fd < 0)
813 		return 0;
814 	n = read(fd, buf, sizeof(buf)-1);
815 	close(fd);
816 	if(n <= 0)
817 		return 0;
818 	buf[n] = 0;
819 	return Bprint(out, "In-Reply-To: %s\n", buf);
820 }
821 
822 Attach*
mkattach(char * file,char * type,int ainline)823 mkattach(char *file, char *type, int ainline)
824 {
825 	int n, pfd[2];
826 	char *p;
827 	char ftype[64];
828 	Attach *a;
829 	Ctype *c;
830 
831 	if(file == nil)
832 		return nil;
833 	if(access(file, 4) == -1){
834 		fprint(2, "%s: %s can't read file\n", argv0, file);
835 		return nil;
836 	}
837 	a = emalloc(sizeof(*a));
838 	a->path = file;
839 	a->next = nil;
840 	a->type = type;
841 	a->ainline = ainline;
842 	a->ctype = nil;
843 	if(type != nil){
844 		for(c = ctype; ; c++)
845 			if(strncmp(type, c->type, strlen(c->type)) == 0){
846 				a->ctype = c;
847 				break;
848 			}
849 		return a;
850 	}
851 
852 	/* pick a type depending on extension */
853 	p = strchr(file, '.');
854 	if(p != nil)
855 		p++;
856 
857 	/* check the builtin extensions */
858 	if(p != nil){
859 		for(c = ctype; c->ext != nil; c++)
860 			if(strcmp(p, c->ext) == 0){
861 				a->type = c->type;
862 				a->ctype = c;
863 				return a;
864 			}
865 	}
866 
867 	/* try the mime types file */
868 	if(p != nil){
869 		if(mimetypes == nil)
870 			readmimetypes();
871 		for(c = mimetypes; c != nil && c->ext != nil; c++)
872 			if(strcmp(p, c->ext) == 0){
873 				a->type = c->type;
874 				a->ctype = c;
875 				return a;
876 			}
877 	}
878 
879 	/* run file to figure out the type */
880 	a->type = "application/octet-stream";	/* safest default */
881 	if(pipe(pfd) < 0)
882 		return a;
883 	switch(fork()){
884 	case -1:
885 		break;
886 	case 0:
887 		close(pfd[1]);
888 		close(0);
889 		dup(pfd[0], 0);
890 		close(1);
891 		dup(pfd[0], 1);
892 		execl("/bin/file", "file", "-m", file, nil);
893 		exits(0);
894 	default:
895 		close(pfd[0]);
896 		n = read(pfd[1], ftype, sizeof(ftype));
897 		if(n > 0){
898 			ftype[n-1] = 0;
899 			a->type = estrdup(ftype);
900 		}
901 		close(pfd[1]);
902 		waitpid();
903 		break;
904 	}
905 
906 	for(c = ctype; ; c++)
907 		if(strncmp(a->type, c->type, strlen(c->type)) == 0){
908 			a->ctype = c;
909 			break;
910 		}
911 	return a;
912 }
913 
914 char*
mkboundary(void)915 mkboundary(void)
916 {
917 	int i;
918 	char buf[32];
919 
920 	srand((time(0)<<16)|getpid());
921 	strcpy(buf, "upas-");
922 	for(i = 5; i < sizeof(buf)-1; i++)
923 		buf[i] = 'a' + nrand(26);
924 	buf[i] = 0;
925 	return estrdup(buf);
926 }
927 
928 /* copy types to two fd's */
929 static void
tee(int in,int out1,int out2)930 tee(int in, int out1, int out2)
931 {
932 	int n;
933 	char buf[8*1024];
934 
935 	while ((n = read(in, buf, sizeof buf)) > 0)
936 		if (write(out1, buf, n) != n ||
937 		    write(out2, buf, n) != n)
938 			break;
939 }
940 
941 /* print the unix from line */
942 int
printunixfrom(int fd)943 printunixfrom(int fd)
944 {
945 	int tz;
946 	Tm *tm;
947 
948 	tm = localtime(time(0));
949 	tz = (tm->tzoff/3600)*100 + (tm->tzoff/60)%60;
950 
951 	return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n",
952 		user,
953 		ascwday[tm->wday], ascmon[tm->mon], tm->mday,
954 		tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900 + tm->year);
955 }
956 
957 char *specialfile[] =
958 {
959 	"pipeto",
960 	"pipefrom",
961 	"L.mbox",
962 	"forward",
963 	"names"
964 };
965 
966 /* return 1 if this is a special file */
967 static int
special(String * s)968 special(String *s)
969 {
970 	int i;
971 	char *p;
972 
973 	p = strrchr(s_to_c(s), '/');
974 	if(p == nil)
975 		p = s_to_c(s);
976 	else
977 		p++;
978 	for(i = 0; i < nelem(specialfile); i++)
979 		if(strcmp(p, specialfile[i]) == 0)
980 			return 1;
981 	return 0;
982 }
983 
984 /* open the folder using the recipients account name */
985 static int
openfolder(char * rcvr)986 openfolder(char *rcvr)
987 {
988 	int c, fd, scarey;
989 	char *p;
990 	Dir *d;
991 	String *file;
992 
993 	file = s_new();
994 	mboxpath("f", user, file, 0);
995 
996 	/* if $mail/f exists, store there, otherwise in $mail */
997 	d = dirstat(s_to_c(file));
998 	if(d == nil || d->qid.type != QTDIR){
999 		scarey = 1;
1000 		file->ptr -= 1;
1001 	} else {
1002 		s_putc(file, '/');
1003 		scarey = 0;
1004 	}
1005 	free(d);
1006 
1007 	p = strrchr(rcvr, '!');
1008 	if(p != nil)
1009 		rcvr = p+1;
1010 
1011 	while(*rcvr && *rcvr != '@'){
1012 		c = *rcvr++;
1013 		if(c == '/')
1014 			c = '_';
1015 		s_putc(file, c);
1016 	}
1017 	s_terminate(file);
1018 
1019 	if(scarey && special(file)){
1020 		fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file));
1021 		s_free(file);
1022 		return -1;
1023 	}
1024 
1025 	fd = open(s_to_c(file), OWRITE);
1026 	if(fd < 0)
1027 		fd = create(s_to_c(file), OWRITE, 0660);
1028 
1029 	s_free(file);
1030 	return fd;
1031 }
1032 
1033 /* start up sendmail and return an fd to talk to it with */
1034 int
sendmail(Addr * to,Addr * cc,int * pid,char * rcvr)1035 sendmail(Addr *to, Addr *cc, int *pid, char *rcvr)
1036 {
1037 	int ac, fd;
1038 	int pfd[2];
1039 	char **av, **v;
1040 	Addr *a;
1041 	String *cmd;
1042 
1043 	fd = -1;
1044 	if(rcvr != nil)
1045 		fd = openfolder(rcvr);
1046 
1047 	ac = 0;
1048 	for(a = to; a != nil; a = a->next)
1049 		ac++;
1050 	for(a = cc; a != nil; a = a->next)
1051 		ac++;
1052 	v = av = emalloc(sizeof(char*)*(ac+20));
1053 	ac = 0;
1054 	v[ac++] = "sendmail";
1055 	if(xflag)
1056 		v[ac++] = "-x";
1057 	if(rflag)
1058 		v[ac++] = "-r";
1059 	if(lbflag)
1060 		v[ac++] = "-#";
1061 	if(dflag)
1062 		v[ac++] = "-d";
1063 	for(a = to; a != nil; a = a->next)
1064 		v[ac++] = a->v;
1065 	for(a = cc; a != nil; a = a->next)
1066 		v[ac++] = a->v;
1067 	v[ac] = 0;
1068 
1069 	if(pipe(pfd) < 0)
1070 		fatal("%r");
1071 	switch(*pid = rfork(RFFDG|RFREND|RFPROC|RFENVG)){
1072 	case -1:
1073 		fatal("%r");
1074 		break;
1075 	case 0:
1076 		if(holding)
1077 			close(holding);
1078 		close(pfd[1]);
1079 		dup(pfd[0], 0);
1080 		close(pfd[0]);
1081 
1082 		if(rcvr != nil){
1083 			if(pipe(pfd) < 0)
1084 				fatal("%r");
1085 			switch(fork()){
1086 			case -1:
1087 				fatal("%r");
1088 				break;
1089 			case 0:
1090 				close(pfd[0]);
1091 				seek(fd, 0, 2);
1092 				printunixfrom(fd);
1093 				tee(0, pfd[1], fd);
1094 				write(fd, "\n", 1);
1095 				exits(0);
1096 			default:
1097 				close(fd);
1098 				close(pfd[1]);
1099 				dup(pfd[0], 0);
1100 				break;
1101 			}
1102 		}
1103 
1104 		if(replymsg != nil)
1105 			putenv("replymsg", replymsg);
1106 
1107 		cmd = mboxpath("pipefrom", login, s_new(), 0);
1108 		exec(s_to_c(cmd), av);
1109 		exec("/bin/myupassend", av);
1110 		exec("/bin/upas/send", av);
1111 		fatal("execing: %r");
1112 		break;
1113 	default:
1114 		if(rcvr != nil)
1115 			close(fd);
1116 		close(pfd[0]);
1117 		break;
1118 	}
1119 	return pfd[1];
1120 }
1121 
1122 /*
1123  * start up pgp process and return an fd to talk to it with.
1124  * its standard output will be the original fd, which goes to sendmail.
1125  */
1126 int
pgpfilter(int * pid,int fd,int pgpflag)1127 pgpfilter(int *pid, int fd, int pgpflag)
1128 {
1129 	int ac;
1130 	int pfd[2];
1131 	char **av, **v;
1132 
1133 	v = av = emalloc(sizeof(char*)*8);
1134 	ac = 0;
1135 	v[ac++] = "pgp";
1136 	v[ac++] = "-fat";		/* operate as a filter, generate text */
1137 	if(pgpflag & PGPsign)
1138 		v[ac++] = "-s";
1139 	if(pgpflag & PGPencrypt)
1140 		v[ac++] = "-e";
1141 	v[ac] = 0;
1142 
1143 	if(pipe(pfd) < 0)
1144 		fatal("%r");
1145 	switch(*pid = fork()){
1146 	case -1:
1147 		fatal("%r");
1148 		break;
1149 	case 0:
1150 		close(pfd[1]);
1151 		dup(pfd[0], 0);
1152 		close(pfd[0]);
1153 		dup(fd, 1);
1154 		close(fd);
1155 
1156 		/* add newline to avoid confusing pgp output with 822 headers */
1157 		write(1, "\n", 1);
1158 		exec("/bin/pgp", av);
1159 		fatal("execing: %r");
1160 		break;
1161 	default:
1162 		close(pfd[0]);
1163 		break;
1164 	}
1165 	close(fd);
1166 	return pfd[1];
1167 }
1168 
1169 /* wait for sendmail and pgp to exit; exit here if either failed */
1170 char*
waitforsubprocs(void)1171 waitforsubprocs(void)
1172 {
1173 	Waitmsg *w;
1174 	char *err;
1175 
1176 	err = nil;
1177 	while((w = wait()) != nil){
1178 		if(w->pid == pid || w->pid == pgppid)
1179 			if(w->msg[0] != 0)
1180 				err = estrdup(w->msg);
1181 		free(w);
1182 	}
1183 	if(err)
1184 		exits(err);
1185 	return nil;
1186 }
1187 
1188 int
cistrncmp(char * a,char * b,int n)1189 cistrncmp(char *a, char *b, int n)
1190 {
1191 	while(n-- > 0)
1192 		if(tolower(*a++) != tolower(*b++))
1193 			return -1;
1194 	return 0;
1195 }
1196 
1197 int
cistrcmp(char * a,char * b)1198 cistrcmp(char *a, char *b)
1199 {
1200 	for(;;){
1201 		if(tolower(*a) != tolower(*b++))
1202 			return -1;
1203 		if(*a++ == 0)
1204 			break;
1205 	}
1206 	return 0;
1207 }
1208 
1209 static uchar t64d[256];
1210 static char t64e[64];
1211 
1212 static void
init64(void)1213 init64(void)
1214 {
1215 	int c, i;
1216 
1217 	memset(t64d, 255, 256);
1218 	memset(t64e, '=', 64);
1219 	i = 0;
1220 	for(c = 'A'; c <= 'Z'; c++){
1221 		t64e[i] = c;
1222 		t64d[c] = i++;
1223 	}
1224 	for(c = 'a'; c <= 'z'; c++){
1225 		t64e[i] = c;
1226 		t64d[c] = i++;
1227 	}
1228 	for(c = '0'; c <= '9'; c++){
1229 		t64e[i] = c;
1230 		t64d[c] = i++;
1231 	}
1232 	t64e[i] = '+';
1233 	t64d['+'] = i++;
1234 	t64e[i] = '/';
1235 	t64d['/'] = i;
1236 }
1237 
1238 int
enc64(char * out,int lim,uchar * in,int n)1239 enc64(char *out, int lim, uchar *in, int n)
1240 {
1241 	int i;
1242 	ulong b24;
1243 	char *start = out;
1244 	char *e = out + lim;
1245 
1246 	if(t64e[0] == 0)
1247 		init64();
1248 	for(i = 0; i < n/3; i++){
1249 		b24 = (*in++)<<16;
1250 		b24 |= (*in++)<<8;
1251 		b24 |= *in++;
1252 		if(out + 5 >= e)
1253 			goto exhausted;
1254 		*out++ = t64e[(b24>>18)];
1255 		*out++ = t64e[(b24>>12)&0x3f];
1256 		*out++ = t64e[(b24>>6)&0x3f];
1257 		*out++ = t64e[(b24)&0x3f];
1258 		if((i%18) == 17)
1259 			*out++ = '\n';
1260 	}
1261 
1262 	switch(n%3){
1263 	case 2:
1264 		b24 = (*in++)<<16;
1265 		b24 |= (*in)<<8;
1266 		if(out + 4 >= e)
1267 			goto exhausted;
1268 		*out++ = t64e[(b24>>18)];
1269 		*out++ = t64e[(b24>>12)&0x3f];
1270 		*out++ = t64e[(b24>>6)&0x3f];
1271 		break;
1272 	case 1:
1273 		b24 = (*in)<<16;
1274 		if(out + 4 >= e)
1275 			goto exhausted;
1276 		*out++ = t64e[(b24>>18)];
1277 		*out++ = t64e[(b24>>12)&0x3f];
1278 		*out++ = '=';
1279 		break;
1280 	case 0:
1281 		if((i%18) != 0)
1282 			*out++ = '\n';
1283 		*out = 0;
1284 		return out - start;
1285 	}
1286 exhausted:
1287 	*out++ = '=';
1288 	*out++ = '\n';
1289 	*out = 0;
1290 	return out - start;
1291 }
1292 
1293 void
freealias(Alias * a)1294 freealias(Alias *a)
1295 {
1296 	freeaddrs(a->addr);
1297 	free(a);
1298 }
1299 
1300 void
freealiases(Alias * a)1301 freealiases(Alias *a)
1302 {
1303 	Alias *next;
1304 
1305 	while(a != nil){
1306 		next = a->next;
1307 		freealias(a);
1308 		a = next;
1309 	}
1310 }
1311 
1312 /*
1313  *  read alias file
1314  */
1315 Alias*
readaliases(void)1316 readaliases(void)
1317 {
1318 	Addr *addr, **al;
1319 	Alias *a, **l, *first;
1320 	Sinstack *sp;
1321 	String *file, *line, *token;
1322 	static int already;
1323 
1324 	first = nil;
1325 	file = s_new();
1326 	line = s_new();
1327 	token = s_new();
1328 
1329 	/* open and get length */
1330 	mboxpath("names", login, file, 0);
1331 	sp = s_allocinstack(s_to_c(file));
1332 	if(sp == nil)
1333 		goto out;
1334 
1335 	l = &first;
1336 
1337 	/* read a line at a time. */
1338 	while(s_rdinstack(sp, s_restart(line))!=nil) {
1339 		s_restart(line);
1340 		a = emalloc(sizeof(Alias));
1341 		al = &a->addr;
1342 		while(s_parse(line, s_restart(token)) != 0) {
1343 			addr = emalloc(sizeof(Addr));
1344 			addr->v = strdup(s_to_c(token));
1345 			addr->next = 0;
1346 			*al = addr;
1347 			al = &addr->next;
1348 		}
1349 		if(a->addr == nil || a->addr->next == nil){
1350 			freealias(a);
1351 			continue;
1352 		}
1353 		a->next = nil;
1354 		*l = a;
1355 		l = &a->next;
1356 	}
1357 	s_freeinstack(sp);
1358 out:
1359 	s_free(file);
1360 	s_free(line);
1361 	s_free(token);
1362 	return first;
1363 }
1364 
1365 Addr*
newaddr(char * name)1366 newaddr(char *name)
1367 {
1368 	Addr *a;
1369 
1370 	a = emalloc(sizeof(*a));
1371 	a->next = nil;
1372 	a->v = estrdup(name);
1373 	if(a->v == nil)
1374 		sysfatal("%r");
1375 	return a;
1376 }
1377 
1378 /*
1379  *  expand personal aliases since the names are meaningless in
1380  *  other contexts
1381  */
1382 Addr*
_expand(Addr * old,int * changedp)1383 _expand(Addr *old, int *changedp)
1384 {
1385 	Addr *first, *next, **l, *a;
1386 	Alias *al;
1387 
1388 	*changedp = 0;
1389 	first = nil;
1390 	l = &first;
1391 	for(;old != nil; old = next){
1392 		next = old->next;
1393 		for(al = aliases; al != nil; al = al->next){
1394 			if(strcmp(al->addr->v, old->v) == 0){
1395 				for(a = al->addr->next; a != nil; a = a->next){
1396 					*l = newaddr(a->v);
1397 					if(*l == nil)
1398 						sysfatal("%r");
1399 					l = &(*l)->next;
1400 					*changedp = 1;
1401 				}
1402 				break;
1403 			}
1404 		}
1405 		if(al != nil){
1406 			freeaddr(old);
1407 			continue;
1408 		}
1409 		*l = old;
1410 		old->next = nil;
1411 		l = &(*l)->next;
1412 	}
1413 	return first;
1414 }
1415 
1416 Addr*
rexpand(Addr * old)1417 rexpand(Addr *old)
1418 {
1419 	int i, changed;
1420 
1421 	changed = 0;
1422 	for(i = 0; i < 32; i++){
1423 		old = _expand(old, &changed);
1424 		if(changed == 0)
1425 			break;
1426 	}
1427 	return old;
1428 }
1429 
1430 Addr*
unique(Addr * first)1431 unique(Addr *first)
1432 {
1433 	Addr *a, **l, *x;
1434 
1435 	for(a = first; a != nil; a = a->next){
1436 		for(l = &a->next; *l != nil;){
1437 			if(strcmp(a->v, (*l)->v) == 0){
1438 				x = *l;
1439 				*l = x->next;
1440 				freeaddr(x);
1441 			} else
1442 				l = &(*l)->next;
1443 		}
1444 	}
1445 	return first;
1446 }
1447 
1448 Addr*
expand(int ac,char ** av)1449 expand(int ac, char **av)
1450 {
1451 	int i;
1452 	Addr *first, **l;
1453 
1454 	first = nil;
1455 
1456 	/* make a list of the starting addresses */
1457 	l = &first;
1458 	for(i = 0; i < ac; i++){
1459 		*l = newaddr(av[i]);
1460 		if(*l == nil)
1461 			sysfatal("%r");
1462 		l = &(*l)->next;
1463 	}
1464 
1465 	/* recurse till we don't change any more */
1466 	return unique(rexpand(first));
1467 }
1468 
1469 Addr*
concataddr(Addr * a,Addr * b)1470 concataddr(Addr *a, Addr *b)
1471 {
1472 	Addr *oa;
1473 
1474 	if(a == nil)
1475 		return b;
1476 
1477 	oa = a;
1478 	for(; a->next; a=a->next)
1479 		;
1480 	a->next = b;
1481 	return oa;
1482 }
1483 
1484 void
freeaddr(Addr * ap)1485 freeaddr(Addr *ap)
1486 {
1487 	free(ap->v);
1488 	free(ap);
1489 }
1490 
1491 void
freeaddrs(Addr * ap)1492 freeaddrs(Addr *ap)
1493 {
1494 	Addr *next;
1495 
1496 	for(; ap; ap=next) {
1497 		next = ap->next;
1498 		freeaddr(ap);
1499 	}
1500 }
1501 
1502 String*
s_copyn(char * s,int n)1503 s_copyn(char *s, int n)
1504 {
1505 	return s_nappend(s_reset(nil), s, n);
1506 }
1507 
1508 /*
1509  * fetch the next token from an RFC822 address string
1510  * we assume the header is RFC822-conformant in that
1511  * we recognize escaping anywhere even though it is only
1512  * supposed to be in quoted-strings, domain-literals, and comments.
1513  *
1514  * i'd use yylex or yyparse here, but we need to preserve
1515  * things like comments, which i think it tosses away.
1516  *
1517  * we're not strictly RFC822 compliant.  we misparse such nonsense as
1518  *
1519  *	To: gre @ (Grace) plan9 . (Emlin) bell-labs.com
1520  *
1521  * make sure there's no whitespace in your addresses and
1522  * you'll be fine.
1523  */
1524 enum {
1525 	Twhite,
1526 	Tcomment,
1527 	Twords,
1528 	Tcomma,
1529 	Tleftangle,
1530 	Trightangle,
1531 	Terror,
1532 	Tend,
1533 };
1534 
1535 // char *ty82[] = {"white", "comment", "words", "comma", "<", ">", "err", "end"};
1536 
1537 #define ISWHITE(p) ((p)==' ' || (p)=='\t' || (p)=='\n' || (p)=='\r')
1538 
1539 int
get822token(String ** tok,char * p,char ** pp)1540 get822token(String **tok, char *p, char **pp)
1541 {
1542 	int type, quoting;
1543 	char *op;
1544 
1545 	op = p;
1546 	switch(*p){
1547 	case '\0':
1548 		*tok = nil;
1549 		*pp = nil;
1550 		return Tend;
1551 
1552 	case ' ':		/* get whitespace */
1553 	case '\t':
1554 	case '\n':
1555 	case '\r':
1556 		type = Twhite;
1557 		while(ISWHITE(*p))
1558 			p++;
1559 		break;
1560 
1561 	case '(':		/* get comment */
1562 		type = Tcomment;
1563 		for(p++; *p && *p != ')'; p++)
1564 			if(*p == '\\') {
1565 				if(*(p+1) == '\0') {
1566 					*tok = nil;
1567 					return Terror;
1568 				}
1569 				p++;
1570 			}
1571 
1572 		if(*p != ')') {
1573 			*tok = nil;
1574 			return Terror;
1575 		}
1576 		p++;
1577 		break;
1578 	case ',':
1579 		type = Tcomma;
1580 		p++;
1581 		break;
1582 	case '<':
1583 		type = Tleftangle;
1584 		p++;
1585 		break;
1586 	case '>':
1587 		type = Trightangle;
1588 		p++;
1589 		break;
1590 	default:	/* bunch of letters, perhaps quoted strings tossed in */
1591 		type = Twords;
1592 		quoting = 0;
1593 		for (; *p && (quoting ||
1594 		    (!ISWHITE(*p) && *p != '>' && *p != '<' && *p != ',')); p++) {
1595 			if(*p == '"')
1596 				quoting = !quoting;
1597 			if(*p == '\\') {
1598 				if(*(p+1) == '\0') {
1599 					*tok = nil;
1600 					return Terror;
1601 				}
1602 				p++;
1603 			}
1604 		}
1605 		break;
1606 	}
1607 
1608 	if(pp)
1609 		*pp = p;
1610 	*tok = s_copyn(op, p-op);
1611 	return type;
1612 }
1613 
1614 /*
1615  * expand local aliases in an RFC822 mail line
1616  * add list of expanded addresses to to.
1617  */
1618 Addr*
expandline(String ** s,Addr * to)1619 expandline(String **s, Addr *to)
1620 {
1621 	int tok, inangle, hadangle, nword;
1622 	char *p;
1623 	Addr *na, *nto, *ap;
1624 	String *os, *ns, *stok, *lastword, *sinceword;
1625 
1626 	os = s_copy(s_to_c(*s));
1627 	p = strchr(s_to_c(*s), ':');
1628 	assert(p != nil);
1629 	p++;
1630 
1631 	ns = s_copyn(s_to_c(*s), p-s_to_c(*s));
1632 	stok = nil;
1633 	nto = nil;
1634 	/*
1635 	 * the only valid mailbox namings are word
1636 	 * and word* < addr >
1637 	 * without comments this would be simple.
1638 	 * we keep the following:
1639 	 * lastword - current guess at the address
1640 	 * sinceword - whitespace and comment seen since lastword
1641 	 */
1642 	lastword = s_new();
1643 	sinceword = s_new();
1644 	inangle = 0;
1645 	nword = 0;
1646 	hadangle = 0;
1647 	for(;;) {
1648 		stok = nil;
1649 		switch(tok = get822token(&stok, p, &p)){
1650 		default:
1651 			abort();
1652 		case Tcomma:
1653 		case Tend:
1654 			if(inangle)
1655 				goto Error;
1656 			if(nword != 1)
1657 				goto Error;
1658 			na = rexpand(newaddr(s_to_c(lastword)));
1659 			s_append(ns, na->v);
1660 			s_append(ns, s_to_c(sinceword));
1661 			for(ap=na->next; ap; ap=ap->next) {
1662 				s_append(ns, ", ");
1663 				s_append(ns, ap->v);
1664 			}
1665 			nto = concataddr(na, nto);
1666 			if(tok == Tcomma){
1667 				s_append(ns, ",");
1668 				s_free(stok);
1669 			}
1670 			if(tok == Tend)
1671 				goto Break2;
1672 			inangle = 0;
1673 			nword = 0;
1674 			hadangle = 0;
1675 			s_reset(sinceword);
1676 			s_reset(lastword);
1677 			break;
1678 		case Twhite:
1679 		case Tcomment:
1680 			s_append(sinceword, s_to_c(stok));
1681 			s_free(stok);
1682 			break;
1683 		case Trightangle:
1684 			if(!inangle)
1685 				goto Error;
1686 			inangle = 0;
1687 			hadangle = 1;
1688 			s_append(sinceword, s_to_c(stok));
1689 			s_free(stok);
1690 			break;
1691 		case Twords:
1692 		case Tleftangle:
1693 			if(hadangle)
1694 				goto Error;
1695 			if(tok != Tleftangle && inangle && s_len(lastword))
1696 				goto Error;
1697 			if(tok == Tleftangle) {
1698 				inangle = 1;
1699 				nword = 1;
1700 			}
1701 			s_append(ns, s_to_c(lastword));
1702 			s_append(ns, s_to_c(sinceword));
1703 			s_reset(sinceword);
1704 			if(tok == Tleftangle) {
1705 				s_append(ns, "<");
1706 				s_reset(lastword);
1707 			} else {
1708 				s_free(lastword);
1709 				lastword = stok;
1710 			}
1711 			if(!inangle)
1712 				nword++;
1713 			break;
1714 		case Terror:		/* give up, use old string, addrs */
1715 		Error:
1716 			ns = os;
1717 			os = nil;
1718 			freeaddrs(nto);
1719 			nto = nil;
1720 			werrstr("rfc822 syntax error");
1721 			rfc822syntaxerror = 1;
1722 			goto Break2;
1723 		}
1724 	}
1725 Break2:
1726 	s_free(*s);
1727 	s_free(os);
1728 	*s = ns;
1729 	nto = concataddr(nto, to);
1730 	return nto;
1731 }
1732 
1733 void
Bdrain(Biobuf * b)1734 Bdrain(Biobuf *b)
1735 {
1736 	char buf[8192];
1737 
1738 	while(Bread(b, buf, sizeof buf) > 0)
1739 		;
1740 }
1741 
1742 void
readmimetypes(void)1743 readmimetypes(void)
1744 {
1745 	char *p;
1746 	char type[256];
1747 	char *f[6];
1748 	Biobuf *b;
1749 	static int alloced, inuse;
1750 
1751 	if(mimetypes == 0){
1752 		alloced = 256;
1753 		mimetypes = emalloc(alloced*sizeof(Ctype));
1754 		mimetypes[0].ext = "";
1755 	}
1756 
1757 	b = Bopen("/sys/lib/mimetype", OREAD);
1758 	if(b == nil)
1759 		return;
1760 	for(;;){
1761 		p = Brdline(b, '\n');
1762 		if(p == nil)
1763 			break;
1764 		p[Blinelen(b)-1] = 0;
1765 		if(tokenize(p, f, 6) < 4)
1766 			continue;
1767 		if (strcmp(f[0], "-") == 0 || strcmp(f[1], "-") == 0 ||
1768 		    strcmp(f[2], "-") == 0)
1769 			continue;
1770 		if(inuse + 1 >= alloced){
1771 			alloced += 256;
1772 			mimetypes = erealloc(mimetypes, alloced*sizeof(Ctype));
1773 		}
1774 		snprint(type, sizeof(type), "%s/%s", f[1], f[2]);
1775 		mimetypes[inuse].type = estrdup(type);
1776 		mimetypes[inuse].ext = estrdup(f[0]+1);
1777 		mimetypes[inuse].display = !strcmp(type, "text/plain");
1778 		inuse++;
1779 
1780 		/* always make sure there's a terminator */
1781 		mimetypes[inuse].ext = 0;
1782 	}
1783 	Bterm(b);
1784 }
1785 
1786 char*
estrdup(char * x)1787 estrdup(char *x)
1788 {
1789 	x = strdup(x);
1790 	if(x == nil)
1791 		fatal("memory");
1792 	return x;
1793 }
1794 
1795 void*
emalloc(int n)1796 emalloc(int n)
1797 {
1798 	void *x;
1799 
1800 	x = malloc(n);
1801 	if(x == nil)
1802 		fatal("%r");
1803 	return x;
1804 }
1805 
1806 void*
erealloc(void * x,int n)1807 erealloc(void *x, int n)
1808 {
1809 	x = realloc(x, n);
1810 	if(x == nil)
1811 		fatal("%r");
1812 	return x;
1813 }
1814 
1815 /*
1816  * Formatter for %"
1817  * Use double quotes to protect white space, frogs, \ and "
1818  */
1819 enum
1820 {
1821 	Qok = 0,
1822 	Qquote,
1823 	Qbackslash,
1824 };
1825 
1826 static int
needtoquote(Rune r)1827 needtoquote(Rune r)
1828 {
1829 	if(r >= Runeself)
1830 		return Qquote;
1831 	if(r <= ' ')
1832 		return Qquote;
1833 	if(r=='\\' || r=='"')
1834 		return Qbackslash;
1835 	return Qok;
1836 }
1837 
1838 int
doublequote(Fmt * f)1839 doublequote(Fmt *f)
1840 {
1841 	int w, quotes;
1842 	char *s, *t;
1843 	Rune r;
1844 
1845 	s = va_arg(f->args, char*);
1846 	if(s == nil || *s == '\0')
1847 		return fmtstrcpy(f, "\"\"");
1848 
1849 	quotes = 0;
1850 	for(t = s; *t; t += w){
1851 		w = chartorune(&r, t);
1852 		quotes |= needtoquote(r);
1853 	}
1854 	if(quotes == 0)
1855 		return fmtstrcpy(f, s);
1856 
1857 	fmtrune(f, '"');
1858 	for(t = s; *t; t += w){
1859 		w = chartorune(&r, t);
1860 		if(needtoquote(r) == Qbackslash)
1861 			fmtrune(f, '\\');
1862 		fmtrune(f, r);
1863 	}
1864 	return fmtrune(f, '"');
1865 }
1866 
1867 int
rfc2047fmt(Fmt * fmt)1868 rfc2047fmt(Fmt *fmt)
1869 {
1870 	char *s, *p;
1871 
1872 	s = va_arg(fmt->args, char*);
1873 	if(s == nil)
1874 		return fmtstrcpy(fmt, "");
1875 	for(p=s; *p; p++)
1876 		if((uchar)*p >= 0x80)
1877 			goto hard;
1878 	return fmtstrcpy(fmt, s);
1879 
1880 hard:
1881 	fmtprint(fmt, "=?utf-8?q?");
1882 	for(p = s; *p; p++){
1883 		if(*p == ' ')
1884 			fmtrune(fmt, '_');
1885 		else if(*p == '_' || *p == '\t' || *p == '=' || *p == '?' ||
1886 		    (uchar)*p >= 0x80)
1887 			fmtprint(fmt, "=%.2uX", (uchar)*p);
1888 		else
1889 			fmtrune(fmt, (uchar)*p);
1890 	}
1891 	fmtprint(fmt, "?=");
1892 	return 0;
1893 }
1894 
1895 char*
mksubject(char * line)1896 mksubject(char *line)
1897 {
1898 	char *p, *q;
1899 	static char buf[1024];
1900 
1901 	p = strchr(line, ':') + 1;
1902 	while(*p == ' ')
1903 		p++;
1904 	for(q = p; *q; q++)
1905 		if((uchar)*q >= 0x80)
1906 			goto hard;
1907 	return line;
1908 
1909 hard:
1910 	snprint(buf, sizeof buf, "Subject: %U", p);
1911 	return buf;
1912 }
1913