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