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.21 (Berkeley) 03/01/93 (with SMTP)"; 14 #else 15 static char sccsid[] = "@(#)srvrsmtp.c 6.21 (Berkeley) 03/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 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 117 if (OutChannel != stdout) 118 { 119 /* arrange for debugging output to go to remote host */ 120 (void) close(1); 121 (void) dup(fileno(OutChannel)); 122 } 123 settime(e); 124 CurHostName = RealHostName; 125 setproctitle("srvrsmtp %s", CurHostName); 126 expand("\201e", inp, &inp[sizeof inp], e); 127 message("220 %s", inp); 128 SmtpPhase = "startup"; 129 sendinghost = NULL; 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 if (strcasecmp(p, MyHostName) == 0) 215 { 216 /* 217 ** Didn't know about alias or MX, 218 ** or connected to an echo server 219 */ 220 221 message("553 %s config error: mail loops back to myself", 222 MyHostName); 223 break; 224 } 225 if (strcasecmp(p, RealHostName) != 0) 226 { 227 char hostbuf[MAXNAME]; 228 229 (void) sprintf(hostbuf, "%s (%s)", p, RealHostName); 230 sendinghost = newstr(hostbuf); 231 } 232 else 233 sendinghost = newstr(p); 234 235 /* send ext. message -- old systems must ignore */ 236 message("250-%s Hello %s, pleased to meet you", 237 MyHostName, sendinghost); 238 if (!bitset(PRIV_NOEXPN, PrivacyFlags)) 239 message("250-EXPN"); 240 message("250 HELP"); 241 gothello = TRUE; 242 break; 243 244 case CMDMAIL: /* mail -- designate sender */ 245 SmtpPhase = "MAIL"; 246 247 /* force a sending host even if no HELO given */ 248 if (sendinghost == NULL && macvalue('s', e) == NULL) 249 sendinghost = RealHostName; 250 251 /* check for validity of this command */ 252 if (!gothello && bitset(PRIV_NEEDMAILHELO, PrivacyFlags)) 253 { 254 message("503 Polite people say HELO first"); 255 break; 256 } 257 if (gotmail) 258 { 259 message("503 Sender already specified"); 260 break; 261 } 262 if (InChild) 263 { 264 errno = 0; 265 syserr("503 Nested MAIL command: MAIL %s", p); 266 finis(); 267 } 268 269 /* fork a subprocess to process this command */ 270 if (runinchild("SMTP-MAIL", e) > 0) 271 break; 272 if (sendinghost != NULL) 273 define('s', sendinghost, e); 274 if (protocol == NULL) 275 protocol = "SMTP"; 276 define('r', protocol, 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); 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 SmtpPhase = "RCPT"; 363 setproctitle("%s %s: %s", e->e_id, CurHostName, inp); 364 if (setjmp(TopFrame) > 0) 365 { 366 e->e_flags &= ~EF_FATALERRS; 367 break; 368 } 369 QuickAbort = TRUE; 370 LogUsrErrs = TRUE; 371 372 /* optimization -- if queueing, don't expand aliases */ 373 if (SendMode == SM_QUEUE) 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 /* collect the text of the message */ 413 SmtpPhase = "collect"; 414 setproctitle("%s %s: %s", e->e_id, CurHostName, inp); 415 collect(TRUE, e); 416 if (Errors != 0) 417 break; 418 419 /* 420 ** Arrange to send to everyone. 421 ** If sending to multiple people, mail back 422 ** errors rather than reporting directly. 423 ** In any case, don't mail back errors for 424 ** anything that has happened up to 425 ** now (the other end will do this). 426 ** Truncate our transcript -- the mail has gotten 427 ** to us successfully, and if we have 428 ** to mail this back, it will be easier 429 ** on the reader. 430 ** Then send to everyone. 431 ** Finally give a reply code. If an error has 432 ** already been given, don't mail a 433 ** message back. 434 ** We goose error returns by clearing error bit. 435 */ 436 437 SmtpPhase = "delivery"; 438 if (e->e_nrcpts != 1) 439 { 440 HoldErrs = TRUE; 441 ErrorMode = EM_MAIL; 442 } 443 e->e_flags &= ~EF_FATALERRS; 444 e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp); 445 446 /* send to all recipients */ 447 sendall(e, SM_DEFAULT); 448 e->e_to = NULL; 449 450 /* save statistics */ 451 markstats(e, (ADDRESS *) NULL); 452 453 /* issue success if appropriate and reset */ 454 if (Errors == 0 || HoldErrs) 455 message("250 Ok"); 456 else 457 e->e_flags &= ~EF_FATALERRS; 458 459 /* if in a child, pop back to our parent */ 460 if (InChild) 461 finis(); 462 463 /* clean up a bit */ 464 gotmail = FALSE; 465 dropenvelope(e); 466 CurEnv = e = newenvelope(e, CurEnv); 467 e->e_flags = BlankEnvelope.e_flags; 468 break; 469 470 case CMDRSET: /* rset -- reset state */ 471 message("250 Reset state"); 472 if (InChild) 473 finis(); 474 475 /* clean up a bit */ 476 gotmail = FALSE; 477 dropenvelope(e); 478 CurEnv = e = newenvelope(e, CurEnv); 479 break; 480 481 case CMDVRFY: /* vrfy -- verify address */ 482 case CMDEXPN: /* expn -- expand address */ 483 vrfy = c->cmdcode == CMDVRFY; 484 if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN, 485 PrivacyFlags)) 486 { 487 message("502 That's none of your business"); 488 break; 489 } 490 else if (!gothello && 491 bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO, 492 PrivacyFlags)) 493 { 494 message("503 I demand that you introduce yourself first"); 495 break; 496 } 497 if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0) 498 break; 499 setproctitle("%s: %s", CurHostName, inp); 500 #ifdef LOG 501 if (LogLevel > 5) 502 syslog(LOG_INFO, "%s: %s", CurHostName, inp); 503 #endif 504 vrfyqueue = NULL; 505 QuickAbort = TRUE; 506 if (vrfy) 507 e->e_flags |= EF_VRFYONLY; 508 (void) sendtolist(p, (ADDRESS *) NULL, &vrfyqueue, e); 509 if (Errors != 0) 510 { 511 if (InChild) 512 finis(); 513 break; 514 } 515 while (vrfyqueue != NULL) 516 { 517 register ADDRESS *a = vrfyqueue->q_next; 518 char *code; 519 520 while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 521 a = a->q_next; 522 523 if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 524 printvrfyaddr(vrfyqueue, a == NULL); 525 else if (a == NULL) 526 message("554 Self destructive alias loop"); 527 vrfyqueue = a; 528 } 529 if (InChild) 530 finis(); 531 break; 532 533 case CMDHELP: /* help -- give user info */ 534 help(p); 535 break; 536 537 case CMDNOOP: /* noop -- do nothing */ 538 message("200 OK"); 539 break; 540 541 case CMDQUIT: /* quit -- leave mail */ 542 message("221 %s closing connection", MyHostName); 543 if (InChild) 544 ExitStat = EX_QUIT; 545 finis(); 546 547 case CMDVERB: /* set verbose mode */ 548 Verbose = TRUE; 549 SendMode = SM_DELIVER; 550 message("200 Verbose mode"); 551 break; 552 553 case CMDONEX: /* doing one transaction only */ 554 OneXact = TRUE; 555 message("200 Only one transaction"); 556 break; 557 558 # ifdef SMTPDEBUG 559 case CMDDBGQSHOW: /* show queues */ 560 printf("Send Queue="); 561 printaddr(e->e_sendqueue, TRUE); 562 break; 563 564 case CMDDBGDEBUG: /* set debug mode */ 565 tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 566 tTflag(p); 567 message("200 Debug set"); 568 break; 569 570 # else /* not SMTPDEBUG */ 571 572 case CMDDBGQSHOW: /* show queues */ 573 case CMDDBGDEBUG: /* set debug mode */ 574 # ifdef LOG 575 if (LogLevel > 0) 576 syslog(LOG_NOTICE, 577 "\"%s\" command from %s (%s)", 578 c->cmdname, RealHostName, 579 inet_ntoa(RealHostAddr.sin_addr)); 580 # endif 581 /* FALL THROUGH */ 582 # endif /* SMTPDEBUG */ 583 584 case CMDERROR: /* unknown command */ 585 message("500 Command unrecognized"); 586 break; 587 588 default: 589 errno = 0; 590 syserr("500 smtp: unknown code %d", c->cmdcode); 591 break; 592 } 593 } 594 } 595 /* 596 ** SKIPWORD -- skip a fixed word. 597 ** 598 ** Parameters: 599 ** p -- place to start looking. 600 ** w -- word to skip. 601 ** 602 ** Returns: 603 ** p following w. 604 ** NULL on error. 605 ** 606 ** Side Effects: 607 ** clobbers the p data area. 608 */ 609 610 static char * 611 skipword(p, w) 612 register char *p; 613 char *w; 614 { 615 register char *q; 616 617 /* find beginning of word */ 618 while (isascii(*p) && isspace(*p)) 619 p++; 620 q = p; 621 622 /* find end of word */ 623 while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p))) 624 p++; 625 while (isascii(*p) && isspace(*p)) 626 *p++ = '\0'; 627 if (*p != ':') 628 { 629 syntax: 630 message("501 Syntax error"); 631 Errors++; 632 return (NULL); 633 } 634 *p++ = '\0'; 635 while (isascii(*p) && isspace(*p)) 636 p++; 637 638 /* see if the input word matches desired word */ 639 if (strcasecmp(q, w)) 640 goto syntax; 641 642 return (p); 643 } 644 /* 645 ** PRINTVRFYADDR -- print an entry in the verify queue 646 ** 647 ** Parameters: 648 ** a -- the address to print 649 ** last -- set if this is the last one. 650 ** 651 ** Returns: 652 ** none. 653 ** 654 ** Side Effects: 655 ** Prints the appropriate 250 codes. 656 */ 657 658 printvrfyaddr(a, last) 659 register ADDRESS *a; 660 bool last; 661 { 662 char fmtbuf[20]; 663 664 strcpy(fmtbuf, "250"); 665 fmtbuf[3] = last ? ' ' : '-'; 666 667 if (strchr(a->q_paddr, '<') != NULL) 668 strcpy(&fmtbuf[4], "%s"); 669 else if (a->q_fullname == NULL) 670 strcpy(&fmtbuf[4], "<%s>"); 671 else 672 { 673 strcpy(&fmtbuf[4], "%s <%s>"); 674 message(fmtbuf, a->q_fullname, a->q_paddr); 675 return; 676 } 677 message(fmtbuf, a->q_paddr); 678 } 679 /* 680 ** HELP -- implement the HELP command. 681 ** 682 ** Parameters: 683 ** topic -- the topic we want help for. 684 ** 685 ** Returns: 686 ** none. 687 ** 688 ** Side Effects: 689 ** outputs the help file to message output. 690 */ 691 692 help(topic) 693 char *topic; 694 { 695 register FILE *hf; 696 int len; 697 char buf[MAXLINE]; 698 bool noinfo; 699 700 if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 701 { 702 /* no help */ 703 errno = 0; 704 message("502 HELP not implemented"); 705 return; 706 } 707 708 if (topic == NULL || *topic == '\0') 709 topic = "smtp"; 710 else 711 makelower(topic); 712 713 len = strlen(topic); 714 noinfo = TRUE; 715 716 while (fgets(buf, sizeof buf, hf) != NULL) 717 { 718 if (strncmp(buf, topic, len) == 0) 719 { 720 register char *p; 721 722 p = strchr(buf, '\t'); 723 if (p == NULL) 724 p = buf; 725 else 726 p++; 727 fixcrlf(p, TRUE); 728 message("214-%s", p); 729 noinfo = FALSE; 730 } 731 } 732 733 if (noinfo) 734 message("504 HELP topic unknown"); 735 else 736 message("214 End of HELP info"); 737 (void) fclose(hf); 738 } 739 /* 740 ** RUNINCHILD -- return twice -- once in the child, then in the parent again 741 ** 742 ** Parameters: 743 ** label -- a string used in error messages 744 ** 745 ** Returns: 746 ** zero in the child 747 ** one in the parent 748 ** 749 ** Side Effects: 750 ** none. 751 */ 752 753 runinchild(label, e) 754 char *label; 755 register ENVELOPE *e; 756 { 757 int childpid; 758 759 if (!OneXact) 760 { 761 childpid = dofork(); 762 if (childpid < 0) 763 { 764 syserr("%s: cannot fork", label); 765 return (1); 766 } 767 if (childpid > 0) 768 { 769 auto int st; 770 771 /* parent -- wait for child to complete */ 772 st = waitfor(childpid); 773 if (st == -1) 774 syserr("%s: lost child", label); 775 776 /* if we exited on a QUIT command, complete the process */ 777 if (st == (EX_QUIT << 8)) 778 finis(); 779 780 return (1); 781 } 782 else 783 { 784 /* child */ 785 InChild = TRUE; 786 QuickAbort = FALSE; 787 clearenvelope(e, FALSE); 788 } 789 } 790 791 /* open alias database */ 792 initaliases(AliasFile, FALSE, e); 793 794 return (0); 795 } 796 797 # endif /* SMTP */ 798