#include <search.h>
#include <stdbool.h>
#include <confuse.h>
#include <arpa/inet.h>
int compar_pcp (const void * a, const void * b) {
return strcmp(*((const char **) a), *((const char **) b));
}
enum type {
nothing,
network,
ns,
ptr,
ptr6c
};
struct trie {
struct trie * dir[3]; // left, right, up
enum type type;
void * data;
};
struct trie * next (struct trie * trie) { // for depth-first walking the trie, pass in the root trie first
if (trie->dir[0]) {
trie->dir[0]->dir[2] = trie;
return trie->dir[0];
}
if (trie->dir[1]) {
trie->dir[1]->dir[2] = trie;
return trie->dir[1];
}
while (trie->dir[2]) {
if (trie->dir[2]->dir[1])
if (trie->dir[2]->dir[1] != trie) {
trie->dir[2]->dir[1]->dir[2] = trie->dir[2];
return trie->dir[2]->dir[1];
}
trie = trie->dir[2];
}
return NULL;
// lahko noč!
}
struct network {
struct in6_addr addr;
int mask;
char * email;
char ** ns; // first one is master
int serial; // for soa record
int ttl;
struct suffix * suffix; // network does not own a suffix
char * from; // master that sent us this network or NULL if I am the master
};
void free_network (struct network * network) {
if (!network)
return;
char ** pcp = network->ns;
if (pcp)
for (; *pcp; pcp++)
free(*pcp);
free(network->ns);
free(network->email);
free(network->from);
free(network);
}
struct ns {
struct in6_addr addr;
int mask;
char ** ns;
int ttl;
char * from; // master that sent us this ns or NULL if I am the master
};
void free_ns (struct ns * ns) {
if (!ns)
return;
char ** pcp = ns->ns;
if (pcp)
for (; *pcp; pcp++)
free(*pcp);
free(ns->ns);
free(ns->from);
free(ns);
}
struct ptr {
struct in6_addr addr;
char * hostname;
int ttl; // time of creation for ptr6c records
char * from; // master that sent us this ptr or NULL if I am the master
};
void free_ptr (struct ptr * ptr) {
if (!ptr)
return;
free(ptr->hostname);
free(ptr->from);
free(ptr);
}
void free_trie (struct trie * trie) {
if (!trie)
return;
switch (trie->type) {
case network:
free_network((struct network *) trie->data);
break;
case ns:
free_ns((struct ns *) trie->data);
break;
case ptr:
case ptr6c:
free_ptr((struct ptr *) trie->data);
break;
case nothing:
free(trie->data);
break;
}
free_trie(trie->dir[0]);
free_trie(trie->dir[1]);
free(trie);
}
void free_trie_ptr (struct trie * trie) { // also frees all tries above that don't hold nothing else and would be useless to keep
if (!trie)
return;
if (trie->dir[2])
if (!trie->dir[2]->dir[0] || !trie->dir[2]->dir[1])
free_trie_ptr(trie->dir[2]);
free_trie(trie);
}
struct suffix {
char * suffix;
struct trie * accept; // trie structure: http://upload.šijanec.eu./d/suffix.jpg (data is irrelevant)
char ** ns; // first one is master
char * email;
int ttl;
int serial;
char * from; // master that sent us this suffix or NULL if I am the master
};
void free_suffix (struct suffix * suffix) {
if (!suffix)
return;
free(suffix->suffix);
free_trie(suffix->accept);
char ** pcp = suffix->ns;
if (pcp)
for (; *pcp; pcp++)
free(*pcp);
free(suffix->ns);
free(suffix->email);
free(suffix->from);
free(suffix);
}
struct config {
struct trie * trie;
char ** masters;
int poll_interval;
char * ptr_file;
void * suffixrp; // for tsearch(3)
};
struct netspec {
struct in6_addr addr;
int mask;
};
void add_accept (struct trie * trie, struct netspec netspec) {
for (int depth = 0; depth < netspec.mask; depth++) {
bool bit = !!(netspec.addr.s6_addr[depth/8] & (1 << (7-depth%8)));
if (!trie->dir[bit])
trie->dir[bit] = calloc(1, sizeof *trie);
trie = trie->dir[bit];
}
trie->data = calloc(1, sizeof(struct netspec));
memcpy(trie->data, &netspec, sizeof netspec);
}
bool is_acceptable (struct trie * trie, struct in6_addr addr) {
int depth = 0;
for (;;) {
bool bit = !!(addr.s6_addr[depth/8] & (1 << (7-depth%8)));
if (!trie->dir[0] && !trie->dir[1])
return true;
if (!trie->dir[bit])
return false;
trie = trie->dir[bit];
}
}
int validate_nonnegative (cfg_t * cfg, cfg_opt_t * opt) {
int value = cfg_opt_getnint(opt, cfg_opt_size(opt) - 1);
if (value < 0) {
cfg_error(cfg, "integer option '%s' must be nonnegative in section '%s'", opt->name, cfg->name);
return -1;
}
return 0;
}
int validate_positive (cfg_t * cfg, cfg_opt_t * opt) {
int value = cfg_opt_getnint(opt, cfg_opt_size(opt) - 1);
if (value < 1) {
cfg_error(cfg, "integer option '%s' must be positive in section '%s'", opt->name, cfg->name);
return -1;
}
return 0;
}
int validate_remote (cfg_t * cfg, cfg_opt_t * opt) {
char * value = cfg_opt_getnstr(opt, cfg_opt_size(opt) - 1);
char * cp = strchr(value, '/');
if (cp) {
char * cp2;
int port = strtol(cp+1, &cp2, 10);
if (*cp2) {
cfg_error(cfg, "remote server specification %s in %s in section %s has non-numeric characters as part of the port", value, opt->name, cfg->name);
return -1;
}
if (port < 1 || port > 65535) {
cfg_error(cfg, "specified port %d of remote server specification %s in %s section %s is not in range 1-65535", port, value, opt->name, cfg->name);
return -1;
}
}
if (cp)
*cp = '\0';
if (strchr(value, ':')) {
struct in6_addr tmp;
switch (inet_pton(AF_INET6, value, &tmp)) {
case -1:
cfg_error(cfg, "error while parsing remote server specification %s in section %s: inet_pton(AF_INET6, \"%s\", &tmp): %s --- your libc must support IPv6", opt->name, cfg->name, value, strerror(errno));
if (cp)
*cp = '/';
return -1;
case 0:
cfg_error(cfg, "remote server specification %s in section %s is an invalid ipv6 or v4mapped address (%s). see section AF_INET6 in `man inet_pton` for syntax.", opt->name, cfg->name, value);
if (cp)
*cp = '/';
return -1;
}
return 0;
}
unsigned char tmp[PACKETSZ];
if (res_mkquery(QUERY, value, C_IN, T_AAAA, NULL, 0, NULL, tmp, PACKETSZ) == -1) {
cfg_error(cfg, "hostname %s in remote server specification %s in section %s couldn't be parsed by res_mkquery", value, opt->name, cfg->name);
if (cp)
*cp = '/';
return -1;
}
if (cp)
*cp = '/';
return 0;
}
int validate_fqdn (cfg_t * cfg, cfg_opt_t * opt) {
char * value = cfg_opt_getnstr(opt, cfg_opt_size(opt) - 1);
unsigned char tmp[PACKETSZ];
if (res_mkquery(QUERY, value, C_IN, T_AAAA, NULL, 0, NULL, tmp, PACKETSZ) == -1) {
cfg_error(cfg, "FQDN %s in option %s in section %s couldn't be parsed by res_mkquery", value, opt->name, cfg->name);
return -1;
}
return 0;
}
int validate_email (cfg_t * cfg, cfg_opt_t * opt) {
char * value = cfg_opt_getnstr(opt, cfg_opt_size(opt) - 1);
value = strdup(value);
char * cp = strchr(value, '@');
if (!cp) {
cfg_error(cfg, "e-mail %s in option %s in section %s does not contain the '@' sign", value, opt->name, cfg->name);
return -1;
}
*cp = '\0';
while (strchr(value, '.'))
strchr(value, '.')[0] = '@';
*cp = '.';
unsigned char tmp[PACKETSZ];
if (res_mkquery(QUERY, value, C_IN, T_AAAA, NULL, 0, NULL, tmp, PACKETSZ) == -1) {
cfg_error(cfg, "e-mail converted to domain name %s in option %s in section %s couldn't be parsed by res_mkquery", value, opt->name, cfg->name);
free(value);
return -1;
}
free(value);
return 0;
}
int validate_network (cfg_t * cfg, cfg_opt_t * opt) {
cfg_t * sec = cfg_opt_getnsec(opt, cfg_opt_size(opt) - 1);
if (!cfg_size(sec, "networks")) {
cfg_error(cfg, "%s section does not contain any networks", opt->name);
return -1;
}
if (!cfg_size(sec, "ns")) {
cfg_error(sec, "%s section does not contain any NS records", opt->name);
return -1;
}
return 0;
}
int validate_netspec (cfg_t * cfg, cfg_opt_t * opt) {
char * value = cfg_opt_getnstr(opt, cfg_opt_size(opt) - 1);
char * cp = strchr(value, '/');
if (!cp) {
cfg_error(cfg, "network specification %s in %s in section %s must contain the '/' character", value, opt->name, cfg->name);
return -1;
}
char * cp2;
int mask = strtol(cp+1, &cp2, 10);
if (*cp2) {
cfg_error(cfg, "network specification %s in %s in section %s has non-numeric characters as part of the netmask", value, opt->name, cfg->name);
return -1;
}
if (mask < 0 || mask > 128) {
cfg_error(cfg, "specified mask %d of network specification %s in %s section %s is not in range 0-128", mask, value, opt->name, cfg->name);
return -1;
}
*cp = '\0';
struct in6_addr tmp;
switch (inet_pton(AF_INET6, value, &tmp)) {
case -1:
cfg_error(cfg, "error while parsing network specification %s in section %s: inet_pton(AF_INET6, \"%s\", &tmp): %s --- your libc must support IPv6", opt->name, cfg->name, value, strerror(errno));
*cp = '/';
return -1;
case 0:
cfg_error(cfg, "network specification %s in section %s is an invalid ipv6 or v4mapped address (%s). see section AF_INET6 in `man inet_pton` for syntax.", opt->name, cfg->name, value);
*cp = '/';
return -1;
}
for (int i = mask; i < 128; i++)
if (tmp.s6_addr[i/8] & (1 << (7-i%8))) {
cfg_error(cfg, "network specification %s in section %s has host bits set (%s/%d) (debug: %d).", opt->name, cfg->name, value, mask, i);
*cp = '/';
return -1;
}
*cp = '/';
return 0;
}
int validate_ptr (cfg_t * cfg, cfg_opt_t * opt) {
cfg_t * sec = cfg_opt_getnsec(opt, cfg_opt_size(opt) - 1);
char * hostname = cfg_getstr(sec, "hostname");
if (!hostname) {
cfg_error(cfg, "%s section %s does not contain required option hostname", opt->name, sec->title);
return -1;
}
struct in6_addr tmp;
switch (inet_pton(AF_INET6, sec->title, &tmp)) {
case -1:
cfg_error(cfg, "error while parsing address %s in section %s: inet_pton(AF_INET6, \"%s\", &tmp): %s --- your libc must support IPv6", opt->name, sec->name, sec->title, strerror(errno));
return -1;
case 0:
cfg_error(cfg, "address in section %s is an invalid ipv6 or v4mapped address (%s). see section AF_INET6 in `man inet_pton` for syntax.", sec->name, sec->title);
return -1;
}
return 0;
}
int validate_ns (cfg_t * cfg, cfg_opt_t * opt) {
cfg_t * sec = cfg_opt_getnsec(opt, cfg_opt_size(opt) - 1);
if (!cfg_size(sec, "networks")) {
cfg_error(cfg, "%s section does not contain any networks", opt->name);
return -1;
}
if (!cfg_size(sec, "ns")) {
cfg_error(sec, "%s section does not contain any NS records", opt->name);
return -1;
}
return 0;
}
int validate_suffix (cfg_t * cfg, cfg_opt_t * opt) {
cfg_t * sec = cfg_opt_getnsec(opt, cfg_opt_size(opt) - 1);
if (!cfg_size(sec, "suffixes")) {
cfg_error(cfg, "%s section does not contain any suffixes", opt->name);
return -1;
}
if (!cfg_size(sec, "ns")) {
cfg_error(sec, "%s section does not contain any NS records", opt->name);
return -1;
}
return 0;
}
int config (struct config * conf, const char * filename, FILE * output) {
cfg_opt_t network_opts[] = {
CFG_STR_LIST("networks", NULL, CFGF_NODEFAULT),
CFG_STR_LIST("ns", NULL, CFGF_NODEFAULT), // REQUIRED at least one.
CFG_STR("admin", "6@sijanec.eu", CFGF_NONE),
CFG_STR("suffix", NULL, CFGF_NONE),
CFG_INT("ttl", 420, CFGF_NONE),
CFG_END()
};
cfg_opt_t suffix_opts[] = {
CFG_STR_LIST("suffixes", NULL, CFGF_NODEFAULT),
CFG_STR_LIST("accept", "{::/0}", CFGF_NONE),
CFG_STR_LIST("ns", NULL, CFGF_NODEFAULT), // REQUIRED at least one.
CFG_STR("admin", "6@sijanec.eu", CFGF_NONE),
CFG_INT("ttl", 420, CFGF_NONE),
CFG_END()
};
cfg_opt_t ptr_opts[] = {
CFG_STR("hostname", NULL, CFGF_NODEFAULT),
CFG_INT("ttl", 420, CFGF_NONE),
CFG_END()
};
cfg_opt_t ns_opts[] = {
CFG_STR_LIST("networks", NULL, CFGF_NODEFAULT),
CFG_STR_LIST("ns", NULL, CFGF_NODEFAULT),
CFG_INT("ttl", 420, CFGF_NONE),
CFG_END()
};
cfg_opt_t opts[] = {
CFG_STR_LIST("masters", "{}", CFGF_NONE),
CFG_INT("poll_interval", 69, CFGF_NONE),
CFG_STR("ptr_file", "/var/lib/cache/6/backup", CFGF_NONE),
CFG_SEC("network", network_opts, CFGF_MULTI),
CFG_SEC("suffix", suffix_opts, CFGF_MULTI),
CFG_SEC("ptr", ptr_opts, CFGF_TITLE | CFGF_MULTI | CFGF_NO_TITLE_DUPES),
CFG_SEC("ns", ns_opts, CFGF_MULTI),
CFG_END()
};
cfg_t * cfg;
cfg = cfg_init(opts, CFGF_NONE);
cfg_set_validate_func(cfg, "poll_interval", validate_nonnegative);
cfg_set_validate_func(cfg, "network|ttl", validate_positive);
cfg_set_validate_func(cfg, "suffix|ttl", validate_positive);
cfg_set_validate_func(cfg, "ptr|ttl", validate_positive);
cfg_set_validate_func(cfg, "ns|ttl", validate_positive);
cfg_set_validate_func(cfg, "masters", validate_remote);
cfg_set_validate_func(cfg, "network|ns", validate_fqdn);
cfg_set_validate_func(cfg, "suffix|ns", validate_fqdn);
cfg_set_validate_func(cfg, "ptr|hostname", validate_fqdn);
cfg_set_validate_func(cfg, "ns|ns", validate_fqdn);
cfg_set_validate_func(cfg, "suffix|suffixes", validate_fqdn);
cfg_set_validate_func(cfg, "suffix|admin", validate_email);
cfg_set_validate_func(cfg, "network|admin", validate_email);
cfg_set_validate_func(cfg, "network|networks", validate_netspec);
cfg_set_validate_func(cfg, "suffix|accept", validate_netspec);
cfg_set_validate_func(cfg, "ns|networks", validate_netspec);
cfg_set_validate_func(cfg, "network", validate_network);
cfg_set_validate_func(cfg, "suffix", validate_suffix);
cfg_set_validate_func(cfg, "ptr", validate_ptr);
cfg_set_validate_func(cfg, "ns", validate_ns);
switch (cfg_parse(cfg, filename)) {
case CFG_FILE_ERROR:
if (output)
fprintf(output, "# configuration file '%s' could not be read: %s\n", filename, strerror(errno));
return 1;
case CFG_PARSE_ERROR:
fprintf(output, "# configuration file '%s' could not be parsed\n", filename);
return 2;
}
if (conf->masters) {
char ** pcp = conf->masters;
for (; *pcp; pcp++)
free(*pcp);
free(conf->masters);
}
conf->masters = calloc(cfg_size(cfg, "masters")+1, sizeof *conf->masters);
for (size_t i = 0; i < cfg_size(cfg, "masters"); i++)
conf->masters[i] = strdup(cfg_getnstr(cfg, "masters", i));
conf->poll_interval = cfg_getint(cfg, "poll_interval");
free(conf->ptr_file);
conf->ptr_file = strdup(cfg_getstr(cfg, "ptr_file"));
for (size_t i = 0; i < cfg_size(cfg, "suffix"); i++) {
cfg_t * cfg_suffix = cfg_getnsec(cfg, "suffix", i);
for (size_t j = 0; j < cfg_size(cfg_suffix, "suffixes"); j++) {
char * suffixstr = strdup(cfg_getnstr(cfg_suffix, "suffixes", j));
struct suffix ** found = tfind(&suffixstr, &conf->suffixrp, compar_pcp);
if (found) {
free_suffix(*found);
tdelete(&suffixstr, &conf->suffixrp, compar_pcp);
}
struct suffix * suffix = calloc(1, sizeof *suffix);
suffix->suffix = suffixstr;
suffix->accept = calloc(1, sizeof *suffix->accept);
if (cfg_getstr(cfg_suffix, "admin"))
suffix->email = strdup(cfg_getstr(cfg_suffix, "admin"));
suffix->ns = calloc(cfg_size(cfg_suffix, "ns")+1, sizeof *suffix->ns);
for (size_t k = 0; k < cfg_size(cfg_suffix, "ns"); k++)
suffix->ns[k] = strdup(cfg_getnstr(cfg_suffix, "ns", k));
suffix->ttl = cfg_getint(cfg_suffix, "ttl");
for (size_t k = 0; k < cfg_size(cfg_suffix, "accept"); k++) {
struct netspec netspec;
char * cp = strchr(cfg_getnstr(cfg_suffix, "accept", k), '/');
cp[0] = '\0';
netspec.mask = strtol(cp+1, NULL, 10);
inet_pton(AF_INET6, cfg_getnstr(cfg_suffix, "accept", k), netspec.addr.s6_addr);
cp[0] = '/';
add_accept(suffix->accept, netspec);
}
suffix->serial = timestamp();
tsearch(suffix, &conf->suffixrp, compar_pcp);
}
}
return 0;
}
#ifndef _GNU_SOURCE
FILE * global_print_suffix_output;
#endif
void print_suffix (const void * nodep, VISIT which,
#ifndef _GNU_SOURCE
int depth __attribute__((unused))
#else
void * closure
#endif
) {
struct suffix * suffix = *((struct suffix **) nodep);
if (!(which == postorder || which == leaf))
return;
#ifndef _GNU_SOURCE
FILE * output = global_print_suffix_output;
#else
FILE * output = (FILE *) closure;
#endif
fprintf(output, "suffix\n{\tttl = %d\n\t", suffix->ttl);
if (suffix->email)
fprintf(output, "admin = \"%s\"\n\t", suffix->email);
fprintf(output, "accept = {");
unsigned i = 0;
for (struct trie * trie = suffix->accept; trie; trie = next(trie)) {
if (trie->dir[0] || trie->dir[1])
continue;
if (i++)
fprintf(output, ", ");
char buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET6, ((struct netspec *) trie->data)->addr.s6_addr, buf, sizeof buf);
fprintf(output, "\"%s/%d\"", buf, ((struct netspec *) trie->data)->mask);
}
fprintf(output, "}\n\tns = {");
char ** pcp = suffix->ns;
while (*pcp) {
fprintf(output, "\"%s\"", *pcp);
if (*++pcp)
fprintf(output, ", ");
}
fprintf(output, "}\n\tsuffixes = {\"%s\"}\n}\n", suffix->suffix);
}
void print_config (const struct config * conf, FILE * output) {
fprintf(output, "masters = {");
char ** pcp = conf->masters;
while (*pcp) {
fprintf(output, "\"%s\"", *pcp);
if (*++pcp)
fprintf(output, ", ");
}
fprintf(output, "}\npoll_interval = %d\nptr_file = \"%s\"\n", conf->poll_interval, conf->ptr_file);
#ifndef _GNU_SOURCE
global_print_suffix_output = output;
twalk(conf->suffixrp, print_suffix);
#else
twalk_r(conf->suffixrp, print_suffix, output);
#endif
}