1 # include <errno.h>
2 # include "sendmail.h"
3 
4 # ifndef SMTP
5 SCCSID(@(#)srvrsmtp.c	3.37		11/24/82	(no SMTP));
6 # else SMTP
7 
8 SCCSID(@(#)srvrsmtp.c	3.37		11/24/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 #define EX_QUIT		22		/* special code for QUIT command */
79 
80 smtp()
81 {
82 	register char *p;
83 	register struct cmd *c;
84 	char *cmd;
85 	extern char *skipword();
86 	extern bool sameword();
87 	bool hasmail;			/* mail command received */
88 	int rcps;			/* number of recipients */
89 	auto ADDRESS *vrfyqueue;
90 	bool onexact = FALSE;		/* one transaction this connection */
91 	bool firsttime = TRUE;		/* this is the first transaction */
92 	char inp[MAXLINE];
93 	extern char Version[];
94 	extern tick();
95 	extern bool iswiz();
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(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));
155 			message("250", "%s Hello %s, pleased to meet you",
156 				HostName, p);
157 			break;
158 
159 		  case CMDMAIL:		/* mail -- designate sender */
160 			firsttime = FALSE;
161 
162 			/* check for validity of this command */
163 			if (hasmail)
164 			{
165 				message("503", "Sender already specified");
166 				break;
167 			}
168 			if (InChild)
169 			{
170 				syserr("Nested MAIL command");
171 				exit(0);
172 			}
173 
174 			/* fork a subprocess to process this command */
175 			if (runinchild("SMTP-MAIL") > 0)
176 				break;
177 			initsys();
178 
179 			/* child -- go do the processing */
180 			p = skipword(p, "from");
181 			if (p == NULL)
182 				break;
183 			setsender(p);
184 			if (Errors == 0)
185 			{
186 				message("250", "Sender ok");
187 				hasmail = TRUE;
188 			}
189 			else if (InChild)
190 				finis();
191 			break;
192 
193 		  case CMDRCPT:		/* rcpt -- designate recipient */
194 			p = skipword(p, "to");
195 			if (p == NULL)
196 				break;
197 			sendto(p, (ADDRESS *) NULL, &CurEnv->e_sendqueue);
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 				HoldErrs = MailBack = TRUE;
238 			CurEnv->e_flags &= ~EF_FATALERRS;
239 
240 			/* send to all recipients */
241 			sendall(CurEnv, SendMode);
242 			CurEnv->e_to = NULL;
243 
244 			/* issue success if appropriate and reset */
245 			if (Errors == 0 || HoldErrs)
246 			{
247 				HoldErrs = FALSE;
248 				message("250", "Ok");
249 			}
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 			sendto(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 			execl("/bin/csh", "sendmail", 0);
355 			execl("/bin/sh", "sendmail", 0);
356 			message("500", "Can't");
357 			break;
358 
359 		  case CMDDBGWIZ:	/* become a wizard */
360 			if (WizWord != NULL)
361 			{
362 				char seed[3];
363 				extern char *crypt();
364 
365 				strncpy(seed, WizWord, 2);
366 				if (strcmp(WizWord, crypt(p, seed)) != 0)
367 				{
368 					message("500", "You are no wizard!");
369 					break;
370 				}
371 			}
372 			IsWiz = TRUE;
373 			message("200", "Please pass, oh mighty wizard");
374 			break;
375 # endif DEBUG
376 
377 		  case CMDERROR:	/* unknown command */
378 			message("500", "Command unrecognized");
379 			break;
380 
381 		  default:
382 			syserr("smtp: unknown code %d", c->cmdcode);
383 			break;
384 		}
385 	}
386 }
387 /*
388 **  SKIPWORD -- skip a fixed word.
389 **
390 **	Parameters:
391 **		p -- place to start looking.
392 **		w -- word to skip.
393 **
394 **	Returns:
395 **		p following w.
396 **		NULL on error.
397 **
398 **	Side Effects:
399 **		clobbers the p data area.
400 */
401 
402 static char *
403 skipword(p, w)
404 	register char *p;
405 	char *w;
406 {
407 	register char *q;
408 	extern bool sameword();
409 
410 	/* find beginning of word */
411 	while (isspace(*p))
412 		p++;
413 	q = p;
414 
415 	/* find end of word */
416 	while (*p != '\0' && *p != ':' && !isspace(*p))
417 		p++;
418 	while (isspace(*p))
419 		*p++ = '\0';
420 	if (*p != ':')
421 	{
422 	  syntax:
423 		message("501", "Syntax error");
424 		Errors++;
425 		return (NULL);
426 	}
427 	*p++ = '\0';
428 	while (isspace(*p))
429 		p++;
430 
431 	/* see if the input word matches desired word */
432 	if (!sameword(q, w))
433 		goto syntax;
434 
435 	return (p);
436 }
437 /*
438 **  HELP -- implement the HELP command.
439 **
440 **	Parameters:
441 **		topic -- the topic we want help for.
442 **
443 **	Returns:
444 **		none.
445 **
446 **	Side Effects:
447 **		outputs the help file to message output.
448 */
449 
450 help(topic)
451 	char *topic;
452 {
453 	register FILE *hf;
454 	int len;
455 	char buf[MAXLINE];
456 	bool noinfo;
457 
458 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
459 	{
460 		/* no help */
461 		message("502", "HELP not implemented");
462 		return;
463 	}
464 
465 	len = strlen(topic);
466 	makelower(topic);
467 	noinfo = TRUE;
468 
469 	while (fgets(buf, sizeof buf, hf) != NULL)
470 	{
471 		if (strncmp(buf, topic, len) == 0)
472 		{
473 			register char *p;
474 
475 			p = index(buf, '\t');
476 			if (p == NULL)
477 				p = buf;
478 			else
479 				p++;
480 			fixcrlf(p, TRUE);
481 			message("214-", p);
482 			noinfo = FALSE;
483 		}
484 	}
485 
486 	if (noinfo)
487 		message("504", "HELP topic unknown");
488 	else
489 		message("214", "End of HELP info");
490 	(void) fclose(hf);
491 }
492 /*
493 **  ISWIZ -- tell us if we are a wizard
494 **
495 **	If not, print a nasty message.
496 **
497 **	Parameters:
498 **		none.
499 **
500 **	Returns:
501 **		TRUE if we are a wizard.
502 **		FALSE if we are not a wizard.
503 **
504 **	Side Effects:
505 **		Prints a 500 exit stat if we are not a wizard.
506 */
507 
508 bool
509 iswiz()
510 {
511 	if (!IsWiz)
512 		message("500", "Mere mortals musn't mutter that mantra");
513 	return (IsWiz);
514 }
515 /*
516 **  RUNINCHILD -- return twice -- once in the child, then in the parent again
517 **
518 **	Parameters:
519 **		label -- a string used in error messages
520 **
521 **	Returns:
522 **		zero in the child
523 **		one in the parent
524 **
525 **	Side Effects:
526 **		none.
527 */
528 
529 runinchild(label)
530 	char *label;
531 {
532 	int childpid;
533 
534 	childpid = dofork();
535 	if (childpid < 0)
536 	{
537 		syserr("%s: cannot fork", label);
538 		return (1);
539 	}
540 	if (childpid > 0)
541 	{
542 		/* parent -- wait for child to complete */
543 		auto int st;
544 		int i;
545 
546 		while ((i = wait(&st)) != childpid)
547 		{
548 			if (i < 0 && errno != EINTR)
549 				break;
550 		}
551 		if (i < 0)
552 			syserr("%s: lost child", label);
553 
554 		/* if we exited on a QUIT command, complete the process */
555 		if (st == (EX_QUIT << 8))
556 			finis();
557 
558 		return (1);
559 	}
560 	else
561 	{
562 		/* child */
563 		InChild = TRUE;
564 		return (0);
565 	}
566 }
567 
568 # endif SMTP
569