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