Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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
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
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
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
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
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
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
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
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
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
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
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
757
printfrom(Biobuf *b)
758
{
759
	return Bprint(b, "From: %s\n", user);
760
}
761
 
762
int
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
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
797
printsubject(Biobuf *b, char *subject)
798
{
799
	return Bprint(b, "Subject: %U\n", subject);
800
}
801
 
802
int
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*
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*
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
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
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
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
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
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
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*
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
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
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
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
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
1294
freealias(Alias *a)
1295
{
1296
	freeaddrs(a->addr);
1297
	free(a);
1298
}
1299
 
1300
void
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*
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*
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*
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*
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*
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*
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*
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
1485
freeaddr(Addr *ap)
1486
{
1487
	free(ap->v);
1488
	free(ap);
1489
}
1490
 
1491
void
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*
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
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*
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
1734
Bdrain(Biobuf *b)
1735
{
1736
	char buf[8192];
1737
 
1738
	while(Bread(b, buf, sizeof buf) > 0)
1739
		;
1740
}
1741
 
1742
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*
1787
estrdup(char *x)
1788
{
1789
	x = strdup(x);
1790
	if(x == nil)
1791
		fatal("memory");
1792
	return x;
1793
}
1794
 
1795
void*
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*
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
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
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
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*
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
}