zonefile.c 10.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

17
#include <assert.h>
18 19 20
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
21
#include <errno.h>
22
#include <sys/types.h>
23
#include <sys/stat.h>
24
#include <fcntl.h>
25
#include <time.h>
26
#include <unistd.h>
Daniel Salzman's avatar
Daniel Salzman committed
27
#include <inttypes.h>
28

29
#include "libknot/libknot.h"
30
#include "libknot/internal/strlcat.h"
31
#include "libknot/internal/strlcpy.h"
32
#include "libknot/internal/macros.h"
33
#include "knot/zone/semantic-check.h"
34
#include "knot/zone/contents.h"
35
#include "knot/dnssec/zone-nsec.h"
36
#include "knot/common/debug.h"
37
#include "knot/zone/zonefile.h"
38
#include "libknot/rdata.h"
39
#include "knot/zone/zone-dump.h"
40
#include "libknot/rrtype/naptr.h"
41

42 43 44
#define ERROR(zone, fmt, ...) log_zone_error(zone, "zone loader, " fmt, ##__VA_ARGS__)
#define WARNING(zone, fmt, ...) log_zone_warning(zone, "zone loader, " fmt, ##__VA_ARGS__)
#define INFO(zone, fmt, ...) log_zone_info(zone, "zone loader, " fmt, ##__VA_ARGS__)
Jan Včelák's avatar
Jan Včelák committed
45

46
void process_error(zs_scanner_t *s)
47
{
48 49 50
	zcreator_t *zc = s->data;
	const knot_dname_t *zname = zc->z->apex->owner;

51
	ERROR(zname, "%s in zone, file '%s', line %"PRIu64" (%s)",
Jan Včelák's avatar
Jan Včelák committed
52 53 54
	      s->stop ? "fatal error" : "error",
	      s->file.name, s->line_counter,
	      zs_strerror(s->error_code));
55
}
56

57
static int add_rdata_to_rr(knot_rrset_t *rrset, const zs_scanner_t *scanner)
58
{
59
	return knot_rrset_add_rdata(rrset, scanner->r_data, scanner->r_data_length,
60
	                         scanner->r_ttl, NULL);
61 62
}

63 64
static bool handle_err(zcreator_t *zc, const zone_node_t *node,
                       const knot_rrset_t *rr, int ret, bool master)
65
{
66
	const knot_dname_t *zname = zc->z->apex->owner;
67
	char *rrname = rr ? knot_dname_to_str_alloc(rr->owner) : NULL;
68
	if (ret == KNOT_EOUTOFZONE) {
Jan Včelák's avatar
Jan Včelák committed
69 70
		WARNING(zname, "ignoring out-of-zone data, owner '%s'",
		        rrname ? rrname : "unknown");
71
		free(rrname);
72
		return true;
73 74 75
	} else if (ret == KNOT_ETTL) {
		free(rrname);
		assert(node);
76
		log_ttl_error(zc->z, node, rr);
77 78
		// Fail if we're the master for this zone.
		return !master;
79
	} else {
Jan Včelák's avatar
Jan Včelák committed
80 81
		ERROR(zname, "failed to process record, owner '%s'",
		      rrname ? rrname : "unknown");
82
		free(rrname);
83 84 85 86
		return false;
	}
}

87 88
void log_ttl_error(const zone_contents_t *zone, const zone_node_t *node,
		   const knot_rrset_t *rr)
89 90 91 92 93 94 95
{
	err_handler_t err_handler;
	err_handler_init(&err_handler);
	// Prepare additional info string.
	char info_str[64] = { '\0' };
	char type_str[16] = { '\0' };
	knot_rrtype_to_string(rr->type, type_str, sizeof(type_str));
Jan Včelák's avatar
Jan Včelák committed
96
	int ret = snprintf(info_str, sizeof(info_str), "record type %s",
97 98 99
	                   type_str);
	if (ret <= 0 || ret >= sizeof(info_str)) {
		*info_str = '\0';
100 101
	}

102
	/*!< \todo REPLACE WITH FATAL ERROR for master. */
103
	err_handler_handle_error(&err_handler, zone, node,
104
	                         ZC_ERR_TTL_MISMATCH, info_str);
105 106
}

