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