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