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 <string.h>
18 #include <unistd.h>
19 #include <sys/time.h>
20 #include <urcu.h>
21 
22 #include "knot/common/log.h"
23 #include "knot/common/stats.h"
24 #include "knot/conf/confio.h"
25 #include "knot/ctl/commands.h"
26 #include "knot/dnssec/key-events.h"
27 #include "knot/events/events.h"
28 #include "knot/events/handlers.h"
29 #include "knot/journal/journal_metadata.h"
30 #include "knot/nameserver/query_module.h"
31 #include "knot/updates/zone-update.h"
32 #include "knot/zone/backup.h"
33 #include "knot/zone/digest.h"
34 #include "knot/zone/timers.h"
35 #include "knot/zone/zonedb-load.h"
36 #include "knot/zone/zonefile.h"
37 #include "libknot/libknot.h"
38 #include "libknot/yparser/yptrafo.h"
39 #include "contrib/files.h"
40 #include "contrib/string.h"
41 #include "contrib/strtonum.h"
42 #include "contrib/ucw/lists.h"
43 #include "libzscanner/scanner.h"
44 
45 #define MATCH_OR_FILTER(args, code) ((args)->data[KNOT_CTL_IDX_FILTER] == NULL || \
46                                      strchr((args)->data[KNOT_CTL_IDX_FILTER], (code)) != NULL)
47 
48 #define MATCH_AND_FILTER(args, code) ((args)->data[KNOT_CTL_IDX_FILTER] != NULL && \
49                                       strchr((args)->data[KNOT_CTL_IDX_FILTER], (code)) != NULL)
50 
51 typedef struct {
52 	ctl_args_t *args;
53 	int type_filter; // -1: no specific type, [0, 2^16]: specific type.
54 	knot_dump_style_t style;
55 	knot_ctl_data_t data;
56 	knot_dname_txt_storage_t zone;
57 	knot_dname_txt_storage_t owner;
58 	char ttl[16];
59 	char type[32];
60 	char rdata[2 * 65536];
61 } send_ctx_t;
62 
63 static struct {
64 	send_ctx_t send_ctx;
65 	zs_scanner_t scanner;
66 	char txt_rr[sizeof(((send_ctx_t *)0)->owner) +
67 	            sizeof(((send_ctx_t *)0)->ttl) +
68 	            sizeof(((send_ctx_t *)0)->type) +
69 	            sizeof(((send_ctx_t *)0)->rdata)];
70 } ctl_globals;
71 
72 /*!
73  * Evaluates a filter pair and checks for conflicting filters.
74  *
75  * \param[in]  args        Command arguments.
76  * \param[out] param       The filter to be set.
77  * \param[in]  dflt        Default filter value.
78  * \param[in]  filter      Name of the filter.
79  * \param[in]  neg_filter  Name of the negative filter.
80  *
81  * \return false if there is a filter conflict, true otherwise.
82  */
83 
eval_opposite_filters(ctl_args_t * args,bool * param,bool dflt,int filter,int neg_filter)84 static bool eval_opposite_filters(ctl_args_t *args, bool *param, bool dflt,
85                                   int filter, int neg_filter)
86 {
87 	bool set = MATCH_AND_FILTER(args, filter);
88 	bool unset = MATCH_AND_FILTER(args, neg_filter);
89 
90 	*param = dflt ? (set || !unset) : (set && !unset);
91 	return !(set && unset);
92 }
93 
schedule_trigger(zone_t * zone,ctl_args_t * args,zone_event_type_t event,bool user)94 static int schedule_trigger(zone_t *zone, ctl_args_t *args, zone_event_type_t event,
95                             bool user)
96 {
97 	int ret = KNOT_EOK;
98 
99 	if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_BLOCKING)) {
100 		ret = zone_events_schedule_blocking(zone, event, user);
101 	} else if (user) {
102 		zone_events_schedule_user(zone, event);
103 	} else {
104 		zone_events_schedule_now(zone, event);
105 	}
106 
107 	return ret;
108 }
109 
ctl_log_conf_data(knot_ctl_data_t * data)110 static void ctl_log_conf_data(knot_ctl_data_t *data)
111 {
112 	if (data == NULL) {
113 		return;
114 	}
115 
116 	const char *section = (*data)[KNOT_CTL_IDX_SECTION];
117 	const char *item = (*data)[KNOT_CTL_IDX_ITEM];
118 	const char *id = (*data)[KNOT_CTL_IDX_ID];
119 
120 	if (section != NULL) {
121 		log_ctl_debug("control, config item '%s%s%s%s%s%s'", section,
122 		              (id   != NULL ? "["  : ""),
123 		              (id   != NULL ? id   : ""),
124 		              (id   != NULL ? "]"  : ""),
125 		              (item != NULL ? "."  : ""),
126 		              (item != NULL ? item : ""));
127 	}
128 }
129 
send_error(ctl_args_t * args,const char * msg)130 static void send_error(ctl_args_t *args, const char *msg)
131 {
132 	knot_ctl_data_t data;
133 	memcpy(&data, args->data, sizeof(data));
134 
135 	data[KNOT_CTL_IDX_ERROR] = msg;
136 
137 	int ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &data);
138 	if (ret != KNOT_EOK) {
139 		log_ctl_debug("control, failed to send error (%s)", knot_strerror(ret));
140 	}
141 }
142 
get_zone(ctl_args_t * args,zone_t ** zone)143 static int get_zone(ctl_args_t *args, zone_t **zone)
144 {
145 	const char *name = args->data[KNOT_CTL_IDX_ZONE];
146 	assert(name != NULL);
147 
148 	knot_dname_storage_t buff;
149 	knot_dname_t *dname = knot_dname_from_str(buff, name, sizeof(buff));
150 	if (dname == NULL) {
151 		return KNOT_EINVAL;
152 	}
153 	knot_dname_to_lower(dname);
154 
155 	*zone = knot_zonedb_find(args->server->zone_db, dname);
156 	if (*zone == NULL) {
157 		return KNOT_ENOZONE;
158 	}
159 
160 	return KNOT_EOK;
161 }
162 
zones_apply(ctl_args_t * args,int (* fcn)(zone_t *,ctl_args_t *))163 static int zones_apply(ctl_args_t *args, int (*fcn)(zone_t *, ctl_args_t *))
164 {
165 	int ret;
166 
167 	// Process all configured zones if none is specified.
168 	if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
169 		bool failed = false;
170 		knot_zonedb_iter_t *it = knot_zonedb_iter_begin(args->server->zone_db);
171 		while (!knot_zonedb_iter_finished(it)) {
172 			args->suppress = false;
173 			ret = fcn((zone_t *)knot_zonedb_iter_val(it), args);
174 			if (ret != KNOT_EOK && !args->suppress) {
175 				failed = true;
176 			}
177 			knot_zonedb_iter_next(it);
178 		}
179 		knot_zonedb_iter_free(it);
180 
181 		if (failed) {
182 			ret = KNOT_CTL_EZONE;
183 			log_ctl_error("control, error (%s)", knot_strerror(ret));
184 			send_error(args, knot_strerror(ret));
185 		}
186 
187 		return KNOT_EOK;
188 	}
189 
190 	while (true) {
191 		zone_t *zone;
192 		ret = get_zone(args, &zone);
193 		if (ret == KNOT_EOK) {
194 			ret = fcn(zone, args);
195 		}
196 		if (ret != KNOT_EOK) {
197 			log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE],
198 			                       "control, error (%s)", knot_strerror(ret));
199 			send_error(args, knot_strerror(ret));
200 		}
201 
202 		// Get next zone name.
203 		ret = knot_ctl_receive(args->ctl, &args->type, &args->data);
204 		if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) {
205 			break;
206 		}
207 		strtolower((char *)args->data[KNOT_CTL_IDX_ZONE]);
208 
209 		// Log the other zones the same way as the first one from process.c.
210 		log_ctl_zone_str_info(args->data[KNOT_CTL_IDX_ZONE],
211 		                      "control, received command '%s'",
212 		                      args->data[KNOT_CTL_IDX_CMD]);
213 	}
214 
215 	return ret;
216 }
217 
zone_status(zone_t * zone,ctl_args_t * args)218 static int zone_status(zone_t *zone, ctl_args_t *args)
219 {
220 	knot_dname_txt_storage_t name;
221 	if (knot_dname_to_str(name, zone->name, sizeof(name)) == NULL) {
222 		return KNOT_EINVAL;
223 	}
224 
225 	knot_ctl_data_t data = {
226 		[KNOT_CTL_IDX_ZONE] = name
227 	};
228 
229 	int ret;
230 	char buff[128];
231 	knot_ctl_type_t type = KNOT_CTL_TYPE_DATA;
232 
233 	if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_ROLE)) {
234 		data[KNOT_CTL_IDX_TYPE] = "role";
235 
236 		if (zone_is_slave(conf(), zone)) {
237 			data[KNOT_CTL_IDX_DATA] = "slave";
238 		} else {
239 			data[KNOT_CTL_IDX_DATA] = "master";
240 		}
241 
242 		ret = knot_ctl_send(args->ctl, type, &data);
243 		if (ret != KNOT_EOK) {
244 			return ret;
245 		} else {
246 			type = KNOT_CTL_TYPE_EXTRA;
247 		}
248 	}
249 
250 	if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_SERIAL)) {
251 		data[KNOT_CTL_IDX_TYPE] = "serial";
252 
253 		if (zone->contents != NULL) {
254 			knot_rdataset_t *soa = node_rdataset(zone->contents->apex,
255 			                                     KNOT_RRTYPE_SOA);
256 			ret = snprintf(buff, sizeof(buff), "%u", knot_soa_serial(soa->rdata));
257 		} else {
258 			ret = snprintf(buff, sizeof(buff), "none");
259 		}
260 		if (ret < 0 || ret >= sizeof(buff)) {
261 			return KNOT_ESPACE;
262 		}
263 
264 		data[KNOT_CTL_IDX_DATA] = buff;
265 
266 		ret = knot_ctl_send(args->ctl, type, &data);
267 		if (ret != KNOT_EOK) {
268 			return ret;
269 		} else {
270 			type = KNOT_CTL_TYPE_EXTRA;
271 		}
272 	}
273 
274 	if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_TRANSACTION)) {
275 		data[KNOT_CTL_IDX_TYPE] = "transaction";
276 		data[KNOT_CTL_IDX_DATA] = (zone->control_update != NULL) ? "open" : "none";
277 		ret = knot_ctl_send(args->ctl, type, &data);
278 		if (ret != KNOT_EOK) {
279 			return ret;
280 		} else {
281 			type = KNOT_CTL_TYPE_EXTRA;
282 		}
283 	}
284 
285 	bool ufrozen = zone->events.ufrozen;
286 	if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_FREEZE)) {
287 		data[KNOT_CTL_IDX_TYPE] = "freeze";
288 		if (ufrozen) {
289 			if (zone_events_get_time(zone, ZONE_EVENT_UTHAW) < time(NULL)) {
290 				data[KNOT_CTL_IDX_DATA] = "yes";
291 			} else {
292 				data[KNOT_CTL_IDX_DATA] = "thawing";
293 			}
294 		} else {
295 			if (zone_events_get_time(zone, ZONE_EVENT_UFREEZE) < time(NULL)) {
296 				data[KNOT_CTL_IDX_DATA] = "no";
297 			} else {
298 				data[KNOT_CTL_IDX_DATA] = "freezing";
299 
300 			}
301 		}
302 		ret = knot_ctl_send(args->ctl, type, &data);
303 		if (ret != KNOT_EOK) {
304 			return ret;
305 		}
306 	}
307 
308 	if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_EVENTS)) {
309 		for (zone_event_type_t i = 0; i < ZONE_EVENT_COUNT; i++) {
310 			// Events not worth showing or used elsewhere.
311 			if (i == ZONE_EVENT_UFREEZE || i == ZONE_EVENT_UTHAW) {
312 				continue;
313 			}
314 
315 			// Skip events affected by freeze.
316 			if (ufrozen && ufreeze_applies(i)) {
317 				continue;
318 			}
319 
320 			data[KNOT_CTL_IDX_TYPE] = zone_events_get_name(i);
321 			time_t ev_time = zone_events_get_time(zone, i);
322 			if (zone->events.running && zone->events.type == i) {
323 				ret = snprintf(buff, sizeof(buff), "running");
324 			} else if (ev_time <= 0) {
325 				ret = snprintf(buff, sizeof(buff), "not scheduled");
326 			} else if (ev_time <= time(NULL)) {
327 				ret = snprintf(buff, sizeof(buff), "pending");
328 			} else {
329 				ret = knot_time_print(TIME_PRINT_HUMAN_MIXED,
330 				                      ev_time, buff, sizeof(buff));
331 			}
332 			if (ret < 0 || ret >= sizeof(buff)) {
333 				return KNOT_ESPACE;
334 			}
335 			data[KNOT_CTL_IDX_DATA] = buff;
336 
337 			ret = knot_ctl_send(args->ctl, type, &data);
338 			if (ret != KNOT_EOK) {
339 				return ret;
340 			}
341 			type = KNOT_CTL_TYPE_EXTRA;
342 		}
343 	}
344 
345 	return KNOT_EOK;
346 }
347 
zone_reload(zone_t * zone,_unused_ ctl_args_t * args)348 static int zone_reload(zone_t *zone, _unused_ ctl_args_t *args)
349 {
350 	if (zone_expired(zone)) {
351 		args->suppress = true;
352 		return KNOT_ENOTSUP;
353 	}
354 
355 	if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) {
356 		return zone_reload_modules(conf(), args->server, zone->name);
357 	}
358 
359 	return schedule_trigger(zone, args, ZONE_EVENT_LOAD, true);
360 }
361 
zone_refresh(zone_t * zone,_unused_ ctl_args_t * args)362 static int zone_refresh(zone_t *zone, _unused_ ctl_args_t *args)
363 {
364 	if (!zone_is_slave(conf(), zone)) {
365 		args->suppress = true;
366 		return KNOT_ENOTSUP;
367 	}
368 
369 	return schedule_trigger(zone, args, ZONE_EVENT_REFRESH, true);
370 }
371 
zone_retransfer(zone_t * zone,_unused_ ctl_args_t * args)372 static int zone_retransfer(zone_t *zone, _unused_ ctl_args_t *args)
373 {
374 	if (!zone_is_slave(conf(), zone)) {
375 		args->suppress = true;
376 		return KNOT_ENOTSUP;
377 	}
378 
379 	zone_set_flag(zone, ZONE_FORCE_AXFR);
380 	return schedule_trigger(zone, args, ZONE_EVENT_REFRESH, true);
381 }
382 
zone_notify(zone_t * zone,_unused_ ctl_args_t * args)383 static int zone_notify(zone_t *zone, _unused_ ctl_args_t *args)
384 {
385 	return schedule_trigger(zone, args, ZONE_EVENT_NOTIFY, true);
386 }
387 
zone_flush(zone_t * zone,ctl_args_t * args)388 static int zone_flush(zone_t *zone, ctl_args_t *args)
389 {
390 	if (MATCH_AND_FILTER(args, CTL_FILTER_FLUSH_OUTDIR)) {
391 		rcu_read_lock();
392 		int ret = zone_dump_to_dir(conf(), zone, args->data[KNOT_CTL_IDX_DATA]);
393 		rcu_read_unlock();
394 		if (ret != KNOT_EOK) {
395 			log_zone_warning(zone->name, "failed to update zone file (%s)",
396 			                 knot_strerror(ret));
397 		}
398 		return ret;
399 	}
400 
401 	if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) {
402 		zone_set_flag(zone, ZONE_FORCE_FLUSH);
403 	}
404 
405 	return schedule_trigger(zone, args, ZONE_EVENT_FLUSH, true);
406 }
407 
init_backup(ctl_args_t * args,bool restore_mode)408 static int init_backup(ctl_args_t *args, bool restore_mode)
409 {
410 	if (!MATCH_AND_FILTER(args, CTL_FILTER_BACKUP_OUTDIR)) {
411 		return KNOT_ENOPARAM;
412 	}
413 
414 	// Make sure that the backup outdir is not the same as the server DB storage.
415 	conf_val_t db_storage_val = conf_db_param(conf(), C_STORAGE);
416 	const char *db_storage = conf_str(&db_storage_val);
417 
418 	const char *backup_dir = args->data[KNOT_CTL_IDX_DATA];
419 
420 	if (same_path(backup_dir, db_storage)) {
421 		char *msg = sprintf_alloc("%s the database storage directory not allowed",
422 		                          restore_mode ? "restore from" : "backup to");
423 
424 		if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
425 			log_ctl_error("%s", msg);
426 		} else {
427 			log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE], "%s", msg);
428 		}
429 		free(msg);
430 		return KNOT_EINVAL;
431 	}
432 
433 	// Evaluate filters (and possibly fail) before writing to the filesystem.
434 	bool filter_zonefile, filter_journal, filter_timers, filter_kaspdb, filter_catalog;
435 
436 	// The default filter values are set just in this paragraph.
437 	if (!(eval_opposite_filters(args, &filter_zonefile, true,
438 	                            CTL_FILTER_BACKUP_ZONEFILE, CTL_FILTER_BACKUP_NOZONEFILE) &&
439 	    eval_opposite_filters(args, &filter_journal, false,
440 	                          CTL_FILTER_BACKUP_JOURNAL, CTL_FILTER_BACKUP_NOJOURNAL) &&
441 	    eval_opposite_filters(args, &filter_timers, true,
442 	                          CTL_FILTER_BACKUP_TIMERS, CTL_FILTER_BACKUP_NOTIMERS) &&
443 	    eval_opposite_filters(args, &filter_kaspdb, true,
444 	                          CTL_FILTER_BACKUP_KASPDB, CTL_FILTER_BACKUP_NOKASPDB) &&
445 	    eval_opposite_filters(args, &filter_catalog, true,
446 	                          CTL_FILTER_BACKUP_CATALOG, CTL_FILTER_BACKUP_NOCATALOG))) {
447 		return KNOT_EXPARAM;
448 	}
449 
450 	bool forced = ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE);
451 
452 	zone_backup_ctx_t *ctx;
453 
454 	// The present timer db size is not up-to-date, use the maximum one.
455 	conf_val_t timer_db_size = conf_db_param(conf(), C_TIMER_DB_MAX_SIZE);
456 
457 	int ret = zone_backup_init(restore_mode, forced,
458 	                           args->data[KNOT_CTL_IDX_DATA],
459 	                           knot_lmdb_copy_size(&args->server->kaspdb),
460 	                           conf_int(&timer_db_size),
461 	                           knot_lmdb_copy_size(&args->server->journaldb),
462 	                           knot_lmdb_copy_size(&args->server->catalog.db),
463 	                           &ctx);
464 	if (ret != KNOT_EOK) {
465 		return ret;
466 	}
467 
468 	assert(ctx != NULL);
469 	ctx->backup_zonefile = filter_zonefile;
470 	ctx->backup_journal = filter_journal;
471 	ctx->backup_timers = filter_timers;
472 	ctx->backup_kaspdb = filter_kaspdb;
473 	ctx->backup_catalog = filter_catalog;
474 
475 	zone_backups_add(&args->server->backup_ctxs, ctx);
476 
477 	return ret;
478 }
479 
latest_backup_ctx(ctl_args_t * args)480 static zone_backup_ctx_t *latest_backup_ctx(ctl_args_t *args)
481 {
482 	// no need to mutex in this case
483 	return (zone_backup_ctx_t *)TAIL(args->server->backup_ctxs.ctxs);
484 }
485 
deinit_backup(ctl_args_t * args)486 static int deinit_backup(ctl_args_t *args)
487 {
488 	return zone_backup_deinit(latest_backup_ctx(args));
489 }
490 
zone_backup_cmd(zone_t * zone,ctl_args_t * args)491 static int zone_backup_cmd(zone_t *zone, ctl_args_t *args)
492 {
493 	zone_backup_ctx_t *ctx = latest_backup_ctx(args);
494 	if (!ctx->restore_mode && ctx->failed) {
495 		// No need to proceed with already faulty backup.
496 		return KNOT_EOK;
497 	}
498 
499 	if (zone->backup_ctx != NULL) {
500 		log_zone_warning(zone->name, "backup or restore already in progress, skipping zone");
501 		ctx->failed = true;
502 		return KNOT_EPROGRESS;
503 	}
504 
505 	zone->backup_ctx = ctx;
506 	pthread_mutex_lock(&ctx->readers_mutex);
507 	ctx->readers++;
508 	pthread_mutex_unlock(&ctx->readers_mutex);
509 	ctx->zone_count++;
510 
511 	int ret = schedule_trigger(zone, args, ZONE_EVENT_BACKUP, true);
512 
513 	if (ret == KNOT_EOK && !ctx->backup_global && (ctx->restore_mode || !ctx->failed)) {
514 		ret = global_backup(ctx, zone->catalog, zone->name);
515 	}
516 
517 	return ret;
518 }
519 
zones_apply_backup(ctl_args_t * args,bool restore_mode)520 static int zones_apply_backup(ctl_args_t *args, bool restore_mode)
521 {
522 	int ret_deinit;
523 	int ret = init_backup(args, restore_mode);
524 
525 	if (ret != KNOT_EOK) {
526 		char *msg = sprintf_alloc("%s init failed (%s)",
527 		                          restore_mode ? "restore" : "backup",
528 		                          knot_strerror(ret));
529 
530 		if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
531 			log_ctl_error("%s", msg);
532 		} else {
533 			log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE],
534 			                       "%s", msg);
535 		}
536 		free (msg);
537 
538 		/* Warning: zone name in the control command params discarded here. */
539 		args->data[KNOT_CTL_IDX_ZONE] = NULL;
540 		send_error(args, knot_strerror(ret));
541 		return KNOT_CTL_EZONE;
542 	}
543 
544 	/* Global catalog zones backup. */
545 	if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
546 		zone_backup_ctx_t *ctx = latest_backup_ctx(args);
547 		ctx->backup_global = true;
548 		ret = global_backup(ctx, &args->server->catalog, NULL);
549 		if (ret != KNOT_EOK) {
550 			log_ctl_error("control, error (%s)", knot_strerror(ret));
551 			send_error(args, knot_strerror(ret));
552 			ret = KNOT_EOK;
553 			goto done;
554 		}
555 	}
556 
557 	ret = zones_apply(args, zone_backup_cmd);
558 
559 done:
560 	ret_deinit = deinit_backup(args);
561 	return ret != KNOT_EOK ? ret : ret_deinit;
562 }
563 
zone_sign(zone_t * zone,_unused_ ctl_args_t * args)564 static int zone_sign(zone_t *zone, _unused_ ctl_args_t *args)
565 {
566 	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
567 	if (!conf_bool(&val)) {
568 		args->suppress = true;
569 		return KNOT_ENOTSUP;
570 	}
571 
572 	zone_set_flag(zone, ZONE_FORCE_RESIGN);
573 	return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true);
574 }
575 
zone_keys_load(zone_t * zone,_unused_ ctl_args_t * args)576 static int zone_keys_load(zone_t *zone, _unused_ ctl_args_t *args)
577 {
578 	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
579 	if (!conf_bool(&val)) {
580 		args->suppress = true;
581 		return KNOT_ENOTSUP;
582 	}
583 
584 	return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true);
585 }
586 
zone_key_roll(zone_t * zone,ctl_args_t * args)587 static int zone_key_roll(zone_t *zone, ctl_args_t *args)
588 {
589 	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
590 	if (!conf_bool(&val)) {
591 		args->suppress = true;
592 		return KNOT_ENOTSUP;
593 	}
594 
595 	const char *key_type = args->data[KNOT_CTL_IDX_TYPE];
596 	if (strncasecmp(key_type, "ksk", 3) == 0) {
597 		zone_set_flag(zone, ZONE_FORCE_KSK_ROLL);
598 	} else if (strncasecmp(key_type, "zsk", 3) == 0) {
599 		zone_set_flag(zone, ZONE_FORCE_ZSK_ROLL);
600 	} else {
601 		return KNOT_EINVAL;
602 	}
603 
604 	return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true);
605 }
606 
zone_ksk_sbm_confirm(zone_t * zone,_unused_ ctl_args_t * args)607 static int zone_ksk_sbm_confirm(zone_t *zone, _unused_ ctl_args_t *args)
608 {
609 	kdnssec_ctx_t ctx = { 0 };
610 
611 	int ret = kdnssec_ctx_init(conf(), &ctx, zone->name, zone->kaspdb, NULL);
612 	if (ret != KNOT_EOK) {
613 		return ret;
614 	}
615 
616 	ret = knot_dnssec_ksk_sbm_confirm(&ctx, 0);
617 	kdnssec_ctx_deinit(&ctx);
618 
619 	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
620 	if (ret == KNOT_EOK && conf_bool(&val)) {
621 		// NOT zone_events_schedule_user(), intentionally!
622 		ret = schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, false);
623 	}
624 
625 	return ret;
626 }
627 
zone_freeze(zone_t * zone,_unused_ ctl_args_t * args)628 static int zone_freeze(zone_t *zone, _unused_ ctl_args_t *args)
629 {
630 	return schedule_trigger(zone, args, ZONE_EVENT_UFREEZE, false);
631 }
632 
zone_thaw(zone_t * zone,_unused_ ctl_args_t * args)633 static int zone_thaw(zone_t *zone, _unused_ ctl_args_t *args)
634 {
635 	return schedule_trigger(zone, args, ZONE_EVENT_UTHAW, false);
636 }
637 
zone_txn_begin(zone_t * zone,_unused_ ctl_args_t * args)638 static int zone_txn_begin(zone_t *zone, _unused_ ctl_args_t *args)
639 {
640 	if (zone->control_update != NULL) {
641 		return KNOT_TXN_EEXISTS;
642 	}
643 
644 	zone->control_update = malloc(sizeof(zone_update_t));
645 	if (zone->control_update == NULL) {
646 		return KNOT_ENOMEM;
647 	}
648 
649 	zone_update_flags_t type = (zone->contents == NULL) ? UPDATE_FULL : UPDATE_INCREMENTAL;
650 	int ret = zone_update_init(zone->control_update, zone, type | UPDATE_STRICT);
651 	if (ret != KNOT_EOK) {
652 		free(zone->control_update);
653 		zone->control_update = NULL;
654 	}
655 
656 	return ret;
657 }
658 
zone_txn_commit(zone_t * zone,_unused_ ctl_args_t * args)659 static int zone_txn_commit(zone_t *zone, _unused_ ctl_args_t *args)
660 {
661 	if (zone->control_update == NULL) {
662 		args->suppress = true;
663 		return KNOT_TXN_ENOTEXISTS;
664 	}
665 
666 	int ret = zone_update_semcheck(zone->control_update);
667 	if (ret != KNOT_EOK) {
668 		return ret; // Recoverable error.
669 	}
670 
671 	// NOOP if empty changeset/contents.
672 	if (((zone->control_update->flags & UPDATE_INCREMENTAL) &&
673 	     changeset_empty(&zone->control_update->change)) ||
674 	    ((zone->control_update->flags & UPDATE_FULL) &&
675 	     zone_contents_is_empty(zone->control_update->new_cont))) {
676 		zone_control_clear(zone);
677 		return KNOT_EOK;
678 	}
679 
680 	// Sign update.
681 	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
682 	bool dnssec_enable = conf_bool(&val);
683 	val = conf_zone_get(conf(), C_ZONEMD_GENERATE, zone->name);
684 	unsigned digest_alg = conf_opt(&val);
685 	if (dnssec_enable) {
686 		zone_sign_reschedule_t resch = { 0 };
687 		bool full = (zone->control_update->flags & UPDATE_FULL);
688 		zone_sign_roll_flags_t rflags = KEY_ROLL_ALLOW_ALL;
689 		ret = (full ? knot_dnssec_zone_sign(zone->control_update, conf(), 0, rflags, 0, &resch) :
690 		              knot_dnssec_sign_update(zone->control_update, conf(), &resch));
691 		event_dnssec_reschedule(conf(), zone, &resch, false);
692 	} else if (digest_alg != ZONE_DIGEST_NONE) {
693 		if (zone_update_to(zone->control_update) == NULL) {
694 			ret = zone_update_increment_soa(zone->control_update, conf());
695 		}
696 		if (ret == KNOT_EOK) {
697 			ret = zone_update_add_digest(zone->control_update, digest_alg, false);
698 		}
699 	}
700 	if (ret != KNOT_EOK) {
701 		zone_control_clear(zone);
702 		return ret;
703 	}
704 
705 	ret = zone_update_commit(conf(), zone->control_update);
706 	if (ret != KNOT_EOK) {
707 		zone_control_clear(zone);
708 		return ret;
709 	}
710 
711 	free(zone->control_update);
712 	zone->control_update = NULL;
713 
714 	zone_events_schedule_now(zone, ZONE_EVENT_NOTIFY);
715 
716 	return KNOT_EOK;
717 }
718 
zone_txn_abort(zone_t * zone,_unused_ ctl_args_t * args)719 static int zone_txn_abort(zone_t *zone, _unused_ ctl_args_t *args)
720 {
721 	if (zone->control_update == NULL) {
722 		args->suppress = true;
723 		return KNOT_TXN_ENOTEXISTS;
724 	}
725 
726 	zone_control_clear(zone);
727 
728 	return KNOT_EOK;
729 }
730 
init_send_ctx(send_ctx_t * ctx,const knot_dname_t * zone_name,ctl_args_t * args)731 static int init_send_ctx(send_ctx_t *ctx, const knot_dname_t *zone_name,
732                          ctl_args_t *args)
733 {
734 	memset(ctx, 0, sizeof(*ctx));
735 
736 	ctx->args = args;
737 
738 	// Set the dump style.
739 	ctx->style.show_ttl = true;
740 	ctx->style.original_ttl = true;
741 	ctx->style.human_tmstamp = true;
742 
743 	// Set the output data buffers.
744 	ctx->data[KNOT_CTL_IDX_ZONE]  = ctx->zone;
745 	ctx->data[KNOT_CTL_IDX_OWNER] = ctx->owner;
746 	ctx->data[KNOT_CTL_IDX_TTL]   = ctx->ttl;
747 	ctx->data[KNOT_CTL_IDX_TYPE]  = ctx->type;
748 	ctx->data[KNOT_CTL_IDX_DATA]  = ctx->rdata;
749 
750 	// Set the ZONE.
751 	if (knot_dname_to_str(ctx->zone, zone_name, sizeof(ctx->zone)) == NULL) {
752 		return KNOT_EINVAL;
753 	}
754 
755 	// Set the TYPE filter.
756 	if (args->data[KNOT_CTL_IDX_TYPE] != NULL) {
757 		uint16_t type;
758 		if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE], &type) != 0) {
759 			return KNOT_EINVAL;
760 		}
761 		ctx->type_filter = type;
762 	} else {
763 		ctx->type_filter = -1;
764 	}
765 
766 	return KNOT_EOK;
767 }
768 
send_rrset(knot_rrset_t * rrset,send_ctx_t * ctx)769 static int send_rrset(knot_rrset_t *rrset, send_ctx_t *ctx)
770 {
771 	if (rrset->type != KNOT_RRTYPE_RRSIG) {
772 		int ret = snprintf(ctx->ttl, sizeof(ctx->ttl), "%u", rrset->ttl);
773 		if (ret <= 0 || ret >= sizeof(ctx->ttl)) {
774 			return KNOT_ESPACE;
775 		}
776 	}
777 
778 	if (knot_rrtype_to_string(rrset->type, ctx->type, sizeof(ctx->type)) < 0) {
779 		return KNOT_ESPACE;
780 	}
781 
782 	for (size_t i = 0; i < rrset->rrs.count; ++i) {
783 		if (rrset->type == KNOT_RRTYPE_RRSIG) {
784 			int ret = snprintf(ctx->ttl, sizeof(ctx->ttl), "%u",
785 			                   knot_rrsig_original_ttl(knot_rdataset_at(&rrset->rrs, i)));
786 			if (ret <= 0 || ret >= sizeof(ctx->ttl)) {
787 				return KNOT_ESPACE;
788 			}
789 		}
790 
791 		int ret = knot_rrset_txt_dump_data(rrset, i, ctx->rdata,
792 		                                   sizeof(ctx->rdata), &ctx->style);
793 		if (ret < 0) {
794 			return ret;
795 		}
796 
797 		ret = knot_ctl_send(ctx->args->ctl, KNOT_CTL_TYPE_DATA, &ctx->data);
798 		if (ret != KNOT_EOK) {
799 			return ret;
800 		}
801 	}
802 
803 	return KNOT_EOK;
804 }
805 
send_node(zone_node_t * node,void * ctx_void)806 static int send_node(zone_node_t *node, void *ctx_void)
807 {
808 	send_ctx_t *ctx = ctx_void;
809 	if (knot_dname_to_str(ctx->owner, node->owner, sizeof(ctx->owner)) == NULL) {
810 		return KNOT_EINVAL;
811 	}
812 
813 	for (size_t i = 0; i < node->rrset_count; ++i) {
814 		knot_rrset_t rrset = node_rrset_at(node, i);
815 
816 		// Check for requested TYPE.
817 		if (ctx->type_filter != -1 && rrset.type != ctx->type_filter) {
818 			continue;
819 		}
820 
821 		int ret = send_rrset(&rrset, ctx);
822 		if (ret != KNOT_EOK) {
823 			return ret;
824 		}
825 	}
826 
827 	return KNOT_EOK;
828 }
829 
get_owner(uint8_t * out,size_t out_len,knot_dname_t * origin,ctl_args_t * args)830 static int get_owner(uint8_t *out, size_t out_len, knot_dname_t *origin,
831                      ctl_args_t *args)
832 {
833 	const char *owner = args->data[KNOT_CTL_IDX_OWNER];
834 	assert(owner != NULL);
835 
836 	bool fqdn = false;
837 	size_t prefix_len = 0;
838 
839 	size_t owner_len = strlen(owner);
840 	if (owner_len > 0 && (owner_len != 1 || owner[0] != '@')) {
841 		// Check if the owner is FQDN.
842 		if (owner[owner_len - 1] == '.') {
843 			fqdn = true;
844 		}
845 
846 		if (knot_dname_from_str(out, owner, out_len) == NULL) {
847 			return KNOT_EINVAL;
848 		}
849 		knot_dname_to_lower(out);
850 
851 		prefix_len = knot_dname_size(out);
852 		if (prefix_len == 0) {
853 			return KNOT_EINVAL;
854 		}
855 
856 		// Ignore trailing dot.
857 		prefix_len--;
858 	}
859 
860 	// Append the origin.
861 	if (!fqdn) {
862 		size_t origin_len = knot_dname_size(origin);
863 		if (origin_len == 0 || origin_len > out_len - prefix_len) {
864 			return KNOT_EINVAL;
865 		}
866 		memcpy(out + prefix_len, origin, origin_len);
867 	}
868 
869 	return KNOT_EOK;
870 }
871 
zone_read(zone_t * zone,ctl_args_t * args)872 static int zone_read(zone_t *zone, ctl_args_t *args)
873 {
874 	send_ctx_t *ctx = &ctl_globals.send_ctx;
875 	int ret = init_send_ctx(ctx, zone->name, args);
876 	if (ret != KNOT_EOK) {
877 		return ret;
878 	}
879 
880 	if (args->data[KNOT_CTL_IDX_OWNER] != NULL) {
881 		knot_dname_storage_t owner;
882 
883 		ret = get_owner(owner, sizeof(owner), zone->name, args);
884 		if (ret != KNOT_EOK) {
885 			return ret;
886 		}
887 
888 		const zone_node_t *node = zone_contents_node_or_nsec3(zone->contents, owner);
889 		if (node == NULL) {
890 			return KNOT_ENONODE;
891 		}
892 
893 		ret = send_node((zone_node_t *)node, ctx);
894 	} else if (zone->contents != NULL) {
895 		ret = zone_contents_apply(zone->contents, send_node, ctx);
896 		if (ret == KNOT_EOK) {
897 			ret = zone_contents_nsec3_apply(zone->contents, send_node, ctx);
898 		}
899 	}
900 
901 	return ret;
902 }
903 
zone_flag_txn_get(zone_t * zone,ctl_args_t * args,const char * flag)904 static int zone_flag_txn_get(zone_t *zone, ctl_args_t *args, const char *flag)
905 {
906 	if (zone->control_update == NULL) {
907 		args->suppress = true;
908 		return KNOT_TXN_ENOTEXISTS;
909 	}
910 
911 	send_ctx_t *ctx = &ctl_globals.send_ctx;
912 	int ret = init_send_ctx(ctx, zone->name, args);
913 	if (ret != KNOT_EOK) {
914 		return ret;
915 	}
916 	ctx->data[KNOT_CTL_IDX_FLAGS] = flag;
917 
918 	if (args->data[KNOT_CTL_IDX_OWNER] != NULL) {
919 		knot_dname_storage_t owner;
920 
921 		ret = get_owner(owner, sizeof(owner), zone->name, args);
922 		if (ret != KNOT_EOK) {
923 			return ret;
924 		}
925 
926 		const zone_node_t *node = zone_contents_node_or_nsec3(zone->control_update->new_cont, owner);
927 		if (node == NULL) {
928 			return KNOT_ENONODE;
929 
930 		}
931 
932 		ret = send_node((zone_node_t *)node, ctx);
933 	} else {
934 		zone_tree_it_t it = { 0 };
935 		ret = zone_tree_it_double_begin(zone->control_update->new_cont->nodes,
936 						zone->control_update->new_cont->nsec3_nodes,
937 						&it);
938 		while (ret == KNOT_EOK && !zone_tree_it_finished(&it)) {
939 			ret = send_node(zone_tree_it_val(&it), ctx);
940 			zone_tree_it_next(&it);
941 		}
942 		zone_tree_it_free(&it);
943 	}
944 
945 	return ret;
946 }
947 
zone_txn_get(zone_t * zone,ctl_args_t * args)948 static int zone_txn_get(zone_t *zone, ctl_args_t *args)
949 {
950 	return zone_flag_txn_get(zone, args, NULL);
951 }
952 
send_changeset_part(changeset_t * ch,send_ctx_t * ctx,bool from)953 static int send_changeset_part(changeset_t *ch, send_ctx_t *ctx, bool from)
954 {
955 	ctx->data[KNOT_CTL_IDX_FLAGS] = from ? CTL_FLAG_REM : CTL_FLAG_ADD;
956 
957 	// Send SOA only if explicitly changed.
958 	if (ch->soa_to != NULL) {
959 		knot_rrset_t *soa = from ? ch->soa_from : ch->soa_to;
960 		assert(soa);
961 
962 		char *owner = knot_dname_to_str(ctx->owner, soa->owner, sizeof(ctx->owner));
963 		if (owner == NULL) {
964 			return KNOT_EINVAL;
965 		}
966 
967 		int ret = send_rrset(soa, ctx);
968 		if (ret != KNOT_EOK) {
969 			return ret;
970 		}
971 	}
972 
973 	// Send other records.
974 	changeset_iter_t it;
975 	int ret = from ? changeset_iter_rem(&it, ch) : changeset_iter_add(&it, ch);
976 	if (ret != KNOT_EOK) {
977 		return ret;
978 	}
979 
980 	knot_rrset_t rrset = changeset_iter_next(&it);
981 	while (!knot_rrset_empty(&rrset)) {
982 		char *owner = knot_dname_to_str(ctx->owner, rrset.owner, sizeof(ctx->owner));
983 		if (owner == NULL) {
984 			changeset_iter_clear(&it);
985 			return KNOT_EINVAL;
986 		}
987 
988 		ret = send_rrset(&rrset, ctx);
989 		if (ret != KNOT_EOK) {
990 			changeset_iter_clear(&it);
991 			return ret;
992 		}
993 
994 		rrset = changeset_iter_next(&it);
995 	}
996 	changeset_iter_clear(&it);
997 
998 	return KNOT_EOK;
999 }
1000 
send_changeset(changeset_t * ch,send_ctx_t * ctx)1001 static int send_changeset(changeset_t *ch, send_ctx_t *ctx)
1002 {
1003 	// First send 'from' changeset part.
1004 	int ret = send_changeset_part(ch, ctx, true);
1005 	if (ret != KNOT_EOK) {
1006 		return ret;
1007 	}
1008 
1009 	// Second send 'to' changeset part.
1010 	return send_changeset_part(ch, ctx, false);
1011 }
1012 
zone_txn_diff(zone_t * zone,ctl_args_t * args)1013 static int zone_txn_diff(zone_t *zone, ctl_args_t *args)
1014 {
1015 	if (zone->control_update == NULL) {
1016 		args->suppress = true;
1017 		return KNOT_TXN_ENOTEXISTS;
1018 	}
1019 
1020 	// FULL update has no changeset to print, do a 'get' instead.
1021 	if (zone->control_update->flags & UPDATE_FULL) {
1022 		return zone_flag_txn_get(zone, args, CTL_FLAG_ADD);
1023 	}
1024 
1025 	send_ctx_t *ctx = &ctl_globals.send_ctx;
1026 	int ret = init_send_ctx(ctx, zone->name, args);
1027 	if (ret != KNOT_EOK) {
1028 		return ret;
1029 	}
1030 
1031 	return send_changeset(&zone->control_update->change, ctx);
1032 }
1033 
get_ttl(zone_t * zone,ctl_args_t * args,uint32_t * ttl)1034 static int get_ttl(zone_t *zone, ctl_args_t *args, uint32_t *ttl)
1035 {
1036 	knot_dname_storage_t owner;
1037 
1038 	int ret = get_owner(owner, sizeof(owner), zone->name, args);
1039 	if (ret != KNOT_EOK) {
1040 		return ret;
1041 	}
1042 
1043 	const zone_node_t *node = zone_contents_node_or_nsec3(zone->control_update->new_cont, owner);
1044 	if (node == NULL) {
1045 		return KNOT_EINVAL;
1046 	}
1047 
1048 	uint16_t type;
1049 	if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE], &type) != 0) {
1050 		return KNOT_EINVAL;
1051 	}
1052 
1053 	*ttl = node_rrset(node, type).ttl;
1054 
1055 	return KNOT_EOK;
1056 }
1057 
create_rrset(knot_rrset_t ** rrset,zone_t * zone,ctl_args_t * args,bool need_ttl)1058 static int create_rrset(knot_rrset_t **rrset, zone_t *zone, ctl_args_t *args,
1059                         bool need_ttl)
1060 {
1061 	knot_dname_txt_storage_t origin_buff;
1062 	char *origin = knot_dname_to_str(origin_buff, zone->name, sizeof(origin_buff));
1063 	if (origin == NULL) {
1064 		return KNOT_EINVAL;
1065 	}
1066 
1067 	const char *owner = args->data[KNOT_CTL_IDX_OWNER];
1068 	const char *type  = args->data[KNOT_CTL_IDX_TYPE];
1069 	const char *data  = args->data[KNOT_CTL_IDX_DATA];
1070 	const char *ttl   = need_ttl ? args->data[KNOT_CTL_IDX_TTL] : NULL;
1071 
1072 	// Prepare a buffer for a reconstructed record.
1073 	const size_t buff_len = sizeof(ctl_globals.txt_rr);
1074 	char *buff = ctl_globals.txt_rr;
1075 
1076 	uint32_t default_ttl = 0;
1077 	if (ttl == NULL) {
1078 		int ret = get_ttl(zone, args, &default_ttl);
1079 		if (need_ttl && ret != KNOT_EOK) {
1080 			return ret;
1081 		}
1082 	}
1083 
1084 	// Reconstruct the record.
1085 	int ret = snprintf(buff, buff_len, "%s %s %s %s\n",
1086 	                   (owner != NULL ? owner : ""),
1087 	                   (ttl   != NULL ? ttl   : ""),
1088 	                   (type  != NULL ? type  : ""),
1089 	                   (data  != NULL ? data  : ""));
1090 	if (ret <= 0 || ret >= buff_len) {
1091 		return KNOT_ESPACE;
1092 	}
1093 	size_t rdata_len = ret;
1094 
1095 	// Parse the record.
1096 	zs_scanner_t *scanner = &ctl_globals.scanner;
1097 	if (zs_init(scanner, origin, KNOT_CLASS_IN, default_ttl) != 0 ||
1098 	    zs_set_input_string(scanner, buff, rdata_len) != 0 ||
1099 	    zs_parse_record(scanner) != 0 ||
1100 	    scanner->state != ZS_STATE_DATA) {
1101 		ret = KNOT_EPARSEFAIL;
1102 		goto parser_failed;
1103 	}
1104 	knot_dname_to_lower(scanner->r_owner);
1105 
1106 	// Create output rrset.
1107 	*rrset = knot_rrset_new(scanner->r_owner, scanner->r_type,
1108 	                        scanner->r_class, scanner->r_ttl, NULL);
1109 	if (*rrset == NULL) {
1110 		ret = KNOT_ENOMEM;
1111 		goto parser_failed;
1112 	}
1113 
1114 	ret = knot_rrset_add_rdata(*rrset, scanner->r_data, scanner->r_data_length,
1115 	                           NULL);
1116 parser_failed:
1117 	zs_deinit(scanner);
1118 
1119 	return ret;
1120 }
1121 
zone_txn_set(zone_t * zone,ctl_args_t * args)1122 static int zone_txn_set(zone_t *zone, ctl_args_t *args)
1123 {
1124 	if (zone->control_update == NULL) {
1125 		args->suppress = true;
1126 		return KNOT_TXN_ENOTEXISTS;
1127 	}
1128 
1129 	if (args->data[KNOT_CTL_IDX_OWNER] == NULL ||
1130 	    args->data[KNOT_CTL_IDX_TYPE]  == NULL) {
1131 		return KNOT_EINVAL;
1132 	}
1133 
1134 	knot_rrset_t *rrset;
1135 	int ret = create_rrset(&rrset, zone, args, true);
1136 	if (ret != KNOT_EOK) {
1137 		return ret;
1138 	}
1139 
1140 	ret = zone_update_add(zone->control_update, rrset);
1141 	knot_rrset_free(rrset, NULL);
1142 
1143 	return ret;
1144 }
1145 
zone_txn_unset(zone_t * zone,ctl_args_t * args)1146 static int zone_txn_unset(zone_t *zone, ctl_args_t *args)
1147 {
1148 	if (zone->control_update == NULL) {
1149 		args->suppress = true;
1150 		return KNOT_TXN_ENOTEXISTS;
1151 	}
1152 
1153 	if (args->data[KNOT_CTL_IDX_OWNER] == NULL) {
1154 		return KNOT_EINVAL;
1155 	}
1156 
1157 	// Remove specific record.
1158 	if (args->data[KNOT_CTL_IDX_DATA] != NULL) {
1159 		if (args->data[KNOT_CTL_IDX_TYPE] == NULL) {
1160 			return KNOT_EINVAL;
1161 		}
1162 
1163 		knot_rrset_t *rrset;
1164 		int ret = create_rrset(&rrset, zone, args, false);
1165 		if (ret != KNOT_EOK) {
1166 			return ret;
1167 		}
1168 
1169 		ret = zone_update_remove(zone->control_update, rrset);
1170 		knot_rrset_free(rrset, NULL);
1171 		return ret;
1172 	} else {
1173 		knot_dname_storage_t owner;
1174 
1175 		int ret = get_owner(owner, sizeof(owner), zone->name, args);
1176 		if (ret != KNOT_EOK) {
1177 			return ret;
1178 		}
1179 
1180 		// Remove whole rrset.
1181 		if (args->data[KNOT_CTL_IDX_TYPE] != NULL) {
1182 			uint16_t type;
1183 			if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE],
1184 			                            &type) != 0) {
1185 				return KNOT_EINVAL;
1186 			}
1187 
1188 			return zone_update_remove_rrset(zone->control_update, owner, type);
1189 		// Remove whole node.
1190 		} else {
1191 			return zone_update_remove_node(zone->control_update, owner);
1192 		}
1193 	}
1194 }
1195 
zone_exists(const knot_dname_t * zone,void * data)1196 static bool zone_exists(const knot_dname_t *zone, void *data)
1197 {
1198 	assert(zone);
1199 	assert(data);
1200 
1201 	knot_zonedb_t *db = data;
1202 
1203 	return knot_zonedb_find(db, zone) != NULL;
1204 }
1205 
zone_names_distinct(const knot_dname_t * zone,void * data)1206 static bool zone_names_distinct(const knot_dname_t *zone, void *data)
1207 {
1208 	assert(zone);
1209 	assert(data);
1210 
1211 	knot_dname_t *zone_to_purge = data;
1212 
1213 	return !knot_dname_is_equal(zone, zone_to_purge);
1214 }
1215 
drop_journal_if_orphan(const knot_dname_t * for_zone,void * ctx)1216 static int drop_journal_if_orphan(const knot_dname_t *for_zone, void *ctx)
1217 {
1218 	server_t *server = ctx;
1219 	zone_journal_t j = { &server->journaldb, for_zone };
1220 	if (!zone_exists(for_zone, server->zone_db)) {
1221 		return journal_scrape_with_md(j, false);
1222 	}
1223 	return KNOT_EOK;
1224 }
1225 
log_if_orphans_error(knot_dname_t * zone_name,int err,char * db_type)1226 static void log_if_orphans_error(knot_dname_t *zone_name, int err, char *db_type)
1227 {
1228 	if (err == KNOT_EOK || err == KNOT_ENOENT || err == KNOT_EFILE) {
1229 		return;
1230 	}
1231 
1232 	char *msg = sprintf_alloc("failed to purge orphan from %s database (%s)",
1233 	                          db_type, knot_strerror(err));
1234 	if (msg == NULL) {
1235 		return;
1236 	}
1237 
1238 	if (zone_name == NULL) {
1239 		log_error("%s", msg);
1240 	} else {
1241 		log_zone_error(zone_name, "%s", msg);
1242 	}
1243 	free(msg);
1244 }
1245 
orphans_purge(ctl_args_t * args)1246 static int orphans_purge(ctl_args_t *args)
1247 {
1248 	assert(args->data[KNOT_CTL_IDX_FILTER] != NULL);
1249 	bool only_orphan = (strlen(args->data[KNOT_CTL_IDX_FILTER]) == 1);
1250 	int ret;
1251 
1252 	if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
1253 		// Purge KASP DB.
1254 		if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_KASPDB)) {
1255 			ret = kasp_db_sweep(&args->server->kaspdb,
1256 			                    zone_exists, args->server->zone_db);
1257 			log_if_orphans_error(NULL, ret, "KASP");
1258 		}
1259 
1260 		// Purge zone journals of unconfigured zones.
1261 		if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_JOURNAL)) {
1262 			ret = journals_walk(&args->server->journaldb,
1263 			                    drop_journal_if_orphan, args->server);
1264 			log_if_orphans_error(NULL, ret, "journal");
1265 		}
1266 
1267 		// Purge timers of unconfigured zones.
1268 		if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_TIMERS)) {
1269 			ret = zone_timers_sweep(&args->server->timerdb,
1270 			                        zone_exists, args->server->zone_db);
1271 			log_if_orphans_error(NULL, ret, "timer");
1272 		}
1273 	} else {
1274 		knot_dname_storage_t buff;
1275 		while (true) {
1276 			knot_dname_t *zone_name =
1277 				knot_dname_from_str(buff, args->data[KNOT_CTL_IDX_ZONE],
1278 				                    sizeof(buff));
1279 			if (zone_name == NULL) {
1280 				log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE],
1281 				                       "control, error (%s)",
1282 				                       knot_strerror(KNOT_EINVAL));
1283 				send_error(args, knot_strerror(KNOT_EINVAL));
1284 				return KNOT_EINVAL;
1285 			}
1286 			knot_dname_to_lower(zone_name);
1287 
1288 			if (!zone_exists(zone_name, args->server->zone_db)) {
1289 				// Purge KASP DB.
1290 				if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_KASPDB)) {
1291 					if (knot_lmdb_open(&args->server->kaspdb) == KNOT_EOK) {
1292 						ret = kasp_db_delete_all(&args->server->kaspdb, zone_name);
1293 						log_if_orphans_error(zone_name, ret, "KASP");
1294 					}
1295 				}
1296 
1297 				// Purge zone journal.
1298 				if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_JOURNAL)) {
1299 					zone_journal_t j = { &args->server->journaldb, zone_name };
1300 					ret = journal_scrape_with_md(j, true);
1301 					log_if_orphans_error(zone_name, ret, "journal");
1302 				}
1303 
1304 				// Purge zone timers.
1305 				if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_TIMERS)) {
1306 					ret = zone_timers_sweep(&args->server->timerdb,
1307 					                        zone_names_distinct, zone_name);
1308 					log_if_orphans_error(zone_name, ret, "timer");
1309 				}
1310 			}
1311 
1312 			// Get next zone name.
1313 			ret = knot_ctl_receive(args->ctl, &args->type, &args->data);
1314 			if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) {
1315 				break;
1316 			}
1317 			strtolower((char *)args->data[KNOT_CTL_IDX_ZONE]);
1318 
1319 			// Log the other zones the same way as the first one from process.c.
1320 			log_ctl_zone_str_info(args->data[KNOT_CTL_IDX_ZONE],
1321 			                      "control, received command '%s'",
1322 			                      args->data[KNOT_CTL_IDX_CMD]);
1323 		}
1324 	}
1325 
1326 	return KNOT_EOK;
1327 }
1328 
1329 #define RETURN_IF_FAILED(str, exception) \
1330 { \
1331 	if (ret != KNOT_EOK && ret != (exception)) { \
1332 		log_zone_error(zone->name, \
1333 		               "failed to %s (%s)", (str), knot_strerror(ret)); \
1334 		return ret; \
1335 	} \
1336 }
1337 
zone_purge(zone_t * zone,ctl_args_t * args)1338 static int zone_purge(zone_t *zone, ctl_args_t *args)
1339 {
1340 	int ret = KNOT_EOK;
1341 
1342 	// Abort possible editing transaction.
1343 	if (MATCH_OR_FILTER(args, CTL_FILTER_PURGE_EXPIRE)) {
1344 		ret = zone_txn_abort(zone, args);
1345 		RETURN_IF_FAILED("abort pending transaction", KNOT_TXN_ENOTEXISTS);
1346 	}
1347 
1348 	// Purge the zone timers.
1349 	if (MATCH_OR_FILTER(args, CTL_FILTER_PURGE_TIMERS)) {
1350 		memset(&zone->timers, 0, sizeof(zone->timers));
1351 		ret = zone_timers_sweep(&args->server->timerdb,
1352 		                        zone_names_distinct, zone->name);
1353 		RETURN_IF_FAILED("purge timers", KNOT_ENOENT);
1354 	}
1355 
1356 	// Expire the zone.
1357 	if (MATCH_OR_FILTER(args, CTL_FILTER_PURGE_EXPIRE)) {
1358 		// KNOT_EOK is the only return value from event_expire().
1359 		(void)schedule_trigger(zone, args, ZONE_EVENT_EXPIRE, true);
1360 	}
1361 
1362 	// Purge the zone file.
1363 	if (MATCH_OR_FILTER(args, CTL_FILTER_PURGE_ZONEFILE)) {
1364 		char *zonefile = conf_zonefile(conf(), zone->name);
1365 		ret = (unlink(zonefile) == -1 ? knot_map_errno() : KNOT_EOK);
1366 		free(zonefile);
1367 		RETURN_IF_FAILED("purge zone file", KNOT_ENOENT);
1368 	}
1369 
1370 	// Purge the zone journal.
1371 	if (MATCH_OR_FILTER(args, CTL_FILTER_PURGE_JOURNAL)) {
1372 		ret = journal_scrape_with_md(zone_journal(zone), true);
1373 		RETURN_IF_FAILED("purge journal", KNOT_ENOENT);
1374 	}
1375 
1376 	// Purge KASP DB.
1377 	if (MATCH_OR_FILTER(args, CTL_FILTER_PURGE_KASPDB)) {
1378 		ret = knot_lmdb_open(zone->kaspdb);
1379 		if (ret == KNOT_EOK) {
1380 			ret = kasp_db_delete_all(zone->kaspdb, zone->name);
1381 		}
1382 		RETURN_IF_FAILED("purge KASP DB", KNOT_ENOENT);
1383 	}
1384 
1385 	return KNOT_EOK;
1386 }
1387 
send_stats_ctr(mod_ctr_t * ctr,uint64_t ** stats_vals,unsigned threads,ctl_args_t * args,knot_ctl_data_t * data)1388 static int send_stats_ctr(mod_ctr_t *ctr, uint64_t **stats_vals, unsigned threads,
1389                           ctl_args_t *args, knot_ctl_data_t *data)
1390 {
1391 	char index[128];
1392 	char value[32];
1393 
1394 	if (ctr->count == 1) {
1395 		uint64_t counter = stats_get_counter(stats_vals, ctr->offset, threads);
1396 		int ret = snprintf(value, sizeof(value), "%"PRIu64, counter);
1397 		if (ret <= 0 || ret >= sizeof(value)) {
1398 			return KNOT_ESPACE;
1399 		}
1400 
1401 		(*data)[KNOT_CTL_IDX_ID] = NULL;
1402 		(*data)[KNOT_CTL_IDX_DATA] = value;
1403 
1404 		ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, data);
1405 		if (ret != KNOT_EOK) {
1406 			return ret;
1407 		}
1408 	} else {
1409 		bool force = ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS],
1410 		                          CTL_FLAG_FORCE);
1411 
1412 		for (uint32_t i = 0; i < ctr->count; i++) {
1413 			uint64_t counter = stats_get_counter(stats_vals, ctr->offset + i, threads);
1414 
1415 			// Skip empty counters.
1416 			if (counter == 0 && !force) {
1417 				continue;
1418 			}
1419 
1420 			int ret;
1421 			if (ctr->idx_to_str) {
1422 				char *str = ctr->idx_to_str(i, ctr->count);
1423 				if (str == NULL) {
1424 					continue;
1425 				}
1426 				ret = snprintf(index, sizeof(index), "%s", str);
1427 				free(str);
1428 			} else {
1429 				ret = snprintf(index, sizeof(index), "%u", i);
1430 			}
1431 			if (ret <= 0 || ret >= sizeof(index)) {
1432 				return KNOT_ESPACE;
1433 			}
1434 
1435 			ret = snprintf(value, sizeof(value), "%"PRIu64, counter);
1436 			if (ret <= 0 || ret >= sizeof(value)) {
1437 				return KNOT_ESPACE;
1438 			}
1439 
1440 			(*data)[KNOT_CTL_IDX_ID] = index;
1441 			(*data)[KNOT_CTL_IDX_DATA] = value;
1442 
1443 			knot_ctl_type_t type = (i == 0) ? KNOT_CTL_TYPE_DATA :
1444 			                                  KNOT_CTL_TYPE_EXTRA;
1445 			ret = knot_ctl_send(args->ctl, type, data);
1446 			if (ret != KNOT_EOK) {
1447 				return ret;
1448 			}
1449 		}
1450 	}
1451 
1452 	return KNOT_EOK;
1453 }
1454 
modules_stats(list_t * query_modules,ctl_args_t * args,knot_dname_t * zone)1455 static int modules_stats(list_t *query_modules, ctl_args_t *args, knot_dname_t *zone)
1456 {
1457 	if (query_modules == NULL) {
1458 		return KNOT_EOK;
1459 	}
1460 
1461 	const char *section = args->data[KNOT_CTL_IDX_SECTION];
1462 	const char *item = args->data[KNOT_CTL_IDX_ITEM];
1463 
1464 	knot_dname_txt_storage_t name = "";
1465 	knot_ctl_data_t data = { 0 };
1466 
1467 	bool section_found = (section == NULL) ? true : false;
1468 	bool item_found = (item == NULL) ? true : false;
1469 
1470 	knotd_mod_t *mod;
1471 	WALK_LIST(mod, *query_modules) {
1472 		// Skip modules without statistics.
1473 		if (mod->stats_count == 0) {
1474 			continue;
1475 		}
1476 
1477 		// Check for specific module.
1478 		if (section != NULL) {
1479 			if (section_found) {
1480 				break;
1481 			} else if (strcasecmp(mod->id->name + 1, section) == 0) {
1482 				section_found = true;
1483 			} else {
1484 				continue;
1485 			}
1486 		}
1487 
1488 		data[KNOT_CTL_IDX_SECTION] = mod->id->name + 1;
1489 
1490 		unsigned threads = knotd_mod_threads(mod);
1491 
1492 		for (int i = 0; i < mod->stats_count; i++) {
1493 			mod_ctr_t *ctr = mod->stats_info + i;
1494 
1495 			// Skip empty counter.
1496 			if (ctr->name == NULL) {
1497 				continue;
1498 			}
1499 
1500 			// Check for specific counter.
1501 			if (item != NULL) {
1502 				if (item_found) {
1503 					break;
1504 				} else if (strcasecmp(ctr->name, item) == 0) {
1505 					item_found = true;
1506 				} else {
1507 					continue;
1508 				}
1509 			}
1510 
1511 			// Prepare zone name if not already prepared.
1512 			if (zone != NULL && name[0] == '\0') {
1513 				if (knot_dname_to_str(name, zone, sizeof(name)) == NULL) {
1514 					return KNOT_EINVAL;
1515 				}
1516 				data[KNOT_CTL_IDX_ZONE] = name;
1517 			}
1518 
1519 			data[KNOT_CTL_IDX_ITEM] = ctr->name;
1520 
1521 			// Send the counters.
1522 			int ret = send_stats_ctr(ctr, mod->stats_vals, threads, args, &data);
1523 			if (ret != KNOT_EOK) {
1524 				return ret;
1525 			}
1526 		}
1527 	}
1528 
1529 	return (section_found && item_found) ? KNOT_EOK : KNOT_ENOENT;
1530 }
1531 
zone_stats(zone_t * zone,ctl_args_t * args)1532 static int zone_stats(zone_t *zone, ctl_args_t *args)
1533 {
1534 	return modules_stats(&zone->query_modules, args, zone->name);
1535 }
1536 
ctl_zone(ctl_args_t * args,ctl_cmd_t cmd)1537 static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd)
1538 {
1539 	switch (cmd) {
1540 	case CTL_ZONE_STATUS:
1541 		return zones_apply(args, zone_status);
1542 	case CTL_ZONE_RELOAD:
1543 		return zones_apply(args, zone_reload);
1544 	case CTL_ZONE_REFRESH:
1545 		return zones_apply(args, zone_refresh);
1546 	case CTL_ZONE_RETRANSFER:
1547 		return zones_apply(args, zone_retransfer);
1548 	case CTL_ZONE_NOTIFY:
1549 		return zones_apply(args, zone_notify);
1550 	case CTL_ZONE_FLUSH:
1551 		return zones_apply(args, zone_flush);
1552 	case CTL_ZONE_BACKUP:
1553 		return zones_apply_backup(args, false);
1554 	case CTL_ZONE_RESTORE:
1555 		return zones_apply_backup(args, true);
1556 	case CTL_ZONE_SIGN:
1557 		return zones_apply(args, zone_sign);
1558 	case CTL_ZONE_KEYS_LOAD:
1559 		return zones_apply(args, zone_keys_load);
1560 	case CTL_ZONE_KEY_ROLL:
1561 		return zones_apply(args, zone_key_roll);
1562 	case CTL_ZONE_KSK_SBM:
1563 		return zones_apply(args, zone_ksk_sbm_confirm);
1564 	case CTL_ZONE_FREEZE:
1565 		return zones_apply(args, zone_freeze);
1566 	case CTL_ZONE_THAW:
1567 		return zones_apply(args, zone_thaw);
1568 	case CTL_ZONE_READ:
1569 		return zones_apply(args, zone_read);
1570 	case CTL_ZONE_BEGIN:
1571 		return zones_apply(args, zone_txn_begin);
1572 	case CTL_ZONE_COMMIT:
1573 		return zones_apply(args, zone_txn_commit);
1574 	case CTL_ZONE_ABORT:
1575 		return zones_apply(args, zone_txn_abort);
1576 	case CTL_ZONE_DIFF:
1577 		return zones_apply(args, zone_txn_diff);
1578 	case CTL_ZONE_GET:
1579 		return zones_apply(args, zone_txn_get);
1580 	case CTL_ZONE_SET:
1581 		return zones_apply(args, zone_txn_set);
1582 	case CTL_ZONE_UNSET:
1583 		return zones_apply(args, zone_txn_unset);
1584 	case CTL_ZONE_PURGE:
1585 		if (MATCH_AND_FILTER(args, CTL_FILTER_PURGE_ORPHAN)) {
1586 			return orphans_purge(args);
1587 		} else {
1588 			return zones_apply(args, zone_purge);
1589 		}
1590 	case CTL_ZONE_STATS:
1591 		return zones_apply(args, zone_stats);
1592 	default:
1593 		assert(0);
1594 		return KNOT_EINVAL;
1595 	}
1596 }
1597 
server_status(ctl_args_t * args)1598 static int server_status(ctl_args_t *args)
1599 {
1600 	const char *type = args->data[KNOT_CTL_IDX_TYPE];
1601 
1602 	if (type == NULL || strlen(type) == 0) {
1603 		return KNOT_EOK;
1604 	}
1605 
1606 	char buff[2048] = "";
1607 
1608 	int ret;
1609 	if (strcasecmp(type, "version") == 0) {
1610 		ret = snprintf(buff, sizeof(buff), "Version: %s", PACKAGE_VERSION);
1611 	} else if (strcasecmp(type, "workers") == 0) {
1612 		int running_bkg_wrk, wrk_queue;
1613 		worker_pool_status(args->server->workers, false, &running_bkg_wrk, &wrk_queue);
1614 		ret = snprintf(buff, sizeof(buff), "UDP workers: %zu, TCP workers: %zu, "
1615 		               "XDP workers: %zu, background workers: %zu (running: %d, pending: %d)",
1616 		               conf()->cache.srv_udp_threads, conf()->cache.srv_tcp_threads,
1617 		               conf()->cache.srv_xdp_threads, conf()->cache.srv_bg_threads,
1618 		               running_bkg_wrk, wrk_queue);
1619 	} else if (strcasecmp(type, "configure") == 0) {
1620 		ret = snprintf(buff, sizeof(buff), "%s", CONFIGURE_SUMMARY);
1621 	} else {
1622 		return KNOT_EINVAL;
1623 	}
1624 	if (ret <= 0 || ret >= sizeof(buff)) {
1625 		return KNOT_ESPACE;
1626 	}
1627 
1628 	args->data[KNOT_CTL_IDX_DATA] = buff;
1629 
1630 	return knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &args->data);
1631 }
1632 
ctl_server(ctl_args_t * args,ctl_cmd_t cmd)1633 static int ctl_server(ctl_args_t *args, ctl_cmd_t cmd)
1634 {
1635 	int ret = KNOT_EOK;
1636 
1637 	switch (cmd) {
1638 	case CTL_STATUS:
1639 		ret = server_status(args);
1640 		if (ret != KNOT_EOK) {
1641 			send_error(args, knot_strerror(ret));
1642 		}
1643 		break;
1644 	case CTL_STOP:
1645 		ret = KNOT_CTL_ESTOP;
1646 		break;
1647 	case CTL_RELOAD:
1648 		ret = server_reload(args->server);
1649 		if (ret != KNOT_EOK) {
1650 			send_error(args, knot_strerror(ret));
1651 		}
1652 		break;
1653 	default:
1654 		assert(0);
1655 		ret = KNOT_EINVAL;
1656 	}
1657 
1658 	return ret;
1659 }
1660 
ctl_stats(ctl_args_t * args,ctl_cmd_t cmd)1661 static int ctl_stats(ctl_args_t *args, ctl_cmd_t cmd)
1662 {
1663 	const char *section = args->data[KNOT_CTL_IDX_SECTION];
1664 	const char *item = args->data[KNOT_CTL_IDX_ITEM];
1665 
1666 	bool found = (section == NULL) ? true : false;
1667 
1668 	// Process server metrics.
1669 	if (section == NULL || strcasecmp(section, "server") == 0) {
1670 		char value[32];
1671 		knot_ctl_data_t data = {
1672 			[KNOT_CTL_IDX_SECTION] = "server",
1673 			[KNOT_CTL_IDX_DATA] = value
1674 		};
1675 
1676 		for (const stats_item_t *i = server_stats; i->name != NULL; i++) {
1677 			if (item != NULL) {
1678 				if (found) {
1679 					break;
1680 				} else if (strcmp(i->name, item) == 0) {
1681 					found = true;
1682 				} else {
1683 					continue;
1684 				}
1685 			} else {
1686 				found = true;
1687 			}
1688 
1689 			data[KNOT_CTL_IDX_ITEM] = i->name;
1690 			int ret = snprintf(value, sizeof(value), "%"PRIu64,
1691 			                   i->val(args->server));
1692 			if (ret <= 0 || ret >= sizeof(value)) {
1693 				ret = KNOT_ESPACE;
1694 				send_error(args, knot_strerror(ret));
1695 				return ret;
1696 			}
1697 
1698 			ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &data);
1699 			if (ret != KNOT_EOK) {
1700 				send_error(args, knot_strerror(ret));
1701 				return ret;
1702 			}
1703 		}
1704 	}
1705 
1706 	// Process modules metrics.
1707 	if (section == NULL || strncasecmp(section, "mod-", strlen("mod-")) == 0) {
1708 		int ret = modules_stats(conf()->query_modules, args, NULL);
1709 		if (ret != KNOT_EOK) {
1710 			send_error(args, knot_strerror(ret));
1711 			return ret;
1712 		}
1713 
1714 		found = true;
1715 	}
1716 
1717 	if (!found) {
1718 		send_error(args, knot_strerror(KNOT_EINVAL));
1719 		return KNOT_EINVAL;
1720 	}
1721 
1722 	return KNOT_EOK;
1723 }
1724 
send_block_data(conf_io_t * io,knot_ctl_data_t * data)1725 static int send_block_data(conf_io_t *io, knot_ctl_data_t *data)
1726 {
1727 	knot_ctl_t *ctl = (knot_ctl_t *)io->misc;
1728 
1729 	const yp_item_t *item = (io->key1 != NULL) ? io->key1 : io->key0;
1730 	assert(item != NULL);
1731 
1732 	char buff[YP_MAX_TXT_DATA_LEN + 1] = "\0";
1733 
1734 	(*data)[KNOT_CTL_IDX_DATA] = buff;
1735 
1736 	// Format explicit binary data value.
1737 	if (io->data.bin != NULL) {
1738 		size_t buff_len = sizeof(buff);
1739 		int ret = yp_item_to_txt(item, io->data.bin, io->data.bin_len, buff,
1740 		                         &buff_len, YP_SNOQUOTE);
1741 		if (ret != KNOT_EOK) {
1742 			return ret;
1743 		}
1744 		return knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, data);
1745 	// Format all multivalued item data if no specified index.
1746 	} else if ((item->flags & YP_FMULTI) && io->data.index == 0) {
1747 		size_t values = conf_val_count(io->data.val);
1748 		for (size_t i = 0; i < values; i++) {
1749 			conf_val(io->data.val);
1750 			size_t buff_len = sizeof(buff);
1751 			int ret = yp_item_to_txt(item, io->data.val->data,
1752 			                         io->data.val->len, buff,&buff_len,
1753 			                         YP_SNOQUOTE);
1754 			if (ret != KNOT_EOK) {
1755 				return ret;
1756 			}
1757 
1758 			knot_ctl_type_t type = (i == 0) ? KNOT_CTL_TYPE_DATA :
1759 			                                  KNOT_CTL_TYPE_EXTRA;
1760 			ret = knot_ctl_send(ctl, type, data);
1761 			if (ret != KNOT_EOK) {
1762 				return ret;
1763 			}
1764 
1765 			conf_val_next(io->data.val);
1766 		}
1767 		return KNOT_EOK;
1768 	// Format singlevalued item data or a specified one from multivalued.
1769 	} else {
1770 		conf_val(io->data.val);
1771 		size_t buff_len = sizeof(buff);
1772 		int ret = yp_item_to_txt(item, io->data.val->data, io->data.val->len,
1773 		                         buff, &buff_len, YP_SNOQUOTE);
1774 		if (ret != KNOT_EOK) {
1775 			return ret;
1776 		}
1777 		return knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, data);
1778 	}
1779 }
1780 
send_block(conf_io_t * io)1781 static int send_block(conf_io_t *io)
1782 {
1783 	knot_ctl_t *ctl = (knot_ctl_t *)io->misc;
1784 
1785 	// Get possible error message.
1786 	const char *err = io->error.str;
1787 	if (err == NULL && io->error.code != KNOT_EOK) {
1788 		err = knot_strerror(io->error.code);
1789 	}
1790 
1791 	knot_ctl_data_t data = {
1792 		[KNOT_CTL_IDX_ERROR] = err,
1793 	};
1794 
1795 	if (io->key0 != NULL) {
1796 		data[KNOT_CTL_IDX_SECTION] = io->key0->name + 1;
1797 	}
1798 	if (io->key1 != NULL) {
1799 		data[KNOT_CTL_IDX_ITEM] = io->key1->name + 1;
1800 	}
1801 
1802 	// Get the item prefix.
1803 	switch (io->type) {
1804 	case NEW: data[KNOT_CTL_IDX_FLAGS] = CTL_FLAG_ADD; break;
1805 	case OLD: data[KNOT_CTL_IDX_FLAGS] = CTL_FLAG_REM; break;
1806 	default: break;
1807 	}
1808 
1809 	knot_dname_txt_storage_t id;
1810 
1811 	// Get the textual item id.
1812 	if (io->id_len > 0 && io->key0 != NULL) {
1813 		size_t id_len = sizeof(id);
1814 		int ret = yp_item_to_txt(io->key0->var.g.id, io->id, io->id_len,
1815 		                         id, &id_len, YP_SNOQUOTE);
1816 		if (ret != KNOT_EOK) {
1817 			return ret;
1818 		}
1819 		if (io->id_as_data) {
1820 			data[KNOT_CTL_IDX_DATA] = id;
1821 		} else {
1822 			data[KNOT_CTL_IDX_ID] = id;
1823 		}
1824 	}
1825 
1826 	if (io->data.val == NULL && io->data.bin == NULL) {
1827 		return knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &data);
1828 	} else {
1829 		return send_block_data(io, &data);
1830 	}
1831 }
1832 
ctl_conf_txn(ctl_args_t * args,ctl_cmd_t cmd)1833 static int ctl_conf_txn(ctl_args_t *args, ctl_cmd_t cmd)
1834 {
1835 	conf_io_t io = {
1836 		.fcn = send_block,
1837 		.misc = args->ctl
1838 	};
1839 
1840 	int ret = KNOT_EOK;
1841 
1842 	switch (cmd) {
1843 	case CTL_CONF_BEGIN:
1844 		ret = conf_io_begin(false);
1845 		break;
1846 	case CTL_CONF_ABORT:
1847 		conf_io_abort(false);
1848 		ret = KNOT_EOK;
1849 		break;
1850 	case CTL_CONF_COMMIT:
1851 		// First check the database.
1852 		ret = conf_io_check(&io);
1853 		if (ret != KNOT_EOK) {
1854 			// A semantic error is already sent by the check function.
1855 			if (io.error.code != KNOT_EOK) {
1856 				return KNOT_EOK;
1857 			}
1858 			// No transaction abort!
1859 			break;
1860 		}
1861 
1862 		ret = conf_io_commit(false);
1863 		if (ret != KNOT_EOK) {
1864 			conf_io_abort(false);
1865 			break;
1866 		}
1867 
1868 		ret = server_reload(args->server);
1869 		break;
1870 	default:
1871 		assert(0);
1872 		ret = KNOT_EINVAL;
1873 	}
1874 
1875 	if (ret != KNOT_EOK) {
1876 		send_error(args, knot_strerror(ret));
1877 	}
1878 
1879 	return ret;
1880 }
1881 
ctl_conf_read(ctl_args_t * args,ctl_cmd_t cmd)1882 static int ctl_conf_read(ctl_args_t *args, ctl_cmd_t cmd)
1883 {
1884 	conf_io_t io = {
1885 		.fcn = send_block,
1886 		.misc = args->ctl
1887 	};
1888 
1889 	int ret = KNOT_EOK;
1890 
1891 	while (true) {
1892 		const char *key0 = args->data[KNOT_CTL_IDX_SECTION];
1893 		const char *key1 = args->data[KNOT_CTL_IDX_ITEM];
1894 		const char *id   = args->data[KNOT_CTL_IDX_ID];
1895 
1896 		ctl_log_conf_data(&args->data);
1897 
1898 		switch (cmd) {
1899 		case CTL_CONF_LIST:
1900 			ret = conf_io_list(key0, &io);
1901 			break;
1902 		case CTL_CONF_READ:
1903 			ret = conf_io_get(key0, key1, id, true, &io);
1904 			break;
1905 		case CTL_CONF_DIFF:
1906 			ret = conf_io_diff(key0, key1, id, &io);
1907 			break;
1908 		case CTL_CONF_GET:
1909 			ret = conf_io_get(key0, key1, id, false, &io);
1910 			break;
1911 		default:
1912 			assert(0);
1913 			ret = KNOT_EINVAL;
1914 		}
1915 		if (ret != KNOT_EOK) {
1916 			send_error(args, knot_strerror(ret));
1917 			break;
1918 		}
1919 
1920 		// Get next data unit.
1921 		ret = knot_ctl_receive(args->ctl, &args->type, &args->data);
1922 		if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) {
1923 			break;
1924 		}
1925 	}
1926 
1927 	return ret;
1928 }
1929 
ctl_conf_modify(ctl_args_t * args,ctl_cmd_t cmd)1930 static int ctl_conf_modify(ctl_args_t *args, ctl_cmd_t cmd)
1931 {
1932 	// Start child transaction.
1933 	int ret = conf_io_begin(true);
1934 	if (ret != KNOT_EOK) {
1935 		send_error(args, knot_strerror(ret));
1936 		return ret;
1937 	}
1938 
1939 	while (true) {
1940 		const char *key0 = args->data[KNOT_CTL_IDX_SECTION];
1941 		const char *key1 = args->data[KNOT_CTL_IDX_ITEM];
1942 		const char *id   = args->data[KNOT_CTL_IDX_ID];
1943 		const char *data = args->data[KNOT_CTL_IDX_DATA];
1944 
1945 		ctl_log_conf_data(&args->data);
1946 
1947 		switch (cmd) {
1948 		case CTL_CONF_SET:
1949 			ret = conf_io_set(key0, key1, id, data);
1950 			break;
1951 		case CTL_CONF_UNSET:
1952 			ret = conf_io_unset(key0, key1, id, data);
1953 			break;
1954 		default:
1955 			assert(0);
1956 			ret = KNOT_EINVAL;
1957 		}
1958 		if (ret != KNOT_EOK) {
1959 			send_error(args, knot_strerror(ret));
1960 			break;
1961 		}
1962 
1963 		// Get next data unit.
1964 		ret = knot_ctl_receive(args->ctl, &args->type, &args->data);
1965 		if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) {
1966 			break;
1967 		}
1968 	}
1969 
1970 	// Finish child transaction.
1971 	if (ret == KNOT_EOK) {
1972 		ret = conf_io_commit(true);
1973 		if (ret != KNOT_EOK) {
1974 			send_error(args, knot_strerror(ret));
1975 		}
1976 	} else {
1977 		conf_io_abort(true);
1978 	}
1979 
1980 	return ret;
1981 }
1982 
1983 typedef struct {
1984 	const char *name;
1985 	int (*fcn)(ctl_args_t *, ctl_cmd_t);
1986 } desc_t;
1987 
1988 static const desc_t cmd_table[] = {
1989 	[CTL_NONE]            = { "" },
1990 
1991 	[CTL_STATUS]          = { "status",          ctl_server },
1992 	[CTL_STOP]            = { "stop",            ctl_server },
1993 	[CTL_RELOAD]          = { "reload",          ctl_server },
1994 	[CTL_STATS]           = { "stats",           ctl_stats },
1995 
1996 	[CTL_ZONE_STATUS]     = { "zone-status",        ctl_zone },
1997 	[CTL_ZONE_RELOAD]     = { "zone-reload",        ctl_zone },
1998 	[CTL_ZONE_REFRESH]    = { "zone-refresh",       ctl_zone },
1999 	[CTL_ZONE_RETRANSFER] = { "zone-retransfer",    ctl_zone },
2000 	[CTL_ZONE_NOTIFY]     = { "zone-notify",        ctl_zone },
2001 	[CTL_ZONE_FLUSH]      = { "zone-flush",         ctl_zone },
2002 	[CTL_ZONE_BACKUP]     = { "zone-backup",        ctl_zone },
2003 	[CTL_ZONE_RESTORE]    = { "zone-restore",       ctl_zone },
2004 	[CTL_ZONE_SIGN]       = { "zone-sign",          ctl_zone },
2005 	[CTL_ZONE_KEYS_LOAD]  = { "zone-keys-load",     ctl_zone },
2006 	[CTL_ZONE_KEY_ROLL]   = { "zone-key-rollover",  ctl_zone },
2007 	[CTL_ZONE_KSK_SBM]    = { "zone-ksk-submitted", ctl_zone },
2008 	[CTL_ZONE_FREEZE]     = { "zone-freeze",        ctl_zone },
2009 	[CTL_ZONE_THAW]       = { "zone-thaw",          ctl_zone },
2010 
2011 	[CTL_ZONE_READ]       = { "zone-read",       ctl_zone },
2012 	[CTL_ZONE_BEGIN]      = { "zone-begin",      ctl_zone },
2013 	[CTL_ZONE_COMMIT]     = { "zone-commit",     ctl_zone },
2014 	[CTL_ZONE_ABORT]      = { "zone-abort",      ctl_zone },
2015 	[CTL_ZONE_DIFF]       = { "zone-diff",       ctl_zone },
2016 	[CTL_ZONE_GET]        = { "zone-get",        ctl_zone },
2017 	[CTL_ZONE_SET]        = { "zone-set",        ctl_zone },
2018 	[CTL_ZONE_UNSET]      = { "zone-unset",      ctl_zone },
2019 	[CTL_ZONE_PURGE]      = { "zone-purge",      ctl_zone },
2020 	[CTL_ZONE_STATS]      = { "zone-stats",	     ctl_zone },
2021 
2022 	[CTL_CONF_LIST]       = { "conf-list",       ctl_conf_read },
2023 	[CTL_CONF_READ]       = { "conf-read",       ctl_conf_read },
2024 	[CTL_CONF_BEGIN]      = { "conf-begin",      ctl_conf_txn },
2025 	[CTL_CONF_COMMIT]     = { "conf-commit",     ctl_conf_txn },
2026 	[CTL_CONF_ABORT]      = { "conf-abort",      ctl_conf_txn },
2027 	[CTL_CONF_DIFF]       = { "conf-diff",       ctl_conf_read },
2028 	[CTL_CONF_GET]        = { "conf-get",        ctl_conf_read },
2029 	[CTL_CONF_SET]        = { "conf-set",        ctl_conf_modify },
2030 	[CTL_CONF_UNSET]      = { "conf-unset",      ctl_conf_modify },
2031 };
2032 
2033 #define MAX_CTL_CODE (sizeof(cmd_table) / sizeof(desc_t) - 1)
2034 
ctl_cmd_to_str(ctl_cmd_t cmd)2035 const char *ctl_cmd_to_str(ctl_cmd_t cmd)
2036 {
2037 	if (cmd <= CTL_NONE || cmd > MAX_CTL_CODE) {
2038 		return NULL;
2039 	}
2040 
2041 	return cmd_table[cmd].name;
2042 }
2043 
ctl_str_to_cmd(const char * cmd_str)2044 ctl_cmd_t ctl_str_to_cmd(const char *cmd_str)
2045 {
2046 	if (cmd_str == NULL) {
2047 		return CTL_NONE;
2048 	}
2049 
2050 	for (ctl_cmd_t cmd = CTL_NONE + 1; cmd <= MAX_CTL_CODE; cmd++) {
2051 		if (strcmp(cmd_str, cmd_table[cmd].name) == 0) {
2052 			return cmd;
2053 		}
2054 	}
2055 
2056 	return CTL_NONE;
2057 }
2058 
ctl_exec(ctl_cmd_t cmd,ctl_args_t * args)2059 int ctl_exec(ctl_cmd_t cmd, ctl_args_t *args)
2060 {
2061 	if (args == NULL) {
2062 		return KNOT_EINVAL;
2063 	}
2064 
2065 	return cmd_table[cmd].fcn(args, cmd);
2066 }
2067 
ctl_has_flag(const char * flags,const char * flag)2068 bool ctl_has_flag(const char *flags, const char *flag)
2069 {
2070 	if (flags == NULL || flag == NULL) {
2071 		return false;
2072 	}
2073 
2074 	return strstr(flags, flag) != NULL;
2075 }
2076