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