view usr/src/cmd/sendmail/src/conf.c @ 3966:7aaa946a7259

6542343 upgrade sendmail to 8.14.1
author jbeck
date Wed, 04 Apr 2007 16:59:29 -0700
parents 8dfb1c11a5d7
children a17d5e4e8666
line wrap: on
line source

/*
 * Copyright (c) 1998-2007 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
 * Copyright (c) 1988, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

/*
 * Copyright 1999-2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sendmail.h>

SM_RCSID("@(#)$Id: conf.c,v 8.1128 2007/04/03 21:32:29 ca Exp $")
SM_IDSTR(i2, "%W% (Sun) %G%")

#include <sm/sendmail.h>
#include <sendmail/pathnames.h>
#if NEWDB
# include "sm/bdb.h"
#endif /* NEWDB */

#include <daemon.h>
#include "map.h"

#ifdef DEC
# if NETINET6
/* for the IPv6 device lookup */
#  define _SOCKADDR_LEN
#  include <macros.h>
# endif /* NETINET6 */
#endif /* DEC */

# include <sys/ioctl.h>
# include <sys/param.h>

#include <limits.h>
#if NETINET || NETINET6
# include <arpa/inet.h>
#endif /* NETINET || NETINET6 */
#if HASULIMIT && defined(HPUX11)
# include <ulimit.h>
#endif /* HASULIMIT && defined(HPUX11) */

static void	setupmaps __P((void));
static void	setupmailers __P((void));
static void	setupqueues __P((void));
static int	get_num_procs_online __P((void));
static int	add_hostnames __P((SOCKADDR *));

#if NETINET6 && NEEDSGETIPNODE
static struct hostent *getipnodebyname __P((char *, int, int, int *));
static struct hostent *getipnodebyaddr __P((char *, int, int, int *));
#endif /* NETINET6 && NEEDSGETIPNODE */


/*
**  CONF.C -- Sendmail Configuration Tables.
**
**	Defines the configuration of this installation.
**
**	Configuration Variables:
**		HdrInfo -- a table describing well-known header fields.
**			Each entry has the field name and some flags,
**			which are described in sendmail.h.
**
**	Notes:
**		I have tried to put almost all the reasonable
**		configuration information into the configuration
**		file read at runtime.  My intent is that anything
**		here is a function of the version of UNIX you
**		are running, or is really static -- for example
**		the headers are a superset of widely used
**		protocols.  If you find yourself playing with
**		this file too much, you may be making a mistake!
*/


/*
**  Header info table
**	Final (null) entry contains the flags used for any other field.
**
**	Not all of these are actually handled specially by sendmail
**	at this time.  They are included as placeholders, to let
**	you know that "someday" I intend to have sendmail do
**	something with them.
*/

struct hdrinfo	HdrInfo[] =
{
		/* originator fields, most to least significant */
	{ "resent-sender",		H_FROM|H_RESENT,	NULL	},
	{ "resent-from",		H_FROM|H_RESENT,	NULL	},
	{ "resent-reply-to",		H_FROM|H_RESENT,	NULL	},
	{ "sender",			H_FROM,			NULL	},
	{ "from",			H_FROM,			NULL	},
	{ "reply-to",			H_FROM,			NULL	},
	{ "errors-to",			H_FROM|H_ERRORSTO,	NULL	},
	{ "full-name",			H_ACHECK,		NULL	},
	{ "return-receipt-to",		H_RECEIPTTO,		NULL	},
	{ "delivery-receipt-to",	H_RECEIPTTO,		NULL	},
	{ "disposition-notification-to",	H_FROM,		NULL	},

		/* destination fields */
	{ "to",				H_RCPT,			NULL	},
	{ "resent-to",			H_RCPT|H_RESENT,	NULL	},
	{ "cc",				H_RCPT,			NULL	},
	{ "resent-cc",			H_RCPT|H_RESENT,	NULL	},
	{ "bcc",			H_RCPT|H_BCC,		NULL	},
	{ "resent-bcc",			H_RCPT|H_BCC|H_RESENT,	NULL	},
	{ "apparently-to",		H_RCPT,			NULL	},

		/* message identification and control */
	{ "message-id",			0,			NULL	},
	{ "resent-message-id",		H_RESENT,		NULL	},
	{ "message",			H_EOH,			NULL	},
	{ "text",			H_EOH,			NULL	},

		/* date fields */
	{ "date",			0,			NULL	},
	{ "resent-date",		H_RESENT,		NULL	},

		/* trace fields */
	{ "received",			H_TRACE|H_FORCE,	NULL	},
	{ "x400-received",		H_TRACE|H_FORCE,	NULL	},
	{ "via",			H_TRACE|H_FORCE,	NULL	},
	{ "mail-from",			H_TRACE|H_FORCE,	NULL	},

		/* miscellaneous fields */
	{ "comments",			H_FORCE|H_ENCODABLE,	NULL	},
	{ "return-path",		H_FORCE|H_ACHECK|H_BINDLATE,	NULL	},
	{ "content-transfer-encoding",	H_CTE,			NULL	},
	{ "content-type",		H_CTYPE,		NULL	},
	{ "content-length",		H_ACHECK,		NULL	},
	{ "subject",			H_ENCODABLE,		NULL	},
	{ "x-authentication-warning",	H_FORCE,		NULL	},

	{ NULL,				0,			NULL	}
};



/*
**  Privacy values
*/

struct prival PrivacyValues[] =
{
	{ "public",		PRIV_PUBLIC		},
	{ "needmailhelo",	PRIV_NEEDMAILHELO	},
	{ "needexpnhelo",	PRIV_NEEDEXPNHELO	},
	{ "needvrfyhelo",	PRIV_NEEDVRFYHELO	},
	{ "noexpn",		PRIV_NOEXPN		},
	{ "novrfy",		PRIV_NOVRFY		},
	{ "restrictexpand",	PRIV_RESTRICTEXPAND	},
	{ "restrictmailq",	PRIV_RESTRICTMAILQ	},
	{ "restrictqrun",	PRIV_RESTRICTQRUN	},
	{ "noetrn",		PRIV_NOETRN		},
	{ "noverb",		PRIV_NOVERB		},
	{ "authwarnings",	PRIV_AUTHWARNINGS	},
	{ "noreceipts",		PRIV_NORECEIPTS		},
	{ "nobodyreturn",	PRIV_NOBODYRETN		},
	{ "goaway",		PRIV_GOAWAY		},
	{ "noactualrecipient",	PRIV_NOACTUALRECIPIENT	},
	{ NULL,			0			}
};

/*
**  DontBlameSendmail values
*/

struct dbsval DontBlameSendmailValues[] =
{
	{ "safe",			DBS_SAFE			},
	{ "assumesafechown",		DBS_ASSUMESAFECHOWN		},
	{ "groupwritabledirpathsafe",	DBS_GROUPWRITABLEDIRPATHSAFE	},
	{ "groupwritableforwardfilesafe",
					DBS_GROUPWRITABLEFORWARDFILESAFE },
	{ "groupwritableincludefilesafe",
					DBS_GROUPWRITABLEINCLUDEFILESAFE },
	{ "groupwritablealiasfile",	DBS_GROUPWRITABLEALIASFILE	},
	{ "worldwritablealiasfile",	DBS_WORLDWRITABLEALIASFILE	},
	{ "forwardfileinunsafedirpath",	DBS_FORWARDFILEINUNSAFEDIRPATH	},
	{ "includefileinunsafedirpath",	DBS_INCLUDEFILEINUNSAFEDIRPATH	},
	{ "mapinunsafedirpath",		DBS_MAPINUNSAFEDIRPATH	},
	{ "linkedaliasfileinwritabledir",
					DBS_LINKEDALIASFILEINWRITABLEDIR },
	{ "linkedclassfileinwritabledir",
					DBS_LINKEDCLASSFILEINWRITABLEDIR },
	{ "linkedforwardfileinwritabledir",
					DBS_LINKEDFORWARDFILEINWRITABLEDIR },
	{ "linkedincludefileinwritabledir",
					DBS_LINKEDINCLUDEFILEINWRITABLEDIR },
	{ "linkedmapinwritabledir",	DBS_LINKEDMAPINWRITABLEDIR	},
	{ "linkedserviceswitchfileinwritabledir",
					DBS_LINKEDSERVICESWITCHFILEINWRITABLEDIR },
	{ "filedeliverytohardlink",	DBS_FILEDELIVERYTOHARDLINK	},
	{ "filedeliverytosymlink",	DBS_FILEDELIVERYTOSYMLINK	},
	{ "writemaptohardlink",		DBS_WRITEMAPTOHARDLINK		},
	{ "writemaptosymlink",		DBS_WRITEMAPTOSYMLINK		},
	{ "writestatstohardlink",	DBS_WRITESTATSTOHARDLINK	},
	{ "writestatstosymlink",	DBS_WRITESTATSTOSYMLINK		},
	{ "forwardfileingroupwritabledirpath",
					DBS_FORWARDFILEINGROUPWRITABLEDIRPATH },
	{ "includefileingroupwritabledirpath",
					DBS_INCLUDEFILEINGROUPWRITABLEDIRPATH },
	{ "classfileinunsafedirpath",	DBS_CLASSFILEINUNSAFEDIRPATH	},
	{ "errorheaderinunsafedirpath",	DBS_ERRORHEADERINUNSAFEDIRPATH	},
	{ "helpfileinunsafedirpath",	DBS_HELPFILEINUNSAFEDIRPATH	},
	{ "forwardfileinunsafedirpathsafe",
					DBS_FORWARDFILEINUNSAFEDIRPATHSAFE },
	{ "includefileinunsafedirpathsafe",
					DBS_INCLUDEFILEINUNSAFEDIRPATHSAFE },
	{ "runprograminunsafedirpath",	DBS_RUNPROGRAMINUNSAFEDIRPATH	},
	{ "runwritableprogram",		DBS_RUNWRITABLEPROGRAM		},
	{ "nonrootsafeaddr",		DBS_NONROOTSAFEADDR		},
	{ "truststickybit",		DBS_TRUSTSTICKYBIT		},
	{ "dontwarnforwardfileinunsafedirpath",
					DBS_DONTWARNFORWARDFILEINUNSAFEDIRPATH },
	{ "insufficiententropy",	DBS_INSUFFICIENTENTROPY },
	{ "groupreadablesasldbfile",	DBS_GROUPREADABLESASLDBFILE	},
	{ "groupwritablesasldbfile",	DBS_GROUPWRITABLESASLDBFILE	},
	{ "groupwritableforwardfile",	DBS_GROUPWRITABLEFORWARDFILE	},
	{ "groupwritableincludefile",	DBS_GROUPWRITABLEINCLUDEFILE	},
	{ "worldwritableforwardfile",	DBS_WORLDWRITABLEFORWARDFILE	},
	{ "worldwritableincludefile",	DBS_WORLDWRITABLEINCLUDEFILE	},
	{ "groupreadablekeyfile",	DBS_GROUPREADABLEKEYFILE	},
#if _FFR_GROUPREADABLEAUTHINFOFILE
	{ "groupreadableadefaultauthinfofile",
					DBS_GROUPREADABLEAUTHINFOFILE	},
#endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
	{ NULL,				0				}
};

/*
**  Miscellaneous stuff.
*/

int	DtableSize =	50;		/* max open files; reset in 4.2bsd */
/*
**  SETDEFAULTS -- set default values
**
**	Some of these must be initialized using direct code since they
**	depend on run-time values. So let's do all of them this way.
**
**	Parameters:
**		e -- the default envelope.
**
**	Returns:
**		none.
**
**	Side Effects:
**		Initializes a bunch of global variables to their
**		default values.
*/

#define MINUTES		* 60
#define HOURS		* 60 MINUTES
#define DAYS		* 24 HOURS

#ifndef MAXRULERECURSION
# define MAXRULERECURSION	50	/* max ruleset recursion depth */
#endif /* ! MAXRULERECURSION */

void
setdefaults(e)
	register ENVELOPE *e;
{
	int i;
	int numprocs;
	struct passwd *pw;

	numprocs = get_num_procs_online();
	SpaceSub = ' ';				/* option B */
	QueueLA = 8 * numprocs;			/* option x */
	RefuseLA = 12 * numprocs;		/* option X */
	WkRecipFact = 30000L;			/* option y */
	WkClassFact = 1800L;			/* option z */
	WkTimeFact = 90000L;			/* option Z */
	QueueFactor = WkRecipFact * 20;		/* option q */
	QueueMode = QM_NORMAL;		/* what queue items to act upon */
	FileMode = (RealUid != geteuid()) ? 0644 : 0600;
						/* option F */
	QueueFileMode = (RealUid != geteuid()) ? 0644 : 0600;
						/* option QueueFileMode */

	if (((pw = sm_getpwnam("mailnull")) != NULL && pw->pw_uid != 0) ||
	    ((pw = sm_getpwnam("sendmail")) != NULL && pw->pw_uid != 0) ||
	    ((pw = sm_getpwnam("daemon")) != NULL && pw->pw_uid != 0))
	{
		DefUid = pw->pw_uid;		/* option u */
		DefGid = pw->pw_gid;		/* option g */
		DefUser = newstr(pw->pw_name);
	}
	else
	{
		DefUid = 1;			/* option u */
		DefGid = 1;			/* option g */
		setdefuser();
	}
	TrustedUid = 0;
	if (tTd(37, 4))
		sm_dprintf("setdefaults: DefUser=%s, DefUid=%d, DefGid=%d\n",
			DefUser != NULL ? DefUser : "<1:1>",
			(int) DefUid, (int) DefGid);
	CheckpointInterval = 10;		/* option C */
	MaxHopCount = 25;			/* option h */
	set_delivery_mode(SM_FORK, e);		/* option d */
	e->e_errormode = EM_PRINT;		/* option e */
	e->e_qgrp = NOQGRP;
	e->e_qdir = NOQDIR;
	e->e_xfqgrp = NOQGRP;
	e->e_xfqdir = NOQDIR;
	e->e_ctime = curtime();
	SevenBitInput = false;			/* option 7 */
	MaxMciCache = 1;			/* option k */
	MciCacheTimeout = 5 MINUTES;		/* option K */
	LogLevel = 9;				/* option L */
#if MILTER
	MilterLogLevel = -1;
#endif /* MILTER */
	inittimeouts(NULL, false);		/* option r */
	PrivacyFlags = PRIV_PUBLIC;		/* option p */
	MeToo = true;				/* option m */
	SendMIMEErrors = true;			/* option f */
	SuperSafe = SAFE_REALLY;		/* option s */
	clrbitmap(DontBlameSendmail);		/* DontBlameSendmail option */
#if MIME8TO7
	MimeMode = MM_CVTMIME|MM_PASS8BIT;	/* option 8 */
#else /* MIME8TO7 */
	MimeMode = MM_PASS8BIT;
#endif /* MIME8TO7 */
	for (i = 0; i < MAXTOCLASS; i++)
	{
		TimeOuts.to_q_return[i] = 5 DAYS;	/* option T */
		TimeOuts.to_q_warning[i] = 0;		/* option T */
	}
	ServiceSwitchFile = "/etc/mail/service.switch";
	ServiceCacheMaxAge = (time_t) 10;
	HostsFile = _PATH_HOSTS;
	PidFile = newstr(_PATH_SENDMAILPID);
	MustQuoteChars = "@,;:\\()[].'";
	MciInfoTimeout = 30 MINUTES;
	MaxRuleRecursion = MAXRULERECURSION;
	MaxAliasRecursion = 10;
	MaxMacroRecursion = 10;
	ColonOkInAddr = true;
	DontLockReadFiles = true;
	DontProbeInterfaces = DPI_PROBEALL;
	DoubleBounceAddr = "postmaster";
	MaxHeadersLength = MAXHDRSLEN;
	MaxMimeHeaderLength = MAXLINE;
	MaxMimeFieldLength = MaxMimeHeaderLength / 2;
	MaxForwardEntries = 0;
	FastSplit = 1;
	MaxNOOPCommands = MAXNOOPCOMMANDS;
#if SASL
	AuthMechanisms = newstr(AUTH_MECHANISMS);
	AuthRealm = NULL;
	MaxSLBits = INT_MAX;
#endif /* SASL */
#if STARTTLS
	TLS_Srv_Opts = TLS_I_SRV;
#endif /* STARTTLS */
#ifdef HESIOD_INIT
	HesiodContext = NULL;
#endif /* HESIOD_INIT */
#if NETINET6
	/* Detect if IPv6 is available at run time */
	i = socket(AF_INET6, SOCK_STREAM, 0);
	if (i >= 0)
	{
		InetMode = AF_INET6;
		(void) close(i);
	}
	else
		InetMode = AF_INET;
#else /* NETINET6 */
	InetMode = AF_INET;
#endif /* NETINET6 */
	ControlSocketName = NULL;
	memset(&ConnectOnlyTo, '\0', sizeof(ConnectOnlyTo));
	DataFileBufferSize = 4096;
	XscriptFileBufferSize = 4096;
	for (i = 0; i < MAXRWSETS; i++)
		RuleSetNames[i] = NULL;
#if MILTER
	InputFilters[0] = NULL;
#endif /* MILTER */
	RejectLogInterval = 3 HOURS;
#if REQUIRES_DIR_FSYNC
	RequiresDirfsync = true;
#endif /* REQUIRES_DIR_FSYNC */
	ConnectionRateWindowSize = 60;
	setupmaps();
	setupqueues();
	setupmailers();
	setupheaders();
}


/*
**  SETDEFUSER -- set/reset DefUser using DefUid (for initgroups())
*/

void
setdefuser()
{
	struct passwd *defpwent;
	static char defuserbuf[40];

	DefUser = defuserbuf;
	defpwent = sm_getpwuid(DefUid);
	(void) sm_strlcpy(defuserbuf,
			  (defpwent == NULL || defpwent->pw_name == NULL)
			   ? "nobody" : defpwent->pw_name,
			  sizeof(defuserbuf));
	if (tTd(37, 4))
		sm_dprintf("setdefuser: DefUid=%d, DefUser=%s\n",
			   (int) DefUid, DefUser);
}
/*
**  SETUPQUEUES -- initialize default queues
**
**	The mqueue QUEUE structure gets filled in after readcf() but
**	we need something to point to now for the mailer setup,
**	which use "mqueue" as default queue.
*/

static void
setupqueues()
{
	char buf[100];

	MaxRunnersPerQueue = 1;
	(void) sm_strlcpy(buf, "mqueue, P=/var/spool/mqueue", sizeof(buf));
	makequeue(buf, false);
}
/*
**  SETUPMAILERS -- initialize default mailers
*/

static void
setupmailers()
{
	char buf[100];

	(void) sm_strlcpy(buf, "prog, P=/bin/sh, F=lsouDq9, T=X-Unix/X-Unix/X-Unix, A=sh -c \201u",
			sizeof(buf));
	makemailer(buf);

	(void) sm_strlcpy(buf, "*file*, P=[FILE], F=lsDFMPEouq9, T=X-Unix/X-Unix/X-Unix, A=FILE \201u",
			sizeof(buf));
	makemailer(buf);

	(void) sm_strlcpy(buf, "*include*, P=/dev/null, F=su, A=INCLUDE \201u",
			sizeof(buf));
	makemailer(buf);
	initerrmailers();
}
/*
**  SETUPMAPS -- set up map classes
*/

#define MAPDEF(name, ext, flags, parse, open, close, lookup, store) \
	{ \
		extern bool parse __P((MAP *, char *)); \
		extern bool open __P((MAP *, int)); \
		extern void close __P((MAP *)); \
		extern char *lookup __P((MAP *, char *, char **, int *)); \
		extern void store __P((MAP *, char *, char *)); \
		s = stab(name, ST_MAPCLASS, ST_ENTER); \
		s->s_mapclass.map_cname = name; \
		s->s_mapclass.map_ext = ext; \
		s->s_mapclass.map_cflags = flags; \
		s->s_mapclass.map_parse = parse; \
		s->s_mapclass.map_open = open; \
		s->s_mapclass.map_close = close; \
		s->s_mapclass.map_lookup = lookup; \
		s->s_mapclass.map_store = store; \
	}

static void
setupmaps()
{
	register STAB *s;

#if NEWDB
# if DB_VERSION_MAJOR > 1
	int major_v, minor_v, patch_v;

	(void) db_version(&major_v, &minor_v, &patch_v);
	if (major_v != DB_VERSION_MAJOR || minor_v != DB_VERSION_MINOR)
	{
		errno = 0;
		syserr("Berkeley DB version mismatch: compiled against %d.%d.%d, run-time linked against %d.%d.%d",
		  DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH,
		  major_v, minor_v, patch_v);
	}
# endif /* DB_VERSION_MAJOR > 1 */

	MAPDEF("hash", ".db", MCF_ALIASOK|MCF_REBUILDABLE,
		map_parseargs, hash_map_open, db_map_close,
		db_map_lookup, db_map_store);

	MAPDEF("btree", ".db", MCF_ALIASOK|MCF_REBUILDABLE,
		map_parseargs, bt_map_open, db_map_close,
		db_map_lookup, db_map_store);
#endif /* NEWDB */

#if NDBM
	MAPDEF("dbm", ".dir", MCF_ALIASOK|MCF_REBUILDABLE,
		map_parseargs, ndbm_map_open, ndbm_map_close,
		ndbm_map_lookup, ndbm_map_store);
#endif /* NDBM */

#if NIS
	MAPDEF("nis", NULL, MCF_ALIASOK,
		map_parseargs, nis_map_open, null_map_close,
		nis_map_lookup, null_map_store);
#endif /* NIS */

#if NISPLUS
	MAPDEF("nisplus", NULL, MCF_ALIASOK,
		map_parseargs, nisplus_map_open, null_map_close,
		nisplus_map_lookup, null_map_store);
#endif /* NISPLUS */

#if LDAPMAP
	MAPDEF("ldap", NULL, MCF_ALIASOK|MCF_NOTPERSIST,
		ldapmap_parseargs, ldapmap_open, ldapmap_close,
		ldapmap_lookup, null_map_store);
#endif /* LDAPMAP */

#if PH_MAP
	MAPDEF("ph", NULL, MCF_NOTPERSIST,
		ph_map_parseargs, ph_map_open, ph_map_close,
		ph_map_lookup, null_map_store);
#endif /* PH_MAP */

#if MAP_NSD
	/* IRIX 6.5 nsd support */
	MAPDEF("nsd", NULL, MCF_ALIASOK,
	       map_parseargs, null_map_open, null_map_close,
	       nsd_map_lookup, null_map_store);
#endif /* MAP_NSD */

#if HESIOD
	MAPDEF("hesiod", NULL, MCF_ALIASOK|MCF_ALIASONLY,
		map_parseargs, hes_map_open, hes_map_close,
		hes_map_lookup, null_map_store);
#endif /* HESIOD */

#if NETINFO
	MAPDEF("netinfo", NULL, MCF_ALIASOK,
		map_parseargs, ni_map_open, null_map_close,
		ni_map_lookup, null_map_store);
#endif /* NETINFO */

#if 0
	MAPDEF("dns", NULL, 0,
		dns_map_init, null_map_open, null_map_close,
		dns_map_lookup, null_map_store);
#endif /* 0 */

#if NAMED_BIND
# if DNSMAP
#  if _FFR_DNSMAP_ALIASABLE
	MAPDEF("dns", NULL, MCF_ALIASOK,
	       dns_map_parseargs, dns_map_open, null_map_close,
	       dns_map_lookup, null_map_store);
#  else /* _FFR_DNSMAP_ALIASABLE */
	MAPDEF("dns", NULL, 0,
	       dns_map_parseargs, dns_map_open, null_map_close,
	       dns_map_lookup, null_map_store);
#  endif /* _FFR_DNSMAP_ALIASABLE */
# endif /* DNSMAP */
#endif /* NAMED_BIND */

#if NAMED_BIND
	/* best MX DNS lookup */
	MAPDEF("bestmx", NULL, MCF_OPTFILE,
		map_parseargs, null_map_open, null_map_close,
		bestmx_map_lookup, null_map_store);
#endif /* NAMED_BIND */

	MAPDEF("host", NULL, 0,
		host_map_init, null_map_open, null_map_close,
		host_map_lookup, null_map_store);

	MAPDEF("text", NULL, MCF_ALIASOK,
		map_parseargs, text_map_open, null_map_close,
		text_map_lookup, null_map_store);

	MAPDEF("stab", NULL, MCF_ALIASOK|MCF_ALIASONLY,
		map_parseargs, stab_map_open, null_map_close,
		stab_map_lookup, stab_map_store);

	MAPDEF("implicit", NULL, MCF_ALIASOK|MCF_ALIASONLY|MCF_REBUILDABLE,
		map_parseargs, impl_map_open, impl_map_close,
		impl_map_lookup, impl_map_store);

	/* access to system passwd file */
	MAPDEF("user", NULL, MCF_OPTFILE,
		map_parseargs, user_map_open, null_map_close,
		user_map_lookup, null_map_store);

	/* dequote map */
	MAPDEF("dequote", NULL, 0,
		dequote_init, null_map_open, null_map_close,
		dequote_map, null_map_store);

#if MAP_REGEX
	MAPDEF("regex", NULL, 0,
		regex_map_init, null_map_open, null_map_close,
		regex_map_lookup, null_map_store);
#endif /* MAP_REGEX */

#if USERDB
	/* user database */
	MAPDEF("userdb", ".db", 0,
		map_parseargs, null_map_open, null_map_close,
		udb_map_lookup, null_map_store);
#endif /* USERDB */

	/* arbitrary programs */
	MAPDEF("program", NULL, MCF_ALIASOK,
		map_parseargs, null_map_open, null_map_close,
		prog_map_lookup, null_map_store);

	/* sequenced maps */
	MAPDEF("sequence", NULL, MCF_ALIASOK,
		seq_map_parse, null_map_open, null_map_close,
		seq_map_lookup, seq_map_store);

	/* switched interface to sequenced maps */
	MAPDEF("switch", NULL, MCF_ALIASOK,
		map_parseargs, switch_map_open, null_map_close,
		seq_map_lookup, seq_map_store);

	/* null map lookup -- really for internal use only */
	MAPDEF("null", NULL, MCF_ALIASOK|MCF_OPTFILE,
		map_parseargs, null_map_open, null_map_close,
		null_map_lookup, null_map_store);

	/* syslog map -- logs information to syslog */
	MAPDEF("syslog", NULL, 0,
		syslog_map_parseargs, null_map_open, null_map_close,
		syslog_map_lookup, null_map_store);

	/* macro storage map -- rulesets can set macros */
	MAPDEF("macro", NULL, 0,
		dequote_init, null_map_open, null_map_close,
		macro_map_lookup, null_map_store);

	/* arithmetic map -- add/subtract/compare */
	MAPDEF("arith", NULL, 0,
		dequote_init, null_map_open, null_map_close,
		arith_map_lookup, null_map_store);

#if SOCKETMAP
	/* arbitrary daemons */
	MAPDEF("socket", NULL, MCF_ALIASOK,
		map_parseargs, socket_map_open, socket_map_close,
		socket_map_lookup, null_map_store);
#endif /* SOCKETMAP */

#if _FFR_DPRINTF_MAP
	/* dprintf map -- logs information to syslog */
	MAPDEF("dprintf", NULL, 0,
		dprintf_map_parseargs, null_map_open, null_map_close,
		dprintf_map_lookup, null_map_store);
#endif /* _FFR_DPRINTF_MAP */

	if (tTd(38, 2))
	{
		/* bogus map -- always return tempfail */
		MAPDEF("bogus",	NULL, MCF_ALIASOK|MCF_OPTFILE,
		       map_parseargs, null_map_open, null_map_close,
		       bogus_map_lookup, null_map_store);
	}
}

