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