/*++ /* 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 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 #include #include #include #include /* * Utility library. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Global library. */ #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 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