#undef MAPDEF
/*
**  INITHOSTMAPS -- initial host-dependent maps
**
**	This should act as an interface to any local service switch
**	provided by the host operating system.
**
**	Parameters:
**		none
**
**	Returns:
**		none
**
**	Side Effects:
**		Should define maps "host" and "users" as necessary
**		for this OS.  If they are not defined, they will get
**		a default value later.  It should check to make sure
**		they are not defined first, since it's possible that
**		the config file has provided an override.
*/

void
inithostmaps()
{
	register int i;
	int nmaps;
	char *maptype[MAXMAPSTACK];
	short mapreturn[MAXMAPACTIONS];
	char buf[MAXLINE];

	/*
	**  Make sure we have a host map.
	*/

	if (stab("host", ST_MAP, ST_FIND) == NULL)
	{
		/* user didn't initialize: set up host map */
		(void) sm_strlcpy(buf, "host host", sizeof(buf));
#if NAMED_BIND
		if (ConfigLevel >= 2)
			(void) sm_strlcat(buf, " -a. -D", sizeof(buf));
#endif /* NAMED_BIND */
		(void) makemapentry(buf);
	}

	/*
	**  Set up default aliases maps
	*/

	nmaps = switch_map_find("aliases", maptype, mapreturn);
	for (i = 0; i < nmaps; i++)
	{
		if (strcmp(maptype[i], "files") == 0 &&
		    stab("aliases.files", ST_MAP, ST_FIND) == NULL)
		{
			(void) sm_strlcpy(buf, "aliases.files null",
					  sizeof(buf));
			(void) makemapentry(buf);
		}
#if NISPLUS
		else if (strcmp(maptype[i], "nisplus") == 0 &&
			 stab("aliases.nisplus", ST_MAP, ST_FIND) == NULL)
		{
			(void) sm_strlcpy(buf, "aliases.nisplus nisplus -kalias -vexpansion mail_aliases.org_dir",
				sizeof(buf));
			(void) makemapentry(buf);
		}
#endif /* NISPLUS */
#if NIS
		else if (strcmp(maptype[i], "nis") == 0 &&
			 stab("aliases.nis", ST_MAP, ST_FIND) == NULL)
		{
			(void) sm_strlcpy(buf, "aliases.nis nis mail.aliases",
				sizeof(buf));
			(void) makemapentry(buf);
		}
#endif /* NIS */
#if NETINFO
		else if (strcmp(maptype[i], "netinfo") == 0 &&
			 stab("aliases.netinfo", ST_MAP, ST_FIND) == NULL)
		{
			(void) sm_strlcpy(buf, "aliases.netinfo netinfo -z, /aliases",
				sizeof(buf));
			(void) makemapentry(buf);
		}
#endif /* NETINFO */
#if HESIOD
		else if (strcmp(maptype[i], "hesiod") == 0 &&
			 stab("aliases.hesiod", ST_MAP, ST_FIND) == NULL)
		{
			(void) sm_strlcpy(buf, "aliases.hesiod hesiod aliases",
				sizeof(buf));
			(void) makemapentry(buf);
		}
#endif /* HESIOD */
#if LDAPMAP && defined(SUN_EXTENSIONS) && \
    defined(SUN_SIMPLIFIED_LDAP) && HASLDAPGETALIASBYNAME
		else if (strcmp(maptype[i], "ldap") == 0 &&
		    stab("aliases.ldap", ST_MAP, ST_FIND) == NULL)
		{
			(void) strlcpy(buf, "aliases.ldap ldap -b . -h localhost -k mail=%0 -v mailgroup",
				sizeof buf);
			(void) makemapentry(buf);
		}
#endif /* LDAPMAP && defined(SUN_EXTENSIONS) && ... */
	}
	if (stab("aliases", ST_MAP, ST_FIND) == NULL)
	{
		(void) sm_strlcpy(buf, "aliases switch aliases", sizeof(buf));
		(void) makemapentry(buf);
	}
}

/*
**  SWITCH_MAP_FIND -- find the list of types associated with a map
**
**	This is the system-dependent interface to the service switch.
**
**	Parameters:
**		service -- the name of the service of interest.
**		maptype -- an out-array of strings containing the types
**			of access to use for this service.  There can
**			be at most MAXMAPSTACK types for a single service.
**		mapreturn -- an out-array of return information bitmaps
**			for the map.
**
**	Returns:
**		The number of map types filled in, or -1 for failure.
**
**	Side effects:
**		Preserves errno so nothing in the routine clobbers it.
*/

#if defined(SOLARIS) || (defined(sony_news) && defined(__svr4))
# define _USE_SUN_NSSWITCH_
#endif /* defined(SOLARIS) || (defined(sony_news) && defined(__svr4)) */

#if _FFR_HPUX_NSSWITCH
# ifdef __hpux
#  define _USE_SUN_NSSWITCH_
# endif /* __hpux */
#endif /* _FFR_HPUX_NSSWITCH */

#ifdef _USE_SUN_NSSWITCH_
# include <nsswitch.h>
#endif /* _USE_SUN_NSSWITCH_ */

#if defined(ultrix) || (defined(__osf__) && defined(__alpha))
# define _USE_DEC_SVC_CONF_
#endif /* defined(ultrix) || (defined(__osf__) && defined(__alpha)) */

#ifdef _USE_DEC_SVC_CONF_
# include <sys/svcinfo.h>
#endif /* _USE_DEC_SVC_CONF_ */

int
switch_map_find(service, maptype, mapreturn)
	char *service;
	char *maptype[MAXMAPSTACK];
	short mapreturn[MAXMAPACTIONS];
{
	int svcno = 0;
	int save_errno = errno;

#ifdef _USE_SUN_NSSWITCH_
	struct __nsw_switchconfig *nsw_conf;
	enum __nsw_parse_err pserr;
	struct __nsw_lookup *lk;
	static struct __nsw_lookup lkp0 =
		{ "files", {1, 0, 0, 0}, NULL, NULL };
	static struct __nsw_switchconfig lkp_default =
		{ 0, "sendmail", 3, &lkp0 };

	for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
		mapreturn[svcno] = 0;

	if ((nsw_conf = __nsw_getconfig(service, &pserr)) == NULL)
		lk = lkp_default.lookups;
	else
		lk = nsw_conf->lookups;
	svcno = 0;
	while (lk != NULL && svcno < MAXMAPSTACK)
	{
		maptype[svcno] = lk->service_name;
		if (lk->actions[__NSW_NOTFOUND] == __NSW_RETURN)
			mapreturn[MA_NOTFOUND] |= 1 << svcno;
		if (lk->actions[__NSW_TRYAGAIN] == __NSW_RETURN)
			mapreturn[MA_TRYAGAIN] |= 1 << svcno;
		if (lk->actions[__NSW_UNAVAIL] == __NSW_RETURN)
			mapreturn[MA_TRYAGAIN] |= 1 << svcno;
		svcno++;
		lk = lk->next;
	}
	errno = save_errno;
	return svcno;
#endif /* _USE_SUN_NSSWITCH_ */

#ifdef _USE_DEC_SVC_CONF_
	struct svcinfo *svcinfo;
	int svc;

	for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
		mapreturn[svcno] = 0;

	svcinfo = getsvc();
	if (svcinfo == NULL)
		goto punt;
	if (strcmp(service, "hosts") == 0)
		svc = SVC_HOSTS;
	else if (strcmp(service, "aliases") == 0)
		svc = SVC_ALIASES;
	else if (strcmp(service, "passwd") == 0)
		svc = SVC_PASSWD;
	else
	{
		errno = save_errno;
		return -1;
	}
	for (svcno = 0; svcno < SVC_PATHSIZE && svcno < MAXMAPSTACK; svcno++)
	{
		switch (svcinfo->svcpath[svc][svcno])
		{
		  case SVC_LOCAL:
			maptype[svcno] = "files";
			break;

		  case SVC_YP:
			maptype[svcno] = "nis";
			break;

		  case SVC_BIND:
			maptype[svcno] = "dns";
			break;

# ifdef SVC_HESIOD
		  case SVC_HESIOD:
			maptype[svcno] = "hesiod";
			break;
# endif /* SVC_HESIOD */

		  case SVC_LAST:
			errno = save_errno;
			return svcno;
		}
	}
	errno = save_errno;
	return svcno;
#endif /* _USE_DEC_SVC_CONF_ */

#if !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_)
	/*
	**  Fall-back mechanism.
	*/

	STAB *st;
	static time_t servicecachetime;	/* time service switch was cached */
	time_t now = curtime();

	for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
		mapreturn[svcno] = 0;

	if ((now - servicecachetime) > (time_t) ServiceCacheMaxAge)
	{
		/* (re)read service switch */
		register SM_FILE_T *fp;
		long sff = SFF_REGONLY|SFF_OPENASROOT|SFF_NOLOCK;

		if (!bitnset(DBS_LINKEDSERVICESWITCHFILEINWRITABLEDIR,
			    DontBlameSendmail))
			sff |= SFF_NOWLINK;

		if (ConfigFileRead)
			servicecachetime = now;
		fp = safefopen(ServiceSwitchFile, O_RDONLY, 0, sff);
		if (fp != NULL)
		{
			char buf[MAXLINE];

			while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf,
					   sizeof(buf)) != NULL)
			{
				register char *p;

				p = strpbrk(buf, "#\n");
				if (p != NULL)
					*p = '\0';
				p = strpbrk(buf, " \t");
				if (p != NULL)
					*p++ = '\0';
				if (buf[0] == '\0')
					continue;
				if (p == NULL)
				{
					sm_syslog(LOG_ERR, NOQID,
						  "Bad line on %.100s: %.100s",
						  ServiceSwitchFile,
						  buf);
					continue;
				}
				while (isspace(*p))
					p++;
				if (*p == '\0')
					continue;

				/*
				**  Find/allocate space for this service entry.
				**	Space for all of the service strings
				**	are allocated at once.  This means
				**	that we only have to free the first
				**	one to free all of them.
				*/

				st = stab(buf, ST_SERVICE, ST_ENTER);
				if (st->s_service[0] != NULL)
					sm_free((void *) st->s_service[0]); /* XXX */
				p = newstr(p);
				for (svcno = 0; svcno < MAXMAPSTACK; )
				{
					if (*p == '\0')
						break;
					st->s_service[svcno++] = p;
					p = strpbrk(p, " \t");
					if (p == NULL)
						break;
					*p++ = '\0';
					while (isspace(*p))
						p++;
				}
				if (svcno < MAXMAPSTACK)
					st->s_service[svcno] = NULL;
			}
			(void) sm_io_close(fp, SM_TIME_DEFAULT);
		}
	}

	/* look up entry in cache */
	st = stab(service, ST_SERVICE, ST_FIND);
	if (st != NULL && st->s_service[0] != NULL)
	{
		/* extract data */
		svcno = 0;
		while (svcno < MAXMAPSTACK)
		{
			maptype[svcno] = st->s_service[svcno];
			if (maptype[svcno++] == NULL)
				break;
		}
		errno = save_errno;
		return --svcno;
	}
#endif /* !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_) */

#if !defined(_USE_SUN_NSSWITCH_)
	/* if the service file doesn't work, use an absolute fallback */
# ifdef _USE_DEC_SVC_CONF_
  punt:
# endif /* _USE_DEC_SVC_CONF_ */
	for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
		mapreturn[svcno] = 0;
	svcno = 0;
	if (strcmp(service, "aliases") == 0)
	{
		maptype[svcno++] = "files";
# if defined(AUTO_NETINFO_ALIASES) && defined (NETINFO)
		maptype[svcno++] = "netinfo";
# endif /* defined(AUTO_NETINFO_ALIASES) && defined (NETINFO) */
# ifdef AUTO_NIS_ALIASES
#  if NISPLUS
		maptype[svcno++] = "nisplus";
#  endif /* NISPLUS */
#  if NIS
		maptype[svcno++] = "nis";
#  endif /* NIS */
# endif /* AUTO_NIS_ALIASES */
		errno = save_errno;
		return svcno;
	}
	if (strcmp(service, "hosts") == 0)
	{
# if NAMED_BIND
		maptype[svcno++] = "dns";
# else /* NAMED_BIND */
#  if defined(sun) && !defined(BSD)
		/* SunOS */
		maptype[svcno++] = "nis";
#  endif /* defined(sun) && !defined(BSD) */
# endif /* NAMED_BIND */
# if defined(AUTO_NETINFO_HOSTS) && defined (NETINFO)
		maptype[svcno++] = "netinfo";
# endif /* defined(AUTO_NETINFO_HOSTS) && defined (NETINFO) */
		maptype[svcno++] = "files";
		errno = save_errno;
		return svcno;
	}
	errno = save_errno;
	return -1;
#endif /* !defined(_USE_SUN_NSSWITCH_) */
}
/*
**  USERNAME -- return the user id of the logged in user.
**
**	Parameters:
**		none.
**
**	Returns:
**		The login name of the logged in user.
**
**	Side Effects:
**		none.
**
**	Notes:
**		The return value is statically allocated.
*/

char *
username()
{
	static char *myname = NULL;
	extern char *getlogin();
	register struct passwd *pw;

	/* cache the result */
	if (myname == NULL)
	{
		myname = getlogin();
		if (myname == NULL || myname[0] == '\0')
		{
			pw = sm_getpwuid(RealUid);
			if (pw != NULL)
				myname = pw->pw_name;
		}
		else
		{
			uid_t uid = RealUid;

			if ((pw = sm_getpwnam(myname)) == NULL ||
			      (uid != 0 && uid != pw->pw_uid))
			{
				pw = sm_getpwuid(uid);
				if (pw != NULL)
					myname = pw->pw_name;
			}
		}
		if (myname == NULL || myname[0] == '\0')
		{
			syserr("554 5.3.0 Who are you?");
			myname = "postmaster";
		}
		else if (strpbrk(myname, ",;:/|\"\\") != NULL)
			myname = addquotes(myname, NULL);
		else
			myname = sm_pstrdup_x(myname);
	}
	return myname;
}
/*
**  TTYPATH -- Get the path of the user's tty
**
**	Returns the pathname of the user's tty.  Returns NULL if
**	the user is not logged in or if s/he has write permission
**	denied.
**
**	Parameters:
**		none
**
**	Returns:
**		pathname of the user's tty.
**		NULL if not logged in or write permission denied.
**
**	Side Effects:
**		none.
**
**	WARNING:
**		Return value is in a local buffer.
**
**	Called By:
**		savemail
*/

char *
ttypath()
{
	struct stat stbuf;
	register char *pathn;
	extern char *ttyname();
	extern char *getlogin();

	/* compute the pathname of the controlling tty */
	if ((pathn = ttyname(2)) == NULL && (pathn = ttyname(1)) == NULL &&
	    (pathn = ttyname(0)) == NULL)
	{
		errno = 0;
		return NULL;
	}

	/* see if we have write permission */
	if (stat(pathn, &stbuf) < 0 || !bitset(S_IWOTH, stbuf.st_mode))
	{
		errno = 0;
		return NULL;
	}

	/* see if the user is logged in */
	if (getlogin() == NULL)
		return NULL;

	/* looks good */
	return pathn;
}
/*
**  CHECKCOMPAT -- check for From and To person compatible.
**
**	This routine can be supplied on a per-installation basis
**	to determine whether a person is allowed to send a message.
**	This allows restriction of certain types of internet
**	forwarding or registration of users.
**
**	If the hosts are found to be incompatible, an error
**	message should be given using "usrerr" and an EX_ code
**	should be returned.  You can also set to->q_status to
**	a DSN-style status code.
**
**	EF_NO_BODY_RETN can be set in e->e_flags to suppress the
**	body during the return-to-sender function; this should be done
**	on huge messages.  This bit may already be set by the ESMTP
**	protocol.
**
**	Parameters:
**		to -- the person being sent to.
**
**	Returns:
**		an exit status
**
**	Side Effects:
**		none (unless you include the usrerr stuff)
*/

int
checkcompat(to, e)
	register ADDRESS *to;
	register ENVELOPE *e;
{
	if (tTd(49, 1))
		sm_dprintf("checkcompat(to=%s, from=%s)\n",
			to->q_paddr, e->e_from.q_paddr);

#ifdef EXAMPLE_CODE
	/* this code is intended as an example only */
	register STAB *s;

	s = stab("arpa", ST_MAILER, ST_FIND);
	if (s != NULL && strcmp(e->e_from.q_mailer->m_name, "local") != 0 &&
	    to->q_mailer == s->s_mailer)
	{
		usrerr("553 No ARPA mail through this machine: see your system administration");
		/* e->e_flags |= EF_NO_BODY_RETN; to suppress body on return */
		to->q_status = "5.7.1";
		return EX_UNAVAILABLE;
	}
#endif /* EXAMPLE_CODE */
	return EX_OK;
}

#ifdef SUN_EXTENSIONS
static void
init_md_sun()
{
	struct stat sbuf;

	/* Check for large file descriptor */
	if (fstat(fileno(stdin), &sbuf) < 0)
	{
		if (errno == EOVERFLOW)
		{
			perror("stdin");
			exit(EX_NOINPUT);
		}
	}
}
#endif /* SUN_EXTENSIONS */

/*
**  INIT_MD -- do machine dependent initializations
**
**	Systems that have global modes that should be set should do
**	them here rather than in main.
*/

#ifdef _AUX_SOURCE
# include <compat.h>
#endif /* _AUX_SOURCE */

#if SHARE_V1
# include <shares.h>
#endif /* SHARE_V1 */

void
init_md(argc, argv)
	int argc;
	char **argv;
{
#ifdef _AUX_SOURCE
	setcompat(getcompat() | COMPAT_BSDPROT);
#endif /* _AUX_SOURCE */

#ifdef SUN_EXTENSIONS
	init_md_sun();
#endif /* SUN_EXTENSIONS */

#if _CONVEX_SOURCE
	/* keep gethostby*() from stripping the local domain name */
	set_domain_trim_off();
#endif /* _CONVEX_SOURCE */
#if defined(__QNX__) && !defined(__QNXNTO__)
	/*
	**  Due to QNX's network distributed nature, you can target a tcpip
	**  stack on a different node in the qnx network; this patch lets
	**  this feature work.  The __sock_locate() must be done before the
	**  environment is clear.
	*/
	__sock_locate();
#endif /* __QNX__ */
#if SECUREWARE || defined(_SCO_unix_)
	set_auth_parameters(argc, argv);

# ifdef _SCO_unix_
	/*
	**  This is required for highest security levels (the kernel
	**  won't let it call set*uid() or run setuid binaries without
	**  it).  It may be necessary on other SECUREWARE systems.
	*/

	if (getluid() == -1)
		setluid(0);
# endif /* _SCO_unix_ */
#endif /* SECUREWARE || defined(_SCO_unix_) */


#ifdef VENDOR_DEFAULT
	VendorCode = VENDOR_DEFAULT;
#else /* VENDOR_DEFAULT */
	VendorCode = VENDOR_BERKELEY;
#endif /* VENDOR_DEFAULT */
}
/*
**  INIT_VENDOR_MACROS -- vendor-dependent macro initializations
**
**	Called once, on startup.
**
**	Parameters:
**		e -- the global envelope.
**
**	Returns:
**		none.
**
**	Side Effects:
**		vendor-dependent.
*/

void
init_vendor_macros(e)
	register ENVELOPE *e;
{
}
/*
**  GETLA -- get the current load average
**
**	This code stolen from la.c.
**
**	Parameters:
**		none.
**
**	Returns:
**		The current load average as an integer.
**
**	Side Effects:
**		none.
*/

/* try to guess what style of load average we have */
#define LA_ZERO		1	/* always return load average as zero */
#define LA_INT		2	/* read kmem for avenrun; interpret as long */
#define LA_FLOAT	3	/* read kmem for avenrun; interpret as float */
#define LA_SUBR		4	/* call getloadavg */
#define LA_MACH		5	/* MACH load averages (as on NeXT boxes) */
#define LA_SHORT	6	/* read kmem for avenrun; interpret as short */
#define LA_PROCSTR	7	/* read string ("1.17") from /proc/loadavg */
#define LA_READKSYM	8	/* SVR4: use MIOC_READKSYM ioctl call */
#define LA_DGUX		9	/* special DGUX implementation */
#define LA_HPUX		10	/* special HPUX implementation */
#define LA_IRIX6	11	/* special IRIX 6.2 implementation */
#define LA_KSTAT	12	/* special Solaris kstat(3k) implementation */
#define LA_DEVSHORT	13	/* read short from a device */
#define LA_ALPHAOSF	14	/* Digital UNIX (OSF/1 on Alpha) table() call */
#define LA_PSET		15	/* Solaris per-processor-set load average */
#define LA_LONGLONG	17 /* read kmem for avenrun; interpret as long long */

/* do guesses based on general OS type */
#ifndef LA_TYPE
# define LA_TYPE	LA_ZERO
#endif /* ! LA_TYPE */

#ifndef FSHIFT
# if defined(unixpc)
#  define FSHIFT	5
# endif /* defined(unixpc) */

# if defined(__alpha) || defined(IRIX)
#  define FSHIFT	10
# endif /* defined(__alpha) || defined(IRIX) */

#endif /* ! FSHIFT */

#ifndef FSHIFT
# define FSHIFT		8
#endif /* ! FSHIFT */

#ifndef FSCALE
# define FSCALE		(1 << FSHIFT)
#endif /* ! FSCALE */

#ifndef LA_AVENRUN
# ifdef SYSTEM5
#  define LA_AVENRUN	"avenrun"
# else /* SYSTEM5 */
#  define LA_AVENRUN	"_avenrun"
# endif /* SYSTEM5 */
#endif /* ! LA_AVENRUN */

/* _PATH_KMEM should be defined in <paths.h> */
#ifndef _PATH_KMEM
# define _PATH_KMEM	"/dev/kmem"
#endif /* ! _PATH_KMEM */

#if (LA_TYPE == LA_INT) || (LA_TYPE == LA_FLOAT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG)

# include <nlist.h>

/* _PATH_UNIX should be defined in <paths.h> */
# ifndef _PATH_UNIX
#  if defined(SYSTEM5)
#   define _PATH_UNIX	"/unix"
#  else /* defined(SYSTEM5) */
#   define _PATH_UNIX	"/vmunix"
#  endif /* defined(SYSTEM5) */
# endif /* ! _PATH_UNIX */

# ifdef _AUX_SOURCE
struct nlist	Nl[2];
# else /* _AUX_SOURCE */
struct nlist	Nl[] =
{
	{ LA_AVENRUN },
	{ 0 },
};
# endif /* _AUX_SOURCE */
# define X_AVENRUN	0

