/*++ /* NAME /* dynamicmaps 3 /* SUMMARY /* load dictionaries dynamically /* SYNOPSIS /* #include /* /* 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 #include #include #include #include /* * Utility library. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * Global library. */ #include #include #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