1 /* 2 * Copyright (c) 1983 Eric P. Allman 3 * Copyright (c) 1988 Regents of the University of California. 4 * All rights reserved. 5 * 6 * %sccs.include.redist.c% 7 */ 8 9 # include "sendmail.h" 10 11 #ifndef lint 12 #ifdef SMTP 13 static char sccsid[] = "@(#)srvrsmtp.c 6.28 (Berkeley) 03/19/93 (with SMTP)"; 14 #else 15 static char sccsid[] = "@(#)srvrsmtp.c 6.28 (Berkeley) 03/19/93 (without SMTP)"; 16 #endif 17 #endif /* not lint */ 18 19 # include <errno.h> 20 # include <signal.h> 21 22 # ifdef SMTP 23 24 /* 25 ** SMTP -- run the SMTP protocol. 26 ** 27 ** Parameters: 28 ** none. 29 ** 30 ** Returns: 31 ** never. 32 ** 33 ** Side Effects: 34 ** Reads commands from the input channel and processes 35 ** them. 36 */ 37 38 struct cmd 39 { 40 char *cmdname; /* command name */ 41 int cmdcode; /* internal code, see below */ 42 }; 43 44 /* values for cmdcode */ 45 # define CMDERROR 0 /* bad command */ 46 # define CMDMAIL 1 /* mail -- designate sender */ 47 # define CMDRCPT 2 /* rcpt -- designate recipient */ 48 # define CMDDATA 3 /* data -- send message text */ 49 # define CMDRSET 4 /* rset -- reset state */ 50 # define CMDVRFY 5 /* vrfy -- verify address */ 51 # define CMDEXPN 6 /* expn -- expand address */ 52 # define CMDNOOP 7 /* noop -- do nothing */ 53 # define CMDQUIT 8 /* quit -- close connection and die */ 54 # define CMDHELO 9 /* helo -- be polite */ 55 # define CMDHELP 10 /* help -- give usage info */ 56 # define CMDEHLO 11 /* ehlo -- extended helo (RFC 1425) */ 57 /* non-standard commands */ 58 # define CMDONEX 16 /* onex -- sending one transaction only */ 59 # define CMDVERB 17 /* verb -- go into verbose mode */ 60 /* debugging-only commands, only enabled if SMTPDEBUG is defined */ 61 # define CMDDBGQSHOW 24 /* showq -- show send queue */ 62 # define CMDDBGDEBUG 25 /* debug -- set debug mode */ 63 64 static struct cmd CmdTab[] = 65 { 66 "mail", CMDMAIL, 67 "rcpt", CMDRCPT, 68 "data", CMDDATA, 69 "rset", CMDRSET, 70 "vrfy", CMDVRFY, 71 "expn", CMDEXPN, 72 "help", CMDHELP, 73 "noop", CMDNOOP, 74 "quit", CMDQUIT, 75 "helo", CMDHELO, 76 "ehlo", CMDEHLO, 77 "verb", CMDVERB, 78 "onex", CMDONEX, 79 /* 80 * remaining commands are here only 81 * to trap and log attempts to use them 82 */ 83 "showq", CMDDBGQSHOW, 84 "debug", CMDDBGDEBUG, 85 NULL, CMDERROR, 86 }; 87 88 bool InChild = FALSE; /* true if running in a subprocess */ 89 bool OneXact = FALSE; /* one xaction only this run */ 90 91 #define EX_QUIT 22 /* special code for QUIT command */ 92 93 smtp(e) 94 register ENVELOPE *e; 95 { 96 register char *p; 97 register struct cmd *c; 98 char *cmd; 99 static char *skipword(); 100 auto ADDRESS *vrfyqueue; 101 ADDRESS *a; 102 char *sendinghost; 103 bool gotmail; /* mail command received */ 104 bool gothello; /* helo command received */ 105 bool vrfy; /* set if this is a vrfy command */ 106 char *protocol; /* sending protocol */ 107 long msize; /* approximate maximum message size */ 108 auto char *delimptr; 109 char *id; 110 char inp[MAXLINE]; 111 char cmdbuf[MAXLINE]; 112 char hostbuf[MAXNAME]; 113 extern char Version[]; 114 extern char *macvalue(); 115 extern ADDRESS *recipient(); 116 extern ENVELOPE BlankEnvelope; 117 extern ENVELOPE *newenvelope(); 118 extern char *anynet_ntoa(); 119 120 if (OutChannel != stdout) 121 { 122 /* arrange for debugging output to go to remote host */ 123 (void) close(1); 124 (void) dup(fileno(OutChannel)); 125 } 126 settime(e); 127 CurHostName = RealHostName; 128 setproctitle("srvrsmtp %s", CurHostName); 129 expand("\201e", inp, &inp[sizeof inp], e); 130 message("220 %s", inp); 131 SmtpPhase = "startup"; 132 sendinghost = NULL; 133 protocol = NULL; 134 gothello = FALSE; 135 gotmail = FALSE; 136 for (;;) 137 { 138 /* arrange for backout */ 139 if (setjmp(TopFrame) > 0 && InChild) 140 finis(); 141 QuickAbort = FALSE; 142 HoldErrs = FALSE; 143 LogUsrErrs = FALSE; 144 e->e_flags &= ~EF_VRFYONLY; 145 146 /* setup for the read */ 147 e->e_to = NULL; 148 Errors = 0; 149 (void) fflush(stdout); 150 151 /* read the input line */ 152 p = sfgets(inp, sizeof inp, InChannel, TimeOuts.to_nextcommand); 153 154 /* handle errors */ 155 if (p == NULL) 156 { 157 /* end of file, just die */ 158 message("421 %s Lost input channel from %s", 159 MyHostName, CurHostName); 160 #ifdef LOG 161 if (LogLevel > 1) 162 syslog(LOG_NOTICE, "lost input channel from %s", 163 CurHostName); 164 #endif 165 if (InChild) 166 ExitStat = EX_QUIT; 167 finis(); 168 } 169 170 /* clean up end of line */ 171 fixcrlf(inp, TRUE); 172 173 /* echo command to transcript */ 174 if (e->e_xfp != NULL) 175 fprintf(e->e_xfp, "<<< %s\n", inp); 176 177 /* break off command */ 178 for (p = inp; isascii(*p) && isspace(*p); p++) 179 continue; 180 cmd = cmdbuf; 181 while (*p != '\0' && 182 !(isascii(*p) && isspace(*p)) && 183 cmd < &cmdbuf[sizeof cmdbuf - 2]) 184 *cmd++ = *p++; 185 *cmd = '\0'; 186 187 /* throw away leading whitespace */ 188 while (isascii(*p) && isspace(*p)) 189 p++; 190 191 /* decode command */ 192 for (c = CmdTab; c->cmdname != NULL; c++) 193 { 194 if (!strcasecmp(c->cmdname, cmdbuf)) 195 break; 196 } 197 198 /* reset errors */ 199 errno = 0; 200 201 /* process command */ 202 switch (c->cmdcode) 203 { 204 case CMDHELO: /* hello -- introduce yourself */ 205 case CMDEHLO: /* extended hello */ 206 if (c->cmdcode == CMDEHLO) 207 { 208 protocol = "ESMTP"; 209 SmtpPhase = "EHLO"; 210 } 211 else 212 { 213 protocol = "SMTP"; 214 SmtpPhase = "HELO"; 215 } 216 setproctitle("%s: %s", CurHostName, inp); 217 if (strcasecmp(p, MyHostName) == 0) 218 { 219 /* 220 ** Didn't know about alias or MX, 221 ** or connected to an echo server 222 */ 223 224 message("553 %s config error: mail loops back to myself", 225 MyHostName); 226 break; 227 } 228 (void) strcpy(hostbuf, p); 229 (void) strcat(hostbuf, " ("); 230 (void) strcat(hostbuf, anynet_ntoa(&RealHostAddr)); 231 if (strcasecmp(p, RealHostName) != 0) 232 { 233 (void) strcat(hostbuf, "; "); 234 (void) strcat(hostbuf, RealHostName); 235 } 236 (void) strcat(hostbuf, ")"); 237 sendinghost = newstr(hostbuf); 238 239 /* send ext. message -- old systems must ignore */ 240 message("250-%s Hello %s, pleased to meet you", 241 MyHostName, sendinghost); 242 if (!bitset(PRIV_NOEXPN, PrivacyFlags)) 243 message("250-EXPN"); 244 message("250-SIZE"); 245 message("250 HELP"); 246 gothello = TRUE; 247 break; 248 249 case CMDMAIL: /* mail -- designate sender */ 250 SmtpPhase = "MAIL"; 251 252 /* force a sending host even if no HELO given */ 253 if (sendinghost == NULL && macvalue('s', e) == NULL) 254 sendinghost = RealHostName; 255 256 /* check for validity of this command */ 257 if (!gothello && bitset(PRIV_NEEDMAILHELO, PrivacyFlags)) 258 { 259 message("503 Polite people say HELO first"); 260 break; 261 } 262 if (gotmail) 263 { 264 message("503 Sender already specified"); 265 break; 266 } 267 if (InChild) 268 { 269 errno = 0; 270 syserr("503 Nested MAIL command: MAIL %s", p); 271 finis(); 272 } 273 274 /* fork a subprocess to process this command */ 275 if (runinchild("SMTP-MAIL", e) > 0) 276 break; 277 if (sendinghost != NULL) 278 define('s', sendinghost, e); 279 if (protocol == NULL) 280 protocol = "SMTP"; 281 define('r', protocol, e); 282 initsys(e); 283 setproctitle("%s %s: %s", e->e_id, CurHostName, inp); 284 285 /* child -- go do the processing */ 286 p = skipword(p, "from"); 287 if (p == NULL) 288 break; 289 if (setjmp(TopFrame) > 0) 290 { 291 /* this failed -- undo work */ 292 if (InChild) 293 finis(); 294 break; 295 } 296 QuickAbort = TRUE; 297 298 /* must parse sender first */ 299 delimptr = NULL; 300 setsender(p, e, &delimptr, FALSE); 301 p = delimptr; 302 if (p != NULL && *p != '\0') 303 *p++ = '\0'; 304 305 /* now parse ESMTP arguments */ 306 msize = 0; 307 for (; p != NULL && *p != '\0'; p++) 308 { 309 char *kp; 310 char *vp; 311 312 /* locate the beginning of the keyword */ 313 while (isascii(*p) && isspace(*p)) 314 p++; 315 if (*p == '\0') 316 break; 317 kp = p; 318 319 /* skip to the value portion */ 320 while (isascii(*p) && isalnum(*p) || *p == '-') 321 p++; 322 if (*p == '=') 323 { 324 *p++ = '\0'; 325 vp = p; 326 327 /* skip to the end of the value */ 328 while (*p != '\0' && *p != ' ' && 329 !(isascii(*p) && iscntrl(*p)) && 330 *p != '=') 331 p++; 332 } 333 334 if (*p != '\0') 335 *p++ = '\0'; 336 337 if (tTd(19, 1)) 338 printf("MAIL: got arg %s=%s\n", kp, 339 vp == NULL ? "<null>" : vp); 340 341 if (strcasecmp(kp, "size") == 0) 342 { 343 if (kp == NULL) 344 { 345 usrerr("501 SIZE requires a value"); 346 /* NOTREACHED */ 347 } 348 msize = atol(vp); 349 } 350 else 351 { 352 usrerr("501 %s parameter unrecognized", kp); 353 /* NOTREACHED */ 354 } 355 } 356 357 if (!enoughspace(msize)) 358 { 359 message("452 Insufficient disk space; try again later"); 360 break; 361 } 362 message("250 Sender ok"); 363 gotmail = TRUE; 364 break; 365 366 case CMDRCPT: /* rcpt -- designate recipient */ 367 SmtpPhase = "RCPT"; 368 setproctitle("%s %s: %s", e->e_id, CurHostName, inp); 369 if (setjmp(TopFrame) > 0) 370 { 371 e->e_flags &= ~EF_FATALERRS; 372 break; 373 } 374 QuickAbort = TRUE; 375 LogUsrErrs = TRUE; 376 377 /* optimization -- if queueing, don't expand aliases */ 378 if (e->e_sendmode == SM_QUEUE) 379 e->e_flags |= EF_VRFYONLY; 380 381 p = skipword(p, "to"); 382 if (p == NULL) 383 break; 384 a = parseaddr(p, (ADDRESS *) NULL, 1, ' ', NULL, e); 385 if (a == NULL) 386 break; 387 a->q_flags |= QPRIMARY; 388 a = recipient(a, &e->e_sendqueue, e); 389 if (Errors != 0) 390 break; 391 392 /* no errors during parsing, but might be a duplicate */ 393 e->e_to = p; 394 if (!bitset(QBADADDR, a->q_flags)) 395 message("250 Recipient ok"); 396 else 397 { 398 /* punt -- should keep message in ADDRESS.... */ 399 message("550 Addressee unknown"); 400 } 401 e->e_to = NULL; 402 break; 403 404 case CMDDATA: /* data -- text of mail */ 405 SmtpPhase = "DATA"; 406 if (!gotmail) 407 { 408 message("503 Need MAIL command"); 409 break; 410 } 411 else if (e->e_nrcpts <= 0) 412 { 413 message("503 Need RCPT (recipient)"); 414 break; 415 } 416 417 /* collect the text of the message */ 418 SmtpPhase = "collect"; 419 setproctitle("%s %s: %s", e->e_id, CurHostName, inp); 420 collect(TRUE, e); 421 if (Errors != 0) 422 break; 423 424 /* 425 ** Arrange to send to everyone. 426 ** If sending to multiple people, mail back 427 ** errors rather than reporting directly. 428 ** In any case, don't mail back errors for 429 ** anything that has happened up to 430 ** now (the other end will do this). 431 ** Truncate our transcript -- the mail has gotten 432 ** to us successfully, and if we have 433 ** to mail this back, it will be easier 434 ** on the reader. 435 ** Then send to everyone. 436 ** Finally give a reply code. If an error has 437 ** already been given, don't mail a 438 ** message back. 439 ** We goose error returns by clearing error bit. 440 */ 441 442 SmtpPhase = "delivery"; 443 if (e->e_nrcpts != 1) 444 { 445 HoldErrs = TRUE; 446 e->e_errormode = EM_MAIL; 447 } 448 e->e_flags &= ~EF_FATALERRS; 449 e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp); 450 id = e->e_id; 451 452 /* send to all recipients */ 453 sendall(e, SM_DEFAULT); 454 e->e_to = NULL; 455 456 /* save statistics */ 457 markstats(e, (ADDRESS *) NULL); 458 459 /* issue success if appropriate and reset */ 460 if (Errors == 0 || HoldErrs) 461 message("250 %s OK", id); 462 else 463 e->e_flags &= ~EF_FATALERRS; 464 465 /* if in a child, pop back to our parent */ 466 if (InChild) 467 finis(); 468 469 /* clean up a bit */ 470 gotmail = FALSE; 471 dropenvelope(e); 472 CurEnv = e = newenvelope(e, CurEnv); 473 e->e_flags = BlankEnvelope.e_flags; 474 break; 475 476 case CMDRSET: /* rset -- reset state */ 477 message("250 Reset state"); 478 if (InChild) 479 finis(); 480 481 /* clean up a bit */ 482 gotmail = FALSE; 483 dropenvelope(e); 484 CurEnv = e = newenvelope(e, CurEnv); 485 break; 486 487 case CMDVRFY: /* vrfy -- verify address */ 488 case CMDEXPN: /* expn -- expand address */ 489 vrfy = c->cmdcode == CMDVRFY; 490 if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN, 491 PrivacyFlags)) 492 { 493 if (vrfy) 494 message("252 Who's to say?"); 495 else 496 message("502 That's none of your business"); 497 break; 498 } 499 else if (!gothello && 500 bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO, 501 PrivacyFlags)) 502 { 503 message("503 I demand that you introduce yourself first"); 504 break; 505 } 506 if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0) 507 break; 508 setproctitle("%s: %s", CurHostName, inp); 509 #ifdef LOG 510 if (LogLevel > 5) 511 syslog(LOG_INFO, "%s: %s", CurHostName, inp); 512 #endif 513 vrfyqueue = NULL; 514 QuickAbort = TRUE; 515 if (vrfy) 516 e->e_flags |= EF_VRFYONLY; 517 (void) sendtolist(p, (ADDRESS *) NULL, &vrfyqueue, e); 518 if (Errors != 0) 519 { 520 if (InChild) 521 finis(); 522 break; 523 } 524 while (vrfyqueue != NULL) 525 { 526 register ADDRESS *a = vrfyqueue->q_next; 527 char *code; 528 529 while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 530 a = a->q_next; 531 532 if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 533 printvrfyaddr(vrfyqueue, a == NULL); 534 else if (a == NULL) 535 message("554 Self destructive alias loop"); 536 vrfyqueue = a; 537 } 538 if (InChild) 539 finis(); 540 break; 541 542 case CMDHELP: /* help -- give user info */ 543 help(p); 544 break; 545 546 case CMDNOOP: /* noop -- do nothing */ 547 message("200 OK"); 548 break; 549 550 case CMDQUIT: /* quit -- leave mail */ 551 message("221 %s closing connection", MyHostName); 552 if (InChild) 553 ExitStat = EX_QUIT; 554 finis(); 555 556 case CMDVERB: /* set verbose mode */ 557 Verbose = TRUE; 558 e->e_sendmode = SM_DELIVER; 559 message("200 Verbose mode"); 560 break; 561 562 case CMDONEX: /* doing one transaction only */ 563 OneXact = TRUE; 564 message("200 Only one transaction"); 565 break; 566 567 # ifdef SMTPDEBUG 568 case CMDDBGQSHOW: /* show queues */ 569 printf("Send Queue="); 570 printaddr(e->e_sendqueue, TRUE); 571 break; 572 573 case CMDDBGDEBUG: /* set debug mode */ 574 tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 575 tTflag(p); 576 message("200 Debug set"); 577 break; 578 579 # else /* not SMTPDEBUG */ 580 581 case CMDDBGQSHOW: /* show queues */ 582 case CMDDBGDEBUG: /* set debug mode */ 583 # ifdef LOG 584 if (LogLevel > 0) 585 syslog(LOG_NOTICE, 586 "\"%s\" command from %s (%s)", 587 c->cmdname, RealHostName, 588 anynet_ntoa(&RealHostAddr)); 589 # endif 590 /* FALL THROUGH */ 591 # endif /* SMTPDEBUG */ 592 593 case CMDERROR: /* unknown command */ 594 message("500 Command unrecognized"); 595 break; 596 597 default: 598 errno = 0; 599 syserr("500 smtp: unknown code %d", c->cmdcode); 600 break; 601 } 602 } 603 } 604 /* 605 ** SKIPWORD -- skip a fixed word. 606 ** 607 ** Parameters: 608 ** p -- place to start looking. 609 ** w -- word to skip. 610 ** 611 ** Returns: 612 ** p following w. 613 ** NULL on error. 614 ** 615 ** Side Effects: 616 ** clobbers the p data area. 617 */ 618 619 static char * 620 skipword(p, w) 621 register char *p; 622 char *w; 623 { 624 register char *q; 625 626 /* find beginning of word */ 627 while (isascii(*p) && isspace(*p)) 628 p++; 629 q = p; 630 631 /* find end of word */ 632 while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p))) 633 p++; 634 while (isascii(*p) && isspace(*p)) 635 *p++ = '\0'; 636 if (*p != ':') 637 { 638 syntax: 639 message("501 Syntax error"); 640 Errors++; 641 return (NULL); 642 } 643 *p++ = '\0'; 644 while (isascii(*p) && isspace(*p)) 645 p++; 646 647 /* see if the input word matches desired word */ 648 if (strcasecmp(q, w)) 649 goto syntax; 650 651 return (p); 652 } 653 /* 654 ** PRINTVRFYADDR -- print an entry in the verify queue 655 ** 656 ** Parameters: 657 ** a -- the address to print 658 ** last -- set if this is the last one. 659 ** 660 ** Returns: 661 ** none. 662 ** 663 ** Side Effects: 664 ** Prints the appropriate 250 codes. 665 */ 666 667 printvrfyaddr(a, last) 668 register ADDRESS *a; 669 bool last; 670 { 671 char fmtbuf[20]; 672 673 strcpy(fmtbuf, "250"); 674 fmtbuf[3] = last ? ' ' : '-'; 675 676 if (strchr(a->q_paddr, '<') != NULL) 677 strcpy(&fmtbuf[4], "%s"); 678 else if (a->q_fullname == NULL) 679 strcpy(&fmtbuf[4], "<%s>"); 680 else 681 { 682 strcpy(&fmtbuf[4], "%s <%s>"); 683 message(fmtbuf, a->q_fullname, a->q_paddr); 684 return; 685 } 686 message(fmtbuf, a->q_paddr); 687 } 688 /* 689 ** HELP -- implement the HELP command. 690 ** 691 ** Parameters: 692 ** topic -- the topic we want help for. 693 ** 694 ** Returns: 695 ** none. 696 ** 697 ** Side Effects: 698 ** outputs the help file to message output. 699 */ 700 701 help(topic) 702 char *topic; 703 { 704 register FILE *hf; 705 int len; 706 char buf[MAXLINE]; 707 bool noinfo; 708 709 if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 710 { 711 /* no help */ 712 errno = 0; 713 message("502 HELP not implemented"); 714 return; 715 } 716 717 if (topic == NULL || *topic == '\0') 718 topic = "smtp"; 719 else 720 makelower(topic); 721 722 len = strlen(topic); 723 noinfo = TRUE; 724 725 while (fgets(buf, sizeof buf, hf) != NULL) 726 { 727 if (strncmp(buf, topic, len) == 0) 728 { 729 register char *p; 730 731 p = strchr(buf, '\t'); 732 if (p == NULL) 733 p = buf; 734 else 735 p++; 736 fixcrlf(p, TRUE); 737 message("214-%s", p); 738 noinfo = FALSE; 739 } 740 } 741 742 if (noinfo) 743 message("504 HELP topic unknown"); 744 else 745 message("214 End of HELP info"); 746 (void) fclose(hf); 747 } 748 /* 749 ** RUNINCHILD -- return twice -- once in the child, then in the parent again 750 ** 751 ** Parameters: 752 ** label -- a string used in error messages 753 ** 754 ** Returns: 755 ** zero in the child 756 ** one in the parent 757 ** 758 ** Side Effects: 759 ** none. 760 */ 761 762 runinchild(label, e) 763 char *label; 764 register ENVELOPE *e; 765 { 766 int childpid; 767 768 if (!OneXact) 769 { 770 childpid = dofork(); 771 if (childpid < 0) 772 { 773 syserr("%s: cannot fork", label); 774 return (1); 775 } 776 if (childpid > 0) 777 { 778 auto int st; 779 780 /* parent -- wait for child to complete */ 781 st = waitfor(childpid); 782 if (st == -1) 783 syserr("%s: lost child", label); 784 785 /* if we exited on a QUIT command, complete the process */ 786 if (st == (EX_QUIT << 8)) 787 finis(); 788 789 return (1); 790 } 791 else 792 { 793 /* child */ 794 InChild = TRUE; 795 QuickAbort = FALSE; 796 clearenvelope(e, FALSE); 797 } 798 } 799 800 /* open alias database */ 801 initaliases(AliasFile, FALSE, e); 802 803 return (0); 804 } 805 806 # endif /* SMTP */ 807