1*9339Seric # include <errno.h>
24549Seric # include "sendmail.h"
34549Seric 
45181Seric # ifndef SMTP
5*9339Seric SCCSID(@(#)srvrsmtp.c	3.37		11/24/82	(no SMTP));
65181Seric # else SMTP
74556Seric 
8*9339Seric SCCSID(@(#)srvrsmtp.c	3.37		11/24/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 */
35*9339Seric # define CMDRSET	4	/* rset -- reset state */
36*9339Seric # define CMDVRFY	5	/* vrfy -- verify address */
37*9339Seric # define CMDHELP	6	/* help -- give usage info */
38*9339Seric # define CMDNOOP	7	/* noop -- do nothing */
39*9339Seric # define CMDQUIT	8	/* quit -- close connection and die */
40*9339Seric # define CMDHELO	9	/* helo -- be polite */
41*9339Seric # define CMDDBGQSHOW	10	/* showq -- show send queue (DEBUG) */
42*9339Seric # define CMDDBGDEBUG	11	/* debug -- set debug mode */
43*9339Seric # define CMDVERB	12	/* verb -- go into verbose mode */
44*9339Seric # define CMDDBGKILL	13	/* kill -- kill sendmail */
45*9339Seric # define CMDDBGWIZ	14	/* wiz -- become a wizard */
46*9339Seric # define CMDONEX	15	/* onex -- sending one transaction only */
47*9339Seric # 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
64*9339Seric 	"showq",	CMDDBGQSHOW,
658544Seric 	"debug",	CMDDBGDEBUG,
668544Seric 	"kill",		CMDDBGKILL,
678544Seric 	"wiz",		CMDDBGWIZ,
68*9339Seric 	"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
77*9339Seric bool	InChild = FALSE;		/* true if running in a subprocess */
78*9339Seric #define EX_QUIT		22		/* special code for QUIT command */
798544Seric 
804549Seric smtp()
814549Seric {
824549Seric 	register char *p;
838544Seric 	register struct cmd *c;
844549Seric 	char *cmd;
854549Seric 	extern char *skipword();
864549Seric 	extern bool sameword();
874549Seric 	bool hasmail;			/* mail command received */
884713Seric 	int rcps;			/* number of recipients */
895003Seric 	auto ADDRESS *vrfyqueue;
909314Seric 	bool onexact = FALSE;		/* one transaction this connection */
919314Seric 	bool firsttime = TRUE;		/* this is the first transaction */
928544Seric 	char inp[MAXLINE];
937124Seric 	extern char Version[];
947356Seric 	extern tick();
958544Seric 	extern bool iswiz();
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,
1067797Seric 			Version, arpadate(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 */
131*9339Seric 		if (Xscript != NULL)
132*9339Seric 			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 */
1545187Seric 			define('s', newstr(p));
1554997Seric 			message("250", "%s Hello %s, pleased to meet you",
1564997Seric 				HostName, p);
1574976Seric 			break;
1584976Seric 
1594549Seric 		  case CMDMAIL:		/* mail -- designate sender */
1609314Seric 			firsttime = FALSE;
1619314Seric 
1629314Seric 			/* check for validity of this command */
1634558Seric 			if (hasmail)
1644558Seric 			{
1654558Seric 				message("503", "Sender already specified");
1664558Seric 				break;
1674558Seric 			}
168*9339Seric 			if (InChild)
169*9339Seric 			{
170*9339Seric 				syserr("Nested MAIL command");
171*9339Seric 				exit(0);
172*9339Seric 			}
173*9339Seric 
174*9339Seric 			/* fork a subprocess to process this command */
175*9339Seric 			if (runinchild("SMTP-MAIL") > 0)
176*9339Seric 				break;
177*9339Seric 			initsys();
178*9339Seric 
179*9339Seric 			/* child -- go do the processing */
1804549Seric 			p = skipword(p, "from");
1814549Seric 			if (p == NULL)
1824549Seric 				break;
1834549Seric 			setsender(p);
1844577Seric 			if (Errors == 0)
1854549Seric 			{
1864549Seric 				message("250", "Sender ok");
1874549Seric 				hasmail = TRUE;
1884549Seric 			}
189*9339Seric 			else if (InChild)
190*9339Seric 				finis();
1914549Seric 			break;
1924549Seric 
1934976Seric 		  case CMDRCPT:		/* rcpt -- designate recipient */
1944549Seric 			p = skipword(p, "to");
1954549Seric 			if (p == NULL)
1964549Seric 				break;
1978081Seric 			sendto(p, (ADDRESS *) NULL, &CurEnv->e_sendqueue);
1984577Seric 			if (Errors == 0)
1994549Seric 			{
2006057Seric 				message("250", "%s... Recipient ok", p);
2014713Seric 				rcps++;
2024549Seric 			}
2034549Seric 			break;
2044549Seric 
2054549Seric 		  case CMDDATA:		/* data -- text of mail */
2064976Seric 			if (!hasmail)
2074549Seric 			{
2084976Seric 				message("503", "Need MAIL command");
2094976Seric 				break;
2104549Seric 			}
2114713Seric 			else if (rcps <= 0)
2124549Seric 			{
2134976Seric 				message("503", "Need RCPT (recipient)");
2144976Seric 				break;
2154549Seric 			}
2164976Seric 
2174976Seric 			/* collect the text of the message */
2184976Seric 			collect(TRUE);
2194976Seric 			if (Errors != 0)
2204976Seric 				break;
2214976Seric 
2228238Seric 			/*
2238238Seric 			**  Arrange to send to everyone.
2248238Seric 			**	If sending to multiple people, mail back
2258238Seric 			**		errors rather than reporting directly.
2268238Seric 			**	In any case, don't mail back errors for
2278238Seric 			**		anything that has happened up to
2288238Seric 			**		now (the other end will do this).
2298238Seric 			**	Then send to everyone.
2308238Seric 			**	Finally give a reply code.  If an error has
2318238Seric 			**		already been given, don't mail a
2328238Seric 			**		message back.
233*9339Seric 			**	We goose error returns by clearing error bit.
2348238Seric 			*/
2358238Seric 
2364976Seric 			if (rcps != 1)
2374976Seric 				HoldErrs = MailBack = TRUE;
238*9339Seric 			CurEnv->e_flags &= ~EF_FATALERRS;
2394976Seric 
2404976Seric 			/* send to all recipients */
2419283Seric 			sendall(CurEnv, SendMode);
2426907Seric 			CurEnv->e_to = NULL;
2434976Seric 
2448238Seric 			/* issue success if appropriate and reset */
2458238Seric 			if (Errors == 0 || HoldErrs)
2468238Seric 			{
2478238Seric 				HoldErrs = FALSE;
2489283Seric 				message("250", "Ok");
2498238Seric 			}
2508238Seric 			else
251*9339Seric 				CurEnv->e_flags &= ~EF_FATALERRS;
252*9339Seric 
253*9339Seric 			/* if in a child, pop back to our parent */
254*9339Seric 			if (InChild)
255*9339Seric 				finis();
2564549Seric 			break;
2574549Seric 
2584549Seric 		  case CMDRSET:		/* rset -- reset state */
2594549Seric 			message("250", "Reset state");
260*9339Seric 			if (InChild)
261*9339Seric 				finis();
262*9339Seric 			break;
2634549Seric 
2644549Seric 		  case CMDVRFY:		/* vrfy -- verify address */
265*9339Seric 			if (runinchild("SMTP-VRFY") > 0)
266*9339Seric 				break;
2675003Seric 			vrfyqueue = NULL;
2687762Seric 			QuickAbort = TRUE;
2698081Seric 			sendto(p, (ADDRESS *) NULL, &vrfyqueue);
2707762Seric 			if (Errors != 0)
271*9339Seric 			{
272*9339Seric 				if (InChild)
273*9339Seric 					finis();
2747762Seric 				break;
275*9339Seric 			}
2765003Seric 			while (vrfyqueue != NULL)
2775003Seric 			{
2785003Seric 				register ADDRESS *a = vrfyqueue->q_next;
2795003Seric 				char *code;
2805003Seric 
2817685Seric 				while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
2825003Seric 					a = a->q_next;
2835003Seric 
2847685Seric 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
2855003Seric 				{
2865003Seric 					if (a != NULL)
2875003Seric 						code = "250-";
2885003Seric 					else
2895003Seric 						code = "250";
2905003Seric 					if (vrfyqueue->q_fullname == NULL)
2915003Seric 						message(code, "<%s>", vrfyqueue->q_paddr);
2925003Seric 					else
2935003Seric 						message(code, "%s <%s>",
2945003Seric 						    vrfyqueue->q_fullname, vrfyqueue->q_paddr);
2955003Seric 				}
2965003Seric 				else if (a == NULL)
2975003Seric 					message("554", "Self destructive alias loop");
2985003Seric 				vrfyqueue = a;
2995003Seric 			}
300*9339Seric 			if (InChild)
301*9339Seric 				finis();
3024549Seric 			break;
3034549Seric 
3044549Seric 		  case CMDHELP:		/* help -- give user info */
3054577Seric 			if (*p == '\0')
3064577Seric 				p = "SMTP";
3074577Seric 			help(p);
3084549Seric 			break;
3094549Seric 
3104549Seric 		  case CMDNOOP:		/* noop -- do nothing */
3114549Seric 			message("200", "OK");
3124549Seric 			break;
3134549Seric 
3144549Seric 		  case CMDQUIT:		/* quit -- leave mail */
3154549Seric 			message("221", "%s closing connection", HostName);
316*9339Seric 			if (InChild)
317*9339Seric 				ExitStat = EX_QUIT;
3184549Seric 			finis();
3194549Seric 
3208544Seric 		  case CMDVERB:		/* set verbose mode */
3218544Seric 			Verbose = TRUE;
3228544Seric 			message("200", "Verbose mode");
3238544Seric 			break;
3248544Seric 
3259314Seric 		  case CMDONEX:		/* doing one transaction only */
3269314Seric 			onexact = TRUE;
3279314Seric 			message("200", "Only one transaction");
3289314Seric 			break;
3299314Seric 
3305003Seric # ifdef DEBUG
331*9339Seric 		  case CMDDBGQSHOW:	/* show queues */
3326907Seric 			printf("Send Queue=");
3336907Seric 			printaddr(CurEnv->e_sendqueue, TRUE);
3345003Seric 			break;
3357275Seric 
3367275Seric 		  case CMDDBGDEBUG:	/* set debug mode */
3377676Seric 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
3387676Seric 			tTflag(p);
3397676Seric 			message("200", "Debug set");
3407275Seric 			break;
3417275Seric 
3427282Seric 		  case CMDDBGKILL:	/* kill the parent */
3438544Seric 			if (!iswiz())
3448544Seric 				break;
3457282Seric 			if (kill(MotherPid, SIGTERM) >= 0)
3467282Seric 				message("200", "Mother is dead");
3477282Seric 			else
3487282Seric 				message("500", "Can't kill Mom");
3497282Seric 			break;
3508544Seric 
351*9339Seric 		  case CMDDBGSHELL:	/* give us an interactive shell */
352*9339Seric 			if (!iswiz())
353*9339Seric 				break;
354*9339Seric 			execl("/bin/csh", "sendmail", 0);
355*9339Seric 			execl("/bin/sh", "sendmail", 0);
356*9339Seric 			message("500", "Can't");
357*9339Seric 			break;
358*9339Seric 
3598544Seric 		  case CMDDBGWIZ:	/* become a wizard */
3608544Seric 			if (WizWord != NULL)
3618544Seric 			{
3628544Seric 				char seed[3];
3638544Seric 				extern char *crypt();
3648544Seric 
3658544Seric 				strncpy(seed, WizWord, 2);
3668544Seric 				if (strcmp(WizWord, crypt(p, seed)) != 0)
3678544Seric 				{
3688544Seric 					message("500", "You are no wizard!");
3698544Seric 					break;
3708544Seric 				}
3718544Seric 			}
3728544Seric 			IsWiz = TRUE;
3738544Seric 			message("200", "Please pass, oh mighty wizard");
3748544Seric 			break;
3755003Seric # endif DEBUG
3765003Seric 
3774549Seric 		  case CMDERROR:	/* unknown command */
3784549Seric 			message("500", "Command unrecognized");
3794549Seric 			break;
3804549Seric 
3814549Seric 		  default:
3824549Seric 			syserr("smtp: unknown code %d", c->cmdcode);
3834549Seric 			break;
3844549Seric 		}
3854549Seric 	}
3864549Seric }
3874549Seric /*
3884549Seric **  SKIPWORD -- skip a fixed word.
3894549Seric **
3904549Seric **	Parameters:
3914549Seric **		p -- place to start looking.
3924549Seric **		w -- word to skip.
3934549Seric **
3944549Seric **	Returns:
3954549Seric **		p following w.
3964549Seric **		NULL on error.
3974549Seric **
3984549Seric **	Side Effects:
3994549Seric **		clobbers the p data area.
4004549Seric */
4014549Seric 
4024549Seric static char *
4034549Seric skipword(p, w)
4044549Seric 	register char *p;
4054549Seric 	char *w;
4064549Seric {
4074549Seric 	register char *q;
4084549Seric 	extern bool sameword();
4094549Seric 
4104549Seric 	/* find beginning of word */
4114549Seric 	while (isspace(*p))
4124549Seric 		p++;
4134549Seric 	q = p;
4144549Seric 
4154549Seric 	/* find end of word */
4164549Seric 	while (*p != '\0' && *p != ':' && !isspace(*p))
4174549Seric 		p++;
4184549Seric 	while (isspace(*p))
4194549Seric 		*p++ = '\0';
4204549Seric 	if (*p != ':')
4214549Seric 	{
4224549Seric 	  syntax:
4234549Seric 		message("501", "Syntax error");
4244549Seric 		Errors++;
4254549Seric 		return (NULL);
4264549Seric 	}
4274549Seric 	*p++ = '\0';
4284549Seric 	while (isspace(*p))
4294549Seric 		p++;
4304549Seric 
4314549Seric 	/* see if the input word matches desired word */
4324549Seric 	if (!sameword(q, w))
4334549Seric 		goto syntax;
4344549Seric 
4354549Seric 	return (p);
4364549Seric }
4374577Seric /*
4384577Seric **  HELP -- implement the HELP command.
4394577Seric **
4404577Seric **	Parameters:
4414577Seric **		topic -- the topic we want help for.
4424577Seric **
4434577Seric **	Returns:
4444577Seric **		none.
4454577Seric **
4464577Seric **	Side Effects:
4474577Seric **		outputs the help file to message output.
4484577Seric */
4494577Seric 
4504577Seric help(topic)
4514577Seric 	char *topic;
4524577Seric {
4534577Seric 	register FILE *hf;
4544577Seric 	int len;
4554577Seric 	char buf[MAXLINE];
4564577Seric 	bool noinfo;
4574577Seric 
4588269Seric 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
4594577Seric 	{
4604577Seric 		/* no help */
4614577Seric 		message("502", "HELP not implemented");
4624577Seric 		return;
4634577Seric 	}
4644577Seric 
4654577Seric 	len = strlen(topic);
4664577Seric 	makelower(topic);
4674577Seric 	noinfo = TRUE;
4684577Seric 
4694577Seric 	while (fgets(buf, sizeof buf, hf) != NULL)
4704577Seric 	{
4714577Seric 		if (strncmp(buf, topic, len) == 0)
4724577Seric 		{
4734577Seric 			register char *p;
4744577Seric 
4754577Seric 			p = index(buf, '\t');
4764577Seric 			if (p == NULL)
4774577Seric 				p = buf;
4784577Seric 			else
4794577Seric 				p++;
4804577Seric 			fixcrlf(p, TRUE);
4814577Seric 			message("214-", p);
4824577Seric 			noinfo = FALSE;
4834577Seric 		}
4844577Seric 	}
4854577Seric 
4864577Seric 	if (noinfo)
4874577Seric 		message("504", "HELP topic unknown");
4884577Seric 	else
4894577Seric 		message("214", "End of HELP info");
4904628Seric 	(void) fclose(hf);
4914577Seric }
4928544Seric /*
4938544Seric **  ISWIZ -- tell us if we are a wizard
4948544Seric **
4958544Seric **	If not, print a nasty message.
4968544Seric **
4978544Seric **	Parameters:
4988544Seric **		none.
4998544Seric **
5008544Seric **	Returns:
5018544Seric **		TRUE if we are a wizard.
5028544Seric **		FALSE if we are not a wizard.
5038544Seric **
5048544Seric **	Side Effects:
5058544Seric **		Prints a 500 exit stat if we are not a wizard.
5068544Seric */
5075181Seric 
5088544Seric bool
5098544Seric iswiz()
5108544Seric {
5118544Seric 	if (!IsWiz)
5128544Seric 		message("500", "Mere mortals musn't mutter that mantra");
5138544Seric 	return (IsWiz);
5148544Seric }
515*9339Seric /*
516*9339Seric **  RUNINCHILD -- return twice -- once in the child, then in the parent again
517*9339Seric **
518*9339Seric **	Parameters:
519*9339Seric **		label -- a string used in error messages
520*9339Seric **
521*9339Seric **	Returns:
522*9339Seric **		zero in the child
523*9339Seric **		one in the parent
524*9339Seric **
525*9339Seric **	Side Effects:
526*9339Seric **		none.
527*9339Seric */
5288544Seric 
529*9339Seric runinchild(label)
530*9339Seric 	char *label;
531*9339Seric {
532*9339Seric 	int childpid;
533*9339Seric 
534*9339Seric 	childpid = dofork();
535*9339Seric 	if (childpid < 0)
536*9339Seric 	{
537*9339Seric 		syserr("%s: cannot fork", label);
538*9339Seric 		return (1);
539*9339Seric 	}
540*9339Seric 	if (childpid > 0)
541*9339Seric 	{
542*9339Seric 		/* parent -- wait for child to complete */
543*9339Seric 		auto int st;
544*9339Seric 		int i;
545*9339Seric 
546*9339Seric 		while ((i = wait(&st)) != childpid)
547*9339Seric 		{
548*9339Seric 			if (i < 0 && errno != EINTR)
549*9339Seric 				break;
550*9339Seric 		}
551*9339Seric 		if (i < 0)
552*9339Seric 			syserr("%s: lost child", label);
553*9339Seric 
554*9339Seric 		/* if we exited on a QUIT command, complete the process */
555*9339Seric 		if (st == (EX_QUIT << 8))
556*9339Seric 			finis();
557*9339Seric 
558*9339Seric 		return (1);
559*9339Seric 	}
560*9339Seric 	else
561*9339Seric 	{
562*9339Seric 		/* child */
563*9339Seric 		InChild = TRUE;
564*9339Seric 		return (0);
565*9339Seric 	}
566*9339Seric }
567*9339Seric 
5685181Seric # endif SMTP
569