1 /*
2 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 *
8 * See the COPYRIGHT file distributed with this work for additional
9 * information regarding copyright ownership.
10 */
11
12 /*! \file */
13
14 #include <inttypes.h>
15 #include <stdbool.h>
16 #include <stdlib.h>
17
18 /*
19 * This file is only built and linked if GeoIP2 has been configured.
20 */
21 #include <math.h>
22 #include <maxminddb.h>
23
24 #include <isc/mem.h>
25 #include <isc/once.h>
26 #include <isc/sockaddr.h>
27 #include <isc/string.h>
28 #include <isc/thread.h>
29 #include <isc/util.h>
30
31 #include <dns/acl.h>
32 #include <dns/geoip.h>
33 #ifndef WIN32
34 #include <netinet/in.h>
35 #else /* ifndef WIN32 */
36 #ifndef _WINSOCKAPI_
37 #define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */
38 #endif /* ifndef _WINSOCKAPI_ */
39 #include <winsock2.h>
40 #endif /* WIN32 */
41 #include <dns/log.h>
42
43 /*
44 * This structure preserves state from the previous GeoIP lookup,
45 * so that successive lookups for the same data from the same IP
46 * address will not require repeated database lookups.
47 * This should improve performance somewhat.
48 *
49 * For all lookups we preserve pointers to the MMDB_lookup_result_s
50 * and MMDB_entry_s structures, a pointer to the database from which
51 * the lookup was answered, and a copy of the request address.
52 *
53 * If the next geoip ACL lookup is for the same database and from the
54 * same address, we can reuse the MMDB entry without repeating the lookup.
55 * This is for the case when a single query has to process multiple
56 * geoip ACLs: for example, when there are multiple views with
57 * match-clients statements that search for different countries.
58 *
59 * (XXX: Currently the persistent state is stored in thread specific
60 * memory, but it could more simply be stored in the client object.
61 * Also multiple entries could be stored in case the ACLs require
62 * searching in more than one GeoIP database.)
63 */
64
65 typedef struct geoip_state {
66 uint16_t subtype;
67 const MMDB_s *db;
68 isc_netaddr_t addr;
69 MMDB_lookup_result_s mmresult;
70 MMDB_entry_s entry;
71 } geoip_state_t;
72
73 ISC_THREAD_LOCAL geoip_state_t geoip_state = { 0 };
74
75 static void
set_state(const MMDB_s * db,const isc_netaddr_t * addr,MMDB_lookup_result_s mmresult,MMDB_entry_s entry)76 set_state(const MMDB_s *db, const isc_netaddr_t *addr,
77 MMDB_lookup_result_s mmresult, MMDB_entry_s entry) {
78 geoip_state.db = db;
79 geoip_state.addr = *addr;
80 geoip_state.mmresult = mmresult;
81 geoip_state.entry = entry;
82 }
83
84 static geoip_state_t *
get_entry_for(MMDB_s * const db,const isc_netaddr_t * addr)85 get_entry_for(MMDB_s *const db, const isc_netaddr_t *addr) {
86 isc_sockaddr_t sa;
87 MMDB_lookup_result_s match;
88 int err;
89
90 if (db == geoip_state.db && isc_netaddr_equal(addr, &geoip_state.addr))
91 {
92 return (&geoip_state);
93 }
94
95 isc_sockaddr_fromnetaddr(&sa, addr, 0);
96 match = MMDB_lookup_sockaddr(db, &sa.type.sa, &err);
97 if (err != MMDB_SUCCESS || !match.found_entry) {
98 return (NULL);
99 }
100
101 set_state(db, addr, match, match.entry);
102
103 return (&geoip_state);
104 }
105
106 static dns_geoip_subtype_t
fix_subtype(const dns_geoip_databases_t * geoip,dns_geoip_subtype_t subtype)107 fix_subtype(const dns_geoip_databases_t *geoip, dns_geoip_subtype_t subtype) {
108 dns_geoip_subtype_t ret = subtype;
109
110 switch (subtype) {
111 case dns_geoip_countrycode:
112 if (geoip->city != NULL) {
113 ret = dns_geoip_city_countrycode;
114 } else if (geoip->country != NULL) {
115 ret = dns_geoip_country_code;
116 }
117 break;
118 case dns_geoip_countryname:
119 if (geoip->city != NULL) {
120 ret = dns_geoip_city_countryname;
121 } else if (geoip->country != NULL) {
122 ret = dns_geoip_country_name;
123 }
124 break;
125 case dns_geoip_continentcode:
126 if (geoip->city != NULL) {
127 ret = dns_geoip_city_continentcode;
128 } else if (geoip->country != NULL) {
129 ret = dns_geoip_country_continentcode;
130 }
131 break;
132 case dns_geoip_continent:
133 if (geoip->city != NULL) {
134 ret = dns_geoip_city_continent;
135 } else if (geoip->country != NULL) {
136 ret = dns_geoip_country_continent;
137 }
138 break;
139 case dns_geoip_region:
140 if (geoip->city != NULL) {
141 ret = dns_geoip_city_region;
142 }
143 break;
144 case dns_geoip_regionname:
145 if (geoip->city != NULL) {
146 ret = dns_geoip_city_regionname;
147 }
148 default:
149 break;
150 }
151
152 return (ret);
153 }
154
155 static MMDB_s *
geoip2_database(const dns_geoip_databases_t * geoip,dns_geoip_subtype_t subtype)156 geoip2_database(const dns_geoip_databases_t *geoip,
157 dns_geoip_subtype_t subtype) {
158 switch (subtype) {
159 case dns_geoip_country_code:
160 case dns_geoip_country_name:
161 case dns_geoip_country_continentcode:
162 case dns_geoip_country_continent:
163 return (geoip->country);
164
165 case dns_geoip_city_countrycode:
166 case dns_geoip_city_countryname:
167 case dns_geoip_city_continentcode:
168 case dns_geoip_city_continent:
169 case dns_geoip_city_region:
170 case dns_geoip_city_regionname:
171 case dns_geoip_city_name:
172 case dns_geoip_city_postalcode:
173 case dns_geoip_city_timezonecode:
174 case dns_geoip_city_metrocode:
175 case dns_geoip_city_areacode:
176 return (geoip->city);
177
178 case dns_geoip_isp_name:
179 return (geoip->isp);
180
181 case dns_geoip_as_asnum:
182 case dns_geoip_org_name:
183 return (geoip->as);
184
185 case dns_geoip_domain_name:
186 return (geoip->domain);
187
188 default:
189 /*
190 * All other subtypes are unavailable in GeoIP2.
191 */
192 return (NULL);
193 }
194 }
195
196 static bool
match_string(MMDB_entry_data_s * value,const char * str)197 match_string(MMDB_entry_data_s *value, const char *str) {
198 REQUIRE(str != NULL);
199
200 if (value == NULL || !value->has_data ||
201 value->type != MMDB_DATA_TYPE_UTF8_STRING ||
202 value->utf8_string == NULL)
203 {
204 return (false);
205 }
206
207 return (strncasecmp(value->utf8_string, str, value->data_size) == 0);
208 }
209
210 static bool
match_int(MMDB_entry_data_s * value,const uint32_t ui32)211 match_int(MMDB_entry_data_s *value, const uint32_t ui32) {
212 if (value == NULL || !value->has_data ||
213 (value->type != MMDB_DATA_TYPE_UINT32 &&
214 value->type != MMDB_DATA_TYPE_UINT16))
215 {
216 return (false);
217 }
218
219 return (value->uint32 == ui32);
220 }
221
222 bool
dns_geoip_match(const isc_netaddr_t * reqaddr,const dns_geoip_databases_t * geoip,const dns_geoip_elem_t * elt)223 dns_geoip_match(const isc_netaddr_t *reqaddr,
224 const dns_geoip_databases_t *geoip,
225 const dns_geoip_elem_t *elt) {
226 MMDB_s *db = NULL;
227 MMDB_entry_data_s value;
228 geoip_state_t *state = NULL;
229 dns_geoip_subtype_t subtype;
230 const char *s = NULL;
231 int ret;
232
233 REQUIRE(reqaddr != NULL);
234 REQUIRE(elt != NULL);
235 REQUIRE(geoip != NULL);
236
237 subtype = fix_subtype(geoip, elt->subtype);
238 db = geoip2_database(geoip, subtype);
239 if (db == NULL) {
240 return (false);
241 }
242
243 state = get_entry_for(db, reqaddr);
244 if (state == NULL) {
245 return (false);
246 }
247
248 switch (subtype) {
249 case dns_geoip_country_code:
250 case dns_geoip_city_countrycode:
251 ret = MMDB_get_value(&state->entry, &value, "country",
252 "iso_code", (char *)0);
253 if (ret == MMDB_SUCCESS) {
254 return (match_string(&value, elt->as_string));
255 }
256 break;
257
258 case dns_geoip_country_name:
259 case dns_geoip_city_countryname:
260 ret = MMDB_get_value(&state->entry, &value, "country", "names",
261 "en", (char *)0);
262 if (ret == MMDB_SUCCESS) {
263 return (match_string(&value, elt->as_string));
264 }
265 break;
266
267 case dns_geoip_country_continentcode:
268 case dns_geoip_city_continentcode:
269 ret = MMDB_get_value(&state->entry, &value, "continent", "code",
270 (char *)0);
271 if (ret == MMDB_SUCCESS) {
272 return (match_string(&value, elt->as_string));
273 }
274 break;
275
276 case dns_geoip_country_continent:
277 case dns_geoip_city_continent:
278 ret = MMDB_get_value(&state->entry, &value, "continent",
279 "names", "en", (char *)0);
280 if (ret == MMDB_SUCCESS) {
281 return (match_string(&value, elt->as_string));
282 }
283 break;
284
285 case dns_geoip_region:
286 case dns_geoip_city_region:
287 ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0",
288 "iso_code", (char *)0);
289 if (ret == MMDB_SUCCESS) {
290 return (match_string(&value, elt->as_string));
291 }
292 break;
293
294 case dns_geoip_regionname:
295 case dns_geoip_city_regionname:
296 ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0",
297 "names", "en", (char *)0);
298 if (ret == MMDB_SUCCESS) {
299 return (match_string(&value, elt->as_string));
300 }
301 break;
302
303 case dns_geoip_city_name:
304 ret = MMDB_get_value(&state->entry, &value, "city", "names",
305 "en", (char *)0);
306 if (ret == MMDB_SUCCESS) {
307 return (match_string(&value, elt->as_string));
308 }
309 break;
310
311 case dns_geoip_city_postalcode:
312 ret = MMDB_get_value(&state->entry, &value, "postal", "code",
313 (char *)0);
314 if (ret == MMDB_SUCCESS) {
315 return (match_string(&value, elt->as_string));
316 }
317 break;
318
319 case dns_geoip_city_timezonecode:
320 ret = MMDB_get_value(&state->entry, &value, "location",
321 "time_zone", (char *)0);
322 if (ret == MMDB_SUCCESS) {
323 return (match_string(&value, elt->as_string));
324 }
325 break;
326
327 case dns_geoip_city_metrocode:
328 ret = MMDB_get_value(&state->entry, &value, "location",
329 "metro_code", (char *)0);
330 if (ret == MMDB_SUCCESS) {
331 return (match_string(&value, elt->as_string));
332 }
333 break;
334
335 case dns_geoip_isp_name:
336 ret = MMDB_get_value(&state->entry, &value, "isp", (char *)0);
337 if (ret == MMDB_SUCCESS) {
338 return (match_string(&value, elt->as_string));
339 }
340 break;
341
342 case dns_geoip_as_asnum:
343 INSIST(elt->as_string != NULL);
344
345 ret = MMDB_get_value(&state->entry, &value,
346 "autonomous_system_number", (char *)0);
347 if (ret == MMDB_SUCCESS) {
348 int i;
349 s = elt->as_string;
350 if (strncasecmp(s, "AS", 2) == 0) {
351 s += 2;
352 }
353 i = strtol(s, NULL, 10);
354 return (match_int(&value, i));
355 }
356 break;
357
358 case dns_geoip_org_name:
359 ret = MMDB_get_value(&state->entry, &value,
360 "autonomous_system_organization",
361 (char *)0);
362 if (ret == MMDB_SUCCESS) {
363 return (match_string(&value, elt->as_string));
364 }
365 break;
366
367 case dns_geoip_domain_name:
368 ret = MMDB_get_value(&state->entry, &value, "domain",
369 (char *)0);
370 if (ret == MMDB_SUCCESS) {
371 return (match_string(&value, elt->as_string));
372 }
373 break;
374
375 default:
376 /*
377 * For any other subtype, we assume the database was
378 * unavailable and return false.
379 */
380 return (false);
381 }
382
383 /*
384 * No database matched: return false.
385 */
386 return (false);
387 }
388