int
getla()
{
	int j;
	static int kmem = -1;
# if LA_TYPE == LA_INT
	long avenrun[3];
# else /* LA_TYPE == LA_INT */
#  if LA_TYPE == LA_SHORT
	short avenrun[3];
#  else
#   if LA_TYPE == LA_LONGLONG
	long long avenrun[3];
#   else /* LA_TYPE == LA_LONGLONG */
	double avenrun[3];
#   endif /* LA_TYPE == LA_LONGLONG */
#  endif /* LA_TYPE == LA_SHORT */
# endif /* LA_TYPE == LA_INT */
	extern off_t lseek();

	if (kmem < 0)
	{
# ifdef _AUX_SOURCE
		(void) sm_strlcpy(Nl[X_AVENRUN].n_name, LA_AVENRUN,
			       sizeof(Nl[X_AVENRUN].n_name));
		Nl[1].n_name[0] = '\0';
# endif /* _AUX_SOURCE */

# if defined(_AIX3) || defined(_AIX4)
		if (knlist(Nl, 1, sizeof(Nl[0])) < 0)
# else /* defined(_AIX3) || defined(_AIX4) */
		if (nlist(_PATH_UNIX, Nl) < 0)
# endif /* defined(_AIX3) || defined(_AIX4) */
		{
			if (tTd(3, 1))
				sm_dprintf("getla: nlist(%s): %s\n", _PATH_UNIX,
					   sm_errstring(errno));
			return -1;
		}
		if (Nl[X_AVENRUN].n_value == 0)
		{
			if (tTd(3, 1))
				sm_dprintf("getla: nlist(%s, %s) ==> 0\n",
					_PATH_UNIX, LA_AVENRUN);
			return -1;
		}
# ifdef NAMELISTMASK
		Nl[X_AVENRUN].n_value &= NAMELISTMASK;
# endif /* NAMELISTMASK */

		kmem = open(_PATH_KMEM, 0, 0);
		if (kmem < 0)
		{
			if (tTd(3, 1))
				sm_dprintf("getla: open(/dev/kmem): %s\n",
					   sm_errstring(errno));
			return -1;
		}
		if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
		    fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
		{
			if (tTd(3, 1))
				sm_dprintf("getla: fcntl(/dev/kmem, FD_CLOEXEC): %s\n",
					   sm_errstring(errno));
			(void) close(kmem);
			kmem = -1;
			return -1;
		}
	}
	if (tTd(3, 20))
		sm_dprintf("getla: symbol address = %#lx\n",
			(unsigned long) Nl[X_AVENRUN].n_value);
	if (lseek(kmem, (off_t) Nl[X_AVENRUN].n_value, SEEK_SET) == -1 ||
	    read(kmem, (char *) avenrun, sizeof(avenrun)) < sizeof(avenrun))
	{
		/* thank you Ian */
		if (tTd(3, 1))
			sm_dprintf("getla: lseek or read: %s\n",
				   sm_errstring(errno));
		return -1;
	}
# if (LA_TYPE == LA_INT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG)
	if (tTd(3, 5))
	{
#  if LA_TYPE == LA_SHORT
		sm_dprintf("getla: avenrun = %d", avenrun[0]);
		if (tTd(3, 15))
			sm_dprintf(", %d, %d", avenrun[1], avenrun[2]);
#  else /* LA_TYPE == LA_SHORT */
#   if LA_TYPE == LA_LONGLONG
		sm_dprintf("getla: avenrun = %lld", avenrun[0]);
		if (tTd(3, 15))
			sm_dprintf(", %lld, %lld", avenrun[1], avenrun[2]);
#   else /* LA_TYPE == LA_LONGLONG */
		sm_dprintf("getla: avenrun = %ld", avenrun[0]);
		if (tTd(3, 15))
			sm_dprintf(", %ld, %ld", avenrun[1], avenrun[2]);
#   endif /* LA_TYPE == LA_LONGLONG */
#  endif /* LA_TYPE == LA_SHORT */
		sm_dprintf("\n");
	}
	if (tTd(3, 1))
		sm_dprintf("getla: %d\n",
			(int) (avenrun[0] + FSCALE/2) >> FSHIFT);
	return ((int) (avenrun[0] + FSCALE/2) >> FSHIFT);
# else /* (LA_TYPE == LA_INT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG) */
	if (tTd(3, 5))
	{
		sm_dprintf("getla: avenrun = %g", avenrun[0]);
		if (tTd(3, 15))
			sm_dprintf(", %g, %g", avenrun[1], avenrun[2]);
		sm_dprintf("\n");
	}
	if (tTd(3, 1))
		sm_dprintf("getla: %d\n", (int) (avenrun[0] +0.5));
	return ((int) (avenrun[0] + 0.5));
# endif /* (LA_TYPE == LA_INT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG) */
}

#endif /* (LA_TYPE == LA_INT) || (LA_TYPE == LA_FLOAT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG) */

#if LA_TYPE == LA_READKSYM

# include <sys/ksym.h>

int
getla()
{
	int j;
	static int kmem = -1;
	long avenrun[3];
	struct mioc_rksym mirk;

	if (kmem < 0)
	{
		kmem = open("/dev/kmem", 0, 0);
		if (kmem < 0)
		{
			if (tTd(3, 1))
				sm_dprintf("getla: open(/dev/kmem): %s\n",
					   sm_errstring(errno));
			return -1;
		}
		if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
		    fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
		{
			if (tTd(3, 1))
				sm_dprintf("getla: fcntl(/dev/kmem, FD_CLOEXEC): %s\n",
					   sm_errstring(errno));
			(void) close(kmem);
			kmem = -1;
			return -1;
		}
	}
	mirk.mirk_symname = LA_AVENRUN;
	mirk.mirk_buf = avenrun;
	mirk.mirk_buflen = sizeof(avenrun);
	if (ioctl(kmem, MIOC_READKSYM, &mirk) < 0)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: ioctl(MIOC_READKSYM) failed: %s\n",
				   sm_errstring(errno));
		return -1;
	}
	if (tTd(3, 5))
	{
		sm_dprintf("getla: avenrun = %d", avenrun[0]);
		if (tTd(3, 15))
			sm_dprintf(", %d, %d", avenrun[1], avenrun[2]);
		sm_dprintf("\n");
	}
	if (tTd(3, 1))
		sm_dprintf("getla: %d\n",
			(int) (avenrun[0] + FSCALE/2) >> FSHIFT);
	return ((int) (avenrun[0] + FSCALE/2) >> FSHIFT);
}

#endif /* LA_TYPE == LA_READKSYM */

#if LA_TYPE == LA_DGUX

# include <sys/dg_sys_info.h>

int
getla()
{
	struct dg_sys_info_load_info load_info;

	dg_sys_info((long *)&load_info,
		DG_SYS_INFO_LOAD_INFO_TYPE, DG_SYS_INFO_LOAD_VERSION_0);

	if (tTd(3, 1))
		sm_dprintf("getla: %d\n", (int) (load_info.one_minute + 0.5));

	return ((int) (load_info.one_minute + 0.5));
}

#endif /* LA_TYPE == LA_DGUX */

#if LA_TYPE == LA_HPUX

/* forward declarations to keep gcc from complaining */
struct pst_dynamic;
struct pst_status;
struct pst_static;
struct pst_vminfo;
struct pst_diskinfo;
struct pst_processor;
struct pst_lv;
struct pst_swapinfo;

# include <sys/param.h>
# include <sys/pstat.h>

int
getla()
{
	struct pst_dynamic pstd;

	if (pstat_getdynamic(&pstd, sizeof(struct pst_dynamic),
			     (size_t) 1, 0) == -1)
		return 0;

	if (tTd(3, 1))
		sm_dprintf("getla: %d\n", (int) (pstd.psd_avg_1_min + 0.5));

	return (int) (pstd.psd_avg_1_min + 0.5);
}

#endif /* LA_TYPE == LA_HPUX */

#if LA_TYPE == LA_SUBR

int
getla()
{
	double avenrun[3];

	if (getloadavg(avenrun, sizeof(avenrun) / sizeof(avenrun[0])) < 0)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: getloadavg failed: %s",
				   sm_errstring(errno));
		return -1;
	}
	if (tTd(3, 1))
		sm_dprintf("getla: %d\n", (int) (avenrun[0] +0.5));
	return ((int) (avenrun[0] + 0.5));
}

#endif /* LA_TYPE == LA_SUBR */

#if LA_TYPE == LA_MACH

/*
**  This has been tested on NEXTSTEP release 2.1/3.X.
*/

# if defined(NX_CURRENT_COMPILER_RELEASE) && NX_CURRENT_COMPILER_RELEASE > NX_COMPILER_RELEASE_3_0
#  include <mach/mach.h>
# else /* defined(NX_CURRENT_COMPILER_RELEASE) && NX_CURRENT_COMPILER_RELEASE > NX_COMPILER_RELEASE_3_0 */
#  include <mach.h>
# endif /* defined(NX_CURRENT_COMPILER_RELEASE) && NX_CURRENT_COMPILER_RELEASE > NX_COMPILER_RELEASE_3_0 */

int
getla()
{
	processor_set_t default_set;
	kern_return_t error;
	unsigned int info_count;
	struct processor_set_basic_info info;
	host_t host;

	error = processor_set_default(host_self(), &default_set);
	if (error != KERN_SUCCESS)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: processor_set_default failed: %s",
				   sm_errstring(errno));
		return -1;
	}
	info_count = PROCESSOR_SET_BASIC_INFO_COUNT;
	if (processor_set_info(default_set, PROCESSOR_SET_BASIC_INFO,
			       &host, (processor_set_info_t)&info,
			       &info_count) != KERN_SUCCESS)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: processor_set_info failed: %s",
				   sm_errstring(errno));
		return -1;
	}
	if (tTd(3, 1))
		sm_dprintf("getla: %d\n",
			(int) ((info.load_average + (LOAD_SCALE / 2)) /
			       LOAD_SCALE));
	return (int) (info.load_average + (LOAD_SCALE / 2)) / LOAD_SCALE;
}

#endif /* LA_TYPE == LA_MACH */

#if LA_TYPE == LA_PROCSTR
# if SM_CONF_BROKEN_STRTOD
	ERROR: This OS has most likely a broken strtod() implemenentation.
	ERROR: The function is required for getla().
	ERROR: Check the compilation options _LA_PROCSTR and
	ERROR: _SM_CONF_BROKEN_STRTOD (without the leading _).
# endif /* SM_CONF_BROKEN_STRTOD */

/*
**  Read /proc/loadavg for the load average.  This is assumed to be
**  in a format like "0.15 0.12 0.06".
**
**	Initially intended for Linux.  This has been in the kernel
**	since at least 0.99.15.
*/

# ifndef _PATH_LOADAVG
#  define _PATH_LOADAVG	"/proc/loadavg"
# endif /* ! _PATH_LOADAVG */

int
getla()
{
	double avenrun;
	register int result;
	SM_FILE_T *fp;

	fp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, _PATH_LOADAVG, SM_IO_RDONLY,
			NULL);
	if (fp == NULL)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: sm_io_open(%s): %s\n",
				   _PATH_LOADAVG, sm_errstring(errno));
		return -1;
	}
	result = sm_io_fscanf(fp, SM_TIME_DEFAULT, "%lf", &avenrun);
	(void) sm_io_close(fp, SM_TIME_DEFAULT);
	if (result != 1)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: sm_io_fscanf() = %d: %s\n",
				   result, sm_errstring(errno));
		return -1;
	}

	if (tTd(3, 1))
		sm_dprintf("getla(): %.2f\n", avenrun);

	return ((int) (avenrun + 0.5));
}

#endif /* LA_TYPE == LA_PROCSTR */

#if LA_TYPE == LA_IRIX6

# include <sys/sysmp.h>

# ifdef _UNICOSMP
#  define CAST_SYSMP(x)	(x)
# else /* _UNICOSMP */
#  define CAST_SYSMP(x)	((x) & 0x7fffffff)
# endif /* _UNICOSMP */

int
getla(void)
{
	int j;
	static int kmem = -1;
	int avenrun[3];

	if (kmem < 0)
	{
		kmem = open(_PATH_KMEM, 0, 0);
		if (kmem < 0)
		{
			if (tTd(3, 1))
				sm_dprintf("getla: open(%s): %s\n", _PATH_KMEM,
					   sm_errstring(errno));
			return -1;
		}
		if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
		    fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
		{
			if (tTd(3, 1))
				sm_dprintf("getla: fcntl(/dev/kmem, FD_CLOEXEC): %s\n",
					   sm_errstring(errno));
			(void) close(kmem);
			kmem = -1;
			return -1;
		}
	}

	if (lseek(kmem, CAST_SYSMP(sysmp(MP_KERNADDR, MPKA_AVENRUN)), SEEK_SET)
		== -1 ||
	    read(kmem, (char *) avenrun, sizeof(avenrun)) < sizeof(avenrun))
	{
		if (tTd(3, 1))
			sm_dprintf("getla: lseek or read: %s\n",
				   sm_errstring(errno));
		return -1;
	}
	if (tTd(3, 5))
	{
		sm_dprintf("getla: avenrun = %ld", (long int) avenrun[0]);
		if (tTd(3, 15))
			sm_dprintf(", %ld, %ld",
				(long int) avenrun[1], (long int) avenrun[2]);
		sm_dprintf("\n");
	}

	if (tTd(3, 1))
		sm_dprintf("getla: %d\n",
			(int) (avenrun[0] + FSCALE/2) >> FSHIFT);
	return ((int) (avenrun[0] + FSCALE/2) >> FSHIFT);

}
#endif /* LA_TYPE == LA_IRIX6 */

#if LA_TYPE == LA_KSTAT

# include <kstat.h>

int
getla()
{
	static kstat_ctl_t *kc = NULL;
	static kstat_t *ksp = NULL;
	kstat_named_t *ksn;
	int la;

	if (kc == NULL)		/* if not initialized before */
		kc = kstat_open();
	if (kc == NULL)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: kstat_open(): %s\n",
				   sm_errstring(errno));
		return -1;
	}
	if (ksp == NULL)
		ksp = kstat_lookup(kc, "unix", 0, "system_misc");
	if (ksp == NULL)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: kstat_lookup(): %s\n",
				   sm_errstring(errno));
		return -1;
	}
	if (kstat_read(kc, ksp, NULL) < 0)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: kstat_read(): %s\n",
				   sm_errstring(errno));
		return -1;
	}
	ksn = (kstat_named_t *) kstat_data_lookup(ksp, "avenrun_1min");
	la = ((double) ksn->value.ul + FSCALE/2) / FSCALE;
	/* kstat_close(kc); /o do not close for fast access */
	return la;
}

#endif /* LA_TYPE == LA_KSTAT */

#if LA_TYPE == LA_DEVSHORT

/*
**  Read /dev/table/avenrun for the load average.  This should contain
**  three shorts for the 1, 5, and 15 minute loads.  We only read the
**  first, since that's all we care about.
**
**	Intended for SCO OpenServer 5.
*/

# ifndef _PATH_AVENRUN
#  define _PATH_AVENRUN	"/dev/table/avenrun"
# endif /* ! _PATH_AVENRUN */

int
getla()
{
	static int afd = -1;
	short avenrun;
	int loadav;
	int r;

	errno = EBADF;

	if (afd == -1 || lseek(afd, 0L, SEEK_SET) == -1)
	{
		if (errno != EBADF)
			return -1;
		afd = open(_PATH_AVENRUN, O_RDONLY|O_SYNC);
		if (afd < 0)
		{
			sm_syslog(LOG_ERR, NOQID,
				"can't open %s: %s",
				_PATH_AVENRUN, sm_errstring(errno));
			return -1;
		}
	}

	r = read(afd, &avenrun, sizeof(avenrun));

	if (tTd(3, 5))
		sm_dprintf("getla: avenrun = %d\n", avenrun);
	loadav = (int) (avenrun + FSCALE/2) >> FSHIFT;
	if (tTd(3, 1))
		sm_dprintf("getla: %d\n", loadav);
	return loadav;
}

#endif /* LA_TYPE == LA_DEVSHORT */

#if LA_TYPE == LA_ALPHAOSF
struct rtentry;
struct mbuf;
# include <sys/table.h>

int
getla()
{
	int ave = 0;
	struct tbl_loadavg tab;

	if (table(TBL_LOADAVG, 0, &tab, 1, sizeof(tab)) == -1)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: table %s\n", sm_errstring(errno));
		return -1;
	}

	if (tTd(3, 1))
		sm_dprintf("getla: scale = %d\n", tab.tl_lscale);

	if (tab.tl_lscale)
		ave = ((tab.tl_avenrun.l[2] + (tab.tl_lscale/2)) /
		       tab.tl_lscale);
	else
		ave = (int) (tab.tl_avenrun.d[2] + 0.5);

	if (tTd(3, 1))
		sm_dprintf("getla: %d\n", ave);

	return ave;
}

#endif /* LA_TYPE == LA_ALPHAOSF */

#if LA_TYPE == LA_PSET

int
getla()
{
	double avenrun[3];

	if (pset_getloadavg(PS_MYID, avenrun,
			    sizeof(avenrun) / sizeof(avenrun[0])) < 0)
	{
		if (tTd(3, 1))
			sm_dprintf("getla: pset_getloadavg failed: %s",
				   sm_errstring(errno));
		return -1;
	}
	if (tTd(3, 1))
		sm_dprintf("getla: %d\n", (int) (avenrun[0] +0.5));
	return ((int) (avenrun[0] + 0.5));
}

#endif /* LA_TYPE == LA_PSET */

#if LA_TYPE == LA_ZERO

int
getla()
{
	if (tTd(3, 1))
		sm_dprintf("getla: ZERO\n");
	return 0;
}

#endif /* LA_TYPE == LA_ZERO */

/*
 * Copyright 1989 Massachusetts Institute of Technology
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  M.I.T. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors:  Many and varied...
 */

/* Non Apollo stuff removed by Don Lewis 11/15/93 */
#ifndef lint
SM_UNUSED(static char  rcsid[]) = "@(#)$OrigId: getloadavg.c,v 1.16 1991/06/21 12:51:15 paul Exp $";
#endif /* ! lint */

#ifdef apollo
# undef volatile
# include <apollo/base.h>

/* ARGSUSED */
int getloadavg( call_data )
	caddr_t call_data;	/* pointer to (double) return value */
{
	double *avenrun = (double *) call_data;
	int i;
	status_$t      st;
	long loadav[3];

	proc1_$get_loadav(loadav, &st);
	*avenrun = loadav[0] / (double) (1 << 16);
	return 0;
}
#endif /* apollo */
/*
**  SM_GETLA -- get the current load average
**
**	Parameters:
**		none
**
**	Returns:
**		none
**
**	Side Effects:
**		Set CurrentLA to the current load average.
**		Set {load_avg} in GlobalMacros to the current load average.
*/

void
sm_getla()
{
	char labuf[8];

	CurrentLA = getla();
	(void) sm_snprintf(labuf, sizeof(labuf), "%d", CurrentLA);
	macdefine(&GlobalMacros, A_TEMP, macid("{load_avg}"), labuf);
}
/*
**  SHOULDQUEUE -- should this message be queued or sent?
**
**	Compares the message cost to the load average to decide.
**
**	Note: Do NOT change this API! It is documented in op.me
**		and theoretically the user can change this function...
**
**	Parameters:
**		pri -- the priority of the message in question.
**		ct -- the message creation time (unused, but see above).
**
**	Returns:
**		true -- if this message should be queued up for the
**			time being.
**		false -- if the load is low enough to send this message.
**
**	Side Effects:
**		none.
*/

/* ARGSUSED1 */
bool
shouldqueue(pri, ct)
	long pri;
	time_t ct;
{
	bool rval;
#if _FFR_MEMSTAT
	long memfree;
#endif /* _FFR_MEMSTAT */

	if (tTd(3, 30))
		sm_dprintf("shouldqueue: CurrentLA=%d, pri=%ld: ",
			CurrentLA, pri);

#if _FFR_MEMSTAT
	if (QueueLowMem > 0 &&
	    sm_memstat_get(MemoryResource, &memfree) >= 0 &&
	    memfree < QueueLowMem)
	{
		if (tTd(3, 30))
			sm_dprintf("true (memfree=%ld < QueueLowMem=%ld)\n",
				memfree, QueueLowMem);
		return true;
	}
#endif /* _FFR_MEMSTAT */
	if (CurrentLA < QueueLA)
	{
		if (tTd(3, 30))
			sm_dprintf("false (CurrentLA < QueueLA)\n");
		return false;
	}
	rval = pri > (QueueFactor / (CurrentLA - QueueLA + 1));
	if (tTd(3, 30))
		sm_dprintf("%s (by calculation)\n", rval ? "true" : "false");
	return rval;
}

/*
**  REFUSECONNECTIONS -- decide if connections should be refused
**
**	Parameters:
**		e -- the current envelope.
**		dn -- number of daemon.
**		active -- was this daemon actually active?
**
**	Returns:
**		true if incoming SMTP connections should be refused
**			(for now).
**		false if we should accept new work.
**
**	Side Effects:
**		Sets process title when it is rejecting connections.
*/

bool
refuseconnections(e, dn, active)
	ENVELOPE *e;
	int dn;
	bool active;
{
	static time_t lastconn[MAXDAEMONS];
	static int conncnt[MAXDAEMONS];
	static time_t firstrejtime[MAXDAEMONS];
	static time_t nextlogtime[MAXDAEMONS];
	int limit;
#if _FFR_MEMSTAT
	long memfree;
#endif /* _FFR_MEMSTAT */

#if XLA
	if (!xla_smtp_ok())
		return true;
#endif /* XLA */

	SM_ASSERT(dn >= 0);
	SM_ASSERT(dn < MAXDAEMONS);
	if (ConnRateThrottle > 0)
	{
		time_t now;

		now = curtime();
		if (active)
		{
			if (now != lastconn[dn])
			{
				lastconn[dn] = now;
				conncnt[dn] = 1;
			}
			else if (conncnt[dn]++ > ConnRateThrottle)
			{
#define D_MSG_CRT "deferring connections on daemon %s: %d per second"
				/* sleep to flatten out connection load */
				sm_setproctitle(true, e, D_MSG_CRT,
						Daemons[dn].d_name,
						ConnRateThrottle);
				if (LogLevel > 8)
					sm_syslog(LOG_INFO, NOQID, D_MSG_CRT,
						  Daemons[dn].d_name,
						  ConnRateThrottle);
				(void) sleep(1);
			}
		}
		else if (now != lastconn[dn])
			conncnt[dn] = 0;
	}


#if _FFR_MEMSTAT
	if (RefuseLowMem > 0 &&
	    sm_memstat_get(MemoryResource, &memfree) >= 0 &&
	    memfree < RefuseLowMem)
	{
# define R_MSG_LM "rejecting connections on daemon %s: free memory: %ld"
		sm_setproctitle(true, e, R_MSG_LM, Daemons[dn].d_name, memfree);
		if (LogLevel > 8)
			sm_syslog(LOG_NOTICE, NOQID, R_MSG_LM,
				Daemons[dn].d_name, memfree);
		return true;
	}
#endif /* _FFR_MEMSTAT */
	sm_getla();
	limit = (Daemons[dn].d_refuseLA != DPO_NOTSET) ?
		Daemons[dn].d_refuseLA : RefuseLA;
	if (limit > 0 && CurrentLA >= limit)
	{
		time_t now;

# define R_MSG_LA "rejecting connections on daemon %s: load average: %d"
# define R2_MSG_LA "have been rejecting connections on daemon %s for %s"
		sm_setproctitle(true, e, R_MSG_LA, Daemons[dn].d_name,
				CurrentLA);
		if (LogLevel > 8)
			sm_syslog(LOG_NOTICE, NOQID, R_MSG_LA,
				Daemons[dn].d_name, CurrentLA);
		now = curtime();
		if (firstrejtime[dn] == 0)
		{
			firstrejtime[dn] = now;
			nextlogtime[dn] = now + RejectLogInterval;
		}
		else if (nextlogtime[dn] < now)
		{
			sm_syslog(LOG_ERR, NOQID, R2_MSG_LA, Daemons[dn].d_name,
				  pintvl(now - firstrejtime[dn], true));
			nextlogtime[dn] = now + RejectLogInterval;
		}
		return true;
	}
	else
		firstrejtime[dn] = 0;

	limit = (Daemons[dn].d_delayLA != DPO_NOTSET) ?
		Daemons[dn].d_delayLA : DelayLA;
	if (limit > 0 && CurrentLA >= limit)
	{
		time_t now;
		static time_t log_delay = (time_t) 0;

# define MIN_DELAY_LOG	90	/* wait before logging this again */
# define D_MSG_LA "delaying connections on daemon %s: load average=%d >= %d"
		/* sleep to flatten out connection load */
		sm_setproctitle(true, e, D_MSG_LA, Daemons[dn].d_name, limit);
		if (LogLevel > 8 && (now = curtime()) > log_delay)
		{
			sm_syslog(LOG_INFO, NOQID, D_MSG_LA,
				  Daemons[dn].d_name, CurrentLA, limit);
			log_delay = now + MIN_DELAY_LOG;
		}
		(void) sleep(1);
	}

	limit = (Daemons[dn].d_maxchildren != DPO_NOTSET) ?
		Daemons[dn].d_maxchildren : MaxChildren;
	if (limit > 0 && CurChildren >= limit)
	{
		proc_list_probe();
		if (CurChildren >= limit)
		{
#define R_MSG_CHILD "rejecting connections on daemon %s: %d children, max %d"
			sm_setproctitle(true, e, R_MSG_CHILD,
					Daemons[dn].d_name, CurChildren,
					limit);
			if (LogLevel > 8)
				sm_syslog(LOG_INFO, NOQID, R_MSG_CHILD,
					Daemons[dn].d_name, CurChildren,
					limit);
			return true;
		}
	}
	return false;
}

