From 53c6e973765c7cd096f982a304bc87fc1ca114ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Ter=C3=A4s?= Date: Thu, 11 Jul 2013 18:51:43 +0300 Subject: [PATCH 2/2] Impelement loading of DJBDNS zone files --- docs/TODO | 12 +- gdnsd/Makefile.am | 2 +- gdnsd/main.c | 1 + gdnsd/zscan_djb.c | 577 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ gdnsd/zscan_djb.h | 38 ++++ gdnsd/zsrc_djb.c | 91 ++++++--- gdnsd/zsrc_djb.h | 2 + 7 files changed, 694 insertions(+), 29 deletions(-) create mode 100644 gdnsd/zscan_djb.c create mode 100644 gdnsd/zscan_djb.h diff --git a/docs/TODO b/docs/TODO index 1e67141..831231b 100644 --- a/docs/TODO +++ b/docs/TODO @@ -161,12 +161,12 @@ Other zonefile formats: ------------------------- Load other zonefile (or zone data in general) formats? The BIND syntax sucks, but I'm keeping it as the default, it's too widespread not to. -However, the zonefile scanner is mostly cleanly separated from the rest -of the code, and it wouldn't be that hard to add support for more -formats (djbdns? a SQL connection?). Update: the core code is -basically ready for this. I even created a mostly-empty zsrc_djb.c -file since that's likely the first/easiest target. Just needs -implementation. + +Additionally, the djbdns style zone files are also supported. + +As the zonefile scanner is mostly cleanly separated from the rest of +the code, additional zonefile formats (e.g. SQL backend) should not +be too hard to implement. Stuff from conversations w/ Paul Dekkers: -------- diff --git a/gdnsd/Makefile.am b/gdnsd/Makefile.am index a57a3d4..a42309d 100644 --- a/gdnsd/Makefile.am +++ b/gdnsd/Makefile.am @@ -4,7 +4,7 @@ AM_CPPFLAGS = -I$(srcdir)/libgdnsd -I$(builddir)/libgdnsd # How to build gdnsd sbin_PROGRAMS = gdnsd -gdnsd_SOURCES = main.c conf.c zsrc_djb.c zsrc_djb.h zsrc_rfc1035.c zsrc_rfc1035.h ztree.c ztree.h zscan_rfc1035.c ltarena.c ltree.c dnspacket.c dnsio_udp.c dnsio_tcp.c dnsio.c statio.c monio.c conf.h dnsio_tcp.h dnsio_udp.h dnsio.h dnspacket.h dnswire.h ltarena.h ltree.h statio.h monio.h zscan_rfc1035.h +gdnsd_SOURCES = main.c conf.c zsrc_djb.c zsrc_djb.h zscan_djb.c zsrc_rfc1035.c zsrc_rfc1035.h ztree.c ztree.h zscan_rfc1035.c ltarena.c ltree.c dnspacket.c dnsio_udp.c dnsio_tcp.c dnsio.c statio.c monio.c conf.h dnsio_tcp.h dnsio_udp.h dnsio.h dnspacket.h dnswire.h ltarena.h ltree.h statio.h monio.h zscan_rfc1035.h gdnsd_LDADD = libgdnsd/libgdnsd.la $(LIBGDNSD_LIBS) $(CAPLIBS) zscan_rfc1035.c: zscan_rfc1035.rl diff --git a/gdnsd/main.c b/gdnsd/main.c index b2b5d22..759ce1e 100644 --- a/gdnsd/main.c +++ b/gdnsd/main.c @@ -89,6 +89,7 @@ static void hup_signal(struct ev_loop* loop V_UNUSED, struct ev_signal *w V_UNUS log_debug("Received SIGHUP"); // these functions should log_info() that they're taking SIGHUP actions, as appropriate + zsrc_djb_sighup(); zsrc_rfc1035_sighup(); } diff --git a/gdnsd/zscan_djb.c b/gdnsd/zscan_djb.c new file mode 100644 index 0000000..34c41e8 --- /dev/null +++ b/gdnsd/zscan_djb.c @@ -0,0 +1,577 @@ +/* Copyright © 2012-2013 Timo Teräs + * + * This file is part of gdnsd. + * + * gdnsd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * gdnsd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gdnsd. If not, see . + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conf.h" +#include "ztree.h" +#include "zscan_djb.h" +#include "gdnsd/log.h" +#include "gdnsd/misc.h" + +#define TTL_NS 259200 +#define TTL_POSITIVE 86400 +#define TTL_NEGATIVE 2560 + +#define parse_abort() \ + siglongjmp(z->jbuf, 1) + +#define parse_warn(_fmt, ...) \ + log_warn("djb: %s: parse error at line %u: " _fmt,z->fn,z->lcount,__VA_ARGS__);\ + +#define parse_error_noargs(_fmt) \ + do {\ + log_err("djb: %s: parse error at line %u: " _fmt,z->fn,z->lcount);\ + parse_abort();\ + } while(0) + +#define parse_error(_fmt, ...) \ + do {\ + log_err("djb: %s: parse error at line %u: " _fmt,z->fn,z->lcount,__VA_ARGS__);\ + parse_abort();\ + } while(0) + +typedef struct { + char* ptr; + unsigned len; +} field_t; + +typedef struct { + uint8_t ns[256]; + uint8_t email[256]; + unsigned ttl; + unsigned serial; + unsigned refresh; + unsigned retry; + unsigned expire; + unsigned cache; + + unsigned mtime; +} soa_info_t; + +typedef struct { + /* variables preserved across files */ + uint64_t mtime; + zscan_djb_zonedata_t* zonedata; + const char* path; + uint8_t** texts; + char* line; + size_t allocated; + int num_texts; + int skipped; + + /* file specific data */ + int lcount; + char* full_fn; + const char* fn; + FILE* file; + + sigjmp_buf jbuf; +} zscan_t; + +static const uint8_t dname_root[] = {1,0}; +static const uint8_t dname_ns[] = {4,2,'n','s',255}; +static const uint8_t dname_mx[] = {4,2,'m','x',255}; +static const uint8_t dname_srv[] = {5,3,'s','r','v',255}; + +void zscan_djbzone_add(zscan_djb_zonedata_t** zd, zone_t *zone) { + zscan_djb_zonedata_t* nzd = malloc(sizeof(zscan_djb_zonedata_t)); + nzd->zone = zone; + nzd->marked = 0; + nzd->next = *zd; + *zd = nzd; +} + +zscan_djb_zonedata_t* zscan_djbzone_get(zscan_djb_zonedata_t* zd, const uint8_t* dname, int exact) { + zscan_djb_zonedata_t* best = NULL; + + for (; zd; zd = zd->next) { + if (exact) { + if (dname_cmp(zd->zone->dname, dname) == 0) + return zd; + } else { + if (!dname_isinzone(zd->zone->dname, dname)) + continue; + if (best == NULL || zd->zone->dname[0] > best->zone->dname[0]) { + best = zd; + if (best->zone->dname[0] == dname[0]) + return best; + } + } + } + return best; +} + +void zscan_djbzone_free(zscan_djb_zonedata_t** zd) { + zscan_djb_zonedata_t* cur = *zd; + zscan_djb_zonedata_t* next; + + while (cur) { + next = cur->next; + free(cur); + cur = next; + } + *zd = NULL; +} + + +F_NONNULL +static uint8_t *parse_dname(zscan_t *z, uint8_t *dname, field_t *f) { + dname_status_t status = dname_from_string(dname, (const uint8_t*) f->ptr, f->len); + + switch(status) { + case DNAME_INVALID: + parse_error("'%.*s' is not a domain name", f->len, f->ptr); + break; + case DNAME_VALID: + break; + case DNAME_PARTIAL: + if(dname_cat(dname, dname_root) == DNAME_INVALID) + parse_error("'%.*s' is not a valid name", f->len, f->ptr); + break; + } + return dname; +} + +F_NONNULL +static uint8_t *make_dname_relative(uint8_t* dname, const uint8_t* parent_dname) { + *dname -= *parent_dname - 1; + dname[*dname] = 0; + dmn_assert(dname_status(dname) == DNAME_VALID); + return dname; +} + +F_NONNULL +static uint8_t *expand_dname(zscan_t *z, uint8_t *dname, field_t *f, const uint8_t *subzone, const uint8_t *zone) { + /* fully qualified name in the primary field? */ + if (strchr(f->ptr, '.') != NULL) + return parse_dname(z, dname, f); + + /* construct dname of form .. + * e.g. ns1.ns.example.com */ + dname_from_string(dname, (const uint8_t*) f->ptr, f->len); + dname_cat(dname, subzone); + switch (dname_cat(dname, zone)) { + case DNAME_VALID: + break; + case DNAME_PARTIAL: + if(dname_cat(dname, dname_root) != DNAME_INVALID) + break; + /* fallthrough */ + case DNAME_INVALID: + parse_error("unable to expand '%.*s' as to valid domain name", f->len, f->ptr); + break; + } + + return dname; +} + +F_NONNULL +static uint32_t parse_ipv4(zscan_t *z, field_t *f) { + struct in_addr addr; + + if(inet_pton(AF_INET, f->ptr, &addr) <= 0) + parse_error("IPv4 address '%s' invalid", f->ptr); + + return addr.s_addr; +} + +F_NONNULL +static unsigned parse_ttl(zscan_t *z, field_t *f, unsigned defttl) { + char *end; + if (f->len == 0) + return defttl; + unsigned ttl = strtol(f->ptr, &end, 10); + if (end != f->ptr + f->len) + parse_error("Invalid TTL '%.*s'", f->len, f->ptr); + return ttl; +} + +F_NONNULL +static unsigned parse_int(zscan_t *z, field_t *f) { + char *end; + unsigned ttl = strtol(f->ptr, &end, 10); + if (end != f->ptr + f->len) + parse_error("Invalid integer value '%.*s'", f->len, f->ptr); + return ttl; +} + +F_NONNULL +static void parse_txt(field_t *f) { + char ch; + unsigned int i; + unsigned int j; + + j = 0; + i = 0; + while (i < f->len) { + ch = f->ptr[i++]; + if (ch == '\\') { + if (i >= f->len) break; + ch = f->ptr[i++]; + if ((ch >= '0') && (ch <= '7')) { + ch -= '0'; + if ((i < f->len) && (f->ptr[i] >= '0') && (f->ptr[i] <= '7')) { + ch <<= 3; + ch += f->ptr[i++] - '0'; + if ((i < f->len) && (f->ptr[i] >= '0') && (f->ptr[i] <= '7')) { + ch <<= 3; + ch += f->ptr[i++] - '0'; + } + } + } + } + f->ptr[j++] = ch; + } + f->len = j; + f->ptr[j] = 0; +} + +static void create_zones(zscan_t *z, char record_type, field_t *field) { + uint8_t dname[256]; + + if (record_type != 'Z') + return; + + parse_dname(z, dname, &field[0]); + if (zscan_djbzone_get(z->zonedata, dname, 1)) + return; + + char* src = gdnsd_str_combine("djb:", z->fn, NULL); + zscan_djbzone_add(&z->zonedata, zone_new(logf_dname(dname), src)); + dmn_fmtbuf_reset(); + free(src); +} + +#define TTDCHECK(fno) if (field[fno].len) { z->skipped++; return; } +#define LOCCHECK(fno) if (field[fno].len) { z->skipped++; return; } + +static void load_zones(zscan_t *z, char record_type, field_t *field) { + uint8_t dname[256], dname2[256], email[256]; + unsigned i, ttl; + + parse_dname(z, dname, &field[0]); + zscan_djb_zonedata_t* zd = zscan_djbzone_get(z->zonedata, dname, 0); + if (!zd) + return; + + //log_info("djb: processing '%s'", logf_dname(dname)); + + zone_t* zone = zd->zone; + make_dname_relative(dname, zone->dname); + + //log_info("djb: record %c name '%s' in zone '%s'", record_type, logf_dname(dname), logf_dname(zone->dname)); + + switch (record_type) { + case 'Z': /* SOA */ + TTDCHECK(9); + LOCCHECK(10); + zone->serial = parse_int(z, &field[3]); + zone->mtime = z->mtime; + if (ltree_add_rec_soa(zone, dname, + parse_dname(z, dname2, &field[1]), + parse_dname(z, email, &field[2]), + parse_ttl(z, &field[8], TTL_NEGATIVE), + zone->serial ?: z->mtime, /* serial */ + parse_int(z, &field[4]) ?: 16384, /* refresh */ + parse_int(z, &field[5]) ?: 2048, /* retry */ + parse_int(z, &field[6]) ?: 1048576, /* expire */ + parse_int(z, &field[7]) ?: 2560 /* cache */)) + parse_abort(); + break; + case '.': /* NS + SOA (+ A) */ + case '&': /* NS (+ A) */ + TTDCHECK(4); + LOCCHECK(5); + expand_dname(z, dname2, &field[2], dname_ns, dname); + ttl = parse_ttl(z, &field[3], TTL_NS); + if (ltree_add_rec_ns(zone, dname, dname2, ttl)) + parse_abort(); + if (field[1].len) { + zd = zscan_djbzone_get(z->zonedata, dname2, 0); + if (zd) { + make_dname_relative(dname2, zd->zone->dname); + log_info("djb: NS+A name '%s' in zone '%s'", logf_dname(dname2), logf_dname(zd->zone->dname)); + if (ltree_add_rec_a(zone, dname2, parse_ipv4(z, &field[1]), ttl, 0, NULL)) + parse_abort(); + } + } + break; + case '@': /* MX (+ A) */ + TTDCHECK(5); + LOCCHECK(6); + expand_dname(z, dname2, &field[2], dname_mx, dname); + ttl = parse_ttl(z, &field[4], TTL_POSITIVE); + if (ltree_add_rec_mx(zone, dname, dname2, ttl, parse_int(z, &field[3]))) + parse_abort(); + if (field[1].len) { + zd = zscan_djbzone_get(z->zonedata, dname2, 0); + if (zd) { + make_dname_relative(dname2, zd->zone->dname); + log_info("djb: MX+A name '%s' in zone '%s'", logf_dname(dname2), logf_dname(zd->zone->dname)); + if (ltree_add_rec_a(zone, dname2, parse_ipv4(z, &field[1]), ttl, 0, NULL)) + parse_abort(); + } + } + break; + case '+': /* A */ + case '=': /* A + PTR */ + TTDCHECK(3); + ttl = parse_ttl(z, &field[2], TTL_POSITIVE); + if (field[4].len == 2 && memcmp(field[4].ptr, "~~", 2) == 0) { + /* FIXME: check ooz is right */ + if (ltree_add_rec_dynaddr(zone, dname, (const uint8_t *) field[1].ptr, ttl, 0, 0, 0)) + parse_abort(); + } else { + LOCCHECK(4); + if (ltree_add_rec_a(zone, dname, parse_ipv4(z, &field[1]), ttl, 0, NULL)) + parse_abort(); +#if 0 + /* FIXME: autogen PTR record */ + if (line[0] == '=') { + ltree_add_rec_ptr(); + } +#endif + } + break; + case 'C': /* CNAME */ + TTDCHECK(3); + ttl = parse_ttl(z, &field[2], TTL_POSITIVE); + if (field[4].len == 2 && memcmp(field[4].ptr, "~~", 2) == 0) { + if (ltree_add_rec_dyncname(zone, dname, (const uint8_t *) field[1].ptr, dname_root, ttl)) + parse_abort(); + } else { + LOCCHECK(4); + if (ltree_add_rec_cname(zone, dname, parse_dname(z, dname2, &field[1]), ttl)) + parse_abort(); + } + break; + case '\'': /* TXT */ + TTDCHECK(3); + LOCCHECK(4); + + parse_txt(&field[1]); + + unsigned bytes = field[1].len; + const char* src = field[1].ptr; + unsigned chunks = (bytes + 254) / 255; + + if(bytes > 255 && gconfig.disable_text_autosplit) + parse_error_noargs("Text chunk too long (>255 unescaped)"); + if(bytes > 65500) + parse_error_noargs("Text chunk too long (>65500 unescaped)"); + + z->texts = realloc(z->texts, sizeof(uint8_t *) * (chunks + 1)); + for (i = 0; i < chunks; i++) { + int s = (bytes > 255 ? 255 : bytes); + z->texts[i] = malloc(s + 1); + z->texts[i][0] = s; + memcpy(&z->texts[i][1], src, s); + bytes -= s; + src += s; + } + z->texts[i] = NULL; + if (ltree_add_rec_txt(zone, dname, chunks, z->texts, parse_ttl(z,&field[2], TTL_POSITIVE))) { + for (i = 0; i < chunks; i++) + free(z->texts[i]); + parse_abort(); + } + break; + case 'S': /* SRV (+ A) */ + TTDCHECK(7); + LOCCHECK(8); + expand_dname(z, dname2, &field[2], dname_srv, dname); + ttl = parse_ttl(z, &field[6], TTL_POSITIVE); + if (ltree_add_rec_srv(zone, dname, dname2, ttl, parse_int(z, &field[4]), parse_int(z, &field[5]), parse_int(z, &field[3]))) + parse_abort(); + if (field[1].len) { + zd = zscan_djbzone_get(z->zonedata, dname2, 0); + if (zd) { + make_dname_relative(dname2, zd->zone->dname); + log_info("djb: SRV+A name '%s' in zone '%s'", logf_dname(dname2), logf_dname(zd->zone->dname)); + if (ltree_add_rec_a(zone, dname2, parse_ipv4(z, &field[1]), ttl, 0, NULL)) + parse_abort(); + } + } + break; + case 'N': /* NAPTR */ + TTDCHECK(8); + LOCCHECK(9); + parse_txt(&field[3]); + parse_txt(&field[4]); + parse_txt(&field[5]); + if (field[3].len > 255 || field[4].len > 255 || field[5].len > 255) + parse_error_noargs("NAPTR label cannot exceed 255 chars"); + + z->texts = realloc(z->texts, 4 * sizeof(uint8_t *)); + for (i = 0; i < 3; i++) { + z->texts[i] = malloc(field[3+i].len + 1); + z->texts[i][0] = field[3+i].len; + memcpy(&z->texts[i][1], field[3+i].ptr, field[3+i].len); + } + z->texts[i] = NULL; + if (ltree_add_rec_naptr(zone, dname, parse_dname(z, dname2, &field[6]), parse_ttl(z, &field[7], TTL_POSITIVE), parse_int(z, &field[1]), parse_int(z, &field[2]), 3, z->texts)) { + for (i = 0; i < 3; i++) + free(z->texts[i]); + parse_abort(); + } + break; +#if 0 + case '3': /* AAAA */ + case '6': /* AAAA + PTR */ + case '^': /* PTR */ + case ':': /* raw */ +#endif + default: + parse_warn("Unsupported djb record type '%c'", record_type); + } +} + +typedef void (*djb_recordcb_t)(zscan_t *z, char record_type, field_t *fields); + +static void zscan_foreach_file_record(zscan_t *z, djb_recordcb_t cb) { + field_t field[15]; + ssize_t len; + size_t i; + char *c; + + z->lcount = 0; + log_debug("Scanning djbzone file '%s'", z->fn); + + z->file = fopen(z->full_fn, "rt"); + if(z->file == NULL) + parse_error("Cannot open zone file '%s' for reading: %s", z->full_fn, logf_errno()); + + while ((len = getline(&z->line, &z->allocated, z->file)) != -1) { + z->lcount++; + + /* Skip empty lines and comments */ + if (len == 0 || z->line[0] == '#' || z->line[0] == '-') + continue; + if (z->line[len-1] == '\n') { + z->line[len-1] = 0; + len--; + } + /* Skip empty lines and location records */ + if (len == 0 || z->line[0] == '%') + continue; + + for (i = 0, c = z->line + 1; i < sizeof(field)/sizeof(field[0]); i++) { + field[i].ptr = c ?: (char*) ""; + field[i].len = 0; + if (c) { + char *n = strchr(c, ':'); + if (n) { + field[i].len = n - c; + *n = 0; + c = n + 1; + } else { + field[i].len = strlen(c); + c = NULL; + } + } + } + + cb(z, z->line[0], field); + } +} + +static bool zscan_foreach_record(zscan_t *z, djb_recordcb_t cb) { + DIR *dir; + struct dirent *e; + bool failed = false; + + dir = opendir(z->path); + if (dir == NULL) { + log_err("djb: cannot open directory '%s': %s", z->path, logf_errno()); + return true; + } + + while ((e = readdir(dir)) != NULL) { + if (e->d_name[0] == '.') + continue; + + struct stat st; + z->full_fn = gdnsd_str_combine(z->path, e->d_name, &z->fn); + if (stat(z->full_fn, &st)) { + log_err("djb: cannot stat file '%s': %s", z->fn, logf_errno()); + parse_abort(); + } + if((st.st_mode & S_IFMT) != S_IFREG) { + free(z->full_fn); + z->fn = z->full_fn = NULL; + continue; + } + uint64_t emtime = get_extended_mtime(&st); + if (emtime > z->mtime) + z->mtime = emtime; + failed = true; + if(!sigsetjmp(z->jbuf, 0)) { + zscan_foreach_file_record(z, cb); + failed = false; + } + if (z->file) { + fclose(z->file); + z->file = NULL; + } + free(z->full_fn); + z->fn = z->full_fn = NULL; + + if (failed) + break; + } + closedir(dir); + + return failed; +} + +F_WUNUSED F_NONNULL +bool zscan_djb(const char* djb_path, zscan_djb_zonedata_t** zonedata) +{ + dmn_assert(djb_path); + + zscan_t _z, *z = &_z; + memset(z, 0, sizeof(*z)); + z->path = djb_path; + + if (zscan_foreach_record(z, create_zones) || zscan_foreach_record(z, load_zones)) + goto error; + + for (zscan_djb_zonedata_t *zd = z->zonedata; zd; zd = zd->next) + if (zone_finalize(zd->zone)) + goto error; + + if (z->skipped) + log_warn("djb: skipped %d records with TTD or location", z->skipped); + + *zonedata = z->zonedata; + return false; + +error: + zscan_djbzone_free(&z->zonedata); + return true; +} diff --git a/gdnsd/zscan_djb.h b/gdnsd/zscan_djb.h new file mode 100644 index 0000000..5c47deb --- /dev/null +++ b/gdnsd/zscan_djb.h @@ -0,0 +1,38 @@ +/* Copyright © 2013 Timo Teräs + * + * This file is part of gdnsd. + * + * gdnsd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * gdnsd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gdnsd. If not, see . + * + */ + +#ifndef GDNSD_ZSCAN_DJB_H +#define GDNSD_ZSCAN_DJB_H + +#include "config.h" + +typedef struct _zscan_djb_zonedata { + zone_t* zone; + int marked; + struct _zscan_djb_zonedata* next; +} zscan_djb_zonedata_t; + +void zscan_djbzone_add(zscan_djb_zonedata_t**, zone_t *zone); +zscan_djb_zonedata_t* zscan_djbzone_get(zscan_djb_zonedata_t*, const uint8_t*, int); +void zscan_djbzone_free(zscan_djb_zonedata_t**); + +F_WUNUSED F_NONNULL +bool zscan_djb(const char* djb_path, zscan_djb_zonedata_t** zonedata); + +#endif // GDNSD_ZSCAN_DJB_H diff --git a/gdnsd/zsrc_djb.c b/gdnsd/zsrc_djb.c index 688d467..9d77a0a 100644 --- a/gdnsd/zsrc_djb.c +++ b/gdnsd/zsrc_djb.c @@ -18,6 +18,7 @@ */ #include "zsrc_djb.h" +#include "zscan_djb.h" #include #include @@ -27,36 +28,82 @@ #include "conf.h" #include "ltree.h" -#include "ltarena.h" -#include "ztree.h" -#include "gdnsd/misc.h" #include "gdnsd/log.h" +#include "gdnsd/paths.h" + +static struct ev_loop* zones_loop = NULL; +static ev_async* sighup_waker = NULL; +static char* djb_dir = NULL; +static zscan_djb_zonedata_t* active_zonedata = NULL; static void unload_zones(void) { - // for every zone_t created and sent to ztree earlier - // during zsrc_djb_load_zones: - // zlist_update(z, NULL); // removes from runtime lookup - // zone_delete(z); // destroys actual data inside - // free other associated local data, if any + ztree_txn_start(); + for (zscan_djb_zonedata_t* cur = active_zonedata; cur; cur = cur->next) + ztree_txn_update(cur->zone, NULL); + ztree_txn_end(); + + zscan_djbzone_free(&active_zonedata); +} + +static void zsrc_djb_sync_zones(void) { + zscan_djb_zonedata_t* zonedata; + int num_zones = 0; + + if (zscan_djb(djb_dir, &zonedata)) + return; + + ztree_txn_start(); + for (zscan_djb_zonedata_t* cur = zonedata; cur; cur = cur->next) { + zscan_djb_zonedata_t* old = zscan_djbzone_get(active_zonedata, cur->zone->dname, 1); + if (old) { + old->marked = 1; + ztree_txn_update(old->zone, cur->zone); + //ztree_update(old->zone, cur->zone); + } else { + ztree_txn_update(NULL, cur->zone); + //ztree_update(NULL, cur->zone); + } + num_zones++; + } + for (zscan_djb_zonedata_t* cur = active_zonedata; cur; cur = cur->next) { + if (!cur->marked) + ztree_txn_update(cur->zone, NULL); + //ztree_update(cur->zone, NULL); + } + ztree_txn_end(); + + log_info("zsrc_djb: loaded %d zones...", num_zones); + + zscan_djbzone_free(&active_zonedata); + active_zonedata = zonedata; } void zsrc_djb_load_zones(void) { - // scan input file(s): - // create zone_t object for each local zone using - // ztree.h:zone_new("example.com", "djb:datafile") - // set zone_t->mtime from filesystem mtime. - // add records to the zone_t via ltree_add_rec_*. - // call zone_finalize(z) to do post-processing - // call zlist_update(NULL, z); for each zone created, - // which makes it available for runtime lookup - // keep track of the zone_t's you created, you're - // responsible for destroying them later. + djb_dir = gdnsd_resolve_path_cfg("djbdns/", NULL); + zsrc_djb_sync_zones(); if(atexit(unload_zones)) log_fatal("zsrc_djb: atexit(unload_zones) failed: %s", logf_errno()); } -void zsrc_djb_runtime_init(struct ev_loop* loop V_UNUSED) { - // for runtime reloading based on FS updates, - // can just no-op for now and load on startup only, above. - return; +// called within our thread/loop to take sighup action +F_NONNULL +static void sighup_cb(struct ev_loop* loop, ev_async* w V_UNUSED, int revents V_UNUSED) { + dmn_assert(loop); dmn_assert(w); + log_info("zsrc_djb: received SIGHUP notification, scanning for changes..."); + zsrc_djb_sync_zones(); +} + +// called from main thread to feed ev_async +void zsrc_djb_sighup(void) { + dmn_assert(zones_loop); dmn_assert(sighup_waker); + ev_async_send(zones_loop, sighup_waker); +} + +void zsrc_djb_runtime_init(struct ev_loop* loop) { + dmn_assert(loop); + + zones_loop = loop; + sighup_waker = malloc(sizeof(ev_async)); + ev_async_init(sighup_waker, sighup_cb); + ev_async_start(loop, sighup_waker); } diff --git a/gdnsd/zsrc_djb.h b/gdnsd/zsrc_djb.h index dc60ae1..dc4c96d 100644 --- a/gdnsd/zsrc_djb.h +++ b/gdnsd/zsrc_djb.h @@ -28,4 +28,6 @@ void zsrc_djb_load_zones(void); F_NONNULL void zsrc_djb_runtime_init(struct ev_loop* loop); +void zsrc_djb_sighup(void); + #endif // GDNSD_ZSRC_DJB_H -- 1.8.3.2