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