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*51951Seric static char sccsid[] = "@(#)srvrsmtp.c 5.32 (Berkeley) 12/15/91 (with SMTP)"; 1433731Sbostic #else 15*51951Seric static char sccsid[] = "@(#)srvrsmtp.c 5.32 (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; 136*51951Seric 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 1814549Seric /* process command */ 1824549Seric switch (c->cmdcode) 1834549Seric { 1844976Seric case CMDHELO: /* hello -- introduce yourself */ 18524943Seric SmtpPhase = "HELO"; 18625050Seric setproctitle("%s: %s", CurHostName, inp); 18733725Sbostic if (!strcasecmp(p, MyHostName)) 18814877Seric { 18936230Skarels /* 19036230Skarels * didn't know about alias, 19136230Skarels * or connected to an echo server 19236230Skarels */ 19347570Seric message("553", "%s config error: mail loops back to myself", 19447570Seric MyHostName); 19514877Seric break; 19614877Seric } 19733725Sbostic if (RealHostName != NULL && strcasecmp(p, RealHostName)) 19811146Seric { 19924981Seric char hostbuf[MAXNAME]; 20011146Seric 20124981Seric (void) sprintf(hostbuf, "%s (%s)", p, RealHostName); 20230448Seric sendinghost = newstr(hostbuf); 20311146Seric } 20411146Seric else 20530448Seric sendinghost = newstr(p); 2064997Seric message("250", "%s Hello %s, pleased to meet you", 20736230Skarels MyHostName, sendinghost); 2084976Seric break; 2094976Seric 2104549Seric case CMDMAIL: /* mail -- designate sender */ 21124943Seric SmtpPhase = "MAIL"; 21224943Seric 21311151Seric /* force a sending host even if no HELO given */ 21411151Seric if (RealHostName != NULL && macvalue('s', CurEnv) == NULL) 21530448Seric sendinghost = RealHostName; 21611151Seric 2179314Seric /* check for validity of this command */ 2184558Seric if (hasmail) 2194558Seric { 2204558Seric message("503", "Sender already specified"); 2214558Seric break; 2224558Seric } 2239339Seric if (InChild) 2249339Seric { 22536230Skarels errno = 0; 2269339Seric syserr("Nested MAIL command"); 2279339Seric exit(0); 2289339Seric } 2299339Seric 2309339Seric /* fork a subprocess to process this command */ 2319339Seric if (runinchild("SMTP-MAIL") > 0) 2329339Seric break; 23330448Seric define('s', sendinghost, CurEnv); 23436579Sbostic define('r', "SMTP", CurEnv); 2359339Seric initsys(); 23625016Seric setproctitle("%s %s: %s", CurEnv->e_id, 23725050Seric CurHostName, inp); 2389339Seric 2399339Seric /* child -- go do the processing */ 2404549Seric p = skipword(p, "from"); 2414549Seric if (p == NULL) 2424549Seric break; 2434549Seric setsender(p); 2444577Seric if (Errors == 0) 2454549Seric { 2464549Seric message("250", "Sender ok"); 2474549Seric hasmail = TRUE; 2484549Seric } 2499339Seric else if (InChild) 2509339Seric finis(); 2514549Seric break; 2524549Seric 2534976Seric case CMDRCPT: /* rcpt -- designate recipient */ 25424943Seric SmtpPhase = "RCPT"; 25525016Seric setproctitle("%s %s: %s", CurEnv->e_id, 25625050Seric CurHostName, inp); 25712612Seric if (setjmp(TopFrame) > 0) 25814785Seric { 25914785Seric CurEnv->e_flags &= ~EF_FATALERRS; 26012612Seric break; 26114785Seric } 26212612Seric QuickAbort = TRUE; 263*51951Seric LogUsrErrs = TRUE; 2644549Seric p = skipword(p, "to"); 2654549Seric if (p == NULL) 2664549Seric break; 26716140Seric a = parseaddr(p, (ADDRESS *) NULL, 1, '\0'); 26812612Seric if (a == NULL) 26912612Seric break; 27016886Seric a->q_flags |= QPRIMARY; 27112612Seric a = recipient(a, &CurEnv->e_sendqueue); 27212612Seric if (Errors != 0) 27312612Seric break; 27412612Seric 27512612Seric /* no errors during parsing, but might be a duplicate */ 27612612Seric CurEnv->e_to = p; 27712612Seric if (!bitset(QBADADDR, a->q_flags)) 27812612Seric message("250", "Recipient ok"); 27912612Seric else 2804549Seric { 28112612Seric /* punt -- should keep message in ADDRESS.... */ 28212612Seric message("550", "Addressee unknown"); 2834549Seric } 28412612Seric CurEnv->e_to = NULL; 2854549Seric break; 2864549Seric 2874549Seric case CMDDATA: /* data -- text of mail */ 28824943Seric SmtpPhase = "DATA"; 2894976Seric if (!hasmail) 2904549Seric { 2914976Seric message("503", "Need MAIL command"); 2924976Seric break; 2934549Seric } 29424943Seric else if (CurEnv->e_nrcpts <= 0) 2954549Seric { 2964976Seric message("503", "Need RCPT (recipient)"); 2974976Seric break; 2984549Seric } 2994976Seric 3004976Seric /* collect the text of the message */ 30124943Seric SmtpPhase = "collect"; 30225016Seric setproctitle("%s %s: %s", CurEnv->e_id, 30325050Seric CurHostName, inp); 3044976Seric collect(TRUE); 3054976Seric if (Errors != 0) 3064976Seric break; 3074976Seric 3088238Seric /* 3098238Seric ** Arrange to send to everyone. 3108238Seric ** If sending to multiple people, mail back 3118238Seric ** errors rather than reporting directly. 3128238Seric ** In any case, don't mail back errors for 3138238Seric ** anything that has happened up to 3148238Seric ** now (the other end will do this). 31510197Seric ** Truncate our transcript -- the mail has gotten 31610197Seric ** to us successfully, and if we have 31710197Seric ** to mail this back, it will be easier 31810197Seric ** on the reader. 3198238Seric ** Then send to everyone. 3208238Seric ** Finally give a reply code. If an error has 3218238Seric ** already been given, don't mail a 3228238Seric ** message back. 3239339Seric ** We goose error returns by clearing error bit. 3248238Seric */ 3258238Seric 32624943Seric SmtpPhase = "delivery"; 32724943Seric if (CurEnv->e_nrcpts != 1) 3289378Seric { 3299378Seric HoldErrs = TRUE; 33016886Seric ErrorMode = EM_MAIL; 3319378Seric } 3329339Seric CurEnv->e_flags &= ~EF_FATALERRS; 33310197Seric CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 3344976Seric 3354976Seric /* send to all recipients */ 33614877Seric sendall(CurEnv, SM_DEFAULT); 3376907Seric CurEnv->e_to = NULL; 3384976Seric 33923516Seric /* save statistics */ 34023516Seric markstats(CurEnv, (ADDRESS *) NULL); 34123516Seric 3428238Seric /* issue success if appropriate and reset */ 3438238Seric if (Errors == 0 || HoldErrs) 3449283Seric message("250", "Ok"); 3458238Seric else 3469339Seric CurEnv->e_flags &= ~EF_FATALERRS; 3479339Seric 3489339Seric /* if in a child, pop back to our parent */ 3499339Seric if (InChild) 3509339Seric finis(); 35124943Seric 35224943Seric /* clean up a bit */ 35324943Seric hasmail = 0; 35424943Seric dropenvelope(CurEnv); 35524943Seric CurEnv = newenvelope(CurEnv); 35624943Seric CurEnv->e_flags = BlankEnvelope.e_flags; 3574549Seric break; 3584549Seric 3594549Seric case CMDRSET: /* rset -- reset state */ 3604549Seric message("250", "Reset state"); 3619339Seric if (InChild) 3629339Seric finis(); 3639339Seric break; 3644549Seric 3654549Seric case CMDVRFY: /* vrfy -- verify address */ 3669339Seric if (runinchild("SMTP-VRFY") > 0) 3679339Seric break; 36825050Seric setproctitle("%s: %s", CurHostName, inp); 3695003Seric vrfyqueue = NULL; 3707762Seric QuickAbort = TRUE; 3719619Seric sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 3727762Seric if (Errors != 0) 3739339Seric { 3749339Seric if (InChild) 3759339Seric finis(); 3767762Seric break; 3779339Seric } 3785003Seric while (vrfyqueue != NULL) 3795003Seric { 3805003Seric register ADDRESS *a = vrfyqueue->q_next; 3815003Seric char *code; 3825003Seric 3837685Seric while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 3845003Seric a = a->q_next; 3855003Seric 3867685Seric if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 3875003Seric { 3885003Seric if (a != NULL) 3895003Seric code = "250-"; 3905003Seric else 3915003Seric code = "250"; 3925003Seric if (vrfyqueue->q_fullname == NULL) 3935003Seric message(code, "<%s>", vrfyqueue->q_paddr); 3945003Seric else 3955003Seric message(code, "%s <%s>", 3965003Seric vrfyqueue->q_fullname, vrfyqueue->q_paddr); 3975003Seric } 3985003Seric else if (a == NULL) 3995003Seric message("554", "Self destructive alias loop"); 4005003Seric vrfyqueue = a; 4015003Seric } 4029339Seric if (InChild) 4039339Seric finis(); 4044549Seric break; 4054549Seric 4064549Seric case CMDHELP: /* help -- give user info */ 4074577Seric help(p); 4084549Seric break; 4094549Seric 4104549Seric case CMDNOOP: /* noop -- do nothing */ 4114549Seric message("200", "OK"); 4124549Seric break; 4134549Seric 4144549Seric case CMDQUIT: /* quit -- leave mail */ 41525050Seric message("221", "%s closing connection", MyHostName); 4169339Seric if (InChild) 4179339Seric ExitStat = EX_QUIT; 4184549Seric finis(); 4194549Seric 4208544Seric case CMDVERB: /* set verbose mode */ 4218544Seric Verbose = TRUE; 42225025Seric SendMode = SM_DELIVER; 4238544Seric message("200", "Verbose mode"); 4248544Seric break; 4258544Seric 4269314Seric case CMDONEX: /* doing one transaction only */ 4279378Seric OneXact = TRUE; 4289314Seric message("200", "Only one transaction"); 4299314Seric break; 4309314Seric 43136230Skarels # ifdef SMTPDEBUG 4329339Seric case CMDDBGQSHOW: /* show queues */ 4336907Seric printf("Send Queue="); 4346907Seric printaddr(CurEnv->e_sendqueue, TRUE); 4355003Seric break; 4367275Seric 4377275Seric case CMDDBGDEBUG: /* set debug mode */ 4387676Seric tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 4397676Seric tTflag(p); 4407676Seric message("200", "Debug set"); 4417275Seric break; 4427275Seric 44336230Skarels # else /* not SMTPDEBUG */ 44424945Seric 44536230Skarels case CMDDBGQSHOW: /* show queues */ 44636230Skarels case CMDDBGDEBUG: /* set debug mode */ 44736233Skarels # ifdef LOG 44836233Skarels if (RealHostName != NULL && LogLevel > 0) 44936230Skarels syslog(LOG_NOTICE, 45036230Skarels "\"%s\" command from %s (%s)\n", 45136230Skarels c->cmdname, RealHostName, 45236230Skarels inet_ntoa(RealHostAddr.sin_addr)); 45336233Skarels # endif 45436230Skarels /* FALL THROUGH */ 45536230Skarels # endif /* SMTPDEBUG */ 45636230Skarels 4574549Seric case CMDERROR: /* unknown command */ 4584549Seric message("500", "Command unrecognized"); 4594549Seric break; 4604549Seric 4614549Seric default: 46236230Skarels errno = 0; 4634549Seric syserr("smtp: unknown code %d", c->cmdcode); 4644549Seric break; 4654549Seric } 4664549Seric } 4674549Seric } 4684549Seric /* 4694549Seric ** SKIPWORD -- skip a fixed word. 4704549Seric ** 4714549Seric ** Parameters: 4724549Seric ** p -- place to start looking. 4734549Seric ** w -- word to skip. 4744549Seric ** 4754549Seric ** Returns: 4764549Seric ** p following w. 4774549Seric ** NULL on error. 4784549Seric ** 4794549Seric ** Side Effects: 4804549Seric ** clobbers the p data area. 4814549Seric */ 4824549Seric 4834549Seric static char * 4844549Seric skipword(p, w) 4854549Seric register char *p; 4864549Seric char *w; 4874549Seric { 4884549Seric register char *q; 4894549Seric 4904549Seric /* find beginning of word */ 4914549Seric while (isspace(*p)) 4924549Seric p++; 4934549Seric q = p; 4944549Seric 4954549Seric /* find end of word */ 4964549Seric while (*p != '\0' && *p != ':' && !isspace(*p)) 4974549Seric p++; 4984549Seric while (isspace(*p)) 4994549Seric *p++ = '\0'; 5004549Seric if (*p != ':') 5014549Seric { 5024549Seric syntax: 5034549Seric message("501", "Syntax error"); 5044549Seric Errors++; 5054549Seric return (NULL); 5064549Seric } 5074549Seric *p++ = '\0'; 5084549Seric while (isspace(*p)) 5094549Seric p++; 5104549Seric 5114549Seric /* see if the input word matches desired word */ 51233725Sbostic if (strcasecmp(q, w)) 5134549Seric goto syntax; 5144549Seric 5154549Seric return (p); 5164549Seric } 5174577Seric /* 5184577Seric ** HELP -- implement the HELP command. 5194577Seric ** 5204577Seric ** Parameters: 5214577Seric ** topic -- the topic we want help for. 5224577Seric ** 5234577Seric ** Returns: 5244577Seric ** none. 5254577Seric ** 5264577Seric ** Side Effects: 5274577Seric ** outputs the help file to message output. 5284577Seric */ 5294577Seric 5304577Seric help(topic) 5314577Seric char *topic; 5324577Seric { 5334577Seric register FILE *hf; 5344577Seric int len; 5354577Seric char buf[MAXLINE]; 5364577Seric bool noinfo; 5374577Seric 5388269Seric if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 5394577Seric { 5404577Seric /* no help */ 54111931Seric errno = 0; 5424577Seric message("502", "HELP not implemented"); 5434577Seric return; 5444577Seric } 5454577Seric 54649669Seric if (topic == NULL || *topic == '\0') 54749669Seric topic = "smtp"; 54849669Seric else 54949669Seric makelower(topic); 55049669Seric 5514577Seric len = strlen(topic); 5524577Seric noinfo = TRUE; 5534577Seric 5544577Seric while (fgets(buf, sizeof buf, hf) != NULL) 5554577Seric { 5564577Seric if (strncmp(buf, topic, len) == 0) 5574577Seric { 5584577Seric register char *p; 5594577Seric 5604577Seric p = index(buf, '\t'); 5614577Seric if (p == NULL) 5624577Seric p = buf; 5634577Seric else 5644577Seric p++; 5654577Seric fixcrlf(p, TRUE); 5664577Seric message("214-", p); 5674577Seric noinfo = FALSE; 5684577Seric } 5694577Seric } 5704577Seric 5714577Seric if (noinfo) 5724577Seric message("504", "HELP topic unknown"); 5734577Seric else 5744577Seric message("214", "End of HELP info"); 5754628Seric (void) fclose(hf); 5764577Seric } 5778544Seric /* 5789339Seric ** RUNINCHILD -- return twice -- once in the child, then in the parent again 5799339Seric ** 5809339Seric ** Parameters: 5819339Seric ** label -- a string used in error messages 5829339Seric ** 5839339Seric ** Returns: 5849339Seric ** zero in the child 5859339Seric ** one in the parent 5869339Seric ** 5879339Seric ** Side Effects: 5889339Seric ** none. 5899339Seric */ 5908544Seric 5919339Seric runinchild(label) 5929339Seric char *label; 5939339Seric { 5949339Seric int childpid; 5959339Seric 59616158Seric if (!OneXact) 5979339Seric { 59816158Seric childpid = dofork(); 59916158Seric if (childpid < 0) 60016158Seric { 60116158Seric syserr("%s: cannot fork", label); 60216158Seric return (1); 60316158Seric } 60416158Seric if (childpid > 0) 60516158Seric { 60616158Seric auto int st; 6079339Seric 60816158Seric /* parent -- wait for child to complete */ 60916158Seric st = waitfor(childpid); 61016158Seric if (st == -1) 61116158Seric syserr("%s: lost child", label); 6129339Seric 61316158Seric /* if we exited on a QUIT command, complete the process */ 61416158Seric if (st == (EX_QUIT << 8)) 61516158Seric finis(); 6169339Seric 61716158Seric return (1); 61816158Seric } 61916158Seric else 62016158Seric { 62116158Seric /* child */ 62216158Seric InChild = TRUE; 62325050Seric QuickAbort = FALSE; 62425614Seric clearenvelope(CurEnv, FALSE); 62516158Seric } 6269339Seric } 62715256Seric 62816158Seric /* open alias database */ 62916158Seric initaliases(AliasFile, FALSE); 63016158Seric 63116158Seric return (0); 6329339Seric } 6339339Seric 6345181Seric # endif SMTP 635