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

    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 24
#include <sys/stat.h>
#include <time.h>
25
#include <unistd.h>
Daniel Salzman's avatar
Daniel Salzman committed
26
#include <inttypes.h>
27

28
#include "libknot/libknot.h"
29
#include "contrib/files.h"
Daniel Salzman's avatar
Daniel Salzman committed
30 31
#include "knot/common/log.h"
#include "knot/dnssec/zone-nsec.h"
32
#include "knot/zone/semantic-check.h"
33 34
#include "knot/zone/contents.h"
#include "knot/zone/zonefile.h"
35
#include "knot/zone/zone-dump.h"
36

37 38
#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__)
39
#define NOTICE(zone, fmt, ...) log_zone_notice(zone, "zone loader, " fmt, ##__VA_ARGS__)
Jan Včelák's avatar
Jan Včelák committed
40

Daniel Salzman's avatar
Daniel Salzman committed
41
static void process_error(zs_scanner_t *s)
42
{
43
	zcreator_t *zc = s->process.data;
44 45
	const knot_dname_t *zname = zc->z->apex->owner;

46
	ERROR(zname, "%s in zone, file '%s', line %"PRIu64" (%s)",
47
	      s->error.fatal ? "fatal error" : "error",
Jan Včelák's avatar
Jan Včelák committed
48
	      s->file.name, s->line_counter,
49
	      zs_strerror(s->error.code));
50
}
51

52
static bool handle_err(zcreator_t *zc, const knot_rrset_t *rr, int ret, bool master)
53
{
54
	const knot_dname_t *zname = zc->z->apex->owner;
55

56 57 58 59 60
	char buff[KNOT_DNAME_TXT_MAXLEN + 1];
	char *owner = knot_dname_to_str(buff, rr->owner, sizeof(buff));
	if (owner == NULL) {
		owner = "";
	}
Daniel Salzman's avatar
Daniel Salzman committed
61

62
	if (ret == KNOT_EOUTOFZONE) {
63
		WARNING(zname, "ignoring out-of-zone data, owner %s", owner);
64
		return true;
65
	} else if (ret == KNOT_ETTL) {
66 67
		char type[16] = { '\0' };
		knot_rrtype_to_string(rr->type, type, sizeof(type));
68 69 70
		NOTICE(zname, "TTL mismatch, owner %s, type %s, TTL set to %u",
		       owner, type, rr->ttl);
		return true;
71
	} else {
72
		ERROR(zname, "failed to process record, owner %s", owner);
73 74 75 76
		return false;
	}
}

77
int zcreator_step(zcreator_t *zc, const knot_rrset_t *rr)
Jan Kadlec's avatar
Jan Kadlec committed
78
{
79
	if (zc == NULL || rr == NULL || rr->rrs.rr_count != 1) {
80 81
		return KNOT_EINVAL;
	}
82

83
	if (rr->type == KNOT_RRTYPE_SOA &&
84
	    node_rrtype_exists(zc->z->apex, KNOT_RRTYPE_SOA)) {
85 86 87
		// Ignore extra SOA
		return KNOT_EOK;
	}
88

89
	zone_node_t *node = NULL;
90
	int ret = zone_contents_add_rr(zc->z, rr, &node);
91
	if (ret != KNOT_EOK) {
92
		if (!handle_err(zc, rr, ret, zc->master)) {
93 94 95
			// Fatal error
			return ret;
		}
96
	}
97

98
	return KNOT_EOK;
Jan Kadlec's avatar
Jan Kadlec committed
99
}
100

Jan Kadlec's avatar
Jan Kadlec committed
101
/*! \brief Creates RR from parser input, passes it to handling function. */
102
static void process_data(zs_scanner_t *scanner)
Jan Kadlec's avatar
Jan Kadlec committed
103
{
104
	zcreator_t *zc = scanner->process.data;
105
	if (zc->ret != KNOT_EOK) {
106
		scanner->state = ZS_STATE_STOP;
Jan Kadlec's avatar
Jan Kadlec committed
107
		return;
108
	}
Daniel Salzman's avatar
Daniel Salzman committed
109

110
	knot_dname_t *owner = knot_dname_copy(scanner->r_owner, NULL);
Jan Kadlec's avatar
Jan Kadlec committed
111
	if (owner == NULL) {
112
		zc->ret = KNOT_ENOMEM;
Jan Kadlec's avatar
Jan Kadlec committed
113
		return;
114
	}
Jan Kadlec's avatar
Jan Kadlec committed
115

116
	knot_rrset_t rr;
117 118 119
	knot_rrset_init(&rr, owner, scanner->r_type, scanner->r_class, scanner->r_ttl);

	int ret = knot_rrset_add_rdata(&rr, scanner->r_data, scanner->r_data_length, NULL);
120
	if (ret != KNOT_EOK) {
121
		knot_rrset_clear(&rr, NULL);
122
		zc->ret = ret;
123 124
		return;
	}
Jan Kadlec's avatar
Jan Kadlec committed
125

126
	/* Convert RDATA dnames to lowercase before adding to zone. */
127 128
	ret = knot_rrset_rr_to_canonical(&rr);
	if (ret != KNOT_EOK) {
129
		knot_rrset_clear(&rr, NULL);
130 131 132
		zc->ret = ret;
		return;
	}
133

134
	zc->ret = zcreator_step(zc, &rr);
135
	knot_rrset_clear(&rr, NULL);
136 137
}

