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