1 # include <errno.h>
2 # include "sendmail.h"
3 
4 # ifndef SMTP
5 SCCSID(@(#)srvrsmtp.c	3.42		12/13/82	(no SMTP));
6 # else SMTP
7 
8 SCCSID(@(#)srvrsmtp.c	3.42		12/13/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 	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 			**	Then send to everyone.
230 			**	Finally give a reply code.  If an error has
231 			**		already been given, don't mail a
232 			**		message back.
233 			**	We goose error returns by clearing error bit.
234 			*/
235 
236 			if (rcps != 1)
237 			{
238 				HoldErrs = TRUE;
239 				ErrorMode == EM_MAIL;
240 			}
241 			CurEnv->e_flags &= ~EF_FATALERRS;
242 
243 			/* send to all recipients */
244 			sendall(CurEnv, SendMode);
245 			CurEnv->e_to = NULL;
246 
247 			/* issue success if appropriate and reset */
248 			if (Errors == 0 || HoldErrs)
249 				message("250", "Ok");
250 			else
251 				CurEnv->e_flags &= ~EF_FATALERRS;
252 
253 			/* if in a child, pop back to our parent */
254 			if (InChild)
255 				finis();
256 			break;
257 
258 		  case CMDRSET:		/* rset -- reset state */
259 			message("250", "Reset state");
260 			if (InChild)
261 				finis();
262 			break;
263 
264 		  case CMDVRFY:		/* vrfy -- verify address */
265 			if (runinchild("SMTP-VRFY") > 0)
266 				break;
267 			vrfyqueue = NULL;
268 			QuickAbort = TRUE;
269 			sendtolist(p, (ADDRESS *) NULL, &vrfyqueue);
270 			if (Errors != 0)
271 			{
272 				if (InChild)
273 					finis();
274 				break;
275 			}
276 			while (vrfyqueue != NULL)
277 			{
278 				register ADDRESS *a = vrfyqueue->q_next;
279 				char *code;
280 
281 				while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
282 					a = a->q_next;
283 
284 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
285 				{
286 					if (a != NULL)
287 						code = "250-";
288 					else
289 						code = "250";
290 					if (vrfyqueue->q_fullname == NULL)
291 						message(code, "<%s>", vrfyqueue->q_paddr);
292 					else
293 						message(code, "%s <%s>",
294 						    vrfyqueue->q_fullname, vrfyqueue->q_paddr);
295 				}
296 				else if (a == NULL)
297 					message("554", "Self destructive alias loop");
298 				vrfyqueue = a;
299 			}
300 			if (InChild)
301 				finis();
302 			break;
303 
304 		  case CMDHELP:		/* help -- give user info */
305 			if (*p == '\0')
306 				p = "SMTP";
307 			help(p);
308 			break;
309 
310 		  case CMDNOOP:		/* noop -- do nothing */
311 			message("200", "OK");
312 			break;
313 
314 		  case CMDQUIT:		/* quit -- leave mail */
315 			message("221", "%s closing connection", HostName);
316 			if (InChild)
317 				ExitStat = EX_QUIT;
318 			finis();
319 
320 		  case CMDVERB:		/* set verbose mode */
321 			Verbose = TRUE;
322 			message("200", "Verbose mode");
323 			break;
324 
325 		  case CMDONEX:		/* doing one transaction only */
326 			OneXact = TRUE;
327 			message("200", "Only one transaction");
328 			break;
329 
330 # ifdef DEBUG
331 		  case CMDDBGQSHOW:	/* show queues */
332 			printf("Send Queue=");
333 			printaddr(CurEnv->e_sendqueue, TRUE);
334 			break;
335 
336 		  case CMDDBGDEBUG:	/* set debug mode */
337 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
338 			tTflag(p);
339 			message("200", "Debug set");
340 			break;
341 
342 		  case CMDDBGKILL:	/* kill the parent */
343 			if (!iswiz())
344 				break;
345 			if (kill(MotherPid, SIGTERM) >= 0)
346 				message("200", "Mother is dead");
347 			else
348 				message("500", "Can't kill Mom");
349 			break;
350 
351 		  case CMDDBGSHELL:	/* give us an interactive shell */
352 			if (!iswiz())
353 				break;
354 			if (fileno(InChannel) != 0)
355 			{
356 				(void) close(0);
357 				(void) dup(fileno(InChannel));
358 				(void) fclose(InChannel);
359 				InChannel = stdin;
360 			}
361 			if (fileno(OutChannel) != 1)
362 			{
363 				(void) close(1);
364 				(void) dup(fileno(OutChannel));
365 				(void) fclose(OutChannel);
366 				OutChannel = stdout;
367 			}
368 			execl("/bin/csh", "sendmail", 0);
369 			execl("/bin/sh", "sendmail", 0);
370 			message("500", "Can't");
371 			exit(EX_UNAVAILABLE);
372 
373 		  case CMDDBGWIZ:	/* become a wizard */
374 			if (WizWord != NULL)
375 			{
376 				char seed[3];
377 				extern char *crypt();
378 
379 				strncpy(seed, WizWord, 2);
380 				if (strcmp(WizWord, crypt(p, seed)) != 0)
381 				{
382 					message("500", "You are no wizard!");
383 					break;
384 				}
385 			}
386 			IsWiz = TRUE;
387 			message("200", "Please pass, oh mighty wizard");
388 			break;
389 # endif DEBUG
390 
391 		  case CMDERROR:	/* unknown command */
392 			message("500", "Command unrecognized");
393 			break;
394 
395 		  default:
396 			syserr("smtp: unknown code %d", c->cmdcode);
397 			break;
398 		}
399 	}
400 }
401 /*
402 **  SKIPWORD -- skip a fixed word.
403 **
404 **	Parameters:
405 **		p -- place to start looking.
406 **		w -- word to skip.
407 **
408 **	Returns:
409 **		p following w.
410 **		NULL on error.
411 **
412 **	Side Effects:
413 **		clobbers the p data area.
414 */
415 
416 static char *
417 skipword(p, w)
418 	register char *p;
419 	char *w;
420 {
421 	register char *q;
422 	extern bool sameword();
423 
424 	/* find beginning of word */
425 	while (isspace(*p))
426 		p++;
427 	q = p;
428 
429 	/* find end of word */
430 	while (*p != '\0' && *p != ':' && !isspace(*p))
431 		p++;
432 	while (isspace(*p))
433 		*p++ = '\0';
434 	if (*p != ':')
435 	{
436 	  syntax:
437 		message("501", "Syntax error");
438 		Errors++;
439 		return (NULL);
440 	}
441 	*p++ = '\0';
442 	while (isspace(*p))
443 		p++;
444 
445 	/* see if the input word matches desired word */
446 	if (!sameword(q, w))
447 		goto syntax;
448 
449 	return (p);
450 }
451 /*
452 **  HELP -- implement the HELP command.
453 **
454 **	Parameters:
455 **		topic -- the topic we want help for.
456 **
457 **	Returns:
458 **		none.
459 **
460 **	Side Effects:
461 **		outputs the help file to message output.
462 */
463 
464 help(topic)
465 	char *topic;
466 {
467 	register FILE *hf;
468 	int len;
469 	char buf[MAXLINE];
470 	bool noinfo;
471 
472 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
473 	{
474 		/* no help */
475 		message("502", "HELP not implemented");
476 		return;
477 	}
478 
479 	len = strlen(topic);
480 	makelower(topic);
481 	noinfo = TRUE;
482 
483 	while (fgets(buf, sizeof buf, hf) != NULL)
484 	{
485 		if (strncmp(buf, topic, len) == 0)
486 		{
487 			register char *p;
488 
489 			p = index(buf, '\t');
490 			if (p == NULL)
491 				p = buf;
492 			else
493 				p++;
494 			fixcrlf(p, TRUE);
495 			message("214-", p);
496 			noinfo = FALSE;
497 		}
498 	}
499 
500 	if (noinfo)
501 		message("504", "HELP topic unknown");
502 	else
503 		message("214", "End of HELP info");
504 	(void) fclose(hf);
505 }
506 /*
507 **  ISWIZ -- tell us if we are a wizard
508 **
509 **	If not, print a nasty message.
510 **
511 **	Parameters:
512 **		none.
513 **
514 **	Returns:
515 **		TRUE if we are a wizard.
516 **		FALSE if we are not a wizard.
517 **
518 **	Side Effects:
519 **		Prints a 500 exit stat if we are not a wizard.
520 */
521 
522 bool
523 iswiz()
524 {
525 	if (!IsWiz)
526 		message("500", "Mere mortals musn't mutter that mantra");
527 	return (IsWiz);
528 }
529 /*
530 **  RUNINCHILD -- return twice -- once in the child, then in the parent again
531 **
532 **	Parameters:
533 **		label -- a string used in error messages
534 **
535 **	Returns:
536 **		zero in the child
537 **		one in the parent
538 **
539 **	Side Effects:
540 **		none.
541 */
542 
543 runinchild(label)
544 	char *label;
545 {
546 	int childpid;
547 
548 	if (OneXact)
549 		return (0);
550 
551 	childpid = dofork();
552 	if (childpid < 0)
553 	{
554 		syserr("%s: cannot fork", label);
555 		return (1);
556 	}
557 	if (childpid > 0)
558 	{
559 		auto int st;
560 
561 		/* parent -- wait for child to complete */
562 		st = waitfor(childpid);
563 		if (st == -1)
564 			syserr("%s: lost child", label);
565 
566 		/* if we exited on a QUIT command, complete the process */
567 		if (st == (EX_QUIT << 8))
568 			finis();
569 
570 		return (1);
571 	}
572 	else
573 	{
574 		/* child */
575 		InChild = TRUE;
576 		clearenvelope(CurEnv);
577 		return (0);
578 	}
579 }
580 
581 # endif SMTP
582