#include "defs.h"
#include "tables.h"
#define NDPD_LOGMSGSIZE 1024
typedef boolean_t (*pfb_t)(char *, uint_t *);
struct configinfo {
char *ci_name;
uint_t ci_min;
uint_t ci_max;
uint_t ci_default;
uint_t ci_index;
pfb_t ci_parsefunc;
};
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 *);
static boolean_t parse_int(char *, uint_t *);
static boolean_t parse_ms(char *, uint_t *);
static boolean_t parse_s(char *, uint_t *);
static boolean_t parse_date(char *, uint_t *);
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);
static struct configinfo iflist[] = {
{ "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 },
{ "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 },
{ "StatelessAddrConf", 0, 1, 1, I_StatelessAddrConf, parse_onoff },
{ "StatefulAddrConf", 0, 1, 1, I_StatefulAddrConf, parse_onoff },
{ "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 }
};
static struct configinfo prefixlist[] = {
{ "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 },
};
struct confvar ifdefaults[I_IFSIZE];
static struct confvar prefixdefaults[I_PREFIXSIZE];
static char conf_filename[MAXPATHLEN];
static int lineno;
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);
}
}
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;
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;
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';
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) {
continue;
}
if (strcmp(argvec[0], "ifdefault") == 0) {
char save[sizeof (ifdefaults)];
if (defaultdone) {
conferr("ifdefault after non-default "
"command\n");
continue;
}
(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;
}
(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);
}
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);
}
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);
if (got >= 2 && line[got-2] == '\\') {
line += got - 2;
length -= got - 2;
goto retry;
}
if (got > 0)
line[got-1] = '\0';
return (1);
}
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;
cp = strchr(line, '#');
if (cp != NULL)
*cp = '\0';
for (;;) {
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++;
if (insingle_quote) {
while (*line != '\'' && *line != '\0')
line++;
if (*line == '\'') {
*line = ' ';
} else {
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 {
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') {
*line++ = '\0';
}
if (i > argcount)
return (argcount);
}
}
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;
}
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;
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;
}
}
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]);
}
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) {
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) {
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;
}
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]);
}
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);
}
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);
}
static boolean_t
parse_ms(char *str, uint_t *resp)
{
char *cp, *last, *nlast;
char str2[BUFSIZ];
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;
case 'h':
multiplier *= 60;
case 'm':
multiplier *= 60;
*last = '\0';
multiplier *= 1000;
break;
case 's':
if (nlast != NULL && *nlast == 'm') {
*nlast = '\0';
} else {
*last = '\0';
multiplier *= 1000;
}
break;
}
if (!parse_int(str2, resp))
return (_B_FALSE);
*resp *= multiplier;
return (_B_TRUE);
}
static boolean_t
parse_s(char *str, uint_t *resp)
{
char *cp, *last;
char str2[BUFSIZ];
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;
case 'h':
multiplier *= 60;
case 'm':
multiplier *= 60;
case 's':
*last = '\0';
break;
}
if (!parse_int(str2, resp))
return (_B_FALSE);
*resp *= multiplier;
return (_B_TRUE);
}
static int
parse_addrprefix(char *strin, struct in6_addr *in6)
{
char str[BUFSIZ];
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);
}
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);
}
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"));
}