diff options
-rw-r--r-- | RELNOTES | 6 | ||||
-rw-r--r-- | common/ns_name.c | 142 | ||||
-rw-r--r-- | common/options.c | 23 | ||||
-rw-r--r-- | common/tests/ns_name_test.c | 64 | ||||
-rw-r--r-- | common/tree.c | 172 | ||||
-rw-r--r-- | includes/dhcpd.h | 2 | ||||
-rw-r--r-- | includes/minires.h | 3 | ||||
-rw-r--r-- | includes/tree.h | 3 |
8 files changed, 414 insertions, 1 deletions
@@ -164,6 +164,12 @@ by Eric Young (eay@cryptsoft.com). corrected. [ISC-Bugs #37368] +- Corrected an issue which caused dhclient to incorrectly form the result when + prepending or appending to the IPv4 domain-search option,received from the + server, when either of the values being combined contain compressed + components. + [ISC-Bugs #20558] + Changes since 4.3.1b1 - Modify the linux and openwrt dhclient scripts to process information diff --git a/common/ns_name.c b/common/ns_name.c index 44b2495b..829fe146 100644 --- a/common/ns_name.c +++ b/common/ns_name.c @@ -645,3 +645,145 @@ dn_find(const u_char *domain, const u_char *msg, errno = ENOENT; return (-1); } + +/*! + * \brief Creates a string of comma-separated domain-names from a + * compressed list + * + * Produces a null-terminated string of comma-separated domain-names from + * a buffer containing a compressed list of domain-names. The names will + * be dotted and without enclosing quotes. For example: + * If a compressed list contains the follwoing two domain names: + * + * a. one.two.com + * b. three.four.com + * + * The compressed data will look like this: + * + * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68 + * 72 65 65 04 66 6f 75 72 c0 08 + * + * and will decompress into: + * + * one.two.com,three.four.com + * + * \param buf - buffer containing the compressed list of domain-names + * \param buflen - length of compressed list of domain-names + * \param dst_buf - buffer to receive the decompressed list + * \param dst_size - size of the destination buffer + * + * \return the length of the decompressed string when successful, -1 on + * error. + */ +int MRns_name_uncompress_list(const unsigned char* buf, int buflen, + char* dst_buf, size_t dst_size) +{ + const unsigned char* src = buf; + char* dst = dst_buf; + int consumed = 1; + int dst_remaining = dst_size; + int added_len = 0; + int first_pass = 1; + + if (!buf || buflen == 0 || *buf == 0x00) { + /* nothing to do */ + *dst = 0; + return (0); + } + + while ((consumed > 0) && (src < (buf + buflen))) + { + if (dst_remaining <= 0) { + errno = EMSGSIZE; + return (-1); + } + + if (!first_pass) { + *dst++ = ','; + *dst = '\0'; + dst_remaining--; + } + + consumed = MRns_name_uncompress(buf, buf + buflen, src, + dst, dst_remaining); + if (consumed < 0) { + return (-1); + } + + src += consumed; + added_len = strlen(dst); + dst_remaining -= added_len; + dst += added_len; + first_pass = 0; + } + *dst='\0'; + + /* return the length of the uncompressed list string */ + return (strlen(dst_buf)); +} + +/*! + * \brief Creates a compressed list from a string of comma-separated + * domain-names + * + * Produces a buffer containing a compressed data version of a list of + * domain-names extracted from a comma-separated string. Given a string + * containing: + * + * one.two.com,three.four.com + * + * It will compress this into: + * + * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68 + * 72 65 65 04 66 6f 75 72 c0 08 + * + * \param buf - buffer containing the uncompressed string of domain-names + * \param buflen - length of uncompressed string of domain-names + * \param compbuf - buffer to receive the compressed list + * \param compbuf_size - size of the compression buffer + * + * \return the length of the compressed data when successful, -1 on error. + */ +int MRns_name_compress_list(const char* buf, int buflen, + unsigned char* compbuf, size_t compbuf_size) +{ + char cur_name[NS_MAXCDNAME]; + const unsigned char *dnptrs[256], **lastdnptr; + const char* src; + const char* src_end; + unsigned clen = 0; + int result = 0; + + memset(compbuf, 0, compbuf_size); + memset(dnptrs, 0, sizeof(dnptrs)); + dnptrs[0] = compbuf; + lastdnptr = &dnptrs[255]; + + src = buf; + src_end = buf + buflen; + while (src < src_end) { + char *comma = strchr(src, ','); + int copylen = ((comma != NULL) ? comma - src : strlen(src)); + if (copylen > (sizeof(cur_name) - 1)) { + errno = EMSGSIZE; + return (-1); + } + + memcpy(cur_name, src, copylen); + cur_name[copylen] = '\0'; + src += copylen + 1; + + result = MRns_name_compress(cur_name, compbuf + clen, + compbuf_size - clen, + dnptrs, lastdnptr); + + if (result < 0) { + return (-1); + } + + clen += result; + } + + /* return size of compressed list */ + return(clen); +} diff --git a/common/options.c b/common/options.c index 31506719..725d3bef 100644 --- a/common/options.c +++ b/common/options.c @@ -2267,6 +2267,29 @@ void set_option (universe, options, option, op) break; } } + + /* If we are trying to combine compressed domain-lists then + * we need to change the expression opcode. The lists must + * be decompressed, combined, and then recompressed to work + * correctly. You cannot simply add two compressed lists + * together. */ + switch (((memcmp(option->option->format, "Dc", 2) == 0) + + (memcmp(oc->option->format, "Dc", 2) == 0))) { + case 1: + /* Only one is "Dc", this won't work + * Not sure if you make this occur, but just + * in case. */ + log_error ("Both options must be Dc format"); + return; + case 2: + /* Both are "Dc", change the code */ + noc->expression->op = expr_concat_dclist; + break; + default: + /* Neither are "Dc", so as you were */ + break; + } + option_reference(&(noc->option), oc->option, MDL); save_option (universe, options, noc); option_cache_dereference (&noc, MDL); diff --git a/common/tests/ns_name_test.c b/common/tests/ns_name_test.c new file mode 100644 index 00000000..adbae602 --- /dev/null +++ b/common/tests/ns_name_test.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014 by Internet Systems Consortium, Inc. ("ISC") + * + * Tests the newly added functions: MRns_name_compress_list and + * MRns_name_uncompress_list. These two functions rely on most of + * the other functions in ns_name.c. If these tests pass, then the + * majority of those functions work. + * + * This is not exhaustive test of these functions, much more could be + * done. + */ +#include "config.h" +#include <atf-c.h> +#include "dhcpd.h" + +ATF_TC(MRns_name_list_funcs); + +ATF_TC_HEAD(MRns_name_list_funcs, tc) { + atf_tc_set_md_var(tc, "descr", "MRns_name list funcs test, " + "compress from text, decompress to text"); +} + +ATF_TC_BODY(MRns_name_list_funcs, tc) { + + const char text_list[] = "one.two.com,three.two.com,four.two.com"; + unsigned char comp_list[] = { + 0x03,0x6f,0x6e,0x65,0x03,0x74,0x77,0x6f,0x03,0x63,0x6f, + 0x6d,0x00,0x05,0x74,0x68,0x72,0x65,0x65,0xc0,0x04,0x04, + 0x66,0x6f,0x75,0x72,0xc0,0x04}; + unsigned char compbuf[sizeof(comp_list)]; + char textbuf[sizeof(text_list)]; + int ret; + + memset(compbuf, 0x00, sizeof(compbuf)); + + /* Compress the reference text list */ + ret = MRns_name_compress_list(text_list, sizeof(text_list), + compbuf, sizeof(compbuf)); + + /* Verify compressed length is correct */ + ATF_REQUIRE_MSG((ret == sizeof(compbuf)), "compressed len %d wrong", ret); + + /* Verify compressed content is correct */ + ATF_REQUIRE_MSG((memcmp(comp_list, compbuf, sizeof(compbuf)) == 0), + "compressed buffer content wrong"); + + /* Decompress the new compressed list */ + ret = MRns_name_uncompress_list(compbuf, ret, textbuf, sizeof(textbuf)); + + /* Verify decompressed length is correct */ + ATF_REQUIRE_MSG((ret == strlen(text_list)), + "uncompressed len %d wrong", ret); + + /* Verify decompressed content is correct */ + ATF_REQUIRE_MSG((memcmp(textbuf, text_list, sizeof(textbuf)) == 0), + "uncompressed buffer content wrong"); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, MRns_name_list_funcs); + + return (atf_no_error()); +} diff --git a/common/tree.c b/common/tree.c index bf5f0ec3..03089b21 100644 --- a/common/tree.c +++ b/common/tree.c @@ -1067,6 +1067,7 @@ int evaluate_boolean_expression (result, packet, lease, client_state, case expr_sname: case expr_gethostname: case expr_v6relay: + case expr_concat_dclist: log_error ("Data opcode in evaluate_boolean_expression: %d", expr -> op); return 0; @@ -2113,6 +2114,49 @@ int evaluate_data_expression (result, packet, lease, client_state, #endif return (s1); + case expr_concat_dclist: { + /* Operands are compressed domain-name lists ("Dc" format) + * Fetch both compressed lists then call concat_dclists which + * combines them into a single compressed list. */ + memset(&data, 0, sizeof data); + int outcome = 0; + s0 = evaluate_data_expression(&data, packet, lease, + client_state, + in_options, cfg_options, scope, + expr->data.concat[0], MDL); + + memset (&other, 0, sizeof other); + s1 = evaluate_data_expression (&other, packet, lease, + client_state, + in_options, cfg_options, scope, + expr->data.concat[1], MDL); + + if (s0 && s1) { + outcome = concat_dclists(result, &data, &other); + if (outcome == 0) { + log_error ("data: concat_dclist failed"); + } + } + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: concat_dclists (%s, %s) = %s", + (s0 ? print_hex_1(data.len, data.data, data.len) + : "NULL"), + (s1 ? print_hex_2(other.len, other.data, other.len) + : "NULL"), + (((s0 && s1) && result->len > 0) + ? print_hex_3 (result->len, result->data, result->len) + : "NULL")); +#endif + if (s0) + data_string_forget (&data, MDL); + + if (s1) + data_string_forget (&other, MDL); + + return (outcome); + } /* expr_concat_dclist */ + case expr_check: case expr_equal: case expr_not_equal: @@ -2164,6 +2208,7 @@ int evaluate_data_expression (result, packet, lease, client_state, case expr_arg: break; + } log_error ("Bogus opcode in evaluate_data_expression: %d", expr -> op); @@ -3116,6 +3161,7 @@ static int op_val (op) case expr_client_state: case expr_gethostname: case expr_v6relay: + case expr_concat_dclist: return 100; case expr_equal: @@ -3209,6 +3255,7 @@ enum expression_context op_context (op) case expr_function: case expr_gethostname: case expr_v6relay: + case expr_concat_dclist: return context_any; case expr_equal: @@ -4087,4 +4134,129 @@ int unset (struct binding_scope *scope, const char *name) return 0; } +/*! + * \brief Adds two Dc-formatted lists into a single Dc-formatted list + * + * Given two data_strings containing compressed lists, it constructs a + * third data_string containing a single compressed list: + * + * 1. Decompressing the first list into a buffer + * 2. Decompressing the second list onto the end of the buffer + * 3. Compressing the buffer into the result + * + * If either list is empty, the result will be the equal to the compressed + * content of the non-empty list. If both lists are empty, the result will + * be an "empty" list: a 1 byte buffer containing 0x00. + * + * It relies on two functions to decompress and compress: + * + * - MRns_name_uncompress_list() - produces a null-terminated string of + * comma-separated domain-names from a buffer containing "Dc" formatted + * data + * + * - MRns_name_compress_list() - produces a buffer containing "Dc" formatted + * data from a null-terminated string containing comma-separated domain-names + * + * \param result data_string which will contain the combined list + * in Dc format + * \param list1 data_string containing first Dc formatted list + * \param list2 data_string containing second Dc formatted list + * \return 0 if there is an error, the length of the new list when successful + */ +int concat_dclists (struct data_string* result, + struct data_string* list1, + struct data_string* list2) +{ + char uncompbuf[32*NS_MAXCDNAME]; + char *uncomp = uncompbuf; + int uncomp_len = 0; + int compbuf_max = 0; + int list_len = 0; + int i; + + /* If not empty, uncompress first list into the uncompressed buffer */ + if ((list1->data) && (list1->len)) { + list_len = MRns_name_uncompress_list(list1->data, + list1->len, uncomp, + sizeof(uncompbuf)); + if (list_len < 0) { + log_error ("concat_dclists:" + " error decompressing domain list 1"); + return (0); + } + + uncomp_len = list_len; + uncomp += list_len; + } + + /* If not empty, uncompress second list into the uncompressed buffer */ + if ((list2->data) && (list2->len)) { + /* If first list wasn't empty, add a comma */ + if (uncomp_len > 0) { + *uncomp++ = ','; + uncomp_len++; + } + + list_len = MRns_name_uncompress_list(list2->data, list2->len, + uncomp, (sizeof(uncompbuf) + - uncomp_len)); + if (list_len < 0) { + log_error ("concat_dclists:" + " error decompressing domain list 2"); + return (0); + } + + uncomp_len += list_len; + uncomp += list_len; + } + + /* If both lists were empty, return an "empty" result */ + if (uncomp_len == 0) { + if (!buffer_allocate (&result->buffer, 1, MDL)) { + log_error ("concat_dclists: empty list allocate fail"); + result->len = 0; + return (0); + } + + result->len = 1; + result->data = result->buffer->data; + return (1); + } + + /* Estimate the buffer size needed for decompression. The largest + * decompression would if one where there are no repeated portions, + * (i.e. no compressions). Therefore that size should be the + * decompressed string length + 2 for each comma + a final null. Each + * dot gets replaced with a length byte and is accounted for in string + * length. Mininum length is * uncomp_len + 3. */ + compbuf_max = uncomp_len + 3; + uncomp = uncompbuf; + for (i = 0; i < uncomp_len; i++) + if (*uncomp++ == ',') + compbuf_max += 2; + + /* Allocate compression buffer based on estimated max */ + if (!buffer_allocate (&result->buffer, compbuf_max, MDL)) { + log_error ("concat_dclists: No memory for result"); + result->len = 0; + return (0); + } + + /* Compress the combined list into result */ + list_len = MRns_name_compress_list(uncompbuf, uncomp_len, + result->buffer->data, compbuf_max); + + if (list_len <= 0) { + log_error ("concat_dlists: error compressing result"); + data_string_forget(result, MDL); + result->len = 0; + return (0); + } + + /* Update result length to actual size */ + result->len = list_len; + result->data = result->buffer->data; + return (list_len); +} + /* vim: set tabstop=8: */ diff --git a/includes/dhcpd.h b/includes/dhcpd.h index 5bc1802e..625a0bd6 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -2218,6 +2218,8 @@ int find_bound_string (struct data_string *, struct binding_scope *, const char *); int unset (struct binding_scope *, const char *); int data_string_sprintfa(struct data_string *ds, const char *fmt, ...); +int concat_dclists (struct data_string *, struct data_string *, + struct data_string *); /* dhcp.c */ extern int outstanding_pings; diff --git a/includes/minires.h b/includes/minires.h index 3555bbb0..2e6126c9 100644 --- a/includes/minires.h +++ b/includes/minires.h @@ -1,4 +1,5 @@ /* + * Copyright (c) 2014 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 2004,2007-2009 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 2001-2003 by Internet Software Consortium * @@ -39,5 +40,7 @@ int MRns_name_pack (const unsigned char *, unsigned char *, unsigned, const unsigned char **, const unsigned char **); int MRns_name_ntop(const unsigned char *, char *, size_t); int MRns_name_pton(const char *, u_char *, size_t); +int MRns_name_uncompress_list(const unsigned char*, int buflen, char*, size_t); +int MRns_name_compress_list(const char*, int buflen, unsigned char*, size_t); #endif /* MINIRES_H */ diff --git a/includes/tree.h b/includes/tree.h index 4f28bb93..8a285a53 100644 --- a/includes/tree.h +++ b/includes/tree.h @@ -191,7 +191,8 @@ enum expr_op { expr_regex_match, expr_iregex_match, expr_gethostname, - expr_v6relay + expr_v6relay, + expr_concat_dclist }; struct expression { |