FR-AD-001 - (v2) use strncmp() instead of memcmp() for bounded data
[freeradius.git] / src / main / conffile.c
index 406285b..af42baa 100644 (file)
@@ -194,8 +194,9 @@ void cf_pair_free(CONF_PAIR **cp)
         *      attr && value are allocated contiguous with cp.
         */
 
+       free((*cp)->item.filename);
 #ifndef NDEBUG
-       memset(*cp, 0, sizeof(*cp));
+       memset(*cp, 0, sizeof(cp));
 #endif
        free(*cp);
 
@@ -214,7 +215,7 @@ static void cf_data_free(CONF_DATA **cd)
                ((*cd)->free)((*cd)->data);
        }
 #ifndef NDEBUG
-       memset(*cd, 0, sizeof(*cd));
+       memset(*cd, 0, sizeof(cd));
 #endif
        free(*cd);
        *cd = NULL;
@@ -255,8 +256,8 @@ static int name2_cmp(const void *a, const void *b)
        rad_assert(strcmp(one->name1, two->name1) == 0);
 
        if (!one->name2 && !two->name2) return 0;
-       if (!one->name2) return -1;
-       if (!two->name2) return +1;
+       if (one->name2 && !two->name2) return -1;
+       if (!one->name2 && two->name2) return +1;
 
        return strcmp(one->name2, two->name2);
 }
@@ -300,10 +301,25 @@ void cf_section_parse_free(CONF_SECTION *cs, void *base)
         *      Free up dynamically allocated string pointers.
         */
        for (i = 0; variables[i].name != NULL; i++) {
+               int type;
                char **p;
 
-               if ((variables[i].type != PW_TYPE_STRING_PTR) &&
-                   (variables[i].type != PW_TYPE_FILENAME)) {
+               type = variables[i].type;
+
+               if (type == PW_TYPE_SUBSECTION) {
+                       CONF_SECTION *subcs;
+                       subcs = cf_section_sub_find(cs, variables[i].name);
+
+                       if (!subcs) continue;
+
+                       if (!variables[i].dflt) continue;
+
+                       cf_section_parse_free(subcs, base);
+                       continue;
+               }
+
+               if ((type != PW_TYPE_STRING_PTR) &&
+                   (type != PW_TYPE_FILENAME)) {
                        continue;
                }
 
@@ -329,6 +345,8 @@ void cf_section_parse_free(CONF_SECTION *cs, void *base)
                free(*p);
                *p = NULL;
        }
+
+       cs->variables = NULL;
 }
 
 
@@ -386,8 +404,9 @@ void cf_section_free(CONF_SECTION **cs)
        /*
         * And free the section
         */
+       free((*cs)->item.filename);
 #ifndef NDEBUG
-       memset(*cs, 0, sizeof(*cs));
+       memset(*cs, 0, sizeof(cs));
 #endif
        free(*cs);
 
