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