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