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