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 <stddef.h>
18 #include <sys/stat.h>
19 
20 #include "contrib/openbsd/strlcat.h"
21 #include "knot/conf/conf.h"
22 #include "knot/common/log.h"
23 #include "utils/knotc/commands.h"
24 #include "utils/knotc/process.h"
25 
get_cmd_desc(const char * command)26 static const cmd_desc_t *get_cmd_desc(const char *command)
27 {
28 	/* Find requested command. */
29 	const cmd_desc_t *desc = cmd_table;
30 	while (desc->name != NULL) {
31 		if (strcmp(desc->name, command) == 0) {
32 			break;
33 		}
34 		desc++;
35 	}
36 	if (desc->name == NULL) {
37 		log_error("invalid command '%s'", command);
38 		return NULL;
39 	}
40 
41 	return desc;
42 }
43 
get_cmd_force_flag(const char * arg)44 static bool get_cmd_force_flag(const char *arg)
45 {
46 	if (strcmp(arg, "-f") == 0 || strcmp(arg, "--force") == 0) {
47 		return true;
48 	}
49 	return false;
50 }
51 
get_cmd_blocking_flag(const char * arg)52 static bool get_cmd_blocking_flag(const char *arg)
53 {
54 	if (strcmp(arg, "-b") == 0 || strcmp(arg, "--blocking") == 0) {
55 		return true;
56 	}
57 	return false;
58 }
59 
set_config(const cmd_desc_t * desc,params_t * params)60 int set_config(const cmd_desc_t *desc, params_t *params)
61 {
62 	if (params->config != NULL && params->confdb != NULL) {
63 		log_error("ambiguous configuration source");
64 		return KNOT_EINVAL;
65 	}
66 
67 	/* Mask relevant flags. */
68 	cmd_flag_t flags = desc->flags & (CMD_FREAD | CMD_FWRITE);
69 	cmd_flag_t mod_flags = desc->flags & (CMD_FOPT_MOD | CMD_FREQ_MOD);
70 
71 	/* Choose the optimal config source. */
72 	struct stat st;
73 	bool import = false;
74 	if (flags == CMD_FNONE && params->socket != NULL) {
75 		/* Control operation, known socket, skip configuration. */
76 		return KNOT_EOK;
77 	} else if (params->confdb != NULL) {
78 		import = false;
79 	} else if (flags == CMD_FWRITE) {
80 		import = false;
81 		params->confdb = CONF_DEFAULT_DBDIR;
82 	} else if (params->config != NULL){
83 		import = true;
84 	} else if (conf_db_exists(CONF_DEFAULT_DBDIR)) {
85 		import = false;
86 		params->confdb = CONF_DEFAULT_DBDIR;
87 	} else if (stat(CONF_DEFAULT_FILE, &st) == 0) {
88 		import = true;
89 		params->config = CONF_DEFAULT_FILE;
90 	} else if (flags != CMD_FNONE) {
91 		log_error("no configuration source available");
92 		return KNOT_EINVAL;
93 	}
94 
95 	const char *src = import ? params->config : params->confdb;
96 	log_debug("%s '%s'", import ? "config" : "confdb",
97 	          (src != NULL) ? src : "empty");
98 
99 	/* Prepare config flags. */
100 	conf_flag_t conf_flags = CONF_FNOHOSTNAME;
101 	if (params->confdb != NULL && !(flags & CMD_FWRITE)) {
102 		conf_flags |= CONF_FREADONLY;
103 	}
104 	if (import || mod_flags & CMD_FOPT_MOD) {
105 		conf_flags |= CONF_FOPTMODULES;
106 	} else if (mod_flags & CMD_FREQ_MOD) {
107 		conf_flags |= CONF_FREQMODULES;
108 	}
109 
110 	/* Open confdb. */
111 	conf_t *new_conf = NULL;
112 	int ret = conf_new(&new_conf, conf_schema, params->confdb,
113 	                   params->max_conf_size, conf_flags);
114 	if (ret != KNOT_EOK) {
115 		log_error("failed to open configuration database '%s' (%s)",
116 		          (params->confdb != NULL) ? params->confdb : "",
117 		          knot_strerror(ret));
118 		return ret;
119 	}
120 
121 	/* Import the config file. */
122 	if (import) {
123 		ret = conf_import(new_conf, params->config, true, false);
124 		if (ret != KNOT_EOK) {
125 			log_error("failed to load configuration file '%s' (%s)",
126 			          params->config, knot_strerror(ret));
127 			conf_free(new_conf);
128 			return ret;
129 		}
130 	}
131 
132 	/* Update to the new config. */
133 	conf_update(new_conf, CONF_UPD_FNONE);
134 
135 	return KNOT_EOK;
136 }
137 
set_ctl(knot_ctl_t ** ctl,const char * socket,int timeout,const cmd_desc_t * desc)138 int set_ctl(knot_ctl_t **ctl, const char *socket, int timeout, const cmd_desc_t *desc)
139 {
140 	if (desc == NULL) {
141 		*ctl = NULL;
142 		return KNOT_EINVAL;
143 	}
144 
145 	/* Mask relevant flags. */
146 	cmd_flag_t flags = desc->flags & (CMD_FREAD | CMD_FWRITE);
147 
148 	/* Check if control socket is required. */
149 	if (flags != CMD_FNONE) {
150 		*ctl = NULL;
151 		return KNOT_EOK;
152 	}
153 
154 	/* Get control socket path. */
155 	char *path = NULL;
156 	if (socket != NULL) {
157 		path = strdup(socket);
158 	} else {
159 		conf_val_t listen_val = conf_get(conf(), C_CTL, C_LISTEN);
160 		conf_val_t rundir_val = conf_get(conf(), C_SRV, C_RUNDIR);
161 		char *rundir = conf_abs_path(&rundir_val, NULL);
162 		path = conf_abs_path(&listen_val, rundir);
163 		free(rundir);
164 	}
165 	if (path == NULL) {
166 		log_error("empty control socket path");
167 		return KNOT_EINVAL;
168 	}
169 
170 	log_debug("socket '%s'", path);
171 
172 	*ctl = knot_ctl_alloc();
173 	if (*ctl == NULL) {
174 		free(path);
175 		return KNOT_ENOMEM;
176 	}
177 
178 	knot_ctl_set_timeout(*ctl, timeout);
179 
180 	int ret = knot_ctl_connect(*ctl, path);
181 	if (ret != KNOT_EOK) {
182 		knot_ctl_free(*ctl);
183 		*ctl = NULL;
184 		log_error("failed to connect to socket '%s' (%s)", path,
185 		          knot_strerror(ret));
186 		free(path);
187 		return ret;
188 	}
189 
190 	free(path);
191 
192 	return KNOT_EOK;
193 }
194 
unset_ctl(knot_ctl_t * ctl)195 void unset_ctl(knot_ctl_t *ctl)
196 {
197 	if (ctl == NULL) {
198 		return;
199 	}
200 
201 	int ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL);
202 	if (ret != KNOT_EOK && ret != KNOT_ECONN) {
203 		log_error("failed to finish control (%s)", knot_strerror(ret));
204 	}
205 
206 	knot_ctl_close(ctl);
207 	knot_ctl_free(ctl);
208 }
209 
process_cmd(int argc,const char ** argv,params_t * params)210 int process_cmd(int argc, const char **argv, params_t *params)
211 {
212 	if (argc == 0) {
213 		return KNOT_ENOTSUP;
214 	}
215 
216 	/* Check the command name. */
217 	const cmd_desc_t *desc = get_cmd_desc(argv[0]);
218 	if (desc == NULL) {
219 		return KNOT_ENOENT;
220 	}
221 
222 	/* Check for exit. */
223 	if (desc->fcn == NULL) {
224 		return KNOT_CTL_ESTOP;
225 	}
226 
227 	/* Set up the configuration. */
228 	int ret = set_config(desc, params);
229 	if (ret != KNOT_EOK) {
230 		return ret;
231 	}
232 
233 	/* Prepare command parameters. */
234 	cmd_args_t args = {
235 		.desc = desc,
236 		.argc = argc - 1,
237 		.argv = argv + 1,
238 		.force = params->force,
239 		.blocking = params->blocking
240 	};
241 
242 	/* Check for special flags after command. */
243 	while (args.argc > 0) {
244 		if (get_cmd_force_flag(args.argv[0])) {
245 			args.force = true;
246 			args.argc--;
247 			args.argv++;
248 		} else if (get_cmd_blocking_flag(args.argv[0])) {
249 			args.blocking = true;
250 			args.argc--;
251 			args.argv++;
252 		} else {
253 			break;
254 		}
255 	}
256 
257 	/* Prepare flags parameter. */
258 	if (args.force) {
259 		strlcat(args.flags, CTL_FLAG_FORCE, sizeof(args.flags));
260 	}
261 	if (args.blocking) {
262 		strlcat(args.flags, CTL_FLAG_BLOCKING, sizeof(args.flags));
263 	}
264 
265 	/* Set control interface if necessary. */
266 	int cmd_timeout = params->timeout != -1 ? params->timeout : DEFAULT_CTL_TIMEOUT_MS;
267 	if (args.blocking && params->timeout == -1) {
268 		cmd_timeout = 0;
269 	}
270 	ret = set_ctl(&args.ctl, params->socket, cmd_timeout, desc);
271 	if (ret != KNOT_EOK) {
272 		conf_update(NULL, CONF_UPD_FNONE);
273 		return ret;
274 	}
275 
276 	/* Execute the command. */
277 	ret = desc->fcn(&args);
278 
279 	/* Cleanup */
280 	unset_ctl(args.ctl);
281 	conf_update(NULL, CONF_UPD_FNONE);
282 
283 	return ret;
284 }
285