diff options
Diffstat (limited to 'src/libknot/updates/ddns.c')
-rw-r--r-- | src/libknot/updates/ddns.c | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/src/libknot/updates/ddns.c b/src/libknot/updates/ddns.c new file mode 100644 index 0000000..4c6ab7b --- /dev/null +++ b/src/libknot/updates/ddns.c @@ -0,0 +1,638 @@ +/* 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/>. + */ + +#include <assert.h> + +#include "updates/ddns.h" +#include "updates/changesets.h" +#include "util/debug.h" +#include "packet/packet.h" +#include "util/error.h" +#include "consts.h" + +/*----------------------------------------------------------------------------*/ +// Copied from XFR - maybe extract somewhere else +static int knot_ddns_prereq_check_rrsets(knot_rrset_t ***rrsets, + size_t *count, size_t *allocated) +{ + int new_count = 0; + if (*count == *allocated) { + new_count = *allocated * 2; + } + + knot_rrset_t **rrsets_new = + (knot_rrset_t **)calloc(new_count, sizeof(knot_rrset_t *)); + if (rrsets_new == NULL) { + return KNOT_ENOMEM; + } + + memcpy(rrsets_new, *rrsets, *count); + *rrsets = rrsets_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_prereq_check_dnames(knot_dname_t ***dnames, + size_t *count, size_t *allocated) +{ + int new_count = 0; + if (*count == *allocated) { + new_count = *allocated * 2; + } + + knot_dname_t **dnames_new = + (knot_dname_t **)calloc(new_count, sizeof(knot_dname_t *)); + if (dnames_new == NULL) { + return KNOT_ENOMEM; + } + + memcpy(dnames_new, *dnames, *count); + *dnames = dnames_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_prereq_rrset(const knot_rrset_t *rrset, + knot_rrset_t ***rrsets, + size_t *count, size_t *allocd) +{ + // check if such RRSet is not already there and merge if needed + int ret; + for (int i = 0; i < *count; ++i) { + if (knot_rrset_compare(rrset, (*rrsets)[i], + KNOT_RRSET_COMPARE_HEADER) == 0) { + ret = knot_rrset_merge((void **)&((*rrsets)[i]), + (void **)&rrset); + if (ret != KNOT_EOK) { + return ret; + } else { + return KNOT_EOK; + } + } + } + + // if we are here, the RRSet was not found + ret = knot_ddns_prereq_check_rrsets(rrsets, count, allocd); + if (ret != KNOT_EOK) { + return ret; + } + + knot_rrset_t *new_rrset = NULL; + ret = knot_rrset_deep_copy(rrset, &new_rrset); + if (ret != KNOT_EOK) { + return ret; + } + + (*rrsets)[(*count)++] = new_rrset; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_prereq_dname(const knot_dname_t *dname, + knot_dname_t ***dnames, + size_t *count, size_t *allocd) +{ + // we do not have to check if the name is not already there + // if it is, we will just check it twice in the zone + + int ret = knot_ddns_prereq_check_dnames(dnames, count, allocd); + if (ret != KNOT_EOK) { + return ret; + } + + knot_dname_t *dname_new = knot_dname_deep_copy(dname); + if (dname_new == NULL) { + return KNOT_ENOMEM; + } + + (*dnames)[(*count)++] = dname_new; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_prereq(knot_ddns_prereq_t *prereqs, + const knot_rrset_t *rrset, uint16_t qclass) +{ + assert(prereqs != NULL); + assert(rrset != NULL); + + if (knot_rrset_ttl(rrset) != 0) { + return KNOT_EMALF; + } + + int ret; + + if (knot_rrset_class(rrset) == KNOT_CLASS_ANY) { + if (knot_rrset_rdata(rrset) != NULL) { + return KNOT_EMALF; + } + if (knot_rrset_type(rrset) == KNOT_RRTYPE_ANY) { + ret = knot_ddns_add_prereq_dname( + knot_rrset_owner(rrset), &prereqs->in_use, + &prereqs->in_use_count, + &prereqs->in_use_allocd); + } else { + ret = knot_ddns_add_prereq_rrset(rrset, + &prereqs->exist, + &prereqs->exist_count, + &prereqs->exist_allocd); + } + } else if (knot_rrset_class(rrset) == KNOT_CLASS_NONE) { + if (knot_rrset_rdata(rrset) != NULL) { + return KNOT_EMALF; + } + if (knot_rrset_type(rrset) == KNOT_RRTYPE_ANY) { + ret = knot_ddns_add_prereq_dname( + knot_rrset_owner(rrset), &prereqs->not_in_use, + &prereqs->not_in_use_count, + &prereqs->not_in_use_allocd); + } else { + ret = knot_ddns_add_prereq_rrset(rrset, + &prereqs->not_exist, + &prereqs->not_exist_count, + &prereqs->not_exist_allocd); + } + } else if (knot_rrset_class(rrset) == qclass) { + ret = knot_ddns_add_prereq_rrset(rrset, + &prereqs->exist_full, + &prereqs->exist_full_count, + &prereqs->exist_full_allocd); + } else { + return KNOT_EMALF; + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_update(knot_changeset_t *changeset, + const knot_rrset_t *rrset, uint16_t qclass) +{ + assert(changeset != NULL); + assert(rrset != NULL); + + int ret; + + // create a copy of the RRSet + /*! \todo If the packet was not parsed all at once, we could save this + * copy. + */ + knot_rrset_t *rrset_copy; + ret = knot_rrset_deep_copy(rrset, &rrset_copy); + if (ret != KNOT_EOK) { + return ret; + } + + /*! \todo What about the SOAs? */ + + if (knot_rrset_class(rrset) == qclass) { + // this RRSet should be added to the zone + ret = knot_changeset_add_rr(&changeset->add, + &changeset->add_count, + &changeset->add_allocated, + rrset_copy); + } else { + // this RRSet marks removal of something from zone + // what should be removed is distinguished when applying + ret = knot_changeset_add_rr(&changeset->remove, + &changeset->remove_count, + &changeset->remove_allocated, + rrset_copy); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_exist(const knot_zone_contents_t *zone, + const knot_rrset_t *rrset, uint8_t *rcode) +{ + assert(zone != NULL); + assert(rrset != NULL); + assert(rcode != NULL); + assert(knot_rrset_rdata(rrset) == NULL); + assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY); + assert(knot_rrset_ttl(rrset) == 0); + assert(knot_rrset_class(rrset) == KNOT_CLASS_ANY); + + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset)); + if (node == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_ENONODE; + } else if (knot_node_rrset(node, knot_rrset_type(rrset)) == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_ENORRSET; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone, + const knot_rrset_t *rrset, uint8_t *rcode) +{ + assert(zone != NULL); + assert(rrset != NULL); + assert(rcode != NULL); + assert(knot_rrset_rdata(rrset) == NULL); + assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY); + assert(knot_rrset_ttl(rrset) == 0); + assert(knot_rrset_class(rrset) == KNOT_CLASS_ANY); + + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + const knot_rrset_t *found; + + node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset)); + if (node == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_EPREREQ; + } else if ((found = knot_node_rrset(node, knot_rrset_type(rrset))) + == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_EPREREQ; + } else { + // do not have to compare the header, it is already done + assert(knot_rrset_type(found) == knot_rrset_type(rrset)); + assert(knot_dname_compare(knot_rrset_owner(found), + knot_rrset_owner(rrset)) == 0); + if (knot_rrset_compare_rdata(found, rrset) <= 0) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_EPREREQ; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_not_exist(const knot_zone_contents_t *zone, + const knot_rrset_t *rrset, uint8_t *rcode) +{ + assert(zone != NULL); + assert(rrset != NULL); + assert(rcode != NULL); + assert(knot_rrset_rdata(rrset) == NULL); + assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY); + assert(knot_rrset_ttl(rrset) == 0); + assert(knot_rrset_class(rrset) == KNOT_CLASS_NONE); + + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + const knot_rrset_t *found; + + node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset)); + if (node == NULL) { + return KNOT_EOK; + } else if ((found = knot_node_rrset(node, knot_rrset_type(rrset))) + == NULL) { + return KNOT_EOK; + } else { + // do not have to compare the header, it is already done + assert(knot_rrset_type(found) == knot_rrset_type(rrset)); + assert(knot_dname_compare(knot_rrset_owner(found), + knot_rrset_owner(rrset)) == 0); + if (knot_rrset_compare_rdata(found, rrset) <= 0) { + return KNOT_EOK; + } + } + + *rcode = KNOT_RCODE_YXRRSET; + return KNOT_EPREREQ; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_in_use(const knot_zone_contents_t *zone, + const knot_dname_t *dname, uint8_t *rcode) +{ + assert(zone != NULL); + assert(dname != NULL); + assert(rcode != NULL); + + if (!knot_dname_is_subdomain(dname, + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + + node = knot_zone_contents_find_node(zone, dname); + if (node == NULL) { + *rcode = KNOT_RCODE_NXDOMAIN; + return KNOT_EPREREQ; + } else if (knot_node_rrset_count(node) == 0) { + *rcode = KNOT_RCODE_NXDOMAIN; + return KNOT_EPREREQ; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_not_in_use(const knot_zone_contents_t *zone, + const knot_dname_t *dname, uint8_t *rcode) +{ + assert(zone != NULL); + assert(dname != NULL); + assert(rcode != NULL); + + if (!knot_dname_is_subdomain(dname, + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + + node = knot_zone_contents_find_node(zone, dname); + if (node == NULL) { + return KNOT_EOK; + } else if (knot_node_rrset_count(node) == 0) { + return KNOT_EOK; + } + + *rcode = KNOT_RCODE_YXDOMAIN; + return KNOT_EPREREQ; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int knot_ddns_check_zone(const knot_zone_t *zone, knot_packet_t *query, + uint8_t *rcode) +{ + if (zone == NULL || query == NULL || rcode == NULL) { + return KNOT_EBADARG; + } + + if (knot_packet_qtype(query) != KNOT_RRTYPE_SOA) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + + if(!knot_zone_contents(zone)) { + *rcode = KNOT_RCODE_REFUSED; + return KNOT_ENOZONE; + } + + // 1) check if the zone is master or slave + if (!knot_zone_is_master(zone)) { + return KNOT_EBADZONE; + } + + // 2) check zone CLASS + if (knot_zone_contents_class(knot_zone_contents(zone)) != + knot_packet_qclass(query)) { + *rcode = KNOT_RCODE_NOTAUTH; + return KNOT_ENOZONE; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ddns_process_prereqs(knot_packet_t *query, + knot_ddns_prereq_t **prereqs, uint8_t *rcode) +{ + /*! \todo Consider not parsing the whole packet at once, but + * parsing one RR at a time - could save some memory and time. + */ + + if (query == NULL || prereqs == NULL || rcode == NULL) { + return KNOT_EBADARG; + } + + // allocate space for the prerequisities + *prereqs = (knot_ddns_prereq_t *)calloc(1, sizeof(knot_ddns_prereq_t)); + CHECK_ALLOC_LOG(*prereqs, KNOT_ENOMEM); + + int ret; + + for (int i = 0; i < knot_packet_answer_rrset_count(query); ++i) { + // we must copy the RRSets, because all those stored in the + // packet will be destroyed + ret = knot_ddns_add_prereq(*prereqs, + knot_packet_answer_rrset(query, i), + knot_packet_qclass(query)); + if (ret != KNOT_EOK) { + dbg_ddns("Failed to add prerequisity RRSet:%s\n", + knot_strerror(ret)); + *rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL; + knot_ddns_prereqs_free(prereqs); + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ddns_check_prereqs(const knot_zone_contents_t *zone, + knot_ddns_prereq_t **prereqs, uint8_t *rcode) +{ + int i, ret; + + for (i = 0; i < (*prereqs)->exist_count; ++i) { + ret = knot_ddns_check_exist(zone, (*prereqs)->exist[i], rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->exist_full_count; ++i) { + ret = knot_ddns_check_exist_full(zone, + (*prereqs)->exist_full[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->not_exist_count; ++i) { + ret = knot_ddns_check_not_exist(zone, (*prereqs)->not_exist[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->in_use_count; ++i) { + ret = knot_ddns_check_in_use(zone, (*prereqs)->in_use[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->not_in_use_count; ++i) { + ret = knot_ddns_check_not_in_use(zone, + (*prereqs)->not_in_use[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_update(const knot_rrset_t *rrset, + const knot_packet_t *query, uint8_t *rcode) +{ + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_packet_qname(query))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + if (knot_rrset_class(rrset) == knot_packet_qclass(query)) { + if (knot_rrtype_is_metatype(knot_rrset_type(rrset))) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else if (knot_rrset_class(rrset) == KNOT_CLASS_ANY) { + if (knot_rrset_rdata(rrset) != NULL + || (knot_rrtype_is_metatype(knot_rrset_type(rrset)) + && knot_rrset_type(rrset) != KNOT_RRTYPE_ANY)) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else if (knot_rrset_class(rrset) == KNOT_CLASS_NONE) { + if (knot_rrset_ttl(rrset) != 0 + || knot_rrtype_is_metatype(knot_rrset_type(rrset))) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ddns_process_update(knot_packet_t *query, + knot_changeset_t **changeset, uint8_t *rcode) +{ + // just put all RRSets from query's Authority section + // it will be distinguished when applying to the zone + + if (query == NULL || changeset == NULL || rcode == NULL) { + return KNOT_EBADARG; + } + + *changeset = (knot_changeset_t *)calloc(1, sizeof(knot_changeset_t)); + CHECK_ALLOC_LOG(*changeset, KNOT_ENOMEM); + + int ret; + + for (int i = 0; i < knot_packet_authority_rrset_count(query); ++i) { + + const knot_rrset_t *rrset = + knot_packet_authority_rrset(query, i); + + ret = knot_ddns_check_update(rrset, query, rcode); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_ddns_add_update(*changeset, rrset, + knot_packet_qclass(query)); + + if (ret != KNOT_EOK) { + dbg_ddns("Failed to add update RRSet:%s\n", + knot_strerror(ret)); + *rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL; + knot_free_changeset(changeset); + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ddns_prereqs_free(knot_ddns_prereq_t **prereq) +{ + int i; + + for (i = 0; i < (*prereq)->exist_count; ++i) { + knot_rrset_deep_free(&(*prereq)->exist[i], 1, 1, 1); + } + + for (i = 0; i < (*prereq)->exist_full_count; ++i) { + knot_rrset_deep_free(&(*prereq)->exist_full[i], 1, 1, 1); + } + + for (i = 0; i < (*prereq)->not_exist_count; ++i) { + knot_rrset_deep_free(&(*prereq)->not_exist[i], 1, 1, 1); + } + + for (i = 0; i < (*prereq)->in_use_count; ++i) { + knot_dname_free(&(*prereq)->in_use[i]); + } + + for (i = 0; i < (*prereq)->not_in_use_count; ++i) { + knot_dname_free(&(*prereq)->not_in_use[i]); + } + + free(*prereq); + *prereq = NULL; +} |