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