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