root/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/config.c
/*
 * 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.
 */

#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"));

}