107
int zcreator_step(zcreator_t *zc, const knot_rrset_t *rr)
Jan Kadlec's avatar
Jan Kadlec committed
108
{
109
	if (zc == NULL || rr == NULL || rr->rrs.rr_count != 1) {
110 111
		return KNOT_EINVAL;
	}
112

113
	if (rr->type == KNOT_RRTYPE_SOA &&
114
	    node_rrtype_exists(zc->z->apex, KNOT_RRTYPE_SOA)) {
115 116 117
		// Ignore extra SOA
		return KNOT_EOK;
	}
118

119
	zone_node_t *node = NULL;
120
	int ret = zone_contents_add_rr(zc->z, rr, &node);
121 122
	if (ret != KNOT_EOK) {
		if (!handle_err(zc, node, rr, ret, zc->master)) {
123 124 125
			// Fatal error
			return ret;
		}
126 127 128
		if (ret == KNOT_EOUTOFZONE) {
			// Skip out-of-zone record
			return KNOT_EOK;
129
		}
130
	}
131
	assert(node);
132

133 134 135 136 137
	// Do node semantic checks
	err_handler_t err_handler;
	err_handler_init(&err_handler);
	bool sem_fatal_error = false;

138
	ret = sem_check_node_plain(zc->z, node,
139 140
	                           &err_handler, true,
	                           &sem_fatal_error);
Jan Kadlec's avatar
Jan Kadlec committed
141 142
	if (ret != KNOT_EOK) {
		return ret;
143 144
	}

145
	return sem_fatal_error ? KNOT_ESEMCHECK : KNOT_EOK;
Jan Kadlec's avatar
Jan Kadlec committed
146
}
147

Jan Kadlec's avatar
Jan Kadlec committed
148
/*! \brief Creates RR from parser input, passes it to handling function. */
149
static void scanner_process(zs_scanner_t *scanner)
Jan Kadlec's avatar
Jan Kadlec committed
150
{
151 152
	zcreator_t *zc = scanner->data;
	if (zc->ret != KNOT_EOK) {
153
		scanner->stop = true;
Jan Kadlec's avatar
Jan Kadlec committed
154
		return;
155
	}
Daniel Salzman's avatar
Daniel Salzman committed
156

157
	knot_dname_t *owner = knot_dname_copy(scanner->r_owner, NULL);
Jan Kadlec's avatar
Jan Kadlec committed
158
	if (owner == NULL) {
159
		zc->ret = KNOT_ENOMEM;
Jan Kadlec's avatar
Jan Kadlec committed
160
		return;
161
	}
Jan Kadlec's avatar
Jan Kadlec committed
162

163 164
	knot_rrset_t rr;
	knot_rrset_init(&rr, owner, scanner->r_type, scanner->r_class);
165
	int ret = add_rdata_to_rr(&rr, scanner);
166
	if (ret != KNOT_EOK) {
167
		char *rr_name = knot_dname_to_str_alloc(rr.owner);
Jan Včelák's avatar
Jan Včelák committed
168
		const knot_dname_t *zname = zc->z->apex->owner;
169
		ERROR(zname, "failed to add RDATA, file '%s', line %"PRIu64", owner '%s'",
Jan Včelák's avatar
Jan Včelák committed
170
		      scanner->file.name, scanner->line_counter, rr_name);
Jan Kadlec's avatar
Jan Kadlec committed
171
		free(rr_name);
172
		knot_dname_free(&owner, NULL);
173
		zc->ret = ret;
174 175
		return;
	}
Jan Kadlec's avatar
Jan Kadlec committed
176

177
	/* Convert RDATA dnames to lowercase before adding to zone. */
178 179 180 181 182 183
	ret = knot_rrset_rr_to_canonical(&rr);
	if (ret != KNOT_EOK) {
		knot_dname_free(&owner, NULL);
		zc->ret = ret;
		return;
	}
184

185
	zc->ret = zcreator_step(zc, &rr);
186
	knot_dname_free(&owner, NULL);
187
	knot_rdataset_clear(&rr.rrs, NULL);
188 189
}

190 191
int zonefile_open(zloader_t *loader, const char *source,
                  const knot_dname_t *origin, bool semantic_checks)
