Ensure we leave room for the trailing NUL
[freeradius.git] / src / main / conffile.c
index 0e3e49b..ee47afb 100644 (file)
@@ -34,11 +34,11 @@ RCSID("$Id$")
 
 #ifdef HAVE_DIRENT_H
 #include <dirent.h>
+#endif
 
 #ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
 #endif
-#endif
 
 #include <ctype.h>
 
@@ -53,13 +53,15 @@ struct conf_item {
        struct conf_item *next;
        struct conf_part *parent;
        int lineno;
+       const char *filename;
        CONF_ITEM_TYPE type;
 };
 struct conf_pair {
        CONF_ITEM item;
-       char *attr;
-       char *value;
-       LRAD_TOKEN operator;
+       const char *attr;
+       const char *value;
+       FR_TOKEN operator;
+       FR_TOKEN value_type;
 };
 struct conf_part {
        CONF_ITEM item;
@@ -97,6 +99,9 @@ static int cf_data_add_internal(CONF_SECTION *cs, const char *name,
 static void *cf_data_find_internal(CONF_SECTION *cs, const char *name,
                                   int flag);
 
+int cf_log_config = 1;
+int cf_log_modules = 1;
+
 /*
  *     Isolate the scary casts in these tiny provably-safe functions
  */
@@ -145,16 +150,34 @@ static CONF_ITEM *cf_datatoitem(CONF_DATA *cd)
  *     Create a new CONF_PAIR
  */
 static CONF_PAIR *cf_pair_alloc(const char *attr, const char *value,
-                               LRAD_TOKEN operator, CONF_SECTION *parent)
+                               FR_TOKEN operator, FR_TOKEN value_type,
+                               CONF_SECTION *parent)
 {
+       char *p;
+       size_t attr_len, value_len = 0;
        CONF_PAIR *cp;
 
-       cp = rad_malloc(sizeof(*cp));
+       if (!attr) return NULL;
+       attr_len = strlen(attr) + 1;
+       if (value) value_len = strlen(value) + 1;
+
+       p = rad_malloc(sizeof(*cp) + attr_len + value_len);
+
+       cp = (CONF_PAIR *) p;
        memset(cp, 0, sizeof(*cp));
        cp->item.type = CONF_ITEM_PAIR;
        cp->item.parent = parent;
-       cp->attr = strdup(attr);
-       cp->value = strdup(value);
+
+       p += sizeof(*cp);
+       memcpy(p, attr, attr_len);
+       cp->attr = p;
+
+       if (value) {
+               p += attr_len;
+               memcpy(p, value, value_len);
+               cp->value = p;
+       }
+       cp->value_type = value_type;
        cp->operator = operator;
 
        return cp;
@@ -167,10 +190,9 @@ void cf_pair_free(CONF_PAIR **cp)
 {
        if (!cp || !*cp) return;
 
-       if ((*cp)->attr)
-               free((*cp)->attr);
-       if ((*cp)->value)
-               free((*cp)->value);
+       /*
+        *      attr && value are allocated contiguous with cp.
+        */
 
 #ifndef NDEBUG
        memset(*cp, 0, sizeof(*cp));
@@ -185,7 +207,7 @@ static void cf_data_free(CONF_DATA **cd)
 {
        if (!cd || !*cd) return;
 
-       if ((*cd)->flag != 0) free((*cd)->name);
+       /* name is allocated contiguous with cd */
        if (!(*cd)->free) {
                free((*cd)->data);
        } else {
@@ -260,9 +282,10 @@ static int data_cmp(const void *a, const void *b)
 /*
  *     Free strings we've parsed into data structures.
  */
-static void cf_section_parse_free(void *base, const CONF_PARSER *variables)
+void cf_section_parse_free(CONF_SECTION *cs, void *base)
 {
        int i;
+       const CONF_PARSER *variables = cs->variables;
 
        /*
         *      Don't automatically free the strings if we're being
@@ -318,9 +341,7 @@ void cf_section_free(CONF_SECTION **cs)
 
        if (!cs || !*cs) return;
 
-       if ((*cs)->variables) {
-               cf_section_parse_free((*cs)->base, (*cs)->variables);
-       }
+       cf_section_parse_free(*cs, (*cs)->base);
 
        for (ci = (*cs)->children; ci; ci = next) {
                next = ci->next;
@@ -333,7 +354,6 @@ void cf_section_free(CONF_SECTION **cs)
                        break;
 
                case CONF_ITEM_SECTION: {
-                               
                                CONF_SECTION *section = cf_itemtosection(ci);
                                cf_section_free(&section);
                        }
@@ -350,10 +370,10 @@ void cf_section_free(CONF_SECTION **cs)
                }
        }
 
-       if ((*cs)->name1)
-               free((*cs)->name1);
-       if ((*cs)->name2)
-               free((*cs)->name2);
+       /*
+        *      Name1 and name2 are allocated contiguous with
+        *      cs.
+        */
        if ((*cs)->pair_tree)
                rbtree_free((*cs)->pair_tree);
        if ((*cs)->section_tree)
@@ -381,27 +401,32 @@ void cf_section_free(CONF_SECTION **cs)
 static CONF_SECTION *cf_section_alloc(const char *name1, const char *name2,
                                      CONF_SECTION *parent)
 {
+       size_t name1_len, name2_len = 0;
+       char *p;
        CONF_SECTION    *cs;
 
        if (!name1) return NULL;
 
-       cs = rad_malloc(sizeof(*cs));
+       name1_len = strlen(name1) + 1;
+       if (name2) name2_len = strlen(name2) + 1;
+
+       p = rad_malloc(sizeof(*cs) + name1_len + name2_len);
+
+       cs = (CONF_SECTION *) p;
        memset(cs, 0, sizeof(*cs));
        cs->item.type = CONF_ITEM_SECTION;
        cs->item.parent = parent;
-       cs->name1 = strdup(name1);
-       if (!cs->name1) {
-               cf_section_free(&cs);
-               return NULL;
-       }
-       
+
+       p += sizeof(*cs);
+       memcpy(p, name1, name1_len);
+       cs->name1 = p;
+
        if (name2 && *name2) {
-               cs->name2 = strdup(name2);
-               if (!cs->name2) {
-                       cf_section_free(&cs);
-                       return NULL;
-               }
+               p += name1_len;
+               memcpy(p, name2, name2_len);
+               cs->name2 = p;
        }
+
        cs->pair_tree = rbtree_create(pair_cmp, NULL, 0);
        if (!cs->pair_tree) {
                cf_section_free(&cs);
@@ -422,6 +447,42 @@ static CONF_SECTION *cf_section_alloc(const char *name1, const char *name2,
        return cs;
 }
 
+/*
+ *     Replace pair in a given section with a new pair,
+ *     of the given value.
+ */
+int cf_pair_replace(CONF_SECTION *cs, CONF_PAIR *cp, const char *value)
+{
+       CONF_PAIR *newp;
+       CONF_ITEM *ci, *cn, **last;
+
+       newp = cf_pair_alloc(cp->attr, value, cp->operator, cp->value_type,
+                            cs);
+       if (!newp) return -1;
+
+       ci = cf_pairtoitem(cp);
+       cn = cf_pairtoitem(newp);
+
+       /*
+        *      Find the old one from the linked list, and replace it
+        *      with the new one.
+        */
+       for (last = &cs->children; (*last) != NULL; last = &(*last)->next) {
+               if (*last == ci) {
+                       cn->next = (*last)->next;
+                       *last = cn;
+                       ci->next = NULL;
+                       break;
+               }
+       }
+
+       rbtree_deletebydata(cs->pair_tree, ci);
+
+       rbtree_insert(cs->pair_tree, cn);
+
+       return 0;
+}
+
 
 /*
  *     Add an item to a configuration section.
@@ -450,24 +511,26 @@ static void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci)
                        case CONF_ITEM_PAIR:
                                rbtree_insert(cs->pair_tree, ci);
                                break;
-                               
+
                        case CONF_ITEM_SECTION: {
-                               const CONF_SECTION *cs_new = cf_itemtosection(ci);
-                               
+                               CONF_SECTION *cs_new = cf_itemtosection(ci);
+
                                if (!cs->section_tree) {
                                        cs->section_tree = rbtree_create(section_cmp, NULL, 0);
-                                       /* ignore any errors */
+                                       if (!cs->section_tree) {
+                                               radlog(L_ERR, "Out of memory");
+                                               _exit(1);
+                                       }
                                }
-                               
-                               if (cs->section_tree) {
-                                       rbtree_insert(cs->section_tree, cs_new);                                }
-                               
+
+                               rbtree_insert(cs->section_tree, cs_new);
+
                                /*
                                 *      Two names: find the named instance.
                                 */
-                               if (cs_new->name2) {
+                               {
                                        CONF_SECTION *old_cs;
-                                       
+
                                        /*
                                         *      Find the FIRST
                                         *      CONF_SECTION having
@@ -477,7 +540,7 @@ static void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci)
                                         */
                                        old_cs = rbtree_finddata(cs->section_tree, cs_new);
                                        if (!old_cs) return; /* this is a bad error! */
-                                       
+
                                        if (!old_cs->name2_tree) {
                                                old_cs->name2_tree = rbtree_create(name2_cmp,
                                                                                   NULL, 0);
@@ -505,28 +568,151 @@ static void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci)
        } /* loop over ci */
 }
 
+
+CONF_ITEM *cf_reference_item(const CONF_SECTION *parentcs,
+                            CONF_SECTION *outercs,
+                            const char *ptr)
+{
+       CONF_PAIR *cp;
+       CONF_SECTION *next;
+       const CONF_SECTION *cs = outercs;
+       char name[8192];
+       char *p;
+
+       strlcpy(name, ptr, sizeof(name));
+       p = name;
+
+       /*
+        *      ".foo" means "foo from the current section"
+        */
+       if (*p == '.') {
+               p++;
+               
+               /*
+                *      ..foo means "foo from the section
+                *      enclosing this section" (etc.)
+                */
+               while (*p == '.') {
+                       if (cs->item.parent)
+                               cs = cs->item.parent;
+                       p++;
+               }
+
+               /*
+                *      "foo.bar.baz" means "from the root"
+                */
+       } else if (strchr(p, '.') != NULL) {
+               if (!parentcs) goto no_such_item;
+
+               cs = parentcs;
+       }
+
+       while (*p) {
+               char *q, *r;
+
+               r = strchr(p, '[');
+               q = strchr(p, '.');
+               if (!r && !q) break;
+
+               if (r && q > r) q = NULL;
+               if (q && q < r) r = NULL;
+
+               /*
+                *      Split off name2.
+                */
+               if (r) {
+                       q = strchr(r + 1, ']');
+                       if (!q) return NULL; /* parse error */
+
+                       /*
+                        *      Points to foo[bar]xx: parse error,
+                        *      it should be foo[bar] or foo[bar].baz
+                        */
+                       if (q[1] && q[1] != '.') goto no_such_item;
+
+                       *r = '\0';
+                       *q = '\0';
+                       next = cf_section_sub_find_name2(cs, p, r + 1);
+                       *r = '[';
+                       *q = ']';
+
+                       /*
+                        *      Points to a named instance of a section.
+                        */
+                       if (!q[1]) {
+                               if (!next) goto no_such_item;
+                               return cf_sectiontoitem(next);
+                       }
+
+                       q++;    /* ensure we skip the ']' and '.' */
+
+               } else {
+                       *q = '\0';
+                       next = cf_section_sub_find(cs, p);
+                       *q = '.';
+               }
+
+               if (!next) break; /* it MAY be a pair in this section! */
+
+               cs = next;
+               p = q + 1;
+       }
+
+       if (!*p) goto no_such_item;
+
+ retry:
+       /*
+        *      Find it in the current referenced
+        *      section.
+        */
+       cp = cf_pair_find(cs, p);
+       if (cp) return cf_pairtoitem(cp);
+
+       next = cf_section_sub_find(cs, p);
+       if (next) return cf_sectiontoitem(next);
+       
+       /*
+        *      "foo" is "in the current section, OR in main".
+        */
+       if ((p == name) && (parentcs != NULL) && (cs != parentcs)) {
+               cs = parentcs;
+               goto retry;
+       }
+
+no_such_item:
+       DEBUG2("WARNING: No such configuration item %s", ptr);
+       return NULL;
+}
+
+
+CONF_SECTION *cf_top_section(CONF_SECTION *cs)
+{
+       while (cs->item.parent != NULL) {
+               cs = cs->item.parent;
+       }
+
+       return cs;
+}
+
+
 /*
  *     Expand the variables in an input string.
  */
 static const char *cf_expand_variables(const char *cf, int *lineno,
-                                      const CONF_SECTION *outercs,
+                                      CONF_SECTION *outercs,
                                       char *output, const char *input)
 {
        char *p;
        const char *end, *ptr;
-       char name[8192];
        const CONF_SECTION *parentcs;
+       char name[8192];
 
        /*
         *      Find the master parent conf section.
         *      We can't use mainconfig.config, because we're in the
         *      process of re-building it, and it isn't set up yet...
         */
-       for (parentcs = outercs;
-            parentcs->item.parent != NULL;
-            parentcs = parentcs->item.parent) {
-               /* do nothing */
-       }
+       parentcs = cf_top_section(outercs);
 
        p = output;
        ptr = input;
@@ -535,9 +721,8 @@ static const char *cf_expand_variables(const char *cf, int *lineno,
                 *      Ignore anything other than "${"
                 */
                if ((*ptr == '$') && (ptr[1] == '{')) {
-                       int up;
+                       CONF_ITEM *ci;
                        CONF_PAIR *cp;
-                       const CONF_SECTION *cs;
 
                        /*
                         *      FIXME: Add support for ${foo:-bar},
@@ -559,107 +744,35 @@ static const char *cf_expand_variables(const char *cf, int *lineno,
 
                        ptr += 2;
 
-                       cp = NULL;
-                       up = 0;
-
                        /*
-                        *      ${.foo} means "foo from the current section"
+                        *      Can't really happen because input lines are
+                        *      capped at 8k, which is sizeof(name)
                         */
-                       if (*ptr == '.') {
-                               up = 1;
-                               cs = outercs;
-                               ptr++;
-
-                               /*
-                                *      ${..foo} means "foo from the section
-                                *      enclosing this section" (etc.)
-                                */
-                               while (*ptr == '.') {
-                                       if (cs->item.parent)
-                                               cs = cs->item.parent;
-                                       ptr++;
-                               }
-
-                       } else {
-                               const char *q;
-                               /*
-                                *      ${foo} is local, with
-                                *      main as lower priority
-                                */
-                               cs = outercs;
-
-                               /*
-                                *      ${foo.bar.baz} is always rooted
-                                *      from the top.
-                                */
-                               for (q = ptr; *q && q != end; q++) {
-                                       if (*q == '.') {
-                                               cs = parentcs;
-                                               up = 1;
-                                               break;
-                                       }
-                               }
+                       if ((size_t) (end - ptr) >= sizeof(name)) {
+                               radlog(L_ERR, "%s[%d]: Reference string is too large",
+                                      cf, *lineno);
+                               return NULL;
                        }
 
-                       while (cp == NULL) {
-                               char *q;
-                               /*
-                                *      Find the next section.
-                                */
-                               for (q = name;
-                                    (*ptr != 0) && (*ptr != '.') &&
-                                            (ptr != end);
-                                    q++, ptr++) {
-                                       *q = *ptr;
-                               }
-                               *q = '\0';
-
-                               /*
-                                *      The character is a '.', find a
-                                *      section (as the user has given
-                                *      us a subsection to find)
-                                */
-                               if (*ptr == '.') {
-                                       CONF_SECTION *next;
-
-                                       ptr++;  /* skip the period */
-
-                                       /*
-                                        *      Find the sub-section.
-                                        */
-                                       next = cf_section_sub_find(cs, name);
-                                       if (next == NULL) {
-                                               radlog(L_ERR, "config: No such section %s in variable %s", name, input);
-                                               return NULL;
-                                       }
-                                       cs = next;
+                       memcpy(name, ptr, end - ptr);
+                       name[end - ptr] = '\0';
 
-                               } else { /* no period, must be a conf-part */
-                                       /*
-                                        *      Find in the current referenced
-                                        *      section.
-                                        */
-                                       cp = cf_pair_find(cs, name);
-                                       if (cp == NULL) {
-                                               /*
-                                                *      It it was NOT ${..foo}
-                                                *      then look in the
-                                                *      top-level config items.
-                                                */
-                                               if (!up) cp = cf_pair_find(parentcs, name);
-                                       }
-                                       if (cp == NULL) {
-                                               radlog(L_ERR, "config: No such configuration item %s in section %s when expanding string \"%s\"", name,
-                                                      cf_section_name1(cs),
-                                                      input);
-                                               return NULL;
-                                       }
-                               }
-                       } /* until cp is non-NULL */
+                       ci = cf_reference_item(parentcs, outercs, name);
+                       if (!ci || (ci->type != CONF_ITEM_PAIR)) {
+                               radlog(L_ERR, "%s[%d]: Reference \"%s\" not found",
+                                      cf, *lineno, input);
+                               return NULL;
+                       }
 
                        /*
                         *  Substitute the value of the variable.
                         */
+                       cp = cf_itemtopair(ci);
+                       if (!cp->value) {
+                               radlog(L_ERR, "%s[%d]: Reference \"%s\" has no value",
+                                      cf, *lineno, input);
+                               return NULL;
+                       }
                        strcpy(p, cp->value);
                        p += strlen(p);
                        ptr = end + 1;
@@ -682,6 +795,16 @@ static const char *cf_expand_variables(const char *cf, int *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)) {
+                               radlog(L_ERR, "%s[%d]: Environment variable name is too large",
+                                      cf, *lineno);
+                               return NULL;
+                       }
+
                        memcpy(name, ptr, end - ptr);
                        name[end - ptr] = '\0';
 
@@ -727,11 +850,11 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
        int rcode = 0;
        char **q;
        const char *value;
-       lrad_ipaddr_t ipaddr;
-       const CONF_PAIR *cp;
+       fr_ipaddr_t ipaddr;
+       const CONF_PAIR *cp = NULL;
        char ipbuf[128];
 
-       cp = cf_pair_find(cs, name);
+       if (cs) cp = cf_pair_find(cs, name);
        if (cp) {
                value = cp->value;
 
@@ -743,6 +866,10 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                value = dflt;
        }
 
+       if (!value) {
+               return 0;
+       }
+
        switch (type) {
        case PW_TYPE_BOOLEAN:
                /*
@@ -759,20 +886,20 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                        radlog(L_ERR, "Bad value \"%s\" for boolean variable %s", value, name);
                        return -1;
                }
-               DEBUG2("\t%s = %s", name, value);
+               cf_log_info(cs, "\t%s = %s", name, value);
                break;
-               
+
        case PW_TYPE_INTEGER:
                *(int *)data = strtol(value, 0, 0);
-               DEBUG2("\t%s = %d", name, *(int *)data);
+               cf_log_info(cs, "\t%s = %d", name, *(int *)data);
                break;
-               
+
        case PW_TYPE_STRING_PTR:
                q = (char **) data;
                if (*q != NULL) {
                        free(*q);
                }
-               
+
                /*
                 *      Expand variables which haven't already been
                 *      expanded automagically when the configuration
@@ -781,7 +908,9 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                if (value == dflt) {
                        char buffer[8192];
 
-                       int lineno = cs->item.lineno;
+                       int lineno = 0;
+
+                       if (cs) lineno = cs->item.lineno;
 
                        /*
                         *      FIXME: sizeof(buffer)?
@@ -791,11 +920,11 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                                                    cs, buffer, value);
                        if (!value) return -1;
                }
-               
-               DEBUG2("\t%s = \"%s\"", name, value ? value : "(null)");
+
+               cf_log_info(cs, "\t%s = \"%s\"", name, value ? value : "(null)");
                *q = value ? strdup(value) : NULL;
                break;
-               
+
                /*
                 *      This is the same as PW_TYPE_STRING_PTR,
                 *      except that we also "stat" the file, and
@@ -806,7 +935,7 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                if (*q != NULL) {
                        free(*q);
                }
-               
+
                /*
                 *      Expand variables which haven't already been
                 *      expanded automagically when the configuration
@@ -815,7 +944,9 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                if (value == dflt) {
                        char buffer[8192];
 
-                       int lineno = cs->item.lineno;
+                       int lineno = 0;
+
+                       if (cs) lineno = cs->item.lineno;
 
                        /*
                         *      FIXME: sizeof(buffer)?
@@ -825,14 +956,18 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                                                    cs, buffer, value);
                        if (!value) return -1;
                }
-               
-               DEBUG2("\t%s = \"%s\"", name, value ? value : "(null)");
+
+               cf_log_info(cs, "\t%s = \"%s\"", name, value ? value : "(null)");
                *q = value ? strdup(value) : NULL;
 
                /*
                 *      And now we "stat" the file.
+                *
+                *      FIXME: This appears to leak memory on exit,
+                *      and we don't use this information.  So it's
+                *      commented out for now.
                 */
-               if (*q) {
+               if (0 && *q) {
                        struct stat buf;
 
                        if (stat(*q, &buf) == 0) {
@@ -853,35 +988,40 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                 */
                if (strcmp(value, "*") == 0) {
                        *(uint32_t *) data = htonl(INADDR_ANY);
-                       DEBUG2("\t%s = *", name);
+                       cf_log_info(cs, "\t%s = *", name);
                        break;
                }
                if (ip_hton(value, AF_INET, &ipaddr) < 0) {
                        radlog(L_ERR, "Can't find IP address for host %s", value);
                        return -1;
                }
-               DEBUG2("\t%s = %s IP address [%s]", name, value,
+               
+               if (strspn(value, "0123456789.") == strlen(value)) {
+                       cf_log_info(cs, "\t%s = %s", name, value);
+               } else {
+                       cf_log_info(cs, "\t%s = %s IP address [%s]", name, value,
                               ip_ntoh(&ipaddr, ipbuf, sizeof(ipbuf)));
+               }
                *(uint32_t *) data = ipaddr.ipaddr.ip4addr.s_addr;
                break;
-               
+
        case PW_TYPE_IPV6ADDR:
                if (ip_hton(value, AF_INET6, &ipaddr) < 0) {
                        radlog(L_ERR, "Can't find IPv6 address for host %s", value);
                        return -1;
                }
-               DEBUG2("\t%s = %s IPv6 address [%s]", name, value,
+               cf_log_info(cs, "\t%s = %s IPv6 address [%s]", name, value,
                               ip_ntoh(&ipaddr, ipbuf, sizeof(ipbuf)));
                memcpy(data, &ipaddr.ipaddr.ip6addr,
                       sizeof(ipaddr.ipaddr.ip6addr));
                break;
-               
+
        default:
                radlog(L_ERR, "type %d not supported yet", type);
                return -1;
                break;
        } /* switch over variable type */
-       
+
        return rcode;
 }
 
@@ -896,11 +1036,13 @@ int cf_section_parse(CONF_SECTION *cs, void *base,
        int i;
        void *data;
 
+       cs->variables = variables; /* this doesn't hurt anything */
+
        if (!cs->name2) {
-               DEBUG2("%.*s%s {", cs->depth, parse_spaces,
+               cf_log_info(cs, "%.*s%s {", cs->depth, parse_spaces,
                       cs->name1);
        } else {
-               DEBUG2("%.*s%s %s {", cs->depth, parse_spaces,
+               cf_log_info(cs, "%.*s%s %s {", cs->depth, parse_spaces,
                       cs->name1, cs->name2);
        }
 
@@ -912,9 +1054,9 @@ int cf_section_parse(CONF_SECTION *cs, void *base,
                 *      Handle subsections specially
                 */
                if (variables[i].type == PW_TYPE_SUBSECTION) {
-                       const CONF_SECTION *subcs;
+                       CONF_SECTION *subcs;
                        subcs = cf_section_sub_find(cs, variables[i].name);
-                       
+
                        /*
                         *      If the configuration section is NOT there,
                         *      then ignore it.
@@ -928,14 +1070,14 @@ int cf_section_parse(CONF_SECTION *cs, void *base,
                                DEBUG2("Internal sanity check 1 failed in cf_section_parse");
                                goto error;
                        }
-                       
+
                        if (cf_section_parse(subcs, base,
                                             (const CONF_PARSER *) variables[i].dflt) < 0) {
                                goto error;
                        }
                        continue;
                } /* else it's a CONF_PAIR */
-               
+
                if (variables[i].data) {
                        data = variables[i].data; /* prefer this. */
                } else if (base) {
@@ -954,30 +1096,140 @@ int cf_section_parse(CONF_SECTION *cs, void *base,
                }
        } /* for all variables in the configuration section */
 
-       DEBUG2("%.*s}", cs->depth, parse_spaces);
+       cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
 
        cs->base = base;
-       cs->variables = variables;
 
        return 0;
 
  error:
-       DEBUG2("%.*s}", cs->depth, parse_spaces);
-       cf_section_parse_free(base, variables);
+       cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
+       cf_section_parse_free(cs, base);
        return -1;
 }
 
 
 /*
+ *     Sanity check the "if" or "elsif", presuming that the first '('
+ *     has already been eaten.
+ *
+ *     We're not really parsing it here, just checking if it's mostly
+ *     well-formed.
+ */
+static int condition_looks_ok(const char **ptr)
+{
+       int num_braces = 1;
+       int quote = 0;
+       const char *p = *ptr;
+
+       while (*p) {
+               if (quote) {
+                       if (*p == quote) {
+                               p++;
+                               quote = 0;
+                               continue;
+                       }
+
+                       if (*p == '\\') {
+                               if (!p[1]) {
+                                       return 0; /* no trailing slash */
+                               }
+                               p += 2;
+                               continue;
+                       }
+                       p++;
+                       continue;
+               }
+
+               switch (*p) {
+               case '\\':
+                       if (!p[1]) {
+                               return 0; /* no trailing slash */
+                       }
+                       p += 2;
+                       continue;
+
+               case '(':
+                       num_braces++;
+                       p++;
+                       continue;
+
+               case ')':
+                       if (num_braces == 1) {
+                               const char *q = p + 1;
+
+                               /*
+                                *      Validate that there isn't much
+                                *      else after the closing brace.
+                                */
+                               while ((*q == ' ') || (*q == '\t')) q++;
+
+                               /*
+                                *      Parse error.
+                                */
+                               if (*q != '{') {
+                                       DEBUG2("Expected open brace '{' after condition at %s", p);
+                                       return 0;
+                               }
+
+                               *ptr = p + 1; /* include the trailing ')' */
+                               return 1;
+                       }
+                       num_braces--;
+                       p++;
+                       continue;
+
+               case '"':
+               case '\'':
+               case '/':
+               case '`':
+                       quote = *p;
+                       /* FALL-THROUGH */
+
+               default:
+                       p++;
+                       break;
+               }
+       }
+
+       DEBUG3("Unexpected error");
+       return 0;
+}
+
+
+static const char *cf_local_file(CONF_SECTION *cs, const char *local,
+                                char *buffer, size_t bufsize)
+{
+       size_t dirsize;
+       const char *p;
+       CONF_SECTION *parentcs = cf_top_section(cs);
+
+       p = strrchr(parentcs->item.filename, FR_DIR_SEP);
+       if (!p) return local;
+
+       dirsize = (p - parentcs->item.filename) + 1;
+
+       if ((dirsize + strlen(local)) >= bufsize) {
+               return NULL;
+       }
+
+       memcpy(buffer, parentcs->item.filename, dirsize);
+       strlcpy(buffer + dirsize, local, bufsize - dirsize);
+
+       return buffer;
+}
+
+
+/*
  *     Read a part of the config file.
  */
-static int cf_section_read(const char *file, int *lineno, FILE *fp,
+static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                           CONF_SECTION *current)
 
 {
        CONF_SECTION *this, *css;
        CONF_PAIR *cpn;
-       char *ptr;
+       const char *ptr;
        const char *value;
        char buf[8192];
        char buf1[8192];
@@ -985,7 +1237,7 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
        char buf3[8192];
        int t1, t2, t3;
        char *cbuf = buf;
-       int len;
+       size_t len;
 
        this = current;         /* add items here */
 
@@ -993,55 +1245,100 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
         *      Read, checking for line continuations ('\\' at EOL)
         */
        for (;;) {
-               int eof;
+               int at_eof;
 
                /*
                 *      Get data, and remember if we are at EOF.
                 */
-               eof = (fgets(cbuf, sizeof(buf) - (cbuf - buf), fp) == NULL);
+               at_eof = (fgets(cbuf, sizeof(buf) - (cbuf - buf), fp) == NULL);
                (*lineno)++;
 
-               len = strlen(cbuf);
-
                /*
-                *      We've filled the buffer, and there isn't
-                *      a CR in it.  Die!
+                *      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.
                 */
-               if ((len == (sizeof(buf) - 1)) &&
-                   (cbuf[len - 1] != '\n')) {
+               len = strlen(cbuf);
+               if ((cbuf + len + 1) >= (buf + sizeof(buf))) {
                        radlog(L_ERR, "%s[%d]: Line too long",
-                              file, *lineno);
+                              filename, *lineno);
                        return -1;
                }
 
                /*
-                *  Check for continuations.
+                *      Not doing continuations: check for edge
+                *      conditions.
                 */
-               if (cbuf[len - 1] == '\n') len--;
+               if (cbuf == buf) {
+                       if (at_eof) break;
+                       
+                       ptr = buf;
+                       while (*ptr && isspace((int) *ptr)) ptr++;
 
-               /*
-                *      Last character is '\\'.  Over-write it,
-                *      and read another line.
-                */
-               if ((len > 0) && (cbuf[len - 1] == '\\')) {
+                       if (!*ptr || (*ptr == '#')) continue;
+
+               } else if (at_eof || (len == 0)) {
+                       radlog(L_ERR, "%s[%d]: Continuation at EOF is illegal",
+                              filename, *lineno);
+                       return -1;
+               }
+
+               /*
+                *      See if there's a continuation.
+                */
+               while ((len > 0) &&
+                      ((cbuf[len - 1] == '\n') || (cbuf[len - 1] == '\r'))) {
+                       len--;
+                       cbuf[len] = '\0';
+               }
+
+               if ((len > 0) && (cbuf[len - 1] == '\\')) {
                        cbuf[len - 1] = '\0';
                        cbuf += len - 1;
                        continue;
                }
 
+               ptr = cbuf = buf;
+
                /*
-                *  We're at EOF, and haven't read anything.  Stop.
+                *      The parser is getting to be evil.
                 */
-               if (eof && (cbuf == buf)) {
-                       break;
-               }
+               while ((*ptr == ' ') || (*ptr == '\t')) ptr++;
 
-               ptr = cbuf = buf;
-               t1 = gettoken(&ptr, buf1, sizeof(buf1));
+               if (((ptr[0] == '%') && (ptr[1] == '{')) ||
+                   (ptr[0] == '`')) {
+                       int hack;
 
-               if ((*buf1 == '#') || (*buf1 == '\0')) {
-                       continue;
-              }
+                       if (ptr[0] == '%') {
+                               hack = rad_copy_variable(buf1, ptr);
+                       } else {
+                               hack = rad_copy_string(buf1, ptr);
+                       }
+                       if (hack < 0) {
+                               radlog(L_ERR, "%s[%d]: Invalid expansion: %s",
+                                      filename, *lineno, ptr);
+                               return -1;
+                       }
+
+                       t1 = T_BARE_WORD;
+                       ptr += hack;
+
+                       t2 = gettoken(&ptr, buf2, sizeof(buf2));
+                       switch (t2) {
+                       case T_EOL:
+                       case T_HASH:
+                               goto do_bare_word;
+                               
+                       default:
+                               radlog(L_ERR, "%s[%d]: Invalid expansion: %s",
+                                      filename, *lineno, ptr);
+                               return -1;
+                       }
+               } else {
+                       t1 = gettoken(&ptr, buf1, sizeof(buf1));
+               }
 
                /*
                 *      The caller eats "name1 name2 {", and calls us
@@ -1052,9 +1349,9 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
               if (t1 == T_RCBRACE) {
                       if (this == current) {
                               radlog(L_ERR, "%s[%d]: Too many closing braces",
-                                     file, *lineno);
+                                     filename, *lineno);
                               return -1;
-                              
+
                       }
                       this = this->item.parent;
                       continue;
@@ -1066,12 +1363,30 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
                 *      This *SHOULD* work for any level include.
                 *      I really really really hate this file.  -cparker
                 */
-               if (strcasecmp(buf1, "$INCLUDE") == 0) {
-                       t2 = getword(&ptr, buf2, sizeof(buf2));
+              if ((strcasecmp(buf1, "$INCLUDE") == 0) ||
+                  (strcasecmp(buf1, "$-INCLUDE") == 0)) {
+                      int relative = 1;
+
+                       t2 = getword(&ptr, buf2, sizeof(buf2));
+
+                       if (buf2[0] == '$') relative = 0;
 
-                       value = cf_expand_variables(file, lineno, this, buf, buf2);
+                       value = cf_expand_variables(filename, lineno, this, buf, buf2);
                        if (!value) return -1;
 
+                       if (!FR_DIR_IS_RELATIVE(value)) relative = 0;
+
+                       if (relative) {
+                               value = cf_local_file(current, value, buf3,
+                                                     sizeof(buf3));
+                               if (!value) {
+                                       radlog(L_ERR, "%s[%d]: Directories too deep.",
+                                              filename, *lineno);
+                                       return -1;
+                               }
+                       }
+
+
 #ifdef HAVE_DIRENT_H
                        /*
                         *      $INCLUDE foo/
@@ -1084,11 +1399,11 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
                                struct dirent   *dp;
                                struct stat stat_buf;
 
-                               DEBUG2( "Config:   including files in directory: %s", value );
+                               DEBUG2("including files in directory %s", value );
                                dir = opendir(value);
                                if (!dir) {
                                        radlog(L_ERR, "%s[%d]: Error reading directory %s: %s",
-                                              file, *lineno, value,
+                                              filename, *lineno, value,
                                               strerror(errno));
                                        return -1;
                                }
@@ -1107,6 +1422,7 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
                                        for (p = dp->d_name; *p != '\0'; p++) {
                                                if (isalpha((int)*p) ||
                                                    isdigit((int)*p) ||
+                                                   (*p == '-') ||
                                                    (*p == '_') ||
                                                    (*p == '.')) continue;
                                                break;
@@ -1130,6 +1446,15 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
                        }  else
 #endif
                        { /* it was a normal file */
+                               if (buf1[1] == '-') {
+                                       struct stat statbuf;
+
+                                       if (stat(value, &statbuf) < 0) {
+                                               DEBUG("WARNING: Not including file %s: %s", value, strerror(errno));
+                                               continue;
+                                       }
+                               }
+
                                if (cf_file_include(value, this) < 0) {
                                        return -1;
                                }
@@ -1137,29 +1462,187 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
                        continue;
                } /* we were in an include */
 
+              if (strcasecmp(buf1, "$template") == 0) {
+                      CONF_ITEM *ci;
+                      CONF_SECTION *parentcs, *templatecs;
+                      t2 = getword(&ptr, buf2, sizeof(buf2));
+
+                      parentcs = cf_top_section(current);
+
+                      templatecs = cf_section_sub_find(parentcs, "templates");
+                      if (!templatecs) {
+                               radlog(L_ERR, "%s[%d]: No \"templates\" section for reference \"%s\"",
+                                      filename, *lineno, buf2);
+                               return -1;
+                      }
+
+                      ci = cf_reference_item(parentcs, templatecs, buf2);
+                      if (!ci || (ci->type != CONF_ITEM_SECTION)) {
+                               radlog(L_ERR, "%s[%d]: Reference \"%s\" not found",
+                                      filename, *lineno, buf2);
+                               return -1;
+                      }
+                      
+                      if (this->template) {
+                               radlog(L_ERR, "%s[%d]: Section already has a template",
+                                      filename, *lineno);
+                               return -1;
+                      }
+
+                      this->template = cf_itemtosection(ci);
+                      continue;
+              }
+
                /*
-                *      No '=': must be a section or sub-section.
+                *      Ensure that the user can't add CONF_PAIRs
+                *      with 'internal' names;
                 */
-               if (strchr(ptr, '=') == NULL) {
-                       t2 = gettoken(&ptr, buf2, sizeof(buf2));
-                       t3 = gettoken(&ptr, buf3, sizeof(buf3));
-               } else {
-                       t2 = gettoken(&ptr, buf2, sizeof(buf2));
-                       t3 = getword(&ptr, buf3, sizeof(buf3));
+               if (buf1[0] == '_') {
+                       radlog(L_ERR, "%s[%d]: Illegal configuration pair name \"%s\"",
+                                       filename, *lineno, buf1);
+                       return -1;
                }
 
                /*
-                * Perhaps a subsection.
+                *      Grab the next token.
                 */
-               if (t2 == T_LCBRACE || t3 == T_LCBRACE) {
+               t2 = gettoken(&ptr, buf2, sizeof(buf2));
+               switch (t2) {
+               case T_EOL:
+               case T_HASH:
+               do_bare_word:
+                       t3 = t2;
+                       t2 = T_OP_EQ;
+                       value = NULL;
+                       goto do_set;
+
+               case T_OP_ADD:
+               case T_OP_CMP_EQ:
+               case T_OP_SUB:
+               case T_OP_LE:
+               case T_OP_GE:
+               case T_OP_CMP_FALSE:
+                       if (!this || (strcmp(this->name1, "update") != 0)) {
+                               radlog(L_ERR, "%s[%d]: Invalid operator in assignment",
+                                      filename, *lineno);
+                               return -1;
+                       }
+
+               case T_OP_EQ:
+               case T_OP_SET:
+                       t3 = getstring(&ptr, buf3, sizeof(buf3));
+                       if (t3 == T_OP_INVALID) {
+                               radlog(L_ERR, "%s[%d]: Parse error: %s",
+                                      filename, *lineno,
+                                      fr_strerror());
+                               return -1;
+                       }
+
+                       /*
+                        *      Handle variable substitution via ${foo}
+                        */
+                       if ((t3 == T_BARE_WORD) ||
+                           (t3 == T_DOUBLE_QUOTED_STRING)) {
+                               value = cf_expand_variables(filename, lineno, this,
+                                                           buf, buf3);
+                               if (!value) return -1;
+                       } else if ((t3 == T_EOL) ||
+                                  (t3 == T_HASH)) {
+                               value = NULL;
+                       } else {
+                               value = buf3;
+                       }
+                       
+                       /*
+                        *      Add this CONF_PAIR to our CONF_SECTION
+                        */
+               do_set:
+                       cpn = cf_pair_alloc(buf1, value, t2, t3, this);
+                       cpn->item.filename = filename;
+                       cpn->item.lineno = *lineno;
+                       cf_item_add(this, cf_pairtoitem(cpn));
+                       continue;
+
+                       /*
+                        *      This horrible code is here to support
+                        *      if/then/else failover in the
+                        *      authorize, etc. sections.  It makes no
+                        *      sense anywhere else.
+                        */
+               case T_LBRACE:
+                       if ((strcmp(buf1, "if") == 0) ||
+                           (strcmp(buf1, "elsif") == 0)) {
+                               const char *end = ptr;
+                               CONF_SECTION *server;
+
+                               if (!condition_looks_ok(&end)) {
+                                       radlog(L_ERR, "%s[%d]: Parse error in condition at: %s",
+                                              filename, *lineno, ptr);
+                                       return -1;
+                               }
+
+                               if ((size_t) (end - ptr) >= (sizeof(buf2) - 1)) {
+                                       radlog(L_ERR, "%s[%d]: Statement too complicated after \"%s\"",
+                                              filename, *lineno, buf1);
+                                       return -1;
+                               }
+
+                               /*
+                                *      More sanity checking.  This is
+                                *      getting to be a horrible hack.
+                                */
+                               server = this;
+                               while (server) {
+                                       if (strcmp(server->name1, "server") == 0) break;
+                                       server = server->item.parent;
+                               }
+                               
+                               if (0 && !server) {
+                                       radlog(L_ERR, "%s[%d]: Processing directives such as \"%s\" cannot be used here.",
+                                              filename, *lineno, buf1);
+                                       return -1;
+                               }
+
+                               buf2[0] = '(';
+                               memcpy(buf2 + 1, ptr, end - ptr);
+                               buf2[end - ptr + 1] = '\0';
+                               ptr = end + 1;
+                               t2 = T_BARE_WORD;
+                               goto section_alloc;
+
+                       } else {
+                               radlog(L_ERR, "%s[%d]: Parse error after \"%s\"",
+                                      filename, *lineno, buf1);
+                               return -1;
+                       }
+
+                       /* FALL-THROUGH */
+
+                       /*
+                        *      No '=', must be a section or sub-section.
+                        */
+               case T_BARE_WORD:
+               case T_DOUBLE_QUOTED_STRING:
+               case T_SINGLE_QUOTED_STRING:
+                       t3 = gettoken(&ptr, buf3, sizeof(buf3));
+                       if (t3 != T_LCBRACE) {
+                               radlog(L_ERR, "%s[%d]: Expecting section start brace '{' after \"%s %s\"",
+                                      filename, *lineno, buf1, buf2);
+                               return -1;
+                       }
+
+               case T_LCBRACE:
+               section_alloc:
                        css = cf_section_alloc(buf1,
                                               t2 == T_LCBRACE ? NULL : buf2,
                                               this);
                        if (!css) {
                                radlog(L_ERR, "%s[%d]: Failed allocating memory for section",
-                                               file, *lineno);
+                                               filename, *lineno);
+                               return -1;
                        }
                        cf_item_add(this, cf_sectiontoitem(css));
+                       css->item.filename = filename;
                        css->item.lineno = *lineno;
 
                        /*
@@ -1167,49 +1650,12 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
                         */
                        this = css;
                        continue;
-               }
-
-               /*
-                *      Ignore semi-colons.
-                */
-               if (*buf2 == ';')
-                       *buf2 = '\0';
-
-               /*
-                *      Must be a normal attr = value line.
-                */
-               if (buf1[0] != 0 && buf2[0] == 0 && buf3[0] == 0) {
-                       t2 = T_OP_EQ;
-               } else if (buf1[0] == 0 || buf2[0] == 0 ||
-                          (t2 < T_EQSTART || t2 > T_EQEND)) {
-                       radlog(L_ERR, "%s[%d]: Line is not in 'attribute = value' format",
-                                       file, *lineno);
-                       return -1;
-               }
 
-               /*
-                *      Ensure that the user can't add CONF_PAIRs
-                *      with 'internal' names;
-                */
-               if (buf1[0] == '_') {
-                       radlog(L_ERR, "%s[%d]: Illegal configuration pair name \"%s\"",
-                                       file, *lineno, buf1);
+               default:
+                       radlog(L_ERR, "%s[%d]: Parse error after \"%s\"",
+                              filename, *lineno, buf1);
                        return -1;
                }
-
-               /*
-                *      Handle variable substitution via ${foo}
-                */
-               value = cf_expand_variables(file, lineno, this, buf, buf3);
-               if (!value) return -1;
-
-
-               /*
-                *      Add this CONF_PAIR to our CONF_SECTION
-                */
-               cpn = cf_pair_alloc(buf1, value, t2, this);
-               cpn->item.lineno = *lineno;
-               cf_item_add(this, cf_pairtoitem(cpn));
        }
 
        /*
@@ -1217,7 +1663,7 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
         */
        if (feof(fp) && (this != current)) {
                radlog(L_ERR, "%s[%d]: EOF reached without closing brace for section %s starting at line %d",
-                      file, *lineno,
+                      filename, *lineno,
                       cf_section_name1(this), cf_section_lineno(this));
                return -1;
        }
@@ -1228,20 +1674,21 @@ static int cf_section_read(const char *file, int *lineno, FILE *fp,
 /*
  *     Include one config file in another.
  */
-int cf_file_include(const char *file, CONF_SECTION *cs)
+int cf_file_include(const char *filename, CONF_SECTION *cs)
 {
        FILE            *fp;
        int             lineno = 0;
        struct stat     statbuf;
        time_t          *mtime;
+       CONF_DATA       *cd;
 
-       DEBUG2( "Config:   including file: %s", file);
+       DEBUG2( "including configuration file %s", filename);
 
-       if (stat(file, &statbuf) == 0) {
+       if (stat(filename, &statbuf) == 0) {
 #ifdef S_IWOTH
                if ((statbuf.st_mode & S_IWOTH) != 0) {
                        radlog(L_ERR|L_CONS, "Configuration file %s is globally writable.  Refusing to start due to insecure configuration.",
-                              file);
+                              filename);
                        return -1;
                }
 #endif
@@ -1249,25 +1696,22 @@ int cf_file_include(const char *file, CONF_SECTION *cs)
 #ifdef S_IROTH
                if (0 && (statbuf.st_mode & S_IROTH) != 0) {
                        radlog(L_ERR|L_CONS, "Configuration file %s is globally readable.  Refusing to start due to insecure configuration.",
-                              file);
+                              filename);
                        return -1;
                }
 #endif
        }
 
-       fp = fopen(file, "r");
+       fp = fopen(filename, "r");
        if (!fp) {
                radlog(L_ERR|L_CONS, "Unable to open file \"%s\": %s",
-                      file, strerror(errno));
+                      filename, strerror(errno));
                return -1;
        }
 
-       /*
-        *      Read the section.  It's OK to have EOF without a
-        *      matching close brace.
-        */
-       if (cf_section_read(file, &lineno, fp, cs) < 0) {
-               fclose(fp);
+       if (cf_data_find_internal(cs, filename, PW_TYPE_FILENAME)) {
+               radlog(L_ERR, "Cannot include the same file twice: \"%s\"",
+                      filename);
                return -1;
        }
 
@@ -1276,9 +1720,33 @@ int cf_file_include(const char *file, CONF_SECTION *cs)
         */
        mtime = rad_malloc(sizeof(*mtime));
        *mtime = statbuf.st_mtime;
-       /* FIXME: error? */
-       cf_data_add_internal(cs, file, mtime, free,
-                            PW_TYPE_FILENAME);
+
+       if (cf_data_add_internal(cs, filename, mtime, free,
+                                PW_TYPE_FILENAME) < 0) {
+               fclose(fp);
+               radlog(L_ERR|L_CONS, "Internal error opening file \"%s\"",
+                      filename);
+               return -1;
+       }
+
+       cd = cf_data_find_internal(cs, filename, PW_TYPE_FILENAME);
+       if (!cd) {
+               fclose(fp);
+               radlog(L_ERR|L_CONS, "Internal error opening file \"%s\"",
+                      filename);
+               return -1;
+       }
+
+       if (!cs->item.filename) cs->item.filename = filename;
+
+       /*
+        *      Read the section.  It's OK to have EOF without a
+        *      matching close brace.
+        */
+       if (cf_section_read(cd->name, &lineno, fp, cs) < 0) {
+               fclose(fp);
+               return -1;
+       }
 
        fclose(fp);
        return 0;
@@ -1287,14 +1755,26 @@ int cf_file_include(const char *file, CONF_SECTION *cs)
 /*
  *     Bootstrap a config file.
  */
-CONF_SECTION *cf_file_read(const char *file)
+CONF_SECTION *cf_file_read(const char *filename)
 {
+       char *p;
+       CONF_PAIR *cp;
        CONF_SECTION *cs;
 
        cs = cf_section_alloc("main", NULL, NULL);
        if (!cs) return NULL;
 
-       if (cf_file_include(file, cs) < 0) {
+       cp = cf_pair_alloc("confdir", filename, T_OP_SET, T_BARE_WORD, cs);
+       if (!cp) return NULL;
+
+       p = strrchr(cp->value, FR_DIR_SEP);
+       if (p) *p = '\0';
+
+       cp->item.filename = "internal";
+       cp->item.lineno = 0;
+       cf_item_add(cs, cf_pairtoitem(cp));
+
+       if (cf_file_include(filename, cs) < 0) {
                cf_section_free(&cs);
                return NULL;
        }
@@ -1310,7 +1790,7 @@ CONF_PAIR *cf_pair_find(const CONF_SECTION *cs, const char *name)
        CONF_ITEM       *ci;
        CONF_PAIR       *cp = NULL;
 
-       if (!cs) cs = mainconfig.config;
+       if (!cs) return NULL;
 
        /*
         *      Find the name in the tree, for speed.
@@ -1340,7 +1820,7 @@ CONF_PAIR *cf_pair_find(const CONF_SECTION *cs, const char *name)
  * Return the attr of a CONF_PAIR
  */
 
-char *cf_pair_attr(CONF_PAIR *pair)
+const char *cf_pair_attr(CONF_PAIR *pair)
 {
        return (pair ? pair->attr : NULL);
 }
@@ -1349,12 +1829,67 @@ char *cf_pair_attr(CONF_PAIR *pair)
  * Return the value of a CONF_PAIR
  */
 
-char *cf_pair_value(CONF_PAIR *pair)
+const char *cf_pair_value(CONF_PAIR *pair)
 {
        return (pair ? pair->value : NULL);
 }
 
 /*
+ *     Copied here for error reporting.
+ */
+extern void fr_strerror_printf(const char *, ...);
+
+/*
+ * Turn a CONF_PAIR into a VALUE_PAIR
+ * For now, ignore the "value_type" field...
+ */
+VALUE_PAIR *cf_pairtovp(CONF_PAIR *pair)
+{
+       VALUE_PAIR *vp;
+
+       if (!pair) {
+               fr_strerror_printf("Internal error");
+               return NULL;
+       }
+
+       if (!pair->value) {
+               fr_strerror_printf("No value given for attribute %s", pair->attr);
+               return NULL;
+       }
+
+       /*
+        *      pairmake handles tags.  pairalloc() doesn't.
+        */
+       vp = pairmake(pair->attr, NULL, pair->operator);
+       if (!vp) {
+               return NULL;
+       }
+
+       if (pair->value_type == T_BARE_WORD) {
+               if ((vp->type == PW_TYPE_STRING) && 
+                   (pair->value[0] == '0') && (pair->value[1] == 'x')) {
+                       vp->type = PW_TYPE_OCTETS;
+               }
+               if (!pairparsevalue(vp, pair->value)) {
+                       pairfree(&vp);
+                       return NULL;
+               }
+               vp->flags.do_xlat = 0;
+         
+       } else if (pair->value_type == T_SINGLE_QUOTED_STRING) {
+               if (!pairparsevalue(vp, pair->value)) {
+                       pairfree(&vp);
+                       return NULL;
+               }
+               vp->flags.do_xlat = 0;
+       } else {
+               vp->flags.do_xlat = 1;
+       }
+
+       return vp;
+}
+
+/*
  * Return the first label of a CONF_SECTION
  */
 
@@ -1375,7 +1910,7 @@ const char *cf_section_name2(const CONF_SECTION *cs)
 /*
  * Find a value in a CONF_SECTION
  */
-char *cf_section_value_find(const CONF_SECTION *cs, const char *attr)
+const char *cf_section_value_find(const CONF_SECTION *cs, const char *attr)
 {
        CONF_PAIR       *cp;
 
@@ -1391,7 +1926,7 @@ char *cf_section_value_find(const CONF_SECTION *cs, const char *attr)
  */
 
 CONF_PAIR *cf_pair_find_next(const CONF_SECTION *cs,
-                            const CONF_PAIR *pair, const char *attr)
+                            CONF_PAIR *pair, const char *attr)
 {
        CONF_ITEM       *ci;
 
@@ -1436,10 +1971,12 @@ CONF_SECTION *cf_section_sub_find(const CONF_SECTION *cs, const char *name)
 {
        CONF_ITEM *ci;
 
+       if (!name) return NULL; /* can't find an un-named section */
+
        /*
         *      Do the fast lookup if possible.
         */
-       if (name && cs->section_tree) {
+       if (cs->section_tree) {
                CONF_SECTION mycs;
 
                mycs.name1 = name;
@@ -1467,16 +2004,14 @@ CONF_SECTION *cf_section_sub_find_name2(const CONF_SECTION *cs,
 {
        CONF_ITEM    *ci;
 
-       if (!name2) return cf_section_sub_find(cs, name1);
-
        if (!cs) cs = mainconfig.config;
 
        if (name1 && (cs->section_tree)) {
                CONF_SECTION mycs, *master_cs;
-               
+
                mycs.name1 = name1;
                mycs.name2 = name2;
-               
+
                master_cs = rbtree_finddata(cs->section_tree, &mycs);
                if (master_cs) {
                        return rbtree_finddata(master_cs->name2_tree, &mycs);
@@ -1545,6 +2080,22 @@ CONF_SECTION *cf_subsection_find_next(CONF_SECTION *section,
        return cf_itemtosection(ci);
 }
 
+
+/*
+ * Return the next section after a CONF_SECTION
+ * with a certain name1 (char *name1). If the requested
+ * name1 is NULL, any name1 matches.
+ */
+
+CONF_SECTION *cf_section_find_next(CONF_SECTION *section,
+                                  CONF_SECTION *subsection,
+                                  const char *name1)
+{
+       if (!section->item.parent) return NULL;
+
+       return cf_subsection_find_next(section->item.parent, subsection, name1);
+}
+
 /*
  * Return the next item after a CONF_ITEM.
  */
@@ -1568,6 +2119,16 @@ int cf_section_lineno(CONF_SECTION *section)
        return cf_sectiontoitem(section)->lineno;
 }
 
+const char *cf_pair_filename(CONF_PAIR *pair)
+{
+       return cf_pairtoitem(pair)->filename;
+}
+
+const char *cf_section_filename(CONF_SECTION *section)
+{
+       return cf_sectiontoitem(section)->filename;
+}
+
 int cf_pair_lineno(CONF_PAIR *pair)
 {
        return cf_pairtoitem(pair)->lineno;
@@ -1586,17 +2147,24 @@ int cf_item_is_pair(CONF_ITEM *item)
 static CONF_DATA *cf_data_alloc(CONF_SECTION *parent, const char *name,
                                void *data, void (*data_free)(void *))
 {
+       char *p;
+       size_t name_len;
        CONF_DATA *cd;
 
-       cd = rad_malloc(sizeof(*cd));
+       name_len = strlen(name) + 1;
+
+       p = rad_malloc(sizeof(*cd) + name_len);
+       cd = (CONF_DATA *) p;
        memset(cd, 0, sizeof(*cd));
 
        cd->item.type = CONF_ITEM_DATA;
        cd->item.parent = parent;
-       cd->name = strdup(name);
        cd->data = data;
        cd->free = data_free;
 
+       p += sizeof(*cd);
+       memcpy(p, name, name_len);
+       cd->name = p;
        return cd;
 }
 
@@ -1605,17 +2173,16 @@ static void *cf_data_find_internal(CONF_SECTION *cs, const char *name,
                                   int flag)
 {
        if (!cs || !name) return NULL;
-       
+
        /*
         *      Find the name in the tree, for speed.
         */
        if (cs->data_tree) {
-               CONF_DATA mycd, *cd;
+               CONF_DATA mycd;
 
                mycd.name = name;
                mycd.flag = flag;
-               cd = rbtree_finddata(cs->data_tree, &mycd);
-               if (cd) return cd->data;
+               return rbtree_finddata(cs->data_tree, &mycd);
        }
 
        return NULL;
@@ -1626,7 +2193,10 @@ static void *cf_data_find_internal(CONF_SECTION *cs, const char *name,
  */
 void *cf_data_find(CONF_SECTION *cs, const char *name)
 {
-       return cf_data_find_internal(cs, name, 0);
+       CONF_DATA *cd = cf_data_find_internal(cs, name, 0);
+
+       if (cd) return cd->data;
+       return NULL;
 }
 
 
@@ -1664,13 +2234,13 @@ int cf_data_add(CONF_SECTION *cs, const char *name,
        return cf_data_add_internal(cs, name, data, data_free, 0);
 }
 
-
+#if 0
 /*
  *     Copy CONF_DATA from src to dst
  */
 static void cf_section_copy_data(CONF_SECTION *s, CONF_SECTION *d)
 {
-       
+
        CONF_ITEM *cd, *next, **last;
 
        /*
@@ -1681,7 +2251,7 @@ static void cf_section_copy_data(CONF_SECTION *s, CONF_SECTION *d)
        rad_assert(d->data_tree == NULL);
        d->data_tree = s->data_tree;
        s->data_tree = NULL;
-       
+
        /*
         *      Walk through src, moving CONF_ITEM_DATA
         *      to dst, by hand.
@@ -1689,13 +2259,13 @@ static void cf_section_copy_data(CONF_SECTION *s, CONF_SECTION *d)
        last = &(s->children);
        for (cd = s->children; cd != NULL; cd = next) {
                next = cd->next;
-               
+
                /*
                 *      Recursively copy data from child sections.
                 */
                if (cd->type == CONF_ITEM_SECTION) {
                        CONF_SECTION *s1, *d1;
-                       
+
                        s1 = cf_itemtosection(cd);
                        d1 = cf_section_sub_find_name2(d, s1->name1, s1->name2);
                        if (d1) {
@@ -1712,13 +2282,13 @@ static void cf_section_copy_data(CONF_SECTION *s, CONF_SECTION *d)
                        last = &(cd->next);
                        continue;
                }
-               
+
                /*
                 *      Remove it from the src list
                 */
                *last = cd->next;
                cd->next = NULL;
-               
+
                /*
                 *      Add it to the dst list
                 */
@@ -1810,7 +2380,7 @@ static int cf_section_cmp(CONF_SECTION *a, CONF_SECTION *b)
                 */
                if ((strcmp(pa->attr, pb->attr) != 0) ||
                    (strcmp(pa->value, pb->value) != 0)) return 0;
-               
+
 
                /*
                 *      And go to the next element.
@@ -1864,6 +2434,7 @@ int cf_section_migrate(CONF_SECTION *dst, CONF_SECTION *src)
 
        return 1;               /* rcode means anything? */
 }
+#endif
 
 int cf_section_template(CONF_SECTION *cs, CONF_SECTION *template)
 {
@@ -1874,6 +2445,59 @@ int cf_section_template(CONF_SECTION *cs, CONF_SECTION *template)
        return 0;
 }
 
+
+/*
+ *     This is here to make the rest of the code easier to read.  It
+ *     ties conffile.c to log.c, but it means we don't have to
+ *     pollute every other function with the knowledge of the
+ *     configuration internals.
+ */
+void cf_log_err(CONF_ITEM *ci, const char *fmt, ...)
+{
+       va_list ap;
+       char buffer[256];
+
+       va_start(ap, fmt);
+       vsnprintf(buffer, sizeof(buffer), fmt, ap);
+       va_end(ap);
+
+       radlog(L_ERR, "%s[%d]: %s", ci->filename, ci->lineno, buffer);
+}
+
+
+void cf_log_info(CONF_SECTION *cs, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       if (debug_flag > 1 && cf_log_config && cs) vradlog(L_DBG, fmt, ap);
+       va_end(ap);
+}
+
+/*
+ *     Wrapper to simplify the code.
+ */
+void cf_log_module(CONF_SECTION *cs, const char *fmt, ...)
+{
+       va_list ap;
+       char buffer[256];
+
+       va_start(ap, fmt);
+       if (debug_flag > 1 && cf_log_modules && cs) {
+               vsnprintf(buffer, sizeof(buffer), fmt, ap);
+
+               radlog(L_DBG, " Module: %s", buffer);
+       }
+       va_end(ap);
+}
+
+const CONF_PARSER *cf_section_parse_table(CONF_SECTION *cs)
+{
+       if (!cs) return NULL;
+
+       return cs->variables;
+}
+
 #if 0
 /*
  * JMG dump_config tries to dump the config structure in a readable format
@@ -1919,8 +2543,164 @@ static int dump_config_section(CONF_SECTION *cs, int indent)
        return 0;
 }
 
-int dump_config(void)
+int dump_config(CONF_SECTION *cs)
 {
-       return dump_config_section(mainconfig.config, 0);
+       return dump_config_section(cs, 0);
 }
 #endif
+
+static const char *cf_pair_print_value(const CONF_PAIR *cp,
+                                      char *buffer, size_t buflen)
+{
+       char *p;
+
+       if (!cp->value) return "";
+
+       switch (cp->value_type) {
+       default:
+       case T_BARE_WORD:
+               snprintf(buffer, buflen, "%s", cp->value);
+               break;
+
+       case T_SINGLE_QUOTED_STRING:
+               snprintf(buffer, buflen, "'%s'", cp->value);
+               break;
+
+       case T_DOUBLE_QUOTED_STRING:
+               buffer[0] = '"';
+               fr_print_string(cp->value, strlen(cp->value),
+                               buffer + 1, buflen - 3);
+               p = buffer + strlen(buffer); /* yuck... */
+               p[0] = '"';
+               p[1] = '\0';
+               break;
+       }
+
+       return buffer;
+}
+
+
+int cf_pair2xml(FILE *fp, const CONF_PAIR *cp)
+{
+       fprintf(fp, "<%s>", cp->attr);
+       if (cp->value) {
+               char buffer[2048];
+
+               char *p = buffer;
+               const char *q = cp->value;
+
+               while (*q && (p < (buffer + sizeof(buffer) - 1))) {
+                       if (q[0] == '&') {
+                               memcpy(p, "&amp;", 4);
+                               p += 5;
+
+                       } else if (q[0] == '<') {
+                               memcpy(p, "&lt;", 4);
+                               p += 4;
+
+                       } else if (q[0] == '>') {
+                               memcpy(p, "&gt;", 4);
+                               p += 4;
+
+                       } else {
+                               *(p++) = *q;
+                       }
+                       q++;
+               }
+
+               *p = '\0';
+               fprintf(fp, "%s", buffer);
+       }
+
+       fprintf(fp, "</%s>\n", cp->attr);
+
+       return 1;
+}
+
+int cf_section2xml(FILE *fp, const CONF_SECTION *cs)
+{
+       CONF_ITEM *ci, *next;
+
+       /*
+        *      Section header
+        */
+       fprintf(fp, "<%s>\n", cs->name1);
+       if (cs->name2) {
+               fprintf(fp, "<_name2>%s</_name2>\n", cs->name2);
+       }
+
+       /*
+        *      Loop over contents.
+        */
+       for (ci = cs->children; ci; ci = next) {
+               next = ci->next;
+
+               switch (ci->type) {
+               case CONF_ITEM_PAIR:
+                       if (!cf_pair2xml(fp, (CONF_PAIR *) ci)) return 0;
+                       break;
+
+               case CONF_ITEM_SECTION:
+                       if (!cf_section2xml(fp, (CONF_SECTION *) ci)) return 0;
+                       break;
+
+               default:        /* should really be an error. */
+                       break;
+               
+               }
+       }
+
+       fprintf(fp, "</%s>\n", cs->name1);
+
+       return 1;               /* success */
+}
+
+int cf_pair2file(FILE *fp, const CONF_PAIR *cp)
+{
+       char buffer[2048];
+
+       fprintf(fp, "\t%s = %s\n", cp->attr,
+               cf_pair_print_value(cp, buffer, sizeof(buffer)));
+
+       return 1;
+}
+
+int cf_section2file(FILE *fp, const CONF_SECTION *cs)
+{
+       const CONF_ITEM *ci, *next;
+
+       /*
+        *      Section header
+        */
+       if (!cs->name2) {
+               fprintf(fp, "%s {\n", cs->name1);
+       } else {
+               fprintf(fp, "%s %s {\n",
+                       cs->name1, cs->name2);
+       }
+
+       /*
+        *      Loop over contents.
+        */
+       for (ci = cs->children; ci; ci = next) {
+               next = ci->next;
+
+               switch (ci->type) {
+               case CONF_ITEM_PAIR:
+                       if (!cf_pair2file(fp, (const CONF_PAIR *) ci)) return 0;
+                       break;
+
+               case CONF_ITEM_SECTION:
+                       if (!cf_section2file(fp, (const CONF_SECTION *) ci)) return 0;
+                       break;
+
+               default:        /* should really be an error. */
+                       break;
+               
+               }
+       }
+
+       fprintf(fp, "}\n");
+
+       return 1;               /* success */
+}