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