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