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