mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-31 06:25:31 +00:00
[master] add geoip support
3504. [func] Add support for ACLs based on geographic location, using MaxMind GeoIP databases. Based on code contributed by Ken Brownfield <kb@slide.com>. [RT #30681]
This commit is contained in:
@@ -31,6 +31,11 @@
|
||||
#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')
|
||||
|
||||
isc_result_t
|
||||
@@ -53,6 +58,8 @@ cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **ret) {
|
||||
isc_mem_attach(mctx, &actx->mctx);
|
||||
ISC_LIST_INIT(actx->named_acl_cache);
|
||||
|
||||
actx->geoip = NULL;
|
||||
|
||||
*ret = actx;
|
||||
return (ISC_R_SUCCESS);
|
||||
|
||||
@@ -230,11 +237,15 @@ count_acl_elements(const cfg_obj_t *caml, const cfg_obj_t *cctx,
|
||||
elt = cfg_list_next(elt)) {
|
||||
const cfg_obj_t *ce = cfg_listelt_value(elt);
|
||||
|
||||
/* negated element; just get the value. */
|
||||
/* might be a negated element, in which case get the value. */
|
||||
if (cfg_obj_istuple(ce)) {
|
||||
ce = cfg_tuple_get(ce, "value");
|
||||
if (has_negative != NULL)
|
||||
*has_negative = ISC_TRUE;
|
||||
const cfg_obj_t *negated =
|
||||
cfg_tuple_get(ce, "negated");
|
||||
if (! cfg_obj_isvoid(negated)) {
|
||||
ce = negated;
|
||||
if (has_negative != NULL)
|
||||
*has_negative = ISC_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg_obj_istype(ce, &cfg_type_keyref)) {
|
||||
@@ -244,6 +255,12 @@ count_acl_elements(const cfg_obj_t *caml, const cfg_obj_t *cctx,
|
||||
n += count_acl_elements(ce, cctx, &negative);
|
||||
if (negative)
|
||||
n++;
|
||||
#ifdef HAVE_GEOIP
|
||||
} else if (cfg_obj_istuple(ce) &&
|
||||
cfg_obj_isvoid(cfg_tuple_get(ce, "negated")))
|
||||
{
|
||||
n++;
|
||||
#endif /* HAVE_GEOIP */
|
||||
} else if (cfg_obj_isstring(ce)) {
|
||||
const char *name = cfg_obj_asstring(ce);
|
||||
if (strcasecmp(name, "localhost") == 0 ||
|
||||
@@ -262,6 +279,267 @@ count_acl_elements(const cfg_obj_t *caml, const cfg_obj_t *cctx,
|
||||
return n;
|
||||
}
|
||||
|
||||
#ifdef 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);
|
||||
}
|
||||
}
|
||||
|
||||
static isc_boolean_t
|
||||
geoip_can_answer(dns_aclelement_t *elt, cfg_aclconfctx_t *ctx) {
|
||||
if (ctx->geoip == NULL)
|
||||
return (ISC_TRUE);
|
||||
|
||||
switch (elt->geoip_elem.subtype) {
|
||||
case dns_geoip_countrycode:
|
||||
case dns_geoip_countrycode3:
|
||||
case dns_geoip_countryname:
|
||||
if (ctx->geoip->city_v4 != NULL ||
|
||||
ctx->geoip->city_v6 != NULL ||
|
||||
ctx->geoip->country_v4 != NULL ||
|
||||
ctx->geoip->country_v6 != NULL ||
|
||||
ctx->geoip->region != NULL)
|
||||
return (ISC_TRUE);
|
||||
case dns_geoip_region:
|
||||
case dns_geoip_regionname:
|
||||
if (ctx->geoip->city_v4 != NULL ||
|
||||
ctx->geoip->city_v6 != NULL ||
|
||||
ctx->geoip->region != NULL)
|
||||
return (ISC_TRUE);
|
||||
case dns_geoip_country_code:
|
||||
case dns_geoip_country_code3:
|
||||
case dns_geoip_country_name:
|
||||
if (ctx->geoip->country_v4 != NULL ||
|
||||
ctx->geoip->country_v6 != NULL)
|
||||
return (ISC_TRUE);
|
||||
case dns_geoip_region_countrycode:
|
||||
case dns_geoip_region_code:
|
||||
case dns_geoip_region_name:
|
||||
if (ctx->geoip->region != NULL)
|
||||
return (ISC_TRUE);
|
||||
case dns_geoip_city_countrycode:
|
||||
case dns_geoip_city_countrycode3:
|
||||
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_timezonecode:
|
||||
if (ctx->geoip->city_v4 != NULL ||
|
||||
ctx->geoip->city_v6 != NULL)
|
||||
return (ISC_TRUE);
|
||||
case dns_geoip_isp_name:
|
||||
if (ctx->geoip->isp != NULL)
|
||||
return (ISC_TRUE);
|
||||
case dns_geoip_org_name:
|
||||
if (ctx->geoip->org != NULL)
|
||||
return (ISC_TRUE);
|
||||
case dns_geoip_as_asnum:
|
||||
if (ctx->geoip->as != NULL)
|
||||
return (ISC_TRUE);
|
||||
case dns_geoip_domain_name:
|
||||
if (ctx->geoip->domain != NULL)
|
||||
return (ISC_TRUE);
|
||||
case dns_geoip_netspeed_id:
|
||||
if (ctx->geoip->netspeed != NULL)
|
||||
return (ISC_TRUE);
|
||||
}
|
||||
|
||||
return (ISC_FALSE);
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx,
|
||||
cfg_aclconfctx_t *ctx, dns_aclelement_t *de)
|
||||
{
|
||||
const cfg_obj_t *ge;
|
||||
const char *dbname = NULL;
|
||||
const char *stype, *search;
|
||||
dns_geoip_subtype_t subtype;
|
||||
|
||||
ge = cfg_tuple_get(obj, "db");
|
||||
if (!cfg_obj_isvoid(ge))
|
||||
dbname = cfg_obj_asstring(ge);
|
||||
|
||||
stype = cfg_obj_asstring(cfg_tuple_get(obj, "subtype"));
|
||||
search = cfg_obj_asstring(cfg_tuple_get(obj, "search"));
|
||||
|
||||
if (strcasecmp(stype, "country") == 0 && strlen(search) == 2) {
|
||||
/* Two-letter country code */
|
||||
subtype = dns_geoip_countrycode;
|
||||
strncpy(de->geoip_elem.as_string, search, 2);
|
||||
} else if (strcasecmp(stype, "country") == 0 && strlen(search) == 3) {
|
||||
/* Three-letter country code */
|
||||
subtype = dns_geoip_countrycode3;
|
||||
strncpy(de->geoip_elem.as_string, search, 3);
|
||||
} else if (strcasecmp(stype, "country") == 0) {
|
||||
/* Country name */
|
||||
subtype = dns_geoip_countryname;
|
||||
strncpy(de->geoip_elem.as_string, search, 255);
|
||||
} else if (strcasecmp(stype, "region") == 0 && strlen(search) == 2) {
|
||||
/* Two-letter region code */
|
||||
subtype = dns_geoip_region;
|
||||
strncpy(de->geoip_elem.as_string, search, 2);
|
||||
} else if (strcasecmp(stype, "region") == 0) {
|
||||
/* Region name */
|
||||
subtype = dns_geoip_regionname;
|
||||
strncpy(de->geoip_elem.as_string, search, 255);
|
||||
} else if (strcasecmp(stype, "city") == 0) {
|
||||
/* City name */
|
||||
subtype = dns_geoip_city_name;
|
||||
strncpy(de->geoip_elem.as_string, search, 255);
|
||||
} else if (strcasecmp(stype, "postal") == 0 && strlen(search) < 7) {
|
||||
subtype = dns_geoip_city_postalcode;
|
||||
strncpy(de->geoip_elem.as_string, search, 6);
|
||||
de->geoip_elem.as_string[6] = '\0';
|
||||
} else if (strcasecmp(stype, "metro") == 0) {
|
||||
subtype = dns_geoip_city_metrocode;
|
||||
de->geoip_elem.as_int = atoi(search);
|
||||
} else if (strcasecmp(stype, "area") == 0) {
|
||||
subtype = dns_geoip_city_areacode;
|
||||
de->geoip_elem.as_int = atoi(search);
|
||||
} else if (strcasecmp(stype, "tz") == 0) {
|
||||
subtype = dns_geoip_city_timezonecode;
|
||||
strncpy(de->geoip_elem.as_string, search, 255);
|
||||
} else if (strcasecmp(stype, "continent") == 0 && strlen(search) == 2) {
|
||||
/* Two-letter continent code */
|
||||
subtype = dns_geoip_city_continentcode;
|
||||
strncpy(de->geoip_elem.as_string, search, 2);
|
||||
} else if (strcasecmp(stype, "isp") == 0) {
|
||||
subtype = dns_geoip_isp_name;
|
||||
strncpy(de->geoip_elem.as_string, search, 255);
|
||||
} else if (strcasecmp(stype, "asnum") == 0) {
|
||||
subtype = dns_geoip_as_asnum;
|
||||
strncpy(de->geoip_elem.as_string, search, 255);
|
||||
} else if (strcasecmp(stype, "org") == 0) {
|
||||
subtype = dns_geoip_org_name;
|
||||
strncpy(de->geoip_elem.as_string, search, 255);
|
||||
} else if (strcasecmp(stype, "domain") == 0) {
|
||||
subtype = dns_geoip_domain_name;
|
||||
strncpy(de->geoip_elem.as_string, search, 255);
|
||||
} else if (strcasecmp(stype, "netspeed") == 0) {
|
||||
subtype = dns_geoip_netspeed_id;
|
||||
de->geoip_elem.as_int = atoi(search);
|
||||
} else
|
||||
INSIST(0);
|
||||
|
||||
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 GeoIP database installed which can answer "
|
||||
"queries of type '%s'", stype);
|
||||
return (ISC_R_FAILURE);
|
||||
}
|
||||
|
||||
return (ISC_R_SUCCESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
isc_result_t
|
||||
cfg_acl_fromconfig(const cfg_obj_t *caml,
|
||||
const cfg_obj_t *cctx,
|
||||
@@ -317,15 +595,18 @@ cfg_acl_fromconfig(const cfg_obj_t *caml,
|
||||
elt != NULL;
|
||||
elt = cfg_list_next(elt)) {
|
||||
const cfg_obj_t *ce = cfg_listelt_value(elt);
|
||||
isc_boolean_t neg;
|
||||
isc_boolean_t neg = ISC_FALSE;
|
||||
|
||||
if (cfg_obj_istuple(ce)) {
|
||||
/* This must be a negated element. */
|
||||
ce = cfg_tuple_get(ce, "value");
|
||||
neg = ISC_TRUE;
|
||||
dacl->has_negatives = ISC_TRUE;
|
||||
} else
|
||||
neg = ISC_FALSE;
|
||||
/* Might be a negated element */
|
||||
const cfg_obj_t *negated =
|
||||
cfg_tuple_get(ce, "negated");
|
||||
if (! cfg_obj_isvoid(negated)) {
|
||||
neg = ISC_TRUE;
|
||||
dacl->has_negatives = ISC_TRUE;
|
||||
ce = negated;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If nest_level is nonzero, then every element is
|
||||
@@ -405,6 +686,16 @@ nested_acl:
|
||||
&de->keyname);
|
||||
if (result != ISC_R_SUCCESS)
|
||||
goto cleanup;
|
||||
#ifdef HAVE_GEOIP
|
||||
} else if (cfg_obj_istuple(ce) &&
|
||||
cfg_obj_isvoid(cfg_tuple_get(ce, "negated")))
|
||||
{
|
||||
result = parse_geoip_element(ce, lctx, ctx, de);
|
||||
if (result != ISC_R_SUCCESS)
|
||||
goto cleanup;
|
||||
de->type = dns_aclelementtype_geoip;
|
||||
de->negative = neg;
|
||||
#endif /* HAVE_GEOIP */
|
||||
} else if (cfg_obj_isstring(ce)) {
|
||||
/* ACL name. */
|
||||
const char *name = cfg_obj_asstring(ce);
|
||||
|
Reference in New Issue
Block a user