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