From: Evan Hunt Date: Fri, 28 Jun 2019 01:16:46 +0000 (-0700) Subject: implement searching of geoip2 database X-Git-Tag: v9.11.9~7^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0d50d9988cf47625a690d6c4425ca03a832166fd;p=thirdparty%2Fbind9.git implement searching of geoip2 database - revise mapping of search terms to database types to match the GeoIP2 schemas. - open GeoIP2 databases when starting up; close when shutting down. - clarify the logged error message when an unknown database type is configured. - add new geoip ACL subtypes to support searching for continent in country databases. - map geoip ACL subtypes to specific MMDB database queries. - perform MMDB lookups based on subtype, saving state between queries so repeated lookups for the same address aren't necessary. (cherry picked from commit 6e0b93e5a0e723d270b2df353006ede6c08d14a9) (cherry picked from commit 0283ab7512de6fb21ecd844c78137b993e8788ce) --- diff --git a/bin/named/geoip.c b/bin/named/geoip.c index f29de0112ab..6bf0ab1a31f 100644 --- a/bin/named/geoip.c +++ b/bin/named/geoip.c @@ -13,18 +13,25 @@ #include +#if defined(HAVE_GEOIP2) +#include +#elif defined(HAVE_GEOIP) +#include +#include +#endif + +#include +#include #include +#include + #include #include -#include - -#ifdef HAVE_GEOIP -static dns_geoip_databases_t geoip_table = { - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL -}; +static dns_geoip_databases_t geoip_table = DNS_GEOIP_DATABASE_INIT; +#if defined(HAVE_GEOIP) static void init_geoip_db(void **dbp, GeoIPDBTypes edition, GeoIPDBTypes fallback, GeoIPOptions method, const char *name) @@ -72,21 +79,58 @@ init_geoip_db(void **dbp, GeoIPDBTypes edition, GeoIPDBTypes fallback, *dbp = db; return; + fail: - if (fallback != 0) + if (fallback != 0) { init_geoip_db(dbp, fallback, 0, method, name); + } + +} +#elif defined(HAVE_GEOIP2) +static MMDB_s geoip_country, geoip_city, geoip_as, geoip_isp, geoip_domain; + +static MMDB_s * +open_geoip2(const char *dir, const char *dbfile, MMDB_s *mmdb) { + char pathbuf[PATH_MAX]; + unsigned int n; + int ret; + + n = snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, dbfile); + if (n >= sizeof(pathbuf)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "GeoIP2 database '%s/%s': path too long", + (dir != NULL) ? dir : ".", dbfile); + return (NULL); + } + + ret = MMDB_open(pathbuf, MMDB_MODE_MMAP, mmdb); + if (ret == MMDB_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "opened GeoIP2 database '%s'", pathbuf); + return (mmdb); + } + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "unable to open GeoIP2 database '%s' (status %d)", + pathbuf, ret); + return (NULL); } -#endif /* HAVE_GEOIP */ +#endif /* HAVE_GEOIP2 */ + void ns_geoip_init(void) { -#if defined(HAVE_GEOIP2) - /* TODO GEOIP2 */ -#elif defined(HAVE_GEOIP) - GeoIP_cleanup(); - if (ns_g_geoip == NULL) +#if defined(HAVE_GEOIP) || defined(HAVE_GEOIP2) + if (ns_g_geoip == NULL) { ns_g_geoip = &geoip_table; + } +#if defined(HAVE_GEOIP) + GeoIP_cleanup(); +#endif #else return; #endif @@ -95,7 +139,31 @@ ns_geoip_init(void) { void ns_geoip_load(char *dir) { #if defined(HAVE_GEOIP2) - /* TODO GEOIP2 */ + REQUIRE(dir != NULL); + + ns_g_geoip->country = open_geoip2(dir, "GeoIP2-Country.mmdb", + &geoip_country); + if (ns_g_geoip->country == NULL) { + ns_g_geoip->country = open_geoip2(dir, + "GeoLite2-Country.mmdb", + &geoip_country); + } + + ns_g_geoip->city = open_geoip2(dir, "GeoIP2-City.mmdb", &geoip_city); + if (ns_g_geoip->city == NULL) { + ns_g_geoip->city = open_geoip2(dir, "GeoLite2-City.mmdb", + &geoip_city); + } + + ns_g_geoip->as = open_geoip2(dir, "GeoIP2-ASN.mmdb", &geoip_as); + if (ns_g_geoip->as == NULL) { + ns_g_geoip->as = open_geoip2(dir, "GeoLite2-ASN.mmdb", + &geoip_as); + } + + ns_g_geoip->isp = open_geoip2(dir, "GeoIP2-ISP.mmdb", &geoip_isp); + ns_g_geoip->domain = open_geoip2(dir, "GeoIP2-Domain.mmdb", + &geoip_domain); #elif defined(HAVE_GEOIP) GeoIPOptions method; @@ -146,3 +214,31 @@ ns_geoip_load(char *dir) { return; #endif } + +void +ns_geoip_shutdown(void) { +#ifdef HAVE_GEOIP2 + if (ns_g_geoip->country != NULL) { + MMDB_close(ns_g_geoip->country); + ns_g_geoip->country = NULL; + } + if (ns_g_geoip->city != NULL) { + MMDB_close(ns_g_geoip->city); + ns_g_geoip->city = NULL; + } + if (ns_g_geoip->as != NULL) { + MMDB_close(ns_g_geoip->as); + ns_g_geoip->as = NULL; + } + if (ns_g_geoip->isp != NULL) { + MMDB_close(ns_g_geoip->isp); + ns_g_geoip->isp = NULL; + } + if (ns_g_geoip->domain != NULL) { + MMDB_close(ns_g_geoip->domain); + ns_g_geoip->domain = NULL; + } +#endif /* HAVE_GEOIP2 */ + + dns_geoip_shutdown(); +} diff --git a/bin/named/include/named/geoip.h b/bin/named/include/named/geoip.h index 04a5a3964cb..38014ffc1d8 100644 --- a/bin/named/include/named/geoip.h +++ b/bin/named/include/named/geoip.h @@ -12,15 +12,14 @@ #ifndef _GEOIP_H #define _GEOIP_H -#ifdef HAVE_GEOIP -#include -#include -#endif /* HAVE_GEOIP */ +extern dns_geoip_databases_t *ns_g_geoip; -void ns_geoip_init(void); -void ns_geoip_load(char *dir); +void +ns_geoip_init(void); -#ifdef HAVE_GEOIP -extern dns_geoip_databases_t *ns_g_geoip; -#endif /* HAVE_GEOIP */ +void +ns_geoip_load(char *dir); + +void +ns_geoip_shutdown(void); #endif diff --git a/bin/named/server.c b/bin/named/server.c index 242d61eae1e..1ed1f1e5bd0 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -8961,7 +8961,7 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { dns_dt_shutdown(); #endif #if defined(HAVE_GEOIP) || defined(HAVE_GEOIP2) - dns_geoip_shutdown(); + ns_geoip_shutdown(); #endif /* HAVE_GEOIP || HAVE_GEOIP2 */ dns_db_detach(&server->in_roothints); diff --git a/lib/dns/acl.c b/lib/dns/acl.c index 0c82b4069aa..72b8622b31f 100644 --- a/lib/dns/acl.c +++ b/lib/dns/acl.c @@ -453,7 +453,7 @@ dns_aclelement_match2(const isc_netaddr_t *reqaddr, dns_acl_t *inner = NULL; int indirectmatch; isc_result_t result; -#ifdef HAVE_GEOIP +#if defined(HAVE_GEOIP) || defined(HAVE_GEOIP2) const isc_netaddr_t *addr = NULL; #endif diff --git a/lib/dns/geoip2.c b/lib/dns/geoip2.c index 029d85563ef..9660260e6b0 100644 --- a/lib/dns/geoip2.c +++ b/lib/dns/geoip2.c @@ -11,23 +11,574 @@ /*! \file */ -#include +#include + +#include +#include +#include + +/* + * This file is only built and linked if GeoIP2 has been configured. + */ +#include +#include +#include +#include +#include +#include #include +#include +#include + +#include +#ifndef WIN32 +#include +#else +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */ +#endif +#include +#endif /* WIN32 */ +#include + +/* + * This structure preserves state from the previous GeoIP lookup, + * so that successive lookups for the same data from the same IP + * address will not require repeated database lookups. + * This should improve performance somewhat. + * + * For all lookups we preserve pointers to the MMDB_lookup_result_s + * and MMDB_entry_s structures, a pointer to the database from which + * the lookup was answered, and a copy of the request address. + * + * If the next geoip ACL lookup is for the same database and from the + * same address, we can reuse the MMDB entry without repeating the lookup. + * This is for the case when a single query has to process multiple + * geoip ACLs: for example, when there are multiple views with + * match-clients statements that search for different countries. + * + * (XXX: Currently the persistent state is stored in thread specific + * memory, but it could more simply be stored in the client object. + * Also multiple entries could be stored in case the ACLs require + * searching in more than one GeoIP database.) + */ + +typedef struct geoip_state { + isc_mem_t *mctx; + uint16_t subtype; + const MMDB_s *db; + isc_netaddr_t addr; + MMDB_lookup_result_s mmresult; + MMDB_entry_s entry; +} geoip_state_t; + +static isc_mutex_t key_mutex; +static bool state_key_initialized = false; +static isc_thread_key_t state_key; +static isc_once_t mutex_once = ISC_ONCE_INIT; +static isc_mem_t *state_mctx = NULL; + +static void +key_mutex_init(void) { + isc_mutex_init(&key_mutex); +} + +static void +free_state(void *arg) { + geoip_state_t *state = arg; + if (state != NULL) { + isc_mem_putanddetach(&state->mctx, + state, sizeof(geoip_state_t)); + } + isc_thread_key_setspecific(state_key, NULL); +} + +static isc_result_t +state_key_init(void) { + isc_result_t result; + + result = isc_once_do(&mutex_once, key_mutex_init); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (!state_key_initialized) { + LOCK(&key_mutex); + if (!state_key_initialized) { + int ret; + + if (state_mctx == NULL) { + result = isc_mem_create(0, 0, &state_mctx); + } + if (result != ISC_R_SUCCESS) { + goto unlock; + } + isc_mem_setname(state_mctx, "geoip_state", NULL); + isc_mem_setdestroycheck(state_mctx, false); + + ret = isc_thread_key_create(&state_key, free_state); + if (ret == 0) { + state_key_initialized = true; + } else { + result = ISC_R_FAILURE; + } + } + unlock: + UNLOCK(&key_mutex); + } + + return (result); +} + +static isc_result_t +set_state(const MMDB_s *db, const isc_netaddr_t *addr, + MMDB_lookup_result_s mmresult, MMDB_entry_s entry, + geoip_state_t **statep) +{ + geoip_state_t *state = NULL; + isc_result_t result; + + result = state_key_init(); + if (result != ISC_R_SUCCESS) { + return (result); + } + + state = (geoip_state_t *) isc_thread_key_getspecific(state_key); + if (state == NULL) { + state = (geoip_state_t *) isc_mem_get(state_mctx, + sizeof(geoip_state_t)); + memset(state, 0, sizeof(*state)); + + result = isc_thread_key_setspecific(state_key, state); + if (result != ISC_R_SUCCESS) { + isc_mem_put(state_mctx, state, sizeof(geoip_state_t)); + return (result); + } + + isc_mem_attach(state_mctx, &state->mctx); + } + + state->db = db; + state->addr = *addr; + state->mmresult = mmresult; + state->entry = entry; + + if (statep != NULL) { + *statep = state; + } + + return (ISC_R_SUCCESS); +} + +static geoip_state_t * +get_entry_for(MMDB_s * const db, const isc_netaddr_t *addr) { + isc_result_t result; + isc_sockaddr_t sa; + geoip_state_t *state; + MMDB_lookup_result_s match; + int err; + + result = state_key_init(); + if (result != ISC_R_SUCCESS) { + return (NULL); + } + + state = (geoip_state_t *) isc_thread_key_getspecific(state_key); + if (state != NULL) { + if (db == state->db && isc_netaddr_equal(addr, &state->addr)) { + return (state); + } + } + + isc_sockaddr_fromnetaddr(&sa, addr, 0); + match = MMDB_lookup_sockaddr(db, &sa.type.sa, &err); + if (err != MMDB_SUCCESS) { + return (NULL); + } + + result = set_state(db, addr, match, match.entry, &state); + if (result != ISC_R_SUCCESS) { + return (NULL); + } + + return (state); +} + +static dns_geoip_subtype_t +fix_subtype(const dns_geoip_databases_t *geoip, dns_geoip_subtype_t subtype) { + dns_geoip_subtype_t ret = subtype; + + switch (subtype) { + case dns_geoip_countrycode: + if (geoip->city != NULL) { + ret = dns_geoip_city_countrycode; + } else if (geoip->country != NULL) { + ret = dns_geoip_country_code; + } + break; + case dns_geoip_countryname: + if (geoip->city != NULL) { + ret = dns_geoip_city_countryname; + } else if (geoip->country != NULL) { + ret = dns_geoip_country_name; + } + break; + case dns_geoip_continentcode: + if (geoip->city != NULL) { + ret = dns_geoip_city_continentcode; + } else if (geoip->country != NULL) { + ret = dns_geoip_country_continentcode; + } + break; + case dns_geoip_continent: + if (geoip->city != NULL) { + ret = dns_geoip_city_continent; + } else if (geoip->country != NULL) { + ret = dns_geoip_country_continent; + } + break; + case dns_geoip_region: + if (geoip->city != NULL) { + ret = dns_geoip_city_region; + } + break; + case dns_geoip_regionname: + if (geoip->city != NULL) { + ret = dns_geoip_city_regionname; + } + default: + break; + } + + return (ret); +} + +static MMDB_s * +geoip2_database(const dns_geoip_databases_t *geoip, + dns_geoip_subtype_t subtype) +{ + switch (subtype) { + case dns_geoip_country_code: + case dns_geoip_country_name: + case dns_geoip_country_continentcode: + case dns_geoip_country_continent: + return (geoip->country); + + case dns_geoip_city_countrycode: + case dns_geoip_city_countryname: + case dns_geoip_city_continentcode: + case dns_geoip_city_continent: + case dns_geoip_city_region: + case dns_geoip_city_regionname: + case dns_geoip_city_name: + case dns_geoip_city_postalcode: + case dns_geoip_city_timezonecode: + case dns_geoip_city_metrocode: + case dns_geoip_city_areacode: + return (geoip->city); + + case dns_geoip_isp_name: + return (geoip->isp); + + case dns_geoip_as_asnum: + case dns_geoip_org_name: + return (geoip->as); + + case dns_geoip_domain_name: + return (geoip->domain); + + default: + /* + * All other subtypes are unavailable in GeoIP2. + */ + return (NULL); + } +} + +static bool +match_string(MMDB_entry_data_s *value, const char *str) { + REQUIRE(str != NULL); + + if (value == NULL || !value->has_data || + value->type != MMDB_DATA_TYPE_UTF8_STRING || + value->utf8_string == NULL) + { + return (false); + } + + return (strncasecmp(value->utf8_string, str, value->data_size) == 0); +} + +static bool +match_int(MMDB_entry_data_s *value, const uint32_t ui32) { + if (value == NULL || !value->has_data || + (value->type != MMDB_DATA_TYPE_UINT32 && + value->type != MMDB_DATA_TYPE_UINT16)) + { + return (false); + } + + return (value->uint32 == ui32); +} + bool -dns_geoip_match(const isc_netaddr_t *reqaddr, +dns_geoip_match(const isc_netaddr_t *reqaddr, uint8_t *scope, const dns_geoip_databases_t *geoip, const dns_geoip_elem_t *elt) { - UNUSED(reqaddr); - UNUSED(geoip); - UNUSED(elt); + MMDB_s *db = NULL; + MMDB_entry_data_s value; + geoip_state_t *state = NULL; + dns_geoip_subtype_t subtype; + const char *s = NULL; + int ret, i; + + REQUIRE(reqaddr != NULL); + REQUIRE(elt != NULL); + REQUIRE(geoip != NULL); + + subtype = fix_subtype(geoip, elt->subtype); + db = geoip2_database(geoip, subtype); + if (db == NULL) { + return (false); + } + + state = get_entry_for(db, reqaddr); + if (state == NULL) { + return (false); + } + + switch (subtype) { + case dns_geoip_country_code: + case dns_geoip_city_countrycode: + ret = MMDB_get_value(&state->entry, &value, + "country", "iso_code", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; +#define isc_netaddr_pf(x) (x)->family + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_country_name: + case dns_geoip_city_countryname: + ret = MMDB_get_value(&state->entry, &value, + "country", "names", "en", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_country_continentcode: + case dns_geoip_city_continentcode: + ret = MMDB_get_value(&state->entry, &value, + "continent", "code", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_country_continent: + case dns_geoip_city_continent: + ret = MMDB_get_value(&state->entry, &value, + "continent", "names", "en", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_region: + case dns_geoip_city_region: + ret = MMDB_get_value(&state->entry, &value, + "subdivisions", "0", "iso_code", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_regionname: + case dns_geoip_city_regionname: + ret = MMDB_get_value(&state->entry, &value, + "subdivisions", "0", "names", "en", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_city_name: + ret = MMDB_get_value(&state->entry, &value, + "city", "names", "en", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_city_postalcode: + ret = MMDB_get_value(&state->entry, &value, + "postal", "code", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_city_timezonecode: + ret = MMDB_get_value(&state->entry, &value, + "location", "time_zone", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + + case dns_geoip_city_metrocode: + ret = MMDB_get_value(&state->entry, &value, + "location", "metro_code", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_isp_name: + ret = MMDB_get_value(&state->entry, &value, "isp", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_as_asnum: + INSIST(elt->as_string != NULL); + + ret = MMDB_get_value(&state->entry, &value, + "autonomous_system_number", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + + s = elt->as_string; + if (strncasecmp(s, "AS", 2) == 0) { + s += 2; + } + + i = strtol(s, NULL, 10); + return (match_int(&value, i)); + } + break; + + case dns_geoip_org_name: + ret = MMDB_get_value(&state->entry, &value, + "autonomous_system_organization", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_domain_name: + ret = MMDB_get_value(&state->entry, &value, "domain", NULL); + if (ret == MMDB_SUCCESS) { + if (scope != NULL) { + *scope = state->mmresult.netmask; + if (isc_netaddr_pf(reqaddr) == AF_INET) { + *scope -= 96; + } + } + return (match_string(&value, elt->as_string)); + } + break; + + default: + /* + * For any other subtype, we assume the database was + * unavailable and return false. + */ + return (false); + } + /* + * No database matched: return false. + */ return (false); } void dns_geoip_shutdown(void) { - return; + if (state_mctx != NULL) { + isc_mem_detach(&state_mctx); + } } diff --git a/lib/dns/include/dns/acl.h b/lib/dns/include/dns/acl.h index 8091cc15598..f868d15793c 100644 --- a/lib/dns/include/dns/acl.h +++ b/lib/dns/include/dns/acl.h @@ -39,12 +39,6 @@ #include #include -#if defined(HAVE_GEOIP2) -#include -#elif defined(HAVE_GEOIP) -#include -#endif - /*** *** Types ***/ diff --git a/lib/dns/include/dns/geoip.h b/lib/dns/include/dns/geoip.h index fc0fc094fbd..c560dbba639 100644 --- a/lib/dns/include/dns/geoip.h +++ b/lib/dns/include/dns/geoip.h @@ -43,11 +43,15 @@ typedef enum { dns_geoip_countrycode, dns_geoip_countrycode3, dns_geoip_countryname, + dns_geoip_continentcode, + dns_geoip_continent, dns_geoip_region, dns_geoip_regionname, dns_geoip_country_code, dns_geoip_country_code3, dns_geoip_country_name, + dns_geoip_country_continentcode, + dns_geoip_country_continent, dns_geoip_region_countrycode, dns_geoip_region_code, dns_geoip_region_name, @@ -61,6 +65,7 @@ typedef enum { dns_geoip_city_metrocode, dns_geoip_city_areacode, dns_geoip_city_continentcode, + dns_geoip_city_continent, dns_geoip_city_timezonecode, dns_geoip_isp_name, dns_geoip_org_name, @@ -85,6 +90,8 @@ typedef struct dns_geoip_databases { void *domain; /* GeoIP2-Domain */ void *isp; /* GeoIP2-ISP */ void *as; /* GeoIP2-ASN or GeoLite2-ASN */ +#define DNS_GEOIP_DATABASE_INIT \ + { NULL, NULL, NULL, NULL, NULL } #elif defined(HAVE_GEOIP) void *country_v4; /* DB 1 */ void *city_v4; /* DB 2 or 6 */ @@ -96,6 +103,8 @@ typedef struct dns_geoip_databases { void *domain; /* DB 11 */ void *country_v6; /* DB 12 */ void *city_v6; /* DB 30 or 31 */ +#define DNS_GEOIP_DATABASE_INIT \ + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } #endif } dns_geoip_databases_t; diff --git a/lib/dns/tests/geoip_test.c b/lib/dns/tests/geoip_test.c index 58469fc65d4..9a57e9db870 100644 --- a/lib/dns/tests/geoip_test.c +++ b/lib/dns/tests/geoip_test.c @@ -67,6 +67,8 @@ _teardown(void **state) { return (0); } + +static dns_geoip_databases_t geoip = DNS_GEOIP_DATABASE_INIT; #endif /* HAVE_GEOIP || HAVE_GEOIP2 */ #ifdef HAVE_GEOIP @@ -74,9 +76,7 @@ _teardown(void **state) { * Helper functions * (Mostly copied from bin/named/geoip.c) */ -static dns_geoip_databases_t geoip = { - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL -}; +static dns_geoip_databases_t geoip = { NULL }; static void init_geoip_db(void **dbp, GeoIPDBTypes edition, GeoIPDBTypes fallback, diff --git a/lib/isccfg/aclconf.c b/lib/isccfg/aclconf.c index deaa1c579c0..132e651bc34 100644 --- a/lib/isccfg/aclconf.c +++ b/lib/isccfg/aclconf.c @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -27,13 +28,31 @@ #include #include -#ifdef HAVE_GEOIP -#include -#include -#endif /* HAVE_GEOIP */ - #define LOOP_MAGIC ISC_MAGIC('L','O','O','P') +#if defined(HAVE_GEOIP2) +static const char *geoip_dbnames[] = { + "country", + "city", + "asnum", + "isp", + "domain", + NULL, +}; +#elif defined(HAVE_GEOIP) +static const char *geoip_dbnames[] = { + "country", + "city", + "region", + "asnum", + "isp", + "domain", + "netspeed", + "org", + NULL, +}; +#endif /* HAVE_GEOIP */ + isc_result_t cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **ret) { isc_result_t result; @@ -298,7 +317,7 @@ count_acl_elements(const cfg_obj_t *caml, const cfg_obj_t *cctx, return (ISC_R_SUCCESS); } -#if defined(HAVE_GEOIP) +#if defined(HAVE_GEOIP2) static dns_geoip_subtype_t get_subtype(const cfg_obj_t *obj, isc_log_t *lctx, dns_geoip_subtype_t subtype, const char *dbname) @@ -311,54 +330,58 @@ get_subtype(const cfg_obj_t *obj, isc_log_t *lctx, case dns_geoip_countrycode: if (strcasecmp(dbname, "city") == 0) { return (dns_geoip_city_countrycode); - } else if (strcasecmp(dbname, "region") == 0) { - return (dns_geoip_region_countrycode); } else if (strcasecmp(dbname, "country") == 0) { return (dns_geoip_country_code); } cfg_obj_log(obj, lctx, ISC_LOG_ERROR, - "invalid GeoIP DB specified for " + "invalid database specified for " "country search: ignored"); return (subtype); - case dns_geoip_countrycode3: + case dns_geoip_countryname: if (strcasecmp(dbname, "city") == 0) { - return (dns_geoip_city_countrycode3); + return (dns_geoip_city_countryname); } else if (strcasecmp(dbname, "country") == 0) { - return (dns_geoip_country_code3); + return (dns_geoip_country_name); } cfg_obj_log(obj, lctx, ISC_LOG_ERROR, - "invalid GeoIP DB specified for " + "invalid database specified for " "country search: ignored"); return (subtype); - case dns_geoip_countryname: + case dns_geoip_continentcode: if (strcasecmp(dbname, "city") == 0) { - return (dns_geoip_city_countryname); + return (dns_geoip_city_continentcode); } else if (strcasecmp(dbname, "country") == 0) { - return (dns_geoip_country_name); + return (dns_geoip_country_continentcode); } cfg_obj_log(obj, lctx, ISC_LOG_ERROR, - "invalid GeoIP DB specified for " - "country search: ignored"); + "invalid database specified for " + "continent search: ignored"); + return (subtype); + case dns_geoip_continent: + if (strcasecmp(dbname, "city") == 0) { + return (dns_geoip_city_continent); + } else if (strcasecmp(dbname, "country") == 0) { + return (dns_geoip_country_continent); + } + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "invalid database specified for " + "continent search: ignored"); return (subtype); case dns_geoip_region: if (strcasecmp(dbname, "city") == 0) { return (dns_geoip_city_region); - } else if (strcasecmp(dbname, "region") == 0) { - return (dns_geoip_region_code); } cfg_obj_log(obj, lctx, ISC_LOG_ERROR, - "invalid GeoIP DB specified for " - "region search: ignored"); + "invalid database specified for " + "region/subdivision search: ignored"); return (subtype); case dns_geoip_regionname: if (strcasecmp(dbname, "city") == 0) { - return (dns_geoip_city_region); - } else if (strcasecmp(dbname, "region") == 0) { - return (dns_geoip_region_name); + return (dns_geoip_city_regionname); } cfg_obj_log(obj, lctx, ISC_LOG_ERROR, - "invalid GeoIP DB specified for " - "region search: ignored"); + "invalid database specified for " + "region/subdivision search: ignored"); return (subtype); /* @@ -369,46 +392,45 @@ get_subtype(const cfg_obj_t *obj, isc_log_t *lctx, case dns_geoip_city_postalcode: case dns_geoip_city_metrocode: case dns_geoip_city_areacode: - case dns_geoip_city_continentcode: case dns_geoip_city_timezonecode: if (strcasecmp(dbname, "city") != 0) { cfg_obj_log(obj, lctx, ISC_LOG_WARNING, - "invalid GeoIP DB specified for " + "invalid database specified for " "a 'city'-only search type: ignoring"); } return (subtype); case dns_geoip_isp_name: if (strcasecmp(dbname, "isp") != 0) { cfg_obj_log(obj, lctx, ISC_LOG_WARNING, - "invalid GeoIP DB specified for " + "invalid database specified for " "an 'isp' search: ignoring"); } return (subtype); case dns_geoip_org_name: if (strcasecmp(dbname, "org") != 0) { cfg_obj_log(obj, lctx, ISC_LOG_WARNING, - "invalid GeoIP DB specified for " + "invalid database specified for " "an 'org' search: ignoring"); } return (subtype); case dns_geoip_as_asnum: if (strcasecmp(dbname, "asnum") != 0) { cfg_obj_log(obj, lctx, ISC_LOG_WARNING, - "invalid GeoIP DB specified for " + "invalid database specified for " "an 'asnum' search: ignoring"); } return (subtype); case dns_geoip_domain_name: if (strcasecmp(dbname, "domain") != 0) { cfg_obj_log(obj, lctx, ISC_LOG_WARNING, - "invalid GeoIP DB specified for " + "invalid database specified for " "a 'domain' search: ignoring"); } return (subtype); case dns_geoip_netspeed_id: if (strcasecmp(dbname, "netspeed") != 0) { cfg_obj_log(obj, lctx, ISC_LOG_WARNING, - "invalid GeoIP DB specified for " + "invalid database specified for " "a 'netspeed' search: ignoring"); } return (subtype); @@ -424,6 +446,327 @@ geoip_can_answer(dns_aclelement_t *elt, cfg_aclconfctx_t *ctx) { return (true); } + switch (elt->geoip_elem.subtype) { + case dns_geoip_countrycode: + case dns_geoip_countryname: + case dns_geoip_continentcode: + case dns_geoip_continent: + if (ctx->geoip->country != NULL || + ctx->geoip->city != NULL) + { + return (true); + } + break; + case dns_geoip_country_code: + case dns_geoip_country_name: + case dns_geoip_country_continentcode: + case dns_geoip_country_continent: + if (ctx->geoip->country != NULL) { + return (true); + } + /* city db can answer these too, so: */ + /* FALLTHROUGH */ + case dns_geoip_region: + case dns_geoip_regionname: + case dns_geoip_city_countrycode: + case dns_geoip_city_countryname: + case dns_geoip_city_region: + case dns_geoip_city_regionname: + case dns_geoip_city_name: + case dns_geoip_city_postalcode: + case dns_geoip_city_metrocode: + case dns_geoip_city_areacode: + case dns_geoip_city_continentcode: + case dns_geoip_city_continent: + case dns_geoip_city_timezonecode: + if (ctx->geoip->city != NULL) { + return (true); + } + break; + case dns_geoip_isp_name: + if (ctx->geoip->isp != NULL) { + return (true); + } + break; + case dns_geoip_as_asnum: + case dns_geoip_org_name: + if (ctx->geoip->as != NULL) { + return (true); + } + break; + case dns_geoip_domain_name: + if (ctx->geoip->domain != NULL) { + return (true); + } + break; + default: + break; + } + + return (false); +} + +static isc_result_t +parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx, + cfg_aclconfctx_t *ctx, dns_aclelement_t *dep) +{ + const cfg_obj_t *ge; + const char *dbname = NULL; + const char *stype = NULL, *search = NULL; + dns_geoip_subtype_t subtype; + dns_aclelement_t de; + size_t len; + + REQUIRE(dep != NULL); + + de = *dep; + + ge = cfg_tuple_get(obj, "db"); + if (!cfg_obj_isvoid(ge)) { + int i; + + dbname = cfg_obj_asstring(ge); + + for (i = 0; geoip_dbnames[i] != NULL; i++) { + if (strcasecmp(dbname, geoip_dbnames[i]) == 0) { + break; + } + } + if (geoip_dbnames[i] == NULL) { + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "database '%s' is not defined for GeoIP2", + dbname); + return (ISC_R_UNEXPECTED); + } + } + + stype = cfg_obj_asstring(cfg_tuple_get(obj, "subtype")); + search = cfg_obj_asstring(cfg_tuple_get(obj, "search")); + len = strlen(search); + + if (len == 0) { + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "zero-length geoip search field"); + return (ISC_R_FAILURE); + } + + if (strcasecmp(stype, "country") == 0 && len == 2) { + /* Two-letter country code */ + subtype = dns_geoip_countrycode; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "country") == 0 && len == 3) { + /* Three-letter country code */ + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "three-letter country codes are unavailable " + "in GeoIP2 databases"); + return (ISC_R_FAILURE); + } else if (strcasecmp(stype, "country") == 0) { + /* Country name */ + subtype = dns_geoip_countryname; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "continent") == 0 && len == 2) { + /* Two-letter continent code */ + subtype = dns_geoip_continentcode; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "continent") == 0) { + subtype = dns_geoip_continent; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if ((strcasecmp(stype, "region") == 0 || + strcasecmp(stype, "subdivision") == 0) && len == 2) + { + /* Two-letter region code */ + subtype = dns_geoip_region; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "region") == 0 || + strcasecmp(stype, "subdivision") == 0) + { + /* Region name */ + subtype = dns_geoip_regionname; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "city") == 0) { + /* City name */ + subtype = dns_geoip_city_name; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "postal") == 0 || + strcasecmp(stype, "postalcode") == 0) + { + if (len < 7) { + subtype = dns_geoip_city_postalcode; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else { + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "geoiop postal code (%s) too long", + search); + return (ISC_R_FAILURE); + } + } else if (strcasecmp(stype, "metro") == 0 || + strcasecmp(stype, "metrocode") == 0) + { + subtype = dns_geoip_city_metrocode; + de.geoip_elem.as_int = atoi(search); + } else if (strcasecmp(stype, "tz") == 0 || + strcasecmp(stype, "timezone") == 0) + { + subtype = dns_geoip_city_timezonecode; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "isp") == 0) { + subtype = dns_geoip_isp_name; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "asnum") == 0) { + subtype = dns_geoip_as_asnum; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "org") == 0) { + subtype = dns_geoip_org_name; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else if (strcasecmp(stype, "domain") == 0) { + subtype = dns_geoip_domain_name; + strlcpy(de.geoip_elem.as_string, search, + sizeof(de.geoip_elem.as_string)); + } else { + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "type '%s' is unavailable " + "in GeoIP2 databases", stype); + return (ISC_R_FAILURE); + } + + de.geoip_elem.subtype = get_subtype(obj, lctx, subtype, dbname); + + if (! geoip_can_answer(&de, ctx)) { + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "no GeoIP2 database installed which can answer " + "queries of type '%s'", stype); + return (ISC_R_FAILURE); + } + + *dep = de; + + return (ISC_R_SUCCESS); +} +#elif defined(HAVE_GEOIP) +static dns_geoip_subtype_t +get_subtype(const cfg_obj_t *obj, isc_log_t *lctx, + dns_geoip_subtype_t subtype, const char *dbname) +{ + if (dbname == NULL) + return (subtype); + + switch (subtype) { + case dns_geoip_countrycode: + if (strcasecmp(dbname, "city") == 0) + return (dns_geoip_city_countrycode); + else if (strcasecmp(dbname, "region") == 0) + return (dns_geoip_region_countrycode); + else if (strcasecmp(dbname, "country") == 0) + return (dns_geoip_country_code); + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "invalid GeoIP DB specified for " + "country search: ignored"); + return (subtype); + case dns_geoip_countrycode3: + if (strcasecmp(dbname, "city") == 0) + return (dns_geoip_city_countrycode3); + else if (strcasecmp(dbname, "country") == 0) + return (dns_geoip_country_code3); + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "invalid GeoIP DB specified for " + "country search: ignored"); + return (subtype); + case dns_geoip_countryname: + if (strcasecmp(dbname, "city") == 0) + return (dns_geoip_city_countryname); + else if (strcasecmp(dbname, "country") == 0) + return (dns_geoip_country_name); + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "invalid GeoIP DB specified for " + "country search: ignored"); + return (subtype); + case dns_geoip_region: + if (strcasecmp(dbname, "city") == 0) + return (dns_geoip_city_region); + else if (strcasecmp(dbname, "region") == 0) + return (dns_geoip_region_code); + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "invalid GeoIP DB specified for " + "region search: ignored"); + return (subtype); + case dns_geoip_regionname: + if (strcasecmp(dbname, "city") == 0) + return (dns_geoip_city_region); + else if (strcasecmp(dbname, "region") == 0) + return (dns_geoip_region_name); + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "invalid GeoIP DB specified for " + "region search: ignored"); + return (subtype); + + /* + * Log a warning if the wrong database was specified + * on an unambiguous query + */ + case dns_geoip_city_name: + case dns_geoip_city_postalcode: + case dns_geoip_city_metrocode: + case dns_geoip_city_areacode: + case dns_geoip_city_continentcode: + case dns_geoip_city_timezonecode: + if (strcasecmp(dbname, "city") != 0) + cfg_obj_log(obj, lctx, ISC_LOG_WARNING, + "invalid GeoIP DB specified for " + "a 'city'-only search type: ignoring"); + return (subtype); + case dns_geoip_isp_name: + if (strcasecmp(dbname, "isp") != 0) + cfg_obj_log(obj, lctx, ISC_LOG_WARNING, + "invalid GeoIP DB specified for " + "an 'isp' search: ignoring"); + return (subtype); + case dns_geoip_org_name: + if (strcasecmp(dbname, "org") != 0) + cfg_obj_log(obj, lctx, ISC_LOG_WARNING, + "invalid GeoIP DB specified for " + "an 'org' search: ignoring"); + return (subtype); + case dns_geoip_as_asnum: + if (strcasecmp(dbname, "asnum") != 0) + cfg_obj_log(obj, lctx, ISC_LOG_WARNING, + "invalid GeoIP DB specified for " + "an 'asnum' search: ignoring"); + return (subtype); + case dns_geoip_domain_name: + if (strcasecmp(dbname, "domain") != 0) + cfg_obj_log(obj, lctx, ISC_LOG_WARNING, + "invalid GeoIP DB specified for " + "a 'domain' search: ignoring"); + return (subtype); + case dns_geoip_netspeed_id: + if (strcasecmp(dbname, "netspeed") != 0) + cfg_obj_log(obj, lctx, ISC_LOG_WARNING, + "invalid GeoIP DB specified for " + "a 'netspeed' search: ignoring"); + return (subtype); + default: + INSIST(0); + ISC_UNREACHABLE(); + } +} + +static bool +geoip_can_answer(dns_aclelement_t *elt, cfg_aclconfctx_t *ctx) { + if (ctx->geoip == NULL) + return (true); + switch (elt->geoip_elem.subtype) { case dns_geoip_countrycode: case dns_geoip_countrycode3: @@ -468,39 +811,42 @@ geoip_can_answer(dns_aclelement_t *elt, cfg_aclconfctx_t *ctx) { case dns_geoip_city_timezonecode: if (ctx->geoip->city_v4 != NULL || ctx->geoip->city_v6 != NULL) - { return (true); - } /* FALLTHROUGH */ case dns_geoip_isp_name: - if (ctx->geoip->isp != NULL) { + if (ctx->geoip->isp != NULL) return (true); - } /* FALLTHROUGH */ case dns_geoip_org_name: - if (ctx->geoip->org != NULL) { + if (ctx->geoip->org != NULL) return (true); - } /* FALLTHROUGH */ case dns_geoip_as_asnum: - if (ctx->geoip->as != NULL) { + if (ctx->geoip->as != NULL) return (true); - } /* FALLTHROUGH */ case dns_geoip_domain_name: - if (ctx->geoip->domain != NULL) { + if (ctx->geoip->domain != NULL) return (true); - } /* FALLTHROUGH */ case dns_geoip_netspeed_id: - if (ctx->geoip->netspeed != NULL) { + if (ctx->geoip->netspeed != NULL) return (true); - } + /* + * The following enums are only valid with GeoIP2, + * not legacy GeoIP. + */ + case dns_geoip_continentcode: + case dns_geoip_continent: + case dns_geoip_country_continentcode: + case dns_geoip_country_continent: + case dns_geoip_city_continent: + INSIST(0); + ISC_UNREACHABLE(); } return (false); } -#endif static isc_result_t parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx, @@ -508,7 +854,7 @@ parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx, { const cfg_obj_t *ge; const char *dbname = NULL; - const char *stype, *search; + const char *stype = NULL, *search = NULL; dns_geoip_subtype_t subtype; dns_aclelement_t de; size_t len; @@ -519,7 +865,21 @@ parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx, ge = cfg_tuple_get(obj, "db"); if (!cfg_obj_isvoid(ge)) { + int i; + dbname = cfg_obj_asstring(ge); + + for (i = 0; geoip_dbnames[i] != NULL; i++) { + if (strcasecmp(dbname, geoip_dbnames[i]) == 0) { + break; + } + } + if (geoip_dbnames[i] == NULL) { + cfg_obj_log(obj, lctx, ISC_LOG_ERROR, + "database '%s' is not defined for GeoIP", + dbname); + return (ISC_R_UNEXPECTED); + } } stype = cfg_obj_asstring(cfg_tuple_get(obj, "subtype")); @@ -637,6 +997,7 @@ parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx, return (ISC_R_SUCCESS); } +#endif /* HAVE_GEOIP */ isc_result_t cfg_acl_fromconfig(const cfg_obj_t *caml, const cfg_obj_t *cctx,