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