138
int zonefile_open(zloader_t *loader, const char *source,
139
		  const knot_dname_t *origin, bool semantic_checks, time_t time)
140
{
141
	if (!loader) {
Marek Vavrusa's avatar
Marek Vavrusa committed
142
		return KNOT_EINVAL;
143
	}
Daniel Salzman's avatar
Daniel Salzman committed
144

145 146
	memset(loader, 0, sizeof(zloader_t));

147
	/* Check zone file. */
148
	if (access(source, F_OK | R_OK) != 0) {
149
		return KNOT_EACCES;
150
	}
Daniel Salzman's avatar
Daniel Salzman committed
151

152
	/* Create context. */
153 154 155 156
	zcreator_t *zc = malloc(sizeof(zcreator_t));
	if (zc == NULL) {
		return KNOT_ENOMEM;
	}
157
	memset(zc, 0, sizeof(zcreator_t));
Daniel Salzman's avatar
Daniel Salzman committed
158

159
	zc->z = zone_contents_new(origin);
160 161
	if (zc->z == NULL) {
		free(zc);
162 163
		return KNOT_ENOMEM;
	}
Daniel Salzman's avatar
Daniel Salzman committed
164

165 166 167
	/* Prepare textual owner for zone scanner. */
	char *origin_str = knot_dname_to_str_alloc(origin);
	if (origin_str == NULL) {
168
		zone_contents_deep_free(zc->z);
169 170 171 172
		free(zc);
		return KNOT_ENOMEM;
	}

173 174 175 176
	if (zs_init(&loader->scanner, origin_str, KNOT_CLASS_IN, 3600) != 0 ||
	    zs_set_input_file(&loader->scanner, source) != 0 ||
	    zs_set_processing(&loader->scanner, process_data, process_error, zc) != 0) {
		zs_deinit(&loader->scanner);
177
		free(origin_str);
178
		zone_contents_deep_free(zc->z);
179
		free(zc);
180
		return KNOT_EFILE;
181
	}
182
	free(origin_str);
Daniel Salzman's avatar
Daniel Salzman committed
183

184
	loader->source = strdup(source);
185
	loader->creator = zc;
186
	loader->semantic_checks = semantic_checks;
187
	loader->time = time;
Daniel Salzman's avatar
Daniel Salzman committed
188

189
	return KNOT_EOK;
190 191
}

