xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/dynamicmaps.c (revision 122b5006ee1bd67145794b4cde92f4fe4781a5ec)
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