]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-packet.c
resolved: refuse OPT RRs in incoming packets that are not in the additional section
[thirdparty/systemd.git] / src / resolve / resolved-dns-packet.c
index 40b662246f89a444de2a86a96b409ff7bb27bb96..7c5be538b8f8e089e6f0d8a64dcdebc2128f49d8 100644 (file)
@@ -28,6 +28,8 @@
 #include "utf8.h"
 #include "util.h"
 
+#define EDNS0_OPT_DO (1<<15)
+
 int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
         DnsPacket *p;
         size_t a;
@@ -63,30 +65,44 @@ 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 */,
-                                                         0 /* tc */,
+                                                         0/* tc */,
                                                          0 /* t */,
                                                          0 /* ra */,
                                                          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 */,
@@ -94,8 +110,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;
@@ -106,6 +139,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;
@@ -118,13 +153,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) {
@@ -133,6 +171,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
@@ -169,6 +209,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)
@@ -176,6 +217,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;
         }
@@ -202,6 +250,7 @@ int dns_packet_validate_query(DnsPacket *p) {
                 return -EBADMSG;
 
         switch (p->protocol) {
+
         case DNS_PROTOCOL_LLMNR:
                 /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */
                 if (DNS_PACKET_QDCOUNT(p) != 1)
@@ -217,6 +266,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;
         }
@@ -267,7 +328,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;
@@ -349,25 +410,10 @@ 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) {
-        void *d;
-        size_t l;
-        int r;
-
         assert(p);
         assert(s);
 
-        l = strlen(s);
-        if (l > 255)
-                return -E2BIG;
-
-        r = dns_packet_extend(p, 1 + l, &d, start);
-        if (r < 0)
-                return r;
-
-        ((uint8_t*) d)[0] = (uint8_t) l;
-        memcpy(((uint8_t*) d) + 1, s, l);
-
-        return 0;
+        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) {
@@ -393,7 +439,7 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_
 }
 
 int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) {
-        void *w;
+        uint8_t *w;
         int r;
 
         assert(p);
@@ -402,12 +448,29 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start
         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) {
+                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++) {
+                        if (d[i] >= 'A' && d[i] <= 'Z')
+                                w[i] = (uint8_t) (d[i] - 'A' + 'a');
+                        else
+                                w[i] = (uint8_t) 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;
 }
@@ -609,8 +672,59 @@ fail:
         return r;
 }
 
-int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) {
-        size_t saved_size, rdlength_offset, end, rdlength;
+/* Append the OPT pseudo-RR described in RFC6891 */
+int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, 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);
+
+        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;
+
+        /* 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, 0, 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 */
+        r = dns_packet_append_uint16(p, 0, NULL);
+        if (r < 0)
+                goto fail;
+
+        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 *rdata_start) {
+        size_t saved_size, rdlength_offset, end, rdlength, rds;
         int r;
 
         assert(p);
@@ -631,6 +745,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:
@@ -788,11 +904,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
                 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;
 
@@ -849,6 +965,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)
@@ -883,6 +1000,8 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
                         goto fail;
 
                 break;
+
+        case DNS_TYPE_OPT:
         case _DNS_TYPE_INVALID: /* unparseable */
         default:
 
@@ -909,6 +1028,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:
@@ -1328,6 +1450,7 @@ fail:
 
 int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) {
         _cleanup_free_ char *name = NULL;
+        bool cache_flush = true;
         uint16_t class, type;
         DnsResourceKey *key;
         size_t saved_rindex;
@@ -1350,12 +1473,23 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) {
         if (r < 0)
                 goto fail;
 
+        if (p->protocol == DNS_PROTOCOL_MDNS) {
+                /* See RFC6762, Section 10.2 */
+
+                if (class & MDNS_RR_CACHE_FLUSH)
+                        class &= ~MDNS_RR_CACHE_FLUSH;
+                else
+                        cache_flush = false;
+        }
+
         key = dns_resource_key_new_consume(class, type, name);
         if (!key) {
                 r = -ENOMEM;
                 goto fail;
         }
 
+        key->cache_flush = cache_flush;
+
         name = NULL;
         *ret = key;
 
@@ -1374,17 +1508,6 @@ 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) {
         _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
@@ -1402,7 +1525,9 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                 goto fail;
 
         if (key->class == DNS_CLASS_ANY ||
-            key->type == DNS_TYPE_ANY) {
+            key->type == DNS_TYPE_ANY ||
+            key->type == DNS_TYPE_AXFR ||
+            key->type == DNS_TYPE_IXFR) {
                 r = -EBADMSG;
                 goto fail;
         }
@@ -1627,6 +1752,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                 }
 
                 break;
+
         case DNS_TYPE_SSHFP:
                 r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL);
                 if (r < 0)
@@ -1649,28 +1775,15 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
 
                 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);
+        case DNS_TYPE_DNSKEY:
+                r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL);
                 if (r < 0)
                         goto fail;
 
-                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;
-                }
-
                 r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL);
                 if (r < 0)
                         goto fail;
@@ -1687,7 +1800,6 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                 }
 
                 break;
-        }
 
         case DNS_TYPE_RRSIG:
                 r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL);
@@ -1735,8 +1847,16 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
 
                 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 explicly 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;
 
@@ -1749,7 +1869,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                  * without the NSEC bit set. */
 
                 break;
-
+        }
         case DNS_TYPE_NSEC3: {
                 uint8_t size;
 
@@ -1795,6 +1915,8 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
 
                 break;
         }
+
+        case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */
         default:
         unparseable:
                 r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.size, NULL);
@@ -1870,9 +1992,26 @@ int dns_packet_extract(DnsPacket *p) {
                         if (r < 0)
                                 goto finish;
 
-                        r = dns_answer_add(answer, rr, p->ifindex);
-                        if (r < 0)
-                                goto finish;
+                        if (rr->key->type == DNS_TYPE_OPT) {
+
+                                /* The OPT RR is only valid in the Additional section */
+                                if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) {
+                                        r = -EBADMSG;
+                                        goto finish;
+                                }
+
+                                /* Two OPT RRs? */
+                                if (p->opt) {
+                                        r = -EBADMSG;
+                                        goto finish;
+                                }
+
+                                p->opt = dns_resource_record_ref(rr);
+                        } else {
+                                r = dns_answer_add(answer, rr, p->ifindex);
+                                if (r < 0)
+                                        goto finish;
+                        }
                 }
         }
 
@@ -1891,6 +2030,30 @@ finish:
         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] = {
         [DNS_RCODE_SUCCESS] = "SUCCESS",
         [DNS_RCODE_FORMERR] = "FORMERR",
@@ -1919,17 +2082,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);