#include #include #include 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]) return trie->dir[0]; if (trie->dir[1]) return trie->dir[1]; while (trie->dir[2]) { if (trie->dir[2]->dir[1]) if (trie->dir[2]->dir[1] != trie) 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 time_t serial; // for soa record time_t 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; time_t 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; time_t 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: 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; time_t ttl; time_t 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 ** master_servers; // char ** master_zones; char ** slaves; int poll_interval; char * ptr_file; void * suffixrp; // for tsearch(3) }; void add_accept (struct trie * trie, struct in6_addr addr, int mask) { int depth = 0; while (depth < mask) { bool bit = !!(addr.s6_addr[depth/8] & (1 << (7-depth%8))); if (!trie->dir[bit]) trie->dir[bit] = calloc(1, sizeof *trie); trie = trie->dir[bit]; } } 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_zone (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 mask = strtol(cp+1, &cp2, 10); if (*cp2) { cfg_error(cfg, "zone %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 zone %s in %s section %s is not in range 0-128", mask, value, opt->name, cfg->name); return -1; } if (mask % 4) { cfg_error(cfg, "specified mask %d of zone %s in %s section %s is not divisible by 4", 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 zone %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, "zone %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; } for (int i = mask; i < 128; i++) if (tmp.s6_addr[i/8] & (1 << (7-i%8))) { cfg_error(cfg, "zone %s in section %s has host bits set (%s/%d) (debug: %d).", opt->name, cfg->name, value, mask, i); if (cp) *cp = '/'; return -1; } *cp = '/'; return 0; } unsigned char tmp[PACKETSZ]; if (res_mkquery(QUERY, value, C_IN, T_AAAA, NULL, 0, NULL, tmp, PACKETSZ) == -1) { cfg_error(cfg, "zone %s in zone specification %s in section %s couldn't be parsed by res_mkquery", value, opt->name, cfg->name); return -1; } 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(sec, "%s section does not contain any networks", opt->name); return -1; } char * master = cfg_getstr(sec, "master"); if (!master) { cfg_error(cfg, "%s section does not contain required option master", 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; } if (mask % 4) { cfg_error(cfg, "specified mask %d of network specification %s in %s section %s is not divisible by 4", 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 config (struct config * conf, const char * filename, FILE * output) { cfg_opt_t network_opts[] = { CFG_STR_LIST("networks", NULL, CFGF_NODEFAULT), // REQUIRED at least one CFG_STR_LIST("slaves", "{}", CFGF_NONE), CFG_STR("admin", "6@sijanec.eu", CFGF_NONE), CFG_STR("master", NULL, CFGF_NODEFAULT), // REQUIRED 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), // REQUIRED at least one CFG_STR_LIST("accept", "{::/0}", CFGF_NONE), CFG_STR_LIST("slaves", "{}", CFGF_NONE), CFG_STR("admin", "6@sijanec.eu", CFGF_NONE), CFG_STR("master", NULL, CFGF_NODEFAULT), // REQUIRED CFG_INT("ttl", 420, CFGF_NONE), CFG_END() }; cfg_opt_t ptr_opts[] = { CFG_STR("hostname", NULL, CFGF_NODEFAULT), // REQUIRED CFG_INT("ttl", 420, CFGF_NONE), CFG_END() }; cfg_opt_t ns_opts[] = { CFG_STR_LIST("networks", NULL, CFGF_NODEFAULT), // REQUIRED at least one CFG_STR_LIST("ns", NULL, CFGF_NODEFAULT), // REQUIRED CFG_INT("ttl", 420, CFGF_NONE), CFG_END() }; cfg_opt_t opts[] = { CFG_STR_LIST("master_servers", "{}", CFGF_NONE), // CFG_STR_LIST("master_zones", "{}", CFGF_NONE), CFG_INT("poll_interval", 69, CFGF_NONE), CFG_STR("ptr_file", "/var/lib/cache/6/backup", CFGF_NONE), CFG_STR_LIST("slaves", "{}", 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, "master_servers", validate_remote); cfg_set_validate_func(cfg, "slaves", validate_remote); // cfg_set_validate_func(cfg, "master_zones", validate_zone); cfg_set_validate_func(cfg, "network|slaves", validate_fqdn); cfg_set_validate_func(cfg, "suffix|slaves", 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, "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->master_servers) { char ** pcp = conf->master_servers; for (; *pcp; pcp++) free(*pcp); free(conf->master_servers); } conf->master_servers = calloc(cfg_size(cfg, "master_servers")+1, sizeof *conf->master_servers); for (size_t i = 0; i < cfg_size(cfg, "master_servers"); i++) conf->master_servers[i] = strdup(cfg_getnstr(cfg, "master_servers", i)); /* if (conf->master_zones) { char ** pcp = conf->master_zones; for (; *pcp; pcp++) free(*pcp); free(conf->master_zones); } conf->master_zones = calloc(cfg_size(cfg, "master_zones")+1, sizeof *conf->master_zones); for (size_t i = 0; i < cfg_size(cfg, "master_zones"); i++) conf->master_zones[i] = strdup(cfg_getnstr(cfg, "master_zones", i)); */ conf->poll_interval = cfg_getint(cfg, "poll_interval"); free(conf->ptr_file); conf->ptr_file = strdup(cfg_getstr(cfg, "ptr_file")); if (conf->slaves) { char ** pcp = conf->slaves; for (; *pcp; pcp++) free(*pcp); free(conf->slaves); } conf->slaves = calloc(cfg_size(cfg, "slaves")+1, sizeof *conf->slaves); for (size_t i = 0; i < cfg_size(cfg, "slaves"); i++) conf->slaves[i] = strdup(cfg_getnstr(cfg, "slaves", i)); 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++) { } } if (!output) return 0; fprintf(output, "master_servers = {"); char ** pcp = conf->master_servers; while (*pcp) { fprintf(output, "\"%s\"", *pcp); if (*++pcp) fprintf(output, ", "); } /* fprintf(output, "}\nmaster_zones = {"); pcp = conf->master_zones; while (*pcp) { fprintf(output, "\"%s\"", *pcp); if (*++pcp) fprintf(output, ", "); } */ fprintf(output, "}\npoll_interval = %d\nptr_file = \"%s\"\nslaves = {", conf->poll_interval, conf->ptr_file); pcp = conf->slaves; while (*pcp) { fprintf(output, "\"%s\"", *pcp); if (*++pcp) fprintf(output, ", "); } fprintf(output, "}\n"); return 0; }