/*
**  SETPROCTITLE -- set process title for ps
**
**	Parameters:
**		fmt -- a printf style format string.
**		a, b, c -- possible parameters to fmt.
**
**	Returns:
**		none.
**
**	Side Effects:
**		Clobbers argv of our main procedure so ps(1) will
**		display the title.
*/

#define SPT_NONE	0	/* don't use it at all */
#define SPT_REUSEARGV	1	/* cover argv with title information */
#define SPT_BUILTIN	2	/* use libc builtin */
#define SPT_PSTAT	3	/* use pstat(PSTAT_SETCMD, ...) */
#define SPT_PSSTRINGS	4	/* use PS_STRINGS->... */
#define SPT_SYSMIPS	5	/* use sysmips() supported by NEWS-OS 6 */
#define SPT_SCO		6	/* write kernel u. area */
#define SPT_CHANGEARGV	7	/* write our own strings into argv[] */

#ifndef SPT_TYPE
# define SPT_TYPE	SPT_REUSEARGV
#endif /* ! SPT_TYPE */


#if SPT_TYPE != SPT_NONE && SPT_TYPE != SPT_BUILTIN

# if SPT_TYPE == SPT_PSTAT
#  include <sys/pstat.h>
# endif /* SPT_TYPE == SPT_PSTAT */
# if SPT_TYPE == SPT_PSSTRINGS
#  include <machine/vmparam.h>
#  include <sys/exec.h>
#  ifndef PS_STRINGS	/* hmmmm....  apparently not available after all */
#   undef SPT_TYPE
#   define SPT_TYPE	SPT_REUSEARGV
#  else /* ! PS_STRINGS */
#   ifndef NKPDE			/* FreeBSD 2.0 */
#    define NKPDE 63
typedef unsigned int	*pt_entry_t;
#   endif /* ! NKPDE */
#  endif /* ! PS_STRINGS */
# endif /* SPT_TYPE == SPT_PSSTRINGS */

# if SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV
#  define SETPROC_STATIC	static
# else /* SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV */
#  define SETPROC_STATIC
# endif /* SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV */

# if SPT_TYPE == SPT_SYSMIPS
#  include <sys/sysmips.h>
#  include <sys/sysnews.h>
# endif /* SPT_TYPE == SPT_SYSMIPS */

# if SPT_TYPE == SPT_SCO
#  include <sys/immu.h>
#  include <sys/dir.h>
#  include <sys/user.h>
#  include <sys/fs/s5param.h>
#  if PSARGSZ > MAXLINE
#   define SPT_BUFSIZE	PSARGSZ
#  endif /* PSARGSZ > MAXLINE */
# endif /* SPT_TYPE == SPT_SCO */

# ifndef SPT_PADCHAR
#  define SPT_PADCHAR	' '
# endif /* ! SPT_PADCHAR */

#endif /* SPT_TYPE != SPT_NONE && SPT_TYPE != SPT_BUILTIN */

#ifndef SPT_BUFSIZE
# define SPT_BUFSIZE	MAXLINE
#endif /* ! SPT_BUFSIZE */

#if _FFR_SPT_ALIGN

/*
**  It looks like the Compaq Tru64 5.1A now aligns argv and envp to
**  64 bit alignment, so unless each piece of argv and envp is a multiple
**  of 8 bytes (including terminating NULL), initsetproctitle() won't use
**  any of the space beyond argv[0].  Be sure to set SPT_ALIGN_SIZE if
**  you use this FFR.
*/

# ifdef SPT_ALIGN_SIZE
#  define SPT_ALIGN(x, align)	(((((x) + SPT_ALIGN_SIZE) >> (align)) << (align)) - 1)
# else /* SPT_ALIGN_SIZE */
#  define SPT_ALIGN(x, align)	(x)
# endif /* SPT_ALIGN_SIZE */
#else /* _FFR_SPT_ALIGN */
# define SPT_ALIGN(x, align)	(x)
#endif /* _FFR_SPT_ALIGN */

/*
**  Pointers for setproctitle.
**	This allows "ps" listings to give more useful information.
*/

static char	**Argv = NULL;		/* pointer to argument vector */
static char	*LastArgv = NULL;	/* end of argv */
#if SPT_TYPE != SPT_BUILTIN
static void	setproctitle __P((const char *, ...));
#endif /* SPT_TYPE != SPT_BUILTIN */

void
initsetproctitle(argc, argv, envp)
	int argc;
	char **argv;
	char **envp;
{
	register int i;
	int align;
	extern char **environ;

	/*
	**  Move the environment so setproctitle can use the space at
	**  the top of memory.
	*/

	if (envp != NULL)
	{
		for (i = 0; envp[i] != NULL; i++)
			continue;
		environ = (char **) xalloc(sizeof(char *) * (i + 1));
		for (i = 0; envp[i] != NULL; i++)
			environ[i] = newstr(envp[i]);
		environ[i] = NULL;
	}

	/*
	**  Save start and extent of argv for setproctitle.
	*/

	Argv = argv;

	/*
	**  Determine how much space we can use for setproctitle.
	**  Use all contiguous argv and envp pointers starting at argv[0]
	*/

	align = -1;
# if _FFR_SPT_ALIGN
#  ifdef SPT_ALIGN_SIZE
	for (i = SPT_ALIGN_SIZE; i > 0; i >>= 1)
		align++;
#  endif /* SPT_ALIGN_SIZE */
# endif /* _FFR_SPT_ALIGN */

	for (i = 0; i < argc; i++)
	{
		if (i == 0 || LastArgv + 1 == argv[i])
			LastArgv = argv[i] + SPT_ALIGN(strlen(argv[i]), align);
	}
	for (i = 0; LastArgv != NULL && envp != NULL && envp[i] != NULL; i++)
	{
		if (LastArgv + 1 == envp[i])
			LastArgv = envp[i] + SPT_ALIGN(strlen(envp[i]), align);
	}
}

#if SPT_TYPE != SPT_BUILTIN

/*VARARGS1*/
static void
# ifdef __STDC__
setproctitle(const char *fmt, ...)
# else /* __STDC__ */
setproctitle(fmt, va_alist)
	const char *fmt;
	va_dcl
# endif /* __STDC__ */
{
# if SPT_TYPE != SPT_NONE
	register int i;
	register char *p;
	SETPROC_STATIC char buf[SPT_BUFSIZE];
	SM_VA_LOCAL_DECL
#  if SPT_TYPE == SPT_PSTAT
	union pstun pst;
#  endif /* SPT_TYPE == SPT_PSTAT */
#  if SPT_TYPE == SPT_SCO
	int j;
	off_t seek_off;
	static int kmem = -1;
	static pid_t kmempid = -1;
	struct user u;
#  endif /* SPT_TYPE == SPT_SCO */

	p = buf;

	/* print sendmail: heading for grep */
	(void) sm_strlcpy(p, "sendmail: ", SPACELEFT(buf, p));
	p += strlen(p);

	/* print the argument string */
	SM_VA_START(ap, fmt);
	(void) sm_vsnprintf(p, SPACELEFT(buf, p), fmt, ap);
	SM_VA_END(ap);

	i = (int) strlen(buf);
	if (i < 0)
		return;

#  if SPT_TYPE == SPT_PSTAT
	pst.pst_command = buf;
	pstat(PSTAT_SETCMD, pst, i, 0, 0);
#  endif /* SPT_TYPE == SPT_PSTAT */
#  if SPT_TYPE == SPT_PSSTRINGS
	PS_STRINGS->ps_nargvstr = 1;
	PS_STRINGS->ps_argvstr = buf;
#  endif /* SPT_TYPE == SPT_PSSTRINGS */
#  if SPT_TYPE == SPT_SYSMIPS
	sysmips(SONY_SYSNEWS, NEWS_SETPSARGS, buf);
#  endif /* SPT_TYPE == SPT_SYSMIPS */
#  if SPT_TYPE == SPT_SCO
	if (kmem < 0 || kmempid != CurrentPid)
	{
		if (kmem >= 0)
			(void) close(kmem);
		kmem = open(_PATH_KMEM, O_RDWR, 0);
		if (kmem < 0)
			return;
		if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
		    fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
		{
			(void) close(kmem);
			kmem = -1;
			return;
		}
		kmempid = CurrentPid;
	}
	buf[PSARGSZ - 1] = '\0';
	seek_off = UVUBLK + (off_t) u.u_psargs - (off_t) &u;
	if (lseek(kmem, (off_t) seek_off, SEEK_SET) == seek_off)
		(void) write(kmem, buf, PSARGSZ);
#  endif /* SPT_TYPE == SPT_SCO */
#  if SPT_TYPE == SPT_REUSEARGV
	if (LastArgv == NULL)
		return;

	if (i > LastArgv - Argv[0] - 2)
	{
		i = LastArgv - Argv[0] - 2;
		buf[i] = '\0';
	}
	(void) sm_strlcpy(Argv[0], buf, i + 1);
	p = &Argv[0][i];
	while (p < LastArgv)
		*p++ = SPT_PADCHAR;
	Argv[1] = NULL;
#  endif /* SPT_TYPE == SPT_REUSEARGV */
#  if SPT_TYPE == SPT_CHANGEARGV
	Argv[0] = buf;
	Argv[1] = 0;
#  endif /* SPT_TYPE == SPT_CHANGEARGV */
# endif /* SPT_TYPE != SPT_NONE */
}

#endif /* SPT_TYPE != SPT_BUILTIN */
/*
**  SM_SETPROCTITLE -- set process task and set process title for ps
**
**	Possibly set process status and call setproctitle() to
**	change the ps display.
**
**	Parameters:
**		status -- whether or not to store as process status
**		e -- the current envelope.
**		fmt -- a printf style format string.
**		a, b, c -- possible parameters to fmt.
**
**	Returns:
**		none.
*/

/*VARARGS2*/
void
#ifdef __STDC__
sm_setproctitle(bool status, ENVELOPE *e, const char *fmt, ...)
#else /* __STDC__ */
sm_setproctitle(status, e, fmt, va_alist)
	bool status;
	ENVELOPE *e;
	const char *fmt;
	va_dcl
#endif /* __STDC__ */
{
	char buf[SPT_BUFSIZE];
	SM_VA_LOCAL_DECL

	/* print the argument string */
	SM_VA_START(ap, fmt);
	(void) sm_vsnprintf(buf, sizeof(buf), fmt, ap);
	SM_VA_END(ap);

	if (status)
		proc_list_set(CurrentPid, buf);

	if (ProcTitlePrefix != NULL)
	{
		char prefix[SPT_BUFSIZE];

		expand(ProcTitlePrefix, prefix, sizeof(prefix), e);
		setproctitle("%s: %s", prefix, buf);
	}
	else
		setproctitle("%s", buf);
}
/*
**  WAITFOR -- wait for a particular process id.
**
**	Parameters:
**		pid -- process id to wait for.
**
**	Returns:
**		status of pid.
**		-1 if pid never shows up.
**
**	Side Effects:
**		none.
*/

int
waitfor(pid)
	pid_t pid;
{
	int st;
	pid_t i;

	do
	{
		errno = 0;
		i = sm_wait(&st);
		if (i > 0)
			proc_list_drop(i, st, NULL);
	} while ((i >= 0 || errno == EINTR) && i != pid);
	if (i < 0)
		return -1;
	return st;
}
/*
**  SM_WAIT -- wait
**
**	Parameters:
**		status -- pointer to status (return value)
**
**	Returns:
**		pid
*/

pid_t
sm_wait(status)
	int *status;
{
# ifdef WAITUNION
	union wait st;
# else /* WAITUNION */
	auto int st;
# endif /* WAITUNION */
	pid_t i;
# if defined(ISC_UNIX) || defined(_SCO_unix_)
	int savesig;
# endif /* defined(ISC_UNIX) || defined(_SCO_unix_) */

# if defined(ISC_UNIX) || defined(_SCO_unix_)
	savesig = sm_releasesignal(SIGCHLD);
# endif /* defined(ISC_UNIX) || defined(_SCO_unix_) */
	i = wait(&st);
# if defined(ISC_UNIX) || defined(_SCO_unix_)
	if (savesig > 0)
		sm_blocksignal(SIGCHLD);
# endif /* defined(ISC_UNIX) || defined(_SCO_unix_) */
# ifdef WAITUNION
	*status = st.w_status;
# else /* WAITUNION */
	*status = st;
# endif /* WAITUNION */
	return i;
}
/*
**  REAPCHILD -- pick up the body of my child, lest it become a zombie
**
**	Parameters:
**		sig -- the signal that got us here (unused).
**
**	Returns:
**		none.
**
**	Side Effects:
**		Picks up extant zombies.
**		Control socket exits may restart/shutdown daemon.
**
**	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
**		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
**		DOING.
*/

/* ARGSUSED0 */
SIGFUNC_DECL
reapchild(sig)
	int sig;
{
	int save_errno = errno;
	int st;
	pid_t pid;
# if HASWAITPID
	auto int status;
	int count;

	count = 0;
	while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
	{
		st = status;
		if (count++ > 1000)
			break;
# else /* HASWAITPID */
#  ifdef WNOHANG
	union wait status;

	while ((pid = wait3(&status, WNOHANG, (struct rusage *) NULL)) > 0)
	{
		st = status.w_status;
#  else /* WNOHANG */
	auto int status;

	/*
	**  Catch one zombie -- we will be re-invoked (we hope) if there
	**  are more.  Unreliable signals probably break this, but this
	**  is the "old system" situation -- waitpid or wait3 are to be
	**  strongly preferred.
	*/

	if ((pid = wait(&status)) > 0)
	{
		st = status;
#  endif /* WNOHANG */
# endif /* HASWAITPID */
		/* Drop PID and check if it was a control socket child */
		proc_list_drop(pid, st, NULL);
	}
	FIX_SYSV_SIGNAL(sig, reapchild);
	errno = save_errno;
	return SIGFUNC_RETURN;
}
/*
**  GETDTABLESIZE -- return number of file descriptors
**
**	Only on non-BSD systems
**
**	Parameters:
**		none
**
**	Returns:
**		size of file descriptor table
**
**	Side Effects:
**		none
*/

#ifdef SOLARIS
# include <sys/resource.h>
#endif /* SOLARIS */

int
getdtsize()
{
# ifdef RLIMIT_NOFILE
	struct rlimit rl;

	if (getrlimit(RLIMIT_NOFILE, &rl) >= 0)
		return rl.rlim_cur;
# endif /* RLIMIT_NOFILE */

# if HASGETDTABLESIZE
	return getdtablesize();
# else /* HASGETDTABLESIZE */
#  ifdef _SC_OPEN_MAX
	return sysconf(_SC_OPEN_MAX);
#  else /* _SC_OPEN_MAX */
	return NOFILE;
#  endif /* _SC_OPEN_MAX */
# endif /* HASGETDTABLESIZE */
}
/*
**  UNAME -- get the UUCP name of this system.
*/

#if !HASUNAME

int
uname(name)
	struct utsname *name;
{
	SM_FILE_T *file;
	char *n;

	name->nodename[0] = '\0';

	/* try /etc/whoami -- one line with the node name */
	if ((file = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, "/etc/whoami",
			       SM_IO_RDONLY, NULL)) != NULL)
	{
		(void) sm_io_fgets(file, SM_TIME_DEFAULT, name->nodename,
				   NODE_LENGTH + 1);
		(void) sm_io_close(file, SM_TIME_DEFAULT);
		n = strchr(name->nodename, '\n');
		if (n != NULL)
			*n = '\0';
		if (name->nodename[0] != '\0')
			return 0;
	}

	/* try /usr/include/whoami.h -- has a #define somewhere */
	if ((file = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
			       "/usr/include/whoami.h", SM_IO_RDONLY, NULL))
	    != NULL)
	{
		char buf[MAXLINE];

		while (sm_io_fgets(file, SM_TIME_DEFAULT,
				   buf, sizeof(buf)) != NULL)
		{
			if (sm_io_sscanf(buf, "#define sysname \"%*[^\"]\"",
					NODE_LENGTH, name->nodename) > 0)
				break;
		}
		(void) sm_io_close(file, SM_TIME_DEFAULT);
		if (name->nodename[0] != '\0')
			return 0;
	}

	return -1;
}
#endif /* !HASUNAME */
/*
**  INITGROUPS -- initialize groups
**
**	Stub implementation for System V style systems
*/

#if !HASINITGROUPS

initgroups(name, basegid)
	char *name;
	int basegid;
{
	return 0;
}

#endif /* !HASINITGROUPS */
/*
**  SETGROUPS -- set group list
**
**	Stub implementation for systems that don't have group lists
*/

#ifndef NGROUPS_MAX

int
setgroups(ngroups, grouplist)
	int ngroups;
	GIDSET_T grouplist[];
{
	return 0;
}

#endif /* ! NGROUPS_MAX */
/*
**  SETSID -- set session id (for non-POSIX systems)
*/

#if !HASSETSID

pid_t
setsid __P ((void))
{
#  ifdef TIOCNOTTY
	int fd;

	fd = open("/dev/tty", O_RDWR, 0);
	if (fd >= 0)
	{
		(void) ioctl(fd, TIOCNOTTY, (char *) 0);
		(void) close(fd);
	}
#  endif /* TIOCNOTTY */
#  ifdef SYS5SETPGRP
	return setpgrp();
#  else /* SYS5SETPGRP */
	return setpgid(0, CurrentPid);
#  endif /* SYS5SETPGRP */
}

#endif /* !HASSETSID */
/*
**  FSYNC -- dummy fsync
*/

#if NEEDFSYNC

fsync(fd)
	int fd;
{
# ifdef O_SYNC
	return fcntl(fd, F_SETFL, O_SYNC);
# else /* O_SYNC */
	/* nothing we can do */
	return 0;
# endif /* O_SYNC */
}

#endif /* NEEDFSYNC */
/*
**  DGUX_INET_ADDR -- inet_addr for DG/UX
**
**	Data General DG/UX version of inet_addr returns a struct in_addr
**	instead of a long.  This patches things.  Only needed on versions
**	prior to 5.4.3.
*/

#ifdef DGUX_5_4_2

# undef inet_addr

long
dgux_inet_addr(host)
	char *host;
{
	struct in_addr haddr;

	haddr = inet_addr(host);
	return haddr.s_addr;
}

#endif /* DGUX_5_4_2 */
/*
**  GETOPT -- for old systems or systems with bogus implementations
*/

#if !SM_CONF_GETOPT

/*
 * Copyright (c) 1985 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */


/*
**  this version hacked to add `atend' flag to allow state machine
**  to reset if invoked by the program to scan args for a 2nd time
*/

# if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)getopt.c	4.3 (Berkeley) 3/9/86";
# endif /* defined(LIBC_SCCS) && !defined(lint) */

/*
**  get option letter from argument vector
*/
# ifdef _CONVEX_SOURCE
extern int	optind, opterr, optopt;
extern char	*optarg;
# else /* _CONVEX_SOURCE */
int	opterr = 1;		/* if error message should be printed */
int	optind = 1;		/* index into parent argv vector */
int	optopt = 0;		/* character checked for validity */
char	*optarg = NULL;		/* argument associated with option */
# endif /* _CONVEX_SOURCE */

# define BADCH	(int)'?'
# define EMSG	""
# define tell(s)	if (opterr) \
			{sm_io_fputs(smioerr, SM_TIME_DEFAULT, *nargv); \
			(void) sm_io_fputs(smioerr, SM_TIME_DEFAULT, s); \
			(void) sm_io_putc(smioerr, SM_TIME_DEFAULT, optopt); \
			(void) sm_io_putc(smioerr, SM_TIME_DEFAULT, '\n'); \
			return BADCH;}

int
getopt(nargc,nargv,ostr)
	int		nargc;
	char *const	*nargv;
	const char	*ostr;
{
	static char	*place = EMSG;	/* option letter processing */
	static char	atend = 0;
	register char	*oli = NULL;	/* option letter list index */

	if (atend) {
		atend = 0;
		place = EMSG;
	}
	if(!*place) {			/* update scanning pointer */
		if (optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) {
			atend++;
			return -1;
		}
		if (*place == '-') {	/* found "--" */
			++optind;
			atend++;
			return -1;
		}
	}				/* option letter okay? */
	if ((optopt = (int)*place++) == (int)':' || !(oli = strchr(ostr,optopt))) {
		if (!*place) ++optind;
		tell(": illegal option -- ");
	}
	if (oli && *++oli != ':') {		/* don't need argument */
		optarg = NULL;
		if (!*place) ++optind;
	}
	else {				/* need an argument */
		if (*place) optarg = place;	/* no white space */
		else if (nargc <= ++optind) {	/* no arg */
			place = EMSG;
			tell(": option requires an argument -- ");
		}
		else optarg = nargv[optind];	/* white space */
		place = EMSG;
		++optind;
	}
	return optopt;			/* dump back option letter */
}

#endif /* !SM_CONF_GETOPT */
/*
**  USERSHELLOK -- tell if a user's shell is ok for unrestricted use
**
**	Parameters:
**		user -- the name of the user we are checking.
**		shell -- the user's shell from /etc/passwd
**
**	Returns:
**		true -- if it is ok to use this for unrestricted access.
**		false -- if the shell is restricted.
*/

#if !HASGETUSERSHELL

# ifndef _PATH_SHELLS
#  define _PATH_SHELLS	"/etc/shells"
# endif /* ! _PATH_SHELLS */

# if defined(_AIX3) || defined(_AIX4)
#  include <userconf.h>
#  if _AIX4 >= 40200
#   include <userpw.h>
#  endif /* _AIX4 >= 40200 */
#  include <usersec.h>
# endif /* defined(_AIX3) || defined(_AIX4) */

static char	*DefaultUserShells[] =
{
	"/bin/sh",		/* standard shell */
# ifdef MPE
	"/SYS/PUB/CI",
# else /* MPE */
	"/usr/bin/sh",
	"/bin/csh",		/* C shell */
	"/usr/bin/csh",
# endif /* MPE */
# ifdef __hpux
#  ifdef V4FS
	"/usr/bin/rsh",		/* restricted Bourne shell */
	"/usr/bin/ksh",		/* Korn shell */
	"/usr/bin/rksh",	/* restricted Korn shell */
	"/usr/bin/pam",
	"/usr/bin/keysh",	/* key shell (extended Korn shell) */
	"/usr/bin/posix/sh",
#  else /* V4FS */
	"/bin/rsh",		/* restricted Bourne shell */
	"/bin/ksh",		/* Korn shell */
	"/bin/rksh",		/* restricted Korn shell */
	"/bin/pam",
	"/usr/bin/keysh",	/* key shell (extended Korn shell) */
	"/bin/posix/sh",
	"/sbin/sh",
#  endif /* V4FS */
# endif /* __hpux */
# if defined(_AIX3) || defined(_AIX4)
	"/bin/ksh",		/* Korn shell */
	"/usr/bin/ksh",
	"/bin/tsh",		/* trusted shell */
	"/usr/bin/tsh",
	"/bin/bsh",		/* Bourne shell */
	"/usr/bin/bsh",
# endif /* defined(_AIX3) || defined(_AIX4) */
# if defined(__svr4__) || defined(__svr5__)
	"/bin/ksh",		/* Korn shell */
	"/usr/bin/ksh",
# endif /* defined(__svr4__) || defined(__svr5__) */
# ifdef sgi
	"/sbin/sh",		/* SGI's shells really live in /sbin */
	"/usr/bin/sh",
	"/sbin/bsh",		/* classic Bourne shell */
	"/bin/bsh",
	"/usr/bin/bsh",
	"/sbin/csh",		/* standard csh */
	"/bin/csh",
	"/usr/bin/csh",
	"/sbin/jsh",		/* classic Bourne shell w/ job control*/
	"/bin/jsh",
	"/usr/bin/jsh",
	"/bin/ksh",		/* Korn shell */
	"/sbin/ksh",
	"/usr/bin/ksh",
	"/sbin/tcsh",		/* Extended csh */
	"/bin/tcsh",
	"/usr/bin/tcsh",
# endif /* sgi */
	NULL
};

#endif /* !HASGETUSERSHELL */

#define WILDCARD_SHELL	"/SENDMAIL/ANY/SHELL/"

