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 } 1913