add profile include support
authorGreg Hudson <ghudson@mit.edu>
Tue, 24 Aug 2010 21:52:32 +0000 (21:52 +0000)
committerGreg Hudson <ghudson@mit.edu>
Tue, 24 Aug 2010 21:52:32 +0000 (21:52 +0000)
Add support for "include" and "includedir" directives in profile files.
See http://k5wiki.kerberos.org/wiki/Projects/Profile_Includes for more
details.

ticket: 6761

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24253 dc483132-0cff-0310-8789-dd5450dbe970

doc/krb5conf.texinfo
src/config-files/krb5.conf.M
src/util/profile/prof_err.et
src/util/profile/prof_parse.c
src/util/profile/prof_test1

index 09825524f528b724732ff93f5dddf7c57e2775d8..21f539653271a6c92d449c08d54d5e2ccf8b5691 100644 (file)
@@ -40,6 +40,20 @@ foo = baz
 
 then the second value of foo (baz) would never be read.
 
+The @code{krb5.conf} file can include other files using either of the
+following directives at the beginning of a line:
+
+@smallexample
+include @var{FILENAME}
+includedir @var{DIRNAME}
+@end smallexample
+
+@var{FILENAME} or @var{DIRNAME} should be an absolute path.  The named
+file or directory must exist and be readable.  Including a directory
+includes all files within the directory whose names consist solely of
+alphanumeric characters, dashes, or underscores.  Included configuration
+fragments should begin with a section header.
+
 The @code{krb5.conf} file may contain any or all of the following 
 sections:
 
index 5ecfd426c70ce04837fecd1eee93ed7dcaecf4ae..40db552d3e413e7461b98fcf77face8058b019e5 100644 (file)
@@ -59,6 +59,16 @@ multiple values.  Here is an example of the INI-style format used by
 .fi
 .sp
 
+.PP
+.I krb5.conf
+can include other files using the directives "include FILENAME" or
+"includedir DIRNAME", which must occur at the beginning of a line.
+FILENAME or DIRNAME should be an absolute path.  The named file or
+directory must exist and be readable.  Including a directory includes
+all files within the directory whose names consist solely of
+alphanumeric characters, dashes, or underscores.  Included profile
+fragments should begin with a section header.
+
 .PP
 The following sections are currently used in the 
 .I krb5.conf
index af7801ee0a804eb3128abd01334f412a6dc453a4..2384127af95dfe46362218f8b1463d42bc26259f 100644 (file)
@@ -60,7 +60,13 @@ error_code   PROF_EXISTS,            "Section already exists"
 error_code     PROF_BAD_BOOLEAN,               "Invalid boolean value"
 error_code     PROF_BAD_INTEGER,               "Invalid integer value"
 
+#
+# new error codes added at end to avoid changing values
+#
 error_code     PROF_MAGIC_FILE_DATA, "Bad magic value in profile_file_data_t"
-
+error_code     PROF_FAIL_INCLUDE_FILE,
+       "Included profile file could not be read"
+error_code     PROF_FAIL_INCLUDE_DIR,
+       "Included profile directory could not be read"
 
 end
index 413c7dfbb0b5b6983991fa2502db75f97e41cbe0..1ed4484430a797ef3b443f1a158731be28badb05 100644 (file)
@@ -1,6 +1,7 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 #include "prof_int.h"
 
+#include <sys/types.h>
 #include <stdio.h>
 #include <string.h>
 #ifdef HAVE_STDLIB_H
@@ -8,6 +9,7 @@
 #endif
 #include <errno.h>
 #include <ctype.h>
+#include <dirent.h>
 
 #define SECTION_SEP_CHAR '/'
 
@@ -22,6 +24,8 @@ struct parse_state {
     struct profile_node *current_section;
 };
 
+static errcode_t parse_file(FILE *f, struct parse_state *state);
+
 static char *skip_over_blanks(char *cp)
 {
     while (*cp && isspace((int) (*cp)))
@@ -33,7 +37,7 @@ static void strip_line(char *line)
 {
     char *p = line + strlen(line);
     while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
-        *p-- = 0;
+        *--p = 0;
 }
 
 static void parse_quoted_string(char *str)
@@ -201,10 +205,76 @@ static errcode_t parse_std_line(char *line, struct parse_state *state)
     return 0;
 }
 
