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