summaryrefslogblamecommitdiffstats
path: root/prog/6/conf.c
blob: d22c7c0edff5d2fbf9c603763421a3424f10f1e0 (plain) (tree)
1
2
3
4
5
6
7
                   

                    
                      


                                                                  












                                                                                                          

                                            
                                    


                                            
                                    
         

                                         

                                                                            
                                                            
                         



                                    
 
                


                             
                                          

                                     

                                                                                   
  











                                              



                             
                
                                                                              
  










                                   

                             
                        
                                                      























                                                                               
                                         


















                                                                                                                                 

                   
                                                                                  
  













                                           
               
                           
                        


                                                 
  






                                                                                  



                                                                 

                                                       




































                                                                                                                                                                         
                 














                                                                                                                                                                                                                                        
                               

















                                                                                                                                                                                                                             
































                                                                                                                                                               
                                                                                      

                          

                                                                                        




















                                                                                                                                                              
















                                                                                                                                                                                                                          
                 

























                                                                                                                                                                                                                 
         




                                                                                        
 











                                                                                        

                                                                         

                                                                                   
                                                            




                                                   
                                                               
                                                            
                                                                                   
                                                            



                                               
                                                          



                                               

                                                               



                                               
                                                         

                                                                          












                                                                                        


                                                                








                                                                         
                                                              










                                                                                                                                

                                            

                                   
                                    
         


                                                                                  


                                                               


                                                                               

























                                                                                                                

                 




























                                                                            
                                              


                                                                                                    
         

                                       



                                                





                                                                         



                                                
         






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