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