F_NONNULL static bool _dcmap_new_iter(const char* key, unsigned klen V_UNUSED, const vscf_data_t* val, void* data) { dmn_assert(key); dmn_assert(val); dmn_assert(data); dcmap_iter_data* did = data; unsigned true_depth = did->true_depth + (did->dcmap->skip_level ? 1 : 0); if(true_depth == 0) validate_continent_code(key, did->map_name); else if(true_depth == 1) validate_country_code(key, did->map_name); did->dcmap->child_names[did->child_num] = strdup(key); if(vscf_is_hash(val)) did->dcmap->child_dcmaps[did->child_num] = dcmap_new(val, did->dclists, did->dcmap->def_dclist, true_depth + 1, did->map_name, did->allow_auto); else did->dcmap->child_dclists[did->child_num] = dclists_find_or_add_vscf(did->dclists, val, did->map_name, did->allow_auto); did->child_num++; return true; }
// arguably, with at least some of the v4-like spaces we could simply translate and hope to de-dupe, // if we upgraded nlist_normalize1 to de-dupe matching dclists instead of failing them F_NONNULL static bool nets_parse(vscf_data_t* nets_cfg, dclists_t* dclists, const char* map_name, nlist_t* nl) { bool rv = false; const unsigned input_nnets = vscf_hash_get_len(nets_cfg); for(unsigned i = 0; i < input_nnets; i++) { // convert 192.0.2.0/24 -> anysin_t w/ mask in port field unsigned net_str_len = 0; const char* net_str_cfg = vscf_hash_get_key_byindex(nets_cfg, i, &net_str_len); char net_str[net_str_len + 1]; memcpy(net_str, net_str_cfg, net_str_len + 1); char* mask_str = strchr(net_str, '/'); if(!mask_str) { log_err("plugin_geoip: map '%s': nets entry '%s' does not parse as addr/mask", map_name, net_str); rv = true; break; } *mask_str++ = '\0'; dmn_anysin_t tempsin; int addr_err = gdnsd_anysin_getaddrinfo(net_str, mask_str, &tempsin); if(addr_err) { log_err("plugin_geoip: map '%s': nets entry '%s/%s' does not parse as addr/mask: %s", map_name, net_str, mask_str, gai_strerror(addr_err)); rv = true; break; } unsigned mask; uint8_t ipv6[16]; // now store the anysin data into net_t if(tempsin.sa.sa_family == AF_INET6) { mask = ntohs(tempsin.sin6.sin6_port); if(mask > 128) { log_err("plugin_geoip: map '%s': nets entry '%s/%s': illegal IPv6 mask (>128)", map_name, net_str, mask_str); rv = true; break; } memcpy(ipv6, tempsin.sin6.sin6_addr.s6_addr, 16); if(check_v4_issues(ipv6, mask)) { log_err("plugin_geoip: map '%s': 'nets' entry '%s/%s' covers illegal IPv4-like space, see the documentation for more info", map_name, net_str, mask_str); rv = true; break; } } else { dmn_assert(tempsin.sa.sa_family == AF_INET); mask = ntohs(tempsin.sin.sin_port) + 96U; if(mask > 128) { log_err("plugin_geoip: map '%s': nets entry '%s/%s': illegal IPv4 mask (>32)", map_name, net_str, mask_str); rv = true; break; } memset(ipv6, 0, 16); memcpy(&ipv6[12], &tempsin.sin.sin_addr.s_addr, 4); } // get dclist integer from rhs vscf_data_t* dc_cfg = vscf_hash_get_data_byindex(nets_cfg, i); const uint32_t dclist = dclists_find_or_add_vscf(dclists, dc_cfg, map_name, false); dmn_assert(dclist <= DCLIST_MAX); // auto not allowed here nlist_append(nl, ipv6, mask, dclist); } return rv; }
dcmap_t* dcmap_new(const vscf_data_t* map_cfg, dclists_t* dclists, const unsigned parent_def, const unsigned true_depth, const char* map_name, const bool allow_auto) { dmn_assert(map_cfg); dmn_assert(dclists); dmn_assert(map_name); dmn_assert(vscf_is_hash(map_cfg)); dcmap_t* dcmap = calloc(1, sizeof(dcmap_t)); unsigned nchild = vscf_hash_get_len(map_cfg); const vscf_data_t* def_cfg = vscf_hash_get_data_byconstkey(map_cfg, "default", true); if(def_cfg) { if(!true_depth) { uint8_t newlist[256]; int status = dclists_xlate_vscf(dclists, def_cfg, map_name, newlist, allow_auto); if(status) { dmn_assert(status == -1 && allow_auto); dcmap->def_dclist = -1; } else { dcmap->def_dclist = 0; dclists_replace_list0(dclists, (uint8_t*)strdup((char*)newlist)); } } else { dcmap->def_dclist = dclists_find_or_add_vscf(dclists, def_cfg, map_name, allow_auto); } nchild--; // don't iterate "default" later } else { if(!true_depth) { dcmap->def_dclist = allow_auto ? -1 : 0; } else { dcmap->def_dclist = parent_def; } } const vscf_data_t* skip_cfg = vscf_hash_get_data_byconstkey(map_cfg, "skip_level", true); if(skip_cfg) { if(!vscf_is_simple(skip_cfg) || !vscf_simple_get_as_bool(skip_cfg, &dcmap->skip_level)) log_fatal("plugin_geoip: map '%s': 'skip_level' must be a boolean value ('true' or 'false')", map_name); nchild--; // don't iterate "skip_level" later } if(nchild) { dcmap->num_children = nchild; dcmap->child_names = calloc(nchild, sizeof(char*)); dcmap->child_dclists = calloc(nchild, sizeof(unsigned)); dcmap->child_dcmaps = calloc(nchild, sizeof(dcmap_t*)); dcmap_iter_data did = { .child_num = 0, .dcmap = dcmap, .dclists = dclists, .map_name = map_name, .true_depth = true_depth, .allow_auto = allow_auto }; vscf_hash_iterate(map_cfg, true, _dcmap_new_iter, &did); } return dcmap; } int dcmap_lookup_loc(const dcmap_t* dcmap, const char* locstr) { dmn_assert(dcmap); dmn_assert(locstr); if(*locstr && dcmap->skip_level) locstr += strlen(locstr) + 1; if(*locstr) { for(unsigned i = 0; i < dcmap->num_children; i++) { if(!strcasecmp(locstr, dcmap->child_names[i])) { if(dcmap->child_dcmaps[i]) return dcmap_lookup_loc(dcmap->child_dcmaps[i], locstr + strlen(locstr) + 1); return dcmap->child_dclists[i]; } } } return dcmap->def_dclist; }