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