view usr/src/cmd/cmd-inet/usr.lib/in.ndpd/config.c @ 13:f60a82e85167 default tip

Revert NEC's changes to fix krb5 build
author Andrew Stormont <andyjstormont@gmail.com>
date Fri, 02 Mar 2012 22:25:26 +0000
parents 1a15d5aaf794
children
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include "defs.h"
#include "tables.h"

/*
 * Parse the config file which consists of entries of the form:
 *	ifdefault	[<variable> <value>]*
 *	prefixdefault	[<variable> <value>]*
 *	if <ifname>	[<variable> <value>]*
 *	prefix <prefix>/<length> <ifname>	[<variable> <value>]*
 *
 * All "ifdefault" and "prefixdefault" entries must preceed any
 * "if" and "prefix" entries.
 *
 * Values (such as expiry dates) which contain white space
 * can be quoted with single or double quotes.
 */

/* maximum length of messages we send to syslog */
#define	NDPD_LOGMSGSIZE	1024
typedef	boolean_t	(*pfb_t)(char *, uint_t *);

struct configinfo {
	char	*ci_name;
	uint_t	ci_min;		/* 0: no min check */
	uint_t	ci_max;		/* ~0U: no max check */
	uint_t	ci_default;
	uint_t	ci_index;	/* Into result array */
	pfb_t	ci_parsefunc;	/* Parse function returns -1 on failure */
};

enum config_type { CONFIG_IF, CONFIG_PREFIX};
typedef enum config_type config_type_t;

static void set_protocol_defaults(void);
static void print_defaults(void);
static void parse_var_value(config_type_t, struct configinfo *, char *, char *,
    struct confvar *);
static void parse_default(config_type_t, struct configinfo *, char **, int,
    struct confvar *);
static void parse_if(struct configinfo *, char **, int);
static void parse_prefix(struct configinfo *, char **, int);
static boolean_t parse_onoff(char *, uint_t *);	/* boolean */
static boolean_t parse_int(char *, uint_t *);	/* integer */
static boolean_t parse_ms(char *, uint_t *);	/* milliseconds */
static boolean_t parse_s(char *, uint_t *);	/* seconds */
static boolean_t parse_date(char *, uint_t *);	/* date format */
static void conferr(char *fmt, ...);
static FILE *open_conffile(char *filename);
static int parse_line(char *line, char *argvec[], int argcount);
static int readline(FILE *fp, char *line, int length);
static int parse_addrprefix(char *strin, struct in6_addr *in6);

/*
 * Per interface configuration variables.
 * Min, max, and default values are from RFC 2461.
 */
static struct configinfo iflist[] = {
	/* Name, Min, Max, Default, Index */
	{ "DupAddrDetectTransmits", 0, 100, 1, I_DupAddrDetectTransmits,
	parse_int },
	{ "AdvSendAdvertisements", 0, 1, 0, I_AdvSendAdvertisements,
	parse_onoff },
	{ "MaxRtrAdvInterval", 4, 1800, 600, I_MaxRtrAdvInterval, parse_s },
	{ "MinRtrAdvInterval", 3, 1350, 200, I_MinRtrAdvInterval, parse_s },
	/*
	 * No greater than .75 * MaxRtrAdvInterval.
	 * Default: 0.33 * MaxRtrAdvInterval
	 */
	{ "AdvManagedFlag", 0, 1, 0, I_AdvManagedFlag, parse_onoff },
	{ "AdvOtherConfigFlag", 0, 1, 0, I_AdvOtherConfigFlag, parse_onoff },
	{ "AdvLinkMTU", IPV6_MIN_MTU, 65535, 0, I_AdvLinkMTU, parse_int },
	{ "AdvReachableTime", 0, 3600000, 0, I_AdvReachableTime, parse_ms },
	{ "AdvRetransTimer", 0, ~0U, 0, I_AdvRetransTimer, parse_ms },
	{ "AdvCurHopLimit", 0, 255, 0, I_AdvCurHopLimit, parse_int },
	{ "AdvDefaultLifetime", 0, 9000, 1800, I_AdvDefaultLifetime, parse_s },
	/*
	 * MUST be either zero or between MaxRtrAdvInterval and 9000 seconds.
	 * Default: 3 * MaxRtrAdvInterval
	 */
	{ "StatelessAddrConf", 0, 1, 1, I_StatelessAddrConf, parse_onoff },
	{ "StatefulAddrConf", 0, 1, 1, I_StatefulAddrConf, parse_onoff },
	/*
	 * Tmp* variables from RFC 3041, where defaults are defined.
	 */
	{ "TmpAddrsEnabled", 0, 1, 0, I_TmpAddrsEnabled, parse_onoff },
	{ "TmpValidLifetime", 0, ~0U, 604800, I_TmpValidLifetime, parse_s },
	{ "TmpPreferredLifetime", 0, ~0U, 86400, I_TmpPreferredLifetime,
	parse_s },
	{ "TmpRegenAdvance", 0, 60, 5, I_TmpRegenAdvance, parse_s },
	{ "TmpMaxDesyncFactor", 0, 600, 600, I_TmpMaxDesyncFactor, parse_s },
	{ NULL, 0, 0, 0, 0 }
};

