diff options
Diffstat (limited to 'src/resolve/resolved-dns-packet.c')
-rw-r--r-- | src/resolve/resolved-dns-packet.c | 1318 |
1 files changed, 911 insertions, 407 deletions
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index b1cde4ab35..337a8c473f 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -19,12 +17,29 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include "utf8.h" -#include "util.h" -#include "strv.h" -#include "unaligned.h" +#include "alloc-util.h" #include "dns-domain.h" #include "resolved-dns-packet.h" +#include "string-table.h" +#include "strv.h" +#include "unaligned.h" +#include "utf8.h" +#include "util.h" + +#define EDNS0_OPT_DO (1<<15) + +typedef struct DnsPacketRewinder { + DnsPacket *packet; + size_t saved_rindex; +} DnsPacketRewinder; + +static void rewind_dns_packet(DnsPacketRewinder *rewinder) { + if (rewinder->packet) + dns_packet_rewind(rewinder->packet, rewinder->saved_rindex); +} + +#define INIT_REWINDER(rewinder, p) do { rewinder.packet = p; rewinder.saved_rindex = p->rindex; } while (0) +#define CANCEL_REWINDER(rewinder) do { rewinder.packet = NULL; } while (0) int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { DnsPacket *p; @@ -54,6 +69,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { p->size = p->rindex = DNS_PACKET_HEADER_SIZE; p->allocated = a; p->protocol = protocol; + p->opt_start = p->opt_size = (size_t) -1; p->n_ref = 1; *ret = p; @@ -61,20 +77,18 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { return 0; } -int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { - DnsPacket *p; - DnsPacketHeader *h; - int r; +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { - assert(ret); + DnsPacketHeader *h; - r = dns_packet_new(&p, protocol, mtu); - if (r < 0) - return r; + assert(p); h = DNS_PACKET_HEADER(p); - if (protocol == DNS_PROTOCOL_LLMNR) + switch(p->protocol) { + case DNS_PROTOCOL_LLMNR: + assert(!truncated); + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, 0 /* opcode */, 0 /* c */, @@ -84,7 +98,23 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { 0 /* ad */, 0 /* cd */, 0 /* rcode */)); - else + break; + + case DNS_PROTOCOL_MDNS: + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + truncated /* tc */, + 0 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + break; + + default: + assert(!truncated); + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, 0 /* opcode */, 0 /* aa */, @@ -92,8 +122,25 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { 1 /* rd (ask for recursion) */, 0 /* ra */, 0 /* ad */, - 0 /* cd */, + dnssec_checking_disabled /* cd */, 0 /* rcode */)); + } +} + +int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { + DnsPacket *p; + int r; + + assert(ret); + + r = dns_packet_new(&p, protocol, mtu); + if (r < 0) + return r; + + /* Always set the TC bit to 0 initially. + * If there are multiple packets later, we'll update the bit shortly before sending. + */ + dns_packet_set_flags(p, dnssec_checking_disabled, false); *ret = p; return 0; @@ -104,6 +151,8 @@ DnsPacket *dns_packet_ref(DnsPacket *p) { if (!p) return NULL; + assert(!p->on_stack); + assert(p->n_ref > 0); p->n_ref++; return p; @@ -116,13 +165,16 @@ static void dns_packet_free(DnsPacket *p) { dns_question_unref(p->question); dns_answer_unref(p->answer); + dns_resource_record_unref(p->opt); while ((s = hashmap_steal_first_key(p->names))) free(s); hashmap_free(p->names); free(p->_data); - free(p); + + if (!p->on_stack) + free(p); } DnsPacket *dns_packet_unref(DnsPacket *p) { @@ -131,6 +183,8 @@ DnsPacket *dns_packet_unref(DnsPacket *p) { assert(p->n_ref > 0); + dns_packet_unref(p->more); + if (p->n_ref == 1) dns_packet_free(p); else @@ -167,6 +221,7 @@ int dns_packet_validate_reply(DnsPacket *p) { return -EBADMSG; switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ if (DNS_PACKET_QDCOUNT(p) != 1) @@ -174,6 +229,13 @@ int dns_packet_validate_reply(DnsPacket *p) { break; + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + default: break; } @@ -200,7 +262,9 @@ int dns_packet_validate_query(DnsPacket *p) { return -EBADMSG; switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_DNS: /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ if (DNS_PACKET_QDCOUNT(p) != 1) return -EBADMSG; @@ -215,6 +279,18 @@ int dns_packet_validate_query(DnsPacket *p) { break; + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_AA(p) != 0 || + DNS_PACKET_RD(p) != 0 || + DNS_PACKET_RA(p) != 0 || + DNS_PACKET_AD(p) != 0 || + DNS_PACKET_CD(p) != 0 || + DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + default: break; } @@ -265,7 +341,7 @@ static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start return 0; } -static void dns_packet_truncate(DnsPacket *p, size_t sz) { +void dns_packet_truncate(DnsPacket *p, size_t sz) { Iterator i; char *s; void *n; @@ -275,7 +351,7 @@ static void dns_packet_truncate(DnsPacket *p, size_t sz) { if (p->size <= sz) return; - HASHMAP_FOREACH_KEY(s, n, p->names, i) { + HASHMAP_FOREACH_KEY(n, s, p->names, i) { if (PTR_TO_SIZE(n) < sz) continue; @@ -347,62 +423,93 @@ 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) { + assert(p); + assert(s); + + return dns_packet_append_raw_string(p, s, strlen(s), start); +} + +int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) { void *d; - size_t l; int r; assert(p); - assert(s); + assert(s || size == 0); - l = strlen(s); - if (l > 255) + if (size > 255) return -E2BIG; - r = dns_packet_extend(p, 1 + l, &d, start); + r = dns_packet_extend(p, 1 + size, &d, start); if (r < 0) return r; - ((uint8_t*) d)[0] = (uint8_t) l; - memcpy(((uint8_t*) d) + 1, s, l); + ((uint8_t*) d)[0] = (uint8_t) size; + + memcpy_safe(((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 dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { + uint8_t *w; int r; + /* Append a label to a packet. Optionally, does this in DNSSEC + * canonical form, if this label is marked as a candidate for + * it, and the canonical form logic is enabled for the + * packet */ + assert(p); assert(d); if (l > DNS_LABEL_MAX) return -E2BIG; - r = dns_packet_extend(p, 1 + l, &w, start); + r = dns_packet_extend(p, 1 + l, (void**) &w, start); if (r < 0) return r; - ((uint8_t*) w)[0] = (uint8_t) l; - memcpy(((uint8_t*) w) + 1, d, l); + *(w++) = (uint8_t) l; + + if (p->canonical_form && canonical_candidate) { + size_t i; + + /* Generate in canonical form, as defined by DNSSEC + * RFC 4034, Section 6.2, i.e. all lower-case. */ + + for (i = 0; i < l; i++) + w[i] = (uint8_t) ascii_tolower(d[i]); + } else + /* Otherwise, just copy the string unaltered. This is + * essential for DNS-SD, where the casing of labels + * matters and needs to be retained. */ + memcpy(w, d, l); return 0; } -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, + bool canonical_candidate, + size_t *start) { + size_t saved_size; int r; assert(p); assert(name); + if (p->refuse_compression) + allow_compression = false; + saved_size = p->size; - while (*name) { - _cleanup_free_ char *s = NULL; + while (!dns_name_is_root(name)) { + const char *z = name; char label[DNS_LABEL_MAX]; size_t n = 0; - int k; if (allow_compression) n = PTR_TO_SIZE(hashmap_get(p->names, name)); @@ -418,32 +525,23 @@ int dns_packet_append_name(DnsPacket *p, const char *name, } } - s = strdup(name); - if (!s) { - r = -ENOMEM; - goto fail; - } - r = dns_label_unescape(&name, label, sizeof(label)); if (r < 0) goto fail; - if (p->protocol == DNS_PROTOCOL_DNS) - k = dns_label_apply_idna(label, r, label, sizeof(label)); - else - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) { - r = k; - goto fail; - } - if (k > 0) - r = k; - - r = dns_packet_append_label(p, label, r, &n); + r = dns_packet_append_label(p, label, r, canonical_candidate, &n); if (r < 0) goto fail; if (allow_compression) { + _cleanup_free_ char *s = NULL; + + s = strdup(z); + if (!s) { + r = -ENOMEM; + goto fail; + } + r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); if (r < 0) goto fail; @@ -480,7 +578,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) saved_size = p->size; - r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, NULL); + r = dns_packet_append_name(p, dns_resource_key_name(k), true, true, NULL); if (r < 0) goto fail; @@ -502,15 +600,13 @@ fail: return r; } -static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, uint8_t *types, size_t *start) { +static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { size_t saved_size; int r; assert(p); assert(types); - - if (length == 0) - return 0; + assert(length > 0); saved_size = p->size; @@ -538,56 +634,166 @@ fail: static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { Iterator i; uint8_t window = 0; - uint8_t len = 0; + uint8_t entry = 0; uint8_t bitmaps[32] = {}; unsigned n; size_t saved_size; int r; assert(p); - assert(types); saved_size = p->size; BITMAP_FOREACH(n, types, i) { - uint8_t entry; - assert(n <= 0xffff); - if ((n << 8) != window) { - r = dns_packet_append_type_window(p, window, len, bitmaps, NULL); + if ((n >> 8) != window && bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); if (r < 0) goto fail; - if (len > 0) { - len = 0; - zero(bitmaps); - } + zero(bitmaps); } - window = n << 8; - len ++; - + window = n >> 8; entry = n & 255; bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); } - r = dns_packet_append_type_window(p, window, len, bitmaps, NULL); + if (bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + } + + if (start) + *start = saved_size; + + return 0; +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +/* Append the OPT pseudo-RR described in RFC6891 */ +int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, int rcode, size_t *start) { + size_t saved_size; + int r; + + assert(p); + /* we must never advertise supported packet size smaller than the legacy max */ + assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX); + assert(rcode >= 0); + assert(rcode <= _DNS_RCODE_MAX); + + if (p->opt_start != (size_t) -1) + return -EBUSY; + + assert(p->opt_size == (size_t) -1); + + saved_size = p->size; + + /* empty name */ + r = dns_packet_append_uint8(p, 0, NULL); + if (r < 0) + return r; + + /* type */ + r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL); if (r < 0) goto fail; + /* class: maximum udp packet that can be received */ + r = dns_packet_append_uint16(p, max_udp_size, NULL); + if (r < 0) + goto fail; + + /* extended RCODE and VERSION */ + r = dns_packet_append_uint16(p, ((uint16_t) rcode & 0x0FF0) << 4, NULL); + if (r < 0) + goto fail; + + /* flags: DNSSEC OK (DO), see RFC3225 */ + r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL); + if (r < 0) + goto fail; + + /* RDLENGTH */ + if (edns0_do && !DNS_PACKET_QR(p)) { + /* If DO is on and this is not a reply, also append RFC6975 Algorithm data */ + + static const uint8_t rfc6975[] = { + + 0, 5, /* OPTION_CODE: DAU */ + 0, 6, /* LIST_LENGTH */ + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA256, + DNSSEC_ALGORITHM_RSASHA512, + DNSSEC_ALGORITHM_ECDSAP256SHA256, + DNSSEC_ALGORITHM_ECDSAP384SHA384, + + 0, 6, /* OPTION_CODE: DHU */ + 0, 3, /* LIST_LENGTH */ + DNSSEC_DIGEST_SHA1, + DNSSEC_DIGEST_SHA256, + DNSSEC_DIGEST_SHA384, + + 0, 7, /* OPTION_CODE: N3U */ + 0, 1, /* LIST_LENGTH */ + NSEC3_ALGORITHM_SHA1, + }; + + r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); + } else + r = dns_packet_append_uint16(p, 0, NULL); + if (r < 0) + goto fail; + + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1); + + p->opt_start = saved_size; + p->opt_size = p->size - saved_size; + if (start) *start = saved_size; return 0; + fail: dns_packet_truncate(p, saved_size); return r; } -int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) { - size_t saved_size, rdlength_offset, end, rdlength; +int dns_packet_truncate_opt(DnsPacket *p) { + assert(p); + + if (p->opt_start == (size_t) -1) { + assert(p->opt_size == (size_t) -1); + return 0; + } + + assert(p->opt_size != (size_t) -1); + assert(DNS_PACKET_ARCOUNT(p) > 0); + + if (p->opt_start + p->opt_size != p->size) + return -EBUSY; + + dns_packet_truncate(p, p->opt_start); + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1); + p->opt_start = p->opt_size = (size_t) -1; + + return 1; +} + +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) { + + size_t saved_size, rdlength_offset, end, rdlength, rds; int r; assert(p); @@ -608,6 +814,8 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; + rds = p->size - saved_size; + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { case DNS_TYPE_SRV: @@ -623,14 +831,14 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->srv.name, true, NULL); + r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); break; case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: case DNS_TYPE_DNAME: - r = dns_packet_append_name(p, rr->ptr.name, true, NULL); + r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); break; case DNS_TYPE_HINFO: @@ -642,19 +850,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; } @@ -662,7 +871,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); @@ -673,11 +881,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_SOA: - r = dns_packet_append_name(p, rr->soa.mname, true, NULL); + r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->soa.rname, true, NULL); + r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); if (r < 0) goto fail; @@ -705,7 +913,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->mx.exchange, true, NULL); + r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); break; case DNS_TYPE_LOC: @@ -761,15 +969,15 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_blob(p, rr->sshfp.key, rr->sshfp.key_size, NULL); + r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL); break; case DNS_TYPE_DNSKEY: - r = dns_packet_append_uint16(p, dnskey_to_flags(rr), NULL); + r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL); if (r < 0) goto fail; - r = dns_packet_append_uint8(p, 3u, NULL); + r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL); if (r < 0) goto fail; @@ -809,7 +1017,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL); + r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); if (r < 0) goto fail; @@ -817,7 +1025,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_NSEC: - r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, NULL); + r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); if (r < 0) goto fail; @@ -826,6 +1034,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star goto fail; break; + case DNS_TYPE_NSEC3: r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); if (r < 0) @@ -860,10 +1069,41 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star goto fail; break; + + case DNS_TYPE_TLSA: + r = dns_packet_append_uint8(p, rr->tlsa.cert_usage, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->tlsa.selector, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->tlsa.matching_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL); + break; + + case DNS_TYPE_CAA: + r = dns_packet_append_uint8(p, rr->caa.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->caa.tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_size, NULL); + break; + + case DNS_TYPE_OPT: + case DNS_TYPE_OPENPGPKEY: case _DNS_TYPE_INVALID: /* unparseable */ default: - r = dns_packet_append_blob(p, rr->generic.data, rr->generic.size, NULL); + r = dns_packet_append_blob(p, rr->generic.data, rr->generic.data_size, NULL); break; } if (r < 0) @@ -872,7 +1112,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star /* Let's calculate the actual data size and update the field */ rdlength = p->size - rdlength_offset - sizeof(uint16_t); if (rdlength > 0xFFFF) { - r = ENOSPC; + r = -ENOSPC; goto fail; } @@ -886,6 +1126,9 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (start) *start = saved_size; + if (rdata_start) + *rdata_start = rds; + return 0; fail: @@ -893,6 +1136,35 @@ fail: return r; } +int dns_packet_append_question(DnsPacket *p, DnsQuestion *q) { + DnsResourceKey *key; + int r; + + assert(p); + + DNS_QUESTION_FOREACH(key, q) { + r = dns_packet_append_key(p, key, NULL); + if (r < 0) + return r; + } + + return 0; +} + +int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a) { + DnsResourceRecord *rr; + int r; + + assert(p); + + DNS_ANSWER_FOREACH(rr, a) { + r = dns_packet_append_rr(p, rr, NULL, NULL); + if (r < 0) + return r; + } + + return 0; +} int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { assert(p); @@ -933,6 +1205,42 @@ int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { return 0; } +static int dns_packet_read_memdup( + DnsPacket *p, size_t size, + void **ret, size_t *ret_size, + size_t *ret_start) { + + const void *src; + size_t start; + int r; + + assert(p); + assert(ret); + + r = dns_packet_read(p, size, &src, &start); + if (r < 0) + return r; + + if (size <= 0) + *ret = NULL; + else { + void *copy; + + copy = memdup(src, size); + if (!copy) + return -ENOMEM; + + *ret = copy; + } + + if (ret_size) + *ret_size = size; + if (ret_start) + *ret_start = start; + + return 0; +} + int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { const void *d; int r; @@ -978,56 +1286,77 @@ 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) { - size_t saved_rindex; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; const void *d; char *t; uint8_t c; int r; assert(p); - - saved_rindex = p->rindex; + INIT_REWINDER(rewinder, p); r = dns_packet_read_uint8(p, &c, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read(p, c, &d, NULL); if (r < 0) - goto fail; + return r; - if (memchr(d, 0, c)) { - r = -EBADMSG; - goto fail; - } + if (memchr(d, 0, c)) + return -EBADMSG; t = strndup(d, c); - if (!t) { - r = -ENOMEM; - goto fail; - } + if (!t) + return -ENOMEM; if (!utf8_is_valid(t)) { free(t); - r = -EBADMSG; - goto fail; + return -EBADMSG; } *ret = t; if (start) - *start = saved_rindex; + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); return 0; +} -fail: - dns_packet_rewind(p, saved_rindex); - return r; +int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + uint8_t c; + int r; + + assert(p); + INIT_REWINDER(rewinder, p); + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + return r; + + r = dns_packet_read(p, c, ret, NULL); + if (r < 0) + return r; + + if (size) + *size = c; + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; } -int dns_packet_read_name(DnsPacket *p, char **_ret, - bool allow_compression, size_t *start) { - size_t saved_rindex, after_rindex = 0, jump_barrier; +int dns_packet_read_name( + DnsPacket *p, + char **_ret, + bool allow_compression, + size_t *start) { + + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + size_t after_rindex = 0, jump_barrier; _cleanup_free_ char *ret = NULL; size_t n = 0, allocated = 0; bool first = true; @@ -1035,44 +1364,42 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, assert(p); assert(_ret); - - saved_rindex = p->rindex; + INIT_REWINDER(rewinder, p); jump_barrier = p->rindex; + if (p->refuse_compression) + allow_compression = false; + for (;;) { uint8_t c, d; r = dns_packet_read_uint8(p, &c, NULL); if (r < 0) - goto fail; + return r; if (c == 0) /* End of name */ break; else if (c <= 63) { - _cleanup_free_ char *t = NULL; const char *label; /* Literal label */ r = dns_packet_read(p, c, (const void**) &label, NULL); if (r < 0) - goto fail; - - r = dns_label_escape(label, c, &t); - if (r < 0) - goto fail; + return r; - if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1)) { - r = -ENOMEM; - goto fail; - } + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; - if (!first) - ret[n++] = '.'; - else + if (first) first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; - memcpy(ret + n, t, r); n += r; continue; } else if (allow_compression && (c & 0xc0) == 0xc0) { @@ -1081,13 +1408,11 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, /* Pointer */ r = dns_packet_read_uint8(p, &d, NULL); if (r < 0) - goto fail; + return r; ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) { - r = -EBADMSG; - goto fail; - } + if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) + return -EBADMSG; if (after_rindex == 0) after_rindex = p->rindex; @@ -1095,16 +1420,12 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ jump_barrier = ptr; p->rindex = ptr; - } else { - r = -EBADMSG; - goto fail; - } + } else + return -EBADMSG; } - if (!GREEDY_REALLOC(ret, allocated, n + 1)) { - r = -ENOMEM; - goto fail; - } + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; ret[n] = 0; @@ -1115,54 +1436,51 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, ret = NULL; if (start) - *start = saved_rindex; + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); return 0; - -fail: - dns_packet_rewind(p, saved_rindex); - return r; } static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) { uint8_t window; uint8_t length; const uint8_t *bitmap; + uint8_t bit = 0; unsigned i; bool found = false; - size_t saved_rindex; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; int r; assert(p); assert(types); - - saved_rindex = p->rindex; + INIT_REWINDER(rewinder, p); r = bitmap_ensure_allocated(types); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint8(p, &window, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint8(p, &length, NULL); if (r < 0) - goto fail; + return r; if (length == 0 || length > 32) return -EBADMSG; r = dns_packet_read(p, length, (const void **)&bitmap, NULL); if (r < 0) - goto fail; + return r; for (i = 0; i < length; i++) { uint8_t bitmask = 1 << 7; - uint8_t bit = 0; if (!bitmap[i]) { found = false; + bit += 8; continue; } @@ -1172,15 +1490,18 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta if (bitmap[i] & bitmask) { uint16_t n; - /* XXX: ignore pseudo-types? see RFC4034 section 4.1.2 */ n = (uint16_t) window << 8 | (uint16_t) bit; + /* Ignore pseudo-types. see RFC4034 section 4.1.2 */ + if (dns_type_is_pseudo(n)) + continue; + r = bitmap_set(*types, n); if (r < 0) - goto fail; + return r; } - bit ++; + bit++; bitmask >>= 1; } } @@ -1189,73 +1510,84 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta return -EBADMSG; if (start) - *start = saved_rindex; + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + int r; + + INIT_REWINDER(rewinder, p); + + while (p->rindex < rewinder.saved_rindex + size) { + r = dns_packet_read_type_window(p, types, NULL); + if (r < 0) + return r; + + /* don't read past end of current RR */ + if (p->rindex > rewinder.saved_rindex + size) + return -EBADMSG; + } + + if (p->rindex != rewinder.saved_rindex + size) + return -EBADMSG; + + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; } -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; _cleanup_free_ char *name = NULL; + bool cache_flush = false; uint16_t class, type; DnsResourceKey *key; - size_t saved_rindex; int r; assert(p); assert(ret); - - saved_rindex = p->rindex; + INIT_REWINDER(rewinder, p); r = dns_packet_read_name(p, &name, true, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint16(p, &type, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint16(p, &class, NULL); if (r < 0) - goto fail; + return r; - key = dns_resource_key_new_consume(class, type, name); - if (!key) { - r = -ENOMEM; - goto fail; + if (p->protocol == DNS_PROTOCOL_MDNS) { + /* See RFC6762, Section 10.2 */ + + if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { + class &= ~MDNS_RR_CACHE_FLUSH; + cache_flush = true; + } } + key = dns_resource_key_new_consume(class, type, name); + if (!key) + return -ENOMEM; + name = NULL; *ret = key; + if (ret_cache_flush) + *ret_cache_flush = cache_flush; if (start) - *start = saved_rindex; - - return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -static int dns_packet_read_public_key(DnsPacket *p, size_t length, - void **dp, size_t *lengthp, - size_t *start) { - int r; - const void *d; - void *d2; - - r = dns_packet_read(p, length, &d, NULL); - if (r < 0) - return r; - - d2 = memdup(d, length); - if (!d2) - return -ENOMEM; + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); - *dp = d2; - *lengthp = length; return 0; } @@ -1265,58 +1597,46 @@ static bool loc_size_ok(uint8_t size) { return m <= 9 && e <= 9 && (m > 0 || e == 0); } -static int dnskey_parse_flags(DnsResourceRecord *rr, uint16_t flags) { - assert(rr); - - if (flags & ~(DNSKEY_FLAG_SEP | DNSKEY_FLAG_ZONE_KEY)) - return -EBADMSG; - - rr->dnskey.zone_key_flag = flags & DNSKEY_FLAG_ZONE_KEY; - rr->dnskey.sep_flag = flags & DNSKEY_FLAG_SEP; - return 0; -} - -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - size_t saved_rindex, offset; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + size_t offset; uint16_t rdlength; - const void *d; + bool cache_flush; int r; assert(p); assert(ret); - saved_rindex = p->rindex; + INIT_REWINDER(rewinder, p); - r = dns_packet_read_key(p, &key, NULL); + r = dns_packet_read_key(p, &key, &cache_flush, NULL); if (r < 0) - goto fail; + return r; - if (key->class == DNS_CLASS_ANY || - key->type == DNS_TYPE_ANY) { - r = -EBADMSG; - goto fail; - } + if (!dns_class_is_valid_rr(key->class) || !dns_type_is_valid_rr(key->type)) + return -EBADMSG; rr = dns_resource_record_new(key); - if (!rr) { - r = -ENOMEM; - goto fail; - } + if (!rr) + return -ENOMEM; r = dns_packet_read_uint32(p, &rr->ttl, NULL); if (r < 0) - goto fail; + return r; + + /* RFC 2181, Section 8, suggests to + * treat a TTL with the MSB set as a zero TTL. */ + if (rr->ttl & UINT32_C(0x80000000)) + rr->ttl = 0; r = dns_packet_read_uint16(p, &rdlength, NULL); if (r < 0) - goto fail; + return r; - if (p->rindex + rdlength > p->size) { - r = -EBADMSG; - goto fail; - } + if (p->rindex + rdlength > p->size) + return -EBADMSG; offset = p->rindex; @@ -1325,13 +1645,13 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { case DNS_TYPE_SRV: r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint16(p, &rr->srv.port, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_name(p, &rr->srv.name, true, NULL); break; @@ -1345,7 +1665,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { case DNS_TYPE_HINFO: r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_string(p, &rr->hinfo.os, NULL); break; @@ -1353,24 +1673,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; } } @@ -1388,27 +1721,27 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { case DNS_TYPE_SOA: r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL); break; @@ -1416,7 +1749,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { case DNS_TYPE_MX: r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); break; @@ -1427,49 +1760,43 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { r = dns_packet_read_uint8(p, &t, &pos); if (r < 0) - goto fail; + return r; if (t == 0) { rr->loc.version = t; r = dns_packet_read_uint8(p, &rr->loc.size, NULL); if (r < 0) - goto fail; + return r; - if (!loc_size_ok(rr->loc.size)) { - r = -EBADMSG; - goto fail; - } + if (!loc_size_ok(rr->loc.size)) + return -EBADMSG; r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); if (r < 0) - goto fail; + return r; - if (!loc_size_ok(rr->loc.horiz_pre)) { - r = -EBADMSG; - goto fail; - } + if (!loc_size_ok(rr->loc.horiz_pre)) + return -EBADMSG; r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); if (r < 0) - goto fail; + return r; - if (!loc_size_ok(rr->loc.vert_pre)) { - r = -EBADMSG; - goto fail; - } + if (!loc_size_ok(rr->loc.vert_pre)) + return -EBADMSG; r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); if (r < 0) - goto fail; + return r; break; } else { @@ -1482,260 +1809,429 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { case DNS_TYPE_DS: r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); if (r < 0) - goto fail; + return r; - r = dns_packet_read_public_key(p, rdlength - 4, - &rr->ds.digest, &rr->ds.digest_size, - NULL); + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->ds.digest, &rr->ds.digest_size, + NULL); if (r < 0) - goto fail; + return r; + + if (rr->ds.digest_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; break; + case DNS_TYPE_SSHFP: r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); if (r < 0) - goto fail; + return r; - r = dns_packet_read_public_key(p, rdlength - 2, - &rr->sshfp.key, &rr->sshfp.key_size, - NULL); - break; + r = dns_packet_read_memdup(p, rdlength - 2, + &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size, + NULL); - case DNS_TYPE_DNSKEY: { - uint16_t flags; - uint8_t proto; + if (rr->sshfp.fingerprint_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; - r = dns_packet_read_uint16(p, &flags, NULL); - if (r < 0) - goto fail; + break; - r = dnskey_parse_flags(rr, flags); + case DNS_TYPE_DNSKEY: + r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); if (r < 0) - goto fail; + return r; - r = dns_packet_read_uint8(p, &proto, NULL); + r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); if (r < 0) - goto fail; - - /* protocol is required to be always 3 */ - if (proto != 3) { - r = -EBADMSG; - goto fail; - } + return r; r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); if (r < 0) - goto fail; + return r; + + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->dnskey.key, &rr->dnskey.key_size, + NULL); + + if (rr->dnskey.key_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; - r = dns_packet_read_public_key(p, rdlength - 4, - &rr->dnskey.key, &rr->dnskey.key_size, - NULL); break; - } case DNS_TYPE_RRSIG: r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); if (r < 0) - goto fail; + return r; + + r = dns_packet_read_memdup(p, offset + rdlength - p->rindex, + &rr->rrsig.signature, &rr->rrsig.signature_size, + NULL); + + if (rr->rrsig.signature_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; - r = dns_packet_read_public_key(p, offset + rdlength - p->rindex, - &rr->rrsig.signature, &rr->rrsig.signature_size, - NULL); break; - case DNS_TYPE_NSEC: - r = dns_packet_read_name(p, &rr->nsec.next_domain_name, false, NULL); + case DNS_TYPE_NSEC: { + + /* + * RFC6762, section 18.14 explictly states mDNS should use name compression. + * This contradicts RFC3845, section 2.1.1 + */ + + bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; + + r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); if (r < 0) - goto fail; + return r; - while (p->rindex != offset + rdlength) { - r = dns_packet_read_type_window(p, &rr->nsec.types, NULL); - if (r < 0) - goto fail; - } + r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); - break; + /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself + * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records + * without the NSEC bit set. */ + break; + } case DNS_TYPE_NSEC3: { uint8_t size; r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); if (r < 0) - goto fail; + return r; r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); if (r < 0) - goto fail; + return r; + /* this may be zero */ r = dns_packet_read_uint8(p, &size, NULL); if (r < 0) - goto fail; + return r; - rr->nsec3.salt_size = size; + r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); + if (r < 0) + return r; - r = dns_packet_read_blob(p, &d, rr->nsec3.salt_size, NULL); + r = dns_packet_read_uint8(p, &size, NULL); if (r < 0) - goto fail; + return r; - rr->nsec3.salt = memdup(d, rr->nsec3.salt_size); - if (!rr->nsec3.salt) { - r = -ENOMEM; - goto fail; - } + if (size <= 0) + return -EBADMSG; - r = dns_packet_read_uint8(p, &size, NULL); + r = dns_packet_read_memdup(p, size, + &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, + NULL); if (r < 0) - goto fail; + return r; - rr->nsec3.next_hashed_name_size = size; + r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); + + /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */ + + break; + } - r = dns_packet_read(p, rr->nsec3.next_hashed_name_size, &d, NULL); + case DNS_TYPE_TLSA: + r = dns_packet_read_uint8(p, &rr->tlsa.cert_usage, NULL); if (r < 0) - goto fail; + return r; - rr->nsec3.next_hashed_name = memdup(d, rr->nsec3.next_hashed_name_size); - if (!rr->nsec3.next_hashed_name) { - r = -ENOMEM; - goto fail; - } + r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL); + if (r < 0) + return r; - r = dns_packet_append_types(p, rr->nsec3.types, NULL); + r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL); if (r < 0) - goto fail; + return r; + + r = dns_packet_read_memdup(p, rdlength - 3, + &rr->tlsa.data, &rr->tlsa.data_size, + NULL); + + if (rr->tlsa.data_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; break; - } - default: - unparseable: - r = dns_packet_read(p, rdlength, &d, NULL); + + case DNS_TYPE_CAA: + r = dns_packet_read_uint8(p, &rr->caa.flags, NULL); if (r < 0) - goto fail; + return r; - rr->generic.data = memdup(d, rdlength); - if (!rr->generic.data) { - r = -ENOMEM; - goto fail; - } + r = dns_packet_read_string(p, &rr->caa.tag, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, + rdlength + offset - p->rindex, + &rr->caa.value, &rr->caa.value_size, NULL); + + break; + + case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ + case DNS_TYPE_OPENPGPKEY: + default: + unparseable: + r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.data_size, NULL); - rr->generic.size = rdlength; break; } if (r < 0) - goto fail; - if (p->rindex != offset + rdlength) { - r = -EBADMSG; - goto fail; - } + return r; + if (p->rindex != offset + rdlength) + return -EBADMSG; *ret = rr; rr = NULL; + if (ret_cache_flush) + *ret_cache_flush = cache_flush; if (start) - *start = saved_rindex; + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; +} + +static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { + const uint8_t* p; + bool found_dau_dhu_n3u = false; + size_t l; + + /* Checks whether the specified OPT RR is well-formed and whether it contains RFC6975 data (which is not OK in + * a reply). */ + + assert(rr); + assert(rr->key->type == DNS_TYPE_OPT); + + /* Check that the version is 0 */ + if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) { + *rfc6975 = false; + return true; /* if it's not version 0, it's OK, but we will ignore the OPT field contents */ + } + + p = rr->opt.data; + l = rr->opt.data_size; + while (l > 0) { + uint16_t option_code, option_length; + + /* At least four bytes for OPTION-CODE and OPTION-LENGTH are required */ + if (l < 4U) + return false; + + option_code = unaligned_read_be16(p); + option_length = unaligned_read_be16(p + 2); + + if (l < option_length + 4U) + return false; + + /* RFC 6975 DAU, DHU or N3U fields found. */ + if (IN_SET(option_code, 5, 6, 7)) + found_dau_dhu_n3u = true; + + p += option_length + 4U; + l -= option_length + 4U; + } + + *rfc6975 = found_dau_dhu_n3u; + return true; } int dns_packet_extract(DnsPacket *p) { _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - size_t saved_rindex; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {}; unsigned n, i; int r; if (p->extracted) return 0; - saved_rindex = p->rindex; + INIT_REWINDER(rewinder, p); dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); n = DNS_PACKET_QDCOUNT(p); if (n > 0) { question = dns_question_new(n); - if (!question) { - r = -ENOMEM; - goto finish; - } + if (!question) + return -ENOMEM; for (i = 0; i < n; i++) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + bool cache_flush; - r = dns_packet_read_key(p, &key, NULL); + r = dns_packet_read_key(p, &key, &cache_flush, NULL); if (r < 0) - goto finish; + return r; + + if (cache_flush) + return -EBADMSG; + + if (!dns_type_is_valid_query(key->type)) + return -EBADMSG; r = dns_question_add(question, key); if (r < 0) - goto finish; + return r; } } n = DNS_PACKET_RRCOUNT(p); if (n > 0) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; + bool bad_opt = false; + answer = dns_answer_new(n); - if (!answer) { - r = -ENOMEM; - goto finish; - } + if (!answer) + return -ENOMEM; for (i = 0; i < n; i++) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + bool cache_flush = false; - r = dns_packet_read_rr(p, &rr, NULL); + r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); if (r < 0) - goto finish; + return r; + + /* Try to reduce memory usage a bit */ + if (previous) + dns_resource_key_reduce(&rr->key, &previous->key); + + if (rr->key->type == DNS_TYPE_OPT) { + bool has_rfc6975; + + if (p->opt || bad_opt) { + /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong + * with the server, and if one is valid we wouldn't know which one. */ + log_debug("Multiple OPT RRs detected, ignoring all."); + bad_opt = true; + continue; + } + + if (!dns_name_is_root(dns_resource_key_name(rr->key))) { + /* If the OPT RR is not owned by the root domain, then it is bad, let's ignore + * it. */ + log_debug("OPT RR is not owned by root domain, ignoring."); + bad_opt = true; + continue; + } + + if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { + /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint + * the EDNS implementation is borked, like the Belkin one is, hence ignore + * it. */ + log_debug("OPT RR in wrong section, ignoring."); + bad_opt = true; + continue; + } + + if (!opt_is_good(rr, &has_rfc6975)) { + log_debug("Malformed OPT RR, ignoring."); + bad_opt = true; + continue; + } + + if (DNS_PACKET_QR(p)) { + /* Additional checks for responses */ + + if (!DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(rr)) { + /* If this is a reply and we don't know the EDNS version then something + * is weird... */ + log_debug("EDNS version newer that our request, bad server."); + return -EBADMSG; + } + + if (has_rfc6975) { + /* If the OPT RR contains RFC6975 algorithm data, then this is indication that + * the server just copied the OPT it got from us (which contained that data) + * back into the reply. If so, then it doesn't properly support EDNS, as + * RFC6975 makes it very clear that the algorithm data should only be contained + * in questions, never in replies. Crappy Belkin routers copy the OPT data for + * example, hence let's detect this so that we downgrade early. */ + log_debug("OPT RR contained RFC6975 data, ignoring."); + bad_opt = true; + continue; + } + } + + p->opt = dns_resource_record_ref(rr); + } else { + + /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be + * cached. Hence mark only those RRs as cacheable by default, but not the ones from the + * Additional or Authority sections. */ + + r = dns_answer_add(answer, rr, p->ifindex, + (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) | + (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0)); + if (r < 0) + return r; + } - r = dns_answer_add(answer, rr); - if (r < 0) - goto finish; + /* Remember this RR, so that we potentically can merge it's ->key object with the next RR. Note + * that we only do this if we actually decided to keep the RR around. */ + dns_resource_record_unref(previous); + previous = dns_resource_record_ref(rr); } + + if (bad_opt) + p->opt = dns_resource_record_unref(p->opt); } p->question = question; @@ -1746,11 +2242,32 @@ int dns_packet_extract(DnsPacket *p) { p->extracted = true; - r = 0; + /* no CANCEL, always rewind */ + return 0; +} -finish: - p->rindex = saved_rindex; - return r; +int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { + int r; + + assert(p); + assert(key); + + /* Checks if the specified packet is a reply for the specified + * key and the specified key is the only one in the question + * section. */ + + if (DNS_PACKET_QR(p) != 1) + return 0; + + /* Let's unpack the packet, if that hasn't happened yet. */ + r = dns_packet_extract(p); + if (r < 0) + return r; + + if (p->question->n_keys != 1) + return 0; + + return dns_resource_key_equal(p->question->keys[0], key); } static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { @@ -1772,6 +2289,7 @@ static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { [DNS_RCODE_BADNAME] = "BADNAME", [DNS_RCODE_BADALG] = "BADALG", [DNS_RCODE_BADTRUNC] = "BADTRUNC", + [DNS_RCODE_BADCOOKIE] = "BADCOOKIE", }; DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); @@ -1781,17 +2299,3 @@ static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { [DNS_PROTOCOL_LLMNR] = "llmnr", }; DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); - -static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { - [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", - [DNSSEC_ALGORITHM_DH] = "DH", - [DNSSEC_ALGORITHM_DSA] = "DSA", - [DNSSEC_ALGORITHM_ECC] = "ECC", - [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", - [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", - [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", - [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", - [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", - [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", -}; -DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int); |