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