1 /* Copyright © 2012 Brandon L Black <blblack@gmail.com>
2 *
3 * This file is part of gdnsd.
4 *
5 * gdnsd-plugin-geoip is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * gdnsd-plugin-geoip is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with gdnsd. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 // gdmaps = GeoIP -> Datacenter Mapping library code
21
22 #include <config.h>
23 #include "dcmap.h"
24
25 #include "dclists.h"
26 #include "gdgeoip.h"
27
28 #include <gdnsd/alloc.h>
29 #include <gdnsd/log.h>
30
31 #include <stdbool.h>
32
33 /***************************************
34 * dcmap_t and related methods
35 **************************************/
36
37 struct _dcmap {
38 // All 3 below are allocated to num_children entries.
39 // For each index, exactly one of the following must be true:
40 // child_dclist[i] is non-zero, indicating a direct dclist
41 // child_dcmap[i] is non-null, indicating another level of depth
42 char** child_names;
43 uint32_t* child_dclists;
44 dcmap_t** child_dcmaps;
45 unsigned def_dclist; // copied from parent if not specced in cfg, required at root
46 unsigned num_children;
47 bool skip_level; // at this level of dcmap, skip ahead one chunk of locstr...
48 };
49
50 typedef struct {
51 dcmap_t* dcmap;
52 dclists_t* dclists;
53 const char* map_name;
54 unsigned child_num;
55 unsigned true_depth;
56 bool allow_auto;
57 } dcmap_iter_data;
58
59 F_NONNULL
_dcmap_new_iter(const char * key,unsigned klen V_UNUSED,vscf_data_t * val,void * data)60 static bool _dcmap_new_iter(const char* key, unsigned klen V_UNUSED, vscf_data_t* val, void* data) {
61 dcmap_iter_data* did = data;
62
63 unsigned true_depth = did->true_depth + (did->dcmap->skip_level ? 1 : 0);
64 if(true_depth == 0)
65 validate_continent_code(key, did->map_name);
66 else if(true_depth == 1)
67 validate_country_code(key, did->map_name);
68
69 did->dcmap->child_names[did->child_num] = strdup(key);
70 if(vscf_is_hash(val))
71 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);
72 else
73 did->dcmap->child_dclists[did->child_num] = dclists_find_or_add_vscf(did->dclists, val, did->map_name, did->allow_auto);
74
75 did->child_num++;
76
77 return true;
78 }
79
dcmap_new(vscf_data_t * map_cfg,dclists_t * dclists,const unsigned parent_def,const unsigned true_depth,const char * map_name,const bool allow_auto)80 dcmap_t* dcmap_new(vscf_data_t* map_cfg, dclists_t* dclists, const unsigned parent_def, const unsigned true_depth, const char* map_name, const bool allow_auto) {
81 dmn_assert(vscf_is_hash(map_cfg));
82
83 dcmap_t* dcmap = xcalloc(1, sizeof(dcmap_t));
84 unsigned nchild = vscf_hash_get_len(map_cfg);
85
86 vscf_data_t* def_cfg = vscf_hash_get_data_byconstkey(map_cfg, "default", true);
87 if(def_cfg) {
88 if(!true_depth) {
89 uint8_t newlist[256];
90 bool is_auto = dclists_xlate_vscf(dclists, def_cfg, map_name, newlist, allow_auto);
91 if(is_auto) {
92 dmn_assert(allow_auto);
93 dcmap->def_dclist = DCLIST_AUTO;
94 }
95 else {
96 dcmap->def_dclist = 0;
97 dclists_replace_list0(dclists, (uint8_t*)strdup((char*)newlist));
98 }
99 }
100 else {
101 dcmap->def_dclist = dclists_find_or_add_vscf(dclists, def_cfg, map_name, allow_auto);
102 }
103 nchild--; // don't iterate "default" later
104 }
105 else {
106 if(!true_depth) {
107 dcmap->def_dclist = allow_auto ? DCLIST_AUTO : 0;
108 }
109 else {
110 dcmap->def_dclist = parent_def;
111 }
112 }
113
114 vscf_data_t* skip_cfg = vscf_hash_get_data_byconstkey(map_cfg, "skip_level", true);
115 if(skip_cfg) {
116 if(!vscf_is_simple(skip_cfg) || !vscf_simple_get_as_bool(skip_cfg, &dcmap->skip_level))
117 log_fatal("plugin_geoip: map '%s': 'skip_level' must be a boolean value ('true' or 'false')", map_name);
118 nchild--; // don't iterate "skip_level" later
119 }
120
121 if(nchild) {
122 dcmap->num_children = nchild;
123 dcmap->child_names = xcalloc(nchild, sizeof(char*));
124 dcmap->child_dclists = xcalloc(nchild, sizeof(uint32_t));
125 dcmap->child_dcmaps = xcalloc(nchild, sizeof(dcmap_t*));
126 dcmap_iter_data did = {
127 .child_num = 0,
128 .dcmap = dcmap,
129 .dclists = dclists,
130 .map_name = map_name,
131 .true_depth = true_depth,
132 .allow_auto = allow_auto
133 };
134 vscf_hash_iterate(map_cfg, true, _dcmap_new_iter, &did);
135 }
136
137 return dcmap;
138 }
139
dcmap_lookup_loc(const dcmap_t * dcmap,const char * locstr)140 uint32_t dcmap_lookup_loc(const dcmap_t* dcmap, const char* locstr) {
141 if(*locstr && dcmap->skip_level)
142 locstr += strlen(locstr) + 1;
143
144 if(*locstr) {
145 for(unsigned i = 0; i < dcmap->num_children; i++) {
146 if(!strcasecmp(locstr, dcmap->child_names[i])) {
147 if(dcmap->child_dcmaps[i])
148 return dcmap_lookup_loc(dcmap->child_dcmaps[i], locstr + strlen(locstr) + 1);
149 return dcmap->child_dclists[i];
150 }
151 }
152 }
153
154 return dcmap->def_dclist;
155 }
156
157 // as above, but supports abitrary levels of nesting in the map without regard
158 // to any named hierarchy, and without prefetching levels from the lookup source
159 // unless the map actually wants to see them.
dcmap_llc_(const dcmap_t * dcmap,dcmap_lookup_cb_t cb,void * data,unsigned level)160 static uint32_t dcmap_llc_(const dcmap_t* dcmap, dcmap_lookup_cb_t cb, void* data, unsigned level) {
161 // map empty within this level, e.g. "US => {}" or "US => { default => [...] }"
162 if(!dcmap->num_children)
163 return dcmap->def_dclist;
164
165 // if skip_level, throw away one level of result from callback
166 if(dcmap->skip_level)
167 cb(data, NULL, level++);
168
169 // This will potentially execute multiple callbacks to search several
170 // levels deep in the network record for a match, but only once we've
171 // explicitly passed the Country level (so search only happens for
172 // subdivisions and cities).
173 char lookup[DCMAP_LOOKUP_MAXLEN];
174 do {
175 lookup[0] = '\0';
176 cb(data, &lookup[0], level++);
177 if(!lookup[0])
178 break;
179 for(unsigned i = 0; i < dcmap->num_children; i++) {
180 if(!strcasecmp(lookup, dcmap->child_names[i])) {
181 if(dcmap->child_dcmaps[i])
182 return dcmap_llc_(dcmap->child_dcmaps[i], cb, data, level);
183 return dcmap->child_dclists[i];
184 }
185 }
186 } while(level > 2); // >1 => post-continent, >2 => post-country
187
188 return dcmap->def_dclist;
189 }
190
dcmap_lookup_loc_callback(const dcmap_t * dcmap,dcmap_lookup_cb_t cb,void * data)191 uint32_t dcmap_lookup_loc_callback(const dcmap_t* dcmap, dcmap_lookup_cb_t cb, void* data) {
192 return dcmap_llc_(dcmap, cb, data, 0);
193 }
194
dcmap_destroy(dcmap_t * dcmap)195 void dcmap_destroy(dcmap_t* dcmap) {
196 if(dcmap->child_names) {
197 for(unsigned i = 0; i < dcmap->num_children; i++) {
198 if(dcmap->child_names[i])
199 free(dcmap->child_names[i]);
200 }
201 free(dcmap->child_names);
202 }
203 if(dcmap->child_dcmaps) {
204 for(unsigned i = 0; i < dcmap->num_children; i++) {
205 if(dcmap->child_dcmaps[i])
206 dcmap_destroy(dcmap->child_dcmaps[i]);
207 }
208 free(dcmap->child_dcmaps);
209 }
210 if(dcmap->child_dclists)
211 free(dcmap->child_dclists);
212 free(dcmap);
213 }
214