aboutsummaryrefslogtreecommitdiff
path: root/smallapp
diff options
context:
space:
mode:
authorDag-Erling Smørgrav <des@FreeBSD.org>2012-07-04 14:24:26 +0000
committerDag-Erling Smørgrav <des@FreeBSD.org>2012-07-04 14:24:26 +0000
commitafb79913ce00d885b8b43f7478e1e054edadb567 (patch)
treeb9037afac70edd3c6342318cedbbadc648b799ca /smallapp
downloadsrc-afb79913ce00d885b8b43f7478e1e054edadb567.tar.gz
src-afb79913ce00d885b8b43f7478e1e054edadb567.zip
import unbound 1.4.17vendor/unbound/1.4.17
Notes
Notes: svn path=/vendor/unbound/dist/; revision=238106 svn path=/vendor/unbound/1.4.17/; revision=238107; tag=vendor/unbound/1.4.17
Diffstat (limited to 'smallapp')
-rw-r--r--smallapp/unbound-anchor.c2165
-rw-r--r--smallapp/unbound-checkconf.c517
-rwxr-xr-xsmallapp/unbound-control-setup.sh162
-rw-r--r--smallapp/unbound-control.c426
-rw-r--r--smallapp/unbound-host.c514
-rw-r--r--smallapp/worker_cb.c242
6 files changed, 4026 insertions, 0 deletions
diff --git a/smallapp/unbound-anchor.c b/smallapp/unbound-anchor.c
new file mode 100644
index 000000000000..e14ca733fd7c
--- /dev/null
+++ b/smallapp/unbound-anchor.c
@@ -0,0 +1,2165 @@
+/*
+ * unbound-anchor.c - update the root anchor if necessary.
+ *
+ * Copyright (c) 2010, 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
+ *
+ * This file checks to see that the current 5011 keys work to prime the
+ * current root anchor. If not a certificate is used to update the anchor.
+ *
+ * This is a concept solution for distribution of the DNSSEC root
+ * trust anchor. It is a small tool, called "unbound-anchor", that
+ * runs before the main validator starts. I.e. in the init script:
+ * unbound-anchor; unbound. Thus it is meant to run at system boot time.
+ *
+ * Management-Abstract:
+ * * first run: fill root.key file with hardcoded DS record.
+ * * mostly: use RFC5011 tracking, quick . DNSKEY UDP query.
+ * * failover: use builtin certificate, do https and update.
+ * Special considerations:
+ * * 30-days RFC5011 timer saves a lot of https traffic.
+ * * DNSKEY probe must be NOERROR, saves a lot of https traffic.
+ * * fail if clock before sign date of the root, if cert expired.
+ * * if the root goes back to unsigned, deals with it.
+ *
+ * It has hardcoded the root DS anchors and the ICANN CA root certificate.
+ * It allows with options to override those. It also takes root-hints (it
+ * has to do a DNS resolve), and also has hardcoded defaults for those.
+ *
+ * Once it starts, just before the validator starts, it quickly checks if
+ * the root anchor file needs to be updated. First it tries to use
+ * RFC5011-tracking of the root key. If that fails (and for 30-days since
+ * last successful probe), then it attempts to update using the
+ * certificate. So most of the time, the RFC5011 tracking will work fine,
+ * and within a couple milliseconds, the main daemon can start. It will
+ * have only probed the . DNSKEY, not done expensive https transfers on the
+ * root infrastructure.
+ *
+ * If there is no root key in the root.key file, it bootstraps the
+ * RFC5011-tracking with its builtin DS anchors; if that fails it
+ * bootstraps the RFC5011-tracking using the certificate. (again to avoid
+ * https, and it is also faster).
+ *
+ * It uses the XML file by converting it to DS records and writing that to the
+ * key file. Unbound can detect that the 'special comments' are gone, and
+ * the file contains a list of normal DNSKEY/DS records, and uses that to
+ * bootstrap 5011 (the KSK is made VALID).
+ *
+ * The certificate update is done by fetching root-anchors.xml and
+ * root-anchors.p7s via SSL. The HTTPS certificate can be logged but is
+ * not validated (https for channel security; the security comes from the
+ * certificate). The 'data.iana.org' domain name A and AAAA are resolved
+ * without DNSSEC. It tries a random IP until the transfer succeeds. It
+ * then checks the p7s signature.
+ *
+ * On any failure, it leaves the root key file untouched. The main
+ * validator has to cope with it, it cannot fix things (So a failure does
+ * not go 'without DNSSEC', no downgrade). If it used its builtin stuff or
+ * did the https, it exits with an exit code, so that this can trigger the
+ * init script to log the event and potentially alert the operator that can
+ * do a manual check.
+ *
+ * The date is also checked. Before 2010-07-15 is a failure (root not
+ * signed yet; avoids attacks on system clock). The
+ * last-successful-RFC5011-probe (if available) has to be more than 30 days
+ * in the past (otherwise, RFC5011 should have worked). This keeps
+ * unneccesary https traffic down. If the main certificate is expired, it
+ * fails.
+ *
+ * The dates on the keys in the xml are checked (uses the libexpat xml
+ * parser), only the valid ones are used to re-enstate RFC5011 tracking.
+ * If 0 keys are valid, the zone has gone to insecure (a special marker is
+ * written in the keyfile that tells the main validator daemon the zone is
+ * insecure).
+ *
+ * Only the root ICANN CA is shipped, not the intermediate ones. The
+ * intermediate CAs are included in the p7s file that was downloaded. (the
+ * root cert is valid to 2028 and the intermediate to 2014, today).
+ *
+ * Obviously, the tool also has options so the operator can provide a new
+ * keyfile, a new certificate and new URLs, and fresh root hints. By
+ * default it logs nothing on failure and success; it 'just works'.
+ *
+ */
+
+#include "config.h"
+#include "libunbound/unbound.h"
+#include <ldns/rr.h>
+#include <expat.h>
+#ifndef HAVE_EXPAT_H
+#error "need libexpat to parse root-anchors.xml file."
+#endif
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#ifdef HAVE_OPENSSL_SSL_H
+#include <openssl/ssl.h>
+#endif
+#ifdef HAVE_OPENSSL_ERR_H
+#include <openssl/err.h>
+#endif
+#ifdef HAVE_OPENSSL_RAND_H
+#include <openssl/rand.h>
+#endif
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+
+/** name of server in URL to fetch HTTPS from */
+#define URLNAME "data.iana.org"
+/** path on HTTPS server to xml file */
+#define XMLNAME "root-anchors/root-anchors.xml"
+/** path on HTTPS server to p7s file */
+#define P7SNAME "root-anchors/root-anchors.p7s"
+/** port number for https access */
+#define HTTPS_PORT 443
+
+#ifdef USE_WINSOCK
+/* sneakily reuse the the wsa_strerror function, on windows */
+char* wsa_strerror(int err);
+#endif
+
+/** verbosity for this application */
+static int verb = 0;
+
+/** list of IP addresses */
+struct ip_list {
+ /** next in list */
+ struct ip_list* next;
+ /** length of addr */
+ socklen_t len;
+ /** address ready to connect to */
+ struct sockaddr_storage addr;
+ /** has the address been used */
+ int used;
+};
+
+/** Give unbound-anchor usage, and exit (1). */
+static void
+usage()
+{
+ printf("Usage: unbound-anchor [opts]\n");
+ printf(" Setup or update root anchor. "
+ "Most options have defaults.\n");
+ printf(" Run this program before you start the validator.\n");
+ printf("\n");
+ printf(" The anchor and cert have default builtin content\n");
+ printf(" if the file does not exist or is empty.\n");
+ printf("\n");
+ printf("-a file root key file, default %s\n", ROOT_ANCHOR_FILE);
+ printf(" The key is input and output for this tool.\n");
+ printf("-c file cert file, default %s\n", ROOT_CERT_FILE);
+ printf("-l list builtin key and cert on stdout\n");
+ printf("-u name server in https url, default %s\n", URLNAME);
+ printf("-x path pathname to xml in url, default %s\n", XMLNAME);
+ printf("-s path pathname to p7s in url, default %s\n", P7SNAME);
+ printf("-4 work using IPv4 only\n");
+ printf("-6 work using IPv6 only\n");
+ printf("-f resolv.conf use given resolv.conf to resolve -u name\n");
+ printf("-r root.hints use given root.hints to resolve -u name\n"
+ " builtin root hints are used by default\n");
+ printf("-v more verbose\n");
+ printf("-C conf debug, read config\n");
+ printf("-P port use port for https connect, default 443\n");
+ printf("-F debug, force update with cert\n");
+ printf("-h show this usage help\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);
+}
+
+/** return the built in root update certificate */
+static const char*
+get_builtin_cert(void)
+{
+ return
+/* The ICANN CA fetched at 24 Sep 2010. Valid to 2028 */
+"-----BEGIN CERTIFICATE-----\n"
+"MIIDdzCCAl+gAwIBAgIBATANBgkqhkiG9w0BAQsFADBdMQ4wDAYDVQQKEwVJQ0FO\n"
+"TjEmMCQGA1UECxMdSUNBTk4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNV\n"
+"BAMTDUlDQU5OIFJvb3QgQ0ExCzAJBgNVBAYTAlVTMB4XDTA5MTIyMzA0MTkxMloX\n"
+"DTI5MTIxODA0MTkxMlowXTEOMAwGA1UEChMFSUNBTk4xJjAkBgNVBAsTHUlDQU5O\n"
+"IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1JQ0FOTiBSb290IENB\n"
+"MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKDb\n"
+"cLhPNNqc1NB+u+oVvOnJESofYS9qub0/PXagmgr37pNublVThIzyLPGCJ8gPms9S\n"
+"G1TaKNIsMI7d+5IgMy3WyPEOECGIcfqEIktdR1YWfJufXcMReZwU4v/AdKzdOdfg\n"
+"ONiwc6r70duEr1IiqPbVm5T05l1e6D+HkAvHGnf1LtOPGs4CHQdpIUcy2kauAEy2\n"
+"paKcOcHASvbTHK7TbbvHGPB+7faAztABLoneErruEcumetcNfPMIjXKdv1V1E3C7\n"
+"MSJKy+jAqqQJqjZoQGB0necZgUMiUv7JK1IPQRM2CXJllcyJrm9WFxY0c1KjBO29\n"
+"iIKK69fcglKcBuFShUECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B\n"
+"Af8EBAMCAf4wHQYDVR0OBBYEFLpS6UmDJIZSL8eZzfyNa2kITcBQMA0GCSqGSIb3\n"
+"DQEBCwUAA4IBAQAP8emCogqHny2UYFqywEuhLys7R9UKmYY4suzGO4nkbgfPFMfH\n"
+"6M+Zj6owwxlwueZt1j/IaCayoKU3QsrYYoDRolpILh+FPwx7wseUEV8ZKpWsoDoD\n"
+"2JFbLg2cfB8u/OlE4RYmcxxFSmXBg0yQ8/IoQt/bxOcEEhhiQ168H2yE5rxJMt9h\n"
+"15nu5JBSewrCkYqYYmaxyOC3WrVGfHZxVI7MpIFcGdvSb2a1uyuua8l0BKgk3ujF\n"
+"0/wsHNeP22qNyVO+XVBzrM8fk8BSUFuiT/6tZTYXRtEt5aKQZgXbKU5dUF3jT9qg\n"
+"j/Br5BZw3X/zd325TvnswzMC1+ljLzHnQGGk\n"
+"-----END CERTIFICATE-----\n"
+ ;
+}
+
+/** return the built in root DS trust anchor */
+static const char*
+get_builtin_ds(void)
+{
+ return
+". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5\n";
+}
+
+/** print hex data */
+static void
+print_data(char* msg, char* data, int len)
+{
+ int i;
+ printf("%s: ", msg);
+ for(i=0; i<len; i++) {
+ printf(" %2.2x", (unsigned char)data[i]);
+ }
+ printf("\n");
+}
+
+/** print ub context creation error and exit */
+static void
+ub_ctx_error_exit(struct ub_ctx* ctx, const char* str, const char* str2)
+{
+ ub_ctx_delete(ctx);
+ if(str && str2 && verb) printf("%s: %s\n", str, str2);
+ if(verb) printf("error: could not create unbound resolver context\n");
+ exit(0);
+}
+
+/**
+ * Create a new unbound context with the commandline settings applied
+ */
+static struct ub_ctx*
+create_unbound_context(char* res_conf, char* root_hints, char* debugconf,
+ int ip4only, int ip6only)
+{
+ int r;
+ struct ub_ctx* ctx = ub_ctx_create();
+ if(!ctx) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ /* do not waste time and network traffic to fetch extra nameservers */
+ r = ub_ctx_set_option(ctx, "target-fetch-policy:", "0 0 0 0 0");
+ if(r && verb) printf("ctx targetfetchpolicy: %s\n", ub_strerror(r));
+ /* read config file first, so its settings can be overridden */
+ if(debugconf) {
+ r = ub_ctx_config(ctx, debugconf);
+ if(r) ub_ctx_error_exit(ctx, debugconf, ub_strerror(r));
+ }
+ if(res_conf) {
+ r = ub_ctx_resolvconf(ctx, res_conf);
+ if(r) ub_ctx_error_exit(ctx, res_conf, ub_strerror(r));
+ }
+ if(root_hints) {
+ r = ub_ctx_set_option(ctx, "root-hints:", root_hints);
+ if(r) ub_ctx_error_exit(ctx, root_hints, ub_strerror(r));
+ }
+ if(ip4only) {
+ r = ub_ctx_set_option(ctx, "do-ip6:", "no");
+ if(r) ub_ctx_error_exit(ctx, "ip4only", ub_strerror(r));
+ }
+ if(ip6only) {
+ r = ub_ctx_set_option(ctx, "do-ip4:", "no");
+ if(r) ub_ctx_error_exit(ctx, "ip6only", ub_strerror(r));
+ }
+ return ctx;
+}
+
+/** printout certificate in detail */
+static void
+verb_cert(char* msg, X509* x)
+{
+ if(verb == 0 || verb == 1) return;
+ if(verb == 2) {
+ if(msg) printf("%s\n", msg);
+ X509_print_ex_fp(stdout, x, 0, (unsigned long)-1
+ ^(X509_FLAG_NO_SUBJECT
+ |X509_FLAG_NO_ISSUER|X509_FLAG_NO_VALIDITY));
+ return;
+ }
+ if(msg) printf("%s\n", msg);
+ X509_print_fp(stdout, x);
+}
+
+/** printout certificates in detail */
+static void
+verb_certs(char* msg, STACK_OF(X509)* sk)
+{
+ int i, num = sk_X509_num(sk);
+ if(verb == 0 || verb == 1) return;
+ for(i=0; i<num; i++) {
+ printf("%s (%d/%d)\n", msg, i, num);
+ verb_cert(NULL, sk_X509_value(sk, i));
+ }
+}
+
+/** read certificates from a PEM bio */
+static STACK_OF(X509)*
+read_cert_bio(BIO* bio)
+{
+ STACK_OF(X509) *sk = sk_X509_new_null();
+ if(!sk) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ while(!BIO_eof(bio)) {
+ X509* x = PEM_read_bio_X509(bio, NULL, 0, NULL);
+ if(x == NULL) {
+ if(verb) {
+ printf("failed to read X509\n");
+ ERR_print_errors_fp(stdout);
+ }
+ continue;
+ }
+ if(!sk_X509_push(sk, x)) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ }
+ return sk;
+}
+
+/* read the certificate file */
+static STACK_OF(X509)*
+read_cert_file(char* file)
+{
+ STACK_OF(X509)* sk;
+ FILE* in;
+ int content = 0;
+ char buf[128];
+ if(file == NULL || strcmp(file, "") == 0) {
+ return NULL;
+ }
+ sk = sk_X509_new_null();
+ if(!sk) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ in = fopen(file, "r");
+ if(!in) {
+ if(verb) printf("%s: %s\n", file, strerror(errno));
+#ifndef S_SPLINT_S
+ sk_X509_pop_free(sk, X509_free);
+#endif
+ return NULL;
+ }
+ while(!feof(in)) {
+ X509* x = PEM_read_X509(in, NULL, 0, NULL);
+ if(x == NULL) {
+ if(verb) {
+ printf("failed to read X509 file\n");
+ ERR_print_errors_fp(stdout);
+ }
+ continue;
+ }
+ if(!sk_X509_push(sk, x)) {
+ if(verb) printf("out of memory\n");
+ fclose(in);
+ exit(0);
+ }
+ content = 1;
+ /* read away newline after --END CERT-- */
+ if(!fgets(buf, (int)sizeof(buf), in))
+ break;
+ }
+ fclose(in);
+ if(!content) {
+ if(verb) printf("%s is empty\n", file);
+#ifndef S_SPLINT_S
+ sk_X509_pop_free(sk, X509_free);
+#endif
+ return NULL;
+ }
+ return sk;
+}
+
+/** read certificates from the builtin certificate */
+static STACK_OF(X509)*
+read_builtin_cert(void)
+{
+ const char* builtin_cert = get_builtin_cert();
+ STACK_OF(X509)* sk;
+ BIO *bio = BIO_new_mem_buf((void*)builtin_cert,
+ (int)strlen(builtin_cert));
+ if(!bio) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ sk = read_cert_bio(bio);
+ if(!sk) {
+ if(verb) printf("internal error, out of memory\n");
+ exit(0);
+ }
+ BIO_free(bio);
+ return sk;
+}
+
+/** read update cert file or use builtin */
+static STACK_OF(X509)*
+read_cert_or_builtin(char* file)
+{
+ STACK_OF(X509) *sk = read_cert_file(file);
+ if(!sk) {
+ if(verb) printf("using builtin certificate\n");
+ sk = read_builtin_cert();
+ }
+ if(verb) printf("have %d trusted certificates\n", sk_X509_num(sk));
+ verb_certs("trusted certificates", sk);
+ return sk;
+}
+
+static void
+do_list_builtin(void)
+{
+ const char* builtin_cert = get_builtin_cert();
+ const char* builtin_ds = get_builtin_ds();
+ printf("%s\n", builtin_ds);
+ printf("%s\n", builtin_cert);
+ exit(0);
+}
+
+/** printout IP address with message */
+static void
+verb_addr(char* msg, struct ip_list* ip)
+{
+ if(verb) {
+ char out[100];
+ void* a = &((struct sockaddr_in*)&ip->addr)->sin_addr;
+ if(ip->len != (socklen_t)sizeof(struct sockaddr_in))
+ a = &((struct sockaddr_in6*)&ip->addr)->sin6_addr;
+
+ if(inet_ntop((int)((struct sockaddr_in*)&ip->addr)->sin_family,
+ a, out, (socklen_t)sizeof(out))==0)
+ printf("%s (inet_ntop error)\n", msg);
+ else printf("%s %s\n", msg, out);
+ }
+}
+
+/** free ip_list */
+static void
+ip_list_free(struct ip_list* p)
+{
+ struct ip_list* np;
+ while(p) {
+ np = p->next;
+ free(p);
+ p = np;
+ }
+}
+
+/** create ip_list entry for a RR record */
+static struct ip_list*
+RR_to_ip(int tp, char* data, int len, int port)
+{
+ struct ip_list* ip = (struct ip_list*)calloc(1, sizeof(*ip));
+ uint16_t p = (uint16_t)port;
+ if(tp == LDNS_RR_TYPE_A) {
+ struct sockaddr_in* sa = (struct sockaddr_in*)&ip->addr;
+ ip->len = (socklen_t)sizeof(*sa);
+ sa->sin_family = AF_INET;
+ sa->sin_port = (in_port_t)htons(p);
+ if(len != (int)sizeof(sa->sin_addr)) {
+ if(verb) printf("skipped badly formatted A\n");
+ free(ip);
+ return NULL;
+ }
+ memmove(&sa->sin_addr, data, sizeof(sa->sin_addr));
+
+ } else if(tp == LDNS_RR_TYPE_AAAA) {
+ struct sockaddr_in6* sa = (struct sockaddr_in6*)&ip->addr;
+ ip->len = (socklen_t)sizeof(*sa);
+ sa->sin6_family = AF_INET6;
+ sa->sin6_port = (in_port_t)htons(p);
+ if(len != (int)sizeof(sa->sin6_addr)) {
+ if(verb) printf("skipped badly formatted AAAA\n");
+ free(ip);
+ return NULL;
+ }
+ memmove(&sa->sin6_addr, data, sizeof(sa->sin6_addr));
+ } else {
+ if(verb) printf("internal error: bad type in RRtoip\n");
+ free(ip);
+ return NULL;
+ }
+ verb_addr("resolved server address", ip);
+ return ip;
+}
+
+/** Resolve name, type, class and add addresses to iplist */
+static void
+resolve_host_ip(struct ub_ctx* ctx, char* host, int port, int tp, int cl,
+ struct ip_list** head)
+{
+ struct ub_result* res = NULL;
+ int r;
+ int i;
+
+ r = ub_resolve(ctx, host, tp, cl, &res);
+ if(r) {
+ if(verb) printf("error: resolve %s %s: %s\n", host,
+ (tp==LDNS_RR_TYPE_A)?"A":"AAAA", ub_strerror(r));
+ return;
+ }
+ if(!res) {
+ if(verb) printf("out of memory\n");
+ ub_ctx_delete(ctx);
+ exit(0);
+ }
+ for(i = 0; res->data[i]; i++) {
+ struct ip_list* ip = RR_to_ip(tp, res->data[i], res->len[i],
+ port);
+ if(!ip) continue;
+ ip->next = *head;
+ *head = ip;
+ }
+ ub_resolve_free(res);
+}
+
+/** parse a text IP address into a sockaddr */
+static struct ip_list*
+parse_ip_addr(char* str, int port)
+{
+ socklen_t len = 0;
+ struct sockaddr_storage* addr = NULL;
+ struct sockaddr_in6 a6;
+ struct sockaddr_in a;
+ struct ip_list* ip;
+ uint16_t p = (uint16_t)port;
+ memset(&a6, 0, sizeof(a6));
+ memset(&a, 0, sizeof(a));
+
+ if(inet_pton(AF_INET6, str, &a6.sin6_addr) > 0) {
+ /* it is an IPv6 */
+ a6.sin6_family = AF_INET6;
+ a6.sin6_port = (in_port_t)htons(p);
+ addr = (struct sockaddr_storage*)&a6;
+ len = (socklen_t)sizeof(struct sockaddr_in6);
+ }
+ if(inet_pton(AF_INET, str, &a.sin_addr) > 0) {
+ /* it is an IPv4 */
+ a.sin_family = AF_INET;
+ a.sin_port = (in_port_t)htons(p);
+ addr = (struct sockaddr_storage*)&a;
+ len = (socklen_t)sizeof(struct sockaddr_in);
+ }
+ if(!len) return NULL;
+ ip = (struct ip_list*)calloc(1, sizeof(*ip));
+ if(!ip) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ ip->len = len;
+ memmove(&ip->addr, addr, len);
+ if(verb) printf("server address is %s\n", str);
+ return ip;
+}
+
+/**
+ * Resolve a domain name (even though the resolver is down and there is
+ * no trust anchor). Without DNSSEC validation.
+ * @param host: the name to resolve.
+ * If this name is an IP4 or IP6 address this address is returned.
+ * @param port: the port number used for the returned IP structs.
+ * @param res_conf: resolv.conf (if any).
+ * @param root_hints: root hints (if any).
+ * @param debugconf: unbound.conf for debugging options.
+ * @param ip4only: use only ip4 for resolve and only lookup A
+ * @param ip6only: use only ip6 for resolve and only lookup AAAA
+ * default is to lookup A and AAAA using ip4 and ip6.
+ * @return list of IP addresses.
+ */
+static struct ip_list*
+resolve_name(char* host, int port, char* res_conf, char* root_hints,
+ char* debugconf, int ip4only, int ip6only)
+{
+ struct ub_ctx* ctx;
+ struct ip_list* list = NULL;
+ /* first see if name is an IP address itself */
+ if( (list=parse_ip_addr(host, port)) ) {
+ return list;
+ }
+
+ /* create resolver context */
+ ctx = create_unbound_context(res_conf, root_hints, debugconf,
+ ip4only, ip6only);
+
+ /* try resolution of A */
+ if(!ip6only) {
+ resolve_host_ip(ctx, host, port, LDNS_RR_TYPE_A,
+ LDNS_RR_CLASS_IN, &list);
+ }
+
+ /* try resolution of AAAA */
+ if(!ip4only) {
+ resolve_host_ip(ctx, host, port, LDNS_RR_TYPE_AAAA,
+ LDNS_RR_CLASS_IN, &list);
+ }
+
+ ub_ctx_delete(ctx);
+ if(!list) {
+ if(verb) printf("%s has no IP addresses I can use\n", host);
+ exit(0);
+ }
+ return list;
+}
+
+/** clear used flags */
+static void
+wipe_ip_usage(struct ip_list* p)
+{
+ while(p) {
+ p->used = 0;
+ p = p->next;
+ }
+}
+
+/** cound unused IPs */
+static int
+count_unused(struct ip_list* p)
+{
+ int num = 0;
+ while(p) {
+ if(!p->used) num++;
+ p = p->next;
+ }
+ return num;
+}
+
+/** pick random unused element from IP list */
+static struct ip_list*
+pick_random_ip(struct ip_list* list)
+{
+ struct ip_list* p = list;
+ int num = count_unused(list);
+ int sel;
+ if(num == 0) return NULL;
+ /* not perfect, but random enough */
+ sel = (int)ldns_get_random() % num;
+ /* skip over unused elements that we did not select */
+ while(sel > 0 && p) {
+ if(!p->used) sel--;
+ p = p->next;
+ }
+ /* find the next unused element */
+ while(p && p->used)
+ p = p->next;
+ if(!p) return NULL; /* robustness */
+ return p;
+}
+
+/** close the fd */
+static void
+fd_close(int fd)
+{
+#ifndef USE_WINSOCK
+ close(fd);
+#else
+ closesocket(fd);
+#endif
+}
+
+/** printout socket errno */
+static void
+print_sock_err(const char* msg)
+{
+#ifndef USE_WINSOCK
+ if(verb) printf("%s: %s\n", msg, strerror(errno));
+#else
+ if(verb) printf("%s: %s\n", msg, wsa_strerror(WSAGetLastError()));
+#endif
+}
+
+/** connect to IP address */
+static int
+connect_to_ip(struct ip_list* ip)
+{
+ int fd;
+ verb_addr("connect to", ip);
+ fd = socket(ip->len==(socklen_t)sizeof(struct sockaddr_in)?
+ AF_INET:AF_INET6, SOCK_STREAM, 0);
+ if(fd == -1) {
+ print_sock_err("socket");
+ return -1;
+ }
+ if(connect(fd, (struct sockaddr*)&ip->addr, ip->len) < 0) {
+ print_sock_err("connect");
+ fd_close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+/** create SSL context */
+static SSL_CTX*
+setup_sslctx(void)
+{
+ SSL_CTX* sslctx = SSL_CTX_new(SSLv23_client_method());
+ if(!sslctx) {
+ if(verb) printf("SSL_CTX_new error\n");
+ return NULL;
+ }
+ return sslctx;
+}
+
+/** initiate TLS on a connection */
+static SSL*
+TLS_initiate(SSL_CTX* sslctx, int fd)
+{
+ X509* x;
+ int r;
+ SSL* ssl = SSL_new(sslctx);
+ if(!ssl) {
+ if(verb) printf("SSL_new error\n");
+ return NULL;
+ }
+ SSL_set_connect_state(ssl);
+ (void)SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
+ if(!SSL_set_fd(ssl, fd)) {
+ if(verb) printf("SSL_set_fd error\n");
+ SSL_free(ssl);
+ return NULL;
+ }
+ 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) {
+ if(verb) printf("SSL handshake failed\n");
+ SSL_free(ssl);
+ return NULL;
+ }
+ /* wants to be called again */
+ }
+ x = SSL_get_peer_certificate(ssl);
+ if(!x) {
+ if(verb) printf("Server presented no peer certificate\n");
+ SSL_free(ssl);
+ return NULL;
+ }
+ verb_cert("server SSL certificate", x);
+ X509_free(x);
+ return ssl;
+}
+
+/** perform neat TLS shutdown */
+static void
+TLS_shutdown(int fd, SSL* ssl, SSL_CTX* sslctx)
+{
+ /* shutdown the SSL connection nicely */
+ if(SSL_shutdown(ssl) == 0) {
+ SSL_shutdown(ssl);
+ }
+ SSL_free(ssl);
+ SSL_CTX_free(sslctx);
+ fd_close(fd);
+}
+
+/** write a line over SSL */
+static int
+write_ssl_line(SSL* ssl, char* str, char* sec)
+{
+ char buf[1024];
+ size_t l;
+ if(sec) {
+ snprintf(buf, sizeof(buf), str, sec);
+ } else {
+ snprintf(buf, sizeof(buf), "%s", str);
+ }
+ l = strlen(buf);
+ if(l+2 >= sizeof(buf)) {
+ if(verb) printf("line too long\n");
+ return 0;
+ }
+ if(verb >= 2) printf("SSL_write: %s\n", buf);
+ buf[l] = '\r';
+ buf[l+1] = '\n';
+ buf[l+2] = 0;
+ /* add \r\n */
+ if(SSL_write(ssl, buf, (int)strlen(buf)) <= 0) {
+ if(verb) printf("could not SSL_write %s", str);
+ return 0;
+ }
+ return 1;
+}
+
+/** process header line, check rcode and keeping track of size */
+static int
+process_one_header(char* buf, size_t* clen, int* chunked)
+{
+ if(verb>=2) printf("header: '%s'\n", buf);
+ if(strncasecmp(buf, "HTTP/1.1 ", 9) == 0) {
+ /* check returncode */
+ if(buf[9] != '2') {
+ if(verb) printf("bad status %s\n", buf+9);
+ return 0;
+ }
+ } else if(strncasecmp(buf, "Content-Length: ", 16) == 0) {
+ if(!*chunked)
+ *clen = (size_t)atoi(buf+16);
+ } else if(strncasecmp(buf, "Transfer-Encoding: chunked", 19+7) == 0) {
+ *clen = 0;
+ *chunked = 1;
+ }
+ return 1;
+}
+
+/**
+ * Read one line from SSL
+ * zero terminates.
+ * skips "\r\n" (but not copied to buf).
+ * @param ssl: the SSL connection to read from (blocking).
+ * @param buf: buffer to return line in.
+ * @param len: size of the buffer.
+ * @return 0 on error, 1 on success.
+ */
+static int
+read_ssl_line(SSL* ssl, char* buf, size_t len)
+{
+ size_t n = 0;
+ int r;
+ int endnl = 0;
+ while(1) {
+ if(n >= len) {
+ if(verb) printf("line too long\n");
+ return 0;
+ }
+ if((r = SSL_read(ssl, buf+n, 1)) <= 0) {
+ if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) {
+ /* EOF */
+ break;
+ }
+ if(verb) printf("could not SSL_read\n");
+ return 0;
+ }
+ if(endnl && buf[n] == '\n') {
+ break;
+ } else if(endnl) {
+ /* bad data */
+ if(verb) printf("error: stray linefeeds\n");
+ return 0;
+ } else if(buf[n] == '\r') {
+ /* skip \r, and also \n on the wire */
+ endnl = 1;
+ continue;
+ } else if(buf[n] == '\n') {
+ /* skip the \n, we are done */
+ break;
+ } else n++;
+ }
+ buf[n] = 0;
+ return 1;
+}
+
+/** read http headers and process them */
+static size_t
+read_http_headers(SSL* ssl, size_t* clen)
+{
+ char buf[1024];
+ int chunked = 0;
+ *clen = 0;
+ while(read_ssl_line(ssl, buf, sizeof(buf))) {
+ if(buf[0] == 0)
+ return 1;
+ if(!process_one_header(buf, clen, &chunked))
+ return 0;
+ }
+ return 0;
+}
+
+/** read a data chunk */
+static char*
+read_data_chunk(SSL* ssl, size_t len)
+{
+ size_t got = 0;
+ int r;
+ char* data = malloc(len+1);
+ if(!data) {
+ if(verb) printf("out of memory\n");
+ return NULL;
+ }
+ while(got < len) {
+ if((r = SSL_read(ssl, data+got, (int)(len-got))) <= 0) {
+ if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) {
+ /* EOF */
+ if(verb) printf("could not SSL_read: unexpected EOF\n");
+ free(data);
+ return NULL;
+ }
+ if(verb) printf("could not SSL_read\n");
+ free(data);
+ return NULL;
+ }
+ if(verb >= 2) printf("at %d/%d\n", (int)got, (int)len);
+ got += r;
+ }
+ if(verb>=2) printf("read %d data\n", (int)len);
+ data[len] = 0;
+ return data;
+}
+
+/** parse chunk header */
+static int
+parse_chunk_header(char* buf, size_t* result)
+{
+ char* e = NULL;
+ size_t v = (size_t)strtol(buf, &e, 16);
+ if(e == buf)
+ return 0;
+ *result = v;
+ return 1;
+}
+
+/** read chunked data from connection */
+static BIO*
+do_chunked_read(SSL* ssl)
+{
+ char buf[1024];
+ size_t len;
+ char* body;
+ BIO* mem = BIO_new(BIO_s_mem());
+ if(verb>=3) printf("do_chunked_read\n");
+ if(!mem) {
+ if(verb) printf("out of memory\n");
+ return NULL;
+ }
+ while(read_ssl_line(ssl, buf, sizeof(buf))) {
+ /* read the chunked start line */
+ if(verb>=2) printf("chunk header: %s\n", buf);
+ if(!parse_chunk_header(buf, &len)) {
+ BIO_free(mem);
+ if(verb>=3) printf("could not parse chunk header\n");
+ return NULL;
+ }
+ if(verb>=2) printf("chunk len: %d\n", (int)len);
+ /* are we done? */
+ if(len == 0) {
+ char z = 0;
+ /* skip end-of-chunk-trailer lines,
+ * until the empty line after that */
+ do {
+ if(!read_ssl_line(ssl, buf, sizeof(buf))) {
+ BIO_free(mem);
+ return NULL;
+ }
+ } while (strlen(buf) > 0);
+ /* end of chunks, zero terminate it */
+ if(BIO_write(mem, &z, 1) <= 0) {
+ if(verb) printf("out of memory\n");
+ BIO_free(mem);
+ return NULL;
+ }
+ return mem;
+ }
+ /* read the chunked body */
+ body = read_data_chunk(ssl, len);
+ if(!body) {
+ BIO_free(mem);
+ return NULL;
+ }
+ if(BIO_write(mem, body, (int)len) <= 0) {
+ if(verb) printf("out of memory\n");
+ free(body);
+ BIO_free(mem);
+ return NULL;
+ }
+ free(body);
+ /* skip empty line after data chunk */
+ if(!read_ssl_line(ssl, buf, sizeof(buf))) {
+ BIO_free(mem);
+ return NULL;
+ }
+ }
+ BIO_free(mem);
+ return NULL;
+}
+
+/** start HTTP1.1 transaction on SSL */
+static int
+write_http_get(SSL* ssl, char* pathname, char* urlname)
+{
+ if(write_ssl_line(ssl, "GET /%s HTTP/1.1", pathname) &&
+ write_ssl_line(ssl, "Host: %s", urlname) &&
+ write_ssl_line(ssl, "User-Agent: unbound-anchor/%s",
+ PACKAGE_VERSION) &&
+ /* We do not really do multiple queries per connection,
+ * but this header setting is also not needed.
+ * write_ssl_line(ssl, "Connection: close", NULL) &&*/
+ write_ssl_line(ssl, "", NULL)) {
+ return 1;
+ }
+ return 0;
+}
+
+/** read chunked data and zero terminate; len is without zero */
+static char*
+read_chunked_zero_terminate(SSL* ssl, size_t* len)
+{
+ /* do the chunked version */
+ BIO* tmp = do_chunked_read(ssl);
+ char* data, *d = NULL;
+ size_t l;
+ if(!tmp) {
+ if(verb) printf("could not read from https\n");
+ return NULL;
+ }
+ l = (size_t)BIO_get_mem_data(tmp, &d);
+ if(verb>=2) printf("chunked data is %d\n", (int)l);
+ if(l == 0 || d == NULL) {
+ if(verb) printf("out of memory\n");
+ return NULL;
+ }
+ *len = l-1;
+ data = (char*)malloc(l);
+ if(data == NULL) {
+ if(verb) printf("out of memory\n");
+ return NULL;
+ }
+ memcpy(data, d, l);
+ BIO_free(tmp);
+ return data;
+}
+
+/** read HTTP result from SSL */
+static BIO*
+read_http_result(SSL* ssl)
+{
+ size_t len = 0;
+ char* data;
+ BIO* m;
+ if(!read_http_headers(ssl, &len)) {
+ return NULL;
+ }
+ if(len == 0) {
+ data = read_chunked_zero_terminate(ssl, &len);
+ } else {
+ data = read_data_chunk(ssl, len);
+ }
+ if(!data) return NULL;
+ if(verb >= 4) print_data("read data", data, (int)len);
+ m = BIO_new_mem_buf(data, (int)len);
+ if(!m) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ return m;
+}
+
+/** https to an IP addr, return BIO with pathname or NULL */
+static BIO*
+https_to_ip(struct ip_list* ip, char* pathname, char* urlname)
+{
+ int fd;
+ SSL* ssl;
+ BIO* bio;
+ SSL_CTX* sslctx = setup_sslctx();
+ if(!sslctx) {
+ return NULL;
+ }
+ fd = connect_to_ip(ip);
+ if(fd == -1) {
+ SSL_CTX_free(sslctx);
+ return NULL;
+ }
+ ssl = TLS_initiate(sslctx, fd);
+ if(!ssl) {
+ SSL_CTX_free(sslctx);
+ fd_close(fd);
+ return NULL;
+ }
+ if(!write_http_get(ssl, pathname, urlname)) {
+ if(verb) printf("could not write to server\n");
+ SSL_free(ssl);
+ SSL_CTX_free(sslctx);
+ fd_close(fd);
+ return NULL;
+ }
+ bio = read_http_result(ssl);
+ TLS_shutdown(fd, ssl, sslctx);
+ return bio;
+}
+
+/**
+ * Do a HTTPS, HTTP1.1 over TLS, to fetch a file
+ * @param ip_list: list of IP addresses to use to fetch from.
+ * @param pathname: pathname of file on server to GET.
+ * @param urlname: name to pass as the virtual host for this request.
+ * @return a memory BIO with the file in it.
+ */
+static BIO*
+https(struct ip_list* ip_list, char* pathname, char* urlname)
+{
+ struct ip_list* ip;
+ BIO* bio = NULL;
+ /* try random address first, and work through the list */
+ wipe_ip_usage(ip_list);
+ while( (ip = pick_random_ip(ip_list)) ) {
+ ip->used = 1;
+ bio = https_to_ip(ip, pathname, urlname);
+ if(bio) break;
+ }
+ if(!bio) {
+ if(verb) printf("could not fetch %s\n", pathname);
+ exit(0);
+ } else {
+ if(verb) printf("fetched %s (%d bytes)\n",
+ pathname, (int)BIO_ctrl_pending(bio));
+ }
+ return bio;
+}
+
+/** free up a downloaded file BIO */
+static void
+free_file_bio(BIO* bio)
+{
+ char* pp = NULL;
+ (void)BIO_reset(bio);
+ (void)BIO_get_mem_data(bio, &pp);
+ free(pp);
+ BIO_free(bio);
+}
+
+/** XML parse private data during the parse */
+struct xml_data {
+ /** the parser, reference */
+ XML_Parser parser;
+ /** the current tag; malloced; or NULL outside of tags */
+ char* tag;
+ /** current date to use during the parse */
+ time_t date;
+ /** number of keys usefully read in */
+ int num_keys;
+ /** the compiled anchors as DS records */
+ BIO* ds;
+
+ /** do we want to use this anchor? */
+ int use_key;
+ /** the current anchor: Zone */
+ BIO* czone;
+ /** the current anchor: KeyTag */
+ BIO* ctag;
+ /** the current anchor: Algorithm */
+ BIO* calgo;
+ /** the current anchor: DigestType */
+ BIO* cdigtype;
+ /** the current anchor: Digest*/
+ BIO* cdigest;
+};
+
+/** The BIO for the tag */
+static BIO*
+xml_selectbio(struct xml_data* data, const char* tag)
+{
+ BIO* b = NULL;
+ if(strcasecmp(tag, "KeyTag") == 0)
+ b = data->ctag;
+ else if(strcasecmp(tag, "Algorithm") == 0)
+ b = data->calgo;
+ else if(strcasecmp(tag, "DigestType") == 0)
+ b = data->cdigtype;
+ else if(strcasecmp(tag, "Digest") == 0)
+ b = data->cdigest;
+ return b;
+}
+
+/**
+ * XML handle character data, the data inside an element.
+ * @param userData: xml_data structure
+ * @param s: the character data. May not all be in one callback.
+ * NOT zero terminated.
+ * @param len: length of this part of the data.
+ */
+void
+xml_charhandle(void *userData, const XML_Char *s, int len)
+{
+ struct xml_data* data = (struct xml_data*)userData;
+ BIO* b = NULL;
+ /* skip characters outside of elements */
+ if(!data->tag)
+ return;
+ if(verb>=4) {
+ int i;
+ printf("%s%s charhandle: '",
+ data->use_key?"use ":"",
+ data->tag?data->tag:"none");
+ for(i=0; i<len; i++)
+ printf("%c", s[i]);
+ printf("'\n");
+ }
+ if(strcasecmp(data->tag, "Zone") == 0) {
+ if(BIO_write(data->czone, s, len) <= 0) {
+ if(verb) printf("out of memory in BIO_write\n");
+ exit(0);
+ }
+ return;
+ }
+ /* only store if key is used */
+ if(!data->use_key)
+ return;
+ b = xml_selectbio(data, data->tag);
+ if(b) {
+ if(BIO_write(b, s, len) <= 0) {
+ if(verb) printf("out of memory in BIO_write\n");
+ exit(0);
+ }
+ }
+}
+
+/**
+ * XML fetch value of particular attribute(by name) or NULL if not present.
+ * @param atts: attribute array (from xml_startelem).
+ * @param name: name of attribute to look for.
+ * @return the value or NULL. (ptr into atts).
+ */
+static const XML_Char*
+find_att(const XML_Char **atts, XML_Char* name)
+{
+ int i;
+ for(i=0; atts[i]; i+=2) {
+ if(strcasecmp(atts[i], name) == 0)
+ return atts[i+1];
+ }
+ return NULL;
+}
+
+/**
+ * XML convert DateTime element to time_t.
+ * [-]CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
+ * (with optional .ssssss fractional seconds)
+ * @param str: the string
+ * @return a time_t representation or 0 on failure.
+ */
+static time_t
+xml_convertdate(const char* str)
+{
+ time_t t = 0;
+ struct tm tm;
+ const char* s;
+ /* for this application, ignore minus in front;
+ * only positive dates are expected */
+ s = str;
+ if(s[0] == '-') s++;
+ memset(&tm, 0, sizeof(tm));
+ /* parse initial content of the string (lots of whitespace allowed) */
+ s = strptime(s, "%t%Y%t-%t%m%t-%t%d%tT%t%H%t:%t%M%t:%t%S%t", &tm);
+ if(!s) {
+ if(verb) printf("xml_convertdate parse failure %s\n", str);
+ return 0;
+ }
+ /* parse remainder of date string */
+ if(*s == '.') {
+ /* optional '.' and fractional seconds */
+ int frac = 0, n = 0;
+ if(sscanf(s+1, "%d%n", &frac, &n) < 1) {
+ if(verb) printf("xml_convertdate f failure %s\n", str);
+ return 0;
+ }
+ /* fraction is not used, time_t has second accuracy */
+ s++;
+ s+=n;
+ }
+ if(*s == 'Z' || *s == 'z') {
+ /* nothing to do for this */
+ s++;
+ } else if(*s == '+' || *s == '-') {
+ /* optional timezone spec: Z or +hh:mm or -hh:mm */
+ int hr = 0, mn = 0, n = 0;
+ if(sscanf(s+1, "%d:%d%n", &hr, &mn, &n) < 2) {
+ if(verb) printf("xml_convertdate tz failure %s\n", str);
+ return 0;
+ }
+ if(*s == '+') {
+ tm.tm_hour += hr;
+ tm.tm_min += mn;
+ } else {
+ tm.tm_hour -= hr;
+ tm.tm_min -= mn;
+ }
+ s++;
+ s += n;
+ }
+ if(*s != 0) {
+ /* not ended properly */
+ /* but ignore, (lenient) */
+ }
+
+ t = mktime(&tm);
+ if(t == (time_t)-1) {
+ if(verb) printf("xml_convertdate mktime failure\n");
+ return 0;
+ }
+ return t;
+}
+
+/**
+ * XML handle the KeyDigest start tag, check validity periods.
+ */
+static void
+handle_keydigest(struct xml_data* data, const XML_Char **atts)
+{
+ data->use_key = 0;
+ if(find_att(atts, "validFrom")) {
+ time_t from = xml_convertdate(find_att(atts, "validFrom"));
+ if(from == 0) {
+ if(verb) printf("error: xml cannot be parsed\n");
+ exit(0);
+ }
+ if(data->date < from)
+ return;
+ }
+ if(find_att(atts, "validUntil")) {
+ time_t until = xml_convertdate(find_att(atts, "validUntil"));
+ if(until == 0) {
+ if(verb) printf("error: xml cannot be parsed\n");
+ exit(0);
+ }
+ if(data->date > until)
+ return;
+ }
+ /* yes we want to use this key */
+ data->use_key = 1;
+ (void)BIO_reset(data->ctag);
+ (void)BIO_reset(data->calgo);
+ (void)BIO_reset(data->cdigtype);
+ (void)BIO_reset(data->cdigest);
+}
+
+/** See if XML element equals the zone name */
+static int
+xml_is_zone_name(BIO* zone, char* name)
+{
+ char buf[1024];
+ char* z = NULL;
+ long zlen;
+ (void)BIO_seek(zone, 0);
+ zlen = BIO_get_mem_data(zone, &z);
+ if(!zlen || !z) return 0;
+ /* zero terminate */
+ if(zlen >= (long)sizeof(buf)) return 0;
+ memmove(buf, z, (size_t)zlen);
+ buf[zlen] = 0;
+ /* compare */
+ return (strncasecmp(buf, name, strlen(name)) == 0);
+}
+
+/**
+ * XML start of element. This callback is called whenever an XML tag starts.
+ * XML_Char is UTF8.
+ * @param userData: the xml_data structure.
+ * @param name: the tag that starts.
+ * @param atts: array of strings, pairs of attr = value, ends with NULL.
+ * i.e. att[0]="att[1]" att[2]="att[3]" att[4]isNull
+ */
+static void
+xml_startelem(void *userData, const XML_Char *name, const XML_Char **atts)
+{
+ struct xml_data* data = (struct xml_data*)userData;
+ BIO* b;
+ if(verb>=4) printf("xml tag start '%s'\n", name);
+ free(data->tag);
+ data->tag = strdup(name);
+ if(!data->tag) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ if(verb>=4) {
+ int i;
+ for(i=0; atts[i]; i+=2) {
+ printf(" %s='%s'\n", atts[i], atts[i+1]);
+ }
+ }
+ /* handle attributes to particular types */
+ if(strcasecmp(name, "KeyDigest") == 0) {
+ handle_keydigest(data, atts);
+ return;
+ } else if(strcasecmp(name, "Zone") == 0) {
+ (void)BIO_reset(data->czone);
+ return;
+ }
+
+ /* for other types we prepare to pick up the data */
+ if(!data->use_key)
+ return;
+ b = xml_selectbio(data, data->tag);
+ if(b) {
+ /* empty it */
+ (void)BIO_reset(b);
+ }
+}
+
+/** Append str to bio */
+static void
+xml_append_str(BIO* b, const char* s)
+{
+ if(BIO_write(b, s, (int)strlen(s)) <= 0) {
+ if(verb) printf("out of memory in BIO_write\n");
+ exit(0);
+ }
+}
+
+/** Append bio to bio */
+static void
+xml_append_bio(BIO* b, BIO* a)
+{
+ char* z = NULL;
+ long i, len;
+ (void)BIO_seek(a, 0);
+ len = BIO_get_mem_data(a, &z);
+ if(!len || !z) {
+ if(verb) printf("out of memory in BIO_write\n");
+ exit(0);
+ }
+ /* remove newlines in the data here */
+ for(i=0; i<len; i++) {
+ if(z[i] == '\r' || z[i] == '\n')
+ z[i] = ' ';
+ }
+ /* write to BIO */
+ if(BIO_write(b, z, len) <= 0) {
+ if(verb) printf("out of memory in BIO_write\n");
+ exit(0);
+ }
+}
+
+/** write the parsed xml-DS to the DS list */
+static void
+xml_append_ds(struct xml_data* data)
+{
+ /* write DS to accumulated DS */
+ xml_append_str(data->ds, ". IN DS ");
+ xml_append_bio(data->ds, data->ctag);
+ xml_append_str(data->ds, " ");
+ xml_append_bio(data->ds, data->calgo);
+ xml_append_str(data->ds, " ");
+ xml_append_bio(data->ds, data->cdigtype);
+ xml_append_str(data->ds, " ");
+ xml_append_bio(data->ds, data->cdigest);
+ xml_append_str(data->ds, "\n");
+ data->num_keys++;
+}
+
+/**
+ * XML end of element. This callback is called whenever an XML tag ends.
+ * XML_Char is UTF8.
+ * @param userData: the xml_data structure
+ * @param name: the tag that ends.
+ */
+static void
+xml_endelem(void *userData, const XML_Char *name)
+{
+ struct xml_data* data = (struct xml_data*)userData;
+ if(verb>=4) printf("xml tag end '%s'\n", name);
+ free(data->tag);
+ data->tag = NULL;
+ if(strcasecmp(name, "KeyDigest") == 0) {
+ if(data->use_key)
+ xml_append_ds(data);
+ data->use_key = 0;
+ } else if(strcasecmp(name, "Zone") == 0) {
+ if(!xml_is_zone_name(data->czone, ".")) {
+ if(verb) printf("xml not for the right zone\n");
+ exit(0);
+ }
+ }
+}
+
+/**
+ * XML parser setup of the callbacks for the tags
+ */
+static void
+xml_parse_setup(XML_Parser parser, struct xml_data* data, time_t now)
+{
+ char buf[1024];
+ memset(data, 0, sizeof(*data));
+ XML_SetUserData(parser, data);
+ data->parser = parser;
+ data->date = now;
+ data->ds = BIO_new(BIO_s_mem());
+ data->ctag = BIO_new(BIO_s_mem());
+ data->czone = BIO_new(BIO_s_mem());
+ data->calgo = BIO_new(BIO_s_mem());
+ data->cdigtype = BIO_new(BIO_s_mem());
+ data->cdigest = BIO_new(BIO_s_mem());
+ if(!data->ds || !data->ctag || !data->calgo || !data->czone ||
+ !data->cdigtype || !data->cdigest) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ snprintf(buf, sizeof(buf), "; created by unbound-anchor on %s",
+ ctime(&now));
+ if(BIO_write(data->ds, buf, (int)strlen(buf)) <= 0) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ XML_SetElementHandler(parser, xml_startelem, xml_endelem);
+ XML_SetCharacterDataHandler(parser, xml_charhandle);
+}
+
+/**
+ * Perform XML parsing of the root-anchors file
+ * Its format description can be read here
+ * https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.txt
+ * It uses libexpat.
+ * @param xml: BIO with xml data.
+ * @param now: the current time for checking DS validity periods.
+ * @return memoryBIO with the DS data in zone format.
+ * or NULL if the zone is insecure.
+ * (It exit()s on error)
+ */
+static BIO*
+xml_parse(BIO* xml, time_t now)
+{
+ char* pp;
+ int len;
+ XML_Parser parser;
+ struct xml_data data;
+
+ parser = XML_ParserCreate(NULL);
+ if(!parser) {
+ if(verb) printf("could not XML_ParserCreate\n");
+ exit(0);
+ }
+
+ /* setup callbacks */
+ xml_parse_setup(parser, &data, now);
+
+ /* parse it */
+ (void)BIO_reset(xml);
+ len = (int)BIO_get_mem_data(xml, &pp);
+ if(!len || !pp) {
+ if(verb) printf("out of memory\n");
+ exit(0);
+ }
+ if(!XML_Parse(parser, pp, len, 1 /*isfinal*/ )) {
+ const char *e = XML_ErrorString(XML_GetErrorCode(parser));
+ if(verb) printf("XML_Parse failure %s\n", e?e:"");
+ exit(0);
+ }
+
+ /* parsed */
+ if(verb) printf("XML was parsed successfully, %d keys\n",
+ data.num_keys);
+ free(data.tag);
+ XML_ParserFree(parser);
+
+ if(verb >= 4) {
+ char* pp = NULL;
+ int len;
+ (void)BIO_seek(data.ds, 0);
+ len = BIO_get_mem_data(data.ds, &pp);
+ printf("got DS bio %d: '", len);
+ if(!fwrite(pp, (size_t)len, 1, stdout))
+ /* compilers do not allow us to ignore fwrite .. */
+ fprintf(stderr, "error writing to stdout\n");
+ printf("'\n");
+ }
+ BIO_free(data.czone);
+ BIO_free(data.ctag);
+ BIO_free(data.calgo);
+ BIO_free(data.cdigtype);
+ BIO_free(data.cdigest);
+
+ if(data.num_keys == 0) {
+ /* the root zone seems to have gone insecure */
+ BIO_free(data.ds);
+ return NULL;
+ } else {
+ return data.ds;
+ }
+}
+
+/** verify a PKCS7 signature, false on failure */
+static int
+verify_p7sig(BIO* data, BIO* p7s, STACK_OF(X509)* trust)
+{
+ PKCS7* p7;
+ X509_STORE *store = X509_STORE_new();
+ int secure = 0;
+ int i;
+#ifdef X509_V_FLAG_CHECK_SS_SIGNATURE
+ X509_VERIFY_PARAM* param = X509_VERIFY_PARAM_new();
+ if(!param) {
+ if(verb) printf("out of memory\n");
+ X509_STORE_free(store);
+ return 0;
+ }
+ /* do the selfcheck on the root certificate; it checks that the
+ * input is valid */
+ X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CHECK_SS_SIGNATURE);
+ if(store) X509_STORE_set1_param(store, param);
+#endif
+ if(!store) {
+ if(verb) printf("out of memory\n");
+#ifdef X509_V_FLAG_CHECK_SS_SIGNATURE
+ X509_VERIFY_PARAM_free(param);
+#endif
+ return 0;
+ }
+
+ (void)BIO_reset(p7s);
+ (void)BIO_reset(data);
+
+ /* convert p7s to p7 (the signature) */
+ p7 = d2i_PKCS7_bio(p7s, NULL);
+ if(!p7) {
+ if(verb) printf("could not parse p7s signature file\n");
+ X509_STORE_free(store);
+ return 0;
+ }
+ if(verb >= 2) printf("parsed the PKCS7 signature\n");
+
+ /* convert trust to trusted certificate store */
+ for(i=0; i<sk_X509_num(trust); i++) {
+ if(!X509_STORE_add_cert(store, sk_X509_value(trust, i))) {
+ if(verb) printf("failed X509_STORE_add_cert\n");
+ X509_STORE_free(store);
+ PKCS7_free(p7);
+ return 0;
+ }
+ }
+ if(verb >= 2) printf("setup the X509_STORE\n");
+
+ if(PKCS7_verify(p7, NULL, store, data, NULL, 0) == 1) {
+ secure = 1;
+ if(verb) printf("the PKCS7 signature verified\n");
+ } else {
+ if(verb) {
+ ERR_print_errors_fp(stdout);
+ }
+ }
+
+ X509_STORE_free(store);
+ PKCS7_free(p7);
+ return secure;
+}
+
+/** write unsigned root anchor file, a 5011 revoked tp */
+static void
+write_unsigned_root(char* root_anchor_file)
+{
+ FILE* out;
+ time_t now = time(NULL);
+ out = fopen(root_anchor_file, "w");
+ if(!out) {
+ if(verb) printf("%s: %s\n", root_anchor_file, strerror(errno));
+ return;
+ }
+ if(fprintf(out, "; autotrust trust anchor file\n"
+ ";;REVOKED\n"
+ ";;id: . 1\n"
+ "; This file was written by unbound-anchor on %s"
+ "; It indicates that the root does not use DNSSEC\n"
+ "; to restart DNSSEC overwrite this file with a\n"
+ "; valid trustanchor or (empty-it and run unbound-anchor)\n"
+ , ctime(&now)) < 0) {
+ if(verb) printf("failed to write 'unsigned' to %s\n",
+ root_anchor_file);
+ if(verb && errno != 0) printf("%s\n", strerror(errno));
+ }
+ fclose(out);
+}
+
+/** write root anchor file */
+static void
+write_root_anchor(char* root_anchor_file, BIO* ds)
+{
+ char* pp = NULL;
+ int len;
+ FILE* out;
+ (void)BIO_seek(ds, 0);
+ len = BIO_get_mem_data(ds, &pp);
+ if(!len || !pp) {
+ if(verb) printf("out of memory\n");
+ return;
+ }
+ out = fopen(root_anchor_file, "w");
+ if(!out) {
+ if(verb) printf("%s: %s\n", root_anchor_file, strerror(errno));
+ return;
+ }
+ if(fwrite(pp, (size_t)len, 1, out) != 1) {
+ if(verb) printf("failed to write all data to %s\n",
+ root_anchor_file);
+ if(verb && errno != 0) printf("%s\n", strerror(errno));
+ }
+ fclose(out);
+}
+
+/** Perform the verification and update of the trustanchor file */
+static void
+verify_and_update_anchor(char* root_anchor_file, BIO* xml, BIO* p7s,
+ STACK_OF(X509)* cert)
+{
+ BIO* ds;
+
+ /* verify xml file */
+ if(!verify_p7sig(xml, p7s, cert)) {
+ printf("the PKCS7 signature failed\n");
+ exit(0);
+ }
+
+ /* parse the xml file into DS records */
+ ds = xml_parse(xml, time(NULL));
+ if(!ds) {
+ /* the root zone is unsigned now */
+ write_unsigned_root(root_anchor_file);
+ } else {
+ /* reinstate 5011 tracking */
+ write_root_anchor(root_anchor_file, ds);
+ }
+ BIO_free(ds);
+}
+
+#ifdef USE_WINSOCK
+static void do_wsa_cleanup(void) { WSACleanup(); }
+#endif
+
+/** perform actual certupdate work */
+static int
+do_certupdate(char* root_anchor_file, char* root_cert_file,
+ char* urlname, char* xmlname, char* p7sname,
+ char* res_conf, char* root_hints, char* debugconf,
+ int ip4only, int ip6only, int port, struct ub_result* dnskey)
+{
+ STACK_OF(X509)* cert;
+ BIO *xml, *p7s;
+ struct ip_list* ip_list = NULL;
+
+ /* read pem file or provide builtin */
+ cert = read_cert_or_builtin(root_cert_file);
+
+ /* lookup A, AAAA for the urlname (or parse urlname if IP address) */
+ ip_list = resolve_name(urlname, port, res_conf, root_hints, debugconf,
+ ip4only, ip6only);
+
+#ifdef USE_WINSOCK
+ if(1) { /* libunbound finished, startup WSA for the https connection */
+ WSADATA wsa_data;
+ int r;
+ if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0) {
+ if(verb) printf("WSAStartup failed: %s\n",
+ wsa_strerror(r));
+ exit(0);
+ }
+ atexit(&do_wsa_cleanup);
+ }
+#endif
+
+ /* fetch the necessary files over HTTPS */
+ xml = https(ip_list, xmlname, urlname);
+ p7s = https(ip_list, p7sname, urlname);
+
+ /* verify and update the root anchor */
+ verify_and_update_anchor(root_anchor_file, xml, p7s, cert);
+ if(verb) printf("success: the anchor has been updated "
+ "using the cert\n");
+
+ free_file_bio(xml);
+ free_file_bio(p7s);
+#ifndef S_SPLINT_S
+ sk_X509_pop_free(cert, X509_free);
+#endif
+ ub_resolve_free(dnskey);
+ ip_list_free(ip_list);
+ return 1;
+}
+
+/**
+ * Try to read the root RFC5011 autotrust anchor file,
+ * @param file: filename.
+ * @return:
+ * 0 if does not exist or empty
+ * 1 if trust-point-revoked-5011
+ * 2 if it is OK.
+ */
+static int
+try_read_anchor(char* file)
+{
+ int empty = 1;
+ char line[10240];
+ char* p;
+ FILE* in = fopen(file, "r");
+ if(!in) {
+ /* only if the file does not exist, can we fix it */
+ if(errno != ENOENT) {
+ if(verb) printf("%s: %s\n", file, strerror(errno));
+ if(verb) printf("error: cannot access the file\n");
+ exit(0);
+ }
+ if(verb) printf("%s does not exist\n", file);
+ return 0;
+ }
+ while(fgets(line, (int)sizeof(line), in)) {
+ line[sizeof(line)-1] = 0;
+ if(strncmp(line, ";;REVOKED", 9) == 0) {
+ fclose(in);
+ if(verb) printf("%s : the trust point is revoked\n"
+ "and the zone is considered unsigned.\n"
+ "if you wish to re-enable, delete the file\n",
+ file);
+ return 1;
+ }
+ p=line;
+ while(*p == ' ' || *p == '\t')
+ p++;
+ if(p[0]==0 || p[0]=='\n' || p[0]==';') continue;
+ /* this line is a line of content */
+ empty = 0;
+ }
+ fclose(in);
+ if(empty) {
+ if(verb) printf("%s is empty\n", file);
+ return 0;
+ }
+ if(verb) printf("%s has content\n", file);
+ return 2;
+}
+
+/** Write the builtin root anchor to a file */
+static void
+write_builtin_anchor(char* file)
+{
+ const char* builtin_root_anchor = get_builtin_ds();
+ FILE* out = fopen(file, "w");
+ if(!out) {
+ if(verb) printf("%s: %s\n", file, strerror(errno));
+ if(verb) printf(" could not write builtin anchor\n");
+ return;
+ }
+ if(!fwrite(builtin_root_anchor, strlen(builtin_root_anchor), 1, out)) {
+ if(verb) printf("%s: %s\n", file, strerror(errno));
+ if(verb) printf(" could not complete write builtin anchor\n");
+ }
+ fclose(out);
+}
+
+/**
+ * Check the root anchor file.
+ * If does not exist, provide builtin and write file.
+ * If empty, provide builtin and write file.
+ * If trust-point-revoked-5011 file: make the program exit.
+ * @param root_anchor_file: filename of the root anchor.
+ * @param used_builtin: set to 1 if the builtin is written.
+ * @return 0 if trustpoint is insecure, 1 on success. Exit on failure.
+ */
+static int
+provide_builtin(char* root_anchor_file, int* used_builtin)
+{
+ /* try to read it */
+ switch(try_read_anchor(root_anchor_file))
+ {
+ case 0: /* no exist or empty */
+ write_builtin_anchor(root_anchor_file);
+ *used_builtin = 1;
+ break;
+ case 1: /* revoked tp */
+ return 0;
+ case 2: /* it is fine */
+ default:
+ break;
+ }
+ return 1;
+}
+
+/**
+ * add an autotrust anchor for the root to the context
+ */
+static void
+add_5011_probe_root(struct ub_ctx* ctx, char* root_anchor_file)
+{
+ int r;
+ r = ub_ctx_set_option(ctx, "auto-trust-anchor-file:", root_anchor_file);
+ if(r) {
+ if(verb) printf("add 5011 probe to ctx: %s\n", ub_strerror(r));
+ ub_ctx_delete(ctx);
+ exit(0);
+ }
+}
+
+/**
+ * Prime the root key and return the result. Exit on error.
+ * @param ctx: the unbound context to perform the priming with.
+ * @return: the result of the prime, on error it exit()s.
+ */
+static struct ub_result*
+prime_root_key(struct ub_ctx* ctx)
+{
+ struct ub_result* res = NULL;
+ int r;
+ r = ub_resolve(ctx, ".", LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN, &res);
+ if(r) {
+ if(verb) printf("resolve DNSKEY: %s\n", ub_strerror(r));
+ ub_ctx_delete(ctx);
+ exit(0);
+ }
+ if(!res) {
+ if(verb) printf("out of memory\n");
+ ub_ctx_delete(ctx);
+ exit(0);
+ }
+ return res;
+}
+
+/** see if ADDPEND keys exist in autotrust file (if possible) */
+static int
+read_if_pending_keys(char* file)
+{
+ FILE* in = fopen(file, "r");
+ char line[8192];
+ if(!in) {
+ if(verb>=2) printf("%s: %s\n", file, strerror(errno));
+ return 0;
+ }
+ while(fgets(line, (int)sizeof(line), in)) {
+ if(line[0]==';') continue;
+ if(strstr(line, "[ ADDPEND ]")) {
+ fclose(in);
+ if(verb) printf("RFC5011-state has ADDPEND keys\n");
+ return 1;
+ }
+ }
+ fclose(in);
+ return 0;
+}
+
+/** read last successful probe time from autotrust file (if possible) */
+static int32_t
+read_last_success_time(char* file)
+{
+ FILE* in = fopen(file, "r");
+ char line[1024];
+ if(!in) {
+ if(verb) printf("%s: %s\n", file, strerror(errno));
+ return 0;
+ }
+ while(fgets(line, (int)sizeof(line), in)) {
+ if(strncmp(line, ";;last_success: ", 16) == 0) {
+ char* e;
+ time_t x = (unsigned int)strtol(line+16, &e, 10);
+ fclose(in);
+ if(line+16 == e) {
+ if(verb) printf("failed to parse "
+ "last_success probe time\n");
+ return 0;
+ }
+ if(verb) printf("last successful probe: %s", ctime(&x));
+ return (int32_t)x;
+ }
+ }
+ fclose(in);
+ if(verb) printf("no last_success probe time in anchor file\n");
+ return 0;
+}
+
+/**
+ * Read autotrust 5011 probe file and see if the date
+ * compared to the current date allows a certupdate.
+ * If the last successful probe was recent then 5011 cannot be behind,
+ * and the failure cannot be solved with a certupdate.
+ * The debugconf is to validation-override the date for testing.
+ * @param root_anchor_file: filename of root key
+ * @return true if certupdate is ok.
+ */
+static int
+probe_date_allows_certupdate(char* root_anchor_file)
+{
+ int has_pending_keys = read_if_pending_keys(root_anchor_file);
+ int32_t last_success = read_last_success_time(root_anchor_file);
+ int32_t now = (int32_t)time(NULL);
+ int32_t leeway = 30 * 24 * 3600; /* 30 days leeway */
+ /* if the date is before 2010-07-15:00.00.00 then the root has not
+ * been signed yet, and thus we refuse to take action. */
+ if(time(NULL) < xml_convertdate("2010-07-15T00:00:00")) {
+ if(verb) printf("the date is before the root was first signed,"
+ " please correct the clock\n");
+ return 0;
+ }
+ if(last_success == 0)
+ return 1; /* no probe time */
+ if(has_pending_keys)
+ return 1; /* key in ADDPEND state, a previous probe has
+ inserted that, and it was present in all recent probes,
+ but it has not become active. The 30 day timer may not have
+ expired, but we know(for sure) there is a rollover going on.
+ If we only managed to pickup the new key on its last day
+ of announcement (for example) this can happen. */
+ if(now - last_success < 0) {
+ if(verb) printf("the last successful probe is in the future,"
+ " clock was modified\n");
+ return 0;
+ }
+ if(now - last_success >= leeway) {
+ if(verb) printf("the last successful probe was more than 30 "
+ "days ago\n");
+ return 1;
+ }
+ if(verb) printf("the last successful probe is recent\n");
+ return 0;
+}
+
+/** perform the unbound-anchor work */
+static int
+do_root_update_work(char* root_anchor_file, char* root_cert_file,
+ char* urlname, char* xmlname, char* p7sname,
+ char* res_conf, char* root_hints, char* debugconf,
+ int ip4only, int ip6only, int force, int port)
+{
+ struct ub_ctx* ctx;
+ struct ub_result* dnskey;
+ int used_builtin = 0;
+
+ /* see if builtin rootanchor needs to be provided, or if
+ * rootanchor is 'revoked-trust-point' */
+ if(!provide_builtin(root_anchor_file, &used_builtin))
+ return 0;
+
+ /* make unbound context with 5011-probe for root anchor,
+ * and probe . DNSKEY */
+ ctx = create_unbound_context(res_conf, root_hints, debugconf,
+ ip4only, ip6only);
+ add_5011_probe_root(ctx, root_anchor_file);
+ dnskey = prime_root_key(ctx);
+ ub_ctx_delete(ctx);
+
+ /* if secure: exit */
+ if(dnskey->secure && !force) {
+ if(verb) printf("success: the anchor is ok\n");
+ ub_resolve_free(dnskey);
+ return used_builtin;
+ }
+ if(force && verb) printf("debug cert update forced\n");
+
+ /* if not (and NOERROR): check date and do certupdate */
+ if((dnskey->rcode == 0 &&
+ probe_date_allows_certupdate(root_anchor_file)) || force) {
+ if(do_certupdate(root_anchor_file, root_cert_file, urlname,
+ xmlname, p7sname, res_conf, root_hints, debugconf,
+ ip4only, ip6only, port, dnskey))
+ return 1;
+ return used_builtin;
+ }
+ if(verb) printf("fail: the anchor is NOT ok and could not be fixed\n");
+ ub_resolve_free(dnskey);
+ return used_builtin;
+}
+
+/** 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-anchor */
+int main(int argc, char* argv[])
+{
+ int c;
+ char* root_anchor_file = ROOT_ANCHOR_FILE;
+ char* root_cert_file = ROOT_CERT_FILE;
+ char* urlname = URLNAME;
+ char* xmlname = XMLNAME;
+ char* p7sname = P7SNAME;
+ char* res_conf = NULL;
+ char* root_hints = NULL;
+ char* debugconf = NULL;
+ int dolist=0, ip4only=0, ip6only=0, force=0, port = HTTPS_PORT;
+ /* parse the options */
+ while( (c=getopt(argc, argv, "46C:FP:a:c:f:hlr:s:u:vx:")) != -1) {
+ switch(c) {
+ case 'l':
+ dolist = 1;
+ break;
+ case '4':
+ ip4only = 1;
+ break;
+ case '6':
+ ip6only = 1;
+ break;
+ case 'a':
+ root_anchor_file = optarg;
+ break;
+ case 'c':
+ root_cert_file = optarg;
+ break;
+ case 'u':
+ urlname = optarg;
+ break;
+ case 'x':
+ xmlname = optarg;
+ break;
+ case 's':
+ p7sname = optarg;
+ break;
+ case 'f':
+ res_conf = optarg;
+ break;
+ case 'r':
+ root_hints = optarg;
+ break;
+ case 'C':
+ debugconf = optarg;
+ break;
+ case 'F':
+ force = 1;
+ break;
+ case 'P':
+ port = atoi(optarg);
+ break;
+ case 'v':
+ verb++;
+ break;
+ case '?':
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if(argc != 0)
+ usage();
+
+ ERR_load_crypto_strings();
+ ERR_load_SSL_strings();
+ OpenSSL_add_all_algorithms();
+ (void)SSL_library_init();
+
+ if(dolist) do_list_builtin();
+
+ return do_root_update_work(root_anchor_file, root_cert_file, urlname,
+ xmlname, p7sname, res_conf, root_hints, debugconf, ip4only,
+ ip6only, force, port);
+}
diff --git a/smallapp/unbound-checkconf.c b/smallapp/unbound-checkconf.c
new file mode 100644
index 000000000000..c73d8bdc7c9c
--- /dev/null
+++ b/smallapp/unbound-checkconf.c
@@ -0,0 +1,517 @@
+/*
+ * checkconf/unbound-checkconf.c - config file checker for unbound.conf file.
+ *
+ * Copyright (c) 2007, 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 config checker checks for syntax and other errors in the unbound.conf
+ * file, and can be used to check for errors before the server is started
+ * or sigHUPped.
+ * Exit status 1 means an error.
+ */
+
+#include "config.h"
+#include "util/log.h"
+#include "util/config_file.h"
+#include "util/module.h"
+#include "util/net_help.h"
+#include "util/regional.h"
+#include "iterator/iterator.h"
+#include "iterator/iter_fwd.h"
+#include "iterator/iter_hints.h"
+#include "validator/validator.h"
+#include "services/localzone.h"
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_GLOB_H
+#include <glob.h>
+#endif
+#ifdef WITH_PYTHONMODULE
+#include "pythonmod/pythonmod.h"
+#endif
+
+/** Give checkconf usage, and exit (1). */
+static void
+usage()
+{
+ printf("Usage: unbound-checkconf [file]\n");
+ printf(" Checks unbound configuration file for errors.\n");
+ printf("file if omitted %s is used.\n", CONFIGFILE);
+ printf("-o option print value of option to stdout.\n");
+ printf("-h show this usage help.\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);
+}
+
+/**
+ * Print given option to stdout
+ * @param cfg: config
+ * @param opt: option name without trailing :.
+ * This is different from config_set_option.
+ */
+static void
+print_option(struct config_file* cfg, const char* opt)
+{
+ if(!config_get_option(cfg, opt, config_print_func, stdout))
+ fatal_exit("cannot print option '%s'", opt);
+}
+
+/** check if module works with config */
+static void
+check_mod(struct config_file* cfg, struct module_func_block* fb)
+{
+ struct module_env env;
+ memset(&env, 0, sizeof(env));
+ env.cfg = cfg;
+ env.scratch = regional_create();
+ env.scratch_buffer = ldns_buffer_new(BUFSIZ);
+ if(!env.scratch || !env.scratch_buffer)
+ fatal_exit("out of memory");
+ if(!(*fb->init)(&env, 0)) {
+ fatal_exit("bad config for %s module", fb->name);
+ }
+ (*fb->deinit)(&env, 0);
+ ldns_buffer_free(env.scratch_buffer);
+ regional_destroy(env.scratch);
+}
+
+/** check localzones */
+static void
+localzonechecks(struct config_file* cfg)
+{
+ struct local_zones* zs;
+ if(!(zs = local_zones_create()))
+ fatal_exit("out of memory");
+ if(!local_zones_apply_cfg(zs, cfg))
+ fatal_exit("failed local-zone, local-data configuration");
+ local_zones_delete(zs);
+}
+
+/** emit warnings for IP in hosts */
+static void
+warn_hosts(const char* typ, struct config_stub* list)
+{
+ struct sockaddr_storage a;
+ socklen_t alen;
+ struct config_stub* s;
+ struct config_strlist* h;
+ for(s=list; s; s=s->next) {
+ for(h=s->hosts; h; h=h->next) {
+ if(extstrtoaddr(h->str, &a, &alen)) {
+ fprintf(stderr, "unbound-checkconf: warning:"
+ " %s %s: \"%s\" is an IP%s address, "
+ "and when looked up as a host name "
+ "during use may not resolve.\n",
+ s->name, typ, h->str,
+ addr_is_ip6(&a, alen)?"6":"4");
+ }
+ }
+ }
+}
+
+/** check interface strings */
+static void
+interfacechecks(struct config_file* cfg)
+{
+ struct sockaddr_storage a;
+ socklen_t alen;
+ int i, j;
+ for(i=0; i<cfg->num_ifs; i++) {
+ if(!extstrtoaddr(cfg->ifs[i], &a, &alen)) {
+ fatal_exit("cannot parse interface specified as '%s'",
+ cfg->ifs[i]);
+ }
+ for(j=0; j<cfg->num_ifs; j++) {
+ if(i!=j && strcmp(cfg->ifs[i], cfg->ifs[j])==0)
+ fatal_exit("interface: %s present twice, "
+ "cannot bind same ports twice.",
+ cfg->ifs[i]);
+ }
+ }
+ for(i=0; i<cfg->num_out_ifs; i++) {
+ if(!ipstrtoaddr(cfg->out_ifs[i], UNBOUND_DNS_PORT,
+ &a, &alen)) {
+ fatal_exit("cannot parse outgoing-interface "
+ "specified as '%s'", cfg->out_ifs[i]);
+ }
+ for(j=0; j<cfg->num_out_ifs; j++) {
+ if(i!=j && strcmp(cfg->out_ifs[i], cfg->out_ifs[j])==0)
+ fatal_exit("outgoing-interface: %s present "
+ "twice, cannot bind same ports twice.",
+ cfg->out_ifs[i]);
+ }
+ }
+}
+
+/** check acl ips */
+static void
+aclchecks(struct config_file* cfg)
+{
+ int d;
+ struct sockaddr_storage a;
+ socklen_t alen;
+ struct config_str2list* acl;
+ for(acl=cfg->acls; acl; acl = acl->next) {
+ if(!netblockstrtoaddr(acl->str, UNBOUND_DNS_PORT, &a, &alen,
+ &d)) {
+ fatal_exit("cannot parse access control address %s %s",
+ acl->str, acl->str2);
+ }
+ }
+}
+
+/** true if fname is a file */
+static int
+is_file(const char* fname)
+{
+ struct stat buf;
+ if(stat(fname, &buf) < 0) {
+ if(errno==EACCES) {
+ printf("warning: no search permission for one of the directories in path: %s\n", fname);
+ return 1;
+ }
+ perror(fname);
+ return 0;
+ }
+ if(S_ISDIR(buf.st_mode)) {
+ printf("%s is not a file\n", fname);
+ return 0;
+ }
+ return 1;
+}
+
+/** true if fname is a directory */
+static int
+is_dir(const char* fname)
+{
+ struct stat buf;
+ if(stat(fname, &buf) < 0) {
+ if(errno==EACCES) {
+ printf("warning: no search permission for one of the directories in path: %s\n", fname);
+ return 1;
+ }
+ perror(fname);
+ return 0;
+ }
+ if(!(S_ISDIR(buf.st_mode))) {
+ printf("%s is not a directory\n", fname);
+ return 0;
+ }
+ return 1;
+}
+
+/** get base dir of a fname */
+static char*
+basedir(char* fname)
+{
+ char* rev;
+ if(!fname) fatal_exit("out of memory");
+ rev = strrchr(fname, '/');
+ if(!rev) return NULL;
+ if(fname == rev) return NULL;
+ rev[0] = 0;
+ return fname;
+}
+
+/** check chroot for a file string */
+static void
+check_chroot_string(const char* desc, char** ss,
+ const char* chrootdir, struct config_file* cfg)
+{
+ char* str = *ss;
+ if(str && str[0]) {
+ *ss = fname_after_chroot(str, cfg, 1);
+ if(!*ss) fatal_exit("out of memory");
+ if(!is_file(*ss)) {
+ if(chrootdir && chrootdir[0])
+ fatal_exit("%s: \"%s\" does not exist in "
+ "chrootdir %s", desc, str, chrootdir);
+ else
+ fatal_exit("%s: \"%s\" does not exist",
+ desc, str);
+ }
+ /* put in a new full path for continued checking */
+ free(str);
+ }
+}
+
+/** check file list, every file must be inside the chroot location */
+static void
+check_chroot_filelist(const char* desc, struct config_strlist* list,
+ const char* chrootdir, struct config_file* cfg)
+{
+ struct config_strlist* p;
+ for(p=list; p; p=p->next) {
+ check_chroot_string(desc, &p->str, chrootdir, cfg);
+ }
+}
+
+/** check file list, with wildcard processing */
+static void
+check_chroot_filelist_wild(const char* desc, struct config_strlist* list,
+ const char* chrootdir, struct config_file* cfg)
+{
+ struct config_strlist* p;
+ for(p=list; p; p=p->next) {
+#ifdef HAVE_GLOB
+ if(strchr(p->str, '*') || strchr(p->str, '[') ||
+ strchr(p->str, '?') || strchr(p->str, '{') ||
+ strchr(p->str, '~')) {
+ char* s = p->str;
+ /* adjust whole pattern for chroot and check later */
+ p->str = fname_after_chroot(p->str, cfg, 1);
+ free(s);
+ } else
+#endif /* HAVE_GLOB */
+ check_chroot_string(desc, &p->str, chrootdir, cfg);
+ }
+}
+
+/** check configuration for errors */
+static void
+morechecks(struct config_file* cfg, const char* fname)
+{
+ warn_hosts("stub-host", cfg->stubs);
+ warn_hosts("forward-host", cfg->forwards);
+ interfacechecks(cfg);
+ aclchecks(cfg);
+
+ if(cfg->verbosity < 0)
+ fatal_exit("verbosity value < 0");
+ if(cfg->num_threads <= 0 || cfg->num_threads > 10000)
+ fatal_exit("num_threads value weird");
+ if(!cfg->do_ip4 && !cfg->do_ip6)
+ fatal_exit("ip4 and ip6 are both disabled, pointless");
+ if(!cfg->do_udp && !cfg->do_tcp)
+ fatal_exit("udp and tcp are both disabled, pointless");
+ if(cfg->edns_buffer_size > cfg->msg_buffer_size)
+ fatal_exit("edns-buffer-size larger than msg-buffer-size, "
+ "answers will not fit in processing buffer");
+
+ if(cfg->chrootdir && cfg->chrootdir[0] &&
+ cfg->chrootdir[strlen(cfg->chrootdir)-1] == '/')
+ fatal_exit("chootdir %s has trailing slash '/' please remove.",
+ cfg->chrootdir);
+ if(cfg->chrootdir && cfg->chrootdir[0] &&
+ !is_dir(cfg->chrootdir)) {
+ fatal_exit("bad chroot directory");
+ }
+ if(cfg->chrootdir && cfg->chrootdir[0]) {
+ char buf[10240];
+ buf[0] = 0;
+ if(fname[0] != '/') {
+ if(getcwd(buf, sizeof(buf)) == NULL)
+ fatal_exit("getcwd: %s", strerror(errno));
+ strncat(buf, "/", sizeof(buf)-strlen(buf)-1);
+ }
+ strncat(buf, fname, sizeof(buf)-strlen(buf)-1);
+ if(strncmp(buf, cfg->chrootdir, strlen(cfg->chrootdir)) != 0)
+ fatal_exit("config file %s is not inside chroot %s",
+ buf, cfg->chrootdir);
+ }
+ if(cfg->directory && cfg->directory[0]) {
+ char* ad = fname_after_chroot(cfg->directory, cfg, 0);
+ if(!ad) fatal_exit("out of memory");
+ if(!is_dir(ad)) fatal_exit("bad chdir directory");
+ free(ad);
+ }
+ if( (cfg->chrootdir && cfg->chrootdir[0]) ||
+ (cfg->directory && cfg->directory[0])) {
+ if(cfg->pidfile && cfg->pidfile[0]) {
+ char* ad = (cfg->pidfile[0]=='/')?strdup(cfg->pidfile):
+ fname_after_chroot(cfg->pidfile, cfg, 1);
+ char* bd = basedir(ad);
+ if(bd && !is_dir(bd))
+ fatal_exit("pidfile directory does not exist");
+ free(ad);
+ }
+ if(cfg->logfile && cfg->logfile[0]) {
+ char* ad = fname_after_chroot(cfg->logfile, cfg, 1);
+ char* bd = basedir(ad);
+ if(bd && !is_dir(bd))
+ fatal_exit("logfile directory does not exist");
+ free(ad);
+ }
+ }
+
+ check_chroot_filelist("file with root-hints",
+ cfg->root_hints, cfg->chrootdir, cfg);
+ check_chroot_filelist("trust-anchor-file",
+ cfg->trust_anchor_file_list, cfg->chrootdir, cfg);
+ check_chroot_filelist("auto-trust-anchor-file",
+ cfg->auto_trust_anchor_file_list, cfg->chrootdir, cfg);
+ check_chroot_filelist_wild("trusted-keys-file",
+ cfg->trusted_keys_file_list, cfg->chrootdir, cfg);
+ check_chroot_string("dlv-anchor-file", &cfg->dlv_anchor_file,
+ cfg->chrootdir, cfg);
+ /* remove chroot setting so that modules are not stripping pathnames*/
+ free(cfg->chrootdir);
+ cfg->chrootdir = NULL;
+
+ if(strcmp(cfg->module_conf, "iterator") != 0
+ && strcmp(cfg->module_conf, "validator iterator") != 0
+#ifdef WITH_PYTHONMODULE
+ && strcmp(cfg->module_conf, "python iterator") != 0
+ && strcmp(cfg->module_conf, "python validator iterator") != 0
+ && strcmp(cfg->module_conf, "validator python iterator") != 0
+#endif
+ ) {
+ fatal_exit("module conf '%s' is not known to work",
+ cfg->module_conf);
+ }
+
+#ifdef HAVE_GETPWNAM
+ if(cfg->username && cfg->username[0]) {
+ if(getpwnam(cfg->username) == NULL)
+ fatal_exit("user '%s' does not exist.", cfg->username);
+ endpwent();
+ }
+#endif
+ if(cfg->remote_control_enable) {
+ check_chroot_string("server-key-file", &cfg->server_key_file,
+ cfg->chrootdir, cfg);
+ check_chroot_string("server-cert-file", &cfg->server_cert_file,
+ cfg->chrootdir, cfg);
+ if(!is_file(cfg->control_key_file))
+ fatal_exit("control-key-file: \"%s\" does not exist",
+ cfg->control_key_file);
+ if(!is_file(cfg->control_cert_file))
+ fatal_exit("control-cert-file: \"%s\" does not exist",
+ cfg->control_cert_file);
+ }
+
+ localzonechecks(cfg);
+}
+
+/** check forwards */
+static void
+check_fwd(struct config_file* cfg)
+{
+ struct iter_forwards* fwd = forwards_create();
+ if(!fwd || !forwards_apply_cfg(fwd, cfg)) {
+ fatal_exit("Could not set forward zones");
+ }
+ forwards_delete(fwd);
+}
+
+/** check hints */
+static void
+check_hints(struct config_file* cfg)
+{
+ struct iter_hints* hints = hints_create();
+ if(!hints || !hints_apply_cfg(hints, cfg)) {
+ fatal_exit("Could not set root or stub hints");
+ }
+ hints_delete(hints);
+}
+
+/** check config file */
+static void
+checkconf(const char* cfgfile, const char* opt)
+{
+ struct config_file* cfg = config_create();
+ if(!cfg)
+ fatal_exit("out of memory");
+ if(!config_read(cfg, cfgfile, NULL)) {
+ /* config_read prints messages to stderr */
+ config_delete(cfg);
+ exit(1);
+ }
+ morechecks(cfg, cfgfile);
+ check_mod(cfg, iter_get_funcblock());
+ check_mod(cfg, val_get_funcblock());
+#ifdef WITH_PYTHONMODULE
+ if(strstr(cfg->module_conf, "python"))
+ check_mod(cfg, pythonmod_get_funcblock());
+#endif
+ check_fwd(cfg);
+ check_hints(cfg);
+ if(opt) print_option(cfg, opt);
+ else printf("unbound-checkconf: no errors in %s\n", cfgfile);
+ 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 checkconf */
+int main(int argc, char* argv[])
+{
+ int c;
+ const char* f;
+ const char* opt = NULL;
+ const char* cfgfile = CONFIGFILE;
+ log_ident_set("unbound-checkconf");
+ log_init(NULL, 0, NULL);
+ checklock_start();
+#ifdef USE_WINSOCK
+ /* use registry config file in preference to compiletime location */
+ if(!(cfgfile=w_lookup_reg_str("Software\\Unbound", "ConfigFile")))
+ cfgfile = CONFIGFILE;
+#endif /* USE_WINSOCK */
+ /* parse the options */
+ while( (c=getopt(argc, argv, "ho:")) != -1) {
+ switch(c) {
+ case 'o':
+ opt = optarg;
+ break;
+ case '?':
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if(argc != 0 && argc != 1)
+ usage();
+ if(argc == 1)
+ f = argv[0];
+ else f = cfgfile;
+ checkconf(f, opt);
+ checklock_stop();
+ return 0;
+}
diff --git a/smallapp/unbound-control-setup.sh b/smallapp/unbound-control-setup.sh
new file mode 100755
index 000000000000..aca62ac8cdf5
--- /dev/null
+++ b/smallapp/unbound-control-setup.sh
@@ -0,0 +1,162 @@
+#!/bin/sh
+#
+# unbound-control-setup.sh - set up SSL certificates for unbound-control
+#
+# 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.
+
+# settings:
+
+# directory for files
+DESTDIR=/usr/local/etc/unbound
+
+# issuer and subject name for certificates
+SERVERNAME=unbound
+CLIENTNAME=unbound-control
+
+# validity period for certificates
+DAYS=7200
+
+# size of keys in bits
+BITS=1536
+
+# hash algorithm
+HASH=sha256
+
+# base name for unbound server keys
+SVR_BASE=unbound_server
+
+# base name for unbound-control keys
+CTL_BASE=unbound_control
+
+# we want -rw-r--- access (say you run this as root: grp=yes (server), all=no).
+umask 0026
+
+# end of options
+
+# functions:
+error ( ) {
+ echo "$0 fatal error: $1"
+ exit 1
+}
+
+# check arguments:
+while test $# -ne 0; do
+ case $1 in
+ -d)
+ if test $# -eq 1; then error "need argument for -d"; fi
+ DESTDIR="$2"
+ shift
+ ;;
+ *)
+ echo "unbound-control-setup.sh - setup SSL keys for unbound-control"
+ echo " -d dir use directory to store keys and certificates."
+ echo " default: $DESTDIR"
+ echo "please run this command using the same user id that the "
+ echo "unbound daemon uses, it needs read privileges."
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+# go!:
+echo "setup in directory $DESTDIR"
+cd "$DESTDIR" || error "could not cd to $DESTDIR"
+
+# create certificate keys; do not recreate if they already exist.
+if test -f $SVR_BASE.key; then
+ echo "$SVR_BASE.key exists"
+else
+ echo "generating $SVR_BASE.key"
+ openssl genrsa -out $SVR_BASE.key $BITS || error "could not genrsa"
+fi
+if test -f $CTL_BASE.key; then
+ echo "$CTL_BASE.key exists"
+else
+ echo "generating $CTL_BASE.key"
+ openssl genrsa -out $CTL_BASE.key $BITS || error "could not genrsa"
+fi
+
+# create self-signed cert for server
+cat >request.cfg <<EOF
+[req]
+default_bits=$BITS
+default_md=$HASH
+prompt=no
+distinguished_name=req_distinguished_name
+
+[req_distinguished_name]
+commonName=$SERVERNAME
+EOF
+test -f request.cfg || error "could not create request.cfg"
+
+echo "create $SVR_BASE.pem (self signed certificate)"
+openssl req -key $SVR_BASE.key -config request.cfg -new -x509 -days $DAYS -out $SVR_BASE.pem || error "could not create $SVR_BASE.pem"
+# create trusted usage pem
+openssl x509 -in $SVR_BASE.pem -addtrust serverAuth -out $SVR_BASE"_trust.pem"
+
+# create client request and sign it, piped
+cat >request.cfg <<EOF
+[req]
+default_bits=$BITS
+default_md=$HASH
+prompt=no
+distinguished_name=req_distinguished_name
+
+[req_distinguished_name]
+commonName=$CLIENTNAME
+EOF
+test -f request.cfg || error "could not create request.cfg"
+
+echo "create $CTL_BASE.pem (signed client certificate)"
+openssl req -key $CTL_BASE.key -config request.cfg -new | openssl x509 -req -days $DAYS -CA $SVR_BASE"_trust.pem" -CAkey $SVR_BASE.key -CAcreateserial -$HASH -out $CTL_BASE.pem
+test -f $CTL_BASE.pem || error "could not create $CTL_BASE.pem"
+# create trusted usage pem
+# openssl x509 -in $CTL_BASE.pem -addtrust clientAuth -out $CTL_BASE"_trust.pem"
+
+# see details with openssl x509 -noout -text < $SVR_BASE.pem
+# echo "create $CTL_BASE""_browser.pfx (web client certificate)"
+# echo "create webbrowser PKCS#12 .PFX certificate file. In Firefox import in:"
+# echo "preferences - advanced - encryption - view certificates - your certs"
+# echo "empty password is used, simply click OK on the password dialog box."
+# openssl pkcs12 -export -in $CTL_BASE"_trust.pem" -inkey $CTL_BASE.key -name "unbound remote control client cert" -out $CTL_BASE"_browser.pfx" -password "pass:" || error "could not create browser certificate"
+
+# remove unused permissions
+chmod o-rw $SVR_BASE.pem $SVR_BASE.key $CTL_BASE.pem $CTL_BASE.key
+
+# remove crap
+rm -f request.cfg
+rm -f $CTL_BASE"_trust.pem" $SVR_BASE"_trust.pem" $SVR_BASE"_trust.srl"
+
+echo "Setup success. Certificates created. Enable in unbound.conf file to use"
+
+exit 0
diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c
new file mode 100644
index 000000000000..58be7b7abfc0
--- /dev/null
+++ b/smallapp/unbound-control.c
@@ -0,0 +1,426 @@
+/*
+ * 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"
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#ifdef HAVE_OPENSSL_SSL_H
+#include <openssl/ssl.h>
+#endif
+#ifdef HAVE_OPENSSL_ERR_H
+#include <openssl/err.h>
+#endif
+#ifdef HAVE_OPENSSL_RAND_H
+#include <openssl/rand.h>
+#endif
+#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(" (this flushes data, stats, requestlist)\n");
+ printf(" stats print statistics\n");
+ printf(" stats_noreset peek at statistics\n");
+ printf(" status display status of server\n");
+ printf(" verbosity <number> change logging detail\n");
+ printf(" log_reopen close and open the logfile\n");
+ printf(" local_zone <name> <type> add new local zone\n");
+ printf(" local_zone_remove <name> remove local zone and its contents\n");
+ printf(" local_data <RR data...> add local data, for example\n");
+ printf(" local_data www.example.com A 192.0.2.1\n");
+ printf(" local_data_remove <name> remove local RR data from name\n");
+ printf(" dump_cache print cache to stdout\n");
+ printf(" load_cache load cache from stdin\n");
+ printf(" lookup <name> print nameservers for name\n");
+ printf(" flush <name> flushes common types for name from cache\n");
+ printf(" types: A, AAAA, MX, PTR, NS,\n");
+ printf(" SOA, CNAME, DNAME, SRV, NAPTR\n");
+ printf(" flush_type <name> <type> flush name, type from cache\n");
+ printf(" flush_zone <name> flush everything at or under name\n");
+ printf(" from rr and dnssec caches\n");
+ printf(" flush_stats flush statistics, make zero\n");
+ printf(" flush_requestlist drop queries that are worked on\n");
+ printf(" dump_requestlist show what is worked on\n");
+ printf(" flush_infra [all | ip] remove ping, edns for one IP or all\n");
+ printf(" dump_infra show ping and edns entries\n");
+ printf(" set_option opt: val set option to value, no reload\n");
+ printf(" get_option opt get option value\n");
+ printf(" list_stubs list stub-zones and root hints in use\n");
+ printf(" list_forwards list forward-zones in use\n");
+ printf(" list_local_zones list local-zones in use\n");
+ printf(" list_local_data list local-data RRs in use\n");
+ printf(" forward_add [+i] zone addr.. add forward-zone with servers\n");
+ printf(" forward_remove [+i] zone remove forward zone\n");
+ printf(" stub_add [+ip] zone addr.. add stub-zone with servers\n");
+ printf(" stub_remove [+i] zone remove stub zone\n");
+ printf(" +i also do dnssec insecure point\n");
+ printf(" +p set stub to use priming\n");
+ printf(" forward [off | addr ...] without arg show forward setup\n");
+ printf(" or off to turn off root forwarding\n");
+ printf(" or give list of ip addresses\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(const char* svr, struct config_file* cfg, int statuscmd)
+{
+ 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";
+ /* config 0 addr (everything), means ask localhost */
+ if(strcmp(svr, "0.0.0.0") == 0)
+ svr = "127.0.0.1";
+ else if(strcmp(svr, "::0") == 0 ||
+ strcmp(svr, "0::0") == 0 ||
+ strcmp(svr, "0::") == 0 ||
+ strcmp(svr, "::") == 0)
+ svr = "::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));
+ if(errno == ECONNREFUSED && statuscmd) {
+ printf("unbound is stopped\n");
+ exit(3);
+ }
+#else
+ log_err("connect: %s", wsa_strerror(WSAGetLastError()));
+ if(WSAGetLastError() == WSAECONNREFUSED && statuscmd) {
+ printf("unbound is stopped\n");
+ exit(3);
+ }
+#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 stdin to server */
+static void
+send_file(SSL* ssl, FILE* in, char* buf, size_t sz)
+{
+ while(fgets(buf, (int)sz, in)) {
+ if(SSL_write(ssl, buf, (int)strlen(buf)) <= 0)
+ ssl_err("could not SSL_write contents");
+ }
+}
+
+/** send command and display result */
+static int
+go_cmd(SSL* ssl, int argc, char* argv[])
+{
+ char pre[10];
+ const char* space=" ";
+ const char* newline="\n";
+ int was_error = 0, first_line = 1;
+ int r, i;
+ char buf[1024];
+ snprintf(pre, sizeof(pre), "UBCT%d ", UNBOUND_CONTROL_VERSION);
+ if(SSL_write(ssl, pre, (int)strlen(pre)) <= 0)
+ ssl_err("could not SSL_write");
+ for(i=0; i<argc; i++) {
+ if(SSL_write(ssl, space, (int)strlen(space)) <= 0)
+ ssl_err("could not SSL_write");
+ if(SSL_write(ssl, argv[i], (int)strlen(argv[i])) <= 0)
+ ssl_err("could not SSL_write");
+ }
+ if(SSL_write(ssl, newline, (int)strlen(newline)) <= 0)
+ ssl_err("could not SSL_write");
+
+ if(argc == 1 && strcmp(argv[0], "load_cache") == 0) {
+ send_file(ssl, stdin, buf, sizeof(buf));
+ }
+
+ 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);
+ if(first_line && strncmp(buf, "error", 5) == 0)
+ was_error = 1;
+ first_line = 0;
+ }
+ return was_error;
+}
+
+/** go ahead and read config, contact server and perform command and display */
+static int
+go(const char* cfgfile, char* svr, int argc, char* argv[])
+{
+ struct config_file* cfg;
+ int fd, ret;
+ SSL_CTX* ctx;
+ SSL* ssl;
+
+ /* read config */
+ if(!(cfg = config_create()))
+ fatal_exit("out of memory");
+ if(!config_read(cfg, cfgfile, NULL))
+ 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, argc>0&&strcmp(argv[0],"status")==0);
+ ssl = setup_ssl(ctx, fd);
+
+ /* send command */
+ ret = go_cmd(ssl, argc, argv);
+
+ SSL_free(ssl);
+#ifndef USE_WINSOCK
+ close(fd);
+#else
+ closesocket(fd);
+#endif
+ SSL_CTX_free(ctx);
+ config_delete(cfg);
+ return ret;
+}
+
+/** 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, ret;
+ const char* cfgfile = CONFIGFILE;
+ char* svr = NULL;
+#ifdef USE_WINSOCK
+ int r;
+ WSADATA wsa_data;
+#endif
+#ifdef USE_THREAD_DEBUG
+ /* stop the file output from unbound-control, overwites the servers */
+ extern int check_locking_order;
+ check_locking_order = 0;
+#endif /* USE_THREAD_DEBUG */
+ 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));
+ /* use registry config file in preference to compiletime location */
+ if(!(cfgfile=w_lookup_reg_str("Software\\Unbound", "ConfigFile")))
+ cfgfile = CONFIGFILE;
+#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));
+ }
+ }
+
+ ret = go(cfgfile, svr, argc, argv);
+
+#ifdef USE_WINSOCK
+ WSACleanup();
+#endif
+ checklock_stop();
+ return ret;
+}
diff --git a/smallapp/unbound-host.c b/smallapp/unbound-host.c
new file mode 100644
index 000000000000..095396749ff1
--- /dev/null
+++ b/smallapp/unbound-host.c
@@ -0,0 +1,514 @@
+/*
+ * checkconf/unbound-host.c - replacement for host that supports validation.
+ *
+ * Copyright (c) 2007, 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
+ *
+ * This file performs functionality like 'host', and also supports validation.
+ * It uses the libunbound library.
+ */
+
+#include "config.h"
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+/* remove alloc checks, not in this part of the code */
+#ifdef UNBOUND_ALLOC_STATS
+#undef malloc
+#undef calloc
+#undef free
+#undef realloc
+#endif
+#ifdef UNBOUND_ALLOC_LITE
+#undef malloc
+#undef calloc
+#undef free
+#undef realloc
+#undef strdup
+#define unbound_lite_wrapstr(s) s
+#endif
+#include "libunbound/unbound.h"
+#include <ldns/ldns.h>
+
+/** verbosity for unbound-host app */
+static int verb = 0;
+
+/** Give unbound-host usage, and exit (1). */
+static void
+usage()
+{
+ printf("Usage: unbound-host [-vdhr46] [-c class] [-t type] hostname\n");
+ printf(" [-y key] [-f keyfile] [-F namedkeyfile]\n");
+ printf(" [-C configfile]\n");
+ printf(" Queries the DNS for information.\n");
+ printf(" The hostname is looked up for IP4, IP6 and mail.\n");
+ printf(" If an ip-address is given a reverse lookup is done.\n");
+ printf(" Use the -v option to see DNSSEC security information.\n");
+ printf(" -t type what type to look for.\n");
+ printf(" -c class what class to look for, if not class IN.\n");
+ printf(" -y 'keystring' specify trust anchor, DS or DNSKEY, like\n");
+ printf(" -y 'example.com DS 31560 5 1 1CFED8478...'\n");
+ printf(" -f keyfile read trust anchors from file, with lines as -y.\n");
+ printf(" -F keyfile read named.conf-style trust anchors.\n");
+ printf(" -C config use the specified unbound.conf (none read by default)\n");
+ printf(" -r read forwarder information from /etc/resolv.conf\n");
+ printf(" breaks validation if the fwder does not do DNSSEC.\n");
+ printf(" -v be more verbose, shows nodata and security.\n");
+ printf(" -d debug, traces the action, -d -d shows more.\n");
+ printf(" -4 use ipv4 network, avoid ipv6.\n");
+ printf(" -6 use ipv6 network, avoid ipv4.\n");
+ printf(" -h show this usage help.\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);
+}
+
+/** determine if str is ip4 and put into reverse lookup format */
+static int
+isip4(const char* nm, char** res)
+{
+ struct in_addr addr;
+ /* ddd.ddd.ddd.ddd.in-addr.arpa. is less than 32 */
+ char buf[32];
+ if(inet_pton(AF_INET, nm, &addr) <= 0) {
+ return 0;
+ }
+ snprintf(buf, sizeof(buf), "%u.%u.%u.%u.in-addr.arpa",
+ (unsigned)((uint8_t*)&addr)[3], (unsigned)((uint8_t*)&addr)[2],
+ (unsigned)((uint8_t*)&addr)[1], (unsigned)((uint8_t*)&addr)[0]);
+ *res = strdup(buf);
+ return 1;
+}
+
+/** determine if str is ip6 and put into reverse lookup format */
+static int
+isip6(const char* nm, char** res)
+{
+ struct in6_addr addr;
+ /* [nibble.]{32}.ip6.arpa. is less than 128 */
+ const char* hex = "0123456789abcdef";
+ char buf[128];
+ char *p;
+ int i;
+ if(inet_pton(AF_INET6, nm, &addr) <= 0) {
+ return 0;
+ }
+ p = buf;
+ for(i=15; i>=0; i--) {
+ uint8_t b = ((uint8_t*)&addr)[i];
+ *p++ = hex[ (b&0x0f) ];
+ *p++ = '.';
+ *p++ = hex[ (b&0xf0) >> 4 ];
+ *p++ = '.';
+ }
+ snprintf(buf+16*4, sizeof(buf)-16*4, "ip6.arpa");
+ *res = strdup(buf);
+ if(!*res) {
+ fprintf(stderr, "error: out of memory\n");
+ exit(1);
+ }
+ return 1;
+}
+
+/** massage input name */
+static char*
+massage_qname(const char* nm, int* reverse)
+{
+ /* recognise IP4 and IP6, create reverse addresses if needed */
+ char* res;
+ if(isip4(nm, &res)) {
+ *reverse = 1;
+ } else if(isip6(nm, &res)) {
+ *reverse = 1;
+ } else {
+ res = strdup(nm);
+ }
+ if(!res) {
+ fprintf(stderr, "error: out of memory\n");
+ exit(1);
+ }
+ return res;
+}
+
+/** massage input type */
+static int
+massage_type(const char* t, int reverse, int* multi)
+{
+ if(t) {
+ int r = ldns_get_rr_type_by_name(t);
+ if(r == 0 && strcasecmp(t, "TYPE0") != 0 &&
+ strcmp(t, "") != 0) {
+ fprintf(stderr, "error unknown type %s\n", t);
+ exit(1);
+ }
+ return r;
+ }
+ if(!t && reverse)
+ return LDNS_RR_TYPE_PTR;
+ *multi = 1;
+ return LDNS_RR_TYPE_A;
+}
+
+/** massage input class */
+static int
+massage_class(const char* c)
+{
+ if(c) {
+ int r = ldns_get_rr_class_by_name(c);
+ if(r == 0 && strcasecmp(c, "CLASS0") != 0 &&
+ strcmp(c, "") != 0) {
+ fprintf(stderr, "error unknown class %s\n", c);
+ exit(1);
+ }
+ return r;
+ }
+ return LDNS_RR_CLASS_IN;
+}
+
+/** nice security status string */
+static const char*
+secure_str(struct ub_result* result)
+{
+ if(result->secure) return "(secure)";
+ if(result->bogus) return "(BOGUS (security failure))";
+ return "(insecure)";
+}
+
+/** nice string for type */
+static void
+pretty_type(char* s, size_t len, int t)
+{
+ char* d = ldns_rr_type2str(t);
+ snprintf(s, len, "%s", d);
+ free(d);
+}
+
+/** nice string for class */
+static void
+pretty_class(char* s, size_t len, int c)
+{
+ char* d = ldns_rr_class2str(c);
+ snprintf(s, len, "%s", d);
+ free(d);
+}
+
+/** nice string for rcode */
+static void
+pretty_rcode(char* s, size_t len, int r)
+{
+ ldns_lookup_table *rcode = ldns_lookup_by_id(ldns_rcodes, r);
+ if(rcode) {
+ snprintf(s, len, "%s", rcode->name);
+ } else {
+ snprintf(s, len, "RCODE%d", r);
+ }
+}
+
+/** convert and print rdata */
+static void
+print_rd(int t, char* data, size_t len)
+{
+ size_t i, pos = 0;
+ uint8_t* rd = (uint8_t*)malloc(len+2);
+ ldns_rr* rr = ldns_rr_new();
+ ldns_status status;
+ if(!rd || !rr) {
+ fprintf(stderr, "out of memory");
+ exit(1);
+ }
+ ldns_rr_set_type(rr, t);
+ ldns_write_uint16(rd, len);
+ memmove(rd+2, data, len);
+ ldns_rr_set_owner(rr, NULL);
+ status = ldns_wire2rdf(rr, rd, len+2, &pos);
+ if(status != LDNS_STATUS_OK) {
+ free(rd);
+ ldns_rr_free(rr);
+ printf("error_printing_data");
+ return;
+ }
+ for(i=0; i<ldns_rr_rd_count(rr); i++) {
+ printf(" ");
+ ldns_rdf_print(stdout, ldns_rr_rdf(rr, i));
+ }
+ ldns_rr_free(rr);
+ free(rd);
+}
+
+/** pretty line of RR data for results */
+static void
+pretty_rdata(char* q, char* cstr, char* tstr, int t, const char* sec,
+ char* data, size_t len)
+{
+ printf("%s", q);
+ if(strcmp(cstr, "IN") != 0)
+ printf(" in class %s", cstr);
+ if(t == LDNS_RR_TYPE_A)
+ printf(" has address");
+ else if(t == LDNS_RR_TYPE_AAAA)
+ printf(" has IPv6 address");
+ else if(t == LDNS_RR_TYPE_MX)
+ printf(" mail is handled by");
+ else if(t == LDNS_RR_TYPE_PTR)
+ printf(" domain name pointer");
+ else printf(" has %s record", tstr);
+ print_rd(t, data, len);
+ if(verb > 0)
+ printf(" %s", sec);
+ printf("\n");
+}
+
+/** pretty line of output for results */
+static void
+pretty_output(char* q, int t, int c, struct ub_result* result, int docname)
+{
+ int i;
+ const char *secstatus = secure_str(result);
+ char tstr[16];
+ char cstr[16];
+ char rcodestr[16];
+ pretty_type(tstr, 16, t);
+ pretty_class(cstr, 16, c);
+ pretty_rcode(rcodestr, 16, result->rcode);
+
+ if(!result->havedata && result->rcode) {
+ printf("Host %s not found: %d(%s).",
+ q, result->rcode, rcodestr);
+ if(verb > 0)
+ printf(" %s", secstatus);
+ printf("\n");
+ if(result->bogus && result->why_bogus)
+ printf("%s\n", result->why_bogus);
+ return;
+ }
+ if(docname && result->canonname &&
+ result->canonname != result->qname) {
+ printf("%s is an alias for %s", result->qname,
+ result->canonname);
+ if(verb > 0)
+ printf(" %s", secstatus);
+ printf("\n");
+ }
+ /* remove trailing . from long canonnames for nicer output */
+ if(result->canonname && strlen(result->canonname) > 1 &&
+ result->canonname[strlen(result->canonname)-1] == '.')
+ result->canonname[strlen(result->canonname)-1] = 0;
+ if(!result->havedata) {
+ if(verb > 0) {
+ printf("%s", result->canonname?result->canonname:q);
+ if(strcmp(cstr, "IN") != 0)
+ printf(" in class %s", cstr);
+ if(t == LDNS_RR_TYPE_A)
+ printf(" has no address");
+ else if(t == LDNS_RR_TYPE_AAAA)
+ printf(" has no IPv6 address");
+ else if(t == LDNS_RR_TYPE_PTR)
+ printf(" has no domain name ptr");
+ else if(t == LDNS_RR_TYPE_MX)
+ printf(" has no mail handler record");
+ else if(t == LDNS_RR_TYPE_ANY) {
+ ldns_pkt* p = NULL;
+ if(ldns_wire2pkt(&p, result->answer_packet,
+ (size_t)result->answer_len)==LDNS_STATUS_OK){
+ if(ldns_rr_list_rr_count(
+ ldns_pkt_answer(p)) == 0)
+ printf(" has no records\n");
+ else {
+ printf(" ANY:\n");
+ ldns_rr_list_print(stdout,
+ ldns_pkt_answer(p));
+ }
+ } else {
+ fprintf(stderr, "could not parse "
+ "reply packet to ANY query\n");
+ exit(1);
+ }
+ ldns_pkt_free(p);
+
+ } else printf(" has no %s record", tstr);
+ printf(" %s\n", secstatus);
+ }
+ /* else: emptiness to indicate no data */
+ if(result->bogus && result->why_bogus)
+ printf("%s\n", result->why_bogus);
+ return;
+ }
+ i=0;
+ while(result->data[i])
+ {
+ pretty_rdata(
+ result->canonname?result->canonname:q,
+ cstr, tstr, t, secstatus, result->data[i],
+ (size_t)result->len[i]);
+ i++;
+ }
+ if(result->bogus && result->why_bogus)
+ printf("%s\n", result->why_bogus);
+}
+
+/** perform a lookup and printout return if domain existed */
+static int
+dnslook(struct ub_ctx* ctx, char* q, int t, int c, int docname)
+{
+ int ret;
+ struct ub_result* result;
+
+ ret = ub_resolve(ctx, q, t, c, &result);
+ if(ret != 0) {
+ fprintf(stderr, "resolve error: %s\n", ub_strerror(ret));
+ exit(1);
+ }
+ pretty_output(q, t, c, result, docname);
+ ret = result->nxdomain;
+ ub_resolve_free(result);
+ return ret;
+}
+
+/** perform host lookup */
+static void
+lookup(struct ub_ctx* ctx, const char* nm, const char* qt, const char* qc)
+{
+ /* massage input into a query name, type and class */
+ int multi = 0; /* no type, so do A, AAAA, MX */
+ int reverse = 0; /* we are doing a reverse lookup */
+ char* realq = massage_qname(nm, &reverse);
+ int t = massage_type(qt, reverse, &multi);
+ int c = massage_class(qc);
+
+ /* perform the query */
+ if(multi) {
+ if(!dnslook(ctx, realq, LDNS_RR_TYPE_A, c, 1)) {
+ /* domain exists, lookup more */
+ (void)dnslook(ctx, realq, LDNS_RR_TYPE_AAAA, c, 0);
+ (void)dnslook(ctx, realq, LDNS_RR_TYPE_MX, c, 0);
+ }
+ } else {
+ (void)dnslook(ctx, realq, t, c, 1);
+ }
+ ub_ctx_delete(ctx);
+ free(realq);
+}
+
+/** print error if any */
+static void
+check_ub_res(int r)
+{
+ if(r != 0) {
+ fprintf(stderr, "error: %s\n", ub_strerror(r));
+ exit(1);
+ }
+}
+
+/** 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 checkconf */
+int main(int argc, char* argv[])
+{
+ int c;
+ char* qclass = NULL;
+ char* qtype = NULL;
+ struct ub_ctx* ctx = NULL;
+ int debuglevel = 0;
+
+ ctx = ub_ctx_create();
+ if(!ctx) {
+ fprintf(stderr, "error: out of memory\n");
+ exit(1);
+ }
+
+ /* parse the options */
+ while( (c=getopt(argc, argv, "46F:c:df:hrt:vy:C:")) != -1) {
+ switch(c) {
+ case '4':
+ check_ub_res(ub_ctx_set_option(ctx, "do-ip6:", "no"));
+ break;
+ case '6':
+ check_ub_res(ub_ctx_set_option(ctx, "do-ip4:", "no"));
+ break;
+ case 'c':
+ qclass = optarg;
+ break;
+ case 'C':
+ check_ub_res(ub_ctx_config(ctx, optarg));
+ break;
+ case 'd':
+ debuglevel++;
+ if(debuglevel < 2)
+ debuglevel = 2; /* at least VERB_DETAIL */
+ break;
+ case 'r':
+ check_ub_res(ub_ctx_resolvconf(ctx, "/etc/resolv.conf"));
+ break;
+ case 't':
+ qtype = optarg;
+ break;
+ case 'v':
+ verb++;
+ break;
+ case 'y':
+ check_ub_res(ub_ctx_add_ta(ctx, optarg));
+ break;
+ case 'f':
+ check_ub_res(ub_ctx_add_ta_file(ctx, optarg));
+ break;
+ case 'F':
+ check_ub_res(ub_ctx_trustedkeys(ctx, optarg));
+ break;
+ case '?':
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ if(debuglevel != 0) /* set after possible -C options */
+ check_ub_res(ub_ctx_debuglevel(ctx, debuglevel));
+ if(ub_ctx_get_option(ctx, "use-syslog", &optarg) == 0) {
+ if(strcmp(optarg, "yes") == 0) /* disable use-syslog */
+ check_ub_res(ub_ctx_set_option(ctx,
+ "use-syslog:", "no"));
+ free(optarg);
+ }
+ argc -= optind;
+ argv += optind;
+ if(argc != 1)
+ usage();
+
+ lookup(ctx, argv[0], qtype, qclass);
+ return 0;
+}
diff --git a/smallapp/worker_cb.c b/smallapp/worker_cb.c
new file mode 100644
index 000000000000..bc37e3307ae4
--- /dev/null
+++ b/smallapp/worker_cb.c
@@ -0,0 +1,242 @@
+/*
+ * checkconf/worker_cb.c - fake callback routines to make fptr_wlist work
+ *
+ * Copyright (c) 2007, 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
+ *
+ * This file contains fake callback functions, so that the symbols exist
+ * and the fptr_wlist continues to work even if the daemon/worker is not
+ * linked into the resulting program.
+ */
+#include "config.h"
+#include "util/log.h"
+#include "services/mesh.h"
+struct comm_reply;
+struct comm_point;
+struct module_qstate;
+struct tube;
+
+void worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube),
+ uint8_t* ATTR_UNUSED(buffer), size_t ATTR_UNUSED(len),
+ int ATTR_UNUSED(error), void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}
+
+int worker_handle_request(struct comm_point* ATTR_UNUSED(c),
+ void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+ struct comm_reply* ATTR_UNUSED(repinfo))
+{
+ log_assert(0);
+ return 0;
+}
+
+int worker_handle_reply(struct comm_point* ATTR_UNUSED(c),
+ void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+ struct comm_reply* ATTR_UNUSED(reply_info))
+{
+ log_assert(0);
+ return 0;
+}
+
+int worker_handle_service_reply(struct comm_point* ATTR_UNUSED(c),
+ void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+ struct comm_reply* ATTR_UNUSED(reply_info))
+{
+ log_assert(0);
+ return 0;
+}
+
+int remote_accept_callback(struct comm_point* ATTR_UNUSED(c),
+ void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+ struct comm_reply* ATTR_UNUSED(repinfo))
+{
+ log_assert(0);
+ return 0;
+}
+
+int remote_control_callback(struct comm_point* ATTR_UNUSED(c),
+ void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+ struct comm_reply* ATTR_UNUSED(repinfo))
+{
+ log_assert(0);
+ return 0;
+}
+
+void worker_sighandler(int ATTR_UNUSED(sig), void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}
+
+struct outbound_entry* worker_send_query(uint8_t* ATTR_UNUSED(qname),
+ size_t ATTR_UNUSED(qnamelen), uint16_t ATTR_UNUSED(qtype),
+ uint16_t ATTR_UNUSED(qclass), uint16_t ATTR_UNUSED(flags),
+ int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec),
+ struct sockaddr_storage* ATTR_UNUSED(addr),
+ socklen_t ATTR_UNUSED(addrlen), struct module_qstate* ATTR_UNUSED(q))
+{
+ log_assert(0);
+ return 0;
+}
+
+#ifdef UB_ON_WINDOWS
+void
+worker_win_stop_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void*
+ ATTR_UNUSED(arg)) {
+ log_assert(0);
+}
+
+void
+wsvc_cron_cb(void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}
+#endif /* UB_ON_WINDOWS */
+
+void
+worker_alloc_cleanup(void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}
+
+struct outbound_entry* libworker_send_query(uint8_t* ATTR_UNUSED(qname),
+ size_t ATTR_UNUSED(qnamelen), uint16_t ATTR_UNUSED(qtype),
+ uint16_t ATTR_UNUSED(qclass), uint16_t ATTR_UNUSED(flags),
+ int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec),
+ struct sockaddr_storage* ATTR_UNUSED(addr),
+ socklen_t ATTR_UNUSED(addrlen), struct module_qstate* ATTR_UNUSED(q))
+{
+ log_assert(0);
+ return 0;
+}
+
+int libworker_handle_reply(struct comm_point* ATTR_UNUSED(c),
+ void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+ struct comm_reply* ATTR_UNUSED(reply_info))
+{
+ log_assert(0);
+ return 0;
+}
+
+int libworker_handle_service_reply(struct comm_point* ATTR_UNUSED(c),
+ void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+ struct comm_reply* ATTR_UNUSED(reply_info))
+{
+ log_assert(0);
+ return 0;
+}
+
+void libworker_handle_control_cmd(struct tube* ATTR_UNUSED(tube),
+ uint8_t* ATTR_UNUSED(buffer), size_t ATTR_UNUSED(len),
+ int ATTR_UNUSED(error), void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}
+
+void libworker_fg_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode),
+ ldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s),
+ char* ATTR_UNUSED(why_bogus))
+{
+ log_assert(0);
+}
+
+void libworker_bg_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode),
+ ldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s),
+ char* ATTR_UNUSED(why_bogus))
+{
+ log_assert(0);
+}
+
+int context_query_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b))
+{
+ log_assert(0);
+ return 0;
+}
+
+void worker_stat_timer_cb(void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}
+
+void worker_probe_timer_cb(void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}
+
+void worker_start_accept(void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}
+
+void worker_stop_accept(void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}
+
+/** keep track of lock id in lock-verify application */
+struct order_id {
+ /** the thread id that created it */
+ int thr;
+ /** the instance number of creation */
+ int instance;
+};
+
+int order_lock_cmp(const void* e1, const void* e2)
+{
+ struct order_id* o1 = (struct order_id*)e1;
+ struct order_id* o2 = (struct order_id*)e2;
+ if(o1->thr < o2->thr) return -1;
+ if(o1->thr > o2->thr) return 1;
+ if(o1->instance < o2->instance) return -1;
+ if(o1->instance > o2->instance) return 1;
+ return 0;
+}
+
+int
+codeline_cmp(const void* a, const void* b)
+{
+ return strcmp((const char*)a, (const char*)b);
+}
+
+int replay_var_compare(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b))
+{
+ log_assert(0);
+ return 0;
+}
+
+void remote_get_opt_ssl(char* ATTR_UNUSED(str), void* ATTR_UNUSED(arg))
+{
+ log_assert(0);
+}