xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_cidr.c (revision c48c605c14fd8622b523d1d6a3f0c0bad133ea89)
1 /*	$NetBSD: dict_cidr.c,v 1.5 2023/12/23 20:30:46 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	dict_cidr 3
6 /* SUMMARY
7 /*	Dictionary interface for CIDR data
8 /* SYNOPSIS
9 /*	#include <dict_cidr.h>
10 /*
11 /*	DICT	*dict_cidr_open(name, open_flags, dict_flags)
12 /*	const char *name;
13 /*	int	open_flags;
14 /*	int	dict_flags;
15 /* DESCRIPTION
16 /*	dict_cidr_open() opens the named file and stores
17 /*	the key/value pairs where the key must be either a
18 /*	"naked" IP address or a netblock in CIDR notation.
19 /* SEE ALSO
20 /*	dict(3) generic dictionary manager
21 /*	cidr_table(5) CIDR table configuration
22 /* AUTHOR(S)
23 /*	Jozsef Kadlecsik
24 /*	kadlec@blackhole.kfki.hu
25 /*	KFKI Research Institute for Particle and Nuclear Physics
26 /*	POB. 49
27 /*	1525 Budapest, Hungary
28 /*
29 /*	Wietse Venema
30 /*	IBM T.J. Watson Research
31 /*	P.O. Box 704
32 /*	Yorktown Heights, NY 10598, USA
33 /*
34 /*	Wietse Venema
35 /*	Google, Inc.
36 /*	111 8th Avenue
37 /*	New York, NY 10011, USA
38 /*--*/
39 
40 /* System library. */
41 
42 #include <sys_defs.h>
43 #include <sys/stat.h>
44 #include <stdlib.h>
45 #include <unistd.h>
46 #include <string.h>
47 #include <ctype.h>
48 #include <sys/socket.h>
49 #include <netinet/in.h>
50 #include <arpa/inet.h>
51 
52 /* Utility library. */
53 
54 #include <mymalloc.h>
55 #include <msg.h>
56 #include <vstream.h>
57 #include <vstring.h>
58 #include <stringops.h>
59 #include <readlline.h>
60 #include <dict.h>
61 #include <myaddrinfo.h>
62 #include <cidr_match.h>
63 #include <dict_cidr.h>
64 #include <warn_stat.h>
65 #include <mvect.h>
66 
67 /* Application-specific. */
68 
69  /*
70   * Each rule in a CIDR table is parsed and stored in a linked list.
71   */
72 typedef struct DICT_CIDR_ENTRY {
73     CIDR_MATCH cidr_info;		/* must be first */
74     char   *value;			/* lookup result */
75     int     lineno;
76 } DICT_CIDR_ENTRY;
77 
78 typedef struct {
79     DICT    dict;			/* generic members */
80     DICT_CIDR_ENTRY *head;		/* first entry */
81 } DICT_CIDR;
82 
83 /* dict_cidr_lookup - CIDR table lookup */
84 
dict_cidr_lookup(DICT * dict,const char * key)85 static const char *dict_cidr_lookup(DICT *dict, const char *key)
86 {
87     DICT_CIDR *dict_cidr = (DICT_CIDR *) dict;
88     DICT_CIDR_ENTRY *entry;
89 
90     if (msg_verbose)
91 	msg_info("dict_cidr_lookup: %s: %s", dict->name, key);
92 
93     dict->error = 0;
94 
95     if ((entry = (DICT_CIDR_ENTRY *)
96 	 cidr_match_execute(&(dict_cidr->head->cidr_info), key)) != 0)
97 	return (entry->value);
98     return (0);
99 }
100 
101 /* dict_cidr_close - close the CIDR table */
102 
dict_cidr_close(DICT * dict)103 static void dict_cidr_close(DICT *dict)
104 {
105     DICT_CIDR *dict_cidr = (DICT_CIDR *) dict;
106     DICT_CIDR_ENTRY *entry;
107     DICT_CIDR_ENTRY *next;
108 
109     for (entry = dict_cidr->head; entry; entry = next) {
110 	next = (DICT_CIDR_ENTRY *) entry->cidr_info.next;
111 	myfree(entry->value);
112 	myfree((void *) entry);
113     }
114     dict_free(dict);
115 }
116 
117 /* dict_cidr_parse_rule - parse CIDR table rule into network, mask and value */
118 
dict_cidr_parse_rule(DICT * dict,char * p,int lineno,int nesting,VSTRING * why)119 static DICT_CIDR_ENTRY *dict_cidr_parse_rule(DICT *dict, char *p, int lineno,
120 					          int nesting, VSTRING *why)
121 {
122     DICT_CIDR_ENTRY *rule;
123     char   *pattern;
124     char   *value;
125     CIDR_MATCH cidr_info;
126     MAI_HOSTADDR_STR hostaddr;
127     int     match = 1;
128 
129     /*
130      * IF must be followed by a pattern.
131      */
132     if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) {
133 	p += 2;
134 	for (;;) {
135 	    if (*p == '!')
136 		match = !match;
137 	    else if (!ISSPACE(*p))
138 		break;
139 	    p++;
140 	}
141 	if (*p == 0) {
142 	    vstring_sprintf(why, "no address pattern");
143 	    return (0);
144 	}
145 	trimblanks(p, 0)[0] = 0;		/* Trim trailing blanks */
146 	if (cidr_match_parse_if(&cidr_info, p, match, why) != 0)
147 	    return (0);
148 	value = "";
149     }
150 
151     /*
152      * ENDIF must not be followed by other text.
153      */
154     else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) {
155 	p += 5;
156 	while (*p && ISSPACE(*p))		/* Skip whitespace */
157 	    p++;
158 	if (*p != 0) {
159 	    vstring_sprintf(why, "garbage after ENDIF");
160 	    return (0);
161 	}
162 	if (nesting == 0) {
163 	    vstring_sprintf(why, "ENDIF without IF");
164 	    return (0);
165 	}
166 	cidr_match_endif(&cidr_info);
167 	value = "";
168     }
169 
170     /*
171      * An address pattern.
172      */
173     else {
174 
175 	/*
176 	 * Process negation operators.
177 	 */
178 	for (;;) {
179 	    if (*p == '!')
180 		match = !match;
181 	    else if (!ISSPACE(*p))
182 		break;
183 	    p++;
184 	}
185 
186 	/*
187 	 * Split the rule into key and value. We already eliminated leading
188 	 * whitespace, comments, empty lines or lines with whitespace only.
189 	 * This means a null key can't happen but we will handle this anyway.
190 	 */
191 	pattern = p;
192 	while (*p && !ISSPACE(*p))		/* Skip over key */
193 	    p++;
194 	if (*p)					/* Terminate key */
195 	    *p++ = 0;
196 	while (*p && ISSPACE(*p))		/* Skip whitespace */
197 	    p++;
198 	value = p;
199 	trimblanks(value, 0)[0] = 0;		/* Trim trailing blanks */
200 	if (*pattern == 0) {
201 	    vstring_sprintf(why, "no address pattern");
202 	    return (0);
203 	}
204 
205 	/*
206 	 * Parse the pattern, destroying it in the process.
207 	 */
208 	if (cidr_match_parse(&cidr_info, pattern, match, why) != 0)
209 	    return (0);
210 
211 	if (*value == 0) {
212 	    vstring_sprintf(why, "no lookup result");
213 	    return (0);
214 	}
215     }
216 
217     /*
218      * Optionally replace the value file the contents of a file.
219      */
220     if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
221 	VSTRING *base64_buf;
222 	char   *err;
223 
224 	if ((base64_buf = dict_file_to_b64(dict, value)) == 0) {
225 	    err = dict_file_get_error(dict);
226 	    vstring_strcpy(why, err);
227 	    myfree(err);
228 	    return (0);
229 	}
230 	value = vstring_str(base64_buf);
231     }
232 
233     /*
234      * Bundle up the result.
235      */
236     rule = (DICT_CIDR_ENTRY *) mymalloc(sizeof(DICT_CIDR_ENTRY));
237     rule->cidr_info = cidr_info;
238     rule->value = mystrdup(value);
239     rule->lineno = lineno;
240 
241     if (msg_verbose) {
242 	if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes,
243 		      hostaddr.buf, sizeof(hostaddr.buf)) == 0)
244 	    msg_fatal("inet_ntop: %m");
245 	msg_info("dict_cidr_open: add %s/%d %s",
246 		 hostaddr.buf, cidr_info.mask_shift, rule->value);
247     }
248     return (rule);
249 }
250 
251 /* dict_cidr_open - parse CIDR table */
252 
dict_cidr_open(const char * mapname,int open_flags,int dict_flags)253 DICT   *dict_cidr_open(const char *mapname, int open_flags, int dict_flags)
254 {
255     const char myname[] = "dict_cidr_open";
256     DICT_CIDR *dict_cidr;
257     VSTREAM *map_fp = 0;
258     struct stat st;
259     VSTRING *line_buffer = 0;
260     VSTRING *why = 0;
261     DICT_CIDR_ENTRY *rule;
262     DICT_CIDR_ENTRY *last_rule = 0;
263     int     last_line = 0;
264     int     lineno;
265     int     nesting = 0;
266     DICT_CIDR_ENTRY **rule_stack = 0;
267     MVECT   mvect;
268 
269     /*
270      * Let the optimizer worry about eliminating redundant code.
271      */
272 #define DICT_CIDR_OPEN_RETURN(d) do { \
273 	DICT *__d = (d); \
274 	if (map_fp != 0 && vstream_fclose(map_fp)) \
275 	    msg_fatal("cidr map %s: read error: %m", mapname); \
276 	if (line_buffer != 0) \
277 	    vstring_free(line_buffer); \
278 	if (why != 0) \
279 	    vstring_free(why); \
280 	return (__d); \
281     } while (0)
282 
283     /*
284      * Sanity checks.
285      */
286     if (open_flags != O_RDONLY)
287 	DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname,
288 					     open_flags, dict_flags,
289 				  "%s:%s map requires O_RDONLY access mode",
290 					     DICT_TYPE_CIDR, mapname));
291 
292     /*
293      * Open the configuration file.
294      */
295     if ((map_fp = dict_stream_open(DICT_TYPE_CIDR, mapname, O_RDONLY,
296 				   dict_flags, &st, &why)) == 0)
297 	DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname,
298 					     open_flags, dict_flags,
299 					     "%s", vstring_str(why)));
300     line_buffer = vstring_alloc(100);
301     why = vstring_alloc(100);
302 
303     /*
304      * XXX Eliminate unnecessary queries by setting a flag that says "this
305      * map matches network addresses only".
306      */
307     dict_cidr = (DICT_CIDR *) dict_alloc(DICT_TYPE_CIDR, mapname,
308 					 sizeof(*dict_cidr));
309     dict_cidr->dict.lookup = dict_cidr_lookup;
310     dict_cidr->dict.close = dict_cidr_close;
311     dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN;
312     dict_cidr->head = 0;
313 
314     dict_cidr->dict.owner.uid = st.st_uid;
315     dict_cidr->dict.owner.status = (st.st_uid != 0);
316 
317     while (readllines(line_buffer, map_fp, &last_line, &lineno)) {
318 	rule = dict_cidr_parse_rule(&dict_cidr->dict,
319 				    vstring_str(line_buffer), lineno,
320 				    nesting, why);
321 	if (rule == 0) {
322 	    msg_warn("cidr map %s, line %d: %s: skipping this rule",
323 		     mapname, lineno, vstring_str(why));
324 	    continue;
325 	}
326 	if (rule->cidr_info.op == CIDR_MATCH_OP_IF) {
327 	    if (rule_stack == 0)
328 		rule_stack = (DICT_CIDR_ENTRY **) mvect_alloc(&mvect,
329 					   sizeof(*rule_stack), nesting + 1,
330 						(MVECT_FN) 0, (MVECT_FN) 0);
331 	    else
332 		rule_stack =
333 		    (DICT_CIDR_ENTRY **) mvect_realloc(&mvect, nesting + 1);
334 	    rule_stack[nesting] = rule;
335 	    nesting++;
336 	} else if (rule->cidr_info.op == CIDR_MATCH_OP_ENDIF) {
337 	    DICT_CIDR_ENTRY *if_rule;
338 
339 	    if (nesting-- <= 0)
340 		/* Already handled in dict_cidr_parse_rule(). */
341 		msg_panic("%s: ENDIF without IF", myname);
342 	    if_rule = rule_stack[nesting];
343 	    if (if_rule->cidr_info.op != CIDR_MATCH_OP_IF)
344 		msg_panic("%s: unexpected rule stack element type %d",
345 			  myname, if_rule->cidr_info.op);
346 	    if_rule->cidr_info.block_end = &(rule->cidr_info);
347 	}
348 	if (last_rule == 0)
349 	    dict_cidr->head = rule;
350 	else
351 	    last_rule->cidr_info.next = &(rule->cidr_info);
352 	last_rule = rule;
353     }
354 
355     while (nesting-- > 0)
356 	msg_warn("cidr map %s, line %d: IF has no matching ENDIF",
357 		 mapname, rule_stack[nesting]->lineno);
358 
359     if (rule_stack)
360 	(void) mvect_free(&mvect);
361 
362     dict_file_purge_buffers(&dict_cidr->dict);
363     DICT_CIDR_OPEN_RETURN(DICT_DEBUG (&dict_cidr->dict));
364 }
365