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