1 /*        $NetBSD: maps.c,v 1.5 2025/02/25 19:15:45 christos Exp $    */
2 
3 /*++
4 /* NAME
5 /*        maps 3
6 /* SUMMARY
7 /*        multi-dictionary search
8 /* SYNOPSIS
9 /*        #include <maps.h>
10 /*
11 /*        MAPS      *maps_create(title, map_names, flags)
12 /*        const char *title;
13 /*        const char *map_names;
14 /*        int       flags;
15 /*
16 /*        const char *maps_find(maps, key, flags)
17 /*        MAPS      *maps;
18 /*        const char *key;
19 /*        int       flags;
20 /*
21 /*        const char *maps_file_find(maps, key, flags)
22 /*        MAPS      *maps;
23 /*        const char *key;
24 /*        int       flags;
25 /*
26 /*        MAPS      *maps_free(maps)
27 /*        MAPS      *maps;
28 /* DESCRIPTION
29 /*        This module implements multi-dictionary searches. it goes
30 /*        through the high-level dictionary interface and does file
31 /*        locking. Dictionaries are opened read-only, and in-memory
32 /*        dictionary instances are shared.
33 /*
34 /*        maps_create() takes list of type:name pairs and opens the
35 /*        named dictionaries.
36 /*        The result is a handle that must be specified along with all
37 /*        other maps_xxx() operations.
38 /*        See dict_open(3) for a description of flags.
39 /*        This includes the flags that specify preferences for search
40 /*        string case folding.
41 /*
42 /*        maps_find() searches the specified list of dictionaries
43 /*        in the specified order for the named key. The result is in
44 /*        memory that is overwritten upon each call.
45 /*        The flags argument is either 0 or specifies a filter:
46 /*        for example, DICT_FLAG_FIXED | DICT_FLAG_PATTERN selects
47 /*        dictionaries that have fixed keys or pattern keys.
48 /*
49 /*        maps_file_find() implements maps_find() but also decodes
50 /*        the base64 lookup result. This requires that the maps are
51 /*        opened with DICT_FLAG_SRC_RHS_IS_FILE.
52 /*
53 /*        maps_free() releases storage claimed by maps_create()
54 /*        and conveniently returns a null pointer.
55 /*
56 /*        Arguments:
57 /* .IP title
58 /*        String used for diagnostics. Typically one specifies the
59 /*        type of information stored in the lookup tables.
60 /* .IP map_names
61 /*        Null-terminated string with type:name dictionary specifications,
62 /*        separated by whitespace or commas.
63 /* .IP flags
64 /*        With maps_create(), flags that are passed to dict_open().
65 /*        With maps_find(), flags that control searching behavior
66 /*        as documented above.
67 /* .IP maps
68 /*        A result from maps_create().
69 /* .IP key
70 /*        Null-terminated string with a lookup key. Table lookup is case
71 /*        sensitive.
72 /* DIAGNOSTICS
73 /*        Panic: inappropriate use; fatal errors: out of memory, unable
74 /*        to open database. Warnings: null string lookup result.
75 /*
76 /*        maps_find() returns a null pointer when the requested
77 /*        information was not found, and logs a warning when the
78 /*        lookup failed due to error. The maps->error value indicates
79 /*        if the last lookup failed due to error.
80 /* BUGS
81 /*        The dictionary name space is flat, so dictionary names allocated
82 /*        by maps_create() may collide with dictionary names allocated by
83 /*        other methods.
84 /*
85 /*        This functionality could be implemented by allowing the user to
86 /*        specify dictionary search paths to dict_lookup() or dict_eval().
87 /*        However, that would either require that the dict(3) module adopts
88 /*        someone else's list notation syntax, or that the dict(3) module
89 /*        imposes syntax restrictions onto other software, neither of which
90 /*        is desirable.
91 /* LICENSE
92 /* .ad
93 /* .fi
94 /*        The Secure Mailer license must be distributed with this software.
95 /* AUTHOR(S)
96 /*        Wietse Venema
97 /*        IBM T.J. Watson Research
98 /*        P.O. Box 704
99 /*        Yorktown Heights, NY 10598, USA
100 /*
101 /*        Wietse Venema
102 /*        Google, Inc.
103 /*        111 8th Avenue
104 /*        New York, NY 10011, USA
105 /*--*/
106 
107 /* System library. */
108 
109 #include <sys_defs.h>
110 #include <string.h>
111 
112 /* Utility library. */
113 
114 #include <argv.h>
115 #include <mymalloc.h>
116 #include <msg.h>
117 #include <dict.h>
118 #include <stringops.h>
119 #include <split_at.h>
120 
121 /* Global library. */
122 
123 #include "mail_conf.h"
124 #include "maps.h"
125 
126 /* maps_create - initialize */
127 
maps_create(const char * title,const char * map_names,int dict_flags)128 MAPS   *maps_create(const char *title, const char *map_names, int dict_flags)
129 {
130     const char *myname = "maps_create";
131     char   *temp;
132     char   *bufp;
133     static char sep[] = CHARS_COMMA_SP;
134     static char parens[] = CHARS_BRACE;
135     MAPS   *maps;
136     char   *map_type_name;
137     VSTRING *map_type_name_flags;
138     DICT   *dict;
139 
140     /*
141      * Initialize.
142      */
143     maps = (MAPS *) mymalloc(sizeof(*maps));
144     maps->title = mystrdup(title);
145     maps->argv = argv_alloc(2);
146     maps->error = 0;
147 
148     /*
149      * For each specified type:name pair, either register a new dictionary,
150      * or increment the reference count of an existing one.
151      */
152     if (*map_names) {
153           bufp = temp = mystrdup(map_names);
154           map_type_name_flags = vstring_alloc(10);
155 
156 #define OPEN_FLAGS  O_RDONLY
157 
158           while ((map_type_name = mystrtokq(&bufp, sep, parens)) != 0) {
159               vstring_sprintf(map_type_name_flags, "%s(%o,%s)",
160                                   map_type_name, OPEN_FLAGS,
161                                   dict_flags_str(dict_flags));
162               if ((dict = dict_handle(vstring_str(map_type_name_flags))) == 0)
163                     dict = dict_open(map_type_name, OPEN_FLAGS, dict_flags);
164               if ((dict->flags & dict_flags) != dict_flags)
165                     msg_panic("%s: map %s has flags 0%o, want flags 0%o",
166                                 myname, map_type_name, dict->flags, dict_flags);
167               dict_register(vstring_str(map_type_name_flags), dict);
168               argv_add(maps->argv, vstring_str(map_type_name_flags), ARGV_END);
169           }
170           myfree(temp);
171           vstring_free(map_type_name_flags);
172     }
173     return (maps);
174 }
175 
176 /* maps_find - search a list of dictionaries */
177 
maps_find(MAPS * maps,const char * name,int flags)178 const char *maps_find(MAPS *maps, const char *name, int flags)
179 {
180     const char *myname = "maps_find";
181     char  **map_name;
182     const char *expansion;
183     DICT   *dict;
184 
185     /*
186      * In case of return without map lookup (empty name or no maps).
187      */
188     maps->error = 0;
189 
190     /*
191      * Temp. workaround, for buggy callers that pass zero-length keys when
192      * given partial addresses.
193      */
194     if (*name == 0)
195           return (0);
196 
197     for (map_name = maps->argv->argv; *map_name; map_name++) {
198           if ((dict = dict_handle(*map_name)) == 0)
199               msg_panic("%s: dictionary not found: %s", myname, *map_name);
200           if (flags != 0 && (dict->flags & flags) == 0) {
201               if (msg_verbose)
202                     msg_info("%s: %s: skipping %s lookup for %s",
203                                myname, maps->title, *map_name, name);
204               continue;
205           }
206           if ((expansion = dict_get(dict, name)) != 0) {
207               if (*expansion == 0) {
208                     msg_warn("%s lookup of %s returns an empty string result",
209                                maps->title, name);
210                     msg_warn("%s should return NO RESULT in case of NOT FOUND",
211                                maps->title);
212                     maps->error = DICT_ERR_CONFIG;
213                     return (0);
214               }
215               if (msg_verbose)
216                     msg_info("%s: %s: %s: %s = %.100s%s", myname, maps->title,
217                                *map_name, name, expansion,
218                                strlen(expansion) > 100 ? "..." : "");
219               return (expansion);
220           } else if ((maps->error = dict->error) != 0) {
221               msg_warn("%s:%s lookup error for \"%s\"",
222                          dict->type, dict->name, name);
223               break;
224           }
225     }
226     if (msg_verbose)
227           msg_info("%s: %s: %s: %s", myname, maps->title, name, maps->error ?
228                      "search aborted" : "not found");
229     return (0);
230 }
231 
232 /* maps_file_find - search a list of dictionaries and base64 decode */
233 
maps_file_find(MAPS * maps,const char * name,int flags)234 const char *maps_file_find(MAPS *maps, const char *name, int flags)
235 {
236     const char *myname = "maps_file_find";
237     char  **map_name;
238     const char *expansion;
239     DICT   *dict;
240     VSTRING *unb64;
241     char   *err;
242 
243     /*
244      * In case of return without map lookup (empty name or no maps).
245      */
246     maps->error = 0;
247 
248     /*
249      * Temp. workaround, for buggy callers that pass zero-length keys when
250      * given partial addresses.
251      */
252     if (*name == 0)
253           return (0);
254 
255     for (map_name = maps->argv->argv; *map_name; map_name++) {
256           if ((dict = dict_handle(*map_name)) == 0)
257               msg_panic("%s: dictionary not found: %s", myname, *map_name);
258           if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) == 0)
259               msg_panic("%s: %s: opened without DICT_FLAG_SRC_RHS_IS_FILE",
260                           myname, maps->title);
261           if (flags != 0 && (dict->flags & flags) == 0) {
262               if (msg_verbose)
263                     msg_info("%s: %s: skipping %s lookup for %s",
264                                myname, maps->title, *map_name, name);
265               continue;
266           }
267           if ((expansion = dict_get(dict, name)) != 0) {
268               if (*expansion == 0) {
269                     msg_warn("%s lookup of %s returns an empty string result",
270                                maps->title, name);
271                     msg_warn("%s should return NO RESULT in case of NOT FOUND",
272                                maps->title);
273                     maps->error = DICT_ERR_CONFIG;
274                     return (0);
275               }
276               if (msg_verbose)
277                     msg_info("%s: %s: %s: %s = %.100s%s", myname, maps->title,
278                                *map_name, name, expansion,
279                                strlen(expansion) > 100 ? "..." : "");
280               if ((unb64 = dict_file_from_b64(dict, expansion)) == 0) {
281                     err = dict_file_get_error(dict);
282                     msg_warn("table %s:%s: key %s: %s",
283                                dict->type, dict->name, name, err);
284                     myfree(err);
285                     maps->error = DICT_ERR_CONFIG;
286                     return (0);
287               }
288               return (vstring_str(unb64));
289           } else if ((maps->error = dict->error) != 0) {
290               msg_warn("%s:%s lookup error for \"%s\"",
291                          dict->type, dict->name, name);
292               break;
293           }
294     }
295     if (msg_verbose)
296           msg_info("%s: %s: %s: %s", myname, maps->title, name, maps->error ?
297                      "search aborted" : "not found");
298     return (0);
299 }
300 
301 /* maps_free - release storage */
302 
maps_free(MAPS * maps)303 MAPS   *maps_free(MAPS *maps)
304 {
305     char  **map_name;
306 
307     for (map_name = maps->argv->argv; *map_name; map_name++) {
308           if (msg_verbose)
309               msg_info("maps_free: %s", *map_name);
310           dict_unregister(*map_name);
311     }
312     myfree(maps->title);
313     argv_free(maps->argv);
314     myfree((void *) maps);
315     return (0);
316 }
317 
318 #ifdef TEST
319 
320 #include <vstring.h>
321 #include <vstream.h>
322 #include <vstring_vstream.h>
323 
main(int argc,char ** argv)324 int     main(int argc, char **argv)
325 {
326     VSTRING *buf = vstring_alloc(100);
327     MAPS   *maps;
328     const char *result;
329 
330     if (argc != 2)
331           msg_fatal("usage: %s maps", argv[0]);
332     msg_verbose = 2;
333     maps = maps_create("whatever", argv[1], DICT_FLAG_LOCK);
334 
335     while (vstring_fgets_nonl(buf, VSTREAM_IN)) {
336           maps->error = 99;
337           vstream_printf("\"%s\": ", vstring_str(buf));
338           if ((result = maps_find(maps, vstring_str(buf), 0)) != 0) {
339               vstream_printf("%s\n", result);
340           } else if (maps->error != 0) {
341               vstream_printf("lookup error\n");
342           } else {
343               vstream_printf("not found\n");
344           }
345           vstream_fflush(VSTREAM_OUT);
346     }
347     maps_free(maps);
348     vstring_free(buf);
349     return (0);
350 }
351 
352 #endif
353