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