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