X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/1ddb1855402d48ad735e46abaf0d662e45600ecd..4687a69c269ee3f2a7f0625e0147a503fd9d3d0b:/src/src/lookups/dnsdb.c diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c index b1bd48bfc..af1ad9dab 100644 --- a/src/src/lookups/dnsdb.c +++ b/src/src/lookups/dnsdb.c @@ -2,9 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2023 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ #include "../exim.h" #include "lf_functions.h" @@ -135,15 +136,12 @@ dnsdb_find(void * handle, const uschar * filename, const uschar * keystring, { int rc; int sep = 0; -int defer_mode = PASS; -int dnssec_mode = PASS; -int save_retrans = dns_retrans; -int save_retry = dns_retry; +int defer_mode = PASS, dnssec_mode = PASS; +int save_retrans = dns_retrans, save_retry = dns_retry; int type; int failrc = FAIL; -const uschar *outsep = CUS"\n"; -const uschar *outsep2 = NULL; -uschar *equals, *domain, *found; +const uschar * outsep = CUS"\n", * outsep2 = NULL; +uschar * equals, * domain, * found; dns_answer * dnsa = store_get_dns_answer(); dns_scan dnss; @@ -156,8 +154,7 @@ gstring * yield = string_get(256); /* If the string starts with '>' we change the output separator. If it's followed by ';' or ',' we set the TXT output separator. */ -while (isspace(*keystring)) keystring++; -if (*keystring == '>') +if (Uskip_whitespace(&keystring) == '>') { outsep = keystring + 1; keystring += 2; @@ -171,7 +168,7 @@ if (*keystring == '>') outsep2 = US""; keystring++; } - while (isspace(*keystring)) keystring++; + Uskip_whitespace(&keystring); } /* Check for a modifier keyword. */ @@ -236,14 +233,14 @@ for (;;) else break; - while (isspace(*keystring)) keystring++; + Uskip_whitespace(&keystring); if (*keystring++ != ',') { *errmsg = US"dnsdb modifier syntax error"; rc = DEFER; goto out; } - while (isspace(*keystring)) keystring++; + Uskip_whitespace(&keystring); } /* Figure out the "type" value if it is not T_TXT. @@ -274,7 +271,7 @@ if ((equals = Ustrchr(keystring, '=')) != NULL) } keystring = equals + 1; - while (isspace(*keystring)) keystring++; + Uskip_whitespace(&keystring); } /* Initialize the resolver in case this is the first time it has been used. */ @@ -384,57 +381,60 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) if (type == T_A || type == T_AAAA || type == T_ADDRESSES) { for (dns_address * da = dns_address_from_rr(dnsa, rr); da; da = da->next) - { - if (yield->ptr) yield = string_catn(yield, outsep, 1); - yield = string_cat(yield, da->address); - } + yield = string_append_listele(yield, *outsep, da->address); continue; } /* Other kinds of record just have one piece of data each, but there may be - several of them, of course. */ + several of them, of course. TXT & SPF can have data in multiple chunks. */ if (yield->ptr) yield = string_catn(yield, outsep, 1); if (type == T_TXT || type == T_SPF) - { - if (outsep2 == NULL) /* output only the first item of data */ - yield = string_catn(yield, US (rr->data+1), (rr->data)[0]); - else - { - /* output all items */ - int data_offset = 0; - while (data_offset < rr->size) - { - uschar chunk_len = (rr->data)[data_offset++]; - if (outsep2[0] != '\0' && data_offset != 1) - yield = string_catn(yield, outsep2, 1); - yield = string_catn(yield, US ((rr->data)+data_offset), chunk_len); - data_offset += chunk_len; - } - } - } + for (unsigned data_offset = 0; data_offset + 1 < rr->size; ) + { + uschar chunk_len = (rr->data)[data_offset]; + int remain; + + if (outsep2 && *outsep2 && data_offset != 0) + yield = string_catn(yield, outsep2, 1); + + /* Apparently there are resolvers that do not check RRs before passing + them on, and glibc fails to do so. So every application must... + Check for chunk len exceeding RR */ + + remain = rr->size - ++data_offset; + if (chunk_len > remain) + chunk_len = remain; + yield = string_catn(yield, US ((rr->data) + data_offset), chunk_len); + data_offset += chunk_len; + + if (!outsep2) break; /* output only the first chunk of the RR */ + } else if (type == T_TLSA) - { - uint8_t usage, selector, matching_type; - uint16_t payload_length; - uschar s[MAX_TLSA_EXPANDED_SIZE]; - uschar * sp = s; - uschar * p = US rr->data; + if (rr->size < 3) + continue; + else + { + uint8_t usage, selector, matching_type; + uint16_t payload_length; + uschar s[MAX_TLSA_EXPANDED_SIZE]; + uschar * sp = s; + uschar * p = US rr->data; + + usage = *p++; + selector = *p++; + matching_type = *p++; + /* What's left after removing the first 3 bytes above */ + payload_length = rr->size - 3; + sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2, + selector, *outsep2, matching_type, *outsep2); + /* Now append the cert/identifier, one hex char at a time */ + while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4)) + sp += sprintf(CS sp, "%02x", *p++); - usage = *p++; - selector = *p++; - matching_type = *p++; - /* What's left after removing the first 3 bytes above */ - payload_length = rr->size - 3; - sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2, - selector, *outsep2, matching_type, *outsep2); - /* Now append the cert/identifier, one hex char at a time */ - while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4)) - sp += sprintf(CS sp, "%02x", *p++); - - yield = string_cat(yield, s); - } + yield = string_cat(yield, s); + } else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */ { int priority, weight, port; @@ -444,17 +444,20 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) switch (type) { case T_MXH: + if (rr_bad_size(rr, sizeof(uint16_t))) continue; /* mxh ignores the priority number and includes only the hostnames */ GETSHORT(priority, p); break; case T_MX: + if (rr_bad_size(rr, sizeof(uint16_t))) continue; GETSHORT(priority, p); sprintf(CS s, "%d%c", priority, *outsep2); yield = string_cat(yield, s); break; case T_SRV: + if (rr_bad_size(rr, 3*sizeof(uint16_t))) continue; GETSHORT(priority, p); GETSHORT(weight, p); GETSHORT(port, p); @@ -464,6 +467,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) break; case T_CSA: + if (rr_bad_size(rr, 3*sizeof(uint16_t))) continue; /* See acl_verify_csa() for more comments about CSA. */ GETSHORT(priority, p); GETSHORT(weight, p); @@ -514,7 +518,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) if (type == T_SOA && outsep2 != NULL) { - unsigned long serial, refresh, retry, expire, minimum; + unsigned long serial = 0, refresh = 0, retry = 0, expire = 0, minimum = 0; p += rc; yield = string_catn(yield, outsep2, 1); @@ -530,8 +534,11 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) else yield = string_cat(yield, s); p += rc; - GETLONG(serial, p); GETLONG(refresh, p); - GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); + if (!rr_bad_increment(rr, p, 5 * sizeof(u_int32_t))) + { + GETLONG(serial, p); GETLONG(refresh, p); + GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); + } sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu", *outsep2, serial, *outsep2, refresh, *outsep2, retry, *outsep2, expire, *outsep2, minimum);