@@ -489,6 +508,8 @@ int cf_pair_replace(CONF_SECTION *cs, CONF_PAIR *cp, const char *value)
  */
 static void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci)
 {
+       if (!cs || !ci) return;
+
        if (!cs->children) {
                rad_assert(cs->tail == NULL);
                cs->children = ci;
@@ -579,6 +600,9 @@ CONF_ITEM *cf_reference_item(const CONF_SECTION *parentcs,
        char name[8192];
        char *p;
 
+       if (cs == NULL)
+               goto no_such_item;
+
        strlcpy(name, ptr, sizeof(name));
        p = name;
 
@@ -687,6 +711,8 @@ no_such_item:
 
 CONF_SECTION *cf_top_section(CONF_SECTION *cs)
 {
+       if (!cs) return NULL;
+
        while (cs->item.parent != NULL) {
                cs = cs->item.parent;
        }
@@ -700,7 +726,8 @@ CONF_SECTION *cf_top_section(CONF_SECTION *cs)
  */
 static const char *cf_expand_variables(const char *cf, int *lineno,
                                       CONF_SECTION *outercs,
-                                      char *output, const char *input)
+                                      char *output, size_t outsize,
+                                      const char *input)
 {
        char *p;
        const char *end, *ptr;
@@ -773,11 +800,18 @@ static const char *cf_expand_variables(const char *cf, int *lineno,
                                       cf, *lineno, input);
                                return NULL;
                        }
+
+                       if (p + strlen(cp->value) >= output + outsize) {
+                               radlog(L_ERR, "%s[%d]: Reference \"%s\" is too long",
+                                      cf, *lineno, input);
+                               return NULL;
+                       }
+
                        strcpy(p, cp->value);
                        p += strlen(p);
                        ptr = end + 1;
 
-               } else if (memcmp(ptr, "$ENV{", 5) == 0) {
+               } else if (strncmp(ptr, "$ENV{", 5) == 0) {
                        char *env;
 
                        ptr += 5;
@@ -818,6 +852,12 @@ static const char *cf_expand_variables(const char *cf, int *lineno,
                                env = name;
                        }
 
+                       if (p + strlen(env) >= output + outsize) {
+                               radlog(L_ERR, "%s[%d]: Reference \"%s\" is too long",
+                                      cf, *lineno, input);
+                               return NULL;
+                       }
+
                        strcpy(p, env);
                        p += strlen(p);
                        ptr = end + 1;
@@ -828,6 +868,12 @@ static const char *cf_expand_variables(const char *cf, int *lineno,
                         */
                        *(p++) = *(ptr++);
                }
+
+               if (p >= (output + outsize)) {
+                       radlog(L_ERR, "%s[%d]: Reference \"%s\" is too long",
+                              cf, *lineno, input);
+                       return NULL;
+               }
        } /* loop over all of the input string. */
 
        *p = '\0';
@@ -835,6 +881,8 @@ static const char *cf_expand_variables(const char *cf, int *lineno,
        return output;
 }
 
+static const char *parse_spaces = "                                                                                                                                                                                                                                                                ";
+
 
 /*
  *     Parses an item (not a CONF_ITEM) into the specified format,
@@ -852,9 +900,16 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
        const char *value;
        fr_ipaddr_t ipaddr;
        const CONF_PAIR *cp = NULL;
+       int depth;
        char ipbuf[128];
 
-       if (cs) cp = cf_pair_find(cs, name);
+       if (cs) {
+               depth = cs->depth;
+               cp = cf_pair_find(cs, name);
+       } else {
+               depth = 0;
+       }
+
        if (cp) {
                value = cp->value;
 
@@ -886,12 +941,14 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                        radlog(L_ERR, "Bad value \"%s\" for boolean variable %s", value, name);
                        return -1;
                }
-               cf_log_info(cs, "\t%s = %s", name, value);
+               cf_log_info(cs, "%.*s\t%s = %s",
+                           depth, parse_spaces, name, value);
                break;
 
        case PW_TYPE_INTEGER:
                *(int *)data = strtol(value, 0, 0);
-               cf_log_info(cs, "\t%s = %d", name, *(int *)data);
+               cf_log_info(cs, "%.*s\t%s = %d",
+                           depth, parse_spaces, name, *(int *)data);
                break;
 
        case PW_TYPE_STRING_PTR:
@@ -915,13 +972,18 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                        /*
                         *      FIXME: sizeof(buffer)?
                         */
-                       value = cf_expand_variables("?",
+                       value = cf_expand_variables("<internal>",
                                                    &lineno,
-                                                   cs, buffer, value);
-                       if (!value) return -1;
+                                                   cs, buffer, sizeof(buffer),
+                                                   value);
+                       if (!value) {
+                               cf_log_err(cf_sectiontoitem(cs),"Failed expanding variable %s", name);
+                               return -1;
+                       }
                }
 
-               cf_log_info(cs, "\t%s = \"%s\"", name, value ? value : "(null)");
+               cf_log_info(cs, "%.*s\t%s = \"%s\"",
+                           depth, parse_spaces, name, value ? value : "(null)");
                *q = value ? strdup(value) : NULL;
                break;
 
@@ -941,23 +1003,23 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                 *      expanded automagically when the configuration
                 *      file was read.
                 */
-               if (value == dflt) {
+               if ((value == dflt) && cs) {
                        char buffer[8192];
 
                        int lineno = 0;
 
-                       if (cs) lineno = cs->item.lineno;
-
                        /*
                         *      FIXME: sizeof(buffer)?
                         */
                        value = cf_expand_variables("?",
                                                    &lineno,
-                                                   cs, buffer, value);
+                                                   cs, buffer, sizeof(buffer),
+                                                   value);
                        if (!value) return -1;
                }
 
-               cf_log_info(cs, "\t%s = \"%s\"", name, value ? value : "(null)");
+               cf_log_info(cs, "%.*s\t%s = \"%s\"",
+                           depth, parse_spaces, name, value ? value : "(null)");
                *q = value ? strdup(value) : NULL;
 
                /*
@@ -988,7 +1050,8 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                 */
                if (strcmp(value, "*") == 0) {
                        *(uint32_t *) data = htonl(INADDR_ANY);
-                       cf_log_info(cs, "\t%s = *", name);
+                       cf_log_info(cs, "%.*s\t%s = *",
+                                   depth, parse_spaces, name);
                        break;
                }
                if (ip_hton(value, AF_INET, &ipaddr) < 0) {
@@ -997,9 +1060,11 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                }
                
                if (strspn(value, "0123456789.") == strlen(value)) {
-                       cf_log_info(cs, "\t%s = %s", name, value);
+                       cf_log_info(cs, "%.*s\t%s = %s",
+                                   depth, parse_spaces, name, value);
                } else {
-                       cf_log_info(cs, "\t%s = %s IP address [%s]", name, value,
+                       cf_log_info(cs, "%.*s\t%s = %s IP address [%s]",
+                                   depth, parse_spaces, name, value,
                               ip_ntoh(&ipaddr, ipbuf, sizeof(ipbuf)));
                }
                *(uint32_t *) data = ipaddr.ipaddr.ip4addr.s_addr;
@@ -1010,8 +1075,9 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
                        radlog(L_ERR, "Can't find IPv6 address for host %s", value);
                        return -1;
                }
-               cf_log_info(cs, "\t%s = %s IPv6 address [%s]", name, value,
-                              ip_ntoh(&ipaddr, ipbuf, sizeof(ipbuf)));
+               cf_log_info(cs, "%.*s\t%s = %s IPv6 address [%s]",
+                           depth, parse_spaces, name, value,
+                           ip_ntoh(&ipaddr, ipbuf, sizeof(ipbuf)));
                memcpy(data, &ipaddr.ipaddr.ip6addr,
                       sizeof(ipaddr.ipaddr.ip6addr));
                break;
@@ -1019,13 +1085,61 @@ int cf_item_parse(CONF_SECTION *cs, const char *name,
        default:
                radlog(L_ERR, "type %d not supported yet", type);
                return -1;
-               break;
        } /* switch over variable type */
 
+       if (!cp) {
+               CONF_PAIR *cpn;
+
+               cpn = cf_pair_alloc(name, value, T_OP_SET, T_BARE_WORD, cs);
+               if (!cpn) return -1;
+               cpn->item.filename = strdup("<internal>");
+               cpn->item.lineno = 0;
+               cf_item_add(cs, cf_pairtoitem(cpn));
+       }
+
        return rcode;
 }
 
