#include #include #include #include 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 }