*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* Copyright 2000 The FreeRADIUS server project
* Copyright 2000 Miquel van Smoorenburg <miquels@cistron.nl>
* Copyright 2000 Alan DeKok <aland@ox.org>
*/
-#include "autoconf.h"
+#include <freeradius-devel/autoconf.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include "radiusd.h"
-#include "rad_assert.h"
-#include "conffile.h"
-#include "token.h"
-#include "modules.h"
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
+#include <freeradius-devel/conffile.h>
+#include <freeradius-devel/token.h>
+#include <freeradius-devel/modules.h>
static const char rcsid[] =
"$Id$";
const char *name1;
const char *name2;
struct conf_item *children;
+ struct conf_item *tail; /* for speed */
rbtree_t *pair_tree; /* and a partridge.. */
rbtree_t *section_tree; /* no jokes here */
rbtree_t *name2_tree; /* for sections of the same name2 */
+ rbtree_t *data_tree;
};
struct conf_data {
CONF_ITEM item;
const char *name;
+ int flag;
void *data; /* user data */
void (*free)(void *); /* free user data function */
};
+
+static int cf_data_add_internal(CONF_SECTION *cs, const char *name,
+ void *data, void (*data_free)(void *),
+ int flag);
+static void *cf_data_find_internal(CONF_SECTION *cs, const char *name,
+ int flag);
+
/*
* Isolate the scary casts in these tiny provably-safe functions
*/
/*
+ * rbtree callback function
+ */
+static int data_cmp(const void *a, const void *b)
+{
+ int rcode;
+
+ const CONF_DATA *one = a;
+ const CONF_DATA *two = b;
+
+ rcode = one->flag - two->flag;
+ if (rcode != 0) return rcode;
+
+ return strcmp(one->name, two->name);
+}
+
+/*
* Free a CONF_SECTION
*/
void cf_section_free(CONF_SECTION **cs)
rbtree_free((*cs)->section_tree);
if ((*cs)->name2_tree)
rbtree_free((*cs)->name2_tree);
+ if ((*cs)->data_tree)
+ rbtree_free((*cs)->data_tree);
/*
* And free the section
}
/*
+ * Don't create a data tree, it may not be needed.
+ */
+
+ /*
* Don't create the section tree here, it may not
* be needed.
*/
*/
static void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci)
{
- CONF_ITEM **last;
-
- /*
- * New entries are added at the bottom of the list.
- */
- for (last = &(cs->children);
- (*last) != NULL;
- last = &((*last)->next)) {
- /* nothing */
+ if (!cs->children) {
+ rad_assert(cs->tail == NULL);
+ cs->children = ci;
+ } else {
+ rad_assert(cs->tail != NULL);
+ cs->tail->next = ci;
}
- *last = ci;
/*
- * We may be adding a list, rather than just one element.
- * If so, loop over all entries.
+ * Update the trees (and tail) for each item added.
*/
- for (*last = ci; ci != NULL; ci = ci->next) {
+ for (/* nothing */; ci != NULL; ci = ci->next) {
+ cs->tail = ci;
+
/*
* For fast lookups, pair's and sections get
* added to rbtree's.
}
if (cs->section_tree) {
- rbtree_insert(cs->section_tree, cs_new);
- }
+ rbtree_insert(cs->section_tree, cs_new); }
/*
* Two names: find the named instance.
} /* was a section */
case CONF_ITEM_DATA:
- /*
- * Don't do anything special.
- */
+ if (!cs->data_tree) {
+ cs->data_tree = rbtree_create(data_cmp, NULL, 0);
+ }
+ if (cs->data_tree) {
+ rbtree_insert(cs->data_tree, ci);
+ }
break;
default: /* FIXME: assert & error! */
* default value was used. Note that the default value will be
* used ONLY if the CONF_PAIR is NULL.
*/
-int cf_item_parse(const CONF_SECTION *cs, const char *name,
+int cf_item_parse(CONF_SECTION *cs, const char *name,
int type, void *data, const char *dflt)
{
int rcode = 0;
*q = value ? strdup(value) : NULL;
break;
+ /*
+ * This is the same as PW_TYPE_STRING_PTR,
+ * except that we also "stat" the file, and
+ * cache the result.
+ */
+ case PW_TYPE_FILENAME:
+ q = (char **) data;
+ if (*q != NULL) {
+ free(*q);
+ }
+
+ /*
+ * Expand variables which haven't already been
+ * expanded automagically when the configuration
+ * file was read.
+ */
+ if (value == dflt) {
+ char buffer[8192];
+
+ int lineno = cs->item.lineno;
+
+ /*
+ * FIXME: sizeof(buffer)?
+ */
+ value = cf_expand_variables("?",
+ &lineno,
+ cs, buffer, value);
+ if (!value) return -1;
+ }
+
+ DEBUG2(" %s: %s = \"%s\"",
+ cs->name1, name,
+ value ? value : "(null)");
+ *q = value ? strdup(value) : NULL;
+
+ /*
+ * And now we "stat" the file.XXX
+ */
+ if (*q) {
+ struct stat buf;
+
+ if (stat(*q, &buf) == 0) {
+ time_t *mtime;
+
+ mtime = rad_malloc(sizeof(*mtime));
+ *mtime = buf.st_mtime;
+ /* FIXME: error? */
+ cf_data_add_internal(cs, *q, mtime, free,
+ PW_TYPE_FILENAME);
+ }
+ }
+ break;
+
case PW_TYPE_IPADDR:
/*
* Allow '*' as any address
*/
if (!subcs) continue;
- if (!variables[i].data) {
+ if (!variables[i].dflt) {
DEBUG2("Internal sanity check 1 failed in cf_section_parse");
return -1;
}
if (cf_section_parse(subcs, base,
- (CONF_PARSER *) variables[i].data) < 0) {
+ (const CONF_PARSER *) variables[i].dflt) < 0) {
return -1;
}
continue;
return 0;
}
+
+/*
+ * Free strings we've parsed into data structures.
+ */
+void cf_section_parse_free_strings(void *base, const CONF_PARSER *variables)
+{
+ int i;
+
+ if (!variables) return;
+
+ /*
+ * Free up dynamically allocated string pointers.
+ */
+ for (i = 0; variables[i].name != NULL; i++) {
+ char **p;
+
+ if ((variables[i].type != PW_TYPE_STRING_PTR) &&
+ (variables[i].type != PW_TYPE_FILENAME)) {
+ continue;
+ }
+
+ /*
+ * Prefer the data, if it's there.
+ * Else use the base + offset.
+ */
+ if (variables[i].data) {
+ p = (char **) &(variables[i].data);
+ } else {
+ p = (char **) (((char *)base) + variables[i].offset);
+ }
+ free(*p);
+ *p = NULL;
+ }
+}
+
+
/*
* Used in a few places, so in one function for clarity.
*/
value, dp->d_name);
if ((stat(buf2, &stat_buf) != 0) ||
S_ISDIR(stat_buf.st_mode)) continue;
- if ((is = conf_read(cf, *lineno, buf2, parent)) == NULL) {
+ if ((is = conf_read(cf, *lineno, buf2, cs)) == NULL) {
closedir(dir);
cf_section_free(&cs);
return NULL;
#endif
{ /* it was a normal file */
DEBUG2( "Config: including file: %s", value );
- if ((is = conf_read(cf, *lineno, value, parent)) == NULL) {
+ if ((is = conf_read(cf, *lineno, value, cs)) == NULL) {
cf_section_free(&cs);
return NULL;
}
}
-/*
- * Find data from a particular section.
- */
-void *cf_data_find(CONF_SECTION *cs, const char *name)
+static void *cf_data_find_internal(CONF_SECTION *cs, const char *name,
+ int flag)
{
- CONF_ITEM *ci;
-
if (!cs || !name) return NULL;
+
+ /*
+ * Find the name in the tree, for speed.
+ */
+ if (cs->data_tree) {
+ CONF_DATA mycd, *cd;
- for (ci = cs->children; ci != NULL; ci = ci->next) {
- CONF_DATA *cd;
-
- /*
- * Data is always inserted at the front of the
- * list.
- */
- if (ci->type != CONF_ITEM_DATA) return NULL;
-
- cd = cf_itemtodata(ci);
- if (strcmp(name, cd->name) == 0) return cd->data;
+ mycd.name = name;
+ mycd.flag = flag;
+ cd = rbtree_finddata(cs->data_tree, &mycd);
+ if (cd) return cd->data;
}
return NULL;
}
+/*
+ * Find data from a particular section.
+ */
+void *cf_data_find(CONF_SECTION *cs, const char *name)
+{
+ return cf_data_find_internal(cs, name, 0);
+}
+
/*
* Add named data to a configuration section.
*/
-int cf_data_add(CONF_SECTION *cs, const char *name,
- void *data, void (*data_free)(void *))
+static int cf_data_add_internal(CONF_SECTION *cs, const char *name,
+ void *data, void (*data_free)(void *),
+ int flag)
{
CONF_DATA *cd;
- if (!cs || !name || !data) return -1;
+ if (!cs || !name) return -1;
/*
* Already exists. Can't add it.
*/
- if (cf_data_find(cs, name) != NULL) return -1;
+ if (cf_data_find_internal(cs, name, flag) != NULL) return -1;
cd = cf_data_alloc(cs, name, data, data_free);
if (!cd) return -1;
+ cd->flag = flag;
cf_item_add(cs, cf_datatoitem(cd));
return 0;
}
+/*
+ * Add named data to a configuration section.
+ */
+int cf_data_add(CONF_SECTION *cs, const char *name,
+ void *data, void (*data_free)(void *))
+{
+ return cf_data_add_internal(cs, name, data, data_free, 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;
+
+ /*
+ * Don't check if s->data_tree is NULL. It's child
+ * sections may have data, even if this section doesn't.
+ */
+
+ 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.
+ */
+ 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) {
+ cf_section_copy_data(s1, d1);
+ }
+ last = &(cd->next);
+ continue;
+ }
+
+ /*
+ * Not conf data, remember last ptr.
+ */
+ if (cd->type != CONF_ITEM_DATA) {
+ last = &(cd->next);
+ continue;
+ }
+
+ /*
+ * Remove it from the src list
+ */
+ *last = cd->next;
+ cd->next = NULL;
+
+ /*
+ * Add it to the dst list
+ */
+ if (!d->children) {
+ rad_assert(d->tail == NULL);
+ d->children = cd;
+ } else {
+ rad_assert(d->tail != NULL);
+ d->tail->next = cd;
+ }
+ d->tail = cd;
+ }
+}
+
+/*
+ * For a CONF_DATA element, stat the filename, if necessary.
+ */
+static int filename_stat(void *context, void *data)
+{
+ struct stat buf;
+ CONF_DATA *cd = data;
+
+ context = context; /* -Wunused */
+
+ if (cd->flag != PW_TYPE_FILENAME) return 0;
+
+ if (stat(cd->name, &buf) < 0) return -1;
+
+ if (buf.st_mtime != *(time_t *) cd->data) return -1;
+
+ return 0;
+}
+
+
+/*
+ * Compare two CONF_SECTIONS. The items MUST be in the same
+ * order.
+ */
+static int cf_section_cmp(CONF_SECTION *a, CONF_SECTION *b)
+{
+ CONF_ITEM *ca = a->children;
+ CONF_ITEM *cb = b->children;
+
+ while (1) {
+ CONF_PAIR *pa, *pb;
+
+ /*
+ * Done. Stop.
+ */
+ if (!ca && !cb) break;
+
+ /*
+ * Skip CONF_DATA.
+ */
+ if (ca && ca->type == CONF_ITEM_DATA) {
+ ca = ca->next;
+ continue;
+ }
+ if (cb && cb->type == CONF_ITEM_DATA) {
+ cb = cb->next;
+ continue;
+ }
+
+ /*
+ * One is smaller than the other. Exit.
+ */
+ if (!ca || !cb) return 0;
+
+ if (ca->type != cb->type) return 0;
+
+ /*
+ * Deal with subsections.
+ */
+ if (ca->type == CONF_ITEM_SECTION) {
+ CONF_SECTION *sa = cf_itemtosection(ca);
+ CONF_SECTION *sb = cf_itemtosection(cb);
+
+ if (!cf_section_cmp(sa, sb)) return 0;
+ goto next;
+ }
+
+ rad_assert(ca->type == CONF_ITEM_PAIR);
+
+ pa = cf_itemtopair(ca);
+ pb = cf_itemtopair(cb);
+
+ /*
+ * Different attr and/or value, Exit.
+ */
+ if ((strcmp(pa->attr, pb->attr) != 0) ||
+ (strcmp(pa->value, pb->value) != 0)) return 0;
+
+
+ /*
+ * And go to the next element.
+ */
+ next:
+ ca = ca->next;
+ cb = cb->next;
+ }
+
+ /*
+ * Walk over the CONF_DATA, stat'ing PW_TYPE_FILENAME.
+ */
+ if (a->data_tree &&
+ (rbtree_walk(a->data_tree, InOrder, filename_stat, NULL) != 0)) {
+ return 0;
+ }
+
+ /*
+ * They must be the same, say so.
+ */
+ return 1;
+}
+
+
+
+
+/*
+ * Migrate CONF_DATA from one section to another.
+ */
+int cf_section_migrate(CONF_SECTION *dst, CONF_SECTION *src)
+{
+ CONF_ITEM *ci;
+ CONF_SECTION *s, *d;
+
+ for (ci = src->children; ci != NULL; ci = ci->next) {
+ if (ci->type != CONF_ITEM_SECTION)
+ continue;
+
+ s = cf_itemtosection(ci);
+ d = cf_section_sub_find_name2(dst, s->name1, s->name2);
+
+ if (!d) continue; /* not in new one, don't migrate it */
+
+ /*
+ * A section of the same name is in BOTH src & dst,
+ * compare the CONF_PAIR's. If they're all the same,
+ * then copy the CONF_DATA from one to the other.
+ */
+ if (cf_section_cmp(s, d)) {
+ cf_section_copy_data(s, d);
+ }
+ }
+
+ return 1; /* rcode means anything? */
+}
+
#if 0
/*