/*
 * Per prefix: AdvPrefixList configuration variables.
 * Min, max, and default values are from RFC 2461.
 */
static struct configinfo prefixlist[] = {
	/* Name, Min, Max, Default, Index */
	{ "AdvValidLifetime", 0, ~0U, 2592000, I_AdvValidLifetime,
	parse_s },
	{ "AdvOnLinkFlag", 0, 1, 1, I_AdvOnLinkFlag, parse_onoff },
	{ "AdvPreferredLifetime", 0, ~0U, 604800, I_AdvPreferredLifetime,
	parse_s},
	{ "AdvAutonomousFlag", 0, 1, 1, I_AdvAutonomousFlag, parse_onoff },
	{ "AdvValidExpiration", 0, ~0U, 0, I_AdvValidExpiration,
	parse_date },
	{ "AdvPreferredExpiration", 0, ~0U, 0, I_AdvPreferredExpiration,
	parse_date},
	{ NULL, 0, 0, 0, 0 },
};

/*
 * Data structures used to merge above protocol defaults
 * with defaults specified in the configuration file.
 * ifdefault is not static because new interfaces can be
 * created outside of the configuration context.
 */
struct confvar ifdefaults[I_IFSIZE];
static struct confvar prefixdefaults[I_PREFIXSIZE];

static char	conf_filename[MAXPATHLEN];
static int	lineno;

/*
 * Checks for violations of section 5.5.3 (c) of RFC 2462.
 */
static void
check_var_consistency(struct confvar *cv, void *save, int size)
{
	boolean_t rollback = _B_FALSE;
	int prefl, prefe, valid;

	prefl = cv[I_AdvPreferredLifetime].cf_value;
	prefe = cv[I_AdvPreferredExpiration].cf_value;
	valid = cv[I_AdvValidLifetime].cf_value;

	if (prefl > valid) {
		conferr("AdvPreferredLifetime (%u) is greater than "
		    "valid lifetime (%u)\n", prefl, valid);
		rollback = _B_TRUE;
	}

	if (prefe > valid) {
		conferr("AdvPreferredExpiration (%u) is greater than "
		    "valid lifetime (%u)\n", prefe, valid);
		rollback = _B_TRUE;
	}

	if (rollback) {
		(void) memcpy(cv, save, size);
	}
}

/*
 * Check for invalid lifetime values for RFC3041 addresses
 */
