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