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