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