]>
Commit | Line | Data |
---|---|---|
89c4ed63 A |
1 | /* |
2 | * checkconf/unbound-control.c - remote control utility for unbound. | |
3 | * | |
4 | * Copyright (c) 2008, NLnet Labs. All rights reserved. | |
5 | * | |
6 | * This software is open source. | |
7 | * | |
8 | * Redistribution and use in source and binary forms, with or without | |
9 | * modification, are permitted provided that the following conditions | |
10 | * are met: | |
11 | * | |
12 | * Redistributions of source code must retain the above copyright notice, | |
13 | * this list of conditions and the following disclaimer. | |
14 | * | |
15 | * Redistributions in binary form must reproduce the above copyright notice, | |
16 | * this list of conditions and the following disclaimer in the documentation | |
17 | * and/or other materials provided with the distribution. | |
18 | * | |
19 | * Neither the name of the NLNET LABS nor the names of its contributors may | |
20 | * be used to endorse or promote products derived from this software without | |
21 | * specific prior written permission. | |
22 | * | |
23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
26 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
27 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
28 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | |
29 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
30 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
31 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
32 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
34 | */ | |
35 | ||
36 | /** | |
37 | * \file | |
38 | * | |
39 | * The remote control utility contacts the unbound server over ssl and | |
40 | * sends the command, receives the answer, and displays the result | |
41 | * from the commandline. | |
42 | */ | |
43 | ||
44 | #include "config.h" | |
45 | #ifdef HAVE_GETOPT_H | |
46 | #include <getopt.h> | |
47 | #endif | |
48 | #ifdef HAVE_OPENSSL_SSL_H | |
49 | #include <openssl/ssl.h> | |
50 | #endif | |
51 | #ifdef HAVE_OPENSSL_ERR_H | |
52 | #include <openssl/err.h> | |
53 | #endif | |
54 | #ifdef HAVE_OPENSSL_RAND_H | |
55 | #include <openssl/rand.h> | |
56 | #endif | |
57 | #include "util/log.h" | |
58 | #include "util/config_file.h" | |
59 | #include "util/locks.h" | |
60 | #include "util/net_help.h" | |
61 | ||
62 | /** Give unbound-control usage, and exit (1). */ | |
63 | static void | |
64 | usage() | |
65 | { | |
66 | printf("Usage: unbound-control [options] command\n"); | |
67 | printf(" Remote control utility for unbound server.\n"); | |
68 | printf("Options:\n"); | |
69 | printf(" -c file config file, default is %s\n", CONFIGFILE); | |
70 | printf(" -s ip[@port] server address, if omitted config is used.\n"); | |
71 | printf(" -q quiet (don't print anything if it works ok).\n"); | |
72 | printf(" -h show this usage help.\n"); | |
73 | printf("Commands:\n"); | |
74 | printf(" start start server; runs unbound(8)\n"); | |
75 | printf(" stop stops the server\n"); | |
76 | printf(" reload reloads the server\n"); | |
77 | printf(" (this flushes data, stats, requestlist)\n"); | |
78 | printf(" stats print statistics\n"); | |
79 | printf(" stats_noreset peek at statistics\n"); | |
80 | printf(" status display status of server\n"); | |
81 | printf(" verbosity <number> change logging detail\n"); | |
82 | printf(" log_reopen close and open the logfile\n"); | |
83 | printf(" local_zone <name> <type> add new local zone\n"); | |
84 | printf(" local_zone_remove <name> remove local zone and its contents\n"); | |
85 | printf(" local_data <RR data...> add local data, for example\n"); | |
86 | printf(" local_data www.example.com A 192.0.2.1\n"); | |
87 | printf(" local_data_remove <name> remove local RR data from name\n"); | |
88 | printf(" dump_cache print cache to stdout\n"); | |
89 | printf(" load_cache load cache from stdin\n"); | |
90 | printf(" lookup <name> print nameservers for name\n"); | |
91 | printf(" flush <name> flushes common types for name from cache\n"); | |
92 | printf(" types: A, AAAA, MX, PTR, NS,\n"); | |
93 | printf(" SOA, CNAME, DNAME, SRV, NAPTR\n"); | |
94 | printf(" flush_type <name> <type> flush name, type from cache\n"); | |
95 | printf(" flush_zone <name> flush everything at or under name\n"); | |
96 | printf(" from rr and dnssec caches\n"); | |
97 | printf(" flush_bogus flush all bogus data\n"); | |
98 | printf(" flush_negative flush all negative data\n"); | |
99 | printf(" flush_stats flush statistics, make zero\n"); | |
100 | printf(" flush_requestlist drop queries that are worked on\n"); | |
101 | printf(" dump_requestlist show what is worked on\n"); | |
102 | printf(" flush_infra [all | ip] remove ping, edns for one IP or all\n"); | |
103 | printf(" dump_infra show ping and edns entries\n"); | |
104 | printf(" set_option opt: val set option to value, no reload\n"); | |
105 | printf(" get_option opt get option value\n"); | |
106 | printf(" list_stubs list stub-zones and root hints in use\n"); | |
107 | printf(" list_forwards list forward-zones in use\n"); | |
108 | printf(" list_local_zones list local-zones in use\n"); | |
109 | printf(" list_local_data list local-data RRs in use\n"); | |
110 | printf(" insecure_add zone add domain-insecure zone\n"); | |
111 | printf(" insecure_remove zone remove domain-insecure zone\n"); | |
112 | printf(" forward_add [+i] zone addr.. add forward-zone with servers\n"); | |
113 | printf(" forward_remove [+i] zone remove forward zone\n"); | |
114 | printf(" stub_add [+ip] zone addr.. add stub-zone with servers\n"); | |
115 | printf(" stub_remove [+i] zone remove stub zone\n"); | |
116 | printf(" +i also do dnssec insecure point\n"); | |
117 | printf(" +p set stub to use priming\n"); | |
118 | printf(" forward [off | addr ...] without arg show forward setup\n"); | |
119 | printf(" or off to turn off root forwarding\n"); | |
120 | printf(" or give list of ip addresses\n"); | |
121 | printf("Version %s\n", PACKAGE_VERSION); | |
122 | printf("BSD licensed, see LICENSE in source package for details.\n"); | |
123 | printf("Report bugs to %s\n", PACKAGE_BUGREPORT); | |
124 | exit(1); | |
125 | } | |
126 | ||
127 | /** exit with ssl error */ | |
128 | static void ssl_err(const char* s) | |
129 | { | |
130 | fprintf(stderr, "error: %s\n", s); | |
131 | ERR_print_errors_fp(stderr); | |
132 | exit(1); | |
133 | } | |
134 | ||
135 | /** setup SSL context */ | |
136 | static SSL_CTX* | |
137 | setup_ctx(struct config_file* cfg) | |
138 | { | |
139 | char* s_cert, *c_key, *c_cert; | |
140 | SSL_CTX* ctx; | |
141 | ||
142 | s_cert = fname_after_chroot(cfg->server_cert_file, cfg, 1); | |
143 | c_key = fname_after_chroot(cfg->control_key_file, cfg, 1); | |
144 | c_cert = fname_after_chroot(cfg->control_cert_file, cfg, 1); | |
145 | if(!s_cert || !c_key || !c_cert) | |
146 | fatal_exit("out of memory"); | |
147 | ctx = SSL_CTX_new(SSLv23_client_method()); | |
148 | if(!ctx) | |
149 | ssl_err("could not allocate SSL_CTX pointer"); | |
89c4ed63 A |
150 | if(!SSL_CTX_use_certificate_file(ctx,c_cert,SSL_FILETYPE_PEM) || |
151 | !SSL_CTX_use_PrivateKey_file(ctx,c_key,SSL_FILETYPE_PEM) | |
152 | || !SSL_CTX_check_private_key(ctx)) | |
153 | ssl_err("Error setting up SSL_CTX client key and cert"); | |
154 | if (SSL_CTX_load_verify_locations(ctx, s_cert, NULL) != 1) | |
155 | ssl_err("Error setting up SSL_CTX verify, server cert"); | |
156 | SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); | |
157 | ||
158 | free(s_cert); | |
159 | free(c_key); | |
160 | free(c_cert); | |
161 | return ctx; | |
162 | } | |
163 | ||
164 | /** contact the server with TCP connect */ | |
165 | static int | |
166 | contact_server(const char* svr, struct config_file* cfg, int statuscmd) | |
167 | { | |
168 | struct sockaddr_storage addr; | |
169 | socklen_t addrlen; | |
170 | int fd; | |
171 | /* use svr or the first config entry */ | |
172 | if(!svr) { | |
173 | if(cfg->control_ifs) | |
174 | svr = cfg->control_ifs->str; | |
175 | else svr = "127.0.0.1"; | |
176 | /* config 0 addr (everything), means ask localhost */ | |
177 | if(strcmp(svr, "0.0.0.0") == 0) | |
178 | svr = "127.0.0.1"; | |
179 | else if(strcmp(svr, "::0") == 0 || | |
180 | strcmp(svr, "0::0") == 0 || | |
181 | strcmp(svr, "0::") == 0 || | |
182 | strcmp(svr, "::") == 0) | |
183 | svr = "::1"; | |
184 | } | |
185 | if(strchr(svr, '@')) { | |
186 | if(!extstrtoaddr(svr, &addr, &addrlen)) | |
187 | fatal_exit("could not parse IP@port: %s", svr); | |
188 | } else { | |
189 | if(!ipstrtoaddr(svr, cfg->control_port, &addr, &addrlen)) | |
190 | fatal_exit("could not parse IP: %s", svr); | |
191 | } | |
192 | fd = socket(addr_is_ip6(&addr, addrlen)?AF_INET6:AF_INET, | |
193 | SOCK_STREAM, 0); | |
194 | if(fd == -1) { | |
195 | #ifndef USE_WINSOCK | |
196 | fatal_exit("socket: %s", strerror(errno)); | |
197 | #else | |
198 | fatal_exit("socket: %s", wsa_strerror(WSAGetLastError())); | |
199 | #endif | |
200 | } | |
201 | if(connect(fd, (struct sockaddr*)&addr, addrlen) < 0) { | |
202 | #ifndef USE_WINSOCK | |
203 | log_err_addr("connect", strerror(errno), &addr, addrlen); | |
204 | if(errno == ECONNREFUSED && statuscmd) { | |
205 | printf("unbound is stopped\n"); | |
206 | exit(3); | |
207 | } | |
208 | #else | |
209 | log_err_addr("connect", wsa_strerror(WSAGetLastError()), &addr, addrlen); | |
210 | if(WSAGetLastError() == WSAECONNREFUSED && statuscmd) { | |
211 | printf("unbound is stopped\n"); | |
212 | exit(3); | |
213 | } | |
214 | #endif | |
215 | exit(1); | |
216 | } | |
217 | return fd; | |
218 | } | |
219 | ||
220 | /** setup SSL on the connection */ | |
221 | static SSL* | |
222 | setup_ssl(SSL_CTX* ctx, int fd) | |
223 | { | |
224 | SSL* ssl; | |
225 | X509* x; | |
226 | int r; | |
227 | ||
228 | ssl = SSL_new(ctx); | |
229 | if(!ssl) | |
230 | ssl_err("could not SSL_new"); | |
231 | SSL_set_connect_state(ssl); | |
232 | (void)SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); | |
233 | if(!SSL_set_fd(ssl, fd)) | |
234 | ssl_err("could not SSL_set_fd"); | |
235 | while(1) { | |
236 | ERR_clear_error(); | |
237 | if( (r=SSL_do_handshake(ssl)) == 1) | |
238 | break; | |
239 | r = SSL_get_error(ssl, r); | |
240 | if(r != SSL_ERROR_WANT_READ && r != SSL_ERROR_WANT_WRITE) | |
241 | ssl_err("SSL handshake failed"); | |
242 | /* wants to be called again */ | |
243 | } | |
244 | ||
245 | /* check authenticity of server */ | |
246 | if(SSL_get_verify_result(ssl) != X509_V_OK) | |
247 | ssl_err("SSL verification failed"); | |
248 | x = SSL_get_peer_certificate(ssl); | |
249 | if(!x) | |
250 | ssl_err("Server presented no peer certificate"); | |
251 | X509_free(x); | |
252 | return ssl; | |
253 | } | |
254 | ||
255 | /** send stdin to server */ | |
256 | static void | |
257 | send_file(SSL* ssl, FILE* in, char* buf, size_t sz) | |
258 | { | |
259 | while(fgets(buf, (int)sz, in)) { | |
260 | if(SSL_write(ssl, buf, (int)strlen(buf)) <= 0) | |
261 | ssl_err("could not SSL_write contents"); | |
262 | } | |
263 | } | |
264 | ||
265 | /** send command and display result */ | |
266 | static int | |
267 | go_cmd(SSL* ssl, int quiet, int argc, char* argv[]) | |
268 | { | |
269 | char pre[10]; | |
270 | const char* space=" "; | |
271 | const char* newline="\n"; | |
272 | int was_error = 0, first_line = 1; | |
273 | int r, i; | |
274 | char buf[1024]; | |
275 | snprintf(pre, sizeof(pre), "UBCT%d ", UNBOUND_CONTROL_VERSION); | |
276 | if(SSL_write(ssl, pre, (int)strlen(pre)) <= 0) | |
277 | ssl_err("could not SSL_write"); | |
278 | for(i=0; i<argc; i++) { | |
279 | if(SSL_write(ssl, space, (int)strlen(space)) <= 0) | |
280 | ssl_err("could not SSL_write"); | |
281 | if(SSL_write(ssl, argv[i], (int)strlen(argv[i])) <= 0) | |
282 | ssl_err("could not SSL_write"); | |
283 | } | |
284 | if(SSL_write(ssl, newline, (int)strlen(newline)) <= 0) | |
285 | ssl_err("could not SSL_write"); | |
286 | ||
287 | if(argc == 1 && strcmp(argv[0], "load_cache") == 0) { | |
288 | send_file(ssl, stdin, buf, sizeof(buf)); | |
289 | } | |
290 | ||
291 | while(1) { | |
292 | ERR_clear_error(); | |
293 | if((r = SSL_read(ssl, buf, (int)sizeof(buf)-1)) <= 0) { | |
294 | if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) { | |
295 | /* EOF */ | |
296 | break; | |
297 | } | |
298 | ssl_err("could not SSL_read"); | |
299 | } | |
300 | buf[r] = 0; | |
301 | if(first_line && strncmp(buf, "error", 5) == 0) { | |
302 | printf("%s", buf); | |
303 | was_error = 1; | |
304 | } else if (!quiet) | |
305 | printf("%s", buf); | |
306 | ||
307 | first_line = 0; | |
308 | } | |
309 | return was_error; | |
310 | } | |
311 | ||
312 | /** go ahead and read config, contact server and perform command and display */ | |
313 | static int | |
314 | go(const char* cfgfile, char* svr, int quiet, int argc, char* argv[]) | |
315 | { | |
316 | struct config_file* cfg; | |
317 | int fd, ret; | |
318 | SSL_CTX* ctx; | |
319 | SSL* ssl; | |
320 | ||
321 | /* read config */ | |
322 | if(!(cfg = config_create())) | |
323 | fatal_exit("out of memory"); | |
324 | if(!config_read(cfg, cfgfile, NULL)) | |
325 | fatal_exit("could not read config file"); | |
326 | if(!cfg->remote_control_enable) | |
327 | log_warn("control-enable is 'no' in the config file."); | |
328 | ctx = setup_ctx(cfg); | |
329 | ||
330 | /* contact server */ | |
331 | fd = contact_server(svr, cfg, argc>0&&strcmp(argv[0],"status")==0); | |
332 | ssl = setup_ssl(ctx, fd); | |
333 | ||
334 | /* send command */ | |
335 | ret = go_cmd(ssl, quiet, argc, argv); | |
336 | ||
337 | SSL_free(ssl); | |
338 | #ifndef USE_WINSOCK | |
339 | close(fd); | |
340 | #else | |
341 | closesocket(fd); | |
342 | #endif | |
343 | SSL_CTX_free(ctx); | |
344 | config_delete(cfg); | |
345 | return ret; | |
346 | } | |
347 | ||
348 | /** getopt global, in case header files fail to declare it. */ | |
349 | extern int optind; | |
350 | /** getopt global, in case header files fail to declare it. */ | |
351 | extern char* optarg; | |
352 | ||
353 | /** Main routine for unbound-control */ | |
354 | int main(int argc, char* argv[]) | |
355 | { | |
356 | int c, ret; | |
357 | int quiet = 0; | |
358 | const char* cfgfile = CONFIGFILE; | |
359 | char* svr = NULL; | |
360 | #ifdef USE_WINSOCK | |
361 | int r; | |
362 | WSADATA wsa_data; | |
363 | #endif | |
364 | #ifdef USE_THREAD_DEBUG | |
365 | /* stop the file output from unbound-control, overwites the servers */ | |
366 | extern int check_locking_order; | |
367 | check_locking_order = 0; | |
368 | #endif /* USE_THREAD_DEBUG */ | |
369 | log_ident_set("unbound-control"); | |
370 | log_init(NULL, 0, NULL); | |
371 | checklock_start(); | |
372 | #ifdef USE_WINSOCK | |
373 | if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0) | |
374 | fatal_exit("WSAStartup failed: %s", wsa_strerror(r)); | |
375 | /* use registry config file in preference to compiletime location */ | |
376 | if(!(cfgfile=w_lookup_reg_str("Software\\Unbound", "ConfigFile"))) | |
377 | cfgfile = CONFIGFILE; | |
378 | #endif | |
379 | ||
380 | ERR_load_crypto_strings(); | |
381 | ERR_load_SSL_strings(); | |
382 | OpenSSL_add_all_algorithms(); | |
383 | (void)SSL_library_init(); | |
384 | ||
385 | if(!RAND_status()) { | |
386 | /* try to seed it */ | |
387 | unsigned char buf[256]; | |
388 | unsigned int seed=(unsigned)time(NULL) ^ (unsigned)getpid(); | |
389 | unsigned int v = seed; | |
390 | size_t i; | |
391 | for(i=0; i<256/sizeof(v); i++) { | |
392 | memmove(buf+i*sizeof(v), &v, sizeof(v)); | |
393 | v = v*seed + (unsigned int)i; | |
394 | } | |
395 | RAND_seed(buf, 256); | |
396 | log_warn("no entropy, seeding openssl PRNG with time\n"); | |
397 | } | |
398 | ||
399 | /* parse the options */ | |
400 | while( (c=getopt(argc, argv, "c:s:qh")) != -1) { | |
401 | switch(c) { | |
402 | case 'c': | |
403 | cfgfile = optarg; | |
404 | break; | |
405 | case 's': | |
406 | svr = optarg; | |
407 | break; | |
408 | case 'q': | |
409 | quiet = 1; | |
410 | break; | |
411 | case '?': | |
412 | case 'h': | |
413 | default: | |
414 | usage(); | |
415 | } | |
416 | } | |
417 | argc -= optind; | |
418 | argv += optind; | |
419 | if(argc == 0) | |
420 | usage(); | |
421 | if(argc >= 1 && strcmp(argv[0], "start")==0) { | |
422 | if(execlp("unbound", "unbound", "-c", cfgfile, | |
423 | (char*)NULL) < 0) { | |
424 | fatal_exit("could not exec unbound: %s", | |
425 | strerror(errno)); | |
426 | } | |
427 | } | |
428 | ||
429 | ret = go(cfgfile, svr, quiet, argc, argv); | |
430 | ||
431 | #ifdef USE_WINSOCK | |
432 | WSACleanup(); | |
433 | #endif | |
434 | checklock_stop(); | |
435 | return ret; | |
436 | } |