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