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