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