192
{
193
	if (!loader) {
Marek Vavrusa's avatar
Marek Vavrusa committed
194
		return KNOT_EINVAL;
195
	}
Daniel Salzman's avatar
Daniel Salzman committed
196

197
	/* Check zone file. */
198
	if (access(source, F_OK | R_OK) != 0) {
199
		return KNOT_EACCES;
200
	}
Daniel Salzman's avatar
Daniel Salzman committed
201

202
	/* Create context. */
203 204 205 206
	zcreator_t *zc = malloc(sizeof(zcreator_t));
	if (zc == NULL) {
		return KNOT_ENOMEM;
	}
207
	memset(zc, 0, sizeof(zcreator_t));
Daniel Salzman's avatar
Daniel Salzman committed
208

209
	zc->z = zone_contents_new(origin);
210 211
	if (zc->z == NULL) {
		free(zc);
212 213
		return KNOT_ENOMEM;
	}
Daniel Salzman's avatar
Daniel Salzman committed
214

215 216 217 218 219 220 221
	/* Prepare textual owner for zone scanner. */
	char *origin_str = knot_dname_to_str_alloc(origin);
	if (origin_str == NULL) {
		free(zc);
		return KNOT_ENOMEM;
	}

222
	/* Create file loader. */
223
	memset(loader, 0, sizeof(zloader_t));
224
	loader->scanner = zs_scanner_create(origin_str, KNOT_CLASS_IN, 3600,
225 226 227
	                                    scanner_process, process_error,
	                                    zc);
	if (loader->scanner == NULL) {
228
		free(origin_str);
229
		free(zc);
230
		return KNOT_ERROR;
231
	}
Daniel Salzman's avatar
Daniel Salzman committed
232

233
	loader->source = strdup(source);
234
	loader->origin = origin_str;
235
	loader->creator = zc;
236
	loader->semantic_checks = semantic_checks;
Daniel Salzman's avatar
Daniel Salzman committed
237

238
	return KNOT_EOK;
239 240
}

241
zone_contents_t *zonefile_load(zloader_t *loader)
242
{
243
	dbg_zload("zload: load: Loading zone, loader: %p.\n", loader);
244
	if (!loader) {
245
		dbg_zload("zload: load: NULL loader!\n");
246
		return NULL;
247
	}
Daniel Salzman's avatar
Daniel Salzman committed
248

249
	zcreator_t *zc = loader->creator;
250 251
	const knot_dname_t *zname = zc->z->apex->owner;

252
	assert(zc);
253
	int ret = zs_scanner_parse_file(loader->scanner, loader->source);
254
	if (ret != 0 && loader->scanner->error_counter == 0) {
255
		ERROR(zname, "failed to load zone, file '%s' (%s)",
256
		      loader->source, zs_strerror(loader->scanner->error_code));
257
		goto fail;
258 259
	}

260
	if (zc->ret != KNOT_EOK) {
261
		ERROR(zname, "failed to load zone, file '%s' (%s)",
Jan Včelák's avatar
Jan Včelák committed
262
		      loader->source, knot_strerror(zc->ret));
263
		goto fail;
264
	}
Daniel Salzman's avatar
Daniel Salzman committed
265

266
	if (loader->scanner->error_counter > 0) {
267
		ERROR(zname, "failed to load zone, file '%s', %"PRIu64" errors",
Jan Včelák's avatar
Jan Včelák committed
268
		      loader->source, loader->scanner->error_counter);
269
		goto fail;
270
	}
Daniel Salzman's avatar
Daniel Salzman committed
271

272
	if (!node_rrtype_exists(loader->creator->z->apex, KNOT_RRTYPE_SOA)) {
Jan Včelák's avatar
Jan Včelák committed
273
		ERROR(zname, "no SOA record, file '%s'", loader->source);
274
		goto fail;
275
	}
Daniel Salzman's avatar
Daniel Salzman committed
276

277 278
	zone_node_t *first_nsec3_node = NULL;
	zone_node_t *last_nsec3_node = NULL;
279

280
	int kret = zone_contents_adjust_full(zc->z,
281
	                                     &first_nsec3_node, &last_nsec3_node);
282
	if (kret != KNOT_EOK) {
Jan Včelák's avatar
Jan Včelák committed
283 284
		ERROR(zname, "failed to finalize zone contents (%s)",
		      knot_strerror(kret));
285
		goto fail;
286
	}
Daniel Salzman's avatar
Daniel Salzman committed
287

288
	if (loader->semantic_checks) {
289
		int check_level = SEM_CHECK_UNSIGNED;
290
		knot_rrset_t soa_rr = node_rrset(zc->z->apex, KNOT_RRTYPE_SOA);
291 292
		assert(!knot_rrset_empty(&soa_rr)); // In this point, SOA has to exist
		const bool have_nsec3param =
293
			node_rrtype_exists(zc->z->apex, KNOT_RRTYPE_NSEC3PARAM);
294
		if (zone_contents_is_signed(zc->z) && !have_nsec3param) {
295

296
			/* Set check level to DNSSEC. */
297
			check_level = SEM_CHECK_NSEC;
298
		} else if (zone_contents_is_signed(zc->z) && have_nsec3param) {
299
			check_level = SEM_CHECK_NSEC3;
300
		}
301 302
		err_handler_t err_handler;
		err_handler_init(&err_handler);
303
		zone_do_sem_checks(zc->z, check_level,
304
		                   &err_handler, first_nsec3_node,
305
		                   last_nsec3_node);
306
		INFO(zname, "semantic check, completed");
307
	}
Daniel Salzman's avatar
Daniel Salzman committed
308

309
	return zc->z;
310 311

fail:
312
	zone_contents_deep_free(&zc->z);
313
	return NULL;
314 315
}