-static const char *parse_spaces = "                                                                                                                                                                                                                                                                ";
+
+/*
+ *     A copy of cf_section_parse that initializes pointers before
+ *     parsing them.
+ */
+static void cf_section_parse_init(CONF_SECTION *cs, void *base,
+                                 const CONF_PARSER *variables)
+{
+       int i;
+       void *data;
+
+       for (i = 0; variables[i].name != NULL; i++) {
+               if (variables[i].type == PW_TYPE_SUBSECTION) {
+                       CONF_SECTION *subcs;
+                       subcs = cf_section_sub_find(cs, variables[i].name);
+                       if (!subcs) continue;
+
+                       if (!variables[i].dflt) continue;
+
+                       cf_section_parse_init(subcs, base,
+                                             (const CONF_PARSER *) variables[i].dflt);
+                       continue;
+               }
+
+               if ((variables[i].type != PW_TYPE_STRING_PTR) &&
+                   (variables[i].type != PW_TYPE_FILENAME)) {
+                       continue;
+               }
+
+               if (variables[i].data) {
+                       data = variables[i].data; /* prefer this. */
+               } else if (base) {
+                       data = ((char *)base) + variables[i].offset;
+               } else {
+                       continue;
+               }
+
+               *(char **) data = NULL;
+       } /* for all variables in the configuration section */
+}
 
 /*
  *     Parse a configuration section into user-supplied variables.
@@ -1046,6 +1160,8 @@ int cf_section_parse(CONF_SECTION *cs, void *base,
                       cs->name1, cs->name2);
        }
 
+       cf_section_parse_init(cs, base, variables);
+
        /*
         *      Handle the known configuration parameters.
         */
