
Also known as ISC bind. This import adds utilities such as host(1), dig(1), and nslookup(1), as well as many other tools and libraries. Change-Id: I035ca46e64f1965d57019e773f4ff0ef035e4aa3
993 lines
30 KiB
C
993 lines
30 KiB
C
/* $NetBSD: zkt-signer.c,v 1.1.1.1 2015/07/08 15:37:48 christos Exp $ */
|
|
|
|
/*****************************************************************
|
|
**
|
|
** @(#) zkt-signer.c (c) Jan 2005 - Jan 2010 Holger Zuleger hznet.de
|
|
**
|
|
** A wrapper around the BIND dnssec-signzone command which is able
|
|
** to resign a zone if necessary and doing a zone or key signing key rollover.
|
|
**
|
|
** Copyright (c) 2005 - 2010, Holger Zuleger HZnet. All rights reserved.
|
|
** This software is open source.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** Redistributions of source code must retain the above copyright notice,
|
|
** this list of conditions and the following disclaimer.
|
|
**
|
|
** Redistributions in binary form must reproduce the above copyright notice,
|
|
** this list of conditions and the following disclaimer in the documentation
|
|
** and/or other materials provided with the distribution.
|
|
**
|
|
** Neither the name of Holger Zuleger HZnet nor the names of its contributors may
|
|
** be used to endorse or promote products derived from this software without
|
|
** specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
** TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
|
** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
** POSSIBILITY OF SUCH DAMAGE.
|
|
**
|
|
*****************************************************************/
|
|
|
|
# include <stdio.h>
|
|
# include <string.h>
|
|
# include <stdlib.h>
|
|
# include <assert.h>
|
|
# include <dirent.h>
|
|
# include <errno.h>
|
|
# include <unistd.h>
|
|
# include <ctype.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
# include "config_zkt.h"
|
|
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
|
|
# include <getopt.h>
|
|
#endif
|
|
# include "zconf.h"
|
|
# include "debug.h"
|
|
# include "misc.h"
|
|
# include "ncparse.h"
|
|
# include "nscomm.h"
|
|
# include "soaserial.h"
|
|
# include "zone.h"
|
|
# include "dki.h"
|
|
# include "rollover.h"
|
|
# include "log.h"
|
|
|
|
# define short_options "c:L:V:D:N:o:O:dfHhnrv"
|
|
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
|
|
static struct option long_options[] = {
|
|
{"reload", no_argument, NULL, 'r'},
|
|
{"force", no_argument, NULL, 'f'},
|
|
{"noexec", no_argument, NULL, 'n'},
|
|
{"verbose", no_argument, NULL, 'v'},
|
|
{"directory", no_argument, NULL, 'd'},
|
|
{"config", required_argument, NULL, 'c'},
|
|
{"option", required_argument, NULL, 'O'},
|
|
{"config-option", required_argument, NULL, 'O'},
|
|
{"logfile", required_argument, NULL, 'L' },
|
|
{"view", required_argument, NULL, 'V' },
|
|
{"directory", required_argument, NULL, 'D'},
|
|
{"named-conf", required_argument, NULL, 'N'},
|
|
{"origin", required_argument, NULL, 'o'},
|
|
{"dynamic", no_argument, NULL, 'd' },
|
|
{"help", no_argument, NULL, 'h'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
#endif
|
|
|
|
|
|
/** function declaration **/
|
|
static void usage (char *mesg, zconf_t *conf);
|
|
static int add2zonelist (const char *dir, const char *view, const char *zone, const char *file);
|
|
static int parsedir (const char *dir, zone_t **zp, const zconf_t *conf);
|
|
static int dosigning (zone_t *zonelist, zone_t *zp);
|
|
static int check_keydb_timestamp (dki_t *keylist, time_t reftime);
|
|
static int new_keysetfiles (const char *dir, time_t zone_signing_time);
|
|
static int writekeyfile (const char *fname, const dki_t *list, int key_ttl);
|
|
static int sign_zone (const zone_t *zp);
|
|
static void register_key (dki_t *listp, const zconf_t *z);
|
|
static void copy_keyset (const char *dir, const char *domain, const zconf_t *conf);
|
|
|
|
/** global command line options **/
|
|
extern int optopt;
|
|
extern int opterr;
|
|
extern int optind;
|
|
extern char *optarg;
|
|
const char *progname;
|
|
static const char *viewname = NULL;
|
|
static const char *logfile = NULL;
|
|
static const char *origin = NULL;
|
|
static const char *namedconf = NULL;
|
|
static const char *dirname = NULL;
|
|
static int verbose = 0;
|
|
static int force = 0;
|
|
static int reloadflag = 0;
|
|
static int noexec = 0;
|
|
static int dynamic_zone = 0; /* dynamic zone ? */
|
|
static zone_t *zonelist = NULL; /* must be static global because add2zonelist use it */
|
|
static zconf_t *config;
|
|
|
|
/** macros **/
|
|
#define set_bind96_dynzone(dz) ((dz) = 6)
|
|
#define bind96_dynzone(dz) ( (dz) >= 6 )
|
|
#define is_defined(str) ( (str) && *(str) )
|
|
|
|
int main (int argc, char *const argv[])
|
|
{
|
|
int c;
|
|
int errcnt;
|
|
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
|
|
int opt_index;
|
|
#endif
|
|
char errstr[255+1];
|
|
char *p;
|
|
const char *defconfname;
|
|
zone_t *zp;
|
|
|
|
progname = *argv;
|
|
if ( (p = strrchr (progname, '/')) )
|
|
progname = ++p;
|
|
|
|
if ( strncmp (progname, "dnssec-signer", 13) == 0 )
|
|
{
|
|
fprintf (stderr, "The use of dnssec-signer is deprecated, please run zkt-signer instead\n");
|
|
viewname = getnameappendix (progname, "dnssec-signer");
|
|
}
|
|
else
|
|
viewname = getnameappendix (progname, "zkt-signer");
|
|
defconfname = getdefconfname (viewname);
|
|
config = loadconfig ("", (zconf_t *)NULL); /* load build-in config */
|
|
if ( fileexist (defconfname) ) /* load default config file */
|
|
config = loadconfig (defconfname, config);
|
|
if ( config == NULL )
|
|
fatal ("Couldn't load config: Out of memory\n");
|
|
|
|
zonelist = NULL;
|
|
opterr = 0;
|
|
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
|
|
while ( (c = getopt_long (argc, argv, short_options, long_options, &opt_index)) != -1 )
|
|
#else
|
|
while ( (c = getopt (argc, argv, short_options)) != -1 )
|
|
#endif
|
|
{
|
|
switch ( c )
|
|
{
|
|
case 'V': /* view name */
|
|
viewname = optarg;
|
|
defconfname = getdefconfname (viewname);
|
|
if ( fileexist (defconfname) ) /* load default config file */
|
|
config = loadconfig (defconfname, config);
|
|
if ( config == NULL )
|
|
fatal ("Out of memory\n");
|
|
break;
|
|
case 'c': /* load config from file */
|
|
config = loadconfig (optarg, config);
|
|
if ( config == NULL )
|
|
fatal ("Out of memory\n");
|
|
break;
|
|
case 'O': /* load config option from commandline */
|
|
config = loadconfig_fromstr (optarg, config);
|
|
if ( config == NULL )
|
|
fatal ("Out of memory\n");
|
|
break;
|
|
case 'o':
|
|
origin = optarg;
|
|
break;
|
|
case 'N':
|
|
namedconf = optarg;
|
|
break;
|
|
case 'D':
|
|
dirname = optarg;
|
|
break;
|
|
case 'L': /* error log file|directory */
|
|
logfile = optarg;
|
|
break;
|
|
case 'f':
|
|
force++;
|
|
break;
|
|
case 'H':
|
|
case 'h':
|
|
usage (NULL, config);
|
|
break;
|
|
case 'd':
|
|
dynamic_zone = 1;
|
|
/* dynamic zone requires a name server reload... */
|
|
reloadflag = 0; /* ...but "rndc thaw" reloads the zone anyway */
|
|
break;
|
|
case 'n':
|
|
noexec = 1;
|
|
break;
|
|
case 'r':
|
|
if ( !dynamic_zone ) /* dynamic zones don't need a rndc reload (see "-d" */
|
|
reloadflag = 1;
|
|
break;
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
case '?':
|
|
if ( isprint (optopt) )
|
|
snprintf (errstr, sizeof(errstr),
|
|
"Unknown option \"-%c\".\n", optopt);
|
|
else
|
|
snprintf (errstr, sizeof (errstr),
|
|
"Unknown option char \\x%x.\n", optopt);
|
|
usage (errstr, config);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
dbg_line();
|
|
|
|
/* store some of the commandline parameter in the config structure */
|
|
setconfigpar (config, "--view", viewname);
|
|
setconfigpar (config, "-v", &verbose);
|
|
setconfigpar (config, "--noexec", &noexec);
|
|
if ( logfile == NULL )
|
|
logfile = config->logfile;
|
|
|
|
if ( lg_open (progname, config->syslogfacility, config->sysloglevel, config->zonedir, logfile, config->loglevel) < -1 )
|
|
fatal ("Couldn't open logfile %s in dir %s\n", logfile, config->zonedir);
|
|
|
|
lg_args (LG_NOTICE, argc, argv);
|
|
|
|
/* 1.0rc1: If the ttl is 0 or not known because of dynamic zone signing, ... */
|
|
/* ... use sig valid time for this */
|
|
if ( config->max_ttl <= 0 || dynamic_zone )
|
|
{
|
|
// config = dupconfig (config);
|
|
config->max_ttl = config->sigvalidity;
|
|
}
|
|
|
|
|
|
if ( origin ) /* option -o ? */
|
|
{
|
|
int ret;
|
|
|
|
if ( (argc - optind) <= 0 ) /* no arguments left ? */
|
|
ret = zone_readdir (".", origin, NULL, &zonelist, config, dynamic_zone);
|
|
else
|
|
ret = zone_readdir (".", origin, argv[optind], &zonelist, config, dynamic_zone);
|
|
|
|
/* anyway, "delete" all (remaining) arguments */
|
|
optind = argc;
|
|
|
|
/* complain if nothing could read in */
|
|
if ( ret != 1 || zonelist == NULL )
|
|
{
|
|
lg_mesg (LG_FATAL, "\"%s\": couldn't read", origin);
|
|
fatal ("Couldn't read zone \"%s\"\n", origin);
|
|
}
|
|
}
|
|
if ( namedconf ) /* option -N ? */
|
|
{
|
|
char dir[255+1];
|
|
|
|
memset (dir, '\0', sizeof (dir));
|
|
if ( config->zonedir )
|
|
strncpy (dir, config->zonedir, sizeof(dir));
|
|
if ( !parse_namedconf (namedconf, config->chroot_dir, dir, sizeof (dir), add2zonelist) )
|
|
fatal ("Can't read file %s as namedconf file\n", namedconf);
|
|
if ( zonelist == NULL )
|
|
fatal ("No signed zone found in file %s\n", namedconf);
|
|
}
|
|
if ( dirname ) /* option -D ? */
|
|
{
|
|
char *dir = strdup (dirname);
|
|
|
|
p = dir + strlen (dir);
|
|
if ( p > dir )
|
|
p--;
|
|
if ( *p == '/' )
|
|
*p = '\0'; /* remove trailing path seperator */
|
|
|
|
if ( !parsedir (dir, &zonelist, config) )
|
|
fatal ("Can't read directory tree %s\n", dir);
|
|
if ( zonelist == NULL )
|
|
fatal ("No signed zone found in directory tree %s\n", dir);
|
|
free (dir);
|
|
}
|
|
|
|
/* none of the above: read default directory tree */
|
|
if ( zonelist == NULL )
|
|
parsedir (config->zonedir, &zonelist, config);
|
|
|
|
#if defined(DBG) && DBG
|
|
for ( zp = zonelist; zp; zp = zp->next )
|
|
zone_print ("in main: ", zp);
|
|
#endif
|
|
for ( zp = zonelist; zp; zp = zp->next )
|
|
if ( in_strarr (zp->zone, &argv[optind], argc - optind) )
|
|
{
|
|
dosigning (zonelist, zp);
|
|
verbmesg (1, zp->conf, "\n");
|
|
}
|
|
|
|
zone_freelist (&zonelist);
|
|
|
|
errcnt = lg_geterrcnt ();
|
|
lg_mesg (LG_NOTICE, "end of run: %d error%s occured", errcnt, errcnt == 1 ? "" : "s");
|
|
lg_close ();
|
|
|
|
return errcnt < 64 ? errcnt : 64;
|
|
}
|
|
|
|
# define sopt_usage(mesg, value) fprintf (stderr, mesg, value)
|
|
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
|
|
# define lopt_usage(mesg, value) fprintf (stderr, mesg, value)
|
|
# define loptstr(lstr, sstr) lstr
|
|
#else
|
|
# define lopt_usage(mesg, value)
|
|
# define loptstr(lstr, sstr) sstr
|
|
#endif
|
|
static void usage (char *mesg, zconf_t *conf)
|
|
{
|
|
fprintf (stderr, "%s version %s compiled for BIND %d\n", progname, ZKT_VERSION, BIND_VERSION);
|
|
fprintf (stderr, "ZKT %s\n", ZKT_COPYRIGHT);
|
|
fprintf (stderr, "\n");
|
|
|
|
fprintf (stderr, "usage: %s [-L] [-V view] [-c file] [-O optstr] ", progname);
|
|
fprintf (stderr, "[-D directorytree] ");
|
|
fprintf (stderr, "[-fhnr] [-v [-v]] [zone ...]\n");
|
|
|
|
fprintf (stderr, "usage: %s [-L] [-V view] [-c file] [-O optstr] ", progname);
|
|
fprintf (stderr, "-N named.conf ");
|
|
fprintf (stderr, "[-fhnr] [-v [-v]] [zone ...]\n");
|
|
|
|
fprintf (stderr, "usage: %s [-L] [-V view] [-c file] [-O optstr] ", progname);
|
|
fprintf (stderr, "-o origin ");
|
|
fprintf (stderr, "[-fhnr] [-v [-v]] [zonefile.signed]\n");
|
|
|
|
fprintf (stderr, "\t-c file%s", loptstr (", --config=file\n", ""));
|
|
fprintf (stderr, "\t\t read config from <file> instead of %s\n", CONFIG_FILE);
|
|
fprintf (stderr, "\t-O optstr%s", loptstr (", --config-option=\"optstr\"\n", ""));
|
|
fprintf (stderr, "\t\t set config options on the commandline\n");
|
|
fprintf (stderr, "\t-L file|dir%s", loptstr (", --logfile=file|dir\n", ""));
|
|
fprintf (stderr, "\t\t specify file or directory for the log output\n");
|
|
fprintf (stderr, "\t-V name%s", loptstr (", --view=name\n", ""));
|
|
fprintf (stderr, "\t\t specify the view name \n");
|
|
fprintf (stderr, "\t-D dir%s", loptstr (", --directory=dir\n", ""));
|
|
fprintf (stderr, "\t\t parse the given directory tree for a list of secure zones \n");
|
|
fprintf (stderr, "\t-N file%s", loptstr (", --named-conf=file\n", ""));
|
|
fprintf (stderr, "\t\t get the list of secure zones out of the named like config file \n");
|
|
fprintf (stderr, "\t-o zone%s", loptstr (", --origin=zone", ""));
|
|
fprintf (stderr, "\tspecify the name of the zone \n");
|
|
fprintf (stderr, "\t\t The file to sign should be given as an argument (default is \"%s.signed\")\n", conf->zonefile);
|
|
fprintf (stderr, "\t-h%s\t print this help\n", loptstr (", --help", "\t"));
|
|
fprintf (stderr, "\t-f%s\t force re-signing\n", loptstr (", --force", "\t"));
|
|
fprintf (stderr, "\t-n%s\t no execution of external signing command\n", loptstr (", --noexec", "\t"));
|
|
// fprintf (stderr, "\t-r%s\t reload zone via <rndc reload zone> (or via the external distribution command)\n", loptstr (", --reload", "\t"));
|
|
fprintf (stderr, "\t-r%s\t reload zone via %s\n", loptstr (", --reload", "\t"), conf->dist_cmd ? conf->dist_cmd: "rndc");
|
|
fprintf (stderr, "\t-v%s\t be verbose (use twice to be very verbose)\n", loptstr (", --verbose", "\t"));
|
|
|
|
fprintf (stderr, "\t[zone]\t sign only those zones given as argument\n");
|
|
|
|
fprintf (stderr, "\n");
|
|
fprintf (stderr, "\tif neither -D nor -N nor -o is given, the directory tree specified\n");
|
|
fprintf (stderr, "\tin the dnssec config file (\"%s\") will be parsed\n", conf->zonedir);
|
|
|
|
if ( mesg && *mesg )
|
|
fprintf (stderr, "%s\n", mesg);
|
|
exit (127);
|
|
}
|
|
|
|
/** fill zonelist with infos coming out of named.conf **/
|
|
static int add2zonelist (const char *dir, const char *view, const char *zone, const char *file)
|
|
{
|
|
#ifdef DBG
|
|
fprintf (stderr, "printzone ");
|
|
fprintf (stderr, "view \"%s\" " , view);
|
|
fprintf (stderr, "zone \"%s\" " , zone);
|
|
fprintf (stderr, "file ");
|
|
if ( dir && *dir )
|
|
fprintf (stderr, "%s/", dir);
|
|
fprintf (stderr, "%s", file);
|
|
fprintf (stderr, "\n");
|
|
#endif
|
|
dbg_line ();
|
|
if ( view[0] != '\0' ) /* view found in named.conf */
|
|
{
|
|
if ( viewname == NULL || viewname[0] == '\0' ) /* viewname wasn't set on startup ? */
|
|
{
|
|
dbg_line ();
|
|
error ("zone \"%s\" in view \"%s\" found in name server config, but no matching view was set on startup\n", zone, view);
|
|
lg_mesg (LG_ERROR, "\"%s\" in view \"%s\" found in name server config, but no matching view was set on startup", zone, view);
|
|
return 0;
|
|
}
|
|
dbg_line ();
|
|
if ( strcmp (viewname, view) != 0 ) /* zone is _not_ in current view */
|
|
return 0;
|
|
}
|
|
return zone_readdir (dir, zone, file, &zonelist, config, dynamic_zone);
|
|
}
|
|
|
|
static int parsedir (const char *dir, zone_t **zp, const zconf_t *conf)
|
|
{
|
|
DIR *dirp;
|
|
struct dirent *dentp;
|
|
char path[MAX_PATHSIZE+1];
|
|
|
|
dbg_val ("parsedir: (%s)\n", dir);
|
|
if ( !is_directory (dir) )
|
|
return 0;
|
|
|
|
dbg_line ();
|
|
zone_readdir (dir, NULL, NULL, zp, conf, dynamic_zone);
|
|
|
|
dbg_val ("parsedir: opendir(%s)\n", dir);
|
|
if ( (dirp = opendir (dir)) == NULL )
|
|
return 0;
|
|
|
|
while ( (dentp = readdir (dirp)) != NULL )
|
|
{
|
|
if ( is_dotfilename (dentp->d_name) )
|
|
continue;
|
|
|
|
pathname (path, sizeof (path), dir, dentp->d_name, NULL);
|
|
if ( !is_directory (path) )
|
|
continue;
|
|
|
|
dbg_val ("parsedir: recursive %s\n", path);
|
|
parsedir (path, zp, conf);
|
|
}
|
|
closedir (dirp);
|
|
return 1;
|
|
}
|
|
|
|
static int dosigning (zone_t *zonelist, zone_t *zp)
|
|
{
|
|
char path[MAX_PATHSIZE+1];
|
|
int err;
|
|
int newkey;
|
|
int newkeysetfile;
|
|
int use_unixtime;
|
|
time_t currtime;
|
|
time_t zfile_time;
|
|
time_t zfilesig_time;
|
|
char mesg[255+1];
|
|
|
|
verbmesg (1, zp->conf, "parsing zone \"%s\" in dir \"%s\"\n", zp->zone, zp->dir);
|
|
|
|
pathname (path, sizeof (path), zp->dir, zp->sfile, NULL);
|
|
dbg_val("parsezonedir fileexist (%s)\n", path);
|
|
if ( !fileexist (path) )
|
|
{
|
|
error ("Not a secure zone directory (%s)!\n", zp->dir);
|
|
lg_mesg (LG_ERROR, "\"%s\": not a secure zone directory (%s)!", zp->zone, zp->dir);
|
|
return 1;
|
|
}
|
|
zfilesig_time = file_mtime (path);
|
|
|
|
pathname (path, sizeof (path), zp->dir, zp->file, NULL);
|
|
dbg_val("parsezonedir fileexist (%s)\n", path);
|
|
if ( !fileexist (path) )
|
|
{
|
|
error ("No zone file found (%s)!\n", path);
|
|
lg_mesg (LG_ERROR, "\"%s\": no zone file found (%s)!", zp->zone, path);
|
|
return 2;
|
|
}
|
|
|
|
zfile_time = file_mtime (path);
|
|
currtime = time (NULL);
|
|
|
|
/* check for domain based logging */
|
|
if ( is_defined (zp->conf->logdomaindir) ) /* parameter is not null or empty ? */
|
|
{
|
|
if ( strcmp (zp->conf->logdomaindir, ".") == 0 ) /* current (".") means zone directory */
|
|
lg_zone_start (zp->dir, zp->zone);
|
|
else
|
|
lg_zone_start (zp->conf->logdomaindir, zp->zone);
|
|
}
|
|
|
|
/* check rfc5011 key signing keys, create new one if necessary */
|
|
dbg_msg("parsezonedir check rfc 5011 ksk ");
|
|
newkey = ksk5011status (&zp->keys, zp->dir, zp->zone, zp->conf);
|
|
if ( (newkey & 02) != 02 ) /* not a rfc 5011 zone ? */
|
|
{
|
|
verbmesg (2, zp->conf, "\t\t->not a rfc5011 zone, looking for a regular ksk rollover\n");
|
|
/* check key signing keys, create new one if necessary */
|
|
dbg_msg("parsezonedir check ksk ");
|
|
newkey |= kskstatus (zonelist, zp);
|
|
}
|
|
else
|
|
newkey &= ~02; /* reset bit 2 */
|
|
|
|
/* check age of zone keys, probably retire (depreciate) or remove old keys */
|
|
dbg_msg("parsezonedir check zsk ");
|
|
newkey += zskstatus (&zp->keys, zp->dir, zp->zone, zp->conf);
|
|
|
|
/* check age of "dnskey.db" file against age of keyfiles */
|
|
pathname (path, sizeof (path), zp->dir, zp->conf->keyfile, NULL);
|
|
dbg_val("parsezonedir check_keydb_timestamp (%s)\n", path);
|
|
if ( !newkey )
|
|
newkey = check_keydb_timestamp (zp->keys, file_mtime (path));
|
|
|
|
newkeysetfile = 0;
|
|
#if defined(ALWAYS_CHECK_KEYSETFILES) && ALWAYS_CHECK_KEYSETFILES /* patch from Shane Wegner 15. June 2009 */
|
|
/* check if there is a new keyset- file */
|
|
if ( !newkey )
|
|
newkeysetfile = new_keysetfiles (zp->dir, zfilesig_time);
|
|
#else
|
|
/* if we work in subdir mode, check if there is a new keyset- file */
|
|
if ( !newkey && zp->conf->keysetdir && strcmp (zp->conf->keysetdir, "..") == 0 )
|
|
newkeysetfile = new_keysetfiles (zp->dir, zfilesig_time);
|
|
#endif
|
|
|
|
/* is there a list of files included in zone.db ? */
|
|
if ( zp->conf->dependfiles && *zp->conf->dependfiles )
|
|
{
|
|
char file[255+1];
|
|
const char *p;
|
|
int i;
|
|
time_t incfile_mtime;
|
|
|
|
/* check the timestamp of each file against "zone.db" */
|
|
p = zp->conf->dependfiles;
|
|
while ( p && *p )
|
|
{
|
|
while ( isflistdelim (*p) )
|
|
p++;
|
|
|
|
for ( i = 0; i < 255 && *p && !isflistdelim (*p); i++ )
|
|
file[i] = *p++;
|
|
file[i] = '\0';
|
|
|
|
pathname (path, sizeof (path), zp->dir, file, NULL);
|
|
|
|
incfile_mtime = file_mtime (path);
|
|
if ( incfile_mtime > zfile_time ) /* include file is newer? */
|
|
zfile_time = incfile_mtime; /* take this one as new mtime */
|
|
}
|
|
}
|
|
|
|
/**
|
|
** Check if it is time to do a re-sign. This is the case if
|
|
** a) the command line flag -f is set, or
|
|
** b) new keys are generated, or
|
|
** c) we found a new KSK of a delegated domain, or
|
|
** d) the "dnskey.db" file is newer than "zone.db"
|
|
** e) the "zone.db" is newer than "zone.db.signed" or
|
|
** f) "zone.db.signed" is older than the re-sign interval
|
|
**/
|
|
mesg[0] = '\0';
|
|
if ( force )
|
|
snprintf (mesg, sizeof(mesg), "Option -f");
|
|
else if ( newkey )
|
|
snprintf (mesg, sizeof(mesg), "Modified zone key set");
|
|
else if ( newkeysetfile )
|
|
snprintf (mesg, sizeof(mesg), "Modified KSK in delegated domain");
|
|
else if ( file_mtime (path) > zfilesig_time )
|
|
snprintf (mesg, sizeof(mesg), "Modified keys");
|
|
else if ( zfile_time > zfilesig_time )
|
|
snprintf (mesg, sizeof(mesg), "Zone file edited");
|
|
else if ( (currtime - zfilesig_time) > zp->conf->resign - (OFFSET) )
|
|
snprintf (mesg, sizeof(mesg), "re-signing interval (%s) reached",
|
|
str_delspace (age2str (zp->conf->resign)));
|
|
|
|
if ( *mesg )
|
|
verbmesg (1, zp->conf, "\tRe-signing necessary: %s\n", mesg);
|
|
else
|
|
verbmesg (1, zp->conf, "\tRe-signing not necessary!\n");
|
|
|
|
if ( *mesg )
|
|
lg_mesg (LG_NOTICE, "\"%s\": re-signing triggered: %s", zp->zone, mesg);
|
|
|
|
dbg_line ();
|
|
if ( !(force || newkey || newkeysetfile || zfile_time > zfilesig_time ||
|
|
file_mtime (path) > zfilesig_time ||
|
|
(currtime - zfilesig_time) > zp->conf->resign - (OFFSET)) )
|
|
{
|
|
verbmesg (2, zp->conf, "\tCheck if there is a parent file to copy\n");
|
|
if ( zp->conf->keysetdir && strcmp (zp->conf->keysetdir, "..") == 0 )
|
|
copy_keyset (zp->dir, zp->zone, zp->conf); /* copy the parent- file if it exist */
|
|
if ( is_defined (zp->conf->logdomaindir) )
|
|
lg_zone_end ();
|
|
return 0; /* nothing to do */
|
|
}
|
|
|
|
/* let's start signing the zone */
|
|
dbg_line ();
|
|
|
|
/* create new "dnskey.db" file */
|
|
pathname (path, sizeof (path), zp->dir, zp->conf->keyfile, NULL);
|
|
verbmesg (1, zp->conf, "\tWriting key file \"%s\"\n", path);
|
|
if ( !writekeyfile (path, zp->keys, zp->conf->key_ttl) )
|
|
{
|
|
error ("Can't create keyfile %s \n", path);
|
|
lg_mesg (LG_ERROR, "\"%s\": can't create keyfile %s", zp->zone , path);
|
|
}
|
|
|
|
err = 1;
|
|
use_unixtime = ( zp->conf->serialform == Unixtime );
|
|
dbg_val1 ("Use unixtime = %d\n", use_unixtime);
|
|
if ( !dynamic_zone && !use_unixtime ) /* increment serial number in static zone files */
|
|
{
|
|
pathname (path, sizeof (path), zp->dir, zp->file, NULL);
|
|
err = 0;
|
|
if ( noexec == 0 )
|
|
{
|
|
if ( (err = inc_serial (path, use_unixtime)) < 0 )
|
|
{
|
|
error ("could not increment serialno of domain %s in file %s: %s!\n",
|
|
zp->zone, path, inc_errstr (err));
|
|
lg_mesg (LG_ERROR,
|
|
"zone \"%s\": couldn't increment serialno in file %s: %s",
|
|
zp->zone, path, inc_errstr (err));
|
|
}
|
|
else
|
|
verbmesg (1, zp->conf, "\tIncrementing serial number in file \"%s\"\n", path);
|
|
}
|
|
else
|
|
verbmesg (1, zp->conf, "\tIncrementing serial number in file \"%s\"\n", path);
|
|
}
|
|
|
|
/* at last, sign the zone file */
|
|
if ( err >= 0 )
|
|
{
|
|
time_t timer;
|
|
|
|
verbmesg (1, zp->conf, "\tSigning zone \"%s\"\n", zp->zone);
|
|
logflush ();
|
|
|
|
/* dynamic zones uses incremental signing, so we have to */
|
|
/* prepare the old (signed) file as new input file */
|
|
if ( dynamic_zone )
|
|
{
|
|
char zfile[MAX_PATHSIZE+1];
|
|
|
|
dyn_update_freeze (zp->zone, zp->conf, 1); /* freeze dynamic zone ! */
|
|
|
|
pathname (zfile, sizeof (zfile), zp->dir, zp->file, NULL);
|
|
pathname (path, sizeof (path), zp->dir, zp->sfile, NULL);
|
|
if ( filesize (path) == 0L ) /* initial signing request ? */
|
|
{
|
|
verbmesg (1, zp->conf, "\tDynamic Zone signing: Initial signing request: Add DNSKEYs to zonefile\n");
|
|
copyfile (zfile, path, zp->conf->keyfile);
|
|
}
|
|
#if 1
|
|
else if ( zfile_time > zfilesig_time ) /* zone.db is newer than signed file */
|
|
{
|
|
verbmesg (1, zp->conf, "\tDynamic Zone signing: zone file manually edited: Use it as new input file\n");
|
|
copyfile (zfile, path, NULL);
|
|
}
|
|
#endif
|
|
verbmesg (1, zp->conf, "\tDynamic Zone signing: copy old signed zone file %s to new input file %s\n",
|
|
path, zfile);
|
|
|
|
if ( newkey ) /* if we have new keys, they should be added to the zone file */
|
|
{
|
|
copyzonefile (path, zfile, zp->conf->keyfile);
|
|
#if 0
|
|
if ( zp->conf->dist_cmd )
|
|
dist_and_reload (zp, 2); /* ... and send to the name server */
|
|
#endif
|
|
}
|
|
else /* else we can do a simple file copy */
|
|
copyfile (path, zfile, NULL);
|
|
}
|
|
|
|
timer = start_timer ();
|
|
if ( (err = sign_zone (zp)) < 0 )
|
|
{
|
|
error ("\tSigning of zone %s failed (%d)!\n", zp->zone, err);
|
|
lg_mesg (LG_ERROR, "\"%s\": signing failed!", zp->zone);
|
|
}
|
|
timer = stop_timer (timer);
|
|
|
|
if ( dynamic_zone )
|
|
dyn_update_freeze (zp->zone, zp->conf, 0); /* thaw dynamic zone file */
|
|
|
|
if ( err >= 0 )
|
|
{
|
|
const char *tstr = str_delspace (age2str (timer));
|
|
|
|
if ( !tstr || *tstr == '\0' )
|
|
tstr = "0s";
|
|
verbmesg (1, zp->conf, "\tSigning completed after %s.\n", tstr);
|
|
}
|
|
}
|
|
|
|
copy_keyset (zp->dir, zp->zone, zp->conf);
|
|
|
|
if ( err >= 0 && reloadflag )
|
|
{
|
|
if ( zp->conf->dist_cmd )
|
|
dist_and_reload (zp, 1);
|
|
else
|
|
reload_zone (zp->zone, zp->conf);
|
|
|
|
register_key (zp->keys, zp->conf);
|
|
}
|
|
|
|
if ( is_defined (zp->conf->logdomaindir) )
|
|
lg_zone_end ();
|
|
|
|
return err;
|
|
}
|
|
|
|
static void register_key (dki_t *list, const zconf_t *z)
|
|
{
|
|
dki_t *dkp;
|
|
time_t age;
|
|
|
|
time_t currtime;
|
|
assert ( list != NULL );
|
|
assert ( z != NULL );
|
|
|
|
currtime = time (NULL);
|
|
for ( dkp = list; dkp && dki_isksk (dkp); dkp = dkp->next )
|
|
{
|
|
age = dki_age (dkp, currtime);
|
|
#if 0
|
|
/* announce "new" and active key signing keys */
|
|
if ( REG_URL && *REG_URL && dki_status (dkp) == DKI_ACT && age <= z->resign * 4 )
|
|
{
|
|
if ( verbose )
|
|
logmesg ("\tRegister new KSK with tag %d for domain %s\n",
|
|
dkp->tag, dkp->name);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function is not working with symbolic links to keyset- files,
|
|
* because file_mtime() returns the mtime of the underlying file, and *not*
|
|
* that of the symlink file.
|
|
* This is bad, because the keyset-file will be newly generated by dnssec-signzone
|
|
* on every re-signing call.
|
|
* Instead, in the case of a hierarchical directory structure, we copy the file
|
|
* (and so we change the timestamp) only if it was modified after the last
|
|
* generation (checked with cmpfile(), see func sign_zone()).
|
|
*/
|
|
# define KEYSET_FILE_PFX "keyset-"
|
|
static int new_keysetfiles (const char *dir, time_t zone_signing_time)
|
|
{
|
|
DIR *dirp;
|
|
struct dirent *dentp;
|
|
char path[MAX_PATHSIZE+1];
|
|
int newkeysetfile;
|
|
|
|
if ( (dirp = opendir (dir)) == NULL )
|
|
return 0;
|
|
|
|
newkeysetfile = 0;
|
|
dbg_val2 ("new_keysetfile (%s, %s)\n", dir, time2str (zone_signing_time, 's'));
|
|
while ( !newkeysetfile && (dentp = readdir (dirp)) != NULL )
|
|
{
|
|
if ( strncmp (dentp->d_name, KEYSET_FILE_PFX, strlen (KEYSET_FILE_PFX)) != 0 )
|
|
continue;
|
|
|
|
pathname (path, sizeof (path), dir, dentp->d_name, NULL);
|
|
dbg_val2 ("newkeysetfile timestamp of %s = %s\n", path, time2str (file_mtime(path), 's'));
|
|
if ( file_mtime (path) > zone_signing_time )
|
|
newkeysetfile = 1;
|
|
}
|
|
closedir (dirp);
|
|
|
|
return newkeysetfile;
|
|
}
|
|
|
|
static int check_keydb_timestamp (dki_t *keylist, time_t reftime)
|
|
{
|
|
dki_t *key;
|
|
|
|
assert ( keylist != NULL );
|
|
if ( reftime == 0 )
|
|
return 1;
|
|
|
|
for ( key = keylist; key; key = key->next )
|
|
if ( dki_time (key) > reftime )
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int writekeyfile (const char *fname, const dki_t *list, int key_ttl)
|
|
{
|
|
FILE *fp;
|
|
const dki_t *dkp;
|
|
time_t curr = time (NULL);
|
|
int ksk;
|
|
|
|
if ( (fp = fopen (fname, "w")) == NULL )
|
|
return 0;
|
|
fprintf (fp, ";\n");
|
|
fprintf (fp, ";\t!!! Don\'t edit this file by hand.\n");
|
|
fprintf (fp, ";\t!!! It will be generated by %s.\n", progname);
|
|
fprintf (fp, ";\n");
|
|
fprintf (fp, ";\t Last generation time %s\n", time2str (curr, 's'));
|
|
fprintf (fp, ";\n");
|
|
|
|
fprintf (fp, "\n");
|
|
fprintf (fp, "; *** List of Key Signing Keys ***\n");
|
|
ksk = 1;
|
|
for ( dkp = list; dkp; dkp = dkp->next )
|
|
{
|
|
if ( ksk && !dki_isksk (dkp) )
|
|
{
|
|
fprintf (fp, "; *** List of Zone Signing Keys ***\n");
|
|
ksk = 0;
|
|
}
|
|
dki_prt_comment (dkp, fp);
|
|
dki_prt_dnskeyttl (dkp, fp, key_ttl);
|
|
putc ('\n', fp);
|
|
}
|
|
|
|
fclose (fp);
|
|
return 1;
|
|
}
|
|
|
|
static int sign_zone (const zone_t *zp)
|
|
{
|
|
char cmd[2047+1];
|
|
char str[1023+1];
|
|
char rparam[254+1];
|
|
char nsec3param[637+1];
|
|
char keysetdir[254+1];
|
|
const char *gends;
|
|
const char *dnskeyksk;
|
|
const char *pseudo;
|
|
const char *param;
|
|
int len;
|
|
FILE *fp;
|
|
|
|
const char *dir;
|
|
const char *domain;
|
|
const char *file;
|
|
const zconf_t *conf;
|
|
|
|
assert (zp != NULL);
|
|
dir = zp->dir;
|
|
domain = zp->zone;
|
|
file = zp->file;
|
|
conf = zp->conf;
|
|
|
|
len = 0;
|
|
str[0] = '\0';
|
|
if ( conf->lookaside && conf->lookaside[0] )
|
|
len = snprintf (str, sizeof (str), "-l %.250s", conf->lookaside);
|
|
|
|
dbg_line();
|
|
if ( !dynamic_zone && conf->serialform == Unixtime )
|
|
snprintf (str+len, sizeof (str) - len, " -N unixtime");
|
|
|
|
gends = "";
|
|
if ( conf->sig_gends )
|
|
gends = "-C -g ";
|
|
|
|
dnskeyksk = "";
|
|
if ( conf->sig_dnskeyksk )
|
|
dnskeyksk = "-x ";
|
|
|
|
pseudo = "";
|
|
if ( conf->sig_pseudo )
|
|
pseudo = "-p ";
|
|
|
|
param = "";
|
|
if ( conf->sig_param && conf->sig_param[0] )
|
|
param = conf->sig_param;
|
|
|
|
nsec3param[0] = '\0';
|
|
if ( conf->k_algo == DK_ALGO_NSEC3DSA || conf->k_algo == DK_ALGO_NSEC3RSASHA1 ||
|
|
conf->nsec3 != NSEC3_OFF )
|
|
{
|
|
char salt[510+1]; /* salt has a maximum of 255 bytes == 510 hex nibbles */
|
|
const char *update;
|
|
const char *optout;
|
|
unsigned int seed;
|
|
|
|
update = "-u "; /* trailing blank is necessary */
|
|
if ( conf->nsec3 == NSEC3_OPTOUT )
|
|
optout = "-A ";
|
|
else
|
|
optout = "";
|
|
|
|
/* static zones can use always a new salt (full zone signing) */
|
|
seed = 0L; /* no seed: use mechanism build in gensalt() */
|
|
if ( dynamic_zone )
|
|
{ /* dynamic zones have to reuse the salt on signing */
|
|
const dki_t *kp;
|
|
|
|
/* use gentime timestamp of ZSK for seeding rand generator */
|
|
kp = dki_find (zp->keys, DKI_ZSK, DKI_ACTIVE, 1);
|
|
assert ( kp != NULL );
|
|
if ( kp->gentime )
|
|
seed = kp->gentime;
|
|
else
|
|
seed = kp->time;
|
|
}
|
|
|
|
if ( gensalt (salt, sizeof (salt), conf->saltbits, seed) )
|
|
snprintf (nsec3param, sizeof (nsec3param), "%s%s-3 %s ", update, optout, salt);
|
|
}
|
|
|
|
dbg_line();
|
|
rparam[0] = '\0';
|
|
if ( conf->sig_random && conf->sig_random[0] )
|
|
snprintf (rparam, sizeof (rparam), "-r %.250s ", conf->sig_random);
|
|
|
|
dbg_line();
|
|
keysetdir[0] = '\0';
|
|
if ( conf->keysetdir && conf->keysetdir[0] && strcmp (conf->keysetdir, "..") != 0 )
|
|
snprintf (keysetdir, sizeof (keysetdir), "-d %.250s ", conf->keysetdir);
|
|
|
|
if ( dir == NULL || *dir == '\0' )
|
|
dir = ".";
|
|
|
|
dbg_line();
|
|
if ( dynamic_zone )
|
|
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",
|
|
dir, SIGNCMD, param, nsec3param, dnskeyksk, gends, pseudo, rparam, keysetdir, domain, conf->sigvalidity, str, file, file);
|
|
else
|
|
snprintf (cmd, sizeof (cmd), "cd %s; %s %s %s%s%s%s%s%s-o %s -e +%ld %s %s K*.private 2>&1",
|
|
dir, SIGNCMD, param, nsec3param, dnskeyksk, gends, pseudo, rparam, keysetdir, domain, conf->sigvalidity, str, file);
|
|
verbmesg (2, conf, "\t Run cmd \"%s\"\n", cmd);
|
|
*str = '\0';
|
|
if ( noexec == 0 )
|
|
{
|
|
#if 0
|
|
if ( (fp = popen (cmd, "r")) == NULL || fgets (str, sizeof str, fp) == NULL )
|
|
return -1;
|
|
#else
|
|
if ( (fp = popen (cmd, "r")) == NULL )
|
|
return -1;
|
|
str[0] = '\0';
|
|
while ( fgets (str, sizeof str, fp) != NULL ) /* eat up all output until the last line */
|
|
;
|
|
#endif
|
|
pclose (fp);
|
|
}
|
|
|
|
dbg_line();
|
|
verbmesg (2, conf, "\t Cmd dnssec-signzone return: \"%s\"\n", str_chop (str, '\n'));
|
|
len = strlen (str) - 6;
|
|
if ( len < 0 || strcmp (str+len, "signed") != 0 )
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void copy_keyset (const char *dir, const char *domain, const zconf_t *conf)
|
|
{
|
|
char fromfile[1024];
|
|
char tofile[1024];
|
|
int ret;
|
|
|
|
/* propagate "keyset"-file to parent dir */
|
|
if ( conf->keysetdir && strcmp (conf->keysetdir, "..") == 0 )
|
|
{
|
|
/* check if special parent-file exist (ksk rollover) */
|
|
snprintf (fromfile, sizeof (fromfile), "%s/parent-%s", dir, domain);
|
|
if ( !fileexist (fromfile) ) /* use "normal" keyset-file */
|
|
snprintf (fromfile, sizeof (fromfile), "%s/keyset-%s", dir, domain);
|
|
|
|
/* verbmesg (2, conf, "\t check \"%s\" against parent dir\n", fromfile); */
|
|
snprintf (tofile, sizeof (tofile), "%s/../keyset-%s", dir, domain);
|
|
if ( cmpfile (fromfile, tofile) != 0 )
|
|
{
|
|
verbmesg (2, conf, "\t copy \"%s\" to parent dir\n", fromfile);
|
|
if ( (ret = copyfile (fromfile, tofile, NULL)) != 0 )
|
|
{
|
|
error ("Couldn't copy \"%s\" to parent dir (%d:%s)\n",
|
|
fromfile, ret, strerror(errno));
|
|
lg_mesg (LG_ERROR, "\%s\": can't copy \"%s\" to parent dir (%d:%s)",
|
|
domain, fromfile, ret, strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
}
|