static void
check_if_var_consistency(struct confvar *cv, void *save, int size)
{
	boolean_t rollback = _B_FALSE;
	int tpref, tvalid, tdesync, tregen;

	tpref = cv[I_TmpPreferredLifetime].cf_value;
	tvalid = cv[I_TmpValidLifetime].cf_value;
	tdesync = cv[I_TmpMaxDesyncFactor].cf_value;
	tregen = cv[I_TmpRegenAdvance].cf_value;

	/*
	 * Only need to do this if tmp addrs are enabled.
	 */
	if (cv[I_TmpAddrsEnabled].cf_value == 0)
		return;

	if (tdesync > tpref) {
		conferr("TmpDesyncFactor (%u) is greater than "
		    "TmpPreferredLifetime (%u)\n", tdesync, tpref);
		rollback = _B_TRUE;
	}

	if (tpref > tvalid) {
		conferr("TmpPreferredLifetime (%u) is greater than "
		    "TmpValidLifetime (%u)\n", tpref, tvalid);
		rollback = _B_TRUE;
	}

	if (tregen > tvalid) {
		conferr("TmpRegenAdvance (%u) is greater than "
		    "TmpValidLifetime (%u)\n", tregen, tvalid);
		rollback = _B_TRUE;
	}

	if (rollback) {
		(void) memcpy(cv, save, size);
	}
}

int
parse_config(char *config_file, boolean_t file_required)
{
	FILE *fp;
	char line[MAXLINELEN];
	char pline[MAXLINELEN];
	int argcount;
	char *argvec[MAXARGSPERLINE];
	int defaultdone = 0;	/* Set when first non-default command found */

	if (debug & D_CONFIG)
		logmsg(LOG_DEBUG, "parse_config()\n");

	set_protocol_defaults();
	if (debug & D_DEFAULTS)
		print_defaults();

	fp = open_conffile(config_file);
	if (fp == NULL) {
		if (errno == ENOENT && !file_required)
			return (0);
		logperror(config_file);
		return (-1);
	}
	while (readline(fp, line, sizeof (line)) != 0) {
		(void) strncpy(pline, line, sizeof (pline));
		pline[sizeof (pline) - 1] = '\0';	/* NULL terminate */
		argcount = parse_line(pline, argvec,
		    sizeof (argvec) / sizeof (argvec[0]));
		if (debug & D_PARSE) {
			int i;

			logmsg(LOG_DEBUG, "scanned %d args\n", argcount);
			for (i = 0; i < argcount; i++)
				logmsg(LOG_DEBUG, "arg[%d]: %s\n",
				    i, argvec[i]);
		}
		if (argcount == 0) {
			/* Empty line - or comment only line */
			continue;
		}
		if (strcmp(argvec[0], "ifdefault") == 0) {
			char save[sizeof (ifdefaults)];

			if (defaultdone) {
				conferr("ifdefault after non-default "
				    "command\n");
				continue;
			}
			/*
			 * Save existing values in case what we read is
			 * invalid and we need to restore previous settings.
			 */
			(void) memcpy(save, ifdefaults, sizeof (ifdefaults));
			parse_default(CONFIG_IF, iflist, argvec+1, argcount-1,
			    ifdefaults);
			check_if_var_consistency(ifdefaults, save,
			    sizeof (save));
		} else if (strcmp(argvec[0], "prefixdefault") == 0) {
			char save[sizeof (prefixdefaults)];

			if (defaultdone) {
				conferr("prefixdefault after non-default "
				    "command\n");
				continue;
			}
			/*
			 * Save existing values in case what we read is
			 * invalid and we need to restore previous settings.
			 */
			(void) memcpy(save, prefixdefaults,
			    sizeof (prefixdefaults));
			parse_default(CONFIG_PREFIX, prefixlist, argvec+1,
			    argcount-1, prefixdefaults);
			check_var_consistency(prefixdefaults, save,
			    sizeof (save));
		} else if (strcmp(argvec[0], "if") == 0) {
			defaultdone = 1;
			parse_if(iflist, argvec+1, argcount-1);
		} else if (strcmp(argvec[0], "prefix") == 0) {
			defaultdone = 1;
			parse_prefix(prefixlist, argvec+1, argcount-1);
		} else {
			conferr("Unknown command: %s\n", argvec[0]);
		}
	}
	(void) fclose(fp);
	if (debug & D_DEFAULTS)
		print_defaults();
	return (0);
}

