19339Seric # include <errno.h>
24549Seric # include "sendmail.h"
34549Seric 
45181Seric # ifndef SMTP
5*10708Seric SCCSID(@(#)srvrsmtp.c	3.45		02/03/83	(no SMTP));
65181Seric # else SMTP
74556Seric 
8*10708Seric SCCSID(@(#)srvrsmtp.c	3.45		02/03/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 */
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 	}
105*10708Seric 	expand("$e", inp, &inp[sizeof inp], CurEnv);
106*10708Seric 	message("220", inp);
1077762Seric 	(void) setjmp(TopFrame);
1087762Seric 	QuickAbort = FALSE;
1099390Seric 	HoldErrs = FALSE;
1104549Seric 	for (;;)
1114549Seric 	{
1127356Seric 		/* setup for the read */
1136907Seric 		CurEnv->e_to = NULL;
1144577Seric 		Errors = 0;
1157275Seric 		(void) fflush(stdout);
1167356Seric 
1177356Seric 		/* read the input line */
1187685Seric 		p = sfgets(inp, sizeof inp, InChannel);
1197356Seric 
1207685Seric 		/* handle errors */
1217356Seric 		if (p == NULL)
1227356Seric 		{
1234549Seric 			/* end of file, just die */
1244558Seric 			message("421", "%s Lost input channel", HostName);
1254549Seric 			finis();
1264549Seric 		}
1274549Seric 
1284549Seric 		/* clean up end of line */
1294558Seric 		fixcrlf(inp, TRUE);
1304549Seric 
1314713Seric 		/* echo command to transcript */
1329545Seric 		if (CurEnv->e_xfp != NULL)
1339545Seric 			fprintf(CurEnv->e_xfp, "<<< %s\n", inp);
1344713Seric 
1354549Seric 		/* break off command */
1364549Seric 		for (p = inp; isspace(*p); p++)
1374549Seric 			continue;
1384549Seric 		cmd = p;
1394549Seric 		while (*++p != '\0' && !isspace(*p))
1404549Seric 			continue;
1414549Seric 		if (*p != '\0')
1424549Seric 			*p++ = '\0';
1434549Seric 
1444549Seric 		/* decode command */
1454549Seric 		for (c = CmdTab; c->cmdname != NULL; c++)
1464549Seric 		{
1474549Seric 			if (sameword(c->cmdname, cmd))
1484549Seric 				break;
1494549Seric 		}
1504549Seric 
1514549Seric 		/* process command */
1524549Seric 		switch (c->cmdcode)
1534549Seric 		{
1544976Seric 		  case CMDHELO:		/* hello -- introduce yourself */
1559378Seric 			define('s', newstr(p), CurEnv);
1564997Seric 			message("250", "%s Hello %s, pleased to meet you",
1574997Seric 				HostName, p);
1584976Seric 			break;
1594976Seric 
1604549Seric 		  case CMDMAIL:		/* mail -- designate sender */
1619314Seric 			/* check for validity of this command */
1624558Seric 			if (hasmail)
1634558Seric 			{
1644558Seric 				message("503", "Sender already specified");
1654558Seric 				break;
1664558Seric 			}
1679339Seric 			if (InChild)
1689339Seric 			{
1699339Seric 				syserr("Nested MAIL command");
1709339Seric 				exit(0);
1719339Seric 			}
1729339Seric 
1739339Seric 			/* fork a subprocess to process this command */
1749339Seric 			if (runinchild("SMTP-MAIL") > 0)
1759339Seric 				break;
1769339Seric 			initsys();
1779339Seric 
1789339Seric 			/* child -- go do the processing */
1794549Seric 			p = skipword(p, "from");
1804549Seric 			if (p == NULL)
1814549Seric 				break;
1824549Seric 			setsender(p);
1834577Seric 			if (Errors == 0)
1844549Seric 			{
1854549Seric 				message("250", "Sender ok");
1864549Seric 				hasmail = TRUE;
1874549Seric 			}
1889339Seric 			else if (InChild)
1899339Seric 				finis();
1904549Seric 			break;
1914549Seric 
1924976Seric 		  case CMDRCPT:		/* rcpt -- designate recipient */
1934549Seric 			p = skipword(p, "to");
1944549Seric 			if (p == NULL)
1954549Seric 				break;
1969619Seric 			sendtolist(p, (ADDRESS *) NULL, &CurEnv->e_sendqueue);
1979390Seric 			CurEnv->e_flags &= ~EF_FATALERRS;
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).
22910197Seric 			**	Truncate our transcript -- the mail has gotten
23010197Seric 			**		to us successfully, and if we have
23110197Seric 			**		to mail this back, it will be easier
23210197Seric 			**		on the reader.
2338238Seric 			**	Then send to everyone.
2348238Seric 			**	Finally give a reply code.  If an error has
2358238Seric 			**		already been given, don't mail a
2368238Seric 			**		message back.
2379339Seric 			**	We goose error returns by clearing error bit.
2388238Seric 			*/
2398238Seric 
2404976Seric 			if (rcps != 1)
2419378Seric 			{
2429378Seric 				HoldErrs = TRUE;
2439378Seric 				ErrorMode == EM_MAIL;
2449378Seric 			}
2459339Seric 			CurEnv->e_flags &= ~EF_FATALERRS;
24610197Seric 			CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp);
2474976Seric 
2484976Seric 			/* send to all recipients */
2499283Seric 			sendall(CurEnv, SendMode);
2506907Seric 			CurEnv->e_to = NULL;
2514976Seric 
2528238Seric 			/* issue success if appropriate and reset */
2538238Seric 			if (Errors == 0 || HoldErrs)
2549283Seric 				message("250", "Ok");
2558238Seric 			else
2569339Seric 				CurEnv->e_flags &= ~EF_FATALERRS;
2579339Seric 
2589339Seric 			/* if in a child, pop back to our parent */
2599339Seric 			if (InChild)
2609339Seric 				finis();
2614549Seric 			break;
2624549Seric 
2634549Seric 		  case CMDRSET:		/* rset -- reset state */
2644549Seric 			message("250", "Reset state");
2659339Seric 			if (InChild)
2669339Seric 				finis();
2679339Seric 			break;
2684549Seric 
2694549Seric 		  case CMDVRFY:		/* vrfy -- verify address */
2709339Seric 			if (runinchild("SMTP-VRFY") > 0)
2719339Seric 				break;
2725003Seric 			vrfyqueue = NULL;
2737762Seric 			QuickAbort = TRUE;
2749619Seric 			sendtolist(p, (ADDRESS *) NULL, &vrfyqueue);
2757762Seric 			if (Errors != 0)
2769339Seric 			{
2779339Seric 				if (InChild)
2789339Seric 					finis();
2797762Seric 				break;
2809339Seric 			}
2815003Seric 			while (vrfyqueue != NULL)
2825003Seric 			{
2835003Seric 				register ADDRESS *a = vrfyqueue->q_next;
2845003Seric 				char *code;
2855003Seric 
2867685Seric 				while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
2875003Seric 					a = a->q_next;
2885003Seric 
2897685Seric 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
2905003Seric 				{
2915003Seric 					if (a != NULL)
2925003Seric 						code = "250-";
2935003Seric 					else
2945003Seric 						code = "250";
2955003Seric 					if (vrfyqueue->q_fullname == NULL)
2965003Seric 						message(code, "<%s>", vrfyqueue->q_paddr);
2975003Seric 					else
2985003Seric 						message(code, "%s <%s>",
2995003Seric 						    vrfyqueue->q_fullname, vrfyqueue->q_paddr);
3005003Seric 				}
3015003Seric 				else if (a == NULL)
3025003Seric 					message("554", "Self destructive alias loop");
3035003Seric 				vrfyqueue = a;
3045003Seric 			}
3059339Seric 			if (InChild)
3069339Seric 				finis();
3074549Seric 			break;
3084549Seric 
3094549Seric 		  case CMDHELP:		/* help -- give user info */
3104577Seric 			if (*p == '\0')
3114577Seric 				p = "SMTP";
3124577Seric 			help(p);
3134549Seric 			break;
3144549Seric 
3154549Seric 		  case CMDNOOP:		/* noop -- do nothing */
3164549Seric 			message("200", "OK");
3174549Seric 			break;
3184549Seric 
3194549Seric 		  case CMDQUIT:		/* quit -- leave mail */
3204549Seric 			message("221", "%s closing connection", HostName);
3219339Seric 			if (InChild)
3229339Seric 				ExitStat = EX_QUIT;
3234549Seric 			finis();
3244549Seric 
3258544Seric 		  case CMDVERB:		/* set verbose mode */
3268544Seric 			Verbose = TRUE;
3278544Seric 			message("200", "Verbose mode");
3288544Seric 			break;
3298544Seric 
3309314Seric 		  case CMDONEX:		/* doing one transaction only */
3319378Seric 			OneXact = TRUE;
3329314Seric 			message("200", "Only one transaction");
3339314Seric 			break;
3349314Seric 
3355003Seric # ifdef DEBUG
3369339Seric 		  case CMDDBGQSHOW:	/* show queues */
3376907Seric 			printf("Send Queue=");
3386907Seric 			printaddr(CurEnv->e_sendqueue, TRUE);
3395003Seric 			break;
3407275Seric 
3417275Seric 		  case CMDDBGDEBUG:	/* set debug mode */
3427676Seric 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
3437676Seric 			tTflag(p);
3447676Seric 			message("200", "Debug set");
3457275Seric 			break;
3467275Seric 
3477282Seric 		  case CMDDBGKILL:	/* kill the parent */
3488544Seric 			if (!iswiz())
3498544Seric 				break;
3507282Seric 			if (kill(MotherPid, SIGTERM) >= 0)
3517282Seric 				message("200", "Mother is dead");
3527282Seric 			else
3537282Seric 				message("500", "Can't kill Mom");
3547282Seric 			break;
3558544Seric 
3569339Seric 		  case CMDDBGSHELL:	/* give us an interactive shell */
3579339Seric 			if (!iswiz())
3589339Seric 				break;
3599378Seric 			if (fileno(InChannel) != 0)
3609378Seric 			{
3619378Seric 				(void) close(0);
3629378Seric 				(void) dup(fileno(InChannel));
36310346Seric 				if (fileno(InChannel) != fileno(OutChannel))
36410346Seric 					(void) fclose(InChannel);
3659378Seric 				InChannel = stdin;
3669378Seric 			}
3679378Seric 			if (fileno(OutChannel) != 1)
3689378Seric 			{
3699378Seric 				(void) close(1);
3709378Seric 				(void) dup(fileno(OutChannel));
3719378Seric 				(void) fclose(OutChannel);
3729378Seric 				OutChannel = stdout;
3739378Seric 			}
37410346Seric 			(void) close(2);
37510346Seric 			(void) dup(1);
3769339Seric 			execl("/bin/csh", "sendmail", 0);
3779339Seric 			execl("/bin/sh", "sendmail", 0);
3789339Seric 			message("500", "Can't");
3799378Seric 			exit(EX_UNAVAILABLE);
3809339Seric 
3818544Seric 		  case CMDDBGWIZ:	/* become a wizard */
3828544Seric 			if (WizWord != NULL)
3838544Seric 			{
3848544Seric 				char seed[3];
3858544Seric 				extern char *crypt();
3868544Seric 
3878544Seric 				strncpy(seed, WizWord, 2);
3888544Seric 				if (strcmp(WizWord, crypt(p, seed)) != 0)
3898544Seric 				{
3908544Seric 					message("500", "You are no wizard!");
3918544Seric 					break;
3928544Seric 				}
3938544Seric 			}
3948544Seric 			IsWiz = TRUE;
3958544Seric 			message("200", "Please pass, oh mighty wizard");
3968544Seric 			break;
3975003Seric # endif DEBUG
3985003Seric 
3994549Seric 		  case CMDERROR:	/* unknown command */
4004549Seric 			message("500", "Command unrecognized");
4014549Seric 			break;
4024549Seric 
4034549Seric 		  default:
4044549Seric 			syserr("smtp: unknown code %d", c->cmdcode);
4054549Seric 			break;
4064549Seric 		}
4074549Seric 	}
4084549Seric }
4094549Seric /*
4104549Seric **  SKIPWORD -- skip a fixed word.
4114549Seric **
4124549Seric **	Parameters:
4134549Seric **		p -- place to start looking.
4144549Seric **		w -- word to skip.
4154549Seric **
4164549Seric **	Returns:
4174549Seric **		p following w.
4184549Seric **		NULL on error.
4194549Seric **
4204549Seric **	Side Effects:
4214549Seric **		clobbers the p data area.
4224549Seric */
4234549Seric 
4244549Seric static char *
4254549Seric skipword(p, w)
4264549Seric 	register char *p;
4274549Seric 	char *w;
4284549Seric {
4294549Seric 	register char *q;
4304549Seric 	extern bool sameword();
4314549Seric 
4324549Seric 	/* find beginning of word */
4334549Seric 	while (isspace(*p))
4344549Seric 		p++;
4354549Seric 	q = p;
4364549Seric 
4374549Seric 	/* find end of word */
4384549Seric 	while (*p != '\0' && *p != ':' && !isspace(*p))
4394549Seric 		p++;
4404549Seric 	while (isspace(*p))
4414549Seric 		*p++ = '\0';
4424549Seric 	if (*p != ':')
4434549Seric 	{
4444549Seric 	  syntax:
4454549Seric 		message("501", "Syntax error");
4464549Seric 		Errors++;
4474549Seric 		return (NULL);
4484549Seric 	}
4494549Seric 	*p++ = '\0';
4504549Seric 	while (isspace(*p))
4514549Seric 		p++;
4524549Seric 
4534549Seric 	/* see if the input word matches desired word */
4544549Seric 	if (!sameword(q, w))
4554549Seric 		goto syntax;
4564549Seric 
4574549Seric 	return (p);
4584549Seric }
4594577Seric /*
4604577Seric **  HELP -- implement the HELP command.
4614577Seric **
4624577Seric **	Parameters:
4634577Seric **		topic -- the topic we want help for.
4644577Seric **
4654577Seric **	Returns:
4664577Seric **		none.
4674577Seric **
4684577Seric **	Side Effects:
4694577Seric **		outputs the help file to message output.
4704577Seric */
4714577Seric 
4724577Seric help(topic)
4734577Seric 	char *topic;
4744577Seric {
4754577Seric 	register FILE *hf;
4764577Seric 	int len;
4774577Seric 	char buf[MAXLINE];
4784577Seric 	bool noinfo;
4794577Seric 
4808269Seric 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
4814577Seric 	{
4824577Seric 		/* no help */
4834577Seric 		message("502", "HELP not implemented");
4844577Seric 		return;
4854577Seric 	}
4864577Seric 
4874577Seric 	len = strlen(topic);
4884577Seric 	makelower(topic);
4894577Seric 	noinfo = TRUE;
4904577Seric 
4914577Seric 	while (fgets(buf, sizeof buf, hf) != NULL)
4924577Seric 	{
4934577Seric 		if (strncmp(buf, topic, len) == 0)
4944577Seric 		{
4954577Seric 			register char *p;
4964577Seric 
4974577Seric 			p = index(buf, '\t');
4984577Seric 			if (p == NULL)
4994577Seric 				p = buf;
5004577Seric 			else
5014577Seric 				p++;
5024577Seric 			fixcrlf(p, TRUE);
5034577Seric 			message("214-", p);
5044577Seric 			noinfo = FALSE;
5054577Seric 		}
5064577Seric 	}
5074577Seric 
5084577Seric 	if (noinfo)
5094577Seric 		message("504", "HELP topic unknown");
5104577Seric 	else
5114577Seric 		message("214", "End of HELP info");
5124628Seric 	(void) fclose(hf);
5134577Seric }
5148544Seric /*
5158544Seric **  ISWIZ -- tell us if we are a wizard
5168544Seric **
5178544Seric **	If not, print a nasty message.
5188544Seric **
5198544Seric **	Parameters:
5208544Seric **		none.
5218544Seric **
5228544Seric **	Returns:
5238544Seric **		TRUE if we are a wizard.
5248544Seric **		FALSE if we are not a wizard.
5258544Seric **
5268544Seric **	Side Effects:
5278544Seric **		Prints a 500 exit stat if we are not a wizard.
5288544Seric */
5295181Seric 
5308544Seric bool
5318544Seric iswiz()
5328544Seric {
5338544Seric 	if (!IsWiz)
5348544Seric 		message("500", "Mere mortals musn't mutter that mantra");
5358544Seric 	return (IsWiz);
5368544Seric }
5379339Seric /*
5389339Seric **  RUNINCHILD -- return twice -- once in the child, then in the parent again
5399339Seric **
5409339Seric **	Parameters:
5419339Seric **		label -- a string used in error messages
5429339Seric **
5439339Seric **	Returns:
5449339Seric **		zero in the child
5459339Seric **		one in the parent
5469339Seric **
5479339Seric **	Side Effects:
5489339Seric **		none.
5499339Seric */
5508544Seric 
5519339Seric runinchild(label)
5529339Seric 	char *label;
5539339Seric {
5549339Seric 	int childpid;
5559339Seric 
5569378Seric 	if (OneXact)
5579378Seric 		return (0);
5589378Seric 
5599339Seric 	childpid = dofork();
5609339Seric 	if (childpid < 0)
5619339Seric 	{
5629339Seric 		syserr("%s: cannot fork", label);
5639339Seric 		return (1);
5649339Seric 	}
5659339Seric 	if (childpid > 0)
5669339Seric 	{
5679339Seric 		auto int st;
5689339Seric 
5699378Seric 		/* parent -- wait for child to complete */
5709378Seric 		st = waitfor(childpid);
5719378Seric 		if (st == -1)
5729339Seric 			syserr("%s: lost child", label);
5739339Seric 
5749339Seric 		/* if we exited on a QUIT command, complete the process */
5759339Seric 		if (st == (EX_QUIT << 8))
5769339Seric 			finis();
5779339Seric 
5789339Seric 		return (1);
5799339Seric 	}
5809339Seric 	else
5819339Seric 	{
5829339Seric 		/* child */
5839339Seric 		InChild = TRUE;
5849545Seric 		clearenvelope(CurEnv);
5859339Seric 		return (0);
5869339Seric 	}
5879339Seric }
5889339Seric 
5895181Seric # endif SMTP
590