]> git.ipfire.org Git - thirdparty/ldns.git/commitdiff
Parsing and printing of SVCB and HTTPS RR type
authorWillem Toorop <willem@nlnetlabs.nl>
Mon, 30 Nov 2020 08:13:05 +0000 (09:13 +0100)
committerWillem Toorop <willem@nlnetlabs.nl>
Mon, 30 Nov 2020 08:13:05 +0000 (09:13 +0100)
12 files changed:
configure.ac
error.c
host2str.c
ldns/error.h
ldns/host2str.h
ldns/rdata.h
ldns/rr.h
ldns/str2host.h
rdata.c
rr.c
str2host.c
wire2host.c

index 0af6f1248e43e66ab256e31d234602928b7992e9..b60dfa9bcc9cd5219be70df1fefdc175aebb71df 100644 (file)
@@ -683,6 +683,14 @@ case "$enable_rrtype_amtrelay" in
        no|*)
                ;;
 esac
+AC_ARG_ENABLE(rrtype-svcb-https, AC_HELP_STRING([--enable-rrtype-svcb-https], [Enable draft RR types SVCB and HTTPS.]))
+case "$enable_rrtype_svcb_https" in
+       yes)
+               AC_DEFINE_UNQUOTED([RRTYPE_SVCB_HTTPS], [], [Define this to enable RR types SVCB and HTTPS.])
+               ;;
+       no|*)
+               ;;
+esac
 
 
 
diff --git a/error.c b/error.c
index 60a76d551f31366eb7d7a9d82ce8818eaae9538a..053f1087bff68786cc4f2884fd18a47d27aede68 100644 (file)
--- a/error.c
+++ b/error.c
@@ -166,6 +166,22 @@ ldns_lookup_table ldns_error_str[] = {
                "but it is not found in the zone" },
        { LDNS_STATUS_NO_VALID_ZONEMD,
                "No ZONEMD matching the zone data was found" },
+       { LDNS_STATUS_SYNTAX_SVCPARAM_KEY_ERR, "Syntax error in a key in "
+               "the ServiceParam rdata field of SVCB or HTTPS RR" },
+       { LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR, "Syntax error in a value in "
+               "the ServiceParam rdata field of SVCB or HTTPS RR" },
+       { LDNS_STATUS_RESERVED_SVCPARAM_KEY,
+               "key65535 is reserved and MUST NOT be used "
+               "in the ServiceParam rdata field of SVCB or HTTPS RR" },
+       { LDNS_STATUS_NO_SVCPARAM_VALUE_EXPECTED,
+               "A value was found for a key that SHOULD not have a value "
+               "in the ServiceParam rdata field of SVCB or HTTPS RR" },
+       { LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE,
+               "A key was found more than once "
+               "in the ServiceParam rdata field of SVCB or HTTPS RR" },
+       { LDNS_STATUS_INVALID_SVCPARAM_VALUE,
+               "Invalid wireformat of a value "
+               "in the ServiceParam rdata field of SVCB or HTTPS RR" },
        { 0, NULL }
 };
 
index 77a6ba7eda8d6d4bacc9e5239d58ed9b4a129606..69b00f08d0475f2b35b24472a0101d8d6f9c6a6d 100644 (file)
@@ -1392,6 +1392,231 @@ ldns_rdf2buffer_str_amtrelay(ldns_buffer *output, const ldns_rdf *rdf)
        return ldns_buffer_status(output);
 }
 