bool
usershellok(user, shell)
	char *user;
	char *shell;
{
# if HASGETUSERSHELL
	register char *p;
	extern char *getusershell();

	if (shell == NULL || shell[0] == '\0' || wordinclass(user, 't') ||
	    ConfigLevel <= 1)
		return true;

	setusershell();
	while ((p = getusershell()) != NULL)
		if (strcmp(p, shell) == 0 || strcmp(p, WILDCARD_SHELL) == 0)
			break;
	endusershell();
	return p != NULL;
# else /* HASGETUSERSHELL */
#  if USEGETCONFATTR
	auto char *v;
#  endif /* USEGETCONFATTR */
	register SM_FILE_T *shellf;
	char buf[MAXLINE];

	if (shell == NULL || shell[0] == '\0' || wordinclass(user, 't') ||
	    ConfigLevel <= 1)
		return true;

#  if USEGETCONFATTR
	/*
	**  Naturally IBM has a "better" idea.....
	**
	**	What a crock.  This interface isn't documented, it is
	**	considered part of the security library (-ls), and it
	**	only works if you are running as root (since the list
	**	of valid shells is obviously a source of great concern).
	**	I recommend that you do NOT define USEGETCONFATTR,
	**	especially since you are going to have to set up an
	**	/etc/shells anyhow to handle the cases where getconfattr
	**	fails.
	*/

	if (getconfattr(SC_SYS_LOGIN, SC_SHELLS, &v, SEC_LIST) == 0 && v != NULL)
	{
		while (*v != '\0')
		{
			if (strcmp(v, shell) == 0 || strcmp(v, WILDCARD_SHELL) == 0)
				return true;
			v += strlen(v) + 1;
		}
		return false;
	}
#  endif /* USEGETCONFATTR */

	shellf = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, _PATH_SHELLS,
			    SM_IO_RDONLY, NULL);
	if (shellf == NULL)
	{
		/* no /etc/shells; see if it is one of the std shells */
		char **d;

		if (errno != ENOENT && LogLevel > 3)
			sm_syslog(LOG_ERR, NOQID,
				  "usershellok: cannot open %s: %s",
				  _PATH_SHELLS, sm_errstring(errno));

		for (d = DefaultUserShells; *d != NULL; d++)
		{
			if (strcmp(shell, *d) == 0)
				return true;
		}
		return false;
	}

	while (sm_io_fgets(shellf, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
	{
		register char *p, *q;

		p = buf;
		while (*p != '\0' && *p != '#' && *p != '/')
			p++;
		if (*p == '#' || *p == '\0')
			continue;
		q = p;
		while (*p != '\0' && *p != '#' && !(isascii(*p) && isspace(*p)))
			p++;
		*p = '\0';
		if (strcmp(shell, q) == 0 || strcmp(WILDCARD_SHELL, q) == 0)
		{
			(void) sm_io_close(shellf, SM_TIME_DEFAULT);
			return true;
		}
	}
	(void) sm_io_close(shellf, SM_TIME_DEFAULT);
	return false;
# endif /* HASGETUSERSHELL */
}
/*
**  FREEDISKSPACE -- see how much free space is on the queue filesystem
**
**	Only implemented if you have statfs.
**
**	Parameters:
**		dir -- the directory in question.
**		bsize -- a variable into which the filesystem
**			block size is stored.
**
**	Returns:
**		The number of blocks free on the queue filesystem.
**		-1 if the statfs call fails.
**
**	Side effects:
**		Puts the filesystem block size into bsize.
*/

/* statfs types */
# define SFS_NONE	0	/* no statfs implementation */
# define SFS_USTAT	1	/* use ustat */
# define SFS_4ARGS	2	/* use four-argument statfs call */
# define SFS_VFS	3	/* use <sys/vfs.h> implementation */
# define SFS_MOUNT	4	/* use <sys/mount.h> implementation */
# define SFS_STATFS	5	/* use <sys/statfs.h> implementation */
# define SFS_STATVFS	6	/* use <sys/statvfs.h> implementation */

# ifndef SFS_TYPE
#  define SFS_TYPE	SFS_NONE
# endif /* ! SFS_TYPE */

# if SFS_TYPE == SFS_USTAT
#  include <ustat.h>
# endif /* SFS_TYPE == SFS_USTAT */
# if SFS_TYPE == SFS_4ARGS || SFS_TYPE == SFS_STATFS
#  include <sys/statfs.h>
# endif /* SFS_TYPE == SFS_4ARGS || SFS_TYPE == SFS_STATFS */
# if SFS_TYPE == SFS_VFS
#  include <sys/vfs.h>
# endif /* SFS_TYPE == SFS_VFS */
# if SFS_TYPE == SFS_MOUNT
#  include <sys/mount.h>
# endif /* SFS_TYPE == SFS_MOUNT */
# if SFS_TYPE == SFS_STATVFS
#  include <sys/statvfs.h>
# endif /* SFS_TYPE == SFS_STATVFS */

long
freediskspace(dir, bsize)
	const char *dir;
	long *bsize;
{
# if SFS_TYPE == SFS_NONE
	if (bsize != NULL)
		*bsize = 4096L;

	/* assume free space is plentiful */
	return (long) LONG_MAX;
# else /* SFS_TYPE == SFS_NONE */
#  if SFS_TYPE == SFS_USTAT
	struct ustat fs;
	struct stat statbuf;
#   define FSBLOCKSIZE	DEV_BSIZE
#   define SFS_BAVAIL	f_tfree
#  else /* SFS_TYPE == SFS_USTAT */
#   if defined(ultrix)
	struct fs_data fs;
#    define SFS_BAVAIL	fd_bfreen
#    define FSBLOCKSIZE	1024L
#   else /* defined(ultrix) */
#    if SFS_TYPE == SFS_STATVFS
	struct statvfs fs;
#     define FSBLOCKSIZE	fs.f_frsize
#    else /* SFS_TYPE == SFS_STATVFS */
	struct statfs fs;
#     define FSBLOCKSIZE	fs.f_bsize
#    endif /* SFS_TYPE == SFS_STATVFS */
#   endif /* defined(ultrix) */
#  endif /* SFS_TYPE == SFS_USTAT */
#  ifndef SFS_BAVAIL
#   define SFS_BAVAIL f_bavail
#  endif /* ! SFS_BAVAIL */

#  if SFS_TYPE == SFS_USTAT
	if (stat(dir, &statbuf) == 0 && ustat(statbuf.st_dev, &fs) == 0)
#  else /* SFS_TYPE == SFS_USTAT */
#   if SFS_TYPE == SFS_4ARGS
	if (statfs(dir, &fs, sizeof(fs), 0) == 0)
#   else /* SFS_TYPE == SFS_4ARGS */
#    if SFS_TYPE == SFS_STATVFS
	if (statvfs(dir, &fs) == 0)
#    else /* SFS_TYPE == SFS_STATVFS */
#     if defined(ultrix)
	if (statfs(dir, &fs) > 0)
#     else /* defined(ultrix) */
	if (statfs(dir, &fs) == 0)
#     endif /* defined(ultrix) */
#    endif /* SFS_TYPE == SFS_STATVFS */
#   endif /* SFS_TYPE == SFS_4ARGS */
#  endif /* SFS_TYPE == SFS_USTAT */
	{
		if (bsize != NULL)
			*bsize = FSBLOCKSIZE;
		if (fs.SFS_BAVAIL <= 0)
			return 0;
		else if (fs.SFS_BAVAIL > LONG_MAX)
			return (long) LONG_MAX;
		else
			return (long) fs.SFS_BAVAIL;
	}
	return -1;
# endif /* SFS_TYPE == SFS_NONE */
}
/*
**  ENOUGHDISKSPACE -- is there enough free space on the queue file systems?
**
**	Parameters:
**		msize -- the size to check against.  If zero, we don't yet
**		know how big the message will be, so just check for
**		a "reasonable" amount.
**		e -- envelope, or NULL -- controls logging
**
**	Returns:
**		true if in every queue group there is at least one
**		queue directory whose file system contains enough free space.
**		false otherwise.
**
**	Side Effects:
**		If there is not enough disk space and e != NULL
**		then sm_syslog is called.
*/

bool
enoughdiskspace(msize, e)
	long msize;
	ENVELOPE *e;
{
	int i;

	if (MinBlocksFree <= 0 && msize <= 0)
	{
		if (tTd(4, 80))
			sm_dprintf("enoughdiskspace: no threshold\n");
		return true;
	}

	filesys_update();
	for (i = 0; i < NumQueue; ++i)
	{
		if (pickqdir(Queue[i], msize, e) < 0)
			return false;
	}
	return true;
}
/*
**  TRANSIENTERROR -- tell if an error code indicates a transient failure
**
**	This looks at an errno value and tells if this is likely to
**	go away if retried later.
**
**	Parameters:
**		err -- the errno code to classify.
**
**	Returns:
**		true if this is probably transient.
**		false otherwise.
*/

bool
transienterror(err)
	int err;
{
	switch (err)
	{
	  case EIO:			/* I/O error */
	  case ENXIO:			/* Device not configured */
	  case EAGAIN:			/* Resource temporarily unavailable */
	  case ENOMEM:			/* Cannot allocate memory */
	  case ENODEV:			/* Operation not supported by device */
	  case ENFILE:			/* Too many open files in system */
	  case EMFILE:			/* Too many open files */
	  case ENOSPC:			/* No space left on device */
	  case ETIMEDOUT:		/* Connection timed out */
#ifdef ESTALE
	  case ESTALE:			/* Stale NFS file handle */
#endif /* ESTALE */
#ifdef ENETDOWN
	  case ENETDOWN:		/* Network is down */
#endif /* ENETDOWN */
#ifdef ENETUNREACH
	  case ENETUNREACH:		/* Network is unreachable */
#endif /* ENETUNREACH */
#ifdef ENETRESET
	  case ENETRESET:		/* Network dropped connection on reset */
#endif /* ENETRESET */
#ifdef ECONNABORTED
	  case ECONNABORTED:		/* Software caused connection abort */
#endif /* ECONNABORTED */
#ifdef ECONNRESET
	  case ECONNRESET:		/* Connection reset by peer */
#endif /* ECONNRESET */
#ifdef ENOBUFS
	  case ENOBUFS:			/* No buffer space available */
#endif /* ENOBUFS */
#ifdef ESHUTDOWN
	  case ESHUTDOWN:		/* Can't send after socket shutdown */
#endif /* ESHUTDOWN */
#ifdef ECONNREFUSED
	  case ECONNREFUSED:		/* Connection refused */
#endif /* ECONNREFUSED */
#ifdef EHOSTDOWN
	  case EHOSTDOWN:		/* Host is down */
#endif /* EHOSTDOWN */
#ifdef EHOSTUNREACH
	  case EHOSTUNREACH:		/* No route to host */
#endif /* EHOSTUNREACH */
#ifdef EDQUOT
	  case EDQUOT:			/* Disc quota exceeded */
#endif /* EDQUOT */
#ifdef EPROCLIM
	  case EPROCLIM:		/* Too many processes */
#endif /* EPROCLIM */
#ifdef EUSERS
	  case EUSERS:			/* Too many users */
#endif /* EUSERS */
#ifdef EDEADLK
	  case EDEADLK:			/* Resource deadlock avoided */
#endif /* EDEADLK */
#ifdef EISCONN
	  case EISCONN:			/* Socket already connected */
#endif /* EISCONN */
#ifdef EINPROGRESS
	  case EINPROGRESS:		/* Operation now in progress */
#endif /* EINPROGRESS */
#ifdef EALREADY
	  case EALREADY:		/* Operation already in progress */
#endif /* EALREADY */
#ifdef EADDRINUSE
	  case EADDRINUSE:		/* Address already in use */
#endif /* EADDRINUSE */
#ifdef EADDRNOTAVAIL
	  case EADDRNOTAVAIL:		/* Can't assign requested address */
#endif /* EADDRNOTAVAIL */
#ifdef ETXTBSY
	  case ETXTBSY:			/* (Apollo) file locked */
#endif /* ETXTBSY */
#if defined(ENOSR) && (!defined(ENOBUFS) || (ENOBUFS != ENOSR))
	  case ENOSR:			/* Out of streams resources */
#endif /* defined(ENOSR) && (!defined(ENOBUFS) || (ENOBUFS != ENOSR)) */
#ifdef ENOLCK
	  case ENOLCK:			/* No locks available */
#endif /* ENOLCK */
	  case E_SM_OPENTIMEOUT:	/* PSEUDO: open timed out */
		return true;
	}

	/* nope, must be permanent */
	return false;
}
/*
**  LOCKFILE -- lock a file using flock or (shudder) fcntl locking
**
**	Parameters:
**		fd -- the file descriptor of the file.
**		filename -- the file name (for error messages).
**		ext -- the filename extension.
**		type -- type of the lock.  Bits can be:
**			LOCK_EX -- exclusive lock.
**			LOCK_NB -- non-blocking.
**			LOCK_UN -- unlock.
**
**	Returns:
**		true if the lock was acquired.
**		false otherwise.
*/

bool
lockfile(fd, filename, ext, type)
	int fd;
	char *filename;
	char *ext;
	int type;
{
	int i;
	int save_errno;
# if !HASFLOCK
	int action;
	struct flock lfd;

	if (ext == NULL)
		ext = "";

	memset(&lfd, '\0', sizeof(lfd));
	if (bitset(LOCK_UN, type))
		lfd.l_type = F_UNLCK;
	else if (bitset(LOCK_EX, type))
		lfd.l_type = F_WRLCK;
	else
		lfd.l_type = F_RDLCK;

	if (bitset(LOCK_NB, type))
		action = F_SETLK;
	else
		action = F_SETLKW;

	if (tTd(55, 60))
		sm_dprintf("lockfile(%s%s, action=%d, type=%d): ",
			filename, ext, action, lfd.l_type);

	while ((i = fcntl(fd, action, &lfd)) < 0 && errno == EINTR)
		continue;
	if (i >= 0)
	{
		if (tTd(55, 60))
			sm_dprintf("SUCCESS\n");
		return true;
	}
	save_errno = errno;

	if (tTd(55, 60))
		sm_dprintf("(%s) ", sm_errstring(save_errno));

	/*
	**  On SunOS, if you are testing using -oQ/tmp/mqueue or
	**  -oA/tmp/aliases or anything like that, and /tmp is mounted
	**  as type "tmp" (that is, served from swap space), the
	**  previous fcntl will fail with "Invalid argument" errors.
	**  Since this is fairly common during testing, we will assume
	**  that this indicates that the lock is successfully grabbed.
	*/

	if (save_errno == EINVAL)
	{
		if (tTd(55, 60))
			sm_dprintf("SUCCESS\n");
		return true;
	}

	if (!bitset(LOCK_NB, type) ||
	    (save_errno != EACCES && save_errno != EAGAIN))
	{
		int omode = fcntl(fd, F_GETFL, 0);
		uid_t euid = geteuid();

		errno = save_errno;
		syserr("cannot lockf(%s%s, fd=%d, type=%o, omode=%o, euid=%d)",
		       filename, ext, fd, type, omode, euid);
		dumpfd(fd, true, true);
	}
# else /* !HASFLOCK */
	if (ext == NULL)
		ext = "";

	if (tTd(55, 60))
		sm_dprintf("lockfile(%s%s, type=%o): ", filename, ext, type);

	while ((i = flock(fd, type)) < 0 && errno == EINTR)
		continue;
	if (i >= 0)
	{
		if (tTd(55, 60))
			sm_dprintf("SUCCESS\n");
		return true;
	}
	save_errno = errno;

	if (tTd(55, 60))
		sm_dprintf("(%s) ", sm_errstring(save_errno));

	if (!bitset(LOCK_NB, type) || save_errno != EWOULDBLOCK)
	{
		int omode = fcntl(fd, F_GETFL, 0);
		uid_t euid = geteuid();

		errno = save_errno;
		syserr("cannot flock(%s%s, fd=%d, type=%o, omode=%o, euid=%d)",
			filename, ext, fd, type, omode, euid);
		dumpfd(fd, true, true);
	}
# endif /* !HASFLOCK */
	if (tTd(55, 60))
		sm_dprintf("FAIL\n");
	errno = save_errno;
	return false;
}
/*
**  CHOWNSAFE -- tell if chown is "safe" (executable only by root)
**
**	Unfortunately, given that we can't predict other systems on which
**	a remote mounted (NFS) filesystem will be mounted, the answer is
**	almost always that this is unsafe.
**
**	Note also that many operating systems have non-compliant
**	implementations of the _POSIX_CHOWN_RESTRICTED variable and the
**	fpathconf() routine.  According to IEEE 1003.1-1990, if
**	_POSIX_CHOWN_RESTRICTED is defined and not equal to -1, then
**	no non-root process can give away the file.  However, vendors
**	don't take NFS into account, so a comfortable value of
**	_POSIX_CHOWN_RESTRICTED tells us nothing.
**
**	Also, some systems (e.g., IRIX 6.2) return 1 from fpathconf()
**	even on files where chown is not restricted.  Many systems get
**	this wrong on NFS-based filesystems (that is, they say that chown
**	is restricted [safe] on NFS filesystems where it may not be, since
**	other systems can access the same filesystem and do file giveaway;
**	only the NFS server knows for sure!)  Hence, it is important to
**	get the value of SAFENFSPATHCONF correct -- it should be defined
**	_only_ after testing (see test/t_pathconf.c) a system on an unsafe
**	NFS-based filesystem to ensure that you can get meaningful results.
**	If in doubt, assume unsafe!
**
**	You may also need to tweak IS_SAFE_CHOWN -- it should be a
**	condition indicating whether the return from pathconf indicates
**	that chown is safe (typically either > 0 or >= 0 -- there isn't
**	even any agreement about whether a zero return means that a file
**	is or is not safe).  It defaults to "> 0".
**
**	If the parent directory is safe (writable only by owner back
**	to the root) then we can relax slightly and trust fpathconf
**	in more circumstances.  This is really a crock -- if this is an
**	NFS mounted filesystem then we really know nothing about the
**	underlying implementation.  However, most systems pessimize and
**	return an error (EINVAL or EOPNOTSUPP) on NFS filesystems, which
**	we interpret as unsafe, as we should.  Thus, this heuristic gets
**	us into a possible problem only on systems that have a broken
**	pathconf implementation and which are also poorly configured
**	(have :include: files in group- or world-writable directories).
**
**	Parameters:
**		fd -- the file descriptor to check.
**		safedir -- set if the parent directory is safe.
**
**	Returns:
**		true -- if the chown(2) operation is "safe" -- that is,
**			only root can chown the file to an arbitrary user.
**		false -- if an arbitrary user can give away a file.
*/

#ifndef IS_SAFE_CHOWN
# define IS_SAFE_CHOWN	> 0
#endif /* ! IS_SAFE_CHOWN */

bool
chownsafe(fd, safedir)
	int fd;
	bool safedir;
{
# if (!defined(_POSIX_CHOWN_RESTRICTED) || _POSIX_CHOWN_RESTRICTED != -1) && \
    (defined(_PC_CHOWN_RESTRICTED) || defined(_GNU_TYPES_H))
	int rval;

	/* give the system administrator a chance to override */
	if (bitnset(DBS_ASSUMESAFECHOWN, DontBlameSendmail))
		return true;

	/*
	**  Some systems (e.g., SunOS) seem to have the call and the
	**  #define _PC_CHOWN_RESTRICTED, but don't actually implement
	**  the call.  This heuristic checks for that.
	*/

	errno = 0;
	rval = fpathconf(fd, _PC_CHOWN_RESTRICTED);
#  if SAFENFSPATHCONF
	return errno == 0 && rval IS_SAFE_CHOWN;
#  else /* SAFENFSPATHCONF */
	return safedir && errno == 0 && rval IS_SAFE_CHOWN;
#  endif /* SAFENFSPATHCONF */
# else /* (!defined(_POSIX_CHOWN_RESTRICTED) || _POSIX_CHOWN_RESTRICTED != -1) && ... */
	return bitnset(DBS_ASSUMESAFECHOWN, DontBlameSendmail);
# endif /* (!defined(_POSIX_CHOWN_RESTRICTED) || _POSIX_CHOWN_RESTRICTED != -1) && ... */
}
/*
**  RESETLIMITS -- reset system controlled resource limits
**
**	This is to avoid denial-of-service attacks
**
**	Parameters:
**		none
**
**	Returns:
**		none
*/

#if HASSETRLIMIT
# ifdef RLIMIT_NEEDS_SYS_TIME_H
#  include <sm/time.h>
# endif /* RLIMIT_NEEDS_SYS_TIME_H */
# include <sys/resource.h>
#endif /* HASSETRLIMIT */

void
resetlimits()
{
#if HASSETRLIMIT
	struct rlimit lim;

	lim.rlim_cur = lim.rlim_max = RLIM_INFINITY;
	(void) setrlimit(RLIMIT_CPU, &lim);
	(void) setrlimit(RLIMIT_FSIZE, &lim);
# ifdef RLIMIT_NOFILE
	lim.rlim_cur = lim.rlim_max = FD_SETSIZE;
	(void) setrlimit(RLIMIT_NOFILE, &lim);
# endif /* RLIMIT_NOFILE */
#else /* HASSETRLIMIT */
# if HASULIMIT
	(void) ulimit(2, 0x3fffff);
	(void) ulimit(4, FD_SETSIZE);
# endif /* HASULIMIT */
#endif /* HASSETRLIMIT */
	errno = 0;
}
/*
**  SETVENDOR -- process vendor code from V configuration line
**
**	Parameters:
**		vendor -- string representation of vendor.
**
**	Returns:
**		true -- if ok.
**		false -- if vendor code could not be processed.
**
**	Side Effects:
**		It is reasonable to set mode flags here to tweak
**		processing in other parts of the code if necessary.
**		For example, if you are a vendor that uses $%y to
**		indicate YP lookups, you could enable that here.
*/

bool
setvendor(vendor)
	char *vendor;
{
	if (sm_strcasecmp(vendor, "Berkeley") == 0)
	{
		VendorCode = VENDOR_BERKELEY;
		return true;
	}

	/* add vendor extensions here */

#ifdef SUN_EXTENSIONS
	if (sm_strcasecmp(vendor, "Sun") == 0)
	{
		VendorCode = VENDOR_SUN;
		return true;
	}
#endif /* SUN_EXTENSIONS */
#ifdef DEC
	if (sm_strcasecmp(vendor, "Digital") == 0)
	{
		VendorCode = VENDOR_DEC;
		return true;
	}
#endif /* DEC */

#if defined(VENDOR_NAME) && defined(VENDOR_CODE)
	if (sm_strcasecmp(vendor, VENDOR_NAME) == 0)
	{
		VendorCode = VENDOR_CODE;
		return true;
	}
#endif /* defined(VENDOR_NAME) && defined(VENDOR_CODE) */

	return false;
}
/*
**  GETVENDOR -- return vendor name based on vendor code
**
**	Parameters:
**		vendorcode -- numeric representation of vendor.
**
**	Returns:
**		string containing vendor name.
*/

char *
getvendor(vendorcode)
	int vendorcode;
{
#if defined(VENDOR_NAME) && defined(VENDOR_CODE)
	/*
	**  Can't have the same switch case twice so need to
	**  handle VENDOR_CODE outside of switch.  It might
	**  match one of the existing VENDOR_* codes.
	*/

	if (vendorcode == VENDOR_CODE)
		return VENDOR_NAME;
#endif /* defined(VENDOR_NAME) && defined(VENDOR_CODE) */

	switch (vendorcode)
	{
	  case VENDOR_BERKELEY:
		return "Berkeley";

	  case VENDOR_SUN:
		return "Sun";

	  case VENDOR_HP:
		return "HP";

	  case VENDOR_IBM:
		return "IBM";

	  case VENDOR_SENDMAIL:
		return "Sendmail";

	  default:
		return "Unknown";
	}
}
/*
**  VENDOR_PRE_DEFAULTS, VENDOR_POST_DEFAULTS -- set vendor-specific defaults
**
**	Vendor_pre_defaults is called before reading the configuration
**	file; vendor_post_defaults is called immediately after.
**
**	Parameters:
**		e -- the global environment to initialize.
**
**	Returns:
**		none.
*/

#if SHARE_V1
int	DefShareUid;	/* default share uid to run as -- unused??? */
#endif /* SHARE_V1 */

void
vendor_pre_defaults(e)
	ENVELOPE *e;
{
#if SHARE_V1
	/* OTHERUID is defined in shares.h, do not be alarmed */
	DefShareUid = OTHERUID;
#endif /* SHARE_V1 */
#if defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES)
	sun_pre_defaults(e);
#endif /* defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES) */
#ifdef apollo
	/*
	**  stupid domain/os can't even open
	**  /etc/mail/sendmail.cf without this
	*/

	sm_setuserenv("ISP", NULL);
	sm_setuserenv("SYSTYPE", NULL);
#endif /* apollo */
}


void
vendor_post_defaults(e)
	ENVELOPE *e;
{
#ifdef __QNX__
	/* Makes sure the SOCK environment variable remains */
	sm_setuserenv("SOCK", NULL);
#endif /* __QNX__ */
#if defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES)
	sun_post_defaults(e);
#endif /* defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES) */
}
/*
**  VENDOR_DAEMON_SETUP -- special vendor setup needed for daemon mode
*/

void
vendor_daemon_setup(e)
	ENVELOPE *e;
{
#if HASSETLOGIN
	(void) setlogin(RunAsUserName);
#endif /* HASSETLOGIN */
#if SECUREWARE
	if (getluid() != -1)
	{
		usrerr("Daemon cannot have LUID");
		finis(false, true, EX_USAGE);
	}
#endif /* SECUREWARE */
}
/*
**  VENDOR_SET_UID -- do setup for setting a user id
**
**	This is called when we are still root.
**
**	Parameters:
**		uid -- the uid we are about to become.
**
**	Returns:
**		none.
*/

void
vendor_set_uid(uid)
	UID_T uid;
{
	/*
	**  We need to setup the share groups (lnodes)
	**  and add auditing information (luid's)
	**  before we loose our ``root''ness.
	*/
#if SHARE_V1
	if (setupshares(uid, syserr) != 0)
		syserr("Unable to set up shares");
#endif /* SHARE_V1 */
#if SECUREWARE
	(void) setup_secure(uid);
#endif /* SECUREWARE */
}
/*
**  VALIDATE_CONNECTION -- check connection for rationality
**
**	If the connection is rejected, this routine should log an
**	appropriate message -- but should never issue any SMTP protocol.
**
**	Parameters:
**		sap -- a pointer to a SOCKADDR naming the peer.
**		hostname -- the name corresponding to sap.
**		e -- the current envelope.
**
**	Returns:
**		error message from rejection.
**		NULL if not rejected.
*/

#if TCPWRAPPERS
# include <tcpd.h>

/* tcpwrappers does no logging, but you still have to declare these -- ugh */
int	allow_severity	= LOG_INFO;
int	deny_severity	= LOG_NOTICE;
#endif /* TCPWRAPPERS */

char *
validate_connection(sap, hostname, e)
	SOCKADDR *sap;
	char *hostname;
	ENVELOPE *e;
{
#if TCPWRAPPERS
	char *host;
	char *addr;
	extern int hosts_ctl();
#endif /* TCPWRAPPERS */

	if (tTd(48, 3))
		sm_dprintf("validate_connection(%s, %s)\n",
			hostname, anynet_ntoa(sap));

	connection_rate_check(sap, e);
	if (rscheck("check_relay", hostname, anynet_ntoa(sap),
		    e, RSF_RMCOMM|RSF_COUNT, 3, NULL, NOQID, NULL) != EX_OK)
	{
		static char reject[BUFSIZ*2];
		extern char MsgBuf[];

		if (tTd(48, 4))
			sm_dprintf("  ... validate_connection: BAD (rscheck)\n");

		if (strlen(MsgBuf) >= 3)
			(void) sm_strlcpy(reject, MsgBuf, sizeof(reject));
		else
			(void) sm_strlcpy(reject, "Access denied", sizeof(reject));

		return reject;
	}

#if TCPWRAPPERS
	if (hostname[0] == '[' && hostname[strlen(hostname) - 1] == ']')
		host = "unknown";
	else
		host = hostname;
	addr = anynet_ntoa(sap);

# if NETINET6
	/* TCP/Wrappers don't want the IPv6: protocol label */
	if (addr != NULL && sm_strncasecmp(addr, "IPv6:", 5) == 0)
		addr += 5;
# endif /* NETINET6 */

	if (!hosts_ctl("sendmail", host, addr, STRING_UNKNOWN))
	{
		if (tTd(48, 4))
			sm_dprintf("  ... validate_connection: BAD (tcpwrappers)\n");
		if (LogLevel > 3)
			sm_syslog(LOG_NOTICE, e->e_id,
				  "tcpwrappers (%s, %s) rejection",
				  host, addr);
		return "Access denied";
	}
#endif /* TCPWRAPPERS */
	if (tTd(48, 4))
		sm_dprintf("  ... validate_connection: OK\n");
	return NULL;
}

/*
**  STRTOL -- convert string to long integer
**
**	For systems that don't have it in the C library.
**
**	This is taken verbatim from the 4.4-Lite C library.
*/

#if NEEDSTRTOL

# if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)strtol.c	8.1 (Berkeley) 6/4/93";
# endif /* defined(LIBC_SCCS) && !defined(lint) */

/*
**  Convert a string to a long integer.
**
**  Ignores `locale' stuff.  Assumes that the upper and lower case
**  alphabets and digits are each contiguous.
*/

long
strtol(nptr, endptr, base)
	const char *nptr;
	char **endptr;
	register int base;
{
	register const char *s = nptr;
	register unsigned long acc;
	register int c;
	register unsigned long cutoff;
	register int neg = 0, any, cutlim;

	/*
	**  Skip white space and pick up leading +/- sign if any.
	**  If base is 0, allow 0x for hex and 0 for octal, else
	**  assume decimal; if base is already 16, allow 0x.
	*/
	do {
		c = *s++;
	} while (isspace(c));
	if (c == '-') {
		neg = 1;
		c = *s++;
	} else if (c == '+')
		c = *s++;
	if ((base == 0 || base == 16) &&
	    c == '0' && (*s == 'x' || *s == 'X')) {
		c = s[1];
		s += 2;
		base = 16;
	}
	if (base == 0)
		base = c == '0' ? 8 : 10;

	/*
	**  Compute the cutoff value between legal numbers and illegal
	**  numbers.  That is the largest legal value, divided by the
	**  base.  An input number that is greater than this value, if
	**  followed by a legal input character, is too big.  One that
	**  is equal to this value may be valid or not; the limit
	**  between valid and invalid numbers is then based on the last
	**  digit.  For instance, if the range for longs is
	**  [-2147483648..2147483647] and the input base is 10,
	**  cutoff will be set to 214748364 and cutlim to either
	**  7 (neg==0) or 8 (neg==1), meaning that if we have accumulated
	**  a value > 214748364, or equal but the next digit is > 7 (or 8),
	**  the number is too big, and we will return a range error.
	**
	**  Set any if any `digits' consumed; make it negative to indicate
	**  overflow.
	*/
	cutoff = neg ? -(unsigned long) LONG_MIN : LONG_MAX;
	cutlim = cutoff % (unsigned long) base;
	cutoff /= (unsigned long) base;
	for (acc = 0, any = 0;; c = *s++) {
		if (isdigit(c))
			c -= '0';
		else if (isalpha(c))
			c -= isupper(c) ? 'A' - 10 : 'a' - 10;
		else
			break;
		if (c >= base)
			break;
		if (any < 0 || acc > cutoff || acc == cutoff && c > cutlim)
			any = -1;
		else {
			any = 1;
			acc *= base;
			acc += c;
		}
	}
	if (any < 0) {
		acc = neg ? LONG_MIN : LONG_MAX;
		errno = ERANGE;
	} else if (neg)
		acc = -acc;
	if (endptr != 0)
		*endptr = (char *)(any ? s - 1 : nptr);
	return acc;
}

#endif /* NEEDSTRTOL */
/*
**  STRSTR -- find first substring in string
**
**	Parameters:
**		big -- the big (full) string.
**		little -- the little (sub) string.
**
**	Returns:
**		A pointer to the first instance of little in big.
**		big if little is the null string.
**		NULL if little is not contained in big.
*/

#if NEEDSTRSTR

char *
strstr(big, little)
	char *big;
	char *little;
{
	register char *p = big;
	int l;

	if (*little == '\0')
		return big;
	l = strlen(little);

	while ((p = strchr(p, *little)) != NULL)
	{
		if (strncmp(p, little, l) == 0)
			return p;
		p++;
	}
	return NULL;
}

#endif /* NEEDSTRSTR */
/*
**  SM_GETHOSTBY{NAME,ADDR} -- compatibility routines for gethostbyXXX
**
**	Some operating systems have wierd problems with the gethostbyXXX
**	routines.  For example, Solaris versions at least through 2.3
**	don't properly deliver a canonical h_name field.  This tries to
**	work around these problems.
**
**	Support IPv6 as well as IPv4.
*/

#if NETINET6 && NEEDSGETIPNODE

# ifndef AI_DEFAULT
#  define AI_DEFAULT	0	/* dummy */
# endif /* ! AI_DEFAULT */
# ifndef AI_ADDRCONFIG
#  define AI_ADDRCONFIG	0	/* dummy */
# endif /* ! AI_ADDRCONFIG */
# ifndef AI_V4MAPPED
#  define AI_V4MAPPED	0	/* dummy */
# endif /* ! AI_V4MAPPED */
# ifndef AI_ALL
#  define AI_ALL	0	/* dummy */
# endif /* ! AI_ALL */

static struct hostent *
getipnodebyname(name, family, flags, err)
	char *name;
	int family;
	int flags;
	int *err;
{
	bool resv6 = true;
	struct hostent *h;

	if (family == AF_INET6)
	{
		/* From RFC2133, section 6.1 */
		resv6 = bitset(RES_USE_INET6, _res.options);
		_res.options |= RES_USE_INET6;
	}
	SM_SET_H_ERRNO(0);
	h = gethostbyname(name);
	if (!resv6)
		_res.options &= ~RES_USE_INET6;
	*err = h_errno;
	return h;
}

static struct hostent *
getipnodebyaddr(addr, len, family, err)
	char *addr;
	int len;
	int family;
	int *err;
{
	struct hostent *h;

	SM_SET_H_ERRNO(0);
	h = gethostbyaddr(addr, len, family);
	*err = h_errno;
	return h;
}

void
freehostent(h)
	struct hostent *h;
{
	/*
	**  Stub routine -- if they don't have getipnodeby*(),
	**  they probably don't have the free routine either.
	*/

	return;
}
#endif /* NETINET6 && NEEDSGETIPNODE */

struct hostent *
sm_gethostbyname(name, family)
	char *name;
	int family;
{
	int save_errno;
	struct hostent *h = NULL;
#if (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4))
# if SOLARIS == 20300 || SOLARIS == 203
	static struct hostent hp;
	static char buf[1000];
	extern struct hostent *_switch_gethostbyname_r();

	if (tTd(61, 10))
		sm_dprintf("_switch_gethostbyname_r(%s)... ", name);
	h = _switch_gethostbyname_r(name, &hp, buf, sizeof(buf), &h_errno);
	save_errno = errno;
# else /* SOLARIS == 20300 || SOLARIS == 203 */
	extern struct hostent *__switch_gethostbyname();

	if (tTd(61, 10))
		sm_dprintf("__switch_gethostbyname(%s)... ", name);
	h = __switch_gethostbyname(name);
	save_errno = errno;
# endif /* SOLARIS == 20300 || SOLARIS == 203 */
#else /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */
	int nmaps;
# if NETINET6
	int flags = AI_DEFAULT|AI_ALL;
	int err;
# endif /* NETINET6 */
	char *maptype[MAXMAPSTACK];
	short mapreturn[MAXMAPACTIONS];
	char hbuf[MAXNAME];

	if (tTd(61, 10))
		sm_dprintf("sm_gethostbyname(%s, %d)... ", name, family);

# if NETINET6
#  if ADDRCONFIG_IS_BROKEN
	flags &= ~AI_ADDRCONFIG;
#  endif /* ADDRCONFIG_IS_BROKEN */
	h = getipnodebyname(name, family, flags, &err);
	SM_SET_H_ERRNO(err);
# else /* NETINET6 */
	h = gethostbyname(name);
# endif /* NETINET6 */

	save_errno = errno;
	if (h == NULL)
	{
		if (tTd(61, 10))
			sm_dprintf("failure\n");

		nmaps = switch_map_find("hosts", maptype, mapreturn);
		while (--nmaps >= 0)
		{
			if (strcmp(maptype[nmaps], "nis") == 0 ||
			    strcmp(maptype[nmaps], "files") == 0)
				break;
		}

		if (nmaps >= 0)
		{
			/* try short name */
			if (strlen(name) > sizeof(hbuf) - 1)
			{
				errno = save_errno;
				return NULL;
			}
			(void) sm_strlcpy(hbuf, name, sizeof(hbuf));
			(void) shorten_hostname(hbuf);

			/* if it hasn't been shortened, there's no point */
			if (strcmp(hbuf, name) != 0)
			{
				if (tTd(61, 10))
					sm_dprintf("sm_gethostbyname(%s, %d)... ",
					       hbuf, family);

# if NETINET6
				h = getipnodebyname(hbuf, family, flags, &err);
				SM_SET_H_ERRNO(err);
				save_errno = errno;
# else /* NETINET6 */
				h = gethostbyname(hbuf);
				save_errno = errno;
# endif /* NETINET6 */
			}
		}
	}
#endif /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */
	if (tTd(61, 10))
	{
		if (h == NULL)
			sm_dprintf("failure\n");
		else
		{
			sm_dprintf("%s\n", h->h_name);
			if (tTd(61, 11))
			{
#if NETINET6
				struct in6_addr ia6;
				char buf6[INET6_ADDRSTRLEN];
#else /* NETINET6 */
				struct in_addr ia;
#endif /* NETINET6 */
				size_t i;

				if (h->h_aliases != NULL)
					for (i = 0; h->h_aliases[i] != NULL;
					     i++)
						sm_dprintf("\talias: %s\n",
							h->h_aliases[i]);
				for (i = 0; h->h_addr_list[i] != NULL; i++)
				{
					char *addr;

#if NETINET6
					memmove(&ia6, h->h_addr_list[i],
						IN6ADDRSZ);
					addr = anynet_ntop(&ia6,
							   buf6, sizeof(buf6));
#else /* NETINET6 */
					memmove(&ia, h->h_addr_list[i],
						INADDRSZ);
					addr = (char *) inet_ntoa(ia);
#endif /* NETINET6 */
					if (addr != NULL)
						sm_dprintf("\taddr: %s\n", addr);
				}
			}
		}
	}
	errno = save_errno;
	return h;
}

struct hostent *
sm_gethostbyaddr(addr, len, type)
	char *addr;
	int len;
	int type;
{
	struct hostent *hp;

#if NETINET6
	if (type == AF_INET6 &&
	    IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *) addr))
	{
		/* Avoid reverse lookup for IPv6 unspecified address */
		SM_SET_H_ERRNO(HOST_NOT_FOUND);
		return NULL;
	}
