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