about summary refs log tree commit diff
path: root/src/server
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 /src/server
downloadshibari-b8d0f83e6cea9640a7ee4402c163ad812237355d.tar.gz
shibari-b8d0f83e6cea9640a7ee4402c163ad812237355d.tar.xz
shibari-b8d0f83e6cea9640a7ee4402c163ad812237355d.zip
Initial commit
Signed-off-by: Laurent Bercot <ska@appnovation.com>
Diffstat (limited to 'src/server')
-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
16 files changed, 870 insertions, 0 deletions
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 ;
+}