diff options
Diffstat (limited to '')
-rw-r--r-- | src/libknot/dname.c | 801 |
1 files changed, 801 insertions, 0 deletions
diff --git a/src/libknot/dname.c b/src/libknot/dname.c new file mode 100644 index 0000000..31b8a5f --- /dev/null +++ b/src/libknot/dname.c @@ -0,0 +1,801 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libknot/attribute.h" +#include "libknot/consts.h" +#include "libknot/dname.h" +#include "libknot/errcode.h" +#include "libknot/packet/wire.h" +#include "contrib/ctype.h" +#include "contrib/mempattern.h" +#include "contrib/tolower.h" + +static bool label_is_equal(const uint8_t *lb1, const uint8_t *lb2, bool no_case) +{ + if (*lb1 != *lb2) { + return false; + } + + if (no_case) { + uint8_t len = *lb1; + for (uint8_t i = 1; i <= len; i++) { + if (knot_tolower(lb1[i]) != knot_tolower(lb2[i])) { + return false; + } + } + + return true; + } else { + return memcmp(lb1 + 1, lb2 + 1, *lb1) == 0; + } +} + +/*! + * \brief Align name end-to-end and return number of common suffix labels. + * + * \param d1 Domain name1. + * \param d1_labels Number of labels in d1. + * \param d2 Domain name2. + * \param d2_labels Number of labels in d2. + */ +static int dname_align(const uint8_t **d1, uint8_t d1_labels, + const uint8_t **d2, uint8_t d2_labels) +{ + assert(d1 && d2); + + for (unsigned j = d1_labels; j < d2_labels; ++j) { + *d2 = knot_wire_next_label(*d2, NULL); + } + + for (unsigned j = d2_labels; j < d1_labels; ++j) { + *d1 = knot_wire_next_label(*d1, NULL); + } + + return (d1_labels < d2_labels) ? d1_labels : d2_labels; +} + +_public_ +int knot_dname_wire_check(const uint8_t *name, const uint8_t *endp, + const uint8_t *pkt) +{ + if (name == NULL || name == endp) { + return KNOT_EINVAL; + } + + int wire_len = 0; + int name_len = 1; /* Keep \x00 terminal label in advance. */ + bool is_compressed = false; + + while (*name != '\0') { + /* Check bounds (must have at least 2 octets remaining). */ + if (name + 2 > endp) { + return KNOT_EMALF; + } + + if (knot_wire_is_pointer(name)) { + /* Check that the pointer points backwards + * otherwise it could result in infinite loop + */ + if (pkt == NULL) { + return KNOT_EINVAL; + } + uint16_t ptr = knot_wire_get_pointer(name); + if (ptr >= (name - pkt)) { + return KNOT_EMALF; + } + + name = pkt + ptr; /* Hop to compressed label */ + if (!is_compressed) { /* Measure compressed size only */ + wire_len += sizeof(uint16_t); + is_compressed = true; + } + } else { + /* Check label length. */ + if (*name > KNOT_DNAME_MAXLABELLEN) { + return KNOT_EMALF; + } + /* Check if there's enough space. */ + int lblen = *name + 1; + if (name_len + lblen > KNOT_DNAME_MAXLEN) { + return KNOT_EMALF; + } + /* Update wire size only for uncompressed part. */ + name_len += lblen; + if (!is_compressed) { + wire_len += lblen; + } + /* Hop to next label. */ + name += lblen; + } + + /* Check bounds (must have at least 1 octet). */ + if (name + 1 > endp) { + return KNOT_EMALF; + } + } + + if (!is_compressed) { /* Terminal label. */ + wire_len += 1; + } + + return wire_len; +} + +_public_ +size_t knot_dname_store(knot_dname_storage_t dst, const knot_dname_t *name) +{ + if (dst == NULL || name == NULL) { + return 0; + } + + size_t len = knot_dname_size(name); + assert(len <= KNOT_DNAME_MAXLEN); + memcpy(dst, name, len); + + return len; +} + +_public_ +knot_dname_t *knot_dname_copy(const knot_dname_t *name, knot_mm_t *mm) +{ + if (name == NULL) { + return NULL; + } + + size_t len = knot_dname_size(name); + knot_dname_t *dst = mm_alloc(mm, len); + if (dst == NULL) { + return NULL; + } + memcpy(dst, name, len); + + return dst; +} + +_public_ +int knot_dname_to_wire(uint8_t *dst, const knot_dname_t *src, size_t maxlen) +{ + if (dst == NULL || src == NULL) { + return KNOT_EINVAL; + } + + size_t len = knot_dname_size(src); + if (len > maxlen) { + return KNOT_ESPACE; + } + memcpy(dst, src, len); + + return len; +} + +_public_ +int knot_dname_unpack(uint8_t *dst, const knot_dname_t *src, + size_t maxlen, const uint8_t *pkt) +{ + if (dst == NULL || src == NULL) { + return KNOT_EINVAL; + } + + /* Seek first real label occurrence. */ + src = knot_wire_seek_label(src, pkt); + + /* Unpack rest of the labels. */ + int len = 0; + while (*src != '\0') { + uint8_t lblen = *src + 1; + if (len + lblen > maxlen) { + return KNOT_ESPACE; + } + memcpy(dst + len, src, lblen); + len += lblen; + src = knot_wire_next_label(src, pkt); + } + + /* Terminal label */ + if (len + 1 > maxlen) { + return KNOT_EINVAL; + } + + *(dst + len) = '\0'; + return len + 1; +} + +_public_ +char *knot_dname_to_str(char *dst, const knot_dname_t *name, size_t maxlen) +{ + if (name == NULL) { + return NULL; + } + + size_t dname_size = knot_dname_size(name); + + /* Check the size for len(dname) + 1 char termination. */ + size_t alloc_size = (dst == NULL) ? dname_size + 1 : maxlen; + if (alloc_size < dname_size + 1) { + return NULL; + } + + char *res = (dst == NULL) ? malloc(alloc_size) : dst; + if (res == NULL) { + return NULL; + } + + uint8_t label_len = 0; + size_t str_len = 0; + + for (unsigned i = 0; i < dname_size; i++) { + uint8_t c = name[i]; + + /* Read next label size. */ + if (label_len == 0) { + label_len = c; + + /* Write label separation. */ + if (str_len > 0 || dname_size == 1) { + if (alloc_size <= str_len + 1) { + goto dname_to_str_failed; + } + res[str_len++] = '.'; + } + + continue; + } + + if (is_alnum(c) || c == '-' || c == '_' || c == '*' || + c == '/') { + if (alloc_size <= str_len + 1) { + goto dname_to_str_failed; + } + res[str_len++] = c; + } else if (is_punct(c) && c != '#') { + /* Exclusion of '#' character is to avoid possible + * collision with rdata hex notation '\#'. So it is + * encoded in \ddd notation. + */ + + if (dst != NULL) { + if (maxlen <= str_len + 2) { + goto dname_to_str_failed; + } + } else { + /* Extend output buffer for \x format. */ + alloc_size += 1; + char *extended = realloc(res, alloc_size); + if (extended == NULL) { + goto dname_to_str_failed; + } + res = extended; + } + + /* Write encoded character. */ + res[str_len++] = '\\'; + res[str_len++] = c; + } else { + if (dst != NULL) { + if (maxlen <= str_len + 4) { + goto dname_to_str_failed; + } + } else { + /* Extend output buffer for \DDD format. */ + alloc_size += 3; + char *extended = realloc(res, alloc_size); + if (extended == NULL) { + goto dname_to_str_failed; + } + res = extended; + } + + /* Write encoded character. */ + int ret = snprintf(res + str_len, alloc_size - str_len, + "\\%03u", c); + if (ret <= 0 || ret >= alloc_size - str_len) { + goto dname_to_str_failed; + } + + str_len += ret; + } + + label_len--; + } + + /* String_termination. */ + assert(str_len < alloc_size); + res[str_len] = 0; + + return res; + +dname_to_str_failed: + + if (dst == NULL) { + free(res); + } + + return NULL; +} + +_public_ +knot_dname_t *knot_dname_from_str(uint8_t *dst, const char *name, size_t maxlen) +{ + if (name == NULL) { + return NULL; + } + + size_t name_len = strlen(name); + if (name_len == 0) { + return NULL; + } + + /* Wire size estimation. */ + size_t alloc_size = maxlen; + if (dst == NULL) { + /* Check for the root label. */ + if (name[0] == '.') { + /* Just the root dname can begin with a dot. */ + if (name_len > 1) { + return NULL; + } + name_len = 0; /* Skip the following parsing. */ + alloc_size = 1; + } else if (name[name_len - 1] != '.') { /* Check for non-FQDN. */ + alloc_size = 1 + name_len + 1; + } else { + alloc_size = 1 + name_len ; /* + 1 ~ first label length. */ + } + } + + /* The minimal (root) dname takes 1 byte. */ + if (alloc_size == 0) { + return NULL; + } + + /* Check the maximal wire size. */ + if (alloc_size > KNOT_DNAME_MAXLEN) { + alloc_size = KNOT_DNAME_MAXLEN; + } + + /* Prepare output buffer. */ + uint8_t *wire = (dst == NULL) ? malloc(alloc_size) : dst; + if (wire == NULL) { + return NULL; + } + + uint8_t *label = wire; + uint8_t *wire_pos = wire + 1; + uint8_t *wire_end = wire + alloc_size; + + /* Initialize the first label (root label). */ + *label = 0; + + const uint8_t *ch = (const uint8_t *)name; + const uint8_t *end = ch + name_len; + + while (ch < end) { + /* Check the output buffer for enough space. */ + if (wire_pos >= wire_end) { + goto dname_from_str_failed; + } + + switch (*ch) { + case '.': + /* Check for invalid zero-length label. */ + if (*label == 0 && name_len > 1) { + goto dname_from_str_failed; + } + label = wire_pos++; + *label = 0; + break; + case '\\': + ch++; + + /* At least one more character is required OR + * check for maximal label length. + */ + if (ch == end || ++(*label) > KNOT_DNAME_MAXLABELLEN) { + goto dname_from_str_failed; + } + + /* Check for \DDD notation. */ + if (is_digit(*ch)) { + /* Check for next two digits. */ + if (ch + 2 >= end || + !is_digit(*(ch + 1)) || + !is_digit(*(ch + 2))) { + goto dname_from_str_failed; + } + + uint32_t num = (*(ch + 0) - '0') * 100 + + (*(ch + 1) - '0') * 10 + + (*(ch + 2) - '0') * 1; + if (num > UINT8_MAX) { + goto dname_from_str_failed; + } + *(wire_pos++) = num; + ch +=2; + } else { + *(wire_pos++) = *ch; + } + break; + default: + /* Check for maximal label length. */ + if (++(*label) > KNOT_DNAME_MAXLABELLEN) { + goto dname_from_str_failed; + } + *(wire_pos++) = *ch; + } + ch++; + } + + /* Check for non-FQDN name. */ + if (*label > 0) { + if (wire_pos >= wire_end) { + goto dname_from_str_failed; + } + *(wire_pos++) = 0; + } + + /* Reduce output buffer if the size is overestimated. */ + if (wire_pos < wire_end && dst == NULL) { + uint8_t *reduced = realloc(wire, wire_pos - wire); + if (reduced == NULL) { + goto dname_from_str_failed; + } + wire = reduced; + } + + return wire; + +dname_from_str_failed: + + if (dst == NULL) { + free(wire); + } + + return NULL; +} + +_public_ +void knot_dname_to_lower(knot_dname_t *name) +{ + if (name == NULL) { + return; + } + + while (*name != '\0') { + uint8_t len = *name; + for (uint8_t i = 1; i <= len; ++i) { + name[i] = knot_tolower(name[i]); + } + name += 1 + len; + } +} + +_public_ +void knot_dname_copy_lower(knot_dname_t *dst, const knot_dname_t *name) +{ + if (dst == NULL || name == NULL) { + return; + } + + while (*name != '\0') { + uint8_t len = *name; + *dst = len; + for (uint8_t i = 1; i <= len; ++i) { + dst[i] = knot_tolower(name[i]); + } + dst += 1 + len; + name += 1 + len; + } + *dst = '\0'; +} + +_public_ +size_t knot_dname_size(const knot_dname_t *name) +{ + if (name == NULL) { + return 0; + } + + /* Count name size without terminal label. */ + size_t len = 0; + while (*name != '\0' && !knot_wire_is_pointer(name)) { + uint8_t lblen = *name + 1; + len += lblen; + name += lblen; + } + + if (knot_wire_is_pointer(name)) { + /* Add 2-octet compression pointer. */ + return len + 2; + } else { + /* Add 1-octet terminal label. */ + return len + 1; + } +} + +_public_ +size_t knot_dname_realsize(const knot_dname_t *name, const uint8_t *pkt) +{ + if (name == NULL) { + return 0; + } + + /* Seek first real label occurrence. */ + name = knot_wire_seek_label(name, pkt); + + size_t len = 0; + while (*name != '\0') { + len += *name + 1; + name = knot_wire_next_label(name, pkt); + } + + /* Add 1-octet terminal label. */ + return len + 1; +} + +_public_ +size_t knot_dname_matched_labels(const knot_dname_t *d1, const knot_dname_t *d2) +{ + /* Count labels. */ + size_t l1 = knot_dname_labels(d1, NULL); + size_t l2 = knot_dname_labels(d2, NULL); + if (l1 == 0 || l2 == 0) { + return 0; + } + + /* Align end-to-end to common suffix. */ + int common = dname_align(&d1, l1, &d2, l2); + + /* Count longest chain leading to root label. */ + size_t matched = 0; + while (common > 0) { + if (label_is_equal(d1, d2, false)) { + ++matched; + } else { + matched = 0; /* Broken chain. */ + } + + /* Next label. */ + d1 = knot_wire_next_label(d1, NULL); + d2 = knot_wire_next_label(d2, NULL); + --common; + } + + return matched; +} + +_public_ +knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name, unsigned labels, + const knot_dname_t *suffix, knot_mm_t *mm) +{ + if (name == NULL) { + return NULL; + } + + /* Calculate prefix and suffix lengths. */ + size_t dname_lbs = knot_dname_labels(name, NULL); + if (dname_lbs < labels) { + return NULL; + } + size_t prefix_lbs = dname_lbs - labels; + + size_t prefix_len = knot_dname_prefixlen(name, prefix_lbs, NULL); + size_t suffix_len = knot_dname_size(suffix); + if (prefix_len == 0 || suffix_len == 0) { + return NULL; + } + + /* Create target name. */ + size_t new_len = prefix_len + suffix_len; + knot_dname_t *out = mm_alloc(mm, new_len); + if (out == NULL) { + return NULL; + } + + /* Copy prefix. */ + uint8_t *dst = out; + while (prefix_lbs > 0) { + memcpy(dst, name, *name + 1); + dst += *name + 1; + name = knot_wire_next_label(name, NULL); + --prefix_lbs; + } + + /* Copy suffix. */ + while (*suffix != '\0') { + memcpy(dst, suffix, *suffix + 1); + dst += *suffix + 1; + suffix = knot_wire_next_label(suffix, NULL); + } + + *dst = '\0'; + return out; +} + +_public_ +void knot_dname_free(knot_dname_t *name, knot_mm_t *mm) +{ + if (name == NULL) { + return; + } + + mm_free(mm, name); +} + +_public_ +int knot_dname_cmp(const knot_dname_t *d1, const knot_dname_t *d2) +{ + if (d1 == NULL) { + return -1; + } else if (d2 == NULL) { + return 1; + } + + /* Convert to lookup format. */ + knot_dname_storage_t lf1_storage; + knot_dname_storage_t lf2_storage; + + uint8_t *lf1 = knot_dname_lf(d1, lf1_storage); + uint8_t *lf2 = knot_dname_lf(d2, lf2_storage); + assert(lf1 && lf2); + + /* Compare common part. */ + uint8_t common = lf1[0]; + if (common > lf2[0]) { + common = lf2[0]; + } + int ret = memcmp(lf1 + 1, lf2 + 1, common); + if (ret != 0) { + return ret; + } + + /* If they match, compare lengths. */ + if (lf1[0] < lf2[0]) { + return -1; + } else if (lf1[0] > lf2[0]) { + return 1; + } else { + return 0; + } +} + +inline static bool dname_is_equal(const knot_dname_t *d1, const knot_dname_t *d2, bool no_case) +{ + if (d1 == NULL || d2 == NULL) { + return false; + } + + while (*d1 != '\0' || *d2 != '\0') { + if (label_is_equal(d1, d2, no_case)) { + d1 = knot_wire_next_label(d1, NULL); + d2 = knot_wire_next_label(d2, NULL); + } else { + return false; + } + } + + return true; +} + +_public_ +bool knot_dname_is_equal(const knot_dname_t *d1, const knot_dname_t *d2) +{ + return dname_is_equal(d1, d2, false); +} + +_public_ +bool knot_dname_is_case_equal(const knot_dname_t *d1, const knot_dname_t *d2) +{ + return dname_is_equal(d1, d2, true); +} + +_public_ +size_t knot_dname_prefixlen(const uint8_t *name, unsigned nlabels, const uint8_t *pkt) +{ + if (name == NULL) { + return 0; + } + + /* Zero labels means no prefix. */ + if (nlabels == 0) { + return 0; + } + + /* Seek first real label occurrence. */ + name = knot_wire_seek_label(name, pkt); + + size_t len = 0; + while (*name != '\0') { + len += *name + 1; + name = knot_wire_next_label(name, pkt); + if (--nlabels == 0) { /* Count N first labels only. */ + break; + } + } + + return len; +} + +_public_ +size_t knot_dname_labels(const uint8_t *name, const uint8_t *pkt) +{ + if (name == NULL) { + return 0; + } + + size_t count = 0; + while (*name != '\0') { + ++count; + name = knot_wire_next_label(name, pkt); + if (name == NULL) { + return 0; + } + } + + return count; +} + +_public_ +uint8_t *knot_dname_lf(const knot_dname_t *src, knot_dname_storage_t storage) +{ + if (src == NULL || storage == NULL) { + return NULL; + } + + uint8_t *dst = storage + KNOT_DNAME_MAXLEN - 1; + + while (*src != 0) { + uint8_t len = *src++; + *dst = '\0'; + dst -= len; + assert(dst >= storage); + + if (len == 1) { + *dst-- = *src++; + } else { + memcpy(dst--, src, len); + src += len; + } + } + + *dst = storage + KNOT_DNAME_MAXLEN - 1 - dst; + assert(dst >= storage); + + return dst; +} + +_public_ +int knot_dname_in_bailiwick(const knot_dname_t *name, const knot_dname_t *bailiwick) +{ + if (name == NULL || bailiwick == NULL) { + return KNOT_EINVAL; + } + + int label_diff = knot_dname_labels(name, NULL) - knot_dname_labels(bailiwick, NULL); + if (label_diff < 0) { + return KNOT_EOUTOFZONE; + } + + for (int i = 0; i < label_diff; ++i) { + name = knot_wire_next_label(name, NULL); + } + + return knot_dname_is_equal(name, bailiwick) ? label_diff : KNOT_EOUTOFZONE; +} |