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