diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
commit | 133a45c109da5310add55824db21af5239951f93 (patch) | |
tree | ba6ac4c0a950a0dda56451944315d66409923918 /contrib/librdns/parse.c | |
parent | Initial commit. (diff) | |
download | rspamd-133a45c109da5310add55824db21af5239951f93.tar.xz rspamd-133a45c109da5310add55824db21af5239951f93.zip |
Adding upstream version 3.8.1.upstream/3.8.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | contrib/librdns/parse.c | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/contrib/librdns/parse.c b/contrib/librdns/parse.c new file mode 100644 index 0000000..f9025e1 --- /dev/null +++ b/contrib/librdns/parse.c @@ -0,0 +1,461 @@ +/* Copyright (c) 2014, Vsevolod Stakhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "rdns.h" +#include "dns_private.h" +#include "parse.h" +#include "logger.h" + +static uint8_t * +rdns_decompress_label (uint8_t *begin, uint16_t *len, uint16_t max) +{ + uint16_t offset = (*len); + + if (offset > max) { + return NULL; + } + *len = *(begin + offset); + return begin + offset; +} + +#define UNCOMPRESS_DNS_OFFSET(p) (((*(p)) ^ DNS_COMPRESSION_BITS) << 8) + *((p) + 1) + +uint8_t * +rdns_request_reply_cmp (struct rdns_request *req, uint8_t *in, int len) +{ + uint8_t *p, *c, *l1, *l2; + uint16_t len1, len2; + int decompressed = 0; + struct rdns_resolver *resolver = req->resolver; + + /* QR format: + * labels - len:octets + * null label - 0 + * class - 2 octets + * type - 2 octets + */ + + /* In p we would store current position in reply and in c - position in request */ + p = in; + c = req->packet + req->pos; + + for (;;) { + /* Get current label */ + len1 = *p; + len2 = *c; + if (p - in > len) { + rdns_info ("invalid dns reply"); + return NULL; + } + /* This may be compressed, so we need to decompress it */ + if (len1 & DNS_COMPRESSION_BITS) { + len1 = UNCOMPRESS_DNS_OFFSET(p); + l1 = rdns_decompress_label (in, &len1, len); + if (l1 == NULL) { + return NULL; + } + decompressed ++; + l1 ++; + p += 2; + } + else { + l1 = ++p; + p += len1; + } + if (len2 & DNS_COMPRESSION_BITS) { + len2 = UNCOMPRESS_DNS_OFFSET(c); + l2 = rdns_decompress_label (c, &len2, len); + if (l2 == NULL) { + rdns_info ("invalid DNS pointer, cannot decompress"); + return NULL; + } + decompressed ++; + l2 ++; + c += 2; + } + else { + l2 = ++c; + c += len2; + } + if (len1 != len2) { + return NULL; + } + if (len1 == 0) { + break; + } + + if (memcmp (l1, l2, len1) != 0) { + return NULL; + } + if (decompressed == 2) { + break; + } + } + + /* p now points to the end of QR section */ + /* Compare class and type */ + if (memcmp (p, c, sizeof (uint16_t) * 2) == 0) { + req->pos = c - req->packet + sizeof (uint16_t) * 2; + return p + sizeof (uint16_t) * 2; + } + return NULL; +} + +#define MAX_RECURSION_LEVEL 10 + +bool +rdns_parse_labels (struct rdns_resolver *resolver, + uint8_t *in, char **target, uint8_t **pos, struct rdns_reply *rep, + int *remain, bool make_name) +{ + uint16_t namelen = 0; + uint8_t *p = *pos, *begin = *pos, *l, *t, *end = *pos + *remain, *new_pos = *pos; + uint16_t llen; + int length = *remain, new_remain = *remain; + int ptrs = 0, labels = 0; + bool got_compression = false; + + /* First go through labels and calculate name length */ + while (p - begin < length) { + if (ptrs > MAX_RECURSION_LEVEL) { + rdns_info ("dns pointers are nested too much"); + return false; + } + llen = *p; + if (llen == 0) { + if (!got_compression) { + /* In case of compression we have already decremented the processing position */ + new_remain -= sizeof (uint8_t); + new_pos += sizeof (uint8_t); + } + break; + } + else if ((llen & DNS_COMPRESSION_BITS)) { + if (end - p > 1) { + ptrs ++; + llen = UNCOMPRESS_DNS_OFFSET(p); + l = rdns_decompress_label (in, &llen, end - in); + if (l == NULL) { + rdns_info ("invalid DNS pointer"); + return false; + } + if (!got_compression) { + /* Our label processing is finished actually */ + new_remain -= sizeof (uint16_t); + new_pos += sizeof (uint16_t); + got_compression = true; + } + if (l < in || l > begin + length) { + rdns_info ("invalid pointer in DNS packet"); + return false; + } + begin = l; + length = end - begin; + p = l + *l + 1; + namelen += *l; + labels ++; + } + else { + rdns_info ("DNS packet has incomplete compressed label, input length: %d bytes, remain: %d", + *remain, new_remain); + return false; + } + } + else { + namelen += llen; + p += llen + 1; + labels ++; + if (!got_compression) { + new_remain -= llen + 1; + new_pos += llen + 1; + } + } + } + + if (!make_name) { + goto end; + } + *target = malloc (namelen + labels + 3); + t = (uint8_t *)*target; + p = *pos; + begin = *pos; + length = *remain; + /* Now copy labels to name */ + while (p - begin < length) { + llen = *p; + if (llen == 0) { + break; + } + else if (llen & DNS_COMPRESSION_BITS) { + llen = UNCOMPRESS_DNS_OFFSET(p); + l = rdns_decompress_label (in, &llen, end - in); + + if (l == NULL) { + goto end; + } + + begin = l; + length = end - begin; + p = l + *l + 1; + memcpy (t, l + 1, *l); + t += *l; + *t ++ = '.'; + } + else { + memcpy (t, p + 1, *p); + t += *p; + *t ++ = '.'; + p += *p + 1; + } + } + if (t > (uint8_t *)*target) { + *(t - 1) = '\0'; + } + else { + /* Handle empty labels */ + **target = '\0'; + } +end: + *remain = new_remain; + *pos = new_pos; + + return true; +} + +#define GET8(x) do {(x) = ((*p)); p += sizeof (uint8_t); *remain -= sizeof (uint8_t); } while(0) +#define GET16(x) do {(x) = ((*p) << 8) + *(p + 1); p += sizeof (uint16_t); *remain -= sizeof (uint16_t); } while(0) +#define GET32(x) do {(x) = ((*p) << 24) + ((*(p + 1)) << 16) + ((*(p + 2)) << 8) + *(p + 3); p += sizeof (uint32_t); *remain -= sizeof (uint32_t); } while(0) +#define SKIP(type) do { p += sizeof(type); *remain -= sizeof(type); } while (0) + +int +rdns_parse_rr (struct rdns_resolver *resolver, + uint8_t *in, struct rdns_reply_entry *elt, uint8_t **pos, + struct rdns_reply *rep, int *remain) +{ + uint8_t *p = *pos, parts; + uint16_t type, datalen, txtlen, copied; + int32_t ttl; + bool parsed = false; + + /* Skip the whole name */ + if (!rdns_parse_labels (resolver, in, NULL, &p, rep, remain, false)) { + rdns_info ("bad RR name"); + return -1; + } + if (*remain < (int)sizeof (uint16_t) * 6) { + rdns_info ("stripped dns reply: %d bytes remain; domain %s", *remain, + rep->requested_name); + return -1; + } + GET16 (type); + /* Skip class */ + SKIP (uint16_t); + GET32 (ttl); + GET16 (datalen); + elt->type = type; + /* Now p points to RR data */ + switch (type) { + case DNS_T_A: + if (!(datalen & 0x3) && datalen <= *remain) { + memcpy (&elt->content.a.addr, p, sizeof (struct in_addr)); + p += datalen; + *remain -= datalen; + parsed = true; + } + else { + rdns_info ("corrupted A record; domain: %s", rep->requested_name); + return -1; + } + break; + case DNS_T_AAAA: + if (datalen == sizeof (struct in6_addr) && datalen <= *remain) { + memcpy (&elt->content.aaa.addr, p, sizeof (struct in6_addr)); + p += datalen; + *remain -= datalen; + parsed = true; + } + else { + rdns_info ("corrupted AAAA record; domain %s", rep->requested_name); + return -1; + } + break; + case DNS_T_PTR: + if (! rdns_parse_labels (resolver, in, &elt->content.ptr.name, &p, + rep, remain, true)) { + rdns_info ("invalid labels in PTR record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_NS: + if (! rdns_parse_labels (resolver, in, &elt->content.ns.name, &p, + rep, remain, true)) { + rdns_info ("invalid labels in NS record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_SOA: + if (! rdns_parse_labels (resolver, in, &elt->content.soa.mname, &p, + rep, remain, true)) { + rdns_info ("invalid labels in SOA record; domain %s", rep->requested_name); + return -1; + } + if (! rdns_parse_labels (resolver, in, &elt->content.soa.admin, &p, + rep, remain, true)) { + rdns_info ("invalid labels in SOA record; domain %s", rep->requested_name); + return -1; + } + if (*remain >= sizeof(int32_t) * 5) { + GET32 (elt->content.soa.serial); + GET32 (elt->content.soa.refresh); + GET32 (elt->content.soa.retry); + GET32 (elt->content.soa.expire); + GET32 (elt->content.soa.minimum); + } + else { + rdns_info ("invalid data in SOA record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_MX: + GET16 (elt->content.mx.priority); + if (! rdns_parse_labels (resolver, in, &elt->content.mx.name, &p, + rep, remain, true)) { + rdns_info ("invalid labels in MX record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_TXT: + case DNS_T_SPF: + if (datalen <= *remain) { + elt->content.txt.data = malloc(datalen + 1); + if (elt->content.txt.data == NULL) { + rdns_err ("failed to allocate %d bytes for TXT record; domain %s", + (int) datalen + 1, rep->requested_name); + return -1; + } + /* Now we should compose data from parts */ + copied = 0; + parts = 0; + while (copied + parts < datalen && *remain > 0) { + txtlen = *p; + if (txtlen + copied + parts <= datalen && *remain >= txtlen + 1) { + parts++; + memcpy (elt->content.txt.data + copied, p + 1, txtlen); + copied += txtlen; + p += txtlen + 1; + *remain -= txtlen + 1; + } + else { + + if (txtlen + copied + parts > datalen) { + /* Incorrect datalen reported ! */ + rdns_err ("incorrect txtlen (%d) > datalen (%d) reported; domain %s", + (txtlen + copied + parts), datalen, + rep->requested_name); + return -1; + } + + /* Reported equal to the actual data copied */ + break; + } + } + *(elt->content.txt.data + copied) = '\0'; + parsed = true; + elt->type = RDNS_REQUEST_TXT; + } + else { + rdns_info ("stripped data in TXT record (%d bytes available, %d requested); " + "domain %s", (int)*remain, (int)datalen, rep->requested_name); + return -1; + } + break; + case DNS_T_SRV: + if (p - *pos > (int)(*remain - sizeof (uint16_t) * 3)) { + rdns_info ("stripped dns reply while reading SRV record; domain %s", rep->requested_name); + return -1; + } + GET16 (elt->content.srv.priority); + GET16 (elt->content.srv.weight); + GET16 (elt->content.srv.port); + if (! rdns_parse_labels (resolver, in, &elt->content.srv.target, + &p, rep, remain, true)) { + rdns_info ("invalid labels in SRV record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_TLSA: + if (p - *pos > (int)(*remain - sizeof (uint8_t) * 3) || datalen <= 3) { + rdns_info ("stripped dns reply while reading TLSA record; domain %s", rep->requested_name); + return -1; + } + + if (datalen > *remain) { + rdns_info ("too large datalen; domain %s", rep->requested_name); + return -1; + } + + GET8 (elt->content.tlsa.usage); + GET8 (elt->content.tlsa.selector); + GET8 (elt->content.tlsa.match_type); + datalen -= 3; + + elt->content.tlsa.data = malloc (datalen); + if (elt->content.tlsa.data == NULL) { + rdns_err ("failed to allocate %d bytes for TLSA record; domain %s", + (int)datalen + 1, rep->requested_name); + return -1; + } + + elt->content.tlsa.datalen = datalen; + memcpy (elt->content.tlsa.data, p, datalen); + p += datalen; + *remain -= datalen; + parsed = true; + break; + case DNS_T_CNAME: + if (! rdns_parse_labels (resolver, in, &elt->content.cname.name, &p, + rep, remain, true)) { + rdns_info ("invalid labels in CNAME record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + default: + rdns_info ("unexpected RR type: %d; domain %s", type, rep->requested_name); + p += datalen; + *remain -= datalen; + break; + } + *pos = p; + + if (parsed) { + elt->ttl = ttl; + return 1; + } + return 0; +} |