| /* fromwire.c |
| * |
| * Copyright (c) 2018-2021 Apple Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * DNS wire-format functions. |
| * |
| * These are really simple functions for constructing DNS messages wire format. |
| * The flow is that there is a transaction structure which contains pointers to both |
| * a message output buffer and a response input buffer. The structure is initialized, |
| * and then the various wire format functions are called repeatedly to store data. |
| * If an error occurs during this process, it's okay to just keep going, because the |
| * error is recorded in the transaction; once all of the copy-in functions have been |
| * called, the error status can be checked once at the end. |
| */ |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include "srp.h" |
| #include "dns-msg.h" |
| |
| bool |
| dns_opt_parse(dns_edns0_t *NONNULL *NULLABLE ret, dns_rr_t *rr) |
| { |
| dns_edns0_t *edns0, **p_edns0 = ret; |
| unsigned offset = 0; |
| dns_rdata_unparsed_t opt; |
| |
| // This would be a weird coding error. |
| if (rr->type != dns_rrtype_opt) { |
| return false; |
| } |
| opt = rr->data.unparsed; |
| |
| // RDATA is a series of TLVs |
| while (offset < opt.len) { |
| uint16_t tlv_type, tlv_len; |
| |
| // Parse the TLV type and length. |
| if (!dns_u16_parse(opt.data, opt.len, &offset, &tlv_type) || |
| !dns_u16_parse(opt.data, opt.len, &offset, &tlv_len)) |
| { |
| return false; |
| } |
| |
| // Range check the contents. |
| if (offset + tlv_len > opt.len) { |
| return false; |
| } |
| |
| edns0 = calloc(1, tlv_len + sizeof(*edns0)); |
| if (edns0 == NULL) { |
| return false; |
| } |
| // Stash the record. |
| edns0->length = tlv_len; |
| edns0->type = tlv_type; |
| memcpy(edns0->data, &opt.data[offset], tlv_len); |
| *p_edns0 = edns0; |
| p_edns0 = &edns0->next; |
| offset += tlv_len; |
| } |
| return true; |
| } |
| |
| dns_label_t * NULLABLE |
| dns_label_parse_(const uint8_t *buf, unsigned mlen, unsigned *NONNULL offp, const char *file, int line) |
| { |
| uint8_t llen = buf[*offp]; |
| dns_label_t *rv; |
| |
| // Make sure that we got the data this label claims to encompass. |
| if (*offp + llen + 1 > mlen) { |
| DEBUG("claimed length of label is too long: %u > %u.\n", *offp + llen + 1, mlen); |
| return NULL; |
| } |
| |
| #ifdef MALLOC_DEBUG_LOGGING |
| rv = debug_calloc(1, (sizeof(*rv) - DNS_MAX_LABEL_SIZE) + llen + 1, file, line); |
| #else |
| (void)file; (void)line; |
| rv = calloc(1, (sizeof(*rv) - DNS_MAX_LABEL_SIZE) + llen + 1); |
| #endif |
| if (rv == NULL) { |
| DEBUG("memory allocation for %u byte label (%.*s) failed.\n", |
| *offp + llen + 1, *offp + llen + 1, &buf[*offp + 1]); |
| return NULL; |
| } |
| |
| rv->len = llen; |
| memcpy(rv->data, &buf[*offp + 1], llen); |
| rv->data[llen] = 0; // We NUL-terminate the label for convenience |
| *offp += llen + 1; |
| return rv; |
| } |
| |
| static bool |
| dns_name_parse_in(dns_label_t *NONNULL *NULLABLE ret, const uint8_t *buf, unsigned len, |
| unsigned *NONNULL offp, unsigned base, const char *file, int line) |
| { |
| dns_label_t *rv; |
| |
| if (*offp == len) { |
| return false; |
| } |
| |
| // A pointer? |
| if ((buf[*offp] & 0xC0) == 0xC0) { |
| unsigned pointer; |
| if (*offp + 2 > len) { |
| DEBUG("incomplete compression pointer: %u > %u", *offp + 2, len); |
| return false; |
| } |
| pointer = (((unsigned)buf[*offp] & 0x3f) << 8) | (unsigned)buf[*offp + 1]; |
| *offp += 2; |
| if (pointer < DNS_HEADER_SIZE) { |
| // Don't allow pointers into the header. |
| DEBUG("compression pointer points into header: %u.\n", pointer); |
| return false; |
| } |
| pointer -= DNS_HEADER_SIZE; |
| if (pointer >= base) { |
| // Don't allow a pointer forward, or to a pointer we've already visited. |
| DEBUG("compression pointer points forward: %u >= %u.\n", pointer, base); |
| return false; |
| } |
| if (buf[pointer] & 0xC0) { |
| // If this is a pointer to a pointer, it's not valid. |
| DEBUG("compression pointer points into pointer: %u %02x%02x.\n", pointer, |
| buf[pointer], pointer + 1 < len ? buf[pointer + 1] : 0xFF); |
| return false; |
| } |
| if (buf[pointer] + pointer >= base || buf[pointer] + pointer >= *offp) { |
| // Possibly this isn't worth checking. |
| DEBUG("compression pointer points to something that goes past current position: %u %u\n", |
| pointer, buf[pointer]); |
| return false; |
| } |
| return dns_name_parse_in(ret, buf, len, &pointer, pointer, file, line); |
| } |
| // We don't support binary labels, which are historical, and at this time there are no other valid |
| // DNS label types. |
| if (buf[*offp] & 0xC0) { |
| DEBUG("invalid label type: %x\n", buf[*offp]); |
| return false; |
| } |
| |
| rv = dns_label_parse_(buf, len, offp, file, line); |
| if (rv == NULL) { |
| return false; |
| } |
| |
| *ret = rv; |
| |
| if (rv->len == 0) { |
| return true; |
| } |
| return dns_name_parse_in(&rv->next, buf, len, offp, base, file, line); |
| } |
| |
| bool |
| dns_name_parse_(dns_label_t *NONNULL *NULLABLE ret, const uint8_t *buf, |
| unsigned len, unsigned *NONNULL offp, unsigned base, const char *file, int line) |
| { |
| dns_label_t *rv = NULL, *next; |
| |
| if (!dns_name_parse_in(&rv, buf, len, offp, base, file, line)) { |
| for (; rv != NULL; rv = next) { |
| next = rv->next; |
| free(rv); |
| } |
| return false; |
| } |
| *ret = rv; |
| return true; |
| } |
| |
| bool |
| dns_u8_parse(const uint8_t *buf, unsigned len, unsigned *NONNULL offp, uint8_t *NONNULL ret) |
| { |
| uint8_t rv; |
| if (*offp + 1 > len) { |
| DEBUG("dns_u8_parse: not enough room: %u > %u.\n", *offp + 1, len); |
| return false; |
| } |
| |
| rv = buf[*offp]; |
| *offp += 1; |
| *ret = rv; |
| return true; |
| } |
| |
| bool |
| dns_u16_parse(const uint8_t *buf, unsigned len, unsigned *NONNULL offp, uint16_t *NONNULL ret) |
| { |
| uint16_t rv; |
| if (*offp + 2 > len) { |
| DEBUG("dns_u16_parse: not enough room: %u > %u.\n", *offp + 2, len); |
| return false; |
| } |
| |
| rv = (uint16_t)(buf[*offp] << 8) | (uint16_t)(buf[*offp + 1]); |
| *offp += 2; |
| *ret = rv; |
| return true; |
| } |
| |
| bool |
| dns_u32_parse(const uint8_t *buf, unsigned len, unsigned *NONNULL offp, uint32_t *NONNULL ret) |
| { |
| uint32_t rv; |
| if (*offp + 4 > len) { |
| DEBUG("dns_u32_parse: not enough room: %u > %u.\n", *offp + 4, len); |
| return false; |
| } |
| |
| rv = (((uint32_t)(buf[*offp]) << 24) | ((uint32_t)(buf[*offp + 1]) << 16) | |
| ((uint32_t)(buf[*offp + 2]) << 8) | (uint32_t)(buf[*offp + 3])); |
| *offp += 4; |
| *ret = rv; |
| return true; |
| } |
| |
| bool |
| dns_u64_parse(const uint8_t *buf, unsigned len, unsigned *NONNULL offp, uint64_t *NONNULL ret) |
| { |
| uint64_t rv; |
| if (*offp + 8 > len) { |
| DEBUG("dns_u64_parse: not enough room: %u > %u.\n", *offp + 8, len); |
| return false; |
| } |
| |
| rv = (((uint64_t)(buf[*offp] ) << 56) | ((uint64_t)(buf[*offp + 1]) << 48) | |
| ((uint64_t)(buf[*offp + 2]) << 40) | ((uint64_t)(buf[*offp + 3]) << 32) | |
| ((uint64_t)(buf[*offp + 4]) << 24) | ((uint64_t)(buf[*offp + 5]) << 16) | |
| ((uint64_t)(buf[*offp + 6]) << 8) | ((uint64_t)(buf[*offp + 7]))); |
| *offp += 8; |
| *ret = rv; |
| return true; |
| } |
| |
| static void |
| dns_rrdata_dump(dns_rr_t *rr, bool dump_to_stderr) |
| { |
| char nbuf[INET6_ADDRSTRLEN]; |
| char buf[DNS_MAX_NAME_SIZE_ESCAPED + 1]; |
| char outbuf[2048]; |
| char *obp; |
| size_t output_len, avail = 2048; |
| |
| #define ADVANCE(result, start, remaining) \ |
| output_len = strlen(start); \ |
| result = start + output_len; \ |
| avail = (remaining) - output_len |
| #define DEPCHAR(ch) \ |
| do { \ |
| if (avail > 1) { \ |
| *obp++ = (ch); \ |
| *obp = 0; \ |
| --avail; \ |
| } \ |
| } while (0) |
| |
| |
| switch(rr->type) { |
| case dns_rrtype_key: |
| snprintf(outbuf, sizeof(outbuf), |
| "KEY <AC %d> <Z %d> <XT %d> <ZZ %d> <NAMTYPE %d> <ZZZZ %d> <ORY %d> %d %d ", |
| ((rr->data.key.flags & 0xC000) >> 14 & 3), ((rr->data.key.flags & 0x2000) >> 13) & 1, |
| ((rr->data.key.flags & 0x1000) >> 12) & 1, ((rr->data.key.flags & 0xC00) >> 10) & 3, |
| ((rr->data.key.flags & 0x300) >> 8) & 3, ((rr->data.key.flags & 0xF0) >> 4) & 15, |
| rr->data.key.flags & 15, rr->data.key.protocol, rr->data.key.algorithm); |
| ADVANCE(obp, outbuf, sizeof outbuf); |
| |
| for (unsigned i = 0; i < rr->data.key.len; i++) { |
| if (i == 0) { |
| snprintf(obp, avail, "%d [%02x", rr->data.key.len, rr->data.key.key[i]); |
| ADVANCE(obp, obp, avail); |
| } else { |
| snprintf(obp, avail, " %02x", rr->data.key.key[i]); |
| ADVANCE(obp, obp, avail); |
| } |
| } |
| DEPCHAR(']'); |
| break; |
| |
| case dns_rrtype_sig: |
| dns_name_print(rr->data.sig.signer, buf, sizeof(buf)); |
| snprintf(outbuf, sizeof(outbuf), "SIG %d %d %d %lu %lu %lu %d %s", |
| rr->data.sig.type, rr->data.sig.algorithm, rr->data.sig.label, |
| (unsigned long)rr->data.sig.rrttl, (unsigned long)rr->data.sig.expiry, |
| (unsigned long)rr->data.sig.inception, rr->data.sig.key_tag, buf); |
| ADVANCE(obp, outbuf, sizeof outbuf); |
| for (unsigned i = 0; i < rr->data.sig.len; i++) { |
| if (i == 0) { |
| snprintf(obp, avail, "%d [%02x", rr->data.sig.len, rr->data.sig.signature[i]); |
| ADVANCE(obp, obp, avail); |
| } else { |
| snprintf(obp, avail, " %02x", rr->data.sig.signature[i]); |
| ADVANCE(obp, obp, avail); |
| } |
| } |
| DEPCHAR(']'); |
| break; |
| |
| case dns_rrtype_srv: |
| dns_name_print(rr->data.srv.name, buf, sizeof(buf)); |
| snprintf(outbuf, sizeof(outbuf), "SRV %d %d %d %s", rr->data.srv.priority, rr->data.srv.weight, |
| rr->data.srv.port, buf); |
| ADVANCE(obp, outbuf, sizeof(outbuf)); |
| break; |
| |
| case dns_rrtype_ptr: |
| dns_name_print(rr->data.ptr.name, buf, sizeof(buf)); |
| snprintf(outbuf, sizeof(outbuf), "PTR %s", buf); |
| ADVANCE(obp, outbuf, sizeof(outbuf)); |
| break; |
| |
| case dns_rrtype_cname: |
| dns_name_print(rr->data.cname.name, buf, sizeof(buf)); |
| snprintf(outbuf, sizeof(outbuf), "CNAME %s", buf); |
| ADVANCE(obp, outbuf, sizeof(outbuf)); |
| break; |
| |
| case dns_rrtype_a: |
| inet_ntop(AF_INET, &rr->data.a, nbuf, sizeof(nbuf)); |
| snprintf(outbuf, sizeof(outbuf), "A %s", nbuf); |
| ADVANCE(obp, outbuf, sizeof(outbuf)); |
| break; |
| |
| case dns_rrtype_aaaa: |
| inet_ntop(AF_INET6, &rr->data.aaaa, nbuf, sizeof(nbuf)); |
| snprintf(outbuf, sizeof(outbuf), "AAAA %s", nbuf); |
| ADVANCE(obp, outbuf, sizeof(outbuf)); |
| break; |
| |
| case dns_rrtype_txt: |
| strcpy(outbuf, "TXT "); |
| ADVANCE(obp, outbuf, sizeof(outbuf)); |
| for (unsigned i = 0; i < rr->data.txt.len; i++) { |
| if (isascii(rr->data.txt.data[i]) && isprint(rr->data.txt.data[i])) { |
| DEPCHAR(rr->data.txt.data[i]); |
| } else { |
| snprintf(obp, avail, "<%x>", rr->data.txt.data[i]); |
| ADVANCE(obp, obp, avail); |
| } |
| } |
| DEPCHAR('"'); |
| break; |
| |
| default: |
| snprintf(outbuf, sizeof(outbuf), "<rrtype %d>:", rr->type); |
| ADVANCE(obp, outbuf, sizeof(outbuf)); |
| if (rr->data.unparsed.len == 0) { |
| snprintf(obp, avail, " <none>"); |
| ADVANCE(obp, obp, avail); |
| } else { |
| for (unsigned i = 0; i < rr->data.unparsed.len; i++) { |
| snprintf(obp, avail, " %02x", rr->data.unparsed.data[i]); |
| ADVANCE(obp, obp, avail); |
| } |
| } |
| break; |
| } |
| if (dump_to_stderr) { |
| fprintf(stderr, "%s\n", outbuf); |
| } else { |
| DEBUG(PUB_S_SRP, outbuf); |
| } |
| } |
| |
| bool |
| dns_rdata_parse_data_(dns_rr_t *NONNULL rr, const uint8_t *buf, unsigned *NONNULL offp, unsigned target, uint16_t rdlen, |
| unsigned rrstart, const char *file, int line) |
| { |
| if (target < *offp) { |
| DEBUG("target %u < *offp %u", target, *offp); |
| return false; |
| } |
| switch(rr->type) { |
| case dns_rrtype_key: |
| if (!dns_u16_parse(buf, target, offp, &rr->data.key.flags) || |
| !dns_u8_parse(buf, target, offp, &rr->data.key.protocol) || |
| !dns_u8_parse(buf, target, offp, &rr->data.key.algorithm)) { |
| return false; |
| } |
| rr->data.key.len = (unsigned)(target - *offp); |
| #ifdef MALLOC_DEBUG_LOGGING |
| rr->data.key.key = debug_malloc(rr->data.key.len, file, line); |
| #else |
| rr->data.key.key = malloc(rr->data.key.len); |
| #endif |
| if (!rr->data.key.key) { |
| return false; |
| } |
| memcpy(rr->data.key.key, &buf[*offp], rr->data.key.len); |
| *offp += rr->data.key.len; |
| break; |
| |
| case dns_rrtype_sig: |
| rr->data.sig.start = rrstart; |
| if (!dns_u16_parse(buf, target, offp, &rr->data.sig.type) || |
| !dns_u8_parse(buf, target, offp, &rr->data.sig.algorithm) || |
| !dns_u8_parse(buf, target, offp, &rr->data.sig.label) || |
| !dns_u32_parse(buf, target, offp, &rr->data.sig.rrttl) || |
| !dns_u32_parse(buf, target, offp, &rr->data.sig.expiry) || |
| !dns_u32_parse(buf, target, offp, &rr->data.sig.inception) || |
| !dns_u16_parse(buf, target, offp, &rr->data.sig.key_tag) || |
| !dns_name_parse_(&rr->data.sig.signer, buf, target, offp, *offp, file, line)) { |
| return false; |
| } |
| // The signature is what's left of the RRDATA. It covers the message up to the signature, so we |
| // remember where it starts so as to know what memory to cover to validate it. |
| rr->data.sig.len = target - *offp; |
| #ifdef MALLOC_DEBUG_LOGGING |
| rr->data.sig.signature = debug_malloc(rr->data.sig.len, file, line); |
| #else |
| rr->data.sig.signature = malloc(rr->data.sig.len); |
| #endif |
| if (!rr->data.sig.signature) { |
| return false; |
| } |
| memcpy(rr->data.sig.signature, &buf[*offp], rr->data.sig.len); |
| *offp += rr->data.sig.len; |
| break; |
| |
| case dns_rrtype_srv: |
| if (!dns_u16_parse(buf, target, offp, &rr->data.srv.priority) || |
| !dns_u16_parse(buf, target, offp, &rr->data.srv.weight) || |
| !dns_u16_parse(buf, target, offp, &rr->data.srv.port)) { |
| return false; |
| } |
| // This fallthrough assumes that the first element in the srv, ptr and cname structs is |
| // a pointer to a domain name. |
| |
| case dns_rrtype_ns: |
| case dns_rrtype_ptr: |
| case dns_rrtype_cname: |
| if (!dns_name_parse_(&rr->data.ptr.name, buf, target, offp, *offp, file, line)) { |
| return false; |
| } |
| break; |
| |
| case dns_rrtype_a: |
| if (rdlen != 4) { |
| DEBUG("dns_rdata_parse: A rdlen is not 4: %u", rdlen); |
| return false; |
| } |
| memcpy(&rr->data.a, &buf[*offp], rdlen); |
| *offp = target; |
| break; |
| |
| case dns_rrtype_aaaa: |
| if (rdlen != 16) { |
| DEBUG("dns_rdata_parse: AAAA rdlen is not 16: %u", rdlen); |
| return false; |
| } |
| memcpy(&rr->data.aaaa, &buf[*offp], rdlen); |
| *offp = target; |
| break; |
| |
| case dns_rrtype_txt: |
| { |
| unsigned left = target - *offp; |
| if (left != rdlen) { |
| ERROR("TXT record length %u doesn't match remaining space %d", rdlen, left); |
| } |
| if (left > UINT8_MAX) { |
| ERROR("TXT record length %u is longer than 255", left); |
| } |
| rr->data.txt.len = (uint8_t)left; |
| #ifdef MALLOC_DEBUG_LOGGING |
| rr->data.txt.data = debug_malloc(rr->data.txt.len, file, line); |
| #else |
| rr->data.txt.data = malloc(rr->data.txt.len); |
| #endif |
| if (rr->data.txt.data == NULL) { |
| DEBUG("dns_rdata_parse: no memory for TXT RR"); |
| return false; |
| } |
| memcpy(rr->data.txt.data, &buf[*offp], rr->data.txt.len); |
| *offp = target; |
| break; |
| } |
| |
| default: |
| if (rdlen > 0) { |
| #ifdef MALLOC_DEBUG_LOGGING |
| rr->data.unparsed.data = debug_malloc(rdlen, file, line); |
| #else |
| rr->data.unparsed.data = malloc(rdlen); |
| #endif |
| if (rr->data.unparsed.data == NULL) { |
| return false; |
| } |
| memcpy(rr->data.unparsed.data, &buf[*offp], rdlen); |
| } |
| rr->data.unparsed.len = rdlen; |
| *offp = target; |
| break; |
| } |
| if (*offp != target) { |
| DEBUG("dns_rdata_parse: parse for rrtype %d not fully contained: %u %u", rr->type, target, *offp); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool |
| dns_rdata_parse_(dns_rr_t *NONNULL rr, |
| const uint8_t *buf, unsigned len, unsigned *NONNULL offp, unsigned rrstart, const char *file, int line) |
| { |
| uint16_t rdlen; |
| unsigned target; |
| |
| if (!dns_u16_parse(buf, len, offp, &rdlen)) { |
| return false; |
| } |
| target = *offp + rdlen; |
| if (target > len) { |
| return false; |
| } |
| return dns_rdata_parse_data_(rr, buf, offp, target, rdlen, rrstart, file, line); |
| } |
| |
| bool |
| dns_rr_parse_(dns_rr_t *NONNULL rr, const uint8_t *buf, unsigned len, unsigned *NONNULL offp, bool rrdata_expected, |
| bool dump_stderr, const char *file, int line) |
| { |
| unsigned rrstart = *offp; // Needed to mark the start of the SIG RR for SIG(0). |
| |
| memset(rr, 0, sizeof(*rr)); |
| if (!dns_name_parse_(&rr->name, buf, len, offp, *offp, file, line)) { |
| return false; |
| } |
| |
| if (!dns_u16_parse(buf, len, offp, &rr->type)) { |
| return false; |
| } |
| |
| if (!dns_u16_parse(buf, len, offp, &rr->qclass)) { |
| return false; |
| } |
| |
| if (rrdata_expected) { |
| if (!dns_u32_parse(buf, len, offp, &rr->ttl)) { |
| return false; |
| } |
| if (!dns_rdata_parse_(rr, buf, len, offp, rrstart, file, line)) { |
| return false; |
| } |
| } |
| |
| DNS_NAME_GEN_SRP(rr->name, name_buf); |
| if (dump_stderr) { |
| fprintf(stderr, "rrtype: %u qclass: %u name: %s %s\n", |
| rr->type, rr->qclass, DNS_NAME_PARAM_SRP(rr->name, name_buf), rrdata_expected ? " rrdata:" : ""); |
| } else { |
| DEBUG("rrtype: %u qclass: %u name: " PRI_DNS_NAME_SRP PUB_S_SRP, |
| rr->type, rr->qclass, DNS_NAME_PARAM_SRP(rr->name, name_buf), rrdata_expected ? " rrdata:" : ""); |
| } |
| if (rrdata_expected) { |
| dns_rrdata_dump(rr, dump_stderr); |
| } |
| return true; |
| } |
| |
| void |
| dns_rrdata_free(dns_rr_t *rr) |
| { |
| if (rr == NULL) { |
| return; |
| } |
| switch(rr->type) { |
| case dns_rrtype_a: |
| case dns_rrtype_aaaa: |
| break; |
| |
| case dns_rrtype_key: |
| free(rr->data.key.key); |
| break; |
| |
| case dns_rrtype_sig: |
| dns_name_free(rr->data.sig.signer); |
| free(rr->data.sig.signature); |
| break; |
| |
| case dns_rrtype_srv: |
| case dns_rrtype_ptr: |
| case dns_rrtype_ns: |
| case dns_rrtype_cname: |
| dns_name_free(rr->data.ptr.name); |
| #ifndef __clang_analyzer__ |
| rr->data.ptr.name = NULL; |
| #endif |
| break; |
| |
| case dns_rrtype_txt: |
| free(rr->data.txt.data); |
| #ifndef __clang_analyzer__ |
| rr->data.txt.data = NULL; |
| #endif |
| break; |
| |
| default: |
| if (rr->data.unparsed.len > 0 && rr->data.unparsed.data != NULL) { |
| free(rr->data.unparsed.data); |
| } |
| rr->data.unparsed.data = NULL; |
| } |
| } |
| |
| void |
| dns_message_free(dns_message_t *message) |
| { |
| dns_edns0_t *edns0, *next; |
| |
| #define FREE(count, sets) \ |
| if (message->sets) { \ |
| for (unsigned i = 0; i < message->count; i++) { \ |
| dns_rr_t *set = &message->sets[i]; \ |
| if (set->type == dns_invalid_rr) { \ |
| continue; \ |
| } \ |
| if (set->name) { \ |
| dns_name_free(set->name); \ |
| } \ |
| dns_rrdata_free(set); \ |
| } \ |
| free(message->sets); \ |
| } |
| FREE(qdcount, questions); |
| FREE(ancount, answers); |
| FREE(nscount, authority); |
| FREE(arcount, additional); |
| #undef FREE |
| for (edns0 = message->edns0; edns0 != NULL; edns0 = next) { |
| next = edns0->next; |
| free(edns0); |
| } |
| free(message); |
| } |
| |
| bool |
| dns_wire_parse_(dns_message_t *NONNULL *NULLABLE ret, dns_wire_t *message, unsigned len, bool dump_to_stderr, |
| const char *file, int line) |
| { |
| unsigned offset = 0; |
| unsigned data_len = len - DNS_HEADER_SIZE; |
| dns_message_t *rv; |
| |
| if (len < DNS_HEADER_SIZE) { |
| return false; |
| } |
| #ifdef MALLOC_DEBUG_LOGGING |
| rv = debug_calloc(1, sizeof(*rv), file, line); |
| #else |
| rv = calloc(1, sizeof(*rv)); |
| #endif |
| if (rv == NULL) { |
| return false; |
| } |
| |
| #define PARSE(count, sets, name, rrdata_expected) \ |
| rv->count = ntohs(message->count); \ |
| if (rv->count > 50) { \ |
| rv->count = 0; \ |
| dns_message_free(rv); \ |
| return false; \ |
| } \ |
| DEBUG("Section %s, %d records", name, rv->count); \ |
| \ |
| if (rv->count != 0) { \ |
| rv->sets = calloc(rv->count, sizeof(*rv->sets)); \ |
| if (rv->sets == NULL) { \ |
| dns_message_free(rv); \ |
| return false; \ |
| } \ |
| } \ |
| \ |
| for (unsigned i = 0; i < rv->count; i++) { \ |
| if (!dns_rr_parse_(&rv->sets[i], message->data, data_len, &offset, \ |
| rrdata_expected, dump_to_stderr, file, line)) { \ |
| dns_message_free(rv); \ |
| ERROR(name " %d RR parse failed.\n", i); \ |
| return false; \ |
| } \ |
| } |
| PARSE(qdcount, questions, "question", false); |
| PARSE(ancount, answers, "answers", true); |
| PARSE(nscount, authority, "authority", true); |
| PARSE(arcount, additional, "additional", true); |
| #undef PARSE |
| |
| for (unsigned i = 0; i < rv->arcount; i++) { |
| // Parse EDNS(0) |
| if (rv->additional[i].type == dns_rrtype_opt) { |
| if (!dns_opt_parse(&rv->edns0, &rv->additional[i])) { |
| dns_message_free(rv); |
| return false; |
| } |
| } |
| } |
| *ret = rv; |
| return true; |
| } |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 108 |
| // indent-tabs-mode: nil |
| // End: |