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 \
$(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)
@-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
$(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)
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 $@
strip:
strip unbound
strip unbound-checkconf
+ strip unbound-control
strip unbound-host
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
$(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
-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"
"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
#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
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();
#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) {
}
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;
}
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;
}
{
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;
}
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
*/
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 */
+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.
# 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
--- /dev/null
+.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).
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
--- /dev/null
+/*
+ * 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;
+}
log_addr(0, "remote address is", addr, *addrlen);
return -1;
}
+ fd_set_nonblock(new_fd);
return new_fd;
}
}
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*
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)
{
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;
}
}
+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;
*/
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.