diff --git a/CHANGES b/CHANGES index 40cc370f86..e8ea884406 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,5 @@ +3008. [func] Response policy zones (RPZ) support. [RT #21726] + 3007. [bug] Named failed to preserve the case of domain names in rdata which is no compressable when writing master files. [RT #22863] diff --git a/bin/named/include/named/query.h b/bin/named/include/named/query.h index 2f65df3b79..b5bb461f1a 100644 --- a/bin/named/include/named/query.h +++ b/bin/named/include/named/query.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: query.h,v 1.43 2010/12/08 02:46:15 marka Exp $ */ +/* $Id: query.h,v 1.44 2011/01/13 01:59:25 marka Exp $ */ #ifndef NAMED_QUERY_H #define NAMED_QUERY_H 1 @@ -26,8 +26,9 @@ #include #include -#include #include +#include +#include #include @@ -35,6 +36,7 @@ typedef struct ns_dbversion { dns_db_t *db; dns_dbversion_t *version; + isc_boolean_t acl_checked; isc_boolean_t queryok; ISC_LINK(struct ns_dbversion) link; } ns_dbversion_t; @@ -55,6 +57,7 @@ struct ns_query { isc_boolean_t isreferral; isc_mutex_t fetchlock; dns_fetch_t * fetch; + dns_rpz_st_t * rpz_st; isc_bufferlist_t namebufs; ISC_LIST(ns_dbversion_t) activeversions; ISC_LIST(ns_dbversion_t) freeversions; diff --git a/bin/named/query.c b/bin/named/query.c index 558ec3908a..05f3617c3a 100644 --- a/bin/named/query.c +++ b/bin/named/query.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: query.c,v 1.350 2010/12/24 02:20:47 each Exp $ */ +/* $Id: query.c,v 1.351 2011/01/13 01:59:25 marka Exp $ */ /*! \file */ @@ -134,6 +134,7 @@ #define DNS_GETDB_NOEXACT 0x01U #define DNS_GETDB_NOLOG 0x02U #define DNS_GETDB_PARTIAL 0x04U +#define DNS_GETDB_IGNOREACL 0x08U #define PENDINGOK(x) (((x) & DNS_DBFIND_PENDINGOK) != 0) @@ -159,6 +160,9 @@ query_findclosestnsec3(dns_name_t *qname, dns_db_t *db, static inline void log_queryerror(ns_client_t *client, isc_result_t result, int line, int level); +static void +rpz_st_clear(ns_client_t *client); + /*% * Increment query statistics counters. */ @@ -353,6 +357,14 @@ query_reset(ns_client_t *client, isc_boolean_t everything) { NS_QUERYATTR_SECURE); client->query.restarts = 0; client->query.timerset = ISC_FALSE; + if (client->query.rpz_st != NULL) { + rpz_st_clear(client); + if (everything) { + isc_mem_put(client->mctx, client->query.rpz_st, + sizeof(*client->query.rpz_st)); + client->query.rpz_st = NULL; + } + } client->query.origqname = NULL; client->query.dboptions = 0; client->query.fetchoptions = 0; @@ -570,6 +582,7 @@ ns_query_init(ns_client_t *client) { ISC_LIST_INIT(client->query.freeversions); client->query.restarts = 0; client->query.timerset = ISC_FALSE; + client->query.rpz_st = NULL; client->query.qname = NULL; result = isc_mutex_init(&client->query.fetchlock); if (result != ISC_R_SUCCESS) @@ -597,8 +610,7 @@ ns_query_init(ns_client_t *client) { } static inline ns_dbversion_t * -query_findversion(ns_client_t *client, dns_db_t *db, - isc_boolean_t *newzonep) +query_findversion(ns_client_t *client, dns_db_t *db) { ns_dbversion_t *dbversion; @@ -624,12 +636,10 @@ query_findversion(ns_client_t *client, dns_db_t *db, return (NULL); dns_db_attach(db, &dbversion->db); dns_db_currentversion(db, &dbversion->version); - dbversion->queryok = ISC_FALSE; + dbversion->acl_checked = ISC_FALSE; ISC_LIST_APPEND(client->query.activeversions, dbversion, link); - *newzonep = ISC_TRUE; - } else - *newzonep = ISC_FALSE; + } return (dbversion); } @@ -641,7 +651,6 @@ query_validatezonedb(ns_client_t *client, dns_name_t *name, dns_dbversion_t **versionp) { isc_result_t result; - isc_boolean_t check_acl, new_zone; dns_acl_t *queryacl; ns_dbversion_t *dbversion; @@ -657,7 +666,7 @@ query_validatezonedb(ns_client_t *client, dns_name_t *name, if (!client->view->additionalfromauth && client->query.authdbset && db != client->query.authdb) - goto refuse; + return (DNS_R_REFUSED); /* * Non recursive query to a static-stub zone is prohibited; its @@ -666,7 +675,7 @@ query_validatezonedb(ns_client_t *client, dns_name_t *name, */ if (dns_zone_gettype(zone) == dns_zone_staticstub && !RECURSIONOK(client)) { - goto refuse; + return (DNS_R_REFUSED); } /* @@ -677,23 +686,19 @@ query_validatezonedb(ns_client_t *client, dns_name_t *name, * Also, get the database version to use. */ - check_acl = ISC_TRUE; /* Keep compiler happy. */ - queryacl = NULL; - /* * Get the current version of this database. */ - dbversion = query_findversion(client, db, &new_zone); - if (dbversion == NULL) { - result = DNS_R_SERVFAIL; - goto fail; - } - if (new_zone) { - check_acl = ISC_TRUE; - } else if (!dbversion->queryok) { - goto refuse; - } else { - check_acl = ISC_FALSE; + dbversion = query_findversion(client, db); + if (dbversion == NULL) + return (DNS_R_SERVFAIL); + + if ((options & DNS_GETDB_IGNOREACL) != 0) + goto approved; + if (dbversion->acl_checked) { + if (!dbversion->queryok) + return (DNS_R_REFUSED); + goto approved; } queryacl = dns_zone_getqueryacl(zone); @@ -707,88 +712,68 @@ query_validatezonedb(ns_client_t *client, dns_name_t *name, * allowed to make queries, otherwise the query should * be refused. */ - check_acl = ISC_FALSE; + dbversion->acl_checked = ISC_TRUE; if ((client->query.attributes & - NS_QUERYATTR_QUERYOK) == 0) - goto refuse; - } else { - /* - * We haven't evaluated the view's queryacl yet. - */ - check_acl = ISC_TRUE; + NS_QUERYATTR_QUERYOK) == 0) { + dbversion->queryok = ISC_FALSE; + return (DNS_R_REFUSED); + } + dbversion->queryok = ISC_TRUE; + goto approved; } } - if (check_acl) { - isc_boolean_t log = ISC_TF((options & DNS_GETDB_NOLOG) == 0); - - result = ns_client_checkaclsilent(client, NULL, queryacl, - ISC_TRUE); - if (log) { - char msg[NS_CLIENT_ACLMSGSIZE("query")]; - if (result == ISC_R_SUCCESS) { - if (isc_log_wouldlog(ns_g_lctx, - ISC_LOG_DEBUG(3))) - { - ns_client_aclmsg("query", name, qtype, - client->view->rdclass, - msg, sizeof(msg)); - ns_client_log(client, - DNS_LOGCATEGORY_SECURITY, - NS_LOGMODULE_QUERY, - ISC_LOG_DEBUG(3), - "%s approved", msg); - } - } else { + result = ns_client_checkaclsilent(client, NULL, queryacl, ISC_TRUE); + if ((options & DNS_GETDB_NOLOG) == 0) { + char msg[NS_CLIENT_ACLMSGSIZE("query")]; + if (result == ISC_R_SUCCESS) { + if (isc_log_wouldlog(ns_g_lctx, ISC_LOG_DEBUG(3))) { ns_client_aclmsg("query", name, qtype, client->view->rdclass, msg, sizeof(msg)); - ns_client_log(client, DNS_LOGCATEGORY_SECURITY, - NS_LOGMODULE_QUERY, ISC_LOG_INFO, - "%s denied", msg); + ns_client_log(client, + DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_QUERY, + ISC_LOG_DEBUG(3), + "%s approved", msg); } + } else { + ns_client_aclmsg("query", name, qtype, + client->view->rdclass, + msg, sizeof(msg)); + ns_client_log(client, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_QUERY, ISC_LOG_INFO, + "%s denied", msg); } - - if (queryacl == client->view->queryacl) { - if (result == ISC_R_SUCCESS) { - /* - * We were allowed by the default - * "allow-query" ACL. Remember this so we - * don't have to check again. - */ - client->query.attributes |= - NS_QUERYATTR_QUERYOK; - } - /* - * We've now evaluated the view's query ACL, and - * the NS_QUERYATTR_QUERYOK attribute is now valid. - */ - client->query.attributes |= NS_QUERYATTR_QUERYOKVALID; - } - - if (result != ISC_R_SUCCESS) - goto refuse; } - /* Approved. */ + if (queryacl == client->view->queryacl) { + if (result == ISC_R_SUCCESS) { + /* + * We were allowed by the default + * "allow-query" ACL. Remember this so we + * don't have to check again. + */ + client->query.attributes |= NS_QUERYATTR_QUERYOK; + } + /* + * We've now evaluated the view's query ACL, and + * the NS_QUERYATTR_QUERYOK attribute is now valid. + */ + client->query.attributes |= NS_QUERYATTR_QUERYOKVALID; + } - /* - * Remember the result of the ACL check so we - * don't have to check again. - */ - dbversion->queryok = ISC_TRUE; + dbversion->acl_checked = ISC_TRUE; + if (result != ISC_R_SUCCESS) { + dbversion->queryok = ISC_FALSE; + return (DNS_R_REFUSED); + } + approved: /* Transfer ownership, if necessary. */ if (versionp != NULL) *versionp = dbversion->version; - return (ISC_R_SUCCESS); - - refuse: - return (DNS_R_REFUSED); - - fail: - return (result); } static inline isc_result_t @@ -844,6 +829,97 @@ query_getzonedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, return (result); } +static void +rpz_log(ns_client_t *client) { + char namebuf1[DNS_NAME_FORMATSIZE]; + char namebuf2[DNS_NAME_FORMATSIZE]; + dns_rpz_st_t *st; + const char *pat; + + if (!ns_g_server->log_queries || + !isc_log_wouldlog(ns_g_lctx, DNS_RPZ_INFO_LEVEL)) + return; + + st = client->query.rpz_st; + dns_name_format(client->query.qname, namebuf1, sizeof(namebuf1)); + dns_name_format(st->qname, namebuf2, sizeof(namebuf2)); + + switch (st->m.policy) { + case DNS_RPZ_POLICY_NO_OP: + pat ="response policy %s rewrite %s NO-OP using %s"; + break; + case DNS_RPZ_POLICY_NXDOMAIN: + pat = "response policy %s rewrite %s to NXDOMAIN using %s"; + break; + case DNS_RPZ_POLICY_NODATA: + pat = "response policy %s rewrite %s to NODATA using %s"; + break; + case DNS_RPZ_POLICY_RECORD: + case DNS_RPZ_POLICY_CNAME: + pat = "response policy %s rewrite %s using %s"; + break; + default: + INSIST(0); + } + ns_client_log(client, NS_LOGCATEGORY_QUERIES, NS_LOGMODULE_QUERY, + DNS_RPZ_INFO_LEVEL, pat, dns_rpz_type2str(st->m.type), + namebuf1, namebuf2); +} + +static void +rpz_fail_log(ns_client_t *client, int level, dns_rpz_type_t rpz_type, + dns_name_t *name, const char *str, isc_result_t result) +{ + char namebuf1[DNS_NAME_FORMATSIZE]; + char namebuf2[DNS_NAME_FORMATSIZE]; + + if (!ns_g_server->log_queries || !isc_log_wouldlog(ns_g_lctx, level)) + return; + + dns_name_format(client->query.qname, namebuf1, sizeof(namebuf1)); + dns_name_format(name, namebuf2, sizeof(namebuf2)); + ns_client_log(client, NS_LOGCATEGORY_QUERY_EERRORS, + NS_LOGMODULE_QUERY, level, + "response policy %s rewrite %s via %s %sfailed: %s", + dns_rpz_type2str(rpz_type), + namebuf1, namebuf2, str, isc_result_totext(result)); +} + +/* + * Get a policy rewrite zone database. + */ +static isc_result_t +rpz_getdb(ns_client_t *client, dns_rpz_type_t rpz_type, + dns_name_t *rpz_qname, dns_zone_t **zonep, + dns_db_t **dbp, dns_dbversion_t **versionp) +{ + char namebuf1[DNS_NAME_FORMATSIZE]; + char namebuf2[DNS_NAME_FORMATSIZE]; + dns_dbversion_t *rpz_version = NULL; + isc_result_t result; + + result = query_getzonedb(client, rpz_qname, dns_rdatatype_any, + DNS_GETDB_IGNOREACL, zonep, dbp, &rpz_version); + if (result == ISC_R_SUCCESS) { + if (ns_g_server->log_queries && + isc_log_wouldlog(ns_g_lctx, DNS_RPZ_DEBUG_LEVEL2)) { + dns_name_format(client->query.qname, namebuf1, + sizeof(namebuf1)); + dns_name_format(rpz_qname, namebuf2, sizeof(namebuf2)); + ns_client_log(client, NS_LOGCATEGORY_QUERIES, + NS_LOGMODULE_QUERY, DNS_RPZ_DEBUG_LEVEL2, + "try rpz %s rewrite %s via %s", + dns_rpz_type2str(rpz_type), + namebuf1, namebuf2); + } + *versionp = rpz_version; + return (ISC_R_SUCCESS); + } + rpz_fail_log(client, DNS_RPZ_ERROR_LEVEL, rpz_type, rpz_qname, + "query_getzonedb() ", result); + return (result); +} + static inline isc_result_t query_getcachedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, dns_db_t **dbp, unsigned int options) @@ -2608,67 +2684,79 @@ query_addns(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version) { return (eresult); } -static inline isc_result_t -query_addcnamelike(ns_client_t *client, dns_name_t *qname, dns_name_t *tname, - dns_rdataset_t *dname, dns_name_t **anamep, - dns_rdatatype_t type) +static isc_result_t +query_add_cname(ns_client_t *client, dns_name_t *qname, dns_name_t *tname, + dns_trust_t trust, dns_ttl_t ttl) { dns_rdataset_t *rdataset; dns_rdatalist_t *rdatalist; dns_rdata_t *rdata; - isc_result_t result; isc_region_t r; + dns_name_t *aname; + isc_result_t result; /* * We assume the name data referred to by tname won't go away. */ - REQUIRE(anamep != NULL); - - rdatalist = NULL; - result = dns_message_gettemprdatalist(client->message, &rdatalist); + aname = NULL; + result = dns_message_gettempname(client->message, &aname); if (result != ISC_R_SUCCESS) return (result); - rdata = NULL; - result = dns_message_gettemprdata(client->message, &rdata); - if (result != ISC_R_SUCCESS) - return (result); - rdataset = NULL; - result = dns_message_gettemprdataset(client->message, &rdataset); - if (result != ISC_R_SUCCESS) - return (result); - dns_rdataset_init(rdataset); - result = dns_name_dup(qname, client->mctx, *anamep); + result = dns_name_dup(qname, client->mctx, aname); if (result != ISC_R_SUCCESS) { - dns_message_puttemprdataset(client->message, &rdataset); + dns_message_puttempname(client->message, &aname); return (result); } - rdatalist->type = type; + rdatalist = NULL; + result = dns_message_gettemprdatalist(client->message, &rdatalist); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(client->message, &aname); + return (result); + } + rdata = NULL; + result = dns_message_gettemprdata(client->message, &rdata); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(client->message, &aname); + dns_message_puttemprdatalist(client->message, &rdatalist); + return (result); + } + rdataset = NULL; + result = dns_message_gettemprdataset(client->message, &rdataset); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(client->message, &aname); + dns_message_puttemprdatalist(client->message, &rdatalist); + dns_message_puttemprdata(client->message, &rdata); + return (result); + } + dns_rdataset_init(rdataset); + rdatalist->type = dns_rdatatype_cname; rdatalist->covers = 0; rdatalist->rdclass = client->message->rdclass; - rdatalist->ttl = dname->ttl; + rdatalist->ttl = ttl; dns_name_toregion(tname, &r); rdata->data = r.base; rdata->length = r.length; rdata->rdclass = client->message->rdclass; - rdata->type = type; + rdata->type = dns_rdatatype_cname; ISC_LIST_INIT(rdatalist->rdata); ISC_LIST_APPEND(rdatalist->rdata, rdata, link); RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) == ISC_R_SUCCESS); - rdataset->trust = dname->trust; + rdataset->trust = trust; - query_addrrset(client, anamep, &rdataset, NULL, NULL, + query_addrrset(client, &aname, &rdataset, NULL, NULL, DNS_SECTION_ANSWER); - if (rdataset != NULL) { if (dns_rdataset_isassociated(rdataset)) dns_rdataset_disassociate(rdataset); dns_message_puttemprdataset(client->message, &rdataset); } + if (aname != NULL) + dns_message_puttempname(client->message, &aname); return (ISC_R_SUCCESS); } @@ -3558,8 +3646,9 @@ query_resume(isc_task_t *task, isc_event_t *event) { } static isc_result_t -query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qdomain, - dns_rdataset_t *nameservers, isc_boolean_t resuming) +query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, + dns_name_t *qdomain, dns_rdataset_t *nameservers, + isc_boolean_t resuming) { isc_result_t result; dns_rdataset_t *rdataset, *sigrdataset; @@ -3659,8 +3748,7 @@ query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qdomain, else peeraddr = NULL; result = dns_resolver_createfetch2(client->view->resolver, - client->query.qname, - qtype, qdomain, nameservers, + qname, qtype, qdomain, nameservers, NULL, peeraddr, client->message->id, client->query.fetchoptions, client->task, @@ -3683,6 +3771,685 @@ query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qdomain, return (result); } +static inline void +rpz_clean(dns_zone_t **zonep, dns_db_t **dbp, dns_dbnode_t **nodep, + dns_rdataset_t **rdatasetp) +{ + if (nodep != NULL && *nodep != NULL) { + REQUIRE(dbp != NULL && *dbp != NULL); + dns_db_detachnode(*dbp, nodep); + } + if (dbp != NULL && *dbp != NULL) + dns_db_detach(dbp); + if (zonep != NULL && *zonep != NULL) + dns_zone_detach(zonep); + if (rdatasetp != NULL && *rdatasetp != NULL && + dns_rdataset_isassociated(*rdatasetp)) + dns_rdataset_disassociate(*rdatasetp); +} + +static inline isc_result_t +rpz_ready(ns_client_t *client, dns_zone_t **zonep, dns_db_t **dbp, + dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp) +{ + REQUIRE(rdatasetp != NULL); + + rpz_clean(zonep, dbp, nodep, rdatasetp); + if (*rdatasetp == NULL) { + *rdatasetp = query_newrdataset(client); + if (*rdatasetp == NULL) + return (DNS_R_SERVFAIL); + } + return (ISC_R_SUCCESS); +} + +static void +rpz_st_clear(ns_client_t *client) { + dns_rpz_st_t *st = client->query.rpz_st; + + rpz_clean(&st->m.zone, &st->m.db, &st->m.node, NULL); + if (st->m.rdataset != NULL) + query_putrdataset(client, &st->m.rdataset); + + rpz_clean(NULL, &st->ns.db, NULL, NULL); + if (st->ns.ns_rdataset != NULL) + query_putrdataset(client, &st->ns.ns_rdataset); + if (st->ns.r_rdataset != NULL) + query_putrdataset(client, &st->ns.r_rdataset); + + rpz_clean(&st->q.zone, &st->q.db, &st->q.node, NULL); + if (st->q.rdataset != NULL) + query_putrdataset(client, &st->q.rdataset); + if (st->q.sigrdataset != NULL) + query_putrdataset(client, &st->q.sigrdataset); + st->state = 0; +} + +/* + * Get NS, A, or AAAA rrset for rpz nsdname or nsip checking. + */ +static isc_result_t +rpz_ns_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type, + dns_db_t **dbp, dns_dbversion_t *version, + dns_rdataset_t **rdatasetp, isc_boolean_t resuming) +{ + dns_rpz_st_t *st; + isc_boolean_t is_zone; + dns_dbnode_t *node; + dns_fixedname_t fixed; + dns_name_t *found; + isc_result_t result; + + st = client->query.rpz_st; + if ((st->state & DNS_RPZ_RECURSING) != 0) { + INSIST(st->ns.r_type == type); + INSIST(dns_name_equal(name, st->r_name)); + INSIST(*rdatasetp == NULL || + !dns_rdataset_isassociated(*rdatasetp)); + st->state &= ~DNS_RPZ_RECURSING; + *dbp = st->ns.db; + st->ns.db = NULL; + if (*rdatasetp != NULL) + query_putrdataset(client, rdatasetp); + *rdatasetp = st->ns.r_rdataset; + st->ns.r_rdataset = NULL; + result = st->ns.r_result; + if (result == DNS_R_DELEGATION) { + rpz_fail_log(client, DNS_RPZ_ERROR_LEVEL, + DNS_RPZ_TYPE_NSIP, name, + "rpz_ns_find() ", result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + result = DNS_R_SERVFAIL; + } + return (result); + } + + result = rpz_ready(client, NULL, NULL, NULL, rdatasetp); + if (result != ISC_R_SUCCESS) { + st->m.policy = DNS_RPZ_POLICY_ERROR; + return (result); + } + if (*dbp != NULL) { + is_zone = ISC_FALSE; + } else { + dns_zone_t *zone; + + version = NULL; + zone = NULL; + result = query_getdb(client, name, type, 0, &zone, dbp, + &version, &is_zone); + if (result != ISC_R_SUCCESS) { + rpz_fail_log(client, DNS_RPZ_ERROR_LEVEL, + DNS_RPZ_TYPE_NSIP, name, "NS getdb() ", + result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + if (zone != NULL) + dns_zone_detach(&zone); + return (result); + } + if (zone != NULL) + dns_zone_detach(&zone); + } + + node = NULL; + dns_fixedname_init(&fixed); + found = dns_fixedname_name(&fixed); + result = dns_db_find(*dbp, name, version, type, 0, client->now, &node, + found, *rdatasetp, NULL); + if (result == DNS_R_DELEGATION && is_zone && USECACHE(client)) { + /* + * Try the cache if we're authoritative for an + * ancestor but not the domain itself. + */ + rpz_clean(NULL, dbp, &node, rdatasetp); + version = NULL; + dns_db_attach(client->view->cachedb, dbp); + result = dns_db_find(*dbp, name, version, dns_rdatatype_ns, + 0, client->now, &node, found, + *rdatasetp, NULL); + } + rpz_clean(NULL, dbp, &node, NULL); + if (result == DNS_R_DELEGATION) { + /* + * Recurse to get NS rrset or A or AAAA rrset for an NS name. + */ + rpz_clean(NULL, NULL, NULL, rdatasetp); + dns_name_copy(name, st->r_name, NULL); + result = query_recurse(client, type, st->r_name, NULL, NULL, + resuming); + if (result == ISC_R_SUCCESS) { + st->state |= DNS_RPZ_RECURSING; + result = DNS_R_DELEGATION; + } + } + return (result); +} + +/* + * Check the IP address in an A or AAAA rdataset against + * the IP or NSIP response policy rules of a view. + */ +static isc_result_t +rpz_rewrite_ip(ns_client_t *client, dns_rdataset_t *rdataset, + dns_rpz_type_t rpz_type) +{ + dns_rpz_st_t *st; + dns_dbversion_t *version; + dns_zone_t *zone; + dns_db_t *db; + dns_rpz_zone_t *new_rpz; + isc_result_t result; + + st = client->query.rpz_st; + if (st->m.rdataset == NULL) { + st->m.rdataset = query_newrdataset(client); + if (st->m.rdataset == NULL) + return (DNS_R_SERVFAIL); + } + zone = NULL; + db = NULL; + for (new_rpz = ISC_LIST_HEAD(client->view->rpz_zones); + new_rpz != NULL; + new_rpz = ISC_LIST_NEXT(new_rpz, link)) { + version = NULL; + + /* + * Find the database for this policy zone to get its + * radix tree. + */ + result = rpz_getdb(client, rpz_type, &new_rpz->origin, + &zone, &db, &version); + if (result != ISC_R_SUCCESS) { + rpz_clean(&zone, &db, NULL, NULL); + continue; + } + /* + * Look for a better (e.g. longer prefix) hit for an IP address + * in this rdataset in this radix tree than than the previous + * hit, if any. Note the domain name and quality of the + * best hit. + */ + result = dns_db_rpz_findips(new_rpz, rpz_type, zone, db, + version, rdataset, st); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + rpz_clean(&zone, &db, NULL, NULL); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +rpz_rewrite_nsip(ns_client_t *client, dns_rdatatype_t type, dns_name_t *name, + dns_db_t **dbp, dns_dbversion_t *version, + dns_rdataset_t **rdatasetp, isc_boolean_t resuming) +{ + isc_result_t result; + + result = rpz_ns_find(client, name, type, dbp, version, rdatasetp, + resuming); + switch (result) { + case ISC_R_SUCCESS: + result = rpz_rewrite_ip(client, *rdatasetp, DNS_RPZ_TYPE_NSIP); + break; + case DNS_R_EMPTYNAME: + case DNS_R_EMPTYWILD: + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NXRRSET: + case DNS_R_NCACHENXRRSET: + result = ISC_R_SUCCESS; + break; + case DNS_R_DELEGATION: + case DNS_R_DUPLICATE: + case DNS_R_DROP: + break; + default: + if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { + client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR; + rpz_fail_log(client, ISC_LOG_WARNING, DNS_RPZ_TYPE_NSIP, + name, "NS address rewrite nsip ", result); + } + break; + } + return (result); +} + +/* + * Get the rrset from a response policy zone. + */ +static isc_result_t +rpz_find(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qnamef, + dns_name_t *sname, dns_rpz_type_t rpz_type, dns_zone_t **zonep, + dns_db_t **dbp, dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, + dns_rpz_policy_t *policyp) +{ + dns_dbversion_t *version; + dns_rpz_policy_t policy; + dns_fixedname_t fixed; + dns_name_t *found; + isc_result_t result; + + result = rpz_ready(client, zonep, dbp, nodep, rdatasetp); + if (result != ISC_R_SUCCESS) { + *policyp = DNS_RPZ_POLICY_ERROR; + return (result); + } + + /* + * Try to get either a CNAME or the type of record demanded by the + * request from the policy zone. + */ + version = NULL; + result = rpz_getdb(client, rpz_type, qnamef, zonep, dbp, &version); + if (result != ISC_R_SUCCESS) { + *policyp = DNS_RPZ_POLICY_ERROR; + return (DNS_R_SERVFAIL); + } + + dns_fixedname_init(&fixed); + found = dns_fixedname_name(&fixed); + result = dns_db_find(*dbp, qnamef, version, dns_rdatatype_any, 0, + client->now, nodep, found, *rdatasetp, NULL); + if (result == ISC_R_SUCCESS) { + dns_rdatasetiter_t *rdsiter; + + rdsiter = NULL; + result = dns_db_allrdatasets(*dbp, *nodep, version, 0, + &rdsiter); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(*dbp, nodep); + rpz_fail_log(client, DNS_RPZ_ERROR_LEVEL, rpz_type, + qnamef, "allrdatasets()", result); + *policyp = DNS_RPZ_POLICY_ERROR; + return (DNS_R_SERVFAIL); + } + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) { + dns_rdatasetiter_current(rdsiter, *rdatasetp); + if ((*rdatasetp)->type == dns_rdatatype_cname || + (*rdatasetp)->type == qtype) + break; + dns_rdataset_disassociate(*rdatasetp); + } + dns_rdatasetiter_destroy(&rdsiter); + if (result != ISC_R_SUCCESS) { + if (result != ISC_R_NOMORE) { + rpz_fail_log(client, DNS_RPZ_ERROR_LEVEL, + rpz_type, qnamef, "rdatasetiter", + result); + *policyp = DNS_RPZ_POLICY_ERROR; + return (DNS_R_SERVFAIL); + } + /* + * Ask again to get the right DNS_R_DNAME/NXRRSET/... + * result if there is neither a CNAME nor target type. + */ + if (dns_rdataset_isassociated(*rdatasetp)) + dns_rdataset_disassociate(*rdatasetp); + dns_db_detachnode(*dbp, nodep); + result = dns_db_find(*dbp, qnamef, version, qtype, 0, + client->now, nodep, found, + *rdatasetp, NULL); + } + } + switch (result) { + case ISC_R_SUCCESS: + if ((*rdatasetp)->type != dns_rdatatype_cname) { + policy = DNS_RPZ_POLICY_RECORD; + } else { + policy = dns_rpz_decode_cname(*rdatasetp, sname); + if (policy == DNS_RPZ_POLICY_RECORD && + qtype != dns_rdatatype_cname && + qtype != dns_rdatatype_any) + result = DNS_R_CNAME; + } + break; + case DNS_R_DNAME: + policy = DNS_RPZ_POLICY_RECORD; + break; + case DNS_R_NXRRSET: + policy = DNS_RPZ_POLICY_NODATA; + break; + case DNS_R_NXDOMAIN: + case DNS_R_EMPTYNAME: + /* + * If we don't get a qname hit, + * see if it is worth looking for other types. + */ + dns_db_rpz_enabled(*dbp, client->query.rpz_st); + dns_db_detach(dbp); + dns_zone_detach(zonep); + policy = DNS_RPZ_POLICY_MISS; + break; + default: + dns_db_detach(dbp); + dns_zone_detach(zonep); + rpz_fail_log(client, DNS_RPZ_ERROR_LEVEL, rpz_type, qnamef, + "", result); + policy = DNS_RPZ_POLICY_ERROR; + result = DNS_R_SERVFAIL; + break; + } + + *policyp = policy; + return (result); +} + +/* + * Build and look for a QNAME or NSDNAME owner name in a response policy zone. + */ +static isc_result_t +rpz_rewrite_name(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, + dns_rpz_type_t rpz_type, dns_rdataset_t **rdatasetp) +{ + dns_rpz_st_t *st; + dns_rpz_zone_t *rpz; + dns_fixedname_t prefixf, rpz_qnamef; + dns_name_t *prefix, *suffix, *rpz_qname; + dns_zone_t *zone; + dns_db_t *db; + dns_dbnode_t *node; + dns_rpz_policy_t policy; + unsigned int labels; + isc_result_t result; + + st = client->query.rpz_st; + zone = NULL; + db = NULL; + node = NULL; + + for (rpz = ISC_LIST_HEAD(client->view->rpz_zones); + rpz != NULL; + rpz = ISC_LIST_NEXT(rpz, link)) { + /* + * Construct the rule's owner name. + */ + dns_fixedname_init(&prefixf); + prefix = dns_fixedname_name(&prefixf); + dns_name_split(qname, 1, prefix, NULL); + if (rpz_type == DNS_RPZ_TYPE_NSDNAME) + suffix = &rpz->nsdname; + else + suffix = &rpz->origin; + dns_fixedname_init(&rpz_qnamef); + rpz_qname = dns_fixedname_name(&rpz_qnamef); + for (;;) { + result = dns_name_concatenate(prefix, suffix, + rpz_qname, NULL); + if (result == ISC_R_SUCCESS) + break; + INSIST(result == DNS_R_NAMETOOLONG); + labels = dns_name_countlabels(prefix); + if (labels < 2) { + rpz_fail_log(client, DNS_RPZ_ERROR_LEVEL, + rpz_type, suffix, + "concatentate() ", result); + return (ISC_R_SUCCESS); + } + if (labels+1 == dns_name_countlabels(qname)) { + rpz_fail_log(client, DNS_RPZ_DEBUG_LEVEL1, + rpz_type, suffix, + "concatentate() ", result); + } + dns_name_split(prefix, labels - 1, NULL, prefix); + } + + /* + * See if the qname rule (or RR) exists. + */ + result = rpz_find(client, qtype, rpz_qname, qname, rpz_type, + &zone, &db, &node, rdatasetp, &policy); + switch (result) { + case DNS_R_NXDOMAIN: + case DNS_R_EMPTYNAME: + break; + case DNS_R_SERVFAIL: + rpz_clean(&zone, &db, &node, rdatasetp); + st->m.policy = DNS_RPZ_POLICY_ERROR; + return (DNS_R_SERVFAIL); + default: + /* + * when more than one name or address hits a rule, + * prefer the first set of names (qname or NS), + * the first policy zone, and the smallest name + */ + if (st->m.type == rpz_type && + rpz->num > st->m.rpz->num && + 0 <= dns_name_compare(rpz_qname, st->qname)) + continue; + rpz_clean(&st->m.zone, &st->m.db, &st->m.node, + &st->m.rdataset); + st->m.rpz = rpz; + st->m.type = rpz_type; + st->m.prefix = 0; + st->m.policy = policy; + st->m.result = result; + dns_name_copy(rpz_qname, st->qname, NULL); + if (dns_rdataset_isassociated(*rdatasetp)) { + dns_rdataset_t *trdataset; + + trdataset = st->m.rdataset; + st->m.rdataset = *rdatasetp; + *rdatasetp = trdataset; + st->m.ttl = st->m.rdataset->ttl; + } else { + st->m.ttl = DNS_RPZ_TTL_DEFAULT; + } + st->m.node = node; + node = NULL; + st->m.db = db; + db = NULL; + st->m.zone = zone; + zone = NULL; + } + } + + rpz_clean(&zone, &db, &node, rdatasetp); + return (ISC_R_SUCCESS); +} + +/* + * Look for response policy zone NSIP and NSDNAME rewriting. + */ +static isc_result_t +rpz_rewrite(ns_client_t *client, dns_rdatatype_t qtype, + isc_boolean_t resuming) +{ + dns_rpz_st_t *st; + dns_db_t *ipdb; + dns_rdataset_t *rdataset; + dns_fixedname_t nsnamef; + dns_name_t *nsname; + dns_dbversion_t *version; + isc_result_t result; + + ipdb = NULL; + rdataset = NULL; + + st = client->query.rpz_st; + if (st == NULL) { + st = isc_mem_get(client->mctx, sizeof(*st)); + if (st == NULL) + return (ISC_R_NOMEMORY); + st->state = 0; + memset(&st->m, 0, sizeof(st->m)); + memset(&st->ns, 0, sizeof(st->ns)); + memset(&st->q, 0, sizeof(st->q)); + dns_fixedname_init(&st->_qnamef); + dns_fixedname_init(&st->_r_namef); + dns_fixedname_init(&st->_fnamef); + st->qname = dns_fixedname_name(&st->_qnamef); + st->r_name = dns_fixedname_name(&st->_r_namef); + st->fname = dns_fixedname_name(&st->_fnamef); + client->query.rpz_st = st; + } + if ((st->state & DNS_RPZ_DONE_QNAME) == 0) { + st->state = DNS_RPZ_DONE_QNAME; + st->m.type = DNS_RPZ_TYPE_BAD; + st->m.policy = DNS_RPZ_POLICY_MISS; + + /* + * Check rules for the name if this it the first time, + * i.e. we've not been recursing. + */ + result = DNS_R_SERVFAIL; + st->state &= ~(DNS_RPZ_HAVE_IP | DNS_RPZ_HAVE_NSIPv4 | + DNS_RPZ_HAVE_NSIPv6 | DNS_RPZ_HAD_NSDNAME); + result = rpz_rewrite_name(client, qtype, client->query.qname, + DNS_RPZ_TYPE_QNAME, &rdataset); + if (st->m.policy != DNS_RPZ_POLICY_MISS) + goto cleanup; + if ((st->state & (DNS_RPZ_HAVE_NSIPv4 | DNS_RPZ_HAVE_NSIPv6 | + DNS_RPZ_HAD_NSDNAME)) == 0) + goto cleanup; + st->ns.label = dns_name_countlabels(client->query.qname); + } + + dns_fixedname_init(&nsnamef); + dns_name_clone(client->query.qname, dns_fixedname_name(&nsnamef)); + while (st->ns.label > 1 && st->m.policy == DNS_RPZ_POLICY_MISS) { + if (st->ns.label == dns_name_countlabels(client->query.qname)) { + nsname = client->query.qname; + } else { + nsname = dns_fixedname_name(&nsnamef); + dns_name_split(client->query.qname, st->ns.label, + NULL, nsname); + } + if (st->ns.ns_rdataset == NULL || + !dns_rdataset_isassociated(st->ns.ns_rdataset)) { + dns_db_t *db = NULL; + result = rpz_ns_find(client, nsname, dns_rdatatype_ns, + &db, NULL, &st->ns.ns_rdataset, + resuming); + if (db != NULL) + dns_db_detach(&db); + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_DELEGATION) + goto cleanup; + if (result == DNS_R_EMPTYNAME || + result == DNS_R_NXRRSET || + result == DNS_R_EMPTYWILD || + result == DNS_R_NXDOMAIN || + result == DNS_R_NCACHENXDOMAIN || + result == DNS_R_NCACHENXRRSET || + result == DNS_R_CNAME || + result == DNS_R_DNAME) { + rpz_fail_log(client, + DNS_RPZ_DEBUG_LEVEL2, + DNS_RPZ_TYPE_NSIP, nsname, + "NS db_find() ", result); + dns_rdataset_disassociate(st->ns. + ns_rdataset); + st->ns.label--; + continue; + } + if (st->m.policy != DNS_RPZ_POLICY_ERROR) { + rpz_fail_log(client, DNS_RPZ_INFO_LEVEL, + DNS_RPZ_TYPE_NSIP, nsname, + "NS db_find() ", result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + } + goto cleanup; + } + result = dns_rdataset_first(st->ns.ns_rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + /* + * Check all NS names. + */ + do { + dns_rdata_ns_t ns; + dns_rdata_t nsrdata = DNS_RDATA_INIT; + + dns_rdataset_current(st->ns.ns_rdataset, &nsrdata); + result = dns_rdata_tostruct(&nsrdata, &ns, NULL); + dns_rdata_reset(&nsrdata); + if (result != ISC_R_SUCCESS) { + rpz_fail_log(client, DNS_RPZ_ERROR_LEVEL, + DNS_RPZ_TYPE_NSIP, nsname, + "rdata_tostruct() ", result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + goto cleanup; + } + if ((st->state & DNS_RPZ_HAD_NSDNAME) != 0) { + result = rpz_rewrite_name(client, qtype, + &ns.name, + DNS_RPZ_TYPE_NSDNAME, + &rdataset); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ns); + goto cleanup; + } + } + /* + * Check all IP addresses for this NS name, but don't + * bother without NSIP rules or with a NSDNAME hit. + */ + version = NULL; + if ((st->state & DNS_RPZ_HAVE_NSIPv4) != 0 && + st->m.type != DNS_RPZ_TYPE_NSDNAME && + (st->state & DNS_RPZ_DONE_A) == 0) { + result = rpz_rewrite_nsip(client, + dns_rdatatype_a, + &ns.name, &ipdb, + version, &rdataset, + resuming); + if (result == ISC_R_SUCCESS) + st->state |= DNS_RPZ_DONE_A; + } + if (result == ISC_R_SUCCESS && + (st->state & DNS_RPZ_HAVE_NSIPv6) != 0 && + st->m.type != DNS_RPZ_TYPE_NSDNAME) { + result = rpz_rewrite_nsip(client, + dns_rdatatype_aaaa, + &ns.name, &ipdb, version, + &rdataset, resuming); + } + dns_rdata_freestruct(&ns); + if (ipdb != NULL) + dns_db_detach(&ipdb); + if (result != ISC_R_SUCCESS) + goto cleanup; + st->state &= ~DNS_RPZ_DONE_A; + result = dns_rdataset_next(st->ns.ns_rdataset); + } while (result == ISC_R_SUCCESS); + dns_rdataset_disassociate(st->ns.ns_rdataset); + st->ns.label--; + } + + /* + * Use the best, if any, hit. + */ + result = ISC_R_SUCCESS; + +cleanup: + if (st->m.policy != DNS_RPZ_POLICY_MISS && + st->m.policy != DNS_RPZ_POLICY_NO_OP && + st->m.policy != DNS_RPZ_POLICY_ERROR && + st->m.rpz->policy != DNS_RPZ_POLICY_GIVEN) + st->m.policy = st->m.rpz->policy; + if (st->m.policy == DNS_RPZ_POLICY_NO_OP) + rpz_log(client); + if (st->m.policy == DNS_RPZ_POLICY_MISS || + st->m.policy == DNS_RPZ_POLICY_NO_OP || + st->m.policy == DNS_RPZ_POLICY_ERROR) + rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset); + if (st->m.policy != DNS_RPZ_POLICY_MISS) + st->state |= DNS_RPZ_REWRITTEN; + if (st->m.policy == DNS_RPZ_POLICY_ERROR) { + st->m.type = DNS_RPZ_TYPE_BAD; + result = DNS_R_SERVFAIL; + } + if (rdataset != NULL) + query_putrdataset(client, &rdataset); + if ((st->state & DNS_RPZ_RECURSING) == 0) { + rpz_clean(NULL, &st->ns.db, NULL, &st->ns.ns_rdataset); + } + + return (result); +} + #define MAX_RESTARTS 16 #define QUERY_ERROR(r) \ @@ -4161,6 +4928,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) unsigned int options; isc_boolean_t empty_wild; dns_rdataset_t *noqname; + dns_rpz_st_t *rpz_st; isc_boolean_t resuming; int line = -1; isc_boolean_t dns64_exclude, dns64; @@ -4201,17 +4969,47 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * and resume. */ want_restart = ISC_FALSE; - authoritative = ISC_FALSE; - qtype = event->qtype; + rpz_st = client->query.rpz_st; + if (rpz_st != NULL && + (rpz_st->state & DNS_RPZ_RECURSING) != 0) { + is_zone = rpz_st->q.is_zone; + authoritative = rpz_st->q.authoritative; + zone = rpz_st->q.zone; + rpz_st->q.zone = NULL; + node = rpz_st->q.node; + rpz_st->q.node = NULL; + db = rpz_st->q.db; + rpz_st->q.db = NULL; + rdataset = rpz_st->q.rdataset; + rpz_st->q.rdataset = NULL; + sigrdataset = rpz_st->q.sigrdataset; + rpz_st->q.sigrdataset = NULL; + qtype = rpz_st->q.qtype; + + if (event->node != NULL) + dns_db_detachnode(db, &event->node); + rpz_st->ns.db = event->db; + rpz_st->ns.r_type = event->qtype; + rpz_st->ns.r_rdataset = event->rdataset; + if (event->sigrdataset != NULL && + dns_rdataset_isassociated(event->sigrdataset)) + dns_rdataset_disassociate(event->sigrdataset); + } else { + authoritative = ISC_FALSE; + + qtype = event->qtype; + db = event->db; + node = event->node; + rdataset = event->rdataset; + sigrdataset = event->sigrdataset; + } + if (qtype == dns_rdatatype_rrsig || qtype == dns_rdatatype_sig) type = dns_rdatatype_any; else type = qtype; - db = event->db; - node = event->node; - rdataset = event->rdataset; - sigrdataset = event->sigrdataset; + if (DNS64(client)) { client->query.attributes &= ~NS_QUERYATTR_DNS64; dns64 = ISC_TRUE; @@ -4234,14 +5032,25 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } - tname = dns_fixedname_name(&event->foundname); + if (rpz_st != NULL && + (rpz_st->state & DNS_RPZ_RECURSING) != 0) { + tname = rpz_st->fname; + } else { + tname = dns_fixedname_name(&event->foundname); + } result = dns_name_copy(tname, fname, NULL); if (result != ISC_R_SUCCESS) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } - - result = event->result; + if (rpz_st != NULL && + (rpz_st->state & DNS_RPZ_RECURSING) != 0) { + rpz_st->ns.r_result = event->result; + result = rpz_st->q.result; + isc_event_free(ISC_EVENT_PTR(&event)); + } else { + result = event->result; + } resuming = ISC_TRUE; goto resume; } @@ -4399,6 +5208,118 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) resume: CTRACE("query_find: resume"); + + if (!ISC_LIST_EMPTY(client->view->rpz_zones) && + RECURSIONOK(client) && !RECURSING(client) && + result != DNS_R_DELEGATION && result != ISC_R_NOTFOUND && + (client->query.rpz_st == NULL || + (client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0) && + !dns_name_equal(client->query.qname, dns_rootname)) { + isc_result_t rresult; + + rresult = rpz_rewrite(client, qtype, resuming); + rpz_st = client->query.rpz_st; + switch (rresult) { + case ISC_R_SUCCESS: + break; + case DNS_R_DELEGATION: + /* + * recursing for NS names or addresses, + * so save the main query state + */ + rpz_st->q.qtype = qtype; + rpz_st->q.is_zone = is_zone; + rpz_st->q.authoritative = authoritative; + rpz_st->q.zone = zone; + zone = NULL; + rpz_st->q.db = db; + db = NULL; + rpz_st->q.node = node; + node = NULL; + rpz_st->q.rdataset = rdataset; + rdataset = NULL; + rpz_st->q.sigrdataset = sigrdataset; + sigrdataset = NULL; + dns_name_copy(fname, rpz_st->fname, NULL); + rpz_st->q.result = result; + client->query.attributes |= NS_QUERYATTR_RECURSING; + result = ISC_R_SUCCESS; + goto cleanup; + default: + RECURSE_ERROR(rresult); + goto cleanup; + } + if (rpz_st->m.policy != DNS_RPZ_POLICY_MISS && + rpz_st->m.policy != DNS_RPZ_POLICY_NO_OP) { + result = dns_name_copy(client->query.qname, fname, + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + finish_rewrite: + rpz_clean(&zone, &db, &node, NULL); + if (rpz_st->m.rdataset != NULL) { + if (rdataset != NULL) + query_putrdataset(client, &rdataset); + rdataset = rpz_st->m.rdataset; + rpz_st->m.rdataset = NULL; + } else if (rdataset != NULL && + dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + node = rpz_st->m.node; + rpz_st->m.node = NULL; + db = rpz_st->m.db; + rpz_st->m.db = NULL; + zone = rpz_st->m.zone; + rpz_st->m.zone = NULL; + + result = rpz_st->m.result; + switch (rpz_st->m.policy) { + case DNS_RPZ_POLICY_NXDOMAIN: + result = DNS_R_NXDOMAIN; + break; + case DNS_RPZ_POLICY_NODATA: + result = DNS_R_NXRRSET; + break; + case DNS_RPZ_POLICY_RECORD: + if (type == dns_rdatatype_any && + dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + break; + case DNS_RPZ_POLICY_CNAME: + result = dns_name_copy(&rpz_st->m.rpz->cname, + fname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + query_keepname(client, fname, dbuf); + result = query_add_cname(client, + client->query.qname, + fname, + dns_trust_authanswer, + rpz_st->m.ttl); + if (result != ISC_R_SUCCESS) + goto cleanup; + ns_client_qnamereplace(client, fname); + fname = NULL; + client->attributes &= ~NS_CLIENTATTR_WANTDNSSEC; + rpz_log(client); + want_restart = ISC_TRUE; + goto cleanup; + default: + INSIST(0); + } + + /* + * Turn off DNSSEC because the results of a + * response policy zone cannot verify. + */ + client->attributes &= ~NS_CLIENTATTR_WANTDNSSEC; + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) + dns_rdataset_disassociate(sigrdataset); + is_zone = ISC_TRUE; + rpz_log(client); + } + } + switch (result) { case ISC_R_SUCCESS: /* @@ -4451,6 +5372,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) */ if (RECURSIONOK(client)) { result = query_recurse(client, qtype, + client->query.qname, NULL, NULL, resuming); if (result == ISC_R_SUCCESS) { client->query.attributes |= @@ -4642,17 +5564,18 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) */ if (dns_rdatatype_atparent(type)) result = query_recurse(client, qtype, - NULL, NULL, - resuming); + client->query.qname, + NULL, NULL, resuming); else if (dns64) result = query_recurse(client, - dns_rdatatype_a, - NULL, NULL, - resuming); + dns_rdatatype_a, + client->query.qname, + NULL, NULL, resuming); else result = query_recurse(client, qtype, - fname, rdataset, - resuming); + client->query.qname, + fname, rdataset, + resuming); if (result == ISC_R_SUCCESS) { client->query.attributes |= @@ -4795,10 +5718,8 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * instead? If so add the nearest to the * closest provable encloser. */ - if (found && - dns_rdataset_isassociated(rdataset) && - !dns_name_equal(qname, found)) - { + if (dns_rdataset_isassociated(rdataset) && + !dns_name_equal(qname, found)) { unsigned int count; unsigned int skip; @@ -5161,11 +6082,11 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) dns_message_puttempname(client->message, &tname); goto cleanup; } - dns_name_init(tname, NULL); dns_name_clone(&dname.dname, tname); dns_rdata_freestruct(&dname); /* - * Construct the new qname. + * Construct the new qname consisting of + * . */ dns_fixedname_init(&fixed); prefix = dns_fixedname_name(&fixed); @@ -5182,8 +6103,8 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) goto cleanup; } result = dns_name_concatenate(prefix, tname, fname, NULL); + dns_message_puttempname(client->message, &tname); if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &tname); if (result == ISC_R_NOSPACE) { /* * RFC2672, section 4.1, subsection 3c says @@ -5196,11 +6117,12 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) } query_keepname(client, fname, dbuf); /* - * Synthesize a CNAME for this DNAME. + * Synthesize a CNAME consisting of + * CNAME + * with * - * We want to synthesize a CNAME since if we don't - * then older software that doesn't understand DNAME - * will not chain like it should. + * Synthesize a CNAME so old old clients that don't understand + * DNAME can chain. * * We do not try to synthesize a signature because we hope * that security aware servers will understand DNAME. Also, @@ -5208,12 +6130,10 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * on-the-fly is costly, and not really legitimate anyway * since the synthesized CNAME is NOT in the zone. */ - dns_name_init(tname, NULL); - (void)query_addcnamelike(client, client->query.qname, fname, - trdataset, &tname, - dns_rdatatype_cname); - if (tname != NULL) - dns_message_puttempname(client->message, &tname); + result = query_add_cname(client, client->query.qname, fname, + trdataset->trust, trdataset->ttl); + if (result != ISC_R_SUCCESS) + goto cleanup; /* * Switch to the new qname and restart. */ @@ -5273,6 +6193,54 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } + + /* + * Check all A and AAAA records in all response policy + * IP address zones + */ + rpz_st = client->query.rpz_st; + if (rpz_st != NULL && + (rpz_st->state & DNS_RPZ_DONE_QNAME) != 0 && + (rpz_st->state & DNS_RPZ_REWRITTEN) == 0 && + RECURSIONOK(client) && !RECURSING(client) && + (rpz_st->state & DNS_RPZ_HAVE_IP) != 0) { + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) { + dns_rdatasetiter_current(rdsiter, rdataset); + if (rdataset->type == dns_rdatatype_a || + rdataset->type == dns_rdatatype_aaaa) + result = rpz_rewrite_ip(client, + rdataset, + DNS_RPZ_TYPE_IP); + dns_rdataset_disassociate(rdataset); + if (result != ISC_R_SUCCESS) + break; + } + if (result != ISC_R_NOMORE) { + dns_rdatasetiter_destroy(&rdsiter); + QUERY_ERROR(DNS_R_SERVFAIL); + goto cleanup; + } + switch (rpz_st->m.policy) { + case DNS_RPZ_POLICY_MISS: + break; + case DNS_RPZ_POLICY_NO_OP: + rpz_log(client); + rpz_st->state |= DNS_RPZ_REWRITTEN; + break; + case DNS_RPZ_POLICY_NXDOMAIN: + case DNS_RPZ_POLICY_NODATA: + case DNS_RPZ_POLICY_RECORD: + case DNS_RPZ_POLICY_CNAME: + dns_rdatasetiter_destroy(&rdsiter); + rpz_st->state |= DNS_RPZ_REWRITTEN; + goto finish_rewrite; + default: + INSIST(0); + } + } + /* * Calling query_addrrset() with a non-NULL dbuf is going * to either keep or release the name. We don't want it to @@ -5389,10 +6357,10 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) dns_rdatasetiter_destroy(&rdsiter); if (RECURSIONOK(client)) { result = query_recurse(client, - qtype, - NULL, - NULL, - resuming); + qtype, + client->query.qname, + NULL, NULL, + resuming); if (result == ISC_R_SUCCESS) client->query.attributes |= NS_QUERYATTR_RECURSING; @@ -5431,6 +6399,49 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * This is the "normal" case -- an ordinary question to which * we know the answer. */ + + /* + * Check all A and AAAA records in all response policy + * IP address zones + */ + rpz_st = client->query.rpz_st; + if (rpz_st != NULL && + (rpz_st->state & DNS_RPZ_DONE_QNAME) != 0 && + (rpz_st->state & DNS_RPZ_REWRITTEN) == 0 && + RECURSIONOK(client) && !RECURSING(client) && + (rpz_st->state & DNS_RPZ_HAVE_IP) != 0 && + (qtype == dns_rdatatype_aaaa || qtype == dns_rdatatype_a)) { + result = rpz_rewrite_ip(client, rdataset, + DNS_RPZ_TYPE_IP); + if (result != ISC_R_SUCCESS) { + QUERY_ERROR(DNS_R_SERVFAIL); + goto cleanup; + } + /* + * After a hit in the radix tree for the policy domain, + * either stop trying to rewrite (DNS_RPZ_POLICY_NO_OP) + * or restart to ask the ordinary database of the + * policy zone for the DNS record corresponding to the + * record in the radix tree. + */ + switch (rpz_st->m.policy) { + case DNS_RPZ_POLICY_MISS: + break; + case DNS_RPZ_POLICY_NO_OP: + rpz_log(client); + rpz_st->state |= DNS_RPZ_REWRITTEN; + break; + case DNS_RPZ_POLICY_NXDOMAIN: + case DNS_RPZ_POLICY_NODATA: + case DNS_RPZ_POLICY_RECORD: + case DNS_RPZ_POLICY_CNAME: + rpz_st->state |= DNS_RPZ_REWRITTEN; + goto finish_rewrite; + default: + INSIST(0); + } + } + #ifdef ALLOW_FILTER_AAAA_ON_V4 /* * Optionally hide AAAAs from IPv4 clients if there is an A. @@ -5492,6 +6503,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * if the recursion for the A succeeds. */ result = query_recurse(client, + client->query.qname, dns_rdatatype_a, NULL, NULL, resuming); if (result == ISC_R_SUCCESS) { @@ -5637,6 +6649,10 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) /* * General cleanup. */ + rpz_st = client->query.rpz_st; + if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) == 0) + rpz_clean(&rpz_st->m.zone, &rpz_st->m.db, &rpz_st->m.node, + &rpz_st->m.rdataset); if (rdataset != NULL) query_putrdataset(client, &rdataset); if (sigrdataset != NULL) @@ -5697,7 +6713,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) INSIST(line >= 0); query_error(client, eresult, line); } - ns_client_detach(&client); + ns_client_detach(&client); } else if (!RECURSING(client)) { /* * We are done. Set up sortlist data for the message diff --git a/bin/named/server.c b/bin/named/server.c index 030dc40e21..5ac2148119 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: server.c,v 1.597 2011/01/11 23:47:12 tbox Exp $ */ +/* $Id: server.c,v 1.598 2011/01/13 01:59:25 marka Exp $ */ /*! \file */ @@ -1438,6 +1438,114 @@ cleanup: return (result); } +static isc_result_t +configure_rpz(dns_view_t *view, const cfg_listelt_t *element) { + const cfg_obj_t *rpz_obj, *policy_obj; + const char *str; + dns_fixedname_t fixed; + dns_name_t *origin; + dns_rpz_zone_t *old, *new; + dns_zone_t *zone; + isc_result_t result; + unsigned int l1, l2; + + new = isc_mem_get(view->mctx, sizeof(*new)); + if (new == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + memset(new, 0, sizeof(*new)); + dns_name_init(&new->nsdname, NULL); + dns_name_init(&new->origin, NULL); + dns_name_init(&new->cname, NULL); + ISC_LIST_INITANDAPPEND(view->rpz_zones, new, link); + + rpz_obj = cfg_listelt_value(element); + policy_obj = cfg_tuple_get(rpz_obj, "policy"); + if (cfg_obj_isvoid(policy_obj)) { + new->policy = DNS_RPZ_POLICY_GIVEN; + } else { + str = cfg_obj_asstring(policy_obj); + new->policy = dns_rpz_str2policy(str); + INSIST(new->policy != DNS_RPZ_POLICY_ERROR); + } + + dns_fixedname_init(&fixed); + origin = dns_fixedname_name(&fixed); + str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "name")); + result = dns_name_fromstring(origin, str, DNS_NAME_DOWNCASE, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid zone '%s'", str); + goto cleanup; + } + + result = dns_name_fromstring2(&new->nsdname, DNS_RPZ_NSDNAME_ZONE, + origin, DNS_NAME_DOWNCASE, view->mctx); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid zone '%s'", str); + goto cleanup; + } + + /* + * The origin is part of 'nsdname' so we don't need to keep it + * seperately. + */ + l1 = dns_name_countlabels(&new->nsdname); + l2 = dns_name_countlabels(origin); + dns_name_getlabelsequence(&new->nsdname, l1 - l2, l2, &new->origin); + + /* + * Are we configured to with the reponse policy zone? + */ + result = dns_view_findzone(view, &new->origin, &zone); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "unknown zone '%s'", str); + goto cleanup; + } + + if (dns_zone_gettype(zone) != dns_zone_master && + dns_zone_gettype(zone) != dns_zone_slave) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "zone '%s' is neither master nor slave", str); + dns_zone_detach(&zone); + result = DNS_R_NOTMASTER; + goto cleanup; + } + dns_zone_detach(&zone); + + for (old = ISC_LIST_HEAD(view->rpz_zones); + old != new; + old = ISC_LIST_NEXT(old, link)) { + ++new->num; + if (dns_name_equal(&old->origin, &new->origin)) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "duplicate '%s'", str); + result = DNS_R_DUPLICATE; + goto cleanup; + } + } + + if (new->policy == DNS_RPZ_POLICY_CNAME) { + str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "cname")); + result = dns_name_fromstring(&new->cname, str, 0, view->mctx); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid cname '%s'", str); + goto cleanup; + } + } + + return (ISC_R_SUCCESS); + + cleanup: + dns_rpz_view_destroy(view); + return (result); +} + /* * Configure 'view' according to 'vconfig', taking defaults from 'config' * where values are missing in 'vconfig'. @@ -2781,6 +2889,29 @@ configure_view(dns_view_t *view, cfg_parser_t* parser, } } + /* + * Make the list of response policy zone names for views that + * are used for real lookups and so care about hints. + */ + zonelist = NULL; + if (view->rdclass == dns_rdataclass_in && need_hints) { + obj = NULL; + result = ns_config_get(maps, "response-policy", &obj); + if (result == ISC_R_SUCCESS) + cfg_map_get(obj, "zone", &zonelist); + } + if (zonelist != NULL) { + + for (element = cfg_list_first(zonelist); + element != NULL; + element = cfg_list_next(element)) { + result = configure_rpz(view, element); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_rpz_set_need(ISC_TRUE); + } + } + result = ISC_R_SUCCESS; cleanup: diff --git a/bin/tests/system/Makefile.in b/bin/tests/system/Makefile.in index 4001666f09..0d171e319f 100644 --- a/bin/tests/system/Makefile.in +++ b/bin/tests/system/Makefile.in @@ -13,7 +13,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: Makefile.in,v 1.33 2010/06/23 23:46:58 tbox Exp $ +# $Id: Makefile.in,v 1.34 2011/01/13 01:59:25 marka Exp $ srcdir = @srcdir@ VPATH = @srcdir@ @@ -21,7 +21,7 @@ top_srcdir = @top_srcdir@ @BIND9_MAKE_INCLUDES@ -SUBDIRS = filter-aaaa lwresd tkey +SUBDIRS = filter-aaaa lwresd rpz tkey TARGETS = @BIND9_MAKE_RULES@ diff --git a/bin/tests/system/README b/bin/tests/system/README index 53aebeb0cc..8f63e5f740 100644 --- a/bin/tests/system/README +++ b/bin/tests/system/README @@ -17,6 +17,7 @@ involving a different DNS setup. They are: nsupdate/ Dynamic update and IXFR tests resolver/ Regression tests for resolver bugs that have been fixed (not a complete resolver test suite) + rpz/ Tests of response policy zone (RPZ) rewriting stub/ Tests of stub zone functionality unknown/ Unknown type and class tests upforwd/ Update forwarding tests @@ -57,4 +58,4 @@ The tests can be run individually like this: To run all the tests, just type "make test". -$Id: README,v 1.14 2010/08/25 23:46:37 tbox Exp $ +$Id: README,v 1.15 2011/01/13 01:59:25 marka Exp $ diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in index c4a0f28a8d..d3e80266a0 100644 --- a/bin/tests/system/conf.sh.in +++ b/bin/tests/system/conf.sh.in @@ -15,7 +15,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: conf.sh.in,v 1.57 2010/12/23 04:07:59 marka Exp $ +# $Id: conf.sh.in,v 1.58 2011/01/13 01:59:26 marka Exp $ # # Common configuration data for system tests, to be sourced into @@ -55,7 +55,7 @@ JOURNALPRINT=$TOP/bin/tools/named-journalprint SUBDIRS="acl allow_query addzone autosign cacheclean checkconf checknames dlv @DLZ_SYSTEM_TEST@ dlzexternal dns64 dnssec forward glue gost ixfr limits lwresd masterfile masterformat metadata notify nsupdate pending pkcs11 - resolver rrsetorder sortlist smartsign staticstub stub tkey + resolver rpz rrsetorder sortlist smartsign staticstub stub tkey tsig tsiggss unknown upforwd views xfer xferquota zonechecks" # PERL will be an empty string if no perl interpreter was found. diff --git a/bin/tests/system/rpz/Makefile.in b/bin/tests/system/rpz/Makefile.in new file mode 100644 index 0000000000..557e8ac4c2 --- /dev/null +++ b/bin/tests/system/rpz/Makefile.in @@ -0,0 +1,55 @@ +# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# $Id: Makefile.in,v 1.2 2011/01/13 01:59:26 marka Exp $ + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +@BIND9_VERSION@ + +@BIND9_MAKE_INCLUDES@ + +CINCLUDES = + +CDEFINES = +CWARNINGS = + +DNSLIBS = +ISCLIBS = . + +DNSDEPLIBS = +ISCDEPLIBS = + +DEPLIBS = + +LIBS = @LIBS@ + +TARGETS = rpz@EXEEXT@ + +RPZOBJS = rpz.@O@ + +SRCS = rpz.c + +@BIND9_MAKE_RULES@ + +all: rpz@EXEEXT@ + +rpz@EXEEXT@: ${RPZOBJS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ ${RPZOBJS} ${LIBS} + +clean distclean:: + rm -f ${TARGETS} + diff --git a/bin/tests/system/rpz/clean.sh b/bin/tests/system/rpz/clean.sh new file mode 100644 index 0000000000..bc8ec846c4 --- /dev/null +++ b/bin/tests/system/rpz/clean.sh @@ -0,0 +1,22 @@ +# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# $Id: clean.sh,v 1.2 2011/01/13 01:59:26 marka Exp $ + + +# Clean up after rpz tests. + +rm -f dig.out* nsupdate.tmp +rm -f */named.memstats */named.run */session.key +rm -f ns3/bl*.db */*.jnl diff --git a/bin/tests/system/rpz/ns1/named.conf b/bin/tests/system/rpz/ns1/named.conf new file mode 100644 index 0000000000..731818075e --- /dev/null +++ b/bin/tests/system/rpz/ns1/named.conf @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: named.conf,v 1.2 2011/01/13 01:59:26 marka Exp $ */ + +controls { /* empty */ }; + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port 5300; + session-keyfile "session.key"; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + notify no; +}; + +zone "." {type master; file "root.db";}; diff --git a/bin/tests/system/rpz/ns1/root.db b/bin/tests/system/rpz/ns1/root.db new file mode 100644 index 0000000000..86fc1b6659 --- /dev/null +++ b/bin/tests/system/rpz/ns1/root.db @@ -0,0 +1,28 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: root.db,v 1.2 2011/01/13 01:59:26 marka Exp $ + +$TTL 120 +@ SOA s1. hostmaster.ns.s1. ( 1 3600 1200 604800 60 ) +@ NS s1 +s1. A 10.53.0.1 + +; rewrite responses from this zone +tld2. NS ns.tld2. +ns.tld2. A 10.53.0.2 + +; requests come from here +tld3. NS ns.tld3. +ns.tld3. A 10.53.0.3 diff --git a/bin/tests/system/rpz/ns2/hints b/bin/tests/system/rpz/ns2/hints new file mode 100644 index 0000000000..66ce619b26 --- /dev/null +++ b/bin/tests/system/rpz/ns2/hints @@ -0,0 +1,18 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: hints,v 1.2 2011/01/13 01:59:26 marka Exp $ + +. 0 NS s1. +s1. 0 A 10.53.0.1 diff --git a/bin/tests/system/rpz/ns2/named.conf b/bin/tests/system/rpz/ns2/named.conf new file mode 100644 index 0000000000..e3aef74c02 --- /dev/null +++ b/bin/tests/system/rpz/ns2/named.conf @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: named.conf,v 1.2 2011/01/13 01:59:26 marka Exp $ */ + +controls { /* empty */ }; + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + notify no; +}; + +zone "." { type hint; file "hints"; }; + +zone "tld2." {type master; file "tld2.db";}; +zone "sub1.tld2." {type master; file "tld2.db";}; +zone "sub2.sub1.tld2." {type master; file "tld2.db";}; diff --git a/bin/tests/system/rpz/ns2/tld2.db b/bin/tests/system/rpz/ns2/tld2.db new file mode 100644 index 0000000000..cd2b37d684 --- /dev/null +++ b/bin/tests/system/rpz/ns2/tld2.db @@ -0,0 +1,57 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: tld2.db,v 1.2 2011/01/13 01:59:26 marka Exp $ + +; RPZ rewrite responses from this zone + +$TTL 120 +@ SOA tld2. hostmaster.ns.tld2. ( 1 3600 1200 604800 60 ) + NS @ + A 10.53.0.2 + +nodata TXT "nodata" +a12 A 12.12.12.12 + +a0-1 A 192.168.0.1 + AAAA 2001:2::1 + TXT "a0-1 text" + +a3-1 A 192.168.3.1 + AAAA 2001:2:3::1 + TXT "a3-1 text" + +a3-2 A 192.168.3.2 + AAAA 2001:2:3::2 + TXT "a3-2 text" + +a4-1 A 192.168.4.1 + AAAA 2001:2:4::1 + TXT "a4-1 text" +a4-1-aaaa AAAA 2001:2:4::1 + +a4-2 A 192.168.4.2 + AAAA 2001:2:4::2 + TXT "a4-2 text" + +a4-3 A 192.168.4.3 + AAAA 2001:2:4::3 + TXT "a4-3 text" + +a4-4 A 192.168.4.4 + AAAA 2001:2:4::4 + TXT "a4-4 text" + +a4-5 CNAME a12 + diff --git a/bin/tests/system/rpz/ns3/base.db b/bin/tests/system/rpz/ns3/base.db new file mode 100644 index 0000000000..7b4e4b2066 --- /dev/null +++ b/bin/tests/system/rpz/ns3/base.db @@ -0,0 +1,31 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: base.db,v 1.2 2011/01/13 01:59:26 marka Exp $ + +; RPZ test + +$TTL 120 +@ SOA tld3. hostmaster.ns.tld3. ( 1 3600 1200 604800 60 ) +@ NS ns.utld. + +; Poke the radix tree a little. +128.1111.2222.3333.4444.5555.6666.7777.8888.rpz-ip CNAME . +128.1111.2222.3333.4444.5555.6666.zz.rpz-ip CNAME . +128.1111.2222.3333.4444.5555.zz.8888.rpz-ip CNAME . +128.1111.2222.3333.4444.zz.8888.rpz-ip CNAME . +128.zz.3333.4444.0.0.8888.rpz-ip CNAME . +128.zz.3333.4444.0.7777.8888.rpz-ip CNAME . +128.zz.3333.4444.0.8777.8888.rpz-ip CNAME . +127.zz.3333.4444.0.8777.8888.rpz-ip CNAME . diff --git a/bin/tests/system/rpz/ns3/hints b/bin/tests/system/rpz/ns3/hints new file mode 100644 index 0000000000..a47fc1639f --- /dev/null +++ b/bin/tests/system/rpz/ns3/hints @@ -0,0 +1,18 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: hints,v 1.2 2011/01/13 01:59:27 marka Exp $ + +. 0 NS s1. +s1. 0 A 10.53.0.1 diff --git a/bin/tests/system/rpz/ns3/named.conf b/bin/tests/system/rpz/ns3/named.conf new file mode 100644 index 0000000000..3d377ddf53 --- /dev/null +++ b/bin/tests/system/rpz/ns3/named.conf @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: named.conf,v 1.2 2011/01/13 01:59:27 marka Exp $ */ + +controls { /* empty */ }; + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + notify no; + + response-policy { + zone "bl"; + zone "bl-given" policy given; + zone "bl-no-op" policy no-op; + zone "bl-nodata" policy nodata; + zone "bl-nxdomain" policy nxdomain; + zone "bl-cname" policy cname nodata.tld2.; + }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; +controls { + inet 10.53.0.3 port 9953 allow { any; } keys { rndc_key; }; +}; + +logging { + category queries { default_stderr; }; + category query-errors { default_stderr; }; +}; + + +zone "." { type hint; file "hints"; }; + + +zone "bl." {type master; file "bl.db"; + allow-update {any;}; +}; +zone "bl-given." {type master; file "bl-given.db"; + allow-update {any;}; +}; +zone "bl-no-op." {type master; file "bl-no-op.db"; + allow-update {any;}; +}; +zone "bl-nodata." {type master; file "bl-nodata.db"; + allow-update {any;}; +}; +zone "bl-nxdomain." {type master; file "bl-nxdomain.db"; + allow-update {any;}; +}; +zone "bl-cname." {type master; file "bl-cname.db"; + allow-update {any;}; +}; + diff --git a/bin/tests/system/rpz/rpz.c b/bin/tests/system/rpz/rpz.c new file mode 100644 index 0000000000..a9d35a6dc8 --- /dev/null +++ b/bin/tests/system/rpz/rpz.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: rpz.c,v 1.2 2011/01/13 01:59:26 marka Exp $ */ + +#include + +#include +#include +#include + + +#define USAGE "usage: nsip | nsdname\n" + +int +main(int argc, char **argv) +{ + if (argc != 2) { + fputs(USAGE, stderr); + return (1); + } + + if (!strcasecmp(argv[1], "nsip")) { +#ifdef ENABLE_RPZ_NSIP + return (0); +#else + return (1); +#endif + } + + if (!strcasecmp(argv[1], "nsdname")) { +#ifdef ENABLE_RPZ_NSDNAME + return (0); +#else + return (1); +#endif + } + + fputs(USAGE, stderr); + return (1); +} diff --git a/bin/tests/system/rpz/setup.sh b/bin/tests/system/rpz/setup.sh new file mode 100644 index 0000000000..8b0f878e37 --- /dev/null +++ b/bin/tests/system/rpz/setup.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# +# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# $Id: setup.sh,v 1.2 2011/01/13 01:59:26 marka Exp $ + +sh clean.sh + +for NM in '' -given -no-op -nodata -nxdomain -cname; do + cp -f ns3/base.db ns3/bl$NM.db +done diff --git a/bin/tests/system/rpz/test1 b/bin/tests/system/rpz/test1 new file mode 100644 index 0000000000..ec3bb95429 --- /dev/null +++ b/bin/tests/system/rpz/test1 @@ -0,0 +1,24 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: test1,v 1.2 2011/01/13 01:59:26 marka Exp $ + + +server 10.53.0.3 5300 + +update add a0-1.tld2.bl. 300 CNAME . +update add a3-1.tld2.bl. 300 CNAME *. +update add *.sub1.tld2.bl. 300 A 12.12.12.12 + +send diff --git a/bin/tests/system/rpz/test2 b/bin/tests/system/rpz/test2 new file mode 100644 index 0000000000..57565d0261 --- /dev/null +++ b/bin/tests/system/rpz/test2 @@ -0,0 +1,35 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: test2,v 1.2 2011/01/13 01:59:26 marka Exp $ + + +server 10.53.0.3 5300 + +; NODATA a3-1.tld2 +update add 32.1.3.168.192.rpz-ip.bl 300 CNAME *. + +; NXDOMAIN for network of a4-1.tld2 +update add 24.0.4.168.192.rpz-ip.bl 300 CNAME . + +; poke hole in NXDOMAIN CIDR block to leave a4-1.tld2 unchanged +update add 32.1.4.168.192.rpz-ip.bl 300 CNAME 32.1.4.168.192 + +; NODATA a4-3.tld2 +update add 32.3.4.168.192.rpz-ip.bl 300 CNAME *. + +; NXDOMAIN for IPv6 a3-1.tld2 +update add 128.1.zz.3.2.2001.rpz-ip.bl 300 CNAME . + +send diff --git a/bin/tests/system/rpz/test3 b/bin/tests/system/rpz/test3 new file mode 100644 index 0000000000..fa37e015cc --- /dev/null +++ b/bin/tests/system/rpz/test3 @@ -0,0 +1,22 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: test3,v 1.2 2011/01/13 01:59:26 marka Exp $ + + +server 10.53.0.3 5300 + +update add *.tld2.rpz-nsdname.bl. 300 CNAME . + +send diff --git a/bin/tests/system/rpz/test4 b/bin/tests/system/rpz/test4 new file mode 100644 index 0000000000..86c3cee596 --- /dev/null +++ b/bin/tests/system/rpz/test4 @@ -0,0 +1,22 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: test4,v 1.2 2011/01/13 01:59:26 marka Exp $ + + +server 10.53.0.3 5300 + +update add 32.2.0.53.10.rpz-nsip.bl. 300 CNAME . + +send diff --git a/bin/tests/system/rpz/test5 b/bin/tests/system/rpz/test5 new file mode 100644 index 0000000000..fff20e5826 --- /dev/null +++ b/bin/tests/system/rpz/test5 @@ -0,0 +1,36 @@ +; Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +; $Id: test5,v 1.2 2011/01/13 01:59:26 marka Exp $ + + +server 10.53.0.3 5300 +update add a3-1.tld2.bl-given. 300 CNAME . +send + +server 10.53.0.3 5300 +update add a3-2.tld2.bl-no-op. 300 CNAME . +send + +server 10.53.0.3 5300 +update add a3-3.tld2.bl-nodata. 300 CNAME . +send + +server 10.53.0.3 5300 +update add a3-4.tld2.bl-nxdomain. 300 CNAME *. +send + +server 10.53.0.3 5300 +update add a3-5.tld2.bl-cname. 300 CNAME . +send diff --git a/bin/tests/system/rpz/tests.sh b/bin/tests/system/rpz/tests.sh new file mode 100644 index 0000000000..6e59a0588c --- /dev/null +++ b/bin/tests/system/rpz/tests.sh @@ -0,0 +1,223 @@ +# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# $Id: tests.sh,v 1.2 2011/01/13 01:59:26 marka Exp $ + +# test response policy zones (RPZ) + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +root=10.53.0.1 +s2=10.53.0.2 +s3=10.53.0.3 + +DIGCMD="$DIG +noadd +nosea +nocmd -p 5300" + + +USAGE="$0: [-x]" +while getopts "x" c; do + case $c in + x) set -x;; + *) echo "$USAGE" 1>&2; exit 1;; + esac +done +shift `expr $OPTIND - 1 || true` +if test "$#" -ne 0; then + echo "$USAGE" 1>&2 + exit 1 +fi +# really quit on control-C +trap 'exit 1' 1 2 15 + + +# set DIGNM=file name for dig output +# $1=target domain $2=optional query type $3=optional string +dignm () { + DIGNM=dig.out-$DIGNM_SUB-$1 + if test -n "$3"; then + DIGNM=$DIGNM-$3 + fi + if test -n "$2"; then + DIGNM=$DIGNM-`expr "x$2" : 'x-t *\(.*\)'` + fi +} + +setret () { + ret=1 + echo "$*" +} + +# check rewrite to NXDOMAIN +# $1=target domain $2=optional query type +nxdomain () { + dignm $1 "$2" + $DIGCMD +noauth $1 $2 @$s3 >$DIGNM + $PERL ../digcomp.pl dig.out-nxdomain $DIGNM || setret " in $DIGNM" +} + +# check rewrite to NODATA +# $1=target domain $2=optional query type +nodata () { + dignm $1 "$2" + $DIGCMD +noauth $1 $2 @$s3 >$DIGNM + $PERL ../digcomp.pl dig.out-nodata $DIGNM || setret " in $DIGNM" +} + +# check rewrite to "A 12.12.12.12" +# modify the output so that it is easily matched, but save the original line +# $1=target domain $2=optional query type +a12 () { + dignm $1 "$2" + $DIGCMD +noauth $1 $2 @$s3 \ + | sed -e "/^$1\. /{" \ + -e "s/.*/;xxx &/p" -e "s/^;xxx $1/a12.tld2/" -e '}' \ + >$DIGNM + $PERL ../digcomp.pl dig.out-a12 $DIGNM || ret=1 +} + +# check that a response is not rewritten +# $1=target domain $2=optional query type +nochange () { + dignm $1 "$2" ok + DIGNM_OK=$DIGNM + dignm $1 "$2" + $DIGCMD $1 $2 @$s3 >$DIGNM + $DIGCMD $1 $2 @$s2 >$DIGNM_OK + $PERL ../digcomp.pl $DIGNM_OK $DIGNM || ret=1 +} + +flush_db () { + if $RNDC -c ../common/rndc.conf -s $s3 -p 9953 freeze; then : ; else + echo "I:failed to freeze policy zone $1" + exit 1 + fi + if $RNDC -c ../common/rndc.conf -s $s3 -p 9953 thaw; then : ; else + echo "I:failed to thaw policy zone $1" + exit 1 + fi +} + +# $1=message $2=test file +start_test () { + ret=0 + if test -n "$1"; then + echo "I:checking $1" + fi + PREV_FILE=$2 + if test -n "$2"; then + DIGNM_SUB=`expr "$2" : 'test\(.\)'` + if $NSUPDATE -v $PREV_FILE; then : ; else + echo "I:failed to update policy zone $1 with $2" + exit 1 + fi + #flush_db + else + DIGNM_SUB="${DIGNM_SUB}x" + fi +} + +end_test () { + if test $ret != 0; then + echo "I:failed" + else + rm -f dig.out-${DIGNM_SUB}* + fi + if test -n "$PREV_FILE"; then + sed -e 's/ add / delete /' $PREV_FILE | $NSUPDATE + status=`expr $status + $ret` + #flush_db + fi +} + + +# make NXDOMAIN and NODATA prototypes +echo "I:making prototype RPZ NXDOMAIN, NODATA, and CNAME results" +$DIGCMD +noauth nonexistent @$s2 >dig.out-nxdomain +$DIGCMD +noauth nodata.tld2 @$s2 >dig.out-nodata +$DIGCMD +noauth a12.tld2 @$s2 >dig.out-a12 + +status=0 + +start_test "RPZ QNAME rewrites" test1 +nxdomain a0-1.tld2 +nodata a3-1.tld2 +a12 a4-1.sub1.tld2 +end_test + +start_test "RPZ IP rewrites" test2 +nodata a3-1.tld2 +nochange a3-2.tld2 +nxdomain a3-99.tld2 +nochange a4-1.tld2 +nxdomain a4-2.tld2 +nochange a4-2.tld2 -taaaa +nochange a4-2.tld2 -ttxt +nxdomain a4-2.tld2 -tany +nodata a4-3.tld2 +nxdomain a3-1.tld2 -tAAAA +nochange a4-1-aaaa.tld2 -tAAAA +end_test + +start_test "RPZ radix tree deletions" +nochange a3-1.tld2 +nochange a3-2.tld2 +nochange a4-1.tld2 +nochange a4-2.tld2 +nochange a4-2.tld2 -taaaa +nochange a4-2.tld2 -ttxt +nochange a4-2.tld2 -tany +nochange a4-3.tld2 +nochange a3-1.tld2 -tAAAA +nochange a4-1-aaaa.tld2 -tAAAA +end_test + +if ./rpz nsdname; then + start_test "RPZ NSDNAME rewrites" test3 + nochange a3-1.tld2 + nxdomain a3-1.sub1.tld2 + nxdomain a3-1.sub2.sub1.tld2 + end_test +else + echo "I:RPZ NSDNAME not checked; named was not built with --enable-rpz-nsdname" +fi + +if ./rpz nsip; then + start_test "RPZ NSIP rewrites" test4 + nxdomain a3-1.tld2 + nochange . + end_test +else + echo "I:RPZ NSIP not checked; named was not built with --enable-rpz-nsip" +fi + +start_test "RPZ policy overrides" test5 +nxdomain a3-1.tld2 +nochange a3-2.tld2 +nodata a3-3.tld2 +nxdomain a3-4.tld2 +dignm a3-5.tld2 -tany +$DIGCMD +noauth a3-5.tld2 -tany @$s3 >$DIGNM +if grep CNAME $DIGNM >/dev/null; then : ; else + echo "'policy cname' failed" + ret=1 +fi +end_test + +if test "$status" -eq 0; then + rm -f dig.out* +fi + +echo "I:exit status: $status" +exit $status diff --git a/config.h.in b/config.h.in index f261974c3b..3f7c8a9c9b 100644 --- a/config.h.in +++ b/config.h.in @@ -16,7 +16,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: config.h.in,v 1.141 2010/12/23 04:09:28 marka Exp $ */ +/* $Id: config.h.in,v 1.142 2011/01/13 01:59:25 marka Exp $ */ /*! \file */ @@ -144,9 +144,6 @@ int sigwait(const unsigned int *set, int *sig); /* Define if threads need PTHREAD_SCOPE_SYSTEM */ #undef NEED_PTHREAD_SCOPE_SYSTEM -/* Define if building universal (internal helper macro) */ -#undef AC_APPLE_UNIVERSAL_BUILD - /* Define to enable the "filter-aaaa-on-v4" option. */ #undef ALLOW_FILTER_AAAA_ON_V4 @@ -160,6 +157,12 @@ int sigwait(const unsigned int *set, int *sig); /* Define to enable "rrset-order fixed" syntax. */ #undef DNS_RDATASET_FIXED +/* Define to enable rpz-nsdname rules. */ +#undef ENABLE_RPZ_NSDNAME + +/* Define to enable rpz-nsip rules. */ +#undef ENABLE_RPZ_NSIP + /* Solaris hack to get select_large_fdset. */ #undef FD_SETSIZE @@ -374,9 +377,6 @@ int sigwait(const unsigned int *set, int *sig); /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME -/* Define to the home page for this package. */ -#undef PACKAGE_URL - /* Define to the version of this package. */ #undef PACKAGE_VERSION @@ -397,26 +397,15 @@ int sigwait(const unsigned int *set, int *sig); /* define if idnkit support is to be included. */ #undef WITH_IDN -/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most - significant byte first (like Motorola and SPARC, unlike Intel). */ -#if defined AC_APPLE_UNIVERSAL_BUILD -# if defined __BIG_ENDIAN__ -# define WORDS_BIGENDIAN 1 -# endif -#else -# ifndef WORDS_BIGENDIAN -# undef WORDS_BIGENDIAN -# endif -#endif +/* Define to 1 if your processor stores words with the most significant byte + first (like Motorola and SPARC, unlike Intel and VAX). */ +#undef WORDS_BIGENDIAN /* Define to empty if `const' does not conform to ANSI C. */ #undef const -/* Define to `__inline__' or `__inline' if that's what the C compiler - calls it, or to nothing if 'inline' is not supported under any name. */ -#ifndef __cplusplus +/* Define to empty if your compiler does not support "static inline". */ #undef inline -#endif /* Define to `unsigned int' if does not define. */ #undef size_t diff --git a/configure.in b/configure.in index 9035a3d500..393dd8db8a 100644 --- a/configure.in +++ b/configure.in @@ -18,7 +18,7 @@ AC_DIVERT_PUSH(1)dnl esyscmd([sed "s/^/# /" COPYRIGHT])dnl AC_DIVERT_POP()dnl -AC_REVISION($Revision: 1.511 $) +AC_REVISION($Revision: 1.512 $) AC_INIT(lib/dns/name.c) AC_PREREQ(2.59) @@ -308,7 +308,7 @@ AC_TRY_COMPILE(, [ ], [AC_MSG_RESULT(no)], [AC_MSG_RESULT(yes) - AC_DEFINE(inline, )]) + AC_DEFINE(inline, ,[Define to empty if your compiler does not support "static inline".])]) AC_TYPE_SIZE_T AC_CHECK_TYPE(ssize_t, int) @@ -2729,6 +2729,42 @@ case "$enable_fixed" in ;; esac +# +# Enable response policy rewriting using NS IP addresses +# +AC_ARG_ENABLE(rpz-nsip, + [ --enable-rpz-nsip enable rpz-nsip rules [[default=no]]], + enable_nsip="$enableval", + enable_nsip="no") +case "$enable_nsip" in + yes) + AC_DEFINE(ENABLE_RPZ_NSIP, 1, + [Define to enable rpz-nsip rules.]) + ;; + no) + ;; + *) + ;; +esac + +# +# Enable response policy rewriting using NS name +# +AC_ARG_ENABLE(rpz-nsdname, + [ --enable-rpz-nsdname enable rpz-nsdname rules [[default=no]]], + enable_nsdname="$enableval", + enable_nsdname="no") +case "$enable_nsdname" in + yes) + AC_DEFINE(ENABLE_RPZ_NSDNAME, 1, + [Define to enable rpz-nsdname rules.]) + ;; + no) + ;; + *) + ;; +esac + # # Activate "filter-aaaa-on-v4" or not? # @@ -3269,6 +3305,7 @@ AC_CONFIG_FILES([ bin/tests/system/filter-aaaa/Makefile bin/tests/system/gost/prereq.sh bin/tests/system/lwresd/Makefile + bin/tests/system/rpz/Makefile bin/tests/system/tkey/Makefile bin/tests/tasks/Makefile bin/tests/timers/Makefile diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 56f4a9ebe0..a3175b8f8a 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -18,7 +18,7 @@ - PERFORMANCE OF THIS SOFTWARE. --> - + BIND 9 Administrator Reference Manual @@ -5165,6 +5165,7 @@ badresp:1,adberr:0,findfail:0,valfail:0] zero-no-soa-ttl-cache yes_or_no ; deny-answer-addresses { address_match_list } except-from { namelist } ; deny-answer-aliases { namelist } except-from { namelist } ; + response-policy { zone_name policy given | no-op | nxdomain | nodata | cname domain ; } ; }; @@ -9183,6 +9184,143 @@ deny-answer-aliases { "example.net"; }; spuriously can break such applications. + + + Response Policy Zone (RPZ) Rewriting + + BIND 9 includes an intentionally limited + mechanism to modify DNS responses for recursive requests + similar to email anti-spam DNS blacklists. + All response policy zones are named in the + response-policy option for the view or among the + global options if there is no response-policy option for the view. + + + + The rules encoded in a response policy zone (RPZ) are applied + only to responses to queries that ask for recursion (RD=1). + RPZs are normal DNS zones containing largely valid RRsets + that can be queried normal if allowed. + It is usually best to restrict those queries with something like + allow-query {none; }; or + allow-query { 127.0.0.1; };. + + + + There are four kinds of RPZ rewrite rules. QNAME rules are + applied to query names in requests and to targets of CNAME + records resolved in the process of generating the response. + The owner name of a QNAME rule is the query name relativized + to the RPZ. + + + + IP rules are triggered by addresses in A and AAAA records. + All IP addresses in A or AAAA RRsets are tested and the rule + longest prefix is applied. Ties between rules with equal prefixes + are broken in favor of the first RPZ mentioned in the + response-policy option. + The rule matching the smallest IP address is chosen among equal + prefix rules from a single RPZ. + IP rules are expressed in RRsets with owner names that are + subdomains of rpz-ip and encoding an IP address block, reversed + as in IN-ARPA. + prefix.B.B.B.B with prefix between 1 and 32 and B between 1 and 255 + encodes an IPv4 address. + IPv6 addresses are encoded by with prefix.W.W.W.W.W.W.W.W or + prefix.WORDS.zz.WORDS. The words in the standard IPv6 text + representation are reversed, "::" is replaced with ".zz.", + and ":" becomes ".". + + + + NSDNAME rules match names in NS RRsets for the response or a + parent. They are encoded as subdomains of rpz-nsdomain relativized + to the RPZ origin name. + + + + NSIP rules match IP addresses in A and AAAA RRsets for names of + responsible servers or the names that can be matched by NSDNAME + rules. The are encoded like IP rules except as subdomains of + rpz-nsip. + + + + Authority verification issues and variations in authority data in + the current version of BIND 9 can cause + inconsistent results from NSIP and NSDNAME. So they are available + only when BIND is built with the + --enable-rpz-nsip or + --enable-rpz-nsdname options + on the "configure" command line. + + + + Four policies can be expressed. + The NXDOMAIN policy causes a NXDOMAIN response + and is expressed with an RRset consisting of a single CNAME + whose target is the root domain (.). + NODATA generates NODATA or ANCOUNT=1 regardless + of query type. + It is expressed with a CNAME whose target is the wildcard + top-level domain (*.). + The NO-OP policy does not change the response + and is used to "poke holes" in policies for larger CIDR blocks or in + zones named later in the response-policy option. + The NO-OP policy is expressed by a CNAME with a target consisting + of the variable part of the owner name, such as "example.com." for + a QNAME rule or "128.1.0.0.127." for an IP rule. + The CNAME policy is used to replace the RRsets + of response. + A and AAAA RRsets are most common and useful to capture + an evil domain in a walled garden, but any valid set of RRsets + is possible. + + + + All of the policies in an RPZ can be overridden with a + policy clause. + given says "do not override." + no-op says "do nothing" regardless of the policy + in RPZ records. + nxdomain causes all RPZ rules to generate + NXDOMAIN results. + nodata gives nodata. + cname domain causes all RPZ rules to act as if + the consisted of a "cname domain" record. + + + + For example, you might use this option statement + +response-policy { zone "bl"; }; + + and this zone statement + +zone "bl" {type master; file "example/bl"; allow-query {none;}; }; + + with this zone file + +$TTL 1H +@ SOA LOCALHOST. named-mgr.example.com (1 1h 15m 30d 2h) + +; QNAME rules +nxdomain.domain.com CNAME . +nodata.domain.com CNAME *. +bad.domain.com A 10.0.0.1 + AAAA 2001:2::1 +ok.domain.com CNAME ok.domain.com. + +; IP rules rewriting all answers for 127/8 except 127.0.0.1 +8.0.0.0.127.ip CNAME . +32.1.0.0.127.ip CNAME 32.1.0.0.127. + +; NSDNAME and NSIP rules +ns.domain.com.rpz-nsdname CNAME . +48.zz.2.2001.rpz-nsip CNAME . + + diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index 20a91b2a54..65dca3a3c3 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -13,7 +13,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: Makefile.in,v 1.175 2011/01/06 23:47:00 tbox Exp $ +# $Id: Makefile.in,v 1.176 2011/01/13 01:59:27 marka Exp $ srcdir = @srcdir@ VPATH = @srcdir@ @@ -64,9 +64,9 @@ DNSOBJS = acache.@O@ acl.@O@ adb.@O@ byaddr.@O@ \ name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ order.@O@ peer.@O@ \ portlist.@O@ private.@O@ \ rbt.@O@ rbtdb.@O@ rbtdb64.@O@ rcode.@O@ rdata.@O@ \ - rdatalist.@O@ \ - rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ request.@O@ \ - resolver.@O@ result.@O@ rootns.@O@ rriterator.@O@ sdb.@O@ \ + rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \ + request.@O@ resolver.@O@ result.@O@ rootns.@O@ rpz.@O@ \ + rriterator.@O@ sdb.@O@ \ sdlz.@O@ soa.@O@ ssu.@O@ ssu_external.@O@ \ stats.@O@ tcpmsg.@O@ time.@O@ timer.@O@ tkey.@O@ \ tsec.@O@ tsig.@O@ ttl.@O@ validator.@O@ \ @@ -90,11 +90,10 @@ DNSSRCS = acache.c acl.c adb.c byaddr.c \ keydata.c keytable.c lib.c log.c lookup.c \ master.c masterdump.c message.c \ name.c ncache.c nsec.c nsec3.c order.c peer.c portlist.c \ - rbt.c rbtdb.c rbtdb64.c rcode.c rdata.c \ - rdatalist.c \ + rbt.c rbtdb.c rbtdb64.c rcode.c rdata.c rdatalist.c \ rdataset.c rdatasetiter.c rdataslab.c request.c \ - resolver.c result.c rootns.c rriterator.c sdb.c sdlz.c \ - soa.c ssu.c ssu_external.c \ + resolver.c result.c rootns.c rpz.c rriterator.c \ + sdb.c sdlz.c soa.c ssu.c ssu_external.c \ stats.c tcpmsg.c time.c timer.c tkey.c \ tsec.c tsig.c ttl.c validator.c \ version.c view.c xfrin.c zone.c zonekey.c zt.c ${OTHERSRCS} diff --git a/lib/dns/db.c b/lib/dns/db.c index f1ac004301..133891188a 100644 --- a/lib/dns/db.c +++ b/lib/dns/db.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: db.c,v 1.95 2009/10/08 23:13:06 marka Exp $ */ +/* $Id: db.c,v 1.96 2011/01/13 01:59:27 marka Exp $ */ /*! \file */ @@ -944,3 +944,21 @@ dns_db_resigned(dns_db_t *db, dns_rdataset_t *rdataset, if (db->methods->resigned != NULL) (db->methods->resigned)(db, rdataset, version); } + +void +dns_db_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st) +{ + if (db->methods->rpz_enabled != NULL) + (db->methods->rpz_enabled)(db, st); +} + +isc_result_t +dns_db_rpz_findips(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + dns_rdataset_t *ardataset, dns_rpz_st_t *st) +{ + if (db->methods->rpz_findips == NULL) + return (ISC_R_NOTIMPLEMENTED); + return ((db->methods->rpz_findips)(rpz, rpz_type, zone, db, version, + ardataset, st)); +} diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index 234f7b5e41..07d94dcae0 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: db.h,v 1.102 2009/11/25 23:49:22 tbox Exp $ */ +/* $Id: db.h,v 1.103 2011/01/13 01:59:28 marka Exp $ */ #ifndef DNS_DB_H #define DNS_DB_H 1 @@ -63,6 +63,7 @@ #include #include #include +#include #include ISC_LANG_BEGINDECLS @@ -170,6 +171,13 @@ typedef struct dns_dbmethods { dns_dbversion_t *version); isc_boolean_t (*isdnssec)(dns_db_t *db); dns_stats_t *(*getrrsetstats)(dns_db_t *db); + void (*rpz_enabled)(dns_db_t *db, dns_rpz_st_t *st); + isc_result_t (*rpz_findips)(dns_rpz_zone_t *rpz, + dns_rpz_type_t rpz_type, + dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *version, + dns_rdataset_t *ardataset, + dns_rpz_st_t *st); } dns_dbmethods_t; typedef isc_result_t @@ -1487,6 +1495,31 @@ dns_db_getrrsetstats(dns_db_t *db); * dns_rdatasetstats_create(); otherwise NULL. */ +void +dns_db_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st); +/*%< + * See if a policy database has DNS_RPZ_TYPE_IP, DNS_RPZ_TYPE_NSIP, or + * DNS_RPZ_TYPE_NSDNAME records. + */ + +isc_result_t +dns_db_rpz_findips(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + dns_rdataset_t *ardataset, dns_rpz_st_t *st); +/*%< + * Search the CDIR block tree of a response policy tree of trees for the best + * match to any of the IP addresses in an A or AAAA rdataset. + * + * Requires: + * \li search in policy zone 'rpz' for a match of 'rpz_type' either + * DNS_RPZ_TYPE_IP or DNS_RPZ_TYPE_NSIP + * \li 'zone' and 'db' are the database corresponding to 'rpz' + * \li 'version' is the required version of the database + * \li 'ardataset' is an A or AAAA rdataset of addresses to check + * \li 'found' specifies the previous best match if any or + * or NULL, an empty name, 0, DNS_RPZ_POLICY_MISS, and 0 + */ + ISC_LANG_ENDDECLS #endif /* DNS_DB_H */ diff --git a/lib/dns/include/dns/name.h b/lib/dns/include/dns/name.h index e00167bf1a..bf8ba35cb5 100644 --- a/lib/dns/include/dns/name.h +++ b/lib/dns/include/dns/name.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: name.h,v 1.135 2010/07/09 23:46:51 tbox Exp $ */ +/* $Id: name.h,v 1.136 2011/01/13 01:59:28 marka Exp $ */ #ifndef DNS_NAME_H #define DNS_NAME_H 1 @@ -756,7 +756,7 @@ dns_name_towire(const dns_name_t *name, dns_compress_t *cctx, isc_result_t dns_name_fromtext(dns_name_t *name, isc_buffer_t *source, - dns_name_t *origin, unsigned int options, + const dns_name_t *origin, unsigned int options, isc_buffer_t *target); /*%< * Convert the textual representation of a DNS name at source @@ -1168,11 +1168,18 @@ dns_name_tostring(dns_name_t *source, char **target, isc_mem_t *mctx); isc_result_t dns_name_fromstring(dns_name_t *target, const char *src, unsigned int options, isc_mem_t *mctx); +isc_result_t +dns_name_fromstring2(dns_name_t *target, const char *src, + const dns_name_t *origin, unsigned int options, + isc_mem_t *mctx); /*%< * Convert a string to a name and place it in target, allocating memory * as necessary. 'options' has the same semantics as that of * dns_name_fromtext(). * + * If 'target' has a buffer then the name will be copied into it rather than + * memory being allocated. + * * Requires: * * \li 'target' is a valid name that is not read-only. diff --git a/lib/dns/include/dns/rpz.h b/lib/dns/include/dns/rpz.h new file mode 100644 index 0000000000..3533c76c97 --- /dev/null +++ b/lib/dns/include/dns/rpz.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: rpz.h,v 1.2 2011/01/13 01:59:28 marka Exp $ */ + +#ifndef DNS_RPZ_H +#define DNS_RPZ_H 1 + +#include + +#include +#include +#include + +ISC_LANG_BEGINDECLS + +#define DNS_RPZ_IP_ZONE "rpz-ip" +#define DNS_RPZ_NSIP_ZONE "rpz-nsip" +#define DNS_RPZ_NSDNAME_ZONE "rpz-nsdname" + +typedef isc_uint8_t dns_rpz_cidr_bits_t; + +typedef enum { + DNS_RPZ_TYPE_BAD, + DNS_RPZ_TYPE_QNAME, + DNS_RPZ_TYPE_IP, + DNS_RPZ_TYPE_NSIP, + DNS_RPZ_TYPE_NSDNAME +} dns_rpz_type_t; + +/* + * Require DNS_RPZ_POLICY_NO_OP < DNS_RPZ_POLICY_NXDOMAIN < + * DNS_RPZ_POLICY_NODATA < DNS_RPZ_POLICY_CNAME. + */ +typedef enum { + DNS_RPZ_POLICY_GIVEN = 0, /* 'given': what something else says */ + DNS_RPZ_POLICY_NO_OP = 1, /* 'no-op': do not rewrite */ + DNS_RPZ_POLICY_NXDOMAIN = 2, /* 'nxdomain': answer with NXDOMAIN */ + DNS_RPZ_POLICY_NODATA = 3, /* 'nodata': answer with ANCOUNT=0 */ + DNS_RPZ_POLICY_CNAME = 4, /* 'cname x': answer with x's rrsets */ + DNS_RPZ_POLICY_RECORD = 5, + DNS_RPZ_POLICY_MISS, + DNS_RPZ_POLICY_ERROR +} dns_rpz_policy_t; + +/* + * Specify a response policy zone. + */ +typedef struct dns_rpz_zone dns_rpz_zone_t; + +struct dns_rpz_zone { + ISC_LINK(dns_rpz_zone_t) link; + int num; + dns_name_t origin; /* Policy zone name */ + dns_name_t nsdname; /* RPZ_NSDNAME_ZONE.origin */ + dns_rpz_policy_t policy; /* RPZ_POLICY_GIVEN or override */ + dns_name_t cname; /* override name for + RPZ_POLICY_CNAME */ +}; + +/* + * Radix trees for response policy IP addresses. + */ +typedef struct dns_rpz_cidr dns_rpz_cidr_t; + +/* + * context for finding the best policy + */ +typedef struct { + unsigned int state; +# define DNS_RPZ_REWRITTEN 0x0001 +# define DNS_RPZ_DONE_QNAME 0x0002 +# define DNS_RPZ_DONE_A 0x0004 +# define DNS_RPZ_RECURSING 0x0008 +# define DNS_RPZ_HAVE_IP 0x0010 +# define DNS_RPZ_HAVE_NSIPv4 0x0020 +# define DNS_RPZ_HAVE_NSIPv6 0x0040 +# define DNS_RPZ_HAD_NSDNAME 0x0080 + /* + * Best match so far. + */ + struct { + dns_rpz_type_t type; + dns_rpz_zone_t *rpz; + dns_rpz_cidr_bits_t prefix; + dns_rpz_policy_t policy; + dns_ttl_t ttl; + isc_result_t result; + dns_zone_t *zone; + dns_db_t *db; + dns_dbnode_t *node; + dns_rdataset_t *rdataset; + } m; + /* + * State for chasing NS names and addresses including recursion. + */ + struct { + unsigned int label; + dns_db_t *db; + dns_rdataset_t *ns_rdataset; + dns_rdatatype_t r_type; + isc_result_t r_result; + dns_rdataset_t *r_rdataset; + } ns; + /* + * State of real query while recursing for NSIP or NSDNAME. + */ + struct { + isc_result_t result; + isc_boolean_t is_zone; + isc_boolean_t authoritative; + dns_zone_t *zone; + dns_db_t *db; + dns_dbnode_t *node; + dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; + dns_rdatatype_t qtype; + } q; + dns_name_t *qname; + dns_name_t *r_name; + dns_name_t *fname; + dns_fixedname_t _qnamef; + dns_fixedname_t _r_namef; + dns_fixedname_t _fnamef; +} dns_rpz_st_t; + +#define DNS_RPZ_TTL_DEFAULT 5 + +/* + * So various response policy zone messages can be turned up or down. + */ +#define DNS_RPZ_ERROR_LEVEL ISC_LOG_WARNING +#define DNS_RPZ_INFO_LEVEL ISC_LOG_INFO +#define DNS_RPZ_DEBUG_LEVEL1 ISC_LOG_DEBUG(1) +#define DNS_RPZ_DEBUG_LEVEL2 ISC_LOG_DEBUG(2) + +const char * +dns_rpz_type2str(dns_rpz_type_t type); + +dns_rpz_policy_t +dns_rpz_str2policy(const char *str); + +void +dns_rpz_set_need(isc_boolean_t need); + +isc_boolean_t +dns_rpz_needed(void); + +void +dns_rpz_cidr_free(dns_rpz_cidr_t **cidr); + +void +dns_rpz_view_destroy(dns_view_t *view); + +isc_result_t +dns_rpz_new_cidr(isc_mem_t *mctx, dns_name_t *origin, + dns_rpz_cidr_t **rbtdb_cidr); +void +dns_rpz_enabled(dns_rpz_cidr_t *cidr, dns_rpz_st_t *st); + +void +dns_rpz_cidr_deleteip(dns_rpz_cidr_t *cidr, dns_name_t *name); + +void +dns_rpz_cidr_addip(dns_rpz_cidr_t *cidr, dns_name_t *name); + +isc_result_t +dns_rpz_cidr_find(dns_rpz_cidr_t *cidr, const isc_netaddr_t *netaddr, + dns_rpz_type_t type, dns_name_t *canon_name, + dns_name_t *search_name, dns_rpz_cidr_bits_t *prefix); + +dns_rpz_policy_t +dns_rpz_decode_cname(dns_rdataset_t *, dns_name_t *selfname); + +#endif /* DNS_RPZ_H */ + diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index d52e466785..153c52e5c2 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: view.h,v 1.131 2011/01/11 23:47:13 tbox Exp $ */ +/* $Id: view.h,v 1.132 2011/01/13 01:59:28 marka Exp $ */ #ifndef DNS_VIEW_H #define DNS_VIEW_H 1 @@ -74,6 +74,7 @@ #include #include #include +#include #include ISC_LANG_BEGINDECLS @@ -160,6 +161,7 @@ struct dns_view { dns_acl_t * v4_aaaa_acl; dns_dns64list_t dns64; unsigned int dns64cnt; + ISC_LIST(dns_rpz_zone_t) rpz_zones; /* * Configurable data for server use only, diff --git a/lib/dns/name.c b/lib/dns/name.c index 8442acef3c..0a18c83677 100644 --- a/lib/dns/name.c +++ b/lib/dns/name.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: name.c,v 1.172 2010/07/09 05:13:15 each Exp $ */ +/* $Id: name.c,v 1.173 2011/01/13 01:59:27 marka Exp $ */ /*! \file */ @@ -1021,7 +1021,7 @@ dns_name_toregion(dns_name_t *name, isc_region_t *r) { isc_result_t dns_name_fromtext(dns_name_t *name, isc_buffer_t *source, - dns_name_t *origin, unsigned int options, + const dns_name_t *origin, unsigned int options, isc_buffer_t *target) { unsigned char *ndata, *label; @@ -2395,6 +2395,14 @@ dns_name_tostring(dns_name_t *name, char **target, isc_mem_t *mctx) { isc_result_t dns_name_fromstring(dns_name_t *target, const char *src, unsigned int options, isc_mem_t *mctx) +{ + return (dns_name_fromstring2(target, src, dns_rootname, options, mctx)); +} + +isc_result_t +dns_name_fromstring2(dns_name_t *target, const char *src, + const dns_name_t *origin, unsigned int options, + isc_mem_t *mctx) { isc_result_t result; isc_buffer_t buf; @@ -2405,14 +2413,19 @@ dns_name_fromstring(dns_name_t *target, const char *src, unsigned int options, isc_buffer_init(&buf, src, strlen(src)); isc_buffer_add(&buf, strlen(src)); - dns_fixedname_init(&fn); - name = dns_fixedname_name(&fn); + if (BINDABLE(target) && target->buffer != NULL) + name = target; + else { + dns_fixedname_init(&fn); + name = dns_fixedname_name(&fn); + } - result = dns_name_fromtext(name, &buf, dns_rootname, options, NULL); + result = dns_name_fromtext(name, &buf, origin, options, NULL); if (result != ISC_R_SUCCESS) return (result); - result = dns_name_dup(name, mctx, target); + if (name != target) + result = dns_name_dupwithoffsets(name, mctx, target); return (result); } diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index 0b120d79e7..0c0f69e690 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: rbtdb.c,v 1.307 2010/12/02 04:58:13 marka Exp $ */ +/* $Id: rbtdb.c,v 1.308 2011/01/13 01:59:27 marka Exp $ */ /*! \file */ @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -437,6 +438,7 @@ typedef struct { dns_rbt_t * tree; dns_rbt_t * nsec; dns_rbt_t * nsec3; + dns_rpz_cidr_t * rpz_cidr; /* Unlocked */ unsigned int quantum; @@ -953,6 +955,9 @@ free_rbtdb(dns_rbtdb_t *rbtdb, isc_boolean_t log, isc_event_t *event) { if (rbtdb->rrsetstats != NULL) dns_stats_detach(&rbtdb->rrsetstats); + if (rbtdb->rpz_cidr != NULL) + dns_rpz_cidr_free(&rbtdb->rpz_cidr); + isc_mem_put(rbtdb->common.mctx, rbtdb->node_locks, rbtdb->node_lock_count * sizeof(rbtdb_nodelock_t)); isc_rwlock_destroy(&rbtdb->tree_lock); @@ -1488,6 +1493,12 @@ delete_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) switch (node->nsec) { case DNS_RBT_NSEC_NORMAL: + if (rbtdb->rpz_cidr != NULL) { + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + dns_rbt_fullnamefromnode(node, name); + dns_rpz_cidr_deleteip(rbtdb->rpz_cidr, name); + } result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE); break; case DNS_RBT_NSEC_HAS_NSEC: @@ -1522,6 +1533,7 @@ delete_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) } } result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE); + dns_rpz_cidr_deleteip(rbtdb->rpz_cidr, name); break; case DNS_RBT_NSEC_NSEC: result = dns_rbt_deletenode(rbtdb->nsec, node, ISC_FALSE); @@ -2495,6 +2507,15 @@ findnode(dns_db_t *db, dns_name_t *name, isc_boolean_t create, node = NULL; result = dns_rbt_addnode(rbtdb->tree, name, &node); if (result == ISC_R_SUCCESS) { + if (rbtdb->rpz_cidr != NULL) { + dns_fixedname_t fnamef; + dns_name_t *fname; + + dns_fixedname_init(&fnamef); + fname = dns_fixedname_name(&fnamef); + dns_rbt_fullnamefromnode(node, fname); + dns_rpz_cidr_addip(rbtdb->rpz_cidr, fname); + } dns_rbt_namefromnode(node, &nodename); #ifdef DNS_RBT_USEHASH node->locknum = node->hashval % rbtdb->node_lock_count; @@ -4510,6 +4531,198 @@ find_coveringnsec(rbtdb_search_t *search, dns_dbnode_t **nodep, return (result); } +/* + * Mark a database for response policy rewriting. + */ +static void +get_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st) +{ + dns_rbtdb_t *rbtdb; + + rbtdb = (dns_rbtdb_t *)db; + REQUIRE(VALID_RBTDB(rbtdb)); + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + dns_rpz_enabled(rbtdb->rpz_cidr, st); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); +} + +/* + * Search the CDIR block tree of a response policy tree of trees for all of + * the IP addresses in an A or AAAA rdataset. + * Among the policies for all IPv4 and IPv6 addresses for a name, choose + * the longest prefix. Among those with the longest prefix, the first + * configured policy. Among answers for with the longest prefixes for + * two or more IP addresses in the A and AAAA rdatasets the lexically + * smallest address. + */ +static isc_result_t +rpz_findips(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + dns_rdataset_t *ardataset, dns_rpz_st_t *st) +{ + dns_rbtdb_t *rbtdb; + struct in_addr ina; + struct in6_addr in6a; + isc_netaddr_t netaddr; + dns_fixedname_t selfnamef, qnamef; + dns_name_t *selfname, *qname; + dns_rbtnode_t *node; + dns_rdataset_t zrdataset; + dns_rpz_cidr_bits_t prefix; + isc_result_t result; + dns_rpz_policy_t rpz_policy; + dns_ttl_t ttl; + + rbtdb = (dns_rbtdb_t *)db; + REQUIRE(VALID_RBTDB(rbtdb)); + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + + if (rbtdb->rpz_cidr == NULL) { + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + dns_db_detach(&db); + dns_zone_detach(&zone); + return (ISC_R_UNEXPECTED); + } + + dns_fixedname_init(&selfnamef); + dns_fixedname_init(&qnamef); + selfname = dns_fixedname_name(&selfnamef); + qname = dns_fixedname_name(&qnamef); + + for (result = dns_rdataset_first(ardataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(ardataset)) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(ardataset, &rdata); + switch (rdata.type) { + case dns_rdatatype_a: + INSIST(rdata.length == 4); + memcpy(&ina.s_addr, rdata.data, 4); + isc_netaddr_fromin(&netaddr, &ina); + break; + case dns_rdatatype_aaaa: + INSIST(rdata.length == 16); + memcpy(in6a.s6_addr, rdata.data, 16); + isc_netaddr_fromin6(&netaddr, &in6a); + break; + default: + continue; + } + + result = dns_rpz_cidr_find(rbtdb->rpz_cidr, &netaddr, rpz_type, + selfname, qname, &prefix); + if (result != ISC_R_SUCCESS) + continue; + + /* + * Choose the policy with the longest matching prefix. + * Between policies with the same prefix, choose the first + * configured. + */ + if (st->m.policy != DNS_RPZ_POLICY_MISS) { + if (prefix < st->m.prefix) + continue; + if (prefix == st->m.prefix && + rpz->num > st->m.rpz->num) + continue; + } + + /* + * We have rpz_st an entry with a prefix at least as long as + * the prefix of the entry we had before. Find the node + * corresponding to CDIR tree entry. + */ + node = NULL; + result = dns_rbt_findnode(rbtdb->tree, qname, NULL, + &node, NULL, 0, NULL, NULL); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(qname, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, DNS_RPZ_ERROR_LEVEL, + "rpz_findips findnode(%s): %s", + namebuf, isc_result_totext(result)); + continue; + } + /* + * First look for a simple rewrite of the IP address. + * If that fails, look for a CNAME. If we cannot find + * a CNAME or the CNAME is neither of the special forms + * "*" or ".", treat it like a real CNAME. + */ + dns_rdataset_init(&zrdataset); + result = dns_db_findrdataset(db, node, version, ardataset->type, + 0, 0, &zrdataset, NULL); + if (result != ISC_R_SUCCESS) + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_cname, + 0, 0, &zrdataset, NULL); + if (result == ISC_R_SUCCESS) { + if (zrdataset.type != dns_rdatatype_cname) { + rpz_policy = DNS_RPZ_POLICY_RECORD; + } else { + rpz_policy = dns_rpz_decode_cname(&zrdataset, + selfname); + if (rpz_policy == DNS_RPZ_POLICY_RECORD) + result = DNS_R_CNAME; + } + ttl = zrdataset.ttl; + } else { + rpz_policy = DNS_RPZ_POLICY_RECORD; + result = DNS_R_NXRRSET; + ttl = DNS_RPZ_TTL_DEFAULT; + } + + /* + * Use an overriding action specified in the configuration file + */ + if (rpz->policy != DNS_RPZ_POLICY_GIVEN && + rpz_policy != DNS_RPZ_POLICY_NO_OP) + rpz_policy = rpz->policy; + + /* + * We know the new prefix is at least as long as the current. + * Prefer the new answer if the new prefix is longer. + * Prefer the zone configured first if the prefixes are equal. + * With two actions from the same zone, prefer the action + * on the "smallest" name. + */ + if (st->m.policy == DNS_RPZ_POLICY_MISS || + prefix > st->m.prefix || + rpz->num <= st->m.rpz->num || + 0 > dns_name_compare(qname, st->qname)) { + if (dns_rdataset_isassociated(st->m.rdataset)) + dns_rdataset_disassociate(st->m.rdataset); + if (st->m.node != NULL) + dns_db_detachnode(st->m.db, &st->m.node); + if (st->m.db != NULL) + dns_db_detach(&st->m.db); + if (st->m.zone != NULL) + dns_zone_detach(&st->m.zone); + st->m.rpz = rpz; + st->m.type = rpz_type; + st->m.prefix = prefix; + st->m.policy = rpz_policy; + st->m.ttl = ttl; + st->m.result = result; + dns_name_copy(qname, st->qname, NULL); + if (rpz_policy == DNS_RPZ_POLICY_RECORD && + result != DNS_R_NXRRSET) { + dns_rdataset_clone(&zrdataset,st->m.rdataset); + dns_db_attachnode(db, node, &st->m.node); + } + dns_db_attach(db, &st->m.db); + dns_zone_attach(zone, &st->m.zone); + } + if (dns_rdataset_isassociated(&zrdataset)) + dns_rdataset_disassociate(&zrdataset); + } + + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + return (ISC_R_SUCCESS); +} + static isc_result_t cache_find(dns_db_t *db, dns_name_t *name, dns_dbversion_t *version, dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, @@ -6583,6 +6796,10 @@ loadnode(dns_rbtdb_t *rbtdb, dns_name_t *name, dns_rbtnode_t **nodep, dns_rbtnode_t *nsecnode; noderesult = dns_rbt_addnode(rbtdb->tree, name, nodep); + + if (noderesult == ISC_R_SUCCESS) + dns_rpz_cidr_addip(rbtdb->rpz_cidr, name); + if (!hasnsec) return (noderesult); if (noderesult == ISC_R_EXISTS) { @@ -6694,7 +6911,7 @@ loading_addrdataset(void *arg, dns_name_t *name, dns_rdataset_t *rdataset) { } if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) return (result); - if (result != ISC_R_EXISTS) { + if (result == ISC_R_SUCCESS) { dns_name_t foundname; dns_name_init(&foundname, NULL); dns_rbt_namefromnode(node, &foundname); @@ -7156,7 +7373,9 @@ static dns_dbmethods_t zone_methods = { getsigningtime, resigned, isdnssec, - NULL + NULL, + get_rpz_enabled, + rpz_findips }; static dns_dbmethods_t cache_methods = { @@ -7195,7 +7414,9 @@ static dns_dbmethods_t cache_methods = { NULL, NULL, isdnssec, - getrrsetstats + getrrsetstats, + NULL, + NULL }; isc_result_t @@ -7377,6 +7598,22 @@ dns_rbtdb_create return (result); } + /* + * Get ready for response policy IP address searching if at least one + * zone has been configured as a response policy zone and this + * is not a cache zone. + * It would be better to know that this database is for a policy + * zone named for a view, but that would require knowledge from + * above such as an argv[] set from data in the zone. + */ + if (type == dns_dbtype_zone && !dns_name_equal(origin, dns_rootname)) { + result = dns_rpz_new_cidr(mctx, origin, &rbtdb->rpz_cidr); + if (result != ISC_R_SUCCESS) { + free_rbtdb(rbtdb, ISC_FALSE, NULL); + return (result); + } + } + /* * In order to set the node callback bit correctly in zone databases, * we need to know if the node has the origin name of the zone. diff --git a/lib/dns/rpz.c b/lib/dns/rpz.c new file mode 100644 index 0000000000..43d369d6c5 --- /dev/null +++ b/lib/dns/rpz.c @@ -0,0 +1,1166 @@ +/* + * Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: rpz.c,v 1.2 2011/01/13 01:59:27 marka Exp $ */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * Parallel radix trees for databases of response policy IP addresses + * + * The radix or Patricia trees are somewhat specialized to handle response + * policy addresses by representing the two test of IP IP addresses and name + * server IP addresses in a single tree. + * + * Each leaf indicates that an IP address is listed in the IP address or the + * name server IP address policy sub-zone (or both) of the corresponding + * response response zone. The policy data such as a CNAME or an A record + * is kept in the policy zone. After an IP address has been found in a radix + * tree, the node in the policy zone's database is found by converting + * the IP address to a domain name in a canonical form. + * + * The response policy zone canonical form of IPv6 addresses is one of: + * prefix.W.W.W.W.W.W.W.W + * prefix.WORDS.zz + * prefix.WORDS.zz.WORDS + * prefix.zz.WORDS + * where + * prefix is the prefix length of the IPv6 address between 1 and 128 + * W is a number between 0 and 65535 + * WORDS is one or more numbers W separated with "." + * zz corresponds to :: in the standard IPv6 text representation + * + * The canonical form of IPv4 addresses is: + * prefix.B.B.B.B + * where + * prefix is the prefix length of the address between 1 and 32 + * B is a number between 0 and 255 + * + * IPv4 addresses are distinguished from IPv6 addresses by having + * 5 labels all of which are numbers, and a prefix between 1 and 32. + */ + + +/* + * Use a private definition of IPv6 addresses because s6_addr32 is not + * always defined and our IPv6 addresses are in non-standard byte order + */ +typedef isc_uint32_t dns_rpz_cidr_word_t; +#define DNS_RPZ_CIDR_WORD_BITS ((int)sizeof(dns_rpz_cidr_word_t)*8) +#define DNS_RPZ_CIDR_KEY_BITS ((int)sizeof(dns_rpz_cidr_key_t)*8) +#define DNS_RPZ_CIDR_WORDS (128/DNS_RPZ_CIDR_WORD_BITS) +typedef struct { + dns_rpz_cidr_word_t w[DNS_RPZ_CIDR_WORDS]; +} dns_rpz_cidr_key_t; + +#define ADDR_V4MAPPED 0xffff + +#define DNS_RPZ_WORD_MASK(b) \ + ((b) == 0 ? (dns_rpz_cidr_word_t)(-1) \ + : ((dns_rpz_cidr_word_t)(-1) \ + << (DNS_RPZ_CIDR_WORD_BITS - (b)))) + +#define DNS_RPZ_IP_BIT(ip, bitno) \ + (1 & ((ip)->w[(bitno)/DNS_RPZ_CIDR_WORD_BITS] >> \ + (DNS_RPZ_CIDR_WORD_BITS - 1 - ((bitno) % DNS_RPZ_CIDR_WORD_BITS)))) + +typedef struct dns_rpz_cidr_node dns_rpz_cidr_node_t; +typedef isc_uint8_t dns_rpz_cidr_flags_t; +struct dns_rpz_cidr_node { + dns_rpz_cidr_node_t *parent; + dns_rpz_cidr_node_t *child[2]; + dns_rpz_cidr_key_t ip; + dns_rpz_cidr_bits_t bits; + dns_rpz_cidr_flags_t flags; +#define DNS_RPZ_CIDR_FG_IP 0x01 /* has IP data or is parent of IP */ +#define DNS_RPZ_CIDR_FG_IP_DATA 0x02 /* has IP data */ +#define DNS_RPZ_CIDR_FG_NSIPv4 0x04 /* has or is parent of NSIPv4 data */ +#define DNS_RPZ_CIDR_FG_NSIPv6 0x08 /* has or is parent of NSIPv6 data */ +#define DNS_RPZ_CIDR_FG_NSIP_DATA 0x10 /* has NSIP data */ +}; + +struct dns_rpz_cidr { + isc_mem_t *mctx; + isc_boolean_t had_nsdname; + dns_rpz_cidr_node_t *root; + dns_name_t ip_name; /* RPZ_IP_ZONE.LOCALHOST. */ + dns_name_t nsip_name; /* RPZ_NSIP_ZONE.LOCALHOST. */ + dns_name_t nsdname_name; /* RPZ_NSDNAME_ZONE.LOCALHOST */ +}; + + +static isc_boolean_t have_rpz_zones = ISC_FALSE; + + +const char * +dns_rpz_type2str(dns_rpz_type_t type) +{ + switch (type) { + case DNS_RPZ_TYPE_QNAME: + return ("QNAME"); + case DNS_RPZ_TYPE_IP: + return ("IP"); + case DNS_RPZ_TYPE_NSIP: + return ("NSIP"); + case DNS_RPZ_TYPE_NSDNAME: + return ("NSDNAME"); + case DNS_RPZ_TYPE_BAD: + break; + } + FATAL_ERROR(__FILE__, __LINE__, + "impossible response policy zone type %d", type); + return ("impossible"); +} + + + +dns_rpz_policy_t +dns_rpz_str2policy(const char *str) +{ + if (str == NULL) + return (DNS_RPZ_POLICY_ERROR); + if (!strcasecmp(str, "given")) + return (DNS_RPZ_POLICY_GIVEN); + if (!strcasecmp(str, "no-op")) + return (DNS_RPZ_POLICY_NO_OP); + if (!strcasecmp(str, "nxdomain")) + return (DNS_RPZ_POLICY_NXDOMAIN); + if (!strcasecmp(str, "nodata")) + return (DNS_RPZ_POLICY_NODATA); + if (!strcasecmp(str, "cname")) + return (DNS_RPZ_POLICY_CNAME); + return (DNS_RPZ_POLICY_ERROR); +} + + + +/* + * Free the radix tree of a response policy database. + */ +void +dns_rpz_cidr_free(dns_rpz_cidr_t **cidrp) { + dns_rpz_cidr_node_t *cur, *child, *parent; + dns_rpz_cidr_t *cidr; + + REQUIRE(cidrp != NULL); + + cidr = *cidrp; + if (cidr == NULL) + return; + + cur = cidr->root; + while (cur != NULL) { + /* Depth first. */ + child = cur->child[0]; + if (child != NULL) { + cur = child; + continue; + } + child = cur->child[1]; + if (child != NULL) { + cur = child; + continue; + } + + /* Delete this leaf and go up. */ + parent = cur->parent; + if (parent == NULL) + cidr->root = NULL; + else + parent->child[parent->child[1] == cur] = NULL; + isc_mem_put(cidr->mctx, cur, sizeof(*cur)); + cur = parent; + } + + dns_name_free(&cidr->ip_name, cidr->mctx); + dns_name_free(&cidr->nsip_name, cidr->mctx); + dns_name_free(&cidr->nsdname_name, cidr->mctx); + isc_mem_put(cidr->mctx, cidr, sizeof(*cidr)); + *cidrp = NULL; +} + + + +/* + * Forget a view's list of policy zones. + */ +void +dns_rpz_view_destroy(dns_view_t *view) { + dns_rpz_zone_t *zone; + + REQUIRE(view != NULL); + + while (!ISC_LIST_EMPTY(view->rpz_zones)) { + zone = ISC_LIST_HEAD(view->rpz_zones); + ISC_LIST_UNLINK(view->rpz_zones, zone, link); + if (dns_name_dynamic(&zone->origin)) + dns_name_free(&zone->origin, view->mctx); + if (dns_name_dynamic(&zone->nsdname)) + dns_name_free(&zone->nsdname, view->mctx); + if (dns_name_dynamic(&zone->cname)) + dns_name_free(&zone->cname, view->mctx); + isc_mem_put(view->mctx, zone, sizeof(*zone)); + } +} + +/* + * Note that we have at least one response policy zone. + * It would be better for something to tell the rbtdb code that the + * zone is in at least one view's list of policy zones. + */ +void +dns_rpz_set_need(isc_boolean_t need) +{ + have_rpz_zones = need; +} + + +isc_boolean_t +dns_rpz_needed(void) +{ + return (have_rpz_zones); +} + + + +/* + * Start a new radix tree for a response policy zone. + */ +isc_result_t +dns_rpz_new_cidr(isc_mem_t *mctx, dns_name_t *origin, + dns_rpz_cidr_t **rbtdb_cidr) +{ + isc_result_t result; + dns_rpz_cidr_t *cidr; + + REQUIRE(rbtdb_cidr != NULL && *rbtdb_cidr == NULL); + + /* + * Only if there is at least one response policy zone. + */ + if (!have_rpz_zones) + return (ISC_R_SUCCESS); + + cidr = isc_mem_get(mctx, sizeof(*cidr)); + if (cidr == NULL) + return (ISC_R_NOMEMORY); + memset(cidr, 0, sizeof(*cidr)); + cidr->mctx = mctx; + + dns_name_init(&cidr->ip_name, NULL); + result = dns_name_fromstring2(&cidr->ip_name, DNS_RPZ_IP_ZONE, origin, + DNS_NAME_DOWNCASE, mctx); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, cidr, sizeof(*cidr)); + return (result); + } + + dns_name_init(&cidr->nsip_name, NULL); + result = dns_name_fromstring2(&cidr->nsip_name, DNS_RPZ_NSIP_ZONE, + origin, DNS_NAME_DOWNCASE, mctx); + if (result != ISC_R_SUCCESS) { + dns_name_free(&cidr->ip_name, mctx); + isc_mem_put(mctx, cidr, sizeof(*cidr)); + return (result); + } + + dns_name_init(&cidr->nsdname_name, NULL); + result = dns_name_fromstring2(&cidr->nsdname_name, DNS_RPZ_NSDNAME_ZONE, + origin, DNS_NAME_DOWNCASE, mctx); + if (result != ISC_R_SUCCESS) { + dns_name_free(&cidr->nsip_name, mctx); + dns_name_free(&cidr->ip_name, mctx); + isc_mem_put(mctx, cidr, sizeof(*cidr)); + return (result); + } + + *rbtdb_cidr = cidr; + return (ISC_R_SUCCESS); +} + + +/* + * See if a policy zone has IP, NSIP, or NSDNAME rules or records. + */ +void +dns_rpz_enabled(dns_rpz_cidr_t *cidr, dns_rpz_st_t *st) { + if (cidr->root != NULL && + (cidr->root->flags & DNS_RPZ_CIDR_FG_IP) != 0) + st->state |= DNS_RPZ_HAVE_IP; + if (cidr->root != NULL && + (cidr->root->flags & DNS_RPZ_CIDR_FG_NSIPv4) != 0) + st->state |= DNS_RPZ_HAVE_NSIPv4; + if (cidr->root != NULL && + (cidr->root->flags & DNS_RPZ_CIDR_FG_NSIPv6) != 0) + st->state |= DNS_RPZ_HAVE_NSIPv6; + if (cidr->had_nsdname) + st->state |= DNS_RPZ_HAD_NSDNAME; +} + +static inline dns_rpz_cidr_flags_t +get_flags(const dns_rpz_cidr_key_t *ip, dns_rpz_cidr_bits_t prefix, + dns_rpz_type_t rpz_type) +{ + if (rpz_type == DNS_RPZ_TYPE_NSIP) { + if (prefix >= 96 && + ip->w[0] == 0 && ip->w[1] == 0 && + ip->w[2] == ADDR_V4MAPPED) + return (DNS_RPZ_CIDR_FG_NSIP_DATA | + DNS_RPZ_CIDR_FG_NSIPv4); + else + return (DNS_RPZ_CIDR_FG_NSIP_DATA | + DNS_RPZ_CIDR_FG_NSIPv6); + } else { + return (DNS_RPZ_CIDR_FG_IP | DNS_RPZ_CIDR_FG_IP_DATA); + } +} + + + +/* + * Mark a node as having IP or NSIP data and all of its parents + * as members of the IP or NSIP tree. + */ +static void +set_node_flags(dns_rpz_cidr_node_t *node, dns_rpz_type_t rpz_type) { + dns_rpz_cidr_flags_t flags; + + flags = get_flags(&node->ip, node->bits, rpz_type); + node->flags |= flags; + flags &= ~(DNS_RPZ_CIDR_FG_NSIP_DATA | DNS_RPZ_CIDR_FG_IP_DATA); + for (;;) { + node = node->parent; + if (node == NULL) + return; + node->flags |= flags; + } +} + + + +/* + * Make a radix tree node. + */ +static dns_rpz_cidr_node_t * +new_node(dns_rpz_cidr_t *cidr, const dns_rpz_cidr_key_t *ip, + dns_rpz_cidr_bits_t bits, dns_rpz_cidr_flags_t flags) +{ + dns_rpz_cidr_node_t *node; + int i, words, wlen; + + node = isc_mem_get(cidr->mctx, sizeof(*node)); + if (node == NULL) + return (NULL); + memset(node, 0, sizeof(*node)); + + node->flags = flags & ~(DNS_RPZ_CIDR_FG_IP_DATA | + DNS_RPZ_CIDR_FG_NSIP_DATA); + + node->bits = bits; + words = bits / DNS_RPZ_CIDR_WORD_BITS; + wlen = bits % DNS_RPZ_CIDR_WORD_BITS; + i = 0; + while (i < words) { + node->ip.w[i] = ip->w[i]; + ++i; + } + if (wlen != 0) { + node->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen); + ++i; + } + while (i < DNS_RPZ_CIDR_WORDS) + node->ip.w[i++] = 0; + + return (node); +} + + + +static void +badname(int level, dns_name_t *name, const char *comment) +{ + char printname[DNS_NAME_FORMATSIZE]; + + if (isc_log_wouldlog(dns_lctx, level)) { + dns_name_format(name, printname, sizeof(printname)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_RBTDB, level, + "invalid response policy name \"%s\"%s", + printname, comment); + } +} + + + +/* + * Convert an IP address from radix tree binary (host byte order) to + * to its canonical response policy domain name and its name in the + * policy zone. + */ +static isc_result_t +ip2name(dns_rpz_cidr_t *cidr, const dns_rpz_cidr_key_t *tgt_ip, + dns_rpz_cidr_bits_t tgt_prefix, dns_rpz_type_t type, + dns_name_t *canon_name, dns_name_t *search_name) +{ +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + int w[DNS_RPZ_CIDR_WORDS*2]; + char str[1+8+1+INET6_ADDRSTRLEN+1]; + isc_buffer_t buffer; + dns_name_t *name; + isc_result_t result; + isc_boolean_t zeros; + int i, n, len; + + if (tgt_prefix > 96 && + tgt_ip->w[0] == 0 && + tgt_ip->w[1] == 0 && + tgt_ip->w[2] == ADDR_V4MAPPED) { + len = snprintf(str, sizeof(str), "%d.%d.%d.%d.%d", + tgt_prefix - 96, + tgt_ip->w[3] & 0xff, + (tgt_ip->w[3]>>8) & 0xff, + (tgt_ip->w[3]>>16) & 0xff, + (tgt_ip->w[3]>>24) & 0xff); + if (len == -1 || len > (int)sizeof(str)) + return (ISC_R_FAILURE); + } else { + for (i = 0; i < DNS_RPZ_CIDR_WORDS; i++) { + w[i*2+1] = ((tgt_ip->w[DNS_RPZ_CIDR_WORDS-1-i] >> 16) + & 0xffff); + w[i*2] = tgt_ip->w[DNS_RPZ_CIDR_WORDS-1-i] & 0xffff; + } + zeros = ISC_FALSE; + len = snprintf(str, sizeof(str), "%d", tgt_prefix); + if (len == -1) + return (ISC_R_FAILURE); + i = 0; + while (i < DNS_RPZ_CIDR_WORDS * 2) { + if (w[i] != 0 || zeros + || i >= DNS_RPZ_CIDR_WORDS * 2 - 1 + || w[i+1] != 0) { + INSIST((size_t)len <= sizeof(str)); + n = snprintf(&str[len], sizeof(str) - len, + ".%x", w[i++]); + if (n < 0) + return (ISC_R_FAILURE); + len += n; + } else { + zeros = ISC_TRUE; + INSIST((size_t)len <= sizeof(str)); + n = snprintf(&str[len], sizeof(str) - len, + ".zz"); + if (n < 0) + return (ISC_R_FAILURE); + len += n; + i += 2; + while (i < DNS_RPZ_CIDR_WORDS * 2 && w[i] == 0) + ++i; + } + if (len > (int)sizeof(str)) + return (ISC_R_FAILURE); + } + } + + if (canon_name != NULL) { + isc__buffer_init(&buffer, str, sizeof(str)); + isc__buffer_add(&buffer, len); + result = dns_name_fromtext(canon_name, &buffer, + dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) + return (result); + } + if (search_name != NULL) { + isc__buffer_init(&buffer, str, sizeof(str)); + isc__buffer_add(&buffer, len); + if (type == DNS_RPZ_TYPE_NSIP) + name = &cidr->nsip_name; + else + name = &cidr->ip_name; + result = dns_name_fromtext(search_name, &buffer, name, 0, NULL); + if (result != ISC_R_SUCCESS) + return (result); + } + return (ISC_R_SUCCESS); +} + + + +/* + * Decide which kind of IP address response policy zone a name is in. + */ +static dns_rpz_type_t +set_type(dns_rpz_cidr_t *cidr, dns_name_t *name) { + + if (dns_name_issubdomain(name, &cidr->ip_name)) + return (DNS_RPZ_TYPE_IP); + + /* + * Require `./configure --enable-rpz-nsip` and nsdname + * until consistency problems are resolved. + */ +#ifdef ENABLE_RPZ_NSIP + if (dns_name_issubdomain(name, &cidr->nsip_name)) + return (DNS_RPZ_TYPE_NSIP); +#endif + +#ifdef ENABLE_RPZ_NSDNAME + if (dns_name_issubdomain(name, &cidr->nsdname_name)) + return (DNS_RPZ_TYPE_NSDNAME); +#endif + + return (DNS_RPZ_TYPE_QNAME); +} + + + +/* + * Convert an IP address from canonical response policy domain name form + * to radix tree binary (host byte order). + */ +static isc_result_t +name2ipkey(dns_rpz_cidr_t *cidr, int level, dns_name_t *src_name, + dns_rpz_type_t type, dns_rpz_cidr_key_t *tgt_ip, + dns_rpz_cidr_bits_t *tgt_prefix) +{ + isc_buffer_t buffer; + unsigned char data[DNS_NAME_MAXWIRE+1]; + dns_fixedname_t fname; + dns_name_t *name; + const char *cp, *end; + char *cp2; + int ip_labels; + dns_rpz_cidr_bits_t bits; + unsigned long prefix, l; + int i; + + /* + * Need at least enough labels for the shortest name, + * :: or 128.*.RPZ_x_ZONE.rpz.LOCALHOST. + */ + ip_labels = dns_name_countlabels(src_name); + ip_labels -= dns_name_countlabels(&cidr->ip_name); + ip_labels--; + if (ip_labels < 1) { + badname(level, src_name, ", too short"); + return (ISC_R_FAILURE); + } + + /* + * Get text for the IP address without RPZ_x_ZONE.rpz.LOCALHOST. + */ + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + dns_name_split(src_name, dns_name_countlabels(&cidr->ip_name), + name, NULL); + isc_buffer_init(&buffer, data, sizeof(data)); + dns_name_totext(name, ISC_TRUE, &buffer); + isc_buffer_putuint8(&buffer, '\0'); + cp = isc_buffer_base(&buffer); + + prefix = strtoul(cp, &cp2, 10); + if (prefix < 1 || prefix > 128 || *cp2 != '.') { + badname(level, src_name, ", bad prefix length"); + return (ISC_R_FAILURE); + } + cp = cp2+1; + + end = isc_buffer_used(&buffer); + if (ip_labels == 4 && !strchr(cp, 'z')) { + /* + * Convert an IPv4 address + * from the form "prefix.w.z.y.x" + */ + if (prefix > 32) { + badname(level, src_name, "; bad IPv4 prefix length"); + return (ISC_R_FAILURE); + } + prefix += 96; + *tgt_prefix = prefix; + tgt_ip->w[0] = 0; + tgt_ip->w[1] = 0; + tgt_ip->w[2] = ADDR_V4MAPPED; + tgt_ip->w[3] = 0; + for (i = 0; i < 32; i += 8) { + l = strtoul(cp, &cp2, 10); + if (l > 255 || (*cp2 != '.' && *cp2 != '\0')) { + badname(level, src_name, "; bad IPv4 address"); + return (ISC_R_FAILURE); + } + tgt_ip->w[3] |= l << i; + cp = cp2 + 1; + } + } else { + /* + * Convert a text IPv6 address. + */ + *tgt_prefix = prefix; + for (i = 0; + ip_labels > 0 && i < DNS_RPZ_CIDR_WORDS * 2; + ip_labels--) { + if (cp[0] == 'z' && cp[1] == 'z' && + (cp[2] == '.' || cp[2] == '\0') && + i <= 6) { + do { + if ((i & 1) == 0) + tgt_ip->w[3-i/2] = 0; + ++i; + } while (ip_labels + i <= 8); + cp += 3; + } else { + l = strtoul(cp, &cp2, 16); + if (l > 0xffff || + (*cp2 != '.' && *cp2 != '\0')) { + badname(level, src_name, ""); + return (ISC_R_FAILURE); + } + if ((i & 1) == 0) + tgt_ip->w[3-i/2] = l; + else + tgt_ip->w[3-i/2] |= l << 16; + i++; + cp = cp2 + 1; + } + } + } + if (cp != end) { + badname(level, src_name, ""); + return (ISC_R_FAILURE); + } + + /* + * Check for 1s after the prefix length. + */ + bits = prefix; + while (bits < DNS_RPZ_CIDR_KEY_BITS) { + dns_rpz_cidr_word_t aword; + + i = bits % DNS_RPZ_CIDR_WORD_BITS; + aword = tgt_ip->w[bits / DNS_RPZ_CIDR_WORD_BITS]; + if ((aword & ~DNS_RPZ_WORD_MASK(i)) != 0) { + badname(level, src_name, "; wrong prefix length"); + return (ISC_R_FAILURE); + } + bits -= i; + bits += DNS_RPZ_CIDR_WORD_BITS; + } + + /* + * Convert the IPv6 address back to a canonical policy domain name + * to ensure that it is in canonical form. + */ + if (ISC_R_SUCCESS != ip2name(cidr, tgt_ip, prefix, type, NULL, name) || + !dns_name_equal(src_name, name)) { + badname(level, src_name, "; not canonical"); + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + + + +/* + * find first differing bit + */ +static int +ffbit(dns_rpz_cidr_word_t w) { + int bit; + + if (w == 0) + return (DNS_RPZ_CIDR_WORD_BITS); + for (bit = 0; (w & (1 << (DNS_RPZ_CIDR_WORD_BITS-1))) == 0; bit++) + w <<= 1; + return (bit); +} + + + +/* + * find the first differing bit in two keys + */ +static int +diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_cidr_bits_t bits1, + const dns_rpz_cidr_key_t *key2, dns_rpz_cidr_bits_t bits2) +{ + dns_rpz_cidr_word_t delta; + dns_rpz_cidr_bits_t maxbit, bit; + int i; + + maxbit = ISC_MIN(bits1, bits2); + + /* + * find the first differing words + */ + for (i = 0, bit = 0; + bit <= maxbit; + i++, bit += DNS_RPZ_CIDR_WORD_BITS) { + delta = key1->w[i] ^ key2->w[i]; + if (delta != 0) { + bit += ffbit(delta); + break; + } + } + return (ISC_MIN(bit, maxbit)); +} + + + +/* + * Search a radix tree for an IP address for ordinary lookup + * or for a CIDR block adding or deleting an entry + * The tree read (for simple search) or write lock must be held by the caller. + * + * return ISC_R_SUCCESS, ISC_R_NOTFOUND, DNS_R_PARTIALMATCH, ISC_R_EXISTS, + * ISC_R_NOMEMORY + */ +static isc_result_t +search(dns_rpz_cidr_t *cidr, const dns_rpz_cidr_key_t *tgt_ip, + dns_rpz_cidr_bits_t tgt_prefix, dns_rpz_type_t type, + isc_boolean_t create, + dns_rpz_cidr_node_t **found) /* NULL or longest match node */ +{ + dns_rpz_cidr_node_t *cur, *parent, *child, *new_parent, *sibling; + int cur_num, child_num; + dns_rpz_cidr_bits_t dbit; + dns_rpz_cidr_flags_t flags, data_flag; + isc_result_t find_result; + + flags = get_flags(tgt_ip, tgt_prefix, type); + data_flag = flags & (DNS_RPZ_CIDR_FG_IP_DATA | + DNS_RPZ_CIDR_FG_NSIP_DATA); + + find_result = ISC_R_NOTFOUND; + if (found != NULL) + *found = NULL; + cur = cidr->root; + parent = NULL; + cur_num = 0; + for (;;) { + if (cur == NULL) { + /* + * No child so we cannot go down. Fail or + * add the target as a child of the current parent. + */ + if (!create) + return (find_result); + child = new_node(cidr, tgt_ip, tgt_prefix, 0); + if (child == NULL) + return (ISC_R_NOMEMORY); + if (parent == NULL) + cidr->root = child; + else + parent->child[cur_num] = child; + child->parent = parent; + set_node_flags(child, type); + if (found != NULL) + *found = cur; + return (ISC_R_SUCCESS); + } + + /* + * Pretend a node not in the correct tree does not exist + * if we are not adding to the tree, + * If we are adding, then continue down to eventually + * add a node and mark/put this node in the correct tree. + */ + if ((cur->flags & flags) == 0 && !create) + return (find_result); + + dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->bits); + /* + * dbit <= tgt_prefix and dbit <= cur->bits always. + * We are finished searching if we matched all of the target. + */ + if (dbit == tgt_prefix) { + if (tgt_prefix == cur->bits) { + /* + * The current node matches the target exactly. + * It is the answer if it has data. + */ + if ((cur->flags & data_flag) != 0) { + if (create) + return (ISC_R_EXISTS); + if (found != NULL) + *found = cur; + return (ISC_R_SUCCESS); + } else if (create) { + /* + * The node had no data but does now. + */ + set_node_flags(cur, type); + if (found != NULL) + *found = cur; + return (ISC_R_SUCCESS); + } + return (find_result); + } + + /* + * We know tgt_prefix < cur_bits which means that + * the target is shorter than the current node. + * Add the target as the current node's parent. + */ + if (!create) + return (find_result); + + new_parent = new_node(cidr, tgt_ip, tgt_prefix, + cur->flags); + if (new_parent == NULL) + return (ISC_R_NOMEMORY); + new_parent->parent = parent; + if (parent == NULL) + cidr->root = new_parent; + else + parent->child[cur_num] = new_parent; + child_num = DNS_RPZ_IP_BIT(&cur->ip, tgt_prefix+1); + new_parent->child[child_num] = cur; + cur->parent = new_parent; + set_node_flags(new_parent, type); + if (found != NULL) + *found = new_parent; + return (ISC_R_SUCCESS); + } + + if (dbit == cur->bits) { + /* + * We have a partial match by matching of all of the + * current node but only part of the target. + * Try to go down. + */ + if ((cur->flags & data_flag) != 0) { + find_result = DNS_R_PARTIALMATCH; + if (found != NULL) + *found = cur; + } + + parent = cur; + cur_num = DNS_RPZ_IP_BIT(tgt_ip, dbit); + cur = cur->child[cur_num]; + continue; + } + + + /* + * dbit < tgt_prefix and dbit < cur->bits, + * so we failed to match both the target and the current node. + * Insert a fork of a parent above the current node and + * add the target as a sibling of the current node + */ + if (!create) + return (find_result); + + sibling = new_node(cidr, tgt_ip, tgt_prefix, 0); + if (sibling == NULL) + return (ISC_R_NOMEMORY); + new_parent = new_node(cidr, tgt_ip, dbit, cur->flags); + if (new_parent == NULL) { + isc_mem_put(cidr->mctx, sibling, sizeof(*sibling)); + return (ISC_R_NOMEMORY); + } + new_parent->parent = parent; + if (parent == NULL) + cidr->root = new_parent; + else + parent->child[cur_num] = new_parent; + child_num = DNS_RPZ_IP_BIT(tgt_ip, dbit); + new_parent->child[child_num] = sibling; + new_parent->child[1-child_num] = cur; + cur->parent = new_parent; + sibling->parent = new_parent; + set_node_flags(sibling, type); + if (found != NULL) + *found = sibling; + return (ISC_R_SUCCESS); + } +} + + + +/* + * Add an IP address to the radix tree of a response policy database. + * The tree write lock must be held by the caller. + */ +void +dns_rpz_cidr_addip(dns_rpz_cidr_t *cidr, dns_name_t *name) +{ + dns_rpz_cidr_key_t tgt_ip; + dns_rpz_cidr_bits_t tgt_prefix; + dns_rpz_type_t type; + + if (cidr == NULL) + return; + + /* + * no worries if the new name is not an IP address + */ + type = set_type(cidr, name); + switch (type) { + case DNS_RPZ_TYPE_IP: + case DNS_RPZ_TYPE_NSIP: + break; + case DNS_RPZ_TYPE_NSDNAME: + cidr->had_nsdname = ISC_TRUE; + return; + case DNS_RPZ_TYPE_QNAME: + case DNS_RPZ_TYPE_BAD: + return; + } + if (ISC_R_SUCCESS != name2ipkey(cidr, DNS_RPZ_ERROR_LEVEL, name, + type, &tgt_ip, &tgt_prefix)) + return; + + if (ISC_R_EXISTS == search(cidr, &tgt_ip, tgt_prefix, type, + ISC_TRUE, NULL) && + isc_log_wouldlog(dns_lctx, DNS_RPZ_ERROR_LEVEL)) { + char printname[DNS_NAME_FORMATSIZE]; + + dns_name_format(name, printname, sizeof(printname)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "duplicate response policy name \"%s\"", + printname); + } +} + + + +/* + * Delete an IP address from the radix tree of a response policy database. + * The tree write lock must be held by the caller. + */ +void +dns_rpz_cidr_deleteip(dns_rpz_cidr_t *cidr, dns_name_t *name) { + dns_rpz_cidr_key_t tgt_ip; + dns_rpz_cidr_bits_t tgt_prefix; + dns_rpz_type_t type; + dns_rpz_cidr_node_t *tgt = NULL, *parent, *child; + dns_rpz_cidr_flags_t flags, data_flag; + + if (cidr == NULL) + return; + + /* + * Decide which kind of policy zone IP address it is, if either + * and then find its node. + */ + type = set_type(cidr, name); + switch (type) { + case DNS_RPZ_TYPE_IP: + case DNS_RPZ_TYPE_NSIP: + break; + case DNS_RPZ_TYPE_NSDNAME: + /* + * We cannot easily count nsdnames because + * internal rbt nodes get deleted. + */ + return; + case DNS_RPZ_TYPE_QNAME: + case DNS_RPZ_TYPE_BAD: + return; + } + + /* + * Do not get excited about the deletion of interior rbt nodes. + */ + if (ISC_R_SUCCESS != name2ipkey(cidr, DNS_RPZ_DEBUG_LEVEL2, name, + type, &tgt_ip, &tgt_prefix)) + return; + if (ISC_R_SUCCESS != search(cidr, &tgt_ip, tgt_prefix, type, + ISC_FALSE, &tgt)) { + if (isc_log_wouldlog(dns_lctx, DNS_RPZ_ERROR_LEVEL)) { + char printname[DNS_NAME_FORMATSIZE]; + + dns_name_format(name, printname, sizeof(printname)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "missing response policy node \"%s\"", + printname); + } + return; + } + + /* + * Mark the node and its parents to reflect the deleted IP address. + */ + flags = get_flags(&tgt_ip, tgt_prefix, type); + data_flag = flags & (DNS_RPZ_CIDR_FG_IP_DATA | + DNS_RPZ_CIDR_FG_NSIP_DATA); + tgt->flags &= ~data_flag; + for (parent = tgt; parent != NULL; parent = parent->parent) { + if ((parent->flags & data_flag) != 0 || + (parent->child[0] != NULL && + (parent->child[0]->flags & flags) != 0) || + (parent->child[1] != NULL && + (parent->child[1]->flags & flags) != 0)) + break; + parent->flags &= ~flags; + } + + /* + * We might need to delete 2 nodes. + */ + do { + /* + * The node is now useless if it has no data of its own + * and 0 or 1 children. We are finished if it is not useless. + */ + if ((child = tgt->child[0]) != NULL) { + if (tgt->child[1] != NULL) + return; + } else { + child = tgt->child[1]; + } + if ((tgt->flags & (DNS_RPZ_CIDR_FG_IP_DATA | + DNS_RPZ_CIDR_FG_NSIP_DATA)) != 0) + return; + + /* + * Replace the pointer to this node in the parent with + * the remaining child or NULL. + */ + parent = tgt->parent; + if (parent == NULL) { + cidr->root = child; + } else { + parent->child[parent->child[1] == tgt] = child; + } + /* + * If the child exists fix up its parent pointer. + */ + if (child != NULL) + child->parent = parent; + isc_mem_put(cidr->mctx, tgt, sizeof(*tgt)); + + tgt = parent; + } while (tgt != NULL); +} + + + +/* + * Caller must hold tree lock. + * Return ISC_R_NOTFOUND + * or ISC_R_SUCCESS and the found entry's canonical and search names + * and its prefix length + */ +isc_result_t +dns_rpz_cidr_find(dns_rpz_cidr_t *cidr, const isc_netaddr_t *netaddr, + dns_rpz_type_t type, dns_name_t *canon_name, + dns_name_t *search_name, dns_rpz_cidr_bits_t *prefix) +{ + dns_rpz_cidr_key_t tgt_ip; + isc_result_t result; + dns_rpz_cidr_node_t *found; + int i; + + /* + * Convert IP address to CIDR tree key. + */ + if (netaddr->family == AF_INET) { + tgt_ip.w[0] = 0; + tgt_ip.w[1] = 0; + tgt_ip.w[2] = ADDR_V4MAPPED; + tgt_ip.w[3] = ntohl(netaddr->type.in.s_addr); + } else if (netaddr->family == AF_INET6) { + dns_rpz_cidr_key_t src_ip6; + + /* + * Given the int aligned struct in_addr member of netaddr->type + * one could cast netaddr->type.in6 to dns_rpz_cidr_key_t *, + * but there are objections. + */ + memcpy(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w)); + for (i = 0; i < 4; i++) { + tgt_ip.w[i] = ntohl(src_ip6.w[i]); + } + } else { + return (ISC_R_NOTFOUND); + } + + result = search(cidr, &tgt_ip, 128, type, ISC_FALSE, &found); + if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) + return (result); + + *prefix = found->bits; + return (ip2name(cidr, &found->ip, found->bits, type, + canon_name, search_name)); +} + + + +/* + * Translate CNAME rdata to a QNAME response policy action. + */ +dns_rpz_policy_t +dns_rpz_decode_cname(dns_rdataset_t *rdataset, dns_name_t *selfname) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_cname_t cname; + isc_result_t result; + + result = dns_rdataset_first(rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + + /* + * CNAME . means NXDOMAIN + */ + if (dns_name_equal(&cname.cname, dns_rootname)) + return (DNS_RPZ_POLICY_NXDOMAIN); + + /* + * CNAME *. means NODATA + */ + if (dns_name_countlabels(&cname.cname) == 2 + && dns_name_iswildcard(&cname.cname)) + return (DNS_RPZ_POLICY_NODATA); + + /* + * 128.1.0.127.rpz-ip CNAME 128.1.0.0.127. means "do not rewrite" + */ + if (selfname != NULL && dns_name_equal(&cname.cname, selfname)) + return (DNS_RPZ_POLICY_NO_OP); + + /* + * evil.com CNAME garden.net rewrites www.evil.com to www.garden.net. + */ + return (DNS_RPZ_POLICY_RECORD); +} diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c index 7d8dd745d3..fff0337727 100644 --- a/lib/dns/sdb.c +++ b/lib/dns/sdb.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: sdb.c,v 1.74 2010/08/16 04:46:16 marka Exp $ */ +/* $Id: sdb.c,v 1.75 2011/01/13 01:59:27 marka Exp $ */ /*! \file */ @@ -1254,6 +1254,8 @@ static dns_dbmethods_t sdb_methods = { NULL, NULL, NULL, + NULL, + NULL }; static isc_result_t diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c index 2e3179aaae..a464a56b6d 100644 --- a/lib/dns/sdlz.c +++ b/lib/dns/sdlz.c @@ -50,7 +50,7 @@ * USE OR PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: sdlz.c,v 1.28 2010/12/19 02:37:08 each Exp $ */ +/* $Id: sdlz.c,v 1.29 2011/01/13 01:59:28 marka Exp $ */ /*! \file */ @@ -1237,6 +1237,8 @@ static dns_dbmethods_t sdlzdb_methods = { NULL, NULL, NULL, + NULL, + NULL, NULL }; diff --git a/lib/dns/view.c b/lib/dns/view.c index 7d795c2185..5c9f3c013f 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: view.c,v 1.176 2011/01/11 23:47:13 tbox Exp $ */ +/* $Id: view.c,v 1.177 2011/01/13 01:59:28 marka Exp $ */ /*! \file */ @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -191,6 +192,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, view->maxudp = 0; view->v4_aaaa = dns_v4_aaaa_ok; view->v4_aaaa_acl = NULL; + ISC_LIST_INIT(view->rpz_zones); dns_fixedname_init(&view->dlv_fixed); view->managed_keys = NULL; #ifdef BIND9 @@ -326,6 +328,7 @@ destroy(dns_view_t *view) { dns_acache_detach(&view->acache); } #endif + dns_rpz_view_destroy(view); if (view->requestmgr != NULL) dns_requestmgr_detach(&view->requestmgr); if (view->task != NULL) diff --git a/lib/dns/win32/libdns.def b/lib/dns/win32/libdns.def index 4c97e06028..c886b8febe 100644 --- a/lib/dns/win32/libdns.def +++ b/lib/dns/win32/libdns.def @@ -603,6 +603,18 @@ dns_result_register dns_result_torcode dns_result_totext dns_rootns_create +dns_rpz_cidr_addip +dns_rpz_cidr_deleteip +dns_rpz_cidr_find +dns_rpz_cidr_free +dns_rpz_decode_cname +dns_rpz_enabled +dns_rpz_needed +dns_rpz_new_cidr +dns_rpz_set_need +dns_rpz_str2policy +dns_rpz_type2str +dns_rpz_view_destroy dns_rriterator_current dns_rriterator_destroy dns_rriterator_first diff --git a/lib/dns/win32/libdns.dsp b/lib/dns/win32/libdns.dsp index 4a7f3e06a9..ef0a9ac067 100644 --- a/lib/dns/win32/libdns.dsp +++ b/lib/dns/win32/libdns.dsp @@ -338,6 +338,10 @@ SOURCE=..\include\dns\rootns.h # End Source File # Begin Source File +SOURCE=..\include\dns\rpz.h +# End Source File +# Begin Source File + SOURCE=..\include\dns\rriterator.h # End Source File # Begin Source File @@ -630,6 +634,10 @@ SOURCE=..\rootns.c # End Source File # Begin Source File +SOURCE=..\rpz.c +# End Source File +# Begin Source File + SOURCE=..\rriterator.c # End Source File # Begin Source File diff --git a/lib/dns/win32/libdns.mak b/lib/dns/win32/libdns.mak index c36b91c550..09abddcebb 100644 --- a/lib/dns/win32/libdns.mak +++ b/lib/dns/win32/libdns.mak @@ -181,6 +181,7 @@ CLEAN : -@erase "$(INTDIR)\resolver.obj" -@erase "$(INTDIR)\result.obj" -@erase "$(INTDIR)\rootns.obj" + -@erase "$(INTDIR)\rpz.obj" -@erase "$(INTDIR)\sdb.obj" -@erase "$(INTDIR)\sdlz.obj" -@erase "$(INTDIR)\soa.obj" @@ -303,6 +304,7 @@ LINK32_OBJS= \ "$(INTDIR)\resolver.obj" \ "$(INTDIR)\result.obj" \ "$(INTDIR)\rootns.obj" \ + "$(INTDIR)\rpz.obj" \ "$(INTDIR)\rriterator.obj" \ "$(INTDIR)\sdb.obj" \ "$(INTDIR)\sdlz.obj" \ @@ -491,6 +493,8 @@ CLEAN : -@erase "$(INTDIR)\result.sbr" -@erase "$(INTDIR)\rootns.obj" -@erase "$(INTDIR)\rootns.sbr" + -@erase "$(INTDIR)\rpz.obj" + -@erase "$(INTDIR)\rpz.sbr" -@erase "$(INTDIR)\rriterator.obj" -@erase "$(INTDIR)\rriterator.sbr" -@erase "$(INTDIR)\sdb.obj" @@ -633,6 +637,7 @@ BSC32_SBRS= \ "$(INTDIR)\resolver.sbr" \ "$(INTDIR)\result.sbr" \ "$(INTDIR)\rootns.sbr" \ + "$(INTDIR)\rpz.sbr" \ "$(INTDIR)\rriterator.sbr" \ "$(INTDIR)\sdb.sbr" \ "$(INTDIR)\sdlz.sbr" \ @@ -726,6 +731,7 @@ LINK32_OBJS= \ "$(INTDIR)\resolver.obj" \ "$(INTDIR)\result.obj" \ "$(INTDIR)\rootns.obj" \ + "$(INTDIR)\rpz.obj" \ "$(INTDIR)\rriterator.obj" \ "$(INTDIR)\sdb.obj" \ "$(INTDIR)\sdlz.obj" \ @@ -1664,6 +1670,24 @@ SOURCE=..\rootns.c $(CPP) $(CPP_PROJ) $(SOURCE) +!ENDIF + +SOURCE=..\rpz.c + +!IF "$(CFG)" == "libdns - Win32 Release" + + +"$(INTDIR)\rpz.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "libdns - Win32 Debug" + + +"$(INTDIR)\rpz.obj" "$(INTDIR)\rpz.sbr" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + !ENDIF SOURCE=..\rriterator.c diff --git a/lib/dns/zone.c b/lib/dns/zone.c index c1289447a7..fc0305876c 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zone.c,v 1.580 2010/12/18 01:56:22 each Exp $ */ +/* $Id: zone.c,v 1.581 2011/01/13 01:59:28 marka Exp $ */ /*! \file */ @@ -322,6 +322,11 @@ struct dns_zone { * True if added by "rndc addzone" */ isc_boolean_t added; + + /*% + * whether a rpz radix was needed when last loaded + */ + isc_boolean_t rpz_zone; }; #define DNS_ZONE_FLAG(z,f) (ISC_TF(((z)->flags & (f)) != 0)) @@ -833,6 +838,7 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { zone->nodes = 100; zone->privatetype = (dns_rdatatype_t)0xffffU; zone->added = ISC_FALSE; + zone->rpz_zone = ISC_FALSE; zone->magic = ZONE_MAGIC; @@ -1434,7 +1440,8 @@ zone_load(dns_zone_t *zone, unsigned int flags) { * "rndc reconfig", we are done. */ if (!isc_time_isepoch(&zone->loadtime) && - (flags & DNS_ZONELOADFLAG_NOSTAT) != 0) { + (flags & DNS_ZONELOADFLAG_NOSTAT) != 0 && + zone->rpz_zone == dns_rpz_needed()) { result = ISC_R_SUCCESS; goto cleanup; } @@ -1443,7 +1450,8 @@ zone_load(dns_zone_t *zone, unsigned int flags) { if (result == ISC_R_SUCCESS) { if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HASINCLUDE) && - isc_time_compare(&filetime, &zone->loadtime) <= 0) { + isc_time_compare(&filetime, &zone->loadtime) <= 0 && + zone->rpz_zone == dns_rpz_needed()) { dns_zone_log(zone, ISC_LOG_DEBUG(1), "skipping load: master file " "older than last load"); @@ -1451,6 +1459,7 @@ zone_load(dns_zone_t *zone, unsigned int flags) { goto cleanup; } loadtime = filetime; + zone->rpz_zone = dns_rpz_needed(); } } diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 66c972bd8a..bc12bc5946 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: namedconf.c,v 1.130 2011/01/07 04:31:39 marka Exp $ */ +/* $Id: namedconf.c,v 1.131 2011/01/13 01:59:28 marka Exp $ */ /*! \file */ @@ -1013,6 +1013,120 @@ static cfg_type_t cfg_type_masterformat = { &cfg_rep_string, &masterformat_enums }; + + +/* + * response-policy { + * zone [ policy (given|no-op|nxdomain|nodata|cname ) ]; + * }; + * + * this is a chimera of doc_optional_keyvalue() and cfg_doc_enum() + */ +static void +doc_rpz_policies(cfg_printer_t *pctx, const cfg_type_t *type) { + const keyword_type_t *kw; + const char * const *p; + + kw = type->of; + cfg_print_chars(pctx, "[ ", 2); + cfg_print_cstr(pctx, kw->name); + cfg_print_chars(pctx, " ", 1); + + cfg_print_chars(pctx, "( ", 2); + for (p = kw->type->of; *p != NULL; p++) { + cfg_print_cstr(pctx, *p); + if (p[1] != NULL) + cfg_print_chars(pctx, " | ", 3); + } +} + +/* + * print_qstring() from parser.c + */ +static void +print_rpz_cname(cfg_printer_t *pctx, const cfg_obj_t *obj) +{ + cfg_print_chars(pctx, "\"", 1); + cfg_print_ustring(pctx, obj); + cfg_print_chars(pctx, "\"", 1); +} + +static void +doc_rpz_cname(cfg_printer_t *pctx, const cfg_type_t *type) { + cfg_doc_terminal(pctx, type); + cfg_print_chars(pctx, " ) ]", 4); +} + +static isc_result_t +parse_rpz(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + const cfg_tuplefielddef_t *fields = type->of; + + CHECK(cfg_create_tuple(pctx, type, &obj)); + CHECK(cfg_parse_obj(pctx, fields[0].type, &obj->value.tuple[0])); + CHECK(cfg_parse_obj(pctx, fields[1].type, &obj->value.tuple[1])); + /* + * parse cname domain only after "policy cname" + */ + if (cfg_obj_isvoid(obj->value.tuple[1]) || + strcasecmp("cname", cfg_obj_asstring(obj->value.tuple[1]))) { + CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[2])); + } else { + CHECK(cfg_parse_obj(pctx, fields[2].type, &obj->value.tuple[2])); + } + + *ret = obj; + return (ISC_R_SUCCESS); + +cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static const char *rpz_policies[] = { + "given", "no-op", "nxdomain", "nodata", "cname", NULL +}; +static cfg_type_t cfg_type_rpz_policylist = { + "policies", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &cfg_rep_string, &rpz_policies +}; +static keyword_type_t rpz_policies_kw = { + "policy", &cfg_type_rpz_policylist +}; +static cfg_type_t cfg_type_rpz_policy = { + "optional_policy", parse_optional_keyvalue, print_keyvalue, + doc_rpz_policies, &cfg_rep_string, &rpz_policies_kw +}; +static cfg_type_t cfg_type_cname = { + "domain", cfg_parse_astring, print_rpz_cname, doc_rpz_cname, + &cfg_rep_string, NULL +}; +static cfg_tuplefielddef_t rpzone_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "policy", &cfg_type_rpz_policy, 0 }, + { "cname", &cfg_type_cname, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_rpzone = { + "rpzone", parse_rpz, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, rpzone_fields +}; +static cfg_clausedef_t rpz_clauses[] = { + { "zone", &cfg_type_rpzone, CFG_CLAUSEFLAG_MULTI }, + { NULL, NULL, 0 } +}; +static cfg_clausedef_t *rpz_clausesets[] = { + rpz_clauses, + NULL +}; +static cfg_type_t cfg_type_rpz = { + "rpz", cfg_parse_map, cfg_print_map, cfg_doc_map, + &cfg_rep_map, rpz_clausesets +}; + + + /*% * dnssec-lookaside */ @@ -1146,6 +1260,7 @@ view_clauses[] = { { "filter-aaaa-on-v4", &cfg_type_v4_aaaa, CFG_CLAUSEFLAG_NOTCONFIGURED }, #endif + { "response-policy", &cfg_type_rpz, 0 }, { NULL, NULL, 0 } };