| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include <math.h> |
| |
| #include "alloc-util.h" |
| #include "dns-domain.h" |
| #include "dns-type.h" |
| #include "escape.h" |
| #include "hexdecoct.h" |
| #include "memory-util.h" |
| #include "resolved-dns-dnssec.h" |
| #include "resolved-dns-packet.h" |
| #include "resolved-dns-rr.h" |
| #include "string-table.h" |
| #include "string-util.h" |
| #include "strv.h" |
| #include "terminal-util.h" |
| |
| DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) { |
| DnsResourceKey *k; |
| size_t l; |
| |
| assert(name); |
| |
| l = strlen(name); |
| k = malloc0(sizeof(DnsResourceKey) + l + 1); |
| if (!k) |
| return NULL; |
| |
| k->n_ref = 1; |
| k->class = class; |
| k->type = type; |
| |
| strcpy((char*) k + sizeof(DnsResourceKey), name); |
| |
| return k; |
| } |
| |
| DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) { |
| int r; |
| |
| assert(key); |
| assert(cname); |
| |
| assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); |
| |
| if (cname->key->type == DNS_TYPE_CNAME) |
| return dns_resource_key_new(key->class, key->type, cname->cname.name); |
| else { |
| DnsResourceKey *k; |
| char *destination = NULL; |
| |
| r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination); |
| if (r < 0) |
| return NULL; |
| if (r == 0) |
| return dns_resource_key_ref((DnsResourceKey*) key); |
| |
| k = dns_resource_key_new_consume(key->class, key->type, destination); |
| if (!k) |
| return mfree(destination); |
| |
| return k; |
| } |
| } |
| |
| int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name) { |
| DnsResourceKey *new_key; |
| char *joined; |
| int r; |
| |
| assert(ret); |
| assert(key); |
| assert(name); |
| |
| if (dns_name_is_root(name)) { |
| *ret = dns_resource_key_ref(key); |
| return 0; |
| } |
| |
| r = dns_name_concat(dns_resource_key_name(key), name, 0, &joined); |
| if (r < 0) |
| return r; |
| |
| new_key = dns_resource_key_new_consume(key->class, key->type, joined); |
| if (!new_key) { |
| free(joined); |
| return -ENOMEM; |
| } |
| |
| *ret = new_key; |
| return 0; |
| } |
| |
| DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { |
| DnsResourceKey *k; |
| |
| assert(name); |
| |
| k = new(DnsResourceKey, 1); |
| if (!k) |
| return NULL; |
| |
| *k = (DnsResourceKey) { |
| .n_ref = 1, |
| .class = class, |
| .type = type, |
| ._name = name, |
| }; |
| |
| return k; |
| } |
| |
| DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) { |
| |
| if (!k) |
| return NULL; |
| |
| /* Static/const keys created with DNS_RESOURCE_KEY_CONST will |
| * set this to -1, they should not be reffed/unreffed */ |
| assert(k->n_ref != (unsigned) -1); |
| |
| assert(k->n_ref > 0); |
| k->n_ref++; |
| |
| return k; |
| } |
| |
| DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) { |
| if (!k) |
| return NULL; |
| |
| assert(k->n_ref != (unsigned) -1); |
| assert(k->n_ref > 0); |
| |
| if (k->n_ref == 1) { |
| free(k->_name); |
| free(k); |
| } else |
| k->n_ref--; |
| |
| return NULL; |
| } |
| |
| const char* dns_resource_key_name(const DnsResourceKey *key) { |
| const char *name; |
| |
| if (!key) |
| return NULL; |
| |
| if (key->_name) |
| name = key->_name; |
| else |
| name = (char*) key + sizeof(DnsResourceKey); |
| |
| if (dns_name_is_root(name)) |
| return "."; |
| else |
| return name; |
| } |
| |
| bool dns_resource_key_is_address(const DnsResourceKey *key) { |
| assert(key); |
| |
| /* Check if this is an A or AAAA resource key */ |
| |
| return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA); |
| } |
| |
| bool dns_resource_key_is_dnssd_ptr(const DnsResourceKey *key) { |
| assert(key); |
| |
| /* Check if this is a PTR resource key used in |
| Service Instance Enumeration as described in RFC6763 p4.1. */ |
| |
| if (key->type != DNS_TYPE_PTR) |
| return false; |
| |
| return dns_name_endswith(dns_resource_key_name(key), "_tcp.local") || |
| dns_name_endswith(dns_resource_key_name(key), "_udp.local"); |
| } |
| |
| int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { |
| int r; |
| |
| if (a == b) |
| return 1; |
| |
| r = dns_name_equal(dns_resource_key_name(a), dns_resource_key_name(b)); |
| if (r <= 0) |
| return r; |
| |
| if (a->class != b->class) |
| return 0; |
| |
| if (a->type != b->type) |
| return 0; |
| |
| return 1; |
| } |
| |
| int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { |
| int r; |
| |
| assert(key); |
| assert(rr); |
| |
| if (key == rr->key) |
| return 1; |
| |
| /* Checks if an rr matches the specified key. If a search |
| * domain is specified, it will also be checked if the key |
| * with the search domain suffixed might match the RR. */ |
| |
| if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) |
| return 0; |
| |
| if (rr->key->type != key->type && key->type != DNS_TYPE_ANY) |
| return 0; |
| |
| r = dns_name_equal(dns_resource_key_name(rr->key), dns_resource_key_name(key)); |
| if (r != 0) |
| return r; |
| |
| if (search_domain) { |
| _cleanup_free_ char *joined = NULL; |
| |
| r = dns_name_concat(dns_resource_key_name(key), search_domain, 0, &joined); |
| if (r < 0) |
| return r; |
| |
| return dns_name_equal(dns_resource_key_name(rr->key), joined); |
| } |
| |
| return 0; |
| } |
| |
| int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) { |
| int r; |
| |
| assert(key); |
| assert(cname); |
| |
| if (cname->class != key->class && key->class != DNS_CLASS_ANY) |
| return 0; |
| |
| if (cname->type == DNS_TYPE_CNAME) |
| r = dns_name_equal(dns_resource_key_name(key), dns_resource_key_name(cname)); |
| else if (cname->type == DNS_TYPE_DNAME) |
| r = dns_name_endswith(dns_resource_key_name(key), dns_resource_key_name(cname)); |
| else |
| return 0; |
| |
| if (r != 0) |
| return r; |
| |
| if (search_domain) { |
| _cleanup_free_ char *joined = NULL; |
| |
| r = dns_name_concat(dns_resource_key_name(key), search_domain, 0, &joined); |
| if (r < 0) |
| return r; |
| |
| if (cname->type == DNS_TYPE_CNAME) |
| return dns_name_equal(joined, dns_resource_key_name(cname)); |
| else if (cname->type == DNS_TYPE_DNAME) |
| return dns_name_endswith(joined, dns_resource_key_name(cname)); |
| } |
| |
| return 0; |
| } |
| |
| int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) { |
| assert(soa); |
| assert(key); |
| |
| /* Checks whether 'soa' is a SOA record for the specified key. */ |
| |
| if (soa->class != key->class) |
| return 0; |
| |
| if (soa->type != DNS_TYPE_SOA) |
| return 0; |
| |
| return dns_name_endswith(dns_resource_key_name(key), dns_resource_key_name(soa)); |
| } |
| |
| static void dns_resource_key_hash_func(const DnsResourceKey *k, struct siphash *state) { |
| assert(k); |
| |
| dns_name_hash_func(dns_resource_key_name(k), state); |
| siphash24_compress(&k->class, sizeof(k->class), state); |
| siphash24_compress(&k->type, sizeof(k->type), state); |
| } |
| |
| static int dns_resource_key_compare_func(const DnsResourceKey *x, const DnsResourceKey *y) { |
| int ret; |
| |
| ret = dns_name_compare_func(dns_resource_key_name(x), dns_resource_key_name(y)); |
| if (ret != 0) |
| return ret; |
| |
| ret = CMP(x->type, y->type); |
| if (ret != 0) |
| return ret; |
| |
| ret = CMP(x->class, y->class); |
| if (ret != 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| DEFINE_HASH_OPS(dns_resource_key_hash_ops, DnsResourceKey, dns_resource_key_hash_func, dns_resource_key_compare_func); |
| |
| char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size) { |
| const char *c, *t; |
| char *ans = buf; |
| |
| /* If we cannot convert the CLASS/TYPE into a known string, |
| use the format recommended by RFC 3597, Section 5. */ |
| |
| c = dns_class_to_string(key->class); |
| t = dns_type_to_string(key->type); |
| |
| snprintf(buf, buf_size, "%s %s%s%.0u %s%s%.0u", |
| dns_resource_key_name(key), |
| strempty(c), c ? "" : "CLASS", c ? 0 : key->class, |
| strempty(t), t ? "" : "TYPE", t ? 0 : key->type); |
| |
| return ans; |
| } |
| |
| bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b) { |
| assert(a); |
| assert(b); |
| |
| /* Try to replace one RR key by another if they are identical, thus saving a bit of memory. Note that we do |
| * this only for RR keys, not for RRs themselves, as they carry a lot of additional metadata (where they come |
| * from, validity data, and suchlike), and cannot be replaced so easily by other RRs that have the same |
| * superficial data. */ |
| |
| if (!*a) |
| return false; |
| if (!*b) |
| return false; |
| |
| /* We refuse merging const keys */ |
| if ((*a)->n_ref == (unsigned) -1) |
| return false; |
| if ((*b)->n_ref == (unsigned) -1) |
| return false; |
| |
| /* Already the same? */ |
| if (*a == *b) |
| return true; |
| |
| /* Are they really identical? */ |
| if (dns_resource_key_equal(*a, *b) <= 0) |
| return false; |
| |
| /* Keep the one which already has more references. */ |
| if ((*a)->n_ref > (*b)->n_ref) { |
| dns_resource_key_unref(*b); |
| *b = dns_resource_key_ref(*a); |
| } else { |
| dns_resource_key_unref(*a); |
| *a = dns_resource_key_ref(*b); |
| } |
| |
| return true; |
| } |
| |
| DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { |
| DnsResourceRecord *rr; |
| |
| rr = new(DnsResourceRecord, 1); |
| if (!rr) |
| return NULL; |
| |
| *rr = (DnsResourceRecord) { |
| .n_ref = 1, |
| .key = dns_resource_key_ref(key), |
| .expiry = USEC_INFINITY, |
| .n_skip_labels_signer = (unsigned) -1, |
| .n_skip_labels_source = (unsigned) -1, |
| }; |
| |
| return rr; |
| } |
| |
| DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name) { |
| _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; |
| |
| key = dns_resource_key_new(class, type, name); |
| if (!key) |
| return NULL; |
| |
| return dns_resource_record_new(key); |
| } |
| |
| static DnsResourceRecord* dns_resource_record_free(DnsResourceRecord *rr) { |
| assert(rr); |
| |
| if (rr->key) { |
| switch(rr->key->type) { |
| |
| case DNS_TYPE_SRV: |
| free(rr->srv.name); |
| break; |
| |
| case DNS_TYPE_PTR: |
| case DNS_TYPE_NS: |
| case DNS_TYPE_CNAME: |
| case DNS_TYPE_DNAME: |
| free(rr->ptr.name); |
| break; |
| |
| case DNS_TYPE_HINFO: |
| free(rr->hinfo.cpu); |
| free(rr->hinfo.os); |
| break; |
| |
| case DNS_TYPE_TXT: |
| case DNS_TYPE_SPF: |
| dns_txt_item_free_all(rr->txt.items); |
| break; |
| |
| case DNS_TYPE_SOA: |
| free(rr->soa.mname); |
| free(rr->soa.rname); |
| break; |
| |
| case DNS_TYPE_MX: |
| free(rr->mx.exchange); |
| break; |
| |
| case DNS_TYPE_DS: |
| free(rr->ds.digest); |
| break; |
| |
| case DNS_TYPE_SSHFP: |
| free(rr->sshfp.fingerprint); |
| break; |
| |
| case DNS_TYPE_DNSKEY: |
| free(rr->dnskey.key); |
| break; |
| |
| case DNS_TYPE_RRSIG: |
| free(rr->rrsig.signer); |
| free(rr->rrsig.signature); |
| break; |
| |
| case DNS_TYPE_NSEC: |
| free(rr->nsec.next_domain_name); |
| bitmap_free(rr->nsec.types); |
| break; |
| |
| case DNS_TYPE_NSEC3: |
| free(rr->nsec3.next_hashed_name); |
| free(rr->nsec3.salt); |
| bitmap_free(rr->nsec3.types); |
| break; |
| |
| case DNS_TYPE_LOC: |
| case DNS_TYPE_A: |
| case DNS_TYPE_AAAA: |
| break; |
| |
| case DNS_TYPE_TLSA: |
| free(rr->tlsa.data); |
| break; |
| |
| case DNS_TYPE_CAA: |
| free(rr->caa.tag); |
| free(rr->caa.value); |
| break; |
| |
| case DNS_TYPE_OPENPGPKEY: |
| default: |
| if (!rr->unparsable) |
| free(rr->generic.data); |
| } |
| |
| if (rr->unparsable) |
| free(rr->generic.data); |
| |
| free(rr->wire_format); |
| dns_resource_key_unref(rr->key); |
| } |
| |
| free(rr->to_string); |
| return mfree(rr); |
| } |
| |
| DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsResourceRecord, dns_resource_record, dns_resource_record_free); |
| |
| int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { |
| _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; |
| _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; |
| _cleanup_free_ char *ptr = NULL; |
| int r; |
| |
| assert(ret); |
| assert(address); |
| assert(hostname); |
| |
| r = dns_name_reverse(family, address, &ptr); |
| if (r < 0) |
| return r; |
| |
| key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr); |
| if (!key) |
| return -ENOMEM; |
| |
| ptr = NULL; |
| |
| rr = dns_resource_record_new(key); |
| if (!rr) |
| return -ENOMEM; |
| |
| rr->ptr.name = strdup(hostname); |
| if (!rr->ptr.name) |
| return -ENOMEM; |
| |
| *ret = TAKE_PTR(rr); |
| |
| return 0; |
| } |
| |
| int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name) { |
| DnsResourceRecord *rr; |
| |
| assert(ret); |
| assert(address); |
| assert(family); |
| |
| if (family == AF_INET) { |
| |
| rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, name); |
| if (!rr) |
| return -ENOMEM; |
| |
| rr->a.in_addr = address->in; |
| |
| } else if (family == AF_INET6) { |
| |
| rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, name); |
| if (!rr) |
| return -ENOMEM; |
| |
| rr->aaaa.in6_addr = address->in6; |
| } else |
| return -EAFNOSUPPORT; |
| |
| *ret = rr; |
| |
| return 0; |
| } |
| |
| #define FIELD_EQUAL(a, b, field) \ |
| ((a).field ## _size == (b).field ## _size && \ |
| memcmp_safe((a).field, (b).field, (a).field ## _size) == 0) |
| |
| int dns_resource_record_payload_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { |
| int r; |
| |
| /* Check if a and b are the same, but don't look at their keys */ |
| |
| if (a->unparsable != b->unparsable) |
| return 0; |
| |
| switch (a->unparsable ? _DNS_TYPE_INVALID : a->key->type) { |
| |
| case DNS_TYPE_SRV: |
| r = dns_name_equal(a->srv.name, b->srv.name); |
| if (r <= 0) |
| return r; |
| |
| return a->srv.priority == b->srv.priority && |
| a->srv.weight == b->srv.weight && |
| a->srv.port == b->srv.port; |
| |
| case DNS_TYPE_PTR: |
| case DNS_TYPE_NS: |
| case DNS_TYPE_CNAME: |
| case DNS_TYPE_DNAME: |
| return dns_name_equal(a->ptr.name, b->ptr.name); |
| |
| case DNS_TYPE_HINFO: |
| return strcaseeq(a->hinfo.cpu, b->hinfo.cpu) && |
| strcaseeq(a->hinfo.os, b->hinfo.os); |
| |
| case DNS_TYPE_SPF: /* exactly the same as TXT */ |
| case DNS_TYPE_TXT: |
| 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; |
| |
| case DNS_TYPE_AAAA: |
| return memcmp(&a->aaaa.in6_addr, &b->aaaa.in6_addr, sizeof(struct in6_addr)) == 0; |
| |
| case DNS_TYPE_SOA: |
| r = dns_name_equal(a->soa.mname, b->soa.mname); |
| if (r <= 0) |
| return r; |
| r = dns_name_equal(a->soa.rname, b->soa.rname); |
| if (r <= 0) |
| return r; |
| |
| return a->soa.serial == b->soa.serial && |
| a->soa.refresh == b->soa.refresh && |
| a->soa.retry == b->soa.retry && |
| a->soa.expire == b->soa.expire && |
| a->soa.minimum == b->soa.minimum; |
| |
| case DNS_TYPE_MX: |
| if (a->mx.priority != b->mx.priority) |
| return 0; |
| |
| return dns_name_equal(a->mx.exchange, b->mx.exchange); |
| |
| case DNS_TYPE_LOC: |
| assert(a->loc.version == b->loc.version); |
| |
| return a->loc.size == b->loc.size && |
| a->loc.horiz_pre == b->loc.horiz_pre && |
| a->loc.vert_pre == b->loc.vert_pre && |
| a->loc.latitude == b->loc.latitude && |
| a->loc.longitude == b->loc.longitude && |
| a->loc.altitude == b->loc.altitude; |
| |
| case DNS_TYPE_DS: |
| return a->ds.key_tag == b->ds.key_tag && |
| a->ds.algorithm == b->ds.algorithm && |
| a->ds.digest_type == b->ds.digest_type && |
| FIELD_EQUAL(a->ds, b->ds, digest); |
| |
| case DNS_TYPE_SSHFP: |
| return a->sshfp.algorithm == b->sshfp.algorithm && |
| a->sshfp.fptype == b->sshfp.fptype && |
| FIELD_EQUAL(a->sshfp, b->sshfp, fingerprint); |
| |
| case DNS_TYPE_DNSKEY: |
| return a->dnskey.flags == b->dnskey.flags && |
| a->dnskey.protocol == b->dnskey.protocol && |
| a->dnskey.algorithm == b->dnskey.algorithm && |
| FIELD_EQUAL(a->dnskey, b->dnskey, key); |
| |
| case DNS_TYPE_RRSIG: |
| /* do the fast comparisons first */ |
| return a->rrsig.type_covered == b->rrsig.type_covered && |
| a->rrsig.algorithm == b->rrsig.algorithm && |
| a->rrsig.labels == b->rrsig.labels && |
| a->rrsig.original_ttl == b->rrsig.original_ttl && |
| a->rrsig.expiration == b->rrsig.expiration && |
| a->rrsig.inception == b->rrsig.inception && |
| a->rrsig.key_tag == b->rrsig.key_tag && |
| FIELD_EQUAL(a->rrsig, b->rrsig, signature) && |
| dns_name_equal(a->rrsig.signer, b->rrsig.signer); |
| |
| case DNS_TYPE_NSEC: |
| return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) && |
| bitmap_equal(a->nsec.types, b->nsec.types); |
| |
| case DNS_TYPE_NSEC3: |
| return a->nsec3.algorithm == b->nsec3.algorithm && |
| a->nsec3.flags == b->nsec3.flags && |
| a->nsec3.iterations == b->nsec3.iterations && |
| FIELD_EQUAL(a->nsec3, b->nsec3, salt) && |
| FIELD_EQUAL(a->nsec3, b->nsec3, next_hashed_name) && |
| bitmap_equal(a->nsec3.types, b->nsec3.types); |
| |
| case DNS_TYPE_TLSA: |
| return a->tlsa.cert_usage == b->tlsa.cert_usage && |
| a->tlsa.selector == b->tlsa.selector && |
| a->tlsa.matching_type == b->tlsa.matching_type && |
| FIELD_EQUAL(a->tlsa, b->tlsa, data); |
| |
| case DNS_TYPE_CAA: |
| return a->caa.flags == b->caa.flags && |
| streq(a->caa.tag, b->caa.tag) && |
| FIELD_EQUAL(a->caa, b->caa, value); |
| |
| case DNS_TYPE_OPENPGPKEY: |
| default: |
| return FIELD_EQUAL(a->generic, b->generic, data); |
| } |
| } |
| |
| int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { |
| int r; |
| |
| assert(a); |
| assert(b); |
| |
| if (a == b) |
| return 1; |
| |
| r = dns_resource_key_equal(a->key, b->key); |
| if (r <= 0) |
| return r; |
| |
| return dns_resource_record_payload_equal(a, b); |
| } |
| |
| static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t altitude, |
| uint8_t size, uint8_t horiz_pre, uint8_t vert_pre) { |
| char *s; |
| char NS = latitude >= 1U<<31 ? 'N' : 'S'; |
| char EW = longitude >= 1U<<31 ? 'E' : 'W'; |
| |
| int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); |
| int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); |
| double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); |
| double siz = (size >> 4) * exp10((double) (size & 0xF)); |
| double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); |
| double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); |
| |
| if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", |
| (lat / 60000 / 60), |
| (lat / 60000) % 60, |
| (lat % 60000) / 1000., |
| NS, |
| (lon / 60000 / 60), |
| (lon / 60000) % 60, |
| (lon % 60000) / 1000., |
| EW, |
| alt / 100., |
| siz / 100., |
| hor / 100., |
| ver / 100.) < 0) |
| return NULL; |
| |
| return s; |
| } |
| |
| static int format_timestamp_dns(char *buf, size_t l, time_t sec) { |
| struct tm tm; |
| |
| assert(buf); |
| assert(l > STRLEN("YYYYMMDDHHmmSS")); |
| |
| if (!gmtime_r(&sec, &tm)) |
| return -EINVAL; |
| |
| if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static char *format_types(Bitmap *types) { |
| _cleanup_strv_free_ char **strv = NULL; |
| _cleanup_free_ char *str = NULL; |
| unsigned type; |
| int r; |
| |
| BITMAP_FOREACH(type, types) { |
| if (dns_type_to_string(type)) { |
| r = strv_extend(&strv, dns_type_to_string(type)); |
| if (r < 0) |
| return NULL; |
| } else { |
| char *t; |
| |
| r = asprintf(&t, "TYPE%u", type); |
| if (r < 0) |
| return NULL; |
| |
| r = strv_consume(&strv, t); |
| if (r < 0) |
| return NULL; |
| } |
| } |
| |
| str = strv_join(strv, " "); |
| if (!str) |
| return NULL; |
| |
| return strjoin("( ", str, " )"); |
| } |
| |
| 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; |
| } |
| |
| const char *dns_resource_record_to_string(DnsResourceRecord *rr) { |
| _cleanup_free_ char *t = NULL; |
| char *s, k[DNS_RESOURCE_KEY_STRING_MAX]; |
| int r; |
| |
| assert(rr); |
| |
| if (rr->to_string) |
| return rr->to_string; |
| |
| dns_resource_key_to_string(rr->key, k, sizeof(k)); |
| |
| switch (rr->unparsable ? _DNS_TYPE_INVALID : rr->key->type) { |
| |
| case DNS_TYPE_SRV: |
| r = asprintf(&s, "%s %u %u %u %s", |
| k, |
| rr->srv.priority, |
| rr->srv.weight, |
| rr->srv.port, |
| strna(rr->srv.name)); |
| if (r < 0) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_PTR: |
| case DNS_TYPE_NS: |
| case DNS_TYPE_CNAME: |
| case DNS_TYPE_DNAME: |
| s = strjoin(k, " ", rr->ptr.name); |
| if (!s) |
| return NULL; |
| |
| break; |
| |
| case DNS_TYPE_HINFO: |
| s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os); |
| if (!s) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_SPF: /* exactly the same as TXT */ |
| case DNS_TYPE_TXT: |
| t = format_txt(rr->txt.items); |
| if (!t) |
| return NULL; |
| |
| s = strjoin(k, " ", t); |
| if (!s) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_A: { |
| _cleanup_free_ char *x = NULL; |
| |
| r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); |
| if (r < 0) |
| return NULL; |
| |
| s = strjoin(k, " ", x); |
| if (!s) |
| return NULL; |
| break; |
| } |
| |
| case DNS_TYPE_AAAA: |
| r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t); |
| if (r < 0) |
| return NULL; |
| |
| s = strjoin(k, " ", t); |
| if (!s) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_SOA: |
| r = asprintf(&s, "%s %s %s %u %u %u %u %u", |
| k, |
| strna(rr->soa.mname), |
| strna(rr->soa.rname), |
| rr->soa.serial, |
| rr->soa.refresh, |
| rr->soa.retry, |
| rr->soa.expire, |
| rr->soa.minimum); |
| if (r < 0) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_MX: |
| r = asprintf(&s, "%s %u %s", |
| k, |
| rr->mx.priority, |
| rr->mx.exchange); |
| if (r < 0) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_LOC: |
| assert(rr->loc.version == 0); |
| |
| t = format_location(rr->loc.latitude, |
| rr->loc.longitude, |
| rr->loc.altitude, |
| rr->loc.size, |
| rr->loc.horiz_pre, |
| rr->loc.vert_pre); |
| if (!t) |
| return NULL; |
| |
| s = strjoin(k, " ", t); |
| if (!s) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_DS: |
| t = hexmem(rr->ds.digest, rr->ds.digest_size); |
| if (!t) |
| return NULL; |
| |
| r = asprintf(&s, "%s %u %u %u %s", |
| k, |
| rr->ds.key_tag, |
| rr->ds.algorithm, |
| rr->ds.digest_type, |
| t); |
| if (r < 0) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_SSHFP: |
| t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); |
| if (!t) |
| return NULL; |
| |
| r = asprintf(&s, "%s %u %u %s", |
| k, |
| rr->sshfp.algorithm, |
| rr->sshfp.fptype, |
| t); |
| if (r < 0) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_DNSKEY: { |
| _cleanup_free_ char *alg = NULL; |
| char *ss; |
| uint16_t key_tag; |
| |
| key_tag = dnssec_keytag(rr, true); |
| |
| r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); |
| if (r < 0) |
| return NULL; |
| |
| r = asprintf(&s, "%s %u %u %s", |
| k, |
| rr->dnskey.flags, |
| rr->dnskey.protocol, |
| alg); |
| if (r < 0) |
| return NULL; |
| |
| r = base64_append(&s, r, |
| rr->dnskey.key, rr->dnskey.key_size, |
| 8, columns()); |
| if (r < 0) |
| return NULL; |
| |
| r = asprintf(&ss, "%s\n" |
| " -- Flags:%s%s%s\n" |
| " -- Key tag: %u", |
| s, |
| rr->dnskey.flags & DNSKEY_FLAG_SEP ? " SEP" : "", |
| rr->dnskey.flags & DNSKEY_FLAG_REVOKE ? " REVOKE" : "", |
| rr->dnskey.flags & DNSKEY_FLAG_ZONE_KEY ? " ZONE_KEY" : "", |
| key_tag); |
| if (r < 0) |
| return NULL; |
| free(s); |
| s = ss; |
| |
| break; |
| } |
| |
| case DNS_TYPE_RRSIG: { |
| _cleanup_free_ char *alg = NULL; |
| char expiration[STRLEN("YYYYMMDDHHmmSS") + 1], inception[STRLEN("YYYYMMDDHHmmSS") + 1]; |
| const char *type; |
| |
| type = dns_type_to_string(rr->rrsig.type_covered); |
| |
| r = dnssec_algorithm_to_string_alloc(rr->rrsig.algorithm, &alg); |
| if (r < 0) |
| return NULL; |
| |
| r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration); |
| if (r < 0) |
| return NULL; |
| |
| r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); |
| if (r < 0) |
| return NULL; |
| |
| /* TYPE?? follows |
| * http://tools.ietf.org/html/rfc3597#section-5 */ |
| |
| r = asprintf(&s, "%s %s%.*u %s %u %u %s %s %u %s", |
| k, |
| type ?: "TYPE", |
| type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, |
| alg, |
| rr->rrsig.labels, |
| rr->rrsig.original_ttl, |
| expiration, |
| inception, |
| rr->rrsig.key_tag, |
| rr->rrsig.signer); |
| if (r < 0) |
| return NULL; |
| |
| r = base64_append(&s, r, |
| rr->rrsig.signature, rr->rrsig.signature_size, |
| 8, columns()); |
| if (r < 0) |
| return NULL; |
| |
| break; |
| } |
| |
| case DNS_TYPE_NSEC: |
| t = format_types(rr->nsec.types); |
| if (!t) |
| return NULL; |
| |
| r = asprintf(&s, "%s %s %s", |
| k, |
| rr->nsec.next_domain_name, |
| t); |
| if (r < 0) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_NSEC3: { |
| _cleanup_free_ char *salt = NULL, *hash = NULL; |
| |
| if (rr->nsec3.salt_size > 0) { |
| salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); |
| if (!salt) |
| return NULL; |
| } |
| |
| hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); |
| if (!hash) |
| return NULL; |
| |
| t = format_types(rr->nsec3.types); |
| if (!t) |
| return NULL; |
| |
| r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", |
| k, |
| rr->nsec3.algorithm, |
| rr->nsec3.flags, |
| rr->nsec3.iterations, |
| rr->nsec3.salt_size > 0 ? salt : "-", |
| hash, |
| t); |
| if (r < 0) |
| return NULL; |
| |
| break; |
| } |
| |
| case DNS_TYPE_TLSA: { |
| const char *cert_usage, *selector, *matching_type; |
| |
| cert_usage = tlsa_cert_usage_to_string(rr->tlsa.cert_usage); |
| selector = tlsa_selector_to_string(rr->tlsa.selector); |
| matching_type = tlsa_matching_type_to_string(rr->tlsa.matching_type); |
| |
| t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); |
| if (!t) |
| return NULL; |
| |
| r = asprintf(&s, |
| "%s %u %u %u %s\n" |
| " -- Cert. usage: %s\n" |
| " -- Selector: %s\n" |
| " -- Matching type: %s", |
| k, |
| rr->tlsa.cert_usage, |
| rr->tlsa.selector, |
| rr->tlsa.matching_type, |
| t, |
| cert_usage, |
| selector, |
| matching_type); |
| if (r < 0) |
| return NULL; |
| |
| break; |
| } |
| |
| case DNS_TYPE_CAA: { |
| _cleanup_free_ char *value; |
| |
| value = octescape(rr->caa.value, rr->caa.value_size); |
| if (!value) |
| return NULL; |
| |
| r = asprintf(&s, "%s %u %s \"%s\"%s%s%s%.0u", |
| k, |
| rr->caa.flags, |
| rr->caa.tag, |
| value, |
| rr->caa.flags ? "\n -- Flags:" : "", |
| rr->caa.flags & CAA_FLAG_CRITICAL ? " critical" : "", |
| rr->caa.flags & ~CAA_FLAG_CRITICAL ? " " : "", |
| rr->caa.flags & ~CAA_FLAG_CRITICAL); |
| if (r < 0) |
| return NULL; |
| |
| break; |
| } |
| |
| case DNS_TYPE_OPENPGPKEY: { |
| r = asprintf(&s, "%s", k); |
| if (r < 0) |
| return NULL; |
| |
| r = base64_append(&s, r, |
| rr->generic.data, rr->generic.data_size, |
| 8, columns()); |
| if (r < 0) |
| return NULL; |
| break; |
| } |
| |
| default: |
| t = hexmem(rr->generic.data, rr->generic.data_size); |
| if (!t) |
| return NULL; |
| |
| /* Format as documented in RFC 3597, Section 5 */ |
| r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.data_size, t); |
| if (r < 0) |
| return NULL; |
| break; |
| } |
| |
| rr->to_string = s; |
| return s; |
| } |
| |
| ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out) { |
| assert(rr); |
| assert(out); |
| |
| switch(rr->unparsable ? _DNS_TYPE_INVALID : rr->key->type) { |
| case DNS_TYPE_SRV: |
| case DNS_TYPE_PTR: |
| case DNS_TYPE_NS: |
| case DNS_TYPE_CNAME: |
| case DNS_TYPE_DNAME: |
| case DNS_TYPE_HINFO: |
| case DNS_TYPE_SPF: |
| case DNS_TYPE_TXT: |
| case DNS_TYPE_A: |
| case DNS_TYPE_AAAA: |
| case DNS_TYPE_SOA: |
| case DNS_TYPE_MX: |
| case DNS_TYPE_LOC: |
| case DNS_TYPE_DS: |
| case DNS_TYPE_DNSKEY: |
| case DNS_TYPE_RRSIG: |
| case DNS_TYPE_NSEC: |
| case DNS_TYPE_NSEC3: |
| return -EINVAL; |
| |
| case DNS_TYPE_SSHFP: |
| *out = rr->sshfp.fingerprint; |
| return rr->sshfp.fingerprint_size; |
| |
| case DNS_TYPE_TLSA: |
| *out = rr->tlsa.data; |
| return rr->tlsa.data_size; |
| |
| case DNS_TYPE_OPENPGPKEY: |
| default: |
| *out = rr->generic.data; |
| return rr->generic.data_size; |
| } |
| } |
| |
| int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { |
| |
| DnsPacket packet = { |
| .n_ref = 1, |
| .protocol = DNS_PROTOCOL_DNS, |
| .on_stack = true, |
| .refuse_compression = true, |
| .canonical_form = canonical, |
| }; |
| |
| size_t start, rds; |
| int r; |
| |
| assert(rr); |
| |
| /* Generates the RR in wire-format, optionally in the |
| * canonical form as discussed in the DNSSEC RFC 4034, Section |
| * 6.2. We allocate a throw-away DnsPacket object on the stack |
| * here, because we need some book-keeping for memory |
| * management, and can reuse the DnsPacket serializer, that |
| * can generate the canonical form, too, but also knows label |
| * compression and suchlike. */ |
| |
| if (rr->wire_format && rr->wire_format_canonical == canonical) |
| return 0; |
| |
| r = dns_packet_append_rr(&packet, rr, 0, &start, &rds); |
| if (r < 0) |
| return r; |
| |
| assert(start == 0); |
| assert(packet._data); |
| |
| free(rr->wire_format); |
| rr->wire_format = packet._data; |
| rr->wire_format_size = packet.size; |
| rr->wire_format_rdata_offset = rds; |
| rr->wire_format_canonical = canonical; |
| |
| packet._data = NULL; |
| dns_packet_unref(&packet); |
| |
| return 0; |
| } |
| |
| int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { |
| const char *n; |
| int r; |
| |
| assert(rr); |
| assert(ret); |
| |
| /* Returns the RRset's signer, if it is known. */ |
| |
| if (rr->n_skip_labels_signer == (unsigned) -1) |
| return -ENODATA; |
| |
| n = dns_resource_key_name(rr->key); |
| r = dns_name_skip(n, rr->n_skip_labels_signer, &n); |
| if (r < 0) |
| return r; |
| if (r == 0) |
| return -EINVAL; |
| |
| *ret = n; |
| return 0; |
| } |
| |
| int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { |
| const char *n; |
| int r; |
| |
| assert(rr); |
| assert(ret); |
| |
| /* Returns the RRset's synthesizing source, if it is known. */ |
| |
| if (rr->n_skip_labels_source == (unsigned) -1) |
| return -ENODATA; |
| |
| n = dns_resource_key_name(rr->key); |
| r = dns_name_skip(n, rr->n_skip_labels_source, &n); |
| if (r < 0) |
| return r; |
| if (r == 0) |
| return -EINVAL; |
| |
| *ret = n; |
| return 0; |
| } |
| |
| int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { |
| const char *signer; |
| int r; |
| |
| assert(rr); |
| |
| r = dns_resource_record_signer(rr, &signer); |
| if (r < 0) |
| return r; |
| |
| return dns_name_equal(zone, signer); |
| } |
| |
| int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { |
| int r; |
| |
| assert(rr); |
| |
| /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ |
| |
| if (rr->n_skip_labels_source == (unsigned) -1) |
| return -ENODATA; |
| |
| if (rr->n_skip_labels_source == 0) |
| return 0; |
| |
| if (rr->n_skip_labels_source > 1) |
| return 1; |
| |
| r = dns_name_startswith(dns_resource_key_name(rr->key), "*"); |
| if (r < 0) |
| return r; |
| |
| return !r; |
| } |
| |
| void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash *state) { |
| assert(rr); |
| |
| dns_resource_key_hash_func(rr->key, state); |
| |
| switch (rr->unparsable ? _DNS_TYPE_INVALID : rr->key->type) { |
| |
| case DNS_TYPE_SRV: |
| siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); |
| siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); |
| siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); |
| dns_name_hash_func(rr->srv.name, state); |
| break; |
| |
| case DNS_TYPE_PTR: |
| case DNS_TYPE_NS: |
| case DNS_TYPE_CNAME: |
| case DNS_TYPE_DNAME: |
| dns_name_hash_func(rr->ptr.name, state); |
| break; |
| |
| case DNS_TYPE_HINFO: |
| string_hash_func(rr->hinfo.cpu, state); |
| string_hash_func(rr->hinfo.os, state); |
| break; |
| |
| case DNS_TYPE_TXT: |
| case DNS_TYPE_SPF: { |
| DnsTxtItem *j; |
| |
| LIST_FOREACH(items, j, rr->txt.items) { |
| siphash24_compress(j->data, j->length, state); |
| |
| /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" |
| * followed by "". */ |
| siphash24_compress_byte(0, state); |
| } |
| break; |
| } |
| |
| case DNS_TYPE_A: |
| siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); |
| break; |
| |
| case DNS_TYPE_AAAA: |
| siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); |
| break; |
| |
| case DNS_TYPE_SOA: |
| dns_name_hash_func(rr->soa.mname, state); |
| dns_name_hash_func(rr->soa.rname, state); |
| siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); |
| siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); |
| siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); |
| siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); |
| siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); |
| break; |
| |
| case DNS_TYPE_MX: |
| siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); |
| dns_name_hash_func(rr->mx.exchange, state); |
| break; |
| |
| case DNS_TYPE_LOC: |
| siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); |
| siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); |
| siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); |
| siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); |
| siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); |
| siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); |
| siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); |
| break; |
| |
| case DNS_TYPE_SSHFP: |
| siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); |
| siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); |
| siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); |
| break; |
| |
| case DNS_TYPE_DNSKEY: |
| siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); |
| siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); |
| siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); |
| siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); |
| break; |
| |
| case DNS_TYPE_RRSIG: |
| siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); |
| siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); |
| siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); |
| siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); |
| siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); |
| siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); |
| siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); |
| dns_name_hash_func(rr->rrsig.signer, state); |
| siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); |
| break; |
| |
| case DNS_TYPE_NSEC: |
| dns_name_hash_func(rr->nsec.next_domain_name, state); |
| /* FIXME: we leave out the type bitmap here. Hash |
| * would be better if we'd take it into account |
| * too. */ |
| break; |
| |
| case DNS_TYPE_DS: |
| siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); |
| siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); |
| siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); |
| siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); |
| break; |
| |
| case DNS_TYPE_NSEC3: |
| siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); |
| siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); |
| siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); |
| siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); |
| siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); |
| /* FIXME: We leave the bitmaps out */ |
| break; |
| |
| case DNS_TYPE_TLSA: |
| siphash24_compress(&rr->tlsa.cert_usage, sizeof(rr->tlsa.cert_usage), state); |
| siphash24_compress(&rr->tlsa.selector, sizeof(rr->tlsa.selector), state); |
| siphash24_compress(&rr->tlsa.matching_type, sizeof(rr->tlsa.matching_type), state); |
| siphash24_compress(rr->tlsa.data, rr->tlsa.data_size, state); |
| break; |
| |
| case DNS_TYPE_CAA: |
| siphash24_compress(&rr->caa.flags, sizeof(rr->caa.flags), state); |
| string_hash_func(rr->caa.tag, state); |
| siphash24_compress(rr->caa.value, rr->caa.value_size, state); |
| break; |
| |
| case DNS_TYPE_OPENPGPKEY: |
| default: |
| siphash24_compress(rr->generic.data, rr->generic.data_size, state); |
| break; |
| } |
| } |
| |
| static int dns_resource_record_compare_func(const DnsResourceRecord *x, const DnsResourceRecord *y) { |
| int r; |
| |
| r = dns_resource_key_compare_func(x->key, y->key); |
| if (r != 0) |
| return r; |
| |
| if (dns_resource_record_equal(x, y)) |
| return 0; |
| |
| /* We still use CMP() here, even though don't implement proper |
| * ordering, since the hashtable doesn't need ordering anyway. */ |
| return CMP(x, y); |
| } |
| |
| DEFINE_HASH_OPS(dns_resource_record_hash_ops, DnsResourceRecord, dns_resource_record_hash_func, dns_resource_record_compare_func); |
| |
| DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) { |
| _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *copy = NULL; |
| DnsResourceRecord *t; |
| |
| assert(rr); |
| |
| copy = dns_resource_record_new(rr->key); |
| if (!copy) |
| return NULL; |
| |
| copy->ttl = rr->ttl; |
| copy->expiry = rr->expiry; |
| copy->n_skip_labels_signer = rr->n_skip_labels_signer; |
| copy->n_skip_labels_source = rr->n_skip_labels_source; |
| copy->unparsable = rr->unparsable; |
| |
| switch (rr->unparsable ? _DNS_TYPE_INVALID : rr->key->type) { |
| |
| case DNS_TYPE_SRV: |
| copy->srv.priority = rr->srv.priority; |
| copy->srv.weight = rr->srv.weight; |
| copy->srv.port = rr->srv.port; |
| copy->srv.name = strdup(rr->srv.name); |
| if (!copy->srv.name) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_PTR: |
| case DNS_TYPE_NS: |
| case DNS_TYPE_CNAME: |
| case DNS_TYPE_DNAME: |
| copy->ptr.name = strdup(rr->ptr.name); |
| if (!copy->ptr.name) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_HINFO: |
| copy->hinfo.cpu = strdup(rr->hinfo.cpu); |
| if (!copy->hinfo.cpu) |
| return NULL; |
| |
| copy->hinfo.os = strdup(rr->hinfo.os); |
| if (!copy->hinfo.os) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_TXT: |
| case DNS_TYPE_SPF: |
| copy->txt.items = dns_txt_item_copy(rr->txt.items); |
| if (!copy->txt.items) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_A: |
| copy->a = rr->a; |
| break; |
| |
| case DNS_TYPE_AAAA: |
| copy->aaaa = rr->aaaa; |
| break; |
| |
| case DNS_TYPE_SOA: |
| copy->soa.mname = strdup(rr->soa.mname); |
| if (!copy->soa.mname) |
| return NULL; |
| copy->soa.rname = strdup(rr->soa.rname); |
| if (!copy->soa.rname) |
| return NULL; |
| copy->soa.serial = rr->soa.serial; |
| copy->soa.refresh = rr->soa.refresh; |
| copy->soa.retry = rr->soa.retry; |
| copy->soa.expire = rr->soa.expire; |
| copy->soa.minimum = rr->soa.minimum; |
| break; |
| |
| case DNS_TYPE_MX: |
| copy->mx.priority = rr->mx.priority; |
| copy->mx.exchange = strdup(rr->mx.exchange); |
| if (!copy->mx.exchange) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_LOC: |
| copy->loc = rr->loc; |
| break; |
| |
| case DNS_TYPE_SSHFP: |
| copy->sshfp.algorithm = rr->sshfp.algorithm; |
| copy->sshfp.fptype = rr->sshfp.fptype; |
| copy->sshfp.fingerprint = memdup(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); |
| if (!copy->sshfp.fingerprint) |
| return NULL; |
| copy->sshfp.fingerprint_size = rr->sshfp.fingerprint_size; |
| break; |
| |
| case DNS_TYPE_DNSKEY: |
| copy->dnskey.flags = rr->dnskey.flags; |
| copy->dnskey.protocol = rr->dnskey.protocol; |
| copy->dnskey.algorithm = rr->dnskey.algorithm; |
| copy->dnskey.key = memdup(rr->dnskey.key, rr->dnskey.key_size); |
| if (!copy->dnskey.key) |
| return NULL; |
| copy->dnskey.key_size = rr->dnskey.key_size; |
| break; |
| |
| case DNS_TYPE_RRSIG: |
| copy->rrsig.type_covered = rr->rrsig.type_covered; |
| copy->rrsig.algorithm = rr->rrsig.algorithm; |
| copy->rrsig.labels = rr->rrsig.labels; |
| copy->rrsig.original_ttl = rr->rrsig.original_ttl; |
| copy->rrsig.expiration = rr->rrsig.expiration; |
| copy->rrsig.inception = rr->rrsig.inception; |
| copy->rrsig.key_tag = rr->rrsig.key_tag; |
| copy->rrsig.signer = strdup(rr->rrsig.signer); |
| if (!copy->rrsig.signer) |
| return NULL; |
| copy->rrsig.signature = memdup(rr->rrsig.signature, rr->rrsig.signature_size); |
| if (!copy->rrsig.signature) |
| return NULL; |
| copy->rrsig.signature_size = rr->rrsig.signature_size; |
| break; |
| |
| case DNS_TYPE_NSEC: |
| copy->nsec.next_domain_name = strdup(rr->nsec.next_domain_name); |
| if (!copy->nsec.next_domain_name) |
| return NULL; |
| copy->nsec.types = bitmap_copy(rr->nsec.types); |
| if (!copy->nsec.types) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_DS: |
| copy->ds.key_tag = rr->ds.key_tag; |
| copy->ds.algorithm = rr->ds.algorithm; |
| copy->ds.digest_type = rr->ds.digest_type; |
| copy->ds.digest = memdup(rr->ds.digest, rr->ds.digest_size); |
| if (!copy->ds.digest) |
| return NULL; |
| copy->ds.digest_size = rr->ds.digest_size; |
| break; |
| |
| case DNS_TYPE_NSEC3: |
| copy->nsec3.algorithm = rr->nsec3.algorithm; |
| copy->nsec3.flags = rr->nsec3.flags; |
| copy->nsec3.iterations = rr->nsec3.iterations; |
| copy->nsec3.salt = memdup(rr->nsec3.salt, rr->nsec3.salt_size); |
| if (!copy->nsec3.salt) |
| return NULL; |
| copy->nsec3.salt_size = rr->nsec3.salt_size; |
| copy->nsec3.next_hashed_name = memdup(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size); |
| if (!copy->nsec3.next_hashed_name) |
| return NULL; |
| copy->nsec3.next_hashed_name_size = rr->nsec3.next_hashed_name_size; |
| copy->nsec3.types = bitmap_copy(rr->nsec3.types); |
| if (!copy->nsec3.types) |
| return NULL; |
| break; |
| |
| case DNS_TYPE_TLSA: |
| copy->tlsa.cert_usage = rr->tlsa.cert_usage; |
| copy->tlsa.selector = rr->tlsa.selector; |
| copy->tlsa.matching_type = rr->tlsa.matching_type; |
| copy->tlsa.data = memdup(rr->tlsa.data, rr->tlsa.data_size); |
| if (!copy->tlsa.data) |
| return NULL; |
| copy->tlsa.data_size = rr->tlsa.data_size; |
| break; |
| |
| case DNS_TYPE_CAA: |
| copy->caa.flags = rr->caa.flags; |
| copy->caa.tag = strdup(rr->caa.tag); |
| if (!copy->caa.tag) |
| return NULL; |
| copy->caa.value = memdup(rr->caa.value, rr->caa.value_size); |
| if (!copy->caa.value) |
| return NULL; |
| copy->caa.value_size = rr->caa.value_size; |
| break; |
| |
| case DNS_TYPE_OPT: |
| default: |
| copy->generic.data = memdup(rr->generic.data, rr->generic.data_size); |
| if (!copy->generic.data) |
| return NULL; |
| copy->generic.data_size = rr->generic.data_size; |
| break; |
| } |
| |
| t = TAKE_PTR(copy); |
| |
| return t; |
| } |
| |
| int dns_resource_record_clamp_ttl(DnsResourceRecord **rr, uint32_t max_ttl) { |
| DnsResourceRecord *old_rr, *new_rr; |
| uint32_t new_ttl; |
| |
| assert(rr); |
| old_rr = *rr; |
| |
| if (old_rr->key->type == DNS_TYPE_OPT) |
| return -EINVAL; |
| |
| new_ttl = MIN(old_rr->ttl, max_ttl); |
| if (new_ttl == old_rr->ttl) |
| return 0; |
| |
| if (old_rr->n_ref == 1) { |
| /* Patch in place */ |
| old_rr->ttl = new_ttl; |
| return 1; |
| } |
| |
| new_rr = dns_resource_record_copy(old_rr); |
| if (!new_rr) |
| return -ENOMEM; |
| |
| new_rr->ttl = new_ttl; |
| |
| dns_resource_record_unref(*rr); |
| *rr = new_rr; |
| |
| return 1; |
| } |
| |
| 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 true; |
| |
| 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); |
| } |
| |
| DnsTxtItem *dns_txt_item_copy(DnsTxtItem *first) { |
| DnsTxtItem *i, *copy = NULL, *end = NULL; |
| |
| LIST_FOREACH(items, i, first) { |
| DnsTxtItem *j; |
| |
| j = memdup(i, offsetof(DnsTxtItem, data) + i->length + 1); |
| if (!j) { |
| dns_txt_item_free_all(copy); |
| return NULL; |
| } |
| |
| LIST_INSERT_AFTER(items, copy, end, j); |
| end = j; |
| } |
| |
| return copy; |
| } |
| |
| int dns_txt_item_new_empty(DnsTxtItem **ret) { |
| DnsTxtItem *i; |
| |
| /* RFC 6763, section 6.1 suggests to treat |
| * empty TXT RRs as equivalent to a TXT record |
| * with a single empty string. */ |
| |
| i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */ |
| if (!i) |
| return -ENOMEM; |
| |
| *ret = i; |
| |
| return 0; |
| } |
| |
| static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { |
| /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ |
| [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_RSASHA256] = "RSASHA256", |
| [DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512", |
| [DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST", |
| [DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256", |
| [DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384", |
| [DNSSEC_ALGORITHM_ED25519] = "ED25519", |
| [DNSSEC_ALGORITHM_ED448] = "ED448", |
| [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", |
| [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", |
| [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", |
| }; |
| DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_algorithm, int, 255); |
| |
| static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { |
| /* Names as listed on https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ |
| [DNSSEC_DIGEST_SHA1] = "SHA-1", |
| [DNSSEC_DIGEST_SHA256] = "SHA-256", |
| [DNSSEC_DIGEST_GOST_R_34_11_94] = "GOST_R_34.11-94", |
| [DNSSEC_DIGEST_SHA384] = "SHA-384", |
| }; |
| DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_digest, int, 255); |