aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenryk Plötz <henryk@ploetzli.ch>2014-10-03 20:19:52 +0200
committerHenryk Plötz <henryk@ploetzli.ch>2014-10-03 20:29:37 +0200
commiteaaa5b4f9301ae2e5a29dd77616d8e29fd3b2c28 (patch)
treec090b31513b85dfca08b878066faf9d2893c7a7a
parent898102385f20b06f08dc5fb379e1c9aa261a5700 (diff)
downloadtinydnssec-eaaa5b4f9301ae2e5a29dd77616d8e29fd3b2c28.tar.gz
tinydnssec-eaaa5b4f9301ae2e5a29dd77616d8e29fd3b2c28.tar.bz2
Add tinydnssec-1.05-1.3.tar.bz2
Source was http://www.tinydnssec.org/download/tinydnssec-1.05-1.3.tar.bz2, SHA1 b33d5c3e0de67f6427aad8c00a99580b59804075
-rw-r--r--INSTALL.tinydnssec45
-rw-r--r--README.tinydnssec132
-rw-r--r--TODO.tinydnssec13
-rw-r--r--djbdns-1.05-dnssec.patch1749
-rw-r--r--gpl-3.0.txt674
-rwxr-xr-xrun-tests.sh104
-rw-r--r--test/a-0116
-rw-r--r--test/a-029
-rw-r--r--test/a-0311
-rw-r--r--test/a-047
-rw-r--r--test/a-057
-rw-r--r--test/a-069
-rw-r--r--test/a-0718
-rw-r--r--test/a-0811
-rw-r--r--test/a-097
-rw-r--r--test/a-109
-rw-r--r--test/a-116
-rw-r--r--test/a-123
-rw-r--r--test/a-133
-rw-r--r--test/a-143
-rw-r--r--test/a-159
-rw-r--r--test/a-163
-rw-r--r--test/a-173
-rw-r--r--test/a-183
-rw-r--r--test/a-194
-rw-r--r--test/a-204
-rw-r--r--test/a-217
-rw-r--r--test/a-227
-rw-r--r--test/a-234
-rw-r--r--test/a-246
-rw-r--r--test/a-259
-rw-r--r--test/a-269
-rw-r--r--test/a-2712
-rw-r--r--test/a-287
-rw-r--r--test/a-2912
-rw-r--r--test/a-303
-rw-r--r--test/a-314
-rw-r--r--test/a-323
-rw-r--r--test/a-334
-rw-r--r--test/a-349
-rw-r--r--test/a-3510
-rw-r--r--test/a-363
-rw-r--r--test/a-373
-rw-r--r--test/a-384
-rw-r--r--test/a-395
-rw-r--r--test/data65
-rw-r--r--test/example.ksk53
-rw-r--r--test/example.zsk17
-rw-r--r--test/q-012
-rw-r--r--test/q-022
-rw-r--r--test/q-032
-rw-r--r--test/q-042
-rw-r--r--test/q-052
-rw-r--r--test/q-062
-rw-r--r--test/q-072
-rw-r--r--test/q-082
-rw-r--r--test/q-092
-rw-r--r--test/q-101
-rw-r--r--test/q-111
-rw-r--r--test/q-121
-rw-r--r--test/q-131
-rw-r--r--test/q-141
-rw-r--r--test/q-151
-rw-r--r--test/q-161
-rw-r--r--test/q-171
-rw-r--r--test/q-181
-rw-r--r--test/q-191
-rw-r--r--test/q-201
-rw-r--r--test/q-211
-rw-r--r--test/q-221
-rw-r--r--test/q-231
-rw-r--r--test/q-241
-rw-r--r--test/q-251
-rw-r--r--test/q-261
-rw-r--r--test/q-271
-rw-r--r--test/q-281
-rw-r--r--test/q-291
-rw-r--r--test/q-301
-rw-r--r--test/q-311
-rw-r--r--test/q-321
-rw-r--r--test/q-331
-rw-r--r--test/q-341
-rw-r--r--test/q-351
-rw-r--r--test/q-361
-rw-r--r--test/q-371
-rw-r--r--test/q-381
-rw-r--r--test/q-391
-rwxr-xr-xtinydns-sign.pl1025
88 files changed, 4191 insertions, 0 deletions
diff --git a/INSTALL.tinydnssec b/INSTALL.tinydnssec
new file mode 100644
index 0000000..677d774
--- /dev/null
+++ b/INSTALL.tinydnssec
@@ -0,0 +1,45 @@
+Installation of the tinydnssec patch
+====================================
+
+Requirements
+------------
+
+This patch is *not* against stock djbdns. Here's the minimal set of patches
+to install before the tinydnssec patch applies:
+
+1. http://www.fefe.de/dns/djbdns-1.05-test25.diff.bz2
+ Unfortunately, fefe refuses to name a license for this patch, which means
+ that I cannot redistribute it.
+
+2. My own fixes to the Makefile (IPv6-related): djbdns-ipv6-make.patch
+
+Build
+-----
+
+1. Download and unpack the original djbdns sources from
+ http://cr.yp.to/djbdns/install.html .
+2. Download and apply the patches listed above.
+3. Download and unpack http://tinydnssec.org/tinydnssec-1.05-1.tar.gz in
+ the top-level source directory.
+4. Apply djbdns-1.05-dnssec.patch.
+5. Install as per usual instructions (see http://cr.yp.to/djbdns/install.html ).
+6. Optional: run tests (see below).
+7. Install djbdns as per original instructions, or whatever your preferred
+ method is.
+8. Install tinydns-sign.pl in your preferred location for system
+ executables, like e. g. /usr/sbin .
+9. Optional: create a manpage for tinydns-sign using e. g.
+ pod2man -s 8 -c '' "tinydns-sign.pl" >tinydns-sign.8
+ then install it in your preferred location for system manpages.
+
+
+Test
+----
+
+run-tests.sh will sign test/data using keys from test/example*, then issue
+some queries using tinydns-get, i. e. without any networking involved.
+
+As root, start tinydns / axfrdns on a local address (127.0.0.3), then execute
+SERVER=127.0.0.3 run-tests.sh -t -u
+to test the same queries via tcp and udp.
+
diff --git a/README.tinydnssec b/README.tinydnssec
new file mode 100644
index 0000000..bf15c76
--- /dev/null
+++ b/README.tinydnssec
@@ -0,0 +1,132 @@
+DNSSEC for tinydns
+==================
+
+This project adds DNSSEC support to D. J. Bernstein's tinydns (see
+http://cr.yp.to/djbdns.html ).
+
+It consists of two parts (mostly):
+
+- tinydns-sign, a perl script for augmenting a tinydns-data file with
+ DNSSEC-related RRs, and
+
+- a patch to tinydns / axfrdns to make them produce DNSSEC-authenticated
+ answers.
+
+The patch tries to preserve the behaviour of tinydns/axfrdns wrt non-DNSSEC
+queries, with these noteworthy exceptions:
+
+- The interpretation of wildcard records now matches the description in
+ RFC-1034 section 4.3.3. Specifically, if there's a wildcard *.x and a
+ record for a.x, then a query for y.a.x will *not* be answered using the
+ wildcard (for a label 'a' and series of labels 'x' and 'y').
+ This change is required for signed domains, because authentication of
+ negative responses requires a common understanding between client and
+ server about the meaning of wildcards.
+
+- EDNS0 in queries will be honoured also for non-DNSSEC queries, i. e.
+ tinydns may produce answers exceeding 512 bytes. (There is a hard
+ limit of 4000 bytes, though.)
+ This *can* lead to problems on IPv6 networks.
+
+- TXT records are split into character-strings of 255 bytes, not 127.
+ This is not really a DNSSEC-related change, but this is kind of a FAQ [5] and
+ tinydns-data and tinydns-sign must agree on how this is handled or the
+ generated RRSIG won't match.
+
+- The patch includes a fix for the broken CNAME handling in tinydns. See [6]
+ for a description of the problem. The patch referenced by that description
+ conflicts with fefe's IPv6 patch and requires further modifications for
+ DNSSEC, so I decided to roll my own solution.
+
+Be careful with publishing signed zones as a secondary nameserver: the
+modified tinydns/axfrdns require certain helper RRs in the database to
+simplify locating NSEC3 records. Without these helpers, tinydns cannot
+generate valid negative response nor valid wildcard responses.
+
+Axfrdns *will* publish these helper RRs, other primaries will most
+likely *not*.
+
+
+HOWTO
+-----
+
+0. Install tinydns-sign and patched tinydns/axfrdns.
+
+1. Generate key(s). See the tinydns-sign manpage for details.
+
+ It is common practice to have a "Key signing key" (KSK, with flags=257)
+ and a "Zone signing key" (ZSK, with flags=256). The KSK is used only for
+ signing the DNSKEY RRs, the ZSK is used for signing the rest. The KSK is
+ more difficult to change because it is used in the delegating domain's
+ referral, therefore it usually has more bits. The ZSK is used for signing
+ all the other records, and is therefore usually shorter and changed more
+ frequently.
+
+ You should keep the keys in a safe place (outside the tinydns ROOT), e. g.
+ in a directory "keys" located above the ROOT.
+
+2. Add the K pseudo records from the key files to your tinydns-data file.
+ Also, add a P pseudo record for each signed zone.
+
+3. Adapt the Makefile to pipe your data file through tinydns-sign before
+ before running tinydns-data, e. g.
+
+data.cdb: data update
+ tinydns-sign ../keys/* <data >data.tmp
+ mv data.tmp data
+ tinydns-data
+ rm -f update
+
+update:
+ touch update
+
+4. Run make.
+
+5. Set up a cronjob to periodically re-sign your data file before the
+ signatures expire.
+
+6. TEST! For example:
+
+ * Use dig axfr <domain> @<server> and validate the result with a dnssec zone
+ validator, like yazvs [1].
+
+ * Use an online DNS or DNSSEC test tool. See [2] for a list.
+
+7. Read RFC-4641 [3] to get a feeling for what is explicitly not called
+ "Best Current Practices". :-)
+
+ In particular, think about key lifetime and how to do a key rollover.
+
+8. Sacrifice a few small animals to a deity of your choice. Get yourself a
+ drink for really tough guys, like prune juice [4].
+
+9. If you feel brave, contact your upstream delegator to publish DS records
+ for your zone.
+
+ Note that this is a really good way to cut yourself off from the rest of the
+ internet. You've been warned, so don't blame me.
+
+LICENSE
+-------
+
+(C) 2012 Peter Conrad <conrad@quisquis.de>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 3 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+[1] http://yazvs.verisignlabs.com/ .
+[2] http://www.bortzmeyer.org/tests-dns.html
+[3] http://tools.ietf.org/html/rfc4641
+[4] http://en.memory-alpha.org/wiki/Prune_juice
+[5] http://marc.info/?l=djbdns&m=120848817816960&w=2
+[6] http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/djbdns-problems.html#tinydns-alias-chain-truncation
diff --git a/TODO.tinydnssec b/TODO.tinydnssec
new file mode 100644
index 0000000..70fe34e
--- /dev/null
+++ b/TODO.tinydnssec
@@ -0,0 +1,13 @@
+
+* Make hardcoded limit for EDNS0 response size configurable (environment).
+
+* Add support for locations + timestamps to tinydns-sign (basically, treat
+ them as separate zones, with timestamp-dependend SOA serials).
+
+* Add support for ((semi-)automatic) ZSK rollover using pre-publish method.
+
+* Refactor tinydns-sign:
+ - separate generation of helper records
+ - put some functionality into modules
+ - make (better) use of existing Net::DNS modules
+
diff --git a/djbdns-1.05-dnssec.patch b/djbdns-1.05-dnssec.patch
new file mode 100644
index 0000000..d2b2323
--- /dev/null
+++ b/djbdns-1.05-dnssec.patch
@@ -0,0 +1,1749 @@
+(C) 2012 Peter Conrad <conrad@quisquis.de>
+
+This patch is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 3 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+diff -rNU3 djbdns-1.05.tds-base/axfrdns.c djbdns-1.05.tinydnssec/axfrdns.c
+--- djbdns-1.05.tds-base/axfrdns.c 2012-12-06 22:45:38.000000000 +0100
++++ djbdns-1.05.tinydnssec/axfrdns.c 2012-12-06 22:39:13.000000000 +0100
+@@ -23,6 +23,7 @@
+ #include "response.h"
+ #include "ip6.h"
+ #include "clientloc.h"
++#include "edns0.h"
+
+ extern int respond(char *,char *,char *);
+
+@@ -346,6 +347,9 @@
+ if (byte_diff(qclass,2,DNS_C_IN) && byte_diff(qclass,2,DNS_C_ANY))
+ strerr_die2x(111,FATAL,"bogus query: bad class");
+
++ pos = check_edns0(header, buf, len, pos);
++ if (!pos) die_truncated();
++
+ qlog(ip,port,header,zone,qtype," ");
+
+ if (byte_equal(qtype,2,DNS_T_AXFR)) {
+diff -rNU3 djbdns-1.05.tds-base/base32hex.c djbdns-1.05.tinydnssec/base32hex.c
+--- djbdns-1.05.tds-base/base32hex.c 1970-01-01 01:00:00.000000000 +0100
++++ djbdns-1.05.tinydnssec/base32hex.c 2012-12-06 22:39:13.000000000 +0100
+@@ -0,0 +1,79 @@
++/* (C) 2012 Peter Conrad <conrad@quisquis.de>
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 3 as
++ * published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "base32hex.h"
++
++#define to_32hex(c) ((c) < 10 ? (c) + '0' : (c) + 'a' - 10)
++
++/* out must point to a buffer of at least (len * 8 / 5) + 1 bytes.
++ * Encoded string is *not* padded.
++ * See RFC-4648. This implementation produces lowercase hex characters.
++ * Returns length of encoded string.
++ */
++unsigned int base32hex(char *out, uint8_t *in, unsigned int len) {
++int buf = 0, bits = 0;
++char *x = out;
++
++ while (len-- > 0) {
++ buf <<= 8;
++ buf |= *in++;
++ bits += 8;
++ while (bits >= 5) {
++ char c = (buf >> (bits - 5)) & 0x1f;
++ *x++ = to_32hex(c);
++ bits -= 5;
++ }
++ }
++ if (bits > 0) {
++ char c = (buf << (5 - bits)) & 0x1f;
++ *x++ = to_32hex(c);
++ }
++ return x - out;
++}
++
++#ifdef TEST
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++static void test(char *in, char *expected, int explen) {
++char buf[255];
++int r;
++
++ if ((r = base32hex(buf, in, strlen(in))) != explen) {
++ printf("Failed: b32h('%s') yields %d chars (expected %d)\n",
++ in, r, explen);
++ exit(1);
++ }
++ if (strncmp(buf, expected, r)) {
++ buf[r] = 0;
++ printf("Failed: b32h('%s') = '%s' (expected %s)\n",
++ in, buf, expected);
++ exit(1);
++ }
++}
++
++int main(int argc, char **argv) {
++ test("", "", 0);
++ test("f", "co", 2);
++ test("fo", "cpng", 4);
++ test("foo", "cpnmu", 5);
++ test("foob", "cpnmuog", 7);
++ test("fooba", "cpnmuoj1", 8);
++ test("foobar", "cpnmuoj1e8", 10);
++ printf("Success!\n");
++}
++
++#endif
+diff -rNU3 djbdns-1.05.tds-base/base32hex.h djbdns-1.05.tinydnssec/base32hex.h
+--- djbdns-1.05.tds-base/base32hex.h 1970-01-01 01:00:00.000000000 +0100
++++ djbdns-1.05.tinydnssec/base32hex.h 2012-12-06 22:39:13.000000000 +0100
+@@ -0,0 +1,23 @@
++/* (C) 2012 Peter Conrad <conrad@quisquis.de>
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 3 as
++ * published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef _BASE32_HEX_H
++#define _BASE32_HEX_H
++
++#include <stdint.h>
++
++extern unsigned int base32hex(char *out, uint8_t *in, unsigned int len);
++
++#endif
+diff -rNU3 djbdns-1.05.tds-base/dns.h djbdns-1.05.tinydnssec/dns.h
+--- djbdns-1.05.tds-base/dns.h 2012-12-06 22:45:38.000000000 +0100
++++ djbdns-1.05.tinydnssec/dns.h 2012-12-06 22:39:13.000000000 +0100
+@@ -20,8 +20,17 @@
+ #define DNS_T_SIG "\0\30"
+ #define DNS_T_KEY "\0\31"
+ #define DNS_T_AAAA "\0\34"
++#define DNS_T_OPT "\0\51"
++#define DNS_T_DS "\0\53"
++#define DNS_T_RRSIG "\0\56"
++#define DNS_T_DNSKEY "\0\60"
++#define DNS_T_NSEC3 "\0\62"
++#define DNS_T_NSEC3PARAM "\0\63"
+ #define DNS_T_AXFR "\0\374"
+ #define DNS_T_ANY "\0\377"
++/* Pseudo-RRs for DNSSEC */
++#define DNS_T_HASHREF "\377\1"
++#define DNS_T_HASHLIST "\377\2"
+
+ struct dns_transmit {
+ char *query; /* 0, or dynamically allocated */
+diff -rNU3 djbdns-1.05.tds-base/edns0.c djbdns-1.05.tinydnssec/edns0.c
+--- djbdns-1.05.tds-base/edns0.c 1970-01-01 01:00:00.000000000 +0100
++++ djbdns-1.05.tinydnssec/edns0.c 2012-12-06 22:39:13.000000000 +0100
+@@ -0,0 +1,45 @@
++/* (C) 2012 Peter Conrad <conrad@quisquis.de>
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 3 as
++ * published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "dns.h"
++#include "edns0.h"
++#include "response.h"
++#include "uint16.h"
++
++unsigned int check_edns0(const char header[12], const char *buf, const int len, unsigned int pos)
++{
++char opt_class[2];
++char opt_ttl[4];
++
++ max_response_len = 512;
++ do_dnssec = 0;
++ if (!header[6] && !header[7] && !header[8] && !header[9]
++ && !header[10] && header[11] == 1) {
++ char nametype[3];
++ uint16 size, min_len;
++ pos = dns_packet_copy(buf,len,pos,nametype,3); if (!pos) return pos;
++ if (nametype[0] || nametype[1] || nametype[2] != DNS_T_OPT[1]) return pos;
++ pos = dns_packet_copy(buf,len,pos,opt_class,2); if (!pos) return pos;
++ pos = dns_packet_copy(buf,len,pos,opt_ttl,4); if (!pos) return pos;
++ if (opt_ttl[0]) return pos; // unsupported RCODE in query
++ if (opt_ttl[1]) return pos; // unsupported version
++ do_dnssec = opt_ttl[2] & 0x80;
++ uint16_unpack_big(opt_class, &size);
++ min_len = do_dnssec ? 1220 : 512;
++ max_response_len = size > 4000 ? 4000 : size;
++ if (max_response_len < min_len) { max_response_len = min_len; }
++ }
++ return pos;
++}
+diff -rNU3 djbdns-1.05.tds-base/edns0.h djbdns-1.05.tinydnssec/edns0.h
+--- djbdns-1.05.tds-base/edns0.h 1970-01-01 01:00:00.000000000 +0100
++++ djbdns-1.05.tinydnssec/edns0.h 2012-12-06 22:39:13.000000000 +0100
+@@ -0,0 +1,22 @@
++/* (C) 2012 Peter Conrad <conrad@quisquis.de>
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 3 as
++ * published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef _EDNS0_H
++
++#define _EDNS0_H
++
++extern unsigned int check_edns0(const char *, const char *, const int, unsigned int);
++
++#endif
+diff -rNU3 djbdns-1.05.tds-base/Makefile djbdns-1.05.tinydnssec/Makefile
+--- djbdns-1.05.tds-base/Makefile 2012-12-06 22:45:45.000000000 +0100
++++ djbdns-1.05.tinydnssec/Makefile 2012-12-07 11:07:23.000000000 +0100
+@@ -53,10 +53,10 @@
+ axfrdns: \
+ load axfrdns.o iopause.o droproot.o tdlookup.o response.o qlog.o \
+ prot.o timeoutread.o timeoutwrite.o clientloc.o dns.a libtai.a alloc.a env.a \
+-cdb.a buffer.a unix.a byte.a
++cdb.a buffer.a unix.a byte.a sha1.o base32hex.o edns0.o
+ ./load axfrdns iopause.o droproot.o tdlookup.o response.o \
+ qlog.o prot.o timeoutread.o timeoutwrite.o clientloc.o dns.a libtai.a \
+- alloc.a env.a cdb.a buffer.a unix.a byte.a
++ alloc.a env.a cdb.a buffer.a unix.a byte.a sha1.o base32hex.o edns0.o
+
+ axfrdns-conf: \
+ load axfrdns-conf.o generic-conf.o auto_home.o buffer.a unix.a byte.a
+@@ -76,6 +76,10 @@
+ response.h uint32.h clientloc.h
+ ./compile axfrdns.c
+
++base32hex.o: \
++compile base32hex.c base32hex.h
++ ./compile base32hex.c
++
+ buffer.a: \
+ makelib buffer.o buffer_1.o buffer_2.o buffer_copy.o buffer_get.o \
+ buffer_put.o strerr_die.o strerr_sys.o
+@@ -454,10 +458,11 @@
+
+ dnsq: \
+ load dnsq.o iopause.o printrecord.o printpacket.o parsetype.o dns.a \
+-env.a libtai.a buffer.a alloc.a unix.a byte.a socket.lib
++env.a libtai.a buffer.a alloc.a unix.a byte.a socket.lib printtype.o \
++base32hex.o
+ ./load dnsq iopause.o printrecord.o printpacket.o \
+ parsetype.o dns.a env.a libtai.a buffer.a alloc.a unix.a \
+- byte.a `cat socket.lib`
++ byte.a `cat socket.lib` printtype.o base32hex.o
+
+ dnsq.o: \
+ compile dnsq.c uint16.h strerr.h buffer.h scan.h str.h byte.h error.h \
+@@ -467,10 +472,11 @@
+
+ dnsqr: \
+ load dnsqr.o iopause.o printrecord.o printpacket.o parsetype.o dns.a \
+-env.a libtai.a buffer.a alloc.a unix.a byte.a socket.lib
++env.a libtai.a buffer.a alloc.a unix.a byte.a socket.lib printtype.o \
++base32hex.o
+ ./load dnsqr iopause.o printrecord.o printpacket.o \
+ parsetype.o dns.a env.a libtai.a buffer.a alloc.a unix.a \
+- byte.a `cat socket.lib`
++ byte.a `cat socket.lib` printtype.o base32hex.o
+
+ dnsqr.o: \
+ compile dnsqr.c uint16.h strerr.h buffer.h scan.h str.h byte.h \
+@@ -480,10 +486,10 @@
+
+ dnstrace: \
+ load dnstrace.o dd.o iopause.o printrecord.o parsetype.o dns.a env.a \
+-libtai.a alloc.a buffer.a unix.a byte.a socket.lib
++libtai.a alloc.a buffer.a unix.a byte.a socket.lib printtype.o base32hex.o
+ ./load dnstrace dd.o iopause.o printrecord.o parsetype.o \
+ dns.a env.a libtai.a alloc.a buffer.a unix.a byte.a `cat \
+- socket.lib`
++ socket.lib` printtype.o base32hex.o
+
+ dnstrace.o: \
+ compile dnstrace.c uint16.h uint32.h fmt.h str.h byte.h ip4.h \
+@@ -514,6 +520,10 @@
+ compile droproot.c env.h scan.h prot.h strerr.h
+ ./compile droproot.c
+
++edns0.o: \
++compile edns0.c edns0.h uint16.h dns.h response.h iopause.h taia.h uint64.h
++ ./compile edns0.c
++
+ env.a: \
+ makelib env.o
+ ./makelib env.a env.o
+@@ -689,10 +699,10 @@
+
+ pickdns: \
+ load pickdns.o server.o iopause.o response.o droproot.o qlog.o prot.o dns.a \
+-env.a libtai.a cdb.a alloc.a buffer.a unix.a byte.a socket.lib
++env.a libtai.a cdb.a alloc.a buffer.a unix.a byte.a socket.lib edns0.o
+ ./load pickdns server.o iopause.o response.o droproot.o qlog.o \
+ prot.o dns.a env.a libtai.a cdb.a alloc.a buffer.a unix.a \
+- byte.a `cat socket.lib`
++ byte.a `cat socket.lib` edns0.o
+
+ pickdns-conf: \
+ load pickdns-conf.o generic-conf.o auto_home.o buffer.a unix.a byte.a
+@@ -725,15 +735,19 @@
+ printpacket.o: \
+ compile printpacket.c uint16.h uint32.h error.h byte.h dns.h \
+ stralloc.h gen_alloc.h iopause.h taia.h tai.h uint64.h taia.h \
+-printrecord.h stralloc.h printpacket.h stralloc.h
++printrecord.h stralloc.h printpacket.h stralloc.h printtype.h
+ ./compile printpacket.c
+
+ printrecord.o: \
+ compile printrecord.c uint16.h uint32.h error.h byte.h dns.h \
+ stralloc.h gen_alloc.h iopause.h taia.h tai.h uint64.h taia.h \
+-printrecord.h stralloc.h
++printrecord.h stralloc.h printtype.h
+ ./compile printrecord.c
+
++printtype.o: \
++compile printtype.c printtype.h dns.h stralloc.h byte.h uint16.h iopause.h taia.h uint64.h
++ ./compile printtype.c
++
+ prog: \
+ dnscache-conf dnscache walldns-conf walldns rbldns-conf rbldns \
+ rbldns-data pickdns-conf pickdns pickdns-data tinydns-conf tinydns \
+@@ -767,10 +781,10 @@
+
+ rbldns: \
+ load rbldns.o server.o iopause.o response.o dd.o droproot.o qlog.o prot.o dns.a \
+-env.a libtai.a cdb.a alloc.a buffer.a unix.a byte.a socket.lib
++env.a libtai.a cdb.a alloc.a buffer.a unix.a byte.a socket.lib edns0.o
+ ./load rbldns server.o iopause.o response.o dd.o droproot.o qlog.o \
+ prot.o dns.a env.a libtai.a cdb.a alloc.a buffer.a unix.a \
+- byte.a `cat socket.lib`
++ byte.a `cat socket.lib` edns0.o
+
+ rbldns-conf: \
+ load rbldns-conf.o generic-conf.o auto_home.o buffer.a unix.a byte.a
+@@ -840,7 +854,7 @@
+ compile server.c byte.h case.h env.h buffer.h strerr.h ip4.h uint16.h \
+ ndelay.h socket.h uint16.h droproot.h qlog.h uint16.h response.h \
+ uint32.h dns.h stralloc.h gen_alloc.h iopause.h taia.h tai.h uint64.h \
+-taia.h iopause.h alloc.h str.h
++taia.h iopause.h alloc.h str.h edns0.h
+ ./compile server.c
+
+ setup: \
+@@ -931,6 +945,10 @@
+ cp /dev/null haven2i.h
+ ./choose cL tryn2i haven2i.h1 haven2i.h2 socket > haven2i.h
+
++sha1.o: \
++compile sha1.c sha1.h
++ ./compile sha1.c
++
+ str_chr.o: \
+ compile str_chr.c str.h
+ ./compile str_chr.c
+@@ -1072,7 +1090,7 @@
+ tdlookup.o: \
+ compile tdlookup.c uint16.h open.h tai.h uint64.h cdb.h uint32.h \
+ byte.h case.h dns.h stralloc.h gen_alloc.h iopause.h taia.h tai.h \
+-taia.h seek.h response.h uint32.h ip6.h clientloc.h
++taia.h seek.h response.h uint32.h ip6.h clientloc.h sha1.h base32hex.h
+ ./compile tdlookup.c
+
+ timeoutread.o: \
+@@ -1088,10 +1106,10 @@
+ tinydns: \
+ load tinydns.o server.o iopause.o droproot.o tdlookup.o response.o qlog.o \
+ prot.o clientloc.o dns.a libtai.a env.a cdb.a alloc.a buffer.a unix.a byte.a \
+-socket.lib
++socket.lib sha1.o base32hex.o edns0.o
+ ./load tinydns server.o iopause.o droproot.o tdlookup.o response.o \
+ qlog.o prot.o clientloc.o dns.a libtai.a env.a cdb.a alloc.a buffer.a \
+- unix.a byte.a `cat socket.lib`
++ unix.a byte.a `cat socket.lib` sha1.o base32hex.o edns0.o
+
+ tinydns-conf: \
+ load tinydns-conf.o generic-conf.o auto_home.o buffer.a unix.a byte.a
+@@ -1126,11 +1144,12 @@
+ ./compile tinydns-edit.c
+
+ tinydns-get: \
+-load tinydns-get.o tdlookup.o response.o printpacket.o printrecord.o \
+-parsetype.o clientloc.o dns.a libtai.a cdb.a buffer.a alloc.a unix.a byte.a
++load tinydns-get.o tdlookup.o sha1.o response.o printpacket.o printrecord.o \
++parsetype.o clientloc.o dns.a libtai.a cdb.a buffer.a alloc.a unix.a byte.a \
++base32hex.o printtype.o
+ ./load tinydns-get tdlookup.o response.o printpacket.o \
+ printrecord.o parsetype.o clientloc.o dns.a libtai.a cdb.a buffer.a \
+- alloc.a unix.a byte.a
++ alloc.a unix.a byte.a sha1.o base32hex.o printtype.o
+
+ tinydns-get.o: \
+ compile tinydns-get.c str.h byte.h scan.h exit.h stralloc.h \
+@@ -1198,10 +1217,10 @@
+
+ walldns: \
+ load walldns.o server.o iopause.o response.o droproot.o qlog.o prot.o dd.o \
+-dns.a env.a cdb.a alloc.a buffer.a unix.a byte.a socket.lib
++dns.a env.a cdb.a alloc.a buffer.a unix.a byte.a socket.lib edns0.o
+ ./load walldns server.o iopause.o response.o droproot.o qlog.o \
+ prot.o dd.o dns.a libtai.a env.a cdb.a alloc.a buffer.a unix.a \
+- byte.a `cat socket.lib`
++ byte.a `cat socket.lib` edns0.o
+
+ walldns-conf: \
+ load walldns-conf.o generic-conf.o auto_home.o buffer.a unix.a byte.a
+@@ -1227,4 +1246,4 @@
+ ./choose c trysa6 sockaddr_in6.h1 sockaddr_in6.h2 > sockaddr_in6.h
+
+ clean:
+- rm -f `cat TARGETS`
++ rm -f `cat TARGETS` data data.cdb test/[ot]*
+diff -rNU3 djbdns-1.05.tds-base/parsetype.c djbdns-1.05.tinydnssec/parsetype.c
+--- djbdns-1.05.tds-base/parsetype.c 2001-02-11 22:11:45.000000000 +0100
++++ djbdns-1.05.tinydnssec/parsetype.c 2012-12-06 22:39:13.000000000 +0100
+@@ -24,6 +24,8 @@
+ else if (case_equals(s,"key")) byte_copy(type,2,DNS_T_KEY);
+ else if (case_equals(s,"aaaa")) byte_copy(type,2,DNS_T_AAAA);
+ else if (case_equals(s,"axfr")) byte_copy(type,2,DNS_T_AXFR);
++ else if (case_equals(s,"dnskey")) byte_copy(type,2,DNS_T_DNSKEY);
++ else if (case_equals(s,"ds")) byte_copy(type,2,DNS_T_DS);
+ else
+ return 0;
+
+diff -rNU3 djbdns-1.05.tds-base/printpacket.c djbdns-1.05.tinydnssec/printpacket.c
+--- djbdns-1.05.tds-base/printpacket.c 2001-02-11 22:11:45.000000000 +0100
++++ djbdns-1.05.tinydnssec/printpacket.c 2012-12-06 22:39:13.000000000 +0100
+@@ -5,6 +5,7 @@
+ #include "dns.h"
+ #include "printrecord.h"
+ #include "printpacket.h"
++#include "printtype.h"
+
+ static char *d;
+
+@@ -67,8 +68,7 @@
+ X("weird class")
+ }
+ else {
+- uint16_unpack_big(data,&type);
+- NUM(type)
++ if (!printtype(out,data)) return 0;
+ X(" ")
+ if (!dns_domain_todot_cat(out,d)) return 0;
+ }
+diff -rNU3 djbdns-1.05.tds-base/printrecord.c djbdns-1.05.tinydnssec/printrecord.c
+--- djbdns-1.05.tds-base/printrecord.c 2012-12-06 22:45:38.000000000 +0100
++++ djbdns-1.05.tinydnssec/printrecord.c 2012-12-06 22:39:13.000000000 +0100
+@@ -5,9 +5,25 @@
+ #include "dns.h"
+ #include "printrecord.h"
+ #include "ip6.h"
++#include "base32hex.h"
++#include "printtype.h"
+
+ static char *d;
+
++static const char *HEX = "0123456789ABCDEF";
++
++static int hexout(stralloc *out,const char *buf,unsigned int len,unsigned int pos,unsigned int n) {
++ unsigned char c;
++ int i;
++
++ for (i = 0; i < n; i++) {
++ pos = dns_packet_copy(buf,len,pos,&c,1); if (!pos) return 0;
++ if (!stralloc_catb(out,&HEX[(c>>4)&0xf],1)) return 0;
++ if (!stralloc_catb(out,&HEX[c&0xf],1)) return 0;
++ }
++ return pos;
++}
++
+ unsigned int printrecord_cat(stralloc *out,const char *buf,unsigned int len,unsigned int pos,const char *q,const char qtype[2])
+ {
+ const char *x;
+@@ -18,6 +34,7 @@
+ unsigned int newpos;
+ int i;
+ unsigned char ch;
++ int rawlen;
+
+ pos = dns_packet_getname(buf,len,pos,&d); if (!pos) return 0;
+ pos = dns_packet_copy(buf,len,pos,misc,10); if (!pos) return 0;
+@@ -33,15 +50,20 @@
+
+ if (!dns_domain_todot_cat(out,d)) return 0;
+ if (!stralloc_cats(out," ")) return 0;
+- uint32_unpack_big(misc + 4,&u32);
+- if (!stralloc_catulong0(out,u32,0)) return 0;
++ if (byte_diff(misc,2,DNS_T_OPT)) {
++ uint32_unpack_big(misc + 4,&u32);
++ if (!stralloc_catulong0(out,u32,0)) return 0;
+
+- if (byte_diff(misc + 2,2,DNS_C_IN)) {
+- if (!stralloc_cats(out," weird class\n")) return 0;
+- return newpos;
++ if (byte_diff(misc + 2,2,DNS_C_IN)) {
++ if (!stralloc_cats(out," weird class\n")) return 0;
++ return newpos;
++ }
++ } else {
++ if (!stralloc_cats(out,"0")) return 0;
+ }
+
+ x = 0;
++ rawlen = 0;
+ if (byte_equal(misc,2,DNS_T_NS)) x = " NS ";
+ if (byte_equal(misc,2,DNS_T_PTR)) x = " PTR ";
+ if (byte_equal(misc,2,DNS_T_CNAME)) x = " CNAME ";
+@@ -92,12 +114,111 @@
+ stringlen=ip6_fmt(ip6str,misc);
+ if (!stralloc_catb(out,ip6str,stringlen)) return 0;
+ }
++ else if (byte_equal(misc,2,DNS_T_DNSKEY)) {
++ pos = dns_packet_copy(buf,len,pos,misc,4); if (!pos) return 0;
++ if (!stralloc_cats(out," DNSKEY ")) return 0;
++ uint16_unpack_big(misc,&u16);
++ if (!stralloc_catulong0(out,u16,0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!stralloc_catulong0(out,misc[2],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!stralloc_catulong0(out,misc[3],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ rawlen = datalen - 4;
++ }
++ else if (byte_equal(misc,2,DNS_T_DS)) {
++ pos = dns_packet_copy(buf,len,pos,misc,4); if (!pos) return 0;
++ if (!stralloc_cats(out," DS ")) return 0;
++ uint16_unpack_big(misc,&u16);
++ if (!stralloc_catulong0(out,u16,0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!stralloc_catulong0(out,misc[2],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!stralloc_catulong0(out,misc[3],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ pos = hexout(out,buf,len,pos,datalen - 4); if (!pos) return 0;
++ }
++ else if (byte_equal(misc,2,DNS_T_RRSIG)) {
++ pos = dns_packet_copy(buf,len,pos,misc,18); if (!pos) return 0;
++ if (!stralloc_cats(out," RRSIG ")) return 0;
++ if (!printtype(out,misc)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!stralloc_catulong0(out,misc[2],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!stralloc_catulong0(out,misc[3],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ uint32_unpack_big(misc + 4,&u32);
++ if (!stralloc_catulong0(out,u32,0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ uint32_unpack_big(misc + 8,&u32);
++ if (!stralloc_catulong0(out,u32,0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ uint32_unpack_big(misc + 12,&u32);
++ if (!stralloc_catulong0(out,u32,0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ uint16_unpack_big(misc + 16,&u16);
++ if (!stralloc_catulong0(out,u16,0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ rawlen = dns_packet_getname(buf,len,pos,&d); if (!pos) return 0;
++ rawlen = datalen - 18 - (rawlen - pos);
++ pos += datalen - 18 - rawlen;
++ if (!dns_domain_todot_cat(out,d)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ }
++ else if (byte_equal(misc,2,DNS_T_NSEC3)) {
++ char nextHash[255];
++ char nextOwner[255*8/5];
++ int j;
++ pos = dns_packet_copy(buf,len,pos,misc,5); if (!pos) return 0;
++ if (!stralloc_cats(out," NSEC3 ")) return 0;
++ if (!stralloc_catulong0(out,misc[0],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!stralloc_catulong0(out,misc[1],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ uint16_unpack_big(misc+2,&u16);
++ if (!stralloc_catulong0(out,u16,0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!misc[4])
++ if (!stralloc_cats(out,"-")) return 0;
++ pos = hexout(out,buf,len,pos,misc[4]); if (!pos) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ pos = dns_packet_copy(buf,len,pos,misc,1); if (!pos) return 0;
++ pos = dns_packet_copy(buf,len,pos,nextHash,misc[0]); if (!pos) return 0;
++ i = base32hex(nextOwner, nextHash, misc[0]);
++ if (!stralloc_catb(out,nextOwner,i)) return 0;
++ while (pos < newpos) {
++ pos = dns_packet_copy(buf,len,pos,misc,2); if (!pos) return 0;
++ pos = dns_packet_copy(buf,len,pos,nextHash,misc[1]); if (!pos) return 0;
++ j = 8 * misc[1];
++ for (i = 0; i < j; i++) {
++ if (nextHash[i/8] & (1 << (7 - (i%8)))) {
++ misc[1] = i;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!printtype(out,misc)) return 0;
++ }
++ }
++ }
++ }
++ else if (byte_equal(misc,2,DNS_T_OPT)) {
++ if (!stralloc_cats(out," OPT ")) return 0;
++ uint16_unpack_big(misc+2, &u16);
++ if (!stralloc_catulong0(out,u16,0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!stralloc_catulong0(out,misc[4],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!stralloc_catulong0(out,misc[5],0)) return 0;
++ if (!stralloc_cats(out," ")) return 0;
++ if (!hexout(out,misc,8,6,2)) return 0;
++ rawlen = datalen;
++ }
+ else {
+ if (!stralloc_cats(out," ")) return 0;
+ uint16_unpack_big(misc,&u16);
+ if (!stralloc_catulong0(out,u16,0)) return 0;
+ if (!stralloc_cats(out," ")) return 0;
+- while (datalen--) {
++ rawlen = datalen;
++ }
++ while (rawlen--) {
+ pos = dns_packet_copy(buf,len,pos,misc,1); if (!pos) return 0;
+ if ((misc[0] >= 33) && (misc[0] <= 126) && (misc[0] != '\\')) {
+ if (!stralloc_catb(out,misc,1)) return 0;
+@@ -111,7 +232,6 @@
+ if (!stralloc_catb(out,misc,4)) return 0;
+ }
+ }
+- }
+
+ if (!stralloc_cats(out,"\n")) return 0;
+ if (pos != newpos) { errno = error_proto; return 0; }
+diff -rNU3 djbdns-1.05.tds-base/printtype.c djbdns-1.05.tinydnssec/printtype.c
+--- djbdns-1.05.tds-base/printtype.c 1970-01-01 01:00:00.000000000 +0100
++++ djbdns-1.05.tinydnssec/printtype.c 2012-12-06 22:39:13.000000000 +0100
+@@ -0,0 +1,47 @@
++/* (C) 2012 Peter Conrad <conrad@quisquis.de>
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 3 as
++ * published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "byte.h"
++#include "dns.h"
++#include "uint16.h"
++#include "printtype.h"
++
++int printtype(stralloc *out, const char type[2]) {
++uint16 u16;
++
++ if (byte_equal(type,2,DNS_T_A)) return stralloc_cats(out,"A");
++ if (byte_equal(type,2,DNS_T_NS)) return stralloc_cats(out,"NS");
++ if (byte_equal(type,2,DNS_T_CNAME)) return stralloc_cats(out,"CNAME");
++ if (byte_equal(type,2,DNS_T_SOA)) return stralloc_cats(out,"SOA");
++ if (byte_equal(type,2,DNS_T_PTR)) return stralloc_cats(out,"PTR");
++ if (byte_equal(type,2,DNS_T_HINFO)) return stralloc_cats(out,"HINFO");
++ if (byte_equal(type,2,DNS_T_MX)) return stralloc_cats(out,"MX");
++ if (byte_equal(type,2,DNS_T_TXT)) return stralloc_cats(out,"TXT");
++ if (byte_equal(type,2,DNS_T_RP)) return stralloc_cats(out,"RP");
++ if (byte_equal(type,2,DNS_T_SIG)) return stralloc_cats(out,"SIG");
++ if (byte_equal(type,2,DNS_T_KEY)) return stralloc_cats(out,"KEY");
++ if (byte_equal(type,2,DNS_T_AAAA)) return stralloc_cats(out,"AAAA");
++ if (byte_equal(type,2,DNS_T_OPT)) return stralloc_cats(out,"OPT");
++ if (byte_equal(type,2,DNS_T_DS)) return stralloc_cats(out,"DS");
++ if (byte_equal(type,2,DNS_T_RRSIG)) return stralloc_cats(out,"RRSIG");
++ if (byte_equal(type,2,DNS_T_DNSKEY)) return stralloc_cats(out,"DNSKEY");
++ if (byte_equal(type,2,DNS_T_NSEC3)) return stralloc_cats(out,"NSEC3");
++ if (byte_equal(type,2,DNS_T_NSEC3PARAM)) return stralloc_cats(out,"NSEC3PARAM");
++ if (byte_equal(type,2,DNS_T_AXFR)) return stralloc_cats(out,"AXFR");
++ if (byte_equal(type,2,DNS_T_ANY)) return stralloc_cats(out,"*");
++
++ uint16_unpack_big(type,&u16);
++ return stralloc_catulong0(out,u16,0);
++}
+diff -rNU3 djbdns-1.05.tds-base/printtype.h djbdns-1.05.tinydnssec/printtype.h
+--- djbdns-1.05.tds-base/printtype.h 1970-01-01 01:00:00.000000000 +0100
++++ djbdns-1.05.tinydnssec/printtype.h 2012-12-06 22:39:13.000000000 +0100
+@@ -0,0 +1,23 @@
++/* (C) 2012 Peter Conrad <conrad@quisquis.de>
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 3 as
++ * published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef _PRINTTYPE_H
++#define _PRINTTYPE_H
++
++#include "stralloc.h"
++
++extern int printtype(stralloc *, const char *);
++
++#endif
+diff -rNU3 djbdns-1.05.tds-base/response.c djbdns-1.05.tinydnssec/response.c
+--- djbdns-1.05.tds-base/response.c 2001-02-11 22:11:45.000000000 +0100
++++ djbdns-1.05.tinydnssec/response.c 2012-12-06 22:39:13.000000000 +0100
+@@ -5,6 +5,8 @@
+
+ char response[65535];
+ unsigned int response_len = 0; /* <= 65535 */
++unsigned int max_response_len = 0; /* <= 65535 */
++unsigned int do_dnssec = 0;
+ static unsigned int tctarget;
+
+ #define NAMES 100
+diff -rNU3 djbdns-1.05.tds-base/response.h djbdns-1.05.tinydnssec/response.h
+--- djbdns-1.05.tds-base/response.h 2001-02-11 22:11:45.000000000 +0100
++++ djbdns-1.05.tinydnssec/response.h 2012-12-06 22:39:13.000000000 +0100
+@@ -5,6 +5,8 @@
+
+ extern char response[];
+ extern unsigned int response_len;
++extern unsigned int max_response_len;
++extern unsigned int do_dnssec;
+
+ extern int response_query(const char *,const char *,const char *);
+ extern void response_nxdomain(void);
+diff -rNU3 djbdns-1.05.tds-base/server.c djbdns-1.05.tinydnssec/server.c
+--- djbdns-1.05.tds-base/server.c 2012-12-06 22:45:38.000000000 +0100
++++ djbdns-1.05.tinydnssec/server.c 2012-12-06 22:39:13.000000000 +0100
+@@ -1,3 +1,4 @@
++#include "edns0.h"
+ #include "byte.h"
+ #include "case.h"
+ #include "env.h"
+@@ -63,6 +64,9 @@
+ if (header[2] & 126) goto NOTIMP;
+ if (byte_equal(qtype,2,DNS_T_AXFR)) goto NOTIMP;
+
++ pos = check_edns0(header, buf, len, pos);
++ if (!pos) goto NOQ;
++
+ case_lowerb(q,dns_domain_length(q));
+ if (!respond(q,qtype,ip)) {
+ qlog(ip,port,header,q,qtype," - ");
+@@ -168,7 +172,7 @@
+ len = socket_recv6(udp53[i],buf,sizeof buf,ip,&port,&ifid);
+ if (len < 0) continue;
+ if (!doit()) continue;
+- if (response_len > 512) response_tc();
++ if (response_len > max_response_len) response_tc();
+ socket_send6(udp53[i],response,response_len,ip,port,ifid);
+ /* may block for buffer space; if it fails, too bad */
+ }
+diff -rNU3 djbdns-1.05.tds-base/sha1.c djbdns-1.05.tinydnssec/sha1.c
+--- djbdns-1.05.tds-base/sha1.c 1970-01-01 01:00:00.000000000 +0100
++++ djbdns-1.05.tinydnssec/sha1.c 2012-12-06 22:39:13.000000000 +0100
+@@ -0,0 +1,385 @@
++/*
++SHA-1 in C
++By Steve Reid <sreid@sea-to-sky.net>
++100% Public Domain
++
++-----------------
++Modified 7/98
++By James H. Brown <jbrown@burgoyne.com>
++Still 100% Public Domain
++
++Corrected a problem which generated improper hash values on 16 bit machines
++Routine SHA1Update changed from
++ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
++len)
++to
++ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
++long len)
++
++The 'len' parameter was declared an int which works fine on 32 bit machines.
++However, on 16 bit machines an int is too small for the shifts being done
++against
++it. This caused the hash function to generate incorrect values if len was
++greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
++
++Since the file IO in main() reads 16K at a time, any file 8K or larger would
++be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million
++"a"s).
++
++I also changed the declaration of variables i & j in SHA1Update to
++unsigned long from unsigned int for the same reason.
++
++These changes should make no difference to any 32 bit implementations since
++an
++int and a long are the same size in those environments.
++
++--
++I also corrected a few compiler warnings generated by Borland C.
++1. Added #include <process.h> for exit() prototype
++2. Removed unused variable 'j' in SHA1Final
++3. Changed exit(0) to return(0) at end of main.
++
++ALL changes I made can be located by searching for comments containing 'JHB'
++-----------------
++Modified 8/98
++By Steve Reid <sreid@sea-to-sky.net>
++Still 100% public domain
++
++1- Removed #include <process.h> and used return() instead of exit()
++2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
++3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
++
++-----------------
++Modified 4/01
++By Saul Kravitz <Saul.Kravitz@celera.com>
++Still 100% PD
++Modified to run on Compaq Alpha hardware.
++
++-----------------
++Modified 07/2002
++By Ralph Giles <giles@ghostscript.com>
++Still 100% public domain
++modified for use with stdint types, autoconf
++code cleanup, removed attribution comments
++switched SHA1Final() argument order for consistency
++use SHA1_ prefix for public api
++move public api to sha1.h
++
++-----------------
++Modified 08/2012
++By Peter Conrad <conrad@quisquis.de>
++Still 100% public domain
++
++Taken from http://svn.ghostscript.com/jbig2dec/trunk/ for inclusion in tinydns
++Added/removed some includes
++Replaced WORDS_BIGENDIAN with BYTE_ORDER == BIG_ENDIAN check
++Test succeeds on x86_64
++*/
++
++/*
++Test Vectors (from FIPS PUB 180-1)
++"abc"
++ A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
++"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
++ 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
++A million repetitions of "a"
++ 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
++*/
++
++/* #define SHA1HANDSOFF */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <endian.h>
++#include <stdio.h>
++#include <string.h>
++
++#include "sha1.h"
++
++void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]);
++
++#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
++
++/* blk0() and blk() perform the initial expand. */
++/* I got the idea of expanding during the round function from SSLeay */
++/* FIXME: can we do this in an endian-proof way? */
++#if BYTE_ORDER == BIG_ENDIAN
++#define blk0(i) block->l[i]
++#else
++#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
++ |(rol(block->l[i],8)&0x00FF00FF))
++#endif
++#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
++ ^block->l[(i+2)&15]^block->l[i&15],1))
++
++/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
++#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
++#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
++#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
++#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
++#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
++
++
++#ifdef VERBOSE /* SAK */
++void SHAPrintContext(SHA1_CTX *context, char *msg){
++ printf("%s (%d,%d) %x %x %x %x %x\n",
++ msg,
++ context->count[0], context->count[1],
++ context->state[0],
++ context->state[1],
++ context->state[2],
++ context->state[3],
++ context->state[4]);
++}
++#endif /* VERBOSE */
++
++/* Hash a single 512-bit block. This is the core of the algorithm. */
++void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64])
++{
++ uint32_t a, b, c, d, e;
++ typedef union {
++ uint8_t c[64];
++ uint32_t l[16];
++ } CHAR64LONG16;
++ CHAR64LONG16* block;
++
++#ifdef SHA1HANDSOFF
++ static uint8_t workspace[64];
++ block = (CHAR64LONG16*)workspace;
++ memcpy(block, buffer, 64);
++#else
++ block = (CHAR64LONG16*)buffer;
++#endif
++
++ /* Copy context->state[] to working vars */
++ a = state[0];
++ b = state[1];
++ c = state[2];
++ d = state[3];
++ e = state[4];
++
++ /* 4 rounds of 20 operations each. Loop unrolled. */
++ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
++ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
++ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
++ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
++ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
++ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
++ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
++ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
++ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
++ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
++ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
++ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
++ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
++ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
++ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
++ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
++ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
++ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
++ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
++ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
++
++ /* Add the working vars back into context.state[] */
++ state[0] += a;
++ state[1] += b;
++ state[2] += c;
++ state[3] += d;
++ state[4] += e;
++
++ /* Wipe variables */
++ a = b = c = d = e = 0;
++}
++
++
++/* SHA1Init - Initialize new context */
++void SHA1_Init(SHA1_CTX* context)
++{
++ /* SHA1 initialization constants */
++ context->state[0] = 0x67452301;
++ context->state[1] = 0xEFCDAB89;
++ context->state[2] = 0x98BADCFE;
++ context->state[3] = 0x10325476;
++ context->state[4] = 0xC3D2E1F0;
++ context->count[0] = context->count[1] = 0;
++}
++
++
++/* Run your data through this. */
++void SHA1_Update(SHA1_CTX* context, const uint8_t* data, const size_t len)
++{
++ size_t i, j;
++
++#ifdef VERBOSE
++ SHAPrintContext(context, "before");
++#endif
++
++ j = (context->count[0] >> 3) & 63;
++ if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++;
++ context->count[1] += (len >> 29);
++ if ((j + len) > 63) {
++ memcpy(&context->buffer[j], data, (i = 64-j));
++ SHA1_Transform(context->state, context->buffer);
++ for ( ; i + 63 < len; i += 64) {
++ SHA1_Transform(context->state, data + i);
++ }
++ j = 0;
++ }
++ else i = 0;
++ memcpy(&context->buffer[j], &data[i], len - i);
++
++#ifdef VERBOSE
++ SHAPrintContext(context, "after ");
++#endif
++}
++
++
++/* Add padding and return the message digest. */
++void SHA1_Final(SHA1_CTX* context, uint8_t digest[SHA1_DIGEST_SIZE])
++{
++ uint32_t i;
++ uint8_t finalcount[8];
++
++ for (i = 0; i < 8; i++) {
++ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
++ >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
++ }
++ SHA1_Update(context, (uint8_t *)"\200", 1);
++ while ((context->count[0] & 504) != 448) {
++ SHA1_Update(context, (uint8_t *)"\0", 1);
++ }
++ SHA1_Update(context, finalcount, 8); /* Should cause a SHA1_Transform() */
++ for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
++ digest[i] = (uint8_t)
++ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
++ }
++
++ /* Wipe variables */
++ i = 0;
++ memset(context->buffer, 0, 64);
++ memset(context->state, 0, 20);
++ memset(context->count, 0, 8);
++ memset(finalcount, 0, 8); /* SWR */
++
++#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite its own static vars */
++ SHA1_Transform(context->state, context->buffer);
++#endif
++}
++
++/*************************************************************/
++
++#if 0
++int main(int argc, char** argv)
++{
++int i, j;
++SHA1_CTX context;
++unsigned char digest[SHA1_DIGEST_SIZE], buffer[16384];
++FILE* file;
++
++ if (argc > 2) {
++ puts("Public domain SHA-1 implementation - by Steve Reid <sreid@sea-to-sky.net>");
++ puts("Modified for 16 bit environments 7/98 - by James H. Brown <jbrown@burgoyne.com>"); /* JHB */
++ puts("Produces the SHA-1 hash of a file, or stdin if no file is specified.");
++ return(0);
++ }
++ if (argc < 2) {
++ file = stdin;
++ }
++ else {
++ if (!(file = fopen(argv[1], "rb"))) {
++ fputs("Unable to open file.", stderr);
++ return(-1);
++ }
++ }
++ SHA1_Init(&context);
++ while (!feof(file)) { /* note: what if ferror(file) */
++ i = fread(buffer, 1, 16384, file);
++ SHA1_Update(&context, buffer, i);
++ }
++ SHA1_Final(&context, digest);
++ fclose(file);
++ for (i = 0; i < SHA1_DIGEST_SIZE/4; i++) {
++ for (j = 0; j < 4; j++) {
++ printf("%02X", digest[i*4+j]);
++ }
++ putchar(' ');
++ }
++ putchar('\n');
++ return(0); /* JHB */
++}
++#endif
++
++/* self test */
++
++#ifdef TEST
++
++static char *test_data[] = {
++ "abc",
++ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
++ "A million repetitions of 'a'"};
++static char *test_results[] = {
++ "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D",
++ "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1",
++ "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F"};
++
++
++void digest_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE], char *output)
++{
++ int i,j;
++ char *c = output;
++
++ for (i = 0; i < SHA1_DIGEST_SIZE/4; i++) {
++ for (j = 0; j < 4; j++) {
++ sprintf(c,"%02X", digest[i*4+j]);
++ c += 2;
++ }
++ sprintf(c, " ");
++ c += 1;
++ }
++ *(c - 1) = '\0';
++}
++
++int main(int argc, char** argv)
++{
++ int k;
++ SHA1_CTX context;
++ uint8_t digest[20];
++ char output[80];
++
++ fprintf(stdout, "verifying SHA-1 implementation... ");
++
++ for (k = 0; k < 2; k++){
++ SHA1_Init(&context);
++ SHA1_Update(&context, (uint8_t*)test_data[k], strlen(test_data[k]));
++ SHA1_Final(&context, digest);
++ digest_to_hex(digest, output);
++
++ if (strcmp(output, test_results[k])) {
++ fprintf(stdout, "FAIL\n");
++ fprintf(stderr,"* hash of \"%s\" incorrect:\n", test_data[k]);
++ fprintf(stderr,"\t%s returned\n", output);
++ fprintf(stderr,"\t%s is correct\n", test_results[k]);
++ return (1);
++ }
++ }
++ /* million 'a' vector we feed separately */
++ SHA1_Init(&context);
++ for (k = 0; k < 1000000; k++)
++ SHA1_Update(&context, (uint8_t*)"a", 1);
++ SHA1_Final(&context, digest);
++ digest_to_hex(digest, output);
++ if (strcmp(output, test_results[2])) {
++ fprintf(stdout, "FAIL\n");
++ fprintf(stderr,"* hash of \"%s\" incorrect:\n", test_data[2]);
++ fprintf(stderr,"\t%s returned\n", output);
++ fprintf(stderr,"\t%s is correct\n", test_results[2]);
++ return (1);
++ }
++
++ /* success */
++ fprintf(stdout, "ok\n");
++ return(0);
++}
++#endif /* TEST */
+diff -rNU3 djbdns-1.05.tds-base/sha1.h djbdns-1.05.tinydnssec/sha1.h
+--- djbdns-1.05.tds-base/sha1.h 1970-01-01 01:00:00.000000000 +0100
++++ djbdns-1.05.tinydnssec/sha1.h 2012-12-06 22:39:13.000000000 +0100
+@@ -0,0 +1,29 @@
++/* public api for steve reid's public domain SHA-1 implementation */
++/* this file is in the public domain */
++
++#ifndef __SHA1_H
++#define __SHA1_H
++
++#include <stdint.h>
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++typedef struct {
++ uint32_t state[5];
++ uint32_t count[2];
++ uint8_t buffer[64];
++} SHA1_CTX;
++
++#define SHA1_DIGEST_SIZE 20
++
++void SHA1_Init(SHA1_CTX* context);
++void SHA1_Update(SHA1_CTX* context, const uint8_t* data, const size_t len);
++void SHA1_Final(SHA1_CTX* context, uint8_t digest[SHA1_DIGEST_SIZE]);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif /* __SHA1_H */
+diff -rNU3 djbdns-1.05.tds-base/TARGETS djbdns-1.05.tinydnssec/TARGETS
+--- djbdns-1.05.tds-base/TARGETS 2012-12-06 22:45:38.000000000 +0100
++++ djbdns-1.05.tinydnssec/TARGETS 2012-12-06 22:39:13.000000000 +0100
+@@ -240,3 +240,8 @@
+ socket_accept6.o
+ socket_connect6.o
+ socket_tcp6.o
++base32hex.o
++sha1.o
++base32hex.o
++printtype.o
++edns0.o
+diff -rNU3 djbdns-1.05.tds-base/tdlookup.c djbdns-1.05.tinydnssec/tdlookup.c
+--- djbdns-1.05.tds-base/tdlookup.c 2012-12-06 22:45:38.000000000 +0100
++++ djbdns-1.05.tinydnssec/tdlookup.c 2012-12-07 11:01:39.000000000 +0100
+@@ -10,6 +10,9 @@
+ #include "response.h"
+ #include "ip6.h"
+ #include "clientloc.h"
++#include "alloc.h"
++#include "sha1.h"
++#include "base32hex.h"
+
+ static int want(const char *owner,const char type[2])
+ {
+@@ -34,7 +37,7 @@
+ }
+
+ static char *d1;
+-
++static char *wantAddr;
+ static char clientloc[2];
+ static struct tai now;
+ static struct cdb c;
+@@ -44,10 +47,18 @@
+ static unsigned int dpos;
+ static char type[2];
+ static uint32 ttl;
++static char *nsec3;
++static char *cname = 0;
+
++/* returns -1 on failure,
++ * returns 0 on not found
++ * returns 1 when found
++ * returns 2 when flagwild is true and no wildcard match has been found but
++ * a direct match exists. This is for RFC-1034 section 4.3.3 compatibility.
++ */
+ static int find(char *d,int flagwild)
+ {
+- int r;
++ int r, direct=0;
+ char ch;
+ struct tai cutoff;
+ char ttd[8];
+@@ -57,7 +68,9 @@
+
+ for (;;) {
+ r = cdb_findnext(&c,d,dns_domain_length(d));
+- if (r <= 0) return r;
++ if (r < 0) return r; /* -1 */
++ if (r == 0) { return flagwild ? direct ? 2 : 0
++ : 0; }
+ dlen = cdb_datalen(&c);
+ if (dlen > sizeof data) return -1;
+ if (cdb_read(&c,data,dlen,cdb_datapos(&c)) == -1) return -1;
+@@ -68,6 +81,7 @@
+ dpos = dns_packet_copy(data,dlen,dpos,recordloc,2); if (!dpos) return -1;
+ if (byte_diff(recordloc,2,clientloc)) continue;
+ }
++ direct = direct || (ch != '*');
+ if (flagwild != (ch == '*')) continue;
+ dpos = dns_packet_copy(data,dlen,dpos,ttlstr,4); if (!dpos) return -1;
+ uint32_unpack_big(ttlstr,&ttl);
+@@ -105,6 +119,123 @@
+ return response_addname(d1);
+ }
+
++static int addNSEC3(char *hashName)
++{
++int r;
++
++ cdb_findstart(&c);
++ while (r = find(hashName,0)) {
++ if (r == -1) return 0;
++ if (byte_equal(type,2,DNS_T_NSEC3)) {
++ if (!response_rstart(hashName,DNS_T_NSEC3,ttl)) return 0;
++ if (!response_addbytes(data + dpos,dlen - dpos)) return 0;
++ response_rfinish(RESPONSE_AUTHORITY);
++ }
++ else if (do_dnssec && byte_equal(type,2,DNS_T_RRSIG) && dlen > dpos+18
++ && byte_equal(data+dpos,2,DNS_T_NSEC3)) {
++ if (!response_rstart(hashName,DNS_T_RRSIG,ttl)) return 0;
++ if (!dobytes(18)) return 0;
++ if (!doname()) return 0;
++ if (!response_addbytes(data + dpos,dlen - dpos)) return 0;
++ response_rfinish(RESPONSE_AUTHORITY);
++ }
++ }
++ return 1;
++}
++
++static int addNSEC3Cover(char *name, char *control, int wild)
++{
++SHA1_CTX ctx;
++int algo = 0, flags = 0, saltlen = 0, r;
++uint16 iterations = 0;
++char salt[255];
++uint8_t digest[SHA1_DIGEST_SIZE];
++
++ /* Search NSEC3PARAM to find hash parameters */
++ cdb_findstart(&c);
++ while (r = find(control,0)) {
++ if (r == -1) return 0;
++ if (byte_equal(type,2,DNS_T_NSEC3PARAM) && dlen - dpos > 5) {
++ algo = data[dpos];
++ flags = data[dpos+1];
++ uint16_unpack_big(data + dpos + 2, &iterations);
++ saltlen = data[dpos+4];
++ if (algo != 1 || flags || dlen - dpos - 5 < saltlen) {
++ algo = 0;
++ } else {
++ byte_copy(salt,saltlen, data + dpos + 5);
++ break;
++ }
++ }
++ }
++ if (algo != 1) return 0; /* not found or unsupported algorithm / flags */
++
++ /* Compute hash value */
++ case_lowerb(name,dns_domain_length(name));
++ SHA1_Init(&ctx);
++ if (wild) SHA1_Update(&ctx, "\1*", 2);
++ SHA1_Update(&ctx, name, dns_domain_length(name));
++ SHA1_Update(&ctx, salt, saltlen);
++ SHA1_Final(&ctx, digest);
++ while (iterations-- > 0) {
++ SHA1_Init(&ctx);
++ SHA1_Update(&ctx, digest, SHA1_DIGEST_SIZE);
++ SHA1_Update(&ctx, salt, saltlen);
++ SHA1_Final(&ctx, digest);
++ }
++
++ /* Find covering hash */
++ char nibble = ((digest[0] >> 4) & 0xf) + '0';
++ if (nibble > '9') { nibble += 'a' - '9' - 1; }
++ salt[0] = 1;
++ salt[1] = nibble;
++ byte_copy(salt+2, dns_domain_length(control), control);
++ cdb_findstart(&c);
++ while (r = find(salt,0)) {
++ if (r == -1) return 0;
++ if (byte_equal(type,2,DNS_T_HASHLIST) && dlen - dpos >= SHA1_DIGEST_SIZE) {
++ int hpos = dpos + SHA1_DIGEST_SIZE;
++ while (byte_diff(digest,SHA1_DIGEST_SIZE,data+hpos) > 0 && hpos < dlen) hpos += SHA1_DIGEST_SIZE;
++ hpos -= SHA1_DIGEST_SIZE;
++ *salt = base32hex(salt+1,data+hpos,SHA1_DIGEST_SIZE);
++ byte_copy(salt + *salt + 1, dns_domain_length(control), control);
++ break;
++ }
++ }
++ if (*salt == 1) return 0; /* not found */
++ return addNSEC3(salt);
++}
++
++static int addClosestEncloserProof(char *name, char *control, int includeWild)
++{
++char *q = name;
++char *hashName = 0;
++int r;
++
++ while (*q) {
++ cdb_findstart(&c);
++ while (r = find(q,0)) {
++ if (r == -1) return 0;
++ if (byte_equal(type,2,DNS_T_HASHREF) && dlen > dpos) {
++ if (!dns_packet_getname(data,dlen,dpos,&hashName)) return 0;
++ break;
++ }
++ }
++ if (hashName) {
++ int rc = addNSEC3(hashName);
++ alloc_free(hashName);
++ if (!rc) return 0;
++ hashName = 0;
++ break;
++ }
++ name = q;
++ q += *q + 1;
++ }
++ if (!*q) return 0;
++ if (includeWild && !addNSEC3Cover(q, control, 1)) return 0;
++ return addNSEC3Cover(name, control, 0);
++}
++
+ static int doit(char *q,char qtype[2])
+ {
+ unsigned int bpos;
+@@ -118,6 +249,8 @@
+ int r;
+ int flagns;
+ int flagauthoritative;
++ int flagsigned;
++ char *flagcname;
+ char x[20];
+ uint16 u16;
+ char addr[8][4];
+@@ -132,18 +265,28 @@
+ for (;;) {
+ flagns = 0;
+ flagauthoritative = 0;
++ flagsigned = 0;
+ cdb_findstart(&c);
+ while (r = find(control,0)) {
+ if (r == -1) return 0;
+ if (byte_equal(type,2,DNS_T_SOA)) flagauthoritative = 1;
+- if (byte_equal(type,2,DNS_T_NS)) flagns = 1;
++ else if (byte_equal(type,2,DNS_T_NS)) flagns = 1;
++ else if (byte_equal(type,2,DNS_T_DNSKEY)) flagsigned |= 1;
++ else if (byte_equal(type,2,DNS_T_RRSIG)) flagsigned |= 2;
++ else if (byte_equal(type,2,DNS_T_NSEC3PARAM)) flagsigned |= 4;
+ }
++ flagsigned = (flagsigned == 7);
+ if (flagns) break;
+- if (!*control) return 0; /* q is not within our bailiwick */
++ if (!*control) {
++ if (!cname) return 0; /* q is not within our bailiwick */
++ response[2] &= ~4; /* CNAME chain ends in external reference */
++ return 1;
++ }
+ control += *control;
+ control += 1;
+ }
+
++ wild = q;
+ if (!flagauthoritative) {
+ response[2] &= ~4;
+ goto AUTHORITY; /* q is in a child zone */
+@@ -152,7 +295,11 @@
+
+ flaggavesoa = 0;
+ flagfound = 0;
+- wild = q;
++ flagcname = 0;
++ if (nsec3) {
++ alloc_free(nsec3);
++ nsec3 = 0;
++ }
+
+ for (;;) {
+ addrnum = addr6num = 0;
+@@ -160,10 +307,29 @@
+ cdb_findstart(&c);
+ while (r = find(wild,wild != q)) {
+ if (r == -1) return 0;
++ if (r == 2) break;
+ flagfound = 1;
+ if (flaggavesoa && byte_equal(type,2,DNS_T_SOA)) continue;
+- if (byte_diff(type,2,qtype) && byte_diff(qtype,2,DNS_T_ANY) && byte_diff(type,2,DNS_T_CNAME)) continue;
+- if (byte_equal(type,2,DNS_T_A) && (dlen - dpos == 4)) {
++ if (do_dnssec && byte_equal(type,2,DNS_T_HASHREF) && dlen > dpos) {
++ if (!dns_packet_getname(data,dlen,dpos,&nsec3)) return 0;
++ }
++ if (byte_diff(type,2,qtype) && byte_diff(qtype,2,DNS_T_ANY) && byte_diff(type,2,DNS_T_CNAME)
++ && (!do_dnssec || byte_diff(type,2,DNS_T_RRSIG))) continue;
++ if (byte_equal(type,2,DNS_T_HASHREF) || byte_equal(type,2,DNS_T_HASHLIST)) continue;
++ if (do_dnssec && byte_equal(type,2,DNS_T_RRSIG) && dlen - dpos > 18) {
++ char sigtype[2];
++ struct tai valid;
++ uint32 validFrom, validUntil;
++ byte_copy(sigtype,2,data + dpos);
++ if (byte_diff(sigtype,2,qtype) && byte_diff(qtype,2,DNS_T_ANY) && byte_diff(sigtype,2,DNS_T_CNAME)) continue;
++ uint32_unpack_big(data + dpos + 12, &validFrom);
++ tai_unix(&valid, validFrom);
++ if (tai_less(&now, &valid)) continue;
++ uint32_unpack_big(data + dpos + 8, &validUntil);
++ tai_unix(&valid, validUntil);
++ if (tai_less(&valid, &now)) continue;
++ }
++ if (byte_equal(type,2,DNS_T_A) && (dlen - dpos == 4) && (!do_dnssec || addrnum < 8)) {
+ addrttl = ttl;
+ i = dns_random(addrnum + 1);
+ if (i < 8) {
+@@ -174,7 +340,7 @@
+ if (addrnum < 1000000) ++addrnum;
+ continue;
+ }
+- if (byte_equal(type,2,DNS_T_AAAA) && (dlen - dpos == 16)) {
++ if (byte_equal(type,2,DNS_T_AAAA) && (dlen - dpos == 16) && (!do_dnssec || addr6num < 8)) {
+ addr6ttl = ttl;
+ i = dns_random(addr6num + 1);
+ if (i < 8) {
+@@ -188,6 +354,9 @@
+ if (!response_rstart(q,type,ttl)) return 0;
+ if (byte_equal(type,2,DNS_T_NS) || byte_equal(type,2,DNS_T_CNAME) || byte_equal(type,2,DNS_T_PTR)) {
+ if (!doname()) return 0;
++ if (byte_equal(type,2,DNS_T_CNAME) && byte_diff(qtype,2,DNS_T_CNAME)) {
++ if (!dns_domain_copy(&flagcname,d1)) return 0;
++ }
+ }
+ else if (byte_equal(type,2,DNS_T_MX)) {
+ if (!dobytes(2)) return 0;
+@@ -199,10 +368,18 @@
+ if (!dobytes(20)) return 0;
+ flaggavesoa = 1;
+ }
++ else if (byte_equal(type,2,DNS_T_RRSIG) && dlen - dpos > 18) {
++ char sigtype[2];
++ byte_copy(sigtype,2,data + dpos);
++ if (!dobytes(18)) return 0;
++ if (!doname()) return 0;
++ if (!response_addbytes(data + dpos,dlen - dpos)) return 0;
++ }
+ else
+ if (!response_addbytes(data + dpos,dlen - dpos)) return 0;
+ response_rfinish(RESPONSE_ANSWER);
+ }
++ if (r == 2) break;
+ for (i = 0;i < addrnum;++i)
+ if (i < 8) {
+ if (!response_rstart(q,DNS_T_A,addrttl)) return 0;
+@@ -223,6 +400,16 @@
+ wild += 1;
+ }
+
++ if (flagcname) {
++ if (response[RESPONSE_ANSWER+1] >= 100) {
++ dns_domain_free(&flagcname); /* most likely a loop */
++ return 0;
++ }
++ if (cname) dns_domain_free(&cname);
++ cname = flagcname;
++ return doit(cname, qtype);
++ }
++
+ if (!flagfound)
+ response_nxdomain();
+
+@@ -230,22 +417,49 @@
+ AUTHORITY:
+ aupos = response_len;
+
+- if (flagauthoritative && (aupos == anpos)) {
+- cdb_findstart(&c);
+- while (r = find(control,0)) {
+- if (r == -1) return 0;
+- if (byte_equal(type,2,DNS_T_SOA)) {
+- if (!response_rstart(control,DNS_T_SOA,ttl)) return 0;
+- if (!doname()) return 0;
+- if (!doname()) return 0;
+- if (!dobytes(20)) return 0;
+- response_rfinish(RESPONSE_AUTHORITY);
+- break;
++ if (flagauthoritative && (aupos == anpos)) { /* NODATA or NXDOMAIN */
++ if (!flaggavesoa) {
++ cdb_findstart(&c);
++ while (r = find(control,0)) {
++ if (r == -1) return 0;
++ if (!flaggavesoa && byte_equal(type,2,DNS_T_SOA)) {
++ if (!response_rstart(control,DNS_T_SOA,ttl)) return 0;
++ if (!doname()) return 0;
++ if (!doname()) return 0;
++ if (!dobytes(20)) return 0;
++ response_rfinish(RESPONSE_AUTHORITY);
++ flaggavesoa = 1;
++ }
++ else if (do_dnssec && byte_equal(type,2,DNS_T_RRSIG) && dlen > dpos+18
++ && byte_equal(data+dpos,2,DNS_T_SOA)) {
++ if (!response_rstart(control,DNS_T_RRSIG,ttl)) return 0;
++ if (!dobytes(18)) return 0;
++ if (!doname()) return 0;
++ if (!response_addbytes(data + dpos,dlen - dpos)) return 0;
++ response_rfinish(RESPONSE_AUTHORITY);
++ }
++ }
++ }
++ if (do_dnssec && flagsigned) {
++ if (flagfound && nsec3) { /* NODATA */
++ if (!addNSEC3(nsec3)) return 0;
++ if (wild != q) { /* Wildcard NODATA */
++ if (!addClosestEncloserProof(q, control, 0)) return 0;
++ }
++ }
++ else { /* NXDOMAIN, or query for NSEC3 owner name */
++ if (!addClosestEncloserProof(q, control, 1)) return 0;
+ }
+ }
+ }
+- else
++ else {
++ if (do_dnssec && wild != q && flagsigned) { /* Wildcard answer */
++ char *nextCloser = q;
++ while (nextCloser + *nextCloser + 1 < wild) { nextCloser += *nextCloser + 1; }
++ if (!addNSEC3Cover(nextCloser, control, 0)) return 0;
++ }
+ if (want(control,DNS_T_NS)) {
++ int have_ds = 0;
+ cdb_findstart(&c);
+ while (r = find(control,0)) {
+ if (r == -1) return 0;
+@@ -254,10 +468,33 @@
+ if (!doname()) return 0;
+ response_rfinish(RESPONSE_AUTHORITY);
+ }
++ else if (do_dnssec && byte_equal(type,2,DNS_T_DS)) {
++ if (!response_rstart(control,DNS_T_DS,ttl)) return 0;
++ if (!response_addbytes(data + dpos,dlen - dpos)) return 0;
++ response_rfinish(RESPONSE_AUTHORITY);
++ have_ds = 1;
++ }
++ else if (do_dnssec && byte_equal(type,2,DNS_T_RRSIG) && dlen > dpos+18
++ && (byte_equal(data+dpos,2,DNS_T_NS)
++ || byte_equal(data+dpos,2,DNS_T_DS))) {
++ if (!response_rstart(control,DNS_T_RRSIG,ttl)) return 0;
++ if (!dobytes(18)) return 0;
++ if (!doname()) return 0;
++ if (!response_addbytes(data + dpos,dlen - dpos)) return 0;
++ response_rfinish(RESPONSE_AUTHORITY);
++ }
+ }
++ if (do_dnssec && !flagauthoritative && !have_ds) { addNSEC3(control); }
+ }
++ }
+
+ arpos = response_len;
++ if (do_dnssec) {
++ /* Add EDNS0 OPT RR */
++ if (!response_rstart("",DNS_T_OPT,1 << 15)) return 0;
++ uint16_pack_big(response+arpos+3, 512);
++ response_rfinish(RESPONSE_ADDITIONAL);
++ }
+
+ bpos = anpos;
+ while (bpos < arpos) {
+@@ -265,25 +502,33 @@
+ bpos = dns_packet_copy(response,arpos,bpos,x,10); if (!bpos) return 0;
+ if (byte_equal(x,2,DNS_T_NS) || byte_equal(x,2,DNS_T_MX)) {
+ if (byte_equal(x,2,DNS_T_NS)) {
+- if (!dns_packet_getname(response,arpos,bpos,&d1)) return 0;
++ if (!dns_packet_getname(response,arpos,bpos,&wantAddr)) return 0;
+ }
+ else
+- if (!dns_packet_getname(response,arpos,bpos + 2,&d1)) return 0;
+- case_lowerb(d1,dns_domain_length(d1));
+- if (want(d1,DNS_T_A)) {
++ if (!dns_packet_getname(response,arpos,bpos + 2,&wantAddr)) return 0;
++ case_lowerb(wantAddr,dns_domain_length(wantAddr));
++ if (want(wantAddr,DNS_T_A)) {
+ cdb_findstart(&c);
+- while (r = find(d1,0)) {
++ while (r = find(wantAddr,0)) {
+ if (r == -1) return 0;
+ if (byte_equal(type,2,DNS_T_A)) {
+- if (!response_rstart(d1,DNS_T_A,ttl)) return 0;
++ if (!response_rstart(wantAddr,DNS_T_A,ttl)) return 0;
+ if (!dobytes(4)) return 0;
+ response_rfinish(RESPONSE_ADDITIONAL);
+ }
+ else if (byte_equal(type,2,DNS_T_AAAA)) {
+- if (!response_rstart(d1,DNS_T_AAAA,ttl)) return 0;
++ if (!response_rstart(wantAddr,DNS_T_AAAA,ttl)) return 0;
+ if (!dobytes(16)) return 0;
+ response_rfinish(RESPONSE_ADDITIONAL);
+ }
++ else if (do_dnssec && byte_equal(type,2,DNS_T_RRSIG) && dlen > dpos+18
++ && (byte_equal(data+dpos,2,DNS_T_A) || byte_equal(data+dpos,2,DNS_T_AAAA))) {
++ if (!response_rstart(wantAddr,DNS_T_RRSIG,ttl)) return 0;
++ if (!dobytes(18)) return 0;
++ if (!doname()) return 0;
++ if (!response_addbytes(data + dpos,dlen - dpos)) return 0;
++ response_rfinish(RESPONSE_ADDITIONAL);
++ }
+ }
+ }
+ }
+@@ -291,10 +536,10 @@
+ bpos += u16;
+ }
+
+- if (flagauthoritative && (response_len > 512)) {
++ if (flagauthoritative && (response_len > max_response_len)) {
+ byte_zero(response + RESPONSE_ADDITIONAL,2);
+ response_len = arpos;
+- if (response_len > 512) {
++ if (!do_dnssec && response_len > max_response_len) {
+ byte_zero(response + RESPONSE_AUTHORITY,2);
+ response_len = aupos;
+ }
+@@ -316,6 +561,9 @@
+ cdb_init(&c,fd);
+
+ r = doit(q,qtype);
++ if (cname) {
++ dns_domain_free(&cname);
++ }
+
+ cdb_free(&c);
+ close(fd);
+diff -rNU3 djbdns-1.05.tds-base/tinydns-data.c djbdns-1.05.tinydnssec/tinydns-data.c
+--- djbdns-1.05.tds-base/tinydns-data.c 2012-12-06 22:45:38.000000000 +0100
++++ djbdns-1.05.tinydnssec/tinydns-data.c 2012-12-06 22:39:13.000000000 +0100
+@@ -436,7 +436,7 @@
+ i = 0;
+ while (i < f[1].len) {
+ k = f[1].len - i;
+- if (k > 127) k = 127;
++ if (k > 255) k = 255;
+ ch = k;
+ rr_add(&ch,1);
+ rr_add(f[1].s + i,k);
+diff -rNU3 djbdns-1.05.tds-base/tinydns-get.c djbdns-1.05.tinydnssec/tinydns-get.c
+--- djbdns-1.05.tds-base/tinydns-get.c 2001-02-11 22:11:45.000000000 +0100
++++ djbdns-1.05.tinydnssec/tinydns-get.c 2012-12-06 22:39:13.000000000 +0100
+@@ -19,7 +19,7 @@
+
+ void usage(void)
+ {
+- strerr_die1x(100,"tinydns-get: usage: tinydns-get type name [ip]");
++ strerr_die1x(100,"tinydns-get: usage: tinydns-get [-s | -S] type name [ip]");
+ }
+ void oops(void)
+ {
+@@ -39,6 +39,14 @@
+ if (!*argv) usage();
+
+ if (!*++argv) usage();
++
++ max_response_len = 512;
++ if ((*argv)[0] == '-') {
++ if ((*argv)[1] != 's' && (*argv)[1] != 'S' || (*argv)[2]) usage();
++ do_dnssec = 1;
++ max_response_len = (*argv)[1] == 's' ? 1220 : 4000;
++ if (!*++argv) usage();
++ }
+ if (!parsetype(*argv,type)) usage();
+
+ if (!*++argv) usage();
diff --git a/gpl-3.0.txt b/gpl-3.0.txt
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/gpl-3.0.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/run-tests.sh b/run-tests.sh
new file mode 100755
index 0000000..14d2c88
--- /dev/null
+++ b/run-tests.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+# (C) 2012 Peter Conrad <conrad@quisquis.de>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+./tinydns-sign.pl test/example.?sk <test/data >data
+./tinydns-data
+
+for i in test/q-*; do
+ id="${i#test/q}"
+ echo -n "$i ... "
+ read sec type name <"$i"
+ ./tinydns-get "$sec" "$type" $name | tail +2 >test/"o$id"
+ sed -s 's/\b[0-9]\{10\}\b/<TIME>/g;/00 RRSIG /s/[^ ]*$/<SIG>/;s/^[0-9]\{1,\}/<SIZE>/' <test/"o$id" >test/"t$id"
+ if diff "test/t$id" "test/a$id" >/dev/null; then
+ echo "OK"
+ else
+ echo "FAIL: tinydns-get"
+ fi
+done
+
+function dig2tiny {
+ awk 'BEGIN { sect = 0; }
+ /^;; QUESTION SECTION/ { sect = 1; }
+ /^;; ANSWER SECTION/ { sect = 2; }
+ /^;; AUTHORITY SECTION/ { sect = 3; }
+ /^;; ADDITIONAL SECTION/ { sect = 4; }
+ /^;; OPT PSEUDOSECTION/ { sect = 5; }
+ /^;; ->>HEADER<<./ { sect = 6; }
+ { if ($0 !~ /^;;/ && $0 !~ /^$/) {
+ if (sect == 1) { print "query: " $3 " " substr($1, 2) }
+ else if (sect == 2) { print "answer: " $0 }
+ else if (sect == 3) { print "authority: " $0 }
+ else if (sect == 4) { print "additional: " $0 }
+ else if (sect == 5) { print "additional: . 0 OPT " $8 " " (0 + $4) " 0 " ($6 == "do;" ? "8000" : "0") }
+ } else if (sect == 6) {
+ if ($0 ~ /^;; ...HEADER/ && $5 == "status:") { status = substr($6, 0, length($6) -1); }
+ else if ($0 ~ /^;; flags: /) {
+ if ($0 ~ /^;; flags: [ a-z]*aa/) { auth = "authoritative, " }
+ else { auth = "" }
+ match($0, /QUERY: ([[:digit:]]+), ANSWER: ([[:digit:]]+), AUTHORITY: ([[:digit:]]+), ADDITIONAL: ([[:digit:]]+)/, rrs);
+ print "<SIZE> bytes, " rrs[1] "+" rrs[2] "+" rrs[3] "+" rrs[4] " records, response, " auth status;
+ }
+ }
+ }' <"$1" | \
+ sed 's=[ ]\{1,\}= =g;s=\(example\|xx\)\. =\1 =g;s=\(example\|xx\)\.$=\1=;s= IN = =g;s=\([0-9a-zA-Z+/]\{40,\}\) =\1=g' >"$2"
+}
+
+if [ "$1" = "-t" ]; then
+ shift
+ for i in test/q-*; do
+ id="${i#test/q}"
+ echo -n "$i (tcp) ... "
+ read sec type name <"$i"
+ if [ "$sec" = "-s" ]; then
+ sec="+dnssec +bufsize=1220"
+ elif [ "$sec" = "-S" ]; then
+ sec="+dnssec +bufsize=2000"
+ fi
+ dig +norecurse +tcp $sec "$type" $name @$SERVER >"test/ot$id"
+ dig2tiny "test/ot$id" "test/ttt$id"
+ sed -s 's/\b[0-9]\{10\}\b/<TIME>/g;s/\b[0-9]\{14\}\b/<TIME>/g;/00 RRSIG /s/[^ ]*$/<SIG>/' <test/"ttt$id" | \
+ sort >test/"tt$id"
+ if sort <"test/a$id" | diff -i - "test/tt$id" >/dev/null; then
+ echo "OK"
+ else
+ echo "FAIL: dig +tcp"
+ fi
+ done
+fi
+
+if [ "$1" = "-u" ]; then
+ shift
+ for i in test/q-*; do
+ id="${i#test/q}"
+ echo -n "$i (udp) ... "
+ read sec type name <"$i"
+ if [ "$sec" = "-s" ]; then
+ sec="+dnssec +bufsize=1220"
+ elif [ "$sec" = "-S" ]; then
+ sec="+dnssec +bufsize=2000"
+ fi
+ dig +norecurse +notcp $sec "$type" $name @$SERVER >"test/ou$id"
+ dig2tiny "test/ou$id" "test/tut$id"
+ sed -s 's/\b[0-9]\{10\}\b/<TIME>/g;s/\b[0-9]\{14\}\b/<TIME>/g;/00 RRSIG /s/[^ ]*$/<SIG>/' <test/"tut$id" | \
+ sort >test/"tu$id"
+ if sort <"test/a$id" | diff -i - "test/tu$id" >/dev/null; then
+ echo "OK"
+ else
+ echo "FAIL: dig +notcp"
+ fi
+ done
+fi
diff --git a/test/a-01 b/test/a-01
new file mode 100644
index 0000000..edbae15
--- /dev/null
+++ b/test/a-01
@@ -0,0 +1,16 @@
+<SIZE> bytes, 1+2+3+9 records, response, authoritative, noerror
+query: MX x.w.example
+answer: x.w.example 3600 MX 1 xx.example
+answer: x.w.example 3600 RRSIG MX 7 3 3600 <TIME> <TIME> 44495 example <SIG>
+authority: example 3600 NS ns1.example
+authority: example 3600 NS ns2.example
+authority: example 3600 RRSIG NS 7 1 3600 <TIME> <TIME> 44495 example <SIG>
+additional: . 0 OPT 512 0 0 8000
+additional: xx.example 3600 A 192.0.2.10
+additional: xx.example 3600 AAAA 2001:db8::f00:baaa
+additional: xx.example 3600 RRSIG A 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: xx.example 3600 RRSIG AAAA 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: ns1.example 3600 A 192.0.2.1
+additional: ns1.example 3600 RRSIG A 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: ns2.example 3600 A 192.0.2.2
+additional: ns2.example 3600 RRSIG A 7 2 3600 <TIME> <TIME> 44495 example <SIG>
diff --git a/test/a-02 b/test/a-02
new file mode 100644
index 0000000..ec13d5d
--- /dev/null
+++ b/test/a-02
@@ -0,0 +1,9 @@
+<SIZE> bytes, 1+0+4+3 records, response, noerror
+query: MX mc.a.example
+authority: a.example 3600 NS ns1.a.example
+authority: a.example 3600 NS ns2.a.example
+authority: a.example 3600 DS 58470 5 1 3079F1593EBAD6DC121E202A8B766A6A4837206C
+authority: a.example 3600 RRSIG DS 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: . 0 OPT 512 0 0 8000
+additional: ns1.a.example 3600 A 192.0.2.5
+additional: ns2.a.example 3600 A 192.0.2.6
diff --git a/test/a-03 b/test/a-03
new file mode 100644
index 0000000..6b543dc
--- /dev/null
+++ b/test/a-03
@@ -0,0 +1,11 @@
+<SIZE> bytes, 1+0+8+1 records, response, authoritative, nxdomain
+query: A a.c.x.w.example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
+authority: example 3600 RRSIG SOA 7 1 3600 <TIME> <TIME> 44495 example <SIG>
+authority: b4um86eghhds6nea196smvmlo4ors995.example 3600 NSEC3 1 0 12 AABBCCDD gjeqe526plbf1g8mklp59enfd789njgi MX RRSIG
+authority: b4um86eghhds6nea196smvmlo4ors995.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+authority: 4g6p9u5gvfshp30pqecj98b3maqbn1ck.example 3600 NSEC3 1 0 12 AABBCCDD b4um86eghhds6nea196smvmlo4ors995 NS
+authority: 4g6p9u5gvfshp30pqecj98b3maqbn1ck.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+authority: 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example 3600 NSEC3 1 0 12 AABBCCDD 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
+authority: 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: . 0 OPT 512 0 0 8000
diff --git a/test/a-04 b/test/a-04
new file mode 100644
index 0000000..b23c1ed
--- /dev/null
+++ b/test/a-04
@@ -0,0 +1,7 @@
+<SIZE> bytes, 1+0+4+1 records, response, authoritative, noerror
+query: MX ns1.example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
+authority: example 3600 RRSIG SOA 7 1 3600 <TIME> <TIME> 44495 example <SIG>
+authority: 2t7b4g4vsa5smi47k61mv5bv1a22bojr.example 3600 NSEC3 1 0 12 AABBCCDD 2vptu5timamqttgl4luu9kg21e0aor3s A RRSIG
+authority: 2t7b4g4vsa5smi47k61mv5bv1a22bojr.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: . 0 OPT 512 0 0 8000
diff --git a/test/a-05 b/test/a-05
new file mode 100644
index 0000000..39dce85
--- /dev/null
+++ b/test/a-05
@@ -0,0 +1,7 @@
+<SIZE> bytes, 1+0+4+1 records, response, authoritative, noerror
+query: A y.w.example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
+authority: example 3600 RRSIG SOA 7 1 3600 <TIME> <TIME> 44495 example <SIG>
+authority: ji6neoaepv8b5o6k4ev33abha8ht9fgc.example 3600 NSEC3 1 0 12 AABBCCDD k8udemvp1j2f7eg6jebps17vp3n8i58h
+authority: ji6neoaepv8b5o6k4ev33abha8ht9fgc.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: . 0 OPT 512 0 0 8000
diff --git a/test/a-06 b/test/a-06
new file mode 100644
index 0000000..ea65ded
--- /dev/null
+++ b/test/a-06
@@ -0,0 +1,9 @@
+<SIZE> bytes, 1+2+5+0 records, response, authoritative, noerror
+query: MX a.z.w.example
+answer: a.z.w.example 3600 MX 1 ai.example
+answer: a.z.w.example 3600 RRSIG MX 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+authority: q04jkcevqvmu85r014c7dkba38o0ji5r.example 3600 NSEC3 1 0 12 AABBCCDD r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+authority: q04jkcevqvmu85r014c7dkba38o0ji5r.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+authority: example 3600 NS ns1.example
+authority: example 3600 NS ns2.example
+authority: example 3600 RRSIG NS 7 1 3600 <TIME> <TIME> 44495 example <SIG>
diff --git a/test/a-07 b/test/a-07
new file mode 100644
index 0000000..3d2e512
--- /dev/null
+++ b/test/a-07
@@ -0,0 +1,18 @@
+<SIZE> bytes, 1+2+5+9 records, response, authoritative, noerror
+query: MX a.z.w.example
+answer: a.z.w.example 3600 MX 1 ai.example
+answer: a.z.w.example 3600 RRSIG MX 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+authority: q04jkcevqvmu85r014c7dkba38o0ji5r.example 3600 NSEC3 1 0 12 AABBCCDD r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+authority: q04jkcevqvmu85r014c7dkba38o0ji5r.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+authority: example 3600 NS ns1.example
+authority: example 3600 NS ns2.example
+authority: example 3600 RRSIG NS 7 1 3600 <TIME> <TIME> 44495 example <SIG>
+additional: . 0 OPT 512 0 0 8000
+additional: ai.example 3600 A 192.0.2.9
+additional: ai.example 3600 AAAA 2001:db8::f00:baa9
+additional: ai.example 3600 RRSIG A 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: ai.example 3600 RRSIG AAAA 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: ns1.example 3600 A 192.0.2.1
+additional: ns1.example 3600 RRSIG A 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: ns2.example 3600 A 192.0.2.2
+additional: ns2.example 3600 RRSIG A 7 2 3600 <TIME> <TIME> 44495 example <SIG>
diff --git a/test/a-08 b/test/a-08
new file mode 100644
index 0000000..2b64468
--- /dev/null
+++ b/test/a-08
@@ -0,0 +1,11 @@
+<SIZE> bytes, 1+0+8+1 records, response, authoritative, noerror
+query: AAAA a.z.w.example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
+authority: example 3600 RRSIG SOA 7 1 3600 <TIME> <TIME> 44495 example <SIG>
+authority: r53bq7cc2uvmubfu5ocmm6pers9tk9en.example 3600 NSEC3 1 0 12 AABBCCDD t644ebqk9bibcna874givr6joj62mlhv MX RRSIG
+authority: r53bq7cc2uvmubfu5ocmm6pers9tk9en.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+authority: k8udemvp1j2f7eg6jebps17vp3n8i58h.example 3600 NSEC3 1 0 12 AABBCCDD kohar7mbb8dc2ce8a9qvl8hon4k53uhi
+authority: k8udemvp1j2f7eg6jebps17vp3n8i58h.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+authority: q04jkcevqvmu85r014c7dkba38o0ji5r.example 3600 NSEC3 1 0 12 AABBCCDD r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+authority: q04jkcevqvmu85r014c7dkba38o0ji5r.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: . 0 OPT 512 0 0 8000
diff --git a/test/a-09 b/test/a-09
new file mode 100644
index 0000000..1cca425
--- /dev/null
+++ b/test/a-09
@@ -0,0 +1,7 @@
+<SIZE> bytes, 1+0+4+1 records, response, authoritative, noerror
+query: DS example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
+authority: example 3600 RRSIG SOA 7 1 3600 <TIME> <TIME> 44495 example <SIG>
+authority: 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example 3600 NSEC3 1 0 12 AABBCCDD 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
+authority: 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example 3600 RRSIG NSEC3 7 2 3600 <TIME> <TIME> 44495 example <SIG>
+additional: . 0 OPT 512 0 0 8000
diff --git a/test/a-10 b/test/a-10
new file mode 100644
index 0000000..8388037
--- /dev/null
+++ b/test/a-10
@@ -0,0 +1,9 @@
+<SIZE> bytes, 1+1+2+4 records, response, authoritative, noerror
+query: MX x.w.example
+answer: x.w.example 3600 MX 1 xx.example
+authority: example 3600 NS ns1.example
+authority: example 3600 NS ns2.example
+additional: xx.example 3600 A 192.0.2.10
+additional: xx.example 3600 AAAA 2001:db8::f00:baaa
+additional: ns1.example 3600 A 192.0.2.1
+additional: ns2.example 3600 A 192.0.2.2
diff --git a/test/a-11 b/test/a-11
new file mode 100644
index 0000000..a374184
--- /dev/null
+++ b/test/a-11
@@ -0,0 +1,6 @@
+<SIZE> bytes, 1+0+2+2 records, response, noerror
+query: MX mc.a.example
+authority: a.example 3600 NS ns1.a.example
+authority: a.example 3600 NS ns2.a.example
+additional: ns1.a.example 3600 A 192.0.2.5
+additional: ns2.a.example 3600 A 192.0.2.6
diff --git a/test/a-12 b/test/a-12
new file mode 100644
index 0000000..fd9f2e2
--- /dev/null
+++ b/test/a-12
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+0+1+0 records, response, authoritative, nxdomain
+query: A a.c.x.w.example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
diff --git a/test/a-13 b/test/a-13
new file mode 100644
index 0000000..8b8cd29
--- /dev/null
+++ b/test/a-13
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+0+1+0 records, response, authoritative, noerror
+query: MX ns1.example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
diff --git a/test/a-14 b/test/a-14
new file mode 100644
index 0000000..b08e678
--- /dev/null
+++ b/test/a-14
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+0+1+0 records, response, authoritative, noerror
+query: A y.w.example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
diff --git a/test/a-15 b/test/a-15
new file mode 100644
index 0000000..81c4011
--- /dev/null
+++ b/test/a-15
@@ -0,0 +1,9 @@
+<SIZE> bytes, 1+1+2+4 records, response, authoritative, noerror
+query: MX a.z.w.example
+answer: a.z.w.example 3600 MX 1 ai.example
+authority: example 3600 NS ns1.example
+authority: example 3600 NS ns2.example
+additional: ai.example 3600 A 192.0.2.9
+additional: ai.example 3600 AAAA 2001:db8::f00:baa9
+additional: ns1.example 3600 A 192.0.2.1
+additional: ns2.example 3600 A 192.0.2.2
diff --git a/test/a-16 b/test/a-16
new file mode 100644
index 0000000..563b112
--- /dev/null
+++ b/test/a-16
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+0+1+0 records, response, authoritative, noerror
+query: AAAA a.z.w.example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
diff --git a/test/a-17 b/test/a-17
new file mode 100644
index 0000000..f9dd3c4
--- /dev/null
+++ b/test/a-17
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+0+1+0 records, response, authoritative, noerror
+query: DS example
+authority: example 3600 SOA ns1.example bugs.x.w.example <TIME> 3600 300 3600000 3600
diff --git a/test/a-18 b/test/a-18
new file mode 100644
index 0000000..16f37ad
--- /dev/null
+++ b/test/a-18
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+1+0+0 records, response, noerror
+query: MX an.example.xx
+answer: an.example.xx 86400 CNAME tripple.xx
diff --git a/test/a-19 b/test/a-19
new file mode 100644
index 0000000..4bcb58e
--- /dev/null
+++ b/test/a-19
@@ -0,0 +1,4 @@
+<SIZE> bytes, 1+2+0+0 records, response, noerror
+query: MX another.example.xx
+answer: another.example.xx 86400 CNAME an.example.xx
+answer: an.example.xx 86400 CNAME tripple.xx
diff --git a/test/a-20 b/test/a-20
new file mode 100644
index 0000000..9f51657
--- /dev/null
+++ b/test/a-20
@@ -0,0 +1,4 @@
+<SIZE> bytes, 1+1+1+0 records, response, authoritative, noerror
+query: MX simple.example.xx
+answer: simple.example.xx 86400 CNAME ns1.example.xx
+authority: example.xx 2500 SOA ns1.example.xx bugs.x.w.example.xx <TIME> 3600 300 3600000 3600
diff --git a/test/a-21 b/test/a-21
new file mode 100644
index 0000000..704ffed
--- /dev/null
+++ b/test/a-21
@@ -0,0 +1,7 @@
+<SIZE> bytes, 1+1+2+2 records, response, noerror
+query: MX sub.example.xx
+answer: sub.example.xx 86400 CNAME ns1.a.example.xx
+authority: a.example.xx 259200 NS ns1.a.example.xx
+authority: a.example.xx 259200 NS ns2.a.example.xx
+additional: ns1.a.example.xx 259200 A 192.0.2.5
+additional: ns2.a.example.xx 259200 A 192.0.2.6
diff --git a/test/a-22 b/test/a-22
new file mode 100644
index 0000000..8af935b
--- /dev/null
+++ b/test/a-22
@@ -0,0 +1,7 @@
+<SIZE> bytes, 1+2+2+1 records, response, authoritative, noerror
+query: A simple.example.xx
+answer: simple.example.xx 86400 CNAME ns1.example.xx
+answer: ns1.example.xx 259200 A 192.0.2.1
+authority: example.xx 259200 NS ns1.example.xx
+authority: example.xx 259200 NS ns2.example.xx
+additional: ns2.example.xx 259200 A 192.0.2.2
diff --git a/test/a-23 b/test/a-23
new file mode 100644
index 0000000..3d73b24
--- /dev/null
+++ b/test/a-23
@@ -0,0 +1,4 @@
+<SIZE> bytes, 1+2+0+0 records, response, noerror
+query: MX an.example.xx
+answer: an.example.xx 86400 CNAME tripple.xx
+answer: an.example.xx 86400 RRSIG CNAME 7 3 86400 <TIME> <TIME> 44495 example.xx <SIG>
diff --git a/test/a-24 b/test/a-24
new file mode 100644
index 0000000..d944f1a
--- /dev/null
+++ b/test/a-24
@@ -0,0 +1,6 @@
+<SIZE> bytes, 1+4+0+0 records, response, noerror
+query: MX another.example.xx
+answer: another.example.xx 86400 CNAME an.example.xx
+answer: another.example.xx 86400 RRSIG CNAME 7 3 86400 <TIME> <TIME> 44495 example.xx <SIG>
+answer: an.example.xx 86400 CNAME tripple.xx
+answer: an.example.xx 86400 RRSIG CNAME 7 3 86400 <TIME> <TIME> 44495 example.xx <SIG>
diff --git a/test/a-25 b/test/a-25
new file mode 100644
index 0000000..e13fdb8
--- /dev/null
+++ b/test/a-25
@@ -0,0 +1,9 @@
+<SIZE> bytes, 1+2+4+1 records, response, authoritative, noerror
+query: MX simple.example.xx
+answer: simple.example.xx 86400 CNAME ns1.example.xx
+answer: simple.example.xx 86400 RRSIG CNAME 7 3 86400 <TIME> <TIME> 44495 example.xx <SIG>
+authority: example.xx 2500 SOA ns1.example.xx bugs.x.w.example.xx <TIME> 3600 300 3600000 3600
+authority: example.xx 2500 RRSIG SOA 7 2 2500 <TIME> <TIME> 44495 example.xx <SIG>
+authority: tmd276tdvbbhrhslmaahlbtn3jvn0pru.example.xx 86400 NSEC3 1 0 12 AABBCCDD ubt957071tgo6dp02qb9j6dp2go2243q A RRSIG
+authority: tmd276tdvbbhrhslmaahlbtn3jvn0pru.example.xx 86400 RRSIG NSEC3 7 3 86400 <TIME> <TIME> 44495 example.xx <SIG>
+additional: . 0 OPT 512 0 0 8000
diff --git a/test/a-26 b/test/a-26
new file mode 100644
index 0000000..ebc2c64
--- /dev/null
+++ b/test/a-26
@@ -0,0 +1,9 @@
+<SIZE> bytes, 1+2+2+3 records, response, noerror
+query: MX sub.example.xx
+answer: sub.example.xx 86400 CNAME ns1.a.example.xx
+answer: sub.example.xx 86400 RRSIG CNAME 7 3 86400 <TIME> <TIME> 44495 example.xx <SIG>
+authority: a.example.xx 259200 NS ns1.a.example.xx
+authority: a.example.xx 259200 NS ns2.a.example.xx
+additional: . 0 OPT 512 0 0 8000
+additional: ns1.a.example.xx 259200 A 192.0.2.5
+additional: ns2.a.example.xx 259200 A 192.0.2.6
diff --git a/test/a-27 b/test/a-27
new file mode 100644
index 0000000..a204012
--- /dev/null
+++ b/test/a-27
@@ -0,0 +1,12 @@
+<SIZE> bytes, 1+4+3+3 records, response, authoritative, noerror
+query: A simple.example.xx
+answer: simple.example.xx 86400 CNAME ns1.example.xx
+answer: simple.example.xx 86400 RRSIG CNAME 7 3 86400 <TIME> <TIME> 44495 example.xx <SIG>
+answer: ns1.example.xx 259200 RRSIG A 7 3 259200 <TIME> <TIME> 44495 example.xx <SIG>
+answer: ns1.example.xx 259200 A 192.0.2.1
+authority: example.xx 259200 NS ns1.example.xx
+authority: example.xx 259200 NS ns2.example.xx
+authority: example.xx 259200 RRSIG NS 7 2 259200 <TIME> <TIME> 44495 example.xx <SIG>
+additional: . 0 OPT 512 0 0 8000
+additional: ns2.example.xx 259200 A 192.0.2.2
+additional: ns2.example.xx 259200 RRSIG A 7 3 259200 <TIME> <TIME> 44495 example.xx <SIG>
diff --git a/test/a-28 b/test/a-28
new file mode 100644
index 0000000..ea2d44b
--- /dev/null
+++ b/test/a-28
@@ -0,0 +1,7 @@
+<SIZE> bytes, 1+1+2+2 records, response, authoritative, noerror
+query: CNAME an.example.xx
+answer: an.example.xx 86400 CNAME tripple.xx
+authority: example.xx 259200 NS ns1.example.xx
+authority: example.xx 259200 NS ns2.example.xx
+additional: ns1.example.xx 259200 A 192.0.2.1
+additional: ns2.example.xx 259200 A 192.0.2.2
diff --git a/test/a-29 b/test/a-29
new file mode 100644
index 0000000..04b11b8
--- /dev/null
+++ b/test/a-29
@@ -0,0 +1,12 @@
+<SIZE> bytes, 1+2+3+5 records, response, authoritative, noerror
+query: CNAME an.example.xx
+answer: an.example.xx 86400 CNAME tripple.xx
+answer: an.example.xx 86400 RRSIG CNAME 7 3 86400 <TIME> <TIME> 44495 example.xx <SIG>
+authority: example.xx 259200 NS ns1.example.xx
+authority: example.xx 259200 NS ns2.example.xx
+authority: example.xx 259200 RRSIG NS 7 2 259200 <TIME> <TIME> 44495 example.xx <SIG>
+additional: . 0 OPT 512 0 0 8000
+additional: ns1.example.xx 259200 A 192.0.2.1
+additional: ns1.example.xx 259200 RRSIG A 7 3 259200 <TIME> <TIME> 44495 example.xx <SIG>
+additional: ns2.example.xx 259200 A 192.0.2.2
+additional: ns2.example.xx 259200 RRSIG A 7 3 259200 <TIME> <TIME> 44495 example.xx <SIG>
diff --git a/test/a-30 b/test/a-30
new file mode 100644
index 0000000..a98f708
--- /dev/null
+++ b/test/a-30
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+0+1+0 records, response, authoritative, noerror
+query: AAAA a.ns.unsigned.xx
+authority: unsigned.xx 2560 SOA a.ns.unsigned.xx hostmaster.unsigned.xx <TIME> 16384 2048 1048576 2560
diff --git a/test/a-31 b/test/a-31
new file mode 100644
index 0000000..eb8f9d7
--- /dev/null
+++ b/test/a-31
@@ -0,0 +1,4 @@
+<SIZE> bytes, 1+0+1+1 records, response, authoritative, noerror
+query: AAAA a.ns.unsigned.xx
+authority: unsigned.xx 2560 SOA a.ns.unsigned.xx hostmaster.unsigned.xx <TIME> 16384 2048 1048576 2560
+additional: . 0 OPT 512 0 0 8000
diff --git a/test/a-32 b/test/a-32
new file mode 100644
index 0000000..614da90
--- /dev/null
+++ b/test/a-32
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+0+1+0 records, response, authoritative, nxdomain
+query: A nx.unsigned.xx
+authority: unsigned.xx 2560 SOA a.ns.unsigned.xx hostmaster.unsigned.xx <TIME> 16384 2048 1048576 2560
diff --git a/test/a-33 b/test/a-33
new file mode 100644
index 0000000..f131292
--- /dev/null
+++ b/test/a-33
@@ -0,0 +1,4 @@
+<SIZE> bytes, 1+0+1+1 records, response, authoritative, nxdomain
+query: A nx.unsigned.xx
+authority: unsigned.xx 2560 SOA a.ns.unsigned.xx hostmaster.unsigned.xx <TIME> 16384 2048 1048576 2560
+additional: . 0 OPT 512 0 0 8000
diff --git a/test/a-34 b/test/a-34
new file mode 100644
index 0000000..299ea04
--- /dev/null
+++ b/test/a-34
@@ -0,0 +1,9 @@
+<SIZE> bytes, 1+1+2+4 records, response, authoritative, noerror
+query: MX wc.w.unsigned.xx
+answer: wc.w.unsigned.xx 3600 MX 1 aaaa.unsigned.xx
+authority: unsigned.xx 259200 NS a.ns.unsigned.xx
+authority: unsigned.xx 259200 NS b.ns.unsigned.xx
+additional: aaaa.unsigned.xx 86400 A 10.11.12.13
+additional: aaaa.unsigned.xx 86400 AAAA 2002:a0b:c0d::1
+additional: a.ns.unsigned.xx 259200 A 10.10.10.10
+additional: b.ns.unsigned.xx 259200 A 10.11.11.11
diff --git a/test/a-35 b/test/a-35
new file mode 100644
index 0000000..de21d45
--- /dev/null
+++ b/test/a-35
@@ -0,0 +1,10 @@
+<SIZE> bytes, 1+1+2+5 records, response, authoritative, noerror
+query: MX wc.w.unsigned.xx
+answer: wc.w.unsigned.xx 3600 MX 1 aaaa.unsigned.xx
+authority: unsigned.xx 259200 NS a.ns.unsigned.xx
+authority: unsigned.xx 259200 NS b.ns.unsigned.xx
+additional: . 0 OPT 512 0 0 8000
+additional: aaaa.unsigned.xx 86400 A 10.11.12.13
+additional: aaaa.unsigned.xx 86400 AAAA 2002:a0b:c0d::1
+additional: a.ns.unsigned.xx 259200 A 10.10.10.10
+additional: b.ns.unsigned.xx 259200 A 10.11.11.11
diff --git a/test/a-36 b/test/a-36
new file mode 100644
index 0000000..f77cb70
--- /dev/null
+++ b/test/a-36
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+1+0+0 records, response, noerror
+query: MX an.unsigned.xx
+answer: an.unsigned.xx 86400 CNAME tripple.xx
diff --git a/test/a-37 b/test/a-37
new file mode 100644
index 0000000..f77cb70
--- /dev/null
+++ b/test/a-37
@@ -0,0 +1,3 @@
+<SIZE> bytes, 1+1+0+0 records, response, noerror
+query: MX an.unsigned.xx
+answer: an.unsigned.xx 86400 CNAME tripple.xx
diff --git a/test/a-38 b/test/a-38
new file mode 100644
index 0000000..ffb7d58
--- /dev/null
+++ b/test/a-38
@@ -0,0 +1,4 @@
+<SIZE> bytes, 1+2+0+0 records, response, noerror
+query: MX another.unsigned.xx
+answer: another.unsigned.xx 86400 CNAME an.example.xx
+answer: an.example.xx 86400 CNAME tripple.xx
diff --git a/test/a-39 b/test/a-39
new file mode 100644
index 0000000..9fbea38
--- /dev/null
+++ b/test/a-39
@@ -0,0 +1,5 @@
+<SIZE> bytes, 1+3+0+0 records, response, noerror
+query: MX another.unsigned.xx
+answer: another.unsigned.xx 86400 CNAME an.example.xx
+answer: an.example.xx 86400 CNAME tripple.xx
+answer: an.example.xx 86400 RRSIG CNAME 7 3 86400 <TIME> <TIME> 44495 example.xx <SIG>
diff --git a/test/data b/test/data
new file mode 100644
index 0000000..0dd7396
--- /dev/null
+++ b/test/data
@@ -0,0 +1,65 @@
+# Example zones for testing.
+
+# This one should match RFC-5155 appendix A with different keys.
+
+Zexample:ns1.example:bugs.x.w.example:1081539377:3600:300:3600000:3600:3600::
+&example:192.0.2.1:ns1.example:3600
+&example:192.0.2.2:ns2.example:3600
+@example:192.0.2.10:xx.example:1:3600
+#Kexample:256:3:7:AwEAAZvSuWicF6oQ8QarGTQ84GDgYD3c12nrz/K2PQ98eCy0SKOZ+p4662QxkUGTmrxoUSGVlPp2arYXNs5AVf+ggt9zpb+ZSLV18Z152kAA6iHXVrwTbUZX1Eg4IqxzrSX2/jqs2dqQF7dDZcNV7xNpKfKDTW5J5d7vbkhcM8/xxb1p:3600::
+#Kexample:257:3:7:AwEAAdBi/kUytR/v7iW9h9qsgtuj5BSDEvE5djja8UEdFLqThQEJ0bnptExEoS8acYQYhLiufAEfp7no0bV2wcS6jR9cIRS0iZ/NNaPeG04U9HDz1VXiZ9xIsifw6feJlHEUAWMxtq7KF2Kyn3jOz05o08zk5XcIQnBDZiPw96bQfDlxMiM8umPWh8iZa6VmzQJ9bbouyoAppm/9O+ha7lCdMUbvIcKLoYVaVyK+pI2vHH4RaqrGUcposqi4hzJzOZvCU5FtJ+4uWGV+rleTmgR+YWS1gwfbNtaGIQacCAcSDA184TwKuUmHoHdBcrond7eyZGMVbKTbQ/MpiRI56uHd2bxqurBc6NmEsG9r3IZs+TsTvnkQVPksho39gcifpt/NKJ0uN01ZNUUvWQIZREm1UB/uR2hFWJ1hCvPkI08B5AbxIeRc8cb864PJHw2zye0wi6Ey0CHJK04KZLU+f6s80v7TsWmy/1gIMQMrDmiClkJQ/oFddCgisi/SgGbiaIkm5HUgEzBkTnZRluWwgZHVmJHI9I1Vwnyh3NReJo0IIRE90QGrOzA9l/op938f96E1ucFze28OzffhuLCQmmsrMXrKHnqg3K7vV+yk2v7vHec2GoKPuDwg5Lc6O1jOfx2AtoK0OC8Aj3XztynbzjdjJscxZr+C4JeN25pl8ilBT0Zp:3600::
+#Pexample:1:0:12::aabbccdd:3600::
+
++2t7b4g4vsa5smi47k61mv5bv1a22bojr.example:192.0.2.127:3600
+
+&a.example:192.0.2.5:ns1.a.example:3600
+&a.example:192.0.2.6:ns2.a.example:3600
+#Da.example:58470:5:1:3079F1593EBAD6DC121E202A8B766A6A4837206C:3600::
+
++ai.example:192.0.2.9:3600
+3ai.example:20010db800000000000000000f00baa9:3600
+:ai.example:13:\006KLH-10\003ITS:3600
+
+&c.example:192.0.2.7:ns1.c.example:3600
+&c.example:192.0.2.8:ns2.c.example:3600
+
+@*.w.example::ai.example:1:3600
+@x.w.example::xx.example:1:3600
+@x.y.w.example::xx.example:1:3600
+
+3xx.example:20010db800000000000000000f00baaa:3600
+:xx.example:13:\006KLH-10\007TOPS-20:3600
+
+
+# This one is somewhat related to RFC-2308.
+Zexample.xx:ns1.example.xx:bugs.x.w.example.xx:1081539377:3600:300:3600000:3600:2500
+&example.xx:192.0.2.1:ns1.example.xx
+&example.xx:192.0.2.2:ns2.example.xx
+#Kexample.xx:256:3:7:AwEAAZvSuWicF6oQ8QarGTQ84GDgYD3c12nrz/K2PQ98eCy0SKOZ+p4662QxkUGTmrxoUSGVlPp2arYXNs5AVf+ggt9zpb+ZSLV18Z152kAA6iHXVrwTbUZX1Eg4IqxzrSX2/jqs2dqQF7dDZcNV7xNpKfKDTW5J5d7vbkhcM8/xxb1p:::
+#Kexample.xx:257:3:7:AwEAAdBi/kUytR/v7iW9h9qsgtuj5BSDEvE5djja8UEdFLqThQEJ0bnptExEoS8acYQYhLiufAEfp7no0bV2wcS6jR9cIRS0iZ/NNaPeG04U9HDz1VXiZ9xIsifw6feJlHEUAWMxtq7KF2Kyn3jOz05o08zk5XcIQnBDZiPw96bQfDlxMiM8umPWh8iZa6VmzQJ9bbouyoAppm/9O+ha7lCdMUbvIcKLoYVaVyK+pI2vHH4RaqrGUcposqi4hzJzOZvCU5FtJ+4uWGV+rleTmgR+YWS1gwfbNtaGIQacCAcSDA184TwKuUmHoHdBcrond7eyZGMVbKTbQ/MpiRI56uHd2bxqurBc6NmEsG9r3IZs+TsTvnkQVPksho39gcifpt/NKJ0uN01ZNUUvWQIZREm1UB/uR2hFWJ1hCvPkI08B5AbxIeRc8cb864PJHw2zye0wi6Ey0CHJK04KZLU+f6s80v7TsWmy/1gIMQMrDmiClkJQ/oFddCgisi/SgGbiaIkm5HUgEzBkTnZRluWwgZHVmJHI9I1Vwnyh3NReJo0IIRE90QGrOzA9l/op938f96E1ucFze28OzffhuLCQmmsrMXrKHnqg3K7vV+yk2v7vHec2GoKPuDwg5Lc6O1jOfx2AtoK0OC8Aj3XztynbzjdjJscxZr+C4JeN25pl8ilBT0Zp:::
+#Pexample.xx:1:0:12::aabbccdd:::
+
+&a.example.xx:192.0.2.5:ns1.a.example.xx
+&a.example.xx:192.0.2.6:ns2.a.example.xx
+
+Can.example.xx:tripple.xx
+Canother.example.xx:an.example.xx
+Csimple.example.xx:ns1.example.xx
+Csub.example.xx:ns1.a.example.xx
+
+
+# Selfmade example for testing an unsigned zone
+.unsigned.xx:10.10.10.10:a
+.unsigned.xx:10.11.11.11:b
+
++aaaa.unsigned.xx:10.11.12.13
+3aaaa.unsigned.xx:20020a0b0c0d00000000000000000001
+
+@*.w.unsigned.xx::aaaa.unsigned.xx:1:3600
+@x.w.unsigned.xx::xx.example:1:3600
+@x.y.w.unsigned.xx::xx.example:1:3600
+
+Can.unsigned.xx:tripple.xx
+Canother.unsigned.xx:an.example.xx
+Csimple.unsigned.xx:ns1.example.xx
+Csub.unsigned.xx:ns1.a.example.xx
diff --git a/test/example.ksk b/test/example.ksk
new file mode 100644
index 0000000..691cfe1
--- /dev/null
+++ b/test/example.ksk
@@ -0,0 +1,53 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKgIBAAKCAgEA0GL+RTK1H+/uJb2H2qyC26PkFIMS8Tl2ONrxQR0UupOFAQnR
+uem0TEShLxpxhBiEuK58AR+nuejRtXbBxLqNH1whFLSJn801o94bThT0cPPVVeJn
+3EiyJ/Dp94mUcRQBYzG2rsoXYrKfeM7PTmjTzOTldwhCcENmI/D3ptB8OXEyIzy6
+Y9aHyJlrpWbNAn1tui7KgCmmb/076FruUJ0xRu8hwouhhVpXIr6kja8cfhFqqsZR
+ymiyqLiHMnM5m8JTkW0n7i5YZX6uV5OaBH5hZLWDB9s21oYhBpwIBxIMDXzhPAq5
+SYegd0Fyuid3t7JkYxVspNtD8ymJEjnq4d3ZvGq6sFzo2YSwb2vchmz5OxO+eRBU
++SyGjf2ByJ+m380onS43TVk1RS9ZAhlESbVQH+5HaEVYnWEK8+QjTwHkBvEh5Fzx
+xvzrg8kfDbPJ7TCLoTLQIckrTgpktT5/qzzS/tOxabL/WAgxAysOaIKWQlD+gV10
+KCKyL9KAZuJoiSbkdSATMGROdlGW5bCBkdWYkcj0jVXCfKHc1F4mjQghET3RAas7
+MD2X+in3fx/3oTW5wXN7bw7N9+G4sJCaaysxesoeeqDcru9X7KTa/u8d5zYago+4
+PCDktzo7WM5/HYC2grQ4LwCPdfO3KdvON2MmxzFmv4Lgl43bmmXyKUFPRmkCAwEA
+AQKCAgEAtUH2I+CYdYAwKHm0arv1UvE6mbDtUA+ISqn+gYG3Hxbj8ORGnayvnEtx
+3FPm+1yMTUvQJvYO4YGmr23t253c1LNBPw5OS0am6rNuErvdZ0ZggUTezFgbRuyh
+xiPQj480KcD7QwwbzUjLt7xDy9MYr7dF9QSlLZsihA68i0f7VcelMctH4UGgeBci
+8Ar8Nbc+M10x3Mrdr7mYW2KEunAQhb/JILxtsV3EPz+OSINRiELEAmlgiWwKQwHG
+71YUfOxJ3kwRGHcNgrLvGNQVeuUm9/9+St4d0/l1Tpd30BadjznZfG1jf5bOOA0g
+qtVbp9guw5TGHJtwfljZlpX0PaTIFy0c6esj/AEkUGl8lCPxh4ffE/t5vdZhLhET
+9F+Y5ktB9dqdcEoUs5FNrb+DKjwrGxkifXGmelKxWAfNfhZIe0ELhp607FIk5TYC
+VxrQX90tI4HO3wdceOafCee0xDF3GDisIlCNd7zRwNhHY3pnEYjTxGQum0u7s9q1
+HbrbSlm+D61fAlQ5stKefhk5P51dVmjONt3TMgOg/IzocsgI1pR9RviKOW14Gs8Z
+1PiC0tZIG0HDUHs8UEHjMkyXrULEdW+i33dHpjEcsK8GK4+EHWRfsC2NXOJXcjIw
+8YwJtIf1BistPz2OGm4zkUB9XFjQrauX4WGofVPvEFp5kTX0CkUCggEBAP5d5VuE
+D36K3rVZLdW/Jr1qdE3bTx01L0ktp9K0kjnMQO6xUvn1+Ef/+mKwN0hBRIzYtz4q
+TADpXX5d9N7Esw5qYbFWFd5IWTuS+Sl7KTwy9StYL7SaBudPaEhCyYj1yxzKZZ5n
+RbzNF4zIv45lxzq4fxLbdm64fAmo/RPG+m1UqrfRXh7qO2By8ykOev5qD9qo1XWe
+DkvNHpiPk3rMA+yzumoD5mnC2j7DhNjuC4KaWHxsNu4Ln616KLw3OXJFmQn8FjB3
+BI5B4yUH8S2vXQGjncgOw/0eH09dIDSy5OGhEf6h2clZaD0FMn4RKT6Bp51v8oik
+qDArnyW5ZSPE718CggEBANG5hQP7GekwO6cniqb22FQyYgXX7zokcjT6OoRgvNSy
+YJVtXTsff3YkR9LQCzCVOwHg1kGlv0ra6XfkwKQ46f5uRoFuq8+raOiYrAmxSWyQ
+5OfkS/V3WeLHeykEZFOR//oDB6JUiXiQ3uL3pE1ja63unKHym4RL1R9l3p0mpsTE
+2jsjnOV9WOYdL3m5WVslIbbN4c+P1sjnzJhAEvaqnPWOTlsHRcTl6idQgMtdgyyu
+TAWxSItCnTD9qN6hlODCx+Mrok7/j1BmD2kY00AbML2nkT2HzdXN7dVOku8ZmaW8
+lqtgsUYvwMQuYNi8NjIz5fRyeVC4HHO44YivqrQzxzcCggEBAPHAre73qX48YAR9
+mIlw3lHiLl2c1SlOVf237avdwKg8D26Mi+9TrkBH8mH/VttOZOd0RVxI+OlY4mnq
+xINA8Wj/BJB139zeaCitvC+HhZ5YVBl4/AYq6erH1rXu+/o2mW9okYImZupVBk/g
+r+aXpu2RUfzLJll+7yyDNtyoHXTxpjuEOm8pcXtuZNqdj5njlePc2Nurd6zla3HV
+63YnFofOHZsKz9+uuJw0WTuPqtLa/MRsWRyPYZiP5M1Vszx/Fz+dHj27sFHAHzEf
+xkIETGH1bk0oc29LRLi6KpTLfatP2XlnvESYu/Ba0y1sBAYLVhHUxu7Fh7AYlW5W
+MOD5GAMCggEAebJKYkX6AhtP8i36wenpG+pkgPmNQtLVRrKQ9DHiQRYE/5CHkgA0
+CU0CNG0uoRFnPS7HhS51sy0WXtDpjCHOfiplVUPwBMB1TssQwUsTzSDDA2CxRw3r
+pbGVYPbAdNH6lIvfiS2+26xM+a0ztQhk+nfEgiDyZzFNyTtmkxBTo0iLTdfbejtj
+M5xp6RtJo58HUjljt/rCarA/Q3Wiy2mzTLY395BfxuKXHhsTsW5g6LN3P7Jg4xZT
+epMmw5FFf6rnLIYsV+Tpt4CRnq2eH8vnW5X5rJe6ND+bq7Q9hOr5AnhNgcLI+25I
+UE6NOhb05+q/nDo09Ubwk6ILlTJCPvDwvwKCAQEAwIE3UchyTPqpxQhwM4pskzp5
+4d22lESv0+hkmxUIoHOABuHPF3USJ+UmptPBlvU9ii9TPh3M3PZmH0e5K/Tuon6Y
+U3F5P5kP2tHv0uChAhlFLgn8ibQu7j77s2F7YnkdGPldahML3s6O/hrbcqWh87t9
+nHYPu9cfipNKvxtyLBqeqAi36ysSngKeg/G6zoRVXKNAxvfVW18ndloopnmJYpUH
+SYpsYHj/Ob9YfXFeCvbsMsXy8384EGA+kt59OBnMn4NPy4uiQXaGEcQWmE+obKZa
+hYZLSp/LT3kPmKedkNkitLonghDLsgcTwL9TPo+OMRZ8GynOI+7Lp9JzMrm55w==
+-----END RSA PRIVATE KEY-----
+#Kexample:257:3:8:AwEAAdBi/kUytR/v7iW9h9qsgtuj5BSDEvE5djja8UEdFLqThQEJ0bnptExEoS8acYQYhLiufAEfp7no0bV2wcS6jR9cIRS0iZ/NNaPeG04U9HDz1VXiZ9xIsifw6feJlHEUAWMxtq7KF2Kyn3jOz05o08zk5XcIQnBDZiPw96bQfDlxMiM8umPWh8iZa6VmzQJ9bbouyoAppm/9O+ha7lCdMUbvIcKLoYVaVyK+pI2vHH4RaqrGUcposqi4hzJzOZvCU5FtJ+4uWGV+rleTmgR+YWS1gwfbNtaGIQacCAcSDA184TwKuUmHoHdBcrond7eyZGMVbKTbQ/MpiRI56uHd2bxqurBc6NmEsG9r3IZs+TsTvnkQVPksho39gcifpt/NKJ0uN01ZNUUvWQIZREm1UB/uR2hFWJ1hCvPkI08B5AbxIeRc8cb864PJHw2zye0wi6Ey0CHJK04KZLU+f6s80v7TsWmy/1gIMQMrDmiClkJQ/oFddCgisi/SgGbiaIkm5HUgEzBkTnZRluWwgZHVmJHI9I1Vwnyh3NReJo0IIRE90QGrOzA9l/op938f96E1ucFze28OzffhuLCQmmsrMXrKHnqg3K7vV+yk2v7vHec2GoKPuDwg5Lc6O1jOfx2AtoK0OC8Aj3XztynbzjdjJscxZr+C4JeN25pl8ilBT0Zp:::
+#Dexample:44062:8:1:e95469a449a82fc0c0ccb257b8a546edcdeb564d:::
diff --git a/test/example.zsk b/test/example.zsk
new file mode 100644
index 0000000..4d5952b
--- /dev/null
+++ b/test/example.zsk
@@ -0,0 +1,17 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCb0rlonBeqEPEGqxk0POBg4GA93Ndp68/ytj0PfHgstEijmfqe
+OutkMZFBk5q8aFEhlZT6dmq2FzbOQFX/oILfc6W/mUi1dfGdedpAAOoh11a8E21G
+V9RIOCKsc60l9v46rNnakBe3Q2XDVe8TaSnyg01uSeXe725IXDPP8cW9aQIDAQAB
+AoGAeZeh3qOPQ8IckpuI+15VqMLt1tgxsBG3Hypd02vJSvkThbZt/nLzpCeZtZY8
+tLCiPpa+vgMRmi3bMm65rNPPyt5pFzJ3sEVSgP98zoN9MmpDBKRe93poMyOiWdKL
+JagmHiBURi9LRk3SG9M7w/HyQn9TUtuLXjgeNlz/zdeLSgECQQDL8PLijCrqeeGL
+9OYBjs63Db2V6dMx3BrI3y3KstM0/aEBH+9mzkZD9IrgqrTn/PtIpPYuIhtWWwos
+i4bpnuu5AkEAw5lhJmSVnNqtrsC9F5YqBUlMyPvfJeQWEW0dwxVk3NCc16AtBUcC
+s/3lrKJNqAKYB5bRTHNca2tk0Lw8BRUXMQJATXQzKqtFWUv0xyy5dfoAtDD5wcfO
+N+96FLP+Ni94W3XAAidYytiogwKLBAyRLFI+NTbBcH/vlfp5gLV1BaEfsQJAc8AN
+pMm7XAJw68x1WkLsBQrWnM2oxWSPxqo7BZpggOgXGOdaHRhjuh5TT3EQ4Y7/ZuZS
+X5qZI5x/IaNzJVLQ0QJBALSHOwr6CA+PH0g6qeTAewMMA5M95/4hCl2JHL9jTvbr
+5JdKq1+NP/0ZY8YeyAJLqgbWZvrvac3h4sKE4e/ey2k=
+-----END RSA PRIVATE KEY-----
+#Kexample:256:3:7:AwEAAZvSuWicF6oQ8QarGTQ84GDgYD3c12nrz/K2PQ98eCy0SKOZ+p4662QxkUGTmrxoUSGVlPp2arYXNs5AVf+ggt9zpb+ZSLV18Z152kAA6iHXVrwTbUZX1Eg4IqxzrSX2/jqs2dqQF7dDZcNV7xNpKfKDTW5J5d7vbkhcM8/xxb1p:::
+#Dexample:44495:7:1:e4c565a032c3709f54b72541ef13e647c9c19e1d:::
diff --git a/test/q-01 b/test/q-01
new file mode 100644
index 0000000..bef04de
--- /dev/null
+++ b/test/q-01
@@ -0,0 +1,2 @@
+-s mx x.w.example
+# RFC-4035 Appendix B.1
diff --git a/test/q-02 b/test/q-02
new file mode 100644
index 0000000..b5cfc62
--- /dev/null
+++ b/test/q-02
@@ -0,0 +1,2 @@
+-s mx mc.a.example
+# RFC-4035 Appendix B.4
diff --git a/test/q-03 b/test/q-03
new file mode 100644
index 0000000..ec55a89
--- /dev/null
+++ b/test/q-03
@@ -0,0 +1,2 @@
+-s a a.c.x.w.example
+# RFC-5155 Appendix B.1
diff --git a/test/q-04 b/test/q-04
new file mode 100644
index 0000000..72ff808
--- /dev/null
+++ b/test/q-04
@@ -0,0 +1,2 @@
+-s mx ns1.example
+# RFC-5155 Appendix B.2
diff --git a/test/q-05 b/test/q-05
new file mode 100644
index 0000000..da2d679
--- /dev/null
+++ b/test/q-05
@@ -0,0 +1,2 @@
+-s a y.w.example
+# RFC-5155 Appendix B.2.1
diff --git a/test/q-06 b/test/q-06
new file mode 100644
index 0000000..c08772a
--- /dev/null
+++ b/test/q-06
@@ -0,0 +1,2 @@
+-s mx a.z.w.example
+# RFC-5155 Appendix B.4
diff --git a/test/q-07 b/test/q-07
new file mode 100644
index 0000000..3079f01
--- /dev/null
+++ b/test/q-07
@@ -0,0 +1,2 @@
+-S mx a.z.w.example
+# RFC-5155 Appendix B.4
diff --git a/test/q-08 b/test/q-08
new file mode 100644
index 0000000..1123888
--- /dev/null
+++ b/test/q-08
@@ -0,0 +1,2 @@
+-s aaaa a.z.w.example
+# RFC-5155 Appendix B.5
diff --git a/test/q-09 b/test/q-09
new file mode 100644
index 0000000..30ed504
--- /dev/null
+++ b/test/q-09
@@ -0,0 +1,2 @@
+-s ds example
+# RFC-5155 Appendix B.6
diff --git a/test/q-10 b/test/q-10
new file mode 100644
index 0000000..3947108
--- /dev/null
+++ b/test/q-10
@@ -0,0 +1 @@
+mx x.w.example
diff --git a/test/q-11 b/test/q-11
new file mode 100644
index 0000000..075e5e6
--- /dev/null
+++ b/test/q-11
@@ -0,0 +1 @@
+mx mc.a.example
diff --git a/test/q-12 b/test/q-12
new file mode 100644
index 0000000..333e0bd
--- /dev/null
+++ b/test/q-12
@@ -0,0 +1 @@
+a a.c.x.w.example
diff --git a/test/q-13 b/test/q-13
new file mode 100644
index 0000000..f98714e
--- /dev/null
+++ b/test/q-13
@@ -0,0 +1 @@
+mx ns1.example
diff --git a/test/q-14 b/test/q-14
new file mode 100644
index 0000000..f442db7
--- /dev/null
+++ b/test/q-14
@@ -0,0 +1 @@
+a y.w.example
diff --git a/test/q-15 b/test/q-15
new file mode 100644
index 0000000..8aeba48
--- /dev/null
+++ b/test/q-15
@@ -0,0 +1 @@
+mx a.z.w.example
diff --git a/test/q-16 b/test/q-16
new file mode 100644
index 0000000..15de40d
--- /dev/null
+++ b/test/q-16
@@ -0,0 +1 @@
+aaaa a.z.w.example
diff --git a/test/q-17 b/test/q-17
new file mode 100644
index 0000000..0362480
--- /dev/null
+++ b/test/q-17
@@ -0,0 +1 @@
+ds example
diff --git a/test/q-18 b/test/q-18
new file mode 100644
index 0000000..18e7a2b
--- /dev/null
+++ b/test/q-18
@@ -0,0 +1 @@
+mx an.example.xx
diff --git a/test/q-19 b/test/q-19
new file mode 100644
index 0000000..195f284
--- /dev/null
+++ b/test/q-19
@@ -0,0 +1 @@
+mx another.example.xx
diff --git a/test/q-20 b/test/q-20
new file mode 100644
index 0000000..fed41b4
--- /dev/null
+++ b/test/q-20
@@ -0,0 +1 @@
+mx simple.example.xx
diff --git a/test/q-21 b/test/q-21
new file mode 100644
index 0000000..d0c8d6a
--- /dev/null
+++ b/test/q-21
@@ -0,0 +1 @@
+mx sub.example.xx
diff --git a/test/q-22 b/test/q-22
new file mode 100644
index 0000000..2bafbd8
--- /dev/null
+++ b/test/q-22
@@ -0,0 +1 @@
+a simple.example.xx
diff --git a/test/q-23 b/test/q-23
new file mode 100644
index 0000000..274715c
--- /dev/null
+++ b/test/q-23
@@ -0,0 +1 @@
+-s mx an.example.xx
diff --git a/test/q-24 b/test/q-24
new file mode 100644
index 0000000..71c8006
--- /dev/null
+++ b/test/q-24
@@ -0,0 +1 @@
+-s mx another.example.xx
diff --git a/test/q-25 b/test/q-25
new file mode 100644
index 0000000..20d96e9
--- /dev/null
+++ b/test/q-25
@@ -0,0 +1 @@
+-s mx simple.example.xx
diff --git a/test/q-26 b/test/q-26
new file mode 100644
index 0000000..626d4b4
--- /dev/null
+++ b/test/q-26
@@ -0,0 +1 @@
+-s mx sub.example.xx
diff --git a/test/q-27 b/test/q-27
new file mode 100644
index 0000000..1030c51
--- /dev/null
+++ b/test/q-27
@@ -0,0 +1 @@
+-s a simple.example.xx
diff --git a/test/q-28 b/test/q-28
new file mode 100644
index 0000000..b53f963
--- /dev/null
+++ b/test/q-28
@@ -0,0 +1 @@
+cname an.example.xx
diff --git a/test/q-29 b/test/q-29
new file mode 100644
index 0000000..9d31260
--- /dev/null
+++ b/test/q-29
@@ -0,0 +1 @@
+-s cname an.example.xx
diff --git a/test/q-30 b/test/q-30
new file mode 100644
index 0000000..15d5ee6
--- /dev/null
+++ b/test/q-30
@@ -0,0 +1 @@
+aaaa a.ns.unsigned.xx
diff --git a/test/q-31 b/test/q-31
new file mode 100644
index 0000000..23d7d82
--- /dev/null
+++ b/test/q-31
@@ -0,0 +1 @@
+-s aaaa a.ns.unsigned.xx
diff --git a/test/q-32 b/test/q-32
new file mode 100644
index 0000000..68769ec
--- /dev/null
+++ b/test/q-32
@@ -0,0 +1 @@
+a nx.unsigned.xx
diff --git a/test/q-33 b/test/q-33
new file mode 100644
index 0000000..bfd510e
--- /dev/null
+++ b/test/q-33
@@ -0,0 +1 @@
+-s a nx.unsigned.xx
diff --git a/test/q-34 b/test/q-34
new file mode 100644
index 0000000..d04f65e
--- /dev/null
+++ b/test/q-34
@@ -0,0 +1 @@
+mx wc.w.unsigned.xx
diff --git a/test/q-35 b/test/q-35
new file mode 100644
index 0000000..15b6fd3
--- /dev/null
+++ b/test/q-35
@@ -0,0 +1 @@
+-s mx wc.w.unsigned.xx
diff --git a/test/q-36 b/test/q-36
new file mode 100644
index 0000000..6c7aec9
--- /dev/null
+++ b/test/q-36
@@ -0,0 +1 @@
+mx an.unsigned.xx
diff --git a/test/q-37 b/test/q-37
new file mode 100644
index 0000000..52a2dec
--- /dev/null
+++ b/test/q-37
@@ -0,0 +1 @@
+-s mx an.unsigned.xx
diff --git a/test/q-38 b/test/q-38
new file mode 100644
index 0000000..b8778b8
--- /dev/null
+++ b/test/q-38
@@ -0,0 +1 @@
+mx another.unsigned.xx
diff --git a/test/q-39 b/test/q-39
new file mode 100644
index 0000000..a79f33f
--- /dev/null
+++ b/test/q-39
@@ -0,0 +1 @@
+-s mx another.unsigned.xx
diff --git a/tinydns-sign.pl b/tinydns-sign.pl
new file mode 100755
index 0000000..fd72033
--- /dev/null
+++ b/tinydns-sign.pl
@@ -0,0 +1,1025 @@
+#!/usr/bin/perl -w
+
+# (C) 2012 Peter Conrad <conrad@quisquis.de>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use Fcntl;
+use Digest::SHA1 qw(sha1 sha1_hex);
+use Crypt::OpenSSL::RSA;
+use MIME::Base64;
+
+$main::ttl = 432000;
+%main::keys = ();
+%main::names = ();
+@main::zones = ();
+%main::locs = ();
+$main::catchAllLoc = undef;
+
+while ($#ARGV >= 0) {
+ if ($ARGV[0] eq '-h' || $ARGV[0] eq '--help') {
+ &usage();
+ # usage does not return
+ } elsif ($ARGV[0] eq '-t') {
+ if ($#ARGV < 1) { &usage(); }
+ $main::ttl = $ARGV[1];
+ shift @ARGV;
+ } elsif ($ARGV[0] eq '-g') {
+ if ($#ARGV != 5) { &usage(); }
+ &genkey($ARGV[1], $ARGV[2], $ARGV[3], $ARGV[4], $ARGV[5]);
+ # genkey does not return
+ exit(0);
+ } else { # keyfile
+ &addKey($ARGV[0]);
+ }
+ shift @ARGV;
+}
+
+my $now = time();
+while ($_ = <STDIN>) {
+ s/\s+$//s;
+ if (/^:[^:]*:(43|46|48|50|51|6528[12]):/) { next; }
+ if (/^$/ || /^-/ || /^#[^KDP]/) {
+ print "$_\n";
+ next;
+ }
+ if (/^\./) {
+ print "# Was: $_\n";
+ while (! /^\.([^:]*):([0-9.]*):([^:]+):(\d*):(\d*):([^:]*)$/) { $_ .= ':'; }
+ my ($dom,$ip,$ns,$ttl,$ts,$lo) = /^\.([^:]*):([0-9.]*):([^:]+):(\d*):(\d*):([^:]*)$/;
+ # &fqdn:ip:x:ttl:timestamp:lo
+ print "\&$dom:$ip:$ns:$ttl:$ts:$lo\n";
+ $dom =~ tr/A-Z/a-z/;
+ $ns =~ tr/A-Z/a-z/;
+ if ($ns !~ /\./) { $ns .= ".ns.$dom"; }
+ my $rec = &getOrCreateRRs($dom);
+ $rec->addNS($ns, $ttl, $ts, $lo);
+ &addA($ns, $ip, $ttl || 259200, $ts, $lo);
+ if (exists($rec->{byType}->{6})) { next; } # don't create more than one SOA per zone
+ # Zfqdn:mname:rname:ser:ref:ret:exp:min:ttl:timestamp:lo
+ $_ = "Z$dom:$ns:hostmaster.${dom}:::::$ttl:$ts:$lo\n";
+ }
+ if (/^Z/) {
+ my @fields = split /:/;
+ if ($#fields < 3 || $fields[3] !~ /^00/) {
+ $fields[3] = $now;
+ $_ = join(":", @fields);
+ }
+ }
+ print "$_\n";
+ if (/^%(\w\w?):?/) {
+ $main::locs{$1} = $';
+ if ($' eq "") {
+ $main::catchAllLoc = $1;
+ }
+ } elsif (/^#[KDP]/) {
+ if (/^#K([^:]*):(\d+):(\d*):(\d+):([^:]*):(\d*):(\d*):([^:]*)$/) {
+ # C<#Kname:flags:proto:algorithm:key:ttl:timestamp:lo>
+ my ($dom,$flags,$proto,$alg,$key,$ttl,$ts,$lo) = ($1, $2, $3, $4, $5, $6, $7, $8);
+ $dom =~ tr/A-Z/a-z/;
+ my $pubkey = decode_base64($key);
+ my $rdata = &htons($flags).chr($proto).chr($alg).$pubkey;
+ &makeGenericRecord($dom, 48, $rdata, $ttl, $ts, $lo);
+ my $rrs = &getOrCreateRRs($dom);
+ $rrs->addKey($flags, $alg, $pubkey);
+ $rrs->addRecord(48, $rdata, $ttl, $ts, $lo);
+ } elsif (/^#D([^:]*):(\d+):(\d+):(\d+):([0-9a-fA-F]*):(\d*):(\d*):([^:]*)$/) {
+ # C<#Dname:tag:algorithm:digest:fingerprint:ttl:timestamp:lo>
+ my ($dom,$tag,$alg,$dig,$fp,$ttl,$ts,$lo) = ($1, $2, $3, $4, $5, $6, $7, $8);
+ $dom =~ tr/A-Z/a-z/;
+ my $rdata = &htons($tag).chr($alg).chr($dig).pack("H*", $fp);
+ &makeGenericRecord($dom, 43, $rdata, $ttl, $ts, $lo);
+ &getOrCreateRRs($dom)->addRecord(43, $rdata, $ttl, $ts, $lo);
+ } elsif (/^#P([^:]*):(\d+):(\d+):(\d+):(\d*):([0-9a-fA-F]*):(\d*):(\d*):([^:]*)$/) {
+ # C<#Pname:algorithm:flags:iter:len:salt:ttl:timestamp:lo>
+ my ($dom,$alg,$flag,$iter,$len,$salt,$ttl,$ts,$lo) = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
+ $dom =~ tr/A-Z/a-z/;
+ if ($salt eq "") {
+ if ($len eq "") { $len = 4; }
+ open(RANDOM, "</dev/urandom") or die("Failed to open /dev/urandom");
+ sysread(RANDOM, $salt, $len);
+ close(RANDOM);
+ $salt = unpack("H*", $salt);
+ } else {
+ $len = length(pack("H*", $salt));
+ }
+ my $rdata = chr($alg).chr($flag).&htons($iter)
+ .chr($len).pack("H*", $salt);
+ &makeGenericRecord($dom, 51, $rdata, $ttl, $ts, $lo);
+ my $rrs = &getOrCreateRRs($dom);
+ $rrs->addNS3P($alg, $flag, $iter, $salt, $ttl, $ts, $lo);
+ $rrs->addRecord(51, $rdata, $ttl, $ts, $lo);
+ } else {
+ print STDERR "Warning: ignored incomplete pseudo record '$_'!\n";
+ }
+ next;
+ }
+ my $type = substr($_, 0, 1);
+ if ($type !~ /^[.\&=+\@'^CZ:36]/) {
+ print STDERR "Warning: ignored unknown record type '$type'\n";
+ next;
+ }
+ my @stuff = split(/:/, substr($_, 1));
+ if ($#stuff < 0) {
+ print STDERR "Warning: ignored empty record '$_'\n";
+ next;
+ }
+ my $dom = shift @stuff;
+ $dom =~ tr/A-Z/a-z/;
+ my $rec = &getOrCreateRRs($dom);
+ if ($type eq '&') {
+ # &fqdn:ip:x:ttl:timestamp:lo
+ my $ns = $stuff[1];
+ if ($ns !~ /\./) { $ns .= ".ns.$dom"; }
+ $rec->addNS($ns, $stuff[2], $stuff[3], $stuff[4]);
+ &addA($ns, $stuff[0], $stuff[2] || 259200, $stuff[3], $stuff[4]);
+ } elsif ($type eq '=') {
+ # =fqdn:ip:ttl:timestamp:lo
+ $rec->addA($stuff[0], $stuff[1], $stuff[2], $stuff[3]);
+ &addPTR($stuff[0], $dom, $stuff[1], $stuff[2], $stuff[3]);
+ } elsif ($type eq '+') {
+ # +fqdn:ip:ttl:timestamp:lo
+ $rec->addA($stuff[0], $stuff[1], $stuff[2], $stuff[3]);
+ } elsif ($type eq '6') {
+ # =fqdn:ip6:ttl:timestamp:lo
+ $rec->addAAAA($stuff[0], $stuff[1], $stuff[2], $stuff[3]);
+ &addPTR($stuff[0], $dom, $stuff[1], $stuff[2], $stuff[3]);
+ } elsif ($type eq '3') {
+ # +fqdn:ip6:ttl:timestamp:lo
+ $rec->addAAAA($stuff[0], $stuff[1], $stuff[2], $stuff[3]);
+ } elsif ($type eq '@') {
+ # @fqdn:ip:x:dist:ttl:timestamp:lo
+ my $mx = $stuff[1];
+ if ($mx !~ /\./) { $mx .= ".mx.$dom"; }
+ $rec->addMX($mx, $stuff[2], $stuff[3], $stuff[4], $stuff[5]);
+ &addA($mx, $stuff[0], $stuff[3], $stuff[4], $stuff[5]);
+ } elsif ($type eq "'") {
+ # 'fqdn:s:ttl:timestamp:lo
+ $rec->addTXT(&parseData($stuff[0]), $stuff[1], $stuff[2], $stuff[3]);
+ } elsif ($type eq '^') {
+ # ^fqdn:p:ttl:timestamp:lo
+ $rec->addPTR($stuff[0], $stuff[1], $stuff[2], $stuff[3]);
+ } elsif ($type eq 'C') {
+ # Cfqdn:p:ttl:timestamp:lo
+ $rec->addCNAME($stuff[0], $stuff[1], $stuff[2], $stuff[3]);
+ } elsif ($type eq 'Z') {
+ # Zfqdn:mname:rname:ser:ref:ret:exp:min:ttl:timestamp:lo
+ $rec->addSOA($stuff[0], $stuff[1], $stuff[2], $stuff[3], $stuff[4], $stuff[5], $stuff[6], $stuff[7], $stuff[8], $stuff[9]);
+ } elsif ($type eq ':') {
+ # :fqdn:n:rdata:ttl:timestamp:lo
+ $rec->addRecord($stuff[0], &parseData($stuff[1]), $stuff[2], $stuff[3], $stuff[4]);
+ }
+}
+
+foreach my $rec (values %main::names) {
+ my $dom = $rec->{name};
+ if (exists($rec->{byType}->{48})) {
+ if (!exists($rec->{byType}->{6})) {
+ print STDERR "Warning: useless DNSKEY for $dom without SOA!\n";
+ } elsif (!exists($rec->{byType}->{51})) {
+ print STDERR "ERROR: DNSKEY for $dom without NSEC3PARAM!\n";
+ exit(1);
+ }
+ } elsif (exists($rec->{byType}->{51})) {
+ print STDERR "Warning: useless NSEC3PARAM for $dom without DNSKEY!\n";
+ }
+ &findControl($rec);
+}
+
+foreach my $zone (@main::zones) {
+ my $rec = $main::names{$zone};
+ if (!exists($rec->{keys})) {
+ print STDERR "Info: ignore unsigned zone $zone\n";
+ foreach my $dom (keys %{$rec->{zone}}) {
+ delete $main::names{$dom};
+ }
+ next;
+ }
+ if (!exists($rec->{nsec3p})) {
+ next;
+ }
+
+ #
+ # Add NSEC3 RRs to zone
+ my %hashes = ();
+ my $n3p = $rec->{nsec3p}->[0];
+ # Generate hashes
+ foreach my $name (keys %{$rec->{zone}}) {
+ my $rr;
+ LOOP: { do {
+ $rr = &getOrCreateRRs($name);
+ if (exists($rr->{hash})) { last; }
+ $rec->{zone}->{$name} = $rr;
+ my $hash = &nsec3hash($name, $n3p->{iterations}, $n3p->{salt});
+ if (exists($hashes{$hash})) {
+ print STDERR "ERROR: hash collision on $name!?\n";
+ exit(1);
+ }
+ $hashes{$hash} = { hash => $hash, name => &base32hex($hash).".".$zone,
+ generator => $name, zone => $zone };
+ $rr->{hash} = $hash;
+ if ($name eq $zone) { last; }
+ if ($name =~ /^[^.]*\./) { $name = $'; }
+ } while ($rr->{name} ne $zone); }
+ }
+ # Find next hash in hash sort order
+ my ($first, $prev);
+ my @byNibble = ();
+ my $prevNib = -1;
+ foreach my $hash (sort keys %hashes) {
+ my $nib = ord($hash) >> 4;
+ if (!defined($byNibble[$nib])) {
+ while ($prevNib < $nib) {
+ $byNibble[++$prevNib] = defined($prev) ? [$prev->{hash}] : [];
+ }
+ }
+ push @{$byNibble[$nib]}, $hash;
+ if (!$first) {
+ $first = $prev = $hashes{$hash};
+ next;
+ }
+ $prev->{next} = $hash;
+ $prev = $hashes{$hash};
+ }
+ $prev->{next} = $first->{hash};
+ unshift @{$byNibble[0]}, $prev->{hash};
+ for (my $nib = 1; $#{$byNibble[$nib]} < 0; $nib++) {
+ unshift @{$byNibble[$nib]}, $prev->{hash};
+ }
+ for (my $nib = $#byNibble + 1; $nib < 16; $nib++) {
+ $byNibble[$nib] = [$prev->{hash}];
+ }
+ # Generate records
+ foreach my $hash (values %hashes) {
+ my $rRec = &getOrCreateRRs($hash->{name});
+ my $gRec = &getOrCreateRRs($hash->{generator});
+ $rec->{zone}->{$hash->{name}} = $rRec;
+ my $rdata = chr($n3p->{alg}).chr($n3p->{flags})
+ .&htons($n3p->{iterations})
+ .chr(length($n3p->{salt})).$n3p->{salt}
+ .chr(length($hash->{next})).$hash->{next}
+ .&genTypeBitmaps(keys %{$gRec->{byType}});
+ print "# NSEC3: ".$hash->{name}." - ".&base32hex($hash->{next})." ("
+ .join(" ", sort keys %{$gRec->{byType}}).")\n";
+ $rRec->addRecord(50, $rdata, $n3p->{ttl}, $n3p->{ts}, $n3p->{lo});
+ &makeGenericRecord($hash->{name}, 50, $rdata, $n3p->{ttl}, $n3p->{ts}, $n3p->{lo});
+ print "# H(".$hash->{generator}.") -> ".$hash->{name}."\n";
+ #$gRec->addRecord(65281, &wireName($hash->{name}), $n3p->{ttl}, $n3p->{ts}, $n3p->{lo});
+ &makeGenericRecord($hash->{generator}, 65281, &wireName($hash->{name}), $n3p->{ttl}, $n3p->{ts}, $n3p->{lo});
+ }
+ for (my $nib = 0; $nib < 16; $nib++) {
+ my $name = sprintf("%x", $nib).".$zone";
+ my $rec = &getOrCreateRRs($name);
+ $rec->addRecord(65282, join("", @{$byNibble[$nib]}), $n3p->{ttl}, $n3p->{ts}, $n3p->{lo});
+ &makeGenericRecord($name, 65282, join("", @{$byNibble[$nib]}), $n3p->{ttl}, $n3p->{ts}, $n3p->{lo});
+ }
+
+ # Add RRSIGs to zone
+ my (@zsks, @ksks);
+ if (!$rec->{haveSEP} || !$rec->{haveZSK}) {
+ @zsks = @ksks = @{$rec->{keys}};
+ } else {
+ @ksks = grep { $_->{flags} & 1 } @{$rec->{keys}};
+ @zsks = grep { !($_->{flags} & 1) } @{$rec->{keys}};
+ }
+ my $validFrom = $now - 3600;
+ my $validUntil = $now + $main::ttl;
+ foreach my $owner (values %{$rec->{zone}}) {
+ my $name = $owner->{name};
+ my $isCutpoint = exists($owner->{byType}->{2}) && $name ne $zone;
+ foreach my $type (keys %{$owner->{byType}}) {
+ if ($isCutpoint && $type != 43 && $type != 50) { next; }
+ my $labels = $name;
+ $labels =~ s/^\*\.//;
+ $labels =~ s/[^.]+//g;
+ $labels = length($labels) + 1;
+ my $ttl = $owner->{byType}->{$type}->[0]->{ttl} || 86400;
+ foreach my $key ($type == 48 ? @ksks : @zsks) {
+ my $keytag = $key->{key}->{basetag} + $key->{alg} + $key->{flags};
+ $keytag = ($keytag + ($keytag >> 16)) & 0xffff;
+ my $rdata = &htons($type).chr($key->{alg}).chr($labels)
+ .&htonl($ttl).&htonl($validUntil)
+ .&htonl($validFrom)
+ .&htons($keytag).&wireName($zone);
+ my $toSign = $rdata;
+ foreach my $rr (sort { $a->{rdata} cmp $b->{rdata} } @{$owner->{byType}->{$type}}) {
+ $toSign .= &wireName($name).&htons($type)."\0\1".&htonl($ttl)
+ .&htons(length($rr->{rdata})).$rr->{rdata};
+ }
+ $key->{key}->{key}->use_pkcs1_padding();
+ if ($key->{alg} == 7) {
+ $key->{key}->{key}->use_sha1_hash();
+ } elsif ($key->{alg} == 8) {
+ $key->{key}->{key}->use_sha256_hash();
+ } elsif ($key->{alg} == 10) {
+ $key->{key}->{key}->use_sha512_hash();
+ } else {
+ print STDERR "Unsupported key type ".$key->{alg}."\n";
+ exit 1;
+ }
+ my $sig = $key->{key}->{key}->sign($toSign);
+ if (!$sig || !$key->{key}->{key}->verify($toSign, $sig)) {
+ print STDERR "Failed to sign $name ($type)!?\n";
+ exit 1;
+ }
+ #$key->{key}->{key}->use_no_padding();
+ #print "# ".unpack("H*", $key->{key}->{key}->public_decrypt($sig))."\n";
+ $rdata .= $sig;
+ print "# RRSIG $type ".$key->{alg}." $labels $ttl $validUntil $validFrom "
+ ."$keytag $name\n";
+ &makeGenericRecord($name, 46, $rdata, $ttl, "", "");
+ }
+ }
+ }
+}
+
+exit(0);
+
+package Records;
+
+sub new {
+ my ($class, $dom) = @_;
+ $dom =~ tr/A-Z/a-z/;
+ my $self = { name => $dom, byType => {}, haveSEP => 0, haveZSK => 0,
+ locs => {}, zone => {} };
+ $self->{zone}->{$dom} = $self;
+ bless $self, $class;
+ return $self;
+}
+
+sub addKey {
+my ($self, $flags, $alg, $key) = @_;
+
+ if (!($flags & 0x100)) { return; }
+ if (!exists($main::keys{$key})) {
+ print STDERR "ERROR: encountered DNSKEY pseudo record without a matching key:\n";
+ print STDERR "$_\n";
+ exit 1;
+ }
+ if ($alg != 7 && $alg != 8 && $alg != 10) {
+ print STDERR "Warning: ignoring DNSKEY with unsupported algorithm $alg\n";
+ return;
+ }
+ my $entry = { flags => $flags, alg => $alg, pubkey => $key,
+ key => $main::keys{$key} };
+ if (exists($self->{keys})) {
+ foreach my $other (@{$self->{keys}}) {
+ if ($other->{flags} == $flags && $other->{alg} == $alg
+ && $other->{pubkey} eq $key) {
+ return;
+ }
+ }
+ push @{$self->{keys}}, $entry;
+ } else {
+ $self->{keys} = [$entry];
+ }
+ if ($flags & 1) {
+ $self->{haveSEP} = 1;
+ } else {
+ $self->{haveZSK} = 1;
+ }
+}
+
+sub addNS3P {
+my ($self, $alg, $flag, $iter, $salt, $ttl, $ts, $lo) = @_;
+
+ if ($flag ne "0") {
+ print STDERR "Warning: don't know about NSEC3 flags values != 0, ignoring\n";
+ }
+ if ($iter > 150) {
+ print STDERR "Warning: large iteration count $iter may lead to problems\n";
+ }
+ if ($alg ne "1") {
+ print STDERR "ERROR: NSEC3 algorithm $alg unknown. Please use algorithm 1.\n";
+ exit 1;
+ }
+
+ my $entry = { flags => $flag, alg => $alg, iterations => $iter,
+ salt => pack("H*", $salt), ttl => $ttl, ts => $ts, lo => $lo };
+ if (exists($self->{nsec3p})) {
+ print STDERR "Warning: ignoring additional NSEC3PARAM for ".$self->{name}."\n";
+# foreach my $other (@$self->{nsec3p}) {
+# if ($other->{flags} == $flag && $other->{alg} == $alg
+# && $other->{iterations} == $iter
+# && $other->{salt} eq $entry->{salt}) {
+# return;
+# }
+# }
+# push @$self->{nsec3p}, $entry;
+ } else {
+ $self->{nsec3p} = [$entry];
+ }
+}
+
+sub addRecord {
+my ($self, $type, $data, $ttl, $ts, $lo) = @_;
+
+ if (!defined($lo)) { $lo = ""; }
+ if (!defined($ttl) || $ttl eq "") { $ttl = 86400; }
+ $self->{locs}->{$lo} = 1;
+ my $entry = { type => $type, rdata => $data, ttl => $ttl, ts => $ts, lo => $lo };
+ if (!exists($self->{byType}->{$type})) {
+ $self->{byType}->{$type} = [$entry];
+ return;
+ }
+ foreach my $other (@{$self->{byType}->{$type}}) {
+ if ($other->{rdata} eq $data
+ && ($other->{lo} eq $lo || $lo eq "" || $other->{lo} eq "")
+ && (($other->{ttl} eq "0") != ($ttl eq "0"))) {
+ print STDERR "Warning: duplicate record ".$self->{name}.":$type:...\n";
+ return;
+ }
+ if (($other->{lo} eq $lo || $lo eq "" || $other->{lo} eq "")
+ && (($other->{ttl} eq "0") == ($ttl eq "0"))
+ && $other->{ttl} ne $ttl) {
+ print STDERR "Warning: ttl mismatch for ".$self->{name}.":$type:...\n";
+ return;
+ }
+ }
+ push @{$self->{byType}->{$type}}, $entry;
+}
+
+sub addA {
+my ($self, $ip, $ttl, $ts, $lo) = @_;
+
+ if ($ip !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
+ print STDERR "Warning: ignoring unparsable IPv4 address '$ip'\n";
+ return;
+ }
+ $self->addRecord(1, pack("C*", $1, $2, $3, $4), $ttl, $ts, $lo);
+}
+
+sub addNS {
+my ($self, $ns, $ttl, $ts, $lo) = @_;
+
+ $self->addRecord(2, &toDomain($ns), $ttl || 259200, $ts, $lo);
+}
+
+sub addCNAME {
+my ($self, $nm, $ttl, $ts, $lo) = @_;
+
+ $self->addRecord(5, &toDomain($nm), $ttl, $ts, $lo);
+}
+
+sub addSOA {
+my ($self, $ns, $hm, $ser, $ref, $ret, $exp, $min, $ttl, $ts, $lo) = @_;
+
+ $self->addRecord(6, &toDomain($ns).&toDomain($hm)
+ .pack("N*", $ser || 1, $ref || 16384, $ret || 2048,
+ $exp || 1048576, $min || 2560),
+ $ttl || 2560, $ts, $lo);
+}
+
+sub addPTR {
+my ($self, $nm, $ttl, $ts, $lo) = @_;
+
+ $self->addRecord(12, &toDomain($nm), $ttl, $ts, $lo);
+}
+
+sub addMX {
+my ($self, $mx, $dist, $ttl, $ts, $lo) = @_;
+
+ $self->addRecord(15, pack("n", $dist || 0).&toDomain($mx), $ttl, $ts, $lo);
+}
+
+sub addTXT {
+my ($self, $txt, $ttl, $ts, $lo) = @_;
+
+ my $cstr = "";
+ while ((my $l = length($txt)) > 0) {
+ if ($l > 255) { $l = 255; }
+ $cstr .= chr($l).substr($txt, 0, $l);
+ $txt = substr($txt, $l);
+ }
+ $self->addRecord(16, $cstr, $ttl, $ts, $lo);
+}
+
+sub addAAAA {
+my ($self, $ip, $ttl, $ts, $lo) = @_;
+
+ if ($ip !~ /^[0-9a-f]{32}$/i) {
+ print STDERR "Warning: ignoring unparsable IPv6 address '$ip'\n";
+ return;
+ }
+ $self->addRecord(28, pack("H*", $ip), $ttl, $ts, $lo);
+}
+
+sub toDomain {
+my $name = shift;
+
+ $name =~ tr/A-Z/a-z/;
+ my $res = "";
+ while ($name =~ /^([^.]*)\./) {
+ $name = $';
+ $res .= chr(length($1)).$1;
+ }
+ return $res.chr(length($name)).$name."\0";
+}
+
+package main;
+
+sub usage {
+ print STDERR "$0 -g <bits> <flags> <algorithm> <domain> <keyfile>\n";
+ print STDERR " or\n";
+ print STDERR "$0 [-t <ttl>] [<keyfile> ...] <input >output\n";
+ exit(1);
+}
+
+sub findControl {
+my $rec = shift;
+
+ if (exists($rec->{control})) { return; }
+
+ my $dom = $rec->{name};
+ my $soa = exists($rec->{byType}->{6});
+ if ($soa) {
+ $rec->{control} = $dom;
+ $rec->{zone}->{$dom} = $rec;
+ push @main::zones, $dom;
+ return;
+ }
+
+ my $anc = $dom;
+ while ($anc =~ /^[^.]*\./) {
+ $anc = $';
+ if ($dom !~ /^\*\./ && exists($main::names{"*.$anc"})
+ && !exists($main::names{"*.$dom"})) {
+ print STDERR "Warning: wildcard *.$anc shadowed by $dom (see RFC-1034 sect. 4.3.3)!\n";
+ }
+ if (exists($main::names{$anc})) {
+ my $ctl_rec = $main::names{$anc};
+ &findControl($ctl_rec);
+ if (!exists($ctl_rec->{control})) {
+ if (!$soa) { print STDERR "Warning: Out-of-bailiwick name '$dom' (oob parent)\n"; }
+ return;
+ }
+ my $control = $ctl_rec->{control};
+ if ($ctl_rec->{byType}->{2}) { $control = $anc; }
+ $ctl_rec = $main::names{$control};
+ if (!$ctl_rec->{byType}->{6}) {
+ # Most likely glue...
+ if (!exists($rec->{byType}->{1})
+ && !exists($rec->{byType}->{28})) {
+ print STDERR "Warning: Out-of-bailiwick name '$dom' (below subdelegation)\n";
+ }
+ } else {
+ $rec->{control} = $control;
+ $ctl_rec->{zone}->{$dom} = $rec;
+ }
+ return;
+ }
+ }
+ print STDERR "Warning: Out-of-bailiwick name '$dom' (no parent)\n";
+}
+
+sub parseData {
+my $in = shift;
+
+ my $res = "";
+ while ($in =~ /^(.*?)\\(\d\d\d)/) {
+ $in = $';
+ $res .= $1.chr(oct($2));
+ }
+ return $res.$in;
+}
+
+sub makeGenericRecord {
+my ($dom, $type, $rdata, $ttl, $ts, $lo) = @_;
+
+ print ":$dom:$type:";
+ while (length($rdata)) {
+ my $char = substr($rdata, 0, 1);
+ $rdata = substr($rdata, 1);
+ if ($char =~ /[0-9a-zA-Z +*.,\/=<>\@\$-]/) {
+ print $char;
+ } else {
+ printf "\\%03o", ord($char);
+ }
+ }
+ print ":$ttl:$ts:$lo\n";
+}
+
+sub getOrCreateRRs {
+my $dom = shift;
+
+ $dom =~ tr/A-Z/a-z/;
+ if (!exists($main::names{$dom})) {
+ $main::names{$dom} = new Records($dom);
+ }
+ return $main::names{$dom};
+}
+
+sub htons {
+my $n = shift;
+
+ return chr($n >> 8).chr($n & 0xff);
+}
+
+sub htonl {
+my $n = shift;
+
+ return &htons($n >> 16).&htons($n & 0xffff);
+}
+
+sub wireName {
+my $name = shift;
+
+ my $wire = "";
+ while ($name =~ /^([^.]+)/) {
+ $wire .= chr(length($1)).$1;
+ $name = ($name eq $1) ? "" : substr($name, length($1) + 1);
+ }
+ return $wire."\0";
+}
+
+sub nsec3hash {
+my ($name, $iter, $salt) = @_;
+
+ my $dig = &wireName($name);
+ while ($iter-- >= 0) {
+ $dig = sha1($dig.$salt);
+ }
+ return $dig;
+}
+
+sub base32hex {
+my $data = shift;
+
+ my $buf = 0;
+ my $bits = 0;
+ my $res = "";
+ while (length($data) > 0) {
+ $buf = ($buf << 8) | ord($data);
+ $data = substr($data, 1);
+ $bits += 8;
+ while ($bits >= 5) {
+ my $dig = ($buf >> ($bits-5)) & 0x1f;
+ $bits -= 5;
+ if ($dig < 10) {
+ $res .= $dig;
+ } else {
+ $res .= chr($dig + 87);
+ }
+ }
+ }
+ if ($bits > 0) {
+ $buf <<= (5 - $bits);
+ $buf &= 0x1f;
+ if ($buf < 10) {
+ $res .= $buf;
+ } else {
+ $res .= chr($buf + 87);
+ }
+ $bits += 3;
+ while ($bits > 0) {
+ if ($bits < 5) { $bits += 8; }
+ else { $res .= "="; $bits -= 5; }
+ }
+ }
+ return $res;
+}
+
+sub tai2unix {
+my $ts = shift;
+
+ $ts =~ s/^40*//;
+ return "0x$ts" - 10;
+}
+
+sub genTypeBitmaps {
+ my %windows = ();
+ if ($#_ > 0 || $#_ == 0 && $_[0] != 2) {
+ # The type bitmaps includes all types except those contributed by NSEC3
+ # itself, including the signature for the NSEC3. I. e. if there's at
+ # least one record here (except for a single NS delegation), there'll
+ # also be an RRSIG here, eventually.
+ push @_, 46;
+ @_ = sort @_;
+ }
+ foreach my $type (@_) {
+ my $window = $type >> 8;
+ $type &= 0xff;
+ if (exists($windows{$window})) {
+ push @{$windows{$window}}, $type;
+ } else {
+ $windows{$window} = [$type];
+ }
+ }
+ my $tbm = "";
+ foreach my $window (sort keys %windows) {
+ my $wbm = "\0" x 32;
+ foreach my $type (@{$windows{$window}}) {
+ substr($wbm, $type >> 3, 1) = chr(ord(substr($wbm, $type >> 3, 1)) | 1 << (7 - ($type & 7)));
+ }
+ $wbm =~ s/\0+$//;
+ $tbm .= chr($window).chr(length($wbm)).$wbm;
+ }
+ return $tbm;
+}
+
+sub gen_pubkey_data {
+my ($n, $e, $flags, $alg, $dom) = @_;
+
+ my $e_bin = $e->to_bin();
+ $e_bin =~ s/^\0+//;
+ my $n_bin = $n->to_bin();
+ $n_bin =~ s/^\0+//;
+
+ my $pubkey = chr(length($e_bin)).$e_bin.$n_bin;
+ my $keytag = $flags + $alg + 3 * 256; # protocol is always 3
+ for (my $i = length($pubkey) - 1; $i >= 0; $i--) {
+ $keytag += ord(substr($pubkey, $i)) * (($i & 1) ? 1 : 256);
+ }
+ if ($alg > 0) {
+ $keytag += $keytag >> 16;
+ $keytag &= 0xffff;
+ }
+ $dom =~ tr/A-Z/a-z/;
+ my $digest = sha1_hex(&wireName($dom).&htons($flags).chr(3).chr($alg).$pubkey);
+ return ($keytag, $pubkey, $digest);
+}
+
+sub addA {
+my ($dom, $ip, $ttl, $ts, $lo) = @_;
+
+ if ($ip eq "") { return; }
+ my $rec = &getOrCreateRRs($dom);
+ $rec->addA($ip, $ttl, $ts, $lo);
+}
+
+sub addPTR {
+my ($ip, $dom, $ttl, $ts, $lo) = @_;
+
+ my $arpa;
+ if ($ip =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
+ $arpa = "$4.$3.$2.$1.in-addr.arpa";
+ } elsif ($ip =~ /^[0-9a-f]{32}$/i) {
+ $ip =~ tr/A-Z/a-z/;
+ $arpa = "ip6.arpa";
+ while ($ip =~ /^./) {
+ $ip = $';
+ $arpa = "$&.$arpa";
+ }
+ } else {
+ print STDERR "Warning: ignoring unparsable IP address '$ip'\n";
+ return;
+ }
+ my $rec = getOrCreateRRs($arpa);
+ $rec->addPTR($dom, $ttl, $ts, $lo);
+}
+
+sub addKey {
+my ($file) = @_;
+
+ open(KEYFILE, "<$file") or die("Can't read $file either:");
+ my $rsa = Crypt::OpenSSL::RSA->new_private_key(join("", <KEYFILE>));
+ close KEYFILE;
+ if (!$rsa) {
+ print STDERR "Failed to read key from $file!\n";
+ exit(1);
+ }
+ my ($n, $e, $x1, $x2, $x3, $x4, $x5, $x6) = $rsa->get_key_parameters();
+ $x1 = $x2 = $x3 = $x4 = $x5 = $x6 = 0; # get rid of private stuff
+ my ($keytag, $keydata, $fp) = &gen_pubkey_data($n, $e, 0, 0, "");
+ $main::keys{$keydata} = { key => $rsa, basetag => $keytag };
+}
+
+sub genkey {
+my ($bits, $flags, $alg, $dom, $file) = @_;
+
+ if ($bits < 1024 || $bits > 4096) {
+ print STDERR "ERROR: Keys of less than 1024 or more than 4096 bits are not supported!\n";
+ exit 1;
+ }
+
+ if ($alg != 7 && $alg != 8 && $alg != 10) {
+ print STDERR "ERROR: $0 only supports algorithms 7 (RSA-SHA1), 8 (RSA-SHA256)\nand 10 (RSA-SHA512).\n";
+ exit 1;
+ }
+
+ if (sysopen(KEYFILE, $file, O_CREAT|O_EXCL|O_WRONLY, 0600)) {
+ my $rsa = Crypt::OpenSSL::RSA->generate_key($bits);
+ print KEYFILE $rsa->get_private_key_string();
+ my ($n, $e, $x1, $x2, $x3, $x4, $x5, $x6) = $rsa->get_key_parameters();
+ $x1 = $x2 = $x3 = $x4 = $x5 = $x6 = 0; # get rid of private stuff
+ my ($keytag, $keydata, $fp) = &gen_pubkey_data($n, $e, $flags, $alg, $dom);
+ print KEYFILE "#K$dom:$flags:3:$alg:".encode_base64($keydata, "").":::\n";
+ print KEYFILE "#D$dom:$keytag:$alg:1:${fp}:::\n";
+ close KEYFILE;
+ } else {
+ print STDERR "Warning: couldn't create $file: $!\n";
+ print STDERR "Attempting to read key...\n";
+ open(KEYFILE, "<$file") or die("Can't read $file either:");
+ my $rsa = Crypt::OpenSSL::RSA->new_private_key(join("", <KEYFILE>));
+ close KEYFILE;
+ if (!$rsa) {
+ print STDERR "Failed to read key from $file!\n";
+ exit(1);
+ }
+ my ($n, $e, $x1, $x2, $x3, $x4, $x5, $x6) = $rsa->get_key_parameters();
+ $x1 = $x2 = $x3 = $x4 = $x5 = $x6 = 0; # get rid of private stuff
+ my ($keytag, $keydata, $fp) = &gen_pubkey_data($n, $e, $flags, $alg, $dom);
+ print "#K$dom:$flags:3:$alg:".encode_base64($keydata, "").":::\n";
+ print "#D$dom:$keytag:$alg:1:${fp}:::\n";
+ }
+ exit 0;
+}
+
+=pod
+
+=head1 NAME
+
+tinydns-sign - Signs records in L<tinydns-data(8)> files
+
+=head1 SYNOPSIS
+
+ tinydns-sign -g bits flags algorithm domain keyfile
+
+ tinydns-sign [-t ttl] [keyfile ...] <infile >outfile
+
+=head1 DESCRIPTION
+
+The first form is used to generate a public/private RSA key pair with a
+modulus of length I<bits>. If F<keyfile> exists, tinydns-sign will try to
+read a private key from the file and print DS and DNSKEY pseudo-records for
+the corresponding public key on stdout. If F<keyfile> does not exist,
+tinydns-sign will generate a new key pair and write the key plus the
+corresponding pseudo-records to F<keyfile>.
+
+In the second form, tinydns-sign reads key pairs from each given F<keyfile>.
+It then reads a L<tinydns-data(8)> file from STDIN and writes the same
+file to STDOUT, with the following modifications:
+
+=over
+
+=item * It will delete all generic records with RRTYPE DS (43), RRSIG (46),
+DNSKEY (48), NSEC3(50), NSEC3PARAM(51) and private types 65281 and 65282.
+
+=item * It will turn each . record into a Z record and a & record.
+
+=item * It will adjust the serial number of all Z records to the current time,
+unless the serial number begins with two zeroes. Note that an SOA must have a
+fixed serial for generating a matching RRSIG record.
+
+=item * It will create new DS, DNSKEY and NSEC3PARAM records from each
+corresponding pseudo record (see below) present in the file.
+
+=item * It will create NSEC3 records for all names in all zones that have at
+least one DNSKEY and NSEC3PARAM in the file.
+
+=item * It will create a generic record with type 65281 for each name
+(including empty non-terminals) containing the owner of its matching NSEC3 RR.
+
+=item * It will create generic records with type 65282 for each hex digit (i.
+e. 0-9a-f) below the zone apex containing all NSEC3 hashes starting with that
+digit.
+
+=item * It will create RRSIG records for all RR-sets in all zones that have at
+least one DNSKEY in the file. If both DNSKEYS with and without the SEP flag set
+are present, then those with the SEP flag will be used only for RRSIGs on
+DNSKEY RRs and those without the SEP flag will be used for the remaining
+RR-sets. Otherwise, RRSIGs will be created using all DNSKEYs.
+
+RRSIGs will be valid beginning one hour in the past and ending at (now + I<ttl>)
+seconds. I<ttl> defaults to 432000 (5 days).
+
+=back
+
+=head2 Pseudo-Records
+
+Pseudo-records are records defined in a syntax that's only understood by
+tinydns-sign. To L<tinydns-data(8)> they look like comments, i. e. they are
+ignored.
+
+tinydns-sign will create one or more generic records for each pseudo-record.
+All generic records with an RR-type for which a pseudo-record can be defined
+are deleted from the input. (Otherwise, removing a pseudo-record would not
+result in removal of the corresponding generic record.)
+
+In contrast to standard tinydns-data behaviour, trailing colons in
+pseudo-records are B<not> optional.
+
+Currently, pseudo-records are defined for the following RR-types:
+
+=over
+
+=item * #Kname:flags:proto:algorithm:key:ttl:timestamp:lo
+
+This generates a DNSKEY record for I<name>. I<flags>. I<proto> and I<algorithm>
+are decimal numbers. At the time of writing, I<proto> must be 3. tinydns-sign
+only supports I<algorithm>s 7 (RSA-SHA1), 8 (RSA-SHA256) and 10 (RSA-SHA512).
+I<key> is base-64 encoded key material, depending on the selected
+I<algorithm>. I<ttl>, I<timestamp> and I<lo> are as usual.
+
+It is an error to have a DNSKEY pseudo-record in the input without a
+corresponding F<keyfile> containing the matching private key.
+
+=item * #Dname:tag:algorithm:digest:fingerprint:ttl:timestamp:lo
+
+This generates a DS record for I<name>. I<tag> is the key tag, I<algorithm>
+specifies the algorithm of the referenced key and I<digest> is the digest type
+(all in decimal). I<fingerprint> is the hex-encoded actual digest value
+(omitting leading/trailing zeroes is not permitted!). I<ttl>, I<timestamp> and
+I<lo> are as usual.
+
+=item * #Pname:algorithm:flags:iter:len:salt:ttl:timestamp:lo
+
+This generates an NSEC3PARAM record for I<name> with the given I<algorithm>,
+I<flags>, I<iter>ation count, salt I<len>gth and I<salt>. If I<salt> is empty,
+a new random salt with the given salt I<len>gth (4 bytes if I<len> is empty)
+will be generated. If I<salt> is non-empty, it must be a string of hex digits
+with even length. The salt length is derived from the given salt value, i. e.
+I<len> is ignored in that case. I<ttl>, I<timestamp> and I<lo> are as usual.
+
+tinydns-sign currently only supports I<algorithm> 1 (SHA-1). At the time of
+writing, I<flags> is defined to be 0, and the I<iter>ation count is limited
+depending on the key length (see L<RFC-5155>).
+
+=back
+
+=head1 EXIT STATUS
+
+tinydns-sign will exit with status 0 if it thinks all went well. Warning
+messages will not trigger a nonzero exit status.
+
+tinydns-sign will exit with nonzero status if an error occurred. In this case,
+the output is most likely incomplete and should not be used to replace an
+input file.
+
+=head1 SEE ALSO
+
+L<tinydns-data(8)>,
+L<RFC-4034|http://tools.ietf.org/html/rfc4034>,
+L<RFC-4035|http://tools.ietf.org/html/rfc4035>,
+L<RFC-5155|http://tools.ietf.org/html/rfc5155>
+
+=head1 LIMITATIONS
+
+=over
+
+=item * Location code handling is incomplete in that location codes must be
+present for all RRs in a zone, or for none at all.
+
+=item * Timestamps are currently mostly ignored, i. e. signatures will happily
+outlive the RR-sets which they sign.
+
+=item * It is currently not possible to protect the private keys with a
+passphrase.
+
+=item * It is not possible to have a signed zone and a signed child zone in
+the same data file.
+
+=item * NSEC3 RRs with Opt-Out child zones are not supported.
+
+=item * The pseudo-RRs with type 65282 contain a list of hash values. The list
+cannot grow bigger than 65kBytes (about 3270 hashes). This is not a problem
+for a typical domain, but it would be a problem if tinydns were to serve the
+.de zone, for example. Also, the list is searched sequentially, which can
+cause a performance impact long before this limit is reached.
+
+=back
+
+=head1 CAVEATS
+
+=over
+
+=item * The system clock should be reasonably close to UTC (i. e. within a few minutes).
+
+=item * Stock tinydns/axfrdns will happily work with signed data.cdb files,
+but they will not produce correct DNSSEC responses!
+
+=item * If a zone contains both keys with and without the SEP flag, you must
+make sure that both key sets cover the same set of algorithms. This is due to
+a requirement in RFC-4035 section 2.2.
+
+=back
+
+=head1 AUTHOR
+
+(C) 2012 Peter Conrad L<mailto:conrad@quisquis.de>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 3 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+=cut
+