1 /* $NetBSD: dynamicmaps.c,v 1.2 2017/02/14 01:16:45 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dynamicmaps 3 6 /* SUMMARY 7 /* load dictionaries dynamically 8 /* SYNOPSIS 9 /* #include <dynamicmaps.h> 10 /* 11 /* void dymap_init(const char *conf_path, const char *plugin_dir) 12 /* DESCRIPTION 13 /* This module reads the dynamicmaps.cf file and performs 14 /* run-time loading of Postfix dictionaries. Each dynamicmaps.cf 15 /* entry specifies the name of a dictionary type, the pathname 16 /* of a shared-library object, the name of a "dict_open" 17 /* function for access to individual dictionary entries, and 18 /* optionally the name of a "mkmap_open" function for bulk-mode 19 /* dictionary creation. Plugins may be specified with a relative 20 /* pathname. 21 /* 22 /* A dictionary may be installed without editing the file 23 /* dynamicmaps.cf, by placing a configuration file under the 24 /* directory dynamicmaps.cf.d, with the same format as 25 /* dynamicmaps.cf. 26 /* 27 /* dymap_init() reads the specified configuration file which 28 /* is in dynamicmaps.cf format, and hooks itself into the 29 /* dict_open(), dict_mapnames(), and mkmap_open() functions. 30 /* 31 /* dymap_init() may be called multiple times during a process 32 /* lifetime, but it will not "unload" dictionaries that have 33 /* already been linked into the process address space, nor 34 /* will it hide their dictionaries types from later "open" 35 /* requests. 36 /* 37 /* Arguments: 38 /* .IP conf_path 39 /* Pathname for the dynamicmaps configuration file. 40 /* .IP plugin_dir 41 /* Default directory for plugins with a relative pathname. 42 /* SEE ALSO 43 /* load_lib(3) low-level run-time linker adapter 44 /* DIAGNOSTICS 45 /* Fatal errors: memory allocation problem, dictionary or 46 /* dictionary function not available. Panic: invalid use. 47 /* LICENSE 48 /* .ad 49 /* .fi 50 /* The Secure Mailer license must be distributed with this software. 51 /* AUTHOR(S) 52 /* LaMont Jones 53 /* Hewlett-Packard Company 54 /* 3404 Harmony Road 55 /* Fort Collins, CO 80528, USA 56 /* 57 /* Wietse Venema 58 /* IBM T.J. Watson Research 59 /* P.O. Box 704 60 /* Yorktown Heights, NY 10598, USA 61 /*--*/ 62 63 /* 64 * System library. 65 */ 66 #include <sys_defs.h> 67 #include <sys/stat.h> 68 #include <errno.h> 69 #include <string.h> 70 #include <ctype.h> 71 72 /* 73 * Utility library. 74 */ 75 #include <msg.h> 76 #include <mymalloc.h> 77 #include <htable.h> 78 #include <argv.h> 79 #include <dict.h> 80 #include <load_lib.h> 81 #include <vstring.h> 82 #include <vstream.h> 83 #include <vstring_vstream.h> 84 #include <stringops.h> 85 #include <split_at.h> 86 #include <scan_dir.h> 87 88 /* 89 * Global library. 90 */ 91 #include <mkmap.h> 92 #include <dynamicmaps.h> 93 94 #ifdef USE_DYNAMIC_MAPS 95 96 /* 97 * Contents of one dynamicmaps.cf entry. 98 */ 99 typedef struct { 100 char *soname; /* shared-object file name */ 101 char *dict_name; /* dict_xx_open() function name */ 102 char *mkmap_name; /* mkmap_xx_open() function name */ 103 } DYMAP_INFO; 104 105 static HTABLE *dymap_info; 106 static int dymap_hooks_done = 0; 107 static DICT_OPEN_EXTEND_FN saved_dict_open_hook = 0; 108 static MKMAP_OPEN_EXTEND_FN saved_mkmap_open_hook = 0; 109 static DICT_MAPNAMES_EXTEND_FN saved_dict_mapnames_hook = 0; 110 111 #define STREQ(x, y) (strcmp((x), (y)) == 0) 112 113 /* dymap_dict_lookup - look up "dict_foo_open" function */ 114 115 static DICT_OPEN_FN dymap_dict_lookup(const char *dict_type) 116 { 117 struct stat st; 118 LIB_FN fn[2]; 119 DICT_OPEN_FN dict_open_fn; 120 DYMAP_INFO *dp; 121 122 /* 123 * Respect the hook nesting order. 124 */ 125 if (saved_dict_open_hook != 0 126 && (dict_open_fn = saved_dict_open_hook(dict_type)) != 0) 127 return (dict_open_fn); 128 129 /* 130 * Allow for graceful degradation when a database is unavailable. This 131 * allows Postfix daemon processes to continue handling email with 132 * reduced functionality. 133 */ 134 if ((dp = (DYMAP_INFO *) htable_find(dymap_info, dict_type)) == 0) 135 return (0); 136 if (stat(dp->soname, &st) < 0) { 137 msg_warn("unsupported dictionary type: %s (%s: %m)", 138 dict_type, dp->soname); 139 return (0); 140 } 141 if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH)) != 0) { 142 msg_warn("unsupported dictionary type: %s " 143 "(%s: file is owned or writable by non-root users)", 144 dict_type, dp->soname); 145 return (0); 146 } 147 fn[0].name = dp->dict_name; 148 fn[1].name = 0; 149 load_library_symbols(dp->soname, fn, (LIB_DP *) 0); 150 return ((DICT_OPEN_FN) fn[0].fptr); 151 } 152 153 /* dymap_mkmap_lookup - look up "mkmap_foo_open" function */ 154 155 static MKMAP_OPEN_FN dymap_mkmap_lookup(const char *dict_type) 156 { 157 struct stat st; 158 LIB_FN fn[2]; 159 MKMAP_OPEN_FN mkmap_open_fn; 160 DYMAP_INFO *dp; 161 162 /* 163 * Respect the hook nesting order. 164 */ 165 if (saved_mkmap_open_hook != 0 166 && (mkmap_open_fn = saved_mkmap_open_hook(dict_type)) != 0) 167 return (mkmap_open_fn); 168 169 /* 170 * All errors are fatal. If the postmap(1) or postalias(1) command can't 171 * create the requested database, then graceful degradation is not 172 * useful. 173 */ 174 if ((dp = (DYMAP_INFO *) htable_find(dymap_info, dict_type)) == 0) 175 msg_fatal("unsupported dictionary type: %s. " 176 "Is the postfix-%s package installed?", 177 dict_type, dict_type); 178 if (!dp->mkmap_name) 179 msg_fatal("unsupported dictionary type: %s does not support " 180 "bulk-mode creation.", dict_type); 181 if (stat(dp->soname, &st) < 0) 182 msg_fatal("unsupported dictionary type: %s (%s: %m). " 183 "Is the postfix-%s package installed?", 184 dict_type, dp->soname, dict_type); 185 if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH)) != 0) 186 msg_fatal("unsupported dictionary type: %s " 187 "(%s: file is owned or writable by non-root users)", 188 dict_type, dp->soname); 189 fn[0].name = dp->mkmap_name; 190 fn[1].name = 0; 191 load_library_symbols(dp->soname, fn, (LIB_DP *) 0); 192 return ((MKMAP_OPEN_FN) fn[0].fptr); 193 } 194 195 /* dymap_list - enumerate dynamically-linked database type names */ 196 197 static void dymap_list(ARGV *map_names) 198 { 199 HTABLE_INFO **ht_list, **ht; 200 201 /* 202 * Respect the hook nesting order. 203 */ 204 if (saved_dict_mapnames_hook != 0) 205 saved_dict_mapnames_hook(map_names); 206 207 for (ht_list = ht = htable_list(dymap_info); *ht != 0; ht++) 208 argv_add(map_names, ht[0]->key, ARGV_END); 209 myfree((void *) ht_list); 210 } 211 212 /* dymap_entry_alloc - allocate dynamicmaps.cf entry */ 213 214 static DYMAP_INFO *dymap_entry_alloc(char **argv) 215 { 216 DYMAP_INFO *dp; 217 218 dp = (DYMAP_INFO *) mymalloc(sizeof(*dp)); 219 dp->soname = mystrdup(argv[0]); 220 dp->dict_name = mystrdup(argv[1]); 221 dp->mkmap_name = argv[2] ? mystrdup(argv[2]) : 0; 222 return (dp); 223 } 224 225 /* dymap_entry_free - htable(3) call-back to destroy dynamicmaps.cf entry */ 226 227 static void dymap_entry_free(void *ptr) 228 { 229 DYMAP_INFO *dp = (DYMAP_INFO *) ptr; 230 231 myfree(dp->soname); 232 myfree(dp->dict_name); 233 if (dp->mkmap_name) 234 myfree(dp->mkmap_name); 235 myfree((void *) dp); 236 } 237 238 /* dymap_read_conf - read dynamicmaps.cf-like file */ 239 240 static void dymap_read_conf(const char *path, const char *path_base) 241 { 242 VSTREAM *fp; 243 VSTRING *buf; 244 char *cp; 245 ARGV *argv; 246 int linenum = 0; 247 struct stat st; 248 249 /* 250 * Silently ignore a missing dynamicmaps.cf file, but be explicit about 251 * problems when the file does exist. 252 */ 253 if ((fp = vstream_fopen(path, O_RDONLY, 0)) != 0) { 254 if (fstat(vstream_fileno(fp), &st) < 0) 255 msg_fatal("%s: fstat failed; %m", path); 256 if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH)) != 0) { 257 msg_warn("%s: file is owned or writable by non-root users" 258 " -- skipping this file", path); 259 } else { 260 buf = vstring_alloc(100); 261 while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) { 262 cp = vstring_str(buf); 263 linenum++; 264 if (*cp == '#' || *cp == '\0') 265 continue; 266 argv = argv_split(cp, " \t"); 267 if (argv->argc != 3 && argv->argc != 4) 268 msg_fatal("%s, line %d: Expected \"dict-type .so-name dict" 269 "-function [mkmap-function]\"", path, linenum); 270 if (!ISALNUM(argv->argv[0][0])) 271 msg_fatal("%s, line %d: unsupported syntax \"%s\"", 272 path, linenum, argv->argv[0]); 273 if (argv->argv[1][0] != '/') { 274 cp = concatenate(path_base, "/", argv->argv[1], (char *) 0); 275 argv_replace_one(argv, 1, cp); 276 myfree(cp); 277 } 278 if (htable_locate(dymap_info, argv->argv[0]) != 0) 279 msg_warn("%s: ignoring duplicate entry for \"%s\"", 280 path, argv->argv[0]); 281 else 282 htable_enter(dymap_info, argv->argv[0], 283 (void *) dymap_entry_alloc(argv->argv + 1)); 284 argv_free(argv); 285 } 286 vstring_free(buf); 287 288 /* 289 * Once-only: hook into the dict_open(3) and mkmap_open(3) 290 * infrastructure, 291 */ 292 if (dymap_hooks_done == 0) { 293 dymap_hooks_done = 1; 294 saved_dict_open_hook = dict_open_extend(dymap_dict_lookup); 295 saved_mkmap_open_hook = mkmap_open_extend(dymap_mkmap_lookup); 296 saved_dict_mapnames_hook = dict_mapnames_extend(dymap_list); 297 } 298 } 299 vstream_fclose(fp); 300 } else if (errno != ENOENT) { 301 msg_fatal("%s: file open failed: %m", path); 302 } 303 } 304 305 /* dymap_init - initialize dictionary type to soname etc. mapping */ 306 307 void dymap_init(const char *conf_path, const char *plugin_dir) 308 { 309 static const char myname[] = "dymap_init"; 310 SCAN_DIR *dir; 311 char *conf_path_d; 312 const char *conf_name; 313 VSTRING *sub_conf_path; 314 315 /* 316 * Reload dynamicsmaps.cf, but don't reload already-loaded plugins. 317 */ 318 if (dymap_info != 0) 319 htable_free(dymap_info, dymap_entry_free); 320 dymap_info = htable_create(3); 321 322 /* 323 * Read dynamicmaps.cf. 324 */ 325 dymap_read_conf(conf_path, plugin_dir); 326 327 /* 328 * Read dynamicmaps.cf.d/filename entries. 329 */ 330 conf_path_d = concatenate(conf_path, ".d", (char *) 0); 331 if (access(conf_path_d, R_OK | X_OK) == 0 332 && (dir = scan_dir_open(conf_path_d)) != 0) { 333 sub_conf_path = vstring_alloc(100); 334 while ((conf_name = scan_dir_next(dir)) != 0) { 335 vstring_sprintf(sub_conf_path, "%s/%s", conf_path_d, conf_name); 336 dymap_read_conf(vstring_str(sub_conf_path), plugin_dir); 337 } 338 if (errno != 0) 339 /* Don't crash all programs - degrade gracefully. */ 340 msg_warn("%s: directory read error: %m", conf_path_d); 341 scan_dir_close(dir); 342 vstring_free(sub_conf_path); 343 } else if (errno != ENOENT) { 344 /* Don't crash all programs - degrade gracefully. */ 345 msg_warn("%s: directory open failed: %m", conf_path_d); 346 } 347 myfree(conf_path_d); 348 349 /* 350 * Future proofing, in case someone "improves" the code. We can't hook 351 * into other functions without initializing our private lookup table. 352 */ 353 if (dymap_hooks_done != 0 && dymap_info == 0) 354 msg_panic("%s: post-condition botch", myname); 355 } 356 357 #endif 358