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