summaryrefslogtreecommitdiff
path: root/p11-kit/conf.c
diff options
context:
space:
mode:
Diffstat (limited to 'p11-kit/conf.c')
-rw-r--r--p11-kit/conf.c429
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;
+}