xref: /openbsd-src/usr.sbin/nsd/dbcreate.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
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 */
31 static size_t
32 add_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 */
56 size_t
57 rr_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 */
69 void
70 udb_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 */
82 int
83 udb_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 */
101 static int
102 write_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 */
113 static int
114 write_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 	int 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, c*100/n));
142 		}
143 	}
144 	return 1;
145 }
146 
147 /** create and write a zone */
148 int
149 write_zone_to_udb(udb_base* udb, zone_type* zone, time_t 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;
169 	ZONE(&z)->is_changed = 0;
170 	udb_zone_set_log_str(udb, &z, NULL);
171 	udb_zone_set_file_str(udb, &z, file_str);
172 	/* write zone */
173 	if(!write_zone(udb, &z, zone)) {
174 		udb_base_set_userflags(udb, 0);
175 		return 0;
176 	}
177 	udb_ptr_unlink(&z, udb);
178 	udb_base_set_userflags(udb, 0);
179 	return 1;
180 }
181 
182 static int
183 print_rrs(FILE* out, struct zone* zone)
184 {
185 	rrset_type *rrset;
186 	domain_type *domain = zone->apex;
187 	region_type* region = region_create(xalloc, free);
188 	struct state_pretty_rr* state = create_pretty_rr(region);
189 	/* first print the SOA record for the zone */
190 	if(zone->soa_rrset) {
191 		size_t i;
192 		for(i=0; i < zone->soa_rrset->rr_count; i++) {
193 			if(!print_rr(out, state, &zone->soa_rrset->rrs[i])){
194 				log_msg(LOG_ERR, "There was an error "
195 				   "printing SOARR to zone %s",
196 				   zone->opts->name);
197 				region_destroy(region);
198 				return 0;
199 			}
200 		}
201 	}
202 	/* go through entire tree below the zone apex (incl subzones) */
203 	while(domain && domain_is_subdomain(domain, zone->apex))
204 	{
205 		for(rrset = domain->rrsets; rrset; rrset=rrset->next)
206 		{
207 			size_t i;
208 			if(rrset->zone != zone || rrset == zone->soa_rrset)
209 				continue;
210 			for(i=0; i < rrset->rr_count; i++) {
211 				if(!print_rr(out, state, &rrset->rrs[i])){
212 					log_msg(LOG_ERR, "There was an error "
213 					   "printing RR to zone %s",
214 					   zone->opts->name);
215 					region_destroy(region);
216 					return 0;
217 				}
218 			}
219 		}
220 		domain = domain_next(domain);
221 	}
222 	region_destroy(region);
223 	return 1;
224 }
225 
226 static int
227 print_header(zone_type* zone, FILE* out, time_t* now, const char* logs)
228 {
229 	char buf[4096];
230 	/* ctime prints newline at end of this line */
231 	snprintf(buf, sizeof(buf), "; zone %s written by NSD %s on %s",
232 		zone->opts->name, PACKAGE_VERSION, ctime(now));
233 	if(!write_data(out, buf, strlen(buf)))
234 		return 0;
235 	if(!logs || logs[0] == 0) return 1;
236 	snprintf(buf, sizeof(buf), "; %s\n", logs);
237 	return write_data(out, buf, strlen(buf));
238 }
239 
240 static int
241 write_to_zonefile(zone_type* zone, const char* filename, const char* logs)
242 {
243 	time_t now = time(0);
244 	FILE *out;
245 	VERBOSITY(1, (LOG_INFO, "writing zone %s to file %s",
246 		zone->opts->name, filename));
247 
248 	out = fopen(filename, "w");
249 	if(!out) {
250 		log_msg(LOG_ERR, "cannot write zone %s file %s: %s",
251 			zone->opts->name, filename, strerror(errno));
252 		return 0;
253 	}
254 	if(!print_header(zone, out, &now, logs)) {
255 		fclose(out);
256 		log_msg(LOG_ERR, "There was an error printing "
257 			"the header to zone %s", zone->opts->name);
258 		return 0;
259 	}
260 	if(!print_rrs(out, zone)) {
261 		fclose(out);
262 		return 0;
263 	}
264 	fclose(out);
265 	return 1;
266 }
267 
268 /** create directories above this file, .../dir/dir/dir/file */
269 int
270 create_dirs(const char* path)
271 {
272 	char dir[4096];
273 	char* p;
274 	strlcpy(dir, path, sizeof(dir));
275 	/* if we start with / then do not try to create '' */
276 	if(dir[0] == PATHSEP)
277 		p = strchr(dir+1, PATHSEP);
278 	else	p = strchr(dir, PATHSEP);
279 	/* create each directory component from the left */
280 	while(p) {
281 		assert(*p == PATHSEP);
282 		*p = 0; /* end the directory name here */
283 		if(mkdir(dir
284 #ifndef MKDIR_HAS_ONE_ARG
285 			, 0750
286 #endif
287 			) == -1) {
288 			if(errno != EEXIST) {
289 				log_msg(LOG_ERR, "create dir %s: %s",
290 					dir, strerror(errno));
291 				return 0;
292 			}
293 			/* it already exists, OK, continue */
294 		}
295 		*p = PATHSEP;
296 		p = strchr(p+1, PATHSEP);
297 	}
298 	return 1;
299 }
300 
301 /** create pathname components and check if file exists */
302 static int
303 create_path_components(const char* path, int* notexist)
304 {
305 	/* stat the file, to see if it exists, and if its directories exist */
306 	struct stat s;
307 	if(stat(path, &s) != 0) {
308 		if(errno == ENOENT) {
309 			*notexist = 1;
310 			/* see if we need to create pathname components */
311 			return create_dirs(path);
312 		}
313 		log_msg(LOG_ERR, "cannot stat %s: %s", path, strerror(errno));
314 		return 0;
315 	}
316 	*notexist = 0;
317 	return 1;
318 }
319 
320 void
321 namedb_write_zonefile(struct nsd* nsd, zone_options_t* zopt)
322 {
323 	const char* zfile;
324 	int notexist = 0;
325 	zone_type* zone;
326 	/* if no zone exists, it has no contents or it has no zonefile
327 	 * configured, then no need to write data to disk */
328 	if(!zopt->pattern->zonefile)
329 		return;
330 	zone = namedb_find_zone(nsd->db, (const dname_type*)zopt->node.key);
331 	if(!zone || !zone->apex)
332 		return;
333 	/* write if file does not exist, or if changed */
334 	/* so, determine filename, create directory components, check exist*/
335 	zfile = config_make_zonefile(zopt, nsd);
336 	if(!create_path_components(zfile, &notexist)) {
337 		log_msg(LOG_ERR, "could not write zone %s to file %s because "
338 			"the path could not be created", zopt->name, zfile);
339 		return;
340 	}
341 
342 	/* if not changed, do not write. */
343 	if(notexist || zone->is_changed) {
344 		char logs[4096];
345 		char bakfile[4096];
346 		udb_ptr zudb;
347 		if(!udb_zone_search(nsd->db->udb, &zudb,
348 			dname_name(domain_dname(zone->apex)),
349 			domain_dname(zone->apex)->name_size))
350 			return; /* zone does not exist in db */
351 		/* write to zfile~ first, then rename if that works */
352 		snprintf(bakfile, sizeof(bakfile), "%s~", zfile);
353 		if(ZONE(&zudb)->log_str.data) {
354 			udb_ptr s;
355 			udb_ptr_new(&s, nsd->db->udb, &ZONE(&zudb)->log_str);
356 			strlcpy(logs, (char*)udb_ptr_data(&s), sizeof(logs));
357 			udb_ptr_unlink(&s, nsd->db->udb);
358 		} else logs[0] = 0;
359 		if(!write_to_zonefile(zone, bakfile, logs)) {
360 			udb_ptr_unlink(&zudb, nsd->db->udb);
361 			return; /* error already printed */
362 		}
363 		if(rename(bakfile, zfile) == -1) {
364 			log_msg(LOG_ERR, "rename(%s to %s) failed: %s",
365 				bakfile, zfile, strerror(errno));
366 			udb_ptr_unlink(&zudb, nsd->db->udb);
367 			return;
368 		}
369 		zone->is_changed = 0;
370 		ZONE(&zudb)->mtime = (uint64_t)time(0);
371 		ZONE(&zudb)->is_changed = 0;
372 		udb_zone_set_log_str(nsd->db->udb, &zudb, NULL);
373 		udb_ptr_unlink(&zudb, nsd->db->udb);
374 	}
375 }
376 
377 void
378 namedb_write_zonefiles(struct nsd* nsd, nsd_options_t* options)
379 {
380 	zone_options_t* zo;
381 	RBTREE_FOR(zo, zone_options_t*, options->zone_options) {
382 		namedb_write_zonefile(nsd, zo);
383 	}
384 }
385