#include <ctype.h>
+bool check_config = false;
+
typedef enum conf_property {
CONF_PROPERTY_INVALID = 0,
CONF_PROPERTY_NAME,
CONF_PARSER const *variables;
};
+typedef struct cf_file_t {
+ char const *filename;
+ CONF_SECTION *cs;
+ struct stat buf;
+} cf_file_t;
+
CONF_SECTION *root_config = NULL;
bool cf_new_escape = false;
char *output, size_t outsize,
char const *input, bool *soft_fail);
+static int cf_file_include(CONF_SECTION *cs, char const *filename_in);
+
+
+
/*
* Isolate the scary casts in these tiny provably-safe functions
*/
return strcmp(one->name, two->name);
}
+/*
+ * Functions for tracking filenames.
+ */
+static int filename_cmp(void const *a, void const *b)
+{
+ cf_file_t const *one = a;
+ cf_file_t const *two = b;
+
+ if (one->buf.st_dev < two->buf.st_dev) return -1;
+ if (one->buf.st_dev > two->buf.st_dev) return +1;
+
+ if (one->buf.st_ino < two->buf.st_ino) return -1;
+ if (one->buf.st_ino > two->buf.st_ino) return +1;
+
+ return 0;
+}
+
+static FILE *cf_file_open(CONF_SECTION *cs, char const *filename)
+{
+ cf_file_t *file;
+ CONF_DATA *cd;
+ CONF_SECTION *top;
+ rbtree_t *tree;
+ int fd;
+ FILE *fp;
+
+ top = cf_top_section(cs);
+ cd = cf_data_find_internal(top, "filename", 0);
+ if (!cd) return NULL;
+
+ tree = cd->data;
+
+ fp = fopen(filename, "r");
+ if (!fp) {
+ ERROR("Unable to open file \"%s\": %s",
+ filename, fr_syserror(errno));
+ return NULL;
+ }
+
+ fd = fileno(fp);
+
+ file = talloc(tree, cf_file_t);
+ if (!file) {
+ fclose(fp);
+ return NULL;
+ }
+
+ file->filename = filename;
+ file->cs = cs;
+
+ if (fstat(fd, &file->buf) == 0) {
+#ifdef S_IWOTH
+ if ((file->buf.st_mode & S_IWOTH) != 0) {
+ ERROR("Configuration file %s is globally writable. "
+ "Refusing to start due to insecure configuration.", filename);
+
+ fclose(fp);
+ talloc_free(file);
+ return NULL;
+ }
+#endif
+ }
+
+ /*
+ * We can include the same file twice. e.g. when it
+ * contains common definitions, such as for SQL.
+ *
+ * Though the admin should really use templates for that.
+ */
+ if (!rbtree_insert(tree, file)) {
+ talloc_free(file);
+ }
+
+ return fp;
+}
+
+/*
+ * Do some checks on the file
+ */
+static bool cf_file_check(CONF_SECTION *cs, char const *filename, bool check_perms)
+{
+ cf_file_t *file;
+ CONF_DATA *cd;
+ CONF_SECTION *top;
+ rbtree_t *tree;
+
+ top = cf_top_section(cs);
+ cd = cf_data_find_internal(top, "filename", 0);
+ if (!cd) return false;
+
+ tree = cd->data;
+
+ file = talloc(tree, cf_file_t);
+ if (!file) return false;
+
+ file->filename = filename;
+ file->cs = cs;
+
+ if (stat(filename, &file->buf) < 0) {
+ ERROR("Unable to check file \"%s\": %s", filename, fr_syserror(errno));
+ talloc_free(file);
+ return false;
+ }
+
+ if (!check_perms) {
+ talloc_free(file);
+ return true;
+ }
+
+#ifdef S_IWOTH
+ if ((file->buf.st_mode & S_IWOTH) != 0) {
+ ERROR("Configuration file %s is globally writable. "
+ "Refusing to start due to insecure configuration.", filename);
+ talloc_free(file);
+ return false;
+ }
+#endif
+
+ /*
+ * It's OK to include the same file twice...
+ */
+ if (!rbtree_insert(tree, file)) {
+ talloc_free(file);
+ }
+
+ return true;
+
+}
+
+
+typedef struct cf_file_callback_t {
+ int rcode;
+ rb_walker_t callback;
+ CONF_SECTION *modules;
+} cf_file_callback_t;
+
+
+/*
+ * Return 0 for keep going, 1 for stop.
+ */
+static int file_callback(void *ctx, void *data)
+{
+ cf_file_callback_t *cb = ctx;
+ cf_file_t *file = data;
+ struct stat buf;
+
+ /*
+ * The file doesn't exist or we can no longer read it.
+ */
+ if (stat(file->filename, &buf) < 0) {
+ cb->rcode = CF_FILE_ERROR;
+ return 1;
+ }
+
+ /*
+ * The file changed, we'll need to re-read it.
+ */
+ if (buf.st_mtime != file->buf.st_mtime) {
+
+ if (cb->callback(cb->modules, file->cs)) {
+ cb->rcode |= CF_FILE_MODULE;
+ DEBUG3("HUP: Changed module file %s", file->filename);
+ } else {
+ DEBUG3("HUP: Changed config file %s", file->filename);
+ cb->rcode |= CF_FILE_CONFIG;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * See if any of the files have changed.
+ */
+int cf_file_changed(CONF_SECTION *cs, rb_walker_t callback)
+{
+ CONF_DATA *cd;
+ CONF_SECTION *top;
+ cf_file_callback_t cb;
+ rbtree_t *tree;
+
+ top = cf_top_section(cs);
+ cd = cf_data_find_internal(top, "filename", 0);
+ if (!cd) return true;
+
+ tree = cd->data;
+
+ cb.rcode = CF_FILE_NONE;
+ cb.callback = callback;
+ cb.modules = cf_section_sub_find(cs, "modules");
+
+ (void) rbtree_walk(tree, RBTREE_IN_ORDER, file_callback, &cb);
+
+ return cb.rcode;
+}
+
static int _cf_section_free(CONF_SECTION *cs)
{
/*
new = cf_pair_alloc(parent, cp->attr, cf_pair_value(cp),
cp->op, cp->lhs_type, cp->rhs_type);
- if (new) {
- new->item.lineno = cp->item.lineno;
+ if (!new) return NULL;
+
+ new->parsed = cp->parsed;
+ new->item.lineno = cp->item.lineno;
+
+ /*
+ * Avoid mallocs if possible.
+ */
+ if (!cp->item.filename || (parent->item.filename && !strcmp(parent->item.filename, cp->item.filename))) {
+ new->item.filename = parent->item.filename;
+ } else {
new->item.filename = talloc_strdup(new, cp->item.filename);
}
if (!name1) return NULL;
- if (name2) {
+ if (name2 && parent) {
if (strchr(name2, '$')) {
name2 = cf_expand_variables(parent->item.filename,
- &parent->item.lineno,
- parent,
- buffer, sizeof(buffer), name2, NULL);
+ &parent->item.lineno,
+ parent,
+ buffer, sizeof(buffer), name2, NULL);
if (!name2) {
ERROR("Failed expanding section name");
return NULL;
* @note recursively duplicates any child sections.
* @note does not duplicate any data associated with a section, or its child sections.
*
- * @param parent section.
+ * @param parent section (may be NULL).
* @param cs to duplicate.
* @param name1 of new section.
* @param name2 of new section.
}
new->item.lineno = cs->item.lineno;
- new->item.filename = talloc_strdup(new, cs->item.filename);
+
+ if (!cs->item.filename || (parent && (strcmp(parent->item.filename, cs->item.filename) == 0))) {
+ new->item.filename = parent->item.filename;
+ } else {
+ new->item.filename = talloc_strdup(new, cs->item.filename);
+ }
for (ci = cs->children; ci; ci = ci->next) {
switch (ci->type) {
* section.
*/
cp = cf_pair_find(cs, p);
- if (cp) return &(cp->item);
+ if (cp) {
+ cp->parsed = true; /* conf pairs which are referenced count as parsed */
+ return &(cp->item);
+ }
next = cf_section_sub_find(cs, p);
if (next) return &(next->item);
ci = cf_reference_item(parentcs, outercs, name);
if (!ci) {
if (soft_fail) *soft_fail = true;
- ERROR("%s[%d]: Reference \"%s\" not found", cf, *lineno, input);
+ ERROR("%s[%d]: Reference \"${%s}\" not found", cf, *lineno, name);
return NULL;
}
ERROR("%s[%d]: Reference \"%s\" type is invalid", cf, *lineno, input);
return NULL;
}
- } else if (memcmp(ptr, "$ENV{", 5) == 0) {
+ } else if (strncmp(ptr, "$ENV{", 5) == 0) {
char *env;
ptr += 5;
* @note The dflt value will only be used if no matching #CONF_PAIR is found. Empty strings will not
* result in the dflt value being used.
*
+ * **PW_TYPE to data type mappings**
+ * | PW_TYPE | Data type | Dynamically allocated |
+ * | ----------------------- | ------------------ | ---------------------- |
+ * | PW_TYPE_TMPL | ``vp_tmpl_t`` | Yes |
+ * | PW_TYPE_BOOLEAN | ``bool`` | No |
+ * | PW_TYPE_INTEGER | ``uint32_t`` | No |
+ * | PW_TYPE_SHORT | ``uint16_t`` | No |
+ * | PW_TYPE_INTEGER64 | ``uint64_t`` | No |
+ * | PW_TYPE_SIGNED | ``int32_t`` | No |
+ * | PW_TYPE_STRING | ``char const *`` | Yes |
+ * | PW_TYPE_IPV4_ADDR | ``fr_ipaddr_t`` | No |
+ * | PW_TYPE_IPV4_PREFIX | ``fr_ipaddr_t`` | No |
+ * | PW_TYPE_IPV6_ADDR | ``fr_ipaddr_t`` | No |
+ * | PW_TYPE_IPV6_PREFIX | ``fr_ipaddr_t`` | No |
+ * | PW_TYPE_COMBO_IP_ADDR | ``fr_ipaddr_t`` | No |
+ * | PW_TYPE_COMBO_IP_PREFIX | ``fr_ipaddr_t`` | No |
+ * | PW_TYPE_TIMEVAL | ``struct timeval`` | No |
+ *
* @param cs to search for matching #CONF_PAIR in.
* @param name of #CONF_PAIR to search for.
* @param type Data type to parse #CONF_PAIR value as.
* Should be one of the following ``data`` types, and one or more of the following ``flag`` types or'd together:
* - ``data`` #PW_TYPE_TMPL - @copybrief PW_TYPE_TMPL
- Feeds the value into #tmpl_afrom_str. Value can be
+ * Feeds the value into #tmpl_afrom_str. Value can be
* obtained when processing requests, with #tmpl_expand or #tmpl_aexpand.
* - ``data`` #PW_TYPE_BOOLEAN - @copybrief PW_TYPE_BOOLEAN
* - ``data`` #PW_TYPE_INTEGER - @copybrief PW_TYPE_INTEGER
* - ``data`` #PW_TYPE_IPV4_ADDR - @copybrief PW_TYPE_IPV4_ADDR (IPv4 address with prefix 32).
* - ``data`` #PW_TYPE_IPV4_PREFIX - @copybrief PW_TYPE_IPV4_PREFIX (IPv4 address with variable prefix).
* - ``data`` #PW_TYPE_IPV6_ADDR - @copybrief PW_TYPE_IPV6_ADDR (IPv6 address with prefix 128).
+ * - ``data`` #PW_TYPE_IPV6_PREFIX - @copybrief PW_TYPE_IPV6_PREFIX (IPv6 address with variable prefix).
* - ``data`` #PW_TYPE_COMBO_IP_ADDR - @copybrief PW_TYPE_COMBO_IP_ADDR (IPv4/IPv6 address with
- prefix 32/128).
+ * prefix 32/128).
* - ``data`` #PW_TYPE_COMBO_IP_PREFIX - @copybrief PW_TYPE_COMBO_IP_PREFIX (IPv4/IPv6 address with
* variable prefix).
* - ``data`` #PW_TYPE_TIMEVAL - @copybrief PW_TYPE_TIMEVAL
* - ``flag`` #PW_TYPE_NOT_EMPTY - @copybrief PW_TYPE_NOT_EMPTY
* @param data Pointer to a global variable, or pointer to a field in the struct being populated with values.
* @param dflt value to use, if no #CONF_PAIR is found.
- * @return -1 on error, -2 if deprecated, 0 on success (correctly parsed), 1 if default value was used.
+ * @return
+ * - 1 if default value was used.
+ * - 0 on success.
+ * - -1 on error.
+ * - -2 if deprecated.
*/
int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *data, char const *dflt)
{
int rcode;
- bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, xlat;
+ bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists;
char **q;
char const *value;
CONF_PAIR *cp = NULL;
fr_ipaddr_t *ipaddr;
char buffer[8192];
+ CONF_ITEM *c_item;
if (!cs) return -1;
+ c_item = &cs->item;
+
deprecated = (type & PW_TYPE_DEPRECATED);
required = (type & PW_TYPE_REQUIRED);
attribute = (type & PW_TYPE_ATTRIBUTE);
secret = (type & PW_TYPE_SECRET);
file_input = (type == PW_TYPE_FILE_INPUT); /* check, not and */
+ file_exists = (type == PW_TYPE_FILE_EXISTS); /* check, not and */
cant_be_empty = (type & PW_TYPE_NOT_EMPTY);
tmpl = (type & PW_TYPE_TMPL);
- xlat = (type & PW_TYPE_XLAT);
+ multi = (type & PW_TYPE_MULTI);
if (attribute) required = true;
if (required) cant_be_empty = true; /* May want to review this in the future... */
+ /*
+ * Everything except templates must have a base type.
+ */
+ if (!(type & 0xff) && !tmpl) {
+ cf_log_err(c_item, "Configuration item \"%s\" must have a data type", name);
+ return -1;
+ }
+
type &= 0xff; /* normal types are small */
+
rcode = 0;
cp = cf_pair_find(cs, name);
* section, use the default value.
*/
if (!cp) {
+ if (deprecated) return 0; /* Don't set the default value */
+
rcode = 1;
value = dflt;
/*
* Something matched, used the CONF_PAIR value.
*/
} else {
+ CONF_PAIR *next = cp;
+
value = cp->value;
cp->parsed = true;
+ c_item = &cp->item;
+
+ if (deprecated) {
+ cf_log_err(c_item, "Configuration item \"%s\" is deprecated", name);
+ return -2;
+ }
+
+ /*
+ * A quick check to see if the next item is the same.
+ */
+ if (!multi && cp->item.next && (cp->item.next->type == CONF_ITEM_PAIR)) {
+ next = cf_item_to_pair(cp->item.next);
+
+ if (strcmp(next->attr, name) == 0) {
+ WARN("%s[%d]: Ignoring duplicate configuration item '%s'",
+ next->item.filename ? next->item.filename : "unknown",
+ next->item.lineno, name);
+ }
+ }
+
+ if (multi) {
+ while ((next = cf_pair_find_next(cs, next, name)) != NULL) {
+ /*
+ * @fixme We should actually validate
+ * the value of the pairs too
+ */
+ next->parsed = true;
+ };
+ }
}
if (!value) {
if (required) {
- is_required:
- if (!cp) {
- cf_log_err(&(cs->item), "Configuration item '%s' must have a value", name);
- } else {
- cf_log_err(&(cp->item), "Configuration item '%s' must have a value", name);
- }
+ cf_log_err(c_item, "Configuration item \"%s\" must have a value", name);
+
return -1;
}
return rcode;
if ((value[0] == '\0') && cant_be_empty) {
cant_be_empty:
- if (!cp) {
- cf_log_err(&(cs->item), "Configuration item '%s' must not be empty (zero length)", name);
- if (!required) cf_log_err(&(cs->item), "Comment item to silence this message");
- } else {
- cf_log_err(&(cp->item), "Configuration item '%s' must not be empty (zero length)", name);
- if (!required) cf_log_err(&(cp->item), "Comment item to silence this message");
- }
+ cf_log_err(c_item, "Configuration item \"%s\" must not be empty (zero length)", name);
+ if (!required) cf_log_err(c_item, "Comment item to silence this message");
+
return -1;
}
- if (deprecated) {
- cf_log_err(&(cs->item), "Configuration item \"%s\" is deprecated", name);
-
- return -2;
- }
/*
- * Process a value as a template.
+ * Process a value as a LITERAL template. Once all of
+ * the attrs and xlats are defined, the pass2 code
+ * converts it to the appropriate type.
*/
if (tmpl) {
- ssize_t slen;
vp_tmpl_t *vpt;
if (!value) {
return 0;
}
- slen = tmpl_afrom_str(cs, &vpt, value, strlen(value),
- cp ? cf_pair_value_type(cp) : T_DOUBLE_QUOTED_STRING,
- REQUEST_CURRENT, PAIR_LIST_REQUEST, true);
- if (slen < 0) {
- char *spaces, *text;
-
- fr_canonicalize_error(cs, &spaces, &text, slen, fr_strerror());
-
- cf_log_err_cs(cs, "Failed parsing configuration item '%s'", name);
- cf_log_err_cs(cs, "%s", value);
- cf_log_err_cs(cs, "%s^ %s", spaces, text);
-
- talloc_free(spaces);
- talloc_free(text);
-
- return -1;
- }
-
- /*
- * Sanity check
- *
- * Don't add default - update with new types.
- */
- switch (vpt->type) {
- case TMPL_TYPE_LITERAL:
- case TMPL_TYPE_ATTR:
- case TMPL_TYPE_ATTR_UNDEFINED:
- case TMPL_TYPE_LIST:
- case TMPL_TYPE_DATA:
- case TMPL_TYPE_EXEC:
- case TMPL_TYPE_XLAT:
- case TMPL_TYPE_XLAT_STRUCT:
- break;
-
- case TMPL_TYPE_UNKNOWN:
- case TMPL_TYPE_REGEX:
- case TMPL_TYPE_REGEX_STRUCT:
- case TMPL_TYPE_NULL:
- rad_assert(0);
- }
-
- /*
- * If the attribute flag is set, the template must be an
- * attribute reference.
- */
- if (attribute && (vpt->type != TMPL_TYPE_ATTR)) {
- cf_log_err(&(cs->item), "Configuration item '%s' must be an attr "
- "but is an %s", name, fr_int2str(tmpl_names, vpt->type, "<INVALID>"));
- talloc_free(vpt);
- return -1;
- }
-
- /*
- * If the xlat flag is set, the template must be an xlat
- */
- if (xlat && (vpt->type != TMPL_TYPE_XLAT_STRUCT)) {
- cf_log_err(&(cs->item), "Configuration item '%s' must be an xlat expansion but is an %s",
- name, fr_int2str(tmpl_names, vpt->type, "<INVALID>"));
- talloc_free(vpt);
- return -1;
- }
-
- /*
- * If we have a type, and the template is an attribute reference
- * check that the attribute reference matches the type.
- */
- if ((type > 0) && (vpt->type == TMPL_TYPE_ATTR) && (vpt->tmpl_da->type != type)) {
- cf_log_err(&(cs->item), "Configuration item '%s' attr must be an %s, but is an %s",
- name, fr_int2str(dict_attr_types, type, "<INVALID>"),
- fr_int2str(dict_attr_types, vpt->tmpl_da->type, "<INVALID>"));
- talloc_free(vpt);
- return -1;
- }
+ rad_assert(!attribute);
+ vpt = tmpl_alloc(cs, TMPL_TYPE_LITERAL, value, strlen(value));
*(vp_tmpl_t **)data = vpt;
return 0;
switch (type) {
case PW_TYPE_BOOLEAN:
/*
- * Allow yes/no and on/off
+ * Allow yes/no, true/false, and on/off
*/
if ((strcasecmp(value, "yes") == 0) ||
+ (strcasecmp(value, "true") == 0) ||
(strcasecmp(value, "on") == 0)) {
*(bool *)data = true;
} else if ((strcasecmp(value, "no") == 0) ||
+ (strcasecmp(value, "false") == 0) ||
(strcasecmp(value, "off") == 0)) {
*(bool *)data = false;
} else {
}
break;
+ case PW_TYPE_BYTE:
+ {
+ unsigned long v = strtoul(value, 0, 0);
+
+ if (v > UINT8_MAX) {
+ cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", value,
+ name, UINT8_MAX);
+ return -1;
+ }
+ *(uint8_t *)data = (uint8_t) v;
+ cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, name, *(uint8_t *)data);
+ }
+ break;
+
case PW_TYPE_SHORT:
{
unsigned long v = strtoul(value, 0, 0);
}
}
- if (required && !value) goto is_required;
if (cant_be_empty && (value[0] == '\0')) goto cant_be_empty;
if (attribute) {
/*
* Hide secrets when using "radiusd -X".
*/
- if (secret && (debug_flag <= 2)) {
+ if (secret && (rad_debug_lvl <= 2)) {
cf_log_info(cs, "%.*s\t%s = <<< secret >>>",
cs->depth, parse_spaces, name);
} else {
* to be caught as early as possible, during
* server startup.
*/
- if (*q && file_input) {
- struct stat buf;
-
- if (stat(*q, &buf) < 0) {
- char user[255], group[255];
-
- ERROR("Unable to open file \"%s\": %s", value, fr_syserror(errno));
- ERROR("Our effective user and group was %s:%s",
- (rad_prints_uid(NULL, user, sizeof(user), geteuid()) < 0) ?
- "unknown" : user,
- (rad_prints_gid(NULL, group, sizeof(group), getegid()) < 0) ?
- "unknown" : group );
+ if (*q && file_input && !cf_file_check(cs, *q, true)) {
+ return -1;
+ }
- return -1;
- }
+ if (*q && file_exists && !cf_file_check(cs, *q, false)) {
+ return -1;
}
break;
case PW_TYPE_COMBO_IP_PREFIX:
ipaddr = data;
- if (fr_pton(ipaddr, value, -1, true) < 0) {
+ if (fr_pton(ipaddr, value, -1, AF_UNSPEC, true) < 0) {
ERROR("%s", fr_strerror());
return -1;
}
for (ci = cs->children; ci; ci = ci->next) {
/*
- * Recurse on sections.
+ * Don't recurse on sections. We can only safely
+ * check conf pairs at the same level as the
+ * section that was just parsed.
*/
- if (ci->type == CONF_ITEM_SECTION) {
- cf_section_parse_warn(cf_item_to_section(ci));
- continue;
- }
-
+ if (ci->type == CONF_ITEM_SECTION) continue;
if (ci->type == CONF_ITEM_PAIR) {
CONF_PAIR *cp;
}
}
-/*
- * Parse a configuration section into user-supplied variables.
+/** Parse a configuration section into user-supplied variables
+ *
+ * @param cs to parse.
+ * @param base pointer to a struct to fill with data. Any buffers will also be talloced
+ * using this parent as a pointer.
+ * @param variables mappings between struct fields and #CONF_ITEM s.
+ * @return
+ * - 0 on success.
+ * - -1 on general error.
+ * - -2 if a deprecated #CONF_ITEM was found.
*/
-int cf_section_parse(CONF_SECTION *cs, void *base,
- CONF_PARSER const *variables)
+int cf_section_parse(CONF_SECTION *cs, void *base, CONF_PARSER const *variables)
{
- int ret;
+ int ret = 0;
int i;
void *data;
cs->variables = variables; /* this doesn't hurt anything */
if (!cs->name2) {
- cf_log_info(cs, "%.*s%s {", cs->depth, parse_spaces,
- cs->name1);
+ cf_log_info(cs, "%.*s%s {", cs->depth, parse_spaces, cs->name1);
} else {
- cf_log_info(cs, "%.*s%s %s {", cs->depth, parse_spaces,
- cs->name1, cs->name2);
+ cf_log_info(cs, "%.*s%s %s {", cs->depth, parse_spaces, cs->name1, cs->name2);
}
cf_section_parse_init(cs, base, variables);
*/
if (!variables[i].dflt || !subcs) {
ERROR("Internal sanity check 1 failed in cf_section_parse %s", variables[i].name);
- goto error;
+ ret = -1;
+ goto finish;
}
- if (cf_section_parse(subcs, (uint8_t *)base + variables[i].offset,
- (CONF_PARSER const *) variables[i].dflt) < 0) goto error;
+ ret = cf_section_parse(subcs, (uint8_t *)base + variables[i].offset,
+ (CONF_PARSER const *) variables[i].dflt);
+ if (ret < 0) goto finish;
continue;
} /* else it's a CONF_PAIR */
} else if (base) {
data = ((char *)base) + variables[i].offset;
} else {
- DEBUG2("Internal sanity check 2 failed in cf_section_parse");
- goto error;
+ ERROR("Internal sanity check 2 failed in cf_section_parse");
+ ret = -1;
+ goto finish;
}
/*
* Parse the pair we found, or a default value.
*/
ret = cf_item_parse(cs, variables[i].name, variables[i].type, data, variables[i].dflt);
- if (ret < 0) {
- /*
- * Be nice, and print the name of the new config item.
- */
- if ((ret == -2) && (variables[i + 1].offset == variables[i].offset) &&
+ switch (ret) {
+ case 1: /* Used default */
+ ret = 0;
+ break;
+
+ case 0: /* OK */
+ break;
+
+ case -1: /* Parse error */
+ goto finish;
+
+ case -2: /* Deprecated CONF ITEM */
+ if ((variables[i + 1].offset == variables[i].offset) &&
(variables[i + 1].data == variables[i].data)) {
cf_log_err(&(cs->item), "Replace \"%s\" with \"%s\"", variables[i].name,
variables[i + 1].name);
+ } else {
+ cf_log_err(&(cs->item), "Cannot use deprecated configuration item \"%s\"", variables[i].name);
}
-
- goto error;
+ goto finish;
}
} /* for all variables in the configuration section */
/*
+ * Ensure we have a proper terminator, type so we catch
+ * missing terminators reliably
+ */
+ rad_assert(variables[i].type == -1);
+
+ /*
* Warn about items in the configuration which weren't
* checked during parsing.
*/
- if (debug_flag >= 3) cf_section_parse_warn(cs);
-
- cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
+ if (rad_debug_lvl >= 3) cf_section_parse_warn(cs);
cs->base = base;
- return 0;
-
- error:
cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
- return -1;
+
+finish:
+ return ret;
}
*/
for (i = 0; variables[i].name != NULL; i++) {
CONF_PAIR *cp;
+ void *data;
/*
* Handle subsections specially
continue;
} /* else it's a CONF_PAIR */
+ /*
+ * Figure out which data we need to fix.
+ */
+ if (variables[i].data) {
+ data = variables[i].data; /* prefer this. */
+ } else if (base) {
+ data = ((char *)base) + variables[i].offset;
+ } else {
+ data = NULL;
+ }
+
cp = cf_pair_find(cs, variables[i].name);
+ xlat = NULL;
redo:
- if (!cp || !cp->value) continue;
+ if (!cp || !cp->value || !data) continue;
if ((cp->rhs_type != T_DOUBLE_QUOTED_STRING) &&
(cp->rhs_type != T_BARE_WORD)) continue;
}
/*
- * xlat expansions should be parseable.
+ * Parse (and throw away) the xlat string.
+ *
+ * FIXME: All of these should be converted from PW_TYPE_XLAT
+ * to PW_TYPE_TMPL.
*/
- value = talloc_strdup(cs, cp->value); /* modified by xlat_tokenize */
- xlat = NULL;
+ if ((variables[i].type & PW_TYPE_XLAT) != 0) {
+ /*
+ * xlat expansions should be parseable.
+ */
+ value = talloc_strdup(cs, cp->value); /* modified by xlat_tokenize */
+ xlat = NULL;
+
+ slen = xlat_tokenize(cs, value, &xlat, &error);
+ if (slen < 0) {
+ char *spaces, *text;
- slen = xlat_tokenize(cs, value, &xlat, &error);
- if (slen < 0) {
- char *spaces, *text;
+ error:
+ fr_canonicalize_error(cs, &spaces, &text, slen, cp->value);
- fr_canonicalize_error(cs, &spaces, &text, slen, cp->value);
+ cf_log_err(&cp->item, "Failed parsing expanded string:");
+ cf_log_err(&cp->item, "%s", text);
+ cf_log_err(&cp->item, "%s^ %s", spaces, error);
- cf_log_err(&cp->item, "Failed parsing expanded string:");
- cf_log_err(&cp->item, "%s", text);
- cf_log_err(&cp->item, "%s^ %s", spaces, error);
+ talloc_free(spaces);
+ talloc_free(text);
+ talloc_free(value);
+ talloc_free(xlat);
+ return -1;
+ }
- talloc_free(spaces);
- talloc_free(text);
talloc_free(value);
talloc_free(xlat);
- return -1;
}
- talloc_free(value);
- talloc_free(xlat);
+ /*
+ * Convert the LITERAL template to the actual
+ * type.
+ */
+ if ((variables[i].type & PW_TYPE_TMPL) != 0) {
+ vp_tmpl_t *vpt;
+
+ slen = tmpl_afrom_str(cs, &vpt, cp->value, talloc_array_length(cp->value) - 1,
+ cp->rhs_type,
+ REQUEST_CURRENT, PAIR_LIST_REQUEST, true);
+ if (slen < 0) {
+ error = fr_strerror();
+ goto error;
+ }
+
+ /*
+ * Sanity check
+ *
+ * Don't add default - update with new types.
+ */
+ switch (vpt->type) {
+ /*
+ * All attributes should have been defined by this point.
+ */
+ case TMPL_TYPE_ATTR_UNDEFINED:
+ cf_log_err(&cp->item, "Unknown attribute '%s'", vpt->tmpl_unknown_name);
+ return -1;
+
+ case TMPL_TYPE_LITERAL:
+ case TMPL_TYPE_ATTR:
+ case TMPL_TYPE_LIST:
+ case TMPL_TYPE_DATA:
+ case TMPL_TYPE_EXEC:
+ case TMPL_TYPE_XLAT:
+ case TMPL_TYPE_XLAT_STRUCT:
+ break;
+
+ case TMPL_TYPE_UNKNOWN:
+ case TMPL_TYPE_REGEX:
+ case TMPL_TYPE_REGEX_STRUCT:
+ case TMPL_TYPE_NULL:
+ rad_assert(0);
+ }
+
+ talloc_free(*(vp_tmpl_t **)data);
+ *(vp_tmpl_t **)data = vpt;
+ }
/*
* If the "multi" flag is set, check all of them.
CONF_SECTION *current)
{
- CONF_SECTION *this, *css, *nextcs;
+ CONF_SECTION *this, *css;
CONF_PAIR *cpn;
char const *ptr;
char const *value;
bool pass2;
char *cbuf = buf;
size_t len;
- fr_cond_t *cond = NULL;
this = current; /* add items here */
*/
for (;;) {
int at_eof;
- nextcs = NULL;
+ css = NULL;
/*
* Get data, and remember if we are at EOF.
goto check_for_more;
}
+ if (t1 != T_BARE_WORD) goto skip_keywords;
+
/*
* Allow for $INCLUDE files
*
char const *error = NULL;
char *p;
CONF_SECTION *server;
+ fr_cond_t *cond = NULL;
/*
* if / elsif MUST be inside of a
}
/*
+ * Can only have "if" in 3 named sections.
+ */
+ server = this->item.parent;
+ while (server &&
+ (strcmp(server->name1, "server") != 0) &&
+ (strcmp(server->name1, "policy") != 0) &&
+ (strcmp(server->name1, "instantiate") != 0)) {
+ server = server->item.parent;
+ if (!server) goto invalid_location;
+ }
+
+ /*
* Skip (...) to find the {
*/
- slen = fr_condition_tokenize(nextcs, cf_section_to_item(nextcs), ptr, &cond,
+ slen = fr_condition_tokenize(this, cf_section_to_item(this), ptr, &cond,
&error, FR_COND_TWO_PASS);
memcpy(&p, &ptr, sizeof(p));
}
} /* else leave it alone */
- server = this->item.parent;
- while ((strcmp(server->name1, "server") != 0) &&
- (strcmp(server->name1, "policy") != 0) &&
- (strcmp(server->name1, "instantiate") != 0)) {
- server = server->item.parent;
- if (!server) goto invalid_location;
- }
-
- nextcs = cf_section_alloc(this, buf1, ptr);
- if (!nextcs) {
+ css = cf_section_alloc(this, buf1, ptr);
+ if (!css) {
ERROR("%s[%d]: Failed allocating memory for section",
filename, *lineno);
return -1;
}
- nextcs->item.filename = talloc_strdup(nextcs, filename);
- nextcs->item.lineno = *lineno;
+ css->item.filename = filename;
+ css->item.lineno = *lineno;
- slen = fr_condition_tokenize(nextcs, cf_section_to_item(nextcs), ptr, &cond,
+ slen = fr_condition_tokenize(css, cf_section_to_item(css), ptr, &cond,
&error, FR_COND_TWO_PASS);
*p = '{'; /* put it back */
if (slen < 0) {
char *spaces, *text;
- fr_canonicalize_error(nextcs, &spaces, &text, slen, ptr);
+ fr_canonicalize_error(this, &spaces, &text, slen, ptr);
ERROR("%s[%d]: Parse error in condition",
filename, *lineno);
talloc_free(spaces);
talloc_free(text);
- talloc_free(nextcs);
+ talloc_free(css);
return -1;
}
if ((size_t) slen >= (sizeof(buf2) - 1)) {
- talloc_free(nextcs);
+ talloc_free(css);
ERROR("%s[%d]: Condition is too large after \"%s\"",
filename, *lineno, buf1);
return -1;
memcpy(buf2, ptr, slen);
buf2[slen] = '\0';
ptr = p;
- t2 = T_BARE_WORD;
if ((t3 = gettoken(&ptr, buf3, sizeof(buf3), true)) != T_LCBRACE) {
- talloc_free(nextcs);
+ talloc_free(css);
ERROR("%s[%d]: Expected '{' %d",
filename, *lineno, t3);
return -1;
* Swap the condition with trailing stuff for
* the final condition.
*/
- memcpy(&p, &nextcs->name2, sizeof(nextcs->name2));
+ memcpy(&p, &css->name2, sizeof(css->name2));
talloc_free(p);
- nextcs->name2 = talloc_typed_strdup(nextcs, buf2);
+ css->name2 = talloc_typed_strdup(css, buf2);
+
+ cf_item_add(this, &(css->item));
+ cf_data_add_internal(css, "if", cond, NULL, false);
- goto section_alloc;
+ /*
+ * The current section is now the child section.
+ */
+ this = css;
+ css = NULL;
+ goto check_for_more;
}
+ skip_keywords:
/*
* Grab the next token.
*/
do_set:
cpn = cf_pair_alloc(this, buf1, value, t2, t1, t3);
if (!cpn) return -1;
- cpn->item.filename = talloc_strdup(cpn, filename);
+ cpn->item.filename = filename;
cpn->item.lineno = *lineno;
cpn->pass2 = pass2;
cf_item_add(this, &(cpn->item));
/* FALL-THROUGH */
case T_LCBRACE:
- section_alloc:
- if (!cond) {
- css = cf_section_alloc(this, buf1,
- t2 == T_LCBRACE ? NULL : buf2);
- if (!css) {
- ERROR("%s[%d]: Failed allocating memory for section",
- filename, *lineno);
- return -1;
- }
-
- css->item.filename = talloc_strdup(css, filename);
- css->item.lineno = *lineno;
- cf_item_add(this, &(css->item));
-
- } else {
- css = nextcs;
- nextcs = NULL;
-
- cf_item_add(this, &(css->item));
- cf_data_add_internal(css, "if", cond, NULL, false);
- cond = NULL; /* eaten by the above line */
+ css = cf_section_alloc(this, buf1,
+ t2 == T_LCBRACE ? NULL : buf2);
+ if (!css) {
+ ERROR("%s[%d]: Failed allocating memory for section",
+ filename, *lineno);
+ return -1;
}
+ css->item.filename = filename;
+ css->item.lineno = *lineno;
+ cf_item_add(this, &(css->item));
+
/*
* There may not be a name2
*/
/*
* Include one config file in another.
*/
-int cf_file_include(CONF_SECTION *cs, char const *filename)
+static int cf_file_include(CONF_SECTION *cs, char const *filename_in)
{
FILE *fp;
int lineno = 0;
- struct stat statbuf;
- time_t *mtime;
- CONF_DATA *cd;
-
- DEBUG2("including configuration file %s", filename);
-
- fp = fopen(filename, "r");
- if (!fp) {
- ERROR("Unable to open file \"%s\": %s",
- filename, fr_syserror(errno));
- return -1;
- }
-
- if (stat(filename, &statbuf) == 0) {
-#ifdef S_IWOTH
- if ((statbuf.st_mode & S_IWOTH) != 0) {
- fclose(fp);
- ERROR("Configuration file %s is globally writable. "
- "Refusing to start due to insecure configuration.", filename);
- return -1;
- }
-#endif
-
-#if 0 && defined(S_IROTH)
- if (statbuf.st_mode & S_IROTH) != 0) {
- fclose(fp);
- ERROR("Configuration file %s is globally readable. "
- "Refusing to start due to insecure configuration", filename);
- return -1;
- }
-#endif
- }
-
- if (cf_data_find_internal(cs, filename, PW_TYPE_FILE_INPUT)) {
- fclose(fp);
- ERROR("Cannot include the same file twice: \"%s\"", filename);
-
- return -1;
- }
+ char const *filename;
/*
- * Add the filename to the section
+ * So we only need to do this once.
*/
- mtime = talloc(cs, time_t);
- *mtime = statbuf.st_mtime;
+ filename = talloc_strdup(cs, filename_in);
- if (cf_data_add_internal(cs, filename, mtime, NULL, PW_TYPE_FILE_INPUT) < 0) {
- fclose(fp);
- ERROR("Internal error opening file \"%s\"",
- filename);
- return -1;
- }
+ DEBUG2("including configuration file %s", filename);
- cd = cf_data_find_internal(cs, filename, PW_TYPE_FILE_INPUT);
- if (!cd) {
- fclose(fp);
- ERROR("Internal error opening file \"%s\"",
- filename);
- return -1;
- }
+ fp = cf_file_open(cs, filename);
+ if (!fp) return -1;
- if (!cs->item.filename) cs->item.filename = talloc_strdup(cs, filename);
+ if (!cs->item.filename) cs->item.filename = filename;
/*
* Read the section. It's OK to have EOF without a
* matching close brace.
*/
- if (cf_section_read(cd->name, &lineno, fp, cs) < 0) {
+ if (cf_section_read(filename, &lineno, fp, cs) < 0) {
fclose(fp);
return -1;
}
{
char *p;
CONF_PAIR *cp;
+ rbtree_t *tree;
cp = cf_pair_alloc(cs, "confdir", filename, T_OP_SET, T_BARE_WORD, T_SINGLE_QUOTED_STRING);
if (!cp) return -1;
p = strrchr(cp->value, FR_DIR_SEP);
if (p) *p = '\0';
- cp->item.filename = "internal";
+ cp->item.filename = "<internal>";
cp->item.lineno = -1;
cf_item_add(cs, &(cp->item));
+ tree = rbtree_create(cs, filename_cmp, NULL, 0);
+ if (!tree) return -1;
+
+ cf_data_add_internal(cs, "filename", tree, NULL, 0);
+
if (cf_file_include(cs, filename) < 0) return -1;
/*
* Now that we've read the file, go back through it and
* expand the variables.
*/
- if (cf_section_pass2(cs) < 0) {
- talloc_free(cs);
- return -1;
- }
+ if (cf_section_pass2(cs) < 0) return -1;
return 0;
}
(pair->rhs_type == T_BACK_QUOTED_STRING))) {
VALUE_PAIR *vp;
- vp = pairmake(pair, NULL, pair->attr, NULL, pair->op);
+ vp = fr_pair_make(pair, NULL, pair->attr, NULL, pair->op);
if (!vp) {
return NULL;
}
- if (pairmark_xlat(vp, pair->value) < 0) {
+ if (fr_pair_mark_xlat(vp, pair->value) < 0) {
talloc_free(vp);
return NULL;
return vp;
}
- return pairmake(pair, NULL, pair->attr, pair->value, pair->op);
+ return fr_pair_make(pair, NULL, pair->attr, pair->value, pair->op);
}
/*
{
CONF_DATA mycd;
CONF_DATA *cd;
+ CONF_ITEM *ci, *it;
void *data;
if (!cs || !name) return NULL;
cd = rbtree_finddata(cs->data_tree, &mycd);
if (!cd) return NULL;
+ ci = cf_data_to_item(cd);
+ if (cs->children == ci) {
+ cs->children = ci->next;
+ if (cs->tail == ci) cs->tail = NULL;
+ } else {
+ for (it = cs->children; it; it = it->next) {
+ if (it->next == ci) {
+ it->next = ci->next;
+ if (cs->tail == ci) cs->tail = it;
+ break;
+ }
+ }
+ }
+
talloc_set_destructor(cd, NULL); /* Disarm the destructor */
rbtree_deletebydata(cs->data_tree, &mycd);
va_list ap;
va_start(ap, fmt);
- if ((debug_flag > 1) && cs) vradlog(L_DBG, fmt, ap);
+ if ((rad_debug_lvl > 1) && cs) vradlog(L_DBG, fmt, ap);
va_end(ap);
}
char buffer[256];
va_start(ap, fmt);
- if (debug_flag > 1 && cs) {
+ if (rad_debug_lvl > 1 && cs) {
vsnprintf(buffer, sizeof(buffer), fmt, ap);
DEBUG("%.*s# %s", cs->depth, parse_spaces, buffer);