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