From c187732f09e477d9a611ad18bfa1739befb86074 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Tue, 24 Aug 2010 21:52:32 +0000 Subject: [PATCH] add profile include support 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 | 14 +++++ src/config-files/krb5.conf.M | 10 ++++ src/util/profile/prof_err.et | 8 ++- src/util/profile/prof_parse.c | 105 +++++++++++++++++++++++++++++----- src/util/profile/prof_test1 | 57 ++++++++++++++++++ 5 files changed, 180 insertions(+), 14 deletions(-) diff --git a/doc/krb5conf.texinfo b/doc/krb5conf.texinfo index 09825524f..21f539653 100644 --- a/doc/krb5conf.texinfo +++ b/doc/krb5conf.texinfo @@ -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: diff --git a/src/config-files/krb5.conf.M b/src/config-files/krb5.conf.M index 5ecfd426c..40db552d3 100644 --- a/src/config-files/krb5.conf.M +++ b/src/config-files/krb5.conf.M @@ -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 diff --git a/src/util/profile/prof_err.et b/src/util/profile/prof_err.et index af7801ee0..2384127af 100644 --- a/src/util/profile/prof_err.et +++ b/src/util/profile/prof_err.et @@ -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 diff --git a/src/util/profile/prof_parse.c b/src/util/profile/prof_parse.c index 413c7dfbb..1ed448443 100644 --- a/src/util/profile/prof_parse.c +++ b/src/util/profile/prof_parse.c @@ -1,6 +1,7 @@ /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ #include "prof_int.h" +#include #include #include #ifdef HAVE_STDLIB_H @@ -8,6 +9,7 @@ #endif #include #include +#include #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 */ diff --git a/src/util/profile/prof_test1 b/src/util/profile/prof_test1 index bd4901272..dc0867123 100644 --- a/src/util/profile/prof_test1 +++ b/src/util/profile/prof_test1 @@ -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 -- 2.26.2