diff options
author | Riccardo Spagni <ric@spagni.net> | 2014-10-05 23:44:31 +0200 |
---|---|---|
committer | Riccardo Spagni <ric@spagni.net> | 2014-10-05 23:44:31 +0200 |
commit | 9ef094b356b4da7542c3cab898dac7e135b76903 (patch) | |
tree | 99b5876712b0b1551fc042fe75447b998e4b0fc1 /external/unbound/smallapp | |
parent | split mnemonic printout over 3 lines (diff) | |
download | monero-9ef094b356b4da7542c3cab898dac7e135b76903.tar.xz |
added unbound to external deps
Diffstat (limited to 'external/unbound/smallapp')
-rw-r--r-- | external/unbound/smallapp/unbound-anchor.c | 2303 | ||||
-rw-r--r-- | external/unbound/smallapp/unbound-checkconf.c | 522 | ||||
-rw-r--r-- | external/unbound/smallapp/unbound-control-setup.sh.in | 163 | ||||
-rw-r--r-- | external/unbound/smallapp/unbound-control.c | 438 | ||||
-rw-r--r-- | external/unbound/smallapp/unbound-host.c | 497 | ||||
-rw-r--r-- | external/unbound/smallapp/worker_cb.c | 250 |
6 files changed, 4173 insertions, 0 deletions
diff --git a/external/unbound/smallapp/unbound-anchor.c b/external/unbound/smallapp/unbound-anchor.c new file mode 100644 index 000000000..9df0d95b4 --- /dev/null +++ b/external/unbound/smallapp/unbound-anchor.c @@ -0,0 +1,2303 @@ +/* + * 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 COPYRIGHT + * HOLDER 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/rrdef.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/x509v3.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" +/** name of the signer of the certificate */ +#define P7SIGNER "dnssec@iana.org" +/** 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("-n name signer's subject emailAddress, default %s\n", P7SIGNER); + 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(const char* msg, const 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(const char* res_conf, const char* root_hints, + const 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(const 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(const 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(const 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(const 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(const 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, const 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); + } + if(!res->havedata || res->rcode || !res->data) { + if(verb) printf("resolve %s %s: no result\n", host, + (tp==LDNS_RR_TYPE_A)?"A":"AAAA"); + return; + } + 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(const char* str, int port) +{ + socklen_t len = 0; + union { + struct sockaddr_in6 a6; + struct sockaddr_in a; + } addr; + struct ip_list* ip; + uint16_t p = (uint16_t)port; + memset(&addr, 0, sizeof(addr)); + + if(inet_pton(AF_INET6, str, &addr.a6.sin6_addr) > 0) { + /* it is an IPv6 */ + addr.a6.sin6_family = AF_INET6; + addr.a6.sin6_port = (in_port_t)htons(p); + len = (socklen_t)sizeof(addr.a6); + } + if(inet_pton(AF_INET, str, &addr.a.sin_addr) > 0) { + /* it is an IPv4 */ + addr.a.sin_family = AF_INET; + addr.a.sin_port = (in_port_t)htons(p); + 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(const char* host, int port, const char* res_conf, + const char* root_hints, const 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)arc4random_uniform((uint32_t)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, const char* str, const 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, const char* pathname, const 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, const char* pathname, const 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, const char* pathname, const 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. + */ +static 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, const 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, const 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); + } + } +} + +/* Stop the parser when an entity declaration is encountered. For safety. */ +static void +xml_entitydeclhandler(void *userData, + const XML_Char *ATTR_UNUSED(entityName), + int ATTR_UNUSED(is_parameter_entity), + const XML_Char *ATTR_UNUSED(value), int ATTR_UNUSED(value_length), + const XML_Char *ATTR_UNUSED(base), + const XML_Char *ATTR_UNUSED(systemId), + const XML_Char *ATTR_UNUSED(publicId), + const XML_Char *ATTR_UNUSED(notationName)) +{ + (void)XML_StopParser((XML_Parser)userData, XML_FALSE); +} + +/** + * 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_SetEntityDeclHandler(parser, xml_entitydeclhandler); + 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) { + (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; + } +} + +/* get key usage out of its extension, returns 0 if no key_usage extension */ +static unsigned long +get_usage_of_ex(X509* cert) +{ + unsigned long val = 0; + ASN1_BIT_STRING* s; + if((s=X509_get_ext_d2i(cert, NID_key_usage, NULL, NULL))) { + if(s->length > 0) { + val = s->data[0]; + if(s->length > 1) + val |= s->data[1] << 8; + } + ASN1_BIT_STRING_free(s); + } + return val; +} + +/** get valid signers from the list of signers in the signature */ +static STACK_OF(X509)* +get_valid_signers(PKCS7* p7, const char* p7signer) +{ + int i; + STACK_OF(X509)* validsigners = sk_X509_new_null(); + STACK_OF(X509)* signers = PKCS7_get0_signers(p7, NULL, 0); + unsigned long usage = 0; + if(!validsigners) { + if(verb) printf("out of memory\n"); + sk_X509_free(signers); + return NULL; + } + if(!signers) { + if(verb) printf("no signers in pkcs7 signature\n"); + sk_X509_free(validsigners); + return NULL; + } + for(i=0; i<sk_X509_num(signers); i++) { + X509_NAME* nm = X509_get_subject_name( + sk_X509_value(signers, i)); + char buf[1024]; + if(!nm) { + if(verb) printf("signer %d: cert has no subject name\n", i); + continue; + } + if(verb && nm) { + char* nmline = X509_NAME_oneline(nm, buf, + (int)sizeof(buf)); + printf("signer %d: Subject: %s\n", i, + nmline?nmline:"no subject"); + if(verb >= 3 && X509_NAME_get_text_by_NID(nm, + NID_commonName, buf, (int)sizeof(buf))) + printf("commonName: %s\n", buf); + if(verb >= 3 && X509_NAME_get_text_by_NID(nm, + NID_pkcs9_emailAddress, buf, (int)sizeof(buf))) + printf("emailAddress: %s\n", buf); + } + if(verb) { + int ku_loc = X509_get_ext_by_NID( + sk_X509_value(signers, i), NID_key_usage, -1); + if(verb >= 3 && ku_loc >= 0) { + X509_EXTENSION *ex = X509_get_ext( + sk_X509_value(signers, i), ku_loc); + if(ex) { + printf("keyUsage: "); + X509V3_EXT_print_fp(stdout, ex, 0, 0); + printf("\n"); + } + } + } + if(!p7signer || strcmp(p7signer, "")==0) { + /* there is no name to check, return all records */ + if(verb) printf("did not check commonName of signer\n"); + } else { + if(!X509_NAME_get_text_by_NID(nm, + NID_pkcs9_emailAddress, + buf, (int)sizeof(buf))) { + if(verb) printf("removed cert with no name\n"); + continue; /* no name, no use */ + } + if(strcmp(buf, p7signer) != 0) { + if(verb) printf("removed cert with wrong name\n"); + continue; /* wrong name, skip it */ + } + } + + /* check that the key usage allows digital signatures + * (the p7s) */ + usage = get_usage_of_ex(sk_X509_value(signers, i)); + if(!(usage & KU_DIGITAL_SIGNATURE)) { + if(verb) printf("removed cert with no key usage Digital Signature allowed\n"); + continue; + } + + /* we like this cert, add it to our list of valid + * signers certificates */ + sk_X509_push(validsigners, sk_X509_value(signers, i)); + } + sk_X509_free(signers); + return validsigners; +} + +/** verify a PKCS7 signature, false on failure */ +static int +verify_p7sig(BIO* data, BIO* p7s, STACK_OF(X509)* trust, const char* p7signer) +{ + PKCS7* p7; + X509_STORE *store = X509_STORE_new(); + STACK_OF(X509)* validsigners; + 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; + } +#ifdef X509_V_FLAG_CHECK_SS_SIGNATURE + X509_VERIFY_PARAM_free(param); +#endif + + (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"); + + /* check what is in the Subject name of the certificates, + * and build a stack that contains only the right certificates */ + validsigners = get_valid_signers(p7, p7signer); + if(!validsigners) { + X509_STORE_free(store); + PKCS7_free(p7); + return 0; + } + if(PKCS7_verify(p7, validsigners, store, data, NULL, PKCS7_NOINTERN) == 1) { + secure = 1; + if(verb) printf("the PKCS7 signature verified\n"); + } else { + if(verb) { + ERR_print_errors_fp(stdout); + } + } + + sk_X509_free(validsigners); + X509_STORE_free(store); + PKCS7_free(p7); + return secure; +} + +/** write unsigned root anchor file, a 5011 revoked tp */ +static void +write_unsigned_root(const 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(const 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(const char* root_anchor_file, BIO* xml, BIO* p7s, + STACK_OF(X509)* cert, const char* p7signer) +{ + BIO* ds; + + /* verify xml file */ + if(!verify_p7sig(xml, p7s, cert, p7signer)) { + 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(const char* root_anchor_file, const char* root_cert_file, + const char* urlname, const char* xmlname, const char* p7sname, + const char* p7signer, const char* res_conf, const char* root_hints, + const 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, p7signer); + 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(const 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(const 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(const 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, const 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(const 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(const 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(const 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(const char* root_anchor_file, const char* root_cert_file, + const char* urlname, const char* xmlname, const char* p7sname, + const char* p7signer, const char* res_conf, const char* root_hints, + const 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, p7signer, 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; + const char* root_anchor_file = ROOT_ANCHOR_FILE; + const char* root_cert_file = ROOT_CERT_FILE; + const char* urlname = URLNAME; + const char* xmlname = XMLNAME; + const char* p7sname = P7SNAME; + const char* p7signer = P7SIGNER; + const char* res_conf = NULL; + const char* root_hints = NULL; + const 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:hln:r: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 'n': + p7signer = 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, p7signer, res_conf, root_hints, debugconf, + ip4only, ip6only, force, port); +} diff --git a/external/unbound/smallapp/unbound-checkconf.c b/external/unbound/smallapp/unbound-checkconf.c new file mode 100644 index 000000000..e26ae91c4 --- /dev/null +++ b/external/unbound/smallapp/unbound-checkconf.c @@ -0,0 +1,522 @@ +/* + * 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 COPYRIGHT + * HOLDER 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" +#include "ldns/sbuffer.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 = sldns_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); + sldns_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)); + (void)strlcat(buf, "/", sizeof(buf)); + } + (void)strlcat(buf, fname, sizeof(buf)); + 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); + } + if(opt) { + print_option(cfg, opt); + config_delete(cfg); + return; + } + 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); + 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/external/unbound/smallapp/unbound-control-setup.sh.in b/external/unbound/smallapp/unbound-control-setup.sh.in new file mode 100644 index 000000000..79605dc6f --- /dev/null +++ b/external/unbound/smallapp/unbound-control-setup.sh.in @@ -0,0 +1,163 @@ +#!/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 COPYRIGHT +# HOLDER 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 +prefix=@prefix@ +DESTDIR=@sysconfdir@/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 0027 + +# 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/external/unbound/smallapp/unbound-control.c b/external/unbound/smallapp/unbound-control.c new file mode 100644 index 000000000..9b78cef94 --- /dev/null +++ b/external/unbound/smallapp/unbound-control.c @@ -0,0 +1,438 @@ +/* + * 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 COPYRIGHT + * HOLDER 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(" -q quiet (don't print anything if it works ok).\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_bogus flush all bogus data\n"); + printf(" flush_negative flush all negative data\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(" insecure_add zone add domain-insecure zone\n"); + printf(" insecure_remove zone remove domain-insecure zone\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) { +#ifndef USE_WINSOCK + log_err_addr("connect", strerror(errno), &addr, addrlen); + if(errno == ECONNREFUSED && statuscmd) { + printf("unbound is stopped\n"); + exit(3); + } +#else + log_err_addr("connect", wsa_strerror(WSAGetLastError()), &addr, addrlen); + 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 quiet, 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; + if(first_line && strncmp(buf, "error", 5) == 0) { + printf("%s", buf); + was_error = 1; + } else if (!quiet) + printf("%s", buf); + + 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 quiet, 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, quiet, 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; + int quiet = 0; + 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 seed=(unsigned)time(NULL) ^ (unsigned)getpid(); + unsigned int v = seed; + 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:qh")) != -1) { + switch(c) { + case 'c': + cfgfile = optarg; + break; + case 's': + svr = optarg; + break; + case 'q': + quiet = 1; + 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, quiet, argc, argv); + +#ifdef USE_WINSOCK + WSACleanup(); +#endif + checklock_stop(); + return ret; +} diff --git a/external/unbound/smallapp/unbound-host.c b/external/unbound/smallapp/unbound-host.c new file mode 100644 index 000000000..8020ad8c8 --- /dev/null +++ b/external/unbound/smallapp/unbound-host.c @@ -0,0 +1,497 @@ +/* + * 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 COPYRIGHT + * HOLDER 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/rrdef.h" +#include "ldns/wire2str.h" +#ifdef HAVE_NSS +/* nss3 */ +#include "nss.h" +#endif + +/** 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(" -D DNSSEC enable with default root anchor\n"); + printf(" from %s\n", ROOT_ANCHOR_FILE); + 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 = sldns_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 = sldns_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[16]; + sldns_wire2str_type_buf((uint16_t)t, d, sizeof(d)); + snprintf(s, len, "%s", d); +} + +/** nice string for class */ +static void +pretty_class(char* s, size_t len, int c) +{ + char d[16]; + sldns_wire2str_class_buf((uint16_t)c, d, sizeof(d)); + snprintf(s, len, "%s", d); +} + +/** nice string for rcode */ +static void +pretty_rcode(char* s, size_t len, int r) +{ + char d[16]; + sldns_wire2str_rcode_buf(r, d, sizeof(d)); + snprintf(s, len, "%s", d); +} + +/** convert and print rdata */ +static void +print_rd(int t, char* data, size_t len) +{ + char s[65535]; + sldns_wire2str_rdata_buf((uint8_t*)data, len, s, sizeof(s), (uint16_t)t); + printf(" %s", s); +} + +/** 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) { + char* s = sldns_wire2str_pkt( + result->answer_packet, + (size_t)result->answer_len); + if(!s) { + fprintf(stderr, "alloc failure\n"); + exit(1); + } + printf("%s\n", s); + } 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); + } + /* no need to fetch additional targets, we only do few lookups */ + check_ub_res(ub_ctx_set_option(ctx, "target-fetch-policy:", "0 0 0 0 0")); + + /* parse the options */ + while( (c=getopt(argc, argv, "46DF: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': + check_ub_res(ub_ctx_add_ta_file(ctx, ROOT_ANCHOR_FILE)); + 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(); + +#ifdef HAVE_NSS + if(NSS_NoDB_Init(".") != SECSuccess) { + fprintf(stderr, "could not init NSS\n"); + return 1; + } +#endif + lookup(ctx, argv[0], qtype, qclass); + return 0; +} diff --git a/external/unbound/smallapp/worker_cb.c b/external/unbound/smallapp/worker_cb.c new file mode 100644 index 000000000..8193bec1b --- /dev/null +++ b/external/unbound/smallapp/worker_cb.c @@ -0,0 +1,250 @@ +/* + * 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 COPYRIGHT + * HOLDER 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 "libunbound/context.h" +#include "libunbound/worker.h" +#include "util/fptr_wlist.h" +#include "util/log.h" +#include "services/mesh.h" + +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), + int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), + socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), + size_t ATTR_UNUSED(zonelen), 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), + int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), + socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), + size_t ATTR_UNUSED(zonelen), 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), + struct sldns_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), + struct sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s), + char* ATTR_UNUSED(why_bogus)) +{ + log_assert(0); +} + +void libworker_event_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), + struct sldns_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); +} |