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