diff options
Diffstat (limited to 'p11-kit/conf.c')
-rw-r--r-- | p11-kit/conf.c | 429 |
1 files changed, 392 insertions, 37 deletions
diff --git a/p11-kit/conf.c b/p11-kit/conf.c index 51fe579..8d4703e 100644 --- a/p11-kit/conf.c +++ b/p11-kit/conf.c @@ -40,6 +40,7 @@ #include "conf.h" #define DEBUG_FLAG DEBUG_CONF #include "debug.h" +#include "private.h" #include <sys/param.h> #include <sys/stat.h> @@ -49,6 +50,7 @@ #include <ctype.h> #include <dirent.h> #include <errno.h> +#include <pwd.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -56,23 +58,6 @@ #include <unistd.h> static void -errmsg (conf_error_func error_func, const char* msg, ...) -{ - #define MAX_MSGLEN 1024 - char buf[MAX_MSGLEN]; - va_list ap; - - if (!error_func) - return; - - va_start (ap, msg); - vsnprintf (buf, MAX_MSGLEN, msg, ap); - buf[MAX_MSGLEN - 1] = 0; - error_func (buf); - va_end (ap); -} - -static void strcln (char* data, char ch) { char* p; @@ -112,22 +97,64 @@ strtrim (char* data) return data; } +static int +strequal (const char *one, const char *two) +{ + return strcmp (one, two) == 0; +} + +static char* +strconcat (const char *first, ...) +{ + size_t length = 0; + const char *arg; + char *result, *at; + va_list va; + + va_start (va, first); + + for (arg = first; arg; arg = va_arg (va, const char*)) + length += strlen (arg); + + va_end (va); + + at = result = malloc (length + 1); + if (!result) { + errno = ENOMEM; + return NULL; + } + + va_start (va, first); + + for (arg = first; arg; arg = va_arg (va, const char*)) { + length = strlen (arg); + memcpy (at, arg, length); + at += length; + } + + va_end (va); + + *at = 0; + return result; +} + /* ----------------------------------------------------------------------------- * CONFIG PARSER */ static char* -read_config_file (const char* filename, int flags, - conf_error_func error_func) +read_config_file (const char* filename, int flags) { char* config = NULL; FILE* f = NULL; + int error = 0; long len; assert (filename); f = fopen (filename, "r"); if (f == NULL) { + error = errno; if ((flags & CONF_IGNORE_MISSING) && (errno == ENOENT || errno == ENOTDIR)) { debug ("config file does not exist"); @@ -136,7 +163,8 @@ read_config_file (const char* filename, int flags, errno = ENOMEM; return config; } - errmsg (error_func, "couldn't open config file: %s", filename); + _p11_warning ("couldn't open config file: %s", filename); + errno = error; return NULL; } @@ -144,19 +172,23 @@ read_config_file (const char* filename, int flags, if (fseek (f, 0, SEEK_END) == -1 || (len = ftell (f)) == -1 || fseek (f, 0, SEEK_SET) == -1) { - errmsg (error_func, "couldn't seek config file: %s", filename); + error = errno; + _p11_warning ("couldn't seek config file: %s", filename); + errno = error; return NULL; } if ((config = (char*)malloc (len + 2)) == NULL) { - errmsg (error_func, "out of memory"); + _p11_warning ("out of memory"); errno = ENOMEM; return NULL; } /* And read in one block */ if (fread (config, 1, len, f) != len) { - errmsg (error_func, "couldn't read config file: %s", filename); + error = errno; + _p11_warning ("couldn't read config file: %s", filename); + errno = error; return NULL; } @@ -172,28 +204,69 @@ read_config_file (const char* filename, int flags, return config; } +int +_p11_conf_merge_defaults (hash_t *ht, hash_t *defaults) +{ + hash_iter_t hi; + void *key; + void *value; + + hash_iterate (defaults, &hi); + while (hash_next (&hi, &key, &value)) { + /* Only override if not set */ + if (hash_get (ht, key)) + continue; + key = strdup (key); + if (key == NULL) { + errno = ENOMEM; + return -1; + } + value = strdup (value); + if (value == NULL) { + free (key); + errno = ENOMEM; + return -1; + } + if (!hash_set (ht, key, value)) { + free (key); + free (value); + errno = ENOMEM; + return -1; + } + key = NULL; + value = NULL; + } + + return 0; +} + hash_t* -conf_parse_file (const char* filename, int flags, - conf_error_func error_func) +_p11_conf_parse_file (const char* filename, int flags) { char *name; char *value; hash_t *ht = NULL; - char *config; + char *data; char *next; char *end; + int error = 0; assert (filename); debug ("reading config file: %s", filename); /* Adds an extra newline to end of file */ - config = read_config_file (filename, flags, error_func); - if (!config) + data = read_config_file (filename, flags); + if (!data) return NULL; ht = hash_create (hash_string_hash, hash_string_equal, free, free); - next = config; + if (ht == NULL) { + free (data); + errno = ENOMEM; + return NULL; + } + next = data; /* Go through lines and process them */ while ((end = strchr (next, '\n')) != NULL) { @@ -208,8 +281,8 @@ conf_parse_file (const char* filename, int flags, /* Look for the break between name: value on the same line */ value = name + strcspn (name, ":"); if (!*value) { - errmsg (error_func, "%s: invalid config line: %s", filename, name); - errno = EINVAL; + _p11_warning ("%s: invalid config line: %s", filename, name); + error = EINVAL; break; } @@ -222,13 +295,13 @@ conf_parse_file (const char* filename, int flags, name = strdup (name); if (!name) { - errno = ENOMEM; + error = ENOMEM; break; } value = strdup (value); if (!value) { free (name); - errno = ENOMEM; + error = ENOMEM; break; } @@ -237,17 +310,299 @@ conf_parse_file (const char* filename, int flags, if (!hash_set (ht, name, value)) { free (name); free (value); - errno = ENOMEM; + error = ENOMEM; break; } } - /* Unsuccessful? */ - if (end != NULL) { + free (data); + + if (error != 0) { hash_free (ht); ht = NULL; + errno = error; } - free (config); return ht; } + +static char* +expand_user_path (const char *path) +{ + const char *env; + struct passwd *pwd; + int error = 0; + + if (path[0] == '~' && path[1] == '/') { + env = getenv ("HOME"); + if (env && env[0]) { + return strconcat (env, path + 1, NULL); + } else { + pwd = getpwuid (getuid ()); + if (!pwd) { + error = errno; + _p11_warning ("couldn't lookup home directory for user %d: %s", + getuid (), strerror (errno)); + errno = error; + return NULL; + } + return strconcat (pwd->pw_dir, path + 1, NULL); + } + } + + return strdup (path); +} + +static int +user_config_mode (hash_t *config, int defmode) +{ + const char *mode; + + /* Whether we should use or override from user directory */ + mode = hash_get (config, "user-config"); + if (mode == NULL) { + return defmode; + } else if (strequal (mode, "none")) { + return CONF_USER_NONE; + } else if (strequal (mode, "merge")) { + return CONF_USER_MERGE; + } else if (strequal (mode, "only")) { + return CONF_USER_ONLY; + } else if (strequal (mode, "override")) { + return CONF_USER_ONLY; + } else { + _p11_warning ("invalid mode for 'user-config': %s", mode); + return CONF_USER_INVALID; + } +} + +hash_t* +_p11_conf_load_globals (const char *system_conf, const char *user_conf, + int *user_mode) +{ + hash_t *config = NULL; + hash_t *uconfig = NULL; + hash_t *result = NULL; + char *path = NULL; + int error = 0; + int mode; + + /* + * This loads the system and user configs. This depends on the user-config + * value in both the system and user configs. A bit more complex than + * you might imagine, since user-config can be set to 'none' in the + * user configuration, essentially turning itself off. + */ + + /* Load the main configuration */ + config = _p11_conf_parse_file (system_conf, CONF_IGNORE_MISSING); + if (!config) + goto finished; + + /* Whether we should use or override from user directory */ + mode = user_config_mode (config, CONF_USER_NONE); + if (mode == CONF_USER_INVALID) { + error = EINVAL; + goto finished; + } + + if (mode != CONF_USER_NONE) { + path = expand_user_path (user_conf); + if (!path) { + error = errno; + goto finished; + } + + /* Load up the user configuration */ + uconfig = _p11_conf_parse_file (path, CONF_IGNORE_MISSING); + if (!uconfig) { + error = errno; + goto finished; + } + + /* Figure out what the user mode is, defaulting to system mode if not set */ + mode = user_config_mode (uconfig, mode); + if (mode == CONF_USER_INVALID) { + error = EINVAL; + goto finished; + } + + /* If merging, then supplement user config with system values */ + if (mode == CONF_USER_MERGE) { + if (_p11_conf_merge_defaults (uconfig, config) < 0) { + error = errno; + goto finished; + } + } + + /* If user config valid at all, then replace system with what we have */ + if (mode != CONF_USER_NONE) { + hash_free (config); + config = uconfig; + uconfig = NULL; + } + } + + if (user_mode) + *user_mode = mode; + + result = config; + config = NULL; + +finished: + free (path); + hash_free (config); + hash_free (uconfig); + errno = error; + return result; +} + +static int +load_config_from_file (const char *configfile, const char *name, hash_t *configs) +{ + hash_t *config; + hash_t *prev; + char *key; + int error = 0; + + assert (configfile); + + config = _p11_conf_parse_file (configfile, 0); + if (!config) + return -1; + + prev = hash_get (configs, name); + if (prev == NULL) { + key = strdup (name); + if (key == NULL) + error = ENOMEM; + else if (!hash_set (configs, key, config)) + error = errno; + else + config = NULL; + } else { + if (_p11_conf_merge_defaults (prev, config) < 0) + error = errno; + } + + /* If still set */ + hash_free (config); + + if (error) { + errno = error; + return -1; + } + + return 0; +} + +static int +load_configs_from_directory (const char *directory, hash_t *configs) +{ + struct dirent *dp; + struct stat st; + DIR *dir; + int error = 0; + int is_dir; + char *path; + int count = 0; + + debug ("loading module configs in: %s", directory); + + /* First we load all the modules */ + dir = opendir (directory); + if (!dir) { + error = errno; + if (errno == ENOENT || errno == ENOTDIR) + return 0; + _p11_warning ("couldn't list directory: %s", directory); + errno = error; + return -1; + } + + /* We're within a global mutex, so readdir is safe */ + while ((dp = readdir(dir)) != NULL) { + path = strconcat (directory, "/", dp->d_name, NULL); + if (!path) { + error = ENOMEM; + break; + } + + is_dir = 0; +#ifdef HAVE_STRUCT_DIRENT_D_TYPE + if(dp->d_type != DT_UNKNOWN) { + is_dir = (dp->d_type == DT_DIR); + } else +#endif + { + if (stat (path, &st) < 0) { + error = errno; + _p11_warning ("couldn't stat path: %s", path); + free (path); + break; + } + is_dir = S_ISDIR (st.st_mode); + } + + if (!is_dir && load_config_from_file (path, dp->d_name, configs) < 0) { + error = errno; + free (path); + break; + } + + free (path); + count ++; + } + + closedir (dir); + + if (error) { + errno = error; + return -1; + } + + return count; +} + +hash_t* +_p11_conf_load_modules (int mode, const char *system_dir, const char *user_dir) +{ + hash_t *configs; + char *path; + int error = 0; + + /* A hash table of name -> config */ + configs = hash_create (hash_string_hash, hash_string_equal, + free, (hash_destroy_func)hash_free); + + /* Load each user config first, if user config is allowed */ + if (mode != CONF_USER_NONE) { + path = expand_user_path (user_dir); + if (!path) + error = errno; + else if (load_configs_from_directory (path, configs) < 0) + error = errno; + free (path); + if (error != 0) { + hash_free (configs); + errno = error; + return NULL; + } + } + + /* + * Now unless user config is overriding, load system modules. + * Basically if a value for the same config name is not already + * loaded above (in the user configs) then they're loaded here. + */ + if (mode != CONF_USER_ONLY) { + if (load_configs_from_directory (system_dir, configs) < 0) { + error = errno; + hash_free (configs); + errno = error; + return NULL; + } + } + + return configs; +} |