From: Willem Toorop Date: Mon, 30 Nov 2020 08:13:05 +0000 (+0100) Subject: Parsing and printing of SVCB and HTTPS RR type X-Git-Tag: 1.8.0-rc.1~37^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c8d888a73d5abbc063d95e2ca36c2578cd007355;p=thirdparty%2Fldns.git Parsing and printing of SVCB and HTTPS RR type --- diff --git a/configure.ac b/configure.ac index 0af6f124..b60dfa9b 100644 --- a/configure.ac +++ b/configure.ac @@ -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 60a76d55..053f1087 100644 --- 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 } }; diff --git a/host2str.c b/host2str.c index 77a6ba7e..69b00f08 100644 --- a/host2str.c +++ b/host2str.c @@ -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 */ diff --git a/ldns/error.h b/ldns/error.h index e5580fcf..26eb92dc 100644 --- a/ldns/error.h +++ b/ldns/error.h @@ -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; diff --git a/ldns/host2str.h b/ldns/host2str.h index 4b443d05..83fe54e9 100644 --- a/ldns/host2str.h +++ b/ldns/host2str.h @@ -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 diff --git a/ldns/rdata.h b/ldns/rdata.h index 2767e8b0..2dbc1fc5 100644 --- a/ldns/rdata.h +++ b/ldns/rdata.h @@ -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. diff --git a/ldns/rr.h b/ldns/rr.h index b56b2386..29b1aa8b 100644 --- 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 */ diff --git a/ldns/str2host.h b/ldns/str2host.h index e6f70043..0ccfd661 100644 --- a/ldns/str2host.h +++ b/ldns/str2host.h @@ -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" " encoding + * Convert a " " 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[=]" 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 caf853d6..4eebeafa 100644 --- 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 2547acb8..7ab6af09 100644 --- 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 }, diff --git a/str2host.c b/str2host.c index 0837acf9..09a51aa2 100644 --- a/str2host.c +++ b/str2host.c @@ -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 ...... 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 ...... 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 */ diff --git a/wire2host.c b/wire2host.c index 1473a947..ee70fbde 100644 --- a/wire2host.c +++ b/wire2host.c @@ -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