diff -r -u bin/named/query.c-orig bin/named/query.c --- bin/named/query.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/named/query.c 2004-01-01 00:00:00.000000000 +0000 @@ -879,11 +879,11 @@ static void rpz_log_rewrite(ns_client_t *client, isc_boolean_t disabled, dns_rpz_policy_t policy, dns_rpz_type_t type, - dns_zone_t *zone, dns_name_t *rpz_qname) + dns_zone_t *p_zone, dns_name_t *p_name) { isc_stats_t *zonestats; char qname_buf[DNS_NAME_FORMATSIZE]; - char rpz_qname_buf[DNS_NAME_FORMATSIZE]; + char p_name_buf[DNS_NAME_FORMATSIZE]; /* * Count enabled rewrites in the global counter. @@ -893,8 +893,8 @@ isc_stats_increment(ns_g_server->nsstats, dns_nsstatscounter_rpz_rewrites); } - if (zone != NULL) { - zonestats = dns_zone_getrequeststats(zone); + if (p_zone != NULL) { + zonestats = dns_zone_getrequeststats(p_zone); if (zonestats != NULL) isc_stats_increment(zonestats, dns_nsstatscounter_rpz_rewrites); @@ -904,68 +904,73 @@ return; dns_name_format(client->query.qname, qname_buf, sizeof(qname_buf)); - dns_name_format(rpz_qname, rpz_qname_buf, sizeof(rpz_qname_buf)); + dns_name_format(p_name, p_name_buf, sizeof(p_name_buf)); ns_client_log(client, DNS_LOGCATEGORY_RPZ, NS_LOGMODULE_QUERY, DNS_RPZ_INFO_LEVEL, "%srpz %s %s rewrite %s via %s", disabled ? "disabled " : "", dns_rpz_type2str(type), dns_rpz_policy2str(policy), - qname_buf, rpz_qname_buf); + qname_buf, p_name_buf); } static void -rpz_log_fail(ns_client_t *client, int level, - dns_rpz_type_t rpz_type, dns_name_t *name, - const char *str, isc_result_t result) +rpz_log_fail(ns_client_t *client, int level, dns_name_t *p_name, + dns_rpz_type_t rpz_type, const char *str, isc_result_t result) { - char namebuf1[DNS_NAME_FORMATSIZE]; - char namebuf2[DNS_NAME_FORMATSIZE]; + char qnamebuf[DNS_NAME_FORMATSIZE]; + char p_namebuf[DNS_NAME_FORMATSIZE]; + const char *failed; if (!isc_log_wouldlog(ns_g_lctx, level)) return; /* - * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed" for problems. */ - dns_name_format(client->query.qname, namebuf1, sizeof(namebuf1)); - dns_name_format(name, namebuf2, sizeof(namebuf2)); + if (level <= DNS_RPZ_DEBUG_LEVEL1) + failed = "failed: "; + else + failed = ": "; + dns_name_format(client->query.qname, qnamebuf, sizeof(qnamebuf)); + dns_name_format(p_name, p_namebuf, sizeof(p_namebuf)); ns_client_log(client, NS_LOGCATEGORY_QUERY_EERRORS, NS_LOGMODULE_QUERY, level, - "rpz %s rewrite %s via %s %sfailed: %s", + "rpz %s rewrite %s via %s%s%s%s", dns_rpz_type2str(rpz_type), - namebuf1, namebuf2, str, isc_result_totext(result)); + qnamebuf, p_namebuf, + str, failed, 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, +rpz_getdb(ns_client_t *client, dns_name_t *p_name, dns_rpz_type_t rpz_type, dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp) { - char namebuf1[DNS_NAME_FORMATSIZE]; - char namebuf2[DNS_NAME_FORMATSIZE]; + char qnamebuf[DNS_NAME_FORMATSIZE]; + char p_namebuf[DNS_NAME_FORMATSIZE]; dns_dbversion_t *rpz_version = NULL; isc_result_t result; - result = query_getzonedb(client, rpz_qname, dns_rdatatype_any, + result = query_getzonedb(client, p_name, dns_rdatatype_any, DNS_GETDB_IGNOREACL, zonep, dbp, &rpz_version); if (result == ISC_R_SUCCESS) { if (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)); + dns_name_format(client->query.qname, qnamebuf, + sizeof(qnamebuf)); + dns_name_format(p_name, p_namebuf, sizeof(p_namebuf)); ns_client_log(client, DNS_LOGCATEGORY_RPZ, NS_LOGMODULE_QUERY, DNS_RPZ_DEBUG_LEVEL2, "try rpz %s rewrite %s via %s", dns_rpz_type2str(rpz_type), - namebuf1, namebuf2); + qnamebuf, p_namebuf); } *versionp = rpz_version; return (ISC_R_SUCCESS); } - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, rpz_qname, - "query_getzonedb() ", result); + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, + " query_getzonedb()", result); return (result); } @@ -3913,7 +3918,7 @@ dns_rdataset_disassociate(*rdatasetp); } -static void +static inline void rpz_match_clear(dns_rpz_st_t *st) { rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset); @@ -3921,16 +3926,16 @@ } 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) +rpz_ready(ns_client_t *client, 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); + } else if (dns_rdataset_isassociated(*rdatasetp)) { + dns_rdataset_disassociate(*rdatasetp); } return (ISC_R_SUCCESS); } @@ -3959,13 +3964,83 @@ st->m.policy = DNS_RPZ_POLICY_MISS; } +static dns_rpz_zbits_t +rpz_get_zbits(ns_client_t *client, + dns_rdatatype_t ip_type, dns_rpz_type_t rpz_type) +{ + dns_rpz_zones_t *rpzs; + dns_rpz_st_t *st; + dns_rpz_zbits_t zbits; + + rpzs = client->view->rpzs; + + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + zbits = rpzs->have.client_ip; + break; + case DNS_RPZ_TYPE_QNAME: + zbits = rpzs->have.qname; + break; + case DNS_RPZ_TYPE_IP: + if (ip_type == dns_rdatatype_a) { + zbits = rpzs->have.ipv4; + } else if (ip_type == dns_rdatatype_aaaa) { + zbits = rpzs->have.ipv6; + } else { + zbits = rpzs->have.ip; + } + break; + case DNS_RPZ_TYPE_NSDNAME: + zbits = rpzs->have.nsdname; + break; + case DNS_RPZ_TYPE_NSIP: + if (ip_type == dns_rdatatype_a) { + zbits = rpzs->have.nsipv4; + } else if (ip_type == dns_rdatatype_aaaa) { + zbits = rpzs->have.nsipv6; + } else { + zbits = rpzs->have.nsip; + } + break; + default: + INSIST(0); + break; + } + + st = client->query.rpz_st; + + /* + * Choose + * the earliest configured policy zone (rpz->num) + * QNAME over IP over NSDNAME over NSIP (rpz_type) + * the smallest name, + * the longest IP address prefix, + * the lexically smallest address. + */ + if (st->m.policy != DNS_RPZ_POLICY_MISS) { + if (st->m.type >= rpz_type) { + zbits &= DNS_RPZ_ZMASK(st->m.rpz->num); + } else{ + zbits &= DNS_RPZ_ZMASK(st->m.rpz->num) >> 1; + } + } + + /* + * If the client wants recursion, allow only compatible policies. + */ + if (!RECURSIONOK(client)) + zbits &= rpzs->p.no_rd_ok; + + return (zbits); +} + /* - * Get NS, A, or AAAA rrset for response policy zone checks. + * Get an NS, A, or AAAA rrset related to the response for the client + * to check the contents of that rrset for hits by eligible policy zones. */ static isc_result_t -rpz_rrset_find(ns_client_t *client, dns_rpz_type_t rpz_type, - dns_name_t *name, dns_rdatatype_t type, - dns_db_t **dbp, dns_dbversion_t *version, +rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type, + dns_rpz_type_t rpz_type, dns_db_t **dbp, dns_dbversion_t *version, dns_rdataset_t **rdatasetp, isc_boolean_t resuming) { dns_rpz_st_t *st; @@ -3977,15 +4052,13 @@ dns_clientinfomethods_t cm; dns_clientinfo_t ci; - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client); - st = client->query.rpz_st; if ((st->state & DNS_RPZ_RECURSING) != 0) { INSIST(st->r.r_type == type); INSIST(dns_name_equal(name, st->r_name)); INSIST(*rdatasetp == NULL || !dns_rdataset_isassociated(*rdatasetp)); + INSIST(*dbp == NULL); st->state &= ~DNS_RPZ_RECURSING; *dbp = st->r.db; st->r.db = NULL; @@ -3995,16 +4068,15 @@ st->r.r_rdataset = NULL; result = st->r.r_result; if (result == DNS_R_DELEGATION) { - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, - rpz_type, name, - "rpz_rrset_find(1) ", result); + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, + rpz_type, " rpz_rrset_find(1)", result); st->m.policy = DNS_RPZ_POLICY_ERROR; result = DNS_R_SERVFAIL; } return (result); } - result = rpz_ready(client, NULL, NULL, NULL, rdatasetp); + result = rpz_ready(client, rdatasetp); if (result != ISC_R_SUCCESS) { st->m.policy = DNS_RPZ_POLICY_ERROR; return (result); @@ -4019,9 +4091,8 @@ result = query_getdb(client, name, type, 0, &zone, dbp, &version, &is_zone); if (result != ISC_R_SUCCESS) { - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, - rpz_type, name, - "rpz_rrset_find(2) ", result); + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, + rpz_type, " rpz_rrset_find(2)", result); st->m.policy = DNS_RPZ_POLICY_ERROR; if (zone != NULL) dns_zone_detach(&zone); @@ -4034,6 +4105,8 @@ node = NULL; dns_fixedname_init(&fixed); found = dns_fixedname_name(&fixed); + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client); result = dns_db_findext(*dbp, name, version, type, DNS_DBFIND_GLUEOK, client->now, &node, found, &cm, &ci, *rdatasetp, NULL); @@ -4072,177 +4145,97 @@ } /* - * Check the IP address in an A or AAAA rdataset against - * the IP or NSIP response policy rules of a view. + * Compute a policy owner name, p_name, in a policy zone given the needed + * policy type and the trigger name. */ static isc_result_t -rpz_rewrite_ip(ns_client_t *client, dns_rdataset_t *rdataset, - dns_rpz_type_t rpz_type) +rpz_get_p_name(ns_client_t *client, dns_name_t *p_name, + dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + dns_name_t *trig_name) { - dns_rpz_st_t *st; - dns_dbversion_t *version; - dns_zone_t *zone; - dns_db_t *db; - dns_rpz_zone_t *rpz; + dns_offsets_t prefix_offsets; + dns_name_t prefix, *suffix; + unsigned int first, labels; 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 (rpz = ISC_LIST_HEAD(client->view->rpz_zones); - rpz != NULL; - rpz = ISC_LIST_NEXT(rpz, link)) { - if (!RECURSIONOK(client) && rpz->recursive_only) - continue; - - /* - * Do not check policy zones that cannot replace a policy - * already known to match. - */ - if (st->m.policy != DNS_RPZ_POLICY_MISS) { - if (st->m.rpz->num < rpz->num) - break; - if (st->m.rpz->num == rpz->num && - st->m.type < rpz_type) - continue; - } - - /* - * Find the database for this policy zone to get its radix tree. - */ - version = NULL; - result = rpz_getdb(client, rpz_type, &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. - */ - dns_db_rpz_findips(rpz, rpz_type, zone, db, version, - rdataset, st, client->query.rpz_st->qname); - rpz_clean(&zone, &db, NULL, NULL); - } - return (ISC_R_SUCCESS); -} - -/* - * Look for an A or AAAA rdataset - * and check for IP or NSIP rewrite policy rules. - */ -static isc_result_t -rpz_rewrite_rrset(ns_client_t *client, dns_rpz_type_t rpz_type, - 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_rrset_find(client, rpz_type, name, type, dbp, version, - rdatasetp, resuming); - switch (result) { - case ISC_R_SUCCESS: - case DNS_R_GLUE: - case DNS_R_ZONECUT: - result = rpz_rewrite_ip(client, *rdatasetp, rpz_type); + /* + * The policy owner name consists of a suffix depending on the type + * and policy zone and a prefix that is the longest possible string + * from the trigger name that keesp the resulting policy owner name + * from being too long. + */ + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + suffix = &rpz->client_ip; 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: - case ISC_R_NOTFOUND: - result = ISC_R_SUCCESS; + case DNS_RPZ_TYPE_QNAME: + suffix = &rpz->origin; break; - case DNS_R_DELEGATION: - case DNS_R_DUPLICATE: - case DNS_R_DROP: + case DNS_RPZ_TYPE_IP: + suffix = &rpz->ip; break; - case DNS_R_CNAME: - case DNS_R_DNAME: - rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, rpz_type, - name, "NS address rewrite rrset ", result); - result = ISC_R_SUCCESS; + case DNS_RPZ_TYPE_NSDNAME: + suffix = &rpz->nsdname; break; - default: - if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { - client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR; - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, - name, "NS address rewrite rrset ", result); - } + case DNS_RPZ_TYPE_NSIP: + suffix = &rpz->nsip; break; + default: + INSIST(0); } - return (result); -} - -/* - * Look for both A and AAAA rdatasets - * and check for IP or NSIP rewrite policy rules. - * Look only for addresses that will be in the ANSWER section - * when checking for IP rules. - */ -static isc_result_t -rpz_rewrite_rrsets(ns_client_t *client, dns_rpz_type_t rpz_type, - dns_name_t *name, dns_rdatatype_t type, - dns_rdataset_t **rdatasetp, isc_boolean_t resuming) -{ - dns_rpz_st_t *st; - dns_dbversion_t *version; - dns_db_t *ipdb; - isc_result_t result; - st = client->query.rpz_st; - version = NULL; - ipdb = NULL; - if ((st->state & DNS_RPZ_DONE_IPv4) == 0 && - ((rpz_type == DNS_RPZ_TYPE_NSIP) ? - (st->state & DNS_RPZ_HAVE_NSIPv4) : - (st->state & DNS_RPZ_HAVE_IP)) != 0 && - (type == dns_rdatatype_any || type == dns_rdatatype_a)) { - result = rpz_rewrite_rrset(client, rpz_type, dns_rdatatype_a, - name, &ipdb, version, rdatasetp, - resuming); + /* + * Start with relative version of the full trigger name, + * and trim enough allow the addition of the suffix. + */ + dns_name_init(&prefix, prefix_offsets); + labels = dns_name_countlabels(trig_name); + first = 0; + for (;;) { + dns_name_getlabelsequence(trig_name, first, labels-first-1, + &prefix); + result = dns_name_concatenate(&prefix, suffix, p_name, NULL); if (result == ISC_R_SUCCESS) - st->state |= DNS_RPZ_DONE_IPv4; - } else { - result = ISC_R_SUCCESS; - } - if (result == ISC_R_SUCCESS && - ((rpz_type == DNS_RPZ_TYPE_NSIP) ? - (st->state & DNS_RPZ_HAVE_NSIPv6) : - (st->state & DNS_RPZ_HAVE_IP)) != 0 && - (type == dns_rdatatype_any || type == dns_rdatatype_aaaa)) { - result = rpz_rewrite_rrset(client, rpz_type, dns_rdatatype_aaaa, - name, &ipdb, version, rdatasetp, - resuming); + return (ISC_R_SUCCESS); + INSIST(result == DNS_R_NAMETOOLONG); + /* + * Trim the trigger name until the combination is not too long. + */ + if (labels-first < 2) { + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, suffix, + rpz_type, " concatentate()", result); + return (ISC_R_FAILURE); + } + /* + * Complain once about trimming the trigger name. + */ + if (first == 0) { + rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, suffix, + rpz_type, " concatentate()", result); + } + ++first; } - if (ipdb != NULL) - dns_db_detach(&ipdb); - return (result); } /* - * Get the rrset from a response policy zone. + * Look in policy zone rpz for a policy of rpz_type by p_name. + * The self-name (usually the client qname or an NS name) is compared with + * the target of a CNAME policy for the old style passthru encoding. + * If found, the policy is recorded in *zonep, *dbp, *versionp, *nodep, + * *rdatasetp, and *policyp. + * The target DNS type, qtype, chooses the best rdataset for *rdatasetp. + * The caller must decide if the found policy is most suitable, including + * better than a previously found policy. + * If it is best, the caller records it in client->query.rpz_st->m. */ static isc_result_t -rpz_find(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qnamef, - dns_name_t *sname, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, - dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp, - dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, - dns_rpz_policy_t *policyp) +rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype, + dns_name_t *p_name, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp, + dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, + dns_rpz_policy_t *policyp) { - dns_rpz_policy_t policy; - dns_fixedname_t fixed; + dns_fixedname_t foundf; dns_name_t *found; isc_result_t result; dns_clientinfomethods_t cm; @@ -4250,31 +4243,28 @@ REQUIRE(nodep != NULL); - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client); - - 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 + * Try to find either a CNAME or the type of record demanded by the * request from the policy zone. */ + rpz_clean(zonep, dbp, nodep, rdatasetp); + result = rpz_ready(client, rdatasetp); + if (result != ISC_R_SUCCESS) + return (DNS_R_SERVFAIL); *versionp = NULL; - result = rpz_getdb(client, rpz_type, qnamef, zonep, dbp, versionp); - if (result != ISC_R_SUCCESS) { - *policyp = DNS_RPZ_POLICY_MISS; + result = rpz_getdb(client, p_name, rpz_type, zonep, dbp, versionp); + if (result != ISC_R_SUCCESS) return (DNS_R_NXDOMAIN); - } - - dns_fixedname_init(&fixed); - found = dns_fixedname_name(&fixed); - result = dns_db_findext(*dbp, qnamef, *versionp, dns_rdatatype_any, 0, + dns_fixedname_init(&foundf); + found = dns_fixedname_name(&foundf); + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client); + result = dns_db_findext(*dbp, p_name, *versionp, dns_rdatatype_any, 0, client->now, nodep, found, &cm, &ci, *rdatasetp, NULL); + /* + * Choose the best rdataset if we found something. + */ if (result == ISC_R_SUCCESS) { dns_rdatasetiter_t *rdsiter; @@ -4282,10 +4272,8 @@ result = dns_db_allrdatasets(*dbp, *nodep, *versionp, 0, &rdsiter); if (result != ISC_R_SUCCESS) { - dns_db_detachnode(*dbp, nodep); - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, - qnamef, "allrdatasets() ", result); - *policyp = DNS_RPZ_POLICY_ERROR; + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, + rpz_type, " allrdatasets()", result); return (DNS_R_SERVFAIL); } for (result = dns_rdatasetiter_first(rdsiter); @@ -4301,9 +4289,8 @@ if (result != ISC_R_SUCCESS) { if (result != ISC_R_NOMORE) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, - rpz_type, qnamef, "rdatasetiter ", - result); - *policyp = DNS_RPZ_POLICY_ERROR; + p_name, rpz_type, + " rdatasetiter", result); return (DNS_R_SERVFAIL); } /* @@ -4318,7 +4305,7 @@ qtype == dns_rdatatype_sig) result = DNS_R_NXRRSET; else - result = dns_db_findext(*dbp, qnamef, *versionp, + result = dns_db_findext(*dbp, p_name, *versionp, qtype, 0, client->now, nodep, found, &cm, &ci, *rdatasetp, NULL); @@ -4327,162 +4314,476 @@ switch (result) { case ISC_R_SUCCESS: if ((*rdatasetp)->type != dns_rdatatype_cname) { - policy = DNS_RPZ_POLICY_RECORD; + *policyp = DNS_RPZ_POLICY_RECORD; } else { - policy = dns_rpz_decode_cname(rpz, *rdatasetp, sname); - if ((policy == DNS_RPZ_POLICY_RECORD || - policy == DNS_RPZ_POLICY_WILDCNAME) && + *policyp = dns_rpz_decode_cname(rpz, *rdatasetp, + self_name); + if ((*policyp == DNS_RPZ_POLICY_RECORD || + *policyp == DNS_RPZ_POLICY_WILDCNAME) && qtype != dns_rdatatype_cname && qtype != dns_rdatatype_any) - result = DNS_R_CNAME; + return (DNS_R_CNAME); } - break; + return (ISC_R_SUCCESS); case DNS_R_NXRRSET: - policy = DNS_RPZ_POLICY_NODATA; - break; + *policyp = DNS_RPZ_POLICY_NODATA; + return (result); case DNS_R_DNAME: /* * DNAME policy RRs have very few if any uses that are not - * better served with simple wildcards. Making the work would + * better served with simple wildcards. Making them work would * require complications to get the number of labels matched * in the name or the found name to the main DNS_R_DNAME case - * in query_find(). - */ - dns_rdataset_disassociate(*rdatasetp); - dns_db_detachnode(*dbp, nodep); - /* - * Fall through to treat it as a miss. + * in query_find(). The domain also does not appear in the + * summary database at the right level, so this happens only + * with a single policy zone when we have no summary database. + * Treat it as a miss. */ 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. - */ - (void)dns_db_rpz_enabled(*dbp, client->query.rpz_st); - dns_db_detach(dbp); - dns_zone_detach(zonep); - result = DNS_R_NXDOMAIN; - policy = DNS_RPZ_POLICY_MISS; - break; + return (DNS_R_NXDOMAIN); default: - dns_db_detach(dbp); - dns_zone_detach(zonep); - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, qnamef, + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, "", result); return (DNS_R_SERVFAIL); } +} - *policyp = policy; - return (result); +static void +rpz_save_p(dns_rpz_st_t *st, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + dns_rpz_policy_t policy, dns_name_t *p_name, dns_rpz_prefix_t prefix, + isc_result_t result, dns_zone_t **zonep, dns_db_t **dbp, + dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, + dns_dbversion_t *version) +{ + dns_rdataset_t *trdataset; + + rpz_match_clear(st); + st->m.rpz = rpz; + st->m.type = rpz_type; + st->m.policy = policy; + dns_name_copy(p_name, st->p_name, NULL); + st->m.prefix = prefix; + st->m.result = result; + st->m.zone = *zonep; + *zonep = NULL; + st->m.db = *dbp; + *dbp = NULL; + st->m.node = *nodep; + *nodep = NULL; + if (*rdatasetp != NULL && dns_rdataset_isassociated(*rdatasetp)) { + /* + * Save the replacement rdataset from the policy + * and make the previous replacement rdataset scratch. + */ + trdataset = st->m.rdataset; + st->m.rdataset = *rdatasetp; + *rdatasetp = trdataset; + st->m.ttl = ISC_MIN(st->m.rdataset->ttl, rpz->max_policy_ttl); + } else { + st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT, rpz->max_policy_ttl); + } + st->m.version = version; } /* - * Build and look for a QNAME or NSDNAME owner name in a response policy zone. + * Check this address in every eligible 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) +rpz_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr, + dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, dns_rdataset_t **p_rdatasetp) { + dns_rpz_zones_t *rpzs; 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_dbversion_t *version; - dns_dbnode_t *node; + dns_rpz_prefix_t prefix; + dns_rpz_num_t rpz_num; + dns_fixedname_t ip_namef, p_namef; + dns_name_t *ip_name, *p_name; + dns_zone_t *p_zone; + dns_db_t *p_db; + dns_dbversion_t *p_version; + dns_dbnode_t *p_node; dns_rpz_policy_t policy; - unsigned int labels; isc_result_t result; - st = client->query.rpz_st; - zone = NULL; - db = NULL; - node = NULL; + dns_fixedname_init(&ip_namef); + ip_name = dns_fixedname_name(&ip_namef); - for (rpz = ISC_LIST_HEAD(client->view->rpz_zones); - rpz != NULL; - rpz = ISC_LIST_NEXT(rpz, link)) { - if (!RECURSIONOK(client) && rpz->recursive_only) - continue; + p_zone = NULL; + p_db = NULL; + p_node = NULL; + + rpzs = client->view->rpzs; + st = client->query.rpz_st; + while (zbits != 0) { + rpz_num = dns_rpz_find_ip(rpzs, rpz_type, zbits, netaddr, + ip_name, &prefix); + if (rpz_num == DNS_RPZ_INVALID_NUM) + break; + zbits &= (DNS_RPZ_ZMASK(rpz_num) >> 1); /* - * Do not check policy zones that cannot replace a policy - * already known to match. + * Do not try applying policy zones that cannot replace a + * previously found policy zone. + * Stop looking if the next best choice cannot + * replace what we already have. */ + rpz = rpzs->zones[rpz_num]; if (st->m.policy != DNS_RPZ_POLICY_MISS) { if (st->m.rpz->num < rpz->num) break; if (st->m.rpz->num == rpz->num && - st->m.type < rpz_type) - continue; + (st->m.type < rpz_type || + st->m.prefix > prefix)) + break; } + /* - * Construct the policy's owner name. + * Get the policy for a prefix at least as long + * as the prefix of the entry we had before. */ - 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); + dns_fixedname_init(&p_namef); + p_name = dns_fixedname_name(&p_namef); + result = rpz_get_p_name(client, p_name, rpz, rpz_type, ip_name); + if (result != ISC_R_SUCCESS) + continue; + result = rpz_find_p(client, ip_name, qtype, + p_name, rpz, rpz_type, + &p_zone, &p_db, &p_version, &p_node, + p_rdatasetp, &policy); + switch (result) { + case DNS_R_NXDOMAIN: /* - * Trim the name until it is not too long. + * Continue after a policy record that is missing + * contrary to the summary data. The summary + * data can out of date during races with and among + * policy zone updates. */ - labels = dns_name_countlabels(prefix); - if (labels < 2) { - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, - rpz_type, suffix, - "concatentate() ", result); - return (ISC_R_SUCCESS); - } - if (labels+1 == dns_name_countlabels(qname)) { - rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, - rpz_type, suffix, - "concatentate() ", result); + continue; + case DNS_R_SERVFAIL: + rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); + st->m.policy = DNS_RPZ_POLICY_ERROR; + return (DNS_R_SERVFAIL); + default: + /* + * Forget this policy if it is not preferable + * to the previously found policy. + * If this policy is not good, then stop looking + * because none of the later policy zones would work. + * + * With more than one applicable policy, prefer + * the earliest configured policy, + * client-IP over QNAME over IP over NSDNAME over NSIP, + * the longest prefix + * the lexically smallest address. + * dns_rpz_find_ip() ensures st->m.rpz->num >= rpz->num. + * We can compare new and current p_name because + * both are of the same type and in the same zone. + * The tests above eliminate other reasons to + * reject this policy. If this policy can't work, + * then neither can later zones. + */ + if (st->m.policy != DNS_RPZ_POLICY_MISS && + rpz->num == st->m.rpz->num && + (st->m.type == rpz_type && + st->m.prefix == prefix && + 0 > dns_name_rdatacompare(st->p_name, p_name))) + break; + + /* + * Stop checking after saving an enabled hit in this + * policy zone. The radix tree in the policy zone + * ensures that we found the longest match. + */ + if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { + rpz_save_p(st, rpz, rpz_type, + policy, p_name, prefix, result, + &p_zone, &p_db, &p_node, + p_rdatasetp, p_version); + break; } - dns_name_split(prefix, labels - 1, NULL, prefix); + + /* + * Log DNS_RPZ_POLICY_DISABLED zones + * and try the next eligible policy zone. + */ + rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type, + p_zone, p_name); + } + } + + rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); + return (ISC_R_SUCCESS); +} + +/* + * Check the IP addresses in the A or AAAA rrsets for name against + * all eligible rpz_type (IP or NSIP) response policy rewrite rules. + */ +static isc_result_t +rpz_rewrite_ip_rrset(ns_client_t *client, + dns_name_t *name, dns_rdatatype_t qtype, + dns_rpz_type_t rpz_type, dns_rdatatype_t ip_type, + dns_db_t **ip_dbp, dns_dbversion_t *ip_version, + dns_rdataset_t **ip_rdatasetp, + dns_rdataset_t **p_rdatasetp, isc_boolean_t resuming) +{ + dns_rpz_zbits_t zbits; + isc_netaddr_t netaddr; + struct in_addr ina; + struct in6_addr in6a; + isc_result_t result; + + zbits = rpz_get_zbits(client, ip_type, rpz_type); + if (zbits == 0) + return (ISC_R_SUCCESS); + + /* + * Get the A or AAAA rdataset. + */ + result = rpz_rrset_find(client, name, ip_type, rpz_type, ip_dbp, + ip_version, ip_rdatasetp, resuming); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_GLUE: + case DNS_R_ZONECUT: + 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: + case ISC_R_NOTFOUND: + return (ISC_R_SUCCESS); + case DNS_R_DELEGATION: + case DNS_R_DUPLICATE: + case DNS_R_DROP: + return (result); + case DNS_R_CNAME: + case DNS_R_DNAME: + rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, name, rpz_type, + " NS address rewrite rrset", result); + return (ISC_R_SUCCESS); + default: + if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { + client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR; + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, + rpz_type, " NS address rewrite rrset", + result); + } + return (DNS_R_SERVFAIL); + } + + /* + * Check all of the IP addresses in the rdataset. + */ + for (result = dns_rdataset_first(*ip_rdatasetp); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(*ip_rdatasetp)) { + + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(*ip_rdatasetp, &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 = rpz_rewrite_ip(client, &netaddr, qtype, rpz_type, + zbits, p_rdatasetp); + if (result != ISC_R_SUCCESS) + return (result); + } + + return (ISC_R_SUCCESS); +} + +/* + * Look for IP addresses in A and AAAA rdatasets + * that trigger all eligible IP or NSIP policy rules. + */ +static isc_result_t +rpz_rewrite_ip_rrsets(ns_client_t *client, dns_name_t *name, + dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, + dns_rdataset_t **ip_rdatasetp, isc_boolean_t resuming) +{ + dns_rpz_st_t *st; + dns_dbversion_t *ip_version; + dns_db_t *ip_db; + dns_rdataset_t *p_rdataset; + isc_result_t result; + + st = client->query.rpz_st; + ip_version = NULL; + ip_db = NULL; + p_rdataset = NULL; + if ((st->state & DNS_RPZ_DONE_IPv4) == 0 && + (qtype == dns_rdatatype_a || + qtype == dns_rdatatype_any || + rpz_type == DNS_RPZ_TYPE_NSIP)) { + /* + * Rewrite based on an IPv4 address that will appear + * in the ANSWER section or if we are checking IP addresses. + */ + result = rpz_rewrite_ip_rrset(client, name, qtype, + rpz_type, dns_rdatatype_a, + &ip_db, ip_version, ip_rdatasetp, + &p_rdataset, resuming); + if (result == ISC_R_SUCCESS) + st->state |= DNS_RPZ_DONE_IPv4; + } else { + result = ISC_R_SUCCESS; + } + if (result == ISC_R_SUCCESS && + (qtype == dns_rdatatype_aaaa || + qtype == dns_rdatatype_any || + rpz_type == DNS_RPZ_TYPE_NSIP)) { + /* + * Rewrite based on IPv6 addresses that will appear + * in the ANSWER section or if we are checking IP addresses. + */ + result = rpz_rewrite_ip_rrset(client, name, qtype, + rpz_type, dns_rdatatype_aaaa, + &ip_db, ip_version, ip_rdatasetp, + &p_rdataset, resuming); + } + if (ip_db != NULL) + dns_db_detach(&ip_db); + query_putrdataset(client, &p_rdataset); + return (result); +} + +/* + * Try to rewrite a request for a qtype rdataset based on the trigger name + * trig_name and rpz_type (DNS_RPZ_TYPE_QNAME or DNS_RPZ_TYPE_NSDNAME). + * Record the results including the replacement rdataset if any + * in client->query.rpz_st. + * *rdatasetp is a scratch rdataset. + */ +static isc_result_t +rpz_rewrite_name(ns_client_t *client, dns_name_t *trig_name, + dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t allowed_zbits, dns_rdataset_t **rdatasetp) +{ + dns_rpz_zone_t *rpz; + dns_rpz_st_t *st; + dns_fixedname_t p_namef; + dns_name_t *p_name; + dns_rpz_zbits_t zbits; + dns_rpz_num_t rpz_num; + dns_zone_t *p_zone; + dns_db_t *p_db; + dns_dbversion_t *p_version; + dns_dbnode_t *p_node; + dns_rpz_policy_t policy; + isc_result_t result; + + zbits = rpz_get_zbits(client, qtype, rpz_type); + zbits &= allowed_zbits; + if (zbits == 0) + return (ISC_R_SUCCESS); + + /* + * If there is only one eligible policy zone, just check it. + * If more than one, then use the summary database to find + * the bit mask of policy zones with policies for this trigger name. + * x&-x is the least significant bit set in x + */ + if (zbits != (zbits & (~zbits + 1))) { + zbits = dns_rpz_find_name(client->view->rpzs, + rpz_type, zbits, trig_name); + if (zbits == 0) + return (ISC_R_SUCCESS); + } + + dns_fixedname_init(&p_namef); + p_name = dns_fixedname_name(&p_namef); + + p_zone = NULL; + p_db = NULL; + p_node = NULL; + + st = client->query.rpz_st; + + /* + * Check the trigger name in every policy zone that the summary data + * says has a hit for the trigger name. + * Most of the time there are no eligible zones and the summary data + * keeps us from getting this far. + * We check the most eligible zone first and so usually check only + * one policy zone. + */ + for (rpz_num = 0; + zbits != 0; + ++rpz_num, zbits >>= 1) { + if ((zbits & 1) == 0) { + INSIST(rpz_num <= client->view->rpzs->p.num_zones); + continue; } /* - * See if the policy record exists and get its policy. + * Do not check policy zones that cannot replace a previously + * found policy. */ - result = rpz_find(client, qtype, rpz_qname, qname, rpz, - rpz_type, &zone, &db, &version, &node, - rdatasetp, &policy); + rpz = client->view->rpzs->zones[rpz_num]; + if (st->m.policy != DNS_RPZ_POLICY_MISS) { + if (st->m.rpz->num < rpz->num) + break; + if (st->m.rpz->num == rpz->num && + st->m.type < rpz_type) + break; + } + + /* + * Get the next policy zone's record for this trigger name. + */ + result = rpz_get_p_name(client, p_name, rpz, rpz_type, + trig_name); + if (result != ISC_R_SUCCESS) + continue; + result = rpz_find_p(client, trig_name, qtype, p_name, + rpz, rpz_type, + &p_zone, &p_db, &p_version, &p_node, + rdatasetp, &policy); switch (result) { case DNS_R_NXDOMAIN: - break; + /* + * Continue after a missing policy record + * contrary to the summary data. The summary + * data can out of date during races with and among + * policy zone updates. + */ + continue; case DNS_R_SERVFAIL: - rpz_clean(&zone, &db, &node, rdatasetp); + rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); st->m.policy = DNS_RPZ_POLICY_ERROR; return (DNS_R_SERVFAIL); default: /* - * We are dealing with names here. * With more than one applicable policy, prefer * the earliest configured policy, - * QNAME over IP over NSDNAME over NSIP, + * client-IP over QNAME over IP over NSDNAME over NSIP, * and the smallest name. - * Because of the testing above, - * we known st->m.rpz->num >= rpz->num and either + * We known st->m.rpz->num >= rpz->num and either * st->m.rpz->num > rpz->num or st->m.type >= rpz_type */ if (st->m.policy != DNS_RPZ_POLICY_MISS && rpz->num == st->m.rpz->num && (st->m.type < rpz_type || (st->m.type == rpz_type && - 0 >= dns_name_compare(rpz_qname, st->qname)))) + 0 >= dns_name_compare(p_name, st->p_name)))) continue; #if 0 /* @@ -4505,11 +4806,12 @@ * names in TLDs that start with "rpz-" should * ICANN ever allow such TLDs. */ - labels = dns_name_countlabels(qname); + unsigned int labels; + labels = dns_name_countlabels(trig_name); if (labels >= 2) { dns_label_t label; - dns_name_getlabel(qname, labels-2, &label); + dns_name_getlabel(trig_name, labels-2, &label); if (label.length >= sizeof(DNS_RPZ_PREFIX)-1 && strncasecmp((const char *)label.base+1, DNS_RPZ_PREFIX, @@ -4517,46 +4819,29 @@ continue; } #endif + if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { + rpz_save_p(st, rpz, rpz_type, + policy, p_name, 0, result, + &p_zone, &p_db, &p_node, + rdatasetp, p_version); + /* + * After a hit, higher numbered policy zones + * are irrelevant + */ + rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); + return (ISC_R_SUCCESS); + } /* - * Merely log DNS_RPZ_POLICY_DISABLED hits. + * Log DNS_RPZ_POLICY_DISABLED zones + * and try the next eligible policy zone. */ - if (rpz->policy == DNS_RPZ_POLICY_DISABLED) { - rpz_log_rewrite(client, ISC_TRUE, policy, - rpz_type, zone, rpz_qname); - continue; - } - - rpz_match_clear(st); - 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 (*rdatasetp != NULL && - dns_rdataset_isassociated(*rdatasetp)) { - dns_rdataset_t *trdataset; - - trdataset = st->m.rdataset; - st->m.rdataset = *rdatasetp; - *rdatasetp = trdataset; - st->m.ttl = ISC_MIN(st->m.rdataset->ttl, - rpz->max_policy_ttl); - } else { - st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT, - rpz->max_policy_ttl); - } - st->m.node = node; - node = NULL; - st->m.db = db; - db = NULL; - st->m.version = version; - st->m.zone = zone; - zone = NULL; + rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type, + p_zone, p_name); + break; } } - rpz_clean(&zone, &db, &node, rdatasetp); + rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); return (ISC_R_SUCCESS); } @@ -4569,7 +4854,7 @@ st = client->query.rpz_st; if (str != NULL) - rpz_log_fail(client, level, DNS_RPZ_TYPE_NSIP, nsname, + rpz_log_fail(client, level, nsname, DNS_RPZ_TYPE_NSIP, str, result); if (st->r.ns_rdataset != NULL && dns_rdataset_isassociated(st->r.ns_rdataset)) @@ -4589,7 +4874,8 @@ dns_rdataset_t *rdataset; dns_fixedname_t nsnamef; dns_name_t *nsname; - isc_boolean_t ck_ip; + int qresult_type; + dns_rpz_zbits_t zbits; isc_result_t result; st = client->query.rpz_st; @@ -4604,10 +4890,10 @@ st->m.ttl = ~0; memset(&st->r, 0, sizeof(st->r)); memset(&st->q, 0, sizeof(st->q)); - dns_fixedname_init(&st->_qnamef); + dns_fixedname_init(&st->_p_namef); dns_fixedname_init(&st->_r_namef); dns_fixedname_init(&st->_fnamef); - st->qname = dns_fixedname_name(&st->_qnamef); + st->p_name = dns_fixedname_name(&st->_p_namef); st->r_name = dns_fixedname_name(&st->_r_namef); st->fname = dns_fixedname_name(&st->_fnamef); client->query.rpz_st = st; @@ -4620,7 +4906,7 @@ case ISC_R_SUCCESS: case DNS_R_GLUE: case DNS_R_ZONECUT: - ck_ip = ISC_TRUE; + qresult_type = 0; break; case DNS_R_EMPTYNAME: case DNS_R_NXRRSET: @@ -4630,73 +4916,155 @@ case DNS_R_NCACHENXRRSET: case DNS_R_CNAME: case DNS_R_DNAME: - ck_ip = ISC_FALSE; + qresult_type = 1; break; case DNS_R_DELEGATION: case ISC_R_NOTFOUND: - return (ISC_R_SUCCESS); + /* + * If recursion is on, do only tentative rewriting. + * If recursion is off, this the normal and only time we + * can rewrite. + */ + if (RECURSIONOK(client)) + qresult_type = 2; + else + qresult_type = 1; + break; case ISC_R_FAILURE: case ISC_R_TIMEDOUT: case DNS_R_BROKENCHAIN: - rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, DNS_RPZ_TYPE_QNAME, - client->query.qname, - "stop on qresult in rpz_rewrite() ", - qresult); + rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, client->query.qname, + DNS_RPZ_TYPE_QNAME, + " stop on qresult in rpz_rewrite()", qresult); return (ISC_R_SUCCESS); default: - rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, DNS_RPZ_TYPE_QNAME, - client->query.qname, - "stop on unrecognized qresult in rpz_rewrite() ", + rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, client->query.qname, + DNS_RPZ_TYPE_QNAME, + " stop on unrecognized qresult in rpz_rewrite()", qresult); return (ISC_R_SUCCESS); } rdataset = NULL; - if ((st->state & DNS_RPZ_DONE_QNAME) == 0) { + + if ((st->state & (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) != + (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) { + isc_netaddr_t netaddr; + dns_rpz_zbits_t allowed; + + if (qresult_type == 2) { + /* + * This request needs recursion that has not been done. + * Get bits for the policy zones that do not need + * to wait for the results of recursion. + */ + allowed = client->view->rpzs->have.qname_skip_recurse; + if (allowed == 0) + return (ISC_R_SUCCESS); + } else { + allowed = DNS_RPZ_ALL_ZBITS; + } + /* - * Check rules for the query name if this is the first time - * for the current qname, i.e. we've not been recursing. - * There is a first time for each name in a CNAME chain. + * Check once for triggers for the client IP address. */ - result = rpz_rewrite_name(client, qtype, client->query.qname, - DNS_RPZ_TYPE_QNAME, &rdataset); - if (result != ISC_R_SUCCESS) - goto cleanup; + if ((st->state & DNS_RPZ_DONE_CLIENT_IP) == 0) { + zbits = rpz_get_zbits(client, dns_rdatatype_none, + DNS_RPZ_TYPE_CLIENT_IP); + zbits &= allowed; + if (zbits != 0) { + isc_netaddr_fromsockaddr(&netaddr, + &client->peeraddr); + result = rpz_rewrite_ip(client, &netaddr, qtype, + DNS_RPZ_TYPE_CLIENT_IP, + zbits, &rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + } + + /* + * Check triggers for the query name if this is the first time + * for the current qname. + * There is a first time for each name in a CNAME chain + */ + if ((st->state & DNS_RPZ_DONE_QNAME) == 0) { + result = rpz_rewrite_name(client, client->query.qname, + qtype, DNS_RPZ_TYPE_QNAME, + allowed, &rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + + /* + * Check IPv4 addresses in A RRs next. + * Reset to the start of the NS names. + */ + st->r.label = dns_name_countlabels(client->query.qname); + st->state &= ~(DNS_RPZ_DONE_QNAME_IP | + DNS_RPZ_DONE_IPv4); - st->r.label = dns_name_countlabels(client->query.qname); + } - st->state &= ~(DNS_RPZ_DONE_QNAME_IP | DNS_RPZ_DONE_IPv4); - st->state |= DNS_RPZ_DONE_QNAME; + /* + * Quit if this was an attempt to find a qname or + * client-IP trigger before recursion. + * We will be back if no pre-recursion triggers hit. + * For example, consider 2 policy zones, both with qname and + * IP address triggers. If the qname misses the 1st zone, + * then we cannot know whether a hit for the qname in the + * 2nd zone matters until after recursing to get the A RRs and + * testing them in the first zone. + * Do not bother saving the work from this attempt, + * because recusion is so slow. + */ + if (qresult_type == 2) + goto cleanup; + + /* + * DNS_RPZ_DONE_QNAME but not DNS_RPZ_DONE_CLIENT_IP + * is reset at the end of dealing with each CNAME. + */ + st->state |= (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME); } /* - * Check known IP addresses for the query name. + * Check known IP addresses for the query name if the database + * lookup resulted in some addresses (qresult_type == 0) + * and if we have not already checked them. * Any recursion required for the query has already happened. * Do not check addresses that will not be in the ANSWER section. */ - if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 && - (st->state & DNS_RPZ_HAVE_IP) != 0 && ck_ip) { - result = rpz_rewrite_rrsets(client, DNS_RPZ_TYPE_IP, - client->query.qname, qtype, - &rdataset, resuming); + if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 && qresult_type == 0 && + rpz_get_zbits(client, qtype, DNS_RPZ_TYPE_IP) != 0) { + result = rpz_rewrite_ip_rrsets(client, + client->query.qname, qtype, + DNS_RPZ_TYPE_IP, + &rdataset, resuming); if (result != ISC_R_SUCCESS) goto cleanup; - st->state &= ~DNS_RPZ_DONE_IPv4; + /* + * We are finished checking the IP addresses for the qname. + * Start with IPv4 if we will check NS IP addesses. + */ st->state |= DNS_RPZ_DONE_QNAME_IP; + st->state &= ~DNS_RPZ_DONE_IPv4; } /* - * Stop looking for rules if there are none of the other kinds. + * Stop looking for rules if there are none of the other kinds + * that could override what we already have. */ - if ((st->state & (DNS_RPZ_HAVE_NSIPv4 | DNS_RPZ_HAVE_NSIPv6 | - DNS_RPZ_HAVE_NSDNAME)) == 0) { + if (rpz_get_zbits(client, dns_rdatatype_any, + DNS_RPZ_TYPE_NSDNAME) == 0 && + rpz_get_zbits(client, dns_rdatatype_any, + DNS_RPZ_TYPE_NSIP) == 0) { result = ISC_R_SUCCESS; goto cleanup; } dns_fixedname_init(&nsnamef); dns_name_clone(client->query.qname, dns_fixedname_name(&nsnamef)); - while (st->r.label > client->view->rpz_min_ns_labels) { + while (st->r.label > client->view->rpzs->p.min_ns_labels) { /* * Get NS rrset for each domain in the current qname. */ @@ -4710,8 +5078,8 @@ if (st->r.ns_rdataset == NULL || !dns_rdataset_isassociated(st->r.ns_rdataset)) { dns_db_t *db = NULL; - result = rpz_rrset_find(client, DNS_RPZ_TYPE_NSDNAME, - nsname, dns_rdatatype_ns, + result = rpz_rrset_find(client, nsname, dns_rdatatype_ns, + DNS_RPZ_TYPE_NSDNAME, &db, NULL, &st->r.ns_rdataset, resuming); if (db != NULL) @@ -4745,12 +5113,12 @@ case ISC_R_FAILURE: rpz_rewrite_ns_skip(client, nsname, result, DNS_RPZ_DEBUG_LEVEL3, - "NS db_find() "); + " NS db_find()"); continue; default: rpz_rewrite_ns_skip(client, nsname, result, DNS_RPZ_INFO_LEVEL, - "unrecognized NS db_find() "); + " unrecognized NS db_find()"); continue; } } @@ -4766,8 +5134,8 @@ dns_rdata_reset(&nsrdata); if (result != ISC_R_SUCCESS) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, - DNS_RPZ_TYPE_NSIP, nsname, - "rdata_tostruct() ", result); + nsname, DNS_RPZ_TYPE_NSIP, + " rdata_tostruct()", result); st->m.policy = DNS_RPZ_POLICY_ERROR; goto cleanup; } @@ -4783,11 +5151,11 @@ * Check this NS name if we did not handle it * during a previous recursion. */ - if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0 && - (st->state & DNS_RPZ_HAVE_NSDNAME) != 0) { - result = rpz_rewrite_name(client, qtype, - &ns.name, + if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0) { + result = rpz_rewrite_name(client, &ns.name, + qtype, DNS_RPZ_TYPE_NSDNAME, + DNS_RPZ_ALL_ZBITS, &rdataset); if (result != ISC_R_SUCCESS) { dns_rdata_freestruct(&ns); @@ -4798,9 +5166,9 @@ /* * Check all IP addresses for this NS name. */ - result = rpz_rewrite_rrsets(client, DNS_RPZ_TYPE_NSIP, - &ns.name, dns_rdatatype_any, - &rdataset, resuming); + result = rpz_rewrite_ip_rrsets(client, &ns.name, qtype, + DNS_RPZ_TYPE_NSIP, + &rdataset, resuming); dns_rdata_freestruct(&ns); if (result != ISC_R_SUCCESS) goto cleanup; @@ -4810,10 +5178,16 @@ } while (result == ISC_R_SUCCESS); dns_rdataset_disassociate(st->r.ns_rdataset); st->r.label--; + + if (rpz_get_zbits(client, dns_rdatatype_any, + DNS_RPZ_TYPE_NSDNAME) == 0 && + rpz_get_zbits(client, dns_rdatatype_any, + DNS_RPZ_TYPE_NSIP) == 0) + break; } /* - * Use the best, if any, hit. + * Use the best hit, if any. */ result = ISC_R_SUCCESS; @@ -4828,7 +5202,7 @@ if (st->m.policy == DNS_RPZ_POLICY_PASSTHRU && result != DNS_R_DELEGATION) rpz_log_rewrite(client, ISC_FALSE, st->m.policy, - st->m.type, st->m.zone, st->qname); + st->m.type, st->m.zone, st->p_name); rpz_match_clear(st); } if (st->m.policy == DNS_RPZ_POLICY_ERROR) { @@ -4847,19 +5221,25 @@ * by the client in DNSSEC or a lack of signatures. */ static isc_boolean_t -rpz_ck_dnssec(ns_client_t *client, isc_result_t result, +rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { dns_fixedname_t fixed; dns_name_t *found; dns_rdataset_t trdataset; dns_rdatatype_t type; + isc_result_t result; - if (client->view->rpz_break_dnssec) + if (client->view->rpzs->p.break_dnssec || !WANTDNSSEC(client)) return (ISC_TRUE); + /* - * sigrdataset == NULL if and only !WANTDNSSEC(client) + * We do not know if there are signatures if we have not recursed + * for them. */ + if (qresult == DNS_R_DELEGATION || qresult == ISC_R_NOTFOUND) + return (ISC_FALSE); + if (sigrdataset == NULL) return (ISC_TRUE); if (dns_rdataset_isassociated(sigrdataset)) @@ -4939,7 +5319,7 @@ if (result != ISC_R_SUCCESS) return (result); rpz_log_rewrite(client, ISC_FALSE, st->m.policy, - st->m.type, st->m.zone, st->qname); + st->m.type, st->m.zone, st->p_name); ns_client_qnamereplace(client, fname); /* * Turn off DNSSEC because the results of a @@ -5998,13 +6378,15 @@ } #endif /* USE_RRL */ - if (!ISC_LIST_EMPTY(client->view->rpz_zones) && - (RECURSIONOK(client) || !client->view->rpz_recursive_only) && + if (client->view->rpzs != NULL && + client->view->rpzs->p.num_zones != 0 && + (RECURSIONOK(client) || client->view->rpzs->p.no_rd_ok != 0) && rpz_ck_dnssec(client, result, rdataset, sigrdataset) && !RECURSING(client) && (client->query.rpz_st == NULL || (client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0) && - !dns_name_equal(client->query.qname, dns_rootname)) { + !dns_name_equal(client->query.qname, dns_rootname)) + { isc_result_t rresult; rresult = rpz_rewrite(client, qtype, result, resuming); @@ -6042,12 +6424,17 @@ rpz_st->state |= DNS_RPZ_REWRITTEN; if (rpz_st->m.policy != DNS_RPZ_POLICY_MISS && rpz_st->m.policy != DNS_RPZ_POLICY_PASSTHRU && + (rpz_st->m.policy != DNS_RPZ_POLICY_TCP_ONLY || + (client->attributes & NS_CLIENTATTR_TCP) == 0) && rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { - if (rpz_st->m.type == DNS_RPZ_TYPE_QNAME) { - result = dns_name_copy(client->query.qname, - fname, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - } + /* We got a hit and are going to answer with our + * fiction. Ensure that we answer with the name + * we looked up even if we were stopped short + * in recursion or for a deferral. + */ + rresult = dns_name_copy(client->query.qname, + fname, NULL); + RUNTIME_CHECK(rresult == ISC_R_SUCCESS); rpz_clean(&zone, &db, &node, NULL); if (rpz_st->m.rdataset != NULL) { query_putrdataset(client, &rdataset); @@ -6067,6 +6454,27 @@ rpz_st->m.zone = NULL; switch (rpz_st->m.policy) { + case DNS_RPZ_POLICY_TCP_ONLY: + client->message->flags |= DNS_MESSAGEFLAG_TC; + if (result == DNS_R_NXDOMAIN || + result == DNS_R_NCACHENXDOMAIN) + client->message->rcode = + dns_rcode_nxdomain; + else + result = ISC_R_SUCCESS; + rpz_log_rewrite(client, ISC_FALSE, + rpz_st->m.policy, + rpz_st->m.type, zone, + rpz_st->p_name); + goto cleanup; + case DNS_RPZ_POLICY_DROP: + result = ISC_R_SUCCESS; + QUERY_ERROR(DNS_R_DROP); + rpz_log_rewrite(client, ISC_FALSE, + rpz_st->m.policy, + rpz_st->m.type, zone, + rpz_st->p_name); + goto cleanup; case DNS_RPZ_POLICY_NXDOMAIN: result = DNS_R_NXDOMAIN; break; @@ -6079,8 +6487,8 @@ result != DNS_R_CNAME) { /* * We will add all of the rdatasets of - * the node by iterating, setting the - * TTL then. + * the node by iterating later, + * and set the TTL then. */ if (dns_rdataset_isassociated(rdataset)) dns_rdataset_disassociate(rdataset); @@ -6135,7 +6543,7 @@ rpz_st->q.is_zone = is_zone; is_zone = ISC_TRUE; rpz_log_rewrite(client, ISC_FALSE, rpz_st->m.policy, - rpz_st->m.type, zone, rpz_st->qname); + rpz_st->m.type, zone, rpz_st->p_name); } } diff -r -u bin/named/server.c-orig bin/named/server.c --- bin/named/server.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/named/server.c 2004-01-01 00:00:00.000000000 +0000 @@ -375,7 +375,8 @@ static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, - cfg_aclconfctx_t *aclconf, isc_boolean_t added); + cfg_aclconfctx_t *aclconf, isc_boolean_t added, + isc_boolean_t old_rpz_ok); static isc_result_t add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx); @@ -1556,17 +1557,24 @@ } static isc_result_t -configure_rpz(dns_view_t *view, const cfg_listelt_t *element, - isc_boolean_t recursive_only_def, dns_ttl_t ttl_def) +configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element, + isc_boolean_t recursive_only_def, dns_ttl_t ttl_def, + const dns_rpz_zone_t *old, isc_boolean_t *old_rpz_okp) { const cfg_obj_t *rpz_obj, *obj; const char *str; - dns_rpz_zone_t *old, *new; + dns_rpz_zone_t *new; isc_result_t result; + dns_rpz_num_t rpz_num; + + REQUIRE(old != NULL || !*old_rpz_okp); rpz_obj = cfg_listelt_value(element); - new = isc_mem_get(view->mctx, sizeof(*new)); + if (view->rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES) + return (ISC_R_NOMEMORY); + + new = isc_mem_get(view->rpzs->mctx, sizeof(*new)); if (new == NULL) { cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, "no memory for response policy zones"); @@ -1574,20 +1582,29 @@ } memset(new, 0, sizeof(*new)); + result = isc_refcount_init(&new->refs, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(view->rpzs->mctx, new, sizeof(*new)); + return (result); + } dns_name_init(&new->origin, NULL); + dns_name_init(&new->client_ip, NULL); + dns_name_init(&new->ip, NULL); dns_name_init(&new->nsdname, NULL); + dns_name_init(&new->nsip, NULL); dns_name_init(&new->passthru, NULL); + dns_name_init(&new->drop, NULL); + dns_name_init(&new->tcp_only, NULL); dns_name_init(&new->cname, NULL); - ISC_LIST_INITANDAPPEND(view->rpz_zones, new, link); + new->num = view->rpzs->p.num_zones++; + view->rpzs->zones[new->num] = new; obj = cfg_tuple_get(rpz_obj, "recursive-only"); - if (cfg_obj_isvoid(obj)) { - new->recursive_only = recursive_only_def; + if (cfg_obj_isvoid(obj) ? recursive_only_def : cfg_obj_asboolean(obj)) { + view->rpzs->p.no_rd_ok &= ~DNS_RPZ_ZBIT(new->num); } else { - new->recursive_only = cfg_obj_asboolean(obj); + view->rpzs->p.no_rd_ok |= DNS_RPZ_ZBIT(new->num); } - if (!new->recursive_only) - view->rpz_recursive_only = ISC_FALSE; obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); if (cfg_obj_isuint32(obj)) { @@ -1595,6 +1612,8 @@ } else { new->max_policy_ttl = ttl_def; } + if (*old_rpz_okp && new->max_policy_ttl != old->max_policy_ttl) + *old_rpz_okp = ISC_FALSE; str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "zone name")); result = configure_rpz_name(view, rpz_obj, &new->origin, str, "zone"); @@ -1605,25 +1624,50 @@ "invalid zone name '%s'", str); return (DNS_R_EMPTYLABEL); } - 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)) { + for (rpz_num = 0; rpz_num < view->rpzs->p.num_zones-1; ++rpz_num) { + if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, + &new->origin)) { cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, "duplicate '%s'", str); result = DNS_R_DUPLICATE; return (result); } } + if (*old_rpz_okp && !dns_name_equal(&old->origin, &new->origin)) + *old_rpz_okp = ISC_FALSE; + + result = configure_rpz_name2(view, rpz_obj, &new->client_ip, + DNS_RPZ_CLIENT_IP_ZONE, &new->origin); + if (result != ISC_R_SUCCESS) + return (result); + + result = configure_rpz_name2(view, rpz_obj, &new->ip, + DNS_RPZ_IP_ZONE, &new->origin); + if (result != ISC_R_SUCCESS) + return (result); result = configure_rpz_name2(view, rpz_obj, &new->nsdname, DNS_RPZ_NSDNAME_ZONE, &new->origin); if (result != ISC_R_SUCCESS) return (result); + result = configure_rpz_name2(view, rpz_obj, &new->nsip, + DNS_RPZ_NSIP_ZONE, &new->origin); + if (result != ISC_R_SUCCESS) + return (result); + result = configure_rpz_name(view, rpz_obj, &new->passthru, - DNS_RPZ_PASSTHRU_ZONE, "zone"); + DNS_RPZ_PASSTHRU_NAME, "name"); + if (result != ISC_R_SUCCESS) + return (result); + + result = configure_rpz_name(view, rpz_obj, &new->drop, + DNS_RPZ_DROP_NAME, "name"); + if (result != ISC_R_SUCCESS) + return (result); + + result = configure_rpz_name(view, rpz_obj, &new->tcp_only, + DNS_RPZ_TCP_ONLY_NAME, "name"); if (result != ISC_R_SUCCESS) return (result); @@ -1642,6 +1686,116 @@ return (result); } } + if (*old_rpz_okp && (new->policy != old->policy || + !dns_name_equal(&old->cname, &new->cname))) + *old_rpz_okp = ISC_FALSE; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +configure_rpz(dns_view_t *view, const cfg_obj_t *rpz_obj, + isc_boolean_t *old_rpz_okp) +{ + const cfg_listelt_t *zone_element; + const cfg_obj_t *sub_obj; + isc_boolean_t recursive_only_def; + dns_ttl_t ttl_def; + dns_rpz_zones_t *new; + const dns_rpz_zones_t *old; + dns_view_t *pview; + const dns_rpz_zone_t *old_zone; + isc_result_t result; + int i; + + *old_rpz_okp = ISC_FALSE; + + zone_element = cfg_list_first(cfg_tuple_get(rpz_obj, "zone list")); + if (zone_element == NULL) + return (ISC_R_SUCCESS); + + result = dns_rpz_new_zones(&view->rpzs, view->mctx); + if (result != ISC_R_SUCCESS) + return (result); + new = view->rpzs; + + sub_obj = cfg_tuple_get(rpz_obj, "recursive-only"); + if (!cfg_obj_isvoid(sub_obj) && + !cfg_obj_asboolean(sub_obj)) + recursive_only_def = ISC_FALSE; + else + recursive_only_def = ISC_TRUE; + + sub_obj = cfg_tuple_get(rpz_obj, "break-dnssec"); + if (!cfg_obj_isvoid(sub_obj) && + cfg_obj_asboolean(sub_obj)) + new->p.break_dnssec = ISC_TRUE; + else + new->p.break_dnssec = ISC_FALSE; + + sub_obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); + if (cfg_obj_isuint32(sub_obj)) + ttl_def = cfg_obj_asuint32(sub_obj); + else + ttl_def = DNS_RPZ_MAX_TTL_DEFAULT; + + sub_obj = cfg_tuple_get(rpz_obj, "min-ns-dots"); + if (cfg_obj_isuint32(sub_obj)) + new->p.min_ns_labels = cfg_obj_asuint32(sub_obj) + 1; + else + new->p.min_ns_labels = 2; + + sub_obj = cfg_tuple_get(rpz_obj, "qname-wait-recurse"); + if (cfg_obj_isvoid(sub_obj) || cfg_obj_asboolean(sub_obj)) + new->p.qname_wait_recurse = ISC_TRUE; + else + new->p.qname_wait_recurse = ISC_FALSE; + + pview = NULL; + result = dns_viewlist_find(&ns_g_server->viewlist, + view->name, view->rdclass, &pview); + if (result == ISC_R_SUCCESS) { + old = pview->rpzs; + } else { + old = NULL; + } + if (old == NULL) + *old_rpz_okp = ISC_FALSE; + else + *old_rpz_okp = ISC_TRUE; + + for (i = 0; + zone_element != NULL; + ++i, zone_element = cfg_list_next(zone_element)) { + if (*old_rpz_okp && i < old->p.num_zones) { + old_zone = old->zones[i]; + } else { + *old_rpz_okp = ISC_FALSE; + old_zone = NULL; + } + result = configure_rpz_zone(view, zone_element, + recursive_only_def, ttl_def, + old_zone, old_rpz_okp); + if (result != ISC_R_SUCCESS) { + if (pview != NULL) + dns_view_detach(&pview); + return (result); + } + } + + /* + * If this is a reloading and the parameters and list of policy + * zones are unchanged, then use the same policy data. + * Data for individual zones that must be reloaded will be merged. + */ + if (old != NULL && memcmp(&old->p, &new->p, sizeof(new->p)) != 0) + *old_rpz_okp = ISC_FALSE; + if (*old_rpz_okp) { + dns_rpz_detach_rpzs(&view->rpzs); + dns_rpz_attach_rpzs(pview->rpzs, &view->rpzs); + } + if (pview != NULL) + dns_view_detach(&pview); return (ISC_R_SUCCESS); } @@ -2109,7 +2263,7 @@ dns_acl_t *clients = NULL, *mapped = NULL, *excluded = NULL; unsigned int query_timeout, ndisp; struct cfg_context *nzctx; - dns_rpz_zone_t *rpz; + isc_boolean_t old_rpz_ok = ISC_FALSE; REQUIRE(DNS_VIEW_VALID(view)); @@ -2207,44 +2361,7 @@ obj = NULL; if (view->rdclass == dns_rdataclass_in && need_hints && ns_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS) { - const cfg_obj_t *rpz_obj; - isc_boolean_t recursive_only_def; - dns_ttl_t ttl_def; - - rpz_obj = cfg_tuple_get(obj, "recursive-only"); - if (!cfg_obj_isvoid(rpz_obj) && - !cfg_obj_asboolean(rpz_obj)) - recursive_only_def = ISC_FALSE; - else - recursive_only_def = ISC_TRUE; - - rpz_obj = cfg_tuple_get(obj, "break-dnssec"); - if (!cfg_obj_isvoid(rpz_obj) && - cfg_obj_asboolean(rpz_obj)) - view->rpz_break_dnssec = ISC_TRUE; - else - view->rpz_break_dnssec = ISC_FALSE; - - rpz_obj = cfg_tuple_get(obj, "max-policy-ttl"); - if (cfg_obj_isuint32(rpz_obj)) - ttl_def = cfg_obj_asuint32(rpz_obj); - else - ttl_def = DNS_RPZ_MAX_TTL_DEFAULT; - - rpz_obj = cfg_tuple_get(obj, "min-ns-dots"); - if (cfg_obj_isuint32(rpz_obj)) - view->rpz_min_ns_labels = cfg_obj_asuint32(rpz_obj) + 1; - else - view->rpz_min_ns_labels = 2; - - element = cfg_list_first(cfg_tuple_get(obj, "zone list")); - while (element != NULL) { - result = configure_rpz(view, element, - recursive_only_def, ttl_def); - if (result != ISC_R_SUCCESS) - goto cleanup; - element = cfg_list_next(element); - } + CHECK(configure_rpz(view, obj, &old_rpz_ok)); } /* @@ -2265,22 +2382,29 @@ { const cfg_obj_t *zconfig = cfg_listelt_value(element); CHECK(configure_zone(config, zconfig, vconfig, mctx, view, - actx, ISC_FALSE)); + actx, ISC_FALSE, old_rpz_ok)); } - for (rpz = ISC_LIST_HEAD(view->rpz_zones); - rpz != NULL; - rpz = ISC_LIST_NEXT(rpz, link)) - { - if (!rpz->defined) { - char namebuf[DNS_NAME_FORMATSIZE]; + /* + * Check that a master or slave zone was found for each + * zone named in the response policy statement. + */ + if (view->rpzs != NULL) { + dns_rpz_num_t n; - dns_name_format(&rpz->origin, namebuf, sizeof(namebuf)); - cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, - "'%s' is not a master or slave zone", - namebuf); - result = ISC_R_NOTFOUND; - goto cleanup; + for (n = 0; n < view->rpzs->p.num_zones; ++n) + { + if ((view->rpzs->defined & DNS_RPZ_ZBIT(n)) == 0) { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(&view->rpzs->zones[n]->origin, + namebuf, sizeof(namebuf)); + cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "'%s' is not a master or slave zone", + namebuf); + result = ISC_R_NOTFOUND; + goto cleanup; + } } } @@ -2306,7 +2430,7 @@ const cfg_obj_t *zconfig = cfg_listelt_value(element); CHECK(configure_zone(config, zconfig, vconfig, mctx, view, actx, - ISC_TRUE)); + ISC_TRUE, ISC_FALSE)); } } @@ -3750,7 +3874,8 @@ static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, - cfg_aclconfctx_t *aclconf, isc_boolean_t added) + cfg_aclconfctx_t *aclconf, isc_boolean_t added, + isc_boolean_t old_rpz_ok) { dns_view_t *pview = NULL; /* Production view */ dns_zone_t *zone = NULL; /* New or reused zone */ @@ -3771,8 +3896,7 @@ const char *zname; dns_rdataclass_t zclass; const char *ztypestr; - isc_boolean_t is_rpz; - dns_rpz_zone_t *rpz; + dns_rpz_num_t rpz_num; options = NULL; (void)cfg_map_get(config, "options", &options); @@ -3934,18 +4058,15 @@ INSIST(dupzone == NULL); /* - * Note whether this is a response policy zone. + * Note whether this is a response policy zone and which one if so. */ - is_rpz = ISC_FALSE; - for (rpz = ISC_LIST_HEAD(view->rpz_zones); - rpz != NULL; - rpz = ISC_LIST_NEXT(rpz, link)) - { - if (dns_name_equal(&rpz->origin, origin)) { - is_rpz = ISC_TRUE; - rpz->defined = ISC_TRUE; + for (rpz_num = 0; ; ++rpz_num) { + if (view->rpzs == NULL || rpz_num >= view->rpzs->p.num_zones) { + rpz_num = DNS_RPZ_INVALID_NUM; break; } + if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, origin)) + break; } /* @@ -3956,7 +4077,9 @@ * - The zone is compatible with the config * options (e.g., an existing master zone cannot * be reused if the options specify a slave zone) - * - The zone was and is or was not and is not a policy zone + * - The zone was not and is still not a response policy zone + * or the zone is a policy zone with an unchanged number + * and we are using the old policy zone summary data. */ result = dns_viewlist_find(&ns_g_server->viewlist, view->name, view->rdclass, &pview); @@ -3970,7 +4093,8 @@ if (zone != NULL && !ns_zone_reusable(zone, zconfig)) dns_zone_detach(&zone); - if (zone != NULL && is_rpz != dns_zone_get_rpz(zone)) + if (zone != NULL && (rpz_num != dns_zone_get_rpz_num(zone) || + (rpz_num != DNS_RPZ_INVALID_NUM && !old_rpz_ok))) dns_zone_detach(&zone); if (zone != NULL) { @@ -3995,8 +4119,8 @@ dns_zone_setstats(zone, ns_g_server->zonestats); } - if (is_rpz) { - result = dns_zone_rpz_enable(zone); + if (rpz_num != DNS_RPZ_INVALID_NUM) { + result = dns_zone_rpz_enable(zone, view->rpzs, rpz_num); if (result != ISC_R_SUCCESS) { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, @@ -8286,7 +8410,8 @@ RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_view_thaw(view); result = configure_zone(cfg->config, parms, vconfig, - server->mctx, view, cfg->actx, ISC_FALSE); + server->mctx, view, cfg->actx, ISC_FALSE, + ISC_FALSE); dns_view_freeze(view); isc_task_endexclusive(server->task); if (result != ISC_R_SUCCESS) diff -r -u bin/tests/system/rpz/Makefile-orig bin/tests/system/rpz/Makefile --- bin/tests/system/rpz/Makefile-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/Makefile 2004-01-01 00:00:00.000000000 +0000 @@ -0,0 +1,478 @@ +# Copyright (C) 2011-2013 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$ + + +srcdir = . + +top_srcdir = ../../../.. + +VERSION=9.10.0pre-alpha + +# Copyright (C) 2004, 2005, 2007, 2012 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 1999-2001 Internet Software Consortium. +# +# 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: includes.in,v 1.21 2007/06/19 23:47:24 tbox Exp $ + +# Search for machine-generated header files in the build tree, +# and for normal headers in the source tree (${top_srcdir}). +# We only need to look in OS-specific subdirectories for the +# latter case, because there are no machine-generated OS-specific +# headers. + +ISC_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/isc/include \ + -I${top_srcdir}/lib/isc \ + -I${top_srcdir}/lib/isc/include \ + -I${top_srcdir}/lib/isc/unix/include \ + -I${top_srcdir}/lib/isc/pthreads/include \ + -I${top_srcdir}/lib/isc/x86_32/include + +ISCCC_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/isccc/include \ + -I${top_srcdir}/lib/isccc/include + +ISCCFG_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/isccfg/include \ + -I${top_srcdir}/lib/isccfg/include + +DNS_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/dns/include \ + -I${top_srcdir}/lib/dns/include + +LWRES_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/lwres/include \ + -I${top_srcdir}/lib/lwres/unix/include \ + -I${top_srcdir}/lib/lwres/include + +BIND9_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/bind9/include \ + -I${top_srcdir}/lib/bind9/include + +TEST_INCLUDES = \ + -I${top_srcdir}/lib/tests/include + +CINCLUDES = + +CDEFINES = +CWARNINGS = + +DNSLIBS = +ISCLIBS = . + +DNSDEPLIBS = +ISCDEPLIBS = + +DEPLIBS = + +LIBS = -L/usr/local/lib -lxml2 -lz -L/usr/local/lib -liconv -lm + +TARGETS = rpz + +RPZOBJS = rpz.o + +SRCS = rpz.c + +# Copyright (C) 2004-2009, 2011-2013 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 1998-2003 Internet Software Consortium. +# +# 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$ + +### +### Common Makefile rules for BIND 9. +### + +### +### Paths +### +### Note: paths that vary by Makefile MUST NOT be listed +### here, or they won't get expanded correctly. + +prefix = /usr +exec_prefix = ${prefix} +bindir = ${exec_prefix}/bin +sbindir = ${exec_prefix}/sbin +includedir = ${prefix}/include +libdir = ${exec_prefix}/lib +sysconfdir = /etc/namedb +localstatedir = ${prefix}/var +mandir = ${datarootdir}/man +datarootdir = ${prefix}/share + +DESTDIR = + + + +top_builddir = /usr/home/vjs/isc/work/rpz3 + +### +### All +### +### Makefile may define: +### TARGETS + +all: subdirs ${TARGETS} testdirs + +### +### Subdirectories +### +### Makefile may define: +### SUBDIRS + +ALL_SUBDIRS = ${SUBDIRS} nulldir +ALL_TESTDIRS = ${TESTDIRS} nulldir + +# +# We use a single-colon rule so that additional dependencies of +# subdirectories can be specified after the inclusion of this file. +# The "depend" and "testdirs" targets are treated the same way. +# +subdirs: + @for i in ${ALL_SUBDIRS}; do \ + if [ "$$i" != "nulldir" -a -d $$i ]; then \ + echo "making all in `pwd`/$$i"; \ + (cd $$i; ${MAKE} ${MAKEDEFS} DESTDIR="${DESTDIR}" all) || exit 1; \ + fi; \ + done + +# +# Tests are built after the targets instead of before +# +testdirs: + @for i in ${ALL_TESTDIRS}; do \ + if [ "$$i" != "nulldir" -a -d $$i ]; then \ + echo "making all in `pwd`/$$i"; \ + (cd $$i; ${MAKE} ${MAKEDEFS} DESTDIR="${DESTDIR}" all) || exit 1; \ + fi; \ + done + +install:: all + +install clean distclean maintainer-clean doc docclean man manclean:: + @for i in ${ALL_SUBDIRS} ${ALL_TESTDIRS}; do \ + if [ "$$i" != "nulldir" -a -d $$i ]; then \ + echo "making $@ in `pwd`/$$i"; \ + (cd $$i; ${MAKE} ${MAKEDEFS} DESTDIR="${DESTDIR}" $@) || exit 1; \ + fi; \ + done + +### +### C Programs +### +### Makefile must define +### CC +### Makefile may define +### CFLAGS +### LDFLAGS +### CINCLUDES +### CDEFINES +### CWARNINGS +### User may define externally +### EXT_CFLAGS + +CC = gcc -pthread +CFLAGS = -g -I/usr/local/include/libxml2 -I/usr/local/include +LDFLAGS = +STD_CINCLUDES = +STD_CDEFINES = -D_THREAD_SAFE +STD_CWARNINGS = -W -Wall -Wmissing-prototypes -Wcast-qual -Wwrite-strings -Wformat -Wpointer-arith -fno-strict-aliasing + +BUILD_CC = gcc -pthread +BUILD_CFLAGS = -g -I/usr/local/include/libxml2 -I/usr/local/include +BUILD_CPPFLAGS = +BUILD_LDFLAGS = +BUILD_LIBS = -L/usr/local/lib -lxml2 -lz -L/usr/local/lib -liconv -lm + +.SUFFIXES: +.SUFFIXES: .c .o + +ALWAYS_INCLUDES = -I${top_builddir} +ALWAYS_DEFINES = -D_REENTRANT +ALWAYS_WARNINGS = + +ALL_CPPFLAGS = \ + ${ALWAYS_INCLUDES} ${CINCLUDES} ${STD_CINCLUDES} \ + ${ALWAYS_DEFINES} ${CDEFINES} ${STD_CDEFINES} + +ALL_CFLAGS = ${EXT_CFLAGS} ${ALL_CPPFLAGS} ${CFLAGS} \ + ${ALWAYS_WARNINGS} ${STD_CWARNINGS} ${CWARNINGS} + +.c.o: + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c $< + +SHELL = /bin/sh +LIBTOOL = +LIBTOOL_MODE_COMPILE = ${LIBTOOL} +LIBTOOL_MODE_INSTALL = ${LIBTOOL} +LIBTOOL_MODE_LINK = ${LIBTOOL} +PURIFY = + +MKDEP = ${SHELL} ${top_builddir}/make/mkdep + +### +### This is a template compound command to build an executable binary with +### an internal symbol table. +### This process is tricky. We first link all objects including a tentative +### empty symbol table, then get a tentative list of symbols from the resulting +### binary ($@tmp0). Next, we re-link all objects, but this time with the +### symbol table just created ($tmp@1). The set of symbols should be the same, +### but the corresponding addresses would be changed due to the difference on +### the size of symbol tables. So we create the symbol table and re-create the +### objects once again. Finally, we check the symbol table embedded in the +### final binaryis consistent with the binary itself; otherwise the process is +### terminated. +### +### To minimize the overhead of creating symbol tables, the autoconf switch +### --enable-symtable takes an argument so that the symbol table can be created +### on a per application basis: unless the argument is set to "all", the symbol +### table is created only when a shell (environment) variable "MAKE_SYMTABLE" is +### set to a non-null value in the rule to build the executable binary. +### +### Each Makefile.in that uses this macro is expected to define "LIBS" and +### "NOSYMLIBS"; the former includes libisc with an empty symbol table, and +### the latter includes libisc without the definition of a symbol table. +### The rule to make the executable binary will look like this +### binary: ${OBJS} +### #export MAKE_SYMTABLE="yes"; \ <- enable if symtable is always needed +### export BASEOBJS="${OBJS}"; \ +### ${FINALBUILDCMD} +### +### Normally, ${LIBS} includes all necessary libraries to build the binary; +### there are some exceptions however, where the rule lists some of the +### necessary libraries explicitly in addition to (or instead of) ${LIBS}, +### like this: +### binary: ${OBJS} +### cc -o $@ ${OBJS} ${OTHERLIB1} ${OTHERLIB2} ${lIBS} +### in order to modify such a rule to use this compound command, a separate +### variable "LIBS0" should be deinfed for the explicitly listed libraries, +### while making sure ${LIBS} still includes libisc. So the above rule would +### be modified as follows: +### binary: ${OBJS} +### export BASEOBJS="${OBJS}"; \ +### export LIBS0="${OTHERLIB1} ${OTHERLIB2}"; \ +### ${FINALBUILDCMD} +### See bin/check/Makefile.in for a complete example of the use of LIBS0. +### +FINALBUILDCMD = if [ X"${MKSYMTBL_PROGRAM}" = X -o X"$${MAKE_SYMTABLE:-${ALWAYS_MAKE_SYMTABLE}}" = X ] ; then \ + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} ${LDFLAGS} \ + -o $@ $${BASEOBJS} $${LIBS0} ${LIBS}; \ + else \ + rm -f $@tmp0; \ + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} ${LDFLAGS} \ + -o $@tmp0 $${BASEOBJS} $${LIBS0} ${LIBS} || exit 1; \ + rm -f $@-symtbl.c $@-symtbl.o; \ + ${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \ + -o $@-symtbl.c $@tmp0 || exit 1; \ + $(MAKE) $@-symtbl.o || exit 1; \ + rm -f $@tmp1; \ + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} ${LDFLAGS} \ + -o $@tmp1 $${BASEOBJS} $@-symtbl.o $${LIBS0} ${NOSYMLIBS} || exit 1; \ + rm -f $@-symtbl.c $@-symtbl.o; \ + ${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \ + -o $@-symtbl.c $@tmp1 || exit 1; \ + $(MAKE) $@-symtbl.o || exit 1; \ + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} ${LDFLAGS} \ + -o $@tmp2 $${BASEOBJS} $@-symtbl.o $${LIBS0} ${NOSYMLIBS}; \ + ${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \ + -o $@-symtbl2.c $@tmp2; \ + count=0; \ + until diff $@-symtbl.c $@-symtbl2.c > /dev/null ; \ + do \ + count=`expr $$count + 1` ; \ + test $$count = 42 && exit 1 ; \ + rm -f $@-symtbl.c $@-symtbl.o; \ + ${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \ + -o $@-symtbl.c $@tmp2 || exit 1; \ + $(MAKE) $@-symtbl.o || exit 1; \ + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} \ + ${LDFLAGS} -o $@tmp2 $${BASEOBJS} $@-symtbl.o \ + $${LIBS0} ${NOSYMLIBS}; \ + ${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \ + -o $@-symtbl2.c $@tmp2; \ + done ; \ + mv $@tmp2 $@; \ + rm -f $@tmp0 $@tmp1 $@tmp2 $@-symtbl2.c; \ + fi + +cleandir: distclean +superclean: maintainer-clean + +clean distclean maintainer-clean:: + rm -f *.o *.o *.lo *.la core *.core *-symtbl.c *tmp0 *tmp1 *tmp2 + rm -rf .depend .libs + +distclean maintainer-clean:: + rm -f Makefile + +depend: + @for i in ${ALL_SUBDIRS}; do \ + if [ "$$i" != "nulldir" -a -d $$i ]; then \ + echo "making depend in `pwd`/$$i"; \ + (cd $$i; ${MAKE} ${MAKEDEFS} DESTDIR="${DESTDIR}" $@) || exit 1; \ + fi; \ + done + @if [ X"${srcdir}" != X. ] ; then \ + if [ X"${SRCS}" != X -a X"${PSRCS}" != X ] ; then \ + echo ${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \ + ${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \ + echo ${MKDEP} -vpath ${srcdir} -ap ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \ + ${MKDEP} -vpath ${srcdir} -ap ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \ + ${DEPENDEXTRA} \ + elif [ X"${SRCS}" != X ] ; then \ + echo ${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \ + ${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \ + ${DEPENDEXTRA} \ + elif [ X"${PSRCS}" != X ] ; then \ + echo ${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \ + ${MKDEP} -vpath ${srcdir} -p ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \ + ${DEPENDEXTRA} \ + fi \ + else \ + if [ X"${SRCS}" != X -a X"${PSRCS}" != X ] ; then \ + echo ${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \ + ${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \ + echo ${MKDEP} -ap ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \ + ${MKDEP} -ap ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \ + ${DEPENDEXTRA} \ + elif [ X"${SRCS}" != X ] ; then \ + echo ${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \ + ${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \ + ${DEPENDEXTRA} \ + elif [ X"${PSRCS}" != X ] ; then \ + echo ${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \ + ${MKDEP} -p ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \ + ${DEPENDEXTRA} \ + fi \ + fi + +FORCE: + +### +### Libraries +### + +AR = /usr/local/bin/ar +ARFLAGS = cruv +RANLIB = ranlib + +### +### Installation +### + +INSTALL = /usr/bin/install -c +INSTALL_PROGRAM = ${INSTALL} +LINK_PROGRAM = ln -s +INSTALL_SCRIPT = ${INSTALL} +INSTALL_DATA = ${INSTALL} -m 644 + +### +### Programs used when generating documentation. It's ok for these +### not to exist when not generating documentation. +### + +XSLTPROC = xsltproc --novalid --xinclude --nonet +PERL = /usr/local/bin/perl5 +LATEX = latex +PDFLATEX = pdflatex +W3M = w3m + +### +### Script language program used to create internal symbol tables +### +MKSYMTBL_PROGRAM = /usr/local/bin/perl5 + +### +### Switch to create internal symbol table selectively +### +ALWAYS_MAKE_SYMTABLE = + +### +### DocBook -> HTML +### DocBook -> man page +### + +.SUFFIXES: .docbook .html .1 .2 .3 .4 .5 .6 .7 .8 + +.docbook.html: + ${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-docbook-html.xsl $< + +.docbook.1: + ${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $< + +.docbook.2: + ${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $< + +.docbook.3: + ${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $< + +.docbook.4: + ${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $< + +.docbook.5: + ${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $< + +.docbook.6: + ${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $< + +.docbook.7: + ${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $< + +.docbook.8: + ${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $< + +### +### Python executable +### +.SUFFIXES: .py +.py: + cp -f $< $@ + chmod +x $@ + + +all: rpz + +rpz: ${RPZOBJS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ ${RPZOBJS} ${LIBS} + +clean distclean:: + rm -f ${TARGETS} + +# DO NOT DELETE THIS LINE -- mkdep uses it. +# DO NOT PUT ANYTHING AFTER THIS LINE, IT WILL GO AWAY. + +rpz.o: rpz.c /usr/home/vjs/isc/work/rpz3/config.h /usr/include/stdlib.h \ + /usr/include/sys/cdefs.h /usr/include/sys/_null.h \ + /usr/include/sys/_types.h /usr/include/machine/_types.h \ + /usr/include/stdio.h /usr/include/string.h /usr/include/strings.h + +# IF YOU PUT ANYTHING HERE IT WILL GO AWAY diff -r -u bin/tests/system/rpz/clean.sh-orig bin/tests/system/rpz/clean.sh --- bin/tests/system/rpz/clean.sh-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/clean.sh 2004-01-01 00:00:00.000000000 +0000 @@ -19,7 +19,7 @@ # Clean up after rpz tests. rm -f proto.* dsset-* random.data trusted.conf dig.out* nsupdate.tmp ns*/*tmp -rm -f ns*/*.key ns*/*.private ns2/tld2s.db ns2/bl.tld2.db +rm -f ns*/*.key ns*/*.private ns2/tld2s.db rm -f ns3/bl*.db ns*/*switch ns5/requests ns5/example.db ns5/bl.db ns5/*.perf rm -f */named.memstats */named.run */named.stats */session.key rm -f */*.jnl */*.core */*.pid diff -r -u bin/tests/system/rpz/ns1/root.db-orig bin/tests/system/rpz/ns1/root.db --- bin/tests/system/rpz/ns1/root.db-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/ns1/root.db 2004-01-01 00:00:00.000000000 +0000 @@ -38,3 +38,6 @@ ; performance test tld5. NS ns.tld5. ns.tld5. A 10.53.0.5 + +; generate SERVFAIL +servfail NS ns.tld2. diff -r -u bin/tests/system/rpz/ns2/bl.tld2.db-orig bin/tests/system/rpz/ns2/bl.tld2.db --- bin/tests/system/rpz/ns2/bl.tld2.db-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/ns2/bl.tld2.db 2004-01-01 00:00:00.000000000 +0000 @@ -0,0 +1,27 @@ +; Copyright (C) 2013 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$ + + + +; master for slave RPZ zone + +$TTL 3600 +@ SOA rpz.tld2. hostmaster.ns.tld2. ( 1 3600 1200 604800 60 ) + NS ns +ns A 10.53.0.2 + A 10.53.0.3 + +32.1.7.168.192.rpz-ip CNAME . diff -r -u bin/tests/system/rpz/ns2/named.conf-orig bin/tests/system/rpz/ns2/named.conf --- bin/tests/system/rpz/ns2/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/ns2/named.conf 2004-01-01 00:00:00.000000000 +0000 @@ -32,14 +32,6 @@ notify no; }; -key rndc_key { - secret "1234abcd8765"; - algorithm hmac-sha256; -}; -controls { - inet 10.53.0.2 port 9953 allow { any; } keys { rndc_key; }; -}; - include "../trusted.conf"; zone "." { type hint; file "hints"; }; @@ -53,4 +45,4 @@ zone "tld2s." {type master; file "tld2s.db";}; -zone "bl.tld2." {type master; file "bl.tld2.db"; notify yes; notify-delay 1;}; +zone "bl.tld2." {type master; file "bl.tld2.db";}; diff -r -u bin/tests/system/rpz/ns2/tld2.db-orig bin/tests/system/rpz/ns2/tld2.db --- bin/tests/system/rpz/ns2/tld2.db-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/ns2/tld2.db 2004-01-01 00:00:00.000000000 +0000 @@ -111,6 +111,9 @@ A 192.168.5.2 TXT "a5-1-2 tld2 text" +a5-2 A 192.168.5.2 + TXT "a5-2 tld2 text" + a5-3 A 192.168.5.3 TXT "a5-3 tld2 text" diff -r -u bin/tests/system/rpz/ns3/base.db-orig bin/tests/system/rpz/ns3/base.db --- bin/tests/system/rpz/ns3/base.db-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/ns3/base.db 2004-01-01 00:00:00.000000000 +0000 @@ -21,30 +21,7 @@ ; Its contents are also changed with nsupdate -$TTL 120 +$TTL 300 @ SOA blx. hostmaster.ns.blx. ( 1 3600 1200 604800 60 ) - NS ns -ns A 10.53.0.3 + NS ns.tld3. -; 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 . - - -; regression testing for some old crashes -redirect A 127.0.0.1 -*.redirect A 127.0.0.1 -*.credirect CNAME google.com. - - -; names in the RPZ TLDs that some say should not be rewritten. -; This is not a bug, because any data leaked by writing 24.4.3.2.10.rpz-ip -; (or whatever) is available by publishing "foo A 10.2.3.4" and then -; resolving foo. -32.3.2.1.127.rpz-ip CNAME walled.invalid. diff -r -u bin/tests/system/rpz/ns3/named.conf-orig bin/tests/system/rpz/ns3/named.conf --- bin/tests/system/rpz/ns3/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/ns3/named.conf 2004-01-01 00:00:00.000000000 +0000 @@ -46,20 +46,24 @@ zone "bl-cname" policy cname txt-only.tld2.; zone "bl-wildcname" policy cname *.tld4.; zone "bl-garden" policy cname a12.tld2.; + zone "bl-drop" policy drop; + zone "bl-tcp-only" policy tcp-only; zone "bl.tld2"; - } min-ns-dots 0; + } + min-ns-dots 0 + qname-wait-recurse yes + ; }; key rndc_key { secret "1234abcd8765"; - algorithm hmac-md5; + algorithm hmac-sha256; }; controls { inet 10.53.0.3 port 9953 allow { any; } keys { rndc_key; }; }; -// include "../trusted.conf"; zone "." { type hint; file "hints"; }; zone "bl." {type master; file "bl.db"; @@ -84,9 +88,13 @@ allow-update {any;};}; zone "bl-garden." {type master; file "bl-garden.db"; allow-update {any;};}; +zone "bl-drop." {type master; file "bl-drop.db"; + allow-update {any;};}; +zone "bl-tcp-only." {type master; file "bl-tcp-only.db"; + allow-update {any;};}; zone "bl.tld2." {type slave; file "bl.tld2.db"; masters {10.53.0.2;}; - request-ixfr no; masterfile-format text;}; + masterfile-format text;}; zone "crash1.tld2" {type master; file "crash1";}; zone "crash2.tld3." {type master; file "crash2";}; diff -r -u bin/tests/system/rpz/ns5/named.args-orig bin/tests/system/rpz/ns5/named.args --- bin/tests/system/rpz/ns5/named.args-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/ns5/named.args 2004-01-01 00:00:00.000000000 +0000 @@ -1,3 +1,3 @@ # run the performace test close to real life --c named.conf -g +-c named.conf -gd3 diff -r -u bin/tests/system/rpz/ns5/named.conf-orig bin/tests/system/rpz/ns5/named.conf --- bin/tests/system/rpz/ns5/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/ns5/named.conf 2004-01-01 00:00:00.000000000 +0000 @@ -40,7 +40,7 @@ key rndc_key { secret "1234abcd8765"; - algorithm hmac-md5; + algorithm hmac-sha256; }; controls { inet 10.53.0.5 port 9953 allow { any; } keys { rndc_key; }; @@ -56,3 +56,20 @@ zone "bl0." {type master; file "bl.db"; }; zone "bl1." {type master; file "bl.db"; }; zone "bl2." {type master; file "bl.db"; }; +zone "bl3." {type master; file "bl.db"; }; +zone "bl4." {type master; file "bl.db"; }; +zone "bl5." {type master; file "bl.db"; }; +zone "bl6." {type master; file "bl.db"; }; +zone "bl7." {type master; file "bl.db"; }; +zone "bl8." {type master; file "bl.db"; }; +zone "bl9." {type master; file "bl.db"; }; +zone "bl10." {type master; file "bl.db"; }; +zone "bl11." {type master; file "bl.db"; }; +zone "bl12." {type master; file "bl.db"; }; +zone "bl13." {type master; file "bl.db"; }; +zone "bl14." {type master; file "bl.db"; }; +zone "bl15." {type master; file "bl.db"; }; +zone "bl16." {type master; file "bl.db"; }; +zone "bl17." {type master; file "bl.db"; }; +zone "bl18." {type master; file "bl.db"; }; +zone "bl19." {type master; file "bl.db"; }; diff -r -u bin/tests/system/rpz/ns5/tld5.db-orig bin/tests/system/rpz/ns5/tld5.db --- bin/tests/system/rpz/ns5/tld5.db-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/ns5/tld5.db 2004-01-01 00:00:00.000000000 +0000 @@ -22,42 +22,10 @@ NS ns1 NS ns2 NS ns3 - NS ns4 - NS ns5 - NS ns6 - NS ns7 - NS ns8 - NS ns9 - NS ns10 - NS ns11 - NS ns12 - NS ns13 - NS ns14 - NS ns15 - NS ns16 - NS ns17 - NS ns18 - NS ns19 ns A 10.53.0.5 ns1 A 10.53.0.5 ns2 A 10.53.0.5 ns3 A 10.53.0.5 -ns4 A 10.53.0.5 -ns5 A 10.53.0.5 -ns6 A 10.53.0.5 -ns7 A 10.53.0.5 -ns8 A 10.53.0.5 -ns9 A 10.53.0.5 -ns10 A 10.53.0.5 -ns11 A 10.53.0.5 -ns12 A 10.53.0.5 -ns13 A 10.53.0.5 -ns14 A 10.53.0.5 -ns15 A 10.53.0.5 -ns16 A 10.53.0.5 -ns17 A 10.53.0.5 -ns18 A 10.53.0.5 -ns19 A 10.53.0.5 $ORIGIN example.tld5. diff -r -u bin/tests/system/rpz/setup.sh-orig bin/tests/system/rpz/setup.sh --- bin/tests/system/rpz/setup.sh-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/setup.sh 2004-01-01 00:00:00.000000000 +0000 @@ -26,11 +26,13 @@ sh clean.sh -# set up test policy zones. bl-2 is used to check competing zones. -# bl-{given,disabled,passthru,no-data,nxdomain,cname,wildcard,garden} -# are used to check policy overrides in named.conf. -# NO-OP is an obsolete synonym for PASSHTRU -for NM in '' -2 -given -disabled -passthru -no-op -nodata -nxdomain -cname -wildcname -garden; do +# set up test policy zones. +# bl is the main test zone +# bl-2 is used to check competing zones. +# bl-{given,disabled,passthru,no-data,nxdomain,cname,wildcard,garden, +# drop,tcp-only} are used to check policy overrides in named.conf. +# NO-OP is an obsolete synonym for PASSHTRU +for NM in '' -2 -given -disabled -passthru -no-op -nodata -nxdomain -cname -wildcname -garden -drop -tcp-only; do sed -e "/SOA/s/blx/bl$NM/g" ns3/base.db >ns3/bl$NM.db done @@ -48,18 +50,22 @@ signzone ns2 tld2s. base-tld2s.db tld2s.db -# Performance checks. +# Performance and a few other checks. cat <ns5/rpz-switch response-policy { - zone "bl0"; zone "bl1"; zone "bl2"; + zone "bl0"; zone "bl1"; zone "bl2"; zone "bl3"; zone "bl4"; + zone "bl5"; zone "bl6"; zone "bl7"; zone "bl8"; zone "bl9"; + zone "bl10"; zone "bl11"; zone "bl12"; zone "bl13"; zone "bl14"; + zone "bl15"; zone "bl16"; zone "bl17"; zone "bl18"; zone "bl19"; } recursive-only no - max-policy-ttl 90 - # min-ns-dots 0 - break-dnssec yes; + max-policy-ttl 90 + break-dnssec yes + qname-wait-recurse no + ; EOF cat <ns5/example.db -\$TTL 120 +\$TTL 300 @ SOA . hostmaster.ns.example.tld5. ( 1 3600 1200 604800 60 ) NS ns NS ns1 @@ -68,15 +74,16 @@ EOF cat <ns5/bl.db -\$TTL 120 +\$TTL 300 @ SOA . hostmaster.ns.blperf. ( 1 3600 1200 604800 60 ) - NS ns -ns A 10.53.0.5 + NS ns.tld5. -; used only in failure for "recursive-only no" in #8 test5 -a3-5.tld2 CNAME *. +; for "qname-wait-recurse no" in #35 test1 +x.servfail A 35.35.35.35 +; for "recursive-only no" in #8 test5 +a3-5.tld2 CNAME . ; for "break-dnssec" in #9 & #10 test5 -a3-5.tld2s CNAME *. +a3-5.tld2s CNAME . ; for "max-policy-ttl 90" in #17 test5 a3-17.tld2 500 A 17.17.17.17 @@ -85,8 +92,7 @@ EOF if test -n "$QPERF"; then - # do not build the full zones if we will not use them to avoid the long - # time otherwise required to shut down the server + # Do not build the full zones if we will not use them. $PERL -e 'for ($val = 1; $val <= 65535; ++$val) { printf("host-%05d\tA 192.168.%d.%d\n", $val, $val/256, $val%256); }' >>ns5/example.db @@ -110,5 +116,3 @@ printf("host-%05d.example.tld5 A\n", $val); $val = ($val * 9 + 32771) % 65536; }' >ns5/requests - -cp ns2/bl.tld2.db.in ns2/bl.tld2.db diff -r -u bin/tests/system/rpz/test1-orig bin/tests/system/rpz/test1 --- bin/tests/system/rpz/test1-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/test1 2004-01-01 00:00:00.000000000 +0000 @@ -24,13 +24,13 @@ ; QNAME tests ; NXDOMAIN -; 2, 20, 25 +; 2, 25 update add a0-1.tld2.bl. 300 CNAME . ; NODATA -; 3, 21 +; 3 update add a3-1.tld2.bl. 300 CNAME *. ; and no assert-botch -; 4, 5, 22, 23 +; 4, 5 update add a3-2.tld2.bl. 300 DNAME example.com. ; ; NXDOMAIN for a4-2-cname.tld2 via its target a4-2.tld2. @@ -77,6 +77,14 @@ ; 19 update add a4-6.tld2.bl. 300 CNAME . update add a4-6-cname.tld2.bl. 300 A 127.0.0.17 +; no change instead of NXDOMAIN because +norecurse +; 20 +update add a5-2.tld2.bl. 300 CNAME . +; no change instead of NODATA because +norecurse +; 21 +update add a5-3.tld2.bl. 300 CNAME *. +; 22, 23 +update add a5-4.tld2.bl. 300 DNAME example.com. ; ; assert in rbtdb.c ; 24 @@ -84,4 +92,10 @@ ; DO=1 without signatures, DO=0 with signatures are rewritten ; 26 - 27 update add a0-1.tld2s.bl. 300 CNAME . +; 32 +update add a3-8.tld2.bl. 300 CNAME rpz-drop. +; 33 +update add a3-9.tld2.bl. 300 CNAME rpz-tcp-only. +; 34 qname-wait-recurse yes +update add x.servfail.bl. 300 A 127.0.0.34 send diff -r -u bin/tests/system/rpz/test2-orig bin/tests/system/rpz/test2 --- bin/tests/system/rpz/test2-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/test2 2004-01-01 00:00:00.000000000 +0000 @@ -58,7 +58,7 @@ send ; prefer QNAME to IP for a5-4.tld2 -; 13 +; 13, 14 update add 32.4.5.168.192.rpz-ip.bl 300 CNAME a12.tld2. update add a5-4.tld2.bl 300 CNAME a14.tld4. ; @@ -72,3 +72,8 @@ send update add c2.crash2.tld3.bl-2 300 A 127.0.0.16 send + +; client-IP address trigger +; 17 +update add 32.1.0.53.10.rpz-client-ip.bl 300 A 127.0.0.17 +send diff -r -u bin/tests/system/rpz/test5-orig bin/tests/system/rpz/test5 --- bin/tests/system/rpz/test5-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/test5 2004-01-01 00:00:00.000000000 +0000 @@ -35,10 +35,8 @@ ; 4 update add a3-4.tld2.bl-disabled. 300 A 127.0.0.4 send -; 5 - 8 +; 5 - 7 update add a3-5.tld2.bl-nodata. 300 A 127.0.0.5 -; 9 - 10 -update add a3-5.tld2s.bl-nodata. 300 A 127.0.0.9 send ; 11 update add a3-6.tld2.bl-nxdomain. 300 A 127.0.0.11 @@ -57,3 +55,9 @@ ; 16 update add a3-16.tld2.bl. 300 A 127.0.0.16 send +; 18 +update add a3-18.tld2.bl-drop. 300 A 127.0.0.18 +send +; 19 +update add a3-19.tld2.bl-tcp-only. 300 A 127.0.0.19 +send diff -r -u bin/tests/system/rpz/test6-orig bin/tests/system/rpz/test6 --- bin/tests/system/rpz/test6-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/test6 2004-01-01 00:00:00.000000000 +0000 @@ -0,0 +1,40 @@ +; Copyright (C) 2011-2013 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. + + + +; Use comment lines instead of blank lines to combine update requests into +; single requests +; Separate update requests for distinct TLDs with blank lines or 'send' +; End the file with a blank line or 'send' + +server 10.53.0.3 5300 + +; Poke the radix tree a little. +update add 128.1111.2222.3333.4444.5555.6666.7777.8888.rpz-ip.bl. 300 CNAME . +update add 128.1111.2222.3333.4444.5555.6666.zz.rpz-ip.bl. 300 CNAME . +update add 128.1111.2222.3333.4444.5555.zz.8888.rpz-ip.bl. 300 CNAME . +update add 128.1111.2222.3333.4444.zz.8888.rpz-ip.bl. 300 CNAME . +update add 128.zz.3333.4444.0.0.8888.rpz-ip.bl. 300 CNAME . +update add 128.zz.3333.4444.0.7777.8888.rpz-ip.bl. 300 CNAME . +update add 128.zz.3333.4444.0.8777.8888.rpz-ip.bl. 300 CNAME . +update add 127.zz.3333.4444.0.8777.8888.rpz-ip.bl. 300 CNAME . +; +; +; regression testing for some old crashes +update add redirect.bl. 300 A 127.0.0.1 +update add *.redirect.bl. 300 A 127.0.0.1 +update add *.credirect.bl. 300 CNAME google.com. +; +send diff -r -u bin/tests/system/rpz/tests.sh-orig bin/tests/system/rpz/tests.sh --- bin/tests/system/rpz/tests.sh-orig 2004-01-01 00:00:00.000000000 +0000 +++ bin/tests/system/rpz/tests.sh 2004-01-01 00:00:00.000000000 +0000 @@ -21,15 +21,15 @@ . $SYSTEMTESTTOP/conf.sh ns=10.53.0 -ns1=$ns.1 # root, defining the others -ns2=$ns.2 # server whose answers are rewritten -ns3=$ns.3 # resolve that does the rewriting -ns4=$ns.4 # another server that is rewritten -ns5=$ns.5 # check performance with this server +ns1=$ns.1 # root, defining the others +ns2=$ns.2 # authoritative server whose records are rewritten +ns3=$ns.3 # main rewriting resolver +ns4=$ns.4 # another authoritative server that is rewritten +ns5=$ns.5 # another rewriting resolver HAVE_CORE= SAVE_RESULTS= -NS3_STATS=47 + USAGE="$0: [-x]" while getopts "x" c; do @@ -57,13 +57,16 @@ RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s" digcmd () { + if test "$1" = TCP; then + shift + fi # Default to +noauth and @$ns3 # Also default to -bX where X is the @value so that OS X will choose - # the right IP source address. - digcmd_args=`echo "+noadd +time=1 +tries=1 -p 5300 $*" | \ - sed -e "/@/!s/.*/& @$ns3/" \ - -e '/-b/!s/@\([^ ]*\)/@\1 -b\1/' \ - -e '/+n?o?auth/!s/.*/+noauth &/'` + # the right IP source address. + digcmd_args=`echo "+noadd +time=1 +tries=1 -p 5300 $*" | \ + sed -e "/@/!s/.*/& @$ns3/" \ + -e '/-b/!s/@\([^ ]*\)/@\1 -b\1/' \ + -e '/+n?o?auth/!s/.*/+noauth &/'` #echo I:dig $digcmd_args 1>&2 $DIG $digcmd_args } @@ -89,10 +92,13 @@ # (re)load the reponse policy zones with the rules in the file $TEST_FILE load_db () { if test -n "$TEST_FILE"; then - $NSUPDATE -v $TEST_FILE || { + if $NSUPDATE -v $TEST_FILE; then : + $RNDCCMD $ns3 sync + else echo "I:failed to update policy zone with $TEST_FILE" + $RNDCCMD $ns3 sync exit 1 - } + fi fi } @@ -135,16 +141,20 @@ return 1 } -# check that statistics for $1 in $2 = $3 ckstats () { - rm -f $2/named.stats - $RNDCCMD $1 stats - CNT=`sed -n -e 's/[ ]*\([0-9]*\).response policy.*/\1/p' \ - $2/named.stats` - CNT=`expr 0$CNT + 0` - if test "$CNT" -ne $3; then - setret "I:wrong $2 statistics of $CNT instead of $3" + HOST=$1 + LABEL="$2" + NSDIR="$3" + EXPECTED="$4" + $RNDCCMD $HOST stats + NEW_CNT=0`sed -n -e 's/[ ]*\([0-9]*\).response policy.*/\1/p' \ + $NSDIR/named.stats | tail -1` + eval "OLD_CNT=0\$${NSDIR}_CNT" + GOT=`expr $NEW_CNT - $OLD_CNT` + if test "$GOT" -ne "$EXPECTED"; then + setret "I:wrong $LABEL $NSDIR statistics of $GOT instead of $EXPECTED" fi + eval "${NSDIR}_CNT=$NEW_CNT" } # $1=message $2=optional test file name @@ -181,6 +191,12 @@ ckresult () { #ckalive "$1" "I:server crashed by 'dig $1'" || return 1 if $PERL $SYSTEMTESTTOP/digcomp.pl $DIGNM $2 >/dev/null; then + NEED_TCP=`echo "$1" | sed -n -e 's/[Tt][Cc][Pp].*/TCP/p'` + RESULT_TCP=`sed -n -e 's/.*Truncated, retrying in TCP.*/TCP/p' $DIGNM` + if test "$NEED_TCP" != "$RESULT_TCP"; then + setret "I:'dig $1' wrong; no or unexpected truncation in $DIGNM" + return 1 + fi clean_result ${DIGNM}* return 0 fi @@ -237,12 +253,14 @@ clean_result ${DIGNM}* } -# check that a response is not rewritten -# $1=target domain $2=optional query type +# Check that a response is not rewritten +# Use $ns1 instead of the authority for most test domains, $ns2 to prevent +# spurious differences for `dig +norecurse` +# $1=optional "TCP" remaining args for dig nochange () { make_dignm digcmd $* >$DIGNM - digcmd $* @$ns2 >${DIGNM}_OK + digcmd $* @$ns1 >${DIGNM}_OK ckresult "$*" ${DIGNM}_OK && clean_result ${DIGNM}_OK } @@ -254,6 +272,20 @@ ckresult "$*" ${DIGNM}_OK } +# check dropped response +DROPPED='^;; connection timed out; no servers could be reached' +drop () { + make_dignm + digcmd $* >$DIGNM + if grep "$DROPPED" $DIGNM >/dev/null; then + clean_result ${DIGNM}* + return 0 + fi + setret "I:'dig $1' wrong; response in $DIGNM" + return 1 +} + + # make prototype files to check against rewritten results digcmd nonexistent @$ns2 >proto.nxdomain digcmd txt-only.tld2 @$ns2 >proto.nodata @@ -281,19 +313,27 @@ addr 57.57.57.57 a3-7.sub1.tld2 # 17 wildcard CNAME addr 127.0.0.16 a4-5-cname3.tld2 # 18 CNAME chain addr 127.0.0.17 a4-6-cname3.tld2 # 19 stop short in CNAME chain -nochange a0-1.tld2 +norecurse # 20 check that RD=1 is required -nochange a3-1.tld2 +norecurse # 21 -nochange a3-2.tld2 +norecurse # 22 -nochange sub.a3-2.tld2 +norecurse # 23 +nochange a5-2.tld2 +norecurse # 20 check that RD=1 is required +nochange a5-3.tld2 +norecurse # 21 +nochange a5-4.tld2 +norecurse # 22 +nochange sub.a5-4.tld2 +norecurse # 23 nxdomain c1.crash2.tld3 # 24 assert in rbtdb.c nxdomain a0-1.tld2 +dnssec # 25 simple DO=1 without signatures -nxdomain a0-1.tld2s # 26 simple DO=0 with signatures +nxdomain a0-1.tld2s +nodnssec # 26 simple DO=0 with signatures nochange a0-1.tld2s +dnssec # 27 simple DO=1 with signatures nxdomain a0-1s-cname.tld2s +dnssec # 28 DNSSEC too early in CNAME chain nochange a0-1-scname.tld2 +dnssec # 29 DNSSEC on target in CNAME chain -nochange a0-1.tld2s srv +auth +dnssec # 30 no write for +DNSSEC and no record -nxdomain a0-1.tld2s srv # 31 +nochange a0-1.tld2s srv +auth +dnssec # 30 no write for DNSSEC and no record +nxdomain a0-1.tld2s srv +nodnssec # 31 +drop a3-8.tld2 any # 32 drop +nochange tcp a3-9.tld2 # 33 tcp-only +here x.servfail <<'EOF' # 34 qname-wait-recurse yes + ;; status: SERVFAIL, x +EOF +addr 35.35.35.35 "x.servfail @$ns5" # 35 qname-wait-recurse no end_group +ckstats $ns3 test1 ns3 22 +ckstats $ns5 test1 ns5 1 start_group "IP rewrites" test2 nodata a3-1.tld2 # 1 NODATA @@ -308,35 +348,14 @@ nochange a4-1-aaaa.tld2 -taaaa # 10 addr 127.0.0.1 a5-1-2.tld2 # 11 prefer smallest policy address addr 127.0.0.1 a5-3.tld2 # 12 prefer first conflicting IP zone -addr 14.14.14.14 a5-4.tld2 # 13 prefer QNAME to IP -nochange a5-4.tld2 +norecurse # 14 check that RD=1 is required +nochange a5-4.tld2 +norecurse # 13 check that RD=1 is required for #14 +addr 14.14.14.14 a5-4.tld2 # 14 prefer QNAME to IP nochange a4-4.tld2 # 15 PASSTHRU nxdomain c2.crash2.tld3 # 16 assert in rbtdb.c -ckstats $ns3 ns3 29 -nxdomain a7-1.tld2 # 17 slave policy zone (RT34450) -cp ns2/blv2.tld2.db.in ns2/bl.tld2.db -$RNDCCMD 10.53.0.2 reload bl.tld2 -goodsoa="rpz.tld2. hostmaster.ns.tld2. 2 3600 1200 604800 60" -for i in 0 1 2 3 4 5 6 7 8 9 10 -do - soa=`$DIG -p 5300 +short soa bl.tld2 @10.53.0.3 -b10.53.0.3` - test "$soa" = "$goodsoa" && break - sleep 1 -done -nochange a7-1.tld2 # 18 PASSTHRU -sleep 1 # ensure that a clock tick has occured so that the reload takes effect -cp ns2/blv3.tld2.db.in ns2/bl.tld2.db -goodsoa="rpz.tld2. hostmaster.ns.tld2. 3 3600 1200 604800 60" -$RNDCCMD 10.53.0.2 reload bl.tld2 -for i in 0 1 2 3 4 5 6 7 8 9 10 -do - soa=`$DIG -p 5300 +short soa bl.tld2 @10.53.0.3 -b10.53.0.3` - test "$soa" = "$goodsoa" && break - sleep 1 -done -nxdomain a7-1.tld2 # 19 slave policy zone (RT34450) -ckstats $ns3 ns3 31 +addr 127.0.0.17 "a4-4.tld2 -b $ns1" # 17 client-IP address trigger +nxdomain a7-1.tld2 # 18 slave policy zone (RT34450) end_group +ckstats $ns3 test2 ns3 11 # check that IP addresses for previous group were deleted from the radix tree start_group "radix tree deletions" @@ -352,6 +371,7 @@ nochange a4-1-aaaa.tld2 -tAAAA nochange a5-1-2.tld2 end_group +ckstats $ns3 'radix tree deletions' ns3 0 if ./rpz nsdname; then # these tests assume "min-ns-dots 0" @@ -369,7 +389,7 @@ addr 127.0.0.2 a3-1.subsub.sub3.tld2 nxdomain xxx.crash1.tld2 # 12 dns_db_detachnode() crash end_group - NS3_STATS=`expr $NS3_STATS + 7` + ckstats $ns3 test3 ns3 7 else echo "I:NSDNAME not checked; named configured with --disable-rpz-nsdname" fi @@ -383,15 +403,15 @@ nochange a3-1.tld4 # 4 different NS IP address end_group -# start_group "walled garden NSIP rewrites" test4a -# addr 41.41.41.41 a3-1.tld2 # 1 walled garden for all of tld2 -# addr 2041::41 'a3-1.tld2 AAAA' # 2 walled garden for all of tld2 -# here a3-1.tld2 TXT <<'EOF' # 3 text message for all of tld2 -# ;; status: NOERROR, x -# a3-1.tld2. x IN TXT "NSIP walled garden" -#EOF -# end_group - NS3_STATS=`expr $NS3_STATS + 1` + start_group "walled garden NSIP rewrites" test4a + addr 41.41.41.41 a3-1.tld2 # 1 walled garden for all of tld2 + addr 2041::41 'a3-1.tld2 AAAA' # 2 walled garden for all of tld2 + here a3-1.tld2 TXT <<'EOF' # 3 text message for all of tld2 + ;; status: NOERROR, x + a3-1.tld2. x IN TXT "NSIP walled garden" +EOF + end_group + ckstats $ns3 test4 ns3 4 else echo "I:NSIP not checked; named configured with --disable-rpz-nsip" fi @@ -403,12 +423,12 @@ nochange a3-2.tld2 # 2 bl-passthru nochange a3-3.tld2 # 3 bl-no-op obsolete for passthru nochange a3-4.tld2 # 4 bl-disabled -nodata a3-5.tld2 # 5 bl-nodata -nodata a3-5.tld2 +norecurse # 6 bl-nodata recursive-only no -nodata a3-5.tld2 # 7 bl-nodata -nodata a3-5.tld2 +norecurse @$ns5 # 8 bl-nodata recursive-only no -nodata a3-5.tld2s @$ns5 # 9 bl-nodata -nodata a3-5.tld2s +dnssec @$ns5 # 10 bl-nodata break-dnssec +nodata a3-5.tld2 # 5 bl-nodata zone recursive-only no +nodata a3-5.tld2 +norecurse # 6 bl-nodata zone recursive-only no +nodata a3-5.tld2 # 7 bl-nodata not needed +nxdomain a3-5.tld2 +norecurse @$ns5 # 8 bl-nodata global recursive-only no +nxdomain a3-5.tld2s @$ns5 # 9 bl-nodata global break-dnssec +nxdomain a3-5.tld2s +dnssec @$ns5 # 10 bl-nodata global break-dnssec nxdomain a3-6.tld2 # 11 bl-nxdomain here a3-7.tld2 -tany <<'EOF' ;; status: NOERROR, x @@ -420,10 +440,15 @@ addr 12.12.12.12 a3-15.tld2 # 15 bl-garden via CNAME to a12.tld2 addr 127.0.0.16 a3-16.tld2 100 # 16 bl max-policy-ttl 100 addr 17.17.17.17 "a3-17.tld2 @$ns5" 90 # 17 ns5 bl max-policy-ttl 90 +drop a3-18.tld2 any # 18 bl-drop +nxdomain TCP a3-19.tld2 # 19 bl-tcp-only end_group +ckstats $ns3 test5 ns3 12 +ckstats $ns5 test5 ns5 4 + # check that miscellaneous bugs are still absent -start_group "crashes" +start_group "crashes" test6 for Q in RRSIG SIG ANY 'ANY +dnssec'; do nocrash a3-1.tld2 -t$Q nocrash a3-2.tld2 -t$Q @@ -437,6 +462,8 @@ # resolving foo. # nxdomain 32.3.2.1.127.rpz-ip end_group +ckstats $ns3 bugs ns3 8 + # superficial test for major performance bugs @@ -449,6 +476,7 @@ $QPERF -c -1 -l30 -d ns5/requests -s $ns5 -p 5300 >/dev/null comment "before real test $1" PFILE="ns5/$2.perf" + $RNDCCMD $ns5 notrace $QPERF -c -1 -l30 -d ns5/requests -s $ns5 -p 5300 >$PFILE comment "after test $1" X=`sed -n -e 's/.*Returned *\([^ ]*:\) *\([0-9]*\) .*/\1\2/p' $PFILE \ @@ -463,17 +491,17 @@ } # get qps with rpz - perf 'with rpz' rpz 'NOERROR:2900 NXDOMAIN:100 ' + perf 'with RPZ' rpz 'NOERROR:2900 NXDOMAIN:100 ' RPZ=`trim rpz` # turn off rpz and measure qps again - echo "# rpz off" >ns5/rpz-switch + echo "# RPZ off" >ns5/rpz-switch RNDCCMD_OUT=`$RNDCCMD $ns5 reload` - perf 'without rpz' norpz 'NOERROR:3000 ' + perf 'without RPZ' norpz 'NOERROR:3000 ' NORPZ=`trim norpz` PERCENT=`expr \( "$RPZ" \* 100 + \( $NORPZ / 2 \) \) / $NORPZ` - echo "I:$RPZ qps with rpz is $PERCENT% of $NORPZ qps without rpz" + echo "I:$RPZ qps with RPZ is $PERCENT% of $NORPZ qps without RPZ" MIN_PERCENT=30 if test "$PERCENT" -lt $MIN_PERCENT; then @@ -484,13 +512,12 @@ setret "I:$RPZ qps with RPZ or $PERCENT% of $NORPZ qps without RPZ is too high" fi - ckstats $ns5 ns5 203 + ckstats $ns5 performance ns5 200 else echo "I:performance not checked; queryperf not available" fi -ckstats $ns3 ns3 57 # restart the main test RPZ server to see if that creates a core file if test -z "$HAVE_CORE"; then diff -r -u doc/arm/Bv9ARM-book.xml-orig doc/arm/Bv9ARM-book.xml --- doc/arm/Bv9ARM-book.xml-orig 2004-01-01 00:00:00.000000000 +0000 +++ doc/arm/Bv9ARM-book.xml 2004-01-01 00:00:00.000000000 +0000 @@ -4873,7 +4873,7 @@ min-table-size number ; } ; response-policy { zone_name - policy given | disabled | passthru | nxdomain | nodata | cname domain + policy given | disabled | passthru | drop | nxdomain | nodata | cname domain recursive-only yes_or_no max-policy-ttl number ; } recursive-only yes_or_no max-policy-ttl number break-dnssec yes_or_no min-ns-dots number ; @@ -9167,77 +9167,122 @@ 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. - RPZs are ordinary DNS zones containing RRsets + Response policy zones are ordinary DNS zones containing RRsets that can be queried normally if allowed. It is usually best to restrict those queries with something like allow-query { localhost; };. - Four policy triggers are encoded in RPZ records, QNAME, IP, NSIP, - and NSDNAME. - QNAME RPZ records triggered by query names of requests and targets - of CNAME records resolved to generate the response. - The owner name of a QNAME RPZ record is the query name relativized - to the RPZ. - + Five policy triggers can be encoded in RPZ records. + + + RPZ-CLIENT-IP + + + IP records are triggered by the IP address of the + DNS client. + Client IP address triggers are encoded in records that have + owner names that are subdomains of + rpz-client-ip relativized to the + policy zone origin name + and encode an address or address block. + IPv4 addresses are represented as + prefixlength.B4.B3.B2.B1.rpz-ip. + The IPv4 prefix length must be between 1 and 32. + All four bytes, B4, B3, B2, and B1, must be present. + B4 is the decimal value of the least significant byte of the + IPv4 address as in IN-ADDR.ARPA. + - - The second kind of RPZ trigger is an IP address in an A and AAAA - record in the ANSWER section of a response. - IP address triggers are encoded in records that have owner names - that are subdomains of rpz-ip relativized - to the RPZ origin name and encode an IP address or address block. - IPv4 trigger addresses are represented as - prefixlength.B4.B3.B2.B1.rpz-ip. - The prefix length must be between 1 and 32. - All four bytes, B4, B3, B2, and B1, must be present. - B4 is the decimal value of the least significant byte of the - IPv4 address as in IN-ADDR.ARPA. - IPv6 addresses are encoded in a format similar to the standard - IPv6 text representation, - prefixlength.W8.W7.W6.W5.W4.W3.W2.W1.rpz-ip. - Each of W8,...,W1 is a one to four digit hexadecimal number - representing 16 bits of the IPv6 address as in the standard text - representation of IPv6 addresses, but reversed as in IN-ADDR.ARPA. - All 8 words must be present except when consecutive - zero words are replaced with .zz. - analogous to double colons (::) in standard IPv6 text encodings. - The prefix length must be between 1 and 128. - + + IPv6 addresses are encoded in a format similar + to the standard IPv6 text representation, + prefixlength.W8.W7.W6.W5.W4.W3.W2.W1.rpz-ip. + Each of W8,...,W1 is a one to four digit hexadecimal number + representing 16 bits of the IPv6 address as in the standard + text representation of IPv6 addresses, + but reversed as in IN-ADDR.ARPA. + All 8 words must be present except when one set of consecutive + zero words is replaced with .zz. + analogous to double colons (::) in standard IPv6 text + encodings. + The IPv6 prefix length must be between 64 and 128. + + + - - NSDNAME triggers match names of authoritative servers - for the query name, a parent of the query name, a CNAME for - query name, or a parent of a CNAME. - They are encoded as subdomains of - rpz-nsdomain relativized - to the RPZ origin name. - NSIP triggers match IP addresses in A and - AAAA RRsets for domains that can be checked against NSDNAME - policy records. - NSIP triggers are encoded like IP triggers except as subdomains of - rpz-nsip. - NSDNAME and NSIP triggers are checked only for names with at - least min-ns-dots dots. - The default value of min-ns-dots is 1 to - exclude top level domains. - + + QNAME + + + QNAME policy records are triggered by query names of + requests and targets of CNAME records resolved to generate + the response. + The owner name of a QNAME policy record is + the query name relativized to the policy zone. + + + + + + RPZ-IP + + + IP triggers are IP addresses in an + A or AAAA record in the ANSWER section of a response. + They are encoded like client-IP triggers except as + subdomains of rpz-ip. + + + + + + RPZ-NSDNAME + + + NSDNAME triggers match names of authoritative servers + for the query name, a parent of the query name, a CNAME for + query name, or a parent of a CNAME. + They are encoded as subdomains of + rpz-nsdname relativized + to the RPZ origin name. + NSIP triggers match IP addresses in A and + AAAA RRsets for domains that can be checked against NSDNAME + policy records. + + + + + + RPZ-NSIP + + + NSIP triggers are encoded like IP triggers except as + subdomains of rpz-nsip. + NSDNAME and NSIP triggers are checked only for names with at + least min-ns-dots dots. + The default value of min-ns-dots is 1 to + exclude top level domains. + + + + - The query response is checked against all RPZs, so - two or more policy records can be triggered by a response. - Because DNS responses can be rewritten according to at most one + The query response is checked against all response policy zones, + so two or more policy records can be triggered by a response. + Because DNS responses are rewritten according to at most one policy record, a single record encoding an action (other than DISABLED actions) must be chosen. - Triggers or the records that encode them are chosen in - the following order: + Triggers or the records that encode them are chosen for the + rewriting in the following order: Choose the triggered record in the zone that appears - first in the response-policy option. + first in the response-policy option. - Prefer QNAME to IP to NSDNAME to NSIP triggers - in a single zone. + Prefer CLIENT-IP to QNAME to IP to NSDNAME to NSIP + triggers in a single zone. Among NSDNAME triggers, prefer the trigger that matches the smallest name under the DNSSEC ordering. @@ -9256,83 +9301,168 @@ When the processing of a response is restarted to resolve DNAME or CNAME records and a policy record set has not been triggered, - all RPZs are again consulted for the DNAME or CNAME names - and addresses. + all response policy zones are again consulted for the + DNAME or CNAME names and addresses. - RPZ record sets are sets of any types of DNS record except - DNAME or DNSSEC that encode actions or responses to queries. - - The NXDOMAIN response is encoded - by a CNAME whose target is the root domain (.) - - A CNAME whose target is the wildcard top-level - domain (*.) specifies the NODATA action, - which rewrites the response to NODATA or ANCOUNT=1. - - The Local Data action is - represented by a set ordinary DNS records that are used - to answer queries. Queries for record types not the - set are answered with NODATA. - - A special form of local data is a CNAME whose target is a - wildcard such as *.example.com. - It is used as if were an ordinary CNAME after the astrisk (*) - has been replaced with the query name. - The purpose for this special form is query logging in the - walled garden's authority DNS server. - - The PASSTHRU policy is specified - by a CNAME whose target is rpz-passthru. - It causes the response to not be rewritten - and is most often used to "poke holes" in policies for - CIDR blocks. - (A CNAME whose target is the variable part of its owner name - is an obsolete specification of the PASSTHRU policy.) - - + RPZ record sets are any types of DNS record except + DNAME or DNSSEC that encode actions or responses to + individual queries. + Any of the policies can be used with any of the triggers. + For example, while the TCP-only policy is + commonly used with client-IP triggers, + it cn be used with any type of trigger to force the use of + TCP for responses with owner names in a zone. + + + PASSTHRU + + + The whitelist policy is specified + by a CNAME whose target is rpz-passthru. + It causes the response to not be rewritten + and is most often used to "poke holes" in policies for + CIDR blocks. + + + + + + DROP + + + The blacklist policy is specified + by a CNAME whose target is rpz-drop. + It causes the response to be discarded. + Nothing is sent to the DNS client. + + + + + + TCP-Only + + + The "slip" policy is specified + by a CNAME whose target is rpz-tcp-only. + It changes UDP responses to short, truncated DNS responses + that require the DNS client to try again with TCP. + It is used to mitigate distributed DNS reflection attacks. + + + + + + NXDOMAIN + + + The domain undefined response is encoded + by a CNAME whose target is the root domain (.) + + + + + + NODATA + + + The empty set of resource records is specified by + CNAME whose target is the wildcard top-level + domain (*.). + It rewrites the response to NODATA or ANCOUNT=1. + + + + + + Local Data + + + A set of ordinary DNS records can be used to answer queries. + Queries for record types not the set are answered with + NODATA. + + + + A special form of local data is a CNAME whose target is a + wildcard such as *.example.com. + It is used as if were an ordinary CNAME after the astrisk (*) + has been replaced with the query name. + The purpose for this special form is query logging in the + walled garden's authority DNS server. + + + + - The actions specified in an RPZ can be overridden with a - policy clause in the + All of the actions specified in all of the individual records + in a policy zone + can be overridden with a policy clause in the response-policy option. - An organization using an RPZ provided by another organization might - use this mechanism to redirect domains to its own walled garden. - - GIVEN says "do not override but - perform the action specified in the zone." - - DISABLED causes policy records to do - nothing but log what they might have done. - The response to the DNS query will be written according to - any triggered policy records that are not disabled. - Disabled policy zones should appear first, - because they will often not be logged - if a higher precedence trigger is found first. - - PASSTHRU causes all policy records - to act as if they were CNAME records with targets the variable - part of their owner name. They protect the response from - being changed. - - NXDOMAIN causes all RPZ records - to specify NXDOMAIN policies. - - NODATA overrides with the - NODATA policy - - CNAME domain causes all RPZ - policy records to act as if they were "cname domain" records. - - + An organization using a policy zone provided by another + organization might use this mechanism to redirect domains + to its own walled garden. + + + GIVEN + + The placeholder policy says "do not override but + perform the action specified in the zone." + + + + + + DISABLED + + + The testing override policy causes policy zone records to do + nothing but log what they would have done if the + policy zone were not disabled. + The response to the DNS query will be written (or not) + according to any triggered policy records that are not + disabled. + Disabled policy zones should appear first, + because they will often not be logged + if a higher precedence trigger is found first. + + + + + + PASSTHRU, + DROP, + TCP-Only, + NXDOMAIN, + and + NODATA + + + override with the corresponding per-record policy. + + + + + + CNAME domain + + + causes all RPZ policy records to act as if they were + "cname domain" records. + + + + - By default, the actions encoded in an RPZ are applied - only to queries that ask for recursion (RD=1). - That default can be changed for a single RPZ or all RPZs in a view + By default, the actions encoded in a response policy zone + are applied only to queries that ask for recursion (RD=1). + That default can be changed for a single policy zone or + all response policy zones in a view with a recursive-only no clause. This feature is useful for serving the same zone files both inside and outside an RFC 1918 cloud and using RPZ to @@ -9341,15 +9471,43 @@ - Also by default, RPZ actions are applied only to DNS requests that - either do not request DNSSEC metadata (DO=0) or when no DNSSEC - records are available for request name in the original zone (not - the response policy zone). - This default can be changed for all RPZs in a view with a - break-dnssec yes clause. - In that case, RPZ actions are applied regardless of DNSSEC. - The name of the clause option reflects the fact that results - rewritten by RPZ actions cannot verify. + Also by default, RPZ actions are applied only to DNS requests + that either do not request DNSSEC metadata (DO=0) or when no + DNSSEC records are available for request name in the original + zone (not the response policy zone). This default can be + changed for all response policy zones in a view with a + break-dnssec yes clause. In that case, RPZ + actions are applied regardless of DNSSEC. The name of the + clause option reflects the fact that results rewritten by RPZ + actions cannot verify. + + + + No DNS records are needed for a QNAME or Client-IP trigger. + The name or IP address itself is sufficient, + so in principle the query name need not be recursively resolved. + However, not resolving the requested + name can leak the fact that response policy rewriting is in use + and that the name is listed in a policy zone to operators of + servers for listed names. To prevent that information leak, by + default any recursion needed for a request is done before any + policy triggers are considered. Because listed domains often + have slow authoritative servers, this default behavior can cost + significant time. + The qname-wait-recurse no option + overrides that default behavior when recursion cannot + change a non-error response. + The option does not affect QNAME or client-IP triggers + in policy zones listed + after other zones containing IP, NSIP and NSDNAME triggers, because + those may depend on the A, AAAA, and NS records that would be + found during recursive resolution. It also does not affect + DNSSEC requests (DO=1) unless break-dnssec yes + is in use, because the response would depend on whether or not + RRSIG records were found during resolution. + The option can cause appear to rewrite error responses + such as SERVFAIL when no recursion is done to discover problems + at the authoritative server. @@ -9377,26 +9535,38 @@ ; QNAME policy records. There are no periods (.) after the owner names. nxdomain.domain.com CNAME . ; NXDOMAIN policy +*.nxdomain.domain.com CNAME . ; NXDOMAIN policy nodata.domain.com CNAME *. ; NODATA policy +*.nodata.domain.com CNAME *. ; NODATA policy bad.domain.com A 10.0.0.1 ; redirect to a walled garden AAAA 2001:2::1 +bzone.domain.com CNAME garden.example.com. ; do not rewrite (PASSTHRU) OK.DOMAIN.COM ok.domain.com CNAME rpz-passthru. -bzone.domain.com CNAME garden.example.com. - ; redirect x.bzone.domain.com to x.bzone.domain.com.garden.example.com *.bzone.domain.com CNAME *.garden.example.com. -; IP policy records that rewrite all answers for 127/8 except 127.0.0.1 +; IP policy records that rewrite all responses containing A records in 127/8 +; except 127.0.0.1 8.0.0.0.127.rpz-ip CNAME . 32.1.0.0.127.rpz-ip CNAME rpz-passthru. ; NSDNAME and NSIP policy records ns.domain.com.rpz-nsdname CNAME . 48.zz.2.2001.rpz-nsip CNAME . + +; blacklist and whitelist some DNS clients +112.zz.2001.rpz-client-ip CNAME rpz-drop. +8.0.0.0.127.rpz-client-ip CNAME rpz-drop. + +; force some DNS clients and responses in the example.com zone to TCP +16.0.0.1.10.rpz-client-ip CNAME rpz-tcp-only. +example.com CNAME rpz-tcp-only. +*.example.com CNAME rpz-tcp-only. + RPZ can affect server performance. diff -r -u lib/dns/db.c-orig lib/dns/db.c --- lib/dns/db.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/db.c 2004-01-01 00:00:00.000000000 +0000 @@ -1007,21 +1007,23 @@ (db->methods->resigned)(db, rdataset, version); } -isc_result_t -dns_db_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st) -{ - if (db->methods->rpz_enabled != NULL) - return ((db->methods->rpz_enabled)(db, st)); - return (ISC_R_SUCCESS); +/* + * Attach a database to policy zone databases. + * This should only happen when the caller has already ensured that + * it is dealing with a database that understands response policy zones. + */ +void +dns_db_rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) { + REQUIRE(db->methods->rpz_attach != NULL); + (db->methods->rpz_attach)(db, rpzs, rpz_num); } -void -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, - dns_name_t *query_qname) -{ - if (db->methods->rpz_findips != NULL) - (db->methods->rpz_findips)(rpz, rpz_type, zone, db, version, - ardataset, st, query_qname); +/* + * Finish loading a response policy zone. + */ +isc_result_t +dns_db_rpz_ready(dns_db_t *db) { + if (db->methods->rpz_ready == NULL) + return (ISC_R_SUCCESS); + return ((db->methods->rpz_ready)(db)); } diff -r -u lib/dns/ecdb.c-orig lib/dns/ecdb.c --- lib/dns/ecdb.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/ecdb.c 2004-01-01 00:00:00.000000000 +0000 @@ -582,8 +582,8 @@ NULL, /* resigned */ NULL, /* isdnssec */ NULL, /* getrrsetstats */ - NULL, /* rpz_enabled */ - NULL, /* rpz_findips */ + NULL, /* rpz_attach */ + NULL, /* rpz_ready */ NULL, /* findnodeext */ NULL /* findext */ }; diff -r -u lib/dns/include/dns/db.h-orig lib/dns/include/dns/db.h --- lib/dns/include/dns/db.h-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/include/dns/db.h 2004-01-01 00:00:00.000000000 +0000 @@ -172,14 +172,9 @@ dns_dbversion_t *version); isc_boolean_t (*isdnssec)(dns_db_t *db); dns_stats_t *(*getrrsetstats)(dns_db_t *db); - isc_result_t (*rpz_enabled)(dns_db_t *db, dns_rpz_st_t *st); - void (*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_name_t *query_qname); + void (*rpz_attach)(dns_db_t *db, dns_rpz_zones_t *rpzs, + dns_rpz_num_t rpz_num); + isc_result_t (*rpz_ready)(dns_db_t *db); isc_result_t (*findnodeext)(dns_db_t *db, dns_name_t *name, isc_boolean_t create, dns_clientinfomethods_t *methods, @@ -1542,30 +1537,17 @@ * dns_rdatasetstats_create(); otherwise NULL. */ -isc_result_t -dns_db_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st); +void +dns_db_rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num); /*%< - * Mark a database for response policy rewriting - * or find which RPZ data is available. + * Attach the response policy information for a view to a database for a + * zone for the view. */ -void -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, - dns_name_t *query_qname); -/*%< - * 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_result_t +dns_db_rpz_ready(dns_db_t *db); +/*%< + * Finish loading a response policy zone. */ ISC_LANG_ENDDECLS diff -r -u lib/dns/include/dns/rpz.h-orig lib/dns/include/dns/rpz.h --- lib/dns/include/dns/rpz.h-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/include/dns/rpz.h 2004-01-01 00:00:00.000000000 +0000 @@ -25,19 +25,31 @@ #include #include #include +#include ISC_LANG_BEGINDECLS #define DNS_RPZ_PREFIX "rpz-" +/* + * Sub-zones of various trigger types. + */ +#define DNS_RPZ_CLIENT_IP_ZONE DNS_RPZ_PREFIX"client-ip" #define DNS_RPZ_IP_ZONE DNS_RPZ_PREFIX"ip" #define DNS_RPZ_NSIP_ZONE DNS_RPZ_PREFIX"nsip" #define DNS_RPZ_NSDNAME_ZONE DNS_RPZ_PREFIX"nsdname" -#define DNS_RPZ_PASSTHRU_ZONE DNS_RPZ_PREFIX"passthru" +/* + * Special policies. + */ +#define DNS_RPZ_PASSTHRU_NAME DNS_RPZ_PREFIX"passthru" +#define DNS_RPZ_DROP_NAME DNS_RPZ_PREFIX"drop" +#define DNS_RPZ_TCP_ONLY_NAME DNS_RPZ_PREFIX"tcp-only" -typedef isc_uint8_t dns_rpz_cidr_bits_t; + +typedef isc_uint8_t dns_rpz_prefix_t; typedef enum { DNS_RPZ_TYPE_BAD, + DNS_RPZ_TYPE_CLIENT_IP, DNS_RPZ_TYPE_QNAME, DNS_RPZ_TYPE_IP, DNS_RPZ_TYPE_NSDNAME, @@ -45,45 +57,151 @@ } dns_rpz_type_t; /* - * Require DNS_RPZ_POLICY_PASSTHRU < DNS_RPZ_POLICY_NXDOMAIN < - * DNS_RPZ_POLICY_NODATA < DNS_RPZ_POLICY_CNAME to choose among competing - * policies. + * Require DNS_RPZ_POLICY_PASSTHRU < DNS_RPZ_POLICY_DROP + * < DNS_RPZ_POLICY_TCP_ONLY DNS_RPZ_POLICY_NXDOMAIN < DNS_RPZ_POLICY_NODATA + * < DNS_RPZ_POLICY_CNAME to choose among competing policies. */ typedef enum { DNS_RPZ_POLICY_GIVEN = 0, /* 'given': what policy record says */ - DNS_RPZ_POLICY_DISABLED = 1, /* 'cname x': answer with x's rrsets */ + DNS_RPZ_POLICY_DISABLED = 1, /* log what would have happened */ DNS_RPZ_POLICY_PASSTHRU = 2, /* 'passthru': do not rewrite */ - DNS_RPZ_POLICY_NXDOMAIN = 3, /* 'nxdomain': answer with NXDOMAIN */ - DNS_RPZ_POLICY_NODATA = 4, /* 'nodata': answer with ANCOUNT=0 */ - DNS_RPZ_POLICY_CNAME = 5, /* 'cname x': answer with x's rrsets */ + DNS_RPZ_POLICY_DROP = 3, /* 'drop': do not respond */ + DNS_RPZ_POLICY_TCP_ONLY = 4, /* 'tcp-only': answer UDP with TC=1 */ + DNS_RPZ_POLICY_NXDOMAIN = 5, /* 'nxdomain': answer with NXDOMAIN */ + DNS_RPZ_POLICY_NODATA = 6, /* 'nodata': answer with ANCOUNT=0 */ + DNS_RPZ_POLICY_CNAME = 7, /* 'cname x': answer with x's rrsets */ DNS_RPZ_POLICY_RECORD, DNS_RPZ_POLICY_WILDCNAME, DNS_RPZ_POLICY_MISS, DNS_RPZ_POLICY_ERROR } dns_rpz_policy_t; +typedef isc_uint8_t dns_rpz_num_t; + +#define DNS_RPZ_MAX_ZONES 32 +#if DNS_RPZ_MAX_ZONES > 32 +# if DNS_RPZ_MAX_ZONES > 64 +# error "rpz zone bit masks must fit in a word" +# endif +typedef isc_uint64_t dns_rpz_zbits_t; +#else +typedef isc_uint32_t dns_rpz_zbits_t; +#endif + +#define DNS_RPZ_ALL_ZBITS ((dns_rpz_zbits_t)-1) + +#define DNS_RPZ_INVALID_NUM DNS_RPZ_MAX_ZONES + +#define DNS_RPZ_ZBIT(n) (((dns_rpz_zbits_t)1) << (dns_rpz_num_t)(n)) + /* - * Specify a response policy zone. + * Mask of the specified and higher numbered policy zones + * Avoid hassles with (1<<33) or (1<<65) */ -typedef struct dns_rpz_zone dns_rpz_zone_t; +#define DNS_RPZ_ZMASK(n) ((dns_rpz_zbits_t)((((n) >= DNS_RPZ_MAX_ZONES-1) ? \ + 0 : (1<<((n)+1))) -1)) +/* + * The number of triggers of each type in a response policy zone. + */ +typedef struct dns_rpz_triggers dns_rpz_triggers_t; +struct dns_rpz_triggers { + int client_ipv4; + int client_ipv6; + int qname; + int ipv4; + int ipv6; + int nsdname; + int nsipv4; + int nsipv6; +}; +/* + * A single 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; /* ordinal in list of policy zones */ - dns_name_t origin; /* Policy zone name */ - dns_name_t nsdname; /* DNS_RPZ_NSDNAME_ZONE.origin */ - dns_name_t passthru;/* DNS_RPZ_PASSTHRU_ZONE. */ - dns_name_t cname; /* override value for ..._CNAME */ - dns_ttl_t max_policy_ttl; - dns_rpz_policy_t policy; /* DNS_RPZ_POLICY_GIVEN or override */ - isc_boolean_t recursive_only; - isc_boolean_t defined; + isc_refcount_t refs; + dns_rpz_num_t num; /* ordinal in list of policy zones */ + dns_name_t origin; /* Policy zone name */ + dns_name_t client_ip; /* DNS_RPZ_CLIENT_IP_ZONE.origin. */ + dns_name_t ip; /* DNS_RPZ_IP_ZONE.origin. */ + dns_name_t nsdname; /* DNS_RPZ_NSDNAME_ZONE.origin */ + dns_name_t nsip; /* DNS_RPZ_NSIP_ZONE.origin. */ + dns_name_t passthru; /* DNS_RPZ_PASSTHRU_NAME. */ + dns_name_t drop; /* DNS_RPZ_DROP_NAME. */ + dns_name_t tcp_only; /* DNS_RPZ_TCP_ONLY_NAME. */ + dns_name_t cname; /* override value for ..._CNAME */ + dns_ttl_t max_policy_ttl; + dns_rpz_policy_t policy; /* DNS_RPZ_POLICY_GIVEN or override */ }; /* - * Radix trees for response policy IP addresses. + * Radix tree node for response policy IP addresses + */ +typedef struct dns_rpz_cidr_node dns_rpz_cidr_node_t; + +/* + * Response policy zones known to a view. */ -typedef struct dns_rpz_cidr dns_rpz_cidr_t; +typedef struct dns_rpz_zones dns_rpz_zones_t; +struct dns_rpz_zones { + struct { + dns_rpz_zbits_t no_rd_ok; + isc_boolean_t break_dnssec; + isc_boolean_t qname_wait_recurse; + unsigned int min_ns_labels; + dns_rpz_num_t num_zones; + } p; + dns_rpz_zone_t *zones[DNS_RPZ_MAX_ZONES]; + dns_rpz_triggers_t triggers[DNS_RPZ_MAX_ZONES]; + + dns_rpz_zbits_t defined; + + /* + * The set of records for a policy zone are in one of these states: + * never loaded load_begun=0 have=0 + * during initial loading load_begun=1 have=0 + * and rbtdb->rpzsp == rbtdb->load_rpzsp + * after good load load_begun=1 have!=0 + * after failed initial load load_begun=1 have=0 + * and rbtdb->load_rpzsp == NULL + * reloading after failure load_begun=1 have=0 + * reloading after success + * main rpzs load_begun=1 have!=0 + * load rpzs load_begun=1 have=0 + */ + dns_rpz_zbits_t load_begun; + struct { + dns_rpz_zbits_t client_ipv4; + dns_rpz_zbits_t client_ipv6; + dns_rpz_zbits_t client_ip; + dns_rpz_zbits_t qname; + dns_rpz_zbits_t ipv4; + dns_rpz_zbits_t ipv6; + dns_rpz_zbits_t ip; + dns_rpz_zbits_t nsdname; + dns_rpz_zbits_t nsipv4; + dns_rpz_zbits_t nsipv6; + dns_rpz_zbits_t nsip; + dns_rpz_zbits_t qname_skip_recurse; + } have; + dns_rpz_triggers_t total_triggers; + + isc_mem_t *mctx; + isc_refcount_t refs; + /* + * One lock for short term read-only search that guarantees the + * consistency of the pointers. + * A second lock for maintenance that guarantees no other thread + * is adding or deleting nodes. + */ + isc_mutex_t search_lock; + isc_mutex_t maint_lock; + + dns_rpz_cidr_node_t *cidr; + dns_rbt_t *rbt; +}; + /* * context for finding the best policy @@ -91,22 +209,19 @@ typedef struct { unsigned int state; # define DNS_RPZ_REWRITTEN 0x0001 -# define DNS_RPZ_DONE_QNAME 0x0002 /* qname checked */ -# define DNS_RPZ_DONE_QNAME_IP 0x0004 /* IP addresses of qname checked */ -# define DNS_RPZ_DONE_NSDNAME 0x0008 /* NS name missed; checking addresses */ -# define DNS_RPZ_DONE_IPv4 0x0010 -# define DNS_RPZ_RECURSING 0x0020 -# define DNS_RPZ_HAVE_IP 0x0040 /* a policy zone has IP addresses */ -# define DNS_RPZ_HAVE_NSIPv4 0x0080 /* IPv4 NISP addresses */ -# define DNS_RPZ_HAVE_NSIPv6 0x0100 /* IPv6 NISP addresses */ -# define DNS_RPZ_HAVE_NSDNAME 0x0200 /* NS names */ +# define DNS_RPZ_DONE_CLIENT_IP 0x0002 /* client IP address checked */ +# define DNS_RPZ_DONE_QNAME 0x0004 /* qname checked */ +# define DNS_RPZ_DONE_QNAME_IP 0x0008 /* IP addresses of qname checked */ +# define DNS_RPZ_DONE_NSDNAME 0x0010 /* NS name missed; checking addresses */ +# define DNS_RPZ_DONE_IPv4 0x0020 +# define DNS_RPZ_RECURSING 0x0040 /* * Best match so far. */ struct { dns_rpz_type_t type; dns_rpz_zone_t *rpz; - dns_rpz_cidr_bits_t prefix; + dns_rpz_prefix_t prefix; dns_rpz_policy_t policy; dns_ttl_t ttl; isc_result_t result; @@ -141,10 +256,15 @@ dns_rdataset_t *sigrdataset; dns_rdatatype_t qtype; } q; - dns_name_t *qname; + /* + * p_name: current policy owner name + * r_name: recursing for this name to possible policy triggers + * f_name: saved found name from before recursion + */ + dns_name_t *p_name; dns_name_t *r_name; dns_name_t *fname; - dns_fixedname_t _qnamef; + dns_fixedname_t _p_namef; dns_fixedname_t _r_namef; dns_fixedname_t _fnamef; } dns_rpz_st_t; @@ -171,32 +291,41 @@ const char * dns_rpz_policy2str(dns_rpz_policy_t policy); -void -dns_rpz_cidr_free(dns_rpz_cidr_t **cidr); - -void -dns_rpz_view_destroy(dns_view_t *view); +dns_rpz_policy_t +dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset, + dns_name_t *selfname); 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_get(dns_rpz_cidr_t *cidr, dns_rpz_st_t *st); +dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, isc_mem_t *mctx); void -dns_rpz_cidr_deleteip(dns_rpz_cidr_t *cidr, dns_name_t *name); +dns_rpz_attach_rpzs(dns_rpz_zones_t *source, dns_rpz_zones_t **target); void -dns_rpz_cidr_addip(dns_rpz_cidr_t *cidr, dns_name_t *name); +dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp); 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_beginload(dns_rpz_zones_t **load_rpzsp, + dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num); -dns_rpz_policy_t -dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset, - dns_name_t *selfname); +isc_result_t +dns_rpz_ready(dns_rpz_zones_t *rpzs, + dns_rpz_zones_t **load_rpzsp, dns_rpz_num_t rpz_num); + +isc_result_t +dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *name); + +void +dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *name); + +dns_rpz_num_t +dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr, + dns_name_t *ip_name, dns_rpz_prefix_t *prefixp); + +dns_rpz_zbits_t +dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, dns_name_t *trig_name); ISC_LANG_ENDDECLS diff -r -u lib/dns/include/dns/view.h-orig lib/dns/include/dns/view.h --- lib/dns/include/dns/view.h-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/include/dns/view.h 2004-01-01 00:00:00.000000000 +0000 @@ -164,10 +164,7 @@ dns_acl_t * v4_aaaa_acl; dns_dns64list_t dns64; unsigned int dns64cnt; - ISC_LIST(dns_rpz_zone_t) rpz_zones; - isc_boolean_t rpz_recursive_only; - isc_boolean_t rpz_break_dnssec; - unsigned int rpz_min_ns_labels; + dns_rpz_zones_t *rpzs; /* * Configurable data for server use only, diff -r -u lib/dns/include/dns/zone.h-orig lib/dns/include/dns/zone.h --- lib/dns/include/dns/zone.h-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/include/dns/zone.h 2004-01-01 00:00:00.000000000 +0000 @@ -2081,19 +2081,20 @@ */ isc_result_t -dns_zone_rpz_enable(dns_zone_t *zone); +dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs, + dns_rpz_num_t rpz_num); /*% * Set the response policy associated with a zone. */ -isc_result_t +void dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db); /*% * If a zone is a response policy zone, mark its new database. */ -isc_boolean_t -dns_zone_get_rpz(dns_zone_t *zone); +dns_rpz_num_t +dns_zone_get_rpz_num(dns_zone_t *zone); void dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level); diff -r -u lib/dns/rbtdb.c-orig lib/dns/rbtdb.c --- lib/dns/rbtdb.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/rbtdb.c 2004-01-01 00:00:00.000000000 +0000 @@ -453,7 +453,9 @@ dns_rbt_t * tree; dns_rbt_t * nsec; dns_rbt_t * nsec3; - dns_rpz_cidr_t * rpz_cidr; + dns_rpz_zones_t *rpzs; + dns_rpz_num_t rpz_num; + dns_rpz_zones_t *load_rpzs; /* Unlocked */ unsigned int quantum; @@ -972,8 +974,18 @@ dns_stats_detach(&rbtdb->rrsetstats); #ifdef BIND9 - if (rbtdb->rpz_cidr != NULL) - dns_rpz_cidr_free(&rbtdb->rpz_cidr); + if (rbtdb->load_rpzs != NULL) { + /* + * We must be cleaning up after a failed zone loading. + */ + REQUIRE(rbtdb->rpzs != NULL && + rbtdb->rpz_num < rbtdb->rpzs->p.num_zones); + dns_rpz_detach_rpzs(&rbtdb->load_rpzs); + } + if (rbtdb->rpzs != NULL) { + REQUIRE(rbtdb->rpz_num < rbtdb->rpzs->p.num_zones); + dns_rpz_detach_rpzs(&rbtdb->rpzs); + } #endif isc_mem_put(rbtdb->common.mctx, rbtdb->node_locks, @@ -1515,11 +1527,11 @@ switch (node->nsec) { case DNS_RBT_NSEC_NORMAL: #ifdef BIND9 - if (rbtdb->rpz_cidr != NULL) { + if (rbtdb->rpzs != NULL) { dns_fixedname_init(&fname); name = dns_fixedname_name(&fname); dns_rbt_fullnamefromnode(node, name); - dns_rpz_cidr_deleteip(rbtdb->rpz_cidr, name); + dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name); } #endif result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE); @@ -1555,11 +1567,11 @@ isc_result_totext(result)); } } + result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE); #ifdef BIND9 - if (rbtdb->rpz_cidr != NULL) - dns_rpz_cidr_deleteip(rbtdb->rpz_cidr, name); + if (rbtdb->rpzs != NULL) + dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name); #endif - result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE); break; case DNS_RBT_NSEC_NSEC: result = dns_rbt_deletenode(rbtdb->nsec, node, ISC_FALSE); @@ -1573,7 +1585,7 @@ DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE, ISC_LOG_WARNING, - "delete_cnode(): " + "delete_node(): " "dns_rbt_deletenode: %s", isc_result_totext(result)); } @@ -2540,14 +2552,15 @@ result = dns_rbt_addnode(tree, name, &node); if (result == ISC_R_SUCCESS) { #ifdef BIND9 - if (tree == rbtdb->tree && rbtdb->rpz_cidr != NULL) { + if (rbtdb->rpzs != NULL && tree == rbtdb->tree) { 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); + result = dns_rpz_add(rbtdb->rpzs, + rbtdb->rpz_num, fname); } #endif dns_rbt_namefromnode(node, &nodename); @@ -4549,228 +4562,45 @@ return (result); } +#ifdef BIND9 /* - * Mark a database for response policy rewriting - * or find which RPZ data is available. + * Connect this RBTDB to the response policy zone summary data for the view. */ -#ifdef BIND9 -static isc_result_t -rpz_enabled(dns_db_t *db, dns_rpz_st_t *st) -{ - dns_rbtdb_t *rbtdb; - isc_result_t result; +static void +rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) { + dns_rbtdb_t * rbtdb; - result = ISC_R_SUCCESS; rbtdb = (dns_rbtdb_t *)db; REQUIRE(VALID_RBTDB(rbtdb)); - RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); - if (st != NULL) { - dns_rpz_enabled_get(rbtdb->rpz_cidr, st); - } else { - result = dns_rpz_new_cidr(rbtdb->common.mctx, - &rbtdb->common.origin, - &rbtdb->rpz_cidr); - } - RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); - return (result); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + REQUIRE(rbtdb->rpzs == NULL && rbtdb->rpz_num == DNS_RPZ_INVALID_NUM); + dns_rpz_attach_rpzs(rpzs, &rbtdb->rpzs); + rbtdb->rpz_num = rpz_num; + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); } /* - * 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 earliest configured policy, - * QNAME over IP over NSDNAME over NSIP, - * the longest prefix, - * the lexically smallest address. - * The caller must have already checked that any existing policy was not - * configured earlier than this policy zone and does not have a higher - * precedence type. + * Enable this RBTDB as a response policy zone. */ -static void -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_name_t *query_qname) -{ - 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; +static isc_result_t +rpz_ready(dns_db_t *db) { + dns_rbtdb_t * rbtdb; 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); - return; - } - - 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); - memmove(&ina.s_addr, rdata.data, 4); - isc_netaddr_fromin(&netaddr, &ina); - break; - case dns_rdatatype_aaaa: - INSIST(rdata.length == 16); - memmove(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; - /* - * If we already have a rule, discard this new rule if - * is not better. - * The caller has checked that st->m.rpz->num > rpz->num - * or st->m.rpz->num == rpz->num and st->m.type >= rpz_type - */ - if (st->m.policy != DNS_RPZ_POLICY_MISS && - st->m.rpz->num == rpz->num && - (st->m.type < rpz_type || - (st->m.type == rpz_type && - (st->m.prefix > prefix || - (st->m.prefix == prefix && - 0 > dns_name_rdatacompare(st->qname, qname)))))) - 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_RPZ, - DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, - "rpz_findips findnode(%s) failed: %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(rpz, - &zrdataset, - selfname); - if (rpz_policy == DNS_RPZ_POLICY_RECORD || - rpz_policy == DNS_RPZ_POLICY_WILDCNAME) - 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) { - /* - * only log DNS_RPZ_POLICY_DISABLED hits - */ - if (rpz->policy == DNS_RPZ_POLICY_DISABLED) { - if (isc_log_wouldlog(dns_lctx, - DNS_RPZ_INFO_LEVEL)) { - char qname_buf[DNS_NAME_FORMATSIZE]; - char rpz_qname_buf[DNS_NAME_FORMATSIZE]; - dns_name_format(query_qname, qname_buf, - sizeof(qname_buf)); - dns_name_format(qname, rpz_qname_buf, - sizeof(rpz_qname_buf)); - - isc_log_write(dns_lctx, - DNS_LOGCATEGORY_RPZ, - DNS_LOGMODULE_RBTDB, - DNS_RPZ_INFO_LEVEL, - "disabled rpz %s %s rewrite" - " %s via %s", - dns_rpz_type2str(rpz_type), - dns_rpz_policy2str(rpz_policy), - qname_buf, rpz_qname_buf); - } - continue; - } - - rpz_policy = rpz->policy; - } - - 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 = ISC_MIN(ttl, rpz->max_policy_ttl); - st->m.result = result; - dns_name_copy(qname, st->qname, NULL); - if ((rpz_policy == DNS_RPZ_POLICY_RECORD || - rpz_policy == DNS_RPZ_POLICY_WILDCNAME) && - 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); - st->m.version = version; - dns_zone_attach(zone, &st->m.zone); - if (dns_rdataset_isassociated(&zrdataset)) - dns_rdataset_disassociate(&zrdataset); + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + if (rbtdb->rpzs == NULL) { + INSIST(rbtdb->rpz_num == DNS_RPZ_INVALID_NUM); + result = ISC_R_SUCCESS; + } else { + result = dns_rpz_ready(rbtdb->rpzs, &rbtdb->load_rpzs, + rbtdb->rpz_num); } - - RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + return (result); } #endif @@ -6938,8 +6768,9 @@ done: #ifdef BIND9 - if (noderesult == ISC_R_SUCCESS && rbtdb->rpz_cidr != NULL) - dns_rpz_cidr_addip(rbtdb->rpz_cidr, name); + if (rbtdb->rpzs != NULL && noderesult == ISC_R_SUCCESS) + noderesult = dns_rpz_add(rbtdb->load_rpzs, rbtdb->rpz_num, + name); #endif if (noderesult == ISC_R_SUCCESS || noderesult == ISC_R_EXISTS) *nodep = node; @@ -7074,6 +6905,20 @@ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); +#ifdef BIND9 + if (rbtdb->rpzs != NULL) { + isc_result_t result; + + result = dns_rpz_beginload(&rbtdb->load_rpzs, + rbtdb->rpzs, rbtdb->rpz_num); + if (result != ISC_R_SUCCESS) { + isc_mem_put(rbtdb->common.mctx, loadctx, + sizeof(*loadctx)); + return (result); + } + } +#endif + REQUIRE((rbtdb->attributes & (RBTDB_ATTR_LOADED|RBTDB_ATTR_LOADING)) == 0); rbtdb->attributes |= RBTDB_ATTR_LOADING; @@ -7476,8 +7321,8 @@ isdnssec, NULL, #ifdef BIND9 - rpz_enabled, - rpz_findips, + rpz_attach, + rpz_ready, #else NULL, NULL, @@ -7791,6 +7636,9 @@ } rbtdb->attributes = 0; rbtdb->task = NULL; + rbtdb->rpzs = NULL; + rbtdb->load_rpzs = NULL; + rbtdb->rpz_num = DNS_RPZ_INVALID_NUM; /* * Version Initialization. diff -r -u lib/dns/rpz.c-orig lib/dns/rpz.c --- lib/dns/rpz.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/rpz.c 2004-01-01 00:00:00.000000000 +0000 @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -44,9 +45,13 @@ /* * 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. + * The radix or patricia trees are somewhat specialized to handle response + * policy addresses by representing the two sets of IP addresses and name + * server IP addresses in a single tree. One set of IP addresses is + * for rpz-ip policies or policies triggered by addresses in A or + * AAAA records in responses. + * The second set is for rpz-nsip policies or policies triggered by addresses + * in A or AAAA records for NS records that are authorities for responses. * * 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 @@ -55,7 +60,8 @@ * 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: + * + * The response policy zone canonical form of an IPv6 address is one of: * prefix.W.W.W.W.W.W.W.W * prefix.WORDS.zz * prefix.WORDS.zz.WORDS @@ -72,7 +78,7 @@ * 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 + * Names for IPv4 addresses are distinguished from IPv6 addresses by having * 5 labels all of which are numbers, and a prefix between 1 and 32. */ @@ -90,43 +96,89 @@ } dns_rpz_cidr_key_t; #define ADDR_V4MAPPED 0xffff +#define KEY_IS_IPV4(prefix,ip) ((prefix) >= 96 && (ip)->w[0] == 0 && \ + (ip)->w[1] == 0 && (ip)->w[2] == ADDR_V4MAPPED) + +#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)))) + +/* + * Get bit #n from the array of words of an IP address. + */ +#define DNS_RPZ_IP_BIT(ip, n) (1 & ((ip)->w[(n)/DNS_RPZ_CIDR_WORD_BITS] >> \ + (DNS_RPZ_CIDR_WORD_BITS \ + - 1 - ((n) % DNS_RPZ_CIDR_WORD_BITS)))) -#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)))) +/* + * A triplet of arrays of bits flagging the existence of + * client-IP, IP, and NSIP policy triggers. + */ +typedef struct dns_rpz_addr_zbits dns_rpz_addr_zbits_t; +struct dns_rpz_addr_zbits { + dns_rpz_zbits_t client_ip; + dns_rpz_zbits_t ip; + dns_rpz_zbits_t nsip; +}; -typedef struct dns_rpz_cidr_node dns_rpz_cidr_node_t; -typedef isc_uint8_t dns_rpz_cidr_flags_t; +/* + * A CIDR or radix tree node. + */ 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 */ + dns_rpz_cidr_node_t *parent; + dns_rpz_cidr_node_t *child[2]; + dns_rpz_cidr_key_t ip; + dns_rpz_prefix_t prefix; + dns_rpz_addr_zbits_t set; + dns_rpz_addr_zbits_t sum; +}; + +/* + * The data in a RBT node has two pairs of bits for policy zones. + * One pair is for the corresponding name of the node such as example.com + * and the other pair is for a wildcard child such as *.example.com. + */ +/* + * A pair of arrays of bits flagging the existence of + * QNAME and NSDNAME policy triggers. + */ +typedef struct dns_rpz_nm_zbits dns_rpz_nm_zbits_t; +struct dns_rpz_nm_zbits { + dns_rpz_zbits_t qname; + dns_rpz_zbits_t ns; }; -struct dns_rpz_cidr { - isc_mem_t *mctx; - isc_boolean_t have_nsdname; /* zone has NSDNAME record */ - dns_rpz_cidr_node_t *root; - dns_name_t ip_name; /* RPZ_IP_ZONE.origin. */ - dns_name_t nsip_name; /* RPZ_NSIP_ZONE.origin. */ - dns_name_t nsdname_name; /* RPZ_NSDNAME_ZONE.origin */ +typedef struct dns_rpz_nm_data dns_rpz_nm_data_t; +struct dns_rpz_nm_data { + dns_rpz_nm_zbits_t set; + dns_rpz_nm_zbits_t wild; }; +#if 0 +/* + * Catch a name while debugging. + */ +static void +catch_name(const dns_name_t *src_name, const char *tgt, const char *str) { + dns_fixedname_t tgt_namef; + dns_name_t *tgt_name; + + dns_fixedname_init(&tgt_namef); + tgt_name = dns_fixedname_name(&tgt_namef); + dns_name_fromstring(tgt_name, tgt, DNS_NAME_DOWNCASE, NULL); + if (dns_name_equal(src_name, tgt_name)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz hit failed: %s %s", str, tgt); + } +} +#endif + const char * dns_rpz_type2str(dns_rpz_type_t type) { switch (type) { + case DNS_RPZ_TYPE_CLIENT_IP: + return ("CLIENT-IP"); case DNS_RPZ_TYPE_QNAME: return ("QNAME"); case DNS_RPZ_TYPE_IP: @@ -138,32 +190,34 @@ case DNS_RPZ_TYPE_BAD: break; } - FATAL_ERROR(__FILE__, __LINE__, - "impossible rpz type %d", type); + FATAL_ERROR(__FILE__, __LINE__, "impossible rpz type %d", type); return ("impossible"); } dns_rpz_policy_t dns_rpz_str2policy(const char *str) { + static struct { + const char *str; + dns_rpz_policy_t policy; + } tbl[] = { + {"given", DNS_RPZ_POLICY_GIVEN}, + {"disabled", DNS_RPZ_POLICY_DISABLED}, + {"passthru", DNS_RPZ_POLICY_PASSTHRU}, + {"drop", DNS_RPZ_POLICY_DROP}, + {"tcp-only", DNS_RPZ_POLICY_TCP_ONLY}, + {"nxdomain", DNS_RPZ_POLICY_NXDOMAIN}, + {"nodata", DNS_RPZ_POLICY_NODATA}, + {"cname", DNS_RPZ_POLICY_CNAME}, + {"no-op", DNS_RPZ_POLICY_PASSTHRU}, /* old passthru */ + }; + unsigned int n; + if (str == NULL) return (DNS_RPZ_POLICY_ERROR); - if (!strcasecmp(str, "given")) - return (DNS_RPZ_POLICY_GIVEN); - if (!strcasecmp(str, "disabled")) - return (DNS_RPZ_POLICY_DISABLED); - if (!strcasecmp(str, "passthru")) - return (DNS_RPZ_POLICY_PASSTHRU); - 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); - /* - * Obsolete - */ - if (!strcasecmp(str, "no-op")) - return (DNS_RPZ_POLICY_PASSTHRU); + for (n = 0; n < sizeof(tbl)/sizeof(tbl[0]); ++n) { + if (!strcasecmp(tbl[n].str, str)) + return (tbl[n].policy); + } return (DNS_RPZ_POLICY_ERROR); } @@ -175,6 +229,12 @@ case DNS_RPZ_POLICY_PASSTHRU: str = "PASSTHRU"; break; + case DNS_RPZ_POLICY_DROP: + str = "DROP"; + break; + case DNS_RPZ_POLICY_TCP_ONLY: + str = "TCP-ONLY"; + break; case DNS_RPZ_POLICY_NXDOMAIN: str = "NXDOMAIN"; break; @@ -196,243 +256,274 @@ return (str); } -/* - * 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; - } +static int +zbit_to_num(dns_rpz_zbits_t zbit) { + dns_rpz_num_t rpz_num; - /* 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; + INSIST(zbit != 0); + rpz_num = 0; +#if DNS_RPZ_MAX_ZONES > 32 + if ((zbit & 0xffffffff00000000L) != 0) { + zbit >>= 32; + rpz_num += 32; } - - 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; +#endif + if ((zbit & 0xffff0000) != 0) { + zbit >>= 16; + rpz_num += 16; + } + if ((zbit & 0xff00) != 0) { + zbit >>= 8; + rpz_num += 8; + } + if ((zbit & 0xf0) != 0) { + zbit >>= 4; + rpz_num += 4; + } + if ((zbit & 0xc) != 0) { + zbit >>= 2; + rpz_num += 2; + } + if ((zbit & 2) != 0) + ++rpz_num; + return (rpz_num); } /* - * Forget a view's list of policy zones. + * Make a set of bit masks given one or more bits and their type. */ -void -dns_rpz_view_destroy(dns_view_t *view) { - dns_rpz_zone_t *zone; - - REQUIRE(view != NULL); +static void +make_addr_set(dns_rpz_addr_zbits_t *tgt_set, dns_rpz_zbits_t zbits, + dns_rpz_type_t type) +{ + switch (type) { + case DNS_RPZ_TYPE_CLIENT_IP: + tgt_set->client_ip = zbits; + tgt_set->ip = 0; + tgt_set->nsip = 0; + break; + case DNS_RPZ_TYPE_IP: + tgt_set->client_ip = 0; + tgt_set->ip = zbits; + tgt_set->nsip = 0; + break; + case DNS_RPZ_TYPE_NSIP: + tgt_set->client_ip = 0; + tgt_set->ip = 0; + tgt_set->nsip = zbits; + break; + default: + INSIST(0); + break; + } +} - 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->passthru)) - dns_name_free(&zone->passthru, 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)); +static void +make_nm_set(dns_rpz_nm_zbits_t *tgt_set, + dns_rpz_num_t rpz_num, dns_rpz_type_t type) +{ + switch (type) { + case DNS_RPZ_TYPE_QNAME: + tgt_set->qname = DNS_RPZ_ZBIT(rpz_num); + tgt_set->ns = 0; + break; + case DNS_RPZ_TYPE_NSDNAME: + tgt_set->qname = 0; + tgt_set->ns = DNS_RPZ_ZBIT(rpz_num); + break; + default: + INSIST(0); + break; } } /* - * Start a new radix tree for a response policy zone. + * Mark a node and all of its parents as having client-IP, IP, or NSIP data */ -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); - - cidr = isc_mem_get(mctx, sizeof(*cidr)); - if (cidr == NULL) - return (ISC_R_NOMEMORY); - memset(cidr, 0, sizeof(*cidr)); - cidr->mctx = mctx; +static void +set_sum_pair(dns_rpz_cidr_node_t *cnode) { + dns_rpz_cidr_node_t *child; + dns_rpz_addr_zbits_t sum; - 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); - } + do { + sum = cnode->set; - 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); - } + child = cnode->child[0]; + if (child != NULL) { + sum.client_ip |= child->sum.client_ip; + sum.ip |= child->sum.ip; + sum.nsip |= child->sum.nsip; + } - 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); - } + child = cnode->child[1]; + if (child != NULL) { + sum.client_ip |= child->sum.client_ip; + sum.ip |= child->sum.ip; + sum.nsip |= child->sum.nsip; + } - *rbtdb_cidr = cidr; - return (ISC_R_SUCCESS); + if (cnode->sum.client_ip == sum.client_ip && + cnode->sum.ip == sum.ip && + cnode->sum.nsip == sum.nsip) + break; + cnode->sum = sum; + cnode = cnode->parent; + } while (cnode != NULL); } -/* - * See if a policy zone has IP, NSIP, or NSDNAME rules or records. - */ -void -dns_rpz_enabled_get(dns_rpz_cidr_t *cidr, dns_rpz_st_t *st) { - if (cidr == NULL) - return; - 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->have_nsdname) - st->state |= DNS_RPZ_HAVE_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); +static void +fix_qname_skip_recurse(dns_rpz_zones_t *rpzs) { + dns_rpz_zbits_t zbits; + + /* + * Get a mask covering all policy zones that are not subordinate to + * other policy zones containing triggers that require that the + * qname be resolved before they can be checked. + */ + if (rpzs->p.qname_wait_recurse) { + zbits = 0; } else { - return (DNS_RPZ_CIDR_FG_IP | DNS_RPZ_CIDR_FG_IP_DATA); + zbits = (rpzs->have.ipv4 || rpzs->have.ipv6 || + rpzs->have.nsdname || + rpzs->have.nsipv4 || rpzs->have.nsipv6); + if (zbits == 0) { + zbits = DNS_RPZ_ALL_ZBITS; + } else { + zbits = DNS_RPZ_ZMASK(zbit_to_num(zbits)); + } } + rpzs->have.qname_skip_recurse = zbits; + + rpzs->have.client_ip = rpzs->have.client_ipv4 | rpzs->have.client_ipv6; + rpzs->have.ip = rpzs->have.ipv4 | rpzs->have.ipv6; + rpzs->have.nsip = rpzs->have.nsipv4 | rpzs->have.nsipv6; } -/* - * 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; +adj_trigger_cnt(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + dns_rpz_type_t rpz_type, + const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix, + isc_boolean_t inc) +{ + int *cnt; + dns_rpz_zbits_t *have; - 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; + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + REQUIRE(tgt_ip != NULL); + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + cnt = &rpzs->triggers[rpz_num].client_ipv4; + have = &rpzs->have.client_ipv4; + } else { + cnt = &rpzs->triggers[rpz_num].client_ipv6; + have = &rpzs->have.client_ipv6; + } + break; + case DNS_RPZ_TYPE_QNAME: + cnt = &rpzs->triggers[rpz_num].qname; + have = &rpzs->have.qname; + break; + case DNS_RPZ_TYPE_IP: + REQUIRE(tgt_ip != NULL); + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + cnt = &rpzs->triggers[rpz_num].ipv4; + have = &rpzs->have.ipv4; + } else { + cnt = &rpzs->triggers[rpz_num].ipv6; + have = &rpzs->have.ipv6; + } + break; + case DNS_RPZ_TYPE_NSDNAME: + cnt = &rpzs->triggers[rpz_num].nsdname; + have = &rpzs->have.nsdname; + break; + case DNS_RPZ_TYPE_NSIP: + REQUIRE(tgt_ip != NULL); + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + cnt = &rpzs->triggers[rpz_num].nsipv4; + have = &rpzs->have.nsipv4; + } else { + cnt = &rpzs->triggers[rpz_num].nsipv6; + have = &rpzs->have.nsipv6; + } + break; + default: + INSIST(0); + } + + if (inc) { + if (++*cnt == 1) { + *have |= DNS_RPZ_ZBIT(rpz_num); + fix_qname_skip_recurse(rpzs); + } + } else { + REQUIRE(*cnt > 0); + if (--*cnt == 0) { + *have &= ~DNS_RPZ_ZBIT(rpz_num); + fix_qname_skip_recurse(rpzs); + } } } -/* - * 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) +new_node(dns_rpz_zones_t *rpzs, + const dns_rpz_cidr_key_t *ip, dns_rpz_prefix_t prefix, + const dns_rpz_cidr_node_t *child) { - dns_rpz_cidr_node_t *node; + dns_rpz_cidr_node_t *new; int i, words, wlen; - node = isc_mem_get(cidr->mctx, sizeof(*node)); - if (node == NULL) + new = isc_mem_get(rpzs->mctx, sizeof(*new)); + if (new == NULL) return (NULL); - memset(node, 0, sizeof(*node)); + memset(new, 0, sizeof(*new)); - node->flags = flags & ~(DNS_RPZ_CIDR_FG_IP_DATA | - DNS_RPZ_CIDR_FG_NSIP_DATA); + if (child != NULL) + new->sum = child->sum; - node->bits = bits; - words = bits / DNS_RPZ_CIDR_WORD_BITS; - wlen = bits % DNS_RPZ_CIDR_WORD_BITS; + new->prefix = prefix; + words = prefix / DNS_RPZ_CIDR_WORD_BITS; + wlen = prefix % DNS_RPZ_CIDR_WORD_BITS; i = 0; while (i < words) { - node->ip.w[i] = ip->w[i]; + new->ip.w[i] = ip->w[i]; ++i; } if (wlen != 0) { - node->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen); + new->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen); ++i; } while (i < DNS_RPZ_CIDR_WORDS) - node->ip.w[i++] = 0; + new->ip.w[i++] = 0; - return (node); + return (new); } static void badname(int level, dns_name_t *name, const char *str1, const char *str2) { - char printname[DNS_NAME_FORMATSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; /* * bin/tests/system/rpz/tests.sh looks for "invalid rpz". */ - if (level < DNS_RPZ_DEBUG_QUIET - && isc_log_wouldlog(dns_lctx, level)) { - dns_name_format(name, printname, sizeof(printname)); + if (level < DNS_RPZ_DEBUG_QUIET && + isc_log_wouldlog(dns_lctx, level)) { + dns_name_format(name, namebuf, sizeof(namebuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB, level, "invalid rpz IP address \"%s\"%s%s", - printname, str1, str2); + namebuf, str1, str2); } } /* * Convert an IP address from radix tree binary (host byte order) to - * to its canonical response policy domain name and its name in the + * to its canonical response policy domain name without the origin of 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) +ip2name(const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix, + dns_name_t *base_name, dns_name_t *ip_name) { #ifndef INET6_ADDRSTRLEN #define INET6_ADDRSTRLEN 46 @@ -440,22 +531,18 @@ 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) { + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { 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)) + if (len < 0 || len > (int)sizeof(str)) return (ISC_R_FAILURE); } else { for (i = 0; i < DNS_RPZ_CIDR_WORDS; i++) { @@ -469,9 +556,9 @@ 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) { + 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++]); @@ -495,48 +582,31 @@ } } - 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); + isc__buffer_init(&buffer, str, sizeof(str)); + isc__buffer_add(&buffer, len); + result = dns_name_fromtext(ip_name, &buffer, base_name, 0, NULL); + return (result); } /* - * Decide which kind of IP address response policy zone a name is in. + * Determine the type a of a name in a response policy zone. */ static dns_rpz_type_t -set_type(dns_rpz_cidr_t *cidr, dns_name_t *name) { +type_from_name(dns_rpz_zone_t *rpz, dns_name_t *name) { - if (dns_name_issubdomain(name, &cidr->ip_name)) + if (dns_name_issubdomain(name, &rpz->ip)) return (DNS_RPZ_TYPE_IP); - /* - * Require `./configure --enable-rpz-nsip` and nsdname - * until consistency problems are resolved. - */ + if (dns_name_issubdomain(name, &rpz->client_ip)) + return (DNS_RPZ_TYPE_CLIENT_IP); + #ifdef ENABLE_RPZ_NSIP - if (dns_name_issubdomain(name, &cidr->nsip_name)) + if (dns_name_issubdomain(name, &rpz->nsip)) return (DNS_RPZ_TYPE_NSIP); #endif #ifdef ENABLE_RPZ_NSDNAME - if (dns_name_issubdomain(name, &cidr->nsdname_name)) + if (dns_name_issubdomain(name, &rpz->nsdname)) return (DNS_RPZ_TYPE_NSDNAME); #endif @@ -545,73 +615,80 @@ /* * Convert an IP address from canonical response policy domain name form - * to radix tree binary (host byte order). + * to radix tree binary (host byte order) for adding or deleting IP or NSIP + * data. */ 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) +name2ipkey(int log_level, + const dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + dns_rpz_type_t rpz_type, dns_name_t *src_name, + dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t *tgt_prefix, + dns_rpz_addr_zbits_t *new_set) { - isc_result_t result; - dns_fixedname_t fname; - dns_name_t *ipname; - char ipstr[DNS_NAME_FORMATSIZE]; + dns_rpz_zone_t *rpz; + char ip_str[DNS_NAME_FORMATSIZE]; + dns_offsets_t ip_name_offsets; + dns_fixedname_t ip_name2f; + dns_name_t ip_name, *ip_name2; const char *prefix_str, *cp, *end; char *cp2; int ip_labels; - dns_rpz_cidr_bits_t bits; - unsigned long prefix, l; + dns_rpz_prefix_t prefix; + unsigned long prefix_num, l; + isc_result_t result; int i; - /* - * Need at least enough labels for the shortest name, - * :: or 128.*.RPZ_x_ZONE.rpz.LOCALHOST. - */ + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + + make_addr_set(new_set, DNS_RPZ_ZBIT(rpz_num), rpz_type); + 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", ""); + if (rpz_type == DNS_RPZ_TYPE_QNAME) + ip_labels -= dns_name_countlabels(&rpz->origin); + else + ip_labels -= dns_name_countlabels(&rpz->nsdname); + if (ip_labels < 2) { + badname(log_level, src_name, "; too short", ""); return (ISC_R_FAILURE); } + dns_name_init(&ip_name, ip_name_offsets); + dns_name_getlabelsequence(src_name, 0, ip_labels, &ip_name); /* * Get text for the IP address */ - dns_fixedname_init(&fname); - ipname = dns_fixedname_name(&fname); - dns_name_split(src_name, dns_name_countlabels(&cidr->ip_name), - ipname, NULL); - dns_name_format(ipname, ipstr, sizeof(ipstr)); - end = &ipstr[strlen(ipstr)+1]; - prefix_str = ipstr; + dns_name_format(&ip_name, ip_str, sizeof(ip_str)); + end = &ip_str[strlen(ip_str)+1]; + prefix_str = ip_str; - prefix = strtoul(prefix_str, &cp2, 10); + prefix_num = strtoul(prefix_str, &cp2, 10); if (*cp2 != '.') { - badname(level, src_name, + badname(log_level, src_name, "; invalid leading prefix length", ""); return (ISC_R_FAILURE); } *cp2 = '\0'; - if (prefix < 1U || prefix > 128U) { - badname(level, src_name, + if (prefix_num < 1U || prefix_num > 128U) { + badname(log_level, src_name, "; invalid prefix length of ", prefix_str); return (ISC_R_FAILURE); } cp = cp2+1; - if (ip_labels == 4 && !strchr(cp, 'z')) { + if (--ip_labels == 4 && !strchr(cp, 'z')) { /* * Convert an IPv4 address * from the form "prefix.w.z.y.x" */ - if (prefix > 32U) { - badname(level, src_name, + if (prefix_num > 32U) { + badname(log_level, src_name, "; invalid IPv4 prefix length of ", prefix_str); return (ISC_R_FAILURE); } - prefix += 96; - *tgt_prefix = (dns_rpz_cidr_bits_t)prefix; + prefix_num += 96; + *tgt_prefix = (dns_rpz_prefix_t)prefix_num; tgt_ip->w[0] = 0; tgt_ip->w[1] = 0; tgt_ip->w[2] = ADDR_V4MAPPED; @@ -621,7 +698,7 @@ if (l > 255U || (*cp2 != '.' && *cp2 != '\0')) { if (*cp2 == '.') *cp2 = '\0'; - badname(level, src_name, + badname(log_level, src_name, "; invalid IPv4 octet ", cp); return (ISC_R_FAILURE); } @@ -632,7 +709,7 @@ /* * Convert a text IPv6 address. */ - *tgt_prefix = (dns_rpz_cidr_bits_t)prefix; + *tgt_prefix = (dns_rpz_prefix_t)prefix_num; for (i = 0; ip_labels > 0 && i < DNS_RPZ_CIDR_WORDS * 2; ip_labels--) { @@ -651,7 +728,7 @@ (*cp2 != '.' && *cp2 != '\0')) { if (*cp2 == '.') *cp2 = '\0'; - badname(level, src_name, + badname(log_level, src_name, "; invalid IPv6 word ", cp); return (ISC_R_FAILURE); } @@ -665,36 +742,37 @@ } } if (cp != end) { - badname(level, src_name, "", ""); + badname(log_level, src_name, "", ""); return (ISC_R_FAILURE); } /* * Check for 1s after the prefix length. */ - bits = (dns_rpz_cidr_bits_t)prefix; - while (bits < DNS_RPZ_CIDR_KEY_BITS) { + prefix = (dns_rpz_prefix_t)prefix_num; + while (prefix < 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]; + i = prefix % DNS_RPZ_CIDR_WORD_BITS; + aword = tgt_ip->w[prefix / DNS_RPZ_CIDR_WORD_BITS]; if ((aword & ~DNS_RPZ_WORD_MASK(i)) != 0) { - badname(level, src_name, + badname(log_level, src_name, "; too small prefix length of ", prefix_str); return (ISC_R_FAILURE); } - bits -= i; - bits += DNS_RPZ_CIDR_WORD_BITS; + prefix -= i; + prefix += DNS_RPZ_CIDR_WORD_BITS; } /* - * Convert the address back to a canonical policy domain name - * to ensure that it is in canonical form. + * Convert the address back to a canonical domain name + * to ensure that the original name is in canonical form. */ - result = ip2name(cidr, tgt_ip, (dns_rpz_cidr_bits_t) prefix, - type, NULL, ipname); - if (result != ISC_R_SUCCESS || !dns_name_equal(src_name, ipname)) { - badname(level, src_name, "; not canonical", ""); + dns_fixedname_init(&ip_name2f); + ip_name2 = dns_fixedname_name(&ip_name2f); + result = ip2name(tgt_ip, (dns_rpz_prefix_t)prefix_num, NULL, ip_name2); + if (result != ISC_R_SUCCESS || !dns_name_equal(&ip_name, ip_name2)) { + badname(log_level, src_name, "; not canonical", ""); return (ISC_R_FAILURE); } @@ -702,10 +780,54 @@ } /* - * Find first differing bit. + * Get trigger name and data bits for adding or deleting summary NSDNAME + * or QNAME data. */ -static int -ffbit(dns_rpz_cidr_word_t w) { +static void +name2data(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + dns_rpz_type_t rpz_type, const dns_name_t *src_name, + dns_name_t *trig_name, dns_rpz_nm_data_t *new_data) +{ + dns_rpz_zone_t *rpz; + dns_offsets_t tmp_name_offsets; + dns_name_t tmp_name; + unsigned int prefix_len, n; + + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + + /* + * Handle wildcards by putting only the parent into the + * summary RBT. The summary database only causes a check of the + * real policy zone where wildcards will be handled. + */ + if (dns_name_iswildcard(src_name)) { + prefix_len = 1; + memset(&new_data->set, 0, sizeof(new_data->set)); + make_nm_set(&new_data->wild, rpz_num, rpz_type); + } else { + prefix_len = 0; + make_nm_set(&new_data->set, rpz_num, rpz_type); + memset(&new_data->wild, 0, sizeof(new_data->wild)); + } + + dns_name_init(&tmp_name, tmp_name_offsets); + n = dns_name_countlabels(src_name); + n -= prefix_len; + if (rpz_type == DNS_RPZ_TYPE_QNAME) + n -= dns_name_countlabels(&rpz->origin); + else + n -= dns_name_countlabels(&rpz->nsdname); + dns_name_getlabelsequence(src_name, prefix_len, n, &tmp_name); + (void)dns_name_concatenate(&tmp_name, dns_rootname, trig_name, NULL); +} + +/* + * Find the first differing bit in a key (IP address) word. + */ +static inline int +ffs_keybit(dns_rpz_cidr_word_t w) { int bit; bit = DNS_RPZ_CIDR_WORD_BITS-1; @@ -731,17 +853,17 @@ } /* - * Find the first differing bit in two keys. + * Find the first differing bit in two keys (IP addresses). */ 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) +diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_prefix_t prefix1, + const dns_rpz_cidr_key_t *key2, dns_rpz_prefix_t prefix2) { dns_rpz_cidr_word_t delta; - dns_rpz_cidr_bits_t maxbit, bit; + dns_rpz_prefix_t maxbit, bit; int i; - maxbit = ISC_MIN(bits1, bits2); + maxbit = ISC_MIN(prefix1, prefix2); /* * find the first differing words @@ -751,7 +873,7 @@ i++, bit += DNS_RPZ_CIDR_WORD_BITS) { delta = key1->w[i] ^ key2->w[i]; if (delta != 0) { - bit += ffbit(delta); + bit += ffs_keybit(delta); break; } } @@ -759,133 +881,170 @@ } /* + * Given a hit while searching the radix trees, + * clear all bits for higher numbered zones. + */ +static inline dns_rpz_zbits_t +trim_zbits(dns_rpz_zbits_t zbits, dns_rpz_zbits_t found) { + dns_rpz_zbits_t x; + + /* + * Isolate the first or smallest numbered hit bit. + * Make a mask of that bit and all smaller numbered bits. + */ + x = zbits & found; + x &= (~x + 1); + x = (x << 1) - 1; + return (zbits &= x); +} + +/* * 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 + * Return ISC_R_SUCCESS, DNS_R_PARTIALMATCH, ISC_R_NOTFOUND, + * and *found=longest match node + * or with create==ISC_TRUE, ISC_R_EXISTS or 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 */ +search(dns_rpz_zones_t *rpzs, + const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix, + const dns_rpz_addr_zbits_t *tgt_set, isc_boolean_t create, + dns_rpz_cidr_node_t **found) { dns_rpz_cidr_node_t *cur, *parent, *child, *new_parent, *sibling; + dns_rpz_addr_zbits_t set; int cur_num, child_num; - dns_rpz_cidr_bits_t dbit; - dns_rpz_cidr_flags_t flags, data_flag; + dns_rpz_prefix_t dbit; 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); - + set = *tgt_set; find_result = ISC_R_NOTFOUND; - if (found != NULL) - *found = NULL; - cur = cidr->root; + *found = NULL; + cur = rpzs->cidr; 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. + * No child so we cannot go down. + * Quit with whatever we already found + * 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); + child = new_node(rpzs, tgt_ip, tgt_prefix, NULL); if (child == NULL) return (ISC_R_NOMEMORY); if (parent == NULL) - cidr->root = child; + rpzs->cidr = child; else parent->child[cur_num] = child; child->parent = parent; - set_node_flags(child, type); - if (found != NULL) - *found = cur; + child->set.client_ip |= tgt_set->client_ip; + child->set.ip |= tgt_set->ip; + child->set.nsip |= tgt_set->nsip; + set_sum_pair(child); + *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); + if ((cur->sum.client_ip & set.client_ip) == 0 && + (cur->sum.ip & set.ip) == 0 && + (cur->sum.nsip & set.nsip) == 0) { + /* + * This node has no relevant data + * and is in none of the target trees. + * Pretend it does not exist if we are not adding. + * + * If we are adding, continue down to eventually add + * a node and mark/put this node in the correct tree. + */ + if (!create) + return (find_result); + } - dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->bits); + dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->prefix); /* - * dbit <= tgt_prefix and dbit <= cur->bits always. + * dbit <= tgt_prefix and dbit <= cur->prefix always. * We are finished searching if we matched all of the target. */ if (dbit == tgt_prefix) { - if (tgt_prefix == cur->bits) { + if (tgt_prefix == cur->prefix) { /* - * The current node matches the target exactly. - * It is the answer if it has data. + * The node's key matches the target exactly. */ - if ((cur->flags & data_flag) != 0) { - if (create) - return (ISC_R_EXISTS); - if (found != NULL) - *found = cur; - return (ISC_R_SUCCESS); + if ((cur->set.client_ip & set.client_ip) != 0 || + (cur->set.ip & set.ip) != 0 || + (cur->set.nsip & set.nsip) != 0) { + /* + * It is the answer if it has data. + */ + *found = cur; + if (create) { + find_result = ISC_R_EXISTS; + } else { + find_result = ISC_R_SUCCESS; + } } else if (create) { /* - * The node had no data but does now. + * The node lacked relevant data, + * but will have it now. */ - set_node_flags(cur, type); - if (found != NULL) - *found = cur; - return (ISC_R_SUCCESS); + cur->set.client_ip |= tgt_set->client_ip; + cur->set.ip |= tgt_set->ip; + cur->set.nsip |= tgt_set->nsip; + set_sum_pair(cur); + *found = cur; + find_result = ISC_R_SUCCESS; } return (find_result); } /* - * We know tgt_prefix < cur_bits which means that + * We know tgt_prefix < cur->prefix 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); + new_parent = new_node(rpzs, tgt_ip, tgt_prefix, cur); if (new_parent == NULL) return (ISC_R_NOMEMORY); new_parent->parent = parent; if (parent == NULL) - cidr->root = new_parent; + rpzs->cidr = 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; + new_parent->set = *tgt_set; + set_sum_pair(new_parent); + *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) { + if (dbit == cur->prefix) { + if ((cur->set.client_ip & set.client_ip) != 0 || + (cur->set.ip & set.ip) != 0 || + (cur->set.nsip & set.nsip) != 0) { + /* + * We have a partial match between of all of the + * current node but only part of the target. + * Continue searching for other hits in the + * same or lower numbered trees. + */ find_result = DNS_R_PARTIALMATCH; - if (found != NULL) - *found = cur; + *found = cur; + set.client_ip = trim_zbits(set.ip, + cur->set.client_ip); + set.ip = trim_zbits(set.ip, + cur->set.ip); + set.nsip = trim_zbits(set.nsip, + cur->set.nsip); } - parent = cur; cur_num = DNS_RPZ_IP_BIT(tgt_ip, dbit); cur = cur->child[cur_num]; @@ -894,7 +1053,7 @@ /* - * dbit < tgt_prefix and dbit < cur->bits, + * dbit < tgt_prefix and dbit < cur->prefix, * 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 @@ -902,17 +1061,17 @@ if (!create) return (find_result); - sibling = new_node(cidr, tgt_ip, tgt_prefix, 0); + sibling = new_node(rpzs, tgt_ip, tgt_prefix, NULL); if (sibling == NULL) return (ISC_R_NOMEMORY); - new_parent = new_node(cidr, tgt_ip, dbit, cur->flags); + new_parent = new_node(rpzs, tgt_ip, dbit, cur); if (new_parent == NULL) { - isc_mem_put(cidr->mctx, sibling, sizeof(*sibling)); + isc_mem_put(rpzs->mctx, sibling, sizeof(*sibling)); return (ISC_R_NOMEMORY); } new_parent->parent = parent; if (parent == NULL) - cidr->root = new_parent; + rpzs->cidr = new_parent; else parent->child[cur_num] = new_parent; child_num = DNS_RPZ_IP_BIT(tgt_ip, dbit); @@ -920,129 +1079,670 @@ 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; + sibling->set = *tgt_set; + set_sum_pair(sibling); + *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. + * Add an IP address to the radix tree. */ -void -dns_rpz_cidr_addip(dns_rpz_cidr_t *cidr, dns_name_t *name) { - isc_result_t result; +static isc_result_t +add_cidr(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + dns_rpz_type_t rpz_type, dns_name_t *src_name) +{ dns_rpz_cidr_key_t tgt_ip; - dns_rpz_cidr_bits_t tgt_prefix; - dns_rpz_type_t type; - - REQUIRE(cidr != NULL); + dns_rpz_prefix_t tgt_prefix; + dns_rpz_addr_zbits_t set; + dns_rpz_cidr_node_t *found; + isc_result_t result; + result = name2ipkey(DNS_RPZ_ERROR_LEVEL, rpzs, rpz_num, rpz_type, + src_name, &tgt_ip, &tgt_prefix, &set); /* - * No worries if the new name is not an IP address. + * Log complaints about bad owner names but let the zone load. */ - type = set_type(cidr, name); - switch (type) { - case DNS_RPZ_TYPE_IP: - case DNS_RPZ_TYPE_NSIP: - break; - case DNS_RPZ_TYPE_NSDNAME: - cidr->have_nsdname = ISC_TRUE; - return; - case DNS_RPZ_TYPE_QNAME: - case DNS_RPZ_TYPE_BAD: - return; - } - result = name2ipkey(cidr, DNS_RPZ_ERROR_LEVEL, name, - type, &tgt_ip, &tgt_prefix); if (result != ISC_R_SUCCESS) - return; + return (ISC_R_SUCCESS); - result = search(cidr, &tgt_ip, tgt_prefix, type, ISC_TRUE, NULL); - if (result == ISC_R_EXISTS && - isc_log_wouldlog(dns_lctx, DNS_RPZ_ERROR_LEVEL)) - { - char printname[DNS_NAME_FORMATSIZE]; + result = search(rpzs, &tgt_ip, tgt_prefix, &set, ISC_TRUE, &found); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; /* * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". */ - dns_name_format(name, printname, sizeof(printname)); + dns_name_format(src_name, namebuf, sizeof(namebuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, - "rpz add failed; \"%s\" is a duplicate name", - printname); + "rpz add_cidr(%s) failed: %s", + namebuf, isc_result_totext(result)); + return (result); } + + adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, ISC_TRUE); + return (result); } -/* - * 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) { +static isc_result_t +add_nm(dns_rpz_zones_t *rpzs, dns_name_t *trig_name, + const dns_rpz_nm_data_t *new_data) +{ + dns_rbtnode_t *nmnode; + dns_rpz_nm_data_t *nm_data; isc_result_t result; - 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; + nmnode = NULL; + result = dns_rbt_addnode(rpzs->rbt, trig_name, &nmnode); + switch (result) { + case ISC_R_SUCCESS: + case ISC_R_EXISTS: + nm_data = nmnode->data; + if (nm_data == NULL) { + nm_data = isc_mem_get(rpzs->mctx, sizeof(*nm_data)); + if (nm_data == NULL) + return (ISC_R_NOMEMORY); + *nm_data = *new_data; + nmnode->data = nm_data; + return (ISC_R_SUCCESS); + } + break; + default: + return (result); + } /* - * Decide which kind of policy zone IP address it is, if either - * and then find its node. + * Do not count bits that are already present */ - type = set_type(cidr, name); - switch (type) { - case DNS_RPZ_TYPE_IP: - case DNS_RPZ_TYPE_NSIP: - break; - case DNS_RPZ_TYPE_NSDNAME: + if ((nm_data->set.qname & new_data->set.qname) != 0 || + (nm_data->set.ns & new_data->set.ns) != 0 || + (nm_data->wild.qname & new_data->wild.qname) != 0 || + (nm_data->wild.ns & new_data->wild.ns) != 0) { + char namebuf[DNS_NAME_FORMATSIZE]; + /* - * We cannot easily count nsdnames because - * internal rbt nodes get deleted. + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". */ - return; + dns_name_format(trig_name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz add_nm(%s): bits already set", namebuf); + return (ISC_R_EXISTS); + } + + nm_data->set.qname |= new_data->set.qname; + nm_data->set.ns |= new_data->set.ns; + nm_data->wild.qname |= new_data->wild.qname; + nm_data->wild.ns |= new_data->wild.ns; + return (ISC_R_SUCCESS); +} + +static isc_result_t +add_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + dns_rpz_type_t rpz_type, dns_name_t *src_name) +{ + dns_rpz_nm_data_t new_data; + dns_fixedname_t trig_namef; + dns_name_t *trig_name; + isc_result_t result; + + dns_fixedname_init(&trig_namef); + trig_name = dns_fixedname_name(&trig_namef); + name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &new_data); + + result = add_nm(rpzs, trig_name, &new_data); + if (result == ISC_R_SUCCESS) + adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, ISC_TRUE); + return (result); +} + +/* + * Callback to free the data for a node in the summary RBT database. + */ +static void +rpz_node_deleter(void *nm_data, void *mctx) { + isc_mem_put(mctx, nm_data, sizeof(dns_rpz_nm_data_t)); +} + +/* + * Get ready for a new set of policy zones. + */ +isc_result_t +dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, isc_mem_t *mctx) { + dns_rpz_zones_t *new; + isc_result_t result; + + REQUIRE(rpzsp != NULL && *rpzsp == NULL); + + *rpzsp = NULL; + + new = isc_mem_get(mctx, sizeof(*new)); + if (new == NULL) + return (ISC_R_NOMEMORY); + memset(new, 0, sizeof(*new)); + + result = isc_mutex_init(&new->search_lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, new, sizeof(*new)); + return (result); + } + + result = isc_mutex_init(&new->maint_lock); + if (result != ISC_R_SUCCESS) { + DESTROYLOCK(&new->search_lock); + isc_mem_put(mctx, new, sizeof(*new)); + return (result); + } + + result = isc_refcount_init(&new->refs, 1); + if (result != ISC_R_SUCCESS) { + DESTROYLOCK(&new->maint_lock); + DESTROYLOCK(&new->search_lock); + isc_mem_put(mctx, new, sizeof(*new)); + return (result); + } + + result = dns_rbt_create(mctx, rpz_node_deleter, mctx, &new->rbt); + if (result != ISC_R_SUCCESS) { + isc_refcount_decrement(&new->refs, NULL); + isc_refcount_destroy(&new->refs); + DESTROYLOCK(&new->maint_lock); + DESTROYLOCK(&new->search_lock); + isc_mem_put(mctx, new, sizeof(*new)); + return (result); + } + + isc_mem_attach(mctx, &new->mctx); + + *rpzsp = new; + return (ISC_R_SUCCESS); +} + +/* + * Free the radix tree of a response policy database. + */ +static void +cidr_free(dns_rpz_zones_t *rpzs) { + dns_rpz_cidr_node_t *cur, *child, *parent; + + cur = rpzs->cidr; + 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) + rpzs->cidr = NULL; + else + parent->child[parent->child[1] == cur] = NULL; + isc_mem_put(rpzs->mctx, cur, sizeof(*cur)); + cur = parent; + } +} + +/* + * Discard a response policy zone blob + * before discarding the overall rpz structure. + */ +static void +rpz_detach(dns_rpz_zone_t **rpzp, dns_rpz_zones_t *rpzs) { + dns_rpz_zone_t *rpz; + unsigned int refs; + + rpz = *rpzp; + *rpzp = NULL; + isc_refcount_decrement(&rpz->refs, &refs); + if (refs != 0) + return; + isc_refcount_destroy(&rpz->refs); + + if (dns_name_dynamic(&rpz->origin)) + dns_name_free(&rpz->origin, rpzs->mctx); + if (dns_name_dynamic(&rpz->client_ip)) + dns_name_free(&rpz->client_ip, rpzs->mctx); + if (dns_name_dynamic(&rpz->ip)) + dns_name_free(&rpz->ip, rpzs->mctx); + if (dns_name_dynamic(&rpz->nsdname)) + dns_name_free(&rpz->nsdname, rpzs->mctx); + if (dns_name_dynamic(&rpz->nsip)) + dns_name_free(&rpz->nsip, rpzs->mctx); + if (dns_name_dynamic(&rpz->passthru)) + dns_name_free(&rpz->passthru, rpzs->mctx); + if (dns_name_dynamic(&rpz->drop)) + dns_name_free(&rpz->drop, rpzs->mctx); + if (dns_name_dynamic(&rpz->tcp_only)) + dns_name_free(&rpz->tcp_only, rpzs->mctx); + if (dns_name_dynamic(&rpz->cname)) + dns_name_free(&rpz->cname, rpzs->mctx); + + isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz)); +} + +void +dns_rpz_attach_rpzs(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **rpzsp) { + REQUIRE(rpzsp != NULL && *rpzsp == NULL); + isc_refcount_increment(&rpzs->refs, NULL); + *rpzsp = rpzs; +} + +/* + * Forget a view's policy zones. + */ +void +dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) { + dns_rpz_zones_t *rpzs; + dns_rpz_zone_t *rpz; + dns_rpz_num_t rpz_num; + unsigned int refs; + + REQUIRE(rpzsp != NULL); + rpzs = *rpzsp; + REQUIRE(rpzs != NULL); + + *rpzsp = NULL; + isc_refcount_decrement(&rpzs->refs, &refs); + + /* + * Forget the last of view's rpz machinery after the last reference. + */ + if (refs == 0) { + for (rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES; ++rpz_num) { + rpz = rpzs->zones[rpz_num]; + rpzs->zones[rpz_num] = NULL; + if (rpz != NULL) + rpz_detach(&rpz, rpzs); + } + + cidr_free(rpzs); + dns_rbt_destroy(&rpzs->rbt); + DESTROYLOCK(&rpzs->maint_lock); + DESTROYLOCK(&rpzs->search_lock); + isc_refcount_destroy(&rpzs->refs); + isc_mem_putanddetach(&rpzs->mctx, rpzs, sizeof(*rpzs)); + } +} + +/* + * Create empty summary database to load one zone. + * The RBTDB write tree lock must be held. + */ +isc_result_t +dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp, + dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) +{ + dns_rpz_zones_t *load_rpzs; + dns_rpz_zone_t *rpz; + dns_rpz_zbits_t tgt; + isc_result_t result; + + REQUIRE(rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + + /* + * When reloading a zone, there are usually records among the summary + * data for the zone. Some of those records might be deleted by the + * reloaded zone data. To deal with that case: + * reload the new zone data into a new blank summary database + * if the reload fails, discard the new summary database + * if the new zone data is acceptable, copy the records for the + * other zones into the new summary database and replace the + * old summary database with the new. + * + * At the first attempt to load a zone, there is no summary data + * for the zone and so no records that need to be deleted. + * This is also the most common case of policy zone loading. + * Most policy zone maintenance should be by incremental changes + * and so by the addition and deletion of individual records. + * Detect that case and load records the first time into the + * operational summary database + */ + tgt = DNS_RPZ_ZBIT(rpz_num); + LOCK(&rpzs->maint_lock); + LOCK(&rpzs->search_lock); + if ((rpzs->load_begun & tgt) == 0) { + /* + * There is no existing version of the target zone. + */ + rpzs->load_begun |= tgt; + dns_rpz_attach_rpzs(rpzs, load_rpzsp); + UNLOCK(&rpzs->search_lock); + UNLOCK(&rpzs->maint_lock); + + } else { + UNLOCK(&rpzs->search_lock); + UNLOCK(&rpzs->maint_lock); + + result = dns_rpz_new_zones(load_rpzsp, rpzs->mctx); + if (result != ISC_R_SUCCESS) + return (result); + load_rpzs = *load_rpzsp; + load_rpzs->p.num_zones = rpzs->p.num_zones; + load_rpzs->total_triggers = rpzs->total_triggers; + memcpy(load_rpzs->triggers, rpzs->triggers, + sizeof(load_rpzs->triggers)); + memset(&load_rpzs->triggers[rpz_num], 0, + sizeof(load_rpzs->triggers[rpz_num])); + load_rpzs->zones[rpz_num] = rpz; + isc_refcount_increment(&rpz->refs, NULL); + } + + return (ISC_R_SUCCESS); +} + +static void +fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) { + dns_rpz_num_t n; + const dns_rpz_zone_t *rpz; + dns_rpz_triggers_t old_totals; + dns_rpz_zbits_t zbit; + char namebuf[DNS_NAME_FORMATSIZE]; + +# define SET_TRIG(n, zbit, type) \ + if (rpzs->triggers[n].type == 0) { \ + rpzs->have.type &= ~zbit; \ + } else { \ + rpzs->total_triggers.type += rpzs->triggers[n].type; \ + rpzs->have.type |= zbit; \ + } + + memcpy(&old_totals, &rpzs->total_triggers, sizeof(old_totals)); + memset(&rpzs->total_triggers, 0, sizeof(rpzs->total_triggers)); + for (n = 0; n < rpzs->p.num_zones; ++n) { + rpz = rpzs->zones[n]; + zbit = DNS_RPZ_ZBIT(n); + SET_TRIG(n, zbit, client_ipv4); + SET_TRIG(n, zbit, client_ipv6); + SET_TRIG(n, zbit, qname); + SET_TRIG(n, zbit, ipv4); + SET_TRIG(n, zbit, ipv6); + SET_TRIG(n, zbit, nsdname); + SET_TRIG(n, zbit, nsipv4); + SET_TRIG(n, zbit, nsipv6); + } + + fix_qname_skip_recurse(rpzs); + + dns_name_format(&rpzs->zones[rpz_num]->origin, + namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_INFO_LEVEL, + "(re)loading policy zone '%s' changed from" + " %d to %d qname, %d to %d nsdname," + " %d to %d IP, %d to %d NSIP entries", + namebuf, + old_totals.qname, rpzs->total_triggers.qname, + old_totals.nsdname, rpzs->total_triggers.nsdname, + old_totals.ipv4 + old_totals.ipv6, + rpzs->total_triggers.ipv4 + rpzs->total_triggers.ipv6, + old_totals.nsipv4 + old_totals.nsipv6, + rpzs->total_triggers.nsipv4 + rpzs->total_triggers.nsipv6); + +# undef SET_TRIG +} + +/* + * Finish loading one zone. + * The RBTDB write tree lock must be held. + */ +isc_result_t +dns_rpz_ready(dns_rpz_zones_t *rpzs, + dns_rpz_zones_t **load_rpzsp, dns_rpz_num_t rpz_num) +{ + dns_rpz_zones_t *load_rpzs; + const dns_rpz_cidr_node_t *cnode, *next_cnode, *parent_cnode; + dns_rpz_cidr_node_t *found; + dns_rpz_zbits_t new_bit; + dns_rpz_addr_zbits_t new_ip; + dns_rbt_t *rbt; + dns_rbtnodechain_t chain; + dns_rbtnode_t *nmnode; + dns_rpz_nm_data_t *nm_data, new_data; + dns_fixedname_t labelf, originf, namef; + dns_name_t *label, *origin, *name; + isc_result_t result; + + INSIST(rpzs != NULL); + LOCK(&rpzs->maint_lock); + load_rpzs = *load_rpzsp; + INSIST(load_rpzs != NULL); + + if (load_rpzs == rpzs) { + /* + * This is a successful initial zone loading, + * perhaps for a new instance of a view. + */ + fix_triggers(rpzs, rpz_num); + UNLOCK(&rpzs->maint_lock); + dns_rpz_detach_rpzs(load_rpzsp); + return (ISC_R_SUCCESS); + } + + LOCK(&load_rpzs->maint_lock); + LOCK(&load_rpzs->search_lock); + + /* + * Unless there is only one policy zone, copy the other policy zones + * from the old policy structure to the new summary databases. + */ + if (rpzs->p.num_zones > 1) { + new_bit = ~DNS_RPZ_ZBIT(rpz_num); + + /* + * Copy to the radix tree. + */ + for (cnode = rpzs->cidr; cnode != NULL; cnode = next_cnode) { + new_ip.ip = cnode->set.ip & new_bit; + new_ip.client_ip = cnode->set.client_ip & new_bit; + new_ip.nsip = cnode->set.nsip & new_bit; + if (new_ip.client_ip != 0 || + new_ip.ip != 0 || + new_ip.nsip != 0) { + result = search(load_rpzs, + &cnode->ip, cnode->prefix, + &new_ip, ISC_TRUE, &found); + if (result == ISC_R_NOMEMORY) + goto unlock_and_detach; + INSIST(result == ISC_R_SUCCESS); + } + /* + * Do down and to the left as far as possible. + */ + next_cnode = cnode->child[0]; + if (next_cnode != NULL) + continue; + /* + * Go up until we find a branch to the right where + * we previously took the branch to the left. + */ + for (;;) { + parent_cnode = cnode->parent; + if (parent_cnode == NULL) + break; + if (parent_cnode->child[0] == cnode) { + next_cnode = parent_cnode->child[1]; + if (next_cnode != NULL) + break; + } + cnode = parent_cnode; + } + } + + /* + * Copy to the summary RBT. + */ + dns_fixedname_init(&namef); + name = dns_fixedname_name(&namef); + dns_fixedname_init(&labelf); + label = dns_fixedname_name(&labelf); + dns_fixedname_init(&originf); + origin = dns_fixedname_name(&originf); + dns_rbtnodechain_init(&chain, NULL); + result = dns_rbtnodechain_first(&chain, rpzs->rbt, NULL, NULL); + while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) { + result = dns_rbtnodechain_current(&chain, label, origin, + &nmnode); + INSIST(result == ISC_R_SUCCESS); + nm_data = nmnode->data; + if (nm_data != NULL) { + new_data.set.qname = (nm_data->set.qname & + new_bit); + new_data.set.ns = nm_data->set.ns & new_bit; + new_data.wild.qname = (nm_data->wild.qname & + new_bit); + new_data.wild.ns = nm_data->wild.ns & new_bit; + if (new_data.set.qname != 0 || + new_data.set.ns != 0 || + new_data.wild.qname != 0 || + new_data.wild.ns != 0) { + result = dns_name_concatenate(label, + origin, name, NULL); + INSIST(result == ISC_R_SUCCESS); + result = add_nm(load_rpzs, name, + &new_data); + if (result != ISC_R_SUCCESS) + goto unlock_and_detach; + } + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + } + if (result != ISC_R_NOMORE && result != ISC_R_NOTFOUND) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "dns_rpz_ready(): unexpected %s", + isc_result_totext(result)); + goto unlock_and_detach; + } + } + + fix_triggers(load_rpzs, rpz_num); + + /* + * Exchange the summary databases. + */ + LOCK(&rpzs->search_lock); + + found = rpzs->cidr; + rpzs->cidr = load_rpzs->cidr; + load_rpzs->cidr = found; + + rbt = rpzs->rbt; + rpzs->rbt = load_rpzs->rbt; + load_rpzs->rbt = rbt; + + rpzs->total_triggers = load_rpzs->total_triggers; + + UNLOCK(&rpzs->search_lock); + + result = ISC_R_SUCCESS; + + unlock_and_detach: + UNLOCK(&rpzs->maint_lock); + UNLOCK(&load_rpzs->search_lock); + UNLOCK(&load_rpzs->maint_lock); + dns_rpz_detach_rpzs(load_rpzsp); + return (result); +} + +/* + * Add an IP address to the radix tree or a name to the summary database. + */ +isc_result_t +dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *src_name) +{ + dns_rpz_zone_t *rpz; + dns_rpz_type_t rpz_type; + isc_result_t result = ISC_R_FAILURE; + + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + + rpz_type = type_from_name(rpz, src_name); + + LOCK(&rpzs->maint_lock); + LOCK(&rpzs->search_lock); + + switch (rpz_type) { case DNS_RPZ_TYPE_QNAME: + case DNS_RPZ_TYPE_NSDNAME: + result = add_name(rpzs, rpz_num, rpz_type, src_name); + break; + case DNS_RPZ_TYPE_CLIENT_IP: + case DNS_RPZ_TYPE_IP: + case DNS_RPZ_TYPE_NSIP: + result = add_cidr(rpzs, rpz_num, rpz_type, src_name); + break; case DNS_RPZ_TYPE_BAD: - return; + break; } + UNLOCK(&rpzs->search_lock); + UNLOCK(&rpzs->maint_lock); + return (result); +} + +/* + * Remove an IP address from the radix tree. + */ +static void +del_cidr(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + dns_rpz_type_t rpz_type, dns_name_t *src_name) +{ + isc_result_t result; + dns_rpz_cidr_key_t tgt_ip; + dns_rpz_prefix_t tgt_prefix; + dns_rpz_addr_zbits_t tgt_set; + dns_rpz_cidr_node_t *tgt, *parent, *child; + /* - * Do not get excited about the deletion of interior rbt nodes. + * Do not worry about invalid rpz IP address names. If we + * are here, then something relevant was added and so was + * valid. Invalid names here are usually internal RBTDB nodes. */ - result = name2ipkey(cidr, DNS_RPZ_DEBUG_QUIET, name, - type, &tgt_ip, &tgt_prefix); + result = name2ipkey(DNS_RPZ_DEBUG_QUIET, rpzs, rpz_num, rpz_type, + src_name, &tgt_ip, &tgt_prefix, &tgt_set); if (result != ISC_R_SUCCESS) return; - result = search(cidr, &tgt_ip, tgt_prefix, type, ISC_FALSE, &tgt); + result = search(rpzs, &tgt_ip, tgt_prefix, &tgt_set, ISC_FALSE, &tgt); if (result != ISC_R_SUCCESS) { - badname(DNS_RPZ_ERROR_LEVEL, name, "; missing rpz node", ""); + INSIST(result == ISC_R_NOTFOUND || + result == DNS_R_PARTIALMATCH); + /* + * Do not worry about missing summary RBT nodes that probably + * correspond to RBTDB nodes that were implicit RBT nodes + * that were later added for (often empty) wildcards + * and then to the RBTDB deferred cleanup list. + */ return; } /* * Mark the node and its parents to reflect the deleted IP address. + * Do not count bits that are already clear for internal RBTDB nodes. */ - 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; - } + tgt_set.client_ip &= tgt->set.client_ip; + tgt_set.ip &= tgt->set.ip; + tgt_set.nsip &= tgt->set.nsip; + tgt->set.client_ip &= ~tgt_set.client_ip; + tgt->set.ip &= ~tgt_set.ip; + tgt->set.nsip &= ~tgt_set.nsip; + set_sum_pair(tgt); + + adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, ISC_FALSE); /* * We might need to delete 2 nodes. @@ -1054,13 +1754,14 @@ */ if ((child = tgt->child[0]) != NULL) { if (tgt->child[1] != NULL) - return; + break; } else { child = tgt->child[1]; } - if ((tgt->flags & (DNS_RPZ_CIDR_FG_IP_DATA | - DNS_RPZ_CIDR_FG_NSIP_DATA)) != 0) - return; + if (tgt->set.client_ip != 0 || + tgt->set.ip != 0 || + tgt->set.nsip != 0) + break; /* * Replace the pointer to this node in the parent with @@ -1068,7 +1769,7 @@ */ parent = tgt->parent; if (parent == NULL) { - cidr->root = child; + rpzs->cidr = child; } else { parent->child[parent->child[1] == tgt] = child; } @@ -1077,26 +1778,144 @@ */ if (child != NULL) child->parent = parent; - isc_mem_put(cidr->mctx, tgt, sizeof(*tgt)); + isc_mem_put(rpzs->mctx, tgt, sizeof(*tgt)); tgt = parent; } while (tgt != NULL); } +static void +del_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + dns_rpz_type_t rpz_type, dns_name_t *src_name) +{ + char namebuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t trig_namef; + dns_name_t *trig_name; + dns_rbtnode_t *nmnode; + dns_rpz_nm_data_t *nm_data, del_data; + isc_result_t result; + + dns_fixedname_init(&trig_namef); + trig_name = dns_fixedname_name(&trig_namef); + name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &del_data); + + /* + * No need for a summary database of names with only 1 policy zone. + */ + if (rpzs->p.num_zones <= 1) { + adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, ISC_FALSE); + return; + } + + nmnode = NULL; + result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, NULL, 0, + NULL, NULL); + if (result != ISC_R_SUCCESS) { + /* + * Do not worry about missing summary RBT nodes that probably + * correspond to RBTDB nodes that were implicit RBT nodes + * that were later added for (often empty) wildcards + * and then to the RBTDB deferred cleanup list. + */ + if (result == ISC_R_NOTFOUND) + return; + dns_name_format(src_name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz del_name(%s) node search failed: %s", + namebuf, isc_result_totext(result)); + return; + } + + nm_data = nmnode->data; + INSIST(nm_data != NULL); + + /* + * Do not count bits that next existed for RBT nodes that would we + * would not have found in a summary for a single RBTDB tree. + */ + del_data.set.qname &= nm_data->set.qname; + del_data.set.ns &= nm_data->set.ns; + del_data.wild.qname &= nm_data->wild.qname; + del_data.wild.ns &= nm_data->wild.ns; + + nm_data->set.qname &= ~del_data.set.qname; + nm_data->set.ns &= ~del_data.set.ns; + nm_data->wild.qname &= ~del_data.wild.qname; + nm_data->wild.ns &= ~del_data.wild.ns; + + if (nm_data->set.qname == 0 && nm_data->set.ns == 0 && + nm_data->wild.qname == 0 && nm_data->wild.ns == 0) { + result = dns_rbt_deletenode(rpzs->rbt, nmnode, ISC_FALSE); + if (result != ISC_R_SUCCESS) { + /* + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + */ + dns_name_format(src_name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz del_name(%s) node delete failed: %s", + namebuf, isc_result_totext(result)); + } + } + + adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, ISC_FALSE); +} + /* - * 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 + * Remove an IP address from the radix tree or a name from the summary database. */ -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) +void +dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + dns_name_t *src_name) { + dns_rpz_zone_t *rpz; + dns_rpz_type_t rpz_type; + + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + + rpz_type = type_from_name(rpz, src_name); + + LOCK(&rpzs->maint_lock); + LOCK(&rpzs->search_lock); + + switch (rpz_type) { + case DNS_RPZ_TYPE_QNAME: + case DNS_RPZ_TYPE_NSDNAME: + del_name(rpzs, rpz_num, rpz_type, src_name); + break; + case DNS_RPZ_TYPE_CLIENT_IP: + case DNS_RPZ_TYPE_IP: + case DNS_RPZ_TYPE_NSIP: + del_cidr(rpzs, rpz_num, rpz_type, src_name); + break; + case DNS_RPZ_TYPE_BAD: + break; + } + + UNLOCK(&rpzs->search_lock); + UNLOCK(&rpzs->maint_lock); +} + +/* + * Search the summary radix tree to get a relative owner name in a + * policy zone relevant to a triggering IP address. + * rpz_type and zbits limit the search for IP address netaddr + * return the policy zone's number or DNS_RPZ_INVALID_NUM + * ip_name is the relative owner name found and + * *prefixp is its prefix length. + */ +dns_rpz_num_t +dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr, + dns_name_t *ip_name, dns_rpz_prefix_t *prefixp) { dns_rpz_cidr_key_t tgt_ip; - isc_result_t result; + dns_rpz_addr_zbits_t tgt_set; dns_rpz_cidr_node_t *found; + isc_result_t result; + dns_rpz_num_t rpz_num; int i; /* @@ -1107,29 +1926,163 @@ tgt_ip.w[1] = 0; tgt_ip.w[2] = ADDR_V4MAPPED; tgt_ip.w[3] = ntohl(netaddr->type.in.s_addr); + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + zbits &= rpzs->have.client_ipv4; + break; + case DNS_RPZ_TYPE_IP: + zbits &= rpzs->have.ipv4; + break; + case DNS_RPZ_TYPE_NSIP: + zbits &= rpzs->have.nsipv4; + break; + default: + INSIST(0); + break; + } } 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. + * but some people object. */ - memmove(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w)); + 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]); } + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + zbits &= rpzs->have.client_ipv6; + break; + case DNS_RPZ_TYPE_IP: + zbits &= rpzs->have.ipv6; + break; + case DNS_RPZ_TYPE_NSIP: + zbits &= rpzs->have.nsipv6; + break; + default: + INSIST(0); + break; + } } else { - return (ISC_R_NOTFOUND); + return (DNS_RPZ_INVALID_NUM); } - result = search(cidr, &tgt_ip, 128, type, ISC_FALSE, &found); - if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) - return (result); + if (zbits == 0) + return (DNS_RPZ_INVALID_NUM); + make_addr_set(&tgt_set, zbits, rpz_type); + + LOCK(&rpzs->search_lock); + result = search(rpzs, &tgt_ip, 128, &tgt_set, ISC_FALSE, &found); + if (result == ISC_R_NOTFOUND) { + /* + * There are no eligible zones for this IP address. + */ + UNLOCK(&rpzs->search_lock); + return (DNS_RPZ_INVALID_NUM); + } - *prefix = found->bits; - return (ip2name(cidr, &found->ip, found->bits, type, - canon_name, search_name)); + /* + * Construct the trigger name for the longest matching trigger + * in the first eligible zone with a match. + */ + *prefixp = found->prefix; + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + rpz_num = zbit_to_num(found->set.client_ip & tgt_set.client_ip); + break; + case DNS_RPZ_TYPE_IP: + rpz_num = zbit_to_num(found->set.ip & tgt_set.ip); + break; + case DNS_RPZ_TYPE_NSIP: + rpz_num = zbit_to_num(found->set.nsip & tgt_set.nsip); + break; + default: + INSIST(0); + break; + } + result = ip2name(&found->ip, found->prefix, dns_rootname, ip_name); + UNLOCK(&rpzs->search_lock); + if (result != ISC_R_SUCCESS) { + /* + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz ip2name() failed: %s", + isc_result_totext(result)); + return (DNS_RPZ_INVALID_NUM); + } + return (rpz_num); +} + +/* + * Search the summary radix tree for policy zones with triggers matching + * a name. + */ +dns_rpz_zbits_t +dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, dns_name_t *trig_name) +{ + char namebuf[DNS_NAME_FORMATSIZE]; + dns_rbtnode_t *nmnode; + const dns_rpz_nm_data_t *nm_data; + dns_rpz_zbits_t found_zbits; + isc_result_t result; + + if (zbits == 0) + return (0); + + found_zbits = 0; + + LOCK(&rpzs->search_lock); + + nmnode = NULL; + result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, NULL, + DNS_RBTFIND_EMPTYDATA, NULL, NULL); + switch (result) { + case ISC_R_SUCCESS: + nm_data = nmnode->data; + if (nm_data != NULL) { + if (rpz_type == DNS_RPZ_TYPE_QNAME) + found_zbits = nm_data->set.qname; + else + found_zbits = nm_data->set.ns; + } + nmnode = nmnode->parent; + /* fall thru */ + case DNS_R_PARTIALMATCH: + while (nmnode != NULL) { + nm_data = nmnode->data; + if (nm_data != NULL) { + if (rpz_type == DNS_RPZ_TYPE_QNAME) + found_zbits |= nm_data->wild.qname; + else + found_zbits |= nm_data->wild.ns; + } + nmnode = nmnode->parent; + } + break; + + case ISC_R_NOTFOUND: + break; + + default: + /* + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + */ + dns_name_format(trig_name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "dns_rpz_find_name(%s) failed: %s", + namebuf, isc_result_totext(result)); + break; + } + + UNLOCK(&rpzs->search_lock); + return (zbits & found_zbits); } /* @@ -1144,10 +2097,10 @@ isc_result_t result; result = dns_rdataset_first(rdataset); - RUNTIME_CHECK(result == ISC_R_SUCCESS); + INSIST(result == ISC_R_SUCCESS); dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &cname, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); + INSIST(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); /* @@ -1174,7 +2127,19 @@ } /* - * CNAME PASSTHRU.origin means "do not rewrite. + * CNAME rpz-tcp-only. means "send truncated UDP responses." + */ + if (dns_name_equal(&cname.cname, &rpz->tcp_only)) + return (DNS_RPZ_POLICY_TCP_ONLY); + + /* + * CNAME rpz-drop. means "do not respond." + */ + if (dns_name_equal(&cname.cname, &rpz->drop)) + return (DNS_RPZ_POLICY_DROP); + + /* + * CNAME rpz-passthru. means "do not rewrite." */ if (dns_name_equal(&cname.cname, &rpz->passthru)) return (DNS_RPZ_POLICY_PASSTHRU); diff -r -u lib/dns/view.c-orig lib/dns/view.c --- lib/dns/view.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/view.c 2004-01-01 00:00:00.000000000 +0000 @@ -197,9 +197,7 @@ view->maxbits = 0; view->v4_aaaa = dns_v4_aaaa_ok; view->v4_aaaa_acl = NULL; - ISC_LIST_INIT(view->rpz_zones); - view->rpz_recursive_only = ISC_TRUE; - view->rpz_break_dnssec = ISC_FALSE; + view->rpzs = NULL; dns_fixedname_init(&view->dlv_fixed); view->managed_keys = NULL; view->redirect = NULL; @@ -336,7 +334,8 @@ dns_acache_putdb(view->acache, view->cachedb); dns_acache_detach(&view->acache); } - dns_rpz_view_destroy(view); + if (view->rpzs != NULL) + dns_rpz_detach_rpzs(&view->rpzs); #ifdef USE_RRL dns_rrl_view_destroy(view); #else /* USE_RRL */ diff -r -u lib/dns/xfrin.c-orig lib/dns/xfrin.c --- lib/dns/xfrin.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/xfrin.c 2004-01-01 00:00:00.000000000 +0000 @@ -279,7 +279,7 @@ 0, NULL, /* XXX guess */ dbp); if (result == ISC_R_SUCCESS) - result = dns_zone_rpz_enable_db(xfr->zone, *dbp); + dns_zone_rpz_enable_db(xfr->zone, *dbp); return (result); } diff -r -u lib/dns/zone.c-orig lib/dns/zone.c --- lib/dns/zone.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/dns/zone.c 2004-01-01 00:00:00.000000000 +0000 @@ -357,9 +357,10 @@ isc_boolean_t added; /*% - * whether this is a response policy zone + * response policy data to be relayed to the database */ - isc_boolean_t is_rpz; + dns_rpz_zones_t *rpzs; + dns_rpz_num_t rpz_num; /*% * Serial number update method. @@ -940,7 +941,8 @@ zone->nodes = 100; zone->privatetype = (dns_rdatatype_t)0xffffU; zone->added = ISC_FALSE; - zone->is_rpz = ISC_FALSE; + zone->rpzs = NULL; + zone->rpz_num = DNS_RPZ_INVALID_NUM; ISC_LIST_INIT(zone->forwards); zone->raw = NULL; zone->secure = NULL; @@ -1043,6 +1045,13 @@ zone_detachdb(zone); if (zone->acache != NULL) dns_acache_detach(&zone->acache); +#ifdef BIND9 + if (zone->rpzs != NULL) { + REQUIRE(zone->rpz_num < zone->rpzs->p.num_zones); + dns_rpz_detach_rpzs(&zone->rpzs); + zone->rpz_num = DNS_RPZ_INVALID_NUM; + } +#endif zone_freedbargs(zone); RUNTIME_CHECK(dns_zone_setmasterswithkeys(zone, NULL, NULL, 0) == ISC_R_SUCCESS); @@ -1535,7 +1544,9 @@ * Set the response policy index and information for a zone. */ isc_result_t -dns_zone_rpz_enable(dns_zone_t *zone) { +dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs, + dns_rpz_num_t rpz_num) +{ /* * Only RBTDB zones can be used for response policy zones, * because only they have the code to load the create the summary data. @@ -1546,26 +1557,37 @@ strcmp(zone->db_argv[0], "rbt64") != 0) return (ISC_R_NOTIMPLEMENTED); - zone->is_rpz = ISC_TRUE; + /* + * This must happen only once or be redundant. + */ + LOCK_ZONE(zone); + if (zone->rpzs != NULL) { + REQUIRE(zone->rpzs == rpzs && zone->rpz_num == rpz_num); + } else { + REQUIRE(zone->rpz_num == DNS_RPZ_INVALID_NUM); + dns_rpz_attach_rpzs(rpzs, &zone->rpzs); + zone->rpz_num = rpz_num; + } + rpzs->defined |= DNS_RPZ_ZBIT(rpz_num); + UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } -isc_boolean_t -dns_zone_get_rpz(dns_zone_t *zone) { - return (zone->is_rpz); +dns_rpz_num_t +dns_zone_get_rpz_num(dns_zone_t *zone) { + return (zone->rpz_num); } /* * If a zone is a response policy zone, mark its new database. */ -isc_result_t +void dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db) { -#ifdef BIND9 - if (zone->is_rpz) - return (dns_db_rpz_enabled(db, NULL)); -#endif - return (ISC_R_SUCCESS); + if (zone->rpz_num != DNS_RPZ_INVALID_NUM) { + REQUIRE(zone->rpzs != NULL); + dns_db_rpz_attach(db, zone->rpzs, zone->rpz_num); + } } static isc_result_t @@ -2025,9 +2047,7 @@ isc_result_t tresult; unsigned int options; - result = dns_zone_rpz_enable_db(zone, db); - if (result != ISC_R_SUCCESS) - return (result); + dns_zone_rpz_enable_db(zone, db); options = get_master_options(zone); if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MANYERRORS)) options |= DNS_MASTER_MANYERRORS; @@ -4210,6 +4230,11 @@ if (result != ISC_R_SUCCESS) goto cleanup; } else { +#ifdef BIND9 + result = dns_db_rpz_ready(db); + if (result != ISC_R_SUCCESS) + goto cleanup; +#endif zone_attachdb(zone, db); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); DNS_ZONE_SETFLAG(zone, @@ -13455,6 +13480,12 @@ if (inline_raw(zone)) REQUIRE(LOCKED_ZONE(zone->secure)); +#ifdef BIND9 + result = dns_db_rpz_ready(db); + if (result != ISC_R_SUCCESS) + return (result); +#endif + result = zone_get_from_db(zone, db, &nscount, &soacount, NULL, NULL, NULL, NULL, NULL, NULL); if (result == ISC_R_SUCCESS) { diff -r -u lib/isccfg/namedconf.c-orig lib/isccfg/namedconf.c --- lib/isccfg/namedconf.c-orig 2004-01-01 00:00:00.000000000 +0000 +++ lib/isccfg/namedconf.c 2004-01-01 00:00:00.000000000 +0000 @@ -1054,11 +1054,12 @@ /*% * response-policy { - * zone [ policy (given|disabled|passthru| + * zone [ policy (given|disabled|passthru|drop|tcp-only| * nxdomain|nodata|cname ) ] * [ recursive-only yes|no ] [ max-policy-ttl number ] ; * } [ recursive-only yes|no ] [ max-policy-ttl number ] ; - * [ break-dnssec yes|no ] [ min-ns-dots number ] ; + * [ break-dnssec yes|no ] [ min-ns-dots number ] + * [ qname-wait-recurse yes|no ] */ static void @@ -1083,7 +1084,7 @@ /* * Parse - * given|disabled|passthru|nxdomain|nodata|cname + * given|disabled|passthru|drop|tcp-only|nxdomain|nodata|cname */ static isc_result_t cfg_parse_rpz_policy(cfg_parser_t *pctx, const cfg_type_t *type, @@ -1214,9 +1215,12 @@ doc_keyvalue, &cfg_rep_string, &zone_kw }; +/* + * "no-op" is an obsolete equivalent of "passthru". + */ static const char *rpz_policies[] = { - "given", "disabled", "passthru", "no-op", "nxdomain", "nodata", - "cname", NULL + "given", "disabled", "passthru", "no-op", "drop", "tcp-only", + "nxdomain", "nodata", "cname", NULL }; static cfg_type_t cfg_type_rpz_policy_name = { "policy name", cfg_parse_enum, cfg_print_ustring, @@ -1261,6 +1265,7 @@ { "break-dnssec", &cfg_type_boolean, 0 }, { "max-policy-ttl", &cfg_type_uint32, 0 }, { "min-ns-dots", &cfg_type_uint32, 0 }, + { "qname-wait-recurse", &cfg_type_boolean, 0 }, { NULL, NULL, 0 } }; static cfg_type_t cfg_type_rpz = { diff -r -u version-orig version --- version-orig 2004-01-01 00:00:00.000000000 +0000 +++ version 2004-01-01 00:00:00.000000000 +0000 @@ -7,7 +7,7 @@ DESCRIPTION="(Extended Support Version)" MAJORVER=9 MINORVER=9 -PATCHVER=5 +PATCHVER=5-rpz2+rl.14038.05 RELEASETYPE=-P RELEASEVER=1 EXTENSIONS=