+#ifdef RRTYPE_SVCB_HTTPS
+ldns_status svcparam_key2buffer_str(ldns_buffer *output, uint16_t key);
+
+static ldns_status
+svcparam_mandatory2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+       if (sz % 2)
+               return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+       svcparam_key2buffer_str(output, ldns_read_uint16(data));
+       for (data += 2, sz -= 2; sz; data += 2, sz -= 2) {
+               ldns_buffer_write_u8(output, ',');
+               svcparam_key2buffer_str(output, ldns_read_uint16(data));
+       }
+       return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_alpn2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+       uint8_t *eod = data + sz, *dp;
+       bool quote = false;
+       size_t i;
+
+       for (dp = data; dp < eod && !quote; dp += 1 + *dp) {
+               if (dp + 1 + *dp > eod)
+                       return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+               for (i = 0; i < *dp; i++)
+                       if (isspace(dp[i + 1]))
+                               break;
+               quote = i < *dp;
+       }
+       if (quote)
+               ldns_buffer_write_u8(output, '"');
+       while (data < eod) {
+               uint8_t *eot = data + 1 + *data;
+
+               if (eot > eod)
+                       return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+               if (eod - data < (int)sz)
+                       ldns_buffer_write_u8(output, ',');
+
+               for (data += 1; data < eot; data += 1) {
+                       uint8_t ch = *data;
+
+                       if (isprint(ch) || ch == '\t') {
+                               if (ch == '"' ||  ch == ',' || ch == '\\')
+                                       ldns_buffer_write_u8(output, '\\');
+                               ldns_buffer_write_u8(output, ch);
+                       } else
+                               ldns_buffer_printf(output, "\\%03u"
+                                                        , (unsigned)ch);
+               }
+       }
+       if (quote)
+               ldns_buffer_write_u8(output, '"');
+       return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_port2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+       if (sz != 2)
+               return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+       ldns_buffer_printf(output, "%d", (int)ldns_read_uint16(data));
+       return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_ipv4hint2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+       char str[INET_ADDRSTRLEN];
+
+       if (sz % 4 || !inet_ntop(AF_INET, data, str, INET_ADDRSTRLEN))
+               return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+       ldns_buffer_write_string(output, str);
+
+       for (data += 4, sz -= 4; sz ; data += 4, sz -= 4 ) {
+               ldns_buffer_write_u8(output, ',');
+               if (!inet_ntop(AF_INET, data, str, INET_ADDRSTRLEN))
+                       return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+               ldns_buffer_write_string(output, str);
+       }
+       return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_echconfig2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+       size_t str_sz = ldns_b64_ntop_calculate_size(sz);
+       int written;
+
+       if (!ldns_buffer_reserve(output, str_sz))
+               return LDNS_STATUS_MEM_ERR;
+
+       written = ldns_b64_ntop( data, sz
+                              , (char *)ldns_buffer_current(output), str_sz);
+       if (written > 0)
+               ldns_buffer_skip(output, written);
+       else
+               return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+       return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_ipv6hint2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+       char str[INET6_ADDRSTRLEN];
+
+       if (sz % 16 || !inet_ntop(AF_INET6, data, str, INET6_ADDRSTRLEN))
+               return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+       ldns_buffer_write_string(output, str);
+
+       for (data += 16, sz -= 16; sz ; data += 16, sz -= 16) {
+               ldns_buffer_write_u8(output, ',');
+               if (!inet_ntop(AF_INET6, data, str, INET6_ADDRSTRLEN))
+                       return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+               ldns_buffer_write_string(output, str);
+       }
+       return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_value2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+       uint8_t *eod = data + sz, *dp;
+       bool quote = false;
+
+       for (dp = data; dp < eod && !isspace(*dp); dp++)
+               ; /* pass */
+
+       if ((quote = dp < eod))
+               ldns_buffer_write_u8(output, '"');
+
+       for (dp = data; dp < eod; dp++) {
+               uint8_t ch = *dp;
+
+               if (isprint(ch) || ch == '\t') {
+                       if (ch == '"' ||  ch == '\\')
+                               ldns_buffer_write_u8(output, '\\');
+                       ldns_buffer_write_u8(output, ch);
+               } else
+                       ldns_buffer_printf(output, "\\%03u", (unsigned)ch);
+       }
+       if (quote)
+               ldns_buffer_write_u8(output, '"');
+       return ldns_buffer_status(output);
+}
+
+ldns_status
+ldns_rdf2buffer_str_svcparams(ldns_buffer *output, const ldns_rdf *rdf)
+{
+       uint8_t    *data, *dp, *next_dp = NULL;
+       size_t      sz;
+       ldns_status st;
+
+       if (!output)
+               return LDNS_STATUS_NULL;
+
+       if (!rdf || !(data = ldns_rdf_data(rdf)) || !(sz = ldns_rdf_size(rdf)))
+               /* No svcparams is just fine. Just nothing to print. */
+               return LDNS_STATUS_OK;
+       
+       for (dp = data; dp + 4 < data + sz; dp = next_dp) {
+               ldns_svcparam_key key    = ldns_read_uint16(dp);
+               uint16_t          val_sz = ldns_read_uint16(dp + 2);
+
+               if ((next_dp = dp + 4 + val_sz) > data + sz)
+                       return LDNS_STATUS_RDATA_OVERFLOW;
+
+               if (dp > data)
+                       ldns_buffer_write_u8(output, ' ');
+
+               if ((st = svcparam_key2buffer_str(output, key)))
+                       return st;
+
+               if (val_sz == 0)
+                       continue;
+               dp += 4;
+               ldns_buffer_write_u8(output, '=');
+               switch (key) {
+               case LDNS_SVCPARAM_KEY_MANDATORY:
+                       st = svcparam_mandatory2buffer_str(output, val_sz, dp);
+                       break;
+               case LDNS_SVCPARAM_KEY_ALPN:
+                       st = svcparam_alpn2buffer_str(output, val_sz, dp);
+                       break;
+               case LDNS_SVCPARAM_KEY_NO_DEFAULT_ALPN:
+                       return LDNS_STATUS_NO_SVCPARAM_VALUE_EXPECTED;
+               case LDNS_SVCPARAM_KEY_PORT:
+                       st = svcparam_port2buffer_str(output, val_sz, dp);
+                       break;
+               case LDNS_SVCPARAM_KEY_IPV4HINT:
+                       st = svcparam_ipv4hint2buffer_str(output, val_sz, dp);
+                       break;
+               case LDNS_SVCPARAM_KEY_ECHCONFIG:
+                       st = svcparam_echconfig2buffer_str(output, val_sz, dp);
+                       break;
+               case LDNS_SVCPARAM_KEY_IPV6HINT:
+                       st = svcparam_ipv6hint2buffer_str(output, val_sz, dp);
+                       break;
+               default:
+                       st = svcparam_value2buffer_str(output, val_sz, dp);
+                       break;
+               }
+               if (st)
+                       return st;
+       }
+       return ldns_buffer_status(output);
+}
+#else  /* #ifdef RRTYPE_SVCB_HTTPS */
+ldns_status
+ldns_rdf2buffer_str_svcparams(ldns_buffer *output, const ldns_rdf *rdf)
+{
+       (void)output; (void)rdf;
+       return LDNS_STATUS_NOT_IMPL;
+}
+#endif /* #ifdef RRTYPE_SVCB_HTTPS */
 
 static ldns_status
 ldns_rdf2buffer_str_fmt(ldns_buffer *buffer,
@@ -1511,6 +1736,9 @@ ldns_rdf2buffer_str_fmt(ldns_buffer *buffer,
                case LDNS_RDF_TYPE_AMTRELAY:
                        res = ldns_rdf2buffer_str_amtrelay(buffer, rdf);
                        break;
+               case LDNS_RDF_TYPE_SVCPARAMS:
+                       res = ldns_rdf2buffer_str_svcparams(buffer, rdf);
+                       break;
                }
        } else {
                /** This will write mangled RRs */
index e5580fcf90ab433520a26a83a766146b02523cf1..26eb92dcf9309c36f812fcba004457c593fb9a94 100644 (file)
@@ -134,7 +134,13 @@ enum ldns_enum_status {
        LDNS_STATUS_ZONEMD_UNKNOWN_HASH,
        LDNS_STATUS_ZONEMD_INVALID_SOA,
        LDNS_STATUS_NO_ZONEMD,
-       LDNS_STATUS_NO_VALID_ZONEMD
+       LDNS_STATUS_NO_VALID_ZONEMD,
+       LDNS_STATUS_SYNTAX_SVCPARAM_KEY_ERR,
+       LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR,
+       LDNS_STATUS_RESERVED_SVCPARAM_KEY,
+       LDNS_STATUS_NO_SVCPARAM_VALUE_EXPECTED,
+       LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE,
+       LDNS_STATUS_INVALID_SVCPARAM_VALUE
 };
 typedef enum ldns_enum_status ldns_status;
 
index 4b443d05f480db176093398e222b2713ea423933..83fe54e95ffdf1374693057f3abfb00efda150ef 100644 (file)
@@ -646,6 +646,14 @@ ldns_status ldns_rdf2buffer_str_hip(ldns_buffer *output,
 ldns_status ldns_rdf2buffer_str_amtrelay(ldns_buffer *output,
                const ldns_rdf *rdf);
 
+/** 
+ * Converts an LDNS_RDF_TYPE_SVCPARAMS rdata element to presentation format.
+ * \param[in] *rdf The rdata to convert
+ * \param[in] *output The buffer to add the data to
+ * \return LDNS_STATUS_OK on success, and error status on failure
+ */
+ldns_status ldns_rdf2buffer_str_svcparams(ldns_buffer *output,
+               const ldns_rdf *rdf);
 
 /**
  * Converts the data in the rdata field to presentation format and
index 2767e8b00b30f229049ea90e5c7209653facee4a..2dbc1fc5b3764db97500543c6b028d392d763a17 100644 (file)
@@ -142,6 +142,9 @@ enum ldns_enum_rdf_type
        /** draft-ietf-mboned-driad-amt-discovery **/
        LDNS_RDF_TYPE_AMTRELAY,
 
+       /** draft-ietf-dnsop-svcb-https **/
+       LDNS_RDF_TYPE_SVCPARAMS,
+
        /* Aliases */
        LDNS_RDF_TYPE_BITMAP = LDNS_RDF_TYPE_NSEC
 };
@@ -165,7 +168,22 @@ enum ldns_enum_cert_algorithm
 };
 typedef enum ldns_enum_cert_algorithm ldns_cert_algorithm;
 
