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