From: Matthijs Mekking Date: Mon, 2 Sep 2019 13:46:28 +0000 (+0200) Subject: Extend ttlval to accept ISO 8601 durations X-Git-Tag: v9.15.6~26^2~41 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b7c5bfb203ea547f552e37c4ae14663c92953764;p=thirdparty%2Fbind9.git Extend ttlval to accept ISO 8601 durations The ttlval configuration types are replaced by duration configuration types. The duration is an ISO 8601 duration that is going to be used for DNSSEC key timings such as key lifetimes, signature resign intervals and refresh periods, etc. But it is also still allowed to use the BIND ttlval ways of configuring intervals (number plus optional unit). A duration is stored as an array of 7 different time parts. A duration can either be expressed in weeks, or in a combination of the other datetime indicators. Add several unit tests to ensure the correct value is parsed given different string values. --- diff --git a/bin/check/named-checkconf.c b/bin/check/named-checkconf.c index a0dea04667a..c8b12911bbe 100644 --- a/bin/check/named-checkconf.c +++ b/bin/check/named-checkconf.c @@ -421,7 +421,7 @@ configure_zone(const char *vclass, const char *view, obj = NULL; if (get_maps(maps, "max-zone-ttl", &obj)) { - maxttl = cfg_obj_asuint32(obj); + maxttl = cfg_obj_asduration(obj); zone_options |= DNS_ZONEOPT_CHECKTTL; } diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook index a013873e18b..ecae8014cc4 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -208,7 +208,7 @@ options { [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... } ] [ zone-directory quoted_string ] [ - in-memory boolean ] [ min-update-interval ttlval ]; ... }; + in-memory boolean ] [ min-update-interval duration ]; ... }; check-dup-records ( fail | warn | ignore ); check-integrity boolean; check-mx ( fail | warn | ignore ); @@ -290,18 +290,18 @@ options { fstrm-set-output-notify-threshold integer; fstrm-set-output-queue-model ( mpsc | spsc ); fstrm-set-output-queue-size integer; - fstrm-set-reopen-interval ttlval; + fstrm-set-reopen-interval duration; geoip-directory ( quoted_string | none ); glue-cache boolean; heartbeat-interval integer; hostname ( quoted_string | none ); inline-signing boolean; - interface-interval ttlval; + interface-interval duration; ixfr-from-differences ( primary | master | secondary | slave | boolean ); keep-response-order { address_match_element; ... }; key-directory quoted_string; - lame-ttl ttlval; + lame-ttl duration; listen-on [ port integer ] [ dscp integer ] { address_match_element; ... }; @@ -315,28 +315,28 @@ options { masterfile-style ( full | relative ); match-mapped-addresses boolean; max-cache-size ( default | unlimited | sizeval | percentage ); - max-cache-ttl ttlval; + max-cache-ttl duration; max-clients-per-query integer; max-journal-size ( default | unlimited | sizeval ); - max-ncache-ttl ttlval; + max-ncache-ttl duration; max-records integer; max-recursion-depth integer; max-recursion-queries integer; max-refresh-time integer; max-retry-time integer; max-rsa-exponent-size integer; - max-stale-ttl ttlval; + max-stale-ttl duration; max-transfer-idle-in integer; max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; max-udp-size integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); memstatistics boolean; memstatistics-file quoted_string; message-compression boolean; - min-cache-ttl ttlval; - min-ncache-ttl ttlval; + min-cache-ttl duration; + min-ncache-ttl duration; min-refresh-time integer; min-retry-time integer; minimal-any boolean; @@ -353,8 +353,8 @@ options { notify-source-v6 ( ipv6_address | * ) [ port ( integer | * ) ] [ dscp integer ]; notify-to-soa boolean; - nta-lifetime ttlval; - nta-recheck ttlval; + nta-lifetime duration; + nta-recheck duration; nxdomain-redirect string; pid-file ( quoted_string | none ); port integer; @@ -401,13 +401,13 @@ options { response-padding { address_match_element; ... } block-size integer; response-policy { zone string [ add-soa boolean ] [ log - boolean ] [ max-policy-ttl ttlval ] [ min-update-interval - ttlval ] [ policy ( cname | disabled | drop | given | no-op | + boolean ] [ max-policy-ttl duration ] [ min-update-interval + duration ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only quoted_string ) ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ]; ... } [ add-soa boolean ] [ - break-dnssec boolean ] [ max-policy-ttl ttlval ] [ - min-update-interval ttlval ] [ min-ns-dots integer ] [ + break-dnssec boolean ] [ max-policy-ttl duration ] [ + min-update-interval duration ] [ min-ns-dots integer ] [ nsip-wait-recurse boolean ] [ qname-wait-recurse boolean ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ] [ dnsrps-enable boolean ] [ @@ -421,7 +421,7 @@ options { serial-query-rate integer; serial-update-method ( date | increment | unixtime ); server-id ( quoted_string | none | hostname ); - servfail-ttl ttlval; + servfail-ttl duration; session-keyalg string; session-keyfile ( quoted_string | none ); session-keyname string; @@ -432,7 +432,7 @@ options { sortlist { address_match_element; ... }; stacksize ( default | unlimited | sizeval ); stale-answer-enable boolean; - stale-answer-ttl ttlval; + stale-answer-ttl duration; startup-notify-rate integer; statistics-file quoted_string; synth-from-dnssec boolean; @@ -564,7 +564,7 @@ view string [ class ] { [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... } ] [ zone-directory quoted_string ] [ - in-memory boolean ] [ min-update-interval ttlval ]; ... }; + in-memory boolean ] [ min-update-interval duration ]; ... }; check-dup-records ( fail | warn | ignore ); check-integrity boolean; check-mx ( fail | warn | ignore ); @@ -642,7 +642,7 @@ view string [ class ] { secret string; }; key-directory quoted_string; - lame-ttl ttlval; + lame-ttl duration; lmdb-mapsize sizeval; managed-keys { string ( static-key | initial-key @@ -655,25 +655,25 @@ view string [ class ] { match-destinations { address_match_element; ... }; match-recursive-only boolean; max-cache-size ( default | unlimited | sizeval | percentage ); - max-cache-ttl ttlval; + max-cache-ttl duration; max-clients-per-query integer; max-journal-size ( default | unlimited | sizeval ); - max-ncache-ttl ttlval; + max-ncache-ttl duration; max-records integer; max-recursion-depth integer; max-recursion-queries integer; max-refresh-time integer; max-retry-time integer; - max-stale-ttl ttlval; + max-stale-ttl duration; max-transfer-idle-in integer; max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; max-udp-size integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); message-compression boolean; - min-cache-ttl ttlval; - min-ncache-ttl ttlval; + min-cache-ttl duration; + min-ncache-ttl duration; min-refresh-time integer; min-retry-time integer; minimal-any boolean; @@ -689,8 +689,8 @@ view string [ class ] { notify-source-v6 ( ipv6_address | * ) [ port ( integer | * ) ] [ dscp integer ]; notify-to-soa boolean; - nta-lifetime ttlval; - nta-recheck ttlval; + nta-lifetime duration; + nta-recheck duration; nxdomain-redirect string; plugin ( query ) string [ { unspecified-text } ]; @@ -732,13 +732,13 @@ view string [ class ] { response-padding { address_match_element; ... } block-size integer; response-policy { zone string [ add-soa boolean ] [ log - boolean ] [ max-policy-ttl ttlval ] [ min-update-interval - ttlval ] [ policy ( cname | disabled | drop | given | no-op | + boolean ] [ max-policy-ttl duration ] [ min-update-interval + duration ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only quoted_string ) ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ]; ... } [ add-soa boolean ] [ - break-dnssec boolean ] [ max-policy-ttl ttlval ] [ - min-update-interval ttlval ] [ min-ns-dots integer ] [ + break-dnssec boolean ] [ max-policy-ttl duration ] [ + min-update-interval duration ] [ min-ns-dots integer ] [ nsip-wait-recurse boolean ] [ qname-wait-recurse boolean ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ] [ dnsrps-enable boolean ] [ @@ -783,14 +783,14 @@ view string [ class ] { integer | * ) ] [ dscp integer ]; transfers integer; }; - servfail-ttl ttlval; + servfail-ttl duration; sig-signing-nodes integer; sig-signing-signatures integer; sig-signing-type integer; sig-validity-interval integer [ integer ]; sortlist { address_match_element; ... }; stale-answer-enable boolean; - stale-answer-ttl ttlval; + stale-answer-ttl duration; synth-from-dnssec boolean; transfer-format ( many-answers | one-answer ); transfer-source ( ipv4_address | * ) [ port ( integer | * ) ] [ @@ -867,7 +867,7 @@ view string [ class ] { max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); min-refresh-time integer; min-retry-time integer; multi-master boolean; @@ -967,7 +967,7 @@ zone string [ class ] { max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); min-refresh-time integer; min-retry-time integer; multi-master boolean; diff --git a/bin/named/server.c b/bin/named/server.c index e7f87e349e9..8730225864e 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -2039,7 +2039,13 @@ conf_dnsrps_num(const cfg_obj_t *obj, const char *name, return; } - conf_dnsrps_sadd(ctx, " %s %d", name, cfg_obj_asuint32(sub_obj)); + if (cfg_obj_isduration(sub_obj)) { + conf_dnsrps_sadd(ctx, " %s %d", name, + cfg_obj_asduration(sub_obj)); + } else { + conf_dnsrps_sadd(ctx, " %s %d", name, + cfg_obj_asuint32(sub_obj)); + } } /* @@ -2221,15 +2227,15 @@ configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element, } obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); - if (cfg_obj_isuint32(obj)) { - zone->max_policy_ttl = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + zone->max_policy_ttl = cfg_obj_asduration(obj); } else { zone->max_policy_ttl = ttl_default; } obj = cfg_tuple_get(rpz_obj, "min-update-interval"); - if (cfg_obj_isuint32(obj)) { - zone->min_update_interval = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + zone->min_update_interval = cfg_obj_asduration(obj); } else { zone->min_update_interval = minupdateinterval_default; } @@ -2448,14 +2454,14 @@ configure_rpz(dns_view_t *view, const cfg_obj_t **maps, } sub_obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); - if (cfg_obj_isuint32(sub_obj)) - ttl_default = cfg_obj_asuint32(sub_obj); + if (cfg_obj_isduration(sub_obj)) + ttl_default = cfg_obj_asduration(sub_obj); else ttl_default = DNS_RPZ_MAX_TTL_DEFAULT; sub_obj = cfg_tuple_get(rpz_obj, "min-update-interval"); - if (cfg_obj_isuint32(sub_obj)) - minupdateinterval_default = cfg_obj_asuint32(sub_obj); + if (cfg_obj_isduration(sub_obj)) + minupdateinterval_default = cfg_obj_asduration(sub_obj); else minupdateinterval_default = DNS_RPZ_MINUPDATEINTERVAL_DEFAULT; @@ -2992,8 +2998,8 @@ configure_catz_zone(dns_view_t *view, const cfg_obj_t *config, } obj = cfg_tuple_get(catz_obj, "min-update-interval"); - if (obj != NULL && cfg_obj_isuint32(obj)) - opts->min_update_interval = cfg_obj_asuint32(obj); + if (obj != NULL && cfg_obj_isduration(obj)) + opts->min_update_interval = cfg_obj_asduration(obj); cleanup: if (pview != NULL) @@ -3641,7 +3647,7 @@ configure_dnstap(const cfg_obj_t **maps, dns_view_t *view) { result = named_config_get(maps, "fstrm-set-reopen-interval", &obj); if (result == ISC_R_SUCCESS) { - i = cfg_obj_asuint32(obj); + i = cfg_obj_asduration(obj); fstrm_iothr_options_set_reopen_interval(fopt, i); } @@ -4217,22 +4223,22 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "max-cache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->maxcachettl = cfg_obj_asuint32(obj); + view->maxcachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "max-ncache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->maxncachettl = cfg_obj_asuint32(obj); + view->maxncachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "min-cache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->mincachettl = cfg_obj_asuint32(obj); + view->mincachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "min-ncache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->minncachettl = cfg_obj_asuint32(obj); + view->minncachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "synth-from-dnssec", &obj); @@ -4242,7 +4248,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "max-stale-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - max_stale_ttl = ISC_MAX(cfg_obj_asuint32(obj), 1); + max_stale_ttl = ISC_MAX(cfg_obj_asduration(obj), 1); obj = NULL; result = named_config_get(maps, "stale-answer-enable", &obj); @@ -4392,7 +4398,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "stale-answer-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->staleanswerttl = ISC_MAX(cfg_obj_asuint32(obj), 1); + view->staleanswerttl = ISC_MAX(cfg_obj_asduration(obj), 1); /* * Resolver. @@ -4512,7 +4518,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "lame-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - lame_ttl = cfg_obj_asuint32(obj); + lame_ttl = cfg_obj_asduration(obj); if (lame_ttl > 1800) lame_ttl = 1800; dns_resolver_setlamettl(view->resolver, lame_ttl); @@ -5216,12 +5222,12 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "nta-recheck", &obj); INSIST(result == ISC_R_SUCCESS); - view->nta_recheck = cfg_obj_asuint32(obj); + view->nta_recheck = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "nta-lifetime", &obj); INSIST(result == ISC_R_SUCCESS); - view->nta_lifetime = cfg_obj_asuint32(obj); + view->nta_lifetime = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "preferred-glue", &obj); @@ -5464,7 +5470,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "servfail-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - fail_ttl = cfg_obj_asuint32(obj); + fail_ttl = cfg_obj_asduration(obj); if (fail_ttl > 30) fail_ttl = 30; dns_view_setfailttl(view, fail_ttl); @@ -8560,7 +8566,7 @@ load_configuration(const char *filename, named_server_t *server, obj = NULL; result = named_config_get(maps, "interface-interval", &obj); INSIST(result == ISC_R_SUCCESS); - interface_interval = cfg_obj_asuint32(obj) * 60; + interface_interval = cfg_obj_asduration(obj); if (interface_interval == 0) { CHECK(isc_timer_reset(server->interface_timer, isc_timertype_inactive, diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index aebea7aa1a0..70b3bdba77a 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1045,8 +1045,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, } else if (result == ISC_R_SUCCESS) { dns_ttl_t maxttl = 0; /* unlimited */ - if (cfg_obj_isuint32(obj)) - maxttl = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) + maxttl = cfg_obj_asduration(obj); dns_zone_setmaxttl(zone, maxttl); if (raw != NULL) dns_zone_setmaxttl(raw, maxttl); diff --git a/doc/arm/master.zoneopt.xml b/doc/arm/master.zoneopt.xml index d6124458809..0ed86ecc750 100644 --- a/doc/arm/master.zoneopt.xml +++ b/doc/arm/master.zoneopt.xml @@ -51,7 +51,7 @@ max-records integer; max-transfer-idle-out integer; max-transfer-time-out integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); notify ( explicit | master-only | boolean ); notify-delay integer; notify-source ( ipv4_address | * ) [ port ( integer | * ) ] [ dscp integer ]; diff --git a/doc/arm/options.grammar.xml b/doc/arm/options.grammar.xml index 3cd76e6d3dd..64a95defb49 100644 --- a/doc/arm/options.grammar.xml +++ b/doc/arm/options.grammar.xml @@ -45,7 +45,7 @@ [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... } ] [ zone-directory quoted_string ] [ - in-memory boolean ] [ min-update-interval ttlval ]; ... }; + in-memory boolean ] [ min-update-interval duration ]; ... }; check-dup-records ( fail | warn | ignore ); check-integrity boolean; check-mx ( fail | warn | ignore ); @@ -127,18 +127,18 @@ fstrm-set-output-notify-threshold integer; fstrm-set-output-queue-model ( mpsc | spsc ); fstrm-set-output-queue-size integer; - fstrm-set-reopen-interval ttlval; + fstrm-set-reopen-interval duration; geoip-directory ( quoted_string | none ); glue-cache boolean; heartbeat-interval integer; hostname ( quoted_string | none ); inline-signing boolean; - interface-interval ttlval; + interface-interval duration; ixfr-from-differences ( primary | master | secondary | slave | boolean ); keep-response-order { address_match_element; ... }; key-directory quoted_string; - lame-ttl ttlval; + lame-ttl duration; listen-on [ port integer ] [ dscp integer ] { address_match_element; ... }; @@ -152,28 +152,28 @@ masterfile-style ( full | relative ); match-mapped-addresses boolean; max-cache-size ( default | unlimited | sizeval | percentage ); - max-cache-ttl ttlval; + max-cache-ttl duration; max-clients-per-query integer; max-journal-size ( default | unlimited | sizeval ); - max-ncache-ttl ttlval; + max-ncache-ttl duration; max-records integer; max-recursion-depth integer; max-recursion-queries integer; max-refresh-time integer; max-retry-time integer; max-rsa-exponent-size integer; - max-stale-ttl ttlval; + max-stale-ttl duration; max-transfer-idle-in integer; max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; max-udp-size integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); memstatistics boolean; memstatistics-file quoted_string; message-compression boolean; - min-cache-ttl ttlval; - min-ncache-ttl ttlval; + min-cache-ttl duration; + min-ncache-ttl duration; min-refresh-time integer; min-retry-time integer; minimal-any boolean; @@ -190,8 +190,8 @@ notify-source-v6 ( ipv6_address | * ) [ port ( integer | * ) ] [ dscp integer ]; notify-to-soa boolean; - nta-lifetime ttlval; - nta-recheck ttlval; + nta-lifetime duration; + nta-recheck duration; nxdomain-redirect string; pid-file ( quoted_string | none ); port integer; @@ -238,13 +238,13 @@ response-padding { address_match_element; ... } block-size integer; response-policy { zone string [ add-soa boolean ] [ log - boolean ] [ max-policy-ttl ttlval ] [ min-update-interval - ttlval ] [ policy ( cname | disabled | drop | given | no-op | + boolean ] [ max-policy-ttl duration ] [ min-update-interval + duration ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only quoted_string ) ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ]; ... } [ add-soa boolean ] [ - break-dnssec boolean ] [ max-policy-ttl ttlval ] [ - min-update-interval ttlval ] [ min-ns-dots integer ] [ + break-dnssec boolean ] [ max-policy-ttl duration ] [ + min-update-interval duration ] [ min-ns-dots integer ] [ nsip-wait-recurse boolean ] [ qname-wait-recurse boolean ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ] [ dnsrps-enable boolean ] [ @@ -258,7 +258,7 @@ serial-query-rate integer; serial-update-method ( date | increment | unixtime ); server-id ( quoted_string | none | hostname ); - servfail-ttl ttlval; + servfail-ttl duration; session-keyalg string; session-keyfile ( quoted_string | none ); session-keyname string; @@ -269,7 +269,7 @@ sortlist { address_match_element; ... }; stacksize ( default | unlimited | sizeval ); stale-answer-enable boolean; - stale-answer-ttl ttlval; + stale-answer-ttl duration; startup-notify-rate integer; statistics-file quoted_string; synth-from-dnssec boolean; diff --git a/doc/arm/redirect.zoneopt.xml b/doc/arm/redirect.zoneopt.xml index 335cfa03877..91622cc2c1a 100644 --- a/doc/arm/redirect.zoneopt.xml +++ b/doc/arm/redirect.zoneopt.xml @@ -21,7 +21,7 @@ masterfile-style ( full | relative ); masters [ port integer ] [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... }; max-records integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); zone-statistics ( full | terse | none | boolean ); }; diff --git a/lib/bind9/check.c b/lib/bind9/check.c index 1426c418672..86d74821572 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -929,7 +929,11 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, (void)cfg_map_get(options, intervals[i].name, &obj); if (obj == NULL) continue; - val = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + val = cfg_obj_asduration(obj); + } else { + val = cfg_obj_asuint32(obj); + } if (val > intervals[i].max) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s '%u' is out of range (0..%u)", @@ -1176,7 +1180,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, obj = NULL; (void)cfg_map_get(options, "nta-lifetime", &obj); if (obj != NULL) { - lifetime = cfg_obj_asuint32(obj); + lifetime = cfg_obj_asduration(obj); if (lifetime > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-lifetime' cannot exceed one week"); @@ -1193,7 +1197,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, obj = NULL; (void)cfg_map_get(options, "nta-recheck", &obj); if (obj != NULL) { - uint32_t recheck = cfg_obj_asuint32(obj); + uint32_t recheck = cfg_obj_asduration(obj); if (recheck > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-recheck' cannot exceed one week"); @@ -1271,7 +1275,11 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (obj == NULL) continue; - value = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + value = cfg_obj_asduration(obj); + } else { + value = cfg_obj_asuint32(obj); + } if (value < fstrm[i].min || (fstrm[i].max != 0U && value > fstrm[i].max)) { if (fstrm[i].max != 0U) diff --git a/lib/isccfg/include/isccfg/cfg.h b/lib/isccfg/include/isccfg/cfg.h index 35729a49919..2aefdce1100 100644 --- a/lib/isccfg/include/isccfg/cfg.h +++ b/lib/isccfg/include/isccfg/cfg.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -331,6 +332,24 @@ cfg_obj_aspercentage(const cfg_obj_t *obj); * \li A 32-bit unsigned integer. */ +bool +cfg_obj_isduration(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of duration type. + */ + +uint32_t +cfg_obj_asduration(const cfg_obj_t *obj); +/*%< + * Returns the value of a configuration object of duration + * + * Requires: + * \li 'obj' points to a valid configuration object of duration type. + * + * Returns: + * \li A duration in seconds. + */ + bool cfg_obj_isstring(const cfg_obj_t *obj); /*%< diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h index e931282a0f4..ad3dd81d176 100644 --- a/lib/isccfg/include/isccfg/grammar.h +++ b/lib/isccfg/include/isccfg/grammar.h @@ -82,6 +82,9 @@ typedef struct cfg_printer cfg_printer_t; typedef ISC_LIST(cfg_listelt_t) cfg_list_t; typedef struct cfg_map cfg_map_t; typedef struct cfg_rep cfg_rep_t; +typedef struct cfg_duration cfg_duration_t; + +#define CFG_DURATION_MAXLEN 64 /* * Function types for configuration object methods @@ -154,6 +157,24 @@ struct cfg_netprefix { unsigned int prefixlen; }; +/*% + * A configuration object to store ISO 8601 durations. + */ +struct cfg_duration { + /* + * The duration is stored in multiple parts: + * [0] Years + * [1] Months + * [2] Weeks + * [3] Days + * [4] Hours + * [5] Minutes + * [6] Seconds + */ + uint32_t parts[7]; + bool iso8601; +}; + /*% * A configuration data representation. */ @@ -183,6 +204,7 @@ struct cfg_obj { isc_dscp_t dscp; } sockaddrdscp; cfg_netprefix_t netprefix; + cfg_duration_t duration; } value; isc_refcount_t references; /*%< reference counter */ const char * file; @@ -290,6 +312,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_netprefix; LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_void; LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_fixedpoint; LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_percentage; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_duration; /*@}*/ /*@{*/ @@ -320,6 +343,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_token; LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_unsupported; LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_fixedpoint; LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_percentage; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_duration; /*@}*/ isc_result_t @@ -504,6 +528,13 @@ cfg_parse_percentage(cfg_parser_t *pctx, const cfg_type_t *type, void cfg_print_percentage(cfg_printer_t *pctx, const cfg_obj_t *obj); +isc_result_t +cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret); + +void +cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj); + isc_result_t cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index d427ec488d9..3282a8b01f6 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -121,7 +121,6 @@ static cfg_type_t cfg_type_sizeval; static cfg_type_t cfg_type_sockaddr4wild; static cfg_type_t cfg_type_sockaddr6wild; static cfg_type_t cfg_type_statschannels; -static cfg_type_t cfg_type_ttlval; static cfg_type_t cfg_type_view; static cfg_type_t cfg_type_viewopts; static cfg_type_t cfg_type_zone; @@ -1054,7 +1053,7 @@ options_clauses[] = { { "fstrm-set-output-notify-threshold", &cfg_type_uint32, 0 }, { "fstrm-set-output-queue-model", &cfg_type_fstrm_model, 0 }, { "fstrm-set-output-queue-size", &cfg_type_uint32, 0 }, - { "fstrm-set-reopen-interval", &cfg_type_ttlval, 0 }, + { "fstrm-set-reopen-interval", &cfg_type_duration, 0 }, #else { "fstrm-set-buffer-hint", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED }, @@ -1068,7 +1067,7 @@ options_clauses[] = { CFG_CLAUSEFLAG_NOTCONFIGURED }, { "fstrm-set-output-queue-size", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED }, - { "fstrm-set-reopen-interval", &cfg_type_ttlval, + { "fstrm-set-reopen-interval", &cfg_type_duration, CFG_CLAUSEFLAG_NOTCONFIGURED }, #endif /* HAVE_DNSTAP */ #if defined(HAVE_GEOIP2) @@ -1083,7 +1082,7 @@ options_clauses[] = { { "host-statistics", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT }, { "host-statistics-max", &cfg_type_uint32, CFG_CLAUSEFLAG_ANCIENT }, { "hostname", &cfg_type_qstringornone, 0 }, - { "interface-interval", &cfg_type_ttlval, 0 }, + { "interface-interval", &cfg_type_duration, 0 }, { "keep-response-order", &cfg_type_bracketed_aml, 0 }, { "listen-on", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI }, { "listen-on-v6", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI }, @@ -1611,8 +1610,8 @@ static cfg_tuplefielddef_t rpz_zone_fields[] = { { "zone name", &cfg_type_rpz_zone, 0 }, { "add-soa", &cfg_type_boolean, 0 }, { "log", &cfg_type_boolean, 0 }, - { "max-policy-ttl", &cfg_type_ttlval, 0 }, - { "min-update-interval", &cfg_type_ttlval, 0 }, + { "max-policy-ttl", &cfg_type_duration, 0 }, + { "min-update-interval", &cfg_type_duration, 0 }, { "policy", &cfg_type_rpz_policy, 0 }, { "recursive-only", &cfg_type_boolean, 0 }, { "nsip-enable", &cfg_type_boolean, 0 }, @@ -1633,8 +1632,8 @@ static cfg_tuplefielddef_t rpz_fields[] = { { "zone list", &cfg_type_rpz_list, 0 }, { "add-soa", &cfg_type_boolean, 0 }, { "break-dnssec", &cfg_type_boolean, 0 }, - { "max-policy-ttl", &cfg_type_ttlval, 0 }, - { "min-update-interval", &cfg_type_ttlval, 0 }, + { "max-policy-ttl", &cfg_type_duration, 0 }, + { "min-update-interval", &cfg_type_duration, 0 }, { "min-ns-dots", &cfg_type_uint32, 0 }, { "nsip-wait-recurse", &cfg_type_boolean, 0 }, { "qname-wait-recurse", &cfg_type_boolean, 0 }, @@ -1671,7 +1670,7 @@ static cfg_tuplefielddef_t catz_zone_fields[] = { { "default-masters", &cfg_type_namesockaddrkeylist, 0 }, { "zone-directory", &cfg_type_qstring, 0 }, { "in-memory", &cfg_type_boolean, 0 }, - { "min-update-interval", &cfg_type_ttlval, 0 }, + { "min-update-interval", &cfg_type_duration, 0 }, { NULL, NULL, 0 } }; static cfg_type_t cfg_type_catz_tuple = { @@ -1899,7 +1898,7 @@ view_clauses[] = { { "filter-aaaa-on-v6", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, { "glue-cache", &cfg_type_boolean, 0 }, { "ixfr-from-differences", &cfg_type_ixfrdifftype, 0 }, - { "lame-ttl", &cfg_type_ttlval, 0 }, + { "lame-ttl", &cfg_type_duration, 0 }, #ifdef HAVE_LMDB { "lmdb-mapsize", &cfg_type_sizeval, 0 }, #else @@ -1907,16 +1906,16 @@ view_clauses[] = { #endif { "max-acache-size", &cfg_type_sizenodefault, CFG_CLAUSEFLAG_OBSOLETE }, { "max-cache-size", &cfg_type_sizeorpercent, 0 }, - { "max-cache-ttl", &cfg_type_ttlval, 0 }, + { "max-cache-ttl", &cfg_type_duration, 0 }, { "max-clients-per-query", &cfg_type_uint32, 0 }, - { "max-ncache-ttl", &cfg_type_ttlval, 0 }, + { "max-ncache-ttl", &cfg_type_duration, 0 }, { "max-recursion-depth", &cfg_type_uint32, 0 }, { "max-recursion-queries", &cfg_type_uint32, 0 }, - { "max-stale-ttl", &cfg_type_ttlval, 0 }, + { "max-stale-ttl", &cfg_type_duration, 0 }, { "max-udp-size", &cfg_type_uint32, 0 }, { "message-compression", &cfg_type_boolean, 0 }, - { "min-cache-ttl", &cfg_type_ttlval, 0 }, - { "min-ncache-ttl", &cfg_type_ttlval, 0 }, + { "min-cache-ttl", &cfg_type_duration, 0 }, + { "min-ncache-ttl", &cfg_type_duration, 0 }, { "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_ANCIENT }, { "minimal-any", &cfg_type_boolean, 0 }, { "minimal-responses", &cfg_type_minimal, 0 }, @@ -1924,8 +1923,8 @@ view_clauses[] = { { "no-case-compress", &cfg_type_bracketed_aml, 0 }, { "nocookie-udp-size", &cfg_type_uint32, 0 }, { "nosit-udp-size", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE }, - { "nta-lifetime", &cfg_type_ttlval, 0 }, - { "nta-recheck", &cfg_type_ttlval, 0 }, + { "nta-lifetime", &cfg_type_duration, 0 }, + { "nta-recheck", &cfg_type_duration, 0 }, { "nxdomain-redirect", &cfg_type_astring, 0 }, { "preferred-glue", &cfg_type_astring, 0 }, { "prefetch", &cfg_type_prefetch, 0 }, @@ -1955,10 +1954,10 @@ view_clauses[] = { { "root-key-sentinel", &cfg_type_boolean, 0 }, { "rrset-order", &cfg_type_rrsetorder, 0 }, { "send-cookie", &cfg_type_boolean, 0 }, - { "servfail-ttl", &cfg_type_ttlval, 0 }, + { "servfail-ttl", &cfg_type_duration, 0 }, { "sortlist", &cfg_type_bracketed_aml, 0 }, { "stale-answer-enable", &cfg_type_boolean, 0 }, - { "stale-answer-ttl", &cfg_type_ttlval, 0 }, + { "stale-answer-ttl", &cfg_type_duration, 0 }, { "suppress-initial-notify", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI }, { "synth-from-dnssec", &cfg_type_boolean, 0 }, { "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_ANCIENT }, @@ -3761,54 +3760,14 @@ static cfg_type_t cfg_type_masterselement = { doc_masterselement, NULL, NULL }; -static isc_result_t -parse_ttlval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { - isc_result_t result; - cfg_obj_t *obj = NULL; - 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 TTL value (number + optional unit). - */ -static cfg_type_t cfg_type_ttlval = { - "ttlval", parse_ttlval, 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 (cfg_parse_enum_or_other(pctx, type, &cfg_type_ttlval, ret)); + return (cfg_parse_enum_or_other(pctx, type, &cfg_type_duration, ret)); } static void doc_maxttl(cfg_printer_t *pctx, const cfg_type_t *type) { - cfg_doc_enum_or_other(pctx, type, &cfg_type_ttlval); + cfg_doc_enum_or_other(pctx, type, &cfg_type_duration); } /*% diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c index 0c7987b8810..68db91a1d18 100644 --- a/lib/isccfg/parser.c +++ b/lib/isccfg/parser.c @@ -34,6 +34,8 @@ #include #include +#include + /* Shorthand */ #define CAT CFG_LOGCATEGORY_CONFIG #define MOD CFG_LOGMODULE_PARSER @@ -132,6 +134,7 @@ LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_fixedpoint = { "fixedpoint", free_noop }; LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_percentage = { "percentage", free_noop }; +LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_duration = { "duration", free_noop }; /* * Configuration type definitions. @@ -977,9 +980,353 @@ LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_uint64 = { &cfg_rep_uint64, NULL }; +/* + * Get the number of digits in a number. + */ +static size_t +numlen(time_t num) { + uint32_t period = (uint32_t) num; + size_t count = 0; + + if (period == 0) { + return 1; + } + while (period > 0) { + count++; + period /= 10; + } + return (count); +} + +/* + * duration + */ +void +cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj) { + char buf[CFG_DURATION_MAXLEN]; + char *str; + const char *indicators = "YMWDHMS"; + int count, i; + int durationlen[7]; + cfg_duration_t duration; + /* + * D ? The duration has a date part. + * T ? The duration has a time part. + */ + bool D = false, T = false; + + REQUIRE(pctx != NULL); + REQUIRE(obj != NULL); + + duration = obj->value.duration; + + /* If this is not an ISO 8601 duration, just print it as a number. */ + if (!duration.iso8601) { + return (cfg_print_rawuint(pctx, duration.parts[6])); + } + + /* Calculate length of string. */ + buf[0] = 'P'; + buf[1] = '\0'; + str = &buf[1]; + count = 2; + for (i = 0; i < 6; i++) { + if (duration.parts[i] > 0) { + durationlen[i] = 1 + numlen(duration.parts[i]); + if (i < 4) { + D = true; + } else { + T = true; + } + } else { + durationlen[i] = 0; + } + count += durationlen[i]; + } + /* + * Special case for seconds which is not taken into account in the + * above for loop: Count the length of the seconds part if it is + * non-zero, or if all the other parts are also zero. In the latter + * case this function will print "PT0S". + */ + if (duration.parts[6] > 0 || + (!D && !duration.parts[4] && !duration.parts[5])) { + durationlen[6] = 1 + numlen(duration.parts[6]); + T = true; + count += durationlen[6]; + } + /* Add one character for the time indicator. */ + if (T) { + count++; + } + INSIST(count < CFG_DURATION_MAXLEN); + + /* Now print the duration. */ + for (i = 0; i < 6; i++) { + /* + * We don't check here if weeks and other time indicator are + * used mutually exclusively. + */ + if (duration.parts[i] > 0) { + snprintf(str, durationlen[i]+2, "%u%c", + (uint32_t) duration.parts[i], indicators[i]); + str += durationlen[i]+1; + } + if (i == 3 && T) { + snprintf(str, 2, "T"); + str += 1; + } + + } + /* Special case for seconds. */ + if (duration.parts[6] > 0 || + (!D && !duration.parts[4] && !duration.parts[3])) { + snprintf(str, durationlen[6]+2, "%u%c", + (uint32_t) duration.parts[6], indicators[6]); + } + cfg_print_chars(pctx, buf, strlen(buf)); +} + +bool +cfg_obj_isduration(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (obj->type->rep == &cfg_rep_duration); +} + +uint32_t +cfg_obj_asduration(const cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_duration); + uint32_t duration = 0; + duration += obj->value.duration.parts[6]; // Seconds + duration += obj->value.duration.parts[5]*60; // Minutes + duration += obj->value.duration.parts[4]*3600; // Hours + duration += obj->value.duration.parts[3]*86400; // Days + duration += obj->value.duration.parts[2]*86400*7; // Weaks + /* + * The below additions are not entirely correct + * because days may very per month and per year. + */ + duration += obj->value.duration.parts[1]*86400*31; // Months + duration += obj->value.duration.parts[0]*86400*365; // Years + return (duration); +} + +/* + * duration_fromtext initially taken from OpenDNSSEC code base. + * Modified to fit the BIND 9 code. + * + * Copyright (c) 2009-2018 NLNet Labs. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +static isc_result_t +duration_fromtext(isc_textregion_t *source, cfg_duration_t *duration) { + char buf[CFG_DURATION_MAXLEN]; + char *P, *X, *T, *W, *str; + bool not_weeks = false; + int i; + + /* + * Copy the buffer as it may not be NULL terminated. + * Anyone having a duration longer than 63 characters is crazy. + */ + if (source->length > sizeof(buf) - 1) { + return (ISC_R_BADNUMBER); + } + /* Copy source->length bytes and NULL terminate. */ + snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base); + str = buf; + + /* Clear out duration. */ + for (i = 0; i < 7; i++) { + duration->parts[i] = 0; + } + + /* Every duration starts with 'P' */ + P = strchr(str, 'P'); + if (!P) { + return (ISC_R_BADNUMBER); + } + + /* Record the time indicator. */ + T = strchr(str, 'T'); + + /* Record years. */ + X = strchr(str, 'Y'); + if (X) { + duration->parts[0] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record months. */ + X = strchr(str, 'M'); + /* + * M could be months or minutes. This is months if there is no time + * part, or this M indicator is before the time indicator. + */ + if (X && (!T || (size_t) (X-P) < (size_t) (T-P))) { + duration->parts[1] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record days. */ + X = strchr(str, 'D'); + if (X) { + duration->parts[3] = atoi(str+1); + str = X; + not_weeks = true; + } + + /* Time part? */ + if (T) { + str = T; + not_weeks = true; + } + + /* Record hours. */ + X = strchr(str, 'H'); + if (X && T) { + duration->parts[4] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record minutes. */ + X = strrchr(str, 'M'); + /* + * M could be months or minutes. This is minutes if there is a time + * part and the M indicator is behind the time indicator. + */ + if (X && T && (size_t) (X-P) > (size_t) (T-P)) { + duration->parts[5] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record seconds. */ + X = strchr(str, 'S'); + if (X && T) { + duration->parts[6] = atoi(str+1); + str = X; + not_weeks = true; + } + + /* Or is the duration configured in weeks? */ + W = strchr(buf, 'W'); + if (W) { + if (not_weeks) { + /* Mix of weeks and other indicators is not allowed */ + return (ISC_R_BADNUMBER); + } else { + duration->parts[2] = atoi(str+1); + str = W; + } + } + + /* Deal with trailing garbage. */ + if (str[1] != '\0') { + return (ISC_R_BADNUMBER); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + cfg_obj_t *obj = NULL; + cfg_duration_t duration; + + UNUSED(type); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type != isc_tokentype_string) { + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + + if (TOKEN_STRING(pctx)[0] == 'P') { + result = duration_fromtext(&pctx->token.value.as_textregion, + &duration); + duration.iso8601 = true; + } else { + uint32_t ttl; + result = dns_ttl_fromtext(&pctx->token.value.as_textregion, + &ttl); + /* + * With dns_ttl_fromtext() the information on optional units. + * is lost, and is treated as seconds from now on. + */ + duration.parts[0] = 0; + duration.parts[1] = 0; + duration.parts[2] = 0; + duration.parts[3] = 0; + duration.parts[4] = 0; + duration.parts[5] = 0; + duration.parts[6] = ttl; + duration.iso8601 = false; + } + if (result == ISC_R_RANGE) { + cfg_parser_error(pctx, CFG_LOG_NEAR, + "duration or TTL out of range"); + return (result); + } else if (result != ISC_R_SUCCESS) { + goto cleanup; + } + CHECK(cfg_create_obj(pctx, &cfg_type_duration, &obj)); + obj->value.duration = duration; + *ret = obj; + return (ISC_R_SUCCESS); + +cleanup: + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected ISO 8601 duration or TTL value"); + return (result); +} + +/*% + * A duration as defined by ISO 8601 (P[n]Y[n]M[n]DT[n]H[n]M[n]S). + * - P is the duration indicator ("period") placed at the start. + * - Y is the year indicator that follows the value for the number of years. + * - M is the month indicator that follows the value for the number of months. + * - D is the day indicator that follows the value for the number of days. + * - T is the time indicator that precedes the time components. + * - H is the hour indicator that follows the value for the number of hours. + * - M is the minute indicator that follows the value for the number of + * minutes. + * - S is the second indicator that follows the value for the number of + * seconds. + * + * A duration can also be a TTL value (number + optional unit). + */ +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_duration = { + "duration", cfg_parse_duration, cfg_print_duration, cfg_doc_terminal, + &cfg_rep_duration, NULL +}; + /* * qstring (quoted string), ustring (unquoted string), astring - * (any string) + * (any string), sstring (secret string) */ /* Create a string object from a null-terminated C string. */ diff --git a/lib/isccfg/tests/Kyuafile b/lib/isccfg/tests/Kyuafile index dc14aace41c..89d50352f2d 100644 --- a/lib/isccfg/tests/Kyuafile +++ b/lib/isccfg/tests/Kyuafile @@ -1,4 +1,5 @@ syntax(2) test_suite('bind9') +tap_test_program{name='duration_test'} tap_test_program{name='parser_test'} diff --git a/lib/isccfg/tests/Makefile.in b/lib/isccfg/tests/Makefile.in index 9e659620982..4517992e038 100644 --- a/lib/isccfg/tests/Makefile.in +++ b/lib/isccfg/tests/Makefile.in @@ -30,13 +30,18 @@ ISCCFGDEPLIBS = ../libisccfg.@A@ LIBS = @LIBS@ @CMOCKA_LIBS@ OBJS = -SRCS = parser_test.c +SRCS = duration_test.c parser_test.c SUBDIRS = -TARGETS = parser_test@EXEEXT@ +TARGETS = duration_test@EXEEXT@ parser_test@EXEEXT@ @BIND9_MAKE_RULES@ +duration_test@EXEEXT@: duration_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${ISCCFGDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ duration_test.@O@ \ + ${ISCCFGLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} + parser_test@EXEEXT@: parser_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${ISCCFGDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ ${LDFLAGS} -o $@ parser_test.@O@ \ diff --git a/lib/isccfg/tests/duration_test.c b/lib/isccfg/tests/duration_test.c new file mode 100644 index 00000000000..2e0249bb070 --- /dev/null +++ b/lib/isccfg/tests/duration_test.c @@ -0,0 +1,207 @@ +/* + * 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. + */ + +#if HAVE_CMOCKA + +#include +#include +#include + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +isc_mem_t *mctx = NULL; +isc_log_t *lctx = NULL; +static isc_logcategory_t categories[] = { + { "", 0 }, + { "client", 0 }, + { "network", 0 }, + { "update", 0 }, + { "queries", 0 }, + { "unmatched", 0 }, + { "update-security", 0 }, + { "query-errors", 0 }, + { NULL, 0 } +}; + +static void +cleanup() { + if (lctx != NULL) { + isc_log_destroy(&lctx); + } + if (mctx != NULL) { + isc_mem_destroy(&mctx); + } +} + +static isc_result_t +setup() { + isc_result_t result; + + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + isc_mem_create(&mctx); + + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + + CHECK(isc_log_create(mctx, &lctx, &logconfig)); + isc_log_registercategories(lctx, categories); + isc_log_setcontext(lctx); + + destination.file.stream = stderr; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + CHECK(isc_log_createchannel(logconfig, "stderr", + ISC_LOG_TOFILEDESC, + ISC_LOG_DYNAMIC, + &destination, 0)); + CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL)); + + return (ISC_R_SUCCESS); + + cleanup: + cleanup(); + return (result); +} + +struct duration_conf { + const char* string; + uint32_t time; +}; +typedef struct duration_conf duration_conf_t; + +/* test cfg_obj_asduration() */ +static void +cfg_obj_asduration_test(void **state) { + isc_result_t result; + duration_conf_t durations[] = { + { .string = "PT0S", .time = 0 }, + { .string = "PT42S", .time = 42 }, + { .string = "PT10M", .time = 600 }, + { .string = "PT10M4S", .time = 604 }, + { .string = "PT2H", .time = 7200 }, + { .string = "PT2H3S", .time = 7203 }, + { .string = "PT2H1M3S", .time = 7263 }, + { .string = "P7D", .time = 604800 }, + { .string = "P7DT2H", .time = 612000 }, + { .string = "P2W", .time = 1209600 }, + { .string = "P3M", .time = 8035200 }, + { .string = "P3MT10M", .time = 8035800 }, + { .string = "P5Y", .time = 157680000 }, + { .string = "P5YT2H", .time = 157687200 }, + { .string = "P1Y1M1DT1H1M1S", .time = 34304461 }, + { .string = "0", .time = 0 }, + { .string = "30", .time = 30 }, + { .string = "42s", .time = 42 }, + { .string = "10m", .time = 600 }, + { .string = "2h", .time = 7200 }, + { .string = "7d", .time = 604800 }, + { .string = "2w", .time = 1209600 }, + }; + int num = 22; + isc_buffer_t buf1; + cfg_parser_t *p1 = NULL; + cfg_obj_t *c1 = NULL; + + UNUSED(state); + + setup(); + + for (int i = 0; i < num; i++) { + const cfg_listelt_t *element; + const cfg_obj_t *kasps = NULL; + char conf[64]; + sprintf(&conf[0], + "dnssec-policy \"dp\"\n{\nsignatures-refresh %s;\n};\n", + durations[i].string); + + isc_buffer_init(&buf1, conf, strlen(conf) - 1); + isc_buffer_add(&buf1, strlen(conf) - 1); + + /* Parse with default line numbering */ + result = cfg_parser_create(mctx, lctx, &p1); + assert_int_equal(result, ISC_R_SUCCESS); + + result = cfg_parse_buffer(p1, &buf1, "text1", 0, + &cfg_type_namedconf, 0, &c1); + assert_int_equal(result, ISC_R_SUCCESS); + + (void)cfg_map_get(c1, "dnssec-policy", &kasps); + assert_non_null(kasps); + for (element = cfg_list_first(kasps); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *d1 = NULL; + const cfg_obj_t *kopts = NULL; + cfg_obj_t *kconf = cfg_listelt_value(element); + assert_non_null(kconf); + + kopts = cfg_tuple_get(kconf, "options"); + result = cfg_map_get(kopts, "signatures-refresh", &d1); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_int_equal(durations[i].time, + cfg_obj_asduration(d1)); + } + + cfg_obj_destroy(p1, &c1); + cfg_parser_destroy(&p1); + } + + cleanup(); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(cfg_obj_asduration_test), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (0); +} + +#endif diff --git a/lib/isccfg/win32/libisccfg.def b/lib/isccfg/win32/libisccfg.def index 96039fbf045..61e6e86ce4c 100644 --- a/lib/isccfg/win32/libisccfg.def +++ b/lib/isccfg/win32/libisccfg.def @@ -36,6 +36,7 @@ cfg_map_get cfg_map_getname cfg_map_nextclause cfg_obj_asboolean +cfg_obj_asduration cfg_obj_asfixedpoint cfg_obj_asnetprefix cfg_obj_aspercentage @@ -48,6 +49,7 @@ cfg_obj_destroy cfg_obj_file cfg_obj_getdscp cfg_obj_isboolean +cfg_obj_isduration cfg_obj_isfixedpoint cfg_obj_islist cfg_obj_ismap @@ -68,6 +70,7 @@ cfg_parse_boolean cfg_parse_bracketed_list cfg_parse_buffer cfg_parse_dscp +cfg_parse_duration cfg_parse_enum cfg_parse_enum_or_other cfg_parse_file @@ -107,6 +110,7 @@ cfg_print_bracketed_list cfg_print_chars cfg_print_clauseflags cfg_print_cstr +cfg_print_duration cfg_print_fixedpoint cfg_print_grammar cfg_print_indent @@ -131,6 +135,7 @@ cfg_ungettoken ; Exported Data ;cfg_rep_boolean +;cfg_rep_duration ;cfg_rep_fixedpoint ;cfg_rep_list ;cfg_rep_map diff --git a/util/copyrights b/util/copyrights index 5671c1c42a5..d10b5bcb559 100644 --- a/util/copyrights +++ b/util/copyrights @@ -2440,6 +2440,7 @@ ./lib/isccfg/namedconf.c C 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 ./lib/isccfg/parser.c C 2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 ./lib/isccfg/tests/Kyuafile X 2017,2018,2019 +./lib/isccfg/tests/duration_test.c C 2019 ./lib/isccfg/tests/parser_test.c C 2016,2018,2019 ./lib/isccfg/version.c C 1998,1999,2000,2001,2004,2005,2007,2016,2018,2019 ./lib/isccfg/win32/DLLMain.c C 2001,2004,2007,2016,2018,2019