summaryrefslogtreecommitdiffstats
path: root/src/global/dynamicmaps.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/dynamicmaps.c')
-rw-r--r--src/global/dynamicmaps.c367
1 files changed, 367 insertions, 0 deletions
diff --git a/src/global/dynamicmaps.c b/src/global/dynamicmaps.c
new file mode 100644
index 0000000..f0213d3
--- /dev/null
+++ b/src/global/dynamicmaps.c
@@ -0,0 +1,367 @@
+/*++
+/* NAME
+/* dynamicmaps 3
+/* SUMMARY
+/* load dictionaries dynamically
+/* SYNOPSIS
+/* #include <dynamicmaps.h>
+/*
+/* void dymap_init(const char *conf_path, const char *plugin_dir)
+/* DESCRIPTION
+/* This module reads the dynamicmaps.cf file and performs
+/* run-time loading of Postfix dictionaries. Each dynamicmaps.cf
+/* entry specifies the name of a dictionary type, the pathname
+/* of a shared-library object, the name of a "dict_open"
+/* function for access to individual dictionary entries, and
+/* optionally the name of a "mkmap_open" function for bulk-mode
+/* dictionary creation. Plugins may be specified with a relative
+/* pathname.
+/*
+/* A dictionary may be installed without editing the file
+/* dynamicmaps.cf, by placing a configuration file under the
+/* directory dynamicmaps.cf.d, with the same format as
+/* dynamicmaps.cf.
+/*
+/* dymap_init() reads the specified configuration file which
+/* is in dynamicmaps.cf format, and hooks itself into the
+/* dict_open(), dict_mapnames(), and mkmap_open() functions.
+/*
+/* dymap_init() may be called multiple times during a process
+/* lifetime, but it will not "unload" dictionaries that have
+/* already been linked into the process address space, nor
+/* will it hide their dictionaries types from later "open"
+/* requests.
+/*
+/* Arguments:
+/* .IP conf_path
+/* Pathname for the dynamicmaps configuration file.
+/* .IP plugin_dir
+/* Default directory for plugins with a relative pathname.
+/* SEE ALSO
+/* load_lib(3) low-level run-time linker adapter
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem, dictionary or
+/* dictionary function not available. Panic: invalid use.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* LaMont Jones
+/* Hewlett-Packard Company
+/* 3404 Harmony Road
+/* Fort Collins, CO 80528, USA
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <argv.h>
+#include <dict.h>
+#include <load_lib.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <scan_dir.h>
+
+ /*
+ * Global library.
+ */
+#include <mkmap.h>
+#include <dynamicmaps.h>
+
+#ifdef USE_DYNAMIC_MAPS
+
+ /*
+ * Contents of one dynamicmaps.cf entry.
+ */
+typedef struct {
+ char *soname; /* shared-object file name */
+ char *dict_name; /* dict_xx_open() function name */
+ char *mkmap_name; /* mkmap_xx_open() function name */
+} DYMAP_INFO;
+
+static HTABLE *dymap_info;
+static int dymap_hooks_done = 0;
+static DICT_OPEN_EXTEND_FN saved_dict_open_hook = 0;
+static MKMAP_OPEN_EXTEND_FN saved_mkmap_open_hook = 0;
+static DICT_MAPNAMES_EXTEND_FN saved_dict_mapnames_hook = 0;
+
+#define STREQ(x, y) (strcmp((x), (y)) == 0)
+
+/* dymap_dict_lookup - look up "dict_foo_open" function */
+
+static DICT_OPEN_FN dymap_dict_lookup(const char *dict_type)
+{
+ struct stat st;
+ LIB_FN fn[2];
+ DICT_OPEN_FN dict_open_fn;
+ DYMAP_INFO *dp;
+
+ /*
+ * Respect the hook nesting order.
+ */
+ if (saved_dict_open_hook != 0
+ && (dict_open_fn = saved_dict_open_hook(dict_type)) != 0)
+ return (dict_open_fn);
+
+ /*
+ * Allow for graceful degradation when a database is unavailable. This
+ * allows Postfix daemon processes to continue handling email with
+ * reduced functionality.
+ */
+ if ((dp = (DYMAP_INFO *) htable_find(dymap_info, dict_type)) == 0)
+ return (0);
+ if (stat(dp->soname, &st) < 0) {
+ msg_warn("unsupported dictionary type: %s (%s: %m)",
+ dict_type, dp->soname);
+ return (0);
+ }
+ if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
+ msg_warn("unsupported dictionary type: %s "
+ "(%s: file is owned or writable by non-root users)",
+ dict_type, dp->soname);
+ return (0);
+ }
+ fn[0].name = dp->dict_name;
+ fn[1].name = 0;
+ load_library_symbols(dp->soname, fn, (LIB_DP *) 0);
+ return ((DICT_OPEN_FN) fn[0].fptr);
+}
+
+/* dymap_mkmap_lookup - look up "mkmap_foo_open" function */
+
+static MKMAP_OPEN_FN dymap_mkmap_lookup(const char *dict_type)
+{
+ struct stat st;
+ LIB_FN fn[2];
+ MKMAP_OPEN_FN mkmap_open_fn;
+ DYMAP_INFO *dp;
+
+ /*
+ * Respect the hook nesting order.
+ */
+ if (saved_mkmap_open_hook != 0
+ && (mkmap_open_fn = saved_mkmap_open_hook(dict_type)) != 0)
+ return (mkmap_open_fn);
+
+ /*
+ * All errors are fatal. If the postmap(1) or postalias(1) command can't
+ * create the requested database, then graceful degradation is not
+ * useful.
+ *
+ * Fix 20220416: if this dictionary type is registered for some non-mkmap
+ * purpose, then don't talk nonsense about a missing package.
+ */
+ if ((dp = (DYMAP_INFO *) htable_find(dymap_info, dict_type)) == 0) {
+ ARGV *types = dict_mapnames();
+ char **cpp;
+
+ for (cpp = types->argv; *cpp; cpp++) {
+ if (strcmp(dict_type, *cpp) == 0)
+ msg_fatal("unsupported dictionary type: %s does not support "
+ "bulk-mode creation.", dict_type);
+ }
+ msg_fatal("unsupported dictionary type: %s. "
+ "Is the postfix-%s package installed?",
+ dict_type, dict_type);
+ }
+ if (!dp->mkmap_name)
+ msg_fatal("unsupported dictionary type: %s does not support "
+ "bulk-mode creation.", dict_type);
+ if (stat(dp->soname, &st) < 0)
+ msg_fatal("unsupported dictionary type: %s (%s: %m). "
+ "Is the postfix-%s package installed?",
+ dict_type, dp->soname, dict_type);
+ if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH)) != 0)
+ msg_fatal("unsupported dictionary type: %s "
+ "(%s: file is owned or writable by non-root users)",
+ dict_type, dp->soname);
+ fn[0].name = dp->mkmap_name;
+ fn[1].name = 0;
+ load_library_symbols(dp->soname, fn, (LIB_DP *) 0);
+ return ((MKMAP_OPEN_FN) fn[0].fptr);
+}
+
+/* dymap_list - enumerate dynamically-linked database type names */
+
+static void dymap_list(ARGV *map_names)
+{
+ HTABLE_INFO **ht_list, **ht;
+
+ /*
+ * Respect the hook nesting order.
+ */
+ if (saved_dict_mapnames_hook != 0)
+ saved_dict_mapnames_hook(map_names);
+
+ for (ht_list = ht = htable_list(dymap_info); *ht != 0; ht++)
+ argv_add(map_names, ht[0]->key, ARGV_END);
+ myfree((void *) ht_list);
+}
+
+/* dymap_entry_alloc - allocate dynamicmaps.cf entry */
+
+static DYMAP_INFO *dymap_entry_alloc(char **argv)
+{
+ DYMAP_INFO *dp;
+
+ dp = (DYMAP_INFO *) mymalloc(sizeof(*dp));
+ dp->soname = mystrdup(argv[0]);
+ dp->dict_name = mystrdup(argv[1]);
+ dp->mkmap_name = argv[2] ? mystrdup(argv[2]) : 0;
+ return (dp);
+}
+
+/* dymap_entry_free - htable(3) call-back to destroy dynamicmaps.cf entry */
+
+static void dymap_entry_free(void *ptr)
+{
+ DYMAP_INFO *dp = (DYMAP_INFO *) ptr;
+
+ myfree(dp->soname);
+ myfree(dp->dict_name);
+ if (dp->mkmap_name)
+ myfree(dp->mkmap_name);
+ myfree((void *) dp);
+}
+
+/* dymap_read_conf - read dynamicmaps.cf-like file */
+
+static void dymap_read_conf(const char *path, const char *path_base)
+{
+ VSTREAM *fp;
+ VSTRING *buf;
+ char *cp;
+ ARGV *argv;
+ int linenum = 0;
+ struct stat st;
+
+ /*
+ * Silently ignore a missing dynamicmaps.cf file, but be explicit about
+ * problems when the file does exist.
+ */
+ if ((fp = vstream_fopen(path, O_RDONLY, 0)) != 0) {
+ if (fstat(vstream_fileno(fp), &st) < 0)
+ msg_fatal("%s: fstat failed; %m", path);
+ if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
+ msg_warn("%s: file is owned or writable by non-root users"
+ " -- skipping this file", path);
+ } else {
+ buf = vstring_alloc(100);
+ while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) {
+ cp = vstring_str(buf);
+ linenum++;
+ if (*cp == '#' || *cp == '\0')
+ continue;
+ argv = argv_split(cp, " \t");
+ if (argv->argc != 3 && argv->argc != 4)
+ msg_fatal("%s, line %d: Expected \"dict-type .so-name dict"
+ "-function [mkmap-function]\"", path, linenum);
+ if (!ISALNUM(argv->argv[0][0]))
+ msg_fatal("%s, line %d: unsupported syntax \"%s\"",
+ path, linenum, argv->argv[0]);
+ if (argv->argv[1][0] != '/') {
+ cp = concatenate(path_base, "/", argv->argv[1], (char *) 0);
+ argv_replace_one(argv, 1, cp);
+ myfree(cp);
+ }
+ if (htable_locate(dymap_info, argv->argv[0]) != 0)
+ msg_warn("%s: ignoring duplicate entry for \"%s\"",
+ path, argv->argv[0]);
+ else
+ htable_enter(dymap_info, argv->argv[0],
+ (void *) dymap_entry_alloc(argv->argv + 1));
+ argv_free(argv);
+ }
+ vstring_free(buf);
+
+ /*
+ * Once-only: hook into the dict_open(3) and mkmap_open(3)
+ * infrastructure,
+ */
+ if (dymap_hooks_done == 0) {
+ dymap_hooks_done = 1;
+ saved_dict_open_hook = dict_open_extend(dymap_dict_lookup);
+ saved_mkmap_open_hook = mkmap_open_extend(dymap_mkmap_lookup);
+ saved_dict_mapnames_hook = dict_mapnames_extend(dymap_list);
+ }
+ }
+ vstream_fclose(fp);
+ } else if (errno != ENOENT) {
+ msg_fatal("%s: file open failed: %m", path);
+ }
+}
+
+/* dymap_init - initialize dictionary type to soname etc. mapping */
+
+void dymap_init(const char *conf_path, const char *plugin_dir)
+{
+ static const char myname[] = "dymap_init";
+ SCAN_DIR *dir;
+ char *conf_path_d;
+ const char *conf_name;
+ VSTRING *sub_conf_path;
+
+ /*
+ * Reload dynamicmaps.cf, but don't reload already-loaded plugins.
+ */
+ if (dymap_info != 0)
+ htable_free(dymap_info, dymap_entry_free);
+ dymap_info = htable_create(3);
+
+ /*
+ * Read dynamicmaps.cf.
+ */
+ dymap_read_conf(conf_path, plugin_dir);
+
+ /*
+ * Read dynamicmaps.cf.d/filename entries.
+ */
+ conf_path_d = concatenate(conf_path, ".d", (char *) 0);
+ if (access(conf_path_d, R_OK | X_OK) == 0
+ && (dir = scan_dir_open(conf_path_d)) != 0) {
+ sub_conf_path = vstring_alloc(100);
+ while ((conf_name = scan_dir_next(dir)) != 0) {
+ vstring_sprintf(sub_conf_path, "%s/%s", conf_path_d, conf_name);
+ dymap_read_conf(vstring_str(sub_conf_path), plugin_dir);
+ }
+ if (errno != 0)
+ /* Don't crash all programs - degrade gracefully. */
+ msg_warn("%s: directory read error: %m", conf_path_d);
+ scan_dir_close(dir);
+ vstring_free(sub_conf_path);
+ } else if (errno != ENOENT) {
+ /* Don't crash all programs - degrade gracefully. */
+ msg_warn("%s: directory open failed: %m", conf_path_d);
+ }
+ myfree(conf_path_d);
+
+ /*
+ * Future proofing, in case someone "improves" the code. We can't hook
+ * into other functions without initializing our private lookup table.
+ */
+ if (dymap_hooks_done != 0 && dymap_info == 0)
+ msg_panic("%s: post-condition botch", myname);
+}
+
+#endif