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