/*
 * Extract the defaults from the configinfo tables to initialize
 * the ifdefaults and prefixdefaults arrays.
 * The arrays are needed to track which defaults have been changed
 * by the config file.
 */
static void
set_protocol_defaults(void)
{
	struct configinfo *cip;

	if (debug & D_DEFAULTS)
		logmsg(LOG_DEBUG, "extract_protocol_defaults\n");
	for (cip = iflist; cip->ci_name != NULL; cip++) {
		ifdefaults[cip->ci_index].cf_value = cip->ci_default;
		ifdefaults[cip->ci_index].cf_notdefault = _B_FALSE;
	}
	for (cip = prefixlist; cip->ci_name != NULL; cip++) {
		prefixdefaults[cip->ci_index].cf_value = cip->ci_default;
		prefixdefaults[cip->ci_index].cf_notdefault = _B_FALSE;
	}
}

void
print_iflist(struct confvar *confvar)
{
	struct configinfo *cip;

	for (cip = iflist; cip->ci_name != NULL; cip++) {
		logmsg(LOG_DEBUG, "\t%s min %u max %u def %u value %u set %d\n",
		    cip->ci_name, cip->ci_min, cip->ci_max, cip->ci_default,
		    confvar[cip->ci_index].cf_value,
		    confvar[cip->ci_index].cf_notdefault);
	}
}

void
print_prefixlist(struct confvar *confvar)
{
	struct configinfo *cip;

	for (cip = prefixlist; cip->ci_name != NULL; cip++) {
		logmsg(LOG_DEBUG, "\t%s min %u max %u def %u value %u set %d\n",
		    cip->ci_name, cip->ci_min, cip->ci_max, cip->ci_default,
		    confvar[cip->ci_index].cf_value,
		    confvar[cip->ci_index].cf_notdefault);
	}
}


static void
print_defaults(void)
{
	logmsg(LOG_DEBUG, "Default interface variables:\n");
	print_iflist(ifdefaults);
	logmsg(LOG_DEBUG, "Default prefix variables:\n");
	print_prefixlist(prefixdefaults);
}

/*
 * Read from fp. Handle \ at the end of the line by joining lines together.
 * Return 0 on EOF.
 */
static int
readline(FILE *fp, char *line, int length)
{
	int got = 0;

retry:
	errno = 0;
	if (fgets(line, length, fp) == NULL) {
		if (errno == EINTR)
			goto retry;
		if (got != 0)
			return (1);
		else
			return (0);
	}
	lineno++;
	got = strlen(line);
	/* Look for trailing \. Note that fgets includes the linefeed. */
	if (got >= 2 && line[got-2] == '\\') {
		/* Skip \ and LF */
		line += got - 2;
		length -= got - 2;
		goto retry;
	}
	/* Remove the trailing linefeed */
	if (got > 0)
		line[got-1] = '\0';

	return (1);
}

/*
 * Parse a line splitting it off at whitspace characters.
 * Modifies the content of the string by inserting NULLs.
 * If more arguments than fits in argvec/argcount then ignore the last.
 * Returns argcount.
 * Handles single quotes and double quotes.
 */
static int
parse_line(char *line, char *argvec[], int argcount)
{
	int i = 0;
	char *cp;
	boolean_t insingle_quote = _B_FALSE;
	boolean_t indouble_quote = _B_FALSE;

	/* Truncate at the beginning of a comment */
	cp = strchr(line, '#');
	if (cp != NULL)
		*cp = '\0';

	for (;;) {
		/* Skip any whitespace */
		while (isspace(*line) && *line != '\0')
			line++;

		if (*line == '\'') {
			line++;
			if (*line == '\0')
				return (i);
			insingle_quote = _B_TRUE;
		} else if (*line == '"') {
			line++;
			if (*line == '\0')
				return (i);
			indouble_quote = _B_TRUE;
		}
		argvec[i] = line;
		if (*line == '\0')
			return (i);
		i++;
		/* Skip until next whitespace or end of quoted text */
		if (insingle_quote) {
			while (*line != '\'' && *line != '\0')
				line++;
			if (*line == '\'') {
				*line = ' ';
			} else {
				/* Handle missing quote at end */
				i--;
				conferr("Missing end quote - ignoring <%s>\n",
				    argvec[i]);
				return (i);
			}
			insingle_quote = _B_FALSE;
		} else if (indouble_quote) {
			while (*line != '"' && *line != '\0')
				line++;
			if (*line == '"') {
				*line = ' ';
			} else {
				/* Handle missing quote at end */
				i--;
				conferr("Missing end quote - ignoring <%s>\n",
				    argvec[i]);
				return (i);
			}
			indouble_quote = _B_FALSE;
		} else {
			while (!isspace(*line) && *line != '\0')
				line++;
		}
		if (*line != '\0') {
			/* Break off argument */
			*line++ = '\0';
		}
		if (i > argcount)
			return (argcount);
	}
	/* NOTREACHED */
}

