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