From 0573cda1b335a9dfbcc33d5b61964dcaae2ed165 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Sun, 27 Mar 2016 19:27:30 +0200 Subject: WIP --- Makefile | 9 +- c_src/Makefile | 17 + c_src/dnssec.c | 138 ++++ c_src/erlport.c | 119 +++ c_src/erlport.h | 18 + c_src/net_read_write.c | 93 +++ c_src/net_read_write.h | 10 + ebin/catlfish.app | 4 +- src/catlfish.erl | 4 +- src/dnssecport.erl | 130 +++ src/v1.erl | 55 +- test/check.erl | 7 +- tools/dnssec/Makefile | 12 + tools/dnssec/README.md | 3 + tools/dnssec/dns-net2wire.c | 1852 ++++++++++++++++++++++++++++++++++++++++++ tools/dnssec/dns-wire2text.c | 149 ++++ tools/dnssec/net2wire.c | 301 +++++++ tools/submitcert.py | 19 +- 18 files changed, 2902 insertions(+), 38 deletions(-) create mode 100644 c_src/Makefile create mode 100644 c_src/dnssec.c create mode 100644 c_src/erlport.c create mode 100644 c_src/erlport.h create mode 100644 c_src/net_read_write.c create mode 100644 c_src/net_read_write.h create mode 100644 src/dnssecport.erl create mode 100644 tools/dnssec/Makefile create mode 100644 tools/dnssec/README.md create mode 100644 tools/dnssec/dns-net2wire.c create mode 100644 tools/dnssec/dns-wire2text.c create mode 100644 tools/dnssec/net2wire.c diff --git a/Makefile b/Makefile index 5435c43..58dc408 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,14 @@ INSTDIR=$(PREFIX)/catlfish SOFTHSM=/usr/local/bin/softhsm2-util build all: + (cd c_src && make all) + mkdir -p priv + cp c_src/dnssecport priv/ ./make.erl clean: + (cd c_src && make clean) + -rm priv/dnssecport -rm ebin/*.beam release: all @@ -232,7 +237,9 @@ dialyze: build dialyzer ebin tags: - find . -name \*.?rl -o -name \*.py | etags - + etags src/*.[he]rl + etags --append c_src/*.[ch] + etags --append tools/*.py # Unit testing. check: all diff --git a/c_src/Makefile b/c_src/Makefile new file mode 100644 index 0000000..ae9c73e --- /dev/null +++ b/c_src/Makefile @@ -0,0 +1,17 @@ +CC = gcc +#CFLAGS = -Wall -Werror -std=gnu99 +CFLAGS = -Wall -Werror -std=gnu99 -DTEST +LDFLAGS = + +PORTS = dnssecport + +common_OBJS = erlport.o net_read_write.o +dnssecport_OBJS = dnssec.o dnssec_test.o $(common_OBJS) + +all: $(PORTS) + +clean: + rm -f $(common_OBJS) $(dnssecport_OBJS) $(PORTS) + +dnssecport: $(dnssecport_OBJS) + $(CC) -o $@ $^ $(LDFLAGS) -ldl -lgetdns diff --git a/c_src/dnssec.c b/c_src/dnssec.c new file mode 100644 index 0000000..693d645 --- /dev/null +++ b/c_src/dnssec.c @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2016, NORDUnet A/S. + * See LICENSE for licensing information. + */ + +#include +#include +#include +#include +#include +#include +#include "erlport.h" +#include "dnssec_test.h" + +static char *testmode = NULL; + +/* getdns/src/convert.c */ +getdns_return_t getdns_wire2rr_dict(const uint8_t *wire, size_t wire_len, + getdns_dict **rr_dict); + +#if !defined(TEST) +static getdns_return_t +validate(const unsigned char *records, + size_t records_len, + getdns_list *trust_anchors) +{ + getdns_return_t r = GETDNS_DNSSEC_INDETERMINATE; + getdns_list *to_validate = getdns_list_create(); + getdns_list *support_records = getdns_list_create(); + getdns_dict *records_dict = NULL; + + if (to_validate == NULL || support_records == NULL) + { + r = GETDNS_RETURN_MEMORY_ERROR; + goto out; + } + + /* TODO: figure out if we get _all_ RRs in records here bc i have + the feeling that we're not supposed to mix RR types in the same + dict; maybe this will help some: + https://getdnsapi.net/pipermail/users/2015-May/000032.html + */ + r = getdns_wire2rr_dict(records, records_len, &records_dict); + if (r) + goto out; + + /* + to_validate: one dict with the DS and one with a RRSIG for that DS + support_records: DS and DNSKEY dicts with accompanying RRSIG's + trust_anchors: DNSKEY (or DS) + */ + r = getdns_list_set_dict(to_validate, i, records_dict); + if (r) + goto out; + + r = getdns_validate_dnssec(to_validate, + support_records, + trust_anchors); + +out: + if (to_validate) + getdns_list_destroy(to_validate); + if (support_records) + getdns_list_destroy(support_records); + + return r; +} +#endif /* !TEST */ + +static void +loop(getdns_list *trust_anchors) +{ + getdns_return_t r = GETDNS_RETURN_GENERIC_ERROR; + unsigned char buf[65536]; + ssize_t len; + + while ((len = read_command(buf, sizeof(buf), 4)) > 0) + { + unsigned char *reply = NULL; + +#if !defined(TEST) + r = validate(buf, len, trust_anchors); +#else + r = test_validate(buf, len, trust_anchors, testmode); +#endif + + switch (r) + { + case GETDNS_RETURN_GOOD: + reply = (unsigned char *) "valid"; + break; + default: + fprintf(stderr, "error %d\n", r); /* DEBUG */ + reply = (unsigned char *) "err"; + } + + write_reply(reply, strlen((const char *) reply), 4); + } +} + +int +main(int argc, char *argv[]) +{ + int c; + getdns_list *trust_anchors = NULL; + time_t trust_anchor_date; + + while (1) { + static struct option long_options[] = { + {"testmode", required_argument, NULL, 't'}, + {0, 0, 0, 0}}; + + c = getopt_long(argc, argv, "", long_options, NULL); + if (c == -1) + break; + + switch (c) + { +#if defined(TEST) + case 't': + testmode = optarg; + break; +#endif + default: + fprintf(stderr, "dnssecport: bad option: %s", argv[optind]); + return -1; + } + } + + if (optind < argc) /* Using getdns trust anchor. */ + { + trust_anchors = getdns_root_trust_anchor(&trust_anchor_date); + } + + loop(trust_anchors); + + return 0; +} diff --git a/c_src/erlport.c b/c_src/erlport.c new file mode 100644 index 0000000..372f98d --- /dev/null +++ b/c_src/erlport.c @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2014-2015, NORDUnet A/S. + * See LICENSE for licensing information. + */ + +#include +#include +#include +#include +#include +#include + +#include "net_read_write.h" +#include "erlport.h" + +static ssize_t +read_length(size_t length_size) +{ + unsigned char buf[4]; + + if (length_size != 2 && length_size != 4) { + return -1; + } + + if (length_size > sizeof(buf)) { + return -1; + } + + ssize_t ret; + + ret = net_read(0, (char *)buf, length_size); + + if (ret != (ssize_t) length_size) { + return -1; + } + + if (length_size == 2) { + return (ssize_t)(((unsigned long)buf[0] << 8) | (unsigned long)buf[1]); + } else { + return (ssize_t)(((unsigned long)buf[0] << 24) | + ((unsigned long)buf[1] << 16) | + ((unsigned long)buf[2] << 8) | + (unsigned long)buf[3]); + } +} + +ssize_t +read_command(unsigned char *buf, size_t maxlen, size_t length_size) +{ + ssize_t len; + + len = read_length(length_size); + + if (len < 0) { + return -1; + } + + if (len > (ssize_t) maxlen) { + return -1; + } + return net_read(0, (char *)buf, (size_t)len); +} + +static int +write_length(size_t len, size_t length_size) +{ + unsigned char buf[4]; + + if (length_size != 2 && length_size != 4) { + return -1; + } + + if (length_size == 2) { + buf[0] = (len >> 8) & 0xff; + buf[1] = len & 0xff; + } else { + buf[0] = (len >> 24) & 0xff; + buf[1] = (len >> 16) & 0xff; + buf[2] = (len >> 8) & 0xff; + buf[3] = len & 0xff; + } + + ssize_t ret; + + ret = net_write(1, (char *)buf, length_size); + + if (ret < 0) { + return -1; + } + + if (ret != (ssize_t) length_size) { + return -1; + } + + return 0; +} + +int +write_reply(unsigned char *msg, size_t len, size_t length_size) +{ + ssize_t ret; + + ret = write_length(len, length_size); + if (ret < 0) { + return -1; + } + ret = net_write(1, (char *)msg, len); + if (ret < 0) { + return -1; + } + + return 0; +} + +int +write_status(char *msg) +{ + return write_reply((unsigned char *)msg, strlen(msg), 2); +} diff --git a/c_src/erlport.h b/c_src/erlport.h new file mode 100644 index 0000000..58b2591 --- /dev/null +++ b/c_src/erlport.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2014-2015, NORDUnet A/S. + * See LICENSE for licensing information. + */ + +#ifndef ERLPORT_H +#define ERLPORT_H + +ssize_t +read_command(unsigned char *buf, size_t maxlen, size_t length_size); + +int +write_reply(unsigned char *msg, size_t len, size_t length_size); + +int +write_status(char *msg); + +#endif diff --git a/c_src/net_read_write.c b/c_src/net_read_write.c new file mode 100644 index 0000000..f8f14f0 --- /dev/null +++ b/c_src/net_read_write.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 1995, 1996, 1997, 1998 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. Neither the name of the Institute 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 INSTITUTE 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 INSTITUTE 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "net_read_write.h" + +/* + * Like read but never return partial data. + */ + +ssize_t +net_read (int fd, void *buf, size_t nbytes) +{ + char *cbuf = (char *)buf; + ssize_t count; + size_t rem = nbytes; + + while (rem > 0) { + count = read (fd, cbuf, rem); + if (count < 0) { + if (errno == EINTR) + continue; + else + return count; + } else if (count == 0) { + return count; + } + cbuf += (size_t) count; + rem -= (size_t) count; + } + return (ssize_t)nbytes; +} + +/* + * Like write but never return partial data. + */ + +ssize_t +net_write (int fd, const void *buf, size_t nbytes) +{ + const char *cbuf = (const char *)buf; + ssize_t count; + size_t rem = nbytes; + + while (rem > 0) { + count = write (fd, cbuf, rem); + if (count < 0) { + if (errno == EINTR) + continue; + else + return count; + } + cbuf += (size_t)count; + rem -= (size_t)count; + } + return (ssize_t)nbytes; +} diff --git a/c_src/net_read_write.h b/c_src/net_read_write.h new file mode 100644 index 0000000..80b92b3 --- /dev/null +++ b/c_src/net_read_write.h @@ -0,0 +1,10 @@ +#ifndef NET_READ_WRITE_H +#define NET_READ_WRITE_H + +ssize_t +net_read (int, void *, size_t); + +ssize_t +net_write (int, const void *, size_t); + +#endif diff --git a/ebin/catlfish.app b/ebin/catlfish.app index af48ae3..aa833bc 100644 --- a/ebin/catlfish.app +++ b/ebin/catlfish.app @@ -1,4 +1,4 @@ -%%% Copyright (c) 2014-2015, NORDUnet A/S. +%%% Copyright (c) 2014-2016, NORDUnet A/S. %%% See LICENSE for licensing information. %% Application resource file for catlfish. @@ -6,6 +6,6 @@ {application, catlfish, [{description, "catlfish -- Certificate Transparency Log Server"}, {vsn, "0.9.0-dev"}, - {modules, [catlfish, catlfish_app, catlfish_sup, catlfish_web, v1, x509]}, + {modules, [catlfish, catlfish_app, catlfish_sup, catlfish_web, v1, ratelimit]}, {applications, [kernel, stdlib, plop, lager, mochiweb, idna, asn1, crypto]}, {mod, {catlfish_app, []}}]}. diff --git a/src/catlfish.erl b/src/catlfish.erl index e3b5939..711deaa 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -286,7 +286,7 @@ entryhash_from_entry(PackedEntry) -> crypto:hash(sha256, [Cert | Chain]). %% Private functions. --spec pack_entry(normal|precert, binary(), binary(), [binary()]) -> binary(). +-spec pack_entry(normal|precert, binary(), binary(), [binary()]) -> list(). pack_entry(Type, MTLText, EndEntityCert, CertChain) -> [{<<"MTL1">>, MTLText}, {case Type of @@ -297,7 +297,7 @@ pack_entry(Type, MTLText, EndEntityCert, CertChain) -> list_to_binary( [tlv:encode(<<"X509">>, E) || E <- CertChain])}]. --spec unpack_entry(binary()) -> {normal|precert, binary(), binary(), [binary()]}. +-spec unpack_entry(list()) -> {normal|precert, binary(), binary(), [binary()]}. unpack_entry(Entry) -> [{<<"MTL1">>, MTLText}|Rest1] = Entry, [{EECType, EndEntityCert}|Rest2] = Rest1, diff --git a/src/dnssecport.erl b/src/dnssecport.erl new file mode 100644 index 0000000..804e727 --- /dev/null +++ b/src/dnssecport.erl @@ -0,0 +1,130 @@ +%%% Copyright (c) 2016, NORDUnet A/S. +%%% See LICENSE for licensing information. + +-module(dnssecport). +-behaviour(gen_server). +-export([start_link/0, stop/0]). +-export([validate/2]). +%% gen_server callbacks. +-export([init/1, handle_call/3, terminate/2, handle_cast/2, handle_info/2, + code_change/3]). + +-include_lib("eunit/include/eunit.hrl"). + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, + [code:priv_dir(catlfish) ++ "/dnssecport"], []). + +stop() -> + gen_server:call(?MODULE, stop). + +validate(ValidateRR, SupportRRs) -> + gen_server:call(?MODULE, {validate, [ValidateRR, SupportRRs]}). + +-record(state, {port :: port()}). + +init(Program) -> + lager:debug("starting dnssec service"), + Port = create_port(Program, []), % TODO: Pass path to dir with trust root. + {ok, #state{port = Port}}. + +handle_call(stop, _From, State) -> + lager:debug("dnssec stop request received"), + stop_port(State); +handle_call({validate, [ValidateRR, SupportRRs]}, _From, State) -> + lager:debug("dnssec validate incoming request: ~p", [ValidateRR]), + case State#state.port of + undefined -> + {error, noport}; + Port when is_port(Port) -> + S = list_to_binary(SupportRRs), + Port ! {self(), {command, <>}}, + receive + {Port, {data, Response}} -> + lager:debug("received response from dnssec port: ~p", + [Response]), + case Response of + "valid" -> + {reply, ok, State}; + Err -> + {reply, {error, Err}, State} + end; + {Port, {exit_status, ExitStatus}} -> + lager:error("dnssec port ~p exiting with status ~p", + [Port, ExitStatus]), + {stop, portexit, State#state{port = undefined}} + after + 3000 -> + lager:error("dnssec port timeout"), + {stop, timeout, State} + end + end. + +handle_info(_Info, State) -> + {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +handle_cast(_Request, State) -> + {noreply, State}. + +terminate(Reason, _State) -> + lager:info("dnssec port terminating: ~p", [Reason]), + ok. + +%%%%%%%%%%%%%%%%%%%% +create_port(Program, Args) -> + open_port({spawn_executable, Program}, + [{args, Args}, + exit_status, % Let us know if process dies. + {packet, 4}]). + +stop_port(State) -> + Port = State#state.port, + Port ! {self(), close}, + receive + {Port, closed} -> + {stop, closed, State#state{port = undefined}}; + {Port, Msg} -> + lager:debug("message received from dying port: ~p", [Msg]), + {stop, unknown, State#state{port = undefined}} + after + 2000 -> + lager:error("dnssec port ~p refuses to die", [Port]), + {stop, timeout, State} + end. + +%%%%%%%%%%%%%%%%%%%% +%% Unit tests. +start_test_port(TestType) -> + Port = create_port("priv/dnssecport", ["--testmode", atom_to_list(TestType)]), + ?debugFmt("Port: ~p", [Port]), + Port. +stop_test_port(Port) -> + {stop, closed, _State} = stop_port(#state{port = Port}), + ok. + +err_test_() -> + {setup, + fun() -> start_test_port(err) end, + fun(Port) -> stop_test_port(Port) end, + fun(Port) -> + R = handle_call({validate, [<<"invalid-DS">>, []]}, + self(), #state{port = Port}), + [ + ?_assertMatch({reply, {error, "err"}, _State}, R) + ] + end}. + +ok_test_() -> + {setup, + fun() -> start_test_port(ok) end, + fun(Port) -> stop_test_port(Port) end, + fun(Port) -> + R = handle_call({validate, [<<"invalid-DS">>, []]}, + self(), #state{port = Port}), + [ + ?_assertMatch({reply, ok, _State}, R) + ] + end}. diff --git a/src/v1.erl b/src/v1.erl index 7b7f6bf..86cd799 100644 --- a/src/v1.erl +++ b/src/v1.erl @@ -1,4 +1,4 @@ -%%% Copyright (c) 2014-2015, NORDUnet A/S. +%%% Copyright (c) 2014-2016, NORDUnet A/S. %%% See LICENSE for licensing information. %%% @doc Certificate Transparency (RFC 6962) @@ -7,7 +7,7 @@ %% API (URL) -export([request/4]). --define(APPURL_CT_V1, "open/gaol/v1"). +-define(APPURL_CT_V1, "dt/v1"). check_valid_sth() -> case plop:sth() of @@ -30,9 +30,9 @@ check_valid_sth() -> end. %% Public functions, i.e. part of URL. -request(post, ?APPURL_CT_V1, "add-blob", Input) -> +request(post, ?APPURL_CT_V1, "add-ds-rr", Input) -> check_valid_sth(), - add_blob(Input); + add_ds(Input); request(get, ?APPURL_CT_V1, "get-sth", _Query) -> check_valid_sth(), @@ -147,29 +147,36 @@ internalerror(Text) -> "~s~n" ++ "~n", [Text])}. --spec add_blob(any()) -> any(). -add_blob(Input) -> +-spec add_ds(any()) -> any(). +add_ds(Input) -> case (catch mochijson2:decode(Input)) of {error, E} -> - err400("add-blob: bad input:", E); - {struct, [{<<"blob">>, Blob}]} -> - case (catch base64:decode(Blob)) of - {'EXIT', _} -> - err400("add-blob: invalid base64-encoded blob", Blob); - DecodedBlob -> - add_blob_helper(DecodedBlob, - application:get_env(catlfish, - max_submit_size, - 0)) + err400("add-ds-rr: bad input:", E); + {struct, [{<<"chain">>, List}]} -> + case decode_chain(List) of + {invalid, ErrText} -> + err400(io:format("add-ds-rr: ~p", [ErrText]), List); + [DSRR, DSRRSIG | SupportRRs] -> + add_ds_helper(DSRR, DSRRSIG, SupportRRs); + _ -> + err400("add-ds-rr: missing one or more entries", List) end; _ -> - err400("add-blob: missing input: blob", Input) + err400("add-ds-rr: missing input: chain", Input) end. -add_blob_helper(Blob, MaxSize) when MaxSize == 0 -> - success(catlfish:add_chain(Blob, [], normal)); -add_blob_helper(Blob, MaxSize) when erlang:size(Blob) =< MaxSize -> - add_blob_helper(Blob, 0); -add_blob_helper(Blob, MaxSize) -> - err400(io_lib:format("add-blob: blob too large (~p > ~p)", - [erlang:size(Blob), MaxSize]), Blob). +decode_chain(List) -> + case (catch [base64:decode(X) || X <- List]) of + {'EXIT', _} -> + {invalid, "invalid base64-encoding"}; + L -> + L + end. + +add_ds_helper(DSRR, DSRRSIG, Support) -> + case dnssecport:dnssec_validate([DSRR, DSRRSIG], Support) of + ok -> + success(catlfish:add_chain(DSRR, [DSRRSIG | Support], normal)); + _ -> + err400("add-ds-rr: invalid DS record", [DSRR, DSRRSIG | Support]) + end. diff --git a/test/check.erl b/test/check.erl index b538346..2cb388c 100755 --- a/test/check.erl +++ b/test/check.erl @@ -1,11 +1,12 @@ #! /usr/bin/env escript %% -*- erlang -*- mode -%%! -pa ebin -pa ../lager/ebin -pa ../lager/deps/goldrush/ebin -pa ../mochiweb/ebin -config test/config/check.config +%%! -pa ebin -pa ../lager/ebin -pa ../lager/deps/goldrush/ebin -pa ../mochiweb/ebin -s lager -config test/config/check.config -%% To enable logging, pass `-s lager' by adding it to the line above. +%% To enable logging, pass `-s lager' to erlang by adding it to the +%% %%!-line above. %% Tweak the amount of logging by changing `lager_console_backend' in %% config/check.config. main(_) -> - ok = x509:test(), + ok = dnssecport:test(). ok = catlfish:test(). diff --git a/tools/dnssec/Makefile b/tools/dnssec/Makefile new file mode 100644 index 0000000..7009220 --- /dev/null +++ b/tools/dnssec/Makefile @@ -0,0 +1,12 @@ +CFLAGS = -Wall -g -std=c99 +CFLAGS_PEDANTIC = -pedantic -Werror -Wextra + +all: dns-net2wire dns-wire2text + +dns-net2wire: dns-net2wire.c + $(CC) $(CFLAGS) -o $@ -lgetdns -lgetdns_ext_event -levent $< +dns-wire2text: dns-wire2text.c + $(CC) $(CFLAGS) $(CFLAGS_PEDANTIC) -o $@ -lgetdns -lgetdns_ext_event -levent $< + +net2wire: net2wire.c + $(CC) $(CFLAGS) -I ~/usr/include -L ~/usr/lib -o $@ -lgetdns -lgetdns_ext_event -levent $< diff --git a/tools/dnssec/README.md b/tools/dnssec/README.md new file mode 100644 index 0000000..e6f2985 --- /dev/null +++ b/tools/dnssec/README.md @@ -0,0 +1,3 @@ +This directory contains tools for DNSSEC Transparency. + +- dns-wire2text reads RR's and prints them in presentation format diff --git a/tools/dnssec/dns-net2wire.c b/tools/dnssec/dns-net2wire.c new file mode 100644 index 0000000..c193139 --- /dev/null +++ b/tools/dnssec/dns-net2wire.c @@ -0,0 +1,1852 @@ +/* + Based on getdns/src/test/getdns_query.c (commit 60be402). + For license information, see end of this file. + + Example usage: + $ dns-net2wire -q +dnssec_return_validation_chain +dnssec_return_status +dnssec_return_only_secure dfri.se + */ + +#define _POSIX_SOURCE + +//#include "config.h" +//#include "debug.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_SCHED(...) + +#define MAX_TIMEOUTS FD_SETSIZE + +#define EXAMPLE_PIN "pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"" + +/* Eventloop based on select */ +typedef struct my_eventloop { + getdns_eventloop base; + getdns_eventloop_event *fd_events[FD_SETSIZE]; + uint64_t fd_timeout_times[FD_SETSIZE]; + getdns_eventloop_event *timeout_events[MAX_TIMEOUTS]; + uint64_t timeout_times[MAX_TIMEOUTS]; +} my_eventloop; + +static uint64_t get_now_plus(uint64_t amount) +{ + struct timeval tv; + uint64_t now; + + if (gettimeofday(&tv, NULL)) { + perror("gettimeofday() failed"); + exit(EXIT_FAILURE); + } + now = tv.tv_sec * 1000000 + tv.tv_usec; + + return (now + amount * 1000) >= now ? now + amount * 1000 : -1; +} + +getdns_return_t +my_eventloop_schedule(getdns_eventloop *loop, + int fd, uint64_t timeout, getdns_eventloop_event *event) +{ + my_eventloop *my_loop = (my_eventloop *)loop; + size_t i; + + assert(loop); + assert(event); + assert(fd < FD_SETSIZE); + + DEBUG_SCHED( "%s(loop: %p, fd: %d, timeout: %"PRIu64", event: %p)\n" + , __FUNCTION__, loop, fd, timeout, event); + if (fd >= 0 && (event->read_cb || event->write_cb)) { + assert(my_loop->fd_events[fd] == NULL); + + my_loop->fd_events[fd] = event; + my_loop->fd_timeout_times[fd] = get_now_plus(timeout); + event->ev = (void *) (intptr_t) fd + 1; + + DEBUG_SCHED( "scheduled read/write at %d\n", fd); + return GETDNS_RETURN_GOOD; + } + + assert(event->timeout_cb && !event->read_cb && !event->write_cb); + + for (i = 0; i < MAX_TIMEOUTS; i++) { + if (my_loop->timeout_events[i] == NULL) { + my_loop->timeout_events[i] = event; + my_loop->timeout_times[i] = get_now_plus(timeout); + event->ev = (void *) (intptr_t) i + 1; + + DEBUG_SCHED( "scheduled timeout at %d\n", (int)i); + return GETDNS_RETURN_GOOD; + } + } + return GETDNS_RETURN_GENERIC_ERROR; +} + +getdns_return_t +my_eventloop_clear(getdns_eventloop *loop, getdns_eventloop_event *event) +{ + my_eventloop *my_loop = (my_eventloop *)loop; + size_t i; + + assert(loop); + assert(event); + + DEBUG_SCHED( "%s(loop: %p, event: %p)\n", __FUNCTION__, loop, event); + + i = (intptr_t)event->ev - 1; + assert(i >= 0 && i < FD_SETSIZE); + + if (event->timeout_cb && !event->read_cb && !event->write_cb) { + assert(my_loop->timeout_events[i] == event); + my_loop->timeout_events[i] = NULL; + } else { + assert(my_loop->fd_events[i] == event); + my_loop->fd_events[i] = NULL; + } + event->ev = NULL; + return GETDNS_RETURN_GOOD; +} + +void my_eventloop_cleanup(getdns_eventloop *loop) +{ +} + +void my_read_cb(int fd, getdns_eventloop_event *event) +{ + DEBUG_SCHED( "%s(fd: %d, event: %p)\n", __FUNCTION__, fd, event); + event->read_cb(event->userarg); +} + +void my_write_cb(int fd, getdns_eventloop_event *event) +{ + DEBUG_SCHED( "%s(fd: %d, event: %p)\n", __FUNCTION__, fd, event); + event->write_cb(event->userarg); +} + +void my_timeout_cb(int fd, getdns_eventloop_event *event) +{ + DEBUG_SCHED( "%s(fd: %d, event: %p)\n", __FUNCTION__, fd, event); + event->timeout_cb(event->userarg); +} + +void my_eventloop_run_once(getdns_eventloop *loop, int blocking) +{ + my_eventloop *my_loop = (my_eventloop *)loop; + + fd_set readfds, writefds; + int fd, max_fd = -1; + uint64_t now, timeout = (uint64_t)-1; + size_t i; + struct timeval tv; + + assert(loop); + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + now = get_now_plus(0); + + for (i = 0; i < MAX_TIMEOUTS; i++) { + if (!my_loop->timeout_events[i]) + continue; + if (now > my_loop->timeout_times[i]) + my_timeout_cb(-1, my_loop->timeout_events[i]); + else if (my_loop->timeout_times[i] < timeout) + timeout = my_loop->timeout_times[i]; + } + for (fd = 0; fd < FD_SETSIZE; fd++) { + if (!my_loop->fd_events[fd]) + continue; + if (my_loop->fd_events[fd]->read_cb) + FD_SET(fd, &readfds); + if (my_loop->fd_events[fd]->write_cb) + FD_SET(fd, &writefds); + if (fd > max_fd) + max_fd = fd; + if (my_loop->fd_timeout_times[fd] < timeout) + timeout = my_loop->fd_timeout_times[fd]; + } + if (max_fd == -1 && timeout == (uint64_t)-1) + return; + + if (! blocking || now > timeout) { + tv.tv_sec = 0; + tv.tv_usec = 0; + } else { + tv.tv_sec = (timeout - now) / 1000000; + tv.tv_usec = (timeout - now) % 1000000; + } + if (select(max_fd + 1, &readfds, &writefds, NULL, &tv) < 0) { + perror("select() failed"); + exit(EXIT_FAILURE); + } + now = get_now_plus(0); + for (fd = 0; fd < FD_SETSIZE; fd++) { + if (my_loop->fd_events[fd] && + my_loop->fd_events[fd]->read_cb && + FD_ISSET(fd, &readfds)) + my_read_cb(fd, my_loop->fd_events[fd]); + + if (my_loop->fd_events[fd] && + my_loop->fd_events[fd]->write_cb && + FD_ISSET(fd, &writefds)) + my_write_cb(fd, my_loop->fd_events[fd]); + + if (my_loop->fd_events[fd] && + my_loop->fd_events[fd]->timeout_cb && + now > my_loop->fd_timeout_times[fd]) + my_timeout_cb(fd, my_loop->fd_events[fd]); + + i = fd; + if (my_loop->timeout_events[i] && + my_loop->timeout_events[i]->timeout_cb && + now > my_loop->timeout_times[i]) + my_timeout_cb(-1, my_loop->timeout_events[i]); + } +} + +void my_eventloop_run(getdns_eventloop *loop) +{ + my_eventloop *my_loop = (my_eventloop *)loop; + size_t i; + + assert(loop); + + i = 0; + while (i < MAX_TIMEOUTS) { + if (my_loop->fd_events[i] || my_loop->timeout_events[i]) { + my_eventloop_run_once(loop, 1); + i = 0; + } else { + i++; + } + } +} + +void my_eventloop_init(my_eventloop *loop) +{ + static getdns_eventloop_vmt my_eventloop_vmt = { + my_eventloop_cleanup, + my_eventloop_schedule, + my_eventloop_clear, + my_eventloop_run, + my_eventloop_run_once + }; + + (void) memset(loop, 0, sizeof(my_eventloop)); + loop->base.vmt = &my_eventloop_vmt; +} + +static int quiet = 0; +static int batch_mode = 0; +static char *query_file = NULL; +static int json = 0; +static char *the_root = "."; +static char *name; +static getdns_context *context; +static getdns_dict *extensions; +static getdns_list *pubkey_pinset = NULL; +static size_t pincount = 0; +static uint16_t request_type = GETDNS_RRTYPE_NS; +static int timeout, edns0_size, padding_blocksize; +static int async = 0, interactive = 0; +static enum { GENERAL, ADDRESS, HOSTNAME, SERVICE } calltype = GENERAL; + +int get_rrtype(const char *t); + +int gqldns_b64_pton(char const *src, uint8_t *target, size_t targsize) +{ + const uint8_t pad64 = 64; /* is 64th in the b64 array */ + const char* s = src; + uint8_t in[4]; + size_t o = 0, incount = 0; + + while(*s) { + /* skip any character that is not base64 */ + /* conceptually we do: + const char* b64 = pad'=' is appended to array + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + const char* d = strchr(b64, *s++); + and use d-b64; + */ + char d = *s++; + if(d <= 'Z' && d >= 'A') + d -= 'A'; + else if(d <= 'z' && d >= 'a') + d = d - 'a' + 26; + else if(d <= '9' && d >= '0') + d = d - '0' + 52; + else if(d == '+') + d = 62; + else if(d == '/') + d = 63; + else if(d == '=') + d = 64; + else continue; + in[incount++] = (uint8_t)d; + if(incount != 4) + continue; + /* process whole block of 4 characters into 3 output bytes */ + if(in[3] == pad64 && in[2] == pad64) { /* A B = = */ + if(o+1 > targsize) + return -1; + target[o] = (in[0]<<2) | ((in[1]&0x30)>>4); + o += 1; + break; /* we are done */ + } else if(in[3] == pad64) { /* A B C = */ + if(o+2 > targsize) + return -1; + target[o] = (in[0]<<2) | ((in[1]&0x30)>>4); + target[o+1]= ((in[1]&0x0f)<<4) | ((in[2]&0x3c)>>2); + o += 2; + break; /* we are done */ + } else { + if(o+3 > targsize) + return -1; + /* write xxxxxxyy yyyyzzzz zzwwwwww */ + target[o] = (in[0]<<2) | ((in[1]&0x30)>>4); + target[o+1]= ((in[1]&0x0f)<<4) | ((in[2]&0x3c)>>2); + target[o+2]= ((in[2]&0x03)<<6) | in[3]; + o += 3; + } + incount = 0; + } + return (int)o; +} + +getdns_dict * +ipaddr_dict(getdns_context *context, char *ipstr) +{ + getdns_dict *r = getdns_dict_create_with_context(context); + char *s = strchr(ipstr, '%'), *scope_id_str = ""; + char *p = strchr(ipstr, '@'), *portstr = ""; + char *t = strchr(ipstr, '#'), *tls_portstr = ""; + char *n = strchr(ipstr, '~'), *tls_namestr = ""; + /* ^[alg:]name:key */ + char *T = strchr(ipstr, '^'), *tsig_name_str = "" + , *tsig_secret_str = "" + , *tsig_algorithm_str = ""; + int tsig_secret_size; + uint8_t tsig_secret_buf[256]; /* 4 times SHA512 */ + getdns_bindata tsig_secret; + uint8_t buf[sizeof(struct in6_addr)]; + getdns_bindata addr; + + addr.data = buf; + + if (!r) return NULL; + if (s) { + *s = 0; + scope_id_str = s + 1; + } + if (p) { + *p = 0; + portstr = p + 1; + } + if (t) { + *t = 0; + tls_portstr = t + 1; + } + if (n) { + *n = 0; + tls_namestr = n + 1; + } + if (T) { + *T = 0; + tsig_name_str = T + 1; + if ((T = strchr(tsig_name_str, ':'))) { + *T = 0; + tsig_secret_str = T + 1; + if ((T = strchr(tsig_secret_str, ':'))) { + *T = 0; + tsig_algorithm_str = tsig_name_str; + tsig_name_str = tsig_secret_str; + tsig_secret_str = T + 1; + } + } else { + tsig_name_str = ""; + } + } + if (strchr(ipstr, ':')) { + getdns_dict_util_set_string(r, "address_type", "IPv6"); + addr.size = 16; + if (inet_pton(AF_INET6, ipstr, buf) <= 0) { + getdns_dict_destroy(r); + return NULL; + } + } else { + getdns_dict_util_set_string(r, "address_type", "IPv4"); + addr.size = 4; + if (inet_pton(AF_INET, ipstr, buf) <= 0) { + getdns_dict_destroy(r); + return NULL; + } + } + getdns_dict_set_bindata(r, "address_data", &addr); + if (*portstr) + getdns_dict_set_int(r, "port", (int32_t)atoi(portstr)); + if (*tls_portstr) + getdns_dict_set_int(r, "tls_port", (int32_t)atoi(tls_portstr)); + if (*tls_namestr) { + getdns_dict_util_set_string(r, "tls_auth_name", tls_namestr); + } + if (*scope_id_str) + getdns_dict_util_set_string(r, "scope_id", scope_id_str); + if (*tsig_name_str) + getdns_dict_util_set_string(r, "tsig_name", tsig_name_str); + if (*tsig_algorithm_str) + getdns_dict_util_set_string(r, "tsig_algorithm", tsig_algorithm_str); + if (*tsig_secret_str) { + tsig_secret_size = gqldns_b64_pton( + tsig_secret_str, tsig_secret_buf, sizeof(tsig_secret_buf)); + if (tsig_secret_size > 0) { + tsig_secret.size = tsig_secret_size; + tsig_secret.data = tsig_secret_buf; + getdns_dict_set_bindata(r, "tsig_secret", &tsig_secret); + } + } + return r; +} + +static getdns_return_t +fill_transport_list(getdns_context *context, char *transport_list_str, + getdns_transport_list_t *transports, size_t *transport_count) +{ + size_t max_transports = *transport_count; + *transport_count = 0; + for ( size_t i = 0 + ; i < max_transports && i < strlen(transport_list_str) + ; i++, (*transport_count)++) { + switch(*(transport_list_str + i)) { + case 'U': + transports[i] = GETDNS_TRANSPORT_UDP; + break; + case 'T': + transports[i] = GETDNS_TRANSPORT_TCP; + break; + case 'L': + transports[i] = GETDNS_TRANSPORT_TLS; + break; + default: + fprintf(stderr, "Unrecognised transport '%c' in string %s\n", + *(transport_list_str + i), transport_list_str); + return GETDNS_RETURN_GENERIC_ERROR; + } + } + return GETDNS_RETURN_GOOD; +} + +void +print_usage(FILE *out, const char *progname) +{ + fprintf(out, "usage: %s [