diff --git a/CHANGES b/CHANGES index a9084d2338..7dd836779a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +3746. [func] New "max-zone-ttl" option enforces maximum + TTLs for zones. If loading a zone containing a + higher TTL, the load fails. DDNS updates with + higher TTLs are accepted but the TTL is truncated. + (Note: Currently supported for master zones only; + inline-signing slaves will be added.) [RT #38405] + 3745. [func] "configure --with-tuning=large" adjusts various compiled-in constants and default settings to values suited to large servers with abundant diff --git a/README b/README index 0f61794af1..604ee31b26 100644 --- a/README +++ b/README @@ -123,6 +123,10 @@ BIND 9.10.0 OpenSSL as an intermediary. This has been tested with the Thales nShield HSM and with SoftHSMv2 from the Open DNSSEC project. + - The new "max-zone-ttl" option enforces maximum TTLs for + zones. This can simplify the process of rolling DNSSEC keys + by guaranteeing that cached signatures will have expired + within the specified amount of time. - New "dnssec-coverage" tool to check DNSSEC key coverage for a zone and report if a lapse in signing coverage has been inadvertently scheduled. diff --git a/bin/check/check-tool.c b/bin/check/check-tool.c index 1a805ed90e..88223d0077 100644 --- a/bin/check/check-tool.c +++ b/bin/check/check-tool.c @@ -40,12 +40,17 @@ #include #include +#include +#include #include #include #include #include #include #include +#include +#include +#include #include #include @@ -108,6 +113,7 @@ unsigned int zone_options = DNS_ZONEOPT_CHECKNS | DNS_ZONEOPT_CHECKWILDCARD | DNS_ZONEOPT_WARNMXCNAME | DNS_ZONEOPT_WARNSRVCNAME; +unsigned int zone_options2 = 0; /* * This needs to match the list in bin/named/log.c. @@ -577,11 +583,93 @@ setup_logging(isc_mem_t *mctx, FILE *errout, isc_log_t **logp) { return (ISC_R_SUCCESS); } +/*% scan the zone for oversize TTLs */ +static isc_result_t +check_ttls(dns_zone_t *zone, dns_ttl_t maxttl) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbversion_t *version = NULL; + dns_dbnode_t *node = NULL; + dns_dbiterator_t *dbiter = NULL; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdataset_t rdataset; + dns_fixedname_t fname; + dns_name_t *name; + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + dns_rdataset_init(&rdataset); + + dns_zone_getdb(zone, &db); + INSIST(db != NULL); + + CHECK(dns_db_newversion(db, &version)); + CHECK(dns_db_createiterator(db, 0, &dbiter)); + + for (result = dns_dbiterator_first(dbiter); + result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbiter)) { + result = dns_dbiterator_current(dbiter, &node, name); + if (result == DNS_R_NEWORIGIN) + result = ISC_R_SUCCESS; + CHECK(result); + + CHECK(dns_db_allrdatasets(db, node, version, 0, &rdsiter)); + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) { + dns_rdatasetiter_current(rdsiter, &rdataset); + if (rdataset.ttl > maxttl) { + char nbuf[DNS_NAME_FORMATSIZE]; + char tbuf[255]; + isc_buffer_t b; + isc_region_t r; + + dns_name_format(name, nbuf, sizeof(nbuf)); + isc_buffer_init(&b, tbuf, sizeof(tbuf) - 1); + CHECK(dns_rdatatype_totext(rdataset.type, &b)); + isc_buffer_usedregion(&b, &r); + r.base[r.length] = 0; + + dns_zone_log(zone, ISC_LOG_ERROR, + "%s/%s TTL %d exceeds " + "maximum TTL %d", + nbuf, tbuf, rdataset.ttl, maxttl); + dns_rdataset_disassociate(&rdataset); + CHECK(ISC_R_RANGE); + } + dns_rdataset_disassociate(&rdataset); + } + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + CHECK(result); + + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(db, &node); + } + + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + + cleanup: + if (node != NULL) + dns_db_detachnode(db, &node); + if (rdsiter != NULL) + dns_rdatasetiter_destroy(&rdsiter); + if (dbiter != NULL) + dns_dbiterator_destroy(&dbiter); + if (version != NULL) + dns_db_closeversion(db, &version, ISC_FALSE); + if (db != NULL) + dns_db_detach(&db); + + return (result); +} + /*% load the zone */ isc_result_t load_zone(isc_mem_t *mctx, const char *zonename, const char *filename, dns_masterformat_t fileformat, const char *classname, - dns_zone_t **zonep) + dns_ttl_t maxttl, dns_zone_t **zonep) { isc_result_t result; dns_rdataclass_t rdclass; @@ -618,7 +706,11 @@ load_zone(isc_mem_t *mctx, const char *zonename, const char *filename, dns_zone_setclass(zone, rdclass); dns_zone_setoption(zone, zone_options, ISC_TRUE); + dns_zone_setoption2(zone, zone_options2, ISC_TRUE); dns_zone_setoption(zone, DNS_ZONEOPT_NOMERGE, nomerge); + + dns_zone_setmaxttl(zone, maxttl); + if (docheckmx) dns_zone_setcheckmx(zone, checkmx); if (docheckns) @@ -627,6 +719,15 @@ load_zone(isc_mem_t *mctx, const char *zonename, const char *filename, dns_zone_setchecksrv(zone, checksrv); CHECK(dns_zone_load(zone)); + + /* + * When loading map files we can't catch oversize TTLs during + * load, so we check for them here. + */ + if (fileformat == dns_masterformat_map && maxttl != 0) { + CHECK(check_ttls(zone, maxttl)); + } + if (zonep != NULL) { *zonep = zone; zone = NULL; diff --git a/bin/check/check-tool.h b/bin/check/check-tool.h index 518ecef00d..5ea53c483e 100644 --- a/bin/check/check-tool.h +++ b/bin/check/check-tool.h @@ -37,7 +37,7 @@ setup_logging(isc_mem_t *mctx, FILE *errout, isc_log_t **logp); isc_result_t load_zone(isc_mem_t *mctx, const char *zonename, const char *filename, dns_masterformat_t fileformat, const char *classname, - dns_zone_t **zonep); + dns_ttl_t maxttl, dns_zone_t **zonep); isc_result_t dump_zone(const char *zonename, dns_zone_t *zone, const char *filename, @@ -56,6 +56,7 @@ extern isc_boolean_t docheckmx; extern isc_boolean_t docheckns; extern isc_boolean_t dochecksrv; extern unsigned int zone_options; +extern unsigned int zone_options2; ISC_LANG_ENDDECLS diff --git a/bin/check/named-checkconf.c b/bin/check/named-checkconf.c index fd3795a57f..97c919fe0a 100644 --- a/bin/check/named-checkconf.c +++ b/bin/check/named-checkconf.c @@ -198,6 +198,7 @@ configure_zone(const char *vclass, const char *view, const cfg_obj_t *obj = NULL; const cfg_obj_t *fmtobj = NULL; dns_masterformat_t masterformat; + dns_ttl_t maxttl = 0; zone_options = DNS_ZONEOPT_CHECKNS | DNS_ZONEOPT_MANYERRORS; @@ -373,11 +374,20 @@ configure_zone(const char *vclass, const char *view, masterformat = dns_masterformat_text; else if (strcasecmp(masterformatstr, "raw") == 0) masterformat = dns_masterformat_raw; + else if (strcasecmp(masterformatstr, "map") == 0) + masterformat = dns_masterformat_map; else INSIST(0); } - result = load_zone(mctx, zname, zfile, masterformat, zclass, NULL); + obj = NULL; + if (get_maps(maps, "max-zone-ttl", &obj)) { + maxttl = cfg_obj_asuint32(obj); + zone_options2 |= DNS_ZONEOPT2_CHECKTTL; + } + + result = load_zone(mctx, zname, zfile, masterformat, + zclass, maxttl, NULL); if (result != ISC_R_SUCCESS) fprintf(stderr, "%s/%s/%s: %s\n", view, zname, zclass, dns_result_totext(result)); diff --git a/bin/check/named-checkzone.c b/bin/check/named-checkzone.c index a13cfa7d5c..b973f79934 100644 --- a/bin/check/named-checkzone.c +++ b/bin/check/named-checkzone.c @@ -115,6 +115,7 @@ main(int argc, char **argv) { dns_masterformat_t outputformat = dns_masterformat_text; dns_masterrawheader_t header; isc_uint32_t rawversion = 1, serialnum = 0; + dns_ttl_t maxttl = 0; isc_boolean_t snset = ISC_FALSE; isc_boolean_t logdump = ISC_FALSE; FILE *errout = stdout; @@ -169,7 +170,7 @@ main(int argc, char **argv) { isc_commandline_errprint = ISC_FALSE; while ((c = isc_commandline_parse(argc, argv, - "c:df:hi:jJ:k:L:m:n:qr:s:t:o:vw:DF:M:S:T:W:")) + "c:df:hi:jJ:k:L:l:m:n:qr:s:t:o:vw:DF:M:S:T:W:")) != EOF) { switch (c) { case 'c': @@ -263,6 +264,18 @@ main(int argc, char **argv) { } break; + case 'l': + zone_options2 |= DNS_ZONEOPT2_CHECKTTL; + endp = NULL; + maxttl = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fprintf(stderr, "maximum TTL " + "must be numeric"); + exit(1); + } + break; + + case 'n': if (ARGCMP("ignore")) { zone_options &= ~(DNS_ZONEOPT_CHECKNS| @@ -523,7 +536,7 @@ main(int argc, char **argv) { origin = argv[isc_commandline_index++]; filename = argv[isc_commandline_index++]; result = load_zone(mctx, origin, filename, inputformat, classname, - &zone); + maxttl, &zone); if (snset) { dns_master_initrawheader(&header); diff --git a/bin/check/named-checkzone.docbook b/bin/check/named-checkzone.docbook index 98d3fc3ff9..61baf5f03f 100644 --- a/bin/check/named-checkzone.docbook +++ b/bin/check/named-checkzone.docbook @@ -74,6 +74,7 @@ + @@ -102,6 +103,7 @@ + @@ -301,6 +303,19 @@ + + -l ttl + + + Sets a maximum permissible TTL for the input file. + Any record with a TTL higher than this value will cause + the zone to be rejected. This is similar to using the + max-zone-ttl option in + named.conf. + + + + -L serial diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index 7b2f414e2a..e7609b177c 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -179,6 +179,8 @@ static isc_boolean_t remove_orphansigs = ISC_FALSE; static isc_boolean_t remove_inactkeysigs = ISC_FALSE; static isc_boolean_t output_dnssec_only = ISC_FALSE; static isc_boolean_t output_stdout = ISC_FALSE; +isc_boolean_t set_maxttl = ISC_FALSE; +static dns_ttl_t maxttl = 0; #define INCSTAT(counter) \ if (printstats) { \ @@ -1232,6 +1234,10 @@ get_soa_ttls(void) { dns_rdataset_current(&soaset, &rdata); zone_soa_min_ttl = dns_soa_getminimum(&rdata); soa_ttl = soaset.ttl; + if (set_maxttl) { + zone_soa_min_ttl = ISC_MIN(zone_soa_min_ttl, maxttl); + soa_ttl = ISC_MIN(soa_ttl, maxttl); + } dns_rdataset_disassociate(&soaset); } @@ -2007,7 +2013,8 @@ nsec3clean(dns_name_t *name, dns_dbnode_t *node, rdatalist.rdclass = rdata.rdclass; rdatalist.type = rdata.type; rdatalist.covers = 0; - rdatalist.ttl = rdataset.ttl; + if (set_maxttl) + rdatalist.ttl = ISC_MIN(rdataset.ttl, maxttl); ISC_LIST_INIT(rdatalist.rdata); dns_rdata_init(&delrdata); dns_rdata_clone(&rdata, &delrdata); @@ -2038,13 +2045,17 @@ nsec3clean(dns_name_t *name, dns_dbnode_t *node, } static void -rrset_remove_duplicates(dns_name_t *name, dns_rdataset_t *rdataset, - dns_diff_t *diff) +rrset_cleanup(dns_name_t *name, dns_rdataset_t *rdataset, + dns_diff_t *add, dns_diff_t *del) { - dns_difftuple_t *tuple = NULL; isc_result_t result; unsigned int count1 = 0; dns_rdataset_t tmprdataset; + char namestr[DNS_NAME_FORMATSIZE]; + char typestr[TYPE_FORMATSIZE]; + + dns_name_format(name, namestr, sizeof(namestr)); + type_format(rdataset->type, typestr, sizeof(typestr)); dns_rdataset_init(&tmprdataset); for (result = dns_rdataset_first(rdataset); @@ -2060,17 +2071,38 @@ rrset_remove_duplicates(dns_name_t *name, dns_rdataset_t *rdataset, result == ISC_R_SUCCESS; result = dns_rdataset_next(&tmprdataset)) { dns_rdata_t rdata2 = DNS_RDATA_INIT; + dns_difftuple_t *tuple = NULL; count2++; - if (count1 >= count2) - continue; dns_rdataset_current(&tmprdataset, &rdata2); - if (dns_rdata_casecompare(&rdata1, &rdata2) == 0) { + if (count1 < count2 && + dns_rdata_casecompare(&rdata1, &rdata2) == 0) + { + vbprintf(2, "removing duplicate at %s/%s\n", + namestr, typestr); result = dns_difftuple_create(mctx, DNS_DIFFOP_DELRESIGN, name, rdataset->ttl, &rdata2, &tuple); check_result(result, "dns_difftuple_create"); - dns_diff_append(diff, &tuple); + dns_diff_append(del, &tuple); + } else if (set_maxttl && rdataset->ttl > maxttl) { + vbprintf(2, "reducing ttl of %s/%s " + "from %d to %d\n", + namestr, typestr, + rdataset->ttl, maxttl); + result = dns_difftuple_create(mctx, + DNS_DIFFOP_DELRESIGN, + name, rdataset->ttl, + &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + tuple = NULL; + result = dns_difftuple_create(mctx, + DNS_DIFFOP_ADDRESIGN, + name, maxttl, + &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(add, &tuple); } } dns_rdataset_disassociate(&tmprdataset); @@ -2078,17 +2110,18 @@ rrset_remove_duplicates(dns_name_t *name, dns_rdataset_t *rdataset, } static void -remove_duplicates(void) { +cleanup_zone(void) { isc_result_t result; dns_dbiterator_t *dbiter = NULL; dns_rdatasetiter_t *rdsiter = NULL; - dns_diff_t diff; + dns_diff_t add, del; dns_dbnode_t *node = NULL; dns_rdataset_t rdataset; dns_fixedname_t fname; dns_name_t *name; - dns_diff_init(mctx, &diff); + dns_diff_init(mctx, &add); + dns_diff_init(mctx, &del); dns_fixedname_init(&fname); name = dns_fixedname_name(&fname); dns_rdataset_init(&rdataset); @@ -2108,7 +2141,7 @@ remove_duplicates(void) { result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(rdsiter)) { dns_rdatasetiter_current(rdsiter, &rdataset); - rrset_remove_duplicates(name, &rdataset, &diff); + rrset_cleanup(name, &rdataset, &add, &del); dns_rdataset_disassociate(&rdataset); } if (result != ISC_R_NOMORE) @@ -2119,11 +2152,14 @@ remove_duplicates(void) { if (result != ISC_R_NOMORE) fatal("zone iteration failed."); - if (!ISC_LIST_EMPTY(diff.tuples)) { - result = dns_diff_applysilently(&diff, gdb, gversion); - check_result(result, "dns_diff_applysilently"); - } - dns_diff_clear(&diff); + result = dns_diff_applysilently(&del, gdb, gversion); + check_result(result, "dns_diff_applysilently"); + + result = dns_diff_applysilently(&add, gdb, gversion); + check_result(result, "dns_diff_applysilently"); + + dns_diff_clear(&del); + dns_diff_clear(&add); dns_dbiterator_destroy(&dbiter); } @@ -2448,11 +2484,11 @@ loadzonekeys(isc_boolean_t preserve_keys, isc_boolean_t load_public) { goto cleanup; if (set_keyttl && keyttl != rdataset.ttl) { - fprintf(stderr, "User-specified TTL (%d) conflicts " + fprintf(stderr, "User-specified TTL %d conflicts " "with existing DNSKEY RRset TTL.\n", keyttl); fprintf(stderr, "Imported keys will use the RRSet " - "TTL (%d) instead.\n", + "TTL %d instead.\n", rdataset.ttl); } keyttl = rdataset.ttl; @@ -3065,9 +3101,9 @@ main(int argc, char *argv[]) { isc_boolean_t set_iter = ISC_FALSE; isc_boolean_t nonsecify = ISC_FALSE; - /* Unused letters: Bb G J M q Yy (and F is reserved). */ + /* Unused letters: Bb G J q Yy (and F is reserved). */ #define CMDLINE_FLAGS \ - "3:AaCc:Dd:E:e:f:FghH:i:I:j:K:k:L:l:m:n:N:o:O:PpQRr:s:ST:tuUv:X:xzZ:" + "3:AaCc:Dd:E:e:f:FghH:i:I:j:K:k:L:l:m:M:n:N:o:O:PpQRr:s:ST:tuUv:X:xzZ:" /* * Process memory debugging argument first. @@ -3240,6 +3276,17 @@ main(int argc, char *argv[]) { check_result(result, "dns_name_fromtext(dlv)"); break; + case 'M': + endp = NULL; + set_maxttl = ISC_TRUE; + maxttl = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fprintf(stderr, "maximum TTL " + "must be numeric"); + exit(1); + } + break; + case 'm': break; @@ -3459,7 +3506,7 @@ main(int argc, char *argv[]) { exit(1); } } else - fatal("unknown file format: %s\n", outputformatstr); + fatal("unknown file format: %s", outputformatstr); } if (serialformatstr != NULL) { @@ -3471,15 +3518,18 @@ main(int argc, char *argv[]) { else if (strcasecmp(serialformatstr, "unixtime") == 0) serialformat = SOA_SERIAL_UNIXTIME; else - fatal("unknown soa serial format: %s\n", + fatal("unknown soa serial format: %s", serialformatstr); } if (output_dnssec_only && outputformat != dns_masterformat_text) - fatal("option -D can only be used with \"-O text\"\n"); + fatal("option -D can only be used with \"-O text\""); if (output_dnssec_only && serialformat != SOA_SERIAL_KEEP) - fatal("option -D can only be used with \"-N keep\"\n"); + fatal("option -D can only be used with \"-N keep\""); + + if (output_dnssec_only && set_maxttl) + fatal("option -D cannot be used with -M"); result = dns_master_stylecreate(&dsstyle, DNS_STYLEFLAG_NO_TTL, 0, 24, 0, 0, 0, 8, mctx); @@ -3492,6 +3542,13 @@ main(int argc, char *argv[]) { gclass = dns_db_class(gdb); get_soa_ttls(); + if (set_maxttl && set_keyttl && keyttl > maxttl) { + fprintf(stderr, "%s: warning: Specified key TTL %d " + "exceeds maximum zone TTL; reducing to %d\n", + program, keyttl, maxttl); + keyttl = maxttl; + } + if (!set_keyttl) keyttl = soa_ttl; @@ -3596,7 +3653,8 @@ main(int argc, char *argv[]) { break; } - remove_duplicates(); + /* Remove duplicates and cap TTLs at maxttl */ + cleanup_zone(); if (!nonsecify) { if (IS_NSEC3) @@ -3725,7 +3783,7 @@ main(int argc, char *argv[]) { result = isc_file_rename(tempfile, output); if (result != ISC_R_SUCCESS) - fatal("failed to rename temp file to %s: %s\n", + fatal("failed to rename temp file to %s: %s", output, isc_result_totext(result)); printf("%s\n", output); diff --git a/bin/dnssec/dnssec-signzone.docbook b/bin/dnssec/dnssec-signzone.docbook index 33c906b025..67d977cdac 100644 --- a/bin/dnssec/dnssec-signzone.docbook +++ b/bin/dnssec/dnssec-signzone.docbook @@ -74,6 +74,7 @@ + @@ -235,6 +236,26 @@ + + -M maxttl + + + Sets the maximum TTL for the signed zone. + Any TTL higher than maxttl in the + input zone will be reduced to maxttl + in the output. This provides certainty as to the largest + possible TTL in the signed zone, which is useful to know when + rolling keys because it is the longest possible time before + signatures that have been retrieved by resolvers will expire + from resolver caches. Zones that are signed with this + option should be configured to use a matching + in named.conf. + (Note: This option is incompatible with , + because it modifies non-DNSSEC data in the output zone.) + + + + -s start-time diff --git a/bin/named/update.c b/bin/named/update.c index b522f59991..217603f9ca 100644 --- a/bin/named/update.c +++ b/bin/named/update.c @@ -2434,7 +2434,6 @@ update_action(isc_task_t *task, isc_event_t *event) { update_event_t *uev = (update_event_t *) event; dns_zone_t *zone = uev->zone; ns_client_t *client = (ns_client_t *)event->ev_arg; - isc_result_t result; dns_db_t *db = NULL; dns_dbversion_t *oldver = NULL; @@ -2450,11 +2449,12 @@ update_action(isc_task_t *task, isc_event_t *event) { dns_ssutable_t *ssutable = NULL; dns_fixedname_t tmpnamefixed; dns_name_t *tmpname = NULL; - unsigned int options; + unsigned int options, options2; dns_difftuple_t *tuple; dns_rdata_dnskey_t dnskey; isc_boolean_t had_dnskey; dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone); + dns_ttl_t maxttl = 0; INSIST(event->ev_type == DNS_EVENT_UPDATE); @@ -2730,6 +2730,7 @@ update_action(isc_task_t *task, isc_event_t *event) { */ options = dns_zone_getoptions(zone); + options2 = dns_zone_getoptions2(zone); for (result = dns_message_firstname(request, DNS_SECTION_UPDATE); result == ISC_R_SUCCESS; result = dns_message_nextname(request, DNS_SECTION_UPDATE)) @@ -2855,6 +2856,18 @@ update_action(isc_task_t *task, isc_event_t *event) { "a non-terminal wildcard", namestr); } + if ((options2 & DNS_ZONEOPT2_CHECKTTL) != 0) { + maxttl = dns_zone_getmaxttl(zone); + if (ttl > maxttl) { + ttl = maxttl; + update_log(client, zone, + LOGLEVEL_PROTOCOL, + "reducing TTL to the " + "configured max-zone-ttl %d", + maxttl); + } + } + if (isc_log_wouldlog(ns_g_lctx, LOGLEVEL_PROTOCOL)) { char namestr[DNS_NAME_FORMATSIZE]; char typestr[DNS_RDATATYPE_FORMATSIZE]; diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index a98f0eb663..38493997ca 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -880,7 +880,6 @@ ns_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, } else dns_zone_settype(zone, ztype); - obj = NULL; result = cfg_map_get(zoptions, "database", &obj); if (result == ISC_R_SUCCESS) @@ -962,6 +961,21 @@ ns_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, INSIST(0); } + obj = NULL; + result = ns_config_get(maps, "max-zone-ttl", &obj); + if (result == ISC_R_SUCCESS && masterformat == dns_masterformat_map) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "zone '%s': 'max-zone-ttl' is not compatible " + "with 'masterfile-format map'", zname); + return (ISC_R_FAILURE); + } else if (result == ISC_R_SUCCESS) { + dns_ttl_t maxttl = cfg_obj_asuint32(obj); + dns_zone_setmaxttl(zone, maxttl); + if (raw != NULL) + dns_zone_setmaxttl(raw, maxttl); + } + if (raw != NULL && filename != NULL) { #define SIGNED ".signed" size_t signedlen = strlen(filename) + sizeof(SIGNED); diff --git a/bin/tests/system/checkconf/bad-maxttlmap.conf b/bin/tests/system/checkconf/bad-maxttlmap.conf new file mode 100644 index 0000000000..3020287e5e --- /dev/null +++ b/bin/tests/system/checkconf/bad-maxttlmap.conf @@ -0,0 +1,22 @@ +/* + * 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. + */ + +zone example { + type master; + masterfile-format map; + file "example.db"; + max-zone-ttl 3600; +}; diff --git a/bin/tests/system/checkconf/clean.sh b/bin/tests/system/checkconf/clean.sh index dde8713191..26cffe1a39 100644 --- a/bin/tests/system/checkconf/clean.sh +++ b/bin/tests/system/checkconf/clean.sh @@ -14,7 +14,5 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: clean.sh,v 1.2 2011/05/07 05:55:17 each Exp $ - -rm -f good.conf.in good.conf.out badzero.conf +rm -f good.conf.in good.conf.out badzero.conf *.out rm -rf test.keydir diff --git a/bin/tests/system/checkconf/max-ttl.conf b/bin/tests/system/checkconf/max-ttl.conf new file mode 100644 index 0000000000..694ad2b518 --- /dev/null +++ b/bin/tests/system/checkconf/max-ttl.conf @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 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. + */ + +options { + directory "."; + max-zone-ttl 600; +}; + +zone "maxttl1.example" { + type master; + file "maxttl-bad.db"; +}; + +zone "maxttl2.example" { + type master; + file "maxttl-bad.db"; + max-zone-ttl 300; +}; + +zone "maxttl3.example" { + type master; + file "maxttl-bad.db"; + max-zone-ttl 120; +}; + diff --git a/bin/tests/system/checkconf/maxttl-bad.conf b/bin/tests/system/checkconf/maxttl-bad.conf new file mode 100644 index 0000000000..6b3b289e25 --- /dev/null +++ b/bin/tests/system/checkconf/maxttl-bad.conf @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014 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. + */ + +options { + directory "."; + max-zone-ttl 8000w; +}; + +zone "maxttl.example" { + type master; + file "maxttl-bad.db"; +}; + + diff --git a/bin/tests/system/checkconf/maxttl-bad.db b/bin/tests/system/checkconf/maxttl-bad.db new file mode 100644 index 0000000000..430f7dce2f --- /dev/null +++ b/bin/tests/system/checkconf/maxttl-bad.db @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns2 +ns2 A 10.53.0.2 + MX 10 mail + +a 600 A 10.0.0.1 +mail 900 A 10.0.0.2 diff --git a/bin/tests/system/checkconf/maxttl.db b/bin/tests/system/checkconf/maxttl.db new file mode 100644 index 0000000000..a8daaa1050 --- /dev/null +++ b/bin/tests/system/checkconf/maxttl.db @@ -0,0 +1,28 @@ +; 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. + +$TTL 600 ; 10 minutes +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns2 +ns2 A 10.53.0.2 + MX 10 mail + +a A 10.0.0.1 +mail A 10.0.0.2 diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh index 3d07b2a435..634c28cc0b 100644 --- a/bin/tests/system/checkconf/tests.sh +++ b/bin/tests/system/checkconf/tests.sh @@ -163,6 +163,20 @@ n=`$CHECKCONF warn-keydir.conf 2>&1 | grep "key-directory" | wc -l` [ $n -eq 0 ] || ret=1 rm -rf test.keydir if [ $ret != 0 ]; then echo "I:failed"; fi + +echo "I: checking that named-checkconf -z catches conflicting ttl with max-ttl" +ret=0 +$CHECKCONF -z max-ttl.conf > check.out 2>&1 +grep 'TTL 900 exceeds configured max-zone-ttl 600' check.out > /dev/null 2>&1 || ret=1 +grep 'TTL 900 exceeds configured max-zone-ttl 600' check.out > /dev/null 2>&1 || ret=1 +grep 'TTL 900 exceeds configured max-zone-ttl 600' check.out > /dev/null 2>&1 || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; ret=1; fi +status=`expr $status + $ret` + +echo "I: checking that named-checkconf -z catches invalid max-ttl" +ret=0 +$CHECKCONF -z max-ttl-bad.conf > /dev/null 2>&1 && ret=1 +if [ $ret != 0 ]; then echo "I:failed"; ret=1; fi status=`expr $status + $ret` echo "I:exit status: $status" diff --git a/bin/tests/system/checkzone/clean.sh b/bin/tests/system/checkzone/clean.sh index 7292efebe4..b248998f4a 100644 --- a/bin/tests/system/checkzone/clean.sh +++ b/bin/tests/system/checkzone/clean.sh @@ -12,6 +12,4 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: clean.sh,v 1.2 2011/03/02 04:20:33 marka Exp $ - -rm -f test.* +rm -f test.* good1.db.map good1.db.raw named-compilezone diff --git a/bin/tests/system/checkzone/setup.sh b/bin/tests/system/checkzone/setup.sh new file mode 100644 index 0000000000..3ddbd8d2e6 --- /dev/null +++ b/bin/tests/system/checkzone/setup.sh @@ -0,0 +1,24 @@ +# Copyright (C) 2014 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. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +rm -f named-compilezone +ln -s $CHECKZONE named-compilezone + +./named-compilezone -D -F raw -o good1.db.raw example \ + zones/good1.db > /dev/null 2>&1 +./named-compilezone -D -F map -o good1.db.map example \ + zones/good1.db > /dev/null 2>&1 diff --git a/bin/tests/system/checkzone/tests.sh b/bin/tests/system/checkzone/tests.sh index a727b411c8..b731767073 100644 --- a/bin/tests/system/checkzone/tests.sh +++ b/bin/tests/system/checkzone/tests.sh @@ -68,4 +68,29 @@ n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` +echo "I:checking with max ttl (text) ($n)" +ret=0 +$CHECKZONE -l 300 example zones/good1.db > test.out1.$n 2>&1 && ret=1 +$CHECKZONE -l 600 example zones/good1.db > test.out2.$n 2>&1 || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:checking with max ttl (raw) ($n)" +ret=0 +$CHECKZONE -f raw -l 300 example good1.db.raw > test.out1.$n 2>&1 && ret=1 +$CHECKZONE -f raw -l 600 example good1.db.raw > test.out2.$n 2>&1 || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:checking with max ttl (map) ($n)" +ret=0 +$CHECKZONE -f map -l 300 example good1.db.map > test.out1.$n 2>&1 && ret=1 +$CHECKZONE -f map -l 600 example good1.db.map > test.out2.$n 2>&1 || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:exit status: $status" exit $status diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index b68dcb40b1..200ad245ed 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -1526,6 +1526,16 @@ awk '/IN *SOA/ {if (NF != 7) exit(1)}' signer/signer.out.4 || ret=1 if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` +echo "I:checking TTLs are capped by dnssec-signzone -M ($n)" +ret=0 +( +cd signer +$SIGNER -O full -f signer.out.8 -S -M 30 -o example example.db > /dev/null 2>&1 +) || ret=1 +awk '/^;/ { next; } $2 > 30 { exit 1; }' signer/signer.out.8 || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + echo "I:checking validated data are not cached longer than originalttl ($n)" ret=0 $DIG $DIGOPTS +ttl +noauth a.ttlpatch.example. @10.53.0.3 a > dig.out.ns3.test$n || ret=1 diff --git a/bin/tests/system/nsupdate/ns1/max-ttl.db b/bin/tests/system/nsupdate/ns1/max-ttl.db new file mode 100644 index 0000000000..2f96cdee00 --- /dev/null +++ b/bin/tests/system/nsupdate/ns1/max-ttl.db @@ -0,0 +1,35 @@ +; Copyright (C) 2004, 2007, 2009 Internet Systems Consortium, Inc. ("ISC") +; Copyright (C) 2000-2002 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: max-ttl.db,v 1.10 2009/01/21 23:47:27 tbox Exp $ + +$ORIGIN . +$TTL 300 ; 5 minutes +max-ttl.nil IN SOA ns1.max-ttl.nil. hostmaster.max-ttl.nil. ( + 1 ; serial + 2000 ; refresh (2000 seconds) + 2000 ; retry (2000 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +max-ttl.nil. NS ns1.max-ttl.nil. +ns1.max-ttl.nil. A 10.53.0.1 +max-ttl.nil. NS ns2.max-ttl.nil. +ns2.max-ttl.nil. A 10.53.0.2 + +$ORIGIN max-ttl.nil. +* MX 10 mail +a TXT "foo foo foo" + PTR foo.net. diff --git a/bin/tests/system/nsupdate/ns1/named.conf b/bin/tests/system/nsupdate/ns1/named.conf index a2bcded8a5..41a7b0016e 100644 --- a/bin/tests/system/nsupdate/ns1/named.conf +++ b/bin/tests/system/nsupdate/ns1/named.conf @@ -58,6 +58,15 @@ zone "example.nil" { allow-transfer { any; }; }; +zone "max-ttl.nil" { + type master; + file "max-ttl.db"; + max-zone-ttl 300; + check-integrity no; + allow-update { any; }; + allow-transfer { any; }; +}; + zone "other.nil" { type master; file "other.db"; diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh old mode 100644 new mode 100755 index b5d7cccf44..6a7b7b0f63 --- a/bin/tests/system/nsupdate/tests.sh +++ b/bin/tests/system/nsupdate/tests.sh @@ -552,6 +552,23 @@ if [ $ret -ne 0 ]; then status=1 fi +n=`expr $n + 1` +ret=0 +echo "I:check that ttl is capped by max-ttl ($n)" +$NSUPDATE < /dev/null || ret=1 +server 10.53.0.1 5300 +update add cap.max-ttl.nil. 600 A 10.10.10.3 +update add nocap.max-ttl.nil. 150 A 10.10.10.3 +send +END +sleep 2 +$DIG @10.53.0.1 -p 5300 cap.max-ttl.nil | grep "^cap.max-ttl.nil. 300" > /dev/null 2>&1 || ret=1 +$DIG @10.53.0.1 -p 5300 nocap.max-ttl.nil | grep "^nocap.max-ttl.nil. 150" > /dev/null 2>&1 || ret=1 +if [ $ret -ne 0 ]; then + echo "I:failed" + status=1 +fi + n=`expr $n + 1` ret=0 echo "I:add a record which is truncated when logged. ($n)" diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index b99e75a725..d6767711ce 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -4879,6 +4879,7 @@ badresp:1,adberr:0,findfail:0,valfail:0] lame-ttl number; max-ncache-ttl number; max-cache-ttl number; + max-zone-ttl number ; sig-validity-interval number number ; sig-signing-nodes number ; sig-signing-signatures number ; @@ -5693,6 +5694,34 @@ options { + max-zone-ttl + + + Specifies a maximum permissible TTL value. + When loading a zone file using a + of + text or raw, + any record encountered with a TTL higher than + will cause the zone to + be rejected. + + + This is useful in DNSSEC-signed zones because when + rolling to a new DNSKEY, the old key needs to remain + available until RRSIG records have expired from + caches. The option guarantees + that the largest TTL in the zone will be no higher + the set value. + + + (NOTE: Because map-format files + load directly into memory, this option cannot be + used with them.) + + + + + zone-statistics @@ -10966,6 +10995,7 @@ view "external" { inline-signing yes_or_no; zero-no-soa-ttl yes_or_no ; serial-update-method increment|unixtime; + max-zone-ttl number ; }; zone zone_name class { @@ -11097,6 +11127,7 @@ zone "." classstring ; masterfile-format (text|raw|map) ; allow-query { address_match_list }; + max-zone-ttl number ; }; zone zone_name class { @@ -12224,6 +12255,16 @@ example.com. NS ns2.example.net. + + max-zone-ttl + + + See the description of max-zone-ttl + in . + + + + dnssec-secure-to-insecure diff --git a/lib/bind9/check.c b/lib/bind9/check.c index 7e75c01008..93b584ba6e 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -1431,6 +1431,7 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, isc_boolean_t root = ISC_FALSE; const cfg_listelt_t *element; isc_boolean_t dlz; + dns_masterformat_t masterformat; static optionstable options[] = { { "allow-query", MASTERZONE | SLAVEZONE | STUBZONE | REDIRECTZONE | @@ -1460,6 +1461,7 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, { "min-retry-time", SLAVEZONE | STUBZONE | STREDIRECTZONE }, { "max-refresh-time", SLAVEZONE | STUBZONE | STREDIRECTZONE }, { "min-refresh-time", SLAVEZONE | STUBZONE | STREDIRECTZONE }, + { "max-zone-ttl", MASTERZONE | REDIRECTZONE }, { "dnssec-secure-to-insecure", MASTERZONE }, { "sig-re-signing-interval", MASTERZONE | SLAVEZONE }, { "sig-signing-nodes", MASTERZONE | SLAVEZONE }, @@ -1858,12 +1860,8 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, if (root) { if (voptions != NULL) (void)cfg_map_get(voptions, "forwarders", &obj); - if (obj == NULL) { - const cfg_obj_t *options = NULL; - (void)cfg_map_get(config, "options", &options); - if (options != NULL) - (void)cfg_map_get(options, "forwarders", &obj); - } + if (obj == NULL && goptions != NULL) + (void)cfg_map_get(goptions, "forwarders", &obj); } if (check_forward(zoptions, obj, logctx) != ISC_R_SUCCESS) result = ISC_R_FAILURE; @@ -1940,6 +1938,41 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, } } + + /* + * Check that max-zone-ttl isn't used with masterfile-format map + */ + masterformat = dns_masterformat_text; + obj = NULL; + (void)cfg_map_get(zoptions, "masterfile-format", &obj); + if (obj != NULL) { + const char *masterformatstr = cfg_obj_asstring(obj); + if (strcasecmp(masterformatstr, "text") == 0) + masterformat = dns_masterformat_text; + else if (strcasecmp(masterformatstr, "raw") == 0) + masterformat = dns_masterformat_raw; + else if (strcasecmp(masterformatstr, "map") == 0) + masterformat = dns_masterformat_map; + else + INSIST(0); + } + + if (masterformat == dns_masterformat_map) { + obj = NULL; + (void)cfg_map_get(zoptions, "max-zone-ttl", &obj); + if (obj == NULL && voptions != NULL) + (void)cfg_map_get(voptions, "max-zone-ttl", &obj); + if (obj == NULL && goptions !=NULL) + (void)cfg_map_get(goptions, "max-zone-ttl", &obj); + if (obj != NULL) { + cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, + "zone '%s': 'max-zone-ttl' is not " + "compatible with 'masterfile-format map'", + znamestr); + result = ISC_R_FAILURE; + } + } + /* * Warn if key-directory doesn't exist */ diff --git a/lib/dns/include/dns/master.h b/lib/dns/include/dns/master.h index 931e930844..998f1a7fac 100644 --- a/lib/dns/include/dns/master.h +++ b/lib/dns/include/dns/master.h @@ -58,6 +58,7 @@ #define DNS_MASTER_RESIGN 0x00002000 #define DNS_MASTER_KEY 0x00004000 /*%< Loading a key zone master file. */ #define DNS_MASTER_NOTTL 0x00008000 /*%< Don't require ttl. */ +#define DNS_MASTER_CHECKTTL 0x00010000 /*%< Check max-zone-ttl */ ISC_LANG_BEGINDECLS @@ -159,6 +160,19 @@ dns_master_loadfile4(const char *master_file, void *include_arg, isc_mem_t *mctx, dns_masterformat_t format); +isc_result_t +dns_master_loadfile5(const char *master_file, + dns_name_t *top, + dns_name_t *origin, + dns_rdataclass_t zclass, + unsigned int options, + isc_uint32_t resign, + dns_rdatacallbacks_t *callbacks, + dns_masterincludecb_t include_cb, + void *include_arg, isc_mem_t *mctx, + dns_masterformat_t format, + dns_ttl_t maxttl); + isc_result_t dns_master_loadstream(FILE *stream, dns_name_t *top, @@ -236,6 +250,21 @@ dns_master_loadfileinc4(const char *master_file, dns_masterincludecb_t include_cb, void *include_arg, isc_mem_t *mctx, dns_masterformat_t format); +isc_result_t +dns_master_loadfileinc5(const char *master_file, + dns_name_t *top, + dns_name_t *origin, + dns_rdataclass_t zclass, + unsigned int options, + isc_uint32_t resign, + dns_rdatacallbacks_t *callbacks, + isc_task_t *task, + dns_loaddonefunc_t done, void *done_arg, + dns_loadctx_t **ctxp, + dns_masterincludecb_t include_cb, void *include_arg, + isc_mem_t *mctx, dns_masterformat_t format, + isc_uint32_t maxttl); + isc_result_t dns_master_loadstreaminc(FILE *stream, dns_name_t *top, diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 5d28c2ac4c..3b7468463c 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -89,6 +89,12 @@ typedef enum { #define DNS_ZONEOPT_CHECKDUPRRFAIL 0x40000000U /*%< fatal check-dup-records failures */ #define DNS_ZONEOPT_CHECKSPF 0x80000000U /*%< check SPF records */ +/* + * The following zone options are shifted left into the + * higher-order 32 bits of the options. + */ +#define DNS_ZONEOPT2_CHECKTTL 0x00000001 /*%< check max-zone-ttl */ + #ifndef NOMINUM_PUBLIC /* * Nominum specific options build down. @@ -289,6 +295,30 @@ dns_zone_getfile(dns_zone_t *zone); *\li Pointer to null-terminated file name, or NULL. */ +void +dns_zone_setmaxttl(dns_zone_t *zone, isc_uint32_t maxttl); +/*%< + * Sets the max ttl of the zone. + * + * Requires: + *\li 'zone' to be valid initialised zone. + * + * Returns: + *\li void + */ + +dns_ttl_t +dns_zone_getmaxttl(dns_zone_t *zone); +/*%< + * Gets the max ttl of the zone. + * + * Requires: + *\li 'zone' to be valid initialised zone. + * + * Returns: + *\li isc_uint32_t maxttl. + */ + isc_result_t dns_zone_load(dns_zone_t *zone); @@ -630,10 +660,19 @@ dns_zone_unload(dns_zone_t *zone); */ void -dns_zone_setoption(dns_zone_t *zone, unsigned int option, isc_boolean_t value); +dns_zone_setoption(dns_zone_t *zone, unsigned int option, + isc_boolean_t value); +void +dns_zone_setoption2(dns_zone_t *zone, unsigned int option, + isc_boolean_t value); /*%< - * Set given options on ('value' == ISC_TRUE) or off ('value' == - * #ISC_FALSE). + * Set the given options on ('value' == ISC_TRUE) or off + * ('value' == #ISC_FALSE). + * + * dns_zone_setoption2() has been introduced because the number + * of options needed now exceeds the 32 bits in the zone->options + * field; it should be used set options with names beginning + * with DNS_ZONEOPT2_. * * Require: *\li 'zone' to be a valid zone. @@ -641,9 +680,17 @@ dns_zone_setoption(dns_zone_t *zone, unsigned int option, isc_boolean_t value); unsigned int dns_zone_getoptions(dns_zone_t *zone); +unsigned int +dns_zone_getoptions2(dns_zone_t *zone); /*%< * Returns the current zone options. * + * Callers should be aware there is now more than one set of zone + * options. dns_zone_getoptions2() has been introduced because the + * number of options needed now exceeds the 32 bits in the + * zone->options field. It returns the options whose names begin + * with DNS_ZONEOPT2_. + * * Require: *\li 'zone' to be a valid zone. */ diff --git a/lib/dns/master.c b/lib/dns/master.c index 93b8236bdc..9ad5d41d55 100644 --- a/lib/dns/master.c +++ b/lib/dns/master.c @@ -114,6 +114,9 @@ struct dns_loadctx { const char *filename); isc_result_t (*load)(dns_loadctx_t *lctx); + /* Members used by all formats */ + isc_uint32_t maxttl; + /* Members specific to the text format: */ isc_lex_t *lex; isc_boolean_t keep_lex; @@ -556,6 +559,8 @@ loadctx_create(dns_masterformat_t format, isc_mem_t *mctx, if (result != ISC_R_SUCCESS) goto cleanup_ctx; + lctx->maxttl = 0; + lctx->format = format; switch (format) { default: @@ -1605,8 +1610,9 @@ load_text(dns_loadctx_t *lctx) { GETTOKEN(lctx->lex, 0, &token, ISC_FALSE); explicit_ttl = ISC_FALSE; - if (dns_ttl_fromtext(&token.value.as_textregion, &lctx->ttl) - == ISC_R_SUCCESS) { + result = dns_ttl_fromtext(&token.value.as_textregion, + &lctx->ttl); + if (result == ISC_R_SUCCESS) { limit_ttl(callbacks, source, line, &lctx->ttl); explicit_ttl = ISC_TRUE; lctx->ttl_known = ISC_TRUE; @@ -1953,6 +1959,17 @@ load_text(dns_loadctx_t *lctx) { lctx->ttl = this->ttl; } + if ((lctx->options & DNS_MASTER_CHECKTTL) != 0 && + lctx->ttl > lctx->maxttl) + { + (callbacks->error)(callbacks, + "dns_master_load: %s:%lu: " + "TTL %d exceeds configured max-zone-ttl %d", + source, line, lctx->ttl, lctx->maxttl); + result = ISC_R_RANGE; + goto log_and_cleanup; + } + ISC_LIST_APPEND(this->rdata, &rdata[rdcount], link); if (ictx->glue != NULL) ictx->glue_line = line; @@ -2381,6 +2398,18 @@ load_raw(dns_loadctx_t *lctx) { if (result != ISC_R_SUCCESS) goto cleanup; + if ((lctx->options & DNS_MASTER_CHECKTTL) != 0 && + rdatalist.ttl > lctx->maxttl) + { + (callbacks->error)(callbacks, + "dns_master_load: " + "TTL %d exceeds configured " + "max-zone-ttl %d", + rdatalist.ttl, lctx->maxttl); + result = ISC_R_RANGE; + goto cleanup; + } + /* Rdata contents. */ if (rdcount > rdata_size) { dns_rdata_t *new_rdata = NULL; @@ -2510,9 +2539,9 @@ dns_master_loadfile(const char *master_file, dns_name_t *top, dns_rdataclass_t zclass, unsigned int options, dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) { - return (dns_master_loadfile4(master_file, top, origin, zclass, options, - 0, callbacks, NULL, NULL, mctx, - dns_masterformat_text)); + return (dns_master_loadfile5(master_file, top, origin, zclass, + options, 0, callbacks, NULL, NULL, + mctx, dns_masterformat_text, 0)); } isc_result_t @@ -2522,8 +2551,9 @@ dns_master_loadfile2(const char *master_file, dns_name_t *top, dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx, dns_masterformat_t format) { - return (dns_master_loadfile4(master_file, top, origin, zclass, options, - 0, callbacks, NULL, NULL, mctx, format)); + return (dns_master_loadfile5(master_file, top, origin, zclass, + options, 0, callbacks, NULL, NULL, + mctx, format, 0)); } isc_result_t @@ -2533,9 +2563,9 @@ dns_master_loadfile3(const char *master_file, dns_name_t *top, dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx, dns_masterformat_t format) { - return (dns_master_loadfile4(master_file, top, origin, zclass, options, - resign, callbacks, NULL, NULL, mctx, - format)); + return (dns_master_loadfile5(master_file, top, origin, zclass, + options, resign, callbacks, NULL, NULL, + mctx, format, 0)); } isc_result_t @@ -2545,6 +2575,21 @@ dns_master_loadfile4(const char *master_file, dns_name_t *top, dns_rdatacallbacks_t *callbacks, dns_masterincludecb_t include_cb, void *include_arg, isc_mem_t *mctx, dns_masterformat_t format) +{ + return (dns_master_loadfile5(master_file, top, origin, zclass, + options, resign, callbacks, + include_cb, include_arg, + mctx, format, 0)); +} + +isc_result_t +dns_master_loadfile5(const char *master_file, dns_name_t *top, + dns_name_t *origin, dns_rdataclass_t zclass, + unsigned int options, isc_uint32_t resign, + dns_rdatacallbacks_t *callbacks, + dns_masterincludecb_t include_cb, void *include_arg, + isc_mem_t *mctx, dns_masterformat_t format, + dns_ttl_t maxttl) { dns_loadctx_t *lctx = NULL; isc_result_t result; @@ -2555,6 +2600,8 @@ dns_master_loadfile4(const char *master_file, dns_name_t *top, if (result != ISC_R_SUCCESS) return (result); + lctx->maxttl = maxttl; + result = (lctx->openfile)(lctx, master_file); if (result != ISC_R_SUCCESS) goto cleanup; @@ -2618,6 +2665,24 @@ dns_master_loadfileinc4(const char *master_file, dns_name_t *top, void *done_arg, dns_loadctx_t **lctxp, dns_masterincludecb_t include_cb, void *include_arg, isc_mem_t *mctx, dns_masterformat_t format) +{ + options &= ~DNS_MASTER_CHECKTTL; + return (dns_master_loadfileinc5(master_file, top, origin, zclass, + options, resign, callbacks, task, + done, done_arg, lctxp, include_cb, + include_arg, mctx, format, 0)); +} + +isc_result_t +dns_master_loadfileinc5(const char *master_file, dns_name_t *top, + dns_name_t *origin, dns_rdataclass_t zclass, + unsigned int options, isc_uint32_t resign, + dns_rdatacallbacks_t *callbacks, + isc_task_t *task, dns_loaddonefunc_t done, + void *done_arg, dns_loadctx_t **lctxp, + dns_masterincludecb_t include_cb, void *include_arg, + isc_mem_t *mctx, dns_masterformat_t format, + isc_uint32_t maxttl) { dns_loadctx_t *lctx = NULL; isc_result_t result; @@ -2631,6 +2696,8 @@ dns_master_loadfileinc4(const char *master_file, dns_name_t *top, if (result != ISC_R_SUCCESS) return (result); + lctx->maxttl = maxttl; + result = (lctx->openfile)(lctx, master_file); if (result != ISC_R_SUCCESS) goto cleanup; diff --git a/lib/dns/ttl.c b/lib/dns/ttl.c index c794859064..d8ed357449 100644 --- a/lib/dns/ttl.c +++ b/lib/dns/ttl.c @@ -142,14 +142,14 @@ dns_ttl_fromtext(isc_textregion_t *source, isc_uint32_t *ttl) { isc_result_t result; result = bind_ttl(source, ttl); - if (result != ISC_R_SUCCESS) + if (result != ISC_R_SUCCESS && result != ISC_R_RANGE) result = DNS_R_BADTTL; return (result); } static isc_result_t bind_ttl(isc_textregion_t *source, isc_uint32_t *ttl) { - isc_uint32_t tmp = 0; + isc_uint64_t tmp = 0ULL; isc_uint32_t n; char *s; char buf[64]; @@ -179,32 +179,32 @@ bind_ttl(isc_textregion_t *source, isc_uint32_t *ttl) { switch (*s) { case 'w': case 'W': - tmp += n * 7 * 24 * 3600; + tmp += (isc_uint64_t) n * 7 * 24 * 3600; s++; break; case 'd': case 'D': - tmp += n * 24 * 3600; + tmp += (isc_uint64_t) n * 24 * 3600; s++; break; case 'h': case 'H': - tmp += n * 3600; + tmp += (isc_uint64_t) n * 3600; s++; break; case 'm': case 'M': - tmp += n * 60; + tmp += (isc_uint64_t) n * 60; s++; break; case 's': case 'S': - tmp += n; + tmp += (isc_uint64_t) n; s++; break; case '\0': /* Plain number? */ - if (tmp != 0) + if (tmp != 0ULL) return (DNS_R_SYNTAX); tmp = n; break; @@ -212,6 +212,10 @@ bind_ttl(isc_textregion_t *source, isc_uint32_t *ttl) { return (DNS_R_SYNTAX); } } while (*s != '\0'); - *ttl = tmp; + + if (tmp > 0xffffffffULL) + return (ISC_R_RANGE); + + *ttl = (isc_uint32_t)(tmp & 0xffffffffUL); return (ISC_R_SUCCESS); } diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 8c41d8ea81..3b3a81ba43 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -227,6 +227,7 @@ struct dns_zone { dns_zonetype_t type; unsigned int flags; unsigned int options; + unsigned int options2; unsigned int db_argc; char **db_argv; isc_time_t expiretime; @@ -395,6 +396,12 @@ struct dns_zone { isc_boolean_t sourceserialset; isc_uint32_t sourceserial; + + /*% + * maximum zone ttl + */ + dns_ttl_t maxttl; + }; typedef struct { @@ -460,6 +467,7 @@ typedef struct { #define DNS_ZONEFLG_SENDSECURE 0x40000000U #define DNS_ZONE_OPTION(z,o) (((z)->options & (o)) != 0) +#define DNS_ZONE_OPTION2(z,o) (((z)->options2 & (o)) != 0) #define DNS_ZONEKEY_OPTION(z,o) (((z)->keyopts & (o)) != 0) /* Flags for zone_load() */ @@ -909,6 +917,7 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { zone->mastersok = NULL; zone->masterscnt = 0; zone->curmaster = 0; + zone->maxttl = 0; zone->notify = NULL; zone->notifykeynames = NULL; zone->notifydscp = NULL; @@ -1516,6 +1525,28 @@ dns_zone_getfile(dns_zone_t *zone) { return (zone->masterfile); } +dns_ttl_t +dns_zone_getmaxttl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->maxttl); +} + +void +dns_zone_setmaxttl(dns_zone_t *zone, dns_ttl_t maxttl) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (maxttl != 0) + zone->options2 |= DNS_ZONEOPT2_CHECKTTL; + else + zone->options2 &= ~DNS_ZONEOPT2_CHECKTTL; + zone->maxttl = maxttl; + UNLOCK_ZONE(zone); + + return; +} + static isc_result_t default_journal(dns_zone_t *zone) { isc_result_t result; @@ -2047,6 +2078,8 @@ get_master_options(dns_zone_t *zone) { options |= DNS_MASTER_CHECKMXFAIL; if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKWILDCARD)) options |= DNS_MASTER_CHECKWILDCARD; + if (DNS_ZONE_OPTION2(zone, DNS_ZONEOPT2_CHECKTTL)) + options |= DNS_MASTER_CHECKTTL; return (options); } @@ -2103,7 +2136,7 @@ zone_gotreadhandle(isc_task_t *task, isc_event_t *event) { options = get_master_options(load->zone); - result = dns_master_loadfileinc4(load->zone->masterfile, + result = dns_master_loadfileinc5(load->zone->masterfile, dns_db_origin(load->db), dns_db_origin(load->db), load->zone->rdclass, options, 0, @@ -2112,7 +2145,8 @@ zone_gotreadhandle(isc_task_t *task, isc_event_t *event) { &load->zone->lctx, zone_registerinclude, load->zone, load->zone->mctx, - load->zone->masterformat); + load->zone->masterformat, + load->zone->maxttl); if (result != ISC_R_SUCCESS && result != DNS_R_CONTINUE && result != DNS_R_SEENINCLUDE) goto fail; @@ -2268,13 +2302,14 @@ zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime) { zone_idetach(&callbacks.zone); return (result); } - result = dns_master_loadfile4(zone->masterfile, + result = dns_master_loadfile5(zone->masterfile, &zone->origin, &zone->origin, zone->rdclass, options, 0, &callbacks, zone_registerinclude, zone, zone->mctx, - zone->masterformat); + zone->masterformat, + zone->maxttl); tresult = dns_db_endload(db, &callbacks); if (result == ISC_R_SUCCESS) result = tresult; @@ -4986,7 +5021,8 @@ dns_zone_setflag(dns_zone_t *zone, unsigned int flags, isc_boolean_t value) { } void -dns_zone_setoption(dns_zone_t *zone, unsigned int option, isc_boolean_t value) +dns_zone_setoption(dns_zone_t *zone, unsigned int option, + isc_boolean_t value) { REQUIRE(DNS_ZONE_VALID(zone)); @@ -4998,14 +5034,34 @@ dns_zone_setoption(dns_zone_t *zone, unsigned int option, isc_boolean_t value) UNLOCK_ZONE(zone); } +void +dns_zone_setoption2(dns_zone_t *zone, unsigned int option, + isc_boolean_t value) +{ + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (value) + zone->options2 |= option; + else + zone->options2 &= ~option; + UNLOCK_ZONE(zone); +} + unsigned int dns_zone_getoptions(dns_zone_t *zone) { - REQUIRE(DNS_ZONE_VALID(zone)); return (zone->options); } +unsigned int +dns_zone_getoptions2(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->options2); +} + void dns_zone_setkeyopt(dns_zone_t *zone, unsigned int keyopt, isc_boolean_t value) { diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 65b26e5b81..fec718ac3b 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -29,6 +29,9 @@ #include #include +#include +#include + #include #include #include @@ -111,6 +114,7 @@ static cfg_type_t cfg_type_logging; static cfg_type_t cfg_type_logseverity; static cfg_type_t cfg_type_lwres; static cfg_type_t cfg_type_masterselement; +static cfg_type_t cfg_type_maxttl; static cfg_type_t cfg_type_nameportiplist; static cfg_type_t cfg_type_negated; static cfg_type_t cfg_type_notifytype; @@ -1641,6 +1645,7 @@ zone_clauses[] = { { "max-transfer-idle-out", &cfg_type_uint32, 0 }, { "max-transfer-time-in", &cfg_type_uint32, 0 }, { "max-transfer-time-out", &cfg_type_uint32, 0 }, + { "max-zone-ttl", &cfg_type_maxttl, 0 }, { "min-refresh-time", &cfg_type_uint32, 0 }, { "min-retry-time", &cfg_type_uint32, 0 }, { "multi-master", &cfg_type_boolean, 0 }, @@ -3155,3 +3160,55 @@ static cfg_type_t cfg_type_masterselement = { "masters_element", parse_masterselement, NULL, doc_masterselement, NULL, NULL }; + +static isc_result_t +parse_maxttlval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + isc_uint32_t ttl; + + UNUSED(type); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type != isc_tokentype_string) { + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + + result = dns_ttl_fromtext(&pctx->token.value.as_textregion, &ttl); + if (result == ISC_R_RANGE ) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "TTL out of range "); + return (result); + } else if (result != ISC_R_SUCCESS) + goto cleanup; + + CHECK(cfg_create_obj(pctx, &cfg_type_uint32, &obj)); + obj->value.uint32 = ttl; + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected integer and optional unit"); + return (result); +} + +/*% + * A size value (number + optional unit). + */ +static cfg_type_t cfg_type_maxttlval = { + "maxttlval", parse_maxttlval, cfg_print_uint64, cfg_doc_terminal, + &cfg_rep_uint64, NULL }; + +static isc_result_t +parse_maxttl(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_enum_or_other(pctx, type, &cfg_type_maxttlval, ret)); +} + +/*% + * A size or "unlimited", but not "default". + */ +static const char *maxttl_enums[] = { "unlimited", NULL }; +static cfg_type_t cfg_type_maxttl = { + "maxttl_no_default", parse_maxttl, cfg_print_ustring, cfg_doc_terminal, + &cfg_rep_string, maxttl_enums +};