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