dbcreate.c revision 1.1.1.1
1/*
2 * dbcreate.c -- routines to create an nsd(8) name 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/stat.h>
13#include <sys/types.h>
14#include <errno.h>
15#include <fcntl.h>
16#include <stdlib.h>
17#include <string.h>
18#include <unistd.h>
19
20#include "namedb.h"
21#include "udb.h"
22#include "udbradtree.h"
23#include "udbzone.h"
24#include "options.h"
25#include "nsd.h"
26
27/* pathname directory separator character */
28#define PATHSEP '/'
29
30/** add an rdata (uncompressed) to the destination */
31static size_t
32add_rdata(rr_type* rr, unsigned i, uint8_t* buf, size_t buflen)
33{
34	switch(rdata_atom_wireformat_type(rr->type, i)) {
35		case RDATA_WF_COMPRESSED_DNAME:
36		case RDATA_WF_UNCOMPRESSED_DNAME:
37		{
38			const dname_type* dname = domain_dname(
39				rdata_atom_domain(rr->rdatas[i]));
40			if(dname->name_size > buflen)
41				return 0;
42			memmove(buf, dname_name(dname), dname->name_size);
43			return dname->name_size;
44		}
45		default:
46			break;
47	}
48	if(rdata_atom_size(rr->rdatas[i]) > buflen)
49		return 0;
50	memmove(buf, rdata_atom_data(rr->rdatas[i]),
51		rdata_atom_size(rr->rdatas[i]));
52	return rdata_atom_size(rr->rdatas[i]);
53}
54
55/* marshal rdata into buffer, must be MAX_RDLENGTH in size */
56size_t
57rr_marshal_rdata(rr_type* rr, uint8_t* rdata, size_t sz)
58{
59	size_t len = 0;
60	unsigned i;
61	assert(rr);
62	for(i=0; i<rr->rdata_count; i++) {
63		len += add_rdata(rr, i, rdata+len, sz-len);
64	}
65	return len;
66}
67
68/** delete an RR */
69void
70udb_del_rr(udb_base* udb, udb_ptr* z, rr_type* rr)
71{
72	/* marshal the rdata (uncompressed) into a buffer */
73	uint8_t rdata[MAX_RDLENGTH];
74	size_t rdatalen = rr_marshal_rdata(rr, rdata, sizeof(rdata));
75	assert(udb);
76	udb_zone_del_rr(udb, z, dname_name(domain_dname(rr->owner)),
77		domain_dname(rr->owner)->name_size, rr->type, rr->klass,
78		rdata, rdatalen);
79}
80
81/** write rr */
82int
83udb_write_rr(udb_base* udb, udb_ptr* z, rr_type* rr)
84{
85	/* marshal the rdata (uncompressed) into a buffer */
86	uint8_t rdata[MAX_RDLENGTH];
87	size_t rdatalen = 0;
88	unsigned i;
89	assert(rr);
90	for(i=0; i<rr->rdata_count; i++) {
91		rdatalen += add_rdata(rr, i, rdata+rdatalen,
92			sizeof(rdata)-rdatalen);
93	}
94	assert(udb);
95	return udb_zone_add_rr(udb, z, dname_name(domain_dname(rr->owner)),
96		domain_dname(rr->owner)->name_size, rr->type, rr->klass,
97		rr->ttl, rdata, rdatalen);
98}
99
100/** write rrset */
101static int
102write_rrset(udb_base* udb, udb_ptr* z, rrset_type* rrset)
103{
104	unsigned i;
105	for(i=0; i<rrset->rr_count; i++) {
106		if(!udb_write_rr(udb, z, &rrset->rrs[i]))
107			return 0;
108	}
109	return 1;
110}
111
112/** write a zone */
113static int
114write_zone(udb_base* udb, udb_ptr* z, zone_type* zone)
115{
116	/* write all domains in the zone */
117	domain_type* walk;
118	rrset_type* rrset;
119	unsigned long n = 0, c = 0;
120	time_t t = time(NULL);
121
122	/* count domains: for pct logging */
123	for(walk=zone->apex; walk && domain_is_subdomain(walk, zone->apex);
124		walk=domain_next(walk)) {
125		n++;
126	}
127	/* write them */
128	for(walk=zone->apex; walk && domain_is_subdomain(walk, zone->apex);
129		walk=domain_next(walk)) {
130		/* write all rrsets (in the zone) for this domain */
131		for(rrset=walk->rrsets; rrset; rrset=rrset->next) {
132			if(rrset->zone == zone) {
133				if(!write_rrset(udb, z, rrset))
134					return 0;
135			}
136		}
137		/* only check every ... domains, and print pct */
138		if(++c % ZONEC_PCT_COUNT == 0 && time(NULL) > t + ZONEC_PCT_TIME) {
139			t = time(NULL);
140			VERBOSITY(1, (LOG_INFO, "write %s %d %%",
141				zone->opts->name, (int)(c*((unsigned long)100)/n)));
142		}
143	}
144	return 1;
145}
146
147/** create and write a zone */
148int
149write_zone_to_udb(udb_base* udb, zone_type* zone, struct timespec* mtime,
150	const char* file_str)
151{
152	udb_ptr z;
153	/* make udb dirty */
154	udb_base_set_userflags(udb, 1);
155	/* find or create zone */
156	if(udb_zone_search(udb, &z, dname_name(domain_dname(zone->apex)),
157		domain_dname(zone->apex)->name_size)) {
158		/* wipe existing contents */
159		udb_zone_clear(udb, &z);
160	} else {
161		if(!udb_zone_create(udb, &z, dname_name(domain_dname(
162			zone->apex)), domain_dname(zone->apex)->name_size)) {
163			udb_base_set_userflags(udb, 0);
164			return 0;
165		}
166	}
167	/* set mtime */
168	ZONE(&z)->mtime = (uint64_t)mtime->tv_sec;
169	ZONE(&z)->mtime_nsec = (uint64_t)mtime->tv_nsec;
170	ZONE(&z)->is_changed = 0;
171	udb_zone_set_log_str(udb, &z, NULL);
172	udb_zone_set_file_str(udb, &z, file_str);
173	/* write zone */
174	if(!write_zone(udb, &z, zone)) {
175		udb_base_set_userflags(udb, 0);
176		return 0;
177	}
178	udb_ptr_unlink(&z, udb);
179	udb_base_set_userflags(udb, 0);
180	return 1;
181}
182
183static int
184print_rrs(FILE* out, struct zone* zone)
185{
186	rrset_type *rrset;
187	domain_type *domain = zone->apex;
188	region_type* region = region_create(xalloc, free);
189	region_type* rr_region = region_create(xalloc, free);
190	buffer_type* rr_buffer = buffer_create(region, MAX_RDLENGTH);
191	struct state_pretty_rr* state = create_pretty_rr(region);
192	/* first print the SOA record for the zone */
193	if(zone->soa_rrset) {
194		size_t i;
195		for(i=0; i < zone->soa_rrset->rr_count; i++) {
196			if(!print_rr(out, state, &zone->soa_rrset->rrs[i],
197				rr_region, rr_buffer)){
198				log_msg(LOG_ERR, "There was an error "
199				   "printing SOARR to zone %s",
200				   zone->opts->name);
201				region_destroy(region);
202				region_destroy(rr_region);
203				return 0;
204			}
205		}
206	}
207	/* go through entire tree below the zone apex (incl subzones) */
208	while(domain && domain_is_subdomain(domain, zone->apex))
209	{
210		for(rrset = domain->rrsets; rrset; rrset=rrset->next)
211		{
212			size_t i;
213			if(rrset->zone != zone || rrset == zone->soa_rrset)
214				continue;
215			for(i=0; i < rrset->rr_count; i++) {
216				if(!print_rr(out, state, &rrset->rrs[i],
217					rr_region, rr_buffer)){
218					log_msg(LOG_ERR, "There was an error "
219					   "printing RR to zone %s",
220					   zone->opts->name);
221					region_destroy(region);
222					region_destroy(rr_region);
223					return 0;
224				}
225			}
226		}
227		domain = domain_next(domain);
228	}
229	region_destroy(region);
230	region_destroy(rr_region);
231	return 1;
232}
233
234static int
235print_header(zone_type* zone, FILE* out, time_t* now, const char* logs)
236{
237	char buf[4096];
238	/* ctime prints newline at end of this line */
239	snprintf(buf, sizeof(buf), "; zone %s written by NSD %s on %s",
240		zone->opts->name, PACKAGE_VERSION, ctime(now));
241	if(!write_data(out, buf, strlen(buf)))
242		return 0;
243	if(!logs || logs[0] == 0) return 1;
244	snprintf(buf, sizeof(buf), "; %s\n", logs);
245	return write_data(out, buf, strlen(buf));
246}
247
248static int
249write_to_zonefile(zone_type* zone, const char* filename, const char* logs)
250{
251	time_t now = time(0);
252	FILE *out = fopen(filename, "w");
253	if(!out) {
254		log_msg(LOG_ERR, "cannot write zone %s file %s: %s",
255			zone->opts->name, filename, strerror(errno));
256		return 0;
257	}
258	if(!print_header(zone, out, &now, logs)) {
259		fclose(out);
260		log_msg(LOG_ERR, "There was an error printing "
261			"the header to zone %s", zone->opts->name);
262		return 0;
263	}
264	if(!print_rrs(out, zone)) {
265		fclose(out);
266		return 0;
267	}
268	if(fclose(out) != 0) {
269		log_msg(LOG_ERR, "cannot write zone %s to file %s: fclose: %s",
270			zone->opts->name, filename, strerror(errno));
271		return 0;
272	}
273	return 1;
274}
275
276/** create directories above this file, .../dir/dir/dir/file */
277int
278create_dirs(const char* path)
279{
280	char dir[4096];
281	char* p;
282	strlcpy(dir, path, sizeof(dir));
283	/* if we start with / then do not try to create '' */
284	if(dir[0] == PATHSEP)
285		p = strchr(dir+1, PATHSEP);
286	else	p = strchr(dir, PATHSEP);
287	/* create each directory component from the left */
288	while(p) {
289		assert(*p == PATHSEP);
290		*p = 0; /* end the directory name here */
291		if(mkdir(dir
292#ifndef MKDIR_HAS_ONE_ARG
293			, 0750
294#endif
295			) == -1) {
296			if(errno != EEXIST) {
297				log_msg(LOG_ERR, "create dir %s: %s",
298					dir, strerror(errno));
299				return 0;
300			}
301			/* it already exists, OK, continue */
302		}
303		*p = PATHSEP;
304		p = strchr(p+1, PATHSEP);
305	}
306	return 1;
307}
308
309/** create pathname components and check if file exists */
310static int
311create_path_components(const char* path, int* notexist)
312{
313	/* stat the file, to see if it exists, and if its directories exist */
314	struct stat s;
315	if(stat(path, &s) != 0) {
316		if(errno == ENOENT) {
317			*notexist = 1;
318			/* see if we need to create pathname components */
319			return create_dirs(path);
320		}
321		log_msg(LOG_ERR, "cannot stat %s: %s", path, strerror(errno));
322		return 0;
323	}
324	*notexist = 0;
325	return 1;
326}
327
328void
329namedb_write_zonefile(struct nsd* nsd, zone_options_t* zopt)
330{
331	const char* zfile;
332	int notexist = 0;
333	zone_type* zone;
334	/* if no zone exists, it has no contents or it has no zonefile
335	 * configured, then no need to write data to disk */
336	if(!zopt->pattern->zonefile)
337		return;
338	zone = namedb_find_zone(nsd->db, (const dname_type*)zopt->node.key);
339	if(!zone || !zone->apex || !zone->soa_rrset)
340		return;
341	/* write if file does not exist, or if changed */
342	/* so, determine filename, create directory components, check exist*/
343	zfile = config_make_zonefile(zopt, nsd);
344	if(!create_path_components(zfile, &notexist)) {
345		log_msg(LOG_ERR, "could not write zone %s to file %s because "
346			"the path could not be created", zopt->name, zfile);
347		return;
348	}
349
350	/* if not changed, do not write. */
351	if(notexist || zone->is_changed) {
352		char logs[4096];
353		char bakfile[4096];
354		struct timespec mtime;
355		udb_ptr zudb;
356		if(nsd->db->udb) {
357			if(!udb_zone_search(nsd->db->udb, &zudb,
358				dname_name(domain_dname(zone->apex)),
359				domain_dname(zone->apex)->name_size))
360				return; /* zone does not exist in db */
361		}
362		/* write to zfile~ first, then rename if that works */
363		snprintf(bakfile, sizeof(bakfile), "%s~", zfile);
364		if(nsd->db->udb && ZONE(&zudb)->log_str.data) {
365			udb_ptr s;
366			udb_ptr_new(&s, nsd->db->udb, &ZONE(&zudb)->log_str);
367			strlcpy(logs, (char*)udb_ptr_data(&s), sizeof(logs));
368			udb_ptr_unlink(&s, nsd->db->udb);
369		} else if(zone->logstr) {
370			strlcpy(logs, zone->logstr, sizeof(logs));
371		} else logs[0] = 0;
372		VERBOSITY(1, (LOG_INFO, "writing zone %s to file %s",
373			zone->opts->name, zfile));
374		if(!write_to_zonefile(zone, bakfile, logs)) {
375			if(nsd->db->udb)
376				udb_ptr_unlink(&zudb, nsd->db->udb);
377			(void)unlink(bakfile); /* delete failed file */
378			return; /* error already printed */
379		}
380		if(rename(bakfile, zfile) == -1) {
381			log_msg(LOG_ERR, "rename(%s to %s) failed: %s",
382				bakfile, zfile, strerror(errno));
383			if(nsd->db->udb)
384				udb_ptr_unlink(&zudb, nsd->db->udb);
385			(void)unlink(bakfile); /* delete failed file */
386			return;
387		}
388		zone->is_changed = 0;
389		/* fetch the mtime of the just created zonefile so we
390		 * do not waste effort reading it back in */
391		if(!file_get_mtime(zfile, &mtime, &notexist)) {
392			get_time(&mtime);
393		}
394		if(nsd->db->udb) {
395			ZONE(&zudb)->mtime = (uint64_t)mtime.tv_sec;
396			ZONE(&zudb)->mtime_nsec = (uint64_t)mtime.tv_nsec;
397			ZONE(&zudb)->is_changed = 0;
398			udb_zone_set_log_str(nsd->db->udb, &zudb, NULL);
399			udb_ptr_unlink(&zudb, nsd->db->udb);
400		} else {
401			zone->mtime = mtime;
402			if(zone->filename)
403				region_recycle(nsd->db->region, zone->filename,
404					strlen(zone->filename)+1);
405			zone->filename = region_strdup(nsd->db->region, zfile);
406			if(zone->logstr)
407				region_recycle(nsd->db->region, zone->logstr,
408					strlen(zone->logstr)+1);
409			zone->logstr = NULL;
410		}
411	}
412}
413
414void
415namedb_write_zonefiles(struct nsd* nsd, nsd_options_t* options)
416{
417	zone_options_t* zo;
418	RBTREE_FOR(zo, zone_options_t*, options->zone_options) {
419		namedb_write_zonefile(nsd, zo);
420	}
421}
422