-
+/**
+ * keys types in SVCPARAMS rdata fields
+ */
+enum ldns_enum_svcparam_key
+{
+       LDNS_SVCPARAM_KEY_MANDATORY             = 0,
+       LDNS_SVCPARAM_KEY_ALPN                  = 1,
+       LDNS_SVCPARAM_KEY_NO_DEFAULT_ALPN       = 2,
+       LDNS_SVCPARAM_KEY_PORT                  = 3,
+       LDNS_SVCPARAM_KEY_IPV4HINT              = 4,
+       LDNS_SVCPARAM_KEY_ECHCONFIG             = 5,
+       LDNS_SVCPARAM_KEY_IPV6HINT              = 6,
+       LDNS_SVCPARAM_KEY_LAST_KEY              = 6,
+       LDNS_SVCPARAM_KEY_RESERVED              = 65535
+};
+typedef        enum ldns_enum_svcparam_key ldns_svcparam_key;
 
 /**
  * Resource record data field.
index b56b23867f5121431047c113792c087d0841bcbf..29b1aa8b988fa02554dd3b4ad9388e19df587e68 100644 (file)
--- a/ldns/rr.h
+++ b/ldns/rr.h
@@ -191,7 +191,9 @@ enum ldns_enum_rr_type
        LDNS_RR_TYPE_CDNSKEY = 60, /* RFC 7344 */
        LDNS_RR_TYPE_OPENPGPKEY = 61, /* RFC 7929 */
        LDNS_RR_TYPE_CSYNC = 62, /* RFC 7477 */
-       LDNS_RR_TYPE_ZONEMD = 63, /* draft-wessels-dns-zone-digest */
+       LDNS_RR_TYPE_ZONEMD = 63, /* draft-ietf-dnsop-dns-zone-digest */
+       LDNS_RR_TYPE_SVCB = 64, /* draft-ietf-dnsop-svcb-https */
+       LDNS_RR_TYPE_HTTPS = 65, /* draft-ietf-dnsop-svcb-https */
 
        LDNS_RR_TYPE_SPF = 99, /* RFC 4408 */
 