static void
parse_var_value(config_type_t type, struct configinfo *list, char *varstr,
    char *valstr, struct confvar *confvar)
{
	struct configinfo *cip;
	uint_t val;

	if (debug & D_CONFIG) {
		logmsg(LOG_DEBUG, "parse_var_value(%d, %s, %s)\n",
		    (int)type, varstr, valstr);
	}

	for (cip = list; cip->ci_name != NULL; cip++) {
		if (strcasecmp(cip->ci_name, varstr) == 0)
			break;
	}
	if (cip->ci_name == NULL) {
		conferr("Unknown variable: <%s>\n", varstr);
		return;
	}
	if (!(*cip->ci_parsefunc)(valstr, &val)) {
		conferr("Bad value: <%s>\n", valstr);
		return;
	}
	if (cip->ci_min != 0 && val < cip->ci_min) {
		conferr("Value %s is below minimum %u for %s\n",
		    valstr, cip->ci_min, varstr);
		return;
	}
	if (cip->ci_max != ~0U && val > cip->ci_max) {
		conferr("Value %s is above maximum %u for %s\n",
		    valstr, cip->ci_max, varstr);
		return;
	}
	/* Check against dynamic/relative limits */
	if (type == CONFIG_IF) {
		if (cip->ci_index == I_MinRtrAdvInterval &&
		    confvar[I_MaxRtrAdvInterval].cf_notdefault &&
		    val > confvar[I_MaxRtrAdvInterval].cf_value * 0.75) {
			conferr("MinRtrAdvInterval exceeds .75 * "
			    "MaxRtrAdvInterval (%u)\n",
			    confvar[I_MaxRtrAdvInterval].cf_value);
			return;
		}
		if (cip->ci_index == I_MaxRtrAdvInterval &&
		    confvar[I_MinRtrAdvInterval].cf_notdefault &&
		    confvar[I_MinRtrAdvInterval].cf_value > val * 0.75) {
			conferr("MinRtrAdvInterval (%u) exceeds .75 * "
			    "MaxRtrAdvInterval\n",
			    confvar[I_MinRtrAdvInterval].cf_value);
			return;
		}
		if (cip->ci_index == I_AdvDefaultLifetime &&
		    confvar[I_MaxRtrAdvInterval].cf_notdefault &&
		    val != 0 &&
		    val < confvar[I_MaxRtrAdvInterval].cf_value) {
			conferr("AdvDefaultLifetime is not between "
			    "MaxRtrAdrInterval (%u) and 9000 seconds\n",
			    confvar[I_MaxRtrAdvInterval].cf_value);
			return;
		}
		if (cip->ci_index == I_MaxRtrAdvInterval &&
		    confvar[I_AdvDefaultLifetime].cf_notdefault &&
		    confvar[I_AdvDefaultLifetime].cf_value < val) {
			conferr("AdvDefaultLifetime (%u) is not between "
			    "MaxRtrAdrInterval and 9000 seconds\n",
			    confvar[I_AdvDefaultLifetime].cf_value);
			return;
		}
	}
	confvar[cip->ci_index].cf_value = val;
	confvar[cip->ci_index].cf_notdefault = _B_TRUE;

	/* Derive dynamic/relative variables based on this one */
	if (type == CONFIG_IF) {
		if (cip->ci_index == I_MaxRtrAdvInterval &&
		    !confvar[I_MinRtrAdvInterval].cf_notdefault)
			confvar[I_MinRtrAdvInterval].cf_value = val / 3;
		if (cip->ci_index == I_MaxRtrAdvInterval &&
		    !confvar[I_AdvDefaultLifetime].cf_notdefault)
		    confvar[I_AdvDefaultLifetime].cf_value = 3 * val;
	}
}

