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