316 317 318 319 320 321 322 323 324 325 326
/*! \brief Return zone file mtime. */
time_t zonefile_mtime(const char *path)
{
	struct stat zonefile_st = { 0 };
	int result = stat(path, &zonefile_st);
	if (result < 0) {
		return result;
	}
	return zonefile_st.st_mtime;
}

327 328 329 330
/*! \brief Moved from zones.c, no doc. @mvavrusa */
static int zones_open_free_filename(const char *old_name, char **new_name)
{
	/* find zone name not present on the disk */
331
	char *suffix = ".XXXXXX";
332
	size_t name_size = strlen(old_name);
333 334
	size_t max_size = name_size + strlen(suffix) + 1;
	*new_name = malloc(max_size);
335 336 337
	if (*new_name == NULL) {
		return -1;
	}
338 339
	strlcpy(*new_name, old_name, max_size);
	strlcat(*new_name, suffix, max_size);
340 341
	mode_t old_mode = umask(077);
	int fd = mkstemp(*new_name);
342
	UNUSED(umask(old_mode));
343 344 345 346 347 348 349 350
	if (fd < 0) {
		free(*new_name);
		*new_name = NULL;
	}

	return fd;
}

351
int zonefile_write(const char *path, zone_contents_t *zone)
352
{
353
	if (!zone || !path) {
354 355 356 357 358 359 360 361 362
		return KNOT_EINVAL;
	}

	char *new_fname = NULL;
	int fd = zones_open_free_filename(path, &new_fname);
	if (fd < 0) {
		return KNOT_EWRITABLE;
	}

363 364
	const knot_dname_t *zname = zone->apex->owner;

365 366
	FILE *f = fdopen(fd, "w");
	if (f == NULL) {
Jan Včelák's avatar
Jan Včelák committed
367
		WARNING(zname, "failed to open zone, file '%s' (%s)",
368
		        new_fname, knot_strerror(-errno));
369 370 371 372 373
		unlink(new_fname);
		free(new_fname);
		return KNOT_ERROR;
	}

374
	if (zone_dump_text(zone, f) != KNOT_EOK) {
Jan Včelák's avatar
Jan Včelák committed
375
		WARNING(zname, "failed to save zone, file '%s'", new_fname);
376 377 378 379 380 381 382 383 384 385 386 387 388 389
		fclose(f);
		unlink(new_fname);
		free(new_fname);
		return KNOT_ERROR;
	}

	/* Set zone file rights to 0640. */
	fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);

	/* Swap temporary zonefile and new zonefile. */
	fclose(f);

	int ret = rename(new_fname, path);
	if (ret < 0 && ret != EEXIST) {
Jan Včelák's avatar
Jan Včelák committed
390 391
		WARNING(zname, "failed to swap zone files, old '%s', new '%s'",
		        path, new_fname);
392 393 394 395 396 397 398 399 400
		unlink(new_fname);
		free(new_fname);
		return KNOT_ERROR;
	}

	free(new_fname);
	return KNOT_EOK;
}

401
void zonefile_close(zloader_t *loader)
402 403 404 405
{
	if (!loader) {
		return;
	}
Daniel Salzman's avatar
Daniel Salzman committed
406

407
	zs_scanner_free(loader->scanner);
408 409

	free(loader->source);
410
	free(loader->origin);
411
	free(loader->creator);
412
}
Jan Včelák's avatar
Jan Včelák committed
413 414 415 416

#undef ERROR
#undef WARNING
#undef INFO