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