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