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