Update the GPL boilerplate with the new address of the FSF.
[freeradius.git] / src / main / conffile.c
index 1cc500b..4d9332a 100644 (file)
  *
  *   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$";
@@ -78,9 +78,11 @@ struct conf_part {
        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;
 };
 
 
@@ -91,10 +93,18 @@ struct conf_part {
 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
  */
@@ -239,6 +249,22 @@ static int name2_cmp(const void *a, const void *b)
 
 
 /*
+ *     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)
@@ -285,6 +311,8 @@ 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
@@ -333,6 +361,10 @@ static CONF_SECTION *cf_section_alloc(const char *name1, const char *name2,
        }
 
        /*
+        *      Don't create a data tree, it may not be needed.
+        */
+
+       /*
         *      Don't create the section tree here, it may not
         *      be needed.
         */
@@ -345,23 +377,20 @@ static CONF_SECTION *cf_section_alloc(const char *name1, const char *name2,
  */
 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.
@@ -380,8 +409,7 @@ static void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci)
                                }
                                
                                if (cs->section_tree) {
-                                       rbtree_insert(cs->section_tree, cs_new);
-                               }
+                                       rbtree_insert(cs->section_tree, cs_new);                                }
                                
                                /*
                                 *      Two names: find the named instance.
@@ -411,9 +439,12 @@ static void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci)
                        } /* 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! */
@@ -637,7 +668,7 @@ static const char *cf_expand_variables(const char *cf, int *lineno,
  *     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;
@@ -716,6 +747,59 @@ int cf_item_parse(const CONF_SECTION *cs, const char *name,
                *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
@@ -785,13 +869,13 @@ int cf_section_parse(const CONF_SECTION *cs, void *base,
                         */
                        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;
@@ -818,6 +902,42 @@ int cf_section_parse(const CONF_SECTION *cs, void *base,
        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.
  */
@@ -1012,7 +1132,7 @@ static CONF_SECTION *cf_section_read(const char *cf, int *lineno, FILE *fp,
                                                 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;
@@ -1025,7 +1145,7 @@ static CONF_SECTION *cf_section_read(const char *cf, int *lineno, FILE *fp,
 #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;
                                }
@@ -1453,55 +1573,272 @@ static CONF_DATA *cf_data_alloc(CONF_SECTION *parent, const char *name,
 }
 
 
-/*
- *     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
 /*