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