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