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