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