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