#endif /* NETINET6 */

#if (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204)
# if SOLARIS == 20300 || SOLARIS == 203
	{
		static struct hostent he;
		static char buf[1000];
		extern struct hostent *_switch_gethostbyaddr_r();

		hp = _switch_gethostbyaddr_r(addr, len, type, &he,
					     buf, sizeof(buf), &h_errno);
	}
# else /* SOLARIS == 20300 || SOLARIS == 203 */
	{
		extern struct hostent *__switch_gethostbyaddr();

		hp = __switch_gethostbyaddr(addr, len, type);
	}
# endif /* SOLARIS == 20300 || SOLARIS == 203 */
#else /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) */
# if NETINET6
	{
		int err;

		hp = getipnodebyaddr(addr, len, type, &err);
		SM_SET_H_ERRNO(err);
	}
# else /* NETINET6 */
	hp = gethostbyaddr(addr, len, type);
# endif /* NETINET6 */
#endif /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) */
	return hp;
}
/*
**  SM_GETPW{NAM,UID} -- wrapper for getpwnam and getpwuid
*/

struct passwd *
sm_getpwnam(user)
	char *user;
{
#ifdef _AIX4
	extern struct passwd *_getpwnam_shadow(const char *, const int);

	return _getpwnam_shadow(user, 0);
#else /* _AIX4 */
	return getpwnam(user);
#endif /* _AIX4 */
}

struct passwd *
sm_getpwuid(uid)
	UID_T uid;
{
#if defined(_AIX4) && 0
	extern struct passwd *_getpwuid_shadow(const int, const int);

	return _getpwuid_shadow(uid,0);
#else /* defined(_AIX4) && 0 */
	return getpwuid(uid);
#endif /* defined(_AIX4) && 0 */
}
/*
**  SECUREWARE_SETUP_SECURE -- Convex SecureWare setup
**
**	Set up the trusted computing environment for C2 level security
**	under SecureWare.
**
**	Parameters:
**		uid -- uid of the user to initialize in the TCB
**
**	Returns:
**		none
**
**	Side Effects:
**		Initialized the user in the trusted computing base
*/

#if SECUREWARE

# include <sys/security.h>
# include <prot.h>

void
secureware_setup_secure(uid)
	UID_T uid;
{
	int rc;

	if (getluid() != -1)
		return;

	if ((rc = set_secure_info(uid)) != SSI_GOOD_RETURN)
	{
		switch (rc)
		{
		  case SSI_NO_PRPW_ENTRY:
			syserr("No protected passwd entry, uid = %d",
			       (int) uid);
			break;

		  case SSI_LOCKED:
			syserr("Account has been disabled, uid = %d",
			       (int) uid);
			break;

		  case SSI_RETIRED:
			syserr("Account has been retired, uid = %d",
			       (int) uid);
			break;

		  case SSI_BAD_SET_LUID:
			syserr("Could not set LUID, uid = %d", (int) uid);
			break;

		  case SSI_BAD_SET_PRIVS:
			syserr("Could not set kernel privs, uid = %d",
			       (int) uid);

		  default:
			syserr("Unknown return code (%d) from set_secure_info(%d)",
				rc, (int) uid);
			break;
		}
		finis(false, true, EX_NOPERM);
	}
}
#endif /* SECUREWARE */
/*
**  ADD_HOSTNAMES -- Add a hostname to class 'w' based on IP address
**
**	Add hostnames to class 'w' based on the IP address read from
**	the network interface.
**
**	Parameters:
**		sa -- a pointer to a SOCKADDR containing the address
**
**	Returns:
**		0 if successful, -1 if host lookup fails.
*/

static int
add_hostnames(sa)
	SOCKADDR *sa;
{
	struct hostent *hp;
	char **ha;
	char hnb[MAXHOSTNAMELEN];

	/* lookup name with IP address */
	switch (sa->sa.sa_family)
	{
#if NETINET
	  case AF_INET:
		hp = sm_gethostbyaddr((char *) &sa->sin.sin_addr,
				      sizeof(sa->sin.sin_addr),
				      sa->sa.sa_family);
		break;
#endif /* NETINET */

#if NETINET6
	  case AF_INET6:
		hp = sm_gethostbyaddr((char *) &sa->sin6.sin6_addr,
				      sizeof(sa->sin6.sin6_addr),
				      sa->sa.sa_family);
		break;
#endif /* NETINET6 */

	  default:
		/* Give warning about unsupported family */
		if (LogLevel > 3)
			sm_syslog(LOG_WARNING, NOQID,
				  "Unsupported address family %d: %.100s",
				  sa->sa.sa_family, anynet_ntoa(sa));
		return -1;
	}

	if (hp == NULL)
	{
		int save_errno = errno;

		if (LogLevel > 3 &&
#if NETINET6
		    !(sa->sa.sa_family == AF_INET6 &&
		      IN6_IS_ADDR_LINKLOCAL(&sa->sin6.sin6_addr)) &&
#endif /* NETINET6 */
		    true)
			sm_syslog(LOG_WARNING, NOQID,
				  "gethostbyaddr(%.100s) failed: %d",
				  anynet_ntoa(sa),
#if NAMED_BIND
				  h_errno
#else /* NAMED_BIND */
				  -1
#endif /* NAMED_BIND */
				 );
		errno = save_errno;
		return -1;
	}

	/* save its cname */
	if (!wordinclass((char *) hp->h_name, 'w'))
	{
		setclass('w', (char *) hp->h_name);
		if (tTd(0, 4))
			sm_dprintf("\ta.k.a.: %s\n", hp->h_name);

		if (sm_snprintf(hnb, sizeof(hnb), "[%s]", hp->h_name) <
								sizeof(hnb)
		    && !wordinclass((char *) hnb, 'w'))
			setclass('w', hnb);
	}
	else
	{
		if (tTd(0, 43))
			sm_dprintf("\ta.k.a.: %s (already in $=w)\n", hp->h_name);
	}

	/* save all it aliases name */
	for (ha = hp->h_aliases; ha != NULL && *ha != NULL; ha++)
	{
		if (!wordinclass(*ha, 'w'))
		{
			setclass('w', *ha);
			if (tTd(0, 4))
				sm_dprintf("\ta.k.a.: %s\n", *ha);
			if (sm_snprintf(hnb, sizeof(hnb),
				     "[%s]", *ha) < sizeof(hnb) &&
			    !wordinclass((char *) hnb, 'w'))
				setclass('w', hnb);
		}
		else
		{
			if (tTd(0, 43))
				sm_dprintf("\ta.k.a.: %s (already in $=w)\n",
					*ha);
		}
	}
#if NETINET6
	freehostent(hp);
#endif /* NETINET6 */
	return 0;
}
/*
**  LOAD_IF_NAMES -- load interface-specific names into $=w
**
**	Parameters:
**		none.
**
**	Returns:
**		none.
**
**	Side Effects:
**		Loads $=w with the names of all the interfaces.
*/

#if !NETINET
# define SIOCGIFCONF_IS_BROKEN	1 /* XXX */
#endif /* !NETINET */

#if defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN
struct rtentry;
struct mbuf;
# ifndef SUNOS403
#  include <sm/time.h>
# endif /* ! SUNOS403 */
# if (_AIX4 >= 40300) && !defined(_NET_IF_H)
#  undef __P
# endif /* (_AIX4 >= 40300) && !defined(_NET_IF_H) */
# include <net/if.h>
#endif /* defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN */

void
load_if_names()
{
# if NETINET6 && defined(SIOCGLIFCONF)
#  ifdef __hpux

    /*
    **  Unfortunately, HP has changed all of the structures,
    **  making life difficult for implementors.
    */

#   define lifconf	if_laddrconf
#   define lifc_len	iflc_len
#   define lifc_buf	iflc_buf
#   define lifreq	if_laddrreq
#   define lifr_addr	iflr_addr
#   define lifr_name	iflr_name
#   define lifr_flags	iflr_flags
#   define ss_family	sa_family
#   undef SIOCGLIFNUM
#  endif /* __hpux */

	int s;
	int i;
	size_t len;
	int numifs;
	char *buf;
	struct lifconf lifc;
#  ifdef SIOCGLIFNUM
	struct lifnum lifn;
#  endif /* SIOCGLIFNUM */

	s = socket(InetMode, SOCK_DGRAM, 0);
	if (s == -1)
		return;

	/* get the list of known IP address from the kernel */
#  ifdef __hpux
	i = ioctl(s, SIOCGIFNUM, (char *) &numifs);
#  endif /* __hpux */
#  ifdef SIOCGLIFNUM
	lifn.lifn_family = AF_UNSPEC;
	lifn.lifn_flags = 0;
	i = ioctl(s, SIOCGLIFNUM, (char *)&lifn);
	numifs = lifn.lifn_count;
#  endif /* SIOCGLIFNUM */

#  if defined(__hpux) || defined(SIOCGLIFNUM)
	if (i < 0)
	{
		/* can't get number of interfaces -- fall back */
		if (tTd(0, 4))
			sm_dprintf("SIOCGLIFNUM failed: %s\n",
				   sm_errstring(errno));
		numifs = -1;
	}
	else if (tTd(0, 42))
		sm_dprintf("system has %d interfaces\n", numifs);
	if (numifs < 0)
#  endif /* defined(__hpux) || defined(SIOCGLIFNUM) */
		numifs = MAXINTERFACES;

	if (numifs <= 0)
	{
		(void) close(s);
		return;
	}

	len = lifc.lifc_len = numifs * sizeof(struct lifreq);
	buf = lifc.lifc_buf = xalloc(lifc.lifc_len);
#  ifndef __hpux
	lifc.lifc_family = AF_UNSPEC;
	lifc.lifc_flags = 0;
#  endif /* ! __hpux */
	if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0)
	{
		if (tTd(0, 4))
			sm_dprintf("SIOCGLIFCONF failed: %s\n",
				   sm_errstring(errno));
		(void) close(s);
		sm_free(buf);
		return;
	}

	/* scan the list of IP address */
	if (tTd(0, 40))
		sm_dprintf("scanning for interface specific names, lifc_len=%ld\n",
			   (long) len);

	for (i = 0; i < len && i >= 0; )
	{
		int flags;
		struct lifreq *ifr = (struct lifreq *)&buf[i];
		SOCKADDR *sa = (SOCKADDR *) &ifr->lifr_addr;
		int af = ifr->lifr_addr.ss_family;
		char *addr;
		char *name;
		struct in6_addr ia6;
		struct in_addr ia;
#  ifdef SIOCGLIFFLAGS
		struct lifreq ifrf;
#  endif /* SIOCGLIFFLAGS */
		char ip_addr[256];
		char buf6[INET6_ADDRSTRLEN];

		/*
		**  We must close and recreate the socket each time
		**  since we don't know what type of socket it is now
		**  (each status function may change it).
		*/

		(void) close(s);

		s = socket(af, SOCK_DGRAM, 0);
		if (s == -1)
		{
			sm_free(buf); /* XXX */
			return;
		}

		/*
		**  If we don't have a complete ifr structure,
		**  don't try to use it.
		*/

		if ((len - i) < sizeof(*ifr))
			break;

#  ifdef BSD4_4_SOCKADDR
		if (sa->sa.sa_len > sizeof(ifr->lifr_addr))
			i += sizeof(ifr->lifr_name) + sa->sa.sa_len;
		else
#  endif /* BSD4_4_SOCKADDR */
#  ifdef DEC
			/* fix for IPv6  size differences */
			i += sizeof(ifr->ifr_name) +
			     max(sizeof(ifr->ifr_addr), ifr->ifr_addr.sa_len);
#   else /* DEC */
			i += sizeof(*ifr);
#   endif /* DEC */

		if (tTd(0, 20))
			sm_dprintf("%s\n", anynet_ntoa(sa));

		if (af != AF_INET && af != AF_INET6)
			continue;

#  ifdef SIOCGLIFFLAGS
		memset(&ifrf, '\0', sizeof(struct lifreq));
		(void) sm_strlcpy(ifrf.lifr_name, ifr->lifr_name,
				  sizeof(ifrf.lifr_name));
		if (ioctl(s, SIOCGLIFFLAGS, (char *) &ifrf) < 0)
		{
			if (tTd(0, 4))
				sm_dprintf("SIOCGLIFFLAGS failed: %s\n",
					   sm_errstring(errno));
			continue;
		}

		name = ifr->lifr_name;
		flags = ifrf.lifr_flags;

		if (tTd(0, 41))
			sm_dprintf("\tflags: %lx\n", (unsigned long) flags);

		if (!bitset(IFF_UP, flags))
			continue;
#  endif /* SIOCGLIFFLAGS */

		ip_addr[0] = '\0';

		/* extract IP address from the list*/
		switch (af)
		{
		  case AF_INET6:
#  ifdef __KAME__
			/* convert into proper scoped address */
			if ((IN6_IS_ADDR_LINKLOCAL(&sa->sin6.sin6_addr) ||
			     IN6_IS_ADDR_SITELOCAL(&sa->sin6.sin6_addr)) &&
			    sa->sin6.sin6_scope_id == 0)
			{
				struct in6_addr *ia6p;

				ia6p = &sa->sin6.sin6_addr;
				sa->sin6.sin6_scope_id = ntohs(ia6p->s6_addr[3] |
							       ((unsigned int)ia6p->s6_addr[2] << 8));
				ia6p->s6_addr[2] = ia6p->s6_addr[3] = 0;
			}
#  endif /* __KAME__ */
			ia6 = sa->sin6.sin6_addr;
			if (IN6_IS_ADDR_UNSPECIFIED(&ia6))
			{
				addr = anynet_ntop(&ia6, buf6, sizeof(buf6));
				message("WARNING: interface %s is UP with %s address",
					name, addr == NULL ? "(NULL)" : addr);
				continue;
			}

			/* save IP address in text from */
			addr = anynet_ntop(&ia6, buf6, sizeof(buf6));
			if (addr != NULL)
				(void) sm_snprintf(ip_addr, sizeof(ip_addr),
						   "[%.*s]",
						   (int) sizeof(ip_addr) - 3,
						   addr);
			break;

		  case AF_INET:
			ia = sa->sin.sin_addr;
			if (ia.s_addr == INADDR_ANY ||
			    ia.s_addr == INADDR_NONE)
			{
				message("WARNING: interface %s is UP with %s address",
					name, inet_ntoa(ia));
				continue;
			}

			/* save IP address in text from */
			(void) sm_snprintf(ip_addr, sizeof(ip_addr), "[%.*s]",
					(int) sizeof(ip_addr) - 3, inet_ntoa(ia));
			break;
		}

		if (*ip_addr == '\0')
			continue;

		if (!wordinclass(ip_addr, 'w'))
		{
			setclass('w', ip_addr);
			if (tTd(0, 4))
				sm_dprintf("\ta.k.a.: %s\n", ip_addr);
		}

#  ifdef SIOCGLIFFLAGS
		/* skip "loopback" interface "lo" */
		if (DontProbeInterfaces == DPI_SKIPLOOPBACK &&
		    bitset(IFF_LOOPBACK, flags))
			continue;
#  endif /* SIOCGLIFFLAGS */
		(void) add_hostnames(sa);
	}
	sm_free(buf); /* XXX */
	(void) close(s);