@@ -1168,6 +1284,7 @@ static int condition_looks_ok(const char **ptr)
                                 *      Parse error.
                                 */
                                if (*q != '{') {
+                                       DEBUG2("Expected open brace '{' after condition at %s", p);
                                        return 0;
                                }
 
@@ -1191,9 +1308,45 @@ static int condition_looks_ok(const char **ptr)
                }
        }
 
+       DEBUG3("Unexpected error");
        return 0;
 }
 
+int cf_exclude_file(const char *filename)
+{
+       int i;
+       size_t len;
+       const char *p = filename;
+
+       /*
+        *      FIXME: Maybe later make this a globally set configuration
+        *      variable.  But that's low priority.
+        */
+       static const char *excluded[] = {
+               "rpmsave", "rpmnew", "dpkg-new", "dpkg-dist", "dpkg-old",
+               "bak", NULL
+       };
+
+       if (!p || !*p) return TRUE; /* coding error */
+
+       if (*p == '.') return TRUE; /* ".", "..", ".foo", ... */
+
+       if (*p == '#') return TRUE; /* #foo# */
+
+       len = strlen(p);
+       if (p[len - 1] == '~') return TRUE; /* foo~ */
+
+       p = strrchr(p, '.');
+       if (!p) return FALSE;   /* just "foo", it's OK */
+
+       p++;
+       for (i = 0; excluded[i] != NULL; i++) {
+               if (strcmp(p, excluded[i]) == 0) return TRUE;
+       }
+
+       return FALSE;
+}
+
 
 static const char *cf_local_file(CONF_SECTION *cs, const char *local,
                                 char *buffer, size_t bufsize)
@@ -1217,6 +1370,28 @@ static const char *cf_local_file(CONF_SECTION *cs, const char *local,
        return buffer;
 }
 
