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