/*
 * Split up the line into <variable> <value> pairs
 */
static void
parse_default(config_type_t type, struct configinfo *list,
    char *argvec[], int argcount, struct confvar *defaults)
{
	if (debug & D_CONFIG)
		logmsg(LOG_DEBUG, "parse_default: argc %d\n", argcount);
	while (argcount >= 2) {
		parse_var_value(type, list, argvec[0], argvec[1], defaults);

		argcount -= 2;
		argvec += 2;
	}
	if (argcount != 0)
		conferr("Trailing text <%s> ignored\n", argvec[0]);
}

/*
 * Returns true if ok; otherwise false.
 */
static void
parse_if(struct configinfo *list, char *argvec[], int argcount)
{
	char *ifname;
	struct phyint *pi;
	char save[sizeof (pi->pi_config)];

	if (debug & D_CONFIG)
		logmsg(LOG_DEBUG, "parse_if: argc %d\n", argcount);

	if (argcount < 1) {
		conferr("Missing interface name\n");
		return;
	}
	ifname = argvec[0];
	argvec++;
	argcount--;

	pi = phyint_lookup(ifname);
	if (pi == NULL) {
		/*
		 * Create the physical interface structure.
		 * Note, phyint_create() sets the interface
		 * defaults in pi_config.
		 */
		pi = phyint_create(ifname);
		if (pi == NULL) {
			conferr("Unable to use interface %s\n", ifname);
			return;
		}
	}

	(void) memcpy(save, pi->pi_config, sizeof (save));
	while (argcount >= 2) {
		parse_var_value(CONFIG_IF, list, argvec[0], argvec[1],
		    pi->pi_config);

		argcount -= 2;
		argvec += 2;
	}
	if (argcount != 0)
		logmsg(LOG_ERR, "Trailing text <%s> ignored\n", argvec[0]);
	check_if_var_consistency(pi->pi_config, save, sizeof (save));
}

static void
parse_prefix(struct configinfo *list, char *argvec[], int argcount)
{
	char *ifname, *prefix;
	struct phyint *pi;
	struct adv_prefix *adv_pr;
	struct in6_addr in6;
	int prefixlen;
	char save[sizeof (adv_pr->adv_pr_config)];

	if (debug & D_CONFIG)
		logmsg(LOG_DEBUG, "parse_prefix: argc %d\n", argcount);

	if (argcount < 2) {
		conferr("Missing prefix and/or interface name\n");
		return;
	}
	prefix = argvec[0];
	ifname = argvec[1];
	argvec += 2;
	argcount -= 2;

	prefixlen = parse_addrprefix(prefix, &in6);
	if (prefixlen == -1) {
		conferr("Bad prefix %s\n", prefix);
		return;
	}

	pi = phyint_lookup(ifname);
	if (pi == NULL) {
		/*
		 * Create the physical interface structure.
		 * Note, phyint_create() sets the interface
		 * defaults in pi_config.
		 */
		pi = phyint_create(ifname);
		if (pi == NULL) {
			conferr("Unable to use interface %s\n", ifname);
			return;
		}
	}
	adv_pr = adv_prefix_lookup(pi, in6, prefixlen);
	if (adv_pr == NULL) {
		int i;

		adv_pr = adv_prefix_create(pi, in6, prefixlen);
		if (adv_pr == NULL) {
			conferr("Unable to create prefix %s\n", prefix);
			return;
		}
		/*
		 * Copy the defaults from the default array.
		 */
		for (i = 0; i < I_PREFIXSIZE; i++) {
			adv_pr->adv_pr_config[i].cf_value =
			    prefixdefaults[i].cf_value;
			adv_pr->adv_pr_config[i].cf_notdefault =
			    prefixdefaults[i].cf_notdefault;
		}
	}

	(void) memcpy(save, adv_pr->adv_pr_config, sizeof (save));
	while (argcount >= 2) {
		parse_var_value(CONFIG_PREFIX, list, argvec[0], argvec[1],
		    adv_pr->adv_pr_config);

		argcount -= 2;
		argvec += 2;
	}
	check_var_consistency(adv_pr->adv_pr_config, save, sizeof (save));
	if (argcount != 0)
		logmsg(LOG_ERR, "Trailing text <%s> ignored\n", argvec[0]);
}