# else /* NETINET6 && defined(SIOCGLIFCONF) */
#  if defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN
	int s;
	int i;
	struct ifconf ifc;
	int numifs;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s == -1)
		return;

	/* get the list of known IP address from the kernel */
#   if defined(SIOCGIFNUM) && !SIOCGIFNUM_IS_BROKEN
	if (ioctl(s, SIOCGIFNUM, (char *) &numifs) < 0)
	{
		/* can't get number of interfaces -- fall back */
		if (tTd(0, 4))
			sm_dprintf("SIOCGIFNUM failed: %s\n",
				   sm_errstring(errno));
		numifs = -1;
	}
	else if (tTd(0, 42))
		sm_dprintf("system has %d interfaces\n", numifs);
	if (numifs < 0)
#   endif /* defined(SIOCGIFNUM) && !SIOCGIFNUM_IS_BROKEN */
		numifs = MAXINTERFACES;

	if (numifs <= 0)
	{
		(void) close(s);
		return;
	}
	ifc.ifc_len = numifs * sizeof(struct ifreq);
	ifc.ifc_buf = xalloc(ifc.ifc_len);
	if (ioctl(s, SIOCGIFCONF, (char *)&ifc) < 0)
	{
		if (tTd(0, 4))
			sm_dprintf("SIOCGIFCONF failed: %s\n",
				   sm_errstring(errno));
		(void) close(s);
		return;
	}

	/* scan the list of IP address */
	if (tTd(0, 40))
		sm_dprintf("scanning for interface specific names, ifc_len=%d\n",
			ifc.ifc_len);

	for (i = 0; i < ifc.ifc_len && i >= 0; )
	{
		int af;
		struct ifreq *ifr = (struct ifreq *) &ifc.ifc_buf[i];
		SOCKADDR *sa = (SOCKADDR *) &ifr->ifr_addr;
#   if NETINET6
		char *addr;
		struct in6_addr ia6;
#   endif /* NETINET6 */
		struct in_addr ia;
#   ifdef SIOCGIFFLAGS
		struct ifreq ifrf;
#   endif /* SIOCGIFFLAGS */
		char ip_addr[256];
#   if NETINET6
		char buf6[INET6_ADDRSTRLEN];
#   endif /* NETINET6 */

		/*
		**  If we don't have a complete ifr structure,
		**  don't try to use it.
		*/

		if ((ifc.ifc_len - i) < sizeof(*ifr))
			break;

#   ifdef BSD4_4_SOCKADDR
		if (sa->sa.sa_len > sizeof(ifr->ifr_addr))
			i += sizeof(ifr->ifr_name) + sa->sa.sa_len;
		else
#   endif /* BSD4_4_SOCKADDR */
			i += sizeof(*ifr);

		if (tTd(0, 20))
			sm_dprintf("%s\n", anynet_ntoa(sa));

		af = ifr->ifr_addr.sa_family;
		if (af != AF_INET
#   if NETINET6
		    && af != AF_INET6
#   endif /* NETINET6 */
		    )
			continue;

#   ifdef SIOCGIFFLAGS
		memset(&ifrf, '\0', sizeof(struct ifreq));
		(void) sm_strlcpy(ifrf.ifr_name, ifr->ifr_name,
			       sizeof(ifrf.ifr_name));
		(void) ioctl(s, SIOCGIFFLAGS, (char *) &ifrf);
		if (tTd(0, 41))
			sm_dprintf("\tflags: %lx\n",
				(unsigned long) ifrf.ifr_flags);
#    define IFRFREF ifrf
#   else /* SIOCGIFFLAGS */
#    define IFRFREF (*ifr)
#   endif /* SIOCGIFFLAGS */

		if (!bitset(IFF_UP, IFRFREF.ifr_flags))
			continue;

		ip_addr[0] = '\0';

		/* extract IP address from the list*/
		switch (af)
		{
		  case AF_INET:
			ia = sa->sin.sin_addr;
			if (ia.s_addr == INADDR_ANY ||
			    ia.s_addr == INADDR_NONE)
			{
				message("WARNING: interface %s is UP with %s address",
					ifr->ifr_name, inet_ntoa(ia));
				continue;
			}

			/* save IP address in text from */
			(void) sm_snprintf(ip_addr, sizeof(ip_addr), "[%.*s]",
					(int) sizeof(ip_addr) - 3,
					inet_ntoa(ia));
			break;

#   if NETINET6
		  case AF_INET6:
#    ifdef __KAME__
			/* convert into proper scoped address */
			if ((IN6_IS_ADDR_LINKLOCAL(&sa->sin6.sin6_addr) ||
			     IN6_IS_ADDR_SITELOCAL(&sa->sin6.sin6_addr)) &&
			    sa->sin6.sin6_scope_id == 0)
			{
				struct in6_addr *ia6p;

				ia6p = &sa->sin6.sin6_addr;
				sa->sin6.sin6_scope_id = ntohs(ia6p->s6_addr[3] |
							       ((unsigned int)ia6p->s6_addr[2] << 8));
				ia6p->s6_addr[2] = ia6p->s6_addr[3] = 0;
			}
#    endif /* __KAME__ */
			ia6 = sa->sin6.sin6_addr;
			if (IN6_IS_ADDR_UNSPECIFIED(&ia6))
			{
				addr = anynet_ntop(&ia6, buf6, sizeof(buf6));
				message("WARNING: interface %s is UP with %s address",
					ifr->ifr_name,
					addr == NULL ? "(NULL)" : addr);
				continue;
			}

			/* save IP address in text from */
			addr = anynet_ntop(&ia6, buf6, sizeof(buf6));
			if (addr != NULL)
				(void) sm_snprintf(ip_addr, sizeof(ip_addr),
						   "[%.*s]",
						   (int) sizeof(ip_addr) - 3,
						   addr);
			break;

#   endif /* NETINET6 */
		}

		if (ip_addr[0] == '\0')
			continue;

		if (!wordinclass(ip_addr, 'w'))
		{
			setclass('w', ip_addr);
			if (tTd(0, 4))
				sm_dprintf("\ta.k.a.: %s\n", ip_addr);
		}

		/* skip "loopback" interface "lo" */
		if (DontProbeInterfaces == DPI_SKIPLOOPBACK &&
		    bitset(IFF_LOOPBACK, IFRFREF.ifr_flags))
			continue;

		(void) add_hostnames(sa);
	}
	sm_free(ifc.ifc_buf); /* XXX */
	(void) close(s);
#   undef IFRFREF
#  endif /* defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN */
# endif /* NETINET6 && defined(SIOCGLIFCONF) */
}
/*
**  ISLOOPBACK -- is socket address in the loopback net?
**
**	Parameters:
**		sa -- socket address.
**
**	Returns:
**		true -- is socket address in the loopback net?
**		false -- otherwise
**
*/

bool
isloopback(sa)
	SOCKADDR sa;
{
#if NETINET6
	if (IN6_IS_ADDR_LOOPBACK(&sa.sin6.sin6_addr))
		return true;
#else /* NETINET6 */
	/* XXX how to correctly extract IN_LOOPBACKNET part? */
	if (((ntohl(sa.sin.sin_addr.s_addr) & IN_CLASSA_NET)
	     >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
		return true;
#endif /* NETINET6 */
	return false;
}
/*
**  GET_NUM_PROCS_ONLINE -- return the number of processors currently online
**
**	Parameters:
**		none.
**
**	Returns:
**		The number of processors online.
*/

static int
get_num_procs_online()
{
	int nproc = 0;

#ifdef USESYSCTL
# if defined(CTL_HW) && defined(HW_NCPU)
	size_t sz;
	int mib[2];

	mib[0] = CTL_HW;
	mib[1] = HW_NCPU;
	sz = (size_t) sizeof(nproc);
	(void) sysctl(mib, 2, &nproc, &sz, NULL, 0);
# endif /* defined(CTL_HW) && defined(HW_NCPU) */
#else /* USESYSCTL */
# ifdef _SC_NPROCESSORS_ONLN
	nproc = (int) sysconf(_SC_NPROCESSORS_ONLN);
# else /* _SC_NPROCESSORS_ONLN */
#  ifdef __hpux
#   include <sys/pstat.h>
	struct pst_dynamic psd;

	if (pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0) != -1)
		nproc = psd.psd_proc_cnt;
#  endif /* __hpux */
# endif /* _SC_NPROCESSORS_ONLN */
#endif /* USESYSCTL */

	if (nproc <= 0)
		nproc = 1;
	return nproc;
}
/*
**  SM_CLOSEFROM -- close file descriptors
**
**	Parameters:
**		lowest -- first fd to close
**		highest -- last fd + 1 to close
**
**	Returns:
**		none
*/

void
sm_closefrom(lowest, highest)
	int lowest, highest;
{
#if HASCLOSEFROM
	closefrom(lowest);
#else /* HASCLOSEFROM */
	int i;

	for (i = lowest; i < highest; i++)
		(void) close(i);
#endif /* HASCLOSEFROM */
}
#if HASFDWALK
/*
**  CLOSEFD_WALK -- walk fd's arranging to close them
**	Callback for fdwalk()
**
**	Parameters:
**		lowest -- first fd to arrange to be closed
**		fd -- fd to arrange to be closed
**
**	Returns:
**		zero
*/

static int
closefd_walk(lowest, fd)
	void *lowest;
	int fd;
{
	if (fd >= *(int *)lowest)
		(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
	return 0;
}
#endif /* HASFDWALK */
/*
**  SM_CLOSE_ON_EXEC -- arrange for file descriptors to be closed
**
**	Parameters:
**		lowest -- first fd to arrange to be closed
**		highest -- last fd + 1 to arrange to be closed
**
**	Returns:
**		none
*/

void
sm_close_on_exec(highest, lowest)
	int highest, lowest;
{
#if HASFDWALK
	(void) fdwalk(closefd_walk, &lowest);
#else /* HASFDWALK */
	int i, j;

	for (i = lowest; i < highest; i++)
	{
		if ((j = fcntl(i, F_GETFD, 0)) != -1)
			(void) fcntl(i, F_SETFD, j | FD_CLOEXEC);
	}
#endif /* HASFDWALK */
}
/*
**  SEED_RANDOM -- seed the random number generator
**
**	Parameters:
**		none
**
**	Returns:
**		none
*/

void
seed_random()
{
#if HASSRANDOMDEV
	srandomdev();
#else /* HASSRANDOMDEV */
	long seed;
	struct timeval t;

	seed = (long) CurrentPid;
	if (gettimeofday(&t, NULL) >= 0)
		seed += t.tv_sec + t.tv_usec;

# if HASRANDOM
	(void) srandom(seed);
# else /* HASRANDOM */
	(void) srand((unsigned int) seed);
# endif /* HASRANDOM */
#endif /* HASSRANDOMDEV */
}
/*
**  SM_SYSLOG -- syslog wrapper to keep messages under SYSLOG_BUFSIZE
**
**	Parameters:
**		level -- syslog level
**		id -- envelope ID or NULL (NOQUEUE)
**		fmt -- format string
**		arg... -- arguments as implied by fmt.
**
**	Returns:
**		none
*/

/* VARARGS3 */
void
#ifdef __STDC__
sm_syslog(int level, const char *id, const char *fmt, ...)
#else /* __STDC__ */
sm_syslog(level, id, fmt, va_alist)
	int level;
	const char *id;
	const char *fmt;
	va_dcl
#endif /* __STDC__ */
{
	char *buf;
	size_t bufsize;
	char *begin, *end;
	int save_errno;
	int seq = 1;
	int idlen;
	char buf0[MAXLINE];
	char *newstring;
	extern int SyslogPrefixLen;
	SM_VA_LOCAL_DECL

	save_errno = errno;
	if (id == NULL)
	{
		id = "NOQUEUE";
		idlen = strlen(id) + SyslogPrefixLen;
	}
	else if (strcmp(id, NOQID) == 0)
	{
		id = "";
		idlen = SyslogPrefixLen;
	}
	else
		idlen = strlen(id) + SyslogPrefixLen;

	buf = buf0;
	bufsize = sizeof(buf0);

	for (;;)
	{
		int n;

		/* print log message into buf */
		SM_VA_START(ap, fmt);
		n = sm_vsnprintf(buf, bufsize, fmt, ap);
		SM_VA_END(ap);
		SM_ASSERT(n > 0);
		if (n < bufsize)
			break;

		/* String too small, redo with correct size */
		bufsize = n + 1;
		if (buf != buf0)
		{
			sm_free(buf);
			buf = NULL;
		}
		buf = sm_malloc_x(bufsize);
	}

	/* clean up buf after it has been expanded with args */
	newstring = str2prt(buf);
	if ((strlen(newstring) + idlen + 1) < SYSLOG_BUFSIZE)
	{
#if LOG
		if (*id == '\0')
		{
			if (tTd(89, 8))
				sm_dprintf("%s\n", newstring);
			else
				syslog(level, "%s", newstring);
		}
		else
		{
			if (tTd(89, 8))
				sm_dprintf("%s: %s\n", id, newstring);
			else
				syslog(level, "%s: %s", id, newstring);
		}
#else /* LOG */
		/*XXX should do something more sensible */
		if (*id == '\0')
			(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s\n",
					     newstring);
		else
			(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
					     "%s: %s\n", id, newstring);
#endif /* LOG */
		if (buf != buf0)
			sm_free(buf);
		errno = save_errno;
		return;
	}

/*
**  additional length for splitting: " ..." + 3, where 3 is magic to
**  have some data for the next entry.
*/

#define SL_SPLIT 7

	begin = newstring;
	idlen += 5;	/* strlen("[999]"), see below */
	while (*begin != '\0' &&
	       (strlen(begin) + idlen) > SYSLOG_BUFSIZE)
	{
		char save;

		if (seq >= 999)
		{
			/* Too many messages */
			break;
		}
		end = begin + SYSLOG_BUFSIZE - idlen - SL_SPLIT;
		while (end > begin)
		{
			/* Break on comma or space */
			if (*end == ',' || *end == ' ')
			{
				end++;	  /* Include separator */
				break;
			}
			end--;
		}
		/* No separator, break midstring... */
		if (end == begin)
			end = begin + SYSLOG_BUFSIZE - idlen - SL_SPLIT;
		save = *end;
		*end = 0;
#if LOG
		if (tTd(89, 8))
			sm_dprintf("%s[%d]: %s ...\n", id, seq++, begin);
		else
			syslog(level, "%s[%d]: %s ...", id, seq++, begin);
#else /* LOG */
		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
				     "%s[%d]: %s ...\n", id, seq++, begin);
#endif /* LOG */
		*end = save;
		begin = end;
	}
	if (seq >= 999)
	{
#if LOG
		if (tTd(89, 8))
			sm_dprintf("%s[%d]: log terminated, too many parts\n",
				id, seq);
		else
			syslog(level, "%s[%d]: log terminated, too many parts",
				id, seq);
#else /* LOG */
		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
			      "%s[%d]: log terminated, too many parts\n", id, seq);
#endif /* LOG */
	}
	else if (*begin != '\0')
	{
#if LOG
		if (tTd(89, 8))
			sm_dprintf("%s[%d]: %s\n", id, seq, begin);
		else
			syslog(level, "%s[%d]: %s", id, seq, begin);
#else /* LOG */
		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
				     "%s[%d]: %s\n", id, seq, begin);
#endif /* LOG */
	}
	if (buf != buf0)
		sm_free(buf);
	errno = save_errno;
}
/*
**  HARD_SYSLOG -- call syslog repeatedly until it works
**
**	Needed on HP-UX, which apparently doesn't guarantee that
**	syslog succeeds during interrupt handlers.
*/

#if defined(__hpux) && !defined(HPUX11)

# define MAXSYSLOGTRIES	100
# undef syslog
# ifdef V4FS
#  define XCNST	const
#  define CAST	(const char *)
# else /* V4FS */
#  define XCNST
#  define CAST
# endif /* V4FS */

void
# ifdef __STDC__
hard_syslog(int pri, XCNST char *msg, ...)
# else /* __STDC__ */
hard_syslog(pri, msg, va_alist)
	int pri;
	XCNST char *msg;
	va_dcl
# endif /* __STDC__ */
{
	int i;
	char buf[SYSLOG_BUFSIZE];
	SM_VA_LOCAL_DECL

	SM_VA_START(ap, msg);
	(void) sm_vsnprintf(buf, sizeof(buf), msg, ap);
	SM_VA_END(ap);

	for (i = MAXSYSLOGTRIES; --i >= 0 && syslog(pri, CAST "%s", buf) < 0; )
		continue;
}

# undef CAST
#endif /* defined(__hpux) && !defined(HPUX11) */
#if NEEDLOCAL_HOSTNAME_LENGTH
/*
**  LOCAL_HOSTNAME_LENGTH
**
**	This is required to get sendmail to compile against BIND 4.9.x
**	on Ultrix.
**
**	Unfortunately, a Compaq Y2K patch kit provides it without
**	bumping __RES in /usr/include/resolv.h so we can't automatically
**	figure out whether it is needed.
*/

int
local_hostname_length(hostname)
	char *hostname;
{
	size_t len_host, len_domain;

	if (!*_res.defdname)
		res_init();
	len_host = strlen(hostname);
	len_domain = strlen(_res.defdname);
	if (len_host > len_domain &&
	    (sm_strcasecmp(hostname + len_host - len_domain,
			_res.defdname) == 0) &&
	    hostname[len_host - len_domain - 1] == '.')
		return len_host - len_domain - 1;
	else
		return 0;
}
#endif /* NEEDLOCAL_HOSTNAME_LENGTH */

#if NEEDLINK
/*
**  LINK -- clone a file
**
**	Some OS's lacks link() and hard links.  Since sendmail is using
**	link() as an efficient way to clone files, this implementation
**	will simply do a file copy.
**
**	NOTE: This link() replacement is not a generic replacement as it
**	does not handle all of the semantics of the real link(2).
**
**	Parameters:
**		source -- pathname of existing file.
**		target -- pathname of link (clone) to be created.
**
**	Returns:
**		0 -- success.
**		-1 -- failure, see errno for details.
*/

int
link(source, target)
	const char *source;
	const char *target;
{
	int save_errno;
	int sff;
	int src = -1, dst = -1;
	ssize_t readlen;
	ssize_t writelen;
	char buf[BUFSIZ];
	struct stat st;

	sff = SFF_REGONLY|SFF_OPENASROOT;
	if (DontLockReadFiles)
		sff |= SFF_NOLOCK;

	/* Open the original file */
	src = safeopen((char *)source, O_RDONLY, 0, sff);
	if (src < 0)
		goto fail;

	/* Obtain the size and the mode */
	if (fstat(src, &st) < 0)
		goto fail;

	/* Create the duplicate copy */
	sff &= ~SFF_NOLOCK;
	sff |= SFF_CREAT;
	dst = safeopen((char *)target, O_CREAT|O_EXCL|O_WRONLY,
		       st.st_mode, sff);
	if (dst < 0)
		goto fail;

	/* Copy all of the bytes one buffer at a time */
	while ((readlen = read(src, &buf, sizeof(buf))) > 0)
	{
		ssize_t left = readlen;
		char *p = buf;

		while (left > 0 &&
		       (writelen = write(dst, p, (size_t) left)) >= 0)
		{
			left -= writelen;
			p += writelen;
		}
		if (writelen < 0)
			break;
	}

	/* Any trouble reading? */
	if (readlen < 0 || writelen < 0)
		goto fail;

	/* Close the input file */
	if (close(src) < 0)
	{
		src = -1;
		goto fail;
	}
	src = -1;

	/* Close the output file */
	if (close(dst) < 0)
	{
		/* don't set dst = -1 here so we unlink the file */
		goto fail;
	}

	/* Success */
	return 0;

 fail:
	save_errno = errno;
	if (src >= 0)
		(void) close(src);
	if (dst >= 0)
	{
		(void) unlink(target);
		(void) close(dst);
	}
	errno = save_errno;
	return -1;
}
#endif /* NEEDLINK */

/*
**  Compile-Time options
*/

char	*CompileOptions[] =
{
#if ALLOW_255
	"ALLOW_255",
#endif /* ALLOW_255 */
#if NAMED_BIND
# if DNSMAP
	"DNSMAP",
# endif /* DNSMAP */
#endif /* NAMED_BIND */
#if EGD
	"EGD",
#endif /* EGD */
#if HESIOD
	"HESIOD",
#endif /* HESIOD */
#if HES_GETMAILHOST
	"HES_GETMAILHOST",
#endif /* HES_GETMAILHOST */
#if LDAPMAP
	"LDAPMAP",
#endif /* LDAPMAP */
#if LDAP_REFERRALS
	"LDAP_REFERRALS",
#endif /* LDAP_REFERRALS */
#if LOG
	"LOG",
#endif /* LOG */
#if MAP_NSD
	"MAP_NSD",
#endif /* MAP_NSD */
#if MAP_REGEX
	"MAP_REGEX",
#endif /* MAP_REGEX */
#if MATCHGECOS
	"MATCHGECOS",
#endif /* MATCHGECOS */
#if MILTER
	"MILTER",
#endif /* MILTER */
#if MIME7TO8
	"MIME7TO8",
#endif /* MIME7TO8 */
#if MIME7TO8_OLD
	"MIME7TO8_OLD",
#endif /* MIME7TO8_OLD */
#if MIME8TO7
	"MIME8TO7",
#endif /* MIME8TO7 */
#if NAMED_BIND
	"NAMED_BIND",
#endif /* NAMED_BIND */
#if NDBM
	"NDBM",
#endif /* NDBM */
#if NETINET
	"NETINET",
#endif /* NETINET */
#if NETINET6
	"NETINET6",
#endif /* NETINET6 */
#if NETINFO
	"NETINFO",
#endif /* NETINFO */
#if NETISO
	"NETISO",
#endif /* NETISO */
#if NETNS
	"NETNS",
#endif /* NETNS */
#if NETUNIX
	"NETUNIX",
#endif /* NETUNIX */
#if NETX25
	"NETX25",
#endif /* NETX25 */
#if NEWDB
	"NEWDB",
#endif /* NEWDB */
#if NIS
	"NIS",
#endif /* NIS */
#if NISPLUS
	"NISPLUS",
#endif /* NISPLUS */
#if NO_DH
	"NO_DH",
#endif /* NO_DH */
#if PH_MAP
	"PH_MAP",
#endif /* PH_MAP */
#ifdef PICKY_HELO_CHECK
	"PICKY_HELO_CHECK",
#endif /* PICKY_HELO_CHECK */
#if PIPELINING
	"PIPELINING",
#endif /* PIPELINING */
#if SASL
# if SASL >= 20000
	"SASLv2",
# else /* SASL >= 20000 */
	"SASL",
# endif /* SASL >= 20000 */
#endif /* SASL */
#if SCANF
	"SCANF",
#endif /* SCANF */
#if SM_LDAP_ERROR_ON_MISSING_ARGS
	"SM_LDAP_ERROR_ON_MISSING_ARGS",
#endif /* SM_LDAP_ERROR_ON_MISSING_ARGS */
#if SMTPDEBUG
	"SMTPDEBUG",
#endif /* SMTPDEBUG */
#if SOCKETMAP
	"SOCKETMAP",
#endif /* SOCKETMAP */
#if STARTTLS
	"STARTTLS",
#endif /* STARTTLS */
#if SUID_ROOT_FILES_OK
	"SUID_ROOT_FILES_OK",
#endif /* SUID_ROOT_FILES_OK */
#if TCPWRAPPERS
	"TCPWRAPPERS",
#endif /* TCPWRAPPERS */
#if TLS_NO_RSA
	"TLS_NO_RSA",
#endif /* TLS_NO_RSA */
#if TLS_VRFY_PER_CTX
	"TLS_VRFY_PER_CTX",
#endif /* TLS_VRFY_PER_CTX */
#if USERDB
	"USERDB",
#endif /* USERDB */
#if USE_LDAP_INIT
	"USE_LDAP_INIT",
#endif /* USE_LDAP_INIT */
#if USE_TTYPATH
	"USE_TTYPATH",
#endif /* USE_TTYPATH */
#if XDEBUG
	"XDEBUG",
#endif /* XDEBUG */
#if XLA
	"XLA",
#endif /* XLA */
	NULL
};


