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