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*46928Sbostic static char sccsid[] = "@(#)srvrsmtp.c 5.29 (Berkeley) 03/02/91 (with SMTP)"; 1433731Sbostic #else 15*46928Sbostic static char sccsid[] = "@(#)srvrsmtp.c 5.29 (Berkeley) 03/02/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; 94*46928Sbostic 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 */ 19236230Skarels message("553", "Local configuration error, hostname not recognized as local"); 19314877Seric break; 19414877Seric } 19533725Sbostic if (RealHostName != NULL && strcasecmp(p, RealHostName)) 19611146Seric { 19724981Seric char hostbuf[MAXNAME]; 19811146Seric 19924981Seric (void) sprintf(hostbuf, "%s (%s)", p, RealHostName); 20030448Seric sendinghost = newstr(hostbuf); 20111146Seric } 20211146Seric else 20330448Seric sendinghost = newstr(p); 2044997Seric message("250", "%s Hello %s, pleased to meet you", 20536230Skarels MyHostName, sendinghost); 2064976Seric break; 2074976Seric 2084549Seric case CMDMAIL: /* mail -- designate sender */ 20924943Seric SmtpPhase = "MAIL"; 21024943Seric 21111151Seric /* force a sending host even if no HELO given */ 21211151Seric if (RealHostName != NULL && macvalue('s', CurEnv) == NULL) 21330448Seric sendinghost = RealHostName; 21411151Seric 2159314Seric /* check for validity of this command */ 2164558Seric if (hasmail) 2174558Seric { 2184558Seric message("503", "Sender already specified"); 2194558Seric break; 2204558Seric } 2219339Seric if (InChild) 2229339Seric { 22336230Skarels errno = 0; 2249339Seric syserr("Nested MAIL command"); 2259339Seric exit(0); 2269339Seric } 2279339Seric 2289339Seric /* fork a subprocess to process this command */ 2299339Seric if (runinchild("SMTP-MAIL") > 0) 2309339Seric break; 23130448Seric define('s', sendinghost, CurEnv); 23236579Sbostic define('r', "SMTP", CurEnv); 2339339Seric initsys(); 23425016Seric setproctitle("%s %s: %s", CurEnv->e_id, 23525050Seric CurHostName, inp); 2369339Seric 2379339Seric /* child -- go do the processing */ 2384549Seric p = skipword(p, "from"); 2394549Seric if (p == NULL) 2404549Seric break; 2414549Seric setsender(p); 2424577Seric if (Errors == 0) 2434549Seric { 2444549Seric message("250", "Sender ok"); 2454549Seric hasmail = TRUE; 2464549Seric } 2479339Seric else if (InChild) 2489339Seric finis(); 2494549Seric break; 2504549Seric 2514976Seric case CMDRCPT: /* rcpt -- designate recipient */ 25224943Seric SmtpPhase = "RCPT"; 25325016Seric setproctitle("%s %s: %s", CurEnv->e_id, 25425050Seric CurHostName, inp); 25512612Seric if (setjmp(TopFrame) > 0) 25614785Seric { 25714785Seric CurEnv->e_flags &= ~EF_FATALERRS; 25812612Seric break; 25914785Seric } 26012612Seric QuickAbort = TRUE; 2614549Seric p = skipword(p, "to"); 2624549Seric if (p == NULL) 2634549Seric break; 26416140Seric a = parseaddr(p, (ADDRESS *) NULL, 1, '\0'); 26512612Seric if (a == NULL) 26612612Seric break; 26716886Seric a->q_flags |= QPRIMARY; 26812612Seric a = recipient(a, &CurEnv->e_sendqueue); 26912612Seric if (Errors != 0) 27012612Seric break; 27112612Seric 27212612Seric /* no errors during parsing, but might be a duplicate */ 27312612Seric CurEnv->e_to = p; 27412612Seric if (!bitset(QBADADDR, a->q_flags)) 27512612Seric message("250", "Recipient ok"); 27612612Seric else 2774549Seric { 27812612Seric /* punt -- should keep message in ADDRESS.... */ 27912612Seric message("550", "Addressee unknown"); 2804549Seric } 28112612Seric CurEnv->e_to = NULL; 2824549Seric break; 2834549Seric 2844549Seric case CMDDATA: /* data -- text of mail */ 28524943Seric SmtpPhase = "DATA"; 2864976Seric if (!hasmail) 2874549Seric { 2884976Seric message("503", "Need MAIL command"); 2894976Seric break; 2904549Seric } 29124943Seric else if (CurEnv->e_nrcpts <= 0) 2924549Seric { 2934976Seric message("503", "Need RCPT (recipient)"); 2944976Seric break; 2954549Seric } 2964976Seric 2974976Seric /* collect the text of the message */ 29824943Seric SmtpPhase = "collect"; 29925016Seric setproctitle("%s %s: %s", CurEnv->e_id, 30025050Seric CurHostName, inp); 3014976Seric collect(TRUE); 3024976Seric if (Errors != 0) 3034976Seric break; 3044976Seric 3058238Seric /* 3068238Seric ** Arrange to send to everyone. 3078238Seric ** If sending to multiple people, mail back 3088238Seric ** errors rather than reporting directly. 3098238Seric ** In any case, don't mail back errors for 3108238Seric ** anything that has happened up to 3118238Seric ** now (the other end will do this). 31210197Seric ** Truncate our transcript -- the mail has gotten 31310197Seric ** to us successfully, and if we have 31410197Seric ** to mail this back, it will be easier 31510197Seric ** on the reader. 3168238Seric ** Then send to everyone. 3178238Seric ** Finally give a reply code. If an error has 3188238Seric ** already been given, don't mail a 3198238Seric ** message back. 3209339Seric ** We goose error returns by clearing error bit. 3218238Seric */ 3228238Seric 32324943Seric SmtpPhase = "delivery"; 32424943Seric if (CurEnv->e_nrcpts != 1) 3259378Seric { 3269378Seric HoldErrs = TRUE; 32716886Seric ErrorMode = EM_MAIL; 3289378Seric } 3299339Seric CurEnv->e_flags &= ~EF_FATALERRS; 33010197Seric CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 3314976Seric 3324976Seric /* send to all recipients */ 33314877Seric sendall(CurEnv, SM_DEFAULT); 3346907Seric CurEnv->e_to = NULL; 3354976Seric 33623516Seric /* save statistics */ 33723516Seric markstats(CurEnv, (ADDRESS *) NULL); 33823516Seric 3398238Seric /* issue success if appropriate and reset */ 3408238Seric if (Errors == 0 || HoldErrs) 3419283Seric message("250", "Ok"); 3428238Seric else 3439339Seric CurEnv->e_flags &= ~EF_FATALERRS; 3449339Seric 3459339Seric /* if in a child, pop back to our parent */ 3469339Seric if (InChild) 3479339Seric finis(); 34824943Seric 34924943Seric /* clean up a bit */ 35024943Seric hasmail = 0; 35124943Seric dropenvelope(CurEnv); 35224943Seric CurEnv = newenvelope(CurEnv); 35324943Seric CurEnv->e_flags = BlankEnvelope.e_flags; 3544549Seric break; 3554549Seric 3564549Seric case CMDRSET: /* rset -- reset state */ 3574549Seric message("250", "Reset state"); 3589339Seric if (InChild) 3599339Seric finis(); 3609339Seric break; 3614549Seric 3624549Seric case CMDVRFY: /* vrfy -- verify address */ 3639339Seric if (runinchild("SMTP-VRFY") > 0) 3649339Seric break; 36525050Seric setproctitle("%s: %s", CurHostName, inp); 3665003Seric vrfyqueue = NULL; 3677762Seric QuickAbort = TRUE; 3689619Seric sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 3697762Seric if (Errors != 0) 3709339Seric { 3719339Seric if (InChild) 3729339Seric finis(); 3737762Seric break; 3749339Seric } 3755003Seric while (vrfyqueue != NULL) 3765003Seric { 3775003Seric register ADDRESS *a = vrfyqueue->q_next; 3785003Seric char *code; 3795003Seric 3807685Seric while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 3815003Seric a = a->q_next; 3825003Seric 3837685Seric if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 3845003Seric { 3855003Seric if (a != NULL) 3865003Seric code = "250-"; 3875003Seric else 3885003Seric code = "250"; 3895003Seric if (vrfyqueue->q_fullname == NULL) 3905003Seric message(code, "<%s>", vrfyqueue->q_paddr); 3915003Seric else 3925003Seric message(code, "%s <%s>", 3935003Seric vrfyqueue->q_fullname, vrfyqueue->q_paddr); 3945003Seric } 3955003Seric else if (a == NULL) 3965003Seric message("554", "Self destructive alias loop"); 3975003Seric vrfyqueue = a; 3985003Seric } 3999339Seric if (InChild) 4009339Seric finis(); 4014549Seric break; 4024549Seric 4034549Seric case CMDHELP: /* help -- give user info */ 4044577Seric if (*p == '\0') 4054577Seric p = "SMTP"; 4064577Seric help(p); 4074549Seric break; 4084549Seric 4094549Seric case CMDNOOP: /* noop -- do nothing */ 4104549Seric message("200", "OK"); 4114549Seric break; 4124549Seric 4134549Seric case CMDQUIT: /* quit -- leave mail */ 41425050Seric message("221", "%s closing connection", MyHostName); 4159339Seric if (InChild) 4169339Seric ExitStat = EX_QUIT; 4174549Seric finis(); 4184549Seric 4198544Seric case CMDVERB: /* set verbose mode */ 4208544Seric Verbose = TRUE; 42125025Seric SendMode = SM_DELIVER; 4228544Seric message("200", "Verbose mode"); 4238544Seric break; 4248544Seric 4259314Seric case CMDONEX: /* doing one transaction only */ 4269378Seric OneXact = TRUE; 4279314Seric message("200", "Only one transaction"); 4289314Seric break; 4299314Seric 43036230Skarels # ifdef SMTPDEBUG 4319339Seric case CMDDBGQSHOW: /* show queues */ 4326907Seric printf("Send Queue="); 4336907Seric printaddr(CurEnv->e_sendqueue, TRUE); 4345003Seric break; 4357275Seric 4367275Seric case CMDDBGDEBUG: /* set debug mode */ 4377676Seric tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 4387676Seric tTflag(p); 4397676Seric message("200", "Debug set"); 4407275Seric break; 4417275Seric 44236230Skarels # else /* not SMTPDEBUG */ 44324945Seric 44436230Skarels case CMDDBGQSHOW: /* show queues */ 44536230Skarels case CMDDBGDEBUG: /* set debug mode */ 44636233Skarels # ifdef LOG 44736233Skarels if (RealHostName != NULL && LogLevel > 0) 44836230Skarels syslog(LOG_NOTICE, 44936230Skarels "\"%s\" command from %s (%s)\n", 45036230Skarels c->cmdname, RealHostName, 45136230Skarels inet_ntoa(RealHostAddr.sin_addr)); 45236233Skarels # endif 45336230Skarels /* FALL THROUGH */ 45436230Skarels # endif /* SMTPDEBUG */ 45536230Skarels 4564549Seric case CMDERROR: /* unknown command */ 4574549Seric message("500", "Command unrecognized"); 4584549Seric break; 4594549Seric 4604549Seric default: 46136230Skarels errno = 0; 4624549Seric syserr("smtp: unknown code %d", c->cmdcode); 4634549Seric break; 4644549Seric } 4654549Seric } 4664549Seric } 4674549Seric /* 4684549Seric ** SKIPWORD -- skip a fixed word. 4694549Seric ** 4704549Seric ** Parameters: 4714549Seric ** p -- place to start looking. 4724549Seric ** w -- word to skip. 4734549Seric ** 4744549Seric ** Returns: 4754549Seric ** p following w. 4764549Seric ** NULL on error. 4774549Seric ** 4784549Seric ** Side Effects: 4794549Seric ** clobbers the p data area. 4804549Seric */ 4814549Seric 4824549Seric static char * 4834549Seric skipword(p, w) 4844549Seric register char *p; 4854549Seric char *w; 4864549Seric { 4874549Seric register char *q; 4884549Seric 4894549Seric /* find beginning of word */ 4904549Seric while (isspace(*p)) 4914549Seric p++; 4924549Seric q = p; 4934549Seric 4944549Seric /* find end of word */ 4954549Seric while (*p != '\0' && *p != ':' && !isspace(*p)) 4964549Seric p++; 4974549Seric while (isspace(*p)) 4984549Seric *p++ = '\0'; 4994549Seric if (*p != ':') 5004549Seric { 5014549Seric syntax: 5024549Seric message("501", "Syntax error"); 5034549Seric Errors++; 5044549Seric return (NULL); 5054549Seric } 5064549Seric *p++ = '\0'; 5074549Seric while (isspace(*p)) 5084549Seric p++; 5094549Seric 5104549Seric /* see if the input word matches desired word */ 51133725Sbostic if (strcasecmp(q, w)) 5124549Seric goto syntax; 5134549Seric 5144549Seric return (p); 5154549Seric } 5164577Seric /* 5174577Seric ** HELP -- implement the HELP command. 5184577Seric ** 5194577Seric ** Parameters: 5204577Seric ** topic -- the topic we want help for. 5214577Seric ** 5224577Seric ** Returns: 5234577Seric ** none. 5244577Seric ** 5254577Seric ** Side Effects: 5264577Seric ** outputs the help file to message output. 5274577Seric */ 5284577Seric 5294577Seric help(topic) 5304577Seric char *topic; 5314577Seric { 5324577Seric register FILE *hf; 5334577Seric int len; 5344577Seric char buf[MAXLINE]; 5354577Seric bool noinfo; 5364577Seric 5378269Seric if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 5384577Seric { 5394577Seric /* no help */ 54011931Seric errno = 0; 5414577Seric message("502", "HELP not implemented"); 5424577Seric return; 5434577Seric } 5444577Seric 5454577Seric len = strlen(topic); 5464577Seric makelower(topic); 5474577Seric noinfo = TRUE; 5484577Seric 5494577Seric while (fgets(buf, sizeof buf, hf) != NULL) 5504577Seric { 5514577Seric if (strncmp(buf, topic, len) == 0) 5524577Seric { 5534577Seric register char *p; 5544577Seric 5554577Seric p = index(buf, '\t'); 5564577Seric if (p == NULL) 5574577Seric p = buf; 5584577Seric else 5594577Seric p++; 5604577Seric fixcrlf(p, TRUE); 5614577Seric message("214-", p); 5624577Seric noinfo = FALSE; 5634577Seric } 5644577Seric } 5654577Seric 5664577Seric if (noinfo) 5674577Seric message("504", "HELP topic unknown"); 5684577Seric else 5694577Seric message("214", "End of HELP info"); 5704628Seric (void) fclose(hf); 5714577Seric } 5728544Seric /* 5739339Seric ** RUNINCHILD -- return twice -- once in the child, then in the parent again 5749339Seric ** 5759339Seric ** Parameters: 5769339Seric ** label -- a string used in error messages 5779339Seric ** 5789339Seric ** Returns: 5799339Seric ** zero in the child 5809339Seric ** one in the parent 5819339Seric ** 5829339Seric ** Side Effects: 5839339Seric ** none. 5849339Seric */ 5858544Seric 5869339Seric runinchild(label) 5879339Seric char *label; 5889339Seric { 5899339Seric int childpid; 5909339Seric 59116158Seric if (!OneXact) 5929339Seric { 59316158Seric childpid = dofork(); 59416158Seric if (childpid < 0) 59516158Seric { 59616158Seric syserr("%s: cannot fork", label); 59716158Seric return (1); 59816158Seric } 59916158Seric if (childpid > 0) 60016158Seric { 60116158Seric auto int st; 6029339Seric 60316158Seric /* parent -- wait for child to complete */ 60416158Seric st = waitfor(childpid); 60516158Seric if (st == -1) 60616158Seric syserr("%s: lost child", label); 6079339Seric 60816158Seric /* if we exited on a QUIT command, complete the process */ 60916158Seric if (st == (EX_QUIT << 8)) 61016158Seric finis(); 6119339Seric 61216158Seric return (1); 61316158Seric } 61416158Seric else 61516158Seric { 61616158Seric /* child */ 61716158Seric InChild = TRUE; 61825050Seric QuickAbort = FALSE; 61925614Seric clearenvelope(CurEnv, FALSE); 62016158Seric } 6219339Seric } 62215256Seric 62316158Seric /* open alias database */ 62416158Seric initaliases(AliasFile, FALSE); 62516158Seric 62616158Seric return (0); 6279339Seric } 6289339Seric 6295181Seric # endif SMTP 630