diff options
Diffstat (limited to 'src/utils/kzonesign/main.c')
-rw-r--r-- | src/utils/kzonesign/main.c | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/src/utils/kzonesign/main.c b/src/utils/kzonesign/main.c new file mode 100644 index 0000000..e70abb6 --- /dev/null +++ b/src/utils/kzonesign/main.c @@ -0,0 +1,283 @@ +/* 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 <getopt.h> +#include <stdlib.h> + +#include "knot/dnssec/zone-events.h" +#include "knot/updates/zone-update.h" +#include "knot/server/server.h" +#include "knot/zone/adjust.h" +#include "knot/zone/zone-load.h" +#include "knot/zone/zonefile.h" +#include "utils/common/msg.h" +#include "utils/common/params.h" +#include "utils/common/signal.h" +#include "utils/common/util_conf.h" +#include "contrib/strtonum.h" + +#define PROGRAM_NAME "kzonesign" + +signal_ctx_t signal_ctx = { 0 }; // global, needed by signal handler + +static void print_help(void) +{ + printf("Usage: %s [-c | -C <path>] [options] <zone_name>\n" + "\n" + "Config options:\n" + " -c, --config <file> Path to a textual configuration file.\n" + " (default %s)\n" + " -C, --confdb <dir> Path to a configuration database directory.\n" + " (default %s)\n" + "Options:\n" + " -o, --outdir <dir_name> Output directory.\n" + " -r, --rollover Allow key rollovers and NSEC3 re-salt.\n" + " -v, --verify Only verify if zone is signed correctly.\n" + " -t, --time <timestamp> Current time specification.\n" + " (default current UNIX time)\n" + " -h, --help Print the program help.\n" + " -V, --version Print the program version.\n", + PROGRAM_NAME, CONF_DEFAULT_FILE, CONF_DEFAULT_DBDIR); +} + +typedef struct { + const char *zone_name_str; + knot_dname_storage_t zone_name; + const char *outdir; + zone_sign_roll_flags_t rollover; + int64_t timestamp; + bool verify; +} sign_params_t; + +static int zonesign(sign_params_t *params) +{ + char *zonefile = NULL; + zone_contents_t *unsigned_conts = NULL; + zone_t *zone_struct = NULL; + zone_update_t up = { 0 }; + server_t fake_server = { 0 }; + zone_sign_reschedule_t next_sign = { 0 }; + int ret = KNOT_ERROR; + + // set the kaspdb for close in emergency + signal_ctx.close_db = &fake_server.kaspdb; + + conf_val_t val = conf_zone_get(conf(), C_DOMAIN, params->zone_name); + if (val.code != KNOT_EOK) { + ERR2("zone '%s' not configured", params->zone_name_str); + ret = KNOT_ENOENT; + goto fail; + } + val = conf_zone_get(conf(), C_DNSSEC_POLICY, params->zone_name); + if (val.code != KNOT_EOK) { + WARN2("DNSSEC policy not configured for zone '%s', taking defaults", + params->zone_name_str); + } + + zone_struct = zone_new(params->zone_name); + if (zone_struct == NULL) { + ERR2("out of memory"); + ret = KNOT_ENOMEM; + goto fail; + } + + ret = zone_load_contents(conf(), params->zone_name, &unsigned_conts, + SEMCHECK_MANDATORY_SOFT, false); + if (ret != KNOT_EOK) { + ERR2("failed to load zone contents (%s)", knot_strerror(ret)); + goto fail; + } + + ret = zone_update_from_contents(&up, zone_struct, unsigned_conts, UPDATE_FULL); + if (ret != KNOT_EOK) { + ERR2("failed to initialize zone update (%s)", knot_strerror(ret)); + zone_contents_deep_free(unsigned_conts); + goto fail; + } + + if (params->verify) { + val = conf_zone_get(conf(), C_ADJUST_THR, params->zone_name); + ret = zone_adjust_full(up.new_cont, conf_int(&val)); + if (ret != KNOT_EOK) { + ERR2("failed to adjust the zone (%s)", knot_strerror(ret)); + zone_update_clear(&up); + goto fail; + } + + ret = knot_dnssec_validate_zone(&up, conf(), params->timestamp, false); + if (ret != KNOT_EOK) { + ERR2("DNSSEC validation failed (%s)", knot_strerror(ret)); + char type_str[16]; + knot_dname_txt_storage_t name_str; + if (knot_dname_to_str(name_str, up.validation_hint.node, sizeof(name_str)) != NULL && + knot_rrtype_to_string(up.validation_hint.rrtype, type_str, sizeof(type_str)) >= 0) { + ERR2("affected node: '%s' type '%s'", name_str, type_str); + } + } else { + INFO2("DNSSEC validation successful"); + } + zone_update_clear(&up); + goto fail; + } + + kasp_db_ensure_init(&fake_server.kaspdb, conf()); + zone_struct->server = &fake_server; + + ret = knot_dnssec_zone_sign(&up, conf(), 0, params->rollover, + params->timestamp, &next_sign); + if (ret == KNOT_DNSSEC_ENOKEY) { // exception: allow generating initial keys + params->rollover = KEY_ROLL_ALLOW_ALL; + ret = knot_dnssec_zone_sign(&up, conf(), 0, params->rollover, + params->timestamp, &next_sign); + } + if (ret != KNOT_EOK) { + ERR2("failed to sign the zone (%s)", knot_strerror(ret)); + zone_update_clear(&up); + goto fail; + } + + if (params->outdir == NULL) { + zonefile = conf_zonefile(conf(), params->zone_name); + ret = zonefile_write(zonefile, up.new_cont); + } else { + zone_contents_t *temp = zone_struct->contents; + zone_struct->contents = up.new_cont; + ret = zone_dump_to_dir(conf(), zone_struct, params->outdir); + zone_struct->contents = temp; + } + zone_update_clear(&up); + if (ret != KNOT_EOK) { + if (params->outdir == NULL) { + ERR2("failed to update zone file '%s' (%s)", + zonefile, knot_strerror(ret)); + } else { + ERR2("failed to flush signed zone to '%s' file (%s)", + params->outdir, knot_strerror(ret)); + + } + goto fail; + } + + INFO2("Next signing: %"KNOT_TIME_PRINTF, next_sign.next_sign); + if (params->rollover) { + INFO2("Next roll-over: %"KNOT_TIME_PRINTF, next_sign.next_rollover); + if (next_sign.next_nsec3resalt) { + INFO2("Next NSEC3 re-salt: %"KNOT_TIME_PRINTF, next_sign.next_nsec3resalt); + } + if (next_sign.plan_ds_check) { + INFO2("KSK submission to parent zone needed"); + } + } + +fail: + if (fake_server.kaspdb.path != NULL) { + knot_lmdb_deinit(&fake_server.kaspdb); + } + zone_free(&zone_struct); + free(zonefile); + + return ret; +} + +int main(int argc, char *argv[]) +{ + sign_params_t params = { 0 }; + + struct option opts[] = { + { "config", required_argument, NULL, 'c' }, + { "confdb", required_argument, NULL, 'C' }, + { "outdir", required_argument, NULL, 'o' }, + { "rollover", no_argument, NULL, 'r' }, + { "verify" , no_argument, NULL, 'v' }, + { "time", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL } + }; + + tzset(); + signal_init_std(); + + int opt = 0; + while ((opt = getopt_long(argc, argv, "c:C:o:rvt:hV", opts, NULL)) != -1) { + switch (opt) { + case 'c': + if (util_conf_init_file(optarg) != KNOT_EOK) { + goto failure; + } + break; + case 'C': + if (util_conf_init_confdb(optarg) != KNOT_EOK) { + goto failure; + } + break; + case 'o': + params.outdir = optarg; + break; + case 'r': + params.rollover = KEY_ROLL_ALLOW_ALL; + break; + case 'v': + params.verify = true; + break; + case 't': + ; uint32_t num = 0; + if (str_to_u32(optarg, &num) != KNOT_EOK || num == 0) { + print_help(); + goto failure; + } + params.timestamp = num; + break; + case 'h': + print_help(); + goto success; + case 'V': + print_version(PROGRAM_NAME); + goto success; + default: + print_help(); + goto failure; + } + } + if (argc - optind != 1) { + ERR2("missing zone name"); + print_help(); + goto failure; + } + params.zone_name_str = argv[optind]; + if (knot_dname_from_str(params.zone_name, params.zone_name_str, + sizeof(params.zone_name)) == NULL) { + ERR2("invalid zone name '%s'", params.zone_name_str); + print_help(); + goto failure; + } + knot_dname_to_lower(params.zone_name); + + if (util_conf_init_default(false) != KNOT_EOK) { + goto failure; + } + + if (zonesign(¶ms) != KNOT_EOK) { + goto failure; + } + +success: + util_conf_deinit(); + return EXIT_SUCCESS; +failure: + util_conf_deinit(); + return EXIT_FAILURE; +} |