diff --git a/Knot.files b/Knot.files index 001186d39adc8cb29de482ee672b122c1f81453e..39d592f8c56559084d4acb9079a9e013b1aafadc 100644 --- a/Knot.files +++ b/Knot.files @@ -251,3 +251,7 @@ src/libknot/tsig.h src/libknot/tsig.c src/libknot/tsig-op.c src/libknot/tsig-op.h +src/libknot/util/conv.c +src/libknot/util/conv.h +src/knot/zone/semantic-check.c +src/knot/zone/semantic-check.h diff --git a/src/Makefile.am b/src/Makefile.am index 7c86e3fa67989dd6cf75160486674b5239393d64..8134c75db2e2d09f4c00be5a58e4ba09c5cb18b7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -294,6 +294,8 @@ libknotd_la_SOURCES = \ knot/server/zones.h \ knot/zone/zone-load.c \ knot/zone/zone-load.h \ + knot/zone/semantic-check.c \ + knot/zone/semantic-check.h \ knot/zone/zone-dump.c \ knot/zone/zone-dump-text.c \ knot/zone/zone-dump-text.h \ diff --git a/src/knot/zone/semantic-check.c b/src/knot/zone/semantic-check.c new file mode 100644 index 0000000000000000000000000000000000000000..3ca38a249bdb16acace4ab7f24ec5b69c30a8c15 --- /dev/null +++ b/src/knot/zone/semantic-check.c @@ -0,0 +1,1244 @@ +#include <stdlib.h> +#include <stdint.h> +#include <arpa/inet.h> + +#include "knot/common.h" +#include "knot/other/error.h" +#include "libknot/libknot.h" +#include "common/base32hex.h" +#include "common/crc.h" + +#include "semantic-check.h" + +static const uint MAX_CNAME_CYCLE_DEPTH = 15; + +err_handler_t *handler_new(char log_cname, char log_glue, + char log_rrsigs, char log_nsec, + char log_nsec3) +{ + err_handler_t *handler = malloc(sizeof(err_handler_t)); + CHECK_ALLOC_LOG(handler, NULL); + + /* It should be initialized, but to be safe */ + memset(handler->errors, 0, sizeof(uint) * (-ZC_ERR_ALLOC + 1)); + + handler->options.log_cname = log_cname; + handler->options.log_glue = log_glue; + handler->options.log_rrsigs = log_rrsigs; + handler->options.log_nsec = log_nsec; + handler->options.log_nsec3 = log_nsec3; + + return handler; +} + +/*! + * \brief Prints error message with node information. + * + * \note If \a node is NULL, only total number of errors is printed. + * + * \param handler Error handler. + * \param node Node with semantic error in it. + * \param error Type of error. + */ +static void log_error_from_node(err_handler_t *handler, + const knot_node_t *node, + uint error) +{ + if (node != NULL) { + char *name = + knot_dname_to_str(knot_node_owner(node)); + fprintf(stderr, "Semantic warning in node: %s: ", name); + fprintf(stderr, "%s", error_messages[-error]); + free(name); + } else { + fprintf(stderr, "Total number of warnings is: %d for error: %s", + handler->errors[-error], + error_messages[-error]); + } +} + +int err_handler_handle_error(err_handler_t *handler, + const knot_node_t *node, + uint error) +{ + assert(handler && node); + if ((error != 0) && + (error > ZC_ERR_GLUE_GENERAL_ERROR)) { + return ZC_ERR_UNKNOWN; + } + + if (error == ZC_ERR_ALLOC) { + ERR_ALLOC_FAILED; + return ZC_ERR_ALLOC; + } + + /* missing SOA can only occur once, so there + * needn't to be an option for it */ + + if ((error != 0) && + (error < ZC_ERR_GENERIC_GENERAL_ERROR)) { + /* The two errors before SOA were handled */ + log_error_from_node(handler, node, error); + + } else if ((error < ZC_ERR_RRSIG_GENERAL_ERROR) && + ((handler->errors[-error] == 0) || + (handler->options.log_rrsigs))) { + + log_error_from_node(handler, node, error); + + } else if ((error > ZC_ERR_RRSIG_GENERAL_ERROR) && + (error < ZC_ERR_NSEC_GENERAL_ERROR) && + ((handler->errors[-error] == 0) || + (handler->options.log_nsec))) { + + log_error_from_node(handler, node, error); + + } else if ((error > ZC_ERR_NSEC_GENERAL_ERROR) && + (error < ZC_ERR_NSEC3_GENERAL_ERROR) && + ((handler->errors[-error] == 0) || + (handler->options.log_nsec3))) { + + log_error_from_node(handler, node, error); + + } else if ((error > ZC_ERR_NSEC3_GENERAL_ERROR) && + (error < ZC_ERR_CNAME_GENERAL_ERROR) && + ((handler->errors[-error] == 0) || + (handler->options.log_cname))) { + + log_error_from_node(handler, node, error); + + } else if ((error > ZC_ERR_CNAME_GENERAL_ERROR) && + (error < ZC_ERR_GLUE_GENERAL_ERROR) && + handler->options.log_glue) { + + log_error_from_node(handler, node, error); + + } + + handler->errors[-error]++; + + return KNOT_EOK; +} + +void err_handler_log_all(err_handler_t *handler) +{ + if (handler == NULL) { + return; + } + + for (int i = ZC_ERR_ALLOC; i < ZC_ERR_GLUE_GENERAL_ERROR; i++) { + if (handler->errors[-i] > 0) { + log_error_from_node(handler, NULL, i); + } + } +} + + +/*! + * \brief Semantic check - CNAME cycles. Uses constant value with maximum + * allowed CNAME chain depth. + * + * \param zone Zone containing the RRSet. + * \param rrset RRSet to be tested. + * + * \retval KNOT_EOK when there is no cycle. + * \retval ZC_ERR_CNAME_CYCLE when cycle is present. + */ +static int check_cname_cycles_in_zone(knot_zone_contents_t *zone, + const knot_rrset_t *rrset) +{ + const knot_rrset_t *next_rrset = rrset; + assert(rrset); + const knot_rdata_t *tmp_rdata = knot_rrset_rdata(next_rrset); + const knot_node_t *next_node = NULL; + + uint i = 0; + + assert(tmp_rdata); + + const knot_dname_t *next_dname = + knot_rdata_cname_name(tmp_rdata); + + assert(next_dname); + + while (i < MAX_CNAME_CYCLE_DEPTH && next_dname != NULL) { + next_node = knot_zone_contents_get_node(zone, next_dname); + if (next_node == NULL) { + next_node = + knot_zone_contents_get_nsec3_node(zone, next_dname); + } + + if (next_node != NULL) { + next_rrset = knot_node_rrset(next_node, + KNOT_RRTYPE_CNAME); + if (next_rrset != NULL) { + next_dname = + knot_rdata_cname_name(next_rrset->rdata); + } else { + next_node = NULL; + next_dname = NULL; + } + } else { + next_dname = NULL; + } + i++; + } + + /* even if the length is 0, i will be 1 */ + if (i >= MAX_CNAME_CYCLE_DEPTH) { + return ZC_ERR_CNAME_CYCLE; + } + + return KNOT_EOK; +} + +/*! + * \brief Return raw data from rdata item structure (without length). + * + * \param item Item to get rdata from. + * \return uint16_t * raw data without length. + */ +static inline uint16_t *rdata_item_data(const knot_rdata_item_t *item) +{ + return (uint16_t *)(item->raw_data + 1); +} + +/*! + * \brief Returns type covered field from RRSIG RRSet's rdata. + * + * \param rdata RRSIG rdata. + * \return uint16_t Type covered. + */ +uint16_t type_covered_from_rdata(const knot_rdata_t *rdata) +{ + return ntohs(*(uint16_t *) rdata_item_data(&(rdata->items[0]))); +} + +/*! + * \brief Check whether DNSKEY rdata are valid. + * + * \param rdata DNSKEY rdata to be checked. + * + * \retval KNOT_EOK when rdata are OK. + * \retval ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER when rdata are not OK. + */ +static int check_dnskey_rdata(const knot_rdata_t *rdata) +{ + /* check that Zone key bit it set - position 7 in net order */ + /*! \todo FIXME: endian? I swear I've fixed this already, it was 7 i guesss*/ + uint16_t mask = 1 << 8; //0b0000000100000000; + + uint16_t flags = + knot_wire_read_u16((uint8_t *)rdata_item_data + (knot_rdata_item(rdata, 0))); + + if (flags & mask) { + return KNOT_EOK; + } else { + /* This error does not exactly fit, but it's better + * than a new one */ + return ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER; + } +} + +/*! + * \brief Calculates keytag for RSA/SHA algorithm. + * + * \param key Key wireformat. + * \param keysize Wireformat size. + * + * \return uint16_t Calculated keytag. + */ +static uint16_t keytag_1(uint8_t *key, uint16_t keysize) +{ + uint16_t ac = 0; + if (keysize > 4) { + memmove(&ac, key + keysize - 3, 2); + } + + ac = ntohs(ac); + return ac; +} + +/*! + * \brief Calculates keytag from key wire. + * + * \param key Key wireformat. + * \param keysize Wireformat size. + * + * \return uint16_t Calculated keytag. + */ +static uint16_t keytag(uint8_t *key, uint16_t keysize ) +{ + uint32_t ac = 0; /* assumed to be 32 bits or larger */ + + /* algorithm RSA/SHA */ + if (key[3] == 1) { + return keytag_1(key, keysize); + } else { + for(int i = 0; i < keysize; i++) { + ac += (i & 1) ? key[i] : key[i] << 8; + } + + ac += (ac >> 16) & 0xFFFF; + return (uint16_t)ac & 0xFFFF; + } +} + +/*! + * \brief Returns size of raw data item. + * + * \param item Raw data item. + * + * \return uint16_t Size of raw data item. + */ +static inline uint16_t rdata_item_size(const knot_rdata_item_t *item) +{ + return item->raw_data[0]; +} + +/*! + * \brief Converts DNSKEY rdata to wireformat. + * + * \param rdata DNSKEY rdata to be converted. + * \param wire Created wire. + * \param size Size of created wire. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ENOMEM on memory error. + */ +static int dnskey_to_wire(const knot_rdata_t *rdata, uint8_t **wire, + uint *size) +{ + assert(*wire == NULL); + /* flags + algorithm + protocol + keysize */ + *size = 2 + 1 + 1 + knot_rdata_item(rdata, 3)->raw_data[0]; + *wire = malloc(sizeof(uint8_t) * *size); + CHECK_ALLOC_LOG(*wire, KNOT_ENOMEM); + + /* copy the wire octet by octet */ + + /* TODO check if we really have that many items */ + if (rdata->count < 4) { + return KNOT_ERROR; + } + + (*wire)[0] = ((uint8_t *)(knot_rdata_item(rdata, 0)->raw_data))[2]; + (*wire)[1] = ((uint8_t *)(knot_rdata_item(rdata, 0)->raw_data))[3]; + + (*wire)[2] = ((uint8_t *)(knot_rdata_item(rdata, 1)->raw_data))[2]; + (*wire)[3] = ((uint8_t *)(knot_rdata_item(rdata, 2)->raw_data))[2]; + + memcpy(*wire + 4, knot_rdata_item(rdata, 3)->raw_data + 1, + knot_rdata_item(rdata, 3)->raw_data[0]); + + return KNOT_EOK; +} + +/*! + * \brief Semantic check - RRSIG rdata. + * + * \param rdata_rrsig RRSIG rdata to be checked. + * \param rrset RRSet containing the rdata. + * \param dnskey_rrset RRSet containing zone's DNSKEY + * + * \retval KNOT_EOK if rdata are OK. + * + * \return Appropriate error code if error was found. + */ +static int check_rrsig_rdata(const knot_rdata_t *rdata_rrsig, + const knot_rrset_t *rrset, + const knot_rrset_t *dnskey_rrset) +{ + if (rdata_rrsig == NULL) { + return ZC_ERR_RRSIG_NO_RRSIG; + } + + if (type_covered_from_rdata(rdata_rrsig) != + knot_rrset_type(rrset)) { + /* zoneparser would not let this happen + * but to be on the safe side + */ + return ZC_ERR_RRSIG_RDATA_TYPE_COVERED; + } + + /* label number at the 2nd index should be same as owner's */ + uint16_t *raw_data = + rdata_item_data(knot_rdata_item(rdata_rrsig, 2)); + + uint8_t labels_rdata = ((uint8_t *)raw_data)[0]; + + int tmp = knot_dname_label_count(knot_rrset_owner(rrset)) - + labels_rdata; + + if (tmp != 0) { + /* if name has wildcard, label must not be included */ + if (!knot_dname_is_wildcard(knot_rrset_owner(rrset))) { + return ZC_ERR_RRSIG_RDATA_LABELS; + } else { + if (abs(tmp) != 1) { + return ZC_ERR_RRSIG_RDATA_LABELS; + } + } + } + + /* check original TTL */ + uint32_t original_ttl = + knot_wire_read_u32((uint8_t *)rdata_item_data( + knot_rdata_item(rdata_rrsig, 3))); + + if (original_ttl != knot_rrset_ttl(rrset)) { + return ZC_ERR_RRSIG_RDATA_TTL; + } + + /* signer's name is same as in the zone apex */ + knot_dname_t *signer_name = + knot_rdata_item(rdata_rrsig, 7)->dname; + + /* dnskey is in the apex node */ + if (knot_dname_compare(signer_name, + knot_rrset_owner(dnskey_rrset)) != 0) { + return ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER; + } + + /* Compare algorithm, key tag and signer's name with DNSKEY rrset + * one of the records has to match. Signer name has been checked + * before */ + char match = 0; + const knot_rdata_t *tmp_dnskey_rdata = + knot_rrset_rdata(dnskey_rrset); + do { + uint8_t alg = + ((uint8_t *)(knot_rdata_item(rdata_rrsig, 1)->raw_data))[2]; + uint8_t alg_dnskey = + ((uint8_t *)(knot_rdata_item(tmp_dnskey_rdata, + 2)->raw_data))[2]; + + raw_data = rdata_item_data(knot_rdata_item(rdata_rrsig, 6)); + uint16_t key_tag_rrsig = + knot_wire_read_u16((uint8_t *)raw_data); + +/* raw_data = + rdata_item_data(knot_rdata_item( + tmp_dnskey_rdata, 3)); + + uint16_t raw_length = rdata_item_size(knot_rdata_item( + tmp_dnskey_rdata, 3)); */ + + uint8_t *dnskey_wire = NULL; + uint dnskey_wire_size = 0; + + int ret = 0; + if ((ret = dnskey_to_wire(tmp_dnskey_rdata, &dnskey_wire, + &dnskey_wire_size)) != KNOT_EOK) { + return ret; + } + + uint16_t key_tag_dnskey = + keytag(dnskey_wire, dnskey_wire_size); + + free(dnskey_wire); + + match = (alg == alg_dnskey) && + (key_tag_rrsig == key_tag_dnskey) && + !check_dnskey_rdata(tmp_dnskey_rdata); + + } while (!match && + ((tmp_dnskey_rdata = + knot_rrset_rdata_next(dnskey_rrset, + tmp_dnskey_rdata)) + != NULL)); + + if (!match) { + return ZC_ERR_RRSIG_RDATA_SIGNED_WRONG; + } + + return KNOT_EOK; +} + +/*! + * \brief Semantic check - RRSet's RRSIG. + * + * \param rrset RRSet containing RRSIG. + * \param dnskey_rrset + * \param nsec3 NSEC3 active. + * + * \retval KNOT_EOK on success. + * + * \return Appropriate error code if error was found. + */ +static int check_rrsig_in_rrset(const knot_rrset_t *rrset, + const knot_rrset_t *dnskey_rrset, + char nsec3) +{ + assert(dnskey_rrset && rrset); + + const knot_rrset_t *rrsigs = knot_rrset_rrsigs(rrset); + + if (rrsigs == NULL) { + return ZC_ERR_RRSIG_NO_RRSIG; + } + + /* signed rrsig - nonsense */ + if (knot_rrset_rrsigs(rrsigs) != NULL) { + return ZC_ERR_RRSIG_SIGNED; + } + + /* Different owner, class, ttl */ + + if (knot_dname_compare(knot_rrset_owner(rrset), + knot_rrset_owner(rrsigs)) != 0) { + return ZC_ERR_RRSIG_OWNER; + } + + if (knot_rrset_class(rrset) != knot_rrset_class(rrsigs)) { + return ZC_ERR_RRSIG_CLASS; + } + + if (knot_rrset_ttl(rrset) != knot_rrset_ttl(rrset)) { + return ZC_ERR_RRSIG_TTL; + } + + /* Check whether all rrsets have their rrsigs */ + const knot_rdata_t *tmp_rdata = knot_rrset_rdata(rrset); + const knot_rdata_t *tmp_rrsig_rdata = knot_rrset_rdata(rrsigs); + + assert(tmp_rdata); + assert(tmp_rrsig_rdata); + int ret = 0; + char all_signed = tmp_rdata && tmp_rrsig_rdata; + do { + if ((ret = check_rrsig_rdata(tmp_rrsig_rdata, + rrset, + dnskey_rrset)) != 0) { + return ret; + } + + all_signed = tmp_rdata && tmp_rrsig_rdata; + } while (((tmp_rdata = knot_rrset_rdata_next(rrset, tmp_rdata)) + != NULL) && + ((tmp_rrsig_rdata = + knot_rrset_rdata_next(rrsigs, tmp_rrsig_rdata)) + != NULL)); + + if (!all_signed) { + return ZC_ERR_RRSIG_NOT_ALL; + } + + return KNOT_EOK; +} + +/*! + * \brief Returns bit on index from array in network order. Taken from NSD. + * + * \param bits Array in network order. + * \param index Index to return from array. + * + * \return int Bit on given index. + */ +static int get_bit(uint8_t *bits, size_t index) +{ + /* + * The bits are counted from left to right, so bit #0 is the + * leftmost bit. + */ + return bits[index / 8] & (1 << (7 - index % 8)); +} + +/*! + * \brief Converts NSEC bitmap to array of integers. (Inspired by NSD code) + * + * \param item Item containing the bitmap. + * \param array Array to be created. + * \param count Count of items in array. + * + * \retval KNOT_OK on success. + * \retval KNOT_NOMEM on memory error. + */ +static int rdata_nsec_to_type_array(const knot_rdata_item_t *item, + uint16_t **array, + uint *count) +{ + assert(*array == NULL); + + uint8_t *data = (uint8_t *)rdata_item_data(item); + + int increment = 0; + *count = 0; + + for (int i = 0; i < rdata_item_size(item); i += increment) { + increment = 0; + uint8_t window = data[i]; + increment++; + + uint8_t bitmap_size = data[i + increment]; + increment++; + + uint8_t *bitmap = + malloc(sizeof(uint8_t) * (bitmap_size)); + if (bitmap == NULL) { + ERR_ALLOC_FAILED; + free(*array); + return KNOT_ENOMEM; + } + + memcpy(bitmap, data + i + increment, + bitmap_size); + + increment += bitmap_size; + + for (int j = 0; j < bitmap_size * 8; j++) { + if (get_bit(bitmap, j)) { + (*count)++; + void *tmp = realloc(*array, + sizeof(uint16_t) * + *count); + if (tmp == NULL) { + ERR_ALLOC_FAILED; + free(bitmap); + free(*array); + return KNOT_ENOMEM; + } + *array = tmp; + (*array)[*count - 1] = j + window * 256; + } + } + free(bitmap); + } + + return KNOT_EOK; +} + +/* should write error, not return values !!! */ + +/*! + * \brief Semantic check - check node's NSEC node. + * + * \param zone Current zone. + * \param node Node to be checked. + * \param handler Error handler + * + * \retval KNOT_EOK if no error was found. + * + * \return Appropriate error code if error was found. + */ +static int check_nsec3_node_in_zone(knot_zone_contents_t *zone, knot_node_t *node, + err_handler_t *handler) +{ + assert(handler); + const knot_node_t *nsec3_node = knot_node_nsec3_node(node, 0); + + if (nsec3_node == NULL) { + /* I know it's probably not what RFCs say, but it will have to + * do for now. */ + if (knot_node_rrset(node, KNOT_RRTYPE_DS) != NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_UNSECURED_DELEGATION); + } else { + /* Unsecured delegation, check whether it is part of + * opt-out span */ + const knot_node_t *nsec3_previous; + const knot_node_t *nsec3_node; + + if (knot_zone_contents_find_nsec3_for_name(zone, + knot_node_owner(node), + &nsec3_node, + &nsec3_previous, 0) != 0) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_NOT_FOUND); + } + + if (nsec3_node == NULL) { + /* Probably should not ever happen */ + return ZC_ERR_NSEC3_NOT_FOUND; + } + + assert(nsec3_previous); + + const knot_rrset_t *previous_rrset = + knot_node_rrset(nsec3_previous, + KNOT_RRTYPE_NSEC3); + + assert(previous_rrset); + + /* check for Opt-Out flag */ + uint8_t flags = + ((uint8_t *)(previous_rrset->rdata->items[1].raw_data))[2]; + + uint8_t opt_out_mask = 1; + + if (!(flags & opt_out_mask)) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_UNSECURED_DELEGATION_OPT); + } + } + } + + const knot_rrset_t *nsec3_rrset = + knot_node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3); + + assert(nsec3_rrset); + + const knot_rrset_t *soa_rrset = + knot_node_rrset(knot_zone_contents_apex(zone), + KNOT_RRTYPE_SOA); + assert(soa_rrset); + + uint32_t minimum_ttl = + knot_wire_read_u32((uint8_t *) + rdata_item_data( + knot_rdata_item( + knot_rrset_rdata( + knot_node_rrset( + knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA)), 6))); + /* Are those getters even worth this? + * Now I have no idea what this code does. */ + + if (knot_rrset_ttl(nsec3_rrset) != minimum_ttl) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_RDATA_TTL); + } + + /* check that next dname is in the zone */ + uint8_t *next_dname_decoded = NULL; + size_t real_size = 0; + + if (((real_size = base32hex_encode_alloc(((char *) + rdata_item_data(&(nsec3_rrset->rdata->items[4]))) + 1, + rdata_item_size(&nsec3_rrset->rdata->items[4]) - 1, + (char **)&next_dname_decoded)) <= 0) || + (next_dname_decoded == NULL)) { + fprintf(stderr, "Could not encode base32 string!\n"); + return KNOT_ERROR; + } + + /* This is why we allocate maximum length of decoded string + 1 */ + memmove(next_dname_decoded + 1, next_dname_decoded, real_size); + next_dname_decoded[0] = real_size; + + /* Local allocation, will be discarded. */ + knot_dname_t *next_dname = + knot_dname_new_from_wire(next_dname_decoded, + real_size + 1, NULL); + CHECK_ALLOC_LOG(next_dname, KNOT_ENOMEM); + + free(next_dname_decoded); + + if (knot_dname_cat(next_dname, + knot_node_owner(knot_zone_contents_apex(zone))) == NULL) { + fprintf(stderr, "Could not concatenate dnames!\n"); + return KNOT_ERROR; + + } + + if (knot_zone_contents_find_nsec3_node(zone, next_dname) == NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_RDATA_CHAIN); + } + + /* Directly discard. */ + knot_dname_free(&next_dname); + + /* This is probably not sufficient, but again, it is covered in + * zone load time */ + + uint count; + uint16_t *array = NULL; + if (rdata_nsec_to_type_array( + knot_rdata_item( + knot_rrset_rdata(nsec3_rrset), 5), + &array, &count) != 0) { + err_handler_handle_error(handler, node, + ZC_ERR_ALLOC); + return KNOT_ERROR; + } + + uint16_t type = 0; + for (int j = 0; j < count; j++) { + /* test for each type's presence */ + type = array[j]; + if (type == KNOT_RRTYPE_RRSIG) { + continue; + } + if (knot_node_rrset(node, + type) == NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_RDATA_BITMAP); + break; +/* char *name = + knot_dname_to_str( + log_zone_error("Node %s does " + "not contain RRSet of type %s " + "but NSEC bitmap says " + "it does!\n", name, + knot_rrtype_to_string(type)); + free(name); */ + } + } + + free(array); + + return KNOT_EOK; +} + +/*! + * \brief Run semantic checks for node without DNSSEC-related types. + * + * \param zone Current zone. + * \param node Node to be checked. + * \param do_checks Level of checks to be done. + * \param handler Error handler. + * + * \retval KNOT_EOK if no error was found. + * + * \return Appropriate error code if error was found. + */ +static int semantic_checks_plain(knot_zone_contents_t *zone, + knot_node_t *node, + char do_checks, + err_handler_t *handler) +{ + assert(handler); + const knot_rrset_t *cname_rrset = + knot_node_rrset(node, KNOT_RRTYPE_CNAME); + if (cname_rrset != NULL) { + if (check_cname_cycles_in_zone(zone, cname_rrset) != + KNOT_EOK) { + err_handler_handle_error(handler, node, + ZC_ERR_CNAME_CYCLE); + } + + /* No DNSSEC and yet there is more than one rrset in node */ + if (do_checks == 1 && + knot_node_rrset_count(node) != 1) { + err_handler_handle_error(handler, node, + ZC_ERR_CNAME_EXTRA_RECORDS); + } else if (knot_node_rrset_count(node) != 1) { + /* With DNSSEC node can contain RRSIG or NSEC */ + if (!(knot_node_rrset(node, KNOT_RRTYPE_RRSIG) || + knot_node_rrset(node, KNOT_RRTYPE_NSEC)) || + knot_node_rrset_count(node) > 3) { + err_handler_handle_error(handler, node, + ZC_ERR_CNAME_EXTRA_RECORDS_DNSSEC); + } + } + + if (knot_rrset_rdata(cname_rrset)->next != + knot_rrset_rdata(cname_rrset)) { + err_handler_handle_error(handler, node, + ZC_ERR_CNAME_MULTIPLE); + } + } + + const knot_rrset_t *dname_rrset = + knot_node_rrset(node, KNOT_RRTYPE_DNAME); + if (dname_rrset != NULL) { + if (check_cname_cycles_in_zone(zone, dname_rrset) != + KNOT_EOK) { + err_handler_handle_error(handler, node, + ZC_ERR_DNAME_CYCLE); + } + + if (knot_node_rrset(node, KNOT_RRTYPE_CNAME)) { + err_handler_handle_error(handler, node, + ZC_ERR_DNAME_EXTRA_RECORDS); + } + + if (knot_rrset_rdata(dname_rrset)->next != + knot_rrset_rdata(dname_rrset)) { + err_handler_handle_error(handler, node, + ZC_ERR_DNAME_MULTIPLE); + } + } + + /* check for glue records at zone cuts */ + if (knot_node_is_deleg_point(node)) { + const knot_rrset_t *ns_rrset = + knot_node_rrset(node, KNOT_RRTYPE_NS); + assert(ns_rrset); + //FIXME this should be an error as well ! (i guess) + + const knot_dname_t *ns_dname = + knot_rdata_get_item(knot_rrset_rdata + (ns_rrset), 0)->dname; + + assert(ns_dname); + + const knot_node_t *glue_node = + knot_zone_contents_find_node(zone, ns_dname); + + if (knot_dname_is_subdomain(ns_dname, + knot_node_owner(knot_zone_contents_apex(zone)))) { + if (glue_node == NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_GLUE_NODE); + } else { + if ((knot_node_rrset(glue_node, + KNOT_RRTYPE_A) == NULL) && + (knot_node_rrset(glue_node, + KNOT_RRTYPE_AAAA) == NULL)) { + err_handler_handle_error(handler, node, + ZC_ERR_GLUE_RECORD); + } + } + } + } + return KNOT_EOK; +} + +/*! + * \brief Run semantic checks for node without DNSSEC-related types. + * + * \param zone Current zone. + * \param node Node to be checked. + * \param first_node First node in canonical order. + * \param last_node Last node in canonical order. + * \param handler Error handler. + * \param nsec3 NSEC3 used. + * + * \retval KNOT_EOK if no error was found. + * + * \return Appropriate error code if error was found. + */ +static int semantic_checks_dnssec(knot_zone_contents_t *zone, + knot_node_t *node, + knot_node_t *first_node, + knot_node_t **last_node, + err_handler_t *handler, + char nsec3) +{ + assert(handler); + assert(node); + char auth = !knot_node_is_non_auth(node); + char deleg = knot_node_is_deleg_point(node); + uint rrset_count = knot_node_rrset_count(node); + const knot_rrset_t **rrsets = knot_node_rrsets(node); + const knot_rrset_t *dnskey_rrset = + knot_node_rrset(knot_zone_contents_apex(zone), + KNOT_RRTYPE_DNSKEY); + + int ret = 0; + + for (int i = 0; i < rrset_count; i++) { + const knot_rrset_t *rrset = rrsets[i]; + if (auth && !deleg && + (ret = check_rrsig_in_rrset(rrset, dnskey_rrset, + nsec3)) != 0) { + /* CLEANUP */ +/* log_zone_error("RRSIG %d node %s\n", ret, + knot_dname_to_str(node->owner));*/ + + err_handler_handle_error(handler, node, ret); + } + + if (!nsec3 && auth) { + /* check for NSEC record */ + const knot_rrset_t *nsec_rrset = + knot_node_rrset(node, + KNOT_RRTYPE_NSEC); + + if (nsec_rrset == NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_NO_NSEC); + /* CLEANUP */ +/* char *name = + knot_dname_to_str(node->owner); + log_zone_error("Missing NSEC in node: " + "%s\n", name); + free(name); + return; */ + } else { + + /* check NSEC/NSEC3 bitmap */ + + uint count; + + uint16_t *array = NULL; + + if (rdata_nsec_to_type_array( + knot_rdata_item( + knot_rrset_rdata(nsec_rrset), + 1), + &array, &count) != 0) { + err_handler_handle_error(handler, + NULL, + ZC_ERR_ALLOC); + return ZC_ERR_ALLOC; /* ... */ + /*return; */ + } + + uint16_t type = 0; + for (int j = 0; j < count; j++) { + /* test for each type's presence */ + type = array[j]; + if (type == KNOT_RRTYPE_RRSIG) { + continue; + } + if (knot_node_rrset(node, + type) == NULL) { + err_handler_handle_error( + handler, + node, + ZC_ERR_NSEC_RDATA_BITMAP); + /* CLEANUP */ + /* char *name = + knot_dname_to_str( + knot_node_owner(node)); + + log_zone_error("Node %s does " + "not contain RRSet of type %s " + "but NSEC bitmap says " + "it does!\n", name, + knot_rrtype_to_string(type)); + + free(name); */ + } + } + free(array); + } + + /* Test that only one record is in the + * NSEC RRSet */ + + if ((nsec_rrset != NULL) && + knot_rrset_rdata(nsec_rrset)->next != + knot_rrset_rdata(nsec_rrset)) { + err_handler_handle_error(handler, + node, + ZC_ERR_NSEC_RDATA_MULTIPLE); + /* CLEANUP */ +/* char *name = + knot_dname_to_str( + knot_node_owner(node)); + log_zone_error("Node %s contains more " + "than one NSEC " + "record!\n", name); + knot_rrset_dump(nsec_rrset, 0); + free(name); */ + } + + /* + * Test that NSEC chain is coherent. + * We have already checked that every + * authoritative node contains NSEC record + * so checking should only be matter of testing + * the next link in each node. + */ + + if (nsec_rrset != NULL) { + knot_dname_t *next_domain = + knot_rdata_item( + knot_rrset_rdata(nsec_rrset), + 0)->dname; + + assert(next_domain); + + if (knot_zone_contents_find_node(zone, next_domain) == + NULL) { + err_handler_handle_error(handler, + node, + ZC_ERR_NSEC_RDATA_CHAIN); + /* CLEANUP */ +/* log_zone_error("NSEC chain is not " + "coherent!\n"); */ + } + + if (knot_dname_compare(next_domain, + knot_node_owner(knot_zone_contents_apex(zone))) + == 0) { + /* saving the last node */ + *last_node = node; + } + + } + } else if (nsec3 && (auth || deleg)) { /* nsec3 */ + int ret = check_nsec3_node_in_zone(zone, node, + handler); + if (ret != KNOT_EOK) { + free(rrsets); + return ret; + } + } + } + free(rrsets); + + return KNOT_EOK; +} + +/*! + * \brief Function called by zone traversal function. Used to call + * knot_zone_save_enclosers. + * + * \param node Node to be searched. + * \param data Arguments. + */ +static void do_checks_in_tree(knot_node_t *node, void *data) +{ + assert(data != NULL); + arg_t *args = (arg_t *)data; + + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + short count = knot_node_rrset_count(node); + + assert(count == 0 || rrsets != NULL); + + knot_zone_contents_t *zone = (knot_zone_contents_t *)args->arg1; + + assert(zone); + + +/* for (int i = 0; i < count; ++i) { + assert(rrsets[i] != NULL); + knot_zone_save_enclosers_rrset(rrsets[i], + zone, + (skip_list_t *)args->arg2); + } */ + + knot_node_t *first_node = (knot_node_t *)args->arg4; + knot_node_t **last_node = (knot_node_t **)args->arg5; + + err_handler_t *handler = (err_handler_t *)args->arg6; + + char do_checks = *((char *)(args->arg3)); + + if (do_checks) { + semantic_checks_plain(zone, node, do_checks, handler); + } + + if (do_checks > 1) { + semantic_checks_dnssec(zone, node, first_node, last_node, + handler, do_checks == 3); + } + + free(rrsets); +} + +void zone_do_sem_checks(knot_zone_contents_t *zone, char do_checks, + err_handler_t *handler, + knot_node_t **last_node) +{ + if (!do_checks) { + return; + } + + arg_t arguments; + arguments.arg1 = zone; + arguments.arg3 = &do_checks; + arguments.arg4 = NULL; + arguments.arg5 = last_node; + arguments.arg6 = handler; + + knot_zone_contents_tree_apply_inorder(zone, + do_checks_in_tree, + (void *)&arguments); +} + +void log_cyclic_errors_in_zone(err_handler_t *handler, + knot_zone_contents_t *zone, + knot_node_t *last_node, + const knot_node_t *first_nsec3_node, + const knot_node_t *last_nsec3_node, + char do_checks) +{ + if (do_checks == 3) { + /* Each NSEC3 node should only contain one RRSET. */ + assert(last_nsec3_node && first_nsec3_node); + const knot_rrset_t *nsec3_rrset = + knot_node_rrset(last_nsec3_node, + KNOT_RRTYPE_NSEC3); + if (nsec3_rrset == NULL) { + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + return; + } + + /* check that next dname is in the zone */ + uint8_t *next_dname_decoded = NULL; + size_t real_size = 0; + + if (((real_size = base32hex_encode_alloc(((char *) + rdata_item_data(&(nsec3_rrset->rdata->items[4]))) + 1, + rdata_item_size(&nsec3_rrset->rdata->items[4]) - 1, + (char **)&next_dname_decoded)) <= 0) || + (next_dname_decoded == NULL)) { + fprintf(stderr, "Could not encode base32 string!\n"); + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + return; + } + + /* This is why allocate maximum length of decoded string + 1 */ + memmove(next_dname_decoded + 1, next_dname_decoded, real_size); + next_dname_decoded[0] = real_size; + + /* Local allocation, will be discarded. */ + knot_dname_t *next_dname = + knot_dname_new_from_wire(next_dname_decoded, + real_size + 1, NULL); + if (next_dname == NULL) { + fprintf(stderr, "Could not allocate dname!\n"); + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_ALLOC); + return; + } + + free(next_dname_decoded); + + /*! \todo Free result and dname! */ + if (knot_dname_cat(next_dname, + knot_node_owner(knot_zone_contents_apex(zone))) == + NULL) { + fprintf(stderr, "Could not concatenate dnames!\n"); + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + return; + } + + /* Check it points somewhere first. */ + if (knot_zone_contents_find_nsec3_node(zone, next_dname) == NULL) { + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + } + + /* Compare with the actual first NSEC3 node. */ + if (knot_dname_compare(first_nsec3_node->owner, + next_dname) != 0) { + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + } + + /* Directly discard. */ + knot_dname_free(&next_dname); + + } else if (do_checks == 2 ) { + if (last_node == NULL) { + err_handler_handle_error(handler, last_node, + ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC); + return; + } else { + const knot_rrset_t *nsec_rrset = + knot_node_rrset(last_node, + KNOT_RRTYPE_NSEC); + + if (nsec_rrset == NULL) { + err_handler_handle_error(handler, last_node, + ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC); + return; + } + + const knot_dname_t *next_dname = + knot_rdata_item( + knot_rrset_rdata(nsec_rrset), 0)->dname; + assert(next_dname); + + const knot_dname_t *apex_dname = + knot_node_owner(knot_zone_contents_apex(zone)); + assert(apex_dname); + + if (knot_dname_compare(next_dname, apex_dname) !=0) { + err_handler_handle_error(handler, last_node, + ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC); + } + } + } +} diff --git a/src/knot/zone/semantic-check.h b/src/knot/zone/semantic-check.h new file mode 100644 index 0000000000000000000000000000000000000000..88d828acf5f946aff33a2c3ef0fb18853fa881e2 --- /dev/null +++ b/src/knot/zone/semantic-check.h @@ -0,0 +1,276 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file semantic-check.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief DNS zone semantic checks. + * + * \addtogroup dnslib + * @{ + */ + +#ifndef _KNOT_SEMANTIC_CHECK_H_ +#define _KNOT_SEMANTIC_CHECK_H_ + +/*! + *\brief Internal error constants. General errors are added for convenience, + * so that code does not have to change if new errors are added. + */ +enum zonechecks_errors { + ZC_ERR_ALLOC = -40, + ZC_ERR_UNKNOWN, + + ZC_ERR_MISSING_SOA, + + ZC_ERR_GENERIC_GENERAL_ERROR, /* isn't there a better name? */ + + ZC_ERR_RRSIG_RDATA_TYPE_COVERED, + ZC_ERR_RRSIG_RDATA_TTL, + ZC_ERR_RRSIG_RDATA_LABELS, + ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER, + ZC_ERR_RRSIG_RDATA_SIGNED_WRONG, + ZC_ERR_RRSIG_NO_RRSIG, + ZC_ERR_RRSIG_SIGNED, + ZC_ERR_RRSIG_OWNER, + ZC_ERR_RRSIG_CLASS, + ZC_ERR_RRSIG_TTL, + ZC_ERR_RRSIG_NOT_ALL, + + ZC_ERR_RRSIG_GENERAL_ERROR, + + ZC_ERR_NO_NSEC, + ZC_ERR_NSEC_RDATA_BITMAP, + ZC_ERR_NSEC_RDATA_MULTIPLE, + ZC_ERR_NSEC_RDATA_CHAIN, + ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC, + + ZC_ERR_NSEC_GENERAL_ERROR, + + ZC_ERR_NSEC3_UNSECURED_DELEGATION, + ZC_ERR_NSEC3_NOT_FOUND, + ZC_ERR_NSEC3_UNSECURED_DELEGATION_OPT, + ZC_ERR_NSEC3_RDATA_TTL, + ZC_ERR_NSEC3_RDATA_CHAIN, + ZC_ERR_NSEC3_RDATA_BITMAP, + + ZC_ERR_NSEC3_GENERAL_ERROR, + + ZC_ERR_CNAME_CYCLE, + ZC_ERR_DNAME_CYCLE, + ZC_ERR_CNAME_EXTRA_RECORDS, + ZC_ERR_DNAME_EXTRA_RECORDS, + ZC_ERR_CNAME_EXTRA_RECORDS_DNSSEC, + ZC_ERR_CNAME_MULTIPLE, + ZC_ERR_DNAME_MULTIPLE, + + ZC_ERR_CNAME_GENERAL_ERROR, + + ZC_ERR_GLUE_NODE, + ZC_ERR_GLUE_RECORD, + + ZC_ERR_GLUE_GENERAL_ERROR, +}; + +static char *error_messages[(-ZC_ERR_ALLOC) + 1] = { + [-ZC_ERR_ALLOC] = "Memory allocation error!\n", + + [-ZC_ERR_MISSING_SOA] = "SOA record missing in zone!\n", + + [-ZC_ERR_RRSIG_RDATA_TYPE_COVERED] = + "RRSIG: Type covered rdata field is wrong!\n", + [-ZC_ERR_RRSIG_RDATA_TTL] = + "RRSIG: TTL rdata field is wrong!\n", + [-ZC_ERR_RRSIG_RDATA_LABELS] = + "RRSIG: Labels rdata field is wrong!\n", + [-ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER] = + "RRSIG: Signer name is different than in DNSKEY!\n", + [-ZC_ERR_RRSIG_RDATA_SIGNED_WRONG] = + "RRSIG: Key error!\n", + [-ZC_ERR_RRSIG_NO_RRSIG] = + "RRSIG: No RRSIG!\n", + [-ZC_ERR_RRSIG_SIGNED] = + "RRSIG: Signed RRSIG!\n", + [-ZC_ERR_RRSIG_OWNER] = + "RRSIG: Owner name rdata field is wrong!\n", + [-ZC_ERR_RRSIG_CLASS] = + "RRSIG: Class is wrong!\n", + [-ZC_ERR_RRSIG_TTL] = + "RRSIG: TTL is wrong!\n", + [-ZC_ERR_RRSIG_NOT_ALL] = + "RRSIG: Not all RRs are signed!\n", + + [-ZC_ERR_NO_NSEC] = + "NSEC: Missing NSEC record\n", + [-ZC_ERR_NSEC_RDATA_BITMAP] = + "NSEC: Wrong NSEC bitmap!\n", + [-ZC_ERR_NSEC_RDATA_MULTIPLE] = + "NSEC: Multiple NSEC records!\n", + [-ZC_ERR_NSEC_RDATA_CHAIN] = + "NSEC: NSEC chain is not coherent!\n", + [-ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC] = + "NSEC: NSEC chain is not cyclic!\n", + + [-ZC_ERR_NSEC3_UNSECURED_DELEGATION] = + "NSEC3: Zone contains unsecured delegation!\n", + [-ZC_ERR_NSEC3_NOT_FOUND] = + "NSEC3: Could not find previous NSEC3 record in the zone!\n", + [-ZC_ERR_NSEC3_UNSECURED_DELEGATION_OPT] = + "NSEC3: Unsecured delegation is not part " + "of the Opt-Out span!\n", + [-ZC_ERR_NSEC3_RDATA_TTL] = + "NSEC3: Original TTL rdata field is wrong!\n", + [-ZC_ERR_NSEC3_RDATA_CHAIN] = + "NSEC3: NSEC3 chain is not coherent!\n", + [-ZC_ERR_NSEC3_RDATA_BITMAP] = + "NSEC3: NSEC3 bitmap error!\n", + + [-ZC_ERR_CNAME_CYCLE] = + "CNAME: CNAME cycle!\n", + [-ZC_ERR_DNAME_CYCLE] = + "CNAME: DNAME cycle!\n", + [-ZC_ERR_CNAME_EXTRA_RECORDS] = + "CNAME: Node with CNAME record has other records!\n", + [-ZC_ERR_DNAME_EXTRA_RECORDS] = + "DNAME: Node with DNAME record has other records!\n", + [-ZC_ERR_CNAME_EXTRA_RECORDS_DNSSEC] = + "CNAME: Node with CNAME record has other " + "records than RRSIG and NSEC/NSEC3!\n", + [-ZC_ERR_CNAME_MULTIPLE] = "CNAME: Multiple CNAME records!\n", + [-ZC_ERR_DNAME_MULTIPLE] = "DNAME: Multiple DNAME records!\n", + + /* ^ + | Important errors (to be logged on first occurence and counted) */ + + + /* Below are errors of lesser importance, to be counted unless + specified otherwise */ + + [-ZC_ERR_GLUE_NODE] = + "GLUE: Node with Glue record missing!\n", + [-ZC_ERR_GLUE_RECORD] = + "GLUE: Record with Glue address missing\n", +}; + +/*! + * \brief Arguments to be used with tree traversal functions. Uses void pointers + * to be more versatile. + * + */ +struct arg { + void *arg1; /* FILE *f / zone */ + void *arg2; /* skip_list_t */ + void *arg3; /* zone */ + void *arg4; /* first node */ + void *arg5; /* last node */ + void *arg6; /* error handler */ + void *arg7; /* CRC */ +}; + +typedef struct arg arg_t; + +/*! + * \brief Structure representing handle options. + */ +struct handler_options { + char log_cname; /*!< Log all CNAME related semantic errors. */ + char log_glue; /*!< Log all glue related semantic errors. */ + char log_rrsigs; /*!< Log all RRSIG related semantic errors. */ + char log_nsec; /*!< Log all NSEC related semantic errors. */ + char log_nsec3; /*!< Log all NSEC3 related semantic errors. */ +}; + +/*! + * \brief Structure for handling semantic errors. + */ +struct err_handler { + /* Consider moving error messages here */ + struct handler_options options; /*!< Handler options. */ + uint errors[(-ZC_ERR_ALLOC) + 1]; /*!< Array with error messages */ +}; + +typedef struct err_handler err_handler_t; + +/*! + * \brief Creates new semantic error handler. + * + * \param log_cname If true, log all CNAME related events. + * \param log_glue If true, log all Glue related events. + * \param log_rrsigs If true, log all RRSIGs related events. + * \param log_nsec If true, log all NSEC related events. + * \param log_nsec3 If true, log all NSEC3 related events. + * + * \return err_handler_t * Created error handler. + */ +err_handler_t *handler_new(char log_cname, char log_glue, + char log_rrsigs, char log_nsec, + char log_nsec3); + +/*! + * \brief Called when error has been encountered in node. Will either log error + * or print it, depending on handler's options. + * + * \param handler Error handler. + * \param node Node with semantic error in it. + * \param error Type of error. + * + * \retval KNOT_EOK on success. + * \retval ZC_ERR_UNKNOWN if unknown error. + * \retval ZC_ERR_ALLOC if memory error. + */ +int err_handler_handle_error(err_handler_t *handler, + const knot_node_t *node, + uint error); + +/*! + * \brief Checks if last node in NSEC/NSEC3 chain points to first node in the + * chain and prints possible errors. + * + * \param handler Semantic error handler. + * \param zone Current zone. + * \param last_node Last node in NSEC/NSEC3 chain. + * \param do_checks Level of semantic checks. + */ +void log_cyclic_errors_in_zone(err_handler_t *handler, + knot_zone_contents_t *zone, + knot_node_t *last_node, + const knot_node_t *first_nsec3_node, + const knot_node_t *last_nsec3_node, + char do_checks); + +/*! + * \brief This function prints all errors that occured in zone. + * + * \param handler Error handler containing found errors. + */ +void err_handler_log_all(err_handler_t *handler); + +/*! + * \brief Helper function - wraps its arguments into arg_t structure and + * calls function that does the actual work. + * + * \param zone Zone to be searched / checked + * \param list Skip list of closests enclosers. + * \param do_checks Level of semantic checks. + * \param handler Semantic error handler. + * \param last_node Last checked node, which is part of NSEC(3) chain. + */ +void zone_do_sem_checks(knot_zone_contents_t *zone, char do_checks, + err_handler_t *handler, + knot_node_t **last_node); + +#endif // _KNOT_SEMANTIC_CHECK_H_ diff --git a/src/knot/zone/zone-dump.c b/src/knot/zone/zone-dump.c index afc577d87ed0f66720fc0501bbd9045be22a41c8..5fff0b487978aa42e0c54aee2789f7a2d328ce94 100644 --- a/src/knot/zone/zone-dump.c +++ b/src/knot/zone/zone-dump.c @@ -26,11 +26,11 @@ #include "libknot/common.h" #include "knot/zone/zone-dump.h" #include "libknot/libknot.h" +#include "common/crc.h" #include "knot/other/debug.h" #include "common/skip-list.h" -#include "common/base32hex.h" -#include "common/crc.h" #include "libknot/util/error.h" +#include "semantic-check.h" #define ZONECHECKS_VERBOSE @@ -51,1344 +51,6 @@ * or raw data stored like this: data_len [data] */ -static const uint MAX_CNAME_CYCLE_DEPTH = 15; - -/*! - *\brief Internal error constants. General errors are added for convenience, - * so that code does not have to change if new errors are added. - */ -enum zonechecks_errors { - ZC_ERR_ALLOC = -40, - ZC_ERR_UNKNOWN, - - ZC_ERR_MISSING_SOA, - - ZC_ERR_GENERIC_GENERAL_ERROR, /* isn't there a better name? */ - - ZC_ERR_RRSIG_RDATA_TYPE_COVERED, - ZC_ERR_RRSIG_RDATA_TTL, - ZC_ERR_RRSIG_RDATA_LABELS, - ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER, - ZC_ERR_RRSIG_RDATA_SIGNED_WRONG, - ZC_ERR_RRSIG_NO_RRSIG, - ZC_ERR_RRSIG_SIGNED, - ZC_ERR_RRSIG_OWNER, - ZC_ERR_RRSIG_CLASS, - ZC_ERR_RRSIG_TTL, - ZC_ERR_RRSIG_NOT_ALL, - - ZC_ERR_RRSIG_GENERAL_ERROR, - - ZC_ERR_NO_NSEC, - ZC_ERR_NSEC_RDATA_BITMAP, - ZC_ERR_NSEC_RDATA_MULTIPLE, - ZC_ERR_NSEC_RDATA_CHAIN, - ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC, - - ZC_ERR_NSEC_GENERAL_ERROR, - - ZC_ERR_NSEC3_UNSECURED_DELEGATION, - ZC_ERR_NSEC3_NOT_FOUND, - ZC_ERR_NSEC3_UNSECURED_DELEGATION_OPT, - ZC_ERR_NSEC3_RDATA_TTL, - ZC_ERR_NSEC3_RDATA_CHAIN, - ZC_ERR_NSEC3_RDATA_BITMAP, - - ZC_ERR_NSEC3_GENERAL_ERROR, - - ZC_ERR_CNAME_CYCLE, - ZC_ERR_DNAME_CYCLE, - ZC_ERR_CNAME_EXTRA_RECORDS, - ZC_ERR_DNAME_EXTRA_RECORDS, - ZC_ERR_CNAME_EXTRA_RECORDS_DNSSEC, - ZC_ERR_CNAME_MULTIPLE, - ZC_ERR_DNAME_MULTIPLE, - - ZC_ERR_CNAME_GENERAL_ERROR, - - ZC_ERR_GLUE_NODE, - ZC_ERR_GLUE_RECORD, - - ZC_ERR_GLUE_GENERAL_ERROR, -}; - -static char *error_messages[(-ZC_ERR_ALLOC) + 1] = { - [-ZC_ERR_ALLOC] = "Memory allocation error!\n", - - [-ZC_ERR_MISSING_SOA] = "SOA record missing in zone!\n", - - [-ZC_ERR_RRSIG_RDATA_TYPE_COVERED] = - "RRSIG: Type covered rdata field is wrong!\n", - [-ZC_ERR_RRSIG_RDATA_TTL] = - "RRSIG: TTL rdata field is wrong!\n", - [-ZC_ERR_RRSIG_RDATA_LABELS] = - "RRSIG: Labels rdata field is wrong!\n", - [-ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER] = - "RRSIG: Signer name is different than in DNSKEY!\n", - [-ZC_ERR_RRSIG_RDATA_SIGNED_WRONG] = - "RRSIG: Key error!\n", - [-ZC_ERR_RRSIG_NO_RRSIG] = - "RRSIG: No RRSIG!\n", - [-ZC_ERR_RRSIG_SIGNED] = - "RRSIG: Signed RRSIG!\n", - [-ZC_ERR_RRSIG_OWNER] = - "RRSIG: Owner name rdata field is wrong!\n", - [-ZC_ERR_RRSIG_CLASS] = - "RRSIG: Class is wrong!\n", - [-ZC_ERR_RRSIG_TTL] = - "RRSIG: TTL is wrong!\n", - [-ZC_ERR_RRSIG_NOT_ALL] = - "RRSIG: Not all RRs are signed!\n", - - [-ZC_ERR_NO_NSEC] = - "NSEC: Missing NSEC record\n", - [-ZC_ERR_NSEC_RDATA_BITMAP] = - "NSEC: Wrong NSEC bitmap!\n", - [-ZC_ERR_NSEC_RDATA_MULTIPLE] = - "NSEC: Multiple NSEC records!\n", - [-ZC_ERR_NSEC_RDATA_CHAIN] = - "NSEC: NSEC chain is not coherent!\n", - [-ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC] = - "NSEC: NSEC chain is not cyclic!\n", - - [-ZC_ERR_NSEC3_UNSECURED_DELEGATION] = - "NSEC3: Zone contains unsecured delegation!\n", - [-ZC_ERR_NSEC3_NOT_FOUND] = - "NSEC3: Could not find previous NSEC3 record in the zone!\n", - [-ZC_ERR_NSEC3_UNSECURED_DELEGATION_OPT] = - "NSEC3: Unsecured delegation is not part " - "of the Opt-Out span!\n", - [-ZC_ERR_NSEC3_RDATA_TTL] = - "NSEC3: Original TTL rdata field is wrong!\n", - [-ZC_ERR_NSEC3_RDATA_CHAIN] = - "NSEC3: NSEC3 chain is not coherent!\n", - [-ZC_ERR_NSEC3_RDATA_BITMAP] = - "NSEC3: NSEC3 bitmap error!\n", - - [-ZC_ERR_CNAME_CYCLE] = - "CNAME: CNAME cycle!\n", - [-ZC_ERR_DNAME_CYCLE] = - "CNAME: DNAME cycle!\n", - [-ZC_ERR_CNAME_EXTRA_RECORDS] = - "CNAME: Node with CNAME record has other records!\n", - [-ZC_ERR_DNAME_EXTRA_RECORDS] = - "DNAME: Node with DNAME record has other records!\n", - [-ZC_ERR_CNAME_EXTRA_RECORDS_DNSSEC] = - "CNAME: Node with CNAME record has other " - "records than RRSIG and NSEC/NSEC3!\n", - [-ZC_ERR_CNAME_MULTIPLE] = "CNAME: Multiple CNAME records!\n", - [-ZC_ERR_DNAME_MULTIPLE] = "DNAME: Multiple DNAME records!\n", - - /* ^ - | Important errors (to be logged on first occurence and counted) */ - - - /* Below are errors of lesser importance, to be counted unless - specified otherwise */ - - [-ZC_ERR_GLUE_NODE] = - "GLUE: Node with Glue record missing!\n", - [-ZC_ERR_GLUE_RECORD] = - "GLUE: Record with Glue address missing\n", -}; - -/*! - * \brief Structure representing handle options. - */ -struct handler_options { - char log_cname; /*!< Log all CNAME related semantic errors. */ - char log_glue; /*!< Log all glue related semantic errors. */ - char log_rrsigs; /*!< Log all RRSIG related semantic errors. */ - char log_nsec; /*!< Log all NSEC related semantic errors. */ - char log_nsec3; /*!< Log all NSEC3 related semantic errors. */ -}; - -/*! - * \brief Structure for handling semantic errors. - */ -struct err_handler { - /* Consider moving error messages here */ - struct handler_options options; /*!< Handler options. */ - uint errors[(-ZC_ERR_ALLOC) + 1]; /*!< Array with error messages */ -}; - -typedef struct err_handler err_handler_t; - -/*! - * \brief Creates new semantic error handler. - * - * \param log_cname If true, log all CNAME related events. - * \param log_glue If true, log all Glue related events. - * \param log_rrsigs If true, log all RRSIGs related events. - * \param log_nsec If true, log all NSEC related events. - * \param log_nsec3 If true, log all NSEC3 related events. - * - * \return err_handler_t * Created error handler. - */ -static err_handler_t *handler_new(char log_cname, char log_glue, - char log_rrsigs, char log_nsec, - char log_nsec3) -{ - err_handler_t *handler = malloc(sizeof(err_handler_t)); - CHECK_ALLOC_LOG(handler, NULL); - - /* It should be initialized, but to be safe */ - memset(handler->errors, 0, sizeof(uint) * (-ZC_ERR_ALLOC + 1)); - - handler->options.log_cname = log_cname; - handler->options.log_glue = log_glue; - handler->options.log_rrsigs = log_rrsigs; - handler->options.log_nsec = log_nsec; - handler->options.log_nsec3 = log_nsec3; - - return handler; -} - -/*! - * \brief Prints error message with node information. - * - * \note If \a node is NULL, only total number of errors is printed. - * - * \param handler Error handler. - * \param node Node with semantic error in it. - * \param error Type of error. - */ -static void log_error_from_node(err_handler_t *handler, - const knot_node_t *node, - uint error) -{ - if (node != NULL) { - char *name = - knot_dname_to_str(knot_node_owner(node)); - fprintf(stderr, "Semantic warning in node: %s: ", name); - fprintf(stderr, "%s", error_messages[-error]); - free(name); - } else { - fprintf(stderr, "Total number of warnings is: %d for error: %s", - handler->errors[-error], - error_messages[-error]); - } -} - -/*! - * \brief Called when error has been encountered in node. Will either log error - * or print it, depending on handler's options. - * - * \param handler Error handler. - * \param node Node with semantic error in it. - * \param error Type of error. - * - * \retval KNOT_EOK on success. - * \retval ZC_ERR_UNKNOWN if unknown error. - * \retval ZC_ERR_ALLOC if memory error. - */ -static int err_handler_handle_error(err_handler_t *handler, - const knot_node_t *node, - uint error) -{ - assert(handler && node); - if ((error != 0) && - (error > ZC_ERR_GLUE_GENERAL_ERROR)) { - return ZC_ERR_UNKNOWN; - } - - if (error == ZC_ERR_ALLOC) { - ERR_ALLOC_FAILED; - return ZC_ERR_ALLOC; - } - - /* missing SOA can only occur once, so there - * needn't to be an option for it */ - - if ((error != 0) && - (error < ZC_ERR_GENERIC_GENERAL_ERROR)) { - /* The two errors before SOA were handled */ - log_error_from_node(handler, node, error); - - } else if ((error < ZC_ERR_RRSIG_GENERAL_ERROR) && - ((handler->errors[-error] == 0) || - (handler->options.log_rrsigs))) { - - log_error_from_node(handler, node, error); - - } else if ((error > ZC_ERR_RRSIG_GENERAL_ERROR) && - (error < ZC_ERR_NSEC_GENERAL_ERROR) && - ((handler->errors[-error] == 0) || - (handler->options.log_nsec))) { - - log_error_from_node(handler, node, error); - - } else if ((error > ZC_ERR_NSEC_GENERAL_ERROR) && - (error < ZC_ERR_NSEC3_GENERAL_ERROR) && - ((handler->errors[-error] == 0) || - (handler->options.log_nsec3))) { - - log_error_from_node(handler, node, error); - - } else if ((error > ZC_ERR_NSEC3_GENERAL_ERROR) && - (error < ZC_ERR_CNAME_GENERAL_ERROR) && - ((handler->errors[-error] == 0) || - (handler->options.log_cname))) { - - log_error_from_node(handler, node, error); - - } else if ((error > ZC_ERR_CNAME_GENERAL_ERROR) && - (error < ZC_ERR_GLUE_GENERAL_ERROR) && - handler->options.log_glue) { - - log_error_from_node(handler, node, error); - - } - - handler->errors[-error]++; - - return KNOT_EOK; -} - -/*! - * \brief This function prints all errors that occured in zone. - * - * \param handler Error handler containing found errors. - */ -static void err_handler_log_all(err_handler_t *handler) -{ - if (handler == NULL) { - return; - } - - for (int i = ZC_ERR_ALLOC; i < ZC_ERR_GLUE_GENERAL_ERROR; i++) { - if (handler->errors[-i] > 0) { - log_error_from_node(handler, NULL, i); - } - } -} - -/*! - * \brief Arguments to be used with tree traversal functions. Uses void pointers - * to be more versatile. - * - */ -struct arg { - void *arg1; /* FILE *f / zone */ - void *arg2; /* skip_list_t */ - void *arg3; /* zone */ - void *arg4; /* first node */ - void *arg5; /* last node */ - void *arg6; /* error handler */ - void *arg7; /* CRC */ -}; - -typedef struct arg arg_t; - -/*! - * \brief Semantic check - CNAME cycles. Uses constant value with maximum - * allowed CNAME chain depth. - * - * \param zone Zone containing the RRSet. - * \param rrset RRSet to be tested. - * - * \retval KNOT_EOK when there is no cycle. - * \retval ZC_ERR_CNAME_CYCLE when cycle is present. - */ -static int check_cname_cycles_in_zone(knot_zone_contents_t *zone, - const knot_rrset_t *rrset) -{ - const knot_rrset_t *next_rrset = rrset; - assert(rrset); - const knot_rdata_t *tmp_rdata = knot_rrset_rdata(next_rrset); - const knot_node_t *next_node = NULL; - - uint i = 0; - - assert(tmp_rdata); - - const knot_dname_t *next_dname = - knot_rdata_cname_name(tmp_rdata); - - assert(next_dname); - - while (i < MAX_CNAME_CYCLE_DEPTH && next_dname != NULL) { - next_node = knot_zone_contents_get_node(zone, next_dname); - if (next_node == NULL) { - next_node = - knot_zone_contents_get_nsec3_node(zone, next_dname); - } - - if (next_node != NULL) { - next_rrset = knot_node_rrset(next_node, - KNOT_RRTYPE_CNAME); - if (next_rrset != NULL) { - next_dname = - knot_rdata_cname_name(next_rrset->rdata); - } else { - next_node = NULL; - next_dname = NULL; - } - } else { - next_dname = NULL; - } - i++; - } - - /* even if the length is 0, i will be 1 */ - if (i >= MAX_CNAME_CYCLE_DEPTH) { - return ZC_ERR_CNAME_CYCLE; - } - - return KNOT_EOK; -} - -/*! - * \brief Return raw data from rdata item structure (without length). - * - * \param item Item to get rdata from. - * \return uint16_t * raw data without length. - */ -static inline uint16_t *rdata_item_data(const knot_rdata_item_t *item) -{ - return (uint16_t *)(item->raw_data + 1); -} - -/*! - * \brief Returns type covered field from RRSIG RRSet's rdata. - * - * \param rdata RRSIG rdata. - * \return uint16_t Type covered. - */ -uint16_t type_covered_from_rdata(const knot_rdata_t *rdata) -{ - return ntohs(*(uint16_t *) rdata_item_data(&(rdata->items[0]))); -} - -/*! - * \brief Check whether DNSKEY rdata are valid. - * - * \param rdata DNSKEY rdata to be checked. - * - * \retval KNOT_EOK when rdata are OK. - * \retval ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER when rdata are not OK. - */ -static int check_dnskey_rdata(const knot_rdata_t *rdata) -{ - /* check that Zone key bit it set - position 7 in net order */ - /*! \todo FIXME: endian? I swear I've fixed this already, it was 7 i guesss*/ - uint16_t mask = 1 << 8; //0b0000000100000000; - - uint16_t flags = - knot_wire_read_u16((uint8_t *)rdata_item_data - (knot_rdata_item(rdata, 0))); - - if (flags & mask) { - return KNOT_EOK; - } else { - /* This error does not exactly fit, but it's better - * than a new one */ - return ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER; - } -} - -/*! - * \brief Calculates keytag for RSA/SHA algorithm. - * - * \param key Key wireformat. - * \param keysize Wireformat size. - * - * \return uint16_t Calculated keytag. - */ -static uint16_t keytag_1(uint8_t *key, uint16_t keysize) -{ - uint16_t ac = 0; - if (keysize > 4) { - memmove(&ac, key + keysize - 3, 2); - } - - ac = ntohs(ac); - return ac; -} - -/*! - * \brief Calculates keytag from key wire. - * - * \param key Key wireformat. - * \param keysize Wireformat size. - * - * \return uint16_t Calculated keytag. - */ -static uint16_t keytag(uint8_t *key, uint16_t keysize ) -{ - uint32_t ac = 0; /* assumed to be 32 bits or larger */ - - /* algorithm RSA/SHA */ - if (key[3] == 1) { - return keytag_1(key, keysize); - } else { - for(int i = 0; i < keysize; i++) { - ac += (i & 1) ? key[i] : key[i] << 8; - } - - ac += (ac >> 16) & 0xFFFF; - return (uint16_t)ac & 0xFFFF; - } -} - -/*! - * \brief Returns size of raw data item. - * - * \param item Raw data item. - * - * \return uint16_t Size of raw data item. - */ -static inline uint16_t rdata_item_size(const knot_rdata_item_t *item) -{ - return item->raw_data[0]; -} - -/*! - * \brief Converts DNSKEY rdata to wireformat. - * - * \param rdata DNSKEY rdata to be converted. - * \param wire Created wire. - * \param size Size of created wire. - * - * \retval KNOT_EOK on success. - * \retval KNOT_ENOMEM on memory error. - */ -static int dnskey_to_wire(const knot_rdata_t *rdata, uint8_t **wire, - uint *size) -{ - assert(*wire == NULL); - /* flags + algorithm + protocol + keysize */ - *size = 2 + 1 + 1 + knot_rdata_item(rdata, 3)->raw_data[0]; - *wire = malloc(sizeof(uint8_t) * *size); - CHECK_ALLOC_LOG(*wire, KNOT_ENOMEM); - - /* copy the wire octet by octet */ - - /* TODO check if we really have that many items */ - if (rdata->count < 4) { - return KNOT_ERROR; - } - - (*wire)[0] = ((uint8_t *)(knot_rdata_item(rdata, 0)->raw_data))[2]; - (*wire)[1] = ((uint8_t *)(knot_rdata_item(rdata, 0)->raw_data))[3]; - - (*wire)[2] = ((uint8_t *)(knot_rdata_item(rdata, 1)->raw_data))[2]; - (*wire)[3] = ((uint8_t *)(knot_rdata_item(rdata, 2)->raw_data))[2]; - - memcpy(*wire + 4, knot_rdata_item(rdata, 3)->raw_data + 1, - knot_rdata_item(rdata, 3)->raw_data[0]); - - return KNOT_EOK; -} - -/*! - * \brief Semantic check - RRSIG rdata. - * - * \param rdata_rrsig RRSIG rdata to be checked. - * \param rrset RRSet containing the rdata. - * \param dnskey_rrset RRSet containing zone's DNSKEY - * - * \retval KNOT_EOK if rdata are OK. - * - * \return Appropriate error code if error was found. - */ -static int check_rrsig_rdata(const knot_rdata_t *rdata_rrsig, - const knot_rrset_t *rrset, - const knot_rrset_t *dnskey_rrset) -{ - if (rdata_rrsig == NULL) { - return ZC_ERR_RRSIG_NO_RRSIG; - } - - if (type_covered_from_rdata(rdata_rrsig) != - knot_rrset_type(rrset)) { - /* zoneparser would not let this happen - * but to be on the safe side - */ - return ZC_ERR_RRSIG_RDATA_TYPE_COVERED; - } - - /* label number at the 2nd index should be same as owner's */ - uint16_t *raw_data = - rdata_item_data(knot_rdata_item(rdata_rrsig, 2)); - - uint8_t labels_rdata = ((uint8_t *)raw_data)[0]; - - int tmp = knot_dname_label_count(knot_rrset_owner(rrset)) - - labels_rdata; - - if (tmp != 0) { - /* if name has wildcard, label must not be included */ - if (!knot_dname_is_wildcard(knot_rrset_owner(rrset))) { - return ZC_ERR_RRSIG_RDATA_LABELS; - } else { - if (abs(tmp) != 1) { - return ZC_ERR_RRSIG_RDATA_LABELS; - } - } - } - - /* check original TTL */ - uint32_t original_ttl = - knot_wire_read_u32((uint8_t *)rdata_item_data( - knot_rdata_item(rdata_rrsig, 3))); - - if (original_ttl != knot_rrset_ttl(rrset)) { - return ZC_ERR_RRSIG_RDATA_TTL; - } - - /* signer's name is same as in the zone apex */ - knot_dname_t *signer_name = - knot_rdata_item(rdata_rrsig, 7)->dname; - - /* dnskey is in the apex node */ - if (knot_dname_compare(signer_name, - knot_rrset_owner(dnskey_rrset)) != 0) { - return ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER; - } - - /* Compare algorithm, key tag and signer's name with DNSKEY rrset - * one of the records has to match. Signer name has been checked - * before */ - char match = 0; - const knot_rdata_t *tmp_dnskey_rdata = - knot_rrset_rdata(dnskey_rrset); - do { - uint8_t alg = - ((uint8_t *)(knot_rdata_item(rdata_rrsig, 1)->raw_data))[2]; - uint8_t alg_dnskey = - ((uint8_t *)(knot_rdata_item(tmp_dnskey_rdata, - 2)->raw_data))[2]; - - raw_data = rdata_item_data(knot_rdata_item(rdata_rrsig, 6)); - uint16_t key_tag_rrsig = - knot_wire_read_u16((uint8_t *)raw_data); - -/* raw_data = - rdata_item_data(knot_rdata_item( - tmp_dnskey_rdata, 3)); - - uint16_t raw_length = rdata_item_size(knot_rdata_item( - tmp_dnskey_rdata, 3)); */ - - uint8_t *dnskey_wire = NULL; - uint dnskey_wire_size = 0; - - int ret = 0; - if ((ret = dnskey_to_wire(tmp_dnskey_rdata, &dnskey_wire, - &dnskey_wire_size)) != KNOT_EOK) { - return ret; - } - - uint16_t key_tag_dnskey = - keytag(dnskey_wire, dnskey_wire_size); - - free(dnskey_wire); - - match = (alg == alg_dnskey) && - (key_tag_rrsig == key_tag_dnskey) && - !check_dnskey_rdata(tmp_dnskey_rdata); - - } while (!match && - ((tmp_dnskey_rdata = - knot_rrset_rdata_next(dnskey_rrset, - tmp_dnskey_rdata)) - != NULL)); - - if (!match) { - return ZC_ERR_RRSIG_RDATA_SIGNED_WRONG; - } - - return KNOT_EOK; -} - -/*! - * \brief Semantic check - RRSet's RRSIG. - * - * \param rrset RRSet containing RRSIG. - * \param dnskey_rrset - * \param nsec3 NSEC3 active. - * - * \retval KNOT_EOK on success. - * - * \return Appropriate error code if error was found. - */ -static int check_rrsig_in_rrset(const knot_rrset_t *rrset, - const knot_rrset_t *dnskey_rrset, - char nsec3) -{ - assert(dnskey_rrset && rrset); - - const knot_rrset_t *rrsigs = knot_rrset_rrsigs(rrset); - - if (rrsigs == NULL) { - return ZC_ERR_RRSIG_NO_RRSIG; - } - - /* signed rrsig - nonsense */ - if (knot_rrset_rrsigs(rrsigs) != NULL) { - return ZC_ERR_RRSIG_SIGNED; - } - - /* Different owner, class, ttl */ - - if (knot_dname_compare(knot_rrset_owner(rrset), - knot_rrset_owner(rrsigs)) != 0) { - return ZC_ERR_RRSIG_OWNER; - } - - if (knot_rrset_class(rrset) != knot_rrset_class(rrsigs)) { - return ZC_ERR_RRSIG_CLASS; - } - - if (knot_rrset_ttl(rrset) != knot_rrset_ttl(rrset)) { - return ZC_ERR_RRSIG_TTL; - } - - /* Check whether all rrsets have their rrsigs */ - const knot_rdata_t *tmp_rdata = knot_rrset_rdata(rrset); - const knot_rdata_t *tmp_rrsig_rdata = knot_rrset_rdata(rrsigs); - - assert(tmp_rdata); - assert(tmp_rrsig_rdata); - int ret = 0; - char all_signed = tmp_rdata && tmp_rrsig_rdata; - do { - if ((ret = check_rrsig_rdata(tmp_rrsig_rdata, - rrset, - dnskey_rrset)) != 0) { - return ret; - } - - all_signed = tmp_rdata && tmp_rrsig_rdata; - } while (((tmp_rdata = knot_rrset_rdata_next(rrset, tmp_rdata)) - != NULL) && - ((tmp_rrsig_rdata = - knot_rrset_rdata_next(rrsigs, tmp_rrsig_rdata)) - != NULL)); - - if (!all_signed) { - return ZC_ERR_RRSIG_NOT_ALL; - } - - return KNOT_EOK; -} - -/*! - * \brief Returns bit on index from array in network order. Taken from NSD. - * - * \param bits Array in network order. - * \param index Index to return from array. - * - * \return int Bit on given index. - */ -static int get_bit(uint8_t *bits, size_t index) -{ - /* - * The bits are counted from left to right, so bit #0 is the - * leftmost bit. - */ - return bits[index / 8] & (1 << (7 - index % 8)); -} - -/*! - * \brief Converts NSEC bitmap to array of integers. (Inspired by NSD code) - * - * \param item Item containing the bitmap. - * \param array Array to be created. - * \param count Count of items in array. - * - * \retval KNOT_OK on success. - * \retval KNOT_NOMEM on memory error. - */ -static int rdata_nsec_to_type_array(const knot_rdata_item_t *item, - uint16_t **array, - uint *count) -{ - assert(*array == NULL); - - uint8_t *data = (uint8_t *)rdata_item_data(item); - - int increment = 0; - *count = 0; - - for (int i = 0; i < rdata_item_size(item); i += increment) { - increment = 0; - uint8_t window = data[i]; - increment++; - - uint8_t bitmap_size = data[i + increment]; - increment++; - - uint8_t *bitmap = - malloc(sizeof(uint8_t) * (bitmap_size)); - if (bitmap == NULL) { - ERR_ALLOC_FAILED; - free(*array); - return KNOT_ENOMEM; - } - - memcpy(bitmap, data + i + increment, - bitmap_size); - - increment += bitmap_size; - - for (int j = 0; j < bitmap_size * 8; j++) { - if (get_bit(bitmap, j)) { - (*count)++; - void *tmp = realloc(*array, - sizeof(uint16_t) * - *count); - if (tmp == NULL) { - ERR_ALLOC_FAILED; - free(bitmap); - free(*array); - return KNOT_ENOMEM; - } - *array = tmp; - (*array)[*count - 1] = j + window * 256; - } - } - free(bitmap); - } - - return KNOT_EOK; -} - -/* should write error, not return values !!! */ - -/*! - * \brief Semantic check - check node's NSEC node. - * - * \param zone Current zone. - * \param node Node to be checked. - * \param handler Error handler - * - * \retval KNOT_EOK if no error was found. - * - * \return Appropriate error code if error was found. - */ -static int check_nsec3_node_in_zone(knot_zone_contents_t *zone, knot_node_t *node, - err_handler_t *handler) -{ - assert(handler); - const knot_node_t *nsec3_node = knot_node_nsec3_node(node, 0); - - if (nsec3_node == NULL) { - /* I know it's probably not what RFCs say, but it will have to - * do for now. */ - if (knot_node_rrset(node, KNOT_RRTYPE_DS) != NULL) { - err_handler_handle_error(handler, node, - ZC_ERR_NSEC3_UNSECURED_DELEGATION); - } else { - /* Unsecured delegation, check whether it is part of - * opt-out span */ - const knot_node_t *nsec3_previous; - const knot_node_t *nsec3_node; - - if (knot_zone_contents_find_nsec3_for_name(zone, - knot_node_owner(node), - &nsec3_node, - &nsec3_previous, 0) != 0) { - err_handler_handle_error(handler, node, - ZC_ERR_NSEC3_NOT_FOUND); - } - - if (nsec3_node == NULL) { - /* Probably should not ever happen */ - return ZC_ERR_NSEC3_NOT_FOUND; - } - - assert(nsec3_previous); - - const knot_rrset_t *previous_rrset = - knot_node_rrset(nsec3_previous, - KNOT_RRTYPE_NSEC3); - - assert(previous_rrset); - - /* check for Opt-Out flag */ - uint8_t flags = - ((uint8_t *)(previous_rrset->rdata->items[1].raw_data))[2]; - - uint8_t opt_out_mask = 1; - - if (!(flags & opt_out_mask)) { - err_handler_handle_error(handler, node, - ZC_ERR_NSEC3_UNSECURED_DELEGATION_OPT); - } - } - } - - const knot_rrset_t *nsec3_rrset = - knot_node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3); - - assert(nsec3_rrset); - - const knot_rrset_t *soa_rrset = - knot_node_rrset(knot_zone_contents_apex(zone), - KNOT_RRTYPE_SOA); - assert(soa_rrset); - - uint32_t minimum_ttl = - knot_wire_read_u32((uint8_t *) - rdata_item_data( - knot_rdata_item( - knot_rrset_rdata( - knot_node_rrset( - knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA)), 6))); - /* Are those getters even worth this? - * Now I have no idea what this code does. */ - - if (knot_rrset_ttl(nsec3_rrset) != minimum_ttl) { - err_handler_handle_error(handler, node, - ZC_ERR_NSEC3_RDATA_TTL); - } - - /* check that next dname is in the zone */ - uint8_t *next_dname_decoded = NULL; - size_t real_size = 0; - - if (((real_size = base32hex_encode_alloc(((char *) - rdata_item_data(&(nsec3_rrset->rdata->items[4]))) + 1, - rdata_item_size(&nsec3_rrset->rdata->items[4]) - 1, - (char **)&next_dname_decoded)) <= 0) || - (next_dname_decoded == NULL)) { - fprintf(stderr, "Could not encode base32 string!\n"); - return KNOT_ERROR; - } - - /* This is why we allocate maximum length of decoded string + 1 */ - memmove(next_dname_decoded + 1, next_dname_decoded, real_size); - next_dname_decoded[0] = real_size; - - /* Local allocation, will be discarded. */ - knot_dname_t *next_dname = - knot_dname_new_from_wire(next_dname_decoded, - real_size + 1, NULL); - CHECK_ALLOC_LOG(next_dname, KNOT_ENOMEM); - - free(next_dname_decoded); - - if (knot_dname_cat(next_dname, - knot_node_owner(knot_zone_contents_apex(zone))) == NULL) { - fprintf(stderr, "Could not concatenate dnames!\n"); - return KNOT_ERROR; - - } - - if (knot_zone_contents_find_nsec3_node(zone, next_dname) == NULL) { - err_handler_handle_error(handler, node, - ZC_ERR_NSEC3_RDATA_CHAIN); - } - - /* Directly discard. */ - knot_dname_free(&next_dname); - - /* This is probably not sufficient, but again, it is covered in - * zone load time */ - - uint count; - uint16_t *array = NULL; - if (rdata_nsec_to_type_array( - knot_rdata_item( - knot_rrset_rdata(nsec3_rrset), 5), - &array, &count) != 0) { - err_handler_handle_error(handler, node, - ZC_ERR_ALLOC); - return KNOT_ERROR; - } - - uint16_t type = 0; - for (int j = 0; j < count; j++) { - /* test for each type's presence */ - type = array[j]; - if (type == KNOT_RRTYPE_RRSIG) { - continue; - } - if (knot_node_rrset(node, - type) == NULL) { - err_handler_handle_error(handler, node, - ZC_ERR_NSEC3_RDATA_BITMAP); - break; -/* char *name = - knot_dname_to_str( - log_zone_error("Node %s does " - "not contain RRSet of type %s " - "but NSEC bitmap says " - "it does!\n", name, - knot_rrtype_to_string(type)); - free(name); */ - } - } - - free(array); - - return KNOT_EOK; -} - -/*! - * \brief Run semantic checks for node without DNSSEC-related types. - * - * \param zone Current zone. - * \param node Node to be checked. - * \param do_checks Level of checks to be done. - * \param handler Error handler. - * - * \retval KNOT_EOK if no error was found. - * - * \return Appropriate error code if error was found. - */ -static int semantic_checks_plain(knot_zone_contents_t *zone, - knot_node_t *node, - char do_checks, - err_handler_t *handler) -{ - assert(handler); - const knot_rrset_t *cname_rrset = - knot_node_rrset(node, KNOT_RRTYPE_CNAME); - if (cname_rrset != NULL) { - if (check_cname_cycles_in_zone(zone, cname_rrset) != - KNOT_EOK) { - err_handler_handle_error(handler, node, - ZC_ERR_CNAME_CYCLE); - } - - /* No DNSSEC and yet there is more than one rrset in node */ - if (do_checks == 1 && - knot_node_rrset_count(node) != 1) { - err_handler_handle_error(handler, node, - ZC_ERR_CNAME_EXTRA_RECORDS); - } else if (knot_node_rrset_count(node) != 1) { - /* With DNSSEC node can contain RRSIG or NSEC */ - if (!(knot_node_rrset(node, KNOT_RRTYPE_RRSIG) || - knot_node_rrset(node, KNOT_RRTYPE_NSEC)) || - knot_node_rrset_count(node) > 3) { - err_handler_handle_error(handler, node, - ZC_ERR_CNAME_EXTRA_RECORDS_DNSSEC); - } - } - - if (knot_rrset_rdata(cname_rrset)->next != - knot_rrset_rdata(cname_rrset)) { - err_handler_handle_error(handler, node, - ZC_ERR_CNAME_MULTIPLE); - } - } - - const knot_rrset_t *dname_rrset = - knot_node_rrset(node, KNOT_RRTYPE_DNAME); - if (dname_rrset != NULL) { - if (check_cname_cycles_in_zone(zone, dname_rrset) != - KNOT_EOK) { - err_handler_handle_error(handler, node, - ZC_ERR_DNAME_CYCLE); - } - - if (knot_node_rrset(node, KNOT_RRTYPE_CNAME)) { - err_handler_handle_error(handler, node, - ZC_ERR_DNAME_EXTRA_RECORDS); - } - - if (knot_rrset_rdata(dname_rrset)->next != - knot_rrset_rdata(dname_rrset)) { - err_handler_handle_error(handler, node, - ZC_ERR_DNAME_MULTIPLE); - } - } - - /* check for glue records at zone cuts */ - if (knot_node_is_deleg_point(node)) { - const knot_rrset_t *ns_rrset = - knot_node_rrset(node, KNOT_RRTYPE_NS); - assert(ns_rrset); - //FIXME this should be an error as well ! (i guess) - - const knot_dname_t *ns_dname = - knot_rdata_get_item(knot_rrset_rdata - (ns_rrset), 0)->dname; - - assert(ns_dname); - - const knot_node_t *glue_node = - knot_zone_contents_find_node(zone, ns_dname); - - if (knot_dname_is_subdomain(ns_dname, - knot_node_owner(knot_zone_contents_apex(zone)))) { - if (glue_node == NULL) { - err_handler_handle_error(handler, node, - ZC_ERR_GLUE_NODE); - } else { - if ((knot_node_rrset(glue_node, - KNOT_RRTYPE_A) == NULL) && - (knot_node_rrset(glue_node, - KNOT_RRTYPE_AAAA) == NULL)) { - err_handler_handle_error(handler, node, - ZC_ERR_GLUE_RECORD); - } - } - } - } - return KNOT_EOK; -} - -/*! - * \brief Run semantic checks for node without DNSSEC-related types. - * - * \param zone Current zone. - * \param node Node to be checked. - * \param first_node First node in canonical order. - * \param last_node Last node in canonical order. - * \param handler Error handler. - * \param nsec3 NSEC3 used. - * - * \retval KNOT_EOK if no error was found. - * - * \return Appropriate error code if error was found. - */ -static int semantic_checks_dnssec(knot_zone_contents_t *zone, - knot_node_t *node, - knot_node_t *first_node, - knot_node_t **last_node, - err_handler_t *handler, - char nsec3) -{ - assert(handler); - assert(node); - char auth = !knot_node_is_non_auth(node); - char deleg = knot_node_is_deleg_point(node); - uint rrset_count = knot_node_rrset_count(node); - const knot_rrset_t **rrsets = knot_node_rrsets(node); - const knot_rrset_t *dnskey_rrset = - knot_node_rrset(knot_zone_contents_apex(zone), - KNOT_RRTYPE_DNSKEY); - - int ret = 0; - - for (int i = 0; i < rrset_count; i++) { - const knot_rrset_t *rrset = rrsets[i]; - if (auth && !deleg && - (ret = check_rrsig_in_rrset(rrset, dnskey_rrset, - nsec3)) != 0) { - /* CLEANUP */ -/* log_zone_error("RRSIG %d node %s\n", ret, - knot_dname_to_str(node->owner));*/ - - err_handler_handle_error(handler, node, ret); - } - - if (!nsec3 && auth) { - /* check for NSEC record */ - const knot_rrset_t *nsec_rrset = - knot_node_rrset(node, - KNOT_RRTYPE_NSEC); - - if (nsec_rrset == NULL) { - err_handler_handle_error(handler, node, - ZC_ERR_NO_NSEC); - /* CLEANUP */ -/* char *name = - knot_dname_to_str(node->owner); - log_zone_error("Missing NSEC in node: " - "%s\n", name); - free(name); - return; */ - } else { - - /* check NSEC/NSEC3 bitmap */ - - uint count; - - uint16_t *array = NULL; - - if (rdata_nsec_to_type_array( - knot_rdata_item( - knot_rrset_rdata(nsec_rrset), - 1), - &array, &count) != 0) { - err_handler_handle_error(handler, - NULL, - ZC_ERR_ALLOC); - return ZC_ERR_ALLOC; /* ... */ - /*return; */ - } - - uint16_t type = 0; - for (int j = 0; j < count; j++) { - /* test for each type's presence */ - type = array[j]; - if (type == KNOT_RRTYPE_RRSIG) { - continue; - } - if (knot_node_rrset(node, - type) == NULL) { - err_handler_handle_error( - handler, - node, - ZC_ERR_NSEC_RDATA_BITMAP); - /* CLEANUP */ - /* char *name = - knot_dname_to_str( - knot_node_owner(node)); - - log_zone_error("Node %s does " - "not contain RRSet of type %s " - "but NSEC bitmap says " - "it does!\n", name, - knot_rrtype_to_string(type)); - - free(name); */ - } - } - free(array); - } - - /* Test that only one record is in the - * NSEC RRSet */ - - if ((nsec_rrset != NULL) && - knot_rrset_rdata(nsec_rrset)->next != - knot_rrset_rdata(nsec_rrset)) { - err_handler_handle_error(handler, - node, - ZC_ERR_NSEC_RDATA_MULTIPLE); - /* CLEANUP */ -/* char *name = - knot_dname_to_str( - knot_node_owner(node)); - log_zone_error("Node %s contains more " - "than one NSEC " - "record!\n", name); - knot_rrset_dump(nsec_rrset, 0); - free(name); */ - } - - /* - * Test that NSEC chain is coherent. - * We have already checked that every - * authoritative node contains NSEC record - * so checking should only be matter of testing - * the next link in each node. - */ - - if (nsec_rrset != NULL) { - knot_dname_t *next_domain = - knot_rdata_item( - knot_rrset_rdata(nsec_rrset), - 0)->dname; - - assert(next_domain); - - if (knot_zone_contents_find_node(zone, next_domain) == - NULL) { - err_handler_handle_error(handler, - node, - ZC_ERR_NSEC_RDATA_CHAIN); - /* CLEANUP */ -/* log_zone_error("NSEC chain is not " - "coherent!\n"); */ - } - - if (knot_dname_compare(next_domain, - knot_node_owner(knot_zone_contents_apex(zone))) - == 0) { - /* saving the last node */ - *last_node = node; - } - - } - } else if (nsec3 && (auth || deleg)) { /* nsec3 */ - int ret = check_nsec3_node_in_zone(zone, node, - handler); - if (ret != KNOT_EOK) { - free(rrsets); - return ret; - } - } - } - free(rrsets); - - return KNOT_EOK; -} - -/*! - * \brief Function called by zone traversal function. Used to call - * knot_zone_save_enclosers. - * - * \param node Node to be searched. - * \param data Arguments. - */ -static void do_checks_in_tree(knot_node_t *node, void *data) -{ - assert(data != NULL); - arg_t *args = (arg_t *)data; - - knot_rrset_t **rrsets = knot_node_get_rrsets(node); - short count = knot_node_rrset_count(node); - - assert(count == 0 || rrsets != NULL); - - knot_zone_contents_t *zone = (knot_zone_contents_t *)args->arg1; - - assert(zone); - - -/* for (int i = 0; i < count; ++i) { - assert(rrsets[i] != NULL); - knot_zone_save_enclosers_rrset(rrsets[i], - zone, - (skip_list_t *)args->arg2); - } */ - - knot_node_t *first_node = (knot_node_t *)args->arg4; - knot_node_t **last_node = (knot_node_t **)args->arg5; - - err_handler_t *handler = (err_handler_t *)args->arg6; - - char do_checks = *((char *)(args->arg3)); - - if (do_checks) { - semantic_checks_plain(zone, node, do_checks, handler); - } - - if (do_checks > 1) { - semantic_checks_dnssec(zone, node, first_node, last_node, - handler, do_checks == 3); - } - - free(rrsets); -} - -/*! - * \brief Helper function - wraps its arguments into arg_t structure and - * calls function that does the actual work. - * - * \param zone Zone to be searched / checked - * \param list Skip list of closests enclosers. - * \param do_checks Level of semantic checks. - * \param handler Semantic error handler. - * \param last_node Last checked node, which is part of NSEC(3) chain. - */ -void zone_do_sem_checks(knot_zone_contents_t *zone, char do_checks, - err_handler_t *handler, - knot_node_t **last_node) -{ - if (!do_checks) { - return; - } - - arg_t arguments; - arguments.arg1 = zone; - arguments.arg3 = &do_checks; - arguments.arg4 = NULL; - arguments.arg5 = last_node; - arguments.arg6 = handler; - - knot_zone_contents_tree_apply_inorder(zone, - do_checks_in_tree, - (void *)&arguments); -} - static inline int fwrite_to_file_crc(const void *src, size_t size, size_t n, FILE *f, crc_t *crc) @@ -1833,125 +495,6 @@ static int zone_is_secure(knot_zone_contents_t *zone) } } -/*! - * \brief Checks if last node in NSEC/NSEC3 chain points to first node in the - * chain and prints possible errors. - * - * \param handler Semantic error handler. - * \param zone Current zone. - * \param last_node Last node in NSEC/NSEC3 chain. - * \param do_checks Level of semantic checks. - */ -static void log_cyclic_errors_in_zone(err_handler_t *handler, - knot_zone_contents_t *zone, - knot_node_t *last_node, - const knot_node_t *first_nsec3_node, - const knot_node_t *last_nsec3_node, - char do_checks) -{ - if (do_checks == 3) { - /* Each NSEC3 node should only contain one RRSET. */ - assert(last_nsec3_node && first_nsec3_node); - const knot_rrset_t *nsec3_rrset = - knot_node_rrset(last_nsec3_node, - KNOT_RRTYPE_NSEC3); - if (nsec3_rrset == NULL) { - err_handler_handle_error(handler, last_nsec3_node, - ZC_ERR_NSEC3_RDATA_CHAIN); - return; - } - - /* check that next dname is in the zone */ - uint8_t *next_dname_decoded = NULL; - size_t real_size = 0; - - if (((real_size = base32hex_encode_alloc(((char *) - rdata_item_data(&(nsec3_rrset->rdata->items[4]))) + 1, - rdata_item_size(&nsec3_rrset->rdata->items[4]) - 1, - (char **)&next_dname_decoded)) <= 0) || - (next_dname_decoded == NULL)) { - fprintf(stderr, "Could not encode base32 string!\n"); - err_handler_handle_error(handler, last_nsec3_node, - ZC_ERR_NSEC3_RDATA_CHAIN); - return; - } - - /* This is why allocate maximum length of decoded string + 1 */ - memmove(next_dname_decoded + 1, next_dname_decoded, real_size); - next_dname_decoded[0] = real_size; - - /* Local allocation, will be discarded. */ - knot_dname_t *next_dname = - knot_dname_new_from_wire(next_dname_decoded, - real_size + 1, NULL); - if (next_dname == NULL) { - fprintf(stderr, "Could not allocate dname!\n"); - err_handler_handle_error(handler, last_nsec3_node, - ZC_ERR_ALLOC); - return; - } - - free(next_dname_decoded); - - /*! \todo Free result and dname! */ - if (knot_dname_cat(next_dname, - knot_node_owner(knot_zone_contents_apex(zone))) == - NULL) { - fprintf(stderr, "Could not concatenate dnames!\n"); - err_handler_handle_error(handler, last_nsec3_node, - ZC_ERR_NSEC3_RDATA_CHAIN); - return; - } - - /* Check it points somewhere first. */ - if (knot_zone_contents_find_nsec3_node(zone, next_dname) == NULL) { - err_handler_handle_error(handler, last_nsec3_node, - ZC_ERR_NSEC3_RDATA_CHAIN); - } - - /* Compare with the actual first NSEC3 node. */ - if (knot_dname_compare(first_nsec3_node->owner, - next_dname) != 0) { - err_handler_handle_error(handler, last_nsec3_node, - ZC_ERR_NSEC3_RDATA_CHAIN); - } - - /* Directly discard. */ - knot_dname_free(&next_dname); - - } else if (do_checks == 2 ) { - if (last_node == NULL) { - err_handler_handle_error(handler, last_node, - ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC); - return; - } else { - const knot_rrset_t *nsec_rrset = - knot_node_rrset(last_node, - KNOT_RRTYPE_NSEC); - - if (nsec_rrset == NULL) { - err_handler_handle_error(handler, last_node, - ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC); - return; - } - - const knot_dname_t *next_dname = - knot_rdata_item( - knot_rrset_rdata(nsec_rrset), 0)->dname; - assert(next_dname); - - const knot_dname_t *apex_dname = - knot_node_owner(knot_zone_contents_apex(zone)); - assert(apex_dname); - - if (knot_dname_compare(next_dname, apex_dname) !=0) { - err_handler_handle_error(handler, last_node, - ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC); - } - } - } -} - /*! * \brief Safe wrapper around fwrite. *