1 /*****************************************************************
2 **
3 **	@(#) zkt-signer.c  (c) Jan 2005 - Jan 2010  Holger Zuleger hznet.de
4 **
5 **	A wrapper around the BIND dnssec-signzone command which is able
6 **	to resign a zone if necessary and doing a zone or key signing key rollover.
7 **
8 **	Copyright (c) 2005 - 2010, Holger Zuleger HZnet. All rights reserved.
9 **	This software is open source.
10 **
11 **	Redistribution and use in source and binary forms, with or without
12 **	modification, are permitted provided that the following conditions
13 **	are met:
14 **
15 **	Redistributions of source code must retain the above copyright notice,
16 **	this list of conditions and the following disclaimer.
17 **
18 **	Redistributions in binary form must reproduce the above copyright notice,
19 **	this list of conditions and the following disclaimer in the documentation
20 **	and/or other materials provided with the distribution.
21 **
22 **	Neither the name of Holger Zuleger HZnet nor the names of its contributors may
23 **	be used to endorse or promote products derived from this software without
24 **	specific prior written permission.
25 **
26 **	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 **	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 **	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 **	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
30 **	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 **	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 **	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 **	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 **	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 **	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 **	POSSIBILITY OF SUCH DAMAGE.
37 **
38 *****************************************************************/
39 
40 # include <stdio.h>
41 # include <string.h>
42 # include <stdlib.h>
43 # include <assert.h>
44 # include <dirent.h>
45 # include <errno.h>
46 # include <unistd.h>
47 # include <ctype.h>
48 
49 #ifdef HAVE_CONFIG_H
50 # include <config.h>
51 #endif
52 # include "config_zkt.h"
53 #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
54 # include <getopt.h>
55 #endif
56 # include "zconf.h"
57 # include "debug.h"
58 # include "misc.h"
59 # include "ncparse.h"
60 # include "nscomm.h"
61 # include "soaserial.h"
62 # include "zone.h"
63 # include "dki.h"
64 # include "rollover.h"
65 # include "log.h"
66 
67 # define	short_options	"c:L:V:D:N:o:O:dfHhnrv"
68 #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
69 static struct option long_options[] = {
70 	{"reload",		no_argument, NULL, 'r'},
71 	{"force",		no_argument, NULL, 'f'},
72 	{"noexec",		no_argument, NULL, 'n'},
73 	{"verbose",		no_argument, NULL, 'v'},
74 	{"directory",		no_argument, NULL, 'd'},
75 	{"config",		required_argument, NULL, 'c'},
76 	{"option",		required_argument, NULL, 'O'},
77 	{"config-option",	required_argument, NULL, 'O'},
78 	{"logfile",		required_argument, NULL, 'L' },
79 	{"view",		required_argument, NULL, 'V' },
80 	{"directory",		required_argument, NULL, 'D'},
81 	{"named-conf",		required_argument, NULL, 'N'},
82 	{"origin",		required_argument, NULL, 'o'},
83 	{"dynamic",		no_argument, NULL, 'd' },
84 	{"help",		no_argument, NULL, 'h'},
85 	{0, 0, 0, 0}
86 };
87 #endif
88 
89 
90 /**	function declaration	**/
91 static	void	usage (char *mesg, zconf_t *conf);
92 static	int	add2zonelist (const char *dir, const char *view, const char *zone, const char *file);
93 static	int	parsedir (const char *dir, zone_t **zp, const zconf_t *conf);
94 static	int	dosigning (zone_t *zonelist, zone_t *zp);
95 static	int	check_keydb_timestamp (dki_t *keylist, time_t reftime);
96 static	int	new_keysetfiles (const char *dir, time_t zone_signing_time);
97 static	int	writekeyfile (const char *fname, const dki_t *list, int key_ttl);
98 static	int	sign_zone (const zone_t *zp);
99 static	void	register_key (dki_t *listp, const zconf_t *z);
100 static	void	copy_keyset (const char *dir, const char *domain, const zconf_t *conf);
101 
102 /**	global command line options	**/
103 extern  int	optopt;
104 extern  int	opterr;
105 extern  int	optind;
106 extern  char	*optarg;
107 const	char	*progname;
108 static	const	char	*viewname = NULL;
109 static	const	char	*logfile = NULL;
110 static	const	char	*origin = NULL;
111 static	const	char	*namedconf = NULL;
112 static	const	char	*dirname = NULL;
113 static	int	verbose = 0;
114 static	int	force = 0;
115 static	int	reloadflag = 0;
116 static	int	noexec = 0;
117 static	int	dynamic_zone = 0;	/* dynamic zone ? */
118 static	zone_t	*zonelist = NULL;	/* must be static global because add2zonelist use it */
119 static	zconf_t	*config;
120 
121 /**	macros **/
122 #define	set_bind96_dynzone(dz)	((dz) = 6)
123 #define	bind96_dynzone(dz)	( (dz) >= 6 )
124 #define	is_defined(str)		( (str) && *(str) )
125 
main(int argc,char * const argv[])126 int	main (int argc, char *const argv[])
127 {
128 	int	c;
129 	int	errcnt;
130 #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
131 	int	opt_index;
132 #endif
133 	char	errstr[255+1];
134 	char	*p;
135 	const	char	*defconfname;
136 	zone_t	*zp;
137 
138 	progname = *argv;
139 	if ( (p = strrchr (progname, '/')) )
140 		progname = ++p;
141 
142 	if ( strncmp (progname, "dnssec-signer", 13) == 0 )
143 	{
144 		fprintf (stderr, "The use of dnssec-signer is deprecated, please run zkt-signer instead\n");
145 		viewname = getnameappendix (progname, "dnssec-signer");
146 	}
147 	else
148 		viewname = getnameappendix (progname, "zkt-signer");
149 	defconfname = getdefconfname (viewname);
150 	config = loadconfig ("", (zconf_t *)NULL);	/* load build-in config */
151 	if ( fileexist (defconfname) )			/* load default config file */
152 		config = loadconfig (defconfname, config);
153 	if ( config == NULL )
154 		fatal ("Couldn't load config: Out of memory\n");
155 
156 	zonelist = NULL;
157         opterr = 0;
158 #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
159 	while ( (c = getopt_long (argc, argv, short_options, long_options, &opt_index)) != -1 )
160 #else
161 	while ( (c = getopt (argc, argv, short_options)) != -1 )
162 #endif
163 	{
164 		switch ( c )
165 		{
166 		case 'V':		/* view name */
167 			viewname = optarg;
168 			defconfname = getdefconfname (viewname);
169 			if ( fileexist (defconfname) )		/* load default config file */
170 				config = loadconfig (defconfname, config);
171 			if ( config == NULL )
172 				fatal ("Out of memory\n");
173 			break;
174 		case 'c':		/* load config from file */
175 			config = loadconfig (optarg, config);
176 			if ( config == NULL )
177 				fatal ("Out of memory\n");
178 			break;
179 		case 'O':		/* load config option from commandline */
180 			config = loadconfig_fromstr (optarg, config);
181 			if ( config == NULL )
182 				fatal ("Out of memory\n");
183 			break;
184 		case 'o':
185 			origin = optarg;
186 			break;
187 		case 'N':
188 			namedconf = optarg;
189 			break;
190 		case 'D':
191 			dirname = optarg;
192 			break;
193 		case 'L':		/* error log file|directory */
194 			logfile = optarg;
195 			break;
196 		case 'f':
197 			force++;
198 			break;
199 		case 'H':
200 		case 'h':
201 			usage (NULL, config);
202 			break;
203 		case 'd':
204 			dynamic_zone = 1;
205 			/* dynamic zone requires a name server reload... */
206 			reloadflag = 0;		/* ...but "rndc thaw" reloads the zone anyway */
207 			break;
208 		case 'n':
209 			noexec = 1;
210 			break;
211 		case 'r':
212 			if ( !dynamic_zone )	/* dynamic zones don't need a rndc reload (see "-d" */
213 				reloadflag = 1;
214 			break;
215 		case 'v':
216 			verbose++;
217 			break;
218 		case '?':
219 			if ( isprint (optopt) )
220 				snprintf (errstr, sizeof(errstr),
221 					"Unknown option \"-%c\".\n", optopt);
222 			else
223 				snprintf (errstr, sizeof (errstr),
224 					"Unknown option char \\x%x.\n", optopt);
225 			usage (errstr, config);
226 			break;
227 		default:
228 			abort();
229 		}
230 	}
231 	dbg_line();
232 
233 	/* store some of the commandline parameter in the config structure */
234 	setconfigpar (config, "--view", viewname);
235 	setconfigpar (config, "-v", &verbose);
236 	setconfigpar (config, "--noexec", &noexec);
237 	if ( logfile == NULL )
238 		logfile = config->logfile;
239 
240 	if ( lg_open (progname, config->syslogfacility, config->sysloglevel, config->zonedir, logfile, config->loglevel) < -1 )
241 		fatal ("Couldn't open logfile %s in dir %s\n", logfile, config->zonedir);
242 
243 	lg_args (LG_NOTICE, argc, argv);
244 
245 	/* 1.0rc1: If the ttl is 0 or not known because of dynamic zone signing, ... */
246 	/* ... use sig valid time for this */
247 	if ( config->max_ttl <= 0 || dynamic_zone )
248 	{
249 		// config = dupconfig (config);
250 		config->max_ttl = config->sigvalidity;
251 	}
252 
253 
254 	if ( origin )		/* option -o ? */
255 	{
256 		int	ret;
257 
258 		if ( (argc - optind) <= 0 )	/* no arguments left ? */
259 			ret = zone_readdir (".", origin, NULL, &zonelist, config, dynamic_zone);
260 		else
261 			ret = zone_readdir (".", origin, argv[optind], &zonelist, config, dynamic_zone);
262 
263 		/* anyway, "delete" all (remaining) arguments */
264 		optind = argc;
265 
266 		/* complain if nothing could read in */
267 		if ( ret != 1 || zonelist == NULL )
268 		{
269 			lg_mesg (LG_FATAL, "\"%s\": couldn't read", origin);
270 			fatal ("Couldn't read zone \"%s\"\n", origin);
271 		}
272 	}
273 	if ( namedconf )	/* option -N ? */
274 	{
275 		char	dir[255+1];
276 
277 		memset (dir, '\0', sizeof (dir));
278 		if ( config->zonedir )
279 			strncpy (dir, config->zonedir, sizeof(dir));
280 		if ( !parse_namedconf (namedconf, config->chroot_dir, dir, sizeof (dir), add2zonelist) )
281 			fatal ("Can't read file %s as namedconf file\n", namedconf);
282 		if ( zonelist == NULL )
283 			fatal ("No signed zone found in file %s\n", namedconf);
284 	}
285 	if ( dirname )		/* option -D ? */
286 	{
287 		char	*dir = strdup (dirname);
288 
289 		p = dir + strlen (dir);
290 		if ( p > dir )
291 			p--;
292 		if ( *p == '/' )
293 			*p = '\0';	/* remove trailing path seperator */
294 
295 		if ( !parsedir (dir, &zonelist, config) )
296 			fatal ("Can't read directory tree %s\n", dir);
297 		if ( zonelist == NULL )
298 			fatal ("No signed zone found in directory tree %s\n", dir);
299 		free (dir);
300 	}
301 
302 	/* none of the above: read default directory tree */
303 	if ( zonelist == NULL )
304 		parsedir (config->zonedir, &zonelist, config);
305 
306 #if defined(DBG) && DBG
307 	for ( zp = zonelist; zp; zp = zp->next )
308 		zone_print ("in main: ", zp);
309 #endif
310 	for ( zp = zonelist; zp; zp = zp->next )
311 		if ( in_strarr (zp->zone, &argv[optind], argc - optind) )
312 		{
313 			dosigning (zonelist, zp);
314 			verbmesg (1, zp->conf, "\n");
315 		}
316 
317 	zone_freelist (&zonelist);
318 
319 	errcnt = lg_geterrcnt ();
320 	lg_mesg (LG_NOTICE, "end of run: %d error%s occured", errcnt, errcnt == 1 ? "" : "s");
321 	lg_close ();
322 
323 	return errcnt < 64 ? errcnt : 64;
324 }
325 
326 # define	sopt_usage(mesg, value) fprintf (stderr, mesg, value)
327 #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
328 # define	lopt_usage(mesg, value) fprintf (stderr, mesg, value)
329 # define	loptstr(lstr, sstr)     lstr
330 #else
331 # define	lopt_usage(mesg, value)
332 # define	loptstr(lstr, sstr)     sstr
333 #endif
usage(char * mesg,zconf_t * conf)334 static	void	usage (char *mesg, zconf_t *conf)
335 {
336 	fprintf (stderr, "%s version %s compiled for BIND %d\n", progname, ZKT_VERSION, BIND_VERSION);
337 	fprintf (stderr, "ZKT %s\n", ZKT_COPYRIGHT);
338 	fprintf (stderr, "\n");
339 
340 	fprintf (stderr, "usage: %s [-L] [-V view] [-c file] [-O optstr] ", progname);
341 	fprintf (stderr, "[-D directorytree] ");
342 	fprintf (stderr, "[-fhnr] [-v [-v]] [zone ...]\n");
343 
344 	fprintf (stderr, "usage: %s [-L] [-V view] [-c file] [-O optstr] ", progname);
345 	fprintf (stderr, "-N named.conf ");
346 	fprintf (stderr, "[-fhnr] [-v [-v]] [zone ...]\n");
347 
348 	fprintf (stderr, "usage: %s [-L] [-V view] [-c file] [-O optstr] ", progname);
349 	fprintf (stderr, "-o origin ");
350 	fprintf (stderr, "[-fhnr] [-v [-v]] [zonefile.signed]\n");
351 
352 	fprintf (stderr, "\t-c file%s", loptstr (", --config=file\n", ""));
353 	fprintf (stderr, "\t\t read config from <file> instead of %s\n", CONFIG_FILE);
354 	fprintf (stderr, "\t-O optstr%s", loptstr (", --config-option=\"optstr\"\n", ""));
355 	fprintf (stderr, "\t\t set config options on the commandline\n");
356 	fprintf (stderr, "\t-L file|dir%s", loptstr (", --logfile=file|dir\n", ""));
357 	fprintf (stderr, "\t\t specify file or directory for the log output\n");
358 	fprintf (stderr, "\t-V name%s", loptstr (", --view=name\n", ""));
359 	fprintf (stderr, "\t\t specify the view name \n");
360 	fprintf (stderr, "\t-D dir%s", loptstr (", --directory=dir\n", ""));
361 	fprintf (stderr, "\t\t parse the given directory tree for a list of secure zones \n");
362 	fprintf (stderr, "\t-N file%s", loptstr (", --named-conf=file\n", ""));
363 	fprintf (stderr, "\t\t get the list of secure zones out of the named like config file \n");
364 	fprintf (stderr, "\t-o zone%s", loptstr (", --origin=zone", ""));
365 	fprintf (stderr, "\tspecify the name of the zone \n");
366 	fprintf (stderr, "\t\t The file to sign should be given as an argument (default is \"%s.signed\")\n", conf->zonefile);
367 	fprintf (stderr, "\t-h%s\t print this help\n", loptstr (", --help", "\t"));
368 	fprintf (stderr, "\t-f%s\t force re-signing\n", loptstr (", --force", "\t"));
369 	fprintf (stderr, "\t-n%s\t no execution of external signing command\n", loptstr (", --noexec", "\t"));
370 	// fprintf (stderr, "\t-r%s\t reload zone via <rndc reload zone> (or via the external distribution command)\n", loptstr (", --reload", "\t"));
371 	fprintf (stderr, "\t-r%s\t reload zone via %s\n", loptstr (", --reload", "\t"), conf->dist_cmd ? conf->dist_cmd: "rndc");
372         fprintf (stderr, "\t-v%s\t be verbose (use twice to be very verbose)\n", loptstr (", --verbose", "\t"));
373 
374         fprintf (stderr, "\t[zone]\t sign only those zones given as argument\n");
375 
376         fprintf (stderr, "\n");
377         fprintf (stderr, "\tif neither -D nor -N nor -o is given, the directory tree specified\n");
378 	fprintf (stderr, "\tin the dnssec config file (\"%s\") will be parsed\n", conf->zonedir);
379 
380 	if ( mesg && *mesg )
381 		fprintf (stderr, "%s\n", mesg);
382 	exit (127);
383 }
384 
385 /**	fill zonelist with infos coming out of named.conf	**/
add2zonelist(const char * dir,const char * view,const char * zone,const char * file)386 static	int	add2zonelist (const char *dir, const char *view, const char *zone, const char *file)
387 {
388 #ifdef DBG
389 	fprintf (stderr, "printzone ");
390 	fprintf (stderr, "view \"%s\" " , view);
391 	fprintf (stderr, "zone \"%s\" " , zone);
392 	fprintf (stderr, "file ");
393 	if ( dir && *dir )
394 		fprintf (stderr, "%s/", dir);
395 	fprintf (stderr, "%s", file);
396 	fprintf (stderr, "\n");
397 #endif
398 	dbg_line ();
399 	if ( view[0] != '\0' )	/* view found in named.conf */
400 	{
401 		if ( viewname == NULL || viewname[0] == '\0' )	/* viewname wasn't set on startup ? */
402 		{
403 			dbg_line ();
404 			error ("zone \"%s\" in view \"%s\" found in name server config, but no matching view was set on startup\n", zone, view);
405 			lg_mesg (LG_ERROR, "\"%s\" in view \"%s\" found in name server config, but no matching view was set on startup", zone, view);
406 			return 0;
407 		}
408 		dbg_line ();
409 		if ( strcmp (viewname, view) != 0 )	/* zone is _not_ in current view */
410 			return 0;
411 	}
412 	return zone_readdir (dir, zone, file, &zonelist, config, dynamic_zone);
413 }
414 
parsedir(const char * dir,zone_t ** zp,const zconf_t * conf)415 static	int	parsedir (const char *dir, zone_t **zp, const zconf_t *conf)
416 {
417 	DIR	*dirp;
418 	struct  dirent  *dentp;
419 	char	path[MAX_PATHSIZE+1];
420 
421 	dbg_val ("parsedir: (%s)\n", dir);
422 	if ( !is_directory (dir) )
423 		return 0;
424 
425 	dbg_line ();
426 	zone_readdir (dir, NULL, NULL, zp, conf, dynamic_zone);
427 
428 	dbg_val ("parsedir: opendir(%s)\n", dir);
429 	if ( (dirp = opendir (dir)) == NULL )
430 		return 0;
431 
432 	while ( (dentp = readdir (dirp)) != NULL )
433 	{
434 		if ( is_dotfilename (dentp->d_name) )
435 			continue;
436 
437 		pathname (path, sizeof (path), dir, dentp->d_name, NULL);
438 		if ( !is_directory (path) )
439 			continue;
440 
441 		dbg_val ("parsedir: recursive %s\n", path);
442 		parsedir (path, zp, conf);
443 	}
444 	closedir (dirp);
445 	return 1;
446 }
447 
dosigning(zone_t * zonelist,zone_t * zp)448 static	int	dosigning (zone_t *zonelist, zone_t *zp)
449 {
450 	char	path[MAX_PATHSIZE+1];
451 	int	err;
452 	int	newkey;
453 	int	newkeysetfile;
454 	int	use_unixtime;
455 	time_t	currtime;
456 	time_t	zfile_time;
457 	time_t	zfilesig_time;
458 	char	mesg[255+1];
459 
460 	verbmesg (1, zp->conf, "parsing zone \"%s\" in dir \"%s\"\n", zp->zone, zp->dir);
461 
462 	pathname (path, sizeof (path), zp->dir, zp->sfile, NULL);
463 	dbg_val("parsezonedir fileexist (%s)\n", path);
464 	if ( !fileexist (path) )
465 	{
466 		error ("Not a secure zone directory (%s)!\n", zp->dir);
467 		lg_mesg (LG_ERROR, "\"%s\": not a secure zone directory (%s)!", zp->zone, zp->dir);
468 		return 1;
469 	}
470 	zfilesig_time = file_mtime (path);
471 
472 	pathname (path, sizeof (path), zp->dir, zp->file, NULL);
473 	dbg_val("parsezonedir fileexist (%s)\n", path);
474 	if ( !fileexist (path) )
475 	{
476 		error ("No zone file found (%s)!\n", path);
477 		lg_mesg (LG_ERROR, "\"%s\": no zone file found (%s)!", zp->zone, path);
478 		return 2;
479 	}
480 
481 	zfile_time = file_mtime (path);
482 	currtime = time (NULL);
483 
484 	/* check for domain based logging */
485 	if ( is_defined (zp->conf->logdomaindir) )	/* parameter is not null or empty ? */
486 	{
487 		if ( strcmp (zp->conf->logdomaindir, ".") == 0 )	/* current (".") means zone directory */
488 			lg_zone_start (zp->dir, zp->zone);
489 		else
490 			lg_zone_start (zp->conf->logdomaindir, zp->zone);
491 	}
492 
493 	/* check rfc5011 key signing keys, create new one if necessary */
494 	dbg_msg("parsezonedir check rfc 5011 ksk ");
495 	newkey = ksk5011status (&zp->keys, zp->dir, zp->zone, zp->conf);
496 	if ( (newkey & 02) != 02 )	/* not a rfc 5011 zone ? */
497 	{
498 		verbmesg (2, zp->conf, "\t\t->not a rfc5011 zone, looking for a regular ksk rollover\n");
499 		/* check key signing keys, create new one if necessary */
500 		dbg_msg("parsezonedir check ksk ");
501 		newkey |= kskstatus (zonelist, zp);
502 	}
503 	else
504 		newkey &= ~02;		/* reset bit 2 */
505 
506 	/* check age of zone keys, probably retire (depreciate) or remove old keys */
507 	dbg_msg("parsezonedir check zsk ");
508 	newkey += zskstatus (&zp->keys, zp->dir, zp->zone, zp->conf);
509 
510 	/* check age of "dnskey.db" file against age of keyfiles */
511 	pathname (path, sizeof (path), zp->dir, zp->conf->keyfile, NULL);
512 	dbg_val("parsezonedir check_keydb_timestamp (%s)\n", path);
513 	if ( !newkey )
514 		newkey = check_keydb_timestamp (zp->keys, file_mtime (path));
515 
516 	newkeysetfile = 0;
517 #if defined(ALWAYS_CHECK_KEYSETFILES) && ALWAYS_CHECK_KEYSETFILES	/* patch from Shane Wegner 15. June 2009 */
518 	/* check if there is a new keyset- file */
519 	if ( !newkey )
520 		newkeysetfile = new_keysetfiles (zp->dir, zfilesig_time);
521 #else
522 	/* if we work in subdir mode, check if there is a new keyset- file */
523 	if ( !newkey && zp->conf->keysetdir && strcmp (zp->conf->keysetdir, "..") == 0 )
524 		newkeysetfile = new_keysetfiles (zp->dir, zfilesig_time);
525 #endif
526 
527 	/* is there a list of files included in zone.db ? */
528 	if ( zp->conf->dependfiles && *zp->conf->dependfiles )
529 	{
530 		char	file[255+1];
531 		const	char	*p;
532 		int	i;
533 		time_t	incfile_mtime;
534 
535 		/* check the timestamp of each file against "zone.db" */
536 		p = zp->conf->dependfiles;
537 		while ( p && *p )
538 		{
539 			while ( isflistdelim (*p) )
540 				p++;
541 
542 			for ( i = 0; i < 255 && *p && !isflistdelim (*p); i++ )
543 				file[i] = *p++;
544 			file[i] = '\0';
545 
546 			pathname (path, sizeof (path), zp->dir, file, NULL);
547 
548 			incfile_mtime = file_mtime (path);
549 			if ( incfile_mtime > zfile_time )	/* include file is newer? */
550 				zfile_time = incfile_mtime;	/* take this one as new mtime */
551 		}
552 	}
553 
554 	/**
555 	** Check if it is time to do a re-sign. This is the case if
556 	**	a) the command line flag -f is set, or
557 	**	b) new keys are generated, or
558 	**	c) we found a new KSK of a delegated domain, or
559 	**	d) the "dnskey.db" file is newer than "zone.db"
560 	**	e) the "zone.db" is newer than "zone.db.signed" or
561 	**	f) "zone.db.signed" is older than the re-sign interval
562 	**/
563 	mesg[0] = '\0';
564 	if ( force )
565 		snprintf (mesg, sizeof(mesg), "Option -f");
566 	else if ( newkey )
567 		snprintf (mesg, sizeof(mesg), "Modified zone key set");
568 	else if ( newkeysetfile )
569 		snprintf (mesg, sizeof(mesg), "Modified KSK in delegated domain");
570 	else if ( file_mtime (path) > zfilesig_time )
571 		snprintf (mesg, sizeof(mesg), "Modified keys");
572 	else if ( zfile_time > zfilesig_time )
573 		snprintf (mesg, sizeof(mesg), "Zone file edited");
574 	else if ( (currtime - zfilesig_time) > zp->conf->resign - (OFFSET) )
575 		snprintf (mesg, sizeof(mesg), "re-signing interval (%s) reached",
576 						str_delspace (age2str (zp->conf->resign)));
577 
578 	if ( *mesg )
579 		verbmesg (1, zp->conf, "\tRe-signing necessary: %s\n", mesg);
580 	else
581 		verbmesg (1, zp->conf, "\tRe-signing not necessary!\n");
582 
583 	if ( *mesg )
584 		lg_mesg (LG_NOTICE, "\"%s\": re-signing triggered: %s", zp->zone,  mesg);
585 
586 	dbg_line ();
587 	if ( !(force || newkey || newkeysetfile || zfile_time > zfilesig_time ||
588 	     file_mtime (path) > zfilesig_time ||
589 	     (currtime - zfilesig_time) > zp->conf->resign - (OFFSET)) )
590 	{
591 		verbmesg (2, zp->conf, "\tCheck if there is a parent file to copy\n");
592 		if ( zp->conf->keysetdir && strcmp (zp->conf->keysetdir, "..") == 0 )
593 			copy_keyset (zp->dir, zp->zone, zp->conf);	/* copy the parent- file if it exist */
594 		if ( is_defined (zp->conf->logdomaindir) )
595 			lg_zone_end ();
596 		return 0;	/* nothing to do */
597 	}
598 
599 	/* let's start signing the zone */
600 	dbg_line ();
601 
602 	/* create new "dnskey.db" file  */
603 	pathname (path, sizeof (path), zp->dir, zp->conf->keyfile, NULL);
604 	verbmesg (1, zp->conf, "\tWriting key file \"%s\"\n", path);
605 	if ( !writekeyfile (path, zp->keys, zp->conf->key_ttl) )
606 	{
607 		error ("Can't create keyfile %s \n", path);
608 		lg_mesg (LG_ERROR, "\"%s\": can't create keyfile %s", zp->zone , path);
609 	}
610 
611 	err = 1;
612 	use_unixtime = ( zp->conf->serialform == Unixtime );
613 	dbg_val1 ("Use unixtime = %d\n", use_unixtime);
614 	if ( !dynamic_zone && !use_unixtime ) /* increment serial number in static zone files */
615 	{
616 		pathname (path, sizeof (path), zp->dir, zp->file, NULL);
617 		err = 0;
618 		if ( noexec == 0 )
619 		{
620 			if ( (err = inc_serial (path, use_unixtime)) < 0 )
621 			{
622 				error ("could not increment serialno of domain %s in file %s: %s!\n",
623 								zp->zone, path, inc_errstr (err));
624 				lg_mesg (LG_ERROR,
625 					"zone \"%s\": couldn't increment serialno in file %s: %s",
626 							zp->zone, path, inc_errstr (err));
627 			}
628 			else
629 			verbmesg (1, zp->conf, "\tIncrementing serial number in file \"%s\"\n", path);
630 		}
631 		else
632 			verbmesg (1, zp->conf, "\tIncrementing serial number in file \"%s\"\n", path);
633 	}
634 
635 	/* at last, sign the zone file */
636 	if ( err >= 0 )
637 	{
638 		time_t	timer;
639 
640 		verbmesg (1, zp->conf, "\tSigning zone \"%s\"\n", zp->zone);
641 		logflush ();
642 
643 		/* dynamic zones uses incremental signing, so we have to */
644 		/* prepare the old (signed) file as new input file */
645 		if ( dynamic_zone )
646 		{
647 			char	zfile[MAX_PATHSIZE+1];
648 
649 			dyn_update_freeze (zp->zone, zp->conf, 1);	/* freeze dynamic zone ! */
650 
651 			pathname (zfile, sizeof (zfile), zp->dir, zp->file, NULL);
652 			pathname (path, sizeof (path), zp->dir, zp->sfile, NULL);
653 			if ( filesize (path) == 0L )    /* initial signing request ? */
654 			{
655 				verbmesg (1, zp->conf, "\tDynamic Zone signing: Initial signing request: Add DNSKEYs to zonefile\n");
656 				copyfile (zfile, path, zp->conf->keyfile);
657 			}
658 #if 1
659 			else if ( zfile_time > zfilesig_time )  /* zone.db is newer than signed file */
660 			{
661 				verbmesg (1, zp->conf, "\tDynamic Zone signing: zone file manually edited: Use it as new input file\n");
662 				copyfile (zfile, path, NULL);
663 			}
664 #endif
665 			verbmesg (1, zp->conf, "\tDynamic Zone signing: copy old signed zone file %s to new input file %s\n",
666 										path, zfile);
667 
668 			if ( newkey )	/* if we have new keys, they should be added to the zone file */
669 			{
670 				copyzonefile (path, zfile, zp->conf->keyfile);
671 #if 0
672 				if ( zp->conf->dist_cmd )
673 					dist_and_reload (zp, 2);	/* ... and send to the name server */
674 #endif
675 			}
676 			else		/* else we can do a simple file copy */
677 				copyfile (path, zfile, NULL);
678 		}
679 
680 		timer = start_timer ();
681 		if ( (err = sign_zone (zp)) < 0 )
682 		{
683 			error ("\tSigning of zone %s failed (%d)!\n", zp->zone, err);
684 			lg_mesg (LG_ERROR, "\"%s\": signing failed!", zp->zone);
685 		}
686 		timer = stop_timer (timer);
687 
688 		if ( dynamic_zone )
689 			dyn_update_freeze (zp->zone, zp->conf, 0);	/* thaw dynamic zone file */
690 
691 		if ( err >= 0 )
692 		{
693 		const	char	*tstr = str_delspace (age2str (timer));
694 
695 		if ( !tstr || *tstr == '\0' )
696 			tstr = "0s";
697 		verbmesg (1, zp->conf, "\tSigning completed after %s.\n", tstr);
698 		}
699 	}
700 
701 	copy_keyset (zp->dir, zp->zone, zp->conf);
702 
703 	if ( err >= 0 && reloadflag )
704 	{
705 		if ( zp->conf->dist_cmd )
706 			dist_and_reload (zp, 1);
707 		else
708 			reload_zone (zp->zone, zp->conf);
709 
710 		register_key (zp->keys, zp->conf);
711 	}
712 
713 	if ( is_defined (zp->conf->logdomaindir) )
714 		lg_zone_end ();
715 
716 	return err;
717 }
718 
719 /*****************************************************************
720 **	This function is no longer needed, and us doing in fact
721 **	nothing.
722 **	The primary idea was to register a new KSK at a experimental
723 **	key registration instance.
724 **	This code is still here, to have an anchor for a (new) client
725 **	parent DS update protocol (e.g. via dynamic updates).
726 *****************************************************************/
register_key(dki_t * list,const zconf_t * z)727 static	void	register_key (dki_t *list, const zconf_t *z)
728 {
729 #if 0
730 	dki_t	*dkp;
731 	time_t	age;
732 	time_t	currtime;
733 
734 	assert ( list != NULL );
735 	assert ( z != NULL );
736 
737 	currtime = time (NULL);
738 	for ( dkp = list; dkp && dki_isksk (dkp); dkp = dkp->next )
739 	{
740 		age = dki_age (dkp, currtime);
741 
742 		/* announce "new" and active key signing keys */
743 		if ( REG_URL && *REG_URL && dki_status (dkp) == DKI_ACT && age <= z->resign * 4 )
744 		{
745 			if ( verbose )
746 				logmesg ("\tRegister new KSK with tag %d for domain %s\n",
747 								dkp->tag, dkp->name);
748 		}
749 	}
750 #endif
751 }
752 
753 /*
754  *	This function is not working with symbolic links to keyset- files,
755  *	because file_mtime() returns the mtime of the underlying file, and *not*
756  *	that of the symlink file.
757  *	This is bad, because the keyset-file will be newly generated by dnssec-signzone
758  *	on every re-signing call.
759  *	Instead, in the case of a hierarchical directory structure, we copy the file
760  *	(and so we change the timestamp) only if it was modified after the last
761  *	generation (checked with cmpfile(), see func sign_zone()).
762  */
763 # define	KEYSET_FILE_PFX	"keyset-"
new_keysetfiles(const char * dir,time_t zone_signing_time)764 static	int	new_keysetfiles (const char *dir, time_t zone_signing_time)
765 {
766 	DIR	*dirp;
767 	struct  dirent  *dentp;
768 	char	path[MAX_PATHSIZE+1];
769 	int	newkeysetfile;
770 
771 	if ( (dirp = opendir (dir)) == NULL )
772 		return 0;
773 
774 	newkeysetfile = 0;
775 	dbg_val2 ("new_keysetfile (%s, %s)\n", dir, time2str (zone_signing_time, 's'));
776 	while ( !newkeysetfile && (dentp = readdir (dirp)) != NULL )
777 	{
778 		if ( strncmp (dentp->d_name, KEYSET_FILE_PFX, strlen (KEYSET_FILE_PFX)) != 0 )
779 			continue;
780 
781 		pathname (path, sizeof (path), dir, dentp->d_name, NULL);
782 		dbg_val2 ("newkeysetfile timestamp of %s = %s\n", path, time2str (file_mtime(path), 's'));
783 		if ( file_mtime (path) > zone_signing_time )
784 			newkeysetfile = 1;
785 	}
786 	closedir (dirp);
787 
788 	return newkeysetfile;
789 }
790 
check_keydb_timestamp(dki_t * keylist,time_t reftime)791 static	int	check_keydb_timestamp (dki_t *keylist, time_t reftime)
792 {
793 	dki_t	*key;
794 
795 	assert ( keylist != NULL );
796 	if ( reftime == 0 )
797 		return 1;
798 
799 	for ( key = keylist; key; key = key->next )
800 		if ( dki_time (key) > reftime )
801 			return 1;
802 
803 	return 0;
804 }
805 
writekeyfile(const char * fname,const dki_t * list,int key_ttl)806 static	int	writekeyfile (const char *fname, const dki_t *list, int key_ttl)
807 {
808 	FILE	*fp;
809 	const	dki_t	*dkp;
810 	time_t	curr = time (NULL);
811 	int	ksk;
812 
813 	if ( (fp = fopen (fname, "w")) == NULL )
814 		return 0;
815 	fprintf (fp, ";\n");
816 	fprintf (fp, ";\t!!! Don\'t edit this file by hand.\n");
817 	fprintf (fp, ";\t!!! It will be generated by %s.\n", progname);
818 	fprintf (fp, ";\n");
819 	fprintf (fp, ";\t Last generation time %s\n", time2str (curr, 's'));
820 	fprintf (fp, ";\n");
821 
822 	fprintf (fp, "\n");
823 	fprintf (fp, ";  ***  List of Key Signing Keys  ***\n");
824 	ksk = 1;
825 	for ( dkp = list; dkp; dkp = dkp->next )
826 	{
827 		if ( ksk && !dki_isksk (dkp) )
828 		{
829 			fprintf (fp, "; ***  List of Zone Signing Keys  ***\n");
830 			ksk = 0;
831 		}
832 		dki_prt_comment (dkp, fp);
833 		dki_prt_dnskeyttl (dkp, fp, key_ttl);
834 		putc ('\n', fp);
835 	}
836 
837 	fclose (fp);
838 	return 1;
839 }
840 
sign_zone(const zone_t * zp)841 static	int	sign_zone (const zone_t *zp)
842 {
843 	char	cmd[2047+1];
844 	char	str[1023+1];
845 	char	rparam[254+1];
846 	char	nsec3param[637+1];
847 	char	keysetdir[254+1];
848 	const	char	*gends;
849 	const	char	*dnskeyksk;
850 	const	char	*pseudo;
851 	const	char	*param;
852 	int	len;
853 
854 	const	char	*dir;
855 	const	char	*domain;
856 	const	char	*file;
857 	const	zconf_t	*conf;
858 
859 	assert (zp != NULL);
860 	dir = zp->dir;
861 	domain = zp->zone;
862 	file = zp->file;
863 	conf = zp->conf;
864 
865 	len = 0;
866 	str[0] = '\0';
867 	if ( conf->lookaside && conf->lookaside[0] )
868 		len = snprintf (str, sizeof (str), "-l %.250s", conf->lookaside);
869 
870 	dbg_line();
871 	if ( !dynamic_zone && conf->serialform == Unixtime )
872 		snprintf (str+len, sizeof (str) - len, " -N unixtime");
873 
874 	gends = "";
875 	if ( conf->sig_gends )
876 		gends = "-C -g ";
877 
878 	dnskeyksk = "";
879 	if ( conf->sig_dnskeyksk )
880 		dnskeyksk = "-x ";
881 
882 	pseudo = "";
883 	if ( conf->sig_pseudo )
884 		pseudo = "-p ";
885 
886 	param = "";
887 	if ( conf->sig_param && conf->sig_param[0] )
888 		param = conf->sig_param;
889 
890 	nsec3param[0] = '\0';
891 	if ( conf->k_algo == DK_ALGO_NSEC3DSA || conf->k_algo == DK_ALGO_NSEC3RSASHA1 ||
892 	     conf->nsec3 != NSEC3_OFF )
893 	{
894 		char	salt[510+1];	/* salt has a maximum of 255 bytes == 510 hex nibbles */
895 		const	char	*update;
896 		const	char	*optout;
897 		unsigned int	seed;
898 
899 		update = "-u ";		/* trailing blank is necessary */
900 		if ( conf->nsec3 == NSEC3_OPTOUT )
901 			optout = "-A ";
902 		else
903 			optout = "";
904 
905 			/* static zones can use always a new salt (full zone signing) */
906 		seed = 0L;	/* no seed: use mechanism build in gensalt() */
907 		if ( dynamic_zone )
908 		{		/* dynamic zones have to reuse the salt on signing */
909 			const	dki_t	*kp;
910 
911 			/* use gentime timestamp of ZSK for seeding rand generator */
912 			kp = dki_find (zp->keys, DKI_ZSK, DKI_ACTIVE, 1);
913 			assert ( kp != NULL );
914 			if ( kp->gentime )
915 				seed = kp->gentime;
916 			else
917 				seed = kp->time;
918 		}
919 
920 		if ( gensalt (salt, sizeof (salt), conf->saltbits, seed) )
921 			snprintf (nsec3param, sizeof (nsec3param), "%s%s-3 %s ", update, optout, salt);
922 	}
923 
924 	dbg_line();
925 	rparam[0] = '\0';
926 	if ( conf->sig_random && conf->sig_random[0] )
927 		snprintf (rparam, sizeof (rparam), "-r %.250s ", conf->sig_random);
928 
929 	dbg_line();
930 	keysetdir[0] = '\0';
931 	if ( conf->keysetdir && conf->keysetdir[0] && strcmp (conf->keysetdir, "..") != 0 )
932 		snprintf (keysetdir, sizeof (keysetdir), "-d %.250s ", conf->keysetdir);
933 
934 	if ( dir == NULL || *dir == '\0' )
935 		dir = ".";
936 
937 	dbg_line();
938 	if ( dynamic_zone )
939 		snprintf (cmd, sizeof (cmd), "cd %s; %s %s %s%s%s%s%s%s-o %s -e +%ld %s -N increment -f %s.dsigned %s K*.private 2>&1",
940 			dir, SIGNCMD, param, nsec3param, dnskeyksk, gends, pseudo, rparam, keysetdir, domain, conf->sigvalidity, str, file, file);
941 	else
942 		snprintf (cmd, sizeof (cmd), "cd %s; %s %s %s%s%s%s%s%s-o %s -e +%ld %s %s K*.private 2>&1",
943 			dir, SIGNCMD, param, nsec3param, dnskeyksk, gends, pseudo, rparam, keysetdir, domain, conf->sigvalidity, str, file);
944 	verbmesg (2, conf, "\t  Run cmd \"%s\"\n", cmd);
945 	*str = '\0';
946 	if ( noexec == 0 )
947 	{
948 		FILE	*fp;
949 		int	exitcode;
950 
951 		if ( (fp = popen (cmd, "r")) == NULL )
952 			return -1;
953 
954 		str[0] = '\0';
955 		while ( fgets (str, sizeof str, fp) != NULL )	/* eat up all output until the last line */
956 			;
957 
958 		exitcode = pclose (fp);
959 		verbmesg (2, conf, "\t  Cmd dnssec-signzone returns with exitcode=%d: \"%s\"\n", exitcode, str_chop (str, '\n'));
960 	}
961 
962 	dbg_line();
963 	len = strlen (str) - 6;
964 	if ( len < 0 || strcmp (str+len, "signed") != 0 )
965 		return -1;
966 
967 	return 0;
968 }
969 
copy_keyset(const char * dir,const char * domain,const zconf_t * conf)970 static	void	copy_keyset (const char *dir, const char *domain, const zconf_t *conf)
971 {
972 	char	fromfile[1024];
973 	char	tofile[1024];
974 	int	ret;
975 
976 	/* propagate "keyset"-file to parent dir */
977 	if ( conf->keysetdir && strcmp (conf->keysetdir, "..") == 0 )
978 	{
979 		/* check if special parent-file exist (ksk rollover) */
980 		snprintf (fromfile, sizeof (fromfile), "%s/parent-%s", dir, domain);
981 		if ( !fileexist (fromfile) )	/* use "normal" keyset-file */
982 			snprintf (fromfile, sizeof (fromfile), "%s/keyset-%s", dir, domain);
983 
984 		/* verbmesg (2, conf, "\t  check \"%s\" against parent dir\n", fromfile); */
985 		snprintf (tofile, sizeof (tofile), "%s/../keyset-%s", dir, domain);
986 		if ( cmpfile (fromfile, tofile) != 0 )
987 		{
988 			verbmesg (2, conf, "\t  copy \"%s\" to parent dir\n", fromfile);
989 			if ( (ret = copyfile (fromfile, tofile, NULL)) != 0 )
990 			{
991 				error ("Couldn't copy \"%s\" to parent dir (%d:%s)\n",
992 					fromfile, ret, strerror(errno));
993 				lg_mesg (LG_ERROR, "\%s\": can't copy \"%s\" to parent dir (%d:%s)",
994 					domain, fromfile, ret, strerror(errno));
995 			}
996 		}
997 	}
998 }
999