diff options
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | c_src/Makefile | 17 | ||||
-rw-r--r-- | c_src/dnssec.c | 138 | ||||
-rw-r--r-- | c_src/erlport.c | 119 | ||||
-rw-r--r-- | c_src/erlport.h | 18 | ||||
-rw-r--r-- | c_src/net_read_write.c | 93 | ||||
-rw-r--r-- | c_src/net_read_write.h | 10 | ||||
-rw-r--r-- | ebin/catlfish.app | 4 | ||||
-rw-r--r-- | src/catlfish.erl | 4 | ||||
-rw-r--r-- | src/dnssecport.erl | 130 | ||||
-rw-r--r-- | src/v1.erl | 55 | ||||
-rwxr-xr-x | test/check.erl | 7 | ||||
-rw-r--r-- | tools/dnssec/Makefile | 12 | ||||
-rw-r--r-- | tools/dnssec/README.md | 3 | ||||
-rw-r--r-- | tools/dnssec/dns-net2wire.c | 1852 | ||||
-rw-r--r-- | tools/dnssec/dns-wire2text.c | 149 | ||||
-rw-r--r-- | tools/dnssec/net2wire.c | 301 | ||||
-rwxr-xr-x | tools/submitcert.py | 19 |
18 files changed, 2902 insertions, 38 deletions
@@ -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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <err.h> +#include <getopt.h> +#include <getdns/getdns.h> +#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 <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#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 <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#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, <<ValidateRR/binary, S/binary>>}}, + 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}. @@ -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" ++ "</body></html>~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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <inttypes.h> +#include <errno.h> +#include <assert.h> +#include <arpa/inet.h> +#include <getdns/getdns.h> +#include <getdns/getdns_extra.h> + +#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 [<option> ...] \\\n" + "\t\t[@<upstream> ...] [+<extension> ...] [<name>] [<type>]\n", progname); + fprintf(out, "\ndefault mode: " +#ifdef HAVE_LIBUNBOUND + "recursive" +#else + "stub" +#endif + ", synchronous resolution of NS record using UDP with TCP fallback\n"); + fprintf(out, "\nupstreams: @<ip>[%%<scope_id>][@<port>][#<tls port>][~<tls name>][^<tsig spec>]\n"); + fprintf(out, "\ntsig spec: [<algorithm>:]<name>:<secret in Base64>\n"); + fprintf(out, "\nextensions:\n"); + fprintf(out, "\t+add_warning_for_bad_dns\n"); + fprintf(out, "\t+dnssec_return_status\n"); + fprintf(out, "\t+dnssec_return_only_secure\n"); + fprintf(out, "\t+dnssec_return_validation_chain\n"); +#ifdef DNSSEC_ROADBLOCK_AVOIDANCE + fprintf(out, "\t+dnssec_roadblock_avoidance\n"); +#endif +#ifdef EDNS_COOKIES + fprintf(out, "\t+edns_cookies\n"); +#endif + fprintf(out, "\t+return_both_v4_and_v6\n"); + fprintf(out, "\t+return_call_reporting\n"); + fprintf(out, "\t+sit=<cookie>\t\tSend along cookie OPT with value <cookie>\n"); + fprintf(out, "\t+specify_class=<class>\n"); + fprintf(out, "\t+0\t\t\tClear all extensions\n"); + fprintf(out, "\noptions:\n"); + fprintf(out, "\t-a\tPerform asynchronous resolution " + "(default = synchronous)\n"); + fprintf(out, "\t-A\taddress lookup (<type> is ignored)\n"); + fprintf(out, "\t-B\tBatch mode. Schedule all messages before processing responses.\n"); + fprintf(out, "\t-b <bufsize>\tSet edns0 max_udp_payload size\n"); + fprintf(out, "\t-c\tSend Client Subnet privacy request\n"); + fprintf(out, "\t-D\tSet edns0 do bit\n"); + fprintf(out, "\t-d\tclear edns0 do bit\n"); + fprintf(out, "\t-e <idle_timeout>\tSet idle timeout in miliseconds\n"); + fprintf(out, "\t-F <filename>\tread the queries from the specified file\n"); + fprintf(out, "\t-f <filename>\tRead DNSSEC trust anchors from <filename>\n"); + fprintf(out, "\t-G\tgeneral lookup\n"); + fprintf(out, "\t-H\thostname lookup. (<name> must be an IP address; <type> is ignored)\n"); + fprintf(out, "\t-h\tPrint this help\n"); + fprintf(out, "\t-i\tPrint api information\n"); + fprintf(out, "\t-I\tInteractive mode (> 1 queries on same context)\n"); + fprintf(out, "\t-j\tOutput json response dict\n"); + fprintf(out, "\t-J\tPretty print json response dict\n"); + fprintf(out, "\t-k\tPrint root trust anchors\n"); + fprintf(out, "\t-K <pin>\tPin a public key for TLS connections (can repeat)\n"); + fprintf(out, "\t\t(should look like '" EXAMPLE_PIN "')\n"); + fprintf(out, "\t-n\tSet TLS authentication mode to NONE (default)\n"); + fprintf(out, "\t-m\tSet TLS authentication mode to REQUIRED\n"); + fprintf(out, "\t-p\tPretty print response dict\n"); + fprintf(out, "\t-P <blocksize>\tPad TLS queries to a multiple of blocksize\n"); + fprintf(out, "\t-r\tSet recursing resolution type\n"); + fprintf(out, "\t-R <filename>\tRead root hints from <filename>\n"); + fprintf(out, "\t-q\tQuiet mode - don't print response\n"); + fprintf(out, "\t-s\tSet stub resolution type (default = recursing)\n"); + fprintf(out, "\t-S\tservice lookup (<type> is ignored)\n"); + fprintf(out, "\t-t <timeout>\tSet timeout in miliseconds\n"); + + fprintf(out, "\t-W\tAppend suffix always (default)\n"); + fprintf(out, "\t-1\tAppend suffix only to single label after failure\n"); + fprintf(out, "\t-M\tAppend suffix only to multi label name after failure\n"); + fprintf(out, "\t-N\tNever append a suffix\n"); + fprintf(out, "\t-Z <suffixes>\tSet suffixes with the given comma separed list\n"); + + fprintf(out, "\t-T\tSet transport to TCP only\n"); + fprintf(out, "\t-O\tSet transport to TCP only keep connections open\n"); + fprintf(out, "\t-L\tSet transport to TLS only keep connections open\n"); + fprintf(out, "\t-E\tSet transport to TLS with TCP fallback only keep connections open\n"); + fprintf(out, "\t-u\tSet transport to UDP with TCP fallback (default)\n"); + fprintf(out, "\t-U\tSet transport to UDP only\n"); + fprintf(out, "\t-l <transports>\tSet transport list. List can contain 1 of each of the characters\n"); + fprintf(out, "\t\t\t U T L S for UDP, TCP or TLS e.g 'UT' or 'LTU' \n"); + +} + +static getdns_return_t validate_chain(getdns_dict *response) +{ + getdns_return_t r; + getdns_list *validation_chain; + getdns_list *replies_tree; + getdns_dict *reply; + getdns_list *to_validate; + getdns_list *trust_anchor; + size_t i; + int s; + + if (!(to_validate = getdns_list_create())) + return GETDNS_RETURN_MEMORY_ERROR; + + if (getdns_context_get_dnssec_trust_anchors(context, &trust_anchor)) + trust_anchor = getdns_root_trust_anchor(NULL); + + if ((r = getdns_dict_get_list( + response, "validation_chain", &validation_chain))) + goto error; + + if ((r = getdns_dict_get_list( + response, "replies_tree", &replies_tree))) + goto error; + + fprintf(stdout, "replies_tree dnssec_status: "); + switch ((s = getdns_validate_dnssec( + replies_tree, validation_chain, trust_anchor))) { + + case GETDNS_DNSSEC_SECURE: + fprintf(stdout, "GETDNS_DNSSEC_SECURE\n"); + break; + case GETDNS_DNSSEC_BOGUS: + fprintf(stdout, "GETDNS_DNSSEC_BOGUS\n"); + break; + case GETDNS_DNSSEC_INDETERMINATE: + fprintf(stdout, "GETDNS_DNSSEC_INDETERMINATE\n"); + break; + case GETDNS_DNSSEC_INSECURE: + fprintf(stdout, "GETDNS_DNSSEC_INSECURE\n"); + break; + case GETDNS_DNSSEC_NOT_PERFORMED: + fprintf(stdout, "GETDNS_DNSSEC_NOT_PERFORMED\n"); + break; + default: + fprintf(stdout, "%d\n", (int)s); + } + + i = 0; + while (!(r = getdns_list_get_dict(replies_tree, i++, &reply))) { + + if ((r = getdns_list_set_dict(to_validate, 0, reply))) + goto error; + + printf("reply %u, dnssec_status: ", (unsigned)i); + switch ((s = getdns_validate_dnssec( + to_validate, validation_chain, trust_anchor))) { + + case GETDNS_DNSSEC_SECURE: + fprintf(stdout, "GETDNS_DNSSEC_SECURE\n"); + break; + case GETDNS_DNSSEC_BOGUS: + fprintf(stdout, "GETDNS_DNSSEC_BOGUS\n"); + break; + case GETDNS_DNSSEC_INDETERMINATE: + fprintf(stdout, "GETDNS_DNSSEC_INDETERMINATE\n"); + break; + case GETDNS_DNSSEC_INSECURE: + fprintf(stdout, "GETDNS_DNSSEC_INSECURE\n"); + break; + case GETDNS_DNSSEC_NOT_PERFORMED: + fprintf(stdout, "GETDNS_DNSSEC_NOT_PERFORMED\n"); + break; + default: + fprintf(stdout, "%d\n", (int)s); + } + } + if (r == GETDNS_RETURN_NO_SUCH_LIST_ITEM) + r = GETDNS_RETURN_GOOD; +error: + getdns_list_destroy(trust_anchor); + getdns_list_destroy(to_validate); + + return GETDNS_RETURN_GOOD; +} + +void callback(getdns_context *context, getdns_callback_type_t callback_type, + getdns_dict *response, void *userarg, getdns_transaction_t trans_id) +{ + char *response_str; + + /* This is a callback with data */; + if (response && !quiet && (response_str = json ? + getdns_print_json_dict(response, json == 1) + : getdns_pretty_print_dict(response))) { + + fprintf(stdout, "ASYNC response:\n%s\n", response_str); + validate_chain(response); + free(response_str); + } + + if (callback_type == GETDNS_CALLBACK_COMPLETE) { + printf("Response code was: GOOD. Status was: Callback with ID %"PRIu64" was successfull.\n", + trans_id); + + } else if (callback_type == GETDNS_CALLBACK_CANCEL) + fprintf(stderr, + "An error occurred: The callback with ID %"PRIu64" was cancelled. Exiting.\n", + trans_id); + else { + fprintf(stderr, + "An error occurred: The callback got a callback_type of %d. Exiting.\n", + callback_type); + fprintf(stderr, + "Error : '%s'\n", + getdns_get_errorstr_by_id(callback_type)); + } + getdns_dict_destroy(response); + response = NULL; +} + +#define CONTINUE ((getdns_return_t)-2) +#define CONTINUE_ERROR ((getdns_return_t)-3) + +static getdns_return_t set_cookie(getdns_dict *exts, char *cookie) +{ + uint8_t data[40]; + size_t i; + getdns_return_t r = GETDNS_RETURN_GENERIC_ERROR; + getdns_bindata bindata; + + getdns_dict *opt_parameters = getdns_dict_create(); + getdns_list *options = getdns_list_create(); + getdns_dict *option = getdns_dict_create(); + + if (*cookie == '=') + cookie++; + + for (i = 0; i < 40 && *cookie; i++) { + if (*cookie >= '0' && *cookie <= '9') + data[i] = (uint8_t)(*cookie - '0') << 4; + else if (*cookie >= 'a' && *cookie <= 'f') + data[i] = (uint8_t)(*cookie - 'a' + 10) << 4; + else if (*cookie >= 'A' && *cookie <= 'F') + data[i] = (uint8_t)(*cookie - 'A' + 10) << 4; + else + goto done; + cookie++; + if (*cookie >= '0' && *cookie <= '9') + data[i] |= (uint8_t)(*cookie - '0'); + else if (*cookie >= 'a' && *cookie <= 'f') + data[i] |= (uint8_t)(*cookie - 'a' + 10); + else if (*cookie >= 'A' && *cookie <= 'F') + data[i] |= (uint8_t)(*cookie - 'A' + 10); + else + goto done; + cookie++;; + } + bindata.data = data; + bindata.size = i; + if ((r = getdns_dict_set_int(option, "option_code", 10))) + goto done; + if ((r = getdns_dict_set_bindata(option, "option_data", &bindata))) + goto done; + if ((r = getdns_list_set_dict(options, 0, option))) + goto done; + if ((r = getdns_dict_set_list(opt_parameters, "options", options))) + goto done; + r = getdns_dict_set_dict(exts, "add_opt_parameters", opt_parameters); +done: + getdns_dict_destroy(option); + getdns_list_destroy(options); + getdns_dict_destroy(opt_parameters); + return r; +} + +getdns_return_t parse_args(int argc, char **argv) +{ + getdns_return_t r = GETDNS_RETURN_GOOD; + size_t i, j; + char *arg, *c, *endptr; + int t, print_api_info = 0, print_trust_anchors = 0; + getdns_list *upstream_list = NULL; + getdns_list *tas = NULL, *hints = NULL; + getdns_dict *pubkey_pin = NULL; + getdns_list *suffixes; + char *suffix; + getdns_bindata bindata; + size_t upstream_count = 0; + FILE *fh; + uint32_t klass; + + for (i = 1; i < argc; i++) { + arg = argv[i]; + if ((t = get_rrtype(arg)) >= 0) { + request_type = t; + continue; + + } else if (arg[0] == '+') { + if (arg[1] == 's' && arg[2] == 'i' && arg[3] == 't' && + (arg[4] == '=' || arg[4] == '\0')) { + if ((r = set_cookie(extensions, arg+4))) { + fprintf(stderr, "Could not set cookie:" + " %d", r); + break; + } + } else if (strncmp(arg+1, "specify_class=", 14) == 0) { + if (strncasecmp(arg+15, "IN", 3) == 0) + r = getdns_dict_set_int(extensions, + "specify_class", GETDNS_RRCLASS_IN); + else if (strncasecmp(arg+15, "CH", 3) == 0) + r = getdns_dict_set_int(extensions, + "specify_class", GETDNS_RRCLASS_CH); + else if (strncasecmp(arg+15, "HS", 3) == 0) + r = getdns_dict_set_int(extensions, + "specify_class", GETDNS_RRCLASS_HS); + else if (strncasecmp(arg+15, "NONE", 5) == 0) + r = getdns_dict_set_int(extensions, + "specify_class", GETDNS_RRCLASS_NONE); + else if (strncasecmp(arg+15, "ANY", 4) == 0) + r = getdns_dict_set_int(extensions, + "specify_class", GETDNS_RRCLASS_ANY); + else if (strncasecmp(arg+15, "CLASS", 5) == 0) { + klass = strtol(arg + 20, &endptr, 10); + if (*endptr || klass > 255) + fprintf(stderr, + "Unknown class: %s\n", + arg+15); + else + r = getdns_dict_set_int(extensions, + "specify_class", klass); + + } else + fprintf(stderr, + "Unknown class: %s\n", arg+15); + } else if (arg[1] == '0') { + /* Unset all existing extensions*/ + getdns_dict_destroy(extensions); + extensions = getdns_dict_create(); + break; + } else if ((r = getdns_dict_set_int(extensions, arg+1, + GETDNS_EXTENSION_TRUE))) { + fprintf(stderr, "Could not set extension " + "\"%s\": %d\n", argv[i], r); + break; + } + continue; + + } else if (arg[0] == '@') { + getdns_dict *upstream = ipaddr_dict(context, arg + 1); + if (upstream) { + if (!upstream_list && + !(upstream_list = + getdns_list_create_with_context(context))){ + fprintf(stderr, "Could not create upstream list\n"); + return GETDNS_RETURN_MEMORY_ERROR; + } + getdns_list_set_dict(upstream_list, + upstream_count++, upstream); + } + continue; + } else if (arg[0] != '-') { + name = arg; + continue; + } + for (c = arg+1; *c; c++) { + switch (*c) { + case 'a': + async = 1; + break; + case 'A': + calltype = ADDRESS; + break; + case 'b': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "max_udp_payload_size " + "expected after -b\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + edns0_size = strtol(argv[i], &endptr, 10); + if (*endptr || edns0_size < 0) { + fprintf(stderr, "positive " + "numeric max_udp_payload_size " + "expected after -b\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + getdns_context_set_edns_maximum_udp_payload_size( + context, (uint16_t) edns0_size); + goto next; + case 'c': + if (getdns_context_set_edns_client_subnet_private(context, 1)) + return GETDNS_RETURN_GENERIC_ERROR; + break; + case 'D': + (void) getdns_context_set_edns_do_bit(context, 1); + break; + case 'd': + (void) getdns_context_set_edns_do_bit(context, 0); + break; + case 'f': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "file name expected " + "after -f\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + if (!(fh = fopen(argv[i], "r"))) { + fprintf(stderr, "Could not open \"%s\"" + ": %s\n",argv[i], strerror(errno)); + return GETDNS_RETURN_GENERIC_ERROR; + } + if (getdns_fp2rr_list(fh, &tas, NULL, 3600)) { + fprintf(stderr,"Could not parse " + "\"%s\"\n", argv[i]); + return GETDNS_RETURN_GENERIC_ERROR; + } + fclose(fh); + if (getdns_context_set_dnssec_trust_anchors( + context, tas)) { + fprintf(stderr,"Could not set " + "trust anchors from \"%s\"\n", + argv[i]); + return GETDNS_RETURN_GENERIC_ERROR; + } + getdns_list_destroy(tas); + tas = NULL; + break; + case 'F': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "file name expected " + "after -F\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + query_file = argv[i]; + interactive = 1; + break; + case 'G': + calltype = GENERAL; + break; + case 'H': + calltype = HOSTNAME; + break; + case 'h': + print_usage(stdout, argv[0]); + return CONTINUE; + case 'i': + print_api_info = 1; + break; + case 'I': + interactive = 1; + break; + case 'j': + json = 2; + break; + case 'J': + json = 1; + break; + case 'K': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "pin string of the form " + EXAMPLE_PIN + "expected after -K\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + pubkey_pin = getdns_pubkey_pin_create_from_string(context, + argv[i]); + if (pubkey_pin == NULL) { + fprintf(stderr, "could not convert '%s' into a " + "public key pin.\n" + "Good pins look like: " EXAMPLE_PIN "\n" + "Please see RFC 7469 for details about " + "the format\n", argv[i]); + return GETDNS_RETURN_GENERIC_ERROR; + } + if (pubkey_pinset == NULL) + pubkey_pinset = getdns_list_create_with_context(context); + if (r = getdns_list_set_dict(pubkey_pinset, pincount++, + pubkey_pin), r) { + fprintf(stderr, "Failed to add pin to pinset (error %d: %s)\n", + r, getdns_get_errorstr_by_id(r)); + getdns_dict_destroy(pubkey_pin); + pubkey_pin = NULL; + return GETDNS_RETURN_GENERIC_ERROR; + } + getdns_dict_destroy(pubkey_pin); + pubkey_pin = NULL; + break; + case 'k': + print_trust_anchors = 1; + break; + case 'n': + getdns_context_set_tls_authentication(context, + GETDNS_AUTHENTICATION_NONE); + break; + case 'm': + getdns_context_set_tls_authentication(context, + GETDNS_AUTHENTICATION_REQUIRED); + break; + case 'P': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "tls_query_padding_blocksize " + "expected after -P\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + padding_blocksize = strtol(argv[i], &endptr, 10); + if (*endptr || padding_blocksize < 0) { + fprintf(stderr, "non-negative " + "numeric padding blocksize expected " + "after -P\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + if (getdns_context_set_tls_query_padding_blocksize( + context, padding_blocksize)) + return GETDNS_RETURN_GENERIC_ERROR; + goto next; + case 'p': + json = 0; + case 'q': + quiet = 1; + break; + case 'r': + getdns_context_set_resolution_type( + context, + GETDNS_RESOLUTION_RECURSING); + break; + case 'R': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "file name expected " + "after -f\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + if (!(fh = fopen(argv[i], "r"))) { + fprintf(stderr, "Could not open \"%s\"" + ": %s\n",argv[i], strerror(errno)); + return GETDNS_RETURN_GENERIC_ERROR; + } + if (getdns_fp2rr_list(fh, &hints, NULL, 3600)) { + fprintf(stderr,"Could not parse " + "\"%s\"\n", argv[i]); + return GETDNS_RETURN_GENERIC_ERROR; + } + fclose(fh); + if (getdns_context_set_dns_root_servers( + context, hints)) { + fprintf(stderr,"Could not set " + "root servers from \"%s\"\n", + argv[i]); + return GETDNS_RETURN_GENERIC_ERROR; + } + getdns_list_destroy(hints); + hints = NULL; + break; + case 's': + getdns_context_set_resolution_type( + context, GETDNS_RESOLUTION_STUB); + break; + case 'S': + calltype = SERVICE; + break; + case 't': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "timeout expected " + "after -t\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + timeout = strtol(argv[i], &endptr, 10); + if (*endptr || timeout < 0) { + fprintf(stderr, "positive " + "numeric timeout expected " + "after -t\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + getdns_context_set_timeout( + context, timeout); + goto next; + case 'e': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "idle timeout expected " + "after -t\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + timeout = strtol(argv[i], &endptr, 10); + if (*endptr || timeout < 0) { + fprintf(stderr, "positive " + "numeric idle timeout expected " + "after -t\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + getdns_context_set_idle_timeout( + context, timeout); + goto next; + case 'W': + (void) getdns_context_set_append_name(context, + GETDNS_APPEND_NAME_ALWAYS); + break; + case '1': + (void) getdns_context_set_append_name(context, + GETDNS_APPEND_NAME_ONLY_TO_SINGLE_LABEL_AFTER_FAILURE); + break; + case 'M': + (void) getdns_context_set_append_name(context, + GETDNS_APPEND_NAME_ONLY_TO_MULTIPLE_LABEL_NAME_AFTER_FAILURE); + break; + case 'N': + (void) getdns_context_set_append_name(context, + GETDNS_APPEND_NAME_NEVER); + break; + case 'Z': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "suffixes expected" + "after -Z\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + if (!(suffixes = getdns_list_create())) + return GETDNS_RETURN_MEMORY_ERROR; + suffix = strtok(argv[i], ","); + j = 0; + while (suffix) { + bindata.size = strlen(suffix); + bindata.data = (void *)suffix; + (void) getdns_list_set_bindata( + suffixes, j++, &bindata); + suffix = strtok(NULL, ","); + } + (void) getdns_context_set_suffix(context, + suffixes); + getdns_list_destroy(suffixes); + goto next; + case 'T': + getdns_context_set_dns_transport(context, + GETDNS_TRANSPORT_TCP_ONLY); + break; + case 'O': + getdns_context_set_dns_transport(context, + GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN); + break; + case 'L': + getdns_context_set_dns_transport(context, + GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN); + break; + case 'E': + getdns_context_set_dns_transport(context, + GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN); + break; + case 'u': + getdns_context_set_dns_transport(context, + GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP); + break; + case 'U': + getdns_context_set_dns_transport(context, + GETDNS_TRANSPORT_UDP_ONLY); + break; + case 'l': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "transport list expected " + "after -l\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + getdns_transport_list_t transports[10]; + size_t transport_count = sizeof(transports); + if ((r = fill_transport_list(context, argv[i], transports, &transport_count)) || + (r = getdns_context_set_dns_transport_list(context, + transport_count, transports))){ + fprintf(stderr, "Could not set transports\n"); + return r; + } + break; + case 'B': + batch_mode = 1; + break; + + + default: + fprintf(stderr, "Unknown option " + "\"%c\"\n", *c); + for (i = 0; i < argc; i++) + fprintf(stderr, "%d: \"%s\"\n", (int)i, argv[i]); + return GETDNS_RETURN_GENERIC_ERROR; + } + } +next: ; + } + if (r) + return r; + if (pubkey_pinset && upstream_count) { + getdns_dict *upstream; + /* apply the accumulated pubkey pinset to all upstreams: */ + for (i = 0; i < upstream_count; i++) { + if (r = getdns_list_get_dict(upstream_list, i, &upstream), r) { + fprintf(stderr, "Failed to get upstream %lu when adding pinset\n", (long unsigned int)i); + return r; + } + if (r = getdns_dict_set_list(upstream, "tls_pubkey_pinset", pubkey_pinset), r) { + fprintf(stderr, "Failed to set pubkey pinset on upstream %lu\n", (long unsigned int)i); + return r; + } + } + } + if (upstream_count && + (r = getdns_context_set_upstream_recursive_servers( + context, upstream_list))) { + fprintf(stderr, "Error setting upstream recursive servers\n"); + } + if (print_api_info) { + fprintf(stdout, "%s\n", getdns_pretty_print_dict( + getdns_context_get_api_information(context))); + return CONTINUE; + } + if (print_trust_anchors) { + if (!getdns_context_get_dnssec_trust_anchors(context, &tas)) { + /* if ((tas = getdns_root_trust_anchor(NULL))) { */ + fprintf(stdout, "%s\n", getdns_pretty_print_list(tas)); + return CONTINUE; + } else + return CONTINUE_ERROR; + } + return r; +} + +getdns_return_t do_the_call(void) +{ + getdns_return_t r; + getdns_dict *address = NULL; + getdns_dict *response = NULL; + char *response_str; + uint32_t status; + + if (calltype == HOSTNAME && + !(address = ipaddr_dict(context, name))) { + fprintf(stderr, "Could not convert \"%s\" " + "to an IP address", name); + return GETDNS_RETURN_GOOD; + } + if (async) { + switch (calltype) { + case GENERAL: + r = getdns_general(context, name, request_type, + extensions, &response, NULL, callback); + break; + case ADDRESS: + r = getdns_address(context, name, + extensions, &response, NULL, callback); + break; + case HOSTNAME: + r = getdns_hostname(context, address, + extensions, &response, NULL, callback); + break; + case SERVICE: + r = getdns_service(context, name, + extensions, &response, NULL, callback); + break; + default: + r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + if (r == GETDNS_RETURN_GOOD && !batch_mode) + getdns_context_run(context); + if (r != GETDNS_RETURN_GOOD) + fprintf(stderr, "An error occurred: %d '%s'\n", r, + getdns_get_errorstr_by_id(r)); + } else { + switch (calltype) { + case GENERAL: + r = getdns_general_sync(context, name, + request_type, extensions, &response); + break; + case ADDRESS: + r = getdns_address_sync(context, name, + extensions, &response); + break; + case HOSTNAME: + r = getdns_hostname_sync(context, address, + extensions, &response); + break; + case SERVICE: + r = getdns_service_sync(context, name, + extensions, &response); + break; + default: + r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + if (r != GETDNS_RETURN_GOOD) { + fprintf(stderr, "An error occurred: %d '%s'\n", r, + getdns_get_errorstr_by_id(r)); + return r; + } + if (response && !quiet) { + if ((response_str = json ? + getdns_print_json_dict(response, json == 1) + : getdns_pretty_print_dict(response))) { + + fprintf( stdout, "SYNC response:\n%s\n" + , response_str); + validate_chain(response); + free(response_str); + } else { + r = GETDNS_RETURN_MEMORY_ERROR; + fprintf( stderr + , "Could not print response\n"); + } + } +#if 1 + FILE *support_out_fp = fopen("treeout_support", "w"); + assert(support_out_fp); + getdns_list *validation_chain = NULL; + if ((r = getdns_dict_get_list( + response, "validation_chain", &validation_chain))) + assert(!r && "get_list validation_chain"); + if (response && support_out_fp) { + ; //fwrite(support_out_fp, fixme, fixme_len); + } + fclose(support_out_fp); +#endif + getdns_dict_get_int(response, "status", &status); + fprintf(stdout, "Response code was: GOOD. Status was: %s\n", + getdns_get_errorstr_by_id(status)); + if (response) + getdns_dict_destroy(response); + } + return r; +} + +my_eventloop my_loop; +FILE *fp; + +void read_line_cb(void *userarg) +{ + getdns_eventloop_event *read_line_ev = userarg; + getdns_return_t r; + + char line[1024], *token, *linev[256]; + int linec; + + if (!fgets(line, 1024, fp) || !*line) { + if (query_file) + fprintf(stdout,"End of file."); + my_eventloop_clear(&my_loop.base, read_line_ev); + return; + } + if (query_file) + fprintf(stdout,"Found query: %s", line); + + linev[0] = __FILE__; + linec = 1; + if (!(token = strtok(line, " \t\f\n\r"))) { + if (! query_file) { + printf("> "); + fflush(stdout); + } + return; + } + if (*token == '#') { + fprintf(stdout,"Result: Skipping comment\n"); + if (! query_file) { + printf("> "); + fflush(stdout); + } + return; + } + do linev[linec++] = token; + while (linec < 256 && (token = strtok(NULL, " \t\f\n\r"))); + + if (((r = parse_args(linec, linev)) || (r = do_the_call())) && + (r != CONTINUE && r != CONTINUE_ERROR)) + my_eventloop_clear(&my_loop.base, read_line_ev); + + else if (! query_file) { + printf("> "); + fflush(stdout); + } +} + +int +main(int argc, char **argv) +{ + getdns_return_t r; + + name = the_root; + if ((r = getdns_context_create(&context, 1))) { + fprintf(stderr, "Create context failed: %d\n", r); + return r; + } + my_eventloop_init(&my_loop); + if ((r = getdns_context_set_eventloop(context, &my_loop.base))) + goto done_destroy_context; + if ((r = getdns_context_set_use_threads(context, 1))) + goto done_destroy_context; + extensions = getdns_dict_create(); + if (! extensions) { + fprintf(stderr, "Could not create extensions dict\n"); + r = GETDNS_RETURN_MEMORY_ERROR; + goto done_destroy_context; + } + if ((r = parse_args(argc, argv))) + goto done_destroy_context; + + if (query_file) { + fp = fopen(query_file, "rt"); + if (fp == NULL) { + fprintf(stderr, "Could not open query file: %s\n", query_file); + goto done_destroy_context; + } + } else + fp = stdin; + + /* Make the call */ + if (interactive) { + getdns_eventloop_event read_line_ev = { + &read_line_ev, read_line_cb, NULL, NULL, NULL }; + (void) my_eventloop_schedule( + &my_loop.base, fileno(fp), -1, &read_line_ev); + if (!query_file) { + printf("> "); + fflush(stdout); + } + my_eventloop_run(&my_loop.base); + } + else + r = do_the_call(); + + if ((r == GETDNS_RETURN_GOOD && batch_mode)) + getdns_context_run(context); + + /* Clean up */ + getdns_dict_destroy(extensions); +done_destroy_context: + getdns_context_destroy(context); + + if (fp) + fclose(fp); + + if (r == CONTINUE) + return 0; + else if (r == CONTINUE_ERROR) + return 1; + fprintf(stdout, "\nAll done.\n"); + return r; +} + +int get_rrtype(const char *t) { + char *endptr; + int r; + + switch (t[0]) { + case 'A': + case 'a': switch (t[1]) { + case '\0': return GETDNS_RRTYPE_A; + case '6': if (t[2] == '\0') return GETDNS_RRTYPE_A6; + return -1; + case 'A': + case 'a': /* before "AA", final "AA" (GETDNS_RRTYPE_AAAA) */ + if ((t[2]|0x20) == 'a' && (t[3]|0x20) == 'a' && t[4] == '\0') + return GETDNS_RRTYPE_AAAA; + return -1; + case 'F': + case 'f': /* before "AF", final "SDB" (GETDNS_RRTYPE_AFSDB) */ + if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'd' && (t[4]|0x20) == 'b' && t[5] == '\0') + return GETDNS_RRTYPE_AFSDB; + return -1; + case 'P': + case 'p': /* before "AP", final "L" (GETDNS_RRTYPE_APL) */ + if ((t[2]|0x20) == 'l' && t[3] == '\0') + return GETDNS_RRTYPE_APL; + return -1; + case 'T': + case 't': /* before "AT", final "MA" (GETDNS_RRTYPE_ATMA) */ + if ((t[2]|0x20) == 'm' && (t[3]|0x20) == 'a' && t[4] == '\0') + return GETDNS_RRTYPE_ATMA; + return -1; + case 'X': + case 'x': /* before "AX", final "FR" (GETDNS_RRTYPE_AXFR) */ + if ((t[2]|0x20) == 'f' && (t[3]|0x20) == 'r' && t[4] == '\0') + return GETDNS_RRTYPE_AXFR; + return -1; + default : return -1; + }; + case 'C': + case 'c': switch (t[1]) { + case 'A': + case 'a': /* before "CA", final "A" (GETDNS_RRTYPE_CAA) */ + if ((t[2]|0x20) == 'a' && t[3] == '\0') + return GETDNS_RRTYPE_CAA; + return -1; + case 'D': + case 'd': switch (t[2]) { + case 'N': + case 'n': /* before "CDN", final "SKEY" (GETDNS_RRTYPE_CDNSKEY) */ + if ((t[3]|0x20) == 's' && (t[4]|0x20) == 'k' && (t[5]|0x20) == 'e' && (t[6]|0x20) == 'y' && t[7] == '\0') + return GETDNS_RRTYPE_CDNSKEY; + return -1; + case 'S': + case 's': if (t[3] == '\0') return GETDNS_RRTYPE_CDS; + return -1; + default : return -1; + }; + case 'E': + case 'e': /* before "CE", final "RT" (GETDNS_RRTYPE_CERT) */ + if ((t[2]|0x20) == 'r' && (t[3]|0x20) == 't' && t[4] == '\0') + return GETDNS_RRTYPE_CERT; + return -1; + case 'N': + case 'n': /* before "CN", final "AME" (GETDNS_RRTYPE_CNAME) */ + if ((t[2]|0x20) == 'a' && (t[3]|0x20) == 'm' && (t[4]|0x20) == 'e' && t[5] == '\0') + return GETDNS_RRTYPE_CNAME; + return -1; + case 'S': + case 's': /* before "CS", final "YNC" (GETDNS_RRTYPE_CSYNC) */ + if ((t[2]|0x20) == 'y' && (t[3]|0x20) == 'n' && (t[4]|0x20) == 'c' && t[5] == '\0') + return GETDNS_RRTYPE_CSYNC; + return -1; + + default : return -1; + }; + case 'D': + case 'd': switch (t[1]) { + case 'H': + case 'h': /* before "DH", final "CID" (GETDNS_RRTYPE_DHCID) */ + if ((t[2]|0x20) == 'c' && (t[3]|0x20) == 'i' && (t[4]|0x20) == 'd' && t[5] == '\0') + return GETDNS_RRTYPE_DHCID; + return -1; + case 'L': + case 'l': /* before "DL", final "V" (GETDNS_RRTYPE_DLV) */ + if ((t[2]|0x20) == 'v' && t[3] == '\0') + return GETDNS_RRTYPE_DLV; + return -1; + case 'N': + case 'n': switch (t[2]) { + case 'A': + case 'a': /* before "DNA", final "ME" (GETDNS_RRTYPE_DNAME) */ + if ((t[3]|0x20) == 'm' && (t[4]|0x20) == 'e' && t[5] == '\0') + return GETDNS_RRTYPE_DNAME; + return -1; + case 'S': + case 's': /* before "DNS", final "KEY" (GETDNS_RRTYPE_DNSKEY) */ + if ((t[3]|0x20) == 'k' && (t[4]|0x20) == 'e' && (t[5]|0x20) == 'y' && t[6] == '\0') + return GETDNS_RRTYPE_DNSKEY; + return -1; + default : return -1; + }; + case 'S': + case 's': if (t[2] == '\0') return GETDNS_RRTYPE_DS; + return -1; + default : return -1; + }; + case 'E': + case 'e': switch (t[1]) { + case 'I': + case 'i': /* before "EI", final "D" (GETDNS_RRTYPE_EID) */ + if ((t[2]|0x20) == 'd' && t[3] == '\0') + return GETDNS_RRTYPE_EID; + return -1; + case 'U': + case 'u': /* before "EU", next "I" */ + if ((t[2]|0x20) != 'i') + return -1; + switch (t[3]) { + case '4': /* before "EUI4", final "8" (GETDNS_RRTYPE_EUI48) */ + if (t[4] == '8' && t[5] == '\0') + return GETDNS_RRTYPE_EUI48; + return -1; + case '6': /* before "EUI6", final "4" (GETDNS_RRTYPE_EUI64) */ + if (t[4] == '4' && t[5] == '\0') + return GETDNS_RRTYPE_EUI64; + return -1; + default : return -1; + }; + default : return -1; + }; + case 'G': + case 'g': switch (t[1]) { + case 'I': + case 'i': /* before "GI", final "D" (GETDNS_RRTYPE_GID) */ + if ((t[2]|0x20) == 'd' && t[3] == '\0') + return GETDNS_RRTYPE_GID; + return -1; + case 'P': + case 'p': /* before "GP", final "OS" (GETDNS_RRTYPE_GPOS) */ + if ((t[2]|0x20) == 'o' && (t[3]|0x20) == 's' && t[4] == '\0') + return GETDNS_RRTYPE_GPOS; + return -1; + default : return -1; + }; + case 'H': + case 'h': /* before "H", next "I" */ + if ((t[1]|0x20) != 'i') + return -1; + switch (t[2]) { + case 'N': + case 'n': /* before "HIN", final "FO" (GETDNS_RRTYPE_HINFO) */ + if ((t[3]|0x20) == 'f' && (t[4]|0x20) == 'o' && t[5] == '\0') + return GETDNS_RRTYPE_HINFO; + return -1; + case 'P': + case 'p': if (t[3] == '\0') return GETDNS_RRTYPE_HIP; + return -1; + default : return -1; + }; + case 'I': + case 'i': switch (t[1]) { + case 'P': + case 'p': /* before "IP", final "SECKEY" (GETDNS_RRTYPE_IPSECKEY) */ + if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'e' && (t[4]|0x20) == 'c' && (t[5]|0x20) == 'k' && (t[6]|0x20) == 'e' && (t[7]|0x20) == 'y' && t[8] == '\0') + return GETDNS_RRTYPE_IPSECKEY; + return -1; + case 'S': + case 's': /* before "IS", final "DN" (GETDNS_RRTYPE_ISDN) */ + if ((t[2]|0x20) == 'd' && (t[3]|0x20) == 'n' && t[4] == '\0') + return GETDNS_RRTYPE_ISDN; + return -1; + case 'X': + case 'x': /* before "IX", final "FR" (GETDNS_RRTYPE_IXFR) */ + if ((t[2]|0x20) == 'f' && (t[3]|0x20) == 'r' && t[4] == '\0') + return GETDNS_RRTYPE_IXFR; + return -1; + default : return -1; + }; + case 'K': + case 'k': switch (t[1]) { + case 'E': + case 'e': /* before "KE", final "Y" (GETDNS_RRTYPE_KEY) */ + if ((t[2]|0x20) == 'y' && t[3] == '\0') + return GETDNS_RRTYPE_KEY; + return -1; + case 'X': + case 'x': if (t[2] == '\0') return GETDNS_RRTYPE_KX; + return -1; + default : return -1; + }; + case 'L': + case 'l': switch (t[1]) { + case '3': /* before "L3", final "2" (GETDNS_RRTYPE_L32) */ + if (t[2] == '2' && t[3] == '\0') + return GETDNS_RRTYPE_L32; + return -1; + case '6': /* before "L6", final "4" (GETDNS_RRTYPE_L64) */ + if (t[2] == '4' && t[3] == '\0') + return GETDNS_RRTYPE_L64; + return -1; + case 'O': + case 'o': /* before "LO", final "C" (GETDNS_RRTYPE_LOC) */ + if ((t[2]|0x20) == 'c' && t[3] == '\0') + return GETDNS_RRTYPE_LOC; + return -1; + case 'P': + case 'p': if (t[2] == '\0') return GETDNS_RRTYPE_LP; + return -1; + default : return -1; + }; + case 'M': + case 'm': switch (t[1]) { + case 'A': + case 'a': /* before "MA", next "IL" */ + if ((t[2]|0x20) != 'i' && (t[3]|0x20) != 'l') + return -1; + switch (t[4]) { + case 'A': + case 'a': if (t[5] == '\0') return GETDNS_RRTYPE_MAILA; + return -1; + case 'B': + case 'b': if (t[5] == '\0') return GETDNS_RRTYPE_MAILB; + return -1; + default : return -1; + }; + case 'B': + case 'b': if (t[2] == '\0') return GETDNS_RRTYPE_MB; + return -1; + case 'D': + case 'd': if (t[2] == '\0') return GETDNS_RRTYPE_MD; + return -1; + case 'F': + case 'f': if (t[2] == '\0') return GETDNS_RRTYPE_MF; + return -1; + case 'G': + case 'g': if (t[2] == '\0') return GETDNS_RRTYPE_MG; + return -1; + case 'I': + case 'i': /* before "MI", final "NFO" (GETDNS_RRTYPE_MINFO) */ + if ((t[2]|0x20) == 'n' && (t[3]|0x20) == 'f' && (t[4]|0x20) == 'o' && t[5] == '\0') + return GETDNS_RRTYPE_MINFO; + return -1; + case 'R': + case 'r': if (t[2] == '\0') return GETDNS_RRTYPE_MR; + return -1; + case 'X': + case 'x': if (t[2] == '\0') return GETDNS_RRTYPE_MX; + return -1; + default : return -1; + }; + case 'N': + case 'n': switch (t[1]) { + case 'A': + case 'a': /* before "NA", final "PTR" (GETDNS_RRTYPE_NAPTR) */ + if ((t[2]|0x20) == 'p' && (t[3]|0x20) == 't' && (t[4]|0x20) == 'r' && t[5] == '\0') + return GETDNS_RRTYPE_NAPTR; + return -1; + case 'I': + case 'i': switch (t[2]) { + case 'D': + case 'd': if (t[3] == '\0') return GETDNS_RRTYPE_NID; + return -1; + case 'M': + case 'm': /* before "NIM", final "LOC" (GETDNS_RRTYPE_NIMLOC) */ + if ((t[3]|0x20) == 'l' && (t[4]|0x20) == 'o' && (t[5]|0x20) == 'c' && t[6] == '\0') + return GETDNS_RRTYPE_NIMLOC; + return -1; + case 'N': + case 'n': /* before "NIN", final "FO" (GETDNS_RRTYPE_NINFO) */ + if ((t[3]|0x20) == 'f' && (t[4]|0x20) == 'o' && t[5] == '\0') + return GETDNS_RRTYPE_NINFO; + return -1; + default : return -1; + }; + case 'S': + case 's': switch (t[2]) { + case '\0': return GETDNS_RRTYPE_NS; + case 'A': + case 'a': /* before "NSA", final "P" (GETDNS_RRTYPE_NSAP) */ + if ((t[3]|0x20) == 'p' && t[4] == '\0') + return GETDNS_RRTYPE_NSAP; + return -1; + case 'E': + case 'e': /* before "NSE", final "C3PARAM" (GETDNS_RRTYPE_NSEC3PARAM) */ + if ((t[3]|0x20) == 'c' && t[4] == '3' && (t[5]|0x20) == 'p' && (t[6]|0x20) == 'a' && (t[7]|0x20) == 'r' && (t[8]|0x20) == 'a' && (t[9]|0x20) == 'm' && t[10] == '\0') + return GETDNS_RRTYPE_NSEC3PARAM; + return -1; + default : return -1; + }; + case 'U': + case 'u': /* before "NU", final "LL" (GETDNS_RRTYPE_NULL) */ + if ((t[2]|0x20) == 'l' && (t[3]|0x20) == 'l' && t[4] == '\0') + return GETDNS_RRTYPE_NULL; + return -1; + case 'X': + case 'x': /* before "NX", final "T" (GETDNS_RRTYPE_NXT) */ + if ((t[2]|0x20) == 't' && t[3] == '\0') + return GETDNS_RRTYPE_NXT; + return -1; + default : return -1; + }; + case 'O': + case 'o': /* before "O", next "P" */ + if ((t[1]|0x20) != 'p') + return -1; + switch (t[2]) { + case 'E': + case 'e': /* before "OPE", final "NPGPKEY" (GETDNS_RRTYPE_OPENPGPKEY) */ + if ((t[3]|0x20) == 'n' && (t[4]|0x20) == 'p' && (t[5]|0x20) == 'g' && (t[6]|0x20) == 'p' && (t[7]|0x20) == 'k' && (t[8]|0x20) == 'e' && (t[9]|0x20) == 'y' && t[10] == '\0') + return GETDNS_RRTYPE_OPENPGPKEY; + return -1; + case 'T': + case 't': if (t[3] == '\0') return GETDNS_RRTYPE_OPT; + return -1; + default : return -1; + }; + case 'P': + case 'p': switch (t[1]) { + case 'T': + case 't': /* before "PT", final "R" (GETDNS_RRTYPE_PTR) */ + if ((t[2]|0x20) == 'r' && t[3] == '\0') + return GETDNS_RRTYPE_PTR; + return -1; + case 'X': + case 'x': if (t[2] == '\0') return GETDNS_RRTYPE_PX; + return -1; + default : return -1; + }; + case 'R': + case 'r': switch (t[1]) { + case 'K': + case 'k': /* before "RK", final "EY" (GETDNS_RRTYPE_RKEY) */ + if ((t[2]|0x20) == 'e' && (t[3]|0x20) == 'y' && t[4] == '\0') + return GETDNS_RRTYPE_RKEY; + return -1; + case 'P': + case 'p': if (t[2] == '\0') return GETDNS_RRTYPE_RP; + return -1; + case 'R': + case 'r': /* before "RR", final "SIG" (GETDNS_RRTYPE_RRSIG) */ + if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'i' && (t[4]|0x20) == 'g' && t[5] == '\0') + return GETDNS_RRTYPE_RRSIG; + return -1; + case 'T': + case 't': if (t[2] == '\0') return GETDNS_RRTYPE_RT; + return -1; + default : return -1; + }; + case 'S': + case 's': switch (t[1]) { + case 'I': + case 'i': switch (t[2]) { + case 'G': + case 'g': if (t[3] == '\0') return GETDNS_RRTYPE_SIG; + return -1; + case 'N': + case 'n': /* before "SIN", final "K" (GETDNS_RRTYPE_SINK) */ + if ((t[3]|0x20) == 'k' && t[4] == '\0') + return GETDNS_RRTYPE_SINK; + return -1; + default : return -1; + }; + case 'O': + case 'o': /* before "SO", final "A" (GETDNS_RRTYPE_SOA) */ + if ((t[2]|0x20) == 'a' && t[3] == '\0') + return GETDNS_RRTYPE_SOA; + return -1; + case 'P': + case 'p': /* before "SP", final "F" (GETDNS_RRTYPE_SPF) */ + if ((t[2]|0x20) == 'f' && t[3] == '\0') + return GETDNS_RRTYPE_SPF; + return -1; + case 'R': + case 'r': /* before "SR", final "V" (GETDNS_RRTYPE_SRV) */ + if ((t[2]|0x20) == 'v' && t[3] == '\0') + return GETDNS_RRTYPE_SRV; + return -1; + case 'S': + case 's': /* before "SS", final "HFP" (GETDNS_RRTYPE_SSHFP) */ + if ((t[2]|0x20) == 'h' && (t[3]|0x20) == 'f' && (t[4]|0x20) == 'p' && t[5] == '\0') + return GETDNS_RRTYPE_SSHFP; + return -1; + default : return -1; + }; + case 'T': + case 't': switch (t[1]) { + case 'A': + case 'a': /* before "TA", final "LINK" (GETDNS_RRTYPE_TALINK) */ + if ((t[2]|0x20) == 'l' && (t[3]|0x20) == 'i' && (t[4]|0x20) == 'n' && (t[5]|0x20) == 'k' && t[6] == '\0') + return GETDNS_RRTYPE_TALINK; + return -1; + case 'K': + case 'k': /* before "TK", final "EY" (GETDNS_RRTYPE_TKEY) */ + if ((t[2]|0x20) == 'e' && (t[3]|0x20) == 'y' && t[4] == '\0') + return GETDNS_RRTYPE_TKEY; + return -1; + case 'L': + case 'l': /* before "TL", final "SA" (GETDNS_RRTYPE_TLSA) */ + if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'a' && t[4] == '\0') + return GETDNS_RRTYPE_TLSA; + return -1; + case 'S': + case 's': /* before "TS", final "IG" (GETDNS_RRTYPE_TSIG) */ + if ((t[2]|0x20) == 'i' && (t[3]|0x20) == 'g' && t[4] == '\0') + return GETDNS_RRTYPE_TSIG; + return -1; + case 'X': + case 'x': /* before "TX", final "T" (GETDNS_RRTYPE_TXT) */ + if ((t[2]|0x20) == 't' && t[3] == '\0') + return GETDNS_RRTYPE_TXT; + return -1; + case 'Y': + case 'y': /* before "TY", then "PE" followed by a number */ + if ((t[2]|0x20) == 'p' && (t[3]|0x20) == 'e' && t[4] != '\0') { + r = (int) strtol(t + 4, &endptr, 10); + if (*endptr == '\0') return r; + } + return -1; + default : return -1; + }; + case 'U': + case 'u': switch (t[1]) { + case 'I': + case 'i': switch (t[2]) { + case 'D': + case 'd': if (t[3] == '\0') return GETDNS_RRTYPE_UID; + return -1; + case 'N': + case 'n': /* before "UIN", final "FO" (GETDNS_RRTYPE_UINFO) */ + if ((t[3]|0x20) == 'f' && (t[4]|0x20) == 'o' && t[5] == '\0') + return GETDNS_RRTYPE_UINFO; + return -1; + default : return -1; + }; + case 'N': + case 'n': /* before "UN", final "SPEC" (GETDNS_RRTYPE_UNSPEC) */ + if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'p' && (t[4]|0x20) == 'e' && (t[5]|0x20) == 'c' && t[6] == '\0') + return GETDNS_RRTYPE_UNSPEC; + return -1; + case 'R': + case 'r': /* before "UR", final "I" (GETDNS_RRTYPE_URI) */ + if ((t[2]|0x20) == 'i' && t[3] == '\0') + return GETDNS_RRTYPE_URI; + return -1; + default : return -1; + }; + case 'W': + case 'w': /* before "W", final "KS" (GETDNS_RRTYPE_WKS) */ + if ((t[1]|0x20) == 'k' && (t[2]|0x20) == 's' && t[3] == '\0') + return GETDNS_RRTYPE_WKS; + return -1; + case 'X': + case 'x': /* before "X", final "25" (GETDNS_RRTYPE_X25) */ + if (t[1] == '2' && t[2] == '5' && t[3] == '\0') + return GETDNS_RRTYPE_X25; + return -1; + default : return -1; + }; +} + +/* + * Copyright (c) 2013, NLNet Labs, Verisign, Inc. + * All rights reserved. + * + * 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 names of the copyright holders 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 Verisign, Inc. 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. + */ + +/* Local Variables: */ +/* c-file-style: "linux" */ +/* End: */ diff --git a/tools/dnssec/dns-wire2text.c b/tools/dnssec/dns-wire2text.c new file mode 100644 index 0000000..8e4b55d --- /dev/null +++ b/tools/dnssec/dns-wire2text.c @@ -0,0 +1,149 @@ +/* + Read RR's in getdns wire format and print them in presentation + format on stdout. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <getdns/getdns.h> +#include <getdns/getdns_extra.h> + +#undef DEBUG + +#if defined(DEBUG) +static void +hd(const char *buf, size_t buf_len) +{ + for (size_t n = 0; n < buf_len; n++) + { + if (n % 16 == 0) + { + if (n != 0) + fprintf(stderr, "\n"); + fprintf(stderr, "%08x ", n); + } + else if (n % 8 == 0) + fprintf(stderr, " "); + fprintf(stderr, "%02hhx ", buf[n]); + } + fprintf(stderr, "\n"); +} +#else /* !DEBUG */ +#define hd(a,b) +#endif + +/* Return value: + <0 -- error, the return value being -errorcode + 0 -- done + >0 -- not done yet, call me again +*/ +static int +read_rr(const uint8_t **buf, size_t *buf_len, getdns_dict **rr_dict) +{ + getdns_return_t r = getdns_wire2rr_dict_scan(buf, buf_len, rr_dict); + + if (r) + return -r; + return *buf_len; +} + +#define INBUFLEN 4096 + +static size_t +read_inbuf(FILE *infp, uint8_t **bufp_out) +{ + size_t nread = 0; + uint8_t *wirebuf = malloc(INBUFLEN); + int chunks = 1; + + if (wirebuf == NULL) + goto out; + + while (1) + { + size_t n = fread(wirebuf + nread, 1, INBUFLEN, infp); + nread += n; + if (n < INBUFLEN) + break; /* Done. */ + + wirebuf = realloc(wirebuf, ++chunks * INBUFLEN); + if (wirebuf == NULL) + break; + } + + out: + if (bufp_out != NULL) + *bufp_out = wirebuf; + return nread; +} + +int +main(int argc, char *argv[]) +{ + int rrv = 0; + uint8_t *inbuf = NULL; + const uint8_t *bufp = NULL; + size_t inbuf_len = 0; + getdns_dict *rr_dict = NULL; + getdns_return_t r = 0; + FILE *infp = stdin; + + if (argc > 1) + { + infp = fopen(argv[1], "r"); + if (infp == NULL) + { + perror("fopen(argv[1])"); + return -errno; + } + } + inbuf_len = read_inbuf(infp, &inbuf); + if (inbuf == NULL) + { + perror("read_inbuf"); + return -errno; + } + if (infp != stdin) + if (fclose(infp)) + perror("fclose"); + hd((const char *) wirebuf, n); + + bufp = inbuf; + while (1) + { + char *stringbuf = NULL; + + rrv = read_rr(&bufp, &inbuf_len, &rr_dict); + if (rrv < 0) + break; + + r = getdns_rr_dict2str(rr_dict, &stringbuf); + if (r) + break; + + getdns_dict_destroy(rr_dict); + printf("%s", stringbuf); + free(stringbuf); + + if (rrv == 0) + break; /* Done. */ + } + free(inbuf); + + if (rrv < 0) + { + fprintf(stderr, "parsing input failed: %s\n", + getdns_get_errorstr_by_id(-rrv)); + return rrv; + } + if (r) + { + fprintf(stderr, "converting dict to string failed: %s\n", + getdns_get_errorstr_by_id(r)); + return -r; + } + + return 0; +} diff --git a/tools/dnssec/net2wire.c b/tools/dnssec/net2wire.c new file mode 100644 index 0000000..fb4620b --- /dev/null +++ b/tools/dnssec/net2wire.c @@ -0,0 +1,301 @@ +/* This program is based on (IIRC) + * getdns/spec/example/example-simple-answers.c. + * + * Resolve DNS name argv[1] of type argv[2] using libgetdns and write + * the resulting RRs to two files, in DNS binary wire format: + * + * - binout: /just_address_answers/0/address_data and + * /just_address_answers/1/address_data + * + * - treeout: output from getdns_rr_dict2wire for all RR's in the + * answer section + * + * Based on getdns/spec/example/example-simple-answers.c. + */ + +#include <assert.h> +#include <inttypes.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <getdns/getdns.h> +#include <getdns/getdns_ext_libevent.h> +#include <event2/event.h> + +static int debug = 1; + +static getdns_return_t +dump_reply(FILE *fp, getdns_dict *reply, const char *section_name) +{ + getdns_list *section = NULL; + size_t section_len = -1; + getdns_return_t r; + uint8_t res_buf[4096], *res = NULL; + size_t res_len = sizeof(res_buf); + + r = getdns_dict_get_list(reply, section_name, §ion); + if (r) { + fprintf(stderr, + "unable to get section \"%s\" from reply\n", + section_name); + return r; + } + + r = getdns_list_get_length(section, §ion_len); + if (r) { + fprintf(stderr, "unable to get length of section\n"); + return r; + } + + for (size_t j = 0; j < section_len; j++) { + getdns_dict *rr = NULL; + + r = getdns_list_get_dict(section, j , &rr); + if (r) { + fprintf(stderr, "unable to get rr from entry " + "%d: %d\n", j, r); + return r; + } + + r = getdns_rr_dict2wire(rr, &res, &res_len); + if (r) { + fprintf(stderr, + "unable to convert entry %d " + "to wire format: %d\n", j, r); + return r; + } + + if (0 && debug) { + char *s = getdns_pretty_print_dict(rr); + puts(s); + free(s); + } + + if (fwrite(res, 1, res_len, fp) != res_len) { + fprintf(stderr, + "unable to write buffer to file: %s\n", + strerror(errno)); + return -errno; + } + + free(res); + } + + return 0; +} + +int +dump_tree(FILE *fp, const getdns_dict *response, const char *tree_name) +{ + getdns_return_t r; + getdns_list *tree = NULL; + size_t n_replies = -1; + + r = getdns_dict_get_list(response, tree_name, &tree); + if (r) { + fprintf(stderr, "unable to get tree %s\n", tree_name); + return r; + } + + r = getdns_list_get_length(tree, &n_replies); + if (r) { + fprintf(stderr, "unable to get number of replies\n"); + return r; + } + + for (size_t i = 0; i < n_replies; i++) { + getdns_dict *reply = NULL; + + r = getdns_list_get_dict(tree, i, &reply); + if (r) { + fprintf(stderr, "unable to get reply %d from tree\n", i); + return r; + } + + if (debug) { + char *s = getdns_pretty_print_dict(reply); + printf("Pretty-printing reply #%d:%s\n", i, s); + free(s); + } + + dump_reply(fp, reply, "answer"); + } + + return 0; +} + + +/* Set up the callback function, which will also do the processing of the results */ +void callback(getdns_context *context, + getdns_callback_type_t callback_type, + getdns_dict *response, + void *userarg, + getdns_transaction_t transaction_id) +{ + getdns_return_t r; /* Holder for all function returns */ + uint32_t status; + getdns_bindata *address_data; + char *first = NULL, *second = NULL; + FILE *binoutfp = NULL, *treeoutfp = NULL; + + (void) context; /* unused parameter */ + + binoutfp = fopen("binout", "w"); + assert(binoutfp != NULL); + treeoutfp = fopen("treeout", "w"); + assert(treeoutfp != NULL); + + printf( "Callback for query \"%s\" with request ID %"PRIu64".\n" + , (char *)userarg, transaction_id ); + + switch(callback_type) { + case GETDNS_CALLBACK_CANCEL: + printf("Transaction with ID %"PRIu64" was cancelled.\n", transaction_id); + return; + case GETDNS_CALLBACK_TIMEOUT: + printf("Transaction with ID %"PRIu64" timed out.\n", transaction_id); + return; + case GETDNS_CALLBACK_ERROR: + printf("An error occurred for transaction ID %"PRIu64".\n", transaction_id); + return; + default: break; + } + assert( callback_type == GETDNS_CALLBACK_COMPLETE ); + + if ((r = getdns_dict_get_int(response, "status", &status))) + fprintf(stderr, "Could not get \"status\" from reponse"); + + else if (status != GETDNS_RESPSTATUS_GOOD) + fprintf(stderr, "The search had no results, and a return value of %"PRIu32".\n", status); + + static char *TREES[] = {"replies_tree", "validation_chain"}; + static const int TREES_LEN = 2; + for (int i = 0; i < TREES_LEN; i++) { + if (dump_tree(treeoutfp, response, TREES[i])) + fprintf(stderr, "Could not dump %s to file\n", TREES[i]); + + else if ((r = getdns_dict_get_bindata(response, "/just_address_answers/0/address_data", &address_data))) + fprintf(stderr, "Could not get first address"); + + else if (fwrite(address_data->data, 1, address_data->size, binoutfp) != + address_data->size) + fprintf(stderr, "first fwrite: %s\n", strerror(errno)); + + else if (!(first = getdns_display_ip_address(address_data))) + fprintf(stderr, "Could not convert first address to string\n"); + + else if ((r = getdns_dict_get_bindata(response, "/just_address_answers/1/address_data", &address_data))) + fprintf(stderr, "Could not get second address"); + + else if (fwrite(address_data->data, 1, address_data->size, binoutfp) != + address_data->size) + fprintf(stderr, "second fwrite: %s\n", strerror(errno)); + + else if (!(second = getdns_display_ip_address(address_data))) + fprintf(stderr, "Could not convert second address to string\n"); + if (first) { + printf("The address is %s\n", first); + free(first); + } + if (second) { + printf("The address is %s\n", second); + free(second); + } + if (r) { + assert( r != GETDNS_RETURN_GOOD ); + fprintf(stderr, ": %d\n", r); + } + } + getdns_dict_destroy(response); + if (binoutfp) fclose(binoutfp); + if (treeoutfp) fclose(treeoutfp); +} + +static getdns_return_t +send_query(getdns_context *context, + char *query_name, + const uint16_t query_type, + getdns_transaction_t *transaction_id) +{ + getdns_return_t r = GETDNS_RETURN_GENERIC_ERROR; + getdns_dict *extensions = getdns_dict_create(); + char *userarg = query_name; + + assert(extensions != NULL); + + r = getdns_dict_set_int(extensions, + "dnssec_return_validation_chain", + GETDNS_EXTENSION_TRUE); + if (r) return r; + +#if 0 + r = getdns_dict_set_int(extensions, + "dnssec_return_status", + GETDNS_EXTENSION_TRUE); + if (r) return r; +#endif + + if (query_type == 0) + r = getdns_address(context, query_name, extensions, userarg, + transaction_id, callback); + else + r = getdns_general(context, query_name, query_type, + extensions, userarg, + transaction_id, callback); + if (r) return r; + + return r; +} + +int main(int argc, char *argv[]) +{ + getdns_return_t r; /* Holder for all function returns */ + getdns_context *context = NULL; + struct event_base *event_base = NULL; + char *query_name = "www.example.com"; + uint16_t query_type = 0; + getdns_transaction_t transaction_id; + getdns_list *trust_anchors; + + + if (argc > 1) + query_name = argv[1]; + if (argc > 2) + query_type = (uint16_t) atoi(argv[2]); + + if ((r = getdns_context_create(&context, 1))) + fprintf(stderr, "Trying to create the context failed"); + + else if ((r = getdns_context_get_dnssec_trust_anchors( + context, &trust_anchors)) || + trust_anchors == NULL) + fprintf(stderr, "No trust anchor.\n"); + + else if (!(event_base = event_base_new())) + fprintf(stderr, "Trying to create the event base failed.\n"); + + else if ((r = getdns_extension_set_libevent_base(context, event_base))) + fprintf(stderr, "Setting the event base failed"); + + else if ((r = send_query(context, query_name, query_type, + &transaction_id))) + fprintf(stderr, "Queueing query failed: %d\n", r); + + else { + printf("Request with transaction ID %"PRIu64" scheduled.\n", + transaction_id); + if (event_base_dispatch(event_base) < 0) + fprintf(stderr, "Error dispatching events\n"); + } + + /* Clean up */ + if (event_base) + event_base_free(event_base); + + if (context) + getdns_context_destroy(context); + + /* Assuming we get here, leave gracefully */ + exit(EXIT_SUCCESS); +} + diff --git a/tools/submitcert.py b/tools/submitcert.py index 91d2111..b185a62 100755 --- a/tools/submitcert.py +++ b/tools/submitcert.py @@ -42,7 +42,9 @@ logpublickey = get_public_key_from_file(args.publickey) if args.publickey else N lookup_in_log = False -if certfilepath[-1] == "/": +if certfilepath is None: + certfiles = None +elif certfilepath[-1] == "/": certfiles = [certfilepath + filename for filename in sorted(os.listdir(certfilepath)) if os.path.isfile(certfilepath + filename)] else: certfiles = [certfilepath] @@ -159,12 +161,17 @@ p = Pool(args.parallel, lambda: signal.signal(signal.SIGINT, signal.SIG_IGN)) nsubmitted = 0 lastprinted = 0 -print "listing certs" -ncerts = get_ncerts(certfiles) - -print ncerts, "certs" +def get_cert_from_stdin(): + yield (('<stdin>', base64.b64encode(sys.stdin.read()))) -certs = get_all_certificates(certfiles) +print "listing certs" +if certfiles is not None: + ncerts = get_ncerts(certfiles) + print ncerts, "certs" + certs = get_all_certificates(certfiles) +else: + ncerts = 1 + certs = get_cert_from_stdin() (result, timing) = submitcert(certs.next()) if result != None: |