]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
implement searching of geoip2 database
authorEvan Hunt <each@isc.org>
Fri, 28 Jun 2019 01:16:46 +0000 (18:16 -0700)
committerEvan Hunt <each@isc.org>
Tue, 2 Jul 2019 19:29:39 +0000 (12:29 -0700)
- 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)

bin/named/geoip.c
bin/named/include/named/geoip.h
bin/named/server.c
lib/dns/acl.c
lib/dns/geoip2.c
lib/dns/include/dns/acl.h
lib/dns/include/dns/geoip.h
lib/dns/tests/geoip_test.c
lib/isccfg/aclconf.c

index f29de0112ab2f39186770efbad565f6e3037d97e..6bf0ab1a31f3028002e37a907a2f9bc21282ebee 100644 (file)
 
 #include <config.h>
 
+#if defined(HAVE_GEOIP2)
+#include <maxminddb.h>
+#elif defined(HAVE_GEOIP)
+#include <GeoIP.h>
+#include <GeoIPCity.h>
+#endif
+
+#include <isc/print.h>
+#include <isc/string.h>
 #include <isc/util.h>
 
+#include <dns/geoip.h>
+
 #include <named/log.h>
 #include <named/geoip.h>
 
-#include <dns/geoip.h>
-
-#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();
+}
index 04a5a3964cbad1b4e24357369d85db62d4ef1b2f..38014ffc1d896c58ae8dff8cbd1426195204f0ed 100644 (file)
 #ifndef _GEOIP_H
 #define _GEOIP_H
 
-#ifdef HAVE_GEOIP
-#include <GeoIP.h>
-#include <GeoIPCity.h>
-#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
index 242d61eae1e0088f1e9c7e632e4d6c6351c611c7..1ed1f1e5bd0e83c5662c03aa1a901002b9f2c1cf 100644 (file)
@@ -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);
index 0c82b4069aad86b075f25cb9cb0ce4bbc0ce9720..72b8622b31fdfe513f9abad7c6e688972d354e08 100644 (file)
@@ -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
 
index 029d85563efe80e67245851ef712237fe8f0f6ad..9660260e6b09a2b4cfb2c1332bc43f79209e25bb 100644 (file)
 
 /*! \file */
 
-#include <dns/geoip.h>
+#include <config.h>
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+/*
+ * This file is only built and linked if GeoIP2 has been configured.
+ */
+#include <maxminddb.h>
 
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/thread.h>
 #include <isc/util.h>
 
+#include <dns/acl.h>
+#include <dns/geoip.h>
+
+#include <math.h>
+#ifndef WIN32
+#include <netinet/in.h>
+#else
+#ifndef _WINSOCKAPI_
+#define _WINSOCKAPI_   /* Prevent inclusion of winsock.h in windows.h */
+#endif
+#include <winsock2.h>
+#endif /* WIN32 */
+#include <dns/log.h>
+
+/*
+ * 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);
+       }
 }
index 8091cc155981260821338603714ed961af5eb16b..f868d15793c2c0416f0ebe509271f4efbc5a528f 100644 (file)
 #include <dns/types.h>
 #include <dns/iptable.h>
 
-#if defined(HAVE_GEOIP2)
-#include <maxminddb.h>
-#elif defined(HAVE_GEOIP)
-#include <GeoIP.h>
-#endif
-
 /***
  *** Types
  ***/
index fc0fc094fbd0fd5f582f0e82154cdcbc7778b976..c560dbba639c8e102ba117d29c9abfaa16b40b9b 100644 (file)
@@ -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;
 
index 58469fc65d44aac79bb80221eddd4f419491032c..9a57e9db8701cd108129fc5f222c5a51d3d96711 100644 (file)
@@ -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,
index deaa1c579c083b1a940e64d6472cfeef91a250ed..132e651bc34e36a214369efac661d1e9e03980fe 100644 (file)
@@ -13,6 +13,7 @@
 
 #include <inttypes.h>
 #include <stdbool.h>
+#include <stdlib.h>
 
 #include <isc/mem.h>
 #include <isc/print.h>
 #include <dns/fixedname.h>
 #include <dns/log.h>
 
-#ifdef HAVE_GEOIP
-#include <stdlib.h>
-#include <math.h>
-#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,