1/*
2 * dbaccess.c -- access methods for nsd(8) database
3 *
4 * Copyright (c) 2001-2006, NLnet Labs. All rights reserved.
5 *
6 * See LICENSE for the license.
7 *
8 */
9
10#include "config.h"
11
12#include <sys/types.h>
13#include <sys/stat.h>
14
15#include <errno.h>
16#include <stdlib.h>
17#include <string.h>
18#include <unistd.h>
19#include <fcntl.h>
20
21#include "dns.h"
22#include "namedb.h"
23#include "util.h"
24#include "options.h"
25#include "rdata.h"
26#include "udb.h"
27#include "zonec.h"
28#include "nsec3.h"
29#include "difffile.h"
30#include "nsd.h"
31#include "ixfr.h"
32#include "ixfrcreate.h"
33
34void
35namedb_close(struct namedb* db)
36{
37	if(db) {
38		zonec_desetup_parser();
39		region_destroy(db->region);
40	}
41}
42
43void
44namedb_free_ixfr(struct namedb* db)
45{
46	struct radnode* n;
47	for(n=radix_first(db->zonetree); n; n=radix_next(n)) {
48		zone_ixfr_free(((zone_type*)n->elem)->ixfr);
49	}
50}
51
52/** create a zone */
53zone_type*
54namedb_zone_create(namedb_type* db, const dname_type* dname,
55	struct zone_options* zo)
56{
57	zone_type* zone = (zone_type *) region_alloc(db->region,
58		sizeof(zone_type));
59	zone->node = radname_insert(db->zonetree, dname_name(dname),
60		dname->name_size, zone);
61	assert(zone->node);
62	zone->apex = domain_table_insert(db->domains, dname);
63	zone->apex->usage++; /* the zone.apex reference */
64	zone->apex->is_apex = 1;
65	zone->soa_rrset = NULL;
66	zone->soa_nx_rrset = NULL;
67	zone->ns_rrset = NULL;
68#ifdef NSEC3
69	zone->nsec3_param = NULL;
70	zone->nsec3_last = NULL;
71	zone->nsec3tree = NULL;
72	zone->hashtree = NULL;
73	zone->wchashtree = NULL;
74	zone->dshashtree = NULL;
75#endif
76	zone->opts = zo;
77	zone->ixfr = NULL;
78	zone->filename = NULL;
79	zone->logstr = NULL;
80	zone->mtime.tv_sec = 0;
81	zone->mtime.tv_nsec = 0;
82	zone->zonestatid = 0;
83	zone->is_secure = 0;
84	zone->is_changed = 0;
85	zone->is_updated = 0;
86	zone->is_skipped = 0;
87	zone->is_checked = 0;
88	zone->is_bad = 0;
89	zone->is_ok = 1;
90	return zone;
91}
92
93void
94namedb_zone_delete(namedb_type* db, zone_type* zone)
95{
96	/* RRs and UDB and NSEC3 and so on must be already deleted */
97	radix_delete(db->zonetree, zone->node);
98
99	/* see if apex can be deleted */
100	if(zone->apex) {
101		zone->apex->usage --;
102		zone->apex->is_apex = 0;
103		if(zone->apex->usage == 0) {
104			/* delete the apex, possibly */
105			domain_table_deldomain(db, zone->apex);
106		}
107	}
108
109	/* soa_rrset is freed when the SOA was deleted */
110	if(zone->soa_nx_rrset) {
111		region_recycle(db->region, zone->soa_nx_rrset->rrs,
112			sizeof(rr_type));
113		region_recycle(db->region, zone->soa_nx_rrset,
114			sizeof(rrset_type));
115	}
116#ifdef NSEC3
117	hash_tree_delete(db->region, zone->nsec3tree);
118	hash_tree_delete(db->region, zone->hashtree);
119	hash_tree_delete(db->region, zone->wchashtree);
120	hash_tree_delete(db->region, zone->dshashtree);
121#endif
122	zone_ixfr_free(zone->ixfr);
123	if(zone->filename)
124		region_recycle(db->region, zone->filename,
125			strlen(zone->filename)+1);
126	if(zone->logstr)
127		region_recycle(db->region, zone->logstr,
128			strlen(zone->logstr)+1);
129	region_recycle(db->region, zone, sizeof(zone_type));
130}
131
132struct namedb *
133namedb_open (struct nsd_options* opt)
134{
135	namedb_type* db;
136
137	/*
138	 * Region used to store the loaded database.  The region is
139	 * freed in namedb_close.
140	 */
141	region_type* db_region;
142
143	(void)opt;
144
145#ifdef USE_MMAP_ALLOC
146	db_region = region_create_custom(mmap_alloc, mmap_free, MMAP_ALLOC_CHUNK_SIZE,
147		MMAP_ALLOC_LARGE_OBJECT_SIZE, MMAP_ALLOC_INITIAL_CLEANUP_SIZE, 1);
148#else /* !USE_MMAP_ALLOC */
149	db_region = region_create_custom(xalloc, free, DEFAULT_CHUNK_SIZE,
150		DEFAULT_LARGE_OBJECT_SIZE, DEFAULT_INITIAL_CLEANUP_SIZE, 1);
151#endif /* !USE_MMAP_ALLOC */
152	db = (namedb_type *) region_alloc(db_region, sizeof(struct namedb));
153	db->region = db_region;
154	db->domains = domain_table_create(db->region);
155	db->zonetree = radix_tree_create(db->region);
156	db->diff_skip = 0;
157	db->diff_pos = 0;
158	zonec_setup_parser(db);
159
160	if (gettimeofday(&(db->diff_timestamp), NULL) != 0) {
161		log_msg(LOG_ERR, "unable to load namedb: cannot initialize timestamp");
162		region_destroy(db_region);
163		return NULL;
164	}
165
166	return db;
167}
168
169/** get the file mtime stat (or nonexist or error) */
170int
171file_get_mtime(const char* file, struct timespec* mtime, int* nonexist)
172{
173	struct stat s;
174	if(stat(file, &s) != 0) {
175		mtime->tv_sec = 0;
176		mtime->tv_nsec = 0;
177		*nonexist = (errno == ENOENT);
178		return 0;
179	}
180	*nonexist = 0;
181	mtime->tv_sec = s.st_mtime;
182#ifdef HAVE_STRUCT_STAT_ST_MTIMENSEC
183	mtime->tv_nsec = s.st_mtimensec;
184#elif defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
185	mtime->tv_nsec = s.st_mtim.tv_nsec;
186#else
187	mtime->tv_nsec = 0;
188#endif
189	return 1;
190}
191
192void
193namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb,
194	udb_ptr* last_task)
195{
196	struct timespec mtime;
197	int nonexist = 0;
198	unsigned int errors;
199	const char* fname;
200	struct ixfr_create* ixfrcr = NULL;
201	int ixfr_create_already_done = 0;
202	if(!nsd->db || !zone || !zone->opts || !zone->opts->pattern->zonefile)
203		return;
204	mtime.tv_sec = 0;
205	mtime.tv_nsec = 0;
206	fname = config_make_zonefile(zone->opts, nsd);
207	assert(fname);
208	if(!file_get_mtime(fname, &mtime, &nonexist)) {
209		if(nonexist) {
210			if(zone_is_slave(zone->opts)) {
211				/* for slave zones not as bad, no zonefile
212				 * may just mean we have to transfer it */
213				VERBOSITY(2, (LOG_INFO, "zonefile %s does not exist",
214					fname));
215			} else {
216				/* without a download option, we can never
217				 * serve data, more severe error printout */
218				log_msg(LOG_ERR, "zonefile %s does not exist", fname);
219			}
220
221		} else
222			log_msg(LOG_ERR, "zonefile %s: %s",
223				fname, strerror(errno));
224		if(taskudb) task_new_soainfo(taskudb, last_task, zone, 0);
225		return;
226	} else {
227		const char* zone_fname = zone->filename;
228		struct timespec zone_mtime = zone->mtime;
229		/* if no zone_fname, then it was acquired in zone transfer,
230		 * see if the file is newer than the zone transfer
231		 * (regardless if this is a different file), because the
232		 * zone transfer is a different content source too */
233		if(!zone_fname && timespec_compare(&zone_mtime, &mtime) >= 0) {
234			VERBOSITY(3, (LOG_INFO, "zonefile %s is older than "
235				"zone transfer in memory", fname));
236			return;
237
238		/* if zone_fname, then the file was acquired from reading it,
239		 * and see if filename changed or mtime newer to read it */
240		} else if(zone_fname && strcmp(zone_fname, fname) == 0 &&
241		   timespec_compare(&zone_mtime, &mtime) == 0) {
242			VERBOSITY(3, (LOG_INFO, "zonefile %s is not modified",
243				fname));
244			return;
245		}
246	}
247	if(ixfr_create_from_difference(zone, fname,
248		&ixfr_create_already_done)) {
249		ixfrcr = ixfr_create_start(zone, fname,
250			zone->opts->pattern->ixfr_size, 0);
251		if(!ixfrcr) {
252			/* leaves the ixfrcr at NULL, so it is not created */
253			log_msg(LOG_ERR, "out of memory starting ixfr create");
254		}
255	}
256
257	assert(parser);
258	/* wipe zone from memory */
259#ifdef NSEC3
260	nsec3_clear_precompile(nsd->db, zone);
261	zone->nsec3_param = NULL;
262#endif
263	delete_zone_rrs(nsd->db, zone);
264	errors = zonec_read(zone->opts->name, fname, zone);
265	if(errors > 0) {
266		log_msg(LOG_ERR, "zone %s file %s read with %u errors",
267			zone->opts->name, fname, errors);
268		/* wipe (partial) zone from memory */
269		zone->is_ok = 1;
270#ifdef NSEC3
271		nsec3_clear_precompile(nsd->db, zone);
272		zone->nsec3_param = NULL;
273#endif
274		delete_zone_rrs(nsd->db, zone);
275		if(zone->filename)
276			region_recycle(nsd->db->region, zone->filename,
277				strlen(zone->filename)+1);
278		zone->filename = NULL;
279		if(zone->logstr)
280			region_recycle(nsd->db->region, zone->logstr,
281				strlen(zone->logstr)+1);
282		zone->logstr = NULL;
283	} else {
284		VERBOSITY(1, (LOG_INFO, "zone %s read with success",
285			zone->opts->name));
286		zone->is_ok = 1;
287		zone->is_changed = 0;
288		/* store zone into udb */
289		zone->mtime = mtime;
290		if(zone->filename)
291			region_recycle(nsd->db->region, zone->filename,
292				strlen(zone->filename)+1);
293		zone->filename = region_strdup(nsd->db->region, fname);
294		if(zone->logstr)
295			region_recycle(nsd->db->region, zone->logstr,
296				strlen(zone->logstr)+1);
297		zone->logstr = NULL;
298		if(ixfr_create_already_done) {
299			ixfr_readup_exist(zone, nsd, fname);
300		} else if(ixfrcr) {
301			if(!ixfr_create_perform(ixfrcr, zone, 1, nsd, fname,
302				zone->opts->pattern->ixfr_number)) {
303				log_msg(LOG_ERR, "failed to create IXFR");
304			} else {
305				VERBOSITY(2, (LOG_INFO, "zone %s created IXFR %s.ixfr",
306					zone->opts->name, fname));
307			}
308			ixfr_create_free(ixfrcr);
309		} else if(zone_is_ixfr_enabled(zone)) {
310			ixfr_read_from_file(nsd, zone, fname);
311		}
312	}
313	if(taskudb) task_new_soainfo(taskudb, last_task, zone, 0);
314#ifdef NSEC3
315	prehash_zone_complete(nsd->db, zone);
316#endif
317}
318
319void namedb_check_zonefile(struct nsd* nsd, udb_base* taskudb,
320	udb_ptr* last_task, struct zone_options* zopt)
321{
322	zone_type* zone;
323	const dname_type* dname = (const dname_type*)zopt->node.key;
324	/* find zone to go with it, or create it */
325	zone = namedb_find_zone(nsd->db, dname);
326	if(!zone) {
327		zone = namedb_zone_create(nsd->db, dname, zopt);
328	}
329	namedb_read_zonefile(nsd, zone, taskudb, last_task);
330}
331
332void namedb_check_zonefiles(struct nsd* nsd, struct nsd_options* opt,
333	udb_base* taskudb, udb_ptr* last_task)
334{
335	struct zone_options* zo;
336	/* check all zones in opt, create if not exist in main db */
337	RBTREE_FOR(zo, struct zone_options*, opt->zone_options) {
338		namedb_check_zonefile(nsd, taskudb, last_task, zo);
339		if(nsd->signal_hint_shutdown) break;
340	}
341}
342