#include "config.h"
#include <inttypes.h>
#include <assert.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include <unistd.h>
#include <stdlib.h>
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#include <netinet/in.h>
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#include "zonec.h"
#include "dname.h"
#include "dns.h"
#include "namedb.h"
#include "rdata.h"
#include "region-allocator.h"
#include "util.h"
#include "options.h"
#include "nsec3.h"
#include "zone.h"
static rrset_type*
domain_find_rrset_any(domain_type *domain, uint16_t type)
{
rrset_type *result = domain->rrsets;
while (result) {
if (rrset_rrtype(result) == type) {
return result;
}
result = result->next;
}
return NULL;
}
static int
check_dname(zone_type* zone)
{
domain_type* domain;
for(domain = zone->apex; domain && domain_is_subdomain(domain,
zone->apex); domain=domain_next(domain))
{
if(domain->is_existing) {
domain_type* parent = domain->parent;
#ifdef NSEC3
if(domain_has_only_NSEC3(domain, NULL))
continue;
#endif
while(parent) {
if(domain_find_rrset_any(parent, TYPE_DNAME)) {
log_msg(LOG_ERR, "While checking node %s,",
domain_to_string(domain));
log_msg(LOG_ERR, "DNAME at %s has data below it. "
"This is not allowed (rfc 2672).",
domain_to_string(parent));
return 0;
}
parent = parent->parent;
}
}
}
return 1;
}
static int
has_soa(domain_type* domain)
{
rrset_type* p = NULL;
if(!domain) return 0;
for(p = domain->rrsets; p; p = p->next)
if(rrset_rrtype(p) == TYPE_SOA)
return 1;
return 0;
}
struct zonec_state {
struct namedb *database;
struct domain_table *domains;
struct zone *zone;
size_t errors;
size_t records;
struct collect_rrs c;
};
static void zonec_commit_rrset(zone_parser_t *parser, struct zonec_state *state)
{
struct rrset *rrset;
int priority = parser->options.secondary ? ZONE_WARNING : ZONE_ERROR;
if(!state->c.domain || state->c.rr_count == 0)
return;
if (!state->c.rrset) {
rrset = region_alloc(state->database->region, sizeof(*rrset)
#ifdef PACKED_STRUCTS
+ sizeof(rr_type*) * state->c.rr_count
#endif
);
rrset->zone = state->zone;
rrset->rr_count = state->c.rr_count;
#ifndef PACKED_STRUCTS
rrset->rrs = region_alloc(state->database->region,
sizeof(rr_type*) * state->c.rr_count);
#endif
memcpy(rrset->rrs, state->c.rrs, state->c.rr_count * sizeof(rr_type*));
switch (state->c.type) {
case TYPE_CNAME:
if (!domain_find_non_cname_rrset(state->c.domain, state->zone))
break;
zone_log(parser, priority, "CNAME and other data at the same name");
break;
case TYPE_RRSIG:
case TYPE_NXT:
case TYPE_SIG:
case TYPE_NSEC:
case TYPE_NSEC3:
break;
default:
if (!domain_find_rrset(state->c.domain, state->zone, TYPE_CNAME))
break;
zone_log(parser, priority, "CNAME and other data at the same name");
break;
}
domain_add_rrset(state->c.domain, rrset);
} else {
#ifndef PACKED_STRUCTS
struct rr **rrs;
#else
struct rrset *rrset_orig;
#endif
rrset = state->c.rrset;
#ifndef PACKED_STRUCTS
rrs = rrset->rrs;
rrset->rrs = region_alloc_array(
state->database->region, rrset->rr_count + state->c.rr_count, sizeof(*rrs));
memcpy(rrset->rrs, rrs, rrset->rr_count * sizeof(*rrs));
region_recycle(state->database->region, rrs, rrset->rr_count * sizeof(*rrs));
#else
rrset_orig = rrset;
rrset = region_alloc(state->database->region,
sizeof(rrset_type) +
(rrset_orig->rr_count+state->c.rr_count)*sizeof(rr_type*));
memcpy(rrset, rrset_orig,
sizeof(rrset_type) +
rrset_orig->rr_count*sizeof(rr_type*));
if(state->c.rrset_prev)
state->c.rrset_prev->next = rrset;
else state->c.domain->rrsets = rrset;
region_recycle(state->database->region, rrset_orig,
sizeof(rrset_type) +
rrset_orig->rr_count*sizeof(rr_type*));
#endif
memcpy(rrset->rrs + rrset->rr_count, state->c.rrs, state->c.rr_count * sizeof(rr_type*));
rrset->rr_count += state->c.rr_count;
}
state->records += state->c.rr_count;;
if (state->c.rrs[0]->owner == state->zone->apex)
apex_rrset_checks(state->database, rrset, state->c.rrs[0]->owner);
state->c.domain = NULL;
state->c.type = -1;
state->c.rrset = NULL;
#ifdef PACKED_STRUCTS
state->c.rrset_prev = NULL;
#endif
state->c.rr_count = 0;
}
int32_t zonec_accept(
zone_parser_t *parser,
const zone_name_t *owner,
uint16_t type,
uint16_t class,
uint32_t ttl,
uint16_t rdlength,
const uint8_t *rdata,
void *user_data)
{
struct rr *rr;
struct dname_buffer dname;
struct domain *domain;
struct buffer buffer;
int priority;
int32_t code;
const struct nsd_type_descriptor *descriptor;
struct zonec_state *state = (struct zonec_state *)user_data;
assert(state);
buffer_create_from(&buffer, rdata, rdlength);
priority = parser->options.secondary ? ZONE_WARNING : ZONE_ERROR;
if (class != CLASS_IN)
zone_log(parser, priority, "only class IN is supported");
if(!dname_make_buffered(&dname, (uint8_t*)owner->octets, 1)) {
zone_log(parser, ZONE_ERROR, "the owner cannot be converted");
return ZONE_BAD_PARAMETER;
}
domain = domain_table_insert(state->domains, (void*)&dname);
assert(domain);
if (domain != state->c.domain || type != state->c.type
|| state->c.rr_count >= (int)(sizeof(state->c.rrs) / sizeof(*state->c.rrs))){
zonec_commit_rrset(parser, state);
state->c.domain = domain;
state->c.type = type;
state->c.rrset = NULL;
#ifdef PACKED_STRUCTS
state->c.rrset_prev = NULL;
#endif
}
descriptor = nsd_type_descriptor(type);
code = descriptor->read_rdata(state->domains, rdlength, &buffer, &rr);
if(code < 0) {
zone_log(parser, ZONE_ERROR, "the RR rdata fields are wrong for the type, %s %s %s",
dname_to_string((void*)&dname,0),
rrtype_to_string(type),
read_rdata_fail_str(code));
if(code == TRUNCATED)
return ZONE_OUT_OF_MEMORY;
return ZONE_BAD_PARAMETER;
}
rr->owner = domain;
rr->type = type;
rr->klass = class;
rr->ttl = ttl;
if (type == TYPE_SOA) {
if (domain != state->zone->apex) {
char s[MAXDOMAINLEN*5];
snprintf(s, sizeof(s), "%s", domain_to_string(domain));
zone_log(parser, priority, "SOA record with invalid domain name, '%s' is not '%s'",
domain_to_string(state->zone->apex), s);
} else if (has_soa(domain)) {
zone_log(parser, priority, "this SOA record was already encountered");
}
domain->is_apex = 1;
}
if (!domain_is_subdomain(domain, state->zone->apex)) {
char s[MAXDOMAINLEN*5];
snprintf(s, sizeof(s), "%s", domain_to_string(state->zone->apex));
zone_log(parser, priority, "out of zone data: %s is outside the zone for fqdn %s",
s, domain_to_string(domain));
if (!parser->options.secondary) {
return ZONE_SEMANTIC_ERROR;
}
}
if (state->c.rr_count == 0) {
#ifndef PACKED_STRUCTS
state->c.rrset = domain_find_rrset(state->c.domain, state->zone, state->c.type);
#else
state->c.rrset = domain_find_rrset_and_prev(state->c.domain, state->zone, state->c.type, &state->c.rrset_prev);
#endif
}
if (type == TYPE_RRSIG)
;
else if (state->c.rrset && ttl != state->c.rrset->rrs[0]->ttl) {
zone_log(parser, ZONE_WARNING,
"%s TTL %"PRIu32" does not match TTL %u of %s RRset",
domain_to_string(domain), ttl,
state->c.rrset->rrs[0]->ttl, rrtype_to_string(type));
} else if (state->c.rr_count && ttl != state->c.rrs[0]->ttl) {
zone_log(parser, ZONE_WARNING,
"%s TTL %"PRIu32" does not match TTL %u of %s RRset",
domain_to_string(domain), ttl,
state->c.rrs[0]->ttl, rrtype_to_string(type));
}
if (state->c.rrset || state->c.rr_count) {
switch (type) {
case TYPE_CNAME:
zone_log(parser, priority, "multiple CNAMEs at the same name");
break;
case TYPE_DNAME:
zone_log(parser, priority, "multiple DNAMEs at the same name");
break;
default:
break;
}
}
if (state->c.rrset) {
for (int i = 0; i < state->c.rrset->rr_count; i++) {
if (!equal_rr_rdata(descriptor, rr, state->c.rrset->rrs[i]))
continue;
rr_lower_usage(state->database, rr);
region_recycle(state->database->region, rr, sizeof(*rr) + rr->rdlength);
return 0;
}
}
for (int i = 0; i < state->c.rr_count; i++) {
if (!equal_rr_rdata(descriptor, rr, state->c.rrs[i]))
continue;
rr_lower_usage(state->database, rr);
region_recycle(state->database->region, rr, sizeof(*rr) + rr->rdlength);
return 0;
}
state->c.rrs[state->c.rr_count++] = rr;
return 0;
}
static int32_t zonec_include(
zone_parser_t *parser,
const char *file,
const char *path,
void *user_data)
{
char **paths;
struct zonec_state *state;
struct namedb *database;
struct zone *zone;
(void)parser;
(void)file;
state = (struct zonec_state *)user_data;
database = state->database;
zone = state->zone;
assert((zone->includes.count == 0) == (zone->includes.paths == NULL));
for (size_t i=0; i < zone->includes.count; i++)
if (strcmp(path, zone->includes.paths[i]) == 0)
return 0;
paths = region_alloc_array(
database->region, zone->includes.count + 1, sizeof(*paths));
if (zone->includes.count) {
const size_t size = zone->includes.count * sizeof(*paths);
memcpy(paths, zone->includes.paths, size);
region_recycle(database->region, zone->includes.paths, size);
}
paths[zone->includes.count] = region_strdup(database->region, path);
zone->includes.count++;
zone->includes.paths = paths;
return 0;
}
static void zonec_log(
zone_parser_t *parser,
uint32_t category,
const char *file,
size_t line,
const char *message,
void *user_data)
{
int priority;
struct zonec_state *state = (struct zonec_state *)user_data;
assert(state);
(void)parser;
switch (category) {
case ZONE_INFO:
priority = LOG_INFO;
break;
case ZONE_WARNING:
priority = LOG_WARNING;
break;
default:
priority = LOG_ERR;
state->errors++;
break;
}
if (file)
log_msg(priority, "%s:%zu: %s", file, line, message);
else
log_msg(priority, "%s", message);
}
unsigned int
zonec_read(
struct namedb *database,
struct domain_table *domains,
const char *name,
const char *zonefile,
struct zone *zone)
{
const struct dname *origin;
zone_parser_t parser;
zone_options_t options;
zone_name_buffer_t name_buffer;
zone_rdata_buffer_t rdata_buffer;
struct zonec_state state;
zone_buffers_t buffers = { 1, &name_buffer, &rdata_buffer };
state.database = database;
state.domains = domains;
state.zone = zone;
state.errors = 0;
state.records = 0;
state.c.domain = NULL;
state.c.type = -1;
state.c.rrset = NULL;
#ifdef PACKED_STRUCTS
state.c.rrset_prev = NULL;
#endif
state.c.rr_count = 0;
origin = domain_dname(zone->apex);
memset(&options, 0, sizeof(options));
options.origin.octets = dname_name(origin);
options.origin.length = origin->name_size;
options.default_ttl = DEFAULT_TTL;
options.default_class = CLASS_IN;
options.secondary = zone_is_slave(zone->opts) != 0;
options.pretty_ttls = true;
options.log.callback = &zonec_log;
options.accept.callback = &zonec_accept;
options.include.callback = &zonec_include;
if (zone_parse(&parser, &options, &buffers, zonefile, &state) != 0) {
for (int i = 0; i < state.c.rr_count; i++) {
rr_lower_usage(database, state.c.rrs[i]);
region_recycle( database->region, state.c.rrs[i]
, sizeof(*state.c.rrs[i])
+ state.c.rrs[i]->rdlength);
}
return state.errors;
}
zonec_commit_rrset(&parser, &state);
if (!zone) {
log_msg(LOG_ERR, "zone configured as '%s' has no content.", name);
state.errors++;
} else if (!zone->soa_rrset || zone->soa_rrset->rr_count == 0) {
log_msg(LOG_ERR, "zone configured as '%s' has no SOA record", name);
state.errors++;
} else if (dname_compare(domain_dname(zone->soa_rrset->rrs[0]->owner), origin) != 0) {
log_msg(LOG_ERR, "zone configured as '%s', but SOA has owner '%s'",
name, domain_to_string(zone->soa_rrset->rrs[0]->owner));
state.errors++;
}
if(!zone_is_slave(zone->opts) && !check_dname(zone))
state.errors++;
return state.errors;
}
void
apex_rrset_checks(namedb_type* db, rrset_type* rrset, domain_type* domain)
{
uint32_t soa_minimum = 0;
unsigned i;
zone_type* zone = rrset->zone;
assert(domain == zone->apex);
(void)domain;
if (rrset_rrtype(rrset) == TYPE_SOA) {
zone->soa_rrset = rrset;
if(zone->soa_nx_rrset == 0) {
zone->soa_nx_rrset = region_alloc(db->region,
sizeof(rrset_type)
#ifdef PACKED_STRUCTS
+ sizeof(rr_type*)
#endif
);
zone->soa_nx_rrset->rr_count = 1;
zone->soa_nx_rrset->next = 0;
zone->soa_nx_rrset->zone = zone;
#ifndef PACKED_STRUCTS
zone->soa_nx_rrset->rrs = region_alloc(db->region,
sizeof(rr_type*));
#endif
zone->soa_nx_rrset->rrs[0] = region_alloc(db->region,
sizeof(rr_type)+rrset->rrs[0]->rdlength);
}
memcpy(zone->soa_nx_rrset->rrs[0], rrset->rrs[0],
sizeof(rr_type)+rrset->rrs[0]->rdlength);
retrieve_soa_rdata_minttl(rrset->rrs[0], &soa_minimum);
if (rrset->rrs[0]->ttl > soa_minimum) {
zone->soa_nx_rrset->rrs[0]->ttl = soa_minimum;
}
} else if (rrset_rrtype(rrset) == TYPE_NS) {
zone->ns_rrset = rrset;
} else if (rrset_rrtype(rrset) == TYPE_RRSIG) {
for (i = 0; i < rrset->rr_count; ++i) {
if(rr_rrsig_type_covered(rrset->rrs[i])==TYPE_DNSKEY){
zone->is_secure = 1;
break;
}
}
}
}