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