diff options
Diffstat (limited to '')
-rw-r--r-- | src/main/conffile.c | 3810 |
1 files changed, 3810 insertions, 0 deletions
diff --git a/src/main/conffile.c b/src/main/conffile.c new file mode 100644 index 0000000..7fe6587 --- /dev/null +++ b/src/main/conffile.c @@ -0,0 +1,3810 @@ +/* + * conffile.c Read the radiusd.conf file. + * + * Yep I should learn to use lex & yacc, or at least + * write a decent parser. I know how to do that, really :) + * miquels@cistron.nl + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + * Copyright 2000 Miquel van Smoorenburg <miquels@cistron.nl> + * Copyright 2000 Alan DeKok <aland@ox.org> + */ + +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/parser.h> +#include <freeradius-devel/rad_assert.h> + +#ifdef HAVE_DIRENT_H +#include <dirent.h> +#endif + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#include <ctype.h> + +bool check_config = false; + +typedef enum conf_property { + CONF_PROPERTY_INVALID = 0, + CONF_PROPERTY_NAME, + CONF_PROPERTY_INSTANCE, +} CONF_PROPERTY; + +static const FR_NAME_NUMBER conf_property_name[] = { + { "name", CONF_PROPERTY_NAME}, + { "instance", CONF_PROPERTY_INSTANCE}, + + { NULL , -1 } +}; + +typedef enum conf_type { + CONF_ITEM_INVALID = 0, + CONF_ITEM_PAIR, + CONF_ITEM_SECTION, + CONF_ITEM_DATA +} CONF_ITEM_TYPE; + +struct conf_item { + struct conf_item *next; //!< Sibling. + struct conf_part *parent; //!< Parent. + int lineno; //!< The line number the config item began on. + char const *filename; //!< The file the config item was parsed from. + CONF_ITEM_TYPE type; //!< Whether the config item is a config_pair, conf_section or conf_data. +}; + +/** Configuration AVP similar to a VALUE_PAIR + * + */ +struct conf_pair { + CONF_ITEM item; + char const *attr; //!< Attribute name + char const *value; //!< Attribute value + FR_TOKEN op; //!< Operator e.g. =, := + FR_TOKEN lhs_type; //!< Name quoting style T_(DOUBLE|SINGLE|BACK)_QUOTE_STRING or T_BARE_WORD. + FR_TOKEN rhs_type; //!< Value Quoting style T_(DOUBLE|SINGLE|BACK)_QUOTE_STRING or T_BARE_WORD. + bool pass2; //!< do expansion in pass2. + bool parsed; //!< Was this item used during parsing? +}; + +/** Internal data that is associated with a configuration section + * + */ +struct conf_data { + CONF_ITEM item; + char const *name; + int flag; + void *data; //!< User data + void (*free)(void *); //!< Free user data function +}; + +struct conf_part { + CONF_ITEM item; + char const *name1; //!< First name token. Given ``foo bar {}`` would be ``foo``. + char const *name2; //!< Second name token. Given ``foo bar {}`` would be ``bar``. + + FR_TOKEN name2_type; //!< The type of quoting around name2. + + CONF_ITEM *children; + CONF_ITEM *tail; //!< For speed. + CONF_SECTION *template; + + rbtree_t *pair_tree; //!< and a partridge.. + rbtree_t *section_tree; //!< no jokes here. + rbtree_t *name2_tree; //!< for sections of the same name2 + rbtree_t *data_tree; + + void *base; + int depth; + + CONF_PARSER const *variables; +}; + +typedef struct cf_file_t { + char const *filename; + CONF_SECTION *cs; + struct stat buf; + bool from_dir; +} cf_file_t; + +CONF_SECTION *root_config = NULL; +bool cf_new_escape = true; + + +static int cf_data_add_internal(CONF_SECTION *cs, char const *name, void *data, + void (*data_free)(void *), int flag); + +static void *cf_data_find_internal(CONF_SECTION const *cs, char const *name, int flag); + +static char const *cf_expand_variables(char const *cf, int *lineno, + CONF_SECTION *outercs, + char *output, size_t outsize, + char const *input, bool *soft_fail); + +static int cf_file_include(CONF_SECTION *cs, char const *filename_in, bool from_dir); + + + +/* + * Isolate the scary casts in these tiny provably-safe functions + */ + +/** Cast a CONF_ITEM to a CONF_PAIR + * + */ +CONF_PAIR *cf_item_to_pair(CONF_ITEM const *ci) +{ + CONF_PAIR *out; + + if (ci == NULL) return NULL; + + rad_assert(ci->type == CONF_ITEM_PAIR); + + memcpy(&out, &ci, sizeof(out)); + return out; +} + +/** Cast a CONF_ITEM to a CONF_SECTION + * + */ +CONF_SECTION *cf_item_to_section(CONF_ITEM const *ci) +{ + CONF_SECTION *out; + + if (ci == NULL) return NULL; + + rad_assert(ci->type == CONF_ITEM_SECTION); + + memcpy(&out, &ci, sizeof(out)); + return out; +} + +/** Cast a CONF_PAIR to a CONF_ITEM + * + */ +CONF_ITEM *cf_pair_to_item(CONF_PAIR const *cp) +{ + CONF_ITEM *out; + + if (cp == NULL) return NULL; + + memcpy(&out, &cp, sizeof(out)); + return out; +} + +/** Cast a CONF_SECTION to a CONF_ITEM + * + */ +CONF_ITEM *cf_section_to_item(CONF_SECTION const *cs) +{ + CONF_ITEM *out; + + if (cs == NULL) return NULL; + + memcpy(&out, &cs, sizeof(out)); + return out; +} + +/** Cast CONF_DATA to a CONF_ITEM + * + */ +static CONF_ITEM *cf_data_to_item(CONF_DATA const *cd) +{ + CONF_ITEM *out; + + if (cd == NULL) { + return NULL; + } + + memcpy(&out, &cd, sizeof(out)); + return out; +} + +static int _cf_data_free(CONF_DATA *cd) +{ + if (cd->free) cd->free(cd->data); + + return 0; +} + +/* + * rbtree callback function + */ +static int pair_cmp(void const *a, void const *b) +{ + CONF_PAIR const *one = a; + CONF_PAIR const *two = b; + + return strcmp(one->attr, two->attr); +} + + +/* + * rbtree callback function + */ +static int section_cmp(void const *a, void const *b) +{ + CONF_SECTION const *one = a; + CONF_SECTION const *two = b; + + return strcmp(one->name1, two->name1); +} + + +/* + * rbtree callback function + */ +static int name2_cmp(void const *a, void const *b) +{ + CONF_SECTION const *one = a; + CONF_SECTION const *two = b; + + rad_assert(strcmp(one->name1, two->name1) == 0); + + if (!one->name2 && !two->name2) return 0; + if (one->name2 && !two->name2) return -1; + if (!one->name2 && two->name2) return +1; + + return strcmp(one->name2, two->name2); +} + + +/* + * rbtree callback function + */ +static int data_cmp(void const *a, void const *b) +{ + int rcode; + + CONF_DATA const *one = a; + CONF_DATA const *two = b; + + rcode = one->flag - two->flag; + if (rcode != 0) return rcode; + + 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 int cf_file_open(CONF_SECTION *cs, char const *filename, bool from_dir, FILE **fp_p) +{ + 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 -1; + + tree = cd->data; + + /* + * If we're including a wildcard directory, then ignore + * any files the users has already explicitly loaded in + * that directory. + */ + if (from_dir) { + cf_file_t my_file; + + my_file.cs = cs; + my_file.filename = filename; + + if (stat(filename, &my_file.buf) < 0) goto error; + + file = rbtree_finddata(tree, &my_file); + if (file && !file->from_dir) return 0; + } + + DEBUG2("including configuration file %s", filename); + + fp = fopen(filename, "r"); + if (!fp) { +error: + ERROR("Unable to open file \"%s\": %s", + filename, fr_syserror(errno)); + return -1; + } + + fd = fileno(fp); + + file = talloc(tree, cf_file_t); + if (!file) { + fclose(fp); + return -1; + } + + 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 -1; + } +#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); + } + + *fp_p = fp; + return 1; +} + +/* + * 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 (file->buf.st_mtime != 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; + } + + /* + * Presume that the file will be immediately + * re-read, so we update the mtime appropriately. + */ + file->buf.st_mtime = buf.st_mtime; + } + + 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) +{ + /* + * Name1 and name2 are allocated contiguous with + * cs. + */ + if (cs->pair_tree) { + rbtree_free(cs->pair_tree); + cs->pair_tree = NULL; + } + if (cs->section_tree) { + rbtree_free(cs->section_tree); + cs->section_tree = NULL; + } + if (cs->name2_tree) { + rbtree_free(cs->name2_tree); + cs->name2_tree = NULL; + } + if (cs->data_tree) { + rbtree_free(cs->data_tree); + cs->data_tree = NULL; + } + + return 0; +} + +/** Allocate a CONF_PAIR + * + * @param parent CONF_SECTION to hang this CONF_PAIR off of. + * @param attr name. + * @param value of CONF_PAIR. + * @param op T_OP_EQ, T_OP_SET etc. + * @param lhs_type T_BARE_WORD, T_DOUBLE_QUOTED_STRING, T_BACK_QUOTED_STRING + * @param rhs_type T_BARE_WORD, T_DOUBLE_QUOTED_STRING, T_BACK_QUOTED_STRING + * @return NULL on error, else a new CONF_SECTION parented by parent. + */ +CONF_PAIR *cf_pair_alloc(CONF_SECTION *parent, char const *attr, char const *value, + FR_TOKEN op, FR_TOKEN lhs_type, FR_TOKEN rhs_type) +{ + CONF_PAIR *cp; + + rad_assert(fr_equality_op[op] || fr_assignment_op[op]); + if (!attr) return NULL; + + cp = talloc_zero(parent, CONF_PAIR); + if (!cp) return NULL; + + cp->item.type = CONF_ITEM_PAIR; + cp->item.parent = parent; + cp->lhs_type = lhs_type; + cp->rhs_type = rhs_type; + cp->op = op; + + cp->attr = talloc_typed_strdup(cp, attr); + if (!cp->attr) { + error: + talloc_free(cp); + return NULL; + } + + if (value) { + cp->value = talloc_typed_strdup(cp, value); + if (!cp->value) goto error; + } + + return cp; +} + +/** Duplicate a CONF_PAIR + * + * @param parent to allocate new pair in. + * @param cp to duplicate. + * @return NULL on error, else a duplicate of the input pair. + */ +CONF_PAIR *cf_pair_dup(CONF_SECTION *parent, CONF_PAIR *cp) +{ + CONF_PAIR *new; + + rad_assert(parent); + rad_assert(cp); + + new = cf_pair_alloc(parent, cp->attr, cf_pair_value(cp), + cp->op, cp->lhs_type, cp->rhs_type); + 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); + } + + return new; +} + +/** Add a configuration pair to a section + * + * @param parent section to add pair to. + * @param cp to add. + */ +void cf_pair_add(CONF_SECTION *parent, CONF_PAIR *cp) +{ + cf_item_add(parent, cf_pair_to_item(cp)); +} + +/** Allocate a CONF_SECTION + * + * @param parent CONF_SECTION to hang this CONF_SECTION off of. + * @param name1 Primary name. + * @param name2 Secondary name. + * @return NULL on error, else a new CONF_SECTION parented by parent. + */ +CONF_SECTION *cf_section_alloc(CONF_SECTION *parent, char const *name1, char const *name2) +{ + CONF_SECTION *cs; + char buffer[1024]; + + if (!name1) return NULL; + + if (name2 && parent) { + if (strchr(name2, '$')) { + name2 = cf_expand_variables(parent->item.filename, + &parent->item.lineno, + parent, + buffer, sizeof(buffer), name2, NULL); + if (!name2) { + ERROR("Failed expanding section name"); + return NULL; + } + } + } + + cs = talloc_zero(parent, CONF_SECTION); + if (!cs) return NULL; + + cs->item.type = CONF_ITEM_SECTION; + cs->item.parent = parent; + + cs->name1 = talloc_typed_strdup(cs, name1); + if (!cs->name1) { + error: + talloc_free(cs); + return NULL; + } + + if (name2) { + cs->name2 = talloc_typed_strdup(cs, name2); + if (!cs->name2) goto error; + } + + cs->pair_tree = rbtree_create(cs, pair_cmp, NULL, 0); + if (!cs->pair_tree) goto error; + + talloc_set_destructor(cs, _cf_section_free); + + /* + * Don't create a data tree, it may not be needed. + */ + + /* + * Don't create the section tree here, it may not + * be needed. + */ + + if (parent) cs->depth = parent->depth + 1; + + return cs; +} + +/** Duplicate a configuration section + * + * @note recursively duplicates any child sections. + * @note does not duplicate any data associated with a section, or its child sections. + * + * @param parent section (may be NULL). + * @param cs to duplicate. + * @param name1 of new section. + * @param name2 of new section. + * @param copy_meta Copy additional meta data for a section (like template, base, depth and variables). + * @return a duplicate of the existing section, or NULL on error. + */ +CONF_SECTION *cf_section_dup(CONF_SECTION *parent, CONF_SECTION const *cs, + char const *name1, char const *name2, bool copy_meta) +{ + CONF_SECTION *new, *subcs; + CONF_PAIR *cp; + CONF_ITEM *ci; + + new = cf_section_alloc(parent, name1, name2); + + if (copy_meta) { + new->template = cs->template; + new->base = cs->base; + new->depth = cs->depth; + new->variables = cs->variables; + } + + new->item.lineno = cs->item.lineno; + + 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) { + case CONF_ITEM_SECTION: + subcs = cf_item_to_section(ci); + subcs = cf_section_dup(new, subcs, + cf_section_name1(subcs), cf_section_name2(subcs), + copy_meta); + if (!subcs) { + talloc_free(new); + return NULL; + } + cf_section_add(new, subcs); + break; + + case CONF_ITEM_PAIR: + cp = cf_pair_dup(new, cf_item_to_pair(ci)); + if (!cp) { + talloc_free(new); + return NULL; + } + cf_pair_add(new, cp); + break; + + case CONF_ITEM_DATA: /* Skip data */ + break; + + case CONF_ITEM_INVALID: + rad_assert(0); + } + } + + return new; +} + +void cf_section_add(CONF_SECTION *parent, CONF_SECTION *cs) +{ + cf_item_add(parent, &(cs->item)); +} + +/** Replace pair in a given section with a new pair, of the given value. + * + * @param cs to replace pair in. + * @param cp to replace. + * @param value New value to assign to cp. + * @return 0 on success, -1 on failure. + */ +int cf_pair_replace(CONF_SECTION *cs, CONF_PAIR *cp, char const *value) +{ + CONF_PAIR *newp; + CONF_ITEM *ci, *cn, **last; + + newp = cf_pair_alloc(cs, cp->attr, value, cp->op, cp->lhs_type, cp->rhs_type); + if (!newp) return -1; + + ci = &(cp->item); + cn = &(newp->item); + + /* + * Find the old one from the linked list, and replace it + * with the new one. + */ + for (last = &cs->children; (*last) != NULL; last = &(*last)->next) { + if (*last == ci) { + cn->next = (*last)->next; + *last = cn; + ci->next = NULL; + break; + } + } + + rbtree_deletebydata(cs->pair_tree, ci); + + rbtree_insert(cs->pair_tree, cn); + + return 0; +} + + +/* + * Add an item to a configuration section. + */ +void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci) +{ +#ifndef NDEBUG + CONF_ITEM *first = ci; +#endif + + rad_assert((void *)cs != (void *)ci); + + if (!cs || !ci) return; + + if (!cs->children) { + rad_assert(cs->tail == NULL); + cs->children = ci; + } else { + rad_assert(cs->tail != NULL); + cs->tail->next = ci; + } + + /* + * Update the trees (and tail) for each item added. + */ + for (/* nothing */; ci != NULL; ci = ci->next) { + rad_assert(ci->next != first); /* simple cycle detection */ + + cs->tail = ci; + + /* + * For fast lookups, pairs and sections get + * added to rbtree's. + */ + switch (ci->type) { + case CONF_ITEM_PAIR: + if (!rbtree_insert(cs->pair_tree, ci)) { + CONF_PAIR *cp = cf_item_to_pair(ci); + + if (strcmp(cp->attr, "confdir") == 0) break; + if (!cp->value) break; /* module name, "ok", etc. */ + } + break; + + case CONF_ITEM_SECTION: { + CONF_SECTION *cs_new = cf_item_to_section(ci); + CONF_SECTION *name1_cs; + + if (!cs->section_tree) { + cs->section_tree = rbtree_create(cs, section_cmp, NULL, 0); + if (!cs->section_tree) { + ERROR("Out of memory"); + fr_exit_now(1); + } + } + + name1_cs = rbtree_finddata(cs->section_tree, cs_new); + if (!name1_cs) { + if (!rbtree_insert(cs->section_tree, cs_new)) { + ERROR("Failed inserting section into tree"); + fr_exit_now(1); + } + break; + } + + /* + * We already have a section of + * this "name1". Add a new + * sub-section based on name2. + */ + if (!name1_cs->name2_tree) { + name1_cs->name2_tree = rbtree_create(name1_cs, name2_cmp, NULL, 0); + if (!name1_cs->name2_tree) { + ERROR("Out of memory"); + fr_exit_now(1); + } + } + + /* + * We don't care if this fails. + * If the user tries to create + * two sections of the same + * name1/name2, the duplicate + * section is just silently + * ignored. + */ + rbtree_insert(name1_cs->name2_tree, cs_new); + break; + } /* was a section */ + + case CONF_ITEM_DATA: + if (!cs->data_tree) { + cs->data_tree = rbtree_create(cs, data_cmp, NULL, 0); + } + if (cs->data_tree) { + rbtree_insert(cs->data_tree, ci); + } + break; + + default: /* FIXME: assert & error! */ + break; + + } /* switch over conf types */ + } /* loop over ci */ +} + + +CONF_ITEM *cf_reference_item(CONF_SECTION const *parentcs, + CONF_SECTION *outercs, + char const *ptr) +{ + CONF_PAIR *cp; + CONF_SECTION *next; + CONF_SECTION const *cs = outercs; + char name[8192]; + char *p; + + if (!cs) goto no_such_item; + + strlcpy(name, ptr, sizeof(name)); + p = name; + + /* + * ".foo" means "foo from the current section" + */ + if (*p == '.') { + p++; + + /* + * Just '.' means the current section + */ + if (*p == '\0') { + return cf_section_to_item(cs); + } + + /* + * ..foo means "foo from the section + * enclosing this section" (etc.) + */ + while (*p == '.') { + if (cs->item.parent) { + cs = cs->item.parent; + } + + /* + * .. means the section + * enclosing this section + */ + if (!*++p) { + return cf_section_to_item(cs); + } + } + + /* + * "foo.bar.baz" means "from the root" + */ + } else if (strchr(p, '.') != NULL) { + if (!parentcs) goto no_such_item; + + cs = parentcs; + } + + while (*p) { + char *q, *r; + + r = strchr(p, '['); + q = strchr(p, '.'); + if (!r && !q) break; + + if (r && q > r) q = NULL; + if (q && q < r) r = NULL; + + /* + * Split off name2. + */ + if (r) { + q = strchr(r + 1, ']'); + if (!q) return NULL; /* parse error */ + + /* + * Points to foo[bar]xx: parse error, + * it should be foo[bar] or foo[bar].baz + */ + if (q[1] && q[1] != '.') goto no_such_item; + + *r = '\0'; + *q = '\0'; + next = cf_section_sub_find_name2(cs, p, r + 1); + *r = '['; + *q = ']'; + + /* + * Points to a named instance of a section. + */ + if (!q[1]) { + if (!next) goto no_such_item; + return &(next->item); + } + + q++; /* ensure we skip the ']' and '.' */ + + } else { + *q = '\0'; + next = cf_section_sub_find(cs, p); + *q = '.'; + } + + if (!next) break; /* it MAY be a pair in this section! */ + + cs = next; + p = q + 1; + } + + if (!*p) goto no_such_item; + + retry: + /* + * Find it in the current referenced + * section. + */ + cp = cf_pair_find(cs, p); + 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); + + /* + * "foo" is "in the current section, OR in main". + */ + if ((p == name) && (parentcs != NULL) && (cs != parentcs)) { + cs = parentcs; + goto retry; + } + +no_such_item: + return NULL; +} + + +CONF_SECTION *cf_top_section(CONF_SECTION *cs) +{ + if (!cs) return NULL; + + while (cs->item.parent != NULL) { + cs = cs->item.parent; + } + + return cs; +} + + +/* + * Expand the variables in an input string. + */ +static char const *cf_expand_variables(char const *cf, int *lineno, + CONF_SECTION *outercs, + char *output, size_t outsize, + char const *input, bool *soft_fail) +{ + char *p; + char const *end, *ptr; + CONF_SECTION const *parentcs; + char name[8192]; + + if (soft_fail) *soft_fail = false; + + /* + * Find the master parent conf section. + * We can't use main_config.config, because we're in the + * process of re-building it, and it isn't set up yet... + */ + parentcs = cf_top_section(outercs); + + p = output; + ptr = input; + while (*ptr) { + /* + * Ignore anything other than "${" + */ + if ((*ptr == '$') && (ptr[1] == '{')) { + CONF_ITEM *ci; + CONF_PAIR *cp; + char *q; + + /* + * FIXME: Add support for ${foo:-bar}, + * like in xlat.c + */ + + /* + * Look for trailing '}', and log a + * warning for anything that doesn't match, + * and exit with a fatal error. + */ + end = strchr(ptr, '}'); + if (end == NULL) { + *p = '\0'; + ERROR("%s[%d]: Variable expansion missing }", + cf, *lineno); + return NULL; + } + + ptr += 2; + + /* + * Can't really happen because input lines are + * capped at 8k, which is sizeof(name) + */ + if ((size_t) (end - ptr) >= sizeof(name)) { + ERROR("%s[%d]: Reference string is too large", + cf, *lineno); + return NULL; + } + + memcpy(name, ptr, end - ptr); + name[end - ptr] = '\0'; + + q = strchr(name, ':'); + if (q) { + *(q++) = '\0'; + } + + ci = cf_reference_item(parentcs, outercs, name); + if (!ci) { + if (soft_fail) *soft_fail = true; + ERROR("%s[%d]: Reference \"${%s}\" not found", cf, *lineno, name); + return NULL; + } + + /* + * The expansion doesn't refer to another item or section + * it's the property of a section. + */ + if (q) { + CONF_SECTION *mycs = cf_item_to_section(ci); + + if (ci->type != CONF_ITEM_SECTION) { + ERROR("%s[%d]: Can only reference properties of sections", cf, *lineno); + return NULL; + } + + switch (fr_str2int(conf_property_name, q, CONF_PROPERTY_INVALID)) { + case CONF_PROPERTY_NAME: + strcpy(p, mycs->name1); + break; + + case CONF_PROPERTY_INSTANCE: + strcpy(p, mycs->name2 ? mycs->name2 : mycs->name1); + break; + + default: + ERROR("%s[%d]: Invalid property '%s'", cf, *lineno, q); + return NULL; + } + p += strlen(p); + ptr = end + 1; + + } else if (ci->type == CONF_ITEM_PAIR) { + /* + * Substitute the value of the variable. + */ + cp = cf_item_to_pair(ci); + + /* + * If the thing we reference is + * marked up as being expanded in + * pass2, don't expand it now. + * Let it be expanded in pass2. + */ + if (cp->pass2) { + if (soft_fail) *soft_fail = true; + + ERROR("%s[%d]: Reference \"%s\" points to a variable which has not been expanded.", + cf, *lineno, input); + return NULL; + } + + /* + * Might as well make + * non-existent string be the + * empty string. + */ + if (!cp->value) { + *p = '\0'; + goto skip_value; + } + + if (p + strlen(cp->value) >= output + outsize) { + ERROR("%s[%d]: Reference \"%s\" is too long", + cf, *lineno, input); + return NULL; + } + + strcpy(p, cp->value); + p += strlen(p); + skip_value: + ptr = end + 1; + + } else if (ci->type == CONF_ITEM_SECTION) { + CONF_SECTION *subcs; + + /* + * Adding an entry again to a + * section is wrong. We don't + * want an infinite loop. + */ + if (ci->parent == outercs) { + ERROR("%s[%d]: Cannot reference different item in same section", cf, *lineno); + return NULL; + } + + /* + * Copy the section instead of + * referencing it. + */ + subcs = cf_item_to_section(ci); + subcs = cf_section_dup(outercs, subcs, + cf_section_name1(subcs), cf_section_name2(subcs), + false); + if (!subcs) { + ERROR("%s[%d]: Failed copying reference %s", cf, *lineno, name); + return NULL; + } + + subcs->item.filename = ci->filename; + subcs->item.lineno = ci->lineno; + cf_item_add(outercs, &(subcs->item)); + + ptr = end + 1; + + } else { + ERROR("%s[%d]: Reference \"%s\" type is invalid", cf, *lineno, input); + return NULL; + } + } else if (strncmp(ptr, "$ENV{", 5) == 0) { + char *env; + + ptr += 5; + + /* + * Look for trailing '}', and log a + * warning for anything that doesn't match, + * and exit with a fatal error. + */ + end = strchr(ptr, '}'); + if (end == NULL) { + *p = '\0'; + ERROR("%s[%d]: Environment variable expansion missing }", + cf, *lineno); + return NULL; + } + + /* + * Can't really happen because input lines are + * capped at 8k, which is sizeof(name) + */ + if ((size_t) (end - ptr) >= sizeof(name)) { + ERROR("%s[%d]: Environment variable name is too large", + cf, *lineno); + return NULL; + } + + memcpy(name, ptr, end - ptr); + name[end - ptr] = '\0'; + + /* + * Get the environment variable. + * If none exists, then make it an empty string. + */ + env = getenv(name); + if (env == NULL) { + *name = '\0'; + env = name; + } + + if (p + strlen(env) >= output + outsize) { + ERROR("%s[%d]: Reference \"%s\" is too long", + cf, *lineno, input); + return NULL; + } + + strcpy(p, env); + p += strlen(p); + ptr = end + 1; + + } else { + /* + * Copy it over verbatim. + */ + *(p++) = *(ptr++); + } + + + if (p >= (output + outsize)) { + ERROR("%s[%d]: Reference \"%s\" is too long", + cf, *lineno, input); + return NULL; + } + } /* loop over all of the input string. */ + + *p = '\0'; + + return output; +} + +static char const parse_spaces[] = " "; + +/** Validation function for ipaddr conffile types + * + */ +static inline int fr_item_validate_ipaddr(CONF_SECTION *cs, char const *name, PW_TYPE type, char const *value, + fr_ipaddr_t *ipaddr) +{ + char ipbuf[128]; + + if (strcmp(value, "*") == 0) { + cf_log_info(cs, "%.*s\t%s = *", cs->depth, parse_spaces, name); + } else if (strspn(value, ".0123456789abdefABCDEF:%[]/") == strlen(value)) { + cf_log_info(cs, "%.*s\t%s = %s", cs->depth, parse_spaces, name, value); + } else { + cf_log_info(cs, "%.*s\t%s = %s IPv%s address [%s]", cs->depth, parse_spaces, name, value, + (ipaddr->af == AF_INET ? "4" : " 6"), ip_ntoh(ipaddr, ipbuf, sizeof(ipbuf))); + } + + switch (type) { + case PW_TYPE_IPV4_ADDR: + case PW_TYPE_IPV6_ADDR: + case PW_TYPE_COMBO_IP_ADDR: + switch (ipaddr->af) { + case AF_INET: + if (ipaddr->prefix == 32) return 0; + + cf_log_err(&(cs->item), "Invalid IPv4 mask length \"/%i\". Only \"/32\" permitted for non-prefix types", + ipaddr->prefix); + break; + + case AF_INET6: + if (ipaddr->prefix == 128) return 0; + + cf_log_err(&(cs->item), "Invalid IPv6 mask length \"/%i\". Only \"/128\" permitted for non-prefix types", + ipaddr->prefix); + break; + + + default: + cf_log_err(&(cs->item), "Unknown address (%d) family passed for parsing IP address.", ipaddr->af); + break; + } + + return -1; + + default: + break; + } + + return 0; +} + +/** Parses a #CONF_PAIR into a C data type, with a default value. + * + * Takes fields from a #CONF_PARSER struct and uses them to parse the string value + * of a #CONF_PAIR into a C data type matching the type argument. + * + * The format of the types are the same as #value_data_t types. + * + * @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 + * 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_SHORT - @copybrief PW_TYPE_SHORT + * - ``data`` #PW_TYPE_INTEGER64 - @copybrief PW_TYPE_INTEGER64 + * - ``data`` #PW_TYPE_SIGNED - @copybrief PW_TYPE_SIGNED + * - ``data`` #PW_TYPE_STRING - @copybrief PW_TYPE_STRING + * - ``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). + * - ``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_DEPRECATED - @copybrief PW_TYPE_DEPRECATED + * - ``flag`` #PW_TYPE_REQUIRED - @copybrief PW_TYPE_REQUIRED + * - ``flag`` #PW_TYPE_ATTRIBUTE - @copybrief PW_TYPE_ATTRIBUTE + * - ``flag`` #PW_TYPE_SECRET - @copybrief PW_TYPE_SECRET + * - ``flag`` #PW_TYPE_FILE_INPUT - @copybrief PW_TYPE_FILE_INPUT + * - ``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 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, 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) { + cf_log_err(&(cs->item), "No enclosing section for configuration item \"%s\"", name); + 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); + 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); + + /* + * No pairs match the configuration item name in the current + * 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) { + 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: + 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; + } + + + /* + * 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) { + vp_tmpl_t *vpt; + + if (!value) { + *(vp_tmpl_t **)data = NULL; + return 0; + } + + 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, 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 { + *(bool *)data = false; + cf_log_err(&(cs->item), "Invalid value \"%s\" for boolean " + "variable %s", value, name); + return -1; + } + cf_log_info(cs, "%.*s\t%s = %s", + cs->depth, parse_spaces, name, value); + break; + + case PW_TYPE_INTEGER: + { + unsigned long v = strtoul(value, 0, 0); + + /* + * Restrict integer values to 0-INT32_MAX, this means + * it will always be safe to cast them to a signed type + * for comparisons, and imposes the same range limit as + * before we switched to using an unsigned type to + * represent config item integers. + */ + if (v > INT32_MAX) { + cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", value, + name, INT32_MAX); + return -1; + } + + *(uint32_t *)data = v; + cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, name, *(uint32_t *)data); + } + 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 (v > UINT16_MAX) { + cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", value, + name, UINT16_MAX); + return -1; + } + *(uint16_t *)data = (uint16_t) v; + cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, name, *(uint16_t *)data); + } + break; + + case PW_TYPE_INTEGER64: + *(uint64_t *)data = strtoull(value, 0, 0); + cf_log_info(cs, "%.*s\t%s = %" PRIu64, cs->depth, parse_spaces, name, *(uint64_t *)data); + break; + + case PW_TYPE_SIGNED: + *(int32_t *)data = strtol(value, 0, 0); + cf_log_info(cs, "%.*s\t%s = %d", cs->depth, parse_spaces, name, *(int32_t *)data); + break; + + case PW_TYPE_STRING: + q = (char **) data; + if (*q != NULL) { + talloc_free(*q); + } + + /* + * Expand variables which haven't already been + * expanded automagically when the configuration + * file was read. + */ + if (value == dflt) { + int lineno = 0; + + lineno = cs->item.lineno; + + value = cf_expand_variables("<internal>", + &lineno, + cs, buffer, sizeof(buffer), + value, NULL); + if (!value) { + cf_log_err(&(cs->item),"Failed expanding variable %s", name); + return -1; + } + } + + if (cant_be_empty && (value[0] == '\0')) goto cant_be_empty; + + if (attribute) { + if (!dict_attrbyname(value)) { + if (!cp) { + cf_log_err(&(cs->item), "No such attribute '%s' for configuration '%s'", + value, name); + } else { + cf_log_err(&(cp->item), "No such attribute '%s'", value); + } + return -1; + } + } + + /* + * Hide secrets when using "radiusd -X". + */ + if (secret && (rad_debug_lvl <= 2)) { + cf_log_info(cs, "%.*s\t%s = <<< secret >>>", + cs->depth, parse_spaces, name); + } else { + cf_log_info(cs, "%.*s\t%s = \"%s\"", + cs->depth, parse_spaces, name, value ? value : "(null)"); + } + *q = value ? talloc_typed_strdup(cs, value) : NULL; + + /* + * If there's data AND it's an input file, check + * that we can read it. This check allows errors + * to be caught as early as possible, during + * server startup. + */ + if (*q && file_input && !cf_file_check(cs, *q, true)) { + cf_log_err(&(cs->item), "Failed parsing configuration item \"%s\"", name); + return -1; + } + + if (*q && file_exists && !cf_file_check(cs, *q, false)) { + cf_log_err(&(cs->item), "Failed parsing configuration item \"%s\"", name); + return -1; + } + break; + + case PW_TYPE_IPV4_ADDR: + case PW_TYPE_IPV4_PREFIX: + ipaddr = data; + + if (fr_pton4(ipaddr, value, -1, true, false) < 0) { + failed: + cf_log_err(&(cs->item), "Failed parsing configuration item \"%s\" - %s", name, fr_strerror()); + return -1; + } + if (fr_item_validate_ipaddr(cs, name, type, value, ipaddr) < 0) return -1; + break; + + case PW_TYPE_IPV6_ADDR: + case PW_TYPE_IPV6_PREFIX: + ipaddr = data; + + if (fr_pton6(ipaddr, value, -1, true, false) < 0) goto failed; + if (fr_item_validate_ipaddr(cs, name, type, value, ipaddr) < 0) return -1; + break; + + case PW_TYPE_COMBO_IP_ADDR: + case PW_TYPE_COMBO_IP_PREFIX: + ipaddr = data; + + if (fr_pton(ipaddr, value, -1, AF_UNSPEC, true) < 0) goto failed; + if (fr_item_validate_ipaddr(cs, name, type, value, ipaddr) < 0) return -1; + break; + + case PW_TYPE_TIMEVAL: { + int sec; + char *end; + struct timeval tv; + + sec = strtoul(value, &end, 10); + tv.tv_sec = sec; + tv.tv_usec = 0; + if (*end == '.') { + size_t len; + + len = strlen(end + 1); + + if (len > 6) { + cf_log_err(&(cs->item), "Too much precision for timeval"); + return -1; + } + + /* + * If they write "0.1", that means + * "10000" microseconds. + */ + sec = strtoul(end + 1, NULL, 10); + while (len < 6) { + sec *= 10; + len++; + } + + tv.tv_usec = sec; + } + cf_log_info(cs, "%.*s\t%s = %d.%06d", + cs->depth, parse_spaces, name, (int) tv.tv_sec, (int) tv.tv_usec); + memcpy(data, &tv, sizeof(tv)); + } + break; + + default: + /* + * If we get here, it's a sanity check error. + * It's not an error parsing the configuration + * file. + */ + rad_assert(type > PW_TYPE_INVALID); + rad_assert(type < PW_TYPE_MAX); + + cf_log_err(&(cs->item), "type '%s' is not supported in the configuration files", + fr_int2str(dict_attr_types, type, "?Unknown?")); + return -1; + } /* switch over variable type */ + + if (!cp) { + CONF_PAIR *cpn; + + cpn = cf_pair_alloc(cs, name, value, T_OP_SET, T_BARE_WORD, T_BARE_WORD); + if (!cpn) return -1; + cpn->parsed = true; + cpn->item.filename = "<internal>"; + cpn->item.lineno = 0; + cf_item_add(cs, &(cpn->item)); + } + + return rcode; +} + + +/* + * A copy of cf_section_parse that initializes pointers before + * parsing them. + */ +static void cf_section_parse_init(CONF_SECTION *cs, void *base, + CONF_PARSER const *variables) +{ + int i; + + for (i = 0; variables[i].name != NULL; i++) { + if (variables[i].type == PW_TYPE_SUBSECTION) { + CONF_SECTION *subcs; + + if (!variables[i].dflt) continue; + + subcs = cf_section_sub_find(cs, variables[i].name); + + /* + * If there's no subsection in the + * config, BUT the CONF_PARSER wants one, + * then create an empty one. This is so + * that we can track the strings, + * etc. allocated in the subsection. + */ + if (!subcs) { + subcs = cf_section_alloc(cs, variables[i].name, NULL); + if (!subcs) return; + + subcs->item.filename = cs->item.filename; + subcs->item.lineno = cs->item.lineno; + cf_item_add(cs, &(subcs->item)); + } + + cf_section_parse_init(subcs, (uint8_t *)base + variables[i].offset, + (CONF_PARSER const *) variables[i].dflt); + continue; + } + + if ((variables[i].type != PW_TYPE_STRING) && + (variables[i].type != PW_TYPE_FILE_INPUT) && + (variables[i].type != PW_TYPE_FILE_OUTPUT)) { + continue; + } + + if (variables[i].data) { + *(char **) variables[i].data = NULL; + } else if (base) { + *(char **) (((char *)base) + variables[i].offset) = NULL; + } else { + continue; + } + } /* for all variables in the configuration section */ +} + + +static void cf_section_parse_warn(CONF_SECTION *cs) +{ + CONF_ITEM *ci; + + for (ci = cs->children; ci; ci = ci->next) { + /* + * 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) continue; + if (ci->type == CONF_ITEM_PAIR) { + CONF_PAIR *cp; + + cp = cf_item_to_pair(ci); + if (cp->parsed) continue; + + WARN("%s[%d]: The item '%s' is defined, but is unused by the configuration", + cp->item.filename ? cp->item.filename : "unknown", + cp->item.lineno ? cp->item.lineno : 0, + cp->attr); + } + + /* + * Skip everything else. + */ + } +} + +/** 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 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); + } else { + cf_log_info(cs, "%.*s%s %s {", cs->depth, parse_spaces, cs->name1, cs->name2); + } + + cf_section_parse_init(cs, base, variables); + + /* + * Handle the known configuration parameters. + */ + for (i = 0; variables[i].name != NULL; i++) { + /* + * Handle subsections specially + */ + if (variables[i].type == PW_TYPE_SUBSECTION) { + CONF_SECTION *subcs; + + subcs = cf_section_sub_find(cs, variables[i].name); + /* + * Default in this case is overloaded to mean a pointer + * to the CONF_PARSER struct for the subsection. + */ + if (!variables[i].dflt || !subcs) { + ERROR("Internal sanity check 1 failed in cf_section_parse %s", variables[i].name); + ret = -1; + goto finish; + } + + 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 */ + + if (variables[i].data) { + data = variables[i].data; /* prefer this. */ + } else if (base) { + data = ((char *)base) + variables[i].offset; + } else { + 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); + 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 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 (rad_debug_lvl >= 3) cf_section_parse_warn(cs); + + cs->base = base; + + cf_log_info(cs, "%.*s}", cs->depth, parse_spaces); + +finish: + return ret; +} + + +/* + * Check XLAT things in pass 2. But don't cache the xlat stuff anywhere. + */ +int cf_section_parse_pass2(CONF_SECTION *cs, void *base, CONF_PARSER const *variables) +{ + int i; + ssize_t slen; + char const *error; + char *value = NULL; + xlat_exp_t *xlat; + + /* + * Handle the known configuration parameters. + */ + for (i = 0; variables[i].name != NULL; i++) { + CONF_PAIR *cp; + void *data; + + /* + * Handle subsections specially + */ + if (variables[i].type == PW_TYPE_SUBSECTION) { + CONF_SECTION *subcs; + subcs = cf_section_sub_find(cs, variables[i].name); + + if (cf_section_parse_pass2(subcs, (uint8_t *)base + variables[i].offset, + (CONF_PARSER const *) variables[i].dflt) < 0) { + return -1; + } + 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 || !data) continue; + + if ((cp->rhs_type != T_DOUBLE_QUOTED_STRING) && + (cp->rhs_type != T_BARE_WORD)) continue; + + /* + * Non-xlat expansions shouldn't have xlat! + */ + if (((variables[i].type & PW_TYPE_XLAT) == 0) && + ((variables[i].type & PW_TYPE_TMPL) == 0)) { + /* + * Ignore %{... in shared secrets. + * They're never dynamically expanded. + */ + if ((variables[i].type & PW_TYPE_SECRET) != 0) continue; + + if (strstr(cp->value, "%{") != NULL) { + WARN("%s[%d]: Found dynamic expansion in string which will not be dynamically expanded", + cp->item.filename ? cp->item.filename : "unknown", + cp->item.lineno ? cp->item.lineno : 0); + } + continue; + } + + /* + * Parse (and throw away) the xlat string. + * + * FIXME: All of these should be converted from PW_TYPE_XLAT + * to PW_TYPE_TMPL. + */ + 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; + + error: + 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); + + 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. + */ + if ((variables[i].type & PW_TYPE_MULTI) != 0) { + cp = cf_pair_find_next(cs, cp, cp->attr); + goto redo; + } + } /* for all variables in the configuration section */ + + return 0; +} + +/* + * Merge the template so everyting else "just works". + */ +static bool cf_template_merge(CONF_SECTION *cs, CONF_SECTION const *template) +{ + CONF_ITEM *ci; + + if (!cs || !template) return true; + + cs->template = NULL; + + /* + * Walk over the template, adding its' entries to the + * current section. But only if the entries don't + * already exist in the current section. + */ + for (ci = template->children; ci; ci = ci->next) { + if (ci->type == CONF_ITEM_PAIR) { + CONF_PAIR *cp1, *cp2; + + /* + * It exists, don't over-write it. + */ + cp1 = cf_item_to_pair(ci); + if (cf_pair_find(cs, cp1->attr)) { + continue; + } + + /* + * Create a new pair with all of the data + * of the old one. + */ + cp2 = cf_pair_dup(cs, cp1); + if (!cp2) return false; + + cp2->item.filename = cp1->item.filename; + cp2->item.lineno = cp1->item.lineno; + + cf_item_add(cs, &(cp2->item)); + continue; + } + + if (ci->type == CONF_ITEM_SECTION) { + CONF_SECTION *subcs1, *subcs2; + + subcs1 = cf_item_to_section(ci); + rad_assert(subcs1 != NULL); + + subcs2 = cf_section_sub_find_name2(cs, subcs1->name1, subcs1->name2); + if (subcs2) { + /* + * sub-sections get merged. + */ + if (!cf_template_merge(subcs2, subcs1)) { + return false; + } + continue; + } + + /* + * Our section doesn't have a matching + * sub-section. Copy it verbatim from + * the template. + */ + subcs2 = cf_section_dup(cs, subcs1, + cf_section_name1(subcs1), cf_section_name2(subcs1), + false); + if (!subcs2) return false; + + subcs2->item.filename = subcs1->item.filename; + subcs2->item.lineno = subcs1->item.lineno; + + cf_item_add(cs, &(subcs2->item)); + continue; + } + + /* ignore everything else */ + } + + return true; +} + +static char const *cf_local_file(char const *base, char const *filename, + char *buffer, size_t bufsize) +{ + size_t dirsize; + char *p; + + strlcpy(buffer, base, bufsize); + + p = strrchr(buffer, FR_DIR_SEP); + if (!p) return filename; + if (p[1]) { /* ./foo */ + p[1] = '\0'; + } + + dirsize = (p - buffer) + 1; + + if ((dirsize + strlen(filename)) >= bufsize) { + return NULL; + } + + strlcpy(p + 1, filename, bufsize - dirsize); + + return buffer; +} + + +/* + * Read a part of the config file. + */ +static int cf_section_read(char const *filename, int *lineno, FILE *fp, + CONF_SECTION *current) + +{ + CONF_SECTION *this, *css; + CONF_PAIR *cpn; + char const *ptr; + char const *value; + char buf[8192]; + char buf1[8192]; + char buf2[8192]; + char buf3[8192]; + char buf4[8192]; + FR_TOKEN t1 = T_INVALID, t2, t3; + bool has_spaces = false; + bool pass2; + char *cbuf = buf; + size_t len; + + this = current; /* add items here */ + + /* + * Read, checking for line continuations ('\\' at EOL) + */ + for (;;) { + int at_eof; + css = NULL; + + /* + * Get data, and remember if we are at EOF. + */ + at_eof = (fgets(cbuf, sizeof(buf) - (cbuf - buf), fp) == NULL); + (*lineno)++; + + /* + * We read the entire 8k worth of data: complain. + * Note that we don't care if the last character + * is \n: it's still forbidden. This means that + * the maximum allowed length of text is 8k-1, which + * should be plenty. + */ + len = strlen(cbuf); + if ((cbuf + len + 1) >= (buf + sizeof(buf))) { + ERROR("%s[%d]: Line too long", + filename, *lineno); + return -1; + } + + if (has_spaces) { + ptr = cbuf; + while (isspace((int) *ptr)) ptr++; + + if (ptr > cbuf) { + memmove(cbuf, ptr, len - (ptr - cbuf)); + len -= (ptr - cbuf); + } + } + + /* + * Not doing continuations: check for edge + * conditions. + */ + if (cbuf == buf) { + if (at_eof) break; + + ptr = buf; + while (*ptr && isspace((int) *ptr)) ptr++; + + if (!*ptr || (*ptr == '#')) continue; + + } else if (at_eof || (len == 0)) { + ERROR("%s[%d]: Continuation at EOF is illegal", + filename, *lineno); + return -1; + } + + /* + * See if there's a continuation. + */ + while ((len > 0) && + ((cbuf[len - 1] == '\n') || (cbuf[len - 1] == '\r'))) { + len--; + cbuf[len] = '\0'; + } + + if ((len > 0) && (cbuf[len - 1] == '\\')) { + /* + * Check for "suppress spaces" magic. + */ + if (!has_spaces && (len > 2) && (cbuf[len - 2] == '"')) { + has_spaces = true; + } + + cbuf[len - 1] = '\0'; + cbuf += len - 1; + continue; + } + + ptr = cbuf = buf; + has_spaces = false; + + get_more: + pass2 = false; + + /* + * The parser is getting to be evil. + */ + while ((*ptr == ' ') || (*ptr == '\t')) ptr++; + + if (((ptr[0] == '%') && (ptr[1] == '{')) || + (ptr[0] == '`')) { + int hack; + + if (ptr[0] == '%') { + hack = rad_copy_variable(buf1, ptr); + } else { + hack = rad_copy_string(buf1, ptr); + } + if (hack < 0) { + ERROR("%s[%d]: Invalid expansion: %s", + filename, *lineno, ptr); + return -1; + } + + ptr += hack; + + t2 = gettoken(&ptr, buf2, sizeof(buf2), true); + switch (t2) { + case T_EOL: + case T_HASH: + goto do_bare_word; + + default: + ERROR("%s[%d]: Invalid expansion: %s", + filename, *lineno, ptr); + return -1; + } + } else { + t1 = gettoken(&ptr, buf1, sizeof(buf1), true); + } + + /* + * The caller eats "name1 name2 {", and calls us + * for the data inside of the section. So if we + * receive a closing brace, then it must mean the + * end of the section. + */ + if (t1 == T_RCBRACE) { + if (this == current) { + ERROR("%s[%d]: Too many closing braces", + filename, *lineno); + return -1; + } + + /* + * Merge the template into the existing + * section. This uses more memory, but + * means that templates now work with + * sub-sections, etc. + */ + if (!cf_template_merge(this, this->template)) { + return -1; + } + + this = this->item.parent; + goto check_for_more; + } + + if (t1 != T_BARE_WORD) goto skip_keywords; + + /* + * Allow for $INCLUDE files + * + * This *SHOULD* work for any level include. + * I really really really hate this file. -cparker + */ + if ((strcasecmp(buf1, "$INCLUDE") == 0) || + (strcasecmp(buf1, "$-INCLUDE") == 0)) { + bool relative = true; + + t2 = getword(&ptr, buf2, sizeof(buf2), true); + if (t2 != T_EOL) { + ERROR("%s[%d]: Unexpected text after $INCLUDE", + filename, *lineno); + return -1; + } + + if (buf2[0] == '$') relative = false; + + value = cf_expand_variables(filename, lineno, this, buf4, sizeof(buf4), buf2, NULL); + if (!value) return -1; + + if (!FR_DIR_IS_RELATIVE(value)) relative = false; + + if (relative) { + value = cf_local_file(filename, value, buf3, + sizeof(buf3)); + if (!value) { + ERROR("%s[%d]: Directories too deep.", + filename, *lineno); + return -1; + } + } + + +#ifdef HAVE_DIRENT_H + /* + * $INCLUDE foo/ + * + * Include ALL non-"dot" files in the directory. + * careful! + */ + if (value[strlen(value) - 1] == '/') { + DIR *dir; + struct dirent *dp; + struct stat stat_buf; + + DEBUG2("including files in directory %s", value ); +#ifdef S_IWOTH + /* + * Security checks. + */ + if (stat(value, &stat_buf) < 0) { + ERROR("%s[%d]: Failed reading directory %s: %s", + filename, *lineno, + value, fr_syserror(errno)); + return -1; + } + + if ((stat_buf.st_mode & S_IWOTH) != 0) { + ERROR("%s[%d]: Directory %s is globally writable. Refusing to start due to " + "insecure configuration", filename, *lineno, value); + return -1; + } +#endif + dir = opendir(value); + if (!dir) { + ERROR("%s[%d]: Error reading directory %s: %s", + filename, *lineno, value, + fr_syserror(errno)); + return -1; + } + + /* + * Read the directory, ignoring "." files. + */ + while ((dp = readdir(dir)) != NULL) { + char const *p; + int slen; + + if (dp->d_name[0] == '.') continue; + + /* + * Check for valid characters + */ + for (p = dp->d_name; *p != '\0'; p++) { + if (isalpha((int)*p) || + isdigit((int)*p) || + (*p == '-') || + (*p == '_') || + (*p == '.')) continue; + break; + } + if (*p != '\0') continue; + + slen = snprintf(buf2, sizeof(buf2), "%s%s", + value, dp->d_name); + if (slen >= (int) sizeof(buf2) || slen < 0) { + ERROR("%s: Full file path is too long.", dp->d_name); + return -1; + } + if ((stat(buf2, &stat_buf) != 0) || + S_ISDIR(stat_buf.st_mode)) continue; + + /* + * Read the file into the current + * configuration section. + */ + if (cf_file_include(this, buf2, true) < 0) { + closedir(dir); + return -1; + } + } + closedir(dir); + } else +#endif + { /* it was a normal file */ + if (buf1[1] == '-') { + struct stat statbuf; + + if (stat(value, &statbuf) < 0) { + WARN("Not including file %s: %s", value, fr_syserror(errno)); + continue; + } + } + + if (cf_file_include(this, value, false) < 0) { + return -1; + } + } + continue; + } /* we were in an include */ + + if (strcasecmp(buf1, "$template") == 0) { + CONF_ITEM *ci; + CONF_SECTION *parentcs, *templatecs; + t2 = getword(&ptr, buf2, sizeof(buf2), true); + + if (t2 != T_EOL) { + ERROR("%s[%d]: Unexpected text after $TEMPLATE", filename, *lineno); + return -1; + } + + parentcs = cf_top_section(current); + + templatecs = cf_section_sub_find(parentcs, "templates"); + if (!templatecs) { + ERROR("%s[%d]: No \"templates\" section for reference \"%s\"", filename, *lineno, buf2); + return -1; + } + + ci = cf_reference_item(parentcs, templatecs, buf2); + if (!ci || (ci->type != CONF_ITEM_SECTION)) { + ERROR("%s[%d]: Reference \"%s\" not found", filename, *lineno, buf2); + return -1; + } + + if (!this) { + ERROR("%s[%d]: Internal sanity check error in template reference", filename, *lineno); + return -1; + } + + if (this->template) { + ERROR("%s[%d]: Section already has a template", filename, *lineno); + return -1; + } + + this->template = cf_item_to_section(ci); + continue; + } + + /* + * Ensure that the user can't add CONF_PAIRs + * with 'internal' names; + */ + if (buf1[0] == '_') { + ERROR("%s[%d]: Illegal configuration pair name \"%s\"", filename, *lineno, buf1); + return -1; + } + + /* + * Handle if/elsif specially. + */ + if ((strcmp(buf1, "if") == 0) || (strcmp(buf1, "elsif") == 0)) { + ssize_t slen; + char const *error = NULL; + char *p; + CONF_SECTION *server; + fr_cond_t *cond = NULL; + + /* + * if / elsif MUST be inside of a + * processing section, which MUST in turn + * be inside of a "server" directive. + */ + if (!this->item.parent) { + invalid_location: + ERROR("%s[%d]: Invalid location for '%s'", + filename, *lineno, buf1); + return -1; + } + + /* + * 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(this, cf_section_to_item(this), ptr, &cond, + &error, FR_COND_TWO_PASS); + memcpy(&p, &ptr, sizeof(p)); + + if (slen < 0) { + if (p[-slen] != '{') goto cond_error; + slen = -slen; + } + TALLOC_FREE(cond); + + /* + * This hack is so that the NEXT stage + * doesn't go "too far" in expanding the + * variable. We can parse the conditions + * without expanding the ${...} stuff. + * BUT we don't want to expand all of the + * stuff AFTER the condition. So we do + * two passes. + * + * The first pass is to discover the end + * of the condition. We then expand THAT + * string, and do a second pass parsing + * the expanded condition. + */ + p += slen; + *p = '\0'; + + /* + * If there's a ${...}. If so, expand it. + */ + if (strchr(ptr, '$') != NULL) { + ptr = cf_expand_variables(filename, lineno, + this, + buf3, sizeof(buf3), + ptr, NULL); + if (!ptr) { + ERROR("%s[%d]: Parse error expanding ${...} in condition", + filename, *lineno); + return -1; + } + } /* else leave it alone */ + + css = cf_section_alloc(this, buf1, ptr); + if (!css) { + ERROR("%s[%d]: Failed allocating memory for section", + filename, *lineno); + return -1; + } + css->item.filename = filename; + css->item.lineno = *lineno; + + slen = fr_condition_tokenize(css, cf_section_to_item(css), ptr, &cond, + &error, FR_COND_TWO_PASS); + *p = '{'; /* put it back */ + + cond_error: + if (slen < 0) { + char *spaces, *text; + + fr_canonicalize_error(this, &spaces, &text, slen, ptr); + + ERROR("%s[%d]: Parse error in condition", + filename, *lineno); + ERROR("%s[%d]: %s", filename, *lineno, text); + ERROR("%s[%d]: %s^ %s", filename, *lineno, spaces, error); + + talloc_free(spaces); + talloc_free(text); + talloc_free(css); + return -1; + } + + if ((size_t) slen >= (sizeof(buf2) - 1)) { + talloc_free(css); + ERROR("%s[%d]: Condition is too large after \"%s\"", + filename, *lineno, buf1); + return -1; + } + + /* + * Copy the expanded and parsed condition + * into buf2. Then, parse the text after + * the condition, which now MUST be a '{. + * + * If it wasn't '{' it would have been + * caught in the first pass of + * conditional parsing, above. + */ + memcpy(buf2, ptr, slen); + buf2[slen] = '\0'; + ptr = p; + + if ((t3 = gettoken(&ptr, buf3, sizeof(buf3), true)) != T_LCBRACE) { + 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, &css->name2, sizeof(css->name2)); + talloc_free(p); + css->name2 = talloc_typed_strdup(css, buf2); + + cf_item_add(this, &(css->item)); + cf_data_add_internal(css, "if", cond, NULL, false); + + /* + * The current section is now the child section. + */ + this = css; + css = NULL; + goto check_for_more; + } + + skip_keywords: + /* + * Grab the next token. + */ + t2 = gettoken(&ptr, buf2, sizeof(buf2), !cf_new_escape); + switch (t2) { + case T_EOL: + case T_HASH: + case T_COMMA: + do_bare_word: + t3 = t2; + t2 = T_OP_EQ; + value = NULL; + goto do_set; + + case T_OP_INCRM: + case T_OP_ADD: + case T_OP_CMP_EQ: + case T_OP_SUB: + case T_OP_LE: + case T_OP_GE: + case T_OP_CMP_FALSE: + if (!this || (strcmp(this->name1, "update") != 0)) { + ERROR("%s[%d]: Invalid operator in assignment", + filename, *lineno); + return -1; + } + /* FALL-THROUGH */ + + case T_OP_EQ: + case T_OP_SET: + case T_OP_PREPEND: + while (isspace((int) *ptr)) ptr++; + + /* + * Be a little more forgiving. + */ + if (*ptr == '#') { + t3 = T_HASH; + } else + + /* + * New parser: non-quoted strings are + * bare words, and we parse everything + * until the next newline, or the next + * comma. If they have { or } in a bare + * word, well... too bad. + */ + if (cf_new_escape && (*ptr != '"') && (*ptr != '\'') + && (*ptr != '`') && (*ptr != '/')) { + const char *q = ptr; + + t3 = T_BARE_WORD; + while (*q && (*q >= ' ') && (*q != ',') && + !isspace(*q)) q++; + + if ((size_t) (q - ptr) >= sizeof(buf3)) { + ERROR("%s[%d]: Parse error: value too long", + filename, *lineno); + return -1; + } + + memcpy(buf3, ptr, (q - ptr)); + buf3[q - ptr] = '\0'; + ptr = q; + + } else { + t3 = getstring(&ptr, buf3, sizeof(buf3), !cf_new_escape); + } + + if (t3 == T_INVALID) { + ERROR("%s[%d]: Parse error: %s", + filename, *lineno, + fr_strerror()); + return -1; + } + + /* + * Allow "foo" by itself, or "foo = bar" + */ + switch (t3) { + bool soft_fail; + + case T_BARE_WORD: + case T_DOUBLE_QUOTED_STRING: + case T_BACK_QUOTED_STRING: + value = cf_expand_variables(filename, lineno, this, buf4, sizeof(buf4), buf3, &soft_fail); + if (!value) { + if (!soft_fail) return -1; + + /* + * References an item which doesn't exist, + * or which is already marked up as being + * expanded in pass2. Wait for pass2 to + * do the expansions. + */ + pass2 = true; + value = buf3; + } + break; + + case T_EOL: + case T_HASH: + value = NULL; + break; + + default: + value = buf3; + break; + } + + /* + * Add this CONF_PAIR to our CONF_SECTION + */ + do_set: + cpn = cf_pair_alloc(this, buf1, value, t2, t1, t3); + if (!cpn) return -1; + cpn->item.filename = filename; + cpn->item.lineno = *lineno; + cpn->pass2 = pass2; + cf_item_add(this, &(cpn->item)); + + /* + * Require a comma, unless there's a comment. + */ + while (isspace(*ptr)) ptr++; + + if (*ptr == ',') { + ptr++; + break; + } + + /* + * module # stuff! + * foo = bar # other stuff + */ + if ((t3 == T_HASH) || (t3 == T_COMMA) || (t3 == T_EOL) || (*ptr == '#')) continue; + + if (!*ptr || (*ptr == '}')) break; + + ERROR("%s[%d]: Syntax error: Expected comma after '%s': %s", + filename, *lineno, value, ptr); + return -1; + + /* + * No '=', must be a section or sub-section. + */ + case T_BARE_WORD: + case T_DOUBLE_QUOTED_STRING: + case T_SINGLE_QUOTED_STRING: + t3 = gettoken(&ptr, buf3, sizeof(buf3), true); + if (t3 != T_LCBRACE) { + ERROR("%s[%d]: Expecting section start brace '{' after \"%s %s\"", + filename, *lineno, buf1, buf2); + return -1; + } + /* FALL-THROUGH */ + + case T_LCBRACE: + 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 + */ + css->name2_type = (t2 == T_LCBRACE) ? T_INVALID : t2; + + /* + * The current section is now the child section. + */ + this = css; + break; + + case T_INVALID: + ERROR("%s[%d]: Syntax error in '%s': %s", filename, *lineno, ptr, fr_strerror()); + + return -1; + + default: + ERROR("%s[%d]: Parse error after \"%s\": unexpected token \"%s\"", + filename, *lineno, buf1, fr_int2str(fr_tokens, t2, "<INVALID>")); + + return -1; + } + + check_for_more: + /* + * Done parsing one thing. Skip to EOL if possible. + */ + while (isspace(*ptr)) ptr++; + + if (*ptr == '#') continue; + + if (*ptr) { + goto get_more; + } + + } + + /* + * See if EOF was unexpected .. + */ + if (feof(fp) && (this != current)) { + ERROR("%s[%d]: EOF reached without closing brace for section %s starting at line %d", + filename, *lineno, cf_section_name1(this), cf_section_lineno(this)); + return -1; + } + + return 0; +} + +/* + * Include one config file in another. + */ +static int cf_file_include(CONF_SECTION *cs, char const *filename_in, bool from_dir) +{ + FILE *fp; + int rcode; + int lineno = 0; + char const *filename; + + /* + * So we only need to do this once. + */ + filename = talloc_strdup(cs, filename_in); + + /* + * This may return "0" if we already loaded the file. + */ + rcode = cf_file_open(cs, filename, from_dir, &fp); + if (rcode <= 0) return rcode; + + 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(filename, &lineno, fp, cs) < 0) { + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + + +/* + * Do variable expansion in pass2. + * + * This is a breadth-first expansion. "deep + */ +static int cf_section_pass2(CONF_SECTION *cs) +{ + CONF_ITEM *ci; + + for (ci = cs->children; ci; ci = ci->next) { + char const *value; + CONF_PAIR *cp; + char buffer[8192]; + + if (ci->type != CONF_ITEM_PAIR) continue; + + cp = cf_item_to_pair(ci); + if (!cp->value || !cp->pass2) continue; + + rad_assert((cp->rhs_type == T_BARE_WORD) || + (cp->rhs_type == T_DOUBLE_QUOTED_STRING) || + (cp->rhs_type == T_BACK_QUOTED_STRING)); + + value = cf_expand_variables(ci->filename, &ci->lineno, cs, buffer, sizeof(buffer), cp->value, NULL); + if (!value) return -1; + + rad_const_free(cp->value); + cp->value = talloc_typed_strdup(cp, value); + } + + for (ci = cs->children; ci; ci = ci->next) { + if (ci->type != CONF_ITEM_SECTION) continue; + + if (cf_section_pass2(cf_item_to_section(ci)) < 0) return -1; + } + + return 0; +} + + +/* + * Bootstrap a config file. + */ +int cf_file_read(CONF_SECTION *cs, char const *filename) +{ + 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.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, false) < 0) return -1; + + /* + * Now that we've read the file, go back through it and + * expand the variables. + */ + if (cf_section_pass2(cs) < 0) return -1; + + return 0; +} + + +void cf_file_free(CONF_SECTION *cs) +{ + talloc_free(cs); +} + + +/* + * Return a CONF_PAIR within a CONF_SECTION. + */ +CONF_PAIR *cf_pair_find(CONF_SECTION const *cs, char const *name) +{ + CONF_PAIR *cp, mycp; + + if (!cs || !name) return NULL; + + mycp.attr = name; + cp = rbtree_finddata(cs->pair_tree, &mycp); + if (cp) return cp; + + if (!cs->template) return NULL; + + return rbtree_finddata(cs->template->pair_tree, &mycp); +} + +/* + * Return the attr of a CONF_PAIR + */ + +char const *cf_pair_attr(CONF_PAIR const *pair) +{ + return (pair ? pair->attr : NULL); +} + +/* + * Return the value of a CONF_PAIR + */ + +char const *cf_pair_value(CONF_PAIR const *pair) +{ + return (pair ? pair->value : NULL); +} + +FR_TOKEN cf_pair_operator(CONF_PAIR const *pair) +{ + return (pair ? pair->op : T_INVALID); +} + +/** Return the value (lhs) type + * + * @param pair to extract value type from. + * @return one of T_BARE_WORD, T_SINGLE_QUOTED_STRING, T_BACK_QUOTED_STRING + * T_DOUBLE_QUOTED_STRING or T_INVALID if the pair is NULL. + */ +FR_TOKEN cf_pair_attr_type(CONF_PAIR const *pair) +{ + return (pair ? pair->lhs_type : T_INVALID); +} + +/** Return the value (rhs) type + * + * @param pair to extract value type from. + * @return one of T_BARE_WORD, T_SINGLE_QUOTED_STRING, T_BACK_QUOTED_STRING + * T_DOUBLE_QUOTED_STRING or T_INVALID if the pair is NULL. + */ +FR_TOKEN cf_pair_value_type(CONF_PAIR const *pair) +{ + return (pair ? pair->rhs_type : T_INVALID); +} + +/* + * Turn a CONF_PAIR into a VALUE_PAIR + * For now, ignore the "value_type" field... + */ +VALUE_PAIR *cf_pairtovp(CONF_PAIR *pair) +{ + if (!pair) { + fr_strerror_printf("Internal error"); + return NULL; + } + + if (!pair->value) { + fr_strerror_printf("No value given for attribute %s", pair->attr); + return NULL; + } + + /* + * false comparisons never match. BUT if it's a "string" + * or `string`, then remember to expand it later. + */ + if ((pair->op != T_OP_CMP_FALSE) && + ((pair->rhs_type == T_DOUBLE_QUOTED_STRING) || + (pair->rhs_type == T_BACK_QUOTED_STRING))) { + VALUE_PAIR *vp; + + vp = fr_pair_make(pair, NULL, pair->attr, NULL, pair->op); + if (!vp) { + return NULL; + } + + if (fr_pair_mark_xlat(vp, pair->value) < 0) { + talloc_free(vp); + + return NULL; + } + + return vp; + } + + return fr_pair_make(pair, NULL, pair->attr, pair->value, pair->op); +} + +/* + * Return the first label of a CONF_SECTION + */ + +char const *cf_section_name1(CONF_SECTION const *cs) +{ + return (cs ? cs->name1 : NULL); +} + +/* + * Return the second label of a CONF_SECTION + */ + +char const *cf_section_name2(CONF_SECTION const *cs) +{ + return (cs ? cs->name2 : NULL); +} + +/** Return name2 if set, else name1 + * + */ +char const *cf_section_name(CONF_SECTION const *cs) +{ + char const *name; + + name = cf_section_name2(cs); + if (name) return name; + + return cf_section_name1(cs); +} + +/* + * Find a value in a CONF_SECTION + */ +char const *cf_section_value_find(CONF_SECTION const *cs, char const *attr) +{ + CONF_PAIR *cp; + + cp = cf_pair_find(cs, attr); + + return (cp ? cp->value : NULL); +} + + +CONF_SECTION *cf_section_find_name2(CONF_SECTION const *cs, + char const *name1, char const *name2) +{ + char const *their2; + CONF_ITEM const *ci; + + if (!cs || !name1) return NULL; + + for (ci = &(cs->item); ci; ci = ci->next) { + if (ci->type != CONF_ITEM_SECTION) + continue; + + if (strcmp(cf_item_to_section(ci)->name1, name1) != 0) { + continue; + } + + their2 = cf_item_to_section(ci)->name2; + + if ((!name2 && !their2) || + (name2 && their2 && (strcmp(name2, their2) == 0))) { + return cf_item_to_section(ci); + } + } + + return NULL; +} + +/** Find a pair with a name matching attr, after specified pair. + * + * @param cs to search in. + * @param pair to search from (may be NULL). + * @param attr to find (may be NULL in which case any attribute matches). + * @return the next matching CONF_PAIR or NULL if none matched. + */ +CONF_PAIR *cf_pair_find_next(CONF_SECTION const *cs, + CONF_PAIR const *pair, char const *attr) +{ + CONF_ITEM *ci; + + if (!cs) return NULL; + + /* + * If pair is NULL and we're trying to find a specific + * attribute this must be a first time run. + * + * Find the pair with correct name. + */ + if (!pair && attr) return cf_pair_find(cs, attr); + + /* + * Start searching from the next child, or from the head + * of the list of children (if no pair was provided). + */ + for (ci = pair ? pair->item.next : cs->children; + ci; + ci = ci->next) { + if (ci->type != CONF_ITEM_PAIR) continue; + + if (!attr || strcmp(cf_item_to_pair(ci)->attr, attr) == 0) break; + } + + return cf_item_to_pair(ci); +} + +/* + * Find a CONF_SECTION, or return the root if name is NULL + */ + +CONF_SECTION *cf_section_find(char const *name) +{ + if (name) + return cf_section_sub_find(root_config, name); + else + return root_config; +} + +/** Find a sub-section in a section + * + * This finds ANY section having the same first name. + * The second name is ignored. + */ +CONF_SECTION *cf_section_sub_find(CONF_SECTION const *cs, char const *name) +{ + CONF_SECTION mycs; + + if (!cs || !name) return NULL; /* can't find an un-named section */ + + /* + * No sub-sections have been defined, so none exist. + */ + if (!cs->section_tree) return NULL; + + mycs.name1 = name; + mycs.name2 = NULL; + return rbtree_finddata(cs->section_tree, &mycs); +} + + +/** Find a CONF_SECTION with both names. + * + */ +CONF_SECTION *cf_section_sub_find_name2(CONF_SECTION const *cs, + char const *name1, char const *name2) +{ + CONF_ITEM *ci; + + if (!cs) cs = root_config; + if (!cs) return NULL; + + if (name1) { + CONF_SECTION mycs, *master_cs; + + if (!cs->section_tree) return NULL; + + mycs.name1 = name1; + mycs.name2 = name2; + + master_cs = rbtree_finddata(cs->section_tree, &mycs); + if (!master_cs) return NULL; + + /* + * Look it up in the name2 tree. If it's there, + * return it. + */ + if (master_cs->name2_tree) { + CONF_SECTION *subcs; + + subcs = rbtree_finddata(master_cs->name2_tree, &mycs); + if (subcs) return subcs; + } + + /* + * We don't insert ourselves into the name2 tree. + * So if there's nothing in the name2 tree, maybe + * *we* are the answer. + */ + if (!master_cs->name2 && name2) return NULL; + if (master_cs->name2 && !name2) return NULL; + if (!master_cs->name2 && !name2) return master_cs; + + if (strcmp(master_cs->name2, name2) == 0) { + return master_cs; + } + + return NULL; + } + + /* + * Else do it the old-fashioned way. + */ + for (ci = cs->children; ci; ci = ci->next) { + CONF_SECTION *subcs; + + if (ci->type != CONF_ITEM_SECTION) + continue; + + subcs = cf_item_to_section(ci); + if (!subcs->name2) { + if (strcmp(subcs->name1, name2) == 0) break; + } else { + if (strcmp(subcs->name2, name2) == 0) break; + } + } + + return cf_item_to_section(ci); +} + +/* + * Return the next subsection after a CONF_SECTION + * with a certain name1 (char *name1). If the requested + * name1 is NULL, any name1 matches. + */ + +CONF_SECTION *cf_subsection_find_next(CONF_SECTION const *section, + CONF_SECTION const *subsection, + char const *name1) +{ + CONF_ITEM *ci; + + if (!section) return NULL; + + /* + * If subsection is NULL this must be a first time run + * Find the subsection with correct name + */ + + if (!subsection) { + ci = section->children; + } else { + ci = subsection->item.next; + } + + for (; ci; ci = ci->next) { + if (ci->type != CONF_ITEM_SECTION) + continue; + if ((name1 == NULL) || + (strcmp(cf_item_to_section(ci)->name1, name1) == 0)) + break; + } + + return cf_item_to_section(ci); +} + + +/* + * Return the next section after a CONF_SECTION + * with a certain name1 (char *name1). If the requested + * name1 is NULL, any name1 matches. + */ + +CONF_SECTION *cf_section_find_next(CONF_SECTION const *section, + CONF_SECTION const *subsection, + char const *name1) +{ + if (!section) return NULL; + + if (!section->item.parent) return NULL; + + return cf_subsection_find_next(section->item.parent, subsection, name1); +} + +/** Return the next item after a CONF_ITEM. + * + */ +CONF_ITEM *cf_item_find_next(CONF_SECTION const *section, CONF_ITEM const *item) +{ + if (!section) return NULL; + + /* + * If item is NULL this must be a first time run + * Return the first item + */ + if (item == NULL) { + return section->children; + } else { + return item->next; + } +} + +static void _pair_count(int *count, CONF_SECTION const *cs) +{ + CONF_ITEM const *ci; + + for (ci = cf_item_find_next(cs, NULL); + ci != NULL; + ci = cf_item_find_next(cs, ci)) { + + if (cf_item_is_section(ci)) { + _pair_count(count, cf_item_to_section(ci)); + continue; + } + + (*count)++; + } +} + +/** Count the number of conf pairs beneath a section + * + * @param[in] cs to search for items in. + * @return number of pairs nested within section. + */ +int cf_pair_count(CONF_SECTION const *cs) +{ + int count = 0; + + _pair_count(&count, cs); + + return count; +} + +CONF_SECTION *cf_item_parent(CONF_ITEM const *ci) +{ + if (!ci) return NULL; + + return ci->parent; +} + +int cf_section_lineno(CONF_SECTION const *section) +{ + return section->item.lineno; +} + +char const *cf_pair_filename(CONF_PAIR const *pair) +{ + return pair->item.filename; +} + +char const *cf_section_filename(CONF_SECTION const *section) +{ + return section->item.filename; +} + +int cf_pair_lineno(CONF_PAIR const *pair) +{ + return pair->item.lineno; +} + +bool cf_item_is_section(CONF_ITEM const *item) +{ + return item->type == CONF_ITEM_SECTION; +} + +bool cf_item_is_pair(CONF_ITEM const *item) +{ + return item->type == CONF_ITEM_PAIR; +} + +bool cf_item_is_data(CONF_ITEM const *item) +{ + return item->type == CONF_ITEM_DATA; +} + +static CONF_DATA *cf_data_alloc(CONF_SECTION *parent, char const *name, + void *data, void (*data_free)(void *)) +{ + CONF_DATA *cd; + + cd = talloc_zero(parent, CONF_DATA); + if (!cd) return NULL; + + cd->item.type = CONF_ITEM_DATA; + cd->item.parent = parent; + cd->name = talloc_typed_strdup(cd, name); + if (!cd->name) { + talloc_free(cd); + return NULL; + } + + cd->data = data; + cd->free = data_free; + + if (cd->free) { + talloc_set_destructor(cd, _cf_data_free); + } + + return cd; +} + +static void *cf_data_find_internal(CONF_SECTION const *cs, char const *name, int flag) +{ + if (!cs || !name) return NULL; + + /* + * Find the name in the tree, for speed. + */ + if (cs->data_tree) { + CONF_DATA mycd; + + mycd.name = name; + mycd.flag = flag; + return rbtree_finddata(cs->data_tree, &mycd); + } + + return NULL; +} + +/* + * Find data from a particular section. + */ +void *cf_data_find(CONF_SECTION const *cs, char const *name) +{ + CONF_DATA *cd = cf_data_find_internal(cs, name, 0); + + if (cd) return cd->data; + return NULL; +} + + +/* + * Add named data to a configuration section. + */ +static int cf_data_add_internal(CONF_SECTION *cs, char const *name, + void *data, void (*data_free)(void *), + int flag) +{ + CONF_DATA *cd; + + if (!cs || !name) return -1; + + /* + * Already exists. Can't add it. + */ + if (cf_data_find_internal(cs, name, flag) != NULL) return -1; + + cd = cf_data_alloc(cs, name, data, data_free); + if (!cd) return -1; + cd->flag = flag; + + cf_item_add(cs, cf_data_to_item(cd)); + + return 0; +} + +/* + * Add named data to a configuration section. + */ +int cf_data_add(CONF_SECTION *cs, char const *name, + void *data, void (*data_free)(void *)) +{ + return cf_data_add_internal(cs, name, data, data_free, 0); +} + +/** Remove named data from a configuration section + * + */ +void *cf_data_remove(CONF_SECTION *cs, char const *name) +{ + CONF_DATA mycd; + CONF_DATA *cd; + CONF_ITEM *ci, *it; + void *data; + + if (!cs || !name) return NULL; + if (!cs->data_tree) return NULL; + + /* + * Find the name in the tree, for speed. + */ + mycd.name = name; + mycd.flag = 0; + 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); + + data = cd->data; + talloc_free(cd); + + return data; +} + +/* + * This is here to make the rest of the code easier to read. It + * ties conffile.c to log.c, but it means we don't have to + * pollute every other function with the knowledge of the + * configuration internals. + */ +void cf_log_err(CONF_ITEM const *ci, char const *fmt, ...) +{ + va_list ap; + char buffer[256]; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + if (ci) { + ERROR("%s[%d]: %s", + ci->filename ? ci->filename : "unknown", + ci->lineno ? ci->lineno : 0, + buffer); + } else { + ERROR("<unknown>[*]: %s", buffer); + } +} + +void cf_log_err_cs(CONF_SECTION const *cs, char const *fmt, ...) +{ + va_list ap; + char buffer[256]; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + rad_assert(cs != NULL); + + ERROR("%s[%d]: %s", + cs->item.filename ? cs->item.filename : "unknown", + cs->item.lineno ? cs->item.lineno : 0, + buffer); +} + +void cf_log_err_cp(CONF_PAIR const *cp, char const *fmt, ...) +{ + va_list ap; + char buffer[256]; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + rad_assert(cp != NULL); + + ERROR("%s[%d]: %s", + cp->item.filename ? cp->item.filename : "unknown", + cp->item.lineno ? cp->item.lineno : 0, + buffer); +} + +void cf_log_info(CONF_SECTION const *cs, char const *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if ((rad_debug_lvl > 1) && cs) vradlog(L_DBG, fmt, ap); + va_end(ap); +} + +/* + * Wrapper to simplify the code. + */ +void cf_log_module(CONF_SECTION const *cs, char const *fmt, ...) +{ + va_list ap; + char buffer[256]; + + va_start(ap, fmt); + if (rad_debug_lvl > 1 && cs) { + vsnprintf(buffer, sizeof(buffer), fmt, ap); + + DEBUG("%.*s# %s", cs->depth, parse_spaces, buffer); + } + va_end(ap); +} + +const CONF_PARSER *cf_section_parse_table(CONF_SECTION *cs) +{ + if (!cs) return NULL; + + return cs->variables; +} + +/* + * For "switch" and "case" statements. + */ +FR_TOKEN cf_section_name2_type(CONF_SECTION const *cs) +{ + if (!cs) return T_INVALID; + + return cs->name2_type; +} |