]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
control channel security.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 11 Sep 2008 14:14:12 +0000 (14:14 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 11 Sep 2008 14:14:12 +0000 (14:14 +0000)
git-svn-id: file:///svn/unbound/trunk@1229 be551aaa-1e26-0410-a405-d3ace91eadb9

13 files changed:
Makefile.in
configure
configure.ac
daemon/daemon.c
daemon/remote.c
daemon/remote.h
doc/Changelog
doc/example.conf.in
doc/unbound-control.8.in [new file with mode: 0644]
doc/unbound.conf.5.in
smallapp/unbound-control.c [new file with mode: 0644]
util/netevent.c
util/netevent.h

index 0ad4c89158bae2bfa7910e846f4ccc4529fa3e99..4403b309a323e123d7a4d75917c9339fd620417f 100644 (file)
@@ -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
index 0eccdb368a6e86a0bfa14913872a1e8fb8aefd23..659b4d7a7e6c3c235d33afba0f973fea59a450ed 100755 (executable)
--- 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
index 759b426c515e07b2068e2b994993b2e7dafccab6..cb1f2d68387bdd031d43f20dc193e382e70a74b7 100644 (file)
@@ -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
index 6da7353afb45ce5e307e15386393221d27c3947b..b98b72b55337a8bce3fdc6f9ef6df4ea72a90d08 100644 (file)
@@ -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();
index 565beb80a05e4d32563b3ce3b21073524a6e66c9..c19c18147b3336536d26d48caf28fbbff1ded7d6 100644 (file)
@@ -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"
 #include <netdb.h>
 #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;
 }
index dced6537a02d86aacd9f0ccf02db83c52c563e85..fd16ffd931d271f8f0542f6db9d9456742b4a84b 100644 (file)
@@ -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 */
index 54856a85ab0260cfe7f25d5880683944166c7245..4b4018cfc4f41b337401ae8a0e0a17f53817c8b6 100644 (file)
@@ -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.
index c008faeb8a7eeb341fc40d5735e510423f32c189..1efd5e822ddb608f542914bf2f04ecfa790443b0 100644 (file)
@@ -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 (file)
index 0000000..8d17fe6
--- /dev/null
@@ -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).
index 49f23c189338963e414967c4a1a743ed331b55da..989cee40ea397efc504e1211b74cc7962aa39377 100644 (file)
@@ -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<yes or no>
+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: <ip address>
+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: <port number>
+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: "<private 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: "<certificate file.pem>"
+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: "<private 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: "<certificate file.pem>"
+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 (file)
index 0000000..98a03c4
--- /dev/null
@@ -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;
+}
index b6d9cccfb1e2c312ef0436c03b2820b186d61bde..6d1726acbe7e8d6334f699eac726ba7c73774d77 100644 (file)
@@ -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;
index ed857b38581a3c73d5efabce31db3da6881b6de5..bc2b72d38bd06e67254558833d183773808ae50c 100644 (file)
@@ -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.