diff --git a/CHANGES b/CHANGES index 95d9a03b4e..c3003f1179 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +5410. [func] Add the ability to specify per-type record count + limits in an "update-policy" statement, which + are enforced when adding records via UPDATE. + [GL #1657] + 5409. [performance] When looking up NSEC3 data in a zone database, skip the check for empty non-terminal nodes; the NSEC3 tree doesn't have any. [GL #1834] diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 7cad6c6b7a..785d392e7b 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -237,7 +237,7 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, dns_ssumatchtype_t mtype = dns_ssumatchtype_name; dns_fixedname_t fname, fident; isc_buffer_t b; - dns_rdatatype_t *types; + dns_ssuruletype_t *types; unsigned int i, n; str = cfg_obj_asstring(mode); @@ -290,7 +290,7 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, if (n == 0) { types = NULL; } else { - types = isc_mem_get(mctx, n * sizeof(dns_rdatatype_t)); + types = isc_mem_get(mctx, n * sizeof(*types)); } i = 0; @@ -298,22 +298,43 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, element2 = cfg_list_next(element2)) { const cfg_obj_t *typeobj; + const char *bracket; isc_textregion_t r; + unsigned long max = 0; INSIST(i < n); typeobj = cfg_listelt_value(element2); str = cfg_obj_asstring(typeobj); DE_CONST(str, r.base); - r.length = strlen(str); - result = dns_rdatatype_fromtext(&types[i++], &r); + bracket = strchr(str, '(' /*)*/); + if (bracket != NULL) { + char *end = NULL; + r.length = bracket - str; + max = strtoul(bracket + 1, &end, 10); + if (max > 0xffff || end[0] != /*(*/ ')' || + end[1] != 0) { + cfg_obj_log(identity, named_g_lctx, + ISC_LOG_ERROR, + "'%s' is not a valid count", + bracket); + isc_mem_put(mctx, types, + n * sizeof(*types)); + goto cleanup; + } + } else { + r.length = strlen(str); + } + types[i].max = max; + + result = dns_rdatatype_fromtext(&types[i++].type, &r); if (result != ISC_R_SUCCESS) { cfg_obj_log(identity, named_g_lctx, ISC_LOG_ERROR, - "'%s' is not a valid type", str); - isc_mem_put(mctx, types, - n * sizeof(dns_rdatatype_t)); + "'%.*s' is not a valid type", + (int)r.length, str); + isc_mem_put(mctx, types, n * sizeof(types)); goto cleanup; } } @@ -323,7 +344,7 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, table, grant, dns_fixedname_name(&fident), mtype, dns_fixedname_name(&fname), n, types); if (types != NULL) { - isc_mem_put(mctx, types, n * sizeof(dns_rdatatype_t)); + isc_mem_put(mctx, types, n * sizeof(*types)); } if (result != ISC_R_SUCCESS) { goto cleanup; @@ -336,7 +357,7 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, * update-policy { grant zonesub any; }; */ if (autoddns) { - dns_rdatatype_t any = dns_rdatatype_any; + dns_ssuruletype_t any = { dns_rdatatype_any, 0 }; if (named_g_server->session_keyname == NULL) { isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, diff --git a/bin/tests/system/checkconf/bad-update-policy16.conf b/bin/tests/system/checkconf/bad-update-policy16.conf new file mode 100644 index 0000000000..6870e65e62 --- /dev/null +++ b/bin/tests/system/checkconf/bad-update-policy16.conf @@ -0,0 +1,18 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +zone "example.com" { + type master; + file "example.com.db"; + update-policy { + grant * tcp-self . "ptr(10 )"; + }; +}; diff --git a/bin/tests/system/checkconf/good-update-policy13.conf b/bin/tests/system/checkconf/good-update-policy13.conf new file mode 100644 index 0000000000..e67e01f319 --- /dev/null +++ b/bin/tests/system/checkconf/good-update-policy13.conf @@ -0,0 +1,18 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +zone "example.com" { + type master; + file "example.com.db"; + update-policy { + grant * tcp-self . ptr(1); + }; +}; diff --git a/bin/tests/system/nsupdate/clean.sh b/bin/tests/system/nsupdate/clean.sh index ef5ec56e65..9b8af4a7ff 100644 --- a/bin/tests/system/nsupdate/clean.sh +++ b/bin/tests/system/nsupdate/clean.sh @@ -60,3 +60,5 @@ rm -f update.out.* rm -f check.out.* rm -f update.out.* rm -f ns*/managed-keys.bind* ns*/*.mkeys* +rm -f nextpart.out.* +rm -f */named.run.prev diff --git a/bin/tests/system/nsupdate/ns6/named.conf.in b/bin/tests/system/nsupdate/ns6/named.conf.in index da64d061e4..b4ec11a326 100644 --- a/bin/tests/system/nsupdate/ns6/named.conf.in +++ b/bin/tests/system/nsupdate/ns6/named.conf.in @@ -34,5 +34,5 @@ controls { zone "in-addr.arpa" { type master; file "in-addr.db"; - update-policy { grant * tcp-self . PTR; }; + update-policy { grant * tcp-self . PTR(1) ANY(2) A; }; }; diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh index 1cbe04b15e..4c5b739113 100755 --- a/bin/tests/system/nsupdate/tests.sh +++ b/bin/tests/system/nsupdate/tests.sh @@ -1031,6 +1031,65 @@ grep "UPDATE, status: NOERROR" nsupdate.out-$n > /dev/null 2>&1 || ret=1 grep "UPDATE, status: FORMERR" nsupdate.out-$n > /dev/null 2>&1 || ret=1 [ $ret = 0 ] || { echo_i "failed"; status=1; } +n=`expr $n + 1` +ret=0 +echo_i "check that max records is enforced ($n)" +nextpart ns6/named.run > /dev/null +$NSUPDATE -v > nsupdate.out.$n 2>&1 << END +server 10.53.0.6 ${PORT} +local 10.53.0.5 +update del 5.0.53.10.in-addr.arpa. +update add 5.0.53.10.in-addr.arpa. 600 PTR localhost. +update add 5.0.53.10.in-addr.arpa. 600 PTR other. +send +END +$DIG $DIGOPTS @10.53.0.6 \ + +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd \ + -x 10.53.0.5 > dig.out.ns6.$n +# the policy is 'grant * tcp-self . PTR(1) ANY(2) A;' so only the +# first PTR record should be added. +grep localhost. dig.out.ns6.$n > /dev/null 2>&1 || ret=1 +grep other. dig.out.ns6.$n > /dev/null 2>&1 && ret=1 +nextpart ns6/named.run > nextpart.out.$n +grep "attempt to add more records than permitted by policy" nextpart.out.$n > /dev/null || ret=1 +if test $ret -ne 0 +then +echo_i "failed"; status=1 +fi + +n=`expr $n + 1` +ret=0 +echo_i "check that max records for ANY is enforced ($n)" +nextpart ns6/named.run > /dev/null +$NSUPDATE -v > nsupdate.out.$n 2>&1 << END +server 10.53.0.6 ${PORT} +local 10.53.0.5 +update del 5.0.53.10.in-addr.arpa. +update add 5.0.53.10.in-addr.arpa. 600 A 1.2.3.4 +update add 5.0.53.10.in-addr.arpa. 600 A 1.2.3.3 +update add 5.0.53.10.in-addr.arpa. 600 A 1.2.3.2 +update add 5.0.53.10.in-addr.arpa. 600 AAAA ::ffff:1.2.3.4 +update add 5.0.53.10.in-addr.arpa. 600 AAAA ::ffff:1.2.3.3 +update add 5.0.53.10.in-addr.arpa. 600 AAAA ::ffff:1.2.3.2 +send +END +$DIG $DIGOPTS @10.53.0.6 \ + +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd \ + ANY -x 10.53.0.5 > dig.out.ns6.test$n +nextpart ns6/named.run > nextpart.out.test$n +grep "attempt to add more records than permitted by policy" nextpart.out.test$n > /dev/null || ret=1 +# the policy is 'grant * tcp-self . PTR(1) ANY(2) A;' so all the A +# records should have been added as there is no limit and the first 2 +# of the AAAA records added as they match ANY(2). +c1=$(awk '$4 == "A" { print }' dig.out.ns6.test$n | wc -l) +c2=$(awk '$4 == "AAAA" { print }' dig.out.ns6.test$n | wc -l) +test "$c1" -eq 3 -a "$c2" -eq 2 || ret=1 +grep "::ffff:1.2.3.2" dig.out.ns6.test$n && ret=1 +if test $ret -ne 0 +then +echo_i "failed"; status=1 +fi + if $FEATURETEST --gssapi ; then n=`expr $n + 1` ret=0 diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index cb94dd6d40..0a3c4a7734 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -8,16 +8,6 @@ See the COPYRIGHT file distributed with this work for additional information regarding copyright ownership. -.. - Copyright (C) Internet Systems Consortium, Inc. ("ISC") - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - - See the COPYRIGHT file distributed with this work for additional - information regarding copyright ownership. - .. Reference: BIND 9 Configuration Reference @@ -5708,6 +5698,29 @@ never be updated). Note that when an attempt is made to delete all records associated with a name, the rules are checked for each existing record type. +If the type is immediately followed by a number in parentheses, +that number is the maximum number of records of that type permitted +to exist in the RRset after an update has been applied. For example, +``PTR(1)`` indicates that only one PTR record is allowed. If an +attempt is made to add two PTR records in an update, the second one +will be silently discarded. If a PTR record already exists, both +new records will be silently discarded. + +If type ANY is specified with a limit, then that limit applies to +all types that are not otherwise specified. For example, ``A PTR(1) +ANY(2)`` indicates that an unlimited number of A records can exist, +but only one PTR record, and no more than two of any other type. + +Typical use with rule ``grant * tcp-self . PTR(1);`` in the zone +2.0.192.IN-ADDR.ARPA:: + + nsupdate -v < #include #include +#include #include #include #include @@ -1850,15 +1851,35 @@ check_update_policy(const cfg_obj_t *policy, isc_log_t *logctx) { element2 = cfg_list_next(element2)) { const cfg_obj_t *typeobj; + const char *bracket; typeobj = cfg_listelt_value(element2); DE_CONST(cfg_obj_asstring(typeobj), r.base); - r.length = strlen(r.base); + + bracket = strchr(r.base, '(' /*)*/); + if (bracket != NULL) { + char *end = NULL; + unsigned long max; + + r.length = bracket - r.base; + max = strtoul(bracket + 1, &end, 10); + if (max > 0xffff || end[0] != /*(*/ ')' || + end[1] != 0) { + cfg_obj_log(typeobj, logctx, + ISC_LOG_ERROR, + "'%s' is not a valid count", + bracket); + result = DNS_R_SYNTAX; + } + } else { + r.length = strlen(r.base); + } tresult = dns_rdatatype_fromtext(&type, &r); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(typeobj, logctx, ISC_LOG_ERROR, - "'%s' is not a valid type", r.base); + "'%.*s' is not a valid type", + (int)r.length, r.base); result = tresult; } } diff --git a/lib/dns/include/dns/ssu.h b/lib/dns/include/dns/ssu.h index e2ead5bc05..cc463a9d6b 100644 --- a/lib/dns/include/dns/ssu.h +++ b/lib/dns/include/dns/ssu.h @@ -47,6 +47,11 @@ typedef enum { dns_ssumatchtype_dlz = 16 /* intentionally higher than _max */ } dns_ssumatchtype_t; +typedef struct dns_ssuruletype { + dns_rdatatype_t type; /* type allowed */ + unsigned int max; /* maximum number of records allowed. */ +} dns_ssuruletype_t; + isc_result_t dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **table); /*%< @@ -103,7 +108,7 @@ isc_result_t dns_ssutable_addrule(dns_ssutable_t *table, bool grant, const dns_name_t *identity, dns_ssumatchtype_t matchtype, const dns_name_t *name, unsigned int ntypes, - dns_rdatatype_t *types); + dns_ssuruletype_t *types); /*%< * Adds a new rule to a simple-secure-update rule table. The rule * either grants or denies update privileges of an identity (or set of @@ -136,7 +141,7 @@ bool dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, const dns_name_t *name, const isc_netaddr_t *addr, bool tcp, const dns_aclenv_t *env, dns_rdatatype_t type, - const dst_key_t *key); + const dst_key_t *key, const dns_ssurule_t **rulep); /*%< * Checks that the attempted update of (name, type) is allowed according * to the rules specified in the simple-secure-update rule table. If @@ -180,18 +185,31 @@ dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, /*% Accessor functions to extract rule components */ bool dns_ssurule_isgrant(const dns_ssurule_t *rule); + /*% Accessor functions to extract rule components */ dns_name_t * dns_ssurule_identity(const dns_ssurule_t *rule); + /*% Accessor functions to extract rule components */ unsigned int dns_ssurule_matchtype(const dns_ssurule_t *rule); + /*% Accessor functions to extract rule components */ dns_name_t * dns_ssurule_name(const dns_ssurule_t *rule); + /*% Accessor functions to extract rule components */ unsigned int -dns_ssurule_types(const dns_ssurule_t *rule, dns_rdatatype_t **types); +dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types); + +unsigned int +dns_ssurule_max(const dns_ssurule_t *rule, dns_rdatatype_t type); +/*%< + * Returns the maximum number of records configured for type `type`. + * If no maximum has been configured for `type` but one has been + * configured for ANY, return that value instead. Otherwise, return + * zero, which implies "unlimited". + */ isc_result_t dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule); diff --git a/lib/dns/ssu.c b/lib/dns/ssu.c index 47758b8cf6..9a601febe8 100644 --- a/lib/dns/ssu.c +++ b/lib/dns/ssu.c @@ -39,12 +39,11 @@ struct dns_ssurule { unsigned int magic; bool grant; /*%< is this a grant or a deny? */ - dns_ssumatchtype_t matchtype; /*%< which type of pattern match? - * */ + dns_ssumatchtype_t matchtype; /*%< which type of pattern match? */ dns_name_t *identity; /*%< the identity to match */ dns_name_t *name; /*%< the name being updated */ unsigned int ntypes; /*%< number of data types covered */ - dns_rdatatype_t *types; /*%< the data types. Can include */ + dns_ssuruletype_t *types; /*%< the data types. Can include */ /* ANY. if NULL, defaults to all */ /* types except SIG, SOA, and NS */ ISC_LINK(dns_ssurule_t) link; @@ -86,15 +85,16 @@ destroy(dns_ssutable_t *table) { dns_ssurule_t *rule = ISC_LIST_HEAD(table->rules); if (rule->identity != NULL) { dns_name_free(rule->identity, mctx); - isc_mem_put(mctx, rule->identity, sizeof(dns_name_t)); + isc_mem_put(mctx, rule->identity, + sizeof(*rule->identity)); } if (rule->name != NULL) { dns_name_free(rule->name, mctx); - isc_mem_put(mctx, rule->name, sizeof(dns_name_t)); + isc_mem_put(mctx, rule->name, sizeof(*rule->name)); } if (rule->types != NULL) { isc_mem_put(mctx, rule->types, - rule->ntypes * sizeof(dns_rdatatype_t)); + rule->ntypes * sizeof(*rule->types)); } ISC_LIST_UNLINK(table->rules, rule, link); rule->magic = 0; @@ -133,7 +133,7 @@ isc_result_t dns_ssutable_addrule(dns_ssutable_t *table, bool grant, const dns_name_t *identity, dns_ssumatchtype_t matchtype, const dns_name_t *name, unsigned int ntypes, - dns_rdatatype_t *types) { + dns_ssuruletype_t *types) { dns_ssurule_t *rule; isc_mem_t *mctx; @@ -149,7 +149,7 @@ dns_ssutable_addrule(dns_ssutable_t *table, bool grant, } mctx = table->mctx; - rule = isc_mem_get(mctx, sizeof(dns_ssurule_t)); + rule = isc_mem_get(mctx, sizeof(*rule)); rule->identity = NULL; rule->name = NULL; @@ -157,11 +157,11 @@ dns_ssutable_addrule(dns_ssutable_t *table, bool grant, rule->grant = grant; - rule->identity = isc_mem_get(mctx, sizeof(dns_name_t)); + rule->identity = isc_mem_get(mctx, sizeof(*rule->identity)); dns_name_init(rule->identity, NULL); dns_name_dup(identity, mctx, rule->identity); - rule->name = isc_mem_get(mctx, sizeof(dns_name_t)); + rule->name = isc_mem_get(mctx, sizeof(*rule->name)); dns_name_init(rule->name, NULL); dns_name_dup(name, mctx, rule->name); @@ -169,9 +169,8 @@ dns_ssutable_addrule(dns_ssutable_t *table, bool grant, rule->ntypes = ntypes; if (ntypes > 0) { - rule->types = isc_mem_get(mctx, - ntypes * sizeof(dns_rdatatype_t)); - memmove(rule->types, types, ntypes * sizeof(dns_rdatatype_t)); + rule->types = isc_mem_get(mctx, ntypes * sizeof(*rule->types)); + memmove(rule->types, types, ntypes * sizeof(*rule->types)); } else { rule->types = NULL; } @@ -284,15 +283,15 @@ bool dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, const dns_name_t *name, const isc_netaddr_t *addr, bool tcp, const dns_aclenv_t *env, dns_rdatatype_t type, - const dst_key_t *key) { - dns_ssurule_t *rule; - unsigned int i; + const dst_key_t *key, const dns_ssurule_t **rulep) { dns_fixedname_t fixed; - dns_name_t *wildcard; - dns_name_t *tcpself; dns_name_t *stfself; - isc_result_t result; + dns_name_t *tcpself; + dns_name_t *wildcard; + dns_ssurule_t *rule; int match; + isc_result_t result; + unsigned int i; REQUIRE(VALID_SSUTABLE(table)); REQUIRE(signer == NULL || dns_name_isabsolute(signer)); @@ -513,8 +512,8 @@ dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, } } else { for (i = 0; i < rule->ntypes; i++) { - if (rule->types[i] == dns_rdatatype_any || - rule->types[i] == type) { + if (rule->types[i].type == dns_rdatatype_any || + rule->types[i].type == type) { break; } } @@ -522,6 +521,9 @@ dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, continue; } } + if (rule->grant && rulep != NULL) { + *rulep = rule; + } return (rule->grant); } @@ -553,13 +555,31 @@ dns_ssurule_name(const dns_ssurule_t *rule) { } unsigned int -dns_ssurule_types(const dns_ssurule_t *rule, dns_rdatatype_t **types) { +dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types) { REQUIRE(VALID_SSURULE(rule)); REQUIRE(types != NULL && *types != NULL); *types = rule->types; return (rule->ntypes); } +unsigned int +dns_ssurule_max(const dns_ssurule_t *rule, dns_rdatatype_t type) { + unsigned int i; + unsigned int max = 0; + + REQUIRE(VALID_SSURULE(rule)); + + for (i = 0; i < rule->ntypes; i++) { + if (rule->types[i].type == dns_rdatatype_any) { + max = rule->types[i].max; + } + if (rule->types[i].type == type) { + return (rule->types[i].max); + } + } + return (max); +} + isc_result_t dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule) { REQUIRE(VALID_SSUTABLE(table)); @@ -599,7 +619,6 @@ dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep, rule->identity = NULL; rule->name = NULL; - rule->types = NULL; rule->grant = true; rule->matchtype = dns_ssumatchtype_dlz; rule->ntypes = 0; diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 629fe53333..2d1c23b2ed 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1019,9 +1019,10 @@ dns_soa_setretry dns_soa_setserial dns_ssu_external_match dns_ssu_mtypefromstring -dns_ssurule_isgrant dns_ssurule_identity +dns_ssurule_isgrant dns_ssurule_matchtype +dns_ssurule_max dns_ssurule_name dns_ssurule_types dns_ssutable_firstrule diff --git a/lib/ns/update.c b/lib/ns/update.c index 3c88115e0b..84e1c7e129 100644 --- a/lib/ns/update.c +++ b/lib/ns/update.c @@ -753,6 +753,17 @@ cleanup_node: typedef bool rr_predicate(dns_rdata_t *update_rr, dns_rdata_t *db_rr); +static isc_result_t +count_action(void *data, rr_t *rr) { + unsigned int *ui = (unsigned int *)data; + + UNUSED(rr); + + (*ui)++; + + return (ISC_R_SUCCESS); +} + /*% * Helper function for rrset_exists(). */ @@ -899,8 +910,9 @@ typedef struct { static isc_result_t ssu_checkrule(void *data, dns_rdataset_t *rrset) { - ssu_check_t *ssuinfo = data; bool result; + const dns_ssurule_t *rule = NULL; + ssu_check_t *ssuinfo = data; /* * If we're deleting all records, it's ok to delete RRSIG and NSEC even @@ -910,9 +922,10 @@ ssu_checkrule(void *data, dns_rdataset_t *rrset) { rrset->type == dns_rdatatype_nsec) { return (ISC_R_SUCCESS); } - result = dns_ssutable_checkrules( - ssuinfo->table, ssuinfo->signer, ssuinfo->name, ssuinfo->addr, - ssuinfo->tcp, ssuinfo->aclenv, rrset->type, ssuinfo->key); + result = dns_ssutable_checkrules(ssuinfo->table, ssuinfo->signer, + ssuinfo->name, ssuinfo->addr, + ssuinfo->tcp, ssuinfo->aclenv, + rrset->type, ssuinfo->key, &rule); return (result == true ? ISC_R_SUCCESS : ISC_R_FAILURE); } @@ -2565,6 +2578,9 @@ update_action(isc_task_t *task, isc_event_t *event) { uint64_t records; dns_aclenv_t *env = ns_interfacemgr_getaclenv(client->manager->interface->mgr); + size_t ruleslen = 0; + size_t rule; + const dns_ssurule_t **rules = NULL; INSIST(event->ev_type == DNS_EVENT_UPDATE); @@ -2739,15 +2755,24 @@ update_action(isc_task_t *task, isc_event_t *event) { /* * Perform the Update Section Prescan. */ + if (ssutable != NULL) { + ruleslen = request->counts[DNS_SECTION_UPDATE]; + rules = isc_mem_get(mctx, sizeof(*rules) * ruleslen); + memset(rules, 0, sizeof(*rules) * ruleslen); + } - for (result = dns_message_firstname(request, DNS_SECTION_UPDATE); + for (rule = 0, + result = dns_message_firstname(request, DNS_SECTION_UPDATE); result == ISC_R_SUCCESS; - result = dns_message_nextname(request, DNS_SECTION_UPDATE)) + rule++, result = dns_message_nextname(request, DNS_SECTION_UPDATE)) { dns_name_t *name = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; dns_ttl_t ttl; dns_rdataclass_t update_class; + + INSIST(ssutable == NULL || rule < ruleslen); + get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name, &rdata, &covers, &ttl, &update_class); @@ -2820,7 +2845,7 @@ update_action(isc_task_t *task, isc_event_t *event) { if (!dns_ssutable_checkrules( ssutable, client->signer, name, &netaddr, TCPCLIENT(client), env, - rdata.type, tsigkey)) + rdata.type, tsigkey, &rules[rule])) { FAILC(DNS_R_REFUSED, "rejected by " "secure update"); @@ -2847,9 +2872,10 @@ update_action(isc_task_t *task, isc_event_t *event) { */ options = dns_zone_getoptions(zone); - for (result = dns_message_firstname(request, DNS_SECTION_UPDATE); + for (rule = 0, + result = dns_message_firstname(request, DNS_SECTION_UPDATE); result == ISC_R_SUCCESS; - result = dns_message_nextname(request, DNS_SECTION_UPDATE)) + rule++, result = dns_message_nextname(request, DNS_SECTION_UPDATE)) { dns_name_t *name = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; @@ -2857,10 +2883,14 @@ update_action(isc_task_t *task, isc_event_t *event) { dns_rdataclass_t update_class; bool flag; + INSIST(ssutable == NULL || rule < ruleslen); + get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name, &rdata, &covers, &ttl, &update_class); if (update_class == zoneclass) { + unsigned int max = 0; + /* * RFC1123 doesn't allow MF and MD in master zones. */ @@ -2986,6 +3016,24 @@ update_action(isc_task_t *task, isc_event_t *event) { } } + if (rules != NULL && rules[rule] != NULL) { + max = dns_ssurule_max(rules[rule], rdata.type); + } + if (max != 0) { + unsigned int count = 0; + CHECK(foreach_rr(db, ver, name, rdata.type, + covers, count_action, &count)); + if (count >= max) { + update_log(client, zone, + LOGLEVEL_PROTOCOL, + "attempt to add more " + "records than permitted by " + "policy max=%u", + max); + continue; + } + } + if (isc_log_wouldlog(ns_lctx, LOGLEVEL_PROTOCOL)) { char namestr[DNS_NAME_FORMATSIZE]; char typestr[DNS_RDATATYPE_FORMATSIZE]; @@ -3420,6 +3468,10 @@ common: dns_db_detach(&db); } + if (rules != NULL) { + isc_mem_put(mctx, rules, sizeof(*rules) * ruleslen); + } + if (ssutable != NULL) { dns_ssutable_detach(&ssutable); }