+static int seen_too_much(const char *filename, int lineno, const char *ptr)
+{
+       while (*ptr) {
+               if (isspace(*ptr)) {
+                       ptr++;
+                       continue;
+               }
+
+               if (*ptr == '#') return FALSE;
+
+               break;
+       }
+
+       if (*ptr) {
+               radlog(L_ERR, "%s[%d] Unexpected text %s.  See \"man unlang\"",
+                      filename, lineno, ptr);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
 
 /*
  *     Read a part of the config file.
@@ -1352,6 +1527,7 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
 
                       }
                       this = this->item.parent;
+                      if (seen_too_much(filename, *lineno, ptr)) return -1;
                       continue;
                }
 
@@ -1369,7 +1545,7 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
 
                        if (buf2[0] == '$') relative = 0;
 
-                       value = cf_expand_variables(filename, lineno, this, buf, buf2);
+                       value = cf_expand_variables(filename, lineno, this, buf, sizeof(buf), buf2);
                        if (!value) return -1;
 
                        if (!FR_DIR_IS_RELATIVE(value)) relative = 0;
@@ -1398,6 +1574,23 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                                struct stat stat_buf;
 
                                DEBUG2("including files in directory %s", value );
+#ifdef S_IWOTH
+                               /*
+                                *      Security checks.
+                                */
+                               if (stat(value, &stat_buf) < 0) {
+                                       radlog(L_ERR, "%s[%d]: Failed reading directory %s: %s",
+                                              filename, *lineno, 
+                                              value, strerror(errno));
+                                       return -1;
+                               }
+
+                               if ((stat_buf.st_mode & S_IWOTH) != 0) {
+                                       radlog(L_ERR|L_CONS, "%s[%d]: Directory %s is globally writable.  Refusing to start due to insecure configuration.",
+                                              filename, *lineno, value);
+                                       return -1;
+                               }
+#endif
                                dir = opendir(value);
                                if (!dir) {
                                        radlog(L_ERR, "%s[%d]: Error reading directory %s: %s",
@@ -1407,25 +1600,11 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                                }
 
                                /*
-                                *      Read the directory, ignoring "." files.
+                                *      Read the directory, ignoring some files.
                                 */
                                while ((dp = readdir(dir)) != NULL) {
-                                       const char *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;
+                                       if (cf_exclude_file(dp->d_name))
+                                               continue;
 
                                        snprintf(buf2, sizeof(buf2), "%s%s",
                                                 value, dp->d_name);
@@ -1462,12 +1641,19 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
 
               if (strcasecmp(buf1, "$template") == 0) {
                       CONF_ITEM *ci;
-                      CONF_SECTION *parentcs;
+                      CONF_SECTION *parentcs, *templatecs;
                       t2 = getword(&ptr, buf2, sizeof(buf2));
 
                       parentcs = cf_top_section(current);
 
-                      ci = cf_reference_item(parentcs, this, buf2);
+                      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);
@@ -1502,6 +1688,7 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                case T_EOL:
                case T_HASH:
                do_bare_word:
+                       t3 = t2;
                        t2 = T_OP_EQ;
                        value = NULL;
                        goto do_set;
@@ -1517,10 +1704,10 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                                       filename, *lineno);
                                return -1;
                        }
+                       /* FALL-THROUGH */
 
                case T_OP_EQ:
                case T_OP_SET:
-               do_set:
                        t3 = getstring(&ptr, buf3, sizeof(buf3));
                        if (t3 == T_OP_INVALID) {
                                radlog(L_ERR, "%s[%d]: Parse error: %s",
@@ -1530,12 +1717,23 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                        }
 
                        /*
+                        *      These are not allowed.  Print a
+                        *      helpful error message.
+                        */
+                       if ((t3 == T_BACK_QUOTED_STRING) &&
+                           (!this || (strcmp(this->name1, "update") != 0))) {
+                               radlog(L_ERR, "%s[%d]: Syntax error: Invalid string `...` in assignment",
+                                      filename, *lineno);
+                               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);
+                                                           buf, sizeof(buf), buf3);
                                if (!value) return -1;
                        } else if ((t3 == T_EOL) ||
                                   (t3 == T_HASH)) {
@@ -1547,8 +1745,9 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                        /*
                         *      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.filename = strdup(filename);
                        cpn->item.lineno = *lineno;
                        cf_item_add(this, cf_pairtoitem(cpn));
                        continue;
@@ -1596,8 +1795,14 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                                buf2[0] = '(';
                                memcpy(buf2 + 1, ptr, end - ptr);
                                buf2[end - ptr + 1] = '\0';
-                               ptr = end + 1;
+                               ptr = end;
                                t2 = T_BARE_WORD;
+
+                               if (gettoken(&ptr, buf3, sizeof(buf3)) != T_LCBRACE) {
+                                       radlog(L_ERR, "%s[%d]: Expected '{'",
+                                              filename, *lineno);
+                                       return -1;
+                               }
                                goto section_alloc;
 
                        } else {
@@ -1620,9 +1825,12 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                                       filename, *lineno, buf1, buf2);
                                return -1;
                        }
+                       /* FALL-THROUGH */
 
                case T_LCBRACE:
                section_alloc:
+                       if (seen_too_much(filename, *lineno, ptr)) return -1;
+
                        css = cf_section_alloc(buf1,
                                               t2 == T_LCBRACE ? NULL : buf2,
                                               this);
@@ -1632,7 +1840,7 @@ static int cf_section_read(const char *filename, int *lineno, FILE *fp,
                                return -1;
                        }
                        cf_item_add(this, cf_sectiontoitem(css));
-                       css->item.filename = filename;
+                       css->item.filename = strdup(filename);
                        css->item.lineno = *lineno;
 
                        /*
@@ -1674,9 +1882,17 @@ int cf_file_include(const char *filename, CONF_SECTION *cs)
 
        DEBUG2( "including configuration file %s", filename);
 
+       fp = fopen(filename, "r");
+       if (!fp) {
+               radlog(L_ERR|L_CONS, "Unable to open file \"%s\": %s",
+                      filename, strerror(errno));
+               return -1;
+       }
+
        if (stat(filename, &statbuf) == 0) {
 #ifdef S_IWOTH
                if ((statbuf.st_mode & S_IWOTH) != 0) {
+                       fclose(fp);
                        radlog(L_ERR|L_CONS, "Configuration file %s is globally writable.  Refusing to start due to insecure configuration.",
                               filename);
                        return -1;
@@ -1685,6 +1901,7 @@ int cf_file_include(const char *filename, CONF_SECTION *cs)
 
 #ifdef S_IROTH
                if (0 && (statbuf.st_mode & S_IROTH) != 0) {
+                       fclose(fp);
                        radlog(L_ERR|L_CONS, "Configuration file %s is globally readable.  Refusing to start due to insecure configuration.",
                               filename);
                        return -1;
@@ -1692,14 +1909,8 @@ int cf_file_include(const char *filename, CONF_SECTION *cs)
 #endif
        }
 
-       fp = fopen(filename, "r");
-       if (!fp) {
-               radlog(L_ERR|L_CONS, "Unable to open file \"%s\": %s",
-                      filename, strerror(errno));
-               return -1;
-       }
-
        if (cf_data_find_internal(cs, filename, PW_TYPE_FILENAME)) {
+               fclose(fp);
                radlog(L_ERR, "Cannot include the same file twice: \"%s\"",
                       filename);
                return -1;
@@ -1727,7 +1938,7 @@ int cf_file_include(const char *filename, CONF_SECTION *cs)
                return -1;
        }
 
-       if (!cs->item.filename) cs->item.filename = filename;
+       if (!cs->item.filename) cs->item.filename = strdup(filename);
 
        /*
         *      Read the section.  It's OK to have EOF without a
@@ -1760,7 +1971,7 @@ CONF_SECTION *cf_file_read(const char *filename)
        p = strrchr(cp->value, FR_DIR_SEP);
        if (p) *p = '\0';
 
-       cp->item.filename = "internal";
+       cp->item.filename = strdup("<internal>");
        cp->item.lineno = 0;
        cf_item_add(cs, cf_pairtoitem(cp));
 
@@ -1824,6 +2035,11 @@ const char *cf_pair_value(CONF_PAIR *pair)
        return (pair ? pair->value : NULL);
 }
 
+FR_TOKEN cf_pair_operator(CONF_PAIR *pair)
+{
+       return (pair ? pair->operator : T_OP_INVALID);
+}
+
 /*
  *     Copied here for error reporting.
  */
@@ -1855,6 +2071,11 @@ VALUE_PAIR *cf_pairtovp(CONF_PAIR *pair)
                return NULL;
        }
 
+       /*
+        *      Ignore the value if it's a false comparison.
+        */
+       if (pair->operator == T_OP_CMP_FALSE) return vp;
+
        if (pair->value_type == T_BARE_WORD) {
                if ((vp->type == PW_TYPE_STRING) && 
                    (pair->value[0] == '0') && (pair->value[1] == 'x')) {
@@ -1909,6 +2130,33 @@ const char *cf_section_value_find(const CONF_SECTION *cs, const char *attr)
        return (cp ? cp->value : NULL);
 }
 
+
+CONF_SECTION *cf_section_find_name2(const CONF_SECTION *section,
+                                   const char *name1, const char *name2)
+{
+       const char      *their2;
+       CONF_ITEM       *ci;
+
+       if (!section || !name1) return NULL;
+
+       for (ci = cf_sectiontoitem(section); ci; ci = ci->next) {
+               if (ci->type != CONF_ITEM_SECTION)
+                       continue;
+
+               if (strcmp(cf_itemtosection(ci)->name1, name1) != 0)
+                       continue;
+
+               their2 = cf_itemtosection(ci)->name2;
+
+               if ((!name2 && !their2) ||
+                   (name2 && their2 && (strcmp(name2, their2) == 0))) {
+                       return cf_itemtosection(ci);
+               }
+       }
+       
+       return NULL;
+}
+
 /*
  * Return the next pair after a CONF_PAIR
  * with a certain name (char *attr) If the requested
@@ -1920,6 +2168,8 @@ CONF_PAIR *cf_pair_find_next(const CONF_SECTION *cs,
 {
        CONF_ITEM       *ci;
 
+       if (!cs) return NULL;
+
        /*
         * If pair is NULL this must be a first time run
         * Find the pair with correct name
@@ -1995,17 +2245,43 @@ CONF_SECTION *cf_section_sub_find_name2(const CONF_SECTION *cs,
        CONF_ITEM    *ci;
 
        if (!cs) cs = mainconfig.config;
-
-       if (name1 && (cs->section_tree)) {
+       if (!cs) return NULL;
+       if (name1) {
                CONF_SECTION mycs, *master_cs;
 
+               if (!cs->section_tree) return NULL;
+
                mycs.name1 = name1;
                mycs.name2 = name2;
 
                master_cs = rbtree_finddata(cs->section_tree, &mycs);
-               if (master_cs) {
-                       return rbtree_finddata(master_cs->name2_tree, &mycs);
+               if (!master_cs) return NULL;
+
+               /*
+                *      Look it up in the name2 tree.  If it's there,
+                *      return it.
+                */
+               if (master_cs->name2_tree) {
+                       CONF_SECTION *subcs;
+
+                       subcs = rbtree_finddata(master_cs->name2_tree, &mycs);
+                       if (subcs) return subcs;
+               }
+
+               /*
+                *      We don't insert ourselves into the name2 tree.
+                *      So if there's nothing in the name2 tree, maybe
+                *      *we* are the answer.
+                */
+               if (!master_cs->name2 && name2) return NULL;
+               if (master_cs->name2 && !name2) return NULL;
+               if (!master_cs->name2 && !name2) return master_cs;
+
+               if (strcmp(master_cs->name2, name2) == 0) {
+                       return master_cs;
                }
+
+               return NULL;
        }
 
        /*
@@ -2027,10 +2303,13 @@ CONF_SECTION *cf_section_sub_find_name2(const CONF_SECTION *cs,
                        continue; /* don't do the string comparisons below */
                }
 
