Postfix3.3.1
dynamicmaps.c
[詳解]
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 
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 
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 
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 
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 
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 
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 
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
void htable_free(HTABLE *table, void(*free_fn)(void *))
Definition: htable.c:287
#define VSTREAM_EOF
Definition: vstream.h:110
void myfree(void *ptr)
Definition: mymalloc.c:207
HTABLE_INFO * htable_locate(HTABLE *table, const char *key)
Definition: htable.c:242
#define ARGV_END
Definition: argv.h:52
char * mystrdup(const char *str)
Definition: mymalloc.c:225
int vstring_get_nonl(VSTRING *vp, VSTREAM *fp)
ARGV * argv_free(ARGV *argvp)
Definition: argv.c:136
Definition: argv.h:17
NORETURN msg_panic(const char *fmt,...)
Definition: msg.c:295
#define vstring_str(vp)
Definition: vstring.h:71
void argv_replace_one(ARGV *argvp, ssize_t where, const char *arg)
Definition: argv.c:293
#define stat(p, s)
Definition: warn_stat.h:18
void load_library_symbols(const char *, LIB_FN *, LIB_DP *)
char ** argv
Definition: argv.h:20
void argv_add(ARGV *argvp,...)
Definition: argv.c:197
DICT_OPEN_FN(* DICT_OPEN_EXTEND_FN)(const char *)
Definition: dict.h:230
Definition: htable.h:25
VSTREAM * vstream_fopen(const char *path, int flags, mode_t mode)
Definition: vstream.c:1241
#define ISALNUM(c)
Definition: sys_defs.h:1745
SCAN_DIR * scan_dir_open(const char *path)
Definition: scan_dir.c:165
HTABLE * htable_create(ssize_t size)
Definition: htable.c:179
DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN)
Definition: dict_open.c:552
HTABLE_INFO ** htable_list(HTABLE *table)
Definition: htable.c:330
int vstream_fclose(VSTREAM *stream)
Definition: vstream.c:1268
SCAN_DIR * scan_dir_close(SCAN_DIR *scan)
Definition: scan_dir.c:210
void msg_warn(const char *fmt,...)
Definition: msg.c:215
VSTRING * vstring_alloc(ssize_t len)
Definition: vstring.c:353
DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN)
Definition: dict_open.c:509
void * htable_find(HTABLE *table, const char *key)
Definition: htable.c:227
VSTRING * vstring_sprintf(VSTRING *vp, const char *format,...)
Definition: vstring.c:602
MKMAP_OPEN_FN(* MKMAP_OPEN_EXTEND_FN)(const char *)
Definition: mkmap.h:49
NORETURN msg_fatal(const char *fmt,...)
Definition: msg.c:249
void(* DICT_MAPNAMES_EXTEND_FN)(ARGV *)
Definition: dict.h:207
char * concatenate(const char *arg0,...)
Definition: concatenate.c:42
ARGV * argv_split(const char *, const char *)
Definition: argv_split.c:63
DICT *(* DICT_OPEN_FN)(const char *, int, int)
Definition: dict.h:229
VSTRING * vstring_free(VSTRING *vp)
Definition: vstring.c:380
#define vstream_fileno(vp)
Definition: vstream.h:115
ssize_t argc
Definition: argv.h:19
MKMAP *(* MKMAP_OPEN_FN)(const char *)
Definition: mkmap.h:48
MKMAP_OPEN_EXTEND_FN mkmap_open_extend(MKMAP_OPEN_EXTEND_FN)
Definition: mkmap_open.c:182
char * scan_dir_next(SCAN_DIR *scan)
Definition: scan_dir.c:177
#define fstat(f, s)
Definition: warn_stat.h:20
void * mymalloc(ssize_t len)
Definition: mymalloc.c:150
HTABLE_INFO * htable_enter(HTABLE *table, const char *key, void *value)
Definition: htable.c:212