122712Sdist /* 234921Sbostic * Copyright (c) 1983 Eric P. Allman 333731Sbostic * Copyright (c) 1988 Regents of the University of California. 433731Sbostic * All rights reserved. 533731Sbostic * 642829Sbostic * %sccs.include.redist.c% 733731Sbostic */ 822712Sdist 933731Sbostic # include "sendmail.h" 1022712Sdist 1133731Sbostic #ifndef lint 1233731Sbostic #ifdef SMTP 13*51954Seric static char sccsid[] = "@(#)srvrsmtp.c 5.33 (Berkeley) 12/15/91 (with SMTP)"; 1433731Sbostic #else 15*51954Seric static char sccsid[] = "@(#)srvrsmtp.c 5.33 (Berkeley) 12/15/91 (without SMTP)"; 1633731Sbostic #endif 1733731Sbostic #endif /* not lint */ 1833731Sbostic 199339Seric # include <errno.h> 2011728Seric # include <signal.h> 214549Seric 2233731Sbostic # ifdef SMTP 234556Seric 244549Seric /* 254549Seric ** SMTP -- run the SMTP protocol. 264549Seric ** 274549Seric ** Parameters: 284549Seric ** none. 294549Seric ** 304549Seric ** Returns: 314549Seric ** never. 324549Seric ** 334549Seric ** Side Effects: 344549Seric ** Reads commands from the input channel and processes 354549Seric ** them. 364549Seric */ 374549Seric 384549Seric struct cmd 394549Seric { 404549Seric char *cmdname; /* command name */ 414549Seric int cmdcode; /* internal code, see below */ 424549Seric }; 434549Seric 444549Seric /* values for cmdcode */ 454549Seric # define CMDERROR 0 /* bad command */ 464549Seric # define CMDMAIL 1 /* mail -- designate sender */ 474976Seric # define CMDRCPT 2 /* rcpt -- designate recipient */ 484549Seric # define CMDDATA 3 /* data -- send message text */ 499339Seric # define CMDRSET 4 /* rset -- reset state */ 509339Seric # define CMDVRFY 5 /* vrfy -- verify address */ 519339Seric # define CMDHELP 6 /* help -- give usage info */ 529339Seric # define CMDNOOP 7 /* noop -- do nothing */ 539339Seric # define CMDQUIT 8 /* quit -- close connection and die */ 549339Seric # define CMDHELO 9 /* helo -- be polite */ 5536230Skarels # define CMDONEX 10 /* onex -- sending one transaction only */ 5636230Skarels # define CMDVERB 11 /* verb -- go into verbose mode */ 5736230Skarels /* debugging-only commands, only enabled if SMTPDEBUG is defined */ 5836230Skarels # define CMDDBGQSHOW 12 /* showq -- show send queue */ 5936230Skarels # define CMDDBGDEBUG 13 /* debug -- set debug mode */ 604549Seric 614549Seric static struct cmd CmdTab[] = 624549Seric { 634549Seric "mail", CMDMAIL, 644976Seric "rcpt", CMDRCPT, 654549Seric "data", CMDDATA, 664549Seric "rset", CMDRSET, 674549Seric "vrfy", CMDVRFY, 687762Seric "expn", CMDVRFY, 694549Seric "help", CMDHELP, 704549Seric "noop", CMDNOOP, 714549Seric "quit", CMDQUIT, 724976Seric "helo", CMDHELO, 738544Seric "verb", CMDVERB, 749314Seric "onex", CMDONEX, 7536230Skarels /* 7636230Skarels * remaining commands are here only 7736230Skarels * to trap and log attempts to use them 7836230Skarels */ 799339Seric "showq", CMDDBGQSHOW, 808544Seric "debug", CMDDBGDEBUG, 814549Seric NULL, CMDERROR, 824549Seric }; 834549Seric 849339Seric bool InChild = FALSE; /* true if running in a subprocess */ 859378Seric bool OneXact = FALSE; /* one xaction only this run */ 8611146Seric 879339Seric #define EX_QUIT 22 /* special code for QUIT command */ 888544Seric 894549Seric smtp() 904549Seric { 914549Seric register char *p; 928544Seric register struct cmd *c; 934549Seric char *cmd; 9446928Sbostic static char *skipword(); 954549Seric bool hasmail; /* mail command received */ 965003Seric auto ADDRESS *vrfyqueue; 9712612Seric ADDRESS *a; 9830448Seric char *sendinghost; 998544Seric char inp[MAXLINE]; 10024981Seric char cmdbuf[100]; 1017124Seric extern char Version[]; 10211151Seric extern char *macvalue(); 10312612Seric extern ADDRESS *recipient(); 10424943Seric extern ENVELOPE BlankEnvelope; 10524943Seric extern ENVELOPE *newenvelope(); 1064549Seric 1075003Seric hasmail = FALSE; 1087363Seric if (OutChannel != stdout) 1097363Seric { 1107363Seric /* arrange for debugging output to go to remote host */ 1117363Seric (void) close(1); 1127363Seric (void) dup(fileno(OutChannel)); 1137363Seric } 11411931Seric settime(); 11524971Seric if (RealHostName != NULL) 11625050Seric { 11725050Seric CurHostName = RealHostName; 11825050Seric setproctitle("srvrsmtp %s", CurHostName); 11925050Seric } 12025050Seric else 12125050Seric { 12225050Seric /* this must be us!! */ 12325050Seric CurHostName = MyHostName; 12425050Seric } 12516153Seric expand("\001e", inp, &inp[sizeof inp], CurEnv); 12610708Seric message("220", inp); 12724943Seric SmtpPhase = "startup"; 12830448Seric sendinghost = NULL; 1294549Seric for (;;) 1304549Seric { 13112612Seric /* arrange for backout */ 13212612Seric if (setjmp(TopFrame) > 0 && InChild) 13312612Seric finis(); 13412612Seric QuickAbort = FALSE; 13512612Seric HoldErrs = FALSE; 13651951Seric LogUsrErrs = FALSE; 13712612Seric 1387356Seric /* setup for the read */ 1396907Seric CurEnv->e_to = NULL; 1404577Seric Errors = 0; 1417275Seric (void) fflush(stdout); 1427356Seric 1437356Seric /* read the input line */ 1447685Seric p = sfgets(inp, sizeof inp, InChannel); 1457356Seric 1467685Seric /* handle errors */ 1477356Seric if (p == NULL) 1487356Seric { 1494549Seric /* end of file, just die */ 15036230Skarels message("421", "%s Lost input channel from %s", 15125050Seric MyHostName, CurHostName); 1524549Seric finis(); 1534549Seric } 1544549Seric 1554549Seric /* clean up end of line */ 1564558Seric fixcrlf(inp, TRUE); 1574549Seric 1584713Seric /* echo command to transcript */ 1599545Seric if (CurEnv->e_xfp != NULL) 1609545Seric fprintf(CurEnv->e_xfp, "<<< %s\n", inp); 1614713Seric 1624549Seric /* break off command */ 1634549Seric for (p = inp; isspace(*p); p++) 1644549Seric continue; 1654549Seric cmd = p; 16624981Seric for (cmd = cmdbuf; *p != '\0' && !isspace(*p); ) 16724981Seric *cmd++ = *p++; 16824981Seric *cmd = '\0'; 1694549Seric 17025691Seric /* throw away leading whitespace */ 17125691Seric while (isspace(*p)) 17225691Seric p++; 17325691Seric 1744549Seric /* decode command */ 1754549Seric for (c = CmdTab; c->cmdname != NULL; c++) 1764549Seric { 17733725Sbostic if (!strcasecmp(c->cmdname, cmdbuf)) 1784549Seric break; 1794549Seric } 1804549Seric 181*51954Seric /* reset errors */ 182*51954Seric errno = 0; 183*51954Seric 1844549Seric /* process command */ 1854549Seric switch (c->cmdcode) 1864549Seric { 1874976Seric case CMDHELO: /* hello -- introduce yourself */ 18824943Seric SmtpPhase = "HELO"; 18925050Seric setproctitle("%s: %s", CurHostName, inp); 19033725Sbostic if (!strcasecmp(p, MyHostName)) 19114877Seric { 19236230Skarels /* 19336230Skarels * didn't know about alias, 19436230Skarels * or connected to an echo server 19536230Skarels */ 19647570Seric message("553", "%s config error: mail loops back to myself", 19747570Seric MyHostName); 19814877Seric break; 19914877Seric } 20033725Sbostic if (RealHostName != NULL && strcasecmp(p, RealHostName)) 20111146Seric { 20224981Seric char hostbuf[MAXNAME]; 20311146Seric 20424981Seric (void) sprintf(hostbuf, "%s (%s)", p, RealHostName); 20530448Seric sendinghost = newstr(hostbuf); 20611146Seric } 20711146Seric else 20830448Seric sendinghost = newstr(p); 2094997Seric message("250", "%s Hello %s, pleased to meet you", 21036230Skarels MyHostName, sendinghost); 2114976Seric break; 2124976Seric 2134549Seric case CMDMAIL: /* mail -- designate sender */ 21424943Seric SmtpPhase = "MAIL"; 21524943Seric 21611151Seric /* force a sending host even if no HELO given */ 21711151Seric if (RealHostName != NULL && macvalue('s', CurEnv) == NULL) 21830448Seric sendinghost = RealHostName; 21911151Seric 2209314Seric /* check for validity of this command */ 2214558Seric if (hasmail) 2224558Seric { 2234558Seric message("503", "Sender already specified"); 2244558Seric break; 2254558Seric } 2269339Seric if (InChild) 2279339Seric { 22836230Skarels errno = 0; 2299339Seric syserr("Nested MAIL command"); 2309339Seric exit(0); 2319339Seric } 2329339Seric 2339339Seric /* fork a subprocess to process this command */ 2349339Seric if (runinchild("SMTP-MAIL") > 0) 2359339Seric break; 23630448Seric define('s', sendinghost, CurEnv); 23736579Sbostic define('r', "SMTP", CurEnv); 2389339Seric initsys(); 23925016Seric setproctitle("%s %s: %s", CurEnv->e_id, 24025050Seric CurHostName, inp); 2419339Seric 2429339Seric /* child -- go do the processing */ 2434549Seric p = skipword(p, "from"); 2444549Seric if (p == NULL) 2454549Seric break; 2464549Seric setsender(p); 2474577Seric if (Errors == 0) 2484549Seric { 2494549Seric message("250", "Sender ok"); 2504549Seric hasmail = TRUE; 2514549Seric } 2529339Seric else if (InChild) 2539339Seric finis(); 2544549Seric break; 2554549Seric 2564976Seric case CMDRCPT: /* rcpt -- designate recipient */ 25724943Seric SmtpPhase = "RCPT"; 25825016Seric setproctitle("%s %s: %s", CurEnv->e_id, 25925050Seric CurHostName, inp); 26012612Seric if (setjmp(TopFrame) > 0) 26114785Seric { 26214785Seric CurEnv->e_flags &= ~EF_FATALERRS; 26312612Seric break; 26414785Seric } 26512612Seric QuickAbort = TRUE; 26651951Seric LogUsrErrs = TRUE; 2674549Seric p = skipword(p, "to"); 2684549Seric if (p == NULL) 2694549Seric break; 27016140Seric a = parseaddr(p, (ADDRESS *) NULL, 1, '\0'); 27112612Seric if (a == NULL) 27212612Seric break; 27316886Seric a->q_flags |= QPRIMARY; 27412612Seric a = recipient(a, &CurEnv->e_sendqueue); 27512612Seric if (Errors != 0) 27612612Seric break; 27712612Seric 27812612Seric /* no errors during parsing, but might be a duplicate */ 27912612Seric CurEnv->e_to = p; 28012612Seric if (!bitset(QBADADDR, a->q_flags)) 28112612Seric message("250", "Recipient ok"); 28212612Seric else 2834549Seric { 28412612Seric /* punt -- should keep message in ADDRESS.... */ 28512612Seric message("550", "Addressee unknown"); 2864549Seric } 28712612Seric CurEnv->e_to = NULL; 2884549Seric break; 2894549Seric 2904549Seric case CMDDATA: /* data -- text of mail */ 29124943Seric SmtpPhase = "DATA"; 2924976Seric if (!hasmail) 2934549Seric { 2944976Seric message("503", "Need MAIL command"); 2954976Seric break; 2964549Seric } 29724943Seric else if (CurEnv->e_nrcpts <= 0) 2984549Seric { 2994976Seric message("503", "Need RCPT (recipient)"); 3004976Seric break; 3014549Seric } 3024976Seric 3034976Seric /* collect the text of the message */ 30424943Seric SmtpPhase = "collect"; 30525016Seric setproctitle("%s %s: %s", CurEnv->e_id, 30625050Seric CurHostName, inp); 3074976Seric collect(TRUE); 3084976Seric if (Errors != 0) 3094976Seric break; 3104976Seric 3118238Seric /* 3128238Seric ** Arrange to send to everyone. 3138238Seric ** If sending to multiple people, mail back 3148238Seric ** errors rather than reporting directly. 3158238Seric ** In any case, don't mail back errors for 3168238Seric ** anything that has happened up to 3178238Seric ** now (the other end will do this). 31810197Seric ** Truncate our transcript -- the mail has gotten 31910197Seric ** to us successfully, and if we have 32010197Seric ** to mail this back, it will be easier 32110197Seric ** on the reader. 3228238Seric ** Then send to everyone. 3238238Seric ** Finally give a reply code. If an error has 3248238Seric ** already been given, don't mail a 3258238Seric ** message back. 3269339Seric ** We goose error returns by clearing error bit. 3278238Seric */ 3288238Seric 32924943Seric SmtpPhase = "delivery"; 33024943Seric if (CurEnv->e_nrcpts != 1) 3319378Seric { 3329378Seric HoldErrs = TRUE; 33316886Seric ErrorMode = EM_MAIL; 3349378Seric } 3359339Seric CurEnv->e_flags &= ~EF_FATALERRS; 33610197Seric CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 3374976Seric 3384976Seric /* send to all recipients */ 33914877Seric sendall(CurEnv, SM_DEFAULT); 3406907Seric CurEnv->e_to = NULL; 3414976Seric 34223516Seric /* save statistics */ 34323516Seric markstats(CurEnv, (ADDRESS *) NULL); 34423516Seric 3458238Seric /* issue success if appropriate and reset */ 3468238Seric if (Errors == 0 || HoldErrs) 3479283Seric message("250", "Ok"); 3488238Seric else 3499339Seric CurEnv->e_flags &= ~EF_FATALERRS; 3509339Seric 3519339Seric /* if in a child, pop back to our parent */ 3529339Seric if (InChild) 3539339Seric finis(); 35424943Seric 35524943Seric /* clean up a bit */ 35624943Seric hasmail = 0; 35724943Seric dropenvelope(CurEnv); 35824943Seric CurEnv = newenvelope(CurEnv); 35924943Seric CurEnv->e_flags = BlankEnvelope.e_flags; 3604549Seric break; 3614549Seric 3624549Seric case CMDRSET: /* rset -- reset state */ 3634549Seric message("250", "Reset state"); 3649339Seric if (InChild) 3659339Seric finis(); 3669339Seric break; 3674549Seric 3684549Seric case CMDVRFY: /* vrfy -- verify address */ 3699339Seric if (runinchild("SMTP-VRFY") > 0) 3709339Seric break; 37125050Seric setproctitle("%s: %s", CurHostName, inp); 3725003Seric vrfyqueue = NULL; 3737762Seric QuickAbort = TRUE; 3749619Seric sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 3757762Seric if (Errors != 0) 3769339Seric { 3779339Seric if (InChild) 3789339Seric finis(); 3797762Seric break; 3809339Seric } 3815003Seric while (vrfyqueue != NULL) 3825003Seric { 3835003Seric register ADDRESS *a = vrfyqueue->q_next; 3845003Seric char *code; 3855003Seric 3867685Seric while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 3875003Seric a = a->q_next; 3885003Seric 3897685Seric if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 3905003Seric { 3915003Seric if (a != NULL) 3925003Seric code = "250-"; 3935003Seric else 3945003Seric code = "250"; 3955003Seric if (vrfyqueue->q_fullname == NULL) 3965003Seric message(code, "<%s>", vrfyqueue->q_paddr); 3975003Seric else 3985003Seric message(code, "%s <%s>", 3995003Seric vrfyqueue->q_fullname, vrfyqueue->q_paddr); 4005003Seric } 4015003Seric else if (a == NULL) 4025003Seric message("554", "Self destructive alias loop"); 4035003Seric vrfyqueue = a; 4045003Seric } 4059339Seric if (InChild) 4069339Seric finis(); 4074549Seric break; 4084549Seric 4094549Seric case CMDHELP: /* help -- give user info */ 4104577Seric help(p); 4114549Seric break; 4124549Seric 4134549Seric case CMDNOOP: /* noop -- do nothing */ 4144549Seric message("200", "OK"); 4154549Seric break; 4164549Seric 4174549Seric case CMDQUIT: /* quit -- leave mail */ 41825050Seric message("221", "%s closing connection", MyHostName); 4199339Seric if (InChild) 4209339Seric ExitStat = EX_QUIT; 4214549Seric finis(); 4224549Seric 4238544Seric case CMDVERB: /* set verbose mode */ 4248544Seric Verbose = TRUE; 42525025Seric SendMode = SM_DELIVER; 4268544Seric message("200", "Verbose mode"); 4278544Seric break; 4288544Seric 4299314Seric case CMDONEX: /* doing one transaction only */ 4309378Seric OneXact = TRUE; 4319314Seric message("200", "Only one transaction"); 4329314Seric break; 4339314Seric 43436230Skarels # ifdef SMTPDEBUG 4359339Seric case CMDDBGQSHOW: /* show queues */ 4366907Seric printf("Send Queue="); 4376907Seric printaddr(CurEnv->e_sendqueue, TRUE); 4385003Seric break; 4397275Seric 4407275Seric case CMDDBGDEBUG: /* set debug mode */ 4417676Seric tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 4427676Seric tTflag(p); 4437676Seric message("200", "Debug set"); 4447275Seric break; 4457275Seric 44636230Skarels # else /* not SMTPDEBUG */ 44724945Seric 44836230Skarels case CMDDBGQSHOW: /* show queues */ 44936230Skarels case CMDDBGDEBUG: /* set debug mode */ 45036233Skarels # ifdef LOG 45136233Skarels if (RealHostName != NULL && LogLevel > 0) 45236230Skarels syslog(LOG_NOTICE, 45336230Skarels "\"%s\" command from %s (%s)\n", 45436230Skarels c->cmdname, RealHostName, 45536230Skarels inet_ntoa(RealHostAddr.sin_addr)); 45636233Skarels # endif 45736230Skarels /* FALL THROUGH */ 45836230Skarels # endif /* SMTPDEBUG */ 45936230Skarels 4604549Seric case CMDERROR: /* unknown command */ 4614549Seric message("500", "Command unrecognized"); 4624549Seric break; 4634549Seric 4644549Seric default: 46536230Skarels errno = 0; 4664549Seric syserr("smtp: unknown code %d", c->cmdcode); 4674549Seric break; 4684549Seric } 4694549Seric } 4704549Seric } 4714549Seric /* 4724549Seric ** SKIPWORD -- skip a fixed word. 4734549Seric ** 4744549Seric ** Parameters: 4754549Seric ** p -- place to start looking. 4764549Seric ** w -- word to skip. 4774549Seric ** 4784549Seric ** Returns: 4794549Seric ** p following w. 4804549Seric ** NULL on error. 4814549Seric ** 4824549Seric ** Side Effects: 4834549Seric ** clobbers the p data area. 4844549Seric */ 4854549Seric 4864549Seric static char * 4874549Seric skipword(p, w) 4884549Seric register char *p; 4894549Seric char *w; 4904549Seric { 4914549Seric register char *q; 4924549Seric 4934549Seric /* find beginning of word */ 4944549Seric while (isspace(*p)) 4954549Seric p++; 4964549Seric q = p; 4974549Seric 4984549Seric /* find end of word */ 4994549Seric while (*p != '\0' && *p != ':' && !isspace(*p)) 5004549Seric p++; 5014549Seric while (isspace(*p)) 5024549Seric *p++ = '\0'; 5034549Seric if (*p != ':') 5044549Seric { 5054549Seric syntax: 5064549Seric message("501", "Syntax error"); 5074549Seric Errors++; 5084549Seric return (NULL); 5094549Seric } 5104549Seric *p++ = '\0'; 5114549Seric while (isspace(*p)) 5124549Seric p++; 5134549Seric 5144549Seric /* see if the input word matches desired word */ 51533725Sbostic if (strcasecmp(q, w)) 5164549Seric goto syntax; 5174549Seric 5184549Seric return (p); 5194549Seric } 5204577Seric /* 5214577Seric ** HELP -- implement the HELP command. 5224577Seric ** 5234577Seric ** Parameters: 5244577Seric ** topic -- the topic we want help for. 5254577Seric ** 5264577Seric ** Returns: 5274577Seric ** none. 5284577Seric ** 5294577Seric ** Side Effects: 5304577Seric ** outputs the help file to message output. 5314577Seric */ 5324577Seric 5334577Seric help(topic) 5344577Seric char *topic; 5354577Seric { 5364577Seric register FILE *hf; 5374577Seric int len; 5384577Seric char buf[MAXLINE]; 5394577Seric bool noinfo; 5404577Seric 5418269Seric if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 5424577Seric { 5434577Seric /* no help */ 54411931Seric errno = 0; 5454577Seric message("502", "HELP not implemented"); 5464577Seric return; 5474577Seric } 5484577Seric 54949669Seric if (topic == NULL || *topic == '\0') 55049669Seric topic = "smtp"; 55149669Seric else 55249669Seric makelower(topic); 55349669Seric 5544577Seric len = strlen(topic); 5554577Seric noinfo = TRUE; 5564577Seric 5574577Seric while (fgets(buf, sizeof buf, hf) != NULL) 5584577Seric { 5594577Seric if (strncmp(buf, topic, len) == 0) 5604577Seric { 5614577Seric register char *p; 5624577Seric 5634577Seric p = index(buf, '\t'); 5644577Seric if (p == NULL) 5654577Seric p = buf; 5664577Seric else 5674577Seric p++; 5684577Seric fixcrlf(p, TRUE); 5694577Seric message("214-", p); 5704577Seric noinfo = FALSE; 5714577Seric } 5724577Seric } 5734577Seric 5744577Seric if (noinfo) 5754577Seric message("504", "HELP topic unknown"); 5764577Seric else 5774577Seric message("214", "End of HELP info"); 5784628Seric (void) fclose(hf); 5794577Seric } 5808544Seric /* 5819339Seric ** RUNINCHILD -- return twice -- once in the child, then in the parent again 5829339Seric ** 5839339Seric ** Parameters: 5849339Seric ** label -- a string used in error messages 5859339Seric ** 5869339Seric ** Returns: 5879339Seric ** zero in the child 5889339Seric ** one in the parent 5899339Seric ** 5909339Seric ** Side Effects: 5919339Seric ** none. 5929339Seric */ 5938544Seric 5949339Seric runinchild(label) 5959339Seric char *label; 5969339Seric { 5979339Seric int childpid; 5989339Seric 59916158Seric if (!OneXact) 6009339Seric { 60116158Seric childpid = dofork(); 60216158Seric if (childpid < 0) 60316158Seric { 60416158Seric syserr("%s: cannot fork", label); 60516158Seric return (1); 60616158Seric } 60716158Seric if (childpid > 0) 60816158Seric { 60916158Seric auto int st; 6109339Seric 61116158Seric /* parent -- wait for child to complete */ 61216158Seric st = waitfor(childpid); 61316158Seric if (st == -1) 61416158Seric syserr("%s: lost child", label); 6159339Seric 61616158Seric /* if we exited on a QUIT command, complete the process */ 61716158Seric if (st == (EX_QUIT << 8)) 61816158Seric finis(); 6199339Seric 62016158Seric return (1); 62116158Seric } 62216158Seric else 62316158Seric { 62416158Seric /* child */ 62516158Seric InChild = TRUE; 62625050Seric QuickAbort = FALSE; 62725614Seric clearenvelope(CurEnv, FALSE); 62816158Seric } 6299339Seric } 63015256Seric 63116158Seric /* open alias database */ 63216158Seric initaliases(AliasFile, FALSE); 63316158Seric 63416158Seric return (0); 6359339Seric } 6369339Seric 6375181Seric # endif SMTP 638