From 2cdcc79e69aa5f1ed255f3894ab7e061b0761d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Mon, 7 Aug 2023 23:15:32 +0200 Subject: 6 --- prog/6/6d.conf | 155 ++++++++++++--- prog/6/conf.c | 561 +++++++++++++++++++++++++++++++++++++++++++++++------ prog/6/daemon.c | 59 ++++-- prog/6/functions.c | 4 + prog/6/makefile | 4 +- 5 files changed, 678 insertions(+), 105 deletions(-) create mode 100644 prog/6/functions.c diff --git a/prog/6/6d.conf b/prog/6/6d.conf index 39e751c..3ecc3ff 100644 --- a/prog/6/6d.conf +++ b/prog/6/6d.conf @@ -1,28 +1,127 @@ -the example configuration file for 6d -this is where you define your zones/networks and static entries -syntax description: -- all lines that are unparsable are considered comments and ignored -- zones/networks are defined with a space separated list of the following items on the same line - 1) an ipv6 network address (see `man inet_pton`) and a netmask with a '/' in between - 2) the admin email address of the dns administrator - 3) a space-separated list of FQDNs of nameservers for this zone, the first entry being the master -- static PTRs for hosts are defined by an IPv6 address and FQDN, separated by a single space on a single line -- NS delegations for subnetworks are defined by an IPv6 subnetwork (address+mask) and nameserver FQDNs, with everything separated by a single space -- the order of configuration lines does not matter - -the following defines a dns zone. there can be many such lines for different networks. -those are authoritative zones for which on-the-fly generation will occur. -all nameservers, provided here, must be 6d nameservers or at least 6d nameservers behind a DNS proxy, such as bind. -there can be as many nameservers (but at least one) after the admin email -2001:db8::/32 dns@t-2.net.example dns1.t-2.net.example dns2.t-2.net.example - -the following line defines a static PTR FQDN for a single host -2001:db8:0:d::b90a tranzistor.sijanec.eu.example - -the following line defines static NS records for a subnetwork. -this subnetwork must be a subnetwork of a zone that 6d will generate records for. -when 6d will be asked for an address that is part of this subnetwork, it will state that some other server is authoritative for this subnetwork and direct the client to the server provided. -there can be as many nameservers (but at least one) after the network name -2001:db8:e77:5500::/56 ns1.sijanec.org.example ns2.sijanec.org.example - -you can run `6d dry ` to parse the configuration file, output it and exit without starting the daemon +# The example configuration file for 6d. +# This is where you define your zones/networks and static entries. +# It is parsed by libconfuse. +# Default TTL is 420. +# You can run `6d dry ` to parse the configuration file, output it and exit. +# You can `killall -SIGHUP 6d` or `service 6d reload` to reload the configuration of a running 6d. Reloading is a safe operation as an unparsable config file will not cause the program to stop, so check the logs after reloading to see if the reload succeeded. +# Hostnames that need to be resolved (for 6d-protocol communication) are resolved when needed and not on configuration reload, so you can change IP addresses of servers in DNS without needing to reload the server. This also means that if something doesn't resolve, it will not be apparent when you reload the config, but later on in runtime. +# Reloading the server also schedules a synchronization from masters (if any). +# Masters and slaves must have accurate clocks (at least minute accuracy is expected) +# To specify IPv4 addresses, use the V4MAPPED address format (::ffff:192.0.2.69). +# Zone transfers are made using a 6d-specific TCP+UDP-based protocol, so if you run 6d being a DNS proxy, such as bind9 zone forward, you must specify the host/port combination where 6d listens, not where bind9 listens. When unsigned, this 6d-specific protocol relies on trusted routing to master servers and no MiTM attacks on the line. +# DNSSEC and signed zone transfers are available upon request (mailto:anton@šijanec.eu). + +#################### SLAVE CONFIGURATION ###################### (You may delete this part on master-only 6d.) + +# You can optionally define master servers here, all of their configuration will be periodically retrieved and mirrored to this 6d instance. A host may optionally be followed by /TCPport. +master_servers = {6master.sijanec.eu, 6d2.example, 2001:db8::1/5353} + +# OBSOLETE NON-FEATURE (NOT WORKING) +# You can optionally define master networks/zones here, they will be also be mirrored from their master server. The master server will be obtained from their SOA record. +# Note that 6d is not a general purpose DNS server! Any FQDNs specified here will be treated as 6d suffix generators. +# Note that if this is used, master must not be behind a DNS proxy, such as bind9 zone forward. Use master_servers instead if that's the case. +# master_zones = {2001:db8:a::/48, 2001:db8:b::/48, 2001:db8:c::/48, 6ptr.sijanec.eu} + +# Master servers will be checked for changes every poll_interval number of seconds. Set to 0 to disable polling. +# 69 is the default. +poll_interval = 69 + +# Slaves hold everything they know in memory. + +#################### MASTER CONFIGURATION ##################### (You may delete this part on slave-only 6d.) + +# A computer in networks defined here may register a PTR for itself with the 6c program; such records are valid for two days. Everytime a record is created, it will be logged into this file. When 6d starts, old records are pruned and valid records are loaded into memory. Pruning is also done every two days. +# Static PTR and NS records obtained from the configuration file or master server always take precedence before 6c PTR requests. +# This binary file is not not portable. You can only read it on the same machine it was created on. +# /var/cache/6/backup is the default file. +ptr_file = /var/cache/6/backup + +# Instead of polling, slaves can be notified on changes. This is done over a DNS-compatible UDP protocol so slaves can run behind bind9 forward zone-like DNS proxies. +slaves = {6slave.sijanec.eu/666, ::ffff:192.0.2.69, 2001:db8::2/5353} + +# Defines networks to generate PTR records on the fly. The only required option is master. +network +{ + # List of networks this block defines. + networks = {2001:db8:d::/48, 2001:db8:e::/48} + # List the following slaves in NS responses. + slaves = {6slave.sijanec.eu, 6slave.example} + # This will be published in the SOA record. + admin = 6@sijanec.eu + # This will be published as the authoritative server in the SOA record (point it to this 6d instance). + master = 6d.example + # PTRs will be generated in form 2001-db8-d--5932.suffix for address 2001:db8:d::5932. + # By default, this suffix is the [...].ip6.arpa domain, so for network 2001:db8::/32, the suffix will be 8.B.D.0.1.0.0.2.IP6.ARPA, and the above mentioned PTR would be 2001-db8-d--5932.8.b.d.0.1.0.0.2.ip6.adpa, which is totaly OK standard-wise. Do not specify [...].ip6.arpa addresses as suffixes yourself, they will be managed automatically. + # The suffix must respond to queries with the correct AAAA records, 6d can serve it for you (see below). + ### suffix = "6ptr.sijanec.eu" + # TTL for generated records and negative caching. + ttl = 420 +} + +# Another networks definition. +network +{ + networks = {2001:db8:f:100::/56, 2001:db8:f:200::/56, 2001:db8:900::/48} + master = ptrdns1.example + suffix = suffixgenerator.net.example +} + +# Define suffixes that will generate AAAA records on the fly. The only required option is master. +suffix +{ + # List of suffixes + suffixes = {6ptr.sijanec.eu, ipv6.isp-provider.example} + # The netmasks that this on-the-fly generator will accept. + # By specifying ::/0 here you allow any network on the internet to use your suffix for PTRs. + # ::/0 is the default. + accept = {::/0} + slaves = {6slave.sijanec.org, 6slave.example} + admin = 6@sijanec.eu + master = 6ptr.sijanec.eu + # TTL for generated records and negative caching. + ttl = 420 +} + +# Another suffixes definition, this time networks are specified, other IPv6 addresses will be NXDOMAIN. +suffix +{ + suffixes = {private-ipv6.net.example, private-ipv6.org.example} + accept = {2001:db8:f:100::/56, 2001:db8:f:200::/56, 2001:db8:900::/48} + master = locked-ns1.net.example +} + +############################# STATIC NS AND PTR RECORDS ############################### + +# A static PTR entry for an IP address. You must configure the hostname to have the correct AAAA record yourself! The only required option is hostname. +ptr 2001:db8:d::1 +{ + hostname = mail.example + ttl = 420 +} + +# Another PTR definition +ptr 2001:db8:d::2 +{ + hostname = mail-out2.example +} + +# A static NS entry for some networks. Instead of on-the-fly generation, PTR queries will redirect clients to this NS. The only requirement is that ns list has a least one element. +ns +{ + networks = {2001:db8:d:1337::/64, 2001:db8:d:1338::/64} + ns = {ns1.sijanec.org, ns2.sijanec.org} + ttl = 420 +} + +# Another NS delegation. +ns +{ + networks = {2001:db8:8:1300::/56} + ns = {ns1.kompot.example} +} + +################################ IMPLEMENTATION NOTES ################################### + +# Specifying overlapping networks in accept clauses of suffix declarations is not suggested. Only the smaller network of two overlapping networks will be accepted. +# Specifying overlapping networks in network clauses is also not suggested. The configuration of the larger network of the two overlapping networks will be used. +# Static records only make sense in networks you are authoritative for. Static records not inside a network will be silently ignored. 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 -#include -#include +#include +#include #include -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; } -*/ diff --git a/prog/6/daemon.c b/prog/6/daemon.c index 849ebb3..a808487 100644 --- a/prog/6/daemon.c +++ b/prog/6/daemon.c @@ -10,7 +10,35 @@ #include #include #include -#define DEBUG 1 +#include +#include "functions.c" +#include "conf.c" +int write_ptr6c (FILE * stream, const struct ptr ptr6c) { + if (!fwrite(ptr6c.addr.s6_addr, sizeof ptr6c.addr.s6_addr, 1, stream)) + return 1; + if (!fwrite(&ptr6c.ttl, sizeof ptr6c.ttl, 1, stream)) + return 2; + if (fputs(ptr6c.hostname, stream) == EOF) + return 3; + fputc('\0', stream); + return 0; +} +int prune (struct config * config, const char * filename) { + FILE * file = fopen(filename, "w"); + if (!filename) { + fprintf(stderr, "fopen(\"%s\", \"w\"): %s", filename, strerror(errno)); + return 1; + } + int ret = 0; + for (struct trie * trie = config->trie; trie; trie = next(trie)) + if (trie->type == ptr6c) { + if (((struct ptr *) trie->data)->ttl+60*48 >= timestamp()) + ret += write_ptr6c(file, *((struct ptr *) trie->data)); + else + free_trie_ptr(trie); + } + return ret; +} int handle (unsigned char * packet, int bytes) { HEADER * header = (HEADER *) packet; ns_msg handle; @@ -31,30 +59,37 @@ int handle (unsigned char * packet, int bytes) { return bytes; } ns_rr rr; - if (ns_parserr(handle, ms_s_qd, 0, &rr) == -1) { + if (ns_parserr(&handle, ns_s_qd, 0, &rr) == -1) { header->rcode = FORMERR; return bytes; } - + return -1; } int main (int argc, char ** argv) { if (argc != 3) { fprintf(stderr, "%s port config\n" - " port: 53 (UDP listening port) (configurable to allow many daemons)\n" - " config: file name of the configuration file (use 6c to check syntax)\n" - "creates PTR and AAAA records with on-the-fly method (RFC 8501, section 2.5)\n" - "an example of records created for IPv6 2001:db8:1\n" - " 1.0.[...].0.8.B.D.0.1.0.0.2.IP6.ARPA. 127800 IN PTR 1.0.[...].0.8.B.D.0.1.0.0.2.IP6.ARPA.\n" - " 1.0.[...].0.8.B.D.0.1.0.0.2.IP6.ARPA. 127800 IN AAAA 2001:db8::1\n" + " port: 53 (TCP+UDP listening port) (configurable to allow many daemons)\n" + " TCP is only for inter-6d zone transfers, queries will not work over TCP\n" + " config: file name of the configuration file (check the example config for documentation)\n" "more information:\n" - " - SOA serial will be the number of days since 2023-08-06\n" + " - SOA serial will be the number of UTC/UNIX (not real) minutes since 2023-08-08 00:00 UTC\n" " - refresh, retry and expire in SOA will have values conforming to standard, but\n" " they are irrelevant, as potential 6d slaves are not real DNS slaves\n" - " - negative cache TTL is 1337, this is irrelevant, as nxdomains aren't expected\n" - " - to exit after reading and printing out the configuration, run %s dry \n" , argv[0]); return 1; } + struct config conf; + memset(&conf, 0, sizeof conf); + FILE * conf_output = stderr; + if (argv[1][0] == 'd') + conf_output = stdout; + int ret = config(&conf, argv[2], conf_output); + if (ret) { + fprintf(stderr, "error %d while parsing the configuration file!\n", ret); + return 9+ret; + } + if (argv[1][0] == 'd') + return 0; int sock = socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); if (sock == -1) { perror("socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)"); diff --git a/prog/6/functions.c b/prog/6/functions.c new file mode 100644 index 0000000..48c0b58 --- /dev/null +++ b/prog/6/functions.c @@ -0,0 +1,4 @@ +#include +int timestamp () { + return (time(NULL)-1691452800)/60; +} diff --git a/prog/6/makefile b/prog/6/makefile index 8a847a2..8bd7dbe 100644 --- a/prog/6/makefile +++ b/prog/6/makefile @@ -1,11 +1,11 @@ DESTDIR=/ CC=cc MYCFLAGS=-O0 -Wall -Wextra -Wformat -pedantic -g -MYLDFLAGS=-lresolv +MYLDFLAGS=-lresolv -lconfuse default: 6d 6c -6d: daemon.c +6d: daemon.c conf.c $(CC) $(MYCFLAGS) $(CFLAGS) $< -o$@ $(MYLDFLAGS) $(LDFLAGS) 6c: client.c -- cgit v1.2.3