1 # include <errno.h>
2 # include "sendmail.h"
3 # include <signal.h>
4 
5 # ifndef SMTP
6 SCCSID(@(#)srvrsmtp.c	4.10		08/18/84	(no SMTP));
7 # else SMTP
8 
9 SCCSID(@(#)srvrsmtp.c	4.10		08/18/84);
10 
11 /*
12 **  SMTP -- run the SMTP protocol.
13 **
14 **	Parameters:
15 **		none.
16 **
17 **	Returns:
18 **		never.
19 **
20 **	Side Effects:
21 **		Reads commands from the input channel and processes
22 **			them.
23 */
24 
25 struct cmd
26 {
27 	char	*cmdname;	/* command name */
28 	int	cmdcode;	/* internal code, see below */
29 };
30 
31 /* values for cmdcode */
32 # define CMDERROR	0	/* bad command */
33 # define CMDMAIL	1	/* mail -- designate sender */
34 # define CMDRCPT	2	/* rcpt -- designate recipient */
35 # define CMDDATA	3	/* data -- send message text */
36 # define CMDRSET	4	/* rset -- reset state */
37 # define CMDVRFY	5	/* vrfy -- verify address */
38 # define CMDHELP	6	/* help -- give usage info */
39 # define CMDNOOP	7	/* noop -- do nothing */
40 # define CMDQUIT	8	/* quit -- close connection and die */
41 # define CMDHELO	9	/* helo -- be polite */
42 # define CMDDBGQSHOW	10	/* showq -- show send queue (DEBUG) */
43 # define CMDDBGDEBUG	11	/* debug -- set debug mode */
44 # define CMDVERB	12	/* verb -- go into verbose mode */
45 # define CMDDBGKILL	13	/* kill -- kill sendmail */
46 # define CMDDBGWIZ	14	/* wiz -- become a wizard */
47 # define CMDONEX	15	/* onex -- sending one transaction only */
48 # define CMDDBGSHELL	16	/* shell -- give us a shell */
49 
50 static struct cmd	CmdTab[] =
51 {
52 	"mail",		CMDMAIL,
53 	"rcpt",		CMDRCPT,
54 	"data",		CMDDATA,
55 	"rset",		CMDRSET,
56 	"vrfy",		CMDVRFY,
57 	"expn",		CMDVRFY,
58 	"help",		CMDHELP,
59 	"noop",		CMDNOOP,
60 	"quit",		CMDQUIT,
61 	"helo",		CMDHELO,
62 	"verb",		CMDVERB,
63 	"onex",		CMDONEX,
64 # ifdef DEBUG
65 	"showq",	CMDDBGQSHOW,
66 	"debug",	CMDDBGDEBUG,
67 	"kill",		CMDDBGKILL,
68 	"wiz",		CMDDBGWIZ,
69 	"shell",	CMDDBGSHELL,
70 # endif DEBUG
71 	NULL,		CMDERROR,
72 };
73 
74 bool	IsWiz = FALSE;			/* set if we are a wizard */
75 char	*WizWord;			/* the wizard word to compare against */
76 bool	InChild = FALSE;		/* true if running in a subprocess */
77 bool	OneXact = FALSE;		/* one xaction only this run */
78 char	*RealHostName = NULL;		/* verified hostname, set in daemon.c */
79 
80 #define EX_QUIT		22		/* special code for QUIT command */
81 
82 smtp()
83 {
84 	register char *p;
85 	register struct cmd *c;
86 	char *cmd;
87 	extern char *skipword();
88 	extern bool sameword();
89 	bool hasmail;			/* mail command received */
90 	int rcps;			/* number of recipients */
91 	auto ADDRESS *vrfyqueue;
92 	ADDRESS *a;
93 	char inp[MAXLINE];
94 	extern char Version[];
95 	extern tick();
96 	extern bool iswiz();
97 	extern char *arpadate();
98 	extern char *macvalue();
99 	extern ADDRESS *recipient();
100 
101 	hasmail = FALSE;
102 	rcps = 0;
103 	if (OutChannel != stdout)
104 	{
105 		/* arrange for debugging output to go to remote host */
106 		(void) close(1);
107 		(void) dup(fileno(OutChannel));
108 	}
109 	settime();
110 	expand("\001e", inp, &inp[sizeof inp], CurEnv);
111 	message("220", inp);
112 	for (;;)
113 	{
114 		/* arrange for backout */
115 		if (setjmp(TopFrame) > 0 && InChild)
116 			finis();
117 		QuickAbort = FALSE;
118 		HoldErrs = FALSE;
119 
120 		/* setup for the read */
121 		CurEnv->e_to = NULL;
122 		Errors = 0;
123 		(void) fflush(stdout);
124 
125 		/* read the input line */
126 		p = sfgets(inp, sizeof inp, InChannel);
127 
128 		/* handle errors */
129 		if (p == NULL)
130 		{
131 			/* end of file, just die */
132 			message("421", "%s Lost input channel", HostName);
133 			finis();
134 		}
135 
136 		/* clean up end of line */
137 		fixcrlf(inp, TRUE);
138 
139 		/* echo command to transcript */
140 		if (CurEnv->e_xfp != NULL)
141 			fprintf(CurEnv->e_xfp, "<<< %s\n", inp);
142 
143 		/* break off command */
144 		for (p = inp; isspace(*p); p++)
145 			continue;
146 		cmd = p;
147 		while (*++p != '\0' && !isspace(*p))
148 			continue;
149 		if (*p != '\0')
150 			*p++ = '\0';
151 
152 		/* decode command */
153 		for (c = CmdTab; c->cmdname != NULL; c++)
154 		{
155 			if (sameword(c->cmdname, cmd))
156 				break;
157 		}
158 
159 		/* process command */
160 		switch (c->cmdcode)
161 		{
162 		  case CMDHELO:		/* hello -- introduce yourself */
163 			if (sameword(p, HostName))
164 			{
165 				/* connected to an echo server */
166 				message("553", "%s I refuse to talk to myself",
167 					HostName);
168 				break;
169 			}
170 			if (RealHostName != NULL && !sameword(p, RealHostName))
171 			{
172 				char buf[MAXNAME];
173 
174 				(void) sprintf(buf, "%s (%s)", p, RealHostName);
175 				define('s', newstr(buf), CurEnv);
176 			}
177 			else
178 				define('s', newstr(p), CurEnv);
179 			message("250", "%s Hello %s, pleased to meet you",
180 				HostName, p);
181 			break;
182 
183 		  case CMDMAIL:		/* mail -- designate sender */
184 			/* force a sending host even if no HELO given */
185 			if (RealHostName != NULL && macvalue('s', CurEnv) == NULL)
186 				define('s', RealHostName, CurEnv);
187 
188 			/* check for validity of this command */
189 			if (hasmail)
190 			{
191 				message("503", "Sender already specified");
192 				break;
193 			}
194 			if (InChild)
195 			{
196 				syserr("Nested MAIL command");
197 				exit(0);
198 			}
199 
200 			/* fork a subprocess to process this command */
201 			if (runinchild("SMTP-MAIL") > 0)
202 				break;
203 			initsys();
204 
205 			/* child -- go do the processing */
206 			p = skipword(p, "from");
207 			if (p == NULL)
208 				break;
209 			setsender(p);
210 			if (Errors == 0)
211 			{
212 				message("250", "Sender ok");
213 				hasmail = TRUE;
214 			}
215 			else if (InChild)
216 				finis();
217 			break;
218 
219 		  case CMDRCPT:		/* rcpt -- designate recipient */
220 			if (setjmp(TopFrame) > 0)
221 			{
222 				CurEnv->e_flags &= ~EF_FATALERRS;
223 				break;
224 			}
225 			QuickAbort = TRUE;
226 			p = skipword(p, "to");
227 			if (p == NULL)
228 				break;
229 			a = parseaddr(p, (ADDRESS *) NULL, 1, '\0');
230 			if (a == NULL)
231 				break;
232 			a->q_flags |= QPRIMARY;
233 			a = recipient(a, &CurEnv->e_sendqueue);
234 			if (Errors != 0)
235 				break;
236 
237 			/* no errors during parsing, but might be a duplicate */
238 			CurEnv->e_to = p;
239 			if (!bitset(QBADADDR, a->q_flags))
240 				message("250", "Recipient ok");
241 			else
242 			{
243 				/* punt -- should keep message in ADDRESS.... */
244 				message("550", "Addressee unknown");
245 			}
246 			CurEnv->e_to = NULL;
247 			rcps++;
248 			break;
249 
250 		  case CMDDATA:		/* data -- text of mail */
251 			if (!hasmail)
252 			{
253 				message("503", "Need MAIL command");
254 				break;
255 			}
256 			else if (rcps <= 0)
257 			{
258 				message("503", "Need RCPT (recipient)");
259 				break;
260 			}
261 
262 			/* collect the text of the message */
263 			collect(TRUE);
264 			if (Errors != 0)
265 				break;
266 
267 			/*
268 			**  Arrange to send to everyone.
269 			**	If sending to multiple people, mail back
270 			**		errors rather than reporting directly.
271 			**	In any case, don't mail back errors for
272 			**		anything that has happened up to
273 			**		now (the other end will do this).
274 			**	Truncate our transcript -- the mail has gotten
275 			**		to us successfully, and if we have
276 			**		to mail this back, it will be easier
277 			**		on the reader.
278 			**	Then send to everyone.
279 			**	Finally give a reply code.  If an error has
280 			**		already been given, don't mail a
281 			**		message back.
282 			**	We goose error returns by clearing error bit.
283 			*/
284 
285 			if (rcps != 1)
286 			{
287 				HoldErrs = TRUE;
288 				ErrorMode = EM_MAIL;
289 			}
290 			CurEnv->e_flags &= ~EF_FATALERRS;
291 			CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp);
292 
293 			/* send to all recipients */
294 			sendall(CurEnv, SM_DEFAULT);
295 			CurEnv->e_to = NULL;
296 
297 			/* issue success if appropriate and reset */
298 			if (Errors == 0 || HoldErrs)
299 				message("250", "Ok");
300 			else
301 				CurEnv->e_flags &= ~EF_FATALERRS;
302 
303 			/* if in a child, pop back to our parent */
304 			if (InChild)
305 				finis();
306 			break;
307 
308 		  case CMDRSET:		/* rset -- reset state */
309 			message("250", "Reset state");
310 			if (InChild)
311 				finis();
312 			break;
313 
314 		  case CMDVRFY:		/* vrfy -- verify address */
315 			if (runinchild("SMTP-VRFY") > 0)
316 				break;
317 			vrfyqueue = NULL;
318 			QuickAbort = TRUE;
319 			sendtolist(p, (ADDRESS *) NULL, &vrfyqueue);
320 			if (Errors != 0)
321 			{
322 				if (InChild)
323 					finis();
324 				break;
325 			}
326 			while (vrfyqueue != NULL)
327 			{
328 				register ADDRESS *a = vrfyqueue->q_next;
329 				char *code;
330 
331 				while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
332 					a = a->q_next;
333 
334 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
335 				{
336 					if (a != NULL)
337 						code = "250-";
338 					else
339 						code = "250";
340 					if (vrfyqueue->q_fullname == NULL)
341 						message(code, "<%s>", vrfyqueue->q_paddr);
342 					else
343 						message(code, "%s <%s>",
344 						    vrfyqueue->q_fullname, vrfyqueue->q_paddr);
345 				}
346 				else if (a == NULL)
347 					message("554", "Self destructive alias loop");
348 				vrfyqueue = a;
349 			}
350 			if (InChild)
351 				finis();
352 			break;
353 
354 		  case CMDHELP:		/* help -- give user info */
355 			if (*p == '\0')
356 				p = "SMTP";
357 			help(p);
358 			break;
359 
360 		  case CMDNOOP:		/* noop -- do nothing */
361 			message("200", "OK");
362 			break;
363 
364 		  case CMDQUIT:		/* quit -- leave mail */
365 			message("221", "%s closing connection", HostName);
366 			if (InChild)
367 				ExitStat = EX_QUIT;
368 			finis();
369 
370 		  case CMDVERB:		/* set verbose mode */
371 			Verbose = TRUE;
372 			message("200", "Verbose mode");
373 			break;
374 
375 		  case CMDONEX:		/* doing one transaction only */
376 			OneXact = TRUE;
377 			message("200", "Only one transaction");
378 			break;
379 
380 # ifdef DEBUG
381 		  case CMDDBGQSHOW:	/* show queues */
382 			printf("Send Queue=");
383 			printaddr(CurEnv->e_sendqueue, TRUE);
384 			break;
385 
386 		  case CMDDBGDEBUG:	/* set debug mode */
387 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
388 			tTflag(p);
389 			message("200", "Debug set");
390 			break;
391 
392 		  case CMDDBGKILL:	/* kill the parent */
393 			if (!iswiz())
394 				break;
395 			if (kill(MotherPid, SIGTERM) >= 0)
396 				message("200", "Mother is dead");
397 			else
398 				message("500", "Can't kill Mom");
399 			break;
400 
401 		  case CMDDBGSHELL:	/* give us an interactive shell */
402 			if (!iswiz())
403 				break;
404 			if (fileno(InChannel) != 0)
405 			{
406 				(void) close(0);
407 				(void) dup(fileno(InChannel));
408 				if (fileno(InChannel) != fileno(OutChannel))
409 					(void) fclose(InChannel);
410 				InChannel = stdin;
411 			}
412 			if (fileno(OutChannel) != 1)
413 			{
414 				(void) close(1);
415 				(void) dup(fileno(OutChannel));
416 				(void) fclose(OutChannel);
417 				OutChannel = stdout;
418 			}
419 			(void) close(2);
420 			(void) dup(1);
421 			execl("/bin/csh", "sendmail", 0);
422 			execl("/bin/sh", "sendmail", 0);
423 			message("500", "Can't");
424 			exit(EX_UNAVAILABLE);
425 
426 		  case CMDDBGWIZ:	/* become a wizard */
427 			if (WizWord != NULL)
428 			{
429 				char seed[3];
430 				extern char *crypt();
431 
432 				strncpy(seed, WizWord, 2);
433 				if (strcmp(WizWord, crypt(p, seed)) == 0)
434 				{
435 					IsWiz = TRUE;
436 					message("200", "Please pass, oh mighty wizard");
437 					break;
438 				}
439 			}
440 			message("500", "You are no wizard!");
441 			break;
442 # endif DEBUG
443 
444 		  case CMDERROR:	/* unknown command */
445 			message("500", "Command unrecognized");
446 			break;
447 
448 		  default:
449 			syserr("smtp: unknown code %d", c->cmdcode);
450 			break;
451 		}
452 	}
453 }
454 /*
455 **  SKIPWORD -- skip a fixed word.
456 **
457 **	Parameters:
458 **		p -- place to start looking.
459 **		w -- word to skip.
460 **
461 **	Returns:
462 **		p following w.
463 **		NULL on error.
464 **
465 **	Side Effects:
466 **		clobbers the p data area.
467 */
468 
469 static char *
470 skipword(p, w)
471 	register char *p;
472 	char *w;
473 {
474 	register char *q;
475 	extern bool sameword();
476 
477 	/* find beginning of word */
478 	while (isspace(*p))
479 		p++;
480 	q = p;
481 
482 	/* find end of word */
483 	while (*p != '\0' && *p != ':' && !isspace(*p))
484 		p++;
485 	while (isspace(*p))
486 		*p++ = '\0';
487 	if (*p != ':')
488 	{
489 	  syntax:
490 		message("501", "Syntax error");
491 		Errors++;
492 		return (NULL);
493 	}
494 	*p++ = '\0';
495 	while (isspace(*p))
496 		p++;
497 
498 	/* see if the input word matches desired word */
499 	if (!sameword(q, w))
500 		goto syntax;
501 
502 	return (p);
503 }
504 /*
505 **  HELP -- implement the HELP command.
506 **
507 **	Parameters:
508 **		topic -- the topic we want help for.
509 **
510 **	Returns:
511 **		none.
512 **
513 **	Side Effects:
514 **		outputs the help file to message output.
515 */
516 
517 help(topic)
518 	char *topic;
519 {
520 	register FILE *hf;
521 	int len;
522 	char buf[MAXLINE];
523 	bool noinfo;
524 
525 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
526 	{
527 		/* no help */
528 		errno = 0;
529 		message("502", "HELP not implemented");
530 		return;
531 	}
532 
533 	len = strlen(topic);
534 	makelower(topic);
535 	noinfo = TRUE;
536 
537 	while (fgets(buf, sizeof buf, hf) != NULL)
538 	{
539 		if (strncmp(buf, topic, len) == 0)
540 		{
541 			register char *p;
542 
543 			p = index(buf, '\t');
544 			if (p == NULL)
545 				p = buf;
546 			else
547 				p++;
548 			fixcrlf(p, TRUE);
549 			message("214-", p);
550 			noinfo = FALSE;
551 		}
552 	}
553 
554 	if (noinfo)
555 		message("504", "HELP topic unknown");
556 	else
557 		message("214", "End of HELP info");
558 	(void) fclose(hf);
559 }
560 /*
561 **  ISWIZ -- tell us if we are a wizard
562 **
563 **	If not, print a nasty message.
564 **
565 **	Parameters:
566 **		none.
567 **
568 **	Returns:
569 **		TRUE if we are a wizard.
570 **		FALSE if we are not a wizard.
571 **
572 **	Side Effects:
573 **		Prints a 500 exit stat if we are not a wizard.
574 */
575 
576 bool
577 iswiz()
578 {
579 	if (!IsWiz)
580 		message("500", "Mere mortals musn't mutter that mantra");
581 	return (IsWiz);
582 }
583 /*
584 **  RUNINCHILD -- return twice -- once in the child, then in the parent again
585 **
586 **	Parameters:
587 **		label -- a string used in error messages
588 **
589 **	Returns:
590 **		zero in the child
591 **		one in the parent
592 **
593 **	Side Effects:
594 **		none.
595 */
596 
597 runinchild(label)
598 	char *label;
599 {
600 	int childpid;
601 
602 	if (!OneXact)
603 	{
604 		childpid = dofork();
605 		if (childpid < 0)
606 		{
607 			syserr("%s: cannot fork", label);
608 			return (1);
609 		}
610 		if (childpid > 0)
611 		{
612 			auto int st;
613 
614 			/* parent -- wait for child to complete */
615 			st = waitfor(childpid);
616 			if (st == -1)
617 				syserr("%s: lost child", label);
618 
619 			/* if we exited on a QUIT command, complete the process */
620 			if (st == (EX_QUIT << 8))
621 				finis();
622 
623 			return (1);
624 		}
625 		else
626 		{
627 			/* child */
628 			InChild = TRUE;
629 		}
630 	}
631 
632 	/* child (or ONEX command specified) */
633 	clearenvelope(CurEnv);
634 
635 	/* open alias database */
636 	initaliases(AliasFile, FALSE);
637 
638 	return (0);
639 }
640 
641 # endif SMTP
642