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