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