summaryrefslogtreecommitdiffstats
path: root/src/lib/module-dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/module-dir.c')
-rw-r--r--src/lib/module-dir.c697
1 files changed, 697 insertions, 0 deletions
diff --git a/src/lib/module-dir.c b/src/lib/module-dir.c
new file mode 100644
index 0000000..26fdeac
--- /dev/null
+++ b/src/lib/module-dir.c
@@ -0,0 +1,697 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "sort.h"
+#include "module-dir.h"
+
+#ifdef HAVE_MODULES
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <dlfcn.h>
+
+#ifndef RTLD_GLOBAL
+# define RTLD_GLOBAL 0
+#endif
+
+#ifndef RTLD_NOW
+# define RTLD_NOW 0
+#endif
+
+static const char *module_name_drop_suffix(const char *name);
+
+void *module_get_symbol_quiet(struct module *module, const char *symbol)
+{
+ /* clear out old errors */
+ (void)dlerror();
+
+ return dlsym(module->handle, symbol);
+}
+
+void *module_get_symbol(struct module *module, const char *symbol)
+{
+ const char *error;
+ void *ret;
+
+ ret = module_get_symbol_quiet(module, symbol);
+ if (ret == NULL) {
+ error = dlerror();
+ if (error != NULL) {
+ i_error("module %s: dlsym(%s) failed: %s",
+ module->path, symbol, error);
+ ret = NULL;
+ }
+ }
+ return ret;
+}
+
+static void *get_symbol(struct module *module, const char *symbol, bool quiet)
+{
+ if (quiet)
+ return module_get_symbol_quiet(module, symbol);
+
+ return module_get_symbol(module, symbol);
+}
+
+static void module_free(struct module *module)
+{
+ if (module->deinit != NULL && module->initialized)
+ module->deinit();
+ /* dlclose()ing removes all symbols from valgrind's visibility.
+ if GDB environment is set, don't actually unload the module
+ (the GDB environment is used elsewhere too) */
+ if (getenv("GDB") == NULL) {
+ if (dlclose(module->handle) != 0)
+ i_error("dlclose(%s) failed: %m", module->path);
+ }
+ i_free(module->path);
+ i_free(module->name);
+ i_free(module);
+}
+
+static bool
+module_check_wrong_binary_dependency(const struct module_dir_load_settings *set,
+ struct module *module, const char **error_r)
+{
+ const char *symbol_name, *binary_dep, *const *names;
+ string_t *errstr;
+
+ if (set->binary_name == NULL)
+ return TRUE;
+
+ symbol_name = t_strconcat(module->name, "_binary_dependency", NULL);
+ binary_dep = dlsym(module->handle, symbol_name);
+ if (binary_dep == NULL)
+ return TRUE;
+
+ names = t_strsplit(binary_dep, " ");
+ if (str_array_find(names, set->binary_name))
+ return TRUE;
+
+ errstr = t_str_new(128);
+ str_printfa(errstr, "Can't load plugin %s: "
+ "Plugin is intended to be used only by ", module->name);
+ if (names[1] == NULL)
+ str_printfa(errstr, "%s binary", binary_dep);
+ else
+ str_printfa(errstr, "binaries: %s", binary_dep);
+ str_printfa(errstr, " (we're %s)", set->binary_name);
+ *error_r = str_c(errstr);
+ return FALSE;
+}
+
+static bool
+module_check_missing_plugin_dependencies(const struct module_dir_load_settings *set,
+ struct module *module,
+ struct module *all_modules,
+ const char **error_r)
+{
+ const char **deps;
+ struct module *m;
+ string_t *errmsg;
+ size_t len;
+
+ deps = dlsym(module->handle,
+ t_strconcat(module->name, "_dependencies", NULL));
+ if (deps == NULL)
+ return TRUE;
+
+ for (; *deps != NULL; deps++) {
+ len = strlen(*deps);
+ for (m = all_modules; m != NULL; m = m->next) {
+ if (strncmp(m->name, *deps, len) == 0 &&
+ (m->name[len] == '\0' ||
+ strcmp(m->name+len, "_plugin") == 0))
+ break;
+ }
+ if (m == NULL) {
+ errmsg = t_str_new(128);
+ str_printfa(errmsg, "Plugin %s must be loaded also",
+ *deps);
+ if (set->setting_name != NULL) {
+ str_printfa(errmsg,
+ " (you must set: %s=$%s %s)",
+ set->setting_name,
+ set->setting_name, *deps);
+ }
+ *error_r = str_c(errmsg);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void *quiet_dlopen(const char *path, int flags)
+{
+#ifndef __OpenBSD__
+ return dlopen(path, flags);
+#else
+ void *handle;
+ int fd;
+
+ /* OpenBSD likes to print all "undefined symbol" errors to stderr.
+ Hide them by sending them to /dev/null. */
+ fd = dup(STDERR_FILENO);
+ if (fd == -1)
+ i_fatal("dup() failed: %m");
+ if (dup2(dev_null_fd, STDERR_FILENO) < 0)
+ i_fatal("dup2() failed: %m");
+ handle = dlopen(path, flags);
+ if (dup2(fd, STDERR_FILENO) < 0)
+ i_fatal("dup2() failed: %m");
+ if (close(fd) < 0)
+ i_error("close() failed: %m");
+ return handle;
+#endif
+}
+
+static bool versions_equal(const char *str1, const char *str2)
+{
+ while (*str1 == *str2) {
+ if (*str1 == '\0' || *str1 == '(')
+ return TRUE;
+ str1++;
+ str2++;
+ }
+ return FALSE;
+}
+
+static int
+module_load(const char *path, const char *name,
+ const struct module_dir_load_settings *set,
+ struct module *all_modules,
+ struct module **module_r, const char **error_r)
+{
+ void *handle;
+ struct module *module;
+ const char *const *module_version;
+ void (*preinit)(void);
+
+ *module_r = NULL;
+ *error_r = NULL;
+
+ if (set->ignore_dlopen_errors) {
+ handle = quiet_dlopen(path, RTLD_GLOBAL | RTLD_NOW);
+ if (handle == NULL) {
+ if (set->debug) {
+ i_debug("Skipping module %s, "
+ "because dlopen() failed: %s "
+ "(this is usually intentional, "
+ "so just ignore this message)",
+ name, dlerror());
+ }
+ return 0;
+ }
+ } else {
+ handle = dlopen(path, RTLD_GLOBAL | RTLD_NOW);
+ if (handle == NULL) {
+ *error_r = t_strdup_printf("dlopen() failed: %s",
+ dlerror());
+#ifdef RTLD_LAZY
+ /* try to give a better error message by lazily loading
+ the plugin and checking its dependencies */
+ handle = dlopen(path, RTLD_LAZY);
+ if (handle == NULL)
+ return -1;
+#else
+ return -1;
+#endif
+ }
+ }
+
+ module = i_new(struct module, 1);
+ module->path = i_strdup(path);
+ module->name = i_strdup(name);
+ module->handle = handle;
+
+ module_version = set->abi_version == NULL ? NULL :
+ get_symbol(module, t_strconcat(name, "_version", NULL), TRUE);
+ if (module_version != NULL &&
+ !versions_equal(*module_version, set->abi_version)) {
+ *error_r = t_strdup_printf(
+ "Module is for different ABI version %s (we have %s)",
+ *module_version, set->abi_version);
+ module_free(module);
+ return -1;
+ }
+
+ /* get our init func */
+ module->init = (void (*)(struct module *))
+ get_symbol(module, t_strconcat(name, "_init", NULL),
+ !set->require_init_funcs);
+ module->deinit = (void (*)(void))
+ get_symbol(module, t_strconcat(name, "_deinit", NULL),
+ !set->require_init_funcs);
+ preinit = (void (*)(void))
+ get_symbol(module, t_strconcat(name, "_preinit", NULL),
+ TRUE);
+ if (preinit != NULL)
+ preinit();
+
+ if ((module->init == NULL || module->deinit == NULL) &&
+ set->require_init_funcs) {
+ *error_r = t_strdup_printf(
+ "Module doesn't have %s function",
+ module->init == NULL ? "init" : "deinit");
+ } else if (!module_check_wrong_binary_dependency(set, module, error_r)) {
+ /* failed */
+ } else if (!module_check_missing_plugin_dependencies(set, module,
+ all_modules, error_r)) {
+ /* failed */
+ }
+
+ if (*error_r != NULL) {
+ module->deinit = NULL;
+ module_free(module);
+ return -1;
+ }
+
+ if (set->debug)
+ i_debug("Module loaded: %s", path);
+ *module_r = module;
+ return 1;
+}
+
+static int module_name_cmp(const char *const *n1, const char *const *n2)
+{
+ const char *s1 = *n1, *s2 = *n2;
+
+ if (str_begins(s1, "lib"))
+ s1 += 3;
+ if (str_begins(s2, "lib"))
+ s2 += 3;
+
+ return strcmp(s1, s2);
+}
+
+static bool module_want_load(const struct module_dir_load_settings *set,
+ const char **names, const char *name)
+{
+ if (set->filter_callback != NULL) {
+ if (!set->filter_callback(name, set->filter_context))
+ return FALSE;
+ }
+ if (names == NULL)
+ return TRUE;
+
+ for (; *names != NULL; names++) {
+ if (strcmp(*names, name) == 0) {
+ *names = "";
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void check_duplicates(ARRAY_TYPE(const_string) *names,
+ const char *name, const char *dir)
+{
+ const char *const *names_p, *base_name, *tmp;
+ unsigned int i, count;
+
+ base_name = module_file_get_name(name);
+ names_p = array_get(names, &count);
+ for (i = 0; i < count; i++) T_BEGIN {
+ tmp = module_file_get_name(names_p[i]);
+
+ if (strcmp(tmp, base_name) == 0)
+ i_fatal("Multiple files for module %s: %s/%s, %s/%s",
+ base_name, dir, name, dir, names_p[i]);
+ } T_END;
+}
+
+struct module *module_dir_find(struct module *modules, const char *name)
+{
+ struct module *module;
+ size_t len = strlen(name);
+
+ for (module = modules; module != NULL; module = module->next) {
+ if (strncmp(module->name, name, len) == 0) {
+ if (module->name[len] == '\0' ||
+ strcmp(module->name + len, "_plugin") == 0)
+ return module;
+ }
+ }
+ return NULL;
+}
+
+static bool module_is_loaded(struct module *modules, const char *name)
+{
+ return module_dir_find(modules, name) != NULL;
+}
+
+static void module_names_fix(const char **module_names)
+{
+ unsigned int i, j;
+
+ if (module_names[0] == NULL)
+ return;
+
+ /* allow giving the module names also in non-base form.
+ convert them in here. */
+ for (i = 0; module_names[i] != NULL; i++)
+ module_names[i] = module_file_get_name(module_names[i]);
+
+ /* @UNSAFE: drop duplicates */
+ i_qsort(module_names, i, sizeof(*module_names), i_strcmp_p);
+ for (i = j = 1; module_names[i] != NULL; i++) {
+ if (strcmp(module_names[i-1], module_names[i]) != 0)
+ module_names[j++] = module_names[i];
+ }
+ module_names[j] = NULL;
+}
+
+static bool
+module_dir_is_all_loaded(struct module *old_modules, const char **module_names)
+{
+ unsigned int i;
+
+ for (i = 0; module_names[i] != NULL; i++) {
+ if (!module_is_loaded(old_modules, module_names[i]))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+module_dir_load_real(struct module **_modules,
+ const char *dir, const char **module_names,
+ const struct module_dir_load_settings *set,
+ char **error_r)
+{
+ DIR *dirp;
+ struct dirent *d;
+ const char *name, *p, *error, *const *names_p;
+ struct module *modules, *module, **module_pos, *old_modules = *_modules;
+ unsigned int i, count;
+ ARRAY_TYPE(const_string) names;
+ pool_t pool;
+ int ret;
+
+ *error_r = NULL;
+
+ if (module_names != NULL) {
+ if (module_dir_is_all_loaded(old_modules, module_names))
+ return 0;
+ }
+
+ if (set->debug)
+ i_debug("Loading modules from directory: %s", dir);
+
+ dirp = opendir(dir);
+ if (dirp == NULL) {
+ *error_r = i_strdup_printf("opendir(%s) failed: %m", dir);
+ if (module_names != NULL) {
+ /* we were given a list of modules to load.
+ we can't fail. */
+ return -1;
+ }
+ return errno == ENOENT ? 0 : -1;
+ }
+
+ pool = pool_alloconly_create("module loader", 4096);
+ p_array_init(&names, pool, 32);
+
+ modules = NULL;
+ for (errno = 0; (d = readdir(dirp)) != NULL; errno = 0) {
+ name = d->d_name;
+
+ if (name[0] == '.')
+ continue;
+
+ p = strstr(name, MODULE_SUFFIX);
+ if (p == NULL || strlen(p) != 3)
+ continue;
+
+ T_BEGIN {
+ check_duplicates(&names, name, dir);
+ } T_END;
+
+ name = p_strdup(pool, d->d_name);
+ array_push_back(&names, &name);
+ }
+ if (errno != 0)
+ *error_r = i_strdup_printf("readdir(%s) failed: %m", dir);
+ if (closedir(dirp) < 0 && *error_r == NULL)
+ *error_r = i_strdup_printf("closedir(%s) failed: %m", dir);
+ if (*error_r != NULL) {
+ pool_unref(&pool);
+ return -1;
+ }
+
+ array_sort(&names, module_name_cmp);
+ names_p = array_get(&names, &count);
+
+ modules = old_modules;
+ module_pos = &modules;
+ while (*module_pos != NULL)
+ module_pos = &(*module_pos)->next;
+ for (i = 0; i < count; i++) T_BEGIN {
+ const char *path, *stripped_name, *suffixless_name;
+
+ name = names_p[i];
+ stripped_name = module_file_get_name(name);
+ suffixless_name = module_name_drop_suffix(stripped_name);
+ if (!module_want_load(set, module_names, suffixless_name) ||
+ module_is_loaded(old_modules, suffixless_name))
+ module = NULL;
+ else {
+ path = t_strconcat(dir, "/", name, NULL);
+ ret = module_load(path, stripped_name, set, modules, &module, &error);
+ if (ret >= 0)
+ ;
+ else if (module_names != NULL) {
+ *error_r = i_strdup_printf("Couldn't load required plugin %s: %s",
+ path, error);
+ i = count;
+ } else {
+ i_error("Couldn't load plugin %s: %s", path, error);
+ }
+ }
+
+ if (module != NULL) {
+ *module_pos = module;
+ module_pos = &module->next;
+ }
+ } T_END;
+ pool_unref(&pool);
+
+ if (module_names != NULL && *error_r == NULL && !set->ignore_missing) {
+ /* make sure all modules were found */
+ for (; *module_names != NULL; module_names++) {
+ if (**module_names != '\0') {
+ *error_r = i_strdup_printf("Plugin '%s' not found from directory %s",
+ *module_names, dir);
+ break;
+ }
+ }
+ }
+ *_modules = modules;
+ return *error_r != NULL ? -1 : 0;
+}
+
+int module_dir_try_load_missing(struct module **modules,
+ const char *dir, const char *module_names,
+ const struct module_dir_load_settings *set,
+ const char **error_r)
+{
+ char *error = NULL;
+ int ret;
+
+ T_BEGIN {
+ const char **arr = NULL;
+
+ if (module_names != NULL) {
+ arr = t_strsplit_spaces(module_names, ", ");
+ module_names_fix(arr);
+ }
+
+ ret = module_dir_load_real(modules, dir, arr, set, &error);
+ } T_END;
+ *error_r = t_strdup(error);
+ i_free(error);
+ return ret;
+}
+
+struct module *
+module_dir_load_missing(struct module *old_modules,
+ const char *dir, const char *module_names,
+ const struct module_dir_load_settings *set)
+{
+ struct module *new_modules = old_modules;
+ const char *error;
+
+ if (module_dir_try_load_missing(&new_modules, dir, module_names,
+ set, &error) < 0) {
+ if (module_names != NULL)
+ i_fatal("%s", error);
+ else
+ i_error("%s", error);
+ }
+ return new_modules;
+}
+
+void module_dir_init(struct module *modules)
+{
+ struct module *module;
+
+ for (module = modules; module != NULL; module = module->next) {
+ if (!module->initialized) {
+ module->initialized = TRUE;
+ if (module->init != NULL) T_BEGIN {
+ module->init(module);
+ } T_END;
+ }
+ }
+}
+
+void module_dir_deinit(struct module *modules)
+{
+ struct module *module, **rev;
+ unsigned int i, count = 0;
+
+ for (module = modules; module != NULL; module = module->next) {
+ if (module->deinit != NULL && module->initialized)
+ count++;
+ }
+
+ if (count == 0)
+ return;
+
+ /* @UNSAFE: deinitialize in reverse order */
+ T_BEGIN {
+ rev = t_new(struct module *, count);
+ for (i = 0, module = modules; i < count; ) {
+ if (module->deinit != NULL && module->initialized) {
+ rev[count-i-1] = module;
+ i++;
+ }
+ module = module->next;
+ }
+
+ for (i = 0; i < count; i++) {
+ module = rev[i];
+
+ T_BEGIN {
+ module->deinit();
+ } T_END;
+ module->initialized = FALSE;
+ }
+ } T_END;
+}
+
+void module_dir_unload(struct module **modules)
+{
+ struct module *module, *next;
+
+ /* Call all modules' deinit() first, so that they may still call each
+ others' functions. */
+ module_dir_deinit(*modules);
+
+ for (module = *modules; module != NULL; module = next) {
+ next = module->next;
+ module_free(module);
+ }
+
+ *modules = NULL;
+}
+
+#else
+
+#ifndef MODULE_SUFFIX
+# define MODULE_SUFFIX ".so" /* just to avoid build failure */
+#endif
+
+struct module *
+module_dir_load_missing(struct module *old_modules ATTR_UNUSED,
+ const char *dir ATTR_UNUSED,
+ const char *module_names,
+ const struct module_dir_load_settings *set ATTR_UNUSED)
+{
+#define NO_SUPPORT_ERRSTR "Dynamically loadable module support not built in"
+ if (module_names == NULL)
+ i_error(NO_SUPPORT_ERRSTR);
+ else {
+ i_fatal(NO_SUPPORT_ERRSTR", can't load plugins: %s",
+ module_names);
+ }
+ return NULL;
+}
+
+void module_dir_init(struct module *modules ATTR_UNUSED)
+{
+}
+
+void module_dir_deinit(struct module *modules ATTR_UNUSED)
+{
+}
+
+void module_dir_unload(struct module **modules ATTR_UNUSED)
+{
+}
+
+struct module *module_dir_find(struct module *modules ATTR_UNUSED,
+ const char *name ATTR_UNUSED)
+{
+ return NULL;
+}
+
+void *module_get_symbol(struct module *module ATTR_UNUSED,
+ const char *symbol ATTR_UNUSED)
+{
+ return NULL;
+}
+
+void *module_get_symbol_quiet(struct module *module ATTR_UNUSED,
+ const char *symbol ATTR_UNUSED)
+{
+ return NULL;
+}
+
+#endif
+
+struct module *module_dir_load(const char *dir, const char *module_names,
+ const struct module_dir_load_settings *set)
+{
+ return module_dir_load_missing(NULL, dir, module_names, set);
+}
+
+const char *module_file_get_name(const char *fname)
+{
+ const char *p;
+
+ /* [lib][nn_]name(.so) */
+ if (str_begins(fname, "lib"))
+ fname += 3;
+
+ for (p = fname; *p != '\0'; p++) {
+ if (*p < '0' || *p > '9')
+ break;
+ }
+ if (*p == '_')
+ fname = p + 1;
+
+ p = strstr(fname, MODULE_SUFFIX);
+ if (p == NULL)
+ return fname;
+
+ return t_strdup_until(fname, p);
+}
+
+static const char *module_name_drop_suffix(const char *name)
+{
+ size_t len;
+
+ len = strlen(name);
+ if (len > 7 && strcmp(name + len - 7, "_plugin") == 0)
+ name = t_strndup(name, len - 7);
+ return name;
+}
+
+const char *module_get_plugin_name(struct module *module)
+{
+ return module_name_drop_suffix(module->name);
+}