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