]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Allow for key lifetime unlimited
authorMatthijs Mekking <matthijs@isc.org>
Thu, 6 Feb 2020 14:41:47 +0000 (15:41 +0100)
committerEvan Hunt <each@isc.org>
Fri, 7 Feb 2020 17:30:26 +0000 (09:30 -0800)
The keyword 'unlimited' can be used instead of PT0S which means the
same but is more comprehensible for users.

Also fix some redundant "none" parameters in the kasp test.

17 files changed:
bin/named/named.conf.docbook
bin/tests/system/checkconf/good-kasp.conf
bin/tests/system/kasp/ns3/named.conf.in
bin/tests/system/kasp/ns3/policies/kasp.conf
bin/tests/system/kasp/ns3/setup.sh
bin/tests/system/kasp/tests.sh
doc/arm/Bv9ARM-book.xml
doc/arm/dnssec-policy.grammar.xml
doc/design/dnssec-policy
doc/misc/dnssec-policy.default.conf
doc/misc/options
doc/misc/options.active
lib/isccfg/include/isccfg/grammar.h
lib/isccfg/kaspconf.c
lib/isccfg/namedconf.c
lib/isccfg/parser.c
lib/isccfg/win32/libisccfg.def

index 87c92540082926265d8d705b91d1a1e4c75762a3..9aeeac10cc1f2bf95fd55fb22ce8fb14b21c9d83 100644 (file)
@@ -115,7 +115,7 @@ dlz <replaceable>string</replaceable> {
     <literallayout class="normal">
 dnssec-policy <replaceable>string</replaceable> {
        dnskey-ttl <replaceable>duration</replaceable>;
-       keys { ( csk | ksk | zsk ) ( key-directory ) lifetime <replaceable>duration</replaceable>
+       keys { ( csk | ksk | zsk ) ( key-directory ) lifetime ( <replaceable>duration</replaceable> | unlimited )
            algorithm <replaceable>integer</replaceable> [ <replaceable>integer</replaceable> ]; ... };
        max-zone-ttl <replaceable>duration</replaceable>;
        parent-ds-ttl <replaceable>duration</replaceable>;
index b4d3c1e562530531ce8a5d4b3ea2cd4801f15ee4..19420f2dfddc7ad7659ac71e279cfc74f182083a 100644 (file)
@@ -19,7 +19,7 @@ dnssec-policy "test" {
        keys {
                ksk key-directory lifetime P1Y algorithm 13 256;
                zsk key-directory lifetime P30D algorithm 13;
-               csk key-directory lifetime P30D algorithm 8 2048;
+               csk key-directory lifetime unlimited algorithm 8 2048;
        };
        max-zone-ttl 86400;
        parent-ds-ttl 7200;
index 2f78aa814653871fc50e8bb241d8ce3da23f3c88..38a656b0d34533c59bd3d7aa2fc287b1c75f0caa 100644 (file)
@@ -45,6 +45,13 @@ zone "default.kasp" {
        dnssec-policy "default";
 };
 
+/* Key lifetime unlimited. */
+zone "unlimited.kasp" {
+       type master;
+       file "unlimited.kasp.db";
+       dnssec-policy "unlimited";
+};
+
 /* A master zone with dnssec-policy, no keys created. */
 zone "rsasha1.kasp" {
        type master;
index fa60476e6577c479ddb3a12a857d2f486dbc2da3..e0ce931d901c1c3a905656322d0f5ced8cf95173 100644 (file)
@@ -9,6 +9,14 @@
  * information regarding copyright ownership.
  */
 
+dnssec-policy "unlimited" {
+       dnskey-ttl 1234;
+
+       keys {
+               csk key-directory lifetime unlimited algorithm 13;
+       };
+};
+
 dnssec-policy "rsasha1" {
        dnskey-ttl 1234;
 
index c39df821af22196c24e6696accc6da9c0f7560e5..a1c1806839ce8d264898f583e491c5c9ccc5f85a 100644 (file)
@@ -43,7 +43,8 @@ U="UNRETENTIVE"
 # Set up zones that will be initially signed.
 #
 for zn in default rsasha1 dnssec-keygen some-keys legacy-keys pregenerated \
-         rumoured rsasha1-nsec3 rsasha256 rsasha512 ecdsa256 ecdsa384 inherit
+         rumoured rsasha1-nsec3 rsasha256 rsasha512 ecdsa256 ecdsa384 \
+         inherit unlimited
 do
        setup "${zn}.kasp"
        cp template.db.in "$zonefile"
index 7a7b4d9eb0e856b8abe50c041592fafc4d19be44..47d8e05747d283a97cc30433f50f46eda6365427 100644 (file)
@@ -1026,6 +1026,22 @@ check_keys
 check_apex
 check_subdomain
 
+#
+# Zone: unlimited.kasp.
+#
+zone_properties "ns3" "unlimited.kasp" "unlimited" "1234" "1" "10.53.0.3"
+key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes"
+key_clear "KEY2"
+key_clear "KEY3"
+# The first key is immediately published and activated.
+key_timings "KEY1" "published" "active" "none" "none" "none"
+# DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait.
+key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden"
+check_keys
+check_apex
+check_subdomain
+dnssec_verify
+
 #
 # Zone: inherit.kasp.
 #
@@ -1451,7 +1467,7 @@ check_subdomain
 # ns5/override.inherit.signed
 # ns5/inherit.override.signed
 key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes"
-key_timings "KEY1" "published" "active" "none" "none" "none" "none"
+key_timings "KEY1" "published" "active" "none" "none" "none"
 key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden"
 
 zone_properties "ns2" "signed.tld" "default" "3600" "1" "10.53.0.2"
@@ -1496,7 +1512,7 @@ dnssec_verify
 # ns5/override.override.unsigned
 # ns5/override.none.unsigned
 key_properties "KEY1" "csk" "0" "14" "ECDSAP384SHA384" "384" "yes" "yes"
-key_timings "KEY1" "published" "active" "none" "none" "none" "none"
+key_timings "KEY1" "published" "active" "none" "none" "none"
 key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden"
 
 zone_properties "ns4" "inherit.inherit.signed" "test" "3600" "1" "10.53.0.4"
index 1fd3e16e63eb75e29ba868aa4daf78a6f57d93be..28f88f89da09c0b2677f8f7362f98e69d3efffc6 100644 (file)
@@ -11113,7 +11113,7 @@ example.com                 CNAME   rpz-tcp-only.
                </para>
 
 <programlisting>keys {
-    ksk key-directory lifetime P5Y algorithm 8 2048;
+    ksk key-directory lifetime unlimited algorithm 8 2048;
     zsk key-directory lifetime P30D algorithm 8;
     csk key-directory lifetime P6MT12H3M15S algorithm 13;
 };
@@ -11133,16 +11133,27 @@ example.com                 CNAME   rpz-tcp-only.
                  <command>key-directory</command>.
                </para>
                <para>
-                 The third token tells how long the key may be used.  In the
-                 example the first key has a lifetime of 5 years, the second
-                 key may be used for 30 days and the third key has a rather
-                 peculiar lifetime of 6 months, 12 hours, 3 minutes and 15
-                 seconds.
+                 The <command>lifetime</command> parameter specifies how
+                 long a key may be used before rolling over.  In the
+                 example above, the first key will have an unlimited
+                 lifetime, the second key may be used for 30 days, and the
+                 third key has a rather peculiar lifetime of 6 months,
+                 12 hours, 3 minutes and 15 seconds.  A lifetime of 0
+                 seconds is the same as <command>unlimited</command>.
                </para>
                <para>
-                 The last token(s) are the key's algorithm and algorithm
-                 length.  The length may be omitted as shown in the
-                 example for the second and third key.
+                 Note that the lifetime of a key may be extended if
+                 retiring it too soon would cause validation failures:
+                 for example, if the key were configured to roll more
+                 frequently than its TTL, its lifetime would
+                 automatically be extended to account for this.
+               </para>
+               <para>
+                 The <command>algorithm</command> parameter(s) are the key's
+                 algorithm, expressed numerically, and its size in bits.  The
+                 size may be omitted, as shown in the example for the
+                 second and third keys; in this case an appropriate
+                 default size will be used.
                </para>
              </listitem>
            </varlistentry>
index d3e21a491832fa187ed0c0c1195f675c1576f6ef..08e3890e6b1f59ec2ad06bf47a61fd8c094a1995 100644 (file)
@@ -14,7 +14,7 @@
 <programlisting>
 <command>dnssec-policy</command> <replaceable>string</replaceable> {
     <command>dnskey-ttl</command> <replaceable>duration</replaceable>;
-    <command>keys</command> { ( csk | ksk | zsk ) key-directory lifetime <replaceable>duration</replaceable> algorithm <replaceable>integer</replaceable> [ <replaceable>integer</replaceable> ] ; ... };
+    <command>keys</command> { ( csk | ksk | zsk ) key-directory lifetime ( <replaceable>duration</replaceable> | unlimited ) algorithm <replaceable>integer</replaceable> [ <replaceable>integer</replaceable> ] ; ... };
     <command>max-zone-ttl</command> <replaceable>duration</replaceable>;
     <command>parent-ds-ttl</command> <replaceable>duration</replaceable>;
     <command>parent-propagation-delay</command> <replaceable>duration</replaceable>;
index ae16195518a6f70c36fde948a1fbe708afe31bb3..69951814dd2e227c2cc6fe5eda618193cd40ac25 100644 (file)
@@ -199,9 +199,9 @@ is referred to as a CSK. Below is an example configuration for the three types
 of keys:
 ```
        keys {
-               ksk key-directory lifetime P5Y  algorithm ECDSAP256SHA256;
+               ksk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
                zsk key-directory lifetime P30D algorithm ECDSAP256SHA256;
-               csk key-directory lifetime PT0S algorithm 8 2048;
+               csk key-directory lifetime P5Y algorithm 8 2048;
        };
 ```
 
index 58283f2a0e4ccfc7140c2e733c16cff0010dcaa6..1e12aec43efca9e8cc84e3032c8cfeb65e64a8e8 100644 (file)
@@ -2,7 +2,7 @@ dnssec-policy "default" {
 
        // Keys
        keys {
-               csk key-directory lifetime 0 algorithm 13;
+               csk key-directory lifetime unlimited algorithm 13;
        };
 
        // Key timings
index 57b2a4393a6964ac68ebc934975617623f52c806..cf66ac3a9743bdbc5e182889be2632659520833f 100644 (file)
@@ -23,7 +23,7 @@ dlz <string> {
 
 dnssec-policy <string> {
         dnskey-ttl <duration>;
-        keys { ( csk | ksk | zsk ) ( key-directory ) lifetime <duration>
+        keys { ( csk | ksk | zsk ) ( key-directory ) lifetime ( <duration> | unlimited )
             algorithm <integer> [ <integer> ]; ... };
         max-zone-ttl <duration>;
         parent-ds-ttl <duration>;
index 0adfbfa9ec5d23547e1a51ed1077c59f5d7543c1..20fc8d3b371ca96e162581e4dac27ea231555f06 100644 (file)
@@ -23,7 +23,7 @@ dlz <string> {
 
 dnssec-policy <string> {
         dnskey-ttl <duration>;
-        keys { ( csk | ksk | zsk ) ( key-directory ) lifetime <duration>
+        keys { ( csk | ksk | zsk ) ( key-directory ) lifetime ( <duration> | unlimited )
             algorithm <integer> [ <integer> ]; ... };
         max-zone-ttl <duration>;
         parent-ds-ttl <duration>;
index ad3dd81d176a9f4c0d6a38d78648a087f113d40f..1b02820ba0f0859291e9841eb1e07b31ad4c734f 100644 (file)
@@ -173,6 +173,7 @@ struct cfg_duration {
         */
        uint32_t parts[7];
        bool iso8601;
+       bool unlimited;
 };
 
 /*%
@@ -344,6 +345,7 @@ 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;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_duration_or_unlimited;
 /*@}*/
 
 isc_result_t
@@ -535,6 +537,13 @@ cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type,
 void
 cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj);
 
+isc_result_t
+cfg_parse_duration_or_unlimited(cfg_parser_t *pctx, const cfg_type_t *type,
+                               cfg_obj_t **ret);
+
+void
+cfg_print_duration_or_unlimited(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);
 
index 036761bdb0ed6a6adae07daa8e4bfb194a0f7acf..8001c122e7958c3b08af59cbb940cd789b679654 100644 (file)
@@ -78,7 +78,7 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t* kasp)
        if (config == NULL) {
                /* We are creating a key reference for the default kasp. */
                key->role |= DNS_KASP_KEY_ROLE_KSK | DNS_KASP_KEY_ROLE_ZSK;
-               key->lifetime = 0;
+               key->lifetime = 0; /* unlimited */
                key->algorithm = DNS_KEYALG_ECDSA256;
                key->length = -1;
        } else {
@@ -94,10 +94,16 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t* kasp)
                        key->role |= DNS_KASP_KEY_ROLE_KSK;
                        key->role |= DNS_KASP_KEY_ROLE_ZSK;
                }
-               key->lifetime = cfg_obj_asduration(
-                                            cfg_tuple_get(config, "lifetime"));
-               key->algorithm = cfg_obj_asuint32(
-                                           cfg_tuple_get(config, "algorithm"));
+
+               key->lifetime = 0; /* unlimited */
+               obj = cfg_tuple_get(config, "lifetime");
+               if (cfg_obj_isduration(obj)) {
+                       key->lifetime = cfg_obj_asduration(obj);
+               }
+
+               obj = cfg_tuple_get(config, "algorithm");
+               key->algorithm = cfg_obj_asuint32(obj);
+
                obj = cfg_tuple_get(config, "length");
                if (cfg_obj_isuint32(obj)) {
                        key->length = cfg_obj_asuint32(obj);
index e8812e9e308c37564af143679434dcf5567542f6..0bfb11a25c8dd38f786ab266d9e86c8d3a8150e4 100644 (file)
@@ -96,7 +96,7 @@ static cfg_type_t cfg_type_logseverity;
 static cfg_type_t cfg_type_logsuffix;
 static cfg_type_t cfg_type_logversions;
 static cfg_type_t cfg_type_masterselement;
-static cfg_type_t cfg_type_maxttl;
+static cfg_type_t cfg_type_maxduration;
 static cfg_type_t cfg_type_minimal;
 static cfg_type_t cfg_type_nameportiplist;
 static cfg_type_t cfg_type_notifytype;
@@ -439,6 +439,24 @@ static cfg_type_t cfg_type_category = {
        &cfg_rep_tuple, category_fields
 };
 
+static isc_result_t
+parse_maxduration(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+       return (cfg_parse_enum_or_other(pctx, type, &cfg_type_duration, ret));
+}
+
+static void
+doc_maxduration(cfg_printer_t *pctx, const cfg_type_t *type) {
+       cfg_doc_enum_or_other(pctx, type, &cfg_type_duration);
+}
+
+/*%
+ * A duration or "unlimited", but not "default".
+ */
+static const char *maxduration_enums[] = { "unlimited", NULL };
+static cfg_type_t cfg_type_maxduration = {
+       "maxduration_no_default", parse_maxduration, cfg_print_ustring,
+       doc_maxduration, &cfg_rep_duration, maxduration_enums
+};
 
 /*%
  * A dnssec key, as used in the "trusted-keys" statement.
@@ -509,7 +527,8 @@ static cfg_type_t cfg_type_algorithm = {
        doc_keyvalue, &cfg_rep_uint32, &algorithm_kw
 };
 
-static keyword_type_t lifetime_kw = { "lifetime", &cfg_type_duration };
+static keyword_type_t lifetime_kw = { "lifetime",
+                                     &cfg_type_duration_or_unlimited };
 static cfg_type_t cfg_type_lifetime = {
        "lifetime", parse_keyvalue, print_keyvalue,
        doc_keyvalue, &cfg_rep_duration, &lifetime_kw
@@ -2227,7 +2246,7 @@ zone_clauses[] = {
        { "max-transfer-time-out", &cfg_type_uint32,
                CFG_ZONE_MASTER | CFG_ZONE_MIRROR | CFG_ZONE_SLAVE
        },
-       { "max-zone-ttl", &cfg_type_maxttl,
+       { "max-zone-ttl", &cfg_type_maxduration,
                CFG_ZONE_MASTER | CFG_ZONE_REDIRECT
        },
        { "min-refresh-time", &cfg_type_uint32,
@@ -3867,25 +3886,6 @@ static cfg_type_t cfg_type_masterselement = {
         doc_masterselement, NULL, 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_duration, ret));
-}
-
-static void
-doc_maxttl(cfg_printer_t *pctx, const cfg_type_t *type) {
-       cfg_doc_enum_or_other(pctx, type, &cfg_type_duration);
-}
-
-/*%
- * 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, doc_maxttl,
-       &cfg_rep_string, maxttl_enums
-};
-
 static int cmp_clause(const void *ap, const void *bp) {
        const cfg_clausedef_t *a = (const cfg_clausedef_t *)ap;
        const cfg_clausedef_t *b = (const cfg_clausedef_t *)bp;
index 97647ca58eee5559337afe7c5e13d8c1c4c106ac..5ae6b6bd88923647bbf42c6cd4cfaf3d2a48e5db 100644 (file)
@@ -1088,6 +1088,22 @@ cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj) {
        cfg_print_chars(pctx, buf, strlen(buf));
 }
 
+void
+cfg_print_duration_or_unlimited(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+       cfg_duration_t duration;
+
+       REQUIRE(pctx != NULL);
+       REQUIRE(obj != NULL);
+
+       duration = obj->value.duration;
+
+       if (duration.unlimited) {
+               cfg_print_cstr(pctx, "unlimited");
+       } else {
+               cfg_print_duration(pctx, obj);
+       }
+}
+
 bool
 cfg_obj_isduration(const cfg_obj_t *obj) {
        REQUIRE(obj != NULL);
@@ -1250,21 +1266,14 @@ duration_fromtext(isc_textregion_t *source, cfg_duration_t *duration) {
        return (ISC_R_SUCCESS);
 }
 
-isc_result_t
-cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type,
-                  cfg_obj_t **ret)
+static isc_result_t
+cfg__parse_duration(cfg_parser_t *pctx, 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;
-       }
+       duration.unlimited = false;
 
        if (TOKEN_STRING(pctx)[0] == 'P') {
                result = duration_fromtext(&pctx->token.value.as_textregion,
@@ -1278,12 +1287,9 @@ cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type,
                 * 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;
+               for (int i = 0; i < 6; i++) {
+                       duration.parts[i] = 0;
+               }
                duration.parts[6] = ttl;
                duration.iso8601 = false;
        }
@@ -1305,6 +1311,66 @@ cleanup:
        return (result);
 }
 
+isc_result_t
+cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type,
+                  cfg_obj_t **ret)
+{
+       isc_result_t result;
+
+       UNUSED(type);
+
+       CHECK(cfg_gettoken(pctx, 0));
+       if (pctx->token.type != isc_tokentype_string) {
+               result = ISC_R_UNEXPECTEDTOKEN;
+               goto cleanup;
+       }
+
+       return cfg__parse_duration(pctx, ret);
+
+cleanup:
+       cfg_parser_error(pctx, CFG_LOG_NEAR,
+                        "expected ISO 8601 duration or TTL value");
+       return (result);
+}
+
+isc_result_t
+cfg_parse_duration_or_unlimited(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 (strcmp(TOKEN_STRING(pctx), "unlimited") == 0) {
+               for (int i = 0; i < 7; i++) {
+                       duration.parts[i] = 0;
+               }
+               duration.iso8601 = false;
+               duration.unlimited = true;
+
+               CHECK(cfg_create_obj(pctx, &cfg_type_duration, &obj));
+               obj->value.duration = duration;
+               *ret = obj;
+               return (ISC_R_SUCCESS);
+       }
+
+       return cfg__parse_duration(pctx, ret);
+
+cleanup:
+       cfg_parser_error(pctx, CFG_LOG_NEAR,
+                        "expected ISO 8601 duration, TTL value, or unlimited");
+       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.
@@ -1324,6 +1390,11 @@ LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_duration = {
        "duration", cfg_parse_duration, cfg_print_duration, cfg_doc_terminal,
        &cfg_rep_duration, NULL
 };
+LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_duration_or_unlimited = {
+       "duration_or_unlimited", cfg_parse_duration_or_unlimited,
+       cfg_print_duration_or_unlimited, cfg_doc_terminal,
+       &cfg_rep_duration, NULL
+};
 
 /*
  * qstring (quoted string), ustring (unquoted string), astring
index 1c40b3c34b1cbb256023df60be53a518c18f9f69..eba897ec3015a9401cc4e041ce050e4b984eadf0 100644 (file)
@@ -72,6 +72,7 @@ cfg_parse_bracketed_list
 cfg_parse_buffer
 cfg_parse_dscp
 cfg_parse_duration
+cfg_parse_duration_or_unlimited
 cfg_parse_enum
 cfg_parse_enum_or_other
 cfg_parse_file
@@ -112,6 +113,7 @@ cfg_print_chars
 cfg_print_clauseflags
 cfg_print_cstr
 cfg_print_duration
+cfg_print_duration_or_unlimited
 cfg_print_fixedpoint
 cfg_print_grammar
 cfg_print_indent