1 #include "common.h" 2 #include "smtp.h" 3 #include <ctype.h> 4 5 char* connect(char*); 6 char* hello(char*); 7 char* mailfrom(char*); 8 char* rcptto(char*); 9 char* data(String*, int); 10 void quit(void); 11 int getreply(void); 12 void addhostdom(String*, char*); 13 String* bangtoat(char*); 14 void convertheader(String*); 15 void printheader(void); 16 char* domainify(char*, char*); 17 void putcrnl(char*, int); 18 char* getcrnl(String*); 19 void printdate(Node*); 20 char *rewritezone(char *); 21 int mxdial(char*, int*, char*); 22 int dBprint(char*, ...); 23 24 #define Retry "Temporary Failure, Retry" 25 #define Giveup "Permanent Failure" 26 27 int debug; /* true if we're debugging */ 28 String *reply; /* last reply */ 29 String *toline; 30 char *sender; /* who to bounce message to */ 31 int last = 'n'; /* last character sent by putcrnl() */ 32 int filter; 33 int unix; 34 int gateway; /* true if we are traversing a mail gateway */ 35 char ddomain[1024]; /* domain name of destination machine */ 36 char *gdomain; /* domain name of gateway */ 37 char *uneaten; /* first character after rfc822 headers */ 38 char hostdomain[256]; 39 Biobuf bin; 40 Biobuf bout; 41 Biobuf berr; 42 43 void 44 usage(void) 45 { 46 fprint(2, "usage: smtp [-du] [-hhost] [.domain] net!host[!service] sender rcpt-list\n"); 47 exits(Giveup); 48 } 49 50 void 51 timeout(void *x, char *msg) 52 { 53 USED(x); 54 if(strstr(msg, "alarm")){ 55 fprint(2, "smtp timeout: no retries"); 56 exits(Giveup); 57 } 58 noted(NDFLT); 59 } 60 61 void 62 main(int argc, char **argv) 63 { 64 char *domain; 65 char *host; 66 String *from; 67 String *fromm; 68 char *addr; 69 char *rv; 70 71 reply = s_new(); 72 unix = 0; 73 host = 0; 74 ARGBEGIN{ 75 case 'f': 76 filter = 1; 77 break; 78 case 'd': 79 debug = 1; 80 break; 81 case 'g': 82 gdomain = ARGF(); 83 break; 84 case 'u': 85 unix = 1; 86 break; 87 case 'h': 88 host = ARGF(); 89 break; 90 default: 91 usage(); 92 break; 93 }ARGEND; 94 95 Binit(&berr, 2, OWRITE); 96 97 /* 98 * get domain and add to host name 99 */ 100 domain = csquery("soa", "", "dom"); 101 if(*argv && **argv=='.') 102 domain = *argv++; 103 if(host == 0) 104 host = sysname_read(); 105 strcpy(hostdomain, domainify(host, domain)); 106 107 /* 108 * get destination address 109 */ 110 if(*argv == 0) 111 usage(); 112 addr = *argv++; 113 114 /* 115 * get sender's machine. 116 * get sender in internet style. domainify if necessary. 117 */ 118 if(*argv == 0) 119 usage(); 120 sender = *argv++; 121 fromm = s_copy(sender); 122 rv = strrchr(s_to_c(fromm), '!'); 123 if(rv) 124 *rv = 0; 125 else 126 *s_to_c(fromm) = 0; 127 from = bangtoat(sender); 128 129 /* 130 * send the mail 131 */ 132 if(filter){ 133 Binit(&bin, 0, OREAD); 134 Binit(&bout, 1, OWRITE); 135 } else { 136 /* 10 minutes to get through the initial handshake */ 137 notify(timeout); 138 alarm(10*60*1000); 139 140 if((rv = connect(addr)) != 0) 141 exits(rv); 142 if((rv = hello(hostdomain)) != 0) 143 goto error; 144 if((rv = mailfrom(s_to_c(from))) != 0) 145 goto error; 146 while(*argv) 147 if((rv = rcptto(*argv++))!=0) 148 goto error; 149 150 alarm(0); 151 } 152 rv = data(from, unix); 153 if(rv != 0) 154 goto error; 155 if(!filter) 156 quit(); 157 exits(""); 158 error: 159 fprint(2, "%s\n", s_to_c(reply)); 160 exits(rv); 161 } 162 163 /* 164 * connect to the remote host 165 */ 166 char * 167 connect(char* net) 168 { 169 char *addr; 170 char buf[256]; 171 int fd; 172 173 addr = netmkaddr(net, 0, "smtp"); 174 175 /* try connecting to destination or any of it's mail routers */ 176 fd = mxdial(addr, &gateway, ddomain); 177 178 /* try our mail gateway */ 179 if(fd < 0 && gdomain){ 180 gateway = 1; 181 fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0); 182 } 183 184 if(fd < 0){ 185 errstr(buf); 186 Bprint(&berr, "smtp: %s %s\n", buf, addr); 187 if(strstr(buf, "illegal") || strstr(buf, "rejected")) 188 return Giveup; 189 else 190 return Retry; 191 } 192 Binit(&bin, fd, OREAD); 193 fd = dup(fd, -1); 194 Binit(&bout, fd, OWRITE); 195 return 0; 196 } 197 198 /* 199 * exchange names with remote host 200 */ 201 char * 202 hello(char *me) 203 { 204 switch(getreply()){ 205 case 2: 206 break; 207 case 5: 208 return Giveup; 209 default: 210 return Retry; 211 } 212 dBprint("HELO %s\r\n", me); 213 switch(getreply()){ 214 case 2: 215 break; 216 case 5: 217 return Giveup; 218 default: 219 return Retry; 220 } 221 return 0; 222 } 223 224 /* 225 * report sender to remote 226 */ 227 char * 228 mailfrom(char *from) 229 { 230 if(strchr(from, '@')) 231 dBprint("MAIL FROM:<%s>\r\n", from); 232 else 233 dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain); 234 switch(getreply()){ 235 case 2: 236 break; 237 case 5: 238 return Giveup; 239 default: 240 return Retry; 241 } 242 return 0; 243 } 244 245 /* 246 * report a recipient to remote 247 */ 248 char * 249 rcptto(char *to) 250 { 251 String *s; 252 253 s = bangtoat(to); 254 if(toline == 0){ 255 toline = s_new(); 256 s_append(toline, "To: "); 257 } else 258 s_append(toline, ", "); 259 s_append(toline, s_to_c(s)); 260 if(strchr(s_to_c(s), '@')) 261 dBprint("RCPT TO:<%s>\r\n", s_to_c(s)); 262 else{ 263 s_append(toline, "@"); 264 s_append(toline, ddomain); 265 dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain); 266 } 267 switch(getreply()){ 268 case 2: 269 break; 270 case 5: 271 return Giveup; 272 default: 273 return Retry; 274 } 275 return 0; 276 } 277 278 /* 279 * send the damn thing 280 */ 281 char * 282 data(String *from, int unix) 283 { 284 char buf[16*1024]; 285 int i, n; 286 int eof; 287 static char errmsg[ERRLEN]; 288 289 /* 290 * input the first 16k bytes. The header had better fit. 291 */ 292 eof = 0; 293 for(n = 0; n < sizeof(buf) - 1; n += i){ 294 i = read(0, buf+n, sizeof(buf)-1-n); 295 if(i <= 0){ 296 eof = 1; 297 break; 298 } 299 } 300 buf[n] = 0; 301 302 /* 303 * parse the header, turn all addresses into @ format 304 */ 305 yyinit(buf); 306 if(!unix){ 307 yyparse(); 308 convertheader(from); 309 } 310 311 /* 312 * print message observing '.' escapes and using \r\n for \n 313 */ 314 if(!filter){ 315 dBprint("DATA\r\n"); 316 switch(getreply()){ 317 case 3: 318 break; 319 case 5: 320 return Giveup; 321 default: 322 return Retry; 323 } 324 } 325 326 /* 327 * send header. add a sender and a date if there 328 * isn't one 329 */ 330 uneaten = buf; 331 if(!unix){ 332 if(originator==0 && usender) 333 Bprint(&bout, "From: %s\r\n", s_to_c(from)); 334 if(destination == 0 && toline) 335 Bprint(&bout, "%s\r\n", s_to_c(toline)); 336 if(date==0 && udate) 337 printdate(udate); 338 if (usys) 339 uneaten = usys->end + 1; 340 printheader(); 341 if (*uneaten != '\n') 342 putcrnl("\n", 1); 343 } 344 345 /* 346 * send body 347 */ 348 putcrnl(uneaten, buf+n - uneaten); 349 if(eof == 0) 350 for(;;){ 351 n = read(0, buf, sizeof(buf)); 352 if(n < 0){ 353 errstr(errmsg); 354 return errmsg; 355 } 356 if(n == 0) 357 break; 358 putcrnl(buf, n); 359 } 360 if(!filter){ 361 if(last != '\n') 362 dBprint("\r\n.\r\n"); 363 else 364 dBprint(".\r\n"); 365 switch(getreply()){ 366 case 2: 367 break; 368 case 5: 369 return Retry; 370 default: 371 return Giveup; 372 } 373 } 374 return 0; 375 } 376 377 /* 378 * we're leaving 379 */ 380 void 381 quit(void) 382 { 383 dBprint("QUIT\r\n"); 384 getreply(); 385 } 386 387 /* 388 * read a reply into a string, return the reply code 389 */ 390 int 391 getreply(void) 392 { 393 char *line; 394 int rv; 395 396 reply = s_reset(reply); 397 for(;;){ 398 line = getcrnl(reply); 399 if(line == 0) 400 return -1; 401 if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2])) 402 return -1; 403 if(line[3] != '-') 404 break; 405 } 406 rv = atoi(line)/100; 407 return rv; 408 } 409 410 /* 411 * Convert from `bang' to `source routing' format. 412 * 413 * a.x.y!b.p.o!c!d -> @a.x.y:c!d@b.p.o 414 */ 415 void 416 addhostdom(String *buf, char *host) 417 { 418 s_append(buf, "@"); 419 s_append(buf, host); 420 } 421 String * 422 bangtoat(char *addr) 423 { 424 String *buf; 425 register int i; 426 int j, d; 427 char *field[128]; 428 429 /* parse the '!' format address */ 430 buf = s_new(); 431 for(i = 0; addr; i++){ 432 field[i] = addr; 433 addr = strchr(addr, '!'); 434 if(addr) 435 *addr++ = 0; 436 } 437 if (i==1) { 438 s_append(buf, field[0]); 439 return buf; 440 } 441 442 /* 443 * count leading domain fields (non-domains don't count) 444 */ 445 d = 0; 446 for( ; d<i-1; d++) 447 if(strchr(field[d], '.')==0) 448 break; 449 /* 450 * if there are more than 1 leading domain elements, 451 * put them in as source routing 452 */ 453 if(d > 1){ 454 addhostdom(buf, field[0]); 455 for(j=1; j<d-1; j++){ 456 s_append(buf, ","); 457 s_append(buf, "@"); 458 s_append(buf, field[j]); 459 } 460 s_append(buf, ":"); 461 } 462 463 /* 464 * throw in the non-domain elements separated by '!'s 465 */ 466 s_append(buf, field[d]); 467 for(j=d+1; j<=i-1; j++) { 468 s_append(buf, "!"); 469 s_append(buf, field[j]); 470 } 471 if(d) 472 addhostdom(buf, field[d-1]); 473 return buf; 474 } 475 476 /* 477 * convert header addresses to @ format. 478 * if the address is a source address, and a domain is specified, 479 * make sure it falls in the domain. 480 */ 481 void 482 convertheader(String *from) 483 { 484 Field *f; 485 Node *p; 486 String *a; 487 488 if(!unix && strchr(s_to_c(from), '@') == 0){ 489 s_append(from, "@"); 490 s_append(from, hostdomain); 491 } 492 for(f = firstfield; f; f = f->next){ 493 for(p = f->node; p; p = p->next){ 494 if(!p->addr) 495 continue; 496 a = bangtoat(s_to_c(p->s)); 497 s_free(p->s); 498 if(!unix && strchr(s_to_c(a), '@') == 0){ 499 s_append(a, "@"); 500 s_append(a, hostdomain); 501 } 502 p->s = a; 503 } 504 } 505 } 506 507 /* 508 * print out the parsed header 509 */ 510 void 511 printheader(void) 512 { 513 Field *f; 514 Node *p; 515 char *cp; 516 char c[1]; 517 518 for(f = firstfield; f; f = f->next){ 519 for(p = f->node; p; p = p->next){ 520 if(p->s) 521 Bprint(&bout, "%s", s_to_c(p->s)); 522 else { 523 c[0] = p->c; 524 putcrnl(c, 1); 525 } 526 if(p->white){ 527 cp = s_to_c(p->white); 528 putcrnl(cp, strlen(cp)); 529 } 530 uneaten = p->end; 531 } 532 putcrnl("\n", 1); 533 uneaten++; /* skip newline */ 534 } 535 } 536 537 /* 538 * add a domain onto an name, return the new name 539 */ 540 char * 541 domainify(char *name, char *domain) 542 { 543 static String *s; 544 545 if(domain==0 || strchr(name, '.')!=0) 546 return name; 547 548 s = s_reset(s); 549 s_append(s, name); 550 if(*domain != '.') 551 s_append(s, "."); 552 s_append(s, domain); 553 return s_to_c(s); 554 } 555 556 /* 557 * print message observing '.' escapes and using \r\n for \n 558 */ 559 void 560 putcrnl(char *cp, int n) 561 { 562 int c; 563 564 for(; n; n--, cp++){ 565 c = *cp; 566 if(c == '\n') 567 Bputc(&bout, '\r'); 568 else if(c == '.' && last=='\n') 569 Bputc(&bout, '.'); 570 Bputc(&bout, c); 571 last = c; 572 } 573 } 574 575 /* 576 * Get a line including a crnl into a string. Convert crnl into nl. 577 */ 578 char * 579 getcrnl(String *s) 580 { 581 int c; 582 int count; 583 584 count = 0; 585 for(;;){ 586 c = Bgetc(&bin); 587 if(debug) 588 Bputc(&berr, c); 589 switch(c){ 590 case -1: 591 s_terminate(s); 592 fprint(2, "smtp: connection closed unexpectedly by remote system\n"); 593 return 0; 594 case '\r': 595 c = Bgetc(&bin); 596 if(c == '\n'){ 597 s_putc(s, c); 598 if(debug) 599 Bputc(&berr, c); 600 count++; 601 s_terminate(s); 602 return s->ptr - count; 603 } 604 Bungetc(&bin); 605 s_putc(s, '\r'); 606 if(debug) 607 Bputc(&berr, '\r'); 608 count++; 609 break; 610 default: 611 s_putc(s, c); 612 count++; 613 break; 614 } 615 } 616 return 0; 617 } 618 619 /* 620 * print out a parsed date 621 */ 622 void 623 printdate(Node *p) 624 { 625 int sep = 0; 626 627 Bprint(&bout, "Date: %s,", s_to_c(p->s)); 628 for(p = p->next; p; p = p->next){ 629 if(p->s){ 630 if(sep == 0) 631 Bputc(&bout, ' '); 632 if (p->next) 633 Bprint(&bout, "%s", s_to_c(p->s)); 634 else 635 Bprint(&bout, "%s", rewritezone(s_to_c(p->s))); 636 sep = 0; 637 } else { 638 Bputc(&bout, p->c); 639 sep = 1; 640 } 641 } 642 Bprint(&bout, "\r\n"); 643 } 644 645 char * 646 rewritezone(char *z) 647 { 648 Tm *t; 649 int h, m, offset; 650 char sign, *p; 651 char *zones = "ECMP"; 652 static char numeric[6]; 653 654 t = localtime(0); 655 if (t->hour >= 12) { 656 sign = '-'; 657 if (t->min == 0) { 658 h = 24 - t->hour; 659 m = 0; 660 } 661 else { 662 h = 23 - t->hour; 663 m = 60 - t->min; 664 } 665 } 666 else { 667 sign = '+'; 668 h = t->hour; 669 m = t->min; 670 } 671 sprint(numeric, "%c%.2d%.2d", sign, h, m); 672 673 /* leave zone alone if we didn't generate it */ 674 if (strncmp(z, t->zone, 4) != 0) 675 return z; 676 if (strcmp(z, "GMT") == 0 || strcmp(z, "UT") == 0) 677 if (t->hour == 0 && t->min == 0) 678 return z; 679 else 680 return numeric; 681 /* check for North American time zone */ 682 if (z[2] == 'T' && (z[1] == 'S' || z[1] == 'D')) { 683 p = strchr(zones, z[0]); 684 if (p) { 685 offset = 24 - 5 - (p - zones) + (z[1] == 'D'); 686 if (offset == t->hour) 687 return z; 688 } 689 } 690 return numeric; 691 } 692 693 /* 694 * stolen from libc/port/print.c 695 */ 696 #define SIZE 4096 697 #define DOTDOT (&fmt+1) 698 int 699 dBprint(char *fmt, ...) 700 { 701 char buf[SIZE], *out; 702 int n; 703 704 out = doprint(buf, buf+SIZE, fmt, DOTDOT); 705 if(debug){ 706 Bwrite(&berr, buf, (long)(out-buf)); 707 Bflush(&berr); 708 } 709 n = Bwrite(&bout, buf, (long)(out-buf)); 710 Bflush(&bout); 711 return n; 712 } 713