summaryrefslogtreecommitdiffstats
path: root/src/global/dynamicmaps.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:59:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:59:03 +0000
commita848231ae0f346dc7cc000973fbeb65b0894ee92 (patch)
tree44b60b367c86723cc78383ef247885d72b388afe /src/global/dynamicmaps.c
parentInitial commit. (diff)
downloadpostfix-a848231ae0f346dc7cc000973fbeb65b0894ee92.tar.xz
postfix-a848231ae0f346dc7cc000973fbeb65b0894ee92.zip
Adding upstream version 3.8.5.upstream/3.8.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/global/dynamicmaps.c')
-rw-r--r--src/global/dynamicmaps.c346
1 files changed, 346 insertions, 0 deletions
diff --git a/src/global/dynamicmaps.c b/src/global/dynamicmaps.c
new file mode 100644
index 0000000..0b66ab9
--- /dev/null
+++ b/src/global/dynamicmaps.c
@@ -0,0 +1,346 @@
+/*++
+/* 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 on-demand
+/* loads 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" wrapper function for bulk-mode
+/* dictionary creation. Plugins may be specified with a relative
+/* pathname.
+/*
+/* A dictionary shared object may be installed on a system
+/* 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
+/* must be in dynamicmaps.cf format, appends ".d" to the path
+/* and scans the named directory for other configuration files,
+/* and on the first call hooks itself into the dict_open() and
+/* dict_mapnames() functions. All files are optional, but if
+/* an existing file cannot be opened, that is a fatal error.
+/*
+/* 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. With ".d"
+/* appended, this becomes the pathname for a directory with
+/* other files in dynamicmaps.cf format.
+/* .IP plugin_dir
+/* Default directory for plugins with a relative pathname.
+/* SEE ALSO
+/* load_lib(3) low-level run-time linker adapter.
+/* DIAGNOSTICS
+/* Warnings: unsupported dictionary type, shared object file
+/* does not exist, shared object permissions are not safe-for-root,
+/* configuration file permissions are not safe-for-root.
+/* Fatal errors: memory allocation problem, error opening an
+/* existing configuration file, bad configuration file syntax.
+/* 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, 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 <mkmap.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 <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 DICT_MAPNAMES_EXTEND_FN saved_dict_mapnames_hook = 0;
+
+#define STREQ(x, y) (strcmp((x), (y)) == 0)
+
+
+/* dymap_dict_lookup - look up DICT_OPEN_INFO */
+
+static const DICT_OPEN_INFO *dymap_dict_lookup(const char *dict_type)
+{
+ const char myname[] = "dymap_dict_lookup";
+ struct stat st;
+ LIB_FN fn[3];
+ DYMAP_INFO *dp;
+ const DICT_OPEN_INFO *op;
+ DICT_OPEN_INFO *np;
+
+ if (msg_verbose > 1)
+ msg_info("%s: %s", myname, dict_type);
+
+ /*
+ * Respect the hook nesting order.
+ */
+ if (saved_dict_open_hook != 0
+ && (op = saved_dict_open_hook(dict_type)) != 0)
+ return (op);
+
+ /*
+ * 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) {
+ msg_warn("unsupported dictionary type: %s. "
+ "Is the postfix-%s package installed?",
+ dict_type, dict_type);
+ 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; /* not null */
+ fn[1].name = dp->mkmap_name; /* may be null */
+ fn[2].name = 0;
+ load_library_symbols(dp->soname, fn, (LIB_DP *) 0);
+ np = (DICT_OPEN_INFO *) mymalloc(sizeof(*op));
+ np->type = mystrdup(dict_type);
+ np->dict_fn = (DICT_OPEN_FN) fn[0].fptr;
+ np->mkmap_fn = (MKMAP_OPEN_FN) (dp->mkmap_name ? fn[1].fptr : 0);
+ return (np);
+}
+
+/* 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)
+{
+ const char myname[] = "dymap_read_conf";
+ 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 (msg_verbose > 1)
+ msg_info("%s: opening %s", myname, path);
+ 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);
+ if (msg_verbose > 1)
+ msg_info("%s: read: %s", myname, cp);
+ 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) infrastructure.
+ */
+ if (dymap_hooks_done == 0) {
+ dymap_hooks_done = 1;
+ saved_dict_open_hook = dict_open_extend(dymap_dict_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;
+
+ if (msg_verbose > 1)
+ msg_info("%s: %s %s", myname, conf_path, plugin_dir);
+
+ /*
+ * 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