/*
**  OS compile options.
*/

char	*OsCompileOptions[] =
{
#if ADDRCONFIG_IS_BROKEN
	"ADDRCONFIG_IS_BROKEN",
#endif /* ADDRCONFIG_IS_BROKEN */
#ifdef AUTO_NETINFO_HOSTS
	"AUTO_NETINFO_HOSTS",
#endif /* AUTO_NETINFO_HOSTS */
#ifdef AUTO_NIS_ALIASES
	"AUTO_NIS_ALIASES",
#endif /* AUTO_NIS_ALIASES */
#if BROKEN_RES_SEARCH
	"BROKEN_RES_SEARCH",
#endif /* BROKEN_RES_SEARCH */
#ifdef BSD4_4_SOCKADDR
	"BSD4_4_SOCKADDR",
#endif /* BSD4_4_SOCKADDR */
#if BOGUS_O_EXCL
	"BOGUS_O_EXCL",
#endif /* BOGUS_O_EXCL */
#if DEC_OSF_BROKEN_GETPWENT
	"DEC_OSF_BROKEN_GETPWENT",
#endif /* DEC_OSF_BROKEN_GETPWENT */
#if FAST_PID_RECYCLE
	"FAST_PID_RECYCLE",
#endif /* FAST_PID_RECYCLE */
#if HASCLOSEFROM
	"HASCLOSEFROM",
#endif /* HASCLOSEFROM */
#if HASFCHOWN
	"HASFCHOWN",
#endif /* HASFCHOWN */
#if HASFCHMOD
	"HASFCHMOD",
#endif /* HASFCHMOD */
#if HASFDWALK
	"HASFDWALK",
#endif /* HASFDWALK */
#if HASFLOCK
	"HASFLOCK",
#endif /* HASFLOCK */
#if HASGETDTABLESIZE
	"HASGETDTABLESIZE",
#endif /* HASGETDTABLESIZE */
#if HASGETUSERSHELL
	"HASGETUSERSHELL",
#endif /* HASGETUSERSHELL */
#if HASINITGROUPS
	"HASINITGROUPS",
#endif /* HASINITGROUPS */
#if HASLDAPGETALIASBYNAME
	"HASLDAPGETALIASBYNAME",
#endif /* HASLDAPGETALIASBYNAME */
#if HASLSTAT
	"HASLSTAT",
#endif /* HASLSTAT */
#if HASNICE
	"HASNICE",
#endif /* HASNICE */
#if HASRANDOM
	"HASRANDOM",
#endif /* HASRANDOM */
#if HASRRESVPORT
	"HASRRESVPORT",
#endif /* HASRRESVPORT */
#if HASSETEGID
	"HASSETEGID",
#endif /* HASSETEGID */
#if HASSETLOGIN
	"HASSETLOGIN",
#endif /* HASSETLOGIN */
#if HASSETREGID
	"HASSETREGID",
#endif /* HASSETREGID */
#if HASSETRESGID
	"HASSETRESGID",
#endif /* HASSETRESGID */
#if HASSETREUID
	"HASSETREUID",
#endif /* HASSETREUID */
#if HASSETRLIMIT
	"HASSETRLIMIT",
#endif /* HASSETRLIMIT */
#if HASSETSID
	"HASSETSID",
#endif /* HASSETSID */
#if HASSETUSERCONTEXT
	"HASSETUSERCONTEXT",
#endif /* HASSETUSERCONTEXT */
#if HASSETVBUF
	"HASSETVBUF",
#endif /* HASSETVBUF */
#if HAS_ST_GEN
	"HAS_ST_GEN",
#endif /* HAS_ST_GEN */
#if HASSRANDOMDEV
	"HASSRANDOMDEV",
#endif /* HASSRANDOMDEV */
#if HASURANDOMDEV
	"HASURANDOMDEV",
#endif /* HASURANDOMDEV */
#if HASSTRERROR
	"HASSTRERROR",
#endif /* HASSTRERROR */
#if HASULIMIT
	"HASULIMIT",
#endif /* HASULIMIT */
#if HASUNAME
	"HASUNAME",
#endif /* HASUNAME */
#if HASUNSETENV
	"HASUNSETENV",
#endif /* HASUNSETENV */
#if HASWAITPID
	"HASWAITPID",
#endif /* HASWAITPID */
#if IDENTPROTO
	"IDENTPROTO",
#endif /* IDENTPROTO */
#if IP_SRCROUTE
	"IP_SRCROUTE",
#endif /* IP_SRCROUTE */
#if O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL
	"LOCK_ON_OPEN",
#endif /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
#if MILTER_NO_NAGLE
	"MILTER_NO_NAGLE ",
#endif /* MILTER_NO_NAGLE */
#if NEEDFSYNC
	"NEEDFSYNC",
#endif /* NEEDFSYNC */
#if NEEDLINK
	"NEEDLINK",
#endif /* NEEDLINK */
#if NEEDLOCAL_HOSTNAME_LENGTH
	"NEEDLOCAL_HOSTNAME_LENGTH",
#endif /* NEEDLOCAL_HOSTNAME_LENGTH */
#if NEEDSGETIPNODE
	"NEEDSGETIPNODE",
#endif /* NEEDSGETIPNODE */
#if NEEDSTRSTR
	"NEEDSTRSTR",
#endif /* NEEDSTRSTR */
#if NEEDSTRTOL
	"NEEDSTRTOL",
#endif /* NEEDSTRTOL */
#ifdef NO_GETSERVBYNAME
	"NO_GETSERVBYNAME",
#endif /* NO_GETSERVBYNAME */
#if NOFTRUNCATE
	"NOFTRUNCATE",
#endif /* NOFTRUNCATE */
#if REQUIRES_DIR_FSYNC
	"REQUIRES_DIR_FSYNC",
#endif /* REQUIRES_DIR_FSYNC */
#if RLIMIT_NEEDS_SYS_TIME_H
	"RLIMIT_NEEDS_SYS_TIME_H",
#endif /* RLIMIT_NEEDS_SYS_TIME_H */
#if SAFENFSPATHCONF
	"SAFENFSPATHCONF",
#endif /* SAFENFSPATHCONF */
#if SECUREWARE
	"SECUREWARE",
#endif /* SECUREWARE */
#if SHARE_V1
	"SHARE_V1",
#endif /* SHARE_V1 */
#if SIOCGIFCONF_IS_BROKEN
	"SIOCGIFCONF_IS_BROKEN",
#endif /* SIOCGIFCONF_IS_BROKEN */
#if SIOCGIFNUM_IS_BROKEN
	"SIOCGIFNUM_IS_BROKEN",
#endif /* SIOCGIFNUM_IS_BROKEN */
#if SNPRINTF_IS_BROKEN
	"SNPRINTF_IS_BROKEN",
#endif /* SNPRINTF_IS_BROKEN */
#if SO_REUSEADDR_IS_BROKEN
	"SO_REUSEADDR_IS_BROKEN",
#endif /* SO_REUSEADDR_IS_BROKEN */
#if SYS5SETPGRP
	"SYS5SETPGRP",
#endif /* SYS5SETPGRP */
#if SYSTEM5
	"SYSTEM5",
#endif /* SYSTEM5 */
#if USE_DOUBLE_FORK
	"USE_DOUBLE_FORK",
#endif /* USE_DOUBLE_FORK */
#if USE_ENVIRON
	"USE_ENVIRON",
#endif /* USE_ENVIRON */
#if USE_SA_SIGACTION
	"USE_SA_SIGACTION",
#endif /* USE_SA_SIGACTION */
#if USE_SIGLONGJMP
	"USE_SIGLONGJMP",
#endif /* USE_SIGLONGJMP */
#if USEGETCONFATTR
	"USEGETCONFATTR",
#endif /* USEGETCONFATTR */
#if USESETEUID
	"USESETEUID",
#endif /* USESETEUID */
#ifdef USESYSCTL
	"USESYSCTL",
#endif /* USESYSCTL */
#if USING_NETSCAPE_LDAP
	"USING_NETSCAPE_LDAP",
#endif /* USING_NETSCAPE_LDAP */
#ifdef WAITUNION
	"WAITUNION",
#endif /* WAITUNION */
	NULL
};

/*
**  FFR compile options.
*/

char	*FFRCompileOptions[] =
{
#if _FFR_ALLOW_SASLINFO
	/* DefaultAuthInfo can be specified by user. */
	/* DefaultAuthInfo doesn't really work in 8.13 anymore. */
	"_FFR_ALLOW_SASLINFO",
#endif /* _FFR_ALLOW_SASLINFO */
#if _FFR_BESTMX_BETTER_TRUNCATION
	/* Better truncation of list of MX records for dns map. */
	"_FFR_BESTMX_BETTER_TRUNCATION",
#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
#if _FFR_CATCH_BROKEN_MTAS
	/* Deal with MTAs that send a reply during the DATA phase. */
	"_FFR_CATCH_BROKEN_MTAS",
#endif /* _FFR_CATCH_BROKEN_MTAS */
#if _FFR_CHK_QUEUE
	/* Stricter checks about queue directory permissions. */
	"_FFR_CHK_QUEUE",
#endif /* _FFR_CHK_QUEUE */
#if _FFR_CLIENT_SIZE
	/* Don't try to send mail if its size exceeds SIZE= of server. */
	"_FFR_CLIENT_SIZE",
#endif /* _FFR_CLIENT_SIZE */
#if _FFR_CRLPATH
	/* CRLPath; needs documentation; Al Smith */
	"_FFR_CRLPATH",
#endif /* _FFR_CRLPATH */
#if _FFR_DAEMON_NETUNIX
	/* Allow local (not just TCP) socket connection to server. */
	"_FFR_DAEMON_NETUNIX",
#endif /* _FFR_DAEMON_NETUNIX */
#if _FFR_DEPRECATE_MAILER_FLAG_I
	/* What it says :-) */
	"_FFR_DEPRECATE_MAILER_FLAG_I",
#endif /* _FFR_DEPRECATE_MAILER_FLAG_I */
#if _FFR_DM_ONE
	/* deliver first TA in background, then queue */
	"_FFR_DM_ONE",
#endif /* _FFR_DM_ONE */
#if _FFR_DIGUNIX_SAFECHOWN
	/* Properly set SAFECHOWN (include/sm/conf.h) for Digital UNIX */
/* Problem noted by Anne Bennett of Concordia University */
	"_FFR_DIGUNIX_SAFECHOWN",
#endif /* _FFR_DIGUNIX_SAFECHOWN */
#if _FFR_DNSMAP_ALIASABLE
	/* Allow dns map type to be used for aliases. */
/* Don Lewis of TDK */
	"_FFR_DNSMAP_ALIASABLE",
#endif /* _FFR_DNSMAP_ALIASABLE */
#if _FFR_DONTLOCKFILESFORREAD_OPTION
	/* Enable DontLockFilesForRead option. */
	"_FFR_DONTLOCKFILESFORREAD_OPTION",
#endif /* _FFR_DONTLOCKFILESFORREAD_OPTION */
#if _FFR_DOTTED_USERNAMES
	/* Allow usernames with '.' */
	"_FFR_DOTTED_USERNAMES",
#endif /* _FFR_DOTTED_USERNAMES */
#if _FFR_DPO_CS
	/*
	**  Make DaemonPortOptions case sensitive.
	**  For some unknown reasons the code converted every option
	**  to uppercase (first letter only, as that's the only one that
	**  is actually checked). This prevented all new lower case options
	**  from working...
	**  The documentation doesn't say anything about case (in)sensitivity,
	**  which means it should be case sensitive by default,
	**  but it's not a good idea to change this within a patch release,
	**  so let's delay this to 8.15.
	*/

	"_FFR_DPO_CS",
#endif /* _FFR_DPO_CS */
#if _FFR_DROP_TRUSTUSER_WARNING
	/*
	**  Don't issue this warning:
	**  "readcf: option TrustedUser may cause problems on systems
	**  which do not support fchown() if UseMSP is not set.
	*/

	"_FFR_DROP_TRUSTUSER_WARNING",
#endif /* _FFR_DROP_TRUSTUSER_WARNING */
#if _FFR_EIGHT_BIT_ADDR_OK
	/* EightBitAddrOK: allow 8-bit e-mail addresses */
	"_FFR_EIGHT_BIT_ADDR_OK",
#endif /* _FFR_EIGHT_BIT_ADDR_OK */
#if _FFR_EXTRA_MAP_CHECK
	/* perform extra checks on $( $) in R lines */
	"_FFR_EXTRA_MAP_CHECK",
#endif /* _FFR_EXTRA_MAP_CHECK */
#if _FFR_GETHBN_ExFILE
	/*
	**  According to Motonori Nakamura some gethostbyname()
	**  implementations (TurboLinux?) may (temporarily) fail
	**  due to a lack of file discriptors. Enabling this FFR
	**  will check errno for EMFILE and ENFILE and in case of a match
	**  cause a temporary error instead of a permanent error.
	**  The right solution is of course to file a bug against those
	**  systems such that they actually set h_errno = TRY_AGAIN.
	*/

	"_FFR_GETHBN_ExFILE",
#endif /* _FFR_GETHBN_ExFILE */
#if _FFR_FIX_DASHT
	/*
	**  If using -t, force not sending to argv recipients, even
	**  if they are mentioned in the headers.
	*/

	"_FFR_FIX_DASHT",
#endif /* _FFR_FIX_DASHT */
#if _FFR_FORWARD_SYSERR
	/* Cause a "syserr" if forward file isn't "safe". */
	"_FFR_FORWARD_SYSERR",
#endif /* _FFR_FORWARD_SYSERR */
#if _FFR_GEN_ORCPT
	/* Generate a ORCPT DSN arg if not already provided */
	"_FFR_GEN_ORCPT",
#endif /* _FFR_GEN_ORCPT */
#if _FFR_GROUPREADABLEAUTHINFOFILE
	/* Allow group readable DefaultAuthInfo file. */
	"_FFR_GROUPREADABLEAUTHINFOFILE",
#endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
#if _FFR_HANDLE_ISO8859_GECOS
	/*
	**  Allow ISO 8859 characters in GECOS field: replace them
	**  ith ASCII "equivalent".
	*/

/* Peter Eriksson of Linkopings universitet */
	"_FFR_HANDLE_ISO8859_GECOS",
#endif /* _FFR_HANDLE_ISO8859_GECOS */
#if _FFR_HPUX_NSSWITCH
	/* Use nsswitch on HP-UX */
	"_FFR_HPUX_NSSWITCH",
#endif /* _FFR_HPUX_NSSWITCH */
#if _FFR_IGNORE_BOGUS_ADDR
	/* Ignore addresses for which prescan() failed */
	"_FFR_IGNORE_BOGUS_ADDR",
#endif /* _FFR_IGNORE_BOGUS_ADDR */
#if _FFR_IGNORE_EXT_ON_HELO
	/* Ignore extensions offered in response to HELO */
	"_FFR_IGNORE_EXT_ON_HELO",
#endif /* _FFR_IGNORE_EXT_ON_HELO */
#if _FFR_MAXDATASIZE
	/*
	**  It is possible that a header is larger than MILTER_CHUNK_SIZE,
	**  hence this shouldn't be used as limit for milter communication.
	**  see also libmilter/comm.c
	**  Gurusamy Sarathy of ActiveState
	*/

	"_FFR_MAXDATASIZE",
#endif /* _FFR_MAXDATASIZE */
#if _FFR_MAX_FORWARD_ENTRIES
	/* Try to limit number of .forward entries */
	/* (doesn't work) */
/* Randall S. Winchester of the University of Maryland */
	"_FFR_MAX_FORWARD_ENTRIES",
#endif /* _FFR_MAX_FORWARD_ENTRIES */
#if _FFR_MAX_SLEEP_TIME
	/* Limit sleep(2) time in libsm/clock.c */
	"_FFR_MAX_SLEEP_TIME",
#endif /* _FFR_MAX_SLEEP_TIME */
#if _FFR_MEMSTAT
	/* Check free memory */
	"_FFR_MEMSTAT",
#endif /* _FFR_MEMSTAT */
#if _FFR_MILTER_CHECK
	"_FFR_MILTER_CHECK",
#endif /* _FFR_MILTER_CHECK */
#if _FFR_MILTER_CONVERT_ALL_LF_TO_CRLF
	/*
	**  milter_body() uses the same conversion algorithm as putbody()
	**  to translate the "local" df format (\n) to SMTP format (\r\n).
	**  However, putbody() and mime8to7() use different conversion
	**  algorithms.
	**  If the input date does not follow the SMTP standard
	**  (e.g., if it has "naked \r"s), then the output from putbody()
	**  and mime8to7() will most likely be different.
	**  By turning on this FFR milter_body() will try to "imitate"
	**  mime8to7().
	**  Note: there is no (simple) way to deal with both conversions
	**  in a consistent manner. Moreover, as the "GiGo" principle applies,
	**  it's not really worth to fix it.
	*/

	"_FFR_MILTER_CONVERT_ALL_LF_TO_CRLF",
#endif /* _FFR_MILTER_CONVERT_ALL_LF_TO_CRLF */
#if _FFR_MILTER_CHECK_REJECTIONS_TOO
	/*
	**  Also send RCPTs that are rejected by check_rcpt to a milter
	**  (if requested during option negotiation).
	*/

	"_FFR_MILTER_CHECK_REJECTIONS_TOO",
#endif /* _FFR_MILTER_CHECK_REJECTIONS_TOO */
#if _FFR_MIME7TO8_OLD
	/* Old mime7to8 code, the new is broken for at least one example. */
	"_FFR_MIME7TO8_OLD",
#endif /* _FFR_MAX_SLEEP_TIME */
#if _FFR_MSG_ACCEPT
	/* allow to override "Message accepted for delivery" */
	"_FFR_MSG_ACCEPT",
#endif /* _FFR_MSG_ACCEPT */
#if _FFR_NODELAYDSN_ON_HOLD
	/* Do not issue a DELAY DSN for mailers that use the hold flag. */
/* Steven Pitzl */
	"_FFR_NODELAYDSN_ON_HOLD",
#endif /* _FFR_NODELAYDSN_ON_HOLD */
#if _FFR_NO_PIPE
	/* Disable PIPELINING, delay client if used. */
	"_FFR_NO_PIPE",
#endif /* _FFR_NO_PIPE */
#if _FFR_LOG_NTRIES
	/* log ntries=, from Nik Clayton of FreeBSD */
	"_FFR_LOG_NTRIES",
#endif /* _FFR_LOG_NTRIES */
#if _FFR_QF_PARANOIA
	"_FFR_QF_PARANOIA",
#endif /* _FFR_QF_PARANOIA */
#if _FFR_QUEUEDELAY
	/* Exponential queue delay; disabled in 8.13 since it isn't used. */
	"_FFR_QUEUEDELAY",
#endif /* _FFR_QUEUEDELAY */
#if _FFR_QUEUE_GROUP_SORTORDER
	/* Allow QueueSortOrder per queue group. */
/* XXX: Still need to actually use qgrp->qg_sortorder */
	"_FFR_QUEUE_GROUP_SORTORDER",
#endif /* _FFR_QUEUE_GROUP_SORTORDER */
#if _FFR_QUEUE_MACRO
	/* Define {queue} macro. */
	"_FFR_QUEUE_MACRO",
#endif /* _FFR_QUEUE_MACRO */
#if _FFR_QUEUE_RUN_PARANOIA
	/* Additional checks when doing queue runs; interval of checks */
	"_FFR_QUEUE_RUN_PARANOIA",
#endif /* _FFR_QUEUE_RUN_PARANOIA */
#if _FFR_QUEUE_SCHED_DBG
	/* Debug output for the queue scheduler. */
	"_FFR_QUEUE_SCHED_DBG",
#endif /* _FFR_QUEUE_SCHED_DBG */
#if _FFR_REDIRECTEMPTY
	/*
	**  envelope <> can't be sent to mailing lists, only owner-
	**  send spam of this type to owner- of the list
	**  ----  to stop spam from going to mailing lists.
	*/

	"_FFR_REDIRECTEMPTY",
#endif /* _FFR_REDIRECTEMPTY */
#if _FFR_RESET_MACRO_GLOBALS
	/* Allow macro 'j' to be set dynamically via rulesets. */
	"_FFR_RESET_MACRO_GLOBALS",
#endif /* _FFR_RESET_MACRO_GLOBALS */
#if _FFR_RHS
	/* Random shuffle for queue sorting. */
	"_FFR_RHS",
#endif /* _FFR_RHS */
#if _FFR_SHM_STATUS
	/* Donated code (unused). */
	"_FFR_SHM_STATUS",
#endif /* _FFR_SHM_STATUS */
#if _FFR_LDAP_SINGLEDN
	/*
	**  The LDAP database map code in Sendmail 8.12.10, when
	**  given the -1 switch, would match only a single DN,
	**  but was able to return multiple attributes for that
	**  DN.  In Sendmail 8.13 this "bug" was corrected to
	**  only return if exactly one attribute matched.
	**
	**  Unfortunately, our configuration uses the former
	**  behaviour.  Attached is a relatively simple patch
	**  to 8.13.4 which adds a -2 switch (for lack of a
	**  better option) which returns the single dn/multiple
	**  attributes.
	**
	** Jeffrey T. Eaton, Carnegie-Mellon University
	*/

	"_FFR_LDAP_SINGLEDN",
#endif /* _FFR_LDAP_SINGLEDN */
#if _FFR_SKIP_DOMAINS
	/* process every N'th domain instead of every N'th message */
	"_FFR_SKIP_DOMAINS",
#endif /* _FFR_SKIP_DOMAINS */
#if _FFR_SLEEP_USE_SELECT
	/* Use select(2) in libsm/clock.c to emulate sleep(2) */
	"_FFR_SLEEP_USE_SELECT ",
#endif /* _FFR_SLEEP_USE_SELECT */
#if _FFR_SPT_ALIGN
	/*
	**  It looks like the Compaq Tru64 5.1A now aligns argv and envp to 64
	**  bit alignment, so unless each piece of argv and envp is a multiple
	**  of 8 bytes (including terminating NULL), initsetproctitle() won't
	**  use any of the space beyond argv[0]. Be sure to set SPT_ALIGN_SIZE
	**  if you use this FFR.
	*/

/* Chris Adams of HiWAAY Informations Services */
	"_FFR_SPT_ALIGN",
#endif /* _FFR_SPT_ALIGN */
#if _FFR_SS_PER_DAEMON
	/* SuperSafe per DaemonPortOptions: 'T' (better letter?) */
	"_FFR_SS_PER_DAEMON",
#endif /* _FFR_SS_PER_DAEMON */
#if _FFR_TIMERS
	/* Donated code (unused). */
	"_FFR_TIMERS",
#endif /* _FFR_TIMERS */
#if _FFR_TLS_1
	/* More STARTTLS options, e.g., secondary certs. */
	"_FFR_TLS_1",
#endif /* _FFR_TLS_1 */
#if _FFR_TRUSTED_QF
	/*
	**  If we don't own the file mark it as unsafe.
	**  However, allow TrustedUser to own it as well
	**  in case TrustedUser manipulates the queue.
	*/

	"_FFR_TRUSTED_QF",
#endif /* _FFR_TRUSTED_QF */
#if _FFR_USE_SEM_LOCKING
	"_FFR_USE_SEM_LOCKING",
#endif /* _FFR_USE_SEM_LOCKING */
#if _FFR_USE_SETLOGIN
	/* Use setlogin() */
/* Peter Philipp */
	"_FFR_USE_SETLOGIN",
#endif /* _FFR_USE_SETLOGIN */
	NULL
};