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