1 /*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7 
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12 
13     You should have received a copy of the GNU General Public License
14     along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  */
16 
17 #include <getopt.h>
18 #include <stdlib.h>
19 
20 #include "knot/conf/conf.h"
21 #include "knot/dnssec/zone-events.h"
22 #include "knot/updates/zone-update.h"
23 #include "knot/zone/zone-load.h"
24 #include "knot/zone/zonefile.h"
25 #include "utils/common/params.h"
26 
27 #define PROGRAM_NAME "kzonesign"
28 
29 static const char *global_outdir = NULL;
30 
31 // copy-pasted from keymgr
init_conf(const char * confdb)32 static bool init_conf(const char *confdb)
33 {
34 	size_t max_conf_size = (size_t)CONF_MAPSIZE * 1024 * 1024;
35 
36 	conf_flag_t flags = CONF_FNOHOSTNAME | CONF_FOPTMODULES;
37 	if (confdb != NULL) {
38 		flags |= CONF_FREADONLY;
39 	}
40 
41 	conf_t *new_conf = NULL;
42 	int ret = conf_new(&new_conf, conf_schema, confdb, max_conf_size, flags);
43 	if (ret != KNOT_EOK) {
44 		printf("Failed opening configuration database %s (%s)\n",
45 		       (confdb == NULL ? "" : confdb), knot_strerror(ret));
46 		return false;
47 	}
48 	conf_update(new_conf, CONF_UPD_FNONE);
49 	return true;
50 }
51 
print_help(void)52 static void print_help(void)
53 {
54 	printf("Usage: %s [parameters] -c <conf_file> <zone_name>\n"
55 	       "\n"
56 	       "Parameters:\n"
57 	       " -o, --outdir <dir_name>  Output directory.\n"
58 	       " -r, --rollover           Allow key rollovers and NSEC3 re-salt.\n"
59 	       " -t, --time <timestamp>   Current time specification.\n"
60 	       "                            (default current UNIX time)\n"
61 	       " -h, --help               Print the program help.\n"
62 	       " -V, --version            Print the program version.\n"
63 	       "\n",
64 	       PROGRAM_NAME);
65 }
66 
main(int argc,char * argv[])67 int main(int argc, char *argv[])
68 {
69 	const char *confile = NULL, *zone_str = NULL;
70 	knot_dname_t *zone_name = NULL;
71 	zone_contents_t *unsigned_conts = NULL;
72 	zone_t *zone_struct = NULL;
73 	zone_update_t up = { 0 };
74 	knot_lmdb_db_t kasp_db = { 0 };
75 	zone_sign_roll_flags_t rollover = 0;
76 	int64_t timestamp = 0;
77 	zone_sign_reschedule_t next_sign = { 0 };
78 
79 	struct option opts[] = {
80 		{ "config",    required_argument, NULL, 'c' },
81 		{ "outdir",    required_argument, NULL, 'o' },
82 		{ "rollover",  no_argument,       NULL, 'r' },
83 		{ "time",      required_argument, NULL, 't' },
84 		{ "help",      no_argument,       NULL, 'h' },
85 		{ "version",   no_argument,       NULL, 'V' },
86 		{ NULL }
87 	};
88 
89 	tzset();
90 
91 	int opt;
92 	while ((opt = getopt_long(argc, argv, "c:o:rt:hV", opts, NULL)) != -1) {
93 		switch (opt) {
94 		case 'c':
95 			confile = optarg;
96 			break;
97 		case 'o':
98 			global_outdir = optarg;
99 			break;
100 		case 'r':
101 			rollover = KEY_ROLL_ALLOW_ALL;
102 			break;
103 		case 't':
104 			timestamp = atol(optarg);
105 			if (timestamp <= 0) {
106 				print_help();
107 				return EXIT_FAILURE;
108 			}
109 			break;
110 		case 'h':
111 			print_help();
112 			return EXIT_SUCCESS;
113 		case 'V':
114 			print_version(PROGRAM_NAME);
115 			return EXIT_SUCCESS;
116 		default:
117 			print_help();
118 			return EXIT_FAILURE;
119 		}
120 	}
121 	if (confile == NULL || argc - optind != 1) {
122 		print_help();
123 		return EXIT_FAILURE;
124 	}
125 
126 	zone_str = argv[optind];
127 	zone_name = knot_dname_from_str_alloc(zone_str);
128 	if (zone_name == NULL) {
129 		printf("Invalid zone name '%s'\n", zone_str);
130 		return EXIT_FAILURE;
131 	}
132 	knot_dname_to_lower(zone_name);
133 
134 	if (!init_conf(NULL)) {
135 		free(zone_name);
136 		return EXIT_FAILURE;
137 	}
138 
139 	int ret = conf_import(conf(), confile, true, false);
140 	if (ret != KNOT_EOK) {
141 		printf("Failed opening configuration file '%s' (%s)\n",
142 		       confile, knot_strerror(ret));
143 		goto fail;
144 	}
145 
146 	conf_val_t val = conf_zone_get(conf(), C_DOMAIN, zone_name);
147 	if (val.code != KNOT_EOK) {
148 		printf("Zone '%s' not configured\n", zone_str);
149 		ret = val.code;
150 		goto fail;
151 	}
152 	val = conf_zone_get(conf(), C_DNSSEC_POLICY, zone_name);
153 	if (val.code != KNOT_EOK) {
154 		printf("Waring: DNSSEC policy not configured for zone '%s', taking defaults\n", zone_str);
155 	}
156 
157 	zone_struct = zone_new(zone_name);
158 	if (zone_struct == NULL) {
159 		printf("out of memory\n");
160 		ret = KNOT_ENOMEM;
161 		goto fail;
162 	}
163 
164 	ret = zone_load_contents(conf(), zone_name, &unsigned_conts, false);
165 	if (ret != KNOT_EOK) {
166 		printf("Failed to load zone contents (%s)\n", knot_strerror(ret));
167 		goto fail;
168 	}
169 
170 	ret = zone_update_from_contents(&up, zone_struct, unsigned_conts, UPDATE_FULL);
171 	if (ret != KNOT_EOK) {
172 		printf("Failed to initialize zone update (%s)\n", knot_strerror(ret));
173 		zone_contents_deep_free(unsigned_conts);
174 		goto fail;
175 	}
176 
177 	kasp_db_ensure_init(&kasp_db, conf());
178 	zone_struct->kaspdb = &kasp_db;
179 
180 	ret = knot_dnssec_zone_sign(&up, conf(), 0, rollover, timestamp, &next_sign);
181 	if (ret == KNOT_DNSSEC_ENOKEY) { // exception: allow generating initial keys
182 		rollover = KEY_ROLL_ALLOW_ALL;
183 		ret = knot_dnssec_zone_sign(&up, conf(), 0, rollover, timestamp, &next_sign);
184 	}
185 	if (ret != KNOT_EOK) {
186 		printf("Failed to sign the zone (%s)\n", knot_strerror(ret));
187 		zone_update_clear(&up);
188 		goto fail;
189 	}
190 
191 	if (global_outdir == NULL) {
192 		char *zonefile = conf_zonefile(conf(), zone_name);
193 		ret = zonefile_write(zonefile, up.new_cont);
194 		free(zonefile);
195 	} else {
196 		zone_contents_t *temp = zone_struct->contents;
197 		zone_struct->contents = up.new_cont;
198 		ret = zone_dump_to_dir(conf(), zone_struct, global_outdir);
199 		zone_struct->contents = temp;
200 	}
201 	zone_update_clear(&up);
202 	if (ret != KNOT_EOK) {
203 		printf("Failed to flush signed zone file (%s)\n", knot_strerror(ret));
204 		goto fail;
205 	}
206 
207 	printf("Next signing: %"KNOT_TIME_PRINTF"\n", next_sign.next_sign);
208 	if (rollover) {
209 		printf("Next roll-over: %"KNOT_TIME_PRINTF"\n", next_sign.next_rollover);
210 		if (next_sign.next_nsec3resalt) {
211 			printf("Next NSEC3 re-salt: %"KNOT_TIME_PRINTF"\n", next_sign.next_nsec3resalt);
212 		}
213 		if (next_sign.plan_ds_check) {
214 			printf("KSK submission to parent zone needed\n");
215 		}
216 	}
217 
218 fail:
219 	if (kasp_db.path != NULL) {
220 		knot_lmdb_deinit(&kasp_db);
221 	}
222 	zone_free(&zone_struct);
223 	conf_free(conf());
224 	free(zone_name);
225 	return ret == KNOT_EOK ? EXIT_SUCCESS : EXIT_FAILURE;
226 }
227