diff options
Diffstat (limited to 'src/global/dynamicmaps.c')
-rw-r--r-- | src/global/dynamicmaps.c | 367 |
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..c624a3e --- /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 dynamicsmaps.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 |