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