diff --git a/src/gldns/parse.c b/src/gldns/parse.c index 53dd7288..6e0a4ec4 100644 --- a/src/gldns/parse.c +++ b/src/gldns/parse.c @@ -149,6 +149,9 @@ gldns_fget_token_l(FILE *f, char *token, const char *delim, size_t limit, int *l if (c != '\0' && c != '\n') { *t++ = c; } + if (c == '\n' && line_nr) { + *line_nr = *line_nr + 1; + } if (c == '\\' && prev_c == '\\') prev_c = 0; else prev_c = c; diff --git a/src/gldns/parseutil.c b/src/gldns/parseutil.c index e7fee906..4730057d 100644 --- a/src/gldns/parseutil.c +++ b/src/gldns/parseutil.c @@ -794,3 +794,18 @@ int gldns_b64url_pton(char const *src, size_t srcsize, uint8_t *target, } return gldns_b64_pton_base(src, srcsize, target, targsize, 1); } + +int gldns_b64_contains_nonurl(char const *src, size_t srcsize) +{ + const char* s = src; + while(*s && srcsize) { + char d = *s++; + srcsize--; + /* the '+' and the '/' and padding '=' is not allowed in b64 + * url encoding */ + if(d == '+' || d == '/' || d == '=') { + return 1; + } + } + return 0; +} diff --git a/src/gldns/parseutil.h b/src/gldns/parseutil.h index 03bf06d9..8a23de00 100644 --- a/src/gldns/parseutil.h +++ b/src/gldns/parseutil.h @@ -58,7 +58,7 @@ time_t gldns_mktime_from_utc(const struct tm *tm); * The function interprets time as the number of seconds since epoch * with respect to now using serial arithmetics (rfc1982). * That number of seconds is then converted to broken-out time information. - * This is especially usefull when converting the inception and expiration + * This is especially useful when converting the inception and expiration * fields of RRSIG records. * * \param[in] time number of seconds since epoch (midnight, January 1st, 1970) @@ -102,6 +102,7 @@ size_t gldns_b64_pton_calculate_size(size_t srcsize); int gldns_b64_pton(char const *src, uint8_t *target, size_t targsize); int gldns_b64url_pton(char const *src, size_t srcsize, uint8_t *target, size_t targsize); +int gldns_b64_contains_nonurl(char const *src, size_t srcsize); /** * calculates the size needed to store the result of b32_ntop diff --git a/src/gldns/rrdef.c b/src/gldns/rrdef.c index 881b3425..1b8c02fe 100644 --- a/src/gldns/rrdef.c +++ b/src/gldns/rrdef.c @@ -155,6 +155,9 @@ static const gldns_rdf_type type_csync_wireformat[] = { static const gldns_rdf_type type_zonemd_wireformat[] = { GLDNS_RDF_TYPE_INT32, GLDNS_RDF_TYPE_INT8, GLDNS_RDF_TYPE_INT8, GLDNS_RDF_TYPE_HEX }; +static const gldns_rdf_type type_svcb_wireformat[] = { + GLDNS_RDF_TYPE_INT16, GLDNS_RDF_TYPE_DNAME +}; /* nsec3 is some vars, followed by same type of data of nsec */ static const gldns_rdf_type type_nsec3_wireformat[] = { /* GLDNS_RDF_TYPE_NSEC3_VARS, GLDNS_RDF_TYPE_NSEC3_NEXT_OWNER, GLDNS_RDF_TYPE_NSEC*/ @@ -388,8 +391,10 @@ static gldns_rr_descriptor rdata_field_descriptors[] = { {GLDNS_RR_TYPE_CSYNC, "CSYNC", 3, 3, type_csync_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 }, /* 63 */ {GLDNS_RR_TYPE_ZONEMD, "ZONEMD", 4, 4, type_zonemd_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 }, -{(enum gldns_enum_rr_type)0, "TYPE64", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 }, -{(enum gldns_enum_rr_type)0, "TYPE65", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 }, + /* 64 */ + {GLDNS_RR_TYPE_SVCB, "SVCB", 2, 2, type_svcb_wireformat, GLDNS_RDF_TYPE_SVCPARAM, GLDNS_RR_NO_COMPRESS, 0 }, + /* 65 */ + {GLDNS_RR_TYPE_HTTPS, "HTTPS", 2, 2, type_svcb_wireformat, GLDNS_RDF_TYPE_SVCPARAM, GLDNS_RR_NO_COMPRESS, 0 }, {(enum gldns_enum_rr_type)0, "TYPE66", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 }, {(enum gldns_enum_rr_type)0, "TYPE67", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 }, {(enum gldns_enum_rr_type)0, "TYPE68", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 }, diff --git a/src/gldns/rrdef.h b/src/gldns/rrdef.h index 412394dd..1ddf854d 100644 --- a/src/gldns/rrdef.h +++ b/src/gldns/rrdef.h @@ -196,8 +196,8 @@ enum gldns_enum_rr_type GLDNS_RR_TYPE_OPENPGPKEY = 61, /* RFC 7929 */ GLDNS_RR_TYPE_CSYNC = 62, /* RFC 7477 */ GLDNS_RR_TYPE_ZONEMD = 63, /* RFC8976 */ - GLDNS_RR_TYPE_SVCB = 64, - GLDNS_RR_TYPE_HTTPS = 65, + GLDNS_RR_TYPE_SVCB = 64, /* draft-ietf-dnsop-svcb-https-04 */ + GLDNS_RR_TYPE_HTTPS = 65, /* draft-ietf-dnsop-svcb-https-04 */ GLDNS_RR_TYPE_SPF = 99, /* RFC 4408 */ @@ -360,8 +360,13 @@ enum gldns_enum_rdf_type /** TSIG extended 16bit error value */ GLDNS_RDF_TYPE_TSIGERROR, + /* draft-ietf-dnsop-svcb-https-05: + * each SvcParam consisting of a SvcParamKey=SvcParamValue pair or + * a standalone SvcParamKey */ + GLDNS_RDF_TYPE_SVCPARAM, + /* Aliases */ - GLDNS_RDF_TYPE_BITMAP = GLDNS_RDF_TYPE_NSEC + GLDNS_RDF_TYPE_BITMAP = GLDNS_RDF_TYPE_NSEC, }; typedef enum gldns_enum_rdf_type gldns_rdf_type; diff --git a/src/gldns/str2wire.c b/src/gldns/str2wire.c index d78231fa..fa5f1344 100644 --- a/src/gldns/str2wire.c +++ b/src/gldns/str2wire.c @@ -26,11 +26,12 @@ #endif #include +/** bits for the offset */ +#define RET_OFFSET_MASK (((unsigned)(~GLDNS_WIREPARSE_MASK))>>GLDNS_WIREPARSE_SHIFT) /** return an error */ -#define RET_ERR(e, off) ((int)((e)|((off)< 0 && !*quoted && - *(gldns_buffer_current(strbuf)) == ' ') { + (*(gldns_buffer_current(strbuf)) == ' ' || + *(gldns_buffer_current(strbuf)) == '\t')) { gldns_buffer_skip(strbuf, 1); } @@ -603,7 +605,10 @@ gldns_affix_token(gldns_buffer* strbuf, char* token, size_t* token_len, size_t addstrlen = 0; /* add space */ - if(addlen < 1) return 0; + /* when addlen < 2, the token buffer is full considering the NULL byte + * from strlen and will lead to buffer overflow with the second + * assignement below. */ + if(addlen < 2) return 0; token[*token_strlen] = ' '; token[++(*token_strlen)] = 0; @@ -616,6 +621,122 @@ gldns_affix_token(gldns_buffer* strbuf, char* token, size_t* token_len, return 1; } +static int gldns_str2wire_svcparam_key_cmp(const void *a, const void *b) +{ + return gldns_read_uint16(*(uint8_t**) a) + - gldns_read_uint16(*(uint8_t**) b); +} + +/** + * Add constraints to the SVCB RRs which involve the whole set + */ +static int gldns_str2wire_check_svcbparams(uint8_t* rdata, uint16_t rdata_len) +{ + size_t nparams = 0, i; + uint8_t new_rdata[GLDNS_MAX_RDFLEN]; + uint8_t* new_rdata_ptr = new_rdata; + uint8_t* svcparams[MAX_NUMBER_OF_SVCPARAMS]; + uint8_t* rdata_ptr = rdata; + uint16_t rdata_remaining = rdata_len; + + /* find the SvcParams */ + while (rdata_remaining) { + uint16_t svcbparam_len; + + svcparams[nparams] = rdata_ptr; + if (rdata_remaining < 4) + return GLDNS_WIREPARSE_ERR_SVCPARAM_BROKEN_RDATA; + svcbparam_len = gldns_read_uint16(rdata_ptr + 2); + rdata_remaining -= 4; + rdata_ptr += 4; + + if (rdata_remaining < svcbparam_len) + return GLDNS_WIREPARSE_ERR_SVCPARAM_BROKEN_RDATA; + rdata_remaining -= svcbparam_len; + rdata_ptr += svcbparam_len; + + nparams += 1; + if (nparams >= MAX_NUMBER_OF_SVCPARAMS) + return GLDNS_WIREPARSE_ERR_SVCB_TOO_MANY_PARAMS; + } + + /* In draft-ietf-dnsop-svcb-https-06 Section 7: + * + * In wire format, the keys are represented by their numeric + * values in network byte order, concatenated in ascending order. + */ + qsort((void *)svcparams + ,nparams + ,sizeof(uint8_t*) + ,gldns_str2wire_svcparam_key_cmp); + + + /* The code below revolves around sematic errors in the SVCParam set. + * So long as we do not distinguish between running Unbound as a primary + * or as a secondary, we default to secondary behavior and we ignore the + * sematic errors. */ + +#ifdef SVCB_SEMANTIC_ERRORS + { + uint8_t* mandatory = NULL; + /* In draft-ietf-dnsop-svcb-https-06 Section 7: + * + * Keys (...) MUST NOT appear more than once. + * + * If they key has already been seen, we have a duplicate + */ + for(i=0; i < nparams; i++) { + uint16_t key = gldns_read_uint16(svcparams[i]); + if(i + 1 < nparams && key == gldns_read_uint16(svcparams[i+1])) + return GLDNS_WIREPARSE_ERR_SVCB_DUPLICATE_KEYS; + if(key == SVCB_KEY_MANDATORY) + mandatory = svcparams[i]; + } + + /* 4. verify that all the SvcParamKeys in mandatory are present */ + if(mandatory) { + /* Divide by sizeof(uint16_t)*/ + uint16_t mandatory_nkeys = gldns_read_uint16(mandatory + 2) / sizeof(uint16_t); + + /* Guaranteed by gldns_str2wire_svcparam_key_value */ + assert(mandatory_nkeys > 0); + + for(i=0; i < mandatory_nkeys; i++) { + uint16_t mandatory_key = gldns_read_uint16( + mandatory + + 2 * sizeof(uint16_t) + + i * sizeof(uint16_t)); + uint8_t found = 0; + size_t j; + + for(j=0; j < nparams; j++) { + if(mandatory_key == gldns_read_uint16(svcparams[j])) { + found = 1; + break; + } + } + + if(!found) + return GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_MISSING_PARAM; + } + } + } +#endif + /* Write rdata in correct order */ + for (i = 0; i < nparams; i++) { + uint16_t svcparam_len = gldns_read_uint16(svcparams[i] + 2) + + 2 * sizeof(uint16_t); + + if ((unsigned)(new_rdata_ptr - new_rdata) + svcparam_len > sizeof(new_rdata)) + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + + memcpy(new_rdata_ptr, svcparams[i], svcparam_len); + new_rdata_ptr += svcparam_len; + } + memcpy(rdata, new_rdata, rdata_len); + return GLDNS_WIREPARSE_ERR_OK; +} + /** parse rdata from string into rr buffer(-remainder after dname). */ static int rrinternal_parse_rdata(gldns_buffer* strbuf, char* token, size_t token_len, @@ -713,6 +834,42 @@ rrinternal_parse_rdata(gldns_buffer* strbuf, char* token, size_t token_len, /* write rdata length */ gldns_write_uint16(rr+dname_len+8, (uint16_t)(rr_cur_len-dname_len-10)); *rr_len = rr_cur_len; + /* SVCB/HTTPS handling */ + if (rr_type == GLDNS_RR_TYPE_SVCB || rr_type == GLDNS_RR_TYPE_HTTPS) { + size_t rdata_len = rr_cur_len - dname_len - 10; + uint8_t *rdata = rr+dname_len + 10; + + /* skip 1st rdata field SvcPriority (uint16_t) */ + if (rdata_len < sizeof(uint16_t)) + return GLDNS_WIREPARSE_ERR_OK; + + rdata_len -= sizeof(uint16_t); + rdata += sizeof(uint16_t); + + /* skip 2nd rdata field dname */ + while (rdata_len && *rdata != 0) { + uint8_t label_len; + + if (*rdata & 0xC0) + return GLDNS_WIREPARSE_ERR_OK; + + label_len = *rdata + 1; + if (rdata_len < label_len) + return GLDNS_WIREPARSE_ERR_OK; + + rdata_len -= label_len; + rdata += label_len; + } + /* The root label is one more character, so smaller + * than 1 + 1 means no Svcparam Keys */ + if (rdata_len < 2 || *rdata != 0) + return GLDNS_WIREPARSE_ERR_OK; + + rdata_len -= 1; + rdata += 1; + return gldns_str2wire_check_svcbparams(rdata, rdata_len); + + } return GLDNS_WIREPARSE_ERR_OK; } @@ -939,6 +1096,524 @@ int gldns_fp2wire_rr_buf(FILE* in, uint8_t* rr, size_t* len, size_t* dname_len, return GLDNS_WIREPARSE_ERR_OK; } +static int +gldns_str2wire_svcparam_key_lookup(const char *key, size_t key_len) +{ + char buf[64]; + char *endptr; + unsigned long int key_value; + + if (key_len >= 4 && key_len <= 8 && !strncmp(key, "key", 3)) { + memcpy(buf, key + 3, key_len - 3); + buf[key_len - 3] = 0; + key_value = strtoul(buf, &endptr, 10); + + if (endptr > buf /* digits seen */ + && *endptr == 0 /* no non-digit chars after digits */ + && key_value <= 65535) /* no overflow */ + return key_value; + + } else switch (key_len) { + case sizeof("mandatory")-1: + if (!strncmp(key, "mandatory", sizeof("mandatory")-1)) + return SVCB_KEY_MANDATORY; + if (!strncmp(key, "echconfig", sizeof("echconfig")-1)) + return SVCB_KEY_ECH; /* allow "echconfig as well as "ech" */ + break; + + case sizeof("alpn")-1: + if (!strncmp(key, "alpn", sizeof("alpn")-1)) + return SVCB_KEY_ALPN; + if (!strncmp(key, "port", sizeof("port")-1)) + return SVCB_KEY_PORT; + break; + + case sizeof("no-default-alpn")-1: + if (!strncmp( key , "no-default-alpn" + , sizeof("no-default-alpn")-1)) + return SVCB_KEY_NO_DEFAULT_ALPN; + break; + + case sizeof("ipv4hint")-1: + if (!strncmp(key, "ipv4hint", sizeof("ipv4hint")-1)) + return SVCB_KEY_IPV4HINT; + if (!strncmp(key, "ipv6hint", sizeof("ipv6hint")-1)) + return SVCB_KEY_IPV6HINT; + break; + + case sizeof("ech")-1: + if (!strncmp(key, "ech", sizeof("ech")-1)) + return SVCB_KEY_ECH; + break; + + default: + break; + } + + /* Although the returned value might be used by the caller, + * the parser has erred, so the zone will not be loaded. + */ + return -1; +} + +static int +gldns_str2wire_svcparam_port(const char* val, uint8_t* rd, size_t* rd_len) +{ + unsigned long int port; + char *endptr; + + if (*rd_len < 6) + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + + port = strtoul(val, &endptr, 10); + + if (endptr > val /* digits seen */ + && *endptr == 0 /* no non-digit chars after digits */ + && port <= 65535) { /* no overflow */ + + gldns_write_uint16(rd, SVCB_KEY_PORT); + gldns_write_uint16(rd + 2, sizeof(uint16_t)); + gldns_write_uint16(rd + 4, port); + *rd_len = 6; + + return GLDNS_WIREPARSE_ERR_OK; + } + + return GLDNS_WIREPARSE_ERR_SVCB_PORT_VALUE_SYNTAX; +} + +static int +gldns_str2wire_svcbparam_ipv4hint(const char* val, uint8_t* rd, size_t* rd_len) +{ + size_t count; + char ip_str[INET_ADDRSTRLEN+1]; + char *next_ip_str; + size_t i; + + for (i = 0, count = 1; val[i]; i++) { + if (val[i] == ',') + count += 1; + if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) { + return GLDNS_WIREPARSE_ERR_SVCB_IPV4_TOO_MANY_ADDRESSES; + } + } + + if (*rd_len < (GLDNS_IP4ADDRLEN * count) + 4) + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + + /* count is number of comma's in val + 1; so the actual number of IPv4 + * addresses in val + */ + gldns_write_uint16(rd, SVCB_KEY_IPV4HINT); + gldns_write_uint16(rd + 2, GLDNS_IP4ADDRLEN * count); + *rd_len = 4; + + while (count) { + if (!(next_ip_str = strchr(val, ','))) { + if (inet_pton(AF_INET, val, rd + *rd_len) != 1) + break; + *rd_len += GLDNS_IP4ADDRLEN; + + assert(count == 1); + + } else if (next_ip_str - val >= (int)sizeof(ip_str)) + break; + + else { + memcpy(ip_str, val, next_ip_str - val); + ip_str[next_ip_str - val] = 0; + if (inet_pton(AF_INET, ip_str, rd + *rd_len) != 1) { + break; + } + *rd_len += GLDNS_IP4ADDRLEN; + + val = next_ip_str + 1; + } + count--; + } + if (count) /* verify that we parsed all values */ + return GLDNS_WIREPARSE_ERR_SYNTAX_IP4; + + return GLDNS_WIREPARSE_ERR_OK; +} + +static int +gldns_str2wire_svcbparam_ipv6hint(const char* val, uint8_t* rd, size_t* rd_len) +{ + size_t count; + char ip_str[INET6_ADDRSTRLEN+1]; + char *next_ip_str; + size_t i; + + for (i = 0, count = 1; val[i]; i++) { + if (val[i] == ',') + count += 1; + if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) { + return GLDNS_WIREPARSE_ERR_SVCB_IPV6_TOO_MANY_ADDRESSES; + } + } + + if (*rd_len < (GLDNS_IP6ADDRLEN * count) + 4) + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + + /* count is number of comma's in val + 1; so the actual number of IPv6 + * addresses in val + */ + gldns_write_uint16(rd, SVCB_KEY_IPV6HINT); + gldns_write_uint16(rd + 2, GLDNS_IP6ADDRLEN * count); + *rd_len = 4; + + while (count) { + if (!(next_ip_str = strchr(val, ','))) { + if (inet_pton(AF_INET6, val, rd + *rd_len) != 1) + break; + *rd_len += GLDNS_IP6ADDRLEN; + + assert(count == 1); + + } else if (next_ip_str - val >= (int)sizeof(ip_str)) + break; + + else { + memcpy(ip_str, val, next_ip_str - val); + ip_str[next_ip_str - val] = 0; + if (inet_pton(AF_INET6, ip_str, rd + *rd_len) != 1) { + break; + } + *rd_len += GLDNS_IP6ADDRLEN; + + val = next_ip_str + 1; + } + count--; + } + if (count) /* verify that we parsed all values */ + return GLDNS_WIREPARSE_ERR_SYNTAX_IP6; + + return GLDNS_WIREPARSE_ERR_OK; +} + +/* compare function used for sorting uint16_t's */ +static int +gldns_network_uint16_cmp(const void *a, const void *b) +{ + return ((int)gldns_read_uint16(a)) - ((int)gldns_read_uint16(b)); +} + +static int +gldns_str2wire_svcbparam_mandatory(const char* val, uint8_t* rd, size_t* rd_len) +{ + size_t i, count, val_len; + char* next_key; + + val_len = strlen(val); + + for (i = 0, count = 1; val[i]; i++) { + if (val[i] == ',') + count += 1; + if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) { + return GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_TOO_MANY_KEYS; + } + } + if (sizeof(uint16_t) * (count + 2) > *rd_len) + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + + gldns_write_uint16(rd, SVCB_KEY_MANDATORY); + gldns_write_uint16(rd + 2, sizeof(uint16_t) * count); + *rd_len = 4; + + while (1) { + int svcparamkey; + + if (!(next_key = strchr(val, ','))) { + svcparamkey = gldns_str2wire_svcparam_key_lookup(val, val_len); + + if (svcparamkey < 0) { + return GLDNS_WIREPARSE_ERR_SVCB_UNKNOWN_KEY; + } + + gldns_write_uint16(rd + *rd_len, svcparamkey); + *rd_len += 2; + break; + } else { + svcparamkey = gldns_str2wire_svcparam_key_lookup(val, next_key - val); + + if (svcparamkey < 0) { + return GLDNS_WIREPARSE_ERR_SVCB_UNKNOWN_KEY; + } + + gldns_write_uint16(rd + *rd_len, + svcparamkey); + *rd_len += 2; + } + + val_len -= next_key - val + 1; + val = next_key + 1; /* skip the comma */ + } + + /* In draft-ietf-dnsop-svcb-https-06 Section 7: + * + * "In wire format, the keys are represented by their numeric + * values in network byte order, concatenated in ascending order." + */ + qsort((void *)(rd + 4), count, sizeof(uint16_t), gldns_network_uint16_cmp); + + /* The code below revolves around sematic errors in the SVCParam set. + * So long as we do not distinguish between running Unbound as a primary + * or as a secondary, we default to secondary behavior and we ignore the + * semantic errors. */ +#ifdef SVCB_SEMANTIC_ERRORS + /* In draft-ietf-dnsop-svcb-https-06 Section 8 + * automatically mandatory MUST NOT appear in its own value-list + */ + if (gldns_read_uint16(rd + 4) == SVCB_KEY_MANDATORY) + return GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_IN_MANDATORY; + + /* Guarantee key uniqueness. After the sort we only need to + * compare neighbouring keys */ + if (count > 1) { + for (i = 0; i < count - 1; i++) { + uint8_t* current_pos = (rd + 4 + (sizeof(uint16_t) * i)); + uint16_t key = gldns_read_uint16(current_pos); + + if (key == gldns_read_uint16(current_pos + 2)) { + return GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_DUPLICATE_KEY; + } + } + } +#endif + return GLDNS_WIREPARSE_ERR_OK; +} + +static int +gldns_str2wire_svcbparam_ech_value(const char* val, uint8_t* rd, size_t* rd_len) +{ + uint8_t buffer[GLDNS_MAX_RDFLEN]; + int wire_len; + + /* single 0 represents empty buffer */ + if(strcmp(val, "0") == 0) { + if (*rd_len < 4) + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + gldns_write_uint16(rd, SVCB_KEY_ECH); + gldns_write_uint16(rd + 2, 0); + + return GLDNS_WIREPARSE_ERR_OK; + } + + wire_len = gldns_b64_pton(val, buffer, GLDNS_MAX_RDFLEN); + + if (wire_len <= 0) { + return GLDNS_WIREPARSE_ERR_SYNTAX_B64; + } else if ((unsigned)wire_len + 4 > *rd_len) { + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + } else { + gldns_write_uint16(rd, SVCB_KEY_ECH); + gldns_write_uint16(rd + 2, wire_len); + memcpy(rd + 4, buffer, wire_len); + *rd_len = 4 + wire_len; + + return GLDNS_WIREPARSE_ERR_OK; + } +} + +static const char* +gldns_str2wire_svcbparam_parse_next_unescaped_comma(const char *val) +{ + while (*val) { + /* Only return when the comma is not escaped*/ + if (*val == '\\'){ + ++val; + if (!*val) + break; + } else if (*val == ',') + return val; + + val++; + } + return NULL; +} + +/* The source is already properly unescaped, this double unescaping is purely to allow for + * comma's in comma separated alpn lists. + * + * In draft-ietf-dnsop-svcb-https-06 Section 7: + * To enable simpler parsing, this SvcParamValue MUST NOT contain escape sequences. + */ +static size_t +gldns_str2wire_svcbparam_parse_copy_unescaped(uint8_t *dst, + const char *src, size_t len) +{ + uint8_t *orig_dst = dst; + + while (len) { + if (*src == '\\') { + src++; + len--; + if (!len) + break; + } + *dst++ = *src++; + len--; + } + return (size_t)(dst - orig_dst); +} + +static int +gldns_str2wire_svcbparam_alpn_value(const char* val, + uint8_t* rd, size_t* rd_len) +{ + uint8_t unescaped_dst[GLDNS_MAX_RDFLEN]; + uint8_t *dst = unescaped_dst; + const char *next_str; + size_t str_len; + size_t dst_len; + size_t val_len; + + val_len = strlen(val); + + if (val_len > sizeof(unescaped_dst)) { + return GLDNS_WIREPARSE_ERR_SVCB_ALPN_KEY_TOO_LARGE; + } + while (val_len) { + size_t key_len; + + str_len = (next_str = gldns_str2wire_svcbparam_parse_next_unescaped_comma(val)) + ? (size_t)(next_str - val) : val_len; + + if (str_len > 255) { + return GLDNS_WIREPARSE_ERR_SVCB_ALPN_KEY_TOO_LARGE; + } + + key_len = gldns_str2wire_svcbparam_parse_copy_unescaped(dst + 1, val, str_len); + *dst++ = key_len; + dst += key_len; + + if (!next_str) + break; + + /* skip the comma in the next iteration */ + val_len -= next_str - val + 1; + val = next_str + 1; + } + dst_len = dst - unescaped_dst; + if (*rd_len < 4 + dst_len) + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + gldns_write_uint16(rd, SVCB_KEY_ALPN); + gldns_write_uint16(rd + 2, dst_len); + memcpy(rd + 4, unescaped_dst, dst_len); + *rd_len = 4 + dst_len; + + return GLDNS_WIREPARSE_ERR_OK; +} + +static int +gldns_str2wire_svcparam_value(const char *key, size_t key_len, + const char *val, uint8_t* rd, size_t* rd_len) +{ + size_t str_len; + int svcparamkey = gldns_str2wire_svcparam_key_lookup(key, key_len); + + if (svcparamkey < 0) { + return GLDNS_WIREPARSE_ERR_SVCB_UNKNOWN_KEY; + } + + /* key without value */ + if (val == NULL) { + switch (svcparamkey) { +#ifdef SVCB_SEMANTIC_ERRORS + case SVCB_KEY_MANDATORY: + case SVCB_KEY_ALPN: + case SVCB_KEY_PORT: + case SVCB_KEY_IPV4HINT: + case SVCB_KEY_IPV6HINT: + return GLDNS_WIREPARSE_ERR_SVCB_MISSING_PARAM; +#endif + default: + if (*rd_len < 4) + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + gldns_write_uint16(rd, svcparamkey); + gldns_write_uint16(rd + 2, 0); + *rd_len = 4; + + return GLDNS_WIREPARSE_ERR_OK; + } + } + + /* value is non-empty */ + switch (svcparamkey) { + case SVCB_KEY_PORT: + return gldns_str2wire_svcparam_port(val, rd, rd_len); + case SVCB_KEY_IPV4HINT: + return gldns_str2wire_svcbparam_ipv4hint(val, rd, rd_len); + case SVCB_KEY_IPV6HINT: + return gldns_str2wire_svcbparam_ipv6hint(val, rd, rd_len); + case SVCB_KEY_MANDATORY: + return gldns_str2wire_svcbparam_mandatory(val, rd, rd_len); +#ifdef SVCB_SEMANTIC_ERRORS + case SVCB_KEY_NO_DEFAULT_ALPN: + return GLDNS_WIREPARSE_ERR_SVCB_NO_DEFAULT_ALPN_VALUE; +#endif + case SVCB_KEY_ECH: + return gldns_str2wire_svcbparam_ech_value(val, rd, rd_len); + case SVCB_KEY_ALPN: + return gldns_str2wire_svcbparam_alpn_value(val, rd, rd_len); + default: + str_len = strlen(val); + if (*rd_len < 4 + str_len) + return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + gldns_write_uint16(rd, svcparamkey); + gldns_write_uint16(rd + 2, str_len); + memcpy(rd + 4, val, str_len); + *rd_len = 4 + str_len; + + return GLDNS_WIREPARSE_ERR_OK; + } + + return GLDNS_WIREPARSE_ERR_GENERAL; +} + +static int gldns_str2wire_svcparam_buf(const char* str, uint8_t* rd, size_t* rd_len) +{ + const char* eq_pos; + char unescaped_val[GLDNS_MAX_RDFLEN]; + char* val_out = unescaped_val; + const char* val_in; + + eq_pos = strchr(str, '='); + + /* case: key=value */ + if (eq_pos != NULL && eq_pos[1]) { + val_in = eq_pos + 1; + + /* unescape characters and "" blocks */ + if (*val_in == '"') { + val_in++; + while (*val_in != '"' + && (unsigned)(val_out - unescaped_val + 1) < sizeof(unescaped_val) + && gldns_parse_char( (uint8_t*) val_out, &val_in)) { + val_out++; + } + } else { + while ((unsigned)(val_out - unescaped_val + 1) < sizeof(unescaped_val) + && gldns_parse_char( (uint8_t*) val_out, &val_in)) { + val_out++; + } + } + *val_out = 0; + + return gldns_str2wire_svcparam_value(str, eq_pos - str, + unescaped_val[0] ? unescaped_val : NULL, rd, rd_len); + } + /* case: key= */ + else if (eq_pos != NULL && !(eq_pos[1])) { + return gldns_str2wire_svcparam_value(str, eq_pos - str, NULL, rd, rd_len); + } + /* case: key */ + else { + return gldns_str2wire_svcparam_value(str, strlen(str), NULL, rd, rd_len); + } +} + int gldns_str2wire_rdf_buf(const char* str, uint8_t* rd, size_t* len, gldns_rdf_type rdftype) { @@ -1013,6 +1688,8 @@ int gldns_str2wire_rdf_buf(const char* str, uint8_t* rd, size_t* len, return gldns_str2wire_int16_data_buf(str, rd, len); case GLDNS_RDF_TYPE_AMTRELAY: return gldns_str2wire_amtrelay_buf(str, rd, len); + case GLDNS_RDF_TYPE_SVCPARAM: + return gldns_str2wire_svcparam_buf(str, rd, len); case GLDNS_RDF_TYPE_UNKNOWN: case GLDNS_RDF_TYPE_SERVICE: return GLDNS_WIREPARSE_ERR_NOT_IMPL; diff --git a/src/gldns/str2wire.h b/src/gldns/str2wire.h index 293a4981..361bc18f 100644 --- a/src/gldns/str2wire.h +++ b/src/gldns/str2wire.h @@ -23,10 +23,27 @@ extern "C" { #endif struct gldns_struct_lookup_table; +#define GLDNS_IP4ADDRLEN (32/8) +#define GLDNS_IP6ADDRLEN (128/8) + /** buffer to read an RR, cannot be larger than 64K because of packet size */ #define GLDNS_RR_BUF_SIZE 65535 /* bytes */ #define GLDNS_DEFAULT_TTL 3600 +/* SVCB keys currently defined in draft-ietf-dnsop-svcb-https */ +#define SVCB_KEY_MANDATORY 0 +#define SVCB_KEY_ALPN 1 +#define SVCB_KEY_NO_DEFAULT_ALPN 2 +#define SVCB_KEY_PORT 3 +#define SVCB_KEY_IPV4HINT 4 +#define SVCB_KEY_ECH 5 +#define SVCB_KEY_IPV6HINT 6 +#define SVCPARAMKEY_COUNT 7 + +#define MAX_NUMBER_OF_SVCPARAMS 64 + +#define SVCB_MAX_COMMA_SEPARATED_VALUES 1000 + /* * To convert class and type to string see * gldns_get_rr_class_by_name(str) @@ -170,7 +187,7 @@ uint8_t* gldns_wirerr_get_rdatawl(uint8_t* rr, size_t len, size_t dname_len); #define GLDNS_WIREPARSE_MASK 0x0fff #define GLDNS_WIREPARSE_SHIFT 12 #define GLDNS_WIREPARSE_ERROR(e) ((e)&GLDNS_WIREPARSE_MASK) -#define GLDNS_WIREPARSE_OFFSET(e) (((e)&~GLDNS_WIREPARSE_MASK)>>GLDNS_WIREPARSE_SHIFT) +#define GLDNS_WIREPARSE_OFFSET(e) ((((unsigned)(e))&~GLDNS_WIREPARSE_MASK)>>GLDNS_WIREPARSE_SHIFT) /* use lookuptable to get error string, gldns_wireparse_errors */ #define GLDNS_WIREPARSE_ERR_OK 0 #define GLDNS_WIREPARSE_ERR_GENERAL 342 @@ -204,6 +221,20 @@ uint8_t* gldns_wirerr_get_rdatawl(uint8_t* rr, size_t len, size_t dname_len); #define GLDNS_WIREPARSE_ERR_SYNTAX_INTEGER_OVERFLOW 370 #define GLDNS_WIREPARSE_ERR_INCLUDE 371 #define GLDNS_WIREPARSE_ERR_PARENTHESIS 372 +#define GLDNS_WIREPARSE_ERR_SVCB_UNKNOWN_KEY 373 +#define GLDNS_WIREPARSE_ERR_SVCB_MISSING_PARAM 374 +#define GLDNS_WIREPARSE_ERR_SVCB_TOO_MANY_PARAMS 375 +#define GLDNS_WIREPARSE_ERR_SVCB_DUPLICATE_KEYS 376 +#define GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_TOO_MANY_KEYS 377 +#define GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_MISSING_PARAM 378 +#define GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_DUPLICATE_KEY 379 +#define GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_IN_MANDATORY 380 +#define GLDNS_WIREPARSE_ERR_SVCB_PORT_VALUE_SYNTAX 381 +#define GLDNS_WIREPARSE_ERR_SVCB_IPV4_TOO_MANY_ADDRESSES 382 +#define GLDNS_WIREPARSE_ERR_SVCB_IPV6_TOO_MANY_ADDRESSES 383 +#define GLDNS_WIREPARSE_ERR_SVCB_ALPN_KEY_TOO_LARGE 384 +#define GLDNS_WIREPARSE_ERR_SVCB_NO_DEFAULT_ALPN_VALUE 385 +#define GLDNS_WIREPARSE_ERR_SVCPARAM_BROKEN_RDATA 386 /** * Get reference to a constant string for the (parse) error. diff --git a/src/gldns/wire2str.c b/src/gldns/wire2str.c index 0620b0dc..7b0a7095 100644 --- a/src/gldns/wire2str.c +++ b/src/gldns/wire2str.c @@ -151,6 +151,30 @@ static gldns_lookup_table gldns_wireparse_errors_data[] = { { GLDNS_WIREPARSE_ERR_SYNTAX_INTEGER_OVERFLOW, "Syntax error, integer overflow" }, { GLDNS_WIREPARSE_ERR_INCLUDE, "$INCLUDE directive was seen in the zone" }, { GLDNS_WIREPARSE_ERR_PARENTHESIS, "Parse error, parenthesis mismatch" }, + { GLDNS_WIREPARSE_ERR_SVCB_UNKNOWN_KEY, "Unknown SvcParamKey"}, + { GLDNS_WIREPARSE_ERR_SVCB_MISSING_PARAM, "SvcParam is missing a SvcParamValue"}, + { GLDNS_WIREPARSE_ERR_SVCB_DUPLICATE_KEYS, "Duplicate SVCB key found"}, + { GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_TOO_MANY_KEYS, "Too many keys in mandatory" }, + { GLDNS_WIREPARSE_ERR_SVCB_TOO_MANY_PARAMS, + "Too many SvcParams. Unbound only allows 63 entries" }, + { GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_MISSING_PARAM, + "Mandatory SvcParamKey is missing"}, + { GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_DUPLICATE_KEY, + "Keys in SvcParam mandatory MUST be unique" }, + { GLDNS_WIREPARSE_ERR_SVCB_MANDATORY_IN_MANDATORY, + "mandatory MUST not be included as mandatory parameter" }, + { GLDNS_WIREPARSE_ERR_SVCB_PORT_VALUE_SYNTAX, + "Could not parse port SvcParamValue" }, + { GLDNS_WIREPARSE_ERR_SVCB_IPV4_TOO_MANY_ADDRESSES, + "Too many IPv4 addresses in ipv4hint" }, + { GLDNS_WIREPARSE_ERR_SVCB_IPV6_TOO_MANY_ADDRESSES, + "Too many IPv6 addresses in ipv6hint" }, + { GLDNS_WIREPARSE_ERR_SVCB_ALPN_KEY_TOO_LARGE, + "Alpn strings need to be smaller than 255 chars"}, + { GLDNS_WIREPARSE_ERR_SVCB_NO_DEFAULT_ALPN_VALUE, + "No-default-alpn should not have a value" }, + { GLDNS_WIREPARSE_ERR_SVCPARAM_BROKEN_RDATA, + "General SVCParam error" }, { 0, NULL } }; gldns_lookup_table* gldns_wireparse_errors = gldns_wireparse_errors_data; @@ -198,6 +222,12 @@ static gldns_lookup_table gldns_tsig_errors_data[] = { }; gldns_lookup_table* gldns_tsig_errors = gldns_tsig_errors_data; +/* draft-ietf-dnsop-svcb-https-06: 6. Initial SvcParamKeys */ +const char *svcparamkey_strs[] = { + "mandatory", "alpn", "no-default-alpn", "port", + "ipv4hint", "ech", "ipv6hint" +}; + char* gldns_wire2str_pkt(uint8_t* data, size_t len) { size_t slen = (size_t)gldns_wire2str_pkt_buf(data, len, NULL, 0); @@ -789,6 +819,7 @@ int gldns_wire2str_dname_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen, unsigned i, counter=0; unsigned maxcompr = 1000; /* loop detection, max compr ptrs */ int in_buf = 1; + size_t dname_len = 0; if(comprloop) { if(*comprloop != 0) maxcompr = 30; /* for like ipv6 reverse name, per label */ @@ -844,6 +875,16 @@ int gldns_wire2str_dname_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen, labellen = (uint8_t)*dlen; else if(!in_buf && pos+(size_t)labellen > pkt+pktlen) labellen = (uint8_t)(pkt + pktlen - pos); + dname_len += ((size_t)labellen)+1; + if(dname_len > GLDNS_MAX_DOMAINLEN) { + /* dname_len counts the uncompressed length we have + * seen so far, and the domain name has become too + * long, prevent the loop from printing overly long + * content. */ + w += gldns_str_print(s, slen, + "ErrorDomainNameTooLong"); + return w; + } for(i=0; i<(unsigned)labellen; i++) { w += dname_char_print(s, slen, *pos++); } @@ -942,6 +983,253 @@ int gldns_wire2str_ttl_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen) return gldns_str_print(s, slen, "%u", (unsigned)ttl); } +static int +gldns_print_svcparamkey(char** s, size_t* slen, uint16_t svcparamkey) +{ + if (svcparamkey < SVCPARAMKEY_COUNT) { + return gldns_str_print(s, slen, "%s", svcparamkey_strs[svcparamkey]); + } + else { + return gldns_str_print(s, slen, "key%d", (int)svcparamkey); + } +} + +static int gldns_wire2str_svcparam_port2str(char** s, + size_t* slen, uint16_t data_len, uint8_t* data) +{ + int w = 0; + + if (data_len != 2) + return -1; /* wireformat error, a short is 2 bytes */ + w = gldns_str_print(s, slen, "=%d", (int)gldns_read_uint16(data)); + + return w; +} + +static int gldns_wire2str_svcparam_ipv4hint2str(char** s, + size_t* slen, uint16_t data_len, uint8_t* data) +{ + char ip_str[INET_ADDRSTRLEN + 1]; + + int w = 0; + + assert(data_len > 0); + + if ((data_len % GLDNS_IP4ADDRLEN) == 0) { + if (inet_ntop(AF_INET, data, ip_str, sizeof(ip_str)) == NULL) + return -1; /* wireformat error, incorrect size or inet family */ + + w += gldns_str_print(s, slen, "=%s", ip_str); + data += GLDNS_IP4ADDRLEN; + + while ((data_len -= GLDNS_IP4ADDRLEN) > 0) { + if (inet_ntop(AF_INET, data, ip_str, sizeof(ip_str)) == NULL) + return -1; /* wireformat error, incorrect size or inet family */ + + w += gldns_str_print(s, slen, ",%s", ip_str); + data += GLDNS_IP4ADDRLEN; + } + } else + return -1; + + return w; +} + +static int gldns_wire2str_svcparam_ipv6hint2str(char** s, + size_t* slen, uint16_t data_len, uint8_t* data) +{ + char ip_str[INET6_ADDRSTRLEN + 1]; + + int w = 0; + + assert(data_len > 0); + + if ((data_len % GLDNS_IP6ADDRLEN) == 0) { + if (inet_ntop(AF_INET6, data, ip_str, sizeof(ip_str)) == NULL) + return -1; /* wireformat error, incorrect size or inet family */ + + w += gldns_str_print(s, slen, "=%s", ip_str); + data += GLDNS_IP6ADDRLEN; + + while ((data_len -= GLDNS_IP6ADDRLEN) > 0) { + if (inet_ntop(AF_INET6, data, ip_str, sizeof(ip_str)) == NULL) + return -1; /* wireformat error, incorrect size or inet family */ + + w += gldns_str_print(s, slen, ",%s", ip_str); + data += GLDNS_IP6ADDRLEN; + } + } else + return -1; + + return w; +} + +static int gldns_wire2str_svcparam_mandatory2str(char** s, + size_t* slen, uint16_t data_len, uint8_t* data) +{ + int w = 0; + + assert(data_len > 0); + + if (data_len % sizeof(uint16_t)) + return -1; // wireformat error, data_len must be multiple of shorts + w += gldns_str_print(s, slen, "="); + w += gldns_print_svcparamkey(s, slen, gldns_read_uint16(data)); + data += 2; + + while ((data_len -= sizeof(uint16_t))) { + w += gldns_str_print(s, slen, ","); + w += gldns_print_svcparamkey(s, slen, gldns_read_uint16(data)); + data += 2; + } + + return w; +} + +static int gldns_wire2str_svcparam_alpn2str(char** s, + size_t* slen, uint16_t data_len, uint8_t* data) +{ + uint8_t *dp = (void *)data; + int w = 0; + + assert(data_len > 0); /* Guaranteed by gldns_wire2str_svcparam_scan */ + + w += gldns_str_print(s, slen, "=\""); + while (data_len) { + /* alpn is list of length byte (str_len) followed by a string of that size */ + uint8_t i, str_len = *dp++; + + if (str_len > --data_len) + return -1; + + for (i = 0; i < str_len; i++) { + if (dp[i] == '"' || dp[i] == '\\') + w += gldns_str_print(s, slen, "\\\\\\%c", dp[i]); + + else if (dp[i] == ',') + w += gldns_str_print(s, slen, "\\\\%c", dp[i]); + + else if (!isprint(dp[i])) + w += gldns_str_print(s, slen, "\\%03u", (unsigned) dp[i]); + + else + w += gldns_str_print(s, slen, "%c", dp[i]); + } + dp += str_len; + if ((data_len -= str_len)) + w += gldns_str_print(s, slen, "%s", ","); + } + w += gldns_str_print(s, slen, "\""); + + return w; +} + +static int gldns_wire2str_svcparam_ech2str(char** s, + size_t* slen, uint16_t data_len, uint8_t* data) +{ + int size; + int w = 0; + + assert(data_len > 0); /* Guaranteed by gldns_wire2str_svcparam_scan */ + + w += gldns_str_print(s, slen, "=\""); + + if ((size = gldns_b64_ntop(data, data_len, *s, *slen)) < 0) + return -1; + + (*s) += size; + (*slen) -= size; + + w += gldns_str_print(s, slen, "\""); + + return w + size; +} + +int gldns_wire2str_svcparam_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen) +{ + uint8_t ch; + uint16_t svcparamkey, data_len; + int written_chars = 0; + int r, i; + + /* verify that we have enough data to read svcparamkey and data_len */ + if(*dlen < 4) + return -1; + + svcparamkey = gldns_read_uint16(*d); + data_len = gldns_read_uint16(*d+2); + *d += 4; + *dlen -= 4; + + /* verify that we have data_len data */ + if (data_len > *dlen) + return -1; + + written_chars += gldns_print_svcparamkey(s, slen, svcparamkey); + if (!data_len) { + + /* Some SvcParams MUST have values */ + switch (svcparamkey) { + case SVCB_KEY_ALPN: + case SVCB_KEY_PORT: + case SVCB_KEY_IPV4HINT: + case SVCB_KEY_IPV6HINT: + case SVCB_KEY_MANDATORY: + return -1; + default: + return written_chars; + } + } + + switch (svcparamkey) { + case SVCB_KEY_PORT: + r = gldns_wire2str_svcparam_port2str(s, slen, data_len, *d); + break; + case SVCB_KEY_IPV4HINT: + r = gldns_wire2str_svcparam_ipv4hint2str(s, slen, data_len, *d); + break; + case SVCB_KEY_IPV6HINT: + r = gldns_wire2str_svcparam_ipv6hint2str(s, slen, data_len, *d); + break; + case SVCB_KEY_MANDATORY: + r = gldns_wire2str_svcparam_mandatory2str(s, slen, data_len, *d); + break; + case SVCB_KEY_NO_DEFAULT_ALPN: + return -1; /* wireformat error, should not have a value */ + case SVCB_KEY_ALPN: + r = gldns_wire2str_svcparam_alpn2str(s, slen, data_len, *d); + break; + case SVCB_KEY_ECH: + r = gldns_wire2str_svcparam_ech2str(s, slen, data_len, *d); + break; + default: + r = gldns_str_print(s, slen, "=\""); + + for (i = 0; i < data_len; i++) { + ch = (*d)[i]; + + if (ch == '"' || ch == '\\') + r += gldns_str_print(s, slen, "\\%c", ch); + + else if (!isprint(ch)) + r += gldns_str_print(s, slen, "\\%03u", (unsigned) ch); + + else + r += gldns_str_print(s, slen, "%c", ch); + + } + r += gldns_str_print(s, slen, "\""); + break; + } + if (r <= 0) + return -1; /* wireformat error */ + + written_chars += r; + *d += data_len; + *dlen -= data_len; + return written_chars; +} + int gldns_wire2str_rdf_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen, int rdftype, uint8_t* pkt, size_t pktlen, int* comprloop) { @@ -1022,6 +1310,8 @@ int gldns_wire2str_rdf_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen, case GLDNS_RDF_TYPE_AMTRELAY: return gldns_wire2str_amtrelay_scan(d, dlen, s, slen, pkt, pktlen, comprloop); + case GLDNS_RDF_TYPE_SVCPARAM: + return gldns_wire2str_svcparam_scan(d, dlen, s, slen); case GLDNS_RDF_TYPE_TSIGERROR: return gldns_wire2str_tsigerror_scan(d, dlen, s, slen); } diff --git a/src/gldns/wire2str.h b/src/gldns/wire2str.h index 62a11a2a..0f1e78e7 100644 --- a/src/gldns/wire2str.h +++ b/src/gldns/wire2str.h @@ -59,7 +59,7 @@ char* gldns_wire2str_pkt(uint8_t* data, size_t len); char* gldns_wire2str_rr(uint8_t* rr, size_t len); /** - * Conver wire dname to a string. + * Convert wire dname to a string. * @param dname: the dname in uncompressed wireformat. * @param dname_len: length of the dname. * @return string or NULL on failure. @@ -494,6 +494,18 @@ int gldns_wire2str_opcode_buf(int opcode, char* str, size_t len); int gldns_wire2str_dname_buf(uint8_t* dname, size_t dname_len, char* str, size_t len); +/** + * Convert wire SVCB to a string with user buffer. + * @param d: the SVCB data in uncompressed wireformat. + * @param dlen: length of the SVCB data. + * @param s: the string to write to. + * @param slen: length of string. + * @return the number of characters for this element, excluding zerobyte. + * Is larger or equal than str_len if output was truncated. + */ +int gldns_wire2str_svcparam_scan(uint8_t** d, size_t* dlen, char** s, + size_t* slen); + /** * Scan wireformat rdf field to string, with user buffers. * It shifts the arguments to move along (see gldns_wire2str_pkt_scan).