/*
 * Returns true if ok (and *resp updated) and false if failed.
 */
static boolean_t
parse_onoff(char *str, uint_t *resp)
{
	if (strcasecmp(str, "on") == 0) {
		*resp = 1;
		return (_B_TRUE);
	}
	if (strcasecmp(str, "off") == 0) {
		*resp = 0;
		return (_B_TRUE);
	}
	if (strcasecmp(str, "true") == 0) {
		*resp = 1;
		return (_B_TRUE);
	}
	if (strcasecmp(str, "false") == 0) {
		*resp = 0;
		return (_B_TRUE);
	}
	if (parse_int(str, resp)) {
		if (*resp == 0 || *resp == 1)
			return (_B_TRUE);
	}
	return (_B_FALSE);
}

/*
 * Returns true if ok (and *resp updated) and false if failed.
 */
static boolean_t
parse_int(char *str, uint_t *resp)
{
	char *end;
	int res;

	res = strtoul(str, &end, 0);
	if (end == str)
		return (_B_FALSE);
	*resp = res;
	return (_B_TRUE);
}

/*
 * Parse something with a unit of millseconds.
 * Regognizes the suffixes "ms", "s", "m", "h", and "d".
 *
 * Returns true if ok (and *resp updated) and false if failed.
 */
static boolean_t
parse_ms(char *str, uint_t *resp)
{
	/* Look at the last and next to last character */
	char *cp, *last, *nlast;
	char str2[BUFSIZ];	/* For local modification */
	int multiplier = 1;

	(void) strncpy(str2, str, sizeof (str2));
	str2[sizeof (str2) - 1] = '\0';

	last = str2;
	nlast = NULL;
	for (cp = str2; *cp != '\0'; cp++) {
		nlast = last;
		last = cp;
	}
	if (debug & D_PARSE) {
		logmsg(LOG_DEBUG, "parse_ms: last <%c> nlast <%c>\n",
		    (last != NULL ? *last : ' '),
		    (nlast != NULL ? *nlast : ' '));
	}
	switch (*last) {
	case 'd':
		multiplier *= 24;
		/* FALLTHRU */
	case 'h':
		multiplier *= 60;
		/* FALLTHRU */
	case 'm':
		multiplier *= 60;
		*last = '\0';
		multiplier *= 1000;	/* Convert to milliseconds */
		break;
	case 's':
		/* Could be "ms" or "s" */
		if (nlast != NULL && *nlast == 'm') {
			/* "ms" */
			*nlast = '\0';
		} else {
			*last = '\0';
			multiplier *= 1000;	/* Convert to milliseconds */
		}
		break;
	}

	if (!parse_int(str2, resp))
		return (_B_FALSE);

	*resp *= multiplier;
	return (_B_TRUE);
}

/*
 * Parse something with a unit of seconds.
 * Regognizes the suffixes "s", "m", "h", and "d".
 *
 * Returns true if ok (and *resp updated) and false if failed.
 */
