19339Seric # include <errno.h>
24549Seric # include "sendmail.h"
34549Seric 
45181Seric # ifndef SMTP
5*9378Seric SCCSID(@(#)srvrsmtp.c	3.39		11/28/82	(no SMTP));
65181Seric # else SMTP
74556Seric 
8*9378Seric SCCSID(@(#)srvrsmtp.c	3.39		11/28/82);
95181Seric 
104549Seric /*
114549Seric **  SMTP -- run the SMTP protocol.
124549Seric **
134549Seric **	Parameters:
144549Seric **		none.
154549Seric **
164549Seric **	Returns:
174549Seric **		never.
184549Seric **
194549Seric **	Side Effects:
204549Seric **		Reads commands from the input channel and processes
214549Seric **			them.
224549Seric */
234549Seric 
244549Seric struct cmd
254549Seric {
264549Seric 	char	*cmdname;	/* command name */
274549Seric 	int	cmdcode;	/* internal code, see below */
284549Seric };
294549Seric 
304549Seric /* values for cmdcode */
314549Seric # define CMDERROR	0	/* bad command */
324549Seric # define CMDMAIL	1	/* mail -- designate sender */
334976Seric # define CMDRCPT	2	/* rcpt -- designate recipient */
344549Seric # define CMDDATA	3	/* data -- send message text */
359339Seric # define CMDRSET	4	/* rset -- reset state */
369339Seric # define CMDVRFY	5	/* vrfy -- verify address */
379339Seric # define CMDHELP	6	/* help -- give usage info */
389339Seric # define CMDNOOP	7	/* noop -- do nothing */
399339Seric # define CMDQUIT	8	/* quit -- close connection and die */
409339Seric # define CMDHELO	9	/* helo -- be polite */
419339Seric # define CMDDBGQSHOW	10	/* showq -- show send queue (DEBUG) */
429339Seric # define CMDDBGDEBUG	11	/* debug -- set debug mode */
439339Seric # define CMDVERB	12	/* verb -- go into verbose mode */
449339Seric # define CMDDBGKILL	13	/* kill -- kill sendmail */
459339Seric # define CMDDBGWIZ	14	/* wiz -- become a wizard */
469339Seric # define CMDONEX	15	/* onex -- sending one transaction only */
479339Seric # define CMDDBGSHELL	16	/* shell -- give us a shell */
484549Seric 
494549Seric static struct cmd	CmdTab[] =
504549Seric {
514549Seric 	"mail",		CMDMAIL,
524976Seric 	"rcpt",		CMDRCPT,
534549Seric 	"data",		CMDDATA,
544549Seric 	"rset",		CMDRSET,
554549Seric 	"vrfy",		CMDVRFY,
567762Seric 	"expn",		CMDVRFY,
574549Seric 	"help",		CMDHELP,
584549Seric 	"noop",		CMDNOOP,
594549Seric 	"quit",		CMDQUIT,
604976Seric 	"helo",		CMDHELO,
618544Seric 	"verb",		CMDVERB,
629314Seric 	"onex",		CMDONEX,
635003Seric # ifdef DEBUG
649339Seric 	"showq",	CMDDBGQSHOW,
658544Seric 	"debug",	CMDDBGDEBUG,
668544Seric 	"kill",		CMDDBGKILL,
678544Seric 	"wiz",		CMDDBGWIZ,
689339Seric 	"shell",	CMDDBGSHELL,
695003Seric # endif DEBUG
704549Seric 	NULL,		CMDERROR,
714549Seric };
724549Seric 
738544Seric # ifdef DEBUG
748544Seric bool	IsWiz = FALSE;			/* set if we are a wizard */
758544Seric char	*WizWord = NULL;		/* the wizard word to compare against */
768544Seric # endif DEBUG
779339Seric bool	InChild = FALSE;		/* true if running in a subprocess */
78*9378Seric bool	OneXact = FALSE;		/* one xaction only this run */
799339Seric #define EX_QUIT		22		/* special code for QUIT command */
808544Seric 
814549Seric smtp()
824549Seric {
834549Seric 	register char *p;
848544Seric 	register struct cmd *c;
854549Seric 	char *cmd;
864549Seric 	extern char *skipword();
874549Seric 	extern bool sameword();
884549Seric 	bool hasmail;			/* mail command received */
894713Seric 	int rcps;			/* number of recipients */
905003Seric 	auto ADDRESS *vrfyqueue;
918544Seric 	char inp[MAXLINE];
927124Seric 	extern char Version[];
937356Seric 	extern tick();
948544Seric 	extern bool iswiz();
959349Seric 	extern char *arpadate();
964549Seric 
975003Seric 	hasmail = FALSE;
984713Seric 	rcps = 0;
997363Seric 	if (OutChannel != stdout)
1007363Seric 	{
1017363Seric 		/* arrange for debugging output to go to remote host */
1027363Seric 		(void) close(1);
1037363Seric 		(void) dup(fileno(OutChannel));
1047363Seric 	}
1057851Seric 	message("220", "%s Sendmail %s ready at %s", HostName,
1069349Seric 			Version, arpadate((char *) NULL));
1077762Seric 	(void) setjmp(TopFrame);
1087762Seric 	QuickAbort = FALSE;
1094549Seric 	for (;;)
1104549Seric 	{
1117356Seric 		/* setup for the read */
1126907Seric 		CurEnv->e_to = NULL;
1134577Seric 		Errors = 0;
1147275Seric 		(void) fflush(stdout);
1157356Seric 
1167356Seric 		/* read the input line */
1177685Seric 		p = sfgets(inp, sizeof inp, InChannel);
1187356Seric 
1197685Seric 		/* handle errors */
1207356Seric 		if (p == NULL)
1217356Seric 		{
1224549Seric 			/* end of file, just die */
1234558Seric 			message("421", "%s Lost input channel", HostName);
1244549Seric 			finis();
1254549Seric 		}
1264549Seric 
1274549Seric 		/* clean up end of line */
1284558Seric 		fixcrlf(inp, TRUE);
1294549Seric 
1304713Seric 		/* echo command to transcript */
1319339Seric 		if (Xscript != NULL)
1329339Seric 			fprintf(Xscript, "<<< %s\n", inp);
1334713Seric 
1344549Seric 		/* break off command */
1354549Seric 		for (p = inp; isspace(*p); p++)
1364549Seric 			continue;
1374549Seric 		cmd = p;
1384549Seric 		while (*++p != '\0' && !isspace(*p))
1394549Seric 			continue;
1404549Seric 		if (*p != '\0')
1414549Seric 			*p++ = '\0';
1424549Seric 
1434549Seric 		/* decode command */
1444549Seric 		for (c = CmdTab; c->cmdname != NULL; c++)
1454549Seric 		{
1464549Seric 			if (sameword(c->cmdname, cmd))
1474549Seric 				break;
1484549Seric 		}
1494549Seric 
1504549Seric 		/* process command */
1514549Seric 		switch (c->cmdcode)
1524549Seric 		{
1534976Seric 		  case CMDHELO:		/* hello -- introduce yourself */
154*9378Seric 			define('s', newstr(p), CurEnv);
1554997Seric 			message("250", "%s Hello %s, pleased to meet you",
1564997Seric 				HostName, p);
1574976Seric 			break;
1584976Seric 
1594549Seric 		  case CMDMAIL:		/* mail -- designate sender */
1609314Seric 			/* check for validity of this command */
1614558Seric 			if (hasmail)
1624558Seric 			{
1634558Seric 				message("503", "Sender already specified");
1644558Seric 				break;
1654558Seric 			}
1669339Seric 			if (InChild)
1679339Seric 			{
1689339Seric 				syserr("Nested MAIL command");
1699339Seric 				exit(0);
1709339Seric 			}
1719339Seric 
1729339Seric 			/* fork a subprocess to process this command */
1739339Seric 			if (runinchild("SMTP-MAIL") > 0)
1749339Seric 				break;
1759339Seric 			initsys();
1769339Seric 
1779339Seric 			/* child -- go do the processing */
1784549Seric 			p = skipword(p, "from");
1794549Seric 			if (p == NULL)
1804549Seric 				break;
1814549Seric 			setsender(p);
1824577Seric 			if (Errors == 0)
1834549Seric 			{
1844549Seric 				message("250", "Sender ok");
1854549Seric 				hasmail = TRUE;
1864549Seric 			}
1879339Seric 			else if (InChild)
1889339Seric 				finis();
1894549Seric 			break;
1904549Seric 
1914976Seric 		  case CMDRCPT:		/* rcpt -- designate recipient */
1924549Seric 			p = skipword(p, "to");
1934549Seric 			if (p == NULL)
1944549Seric 				break;
1958081Seric 			sendto(p, (ADDRESS *) NULL, &CurEnv->e_sendqueue);
1964577Seric 			if (Errors == 0)
1974549Seric 			{
1986057Seric 				message("250", "%s... Recipient ok", p);
1994713Seric 				rcps++;
2004549Seric 			}
2014549Seric 			break;
2024549Seric 
2034549Seric 		  case CMDDATA:		/* data -- text of mail */
2044976Seric 			if (!hasmail)
2054549Seric 			{
2064976Seric 				message("503", "Need MAIL command");
2074976Seric 				break;
2084549Seric 			}
2094713Seric 			else if (rcps <= 0)
2104549Seric 			{
2114976Seric 				message("503", "Need RCPT (recipient)");
2124976Seric 				break;
2134549Seric 			}
2144976Seric 
2154976Seric 			/* collect the text of the message */
2164976Seric 			collect(TRUE);
2174976Seric 			if (Errors != 0)
2184976Seric 				break;
2194976Seric 
2208238Seric 			/*
2218238Seric 			**  Arrange to send to everyone.
2228238Seric 			**	If sending to multiple people, mail back
2238238Seric 			**		errors rather than reporting directly.
2248238Seric 			**	In any case, don't mail back errors for
2258238Seric 			**		anything that has happened up to
2268238Seric 			**		now (the other end will do this).
2278238Seric 			**	Then send to everyone.
2288238Seric 			**	Finally give a reply code.  If an error has
2298238Seric 			**		already been given, don't mail a
2308238Seric 			**		message back.
2319339Seric 			**	We goose error returns by clearing error bit.
2328238Seric 			*/
2338238Seric 
2344976Seric 			if (rcps != 1)
235*9378Seric 			{
236*9378Seric 				HoldErrs = TRUE;
237*9378Seric 				ErrorMode == EM_MAIL;
238*9378Seric 			}
2399339Seric 			CurEnv->e_flags &= ~EF_FATALERRS;
2404976Seric 
2414976Seric 			/* send to all recipients */
2429283Seric 			sendall(CurEnv, SendMode);
2436907Seric 			CurEnv->e_to = NULL;
2444976Seric 
2458238Seric 			/* issue success if appropriate and reset */
2468238Seric 			if (Errors == 0 || HoldErrs)
2478238Seric 			{
2488238Seric 				HoldErrs = FALSE;
2499283Seric 				message("250", "Ok");
2508238Seric 			}
2518238Seric 			else
2529339Seric 				CurEnv->e_flags &= ~EF_FATALERRS;
2539339Seric 
2549339Seric 			/* if in a child, pop back to our parent */
2559339Seric 			if (InChild)
2569339Seric 				finis();
2574549Seric 			break;
2584549Seric 
2594549Seric 		  case CMDRSET:		/* rset -- reset state */
2604549Seric 			message("250", "Reset state");
2619339Seric 			if (InChild)
2629339Seric 				finis();
2639339Seric 			break;
2644549Seric 
2654549Seric 		  case CMDVRFY:		/* vrfy -- verify address */
2669339Seric 			if (runinchild("SMTP-VRFY") > 0)
2679339Seric 				break;
2685003Seric 			vrfyqueue = NULL;
2697762Seric 			QuickAbort = TRUE;
2708081Seric 			sendto(p, (ADDRESS *) NULL, &vrfyqueue);
2717762Seric 			if (Errors != 0)
2729339Seric 			{
2739339Seric 				if (InChild)
2749339Seric 					finis();
2757762Seric 				break;
2769339Seric 			}
2775003Seric 			while (vrfyqueue != NULL)
2785003Seric 			{
2795003Seric 				register ADDRESS *a = vrfyqueue->q_next;
2805003Seric 				char *code;
2815003Seric 
2827685Seric 				while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
2835003Seric 					a = a->q_next;
2845003Seric 
2857685Seric 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
2865003Seric 				{
2875003Seric 					if (a != NULL)
2885003Seric 						code = "250-";
2895003Seric 					else
2905003Seric 						code = "250";
2915003Seric 					if (vrfyqueue->q_fullname == NULL)
2925003Seric 						message(code, "<%s>", vrfyqueue->q_paddr);
2935003Seric 					else
2945003Seric 						message(code, "%s <%s>",
2955003Seric 						    vrfyqueue->q_fullname, vrfyqueue->q_paddr);
2965003Seric 				}
2975003Seric 				else if (a == NULL)
2985003Seric 					message("554", "Self destructive alias loop");
2995003Seric 				vrfyqueue = a;
3005003Seric 			}
3019339Seric 			if (InChild)
3029339Seric 				finis();
3034549Seric 			break;
3044549Seric 
3054549Seric 		  case CMDHELP:		/* help -- give user info */
3064577Seric 			if (*p == '\0')
3074577Seric 				p = "SMTP";
3084577Seric 			help(p);
3094549Seric 			break;
3104549Seric 
3114549Seric 		  case CMDNOOP:		/* noop -- do nothing */
3124549Seric 			message("200", "OK");
3134549Seric 			break;
3144549Seric 
3154549Seric 		  case CMDQUIT:		/* quit -- leave mail */
3164549Seric 			message("221", "%s closing connection", HostName);
3179339Seric 			if (InChild)
3189339Seric 				ExitStat = EX_QUIT;
3194549Seric 			finis();
3204549Seric 
3218544Seric 		  case CMDVERB:		/* set verbose mode */
3228544Seric 			Verbose = TRUE;
3238544Seric 			message("200", "Verbose mode");
3248544Seric 			break;
3258544Seric 
3269314Seric 		  case CMDONEX:		/* doing one transaction only */
327*9378Seric 			OneXact = TRUE;
3289314Seric 			message("200", "Only one transaction");
3299314Seric 			break;
3309314Seric 
3315003Seric # ifdef DEBUG
3329339Seric 		  case CMDDBGQSHOW:	/* show queues */
3336907Seric 			printf("Send Queue=");
3346907Seric 			printaddr(CurEnv->e_sendqueue, TRUE);
3355003Seric 			break;
3367275Seric 
3377275Seric 		  case CMDDBGDEBUG:	/* set debug mode */
3387676Seric 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
3397676Seric 			tTflag(p);
3407676Seric 			message("200", "Debug set");
3417275Seric 			break;
3427275Seric 
3437282Seric 		  case CMDDBGKILL:	/* kill the parent */
3448544Seric 			if (!iswiz())
3458544Seric 				break;
3467282Seric 			if (kill(MotherPid, SIGTERM) >= 0)
3477282Seric 				message("200", "Mother is dead");
3487282Seric 			else
3497282Seric 				message("500", "Can't kill Mom");
3507282Seric 			break;
3518544Seric 
3529339Seric 		  case CMDDBGSHELL:	/* give us an interactive shell */
3539339Seric 			if (!iswiz())
3549339Seric 				break;
355*9378Seric 			if (fileno(InChannel) != 0)
356*9378Seric 			{
357*9378Seric 				(void) close(0);
358*9378Seric 				(void) dup(fileno(InChannel));
359*9378Seric 				(void) fclose(InChannel);
360*9378Seric 				InChannel = stdin;
361*9378Seric 			}
362*9378Seric 			if (fileno(OutChannel) != 1)
363*9378Seric 			{
364*9378Seric 				(void) close(1);
365*9378Seric 				(void) dup(fileno(OutChannel));
366*9378Seric 				(void) fclose(OutChannel);
367*9378Seric 				OutChannel = stdout;
368*9378Seric 			}
3699339Seric 			execl("/bin/csh", "sendmail", 0);
3709339Seric 			execl("/bin/sh", "sendmail", 0);
3719339Seric 			message("500", "Can't");
372*9378Seric 			exit(EX_UNAVAILABLE);
3739339Seric 
3748544Seric 		  case CMDDBGWIZ:	/* become a wizard */
3758544Seric 			if (WizWord != NULL)
3768544Seric 			{
3778544Seric 				char seed[3];
3788544Seric 				extern char *crypt();
3798544Seric 
3808544Seric 				strncpy(seed, WizWord, 2);
3818544Seric 				if (strcmp(WizWord, crypt(p, seed)) != 0)
3828544Seric 				{
3838544Seric 					message("500", "You are no wizard!");
3848544Seric 					break;
3858544Seric 				}
3868544Seric 			}
3878544Seric 			IsWiz = TRUE;
3888544Seric 			message("200", "Please pass, oh mighty wizard");
3898544Seric 			break;
3905003Seric # endif DEBUG
3915003Seric 
3924549Seric 		  case CMDERROR:	/* unknown command */
3934549Seric 			message("500", "Command unrecognized");
3944549Seric 			break;
3954549Seric 
3964549Seric 		  default:
3974549Seric 			syserr("smtp: unknown code %d", c->cmdcode);
3984549Seric 			break;
3994549Seric 		}
4004549Seric 	}
4014549Seric }
4024549Seric /*
4034549Seric **  SKIPWORD -- skip a fixed word.
4044549Seric **
4054549Seric **	Parameters:
4064549Seric **		p -- place to start looking.
4074549Seric **		w -- word to skip.
4084549Seric **
4094549Seric **	Returns:
4104549Seric **		p following w.
4114549Seric **		NULL on error.
4124549Seric **
4134549Seric **	Side Effects:
4144549Seric **		clobbers the p data area.
4154549Seric */
4164549Seric 
4174549Seric static char *
4184549Seric skipword(p, w)
4194549Seric 	register char *p;
4204549Seric 	char *w;
4214549Seric {
4224549Seric 	register char *q;
4234549Seric 	extern bool sameword();
4244549Seric 
4254549Seric 	/* find beginning of word */
4264549Seric 	while (isspace(*p))
4274549Seric 		p++;
4284549Seric 	q = p;
4294549Seric 
4304549Seric 	/* find end of word */
4314549Seric 	while (*p != '\0' && *p != ':' && !isspace(*p))
4324549Seric 		p++;
4334549Seric 	while (isspace(*p))
4344549Seric 		*p++ = '\0';
4354549Seric 	if (*p != ':')
4364549Seric 	{
4374549Seric 	  syntax:
4384549Seric 		message("501", "Syntax error");
4394549Seric 		Errors++;
4404549Seric 		return (NULL);
4414549Seric 	}
4424549Seric 	*p++ = '\0';
4434549Seric 	while (isspace(*p))
4444549Seric 		p++;
4454549Seric 
4464549Seric 	/* see if the input word matches desired word */
4474549Seric 	if (!sameword(q, w))
4484549Seric 		goto syntax;
4494549Seric 
4504549Seric 	return (p);
4514549Seric }
4524577Seric /*
4534577Seric **  HELP -- implement the HELP command.
4544577Seric **
4554577Seric **	Parameters:
4564577Seric **		topic -- the topic we want help for.
4574577Seric **
4584577Seric **	Returns:
4594577Seric **		none.
4604577Seric **
4614577Seric **	Side Effects:
4624577Seric **		outputs the help file to message output.
4634577Seric */
4644577Seric 
4654577Seric help(topic)
4664577Seric 	char *topic;
4674577Seric {
4684577Seric 	register FILE *hf;
4694577Seric 	int len;
4704577Seric 	char buf[MAXLINE];
4714577Seric 	bool noinfo;
4724577Seric 
4738269Seric 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
4744577Seric 	{
4754577Seric 		/* no help */
4764577Seric 		message("502", "HELP not implemented");
4774577Seric 		return;
4784577Seric 	}
4794577Seric 
4804577Seric 	len = strlen(topic);
4814577Seric 	makelower(topic);
4824577Seric 	noinfo = TRUE;
4834577Seric 
4844577Seric 	while (fgets(buf, sizeof buf, hf) != NULL)
4854577Seric 	{
4864577Seric 		if (strncmp(buf, topic, len) == 0)
4874577Seric 		{
4884577Seric 			register char *p;
4894577Seric 
4904577Seric 			p = index(buf, '\t');
4914577Seric 			if (p == NULL)
4924577Seric 				p = buf;
4934577Seric 			else
4944577Seric 				p++;
4954577Seric 			fixcrlf(p, TRUE);
4964577Seric 			message("214-", p);
4974577Seric 			noinfo = FALSE;
4984577Seric 		}
4994577Seric 	}
5004577Seric 
5014577Seric 	if (noinfo)
5024577Seric 		message("504", "HELP topic unknown");
5034577Seric 	else
5044577Seric 		message("214", "End of HELP info");
5054628Seric 	(void) fclose(hf);
5064577Seric }
5078544Seric /*
5088544Seric **  ISWIZ -- tell us if we are a wizard
5098544Seric **
5108544Seric **	If not, print a nasty message.
5118544Seric **
5128544Seric **	Parameters:
5138544Seric **		none.
5148544Seric **
5158544Seric **	Returns:
5168544Seric **		TRUE if we are a wizard.
5178544Seric **		FALSE if we are not a wizard.
5188544Seric **
5198544Seric **	Side Effects:
5208544Seric **		Prints a 500 exit stat if we are not a wizard.
5218544Seric */
5225181Seric 
5238544Seric bool
5248544Seric iswiz()
5258544Seric {
5268544Seric 	if (!IsWiz)
5278544Seric 		message("500", "Mere mortals musn't mutter that mantra");
5288544Seric 	return (IsWiz);
5298544Seric }
5309339Seric /*
5319339Seric **  RUNINCHILD -- return twice -- once in the child, then in the parent again
5329339Seric **
5339339Seric **	Parameters:
5349339Seric **		label -- a string used in error messages
5359339Seric **
5369339Seric **	Returns:
5379339Seric **		zero in the child
5389339Seric **		one in the parent
5399339Seric **
5409339Seric **	Side Effects:
5419339Seric **		none.
5429339Seric */
5438544Seric 
5449339Seric runinchild(label)
5459339Seric 	char *label;
5469339Seric {
5479339Seric 	int childpid;
5489339Seric 
549*9378Seric 	if (OneXact)
550*9378Seric 		return (0);
551*9378Seric 
5529339Seric 	childpid = dofork();
5539339Seric 	if (childpid < 0)
5549339Seric 	{
5559339Seric 		syserr("%s: cannot fork", label);
5569339Seric 		return (1);
5579339Seric 	}
5589339Seric 	if (childpid > 0)
5599339Seric 	{
5609339Seric 		auto int st;
5619339Seric 
562*9378Seric 		/* parent -- wait for child to complete */
563*9378Seric 		st = waitfor(childpid);
564*9378Seric 		if (st == -1)
5659339Seric 			syserr("%s: lost child", label);
5669339Seric 
5679339Seric 		/* if we exited on a QUIT command, complete the process */
5689339Seric 		if (st == (EX_QUIT << 8))
5699339Seric 			finis();
5709339Seric 
5719339Seric 		return (1);
5729339Seric 	}
5739339Seric 	else
5749339Seric 	{
5759339Seric 		/* child */
5769339Seric 		InChild = TRUE;
5779339Seric 		return (0);
5789339Seric 	}
5799339Seric }
5809339Seric 
5815181Seric # endif SMTP
582