+/* Parse lines from filename as if they were part of the profile file. */
+static errcode_t parse_include_file(char *filename, struct parse_state *state)
+{
+    FILE    *fp;
+    errcode_t retval = 0;
+
+    fp = fopen(filename, "r");
+    if (fp == NULL)
+        return PROF_FAIL_INCLUDE_FILE;
+    retval = parse_file(fp, state);
+    fclose(fp);
+    return retval;
+}
+
+/* Return non-zero if filename contains only alphanumeric characters and
+ * underscores. */
+static int valid_name(const char *filename)
+{
+    const char *p;
+
+    for (p = filename; *p != '\0'; p++) {
+        if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_')
+            return 0;
+    }
+    return 1;
+}
+
+/*
+ * Parse lines from files in dirname as if they were part of the profile file.
+ * Only files with names consisting entirely of alphanumeric chracters and
+ * underscores are parsed, in order to avoid parsing editor backup files,
+ * .rpmsave files, and the like.
+ */
+static errcode_t parse_include_dir(char *dirname, struct parse_state *state)
+{
+    DIR     *dir;
+    char    *pathname;
+    errcode_t retval;
+    struct dirent *ent;
+
+    dir = opendir(dirname);
+    if (dir == NULL)
+        return PROF_FAIL_INCLUDE_DIR;
+    while ((ent = readdir(dir)) != NULL) {
+        if (!valid_name(ent->d_name))
+            continue;
+        if (asprintf(&pathname, "%s/%s", dirname, ent->d_name) < 0)
+            return ENOMEM;
+        retval = parse_include_file(pathname, state);
+        free(pathname);
+        if (retval)
+            return retval;
+    }
+    return 0;
+}
+
 static errcode_t parse_line(char *line, struct parse_state *state)
 {
     char    *cp;
 
+    if (strncmp(line, "include", 7) == 0 && isspace(line[7])) {
+        cp = skip_over_blanks(line + 7);
+        strip_line(cp);
+        return parse_include_file(cp, state);
+    }
+    if (strncmp(line, "includedir", 10) == 0 && isspace(line[10])) {
+        cp = skip_over_blanks(line + 10);
+        strip_line(cp);
+        return parse_include_dir(cp, state);
+    }
     switch (state->state) {
     case STATE_INIT_COMMENT:
         if (line[0] != '[')
@@ -221,29 +291,22 @@ static errcode_t parse_line(char *line, struct parse_state *state)
     return 0;
 }
 
-errcode_t profile_parse_file(FILE *f, struct profile_node **root)
+static errcode_t parse_file(FILE *f, struct parse_state *state)
 {
 #define BUF_SIZE        2048
     char *bptr;
     errcode_t retval;
-    struct parse_state state;
 
     bptr = malloc (BUF_SIZE);
     if (!bptr)
         return ENOMEM;
 
-    retval = parse_init_state(&state);
-    if (retval) {
-        free (bptr);
-        return retval;
-    }
     while (!feof(f)) {
         if (fgets(bptr, BUF_SIZE, f) == NULL)
             break;
 #ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
-        retval = parse_line(bptr, &state);
+        retval = parse_line(bptr, state);
         if (retval) {
-            profile_free_node(state.root_section);
             free (bptr);
             return retval;
         }
@@ -286,9 +349,8 @@ errcode_t profile_parse_file(FILE *f, struct profile_node **root)
 
                 /* parse_line modifies contents of p */
                 newp = p + strlen (p) + 1;
-                retval = parse_line (p, &state);
+                retval = parse_line (p, state);
                 if (retval) {
-                    profile_free_node(state.root_section);
                     free (bptr);
                     return retval;
                 }
@@ -298,12 +360,29 @@ errcode_t profile_parse_file(FILE *f, struct profile_node **root)
         }
 #endif
     }
-    *root = state.root_section;
 
     free (bptr);
     return 0;
 }
 
+errcode_t profile_parse_file(FILE *f, struct profile_node **root)
+{
+    struct parse_state state;
+    errcode_t retval;
+
+    *root = NULL;
+    retval = parse_init_state(&state);
+    if (retval)
+        return retval;
+    retval = parse_file(f, &state);
+    if (retval) {
+        profile_free_node(state.root_section);
+        return retval;
+    }
+    *root = state.root_section;
+    return 0;
+}
+
 /*
  * Return TRUE if the string begins or ends with whitespace
  */
index bd4901272dc563b245dae87b30892d6f8b157b5e..dc0867123dda431d3ca97d29add44544f8bcab82 100644 (file)
@@ -147,8 +147,65 @@ proc test3 {} {
     puts "OK: test3: Clearing relation and adding one entry yields correct count."
 }
 
+# Exercise the include and includedir directives.
+proc test4 {} {
+    global wd verbose
+
+    # Test expected error message when including nonexistent file.
+    catch [file delete $wd/testinc.ini]
+    exec echo "include does-not-exist" >$wd/testinc.ini
+    catch { profile_init_path $wd/testinc.ini } err
+    if $verbose { puts "Got error message $err" }
+    if { $err ne "Included profile file could not be read" } {
+       puts stderr "Error: test4: Did not get expected error when including nonexistent file."
+       exit 1
+    }
+
+    # Test expected error message when including nonexistent directory.
+    catch [file delete $wd/testinc.ini]
+    exec echo "includedir does-not-exist" >$wd/testinc.ini
+    catch { profile_init_path $wd/testinc.ini } err
+    if $verbose { puts "Got error message $err" }
+    if { $err ne "Included profile directory could not be read" } {
+       puts stderr "Error: test4: Did not get expected error when including nonexistent directory."
+       exit 1
+    }
+
+    # Test including a file.
+    catch [file delete $wd/testinc.ini]
+    exec echo "include $wd/test2.ini" >$wd/testinc.ini
+    set p [profile_init_path $wd/testinc.ini]
+    set x [profile_get_values $p {{test section 1} bar}]
+    if $verbose { puts "Read $x from included profile" }
+    if { [lindex $x 0] ne "foo" } {
+       puts stderr "Error: test4: Did not get expected result from included profile."
+       exit 1
+    }
+    profile_release $p
+
+    # Test including a directory.  (Put two copies of test2.ini inside
+    # it and check that we get two values for one of the variables.)
+    catch [file delete -force $wd/test_include_dir]
+    exec mkdir $wd/test_include_dir
+    exec cp $wd/test2.ini $wd/test_include_dir/a
+    exec cp $wd/test2.ini $wd/test_include_dir/b
+    catch [file delete $wd/testinc.ini]
+    exec echo "includedir $wd/test_include_dir" >$wd/testinc.ini
+    set p [profile_init_path $wd/testinc.ini]
+    set x [profile_get_values $p {{test section 1} bar}]
+    if $verbose { puts "Read $x from included directory" }
+    if { $x ne "foo foo" } {
+       puts stderr, "Error: test4: Did not get expected result from included directory."
+       exit 1
+    }
+    profile_release $p
+
+    puts "OK: test4: include and includedir directives"
+}
+
 test1
 test2
 test3
+test4
 
 exit 0