static boolean_t
parse_s(char *str, uint_t *resp)
{
	/* Look at the last character */
	char *cp, *last;
	char str2[BUFSIZ];	/* For local modification */
	int multiplier = 1;

	(void) strncpy(str2, str, sizeof (str2));
	str2[sizeof (str2) - 1] = '\0';

	last = str2;
	for (cp = str2; *cp != '\0'; cp++) {
		last = cp;
	}
	if (debug & D_PARSE) {
		logmsg(LOG_DEBUG, "parse_s: last <%c>\n",
		    (last != NULL ? *last : ' '));
	}
	switch (*last) {
	case 'd':
		multiplier *= 24;
		/* FALLTHRU */
	case 'h':
		multiplier *= 60;
		/* FALLTHRU */
	case 'm':
		multiplier *= 60;
		/* FALLTHRU */
	case 's':
		*last = '\0';
		break;
	}
	if (!parse_int(str2, resp))
		return (_B_FALSE);

	*resp *= multiplier;
	return (_B_TRUE);
}

/*
 * Return prefixlen (0 to 128) if ok; -1 if failed.
 */
static int
parse_addrprefix(char *strin, struct in6_addr *in6)
{
	char str[BUFSIZ];	/* Local copy for modification */
	int prefixlen;
	char *cp;
	char *end;

	(void) strncpy(str, strin, sizeof (str));
	str[sizeof (str) - 1] = '\0';

	cp = strchr(str, '/');
	if (cp == NULL)
		return (-1);
	*cp = '\0';
	cp++;

	prefixlen = strtol(cp, &end, 10);
	if (cp == end)
		return (-1);

	if (prefixlen < 0 || prefixlen > IPV6_ABITS)
		return (-1);

	if (inet_pton(AF_INET6, str, in6) != 1)
		return (-1);

	return (prefixlen);
}

/*
 * Parse an absolute date using a datemsk config file.
 * Return the difference (measured in seconds) between that date/time and
 * the current date/time.
 * If the date has passed return zero.
 *
 * Returns true if ok (and *resp updated) and false if failed.
 * XXX Due to getdate limitations can not exceed year 2038.
 */
static boolean_t
parse_date(char *str, uint_t *resp)
{
	struct tm *tm;
	struct timeval tvs;
	time_t time, ntime;

	if (getenv("DATEMSK") == NULL) {
		(void) putenv("DATEMSK=/etc/inet/datemsk.ndpd");
	}

	if (gettimeofday(&tvs, NULL) < 0) {
		logperror("gettimeofday");
		return (_B_FALSE);
	}
	time = tvs.tv_sec;
	tm = getdate(str);
	if (tm == NULL) {
		logmsg(LOG_ERR, "Bad date <%s> (error %d)\n",
		    str, getdate_err);
		return (_B_FALSE);
	}

	ntime = mktime(tm);

	if (debug & D_PARSE) {
		char buf[BUFSIZ];

		(void) strftime(buf, sizeof (buf), "%Y-%m-%d %R %Z", tm);
		logmsg(LOG_DEBUG, "parse_date: <%s>, delta %ld seconds\n",
		    buf, ntime - time);
	}
	if (ntime < time) {
		conferr("Date in the past <%s>\n", str);
		*resp = 0;
		return (_B_TRUE);
	}
	*resp = (ntime - time);
	return (_B_TRUE);
}

/* PRINTFLIKE1 */
static void
conferr(char *fmt, ...)
{
	char msg[NDPD_LOGMSGSIZE];
	size_t slen;

	va_list ap;
	va_start(ap, fmt);

	(void) snprintf(msg, NDPD_LOGMSGSIZE, "%s line %d: ",
	    conf_filename, lineno);
	slen = strlen(msg);
	(void) vsnprintf(msg + slen, NDPD_LOGMSGSIZE - slen, fmt, ap);

	logmsg(LOG_ERR, "%s", msg);

	va_end(ap);
}

static FILE *
open_conffile(char *filename)
{
	if (strlcpy(conf_filename, filename, MAXPATHLEN) >= MAXPATHLEN) {
		logmsg(LOG_ERR, "config file pathname is too long\n");
		return (NULL);
	}

	lineno = 0;

	return (fopen(filename, "r"));

}