diff options
Diffstat (limited to '')
-rw-r--r-- | prog/6/conf.c | 561 |
1 files changed, 498 insertions, 63 deletions
diff --git a/prog/6/conf.c b/prog/6/conf.c index 6945876..b8ce0be 100644 --- a/prog/6/conf.c +++ b/prog/6/conf.c @@ -1,88 +1,523 @@ -#include <stdio.h> -#include <search.h> -#include <string.h> +#include <stdbool.h> +#include <confuse.h> #include <arpa/inet.h> -int ipv6_compare (const struct in6_addr * a, const struct in6_addr * b) { - return memcmp(a->s6_addr, b->s6_addr, sizeof a->s6_addr); +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 zone { +struct network { struct in6_addr addr; int mask; char * email; - char ** ns; - int nslen; - struct zone * next; + 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; - int nslen; - struct ns * next; + 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; - int mask; - char ptr; - time_t created; + 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 * zone; // linked list TODO use https://en.wikipedia.org/wiki/Trie instead - struct * ns; // linked list TODO use https://en.wikipedia.org/wiki/Trie instead - void * ptrrp; // ptr root pointer for tsearch(3) + struct trie * trie; + char ** master_servers; + // char ** master_zones; + char ** slaves; + int poll_interval; + char * ptr_file; + void * suffixrp; // for tsearch(3) }; -struct config config (FILE * file) { - char line[1024]; - while (!ferror(file) && !feof(file)) { - char * ret = fgets(line, sizeof line, file); - if (!ret) - break; - char * cp = strchr(line, '/'); - int mask = -1; - if (cp) { - cp = '\0'; - if (cp[1] <= '9' && cp[1] >= '0') - mask = strtol(cp+1, &cp, 10); - else - cp++; - } else { - cp = line; - for (; strchr("0123456789abcdefABCDEF:", *cp); cp++); - if (!*cp) - continue; - cp++; +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; } - struct in6_addr addr; - switch (inet_pton(AF_INET6, line, addr.s6_addr)) { + 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: - continue; + 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: - perror("inet_pton"); - exit(EXIT_FAILURE); + 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; } - line = cp; - char * saveptr = NULL; - cp = strtok_r(line, "\t\r\n ", &saveptr); - if (!cp) - continue; - if (mask == -1) { // ptr record - struct ; - continue; + 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 main (int argc, char ** argv) { - if (argc != 2) { - fprintf(stderr, "6d configuration file checker\n" - " usage: %s configfile\n" - "an example configuration file can be found in the following locations:\n" - " /etc/6d\n" - " /usr/share/doc/6d/conf\n" - " http://ni.šijanec.eu/sijanec/r/tree/prog/6/6d.conf\n" - , argv[0]); - return 1; +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, ", "); } - config_file(); + fprintf(output, "}\n"); + return 0; } -*/ |