1 /*
2  * Copyright (c) 1983 Eric P. Allman
3  * Copyright (c) 1988, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 # include "sendmail.h"
10 
11 #ifndef lint
12 #ifdef SMTP
13 static char sccsid[] = "@(#)srvrsmtp.c	8.31 (Berkeley) 03/04/94 (with SMTP)";
14 #else
15 static char sccsid[] = "@(#)srvrsmtp.c	8.31 (Berkeley) 03/04/94 (without SMTP)";
16 #endif
17 #endif /* not lint */
18 
19 # include <errno.h>
20 
21 # ifdef SMTP
22 
23 /*
24 **  SMTP -- run the SMTP protocol.
25 **
26 **	Parameters:
27 **		none.
28 **
29 **	Returns:
30 **		never.
31 **
32 **	Side Effects:
33 **		Reads commands from the input channel and processes
34 **			them.
35 */
36 
37 struct cmd
38 {
39 	char	*cmdname;	/* command name */
40 	int	cmdcode;	/* internal code, see below */
41 };
42 
43 /* values for cmdcode */
44 # define CMDERROR	0	/* bad command */
45 # define CMDMAIL	1	/* mail -- designate sender */
46 # define CMDRCPT	2	/* rcpt -- designate recipient */
47 # define CMDDATA	3	/* data -- send message text */
48 # define CMDRSET	4	/* rset -- reset state */
49 # define CMDVRFY	5	/* vrfy -- verify address */
50 # define CMDEXPN	6	/* expn -- expand address */
51 # define CMDNOOP	7	/* noop -- do nothing */
52 # define CMDQUIT	8	/* quit -- close connection and die */
53 # define CMDHELO	9	/* helo -- be polite */
54 # define CMDHELP	10	/* help -- give usage info */
55 # define CMDEHLO	11	/* ehlo -- extended helo (RFC 1425) */
56 /* non-standard commands */
57 # define CMDONEX	16	/* onex -- sending one transaction only */
58 # define CMDVERB	17	/* verb -- go into verbose mode */
59 /* use this to catch and log "door handle" attempts on your system */
60 # define CMDLOGBOGUS	23	/* bogus command that should be logged */
61 /* debugging-only commands, only enabled if SMTPDEBUG is defined */
62 # define CMDDBGQSHOW	24	/* showq -- show send queue */
63 # define CMDDBGDEBUG	25	/* debug -- set debug mode */
64 
65 static struct cmd	CmdTab[] =
66 {
67 	"mail",		CMDMAIL,
68 	"rcpt",		CMDRCPT,
69 	"data",		CMDDATA,
70 	"rset",		CMDRSET,
71 	"vrfy",		CMDVRFY,
72 	"expn",		CMDEXPN,
73 	"help",		CMDHELP,
74 	"noop",		CMDNOOP,
75 	"quit",		CMDQUIT,
76 	"helo",		CMDHELO,
77 	"ehlo",		CMDEHLO,
78 	"verb",		CMDVERB,
79 	"onex",		CMDONEX,
80 	/*
81 	 * remaining commands are here only
82 	 * to trap and log attempts to use them
83 	 */
84 	"showq",	CMDDBGQSHOW,
85 	"debug",	CMDDBGDEBUG,
86 	"wiz",		CMDLOGBOGUS,
87 	NULL,		CMDERROR,
88 };
89 
90 bool	OneXact = FALSE;		/* one xaction only this run */
91 char	*CurSmtpClient;			/* who's at the other end of channel */
92 
93 static char	*skipword();
94 
95 #define MAXBADCOMMANDS	25		/* maximum number of bad commands */
96 
97 smtp(e)
98 	register ENVELOPE *e;
99 {
100 	register char *p;
101 	register struct cmd *c;
102 	char *cmd;
103 	auto ADDRESS *vrfyqueue;
104 	ADDRESS *a;
105 	bool gotmail;			/* mail command received */
106 	bool gothello;			/* helo command received */
107 	bool vrfy;			/* set if this is a vrfy command */
108 	char *protocol;			/* sending protocol */
109 	char *sendinghost;		/* sending hostname */
110 	long msize;			/* approximate maximum message size */
111 	char *peerhostname;		/* name of SMTP peer or "localhost" */
112 	auto char *delimptr;
113 	char *id;
114 	int nrcpts;			/* number of RCPT commands */
115 	bool doublequeue;
116 	int badcommands = 0;		/* count of bad commands */
117 	char inp[MAXLINE];
118 	char cmdbuf[MAXLINE];
119 	extern char Version[];
120 	extern ENVELOPE BlankEnvelope;
121 
122 	if (fileno(OutChannel) != fileno(stdout))
123 	{
124 		/* arrange for debugging output to go to remote host */
125 		(void) dup2(fileno(OutChannel), fileno(stdout));
126 	}
127 	settime(e);
128 	peerhostname = RealHostName;
129 	if (peerhostname == NULL)
130 		peerhostname = "localhost";
131 	CurHostName = peerhostname;
132 	CurSmtpClient = macvalue('_', e);
133 	if (CurSmtpClient == NULL)
134 		CurSmtpClient = CurHostName;
135 
136 	setproctitle("server %s startup", CurSmtpClient);
137 	expand("\201e", inp, &inp[sizeof inp], e);
138 	if (BrokenSmtpPeers)
139 	{
140 		message("220 %s", inp);
141 	}
142 	else
143 	{
144 		message("220-%s", inp);
145 		message("220 ESMTP spoken here");
146 	}
147 	protocol = NULL;
148 	sendinghost = macvalue('s', e);
149 	gothello = FALSE;
150 	gotmail = FALSE;
151 	for (;;)
152 	{
153 		/* arrange for backout */
154 		if (setjmp(TopFrame) > 0)
155 		{
156 			/* if() nesting is necessary for Cray UNICOS */
157 			if (InChild)
158 			{
159 				QuickAbort = FALSE;
160 				SuprErrs = TRUE;
161 				finis();
162 			}
163 		}
164 		QuickAbort = FALSE;
165 		HoldErrs = FALSE;
166 		LogUsrErrs = FALSE;
167 		e->e_flags &= ~(EF_VRFYONLY|EF_GLOBALERRS);
168 
169 		/* setup for the read */
170 		e->e_to = NULL;
171 		Errors = 0;
172 		(void) fflush(stdout);
173 
174 		/* read the input line */
175 		SmtpPhase = "server cmd read";
176 		setproctitle("server %s cmd read", CurHostName);
177 		p = sfgets(inp, sizeof inp, InChannel, TimeOuts.to_nextcommand,
178 				SmtpPhase);
179 
180 		/* handle errors */
181 		if (p == NULL)
182 		{
183 			/* end of file, just die */
184 			disconnect(1, e);
185 			message("421 %s Lost input channel from %s",
186 				MyHostName, CurSmtpClient);
187 #ifdef LOG
188 			if (LogLevel > (gotmail ? 1 : 19))
189 				syslog(LOG_NOTICE, "lost input channel from %s",
190 					CurSmtpClient);
191 #endif
192 			if (InChild)
193 				ExitStat = EX_QUIT;
194 			finis();
195 		}
196 
197 		/* clean up end of line */
198 		fixcrlf(inp, TRUE);
199 
200 		/* echo command to transcript */
201 		if (e->e_xfp != NULL)
202 			fprintf(e->e_xfp, "<<< %s\n", inp);
203 
204 		if (e->e_id == NULL)
205 			setproctitle("%s: %.80s", CurSmtpClient, inp);
206 		else
207 			setproctitle("%s %s: %.80s", e->e_id, CurSmtpClient, inp);
208 
209 		/* break off command */
210 		for (p = inp; isascii(*p) && isspace(*p); p++)
211 			continue;
212 		cmd = cmdbuf;
213 		while (*p != '\0' &&
214 		       !(isascii(*p) && isspace(*p)) &&
215 		       cmd < &cmdbuf[sizeof cmdbuf - 2])
216 			*cmd++ = *p++;
217 		*cmd = '\0';
218 
219 		/* throw away leading whitespace */
220 		while (isascii(*p) && isspace(*p))
221 			p++;
222 
223 		/* decode command */
224 		for (c = CmdTab; c->cmdname != NULL; c++)
225 		{
226 			if (!strcasecmp(c->cmdname, cmdbuf))
227 				break;
228 		}
229 
230 		/* reset errors */
231 		errno = 0;
232 
233 		/* process command */
234 		switch (c->cmdcode)
235 		{
236 		  case CMDHELO:		/* hello -- introduce yourself */
237 		  case CMDEHLO:		/* extended hello */
238 			if (c->cmdcode == CMDEHLO)
239 			{
240 				protocol = "ESMTP";
241 				SmtpPhase = "server EHLO";
242 			}
243 			else
244 			{
245 				protocol = "SMTP";
246 				SmtpPhase = "server HELO";
247 			}
248 			sendinghost = newstr(p);
249 			gothello = TRUE;
250 			if (c->cmdcode != CMDEHLO)
251 			{
252 				/* print old message and be done with it */
253 				message("250 %s Hello %s, pleased to meet you",
254 					MyHostName, CurSmtpClient);
255 				break;
256 			}
257 
258 			/* print extended message and brag */
259 			message("250-%s Hello %s, pleased to meet you",
260 				MyHostName, p);
261 			if (!bitset(PRIV_NOEXPN, PrivacyFlags))
262 				message("250-EXPN");
263 			if (MaxMessageSize > 0)
264 				message("250-SIZE %ld", MaxMessageSize);
265 			else
266 				message("250-SIZE");
267 			message("250 HELP");
268 			break;
269 
270 		  case CMDMAIL:		/* mail -- designate sender */
271 			SmtpPhase = "server MAIL";
272 
273 			/* check for validity of this command */
274 			if (!gothello)
275 			{
276 				/* set sending host to our known value */
277 				if (sendinghost == NULL)
278 					sendinghost = peerhostname;
279 
280 				if (bitset(PRIV_NEEDMAILHELO, PrivacyFlags))
281 				{
282 					message("503 Polite people say HELO first");
283 					break;
284 				}
285 			}
286 			if (gotmail)
287 			{
288 				message("503 Sender already specified");
289 				if (InChild)
290 					finis();
291 				break;
292 			}
293 			if (InChild)
294 			{
295 				errno = 0;
296 				syserr("503 Nested MAIL command: MAIL %s", p);
297 				finis();
298 			}
299 
300 			/* fork a subprocess to process this command */
301 			if (runinchild("SMTP-MAIL", e) > 0)
302 				break;
303 			if (!gothello)
304 			{
305 				auth_warning(e,
306 					"Host %s didn't use HELO protocol",
307 					peerhostname);
308 			}
309 #ifdef PICKY_HELO_CHECK
310 			if (strcasecmp(sendinghost, peerhostname) != 0 &&
311 			    (strcasecmp(peerhostname, "localhost") != 0 ||
312 			     strcasecmp(sendinghost, MyHostName) != 0))
313 			{
314 				auth_warning(e, "Host %s claimed to be %s",
315 					peerhostname, sendinghost);
316 			}
317 #endif
318 
319 			if (protocol == NULL)
320 				protocol = "SMTP";
321 			define('r', protocol, e);
322 			define('s', sendinghost, e);
323 			initsys(e);
324 			nrcpts = 0;
325 			e->e_flags |= EF_LOGSENDER;
326 			setproctitle("%s %s: %.80s", e->e_id, CurSmtpClient, inp);
327 
328 			/* child -- go do the processing */
329 			p = skipword(p, "from");
330 			if (p == NULL)
331 				break;
332 			if (setjmp(TopFrame) > 0)
333 			{
334 				/* this failed -- undo work */
335 				if (InChild)
336 				{
337 					QuickAbort = FALSE;
338 					SuprErrs = TRUE;
339 					e->e_flags &= ~EF_FATALERRS;
340 					finis();
341 				}
342 				break;
343 			}
344 			QuickAbort = TRUE;
345 
346 			/* must parse sender first */
347 			delimptr = NULL;
348 			setsender(p, e, &delimptr, FALSE);
349 			p = delimptr;
350 			if (p != NULL && *p != '\0')
351 				*p++ = '\0';
352 
353 			/* now parse ESMTP arguments */
354 			msize = 0;
355 			for (; p != NULL && *p != '\0'; p++)
356 			{
357 				char *kp;
358 				char *vp = NULL;
359 
360 				/* locate the beginning of the keyword */
361 				while (isascii(*p) && isspace(*p))
362 					p++;
363 				if (*p == '\0')
364 					break;
365 				kp = p;
366 
367 				/* skip to the value portion */
368 				while (isascii(*p) && isalnum(*p) || *p == '-')
369 					p++;
370 				if (*p == '=')
371 				{
372 					*p++ = '\0';
373 					vp = p;
374 
375 					/* skip to the end of the value */
376 					while (*p != '\0' && *p != ' ' &&
377 					       !(isascii(*p) && iscntrl(*p)) &&
378 					       *p != '=')
379 						p++;
380 				}
381 
382 				if (*p != '\0')
383 					*p++ = '\0';
384 
385 				if (tTd(19, 1))
386 					printf("MAIL: got arg %s=%s\n", kp,
387 						vp == NULL ? "<null>" : vp);
388 
389 				if (strcasecmp(kp, "size") == 0)
390 				{
391 					if (vp == NULL)
392 					{
393 						usrerr("501 SIZE requires a value");
394 						/* NOTREACHED */
395 					}
396 					msize = atol(vp);
397 				}
398 				else if (strcasecmp(kp, "body") == 0)
399 				{
400 					if (vp == NULL)
401 					{
402 						usrerr("501 BODY requires a value");
403 						/* NOTREACHED */
404 					}
405 # ifdef MIME
406 					if (strcasecmp(vp, "8bitmime") == 0)
407 					{
408 						e->e_bodytype = "8BITMIME";
409 						SevenBit = FALSE;
410 					}
411 					else if (strcasecmp(vp, "7bit") == 0)
412 					{
413 						e->e_bodytype = "7BIT";
414 						SevenBit = TRUE;
415 					}
416 					else
417 					{
418 						usrerr("501 Unknown BODY type %s",
419 							vp);
420 					}
421 # endif
422 				}
423 				else
424 				{
425 					usrerr("501 %s parameter unrecognized", kp);
426 					/* NOTREACHED */
427 				}
428 			}
429 
430 			if (MaxMessageSize > 0 && msize > MaxMessageSize)
431 			{
432 				usrerr("552 Message size exceeds fixed maximum message size (%ld)",
433 					MaxMessageSize);
434 				/* NOTREACHED */
435 			}
436 
437 			if (!enoughspace(msize))
438 			{
439 				message("452 Insufficient disk space; try again later");
440 				break;
441 			}
442 			message("250 Sender ok");
443 			gotmail = TRUE;
444 			break;
445 
446 		  case CMDRCPT:		/* rcpt -- designate recipient */
447 			if (!gotmail)
448 			{
449 				usrerr("503 Need MAIL before RCPT");
450 				break;
451 			}
452 			SmtpPhase = "server RCPT";
453 			if (setjmp(TopFrame) > 0)
454 			{
455 				e->e_flags &= ~EF_FATALERRS;
456 				break;
457 			}
458 			QuickAbort = TRUE;
459 			LogUsrErrs = TRUE;
460 
461 			if (e->e_sendmode != SM_DELIVER)
462 				e->e_flags |= EF_VRFYONLY;
463 
464 			p = skipword(p, "to");
465 			if (p == NULL)
466 				break;
467 			a = parseaddr(p, NULLADDR, RF_COPYALL, ' ', NULL, e);
468 			if (a == NULL)
469 				break;
470 			a->q_flags |= QPRIMARY;
471 			a = recipient(a, &e->e_sendqueue, e);
472 			if (Errors != 0)
473 				break;
474 
475 			/* no errors during parsing, but might be a duplicate */
476 			e->e_to = p;
477 			if (!bitset(QBADADDR, a->q_flags))
478 			{
479 				message("250 Recipient ok%s",
480 					bitset(QQUEUEUP, a->q_flags) ?
481 						" (will queue)" : "");
482 				nrcpts++;
483 			}
484 			else
485 			{
486 				/* punt -- should keep message in ADDRESS.... */
487 				message("550 Addressee unknown");
488 			}
489 			e->e_to = NULL;
490 			break;
491 
492 		  case CMDDATA:		/* data -- text of mail */
493 			SmtpPhase = "server DATA";
494 			if (!gotmail)
495 			{
496 				message("503 Need MAIL command");
497 				break;
498 			}
499 			else if (nrcpts <= 0)
500 			{
501 				message("503 Need RCPT (recipient)");
502 				break;
503 			}
504 
505 			/* check to see if we need to re-expand aliases */
506 			/* also reset QBADADDR on already-diagnosted addrs */
507 			doublequeue = FALSE;
508 			for (a = e->e_sendqueue; a != NULL; a = a->q_next)
509 			{
510 				if (bitset(QVERIFIED, a->q_flags))
511 				{
512 					/* need to re-expand aliases */
513 					doublequeue = TRUE;
514 				}
515 				if (bitset(QBADADDR, a->q_flags))
516 				{
517 					/* make this "go away" */
518 					a->q_flags |= QDONTSEND;
519 					a->q_flags &= ~QBADADDR;
520 				}
521 			}
522 
523 			/* collect the text of the message */
524 			SmtpPhase = "collect";
525 			collect(TRUE, doublequeue, e);
526 			if (Errors != 0)
527 				goto abortmessage;
528 			HoldErrs = TRUE;
529 
530 			/*
531 			**  Arrange to send to everyone.
532 			**	If sending to multiple people, mail back
533 			**		errors rather than reporting directly.
534 			**	In any case, don't mail back errors for
535 			**		anything that has happened up to
536 			**		now (the other end will do this).
537 			**	Truncate our transcript -- the mail has gotten
538 			**		to us successfully, and if we have
539 			**		to mail this back, it will be easier
540 			**		on the reader.
541 			**	Then send to everyone.
542 			**	Finally give a reply code.  If an error has
543 			**		already been given, don't mail a
544 			**		message back.
545 			**	We goose error returns by clearing error bit.
546 			*/
547 
548 			SmtpPhase = "delivery";
549 			if (nrcpts != 1 && !doublequeue)
550 			{
551 				HoldErrs = TRUE;
552 				e->e_errormode = EM_MAIL;
553 			}
554 			e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp);
555 			id = e->e_id;
556 
557 			/* send to all recipients */
558 			sendall(e, doublequeue ? SM_QUEUE : SM_DEFAULT);
559 			e->e_to = NULL;
560 
561 			/* issue success if appropriate and reset */
562 			if (Errors == 0 || HoldErrs)
563 				message("250 %s Message accepted for delivery", id);
564 
565 			if (bitset(EF_FATALERRS, e->e_flags) && !HoldErrs)
566 			{
567 				/* avoid sending back an extra message */
568 				e->e_flags &= ~EF_FATALERRS;
569 				e->e_flags |= EF_CLRQUEUE;
570 			}
571 			else
572 			{
573 				/* from now on, we have to operate silently */
574 				HoldErrs = TRUE;
575 				e->e_errormode = EM_MAIL;
576 
577 				/* if we just queued, poke it */
578 				if (doublequeue && e->e_sendmode != SM_QUEUE)
579 				{
580 					extern pid_t dowork();
581 
582 					unlockqueue(e);
583 					(void) dowork(id, TRUE, TRUE, e);
584 				}
585 			}
586 
587   abortmessage:
588 			/* if in a child, pop back to our parent */
589 			if (InChild)
590 				finis();
591 
592 			/* clean up a bit */
593 			gotmail = FALSE;
594 			dropenvelope(e);
595 			CurEnv = e = newenvelope(e, CurEnv);
596 			e->e_flags = BlankEnvelope.e_flags;
597 			break;
598 
599 		  case CMDRSET:		/* rset -- reset state */
600 			message("250 Reset state");
601 			e->e_flags |= EF_CLRQUEUE;
602 			if (InChild)
603 				finis();
604 
605 			/* clean up a bit */
606 			gotmail = FALSE;
607 			dropenvelope(e);
608 			CurEnv = e = newenvelope(e, CurEnv);
609 			break;
610 
611 		  case CMDVRFY:		/* vrfy -- verify address */
612 		  case CMDEXPN:		/* expn -- expand address */
613 			vrfy = c->cmdcode == CMDVRFY;
614 			if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN,
615 						PrivacyFlags))
616 			{
617 				if (vrfy)
618 					message("252 Who's to say?");
619 				else
620 					message("502 Sorry, we do not allow this operation");
621 #ifdef LOG
622 				if (LogLevel > 5)
623 					syslog(LOG_INFO, "%s: %s [rejected]",
624 						CurSmtpClient, inp);
625 #endif
626 				break;
627 			}
628 			else if (!gothello &&
629 				 bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO,
630 						PrivacyFlags))
631 			{
632 				message("503 I demand that you introduce yourself first");
633 				break;
634 			}
635 			if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0)
636 				break;
637 #ifdef LOG
638 			if (LogLevel > 5)
639 				syslog(LOG_INFO, "%s: %s", CurSmtpClient, inp);
640 #endif
641 			vrfyqueue = NULL;
642 			QuickAbort = TRUE;
643 			if (vrfy)
644 				e->e_flags |= EF_VRFYONLY;
645 			while (*p != '\0' && isascii(*p) && isspace(*p))
646 				*p++;
647 			if (*p == '\0')
648 			{
649 				message("501 Argument required");
650 				Errors++;
651 			}
652 			else
653 			{
654 				(void) sendtolist(p, NULLADDR, &vrfyqueue, e);
655 			}
656 			if (Errors != 0)
657 			{
658 				if (InChild)
659 					finis();
660 				break;
661 			}
662 			if (vrfyqueue == NULL)
663 			{
664 				message("554 Nothing to %s", vrfy ? "VRFY" : "EXPN");
665 			}
666 			while (vrfyqueue != NULL)
667 			{
668 				a = vrfyqueue;
669 				while ((a = a->q_next) != NULL &&
670 				       bitset(QDONTSEND|QBADADDR, a->q_flags))
671 					continue;
672 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
673 					printvrfyaddr(vrfyqueue, a == NULL);
674 				vrfyqueue = vrfyqueue->q_next;
675 			}
676 			if (InChild)
677 				finis();
678 			break;
679 
680 		  case CMDHELP:		/* help -- give user info */
681 			help(p);
682 			break;
683 
684 		  case CMDNOOP:		/* noop -- do nothing */
685 			message("250 OK");
686 			break;
687 
688 		  case CMDQUIT:		/* quit -- leave mail */
689 			message("221 %s closing connection", MyHostName);
690 
691 doquit:
692 			/* avoid future 050 messages */
693 			disconnect(1, e);
694 
695 			if (InChild)
696 				ExitStat = EX_QUIT;
697 			finis();
698 
699 		  case CMDVERB:		/* set verbose mode */
700 			if (bitset(PRIV_NOEXPN, PrivacyFlags))
701 			{
702 				/* this would give out the same info */
703 				message("502 Verbose unavailable");
704 				break;
705 			}
706 			Verbose = TRUE;
707 			e->e_sendmode = SM_DELIVER;
708 			message("250 Verbose mode");
709 			break;
710 
711 		  case CMDONEX:		/* doing one transaction only */
712 			OneXact = TRUE;
713 			message("250 Only one transaction");
714 			break;
715 
716 # ifdef SMTPDEBUG
717 		  case CMDDBGQSHOW:	/* show queues */
718 			printf("Send Queue=");
719 			printaddr(e->e_sendqueue, TRUE);
720 			break;
721 
722 		  case CMDDBGDEBUG:	/* set debug mode */
723 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
724 			tTflag(p);
725 			message("200 Debug set");
726 			break;
727 
728 # else /* not SMTPDEBUG */
729 		  case CMDDBGQSHOW:	/* show queues */
730 		  case CMDDBGDEBUG:	/* set debug mode */
731 # endif /* SMTPDEBUG */
732 		  case CMDLOGBOGUS:	/* bogus command */
733 # ifdef LOG
734 			if (LogLevel > 0)
735 				syslog(LOG_CRIT,
736 				    "\"%s\" command from %s (%s)",
737 				    c->cmdname, peerhostname,
738 				    anynet_ntoa(&RealHostAddr));
739 # endif
740 			/* FALL THROUGH */
741 
742 		  case CMDERROR:	/* unknown command */
743 			if (++badcommands > MAXBADCOMMANDS)
744 			{
745 				message("421 %s Too many bad commands; closing connection",
746 					MyHostName);
747 				goto doquit;
748 			}
749 
750 			message("500 Command unrecognized");
751 			break;
752 
753 		  default:
754 			errno = 0;
755 			syserr("500 smtp: unknown code %d", c->cmdcode);
756 			break;
757 		}
758 	}
759 }
760 /*
761 **  SKIPWORD -- skip a fixed word.
762 **
763 **	Parameters:
764 **		p -- place to start looking.
765 **		w -- word to skip.
766 **
767 **	Returns:
768 **		p following w.
769 **		NULL on error.
770 **
771 **	Side Effects:
772 **		clobbers the p data area.
773 */
774 
775 static char *
776 skipword(p, w)
777 	register char *p;
778 	char *w;
779 {
780 	register char *q;
781 	char *firstp = p;
782 
783 	/* find beginning of word */
784 	while (isascii(*p) && isspace(*p))
785 		p++;
786 	q = p;
787 
788 	/* find end of word */
789 	while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p)))
790 		p++;
791 	while (isascii(*p) && isspace(*p))
792 		*p++ = '\0';
793 	if (*p != ':')
794 	{
795 	  syntax:
796 		message("501 Syntax error in parameters scanning \"%s\"",
797 			firstp);
798 		Errors++;
799 		return (NULL);
800 	}
801 	*p++ = '\0';
802 	while (isascii(*p) && isspace(*p))
803 		p++;
804 
805 	if (*p == '\0')
806 		goto syntax;
807 
808 	/* see if the input word matches desired word */
809 	if (strcasecmp(q, w))
810 		goto syntax;
811 
812 	return (p);
813 }
814 /*
815 **  PRINTVRFYADDR -- print an entry in the verify queue
816 **
817 **	Parameters:
818 **		a -- the address to print
819 **		last -- set if this is the last one.
820 **
821 **	Returns:
822 **		none.
823 **
824 **	Side Effects:
825 **		Prints the appropriate 250 codes.
826 */
827 
828 printvrfyaddr(a, last)
829 	register ADDRESS *a;
830 	bool last;
831 {
832 	char fmtbuf[20];
833 
834 	strcpy(fmtbuf, "250");
835 	fmtbuf[3] = last ? ' ' : '-';
836 
837 	if (a->q_fullname == NULL)
838 	{
839 		if (strchr(a->q_user, '@') == NULL)
840 			strcpy(&fmtbuf[4], "<%s@%s>");
841 		else
842 			strcpy(&fmtbuf[4], "<%s>");
843 		message(fmtbuf, a->q_user, MyHostName);
844 	}
845 	else
846 	{
847 		if (strchr(a->q_user, '@') == NULL)
848 			strcpy(&fmtbuf[4], "%s <%s@%s>");
849 		else
850 			strcpy(&fmtbuf[4], "%s <%s>");
851 		message(fmtbuf, a->q_fullname, a->q_user, MyHostName);
852 	}
853 }
854 /*
855 **  HELP -- implement the HELP command.
856 **
857 **	Parameters:
858 **		topic -- the topic we want help for.
859 **
860 **	Returns:
861 **		none.
862 **
863 **	Side Effects:
864 **		outputs the help file to message output.
865 */
866 
867 help(topic)
868 	char *topic;
869 {
870 	register FILE *hf;
871 	int len;
872 	char buf[MAXLINE];
873 	bool noinfo;
874 
875 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
876 	{
877 		/* no help */
878 		errno = 0;
879 		message("502 HELP not implemented");
880 		return;
881 	}
882 
883 	if (topic == NULL || *topic == '\0')
884 		topic = "smtp";
885 	else
886 		makelower(topic);
887 
888 	len = strlen(topic);
889 	noinfo = TRUE;
890 
891 	while (fgets(buf, sizeof buf, hf) != NULL)
892 	{
893 		if (strncmp(buf, topic, len) == 0)
894 		{
895 			register char *p;
896 
897 			p = strchr(buf, '\t');
898 			if (p == NULL)
899 				p = buf;
900 			else
901 				p++;
902 			fixcrlf(p, TRUE);
903 			message("214-%s", p);
904 			noinfo = FALSE;
905 		}
906 	}
907 
908 	if (noinfo)
909 		message("504 HELP topic unknown");
910 	else
911 		message("214 End of HELP info");
912 	(void) fclose(hf);
913 }
914 /*
915 **  RUNINCHILD -- return twice -- once in the child, then in the parent again
916 **
917 **	Parameters:
918 **		label -- a string used in error messages
919 **
920 **	Returns:
921 **		zero in the child
922 **		one in the parent
923 **
924 **	Side Effects:
925 **		none.
926 */
927 
928 runinchild(label, e)
929 	char *label;
930 	register ENVELOPE *e;
931 {
932 	int childpid;
933 
934 	if (!OneXact)
935 	{
936 		childpid = dofork();
937 		if (childpid < 0)
938 		{
939 			syserr("%s: cannot fork", label);
940 			return (1);
941 		}
942 		if (childpid > 0)
943 		{
944 			auto int st;
945 
946 			/* parent -- wait for child to complete */
947 			setproctitle("server %s child wait", CurHostName);
948 			st = waitfor(childpid);
949 			if (st == -1)
950 				syserr("%s: lost child", label);
951 			else if (!WIFEXITED(st))
952 				syserr("%s: died on signal %d",
953 					label, st & 0177);
954 
955 			/* if we exited on a QUIT command, complete the process */
956 			if (WEXITSTATUS(st) == EX_QUIT)
957 			{
958 				disconnect(1, e);
959 				finis();
960 			}
961 
962 			return (1);
963 		}
964 		else
965 		{
966 			/* child */
967 			InChild = TRUE;
968 			QuickAbort = FALSE;
969 			clearenvelope(e, FALSE);
970 		}
971 	}
972 
973 	/* open alias database */
974 	initmaps(FALSE, e);
975 
976 	return (0);
977 }
978 
979 # endif /* SMTP */
980