192
zone_contents_t *zonefile_load(zloader_t *loader)
193 194
{
	if (!loader) {
195
		return NULL;
196
	}
Daniel Salzman's avatar
Daniel Salzman committed
197

198
	zcreator_t *zc = loader->creator;
199 200
	const knot_dname_t *zname = zc->z->apex->owner;

201
	assert(zc);
202 203
	int ret = zs_parse_all(&loader->scanner);
	if (ret != 0 && loader->scanner.error.counter == 0) {
204
		ERROR(zname, "failed to load zone, file '%s' (%s)",
205
		      loader->source, zs_strerror(loader->scanner.error.code));
206
		goto fail;
207 208
	}

209
	if (zc->ret != KNOT_EOK) {
210
		ERROR(zname, "failed to load zone, file '%s' (%s)",
Jan Včelák's avatar
Jan Včelák committed
211
		      loader->source, knot_strerror(zc->ret));
212
		goto fail;
213
	}
Daniel Salzman's avatar
Daniel Salzman committed
214

215
	if (loader->scanner.error.counter > 0) {
216
		ERROR(zname, "failed to load zone, file '%s', %"PRIu64" errors",
217
		      loader->source, loader->scanner.error.counter);
218
		goto fail;
219
	}
Daniel Salzman's avatar
Daniel Salzman committed
220

221
	if (!node_rrtype_exists(loader->creator->z->apex, KNOT_RRTYPE_SOA)) {
222 223
		loader->err_handler->fatal_error = true;
		loader->err_handler->cb(loader->err_handler, zc->z, NULL,
224
		                        SEM_ERR_SOA_NONE, NULL);
225
		goto fail;
226
	}
Daniel Salzman's avatar
Daniel Salzman committed
227

228
	ret = zone_contents_adjust_full(zc->z);
229
	if (ret != KNOT_EOK) {
Jan Včelák's avatar
Jan Včelák committed
230
		ERROR(zname, "failed to finalize zone contents (%s)",
231
		      knot_strerror(ret));
232
		goto fail;
233
	}
Daniel Salzman's avatar
Daniel Salzman committed
234

235
	ret = sem_checks_process(zc->z, loader->semantic_checks,
236
	                         loader->err_handler, loader->time);
237

Vitezslav Kriz's avatar
Vitezslav Kriz committed
238 239 240 241
	if (ret != KNOT_EOK) {
		ERROR(zname, "failed to load zone, file '%s' (%s)",
		      loader->source, knot_strerror(ret));
		goto fail;
242
	}
Daniel Salzman's avatar
Daniel Salzman committed
243

244
	return zc->z;
245 246

fail:
247
	zone_contents_deep_free(zc->z);
248
	return NULL;
249 250
}

251
int zonefile_exists(const char *path, time_t *mtime)
252
{
253 254 255 256
	if (path == NULL) {
		return KNOT_EINVAL;
	}

257
	struct stat zonefile_st = { 0 };
258 259
	if (stat(path, &zonefile_st) < 0) {
		return knot_map_errno();
260
	}
261 262 263 264 265 266

	if (mtime != NULL) {
		*mtime = zonefile_st.st_mtime;
	}

	return KNOT_EOK;
267 268
}

269
int zonefile_write(const char *path, zone_contents_t *zone)
270
{
271
	if (!zone || !path) {
272 273 274
		return KNOT_EINVAL;
	}

275 276 277 278 279
	int ret = make_path(path, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP);
	if (ret != KNOT_EOK) {
		return ret;
	}

280 281
	FILE *file = NULL;
	char *tmp_name = NULL;
282
	ret = open_tmp_file(path, &tmp_name, &file, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
283
	if (ret != KNOT_EOK) {
284
		return ret;
285 286
	}

287
	ret = zone_dump_text(zone, file, true);
288
	fclose(file);
289
	if (ret != KNOT_EOK) {
290 291
		unlink(tmp_name);
		free(tmp_name);
292
		return ret;
293 294
	}

295
	/* Swap temporary zonefile and new zonefile. */
296
	ret = rename(tmp_name, path);
297 298
	if (ret != 0) {
		ret = knot_map_errno();
299 300
		unlink(tmp_name);
		free(tmp_name);
301
		return ret;
302 303
	}

304
	free(tmp_name);
305

306 307 308
	return KNOT_EOK;
}

309
void zonefile_close(zloader_t *loader)
310 311 312 313
{
	if (!loader) {
		return;
	}
Daniel Salzman's avatar
Daniel Salzman committed
314

315
	zs_deinit(&loader->scanner);
316
	free(loader->source);
317
	free(loader->creator);
318
}
Jan Včelák's avatar
Jan Včelák committed
319

320 321
void err_handler_logger(sem_handler_t *handler, const zone_contents_t *zone,
                        const zone_node_t *node, sem_error_t error, const char *data)
322 323 324 325
{
	assert(handler != NULL);
	assert(zone != NULL);

326 327 328
	char buff[KNOT_DNAME_TXT_MAXLEN + 1] = "";
	if (node != NULL) {
		(void)knot_dname_to_str(buff, node->owner, sizeof(buff));
329 330
	}

331 332 333 334 335
	log_fmt_zone(handler->fatal_error ? LOG_ERR : LOG_WARNING,
	             LOG_SOURCE_ZONE, zone->apex->owner,
	             "check%s%s, %s%s%s",
	             (node != NULL ? ", node " : ""),
	             (node != NULL ? buff      : ""),
336
	             sem_error_msg(error),
337 338
	             (data != NULL ? " "  : ""),
	             (data != NULL ? data : ""));
339
}
Jan Včelák's avatar
Jan Včelák committed
340 341 342

#undef ERROR
#undef WARNING
343
#undef NOTICE