diff options
Diffstat (limited to 'contrib/zkt/dnssec-signer.c')
-rw-r--r-- | contrib/zkt/dnssec-signer.c | 1002 |
1 files changed, 1002 insertions, 0 deletions
diff --git a/contrib/zkt/dnssec-signer.c b/contrib/zkt/dnssec-signer.c new file mode 100644 index 0000000..5b2b8f6 --- /dev/null +++ b/contrib/zkt/dnssec-signer.c @@ -0,0 +1,1002 @@ +/***************************************************************** +** +** @(#) dnssec-signer.c (c) Jan 2005 Holger Zuleger hznet.de +** +** A wrapper around the BIND dnssec-signzone command which is able +** to resign a zone if neccessary and doing a zone or key signing key rollover. +** +** Copyright (c) 2005 - 2008, 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 "zone.h" +# include "dki.h" +# include "rollover.h" +# include "log.h" + +#if defined(BIND_VERSION) && BIND_VERSION >= 940 +# define short_options "c:L:V:D:N:o:O:dfHhnrv" +#else +# define short_options "c:L:V:D:N:o:O:fHhnrv" +#endif +#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'}, +#if defined(BIND_VERSION) && BIND_VERSION >= 940 + {"dynamic", no_argument, NULL, 'd' }, +#endif + {"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 char *dir, const char *domain, const char *file, const zconf_t *conf); +static int dyn_update_freeze (const char *domain, const zconf_t *z, int freeze); +static int reload_zone (const char *domain, const zconf_t *z); +static int dist_and_reload (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; +const char *viewname = NULL; +const char *logfile = NULL; +const char *origin = NULL; +const char *namedconf = NULL; +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; + +int main (int argc, char *const argv[]) +{ + int c; + int errcnt; + int opt_index; + char errstr[255+1]; + char *p; + const char *defconfname; + zone_t *zp; + + progname = *argv; + if ( (p = strrchr (progname, '/')) ) + progname = ++p; + viewname = getnameappendix (progname, "dnssec-signer"); + + defconfname = getdefconfname (viewname); + config = loadconfig ("", (zconf_t *)NULL); /* load built 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; +#if defined(BIND_VERSION) && BIND_VERSION >= 940 + case 'd': + dynamic_zone = 1; + /* dynamic zone requires a name server reload... */ + reloadflag = 0; /* ...but "rndc thaw" reloads the zone anyway */ + break; +#endif + case 'n': + noexec = 1; + break; + case 'r': + 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); + 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); + +#if defined(DBG) && DBG + for ( zp = zonelist; zp; zp = zp->next ) + zone_print ("in main: ", zp); +#endif + lg_args (LG_NOTICE, argc, argv); + + if ( origin ) /* option -o ? */ + { + if ( (argc - optind) <= 0 ) /* no arguments left ? */ + zone_readdir (".", origin, NULL, &zonelist, config, dynamic_zone); + else + zone_readdir (".", origin, argv[optind], &zonelist, config, dynamic_zone); + + /* anyway, "delete" all (remaining) arguments */ + optind = argc; + + /* complain if nothing could read in */ + if ( 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, 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 ? */ + { + if ( !parsedir (dirname, &zonelist, config) ) + fatal ("Can't read directory tree %s\n", dirname); + if ( zonelist == NULL ) + fatal ("No signed zone found in directory tree %s\n", dirname); + } + + /* none of the above: read current directory tree */ + if ( zonelist == NULL ) + parsedir (config->zonedir, &zonelist, config); + + 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\n", progname, ZKT_VERSION); + fprintf (stderr, "\n"); + + fprintf (stderr, "usage: %s [-c file] [-O optstr] ", progname); + fprintf (stderr, "[-D directorytree] "); + fprintf (stderr, "[-fhnr] [-v [-v]] [zone ...]\n"); + + fprintf (stderr, "usage: %s [-c file] [-O optstr] ", progname); + fprintf (stderr, "-N named.conf "); + fprintf (stderr, "[-fhnr] [-v [-v]] [zone ...]\n"); + + fprintf (stderr, "usage: %s [-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-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_dotfile (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 rfc5011 key signing keys, create new one if neccessary */ + 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 neccessary */ + 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)); + + /* if we work in subdir mode, check if there is a new keyset- file */ + newkeysetfile = 0; + if ( !newkey && zp->conf->keysetdir && strcmp (zp->conf->keysetdir, "..") == 0 ) + newkeysetfile = new_keysetfiles (zp->dir, zfilesig_time); + + /** + ** 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), "New zone key"); + 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))); + else if ( dynamic_zone ) + snprintf (mesg, sizeof(mesg), "dynamic zone"); + + 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) || dynamic_zone) ) + { + 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 */ + 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 defined(BIND_VERSION) && BIND_VERSION >= 940 + if ( !dynamic_zone && !use_unixtime ) /* increment serial no in static zone files */ +#else + if ( !dynamic_zone ) /* increment serial no in static zone files */ +#endif + { + 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); + } + 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); + else /* else we can do a simple file copy */ + copyfile (path, zfile, NULL); + } + + timer = start_timer (); + if ( (err = sign_zone (zp->dir, zp->zone, zp->file, zp->conf)) < 0 ) + { + error ("Signing 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 */ + + { + 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); + else + reload_zone (zp->zone, zp->conf); + + register_key (zp->keys, zp->conf); + } + + return err; +} + +static void register_key (dki_t *list, const zconf_t *z) +{ + dki_t *dkp; + time_t currtime; + time_t age; + + 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 char *dir, const char *domain, const char *file, const zconf_t *conf) +{ + char cmd[1023+1]; + char str[1023+1]; + char rparam[254+1]; + char keysetdir[254+1]; + const char *gends; + const char *pseudo; + const char *param; + int len; + FILE *fp; + + assert (conf != NULL); + assert (domain != NULL); + + len = 0; + str[0] = '\0'; + if ( conf->lookaside && conf->lookaside[0] ) + len = snprintf (str, sizeof (str), "-l %.250s", conf->lookaside); + + dbg_line(); +#if defined(BIND_VERSION) && BIND_VERSION >= 940 + if ( !dynamic_zone && conf->serialform == Unixtime ) + snprintf (str+len, sizeof (str) - len, " -N unixtime"); +#endif + + gends = ""; + if ( conf->sig_gends ) + gends = "-g "; + + pseudo = ""; + if ( conf->sig_pseudo ) + pseudo = "-p "; + + param = ""; + if ( conf->sig_param && conf->sig_param[0] ) + param = conf->sig_param; + + 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 defined(BIND_VERSION) && BIND_VERSION >= 940 + if ( dynamic_zone ) + snprintf (cmd, sizeof (cmd), "cd %s; %s %s %s%s%s%s-o %s -e +%d %s -N increment -f %s.dsigned %s K*.private", + dir, SIGNCMD, param, gends, pseudo, rparam, keysetdir, domain, conf->sigvalidity, str, file, file); + else +#endif + snprintf (cmd, sizeof (cmd), "cd %s; %s %s %s%s%s%s-o %s -e +%d %s %s K*.private", + dir, SIGNCMD, param, gends, pseudo, rparam, keysetdir, domain, conf->sigvalidity, str, file); + verbmesg (2, conf, "\t Run cmd \"%s\"\n", cmd); + *str = '\0'; + if ( noexec == 0 ) + { + if ( (fp = popen (cmd, "r")) == NULL || fgets (str, sizeof str, fp) == NULL ) + return -1; + pclose (fp); + } + + dbg_line(); + verbmesg (2, conf, "\t Cmd dnssec-signzone return: \"%s\"\n", str_chop (str, '\n')); + + 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)); + } + } + } +} + +static int dyn_update_freeze (const char *domain, const zconf_t *z, int freeze) +{ + char cmdline[254+1]; + char str[254+1]; + char *action; + FILE *fp; + + assert (z != NULL); + if ( freeze ) + action = "freeze"; + else + action = "thaw"; + + if ( z->view ) + snprintf (str, sizeof (str), "\"%s\" in view \"%s\"", domain, z->view); + else + snprintf (str, sizeof (str), "\"%s\"", domain); + + lg_mesg (LG_NOTICE, "%s: %s dynamic zone", str, action); + verbmesg (1, z, "\t%s dynamic zone %s\n", action, str); + + if ( z->view ) + snprintf (cmdline, sizeof (cmdline), "%s %s %s IN %s", RELOADCMD, action, domain, z->view); + else + snprintf (cmdline, sizeof (cmdline), "%s %s %s", RELOADCMD, action, domain); + + verbmesg (2, z, "\t Run cmd \"%s\"\n", cmdline); + *str = '\0'; + if ( noexec == 0 ) + { + if ( (fp = popen (cmdline, "r")) == NULL || fgets (str, sizeof str, fp) == NULL ) + return -1; + pclose (fp); + } + + verbmesg (2, z, "\t rndc %s return: \"%s\"\n", action, str_chop (str, '\n')); + + return 0; +} + +/***************************************************************** +** distribute and reload a zone via "distribute_command" +*****************************************************************/ +static int dist_and_reload (const zone_t *zp) +{ + char path[MAX_PATHSIZE+1]; + char cmdline[254+1]; + char zone[254+1]; + char str[254+1]; + FILE *fp; + + assert (zp != NULL); + assert (zp->conf->dist_cmd != NULL); + + if ( !is_exec_ok (zp->conf->dist_cmd) ) + { + char *mesg; + + if ( getuid () == 0 ) + mesg = "\tDistribution command %s not run as root\n"; + else + mesg = "\tDistribution command %s not run due to strange file mode settings\n"; + + verbmesg (1, zp->conf, mesg, zp->conf->dist_cmd); + lg_mesg (LG_ERROR, "exec of distribution command %s disabled due to security reasons", zp->conf->dist_cmd); + + return -1; + } + + if ( zp->conf->view ) + snprintf (zone, sizeof (zone), "\"%s\" in view \"%s\"", zp->zone, zp->conf->view); + else + snprintf (zone, sizeof (zone), "\"%s\"", zp->zone); + + + pathname (path, sizeof (path), zp->dir, zp->sfile, NULL); + + lg_mesg (LG_NOTICE, "%s: distribution triggered", zone); + verbmesg (1, zp->conf, "\tDistribute zone %s\n", zone); + if ( zp->conf->view ) + snprintf (cmdline, sizeof (cmdline), "%s distribute %s %s %s", zp->conf->dist_cmd, zp->zone, path, zp->conf->view); + else + snprintf (cmdline, sizeof (cmdline), "%s distribute %s %s", zp->conf->dist_cmd, zp->zone, path); + + *str = '\0'; + if ( noexec == 0 ) + { + verbmesg (2, zp->conf, "\t Run cmd \"%s\"\n", cmdline); + if ( (fp = popen (cmdline, "r")) == NULL || fgets (str, sizeof str, fp) == NULL ) + return -2; + pclose (fp); + verbmesg (2, zp->conf, "\t %s distribute return: \"%s\"\n", zp->conf->dist_cmd, str_chop (str, '\n')); + } + + + lg_mesg (LG_NOTICE, "%s: reload triggered", zone); + verbmesg (1, zp->conf, "\tReload zone %s\n", zone); + if ( zp->conf->view ) + snprintf (cmdline, sizeof (cmdline), "%s reload %s %s %s", zp->conf->dist_cmd, zp->zone, path, zp->conf->view); + else + snprintf (cmdline, sizeof (cmdline), "%s reload %s %s", zp->conf->dist_cmd, zp->zone, path); + + *str = '\0'; + if ( noexec == 0 ) + { + verbmesg (2, zp->conf, "\t Run cmd \"%s\"\n", cmdline); + if ( (fp = popen (cmdline, "r")) == NULL || fgets (str, sizeof str, fp) == NULL ) + return -2; + pclose (fp); + verbmesg (2, zp->conf, "\t %s reload return: \"%s\"\n", zp->conf->dist_cmd, str_chop (str, '\n')); + } + + return 0; +} + +/***************************************************************** +** reload a zone via "rndc" +*****************************************************************/ +static int reload_zone (const char *domain, const zconf_t *z) +{ + char cmdline[254+1]; + char str[254+1]; + FILE *fp; + + assert (z != NULL); + // fprintf (stderr, "reload_zone %d :%s: :%s:\n", z->verbosity, domain, z->view); + if ( z->view ) + snprintf (str, sizeof (str), "\"%s\" in view \"%s\"", domain, z->view); + else + snprintf (str, sizeof (str), "\"%s\"", domain); + + lg_mesg (LG_NOTICE, "%s: reload triggered", str); + verbmesg (1, z, "\tReload zone %s\n", str); + + if ( z->view ) + snprintf (cmdline, sizeof (cmdline), "%s reload %s IN %s", RELOADCMD, domain, z->view); + else + snprintf (cmdline, sizeof (cmdline), "%s reload %s", RELOADCMD, domain); + + *str = '\0'; + if ( noexec == 0 ) + { + verbmesg (2, z, "\t Run cmd \"%s\"\n", cmdline); + if ( (fp = popen (cmdline, "r")) == NULL || fgets (str, sizeof str, fp) == NULL ) + return -1; + pclose (fp); + verbmesg (2, z, "\t rndc reload return: \"%s\"\n", str_chop (str, '\n')); + } + + return 0; +} |