diff options
Diffstat (limited to 'src/knot/zone/zonefile.c')
-rw-r--r-- | src/knot/zone/zonefile.c | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/src/knot/zone/zonefile.c b/src/knot/zone/zonefile.c new file mode 100644 index 0000000..e545497 --- /dev/null +++ b/src/knot/zone/zonefile.c @@ -0,0 +1,371 @@ +/* Copyright (C) 2023 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> +#include <inttypes.h> + +#include "libknot/libknot.h" +#include "contrib/files.h" +#include "knot/common/log.h" +#include "knot/dnssec/zone-nsec.h" +#include "knot/zone/semantic-check.h" +#include "knot/zone/adjust.h" +#include "knot/zone/contents.h" +#include "knot/zone/zonefile.h" +#include "knot/zone/zone-dump.h" + +#define ERROR(zone, fmt, ...) log_zone_error(zone, "zone loader, " fmt, ##__VA_ARGS__) +#define WARNING(zone, fmt, ...) log_zone_warning(zone, "zone loader, " fmt, ##__VA_ARGS__) +#define NOTICE(zone, fmt, ...) log_zone_notice(zone, "zone loader, " fmt, ##__VA_ARGS__) + +static void process_error(zs_scanner_t *s) +{ + zcreator_t *zc = s->process.data; + const knot_dname_t *zname = zc->z->apex->owner; + + ERROR(zname, "%s in zone, file '%s', line %"PRIu64" (%s)", + s->error.fatal ? "fatal error" : "error", + s->file.name, s->line_counter, + zs_strerror(s->error.code)); +} + +static bool handle_err(zcreator_t *zc, const knot_rrset_t *rr, int ret, bool master) +{ + const knot_dname_t *zname = zc->z->apex->owner; + + knot_dname_txt_storage_t buff; + char *owner = knot_dname_to_str(buff, rr->owner, sizeof(buff)); + if (owner == NULL) { + owner = ""; + } + + if (ret == KNOT_EOUTOFZONE) { + WARNING(zname, "ignoring out-of-zone data, owner %s", owner); + return true; + } else if (ret == KNOT_ETTL) { + char type[16] = ""; + knot_rrtype_to_string(rr->type, type, sizeof(type)); + NOTICE(zname, "TTL mismatch, owner %s, type %s, TTL set to %u", + owner, type, rr->ttl); + return true; + } else { + ERROR(zname, "failed to process record, owner %s", owner); + return false; + } +} + +int zcreator_step(zcreator_t *zc, const knot_rrset_t *rr) +{ + if (zc == NULL || rr == NULL || rr->rrs.count != 1) { + return KNOT_EINVAL; + } + + if (rr->type == KNOT_RRTYPE_SOA && + node_rrtype_exists(zc->z->apex, KNOT_RRTYPE_SOA)) { + // Ignore extra SOA + return KNOT_EOK; + } + + zone_node_t *node = NULL; + int ret = zone_contents_add_rr(zc->z, rr, &node); + if (ret != KNOT_EOK) { + if (!handle_err(zc, rr, ret, zc->master)) { + // Fatal error + return ret; + } + } + + return KNOT_EOK; +} + +/*! \brief Creates RR from parser input, passes it to handling function. */ +static void process_data(zs_scanner_t *scanner) +{ + zcreator_t *zc = scanner->process.data; + if (zc->ret != KNOT_EOK) { + scanner->state = ZS_STATE_STOP; + return; + } + + knot_dname_t *owner = knot_dname_copy(scanner->r_owner, NULL); + if (owner == NULL) { + zc->ret = KNOT_ENOMEM; + return; + } + + knot_rrset_t rr; + knot_rrset_init(&rr, owner, scanner->r_type, scanner->r_class, scanner->r_ttl); + + int ret = knot_rrset_add_rdata(&rr, scanner->r_data, scanner->r_data_length, NULL); + if (ret != KNOT_EOK) { + knot_rrset_clear(&rr, NULL); + zc->ret = ret; + return; + } + + /* Convert RDATA dnames to lowercase before adding to zone. */ + ret = knot_rrset_rr_to_canonical(&rr); + if (ret != KNOT_EOK) { + knot_rrset_clear(&rr, NULL); + zc->ret = ret; + return; + } + + zc->ret = zcreator_step(zc, &rr); + knot_rrset_clear(&rr, NULL); +} + +int zonefile_open(zloader_t *loader, const char *source, + const knot_dname_t *origin, semcheck_optional_t semantic_checks, time_t time) +{ + if (!loader) { + return KNOT_EINVAL; + } + + memset(loader, 0, sizeof(zloader_t)); + + /* Check zone file. */ + if (access(source, F_OK | R_OK) != 0) { + return knot_map_errno(); + } + + /* Create context. */ + zcreator_t *zc = malloc(sizeof(zcreator_t)); + if (zc == NULL) { + return KNOT_ENOMEM; + } + memset(zc, 0, sizeof(zcreator_t)); + + zc->z = zone_contents_new(origin, true); + if (zc->z == NULL) { + free(zc); + return KNOT_ENOMEM; + } + + /* Prepare textual owner for zone scanner. */ + char *origin_str = knot_dname_to_str_alloc(origin); + if (origin_str == NULL) { + zone_contents_deep_free(zc->z); + free(zc); + return KNOT_ENOMEM; + } + + if (zs_init(&loader->scanner, origin_str, KNOT_CLASS_IN, 3600) != 0 || + zs_set_input_file(&loader->scanner, source) != 0 || + zs_set_processing(&loader->scanner, process_data, process_error, zc) != 0) { + zs_deinit(&loader->scanner); + free(origin_str); + zone_contents_deep_free(zc->z); + free(zc); + return KNOT_EFILE; + } + free(origin_str); + + loader->source = strdup(source); + loader->creator = zc; + loader->semantic_checks = semantic_checks; + loader->time = time; + + return KNOT_EOK; +} + +zone_contents_t *zonefile_load(zloader_t *loader) +{ + if (!loader) { + return NULL; + } + + zcreator_t *zc = loader->creator; + const knot_dname_t *zname = zc->z->apex->owner; + + assert(zc); + int ret = zs_parse_all(&loader->scanner); + if (ret != 0 && loader->scanner.error.counter == 0) { + ERROR(zname, "failed to load zone, file '%s' (%s)", + loader->source, zs_strerror(loader->scanner.error.code)); + goto fail; + } + + if (zc->ret != KNOT_EOK) { + ERROR(zname, "failed to load zone, file '%s' (%s)", + loader->source, knot_strerror(zc->ret)); + goto fail; + } + + if (loader->scanner.error.counter > 0) { + ERROR(zname, "failed to load zone, file '%s', %"PRIu64" errors", + loader->source, loader->scanner.error.counter); + goto fail; + } + + if (!node_rrtype_exists(loader->creator->z->apex, KNOT_RRTYPE_SOA)) { + loader->err_handler->error = true; + loader->err_handler->cb(loader->err_handler, zc->z, NULL, + SEM_ERR_SOA_NONE, NULL); + goto fail; + } + + ret = zone_adjust_contents(zc->z, adjust_cb_flags_and_nsec3, adjust_cb_nsec3_flags, + true, true, 1, NULL); + if (ret != KNOT_EOK) { + ERROR(zname, "failed to finalize zone contents (%s)", + knot_strerror(ret)); + goto fail; + } + + ret = sem_checks_process(zc->z, loader->semantic_checks, + loader->err_handler, loader->time); + + if (ret != KNOT_EOK) { + ERROR(zname, "failed to load zone, file '%s' (%s)", + loader->source, knot_strerror(ret)); + goto fail; + } + + /* The contents will now change possibly messing up NSEC3 tree, it will + be adjusted again at zone_update_commit. */ + ret = zone_adjust_contents(zc->z, unadjust_cb_point_to_nsec3, NULL, + false, false, 1, NULL); + if (ret != KNOT_EOK) { + ERROR(zname, "failed to finalize zone contents (%s)", + knot_strerror(ret)); + goto fail; + } + + return zc->z; + +fail: + zone_contents_deep_free(zc->z); + return NULL; +} + +int zonefile_exists(const char *path, struct timespec *mtime) +{ + if (path == NULL) { + return KNOT_EINVAL; + } + + struct stat zonefile_st = { 0 }; + if (stat(path, &zonefile_st) < 0) { + return knot_map_errno(); + } + + if (mtime != NULL) { + *mtime = zonefile_st.st_mtim; + } + + return KNOT_EOK; +} + +int zonefile_write(const char *path, zone_contents_t *zone) +{ + if (path == NULL) { + return KNOT_EINVAL; + } + + if (zone == NULL) { + return KNOT_EEMPTYZONE; + } + + int ret = make_path(path, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP); + if (ret != KNOT_EOK) { + return ret; + } + + FILE *file = NULL; + char *tmp_name = NULL; + ret = open_tmp_file(path, &tmp_name, &file, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); + if (ret != KNOT_EOK) { + return ret; + } + + ret = zone_dump_text(zone, file, true, NULL); + fclose(file); + if (ret != KNOT_EOK) { + unlink(tmp_name); + free(tmp_name); + return ret; + } + + /* Swap temporary zonefile and new zonefile. */ + ret = rename(tmp_name, path); + if (ret != 0) { + ret = knot_map_errno(); + unlink(tmp_name); + free(tmp_name); + return ret; + } + + free(tmp_name); + + return KNOT_EOK; +} + +void zonefile_close(zloader_t *loader) +{ + if (!loader) { + return; + } + + zs_deinit(&loader->scanner); + free(loader->source); + free(loader->creator); +} + +void err_handler_logger(sem_handler_t *handler, const zone_contents_t *zone, + const knot_dname_t *node, sem_error_t error, const char *data) +{ + assert(handler != NULL); + assert(zone != NULL); + + if (handler->error) { + handler->fatal_error = true; + } else { + handler->warning = true; + } + + knot_dname_txt_storage_t owner; + if (node != NULL) { + if (knot_dname_to_str(owner, node, sizeof(owner)) == NULL) { + owner[0] = '\0'; + } + } + + int level = handler->soft_check ? LOG_NOTICE : + (handler->error ? LOG_ERR : LOG_WARNING); + + log_fmt_zone(level, LOG_SOURCE_ZONE, zone->apex->owner, NULL, + "check%s%s, %s%s%s", + (node != NULL ? ", node " : ""), + (node != NULL ? owner : ""), + sem_error_msg(error), + (data != NULL ? " " : ""), + (data != NULL ? data : "")); + + handler->error = false; +} + +#undef ERROR +#undef WARNING +#undef NOTICE |