resolved: accept TXT records with non-UTF8 strings

RFC 6763 is very clear that TXT RRs should allow arbitrary binary
content, hence let's actually accept that. This also means accepting NUL
bytes in the middle of strings.
This commit is contained in:
Lennart Poettering 2015-11-20 19:01:43 +01:00
parent 0a49b6b6dc
commit 2001c80560
4 changed files with 165 additions and 25 deletions

View file

@ -370,6 +370,28 @@ int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) {
return 0;
}
int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) {
void *d;
int r;
assert(p);
assert(s || size == 0);
if (size > 255)
return -E2BIG;
r = dns_packet_extend(p, 1 + size, &d, start);
if (r < 0)
return r;
((uint8_t*) d)[0] = (uint8_t) size;
if (size > 0)
memcpy(((uint8_t*) d) + 1, s, size);
return 0;
}
int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) {
void *w;
int r;
@ -643,19 +665,20 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
break;
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT: {
char **s;
case DNS_TYPE_TXT:
if (strv_isempty(rr->txt.strings)) {
if (!rr->txt.items) {
/* RFC 6763, section 6.1 suggests to generate
* single empty string for an empty array. */
r = dns_packet_append_string(p, "", NULL);
r = dns_packet_append_raw_string(p, NULL, 0, NULL);
if (r < 0)
goto fail;
} else {
STRV_FOREACH(s, rr->txt.strings) {
r = dns_packet_append_string(p, *s, NULL);
DnsTxtItem *i;
LIST_FOREACH(items, i, rr->txt.items) {
r = dns_packet_append_raw_string(p, i->data, i->length, NULL);
if (r < 0)
goto fail;
}
@ -663,7 +686,6 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
r = 0;
break;
}
case DNS_TYPE_A:
r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL);
@ -1062,6 +1084,35 @@ fail:
return r;
}
int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) {
size_t saved_rindex;
uint8_t c;
int r;
assert(p);
saved_rindex = p->rindex;
r = dns_packet_read_uint8(p, &c, NULL);
if (r < 0)
goto fail;
r = dns_packet_read(p, c, ret, NULL);
if (r < 0)
goto fail;
if (size)
*size = c;
if (start)
*start = saved_rindex;
return 0;
fail:
dns_packet_rewind(p, saved_rindex);
return r;
}
int dns_packet_read_name(
DnsPacket *p,
char **_ret,
@ -1412,24 +1463,37 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT:
if (rdlength <= 0) {
DnsTxtItem *i;
/* RFC 6763, section 6.1 suggests to treat
* empty TXT RRs as equivalent to a TXT record
* with a single empty string. */
r = strv_extend(&rr->txt.strings, "");
if (r < 0)
goto fail;
i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */
if (!i)
return -ENOMEM;
rr->txt.items = i;
} else {
DnsTxtItem *last = NULL;
while (p->rindex < offset + rdlength) {
char *s;
DnsTxtItem *i;
const void *data;
size_t sz;
r = dns_packet_read_string(p, &s, NULL);
r = dns_packet_read_raw_string(p, &data, &sz, NULL);
if (r < 0)
goto fail;
return r;
r = strv_consume(&rr->txt.strings, s);
if (r < 0)
goto fail;
i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */
if (!i)
return -ENOMEM;
memcpy(i->data, data, sz);
i->length = sz;
LIST_INSERT_AFTER(items, rr->txt.items, last, i);
last = i;
}
}

View file

@ -155,9 +155,9 @@ int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start);
int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start);
int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start);
int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start);
int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start);
int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start);
int dns_packet_append_name(DnsPacket *p, const char *name,
bool allow_compression, size_t *start);
int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, size_t *start);
int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start);
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start);
@ -167,8 +167,8 @@ int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start);
int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start);
int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start);
int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start);
int dns_packet_read_name(DnsPacket *p, char **ret,
bool allow_compression, size_t *start);
int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start);
int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, size_t *start);
int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start);
int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start);

View file

@ -273,7 +273,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) {
case DNS_TYPE_TXT:
case DNS_TYPE_SPF:
strv_free(rr->txt.strings);
dns_txt_item_free_all(rr->txt.items);
break;
case DNS_TYPE_SOA:
@ -430,7 +430,7 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT:
return strv_equal(a->txt.strings, b->txt.strings);
return dns_txt_item_equal(a->txt.items, b->txt.items);
case DNS_TYPE_A:
return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0;
@ -600,6 +600,43 @@ static char *format_types(Bitmap *types) {
return strjoin("( ", str, " )", NULL);
}
static char *format_txt(DnsTxtItem *first) {
DnsTxtItem *i;
size_t c = 1;
char *p, *s;
LIST_FOREACH(items, i, first)
c += i->length * 4 + 3;
p = s = new(char, c);
if (!s)
return NULL;
LIST_FOREACH(items, i, first) {
size_t j;
if (i != first)
*(p++) = ' ';
*(p++) = '"';
for (j = 0; j < i->length; j++) {
if (i->data[j] < ' ' || i->data[j] == '"' || i->data[j] >= 127) {
*(p++) = '\\';
*(p++) = '0' + (i->data[j] / 100);
*(p++) = '0' + ((i->data[j] / 10) % 10);
*(p++) = '0' + (i->data[j] % 10);
} else
*(p++) = i->data[j];
}
*(p++) = '"';
}
*p = 0;
return s;
}
int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
_cleanup_free_ char *k = NULL, *t = NULL;
char *s;
@ -642,14 +679,13 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT:
t = strv_join_quoted(rr->txt.strings);
t = format_txt(rr->txt.items);
if (!t)
return -ENOMEM;
s = strjoin(k, " ", t, NULL);
if (!s)
return -ENOMEM;
break;
case DNS_TYPE_A: {
@ -890,3 +926,32 @@ int dns_class_from_string(const char *s, uint16_t *class) {
return 0;
}
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) {
DnsTxtItem *n;
if (!i)
return NULL;
n = i->items_next;
free(i);
return dns_txt_item_free_all(n);
}
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
if (!a != !b)
return false;
if (!a)
return true;
if (a->length != b->length)
return false;
if (memcmp(a->data, b->data, a->length) != 0)
return false;
return dns_txt_item_equal(a->items_next, b->items_next);
}

View file

@ -27,9 +27,11 @@
#include "dns-type.h"
#include "hashmap.h"
#include "in-addr-util.h"
#include "list.h"
typedef struct DnsResourceKey DnsResourceKey;
typedef struct DnsResourceRecord DnsResourceRecord;
typedef struct DnsTxtItem DnsTxtItem;
/* DNS record classes, see RFC 1035 */
enum {
@ -45,6 +47,12 @@ struct DnsResourceKey {
char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */
};
struct DnsTxtItem {
size_t length;
LIST_FIELDS(DnsTxtItem, items);
uint8_t data[];
};
struct DnsResourceRecord {
unsigned n_ref;
DnsResourceKey *key;
@ -73,7 +81,7 @@ struct DnsResourceRecord {
} hinfo;
struct {
char **strings;
DnsTxtItem *items;
} txt, spf;
struct {
@ -198,6 +206,9 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor
int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref);
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i);
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
const char *dns_class_to_string(uint16_t type);
int dns_class_from_string(const char *name, uint16_t *class);