Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: maps.c,v 1.6 2026/05/09 18:49:16 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 
    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     DICT   *dict;
    138 
    139     /*
    140      * Initialize.
    141      */
    142     maps = (MAPS *) mymalloc(sizeof(*maps));
    143     maps->title = mystrdup(title);
    144     maps->argv = argv_alloc(2);
    145     maps->error = 0;
    146 
    147     /*
    148      * For each specified type:name pair, either register a new dictionary,
    149      * or increment the reference count of an existing one.
    150      */
    151     if (*map_names) {
    152 	bufp = temp = mystrdup(map_names);
    153 
    154 #define OPEN_FLAGS	O_RDONLY
    155 
    156 	while ((map_type_name = mystrtokq(&bufp, sep, parens)) != 0) {
    157 	    dict = dict_open(map_type_name, OPEN_FLAGS, dict_flags);
    158 	    if ((dict->flags & dict_flags) != dict_flags)
    159 		msg_panic("%s: map %s has flags 0%o, want flags 0%o",
    160 			  myname, map_type_name, dict->flags, dict_flags);
    161 	    argv_add(maps->argv, dict->reg_name, ARGV_END);
    162 	}
    163 	myfree(temp);
    164     }
    165     return (maps);
    166 }
    167 
    168 /* maps_find - search a list of dictionaries */
    169 
    170 const char *maps_find(MAPS *maps, const char *name, int flags)
    171 {
    172     const char *myname = "maps_find";
    173     char  **map_name;
    174     const char *expansion;
    175     DICT   *dict;
    176 
    177     /*
    178      * In case of return without map lookup (empty name or no maps).
    179      */
    180     maps->error = 0;
    181 
    182     /*
    183      * Temp. workaround, for buggy callers that pass zero-length keys when
    184      * given partial addresses.
    185      */
    186     if (*name == 0)
    187 	return (0);
    188 
    189     for (map_name = maps->argv->argv; *map_name; map_name++) {
    190 	if ((dict = dict_handle(*map_name)) == 0)
    191 	    msg_panic("%s: dictionary not found: %s", myname, *map_name);
    192 	if (flags != 0 && (dict->flags & flags) == 0) {
    193 	    if (msg_verbose)
    194 		msg_info("%s: %s: skipping %s lookup for %s",
    195 			 myname, maps->title, *map_name, name);
    196 	    continue;
    197 	}
    198 	if ((expansion = dict_get(dict, name)) != 0) {
    199 	    if (*expansion == 0) {
    200 		msg_warn("%s lookup of %s returns an empty string result",
    201 			 maps->title, name);
    202 		msg_warn("%s should return NO RESULT in case of NOT FOUND",
    203 			 maps->title);
    204 		maps->error = DICT_ERR_CONFIG;
    205 		return (0);
    206 	    }
    207 	    if (msg_verbose)
    208 		msg_info("%s: %s: %s: %s = %.100s%s", myname, maps->title,
    209 			 *map_name, name, expansion,
    210 			 strlen(expansion) > 100 ? "..." : "");
    211 	    return (expansion);
    212 	} else if ((maps->error = dict->error) != 0) {
    213 	    msg_warn("%s:%s lookup error for \"%s\"",
    214 		     dict->type, dict->name, name);
    215 	    break;
    216 	}
    217     }
    218     if (msg_verbose)
    219 	msg_info("%s: %s: %s: %s", myname, maps->title, name, maps->error ?
    220 		 "search aborted" : "not found");
    221     return (0);
    222 }
    223 
    224 /* maps_file_find - search a list of dictionaries and base64 decode */
    225 
    226 const char *maps_file_find(MAPS *maps, const char *name, int flags)
    227 {
    228     const char *myname = "maps_file_find";
    229     char  **map_name;
    230     const char *expansion;
    231     DICT   *dict;
    232     VSTRING *unb64;
    233     char   *err;
    234 
    235     /*
    236      * In case of return without map lookup (empty name or no maps).
    237      */
    238     maps->error = 0;
    239 
    240     /*
    241      * Temp. workaround, for buggy callers that pass zero-length keys when
    242      * given partial addresses.
    243      */
    244     if (*name == 0)
    245 	return (0);
    246 
    247     for (map_name = maps->argv->argv; *map_name; map_name++) {
    248 	if ((dict = dict_handle(*map_name)) == 0)
    249 	    msg_panic("%s: dictionary not found: %s", myname, *map_name);
    250 	if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) == 0)
    251 	    msg_panic("%s: %s: opened without DICT_FLAG_SRC_RHS_IS_FILE",
    252 		      myname, maps->title);
    253 	if (flags != 0 && (dict->flags & flags) == 0) {
    254 	    if (msg_verbose)
    255 		msg_info("%s: %s: skipping %s lookup for %s",
    256 			 myname, maps->title, *map_name, name);
    257 	    continue;
    258 	}
    259 	if ((expansion = dict_get(dict, name)) != 0) {
    260 	    if (*expansion == 0) {
    261 		msg_warn("%s lookup of %s returns an empty string result",
    262 			 maps->title, name);
    263 		msg_warn("%s should return NO RESULT in case of NOT FOUND",
    264 			 maps->title);
    265 		maps->error = DICT_ERR_CONFIG;
    266 		return (0);
    267 	    }
    268 	    if (msg_verbose)
    269 		msg_info("%s: %s: %s: %s = %.100s%s", myname, maps->title,
    270 			 *map_name, name, expansion,
    271 			 strlen(expansion) > 100 ? "..." : "");
    272 	    if ((unb64 = dict_file_from_b64(dict, expansion)) == 0) {
    273 		err = dict_file_get_error(dict);
    274 		msg_warn("table %s:%s: key %s: %s",
    275 			 dict->type, dict->name, name, err);
    276 		myfree(err);
    277 		maps->error = DICT_ERR_CONFIG;
    278 		return (0);
    279 	    }
    280 	    return (vstring_str(unb64));
    281 	} else if ((maps->error = dict->error) != 0) {
    282 	    msg_warn("%s:%s lookup error for \"%s\"",
    283 		     dict->type, dict->name, name);
    284 	    break;
    285 	}
    286     }
    287     if (msg_verbose)
    288 	msg_info("%s: %s: %s: %s", myname, maps->title, name, maps->error ?
    289 		 "search aborted" : "not found");
    290     return (0);
    291 }
    292 
    293 /* maps_free - release storage */
    294 
    295 MAPS   *maps_free(MAPS *maps)
    296 {
    297     const char *myname = "maps_free";
    298     DICT   *dict;
    299     char  **map_name;
    300 
    301     for (map_name = maps->argv->argv; *map_name; map_name++) {
    302 	if (msg_verbose)
    303 	    msg_info("maps_free: %s", *map_name);
    304 	if ((dict = dict_handle(*map_name)) == 0)
    305 	    msg_panic("%s: dictionary not found: %s", myname, *map_name);
    306 	dict_close(dict);
    307     }
    308     myfree(maps->title);
    309     argv_free(maps->argv);
    310     myfree((void *) maps);
    311     return (0);
    312 }
    313 
    314 #ifdef TEST
    315 
    316 #include <vstring.h>
    317 #include <vstream.h>
    318 #include <vstring_vstream.h>
    319 
    320 int     main(int argc, char **argv)
    321 {
    322     VSTRING *buf = vstring_alloc(100);
    323     MAPS   *maps;
    324     const char *result;
    325 
    326     if (argc != 2)
    327 	msg_fatal("usage: %s maps", argv[0]);
    328     msg_verbose = 2;
    329     maps = maps_create("whatever", argv[1], DICT_FLAG_LOCK);
    330 
    331     while (vstring_fgets_nonl(buf, VSTREAM_IN)) {
    332 	maps->error = 99;
    333 	vstream_printf("\"%s\": ", vstring_str(buf));
    334 	if ((result = maps_find(maps, vstring_str(buf), 0)) != 0) {
    335 	    vstream_printf("%s\n", result);
    336 	} else if (maps->error != 0) {
    337 	    vstream_printf("lookup error\n");
    338 	} else {
    339 	    vstream_printf("not found\n");
    340 	}
    341 	vstream_fflush(VSTREAM_OUT);
    342     }
    343     maps_free(maps);
    344     vstring_free(buf);
    345     return (0);
    346 }
    347 
    348 #endif
    349