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 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 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 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 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