From af57e5163dc02289cb73f69e49a11ce3a749357c Mon Sep 17 00:00:00 2001 From: Wouter Wijngaards Date: Thu, 11 Sep 2008 14:14:12 +0000 Subject: [PATCH] control channel security. git-svn-id: file:///svn/unbound/trunk@1229 be551aaa-1e26-0410-a405-d3ace91eadb9 --- Makefile.in | 19 ++- configure | 3 +- configure.ac | 2 +- daemon/daemon.c | 3 + daemon/remote.c | 167 +++++++++++++++++++- daemon/remote.h | 5 + doc/Changelog | 6 + doc/example.conf.in | 4 +- doc/unbound-control.8.in | 81 ++++++++++ doc/unbound.conf.5.in | 46 ++++++ smallapp/unbound-control.c | 310 +++++++++++++++++++++++++++++++++++++ util/netevent.c | 27 +++- util/netevent.h | 8 + 13 files changed, 664 insertions(+), 17 deletions(-) create mode 100644 doc/unbound-control.8.in create mode 100644 smallapp/unbound-control.c diff --git a/Makefile.in b/Makefile.in index 0ad4c8915..4403b309a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -76,6 +76,8 @@ DAEMON_SRC=$(patsubst $(srcdir)/%,%, $(wildcard $(srcdir)/daemon/*.c)) \ DAEMON_OBJ=$(addprefix $(BUILD),$(DAEMON_SRC:.c=.lo)) $(COMPAT_OBJ) CHECKCONF_SRC=smallapp/unbound-checkconf.c smallapp/worker_cb.c $(COMMON_SRC) CHECKCONF_OBJ=$(addprefix $(BUILD),$(CHECKCONF_SRC:.c=.lo)) $(COMPAT_OBJ) +CONTROL_SRC=smallapp/unbound-control.c smallapp/worker_cb.c $(COMMON_SRC) +CONTROL_OBJ=$(addprefix $(BUILD),$(CONTROL_SRC:.c=.lo)) $(COMPAT_OBJ) HOST_SRC=smallapp/unbound-host.c HOST_OBJ=$(addprefix $(BUILD),$(HOST_SRC:.c=.lo)) $(COMPAT_OBJ) TESTBOUND_SRC=testcode/testbound.c testcode/ldns-testpkts.c \ @@ -109,7 +111,7 @@ ALL_SRC=$(sort $(COMMON_SRC) $(UNITTEST_SRC) $(DAEMON_SRC) \ $(TESTBOUND_SRC) $(LOCKVERIFY_SRC) $(PKTVIEW_SRC) $(SIGNIT_SRC) \ $(MEMSTATS_SRC) $(CHECKCONF_SRC) $(LIBUNBOUND_SRC) $(HOST_SRC) \ $(ASYNCLOOK_SRC) $(STREAMTCP_SRC) $(PERF_SRC) $(DELAYER_SRC) \ - $(HARVEST_SRC) ) + $(HARVEST_SRC) $(CONTROL_SRC)) ALL_OBJ=$(addprefix $(BUILD),$(ALL_SRC:.c=.lo) \ $(addprefix compat/,$(LIBOBJS:.o=.lo))) $(COMPAT_OBJ) @@ -126,7 +128,7 @@ $(BUILD)%.lo: $(srcdir)/%.c @-if test ! -d $(dir $@); then $(INSTALL) -d $(patsubst %/,%,$(dir $@)); fi $Q$(COMPILE) -c $< -o $@ -all: $(COMMON_OBJ) unbound unbound-checkconf lib unbound-host +all: $(COMMON_OBJ) unbound unbound-checkconf lib unbound-host unbound-control TEST_BIN=asynclook delayer harvest lock-verify memstats perf pktview signit \ streamtcp testbound unittest @@ -158,6 +160,10 @@ unbound-checkconf: $(CHECKCONF_OBJ) $(ldnslib) $(INFO) Link $@ $Q$(LINK) -o $@ $(sort $(CHECKCONF_OBJ)) $(LIBS) +unbound-control: $(CONTROL_OBJ) $(ldnslib) + $(INFO) Link $@ + $Q$(LINK) -o $@ $(sort $(CONTROL_OBJ)) -lssl $(LIBS) + unbound-host: $(HOST_OBJ) libunbound.la $(ldnslib) $(INFO) Link $@ $Q$(LINK) -o $@ $(sort $(HOST_OBJ)) -L. -L.libs -lunbound $(LIBS) @@ -168,7 +174,7 @@ unittest: $(UNITTEST_OBJ) $(ldnslib) testbound: $(TESTBOUND_OBJ) $(ldnslib) $(INFO) Link $@ - $Q$(LINK) -o $@ $(sort $(TESTBOUND_OBJ)) $(LIBS) + $Q$(LINK) -o $@ $(sort $(TESTBOUND_OBJ)) -lssl $(LIBS) lock-verify: $(LOCKVERIFY_OBJ) $(ldnslib) $(INFO) Link $@ @@ -258,6 +264,7 @@ doc: strip: strip unbound strip unbound-checkconf + strip unbound-control strip unbound-host install: @@ -271,9 +278,11 @@ install: $(INSTALL) -m 755 -d $(DESTDIR)$(includedir) $(LIBTOOL) --mode=install cp unbound $(DESTDIR)$(sbindir)/unbound $(LIBTOOL) --mode=install cp unbound-checkconf $(DESTDIR)$(sbindir)/unbound-checkconf + $(LIBTOOL) --mode=install cp unbound-control $(DESTDIR)$(sbindir)/unbound-control $(LIBTOOL) --mode=install cp unbound-host $(DESTDIR)$(sbindir)/unbound-host $(INSTALL) -c -m 644 doc/unbound.8 $(DESTDIR)$(mandir)/man8 $(INSTALL) -c -m 644 doc/unbound-checkconf.8 $(DESTDIR)$(mandir)/man8 + $(INSTALL) -c -m 644 doc/unbound-control.8 $(DESTDIR)$(mandir)/man8 $(INSTALL) -c -m 644 doc/unbound.conf.5 $(DESTDIR)$(mandir)/man5 $(INSTALL) -c -m 644 $(srcdir)/doc/unbound-host.1 $(DESTDIR)$(mandir)/man1 $(INSTALL) -c -m 644 doc/libunbound.3 $(DESTDIR)$(mandir)/man3 @@ -283,8 +292,8 @@ install: $(LIBTOOL) --mode=finish $(DESTDIR)$(libdir) uninstall: - rm -f -- $(DESTDIR)$(sbindir)/unbound $(DESTDIR)$(sbindir)/unbound-checkconf $(DESTDIR)$(sbindir)/unbound-host - rm -f -- $(DESTDIR)$(mandir)/man8/unbound.8 $(DESTDIR)$(mandir)/man8/unbound-checkconf.8 $(DESTDIR)$(mandir)/man5/unbound.conf.5 + rm -f -- $(DESTDIR)$(sbindir)/unbound $(DESTDIR)$(sbindir)/unbound-checkconf $(DESTDIR)$(sbindir)/unbound-host $(DESTDIR)$(sbindir)/unbound-control + rm -f -- $(DESTDIR)$(mandir)/man8/unbound.8 $(DESTDIR)$(mandir)/man8/unbound-checkconf.8 $(DESTDIR)$(mandir)/man5/unbound.conf.5 $(DESTDIR)$(mandir)/man8/unbound-control.8 rm -f -- $(DESTDIR)$(mandir)/man1/unbound-host.1 $(DESTDIR)$(mandir)/man3/libunbound.3 rm -f -- $(DESTDIR)$(includedir)/unbound.h $(LIBTOOL) --mode=uninstall rm -f $(DESTDIR)$(libdir)/libunbound.la diff --git a/configure b/configure index 0eccdb368..659b4d7a7 100755 --- a/configure +++ b/configure @@ -25788,7 +25788,7 @@ _ACEOF -ac_config_files="$ac_config_files Makefile doc/example.conf doc/libunbound.3 doc/unbound.8 doc/unbound-checkconf.8 doc/unbound.conf.5" +ac_config_files="$ac_config_files Makefile doc/example.conf doc/libunbound.3 doc/unbound.8 doc/unbound-checkconf.8 doc/unbound.conf.5 doc/unbound-control.8" ac_config_headers="$ac_config_headers config.h" @@ -26350,6 +26350,7 @@ do "doc/unbound.8") CONFIG_FILES="$CONFIG_FILES doc/unbound.8" ;; "doc/unbound-checkconf.8") CONFIG_FILES="$CONFIG_FILES doc/unbound-checkconf.8" ;; "doc/unbound.conf.5") CONFIG_FILES="$CONFIG_FILES doc/unbound.conf.5" ;; + "doc/unbound-control.8") CONFIG_FILES="$CONFIG_FILES doc/unbound-control.8" ;; "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 diff --git a/configure.ac b/configure.ac index 759b426c5..cb1f2d683 100644 --- a/configure.ac +++ b/configure.ac @@ -1056,6 +1056,6 @@ void *unbound_stat_realloc_log(void *ptr, size_t size, const char* file, #define UNBOUND_DNS_PORT 53 ]) -AC_CONFIG_FILES([Makefile doc/example.conf doc/libunbound.3 doc/unbound.8 doc/unbound-checkconf.8 doc/unbound.conf.5]) +AC_CONFIG_FILES([Makefile doc/example.conf doc/libunbound.3 doc/unbound.8 doc/unbound-checkconf.8 doc/unbound.conf.5 doc/unbound-control.8]) AC_CONFIG_HEADER([config.h]) AC_OUTPUT diff --git a/daemon/daemon.c b/daemon/daemon.c index 6da7353af..b98b72b55 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -164,6 +164,9 @@ daemon_init() signal_handling_record(); checklock_start(); ERR_load_crypto_strings(); + ERR_load_SSL_strings(); + OpenSSL_add_all_algorithms(); + (void)SSL_library_init(); #ifdef HAVE_TZSET /* init timezone info while we are not chrooted yet */ tzset(); diff --git a/daemon/remote.c b/daemon/remote.c index 565beb80a..c19c18147 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -45,6 +45,7 @@ #include "config.h" #include "daemon/remote.h" #include "daemon/worker.h" +#include "daemon/daemon.h" #include "util/log.h" #include "util/config_file.h" #include "util/net_help.h" @@ -57,9 +58,27 @@ #include #endif +/** log ssl crypto err */ +static void +log_crypto_err(const char* str) +{ + /* error:[error code]:[library name]:[function name]:[reason string] */ + char buf[128]; + int e; + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + log_err("%s crypto %s", str, buf); + while( (e=ERR_get_error()) ) { + ERR_error_string_n(e, buf, sizeof(buf)); + log_err("and additionally crypto %s", buf); + } +} + struct daemon_remote* daemon_remote_create(struct worker* worker) { + char* s_cert; + char* s_key; + struct config_file* cfg = worker->daemon->cfg; struct daemon_remote* rc = (struct daemon_remote*)calloc(1, sizeof(*rc)); if(!rc) { @@ -68,7 +87,43 @@ daemon_remote_create(struct worker* worker) } rc->worker = worker; rc->max_active = 10; - /* TODO setup the context */ + + rc->ctx = SSL_CTX_new(SSLv23_server_method()); + if(!rc->ctx) { + log_crypto_err("could not SSL_CTX_new"); + free(rc); + return NULL; + } + /* no SSLv2 because has defects */ + if(!(SSL_CTX_set_options(rc->ctx, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2)){ + log_crypto_err("could not set SSL_OP_NO_SSLv2"); + daemon_remote_delete(rc); + return NULL; + } + s_cert = cfg->server_cert_file; + s_key = cfg->server_key_file; + if(cfg->chrootdir && cfg->chrootdir[0]) { + if(strncmp(s_cert, cfg->chrootdir, strlen(cfg->chrootdir))==0) + s_cert += strlen(cfg->chrootdir); + if(strncmp(s_key, cfg->chrootdir, strlen(cfg->chrootdir))==0) + s_key += strlen(cfg->chrootdir); + } + verbose(VERB_ALGO, "setup SSL certificates"); + if (!SSL_CTX_use_certificate_file(rc->ctx,s_cert,SSL_FILETYPE_PEM) + || !SSL_CTX_use_PrivateKey_file(rc->ctx,s_key,SSL_FILETYPE_PEM) + || !SSL_CTX_check_private_key(rc->ctx)) { + log_crypto_err("Error setting up SSL_CTX key and cert"); + daemon_remote_delete(rc); + return NULL; + } + if(!SSL_CTX_load_verify_locations(rc->ctx, s_cert, NULL)) { + log_crypto_err("Error setting up SSL_CTX verify locations"); + daemon_remote_delete(rc); + return NULL; + } + SSL_CTX_set_client_CA_list(rc->ctx, SSL_load_client_CA_file(s_cert)); + SSL_CTX_set_verify(rc->ctx, SSL_VERIFY_PEER, NULL); + return rc; } @@ -256,13 +311,37 @@ int remote_accept_callback(struct comm_point* c, void* arg, int err, return 0; } log_addr(VERB_QUERY, "new control connection from", &addr, addrlen); + n->c->do_not_close = 0; + comm_point_stop_listening(n->c); + comm_point_start_listening(n->c, -1, REMOTE_CONTROL_TCP_TIMEOUT); memcpy(&n->c->repinfo.addr, &addr, addrlen); n->c->repinfo.addrlen = addrlen; - /* TODO setup ssl, assign fd */ + n->shake_state = rc_hs_read; + n->ssl = SSL_new(rc->ctx); + if(!n->ssl) { + log_crypto_err("could not SSL_new"); + close(newfd); + free(n); + return 0; + } + SSL_set_accept_state(n->ssl); + (void)SSL_set_mode(n->ssl, SSL_MODE_AUTO_RETRY); + if(!SSL_set_fd(n->ssl, newfd)) { + log_crypto_err("could not SSL_set_fd"); + close(newfd); + SSL_free(n->ssl); + free(n); + return 0; + } + n->rc = rc; n->next = rc->busy_list; rc->busy_list = n; rc->active ++; + + /* perform the first nonblocking read already, for windows, + * so it can return wouldblock. could be faster too. */ + (void)remote_control_callback(n->c, n, NETEVENT_NOERROR, NULL); return 0; } @@ -285,26 +364,106 @@ clean_point(struct daemon_remote* rc, struct rc_state* s) { state_list_remove_elem(&rc->busy_list, s->c); rc->active --; - if(s->ssl) + if(s->ssl) { + SSL_shutdown(s->ssl); SSL_free(s->ssl); + } comm_point_delete(s->c); free(s); } +/** handle remote control request */ +static void +handle_req(struct daemon_remote* rc, struct rc_state* s, SSL* ssl) +{ + char* msg = "HTTP/1.0 200 OK\r\nContent-type: text/plain\r\n\r\n" + "unbound server control channel\n"; + int r; + char buf[1024]; + fd_set_block(s->c->fd); + + ERR_clear_error(); + if((r=SSL_read(ssl, buf, (int)sizeof(buf)-1)) <= 0) { + if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) + return; + log_crypto_err("could not SSL_read"); + return; + } + buf[r] = 0; + log_info("got '%s'", buf); + + ERR_clear_error(); + if((r=SSL_write(ssl, msg, (int)strlen(msg))) <= 0) { + if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) + return; + log_crypto_err("could not SSL_write"); + return; + } +} + int remote_control_callback(struct comm_point* c, void* arg, int err, struct comm_reply* ATTR_UNUSED(rep)) { struct rc_state* s = (struct rc_state*)arg; struct daemon_remote* rc = s->rc; + int r; if(err != NETEVENT_NOERROR) { + if(err==NETEVENT_TIMEOUT) + log_err("remote control timed out"); clean_point(rc, s); return 0; } - /* TODO (continue to) setup the SSL connection */ + /* (continue to) setup the SSL connection */ + ERR_clear_error(); + r = SSL_do_handshake(s->ssl); + if(r != 1) { + r = SSL_get_error(s->ssl, r); + if(r == SSL_ERROR_WANT_READ) { + if(s->shake_state == rc_hs_read) { + /* try again later */ + return 0; + } + s->shake_state = rc_hs_read; + comm_point_listen_for_rw(c, 1, 0); + return 0; + } else if(r == SSL_ERROR_WANT_WRITE) { + if(s->shake_state == rc_hs_write) { + /* try again later */ + return 0; + } + s->shake_state = rc_hs_write; + comm_point_listen_for_rw(c, 0, 1); + return 0; + } else { + log_crypto_err("remote control failed ssl"); + clean_point(rc, s); + return 0; + } + } + s->shake_state = rc_none; /* once handshake has completed, check authentication */ + if(SSL_get_verify_result(s->ssl) == X509_V_OK) { + X509* x = SSL_get_peer_certificate(s->ssl); + if(!x) { + verbose(VERB_DETAIL, "remote control connection " + "provided no client certificate"); + clean_point(rc, s); + return 0; + } + verbose(VERB_ALGO, "remote control connection authenticated"); + X509_free(x); + } else { + verbose(VERB_DETAIL, "remote control connection failed to " + "authenticate with client certificate"); + clean_point(rc, s); + return 0; + } /* if OK start to actually handle the request */ + handle_req(rc, s, s->ssl); + verbose(VERB_ALGO, "remote control operation completed"); + clean_point(rc, s); return 0; } diff --git a/daemon/remote.h b/daemon/remote.h index dced6537a..fd16ffd93 100644 --- a/daemon/remote.h +++ b/daemon/remote.h @@ -53,6 +53,9 @@ struct comm_reply; struct comm_point; struct daemon_remote; +/** number of seconds timeout on incoming remote control handshake */ +#define REMOTE_CONTROL_TCP_TIMEOUT 120 + /** * a busy control command connection, SSL state */ @@ -61,6 +64,8 @@ struct rc_state { struct rc_state* next; /** the commpoint */ struct comm_point* c; + /** in the handshake part */ + enum { rc_none, rc_hs_read, rc_hs_write } shake_state; /** the ssl state */ SSL* ssl; /** the rc this is part of */ diff --git a/doc/Changelog b/doc/Changelog index 54856a85a..4b4018cfc 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,9 @@ +11 September 2008: Wouter + - set nonblocking on new TCP streams, because linux does not inherit + the socket options to the accepted socket. + - fix TCP timeouts. + - SSL protected connection between server and unbound-control. + 10 September 2008: Wouter - remove memleak in privacy addresses on reloads and quits. - remote control work. diff --git a/doc/example.conf.in b/doc/example.conf.in index c008faeb8..1efd5e822 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -383,13 +383,13 @@ remote-control: # server-key-file: "@UNBOUND_RUN_DIR@/unbound_server.key" # unbound server certificate file. - # server-key-file: "@UNBOUND_RUN_DIR@/unbound_server.pem" + # server-cert-file: "@UNBOUND_RUN_DIR@/unbound_server.pem" # unbound-control key file. # control-key-file: "@UNBOUND_RUN_DIR@/unbound_control.key" # unbound-control certificate file. - # control-key-file: "@UNBOUND_RUN_DIR@/unbound_control.pem" + # control-cert-file: "@UNBOUND_RUN_DIR@/unbound_control.pem" # Stub zones. # Create entries like below, to make all queries for 'example.com' and diff --git a/doc/unbound-control.8.in b/doc/unbound-control.8.in new file mode 100644 index 000000000..8d17fe642 --- /dev/null +++ b/doc/unbound-control.8.in @@ -0,0 +1,81 @@ +.TH "unbound-control" "8" "@date@" "NLnet Labs" "unbound @version@" +.\" +.\" unbound-control.8 -- unbound remote control manual +.\" +.\" Copyright (c) 2008, NLnet Labs. All rights reserved. +.\" +.\" See LICENSE for the license. +.\" +.\" +.SH "NAME" +.LP +unbound-control +\- Unbound remote server control utility. +.SH "SYNOPSIS" +.B unbound-control +.RB [ \-h ] +.RB [ \-c +.IR cfgfile ] +.RB [ \-s +.IR server ] +.IR command +.SH "DESCRIPTION" +.B Unbound-control +Performs remote administration on the \fIunbound\fR(8) DNS server. +It reads the configuration file, contacts the unbound server over SSL +sends the command and displays the result. +.P +The available options are: +.TP +.B \-h +Show the version and commandline option help. +.TP +.B \-c \fIcfgfile +The config file to read with settings. If not given the default +config file @ub_conf_file@ is used. +.TP +.B \-s \fIserver[@port] +IPv4 or IPv6 address of the server to contact. If not given, the +address is read from the config file. +.SH "COMMANDS" +There are several commands that the server understands. +.TP +.B start +Start the server. Simply execs \fIunbound\fR(8). +.TP +.B stop +Stop the server. +.TP +.B reload +Reload the server. +.SH "EXIT CODE" +The unbound-control program exits with status code 1 on error. +.SH "SET UP" +The setup requires a self\-signed certificate and private keys for both +the server and client. The script \fIunbound\-control\-setup\fR generates +these in the default run directory, or with \-d in another directory. +The script preserves private keys present in the directory. +After running the script as root, turn on \fBcontrol-enable\fR in +\fIunbound.conf\fR. +.SH "BROWSER SUPPORT" +It is also possible to administer via a browser. The client key needs +to be loaded into the browser, the setup script (see above) has generated +the file \fIunbound_control_browser.pfx\fR, with the client key and +certificate. By default it is stored with an empty password. +This can be loaded into a web browser, say Firefox, in the preferences \- +advanced \- encryption \- view certificates \- your certs window. +Then connect to the server control port (https://localhost:953) and +create a security override to accept the self-signed certificate from +the unbound server. +.SH "FILES" +.TP +.I @ub_conf_file@ +unbound configuration file. +.TP +.I @UNBOUND_RUN_DIR@ +directory with private keys (unbound_server.key and unbound_control.key), +self-signed certificates (unbound_server.pem and unbound_control.pem) and +unbound_control_browser.pfx file. +.SH "SEE ALSO" +\fIunbound.conf\fR(5), +\fIunbound\fR(8). diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 49f23c189..989cee40e 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -670,6 +670,52 @@ local\-data: 'example. TXT "text"'. If you need more complicated authoritative data, with referrals, wildcards, CNAME/DNAME support, or DNSSEC authoritative service, setup a stub\-zone for it as detailed in the stub zone section below. +.SS "Remote Control Options" +In the +.B remote\-control: +clause are the declarations for the remote control facility. If this is +enabled, the \fIunbound\-control\fR(8) utility can be used to send +commands to the running unbound server. The server uses these clauses +to setup SSLv3 / TLSv1 security for the connection. The +\fIunbound\-control\fR(8) utility also reads the \fBremote\-control\fR +section for options. To setup the correct self-signed certificates use the +\fIunbound\-control\-setup\fR(8) utility. +.TP 5 +.B control\-enable: \fI +The option is used to enable remote control, default is "no". +If turned off, the server does not listen for control commands. +.TP 5 +.B control\-interface: +Give IPv4 or IPv6 addresses to listen on for control commands. +By default localhost (127.0.0.1 and ::1) is listened to. +Use 0.0.0.0 and ::0 to listen to all interfaces. +.TP 5 +.B control\-port: +The port number to listen on for control commands, default is 953 +(that is the same port number named uses to listen to rndc). +If you change this port number, and permissions have been dropped, a +reload is not sufficient to open the port again, you must then restart. +.TP 5 +.B server\-key\-file: "" +Path to the server private key, by default unbound_server.key. +This file is generated by the \fIunbound\-control\-setup\fR utility. +This file is used by the unbound server, but not by \fIunbound\-control\fR. +.TP 5 +.B server\-cert\-file: "" +Path to the server self signed certificate, by default unbound_server.pem. +This file is generated by the \fIunbound\-control\-setup\fR utility. +This file is used by the unbound server, and also by \fIunbound\-control\fR. +.TP 5 +.B control\-key\-file: "" +Path to the control client private key, by default unbound_control.key. +This file is generated by the \fIunbound\-control\-setup\fR utility. +This file is used by \fIunbound\-control\fR. +.TP 5 +.B control\-cert\-file: "" +Path to the control client certificate, by default unbound_control.pem. +This certificate has to be signed with the server certificate. +This file is generated by the \fIunbound\-control\-setup\fR utility. +This file is used by \fIunbound\-control\fR. .SS "Stub Zone Options" .LP There may be multiple diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c new file mode 100644 index 000000000..98a03c4bf --- /dev/null +++ b/smallapp/unbound-control.c @@ -0,0 +1,310 @@ +/* + * checkconf/unbound-control.c - remote control utility for unbound. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * The remote control utility contacts the unbound server over ssl and + * sends the command, receives the answer, and displays the result + * from the commandline. + */ + +#include "config.h" +#include "util/log.h" +#include "util/config_file.h" +#include "util/locks.h" +#include "util/net_help.h" + +/** Give unbound-control usage, and exit (1). */ +static void +usage() +{ + printf("Usage: unbound-control [options] command\n"); + printf(" Remote control utility for unbound server.\n"); + printf("Options:\n"); + printf(" -c file config file, default is %s\n", CONFIGFILE); + printf(" -s ip[@port] server address, if omitted config is used.\n"); + printf(" -h show this usage help.\n"); + printf("Commands:\n"); + printf(" start start server; runs unbound(8)\n"); + printf(" stop stops the server\n"); + printf(" reload reloads the server\n"); + printf("Version %s\n", PACKAGE_VERSION); + printf("BSD licensed, see LICENSE in source package for details.\n"); + printf("Report bugs to %s\n", PACKAGE_BUGREPORT); + exit(1); +} + +/** exit with ssl error */ +static void ssl_err(const char* s) +{ + fprintf(stderr, "error: %s\n", s); + ERR_print_errors_fp(stderr); + exit(1); +} + +/** setup SSL context */ +static SSL_CTX* +setup_ctx(struct config_file* cfg) +{ + char* s_cert, *c_key, *c_cert; + SSL_CTX* ctx; + + s_cert = fname_after_chroot(cfg->server_cert_file, cfg, 1); + c_key = fname_after_chroot(cfg->control_key_file, cfg, 1); + c_cert = fname_after_chroot(cfg->control_cert_file, cfg, 1); + if(!s_cert || !c_key || !c_cert) + fatal_exit("out of memory"); + ctx = SSL_CTX_new(SSLv23_client_method()); + if(!ctx) + ssl_err("could not allocate SSL_CTX pointer"); + if(!(SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2)) + ssl_err("could not set SSL_OP_NO_SSLv2"); + if(!SSL_CTX_use_certificate_file(ctx,c_cert,SSL_FILETYPE_PEM) || + !SSL_CTX_use_PrivateKey_file(ctx,c_key,SSL_FILETYPE_PEM) + || !SSL_CTX_check_private_key(ctx)) + ssl_err("Error setting up SSL_CTX client key and cert"); + if (SSL_CTX_load_verify_locations(ctx, s_cert, NULL) != 1) + ssl_err("Error setting up SSL_CTX verify, server cert"); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + + free(s_cert); + free(c_key); + free(c_cert); + return ctx; +} + +/** contact the server with TCP connect */ +static int +contact_server(char* svr, struct config_file* cfg) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + int fd; + /* use svr or the first config entry */ + if(!svr) { + if(cfg->control_ifs) + svr = cfg->control_ifs->str; + else svr = "127.0.0.1"; + } + if(strchr(svr, '@')) { + if(!extstrtoaddr(svr, &addr, &addrlen)) + fatal_exit("could not parse IP@port: %s", svr); + } else { + if(!ipstrtoaddr(svr, cfg->control_port, &addr, &addrlen)) + fatal_exit("could not parse IP: %s", svr); + } + fd = socket(addr_is_ip6(&addr, addrlen)?AF_INET6:AF_INET, + SOCK_STREAM, 0); + if(fd == -1) { +#ifndef USE_WINSOCK + fatal_exit("socket: %s", strerror(errno)); +#else + fatal_exit("socket: %s", wsa_strerror(WSAGetLastError())); +#endif + } + if(connect(fd, (struct sockaddr*)&addr, addrlen) < 0) { + log_addr(0, "address", &addr, addrlen); +#ifndef USE_WINSOCK + log_err("connect: %s", strerror(errno)); +#else + log_err("connect: %s", wsa_strerror(WSAGetLastError())); +#endif + exit(1); + } + return fd; +} + +/** setup SSL on the connection */ +static SSL* +setup_ssl(SSL_CTX* ctx, int fd) +{ + SSL* ssl; + X509* x; + int r; + + ssl = SSL_new(ctx); + if(!ssl) + ssl_err("could not SSL_new"); + SSL_set_connect_state(ssl); + (void)SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + if(!SSL_set_fd(ssl, fd)) + ssl_err("could not SSL_set_fd"); + while(1) { + ERR_clear_error(); + if( (r=SSL_do_handshake(ssl)) == 1) + break; + r = SSL_get_error(ssl, r); + if(r != SSL_ERROR_WANT_READ && r != SSL_ERROR_WANT_WRITE) + ssl_err("SSL handshake failed"); + /* wants to be called again */ + } + + /* check authenticity of server */ + if(SSL_get_verify_result(ssl) != X509_V_OK) + ssl_err("SSL verification failed"); + x = SSL_get_peer_certificate(ssl); + if(!x) + ssl_err("Server presented no peer certificate"); + X509_free(x); + return ssl; +} + +/** send command and display result */ +static void +go_cmd(SSL* ssl, int argc, char* argv[]) +{ + char* cmd = "GET / HTTP/1.0\n\n"; + int r; + char buf[1024]; + if(SSL_write(ssl, cmd, (int)strlen(cmd)) <= 0) + ssl_err("could not SSL_write"); + while(1) { + ERR_clear_error(); + if((r = SSL_read(ssl, buf, (int)sizeof(buf)-1)) <= 0) { + if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) { + /* EOF */ + break; + } + ssl_err("could not SSL_read"); + } + buf[r] = 0; + printf("%s", buf); + } +} + +/** go ahead and read config, contact server and perform command and display */ +static void +go(char* cfgfile, char* svr, int argc, char* argv[]) +{ + struct config_file* cfg; + int fd; + SSL_CTX* ctx; + SSL* ssl; + + /* read config */ + if(!(cfg = config_create())) + fatal_exit("out of memory"); + if(!config_read(cfg, cfgfile)) + fatal_exit("could not read config file"); + if(!cfg->remote_control_enable) + log_warn("control-enable is 'no' in the config file."); + ctx = setup_ctx(cfg); + + /* contact server */ + fd = contact_server(svr, cfg); + ssl = setup_ssl(ctx, fd); + + /* send command */ + go_cmd(ssl, argc, argv); + + SSL_free(ssl); + close(fd); + SSL_CTX_free(ctx); + config_delete(cfg); +} + +/** getopt global, in case header files fail to declare it. */ +extern int optind; +/** getopt global, in case header files fail to declare it. */ +extern char* optarg; + +/** Main routine for unbound-control */ +int main(int argc, char* argv[]) +{ + int c; + char* cfgfile = CONFIGFILE; + char* svr = NULL; + log_ident_set("unbound-control"); + log_init(NULL, 0, NULL); + checklock_start(); +#ifdef USE_WINSOCK + if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0) + fatal_exit("WSAStartup failed: %s", wsa_strerror(r)); +#endif + + ERR_load_crypto_strings(); + ERR_load_SSL_strings(); + OpenSSL_add_all_algorithms(); + (void)SSL_library_init(); + + if(!RAND_status()) { + /* try to seed it */ + unsigned char buf[256]; + unsigned int v, seed=(unsigned)time(NULL) ^ (unsigned)getpid(); + size_t i; + for(i=0; i<256/sizeof(v); i++) { + memmove(buf+i*sizeof(v), &v, sizeof(v)); + v = v*seed + (unsigned int)i; + } + RAND_seed(buf, 256); + log_warn("no entropy, seeding openssl PRNG with time\n"); + } + + /* parse the options */ + while( (c=getopt(argc, argv, "c:s:h")) != -1) { + switch(c) { + case 'c': + cfgfile = optarg; + break; + case 's': + svr = optarg; + break; + case '?': + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + if(argc == 0) + usage(); + if(argc == 1 && strcmp(argv[0], "start")==0) { + if(execlp("unbound", "unbound", "-c", cfgfile, + (char*)NULL) < 0) { + fatal_exit("could not exec unbound: %s", + strerror(errno)); + } + } + + go(cfgfile, svr, argc, argv); + +#ifdef USE_WINSOCK + WSACleanup(); +#endif + checklock_stop(); + return 0; +} diff --git a/util/netevent.c b/util/netevent.c index b6d9cccfb..6d1726acb 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -574,6 +574,7 @@ int comm_point_perform_accept(struct comm_point* c, log_addr(0, "remote address is", addr, *addrlen); return -1; } + fd_set_nonblock(new_fd); return new_fd; } @@ -950,14 +951,17 @@ void comm_point_local_handle_callback(int fd, short event, void* arg) } void comm_point_raw_handle_callback(int ATTR_UNUSED(fd), - short ATTR_UNUSED(event), void* arg) + short event, void* arg) { struct comm_point* c = (struct comm_point*)arg; + int err = NETEVENT_NOERROR; log_assert(c->type == comm_raw); comm_base_now(c->ev->base); - + + if(event&EV_TIMEOUT) + err = NETEVENT_TIMEOUT; fptr_ok(fptr_whitelist_comm_point_raw(c->callback)); - (void)(*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, NULL); + (void)(*c->callback)(c, c->cb_arg, err, NULL); } struct comm_point* @@ -1108,7 +1112,7 @@ comm_point_create_tcp_handler(struct comm_base *base, c->tcp_free = parent->tcp_free; parent->tcp_free = c; /* libevent stuff */ - evbits = EV_PERSIST | EV_READ; + evbits = EV_PERSIST | EV_READ | EV_TIMEOUT; event_set(&c->ev->ev, c->fd, evbits, comm_point_tcp_handle_callback, c); if(event_base_set(base->eb->base, &c->ev->ev) != 0) { @@ -1437,6 +1441,7 @@ comm_point_start_listening(struct comm_point* c, int newfd, int sec) return; } } + c->ev->ev.ev_events |= EV_TIMEOUT; #ifndef S_SPLINT_S /* splint fails on struct timeval. */ c->timeout->tv_sec = sec; c->timeout->tv_usec = 0; @@ -1459,6 +1464,20 @@ comm_point_start_listening(struct comm_point* c, int newfd, int sec) } } +void comm_point_listen_for_rw(struct comm_point* c, int rd, int wr) +{ + verbose(VERB_ALGO, "comm point listen_for_rw %d %d", c->fd, wr); + if(event_del(&c->ev->ev) != 0) { + log_err("event_del error to cplf"); + } + c->ev->ev.ev_events &= ~(EV_READ|EV_WRITE); + if(rd) c->ev->ev.ev_events |= EV_READ; + if(wr) c->ev->ev.ev_events |= EV_WRITE; + if(event_add(&c->ev->ev, c->timeout) != 0) { + log_err("event_add failed. in cplf."); + } +} + size_t comm_point_get_mem(struct comm_point* c) { size_t s; diff --git a/util/netevent.h b/util/netevent.h index ed857b385..bc2b72d38 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -443,6 +443,14 @@ void comm_point_stop_listening(struct comm_point* c); */ void comm_point_start_listening(struct comm_point* c, int newfd, int sec); +/** + * Stop listening and start listening again for reading or writing. + * @param c: commpoint + * @param rd: if true, listens for reading. + * @param wr: if true, listens for writing. + */ +void comm_point_listen_for_rw(struct comm_point* c, int rd, int wr); + /** * Get size of memory used by comm point. * For TCP handlers this includes subhandlers.