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