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 <stdio.h>
18 #include <histedit.h>
19 
20 #include "knot/common/log.h"
21 #include "utils/common/lookup.h"
22 #include "utils/knotc/interactive.h"
23 #include "utils/knotc/commands.h"
24 #include "contrib/string.h"
25 
26 #define PROGRAM_NAME	"knotc"
27 #define HISTORY_FILE	".knotc_history"
28 
29 extern params_t params;
30 
cmds_lookup(EditLine * el,const char * str,size_t str_len)31 static void cmds_lookup(EditLine *el, const char *str, size_t str_len)
32 {
33 	lookup_t lookup;
34 	int ret = lookup_init(&lookup);
35 	if (ret != KNOT_EOK) {
36 		return;
37 	}
38 
39 	// Fill the lookup with command names.
40 	for (const cmd_desc_t *desc = cmd_table; desc->name != NULL; desc++) {
41 		ret = lookup_insert(&lookup, desc->name, NULL);
42 		if (ret != KNOT_EOK) {
43 			goto cmds_lookup_finish;
44 		}
45 	}
46 
47 	lookup_complete(&lookup, str, str_len, el, true);
48 
49 cmds_lookup_finish:
50 	lookup_deinit(&lookup);
51 }
52 
local_zones_lookup(EditLine * el,const char * str,size_t str_len)53 static void local_zones_lookup(EditLine *el, const char *str, size_t str_len)
54 {
55 	lookup_t lookup;
56 	int ret = lookup_init(&lookup);
57 	if (ret != KNOT_EOK) {
58 		return;
59 	}
60 
61 	knot_dname_txt_storage_t buff;
62 
63 	// Fill the lookup with local zone names.
64 	for (conf_iter_t iter = conf_iter(conf(), C_ZONE);
65 	     iter.code == KNOT_EOK; conf_iter_next(conf(), &iter)) {
66 		conf_val_t val = conf_iter_id(conf(), &iter);
67 		char *name = knot_dname_to_str(buff, conf_dname(&val), sizeof(buff));
68 
69 		ret = lookup_insert(&lookup, name, NULL);
70 		if (ret != KNOT_EOK) {
71 			conf_iter_finish(conf(), &iter);
72 			goto local_zones_lookup_finish;
73 		}
74 	}
75 
76 	lookup_complete(&lookup, str, str_len, el, true);
77 
78 local_zones_lookup_finish:
79 	lookup_deinit(&lookup);
80 }
81 
get_id_name(const char * section)82 static char *get_id_name(const char *section)
83 {
84 	const cmd_desc_t *desc = cmd_table;
85 	while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) {
86 		desc++;
87 	}
88 	assert(desc->name != NULL);
89 
90 	knot_ctl_data_t query = {
91 		[KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd),
92 		[KNOT_CTL_IDX_SECTION] = section
93 	};
94 
95 	knot_ctl_t *ctl = NULL;
96 	knot_ctl_type_t type;
97 	knot_ctl_data_t reply;
98 
99 	// Try to get the first group item (possible id).
100 	if (set_ctl(&ctl, params.socket, DEFAULT_CTL_TIMEOUT_MS, desc) != KNOT_EOK ||
101 	    knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK ||
102 	    knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK ||
103 	    knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK ||
104 	    type != KNOT_CTL_TYPE_DATA || reply[KNOT_CTL_IDX_ERROR] != NULL) {
105 		unset_ctl(ctl);
106 		return NULL;
107 	}
108 
109 	char *id_name = strdup(reply[KNOT_CTL_IDX_ITEM]);
110 
111 	unset_ctl(ctl);
112 
113 	return id_name;
114 }
115 
id_lookup(EditLine * el,const char * str,size_t str_len,const cmd_desc_t * cmd_desc,const char * section,bool add_space)116 static void id_lookup(EditLine *el, const char *str, size_t str_len,
117                       const cmd_desc_t *cmd_desc, const char *section, bool add_space)
118 {
119 	// Decide which confdb transaction to ask.
120 	unsigned ctl_code = (cmd_desc->flags & CMD_FREQ_TXN) ?
121 	                    CTL_CONF_GET : CTL_CONF_READ;
122 
123 	const cmd_desc_t *desc = cmd_table;
124 	while (desc->name != NULL && desc->cmd != ctl_code) {
125 		desc++;
126 	}
127 	assert(desc->name != NULL);
128 
129 	char *id_name = get_id_name(section);
130 	if (id_name == NULL) {
131 		return;
132 	}
133 
134 	knot_ctl_data_t query = {
135 		[KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd),
136 		[KNOT_CTL_IDX_SECTION] = section,
137 		[KNOT_CTL_IDX_ITEM] = id_name
138 	};
139 
140 	lookup_t lookup;
141 	knot_ctl_t *ctl = NULL;
142 
143 	if (set_ctl(&ctl, params.socket, DEFAULT_CTL_TIMEOUT_MS, desc) != KNOT_EOK ||
144 	    knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK ||
145 	    knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK ||
146 	    lookup_init(&lookup) != KNOT_EOK) {
147 		unset_ctl(ctl);
148 		free(id_name);
149 		return;
150 	}
151 
152 	free(id_name);
153 
154 	while (true) {
155 		knot_ctl_type_t type;
156 		knot_ctl_data_t reply;
157 
158 		// Receive one section id.
159 		if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) {
160 			goto id_lookup_finish;
161 		}
162 
163 		// Stop if finished transfer.
164 		if (type != KNOT_CTL_TYPE_DATA) {
165 			break;
166 		}
167 
168 		// Insert the id into the lookup.
169 		if (reply[KNOT_CTL_IDX_ERROR] != NULL ||
170 		    lookup_insert(&lookup, reply[KNOT_CTL_IDX_DATA], NULL) != KNOT_EOK) {
171 			goto id_lookup_finish;
172 		}
173 	}
174 
175 	lookup_complete(&lookup, str, str_len, el, add_space);
176 
177 id_lookup_finish:
178 	lookup_deinit(&lookup);
179 	unset_ctl(ctl);
180 }
181 
list_lookup(EditLine * el,const char * section,const char * item)182 static void list_lookup(EditLine *el, const char *section, const char *item)
183 {
184 	const cmd_desc_t *desc = cmd_table;
185 	while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) {
186 		desc++;
187 	}
188 	assert(desc->name != NULL);
189 
190 	knot_ctl_data_t query = {
191 		[KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd),
192 		[KNOT_CTL_IDX_SECTION] = section
193 	};
194 
195 	lookup_t lookup;
196 	knot_ctl_t *ctl = NULL;
197 
198 	if (set_ctl(&ctl, params.socket, DEFAULT_CTL_TIMEOUT_MS, desc) != KNOT_EOK ||
199 	    knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK ||
200 	    knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK ||
201 	    lookup_init(&lookup) != KNOT_EOK) {
202 		unset_ctl(ctl);
203 		return;
204 	}
205 
206 	while (true) {
207 		knot_ctl_type_t type;
208 		knot_ctl_data_t reply;
209 
210 		// Receive one section/item name.
211 		if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) {
212 			goto list_lookup_finish;
213 		}
214 
215 		// Stop if finished transfer.
216 		if (type != KNOT_CTL_TYPE_DATA) {
217 			break;
218 		}
219 
220 		const char *str = (section == NULL) ? reply[KNOT_CTL_IDX_SECTION] :
221 		                                      reply[KNOT_CTL_IDX_ITEM];
222 
223 		// Insert the name into the lookup.
224 		if (reply[KNOT_CTL_IDX_ERROR] != NULL ||
225 		    lookup_insert(&lookup, str, NULL) != KNOT_EOK) {
226 			goto list_lookup_finish;
227 		}
228 	}
229 
230 	lookup_complete(&lookup, item, strlen(item), el, section != NULL);
231 
232 list_lookup_finish:
233 	lookup_deinit(&lookup);
234 	unset_ctl(ctl);
235 }
236 
item_lookup(EditLine * el,const char * str,const cmd_desc_t * cmd_desc)237 static void item_lookup(EditLine *el, const char *str, const cmd_desc_t *cmd_desc)
238 {
239 	// List all sections.
240 	if (str == NULL) {
241 		list_lookup(el, NULL, "");
242 		return;
243 	}
244 
245 	// Check for id specification.
246 	char *id = (strchr(str, '['));
247 	if (id != NULL) {
248 		char *section = strndup(str, id - str);
249 
250 		// Check for completed id specification.
251 		char *id_stop = (strchr(id, ']'));
252 		if (id_stop != NULL) {
253 			// Complete the item name.
254 			if (*(id_stop + 1) == '.') {
255 				list_lookup(el, section, id_stop + 2);
256 			}
257 		} else {
258 			// Complete the section id.
259 			id_lookup(el, id + 1, strlen(id + 1), cmd_desc, section, false);
260 		}
261 
262 		free(section);
263 	} else {
264 		// Check for item specification.
265 		char *dot = (strchr(str, '.'));
266 		if (dot != NULL) {
267 			// Complete the item name.
268 			char *section = strndup(str, dot - str);
269 			list_lookup(el, section, dot + 1);
270 			free(section);
271 		} else {
272 			// Complete the section name.
273 			list_lookup(el, NULL, str);
274 		}
275 	}
276 }
277 
complete(EditLine * el,int ch)278 static unsigned char complete(EditLine *el, int ch)
279 {
280 	int argc, token, pos;
281 	const char **argv;
282 
283 	const LineInfo *li = el_line(el);
284 	Tokenizer *tok = tok_init(NULL);
285 
286 	// Parse the line.
287 	int ret = tok_line(tok, li, &argc, &argv, &token, &pos);
288 	if (ret != 0) {
289 		goto complete_exit;
290 	}
291 
292 	// Show possible commands.
293 	if (argc == 0) {
294 		print_commands();
295 		goto complete_exit;
296 	}
297 
298 	// Complete the command name.
299 	if (token == 0) {
300 		cmds_lookup(el, argv[0], pos);
301 		goto complete_exit;
302 	}
303 
304 	// Find the command descriptor.
305 	const cmd_desc_t *desc = cmd_table;
306 	while (desc->name != NULL && strcmp(desc->name, argv[0]) != 0) {
307 		desc++;
308 	}
309 	if (desc->name == NULL) {
310 		goto complete_exit;
311 	}
312 
313 	// Finish if a command with no or unsupported arguments.
314 	if (desc->flags == CMD_FNONE || desc->flags == CMD_FREAD ||
315 	    desc->flags == CMD_FWRITE) {
316 		goto complete_exit;
317 	}
318 
319 	ret = set_config(desc, &params);
320 	if (ret != KNOT_EOK) {
321 		goto complete_exit;
322 	}
323 
324 	// Complete the zone name.
325 	if (desc->flags & (CMD_FREQ_ZONE | CMD_FOPT_ZONE)) {
326 		if (token > 1 && !(desc->flags & CMD_FOPT_ZONE)) {
327 			goto complete_exit;
328 		}
329 
330 		if (desc->flags & CMD_FREAD) {
331 			local_zones_lookup(el, argv[token], pos);
332 		} else {
333 			id_lookup(el, argv[token], pos, desc, "zone", true);
334 		}
335 		goto complete_exit;
336 	// Complete the section/id/item name.
337 	} else if (desc->flags & (CMD_FOPT_ITEM | CMD_FREQ_ITEM)) {
338 		if (token == 1) {
339 			item_lookup(el, argv[1], desc);
340 		}
341 		goto complete_exit;
342 	}
343 
344 complete_exit:
345 	conf_update(NULL, CONF_UPD_FNONE);
346 	tok_reset(tok);
347 	tok_end(tok);
348 
349 	return CC_REDISPLAY;
350 }
351 
prompt(EditLine * el)352 static char *prompt(EditLine *el)
353 {
354 	return PROGRAM_NAME"> ";
355 }
356 
interactive_loop(params_t * process_params)357 int interactive_loop(params_t *process_params)
358 {
359 	char *hist_file = NULL;
360 	const char *home = getenv("HOME");
361 	if (home != NULL) {
362 		hist_file = sprintf_alloc("%s/%s", home, HISTORY_FILE);
363 	}
364 	if (hist_file == NULL) {
365 		log_notice("failed to get home directory");
366 	}
367 
368 	EditLine *el = el_init(PROGRAM_NAME, stdin, stdout, stderr);
369 	if (el == NULL) {
370 		log_error("interactive mode not available");
371 		free(hist_file);
372 		return KNOT_ERROR;
373 	}
374 
375 	History *hist = history_init();
376 	if (hist == NULL) {
377 		log_error("interactive mode not available");
378 		el_end(el);
379 		free(hist_file);
380 		return KNOT_ERROR;
381 	}
382 
383 	HistEvent hev = { 0 };
384 	history(hist, &hev, H_SETSIZE, 1000);
385 	history(hist, &hev, H_SETUNIQUE, 1);
386 	el_set(el, EL_HIST, history, hist);
387 	history(hist, &hev, H_LOAD, hist_file);
388 
389 	el_set(el, EL_TERMINAL, NULL);
390 	el_set(el, EL_EDITOR, "emacs");
391 	el_set(el, EL_PROMPT, prompt);
392 	el_set(el, EL_SIGNAL, 1);
393 	el_source(el, NULL);
394 
395 	// Warning: these two el_sets()'s always leak -- in libedit2 library!
396 	// For more details see this commit's message.
397 	el_set(el, EL_ADDFN, PROGRAM_NAME"-complete",
398 	       "Perform "PROGRAM_NAME" completion.", complete);
399 	el_set(el, EL_BIND, "^I",  PROGRAM_NAME"-complete", NULL);
400 
401 	int count;
402 	const char *line;
403 	while ((line = el_gets(el, &count)) != NULL && count > 0) {
404 		Tokenizer *tok = tok_init(NULL);
405 
406 		// Tokenize the current line.
407 		int argc;
408 		const char **argv;
409 		const LineInfo *li = el_line(el);
410 		int ret = tok_line(tok, li, &argc, &argv, NULL, NULL);
411 		if (ret == 0 && argc != 0) {
412 			history(hist, &hev, H_ENTER, line);
413 			history(hist, &hev, H_SAVE, hist_file);
414 
415 			// Process the command.
416 			ret = process_cmd(argc, argv, process_params);
417 		}
418 
419 		tok_reset(tok);
420 		tok_end(tok);
421 
422 		// Check for the exit command.
423 		if (ret == KNOT_CTL_ESTOP) {
424 			break;
425 		}
426 	}
427 
428 	history_end(hist);
429 	free(hist_file);
430 
431 	el_end(el);
432 
433 	return KNOT_EOK;
434 }
435