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