1 #include "common.h" 2 #include "smtp.h" 3 #include <ctype.h> 4 #include <mp.h> 5 #include <libsec.h> 6 #include <auth.h> 7 8 static char* connect(char*); 9 static char* dotls(char*); 10 static char* doauth(char*); 11 12 void addhostdom(String*, char*); 13 String* bangtoat(char*); 14 String* convertheader(String*); 15 int dBprint(char*, ...); 16 int dBputc(int); 17 char* data(String*, Biobuf*); 18 char* domainify(char*, char*); 19 String* fixrouteaddr(String*, Node*, Node*); 20 char* getcrnl(String*); 21 int getreply(void); 22 char* hello(char*, int); 23 char* mailfrom(char*); 24 int printdate(Node*); 25 int printheader(void); 26 void putcrnl(char*, int); 27 void quit(char*); 28 char* rcptto(char*); 29 char *rewritezone(char *); 30 31 #define Retry "Retry, Temporary Failure" 32 #define Giveup "Permanent Failure" 33 34 String *reply; /* last reply */ 35 String *toline; 36 37 int alarmscale; 38 int autistic; 39 int debug; /* true if we're debugging */ 40 int filter; 41 int insecure; 42 int last = 'n'; /* last character sent by putcrnl() */ 43 int ping; 44 int quitting; /* when error occurs in quit */ 45 int tryauth; /* Try to authenticate, if supported */ 46 int trysecure; /* Try to use TLS if the other side supports it */ 47 48 char *quitrv; /* deferred return value when in quit */ 49 char ddomain[1024]; /* domain name of destination machine */ 50 char *gdomain; /* domain name of gateway */ 51 char *uneaten; /* first character after rfc822 headers */ 52 char *farend; /* system we are trying to send to */ 53 char *user; /* user we are authenticating as, if authenticating */ 54 char hostdomain[256]; 55 56 Biobuf bin; 57 Biobuf bout; 58 Biobuf berr; 59 Biobuf bfile; 60 61 static int bustedmx; 62 63 void 64 usage(void) 65 { 66 fprint(2, "usage: smtp [-aAdfips] [-b busted-mx] [-g gw] [-h host] " 67 "[-u user] [.domain] net!host[!service] sender rcpt-list\n"); 68 exits(Giveup); 69 } 70 71 int 72 timeout(void *x, char *msg) 73 { 74 USED(x); 75 syslog(0, "smtp.fail", "interrupt: %s: %s", farend, msg); 76 if(strstr(msg, "alarm")){ 77 fprint(2, "smtp timeout: connection to %s timed out\n", farend); 78 if(quitting) 79 exits(quitrv); 80 exits(Retry); 81 } 82 if(strstr(msg, "closed pipe")){ 83 /* call _exits() to prevent Bio from trying to flush closed pipe */ 84 fprint(2, "smtp timeout: connection closed to %s\n", farend); 85 if(quitting){ 86 syslog(0, "smtp.fail", "closed pipe to %s", farend); 87 _exits(quitrv); 88 } 89 _exits(Retry); 90 } 91 return 0; 92 } 93 94 void 95 removenewline(char *p) 96 { 97 int n = strlen(p)-1; 98 99 if(n < 0) 100 return; 101 if(p[n] == '\n') 102 p[n] = 0; 103 } 104 105 void 106 main(int argc, char **argv) 107 { 108 int i, ok, rcvrs; 109 char *addr, *rv, *trv, *host, *domain; 110 char **errs; 111 char hellodomain[256]; 112 String *from, *fromm, *sender; 113 114 alarmscale = 60*1000; /* minutes */ 115 quotefmtinstall(); 116 errs = malloc(argc*sizeof(char*)); 117 reply = s_new(); 118 host = 0; 119 ARGBEGIN{ 120 case 'a': 121 tryauth = 1; 122 trysecure = 1; 123 break; 124 case 'A': /* autistic: won't talk to us until we talk (Verizon) */ 125 autistic = 1; 126 break; 127 case 'b': 128 if (bustedmx >= Maxbustedmx) 129 sysfatal("more than %d busted mxs given", Maxbustedmx); 130 bustedmxs[bustedmx++] = EARGF(usage()); 131 break; 132 case 'd': 133 debug = 1; 134 break; 135 case 'f': 136 filter = 1; 137 break; 138 case 'g': 139 gdomain = EARGF(usage()); 140 break; 141 case 'h': 142 host = EARGF(usage()); 143 break; 144 case 'i': 145 insecure = 1; 146 break; 147 case 'p': 148 alarmscale = 10*1000; /* tens of seconds */ 149 ping = 1; 150 break; 151 case 's': 152 trysecure = 1; 153 break; 154 case 'u': 155 user = EARGF(usage()); 156 break; 157 default: 158 usage(); 159 break; 160 }ARGEND; 161 162 Binit(&berr, 2, OWRITE); 163 Binit(&bfile, 0, OREAD); 164 165 /* 166 * get domain and add to host name 167 */ 168 if(*argv && **argv=='.') { 169 domain = *argv; 170 argv++; argc--; 171 } else 172 domain = domainname_read(); 173 if(host == 0) 174 host = sysname_read(); 175 strcpy(hostdomain, domainify(host, domain)); 176 strcpy(hellodomain, domainify(sysname_read(), domain)); 177 178 /* 179 * get destination address 180 */ 181 if(*argv == 0) 182 usage(); 183 addr = *argv++; argc--; 184 farend = addr; 185 186 /* 187 * get sender's machine. 188 * get sender in internet style. domainify if necessary. 189 */ 190 if(*argv == 0) 191 usage(); 192 sender = unescapespecial(s_copy(*argv++)); 193 argc--; 194 fromm = s_clone(sender); 195 rv = strrchr(s_to_c(fromm), '!'); 196 if(rv) 197 *rv = 0; 198 else 199 *s_to_c(fromm) = 0; 200 from = bangtoat(s_to_c(sender)); 201 202 /* 203 * send the mail 204 */ 205 if(filter){ 206 Binit(&bout, 1, OWRITE); 207 rv = data(from, &bfile); 208 if(rv != 0) 209 goto error; 210 exits(0); 211 } 212 213 /* mxdial uses its own timeout handler */ 214 if((rv = connect(addr)) != 0) 215 exits(rv); 216 217 /* 10 minutes to get through the initial handshake */ 218 atnotify(timeout, 1); 219 alarm(10*alarmscale); 220 if((rv = hello(hellodomain, 0)) != 0) 221 goto error; 222 alarm(10*alarmscale); 223 if((rv = mailfrom(s_to_c(from))) != 0) 224 goto error; 225 226 ok = 0; 227 rcvrs = 0; 228 /* if any rcvrs are ok, we try to send the message */ 229 for(i = 0; i < argc; i++){ 230 if((trv = rcptto(argv[i])) != 0){ 231 /* remember worst error */ 232 if(rv != Giveup) 233 rv = trv; 234 errs[rcvrs] = strdup(s_to_c(reply)); 235 removenewline(errs[rcvrs]); 236 } else { 237 ok++; 238 errs[rcvrs] = 0; 239 } 240 rcvrs++; 241 } 242 243 /* if no ok rcvrs or worst error is retry, give up */ 244 if(ok == 0 || rv == Retry) 245 goto error; 246 247 if(ping){ 248 quit(0); 249 exits(0); 250 } 251 252 rv = data(from, &bfile); 253 if(rv != 0) 254 goto error; 255 quit(0); 256 if(rcvrs == ok) 257 exits(0); 258 259 /* 260 * here when some but not all rcvrs failed 261 */ 262 fprint(2, "%s connect to %s:\n", thedate(), addr); 263 for(i = 0; i < rcvrs; i++){ 264 if(errs[i]){ 265 syslog(0, "smtp.fail", "delivery to %s at %s failed: %s", argv[i], addr, errs[i]); 266 fprint(2, " mail to %s failed: %s", argv[i], errs[i]); 267 } 268 } 269 exits(Giveup); 270 271 /* 272 * here when all rcvrs failed 273 */ 274 error: 275 removenewline(s_to_c(reply)); 276 syslog(0, "smtp.fail", "%s to %s failed: %s", 277 ping ? "ping" : "delivery", 278 addr, s_to_c(reply)); 279 fprint(2, "%s connect to %s:\n%s\n", thedate(), addr, s_to_c(reply)); 280 if(!filter) 281 quit(rv); 282 exits(rv); 283 } 284 285 /* 286 * connect to the remote host 287 */ 288 static char * 289 connect(char* net) 290 { 291 char buf[256]; 292 int fd; 293 294 fd = mxdial(net, ddomain, gdomain); 295 296 if(fd < 0){ 297 rerrstr(buf, sizeof(buf)); 298 Bprint(&berr, "smtp: %s (%s)\n", buf, net); 299 syslog(0, "smtp.fail", "%s (%s)", buf, net); 300 if(strstr(buf, "illegal") 301 || strstr(buf, "unknown") 302 || strstr(buf, "can't translate")) 303 return Giveup; 304 else 305 return Retry; 306 } 307 Binit(&bin, fd, OREAD); 308 fd = dup(fd, -1); 309 Binit(&bout, fd, OWRITE); 310 return 0; 311 } 312 313 static char smtpthumbs[] = "/sys/lib/tls/smtp"; 314 static char smtpexclthumbs[] = "/sys/lib/tls/smtp.exclude"; 315 316 /* 317 * exchange names with remote host, attempt to 318 * enable encryption and optionally authenticate. 319 * not fatal if we can't. 320 */ 321 static char * 322 dotls(char *me) 323 { 324 TLSconn *c; 325 Thumbprint *goodcerts; 326 char *h; 327 int fd; 328 uchar hash[SHA1dlen]; 329 330 c = mallocz(sizeof(*c), 1); /* Note: not freed on success */ 331 if (c == nil) 332 return Giveup; 333 334 dBprint("STARTTLS\r\n"); 335 if (getreply() != 2) 336 return Giveup; 337 338 fd = tlsClient(Bfildes(&bout), c); 339 if (fd < 0) { 340 syslog(0, "smtp", "tlsClient to %q: %r", ddomain); 341 return Giveup; 342 } 343 goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs); 344 if (goodcerts == nil) { 345 free(c); 346 close(fd); 347 syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs); 348 return Giveup; /* how to recover? TLS is started */ 349 } 350 351 /* compute sha1 hash of remote's certificate, see if we know it */ 352 sha1(c->cert, c->certlen, hash, nil); 353 if (!okThumbprint(hash, goodcerts)) { 354 /* TODO? if not excluded, add hash to thumb list */ 355 free(c); 356 close(fd); 357 h = malloc(2*sizeof hash + 1); 358 if (h != nil) { 359 enc16(h, 2*sizeof hash + 1, hash, sizeof hash); 360 // fprint(2, "x509 sha1=%s", h); 361 syslog(0, "smtp", 362 "remote cert. has bad thumbprint: x509 sha1=%s server=%q", 363 h, ddomain); 364 free(h); 365 } 366 return Giveup; /* how to recover? TLS is started */ 367 } 368 freeThumbprints(goodcerts); 369 Bterm(&bin); 370 Bterm(&bout); 371 372 /* 373 * set up bin & bout to use the TLS fd, i/o upon which generates 374 * i/o on the original, underlying fd. 375 */ 376 Binit(&bin, fd, OREAD); 377 fd = dup(fd, -1); 378 Binit(&bout, fd, OWRITE); 379 380 syslog(0, "smtp", "started TLS to %q", ddomain); 381 return(hello(me, 1)); 382 } 383 384 static char * 385 doauth(char *methods) 386 { 387 char *buf, *base64; 388 int n; 389 DS ds; 390 UserPasswd *p; 391 392 dial_string_parse(ddomain, &ds); 393 394 if(user != nil) 395 p = auth_getuserpasswd(nil, 396 "proto=pass service=smtp server=%q user=%q", ds.host, user); 397 else 398 p = auth_getuserpasswd(nil, 399 "proto=pass service=smtp server=%q", ds.host); 400 if (p == nil) 401 return Giveup; 402 403 if (strstr(methods, "LOGIN")){ 404 dBprint("AUTH LOGIN\r\n"); 405 if (getreply() != 3) 406 return Retry; 407 408 n = strlen(p->user); 409 base64 = malloc(2*n); 410 if (base64 == nil) 411 return Retry; /* Out of memory */ 412 enc64(base64, 2*n, (uchar *)p->user, n); 413 dBprint("%s\r\n", base64); 414 if (getreply() != 3) 415 return Retry; 416 417 n = strlen(p->passwd); 418 base64 = malloc(2*n); 419 if (base64 == nil) 420 return Retry; /* Out of memory */ 421 enc64(base64, 2*n, (uchar *)p->passwd, n); 422 dBprint("%s\r\n", base64); 423 if (getreply() != 2) 424 return Retry; 425 426 free(base64); 427 } 428 else 429 if (strstr(methods, "PLAIN")){ 430 n = strlen(p->user) + strlen(p->passwd) + 3; 431 buf = malloc(n); 432 base64 = malloc(2 * n); 433 if (buf == nil || base64 == nil) { 434 free(buf); 435 return Retry; /* Out of memory */ 436 } 437 snprint(buf, n, "%c%s%c%s", 0, p->user, 0, p->passwd); 438 enc64(base64, 2 * n, (uchar *)buf, n - 1); 439 free(buf); 440 dBprint("AUTH PLAIN %s\r\n", base64); 441 free(base64); 442 if (getreply() != 2) 443 return Retry; 444 } 445 else 446 return "No supported AUTH method"; 447 return(0); 448 } 449 450 char * 451 hello(char *me, int encrypted) 452 { 453 int ehlo; 454 String *r; 455 char *ret, *s, *t; 456 457 if (!encrypted) { 458 /* 459 * Verizon fails to print the smtp greeting banner when it 460 * answers a call. Send a no-op in the hope of making it 461 * talk. 462 */ 463 if (autistic) { 464 dBprint("NOOP\r\n"); 465 getreply(); /* consume the smtp greeting */ 466 /* next reply will be response to noop */ 467 } 468 switch(getreply()){ 469 case 2: 470 break; 471 case 5: 472 return Giveup; 473 default: 474 return Retry; 475 } 476 } 477 478 ehlo = 1; 479 Again: 480 if(ehlo) 481 dBprint("EHLO %s\r\n", me); 482 else 483 dBprint("HELO %s\r\n", me); 484 switch (getreply()) { 485 case 2: 486 break; 487 case 5: 488 if(ehlo){ 489 ehlo = 0; 490 goto Again; 491 } 492 return Giveup; 493 default: 494 return Retry; 495 } 496 r = s_clone(reply); 497 if(r == nil) 498 return Retry; /* Out of memory or couldn't get string */ 499 500 /* Invariant: every line has a newline, a result of getcrlf() */ 501 for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){ 502 *t = '\0'; 503 for (t = s; *t != '\0'; t++) 504 *t = toupper(*t); 505 if(!encrypted && trysecure && 506 (strcmp(s, "250-STARTTLS") == 0 || 507 strcmp(s, "250 STARTTLS") == 0)){ 508 s_free(r); 509 return dotls(me); 510 } 511 if(tryauth && (encrypted || insecure) && 512 (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 || 513 strncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){ 514 ret = doauth(s + strlen("250 AUTH ")); 515 s_free(r); 516 return ret; 517 } 518 } 519 s_free(r); 520 return 0; 521 } 522 523 /* 524 * report sender to remote 525 */ 526 char * 527 mailfrom(char *from) 528 { 529 if(!returnable(from)) 530 dBprint("MAIL FROM:<>\r\n"); 531 else 532 if(strchr(from, '@')) 533 dBprint("MAIL FROM:<%s>\r\n", from); 534 else 535 dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain); 536 switch(getreply()){ 537 case 2: 538 break; 539 case 5: 540 return Giveup; 541 default: 542 return Retry; 543 } 544 return 0; 545 } 546 547 /* 548 * report a recipient to remote 549 */ 550 char * 551 rcptto(char *to) 552 { 553 String *s; 554 555 s = unescapespecial(bangtoat(to)); 556 if(toline == 0) 557 toline = s_new(); 558 else 559 s_append(toline, ", "); 560 s_append(toline, s_to_c(s)); 561 if(strchr(s_to_c(s), '@')) 562 dBprint("RCPT TO:<%s>\r\n", s_to_c(s)); 563 else { 564 s_append(toline, "@"); 565 s_append(toline, ddomain); 566 dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain); 567 } 568 alarm(10*alarmscale); 569 switch(getreply()){ 570 case 2: 571 break; 572 case 5: 573 return Giveup; 574 default: 575 return Retry; 576 } 577 return 0; 578 } 579 580 static char hex[] = "0123456789abcdef"; 581 582 /* 583 * send the damn thing 584 */ 585 char * 586 data(String *from, Biobuf *b) 587 { 588 char *buf, *cp; 589 int i, n, nbytes, bufsize, eof, r; 590 String *fromline; 591 char errmsg[Errlen]; 592 char id[40]; 593 594 /* 595 * input the header. 596 */ 597 598 buf = malloc(1); 599 if(buf == 0){ 600 s_append(s_restart(reply), "out of memory"); 601 return Retry; 602 } 603 n = 0; 604 eof = 0; 605 for(;;){ 606 cp = Brdline(b, '\n'); 607 if(cp == nil){ 608 eof = 1; 609 break; 610 } 611 nbytes = Blinelen(b); 612 buf = realloc(buf, n+nbytes+1); 613 if(buf == 0){ 614 s_append(s_restart(reply), "out of memory"); 615 return Retry; 616 } 617 strncpy(buf+n, cp, nbytes); 618 n += nbytes; 619 if(nbytes == 1) /* end of header */ 620 break; 621 } 622 buf[n] = 0; 623 bufsize = n; 624 625 /* 626 * parse the header, turn all addresses into @ format 627 */ 628 yyinit(buf, n); 629 yyparse(); 630 631 /* 632 * print message observing '.' escapes and using \r\n for \n 633 */ 634 alarm(20*alarmscale); 635 if(!filter){ 636 dBprint("DATA\r\n"); 637 switch(getreply()){ 638 case 3: 639 break; 640 case 5: 641 free(buf); 642 return Giveup; 643 default: 644 free(buf); 645 return Retry; 646 } 647 } 648 /* 649 * send header. add a message-id, a sender, and a date if there 650 * isn't one 651 */ 652 nbytes = 0; 653 fromline = convertheader(from); 654 uneaten = buf; 655 656 srand(truerand()); 657 if(messageid == 0){ 658 for(i=0; i<16; i++){ 659 r = rand()&0xFF; 660 id[2*i] = hex[r&0xF]; 661 id[2*i+1] = hex[(r>>4)&0xF]; 662 } 663 id[2*i] = '\0'; 664 nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain); 665 if(debug) 666 Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain); 667 } 668 669 if(originator==0){ 670 nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline)); 671 if(debug) 672 Bprint(&berr, "From: %s\r\n", s_to_c(fromline)); 673 } 674 s_free(fromline); 675 676 if(destination == 0 && toline) 677 if(*s_to_c(toline) == '@'){ /* route addr */ 678 nbytes += Bprint(&bout, "To: <%s>\r\n", s_to_c(toline)); 679 if(debug) 680 Bprint(&berr, "To: <%s>\r\n", s_to_c(toline)); 681 } else { 682 nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline)); 683 if(debug) 684 Bprint(&berr, "To: %s\r\n", s_to_c(toline)); 685 } 686 687 if(date==0 && udate) 688 nbytes += printdate(udate); 689 if (usys) 690 uneaten = usys->end + 1; 691 nbytes += printheader(); 692 if (*uneaten != '\n') 693 putcrnl("\n", 1); 694 695 /* 696 * send body 697 */ 698 699 putcrnl(uneaten, buf+n - uneaten); 700 nbytes += buf+n - uneaten; 701 if(eof == 0){ 702 for(;;){ 703 n = Bread(b, buf, bufsize); 704 if(n < 0){ 705 rerrstr(errmsg, sizeof(errmsg)); 706 s_append(s_restart(reply), errmsg); 707 free(buf); 708 return Retry; 709 } 710 if(n == 0) 711 break; 712 alarm(10*alarmscale); 713 putcrnl(buf, n); 714 nbytes += n; 715 } 716 } 717 free(buf); 718 if(!filter){ 719 if(last != '\n') 720 dBprint("\r\n.\r\n"); 721 else 722 dBprint(".\r\n"); 723 alarm(10*alarmscale); 724 switch(getreply()){ 725 case 2: 726 break; 727 case 5: 728 return Giveup; 729 default: 730 return Retry; 731 } 732 syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from), 733 nbytes, s_to_c(toline));/**/ 734 } 735 return 0; 736 } 737 738 /* 739 * we're leaving 740 */ 741 void 742 quit(char *rv) 743 { 744 /* 60 minutes to quit */ 745 quitting = 1; 746 quitrv = rv; 747 alarm(60*alarmscale); 748 dBprint("QUIT\r\n"); 749 getreply(); 750 Bterm(&bout); 751 Bterm(&bfile); 752 } 753 754 /* 755 * read a reply into a string, return the reply code 756 */ 757 int 758 getreply(void) 759 { 760 char *line; 761 int rv; 762 763 reply = s_reset(reply); 764 for(;;){ 765 line = getcrnl(reply); 766 if(debug) 767 Bflush(&berr); 768 if(line == 0) 769 return -1; 770 if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2])) 771 return -1; 772 if(line[3] != '-') 773 break; 774 } 775 if(debug) 776 Bflush(&berr); 777 rv = atoi(line)/100; 778 return rv; 779 } 780 void 781 addhostdom(String *buf, char *host) 782 { 783 s_append(buf, "@"); 784 s_append(buf, host); 785 } 786 787 /* 788 * Convert from `bang' to `source routing' format. 789 * 790 * a.x.y!b.p.o!c!d -> @a.x.y:c!d@b.p.o 791 */ 792 String * 793 bangtoat(char *addr) 794 { 795 String *buf; 796 register int i; 797 int j, d; 798 char *field[128]; 799 800 /* parse the '!' format address */ 801 buf = s_new(); 802 for(i = 0; addr; i++){ 803 field[i] = addr; 804 addr = strchr(addr, '!'); 805 if(addr) 806 *addr++ = 0; 807 } 808 if (i==1) { 809 s_append(buf, field[0]); 810 return buf; 811 } 812 813 /* 814 * count leading domain fields (non-domains don't count) 815 */ 816 for(d = 0; d<i-1; d++) 817 if(strchr(field[d], '.')==0) 818 break; 819 /* 820 * if there are more than 1 leading domain elements, 821 * put them in as source routing 822 */ 823 if(d > 1){ 824 addhostdom(buf, field[0]); 825 for(j=1; j<d-1; j++){ 826 s_append(buf, ","); 827 s_append(buf, "@"); 828 s_append(buf, field[j]); 829 } 830 s_append(buf, ":"); 831 } 832 833 /* 834 * throw in the non-domain elements separated by '!'s 835 */ 836 s_append(buf, field[d]); 837 for(j=d+1; j<=i-1; j++) { 838 s_append(buf, "!"); 839 s_append(buf, field[j]); 840 } 841 if(d) 842 addhostdom(buf, field[d-1]); 843 return buf; 844 } 845 846 /* 847 * convert header addresses to @ format. 848 * if the address is a source address, and a domain is specified, 849 * make sure it falls in the domain. 850 */ 851 String* 852 convertheader(String *from) 853 { 854 Field *f; 855 Node *p, *lastp; 856 String *a; 857 858 if(!returnable(s_to_c(from))){ 859 from = s_new(); 860 s_append(from, "Postmaster"); 861 addhostdom(from, hostdomain); 862 } else 863 if(strchr(s_to_c(from), '@') == 0){ 864 a = username(from); 865 if(a) { 866 s_append(a, " <"); 867 s_append(a, s_to_c(from)); 868 addhostdom(a, hostdomain); 869 s_append(a, ">"); 870 from = a; 871 } else { 872 from = s_copy(s_to_c(from)); 873 addhostdom(from, hostdomain); 874 } 875 } else 876 from = s_copy(s_to_c(from)); 877 for(f = firstfield; f; f = f->next){ 878 lastp = 0; 879 for(p = f->node; p; lastp = p, p = p->next){ 880 if(!p->addr) 881 continue; 882 a = bangtoat(s_to_c(p->s)); 883 s_free(p->s); 884 if(strchr(s_to_c(a), '@') == 0) 885 addhostdom(a, hostdomain); 886 else if(*s_to_c(a) == '@') 887 a = fixrouteaddr(a, p->next, lastp); 888 p->s = a; 889 } 890 } 891 return from; 892 } 893 /* 894 * ensure route addr has brackets around it 895 */ 896 String* 897 fixrouteaddr(String *raddr, Node *next, Node *last) 898 { 899 String *a; 900 901 if(last && last->c == '<' && next && next->c == '>') 902 return raddr; /* properly formed already */ 903 904 a = s_new(); 905 s_append(a, "<"); 906 s_append(a, s_to_c(raddr)); 907 s_append(a, ">"); 908 s_free(raddr); 909 return a; 910 } 911 912 /* 913 * print out the parsed header 914 */ 915 int 916 printheader(void) 917 { 918 int n, len; 919 Field *f; 920 Node *p; 921 char *cp; 922 char c[1]; 923 924 n = 0; 925 for(f = firstfield; f; f = f->next){ 926 for(p = f->node; p; p = p->next){ 927 if(p->s) 928 n += dBprint("%s", s_to_c(p->s)); 929 else { 930 c[0] = p->c; 931 putcrnl(c, 1); 932 n++; 933 } 934 if(p->white){ 935 cp = s_to_c(p->white); 936 len = strlen(cp); 937 putcrnl(cp, len); 938 n += len; 939 } 940 uneaten = p->end; 941 } 942 putcrnl("\n", 1); 943 n++; 944 uneaten++; /* skip newline */ 945 } 946 return n; 947 } 948 949 /* 950 * add a domain onto an name, return the new name 951 */ 952 char * 953 domainify(char *name, char *domain) 954 { 955 static String *s; 956 char *p; 957 958 if(domain==0 || strchr(name, '.')!=0) 959 return name; 960 961 s = s_reset(s); 962 s_append(s, name); 963 p = strchr(domain, '.'); 964 if(p == 0){ 965 s_append(s, "."); 966 p = domain; 967 } 968 s_append(s, p); 969 return s_to_c(s); 970 } 971 972 /* 973 * print message observing '.' escapes and using \r\n for \n 974 */ 975 void 976 putcrnl(char *cp, int n) 977 { 978 int c; 979 980 for(; n; n--, cp++){ 981 c = *cp; 982 if(c == '\n') 983 dBputc('\r'); 984 else if(c == '.' && last=='\n') 985 dBputc('.'); 986 dBputc(c); 987 last = c; 988 } 989 } 990 991 /* 992 * Get a line including a crnl into a string. Convert crnl into nl. 993 */ 994 char * 995 getcrnl(String *s) 996 { 997 int c; 998 int count; 999 1000 count = 0; 1001 for(;;){ 1002 c = Bgetc(&bin); 1003 if(debug) 1004 Bputc(&berr, c); 1005 switch(c){ 1006 case -1: 1007 s_append(s, "connection closed unexpectedly by remote system"); 1008 s_terminate(s); 1009 return 0; 1010 case '\r': 1011 c = Bgetc(&bin); 1012 if(c == '\n'){ 1013 case '\n': 1014 s_putc(s, c); 1015 if(debug) 1016 Bputc(&berr, c); 1017 count++; 1018 s_terminate(s); 1019 return s->ptr - count; 1020 } 1021 Bungetc(&bin); 1022 s_putc(s, '\r'); 1023 if(debug) 1024 Bputc(&berr, '\r'); 1025 count++; 1026 break; 1027 default: 1028 s_putc(s, c); 1029 count++; 1030 break; 1031 } 1032 } 1033 } 1034 1035 /* 1036 * print out a parsed date 1037 */ 1038 int 1039 printdate(Node *p) 1040 { 1041 int n, sep = 0; 1042 1043 n = dBprint("Date: %s,", s_to_c(p->s)); 1044 for(p = p->next; p; p = p->next){ 1045 if(p->s){ 1046 if(sep == 0) { 1047 dBputc(' '); 1048 n++; 1049 } 1050 if (p->next) 1051 n += dBprint("%s", s_to_c(p->s)); 1052 else 1053 n += dBprint("%s", rewritezone(s_to_c(p->s))); 1054 sep = 0; 1055 } else { 1056 dBputc(p->c); 1057 n++; 1058 sep = 1; 1059 } 1060 } 1061 n += dBprint("\r\n"); 1062 return n; 1063 } 1064 1065 char * 1066 rewritezone(char *z) 1067 { 1068 int mindiff; 1069 char s; 1070 Tm *tm; 1071 static char x[7]; 1072 1073 tm = localtime(time(0)); 1074 mindiff = tm->tzoff/60; 1075 1076 /* if not in my timezone, don't change anything */ 1077 if(strcmp(tm->zone, z) != 0) 1078 return z; 1079 1080 if(mindiff < 0){ 1081 s = '-'; 1082 mindiff = -mindiff; 1083 } else 1084 s = '+'; 1085 1086 sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60); 1087 return x; 1088 } 1089 1090 /* 1091 * stolen from libc/port/print.c 1092 */ 1093 #define SIZE 4096 1094 int 1095 dBprint(char *fmt, ...) 1096 { 1097 char buf[SIZE], *out; 1098 va_list arg; 1099 int n; 1100 1101 va_start(arg, fmt); 1102 out = vseprint(buf, buf+SIZE, fmt, arg); 1103 va_end(arg); 1104 if(debug){ 1105 Bwrite(&berr, buf, (long)(out-buf)); 1106 Bflush(&berr); 1107 } 1108 n = Bwrite(&bout, buf, (long)(out-buf)); 1109 Bflush(&bout); 1110 return n; 1111 } 1112 1113 int 1114 dBputc(int x) 1115 { 1116 if(debug) 1117 Bputc(&berr, x); 1118 return Bputc(&bout, x); 1119 } 1120