]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Split the horror into three
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 13 Jun 2017 16:30:49 +0000 (12:30 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 13 Jun 2017 16:30:49 +0000 (12:30 -0400)
18 files changed:
src/include/cf_file.h [new file with mode: 0644]
src/include/cf_parse.h [moved from src/include/conf_file.h with 71% similarity]
src/include/cf_util.h [new file with mode: 0644]
src/include/dl.h
src/include/interpreter.h
src/include/map.h
src/include/map_proc.h
src/include/modules.h
src/include/radiusd.h
src/include/tls-h
src/include/xlat.h
src/lib/io/application.h
src/main/cf_file.c [new file with mode: 0644]
src/main/cf_parse.c [new file with mode: 0644]
src/main/cf_priv.h [new file with mode: 0644]
src/main/cf_util.c [new file with mode: 0644]
src/main/conf_file.c [deleted file]
src/main/libfreeradius-server.mk

diff --git a/src/include/cf_file.h b/src/include/cf_file.h
new file mode 100644 (file)
index 0000000..39f9581
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ *  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
+ */
+#ifndef _FR_CF_FILE_H
+#define _FR_CF_FILE_H
+/**
+ * $Id$
+ *
+ * @file include/conf_file.h
+ * @brief Parse on-disk text based config files into the FreeRADIUS internal format.
+ *
+ * @copyright 2017  The FreeRADIUS server project
+ */
+RCSIDH(cf_file_h, "$Id$")
+
+#include <stddef.h>
+#include <stdint.h>
+#include <freeradius-devel/token.h>
+#include <freeradius-devel/rbtree.h>
+#include <freeradius-devel/cf_util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CF_FILE_NONE   (0)
+#define CF_FILE_ERROR  (1)
+#define CF_FILE_CONFIG (1 << 2)
+#define CF_FILE_MODULE (1 << 3)
+
+/*
+ *     Config file parsing
+ */
+int            cf_file_read(CONF_SECTION *cs, char const *file);
+void           cf_file_free(CONF_SECTION *cs);
+
+bool           cf_file_check(CONF_SECTION *cs, char const *filename, bool check_perms);
+void           cf_file_check_user(uid_t uid, gid_t gid);
+int            cf_file_changed(CONF_SECTION *cs, rb_walker_t callback);
+
+/*
+ *     Config file writing
+ */
+#ifdef WITH_CONF_WRITE
+size_t         cf_section_write(FILE *fp, CONF_SECTION *cs, int depth);
+#endif
+
+/*
+ *     Misc
+ */
+CONF_ITEM      *cf_reference_item(CONF_SECTION const *parentcs, CONF_SECTION const *outercs, char const *ptr);
+char const     *cf_expand_variables(char const *cf, int *lineno,
+                                    CONF_SECTION *outer_cs,
+                                    char *output, size_t outsize,
+                                    char const *input, bool *soft_fail);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FR_CF_FILE */
similarity index 71%
rename from src/include/conf_file.h
rename to src/include/cf_parse.h
index 31e830713920b8ecfeee51f74e780722a248fbd6..6d48a54e20d08472793a0ce0bb0238524e1833a8 100644 (file)
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
-#ifndef _FR_CONFFILE_H
-#define _FR_CONFFILE_H
+#ifndef _FR_CF_PARSE_H
+#define _FR_CF_PARSE_H
 /**
  * $Id$
  *
- * @file include/conf_file.h
- * @brief API to parse FreeRADIUS configuration file format, and convert string
- *     values to native C datatypes.
+ * @file include/cf_parse.h
+ * @brief API to parse internal format configuration items into native C types.
  *
  * @copyright 2015  The FreeRADIUS server project
  */
-RCSIDH(conf_file_h, "$Id$")
+RCSIDH(cf_parse_h, "$Id$")
 
 #include <stddef.h>
 #include <stdint.h>
@@ -33,21 +32,11 @@ RCSIDH(conf_file_h, "$Id$")
 #include <sys/time.h>
 #include <freeradius-devel/token.h>
 #include <freeradius-devel/rbtree.h>
+#include <freeradius-devel/cf_util.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
-
-/*
- * Export the minimum amount of information about these structs
- */
-typedef struct cf_item CONF_ITEM;      //!< Generic configuration element, extended to become
-                                       ///< a #CONF_PAIR, a #CONF_SECTION or #CONF_DATA.
-typedef struct cf_section CONF_SECTION;        //!< #CONF_ITEM used to group multiple #CONF_PAIR and #CONF_SECTION, together.
-typedef struct cf_pair CONF_PAIR;      //!< #CONF_ITEM with an attribute, an operator and a value.
-typedef struct cf_data CONF_DATA;      //!< #CONF_ITEM used to associate arbitrary data
-                                       ///< with a #CONF_PAIR or #CONF_SECTION.
-
 /*
  * Dumb hack for GCC which explodes with lots of errors masking the real
  * error cause, if we don't use typdefs for these structures.
@@ -347,8 +336,6 @@ do {\
        }\
 } while (0)
 
-#define FR_TIMEVAL_TO_MS(_x) (((_x)->tv_usec / 1000) + ((_x)->tv_sec * (uint64_t)1000))
-#define FR_TIMESPEC_TO_MS(_x) (((_x)->tv_usec / 1000000) + ((_x)->tv_sec * (uint64_t)1000))
 extern bool check_config;
 
 /** Defines a #CONF_PAIR to C data type mapping
@@ -431,46 +418,6 @@ typedef struct CONF_PARSER {
 #define CF_FILE_CONFIG (1 << 2)
 #define CF_FILE_MODULE (1 << 3)
 
-/** Auto cast from the input type to CONF_ITEM (which is the base type)
- *
- * Automatically casts:
- * - #CONF_SECTION
- * - #CONF_PAIR
- * - #CONF_DATA
- *
- * To a #CONF_ITEM, whilst performing talloc type checks.
- */
-#define CF_TO_ITEM(_cf) \
-_Generic((_cf), \
-       CONF_SECTION *: cf_section_to_item((CONF_SECTION const *)_cf), \
-       CONF_SECTION const *: cf_section_to_item((CONF_SECTION const *)_cf), \
-       CONF_PAIR *: cf_pair_to_item((CONF_PAIR const *)_cf), \
-       CONF_PAIR const *: cf_pair_to_item((CONF_PAIR const *)_cf), \
-       CONF_DATA *: cf_data_to_item((CONF_DATA const *)_cf), \
-       CONF_DATA const *: cf_data_to_item((CONF_DATA const *)_cf), \
-       default: _cf \
-)
-
-typedef int (*cf_walker_t)(void *data, void *ctx);
-
-extern char const *CF_IDENT_ANY;
-
-/*
- *     Config file parsing
- */
-int            cf_file_read(CONF_SECTION *cs, char const *file);
-void           cf_file_free(CONF_SECTION *cs);
-
-void           cf_file_check_user(uid_t uid, gid_t gid);
-int            cf_file_changed(CONF_SECTION *cs, rb_walker_t callback);
-
-/*
- *     Config file writing
- */
-#ifdef WITH_CONF_WRITE
-size_t         cf_section_write(FILE *fp, CONF_SECTION *cs, int depth);
-#endif
-
 /*
  *     Type validation and conversion
  */
@@ -479,128 +426,8 @@ int               cf_pair_parse(TALLOC_CTX *ctx, CONF_SECTION *cs, char const *name,
 int            cf_section_parse(TALLOC_CTX *ctx, void *base, CONF_SECTION *cs, CONF_PARSER const *variables);
 int            cf_section_parse_pass2(void *base, CONF_SECTION *cs, CONF_PARSER const *variables);
 CONF_PARSER const      *cf_section_parse_table(CONF_SECTION *cs);
-
-CONF_ITEM              *cf_reference_item(CONF_SECTION const *parentcs, CONF_SECTION const *outercs, char const *ptr);
-
-/*
- *     Generic functions that apply to all types of #CONF_ITEM
- */
-#define                cf_item_add(_parent, _child) _cf_item_add(CF_TO_ITEM(_parent), _child)
-void           _cf_item_add(CONF_ITEM *parent, CONF_ITEM *child);
-
-#define                cf_item_next(_ci, _prev) _cf_item_next(CF_TO_ITEM(_ci), _prev)
-CONF_ITEM      *_cf_item_next(CONF_ITEM const *ci, CONF_ITEM const *prev);
-
-#define                cf_root(_cf) _cf_root(CF_TO_ITEM(_cf))
-CONF_SECTION   *_cf_root(CONF_ITEM const *ci);
-
-#define                cf_parent(_cf) _cf_parent(CF_TO_ITEM(_cf))
-CONF_ITEM      *_cf_parent(CONF_ITEM const *ci);
-
-#define                cf_lineno(_cf) _cf_lineno(CF_TO_ITEM(_cf))
-int            _cf_lineno(CONF_ITEM const *ci);
-
-#define                cf_filename(_cf) _cf_filename(CF_TO_ITEM(_cf))
-char const     *_cf_filename(CONF_ITEM const *ci);
-
-bool           cf_item_is_section(CONF_ITEM const *ci);
-bool           cf_item_is_pair(CONF_ITEM const *ci);
-bool           cf_item_is_data(CONF_ITEM const *ci);
-
-CONF_PAIR      *cf_item_to_pair(CONF_ITEM const *ci);
-CONF_SECTION   *cf_item_to_section(CONF_ITEM const *ci);
-CONF_DATA      *cf_item_to_data(CONF_ITEM const *ci);
-
-CONF_ITEM      *cf_pair_to_item(CONF_PAIR const *cp);
-CONF_ITEM      *cf_section_to_item(CONF_SECTION const *cs);
-CONF_ITEM      *cf_data_to_item(CONF_DATA const *cs);
-
-/*
- *     Section manipulation and searching
- */
-CONF_SECTION   *cf_section_alloc(CONF_SECTION *parent, char const *name1, char const *name2);
-CONF_SECTION   *cf_section_dup(CONF_SECTION *parent, CONF_SECTION const *cs,
-                               char const *name1, char const *name2, bool copy_meta);
-void           cf_section_add(CONF_SECTION *parent, CONF_SECTION *cs);
-CONF_SECTION   *cf_section_next(CONF_SECTION const *cs, CONF_SECTION const *prev);
-CONF_SECTION   *cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2);
-CONF_SECTION   *cf_section_find_next(CONF_SECTION const *cs, CONF_SECTION const *subcs,
-                                     char const *name1, char const *name2);
-
-char const     *cf_section_value_find(CONF_SECTION const *, char const *attr);
-
-char const     *cf_section_name1(CONF_SECTION const *cs);
-char const     *cf_section_name2(CONF_SECTION const *cs);
-char const     *cf_section_name(CONF_SECTION const *cs);
-char const     *cf_section_argv(CONF_SECTION const *cs, int argc);
-FR_TOKEN       cf_section_name2_quote(CONF_SECTION const *cs);
-FR_TOKEN       cf_section_argv_quote(CONF_SECTION const *cs, int argc);
-
-/*
- *     Pair manipulation and searching
- */
-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      *cf_pair_dup(CONF_SECTION *parent, CONF_PAIR *cp);
-int            cf_pair_replace(CONF_SECTION *cs, CONF_PAIR *cp, char const *value);
-void           cf_pair_add(CONF_SECTION *parent, CONF_PAIR *cp);
-CONF_PAIR      *cf_pair_next(CONF_SECTION const *cs, CONF_PAIR const *prev);
-CONF_PAIR      *cf_pair_find(CONF_SECTION const *cs, char const *name);
-CONF_PAIR      *cf_pair_find_next(CONF_SECTION const *cs, CONF_PAIR const *prev, char const *name);
-int            cf_pair_count(CONF_SECTION const *cs);
-
-char const     *cf_pair_attr(CONF_PAIR const *pair);
-char const     *cf_pair_value(CONF_PAIR const *pair);
-FR_TOKEN       cf_pair_operator(CONF_PAIR const *pair);
-
-FR_TOKEN       cf_pair_attr_quote(CONF_PAIR const *pair);
-FR_TOKEN       cf_pair_value_quote(CONF_PAIR const *pair);
-
-/*
- *     Data manipulation and searching
- */
-#define                cf_data_find(_cf, _type, _name) _cf_data_find(CF_TO_ITEM(_cf), #_type, _name)
-CONF_DATA const        *_cf_data_find(CONF_ITEM const *ci, char const *type, char const *name);
-
-#define                cf_data_find_next(_cf, _prev, _type, _name) _cf_data_find(CF_TO_ITEM(_cf), CF_TO_ITEM(_prev), #_type, _name)
-CONF_DATA const        *_cf_data_find_next(CONF_ITEM const *ci, CONF_ITEM const *prev, char const *type, char const *name);
-
-void           *cf_data_value(CONF_DATA const *cd);
-
-#define                cf_data_add(_cf, _data, _name, _free) _cf_data_add(CF_TO_ITEM(_cf), _data, _name, _free)
-CONF_DATA const *_cf_data_add(CONF_ITEM *ci, void const *data, char const *name, bool free);
-
-#define                cf_data_remove(_cf, _cd) _cf_data_remove(CF_TO_ITEM(_cf), _cd);
-void           *_cf_data_remove(CONF_ITEM *ci, CONF_DATA const *_cd);
-
-#define                cf_data_walk(_cf, _type, _cb, _ctx) _cf_data_walk(CF_TO_ITEM(_cf), #_type, _cb, _ctx)
-int            _cf_data_walk(CONF_ITEM *ci, char const *type, cf_walker_t cb, void *ctx);
-
-/*
- *     Error logging
- */
-#define                cf_log_err(_cf, _fmt, ...) _cf_log_err(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
-void           _cf_log_err(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
-
-#define                cf_log_perr(_cf, _fmt, ...) _cf_log_perr(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
-void           _cf_log_perr(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
-
-#define                cf_log_warn(_cf, _fmt, ...) _cf_log_warn(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
-void           _cf_log_warn(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
-
-#define                cf_log_info(_cf, _fmt, ...) _cf_log_info(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
-void           _cf_log_info(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
-
-#define                cf_log_debug(_cf, _fmt, ...) _cf_log_debug(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
-void           _cf_log_debug(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
-
-void           cf_log_err_by_name(CONF_SECTION const *parent,
-                                  char const *name, char const *fmt, ...) CC_HINT(format (printf, 3, 4));
-
-#define                cf_debug(_cf) _cf_debug(CF_TO_ITEM(_cf))
-void           _cf_debug(CONF_ITEM const *ci);
 #ifdef __cplusplus
 }
 #endif
 
-#endif /* _FR_CONFFILE_H */
+#endif /* _FR_CF_PARSE_H */
diff --git a/src/include/cf_util.h b/src/include/cf_util.h
new file mode 100644 (file)
index 0000000..142cfed
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ *  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
+ */
+#ifndef _FR_CF_UTIL_H
+#define _FR_CF_UTIL_H
+/**
+ * $Id$
+ *
+ * @file include/cf_util.h
+ * @brief API to create and manipulate internal format configurations.
+ *
+ * @copyright 2017  The FreeRADIUS server project
+ */
+RCSIDH(conf_file_h, "$Id$")
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <freeradius-devel/token.h>
+#include <freeradius-devel/rbtree.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Export the minimum amount of information about these structs
+ */
+typedef struct cf_item CONF_ITEM;      //!< Generic configuration element, extended to become
+                                       ///< a #CONF_PAIR, a #CONF_SECTION or #CONF_DATA.
+typedef struct cf_section CONF_SECTION;        //!< #CONF_ITEM used to group multiple #CONF_PAIR and #CONF_SECTION, together.
+typedef struct cf_pair CONF_PAIR;      //!< #CONF_ITEM with an attribute, an operator and a value.
+typedef struct cf_data CONF_DATA;      //!< #CONF_ITEM used to associate arbitrary data
+                                       ///< with a #CONF_PAIR or #CONF_SECTION.
+
+
+#define FR_TIMEVAL_TO_MS(_x) (((_x)->tv_usec / 1000) + ((_x)->tv_sec * (uint64_t)1000))
+#define FR_TIMESPEC_TO_MS(_x) (((_x)->tv_usec / 1000000) + ((_x)->tv_sec * (uint64_t)1000))
+
+/** Auto cast from the input type to CONF_ITEM (which is the base type)
+ *
+ * Automatically casts:
+ * - #CONF_SECTION
+ * - #CONF_PAIR
+ * - #CONF_DATA
+ *
+ * To a #CONF_ITEM, whilst performing talloc type checks.
+ */
+#define CF_TO_ITEM(_cf) \
+_Generic((_cf), \
+       CONF_SECTION *: cf_section_to_item((CONF_SECTION const *)_cf), \
+       CONF_SECTION const *: cf_section_to_item((CONF_SECTION const *)_cf), \
+       CONF_PAIR *: cf_pair_to_item((CONF_PAIR const *)_cf), \
+       CONF_PAIR const *: cf_pair_to_item((CONF_PAIR const *)_cf), \
+       CONF_DATA *: cf_data_to_item((CONF_DATA const *)_cf), \
+       CONF_DATA const *: cf_data_to_item((CONF_DATA const *)_cf), \
+       default: _cf \
+)
+
+typedef int (*cf_walker_t)(void *data, void *ctx);
+
+extern char const *CF_IDENT_ANY;
+
+/*
+ *     Generic functions that apply to all types of #CONF_ITEM
+ */
+#define                cf_item_add(_parent, _child) _cf_item_add(CF_TO_ITEM(_parent), _child)
+void           _cf_item_add(CONF_ITEM *parent, CONF_ITEM *child);
+
+#define                cf_item_next(_ci, _prev) _cf_item_next(CF_TO_ITEM(_ci), _prev)
+CONF_ITEM      *_cf_item_next(CONF_ITEM const *ci, CONF_ITEM const *prev);
+
+#define                cf_root(_cf) _cf_root(CF_TO_ITEM(_cf))
+CONF_SECTION   *_cf_root(CONF_ITEM const *ci);
+
+#define                cf_parent(_cf) _cf_parent(CF_TO_ITEM(_cf))
+CONF_ITEM      *_cf_parent(CONF_ITEM const *ci);
+
+#define                cf_lineno(_cf) _cf_lineno(CF_TO_ITEM(_cf))
+int            _cf_lineno(CONF_ITEM const *ci);
+
+#define                cf_filename(_cf) _cf_filename(CF_TO_ITEM(_cf))
+char const     *_cf_filename(CONF_ITEM const *ci);
+
+bool           cf_item_is_section(CONF_ITEM const *ci);
+bool           cf_item_is_pair(CONF_ITEM const *ci);
+bool           cf_item_is_data(CONF_ITEM const *ci);
+
+CONF_PAIR      *cf_item_to_pair(CONF_ITEM const *ci);
+CONF_SECTION   *cf_item_to_section(CONF_ITEM const *ci);
+CONF_DATA      *cf_item_to_data(CONF_ITEM const *ci);
+
+CONF_ITEM      *cf_pair_to_item(CONF_PAIR const *cp);
+CONF_ITEM      *cf_section_to_item(CONF_SECTION const *cs);
+CONF_ITEM      *cf_data_to_item(CONF_DATA const *cs);
+
+/*
+ *     Section manipulation and searching
+ */
+CONF_SECTION   *cf_section_alloc(CONF_SECTION *parent, char const *name1, char const *name2);
+CONF_SECTION   *cf_section_dup(CONF_SECTION *parent, CONF_SECTION const *cs,
+                               char const *name1, char const *name2, bool copy_meta);
+void           cf_section_add(CONF_SECTION *parent, CONF_SECTION *cs);
+CONF_SECTION   *cf_section_next(CONF_SECTION const *cs, CONF_SECTION const *prev);
+CONF_SECTION   *cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2);
+CONF_SECTION   *cf_section_find_next(CONF_SECTION const *cs, CONF_SECTION const *subcs,
+                                     char const *name1, char const *name2);
+
+char const     *cf_section_value_find(CONF_SECTION const *, char const *attr);
+
+char const     *cf_section_name1(CONF_SECTION const *cs);
+char const     *cf_section_name2(CONF_SECTION const *cs);
+char const     *cf_section_name(CONF_SECTION const *cs);
+char const     *cf_section_argv(CONF_SECTION const *cs, int argc);
+FR_TOKEN       cf_section_name2_quote(CONF_SECTION const *cs);
+FR_TOKEN       cf_section_argv_quote(CONF_SECTION const *cs, int argc);
+
+/*
+ *     Pair manipulation and searching
+ */
+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      *cf_pair_dup(CONF_SECTION *parent, CONF_PAIR *cp);
+int            cf_pair_replace(CONF_SECTION *cs, CONF_PAIR *cp, char const *value);
+void           cf_pair_add(CONF_SECTION *parent, CONF_PAIR *cp);
+CONF_PAIR      *cf_pair_next(CONF_SECTION const *cs, CONF_PAIR const *prev);
+CONF_PAIR      *cf_pair_find(CONF_SECTION const *cs, char const *name);
+CONF_PAIR      *cf_pair_find_next(CONF_SECTION const *cs, CONF_PAIR const *prev, char const *name);
+int            cf_pair_count(CONF_SECTION const *cs);
+
+char const     *cf_pair_attr(CONF_PAIR const *pair);
+char const     *cf_pair_value(CONF_PAIR const *pair);
+FR_TOKEN       cf_pair_operator(CONF_PAIR const *pair);
+
+FR_TOKEN       cf_pair_attr_quote(CONF_PAIR const *pair);
+FR_TOKEN       cf_pair_value_quote(CONF_PAIR const *pair);
+
+/*
+ *     Data manipulation and searching
+ */
+#define                cf_data_find(_cf, _type, _name) _cf_data_find(CF_TO_ITEM(_cf), #_type, _name)
+CONF_DATA const        *_cf_data_find(CONF_ITEM const *ci, char const *type, char const *name);
+
+#define                cf_data_find_next(_cf, _prev, _type, _name) _cf_data_find(CF_TO_ITEM(_cf), CF_TO_ITEM(_prev), #_type, _name)
+CONF_DATA const        *_cf_data_find_next(CONF_ITEM const *ci, CONF_ITEM const *prev, char const *type, char const *name);
+
+void           *cf_data_value(CONF_DATA const *cd);
+
+#define                cf_data_add(_cf, _data, _name, _free) _cf_data_add(CF_TO_ITEM(_cf), _data, _name, _free)
+CONF_DATA const *_cf_data_add(CONF_ITEM *ci, void const *data, char const *name, bool free);
+
+#define                cf_data_remove(_cf, _cd) _cf_data_remove(CF_TO_ITEM(_cf), _cd);
+void           *_cf_data_remove(CONF_ITEM *ci, CONF_DATA const *_cd);
+
+#define                cf_data_walk(_cf, _type, _cb, _ctx) _cf_data_walk(CF_TO_ITEM(_cf), #_type, _cb, _ctx)
+int            _cf_data_walk(CONF_ITEM *ci, char const *type, cf_walker_t cb, void *ctx);
+
+/*
+ *     Error logging
+ */
+#define                cf_log_err(_cf, _fmt, ...) _cf_log_err(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
+void           _cf_log_err(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
+
+#define                cf_log_perr(_cf, _fmt, ...) _cf_log_perr(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
+void           _cf_log_perr(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
+
+#define                cf_log_warn(_cf, _fmt, ...) _cf_log_warn(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
+void           _cf_log_warn(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
+
+#define                cf_log_info(_cf, _fmt, ...) _cf_log_info(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
+void           _cf_log_info(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
+
+#define                cf_log_debug(_cf, _fmt, ...) _cf_log_debug(CF_TO_ITEM(_cf), _fmt, ## __VA_ARGS__)
+void           _cf_log_debug(CONF_ITEM const *ci, char const *fmt, ...) CC_HINT(format (printf, 2, 3));
+
+void           cf_log_err_by_name(CONF_SECTION const *parent,
+                                  char const *name, char const *fmt, ...) CC_HINT(format (printf, 3, 4));
+
+#define                cf_debug(_cf) _cf_debug(CF_TO_ITEM(_cf))
+void           _cf_debug(CONF_ITEM const *ci);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FR_CF_UTIL_H */
index 9489681aa654068c9e3b84b9844f6ecb5f18349f..77f98753c2f3827fce43ff263d851c16322e37fb 100644 (file)
@@ -32,7 +32,7 @@ RCSIDH(dl_h, "$Id$")
 #endif
 
 #include <freeradius-devel/version.h>
-#include <freeradius-devel/conf_file.h>
+#include <freeradius-devel/cf_parse.h>
 
 #ifdef __cplusplus
 extern "C" {
index 608786f8a9189aab7f451641aaf1f1f7fd6490a0..7e96a660ed04d8eea65f18430052d9b63c7b3b4b 100644 (file)
@@ -23,7 +23,7 @@
  *
  * @author Alan DeKok <aland@freeradius.org>
  */
-#include <freeradius-devel/conf_file.h> /* Need CONF_* definitions */
+#include <freeradius-devel/cf_util.h> /* Need CONF_* definitions */
 #include <freeradius-devel/map_proc.h>
 #include <freeradius-devel/modpriv.h>
 #include <freeradius-devel/rad_assert.h>
index a73b51f0ca376cb572255b323022b09ebe5768ab..eb1305cd667e21b4946f7281c0f3cde037fab959 100644 (file)
@@ -26,7 +26,7 @@
  */
 RCSIDH(map_h, "$Id$")
 
-#include <freeradius-devel/conf_file.h>
+#include <freeradius-devel/cf_util.h>
 #include <freeradius-devel/tmpl.h>
 
 #ifdef __cplusplus
index f759d11d747216c21f26ce34a4e186dfcf9a3070..a2450c0920cb27af476cd8174d07f65b4cb77fe1 100644 (file)
@@ -26,7 +26,7 @@
  */
 RCSIDH(map_proc_h, "$Id$")
 
-#include <freeradius-devel/conf_file.h>
+#include <freeradius-devel/cf_util.h>
 #include <freeradius-devel/tmpl.h>
 
 #ifdef __cplusplus
index 924af4bafd8286ddb42f65cfa705b81d07d6e019..a241fe1d487eab4887628b1a8a366932603ae76e 100644 (file)
@@ -25,7 +25,7 @@
  */
 RCSIDH(modules_h, "$Id$")
 
-#include <freeradius-devel/conf_file.h>
+#include <freeradius-devel/cf_parse.h>
 #include <freeradius-devel/dl.h>
 #include <freeradius-devel/features.h>
 #include <freeradius-devel/pool.h>
index 8a1f2b9e07347a649c879c43cf8037c7f93b2cd9..f90f57b65f25146d7fb92a04cf4f8d5fa5aa0791 100644 (file)
@@ -27,7 +27,7 @@ RCSIDH(radiusd_h, "$Id$")
 
 #include <freeradius-devel/libradius.h>
 #include <freeradius-devel/conf.h>
-#include <freeradius-devel/conf_file.h>
+#include <freeradius-devel/cf_file.h>
 #include <freeradius-devel/event.h>
 #include <freeradius-devel/heap.h>
 
index d11057882732bd5c328748742a2c69c46724ee97..34f01b5d696d9861c3e1e1dde70e6f8fac2e6f96 100644 (file)
@@ -28,7 +28,7 @@
  */
 RCSIDH(tls_h, "$Id$")
 
-#include <freeradius-devel/conf_file.h>
+#include <freeradius-devel/cf_parse.h>
 #include <freeradius-devel/tmpl.h>
 
 /*
index a346a3eb49023b5c7c00c370a5646bf4dce0c9f0..29d6dee5df47614e90ca3728ce2e919463f5acff 100644 (file)
@@ -29,7 +29,7 @@ RCSIDH(xlat_h, "$Id$")
 extern "C" {
 #endif
 
-#include <freeradius-devel/conf_file.h>
+#include <freeradius-devel/cf_util.h>
 
 typedef struct xlat_exp xlat_exp_t;
 
index 7c85e068c371cfdafa01027581a1e8b33e07ca33..939e23669720bf03ebb65aba95c590d86d5becd9 100644 (file)
@@ -16,7 +16,7 @@
 #ifndef _FR_APPLICATION_H
 #define _FR_APPLICATION_H
 
-#include <freeradius-devel/conf_file.h>
+#include <freeradius-devel/cf_util.h>
 #include <freeradius-devel/dl.h>
 #include <freeradius-devel/io/io.h>
 
diff --git a/src/main/cf_file.c b/src/main/cf_file.c
new file mode 100644 (file)
index 0000000..7b19b9a
--- /dev/null
@@ -0,0 +1,2079 @@
+/*
+ *   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
+ */
+
+/**
+ * $Id$
+ * @file conf_file.c
+ * @brief Read the radiusd.conf file.
+ *
+ * @note  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
+ *
+ * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * @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>
+
+#include <freeradius-devel/cf_file.h>
+#include "cf_priv.h"
+
+#include <freeradius-devel/cursor.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifdef HAVE_DIRENT_H
+#  include <dirent.h>
+#endif
+
+#ifdef HAVE_SYS_STAT_H
+#  include <sys/stat.h>
+#endif
+
+#include <ctype.h>
+#include <fcntl.h>
+
+bool check_config = false;
+static uid_t conf_check_uid = (uid_t)-1;
+static gid_t conf_check_gid = (gid_t)-1;
+
+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 }
+};
+
+static int cf_file_include(CONF_SECTION *cs, char const *filename_in, CONF_INCLUDE_TYPE file_type, char *buff[7]);
+
+/*
+ *     Expand the variables in an input string.
+ */
+char const *cf_expand_variables(char const *cf, int *lineno,
+                               CONF_SECTION *outer_cs,
+                               char *output, size_t outsize,
+                               char const *input, bool *soft_fail)
+{
+       char *p;
+       char const *end, *ptr;
+       CONF_SECTION const *parent_cs;
+       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...
+        */
+       parent_cs = cf_root(outer_cs);
+
+       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';
+                               INFO("%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(parent_cs, outer_cs, 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 *find = 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, find->name1);
+                                       break;
+
+                               case CONF_PROPERTY_INSTANCE:
+                                       strcpy(p, find->name2 ? find->name2 : find->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;
+                               }
+
+                               if (!cp->value) {
+                                       ERROR("%s[%d]: Reference \"%s\" has no value",
+                                              cf, *lineno, input);
+                                       return NULL;
+                               }
+
+                               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);
+                               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 (cf_item_to_section(ci->parent) == outer_cs) {
+                                       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(outer_cs, 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(outer_cs, &(subcs->item));
+
+                               ptr = end + 1;
+
+                       } else {
+                               ERROR("%s[%d]: Reference \"%s\" type is invalid", cf, *lineno, input);
+                               return NULL;
+                       }
+               } else if (memcmp(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';
+                               INFO("%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;
+}
+
+/*
+ *     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->item.child; 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_find(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;
+}
+
+/*
+ *     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_SECTION *top;
+       rbtree_t *tree;
+       int fd;
+       FILE *fp;
+
+       top = cf_root(cs);
+       tree = cf_data_value(cf_data_find(top, rbtree_t, "filename"));
+       rad_assert(tree);
+
+       fp = fopen(filename, "r");
+       if (!fp) {
+               ERROR("Unable to open file \"%s\": %s", filename, fr_syserror(errno));
+               return NULL;
+       }
+
+       fd = fileno(fp);
+
+       MEM(file = talloc(tree, cf_file_t));
+
+       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 as an "input" file.  i.e. one read by a module.
+ *
+ * @note Must be called with super user privileges.
+ *
+ * @param cs           currently being processed.
+ * @param filename     to check.
+ * @param check_perms  If true - will return false if file is world readable,
+ *                     or not readable by the unprivileged user/group.
+ * @return
+ *     - true if permissions are OK, or the file exists.
+ *     - false if the file does not exist or the permissions are incorrect.
+ */
+bool cf_file_check(CONF_SECTION *cs, char const *filename, bool check_perms)
+{
+       cf_file_t       *file;
+       CONF_SECTION    *top;
+       rbtree_t        *tree;
+       int             fd = -1;
+
+       top = cf_root(cs);
+       tree = cf_data_value(cf_data_find(top, rbtree_t, "filename"));
+       if (!tree) return false;
+
+       file = talloc(tree, cf_file_t);
+       if (!file) return false;
+
+       file->filename = filename;
+       file->cs = cs;
+
+       if (!check_perms) {
+               if (stat(filename, &file->buf) < 0) {
+               perm_error:
+                       rad_file_error(errno);  /* Write error and euid/egid to error buff */
+                       ERROR("Unable to open file \"%s\": %s", filename, fr_strerror());
+               error:
+                       if (fd >= 0) close(fd);
+                       talloc_free(file);
+                       return false;
+               }
+               talloc_free(file);
+               return true;
+       }
+
+       /*
+        *      This really does seem to be the simplest way
+        *      to check that the file can be read with the
+        *      euid/egid.
+        */
+       {
+               uid_t euid = (uid_t)-1;
+               gid_t egid = (gid_t)-1;
+
+               if ((conf_check_gid != (gid_t)-1) && ((egid = getegid()) != conf_check_gid)) {
+                       if (setegid(conf_check_gid) < 0) {
+                               ERROR("Failed setting effective group ID for file check");
+                               goto error;
+                       }
+               }
+               if ((conf_check_uid != (uid_t)-1) && ((euid = geteuid()) != conf_check_uid)) {
+                       if (seteuid(conf_check_uid) < 0) {
+                               ERROR("Failed setting effective user ID for file check");
+                               goto error;
+                       }
+               }
+               fd = open(filename, O_RDONLY);
+               if (conf_check_uid != euid) {
+                       if (seteuid(euid) < 0) {
+                               ERROR("Failed restoring effective user ID after file check");
+
+                               goto error;
+                       }
+               }
+               if (conf_check_gid != egid) {
+                       if (setegid(egid) < 0) {
+                               ERROR("Failed restoring effective group ID after file check");
+                               goto error;
+                       }
+               }
+       }
+
+       if (fd < 0) goto perm_error;
+       if (fstat(fd, &file->buf) < 0) goto perm_error;
+
+       close(fd);
+
+#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;
+}
+
+/*
+ *     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->item.child; 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_quote == T_BARE_WORD) ||
+                          (cp->rhs_quote == T_DOUBLE_QUOTED_STRING) ||
+                          (cp->rhs_quote == T_BACK_QUOTED_STRING));
+
+               value = cf_expand_variables(ci->filename, &ci->lineno, cs, buffer, sizeof(buffer), cp->value, NULL);
+               if (!value) return -1;
+
+               talloc_const_free(cp->value);
+               cp->value = talloc_typed_strdup(cp, value);
+       }
+
+       for (ci = cs->item.child; 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;
+}
+
+
+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;
+}
+
+static bool invalid_location(CONF_SECTION *this, char const *name, char const *filename, int lineno)
+{
+       /*
+        *      if / elsif MUST be inside of a
+        *      processing section, which MUST in turn
+        *      be inside of a "server" directive.
+        */
+       if (!this || !this->item.parent) {
+       invalid_location:
+               ERROR("%s[%d]: Invalid location for '%s'",
+                     filename, lineno, name);
+               return true;
+       }
+
+       /*
+        *      Can only have "if" in 3 named sections.
+        */
+       this = cf_item_to_section(this->item.parent);
+       while ((strcmp(this->name1, "server") != 0) &&
+              (strcmp(this->name1, "policy") != 0) &&
+              (strcmp(this->name1, "instantiate") != 0)) {
+               this = cf_item_to_section(this->item.parent);
+               if (!this) goto invalid_location;
+       }
+
+       return false;
+}
+
+#ifdef WITH_CONF_WRITE
+static void cf_comment_add(CONF_SECTION *cs, int lineno, char const *ptr)
+{
+       CONF_COMMENT *cc;
+
+       cc = talloc_zero(cs, CONF_COMMENT);
+       cc->item.type = CONF_ITEM_COMMENT;
+       cc->item.parent = cs;
+       cc->item.filename = cs->item.filename;
+       cc->item.lineno = lineno;
+       cc->comment = talloc_typed_strdup(cc, ptr);
+
+
+       cf_item_add(cs, &(cc->item));
+}
+
+static void cf_include_add(CONF_SECTION *cs, char const *filename, CONF_INCLUDE_TYPE file_type)
+{
+       CONF_INCLUDE *cc;
+
+       cc = talloc_zero(cs, CONF_INCLUDE);
+       cc->item.type = CONF_ITEM_INCLUDE;
+       cc->item.parent = cs;
+       cc->item.filename = cs->item.filename;
+       cc->item.lineno = 0;
+       cc->filename = talloc_typed_strdup(cc, filename);
+       cc->file_type = file_type;
+
+       cf_item_add(cs, &(cc->item));
+}
+#endif
+
+/*
+ *     Read a part of the config file.
+ */
+static int cf_section_read(char const *filename, int *lineno, FILE *fp,
+                          CONF_SECTION *current, char *buff[7])
+
+{
+       CONF_SECTION    *this, *css;
+       CONF_PAIR       *cpn;
+       char const      *ptr;
+       char const      *value;
+#ifdef WITH_CONF_WRITE
+       char const      *orig_value = NULL;
+#endif
+
+       FR_TOKEN        t1 = T_INVALID, t2, t3;
+       bool            has_spaces = false;
+       bool            pass2;
+       char            *cbuff;
+       size_t          len;
+
+       this = current;         /* add items here */
+
+       cbuff = buff[0];
+
+       /*
+        *      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(cbuff, talloc_array_length(buff[0]) - (cbuff - buff[0]), 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(cbuff);
+               if ((cbuff + len + 1) >= (buff[0] + talloc_array_length(buff[0]))) {
+                       ERROR("%s[%d]: Line too long", filename, *lineno);
+               error:
+                       return -1;
+               }
+
+               if (has_spaces) {
+                       ptr = cbuff;
+                       while (isspace((int) *ptr)) ptr++;
+
+                       if (ptr > cbuff) {
+                               memmove(cbuff, ptr, len - (ptr - cbuff));
+                               len -= (ptr - cbuff);
+                       }
+               }
+
+               /*
+                *      Not doing continuations: check for edge
+                *      conditions.
+                */
+               if (cbuff == buff[0]) {
+                       if (at_eof) break;
+
+                       ptr = buff[0];
+                       while (*ptr && isspace((int) *ptr)) ptr++;
+
+#ifdef WITH_CONF_WRITE
+                       /*
+                        *      This is where all of the comments are handled
+                        */
+                       if (*ptr == '#') {
+                               cf_comment_add(this, *lineno, ptr + 1);
+                       }
+#endif
+
+                       if (!*ptr || (*ptr == '#')) continue;
+
+               } else if (at_eof || (len == 0)) {
+                       ERROR("%s[%d]: Continuation at EOF is illegal", filename, *lineno);
+                       goto error;
+               }
+
+               /*
+                *      See if there's a continuation.
+                */
+               while ((len > 0) &&
+                      ((cbuff[len - 1] == '\n') || (cbuff[len - 1] == '\r'))) {
+                       len--;
+                       cbuff[len] = '\0';
+               }
+
+               if ((len > 0) && (cbuff[len - 1] == '\\')) {
+                       /*
+                        *      Check for "suppress spaces" magic.
+                        */
+                       if (!has_spaces && (len > 2) && (cbuff[len - 2] == '"')) {
+                               has_spaces = true;
+                       }
+
+                       cbuff[len - 1] = '\0';
+                       cbuff += len - 1;
+                       continue;
+               }
+
+               ptr = cbuff = buff[0];
+               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] == '`')) {
+                       ssize_t slen;
+
+                       if (ptr[0] == '%') {
+                               slen = rad_copy_variable(buff[1], ptr);
+                       } else {
+                               slen = rad_copy_string(buff[1], ptr);
+                       }
+                       if (slen <= 0) {
+                               char *spaces, *text;
+
+                               fr_canonicalize_error(current, &spaces, &text, slen, ptr);
+
+                               ERROR("%s[%d]: %s", filename, *lineno, text);
+                               ERROR("%s[%d]: %s^ Invalid expansion", filename, *lineno, spaces);
+
+                               talloc_free(spaces);
+                               talloc_free(text);
+
+                               goto error;
+                       }
+
+                       ptr += slen;
+
+                       t2 = gettoken(&ptr, buff[2], talloc_array_length(buff[2]), true);
+                       switch (t2) {
+                       case T_HASH:
+                       case T_EOL:
+                               goto do_bare_word;
+
+                       default:
+                               ERROR("%s[%d]: Invalid expansion: %s", filename, *lineno, ptr);
+                               goto error;
+                       }
+               } else {
+                       t1 = gettoken(&ptr, buff[1], talloc_array_length(buff[1]), 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);
+                              goto error;
+                      }
+
+                      /*
+                       *       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)) goto error;
+
+                      this = cf_item_to_section(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(buff[1], "$INCLUDE") == 0) ||
+                  (strcasecmp(buff[1], "$-INCLUDE") == 0)) {
+                       bool relative = true;
+
+                       t2 = getword(&ptr, buff[2], talloc_array_length(buff[2]), true);
+                       if (t2 != T_EOL) {
+                              ERROR("%s[%d]: Unexpected text after $INCLUDE", filename, *lineno);
+                              goto error;
+                       }
+
+                       if (buff[2][0] == '$') relative = false;
+
+                       value = cf_expand_variables(filename, lineno, this, buff[4], talloc_array_length(buff[4]),
+                                                   buff[2], NULL);
+                       if (!value) goto error;
+
+                       if (!FR_DIR_IS_RELATIVE(value)) relative = false;
+
+                       if (relative) {
+                               value = cf_local_file(filename, value, buff[3], talloc_array_length(buff[3]));
+                               if (!value) {
+                                       ERROR("%s[%d]: Directories too deep", filename, *lineno);
+                                       goto error;
+                               }
+                       }
+
+
+#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;
+                               char *my_directory;
+
+                               my_directory = talloc_strdup(this, value);
+
+                               cf_log_debug(current, "Including files in directory \"%s\"", my_directory);
+
+#ifdef WITH_CONF_WRITE
+                               /*
+                                *      We print this out, but don't
+                                *      actually open a file based on
+                                *      it.
+                                */
+                               cf_include_add(this, my_directory, CONF_INCLUDE_DIR);
+#endif
+
+#ifdef S_IWOTH
+                               /*
+                                *      Security checks.
+                                */
+                               if (stat(my_directory, &stat_buf) < 0) {
+                                       ERROR("%s[%d]: Failed reading directory %s: %s", filename, *lineno,
+                                             my_directory, fr_syserror(errno));
+                                       talloc_free(my_directory);
+                                       goto error;
+                               }
+
+                               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, my_directory);
+                                       talloc_free(my_directory);
+                                       goto error;
+                               }
+#endif
+                               dir = opendir(my_directory);
+                               if (!dir) {
+                                       ERROR("%s[%d]: Error reading directory %s: %s",
+                                             filename, *lineno, value,
+                                             fr_syserror(errno));
+                                       talloc_free(my_directory);
+                                       goto error;
+                               }
+
+                               /*
+                                *      Read the directory, ignoring "." files.
+                                */
+                               while ((dp = readdir(dir)) != NULL) {
+                                       char const *p;
+
+                                       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;
+
+
+                                       snprintf(buff[2], talloc_array_length(buff[2]), "%s%s",
+                                                my_directory, dp->d_name);
+                                       if ((stat(buff[2], &stat_buf) != 0) ||
+                                           S_ISDIR(stat_buf.st_mode)) continue;
+
+                                       /*
+                                        *      Read the file into the current
+                                        *      configuration section.
+                                        */
+                                       if (cf_file_include(this, buff[2], CONF_INCLUDE_FROMDIR, buff) < 0) {
+                                               closedir(dir);
+                                               goto error;
+                                       }
+                               }
+                               closedir(dir);
+                               talloc_free(my_directory);
+
+                       }  else
+#endif
+                       { /* it was a normal file */
+                               if (buff[1][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, CONF_INCLUDE_FILE, buff) < 0) goto error;
+                       }
+                       continue;
+               } /* we were in an include */
+
+              if (strcasecmp(buff[1], "$template") == 0) {
+                      CONF_ITEM *ci;
+                      CONF_SECTION *parent_cs, *templatecs;
+                      t2 = getword(&ptr, buff[2], talloc_array_length(buff[2]), true);
+
+                      if (t2 != T_EOL) {
+                               ERROR("%s[%d]: Unexpected text after $TEMPLATE", filename, *lineno);
+                               goto error;
+                      }
+
+                      parent_cs = cf_root(current);
+
+                      templatecs = cf_section_find(parent_cs, "templates", NULL);
+                      if (!templatecs) {
+                               ERROR("%s[%d]: No \"templates\" section for reference \"%s\"", filename, *lineno, buff[2]);
+                               goto error;
+                      }
+
+                      ci = cf_reference_item(parent_cs, templatecs, buff[2]);
+                      if (!ci || (ci->type != CONF_ITEM_SECTION)) {
+                               ERROR("%s[%d]: Reference \"%s\" not found", filename, *lineno, buff[2]);
+                               goto error;
+                      }
+
+                      if (!this) {
+                               ERROR("%s[%d]: Internal sanity check error in template reference", filename, *lineno);
+                               goto error;
+                      }
+
+                      if (this->template) {
+                               ERROR("%s[%d]: Section already has a template", filename, *lineno);
+                               goto error;
+                      }
+
+                      this->template = cf_item_to_section(ci);
+                      continue;
+              }
+
+               /*
+                *      Ensure that the user can't add CONF_PAIRs
+                *      with 'internal' names;
+                */
+               if (buff[1][0] == '_') {
+                       ERROR("%s[%d]: Illegal configuration pair name \"%s\"", filename, *lineno, buff[1]);
+                       goto error;
+               }
+
+               /*
+                *      Handle if/elsif specially.
+                */
+               if ((strcmp(buff[1], "if") == 0) || (strcmp(buff[1], "elsif") == 0)) {
+                       ssize_t slen;
+                       char const *error = NULL;
+                       char *p;
+                       fr_cond_t *cond = NULL;
+
+                       if (invalid_location(this, buff[1], filename, *lineno)) goto error;
+
+                       /*
+                        *      Skip (...) to find the {
+                        */
+                       slen = fr_cond_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';
+
+                       /*
+                        *      Nuke trailing spaces.  This hack
+                        *      really belongs in the parser.
+                        */
+                       while ((p > ptr) && (isspace((int) p[-1]))) {
+                               p--;
+                               *p = '\0';
+                       }
+
+                       /*
+                        *      If there's a ${...}.  If so, expand it.
+                        */
+                       if (strchr(ptr, '$') != NULL) {
+                               ptr = cf_expand_variables(filename, lineno,
+                                                         this,
+                                                         buff[3], talloc_array_length(buff[3]),
+                                                         ptr, NULL);
+                               if (!ptr) {
+                                       ERROR("%s[%d]: Parse error expanding ${...} in condition",
+                                             filename, *lineno);
+                                       goto error;
+                               }
+                       } /* else leave it alone */
+
+                       css = cf_section_alloc(this, buff[1], ptr);
+                       if (!css) {
+                               ERROR("%s[%d]: Failed allocating memory for section", filename, *lineno);
+                               goto error;
+                       }
+                       css->item.filename = filename;
+                       css->item.lineno = *lineno;
+
+                       slen = fr_cond_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);
+                               goto error;
+                       }
+
+                       if ((size_t) slen >= (talloc_array_length(buff[2]) - 1)) {
+                               talloc_free(css);
+                               ERROR("%s[%d]: Condition is too large after \"%s\"", filename, *lineno, buff[1]);
+                               goto error;
+                       }
+
+                       /*
+                        *      Copy the expanded and parsed condition
+                        *      into buff[2].  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(buff[2], ptr, slen);
+                       buff[2][slen] = '\0';
+                       ptr = p;
+
+                       if ((t3 = gettoken(&ptr, buff[3], talloc_array_length(buff[3]), true)) != T_LCBRACE) {
+                               talloc_free(css);
+                               ERROR("%s[%d]: Expected '{' %d", filename, *lineno, t3);
+                               goto error;
+                       }
+
+                       /*
+                        *      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, buff[2]);
+
+                       cf_data_add(css, cond, NULL, false);
+
+               add_section:
+                       cf_item_add(this, &(css->item));
+
+                       /*
+                        *      The current section is now the child section.
+                        */
+                       this = css;
+                       css = NULL;
+                       goto check_for_more;
+               }
+
+               /*
+                *      "map" sections have three arguments!
+                */
+               if (strcmp(buff[1], "map") == 0) {
+                       char const *mod;
+                       char const *exp = NULL;
+                       char const *p;
+
+                       t2 = gettoken(&ptr, buff[2], talloc_array_length(buff[2]), false);
+
+                       if (invalid_location(this, buff[1], filename, *lineno)) {
+                               if (t2 != T_LCBRACE) {
+                                       ERROR("%s[%d]: Invalid syntax for 'map'", filename, *lineno);
+                                       goto error;
+                               }
+
+                               goto alloc_section;
+                       }
+
+                       if (t2 != T_BARE_WORD) {
+                               ERROR("%s[%d]: Expected module name after 'map'", filename, *lineno);
+                               goto error;
+                       }
+
+                       mod = cf_expand_variables(filename, lineno,
+                                                 this,
+                                                 buff[3], talloc_array_length(buff[3]),
+                                                 buff[2], NULL);
+                       if (!mod) {
+                               ERROR("%s[%d]: Parse error expanding ${...} in map module name",
+                                     filename, *lineno);
+                               goto error;
+                       }
+
+                       p = ptr;
+                       t3 = gettoken(&p, buff[4], talloc_array_length(buff[4]), false);
+                       if (fr_str_tok[t3]) {
+                               ptr = p;
+
+                               exp = cf_expand_variables(filename, lineno,
+                                                         this,
+                                                         buff[5], talloc_array_length(buff[5]),
+                                                         buff[4], NULL);
+                               if (!exp) {
+                                       ERROR("%s[%d]: Parse error expanding ${...} in map module name",
+                                             filename, *lineno);
+                                       goto error;
+                               }
+                       }
+
+                       if (gettoken(&ptr, buff[6], talloc_array_length(buff[6]), false) != T_LCBRACE) {
+                               ERROR("%s[%d]: Expecting section start brace '{' in 'map' definition",
+                                     filename, *lineno);
+                               goto error;
+                       }
+
+                       /*
+                        *      Allocate the section
+                        */
+                       css = cf_section_alloc(this, buff[1], mod);
+                       if (!css) {
+                               ERROR("%s[%d]: Failed allocating memory for section", filename, *lineno);
+                               goto error;
+                       }
+                       css->item.filename = filename;
+                       css->item.lineno = *lineno;
+                       css->name2_quote = T_BARE_WORD;
+
+                       css->argc = 0;
+                       if (exp) {
+                               css->argv = talloc_array(css, char const *, 1);
+                               css->argv[0] = talloc_typed_strdup(css->argv, exp);
+                               css->argv_quote = talloc_array(css, FR_TOKEN, 1);
+                               css->argv_quote[0] = t3;
+                               css->argc++;
+                       }
+
+                       goto add_section;
+               }
+
+       skip_keywords:
+               /*
+                *      Grab the next token.
+                */
+               t2 = gettoken(&ptr, buff[2], talloc_array_length(buff[2]), false);
+               switch (t2) {
+               case T_HASH:
+               case T_EOL:
+               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_SUB:
+               case T_OP_NE:
+               case T_OP_GE:
+               case T_OP_GT:
+               case T_OP_LE:
+               case T_OP_LT:
+               case T_OP_CMP_EQ:
+               case T_OP_CMP_FALSE:
+                       if (!this || ((strcmp(this->name1, "update") != 0) && (strcmp(this->name1, "map") != 0))) {
+                               ERROR("%s[%d]: Invalid operator in assignment",
+                                      filename, *lineno);
+                               goto error;
+                       }
+                       /* FALL-THROUGH */
+
+               case T_OP_EQ:
+               case T_OP_SET:
+                       while (isspace((int) *ptr)) ptr++;
+
+                       /*
+                        *      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.
+                        */
+                       switch (*ptr) {
+                       case '"':
+                       case '\'':
+                       case '`':
+                       case '/':
+                               t3 = getstring(&ptr, buff[3], talloc_array_length(buff[3]), false);
+                               break;
+
+                       default:
+                       {
+                               const char *q = ptr;
+
+                               t3 = T_BARE_WORD;
+                               while (*q && (*q >= ' ') && (*q != ',') &&
+                                      !isspace(*q)) q++;
+
+                               if ((size_t) (q - ptr) >= talloc_array_length(buff[3])) {
+                                       ERROR("%s[%d]: Parse error: value too long", filename, *lineno);
+                                       goto error;
+                               }
+
+                               memcpy(buff[3], ptr, (q - ptr));
+                               buff[3][q - ptr] = '\0';
+                               ptr = q;
+                       }
+                       }
+
+                       if (t3 == T_INVALID) {
+                               ERROR("%s[%d]: Parse error: %s", filename, *lineno, fr_strerror());
+                               goto error;
+                       }
+
+                       /*
+                        *      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:
+#ifdef WITH_CONF_WRITE
+                               orig_value = buff[3];
+#endif
+                               value = cf_expand_variables(filename, lineno, this, buff[4], talloc_array_length(buff[4]), buff[3], &soft_fail);
+                               if (!value) {
+                                       if (!soft_fail) goto error;
+
+                                       /*
+                                        *      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 = buff[3];
+                               }
+                               break;
+
+                       case T_HASH:
+                       case T_EOL:
+                               value = NULL;
+                               break;
+
+                       default:
+                               value = buff[3];
+                               break;
+                       }
+
+                       /*
+                        *      Add this CONF_PAIR to our CONF_SECTION
+                        */
+               do_set:
+                       cpn = cf_pair_alloc(this, buff[1], value, t2, t1, t3);
+                       if (!cpn) goto error;
+                       cpn->item.filename = filename;
+                       cpn->item.lineno = *lineno;
+                       cpn->pass2 = pass2;
+                       cf_item_add(this, &(cpn->item));
+
+#ifdef WITH_CONF_WRITE
+                       if (orig_value) cpn->orig_value = talloc_typed_strdup(cpn, orig_value);
+                       orig_value = NULL;
+#endif
+                       /*
+                        *      Require a comma, unless there's a comment.
+                        */
+                       while (isspace(*ptr)) ptr++;
+
+                       if (*ptr == ',') {
+                               ptr++;
+                               break;
+                       }
+
+                       /*
+                        *      module # stuff!
+                        *      foo = bar # other stuff
+                        */
+#ifdef WITH_CONF_WRITE
+                       if (*ptr == '#') {
+                               t3 = T_HASH;
+                               ptr++;
+                       }
+
+                       /*
+                        *      Allocate a CONF_COMMENT, and add it to the list of children.
+                        */
+                       if ((t3 == T_HASH) && (*ptr >= ' ')) {
+                               cf_comment_add(this, *lineno, ptr);
+                       }
+#endif
+
+                       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);
+                       goto error;
+
+                       /*
+                        *      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, buff[3], talloc_array_length(buff[3]), true);
+                       if (t3 != T_LCBRACE) {
+                               ERROR("%s[%d]: Expecting section start brace '{' after \"%s %s\"",
+                                     filename, *lineno, buff[1], buff[2]);
+                               goto error;
+                       }
+                       /* FALL-THROUGH */
+
+               alloc_section:
+               case T_LCBRACE:
+                       css = cf_section_alloc(this, buff[1],
+                                              t2 == T_LCBRACE ? NULL : buff[2]);
+                       if (!css) {
+                               ERROR("%s[%d]: Failed allocating memory for section",
+                                     filename, *lineno);
+                               goto error;
+                       }
+
+                       css->item.filename = filename;
+                       css->item.lineno = *lineno;
+                       cf_item_add(this, &(css->item));
+
+                       /*
+                        *      There may not be a name2
+                        */
+                       css->name2_quote = (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());
+
+                       goto error;
+
+               default:
+                       ERROR("%s[%d]: Parse error after \"%s\": unexpected token \"%s\"",
+                             filename, *lineno, buff[1], fr_int2str(fr_tokens_table, t2, "<INVALID>"));
+
+                       goto error;
+               }
+
+       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_lineno(this));
+               goto error;
+       }
+
+       return 0;
+}
+
+/*
+ *     Include one config file in another.
+ */
+static int cf_file_include(CONF_SECTION *cs, char const *filename_in,
+#ifndef WITH_CONF_WRITE
+                          UNUSED
+#endif
+                          CONF_INCLUDE_TYPE file_type, char *buff[7])
+{
+       FILE            *fp;
+       int             lineno = 0;
+       char const      *filename;
+
+       /*
+        *      So we only need to do this once.
+        */
+       filename = talloc_strdup(cs, filename_in);
+
+       DEBUG2("Including configuration file \"%s\"", filename);
+
+       fp = cf_file_open(cs, filename);
+       if (!fp) return -1;
+
+       if (!cs->item.filename) cs->item.filename = filename;
+
+#ifdef WITH_CONF_WRITE
+       /*
+        *      Instruct the parser that we've started to include a
+        *      file at this point.
+        */
+       cf_include_add(cs, filename, file_type);
+#endif
+
+       /*
+        *      Read the section.  It's OK to have EOF without a
+        *      matching close brace.
+        */
+       if (cf_section_read(filename, &lineno, fp, cs, buff) < 0) {
+               ERROR("Failed parsing configuration file \"%s\"", filename);
+               fclose(fp);
+               return -1;
+       }
+
+#ifdef WITH_CONF_WRITE
+       /*
+        *      Instruct the parser that we've finished including a
+        *      file at this point.
+        */
+       cf_include_add(cs, NULL, file_type);
+#endif
+
+       fclose(fp);
+       return 0;
+}
+
+/*
+ *     Bootstrap a config file.
+ */
+int cf_file_read(CONF_SECTION *cs, char const *filename)
+{
+       int             i;
+       char            *p;
+       CONF_PAIR       *cp;
+       rbtree_t        *tree;
+       char            **buff;
+
+       cp = cf_pair_alloc(cs, "confdir", filename, T_OP_EQ, T_BARE_WORD, T_SINGLE_QUOTED_STRING);
+       if (!cp) return -1;
+
+       p = strrchr(cp->value, FR_DIR_SEP);
+       if (p) *p = '\0';
+
+       cf_item_add(cs, &(cp->item));
+
+       MEM(tree = rbtree_create(cs, _filename_cmp, NULL, 0));
+
+       cf_data_add(cs, tree, "filename", false);
+
+       /*
+        *      Allocate temporary buffers on the heap (so we don't use *all* the stack space)
+        */
+       buff = talloc_array(cs, char *, 7);
+       for (i = 0; i < 7; i++) MEM(buff[i] = talloc_array(buff, char, 8192));
+
+       if (cf_file_include(cs, filename, CONF_INCLUDE_FILE, buff) < 0) {
+               talloc_free(buff);
+               return -1;
+       }
+
+       talloc_free(buff);
+
+       /*
+        *      Now that we've read the file, go back through it and
+        *      expand the variables.
+        */
+       if (cf_section_pass2(cs) < 0) {
+               cf_log_err(cs, "Parsing config items failed");
+               return -1;
+       }
+
+       return 0;
+}
+
+void cf_file_free(CONF_SECTION *cs)
+{
+       talloc_free(cs);
+}
+
+/** Set the euid/egid used when performing file checks
+ *
+ * Sets the euid, and egid used when cf_file_check is called to check
+ * permissions on conf items of type #FR_TYPE_FILE_INPUT.
+ *
+ * @note This is probably only useful for the freeradius daemon itself.
+ *
+ * @param uid to set, (uid_t)-1 to use current euid.
+ * @param gid to set, (gid_t)-1 to use current egid.
+ */
+void cf_file_check_user(uid_t uid, gid_t gid)
+{
+       if (uid != 0) conf_check_uid = uid;
+       if (gid != 0) conf_check_gid = gid;
+}
+
+/*
+ *     See if any of the files have changed.
+ */
+int cf_file_changed(CONF_SECTION *cs, rb_walker_t callback)
+{
+       CONF_SECTION            *top;
+       cf_file_callback_t      cb;
+       rbtree_t                *tree;
+
+       top = cf_root(cs);
+       tree = cf_data_value(cf_data_find(top, rbtree_t, "filename"));
+       if (!tree) return true;
+
+       cb.rcode = CF_FILE_NONE;
+       cb.callback = callback;
+       cb.modules = cf_section_find(cs, "modules", NULL);
+
+       (void) rbtree_walk(tree, RBTREE_IN_ORDER, _file_callback, &cb);
+
+       return cb.rcode;
+}
+
+#ifdef WITH_CONF_WRITE
+static char const parse_tabs[] = "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             ";
+
+static ssize_t cf_string_write(FILE *fp, char const *string, size_t len, FR_TOKEN t)
+{
+       size_t  outlen;
+       char    c;
+       char    buffer[2048];
+
+       switch (t) {
+       default:
+               c = '\0';
+               break;
+
+       case T_DOUBLE_QUOTED_STRING:
+               c = '"';
+               break;
+
+       case T_SINGLE_QUOTED_STRING:
+               c = '\'';
+               break;
+
+       case T_BACK_QUOTED_STRING:
+               c = '`';
+               break;
+       }
+
+       if (c) fprintf(fp, "%c", c);
+
+       outlen = fr_snprint(buffer, sizeof(buffer), string, len, c);
+       fwrite(buffer, outlen, 1, fp);
+
+       if (c) fprintf(fp, "%c", c);
+       return 1;
+}
+
+static size_t cf_pair_write(FILE *fp, CONF_PAIR *cp)
+{
+       if (!cp->value) {
+               fprintf(fp, "%s\n", cp->attr);
+               return 0;
+       }
+
+       cf_string_write(fp, cp->attr, strlen(cp->attr), cp->lhs_quote);
+       fprintf(fp, " %s ", fr_int2str(fr_tokens_table, cp->op, "<INVALID>"));
+       cf_string_write(fp, cp->orig_value, strlen(cp->orig_value), cp->rhs_quote);
+       fprintf(fp, "\n");
+
+       return 1;               /* FIXME */
+}
+
+static FILE *cf_file_write(CONF_SECTION *cs, char const *filename)
+{
+       FILE    *fp;
+       char    *p;
+       char    const *q;
+       char    buffer[8192];
+
+       q = filename;
+       if ((q[0] == '.') && (q[1] == '/')) q += 2;
+
+       snprintf(buffer, sizeof(buffer), "%s/%s", main_config.write_dir, q);
+
+       p = strrchr(buffer, '/');
+       *p = '\0';
+       if ((rad_mkdir(buffer, 0700, -1, -1) < 0) &&
+           (errno != EEXIST)) {
+               cf_log_err(cs, "Failed creating directory %s: %s",
+                             buffer, strerror(errno));
+               return NULL;
+       }
+
+       /*
+        *      And again, because rad_mkdir() butchers the buffer.
+        */
+       snprintf(buffer, sizeof(buffer), "%s/%s", main_config.write_dir, q);
+
+       fp = fopen(buffer, "a");
+       if (!fp) {
+               cf_log_err(cs, "Failed creating file %s: %s",
+                             buffer, strerror(errno));
+               return NULL;
+       }
+
+       return fp;
+}
+
+size_t cf_section_write(FILE *in_fp, CONF_SECTION *cs, int depth)
+{
+       bool            prev = false;
+       CONF_ITEM       *ci;
+       FILE            *fp = NULL;
+       int             fp_max = 0;
+       FILE            *array[32];
+
+       /*
+        *      Default to writing to the FP we're given.
+        */
+       fp = in_fp;
+       array[0] = fp;
+       fp_max = 0;
+
+       /*
+        *      If we have somewhere to print, then print the section
+        *      name1, etc.
+        */
+       if (fp) {
+               fwrite(parse_tabs, depth, 1, fp);
+               cf_string_write(fp, cs->name1, strlen(cs->name1), T_BARE_WORD);
+
+               /*
+                *      FIXME: check for "if" or "elsif".  And if so, print
+                *      out the parsed condition, instead of the input text
+                *
+                *      cf_data_find(cs, CF_DATA_TYPE_UNLANG, "if");
+                */
+
+               if (cs->name2) {
+                       fr_cond_t *c;
+
+                       fputs(" ", fp);
+
+                       c = cf_data_value(cf_data_find(cs, fr_cond_t, NULL));
+                       if (c) {
+                               char buffer[1024];
+
+                               cond_snprint(buffer, sizeof(buffer), c);
+                               fprintf(fp, "(%s)", buffer);
+
+                       } else {        /* dump the string as-is */
+                               cf_string_write(fp, cs->name2, strlen(cs->name2), cs->name2_quote);
+                       }
+               }
+
+               fputs(" {\n", fp);
+       }
+
+       /*
+        *      Loop over the children.  Either recursing, or opening
+        *      a new file.
+        */
+       for (ci = cs->item.child; ci; ci = ci->next) {
+               switch (ci->type) {
+               case CONF_ITEM_SECTION:
+                       if (!fp) continue;
+
+                       cf_section_write(fp, cf_item_to_section(ci), depth + 1);
+                       break;
+
+               case CONF_ITEM_PAIR:
+                       if (!fp) continue;
+
+                       /*
+                        *      Ignore internal things.
+                        */
+                       if (!ci->filename || (ci->filename[0] == '<')) break;
+
+                       fwrite(parse_tabs, depth + 1, 1, fp);
+                       cf_pair_write(fp, cf_item_to_pair(ci));
+                       if (!prev) fputs("\n", fp);
+                       prev = true;
+                       break;
+
+               case CONF_ITEM_COMMENT:
+                       rad_assert(fp != NULL);
+
+                       prev = false;
+                       fwrite(parse_tabs, depth + 1, 1, fp);
+                       fprintf(fp, "#%s", ((CONF_COMMENT *)ci)->comment);
+                       break;
+
+               case CONF_ITEM_INCLUDE:
+                       /*
+                        *      Filename == open the new filename and use that.
+                        *
+                        *      NULL == close the previous filename
+                        */
+                       if (((CONF_INCLUDE *) ci)->filename) {
+                               CONF_INCLUDE *cc = (CONF_INCLUDE *) ci;
+
+                               /*
+                                *      Print out
+                                *
+                                *      $INCLUDE foo.conf
+                                *      $INCLUDE foo/
+                                *
+                                *      but not the files included from the last one.
+                                */
+                               if (fp && (cc->file_type != CONF_INCLUDE_FROMDIR)) {
+                                       fprintf(fp, "$INCLUDE %s\n", ((CONF_INCLUDE *)ci)->filename);
+                               }
+
+                               /*
+                                *      If it's a file, we write the
+                                *      file.  We ignore the
+                                *      directories.  They're just for printing.
+                                */
+                               if (cc->file_type != CONF_INCLUDE_DIR) {
+                                       fp = cf_file_write(cs, ((CONF_INCLUDE *) ci)->filename);
+                                       if (!fp) return 0;
+
+                                       fp_max++;
+                                       array[fp_max] = fp;
+                               }
+                       } else {
+                               /*
+                                *      We're done the current file.
+                                */
+                               rad_assert(fp != NULL);
+                               rad_assert(fp_max > 0);
+                               fclose(fp);
+
+                               fp_max--;
+                               fp = array[fp_max];
+                       }
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       if (fp) {
+               fwrite(parse_tabs, depth, 1, fp);
+               fputs("}\n\n", fp);
+       }
+
+       return 1;
+}
+#endif /* WITH_CONF_WRITE */
+
+
+
+CONF_ITEM *cf_reference_item(CONF_SECTION const *parent_cs,
+                            CONF_SECTION const *outer_cs,
+                            char const *ptr)
+{
+       CONF_PAIR               *cp;
+       CONF_SECTION            *next;
+       CONF_SECTION const      *cs = outer_cs;
+       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 = cf_item_to_section(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 (!parent_cs) goto no_such_item;
+               cs = parent_cs;
+       }
+
+       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_find(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_find(cs, p, NULL);
+                       *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_find(cs, p, NULL);
+       if (next) return &(next->item);
+
+       /*
+        *      "foo" is "in the current section, OR in main".
+        */
+       if ((p == name) && (parent_cs != NULL) && (cs != parent_cs)) {
+               cs = parent_cs;
+               goto retry;
+       }
+
+no_such_item:
+       return NULL;
+}
diff --git a/src/main/cf_parse.c b/src/main/cf_parse.c
new file mode 100644 (file)
index 0000000..38d4a02
--- /dev/null
@@ -0,0 +1,1249 @@
+/*
+ *   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
+ */
+
+/**
+ * $Id$
+ * @file conf_parse.c
+ * @brief Covert internal format configuration values into native C types.
+ *
+ * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * @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 <string.h>
+
+#include <freeradius-devel/cf_parse.h>
+#include <freeradius-devel/cf_file.h>
+#include "cf_priv.h"
+
+#include <freeradius-devel/types.h>
+#include <freeradius-devel/inet.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/log.h>
+
+static CONF_PARSER conf_term = CONF_PARSER_TERMINATOR;
+static char const parse_spaces[] = "                                                                                                                                                                                                                                                                ";
+
+/** Validation function for ipaddr conf_file types
+ *
+ */
+static inline int fr_item_validate_ipaddr(CONF_SECTION *cs, char const *name, fr_type_t 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"), fr_inet_ntoh(ipaddr, ipbuf, sizeof(ipbuf)));
+       }
+
+       switch (type) {
+       case FR_TYPE_IPV4_ADDR:
+       case FR_TYPE_IPV6_ADDR:
+       case FR_TYPE_COMBO_IP_ADDR:
+               switch (ipaddr->af) {
+               case AF_INET:
+               if (ipaddr->prefix != 32) {
+                       ERROR("Invalid IPv4 mask length \"/%i\".  Only \"/32\" permitted for non-prefix types",
+                             ipaddr->prefix);
+
+                       return -1;
+               }
+                       break;
+
+               case AF_INET6:
+               if (ipaddr->prefix != 128) {
+                       ERROR("Invalid IPv6 mask length \"/%i\".  Only \"/128\" permitted for non-prefix types",
+                             ipaddr->prefix);
+
+                       return -1;
+               }
+                       break;
+
+               default:
+                       return -1;
+               }
+       default:
+               return 0;
+       }
+}
+
+/** Parses a #CONF_PAIR into a C data type
+ *
+ * @copybrief cf_pair_value
+ * @see cf_pair_value
+ *
+ * @param[out] out     Where to write the parsed value.
+ * @param[in] ctx      to allocate any dynamic buffers in.
+ * @param[in] cs       containing the cp.
+ * @param[in] cp       to parse.
+ * @param[in] type     to parse to.  May contain flags.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int cf_pair_parse_value(TALLOC_CTX *ctx, void *out, CONF_SECTION *cs, CONF_PAIR *cp, unsigned int type)
+{
+       int             rcode = 0;
+       bool            attribute, required, secret, file_input, cant_be_empty, tmpl, file_exists;
+
+       fr_ipaddr_t     *ipaddr;
+       ssize_t         slen;
+
+       if (!cs) return -1;
+
+       attribute = (type & FR_TYPE_ATTRIBUTE);
+       required = (type & FR_TYPE_REQUIRED);
+       secret = (type & FR_TYPE_SECRET);
+       file_input = (type == FR_TYPE_FILE_INPUT);      /* check, not and */
+       file_exists = (type == FR_TYPE_FILE_EXISTS);    /* check, not and */
+       cant_be_empty = (type & FR_TYPE_NOT_EMPTY);
+       tmpl = (type & FR_TYPE_TMPL);
+
+       rad_assert(cp);
+       rad_assert(!(type & FR_TYPE_ATTRIBUTE) || tmpl);         /* Attribute flag only valid for templates */
+
+       if (required) cant_be_empty = true;             /* May want to review this in the future... */
+
+       type = FR_BASE_TYPE(type);                      /* normal types are small */
+
+       /*
+        *      Everything except templates must have a base type.
+        */
+       if (!type && !tmpl) {
+               cf_log_err(cp, "Configuration pair \"%s\" must have a data type", cf_pair_attr(cp));
+               return -1;
+       }
+
+       rad_assert(cp->value);
+
+       /*
+        *      Check for zero length strings
+        */
+       if ((cp->value[0] == '\0') && cant_be_empty) {
+               cf_log_err(cp, "Configuration pair \"%s\" must not be empty (zero length)", cf_pair_attr(cp));
+               if (!required) cf_log_err(cp, "Comment item to silence this message");
+               rcode = -1;
+
+       error:
+               return rcode;
+       }
+
+       if (tmpl) {
+               vp_tmpl_t *vpt;
+
+               /*
+                *      This is so we produce TMPL_TYPE_ATTR_UNDEFINED template that
+                *      the bootstrap functions can use to create an attribute.
+                *
+                *      For other types of template such as xlats, we don't bother.
+                *      There's no reason bootstrap functions need access to the raw
+                *      xlat strings.
+                */
+               if (attribute) {
+                       slen = tmpl_afrom_attr_str(cp, &vpt, cp->value, REQUEST_CURRENT, PAIR_LIST_REQUEST,
+                                                  true, true);
+                       if (slen < 0) {
+                               char *spaces, *text;
+
+                               fr_canonicalize_error(ctx, &spaces, &text, slen, cp->value);
+
+                               cf_log_err(&cp->item, "Failed parsing attribute reference:");
+                               cf_log_err(&cp->item, "%s", text);
+                               cf_log_err(&cp->item, "%s^ %s", spaces, fr_strerror());
+
+                               talloc_free(spaces);
+                               talloc_free(text);
+                               goto error;
+                       }
+                       *(vp_tmpl_t **)out = vpt;
+               }
+               goto finish;
+       }
+
+       switch (type) {
+       case FR_TYPE_BOOL:
+               /*
+                *      Allow yes/no, true/false, and on/off
+                */
+               if ((strcasecmp(cp->value, "yes") == 0) ||
+                   (strcasecmp(cp->value, "true") == 0) ||
+                   (strcasecmp(cp->value, "on") == 0)) {
+                       *(bool *)out = true;
+               } else if ((strcasecmp(cp->value, "no") == 0) ||
+                          (strcasecmp(cp->value, "false") == 0) ||
+                          (strcasecmp(cp->value, "off") == 0)) {
+                       *(bool *)out = false;
+               } else {
+                       cf_log_err(&(cs->item), "Invalid value \"%s\" for boolean variable %s",
+                                  cp->value, cf_pair_attr(cp));
+                       rcode = -1;
+                       goto error;
+               }
+               cf_log_info(cs, "%.*s\t%s = %s", cs->depth, parse_spaces, cf_pair_attr(cp), cp->value);
+               break;
+
+       case FR_TYPE_UINT32:
+       {
+               unsigned long v = strtoul(cp->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", cp->value,
+                                  cf_pair_attr(cp), INT32_MAX);
+                       rcode = -1;
+                       goto error;
+               }
+
+               *(uint32_t *)out = v;
+               cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, cf_pair_attr(cp), *(uint32_t *)out);
+       }
+               break;
+
+       case FR_TYPE_UINT8:
+       {
+               unsigned long v = strtoul(cp->value, 0, 0);
+
+               if (v > UINT8_MAX) {
+                       cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", cp->value,
+                                  cf_pair_attr(cp), UINT8_MAX);
+                       rcode = -1;
+                       goto error;
+               }
+               *(uint8_t *)out = (uint8_t) v;
+               cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, cf_pair_attr(cp), *(uint8_t *)out);
+       }
+               break;
+
+       case FR_TYPE_UINT16:
+       {
+               unsigned long v = strtoul(cp->value, 0, 0);
+
+               if (v > UINT16_MAX) {
+                       cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", cp->value,
+                                  cf_pair_attr(cp), UINT16_MAX);
+                       rcode = -1;
+                       goto error;
+               }
+               *(uint16_t *)out = (uint16_t) v;
+               cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, cf_pair_attr(cp), *(uint16_t *)out);
+       }
+               break;
+
+       case FR_TYPE_UINT64:
+               *(uint64_t *)out = strtoull(cp->value, NULL, 10);
+               cf_log_info(cs, "%.*s\t%s = %" PRIu64, cs->depth, parse_spaces, cf_pair_attr(cp), *(uint64_t *)out);
+               break;
+
+       case FR_TYPE_SIZE:
+       {
+               if (fr_size_from_str((size_t *)out, cp->value) < 0) {
+                       cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s: %s", cp->value,
+                                  cf_pair_attr(cp), fr_strerror());
+                       rcode = -1;
+                       goto error;
+               }
+               cf_log_info(cs, "%.*s\t%s = %zu", cs->depth, parse_spaces, cf_pair_attr(cp), *(size_t *)out);
+               break;
+       }
+
+       case FR_TYPE_INT32:
+               *(int32_t *)out = strtol(cp->value, NULL, 10);
+               cf_log_info(cs, "%.*s\t%s = %d", cs->depth, parse_spaces, cf_pair_attr(cp), *(int32_t *)out);
+               break;
+
+       case FR_TYPE_STRING:
+       {
+               char **str = out;
+
+               /*
+                *      Hide secrets when using "radiusd -X".
+                */
+               if (secret && (rad_debug_lvl < L_DBG_LVL_3)) {
+                       cf_log_info(cs, "%.*s\t%s = <<< secret >>>", cs->depth, parse_spaces, cf_pair_attr(cp));
+               } else {
+                       cf_log_info(cs, "%.*s\t%s = \"%s\"", cs->depth, parse_spaces, cf_pair_attr(cp), cp->value);
+               }
+
+               /*
+                *      If there's out 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 (file_input && !cf_file_check(cs, cp->value, true)) {
+                       rcode = -1;
+                       goto error;
+               }
+
+               if (file_exists && !cf_file_check(cs, cp->value, false)) {
+                       rcode = -1;
+                       goto error;
+               }
+
+               /*
+                *      Free any existing buffers
+                */
+               talloc_free(*str);
+               *str = talloc_typed_strdup(cs, cp->value);
+       }
+               break;
+
+       case FR_TYPE_IPV4_ADDR:
+       case FR_TYPE_IPV4_PREFIX:
+               ipaddr = out;
+
+               if (fr_inet_pton4(ipaddr, cp->value, -1, true, false, true) < 0) {
+                       cf_log_err(&(cp->item), "%s", fr_strerror());
+                       rcode = -1;
+                       goto error;
+               }
+               /* Also prints the IP to the log */
+               if (fr_item_validate_ipaddr(cs, cf_pair_attr(cp), type, cp->value, ipaddr) < 0) {
+                       rcode = -1;
+                       goto error;
+               }
+               break;
+
+       case FR_TYPE_IPV6_ADDR:
+       case FR_TYPE_IPV6_PREFIX:
+               ipaddr = out;
+
+               if (fr_inet_pton6(ipaddr, cp->value, -1, true, false, true) < 0) {
+                       cf_log_err(&(cp->item), "%s", fr_strerror());
+                       rcode = -1;
+                       goto error;
+               }
+               /* Also prints the IP to the log */
+               if (fr_item_validate_ipaddr(cs, cf_pair_attr(cp), type, cp->value, ipaddr) < 0) {
+                       rcode = -1;
+                       goto error;
+               }
+               break;
+
+       case FR_TYPE_COMBO_IP_ADDR:
+       case FR_TYPE_COMBO_IP_PREFIX:
+               ipaddr = out;
+
+               if (fr_inet_pton(ipaddr, cp->value, -1, AF_UNSPEC, true, true) < 0) {
+                       cf_log_err(&(cp->item), "%s", fr_strerror());
+                       rcode = -1;
+                       goto error;
+               }
+               /* Also prints the IP to the log */
+               if (fr_item_validate_ipaddr(cs, cf_pair_attr(cp), type, cp->value, ipaddr) < 0) {
+                       rcode = -1;
+                       goto error;
+               }
+               break;
+
+       case FR_TYPE_TIMEVAL:
+       {
+               struct timeval tv;
+
+               if (fr_timeval_from_str(&tv, cp->value) < 0) {
+                       cf_log_err(&(cp->item), "%s", fr_strerror());
+                       rcode = -1;
+                       goto error;
+               }
+               cf_log_info(cs, "%.*s\t%s = %d.%06d", cs->depth, parse_spaces, cf_pair_attr(cp),
+                           (int)tv.tv_sec, (int)tv.tv_usec);
+               memcpy(out, &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 > FR_TYPE_INVALID);
+               rad_assert(type < FR_TYPE_MAX);
+
+               cf_log_err(&(cp->item), "type '%s' (%i) is not supported in the configuration files",
+                          fr_int2str(dict_attr_types, type, "?Unknown?"), type);
+               rcode = -1;
+               goto error;
+       }
+
+finish:
+       cp->parsed = true;
+
+       return rcode;
+}
+
+/** Allocate a pair using the dflt value and quotation
+ *
+ * The pair created by this function should fed to #cf_pair_parse for parsing.
+ *
+ * @param[out] out Where to write the CONF_PAIR we created with the default value.
+ * @param[in] cs to parent the CONF_PAIR from.
+ * @param[in] name of the CONF_PAIR to create.
+ * @param[in] type of conf item being parsed (determines default quoting).
+ * @param[in] dflt value to assign the CONF_PAIR.
+ * @param[in] dflt_quote surrounding the CONF_PAIR.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int cf_pair_default(CONF_PAIR **out, CONF_SECTION *cs, char const *name,
+                          int type, char const *dflt, FR_TOKEN dflt_quote)
+{
+       int             lineno = 0;
+       char const      *expanded;
+       CONF_PAIR       *cp;
+       char            buffer[8192];
+
+       rad_assert(dflt);
+
+       type = FR_BASE_TYPE(type);
+
+       /*
+        *      Defaults may need their values expanding
+        */
+       expanded = cf_expand_variables("<internal>", &lineno, cs, buffer, sizeof(buffer), dflt, NULL);
+       if (!expanded) {
+               cf_log_err(&(cs->item), "Failed expanding variable %s", name);
+               return -1;
+       }
+
+       /*
+        *      If no default quote was set, determine it from the type
+        */
+       if (dflt_quote == T_INVALID) {
+               switch (type) {
+               case FR_TYPE_STRING:
+                       dflt_quote = T_DOUBLE_QUOTED_STRING;
+                       break;
+
+               case FR_TYPE_FILE_INPUT:
+               case FR_TYPE_FILE_OUTPUT:
+                       dflt_quote = T_DOUBLE_QUOTED_STRING;
+                       break;
+
+               default:
+                       dflt_quote = T_BARE_WORD;
+                       break;
+               }
+       }
+
+       cp = cf_pair_alloc(cs, name, expanded, T_OP_EQ, T_BARE_WORD, dflt_quote);
+       if (!cp) return -1;
+
+       cp->parsed = true;
+
+       /*
+        *      Set the rcode to indicate we used a default value
+        */
+       *out = cp;
+
+       return 1;
+}
+
+/** 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 #fr_value_box_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.
+ *
+ * **fr_type_t to data type mappings**
+ * | fr_type_t               | Data type          | Dynamically allocated  |
+ * | ----------------------- | ------------------ | ---------------------- |
+ * | FR_TYPE_TMPL            | ``vp_tmpl_t``      | Yes                    |
+ * | FR_TYPE_BOOL            | ``bool``           | No                     |
+ * | FR_TYPE_UINT32          | ``uint32_t``       | No                     |
+ * | FR_TYPE_UINT16          | ``uint16_t``       | No                     |
+ * | FR_TYPE_UINT64          | ``uint64_t``       | No                     |
+ * | FR_TYPE_INT32           | ``int32_t``        | No                     |
+ * | FR_TYPE_STRING          | ``char const *``   | Yes                    |
+ * | FR_TYPE_IPV4_ADDR       | ``fr_ipaddr_t``    | No                     |
+ * | FR_TYPE_IPV4_PREFIX     | ``fr_ipaddr_t``    | No                     |
+ * | FR_TYPE_IPV6_ADDR       | ``fr_ipaddr_t``    | No                     |
+ * | FR_TYPE_IPV6_PREFIX     | ``fr_ipaddr_t``    | No                     |
+ * | FR_TYPE_COMBO_IP_ADDR   | ``fr_ipaddr_t``    | No                     |
+ * | FR_TYPE_COMBO_IP_PREFIX | ``fr_ipaddr_t``    | No                     |
+ * | FR_TYPE_TIMEVAL         | ``struct timeval`` | No                     |
+ *
+ * @param[in] ctx      To allocate arrays and values in.
+ * @param[in] cs       to search for matching #CONF_PAIR in.
+ * @param[in] name     of #CONF_PAIR to search for.
+ * @param[in] 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`` #FR_TYPE_TMPL                - @copybrief FR_TYPE_TMPL
+ *                                               Feeds the value into #tmpl_afrom_str. Value can be
+ *                                               obtained when processing requests, with #tmpl_expand or #tmpl_aexpand.
+ *     - ``data`` #FR_TYPE_BOOL                - @copybrief FR_TYPE_BOOL
+ *     - ``data`` #FR_TYPE_UINT32              - @copybrief FR_TYPE_UINT32
+ *     - ``data`` #FR_TYPE_UINT16              - @copybrief FR_TYPE_UINT16
+ *     - ``data`` #FR_TYPE_UINT64              - @copybrief FR_TYPE_UINT64
+ *     - ``data`` #FR_TYPE_INT32               - @copybrief FR_TYPE_INT32
+ *     - ``data`` #FR_TYPE_STRING              - @copybrief FR_TYPE_STRING
+ *     - ``data`` #FR_TYPE_IPV4_ADDR           - @copybrief FR_TYPE_IPV4_ADDR (IPv4 address with prefix 32).
+ *     - ``data`` #FR_TYPE_IPV4_PREFIX         - @copybrief FR_TYPE_IPV4_PREFIX (IPv4 address with variable prefix).
+ *     - ``data`` #FR_TYPE_IPV6_ADDR           - @copybrief FR_TYPE_IPV6_ADDR (IPv6 address with prefix 128).
+ *     - ``data`` #FR_TYPE_IPV6_PREFIX         - @copybrief FR_TYPE_IPV6_PREFIX (IPv6 address with variable prefix).
+ *     - ``data`` #FR_TYPE_COMBO_IP_ADDR       - @copybrief FR_TYPE_COMBO_IP_ADDR (IPv4/IPv6 address with
+ *                                               prefix 32/128).
+ *     - ``data`` #FR_TYPE_COMBO_IP_PREFIX     - @copybrief FR_TYPE_COMBO_IP_PREFIX (IPv4/IPv6 address with
+ *                                               variable prefix).
+ *     - ``data`` #FR_TYPE_TIMEVAL             - @copybrief FR_TYPE_TIMEVAL
+ *     - ``flag`` #FR_TYPE_DEPRECATED          - @copybrief FR_TYPE_DEPRECATED
+ *     - ``flag`` #FR_TYPE_REQUIRED            - @copybrief FR_TYPE_REQUIRED
+ *     - ``flag`` #FR_TYPE_ATTRIBUTE           - @copybrief FR_TYPE_ATTRIBUTE
+ *     - ``flag`` #FR_TYPE_SECRET              - @copybrief FR_TYPE_SECRET
+ *     - ``flag`` #FR_TYPE_FILE_INPUT          - @copybrief FR_TYPE_FILE_INPUT
+ *     - ``flag`` #FR_TYPE_NOT_EMPTY           - @copybrief FR_TYPE_NOT_EMPTY
+ *     - ``flag`` #FR_TYPE_MULTI               - @copybrief FR_TYPE_MULTI
+ *     - ``flag`` #FR_TYPE_IS_SET              - @copybrief FR_TYPE_IS_SET
+ * @param[out] out     Pointer to a global variable, or pointer to a field in the struct being populated with values.
+ * @param[in] dflt             value to use, if no #CONF_PAIR is found.
+ * @param[in] dflt_quote       around the dflt value.
+ * @return
+ *     - 1 if default value was used, or if there was no CONF_PAIR or dflt.
+ *     - 0 on success.
+ *     - -1 on error.
+ *     - -2 if deprecated.
+ */
+int cf_pair_parse(TALLOC_CTX *ctx, CONF_SECTION *cs,
+                 char const *name, unsigned int type, void *out,
+                 char const *dflt, FR_TOKEN dflt_quote)
+{
+       bool            multi, required, deprecated;
+       size_t          count = 0;
+       CONF_PAIR       *cp, *dflt_cp = NULL;
+
+       rad_assert(!(type & FR_TYPE_TMPL) || !dflt || (dflt_quote != T_INVALID)); /* We ALWAYS need a quoting type for templates */
+
+       multi = (type & FR_TYPE_MULTI);
+       required = (type & FR_TYPE_REQUIRED);
+       deprecated = (type & FR_TYPE_DEPRECATED);
+
+       /*
+        *      If the item is multi-valued we allocate an array
+        *      to hold the multiple values.
+        */
+       if (multi) {
+               CONF_PAIR       *first;
+               void            **array;
+               size_t          i;
+
+               /*
+                *      Easier than re-allocing
+                */
+               for (cp = first = cf_pair_find(cs, name);
+                    cp;
+                    cp = cf_pair_find_next(cs, cp, name)) count++;
+
+               /*
+                *      Multivalued, but there's no value, create a
+                *      default pair.
+                */
+               if (!count) {
+                       if (deprecated) return 0;
+                       if (!dflt) {
+                               if (required) {
+                       need_value:
+                                       cf_log_err(cs, "Configuration item \"%s\" must have a value", name);
+                                       return -1;
+                               }
+                               return 1;
+                       }
+
+                       if (cf_pair_default(&dflt_cp, cs, name, type, dflt, dflt_quote) < 0) return -1;
+                       cp = dflt_cp;
+                       count = 1;      /* Need one to hold the default */
+               } else {
+                       cp = first;     /* reset */
+               }
+
+               if (deprecated) {
+               deprecated:
+                       cf_log_err(cp, "Configuration pair \"%s\" is deprecated", cf_pair_attr(cp));
+                       return -2;
+               }
+
+               /*
+                *      Tmpl is outside normal range
+                */
+               if (type & FR_TYPE_TMPL) {
+                       array = (void **)talloc_zero_array(ctx, vp_tmpl_t *, count);
+               /*
+                *      Allocate an array of values.
+                *
+                *      We don't NULL terminate.  Consumer must use
+                *      talloc_array_length().
+                */
+               } else switch (FR_BASE_TYPE(type)) {
+               case FR_TYPE_BOOL:
+                       array = (void **)talloc_zero_array(ctx, bool, count);
+                       break;
+
+               case FR_TYPE_UINT32:
+                       array = (void **)talloc_zero_array(ctx, uint32_t, count);
+                       break;
+
+               case FR_TYPE_UINT16:
+                       array = (void **)talloc_zero_array(ctx, uint16_t, count);
+                       break;
+
+               case FR_TYPE_UINT64:
+                       array = (void **)talloc_zero_array(ctx, uint64_t, count);
+                       break;
+
+               case FR_TYPE_INT32:
+                       array = (void **)talloc_zero_array(ctx, int32_t, count);
+                       break;
+
+               case FR_TYPE_STRING:
+                       array = (void **)talloc_zero_array(ctx, char *, count);
+                       break;
+
+               case FR_TYPE_IPV4_ADDR:
+               case FR_TYPE_IPV4_PREFIX:
+               case FR_TYPE_IPV6_ADDR:
+               case FR_TYPE_IPV6_PREFIX:
+               case FR_TYPE_COMBO_IP_ADDR:
+               case FR_TYPE_COMBO_IP_PREFIX:
+                       array = (void **)talloc_zero_array(ctx, fr_ipaddr_t, count);
+                       break;
+
+               case FR_TYPE_TIMEVAL:
+                       array = (void **)talloc_zero_array(ctx, struct timeval, count);
+                       break;
+
+               default:
+                       rad_assert(0);  /* Unsupported type */
+                       return -1;
+               }
+
+               for (i = 0; i < count; i++, cp = cf_pair_find_next(cs, cp, name)) {
+                       if (cf_pair_parse_value(array, &array[i], cs, cp, type) < 0) {
+                               talloc_free(array);
+                               talloc_free(dflt_cp);
+                               return -1;
+                       }
+               }
+
+               *(void **)out = array;
+       /*
+        *      Single valued config item gets written to
+        *      the data pointer directly.
+        */
+       } else {
+               CONF_PAIR *next;
+
+               cp = cf_pair_find(cs, name);
+               if (!cp) {
+                       if (deprecated) return 0;
+                       if (!dflt) {
+                               if (required) goto need_value;
+                               return 1;
+                       }
+
+                       if (cf_pair_default(&dflt_cp, cs, name, type, dflt, dflt_quote) < 0) return -1;
+                       cp = dflt_cp;
+               }
+
+               next = cf_pair_find_next(cs, cp, name);
+               if (next) {
+                       cf_log_err(&(next->item), "Invalid duplicate configuration item '%s'", name);
+                       return -1;
+               }
+
+               if (deprecated) goto deprecated;
+
+               if (cf_pair_parse_value(ctx, out, cs, cp, type) < 0) {
+                       talloc_free(dflt_cp);
+                       return -1;
+               }
+       }
+
+       /*
+        *      If we created a default cp and succeeded
+        *      in parsing the dflt value, add the new
+        *      cp to the enclosing section.
+        */
+       if (dflt_cp) {
+               cf_item_add(cs, &(dflt_cp->item));
+               return 1;
+       }
+
+       return 0;
+}
+
+/** Pre-allocate a config section structure to allow defaults to be set
+ *
+ * @param cs           The parent subsection.
+ * @param base         pointer or variable.
+ * @param variables    that may have defaults in this config section.
+ */
+static int cf_section_parse_init(CONF_SECTION *cs, void *base, CONF_PARSER const *variables)
+{
+       int i;
+
+       for (i = 0; variables[i].name != NULL; i++) {
+               if ((FR_BASE_TYPE(variables[i].type) == FR_TYPE_SUBSECTION)) {
+                       CONF_SECTION *subcs;
+
+                       if (!variables[i].dflt) continue;
+
+                       subcs = cf_section_find(cs, variables[i].name, NULL);
+                       if (!subcs && (variables[i].type & FR_TYPE_REQUIRED)) {
+                               cf_log_err(cs, "Missing %s {} subsection", variables[i].name);
+                               return -1;
+                       }
+
+                       /*
+                        *      Set the is_set field for the subsection.
+                        */
+                       if (variables[i].type & FR_TYPE_IS_SET) {
+                               bool *is_set;
+
+                               is_set = variables[i].data ? variables[i].is_set_ptr :
+                                                            ((uint8_t *)base) + variables[i].is_set_offset;
+                               if (is_set) *is_set = !!subcs;
+                       }
+
+                       /*
+                        *      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 -1;
+
+                               cf_item_add(cs, &(subcs->item));
+                       }
+
+                       continue;
+               }
+
+               if ((FR_BASE_TYPE(variables[i].type) != FR_TYPE_STRING) &&
+                   (variables[i].type != FR_TYPE_FILE_INPUT) &&
+                   (variables[i].type != FR_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 */
+
+       return 0;
+}
+
+static void cf_section_parse_warn(CONF_SECTION *cs)
+{
+       CONF_ITEM *ci;
+
+       for (ci = cs->item.child; 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 || (ci->lineno < 0)) continue;
+
+                       WARN("%s[%d]: The item '%s' is defined, but is unused by the configuration",
+                            ci->filename, ci->lineno,
+                            cp->attr);
+               }
+
+               /*
+                *      Skip everything else.
+                */
+       }
+}
+
+/** Parse a subsection
+ *
+ * @note Turns out using nested structures (instead of pointers) for subsections, was actually
+ *     a pretty bad design decision, and will need to be fixed at some future point.
+ *     For now we have a horrible hack where only multi-subsections get an array of structures
+ *     of the appropriate size.
+ *
+ * @param[in] ctx              to allocate any additional structures under.
+ * @param[out] out             pointer to a struct/pointer to fill with data.
+ * @param[in] cs               to parse.
+ * @param[in] name             of subsection to parse.
+ * @param[in] type             flags.
+ * @param[in] subcs_vars       CONF_PARSER definitions for the subsection.
+ * @param[in] subcs_size       size of subsection structures to allocate.
+ * @return
+ *     - 0 on success.
+ *     - -1 on general error.
+ *     - -2 if a deprecated #CONF_ITEM was found.
+ */
+static int cf_subsection_parse(TALLOC_CTX *ctx, void *out, CONF_SECTION *cs,
+                              char const *name, fr_type_t type, CONF_PARSER const *subcs_vars, size_t subcs_size)
+{
+       CONF_SECTION    *subcs;
+       int             count, i, ret;
+       uint8_t         **array;
+
+       rad_assert(type & FR_TYPE_SUBSECTION);
+
+       subcs = cf_section_find(cs, name, NULL);
+       if (!subcs) return 0;
+
+       /*
+        *      Handle the single subsection case (which is simple)
+        */
+       if (!(type & FR_TYPE_MULTI)) {
+               uint8_t *buff;
+
+               /*
+                *      FIXME: We shouldn't allow nested structures like this.
+                *      Each subsection struct should be allocated separately so
+                *      we have a clean talloc hierarchy.
+                */
+               if (!subcs_size) return cf_section_parse(ctx, out, subcs, subcs_vars);
+
+               MEM(buff = talloc_array(ctx, uint8_t, subcs_size));
+               ret = cf_section_parse(buff, buff, subcs, subcs_vars);
+               if (ret < 0) {
+                       talloc_free(buff);
+                       return -1;
+               }
+
+               *((uint8_t **)out) = buff;
+       }
+
+       rad_assert(subcs_size);
+
+       /*
+        *      Handle the multi subsection case (which is harder)
+        */
+       for (subcs = cf_section_find(cs, name, NULL), count = 0;
+            subcs;
+            subcs = cf_section_find_next(cs, subcs, name, NULL), count++);
+
+       /*
+        *      Allocate an array to hold the subsections
+        */
+       MEM(array = talloc_array(ctx, uint8_t *, count));
+
+       /*
+        *      Start parsing...
+        *
+        *      Note, we allocate each subsection structure individually
+        *      so that they can be used as talloc contexts and we can
+        *      keep the talloc hierarchy clean.
+        */
+       for (subcs = cf_section_find(cs, name, NULL), i = 0;
+            subcs;
+            subcs = cf_section_find_next(cs, subcs, name, NULL), i++) {
+               uint8_t *buff;
+
+               MEM(buff = talloc_zero_array(array, uint8_t, subcs_size));
+               array[i] = buff;
+
+               ret = cf_section_parse(buff, buff, subcs, subcs_vars);
+               if (ret < 0) {
+                       talloc_free(array);
+                       return ret;
+               }
+       }
+
+       *((uint8_t ***)out) = array;
+
+       return 0;
+}
+
+/** Parse a configuration section into user-supplied variables
+ *
+ * @param[in] ctx              to allocate any strings, or additional structures in.
+ *                             Usually the same as base, unless base is a nested struct.
+ * @param[out] base            pointer to a struct to fill with data.
+ * @param[in] cs               to parse.
+ * @param[in] 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(TALLOC_CTX *ctx, void *base, CONF_SECTION *cs, CONF_PARSER const *variables)
+{
+       int     ret = 0;
+       int     i;
+       void    *data;
+       bool    *is_set = NULL;
+
+       /*
+        *      Hack for partially parsed sections.
+        */
+       if (!variables) {
+               cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
+               return 0;
+       }
+
+       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);
+       }
+
+       if (cf_section_parse_init(cs, base, variables) < 0) return -1;
+
+       /*
+        *      Handle the known configuration parameters.
+        */
+       for (i = 0; variables[i].name != NULL; i++) {
+               /*
+                *      Handle subsections specially
+                */
+               if (FR_BASE_TYPE(variables[i].type) == FR_TYPE_SUBSECTION) {
+                       if (cf_subsection_parse(ctx, (uint8_t *)base + variables[i].offset, cs,
+                                               variables[i].name, variables[i].type,
+                                               variables[i].subcs, variables[i].subcs_size) < 0) goto finish;
+                       continue;
+               } /* else it's a CONF_PAIR */
+
+               if (variables[i].data) {
+                       data = variables[i].data; /* prefer this. */
+               } else if (base) {
+                       data = ((uint8_t *)base) + variables[i].offset;
+               } else if (!rad_cond_assert(0)) {
+                       ret = -1;
+                       goto finish;
+               }
+
+               /*
+                *      Get pointer to where we need to write out
+                *      whether the pointer was set.
+                */
+               if (variables[i].type & FR_TYPE_IS_SET) {
+                       is_set = variables[i].data ? variables[i].is_set_ptr :
+                                                    ((uint8_t *)base) + variables[i].is_set_offset;
+               }
+
+               /*
+                *      Parse the pair we found, or a default value.
+                */
+               ret = cf_pair_parse(ctx, cs, variables[i].name, variables[i].type, data,
+                                   variables[i].dflt, variables[i].quote);
+               switch (ret) {
+               case 1:         /* Used default (or not present) */
+                       if (is_set) *is_set = false;
+                       ret = 0;
+                       break;
+
+               case 0:         /* OK */
+                       if (is_set) *is_set = true;
+                       break;
+
+               case -1:        /* Parse error */
+                       goto finish;
+
+               case -2:        /* Deprecated CONF ITEM */
+                       if ((variables[i + 1].offset && (variables[i + 1].offset == variables[i].offset)) ||
+                           (variables[i + 1].data && (variables[i + 1].data == variables[i].data))) {
+                               cf_log_err(&(cs->item), "Replace \"%s\" with \"%s\"", variables[i].name,
+                                          variables[i + 1].name);
+                       }
+                       goto finish;
+               }
+       } /* for all variables in the configuration section */
+
+       /*
+        *      Ensure we have a proper terminator, type so we catch
+        *      missing terminators reliably
+        */
+       rad_cond_assert(variables[i].type == conf_term.type);
+
+       cs->base = base;
+
+       /*
+        *      Hack for partially parsed sections.  We don't print
+        *      out the final "}", that will be printed out when the
+        *      caller re-calls us with 'variable=NULL'.  And, we don't warn about unused
+        */
+       if (variables[i].offset == 1) return ret;
+
+       /*
+        *      Warn about items in the configuration which weren't
+        *      checked during parsing.
+        */
+       if (rad_debug_lvl >= 3) cf_section_parse_warn(cs);
+
+       cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
+
+finish:
+       return ret;
+}
+
+/** Fixup xlat expansions and attributes
+ *
+ * @note Despite the name, this is really the second phase of #cf_pair_parse.
+ *
+ * @param[out] base start of structure to write #vp_tmpl_t s to.
+ * @param[in] cs CONF_SECTION to fixup.
+ * @param[in] variables Array of CONF_PARSER structs to process.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure (parse errors etc...).
+ */
+int cf_section_parse_pass2(void *base, CONF_SECTION *cs, CONF_PARSER const variables[])
+{
+
+       int i;
+
+       /*
+        *      Handle the known configuration parameters.
+        */
+       for (i = 0; variables[i].name != NULL; i++) {
+               bool            attribute, multi, is_tmpl, is_xlat;
+               CONF_PAIR       *cp;
+               void            *data;
+
+               char const      *name = variables[i].name;
+               int             type = variables[i].type;
+
+               is_tmpl = (type & FR_TYPE_TMPL);
+               is_xlat = (type & FR_TYPE_XLAT);
+               attribute = (type & FR_TYPE_ATTRIBUTE);
+               multi = (type & FR_TYPE_MULTI);
+
+               type = FR_BASE_TYPE(type);              /* normal types are small */
+
+               /*
+                *      It's a section, recurse!
+                */
+               if (type == FR_TYPE_SUBSECTION) {
+                       uint8_t         *subcs_base;
+                       CONF_SECTION    *subcs = cf_section_find(cs, name, NULL);
+
+                       /*
+                        *      Select base by whether this is a nested struct,
+                        *      or a pointer to another struct.
+                        */
+                       if (!base) {
+                               subcs_base = NULL;
+                       } else if (multi) {
+                               size_t          j, len;
+                               uint8_t         **array;
+
+                               array = (uint8_t **)((uint8_t *)base) + variables[i].offset;
+                               len = talloc_array_length(array);
+
+                               for (j = 0; j < len; j++) {
+                                       if (cf_section_parse_pass2(array[j], subcs,
+                                                                  (CONF_PARSER const *)variables[i].dflt) < 0) {
+                                               return -1;
+                                       }
+                               }
+                               continue;
+                       } else if (variables[i].subcs_size) {
+                               subcs_base = (*(uint8_t **)((uint8_t *)base) + variables[i].offset);
+                       } else {
+                               subcs_base = (uint8_t *)base + variables[i].offset;
+                       }
+
+                       if (cf_section_parse_pass2(subcs_base, subcs,
+                                                  (CONF_PARSER const *)variables[i].dflt) < 0) return -1;
+
+                       continue;
+               }
+
+               /*
+                *      Find the CONF_PAIR, may still not exist if there was
+                *      no default set for the CONF_PARSER.
+                */
+               cp = cf_pair_find(cs, name);
+               if (!cp) continue;
+
+               /*
+                *      Figure out which data we need to fix.
+                */
+               data = variables[i].data; /* prefer this. */
+               if (!data && base) data = ((char *)base) + variables[i].offset;
+               if (!data) continue;
+
+               /*
+                *      Non-xlat expansions shouldn't have xlat!
+                */
+               if (!is_xlat && !is_tmpl) {
+                       /*
+                        *      Ignore %{... in shared secrets.
+                        *      They're never dynamically expanded.
+                        */
+                       if ((variables[i].type & FR_TYPE_SECRET) != 0) continue;
+
+                       if (strstr(cp->value, "%{") != NULL) {
+                               cf_log_err(&cp->item, "Found dynamic expansion in string which "
+                                          "will not be dynamically expanded");
+                               return -1;
+                       }
+                       continue;
+               }
+
+               /*
+                *      Parse (and throw away) the xlat string (for validation).
+                *
+                *      FIXME: All of these should be converted from FR_TYPE_XLAT
+                *      to FR_TYPE_TMPL.
+                */
+               if (is_xlat) {
+                       char const      *error;
+                       ssize_t         slen;
+                       char            *value;
+                       xlat_exp_t      *xlat;
+
+               redo:
+                       xlat = NULL;
+
+                       /*
+                        *      xlat expansions should be parseable.
+                        */
+                       value = talloc_strdup(cs, cp->value); /* modified by xlat_tokenize */
+                       slen = xlat_tokenize(cs, value, &xlat, &error);
+                       if (slen < 0) {
+                               char *spaces, *text;
+
+                               fr_canonicalize_error(cs, &spaces, &text, slen, cp->value);
+
+                               cf_log_err(cp, "Failed parsing expanded string:");
+                               cf_log_err(cp, "%s", text);
+                               cf_log_err(cp, "%s^ %s", spaces, error);
+
+                               talloc_free(spaces);
+                               talloc_free(text);
+                               talloc_free(value);
+                               talloc_free(xlat);
+                               return -1;
+                       }
+
+                       talloc_free(value);
+                       talloc_free(xlat);
+
+                       /*
+                        *      If the "multi" flag is set, check all of them.
+                        */
+                       if (multi) {
+                               cp = cf_pair_find_next(cs, cp, cp->attr);
+                               if (cp) goto redo;
+                       }
+                       continue;
+
+               /*
+                *      Parse the pair into a template
+                */
+               } else if (is_tmpl) {
+                       ssize_t slen;
+
+                       vp_tmpl_t **out = (vp_tmpl_t **)data;
+                       vp_tmpl_t *vpt;
+
+                       slen = tmpl_afrom_str(cs, &vpt, cp->value, talloc_array_length(cp->value) - 1,
+                                             cf_pair_value_quote(cp),
+                                             REQUEST_CURRENT, PAIR_LIST_REQUEST, true);
+                       if (slen < 0) {
+                               char *spaces, *text;
+
+                               fr_canonicalize_error(vpt, &spaces, &text, slen, cp->value);
+
+                               cf_log_err(cp, "%s", text);
+                               cf_log_err(cp, "%s^ %s", spaces, fr_strerror());
+
+                               talloc_free(spaces);
+                               talloc_free(text);
+                               return -1;
+                       }
+
+                       if (attribute && (vpt->type != TMPL_TYPE_ATTR)) {
+                               cf_log_err(&cp->item, "Expected attr got %s",
+                                          fr_int2str(tmpl_names, vpt->type, "???"));
+                               return -1;
+                       }
+
+                       switch (vpt->type) {
+                       /*
+                        *      All attributes should have been defined by this point.
+                        */
+                       case TMPL_TYPE_ATTR_UNDEFINED:
+                               talloc_free(vpt);
+                               cf_log_err(&cp->item, "Unknown attribute '%s'", vpt->tmpl_unknown_name);
+                               return -1;
+
+                       case TMPL_TYPE_UNPARSED:
+                       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);
+                       /* Don't add default */
+                       }
+
+                       /*
+                        *      Free the old value if we're overwriting
+                        */
+                       TALLOC_FREE(*out);
+                       *(vp_tmpl_t **)out = vpt;
+               }
+       } /* for all variables in the configuration section */
+
+       return 0;
+}
+
+const CONF_PARSER *cf_section_parse_table(CONF_SECTION *cs)
+{
+       if (!cs) return NULL;
+
+       return cs->variables;
+}
diff --git a/src/main/cf_priv.h b/src/main/cf_priv.h
new file mode 100644 (file)
index 0000000..371e7eb
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ *  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
+ */
+#ifndef _FR_CF_PRIV_H
+#define _FR_CF_PRIV_H
+/**
+ * $Id$
+ *
+ * @file include/cf_priv.h
+ * @brief Private data structures and types for cf_*.c
+ *
+ * @copyright 2017  The FreeRADIUS server project
+ */
+RCSIDH(cf_priv_h, "$Id$")
+
+#include <stdint.h>
+#include <sys/stat.h>
+
+#include <freeradius-devel/cf_parse.h>
+#include <freeradius-devel/rbtree.h>
+#include <freeradius-devel/cursor.h>
+
+typedef enum conf_type {
+       CONF_ITEM_INVALID = 0,
+       CONF_ITEM_PAIR,
+       CONF_ITEM_SECTION,
+       CONF_ITEM_DATA,
+#ifdef WITH_CONF_WRITE
+       CONF_ITEM_COMMENT,
+       CONF_ITEM_INCLUDE
+#endif
+} CONF_ITEM_TYPE;
+
+/** Common header for all CONF_* types
+ *
+ */
+struct cf_item {
+       CONF_ITEM_TYPE          type;           //!< Whether the config item is a config_pair, conf_section or cf_data.
+
+       CONF_ITEM               *next;          //!< Sibling.
+       CONF_ITEM               *parent;        //!< Parent.
+
+       CONF_ITEM               *child;         //!< The head of the ordered list of children.
+       fr_cursor_t             cursor;         //!< Cursor to iterate over children.  Maintains a 'tail' pointer for
+                                               //!< efficient insertion.
+
+       rbtree_t                *ident1;        //!< Tree to store the first identifier (name1 || type || attr).
+       rbtree_t                *ident2;        //!< Tree to store the second identifier (name2 || name).
+
+       int                     lineno;         //!< The line number the config item began on.
+       char const              *filename;      //!< The file the config item was parsed from.
+};
+
+/** Configuration AVP similar to a VALUE_PAIR
+ *
+ */
+struct cf_pair {
+       CONF_ITEM               item;           //!< Common set of fields.
+
+       char const              *attr;          //!< Attribute name
+#ifdef WITH_CONF_WRITE
+       char const              *orig_value;    /* original value */
+#endif
+       char const              *value;         //!< Attribute value
+
+       FR_TOKEN                op;             //!< Operator e.g. =, :=
+       FR_TOKEN                lhs_quote;      //!< Name quoting style T_(DOUBLE|SINGLE|BACK)_QUOTE_STRING or T_BARE_WORD.
+       FR_TOKEN                rhs_quote;      //!< 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?
+};
+
+/** A section grouping multiple #CONF_PAIR
+ *
+ */
+struct cf_section {
+       CONF_ITEM               item;           //!< Common set of fields.
+
+       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_quote;    //!< The type of quoting around name2.
+
+       int                     argc;           //!< number of additional arguments
+       char const              **argv;         //!< additional arguments
+       FR_TOKEN                *argv_quote;
+
+       void                    *base;
+       int                     depth;
+
+       CONF_SECTION            *template;
+       CONF_PARSER const       *variables;     //!< the section was parsed with.
+};
+
+/** Internal data that is associated with a configuration section
+ *
+ */
+struct cf_data {
+       CONF_ITEM               item;           //!< Common set of fields.
+
+       char const              *type;          //!< C type of data being stored.
+       char const              *name;          //!< Additional qualification of type.
+
+       void const              *data;          //!< User data.
+       bool                    free;           //!< If true, free data with talloc if parent node is freed.
+};
+
+typedef enum cf_include_type {
+       CONF_INCLUDE_FILE,
+       CONF_INCLUDE_DIR,
+       CONF_INCLUDE_FROMDIR,
+} CONF_INCLUDE_TYPE;
+
+#ifdef WITH_CONF_WRITE
+typedef struct conf_comment {
+       CONF_ITEM               item;
+       char const              *comment;
+} CONF_COMMENT;
+
+typedef struct conf_include {
+       CONF_ITEM               item;
+       char const              *filename;
+       CONF_INCLUDE_TYPE       file_type;
+} CONF_INCLUDE;
+#endif
+
+typedef struct cf_file_t {
+       char const              *filename;
+       CONF_SECTION            *cs;
+       struct stat             buf;
+} cf_file_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FR_CF_PRIV_H */
diff --git a/src/main/cf_util.c b/src/main/cf_util.c
new file mode 100644 (file)
index 0000000..b79770c
--- /dev/null
@@ -0,0 +1,1747 @@
+/*
+ *   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
+ */
+
+/**
+ * $Id$
+ * @file cf_util.c
+ * @brief Functions for building and managing the structure of internal format config items.
+ *
+ * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+RCSID("$Id$")
+
+#include <string.h>
+
+#include <freeradius-devel/cf_util.h>
+#include "cf_priv.h"
+
+#include <freeradius-devel/cursor.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include <freeradius-devel/radiusd.h>
+
+char const *CF_IDENT_ANY = "<any>";
+
+static inline int cf_ident2_cmp(void const *a, void const *b);
+static int _cf_ident1_cmp(void const *a, void const *b);
+static int _cf_ident2_cmp(void const *a, void const *b);
+
+/** Return the next child that's of the specified type
+ *
+ * @param[in] parent   to return children from.
+ * @param[in] prev     child to start searching from.
+ * @param[in] type     to search for.
+ * @return
+ *     - The next #CONF_ITEM that's a child of ci matching type.
+ *     - NULL if no #CONF_ITEM matches that criteria.
+ */
+static CONF_ITEM *cf_next(CONF_ITEM const *parent, CONF_ITEM const *prev, CONF_ITEM_TYPE type)
+{
+       CONF_ITEM *ci;
+
+       for (ci = prev ? prev->next : parent->child;
+            ci;
+            ci = ci->next) {
+               if (ci->type == type) return ci;
+       }
+
+       return NULL;
+}
+
+/** Return the next child that's of the specified type with the specified identifiers
+ *
+ * @param[in] parent   The section we're searching in.
+ * @param[in] type     of #CONF_ITEM we're searching for.
+ * @param[in] ident1   The first identifier.
+ * @param[in] ident2   The second identifier. Special value CF_IDENT_ANY
+ *                     can be used to match any ident2 value.
+ * @return
+ *     - The first matching item.
+ *     - NULL if no items matched.
+ */
+static CONF_ITEM *cf_find(CONF_ITEM const *parent, CONF_ITEM_TYPE type, char const *ident1, char const *ident2)
+{
+       CONF_SECTION    cs_find;
+       CONF_PAIR       cp_find;
+       CONF_DATA       cd_find;
+       CONF_ITEM       *find;
+
+       if (!parent) return NULL;
+       if (!parent->child) return NULL;        /* No children */
+
+       if (!ident1) return cf_next(parent, NULL, type);
+
+       switch (type) {
+       case CONF_ITEM_SECTION:
+               memset(&cs_find, 0, sizeof(cs_find));
+               cs_find.item.type = CONF_ITEM_SECTION;
+               cs_find.name1 = ident1;
+               if (ident2 != CF_IDENT_ANY) cs_find.name2 = ident2;
+
+               find = (CONF_ITEM *)&cs_find;
+               break;
+
+       case CONF_ITEM_PAIR:
+               rad_assert((ident2 == NULL) || (ident2 == CF_IDENT_ANY));
+
+               memset(&cp_find, 0, sizeof(cp_find));
+               cp_find.item.type = CONF_ITEM_PAIR;
+               cp_find.attr = ident1;
+
+               find = (CONF_ITEM *)&cp_find;
+               break;
+
+       case CONF_ITEM_DATA:
+               memset(&cd_find, 0, sizeof(cd_find));
+               cd_find.item.type = CONF_ITEM_DATA;
+               cd_find.type = ident1;
+               if (ident2 != CF_IDENT_ANY) cd_find.name = ident2;
+
+               find = (CONF_ITEM *)&cd_find;
+               break;
+
+       default:
+               rad_assert(0);
+       }
+
+       /*
+        *      No ident1, iterate over the child list
+        */
+       if (ident1 == CF_IDENT_ANY) {
+               CONF_ITEM *ci;
+
+               for (ci = parent->child;
+                    ci && (cf_ident2_cmp(find, ci) != 0);
+                    ci = ci->next);
+
+               return ci;
+       }
+
+       /*
+        *      No ident2, use the ident1 tree.
+        */
+       if (ident2 == CF_IDENT_ANY) return rbtree_finddata(parent->ident1, find);
+
+       /*
+        *      Both ident1 and ident2 use the ident2 tree.
+        */
+       return rbtree_finddata(parent->ident2, find);
+}
+
+/** Return the next child that's of the specified type with the specified identifiers
+ *
+ * @param[in] parent   The section we're searching in.
+ * @param[in] prev     item we found, or NULL to start from the beginning.
+ * @param[in] type     of #CONF_ITEM we're searching for.
+ * @param[in] ident1   The first identifier.
+ * @param[in] ident2   The second identifier. Special value CF_IDENT_ANY
+ *                     can be used to match any ident2 value.
+ * @return
+ *     - The first matching item.
+ *     - NULL if no items matched.
+ */
+static CONF_ITEM *cf_find_next(CONF_ITEM const *parent, CONF_ITEM const *prev,
+                              CONF_ITEM_TYPE type, char const *ident1, char const *ident2)
+{
+       CONF_SECTION    cs_find;
+       CONF_PAIR       cp_find;
+       CONF_DATA       cd_find;
+       CONF_ITEM       *find;
+       CONF_ITEM       *ci;
+
+       if (!parent) return NULL;
+
+       if (!prev) {
+               if (!ident1) return cf_next(parent, NULL, type);
+               return cf_find(parent, type, ident1, ident2);
+       }
+       if (!ident1) return cf_next(parent, prev, type);
+
+       switch (type) {
+       case CONF_ITEM_SECTION:
+               memset(&cs_find, 0, sizeof(cs_find));
+               cs_find.item.type = CONF_ITEM_SECTION;
+               cs_find.name1 = ident1;
+               if (ident2 != CF_IDENT_ANY) cs_find.name2 = ident2;
+
+               find = (CONF_ITEM *)&cs_find;
+               break;
+
+       case CONF_ITEM_PAIR:
+               rad_assert((ident2 == NULL) || (ident2 == CF_IDENT_ANY));
+
+               memset(&cp_find, 0, sizeof(cp_find));
+               cp_find.item.type = CONF_ITEM_PAIR;
+               cp_find.attr = ident1;
+
+               find = (CONF_ITEM *)&cp_find;
+               break;
+
+       case CONF_ITEM_DATA:
+               memset(&cd_find, 0, sizeof(cd_find));
+               cd_find.item.type = CONF_ITEM_DATA;
+               cd_find.type = ident1;
+               if (ident2 != CF_IDENT_ANY) cd_find.name = ident2;
+
+               find = (CONF_ITEM *)&cd_find;
+               break;
+
+       default:
+               rad_assert(0);
+       }
+
+       if (ident1 == CF_IDENT_ANY) {
+               for (ci = prev->next;
+                    ci && (cf_ident2_cmp(ci, find) != 0);
+                    ci = ci->next);
+
+               return ci;
+       }
+
+       if (ident2 == CF_IDENT_ANY) {
+               for (ci = prev->next;
+                    ci && (_cf_ident1_cmp(ci, find) != 0);
+                    ci = ci->next);
+
+               return ci;
+       }
+
+       for (ci = prev->next;
+            ci && (_cf_ident2_cmp(ci, find) != 0);
+            ci = ci->next);
+
+       return ci;
+}
+
+/** Compare the first identifier of a child
+ *
+ * For CONF_ITEM_PAIR this is 'attr'.
+ * For CONF_ITEM_SECTION this is 'name1'.
+ * For CONF_ITEM_DATA this is 'type'.
+ *
+ * @param[in] a        First CONF_ITEM to compare.
+ * @param[in] b Second CONF_ITEM to compare.
+ * @return
+ *     - >0 if a > b.
+ *     - <0 if a < b.
+ *     - 0 if a == b.
+ */
+static inline int _cf_ident1_cmp(void const *a, void const *b)
+{
+       CONF_ITEM_TYPE type;
+
+       {
+               CONF_ITEM const *one = a;
+               CONF_ITEM const *two = b;
+
+               if (one->type > two->type) return +1;
+               if (one->type < two->type) return -1;
+
+               type = one->type;
+       }
+
+       switch (type) {
+       case CONF_ITEM_PAIR:
+       {
+               CONF_PAIR const *one = a;
+               CONF_PAIR const *two = b;
+
+               return strcmp(one->attr, two->attr);
+       }
+
+       case CONF_ITEM_SECTION:
+       {
+               CONF_SECTION const *one = a;
+               CONF_SECTION const *two = b;
+
+               return strcmp(one->name1, two->name1);
+       }
+
+       case CONF_ITEM_DATA:
+       {
+               CONF_DATA const *one = a;
+               CONF_DATA const *two = b;
+
+               return strcmp(one->type, two->type);
+       }
+
+       default:
+               rad_assert(0);
+       }
+}
+
+/** Compare only the second identifier of a child
+ *
+ * For CONF_ITEM_SECTION this is 'name2'.
+ * For CONF_ITEM_DATA this is 'name'.
+ *
+ * @param[in] a        First CONF_ITEM to compare.
+ * @param[in] b Second CONF_ITEM to compare.
+ * @return
+ *     - >0 if a > b.
+ *     - <0 if a < b.
+ *     - 0 if a == b.
+ */
+static inline int cf_ident2_cmp(void const *a, void const *b)
+{
+       CONF_ITEM const *ci = a;
+
+       switch (ci->type) {
+       case CONF_ITEM_PAIR:
+               return 0;
+
+       case CONF_ITEM_SECTION:
+       {
+               CONF_SECTION const *one = a;
+               CONF_SECTION const *two = b;
+
+               if (!two->name2 && one->name2) return +1;
+               if (two->name2 && !one->name2) return -1;
+               if (!two->name2 && !one->name2) return 0;
+
+               return strcmp(one->name2, two->name2);
+       }
+
+       case CONF_ITEM_DATA:
+       {
+               CONF_DATA const *one = a;
+               CONF_DATA const *two = b;
+
+               if (!two->name && one->name) return +1;
+               if (two->name && !one->name) return -1;
+               if (!two->name && !one->name) return 0;
+
+               return strcmp(one->name, two->name);
+       }
+
+       default:
+               rad_assert(0);
+       }
+}
+
+/** Compare the first and second identifiers of a child
+ *
+ * For CONF_ITEM_SECTION this is 'name2'.
+ * For CONF_ITEM_DATA this is 'name'.
+ *
+ * @param[in] a        First CONF_ITEM to compare.
+ * @param[in] b Second CONF_ITEM to compare.
+ * @return
+ *     - >0 if a > b.
+ *     - <0 if a < b.
+ *     - 0 if a == b.
+ */
+static int _cf_ident2_cmp(void const *a, void const *b)
+{
+       int ret;
+
+       ret = _cf_ident1_cmp(a, b);
+       if (ret != 0) return ret;
+
+       return cf_ident2_cmp(a, b);
+}
+
+/** Add a child
+ *
+ * @param[in] parent   to add child to.
+ * @param[in] child    to add.
+ */
+void _cf_item_add(CONF_ITEM *parent, CONF_ITEM *child)
+{
+       fr_cursor_t     to_merge;
+       CONF_ITEM       *ci;
+
+       rad_assert(parent != child);
+
+       if (!parent || !child) return;
+
+       /*
+        *      New child, add child trees.
+        */
+       if (!parent->ident1) parent->ident1 = rbtree_create(parent, _cf_ident1_cmp, NULL, RBTREE_FLAG_NONE);
+       if (!parent->ident2) parent->ident2 = rbtree_create(parent, _cf_ident2_cmp, NULL, RBTREE_FLAG_NONE);
+
+       fr_cursor_init(&to_merge, &child);
+
+       for (ci = fr_cursor_head(&to_merge);
+            ci;
+            ci = fr_cursor_next(&to_merge)) {
+               rbtree_insert(parent->ident1, ci);
+               rbtree_insert(parent->ident2, ci);      /* NULL ident2 is still a value */
+               fr_cursor_append(&parent->cursor, ci);  /* Append to the list of children */
+       }
+}
+
+/** Remove item from parent and fixup trees
+ *
+ * @param[in] parent   to remove child from.
+ * @param[in] child    to remove.
+ * @return
+ *     - The item removed.
+ *     - NULL if the item wasn't set.
+ */
+static CONF_ITEM *cf_remove(CONF_ITEM *parent, CONF_ITEM *child)
+{
+       CONF_ITEM       *found;
+       bool            in_ident1, in_ident2;
+
+       if (!parent || !parent->child) return NULL;
+       if (parent != child->parent) return NULL;
+
+       for (found = fr_cursor_head(&parent->cursor);
+            found && (child != found);
+            found = fr_cursor_next(&parent->cursor));
+
+       if (!found) return NULL;
+
+       /*
+        *      Fixup the linked list
+        */
+       found = fr_cursor_remove(&parent->cursor);
+       rad_assert(found == child);
+
+       in_ident1 = (rbtree_finddata(parent->ident1, child) == child);
+       if (in_ident1 && (!rbtree_deletebydata(parent->ident1, child))) {
+               rad_assert(0);
+               return NULL;
+       }
+
+       in_ident2 = (rbtree_finddata(parent->ident2, child) == child);
+       if (in_ident2 && (!rbtree_deletebydata(parent->ident2, child))) {
+               rad_assert(0);
+               return NULL;
+       }
+
+       /*
+        *      Look for twins
+        */
+       for (found = fr_cursor_head(&parent->cursor);
+            found && (in_ident1 || in_ident2);
+            found = fr_cursor_next(&parent->cursor)) {
+               if (in_ident1 && (_cf_ident1_cmp(found, child) == 0)) {
+                       rbtree_insert(parent->ident1, child);
+                       in_ident1 = false;
+               }
+
+               if (in_ident2 && (_cf_ident2_cmp(found, child) == 0)) {
+                       rbtree_insert(parent->ident2, child);
+                       in_ident2 = false;
+               }
+       }
+
+       return child;
+}
+
+/** Return the next child of cs
+ *
+ * @param[in] ci       to return children from.
+ * @param[in] prev     child to start searching from.
+ * @return
+ *     - The next #CONF_ITEM that's a child of cs.
+ *     - NULL if no more #CONF_ITEM.
+ */
+CONF_ITEM *_cf_item_next(CONF_ITEM const *ci, CONF_ITEM const *prev)
+{
+       return prev ? prev->next : ci->child;
+}
+
+/** Return the top level #CONF_SECTION holding all other #CONF_ITEM
+ *
+ * @param[in] ci       to traverse up from.
+ * @return
+ *     - NULL if ci was NULL.
+ *     - The top level #CONF_SECTION
+ */
+CONF_SECTION *_cf_root(CONF_ITEM const *ci)
+{
+       CONF_ITEM const *ci_p;
+
+       if (!ci) return NULL;
+
+       for (ci_p = ci; ci_p->parent; ci_p = ci_p->parent);
+
+       return cf_item_to_section(ci_p);
+}
+
+/** Return the parent of a #CONF_ITEM
+ *
+ * @param[in] ci       to return the parent of.
+ * @return
+ *     - NULL if ci was NULL or it has no parents.
+ *     - The parent of ci.
+ */
+CONF_ITEM *_cf_parent(CONF_ITEM const *ci)
+{
+       if (!ci) return NULL;
+
+       return ci->parent;
+}
+
+/** Return the lineno the #CONF_ITEM was parsed at
+ *
+ * @param[in] ci       to return the location of.
+ * @return
+ *     - -1 if the #CONF_ITEM was created programatically.
+ *     - >= 0 where in the config file the line was parsed from.
+ */
+int _cf_lineno(CONF_ITEM const *ci)
+{
+       return ci->lineno;
+}
+
+/** Return the filename the #CONF_ITEM was parsed in
+ *
+ * @param[in] ci       to return the location of.
+ * @return
+ *     - NULL if the #CONF_ITEM was created programatically.
+ *     - The path of the config file the #CONF_ITEM was located in.
+ */
+char const *_cf_filename(CONF_ITEM const *ci)
+{
+       return ci->filename;
+}
+
+/** Determine if #CONF_ITEM is a #CONF_SECTION
+ *
+ * @param[in] ci       to check.
+ * @return
+ *     - true if ci is a #CONF_SECTION.
+ *     - false if ci is another specialisation.
+ */
+bool cf_item_is_section(CONF_ITEM const *ci)
+{
+       return ci->type == CONF_ITEM_SECTION;
+}
+
+/** Determine if #CONF_ITEM is a #CONF_PAIR
+ *
+ * @param[in] ci       to check.
+ * @return
+ *     - true if ci is a #CONF_PAIR.
+ *     - false if ci is another specialisation.
+ */
+bool cf_item_is_pair(CONF_ITEM const *ci)
+{
+       return ci->type == CONF_ITEM_PAIR;
+}
+
+/** Determine if #CONF_ITEM is #CONF_DATA
+ *
+ * @param[in] ci       to check.
+ * @return
+ *     - true if ci is #CONF_DATA.
+ *     - false if ci is another specialisation.
+ */
+bool cf_item_is_data(CONF_ITEM const *ci)
+{
+       return ci->type == CONF_ITEM_DATA;
+}
+
+/** Cast a #CONF_ITEM to a #CONF_PAIR
+ *
+ * @note Will assert if ci does not contain #CONF_PAIR.
+ *
+ * @param[in] ci       to cast.
+ * @return
+ *     - #CONF_PAIR.
+ *     - NULL if ci was NULL.
+ */
+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
+ *
+ * @note Will assert if ci does not contain #CONF_SECTION.
+ *
+ * @param[in] ci       to cast.
+ * @return
+ *     - #CONF_SECTION.
+ *     - NULL if ci was NULL.
+ */
+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 #CONF_ITEM to #CONF_DATA performing a type check
+ *
+ * @note Will assert if ci does not contain #CONF_DATA.
+ *
+ * @param[in] ci       to cast.
+ * @return
+ *     - #CONF_DATA.
+ *     - NULL if ci was NULL.
+ */
+CONF_DATA *cf_item_to_data(CONF_ITEM const *ci)
+{
+       CONF_DATA *out;
+
+       if (ci == NULL) return NULL;
+
+       rad_assert(ci->type == CONF_ITEM_DATA);
+
+       memcpy(&out, &ci, sizeof(out));
+       return out;
+}
+
+/** Cast a #CONF_PAIR to a #CONF_ITEM
+ *
+ * @param[in] cp       to cast.
+ * @return
+ *     - The common #CONF_ITEM header.
+ *     - NULL if cp was NULL.
+ */
+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
+ *
+ * @param[in] cs       to cast.
+ * @return
+ *     - The common #CONF_ITEM header.
+ *     - NULL if cs was NULL.
+ */
+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
+ *
+ * @param[in] cd       to cast.
+ * @return
+ *     - The common #CONF_ITEM header.
+ *     - NULL if cd was NULL.
+ */
+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;
+}
+
+/** Free a section and associated trees
+ *
+ * @param[in] cs       to free.
+ * @return 0
+ */
+static int _cf_section_free(CONF_SECTION *cs)
+{
+       if (cs->item.ident1) TALLOC_FREE(cs->item.ident1);
+       if (cs->item.ident2) TALLOC_FREE(cs->item.ident2);
+
+       return 0;
+}
+
+/** Allocate a #CONF_SECTION
+ *
+ * @param[in] parent   #CONF_SECTION to hang this #CONF_SECTION off of.
+ * @param[in] name1    Primary name.
+ * @param[in] name2    Secondary name.
+ * @return
+ *     - NULL on error.
+ *     - A new #CONF_SECTION parented by parent.
+ */
+CONF_SECTION *cf_section_alloc(CONF_SECTION *parent, char const *name1, char const *name2)
+{
+       CONF_SECTION *cs;
+
+       if (!name1) return NULL;
+
+       if (name2 && parent) {
+               char buffer[1024];
+
+               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 = cf_section_to_item(parent);
+       fr_cursor_init(&cs->item.cursor, &cs->item.child);
+
+       MEM(cs->name1 = talloc_typed_strdup(cs, name1));
+       if (name2) {
+               MEM(cs->name2 = talloc_typed_strdup(cs, name2));
+               cs->name2_quote = T_BARE_WORD;
+       }
+       talloc_set_destructor(cs, _cf_section_free);
+
+       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[in] parent   section (may be NULL).
+ * @param[in] cs       to duplicate.
+ * @param[in] name1    of new section.
+ * @param[in] name2    of new section.
+ * @param[in] copy_meta        Copy additional meta data for a section
+ *                     (like template, base, depth and variables).
+ * @return
+ *     - A duplicate of the existing section.
+ *     - 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;
+       fr_cursor_t     cursor;
+
+       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;
+       new->item.filename = cs->item.filename;
+
+       fr_cursor_copy(&cursor, &cs->item.cursor);      /* Mutable cursor */
+       for (ci = fr_cursor_head(&cursor);
+            ci;
+            ci = fr_cursor_next(&cursor)) {
+               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 */
+#ifdef WITH_CONF_WRITE
+               case CONF_ITEM_COMMENT:
+               case CONF_ITEM_INCLUDE:
+#endif
+                       break;
+
+               case CONF_ITEM_INVALID:
+                       rad_assert(0);
+               }
+       }
+
+       return new;
+}
+
+/** Add a section as a child of another section
+ *
+ * @param[in] parent   section we're adding to.
+ * @param[in] cs       we're adding.
+ */
+void cf_section_add(CONF_SECTION *parent, CONF_SECTION *cs)
+{
+       cf_item_add(parent, &(cs->item));
+}
+
+/** Return the next child that's a #CONF_SECTION
+ *
+ * @param[in] cs       to return children from.
+ * @param[in] prev     child to start searching from.
+ * @return
+ *     - The next #CONF_ITEM that's a child of cs and a CONF_SECTION.
+ *     - NULL if no #CONF_ITEM matches that criteria.
+ */
+CONF_SECTION *cf_section_next(CONF_SECTION const *cs, CONF_SECTION const *prev)
+{
+       return cf_item_to_section(cf_next(cf_section_to_item(cs), cf_section_to_item(prev), CONF_ITEM_SECTION));
+}
+
+/** Find a CONF_SECTION with name1 and optionally name2.
+ *
+ * @param[in] cs       The section we're searching in.
+ * @param[in] name1    of the section we're searching for. Special value CF_IDENT_ANY
+ *                     can be used to match any name1 value.
+ * @param[in] name2    of the section we're searching for. Special value CF_IDENT_ANY
+ *                     can be used to match any name2 value.
+ * @return
+ *     - The first matching subsection.
+ *     - NULL if no subsections match.
+ */
+CONF_SECTION *cf_section_find(CONF_SECTION const *cs,
+                             char const *name1, char const *name2)
+{
+       return cf_item_to_section(cf_find(cf_section_to_item(cs), CONF_ITEM_SECTION, name1, name2));
+}
+
+/** Return the next matching section
+ *
+ * @param[in] cs       The section we're searching in.
+ * @param[in] prev     section we found.  May be NULL in which case
+ *                     we just return the next section after prev.
+ * @param[in] name1    of the section we're searching for.  Special value CF_IDENT_ANY
+ *                     can be used to match any name1 value.
+ * @param[in] name2    of the section we're searching for.  Special value CF_IDENT_ANY
+ *                     can be used to match any name2 value.
+ * @return
+ *     - The next CONF_SECTION.
+ *     - NULL if there are no more CONF_SECTIONs
+ */
+CONF_SECTION *cf_section_find_next(CONF_SECTION const *cs, CONF_SECTION const *prev,
+                                  char const *name1, char const *name2)
+{
+       return cf_item_to_section(cf_find_next(cf_section_to_item(cs), cf_section_to_item(prev),
+                                              CONF_ITEM_SECTION, name1, name2));
+}
+
+/** Find a pair in a #CONF_SECTION
+ *
+ * @param[in] cs       the #CONF_SECTION to search in.
+ * @param[in] attr     to search for.
+ * @return
+ *     - NULL if no pair can be found.
+ *     - The value of the pair found.
+ */
+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);
+}
+
+/** Return the second identifier of a #CONF_SECTION
+ *
+ * @param[in] cs       to return identifiers for.
+ * @return
+ *     - The first identifier.
+ *     - NULL if cs was NULL or no name1 set.
+ */
+char const *cf_section_name1(CONF_SECTION const *cs)
+{
+       return (cs ? cs->name1 : NULL);
+}
+
+/** Return the second identifier of a #CONF_SECTION
+ *
+ * @param[in] cs       to return identifiers for.
+ * @return
+ *     - The second identifier.
+ *     - NULL if cs was NULL or no name2 set.
+ */
+char const *cf_section_name2(CONF_SECTION const *cs)
+{
+       return (cs ? cs->name2 : NULL);
+}
+
+/** Return name2 if set, else name1
+ *
+ * @param[in] cs       to return identifiers for.
+ * @return name1 or name2 identifier.
+ */
+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);
+}
+
+/** Return variadic argument at the specified index
+ *
+ * @param[in] cs       containing the arguments.
+ * @param[in] argc     Argument index. Note name1 and name2 are not counted in this index.
+ * @return the argument value or NULL.
+ */
+char const *cf_section_argv(CONF_SECTION const *cs, int argc)
+{
+       if (!cs || !cs->argv || (argc < 0) || (argc > cs->argc)) return NULL;
+
+       return cs->argv[argc];
+}
+
+/** Return the quoting of the name2 identifier
+ *
+ * @param[in] cs       containing name2.
+ * @return
+ *     - #T_BARE_WORD.
+ *     - #T_SINGLE_QUOTED_STRING.
+ *     - #T_BACK_QUOTED_STRING.
+ *     - #T_DOUBLE_QUOTED_STRING.
+ *     - #T_INVALID if cs was NULL.
+ */
+FR_TOKEN cf_section_name2_quote(CONF_SECTION const *cs)
+{
+       if (!cs) return T_INVALID;
+
+       return cs->name2_quote;
+}
+
+/** Return the quoting for one of the variadic arguments
+ *
+ * @param[in] cs       containing the arguments.
+ * @param[in] argc     Argument index.  Note name1 and name2 are not counted in this index.
+ * @return
+ *     - #T_BARE_WORD.
+ *     - #T_SINGLE_QUOTED_STRING.
+ *     - #T_BACK_QUOTED_STRING.
+ *     - #T_DOUBLE_QUOTED_STRING.
+ *     - #T_INVALID if cs was NULL or the index was invalid.
+ */
+FR_TOKEN cf_section_argv_quote(CONF_SECTION const *cs, int argc)
+{
+       if (!cs || !cs->argv_quote || (argc < 0) || (argc > cs->argc)) return T_INVALID;
+
+       return cs->argv_quote[argc];
+}
+
+/** Allocate a #CONF_PAIR
+ *
+ * @param[in] parent           #CONF_SECTION to hang this #CONF_PAIR off of.
+ * @param[in] attr             name.
+ * @param[in] value            of #CONF_PAIR.
+ * @param[in] op               #T_OP_EQ, #T_OP_SET etc.
+ * @param[in] lhs_quote                #T_BARE_WORD, #T_DOUBLE_QUOTED_STRING, #T_BACK_QUOTED_STRING.
+ * @param[in] rhs_quote                #T_BARE_WORD, #T_DOUBLE_QUOTED_STRING, #T_BACK_QUOTED_STRING.
+ * @return
+ *     - NULL on error.
+ *     - 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_quote, FR_TOKEN rhs_quote)
+{
+       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 = cf_section_to_item(parent);
+       cp->lhs_quote = lhs_quote;
+       cp->rhs_quote = rhs_quote;
+       cp->op = op;
+       cp->item.filename = "<internal>"; /* will be over-written if necessary */
+       fr_cursor_init(&cp->item.cursor, &cp->item.child);
+
+       cp->attr = talloc_typed_strdup(cp, attr);
+       if (!cp->attr) {
+       error:
+               talloc_free(cp);
+               return NULL;
+       }
+
+       if (value) {
+#ifdef WITH_CONF_WRITE
+               cp->orig_value = talloc_typed_strdup(cp, value);
+#endif
+               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.
+ *     - 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_quote, cp->rhs_quote);
+       if (!new) return NULL;
+
+       new->parsed = cp->parsed;
+       new->item.lineno = cp->item.lineno;
+       new->item.filename = cp->item.filename;
+
+       return new;
+}
+
+/** Replace pair in a given section with a new pair, of the given value.
+ *
+ * @note A new pair with the same metadata as the #CONF_PAIR will be added
+ *     even if the #CONF_PAIR can't be found inside the #CONF_SECTION.
+ *
+ * @param[in] cs       to replace pair in.
+ * @param[in] cp       to replace.
+ * @param[in] 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 *new_cp;
+       CONF_ITEM *ci;
+
+       if (!cs || !cp || !value) return -1;
+
+       MEM(new_cp = cf_pair_alloc(cs, cp->attr, value, cp->op, cp->lhs_quote, cp->rhs_quote));
+
+       /*
+        *      Remove the old CONF_PAIR
+        */
+       ci = cf_remove(cf_section_to_item(cs), cf_pair_to_item(cp));
+       rad_assert(!ci || (ci == cf_pair_to_item(cp)));
+
+       /*
+        *      Add the new CONF_PAIR
+        */
+       cf_item_add(cf_section_to_item(cs), cf_pair_to_item(new_cp));
+
+       cp = cf_pair_find(cs, cp->attr);
+       rad_assert(cp == new_cp);
+
+       talloc_free(ci);
+
+       return 0;
+}
+
+/** Add a configuration pair to a section
+ *
+ * @param[in] parent section to add pair to.
+ * @param[in] cp to add.
+ */
+void cf_pair_add(CONF_SECTION *parent, CONF_PAIR *cp)
+{
+       cf_item_add(parent, cf_pair_to_item(cp));
+}
+
+/** Return the next child that's a #CONF_PAIR
+ *
+ * @param[in] cs       to return children from.
+ * @param[in] prev     child to start searching from.
+ * @return
+ *     - The next #CONF_ITEM that's a child of cs and a CONF_PAIR.
+ *     - NULL if no #CONF_ITEM matches that criteria.
+ */
+CONF_PAIR *cf_pair_next(CONF_SECTION const *cs, CONF_PAIR const *prev)
+{
+       return cf_item_to_pair(cf_next(cf_section_to_item(cs), cf_pair_to_item(prev), CONF_ITEM_PAIR));
+}
+
+/** Search for a #CONF_PAIR with a specific name
+ *
+ * @param[in] cs       to search in.
+ * @param[in] attr     to find.
+ * @return
+ *     - The next matching #CONF_PAIR.
+ *     - NULL if none matched.
+ */
+CONF_PAIR *cf_pair_find(CONF_SECTION const *cs, char const *attr)
+{
+       return cf_item_to_pair(cf_find(cf_section_to_item(cs), CONF_ITEM_PAIR, attr, NULL));
+}
+
+/** Find a pair with a name matching attr, after specified pair.
+ *
+ * @param[in] cs       to search in.
+ * @param[in] prev     Pair to search from (may be NULL).
+ * @param[in] attr     to find (may be NULL in which case any attribute matches).
+ * @return
+ *     - The next matching #CONF_PAIR
+ *     - NULL if none matched.
+ */
+CONF_PAIR *cf_pair_find_next(CONF_SECTION const *cs, CONF_PAIR const *prev, char const *attr)
+{
+       return cf_item_to_pair(cf_find_next(cf_section_to_item(cs), cf_pair_to_item(prev), CONF_ITEM_PAIR, attr, NULL));
+}
+
+/** Callback to determine the number of pairs in a section
+ *
+ * @param[out] count   Where we store the number of pairs found.
+ * @param[in] cs       we're currently searching in.
+ */
+static void _pair_count(int *count, CONF_SECTION const *cs)
+{
+       CONF_ITEM const *ci = NULL;
+
+       while ((ci = cf_item_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 The number of pairs nested within section.
+ */
+int cf_pair_count(CONF_SECTION const *cs)
+{
+       int count = 0;
+
+       _pair_count(&count, cs);
+
+       return count;
+}
+
+/** Return the attr of a #CONF_PAIR
+ *
+ * Return the LHS value of a pair (the attribute).
+ *
+ * @param[in] pair     to return the attribute for.
+ * @return
+ *     - NULL if the pair was NULL.
+ *     - The attribute name of the pair.
+ */
+char const *cf_pair_attr(CONF_PAIR const *pair)
+{
+       return (pair ? pair->attr : NULL);
+}
+
+/** Return the value of a #CONF_PAIR
+ *
+ * Return the RHS value of a pair (the value).
+ *
+ * @param[in]  pair to return the value of.
+ * @return
+ *     - NULL if pair was NULL or the pair has no value.
+ *     - The string value of the pair.
+ */
+char const *cf_pair_value(CONF_PAIR const *pair)
+{
+       return (pair ? pair->value : NULL);
+}
+
+/** Return the operator of a pair
+ *
+ * @param[in] pair     to return the operator of.
+ * @return
+ *     - T_INVALID if pair was NULL.
+ *     - T_OP_* (one of the operator constants).
+ */
+FR_TOKEN cf_pair_operator(CONF_PAIR const *pair)
+{
+       return (pair ? pair->op : T_INVALID);
+}
+
+/** Return the value (lhs) quoting of a pair
+ *
+ * @param pair to extract value type from.
+ * @return
+ *     - #T_BARE_WORD.
+ *     - #T_SINGLE_QUOTED_STRING.
+ *     - #T_BACK_QUOTED_STRING.
+ *     - #T_DOUBLE_QUOTED_STRING.
+ *     - #T_INVALID if the pair is NULL.
+ */
+FR_TOKEN cf_pair_attr_quote(CONF_PAIR const *pair)
+{
+       return (pair ? pair->lhs_quote : T_INVALID);
+}
+
+/** Return the value (rhs) quoting of a pair
+ *
+ * @param pair to extract value type from.
+ * @return
+ *     - #T_BARE_WORD.
+ *     - #T_SINGLE_QUOTED_STRING.
+ *     - #T_BACK_QUOTED_STRING.
+ *     - #T_DOUBLE_QUOTED_STRING.
+ *     - #T_INVALID if the pair is NULL.
+ */
+FR_TOKEN cf_pair_value_quote(CONF_PAIR const *pair)
+{
+       return (pair ? pair->rhs_quote : T_INVALID);
+}
+
+/** Free user data associated with #CONF_DATA
+ *
+ * @param[in] cd       being freed.
+ * @return 0
+ */
+static int _cd_free(CONF_DATA *cd)
+{
+       void *to_free;
+
+       memcpy(&to_free, cd->data, sizeof(to_free));
+
+       if (cd->free) talloc_free(to_free);
+
+       return 0;
+}
+
+/** Allocate a new user data container
+ *
+ * @param[in] parent   #CONF_PAIR, or #CONF_SECTION to hang CONF_DATA off of.
+ * @param[in] name     String identifier of the user data.
+ * @param[in] data     being added.
+ * @param[in] do_free  function, called when the parent #CONF_SECTION is being freed.
+ * @return
+ *     - CONF_DATA on success.
+ *     - NULL on error.
+ */
+static CONF_DATA *cf_data_alloc(CONF_ITEM *parent, void const *data, char const *name, bool do_free)
+{
+       CONF_DATA *cd;
+
+       cd = talloc_zero(parent, CONF_DATA);
+       if (!cd) return NULL;
+
+       cd->item.type = CONF_ITEM_DATA;
+       cd->item.parent = parent;
+       fr_cursor_init(&cd->item.cursor, &cd->item.child);
+
+       /*
+        *      strdup so if the data is freed, we can
+        *      still remove it from the section without
+        *      explosions.
+        */
+       if (data) {
+               cd->type = talloc_typed_strdup(cd, talloc_get_name(data));
+               cd->data = data;
+       }
+       if (name) cd->name = talloc_typed_strdup(cd, name);
+
+       if (do_free) {
+               cd->free = true;
+               talloc_set_destructor(cd, _cd_free);
+       }
+
+       return cd;
+}
+
+/** Find user data in a config section
+ *
+ * @param[in] ci       to add data to.
+ * @param[in] type     of user data.  Used for name spacing and walking over a specific
+ *                     type of user data.
+ * @param[in] name     String identifier of the user data.  Special value CF_IDENT_ANY
+ *                     may be used to match on type only.
+ * @return
+ *     - The user data.
+ *     - NULL if no user data exists.
+ */
+CONF_DATA const *_cf_data_find(CONF_ITEM const *ci, char const *type, char const *name)
+{
+       return cf_item_to_data(cf_find(ci, CONF_ITEM_DATA, type, name));
+}
+
+/** Return the next matching section
+ *
+ * @param[in] ci       The section we're searching in.
+ * @param[in] prev     section we found.  May be NULL in which case
+ *                     we just return the next section after prev.
+ * @param[in] type     of user data.  Used for name spacing and walking over a specific
+ *                     type of user data.
+ * @param[in] name     String identifier of the user data.  Special value CF_IDENT_ANY
+ *                     can be used to match any name2 value.
+ * @return
+ *     - The next #CONF_SECTION.
+ *     - NULL if there are no more #CONF_SECTION.
+ */
+CONF_DATA const *_cf_data_find_next(CONF_ITEM const *ci, CONF_ITEM const *prev, char const *type, char const *name)
+{
+       return cf_item_to_data(cf_find_next(ci, prev, CONF_ITEM_DATA, type, name));
+}
+
+/** Return the user assigned value of #CONF_DATA
+ *
+ * @param[in] cd       to return value of.
+ * @return the user data stored within the #CONF_DATA.
+ */
+void *cf_data_value(CONF_DATA const *cd)
+{
+       void *to_return;
+
+       if (!cd) return NULL;
+
+       memcpy(&to_return, &cd->data, sizeof(to_return));
+
+       return to_return;
+}
+
+/** Add user data to a config section
+ *
+ * @param[in] ci       to add data to.
+ * @param[in] data     to add.
+ * @param[in] name     String identifier of the user data.
+ * @param[in] do_free  Function to free user data when the CONF_SECTION is freed.
+ * @return
+ *     - #CONF_DATA  - opaque handle to the stored data - on success.
+ *     - NULL error.
+ */
+CONF_DATA const *_cf_data_add(CONF_ITEM *ci, void const *data, char const *name, bool do_free)
+{
+       CONF_DATA       *cd;
+       char const      *type = NULL;
+
+       if (!ci) return NULL;
+
+       if (data) type = talloc_get_name(data);
+
+       /*
+        *      Already exists.  Can't add it.
+        */
+       if (_cf_data_find(ci, type, name)) {
+               cf_log_err(ci, "Data of type %s with name %s already exists", type, name);
+               return NULL;
+       }
+
+       cd = cf_data_alloc(ci, data, name, do_free);
+       if (!cd) {
+               cf_log_err(ci, "Failed allocating data");
+               return NULL;
+       }
+
+       cf_item_add(ci, cf_data_to_item(cd));
+
+       return cd;
+}
+
+/** Remove data from a configuration section
+ *
+ * @note If cd was not found it will not be freed, and it is the caller's responsibility
+ *     to free it explicitly, or free the section it belongs to.
+ *
+ * @param[in] parent   to remove data from.
+ * @param[in] cd       opaque handle of the stored data.
+ * @return
+ *     - The value stored within the data (if cd is valid and was found and removed).
+ *     - NULL if not found.
+ */
+void *_cf_data_remove(CONF_ITEM *parent, CONF_DATA const *cd)
+{
+       void *data;
+       CONF_ITEM *ci;
+
+       ci = cf_remove(parent, cf_data_to_item(cd));
+       rad_assert(!ci || (ci == cf_data_to_item(cd)));
+       if (!ci) return NULL;
+
+       talloc_set_destructor(cd, NULL);        /* Disarm the destructor */
+       memcpy(&data, &cd->data, sizeof(data));
+       talloc_const_free(cd);
+
+       return data;
+}
+
+/** ctx data for a _cf_data_walk_call
+ *
+ */
+typedef struct cf_data_walk_ctx {
+       char const      *type;          //!< of CONF_DATA we're iterating over.
+       cf_walker_t     cb;             //!< cb to process CONF_DATA.
+       void            *ctx;           //!< to pass to cb.
+} cf_data_walk_ctx_t;
+
+/** Wrap a cf_walker_t in an rb_walker_t
+ *
+ * @param[in] ctx      A cf_data_walk_ctx_t.
+ * @param[in] data     A CONF_DATA entry.
+ */
+static int _cf_data_walk_cb(void *ctx, void *data)
+{
+       cf_data_walk_ctx_t      *cd_ctx = ctx;
+       CONF_DATA               *cd = data;
+       CONF_ITEM               *ci = data;
+       void                    *mutable;
+       int                     ret;
+
+       /*
+        *      We're walking ident2, not all of the items will be data
+        */
+       if (ci->type != CONF_ITEM_DATA) return 0;
+
+       if ((cd->type != cd_ctx->type) && (strcmp(cd->type, cd_ctx->type) != 0)) return 0;
+
+       memcpy(&mutable, &cd->data, sizeof(data));
+       ret = cd_ctx->cb(mutable, cd_ctx->ctx);
+
+       return ret;
+}
+
+/** Walk over a specific type of CONF_DATA
+ *
+ * @param[in] ci       containing the CONF_DATA to walk over.
+ * @param[in] type     of CONF_DATA to walk over.
+ * @param[in] cb       to call when we find CONF_DATA of the specified type.
+ * @param[in] ctx      to pass to cb.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+int _cf_data_walk(CONF_ITEM *ci, char const *type, cf_walker_t cb, void *ctx)
+{
+       cf_data_walk_ctx_t cd_ctx = {
+               .type = type,
+               .cb = cb,
+               .ctx = ctx
+       };
+
+       if (!ci->ident2) return 0;
+
+       return rbtree_walk(ci->ident2, RBTREE_IN_ORDER, _cf_data_walk_cb, &cd_ctx);
+}
+
+/** Log an error message relating to a #CONF_ITEM
+ *
+ * @param[in] ci       #CONF_ITEM to print file/lineno for.
+ * @param[in] fmt      of the message.
+ * @param[in] ...      Message args.
+ */
+void _cf_log_err(CONF_ITEM const *ci, char const *fmt, ...)
+{
+       va_list ap;
+       char    *msg;
+
+       va_start(ap, fmt);
+       msg = talloc_vasprintf(NULL, fmt, ap);
+       va_end(ap);
+
+       if (!ci || !ci->filename) {
+               ERROR("%s", msg);
+       } else {
+               ERROR("%s[%d]: %s", ci->filename, ci->lineno, msg);
+       }
+
+       talloc_free(msg);
+}
+
+/** Log an error message relating to a #CONF_ITEM
+ *
+ * Drains the fr_strerror() stack emitting one or more error messages.
+ *
+ * @param[in] ci       #CONF_ITEM to print file/lineno for.
+ * @param[in] fmt      of the message.
+ * @param[in] ...      Message args.
+ */
+void _cf_log_perr(CONF_ITEM const *ci, char const *fmt, ...)
+{
+       va_list ap;
+       char    *msg;
+
+       va_start(ap, fmt);
+       msg = talloc_vasprintf(NULL, fmt, ap);
+       va_end(ap);
+
+       if (!ci || !ci->filename) {
+               PERROR("%s", msg);
+       } else {
+               PERROR("%s[%d]: %s", ci->filename, ci->lineno, msg);
+       }
+
+       talloc_free(msg);
+}
+
+/** Log a warning message relating to a #CONF_ITEM
+ *
+ * @param[in] ci       #CONF_ITEM to print file/lineno for.
+ * @param[in] fmt      of the message.
+ * @param[in] ...      Message args.
+ */
+void _cf_log_warn(CONF_ITEM const *ci, char const *fmt, ...)
+{
+       va_list ap;
+       char    *msg;
+
+       va_start(ap, fmt);
+       msg = talloc_vasprintf(NULL, fmt, ap);
+       va_end(ap);
+
+       if (!ci || !ci->filename) {
+               WARN("%s", msg);
+       } else {
+               WARN("%s[%d]: %s", ci->filename, ci->lineno, msg);
+       }
+
+       talloc_free(msg);
+}
+
+/** Log a info message relating to a #CONF_ITEM
+ *
+ * @param[in] ci       #CONF_ITEM to print file/lineno for.
+ * @param[in] fmt      of the message.
+ * @param[in] ...      Message args.
+ */
+void _cf_log_info(CONF_ITEM const *ci, char const *fmt, ...)
+{
+       va_list ap;
+       char    *msg;
+
+       va_start(ap, fmt);
+       msg = talloc_vasprintf(NULL, fmt, ap);
+       va_end(ap);
+
+       if (!ci || !ci->filename || !DEBUG_ENABLED3) {
+               INFO("%s", msg);
+       } else {
+               INFO("%s[%d]: %s", ci->filename, ci->lineno, msg);
+       }
+
+       talloc_free(msg);
+}
+
+/** Log a debug message relating to a #CONF_ITEM
+ *
+ * @note File and line number are only emitted at debug level 4.
+ *
+ * @param[in] ci       #CONF_ITEM to print file/lineno for.
+ * @param[in] fmt      of the message.
+ * @param[in] ...      Message args.
+ */
+void _cf_log_debug(CONF_ITEM const *ci, char const *fmt, ...)
+{
+       va_list ap;
+       char    *msg;
+
+       if (rad_debug_lvl < 1) return;
+
+       va_start(ap, fmt);
+       msg = talloc_vasprintf(NULL, fmt, ap);
+       va_end(ap);
+
+       if (!ci || !ci->filename || !DEBUG_ENABLED4) {
+               DEBUG("%s", msg);
+       } else {
+               DEBUG("%s[%d]: %s", ci->filename, ci->lineno, msg);
+       }
+
+       talloc_free(msg);
+}
+
+/** Log an error message in the context of a child pair of the specified parent
+ *
+ * @param[in] parent   containing the pair.
+ * @param[in] name     of the pair.
+ * @param[in] fmt      of the message.
+ * @param[in] ...      Message args.
+ */
+void cf_log_err_by_name(CONF_SECTION const *parent, char const *name, char const *fmt, ...)
+{
+       va_list         ap;
+       char            *msg;
+       CONF_PAIR const *cp;
+
+       va_start(ap, fmt);
+       msg = talloc_vasprintf(NULL, fmt, ap);
+       va_end(ap);
+
+       cp = cf_pair_find(parent, name);
+       if (cp) {
+               ERROR("%s[%d]: %s", cp->item.filename, cp->item.lineno, msg);
+       } else {
+               CONF_ITEM const *ci;
+
+               ci = cf_section_to_item(parent);
+               ERROR("%s[%d]: %s", ci->filename, ci->lineno, msg);
+       }
+       talloc_free(msg);
+}
+
+/** Print out debugging information about a CONFIG_ITEM
+ *
+ * @param[in] ci       being debugged.
+ */
+void _cf_debug(CONF_ITEM const *ci)
+{
+       fr_cursor_t     cursor;
+       CONF_ITEM const *child;
+
+       /*
+        *      Print summary of the item
+        */
+       switch (ci->type) {
+       case CONF_ITEM_SECTION:
+       {
+               CONF_SECTION const *cs = cf_item_to_section(ci);
+               int i;
+
+               DEBUG("SECTION - %p", cs);
+               DEBUG("  name1         : %s", cs->name1);
+               DEBUG("  name2         : %s", cs->name2 ? cs->name2 : "<none>");
+               DEBUG("  name2_quote   : %s", fr_int2str(fr_token_quotes_table, cs->name2_quote, "<INVALID>"));
+               DEBUG("  argc          : %u", cs->argc);
+
+               for (i = 0; i < cs->argc; i++) {
+                       char const *quote = fr_int2str(fr_token_quotes_table, cs->argv_quote[i], "<INVALID>");
+                       DEBUG("  argv[%i]      : %s%s%s", i, quote, cs->argv[i], quote);
+               }
+       }
+               break;
+
+       case CONF_ITEM_PAIR:
+       {
+               CONF_PAIR const *cp = cf_item_to_pair(ci);
+
+               DEBUG("PAIR - %p", cp);
+               DEBUG("  attr          : %s", cp->attr);
+               DEBUG("  value         : %s", cp->value);
+               DEBUG("  operator      : %s", fr_int2str(fr_tokens_table, cp->op, "<INVALID>"));
+               DEBUG("  lhs_quote     : %s", fr_int2str(fr_token_quotes_table, cp->lhs_quote, "<INVALID>"));
+               DEBUG("  rhs_quote     : %s", fr_int2str(fr_token_quotes_table, cp->rhs_quote, "<INVALID>"));
+               DEBUG("  pass2         : %s", cp->pass2 ? "yes" : "no");
+               DEBUG("  parsed        : %s", cp->parsed ? "yes" : "no");
+       }
+               break;
+
+       case CONF_ITEM_DATA:
+       {
+               CONF_DATA const *cd = cf_item_to_data(ci);
+
+               DEBUG("DATA - %p", cd);
+               DEBUG("  type          : %s", cd->type);
+               DEBUG("  name          : %s", cd->name);
+               DEBUG("  data          : %p", cd->data);
+               DEBUG("  free wth prnt : %s", cd->free ? "yes" : "no");
+       }
+               break;
+
+       default:
+               DEBUG("INVALID - %p", ci);
+               return;
+       }
+
+       DEBUG("  filename      : %s", ci->filename);
+       DEBUG("  line          : %i", ci->lineno);
+       DEBUG("  next          : %p", ci->next);
+       DEBUG("  parent        : %p", ci->parent);
+       DEBUG("  children      : %s", ci->child ? "yes" : "no");
+       DEBUG("  ident1 tree   : %p (%u entries)", ci->ident1, ci->ident1 ? rbtree_num_elements(ci->ident1) : 0);
+       DEBUG("  ident2 tree   : %p (%u entries)", ci->ident2, ci->ident2 ? rbtree_num_elements(ci->ident2) : 0);
+
+       if (!ci->child) return;
+
+       /*
+        *      Print summary of the item's children
+        */
+       DEBUG("CHILDREN");
+       fr_cursor_copy(&cursor, &ci->cursor);
+
+       for (child = fr_cursor_head(&cursor);
+            child;
+            child = fr_cursor_next(&cursor)) {
+               char const *in_ident1, *in_ident2;
+
+               in_ident1 = rbtree_finddata(ci->ident1, child) == child? "in ident1 " : "";
+               in_ident2 = rbtree_finddata(ci->ident2, child) == child? "in ident2 " : "";
+
+               switch (child->type) {
+               case CONF_ITEM_SECTION:
+               {
+                       CONF_SECTION const *cs = cf_item_to_section(child);
+
+                       DEBUG("  SECTION %p (%s %s) %s%s", child, cs->name1, cs->name2 ? cs->name2 : "<none>",
+                             in_ident1, in_ident2);
+               }
+                       break;
+
+               case CONF_ITEM_PAIR:
+               {
+                       CONF_PAIR const *cp = cf_item_to_pair(child);
+                       char const      *lhs_quote = fr_int2str(fr_token_quotes_table, cp->lhs_quote, "<INVALID>");
+                       char const      *rhs_quote = fr_int2str(fr_token_quotes_table, cp->rhs_quote, "<INVALID>");
+
+                       DEBUG("  PAIR %p (%s%s%s %s %s%s%s) %s%s", child,
+                             lhs_quote, cp->attr, lhs_quote,
+                             fr_int2str(fr_tokens_table, cp->op, "<INVALID>"),
+                             rhs_quote, cp->value, rhs_quote,
+                             in_ident1, in_ident2);
+               }
+                       break;
+
+               case CONF_ITEM_DATA:
+               {
+                       CONF_DATA const *cd = cf_item_to_data(child);
+
+                       DEBUG("  DATA %p (%s *)%s = %p %s%s", child,
+                             cd->type, cd->name ? cd->name : "", cd->data,
+                             in_ident1, in_ident2);
+                       break;
+               }
+
+               default:
+                       DEBUG("  INVALID - %p", child);
+                       break;
+               }
+       }
+}
diff --git a/src/main/conf_file.c b/src/main/conf_file.c
deleted file mode 100644 (file)
index 73ba5fa..0000000
+++ /dev/null
@@ -1,4933 +0,0 @@
-/*
- *   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
- */
-
-/**
- * $Id$
- * @file conf_file.c
- * @brief Read the radiusd.conf file.
- *
- * @note  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
- *
- * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
- * @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>
-#include <freeradius-devel/cursor.h>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#ifdef HAVE_DIRENT_H
-#  include <dirent.h>
-#endif
-
-#ifdef HAVE_SYS_STAT_H
-#  include <sys/stat.h>
-#endif
-
-#include <ctype.h>
-#include <fcntl.h>
-
-bool check_config = false;
-static uid_t conf_check_uid = (uid_t)-1;
-static gid_t conf_check_gid = (gid_t)-1;
-static CONF_PARSER conf_term = CONF_PARSER_TERMINATOR;
-
-char const *CF_IDENT_ANY = "<any>";
-
-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,
-#ifdef WITH_CONF_WRITE
-       CONF_ITEM_COMMENT,
-       CONF_ITEM_INCLUDE
-#endif
-} CONF_ITEM_TYPE;
-
-/** Common header for all CONF_* types
- *
- */
-struct cf_item {
-       CONF_ITEM_TYPE          type;           //!< Whether the config item is a config_pair, conf_section or cf_data.
-
-       CONF_ITEM               *next;          //!< Sibling.
-       CONF_ITEM               *parent;        //!< Parent.
-
-       CONF_ITEM               *child;         //!< The head of the ordered list of children.
-       fr_cursor_t             cursor;         //!< Cursor to iterate over children.  Maintains a 'tail' pointer for
-                                               //!< efficient insertion.
-
-       rbtree_t                *ident1;        //!< Tree to store the first identifier (name1 || type || attr).
-       rbtree_t                *ident2;        //!< Tree to store the second identifier (name2 || name).
-
-       int                     lineno;         //!< The line number the config item began on.
-       char const              *filename;      //!< The file the config item was parsed from.
-};
-
-/** Configuration AVP similar to a VALUE_PAIR
- *
- */
-struct cf_pair {
-       CONF_ITEM               item;           //!< Common set of fields.
-
-       char const              *attr;          //!< Attribute name
-#ifdef WITH_CONF_WRITE
-       char const              *orig_value;    /* original value */
-#endif
-       char const              *value;         //!< Attribute value
-
-       FR_TOKEN                op;             //!< Operator e.g. =, :=
-       FR_TOKEN                lhs_quote;      //!< Name quoting style T_(DOUBLE|SINGLE|BACK)_QUOTE_STRING or T_BARE_WORD.
-       FR_TOKEN                rhs_quote;      //!< 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?
-};
-
-/** A section grouping multiple #CONF_PAIR
- *
- */
-struct cf_section {
-       CONF_ITEM               item;           //!< Common set of fields.
-
-       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_quote;    //!< The type of quoting around name2.
-
-       int                     argc;           //!< number of additional arguments
-       char const              **argv;         //!< additional arguments
-       FR_TOKEN                *argv_quote;
-
-       void                    *base;
-       int                     depth;
-
-       CONF_SECTION            *template;
-       CONF_PARSER const       *variables;     //!< the section was parsed with.
-};
-
-/** Internal data that is associated with a configuration section
- *
- */
-struct cf_data {
-       CONF_ITEM               item;           //!< Common set of fields.
-
-       char const              *type;          //!< C type of data being stored.
-       char const              *name;          //!< Additional qualification of type.
-
-       void const              *data;          //!< User data.
-       bool                    free;           //!< If true, free data with talloc if parent node is freed.
-};
-
-typedef enum cf_include_type {
-       CONF_INCLUDE_FILE,
-       CONF_INCLUDE_DIR,
-       CONF_INCLUDE_FROMDIR,
-} CONF_INCLUDE_TYPE;
-
-#ifdef WITH_CONF_WRITE
-typedef struct conf_comment {
-       CONF_ITEM               item;
-       char const              *comment;
-} CONF_COMMENT;
-
-typedef struct conf_include {
-       CONF_ITEM               item;
-       char const              *filename;
-       CONF_INCLUDE_TYPE       file_type;
-} CONF_INCLUDE;
-#endif
-
-typedef struct cf_file_t {
-       char const              *filename;
-       CONF_SECTION            *cs;
-       struct stat             buf;
-} cf_file_t;
-
-static inline int cf_ident2_cmp(void const *a, void const *b);
-static int _cf_ident1_cmp(void const *a, void const *b);
-static int _cf_ident2_cmp(void const *a, void const *b);
-
-static int cf_file_include(CONF_SECTION *cs, char const *filename_in, CONF_INCLUDE_TYPE file_type, char *buff[7]);
-
-/*
- *     Expand the variables in an input string.
- */
-static char const *cf_expand_variables(char const *cf, int *lineno,
-                                      CONF_SECTION *outer_cs,
-                                      char *output, size_t outsize,
-                                      char const *input, bool *soft_fail)
-{
-       char *p;
-       char const *end, *ptr;
-       CONF_SECTION const *parent_cs;
-       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...
-        */
-       parent_cs = cf_root(outer_cs);
-
-       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';
-                               INFO("%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(parent_cs, outer_cs, 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 *find = 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, find->name1);
-                                       break;
-
-                               case CONF_PROPERTY_INSTANCE:
-                                       strcpy(p, find->name2 ? find->name2 : find->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;
-                               }
-
-                               if (!cp->value) {
-                                       ERROR("%s[%d]: Reference \"%s\" has no value",
-                                              cf, *lineno, input);
-                                       return NULL;
-                               }
-
-                               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);
-                               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 (cf_item_to_section(ci->parent) == outer_cs) {
-                                       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(outer_cs, 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(outer_cs, &(subcs->item));
-
-                               ptr = end + 1;
-
-                       } else {
-                               ERROR("%s[%d]: Reference \"%s\" type is invalid", cf, *lineno, input);
-                               return NULL;
-                       }
-               } else if (memcmp(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';
-                               INFO("%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;
-}
-
-/*
- *     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->item.child; 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_find(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 int _cd_free(CONF_DATA *cd)
-{
-       void *to_free;
-
-       memcpy(&to_free, cd->data, sizeof(to_free));
-
-       if (cd->free) talloc_free(to_free);
-
-       return 0;
-}
-
-/*
- *     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_SECTION *top;
-       rbtree_t *tree;
-       int fd;
-       FILE *fp;
-
-       top = cf_root(cs);
-       tree = cf_data_value(cf_data_find(top, rbtree_t, "filename"));
-       rad_assert(tree);
-
-       fp = fopen(filename, "r");
-       if (!fp) {
-               ERROR("Unable to open file \"%s\": %s", filename, fr_syserror(errno));
-               return NULL;
-       }
-
-       fd = fileno(fp);
-
-       MEM(file = talloc(tree, cf_file_t));
-
-       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 as an "input" file.  i.e. one read by a module.
- *
- * @note Must be called with super user privileges.
- *
- * @param cs           currently being processed.
- * @param filename     to check.
- * @param check_perms  If true - will return false if file is world readable,
- *                     or not readable by the unprivileged user/group.
- * @return
- *     - true if permissions are OK, or the file exists.
- *     - false if the file does not exist or the permissions are incorrect.
- */
-static bool cf_file_check(CONF_SECTION *cs, char const *filename, bool check_perms)
-{
-       cf_file_t       *file;
-       CONF_SECTION    *top;
-       rbtree_t        *tree;
-       int             fd = -1;
-
-       top = cf_root(cs);
-       tree = cf_data_value(cf_data_find(top, rbtree_t, "filename"));
-       if (!tree) return false;
-
-       file = talloc(tree, cf_file_t);
-       if (!file) return false;
-
-       file->filename = filename;
-       file->cs = cs;
-
-       if (!check_perms) {
-               if (stat(filename, &file->buf) < 0) {
-               perm_error:
-                       rad_file_error(errno);  /* Write error and euid/egid to error buff */
-                       ERROR("Unable to open file \"%s\": %s", filename, fr_strerror());
-               error:
-                       if (fd >= 0) close(fd);
-                       talloc_free(file);
-                       return false;
-               }
-               talloc_free(file);
-               return true;
-       }
-
-       /*
-        *      This really does seem to be the simplest way
-        *      to check that the file can be read with the
-        *      euid/egid.
-        */
-       {
-               uid_t euid = (uid_t)-1;
-               gid_t egid = (gid_t)-1;
-
-               if ((conf_check_gid != (gid_t)-1) && ((egid = getegid()) != conf_check_gid)) {
-                       if (setegid(conf_check_gid) < 0) {
-                               ERROR("Failed setting effective group ID for file check");
-                               goto error;
-                       }
-               }
-               if ((conf_check_uid != (uid_t)-1) && ((euid = geteuid()) != conf_check_uid)) {
-                       if (seteuid(conf_check_uid) < 0) {
-                               ERROR("Failed setting effective user ID for file check");
-                               goto error;
-                       }
-               }
-               fd = open(filename, O_RDONLY);
-               if (conf_check_uid != euid) {
-                       if (seteuid(euid) < 0) {
-                               ERROR("Failed restoring effective user ID after file check");
-
-                               goto error;
-                       }
-               }
-               if (conf_check_gid != egid) {
-                       if (setegid(egid) < 0) {
-                               ERROR("Failed restoring effective group ID after file check");
-                               goto error;
-                       }
-               }
-       }
-
-       if (fd < 0) goto perm_error;
-       if (fstat(fd, &file->buf) < 0) goto perm_error;
-
-       close(fd);
-
-#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;
-}
-
-/*
- *     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->item.child; 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_quote == T_BARE_WORD) ||
-                          (cp->rhs_quote == T_DOUBLE_QUOTED_STRING) ||
-                          (cp->rhs_quote == T_BACK_QUOTED_STRING));
-
-               value = cf_expand_variables(ci->filename, &ci->lineno, cs, buffer, sizeof(buffer), cp->value, NULL);
-               if (!value) return -1;
-
-               talloc_const_free(cp->value);
-               cp->value = talloc_typed_strdup(cp, value);
-       }
-
-       for (ci = cs->item.child; 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;
-}
-
-
-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;
-}
-
-static bool invalid_location(CONF_SECTION *this, char const *name, char const *filename, int lineno)
-{
-       /*
-        *      if / elsif MUST be inside of a
-        *      processing section, which MUST in turn
-        *      be inside of a "server" directive.
-        */
-       if (!this || !this->item.parent) {
-       invalid_location:
-               ERROR("%s[%d]: Invalid location for '%s'",
-                     filename, lineno, name);
-               return true;
-       }
-
-       /*
-        *      Can only have "if" in 3 named sections.
-        */
-       this = cf_item_to_section(this->item.parent);
-       while ((strcmp(this->name1, "server") != 0) &&
-              (strcmp(this->name1, "policy") != 0) &&
-              (strcmp(this->name1, "instantiate") != 0)) {
-               this = cf_item_to_section(this->item.parent);
-               if (!this) goto invalid_location;
-       }
-
-       return false;
-}
-
-#ifdef WITH_CONF_WRITE
-static void cf_comment_add(CONF_SECTION *cs, int lineno, char const *ptr)
-{
-       CONF_COMMENT *cc;
-
-       cc = talloc_zero(cs, CONF_COMMENT);
-       cc->item.type = CONF_ITEM_COMMENT;
-       cc->item.parent = cs;
-       cc->item.filename = cs->item.filename;
-       cc->item.lineno = lineno;
-       cc->comment = talloc_typed_strdup(cc, ptr);
-
-
-       cf_item_add(cs, &(cc->item));
-}
-
-static void cf_include_add(CONF_SECTION *cs, char const *filename, CONF_INCLUDE_TYPE file_type)
-{
-       CONF_INCLUDE *cc;
-
-       cc = talloc_zero(cs, CONF_INCLUDE);
-       cc->item.type = CONF_ITEM_INCLUDE;
-       cc->item.parent = cs;
-       cc->item.filename = cs->item.filename;
-       cc->item.lineno = 0;
-       cc->filename = talloc_typed_strdup(cc, filename);
-       cc->file_type = file_type;
-
-       cf_item_add(cs, &(cc->item));
-}
-#endif
-
-/*
- *     Read a part of the config file.
- */
-static int cf_section_read(char const *filename, int *lineno, FILE *fp,
-                          CONF_SECTION *current, char *buff[7])
-
-{
-       CONF_SECTION    *this, *css;
-       CONF_PAIR       *cpn;
-       char const      *ptr;
-       char const      *value;
-#ifdef WITH_CONF_WRITE
-       char const      *orig_value = NULL;
-#endif
-
-       FR_TOKEN        t1 = T_INVALID, t2, t3;
-       bool            has_spaces = false;
-       bool            pass2;
-       char            *cbuff;
-       size_t          len;
-
-       this = current;         /* add items here */
-
-       cbuff = buff[0];
-
-       /*
-        *      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(cbuff, talloc_array_length(buff[0]) - (cbuff - buff[0]), 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(cbuff);
-               if ((cbuff + len + 1) >= (buff[0] + talloc_array_length(buff[0]))) {
-                       ERROR("%s[%d]: Line too long", filename, *lineno);
-               error:
-                       return -1;
-               }
-
-               if (has_spaces) {
-                       ptr = cbuff;
-                       while (isspace((int) *ptr)) ptr++;
-
-                       if (ptr > cbuff) {
-                               memmove(cbuff, ptr, len - (ptr - cbuff));
-                               len -= (ptr - cbuff);
-                       }
-               }
-
-               /*
-                *      Not doing continuations: check for edge
-                *      conditions.
-                */
-               if (cbuff == buff[0]) {
-                       if (at_eof) break;
-
-                       ptr = buff[0];
-                       while (*ptr && isspace((int) *ptr)) ptr++;
-
-#ifdef WITH_CONF_WRITE
-                       /*
-                        *      This is where all of the comments are handled
-                        */
-                       if (*ptr == '#') {
-                               cf_comment_add(this, *lineno, ptr + 1);
-                       }
-#endif
-
-                       if (!*ptr || (*ptr == '#')) continue;
-
-               } else if (at_eof || (len == 0)) {
-                       ERROR("%s[%d]: Continuation at EOF is illegal", filename, *lineno);
-                       goto error;
-               }
-
-               /*
-                *      See if there's a continuation.
-                */
-               while ((len > 0) &&
-                      ((cbuff[len - 1] == '\n') || (cbuff[len - 1] == '\r'))) {
-                       len--;
-                       cbuff[len] = '\0';
-               }
-
-               if ((len > 0) && (cbuff[len - 1] == '\\')) {
-                       /*
-                        *      Check for "suppress spaces" magic.
-                        */
-                       if (!has_spaces && (len > 2) && (cbuff[len - 2] == '"')) {
-                               has_spaces = true;
-                       }
-
-                       cbuff[len - 1] = '\0';
-                       cbuff += len - 1;
-                       continue;
-               }
-
-               ptr = cbuff = buff[0];
-               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] == '`')) {
-                       ssize_t slen;
-
-                       if (ptr[0] == '%') {
-                               slen = rad_copy_variable(buff[1], ptr);
-                       } else {
-                               slen = rad_copy_string(buff[1], ptr);
-                       }
-                       if (slen <= 0) {
-                               char *spaces, *text;
-
-                               fr_canonicalize_error(current, &spaces, &text, slen, ptr);
-
-                               ERROR("%s[%d]: %s", filename, *lineno, text);
-                               ERROR("%s[%d]: %s^ Invalid expansion", filename, *lineno, spaces);
-
-                               talloc_free(spaces);
-                               talloc_free(text);
-
-                               goto error;
-                       }
-
-                       ptr += slen;
-
-                       t2 = gettoken(&ptr, buff[2], talloc_array_length(buff[2]), true);
-                       switch (t2) {
-                       case T_HASH:
-                       case T_EOL:
-                               goto do_bare_word;
-
-                       default:
-                               ERROR("%s[%d]: Invalid expansion: %s", filename, *lineno, ptr);
-                               goto error;
-                       }
-               } else {
-                       t1 = gettoken(&ptr, buff[1], talloc_array_length(buff[1]), 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);
-                              goto error;
-                      }
-
-                      /*
-                       *       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)) goto error;
-
-                      this = cf_item_to_section(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(buff[1], "$INCLUDE") == 0) ||
-                  (strcasecmp(buff[1], "$-INCLUDE") == 0)) {
-                       bool relative = true;
-
-                       t2 = getword(&ptr, buff[2], talloc_array_length(buff[2]), true);
-                       if (t2 != T_EOL) {
-                              ERROR("%s[%d]: Unexpected text after $INCLUDE", filename, *lineno);
-                              goto error;
-                       }
-
-                       if (buff[2][0] == '$') relative = false;
-
-                       value = cf_expand_variables(filename, lineno, this, buff[4], talloc_array_length(buff[4]),
-                                                   buff[2], NULL);
-                       if (!value) goto error;
-
-                       if (!FR_DIR_IS_RELATIVE(value)) relative = false;
-
-                       if (relative) {
-                               value = cf_local_file(filename, value, buff[3], talloc_array_length(buff[3]));
-                               if (!value) {
-                                       ERROR("%s[%d]: Directories too deep", filename, *lineno);
-                                       goto error;
-                               }
-                       }
-
-
-#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;
-                               char *my_directory;
-
-                               my_directory = talloc_strdup(this, value);
-
-                               cf_log_debug(current, "Including files in directory \"%s\"", my_directory);
-
-#ifdef WITH_CONF_WRITE
-                               /*
-                                *      We print this out, but don't
-                                *      actually open a file based on
-                                *      it.
-                                */
-                               cf_include_add(this, my_directory, CONF_INCLUDE_DIR);
-#endif
-
-#ifdef S_IWOTH
-                               /*
-                                *      Security checks.
-                                */
-                               if (stat(my_directory, &stat_buf) < 0) {
-                                       ERROR("%s[%d]: Failed reading directory %s: %s", filename, *lineno,
-                                             my_directory, fr_syserror(errno));
-                                       talloc_free(my_directory);
-                                       goto error;
-                               }
-
-                               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, my_directory);
-                                       talloc_free(my_directory);
-                                       goto error;
-                               }
-#endif
-                               dir = opendir(my_directory);
-                               if (!dir) {
-                                       ERROR("%s[%d]: Error reading directory %s: %s",
-                                             filename, *lineno, value,
-                                             fr_syserror(errno));
-                                       talloc_free(my_directory);
-                                       goto error;
-                               }
-
-                               /*
-                                *      Read the directory, ignoring "." files.
-                                */
-                               while ((dp = readdir(dir)) != NULL) {
-                                       char const *p;
-
-                                       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;
-
-
-                                       snprintf(buff[2], talloc_array_length(buff[2]), "%s%s",
-                                                my_directory, dp->d_name);
-                                       if ((stat(buff[2], &stat_buf) != 0) ||
-                                           S_ISDIR(stat_buf.st_mode)) continue;
-
-                                       /*
-                                        *      Read the file into the current
-                                        *      configuration section.
-                                        */
-                                       if (cf_file_include(this, buff[2], CONF_INCLUDE_FROMDIR, buff) < 0) {
-                                               closedir(dir);
-                                               goto error;
-                                       }
-                               }
-                               closedir(dir);
-                               talloc_free(my_directory);
-
-                       }  else
-#endif
-                       { /* it was a normal file */
-                               if (buff[1][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, CONF_INCLUDE_FILE, buff) < 0) goto error;
-                       }
-                       continue;
-               } /* we were in an include */
-
-              if (strcasecmp(buff[1], "$template") == 0) {
-                      CONF_ITEM *ci;
-                      CONF_SECTION *parent_cs, *templatecs;
-                      t2 = getword(&ptr, buff[2], talloc_array_length(buff[2]), true);
-
-                      if (t2 != T_EOL) {
-                               ERROR("%s[%d]: Unexpected text after $TEMPLATE", filename, *lineno);
-                               goto error;
-                      }
-
-                      parent_cs = cf_root(current);
-
-                      templatecs = cf_section_find(parent_cs, "templates", NULL);
-                      if (!templatecs) {
-                               ERROR("%s[%d]: No \"templates\" section for reference \"%s\"", filename, *lineno, buff[2]);
-                               goto error;
-                      }
-
-                      ci = cf_reference_item(parent_cs, templatecs, buff[2]);
-                      if (!ci || (ci->type != CONF_ITEM_SECTION)) {
-                               ERROR("%s[%d]: Reference \"%s\" not found", filename, *lineno, buff[2]);
-                               goto error;
-                      }
-
-                      if (!this) {
-                               ERROR("%s[%d]: Internal sanity check error in template reference", filename, *lineno);
-                               goto error;
-                      }
-
-                      if (this->template) {
-                               ERROR("%s[%d]: Section already has a template", filename, *lineno);
-                               goto error;
-                      }
-
-                      this->template = cf_item_to_section(ci);
-                      continue;
-              }
-
-               /*
-                *      Ensure that the user can't add CONF_PAIRs
-                *      with 'internal' names;
-                */
-               if (buff[1][0] == '_') {
-                       ERROR("%s[%d]: Illegal configuration pair name \"%s\"", filename, *lineno, buff[1]);
-                       goto error;
-               }
-
-               /*
-                *      Handle if/elsif specially.
-                */
-               if ((strcmp(buff[1], "if") == 0) || (strcmp(buff[1], "elsif") == 0)) {
-                       ssize_t slen;
-                       char const *error = NULL;
-                       char *p;
-                       fr_cond_t *cond = NULL;
-
-                       if (invalid_location(this, buff[1], filename, *lineno)) goto error;
-
-                       /*
-                        *      Skip (...) to find the {
-                        */
-                       slen = fr_cond_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';
-
-                       /*
-                        *      Nuke trailing spaces.  This hack
-                        *      really belongs in the parser.
-                        */
-                       while ((p > ptr) && (isspace((int) p[-1]))) {
-                               p--;
-                               *p = '\0';
-                       }
-
-                       /*
-                        *      If there's a ${...}.  If so, expand it.
-                        */
-                       if (strchr(ptr, '$') != NULL) {
-                               ptr = cf_expand_variables(filename, lineno,
-                                                         this,
-                                                         buff[3], talloc_array_length(buff[3]),
-                                                         ptr, NULL);
-                               if (!ptr) {
-                                       ERROR("%s[%d]: Parse error expanding ${...} in condition",
-                                             filename, *lineno);
-                                       goto error;
-                               }
-                       } /* else leave it alone */
-
-                       css = cf_section_alloc(this, buff[1], ptr);
-                       if (!css) {
-                               ERROR("%s[%d]: Failed allocating memory for section", filename, *lineno);
-                               goto error;
-                       }
-                       css->item.filename = filename;
-                       css->item.lineno = *lineno;
-
-                       slen = fr_cond_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);
-                               goto error;
-                       }
-
-                       if ((size_t) slen >= (talloc_array_length(buff[2]) - 1)) {
-                               talloc_free(css);
-                               ERROR("%s[%d]: Condition is too large after \"%s\"", filename, *lineno, buff[1]);
-                               goto error;
-                       }
-
-                       /*
-                        *      Copy the expanded and parsed condition
-                        *      into buff[2].  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(buff[2], ptr, slen);
-                       buff[2][slen] = '\0';
-                       ptr = p;
-
-                       if ((t3 = gettoken(&ptr, buff[3], talloc_array_length(buff[3]), true)) != T_LCBRACE) {
-                               talloc_free(css);
-                               ERROR("%s[%d]: Expected '{' %d", filename, *lineno, t3);
-                               goto error;
-                       }
-
-                       /*
-                        *      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, buff[2]);
-
-                       cf_data_add(css, cond, NULL, false);
-
-               add_section:
-                       cf_item_add(this, &(css->item));
-
-                       /*
-                        *      The current section is now the child section.
-                        */
-                       this = css;
-                       css = NULL;
-                       goto check_for_more;
-               }
-
-               /*
-                *      "map" sections have three arguments!
-                */
-               if (strcmp(buff[1], "map") == 0) {
-                       char const *mod;
-                       char const *exp = NULL;
-                       char const *p;
-
-                       t2 = gettoken(&ptr, buff[2], talloc_array_length(buff[2]), false);
-
-                       if (invalid_location(this, buff[1], filename, *lineno)) {
-                               if (t2 != T_LCBRACE) {
-                                       ERROR("%s[%d]: Invalid syntax for 'map'", filename, *lineno);
-                                       goto error;
-                               }
-
-                               goto alloc_section;
-                       }
-
-                       if (t2 != T_BARE_WORD) {
-                               ERROR("%s[%d]: Expected module name after 'map'", filename, *lineno);
-                               goto error;
-                       }
-
-                       mod = cf_expand_variables(filename, lineno,
-                                                 this,
-                                                 buff[3], talloc_array_length(buff[3]),
-                                                 buff[2], NULL);
-                       if (!mod) {
-                               ERROR("%s[%d]: Parse error expanding ${...} in map module name",
-                                     filename, *lineno);
-                               goto error;
-                       }
-
-                       p = ptr;
-                       t3 = gettoken(&p, buff[4], talloc_array_length(buff[4]), false);
-                       if (fr_str_tok[t3]) {
-                               ptr = p;
-
-                               exp = cf_expand_variables(filename, lineno,
-                                                         this,
-                                                         buff[5], talloc_array_length(buff[5]),
-                                                         buff[4], NULL);
-                               if (!exp) {
-                                       ERROR("%s[%d]: Parse error expanding ${...} in map module name",
-                                             filename, *lineno);
-                                       goto error;
-                               }
-                       }
-
-                       if (gettoken(&ptr, buff[6], talloc_array_length(buff[6]), false) != T_LCBRACE) {
-                               ERROR("%s[%d]: Expecting section start brace '{' in 'map' definition",
-                                     filename, *lineno);
-                               goto error;
-                       }
-
-                       /*
-                        *      Allocate the section
-                        */
-                       css = cf_section_alloc(this, buff[1], mod);
-                       if (!css) {
-                               ERROR("%s[%d]: Failed allocating memory for section", filename, *lineno);
-                               goto error;
-                       }
-                       css->item.filename = filename;
-                       css->item.lineno = *lineno;
-                       css->name2_quote = T_BARE_WORD;
-
-                       css->argc = 0;
-                       if (exp) {
-                               css->argv = talloc_array(css, char const *, 1);
-                               css->argv[0] = talloc_typed_strdup(css->argv, exp);
-                               css->argv_quote = talloc_array(css, FR_TOKEN, 1);
-                               css->argv_quote[0] = t3;
-                               css->argc++;
-                       }
-
-                       goto add_section;
-               }
-
-       skip_keywords:
-               /*
-                *      Grab the next token.
-                */
-               t2 = gettoken(&ptr, buff[2], talloc_array_length(buff[2]), false);
-               switch (t2) {
-               case T_HASH:
-               case T_EOL:
-               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_SUB:
-               case T_OP_NE:
-               case T_OP_GE:
-               case T_OP_GT:
-               case T_OP_LE:
-               case T_OP_LT:
-               case T_OP_CMP_EQ:
-               case T_OP_CMP_FALSE:
-                       if (!this || ((strcmp(this->name1, "update") != 0) && (strcmp(this->name1, "map") != 0))) {
-                               ERROR("%s[%d]: Invalid operator in assignment",
-                                      filename, *lineno);
-                               goto error;
-                       }
-                       /* FALL-THROUGH */
-
-               case T_OP_EQ:
-               case T_OP_SET:
-                       while (isspace((int) *ptr)) ptr++;
-
-                       /*
-                        *      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.
-                        */
-                       switch (*ptr) {
-                       case '"':
-                       case '\'':
-                       case '`':
-                       case '/':
-                               t3 = getstring(&ptr, buff[3], talloc_array_length(buff[3]), false);
-                               break;
-
-                       default:
-                       {
-                               const char *q = ptr;
-
-                               t3 = T_BARE_WORD;
-                               while (*q && (*q >= ' ') && (*q != ',') &&
-                                      !isspace(*q)) q++;
-
-                               if ((size_t) (q - ptr) >= talloc_array_length(buff[3])) {
-                                       ERROR("%s[%d]: Parse error: value too long", filename, *lineno);
-                                       goto error;
-                               }
-
-                               memcpy(buff[3], ptr, (q - ptr));
-                               buff[3][q - ptr] = '\0';
-                               ptr = q;
-                       }
-                       }
-
-                       if (t3 == T_INVALID) {
-                               ERROR("%s[%d]: Parse error: %s", filename, *lineno, fr_strerror());
-                               goto error;
-                       }
-
-                       /*
-                        *      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:
-#ifdef WITH_CONF_WRITE
-                               orig_value = buff[3];
-#endif
-                               value = cf_expand_variables(filename, lineno, this, buff[4], talloc_array_length(buff[4]), buff[3], &soft_fail);
-                               if (!value) {
-                                       if (!soft_fail) goto error;
-
-                                       /*
-                                        *      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 = buff[3];
-                               }
-                               break;
-
-                       case T_HASH:
-                       case T_EOL:
-                               value = NULL;
-                               break;
-
-                       default:
-                               value = buff[3];
-                               break;
-                       }
-
-                       /*
-                        *      Add this CONF_PAIR to our CONF_SECTION
-                        */
-               do_set:
-                       cpn = cf_pair_alloc(this, buff[1], value, t2, t1, t3);
-                       if (!cpn) goto error;
-                       cpn->item.filename = filename;
-                       cpn->item.lineno = *lineno;
-                       cpn->pass2 = pass2;
-                       cf_item_add(this, &(cpn->item));
-
-#ifdef WITH_CONF_WRITE
-                       if (orig_value) cpn->orig_value = talloc_typed_strdup(cpn, orig_value);
-                       orig_value = NULL;
-#endif
-                       /*
-                        *      Require a comma, unless there's a comment.
-                        */
-                       while (isspace(*ptr)) ptr++;
-
-                       if (*ptr == ',') {
-                               ptr++;
-                               break;
-                       }
-
-                       /*
-                        *      module # stuff!
-                        *      foo = bar # other stuff
-                        */
-#ifdef WITH_CONF_WRITE
-                       if (*ptr == '#') {
-                               t3 = T_HASH;
-                               ptr++;
-                       }
-
-                       /*
-                        *      Allocate a CONF_COMMENT, and add it to the list of children.
-                        */
-                       if ((t3 == T_HASH) && (*ptr >= ' ')) {
-                               cf_comment_add(this, *lineno, ptr);
-                       }
-#endif
-
-                       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);
-                       goto error;
-
-                       /*
-                        *      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, buff[3], talloc_array_length(buff[3]), true);
-                       if (t3 != T_LCBRACE) {
-                               ERROR("%s[%d]: Expecting section start brace '{' after \"%s %s\"",
-                                     filename, *lineno, buff[1], buff[2]);
-                               goto error;
-                       }
-                       /* FALL-THROUGH */
-
-               alloc_section:
-               case T_LCBRACE:
-                       css = cf_section_alloc(this, buff[1],
-                                              t2 == T_LCBRACE ? NULL : buff[2]);
-                       if (!css) {
-                               ERROR("%s[%d]: Failed allocating memory for section",
-                                     filename, *lineno);
-                               goto error;
-                       }
-
-                       css->item.filename = filename;
-                       css->item.lineno = *lineno;
-                       cf_item_add(this, &(css->item));
-
-                       /*
-                        *      There may not be a name2
-                        */
-                       css->name2_quote = (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());
-
-                       goto error;
-
-               default:
-                       ERROR("%s[%d]: Parse error after \"%s\": unexpected token \"%s\"",
-                             filename, *lineno, buff[1], fr_int2str(fr_tokens_table, t2, "<INVALID>"));
-
-                       goto error;
-               }
-
-       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_lineno(this));
-               goto error;
-       }
-
-       return 0;
-}
-
-/*
- *     Include one config file in another.
- */
-static int cf_file_include(CONF_SECTION *cs, char const *filename_in,
-#ifndef WITH_CONF_WRITE
-                          UNUSED
-#endif
-                          CONF_INCLUDE_TYPE file_type, char *buff[7])
-{
-       FILE            *fp;
-       int             lineno = 0;
-       char const      *filename;
-
-       /*
-        *      So we only need to do this once.
-        */
-       filename = talloc_strdup(cs, filename_in);
-
-       DEBUG2("Including configuration file \"%s\"", filename);
-
-       fp = cf_file_open(cs, filename);
-       if (!fp) return -1;
-
-       if (!cs->item.filename) cs->item.filename = filename;
-
-#ifdef WITH_CONF_WRITE
-       /*
-        *      Instruct the parser that we've started to include a
-        *      file at this point.
-        */
-       cf_include_add(cs, filename, file_type);
-#endif
-
-       /*
-        *      Read the section.  It's OK to have EOF without a
-        *      matching close brace.
-        */
-       if (cf_section_read(filename, &lineno, fp, cs, buff) < 0) {
-               ERROR("Failed parsing configuration file \"%s\"", filename);
-               fclose(fp);
-               return -1;
-       }
-
-#ifdef WITH_CONF_WRITE
-       /*
-        *      Instruct the parser that we've finished including a
-        *      file at this point.
-        */
-       cf_include_add(cs, NULL, file_type);
-#endif
-
-       fclose(fp);
-       return 0;
-}
-
-/*
- *     Bootstrap a config file.
- */
-int cf_file_read(CONF_SECTION *cs, char const *filename)
-{
-       int             i;
-       char            *p;
-       CONF_PAIR       *cp;
-       rbtree_t        *tree;
-       char            **buff;
-
-       cp = cf_pair_alloc(cs, "confdir", filename, T_OP_EQ, T_BARE_WORD, T_SINGLE_QUOTED_STRING);
-       if (!cp) return -1;
-
-       p = strrchr(cp->value, FR_DIR_SEP);
-       if (p) *p = '\0';
-
-       cf_item_add(cs, &(cp->item));
-
-       MEM(tree = rbtree_create(cs, _filename_cmp, NULL, 0));
-
-       cf_data_add(cs, tree, "filename", false);
-
-       /*
-        *      Allocate temporary buffers on the heap (so we don't use *all* the stack space)
-        */
-       buff = talloc_array(cs, char *, 7);
-       for (i = 0; i < 7; i++) MEM(buff[i] = talloc_array(buff, char, 8192));
-
-       if (cf_file_include(cs, filename, CONF_INCLUDE_FILE, buff) < 0) {
-               talloc_free(buff);
-               return -1;
-       }
-
-       talloc_free(buff);
-
-       /*
-        *      Now that we've read the file, go back through it and
-        *      expand the variables.
-        */
-       if (cf_section_pass2(cs) < 0) {
-               cf_log_err(cs, "Parsing config items failed");
-               return -1;
-       }
-
-       return 0;
-}
-
-void cf_file_free(CONF_SECTION *cs)
-{
-       talloc_free(cs);
-}
-
-/** Set the euid/egid used when performing file checks
- *
- * Sets the euid, and egid used when cf_file_check is called to check
- * permissions on conf items of type #FR_TYPE_FILE_INPUT.
- *
- * @note This is probably only useful for the freeradius daemon itself.
- *
- * @param uid to set, (uid_t)-1 to use current euid.
- * @param gid to set, (gid_t)-1 to use current egid.
- */
-void cf_file_check_user(uid_t uid, gid_t gid)
-{
-       if (uid != 0) conf_check_uid = uid;
-       if (gid != 0) conf_check_gid = gid;
-}
-
-/*
- *     See if any of the files have changed.
- */
-int cf_file_changed(CONF_SECTION *cs, rb_walker_t callback)
-{
-       CONF_SECTION            *top;
-       cf_file_callback_t      cb;
-       rbtree_t                *tree;
-
-       top = cf_root(cs);
-       tree = cf_data_value(cf_data_find(top, rbtree_t, "filename"));
-       if (!tree) return true;
-
-       cb.rcode = CF_FILE_NONE;
-       cb.callback = callback;
-       cb.modules = cf_section_find(cs, "modules", NULL);
-
-       (void) rbtree_walk(tree, RBTREE_IN_ORDER, _file_callback, &cb);
-
-       return cb.rcode;
-}
-
-#ifdef WITH_CONF_WRITE
-static char const parse_tabs[] = "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             ";
-
-static ssize_t cf_string_write(FILE *fp, char const *string, size_t len, FR_TOKEN t)
-{
-       size_t  outlen;
-       char    c;
-       char    buffer[2048];
-
-       switch (t) {
-       default:
-               c = '\0';
-               break;
-
-       case T_DOUBLE_QUOTED_STRING:
-               c = '"';
-               break;
-
-       case T_SINGLE_QUOTED_STRING:
-               c = '\'';
-               break;
-
-       case T_BACK_QUOTED_STRING:
-               c = '`';
-               break;
-       }
-
-       if (c) fprintf(fp, "%c", c);
-
-       outlen = fr_snprint(buffer, sizeof(buffer), string, len, c);
-       fwrite(buffer, outlen, 1, fp);
-
-       if (c) fprintf(fp, "%c", c);
-       return 1;
-}
-
-static size_t cf_pair_write(FILE *fp, CONF_PAIR *cp)
-{
-       if (!cp->value) {
-               fprintf(fp, "%s\n", cp->attr);
-               return 0;
-       }
-
-       cf_string_write(fp, cp->attr, strlen(cp->attr), cp->lhs_quote);
-       fprintf(fp, " %s ", fr_int2str(fr_tokens_table, cp->op, "<INVALID>"));
-       cf_string_write(fp, cp->orig_value, strlen(cp->orig_value), cp->rhs_quote);
-       fprintf(fp, "\n");
-
-       return 1;               /* FIXME */
-}
-
-static FILE *cf_file_write(CONF_SECTION *cs, char const *filename)
-{
-       FILE    *fp;
-       char    *p;
-       char    const *q;
-       char    buffer[8192];
-
-       q = filename;
-       if ((q[0] == '.') && (q[1] == '/')) q += 2;
-
-       snprintf(buffer, sizeof(buffer), "%s/%s", main_config.write_dir, q);
-
-       p = strrchr(buffer, '/');
-       *p = '\0';
-       if ((rad_mkdir(buffer, 0700, -1, -1) < 0) &&
-           (errno != EEXIST)) {
-               cf_log_err(cs, "Failed creating directory %s: %s",
-                             buffer, strerror(errno));
-               return NULL;
-       }
-
-       /*
-        *      And again, because rad_mkdir() butchers the buffer.
-        */
-       snprintf(buffer, sizeof(buffer), "%s/%s", main_config.write_dir, q);
-
-       fp = fopen(buffer, "a");
-       if (!fp) {
-               cf_log_err(cs, "Failed creating file %s: %s",
-                             buffer, strerror(errno));
-               return NULL;
-       }
-
-       return fp;
-}
-
-size_t cf_section_write(FILE *in_fp, CONF_SECTION *cs, int depth)
-{
-       bool            prev = false;
-       CONF_ITEM       *ci;
-       FILE            *fp = NULL;
-       int             fp_max = 0;
-       FILE            *array[32];
-
-       /*
-        *      Default to writing to the FP we're given.
-        */
-       fp = in_fp;
-       array[0] = fp;
-       fp_max = 0;
-
-       /*
-        *      If we have somewhere to print, then print the section
-        *      name1, etc.
-        */
-       if (fp) {
-               fwrite(parse_tabs, depth, 1, fp);
-               cf_string_write(fp, cs->name1, strlen(cs->name1), T_BARE_WORD);
-
-               /*
-                *      FIXME: check for "if" or "elsif".  And if so, print
-                *      out the parsed condition, instead of the input text
-                *
-                *      cf_data_find(cs, CF_DATA_TYPE_UNLANG, "if");
-                */
-
-               if (cs->name2) {
-                       fr_cond_t *c;
-
-                       fputs(" ", fp);
-
-                       c = cf_data_value(cf_data_find(cs, fr_cond_t, NULL));
-                       if (c) {
-                               char buffer[1024];
-
-                               cond_snprint(buffer, sizeof(buffer), c);
-                               fprintf(fp, "(%s)", buffer);
-
-                       } else {        /* dump the string as-is */
-                               cf_string_write(fp, cs->name2, strlen(cs->name2), cs->name2_quote);
-                       }
-               }
-
-               fputs(" {\n", fp);
-       }
-
-       /*
-        *      Loop over the children.  Either recursing, or opening
-        *      a new file.
-        */
-       for (ci = cs->item.child; ci; ci = ci->next) {
-               switch (ci->type) {
-               case CONF_ITEM_SECTION:
-                       if (!fp) continue;
-
-                       cf_section_write(fp, cf_item_to_section(ci), depth + 1);
-                       break;
-
-               case CONF_ITEM_PAIR:
-                       if (!fp) continue;
-
-                       /*
-                        *      Ignore internal things.
-                        */
-                       if (!ci->filename || (ci->filename[0] == '<')) break;
-
-                       fwrite(parse_tabs, depth + 1, 1, fp);
-                       cf_pair_write(fp, cf_item_to_pair(ci));
-                       if (!prev) fputs("\n", fp);
-                       prev = true;
-                       break;
-
-               case CONF_ITEM_COMMENT:
-                       rad_assert(fp != NULL);
-
-                       prev = false;
-                       fwrite(parse_tabs, depth + 1, 1, fp);
-                       fprintf(fp, "#%s", ((CONF_COMMENT *)ci)->comment);
-                       break;
-
-               case CONF_ITEM_INCLUDE:
-                       /*
-                        *      Filename == open the new filename and use that.
-                        *
-                        *      NULL == close the previous filename
-                        */
-                       if (((CONF_INCLUDE *) ci)->filename) {
-                               CONF_INCLUDE *cc = (CONF_INCLUDE *) ci;
-
-                               /*
-                                *      Print out
-                                *
-                                *      $INCLUDE foo.conf
-                                *      $INCLUDE foo/
-                                *
-                                *      but not the files included from the last one.
-                                */
-                               if (fp && (cc->file_type != CONF_INCLUDE_FROMDIR)) {
-                                       fprintf(fp, "$INCLUDE %s\n", ((CONF_INCLUDE *)ci)->filename);
-                               }
-
-                               /*
-                                *      If it's a file, we write the
-                                *      file.  We ignore the
-                                *      directories.  They're just for printing.
-                                */
-                               if (cc->file_type != CONF_INCLUDE_DIR) {
-                                       fp = cf_file_write(cs, ((CONF_INCLUDE *) ci)->filename);
-                                       if (!fp) return 0;
-
-                                       fp_max++;
-                                       array[fp_max] = fp;
-                               }
-                       } else {
-                               /*
-                                *      We're done the current file.
-                                */
-                               rad_assert(fp != NULL);
-                               rad_assert(fp_max > 0);
-                               fclose(fp);
-
-                               fp_max--;
-                               fp = array[fp_max];
-                       }
-                       break;
-
-               default:
-                       break;
-               }
-       }
-
-       if (fp) {
-               fwrite(parse_tabs, depth, 1, fp);
-               fputs("}\n\n", fp);
-       }
-
-       return 1;
-}
-#endif /* WITH_CONF_WRITE */
-
-static char const parse_spaces[] = "                                                                                                                                                                                                                                                                ";
-
-/** Validation function for ipaddr conf_file types
- *
- */
-static inline int fr_item_validate_ipaddr(CONF_SECTION *cs, char const *name, fr_type_t 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"), fr_inet_ntoh(ipaddr, ipbuf, sizeof(ipbuf)));
-       }
-
-       switch (type) {
-       case FR_TYPE_IPV4_ADDR:
-       case FR_TYPE_IPV6_ADDR:
-       case FR_TYPE_COMBO_IP_ADDR:
-               switch (ipaddr->af) {
-               case AF_INET:
-               if (ipaddr->prefix != 32) {
-                       ERROR("Invalid IPv4 mask length \"/%i\".  Only \"/32\" permitted for non-prefix types",
-                             ipaddr->prefix);
-
-                       return -1;
-               }
-                       break;
-
-               case AF_INET6:
-               if (ipaddr->prefix != 128) {
-                       ERROR("Invalid IPv6 mask length \"/%i\".  Only \"/128\" permitted for non-prefix types",
-                             ipaddr->prefix);
-
-                       return -1;
-               }
-                       break;
-
-               default:
-                       return -1;
-               }
-       default:
-               return 0;
-       }
-}
-
-/** Parses a #CONF_PAIR into a C data type
- *
- * @copybrief cf_pair_value
- * @see cf_pair_value
- *
- * @param[out] out Where to write the parsed value.
- * @param[in] ctx to allocate any dynamic buffers in.
- * @param[in] cs containing the cp.
- * @param[in] cp to parse.
- * @param[in] type to parse to.  May contain flags.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
- */
-static int cf_pair_parse_value(TALLOC_CTX *ctx, void *out, CONF_SECTION *cs, CONF_PAIR *cp, unsigned int type)
-{
-       int             rcode = 0;
-       bool            attribute, required, secret, file_input, cant_be_empty, tmpl, file_exists;
-
-       fr_ipaddr_t     *ipaddr;
-       ssize_t         slen;
-
-       if (!cs) return -1;
-
-       attribute = (type & FR_TYPE_ATTRIBUTE);
-       required = (type & FR_TYPE_REQUIRED);
-       secret = (type & FR_TYPE_SECRET);
-       file_input = (type == FR_TYPE_FILE_INPUT);      /* check, not and */
-       file_exists = (type == FR_TYPE_FILE_EXISTS);    /* check, not and */
-       cant_be_empty = (type & FR_TYPE_NOT_EMPTY);
-       tmpl = (type & FR_TYPE_TMPL);
-
-       rad_assert(cp);
-       rad_assert(!(type & FR_TYPE_ATTRIBUTE) || tmpl);         /* Attribute flag only valid for templates */
-
-       if (required) cant_be_empty = true;             /* May want to review this in the future... */
-
-       type = FR_BASE_TYPE(type);                                      /* normal types are small */
-
-       /*
-        *      Everything except templates must have a base type.
-        */
-       if (!type && !tmpl) {
-               cf_log_err(cp, "Configuration pair \"%s\" must have a data type", cf_pair_attr(cp));
-               return -1;
-       }
-
-       rad_assert(cp->value);
-
-       /*
-        *      Check for zero length strings
-        */
-       if ((cp->value[0] == '\0') && cant_be_empty) {
-               cf_log_err(cp, "Configuration pair \"%s\" must not be empty (zero length)", cf_pair_attr(cp));
-               if (!required) cf_log_err(cp, "Comment item to silence this message");
-               rcode = -1;
-
-       error:
-               return rcode;
-       }
-
-       if (tmpl) {
-               vp_tmpl_t *vpt;
-
-               /*
-                *      This is so we produce TMPL_TYPE_ATTR_UNDEFINED template that
-                *      the bootstrap functions can use to create an attribute.
-                *
-                *      For other types of template such as xlats, we don't bother.
-                *      There's no reason bootstrap functions need access to the raw
-                *      xlat strings.
-                */
-               if (attribute) {
-                       slen = tmpl_afrom_attr_str(cp, &vpt, cp->value, REQUEST_CURRENT, PAIR_LIST_REQUEST,
-                                                  true, true);
-                       if (slen < 0) {
-                               char *spaces, *text;
-
-                               fr_canonicalize_error(ctx, &spaces, &text, slen, cp->value);
-
-                               cf_log_err(&cp->item, "Failed parsing attribute reference:");
-                               cf_log_err(&cp->item, "%s", text);
-                               cf_log_err(&cp->item, "%s^ %s", spaces, fr_strerror());
-
-                               talloc_free(spaces);
-                               talloc_free(text);
-                               goto error;
-                       }
-                       *(vp_tmpl_t **)out = vpt;
-               }
-               goto finish;
-       }
-
-       switch (type) {
-       case FR_TYPE_BOOL:
-               /*
-                *      Allow yes/no, true/false, and on/off
-                */
-               if ((strcasecmp(cp->value, "yes") == 0) ||
-                   (strcasecmp(cp->value, "true") == 0) ||
-                   (strcasecmp(cp->value, "on") == 0)) {
-                       *(bool *)out = true;
-               } else if ((strcasecmp(cp->value, "no") == 0) ||
-                          (strcasecmp(cp->value, "false") == 0) ||
-                          (strcasecmp(cp->value, "off") == 0)) {
-                       *(bool *)out = false;
-               } else {
-                       cf_log_err(&(cs->item), "Invalid value \"%s\" for boolean variable %s",
-                                  cp->value, cf_pair_attr(cp));
-                       rcode = -1;
-                       goto error;
-               }
-               cf_log_info(cs, "%.*s\t%s = %s", cs->depth, parse_spaces, cf_pair_attr(cp), cp->value);
-               break;
-
-       case FR_TYPE_UINT32:
-       {
-               unsigned long v = strtoul(cp->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", cp->value,
-                                  cf_pair_attr(cp), INT32_MAX);
-                       rcode = -1;
-                       goto error;
-               }
-
-               *(uint32_t *)out = v;
-               cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, cf_pair_attr(cp), *(uint32_t *)out);
-       }
-               break;
-
-       case FR_TYPE_UINT8:
-       {
-               unsigned long v = strtoul(cp->value, 0, 0);
-
-               if (v > UINT8_MAX) {
-                       cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", cp->value,
-                                  cf_pair_attr(cp), UINT8_MAX);
-                       rcode = -1;
-                       goto error;
-               }
-               *(uint8_t *)out = (uint8_t) v;
-               cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, cf_pair_attr(cp), *(uint8_t *)out);
-       }
-               break;
-
-       case FR_TYPE_UINT16:
-       {
-               unsigned long v = strtoul(cp->value, 0, 0);
-
-               if (v > UINT16_MAX) {
-                       cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", cp->value,
-                                  cf_pair_attr(cp), UINT16_MAX);
-                       rcode = -1;
-                       goto error;
-               }
-               *(uint16_t *)out = (uint16_t) v;
-               cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, cf_pair_attr(cp), *(uint16_t *)out);
-       }
-               break;
-
-       case FR_TYPE_UINT64:
-               *(uint64_t *)out = strtoull(cp->value, NULL, 10);
-               cf_log_info(cs, "%.*s\t%s = %" PRIu64, cs->depth, parse_spaces, cf_pair_attr(cp), *(uint64_t *)out);
-               break;
-
-       case FR_TYPE_SIZE:
-       {
-               if (fr_size_from_str((size_t *)out, cp->value) < 0) {
-                       cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s: %s", cp->value,
-                                  cf_pair_attr(cp), fr_strerror());
-                       rcode = -1;
-                       goto error;
-               }
-               cf_log_info(cs, "%.*s\t%s = %zu", cs->depth, parse_spaces, cf_pair_attr(cp), *(size_t *)out);
-               break;
-       }
-
-       case FR_TYPE_INT32:
-               *(int32_t *)out = strtol(cp->value, NULL, 10);
-               cf_log_info(cs, "%.*s\t%s = %d", cs->depth, parse_spaces, cf_pair_attr(cp), *(int32_t *)out);
-               break;
-
-       case FR_TYPE_STRING:
-       {
-               char **str = out;
-
-               /*
-                *      Hide secrets when using "radiusd -X".
-                */
-               if (secret && (rad_debug_lvl < L_DBG_LVL_3)) {
-                       cf_log_info(cs, "%.*s\t%s = <<< secret >>>", cs->depth, parse_spaces, cf_pair_attr(cp));
-               } else {
-                       cf_log_info(cs, "%.*s\t%s = \"%s\"", cs->depth, parse_spaces, cf_pair_attr(cp), cp->value);
-               }
-
-               /*
-                *      If there's out 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 (file_input && !cf_file_check(cs, cp->value, true)) {
-                       rcode = -1;
-                       goto error;
-               }
-
-               if (file_exists && !cf_file_check(cs, cp->value, false)) {
-                       rcode = -1;
-                       goto error;
-               }
-
-               /*
-                *      Free any existing buffers
-                */
-               talloc_free(*str);
-               *str = talloc_typed_strdup(cs, cp->value);
-       }
-               break;
-
-       case FR_TYPE_IPV4_ADDR:
-       case FR_TYPE_IPV4_PREFIX:
-               ipaddr = out;
-
-               if (fr_inet_pton4(ipaddr, cp->value, -1, true, false, true) < 0) {
-                       cf_log_err(&(cp->item), "%s", fr_strerror());
-                       rcode = -1;
-                       goto error;
-               }
-               /* Also prints the IP to the log */
-               if (fr_item_validate_ipaddr(cs, cf_pair_attr(cp), type, cp->value, ipaddr) < 0) {
-                       rcode = -1;
-                       goto error;
-               }
-               break;
-
-       case FR_TYPE_IPV6_ADDR:
-       case FR_TYPE_IPV6_PREFIX:
-               ipaddr = out;
-
-               if (fr_inet_pton6(ipaddr, cp->value, -1, true, false, true) < 0) {
-                       cf_log_err(&(cp->item), "%s", fr_strerror());
-                       rcode = -1;
-                       goto error;
-               }
-               /* Also prints the IP to the log */
-               if (fr_item_validate_ipaddr(cs, cf_pair_attr(cp), type, cp->value, ipaddr) < 0) {
-                       rcode = -1;
-                       goto error;
-               }
-               break;
-
-       case FR_TYPE_COMBO_IP_ADDR:
-       case FR_TYPE_COMBO_IP_PREFIX:
-               ipaddr = out;
-
-               if (fr_inet_pton(ipaddr, cp->value, -1, AF_UNSPEC, true, true) < 0) {
-                       cf_log_err(&(cp->item), "%s", fr_strerror());
-                       rcode = -1;
-                       goto error;
-               }
-               /* Also prints the IP to the log */
-               if (fr_item_validate_ipaddr(cs, cf_pair_attr(cp), type, cp->value, ipaddr) < 0) {
-                       rcode = -1;
-                       goto error;
-               }
-               break;
-
-       case FR_TYPE_TIMEVAL:
-       {
-               struct timeval tv;
-
-               if (fr_timeval_from_str(&tv, cp->value) < 0) {
-                       cf_log_err(&(cp->item), "%s", fr_strerror());
-                       rcode = -1;
-                       goto error;
-               }
-               cf_log_info(cs, "%.*s\t%s = %d.%06d", cs->depth, parse_spaces, cf_pair_attr(cp),
-                           (int)tv.tv_sec, (int)tv.tv_usec);
-               memcpy(out, &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 > FR_TYPE_INVALID);
-               rad_assert(type < FR_TYPE_MAX);
-
-               cf_log_err(&(cp->item), "type '%s' (%i) is not supported in the configuration files",
-                          fr_int2str(dict_attr_types, type, "?Unknown?"), type);
-               rcode = -1;
-               goto error;
-       }
-
-finish:
-       cp->parsed = true;
-
-       return rcode;
-}
-
-/** Allocate a pair using the dflt value and quotation
- *
- * The pair created by this function should fed to #cf_pair_parse for parsing.
- *
- * @param[out] out Where to write the CONF_PAIR we created with the default value.
- * @param[in] cs to parent the CONF_PAIR from.
- * @param[in] name of the CONF_PAIR to create.
- * @param[in] type of conf item being parsed (determines default quoting).
- * @param[in] dflt value to assign the CONF_PAIR.
- * @param[in] dflt_quote surrounding the CONF_PAIR.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
- */
-static int cf_pair_default(CONF_PAIR **out, CONF_SECTION *cs, char const *name,
-                          int type, char const *dflt, FR_TOKEN dflt_quote)
-{
-       int             lineno = 0;
-       char const      *expanded;
-       CONF_PAIR       *cp;
-       char            buffer[8192];
-
-       rad_assert(dflt);
-
-       type = FR_BASE_TYPE(type);
-
-       /*
-        *      Defaults may need their values expanding
-        */
-       expanded = cf_expand_variables("<internal>", &lineno, cs, buffer, sizeof(buffer), dflt, NULL);
-       if (!expanded) {
-               cf_log_err(&(cs->item), "Failed expanding variable %s", name);
-               return -1;
-       }
-
-       /*
-        *      If no default quote was set, determine it from the type
-        */
-       if (dflt_quote == T_INVALID) {
-               switch (type) {
-               case FR_TYPE_STRING:
-                       dflt_quote = T_DOUBLE_QUOTED_STRING;
-                       break;
-
-               case FR_TYPE_FILE_INPUT:
-               case FR_TYPE_FILE_OUTPUT:
-                       dflt_quote = T_DOUBLE_QUOTED_STRING;
-                       break;
-
-               default:
-                       dflt_quote = T_BARE_WORD;
-                       break;
-               }
-       }
-
-       cp = cf_pair_alloc(cs, name, expanded, T_OP_EQ, T_BARE_WORD, dflt_quote);
-       if (!cp) return -1;
-
-       cp->parsed = true;
-
-       /*
-        *      Set the rcode to indicate we used a default value
-        */
-       *out = cp;
-
-       return 1;
-}
-
-/** 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 #fr_value_box_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.
- *
- * **fr_type_t to data type mappings**
- * | fr_type_t               | Data type          | Dynamically allocated  |
- * | ----------------------- | ------------------ | ---------------------- |
- * | FR_TYPE_TMPL            | ``vp_tmpl_t``      | Yes                    |
- * | FR_TYPE_BOOL            | ``bool``           | No                     |
- * | FR_TYPE_UINT32          | ``uint32_t``       | No                     |
- * | FR_TYPE_UINT16          | ``uint16_t``       | No                     |
- * | FR_TYPE_UINT64          | ``uint64_t``       | No                     |
- * | FR_TYPE_INT32           | ``int32_t``        | No                     |
- * | FR_TYPE_STRING          | ``char const *``   | Yes                    |
- * | FR_TYPE_IPV4_ADDR       | ``fr_ipaddr_t``    | No                     |
- * | FR_TYPE_IPV4_PREFIX     | ``fr_ipaddr_t``    | No                     |
- * | FR_TYPE_IPV6_ADDR       | ``fr_ipaddr_t``    | No                     |
- * | FR_TYPE_IPV6_PREFIX     | ``fr_ipaddr_t``    | No                     |
- * | FR_TYPE_COMBO_IP_ADDR   | ``fr_ipaddr_t``    | No                     |
- * | FR_TYPE_COMBO_IP_PREFIX | ``fr_ipaddr_t``    | No                     |
- * | FR_TYPE_TIMEVAL         | ``struct timeval`` | No                     |
- *
- * @param[in] ctx      To allocate arrays and values in.
- * @param[in] cs       to search for matching #CONF_PAIR in.
- * @param[in] name     of #CONF_PAIR to search for.
- * @param[in] 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`` #FR_TYPE_TMPL                - @copybrief FR_TYPE_TMPL
- *                                               Feeds the value into #tmpl_afrom_str. Value can be
- *                                               obtained when processing requests, with #tmpl_expand or #tmpl_aexpand.
- *     - ``data`` #FR_TYPE_BOOL                - @copybrief FR_TYPE_BOOL
- *     - ``data`` #FR_TYPE_UINT32              - @copybrief FR_TYPE_UINT32
- *     - ``data`` #FR_TYPE_UINT16              - @copybrief FR_TYPE_UINT16
- *     - ``data`` #FR_TYPE_UINT64              - @copybrief FR_TYPE_UINT64
- *     - ``data`` #FR_TYPE_INT32               - @copybrief FR_TYPE_INT32
- *     - ``data`` #FR_TYPE_STRING              - @copybrief FR_TYPE_STRING
- *     - ``data`` #FR_TYPE_IPV4_ADDR           - @copybrief FR_TYPE_IPV4_ADDR (IPv4 address with prefix 32).
- *     - ``data`` #FR_TYPE_IPV4_PREFIX         - @copybrief FR_TYPE_IPV4_PREFIX (IPv4 address with variable prefix).
- *     - ``data`` #FR_TYPE_IPV6_ADDR           - @copybrief FR_TYPE_IPV6_ADDR (IPv6 address with prefix 128).
- *     - ``data`` #FR_TYPE_IPV6_PREFIX         - @copybrief FR_TYPE_IPV6_PREFIX (IPv6 address with variable prefix).
- *     - ``data`` #FR_TYPE_COMBO_IP_ADDR       - @copybrief FR_TYPE_COMBO_IP_ADDR (IPv4/IPv6 address with
- *                                               prefix 32/128).
- *     - ``data`` #FR_TYPE_COMBO_IP_PREFIX     - @copybrief FR_TYPE_COMBO_IP_PREFIX (IPv4/IPv6 address with
- *                                               variable prefix).
- *     - ``data`` #FR_TYPE_TIMEVAL             - @copybrief FR_TYPE_TIMEVAL
- *     - ``flag`` #FR_TYPE_DEPRECATED          - @copybrief FR_TYPE_DEPRECATED
- *     - ``flag`` #FR_TYPE_REQUIRED            - @copybrief FR_TYPE_REQUIRED
- *     - ``flag`` #FR_TYPE_ATTRIBUTE           - @copybrief FR_TYPE_ATTRIBUTE
- *     - ``flag`` #FR_TYPE_SECRET              - @copybrief FR_TYPE_SECRET
- *     - ``flag`` #FR_TYPE_FILE_INPUT          - @copybrief FR_TYPE_FILE_INPUT
- *     - ``flag`` #FR_TYPE_NOT_EMPTY           - @copybrief FR_TYPE_NOT_EMPTY
- *     - ``flag`` #FR_TYPE_MULTI               - @copybrief FR_TYPE_MULTI
- *     - ``flag`` #FR_TYPE_IS_SET              - @copybrief FR_TYPE_IS_SET
- * @param[out] out     Pointer to a global variable, or pointer to a field in the struct being populated with values.
- * @param[in] dflt             value to use, if no #CONF_PAIR is found.
- * @param[in] dflt_quote       around the dflt value.
- * @return
- *     - 1 if default value was used, or if there was no CONF_PAIR or dflt.
- *     - 0 on success.
- *     - -1 on error.
- *     - -2 if deprecated.
- */
-int cf_pair_parse(TALLOC_CTX *ctx, CONF_SECTION *cs,
-                 char const *name, unsigned int type, void *out,
-                 char const *dflt, FR_TOKEN dflt_quote)
-{
-       bool            multi, required, deprecated;
-       size_t          count = 0;
-       CONF_PAIR       *cp, *dflt_cp = NULL;
-
-       rad_assert(!(type & FR_TYPE_TMPL) || !dflt || (dflt_quote != T_INVALID)); /* We ALWAYS need a quoting type for templates */
-
-       multi = (type & FR_TYPE_MULTI);
-       required = (type & FR_TYPE_REQUIRED);
-       deprecated = (type & FR_TYPE_DEPRECATED);
-
-       /*
-        *      If the item is multi-valued we allocate an array
-        *      to hold the multiple values.
-        */
-       if (multi) {
-               CONF_PAIR       *first;
-               void            **array;
-               size_t          i;
-
-               /*
-                *      Easier than re-allocing
-                */
-               for (cp = first = cf_pair_find(cs, name);
-                    cp;
-                    cp = cf_pair_find_next(cs, cp, name)) count++;
-
-               /*
-                *      Multivalued, but there's no value, create a
-                *      default pair.
-                */
-               if (!count) {
-                       if (deprecated) return 0;
-                       if (!dflt) {
-                               if (required) {
-                       need_value:
-                                       cf_log_err(cs, "Configuration item \"%s\" must have a value", name);
-                                       return -1;
-                               }
-                               return 1;
-                       }
-
-                       if (cf_pair_default(&dflt_cp, cs, name, type, dflt, dflt_quote) < 0) return -1;
-                       cp = dflt_cp;
-                       count = 1;      /* Need one to hold the default */
-               } else {
-                       cp = first;     /* reset */
-               }
-
-               if (deprecated) {
-               deprecated:
-                       cf_log_err(cp, "Configuration pair \"%s\" is deprecated", cf_pair_attr(cp));
-                       return -2;
-               }
-
-               /*
-                *      Tmpl is outside normal range
-                */
-               if (type & FR_TYPE_TMPL) {
-                       array = (void **)talloc_zero_array(ctx, vp_tmpl_t *, count);
-               /*
-                *      Allocate an array of values.
-                *
-                *      We don't NULL terminate.  Consumer must use
-                *      talloc_array_length().
-                */
-               } else switch (FR_BASE_TYPE(type)) {
-               case FR_TYPE_BOOL:
-                       array = (void **)talloc_zero_array(ctx, bool, count);
-                       break;
-
-               case FR_TYPE_UINT32:
-                       array = (void **)talloc_zero_array(ctx, uint32_t, count);
-                       break;
-
-               case FR_TYPE_UINT16:
-                       array = (void **)talloc_zero_array(ctx, uint16_t, count);
-                       break;
-
-               case FR_TYPE_UINT64:
-                       array = (void **)talloc_zero_array(ctx, uint64_t, count);
-                       break;
-
-               case FR_TYPE_INT32:
-                       array = (void **)talloc_zero_array(ctx, int32_t, count);
-                       break;
-
-               case FR_TYPE_STRING:
-                       array = (void **)talloc_zero_array(ctx, char *, count);
-                       break;
-
-               case FR_TYPE_IPV4_ADDR:
-               case FR_TYPE_IPV4_PREFIX:
-               case FR_TYPE_IPV6_ADDR:
-               case FR_TYPE_IPV6_PREFIX:
-               case FR_TYPE_COMBO_IP_ADDR:
-               case FR_TYPE_COMBO_IP_PREFIX:
-                       array = (void **)talloc_zero_array(ctx, fr_ipaddr_t, count);
-                       break;
-
-               case FR_TYPE_TIMEVAL:
-                       array = (void **)talloc_zero_array(ctx, struct timeval, count);
-                       break;
-
-               default:
-                       rad_assert(0);  /* Unsupported type */
-                       return -1;
-               }
-
-               for (i = 0; i < count; i++, cp = cf_pair_find_next(cs, cp, name)) {
-                       if (cf_pair_parse_value(array, &array[i], cs, cp, type) < 0) {
-                               talloc_free(array);
-                               talloc_free(dflt_cp);
-                               return -1;
-                       }
-               }
-
-               *(void **)out = array;
-       /*
-        *      Single valued config item gets written to
-        *      the data pointer directly.
-        */
-       } else {
-               CONF_PAIR *next;
-
-               cp = cf_pair_find(cs, name);
-               if (!cp) {
-                       if (deprecated) return 0;
-                       if (!dflt) {
-                               if (required) goto need_value;
-                               return 1;
-                       }
-
-                       if (cf_pair_default(&dflt_cp, cs, name, type, dflt, dflt_quote) < 0) return -1;
-                       cp = dflt_cp;
-               }
-
-               next = cf_pair_find_next(cs, cp, name);
-               if (next) {
-                       cf_log_err(&(next->item), "Invalid duplicate configuration item '%s'", name);
-                       return -1;
-               }
-
-               if (deprecated) goto deprecated;
-
-               if (cf_pair_parse_value(ctx, out, cs, cp, type) < 0) {
-                       talloc_free(dflt_cp);
-                       return -1;
-               }
-       }
-
-       /*
-        *      If we created a default cp and succeeded
-        *      in parsing the dflt value, add the new
-        *      cp to the enclosing section.
-        */
-       if (dflt_cp) {
-               cf_item_add(cs, &(dflt_cp->item));
-               return 1;
-       }
-
-       return 0;
-}
-
-/** Pre-allocate a config section structure to allow defaults to be set
- *
- * @param cs           The parent subsection.
- * @param base         pointer or variable.
- * @param variables    that may have defaults in this config section.
- */
-static int cf_section_parse_init(CONF_SECTION *cs, void *base, CONF_PARSER const *variables)
-{
-       int i;
-
-       for (i = 0; variables[i].name != NULL; i++) {
-               if ((FR_BASE_TYPE(variables[i].type) == FR_TYPE_SUBSECTION)) {
-                       CONF_SECTION *subcs;
-
-                       if (!variables[i].dflt) continue;
-
-                       subcs = cf_section_find(cs, variables[i].name, NULL);
-                       if (!subcs && (variables[i].type & FR_TYPE_REQUIRED)) {
-                               cf_log_err(cs, "Missing %s {} subsection", variables[i].name);
-                               return -1;
-                       }
-
-                       /*
-                        *      Set the is_set field for the subsection.
-                        */
-                       if (variables[i].type & FR_TYPE_IS_SET) {
-                               bool *is_set;
-
-                               is_set = variables[i].data ? variables[i].is_set_ptr :
-                                                            ((uint8_t *)base) + variables[i].is_set_offset;
-                               if (is_set) *is_set = !!subcs;
-                       }
-
-                       /*
-                        *      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 -1;
-
-                               cf_item_add(cs, &(subcs->item));
-                       }
-
-                       continue;
-               }
-
-               if ((FR_BASE_TYPE(variables[i].type) != FR_TYPE_STRING) &&
-                   (variables[i].type != FR_TYPE_FILE_INPUT) &&
-                   (variables[i].type != FR_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 */
-
-       return 0;
-}
-
-static void cf_section_parse_warn(CONF_SECTION *cs)
-{
-       CONF_ITEM *ci;
-
-       for (ci = cs->item.child; 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 || (ci->lineno < 0)) continue;
-
-                       WARN("%s[%d]: The item '%s' is defined, but is unused by the configuration",
-                            ci->filename, ci->lineno,
-                            cp->attr);
-               }
-
-               /*
-                *      Skip everything else.
-                */
-       }
-}
-
-/** Parse a subsection
- *
- * @note Turns out using nested structures (instead of pointers) for subsections, was actually
- *     a pretty bad design decision, and will need to be fixed at some future point.
- *     For now we have a horrible hack where only multi-subsections get an array of structures
- *     of the appropriate size.
- *
- * @param[in] ctx              to allocate any additional structures under.
- * @param[out] out             pointer to a struct/pointer to fill with data.
- * @param[in] cs               to parse.
- * @param[in] name             of subsection to parse.
- * @param[in] type             flags.
- * @param[in] subcs_vars       CONF_PARSER definitions for the subsection.
- * @param[in] subcs_size       size of subsection structures to allocate.
- * @return
- *     - 0 on success.
- *     - -1 on general error.
- *     - -2 if a deprecated #CONF_ITEM was found.
- */
-static int cf_subsection_parse(TALLOC_CTX *ctx, void *out, CONF_SECTION *cs,
-                              char const *name, fr_type_t type, CONF_PARSER const *subcs_vars, size_t subcs_size)
-{
-       CONF_SECTION    *subcs;
-       int             count, i, ret;
-       uint8_t         **array;
-
-       rad_assert(type & FR_TYPE_SUBSECTION);
-
-       subcs = cf_section_find(cs, name, NULL);
-       if (!subcs) return 0;
-
-       /*
-        *      Handle the single subsection case (which is simple)
-        */
-       if (!(type & FR_TYPE_MULTI)) {
-               uint8_t *buff;
-
-               /*
-                *      FIXME: We shouldn't allow nested structures like this.
-                *      Each subsection struct should be allocated separately so
-                *      we have a clean talloc hierarchy.
-                */
-               if (!subcs_size) return cf_section_parse(ctx, out, subcs, subcs_vars);
-
-               MEM(buff = talloc_array(ctx, uint8_t, subcs_size));
-               ret = cf_section_parse(buff, buff, subcs, subcs_vars);
-               if (ret < 0) {
-                       talloc_free(buff);
-                       return -1;
-               }
-
-               *((uint8_t **)out) = buff;
-       }
-
-       rad_assert(subcs_size);
-
-       /*
-        *      Handle the multi subsection case (which is harder)
-        */
-       for (subcs = cf_section_find(cs, name, NULL), count = 0;
-            subcs;
-            subcs = cf_section_find_next(cs, subcs, name, NULL), count++);
-
-       /*
-        *      Allocate an array to hold the subsections
-        */
-       MEM(array = talloc_array(ctx, uint8_t *, count));
-
-       /*
-        *      Start parsing...
-        *
-        *      Note, we allocate each subsection structure individually
-        *      so that they can be used as talloc contexts and we can
-        *      keep the talloc hierarchy clean.
-        */
-       for (subcs = cf_section_find(cs, name, NULL), i = 0;
-            subcs;
-            subcs = cf_section_find_next(cs, subcs, name, NULL), i++) {
-               uint8_t *buff;
-
-               MEM(buff = talloc_zero_array(array, uint8_t, subcs_size));
-               array[i] = buff;
-
-               ret = cf_section_parse(buff, buff, subcs, subcs_vars);
-               if (ret < 0) {
-                       talloc_free(array);
-                       return ret;
-               }
-       }
-
-       *((uint8_t ***)out) = array;
-
-       return 0;
-}
-
-/** Parse a configuration section into user-supplied variables
- *
- * @param[in] ctx              to allocate any strings, or additional structures in.
- *                             Usually the same as base, unless base is a nested struct.
- * @param[out] base            pointer to a struct to fill with data.
- * @param[in] cs               to parse.
- * @param[in] 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(TALLOC_CTX *ctx, void *base, CONF_SECTION *cs, CONF_PARSER const *variables)
-{
-       int     ret = 0;
-       int     i;
-       void    *data;
-       bool    *is_set = NULL;
-
-       /*
-        *      Hack for partially parsed sections.
-        */
-       if (!variables) {
-               cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
-               return 0;
-       }
-
-       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);
-       }
-
-       if (cf_section_parse_init(cs, base, variables) < 0) return -1;
-
-       /*
-        *      Handle the known configuration parameters.
-        */
-       for (i = 0; variables[i].name != NULL; i++) {
-               /*
-                *      Handle subsections specially
-                */
-               if (FR_BASE_TYPE(variables[i].type) == FR_TYPE_SUBSECTION) {
-                       if (cf_subsection_parse(ctx, (uint8_t *)base + variables[i].offset, cs,
-                                               variables[i].name, variables[i].type,
-                                               variables[i].subcs, variables[i].subcs_size) < 0) goto finish;
-                       continue;
-               } /* else it's a CONF_PAIR */
-
-               if (variables[i].data) {
-                       data = variables[i].data; /* prefer this. */
-               } else if (base) {
-                       data = ((uint8_t *)base) + variables[i].offset;
-               } else if (!rad_cond_assert(0)) {
-                       ret = -1;
-                       goto finish;
-               }
-
-               /*
-                *      Get pointer to where we need to write out
-                *      whether the pointer was set.
-                */
-               if (variables[i].type & FR_TYPE_IS_SET) {
-                       is_set = variables[i].data ? variables[i].is_set_ptr :
-                                                    ((uint8_t *)base) + variables[i].is_set_offset;
-               }
-
-               /*
-                *      Parse the pair we found, or a default value.
-                */
-               ret = cf_pair_parse(ctx, cs, variables[i].name, variables[i].type, data,
-                                   variables[i].dflt, variables[i].quote);
-               switch (ret) {
-               case 1:         /* Used default (or not present) */
-                       if (is_set) *is_set = false;
-                       ret = 0;
-                       break;
-
-               case 0:         /* OK */
-                       if (is_set) *is_set = true;
-                       break;
-
-               case -1:        /* Parse error */
-                       goto finish;
-
-               case -2:        /* Deprecated CONF ITEM */
-                       if ((variables[i + 1].offset && (variables[i + 1].offset == variables[i].offset)) ||
-                           (variables[i + 1].data && (variables[i + 1].data == variables[i].data))) {
-                               cf_log_err(&(cs->item), "Replace \"%s\" with \"%s\"", variables[i].name,
-                                          variables[i + 1].name);
-                       }
-                       goto finish;
-               }
-       } /* for all variables in the configuration section */
-
-       /*
-        *      Ensure we have a proper terminator, type so we catch
-        *      missing terminators reliably
-        */
-       rad_cond_assert(variables[i].type == conf_term.type);
-
-       cs->base = base;
-
-       /*
-        *      Hack for partially parsed sections.  We don't print
-        *      out the final "}", that will be printed out when the
-        *      caller re-calls us with 'variable=NULL'.  And, we don't warn about unused
-        */
-       if (variables[i].offset == 1) return ret;
-
-       /*
-        *      Warn about items in the configuration which weren't
-        *      checked during parsing.
-        */
-       if (rad_debug_lvl >= 3) cf_section_parse_warn(cs);
-
-       cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
-
-finish:
-       return ret;
-}
-
-/** Fixup xlat expansions and attributes
- *
- * @note Despite the name, this is really the second phase of #cf_pair_parse.
- *
- * @param[out] base start of structure to write #vp_tmpl_t s to.
- * @param[in] cs CONF_SECTION to fixup.
- * @param[in] variables Array of CONF_PARSER structs to process.
- * @return
- *     - 0 on success.
- *     - -1 on failure (parse errors etc...).
- */
-int cf_section_parse_pass2(void *base, CONF_SECTION *cs, CONF_PARSER const variables[])
-{
-
-       int i;
-
-       /*
-        *      Handle the known configuration parameters.
-        */
-       for (i = 0; variables[i].name != NULL; i++) {
-               bool            attribute, multi, is_tmpl, is_xlat;
-               CONF_PAIR       *cp;
-               void            *data;
-
-               char const      *name = variables[i].name;
-               int             type = variables[i].type;
-
-               is_tmpl = (type & FR_TYPE_TMPL);
-               is_xlat = (type & FR_TYPE_XLAT);
-               attribute = (type & FR_TYPE_ATTRIBUTE);
-               multi = (type & FR_TYPE_MULTI);
-
-               type = FR_BASE_TYPE(type);              /* normal types are small */
-
-               /*
-                *      It's a section, recurse!
-                */
-               if (type == FR_TYPE_SUBSECTION) {
-                       uint8_t         *subcs_base;
-                       CONF_SECTION    *subcs = cf_section_find(cs, name, NULL);
-
-                       /*
-                        *      Select base by whether this is a nested struct,
-                        *      or a pointer to another struct.
-                        */
-                       if (!base) {
-                               subcs_base = NULL;
-                       } else if (multi) {
-                               size_t          j, len;
-                               uint8_t         **array;
-
-                               array = (uint8_t **)((uint8_t *)base) + variables[i].offset;
-                               len = talloc_array_length(array);
-
-                               for (j = 0; j < len; j++) {
-                                       if (cf_section_parse_pass2(array[j], subcs,
-                                                                  (CONF_PARSER const *)variables[i].dflt) < 0) {
-                                               return -1;
-                                       }
-                               }
-                               continue;
-                       } else if (variables[i].subcs_size) {
-                               subcs_base = (*(uint8_t **)((uint8_t *)base) + variables[i].offset);
-                       } else {
-                               subcs_base = (uint8_t *)base + variables[i].offset;
-                       }
-
-                       if (cf_section_parse_pass2(subcs_base, subcs,
-                                                  (CONF_PARSER const *)variables[i].dflt) < 0) return -1;
-
-                       continue;
-               }
-
-               /*
-                *      Find the CONF_PAIR, may still not exist if there was
-                *      no default set for the CONF_PARSER.
-                */
-               cp = cf_pair_find(cs, name);
-               if (!cp) continue;
-
-               /*
-                *      Figure out which data we need to fix.
-                */
-               data = variables[i].data; /* prefer this. */
-               if (!data && base) data = ((char *)base) + variables[i].offset;
-               if (!data) continue;
-
-               /*
-                *      Non-xlat expansions shouldn't have xlat!
-                */
-               if (!is_xlat && !is_tmpl) {
-                       /*
-                        *      Ignore %{... in shared secrets.
-                        *      They're never dynamically expanded.
-                        */
-                       if ((variables[i].type & FR_TYPE_SECRET) != 0) continue;
-
-                       if (strstr(cp->value, "%{") != NULL) {
-                               cf_log_err(&cp->item, "Found dynamic expansion in string which "
-                                          "will not be dynamically expanded");
-                               return -1;
-                       }
-                       continue;
-               }
-
-               /*
-                *      Parse (and throw away) the xlat string (for validation).
-                *
-                *      FIXME: All of these should be converted from FR_TYPE_XLAT
-                *      to FR_TYPE_TMPL.
-                */
-               if (is_xlat) {
-                       char const      *error;
-                       ssize_t         slen;
-                       char            *value;
-                       xlat_exp_t      *xlat;
-
-               redo:
-                       xlat = NULL;
-
-                       /*
-                        *      xlat expansions should be parseable.
-                        */
-                       value = talloc_strdup(cs, cp->value); /* modified by xlat_tokenize */
-                       slen = xlat_tokenize(cs, value, &xlat, &error);
-                       if (slen < 0) {
-                               char *spaces, *text;
-
-                               fr_canonicalize_error(cs, &spaces, &text, slen, cp->value);
-
-                               cf_log_err(cp, "Failed parsing expanded string:");
-                               cf_log_err(cp, "%s", text);
-                               cf_log_err(cp, "%s^ %s", spaces, error);
-
-                               talloc_free(spaces);
-                               talloc_free(text);
-                               talloc_free(value);
-                               talloc_free(xlat);
-                               return -1;
-                       }
-
-                       talloc_free(value);
-                       talloc_free(xlat);
-
-                       /*
-                        *      If the "multi" flag is set, check all of them.
-                        */
-                       if (multi) {
-                               cp = cf_pair_find_next(cs, cp, cp->attr);
-                               if (cp) goto redo;
-                       }
-                       continue;
-
-               /*
-                *      Parse the pair into a template
-                */
-               } else if (is_tmpl) {
-                       ssize_t slen;
-
-                       vp_tmpl_t **out = (vp_tmpl_t **)data;
-                       vp_tmpl_t *vpt;
-
-                       slen = tmpl_afrom_str(cs, &vpt, cp->value, talloc_array_length(cp->value) - 1,
-                                             cf_pair_value_quote(cp),
-                                             REQUEST_CURRENT, PAIR_LIST_REQUEST, true);
-                       if (slen < 0) {
-                               char *spaces, *text;
-
-                               fr_canonicalize_error(vpt, &spaces, &text, slen, cp->value);
-
-                               cf_log_err(cp, "%s", text);
-                               cf_log_err(cp, "%s^ %s", spaces, fr_strerror());
-
-                               talloc_free(spaces);
-                               talloc_free(text);
-                               return -1;
-                       }
-
-                       if (attribute && (vpt->type != TMPL_TYPE_ATTR)) {
-                               cf_log_err(&cp->item, "Expected attr got %s",
-                                          fr_int2str(tmpl_names, vpt->type, "???"));
-                               return -1;
-                       }
-
-                       switch (vpt->type) {
-                       /*
-                        *      All attributes should have been defined by this point.
-                        */
-                       case TMPL_TYPE_ATTR_UNDEFINED:
-                               talloc_free(vpt);
-                               cf_log_err(&cp->item, "Unknown attribute '%s'", vpt->tmpl_unknown_name);
-                               return -1;
-
-                       case TMPL_TYPE_UNPARSED:
-                       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);
-                       /* Don't add default */
-                       }
-
-                       /*
-                        *      Free the old value if we're overwriting
-                        */
-                       TALLOC_FREE(*out);
-                       *(vp_tmpl_t **)out = vpt;
-               }
-       } /* for all variables in the configuration section */
-
-       return 0;
-}
-
-const CONF_PARSER *cf_section_parse_table(CONF_SECTION *cs)
-{
-       if (!cs) return NULL;
-
-       return cs->variables;
-}
-
-CONF_ITEM *cf_reference_item(CONF_SECTION const *parent_cs,
-                            CONF_SECTION const *outer_cs,
-                            char const *ptr)
-{
-       CONF_PAIR               *cp;
-       CONF_SECTION            *next;
-       CONF_SECTION const      *cs = outer_cs;
-       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 = cf_item_to_section(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 (!parent_cs) goto no_such_item;
-               cs = parent_cs;
-       }
-
-       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_find(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_find(cs, p, NULL);
-                       *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_find(cs, p, NULL);
-       if (next) return &(next->item);
-
-       /*
-        *      "foo" is "in the current section, OR in main".
-        */
-       if ((p == name) && (parent_cs != NULL) && (cs != parent_cs)) {
-               cs = parent_cs;
-               goto retry;
-       }
-
-no_such_item:
-       return NULL;
-}
-
-/** Return the next child that's of the specified type
- *
- * @param[in] parent   to return children from.
- * @param[in] prev     child to start searching from.
- * @param[in] type     to search for.
- * @return
- *     - The next #CONF_ITEM that's a child of ci matching type.
- *     - NULL if no #CONF_ITEM matches that criteria.
- */
-static CONF_ITEM *cf_next(CONF_ITEM const *parent, CONF_ITEM const *prev, CONF_ITEM_TYPE type)
-{
-       CONF_ITEM *ci;
-
-       for (ci = prev ? prev->next : parent->child;
-            ci;
-            ci = ci->next) {
-               if (ci->type == type) return ci;
-       }
-
-       return NULL;
-}
-
-/** Return the next child that's of the specified type with the specified identifiers
- *
- * @param[in] parent   The section we're searching in.
- * @param[in] type     of #CONF_ITEM we're searching for.
- * @param[in] ident1   The first identifier.
- * @param[in] ident2   The second identifier. Special value CF_IDENT_ANY
- *                     can be used to match any ident2 value.
- * @return
- *     - The first matching item.
- *     - NULL if no items matched.
- */
-static CONF_ITEM *cf_find(CONF_ITEM const *parent, CONF_ITEM_TYPE type, char const *ident1, char const *ident2)
-{
-       CONF_SECTION    cs_find;
-       CONF_PAIR       cp_find;
-       CONF_DATA       cd_find;
-       CONF_ITEM       *find;
-
-       if (!parent) return NULL;
-       if (!parent->child) return NULL;        /* No children */
-
-       if (!ident1) return cf_next(parent, NULL, type);
-
-       switch (type) {
-       case CONF_ITEM_SECTION:
-               memset(&cs_find, 0, sizeof(cs_find));
-               cs_find.item.type = CONF_ITEM_SECTION;
-               cs_find.name1 = ident1;
-               if (ident2 != CF_IDENT_ANY) cs_find.name2 = ident2;
-
-               find = (CONF_ITEM *)&cs_find;
-               break;
-
-       case CONF_ITEM_PAIR:
-               rad_assert((ident2 == NULL) || (ident2 == CF_IDENT_ANY));
-
-               memset(&cp_find, 0, sizeof(cp_find));
-               cp_find.item.type = CONF_ITEM_PAIR;
-               cp_find.attr = ident1;
-
-               find = (CONF_ITEM *)&cp_find;
-               break;
-
-       case CONF_ITEM_DATA:
-               memset(&cd_find, 0, sizeof(cd_find));
-               cd_find.item.type = CONF_ITEM_DATA;
-               cd_find.type = ident1;
-               if (ident2 != CF_IDENT_ANY) cd_find.name = ident2;
-
-               find = (CONF_ITEM *)&cd_find;
-               break;
-
-       default:
-               rad_assert(0);
-       }
-
-       /*
-        *      No ident1, iterate over the child list
-        */
-       if (ident1 == CF_IDENT_ANY) {
-               CONF_ITEM *ci;
-
-               for (ci = parent->child;
-                    ci && (cf_ident2_cmp(find, ci) != 0);
-                    ci = ci->next);
-
-               return ci;
-       }
-
-       /*
-        *      No ident2, use the ident1 tree.
-        */
-       if (ident2 == CF_IDENT_ANY) return rbtree_finddata(parent->ident1, find);
-
-       /*
-        *      Both ident1 and ident2 use the ident2 tree.
-        */
-       return rbtree_finddata(parent->ident2, find);
-}
-
-/** Return the next child that's of the specified type with the specified identifiers
- *
- * @param[in] parent   The section we're searching in.
- * @param[in] prev     item we found, or NULL to start from the beginning.
- * @param[in] type     of #CONF_ITEM we're searching for.
- * @param[in] ident1   The first identifier.
- * @param[in] ident2   The second identifier. Special value CF_IDENT_ANY
- *                     can be used to match any ident2 value.
- * @return
- *     - The first matching item.
- *     - NULL if no items matched.
- */
-static CONF_ITEM *cf_find_next(CONF_ITEM const *parent, CONF_ITEM const *prev,
-                              CONF_ITEM_TYPE type, char const *ident1, char const *ident2)
-{
-       CONF_SECTION    cs_find;
-       CONF_PAIR       cp_find;
-       CONF_DATA       cd_find;
-       CONF_ITEM       *find;
-       CONF_ITEM       *ci;
-
-       if (!parent) return NULL;
-
-       if (!prev) {
-               if (!ident1) return cf_next(parent, NULL, type);
-               return cf_find(parent, type, ident1, ident2);
-       }
-       if (!ident1) return cf_next(parent, prev, type);
-
-       switch (type) {
-       case CONF_ITEM_SECTION:
-               memset(&cs_find, 0, sizeof(cs_find));
-               cs_find.item.type = CONF_ITEM_SECTION;
-               cs_find.name1 = ident1;
-               if (ident2 != CF_IDENT_ANY) cs_find.name2 = ident2;
-
-               find = (CONF_ITEM *)&cs_find;
-               break;
-
-       case CONF_ITEM_PAIR:
-               rad_assert((ident2 == NULL) || (ident2 == CF_IDENT_ANY));
-
-               memset(&cp_find, 0, sizeof(cp_find));
-               cp_find.item.type = CONF_ITEM_PAIR;
-               cp_find.attr = ident1;
-
-               find = (CONF_ITEM *)&cp_find;
-               break;
-
-       case CONF_ITEM_DATA:
-               memset(&cd_find, 0, sizeof(cd_find));
-               cd_find.item.type = CONF_ITEM_DATA;
-               cd_find.type = ident1;
-               if (ident2 != CF_IDENT_ANY) cd_find.name = ident2;
-
-               find = (CONF_ITEM *)&cd_find;
-               break;
-
-       default:
-               rad_assert(0);
-       }
-
-       if (ident1 == CF_IDENT_ANY) {
-               for (ci = prev->next;
-                    ci && (cf_ident2_cmp(ci, find) != 0);
-                    ci = ci->next);
-
-               return ci;
-       }
-
-       if (ident2 == CF_IDENT_ANY) {
-               for (ci = prev->next;
-                    ci && (_cf_ident1_cmp(ci, find) != 0);
-                    ci = ci->next);
-
-               return ci;
-       }
-
-       for (ci = prev->next;
-            ci && (_cf_ident2_cmp(ci, find) != 0);
-            ci = ci->next);
-
-       return ci;
-}
-
-/** Compare the first identifier of a child
- *
- * For CONF_ITEM_PAIR this is 'attr'.
- * For CONF_ITEM_SECTION this is 'name1'.
- * For CONF_ITEM_DATA this is 'type'.
- *
- * @param[in] a        First CONF_ITEM to compare.
- * @param[in] b Second CONF_ITEM to compare.
- * @return
- *     - >0 if a > b.
- *     - <0 if a < b.
- *     - 0 if a == b.
- */
-static inline int _cf_ident1_cmp(void const *a, void const *b)
-{
-       CONF_ITEM_TYPE type;
-
-       {
-               CONF_ITEM const *one = a;
-               CONF_ITEM const *two = b;
-
-               if (one->type > two->type) return +1;
-               if (one->type < two->type) return -1;
-
-               type = one->type;
-       }
-
-       switch (type) {
-       case CONF_ITEM_PAIR:
-       {
-               CONF_PAIR const *one = a;
-               CONF_PAIR const *two = b;
-
-               return strcmp(one->attr, two->attr);
-       }
-
-       case CONF_ITEM_SECTION:
-       {
-               CONF_SECTION const *one = a;
-               CONF_SECTION const *two = b;
-
-               return strcmp(one->name1, two->name1);
-       }
-
-       case CONF_ITEM_DATA:
-       {
-               CONF_DATA const *one = a;
-               CONF_DATA const *two = b;
-
-               return strcmp(one->type, two->type);
-       }
-
-       default:
-               rad_assert(0);
-       }
-}
-
-/** Compare only the second identifier of a child
- *
- * For CONF_ITEM_SECTION this is 'name2'.
- * For CONF_ITEM_DATA this is 'name'.
- *
- * @param[in] a        First CONF_ITEM to compare.
- * @param[in] b Second CONF_ITEM to compare.
- * @return
- *     - >0 if a > b.
- *     - <0 if a < b.
- *     - 0 if a == b.
- */
-static inline int cf_ident2_cmp(void const *a, void const *b)
-{
-       CONF_ITEM const *ci = a;
-
-       switch (ci->type) {
-       case CONF_ITEM_PAIR:
-               return 0;
-
-       case CONF_ITEM_SECTION:
-       {
-               CONF_SECTION const *one = a;
-               CONF_SECTION const *two = b;
-
-               if (!two->name2 && one->name2) return +1;
-               if (two->name2 && !one->name2) return -1;
-               if (!two->name2 && !one->name2) return 0;
-
-               return strcmp(one->name2, two->name2);
-       }
-
-       case CONF_ITEM_DATA:
-       {
-               CONF_DATA const *one = a;
-               CONF_DATA const *two = b;
-
-               if (!two->name && one->name) return +1;
-               if (two->name && !one->name) return -1;
-               if (!two->name && !one->name) return 0;
-
-               return strcmp(one->name, two->name);
-       }
-
-       default:
-               rad_assert(0);
-       }
-}
-
-/** Compare the first and second identifiers of a child
- *
- * For CONF_ITEM_SECTION this is 'name2'.
- * For CONF_ITEM_DATA this is 'name'.
- *
- * @param[in] a        First CONF_ITEM to compare.
- * @param[in] b Second CONF_ITEM to compare.
- * @return
- *     - >0 if a > b.
- *     - <0 if a < b.
- *     - 0 if a == b.
- */
-static int _cf_ident2_cmp(void const *a, void const *b)
-{
-       int ret;
-
-       ret = _cf_ident1_cmp(a, b);
-       if (ret != 0) return ret;
-
-       return cf_ident2_cmp(a, b);
-}
-
-/** Add a child
- *
- */
-void _cf_item_add(CONF_ITEM *parent, CONF_ITEM *child)
-{
-       fr_cursor_t     to_merge;
-       CONF_ITEM       *ci;
-
-       rad_assert(parent != child);
-
-       if (!parent || !child) return;
-
-       /*
-        *      New child, add child trees.
-        */
-       if (!parent->ident1) parent->ident1 = rbtree_create(parent, _cf_ident1_cmp, NULL, RBTREE_FLAG_NONE);
-       if (!parent->ident2) parent->ident2 = rbtree_create(parent, _cf_ident2_cmp, NULL, RBTREE_FLAG_NONE);
-
-       fr_cursor_init(&to_merge, &child);
-
-       for (ci = fr_cursor_head(&to_merge);
-            ci;
-            ci = fr_cursor_next(&to_merge)) {
-               rbtree_insert(parent->ident1, ci);
-               rbtree_insert(parent->ident2, ci);      /* NULL ident2 is still a value */
-               fr_cursor_append(&parent->cursor, ci);  /* Append to the list of children */
-       }
-}
-
-/** Remove item from parent and fixup trees
- *
- * @param[in] parent   to remove child from.
- * @param[in] child    to remove.
- * @return
- *     - The item removed.
- *     - NULL if the item wasn't set.
- */
-static CONF_ITEM *cf_remove(CONF_ITEM *parent, CONF_ITEM *child)
-{
-       CONF_ITEM       *found;
-       bool            in_ident1, in_ident2;
-
-       if (!parent || !parent->child) return NULL;
-       if (parent != child->parent) return NULL;
-
-       for (found = fr_cursor_head(&parent->cursor);
-            found && (child != found);
-            found = fr_cursor_next(&parent->cursor));
-
-       if (!found) return NULL;
-
-       /*
-        *      Fixup the linked list
-        */
-       found = fr_cursor_remove(&parent->cursor);
-       rad_assert(found == child);
-
-       in_ident1 = (rbtree_finddata(parent->ident1, child) == child);
-       if (in_ident1 && (!rbtree_deletebydata(parent->ident1, child))) {
-               rad_assert(0);
-               return NULL;
-       }
-
-       in_ident2 = (rbtree_finddata(parent->ident2, child) == child);
-       if (in_ident2 && (!rbtree_deletebydata(parent->ident2, child))) {
-               rad_assert(0);
-               return NULL;
-       }
-
-       /*
-        *      Look for twins
-        */
-       for (found = fr_cursor_head(&parent->cursor);
-            found && (in_ident1 || in_ident2);
-            found = fr_cursor_next(&parent->cursor)) {
-               if (in_ident1 && (_cf_ident1_cmp(found, child) == 0)) {
-                       rbtree_insert(parent->ident1, child);
-                       in_ident1 = false;
-               }
-
-               if (in_ident2 && (_cf_ident2_cmp(found, child) == 0)) {
-                       rbtree_insert(parent->ident2, child);
-                       in_ident2 = false;
-               }
-       }
-
-       return child;
-}
-
-/** Return the next child of cs
- *
- * @param[in] ci       to return children from.
- * @param[in] prev     child to start searching from.
- * @return
- *     - The next #CONF_ITEM that's a child of cs.
- *     - NULL if no more #CONF_ITEM.
- */
-CONF_ITEM *_cf_item_next(CONF_ITEM const *ci, CONF_ITEM const *prev)
-{
-       return prev ? prev->next : ci->child;
-}
-
-CONF_SECTION *_cf_root(CONF_ITEM const *ci)
-{
-       CONF_ITEM const *ci_p;
-
-       if (!ci) return NULL;
-
-       for (ci_p = ci; ci_p->parent; ci_p = ci_p->parent);
-
-       return cf_item_to_section(ci_p);
-}
-
-CONF_ITEM *_cf_parent(CONF_ITEM const *ci)
-{
-       if (!ci) return NULL;
-
-       return ci->parent;
-}
-
-int _cf_lineno(CONF_ITEM const *ci)
-{
-       return ci->lineno;
-}
-
-char const *_cf_filename(CONF_ITEM const *ci)
-{
-       return ci->filename;
-}
-
-bool cf_item_is_section(CONF_ITEM const *ci)
-{
-       return ci->type == CONF_ITEM_SECTION;
-}
-
-bool cf_item_is_pair(CONF_ITEM const *ci)
-{
-       return ci->type == CONF_ITEM_PAIR;
-}
-
-bool cf_item_is_data(CONF_ITEM const *ci)
-{
-       return ci->type == CONF_ITEM_DATA;
-}
-
-/** 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;
-}
-
-CONF_DATA *cf_item_to_data(CONF_ITEM const *ci)
-{
-       CONF_DATA *out;
-
-       if (ci == NULL) return NULL;
-
-       rad_assert(ci->type == CONF_ITEM_DATA);
-
-       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
- *
- */
-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_section_free(CONF_SECTION *cs)
-{
-       if (cs->item.ident1) TALLOC_FREE(cs->item.ident1);
-       if (cs->item.ident2) TALLOC_FREE(cs->item.ident2);
-
-       return 0;
-}
-
-/** 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.
- *     - A new #CONF_SECTION parented by parent.
- */
-CONF_SECTION *cf_section_alloc(CONF_SECTION *parent, char const *name1, char const *name2)
-{
-       CONF_SECTION *cs;
-
-       if (!name1) return NULL;
-
-       if (name2 && parent) {
-               char buffer[1024];
-
-               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 = cf_section_to_item(parent);
-       fr_cursor_init(&cs->item.cursor, &cs->item.child);
-
-       MEM(cs->name1 = talloc_typed_strdup(cs, name1));
-       if (name2) {
-               MEM(cs->name2 = talloc_typed_strdup(cs, name2));
-               cs->name2_quote = T_BARE_WORD;
-       }
-       talloc_set_destructor(cs, _cf_section_free);
-
-       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[in] parent   section (may be NULL).
- * @param[in] cs       to duplicate.
- * @param[in] name1    of new section.
- * @param[in] name2    of new section.
- * @param[in] copy_meta        Copy additional meta data for a section
- *                     (like template, base, depth and variables).
- * @return
- *     - A duplicate of the existing section.
- *     - 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;
-       fr_cursor_t     cursor;
-
-       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;
-       new->item.filename = cs->item.filename;
-
-       fr_cursor_copy(&cursor, &cs->item.cursor);      /* Mutable cursor */
-       for (ci = fr_cursor_head(&cursor);
-            ci;
-            ci = fr_cursor_next(&cursor)) {
-               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 */
-#ifdef WITH_CONF_WRITE
-               case CONF_ITEM_COMMENT:
-               case CONF_ITEM_INCLUDE:
-#endif
-                       break;
-
-               case CONF_ITEM_INVALID:
-                       rad_assert(0);
-               }
-       }
-
-       return new;
-}
-
-/** Add a section as a child of another section
- *
- * @param[in] parent   section we're adding to.
- * @param[in] cs       we're adding.
- */
-void cf_section_add(CONF_SECTION *parent, CONF_SECTION *cs)
-{
-       cf_item_add(parent, &(cs->item));
-}
-
-/** Return the next child that's a #CONF_SECTION
- *
- * @param[in] cs       to return children from.
- * @param[in] prev     child to start searching from.
- * @return
- *     - The next #CONF_ITEM that's a child of cs and a CONF_SECTION.
- *     - NULL if no #CONF_ITEM matches that criteria.
- */
-CONF_SECTION *cf_section_next(CONF_SECTION const *cs, CONF_SECTION const *prev)
-{
-       return cf_item_to_section(cf_next(cf_section_to_item(cs), cf_section_to_item(prev), CONF_ITEM_SECTION));
-}
-
-/** Find a CONF_SECTION with name1 and optionally name2.
- *
- * @param[in] cs       The section we're searching in.
- * @param[in] name1    The first section identifier.
- * @param[in] name2    The second section identifier. Special value CF_IDENT_ANY
- *                     can be used to match any name2 value.
- * @return
- *     - The first matching subsection.
- *     - NULL if no subsections match.
- */
-CONF_SECTION *cf_section_find(CONF_SECTION const *cs,
-                             char const *name1, char const *name2)
-{
-       return cf_item_to_section(cf_find(cf_section_to_item(cs), CONF_ITEM_SECTION, name1, name2));
-}
-
-/** Return the next matching section
- *
- * @param[in] cs       The section we're searching in.
- * @param[in] prev     section we found.  May be NULL in which case
- *                     we just return the next section after prev.
- * @param[in] name1    of the section we're searching for.
- * @param[in] name2    of the section we're searching for.  Special value CF_IDENT_ANY
- *                     can be used to match any name2 value.
- * @return
- *     - The next CONF_SECTION.
- *     - NULL if there are no more CONF_SECTIONs
- */
-CONF_SECTION *cf_section_find_next(CONF_SECTION const *cs, CONF_SECTION const *prev,
-                                  char const *name1, char const *name2)
-{
-       return cf_item_to_section(cf_find_next(cf_section_to_item(cs), cf_section_to_item(prev),
-                                              CONF_ITEM_SECTION, name1, name2));
-}
-
-/** 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);
-}
-
-/** 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);
-}
-
-char const *cf_section_argv(CONF_SECTION const *cs, int argc)
-{
-       if (!cs || !cs->argv || (argc < 0) || (argc > cs->argc)) return NULL;
-
-       return cs->argv[argc];
-}
-
-/*
- *     For "switch" and "case" statements.
- */
-FR_TOKEN cf_section_name2_quote(CONF_SECTION const *cs)
-{
-       if (!cs) return T_INVALID;
-
-       return cs->name2_quote;
-}
-
-FR_TOKEN cf_section_argv_quote(CONF_SECTION const *cs, int argc)
-{
-       if (!cs || !cs->argv_quote || (argc < 0) || (argc > cs->argc)) return T_INVALID;
-
-       return cs->argv_quote[argc];
-}
-
-/** Allocate a #CONF_PAIR
- *
- * @param[in] parent           #CONF_SECTION to hang this #CONF_PAIR off of.
- * @param[in] attr             name.
- * @param[in] value            of #CONF_PAIR.
- * @param[in] op               #T_OP_EQ, #T_OP_SET etc.
- * @param[in] lhs_quote                #T_BARE_WORD, #T_DOUBLE_QUOTED_STRING, #T_BACK_QUOTED_STRING.
- * @param[in] rhs_quote                #T_BARE_WORD, #T_DOUBLE_QUOTED_STRING, #T_BACK_QUOTED_STRING.
- * @return
- *     - NULL on error.
- *     - 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_quote, FR_TOKEN rhs_quote)
-{
-       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 = cf_section_to_item(parent);
-       cp->lhs_quote = lhs_quote;
-       cp->rhs_quote = rhs_quote;
-       cp->op = op;
-       cp->item.filename = "<internal>"; /* will be over-written if necessary */
-       fr_cursor_init(&cp->item.cursor, &cp->item.child);
-
-       cp->attr = talloc_typed_strdup(cp, attr);
-       if (!cp->attr) {
-       error:
-               talloc_free(cp);
-               return NULL;
-       }
-
-       if (value) {
-#ifdef WITH_CONF_WRITE
-               cp->orig_value = talloc_typed_strdup(cp, value);
-#endif
-               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.
- *     - 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_quote, cp->rhs_quote);
-       if (!new) return NULL;
-
-       new->parsed = cp->parsed;
-       new->item.lineno = cp->item.lineno;
-       new->item.filename = cp->item.filename;
-
-       return new;
-}
-
-/** Replace pair in a given section with a new pair, of the given value.
- *
- * @param[in] cs       to replace pair in.
- * @param[in] cp       to replace.
- * @param[in] 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 *new_cp;
-       CONF_ITEM *ci;
-
-       if (!cs || !cp || !value) return -1;
-
-       MEM(new_cp = cf_pair_alloc(cs, cp->attr, value, cp->op, cp->lhs_quote, cp->rhs_quote));
-
-       /*
-        *      Remove the old CONF_PAIR
-        */
-       ci = cf_remove(cf_section_to_item(cs), cf_pair_to_item(cp));
-       rad_assert(!ci || (ci == cf_pair_to_item(cp)));
-
-       /*
-        *      Add the new CONF_PAIR
-        */
-       cf_item_add(cf_section_to_item(cs), cf_pair_to_item(new_cp));
-
-       cp = cf_pair_find(cs, cp->attr);
-       rad_assert(cp == new_cp);
-
-       talloc_free(ci);
-
-       return 0;
-}
-
-/** Add a configuration pair to a section
- *
- * @param[in] parent section to add pair to.
- * @param[in] cp to add.
- */
-void cf_pair_add(CONF_SECTION *parent, CONF_PAIR *cp)
-{
-       cf_item_add(parent, cf_pair_to_item(cp));
-}
-
-/** Return the next child that's a #CONF_PAIR
- *
- * @param[in] cs       to return children from.
- * @param[in] prev     child to start searching from.
- * @return
- *     - The next #CONF_ITEM that's a child of cs and a CONF_PAIR.
- *     - NULL if no #CONF_ITEM matches that criteria.
- */
-CONF_PAIR *cf_pair_next(CONF_SECTION const *cs, CONF_PAIR const *prev)
-{
-       return cf_item_to_pair(cf_next(cf_section_to_item(cs), cf_pair_to_item(prev), CONF_ITEM_PAIR));
-}
-
-/** Search for a #CONF_PAIR with a specific name
- *
- * @param[in] cs       to search in.
- * @param[in] attr     to find.
- * @return
- *     - The next matching #CONF_PAIR.
- *     - NULL if none matched.
- */
-CONF_PAIR *cf_pair_find(CONF_SECTION const *cs, char const *attr)
-{
-       return cf_item_to_pair(cf_find(cf_section_to_item(cs), CONF_ITEM_PAIR, attr, NULL));
-}
-
-/** Find a pair with a name matching attr, after specified pair.
- *
- * @param[in] cs       to search in.
- * @param[in] prev     Pair to search from (may be NULL).
- * @param[in] attr     to find (may be NULL in which case any attribute matches).
- * @return
- *     - The next matching #CONF_PAIR
- *     - NULL if none matched.
- */
-CONF_PAIR *cf_pair_find_next(CONF_SECTION const *cs, CONF_PAIR const *prev, char const *attr)
-{
-       return cf_item_to_pair(cf_find_next(cf_section_to_item(cs), cf_pair_to_item(prev), CONF_ITEM_PAIR, attr, NULL));
-}
-
-/** Callback to determine the number of pairs in a section
- *
- */
-static void _pair_count(int *count, CONF_SECTION const *cs)
-{
-       CONF_ITEM const *ci = NULL;
-
-       while ((ci = cf_item_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 The number of pairs nested within section.
- */
-int cf_pair_count(CONF_SECTION const *cs)
-{
-       int count = 0;
-
-       _pair_count(&count, cs);
-
-       return count;
-}
-
-/** 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
- *     - #T_BARE_WORD.
- *     - #T_SINGLE_QUOTED_STRING.
- *     - #T_BACK_QUOTED_STRING.
- *     - #T_DOUBLE_QUOTED_STRING.
- *     - #T_INVALID if the pair is NULL.
- */
-FR_TOKEN cf_pair_attr_quote(CONF_PAIR const *pair)
-{
-       return (pair ? pair->lhs_quote : T_INVALID);
-}
-
-/** Return the value (rhs) type
- *
- * @param pair to extract value type from.
- * @return
- *     - #T_BARE_WORD.
- *     - #T_SINGLE_QUOTED_STRING.
- *     - #T_BACK_QUOTED_STRING.
- *     - #T_DOUBLE_QUOTED_STRING.
- *     - #T_INVALID if the pair is NULL.
- */
-FR_TOKEN cf_pair_value_quote(CONF_PAIR const *pair)
-{
-       return (pair ? pair->rhs_quote : T_INVALID);
-}
-
-/** Allocate a new user data container
- *
- * @param[in] parent   #CONF_PAIR, or #CONF_SECTION to hang CONF_DATA off of.
- * @param[in] name     String identifier of the user data.
- * @param[in] data     being added.
- * @param[in] do_free  function, called when the parent #CONF_SECTION is being freed.
- * @return
- *     - CONF_DATA on success.
- *     - NULL on error.
- */
-static CONF_DATA *cf_data_alloc(CONF_ITEM *parent, void const *data, char const *name, bool do_free)
-{
-       CONF_DATA *cd;
-
-       cd = talloc_zero(parent, CONF_DATA);
-       if (!cd) return NULL;
-
-       cd->item.type = CONF_ITEM_DATA;
-       cd->item.parent = parent;
-       fr_cursor_init(&cd->item.cursor, &cd->item.child);
-
-       /*
-        *      strdup so if the data is freed, we can
-        *      still remove it from the section without
-        *      explosions.
-        */
-       if (data) {
-               cd->type = talloc_typed_strdup(cd, talloc_get_name(data));
-               cd->data = data;
-       }
-       if (name) cd->name = talloc_typed_strdup(cd, name);
-
-       if (do_free) {
-               cd->free = true;
-               talloc_set_destructor(cd, _cd_free);
-       }
-
-       return cd;
-}
-
-/** Find user data in a config section
- *
- * @param[in] ci       to add data to.
- * @param[in] type     of user data.  Used for name spacing and walking over a specific
- *                     type of user data.
- * @param[in] name     String identifier of the user data.  Special value CF_IDENT_ANY
- *                     may be used to match on type only.
- * @return
- *     - The user data.
- *     - NULL if no user data exists.
- */
-CONF_DATA const *_cf_data_find(CONF_ITEM const *ci, char const *type, char const *name)
-{
-       return cf_item_to_data(cf_find(ci, CONF_ITEM_DATA, type, name));
-}
-
-/** Return the next matching section
- *
- * @param[in] ci       The section we're searching in.
- * @param[in] prev     section we found.  May be NULL in which case
- *                     we just return the next section after prev.
- * @param[in] type     of user data.  Used for name spacing and walking over a specific
- *                     type of user data.
- * @param[in] name     String identifier of the user data.  Special value CF_IDENT_ANY
- *                     can be used to match any name2 value.
- * @return
- *     - The next CONF_SECTION.
- *     - NULL if there are no more CONF_SECTIONs
- */
-CONF_DATA const *_cf_data_find_next(CONF_ITEM const *ci, CONF_ITEM const *prev, char const *type, char const *name)
-{
-       return cf_item_to_data(cf_find_next(ci, prev, CONF_ITEM_DATA, type, name));
-}
-
-/** Return the user assigned value of #CONF_DATA
- *
- */
-void *cf_data_value(CONF_DATA const *cd)
-{
-       void *to_return;
-
-       if (!cd) return NULL;
-
-       memcpy(&to_return, &cd->data, sizeof(to_return));
-
-       return to_return;
-}
-
-/** Add user data to a config section
- *
- * @param[in] ci       to add data to.
- * @param[in] data     to add.
- * @param[in] name     String identifier of the user data.
- * @param[in] do_free  Function to free user data when the CONF_SECTION is freed.
- * @return
- *     - #CONF_DATA  - opaque handle to the stored data - on success.
- *     - NULL error.
- */
-CONF_DATA const *_cf_data_add(CONF_ITEM *ci, void const *data, char const *name, bool do_free)
-{
-       CONF_DATA       *cd;
-       char const      *type = NULL;
-
-       if (!ci) return NULL;
-
-       if (data) type = talloc_get_name(data);
-
-       /*
-        *      Already exists.  Can't add it.
-        */
-       if (_cf_data_find(ci, type, name)) {
-               cf_log_err(ci, "Data of type %s with name %s already exists", type, name);
-               return NULL;
-       }
-
-       cd = cf_data_alloc(ci, data, name, do_free);
-       if (!cd) {
-               cf_log_err(ci, "Failed allocating data");
-               return NULL;
-       }
-
-       cf_item_add(ci, cf_data_to_item(cd));
-
-       return cd;
-}
-
-/** Remove named data from a configuration section
- *
- * @param[in] parent   to remove data from.
- * @param[in] cd       opaque handle of the stored data.
- * @return
- *     - The value stored within the data (if cd is valid and was found and removed).
- *     - NULL if not found.
- */
-void *_cf_data_remove(CONF_ITEM *parent, CONF_DATA const *cd)
-{
-       void *data;
-       CONF_ITEM *ci;
-
-       ci = cf_remove(parent, cf_data_to_item(cd));
-       rad_assert(!ci || (ci == cf_data_to_item(cd)));
-       if (!ci) return NULL;
-
-       talloc_set_destructor(cd, NULL);        /* Disarm the destructor */
-       memcpy(&data, &cd->data, sizeof(data));
-       talloc_const_free(cd);
-
-       return data;
-}
-
-/** ctx data for a _cf_data_walk_call
- *
- */
-typedef struct cf_data_walk_ctx {
-       char const      *type;          //!< of CONF_DATA we're iterating over.
-       cf_walker_t     cb;             //!< cb to process CONF_DATA.
-       void            *ctx;           //!< to pass to cb.
-} cf_data_walk_ctx_t;
-
-/** Wrap a cf_walker_t in an rb_walker_t
- *
- * @param[in] ctx      A cf_data_walk_ctx_t.
- * @param[in] data     A CONF_DATA entry.
- */
-static int _cf_data_walk_cb(void *ctx, void *data)
-{
-       cf_data_walk_ctx_t      *cd_ctx = ctx;
-       CONF_DATA               *cd = data;
-       CONF_ITEM               *ci = data;
-       void                    *mutable;
-       int                     ret;
-
-       /*
-        *      We're walking ident2, not all of the items will be data
-        */
-       if (ci->type != CONF_ITEM_DATA) return 0;
-
-       if ((cd->type != cd_ctx->type) && (strcmp(cd->type, cd_ctx->type) != 0)) return 0;
-
-       memcpy(&mutable, &cd->data, sizeof(data));
-       ret = cd_ctx->cb(mutable, cd_ctx->ctx);
-
-       return ret;
-}
-
-/** Walk over a specific type of CONF_DATA
- *
- * @param[in] ci       containing the CONF_DATA to walk over.
- * @param[in] type     of CONF_DATA to walk over.
- * @param[in] cb       to call when we find CONF_DATA of the specified type.
- * @param[in] ctx      to pass to cb.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
- */
-int _cf_data_walk(CONF_ITEM *ci, char const *type, cf_walker_t cb, void *ctx)
-{
-       cf_data_walk_ctx_t cd_ctx = {
-               .type = type,
-               .cb = cb,
-               .ctx = ctx
-       };
-
-       if (!ci->ident2) return 0;
-
-       return rbtree_walk(ci->ident2, RBTREE_IN_ORDER, _cf_data_walk_cb, &cd_ctx);
-}
-
-/** Log an error message relating to a #CONF_ITEM
- *
- * @param[in] ci       #CONF_ITEM to print file/lineno for.
- * @param[in] fmt      Msg format string.
- * @param[in] ...      Format args.
- */
-void _cf_log_err(CONF_ITEM const *ci, char const *fmt, ...)
-{
-       va_list ap;
-       char    *msg;
-
-       va_start(ap, fmt);
-       msg = talloc_vasprintf(NULL, fmt, ap);
-       va_end(ap);
-
-       if (!ci || !ci->filename) {
-               ERROR("%s", msg);
-       } else {
-               ERROR("%s[%d]: %s", ci->filename, ci->lineno, msg);
-       }
-
-       talloc_free(msg);
-}
-
-/** Log an error message relating to a #CONF_ITEM
- *
- * Drains the fr_strerror() stack emitting one or more error messages.
- *
- * @param[in] ci       #CONF_ITEM to print file/lineno for.
- * @param[in] fmt      Msg format string.
- * @param[in] ...      Format args.
- */
-void _cf_log_perr(CONF_ITEM const *ci, char const *fmt, ...)
-{
-       va_list ap;
-       char    *msg;
-
-       va_start(ap, fmt);
-       msg = talloc_vasprintf(NULL, fmt, ap);
-       va_end(ap);
-
-       if (!ci || !ci->filename) {
-               PERROR("%s", msg);
-       } else {
-               PERROR("%s[%d]: %s", ci->filename, ci->lineno, msg);
-       }
-
-       talloc_free(msg);
-}
-
-/** Log a warning message relating to a #CONF_ITEM
- *
- * @param[in] ci       #CONF_ITEM to print file/lineno for.
- * @param[in] fmt      Msg format string.
- * @param[in] ...      Format args.
- */
-void _cf_log_warn(CONF_ITEM const *ci, char const *fmt, ...)
-{
-       va_list ap;
-       char    *msg;
-
-       va_start(ap, fmt);
-       msg = talloc_vasprintf(NULL, fmt, ap);
-       va_end(ap);
-
-       if (!ci || !ci->filename) {
-               WARN("%s", msg);
-       } else {
-               WARN("%s[%d]: %s", ci->filename, ci->lineno, msg);
-       }
-
-       talloc_free(msg);
-}
-
-/** Log a info message relating to a #CONF_ITEM
- *
- * @param[in] ci       #CONF_ITEM to print file/lineno for.
- * @param[in] fmt      Msg format string.
- * @param[in] ...      Format args.
- */
-void _cf_log_info(CONF_ITEM const *ci, char const *fmt, ...)
-{
-       va_list ap;
-       char    *msg;
-
-       va_start(ap, fmt);
-       msg = talloc_vasprintf(NULL, fmt, ap);
-       va_end(ap);
-
-       if (!ci || !ci->filename || !DEBUG_ENABLED3) {
-               INFO("%s", msg);
-       } else {
-               INFO("%s[%d]: %s", ci->filename, ci->lineno, msg);
-       }
-
-       talloc_free(msg);
-}
-
-/** Log a debug message relating to a #CONF_ITEM
- *
- * @param[in] ci       #CONF_ITEM to print file/lineno for.
- * @param[in] fmt      Msg format string.
- * @param[in] ...      Format args.
- */
-void _cf_log_debug(CONF_ITEM const *ci, char const *fmt, ...)
-{
-       va_list ap;
-       char    *msg;
-
-       if (rad_debug_lvl < 1) return;
-
-       va_start(ap, fmt);
-       msg = talloc_vasprintf(NULL, fmt, ap);
-       va_end(ap);
-
-       if (!ci || !ci->filename || !DEBUG_ENABLED4) {
-               DEBUG("%s", msg);
-       } else {
-               DEBUG("%s[%d]: %s", ci->filename, ci->lineno, msg);
-       }
-
-       talloc_free(msg);
-}
-
-void cf_log_err_by_name(CONF_SECTION const *parent, char const *name, char const *fmt, ...)
-{
-       va_list         ap;
-       char            *msg;
-       CONF_PAIR const *cp;
-
-       va_start(ap, fmt);
-       msg = talloc_vasprintf(NULL, fmt, ap);
-       va_end(ap);
-
-       cp = cf_pair_find(parent, name);
-       if (cp) {
-               ERROR("%s[%d]: %s", cp->item.filename, cp->item.lineno, msg);
-       } else {
-               CONF_ITEM const *ci;
-
-               ci = cf_section_to_item(parent);
-               ERROR("%s[%d]: %s", ci->filename, ci->lineno, msg);
-       }
-       talloc_free(msg);
-}
-
-/** Print out debugging information about a CONFIG_ITEM
- *
- * @param[in] ci       being debugged.
- */
-void _cf_debug(CONF_ITEM const *ci)
-{
-       fr_cursor_t     cursor;
-       CONF_ITEM const *child;
-
-       /*
-        *      Print summary of the item
-        */
-       switch (ci->type) {
-       case CONF_ITEM_SECTION:
-       {
-               CONF_SECTION const *cs = cf_item_to_section(ci);
-               int i;
-
-               DEBUG("SECTION - %p", cs);
-               DEBUG("  name1         : %s", cs->name1);
-               DEBUG("  name2         : %s", cs->name2 ? cs->name2 : "<none>");
-               DEBUG("  name2_quote   : %s", fr_int2str(fr_token_quotes_table, cs->name2_quote, "<INVALID>"));
-               DEBUG("  argc          : %u", cs->argc);
-
-               for (i = 0; i < cs->argc; i++) {
-                       char const *quote = fr_int2str(fr_token_quotes_table, cs->argv_quote[i], "<INVALID>");
-                       DEBUG("  argv[%i]      : %s%s%s", i, quote, cs->argv[i], quote);
-               }
-       }
-               break;
-
-       case CONF_ITEM_PAIR:
-       {
-               CONF_PAIR const *cp = cf_item_to_pair(ci);
-
-               DEBUG("PAIR - %p", cp);
-               DEBUG("  attr          : %s", cp->attr);
-               DEBUG("  value         : %s", cp->value);
-               DEBUG("  operator      : %s", fr_int2str(fr_tokens_table, cp->op, "<INVALID>"));
-               DEBUG("  lhs_quote     : %s", fr_int2str(fr_token_quotes_table, cp->lhs_quote, "<INVALID>"));
-               DEBUG("  rhs_quote     : %s", fr_int2str(fr_token_quotes_table, cp->rhs_quote, "<INVALID>"));
-               DEBUG("  pass2         : %s", cp->pass2 ? "yes" : "no");
-               DEBUG("  parsed        : %s", cp->parsed ? "yes" : "no");
-       }
-               break;
-
-       case CONF_ITEM_DATA:
-       {
-               CONF_DATA const *cd = cf_item_to_data(ci);
-
-               DEBUG("DATA - %p", cd);
-               DEBUG("  type          : %s", cd->type);
-               DEBUG("  name          : %s", cd->name);
-               DEBUG("  data          : %p", cd->data);
-               DEBUG("  free wth prnt : %s", cd->free ? "yes" : "no");
-       }
-               break;
-
-       default:
-               DEBUG("INVALID - %p", ci);
-               return;
-       }
-
-       DEBUG("  filename      : %s", ci->filename);
-       DEBUG("  line          : %i", ci->lineno);
-       DEBUG("  next          : %p", ci->next);
-       DEBUG("  parent        : %p", ci->parent);
-       DEBUG("  children      : %s", ci->child ? "yes" : "no");
-       DEBUG("  ident1 tree   : %p (%u entries)", ci->ident1, ci->ident1 ? rbtree_num_elements(ci->ident1) : 0);
-       DEBUG("  ident2 tree   : %p (%u entries)", ci->ident2, ci->ident2 ? rbtree_num_elements(ci->ident2) : 0);
-
-       if (!ci->child) return;
-
-       /*
-        *      Print summary of the item's children
-        */
-       DEBUG("CHILDREN");
-       fr_cursor_copy(&cursor, &ci->cursor);
-
-       for (child = fr_cursor_head(&cursor);
-            child;
-            child = fr_cursor_next(&cursor)) {
-               char const *in_ident1, *in_ident2;
-
-               in_ident1 = rbtree_finddata(ci->ident1, child) == child? "in ident1 " : "";
-               in_ident2 = rbtree_finddata(ci->ident2, child) == child? "in ident2 " : "";
-
-               switch (child->type) {
-               case CONF_ITEM_SECTION:
-               {
-                       CONF_SECTION const *cs = cf_item_to_section(child);
-
-                       DEBUG("  SECTION %p (%s %s) %s%s", child, cs->name1, cs->name2 ? cs->name2 : "<none>",
-                             in_ident1, in_ident2);
-               }
-                       break;
-
-               case CONF_ITEM_PAIR:
-               {
-                       CONF_PAIR const *cp = cf_item_to_pair(child);
-                       char const      *lhs_quote = fr_int2str(fr_token_quotes_table, cp->lhs_quote, "<INVALID>");
-                       char const      *rhs_quote = fr_int2str(fr_token_quotes_table, cp->rhs_quote, "<INVALID>");
-
-                       DEBUG("  PAIR %p (%s%s%s %s %s%s%s) %s%s", child,
-                             lhs_quote, cp->attr, lhs_quote,
-                             fr_int2str(fr_tokens_table, cp->op, "<INVALID>"),
-                             rhs_quote, cp->value, rhs_quote,
-                             in_ident1, in_ident2);
-               }
-                       break;
-
-               case CONF_ITEM_DATA:
-               {
-                       CONF_DATA const *cd = cf_item_to_data(child);
-
-                       DEBUG("  DATA %p (%s *)%s = %p %s%s", child,
-                             cd->type, cd->name ? cd->name : "", cd->data,
-                             in_ident1, in_ident2);
-                       break;
-               }
-
-               default:
-                       DEBUG("  INVALID - %p", child);
-                       break;
-               }
-       }
-}
index 577a2002710e14cf9f5ea6c019cb44f67ac3ca02..5408f81c7c8a1a6ec482018a04bbfe249ed4d5fe 100644 (file)
@@ -2,7 +2,9 @@ TARGET  := libfreeradius-server.a
 
 SOURCES        :=      cond_eval.c \
                cond_tokenize.c \
-               conf_file.c \
+               cf_file.c \
+               cf_parse.c \
+               cf_util.c \
                connection.c \
                dl.c \
                exec.c \