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