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