index e6f7004366b3c8d7443329c10e0ff3152f54069d..0ccfd6617546f26157950edf7fae37e70be5b170 100644 (file)
@@ -312,16 +312,26 @@ ldns_status ldns_str2rdf_long_str(ldns_rdf **rd, const char *str);
 ldns_status ldns_str2rdf_hip(ldns_rdf **rd, const char *str);
 
 /**
- * Concert a"<precedence> <D-bit> <type> <relay>" encoding
+ * Convert a "<precedence> <D-bit> <type> <relay>" encoding
  * of the value field as specified in Section  4.3.1 of
  * [draft-ietf-mboned-driad-amt-discovery], encoded as wireformat as specified in
- * ection 4.2 of [draft-ietf-mboned-driad-amt-discovery]
+ * Section 4.2 of [draft-ietf-mboned-driad-amt-discovery]
  * \param[in] rd the rdf where to put the data
  * \param[in] str the string to be converted
  * \return ldns_status
  */
 ldns_status ldns_str2rdf_amtrelay(ldns_rdf **rd, const char *str);
 
+/**
+ * Convert a series of "key[=<value>]" encodings to wireformat as described in
+ * [draft-ietf-dnsop-svcb-https].
+ * \param[in] rd the rdf where to put the data
+ * \param[in] str the string to be converted
+ * \return ldns_status
+ */
+ldns_status ldns_str2rdf_svcparams(ldns_rdf **rd, const char *str);
+
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/rdata.c b/rdata.c
index caf853d618c8ca8c2fa77d04ee9cb6eb80376475..4eebeafa541e91d74ed38266465a81cc48370fc3 100644 (file)
--- a/rdata.c
+++ b/rdata.c
@@ -363,6 +363,9 @@ ldns_rdf_new_frm_str(ldns_rdf_type type, const char *str)
        case LDNS_RDF_TYPE_AMTRELAY:
                status = ldns_str2rdf_amtrelay(&rdf, str);
                break;
+       case LDNS_RDF_TYPE_SVCPARAMS:
+               status = ldns_str2rdf_svcparams(&rdf, str);
+               break;
        case LDNS_RDF_TYPE_NONE:
        default:
                /* default default ??? */
