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*49669Seric static char sccsid[] = "@(#)srvrsmtp.c 5.31 (Berkeley) 05/10/91 (with SMTP)"; 1433731Sbostic #else 15*49669Seric static char sccsid[] = "@(#)srvrsmtp.c 5.31 (Berkeley) 05/10/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; 13612612Seric 1377356Seric /* setup for the read */ 1386907Seric CurEnv->e_to = NULL; 1394577Seric Errors = 0; 1407275Seric (void) fflush(stdout); 1417356Seric 1427356Seric /* read the input line */ 1437685Seric p = sfgets(inp, sizeof inp, InChannel); 1447356Seric 1457685Seric /* handle errors */ 1467356Seric if (p == NULL) 1477356Seric { 1484549Seric /* end of file, just die */ 14936230Skarels message("421", "%s Lost input channel from %s", 15025050Seric MyHostName, CurHostName); 1514549Seric finis(); 1524549Seric } 1534549Seric 1544549Seric /* clean up end of line */ 1554558Seric fixcrlf(inp, TRUE); 1564549Seric 1574713Seric /* echo command to transcript */ 1589545Seric if (CurEnv->e_xfp != NULL) 1599545Seric fprintf(CurEnv->e_xfp, "<<< %s\n", inp); 1604713Seric 1614549Seric /* break off command */ 1624549Seric for (p = inp; isspace(*p); p++) 1634549Seric continue; 1644549Seric cmd = p; 16524981Seric for (cmd = cmdbuf; *p != '\0' && !isspace(*p); ) 16624981Seric *cmd++ = *p++; 16724981Seric *cmd = '\0'; 1684549Seric 16925691Seric /* throw away leading whitespace */ 17025691Seric while (isspace(*p)) 17125691Seric p++; 17225691Seric 1734549Seric /* decode command */ 1744549Seric for (c = CmdTab; c->cmdname != NULL; c++) 1754549Seric { 17633725Sbostic if (!strcasecmp(c->cmdname, cmdbuf)) 1774549Seric break; 1784549Seric } 1794549Seric 1804549Seric /* process command */ 1814549Seric switch (c->cmdcode) 1824549Seric { 1834976Seric case CMDHELO: /* hello -- introduce yourself */ 18424943Seric SmtpPhase = "HELO"; 18525050Seric setproctitle("%s: %s", CurHostName, inp); 18633725Sbostic if (!strcasecmp(p, MyHostName)) 18714877Seric { 18836230Skarels /* 18936230Skarels * didn't know about alias, 19036230Skarels * or connected to an echo server 19136230Skarels */ 19247570Seric message("553", "%s config error: mail loops back to myself", 19347570Seric MyHostName); 19414877Seric break; 19514877Seric } 19633725Sbostic if (RealHostName != NULL && strcasecmp(p, RealHostName)) 19711146Seric { 19824981Seric char hostbuf[MAXNAME]; 19911146Seric 20024981Seric (void) sprintf(hostbuf, "%s (%s)", p, RealHostName); 20130448Seric sendinghost = newstr(hostbuf); 20211146Seric } 20311146Seric else 20430448Seric sendinghost = newstr(p); 2054997Seric message("250", "%s Hello %s, pleased to meet you", 20636230Skarels MyHostName, sendinghost); 2074976Seric break; 2084976Seric 2094549Seric case CMDMAIL: /* mail -- designate sender */ 21024943Seric SmtpPhase = "MAIL"; 21124943Seric 21211151Seric /* force a sending host even if no HELO given */ 21311151Seric if (RealHostName != NULL && macvalue('s', CurEnv) == NULL) 21430448Seric sendinghost = RealHostName; 21511151Seric 2169314Seric /* check for validity of this command */ 2174558Seric if (hasmail) 2184558Seric { 2194558Seric message("503", "Sender already specified"); 2204558Seric break; 2214558Seric } 2229339Seric if (InChild) 2239339Seric { 22436230Skarels errno = 0; 2259339Seric syserr("Nested MAIL command"); 2269339Seric exit(0); 2279339Seric } 2289339Seric 2299339Seric /* fork a subprocess to process this command */ 2309339Seric if (runinchild("SMTP-MAIL") > 0) 2319339Seric break; 23230448Seric define('s', sendinghost, CurEnv); 23336579Sbostic define('r', "SMTP", CurEnv); 2349339Seric initsys(); 23525016Seric setproctitle("%s %s: %s", CurEnv->e_id, 23625050Seric CurHostName, inp); 2379339Seric 2389339Seric /* child -- go do the processing */ 2394549Seric p = skipword(p, "from"); 2404549Seric if (p == NULL) 2414549Seric break; 2424549Seric setsender(p); 2434577Seric if (Errors == 0) 2444549Seric { 2454549Seric message("250", "Sender ok"); 2464549Seric hasmail = TRUE; 2474549Seric } 2489339Seric else if (InChild) 2499339Seric finis(); 2504549Seric break; 2514549Seric 2524976Seric case CMDRCPT: /* rcpt -- designate recipient */ 25324943Seric SmtpPhase = "RCPT"; 25425016Seric setproctitle("%s %s: %s", CurEnv->e_id, 25525050Seric CurHostName, inp); 25612612Seric if (setjmp(TopFrame) > 0) 25714785Seric { 25814785Seric CurEnv->e_flags &= ~EF_FATALERRS; 25912612Seric break; 26014785Seric } 26112612Seric QuickAbort = TRUE; 2624549Seric p = skipword(p, "to"); 2634549Seric if (p == NULL) 2644549Seric break; 26516140Seric a = parseaddr(p, (ADDRESS *) NULL, 1, '\0'); 26612612Seric if (a == NULL) 26712612Seric break; 26816886Seric a->q_flags |= QPRIMARY; 26912612Seric a = recipient(a, &CurEnv->e_sendqueue); 27012612Seric if (Errors != 0) 27112612Seric break; 27212612Seric 27312612Seric /* no errors during parsing, but might be a duplicate */ 27412612Seric CurEnv->e_to = p; 27512612Seric if (!bitset(QBADADDR, a->q_flags)) 27612612Seric message("250", "Recipient ok"); 27712612Seric else 2784549Seric { 27912612Seric /* punt -- should keep message in ADDRESS.... */ 28012612Seric message("550", "Addressee unknown"); 2814549Seric } 28212612Seric CurEnv->e_to = NULL; 2834549Seric break; 2844549Seric 2854549Seric case CMDDATA: /* data -- text of mail */ 28624943Seric SmtpPhase = "DATA"; 2874976Seric if (!hasmail) 2884549Seric { 2894976Seric message("503", "Need MAIL command"); 2904976Seric break; 2914549Seric } 29224943Seric else if (CurEnv->e_nrcpts <= 0) 2934549Seric { 2944976Seric message("503", "Need RCPT (recipient)"); 2954976Seric break; 2964549Seric } 2974976Seric 2984976Seric /* collect the text of the message */ 29924943Seric SmtpPhase = "collect"; 30025016Seric setproctitle("%s %s: %s", CurEnv->e_id, 30125050Seric CurHostName, inp); 3024976Seric collect(TRUE); 3034976Seric if (Errors != 0) 3044976Seric break; 3054976Seric 3068238Seric /* 3078238Seric ** Arrange to send to everyone. 3088238Seric ** If sending to multiple people, mail back 3098238Seric ** errors rather than reporting directly. 3108238Seric ** In any case, don't mail back errors for 3118238Seric ** anything that has happened up to 3128238Seric ** now (the other end will do this). 31310197Seric ** Truncate our transcript -- the mail has gotten 31410197Seric ** to us successfully, and if we have 31510197Seric ** to mail this back, it will be easier 31610197Seric ** on the reader. 3178238Seric ** Then send to everyone. 3188238Seric ** Finally give a reply code. If an error has 3198238Seric ** already been given, don't mail a 3208238Seric ** message back. 3219339Seric ** We goose error returns by clearing error bit. 3228238Seric */ 3238238Seric 32424943Seric SmtpPhase = "delivery"; 32524943Seric if (CurEnv->e_nrcpts != 1) 3269378Seric { 3279378Seric HoldErrs = TRUE; 32816886Seric ErrorMode = EM_MAIL; 3299378Seric } 3309339Seric CurEnv->e_flags &= ~EF_FATALERRS; 33110197Seric CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 3324976Seric 3334976Seric /* send to all recipients */ 33414877Seric sendall(CurEnv, SM_DEFAULT); 3356907Seric CurEnv->e_to = NULL; 3364976Seric 33723516Seric /* save statistics */ 33823516Seric markstats(CurEnv, (ADDRESS *) NULL); 33923516Seric 3408238Seric /* issue success if appropriate and reset */ 3418238Seric if (Errors == 0 || HoldErrs) 3429283Seric message("250", "Ok"); 3438238Seric else 3449339Seric CurEnv->e_flags &= ~EF_FATALERRS; 3459339Seric 3469339Seric /* if in a child, pop back to our parent */ 3479339Seric if (InChild) 3489339Seric finis(); 34924943Seric 35024943Seric /* clean up a bit */ 35124943Seric hasmail = 0; 35224943Seric dropenvelope(CurEnv); 35324943Seric CurEnv = newenvelope(CurEnv); 35424943Seric CurEnv->e_flags = BlankEnvelope.e_flags; 3554549Seric break; 3564549Seric 3574549Seric case CMDRSET: /* rset -- reset state */ 3584549Seric message("250", "Reset state"); 3599339Seric if (InChild) 3609339Seric finis(); 3619339Seric break; 3624549Seric 3634549Seric case CMDVRFY: /* vrfy -- verify address */ 3649339Seric if (runinchild("SMTP-VRFY") > 0) 3659339Seric break; 36625050Seric setproctitle("%s: %s", CurHostName, inp); 3675003Seric vrfyqueue = NULL; 3687762Seric QuickAbort = TRUE; 3699619Seric sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 3707762Seric if (Errors != 0) 3719339Seric { 3729339Seric if (InChild) 3739339Seric finis(); 3747762Seric break; 3759339Seric } 3765003Seric while (vrfyqueue != NULL) 3775003Seric { 3785003Seric register ADDRESS *a = vrfyqueue->q_next; 3795003Seric char *code; 3805003Seric 3817685Seric while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 3825003Seric a = a->q_next; 3835003Seric 3847685Seric if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 3855003Seric { 3865003Seric if (a != NULL) 3875003Seric code = "250-"; 3885003Seric else 3895003Seric code = "250"; 3905003Seric if (vrfyqueue->q_fullname == NULL) 3915003Seric message(code, "<%s>", vrfyqueue->q_paddr); 3925003Seric else 3935003Seric message(code, "%s <%s>", 3945003Seric vrfyqueue->q_fullname, vrfyqueue->q_paddr); 3955003Seric } 3965003Seric else if (a == NULL) 3975003Seric message("554", "Self destructive alias loop"); 3985003Seric vrfyqueue = a; 3995003Seric } 4009339Seric if (InChild) 4019339Seric finis(); 4024549Seric break; 4034549Seric 4044549Seric case CMDHELP: /* help -- give user info */ 4054577Seric help(p); 4064549Seric break; 4074549Seric 4084549Seric case CMDNOOP: /* noop -- do nothing */ 4094549Seric message("200", "OK"); 4104549Seric break; 4114549Seric 4124549Seric case CMDQUIT: /* quit -- leave mail */ 41325050Seric message("221", "%s closing connection", MyHostName); 4149339Seric if (InChild) 4159339Seric ExitStat = EX_QUIT; 4164549Seric finis(); 4174549Seric 4188544Seric case CMDVERB: /* set verbose mode */ 4198544Seric Verbose = TRUE; 42025025Seric SendMode = SM_DELIVER; 4218544Seric message("200", "Verbose mode"); 4228544Seric break; 4238544Seric 4249314Seric case CMDONEX: /* doing one transaction only */ 4259378Seric OneXact = TRUE; 4269314Seric message("200", "Only one transaction"); 4279314Seric break; 4289314Seric 42936230Skarels # ifdef SMTPDEBUG 4309339Seric case CMDDBGQSHOW: /* show queues */ 4316907Seric printf("Send Queue="); 4326907Seric printaddr(CurEnv->e_sendqueue, TRUE); 4335003Seric break; 4347275Seric 4357275Seric case CMDDBGDEBUG: /* set debug mode */ 4367676Seric tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 4377676Seric tTflag(p); 4387676Seric message("200", "Debug set"); 4397275Seric break; 4407275Seric 44136230Skarels # else /* not SMTPDEBUG */ 44224945Seric 44336230Skarels case CMDDBGQSHOW: /* show queues */ 44436230Skarels case CMDDBGDEBUG: /* set debug mode */ 44536233Skarels # ifdef LOG 44636233Skarels if (RealHostName != NULL && LogLevel > 0) 44736230Skarels syslog(LOG_NOTICE, 44836230Skarels "\"%s\" command from %s (%s)\n", 44936230Skarels c->cmdname, RealHostName, 45036230Skarels inet_ntoa(RealHostAddr.sin_addr)); 45136233Skarels # endif 45236230Skarels /* FALL THROUGH */ 45336230Skarels # endif /* SMTPDEBUG */ 45436230Skarels 4554549Seric case CMDERROR: /* unknown command */ 4564549Seric message("500", "Command unrecognized"); 4574549Seric break; 4584549Seric 4594549Seric default: 46036230Skarels errno = 0; 4614549Seric syserr("smtp: unknown code %d", c->cmdcode); 4624549Seric break; 4634549Seric } 4644549Seric } 4654549Seric } 4664549Seric /* 4674549Seric ** SKIPWORD -- skip a fixed word. 4684549Seric ** 4694549Seric ** Parameters: 4704549Seric ** p -- place to start looking. 4714549Seric ** w -- word to skip. 4724549Seric ** 4734549Seric ** Returns: 4744549Seric ** p following w. 4754549Seric ** NULL on error. 4764549Seric ** 4774549Seric ** Side Effects: 4784549Seric ** clobbers the p data area. 4794549Seric */ 4804549Seric 4814549Seric static char * 4824549Seric skipword(p, w) 4834549Seric register char *p; 4844549Seric char *w; 4854549Seric { 4864549Seric register char *q; 4874549Seric 4884549Seric /* find beginning of word */ 4894549Seric while (isspace(*p)) 4904549Seric p++; 4914549Seric q = p; 4924549Seric 4934549Seric /* find end of word */ 4944549Seric while (*p != '\0' && *p != ':' && !isspace(*p)) 4954549Seric p++; 4964549Seric while (isspace(*p)) 4974549Seric *p++ = '\0'; 4984549Seric if (*p != ':') 4994549Seric { 5004549Seric syntax: 5014549Seric message("501", "Syntax error"); 5024549Seric Errors++; 5034549Seric return (NULL); 5044549Seric } 5054549Seric *p++ = '\0'; 5064549Seric while (isspace(*p)) 5074549Seric p++; 5084549Seric 5094549Seric /* see if the input word matches desired word */ 51033725Sbostic if (strcasecmp(q, w)) 5114549Seric goto syntax; 5124549Seric 5134549Seric return (p); 5144549Seric } 5154577Seric /* 5164577Seric ** HELP -- implement the HELP command. 5174577Seric ** 5184577Seric ** Parameters: 5194577Seric ** topic -- the topic we want help for. 5204577Seric ** 5214577Seric ** Returns: 5224577Seric ** none. 5234577Seric ** 5244577Seric ** Side Effects: 5254577Seric ** outputs the help file to message output. 5264577Seric */ 5274577Seric 5284577Seric help(topic) 5294577Seric char *topic; 5304577Seric { 5314577Seric register FILE *hf; 5324577Seric int len; 5334577Seric char buf[MAXLINE]; 5344577Seric bool noinfo; 5354577Seric 5368269Seric if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 5374577Seric { 5384577Seric /* no help */ 53911931Seric errno = 0; 5404577Seric message("502", "HELP not implemented"); 5414577Seric return; 5424577Seric } 5434577Seric 544*49669Seric if (topic == NULL || *topic == '\0') 545*49669Seric topic = "smtp"; 546*49669Seric else 547*49669Seric makelower(topic); 548*49669Seric 5494577Seric len = strlen(topic); 5504577Seric noinfo = TRUE; 5514577Seric 5524577Seric while (fgets(buf, sizeof buf, hf) != NULL) 5534577Seric { 5544577Seric if (strncmp(buf, topic, len) == 0) 5554577Seric { 5564577Seric register char *p; 5574577Seric 5584577Seric p = index(buf, '\t'); 5594577Seric if (p == NULL) 5604577Seric p = buf; 5614577Seric else 5624577Seric p++; 5634577Seric fixcrlf(p, TRUE); 5644577Seric message("214-", p); 5654577Seric noinfo = FALSE; 5664577Seric } 5674577Seric } 5684577Seric 5694577Seric if (noinfo) 5704577Seric message("504", "HELP topic unknown"); 5714577Seric else 5724577Seric message("214", "End of HELP info"); 5734628Seric (void) fclose(hf); 5744577Seric } 5758544Seric /* 5769339Seric ** RUNINCHILD -- return twice -- once in the child, then in the parent again 5779339Seric ** 5789339Seric ** Parameters: 5799339Seric ** label -- a string used in error messages 5809339Seric ** 5819339Seric ** Returns: 5829339Seric ** zero in the child 5839339Seric ** one in the parent 5849339Seric ** 5859339Seric ** Side Effects: 5869339Seric ** none. 5879339Seric */ 5888544Seric 5899339Seric runinchild(label) 5909339Seric char *label; 5919339Seric { 5929339Seric int childpid; 5939339Seric 59416158Seric if (!OneXact) 5959339Seric { 59616158Seric childpid = dofork(); 59716158Seric if (childpid < 0) 59816158Seric { 59916158Seric syserr("%s: cannot fork", label); 60016158Seric return (1); 60116158Seric } 60216158Seric if (childpid > 0) 60316158Seric { 60416158Seric auto int st; 6059339Seric 60616158Seric /* parent -- wait for child to complete */ 60716158Seric st = waitfor(childpid); 60816158Seric if (st == -1) 60916158Seric syserr("%s: lost child", label); 6109339Seric 61116158Seric /* if we exited on a QUIT command, complete the process */ 61216158Seric if (st == (EX_QUIT << 8)) 61316158Seric finis(); 6149339Seric 61516158Seric return (1); 61616158Seric } 61716158Seric else 61816158Seric { 61916158Seric /* child */ 62016158Seric InChild = TRUE; 62125050Seric QuickAbort = FALSE; 62225614Seric clearenvelope(CurEnv, FALSE); 62316158Seric } 6249339Seric } 62515256Seric 62616158Seric /* open alias database */ 62716158Seric initaliases(AliasFile, FALSE); 62816158Seric 62916158Seric return (0); 6309339Seric } 6319339Seric 6325181Seric # endif SMTP 633