-               if ((strcmp(subcs->name1, name1) == 0) &&
-                   (subcs->name2 != NULL) &&
-                   (strcmp(subcs->name2, name2) == 0))
-                       break;
+               if (strcmp(subcs->name1, name1) != 0) continue;
+               if (!subcs->name2 && name2) continue;
+               if (subcs->name2 && !name2) continue;
+
+               if (!subcs->name2 && !name2) break;
+
+               if (strcmp(subcs->name2, name2) == 0) break;
        }
 
        return cf_itemtosection(ci);
@@ -2048,6 +2327,8 @@ CONF_SECTION *cf_subsection_find_next(CONF_SECTION *section,
 {
        CONF_ITEM       *ci;
 
+       if (!section) return NULL;
+
        /*
         * If subsection is NULL this must be a first time run
         * Find the subsection with correct name
@@ -2081,6 +2362,8 @@ CONF_SECTION *cf_section_find_next(CONF_SECTION *section,
                                   CONF_SECTION *subsection,
                                   const char *name1)
 {
+       if (!section) return NULL;
+
        if (!section->item.parent) return NULL;
 
        return cf_subsection_find_next(section->item.parent, subsection, name1);
@@ -2092,6 +2375,8 @@ CONF_SECTION *cf_section_find_next(CONF_SECTION *section,
 
 CONF_ITEM *cf_item_find_next(CONF_SECTION *section, CONF_ITEM *item)
 {
+       if (!section) return NULL;
+
        /*
         * If item is NULL this must be a first time run
         * Return the first item
@@ -2104,6 +2389,13 @@ CONF_ITEM *cf_item_find_next(CONF_SECTION *section, CONF_ITEM *item)
        }
 }
 
+CONF_SECTION *cf_item_parent(CONF_ITEM *ci)
+{
+       if (!ci) return NULL;
+
+       return ci->parent;
+}
+
 int cf_section_lineno(CONF_SECTION *section)
 {
        return cf_sectiontoitem(section)->lineno;
@@ -2570,7 +2862,7 @@ static const char *cf_pair_print_value(const CONF_PAIR *cp,
 }
 
 
-int cf_pair2xml(FILE *fp, CONF_PAIR *cp)
+int cf_pair2xml(FILE *fp, const CONF_PAIR *cp)
 {
        fprintf(fp, "<%s>", cp->attr);
        if (cp->value) {
@@ -2579,7 +2871,7 @@ int cf_pair2xml(FILE *fp, CONF_PAIR *cp)
                char *p = buffer;
                const char *q = cp->value;
 
-               while (*q && (p < (buffer + sizeof(buffer)))) {
+               while (*q && (p < (buffer + sizeof(buffer) - 1))) {
                        if (q[0] == '&') {
                                memcpy(p, "&amp;", 4);
                                p += 5;
@@ -2607,7 +2899,7 @@ int cf_pair2xml(FILE *fp, CONF_PAIR *cp)
        return 1;
 }
 
-int cf_section2xml(FILE *fp, CONF_SECTION *cs)
+int cf_section2xml(FILE *fp, const CONF_SECTION *cs)
 {
        CONF_ITEM *ci, *next;
 
@@ -2645,7 +2937,7 @@ int cf_section2xml(FILE *fp, CONF_SECTION *cs)
        return 1;               /* success */
 }
 
-int cf_pair2file(FILE *fp, CONF_PAIR *cp)
+int cf_pair2file(FILE *fp, const CONF_PAIR *cp)
 {
        char buffer[2048];
 
@@ -2655,9 +2947,9 @@ int cf_pair2file(FILE *fp, CONF_PAIR *cp)
        return 1;
 }
 
-int cf_section2file(FILE *fp, CONF_SECTION *cs)
+int cf_section2file(FILE *fp, const CONF_SECTION *cs)
 {
-       CONF_ITEM *ci, *next;
+       const CONF_ITEM *ci, *next;
 
        /*
         *      Section header
@@ -2677,11 +2969,11 @@ int cf_section2file(FILE *fp, CONF_SECTION *cs)
 
                switch (ci->type) {
                case CONF_ITEM_PAIR:
-                       if (!cf_pair2file(fp, (CONF_PAIR *) ci)) return 0;
+                       if (!cf_pair2file(fp, (const CONF_PAIR *) ci)) return 0;
                        break;
 
                case CONF_ITEM_SECTION:
-                       if (!cf_section2file(fp, (CONF_SECTION *) ci)) return 0;
+                       if (!cf_section2file(fp, (const CONF_SECTION *) ci)) return 0;
                        break;
 
                default:        /* should really be an error. */