1 /*
2  * Copyright (c) 1983, 1995 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.73 (Berkeley) 05/18/95 (with SMTP)";
14 #else
15 static char sccsid[] = "@(#)srvrsmtp.c	8.73 (Berkeley) 05/18/95 (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 extern char	RealUserName[];
95 
96 
97 #define MAXBADCOMMANDS	25		/* maximum number of bad commands */
98 
99 smtp(e)
100 	register ENVELOPE *e;
101 {
102 	register char *p;
103 	register struct cmd *c;
104 	char *cmd;
105 	auto ADDRESS *vrfyqueue;
106 	ADDRESS *a;
107 	bool gotmail;			/* mail command received */
108 	bool gothello;			/* helo command received */
109 	bool vrfy;			/* set if this is a vrfy command */
110 	char *protocol;			/* sending protocol */
111 	char *sendinghost;		/* sending hostname */
112 	char *peerhostname;		/* name of SMTP peer or "localhost" */
113 	auto char *delimptr;
114 	char *id;
115 	int nrcpts = 0;			/* number of RCPT commands */
116 	bool doublequeue;
117 	int badcommands = 0;		/* count of bad commands */
118 	char inp[MAXLINE];
119 	char cmdbuf[MAXLINE];
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, sizeof inp, e);
138 
139 	/* output the first line, inserting "ESMTP" as second word */
140 	p = strchr(inp, '\n');
141 	if (p != NULL)
142 		*p++ = '\0';
143 	id = strchr(inp, ' ');
144 	if (id == NULL)
145 		id = &inp[strlen(inp)];
146 	cmd = p == NULL ? "220 %.*s ESMTP%s" : "220-%.*s ESMTP%s";
147 	message(cmd, id - inp, inp, id);
148 
149 	/* output remaining lines */
150 	while ((id = p) != NULL && (p = strchr(id, '\n')) != NULL)
151 	{
152 		*p++ = '\0';
153 		if (isascii(*id) && isspace(*id))
154 			id++;
155 		message("220-%s", id);
156 	}
157 	if (id != NULL)
158 	{
159 		if (isascii(*id) && isspace(*id))
160 			id++;
161 		message("220 %s", id);
162 	}
163 
164 	protocol = NULL;
165 	sendinghost = macvalue('s', e);
166 	gothello = FALSE;
167 	gotmail = FALSE;
168 	for (;;)
169 	{
170 		/* arrange for backout */
171 		if (setjmp(TopFrame) > 0)
172 		{
173 			/* if() nesting is necessary for Cray UNICOS */
174 			if (InChild)
175 			{
176 				QuickAbort = FALSE;
177 				SuprErrs = TRUE;
178 				finis();
179 			}
180 		}
181 		QuickAbort = FALSE;
182 		HoldErrs = FALSE;
183 		LogUsrErrs = FALSE;
184 		e->e_flags &= ~(EF_VRFYONLY|EF_GLOBALERRS);
185 
186 		/* setup for the read */
187 		e->e_to = NULL;
188 		Errors = 0;
189 		(void) fflush(stdout);
190 
191 		/* read the input line */
192 		SmtpPhase = "server cmd read";
193 		setproctitle("server %s cmd read", CurHostName);
194 		p = sfgets(inp, sizeof inp, InChannel, TimeOuts.to_nextcommand,
195 				SmtpPhase);
196 
197 		/* handle errors */
198 		if (p == NULL)
199 		{
200 			/* end of file, just die */
201 			disconnect(1, e);
202 			message("421 %s Lost input channel from %s",
203 				MyHostName, CurSmtpClient);
204 #ifdef LOG
205 			if (LogLevel > (gotmail ? 1 : 19))
206 				syslog(LOG_NOTICE, "lost input channel from %s",
207 					CurSmtpClient);
208 #endif
209 			if (InChild)
210 				ExitStat = EX_QUIT;
211 			finis();
212 		}
213 
214 		/* clean up end of line */
215 		fixcrlf(inp, TRUE);
216 
217 		/* echo command to transcript */
218 		if (e->e_xfp != NULL)
219 			fprintf(e->e_xfp, "<<< %s\n", inp);
220 
221 		if (e->e_id == NULL)
222 			setproctitle("%s: %.80s", CurSmtpClient, inp);
223 		else
224 			setproctitle("%s %s: %.80s", e->e_id, CurSmtpClient, inp);
225 
226 		/* break off command */
227 		for (p = inp; isascii(*p) && isspace(*p); p++)
228 			continue;
229 		cmd = cmdbuf;
230 		while (*p != '\0' &&
231 		       !(isascii(*p) && isspace(*p)) &&
232 		       cmd < &cmdbuf[sizeof cmdbuf - 2])
233 			*cmd++ = *p++;
234 		*cmd = '\0';
235 
236 		/* throw away leading whitespace */
237 		while (isascii(*p) && isspace(*p))
238 			p++;
239 
240 		/* decode command */
241 		for (c = CmdTab; c->cmdname != NULL; c++)
242 		{
243 			if (!strcasecmp(c->cmdname, cmdbuf))
244 				break;
245 		}
246 
247 		/* reset errors */
248 		errno = 0;
249 
250 		/* process command */
251 		switch (c->cmdcode)
252 		{
253 		  case CMDHELO:		/* hello -- introduce yourself */
254 		  case CMDEHLO:		/* extended hello */
255 			if (c->cmdcode == CMDEHLO)
256 			{
257 				protocol = "ESMTP";
258 				SmtpPhase = "server EHLO";
259 			}
260 			else
261 			{
262 				protocol = "SMTP";
263 				SmtpPhase = "server HELO";
264 			}
265 
266 			/* check for valid domain name (re 1123 5.2.5) */
267 			if (*p == '\0')
268 			{
269 				message("501 %s requires domain address",
270 					cmdbuf);
271 				break;
272 			}
273 			else
274 			{
275 				register char *q;
276 
277 				for (q = p; *q != '\0'; q++)
278 				{
279 					if (!isascii(*q))
280 						break;
281 					if (isalnum(*q))
282 						continue;
283 					if (strchr("[].-_#", *q) == NULL)
284 						break;
285 				}
286 				if (*q != '\0')
287 				{
288 					message("501 Invalid domain name");
289 					break;
290 				}
291 			}
292 
293 			sendinghost = newstr(p);
294 			gothello = TRUE;
295 			if (c->cmdcode != CMDEHLO)
296 			{
297 				/* print old message and be done with it */
298 				message("250 %s Hello %s, pleased to meet you",
299 					MyHostName, CurSmtpClient);
300 				break;
301 			}
302 
303 			/* print extended message and brag */
304 			message("250-%s Hello %s, pleased to meet you",
305 				MyHostName, CurSmtpClient);
306 			if (!bitset(PRIV_NOEXPN, PrivacyFlags))
307 				message("250-EXPN");
308 #if MIME8TO7
309 			message("250-8BITMIME");
310 #endif
311 			if (MaxMessageSize > 0)
312 				message("250-SIZE %ld", MaxMessageSize);
313 			else
314 				message("250-SIZE");
315 #if DSN
316 			message("250-X-DSN-03 (Draft of 12 Mar 1995)");
317 #endif
318 			message("250 HELP");
319 			break;
320 
321 		  case CMDMAIL:		/* mail -- designate sender */
322 			SmtpPhase = "server MAIL";
323 
324 			/* check for validity of this command */
325 			if (!gothello)
326 			{
327 				/* set sending host to our known value */
328 				if (sendinghost == NULL)
329 					sendinghost = peerhostname;
330 
331 				if (bitset(PRIV_NEEDMAILHELO, PrivacyFlags))
332 				{
333 					message("503 Polite people say HELO first");
334 					break;
335 				}
336 			}
337 			if (gotmail)
338 			{
339 				message("503 Sender already specified");
340 				if (InChild)
341 					finis();
342 				break;
343 			}
344 			if (InChild)
345 			{
346 				errno = 0;
347 				syserr("503 Nested MAIL command: MAIL %s", p);
348 				finis();
349 			}
350 
351 			/* fork a subprocess to process this command */
352 			if (runinchild("SMTP-MAIL", e) > 0)
353 				break;
354 			if (!gothello)
355 			{
356 				auth_warning(e,
357 					"Host %s didn't use HELO protocol",
358 					peerhostname);
359 			}
360 #ifdef PICKY_HELO_CHECK
361 			if (strcasecmp(sendinghost, peerhostname) != 0 &&
362 			    (strcasecmp(peerhostname, "localhost") != 0 ||
363 			     strcasecmp(sendinghost, MyHostName) != 0))
364 			{
365 				auth_warning(e, "Host %s claimed to be %s",
366 					peerhostname, sendinghost);
367 			}
368 #endif
369 
370 			if (protocol == NULL)
371 				protocol = "SMTP";
372 			define('r', protocol, e);
373 			define('s', sendinghost, e);
374 			initsys(e);
375 			nrcpts = 0;
376 			e->e_flags |= EF_LOGSENDER|EF_CLRQUEUE;
377 			setproctitle("%s %s: %.80s", e->e_id, CurSmtpClient, inp);
378 
379 			/* child -- go do the processing */
380 			p = skipword(p, "from");
381 			if (p == NULL)
382 				break;
383 			if (setjmp(TopFrame) > 0)
384 			{
385 				/* this failed -- undo work */
386 				if (InChild)
387 				{
388 					QuickAbort = FALSE;
389 					SuprErrs = TRUE;
390 					e->e_flags &= ~EF_FATALERRS;
391 					finis();
392 				}
393 				break;
394 			}
395 			QuickAbort = TRUE;
396 
397 			/* must parse sender first */
398 			delimptr = NULL;
399 			setsender(p, e, &delimptr, FALSE);
400 			p = delimptr;
401 			if (p != NULL && *p != '\0')
402 				*p++ = '\0';
403 
404 			/* check for possible spoofing */
405 			if (RealUid != 0 && OpMode == MD_SMTP &&
406 			    !bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) &&
407 			    strcmp(e->e_from.q_user, RealUserName) != 0)
408 			{
409 				auth_warning(e, "%s owned process doing -bs",
410 					RealUserName);
411 			}
412 
413 			/* now parse ESMTP arguments */
414 			e->e_msgsize = 0;
415 			while (p != NULL && *p != '\0')
416 			{
417 				char *kp;
418 				char *vp = NULL;
419 
420 				/* locate the beginning of the keyword */
421 				while (isascii(*p) && isspace(*p))
422 					p++;
423 				if (*p == '\0')
424 					break;
425 				kp = p;
426 
427 				/* skip to the value portion */
428 				while (isascii(*p) && isalnum(*p) || *p == '-')
429 					p++;
430 				if (*p == '=')
431 				{
432 					*p++ = '\0';
433 					vp = p;
434 
435 					/* skip to the end of the value */
436 					while (*p != '\0' && *p != ' ' &&
437 					       !(isascii(*p) && iscntrl(*p)) &&
438 					       *p != '=')
439 						p++;
440 				}
441 
442 				if (*p != '\0')
443 					*p++ = '\0';
444 
445 				if (tTd(19, 1))
446 					printf("MAIL: got arg %s=\"%s\"\n", kp,
447 						vp == NULL ? "<null>" : vp);
448 
449 				mail_esmtp_args(kp, vp, e);
450 			}
451 
452 			if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize)
453 			{
454 				usrerr("552 Message size exceeds fixed maximum message size (%ld)",
455 					MaxMessageSize);
456 				/* NOTREACHED */
457 			}
458 
459 			if (!enoughspace(e->e_msgsize))
460 			{
461 				message("452 Insufficient disk space; try again later");
462 				break;
463 			}
464 			message("250 Sender ok");
465 			gotmail = TRUE;
466 			break;
467 
468 		  case CMDRCPT:		/* rcpt -- designate recipient */
469 			if (!gotmail)
470 			{
471 				usrerr("503 Need MAIL before RCPT");
472 				break;
473 			}
474 			SmtpPhase = "server RCPT";
475 			if (setjmp(TopFrame) > 0)
476 			{
477 				e->e_flags &= ~EF_FATALERRS;
478 				break;
479 			}
480 			QuickAbort = TRUE;
481 			LogUsrErrs = TRUE;
482 
483 			if (e->e_sendmode != SM_DELIVER)
484 				e->e_flags |= EF_VRFYONLY;
485 
486 			p = skipword(p, "to");
487 			if (p == NULL)
488 				break;
489 			a = parseaddr(p, NULLADDR, RF_COPYALL, ' ', &delimptr, e);
490 			if (a == NULL)
491 				break;
492 			p = delimptr;
493 
494 			/* now parse ESMTP arguments */
495 			while (p != NULL && *p != '\0')
496 			{
497 				char *kp;
498 				char *vp = NULL;
499 
500 				/* locate the beginning of the keyword */
501 				while (isascii(*p) && isspace(*p))
502 					p++;
503 				if (*p == '\0')
504 					break;
505 				kp = p;
506 
507 				/* skip to the value portion */
508 				while (isascii(*p) && isalnum(*p) || *p == '-')
509 					p++;
510 				if (*p == '=')
511 				{
512 					*p++ = '\0';
513 					vp = p;
514 
515 					/* skip to the end of the value */
516 					while (*p != '\0' && *p != ' ' &&
517 					       !(isascii(*p) && iscntrl(*p)) &&
518 					       *p != '=')
519 						p++;
520 				}
521 
522 				if (*p != '\0')
523 					*p++ = '\0';
524 
525 				if (tTd(19, 1))
526 					printf("RCPT: got arg %s=\"%s\"\n", kp,
527 						vp == NULL ? "<null>" : vp);
528 
529 				rcpt_esmtp_args(a, kp, vp, e);
530 
531 			}
532 
533 			/* save in recipient list after ESMTP mods */
534 			a = recipient(a, &e->e_sendqueue, 0, e);
535 
536 			if (Errors != 0)
537 				break;
538 
539 			/* no errors during parsing, but might be a duplicate */
540 			e->e_to = p;
541 			if (!bitset(QBADADDR, a->q_flags))
542 			{
543 				message("250 Recipient ok%s",
544 					bitset(QQUEUEUP, a->q_flags) ?
545 						" (will queue)" : "");
546 				nrcpts++;
547 			}
548 			else
549 			{
550 				/* punt -- should keep message in ADDRESS.... */
551 				message("550 Addressee unknown");
552 			}
553 			e->e_to = NULL;
554 			break;
555 
556 		  case CMDDATA:		/* data -- text of mail */
557 			SmtpPhase = "server DATA";
558 			if (!gotmail)
559 			{
560 				message("503 Need MAIL command");
561 				break;
562 			}
563 			else if (nrcpts <= 0)
564 			{
565 				message("503 Need RCPT (recipient)");
566 				break;
567 			}
568 
569 			/* check to see if we need to re-expand aliases */
570 			/* also reset QBADADDR on already-diagnosted addrs */
571 			doublequeue = FALSE;
572 			for (a = e->e_sendqueue; a != NULL; a = a->q_next)
573 			{
574 				if (bitset(QVERIFIED, a->q_flags))
575 				{
576 					/* need to re-expand aliases */
577 					doublequeue = TRUE;
578 				}
579 				if (bitset(QBADADDR, a->q_flags))
580 				{
581 					/* make this "go away" */
582 					a->q_flags |= QDONTSEND;
583 					a->q_flags &= ~QBADADDR;
584 				}
585 			}
586 
587 			/* collect the text of the message */
588 			SmtpPhase = "collect";
589 			buffer_errors();
590 			collect(InChannel, TRUE, doublequeue, NULL, e);
591 			flush_errors(TRUE);
592 			if (Errors != 0)
593 				goto abortmessage;
594 
595 			/* make sure we actually do delivery */
596 			e->e_flags &= ~EF_CLRQUEUE;
597 
598 			/* from now on, we have to operate silently */
599 			buffer_errors();
600 			e->e_errormode = EM_MAIL;
601 
602 			/*
603 			**  Arrange to send to everyone.
604 			**	If sending to multiple people, mail back
605 			**		errors rather than reporting directly.
606 			**	In any case, don't mail back errors for
607 			**		anything that has happened up to
608 			**		now (the other end will do this).
609 			**	Truncate our transcript -- the mail has gotten
610 			**		to us successfully, and if we have
611 			**		to mail this back, it will be easier
612 			**		on the reader.
613 			**	Then send to everyone.
614 			**	Finally give a reply code.  If an error has
615 			**		already been given, don't mail a
616 			**		message back.
617 			**	We goose error returns by clearing error bit.
618 			*/
619 
620 			SmtpPhase = "delivery";
621 			e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp);
622 			id = e->e_id;
623 
624 			if (doublequeue)
625 			{
626 				/* make sure it is in the queue */
627 				queueup(e, TRUE, FALSE);
628 				if (e->e_sendmode == SM_QUEUE)
629 					e->e_flags |= EF_KEEPQUEUE;
630 			}
631 			else
632 			{
633 				/* send to all recipients */
634 				sendall(e, SM_DEFAULT);
635 			}
636 			e->e_to = NULL;
637 
638 			/* issue success message */
639 			message("250 %s Message accepted for delivery", id);
640 
641 			/* if we just queued, poke it */
642 			if (doublequeue && e->e_sendmode != SM_QUEUE)
643 			{
644 				extern pid_t dowork();
645 
646 				unlockqueue(e);
647 				(void) dowork(id, TRUE, TRUE, e);
648 			}
649 
650   abortmessage:
651 			/* if in a child, pop back to our parent */
652 			if (InChild)
653 				finis();
654 
655 			/* clean up a bit */
656 			gotmail = FALSE;
657 			dropenvelope(e);
658 			CurEnv = e = newenvelope(e, CurEnv);
659 			e->e_flags = BlankEnvelope.e_flags;
660 			break;
661 
662 		  case CMDRSET:		/* rset -- reset state */
663 			message("250 Reset state");
664 
665 			/* arrange to ignore any current send list */
666 			e->e_sendqueue = NULL;
667 			e->e_flags |= EF_CLRQUEUE;
668 			if (InChild)
669 				finis();
670 
671 			/* clean up a bit */
672 			gotmail = FALSE;
673 			dropenvelope(e);
674 			CurEnv = e = newenvelope(e, CurEnv);
675 			break;
676 
677 		  case CMDVRFY:		/* vrfy -- verify address */
678 		  case CMDEXPN:		/* expn -- expand address */
679 			vrfy = c->cmdcode == CMDVRFY;
680 			if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN,
681 						PrivacyFlags))
682 			{
683 				if (vrfy)
684 					message("252 Cannot VRFY user; try RCPT to attempt delivery (or try finger)");
685 				else
686 					message("502 Sorry, we do not allow this operation");
687 #ifdef LOG
688 				if (LogLevel > 5)
689 					syslog(LOG_INFO, "%s: %s [rejected]",
690 						CurSmtpClient, inp);
691 #endif
692 				break;
693 			}
694 			else if (!gothello &&
695 				 bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO,
696 						PrivacyFlags))
697 			{
698 				message("503 I demand that you introduce yourself first");
699 				break;
700 			}
701 			if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0)
702 				break;
703 #ifdef LOG
704 			if (LogLevel > 5)
705 				syslog(LOG_INFO, "%s: %s", CurSmtpClient, inp);
706 #endif
707 			vrfyqueue = NULL;
708 			QuickAbort = TRUE;
709 			if (vrfy)
710 				e->e_flags |= EF_VRFYONLY;
711 			while (*p != '\0' && isascii(*p) && isspace(*p))
712 				p++;
713 			if (*p == '\0')
714 			{
715 				message("501 Argument required");
716 				Errors++;
717 			}
718 			else
719 			{
720 				(void) sendtolist(p, NULLADDR, &vrfyqueue, 0, e);
721 			}
722 			if (Errors != 0)
723 			{
724 				if (InChild)
725 					finis();
726 				break;
727 			}
728 			if (vrfyqueue == NULL)
729 			{
730 				message("554 Nothing to %s", vrfy ? "VRFY" : "EXPN");
731 			}
732 			while (vrfyqueue != NULL)
733 			{
734 				a = vrfyqueue;
735 				while ((a = a->q_next) != NULL &&
736 				       bitset(QDONTSEND|QBADADDR, a->q_flags))
737 					continue;
738 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
739 					printvrfyaddr(vrfyqueue, a == NULL);
740 				vrfyqueue = vrfyqueue->q_next;
741 			}
742 			if (InChild)
743 				finis();
744 			break;
745 
746 		  case CMDHELP:		/* help -- give user info */
747 			help(p);
748 			break;
749 
750 		  case CMDNOOP:		/* noop -- do nothing */
751 			message("250 OK");
752 			break;
753 
754 		  case CMDQUIT:		/* quit -- leave mail */
755 			message("221 %s closing connection", MyHostName);
756 
757 doquit:
758 			/* arrange to ignore any current send list */
759 			e->e_sendqueue = NULL;
760 
761 			/* avoid future 050 messages */
762 			disconnect(1, e);
763 
764 			if (InChild)
765 				ExitStat = EX_QUIT;
766 			finis();
767 
768 		  case CMDVERB:		/* set verbose mode */
769 			if (bitset(PRIV_NOEXPN, PrivacyFlags))
770 			{
771 				/* this would give out the same info */
772 				message("502 Verbose unavailable");
773 				break;
774 			}
775 			Verbose = TRUE;
776 			e->e_sendmode = SM_DELIVER;
777 			message("250 Verbose mode");
778 			break;
779 
780 		  case CMDONEX:		/* doing one transaction only */
781 			OneXact = TRUE;
782 			message("250 Only one transaction");
783 			break;
784 
785 # ifdef SMTPDEBUG
786 		  case CMDDBGQSHOW:	/* show queues */
787 			printf("Send Queue=");
788 			printaddr(e->e_sendqueue, TRUE);
789 			break;
790 
791 		  case CMDDBGDEBUG:	/* set debug mode */
792 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
793 			tTflag(p);
794 			message("200 Debug set");
795 			break;
796 
797 # else /* not SMTPDEBUG */
798 		  case CMDDBGQSHOW:	/* show queues */
799 		  case CMDDBGDEBUG:	/* set debug mode */
800 # endif /* SMTPDEBUG */
801 		  case CMDLOGBOGUS:	/* bogus command */
802 # ifdef LOG
803 			if (LogLevel > 0)
804 				syslog(LOG_CRIT,
805 				    "\"%s\" command from %s (%s)",
806 				    c->cmdname, peerhostname,
807 				    anynet_ntoa(&RealHostAddr));
808 # endif
809 			/* FALL THROUGH */
810 
811 		  case CMDERROR:	/* unknown command */
812 			if (++badcommands > MAXBADCOMMANDS)
813 			{
814 				message("421 %s Too many bad commands; closing connection",
815 					MyHostName);
816 				goto doquit;
817 			}
818 
819 			message("500 Command unrecognized");
820 			break;
821 
822 		  default:
823 			errno = 0;
824 			syserr("500 smtp: unknown code %d", c->cmdcode);
825 			break;
826 		}
827 	}
828 }
829 /*
830 **  SKIPWORD -- skip a fixed word.
831 **
832 **	Parameters:
833 **		p -- place to start looking.
834 **		w -- word to skip.
835 **
836 **	Returns:
837 **		p following w.
838 **		NULL on error.
839 **
840 **	Side Effects:
841 **		clobbers the p data area.
842 */
843 
844 static char *
845 skipword(p, w)
846 	register char *p;
847 	char *w;
848 {
849 	register char *q;
850 	char *firstp = p;
851 
852 	/* find beginning of word */
853 	while (isascii(*p) && isspace(*p))
854 		p++;
855 	q = p;
856 
857 	/* find end of word */
858 	while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p)))
859 		p++;
860 	while (isascii(*p) && isspace(*p))
861 		*p++ = '\0';
862 	if (*p != ':')
863 	{
864 	  syntax:
865 		message("501 Syntax error in parameters scanning \"%s\"",
866 			firstp);
867 		Errors++;
868 		return (NULL);
869 	}
870 	*p++ = '\0';
871 	while (isascii(*p) && isspace(*p))
872 		p++;
873 
874 	if (*p == '\0')
875 		goto syntax;
876 
877 	/* see if the input word matches desired word */
878 	if (strcasecmp(q, w))
879 		goto syntax;
880 
881 	return (p);
882 }
883 /*
884 **  MAIL_ESMTP_ARGS -- process ESMTP arguments from MAIL line
885 **
886 **	Parameters:
887 **		kp -- the parameter key.
888 **		vp -- the value of that parameter.
889 **		e -- the envelope.
890 **
891 **	Returns:
892 **		none.
893 */
894 
895 mail_esmtp_args(kp, vp, e)
896 	char *kp;
897 	char *vp;
898 	ENVELOPE *e;
899 {
900 	if (strcasecmp(kp, "size") == 0)
901 	{
902 		if (vp == NULL)
903 		{
904 			usrerr("501 SIZE requires a value");
905 			/* NOTREACHED */
906 		}
907 # if defined(__STDC__) && !defined(BROKEN_ANSI_LIBRARY)
908 		e->e_msgsize = strtoul(vp, (char **) NULL, 10);
909 # else
910 		e->e_msgsize = strtol(vp, (char **) NULL, 10);
911 # endif
912 	}
913 	else if (strcasecmp(kp, "body") == 0)
914 	{
915 		if (vp == NULL)
916 		{
917 			usrerr("501 BODY requires a value");
918 			/* NOTREACHED */
919 		}
920 		if (strcasecmp(vp, "8bitmime") == 0)
921 		{
922 			SevenBitInput = FALSE;
923 			e->e_flags |= EF_NL_NOT_EOL;
924 		}
925 		else if (strcasecmp(vp, "7bit") == 0)
926 		{
927 			SevenBitInput = TRUE;
928 		}
929 		else
930 		{
931 			usrerr("501 Unknown BODY type %s",
932 				vp);
933 			/* NOTREACHED */
934 		}
935 		e->e_bodytype = newstr(vp);
936 	}
937 	else if (strcasecmp(kp, "envid") == 0)
938 	{
939 		if (vp == NULL)
940 		{
941 			usrerr("501 ENVID requires a value");
942 			/* NOTREACHED */
943 		}
944 		if (!xtextok(vp))
945 		{
946 			usrerr("501 Syntax error in ENVID parameter value");
947 			/* NOTREACHED */
948 		}
949 		if (e->e_envid != NULL)
950 		{
951 			usrerr("501 Duplicate ENVID parameter");
952 			/* NOTREACHED */
953 		}
954 		e->e_envid = newstr(vp);
955 	}
956 	else if (strcasecmp(kp, "ret") == 0)
957 	{
958 		if (vp == NULL)
959 		{
960 			usrerr("501 RET requires a value");
961 			/* NOTREACHED */
962 		}
963 		if (bitset(EF_RET_PARAM, e->e_flags))
964 		{
965 			usrerr("501 Duplicate RET parameter");
966 			/* NOTREACHED */
967 		}
968 		e->e_flags |= EF_RET_PARAM;
969 		if (strcasecmp(vp, "hdrs") == 0)
970 			e->e_flags |= EF_NO_BODY_RETN;
971 		else if (strcasecmp(vp, "full") != 0)
972 		{
973 			usrerr("501 Bad argument \"%s\" to RET", vp);
974 			/* NOTREACHED */
975 		}
976 	}
977 	else
978 	{
979 		usrerr("501 %s parameter unrecognized", kp);
980 		/* NOTREACHED */
981 	}
982 }
983 /*
984 **  RCPT_ESMTP_ARGS -- process ESMTP arguments from RCPT line
985 **
986 **	Parameters:
987 **		a -- the address corresponding to the To: parameter.
988 **		kp -- the parameter key.
989 **		vp -- the value of that parameter.
990 **		e -- the envelope.
991 **
992 **	Returns:
993 **		none.
994 */
995 
996 rcpt_esmtp_args(a, kp, vp, e)
997 	ADDRESS *a;
998 	char *kp;
999 	char *vp;
1000 	ENVELOPE *e;
1001 {
1002 	if (strcasecmp(kp, "notify") == 0)
1003 	{
1004 		char *p;
1005 
1006 		if (vp == NULL)
1007 		{
1008 			usrerr("501 NOTIFY requires a value");
1009 			/* NOTREACHED */
1010 		}
1011 		a->q_flags &= ~(QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY);
1012 		a->q_flags |= QHASNOTIFY;
1013 		if (strcasecmp(vp, "never") == 0)
1014 			return;
1015 		for (p = vp; p != NULL; vp = p)
1016 		{
1017 			p = strchr(p, ',');
1018 			if (p != NULL)
1019 				*p++ = '\0';
1020 			if (strcasecmp(vp, "success") == 0)
1021 				a->q_flags |= QPINGONSUCCESS;
1022 			else if (strcasecmp(vp, "failure") == 0)
1023 				a->q_flags |= QPINGONFAILURE;
1024 			else if (strcasecmp(vp, "delay") == 0)
1025 				a->q_flags |= QPINGONDELAY;
1026 			else
1027 			{
1028 				usrerr("501 Bad argument \"%s\"  to NOTIFY",
1029 					vp);
1030 				/* NOTREACHED */
1031 			}
1032 		}
1033 	}
1034 	else if (strcasecmp(kp, "orcpt") == 0)
1035 	{
1036 		if (vp == NULL)
1037 		{
1038 			usrerr("501 ORCPT requires a value");
1039 			/* NOTREACHED */
1040 		}
1041 		if (!xtextok(vp))
1042 		{
1043 			usrerr("501 Syntax error in ORCPT parameter value");
1044 			/* NOTREACHED */
1045 		}
1046 		if (a->q_orcpt != NULL)
1047 		{
1048 			usrerr("501 Duplicate ORCPT parameter");
1049 			/* NOTREACHED */
1050 		}
1051 		a->q_orcpt = newstr(vp);
1052 	}
1053 	else
1054 	{
1055 		usrerr("501 %s parameter unrecognized", kp);
1056 		/* NOTREACHED */
1057 	}
1058 }
1059 /*
1060 **  PRINTVRFYADDR -- print an entry in the verify queue
1061 **
1062 **	Parameters:
1063 **		a -- the address to print
1064 **		last -- set if this is the last one.
1065 **
1066 **	Returns:
1067 **		none.
1068 **
1069 **	Side Effects:
1070 **		Prints the appropriate 250 codes.
1071 */
1072 
1073 printvrfyaddr(a, last)
1074 	register ADDRESS *a;
1075 	bool last;
1076 {
1077 	char fmtbuf[20];
1078 
1079 	strcpy(fmtbuf, "250");
1080 	fmtbuf[3] = last ? ' ' : '-';
1081 
1082 	if (a->q_fullname == NULL)
1083 	{
1084 		if (strchr(a->q_user, '@') == NULL)
1085 			strcpy(&fmtbuf[4], "<%s@%s>");
1086 		else
1087 			strcpy(&fmtbuf[4], "<%s>");
1088 		message(fmtbuf, a->q_user, MyHostName);
1089 	}
1090 	else
1091 	{
1092 		if (strchr(a->q_user, '@') == NULL)
1093 			strcpy(&fmtbuf[4], "%s <%s@%s>");
1094 		else
1095 			strcpy(&fmtbuf[4], "%s <%s>");
1096 		message(fmtbuf, a->q_fullname, a->q_user, MyHostName);
1097 	}
1098 }
1099 /*
1100 **  HELP -- implement the HELP command.
1101 **
1102 **	Parameters:
1103 **		topic -- the topic we want help for.
1104 **
1105 **	Returns:
1106 **		none.
1107 **
1108 **	Side Effects:
1109 **		outputs the help file to message output.
1110 */
1111 
1112 help(topic)
1113 	char *topic;
1114 {
1115 	register FILE *hf;
1116 	int len;
1117 	bool noinfo;
1118 	char buf[MAXLINE];
1119 	extern char Version[];
1120 
1121 
1122 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
1123 	{
1124 		/* no help */
1125 		errno = 0;
1126 		message("502 Sendmail %s -- HELP not implemented", Version);
1127 		return;
1128 	}
1129 
1130 	if (topic == NULL || *topic == '\0')
1131 	{
1132 		topic = "smtp";
1133 		message("214-This is Sendmail version %s", Version);
1134 		noinfo = FALSE;
1135 	}
1136 	else
1137 	{
1138 		makelower(topic);
1139 		noinfo = TRUE;
1140 	}
1141 
1142 	len = strlen(topic);
1143 
1144 	while (fgets(buf, sizeof buf, hf) != NULL)
1145 	{
1146 		if (strncmp(buf, topic, len) == 0)
1147 		{
1148 			register char *p;
1149 
1150 			p = strchr(buf, '\t');
1151 			if (p == NULL)
1152 				p = buf;
1153 			else
1154 				p++;
1155 			fixcrlf(p, TRUE);
1156 			message("214-%s", p);
1157 			noinfo = FALSE;
1158 		}
1159 	}
1160 
1161 	if (noinfo)
1162 		message("504 HELP topic unknown");
1163 	else
1164 		message("214 End of HELP info");
1165 	(void) fclose(hf);
1166 }
1167 /*
1168 **  RUNINCHILD -- return twice -- once in the child, then in the parent again
1169 **
1170 **	Parameters:
1171 **		label -- a string used in error messages
1172 **
1173 **	Returns:
1174 **		zero in the child
1175 **		one in the parent
1176 **
1177 **	Side Effects:
1178 **		none.
1179 */
1180 
1181 runinchild(label, e)
1182 	char *label;
1183 	register ENVELOPE *e;
1184 {
1185 	int childpid;
1186 
1187 	if (!OneXact)
1188 	{
1189 		childpid = dofork();
1190 		if (childpid < 0)
1191 		{
1192 			syserr("%s: cannot fork", label);
1193 			return (1);
1194 		}
1195 		if (childpid > 0)
1196 		{
1197 			auto int st;
1198 
1199 			/* parent -- wait for child to complete */
1200 			setproctitle("server %s child wait", CurHostName);
1201 			st = waitfor(childpid);
1202 			if (st == -1)
1203 				syserr("%s: lost child", label);
1204 			else if (!WIFEXITED(st))
1205 				syserr("%s: died on signal %d",
1206 					label, st & 0177);
1207 
1208 			/* if we exited on a QUIT command, complete the process */
1209 			if (WEXITSTATUS(st) == EX_QUIT)
1210 			{
1211 				disconnect(1, e);
1212 				finis();
1213 			}
1214 
1215 			return (1);
1216 		}
1217 		else
1218 		{
1219 			/* child */
1220 			InChild = TRUE;
1221 			QuickAbort = FALSE;
1222 			clearenvelope(e, FALSE);
1223 		}
1224 	}
1225 
1226 	/* open alias database */
1227 	initmaps(FALSE, e);
1228 
1229 	return (0);
1230 }
1231 
1232 # endif /* SMTP */
1233