1304 lines
29 KiB
C
1304 lines
29 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/* draft-ietf-dnsop-svcb-https-02 */
|
|
|
|
#ifndef RDATA_IN_1_SVCB_64_C
|
|
#define RDATA_IN_1_SVCB_64_C
|
|
|
|
#define RRTYPE_SVCB_ATTRIBUTES (DNS_RDATATYPEATTR_FOLLOWADDITIONAL)
|
|
|
|
#define SVCB_MAN_KEY 0
|
|
#define SVCB_ALPN_KEY 1
|
|
#define SVCB_NO_DEFAULT_ALPN_KEY 2
|
|
#define SVCB_DOHPATH_KEY 7
|
|
#define MAX_CNAMES 16 /* See ns/query.c MAX_RESTARTS */
|
|
|
|
/*
|
|
* Service Binding Parameter Registry
|
|
*/
|
|
enum encoding {
|
|
sbpr_text,
|
|
sbpr_port,
|
|
sbpr_ipv4s,
|
|
sbpr_ipv6s,
|
|
sbpr_base64,
|
|
sbpr_empty,
|
|
sbpr_alpn,
|
|
sbpr_keylist,
|
|
sbpr_dohpath
|
|
};
|
|
static const struct {
|
|
const char *name; /* Restricted to lowercase LDH by registry. */
|
|
unsigned int value;
|
|
enum encoding encoding;
|
|
bool initial; /* Part of the first defined set of encodings. */
|
|
} sbpr[] = {
|
|
{ "mandatory", 0, sbpr_keylist, true },
|
|
{ "alpn", 1, sbpr_alpn, true },
|
|
{ "no-default-alpn", 2, sbpr_empty, true },
|
|
{ "port", 3, sbpr_port, true },
|
|
{ "ipv4hint", 4, sbpr_ipv4s, true },
|
|
{ "ech", 5, sbpr_base64, true },
|
|
{ "ipv6hint", 6, sbpr_ipv6s, true },
|
|
{ "dohpath", 7, sbpr_dohpath, false },
|
|
};
|
|
|
|
static isc_result_t
|
|
alpn_fromtxt(isc_textregion_t *source, isc_buffer_t *target) {
|
|
isc_textregion_t source0 = *source;
|
|
do {
|
|
RETERR(commatxt_fromtext(&source0, true, target));
|
|
} while (source0.length != 0);
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
svckeycmp(const void *a1, const void *a2) {
|
|
const unsigned char *u1 = a1, *u2 = a2;
|
|
if (*u1 != *u2) {
|
|
return *u1 - *u2;
|
|
}
|
|
return *(++u1) - *(++u2);
|
|
}
|
|
|
|
static isc_result_t
|
|
svcsortkeylist(isc_buffer_t *target, unsigned int used) {
|
|
isc_region_t region;
|
|
|
|
isc_buffer_usedregion(target, ®ion);
|
|
isc_region_consume(®ion, used);
|
|
INSIST(region.length > 0U);
|
|
qsort(region.base, region.length / 2, 2, svckeycmp);
|
|
/* Reject duplicates. */
|
|
while (region.length >= 4) {
|
|
if (region.base[0] == region.base[2] &&
|
|
region.base[1] == region.base[3])
|
|
{
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
isc_region_consume(®ion, 2);
|
|
}
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
svcb_validate(uint16_t key, isc_region_t *region) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sbpr); i++) {
|
|
if (sbpr[i].value == key) {
|
|
switch (sbpr[i].encoding) {
|
|
case sbpr_port:
|
|
if (region->length != 2) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
break;
|
|
case sbpr_ipv4s:
|
|
if ((region->length % 4) != 0 ||
|
|
region->length == 0)
|
|
{
|
|
return DNS_R_FORMERR;
|
|
}
|
|
break;
|
|
case sbpr_ipv6s:
|
|
if ((region->length % 16) != 0 ||
|
|
region->length == 0)
|
|
{
|
|
return DNS_R_FORMERR;
|
|
}
|
|
break;
|
|
case sbpr_alpn: {
|
|
if (region->length == 0) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
while (region->length != 0) {
|
|
size_t l = *region->base + 1;
|
|
if (l == 1U || l > region->length) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
isc_region_consume(region, l);
|
|
}
|
|
break;
|
|
}
|
|
case sbpr_keylist: {
|
|
if ((region->length % 2) != 0 ||
|
|
region->length == 0)
|
|
{
|
|
return DNS_R_FORMERR;
|
|
}
|
|
/* In order? */
|
|
while (region->length >= 4) {
|
|
if (region->base[0] > region->base[2] ||
|
|
(region->base[0] ==
|
|
region->base[2] &&
|
|
region->base[1] >=
|
|
region->base[3]))
|
|
{
|
|
return DNS_R_FORMERR;
|
|
}
|
|
isc_region_consume(region, 2);
|
|
}
|
|
break;
|
|
}
|
|
case sbpr_text:
|
|
case sbpr_base64:
|
|
break;
|
|
case sbpr_dohpath:
|
|
if (!validate_dohpath(region)) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
break;
|
|
case sbpr_empty:
|
|
if (region->length != 0) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Parse keyname from region.
|
|
*/
|
|
static isc_result_t
|
|
svc_keyfromregion(isc_textregion_t *region, char sep, uint16_t *value,
|
|
isc_buffer_t *target) {
|
|
char *e = NULL;
|
|
size_t i;
|
|
unsigned long ul;
|
|
|
|
/* Look for known key names. */
|
|
for (i = 0; i < ARRAY_SIZE(sbpr); i++) {
|
|
size_t len = strlen(sbpr[i].name);
|
|
if (strncasecmp(region->base, sbpr[i].name, len) != 0 ||
|
|
(region->base[len] != 0 && region->base[len] != sep))
|
|
{
|
|
continue;
|
|
}
|
|
isc_textregion_consume(region, len);
|
|
ul = sbpr[i].value;
|
|
goto finish;
|
|
}
|
|
/* Handle keyXXXXX form. */
|
|
if (strncmp(region->base, "key", 3) != 0) {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
isc_textregion_consume(region, 3);
|
|
/* Disallow [+-]XXXXX which is allowed by strtoul. */
|
|
if (region->length == 0 || *region->base == '-' || *region->base == '+')
|
|
{
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
/* No zero padding. */
|
|
if (region->length > 1 && *region->base == '0' &&
|
|
region->base[1] != sep)
|
|
{
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
ul = strtoul(region->base, &e, 10);
|
|
/* Valid number? */
|
|
if (e == region->base || (*e != sep && *e != 0)) {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
if (ul > 0xffff) {
|
|
return ISC_R_RANGE;
|
|
}
|
|
isc_textregion_consume(region, e - region->base);
|
|
finish:
|
|
if (sep == ',' && region->length == 1) {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
/* Consume separator. */
|
|
if (region->length != 0) {
|
|
isc_textregion_consume(region, 1);
|
|
}
|
|
RETERR(uint16_tobuffer(ul, target));
|
|
SET_IF_NOT_NULL(value, ul);
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
svc_fromtext(isc_textregion_t *region, isc_buffer_t *target) {
|
|
char *e = NULL;
|
|
char abuf[16];
|
|
char tbuf[sizeof("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:255.255.255.255,")];
|
|
isc_buffer_t sb;
|
|
isc_region_t keyregion;
|
|
size_t len;
|
|
uint16_t key;
|
|
unsigned int i;
|
|
unsigned int used;
|
|
unsigned long ul;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sbpr); i++) {
|
|
len = strlen(sbpr[i].name);
|
|
if (strncmp(region->base, sbpr[i].name, len) != 0 ||
|
|
(region->base[len] != 0 && region->base[len] != '='))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (region->base[len] == '=') {
|
|
len++;
|
|
}
|
|
|
|
RETERR(uint16_tobuffer(sbpr[i].value, target));
|
|
isc_textregion_consume(region, len);
|
|
|
|
sb = *target;
|
|
RETERR(uint16_tobuffer(0, target)); /* length */
|
|
|
|
switch (sbpr[i].encoding) {
|
|
case sbpr_text:
|
|
case sbpr_dohpath:
|
|
RETERR(multitxt_fromtext(region, target));
|
|
break;
|
|
case sbpr_alpn:
|
|
RETERR(alpn_fromtxt(region, target));
|
|
break;
|
|
case sbpr_port:
|
|
if (!isdigit((unsigned char)*region->base)) {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
ul = strtoul(region->base, &e, 10);
|
|
if (*e != '\0') {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
if (ul > 0xffff) {
|
|
return ISC_R_RANGE;
|
|
}
|
|
RETERR(uint16_tobuffer(ul, target));
|
|
break;
|
|
case sbpr_ipv4s:
|
|
do {
|
|
snprintf(tbuf, sizeof(tbuf), "%*s",
|
|
(int)(region->length), region->base);
|
|
e = strchr(tbuf, ',');
|
|
if (e != NULL) {
|
|
*e++ = 0;
|
|
isc_textregion_consume(region,
|
|
e - tbuf);
|
|
}
|
|
if (inet_pton(AF_INET, tbuf, abuf) != 1) {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
mem_tobuffer(target, abuf, 4);
|
|
} while (e != NULL);
|
|
break;
|
|
case sbpr_ipv6s:
|
|
do {
|
|
snprintf(tbuf, sizeof(tbuf), "%*s",
|
|
(int)(region->length), region->base);
|
|
e = strchr(tbuf, ',');
|
|
if (e != NULL) {
|
|
*e++ = 0;
|
|
isc_textregion_consume(region,
|
|
e - tbuf);
|
|
}
|
|
if (inet_pton(AF_INET6, tbuf, abuf) != 1) {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
mem_tobuffer(target, abuf, 16);
|
|
} while (e != NULL);
|
|
break;
|
|
case sbpr_base64:
|
|
RETERR(isc_base64_decodestring(region->base, target));
|
|
break;
|
|
case sbpr_empty:
|
|
if (region->length != 0) {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
break;
|
|
case sbpr_keylist:
|
|
if (region->length == 0) {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
used = isc_buffer_usedlength(target);
|
|
while (region->length != 0) {
|
|
RETERR(svc_keyfromregion(region, ',', NULL,
|
|
target));
|
|
}
|
|
RETERR(svcsortkeylist(target, used));
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
len = isc_buffer_usedlength(target) -
|
|
isc_buffer_usedlength(&sb) - 2;
|
|
RETERR(uint16_tobuffer(len, &sb)); /* length */
|
|
switch (sbpr[i].encoding) {
|
|
case sbpr_dohpath:
|
|
/*
|
|
* Apply constraints not applied by multitxt_fromtext.
|
|
*/
|
|
keyregion.base = isc_buffer_used(&sb);
|
|
keyregion.length = isc_buffer_usedlength(target) -
|
|
isc_buffer_usedlength(&sb);
|
|
RETERR(svcb_validate(sbpr[i].value, &keyregion));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
RETERR(svc_keyfromregion(region, '=', &key, target));
|
|
if (region->length == 0) {
|
|
RETERR(uint16_tobuffer(0, target)); /* length */
|
|
/* Sanity check keyXXXXX form. */
|
|
keyregion.base = isc_buffer_used(target);
|
|
keyregion.length = 0;
|
|
return svcb_validate(key, &keyregion);
|
|
}
|
|
sb = *target;
|
|
RETERR(uint16_tobuffer(0, target)); /* dummy length */
|
|
RETERR(multitxt_fromtext(region, target));
|
|
len = isc_buffer_usedlength(target) - isc_buffer_usedlength(&sb) - 2;
|
|
RETERR(uint16_tobuffer(len, &sb)); /* length */
|
|
/* Sanity check keyXXXXX form. */
|
|
keyregion.base = isc_buffer_used(&sb);
|
|
keyregion.length = len;
|
|
return svcb_validate(key, &keyregion);
|
|
}
|
|
|
|
static const char *
|
|
svcparamkey(unsigned short value, enum encoding *encoding, char *buf,
|
|
size_t len) {
|
|
size_t i;
|
|
int n;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sbpr); i++) {
|
|
if (sbpr[i].value == value && sbpr[i].initial) {
|
|
*encoding = sbpr[i].encoding;
|
|
return sbpr[i].name;
|
|
}
|
|
}
|
|
n = snprintf(buf, len, "key%u", value);
|
|
INSIST(n > 0 && (unsigned int)n < len);
|
|
*encoding = sbpr_text;
|
|
return buf;
|
|
}
|
|
|
|
static isc_result_t
|
|
svcsortkeys(isc_buffer_t *target, unsigned int used) {
|
|
isc_region_t r1, r2, man = { .base = NULL, .length = 0 };
|
|
unsigned char buf[1024];
|
|
uint16_t mankey = 0;
|
|
bool have_alpn = false;
|
|
|
|
if (isc_buffer_usedlength(target) == used) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Get the parameters into r1.
|
|
*/
|
|
isc_buffer_usedregion(target, &r1);
|
|
isc_region_consume(&r1, used);
|
|
|
|
while (1) {
|
|
uint16_t key1, len1, key2, len2;
|
|
unsigned char *base1, *base2;
|
|
|
|
r2 = r1;
|
|
|
|
/*
|
|
* Get the first parameter.
|
|
*/
|
|
base1 = r1.base;
|
|
key1 = uint16_fromregion(&r1);
|
|
isc_region_consume(&r1, 2);
|
|
len1 = uint16_fromregion(&r1);
|
|
isc_region_consume(&r1, 2);
|
|
isc_region_consume(&r1, len1);
|
|
|
|
/*
|
|
* Was there only one key left?
|
|
*/
|
|
if (r1.length == 0) {
|
|
if (mankey != 0) {
|
|
/* Is this the last mandatory key? */
|
|
if (key1 != mankey || man.length != 0) {
|
|
return DNS_R_INCONSISTENTRR;
|
|
}
|
|
} else if (key1 == SVCB_MAN_KEY) {
|
|
/* Lone mandatory field. */
|
|
return DNS_R_DISALLOWED;
|
|
} else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY &&
|
|
!have_alpn)
|
|
{
|
|
/* Missing required ALPN field. */
|
|
return DNS_R_DISALLOWED;
|
|
}
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Find the smallest parameter.
|
|
*/
|
|
while (r1.length != 0) {
|
|
base2 = r1.base;
|
|
key2 = uint16_fromregion(&r1);
|
|
isc_region_consume(&r1, 2);
|
|
len2 = uint16_fromregion(&r1);
|
|
isc_region_consume(&r1, 2);
|
|
isc_region_consume(&r1, len2);
|
|
if (key2 == key1) {
|
|
return DNS_R_DUPLICATE;
|
|
}
|
|
if (key2 < key1) {
|
|
base1 = base2;
|
|
key1 = key2;
|
|
len1 = len2;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Do we need to move the smallest parameter to the start?
|
|
*/
|
|
if (base1 != r2.base) {
|
|
size_t offset = 0;
|
|
size_t bytes = len1 + 4;
|
|
size_t length = base1 - r2.base;
|
|
|
|
/*
|
|
* Move the smallest parameter to the start.
|
|
*/
|
|
while (bytes > 0) {
|
|
size_t count;
|
|
|
|
if (bytes > sizeof(buf)) {
|
|
count = sizeof(buf);
|
|
} else {
|
|
count = bytes;
|
|
}
|
|
memmove(buf, base1, count);
|
|
memmove(r2.base + offset + count,
|
|
r2.base + offset, length);
|
|
memmove(r2.base + offset, buf, count);
|
|
base1 += count;
|
|
bytes -= count;
|
|
offset += count;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check ALPN is present when NO-DEFAULT-ALPN is set.
|
|
*/
|
|
if (key1 == SVCB_ALPN_KEY) {
|
|
have_alpn = true;
|
|
} else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY && !have_alpn) {
|
|
/* Missing required ALPN field. */
|
|
return DNS_R_DISALLOWED;
|
|
}
|
|
|
|
/*
|
|
* Check key against mandatory key list.
|
|
*/
|
|
if (mankey != 0) {
|
|
if (key1 > mankey) {
|
|
return DNS_R_INCONSISTENTRR;
|
|
}
|
|
if (key1 == mankey) {
|
|
if (man.length >= 2) {
|
|
mankey = uint16_fromregion(&man);
|
|
isc_region_consume(&man, 2);
|
|
} else {
|
|
mankey = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Is this the mandatory key?
|
|
*/
|
|
if (key1 == SVCB_MAN_KEY) {
|
|
man = r2;
|
|
man.length = len1 + 4;
|
|
isc_region_consume(&man, 4);
|
|
if (man.length >= 2) {
|
|
mankey = uint16_fromregion(&man);
|
|
isc_region_consume(&man, 2);
|
|
if (mankey == SVCB_MAN_KEY) {
|
|
return DNS_R_DISALLOWED;
|
|
}
|
|
} else {
|
|
return DNS_R_SYNTAX;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Consume the smallest parameter.
|
|
*/
|
|
isc_region_consume(&r2, len1 + 4);
|
|
r1 = r2;
|
|
}
|
|
}
|
|
|
|
static isc_result_t
|
|
generic_fromtext_in_svcb(ARGS_FROMTEXT) {
|
|
isc_token_t token;
|
|
dns_name_t name;
|
|
isc_buffer_t buffer;
|
|
bool alias;
|
|
bool ok = true;
|
|
unsigned int used;
|
|
|
|
UNUSED(type);
|
|
UNUSED(rdclass);
|
|
UNUSED(callbacks);
|
|
|
|
/*
|
|
* SvcPriority.
|
|
*/
|
|
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
|
|
false));
|
|
if (token.value.as_ulong > 0xffffU) {
|
|
RETTOK(ISC_R_RANGE);
|
|
}
|
|
RETERR(uint16_tobuffer(token.value.as_ulong, target));
|
|
|
|
alias = token.value.as_ulong == 0;
|
|
|
|
/*
|
|
* TargetName.
|
|
*/
|
|
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
|
|
false));
|
|
dns_name_init(&name, NULL);
|
|
buffer_fromregion(&buffer, &token.value.as_region);
|
|
if (origin == NULL) {
|
|
origin = dns_rootname;
|
|
}
|
|
RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
|
|
if (!alias && (options & DNS_RDATA_CHECKNAMES) != 0) {
|
|
ok = dns_name_ishostname(&name, false);
|
|
}
|
|
if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
|
|
RETTOK(DNS_R_BADNAME);
|
|
}
|
|
if (!ok && callbacks != NULL) {
|
|
warn_badname(&name, lexer, callbacks);
|
|
}
|
|
|
|
/*
|
|
* SvcParams
|
|
*/
|
|
used = isc_buffer_usedlength(target);
|
|
while (1) {
|
|
RETERR(isc_lex_getmastertoken(lexer, &token,
|
|
isc_tokentype_qvpair, true));
|
|
if (token.type == isc_tokentype_eol ||
|
|
token.type == isc_tokentype_eof)
|
|
{
|
|
isc_lex_ungettoken(lexer, &token);
|
|
return svcsortkeys(target, used);
|
|
}
|
|
|
|
if (token.type != isc_tokentype_string && /* key only */
|
|
token.type != isc_tokentype_qvpair &&
|
|
token.type != isc_tokentype_vpair)
|
|
{
|
|
RETTOK(DNS_R_SYNTAX);
|
|
}
|
|
RETTOK(svc_fromtext(&token.value.as_textregion, target));
|
|
}
|
|
}
|
|
|
|
static isc_result_t
|
|
fromtext_in_svcb(ARGS_FROMTEXT) {
|
|
REQUIRE(type == dns_rdatatype_svcb);
|
|
REQUIRE(rdclass == dns_rdataclass_in);
|
|
UNUSED(type);
|
|
UNUSED(rdclass);
|
|
UNUSED(callbacks);
|
|
|
|
return generic_fromtext_in_svcb(CALL_FROMTEXT);
|
|
}
|
|
|
|
static isc_result_t
|
|
generic_totext_in_svcb(ARGS_TOTEXT) {
|
|
isc_region_t region;
|
|
dns_name_t name;
|
|
dns_name_t prefix;
|
|
unsigned int opts;
|
|
char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")];
|
|
unsigned short num;
|
|
int n;
|
|
|
|
REQUIRE(rdata->length != 0);
|
|
|
|
dns_name_init(&name, NULL);
|
|
dns_name_init(&prefix, NULL);
|
|
|
|
dns_rdata_toregion(rdata, ®ion);
|
|
|
|
/*
|
|
* SvcPriority.
|
|
*/
|
|
num = uint16_fromregion(®ion);
|
|
isc_region_consume(®ion, 2);
|
|
n = snprintf(buf, sizeof(buf), "%u ", num);
|
|
INSIST(n > 0 && (unsigned int)n < sizeof(buf));
|
|
RETERR(str_totext(buf, target));
|
|
|
|
/*
|
|
* TargetName.
|
|
*/
|
|
dns_name_fromregion(&name, ®ion);
|
|
isc_region_consume(®ion, name_length(&name));
|
|
opts = name_prefix(&name, tctx->origin, &prefix) ? DNS_NAME_OMITFINALDOT
|
|
: 0;
|
|
RETERR(dns_name_totext(&prefix, opts, target));
|
|
|
|
while (region.length > 0) {
|
|
isc_region_t r;
|
|
enum encoding encoding;
|
|
|
|
RETERR(str_totext(" ", target));
|
|
|
|
INSIST(region.length >= 2);
|
|
num = uint16_fromregion(®ion);
|
|
isc_region_consume(®ion, 2);
|
|
RETERR(str_totext(svcparamkey(num, &encoding, buf, sizeof(buf)),
|
|
target));
|
|
|
|
INSIST(region.length >= 2);
|
|
num = uint16_fromregion(®ion);
|
|
isc_region_consume(®ion, 2);
|
|
|
|
INSIST(region.length >= num);
|
|
r = region;
|
|
r.length = num;
|
|
isc_region_consume(®ion, num);
|
|
if (num == 0) {
|
|
continue;
|
|
}
|
|
if (encoding != sbpr_empty) {
|
|
RETERR(str_totext("=", target));
|
|
}
|
|
switch (encoding) {
|
|
case sbpr_text:
|
|
RETERR(multitxt_totext(&r, target));
|
|
break;
|
|
case sbpr_port:
|
|
num = uint16_fromregion(&r);
|
|
isc_region_consume(&r, 2);
|
|
n = snprintf(buf, sizeof(buf), "%u", num);
|
|
INSIST(n > 0 && (unsigned int)n < sizeof(buf));
|
|
RETERR(str_totext(buf, target));
|
|
INSIST(r.length == 0U);
|
|
break;
|
|
case sbpr_ipv4s:
|
|
while (r.length > 0U) {
|
|
INSIST(r.length >= 4U);
|
|
inet_ntop(AF_INET, r.base, buf, sizeof(buf));
|
|
RETERR(str_totext(buf, target));
|
|
isc_region_consume(&r, 4);
|
|
if (r.length != 0U) {
|
|
RETERR(str_totext(",", target));
|
|
}
|
|
}
|
|
break;
|
|
case sbpr_ipv6s:
|
|
while (r.length > 0U) {
|
|
INSIST(r.length >= 16U);
|
|
inet_ntop(AF_INET6, r.base, buf, sizeof(buf));
|
|
RETERR(str_totext(buf, target));
|
|
isc_region_consume(&r, 16);
|
|
if (r.length != 0U) {
|
|
RETERR(str_totext(",", target));
|
|
}
|
|
}
|
|
break;
|
|
case sbpr_base64:
|
|
RETERR(isc_base64_totext(&r, 0, "", target));
|
|
break;
|
|
case sbpr_alpn:
|
|
INSIST(r.length != 0U);
|
|
RETERR(str_totext("\"", target));
|
|
while (r.length != 0) {
|
|
commatxt_totext(&r, false, true, target);
|
|
if (r.length != 0) {
|
|
RETERR(str_totext(",", target));
|
|
}
|
|
}
|
|
RETERR(str_totext("\"", target));
|
|
break;
|
|
case sbpr_empty:
|
|
INSIST(r.length == 0U);
|
|
break;
|
|
case sbpr_keylist:
|
|
while (r.length > 0) {
|
|
num = uint16_fromregion(&r);
|
|
isc_region_consume(&r, 2);
|
|
RETERR(str_totext(svcparamkey(num, &encoding,
|
|
buf, sizeof(buf)),
|
|
target));
|
|
if (r.length != 0) {
|
|
RETERR(str_totext(",", target));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
totext_in_svcb(ARGS_TOTEXT) {
|
|
REQUIRE(rdata->type == dns_rdatatype_svcb);
|
|
REQUIRE(rdata->rdclass == dns_rdataclass_in);
|
|
REQUIRE(rdata->length != 0);
|
|
|
|
return generic_totext_in_svcb(CALL_TOTEXT);
|
|
}
|
|
|
|
static isc_result_t
|
|
generic_fromwire_in_svcb(ARGS_FROMWIRE) {
|
|
dns_name_t name;
|
|
isc_region_t region, man = { .base = NULL, .length = 0 };
|
|
bool first = true, have_alpn = false;
|
|
uint16_t lastkey = 0, mankey = 0;
|
|
|
|
UNUSED(type);
|
|
UNUSED(rdclass);
|
|
|
|
dctx = dns_decompress_setpermitted(dctx, false);
|
|
|
|
dns_name_init(&name, NULL);
|
|
|
|
/*
|
|
* SvcPriority.
|
|
*/
|
|
isc_buffer_activeregion(source, ®ion);
|
|
if (region.length < 2) {
|
|
return ISC_R_UNEXPECTEDEND;
|
|
}
|
|
RETERR(mem_tobuffer(target, region.base, 2));
|
|
isc_buffer_forward(source, 2);
|
|
|
|
/*
|
|
* TargetName.
|
|
*/
|
|
RETERR(dns_name_fromwire(&name, source, dctx, target));
|
|
|
|
/*
|
|
* SvcParams.
|
|
*/
|
|
isc_buffer_activeregion(source, ®ion);
|
|
while (region.length > 0U) {
|
|
isc_region_t keyregion;
|
|
uint16_t key, len;
|
|
|
|
/*
|
|
* SvcParamKey
|
|
*/
|
|
if (region.length < 2U) {
|
|
return ISC_R_UNEXPECTEDEND;
|
|
}
|
|
RETERR(mem_tobuffer(target, region.base, 2));
|
|
key = uint16_fromregion(®ion);
|
|
isc_region_consume(®ion, 2);
|
|
|
|
/*
|
|
* Keys must be unique and in order.
|
|
*/
|
|
if (!first && key <= lastkey) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
|
|
/*
|
|
* Check mandatory keys.
|
|
*/
|
|
if (mankey != 0) {
|
|
/* Missing mandatory key? */
|
|
if (key > mankey) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
if (key == mankey) {
|
|
/* Get next mandatory key. */
|
|
if (man.length >= 2) {
|
|
mankey = uint16_fromregion(&man);
|
|
isc_region_consume(&man, 2);
|
|
} else {
|
|
mankey = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check alpn present when no-default-alpn is set.
|
|
*/
|
|
if (key == SVCB_ALPN_KEY) {
|
|
have_alpn = true;
|
|
} else if (key == SVCB_NO_DEFAULT_ALPN_KEY && !have_alpn) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
|
|
first = false;
|
|
lastkey = key;
|
|
|
|
/*
|
|
* SvcParamValue length.
|
|
*/
|
|
if (region.length < 2U) {
|
|
return ISC_R_UNEXPECTEDEND;
|
|
}
|
|
RETERR(mem_tobuffer(target, region.base, 2));
|
|
len = uint16_fromregion(®ion);
|
|
isc_region_consume(®ion, 2);
|
|
|
|
/*
|
|
* SvcParamValue.
|
|
*/
|
|
if (region.length < len) {
|
|
return ISC_R_UNEXPECTEDEND;
|
|
}
|
|
|
|
/*
|
|
* Remember manatory key.
|
|
*/
|
|
if (key == SVCB_MAN_KEY) {
|
|
man = region;
|
|
man.length = len;
|
|
/* Get first mandatory key */
|
|
if (man.length >= 2) {
|
|
mankey = uint16_fromregion(&man);
|
|
isc_region_consume(&man, 2);
|
|
if (mankey == SVCB_MAN_KEY) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
} else {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
}
|
|
keyregion = region;
|
|
keyregion.length = len;
|
|
RETERR(svcb_validate(key, &keyregion));
|
|
RETERR(mem_tobuffer(target, region.base, len));
|
|
isc_region_consume(®ion, len);
|
|
isc_buffer_forward(source, len + 4);
|
|
}
|
|
|
|
/*
|
|
* Do we have an outstanding mandatory key?
|
|
*/
|
|
if (mankey != 0) {
|
|
return DNS_R_FORMERR;
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
fromwire_in_svcb(ARGS_FROMWIRE) {
|
|
REQUIRE(type == dns_rdatatype_svcb);
|
|
REQUIRE(rdclass == dns_rdataclass_in);
|
|
|
|
return generic_fromwire_in_svcb(CALL_FROMWIRE);
|
|
}
|
|
|
|
static isc_result_t
|
|
generic_towire_in_svcb(ARGS_TOWIRE) {
|
|
dns_name_t name;
|
|
dns_offsets_t offsets;
|
|
isc_region_t region;
|
|
|
|
REQUIRE(rdata->length != 0);
|
|
|
|
dns_compress_setpermitted(cctx, false);
|
|
|
|
/*
|
|
* SvcPriority.
|
|
*/
|
|
dns_rdata_toregion(rdata, ®ion);
|
|
RETERR(mem_tobuffer(target, region.base, 2));
|
|
isc_region_consume(®ion, 2);
|
|
|
|
/*
|
|
* TargetName.
|
|
*/
|
|
dns_name_init(&name, offsets);
|
|
dns_name_fromregion(&name, ®ion);
|
|
RETERR(dns_name_towire(&name, cctx, target, NULL));
|
|
isc_region_consume(®ion, name_length(&name));
|
|
|
|
/*
|
|
* SvcParams.
|
|
*/
|
|
return mem_tobuffer(target, region.base, region.length);
|
|
}
|
|
|
|
static isc_result_t
|
|
towire_in_svcb(ARGS_TOWIRE) {
|
|
REQUIRE(rdata->type == dns_rdatatype_svcb);
|
|
REQUIRE(rdata->length != 0);
|
|
|
|
return generic_towire_in_svcb(CALL_TOWIRE);
|
|
}
|
|
|
|
static int
|
|
compare_in_svcb(ARGS_COMPARE) {
|
|
isc_region_t region1;
|
|
isc_region_t region2;
|
|
|
|
REQUIRE(rdata1->type == rdata2->type);
|
|
REQUIRE(rdata1->rdclass == rdata2->rdclass);
|
|
REQUIRE(rdata1->type == dns_rdatatype_svcb);
|
|
REQUIRE(rdata1->rdclass == dns_rdataclass_in);
|
|
REQUIRE(rdata1->length != 0);
|
|
REQUIRE(rdata2->length != 0);
|
|
|
|
dns_rdata_toregion(rdata1, ®ion1);
|
|
dns_rdata_toregion(rdata2, ®ion2);
|
|
|
|
return isc_region_compare(®ion1, ®ion2);
|
|
}
|
|
|
|
static isc_result_t
|
|
generic_fromstruct_in_svcb(ARGS_FROMSTRUCT) {
|
|
dns_rdata_in_svcb_t *svcb = source;
|
|
isc_region_t region;
|
|
|
|
REQUIRE(svcb != NULL);
|
|
REQUIRE(svcb->common.rdtype == type);
|
|
REQUIRE(svcb->common.rdclass == rdclass);
|
|
|
|
UNUSED(type);
|
|
UNUSED(rdclass);
|
|
|
|
RETERR(uint16_tobuffer(svcb->priority, target));
|
|
dns_name_toregion(&svcb->svcdomain, ®ion);
|
|
RETERR(isc_buffer_copyregion(target, ®ion));
|
|
|
|
return mem_tobuffer(target, svcb->svc, svcb->svclen);
|
|
}
|
|
|
|
static isc_result_t
|
|
fromstruct_in_svcb(ARGS_FROMSTRUCT) {
|
|
dns_rdata_in_svcb_t *svcb = source;
|
|
|
|
REQUIRE(type == dns_rdatatype_svcb);
|
|
REQUIRE(rdclass == dns_rdataclass_in);
|
|
REQUIRE(svcb != NULL);
|
|
REQUIRE(svcb->common.rdtype == type);
|
|
REQUIRE(svcb->common.rdclass == rdclass);
|
|
|
|
return generic_fromstruct_in_svcb(CALL_FROMSTRUCT);
|
|
}
|
|
|
|
static isc_result_t
|
|
generic_tostruct_in_svcb(ARGS_TOSTRUCT) {
|
|
isc_region_t region;
|
|
dns_rdata_in_svcb_t *svcb = target;
|
|
dns_name_t name;
|
|
|
|
REQUIRE(svcb != NULL);
|
|
REQUIRE(rdata->length != 0);
|
|
|
|
svcb->common.rdclass = rdata->rdclass;
|
|
svcb->common.rdtype = rdata->type;
|
|
ISC_LINK_INIT(&svcb->common, link);
|
|
|
|
dns_rdata_toregion(rdata, ®ion);
|
|
|
|
svcb->priority = uint16_fromregion(®ion);
|
|
isc_region_consume(®ion, 2);
|
|
|
|
dns_name_init(&svcb->svcdomain, NULL);
|
|
dns_name_init(&name, NULL);
|
|
dns_name_fromregion(&name, ®ion);
|
|
isc_region_consume(®ion, name_length(&name));
|
|
|
|
name_duporclone(&name, mctx, &svcb->svcdomain);
|
|
svcb->svclen = region.length;
|
|
svcb->svc = mem_maybedup(mctx, region.base, region.length);
|
|
|
|
svcb->offset = 0;
|
|
svcb->mctx = mctx;
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
tostruct_in_svcb(ARGS_TOSTRUCT) {
|
|
dns_rdata_in_svcb_t *svcb = target;
|
|
|
|
REQUIRE(rdata->rdclass == dns_rdataclass_in);
|
|
REQUIRE(rdata->type == dns_rdatatype_svcb);
|
|
REQUIRE(svcb != NULL);
|
|
REQUIRE(rdata->length != 0);
|
|
|
|
return generic_tostruct_in_svcb(CALL_TOSTRUCT);
|
|
}
|
|
|
|
static void
|
|
generic_freestruct_in_svcb(ARGS_FREESTRUCT) {
|
|
dns_rdata_in_svcb_t *svcb = source;
|
|
|
|
REQUIRE(svcb != NULL);
|
|
|
|
if (svcb->mctx == NULL) {
|
|
return;
|
|
}
|
|
|
|
dns_name_free(&svcb->svcdomain, svcb->mctx);
|
|
isc_mem_free(svcb->mctx, svcb->svc);
|
|
svcb->mctx = NULL;
|
|
}
|
|
|
|
static void
|
|
freestruct_in_svcb(ARGS_FREESTRUCT) {
|
|
dns_rdata_in_svcb_t *svcb = source;
|
|
|
|
REQUIRE(svcb != NULL);
|
|
REQUIRE(svcb->common.rdclass == dns_rdataclass_in);
|
|
REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb);
|
|
|
|
generic_freestruct_in_svcb(CALL_FREESTRUCT);
|
|
}
|
|
|
|
static isc_result_t
|
|
generic_additionaldata_in_svcb(ARGS_ADDLDATA) {
|
|
bool alias, done = false;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t name, *fname = NULL;
|
|
dns_offsets_t offsets;
|
|
dns_rdataset_t rdataset;
|
|
isc_region_t region;
|
|
unsigned int cnames = 0;
|
|
|
|
dns_name_init(&name, offsets);
|
|
dns_rdata_toregion(rdata, ®ion);
|
|
alias = uint16_fromregion(®ion) == 0;
|
|
isc_region_consume(®ion, 2);
|
|
|
|
dns_name_fromregion(&name, ®ion);
|
|
|
|
if (dns_name_equal(&name, dns_rootname)) {
|
|
/*
|
|
* "." only means owner name in service form.
|
|
*/
|
|
if (alias || dns_name_equal(owner, dns_rootname) ||
|
|
!dns_name_ishostname(owner, false))
|
|
{
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
/* Only lookup address records */
|
|
return (add)(arg, owner, dns_rdatatype_a,
|
|
NULL DNS__DB_FILELINE);
|
|
}
|
|
|
|
/*
|
|
* Follow CNAME chains when processing HTTPS and SVCB records.
|
|
*/
|
|
dns_rdataset_init(&rdataset);
|
|
fname = dns_fixedname_initname(&fixed);
|
|
do {
|
|
RETERR((add)(arg, &name, dns_rdatatype_cname,
|
|
&rdataset DNS__DB_FILELINE));
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
isc_result_t result;
|
|
result = dns_rdataset_first(&rdataset);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_rdata_t current = DNS_RDATA_INIT;
|
|
dns_rdata_cname_t cname;
|
|
|
|
dns_rdataset_current(&rdataset, ¤t);
|
|
|
|
result = dns_rdata_tostruct(¤t, &cname,
|
|
NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
dns_name_copy(&cname.cname, fname);
|
|
dns_name_clone(fname, &name);
|
|
} else {
|
|
done = true;
|
|
}
|
|
dns_rdataset_disassociate(&rdataset);
|
|
} else {
|
|
done = true;
|
|
}
|
|
/*
|
|
* Stop following a potentially infinite CNAME chain.
|
|
*/
|
|
if (!done && cnames++ > MAX_CNAMES) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
} while (!done);
|
|
|
|
/*
|
|
* Look up HTTPS/SVCB records when processing the alias form.
|
|
*/
|
|
if (alias) {
|
|
RETERR((add)(arg, &name, rdata->type,
|
|
&rdataset DNS__DB_FILELINE));
|
|
/*
|
|
* Don't return A or AAAA if this is not the last element
|
|
* in the HTTP / SVCB chain.
|
|
*/
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
}
|
|
return (add)(arg, &name, dns_rdatatype_a, NULL DNS__DB_FILELINE);
|
|
}
|
|
|
|
static isc_result_t
|
|
additionaldata_in_svcb(ARGS_ADDLDATA) {
|
|
REQUIRE(rdata->type == dns_rdatatype_svcb);
|
|
REQUIRE(rdata->rdclass == dns_rdataclass_in);
|
|
|
|
return generic_additionaldata_in_svcb(CALL_ADDLDATA);
|
|
}
|
|
|
|
static isc_result_t
|
|
digest_in_svcb(ARGS_DIGEST) {
|
|
isc_region_t region1;
|
|
|
|
REQUIRE(rdata->type == dns_rdatatype_svcb);
|
|
REQUIRE(rdata->rdclass == dns_rdataclass_in);
|
|
|
|
dns_rdata_toregion(rdata, ®ion1);
|
|
return (digest)(arg, ®ion1);
|
|
}
|
|
|
|
static bool
|
|
checkowner_in_svcb(ARGS_CHECKOWNER) {
|
|
REQUIRE(type == dns_rdatatype_svcb);
|
|
REQUIRE(rdclass == dns_rdataclass_in);
|
|
|
|
UNUSED(name);
|
|
UNUSED(type);
|
|
UNUSED(rdclass);
|
|
UNUSED(wildcard);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
generic_checknames_in_svcb(ARGS_CHECKNAMES) {
|
|
isc_region_t region;
|
|
dns_name_t name;
|
|
bool alias;
|
|
|
|
UNUSED(owner);
|
|
|
|
dns_rdata_toregion(rdata, ®ion);
|
|
INSIST(region.length > 1);
|
|
alias = uint16_fromregion(®ion) == 0;
|
|
isc_region_consume(®ion, 2);
|
|
dns_name_init(&name, NULL);
|
|
dns_name_fromregion(&name, ®ion);
|
|
if (!alias && !dns_name_ishostname(&name, false)) {
|
|
if (bad != NULL) {
|
|
dns_name_clone(&name, bad);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
checknames_in_svcb(ARGS_CHECKNAMES) {
|
|
REQUIRE(rdata->type == dns_rdatatype_svcb);
|
|
REQUIRE(rdata->rdclass == dns_rdataclass_in);
|
|
|
|
return generic_checknames_in_svcb(CALL_CHECKNAMES);
|
|
}
|
|
|
|
static int
|
|
casecompare_in_svcb(ARGS_COMPARE) {
|
|
return compare_in_svcb(rdata1, rdata2);
|
|
}
|
|
|
|
static isc_result_t
|
|
generic_rdata_in_svcb_first(dns_rdata_in_svcb_t *svcb) {
|
|
if (svcb->svclen == 0) {
|
|
return ISC_R_NOMORE;
|
|
}
|
|
svcb->offset = 0;
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
generic_rdata_in_svcb_next(dns_rdata_in_svcb_t *svcb) {
|
|
isc_region_t region;
|
|
size_t len;
|
|
|
|
if (svcb->offset >= svcb->svclen) {
|
|
return ISC_R_NOMORE;
|
|
}
|
|
|
|
region.base = svcb->svc + svcb->offset;
|
|
region.length = svcb->svclen - svcb->offset;
|
|
INSIST(region.length >= 4);
|
|
isc_region_consume(®ion, 2);
|
|
len = uint16_fromregion(®ion);
|
|
INSIST(region.length >= len + 2);
|
|
svcb->offset += len + 4;
|
|
return svcb->offset >= svcb->svclen ? ISC_R_NOMORE : ISC_R_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
generic_rdata_in_svcb_current(dns_rdata_in_svcb_t *svcb, isc_region_t *region) {
|
|
size_t len;
|
|
|
|
INSIST(svcb->offset <= svcb->svclen);
|
|
|
|
region->base = svcb->svc + svcb->offset;
|
|
region->length = svcb->svclen - svcb->offset;
|
|
INSIST(region->length >= 4);
|
|
isc_region_consume(region, 2);
|
|
len = uint16_fromregion(region);
|
|
INSIST(region->length >= len + 2);
|
|
region->base = svcb->svc + svcb->offset;
|
|
region->length = len + 4;
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_in_svcb_first(dns_rdata_in_svcb_t *svcb) {
|
|
REQUIRE(svcb != NULL);
|
|
REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb);
|
|
REQUIRE(svcb->common.rdclass == dns_rdataclass_in);
|
|
|
|
return generic_rdata_in_svcb_first(svcb);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_in_svcb_next(dns_rdata_in_svcb_t *svcb) {
|
|
REQUIRE(svcb != NULL);
|
|
REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb);
|
|
REQUIRE(svcb->common.rdclass == dns_rdataclass_in);
|
|
|
|
return generic_rdata_in_svcb_next(svcb);
|
|
}
|
|
|
|
void
|
|
dns_rdata_in_svcb_current(dns_rdata_in_svcb_t *svcb, isc_region_t *region) {
|
|
REQUIRE(svcb != NULL);
|
|
REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb);
|
|
REQUIRE(svcb->common.rdclass == dns_rdataclass_in);
|
|
REQUIRE(region != NULL);
|
|
|
|
generic_rdata_in_svcb_current(svcb, region);
|
|
}
|
|
|
|
#endif /* RDATA_IN_1_SVCB_64_C */
|