19339Seric # include <errno.h>
24549Seric # include "sendmail.h"
34549Seric 
45181Seric # ifndef SMTP
5*11146Seric SCCSID(@(#)srvrsmtp.c	3.46		02/18/83	(no SMTP));
65181Seric # else SMTP
74556Seric 
8*11146Seric SCCSID(@(#)srvrsmtp.c	3.46		02/18/83);
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 */
789378Seric bool	OneXact = FALSE;		/* one xaction only this run */
79*11146Seric char	*RealHostName = NULL;		/* verified hostname, set in daemon.c */
80*11146Seric 
819339Seric #define EX_QUIT		22		/* special code for QUIT command */
828544Seric 
834549Seric smtp()
844549Seric {
854549Seric 	register char *p;
868544Seric 	register struct cmd *c;
874549Seric 	char *cmd;
884549Seric 	extern char *skipword();
894549Seric 	extern bool sameword();
904549Seric 	bool hasmail;			/* mail command received */
914713Seric 	int rcps;			/* number of recipients */
925003Seric 	auto ADDRESS *vrfyqueue;
938544Seric 	char inp[MAXLINE];
947124Seric 	extern char Version[];
957356Seric 	extern tick();
968544Seric 	extern bool iswiz();
979349Seric 	extern char *arpadate();
984549Seric 
995003Seric 	hasmail = FALSE;
1004713Seric 	rcps = 0;
1017363Seric 	if (OutChannel != stdout)
1027363Seric 	{
1037363Seric 		/* arrange for debugging output to go to remote host */
1047363Seric 		(void) close(1);
1057363Seric 		(void) dup(fileno(OutChannel));
1067363Seric 	}
10710708Seric 	expand("$e", inp, &inp[sizeof inp], CurEnv);
10810708Seric 	message("220", inp);
1097762Seric 	(void) setjmp(TopFrame);
1107762Seric 	QuickAbort = FALSE;
1119390Seric 	HoldErrs = FALSE;
1124549Seric 	for (;;)
1134549Seric 	{
1147356Seric 		/* setup for the read */
1156907Seric 		CurEnv->e_to = NULL;
1164577Seric 		Errors = 0;
1177275Seric 		(void) fflush(stdout);
1187356Seric 
1197356Seric 		/* read the input line */
1207685Seric 		p = sfgets(inp, sizeof inp, InChannel);
1217356Seric 
1227685Seric 		/* handle errors */
1237356Seric 		if (p == NULL)
1247356Seric 		{
1254549Seric 			/* end of file, just die */
1264558Seric 			message("421", "%s Lost input channel", HostName);
1274549Seric 			finis();
1284549Seric 		}
1294549Seric 
1304549Seric 		/* clean up end of line */
1314558Seric 		fixcrlf(inp, TRUE);
1324549Seric 
1334713Seric 		/* echo command to transcript */
1349545Seric 		if (CurEnv->e_xfp != NULL)
1359545Seric 			fprintf(CurEnv->e_xfp, "<<< %s\n", inp);
1364713Seric 
1374549Seric 		/* break off command */
1384549Seric 		for (p = inp; isspace(*p); p++)
1394549Seric 			continue;
1404549Seric 		cmd = p;
1414549Seric 		while (*++p != '\0' && !isspace(*p))
1424549Seric 			continue;
1434549Seric 		if (*p != '\0')
1444549Seric 			*p++ = '\0';
1454549Seric 
1464549Seric 		/* decode command */
1474549Seric 		for (c = CmdTab; c->cmdname != NULL; c++)
1484549Seric 		{
1494549Seric 			if (sameword(c->cmdname, cmd))
1504549Seric 				break;
1514549Seric 		}
1524549Seric 
1534549Seric 		/* process command */
1544549Seric 		switch (c->cmdcode)
1554549Seric 		{
1564976Seric 		  case CMDHELO:		/* hello -- introduce yourself */
157*11146Seric 			if (RealHostName != NULL && !sameword(p, RealHostName))
158*11146Seric 			{
159*11146Seric 				char buf[MAXNAME];
160*11146Seric 
161*11146Seric 				(void) sprintf(buf, "%s (%s)", p, RealHostName);
162*11146Seric 				define('s', newstr(buf), CurEnv);
163*11146Seric 			}
164*11146Seric 			else
165*11146Seric 				define('s', newstr(p), CurEnv);
1664997Seric 			message("250", "%s Hello %s, pleased to meet you",
1674997Seric 				HostName, p);
1684976Seric 			break;
1694976Seric 
1704549Seric 		  case CMDMAIL:		/* mail -- designate sender */
1719314Seric 			/* check for validity of this command */
1724558Seric 			if (hasmail)
1734558Seric 			{
1744558Seric 				message("503", "Sender already specified");
1754558Seric 				break;
1764558Seric 			}
1779339Seric 			if (InChild)
1789339Seric 			{
1799339Seric 				syserr("Nested MAIL command");
1809339Seric 				exit(0);
1819339Seric 			}
1829339Seric 
1839339Seric 			/* fork a subprocess to process this command */
1849339Seric 			if (runinchild("SMTP-MAIL") > 0)
1859339Seric 				break;
1869339Seric 			initsys();
1879339Seric 
1889339Seric 			/* child -- go do the processing */
1894549Seric 			p = skipword(p, "from");
1904549Seric 			if (p == NULL)
1914549Seric 				break;
1924549Seric 			setsender(p);
1934577Seric 			if (Errors == 0)
1944549Seric 			{
1954549Seric 				message("250", "Sender ok");
1964549Seric 				hasmail = TRUE;
1974549Seric 			}
1989339Seric 			else if (InChild)
1999339Seric 				finis();
2004549Seric 			break;
2014549Seric 
2024976Seric 		  case CMDRCPT:		/* rcpt -- designate recipient */
2034549Seric 			p = skipword(p, "to");
2044549Seric 			if (p == NULL)
2054549Seric 				break;
2069619Seric 			sendtolist(p, (ADDRESS *) NULL, &CurEnv->e_sendqueue);
2079390Seric 			CurEnv->e_flags &= ~EF_FATALERRS;
2084577Seric 			if (Errors == 0)
2094549Seric 			{
2106057Seric 				message("250", "%s... Recipient ok", p);
2114713Seric 				rcps++;
2124549Seric 			}
2134549Seric 			break;
2144549Seric 
2154549Seric 		  case CMDDATA:		/* data -- text of mail */
2164976Seric 			if (!hasmail)
2174549Seric 			{
2184976Seric 				message("503", "Need MAIL command");
2194976Seric 				break;
2204549Seric 			}
2214713Seric 			else if (rcps <= 0)
2224549Seric 			{
2234976Seric 				message("503", "Need RCPT (recipient)");
2244976Seric 				break;
2254549Seric 			}
2264976Seric 
2274976Seric 			/* collect the text of the message */
2284976Seric 			collect(TRUE);
2294976Seric 			if (Errors != 0)
2304976Seric 				break;
2314976Seric 
2328238Seric 			/*
2338238Seric 			**  Arrange to send to everyone.
2348238Seric 			**	If sending to multiple people, mail back
2358238Seric 			**		errors rather than reporting directly.
2368238Seric 			**	In any case, don't mail back errors for
2378238Seric 			**		anything that has happened up to
2388238Seric 			**		now (the other end will do this).
23910197Seric 			**	Truncate our transcript -- the mail has gotten
24010197Seric 			**		to us successfully, and if we have
24110197Seric 			**		to mail this back, it will be easier
24210197Seric 			**		on the reader.
2438238Seric 			**	Then send to everyone.
2448238Seric 			**	Finally give a reply code.  If an error has
2458238Seric 			**		already been given, don't mail a
2468238Seric 			**		message back.
2479339Seric 			**	We goose error returns by clearing error bit.
2488238Seric 			*/
2498238Seric 
2504976Seric 			if (rcps != 1)
2519378Seric 			{
2529378Seric 				HoldErrs = TRUE;
2539378Seric 				ErrorMode == EM_MAIL;
2549378Seric 			}
2559339Seric 			CurEnv->e_flags &= ~EF_FATALERRS;
25610197Seric 			CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp);
2574976Seric 
2584976Seric 			/* send to all recipients */
2599283Seric 			sendall(CurEnv, SendMode);
2606907Seric 			CurEnv->e_to = NULL;
2614976Seric 
2628238Seric 			/* issue success if appropriate and reset */
2638238Seric 			if (Errors == 0 || HoldErrs)
2649283Seric 				message("250", "Ok");
2658238Seric 			else
2669339Seric 				CurEnv->e_flags &= ~EF_FATALERRS;
2679339Seric 
2689339Seric 			/* if in a child, pop back to our parent */
2699339Seric 			if (InChild)
2709339Seric 				finis();
2714549Seric 			break;
2724549Seric 
2734549Seric 		  case CMDRSET:		/* rset -- reset state */
2744549Seric 			message("250", "Reset state");
2759339Seric 			if (InChild)
2769339Seric 				finis();
2779339Seric 			break;
2784549Seric 
2794549Seric 		  case CMDVRFY:		/* vrfy -- verify address */
2809339Seric 			if (runinchild("SMTP-VRFY") > 0)
2819339Seric 				break;
2825003Seric 			vrfyqueue = NULL;
2837762Seric 			QuickAbort = TRUE;
2849619Seric 			sendtolist(p, (ADDRESS *) NULL, &vrfyqueue);
2857762Seric 			if (Errors != 0)
2869339Seric 			{
2879339Seric 				if (InChild)
2889339Seric 					finis();
2897762Seric 				break;
2909339Seric 			}
2915003Seric 			while (vrfyqueue != NULL)
2925003Seric 			{
2935003Seric 				register ADDRESS *a = vrfyqueue->q_next;
2945003Seric 				char *code;
2955003Seric 
2967685Seric 				while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
2975003Seric 					a = a->q_next;
2985003Seric 
2997685Seric 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
3005003Seric 				{
3015003Seric 					if (a != NULL)
3025003Seric 						code = "250-";
3035003Seric 					else
3045003Seric 						code = "250";
3055003Seric 					if (vrfyqueue->q_fullname == NULL)
3065003Seric 						message(code, "<%s>", vrfyqueue->q_paddr);
3075003Seric 					else
3085003Seric 						message(code, "%s <%s>",
3095003Seric 						    vrfyqueue->q_fullname, vrfyqueue->q_paddr);
3105003Seric 				}
3115003Seric 				else if (a == NULL)
3125003Seric 					message("554", "Self destructive alias loop");
3135003Seric 				vrfyqueue = a;
3145003Seric 			}
3159339Seric 			if (InChild)
3169339Seric 				finis();
3174549Seric 			break;
3184549Seric 
3194549Seric 		  case CMDHELP:		/* help -- give user info */
3204577Seric 			if (*p == '\0')
3214577Seric 				p = "SMTP";
3224577Seric 			help(p);
3234549Seric 			break;
3244549Seric 
3254549Seric 		  case CMDNOOP:		/* noop -- do nothing */
3264549Seric 			message("200", "OK");
3274549Seric 			break;
3284549Seric 
3294549Seric 		  case CMDQUIT:		/* quit -- leave mail */
3304549Seric 			message("221", "%s closing connection", HostName);
3319339Seric 			if (InChild)
3329339Seric 				ExitStat = EX_QUIT;
3334549Seric 			finis();
3344549Seric 
3358544Seric 		  case CMDVERB:		/* set verbose mode */
3368544Seric 			Verbose = TRUE;
3378544Seric 			message("200", "Verbose mode");
3388544Seric 			break;
3398544Seric 
3409314Seric 		  case CMDONEX:		/* doing one transaction only */
3419378Seric 			OneXact = TRUE;
3429314Seric 			message("200", "Only one transaction");
3439314Seric 			break;
3449314Seric 
3455003Seric # ifdef DEBUG
3469339Seric 		  case CMDDBGQSHOW:	/* show queues */
3476907Seric 			printf("Send Queue=");
3486907Seric 			printaddr(CurEnv->e_sendqueue, TRUE);
3495003Seric 			break;
3507275Seric 
3517275Seric 		  case CMDDBGDEBUG:	/* set debug mode */
3527676Seric 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
3537676Seric 			tTflag(p);
3547676Seric 			message("200", "Debug set");
3557275Seric 			break;
3567275Seric 
3577282Seric 		  case CMDDBGKILL:	/* kill the parent */
3588544Seric 			if (!iswiz())
3598544Seric 				break;
3607282Seric 			if (kill(MotherPid, SIGTERM) >= 0)
3617282Seric 				message("200", "Mother is dead");
3627282Seric 			else
3637282Seric 				message("500", "Can't kill Mom");
3647282Seric 			break;
3658544Seric 
3669339Seric 		  case CMDDBGSHELL:	/* give us an interactive shell */
3679339Seric 			if (!iswiz())
3689339Seric 				break;
3699378Seric 			if (fileno(InChannel) != 0)
3709378Seric 			{
3719378Seric 				(void) close(0);
3729378Seric 				(void) dup(fileno(InChannel));
37310346Seric 				if (fileno(InChannel) != fileno(OutChannel))
37410346Seric 					(void) fclose(InChannel);
3759378Seric 				InChannel = stdin;
3769378Seric 			}
3779378Seric 			if (fileno(OutChannel) != 1)
3789378Seric 			{
3799378Seric 				(void) close(1);
3809378Seric 				(void) dup(fileno(OutChannel));
3819378Seric 				(void) fclose(OutChannel);
3829378Seric 				OutChannel = stdout;
3839378Seric 			}
38410346Seric 			(void) close(2);
38510346Seric 			(void) dup(1);
3869339Seric 			execl("/bin/csh", "sendmail", 0);
3879339Seric 			execl("/bin/sh", "sendmail", 0);
3889339Seric 			message("500", "Can't");
3899378Seric 			exit(EX_UNAVAILABLE);
3909339Seric 
3918544Seric 		  case CMDDBGWIZ:	/* become a wizard */
3928544Seric 			if (WizWord != NULL)
3938544Seric 			{
3948544Seric 				char seed[3];
3958544Seric 				extern char *crypt();
3968544Seric 
3978544Seric 				strncpy(seed, WizWord, 2);
3988544Seric 				if (strcmp(WizWord, crypt(p, seed)) != 0)
3998544Seric 				{
4008544Seric 					message("500", "You are no wizard!");
4018544Seric 					break;
4028544Seric 				}
4038544Seric 			}
4048544Seric 			IsWiz = TRUE;
4058544Seric 			message("200", "Please pass, oh mighty wizard");
4068544Seric 			break;
4075003Seric # endif DEBUG
4085003Seric 
4094549Seric 		  case CMDERROR:	/* unknown command */
4104549Seric 			message("500", "Command unrecognized");
4114549Seric 			break;
4124549Seric 
4134549Seric 		  default:
4144549Seric 			syserr("smtp: unknown code %d", c->cmdcode);
4154549Seric 			break;
4164549Seric 		}
4174549Seric 	}
4184549Seric }
4194549Seric /*
4204549Seric **  SKIPWORD -- skip a fixed word.
4214549Seric **
4224549Seric **	Parameters:
4234549Seric **		p -- place to start looking.
4244549Seric **		w -- word to skip.
4254549Seric **
4264549Seric **	Returns:
4274549Seric **		p following w.
4284549Seric **		NULL on error.
4294549Seric **
4304549Seric **	Side Effects:
4314549Seric **		clobbers the p data area.
4324549Seric */
4334549Seric 
4344549Seric static char *
4354549Seric skipword(p, w)
4364549Seric 	register char *p;
4374549Seric 	char *w;
4384549Seric {
4394549Seric 	register char *q;
4404549Seric 	extern bool sameword();
4414549Seric 
4424549Seric 	/* find beginning of word */
4434549Seric 	while (isspace(*p))
4444549Seric 		p++;
4454549Seric 	q = p;
4464549Seric 
4474549Seric 	/* find end of word */
4484549Seric 	while (*p != '\0' && *p != ':' && !isspace(*p))
4494549Seric 		p++;
4504549Seric 	while (isspace(*p))
4514549Seric 		*p++ = '\0';
4524549Seric 	if (*p != ':')
4534549Seric 	{
4544549Seric 	  syntax:
4554549Seric 		message("501", "Syntax error");
4564549Seric 		Errors++;
4574549Seric 		return (NULL);
4584549Seric 	}
4594549Seric 	*p++ = '\0';
4604549Seric 	while (isspace(*p))
4614549Seric 		p++;
4624549Seric 
4634549Seric 	/* see if the input word matches desired word */
4644549Seric 	if (!sameword(q, w))
4654549Seric 		goto syntax;
4664549Seric 
4674549Seric 	return (p);
4684549Seric }
4694577Seric /*
4704577Seric **  HELP -- implement the HELP command.
4714577Seric **
4724577Seric **	Parameters:
4734577Seric **		topic -- the topic we want help for.
4744577Seric **
4754577Seric **	Returns:
4764577Seric **		none.
4774577Seric **
4784577Seric **	Side Effects:
4794577Seric **		outputs the help file to message output.
4804577Seric */
4814577Seric 
4824577Seric help(topic)
4834577Seric 	char *topic;
4844577Seric {
4854577Seric 	register FILE *hf;
4864577Seric 	int len;
4874577Seric 	char buf[MAXLINE];
4884577Seric 	bool noinfo;
4894577Seric 
4908269Seric 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
4914577Seric 	{
4924577Seric 		/* no help */
4934577Seric 		message("502", "HELP not implemented");
4944577Seric 		return;
4954577Seric 	}
4964577Seric 
4974577Seric 	len = strlen(topic);
4984577Seric 	makelower(topic);
4994577Seric 	noinfo = TRUE;
5004577Seric 
5014577Seric 	while (fgets(buf, sizeof buf, hf) != NULL)
5024577Seric 	{
5034577Seric 		if (strncmp(buf, topic, len) == 0)
5044577Seric 		{
5054577Seric 			register char *p;
5064577Seric 
5074577Seric 			p = index(buf, '\t');
5084577Seric 			if (p == NULL)
5094577Seric 				p = buf;
5104577Seric 			else
5114577Seric 				p++;
5124577Seric 			fixcrlf(p, TRUE);
5134577Seric 			message("214-", p);
5144577Seric 			noinfo = FALSE;
5154577Seric 		}
5164577Seric 	}
5174577Seric 
5184577Seric 	if (noinfo)
5194577Seric 		message("504", "HELP topic unknown");
5204577Seric 	else
5214577Seric 		message("214", "End of HELP info");
5224628Seric 	(void) fclose(hf);
5234577Seric }
5248544Seric /*
5258544Seric **  ISWIZ -- tell us if we are a wizard
5268544Seric **
5278544Seric **	If not, print a nasty message.
5288544Seric **
5298544Seric **	Parameters:
5308544Seric **		none.
5318544Seric **
5328544Seric **	Returns:
5338544Seric **		TRUE if we are a wizard.
5348544Seric **		FALSE if we are not a wizard.
5358544Seric **
5368544Seric **	Side Effects:
5378544Seric **		Prints a 500 exit stat if we are not a wizard.
5388544Seric */
5395181Seric 
5408544Seric bool
5418544Seric iswiz()
5428544Seric {
5438544Seric 	if (!IsWiz)
5448544Seric 		message("500", "Mere mortals musn't mutter that mantra");
5458544Seric 	return (IsWiz);
5468544Seric }
5479339Seric /*
5489339Seric **  RUNINCHILD -- return twice -- once in the child, then in the parent again
5499339Seric **
5509339Seric **	Parameters:
5519339Seric **		label -- a string used in error messages
5529339Seric **
5539339Seric **	Returns:
5549339Seric **		zero in the child
5559339Seric **		one in the parent
5569339Seric **
5579339Seric **	Side Effects:
5589339Seric **		none.
5599339Seric */
5608544Seric 
5619339Seric runinchild(label)
5629339Seric 	char *label;
5639339Seric {
5649339Seric 	int childpid;
5659339Seric 
5669378Seric 	if (OneXact)
5679378Seric 		return (0);
5689378Seric 
5699339Seric 	childpid = dofork();
5709339Seric 	if (childpid < 0)
5719339Seric 	{
5729339Seric 		syserr("%s: cannot fork", label);
5739339Seric 		return (1);
5749339Seric 	}
5759339Seric 	if (childpid > 0)
5769339Seric 	{
5779339Seric 		auto int st;
5789339Seric 
5799378Seric 		/* parent -- wait for child to complete */
5809378Seric 		st = waitfor(childpid);
5819378Seric 		if (st == -1)
5829339Seric 			syserr("%s: lost child", label);
5839339Seric 
5849339Seric 		/* if we exited on a QUIT command, complete the process */
5859339Seric 		if (st == (EX_QUIT << 8))
5869339Seric 			finis();
5879339Seric 
5889339Seric 		return (1);
5899339Seric 	}
5909339Seric 	else
5919339Seric 	{
5929339Seric 		/* child */
5939339Seric 		InChild = TRUE;
5949545Seric 		clearenvelope(CurEnv);
5959339Seric 		return (0);
5969339Seric 	}
5979339Seric }
5989339Seric 
5995181Seric # endif SMTP
600