X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fresolve%2Fresolved-dns-packet.c;h=cf5b6189c571daa01de3465d970fa71382fdd9f7;hp=5eaee2cb4f751e2be52a1475deb869366f3d6ad1;hb=1ccda9b7168e89141b60290295170e07e760efeb;hpb=ea917db9e662ae6e6d0ae07e0118b323688c8616 diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 5eaee2cb4..cf5b6189c 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -21,6 +21,8 @@ #include "utf8.h" #include "util.h" +#include "strv.h" +#include "unaligned.h" #include "resolved-dns-domain.h" #include "resolved-dns-packet.h" @@ -310,8 +312,7 @@ int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { if (r < 0) return r; - ((uint8_t*) d)[0] = (uint8_t) (v >> 8); - ((uint8_t*) d)[1] = (uint8_t) v; + unaligned_write_be16(d, v); return 0; } @@ -326,10 +327,7 @@ int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { if (r < 0) return r; - ((uint8_t*) d)[0] = (uint8_t) (v >> 24); - ((uint8_t*) d)[1] = (uint8_t) (v >> 16); - ((uint8_t*) d)[2] = (uint8_t) (v >> 8); - ((uint8_t*) d)[3] = (uint8_t) v; + unaligned_write_be32(d, v); return 0; } @@ -376,7 +374,8 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start return 0; } -int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) { +int dns_packet_append_name(DnsPacket *p, const char *name, + bool allow_compression, size_t *start) { size_t saved_size; int r; @@ -388,9 +387,11 @@ int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) { while (*name) { _cleanup_free_ char *s = NULL; char label[DNS_LABEL_MAX]; - size_t n; + size_t n = 0; + int k; - n = PTR_TO_SIZE(hashmap_get(p->names, name)); + if (allow_compression) + n = PTR_TO_SIZE(hashmap_get(p->names, name)); if (n > 0) { assert(n < p->size); @@ -413,19 +414,32 @@ int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) { if (r < 0) goto fail; - r = dns_packet_append_label(p, label, r, &n); - if (r < 0) + 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 = hashmap_ensure_allocated(&p->names, dns_name_hash_func, dns_name_compare_func); + r = dns_packet_append_label(p, label, r, &n); if (r < 0) goto fail; - r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); - if (r < 0) - goto fail; + if (allow_compression) { + r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); + if (r < 0) + goto fail; + + r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); + if (r < 0) + goto fail; - s = NULL; + s = NULL; + } } r = dns_packet_append_uint8(p, 0, NULL); @@ -452,7 +466,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), NULL); + r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, NULL); if (r < 0) goto fail; @@ -496,12 +510,29 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - switch (rr->key->type) { + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + r = dns_packet_append_uint16(p, rr->srv.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.weight, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.port, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->srv.name, true, NULL); + break; case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: - r = dns_packet_append_name(p, rr->ptr.name, NULL); + case DNS_TYPE_DNAME: + r = dns_packet_append_name(p, rr->ptr.name, true, NULL); break; case DNS_TYPE_HINFO: @@ -512,6 +543,29 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star r = dns_packet_append_string(p, rr->hinfo.os, NULL); break; + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: { + char **s; + + if (strv_isempty(rr->txt.strings)) { + /* RFC 6763, section 6.1 suggests to generate + * single empty string for an empty array. */ + + r = dns_packet_append_string(p, "", NULL); + if (r < 0) + goto fail; + } else { + STRV_FOREACH(s, rr->txt.strings) { + r = dns_packet_append_string(p, *s, NULL); + if (r < 0) + goto fail; + } + } + + r = 0; + break; + } + case DNS_TYPE_A: r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); break; @@ -521,11 +575,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, NULL); + r = dns_packet_append_name(p, rr->soa.mname, true, NULL); if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->soa.rname, NULL); + r = dns_packet_append_name(p, rr->soa.rname, true, NULL); if (r < 0) goto fail; @@ -549,11 +603,108 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_MX: - case DNS_TYPE_TXT: - case DNS_TYPE_SRV: - case DNS_TYPE_DNAME: + r = dns_packet_append_uint16(p, rr->mx.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->mx.exchange, true, NULL); + break; + + case DNS_TYPE_LOC: + r = dns_packet_append_uint8(p, rr->loc.version, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); + break; + case DNS_TYPE_SSHFP: + r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->sshfp.key, rr->sshfp.key_size, NULL); + break; + + case DNS_TYPE_DNSKEY: + r = dns_packet_append_uint16(p, dnskey_to_flags(rr), NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, 3u, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->dnskey.key, rr->dnskey.key_size, NULL); + break; + + case DNS_TYPE_RRSIG: + r = dns_packet_append_uint16(p, rr->rrsig.type_covered, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); + break; + + case _DNS_TYPE_INVALID: /* unparseable */ default: + r = dns_packet_append_blob(p, rr->generic.data, rr->generic.size, NULL); break; } @@ -574,6 +725,9 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star goto fail; p->size = end; + if (start) + *start = saved_size; + return 0; fail: @@ -645,8 +799,8 @@ int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { if (r < 0) return r; - *ret = (((uint16_t) ((uint8_t*) d)[0]) << 8) | - ((uint16_t) ((uint8_t*) d)[1]); + *ret = unaligned_read_be16(d); + return 0; } @@ -660,10 +814,7 @@ int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { if (r < 0) return r; - *ret = (((uint32_t) ((uint8_t*) d)[0]) << 24) | - (((uint32_t) ((uint8_t*) d)[1]) << 16) | - (((uint32_t) ((uint8_t*) d)[2]) << 8) | - ((uint32_t) ((uint8_t*) d)[3]); + *ret = unaligned_read_be32(d); return 0; } @@ -716,8 +867,9 @@ fail: return r; } -int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { - size_t saved_rindex, after_rindex = 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; _cleanup_free_ char *ret = NULL; size_t n = 0, allocated = 0; bool first = true; @@ -727,6 +879,7 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { assert(_ret); saved_rindex = p->rindex; + jump_barrier = p->rindex; for (;;) { uint8_t c, d; @@ -761,10 +914,10 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { else first = false; - memcpy(ret + n, t, c); + memcpy(ret + n, t, r); n += r; continue; - } else if ((c & 0xc0) == 0xc0) { + } else if (allow_compression && (c & 0xc0) == 0xc0) { uint16_t ptr; /* Pointer */ @@ -773,7 +926,7 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { goto fail; ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= saved_rindex) { + if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) { r = -EBADMSG; goto fail; } @@ -781,9 +934,13 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { if (after_rindex == 0) after_rindex = p->rindex; + /* Jumps are limited to a "prior occurence" (RFC-1035 4.1.4) */ + jump_barrier = ptr; p->rindex = ptr; - } else + } else { + r = -EBADMSG; goto fail; + } } if (!GREEDY_REALLOC(ret, allocated, n + 1)) { @@ -821,7 +978,7 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { saved_rindex = p->rindex; - r = dns_packet_read_name(p, &name, NULL); + r = dns_packet_read_name(p, &name, true, NULL); if (r < 0) goto fail; @@ -851,6 +1008,43 @@ fail: 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; + + *dp = d2; + *lengthp = length; + return 0; +} + +static bool loc_size_ok(uint8_t size) { + uint8_t m = size >> 4, e = size & 0xF; + + 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) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; @@ -868,6 +1062,12 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; + if (key->class == DNS_CLASS_ANY || + key->type == DNS_TYPE_ANY) { + r = -EBADMSG; + goto fail; + } + rr = dns_resource_record_new(key); if (!rr) { r = -ENOMEM; @@ -891,10 +1091,24 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { switch (rr->key->type) { + case DNS_TYPE_SRV: + r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.port, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_name(p, &rr->srv.name, true, NULL); + break; + case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: - r = dns_packet_read_name(p, &rr->ptr.name, NULL); + case DNS_TYPE_DNAME: + r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); break; case DNS_TYPE_HINFO: @@ -905,6 +1119,33 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { r = dns_packet_read_string(p, &rr->hinfo.os, NULL); break; + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + if (rdlength <= 0) { + /* 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; + } else { + while (p->rindex < offset + rdlength) { + char *s; + + r = dns_packet_read_string(p, &s, NULL); + if (r < 0) + goto fail; + + r = strv_consume(&rr->txt.strings, s); + if (r < 0) + goto fail; + } + } + + r = 0; + break; + case DNS_TYPE_A: r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); break; @@ -914,11 +1155,11 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { break; case DNS_TYPE_SOA: - r = dns_packet_read_name(p, &rr->soa.mname, NULL); + r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); if (r < 0) goto fail; - r = dns_packet_read_name(p, &rr->soa.rname, NULL); + r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); if (r < 0) goto fail; @@ -942,11 +1183,157 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { break; case DNS_TYPE_MX: - case DNS_TYPE_TXT: - case DNS_TYPE_SRV: - case DNS_TYPE_DNAME: + r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); + break; + + case DNS_TYPE_LOC: { + uint8_t t; + size_t pos; + + r = dns_packet_read_uint8(p, &t, &pos); + if (r < 0) + goto fail; + + if (t == 0) { + rr->loc.version = t; + + r = dns_packet_read_uint8(p, &rr->loc.size, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.size)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.horiz_pre)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.vert_pre)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); + if (r < 0) + goto fail; + + break; + } else { + dns_packet_rewind(p, pos); + rr->unparseable = true; + goto unparseable; + } + } + case DNS_TYPE_SSHFP: + r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_public_key(p, rdlength - 2, + &rr->sshfp.key, &rr->sshfp.key_size, + NULL); + break; + + case DNS_TYPE_DNSKEY: { + uint16_t flags; + uint8_t proto; + + r = dns_packet_read_uint16(p, &flags, NULL); + if (r < 0) + goto fail; + + r = dnskey_parse_flags(rr, flags); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &proto, NULL); + if (r < 0) + goto fail; + + /* protocol is required to be always 3 */ + if (proto != 3) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + 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; + + r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_public_key(p, offset + rdlength - p->rindex, + &rr->rrsig.signature, &rr->rrsig.signature_size, + NULL); + break; + default: + unparseable: r = dns_packet_read(p, rdlength, &d, NULL); if (r < 0) goto fail; @@ -986,6 +1373,9 @@ int dns_packet_extract(DnsPacket *p) { unsigned n, i; int r; + if (p->extracted) + return 0; + saved_rindex = p->rindex; dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); @@ -1037,6 +1427,8 @@ int dns_packet_extract(DnsPacket *p) { p->answer = answer; answer = NULL; + p->extracted = true; + r = 0; finish: @@ -1072,3 +1464,15 @@ 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_INDIRECT] = "INDIRECT", + [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", + [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int);