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