From b3e4186cf22d67907d48547d79665f2da819ba65 Mon Sep 17 00:00:00 2001 From: Wouter Wijngaards Date: Fri, 1 Oct 2010 11:31:35 +0000 Subject: [PATCH] Test for unbound-anchor. git-svn-id: file:///svn/unbound/trunk@2268 be551aaa-1e26-0410-a405-d3ace91eadb9 --- Makefile.in | 10 +- doc/Changelog | 3 + doc/unbound-anchor.8.in | 2 +- smallapp/unbound-anchor.c | 19 +- testcode/petal.c | 582 ++++++++++++++++++++++++++++++++ testdata/10-unbound-anchor.tpkg | Bin 0 -> 12567 bytes 6 files changed, 608 insertions(+), 8 deletions(-) create mode 100644 testcode/petal.c create mode 100644 testdata/10-unbound-anchor.tpkg diff --git a/Makefile.in b/Makefile.in index 9c7116874..57d6a5c77 100644 --- a/Makefile.in +++ b/Makefile.in @@ -119,6 +119,8 @@ TESTBOUND_SRC=testcode/testbound.c testcode/ldns-testpkts.c \ TESTBOUND_OBJ=$(addprefix $(BUILD),$(TESTBOUND_SRC:.c=.lo)) $(COMPAT_OBJ) LOCKVERIFY_SRC=testcode/lock_verify.c smallapp/worker_cb.c $(COMMON_SRC) LOCKVERIFY_OBJ=$(addprefix $(BUILD),$(LOCKVERIFY_SRC:.c=.lo)) $(COMPAT_OBJ) +PETAL_SRC=testcode/petal.c +PETAL_OBJ=$(addprefix $(BUILD),$(PETAL_SRC:.c=.lo)) $(COMPAT_OBJ) PKTVIEW_SRC=testcode/pktview.c testcode/readhex.c smallapp/worker_cb.c \ $(COMMON_SRC) PKTVIEW_OBJ=$(addprefix $(BUILD),$(PKTVIEW_SRC:.c=.lo)) $(COMPAT_OBJ) @@ -143,7 +145,7 @@ ALL_SRC=$(sort $(COMMON_SRC) $(UNITTEST_SRC) $(DAEMON_SRC) \ $(TESTBOUND_SRC) $(LOCKVERIFY_SRC) $(PKTVIEW_SRC) $(SIGNIT_SRC) \ $(MEMSTATS_SRC) $(CHECKCONF_SRC) $(LIBUNBOUND_SRC) $(HOST_SRC) \ $(ASYNCLOOK_SRC) $(STREAMTCP_SRC) $(PERF_SRC) $(DELAYER_SRC) \ - $(HARVEST_SRC) $(CONTROL_SRC) $(UBANCHOR_SRC)) + $(HARVEST_SRC) $(CONTROL_SRC) $(UBANCHOR_SRC) $(PETAL_SRC)) ALL_OBJ=$(addprefix $(BUILD),$(ALL_SRC:.c=.lo) \ $(addprefix compat/,$(LIBOBJS:.o=.lo))) $(COMPAT_OBJ) @@ -194,7 +196,7 @@ $(BUILD)%.lo: $(srcdir)/%.c all: $(COMMON_OBJ) unbound$(EXEEXT) unbound-checkconf$(EXEEXT) lib unbound-host$(EXEEXT) unbound-control$(EXEEXT) unbound-anchor$(EXEEXT) unbound-control-setup $(WINAPPS) TEST_BIN=$(addsuffix $(EXEEXT),asynclook delayer harvest lock-verify \ - memstats perf pktview signit streamtcp testbound unittest) + memstats perf petal pktview signit streamtcp testbound unittest) tests: all $(TEST_BIN) check: test @@ -269,6 +271,10 @@ lock-verify$(EXEEXT): $(LOCKVERIFY_OBJ) $(ldnslib) $(INFO) Link $@ $Q$(LINK) -o $@ $(sort $(LOCKVERIFY_OBJ)) $(LIBS) +petal$(EXEEXT): $(PETAL_OBJ) + $(INFO) Link $@ + $Q$(LINK) -o $@ $(sort $(PETAL_OBJ)) -lssl $(LIBS) + pktview$(EXEEXT): $(PKTVIEW_OBJ) $(ldnslib) $(INFO) Link $@ $Q$(LINK) -o $@ $(sort $(PKTVIEW_OBJ)) $(LIBS) diff --git a/doc/Changelog b/doc/Changelog index ff08d5311..39e257253 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +1 October 2010: Wouter + - test for unbound-anchor. fix for reading certs. + 28 September 2010: Wouter - unbound-anchor working, it creates or updates a root.key file. Use it before you start the validator (e.g. at system boot time). diff --git a/doc/unbound-anchor.8.in b/doc/unbound-anchor.8.in index b333f49a0..d98ccb4aa 100644 --- a/doc/unbound-anchor.8.in +++ b/doc/unbound-anchor.8.in @@ -1,6 +1,6 @@ .TH "unbound-anchor" "8" "@date@" "NLnet Labs" "unbound @version@" .\" -.\" unbound-control.8 -- unbound remote control manual +.\" unbound-anchor.8 -- unbound anchor maintenance utility manual .\" .\" Copyright (c) 2008, NLnet Labs. All rights reserved. .\" diff --git a/smallapp/unbound-anchor.c b/smallapp/unbound-anchor.c index 746438618..901353524 100644 --- a/smallapp/unbound-anchor.c +++ b/smallapp/unbound-anchor.c @@ -322,7 +322,10 @@ read_cert_bio(BIO* bio) 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"); + if(verb) { + printf("failed to read X509\n"); + ERR_print_errors_fp(stdout); + } continue; } if(!sk_X509_push(sk, x)) { @@ -340,6 +343,7 @@ read_cert_file(char* file) STACK_OF(X509)* sk; FILE* in; int content = 0; + char buf[128]; if(file == NULL || strcmp(file, "") == 0) { return NULL; } @@ -359,7 +363,10 @@ read_cert_file(char* file) while(!feof(in)) { X509* x = PEM_read_X509(in, NULL, 0, NULL); if(x == NULL) { - if(verb) printf("failed to read X509\n"); + if(verb) { + printf("failed to read X509 file\n"); + ERR_print_errors_fp(stdout); + } continue; } if(!sk_X509_push(sk, x)) { @@ -367,6 +374,8 @@ read_cert_file(char* file) fclose(in); exit(0); } + /* read away newline after --END CERT-- */ + (void)fgets(buf, (int)sizeof(buf), in); content = 1; } fclose(in); @@ -1008,8 +1017,8 @@ read_http_result(SSL* ssl) /* do the chunked version */ BIO* tmp = do_chunked_read(ssl); char* d = NULL; - long l; - l = BIO_get_mem_data(tmp, &d); + size_t l; + 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"); @@ -1017,7 +1026,7 @@ read_http_result(SSL* ssl) } /* the result is zero terminated for robustness, but we * do not include that in the BIO len (for binary data) */ - len = (size_t)l-1; + len = l-1; data = (char*)malloc(l); if(data == NULL) { if(verb) printf("out of memory\n"); diff --git a/testcode/petal.c b/testcode/petal.c new file mode 100644 index 000000000..2e6f1c53e --- /dev/null +++ b/testcode/petal.c @@ -0,0 +1,582 @@ +/* + * petal.c - https daemon that is small and beautiful. + * + * Copyright (c) 2010, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * HTTP1.1/SSL server. + */ + +#include "config.h" +#ifdef HAVE_GETOPT_H +#include +#endif +#ifdef HAVE_OPENSSL_SSL_H +#include +#endif +#ifdef HAVE_OPENSSL_ERR_H +#include +#endif +#ifdef HAVE_OPENSSL_RAND_H +#include +#endif +#include +#include +#include +#include + +/** verbosity for this application */ +static int verb = 0; + +/** Give petal usage, and exit (1). */ +static void +usage() +{ + printf("Usage: petal [opts]\n"); + printf(" https daemon serves files from ./'host'/filename\n"); + printf(" (no hostname: from the 'default' directory)\n"); + printf("-a addr bind to this address, 127.0.0.1\n"); + printf("-p port port number, default 443\n"); + printf("-k keyfile SSL private key file (PEM), petal.key\n"); + printf("-c certfile SSL certificate file (PEM), petal.pem\n"); + printf("-v more verbose\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); +} + +/** fatal exit */ +static void print_exit(const char* str) {printf("error %s\n", str); exit(1);} +/** print errno */ +static void log_errno(const char* str) +{printf("error %s: %s\n", str, strerror(errno));} + +/** parse a text IP address into a sockaddr */ +static int +parse_ip_addr(char* str, int port, struct sockaddr_storage* ret, socklen_t* l) +{ + socklen_t len = 0; + struct sockaddr_storage* addr = NULL; + struct sockaddr_in6 a6; + struct sockaddr_in a; + uint16_t p = (uint16_t)port; + int fam = 0; + memset(&a6, 0, sizeof(a6)); + memset(&a, 0, sizeof(a)); + + if(inet_pton(AF_INET6, str, &a6.sin6_addr) > 0) { + /* it is an IPv6 */ + fam = AF_INET6; + a6.sin6_family = AF_INET6; + a6.sin6_port = (in_port_t)htons(p); + addr = (struct sockaddr_storage*)&a6; + len = (socklen_t)sizeof(struct sockaddr_in6); + } + if(inet_pton(AF_INET, str, &a.sin_addr) > 0) { + /* it is an IPv4 */ + fam = AF_INET; + a.sin_family = AF_INET; + a.sin_port = (in_port_t)htons(p); + addr = (struct sockaddr_storage*)&a; + len = (socklen_t)sizeof(struct sockaddr_in); + } + if(!len) print_exit("cannot parse addr"); + *l = len; + memmove(ret, addr, len); + return fam; +} + +/** close the fd */ +static void +fd_close(int fd) +{ +#ifndef USE_WINSOCK + close(fd); +#else + closesocket(fd); +#endif +} + +/** + * 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; +} + +/** process one http header */ +static int +process_one_header(char* buf, char* file, size_t flen, char* host, size_t hlen, + int* vs) +{ + if(strncasecmp(buf, "GET ", 4) == 0) { + char* e = strstr(buf, " HTTP/1.1"); + if(!e) e = strstr(buf, " http/1.1"); + if(!e) { + e = strstr(buf, " HTTP/1.0"); + if(!e) e = strstr(buf, " http/1.0"); + if(!e) e = strrchr(buf, ' '); + if(!e) e = strrchr(buf, '\t'); + if(e) *vs = 10; + } + if(e) *e = 0; + if(strlen(buf) < 4) return 0; + (void)strlcpy(file, buf+4, flen); + } else if(strncasecmp(buf, "Host: ", 6) == 0) { + (void)strlcpy(host, buf+6, hlen); + } + return 1; +} + +/** read http headers and process them */ +static int +read_http_headers(SSL* ssl, char* file, size_t flen, char* host, size_t hlen, + int* vs) +{ + char buf[1024]; + file[0] = 0; + host[0] = 0; + while(read_ssl_line(ssl, buf, sizeof(buf))) { + if(verb>=2) printf("read: %s\n", buf); + if(buf[0] == 0) + return 1; + if(!process_one_header(buf, file, flen, host, hlen, vs)) + return 0; + } + return 0; +} + +/** setup SSL context */ +static SSL_CTX* +setup_ctx(char* key, char* cert) +{ + SSL_CTX* ctx = SSL_CTX_new(SSLv23_server_method()); + if(!ctx) print_exit("out of memory"); + (void)SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); + if(!SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM)) + print_exit("cannot read cert"); + if(!SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM)) + print_exit("cannot read key"); + if(!SSL_CTX_check_private_key(ctx)) + print_exit("private key is not correct"); + if(!SSL_CTX_load_verify_locations(ctx, cert, NULL)) + print_exit("cannot load cert verify locations"); + return ctx; +} + +/** setup listening TCP */ +static int +setup_fd(char* addr, int port) +{ + struct sockaddr_storage ad; + socklen_t len; + int fd; + int c = 1; + int fam = parse_ip_addr(addr, port, &ad, &len); + fd = socket(fam, SOCK_STREAM, 0); + if(fd == -1) { + log_errno("socket"); + return -1; + } + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (void*)&c, (socklen_t) sizeof(int)) < 0) { + log_errno("setsockopt(SOL_SOCKET, SO_REUSEADDR)"); + } + if(bind(fd, (struct sockaddr*)&ad, len) == -1) { + log_errno("bind"); + fd_close(fd); + return -1; + } + if(listen(fd, 5) == -1) { + log_errno("listen"); + fd_close(fd); + return -1; + } + return fd; +} + +/** setup SSL connection to the client */ +static SSL* +setup_ssl(int s, SSL_CTX* ctx) +{ + SSL* ssl = SSL_new(ctx); + if(!ssl) return NULL; + SSL_set_accept_state(ssl); + (void)SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + if(!SSL_set_fd(ssl, s)) { + SSL_free(ssl); + return NULL; + } + return ssl; +} + +/** check a file name for safety */ +static int +file_name_is_safe(char* s) +{ + size_t l = strlen(s); + if(s[0] != '/') + return 0; /* must start with / */ + if(strstr(s, "/../")) + return 0; /* no updirs in URL */ + if(l>=3 && s[l-1]=='.' && s[l-2]=='.' && s[l-3]=='/') + return 0; /* ends with /.. */ + return 1; +} + +/** adjust host and filename */ +static void +adjust_host_file(char* host, char* file) +{ + size_t i, len; + /* remove a port number if present */ + if(strrchr(host, ':')) + *strrchr(host, ':') = 0; + /* lowercase */ + len = strlen(host); + for(i=0; i avail) break; /* robust */ + memmove(at, tmpbuf, red); + at += red; + avail -= red; + r = snprintf(at, avail, "\r\n"); + at += r; + avail -= r; + } + if(in && feof(in) && red != 0) { + r = snprintf(at, avail, "0\r\n"); + at += r; + avail -= r; + } + if(!in || feof(in)) { + r = snprintf(at, avail, "\r\n"); + at += r; + avail -= r; + } + /* send chunk */ + if(SSL_write(ssl, buf, at-buf) <= 0) { + /* SSL error */ + break; + } + + /* setup for next chunk */ + at = buf; + avail = sizeof(buf); + } while(in && !feof(in) && !ferror(in)); + + if(in) fclose(in); +} + +/** provide service to the ssl descriptor */ +static void +service_ssl(SSL* ssl, struct sockaddr_storage* from, socklen_t falen) +{ + char file[1024]; + char host[1024]; + char combined[2048]; + int vs = 11; + if(!read_http_headers(ssl, file, sizeof(file), host, sizeof(host), + &vs)) + return; + adjust_host_file(host, file); + if(host[0] == 0 || !host_name_is_safe(host)) + (void)strlcpy(host, "default", sizeof(host)); + if(!file_name_is_safe(file)) { + return; + } + snprintf(combined, sizeof(combined), "%s%s", host, file); + if(verb) { + char out[100]; + void* a = &((struct sockaddr_in*)from)->sin_addr; + if(falen != (socklen_t)sizeof(struct sockaddr_in)) + a = &((struct sockaddr_in6*)from)->sin6_addr; + out[0]=0; + (void)inet_ntop((int)((struct sockaddr_in*)from)->sin_family, + a, out, (socklen_t)sizeof(out)); + printf("%s requests %s\n", out, combined); + } + if(vs == 10) + provide_file_10(ssl, combined); + else provide_file_chunked(ssl, combined); +} + +/** provide ssl service */ +static void +do_service(char* addr, int port, char* key, char* cert) +{ + SSL_CTX* sslctx = setup_ctx(key, cert); + int fd = setup_fd(addr, port); + int go = 1; + if(fd == -1) print_exit("could not setup sockets"); + if(verb) {printf("petal start\n"); fflush(stdout);} + while(go) { + struct sockaddr_storage from; + socklen_t flen = (socklen_t)sizeof(from); + int s = accept(fd, (struct sockaddr*)&from, &flen); + if(s != -1) { + SSL* ssl = setup_ssl(s, sslctx); + if(ssl) { + service_ssl(ssl, &from, flen); + SSL_shutdown(ssl); + SSL_free(ssl); + } + fd_close(s); + } else if (verb >=2) log_errno("accept"); + } + /* if we get a kill signal, the process dies and the OS reaps us */ + if(verb) printf("petal end\n"); + fd_close(fd); + SSL_CTX_free(sslctx); +} + +/** 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 petal */ +int main(int argc, char* argv[]) +{ + int c; + int port = 443; + char* addr = "127.0.0.1", *key = "petal.key", *cert = "petal.pem"; + /* parse the options */ + while( (c=getopt(argc, argv, "a:c:k:hp:v")) != -1) { + switch(c) { + case 'a': + addr = optarg; + break; + case 'c': + cert = optarg; + break; + case 'k': + key = optarg; + break; + case 'p': + port = atoi(optarg); + break; + case 'v': + verb++; + break; + case '?': + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + if(argc != 0) + usage(); + + (void)signal(SIGPIPE, SIG_IGN); + ERR_load_crypto_strings(); + ERR_load_SSL_strings(); + OpenSSL_add_all_algorithms(); + (void)SSL_library_init(); + + do_service(addr, port, key, cert); + + CRYPTO_cleanup_all_ex_data(); + ERR_remove_state(0); + ERR_free_strings(); + RAND_cleanup(); + return 0; +} diff --git a/testdata/10-unbound-anchor.tpkg b/testdata/10-unbound-anchor.tpkg new file mode 100644 index 0000000000000000000000000000000000000000..0049021329741dcd9fc85263b9ae7130d576cb42 GIT binary patch literal 12567 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