diff --git a/rr.c b/rr.c
index 2547acb805f55a62c1f7fb801c56b361d6ec2c8e..7ab6af0907bf683f3baa6349cd84378c1b08e4ee 100644 (file)
--- a/rr.c
+++ b/rr.c
@@ -347,11 +347,12 @@ ldns_rr_new_frm_str_internal(ldns_rr **newrr, const char *str,
                switch (ldns_rr_descriptor_field_type(desc, r_cnt)) {
                case LDNS_RDF_TYPE_B64        :
                case LDNS_RDF_TYPE_HEX        : /* These rdf types may con- */
-               case LDNS_RDF_TYPE_LOC        : /* tain whitespace, only if */
-               case LDNS_RDF_TYPE_WKS        : /* it is the last rd field. */
+               case LDNS_RDF_TYPE_NSEC       : /* tain whitespace, only if */
+               case LDNS_RDF_TYPE_LOC        : /* it is the last rd field. */
+               case LDNS_RDF_TYPE_WKS        :
                case LDNS_RDF_TYPE_IPSECKEY   :
                case LDNS_RDF_TYPE_AMTRELAY   :
-               case LDNS_RDF_TYPE_NSEC       : if (r_cnt == r_max - 1) {
+               case LDNS_RDF_TYPE_SVCPARAMS  : if (r_cnt == r_max - 1) {
                                                        delimiters = "\n";
                                                        break;
                                                }
@@ -1971,7 +1972,13 @@ static const ldns_rdf_type type_zonemd_wireformat[] = {
        LDNS_RDF_TYPE_INT32,
        LDNS_RDF_TYPE_INT8, LDNS_RDF_TYPE_INT8, LDNS_RDF_TYPE_HEX
 };
-
+#ifdef RRTYPE_SVCB_HTTPS
+static const ldns_rdf_type type_svcb_wireformat[] = {
+       LDNS_RDF_TYPE_INT16,
+       LDNS_RDF_TYPE_DNAME, 
+       LDNS_RDF_TYPE_SVCPARAMS
+};
+#endif
 /* nsec3 is some vars, followed by same type of data of nsec */
 static const ldns_rdf_type type_nsec3_wireformat[] = {
 /*     LDNS_RDF_TYPE_NSEC3_VARS, LDNS_RDF_TYPE_NSEC3_NEXT_OWNER, LDNS_RDF_TYPE_NSEC*/
@@ -2218,11 +2225,20 @@ static ldns_rr_descriptor rdata_field_descriptors[] = {
 #else
 {LDNS_RR_TYPE_NULL, "TYPE61", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
 #endif
+       /* 62 */
+       {LDNS_RR_TYPE_CSYNC, "CSYNC", 3, 3, type_csync_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+       /* 63 */
+       {LDNS_RR_TYPE_ZONEMD, "ZONEMD", 4, 4, type_zonemd_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+#ifdef RRTYPE_SVCB_HTTPS
+       /* 64 */
+       {LDNS_RR_TYPE_SVCB, "SVCB", 2, 3, type_svcb_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+       /* 65 */
+       {LDNS_RR_TYPE_HTTPS, "HTTPS", 1, 1, type_svcb_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
 
-{LDNS_RR_TYPE_CSYNC, "CSYNC", 3, 3, type_csync_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
-{LDNS_RR_TYPE_ZONEMD, "ZONEMD", 4, 4, type_zonemd_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+#else
 {LDNS_RR_TYPE_NULL, "TYPE64", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
 {LDNS_RR_TYPE_NULL, "TYPE65", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+#endif
 {LDNS_RR_TYPE_NULL, "TYPE66", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
 {LDNS_RR_TYPE_NULL, "TYPE67", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
 {LDNS_RR_TYPE_NULL, "TYPE68", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
index 0837acf96c67e4f522d7c1a400b6b02d8415e7d5..09a51aa2cd400473b41b4482dbddcb5c0e8308fc 100644 (file)
@@ -1800,3 +1800,619 @@ ldns_str2rdf_amtrelay(ldns_rdf **rd, const char *str)
        if(!*rd) return LDNS_STATUS_MEM_ERR;
        return LDNS_STATUS_OK;
 }
+
+#ifdef RRTYPE_SVCB_HTTPS
+static int
+network_uint16_cmp(const void *a, const void *b)
+{
+       return ((int)ldns_read_uint16(a)) - ((int)ldns_read_uint16(b));
+}
+
+static ldns_status parse_svcparam_key(const char **s, ldns_svcparam_key *key);
+static ldns_status
+parse_svcparam_mandatory(const char **s, uint8_t **dp, uint8_t *eod)
+{
+       bool quoted = false;
+       uint8_t *keys = *dp;
+       int prev_key;
+
+       if (**s == '"') {
+               *s += 1;
+               quoted = true;
+       }
+       for (;;) {
+               ldns_status st;
+               ldns_svcparam_key key;
+
+               if ((st = parse_svcparam_key(s, &key)))
+                       return st;
+
+               if (*dp + 2 > eod)
+                       return LDNS_STATUS_RDATA_OVERFLOW;
+               
+               ldns_write_uint16(*dp, key);
+               *dp += 2;
+
+               if (**s == ',')
+                       *s += 1;
+               else
+                       break;
+       }
+       if (quoted) {
+               if (**s != '"')
+                       return LDNS_STATUS_INVALID_STR;
+               *s += 1;
+       }
+       if (*dp - keys == 0)
+               return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+       if (**s && !isspace(**s))
+               return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+       /* In draft-ietf-dnsop-svcb-https-02 Section 7:
+        *
+        *     In wire format, the keys are represented by their numeric
+        *     values in network byte order, concatenated in ascending order.
+        */
+       qsort(keys, (*dp - keys) / 2, 2, network_uint16_cmp);
+
+       /* In draft-ietf-dnsop-svcb-https-02 Section 7:
+        *
+        *     Keys ...<snip>... MUST NOT appear more than once.
+        */
+       prev_key = -1;
+       while (keys < *dp) {
+               uint16_t key = ldns_read_uint16(keys);
+
+               if (key == prev_key) {
+                       /* "Be conservative in what you send,
+                        *  be liberal in what you accept"
+                        *
+                        * Instead of
+                        *   `return LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE;`,
+                        *
+                        * we eliminate the double occurrence.
+                        */
+                       memmove(keys - 2, keys, *dp - keys);
+                       *dp -= 2;
+               } else {
+                       prev_key = key;
+                       keys += 2;
+               }
+       }
+       return LDNS_STATUS_OK;
+}
+
+INLINE bool parse_escape2(uint8_t *ch_p, const char** str_p)
+{ *str_p += 1; return parse_escape(ch_p, str_p); }
+
+static ldns_status
+parse_svcparam_alpn(const char **s, uint8_t **dp, uint8_t *eod)
+{
+       uint8_t *val;
+       size_t len;
+
+       if (*dp + 1 > eod)
+               return LDNS_STATUS_RDATA_OVERFLOW;
+       *dp += 1;
+       val = *dp;
+       if (**s == '"') {
+               *s += 1;
+               while (**s != '"') {
+                       if (**s == 0)
+                               return LDNS_STATUS_INVALID_STR;
+
+                       else if (**s == ',') {
+                               len = *dp - val;
+                               if (len == 0 || len > 255)
+                                       return LDNS_STATUS_INVALID_STR;
+                               val[-1] = len;
+                               if (*dp + 1 > eod)
+                                       return LDNS_STATUS_RDATA_OVERFLOW;
+                               *dp += 1;
+                               val = *dp;
+                               *s += 1;
+
+                       } else if (*dp + 1 > eod)
+                               return LDNS_STATUS_RDATA_OVERFLOW;
+
+                       else if (**s != '\\')
+                               *(*dp)++ = (uint8_t)*(*s)++;
+
+                       else if (!parse_escape2(*dp, s))
+                               return LDNS_STATUS_SYNTAX_BAD_ESCAPE;
+                       else
+                               *dp += 1;
+               }
+               *s += 1;
+
+       } else while (**s && !isspace(**s)) {
+               if (**s == ',') {
+                       len = *dp - val;
+                       if (len == 0 || len > 255)
+                               return LDNS_STATUS_INVALID_STR;
+                       val[-1] = len;
+                       if (*dp + 1 > eod)
+                               return LDNS_STATUS_RDATA_OVERFLOW;
+                       *dp += 1;
+                       val = *dp;
+                       *s += 1;
+
+               } else if (*dp + 1 > eod)
+                       return LDNS_STATUS_RDATA_OVERFLOW;
+
+               else if (**s != '\\')
+                       *(*dp)++ = (uint8_t)*(*s)++;
+
+               else if (!parse_escape2(*dp, s))
+                       return LDNS_STATUS_SYNTAX_BAD_ESCAPE;
+               else
+                       *dp += 1;
+       }
+       len = *dp - val;
+       if (len == 0 || len > 255)
+               return LDNS_STATUS_INVALID_STR;
+       val[-1] = len;
+       return **s && !isspace(**s) ? LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR
+                                   : LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_value(const char **s, uint8_t **dp, uint8_t *eod)
+{
+       if (**s == '"') {
+               *s += 1;
+               while (**s != '"') {
+                       if (**s == 0)
+                               return LDNS_STATUS_INVALID_STR;
+
+                       else if (*dp + 1 > eod)
+                               return LDNS_STATUS_RDATA_OVERFLOW;
+
+                       else if (**s != '\\')
+                               *(*dp)++ = (uint8_t)*(*s)++;
+
+                       else if (!parse_escape2(*dp, s))
+                               return LDNS_STATUS_SYNTAX_BAD_ESCAPE;
+                       else
+                               *dp += 1;
+               }
+               *s += 1;
+
+       } else while (**s && !isspace(**s)) {
+               if (*dp + 1 > eod)
+                       return LDNS_STATUS_RDATA_OVERFLOW;
+
+               else if (**s != '\\')
+                       *(*dp)++ = (uint8_t)*(*s)++;
+
+               else if (!parse_escape2(*dp, s))
+                       return LDNS_STATUS_SYNTAX_BAD_ESCAPE;
+               else
+                       *dp += 1;
+       }
+       return **s && !isspace(**s) ? LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR
+                                   : LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_port(const char **s, uint8_t **dp, uint8_t *eod)
+{
+       uint8_t *val = *dp;
+       ldns_status st;
+       size_t len;
+       char num_str[6];
+       char *endptr;
+       unsigned long int num;
+
+       if ((st = parse_svcparam_value(s, dp, eod)))
+               return st;
+       len = *dp - val;
+       if (len == 0 || len > 5)
+               return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+       memcpy(num_str, val, len);
+       num_str[len] = 0;
+       num = strtoul(num_str, &endptr, 10);
+       if (*endptr)
+               return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+       ldns_write_uint16(val, num);
+       *dp = val + 2;
+       return LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_ipv4hint(const char **s, uint8_t **dp, uint8_t *eod)                
+{                                                                               
+       bool quoted = false;
+
+       if (**s == '"') {
+               *s += 1;
+               quoted = true;
+       }
+       for (;;) {
+               const char *ipv4_start = *s;
+               char        ipv4_str[16];
+               size_t      len;
+
+               while (isdigit(**s) || **s == '.')
+                       *s += 1;
+               
+               len = *s - ipv4_start;
+               if (len == 0 || len > 15)
+                       return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+               if (*dp + 4 > eod)
+                       return LDNS_STATUS_RDATA_OVERFLOW;
+
+               memcpy(ipv4_str, ipv4_start, len);
+               ipv4_str[len] = 0;
+               if (inet_pton(AF_INET, ipv4_str, *dp) != 1)
+                       return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+               *dp += 4;
+               if (**s == ',')
+                       *s += 1;
+               else
+                       break;
+       }
+       if (quoted) {
+               if (**s != '"')
+                       return LDNS_STATUS_INVALID_STR;
+               *s += 1;
+       }
+       return **s && !isspace(**s) ? LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR
+                                   : LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_echconfig(const char **s, uint8_t **dp, uint8_t *eod)                
+{                                                                               
+       bool quoted = false;
+       const char *b64_str;
+       size_t len, pad, out_len;
+       char in_buf[4096];
+       char *in = in_buf;
+       int out;
+
+       if (**s == '"') {
+               *s += 1;
+               quoted = true;
+       }
+       b64_str = *s;
+       while (isalnum(**s) || **s == '+' || **s == '/' || **s == '=')
+               *s += 1;
+
+       len = *s - b64_str;
+       pad = len % 4;
+       pad = pad ? 4 - pad : 0;
+       if (len == 0 || pad == 3)
+               return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+       if (quoted) {
+               if (**s != '"')
+                       return LDNS_STATUS_INVALID_STR;
+               *s += 1;
+       }
+       if (**s && !isspace(**s))
+               return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+       
+       out_len = ldns_b64_pton_calculate_size(len);
+       if (*dp + out_len > eod)
+               return LDNS_STATUS_RDATA_OVERFLOW;
+
+       if (len + pad > sizeof(in_buf) - 1
+       && !(in = LDNS_XMALLOC(char, len + pad + 1)))
+               return LDNS_STATUS_MEM_ERR;
+
+       memcpy(in, b64_str, len);
+       while (pad--)
+               in[len++] = '=';
+       in[len] = 0;
+       out = ldns_b64_pton(in, *dp, out_len);
+       if (in != in_buf)
+               LDNS_FREE(in);
+
+       if (out <= 0)
+               return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+       *dp += out;
+       return LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_ipv6hint(const char **s, uint8_t **dp, uint8_t *eod)                
+{                                                                               
+       bool quoted = false;
+
+       if (**s == '"') {
+               *s += 1;
+               quoted = true;
+       }
+       for (;;) {
+               const char *ipv6_start = *s;
+               char        ipv6_str[40];
+               size_t      len;
+
+               while (isxdigit(**s) || **s == ':')
+                       *s += 1;
+               
+               len = *s - ipv6_start;
+               if (len == 0 || len > 39)
+                       return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+               if (*dp + 16 > eod)
+                       return LDNS_STATUS_RDATA_OVERFLOW;
+
+               memcpy(ipv6_str, ipv6_start, len);
+               ipv6_str[len] = 0;
+               if (inet_pton(AF_INET6, ipv6_str, *dp) != 1)
+                       return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+               *dp += 16;
+               if (**s == ',')
+                       *s += 1;
+               else
+                       break;
+       }
+       if (quoted) {
+               if (**s != '"')
+                       return LDNS_STATUS_INVALID_STR;
+               *s += 1;
+       }
+       return **s && !isspace(**s) ? LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR
+                                   : LDNS_STATUS_OK;
+}
+
+struct struct_svcparam_key_def {
+       const char *str;
+       size_t      len;
+};
+typedef struct struct_svcparam_key_def svcparam_key_def;
+
+static svcparam_key_def svcparam_key_defs[] = { { "mandatory"      ,  9 }
+                                              , { "alpn"           ,  4 }
+                                              , { "no-default-alpn", 15 }
+                                              , { "port"           ,  4 }
+                                              , { "ipv4hint"       ,  8 }
+                                              , { "echconfig"      ,  9 }
+                                              , { "ipv6hint"       ,  8 } };
+
+static const size_t svcparam_key_defs_len = sizeof(svcparam_key_defs)
+                                          / sizeof(svcparam_key_def);
+
+/* svcparam_key2buffer_str() should actually be in host2str.c, but we need the
+ * svcparam_key_defs for it and it is not an exposed symbol anyway.
+ */
+ldns_status svcparam_key2buffer_str(ldns_buffer *output, uint16_t key)
+{
+       if (key <= LDNS_SVCPARAM_KEY_LAST_KEY)
+               ldns_buffer_write_string(output, svcparam_key_defs[key].str);
+       else
+               ldns_buffer_printf(output, "key%d", (int)key);
+       return  ldns_buffer_status(output);
+}
+
+static ldns_status
+parse_svcparam_key(const char **s, ldns_svcparam_key *key)
+{
+       size_t i, len;
+       const char *key_str = *s;
+       char num_str[6];
+       char *endptr;
+       unsigned long int num;
+
+       /* parse key */
+       while (islower(**s) || isdigit(**s) || **s == '-')
+               *s += 1;
+
+       len = *s - key_str;
+       for (i = 0; i < svcparam_key_defs_len; i++) {
+               if (len == svcparam_key_defs[i].len
+               && !strncmp(key_str, svcparam_key_defs[i].str, len)) {
+                       *key = i;
+                       return LDNS_STATUS_OK;
+               }
+       }
+       if (len < 4 || len > 8 || strncmp(key_str, "key", 3))
+               return LDNS_STATUS_SYNTAX_SVCPARAM_KEY_ERR;
+
+       memcpy(num_str, key_str + 3, len - 3);
+       num_str[len - 3] = 0;
+       num = strtoul(num_str, &endptr, 10);
+       if (*endptr || num > 65535)
+               return LDNS_STATUS_SYNTAX_SVCPARAM_KEY_ERR;
+
+       /* key65535 is Reserved to be an ("Invalid key"), though there is no
+        * physiological reason to deny usage. We restrict ourselves to the
+        * anatomical limitations only to maximize serviceability.
+        * ```
+        * if (num == 65535)
+        *      return LDNS_STATUS_RESERVED_SVCPARAM_KEY;
+        * ```
+        */
+       *key = num;
+       return LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam(const char **s, uint8_t **dp, uint8_t *eod)
+{
+       ldns_svcparam_key key;
+       ldns_status st;
+       uint8_t *val;
+
+       if (*dp + 4 > eod)
+               return LDNS_STATUS_RDATA_OVERFLOW;
+       
+       if ((st = parse_svcparam_key(s, &key)))
+               return st;
+
+       ldns_write_uint16(*dp, key);
+       ldns_write_uint16(*dp + 2, 0);
+       *dp += 4;
+       if (isspace(**s) || !**s)
+               return LDNS_STATUS_OK;
+
+       else if (**s != '=')
+               return LDNS_STATUS_SYNTAX_ERR;
+       *s += 1;
+       val = *dp;
+       switch(key) {
+       case LDNS_SVCPARAM_KEY_MANDATORY:
+               st = parse_svcparam_mandatory(s, dp, eod);
+               break;
+       case LDNS_SVCPARAM_KEY_ALPN:
+               st = parse_svcparam_alpn(s, dp, eod);
+               break;
+       case LDNS_SVCPARAM_KEY_NO_DEFAULT_ALPN:
+               return LDNS_STATUS_NO_SVCPARAM_VALUE_EXPECTED;
+       case LDNS_SVCPARAM_KEY_PORT:
+               st = parse_svcparam_port(s, dp, eod);
+               break;
+       case LDNS_SVCPARAM_KEY_IPV4HINT:
+               st = parse_svcparam_ipv4hint(s, dp, eod);
+               break;
+       case LDNS_SVCPARAM_KEY_ECHCONFIG:
+               st = parse_svcparam_echconfig(s, dp, eod);
+               break;
+       case LDNS_SVCPARAM_KEY_IPV6HINT:
+               st = parse_svcparam_ipv6hint(s, dp, eod);
+               break;
+       default:
+               st = parse_svcparam_value(s, dp, eod);
+               break;
+       }
+       if (st)
+               return st;
+       ldns_write_uint16(val - 2, *dp - val);
+       return LDNS_STATUS_OK;
+}
+
+static int
+svcparam_ptr_cmp(const void *a, const void *b)
+{
+       uint8_t *x = *(uint8_t **)a          , *y = *(uint8_t **)b;
+       uint16_t x_type = ldns_read_uint16(x),  y_type = ldns_read_uint16(y);
+       uint16_t x_len                       ,  y_len;
+
+       if (x_type != y_type)
+               return x_type > y_type ? 1 : -1;
+
+       x_len = ldns_read_uint16(x + 2);
+       y_len = ldns_read_uint16(y + 2);
+
+       return  x_len != y_len
+            ? (x_len >  y_len ? 1 : -1)
+            : (x_len == 0     ? 0 : memcmp(x + 4, y + 4, x_len));
+}
+
+ldns_status
+ldns_str2rdf_svcparams(ldns_rdf **rd, const char *str)
+{
+       uint8_t *data, *dp, *eod, *p, *new_data;
+       ldns_status st = LDNS_STATUS_OK;
+       size_t length, i;
+       size_t nparams = 0;
+       uint8_t **svcparams;
+       int prev_key;
+
+       if (!rd || !str)
+               return LDNS_STATUS_NULL;
+
+       length = strlen(str);
+       /* Worst case space requirement. We'll realloc to actual size later. */
+       if (!(dp = data = LDNS_XMALLOC(uint8_t, length)))
+               return LDNS_STATUS_MEM_ERR;
+       eod = data + length;
+
+       /* Fill data with parsed bytes */
+       for (;;) {
+               while (isspace(*str))
+                       str += 1;
+               if(!*str)
+                       break;
+               if ((st = parse_svcparam(&str, &dp, eod))) {
+                       LDNS_FREE(data);
+                       return st;
+               }
+               nparams += 1;
+       }
+
+       /* draft-ietf-dnsop-svcb-https-02 in Section 2.2:
+        *
+        *     SvcParamKeys SHALL appear in increasing numeric order
+        *
+        * A svcparams array (with pointers to the individual key, value pairs)
+        * is created to qsort the pairs in increasing numeric order.
+        */
+       if (!(svcparams = LDNS_XMALLOC(uint8_t *, nparams))) {
+               LDNS_FREE(data);
+               return LDNS_STATUS_MEM_ERR;
+       }
+       for ( p = data, i = 0
+           ; p < dp && i < nparams
+           ; p += 4 + ldns_read_uint16(p + 2))
+               svcparams[i++] = p;
+
+       qsort(svcparams, i, sizeof(uint8_t *), svcparam_ptr_cmp);
+
+       /* Write out the (key, value) pairs to a newly allocated data in
+        * sorted order.
+        */
+       length = dp - data;
+       if (!(new_data = LDNS_XMALLOC(uint8_t, length))) {
+               LDNS_FREE(data);
+               LDNS_FREE(svcparams);
+               return LDNS_STATUS_MEM_ERR;
+       }
+       prev_key = -1;
+       for ( p = new_data, i = 0
+           ; p < new_data + length && i < nparams
+           ; p += 4 + ldns_read_uint16(p + 2), i += 1) {
+               uint16_t key = ldns_read_uint16(svcparams[i]);
+
+               /* In draft-ietf-dnsop-svcb-https-02 Section 2.1:
+                *
+                *     SvcParams ...<snip>... keys MUST NOT be repeated.
+                *
+                * ldns will not impose this limitation on the library user,
+                * but we can merge completely equal repetitions into one.
+                * So, not doing
+                * ```
+                * if (key == prev_key)
+                *      return LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE;
+                * ```
+                * but instead:
+                */
+               if (key == prev_key && ldns_read_uint16(svcparams[i] + 2)
+                                   == ldns_read_uint16(svcparams[i - 1] + 2)
+               &&  0 == memcmp( svcparams[i    ] + 4
+                              , svcparams[i - 1] + 4
+                              , ldns_read_uint16(svcparams[i] + 2))) {
+                       p -= 4 + ldns_read_uint16(svcparams[i] + 2);
+                       continue;
+               }
+               memcpy(p, svcparams[i], 4 + ldns_read_uint16(svcparams[i] + 2));
+               prev_key = key;
+       }
+       LDNS_FREE(data);
+       LDNS_FREE(svcparams);
+
+       /* Create rdf */
+       *rd = ldns_rdf_new(LDNS_RDF_TYPE_SVCPARAMS, p - new_data, new_data);
+       if (! *rd) {
+               LDNS_FREE(new_data);
+               return LDNS_STATUS_MEM_ERR;
+       }
+       return LDNS_STATUS_OK;
+}
+#else  /* #ifdef RRTYPE_SVCB_HTTPS */
+ldns_status
+ldns_str2rdf_svcparams(ldns_rdf **rd, const char *str)
+{
+       (void)rd; (void)str;
+       return LDNS_STATUS_NOT_IMPL;
+}
+#endif /* #ifdef RRTYPE_SVCB_HTTPS */
index 1473a9478dfb44afd425db58f467d0b428f5b252..ee70fbdeb34e9e6395f00751da3a4891af6560a9 100644 (file)
@@ -273,6 +273,7 @@ ldns_wire2rdf(ldns_rr *rr, const uint8_t *wire, size_t max, size_t *pos)
                case LDNS_RDF_TYPE_IPSECKEY:
                case LDNS_RDF_TYPE_LONG_STR:
                case LDNS_RDF_TYPE_AMTRELAY:
+               case LDNS_RDF_TYPE_SVCPARAMS:
                case LDNS_RDF_TYPE_NONE:
                        /*
                         * Read to end of rr rdata