about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2023-12-10 11:48:01 +0000
committerLaurent Bercot <ska@appnovation.com>2023-12-10 11:48:01 +0000
commitb8d0f83e6cea9640a7ee4402c163ad812237355d (patch)
tree57a64ac8aa0e98c40db8c36e96e7379490e44dbf
downloadshibari-b8d0f83e6cea9640a7ee4402c163ad812237355d.tar.gz
shibari-b8d0f83e6cea9640a7ee4402c163ad812237355d.tar.xz
shibari-b8d0f83e6cea9640a7ee4402c163ad812237355d.zip
Initial commit
Signed-off-by: Laurent Bercot <ska@appnovation.com>
-rw-r--r--.gitignore12
-rw-r--r--AUTHORS5
-rw-r--r--CONTRIBUTING5
-rw-r--r--COPYING13
-rw-r--r--DCO37
-rw-r--r--INSTALL175
-rw-r--r--Makefile148
-rw-r--r--NEWS6
-rw-r--r--README23
-rw-r--r--README.macos3
-rw-r--r--README.solaris11
-rwxr-xr-xconfigure459
-rw-r--r--doc/index.html181
-rw-r--r--doc/shibari-server-tcp.html213
-rw-r--r--doc/shibari-server-udp.html163
-rw-r--r--doc/upgrade.html28
-rw-r--r--package/deps-build3
-rw-r--r--package/deps.mak66
-rw-r--r--package/info4
-rw-r--r--package/modes2
-rw-r--r--package/targets.mak7
-rwxr-xr-xpatch-for-solaris21
-rw-r--r--src/cache/dcache-internal.h18
-rw-r--r--src/cache/dcache_add.c85
-rw-r--r--src/cache/dcache_clean_expired.c20
-rw-r--r--src/cache/dcache_delete.c19
-rw-r--r--src/cache/dcache_free.c22
-rw-r--r--src/cache/dcache_init.c55
-rw-r--r--src/cache/dcache_load.c81
-rw-r--r--src/cache/dcache_save.c73
-rw-r--r--src/cache/dcache_search.c15
-rw-r--r--src/cache/deps-lib/dcache8
-rw-r--r--src/common/deps-lib/shibari-common10
-rw-r--r--src/common/shibari_log_answer.c36
-rw-r--r--src/common/shibari_log_exit.c14
-rw-r--r--src/common/shibari_log_query.c18
-rw-r--r--src/common/shibari_log_queryplus.c24
-rw-r--r--src/common/shibari_log_start.c17
-rw-r--r--src/common/shibari_util_get_prefixlen.c14
-rw-r--r--src/common/shibari_util_qtype_num.c121
-rw-r--r--src/common/shibari_util_qtype_str.c285
-rw-r--r--src/common/shibari_util_rcode_str.c41
-rw-r--r--src/include/shibari/cache.h8
-rw-r--r--src/include/shibari/client.h6
-rw-r--r--src/include/shibari/common.h9
-rw-r--r--src/include/shibari/constants.h109
-rw-r--r--src/include/shibari/dcache.h54
-rw-r--r--src/include/shibari/log.h20
-rw-r--r--src/include/shibari/packet.h39
-rw-r--r--src/include/shibari/server.h10
-rw-r--r--src/include/shibari/shibari.h11
-rw-r--r--src/include/shibari/tdb.h29
-rw-r--r--src/include/shibari/util.h14
-rw-r--r--src/server/deps-exe/shibari-server-tcp4
-rw-r--r--src/server/deps-exe/shibari-server-udp6
-rw-r--r--src/server/deps-lib/shibari-server13
-rw-r--r--src/server/shibari-server-tcp.c235
-rw-r--r--src/server/shibari-server-udp.c207
-rw-r--r--src/server/shibari_packet_add_glue.c48
-rw-r--r--src/server/shibari_packet_add_rr.c46
-rw-r--r--src/server/shibari_packet_assert_authority.c18
-rw-r--r--src/server/shibari_packet_begin.c32
-rw-r--r--src/server/shibari_packet_end.c13
-rw-r--r--src/server/shibari_packet_init.c14
-rw-r--r--src/server/shibari_packet_tdb_answer_query.c93
-rw-r--r--src/server/shibari_tdb_entry_parse.c56
-rw-r--r--src/server/shibari_tdb_extract_domain.c17
-rw-r--r--src/server/shibari_tdb_find_authority.c46
-rw-r--r--src/server/shibari_tdb_read_entry.c22
-rwxr-xr-xtools/gen-deps.sh93
-rwxr-xr-xtools/install.sh64
71 files changed, 3897 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0552150
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+*.o
+*.lo
+/config.mak
+/src/include/shibari/config.h
+/libdcache.a.xyzzy
+/libdcache.so.xyzzy
+/libshibari-common.a.xyzzy
+/libshibari-common.so.xyzzy
+/libshibari-server.a.xyzzy
+/libshibari-server.so.xyzzy
+/shibari-server-tcp
+/shibari-server-udp
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..6bbf321
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,5 @@
+Main author:
+  Laurent Bercot <ska-skaware@skarnet.org>
+
+Thanks to:
+  Dan J. Bernstein <djb@cr.yp.to>
diff --git a/CONTRIBUTING b/CONTRIBUTING
new file mode 100644
index 0000000..6279422
--- /dev/null
+++ b/CONTRIBUTING
@@ -0,0 +1,5 @@
+ Please add a Signed-Off-By: line at the end of your commit,
+which certifies that you have the right and authority to pass
+it on as an open-source patch, as explicited in the Developer's
+Certificate of Origin available in this project's DCO file,
+or at https://developercertificate.org/
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..fe81b1d
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,13 @@
+Copyright (c) 2023 Laurent Bercot <ska-skaware@skarnet.org>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/DCO b/DCO
new file mode 100644
index 0000000..8201f99
--- /dev/null
+++ b/DCO
@@ -0,0 +1,37 @@
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+1 Letterman Drive
+Suite D4700
+San Francisco, CA, 94129
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..5e2d943
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,175 @@
+Build Instructions
+------------------
+
+* Requirements
+  ------------
+
+  - A POSIX-compliant C development environment
+  - GNU make version 3.81 or later
+  - skalibs version 2.14.0.1 or later: https://skarnet.org/software/skalibs/
+  - s6 version 2.12.0.2 or later: https://skarnet.org/software/s6/
+  - for now: s6-dns version 2.3.7.0 or later: https://skarnet.org/software/s6-dns/
+
+ This software will run on any operating system that implements
+POSIX.1-2008, available at:
+  https://pubs.opengroup.org/onlinepubs/9699919799/
+
+
+* Standard usage
+  --------------
+
+  ./configure && make && sudo make install
+
+ will work for most users.
+ It will install the binaries in /usr/bin and the static libraries in
+/usr/lib/shibari.
+
+ Please note that static libraries in /usr/lib/shibari *will not*
+be found by a default linker invocation: you need -L/usr/lib/shibari.
+Other skarnet.org software automatically handles that case if the
+default configuration is used, but if you change the configuration,
+remember to use the appropriate --with-lib configure option.
+
+ You can strip the binaries and libraries of their extra symbols via
+"make strip" before the "make install" phase. It will shave a few bytes
+off them.
+
+
+* Customization
+  -------------
+
+ You can customize paths via flags given to configure.
+ See ./configure --help for a list of all available configure options.
+
+
+* Environment variables
+  ---------------------
+
+ Controlling a build process via environment variables is a big and
+dangerous hammer. You should try and pass flags to configure instead;
+nevertheless, a few standard environment variables are recognized.
+
+ If the CC environment variable is set, its value will override compiler
+detection by configure. The --host=HOST option will still add a HOST-
+prefix to the value of CC.
+
+ The values of CFLAGS, CPPFLAGS and LDFLAGS will be appended to flags
+auto-detected by configure. To entirely override the flags set by
+configure instead, use make variables.
+
+
+* Make variables
+  --------------
+
+ You can invoke make with a few variables for more configuration.
+
+ CC, CFLAGS, CPPFLAGS, LDFLAGS, LDLIBS, AR, RANLIB, STRIP, INSTALL and
+CROSS_COMPILE can all be overridden on the make command line. This is
+an even bigger hammer than running ./configure with environment
+variables, so it is advised to only do this when it is the only way of
+obtaining the behaviour you want.
+
+ DESTDIR can be given on the "make install" command line in order to
+install to a staging directory.
+
+
+* Shared libraries
+  ----------------
+
+ Software from skarnet.org is small enough that shared libraries are
+generally not worth using. Static linking is simpler and incurs less
+runtime overhead and less points of failure: so by default, shared
+libraries are not built and binaries are linked against the static
+versions of the skarnet.org libraries. Nevertheless, you can:
+  * build shared libraries: --enable-shared
+  * link binaries against shared libraries: --disable-allstatic
+
+
+* Static binaries
+  ---------------
+
+ By default, binaries are linked against static versions of all the
+libraries they depend on, except for the libc. You can enforce
+linking against the static libc with --enable-static-libc.
+
+ (If you are using a GNU/Linux system, be aware that the GNU libc
+behaves badly with static linking and produces huge executables,
+which is why it is not the default. Other libcs are better suited
+to static linking, for instance musl: http://musl-libc.org/)
+
+
+* Cross-compilation
+  -----------------
+
+ skarnet.org packages centralize all the difficulty of
+cross-compilation in one place: skalibs. Once you have
+cross-compiled skalibs, the rest is easy.
+
+ * Use the --host=HOST option to configure, HOST being the triplet
+for your target.
+ * Make sure your cross-toolchain binaries (i.e. prefixed with HOST-)
+are accessible via your PATH environment variable.
+ * Make sure to use the correct version of skalibs for your target,
+and the correct sysdeps directory, making use of the
+--with-include, --with-lib, --with-dynlib and --with-sysdeps
+options as necessary.
+
+
+* The slashpackage convention
+  ---------------------------
+
+ The slashpackage convention (http://cr.yp.to/slashpackage.html)
+is a package installation scheme that provides a few guarantees
+over other conventions such as the FHS, for instance fixed
+absolute pathnames. skarnet.org packages support it: use the
+--enable-slashpackage option to configure, or
+--enable-slashpackage=DIR for a prefixed DIR/package tree.
+This option will activate slashpackage support during the build
+and set slashpackage-compatible installation directories.
+If $package_home is the home of the package, defined as
+DIR/package/$category/$package-$version with the variables
+read from the package/info file, then:
+
+  --dynlibdir is set to $package_home/library.so
+  --bindir is set to $package_home/command
+  --sbindir is also set to $package_home/command (slashpackage
+differentiates root-only binaries by their Unix rights, not their
+location in the filesystem)
+  --libexecdir is also set to $package_home/command (slashpackage
+does not need a specific directory for internal binaries)
+  --libdir is set to $package_home/library
+  --includedir is set to $package_home/include
+
+ --prefix is pretty much ignored when you use --enable-slashpackage.
+You should probably not use both --enable-slashpackage and --prefix.
+
+ When using slashpackage, two additional Makefile targets are
+available after "make install":
+ - "make update" changes the default version of the software to the
+freshly installed one. (This is useful when you have several installed
+versions of the same software, which slashpackage supports.)
+ - "make -L global-links" adds links from /command and /library.so to the
+default version of the binaries and shared libraries. The "-L" option to
+make is necessary because targets are symbolic links, and the default make
+behaviour is to check the pointed file's timestamp and not the symlink's
+timestamp.
+
+
+* Absolute pathnames
+  ------------------
+
+ You may want to use fixed absolute pathnames even if you're not
+following the slashpackage convention: for instance, the Nix packaging
+system prefers calling binaries with immutable paths rather than rely on
+PATH resolution. If you are in that case, use the --enable-absolute-paths
+option to configure. This will ensure that programs calling binaries from
+this package will call them with their full installation path (in bindir)
+without relying on a PATH search.
+
+
+* Out-of-tree builds
+  ------------------
+
+ skarnet.org packages do not support out-of-tree builds. They
+are small, so it does not cost much to duplicate the entire
+source tree if parallel builds are needed.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b82361b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,148 @@
+#
+# This Makefile requires GNU make.
+#
+# Do not make changes here.
+# Use the included .mak files.
+#
+
+it: all
+
+make_need := 3.81
+ifeq "" "$(strip $(filter $(make_need), $(firstword $(sort $(make_need) $(MAKE_VERSION)))))"
+fail := $(error Your make ($(MAKE_VERSION)) is too old. You need $(make_need) or newer)
+endif
+
+CC = $(error Please use ./configure first)
+
+STATIC_LIBS :=
+SHARED_LIBS :=
+INTERNAL_LIBS :=
+EXTRA_TARGETS :=
+LIB_DEFS :=
+
+define library_definition
+LIB$(firstword $(subst =, ,$(1))) := lib$(lastword $(subst =, ,$(1))).$(if $(DO_ALLSTATIC),a,so).xyzzy
+ifdef DO_SHARED
+SHARED_LIBS += lib$(lastword $(subst =, ,$(1))).so.xyzzy
+endif
+ifdef DO_STATIC
+STATIC_LIBS += lib$(lastword $(subst =, ,$(1))).a.xyzzy
+endif
+endef
+
+-include config.mak
+include package/targets.mak
+
+$(foreach var,$(LIB_DEFS),$(eval $(call library_definition,$(var))))
+
+include package/deps.mak
+
+version_m := $(basename $(version))
+version_M := $(basename $(version_m))
+version_l := $(basename $(version_M))
+CPPFLAGS_ALL := $(CPPFLAGS_AUTO) $(CPPFLAGS)
+CFLAGS_ALL := $(CFLAGS_AUTO) $(CFLAGS)
+ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
+CFLAGS_SHARED := -fPIC
+else
+CFLAGS_SHARED :=
+endif
+LDFLAGS_ALL := $(LDFLAGS_AUTO) $(LDFLAGS)
+AR := $(CROSS_COMPILE)ar
+RANLIB := $(CROSS_COMPILE)ranlib
+STRIP := $(CROSS_COMPILE)strip
+INSTALL := ./tools/install.sh
+
+ALL_BINS := $(LIBEXEC_TARGETS) $(BIN_TARGETS)
+ALL_LIBS := $(SHARED_LIBS) $(STATIC_LIBS) $(INTERNAL_LIBS)
+ALL_INCLUDES := $(wildcard src/include/$(package)/*.h)
+
+all: $(ALL_LIBS) $(ALL_BINS) $(ALL_INCLUDES) $(EXTRA_INCLUDES)
+
+clean:
+	@exec rm -f $(ALL_LIBS) $(ALL_BINS) $(wildcard src/*/*.o src/*/*.lo) $(EXTRA_TARGETS)
+
+distclean: clean
+	@exec rm -f config.mak src/include/$(package)/config.h
+
+tgz: distclean
+	@. package/info && \
+	rm -rf /tmp/$$package-$$version && \
+	cp -a . /tmp/$$package-$$version && \
+	cd /tmp && \
+	tar -zpcv --owner=0 --group=0 --numeric-owner --exclude=.git* -f /tmp/$$package-$$version.tar.gz $$package-$$version && \
+	exec rm -rf /tmp/$$package-$$version
+
+strip: $(ALL_LIBS) $(ALL_BINS)
+ifneq ($(strip $(STATIC_LIBS)),)
+	exec $(STRIP) -x -R .note -R .comment $(STATIC_LIBS)
+endif
+ifneq ($(strip $(ALL_BINS)$(SHARED_LIBS)),)
+	exec $(STRIP) -R .note -R .comment $(ALL_BINS) $(SHARED_LIBS)
+endif
+
+install: install-dynlib install-libexec install-bin install-lib install-include
+install-dynlib: $(SHARED_LIBS:lib%.so.xyzzy=$(DESTDIR)$(dynlibdir)/lib%.so)
+install-libexec: $(LIBEXEC_TARGETS:%=$(DESTDIR)$(libexecdir)/%)
+install-bin: $(BIN_TARGETS:%=$(DESTDIR)$(bindir)/%)
+install-lib: $(STATIC_LIBS:lib%.a.xyzzy=$(DESTDIR)$(libdir)/lib%.a)
+install-include: $(ALL_INCLUDES:src/include/$(package)/%.h=$(DESTDIR)$(includedir)/$(package)/%.h) $(EXTRA_INCLUDES:src/include/%.h=$(DESTDIR)$(includedir)/%.h)
+
+ifneq ($(exthome),)
+
+$(DESTDIR)$(exthome): $(DESTDIR)$(home)
+	exec $(INSTALL) -l $(notdir $(home)) $(DESTDIR)$(exthome)
+
+update: $(DESTDIR)$(exthome)
+
+global-links: $(DESTDIR)$(exthome) $(SHARED_LIBS:lib%.so.xyzzy=$(DESTDIR)$(sproot)/library.so/lib%.so.$(version_M)) $(BIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%)
+
+$(DESTDIR)$(sproot)/command/%: $(DESTDIR)$(home)/command/%
+	exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/command/$(<F) $@
+
+$(DESTDIR)$(sproot)/library.so/lib%.so.$(version_M): $(DESTDIR)$(dynlibdir)/lib%.so.$(version_M)
+	exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/library.so/$(<F) $@
+
+.PHONY: update global-links
+
+endif
+
+$(DESTDIR)$(dynlibdir)/lib%.so $(DESTDIR)$(dynlibdir)/lib%.so.$(version_M): lib%.so.xyzzy
+	$(INSTALL) -D -m 755 $< $@.$(version) && \
+	$(INSTALL) -l $(@F).$(version) $@.$(version_M) && \
+	exec $(INSTALL) -l $(@F).$(version_M) $@
+
+$(DESTDIR)$(libexecdir)/% $(DESTDIR)$(bindir)/%: % package/modes
+	exec $(INSTALL) -D -m 600 $< $@
+	grep -- ^$(@F) < package/modes | { read name mode owner && \
+	if [ x$$owner != x ] ; then chown -- $$owner $@ ; fi && \
+	chmod $$mode $@ ; }
+
+$(DESTDIR)$(libdir)/lib%.a: lib%.a.xyzzy
+	exec $(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(includedir)/$(package)/%.h: src/include/$(package)/%.h
+	exec $(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(includedir)/%.h: src/include/%.h
+	exec $(INSTALL) -D -m 644 $< $@
+
+%.o: %.c
+	exec $(CC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) -c -o $@ $<
+
+%.lo: %.c
+	exec $(CC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) $(CFLAGS_SHARED) -c -o $@ $<
+
+$(ALL_BINS):
+	exec $(CC) -o $@ $(CFLAGS_ALL) $(LDFLAGS_ALL) $(LDFLAGS_NOSHARED) $^ $(EXTRA_LIBS) $(LDLIBS)
+
+lib%.a.xyzzy:
+	exec $(AR) rc $@ $^
+	exec $(RANLIB) $@
+
+lib%.so.xyzzy:
+	exec $(CC) -o $@ $(CFLAGS_ALL) $(CFLAGS_SHARED) $(LDFLAGS_ALL) $(LDFLAGS_SHARED) -Wl,-soname,$(patsubst lib%.so.xyzzy,lib%.so.$(version_M),$@) $^ $(EXTRA_LIBS) $(LDLIBS)
+
+.PHONY: it all clean distclean tgz strip install install-dynlib install-bin install-lib install-include
+
+.DELETE_ON_ERROR:
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..c46e1c0
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,6 @@
+Changelog for shibari.
+
+In 0.0.1.0
+----------
+
+ - Initial release.
diff --git a/README b/README
new file mode 100644
index 0000000..d04245c
--- /dev/null
+++ b/README
@@ -0,0 +1,23 @@
+shibari - a collection of DNS tools
+-----------------------------------
+
+ shibari is a suite of DNS programs and libraries for
+Unix systems, as an alternative to BIND, Unbound, djbdns,
+or similar DNS software.
+
+ See https://skarnet.org/software/shibari/ for details.
+
+
+* Installation
+  ------------
+
+ See the INSTALL file.
+
+
+* Contact information
+  -------------------
+
+ Laurent Bercot <ska-skaware at skarnet.org>
+
+ Please use the <skaware at list.skarnet.org> mailing-list for
+questions about shibari.
diff --git a/README.macos b/README.macos
new file mode 100644
index 0000000..64e0742
--- /dev/null
+++ b/README.macos
@@ -0,0 +1,3 @@
+ This package will compile and run on Darwin (MacOS X), but the building of
+shared libraries is not supported.
+ Make sure you use the --disable-shared option to configure.
diff --git a/README.solaris b/README.solaris
new file mode 100644
index 0000000..0450a37
--- /dev/null
+++ b/README.solaris
@@ -0,0 +1,11 @@
+ This package assumes the existence of a POSIX shell in /bin/sh.
+ On Solaris, /bin/sh is not POSIX. Most versions of Solaris provide
+a POSIX shell in /usr/xpg4/bin/sh.
+
+ To compile this package on Solaris, you will need to run
+
+     ./patch-for-solaris
+
+ before you run ./configure. This script will change the #! invocation
+of the configure script and various tools so that a POSIX shell is used
+for the compilation process.
diff --git a/configure b/configure
new file mode 100755
index 0000000..efee0c9
--- /dev/null
+++ b/configure
@@ -0,0 +1,459 @@
+#!/bin/sh
+
+cd `dirname "$0"`
+. package/info
+
+usage () {
+cat <<EOF
+Usage: $0 [OPTION]... [TARGET]
+
+Defaults for the options are specified in brackets.
+
+System types:
+  --target=TARGET               configure to run on target TARGET [detected]
+  --host=TARGET                 same as --target
+
+Installation directories:
+  --prefix=PREFIX               main installation prefix [/]
+  --exec-prefix=EPREFIX         installation prefix for executable files [PREFIX]
+
+Fine tuning of the installation directories:
+  --dynlibdir=DIR               shared library files [PREFIX/lib]
+  --bindir=BINDIR               user executables [EPREFIX/bin]
+  --libexecdir=DIR              package-scoped executables [EPREFIX/libexec]
+  --libdir=DIR                  static library files [PREFIX/lib/$package]
+  --includedir=DIR              C header files [PREFIX/include]
+
+ If no --prefix option is given, by default libdir (but not dynlibdir) will be
+ /usr/lib/$package, and includedir will be /usr/include.
+
+Dependencies:
+  --with-sysdeps=DIR            use sysdeps in DIR [PREFIX/lib/skalibs/sysdeps]
+  --with-include=DIR            add DIR to the list of searched directories for headers
+  --with-lib=DIR                add DIR to the list of searched directories for static libraries
+  --with-dynlib=DIR             add DIR to the list of searched directories for shared libraries
+
+ If no --prefix option is given, by default sysdeps will be fetched from
+ /usr/lib/skalibs/sysdeps.
+
+Optional features:
+  --enable-shared               build shared libraries [disabled]
+  --disable-static              do not build static libraries [enabled]
+  --disable-allstatic           do not prefer linking against static libraries [enabled]
+  --enable-static-libc          make entirely static binaries [disabled]
+  --disable-all-pic             do not build executables or static libs as PIC [enabled]
+  --enable-slashpackage[=ROOT]  assume /package installation at ROOT [disabled]
+  --enable-absolute-paths       do not rely on PATH to access this package's binaries,
+                                  hardcode absolute BINDIR/foobar paths instead [disabled]
+
+EOF
+exit 0
+}
+
+# Helper functions
+
+# If your system does not have printf, you can comment this, but it is
+# generally not a good idea to use echo.
+# See http://etalabs.net/sh_tricks.html
+echo () {
+  IFS=" "
+  printf %s\\n "$*"
+}
+
+quote () {
+  tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/dev/null 2>&1 && { echo "$1" ; return 0 ; }
+$1
+EOF
+  echo "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#" -e "s|\*/|* /|g"
+}
+
+fail () {
+  echo "$*"
+  exit 1
+}
+
+fnmatch () {
+  eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac"
+}
+
+cmdexists () {
+  type "$1" >/dev/null 2>&1
+}
+
+trycc () {
+  test -z "$CC_AUTO" && cmdexists "$1" && CC_AUTO="$*"
+}
+
+stripdir () {
+  while eval "fnmatch '*/' \"\${$1}\"" ; do
+    eval "$1=\${$1%/}"
+  done
+}
+
+tryflag () {
+  echo "Checking whether compiler accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST "$2" -c -o "$tmpo" "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+tryldflag () {
+  echo "Checking whether linker accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CFLAGS_AUTO $CFLAGS $CFLAGS_POST $LDFLAGS_AUTO $LDFLAGS $LDFLAGS_POST -nostdlib "$2" -o "$tmpe" "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+
+# Actual script
+
+CC_AUTO=
+CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -iquote src/include-local -Isrc/include"
+CPPFLAGS_POST="$CPPFLAGS"
+CPPFLAGS=
+CFLAGS_AUTO="-pipe -Wall"
+CFLAGS_POST="$CFLAGS"
+CFLAGS=-O2
+LDFLAGS_AUTO=
+LDFLAGS_POST="$LDFLAGS"
+LDFLAGS=
+LDFLAGS_NOSHARED=
+LDFLAGS_SHARED=-shared
+prefix=/usr
+exec_prefix='$prefix'
+dynlibdir='$prefix/lib'
+libexecdir='$exec_prefix/libexec'
+bindir='$exec_prefix/bin'
+libdir='$prefix/lib/$package'
+includedir='$prefix/include'
+sysdeps='$prefix/lib/skalibs/sysdeps'
+manualsysdeps=false
+shared=false
+static=true
+allpic=true
+slashpackage=false
+abspath=false
+sproot=
+home=
+exthome=
+allstatic=true
+evenmorestatic=false
+addincpath=''
+addlibspath=''
+addlibdpath=''
+vpaths=''
+vpathd=''
+build=
+
+for arg ; do
+  case "$arg" in
+    --help) usage ;;
+    --prefix=*) prefix=${arg#*=} ;;
+    --exec-prefix=*) exec_prefix=${arg#*=} ;;
+    --dynlibdir=*) dynlibdir=${arg#*=} ;;
+    --libexecdir=*) libexecdir=${arg#*=} ;;
+    --bindir=*) bindir=${arg#*=} ;;
+    --libdir=*) libdir=${arg#*=} ;;
+    --includedir=*) includedir=${arg#*=} ;;
+    --with-sysdeps=*) sysdeps=${arg#*=} manualsysdeps=true ;;
+    --with-include=*) var=${arg#*=} ; stripdir var ; addincpath="$addincpath -I$var" ;;
+    --with-lib=*) var=${arg#*=} ; stripdir var ; addlibspath="$addlibspath -L$var" ; vpaths="$vpaths $var" ;;
+    --with-dynlib=*) var=${arg#*=} ; stripdir var ; addlibdpath="$addlibdpath -L$var" ; vpathd="$vpathd $var" ;;
+    --enable-shared|--enable-shared=yes) shared=true ;;
+    --disable-shared|--enable-shared=no) shared=false ;;
+    --enable-static|--enable-static=yes) static=true ;;
+    --disable-static|--enable-static=no) static=false ;;
+    --enable-allstatic|--enable-allstatic=yes) allstatic=true ;;
+    --disable-allstatic|--enable-allstatic=no) allstatic=false ; evenmorestatic=false ;;
+    --enable-static-libc|--enable-static-libc=yes) allstatic=true ; evenmorestatic=true ;;
+    --disable-static-libc|--enable-static-libc=no) evenmorestatic=false ;;
+    --enable-all-pic|--enable-all-pic=yes) allpic=true ;;
+    --disable-all-pic|--enable-all-pic=no) allpic=false ;;
+    --enable-slashpackage=*) sproot=${arg#*=} ; slashpackage=true ; ;;
+    --enable-slashpackage) sproot= ; slashpackage=true ;;
+    --disable-slashpackage) sproot= ; slashpackage=false ;;
+    --enable-absolute-paths|--enable-absolute-paths=yes) abspath=true ;;
+    --disable-absolute-paths|--enable-absolute-paths=no) abspath=false ;;
+    --enable-*|--disable-*|--with-*|--without-*|--*dir=*) ;;
+    --host=*|--target=*) target=${arg#*=} ;;
+    --build=*) build=${arg#*=} ;;
+    -* ) echo "$0: unknown option $arg" ;;
+    *=*) eval "${arg%%=*}=\${arg#*=}" ;;
+    *) target=$arg ;;
+  esac
+done
+
+# Expand installation directories
+stripdir prefix
+for i in exec_prefix dynlibdir libexecdir bindir libdir includedir sysdeps sproot ; do
+  eval tmp=\${$i}
+  eval $i=$tmp
+  stripdir $i
+done
+
+# Get usable temp filenames
+i=0
+set -C
+while : ; do
+  i=$(($i+1))
+  tmpc="./tmp-configure-$$-$PPID-$i.c"
+  tmpo="./tmp-configure-$$-$PPID-$i.o"
+  tmpe="./tmp-configure-$$-$PPID-$i.tmp"
+  2>|/dev/null > "$tmpc" && break
+  2>|/dev/null > "$tmpo" && break
+  2>|/dev/null > "$tmpe" && break
+  test "$i" -gt 50 && fail "$0: cannot create temporary files"
+done
+set +C
+trap 'rm -f "$tmpc" "$tmpo" "$tmpe"' EXIT ABRT INT QUIT TERM HUP
+
+# Set slashpackage values
+if $slashpackage ; then
+  home=${sproot}/package/${category}/${package}-${version}
+  exthome=${sproot}/package/${category}/${package}
+  if $manualsysdeps ; then
+    :
+  else
+    sysdeps=${DESTDIR}${sproot}/package/prog/skalibs/sysdeps
+  fi
+  prefix=
+  extbinprefix=${exthome}/command
+  dynlibdir=${home}/library.so
+  bindir=${home}/command
+  libdir=${home}/library
+  libexecdir=$bindir
+  includedir=${home}/include
+  while read dep condvar ; do
+    if test -n "$condvar" ; then
+      eval "cond=$condvar"
+    else
+      cond=true
+    fi
+    if $cond ; then
+      addincpath="$addincpath -I${DESTDIR}${sproot}${dep}/include"
+      vpaths="$vpaths ${DESTDIR}${sproot}${dep}/library"
+      addlibspath="$addlibspath -L${DESTDIR}${sproot}${dep}/library"
+      vpathd="$vpathd ${DESTDIR}${sproot}${dep}/library.so"
+      addlibdpath="$addlibdpath -L${DESTDIR}${sproot}${dep}/library.so"
+    fi
+  done < package/deps-build
+fi
+
+# Find a C compiler to use
+if test -n "$target" && test x${build} != x${target} ; then
+  cross=${target}-
+else
+  cross=
+fi
+echo "Checking for C compiler..."
+trycc ${CC}
+if test -n "$CC_AUTO" ; then
+  b=`basename "$CC"`
+  adjust_cross=false
+  if test "$b" != "$CC" ; then
+    adjust_cross=true
+    echo "$0: warning: compiler $CC is declared with its own path. If it's not accessible via PATH, you will need to pass AR, RANLIB and STRIP make variables to the make invocation." 1>&2
+  fi
+  if test -n "$cross" ; then
+    if test "$b" = "${b##$cross}" ; then
+      echo "$0: warning: compiler $CC is declared as a cross-compiler for target $target but does not start with prefix ${cross}" 1>&2
+    elif $adjust_cross ; then
+      cross=`dirname "$CC"`/"$cross"
+    fi
+  fi
+fi
+trycc ${cross}gcc
+trycc ${cross}clang
+trycc ${cross}cc
+test -n "$CC_AUTO" || { echo "$0: cannot find a C compiler" ; exit 1 ; }
+echo "  ... $CC_AUTO"
+echo "Checking whether C compiler works... "
+echo "typedef int x;" > "$tmpc"
+if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST -c -o "$tmpo" "$tmpc" 2>"$tmpe" ; then
+  echo "  ... yes"
+else
+  echo "  ... no. Compiler output follows:"
+  cat < "$tmpe"
+  exit 1
+fi
+
+echo "Checking target system type..."
+if test -z "$target" ; then
+  if test -n "$build" ; then
+    target=$build ;
+  else
+    target=$($CC_AUTO -dumpmachine 2>/dev/null) || target=unknown
+  fi
+fi
+echo "  ... $target"
+if test ! -d $sysdeps || test ! -f $sysdeps/target ; then
+  echo "$0: error: $sysdeps is not a valid sysdeps directory"
+  exit 1
+fi
+if [ "x$target" != "x$(cat $sysdeps/target)" ] ; then
+  echo "$0: error: target $target does not match the contents of $sysdeps/target"
+  exit 1
+fi
+
+spawn_lib=$(cat $sysdeps/spawn.lib)
+socket_lib=$(cat $sysdeps/socket.lib)
+sysclock_lib=$(cat $sysdeps/sysclock.lib)
+timer_lib=$(cat $sysdeps/timer.lib)
+util_lib=$(cat $sysdeps/util.lib)
+
+if $allpic ; then
+  tryflag CPPFLAGS_AUTO -fPIC
+fi
+tryflag CFLAGS_AUTO -std=c99
+tryflag CFLAGS -fomit-frame-pointer
+tryflag CFLAGS_AUTO -fno-exceptions
+tryflag CFLAGS_AUTO -fno-unwind-tables
+tryflag CFLAGS_AUTO -fno-asynchronous-unwind-tables
+tryflag CPPFLAGS_AUTO -Werror=implicit-function-declaration
+tryflag CPPFLAGS_AUTO -Werror=implicit-int
+tryflag CPPFLAGS_AUTO -Werror=pointer-sign
+tryflag CPPFLAGS_AUTO -Werror=pointer-arith
+tryflag CFLAGS_AUTO -ffunction-sections
+tryflag CFLAGS_AUTO -fdata-sections
+
+tryldflag LDFLAGS_AUTO -Wl,--sort-section=alignment
+tryldflag LDFLAGS_AUTO -Wl,--sort-common
+
+CPPFLAGS_AUTO="${CPPFLAGS_AUTO}${addincpath}"
+
+if $evenmorestatic ; then
+  LDFLAGS_NOSHARED=-static
+fi
+
+if $shared ; then
+  tryldflag LDFLAGS -Wl,--hash-style=both
+fi
+
+LDFLAGS_SHARED="${LDFLAGS_SHARED}${addlibdpath}"
+
+if test -z "$vpaths" ; then
+  while read dep ; do
+    base=$(basename $dep) ;
+    vpaths="$vpaths /usr/lib/$base"
+    addlibspath="$addlibspath -L/usr/lib/$base"
+  done < package/deps-build  
+fi
+
+if $allstatic ; then
+  LDFLAGS_NOSHARED="${LDFLAGS_NOSHARED}${addlibspath}"
+  tryldflag LDFLAGS_NOSHARED -Wl,--gc-sections
+else
+  LDFLAGS_NOSHARED="${LDFLAGS_NOSHARED}${addlibdpath}"
+fi
+
+echo "Creating config.mak..."
+cmdline=$(quote "$0")
+for i ; do cmdline="$cmdline $(quote "$i")" ; done
+exec 3>&1 1>config.mak
+cat << EOF
+# This file was generated by:
+# $cmdline
+# Any changes made here will be lost if configure is re-run.
+
+target := $target
+package := $package
+prefix := $prefix
+exec_prefix := $exec_prefix
+dynlibdir := $dynlibdir
+libexecdir := $libexecdir
+bindir := $bindir
+libdir := $libdir
+includedir := $includedir
+sysdeps := $sysdeps
+slashpackage := $slashpackage
+sproot := $sproot
+version := $version
+home := $home
+exthome := $exthome
+SPAWN_LIB := ${spawn_lib}
+SOCKET_LIB := ${socket_lib}
+SYSCLOCK_LIB := ${sysclock_lib}
+TIMER_LIB := ${timer_lib}
+UTIL_LIB := ${util_lib}
+
+CC := $CC_AUTO
+CPPFLAGS_AUTO := $CPPFLAGS_AUTO
+CPPFLAGS := $CPPFLAGS $CPPFLAGS_POST
+CFLAGS_AUTO := $CFLAGS_AUTO
+CFLAGS := $CFLAGS $CFLAGS_POST
+LDFLAGS_AUTO := $LDFLAGS_AUTO
+LDFLAGS := $LDFLAGS $LDFLAGS_POST
+LDFLAGS_SHARED := $LDFLAGS_SHARED
+LDFLAGS_NOSHARED := $LDFLAGS_NOSHARED
+CROSS_COMPILE := $cross
+
+vpath lib%.a$vpaths
+vpath lib%.so$vpathd
+EOF
+if $allstatic ; then
+  echo ".LIBPATTERNS := lib%.a"
+  echo "DO_ALLSTATIC := 1"
+else
+  echo ".LIBPATTERNS := lib%.so"
+fi
+if $static ; then
+  echo "DO_STATIC := 1"
+else
+  echo "DO_STATIC :="
+fi
+if $shared ; then
+  echo "DO_SHARED := 1"
+else
+  echo "DO_SHARED :="
+fi
+if $allpic ; then
+  echo "STATIC_LIBS_ARE_PIC := 1"
+else
+  echo "STATIC_LIBS_ARE_PIC :="
+fi
+
+exec 1>&3 3>&-
+echo "  ... done."
+
+echo "Creating src/include/${package}/config.h..."
+mkdir -p -m 0755 src/include/${package}
+exec 3>&1 1> src/include/${package}/config.h
+cat <<EOF
+/* ISC license. */
+
+/* Generated by: $cmdline */
+
+#ifndef ${package_macro_name}_CONFIG_H
+#define ${package_macro_name}_CONFIG_H
+
+#define ${package_macro_name}_VERSION "$version"
+EOF
+if $slashpackage ; then
+  echo "#define ${package_macro_name}_BINPREFIX \"$bindir/\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"$extbinprefix/\""
+elif $abspath ; then
+  echo "#define ${package_macro_name}_BINPREFIX \"$bindir/\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"$bindir/\""
+else
+  echo "#define ${package_macro_name}_BINPREFIX \"\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"\""
+fi
+echo "#define ${package_macro_name}_LIBEXECPREFIX \"$libexecdir/\""
+echo
+echo "#endif"
+exec 1>&3 3>&-
+echo "  ... done."
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..fd59558
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,181 @@
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>shibari - a collection of DNS tools </title>
+    <meta name="Description" content="shibari - a collection of DNS tools" />
+    <meta name="Keywords" content="shibari s6-dns DNS resolution server unix linux laurent bercot ska skarnet" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> shibari </h1>
+
+<h2> What is it&nbsp;? </h2>
+
+<p>
+ shibari is a collection of DNS tools for Unix systems, as an
+alternative to BIND, Unbound, djbdns or other similar suites of
+programs.
+</p>
+
+<p>
+ It was previously named s6-dns. The name of the project was changed to
+avoid confusion; despite being written by the same author and with the
+same mindset, it is not part of the s6 project.
+</p>
+
+<h3> Why "shibari"? </h3>
+
+<p>
+ There's a de facto tradition that DNS software has a name related to
+binding. shibari aims to be the most pleasant of all DNS software.
+</p>
+
+<hr />
+
+<h2> Installation </h2>
+
+<h3> Requirements </h3>
+
+<ul>
+ <li> A POSIX-compliant system with a standard C development environment </li>
+ <li> GNU make, version 3.81 or later </li>
+ <li> <a href="//skarnet.org/software/skalibs/">skalibs</a> version
+2.14.0.1 or later. It's a build-time requirement. It's also a run-time
+requirement if you link against the shared version of the skalibs library. </li>
+ <li> <a href="//skarnet.org/software/s6/">s6</a> version
+2.12.0.2 or later. It's a build-time requirement. It's also a run-time
+requirement if you link against the shared version of the s6 library. That
+library is used for the access control and client location features in
+<a href="shibari-server-udp.html">shibari-server-udp</a>. </li>
+ <li> (for now) <a href="//skarnet.org/software/s6-dns/">s6-dns</a> version
+2.3.7.0 or later. It's a build-time requirement. It's also a run-time
+requirement if you link against the shared version of the s6-dns library. </li>
+</ul>
+
+<h3> Licensing </h3>
+
+<p>
+ shibari is free software. It is available under the
+<a href="https://opensource.org/licenses/ISC">ISC license</a>.
+</p>
+
+<h3> Download </h3>
+
+<ul>
+ <li> The current released version of shibari is <a href="shibari-0.0.1.0.tar.gz">0.0.1.0</a>.
+(That is a lie. shibari is currently unreleased, so that link does not work.) </li>
+ <li> You can checkout a copy of the
+<a href="//git.skarnet.org/cgi-bin/cgit.cgi/shibari/">shibari
+git repository</a>:
+<pre> git clone git://git.skarnet.org/shibari </pre> </li>
+ <li> There's also a
+<a href="https://github.com/skarnet/shibari">GitHub mirror</a>
+of the shibari git repository. </li>
+</ul>
+
+<h3> Build and installation </h3>
+
+<ul>
+ <li> See the enclosed INSTALL file for build and installation details. </li>
+</ul>
+
+<h3> Upgrade notes </h3>
+
+<ul>
+ <li> <a href="upgrade.html">This page</a> lists the differences to be aware of between
+the previous versions of shibari and the current one. </li>
+</ul>
+
+<hr />
+
+<h2> Reference </h2>
+
+<h3> Commands </h3>
+
+<p>
+ All these commands exit 111 if they encounter a temporary error or
+hardware error, and
+100 if they encounter a permanent error - such as a misuse. Short-lived
+commands exit 0 on success. Other exit codes are documented in the
+relevant page.
+</p>
+
+<h4> Command-line DNS clients programs </h4>
+
+<ul>
+</ul>
+
+<h4> Caches </h4>
+
+<ul>
+</ul>
+
+<h4> Servers </h4>
+
+<ul>
+ <li> The <a href="shibari-server-tcp.html">shibari-server-tcp</a> program </li>
+ <li> The <a href="shibari-server-udp.html">shibari-server-udp</a> program </li>
+</ul>
+
+<h4> Filtering tools </h4>
+
+<ul>
+</ul>
+
+<h4> Command-line qualification </h4>
+
+<ul>
+</ul>
+
+<h4> DNS analysis and debug tools </h4>
+
+<ul>
+</ul>
+
+<h4> Miscellaneous utilities </h4>
+
+<h3> Libraries </h3>
+
+<h4> Protocol implementation and synchronous resolution </h4>
+
+<ul>
+</ul>
+
+<h4> Asynchronous resolution </h4>
+
+<ul>
+</ul>
+
+<hr />
+
+<a name="related">
+<h2> Related resources </h2>
+</a>
+
+<h3> shibari discussion </h3>
+
+<ul>
+ <li> <tt>shibari</tt> is discussed on the
+<a href="//skarnet.org/lists/#skaware">skaware</a> mailing-list. </li>
+ <li> It can also be discussed on the
+<a href="https://cr.yp.to/lists.html#dns">cr.yp.to dns mailing-list</a>. </li>
+</ul>
+
+<h3> Similar work </h3>
+
+<ul>
+ <li> <a href="https://www.isc.org/software/bind">BIND</a> </li>
+ <li> <a href="https://nlnetlabs.nl/projects/unbound/about/">Unbound</a> </li>
+ <li> <a href="https://cr.yp.to/djbdns.html">djbdns</a> </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/shibari-server-tcp.html b/doc/shibari-server-tcp.html
new file mode 100644
index 0000000..5f36087
--- /dev/null
+++ b/doc/shibari-server-tcp.html
@@ -0,0 +1,213 @@
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>shibari: the shibari-server-tcp program</title>
+    <meta name="Description" content="shibari: the shibari-server-tcp program" />
+    <meta name="Keywords" content="shibari DNS s6-dns server database authoritative TCP s6-networking ucspi" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">shibari</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The shibari-server-tcp program </h1>
+
+<p>
+  shibari-server-tcp reads DNS queries on its standard input, and answers them
+on its standard output.
+</p>
+
+<div id="interface">
+<h2> Interface </h2>
+</div>
+
+<pre>
+     shibari-server-tcp [ -v <em>verbosity</em> [ -f <em>tdbfile</em> ] [ -r <em>rtimeout</em> ] [ -w <em>wtimeout</em> ]
+</pre>
+
+<ul>
+ <li> shibari-server-tcp reads a stream of DNS queries on its stdin (encoded
+in the TCP DNS way, i.e. 2 bytes of length then the payload), and tries to fulfill them,
+sending answers to stdout. It logs its actions to stderr. </li>
+ <li> It supports normal queries and AXFR queries. </li>
+ <li> It reads DNS data information from a
+<a href="https://en.wikipedia.org/wiki/Cdb_(software)">cdb</a> database; the
+database must use the output format from
+<a href="https://cr.yp.to/djbdns/tinydns-data.html">tinydns-data</a>. </li>
+</ul>
+
+<div id="commonusage">
+<h2> Common usage </h2>
+</div>
+
+<p>
+ shibari-server-tcp is intended to be run under a TCP super-server such as
+<a href="//skarnet.org/software/s6-networking/s6-tcpserver.html">s6-tcpserver</a>.
+It delegates to the super-server the job of binding and listening to
+the socket, accepting connections, and spawning a separate process to handle a
+given connection.
+</p>
+
+<p>
+ As such, a command line for shibari-server-tcp, running as user <tt>dns</tt>, listening
+on address <tt>${ip}</tt>, would typically look like this:
+</p>
+
+<pre>
+     s6-envuidgid dns s6-tcpserver -U -- ${ip} 53 s6-tcpserver-access -x rules.cdb -- shibari-server-tcp
+</pre>
+
+<p>
+ Most users will want to run these command lines as <em>services</em>, i.e. daemons
+run in the background when the machine starts. The <tt>examples/</tt> subdirectory
+of the shibari package provides service templates to help you run shibari-server-tcp under
+<a href="https://wiki.gentoo.org/wiki/OpenRC">OpenRC</a>,
+<a href="//skarnet.org/software/s6/">s6</a> and
+<a href="//skarnet.org/software/s6-rc/">s6-rc</a>.
+</p>
+
+<div id="exitcodes">
+<h2> Exit codes </h2>
+</div>
+
+<dl>
+ <dt> 0 </dt> <dd> Clean exit. There was a successful series of DNS exchanges
+and tipideed received EOF, or timed out while the client was idle. </dd>
+ <dt> 1 </dt> <dd> Invalid DNS query. The client spoke garbage. </dd>
+ <dt> 100 </dt> <dd> Bad usage. shibari-server-tcp was run in an incorrect way: bad command
+line options, or missing environment variables, etc. </dd>
+ <dt> 101 </dt> <dd> Cannot happen. This signals a bug in shibari-server-tcp, and comes with an
+error message asking you to report the bug. Please do so, on the
+<a href="//skarnet.org/lists/#skaware">skaware mailing-list</a>. </dd>
+ <dt> 102 </dt> <dd> Misconfiguration. shibari-server-tcp found something in its DNS data file
+that it does not like. </dd>
+ <dt> 111 </dt> <dd> System call failed. This usually signals an issue with the
+underlying operating system. </dd>
+</dl>
+
+<div id="environment">
+<h2> Environment variables </h2>
+</div>
+
+<p>
+ shibari-server-tcp expects the following variables in its environment, and will exit
+with an error message if they are undefined. When run under
+<a href="//skarnet.org/software/s6-networking/s6-tcpserver.html">s6-tcpserver</a>,
+these variables are automatically set by the super-server. This is the way
+shibari-server-tcp gets its network information without having to perform network
+operations itself.
+</p>
+
+<dl>
+ <dt> TCPLOCALIP </dt>
+ <dd> The local IP address that the super-server is listening on. </dd>
+
+ <dt> TCPLOCALPORT </dt>
+ <dd> The local port that the super-server is listening on. In normal usage
+this will be 53. </dd>
+
+ <dt> TCPREMOTEIP </dt>
+ <dd> The IP address of the client. </dd>
+
+ <dt> TCPREMOTEPORT </dt>
+ <dd> The remote port that the client is connecting from. </dd>
+</dl>
+
+<p>
+ The following variables are optional, but will inform shibari-server-tcp's
+behaviour. They are typically set by
+<a href="//skarnet.org/software/s6-networking/s6-tcpserver-access.html">s6-tcpserver-access</a>
+with the <tt>-i</tt> or <tt>-x</tt> option, when the access rules database
+defines environment variables depending on client IP ranges.
+</p>
+
+<dl>
+ <dt> AXFR </dt>
+ <dd> If this variable is set, it controls what zones the client is allowed
+to make AXFR queries for. A value of <tt>*</tt> (star) means the client is
+allowed to make AXFR queries for any zone, same as when the variable is not
+defined. Else, the value needs to be a space-, comma-, semicolon-, or
+slash-separated list of zones; these are the allowed zones. </dd>
+
+ <dt> LOC </dt>
+ <dd> If this variable is set, it defines a client location that is used to
+implement views. A client location is at most two charaters; if the value
+is <tt>lo</tt>, then the client will be granted access to DNS data guarded
+by a <tt>%lo</tt> location indicator in the
+<a href="https://cr.yp.to/djbdns/tinydns-data.html">tinydns-data</a> file.
+Note that shibari-server-tcp ignores client IP prefix matching compiled in
+the database via <tt>%lo:ipprefix</tt> lines: it only takes its location
+information from the LOC variable, and will use the contents of LOC to match
+lines ending with <tt>:%lo</tt>. The idea is to only have one place centralizing
+what clients are authorized to do depending on their IP, and that place is the
+<a href="//skarnet.org/software/s6-networking/s6-tcpserver-access.html">s6-tcpserver-access</a>
+rules database. </dd>
+</dl>
+
+<div id="options">
+<h2> Options </h2>
+</div>
+
+<dl>
+ <dt> -v <em>verbosity</em> </dt>
+ <dd> Be more or less verbose.
+A <em>verbosity</em> of 0 means no warnings, no logs, only error messages. 1
+means warnings and terse logs. 2 or more means more logs.
+Default is <strong>1</strong>. </dd>
+
+ <dt> -f <em>tdbfile</em> </dt>
+ <dd> Read DNS data from <em>tdbfile</em>.
+The default is <strong><tt>data.cdb</tt></strong>, in the current working
+directory of the shibari-server-tcp process. </dd>
+
+ <dt> -r <em>rtimeout</em> </dt>
+ <dd> Read timeout. If <em>rtimeout</em> milliseconds
+elapse while shibari-server-tcp is waiting for a DNS query, just exit.
+The default is <strong>0</strong>, meaning infinite: shibari-server-tcp
+will never close the connection until it receives EOF. </dd>
+
+ <dt> -w <em>wtimeout</em> </dt>
+ <dd> Write timeout. If shibari-server-tcp is unable
+to send its answer in <em>wtimeout</em> milliseconds, which means the network is
+congested, give up and close the connection. The default is <strong>0</strong>, which
+means infinite: shibari-server-tcp will wait forever until the network decongests in
+order to send its answer. </dd>
+</dl>
+
+<div id="notes">
+<h2> Notes </h2>
+</div>
+
+<ul>
+ <li> The DNS database can be changed at any time via an invocation of
+<a href="https://cr.yp.to/djbdns/tinydns-data.html">tinydns-data</a>.
+shibari-server-tcp will keep using the old data until its current stream
+ends and it exits. The next instance of shibari-server-tcp,
+spawned by the super-server, will use the new data. </li>
+ <li> shibari-server-tcp is a drop-in replacement for
+<a href="https://cr.yp.to/djbdns/axfrdns.html">axfrdns</a>, with one
+caveat: client location information needs to be migrated from the DNS
+database to LOC definitions in the TCP access rules database. For instance,
+if you have a <tt>%lo:1.2.3</tt> line in your text data file, you need to
+add the following entries to your TCP access rules database:
+  <ul>
+   <li> <tt>ip4/1.2.3.0_24/allow</tt> (may be empty) </li>
+   <li> <tt>ip4/1.2.3.0_24/env/LOC</tt> containing <tt>lo</tt> </li>
+  </ul> </li>
+ <li> If you are using such an access rules database via a
+<a href="//skarnet.org/software/s6-networking/s6-tcpserver-access.html">s6-tcpserver-access</a>
+invocation, make sure your
+<a href="shibari-server-udp.html">shibari-server-udp</a> service is using the
+same database via the <tt>-i</tt> or <tt>-x</tt> option. You
+don't want to give different permissions, or different location information,
+depending on whether a query is made over TCP or UDP. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/shibari-server-udp.html b/doc/shibari-server-udp.html
new file mode 100644
index 0000000..c8e46f8
--- /dev/null
+++ b/doc/shibari-server-udp.html
@@ -0,0 +1,163 @@
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>shibari: the shibari-server-udp program</title>
+    <meta name="Description" content="shibari: the shibari-server-udp program" />
+    <meta name="Keywords" content="shibari DNS s6-dns server database authoritative UDP s6-networking tinydns" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">shibari</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The shibari-server-udp program </h1>
+
+<p>
+  shibari-server-udp is a long-lived process. It binds to a UDP socket, then
+answers DNS queries it receives, until it is killed.
+</p>
+
+<div id="interface">
+<h2> Interface </h2>
+</div>
+
+<pre>
+     shibari-server-udp [ -v <em>verbosity</em> ] [ -d <em>notif</em> ] [ -f <em>tdbfile</em> ] [ -i <em>rulesdir</em> ] [ -x <em>rulesfile</em> ] [ -p <em>port</em> ] <em>ip</em>
+</pre>
+
+<ul>
+ <li> shibari-server-udp creates a UDP socket and binds it to address <em>ip</em>
+on port 53. <em>ip</em> can be IPv4 or IPv6. </li>
+ <li> It listens to non-recursive DNS queries, sent by DNS caches, and, if
+appropriate, answers with data it reads from its data file. </li>
+ <li> It reloads its data file on SIGHUP, and exits 0 on SIGTERM. </li>
+ <li> The data file is a
+<a href="https://en.wikipedia.org/wiki/Cdb_(software)">cdb</a> database; it
+must use the output format from
+<a href="https://cr.yp.to/djbdns/tinydns-data.html">tinydns-data</a>. </li>
+</ul>
+
+<div id="exitcodes">
+<h2> Exit codes </h2>
+</div>
+
+<dl>
+ <dt> 0 </dt> <dd> Clean exit. shibari-server-udp received a SIGTERM and exited. <dd>
+ <dt> 100 </dt> <dd> Bad usage. shibari-server-udp was run in an incorrect way:
+typically bad command line options. </dd>
+ <dt> 101 </dt> <dd> Cannot happen. This signals a bug in shibari-server-udp, and comes with an
+error message asking you to report the bug. Please do so, on the
+<a href="//skarnet.org/lists/#skaware">skaware mailing-list</a>. </dd>
+ <dt> 102 </dt> <dd> Misconfiguration. shibari-server-udp found something in its DNS data file
+that it does not like. </dd>
+ <dt> 111 </dt> <dd> System call failed. This usually signals an issue with the
+underlying operating system. </dd>
+</dl>
+
+<div id="options">
+<h2> Options </h2>
+</div>
+
+<dl>
+ <dt> -v <em>verbosity</em> </dt>
+ <dd> Be more or less verbose.
+A <em>verbosity</em> of 0 means no warnings, no logs, only error messages. 1
+means warnings and terse logs. 2 or more means more logs.
+Default is <strong>1</strong>. </dd>
+
+ <dt> -d <em>notif</em> </dt>
+ <dd> Write a newline to file descriptor <em>notif</em>, then close it, when
+shibari-server-udp has bound its socket, opened its file, and is ready to serve.
+This is the <a href="https://skarnet.org/software/s6/notifywhenup.html">s6
+readiness notification</a> mechanism. By default, when this option isn't given
+no readiness notification is sent. </dd>
+
+ <dt> -f <em>tdbfile</em> </dt>
+ <dd> Read DNS data from <em>tdbfile</em>.
+The default is <strong><tt>data.cdb</tt></strong>, in the current working
+directory of the shibari-server-udp process. </dd>
+
+ <dt> -i <em>rulesdir</em> </dt>
+ <dd> Use <em>rulesdir</em> as a filesystem-based
+<a href="//skarnet.org/software/s6/libs6/accessrules.html">access rules
+database</a>: ignore any message whose originating IP address isn't
+explicitly allowed. The access rules database is also used to get
+<a href="#clientlocation">client location information</a>.
+If something in <em>rulesdir</em> changes while shibari-server-udp is
+running, it will immediately pick up the change. </dd>
+
+ <dt> -x <em>rulesfile</em> </dt>
+ <dd> Use <em>rulesfile</em> as a cdb
+<a href="//skarnet.org/software/s6/libs6/accessrules.html">access rules
+database</a>, see description of <tt>-i</tt> above. <tt>-i</tt> and
+<tt>-x</tt> are equivalent; you can switch between <em>rulesdir</em>
+and <em>rulesfile</em> via the
+<a href="//skarnet.org/software/s6/s6-accessrules-cdb-from-fs.html">s6-accessrules-cdb-from-fs</a> and
+<a href="//skarnet.org/software/s6/s6-accessrules-fs-from-cdb.html">s6-accessrules-fs-from-cdb</a>
+programs. The cdb format is more efficient but more static than the
+filesystem format. If <em>rulesfile</em> changes while shibari-server-udp
+is running, it will continue to use the old data until it receives a SIGHUP. </dd>
+
+ <dt> -p <em>port</em> </dt>
+ <dd> Binds to port <em>port</em>. Default is <strong>53</strong>. </dd>
+</dl>
+
+<div id="clientlocation">
+<h2> Client location </h2>
+</div>
+
+<p>
+ shibari-server-udp ignores client location information given as
+<tt>%lo:ipprefix</tt> lines in the file created by
+<a href="https://cr.yp.to/djbdns/tinydns-data.html">tinydns-data</a>.
+Instead, it reads client location information in LOC definitions
+present in the <em>rulesdir</em> or <em>rulesfile</em>
+access rules database. For instance,
+if you have a <tt>%lo:1.2.3</tt> line in your text data file, meaning
+that clients whose IP address is in the <tt>1.2.3.0/24</tt> IPv4
+range are identified with the <tt>lo</tt> location and that DNS data
+entries ending with <tt>:lo</tt> are visible to them, you need to
+translate this information into the accessrules format. Your
+<em>rulesdir</em> must contain the following files:
+</p>
+
+<ul>
+ <li> <tt>ip4/1.2.3.0_24/allow</tt> (may be empty) </li>
+ <li> <tt>ip4/1.2.3.0_24/env/LOC</tt> containing <tt>lo</tt> </li>
+</ul>
+
+<p>
+ (To use the <tt>-x</tt> option instead, you'd do the same, then run
+<tt>s6-accessrules-cdb-from-fs <em>rulesfile</em> <em>rulesdir</em></tt>
+to compile the information into <em>rulesfile</em>.)
+</p>
+
+<div id="notes">
+<h2> Notes </h2>
+</div>
+
+<ul>
+ <li> The DNS database can be changed at any time via an invocation of
+<a href="https://cr.yp.to/djbdns/tinydns-data.html">tinydns-data</a>.
+shibari-server-udp will keep using the old data until it receives a
+SIGHUP, at which point it will reopen its database. </li>
+ <li> shibari-server-udp is a drop-in replacement for
+<a href="https://cr.yp.to/djbdns/tinydns.html">tinydns</a>, with the
+caveat of the <a href="#clientlocation">client location mechanism</a>. </li>
+ <li> If you are using the <tt>-i<tt> or <tt>-x</tt> option, the
+access rules database can, and should, be the same one that is used by the
+<a href="//skarnet.org/software/s6-networking/s6-tcpserver-access.html">s6-tcpserver-access</a>
+program in your
+<a href="shibari-server-tcp.html">shibari-server-tcp</a> service. You
+don't want to give different permissions, or different location information,
+depending on whether a query is made over TCP or UDP. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/upgrade.html b/doc/upgrade.html
new file mode 100644
index 0000000..52eeaa5
--- /dev/null
+++ b/doc/upgrade.html
@@ -0,0 +1,28 @@
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>shibari: how to upgrade</title>
+    <meta name="Description" content="shibari: how to upgrade" />
+    <meta name="Keywords" content="shibari installation upgrade" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">shibari</a><br />
+<a href="//skarnet.org/software/">Software</a> <br>
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> What has changed in shibari </h1>
+
+<h2> in 0.0.1.0 </h2>
+
+<ul>
+ <li> Initial release. </li>
+</ul>
+
+</body>
+</html>
diff --git a/package/deps-build b/package/deps-build
new file mode 100644
index 0000000..e2af791
--- /dev/null
+++ b/package/deps-build
@@ -0,0 +1,3 @@
+/package/prog/skalibs
+/package/admin/s6
+/package/web/s6-dns
diff --git a/package/deps.mak b/package/deps.mak
new file mode 100644
index 0000000..5fbe083
--- /dev/null
+++ b/package/deps.mak
@@ -0,0 +1,66 @@
+#
+# This file has been generated by tools/gen-deps.sh
+#
+
+src/include/shibari/cache.h: src/include/shibari/dcache.h
+src/include/shibari/common.h: src/include/shibari/constants.h src/include/shibari/util.h
+src/include/shibari/packet.h: src/include/shibari/tdb.h
+src/include/shibari/server.h: src/include/shibari/log.h src/include/shibari/packet.h src/include/shibari/tdb.h
+src/include/shibari/shibari.h: src/include/shibari/cache.h src/include/shibari/client.h src/include/shibari/common.h src/include/shibari/server.h
+src/cache/dcache-internal.h: src/include/shibari/dcache.h
+src/cache/dcache_add.o src/cache/dcache_add.lo: src/cache/dcache_add.c src/cache/dcache-internal.h src/include/shibari/dcache.h
+src/cache/dcache_clean_expired.o src/cache/dcache_clean_expired.lo: src/cache/dcache_clean_expired.c src/cache/dcache-internal.h src/include/shibari/dcache.h
+src/cache/dcache_delete.o src/cache/dcache_delete.lo: src/cache/dcache_delete.c src/cache/dcache-internal.h src/include/shibari/dcache.h
+src/cache/dcache_free.o src/cache/dcache_free.lo: src/cache/dcache_free.c src/include/shibari/dcache.h
+src/cache/dcache_init.o src/cache/dcache_init.lo: src/cache/dcache_init.c src/include/shibari/dcache.h
+src/cache/dcache_load.o src/cache/dcache_load.lo: src/cache/dcache_load.c src/include/shibari/dcache.h
+src/cache/dcache_save.o src/cache/dcache_save.lo: src/cache/dcache_save.c src/include/shibari/dcache.h
+src/cache/dcache_search.o src/cache/dcache_search.lo: src/cache/dcache_search.c src/cache/dcache-internal.h src/include/shibari/dcache.h
+src/common/shibari_log_answer.o src/common/shibari_log_answer.lo: src/common/shibari_log_answer.c src/include/shibari/log.h src/include/shibari/util.h
+src/common/shibari_log_exit.o src/common/shibari_log_exit.lo: src/common/shibari_log_exit.c src/include/shibari/log.h
+src/common/shibari_log_query.o src/common/shibari_log_query.lo: src/common/shibari_log_query.c src/include/shibari/log.h src/include/shibari/util.h
+src/common/shibari_log_queryplus.o src/common/shibari_log_queryplus.lo: src/common/shibari_log_queryplus.c src/include/shibari/log.h src/include/shibari/util.h
+src/common/shibari_log_start.o src/common/shibari_log_start.lo: src/common/shibari_log_start.c src/include/shibari/log.h
+src/common/shibari_util_get_prefixlen.o src/common/shibari_util_get_prefixlen.lo: src/common/shibari_util_get_prefixlen.c src/include/shibari/util.h
+src/common/shibari_util_qtype_num.o src/common/shibari_util_qtype_num.lo: src/common/shibari_util_qtype_num.c src/include/shibari/util.h
+src/common/shibari_util_qtype_str.o src/common/shibari_util_qtype_str.lo: src/common/shibari_util_qtype_str.c src/include/shibari/util.h
+src/common/shibari_util_rcode_str.o src/common/shibari_util_rcode_str.lo: src/common/shibari_util_rcode_str.c src/include/shibari/util.h
+src/server/shibari-server-tcp.o src/server/shibari-server-tcp.lo: src/server/shibari-server-tcp.c src/include/shibari/common.h src/include/shibari/server.h
+src/server/shibari-server-udp.o src/server/shibari-server-udp.lo: src/server/shibari-server-udp.c src/include/shibari/common.h src/include/shibari/server.h
+src/server/shibari_packet_add_glue.o src/server/shibari_packet_add_glue.lo: src/server/shibari_packet_add_glue.c src/include/shibari/constants.h src/include/shibari/packet.h src/include/shibari/tdb.h src/include/shibari/util.h
+src/server/shibari_packet_add_rr.o src/server/shibari_packet_add_rr.lo: src/server/shibari_packet_add_rr.c src/include/shibari/constants.h src/include/shibari/packet.h
+src/server/shibari_packet_assert_authority.o src/server/shibari_packet_assert_authority.lo: src/server/shibari_packet_assert_authority.c src/include/shibari/constants.h src/include/shibari/packet.h src/include/shibari/tdb.h src/include/shibari/util.h
+src/server/shibari_packet_begin.o src/server/shibari_packet_begin.lo: src/server/shibari_packet_begin.c src/include/shibari/constants.h src/include/shibari/packet.h
+src/server/shibari_packet_end.o src/server/shibari_packet_end.lo: src/server/shibari_packet_end.c src/include/shibari/packet.h
+src/server/shibari_packet_init.o src/server/shibari_packet_init.lo: src/server/shibari_packet_init.c src/include/shibari/packet.h
+src/server/shibari_packet_tdb_answer_query.o src/server/shibari_packet_tdb_answer_query.lo: src/server/shibari_packet_tdb_answer_query.c src/include/shibari/constants.h src/include/shibari/packet.h src/include/shibari/tdb.h
+src/server/shibari_tdb_entry_parse.o src/server/shibari_tdb_entry_parse.lo: src/server/shibari_tdb_entry_parse.c src/include/shibari/constants.h src/include/shibari/tdb.h
+src/server/shibari_tdb_extract_domain.o src/server/shibari_tdb_extract_domain.lo: src/server/shibari_tdb_extract_domain.c src/include/shibari/constants.h src/include/shibari/tdb.h
+src/server/shibari_tdb_find_authority.o src/server/shibari_tdb_find_authority.lo: src/server/shibari_tdb_find_authority.c src/include/shibari/constants.h src/include/shibari/tdb.h
+src/server/shibari_tdb_read_entry.o src/server/shibari_tdb_read_entry.lo: src/server/shibari_tdb_read_entry.c src/include/shibari/tdb.h
+
+ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
+libdcache.a.xyzzy: src/cache/dcache_add.o src/cache/dcache_clean_expired.o src/cache/dcache_delete.o src/cache/dcache_free.o src/cache/dcache_init.o src/cache/dcache_load.o src/cache/dcache_save.o src/cache/dcache_search.o
+else
+libdcache.a.xyzzy: src/cache/dcache_add.lo src/cache/dcache_clean_expired.lo src/cache/dcache_delete.lo src/cache/dcache_free.lo src/cache/dcache_init.lo src/cache/dcache_load.lo src/cache/dcache_save.lo src/cache/dcache_search.lo
+endif
+libdcache.so.xyzzy: EXTRA_LIBS :=
+libdcache.so.xyzzy: src/cache/dcache_add.lo src/cache/dcache_clean_expired.lo src/cache/dcache_delete.lo src/cache/dcache_free.lo src/cache/dcache_init.lo src/cache/dcache_load.lo src/cache/dcache_save.lo src/cache/dcache_search.lo
+ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
+libshibari-common.a.xyzzy: src/common/shibari_log_answer.o src/common/shibari_log_exit.o src/common/shibari_log_query.o src/common/shibari_log_queryplus.o src/common/shibari_log_start.o src/common/shibari_util_qtype_num.o src/common/shibari_util_qtype_str.o src/common/shibari_util_rcode_str.o src/common/shibari_util_get_prefixlen.o
+else
+libshibari-common.a.xyzzy: src/common/shibari_log_answer.lo src/common/shibari_log_exit.lo src/common/shibari_log_query.lo src/common/shibari_log_queryplus.lo src/common/shibari_log_start.lo src/common/shibari_util_qtype_num.lo src/common/shibari_util_qtype_str.lo src/common/shibari_util_rcode_str.lo src/common/shibari_util_get_prefixlen.lo
+endif
+libshibari-common.so.xyzzy: EXTRA_LIBS := -lskarnet
+libshibari-common.so.xyzzy: src/common/shibari_log_answer.lo src/common/shibari_log_exit.lo src/common/shibari_log_query.lo src/common/shibari_log_queryplus.lo src/common/shibari_log_start.lo src/common/shibari_util_qtype_num.lo src/common/shibari_util_qtype_str.lo src/common/shibari_util_rcode_str.lo src/common/shibari_util_get_prefixlen.lo
+ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
+libshibari-server.a.xyzzy: src/server/shibari_packet_init.o src/server/shibari_packet_begin.o src/server/shibari_packet_end.o src/server/shibari_packet_add_rr.o src/server/shibari_tdb_entry_parse.o src/server/shibari_tdb_extract_domain.o src/server/shibari_tdb_find_authority.o src/server/shibari_tdb_read_entry.o src/server/shibari_packet_add_glue.o src/server/shibari_packet_assert_authority.o src/server/shibari_packet_tdb_answer_query.o
+else
+libshibari-server.a.xyzzy: src/server/shibari_packet_init.lo src/server/shibari_packet_begin.lo src/server/shibari_packet_end.lo src/server/shibari_packet_add_rr.lo src/server/shibari_tdb_entry_parse.lo src/server/shibari_tdb_extract_domain.lo src/server/shibari_tdb_find_authority.lo src/server/shibari_tdb_read_entry.lo src/server/shibari_packet_add_glue.lo src/server/shibari_packet_assert_authority.lo src/server/shibari_packet_tdb_answer_query.lo
+endif
+libshibari-server.so.xyzzy: EXTRA_LIBS := -ls6dns -lskarnet
+libshibari-server.so.xyzzy: src/server/shibari_packet_init.lo src/server/shibari_packet_begin.lo src/server/shibari_packet_end.lo src/server/shibari_packet_add_rr.lo src/server/shibari_tdb_entry_parse.lo src/server/shibari_tdb_extract_domain.lo src/server/shibari_tdb_find_authority.lo src/server/shibari_tdb_read_entry.lo src/server/shibari_packet_add_glue.lo src/server/shibari_packet_assert_authority.lo src/server/shibari_packet_tdb_answer_query.lo
+shibari-server-tcp: EXTRA_LIBS := -ls6dns -lskarnet
+shibari-server-tcp: src/server/shibari-server-tcp.o ${LIBSHIBARI_SERVER} ${LIBSHIBARI_COMMON}
+shibari-server-udp: EXTRA_LIBS := -ls6dns -ls6 -lskarnet ${SOCKET_LIB}
+shibari-server-udp: src/server/shibari-server-udp.o ${LIBSHIBARI_SERVER} ${LIBSHIBARI_COMMON}
diff --git a/package/info b/package/info
new file mode 100644
index 0000000..d1c92dc
--- /dev/null
+++ b/package/info
@@ -0,0 +1,4 @@
+package=shibari
+version=0.0.1.0
+category=web
+package_macro_name=SHIBARI
diff --git a/package/modes b/package/modes
new file mode 100644
index 0000000..94bdf3e
--- /dev/null
+++ b/package/modes
@@ -0,0 +1,2 @@
+shibari-server-tcp	0755
+shibari-server-udp	0755
diff --git a/package/targets.mak b/package/targets.mak
new file mode 100644
index 0000000..0f2d479
--- /dev/null
+++ b/package/targets.mak
@@ -0,0 +1,7 @@
+BIN_TARGETS := \
+shibari-server-tcp \
+shibari-server-udp
+
+LIBEXEC_TARGETS :=
+
+LIB_DEFS := SHIBARI_SERVER=shibari-server SHIBARI_COMMON=shibari-common DCACHE=dcache
diff --git a/patch-for-solaris b/patch-for-solaris
new file mode 100755
index 0000000..2d1296b
--- /dev/null
+++ b/patch-for-solaris
@@ -0,0 +1,21 @@
+#!/usr/xpg4/bin/sh
+
+patchit () {
+  echo '#!/usr/xpg4/bin/sh' > $1.tmp
+  tail -n +2 $1 >> $1.tmp
+  mv -f $1.tmp $1
+  chmod 755 $1
+}
+
+# Solaris doesn't understand POSIX.1-2008 either.
+sed -e 's/XOPEN_SOURCE=700/XOPEN_SOURCE=600/' < configure > configure.tmp
+mv -f configure.tmp configure
+
+patchit ./configure
+patchit ./tools/install.sh
+patchit ./tools/gen-deps.sh
+
+echo 'SHELL := /usr/xpg4/bin/sh' > Makefile.tmp
+echo >> Makefile.tmp
+cat Makefile >> Makefile.tmp
+mv -f Makefile.tmp Makefile
diff --git a/src/cache/dcache-internal.h b/src/cache/dcache-internal.h
new file mode 100644
index 0000000..2a2e36f
--- /dev/null
+++ b/src/cache/dcache-internal.h
@@ -0,0 +1,18 @@
+/* ISC license. */
+
+#ifndef SHIBARI_DCACHE_INTERNAL_H
+#define SHIBARI_DCACHE_INTERNAL_H
+
+#include <stdint.h>
+
+#include <skalibs/avlnode.h>
+#include <skalibs/gensetdyn.h>
+
+#include <shibari/dcache.h>
+
+#define DNODE(z, i) GENSETDYN_P(dcache_node_t, &(z)->storage, i)
+#define DCACHE_NODE_OVERHEAD (32 + sizeof(dcache_node_t) + 3 * sizeof(avlnode))
+
+extern void dcache_delete (dcache_t *, uint32_t) ;
+
+#endif
diff --git a/src/cache/dcache_add.c b/src/cache/dcache_add.c
new file mode 100644
index 0000000..7260726
--- /dev/null
+++ b/src/cache/dcache_add.c
@@ -0,0 +1,85 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include <skalibs/uint64.h>
+#include <skalibs/alloc.h>
+#include <skalibs/tai.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/avlnode.h>
+#include <skalibs/avltree.h>
+
+#include <shibari/dcache.h>
+#include "dcache-internal.h"
+
+static void uniquify (avltree const *tree, tain *stamp)
+{
+  static tain const nano = { .sec = TAI_ZERO, .nano = 1 } ;
+  uint32_t dummy ;
+  while (avltree_search(tree, stamp, &dummy))
+    tain_add(stamp, stamp, &nano) ;
+}
+
+static inline void dcache_gc_by_entry (dcache_t *z, uint64_t max)
+{
+  while (z->size > max)
+  {
+    uint32_t oldest ;
+    if (!avltree_min(&z->by_entry, &oldest)) break ;
+    dcache_delete(z, oldest) ;
+  }
+}
+
+static inline int dcache_add_node (dcache_t *z, dcache_node_t const *node)
+{
+  uint32_t i ;
+  dcache_node_t *y ;
+  if (!gensetdyn_new(&z->storage, &i)) return 0 ;
+  y = DNODE(z, i) ; *y = *node ;
+  uniquify(&z->by_entry, &y->entry) ;
+  uniquify(&z->by_expire, &y->expire) ;
+  if (!avltree_insert(&z->by_key, i)) goto err1 ;
+  if (!avltree_insert(&z->by_entry, i)) goto err2 ;
+  if (!avltree_insert(&z->by_expire, i)) goto err3 ;
+  return 1 ;
+
+ err3:
+  avltree_delete(&z->by_entry, &y->entry) ;
+ err2:
+  avltree_delete(&z->by_key, &y->key) ;
+ err1:
+  gensetdyn_delete(&z->storage, i) ;
+  return 0 ;
+}
+
+static inline int dcache_add_unbounded (dcache_t *z, char const *key, uint16_t keylen, char const *data, uint16_t datalen, tain const *expire, tain const *stamp)
+{
+  uint32_t len = (uint32_t)keylen + (uint32_t)datalen ;
+  dcache_node_t y = { .key = { .s = alloc(len) } } ;
+  if (!y.key.s) return 0 ;
+  memcpy(y.key.s, key, keylen) ;
+  memcpy(y.key.s + keylen, data, datalen) ;
+  y.key.len = keylen ;
+  y.datalen = datalen ;
+  y.entry = *stamp ;
+  y.expire = *expire ;
+  if (!dcache_add_node(z, &y))
+  {
+    alloc_free(y.key.s) ;
+    return 0 ;
+  }
+  z->size += DCACHE_NODE_OVERHEAD + len ;
+  z->motion += DCACHE_NODE_OVERHEAD + len ;
+  return 1 ;
+}
+
+int dcache_add (dcache_t *z, uint64_t max, char const *key, uint16_t keylen, char const *data, uint16_t datalen, tain const *expire, tain const *stamp)
+{
+  uint64_t size = DCACHE_NODE_OVERHEAD + keylen + datalen ;
+  if (size > max) return (errno = EINVAL, 0) ;
+  if (z->size > max - size) dcache_clean_expired(z, stamp) ;
+  if (z->size > max - size) dcache_gc_by_entry(z, max - size) ;
+  return dcache_add_unbounded(z, key, keylen, data, datalen, expire, stamp) ;
+}
diff --git a/src/cache/dcache_clean_expired.c b/src/cache/dcache_clean_expired.c
new file mode 100644
index 0000000..0e23443
--- /dev/null
+++ b/src/cache/dcache_clean_expired.c
@@ -0,0 +1,20 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <skalibs/tai.h>
+#include <skalibs/avltree.h>
+
+#include <shibari/dcache.h>
+#include "dcache-internal.h"
+
+void dcache_clean_expired (dcache_t *z, tain const *stamp)
+{
+  for (;;)
+  {
+    uint32_t i ;
+    if (!avltree_min(&z->by_expire, &i)) break ;
+    if (tain_less(stamp, &DNODE(z, i)->expire)) break ;
+    dcache_delete(z, i) ;
+  }
+}
diff --git a/src/cache/dcache_delete.c b/src/cache/dcache_delete.c
new file mode 100644
index 0000000..92a5fcc
--- /dev/null
+++ b/src/cache/dcache_delete.c
@@ -0,0 +1,19 @@
+/* ISC license. */
+
+#include <skalibs/alloc.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/avltree.h>
+
+#include <shibari/dcache.h>
+#include "dcache-internal.h"
+
+void dcache_delete (dcache_t *z, uint32_t i)
+{
+  dcache_node_t *y = DNODE(z, i) ;
+  avltree_delete(&z->by_expire, &y->expire) ;
+  avltree_delete(&z->by_entry, &y->entry) ;
+  avltree_delete(&z->by_key, &y->key) ;
+  alloc_free(y->key.s) ;
+  z->size -= DCACHE_NODE_OVERHEAD + y->key.len + y->datalen ;
+  gensetdyn_delete(&z->storage, i) ;
+}
diff --git a/src/cache/dcache_free.c b/src/cache/dcache_free.c
new file mode 100644
index 0000000..16c074e
--- /dev/null
+++ b/src/cache/dcache_free.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <skalibs/alloc.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/avltree.h>
+
+#include <shibari/dcache.h>
+
+static void dcache_node_free (void *p)
+{
+  alloc_free(((dcache_node_t *)p)->key.s) ;
+}
+
+void dcache_free (dcache_t *z)
+{
+  static dcache_t const dcache_zero = DCACHE_ZERO ;
+  avltree_free(&z->by_expire) ;
+  avltree_free(&z->by_entry) ;
+  avltree_free(&z->by_key) ;
+  gensetdyn_deepfree(&z->storage, &dcache_node_free) ;
+  *z = dcache_zero ;
+}
diff --git a/src/cache/dcache_init.c b/src/cache/dcache_init.c
new file mode 100644
index 0000000..d42ec62
--- /dev/null
+++ b/src/cache/dcache_init.c
@@ -0,0 +1,55 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <skalibs/uint64.h>
+#include <skalibs/tai.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/avltree.h>
+
+#include <shibari/dcache.h>
+
+static int key_cmp (void const *a, void const *b, void *x)
+{
+  dcache_key_t const *ka = a ;
+  dcache_key_t const *kb = b ;
+  if (ka->len < kb->len) return -1 ;
+  if (kb->len < ka->len) return 1 ;
+  (void)x ;
+  return memcmp(ka->s, kb->s, ka->len) ;
+}
+
+static int tain_cmp (void const *a, void const *b, void *x)
+{
+  tain const *ta = a ;
+  tain const *tb = b ;
+  (void)x ;
+  return tain_less(ta, tb) ? -1 : tain_less(tb, ta) ;
+}
+
+static void *key_dtok (uint32_t d, void *x)
+{
+  return &GENSETDYN_P(dcache_node_t, (gensetdyn *)x, d)->key ;
+}
+
+static void *entry_dtok (uint32_t d, void *x)
+{
+  return &GENSETDYN_P(dcache_node_t, (gensetdyn *)x, d)->entry ;
+}
+
+static void *expire_dtok (uint32_t d, void *x)
+{
+  return &GENSETDYN_P(dcache_node_t, (gensetdyn *)x, d)->expire ;
+}
+
+
+void dcache_init (dcache_t *z, uint64_t max)
+{
+  gensetdyn_init(&z->storage, sizeof(dcache_node_t), max >> 9, 3, 8) ;
+  avltree_init(&z->by_key, max >> 9, 3, 8, &key_dtok, &key_cmp, &z->storage) ;
+  avltree_init(&z->by_entry, max >> 9, 3, 8, &entry_dtok, &tain_cmp, &z->storage) ;
+  avltree_init(&z->by_expire, max >> 9, 3, 8, &expire_dtok, &tain_cmp, &z->storage) ;
+  z->size = 0 ;
+  z->motion = 0 ;
+}
diff --git a/src/cache/dcache_load.c b/src/cache/dcache_load.c
new file mode 100644
index 0000000..a0ff233
--- /dev/null
+++ b/src/cache/dcache_load.c
@@ -0,0 +1,81 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include <skalibs/posixishard.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint64.h>
+#include <skalibs/buffer.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+
+#include <shibari/dcache.h>
+
+static inline int dcache_load_node (dcache_t *z, uint64_t max, buffer *b)
+{
+  tain entry = { .nano = 0 } ;
+  tain expire = { .nano = 0 } ;
+  uint16_t keylen ;
+  uint16_t datalen ;
+  char pack[TAI_PACK * 2 + 4] ;
+  ssize_t r = buffer_get(b, pack, TAI_PACK * 2 + 4) ;
+  if (!r) return 0 ;
+  if (r < TAI_PACK * 2 + 4) return -1 ;
+  tai_unpack(pack, tain_secp(&entry)) ;
+  tai_unpack(pack + TAI_PACK, tain_secp(&expire)) ;
+  uint16_unpack_big(pack + TAI_PACK * 2, &keylen) ;
+  uint16_unpack_big(pack + TAI_PACK * 2 + 2, &datalen) ;
+  {
+    uint32_t len = (uint32_t)keylen + (uint32_t)datalen ;
+    char blob[len+1] ;  /* 128 kB max, it's ok */ 
+    r = buffer_get(b, blob, len+1) ;
+    if (!r) return (errno = EPIPE, -1) ;
+    if (r < len) return -1 ;
+    if (blob[len]) return (errno = EPROTO, -1) ;
+    if (!dcache_add(z, max, blob, keylen, blob + keylen, datalen, &expire, &entry)) return -1 ;
+  }
+  return 1 ;
+}
+
+static inline int dcache_load_from_buffer (dcache_t *z, uint64_t max, buffer *b)
+{
+  {
+    char banner[sizeof(DCACHE_MAGIC) - 1] ;
+    char pack[8] ;
+    if (buffer_get(b, banner, sizeof(DCACHE_MAGIC) - 1) < sizeof(DCACHE_MAGIC) - 1)
+      return 0 ;
+    if (memcmp(banner, DCACHE_MAGIC, sizeof(DCACHE_MAGIC) - 1)) return 0 ;
+    if (buffer_get(b, pack, 8) < 8) return 0 ;
+    uint64_unpack_big(pack, &z->size) ;
+    if (buffer_get(b, pack, 8) < 8) return 0 ;
+    uint64_unpack_big(pack, &z->motion) ;
+  }
+  for (;;)
+  {
+    int r = dcache_load_node(z, max, b) ;
+    if (r < 0) return 0 ;
+    if (!r) break ;
+  }
+  return 1 ;
+}
+
+#define N 8192
+
+int dcache_load (dcache_t *z, uint64_t max, char const *file)
+{
+  char buf[N] ;
+  buffer b ;
+  int fd = open_readb(file) ;
+  if (fd == -1) return 0 ;
+  buffer_init(&b, &buffer_read, fd, buf, N) ;
+  if (!dcache_load_from_buffer(z, max, &b)) goto err ;
+  fd_close(fd) ;
+  return 1 ;
+
+ err:
+  dcache_free(z) ;
+  fd_close(fd) ;
+  return 0 ;
+}
diff --git a/src/cache/dcache_save.c b/src/cache/dcache_save.c
new file mode 100644
index 0000000..7277771
--- /dev/null
+++ b/src/cache/dcache_save.c
@@ -0,0 +1,73 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <skalibs/posixplz.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint64.h>
+#include <skalibs/buffer.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/gensetdyn.h>
+
+#include <shibari/dcache.h>
+
+static int write_node_iter (void *data, void *aux)
+{
+  dcache_node_t *y = data ;
+  buffer *b = aux ;
+  char pack[TAI_PACK * 2 + 4] ;
+  tai_pack(pack, tain_secp(&y->entry)) ;
+  tai_pack(pack + TAI_PACK, tain_secp(&y->expire)) ;
+  uint16_pack(pack + TAI_PACK * 2, y->key.len) ;
+  uint16_pack(pack + TAI_PACK * 2 + 2, y->datalen) ;
+  if (buffer_put(b, pack, TAI_PACK * 2 + 4) == -1) return 0 ;
+  if (buffer_put(b, y->key.s, y->key.len + y->datalen) == -1) return 0 ;
+  if (buffer_put(b, "", 1) == -1) return 0 ;
+  return 1 ;
+}
+
+static inline int dcache_save_to_buffer (dcache_t const *z, buffer *b)
+{
+  char pack[16] ;
+  if (buffer_puts(b, DCACHE_MAGIC) == -1) return 0 ;
+  uint64_pack_big(pack, z->size) ;
+  uint64_pack_big(pack + 8, z->motion) ;
+  if (buffer_put(b, pack, 16) < 16) return 0 ;
+
+ /* XXX: can gensetdyn_iter blow up the stack if z->storage is huge? */
+  if (gensetdyn_iter_nocancel((gensetdyn *)&z->storage, gensetdyn_n(&z->storage), &write_node_iter, b) < gensetdyn_n(&z->storage)) return 0 ;
+
+  return buffer_flush(b) ;
+}
+
+#define N 8192
+
+int dcache_save (dcache_t const *z, char const *file)
+{
+  size_t len = strlen(file) ;
+  int fd ;
+  buffer b ;
+  char buf[N] ;
+  char tmp[len + 20] ;
+  memcpy(tmp, file, len) ;
+  memcpy(tmp + len, ":dcache_save:XXXXXX", 20) ;
+  fd = mkstemp(tmp) ;
+  if (fd == -1) return 0 ;
+  buffer_init(&b, &buffer_write, fd, buf, N) ;
+  if (!dcache_save_to_buffer(z, &b) || fsync(fd) < 0) goto err2 ;
+  fd_close(fd) ;
+  if (rename(tmp, file) == -1) goto err1 ;
+  return 1 ;
+
+ err2:
+  fd_close(fd) ;
+ err1:
+  unlink_void(tmp) ;
+  return 0 ;
+}
diff --git a/src/cache/dcache_search.c b/src/cache/dcache_search.c
new file mode 100644
index 0000000..0239b88
--- /dev/null
+++ b/src/cache/dcache_search.c
@@ -0,0 +1,15 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <skalibs/avltree.h>
+
+#include <shibari/dcache.h>
+#include "dcache-internal.h"
+
+dcache_node_t *dcache_search (dcache_t *z, char const *key, uint16_t keylen)
+{
+  uint32_t i ;
+  dcache_key_t k = { .s = (char *)key, .len = keylen } ;
+  return avltree_search(&z->by_key, &k, &i) ? DNODE(z, i) : 0 ;
+}
diff --git a/src/cache/deps-lib/dcache b/src/cache/deps-lib/dcache
new file mode 100644
index 0000000..fcc09a0
--- /dev/null
+++ b/src/cache/deps-lib/dcache
@@ -0,0 +1,8 @@
+dcache_add.o
+dcache_clean_expired.o
+dcache_delete.o
+dcache_free.o
+dcache_init.o
+dcache_load.o
+dcache_save.o
+dcache_search.o
diff --git a/src/common/deps-lib/shibari-common b/src/common/deps-lib/shibari-common
new file mode 100644
index 0000000..a5c44a4
--- /dev/null
+++ b/src/common/deps-lib/shibari-common
@@ -0,0 +1,10 @@
+shibari_log_answer.o
+shibari_log_exit.o
+shibari_log_query.o
+shibari_log_queryplus.o
+shibari_log_start.o
+shibari_util_qtype_num.o
+shibari_util_qtype_str.o
+shibari_util_rcode_str.o
+shibari_util_get_prefixlen.o
+-lskarnet
diff --git a/src/common/shibari_log_answer.c b/src/common/shibari_log_answer.c
new file mode 100644
index 0000000..f34cee5
--- /dev/null
+++ b/src/common/shibari_log_answer.c
@@ -0,0 +1,36 @@
+/* ISC license. */
+
+#include <skalibs/uint16.h>
+#include <skalibs/strerr.h>
+
+#include <s6-dns/s6dns-message.h>
+
+#include <shibari/util.h>
+#include <shibari/log.h>
+
+void shibari_log_answer (uint32_t v, s6dns_message_header_t const *hdr, uint16_t len)
+{
+  if (v < 2) return ;
+  if (hdr->rcode)
+  {
+    char fmtr[UINT16_FMT] ;
+    fmtr[uint16_fmt(fmtr, hdr->rcode)] = 0 ;
+    strerr_warni4x("answer ", fmtr, " ", shibari_util_rcode_str(hdr->rcode)) ;
+  }
+  else
+  {
+    size_t pos = 0 ;
+    char fmt[UINT16_FMT << 2] ;
+    char fmtl[UINT16_FMT] ;
+    pos += uint16_fmt(fmt + pos, hdr->counts.qd) ;
+    fmt[pos++] = '+' ;
+    pos += uint16_fmt(fmt + pos, hdr->counts.an) ;
+    fmt[pos++] = '+' ;
+    pos += uint16_fmt(fmt + pos, hdr->counts.ns) ;
+    fmt[pos++] = '+' ;
+    pos += uint16_fmt(fmt + pos, hdr->counts.qd) ;
+    fmt[pos] = 0 ;
+    fmtl[uint16_fmt(fmtl, len)] = 0 ;
+    strerr_warni5x("answer 0 noerror ", fmt, " len ", fmtl, hdr->tc ? " tc" : "") ;
+  }
+}
diff --git a/src/common/shibari_log_exit.c b/src/common/shibari_log_exit.c
new file mode 100644
index 0000000..8e383c6
--- /dev/null
+++ b/src/common/shibari_log_exit.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+
+#include <shibari/log.h>
+
+void shibari_log_exit (uint32_t v, int e)
+{
+  char fmt[UINT_FMT] ;
+  if (v < 2) return ;
+  fmt[uint_fmt(fmt, (unsigned int)e)] = 0 ;
+  strerr_warni2x("exit ", fmt) ;
+}
diff --git a/src/common/shibari_log_query.c b/src/common/shibari_log_query.c
new file mode 100644
index 0000000..b04ee27
--- /dev/null
+++ b/src/common/shibari_log_query.c
@@ -0,0 +1,18 @@
+/* ISC license. */
+
+#include <skalibs/strerr.h>
+
+#include <s6-dns/s6dns-domain.h>
+
+#include <shibari/util.h>
+#include <shibari/log.h>
+
+void shibari_log_query (uint32_t v, s6dns_domain_t const *q, uint16_t qtype)
+{
+  char qs[256] ;
+  s6dns_domain_t qe ;
+  if (v < 2) return ;
+  qe = *q ;
+  if (!s6dns_domain_encode(&qe) || !s6dns_domain_tostring(qs, 256, &qe)) return ;
+  strerr_warni4x("query ", shibari_util_qtype_str(qtype), " ", qs) ;
+}
diff --git a/src/common/shibari_log_queryplus.c b/src/common/shibari_log_queryplus.c
new file mode 100644
index 0000000..7e7fecc
--- /dev/null
+++ b/src/common/shibari_log_queryplus.c
@@ -0,0 +1,24 @@
+/* ISC license. */
+
+#include <skalibs/uint16.h>
+#include <skalibs/ip46.h>
+#include <skalibs/strerr.h>
+
+#include <s6-dns/s6dns-domain.h>
+
+#include <shibari/util.h>
+#include <shibari/log.h>
+
+void shibari_log_queryplus (uint32_t v, s6dns_domain_t const *q, uint16_t qtype, ip46 const *ip, uint16_t port)
+{
+  char qs[256] ;
+  char fmti[IP46_FMT] ;
+  char fmtp[UINT16_FMT] ;
+  s6dns_domain_t qe ;
+  if (v < 2) return ;
+  qe = *q ;
+  if (!s6dns_domain_encode(&qe) || !s6dns_domain_tostring(qs, 256, &qe)) return ;
+  fmti[ip46_fmt(fmti, ip)] = 0 ;
+  fmtp[uint16_fmt(fmtp, port)] = 0 ;
+  strerr_warni8x("query ", shibari_util_qtype_str(qtype), " ", qs, " ip ", fmti, " port ", fmtp) ;
+}
diff --git a/src/common/shibari_log_start.c b/src/common/shibari_log_start.c
new file mode 100644
index 0000000..214dc60
--- /dev/null
+++ b/src/common/shibari_log_start.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <skalibs/uint16.h>
+#include <skalibs/ip46.h>
+#include <skalibs/strerr.h>
+
+#include <shibari/log.h>
+
+void shibari_log_start (uint32_t v, ip46 const *ip, uint16_t port)
+{
+  char fmti[IP46_FMT] ;
+  char fmtp[UINT16_FMT] ;
+  if (v < 2) return ;
+  fmti[ip46_fmt(fmti, ip)] = 0 ;
+  fmtp[uint16_fmt(fmtp, port)] = 0 ;
+  strerr_warni4x("start ip ", fmti, " port ", fmtp) ;
+}
diff --git a/src/common/shibari_util_get_prefixlen.c b/src/common/shibari_util_get_prefixlen.c
new file mode 100644
index 0000000..784a3ee
--- /dev/null
+++ b/src/common/shibari_util_get_prefixlen.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <shibari/util.h>
+
+int shibari_util_get_prefixlen (char const *name, uint16_t namelen, char const *zone, uint16_t zonelen)
+{
+  return
+    namelen < zonelen ? -1 :
+    memcmp(name + namelen - zonelen, zone, zonelen) ? -1 :
+    namelen - zonelen ;
+}
diff --git a/src/common/shibari_util_qtype_num.c b/src/common/shibari_util_qtype_num.c
new file mode 100644
index 0000000..350e268
--- /dev/null
+++ b/src/common/shibari_util_qtype_num.c
@@ -0,0 +1,121 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <strings.h>
+#include <stdlib.h>
+
+#include <shibari/util.h>
+
+struct map_s
+{
+  char const *s ;
+  uint16_t num ;
+} ;
+
+static int map_cmp (void const *a, void const *b)
+{
+  return strcasecmp((char const *)a, ((struct map_s const *)b)->s) ;
+}
+
+#define BSEARCH(key, array) bsearch(key, (array), sizeof(array)/sizeof(struct map_s), sizeof(struct map_s), &map_cmp)
+
+static struct map_s const qtype_table[] =
+{
+  { "*", 255 },
+  { "A", 1 },
+  { "A6", 38 },
+  { "AAAA", 28 },
+  { "AFSDB", 18 },
+  { "AMTRELAY", 260 },
+  { "ANY", 255 },
+  { "APL", 42 },
+  { "ATMA", 34 },
+  { "AVC", 258 },
+  { "AXFR", 252 },
+  { "CAA", 257 },
+  { "CDNSKEY", 60 },
+  { "CDS", 59 },
+  { "CERT", 37 },
+  { "CNAME", 5 },
+  { "CSYNC", 62 },
+  { "DHCID", 49 },
+  { "DLV", 32769 },
+  { "DNAME", 39 },
+  { "DNSKEY", 48 },
+  { "DOA", 259 },
+  { "DS", 43 },
+  { "EID", 31 },
+  { "EUI48", 108 },
+  { "EUI64", 109 },
+  { "GID", 102 },
+  { "GPOS", 27 },
+  { "HINFO", 13 },
+  { "HIP", 55 },
+  { "HTTPS", 65 },
+  { "IPSECKEY", 45 },
+  { "ISDN", 20 },
+  { "IXFR", 251 },
+  { "KEY", 25 },
+  { "KX", 36 },
+  { "L32", 105 },
+  { "L64", 106 },
+  { "LOC", 29 },
+  { "LP", 107 },
+  { "MAILA", 254 },
+  { "MAILB", 253 },
+  { "MB", 7 },
+  { "MD", 3 },
+  { "MF", 4 },
+  { "MG", 8 },
+  { "MINFO", 14 },
+  { "MR", 9 },
+  { "MX", 15 },
+  { "NAPTR", 35 },
+  { "NID", 104 },
+  { "NIMLOC", 32 },
+  { "NINFO", 56 },
+  { "NS", 2 },
+  { "NSAP", 22 },
+  { "NSAP-PTR", 23 },
+  { "NSEC", 47 },
+  { "NSEC3", 50 },
+  { "NSEC3PARAM", 51 },
+  { "NULL", 10 },
+  { "NXT", 30 },
+  { "OPENPGPKEY", 61 },
+  { "OPT", 41 },
+  { "PTR", 12 },
+  { "PX", 26 },
+  { "RESINFO", 261 },
+  { "RKEY", 57 },
+  { "RP", 17 },
+  { "RRSIG", 46 },
+  { "RT", 21 },
+  { "SIG", 24 },
+  { "SINK", 40 },
+  { "SMIMEA", 53 },
+  { "SOA", 6 },
+  { "SPF", 99 },
+  { "SRV", 33 },
+  { "SSHFP", 44 },
+  { "SVCB", 64 },
+  { "TA", 32768 },
+  { "TALINK", 58 },
+  { "TKEY", 249 },
+  { "TLSA", 52 },
+  { "TSIG", 250 },
+  { "TXT", 16 },
+  { "UID", 101 },
+  { "UINFO", 100 },
+  { "UNSPEC", 103 },
+  { "URI", 256 },
+  { "WKS", 11 },
+  { "X25", 19 },
+  { "ZONEMD", 63 }
+} ;
+
+uint16_t shibari_util_qtype_num (char const *s)
+{
+  struct map_s const *p = BSEARCH(s, qtype_table) ;
+  return p ? p->num : 0 ;
+}
diff --git a/src/common/shibari_util_qtype_str.c b/src/common/shibari_util_qtype_str.c
new file mode 100644
index 0000000..97b701d
--- /dev/null
+++ b/src/common/shibari_util_qtype_str.c
@@ -0,0 +1,285 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <shibari/util.h>
+
+static char const *const qtype_table[262] =
+{
+  "error",
+  "A",
+  "NS",
+  "MD",
+  "MF",
+  "CNAME",
+  "SOA",
+  "MB"
+  "MG",
+  "MR",
+  "NULL",
+  "WKS",
+  "PTR",
+  "HINFO",
+  "MINFO",
+  "MX",
+  "TXT",
+  "RP",
+  "AFSDB",
+  "X25",
+  "ISDN",
+  "RT",
+  "NSAP",
+  "NSAP-PTR",
+  "SIG",
+  "KEY",
+  "PX",
+  "GPOS",
+  "AAAA",
+  "LOC",
+  "NXT",
+  "EID",
+  "NIMLOC",
+  "SRV",
+  "ATMA",
+  "NAPTR",
+  "KX",
+  "CERT",
+  "A6",
+  "DNAME",
+  "SINK",
+  "OPT",
+  "APL",
+  "DS",
+  "SSHFP",
+  "IPSECKEY",
+  "RRSIG",
+  "NSEC",
+  "DNSKEY",
+  "DHCID",
+  "NSEC3",
+  "NSEC3PARAM",
+  "TLSA",
+  "SMIMEA",
+  "unassigned",
+  "HIP",
+  "NINFO",
+  "RKEY",
+  "TALINK",
+  "CDS",
+  "CDNSKEY",
+  "OPENPGPKEY",
+  "CSYNC",
+  "ZONEMD",
+  "SVCB",
+  "HTTPS",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "SPF",
+  "UINFO",
+  "UID",
+  "GID",
+  "UNSPEC",
+  "NID",
+  "L32",
+  "L64",
+  "LP",
+  "EUI48",
+  "EUI64",
+
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+  "unassigned",
+
+  "TKEY",
+  "TSIG",
+  "IXFR",
+  "AXFR",
+  "MAILB",
+  "MAILA",
+  "*",
+  "URI",
+  "CAA",
+  "AVC",
+  "DOA",
+  "AMTRELAY",
+  "RESINFO"
+} ;
+
+
+char const *shibari_util_qtype_str (uint16_t qtype)
+{
+  if (qtype < 262) return qtype_table[qtype] ;
+  if (qtype < 32768) return "unassigned" ;
+  if (qtype == 32768) return "TA" ;
+  if (qtype == 32769) return "DLV" ;
+  if (qtype < 65279) return "unassigned" ;
+  if (qtype < 65535) return "private" ;
+  return "reserved" ;
+}
diff --git a/src/common/shibari_util_rcode_str.c b/src/common/shibari_util_rcode_str.c
new file mode 100644
index 0000000..142cc0e
--- /dev/null
+++ b/src/common/shibari_util_rcode_str.c
@@ -0,0 +1,41 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <shibari/util.h>
+
+char const *shibari_util_rcode_str (uint16_t rcode)
+{
+  static char const *const rcode_table[24] =
+  {
+    "noerror",
+    "formerr",
+    "servfail",
+    "nxdomain",
+    "notimp",
+    "refused",
+    "yxdomain",
+    "yxrrset",
+    "nxrrset",
+    "notauth",
+    "notzone",
+    "dsotypeni",
+    "unassigned",
+    "unassigned",
+    "unassigned",
+    "unassigned",
+    "badsig",
+    "badkey",
+    "badtime",
+    "badmode",
+    "badname",
+    "badalg",
+    "badtrunc",
+    "badcookie"
+  } ;
+  if (rcode < 24) return rcode_table[rcode] ;
+  if (rcode < 3841) return "unassigned" ;
+  if (rcode < 4096) return "private" ;
+  if (rcode < 65535) return "unassigned" ;
+  return "reserved" ;
+}
diff --git a/src/include/shibari/cache.h b/src/include/shibari/cache.h
new file mode 100644
index 0000000..d0c6004
--- /dev/null
+++ b/src/include/shibari/cache.h
@@ -0,0 +1,8 @@
+/* ISC license. */
+
+#ifndef SHIBARI_CACHE_H
+#define SHIBARI_CACHE_H
+
+#include <shibari/dcache.h>
+
+#endif
diff --git a/src/include/shibari/client.h b/src/include/shibari/client.h
new file mode 100644
index 0000000..03f8b1b
--- /dev/null
+++ b/src/include/shibari/client.h
@@ -0,0 +1,6 @@
+/* ISC license. */
+
+#ifndef SHIBARI_CLIENT_H
+#define SHIBARI_CLIENT_H
+
+#endif
diff --git a/src/include/shibari/common.h b/src/include/shibari/common.h
new file mode 100644
index 0000000..d36d055
--- /dev/null
+++ b/src/include/shibari/common.h
@@ -0,0 +1,9 @@
+/* ISC license. */
+
+#ifndef SHIBARI_COMMON_H
+#define SHIBARI_COMMON_H
+
+#include <shibari/constants.h>
+#include <shibari/util.h>
+
+#endif
diff --git a/src/include/shibari/constants.h b/src/include/shibari/constants.h
new file mode 100644
index 0000000..a4f86f3
--- /dev/null
+++ b/src/include/shibari/constants.h
@@ -0,0 +1,109 @@
+/* ISC license. */
+
+#ifndef SHIBARI_CONSTANTS_H
+#define SHIBARI_CONSTANTS_H
+
+enum shibari_qclass_e
+{
+  SHIBARI_C_IN = 1,
+  SHIBARI_C_CH = 3,
+  SHIBARI_C_HS = 4,
+  SHIBARI_C_NONE = 254,
+  SHIBARI_C_ANY = 255,
+} ;
+
+enum shibari_qtype_e
+{
+  SHIBARI_T_A = 1,
+  SHIBARI_T_NS = 2,
+  SHIBARI_T_MD = 3,
+  SHIBARI_T_MF = 4,
+  SHIBARI_T_CNAME = 5,
+  SHIBARI_T_SOA = 6,
+  SHIBARI_T_MB = 7,
+  SHIBARI_T_MG = 8,
+  SHIBARI_T_MR = 9,
+  SHIBARI_T_NULL = 10,
+  SHIBARI_T_WKS = 11,
+  SHIBARI_T_PTR = 12,
+  SHIBARI_T_HINFO = 13,
+  SHIBARI_T_MINFO = 14,
+  SHIBARI_T_MX = 15,
+  SHIBARI_T_TXT = 16,
+  SHIBARI_T_RP = 17,
+  SHIBARI_T_AFSDB = 18,
+  SHIBARI_T_X25 = 19,
+  SHIBARI_T_ISDN = 20,
+  SHIBARI_T_RT = 21,
+  SHIBARI_T_NSAP = 22,
+  SHIBARI_T_NSAP_PTR = 23,
+  SHIBARI_T_SIG = 24,
+  SHIBARI_T_KEY = 25,
+  SHIBARI_T_PX = 26,
+  SHIBARI_T_GPOS = 27,
+  SHIBARI_T_AAAA = 28,
+  SHIBARI_T_LOC = 29,
+  SHIBARI_T_NXT = 30,
+  SHIBARI_T_EID = 31,
+  SHIBARI_T_NIMLOC = 32,
+  SHIBARI_T_SRV = 33,
+  SHIBARI_T_ATMA = 34,
+  SHIBARI_T_NAPTR = 35,
+  SHIBARI_T_KX = 36,
+  SHIBARI_T_CERT = 37,
+  SHIBARI_T_A6 = 38,
+  SHIBARI_T_DNAME = 39,
+  SHIBARI_T_SINK = 40,
+  SHIBARI_T_OPT = 41,
+  SHIBARI_T_APL = 42,
+  SHIBARI_T_DS = 43,
+  SHIBARI_T_SSHFP = 44,
+  SHIBARI_T_IPSECKEY = 45,
+  SHIBARI_T_RRSIG = 46,
+  SHIBARI_T_NSEC = 47,
+  SHIBARI_T_DNSKEY = 48,
+  SHIBARI_T_DHCID = 49,
+  SHIBARI_T_NSEC3 = 50,
+  SHIBARI_T_NSEC3PARAM = 51,
+  SHIBARI_T_TLSA = 52,
+  SHIBARI_T_SMIMEA = 53,
+  SHIBARI_T_HIP = 55,
+  SHIBARI_T_NINFO = 56,
+  SHIBARI_T_RKEY = 57,
+  SHIBARI_T_TALINK = 58,
+  SHIBARI_T_CDS = 59,
+  SHIBARI_T_CDNSKEY = 60,
+  SHIBARI_T_OPENPGPKEY = 61,
+  SHIBARI_T_CSYNC = 62,
+  SHIBARI_T_ZONEMD = 63,
+  SHIBARI_T_SVCB = 64,
+  SHIBARI_T_HTTPS = 65,
+  SHIBARI_T_SPF = 99,
+  SHIBARI_T_UINFO = 100,
+  SHIBARI_T_UID = 101,
+  SHIBARI_T_GID = 102,
+  SHIBARI_T_UNSPEC = 103,
+  SHIBARI_T_NID = 104,
+  SHIBARI_T_L32 = 105,
+  SHIBARI_T_L64 = 106,
+  SHIBARI_T_LP = 107,
+  SHIBARI_T_EUI48 = 108,
+  SHIBARI_T_EUI64 = 109,
+  SHIBARI_T_TKEY = 249,
+  SHIBARI_T_TSIG = 250,
+  SHIBARI_T_IXFR = 251,
+  SHIBARI_T_AXFR = 252,
+  SHIBARI_T_MAILB = 253,
+  SHIBARI_T_MAILA = 254,
+  SHIBARI_T_ANY = 255,
+  SHIBARI_T_URI = 256,
+  SHIBARI_T_CAA = 257,
+  SHIBARI_T_AVC = 258,
+  SHIBARI_T_DOA = 259,
+  SHIBARI_T_AMIRELAY = 260,
+  SHIBARI_T_RESINFO = 261,
+  SHIBARI_T_TA = 32768,
+  SHIBARI_T_DLV = 32769
+} ;
+
+#endif
diff --git a/src/include/shibari/dcache.h b/src/include/shibari/dcache.h
new file mode 100644
index 0000000..6e0d0ab
--- /dev/null
+++ b/src/include/shibari/dcache.h
@@ -0,0 +1,54 @@
+/* ISC license. */
+
+#ifndef SHIBARI_DCACHE_H
+#define SHIBARI_DCACHE_H
+
+#include <stdint.h>
+
+#include <skalibs/uint64.h>
+#include <skalibs/tai.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/avltree.h>
+
+#define DCACHE_MAGIC "--DCACHE--\n"
+
+typedef struct dcache_key_s dcache_key_t, *dcache_key_t_ref ;
+struct dcache_key_s
+{
+  char *s ;
+  uint16_t len ;
+} ;
+
+typedef struct dcache_node_s dcache_node_t, *dcache_node_t_ref ;
+struct dcache_node_s
+{
+  dcache_key_t key ;
+  uint16_t datalen ;
+  tain entry ;
+  tain expire ;
+} ;
+
+typedef struct dcache_s dcache_t, *dcache_t_ref ;
+struct dcache_s
+{
+  gensetdyn storage ; /* dcache_node_t */
+  avltree by_key ;
+  avltree by_entry ;
+  avltree by_expire ;
+  uint64_t size ;
+  uint64_t motion ;
+} ;
+#define DCACHE_ZERO { .storage = GENSETDYN_ZERO, .by_key = AVLTREE_ZERO, .by_entry = AVLTREE_ZERO, .by_expire = AVLTREE_ZERO, .size = 0, .motion = 0 }
+
+extern void dcache_init (dcache_t *, uint64_t) ;
+extern dcache_node_t *dcache_search (dcache_t *, char const *, uint16_t) ;
+extern int dcache_add (dcache_t *, uint64_t, char const *, uint16_t, char const *, uint16_t, tain const *, tain const *) ;
+#define dcache_add_g(d, max, key, keylen, data, datalen, expire) dcache_add(d, max, key, keylen, data, datalen, (expire), &STAMP)
+extern void dcache_clean_expired (dcache_t *, tain const *) ;
+#define dcache_clean_expired_g(d) dcache_clean_expired((d), &STAMP)
+extern void dcache_free (dcache_t *) ;
+
+extern int dcache_save (dcache_t const *, char const *) ;
+extern int dcache_load (dcache_t *, uint64_t, char const *) ;
+
+#endif
diff --git a/src/include/shibari/log.h b/src/include/shibari/log.h
new file mode 100644
index 0000000..a01e499
--- /dev/null
+++ b/src/include/shibari/log.h
@@ -0,0 +1,20 @@
+/* ISC license. */
+
+#ifndef SHIBARI_LOG_H
+#define SHIBARI_LOG_H
+
+#include <stdint.h>
+
+#include <skalibs/ip46.h>
+
+#include <s6-dns/s6dns-domain.h>
+#include <s6-dns/s6dns-message.h>
+
+extern void shibari_log_start (uint32_t, ip46 const *, uint16_t) ;
+extern void shibari_log_exit (uint32_t, int) ;
+
+extern void shibari_log_query (uint32_t, s6dns_domain_t const *, uint16_t) ;
+extern void shibari_log_queryplus (uint32_t, s6dns_domain_t const *, uint16_t, ip46 const *, uint16_t) ;
+extern void shibari_log_answer (uint32_t, s6dns_message_header_t const *, uint16_t) ;
+
+#endif
diff --git a/src/include/shibari/packet.h b/src/include/shibari/packet.h
new file mode 100644
index 0000000..1f4fa93
--- /dev/null
+++ b/src/include/shibari/packet.h
@@ -0,0 +1,39 @@
+/* ISC license. */
+
+#ifndef SHIBARI_PACKET_H
+#define SHIBARI_PACKET_H
+
+#include <stdint.h>
+
+#include <skalibs/cdb.h>
+#include <skalibs/tai.h>
+
+#include <s6-dns/s6dns-domain.h>
+#include <s6-dns/s6dns-message.h>
+
+#include <shibari/tdb.h>
+
+typedef struct shibari_packet_s shibari_packet, *shibari_packet_ref ;
+struct shibari_packet_s
+{
+  s6dns_message_header_t hdr ;
+  char *buf ;
+  uint16_t max ;
+  uint16_t pos ;
+  uint8_t flagtcp : 1 ;
+} ;
+#define SHIBARI_PACKET_ZERO { .hdr = S6DNS_MESSAGE_HEADER_ZERO, .buf = "", .pos = 0, .flagtcp = 0 }
+#define SHIBARI_PACKET_INIT(rbuf, rmax, tcp) { .hdr = S6DNS_MESSAGE_HEADER_ZERO, .buf = tcp ? rbuf + 2 : rbuf, .max = tcp ? rmax - 2 : rmax, .pos = 0, .flagtcp = !!tcp }
+
+extern void shibari_packet_init (shibari_packet *, char *, uint32_t, int) ;
+
+extern void shibari_packet_begin (shibari_packet *, uint16_t, s6dns_domain_t const *, uint16_t) ;
+extern void shibari_packet_end (shibari_packet *) ;
+
+extern int shibari_packet_add_rr (shibari_packet *, shibari_tdb_entry const *, int, uint16_t, unsigned int) ;
+extern unsigned int shibari_packet_add_glue (shibari_packet *, cdb const *, char const *, uint16_t, uint16_t, char const *, uint16_t, uint16_t, uint16_t, char const *, tain const *) ;
+extern unsigned int shibari_packet_assert_authority (shibari_packet *, cdb const *, char const *, uint16_t, uint16_t, char const *, tain const *) ;
+
+extern unsigned int shibari_packet_tdb_answer_query (shibari_packet *, cdb const *, s6dns_message_header_t const *, s6dns_domain_t const *, uint16_t, char const *, tain const *) ;
+
+#endif
diff --git a/src/include/shibari/server.h b/src/include/shibari/server.h
new file mode 100644
index 0000000..db5af40
--- /dev/null
+++ b/src/include/shibari/server.h
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#ifndef SHIBARI_SERVER_H
+#define SHIBARI_SERVER_H
+
+#include <shibari/log.h>
+#include <shibari/tdb.h>
+#include <shibari/packet.h>
+
+#endif
diff --git a/src/include/shibari/shibari.h b/src/include/shibari/shibari.h
new file mode 100644
index 0000000..a9242d3
--- /dev/null
+++ b/src/include/shibari/shibari.h
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#ifndef SHIBARI_H
+#define SHIBARI_H
+
+#include <shibari/common.h>
+#include <shibari/client.h>
+#include <shibari/cache.h>
+#include <shibari/server.h>
+
+#endif
diff --git a/src/include/shibari/tdb.h b/src/include/shibari/tdb.h
new file mode 100644
index 0000000..f61df5b
--- /dev/null
+++ b/src/include/shibari/tdb.h
@@ -0,0 +1,29 @@
+/* ISC license. */
+
+#ifndef SHIBARI_TDB_H
+#define SHIBARI_TDB_H
+
+#include <stdint.h>
+
+#include <skalibs/cdb.h>
+#include <skalibs/tai.h>
+
+#include <s6-dns/s6dns-message.h>
+
+typedef struct shibari_tdb_entry_s shibari_tdb_entry, *shibari_tdb_entry_ref ;
+struct shibari_tdb_entry_s
+{
+  uint16_t type ;
+  uint16_t len ;
+  uint32_t ttl ;
+  uint32_t flags ;
+  cdb_data key ;
+  cdb_data data ;
+} ;
+
+extern int shibari_tdb_entry_parse (shibari_tdb_entry *, char const *, uint16_t, uint16_t, unsigned int, char const *, tain const *) ;
+extern int shibari_tdb_read_entry (cdb const *, cdb_find_state *, shibari_tdb_entry *, char const *, uint16_t, uint16_t, unsigned int, char const *, tain const *, uint32_t *) ;
+extern int shibari_tdb_extract_domain (shibari_tdb_entry const *, cdb_data *) ;
+extern int shibari_tdb_find_authority (cdb const *, char const *, uint16_t, char const *, tain const *, int *) ;
+
+#endif
diff --git a/src/include/shibari/util.h b/src/include/shibari/util.h
new file mode 100644
index 0000000..82092a3
--- /dev/null
+++ b/src/include/shibari/util.h
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#ifndef SHIBARI_UTIL_H
+#define SHIBARI_UTIL_H
+
+#include <stdint.h>
+
+extern char const *shibari_util_qtype_str (uint16_t) ;
+extern uint16_t shibari_util_qtype_num (char const *) ;
+extern char const *shibari_util_rcode_str (uint16_t) ;
+
+extern int shibari_util_get_prefixlen (char const *, uint16_t, char const *, uint16_t) ;
+
+#endif
diff --git a/src/server/deps-exe/shibari-server-tcp b/src/server/deps-exe/shibari-server-tcp
new file mode 100644
index 0000000..c25f645
--- /dev/null
+++ b/src/server/deps-exe/shibari-server-tcp
@@ -0,0 +1,4 @@
+${LIBSHIBARI_SERVER}
+${LIBSHIBARI_COMMON}
+-ls6dns
+-lskarnet
diff --git a/src/server/deps-exe/shibari-server-udp b/src/server/deps-exe/shibari-server-udp
new file mode 100644
index 0000000..0c1f81d
--- /dev/null
+++ b/src/server/deps-exe/shibari-server-udp
@@ -0,0 +1,6 @@
+${LIBSHIBARI_SERVER}
+${LIBSHIBARI_COMMON}
+-ls6dns
+-ls6
+-lskarnet
+${SOCKET_LIB}
diff --git a/src/server/deps-lib/shibari-server b/src/server/deps-lib/shibari-server
new file mode 100644
index 0000000..7c5b981
--- /dev/null
+++ b/src/server/deps-lib/shibari-server
@@ -0,0 +1,13 @@
+shibari_packet_init.o
+shibari_packet_begin.o
+shibari_packet_end.o
+shibari_packet_add_rr.o
+shibari_tdb_entry_parse.o
+shibari_tdb_extract_domain.o
+shibari_tdb_find_authority.o
+shibari_tdb_read_entry.o
+shibari_packet_add_glue.o
+shibari_packet_assert_authority.o
+shibari_packet_tdb_answer_query.o
+-ls6dns
+-lskarnet
diff --git a/src/server/shibari-server-tcp.c b/src/server/shibari-server-tcp.c
new file mode 100644
index 0000000..e646a44
--- /dev/null
+++ b/src/server/shibari-server-tcp.c
@@ -0,0 +1,235 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/sig.h>
+#include <skalibs/tai.h>
+#include <skalibs/ip46.h>
+#include <skalibs/cdb.h>
+#include <skalibs/unix-timed.h>
+
+#include <s6-dns/s6dns-domain.h>
+#include <s6-dns/s6dns-message.h>
+
+#include <shibari/common.h>
+#include <shibari/server.h>
+
+#define PROGNAME "shibari-server-tcp"
+#define USAGE PROGNAME " [ -v verbosity ] [ -f cdbfile ] [ -r timeout ] [ -w timeout ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+#define QMAX 2048
+#define RMAX 65535
+
+static uint32_t verbosity = 1 ;
+
+static inline void get_socket_info (ip46 *localip, uint16_t *localport, ip46 *remoteip, uint16_t *remoteport)
+{
+  char const *x = getenv("PROTO") ;
+  if (!x) strerr_dienotset(100, "PROTO") ;
+  {
+    size_t protolen = strlen(x) ;
+    char var[protolen + 11] ;
+    memcpy(var, x, protolen) ;
+    memcpy(var + protolen, "LOCALIP", 8) ;
+    x = getenv(var) ;
+    if (!x) strerr_dienotset(100, var) ;
+    if (!ip46_scan(x, localip)) strerr_dieinvalid(100, var) ;
+    memcpy(var + protolen + 5, "PORT", 5) ;
+    x = getenv(var) ;
+    if (!x) strerr_dienotset(100, var) ;
+    if (!uint160_scan(x, localport)) strerr_dieinvalid(100, var) ;
+    memcpy(var + protolen, "REMOTEIP", 9) ;
+    x = getenv(var) ;
+    if (!x) strerr_dienotset(100, var) ;
+    if (!ip46_scan(x, remoteip)) strerr_dieinvalid(100, var) ;
+    memcpy(var + protolen + 6, "PORT", 5) ;
+    x = getenv(var) ;
+    if (!x) strerr_dienotset(100, var) ;
+    if (!uint160_scan(x, remoteport)) strerr_dieinvalid(100, var) ;
+  }
+}
+
+static void add (shibari_packet *pkt, shibari_tdb_entry const *entry, int prefixlen, uint16_t id, s6dns_domain_t const *zone, tain const *deadline)
+{
+  if (!shibari_packet_add_rr(pkt, entry, prefixlen, 0, 2))
+  {
+    shibari_packet_end(pkt) ;
+    if (!buffer_timed_put_g(buffer_1, pkt->buf - 2, pkt->pos + 2, deadline))
+      strerr_diefu1sys(111, "write to stdout") ;
+    shibari_packet_begin(pkt, id, zone, SHIBARI_T_AXFR) ;
+    if (!shibari_packet_add_rr(pkt, entry, prefixlen, 0, 2))
+      strerr_dief1x(101, "can't happen: record too long to fit in single packet") ;
+  }
+}
+
+static inline int axfr (char const *axfrok, char const *loc, cdb const *tdb, s6dns_message_header_t const *qhdr, s6dns_domain_t const *zone, shibari_packet *pkt, tain const *deadline, tain const *wstamp)
+{
+  shibari_tdb_entry soa ;
+  shibari_tdb_entry cur ;
+  uint32_t pos = CDB_TRAVERSE_INIT() ;
+  if (!axfrok) return 5 ;
+  if (axfrok[0] != '*')
+  {
+    s6dns_domain_t decoded = *zone ;
+    unsigned int zonelen ;
+    size_t len = strlen(axfrok) + 1 ;
+    char buf[256] ;
+    if (!s6dns_domain_decode(&decoded)) return 1 ;
+    zonelen = s6dns_domain_tostring(buf, 256, &decoded) ;
+    while (len >= zonelen)
+    {
+      if (!strncmp(buf, axfrok, zonelen) && (!axfrok[zonelen] || strchr("/,; \t\n", axfrok[zonelen]))) break ;
+      axfrok += zonelen + 1 ;
+      len -= zonelen + 1 ;
+    }
+    if (len < zonelen) return 5 ;
+  }
+
+  {
+    cdb_find_state state = CDB_FIND_STATE_ZERO ;
+    int r = shibari_tdb_read_entry(tdb, &state, &soa, zone->s, zone->len, SHIBARI_T_SOA, 0, loc, wstamp, 0) ;
+    if (r == -1) return 2 ;
+    if (!r) return 9 ;
+  }
+
+  shibari_packet_begin(pkt, qhdr->id, zone, SHIBARI_T_AXFR) ;
+  pkt->hdr.aa = 1 ;
+  add(pkt, &soa, 0, qhdr->id, zone, deadline) ;
+
+  for (;;)
+  {
+    cdb_data data ;
+    int prefixlen ;
+    int r = cdb_traverse_next(tdb, &cur.key, &data, &pos) ;
+    if (r == -1) return 2 ;
+    if (!r) break ;
+    prefixlen = shibari_util_get_prefixlen(cur.key.s, cur.key.len, zone->s, zone->len) ;
+    if (prefixlen == -1) continue ;
+    r = shibari_tdb_entry_parse(&cur, data.s, data.len, SHIBARI_T_ANY, 2, loc, wstamp) ;
+    if (r == -1) return 2 ;
+    if (!r) continue ;
+    if (cur.type == SHIBARI_T_SOA) continue ;
+    add(pkt, &cur, prefixlen, qhdr->id, zone, deadline) ;
+  }
+
+  add(pkt, &soa, 0, qhdr->id, zone, deadline) ;
+  shibari_packet_end(pkt) ;
+  return 0 ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  cdb tdb = CDB_ZERO ;
+  char const *axfrok = getenv("AXFR") ;
+  char const *loc = getenv("LOC") ;
+  tain rtto = TAIN_INFINITE_RELATIVE, wtto = TAIN_INFINITE_RELATIVE ;
+  ip46 localip, remoteip ;
+  uint16_t localport, remoteport ;
+  char progbuf[sizeof(PROGNAME) + 5 + PID_FMT] = PROGNAME ": pid " ;
+  char buf[RMAX + 2] ;
+  shibari_packet pkt = SHIBARI_PACKET_INIT(buf, RMAX + 2, 1) ;
+  PROG = "shibari-server-tcp" ;
+
+  {
+    size_t pos = sizeof(PROGNAME) + 5 ;
+    pos += pid_fmt(progbuf + pos, getpid()) ;
+    progbuf[pos++] = 0 ;
+  }
+
+  {
+    char const *tdbfile = "data.cdb" ;
+    uint32_t r = 0, w = 0 ;
+    subgetopt l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      int opt = subgetopt_r(argc, argv, "v:f:r:w:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'v' : if (!uint320_scan(l.arg, &verbosity)) dieusage() ; break ;
+        case 'f' : tdbfile = l.arg ; break ;
+        case 'r' : if (!uint320_scan(l.arg, &r)) dieusage() ; break ;
+        case 'w' : if (!uint320_scan(l.arg, &w)) dieusage() ; break ;
+        default :  dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+    if (r) tain_from_millisecs(&rtto, r) ;
+    if (w) tain_from_millisecs(&wtto, w) ;
+    get_socket_info(&localip, &localport, &remoteip, &remoteport) ;
+    PROG = progbuf ;
+    if (!cdb_init(&tdb, tdbfile)) strerr_diefu2sys(111, "open DNS database file ", tdbfile) ;
+  }
+
+  if (!sig_altignore(SIGPIPE)) strerr_diefu1sys(111, "ignore SIGPIPE") ;
+  tain_now_set_stopwatch_g() ;
+  shibari_log_start(verbosity, &remoteip, remoteport) ;
+
+  for (;;)
+  {
+    tain wstamp ;
+    size_t w ;
+    tain deadline ;
+    s6dns_message_header_t hdr ;
+    s6dns_message_counts_t counts ;
+    s6dns_domain_t name ;
+    unsigned int rcode ;
+    uint16_t qtype ;
+    uint16_t len ;
+    tain_add_g(&deadline, &rtto) ;
+    w = buffer_timed_get_g(buffer_0, buf, 2, &deadline) ;
+    if (w == 1) strerr_dief1x(1, "invalid request") ;
+    if (!w)
+    {
+      if (errno != EPIPE && errno != ETIMEDOUT)
+        strerr_diefu1sys(111, "read from stdin") ;
+      else break ;
+    }
+    uint16_unpack_big(buf, &len) ;
+    if (len > QMAX) strerr_dief1x(1, "request too large") ;
+    if (buffer_timed_get_g(buffer_0, buf, len, &deadline) < len)
+      strerr_diefu1sys(111, "read from stdin") ;
+
+    if (!s6dns_message_parse_init(&hdr, &counts, buf, len, &rcode))
+      strerr_diefu1sys(111, "parse message") ;
+    if (hdr.opcode) { rcode = 4 ; goto answer ; }
+    if (!s6dns_message_parse_question(&counts, &name, &qtype, buf, len, &rcode) || !s6dns_domain_encode(&name))
+    {
+      rcode = errno == ENOTSUP ? 4 : 1 ;
+      goto answer ;
+    }
+    shibari_log_query(verbosity, &name, qtype) ;
+    tain_add_g(&deadline, &wtto) ;
+    tain_wallclock_read(&wstamp) ;
+    rcode = qtype == SHIBARI_T_AXFR ?
+      axfr(axfrok, loc, &tdb, &hdr, &name, &pkt, &deadline, &wstamp) :
+      shibari_packet_tdb_answer_query(&pkt, &tdb, &hdr, &name, qtype, loc, &wstamp) ;
+
+ answer:
+    if (rcode && rcode != 3)
+    {
+      shibari_packet_begin(&pkt, hdr.id, &name, qtype) ;
+      pkt.hdr.rcode = rcode ;
+      shibari_packet_end(&pkt) ;
+    }
+    shibari_log_answer(verbosity, &pkt.hdr, pkt.pos) ;
+    if (!buffer_timed_put_g(buffer_1, buf, pkt.pos + 2, &deadline)
+     || !buffer_timed_flush_g(buffer_1, &deadline))
+      strerr_diefu1sys(111, "write to stdout") ;
+  }
+
+  shibari_log_exit(verbosity, 0) ;
+  return 0 ;
+}
diff --git a/src/server/shibari-server-udp.c b/src/server/shibari-server-udp.c
new file mode 100644
index 0000000..d834c94
--- /dev/null
+++ b/src/server/shibari-server-udp.c
@@ -0,0 +1,207 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include <skalibs/posixplz.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
+#include <skalibs/types.h>
+#include <skalibs/error.h>
+#include <skalibs/strerr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/tai.h>
+#include <skalibs/socket.h>
+#include <skalibs/ip46.h>
+#include <skalibs/cdb.h>
+#include <skalibs/sig.h>
+
+#include <s6/accessrules.h>
+
+#include <shibari/common.h>
+#include <shibari/server.h>
+
+#define USAGE "shibari-server-udp [ -v verbosity ] [ -d notif ] [ -f cdbfile ] [ -i rulesdir | -x rulesfile ] [ -p port ] ip"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+#define VAR "LOC"
+
+static char const *tdbfile = "data.cdb" ;
+static cdb tdb = CDB_ZERO ;
+static cdb rules = CDB_ZERO ;
+static char const *rulesfile = 0 ;
+static unsigned int rulestype = 0 ;
+static int cont = 1 ;
+static uint32_t verbosity = 1 ;
+
+static void on_term (int s)
+{
+  (void)s ;
+  cont = 0 ;
+}
+
+static void on_hup (int s)
+{
+  cdb newtdb = CDB_ZERO ;
+  (void)s ;
+  if (!cdb_init(&newtdb, tdbfile))
+  {
+    if (verbosity) strerr_warnwu2sys("reopen DNS data file ", tdbfile) ;
+  }
+  else
+  {
+    cdb_free(&tdb) ;
+    tdb = newtdb ;
+  }
+  if (rulestype == 2)
+  {
+    cdb newrules = CDB_ZERO ;
+    if (!cdb_init(&newrules, rulesfile))
+    {
+      if (verbosity) strerr_warnwu2sys("reopen access rules file ", rulesfile) ;
+    }
+    else
+    {
+      cdb_free(&rules) ;
+      rules = newrules ;
+    }
+  }
+}
+
+static int check_rules (ip46 const *remoteip, s6_accessrules_params_t *params, char const **loc)
+{
+  s6_accessrules_result_t r ;
+  params->env.len = 0 ;
+  params->exec.len = 0 ;
+  r = rulestype == 2 ?
+    s6_accessrules_ip46_cdb(remoteip, &rules, params) :
+    s6_accessrules_ip46_fs(remoteip, rulesfile, params) ;
+  if (r != S6_ACCESSRULES_ALLOW) return 0 ;
+
+  if (params->env.len)
+  {
+    char const *p ;
+    if (params->env.s[params->env.len - 1])
+    {
+      if (verbosity)
+      {
+        char fmt[IP46_FMT] ;
+        fmt[ip46_fmt(fmt, remoteip)] = 0 ;
+        strerr_warnw6x("invalid environment parameters in rules ", rulestype == 2 ? "cdb " : "directory ", rulesfile, " for ip ", fmt, " - denying connection") ;
+      }
+      return 0 ;
+    }
+    p = memmem(params->env.s, params->env.len - 1, VAR "=", sizeof(VAR)) ;
+    if (p && (p == params->env.s || !p[-1])) *loc = p + sizeof(VAR) ;
+  }
+  return 1 ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  s6_accessrules_params_t params = S6_ACCESSRULES_PARAMS_ZERO ;
+  int s ;
+  unsigned int notif = 0 ;
+  char buf[512] ;
+  shibari_packet pkt = SHIBARI_PACKET_INIT(buf, 512, 0) ;
+  uint16_t localport = 53 ;
+  ip46 localip ;
+
+  PROG = "shibari-server-udp" ;
+
+  {
+    subgetopt l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      int opt = subgetopt_r(argc, argv, "v:d:f:i:x:p:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'v' : if (!uint320_scan(l.arg, &verbosity)) dieusage() ; break ;
+        case 'd' : if (!uint0_scan(l.arg, &notif)) dieusage() ; break ;
+        case 'f' : tdbfile = l.arg ; break ;
+        case 'i' : rulesfile = l.arg ; rulestype = 1 ; break ;
+        case 'x' : rulesfile = l.arg ; rulestype = 2 ; break ;
+        case 'p' : if (!uint160_scan(l.arg, &localport)) dieusage() ; break ;
+        default : strerr_dieusage(10, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if (!argc) dieusage() ;
+  if (!ip46_scan(argv[0], &localip)) dieusage() ;
+
+  if (notif)
+  {
+    if (notif < 3) strerr_dief1x(100, "notification fd cannot be 0, 1 or 2") ;
+    if (fcntl(notif, F_GETFD) == -1) strerr_diefu1sys(111, "check notification fd") ;
+  }
+
+  close(0) ;
+  close(1) ;
+  s = socket_udp46_b(ip46_is6(&localip)) ;
+  if (s == -1) strerr_diefu1sys(111, "create socket") ;
+  if (socket_bind46_reuse(s, &localip, localport) == -1) strerr_diefu1sys(111, "bind socket") ;
+
+  if (!cdb_init(&tdb, tdbfile)) strerr_diefu2sys(111, "open cdb file ", tdbfile) ;
+  if (rulestype == 2 && !cdb_init(&rules, rulesfile)) strerr_diefu2sys(111, "open rules file ", rulesfile) ;
+  if (!sig_catch(SIGHUP, &on_hup)) strerr_diefu1sys(111, "catch SIGHUP") ;
+  if (!sig_catch(SIGTERM, &on_term)) strerr_diefu1sys(111, "catch SIGTERM") ;
+
+  shibari_log_start(verbosity, &localip, localport) ;
+  if (notif)
+  {
+    write(notif, "\n", 1) ;
+    close(notif) ;
+  }
+
+  for (; cont ; sig_unblock(SIGHUP))
+  {
+    tain wstamp ;
+    char const *loc = 0 ;
+    s6dns_message_header_t hdr ;
+    s6dns_message_counts_t counts ;
+    s6dns_domain_t name ;
+    unsigned int rcode ;
+    ssize_t r ;
+    uint16_t qtype ;
+    uint16_t remoteport ;
+    ip46 remoteip ;
+
+    r = socket_recv46(s, buf, 512, &remoteip, &remoteport) ;
+    if (r == -1) strerr_diefu1sys(111, "recv from socket") ;
+    if (!r) strerr_dief1x(111, "huh? got EOF on a connection-less socket") ;
+    sig_block(SIGHUP) ;
+    if (rulestype && !check_rules(&remoteip, &params, &loc)) continue ;
+    if (!s6dns_message_parse_init(&hdr, &counts, buf, r, &rcode)) continue ;
+    if (hdr.opcode) { rcode = 4 ; goto answer ; }
+    if (!s6dns_message_parse_question(&counts, &name, &qtype, buf, r, &rcode) || !s6dns_domain_encode(&name))
+    {
+      rcode = errno == ENOTSUP ? 4 : 1 ;
+      goto answer ;
+    }
+    shibari_log_queryplus(verbosity, &name, qtype, &remoteip, remoteport) ;
+    tain_wallclock_read(&wstamp) ;
+    rcode = shibari_packet_tdb_answer_query(&pkt, &tdb, &hdr, &name, qtype, loc, &wstamp) ;
+
+ answer:
+    if (rcode && rcode != 3)
+    {
+      shibari_packet_begin(&pkt, hdr.id, &name, qtype) ;
+      pkt.hdr.rcode = rcode ;
+      shibari_packet_end(&pkt) ;
+    }
+    shibari_log_answer(verbosity, &pkt.hdr, pkt.pos) ;
+    if (socket_send46(s, buf, pkt.pos, &remoteip, remoteport) < pkt.pos && verbosity)
+      strerr_warnwu1sys("send answer") ;
+  }
+
+  shibari_log_exit(verbosity, 0) ;
+  return 0 ;
+}
diff --git a/src/server/shibari_packet_add_glue.c b/src/server/shibari_packet_add_glue.c
new file mode 100644
index 0000000..4a0abf1
--- /dev/null
+++ b/src/server/shibari_packet_add_glue.c
@@ -0,0 +1,48 @@
+/* ISC license. */
+
+#include <skalibs/cdb.h>
+
+#include <shibari/constants.h>
+#include <shibari/util.h>
+#include <shibari/tdb.h>
+#include <shibari/packet.h>
+
+static int shibari_packet_add_glue_for_rr (shibari_packet *pkt, cdb const *tdb, char const *s, uint16_t len, uint16_t prefixlen, uint16_t offset, char const *loc, tain const *stamp)
+{
+  cdb_find_state state = CDB_FIND_STATE_ZERO ;
+  for (;;)
+  {
+    shibari_tdb_entry entry ;
+    int r = shibari_tdb_read_entry(tdb, &state, &entry, s, len, SHIBARI_T_ANY, 0, loc, stamp, 0) ;
+    if (r == -1) return 2 ;
+    if (!r) break ;
+    if (entry.type != SHIBARI_T_A && entry.type != SHIBARI_T_AAAA) continue ;
+    if (!shibari_packet_add_rr(pkt, &entry, prefixlen, offset, 4))
+    {
+      pkt->hdr.tc = 1 ;
+      return 0 ;
+    }
+  }
+  return -1 ;
+}
+
+unsigned int shibari_packet_add_glue (shibari_packet *pkt, cdb const *tdb, char const *s, uint16_t len, uint16_t qtype, char const *z, uint16_t zlen, uint16_t zoffset, uint16_t wildpos, char const *loc, tain const *stamp)
+{
+  cdb_find_state state = CDB_FIND_STATE_ZERO ;
+  for (;;)
+  {
+    shibari_tdb_entry entry ;
+    cdb_data domain ;
+    int zprefixlen, sprefixlen ;
+    int r = shibari_tdb_read_entry(tdb, &state, &entry, s + wildpos, len - wildpos, qtype, !!wildpos, loc, stamp, 0) ;
+    if (r == -1) return 2 ;
+    if (!r) break ;
+    if (!shibari_tdb_extract_domain(&entry, &domain)) continue ;
+    zprefixlen = shibari_util_get_prefixlen(domain.s, domain.len, z, zlen) ;
+    if (zprefixlen == -1) continue ;
+    sprefixlen = shibari_util_get_prefixlen(domain.s, domain.len, s, len) ;
+    r = shibari_packet_add_glue_for_rr(pkt, tdb, domain.s, domain.len, sprefixlen == -1 ? zprefixlen : sprefixlen, sprefixlen == -1 ? zoffset : 0, loc, stamp) ;
+    if (r >= 0) return r ;
+  }
+  return 0 ;
+}
diff --git a/src/server/shibari_packet_add_rr.c b/src/server/shibari_packet_add_rr.c
new file mode 100644
index 0000000..b92e8dd
--- /dev/null
+++ b/src/server/shibari_packet_add_rr.c
@@ -0,0 +1,46 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
+
+#include <shibari/constants.h>
+#include <shibari/packet.h>
+
+int shibari_packet_add_rr (shibari_packet *p, shibari_tdb_entry const *entry, int prefixlen, uint16_t offset, unsigned int section)
+{
+  uint16_t *count[4] = { &p->hdr.counts.qd, &p->hdr.counts.an, &p->hdr.counts.ns, &p->hdr.counts.nr } ;
+  uint16_t rrlen = 10 + entry->data.len + (entry->flags & 1 ? 2 : 0) + (prefixlen >= 0 ? prefixlen + 2 : entry->key.len) ;
+  if (p->max - p->pos < rrlen) return 0 ;
+  if (entry->flags & 1)
+  {
+    p->buf[p->pos++] = 1 ;
+    p->buf[p->pos++] = '*' ;
+  }
+  if (prefixlen >= 0)
+  {
+    memcpy(p->buf + p->pos, entry->key.s, prefixlen) ;
+    p->pos += prefixlen ;
+    uint16_pack_big(p->buf + p->pos, 49164 + offset) ;
+    p->pos += 2 ;
+  }
+  else
+  {
+    memcpy(p->buf + p->pos, entry->key.s, entry->key.len) ;
+    p->pos += entry->key.len ;
+  }
+  uint16_pack_big(p->buf + p->pos, entry->type) ;
+  p->pos += 2 ;
+  uint16_pack_big(p->buf + p->pos, SHIBARI_C_IN) ;
+  p->pos += 2 ;
+  uint32_pack_big(p->buf + p->pos, entry->ttl) ;
+  p->pos += 4 ;
+  uint16_pack_big(p->buf + p->pos, entry->data.len) ;
+  p->pos += 2 ;
+  memcpy(p->buf + p->pos, entry->data.s, entry->data.len) ;
+  p->pos += entry->data.len ;
+  (*count[section-1])++ ;
+  return 1 ;
+}
diff --git a/src/server/shibari_packet_assert_authority.c b/src/server/shibari_packet_assert_authority.c
new file mode 100644
index 0000000..18c8299
--- /dev/null
+++ b/src/server/shibari_packet_assert_authority.c
@@ -0,0 +1,18 @@
+/* ISC license. */
+
+#include <skalibs/cdb.h>
+
+#include <shibari/constants.h>
+#include <shibari/util.h>
+#include <shibari/tdb.h>
+#include <shibari/packet.h>
+
+unsigned int shibari_packet_assert_authority (shibari_packet *pkt, cdb const *tdb, char const *z, uint16_t zlen, uint16_t zoffset, char const *loc, tain const *stamp)
+{
+  cdb_find_state state = CDB_FIND_STATE_ZERO ;
+  shibari_tdb_entry soa ;
+  int r = shibari_tdb_read_entry(tdb, &state, &soa, z, zlen, SHIBARI_T_SOA, 0, loc, stamp, 0) ;
+  if (r <= 0) return 2 ;
+  if (!shibari_packet_add_rr(pkt, &soa, 0, zoffset, 3)) pkt->hdr.tc = 1 ;
+  return 0 ;
+}
diff --git a/src/server/shibari_packet_begin.c b/src/server/shibari_packet_begin.c
new file mode 100644
index 0000000..5ea7b16
--- /dev/null
+++ b/src/server/shibari_packet_begin.c
@@ -0,0 +1,32 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include <skalibs/uint16.h>
+
+#include <shibari/constants.h>
+#include <shibari/packet.h>
+
+void shibari_packet_begin (shibari_packet *p, uint16_t id, s6dns_domain_t const *q, uint16_t qtype)
+{
+  p->hdr.id = id ;
+  p->hdr.qr = 1 ;
+  p->hdr.opcode = 0 ;
+  p->hdr.aa = 0 ;
+  p->hdr.tc = 0 ;
+  p->hdr.rd = 0 ;
+  p->hdr.ra = 0 ;
+  p->hdr.z = 0 ;
+  p->hdr.rcode = 0 ;
+  p->hdr.counts.qd = 1 ;
+  p->hdr.counts.an = 0 ;
+  p->hdr.counts.ns = 0 ;
+  p->hdr.counts.nr = 0 ;
+  p->pos = 12 ;
+  memcpy(p->buf + p->pos, q->s, q->len) ;
+  p->pos += q->len ;
+  uint16_pack_big(p->buf + p->pos, qtype) ;
+  p->pos += 2 ;
+  uint16_pack_big(p->buf + p->pos, SHIBARI_C_IN) ;
+  p->pos += 2 ;
+}
diff --git a/src/server/shibari_packet_end.c b/src/server/shibari_packet_end.c
new file mode 100644
index 0000000..41aca87
--- /dev/null
+++ b/src/server/shibari_packet_end.c
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#include <skalibs/uint16.h>
+
+#include <s6-dns/s6dns-message.h>
+
+#include <shibari/packet.h>
+
+void shibari_packet_end (shibari_packet *p)
+{
+  s6dns_message_header_pack(p->buf, &p->hdr) ;
+  if (p->flagtcp) uint16_pack_big(p->buf - 2, p->pos) ;
+}
diff --git a/src/server/shibari_packet_init.c b/src/server/shibari_packet_init.c
new file mode 100644
index 0000000..a0aff97
--- /dev/null
+++ b/src/server/shibari_packet_init.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <s6-dns/s6dns-message.h>
+
+#include <shibari/packet.h>
+
+void shibari_packet_init (shibari_packet *p, char *buf, uint32_t max, int istcp)
+{
+  p->hdr = s6dns_message_header_zero ;
+  p->buf = istcp ? buf + 2 : buf ;
+  p->max = istcp ? max - 2 : max ;
+  p->pos = 0 ;
+  p->flagtcp = !!istcp ;
+}
diff --git a/src/server/shibari_packet_tdb_answer_query.c b/src/server/shibari_packet_tdb_answer_query.c
new file mode 100644
index 0000000..a22927f
--- /dev/null
+++ b/src/server/shibari_packet_tdb_answer_query.c
@@ -0,0 +1,93 @@
+/* ISC license. */
+
+#include <skalibs/cdb.h>
+
+#include <shibari/constants.h>
+#include <shibari/tdb.h>
+#include <shibari/packet.h>
+
+static unsigned int childzone (shibari_packet *pkt, cdb const *tdb, s6dns_domain_t const *q, char const *loc, tain const *stamp, uint16_t nplen, uint16_t zplen)
+{
+  cdb_find_state state = CDB_FIND_STATE_ZERO ;
+  unsigned int gr ;
+  for (;;)
+  {
+    shibari_tdb_entry ns ;
+    int r = shibari_tdb_read_entry(tdb, &state, &ns, q->s + nplen, q->len - nplen, SHIBARI_T_NS, 0, loc, stamp, 0) ;
+    if (r == -1) return 2 ;
+    if (!r) break ;
+    r = shibari_packet_add_rr(pkt, &ns, nplen, 0, 3) ;
+    if (!r) { pkt->hdr.tc = 1 ; goto end ; }
+  }
+  gr = shibari_packet_add_glue(pkt, tdb, q->s + nplen, q->len - nplen, SHIBARI_T_NS, q->s + zplen, q->len - zplen, zplen, 0, loc, stamp) ;
+  if (gr > 0) return gr ;
+ end:
+  shibari_packet_end(pkt) ;
+  return 0 ;
+}
+
+unsigned int shibari_packet_tdb_answer_query (shibari_packet *pkt, cdb const *tdb, s6dns_message_header_t const *qhdr, s6dns_domain_t const *q, uint16_t qtype, char const *loc, tain const *stamp)
+{
+  unsigned int rcode = 0 ;
+  cdb_find_state state = CDB_FIND_STATE_ZERO ;
+  uint32_t flagyxdomain = 0 ;
+  int nplen, zplen ;
+  uint16_t gluetype = 0 ;
+  uint16_t wildpos = 0 ;
+
+  shibari_packet_begin(pkt, qhdr->id, q, qtype) ;
+  pkt->hdr.rd = qhdr->rd ;
+  zplen = shibari_tdb_find_authority(tdb, q->s, q->len, loc, stamp, &nplen) ;
+  switch (zplen)
+  {
+    case -2 : return 9 ;
+    case -1 : return 2 ;
+    default : break ;
+  }
+  if (nplen >= 0 && nplen < zplen)
+    return childzone(pkt, tdb, q, loc, stamp, nplen, zplen) ;
+
+  pkt->hdr.aa = 1 ;  /* we're in the zone, man */
+
+  while (wildpos <= zplen)
+  {
+    for (;;)
+    {
+      shibari_tdb_entry entry ;
+      int r = shibari_tdb_read_entry(tdb, &state, &entry, q->s + wildpos, q->len + wildpos, qtype, !!wildpos, loc, stamp, &flagyxdomain) ;
+      if (r == -1) return 2 ;
+      if (!r) break ;
+      if (!shibari_packet_add_rr(pkt, &entry, 0, 0, 2))
+      {
+        pkt->hdr.tc = 1 ;
+        return 0 ;
+      }
+      switch (entry.type)
+      {
+        case SHIBARI_T_NS :
+        case SHIBARI_T_MX :
+        case SHIBARI_T_CNAME :  /* we're not supposed to but meh */
+          gluetype = entry.type ;
+        default : break ;
+      }
+    }
+    if (pkt->hdr.counts.an) break ;
+    wildpos += 1 + q->s[wildpos] ;
+  }
+
+  if (!flagyxdomain) pkt->hdr.rcode = 3 ;
+
+  if (!pkt->hdr.counts.an)
+  {
+    unsigned int r = shibari_packet_assert_authority(pkt, tdb, q->s + zplen, q->len - zplen, zplen, loc, stamp) ;
+    if (r) return r ;
+  }
+  else if (gluetype)
+  {
+    unsigned int r = shibari_packet_add_glue(pkt, tdb, q->s, q->len, gluetype, q->s + zplen, q->len - zplen, zplen, wildpos, loc, stamp) ;
+    if (r) return r ;
+  }
+
+  shibari_packet_end(pkt) ;
+  return rcode ;
+}
diff --git a/src/server/shibari_tdb_entry_parse.c b/src/server/shibari_tdb_entry_parse.c
new file mode 100644
index 0000000..61f076f
--- /dev/null
+++ b/src/server/shibari_tdb_entry_parse.c
@@ -0,0 +1,56 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
+#include <skalibs/tai.h>
+
+#include <shibari/constants.h>
+#include <shibari/tdb.h>
+
+int shibari_tdb_entry_parse (shibari_tdb_entry *out, char const *s, uint16_t len, uint16_t qtype, unsigned int wild, char const *loc, tain const *stamp)
+{
+  tai ttd ;
+  uint32_t ttl ;
+  uint32_t flags = 0 ;
+  uint16_t type ;
+  if (len < 15) return -1 ;
+  uint16_unpack_big(s, &type) ;
+  if (qtype != SHIBARI_T_ANY && qtype != type && type != SHIBARI_T_CNAME) return 0 ;
+  s += 3 ; len -= 3 ;
+  switch (s[-1])
+  {
+    case '+' : flags |= 1 ;
+    case '>' :
+      if (len < 14) return -1 ;
+      if (loc && loc[0] && (loc[0] != s[0] || loc[1] != s[1])) return 0 ;
+      s += 2 ; len -= 2 ;
+      break ;
+    case '*' : flags |= 1 ;
+    case '=' : break ;
+    default : return -1 ;
+  }
+  if (wild < 2 && wild != (flags & 1)) return 0 ;
+  uint32_unpack_big(s, &ttl) ;
+  s += 4 ; len -= 4 ;
+  tai_unpack(s, &ttd) ;
+  s += 8 ; len -= 8 ;
+  if (tai_sec(&ttd))
+  {
+    if (!ttl == !tai_less(tain_secp(stamp), &ttd)) return 0 ;
+    if (!ttl)
+    {
+      tai t ;
+      tai_sub(&t, &ttd, tain_secp(stamp)) ;
+      if (tai_sec(&t) < 2) ttl = 2 ;
+      else if (tai_sec(&t) > 3600 && qtype != SHIBARI_T_ANY) ttl = 3600 ;
+    }
+  }
+  out->ttl = ttl ;
+  out->flags = flags ;
+  out->type = type ;
+  out->data.s = s ;
+  out->data.len = len ;
+  return 1 ;
+}
diff --git a/src/server/shibari_tdb_extract_domain.c b/src/server/shibari_tdb_extract_domain.c
new file mode 100644
index 0000000..dfa6009
--- /dev/null
+++ b/src/server/shibari_tdb_extract_domain.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <shibari/constants.h>
+#include <shibari/tdb.h>
+
+int shibari_tdb_extract_domain (shibari_tdb_entry const *entry, cdb_data *domain)
+{
+  switch (entry->type)
+  {
+    case SHIBARI_T_CNAME :
+    case SHIBARI_T_NS :
+      *domain = entry->data ; break ;
+    case SHIBARI_T_MX : domain->s = entry->data.s + 2 ; domain->len = entry->data.len - 2 ; break ;
+    default : return 0 ;
+  }
+  return 1 ;
+}
diff --git a/src/server/shibari_tdb_find_authority.c b/src/server/shibari_tdb_find_authority.c
new file mode 100644
index 0000000..5550f52
--- /dev/null
+++ b/src/server/shibari_tdb_find_authority.c
@@ -0,0 +1,46 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <skalibs/cdb.h>
+
+#include <shibari/constants.h>
+#include <shibari/tdb.h>
+
+static int find_ns_and_soa (cdb const *tdb, char const *s, uint16_t len, char const *loc, tain const *stamp)
+{
+  cdb_find_state state = CDB_FIND_STATE_ZERO ;
+  unsigned int flags = 0 ;
+  for (;;)
+  {
+    shibari_tdb_entry entry ;
+    cdb_data data ;
+    int r = cdb_findnext(tdb, &data, s, len, &state) ;
+    if (r == -1) return -1 ;
+    if (!r) break ;
+    r = shibari_tdb_entry_parse(&entry, data.s, data.len, SHIBARI_T_ANY, 0, loc, stamp) ;
+    if (r == -1) return -1 ;
+    if (!r) continue ;
+    if (entry.type == SHIBARI_T_SOA) flags |= 1 ;
+    else if (entry.type == SHIBARI_T_NS) flags |= 2 ;
+  }
+  return flags ;
+}
+
+int shibari_tdb_find_authority (cdb const *tdb, char const *s, uint16_t len, char const *loc, tain const *stamp, int *npl)
+{
+  uint16_t pos = 0 ;
+  uint16_t zplen = 0 ;
+  int nplen = -1 ;
+  while (pos < len)
+  {
+    int flags = find_ns_and_soa(tdb, s + pos, len - pos, loc, stamp) ;
+    if (flags == -1) return -1 ;
+    if (flags & 2) nplen = pos ;
+    if (flags & 1) { zplen = pos ; break ; }
+    pos += 1 + (uint8_t)s[pos] ;
+  }
+  if (pos >= len) return -2 ;  /* out of bailiwick */
+  *npl = nplen ;
+  return zplen ;
+}
diff --git a/src/server/shibari_tdb_read_entry.c b/src/server/shibari_tdb_read_entry.c
new file mode 100644
index 0000000..b2d877c
--- /dev/null
+++ b/src/server/shibari_tdb_read_entry.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <skalibs/cdb.h>
+
+#include <shibari/tdb.h>
+
+int shibari_tdb_read_entry (cdb const *tdb, cdb_find_state *state, shibari_tdb_entry *out, char const *s, uint16_t len, uint16_t qtype, unsigned int wild, char const *loc, tain const *stamp, uint32_t *flags)
+{
+  cdb_data data ;
+  int r = 0 ;
+  while (!r)
+  {
+    r = cdb_findnext(tdb, &data, s, len, state) ;
+    if (r <= 0) return r ;
+    if (flags) *flags |= 1 ;
+    r = shibari_tdb_entry_parse(out, data.s, data.len, qtype, wild, loc, stamp) ;
+    if (r == -1) return -1 ;
+  }
+  out->key.s = s ;
+  out->key.len = len ;
+  return 1 ;
+}
diff --git a/tools/gen-deps.sh b/tools/gen-deps.sh
new file mode 100755
index 0000000..27e5b3e
--- /dev/null
+++ b/tools/gen-deps.sh
@@ -0,0 +1,93 @@
+#!/bin/sh -e
+
+. package/info
+
+echo '#'
+echo '# This file has been generated by tools/gen-deps.sh'
+echo '#'
+echo
+
+for dir in src/include/${package} src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.h$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      if test -n "$deps" ; then
+        echo "${dir}/${file}:${deps}"
+      fi
+    }
+  done
+done
+
+for dir in src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.c$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=" ${dir}/$file"
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      o=$(echo $file | sed s/\\.c$/.o/)
+      lo=$(echo $file | sed s/\\.c$/.lo/)
+      echo "${dir}/${o} ${dir}/${lo}:${deps}"
+    }
+  done
+done
+echo
+
+for dir in $(ls -1 src | grep -v ^include) ; do
+  for file in $(ls -1 src/$dir/deps-lib) ; do
+    deps=
+    libs=
+    while read dep ; do
+      if echo $dep | grep -q -e ^-l -e '^\${.*_LIB}' ; then
+        libs="$libs $dep"
+      else
+        deps="$deps src/$dir/$dep"
+      fi
+    done < src/$dir/deps-lib/$file
+    echo 'ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)'
+    echo "lib${file}.a.xyzzy:$deps"
+    echo else
+    echo "lib${file}.a.xyzzy:$(echo "$deps" | sed 's/\.o/.lo/g')"
+    echo endif
+    echo "lib${file}.so.xyzzy: EXTRA_LIBS :=$libs"
+    echo "lib${file}.so.xyzzy:$(echo "$deps" | sed 's/\.o/.lo/g')"
+  done
+
+  for file in $(ls -1 src/$dir/deps-exe) ; do
+    deps=
+    libs=
+    while read dep ; do
+      if echo $dep | grep -q -- \\.o$ ; then
+        dep="src/$dir/$dep"
+      fi
+      if echo $dep | grep -q -e ^-l -e '^\${.*_LIB}' ; then
+        libs="$libs $dep"
+      else
+        deps="$deps $dep"
+      fi
+    done < src/$dir/deps-exe/$file
+    echo "$file: EXTRA_LIBS :=$libs"
+    echo "$file: src/$dir/$file.o$deps"
+  done
+done
diff --git a/tools/install.sh b/tools/install.sh
new file mode 100755
index 0000000..89f9428
--- /dev/null
+++ b/tools/install.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+usage() {
+  echo "usage: $0 [-D] [-l] [-m mode] src dst" 1>&2
+  exit 1
+}
+
+mkdirp=false
+symlink=false
+mode=0755
+
+while getopts Dlm: name ; do
+  case "$name" in
+    D) mkdirp=true ;;
+    l) symlink=true ;;
+    m) mode=$OPTARG ;;
+    ?) usage ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+test "$#" -eq 2 || usage
+src=$1
+dst=$2
+tmp="$dst.tmp.$$"
+
+case "$dst" in
+  */) echo "$0: $dst ends in /" 1>&2 ; exit 1 ;;
+esac
+
+set -C
+set -e
+
+if $mkdirp ; then
+  umask 022
+  case "$2" in
+    */*) mkdir -p "${dst%/*}" ;;
+  esac
+fi
+
+trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP
+
+umask 077
+
+if $symlink ; then
+  ln -s "$src" "$tmp"
+else
+  cat < "$1" > "$tmp"
+  chmod "$mode" "$tmp"
+fi
+
+mv -f "$tmp" "$dst"
+if test -d "$dst" ; then
+  rm -f "$dst/$(basename $tmp)"
+  if $symlink ; then
+    mkdir "$tmp"
+    ln -s "$src" "$tmp/$(basename $dst)"
+    mv -f "$tmp/$(basename $dst)" "${dst%/*}"
+    rmdir "$tmp"
+  else
+    echo "$0: $dst is a directory" 1>&2
+    exit 1
+  fi
+fi