1 # include <errno.h>
2 # include "sendmail.h"
3 
4 # ifndef SMTP
5 SCCSID(@(#)srvrsmtp.c	3.43		01/08/83	(no SMTP));
6 # else SMTP
7 
8 SCCSID(@(#)srvrsmtp.c	3.43		01/08/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 				(void) fclose(InChannel);
364 				InChannel = stdin;
365 			}
366 			if (fileno(OutChannel) != 1)
367 			{
368 				(void) close(1);
369 				(void) dup(fileno(OutChannel));
370 				(void) fclose(OutChannel);
371 				OutChannel = stdout;
372 			}
373 			execl("/bin/csh", "sendmail", 0);
374 			execl("/bin/sh", "sendmail", 0);
375 			message("500", "Can't");
376 			exit(EX_UNAVAILABLE);
377 
378 		  case CMDDBGWIZ:	/* become a wizard */
379 			if (WizWord != NULL)
380 			{
381 				char seed[3];
382 				extern char *crypt();
383 
384 				strncpy(seed, WizWord, 2);
385 				if (strcmp(WizWord, crypt(p, seed)) != 0)
386 				{
387 					message("500", "You are no wizard!");
388 					break;
389 				}
390 			}
391 			IsWiz = TRUE;
392 			message("200", "Please pass, oh mighty wizard");
393 			break;
394 # endif DEBUG
395 
396 		  case CMDERROR:	/* unknown command */
397 			message("500", "Command unrecognized");
398 			break;
399 
400 		  default:
401 			syserr("smtp: unknown code %d", c->cmdcode);
402 			break;
403 		}
404 	}
405 }
406 /*
407 **  SKIPWORD -- skip a fixed word.
408 **
409 **	Parameters:
410 **		p -- place to start looking.
411 **		w -- word to skip.
412 **
413 **	Returns:
414 **		p following w.
415 **		NULL on error.
416 **
417 **	Side Effects:
418 **		clobbers the p data area.
419 */
420 
421 static char *
422 skipword(p, w)
423 	register char *p;
424 	char *w;
425 {
426 	register char *q;
427 	extern bool sameword();
428 
429 	/* find beginning of word */
430 	while (isspace(*p))
431 		p++;
432 	q = p;
433 
434 	/* find end of word */
435 	while (*p != '\0' && *p != ':' && !isspace(*p))
436 		p++;
437 	while (isspace(*p))
438 		*p++ = '\0';
439 	if (*p != ':')
440 	{
441 	  syntax:
442 		message("501", "Syntax error");
443 		Errors++;
444 		return (NULL);
445 	}
446 	*p++ = '\0';
447 	while (isspace(*p))
448 		p++;
449 
450 	/* see if the input word matches desired word */
451 	if (!sameword(q, w))
452 		goto syntax;
453 
454 	return (p);
455 }
456 /*
457 **  HELP -- implement the HELP command.
458 **
459 **	Parameters:
460 **		topic -- the topic we want help for.
461 **
462 **	Returns:
463 **		none.
464 **
465 **	Side Effects:
466 **		outputs the help file to message output.
467 */
468 
469 help(topic)
470 	char *topic;
471 {
472 	register FILE *hf;
473 	int len;
474 	char buf[MAXLINE];
475 	bool noinfo;
476 
477 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
478 	{
479 		/* no help */
480 		message("502", "HELP not implemented");
481 		return;
482 	}
483 
484 	len = strlen(topic);
485 	makelower(topic);
486 	noinfo = TRUE;
487 
488 	while (fgets(buf, sizeof buf, hf) != NULL)
489 	{
490 		if (strncmp(buf, topic, len) == 0)
491 		{
492 			register char *p;
493 
494 			p = index(buf, '\t');
495 			if (p == NULL)
496 				p = buf;
497 			else
498 				p++;
499 			fixcrlf(p, TRUE);
500 			message("214-", p);
501 			noinfo = FALSE;
502 		}
503 	}
504 
505 	if (noinfo)
506 		message("504", "HELP topic unknown");
507 	else
508 		message("214", "End of HELP info");
509 	(void) fclose(hf);
510 }
511 /*
512 **  ISWIZ -- tell us if we are a wizard
513 **
514 **	If not, print a nasty message.
515 **
516 **	Parameters:
517 **		none.
518 **
519 **	Returns:
520 **		TRUE if we are a wizard.
521 **		FALSE if we are not a wizard.
522 **
523 **	Side Effects:
524 **		Prints a 500 exit stat if we are not a wizard.
525 */
526 
527 bool
528 iswiz()
529 {
530 	if (!IsWiz)
531 		message("500", "Mere mortals musn't mutter that mantra");
532 	return (IsWiz);
533 }
534 /*
535 **  RUNINCHILD -- return twice -- once in the child, then in the parent again
536 **
537 **	Parameters:
538 **		label -- a string used in error messages
539 **
540 **	Returns:
541 **		zero in the child
542 **		one in the parent
543 **
544 **	Side Effects:
545 **		none.
546 */
547 
548 runinchild(label)
549 	char *label;
550 {
551 	int childpid;
552 
553 	if (OneXact)
554 		return (0);
555 
556 	childpid = dofork();
557 	if (childpid < 0)
558 	{
559 		syserr("%s: cannot fork", label);
560 		return (1);
561 	}
562 	if (childpid > 0)
563 	{
564 		auto int st;
565 
566 		/* parent -- wait for child to complete */
567 		st = waitfor(childpid);
568 		if (st == -1)
569 			syserr("%s: lost child", label);
570 
571 		/* if we exited on a QUIT command, complete the process */
572 		if (st == (EX_QUIT << 8))
573 			finis();
574 
575 		return (1);
576 	}
577 	else
578 	{
579 		/* child */
580 		InChild = TRUE;
581 		clearenvelope(CurEnv);
582 		return (0);
583 	}
584 }
585 
586 # endif SMTP
587