diff --git a/CHANGES b/CHANGES
index 58988d631a..d8cc4fc558 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,9 @@
4350. [contrib] Declare result in dlz_filesystem_dynamic.c.
+4348. [cleanup] Refactor dnssec-coverage and dnssec-checkds
+ functionality into an "isc" python module. [RT #39211]
+
--- 9.10.4 released ---
--- 9.10.4rc1 released ---
diff --git a/bin/dnssec/dnssec-settime.c b/bin/dnssec/dnssec-settime.c
index 67b868afad..4259be4bd9 100644
--- a/bin/dnssec/dnssec-settime.c
+++ b/bin/dnssec/dnssec-settime.c
@@ -472,11 +472,12 @@ main(int argc, char **argv) {
if ((setdel && setinact && del < inact) ||
(dst_key_gettime(key, DST_TIME_INACTIVE,
&previnact) == ISC_R_SUCCESS &&
- setdel && !setinact && del < previnact) ||
+ setdel && !setinact && !unsetinact && del < previnact) ||
(dst_key_gettime(key, DST_TIME_DELETE,
&prevdel) == ISC_R_SUCCESS &&
- setinact && !setdel && prevdel < inact) ||
- (!setdel && !setinact && prevdel < previnact))
+ setinact && !setdel && !unsetdel && prevdel < inact) ||
+ (!setdel && !unsetdel && !setinact && !unsetinact &&
+ prevdel < previnact))
fprintf(stderr, "%s: warning: Key is scheduled to "
"be deleted before it is\n\t"
"scheduled to be inactive.\n",
diff --git a/bin/python/.gitignore b/bin/python/.gitignore
index f770f2396e..f7bf29a43b 100644
--- a/bin/python/.gitignore
+++ b/bin/python/.gitignore
@@ -2,3 +2,4 @@ dnssec-checkds
dnssec-checkds.py
dnssec-coverage
dnssec-coverage.py
+*.pyc
diff --git a/bin/python/Makefile.in b/bin/python/Makefile.in
index dfb5d59711..cabadaccbc 100644
--- a/bin/python/Makefile.in
+++ b/bin/python/Makefile.in
@@ -12,8 +12,6 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
srcdir = @srcdir@
VPATH = @srcdir@
top_srcdir = @top_srcdir@
@@ -22,6 +20,8 @@ top_srcdir = @top_srcdir@
PYTHON = @PYTHON@
+SUBDIRS = isc
+
TARGETS = dnssec-checkds dnssec-coverage
PYSRCS = dnssec-checkds.py dnssec-coverage.py
@@ -49,8 +49,8 @@ installdirs:
$(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${mandir}/man8
install:: ${TARGETS} installdirs
- ${INSTALL_SCRIPT} dnssec-checkds@EXEEXT@ ${DESTDIR}${sbindir}
- ${INSTALL_SCRIPT} dnssec-coverage@EXEEXT@ ${DESTDIR}${sbindir}
+ ${INSTALL_SCRIPT} dnssec-checkds ${DESTDIR}${sbindir}
+ ${INSTALL_SCRIPT} dnssec-coverage ${DESTDIR}${sbindir}
${INSTALL_DATA} ${srcdir}/dnssec-checkds.8 ${DESTDIR}${mandir}/man8
${INSTALL_DATA} ${srcdir}/dnssec-coverage.8 ${DESTDIR}${mandir}/man8
diff --git a/bin/python/dnssec-checkds.py.in b/bin/python/dnssec-checkds.py.in
index 40b730ba07..79db6f1aa1 100644
--- a/bin/python/dnssec-checkds.py.in
+++ b/bin/python/dnssec-checkds.py.in
@@ -15,314 +15,13 @@
# PERFORMANCE OF THIS SOFTWARE.
############################################################################
-import argparse
-import pprint
import os
+import sys
-prog='dnssec-checkds'
+sys.path.insert(0, os.path.dirname(sys.argv[0]))
+sys.path.insert(1, os.path.join('@prefix@', 'lib'))
-# These routines permit platform-independent location of BIND 9 tools
-if os.name == 'nt':
- import win32con
- import win32api
-
-def prefix(bindir = ''):
- if os.name != 'nt':
- return os.path.join('@prefix@', bindir)
-
- bind_subkey = "Software\\ISC\\BIND"
- hKey = None
- keyFound = True
- try:
- hKey = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, bind_subkey)
- except:
- keyFound = False
- if keyFound:
- try:
- (namedBase, _) = win32api.RegQueryValueEx(hKey, "InstallDir")
- except:
- keyFound = False
- win32api.RegCloseKey(hKey)
- if keyFound:
- return os.path.join(namedBase, bindir)
- return os.path.join(win32api.GetSystemDirectory(), bindir)
-
-def shellquote(s):
- if os.name == 'nt':
- return '"' + s.replace('"', '"\\"') + '"'
- return "'" + s.replace("'", "'\\''") + "'"
-
-############################################################################
-# DSRR class:
-# Delegation Signer (DS) resource record
-############################################################################
-class DSRR:
- hashalgs = {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384' }
- rrname=''
- rrclass='IN'
- rrtype='DS'
- keyid=None
- keyalg=None
- hashalg=None
- digest=''
- ttl=0
-
- def __init__(self, rrtext):
- if not rrtext:
- return
-
- fields = rrtext.split()
- if len(fields) < 7:
- return
-
- self.rrname = fields[0].lower()
- fields = fields[1:]
- if fields[0].upper() in ['IN','CH','HS']:
- self.rrclass = fields[0].upper()
- fields = fields[1:]
- else:
- self.ttl = int(fields[0])
- self.rrclass = fields[1].upper()
- fields = fields[2:]
-
- if fields[0].upper() != 'DS':
- raise Exception
-
- self.rrtype = 'DS'
- self.keyid = int(fields[1])
- self.keyalg = int(fields[2])
- self.hashalg = int(fields[3])
- self.digest = ''.join(fields[4:]).upper()
-
- def __repr__(self):
- return('%s %s %s %d %d %d %s' %
- (self.rrname, self.rrclass, self.rrtype, self.keyid,
- self.keyalg, self.hashalg, self.digest))
-
- def __eq__(self, other):
- return self.__repr__() == other.__repr__()
-
-############################################################################
-# DLVRR class:
-# DNSSEC Lookaside Validation (DLV) resource record
-############################################################################
-class DLVRR:
- hashalgs = {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384' }
- parent=''
- dlvname=''
- rrname='IN'
- rrclass='IN'
- rrtype='DLV'
- keyid=None
- keyalg=None
- hashalg=None
- digest=''
- ttl=0
-
- def __init__(self, rrtext, dlvname):
- if not rrtext:
- return
-
- fields = rrtext.split()
- if len(fields) < 7:
- return
-
- self.dlvname = dlvname.lower()
- parent = fields[0].lower().strip('.').split('.')
- parent.reverse()
- dlv = dlvname.split('.')
- dlv.reverse()
- while len(dlv) != 0 and len(parent) != 0 and parent[0] == dlv[0]:
- parent = parent[1:]
- dlv = dlv[1:]
- if len(dlv) != 0:
- raise Exception
- parent.reverse()
- self.parent = '.'.join(parent)
- self.rrname = self.parent + '.' + self.dlvname + '.'
-
- fields = fields[1:]
- if fields[0].upper() in ['IN','CH','HS']:
- self.rrclass = fields[0].upper()
- fields = fields[1:]
- else:
- self.ttl = int(fields[0])
- self.rrclass = fields[1].upper()
- fields = fields[2:]
-
- if fields[0].upper() != 'DLV':
- raise Exception
-
- self.rrtype = 'DLV'
- self.keyid = int(fields[1])
- self.keyalg = int(fields[2])
- self.hashalg = int(fields[3])
- self.digest = ''.join(fields[4:]).upper()
-
- def __repr__(self):
- return('%s %s %s %d %d %d %s' %
- (self.rrname, self.rrclass, self.rrtype,
- self.keyid, self.keyalg, self.hashalg, self.digest))
-
- def __eq__(self, other):
- return self.__repr__() == other.__repr__()
-
-############################################################################
-# checkds:
-# Fetch DS RRset for the given zone from the DNS; fetch DNSKEY
-# RRset from the masterfile if specified, or from DNS if not.
-# Generate a set of expected DS records from the DNSKEY RRset,
-# and report on congruency.
-############################################################################
-def checkds(zone, masterfile = None):
- dslist=[]
- fp=os.popen("%s +noall +answer -t ds -q %s" %
- (shellquote(args.dig), shellquote(zone)))
- for line in fp:
- dslist.append(DSRR(line))
- dslist = sorted(dslist, key=lambda ds: (ds.keyid, ds.keyalg, ds.hashalg))
- fp.close()
-
- dsklist=[]
-
- if masterfile:
- fp = os.popen("%s -f %s %s " %
- (shellquote(args.dsfromkey), shellquote(masterfile),
- shellquote(zone)))
- else:
- fp = os.popen("%s +noall +answer -t dnskey -q %s | %s -f - %s" %
- (shellquote(args.dig), shellquote(zone),
- shellquote(args.dsfromkey), shellquote(zone)))
-
- for line in fp:
- dsklist.append(DSRR(line))
-
- fp.close()
-
- if (len(dsklist) < 1):
- print ("No DNSKEY records found in zone apex")
- return False
-
- found = False
- for ds in dsklist:
- if ds in dslist:
- print ("DS for KSK %s/%03d/%05d (%s) found in parent" %
- (ds.rrname.strip('.'), ds.keyalg,
- ds.keyid, DSRR.hashalgs[ds.hashalg]))
- found = True
- else:
- print ("DS for KSK %s/%03d/%05d (%s) missing from parent" %
- (ds.rrname.strip('.'), ds.keyalg,
- ds.keyid, DSRR.hashalgs[ds.hashalg]))
-
- if not found:
- print ("No DS records were found for any DNSKEY")
-
- return found
-
-############################################################################
-# checkdlv:
-# Fetch DLV RRset for the given zone from the DNS; fetch DNSKEY
-# RRset from the masterfile if specified, or from DNS if not.
-# Generate a set of expected DLV records from the DNSKEY RRset,
-# and report on congruency.
-############################################################################
-def checkdlv(zone, lookaside, masterfile = None):
- dlvlist=[]
- fp=os.popen("%s +noall +answer -t dlv -q %s" %
- (shellquote(args.dig), shellquote(zone + '.' + lookaside)))
- for line in fp:
- dlvlist.append(DLVRR(line, lookaside))
- dlvlist = sorted(dlvlist,
- key=lambda dlv: (dlv.keyid, dlv.keyalg, dlv.hashalg))
- fp.close()
-
- #
- # Fetch DNSKEY records from DNS and generate DLV records from them
- #
- dlvklist=[]
- if masterfile:
- fp = os.popen("%s -f %s -l %s %s " %
- (args.dsfromkey, masterfile, lookaside, zone))
- else:
- fp = os.popen("%s +noall +answer -t dnskey %s | %s -f - -l %s %s"
- % (shellquote(args.dig), shellquote(zone),
- shellquote(args.dsfromkey), shellquote(lookaside),
- shellquote(zone)))
-
- for line in fp:
- dlvklist.append(DLVRR(line, lookaside))
-
- fp.close()
-
- if (len(dlvklist) < 1):
- print ("No DNSKEY records found in zone apex")
- return False
-
- found = False
- for dlv in dlvklist:
- if dlv in dlvlist:
- print ("DLV for KSK %s/%03d/%05d (%s) found in %s" %
- (dlv.parent, dlv.keyalg, dlv.keyid,
- DLVRR.hashalgs[dlv.hashalg], dlv.dlvname))
- found = True
- else:
- print ("DLV for KSK %s/%03d/%05d (%s) missing from %s" %
- (dlv.parent, dlv.keyalg, dlv.keyid,
- DLVRR.hashalgs[dlv.hashalg], dlv.dlvname))
-
- if not found:
- print ("No DLV records were found for any DNSKEY")
-
- return found
-
-
-############################################################################
-# parse_args:
-# Read command line arguments, set global 'args' structure
-############################################################################
-def parse_args():
- global args
- parser = argparse.ArgumentParser(description=prog + ': checks DS coverage')
-
- bindir = 'bin'
- if os.name == 'nt':
- sbindir = 'bin'
- else:
- sbindir = 'sbin'
-
- parser.add_argument('zone', type=str, help='zone to check')
- parser.add_argument('-f', '--file', dest='masterfile', type=str,
- help='zone master file')
- parser.add_argument('-l', '--lookaside', dest='lookaside', type=str,
- help='DLV lookaside zone')
- parser.add_argument('-d', '--dig', dest='dig',
- default=os.path.join(prefix(bindir), 'dig'),
- type=str, help='path to \'dig\'')
- parser.add_argument('-D', '--dsfromkey', dest='dsfromkey',
- default=os.path.join(prefix(sbindir),
- 'dnssec-dsfromkey'),
- type=str, help='path to \'dig\'')
- parser.add_argument('-v', '--version', action='version',
- version='@BIND9_VERSION@')
- args = parser.parse_args()
-
- args.zone = args.zone.strip('.')
- if args.lookaside:
- lookaside = args.lookaside.strip('.')
-
-############################################################################
-# Main
-############################################################################
-def main():
- parse_args()
-
- if args.lookaside:
- found = checkdlv(args.zone, args.lookaside, args.masterfile)
- else:
- found = checkds(args.zone, args.masterfile)
-
- exit(0 if found else 1)
+import isc.checkds
if __name__ == "__main__":
- main()
+ isc.checkds.main()
diff --git a/bin/python/dnssec-coverage.docbook b/bin/python/dnssec-coverage.docbook
index 45d5fa86d1..892801ecd0 100644
--- a/bin/python/dnssec-coverage.docbook
+++ b/bin/python/dnssec-coverage.docbook
@@ -56,7 +56,7 @@
- zone
+ zone
@@ -151,10 +151,15 @@
'd' for days, 'w' for weeks, 'mo' for months, 'y' for years.
- This option is mandatory unless the has
- been used to specify a zone file. (If has
+ This option is not necessary if the has
+ been used to specify a zone file. If has
been specified, this option may still be used; it will override
- the value found in the file.)
+ the value found in the file.
+
+
+ If this option is not used and the maximum TTL cannot be retrieved
+ from a zone file, a warning is generated and a default value of
+ 1 week is used.
@@ -166,11 +171,10 @@
Sets the value to be used as the DNSKEY TTL for the zone or
zones being analyzed when determining whether there is a
possibility of validation failure. When a key is rolled (that
- is, replaced with a new key), there must be enough time
- for the old DNSKEY RRset to have expired from resolver caches
- before the new key is activated and begins generating
- signatures. If that condition does not apply, a warning
- will be generated.
+ is, replaced with a new key), there must be enough time for the
+ old DNSKEY RRset to have expired from resolver caches before
+ the new key is activated and begins generating signatures. If
+ that condition does not apply, a warning will be generated.
The length of the TTL can be set in seconds, or in larger units
@@ -178,12 +182,18 @@
'd' for days, 'w' for weeks, 'mo' for months, 'y' for years.
- This option is mandatory unless the has
- been used to specify a zone file, or a default key TTL was
- set with the to
- dnssec-keygen. (If either of those is true,
- this option may still be used; it will override the value found
- in the zone or key file.)
+ This option is not necessary if has
+ been used to specify a zone file from which the TTL
+ of the DNSKEY RRset can be read, or if a default key TTL was
+ set using ith the to
+ dnssec-keygen. If either of those is true,
+ this option may still be used; it will override the values
+ found in the zone file or the key file.
+
+
+ If this option is not used and the key TTL cannot be retrieved
+ from the zone file or the key file, then a warning is generated
+ and a default value of 1 day is used.
diff --git a/bin/python/dnssec-coverage.py.in b/bin/python/dnssec-coverage.py.in
old mode 100755
new mode 100644
index ccd01a9cc1..bbd8629d58
--- a/bin/python/dnssec-coverage.py.in
+++ b/bin/python/dnssec-coverage.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
############################################################################
-# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -15,785 +15,13 @@
# PERFORMANCE OF THIS SOFTWARE.
############################################################################
-import argparse
import os
-import glob
import sys
-import re
-import time
-import calendar
-from collections import defaultdict
-import pprint
-prog='dnssec-coverage'
+sys.path.insert(0, os.path.dirname(sys.argv[0]))
+sys.path.insert(1, os.path.join('@prefix@', 'lib'))
-# These routines permit platform-independent location of BIND 9 tools
-if os.name == 'nt':
- import win32con
- import win32api
-
-def prefix(bindir = ''):
- if os.name != 'nt':
- return os.path.join('@prefix@', bindir)
-
- bind_subkey = "Software\\ISC\\BIND"
- hKey = None
- keyFound = True
- try:
- hKey = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, bind_subkey)
- except:
- keyFound = False
- if keyFound:
- try:
- (namedBase, _) = win32api.RegQueryValueEx(hKey, "InstallDir")
- except:
- keyFound = False
- win32api.RegCloseKey(hKey)
- if keyFound:
- return os.path.join(namedBase, bindir)
- return os.path.join(win32api.GetSystemDirectory(), bindir)
-
-########################################################################
-# Class Event
-########################################################################
-class Event:
- """ A discrete key metadata event, e.g., Publish, Activate, Inactive,
- Delete. Stores the date of the event, and identifying information about
- the key to which the event will occur."""
-
- def __init__(self, _what, _key):
- now = time.time()
- self.what = _what
- self.when = _key.metadata[_what]
- self.key = _key
- self.keyid = _key.keyid
- self.sep = _key.sep
- self.zone = _key.zone
- self.alg = _key.alg
-
- def __repr__(self):
- return repr((self.when, self.what, self.keyid, self.sep,
- self.zone, self.alg))
-
- def showtime(self):
- return time.strftime("%a %b %d %H:%M:%S UTC %Y", self.when)
-
- def showkey(self):
- return self.key.showkey()
-
- def showkeytype(self):
- return self.key.showkeytype()
-
-########################################################################
-# Class Key
-########################################################################
-class Key:
- """An individual DNSSEC key. Identified by path, zone, algorithm, keyid.
- Contains a dictionary of metadata events."""
-
- def __init__(self, keyname):
- directory = os.path.dirname(keyname)
- key = os.path.basename(keyname)
- (zone, alg, keyid) = key.split('+')
- keyid = keyid.split('.')[0]
- key = [zone, alg, keyid]
- key_file = directory + os.sep + '+'.join(key) + ".key"
- private_file = directory + os.sep + '+'.join(key) + ".private"
-
- self.zone = zone[1:-1]
- self.alg = int(alg)
- self.keyid = int(keyid)
-
- kfp = open(key_file, "r")
- for line in kfp:
- if line[0] == ';':
- continue
- tokens = line.split()
- if not tokens:
- continue
-
- if tokens[1].lower() in ('in', 'ch', 'hs'):
- septoken = 3
- self.ttl = args.keyttl
- if not self.ttl:
- vspace()
- print("WARNING: Unable to determine TTL for DNSKEY %s." %
- self.showkey())
- print("\t Using 1 day (86400 seconds); re-run with the -d "
- "option for more\n\t accurate results.")
- self.ttl = 86400
- else:
- septoken = 4
- self.ttl = int(tokens[1]) if not args.keyttl else args.keyttl
-
- if (int(tokens[septoken]) & 0x1) == 1:
- self.sep = True
- else:
- self.sep = False
- kfp.close()
-
- pfp = open(private_file, "rU")
- propDict = dict()
- for propLine in pfp:
- propDef = propLine.strip()
- if len(propDef) == 0:
- continue
- if propDef[0] in ('!', '#'):
- continue
- punctuation = [propDef.find(c) for c in ':= '] + [len(propDef)]
- found = min([ pos for pos in punctuation if pos != -1 ])
- name = propDef[:found].rstrip()
- value = propDef[found:].lstrip(":= ").rstrip()
- propDict[name] = value
-
- if("Publish" in propDict):
- propDict["Publish"] = time.strptime(propDict["Publish"],
- "%Y%m%d%H%M%S")
-
- if("Activate" in propDict):
- propDict["Activate"] = time.strptime(propDict["Activate"],
- "%Y%m%d%H%M%S")
-
- if("Inactive" in propDict):
- propDict["Inactive"] = time.strptime(propDict["Inactive"],
- "%Y%m%d%H%M%S")
-
- if("Delete" in propDict):
- propDict["Delete"] = time.strptime(propDict["Delete"],
- "%Y%m%d%H%M%S")
-
- if("Revoke" in propDict):
- propDict["Revoke"] = time.strptime(propDict["Revoke"],
- "%Y%m%d%H%M%S")
- pfp.close()
- self.metadata = propDict
-
- def showkey(self):
- return "%s/%03d/%05d" % (self.zone, self.alg, self.keyid);
-
- def showkeytype(self):
- return ("KSK" if self.sep else "ZSK")
-
- # ensure that the gap between Publish and Activate is big enough
- def check_prepub(self):
- now = time.time()
-
- if (not "Activate" in self.metadata):
- debug_print("No Activate information in key: %s" % self.showkey())
- return False
- a = calendar.timegm(self.metadata["Activate"])
-
- if (not "Publish" in self.metadata):
- debug_print("No Publish information in key: %s" % self.showkey())
- if a > now:
- vspace()
- print("WARNING: Key %s (%s) is scheduled for activation but \n"
- "\t not for publication." %
- (self.showkey(), self.showkeytype()))
- return False
- p = calendar.timegm(self.metadata["Publish"])
-
- now = time.time()
- if p < now and a < now:
- return True
-
- if p == a:
- vspace()
- print ("WARNING: %s (%s) is scheduled to be published and\n"
- "\t activated at the same time. This could result in a\n"
- "\t coverage gap if the zone was previously signed." %
- (self.showkey(), self.showkeytype()))
- print("\t Activation should be at least %s after publication."
- % duration(self.ttl))
- return True
-
- if a < p:
- vspace()
- print("WARNING: Key %s (%s) is active before it is published" %
- (self.showkey(), self.showkeytype()))
- return False
-
- if (a - p < self.ttl):
- vspace()
- print("WARNING: Key %s (%s) is activated too soon after\n"
- "\t publication; this could result in coverage gaps due to\n"
- "\t resolver caches containing old data."
- % (self.showkey(), self.showkeytype()))
- print("\t Activation should be at least %s after publication." %
- duration(self.ttl))
- return False
-
- return True
-
- # ensure that the gap between Inactive and Delete is big enough
- def check_postpub(self, timespan = None):
- if not timespan:
- timespan = self.ttl
-
- now = time.time()
-
- if (not "Delete" in self.metadata):
- debug_print("No Delete information in key: %s" % self.showkey())
- return False
- d = calendar.timegm(self.metadata["Delete"])
-
- if (not "Inactive" in self.metadata):
- debug_print("No Inactive information in key: %s" % self.showkey())
- if d > now:
- vspace()
- print("WARNING: Key %s (%s) is scheduled for deletion but\n"
- "\t not for inactivation." %
- (self.showkey(), self.showkeytype()))
- return False
- i = calendar.timegm(self.metadata["Inactive"])
-
- if d < now and i < now:
- return True
-
- if (d < i):
- vspace()
- print("WARNING: Key %s (%s) is scheduled for deletion before\n"
- "\t inactivation." % (self.showkey(), self.showkeytype()))
- return False
-
- if (d - i < timespan):
- vspace()
- print("WARNING: Key %s (%s) scheduled for deletion too soon after\n"
- "\t deactivation; this may result in coverage gaps due to\n"
- "\t resolver caches containing old data."
- % (self.showkey(), self.showkeytype()))
- print("\t Deletion should be at least %s after inactivation." %
- duration(timespan))
- return False
-
- return True
-
-########################################################################
-# class Zone
-########################################################################
-class Zone:
- """Stores data about a specific zone"""
-
- def __init__(self, _name, _keyttl = None, _maxttl = None):
- self.name = _name
- self.keyttl = _keyttl
- self.maxttl = _maxttl
-
- def load(self, filename):
- if not args.compilezone:
- sys.stderr.write(prog + ': FATAL: "named-compilezone" not found\n')
- exit(1)
-
- if not self.name:
- return
-
- maxttl = keyttl = None
-
- fp = os.popen("%s -o - %s %s 2> /dev/null" %
- (args.compilezone, self.name, filename))
- for line in fp:
- fields = line.split()
- if not maxttl or int(fields[1]) > maxttl:
- maxttl = int(fields[1])
- if fields[3] == "DNSKEY":
- keyttl = int(fields[1])
- fp.close()
-
- self.keyttl = keyttl
- self.maxttl = maxttl
-
-############################################################################
-# debug_print:
-############################################################################
-def debug_print(debugVar):
- """pretty print a variable iff debug mode is enabled"""
- if not args.debug_mode:
- return
- if type(debugVar) == str:
- print("DEBUG: " + debugVar)
- else:
- print("DEBUG: " + pprint.pformat(debugVar))
- return
-
-############################################################################
-# vspace:
-############################################################################
-_firstline = True
-def vspace():
- """adds vertical space between two sections of output text if and only
- if this is *not* the first section being printed"""
- global _firstline
- if _firstline:
- _firstline = False
- else:
- print('')
-
-############################################################################
-# vreset:
-############################################################################
-def vreset():
- """reset vertical spacing"""
- global _firstline
- _firstline = True
-
-############################################################################
-# getunit
-############################################################################
-def getunit(secs, size):
- """given a number of seconds, and a number of seconds in a larger unit of
- time, calculate how many of the larger unit there are and return both
- that and a remainder value"""
- bigunit = secs // size
- if bigunit:
- secs %= size
- return (bigunit, secs)
-
-############################################################################
-# addtime
-############################################################################
-def addtime(output, unit, t):
- """add a formatted unit of time to an accumulating string"""
- if t:
- output += ("%s%d %s%s" %
- ((", " if output else ""),
- t, unit, ("s" if t > 1 else "")))
-
- return output
-
-############################################################################
-# duration:
-############################################################################
-def duration(secs):
- """given a length of time in seconds, print a formatted human duration
- in larger units of time
- """
- # define units:
- minute = 60
- hour = minute * 60
- day = hour * 24
- month = day * 30
- year = day * 365
-
- # calculate time in units:
- (years, secs) = getunit(secs, year)
- (months, secs) = getunit(secs, month)
- (days, secs) = getunit(secs, day)
- (hours, secs) = getunit(secs, hour)
- (minutes, secs) = getunit(secs, minute)
-
- output = ''
- output = addtime(output, "year", years)
- output = addtime(output, "month", months)
- output = addtime(output, "day", days)
- output = addtime(output, "hour", hours)
- output = addtime(output, "minute", minutes)
- output = addtime(output, "second", secs)
- return output
-
-############################################################################
-# parse_time
-############################################################################
-def parse_time(s):
- """convert a formatted time (e.g., 1y, 6mo, 15mi, etc) into seconds"""
- s = s.strip()
-
- # if s is an integer, we're done already
- try:
- n = int(s)
- return n
- except:
- pass
-
- # try to parse as a number with a suffix indicating unit of time
- r = re.compile('([0-9][0-9]*)\s*([A-Za-z]*)')
- m = r.match(s)
- if not m:
- raise Exception("Cannot parse %s" % s)
- (n, unit) = m.groups()
- n = int(n)
- unit = unit.lower()
- if unit[0] == 'y':
- return n * 31536000
- elif unit[0] == 'm' and unit[1] == 'o':
- return n * 2592000
- elif unit[0] == 'w':
- return n * 604800
- elif unit[0] == 'd':
- return n * 86400
- elif unit[0] == 'h':
- return n * 3600
- elif unit[0] == 'm' and unit[1] == 'i':
- return n * 60
- elif unit[0] == 's':
- return n
- else:
- raise Exception("Invalid suffix %s" % unit)
-
-############################################################################
-# algname:
-############################################################################
-def algname(alg):
- """return the mnemonic for a DNSSEC algorithm"""
- names = (None, 'RSAMD5', 'DH', 'DSA', 'ECC', 'RSASHA1',
- 'NSEC3DSA', 'NSEC3RSASHA1', 'RSASHA256', None,
- 'RSASHA512', None, 'ECCGOST', 'ECDSAP256SHA256',
- 'ECDSAP384SHA384')
- name = None
- if alg in range(len(names)):
- name = names[alg]
- return (name if name else str(alg))
-
-############################################################################
-# list_events:
-############################################################################
-def list_events(eventgroup):
- """print a list of the events in an eventgroup"""
- if not eventgroup:
- return
- print (" " + eventgroup[0].showtime() + ":")
- for event in eventgroup:
- print (" %s: %s (%s)" %
- (event.what, event.showkey(), event.showkeytype()))
-
-############################################################################
-# process_events:
-############################################################################
-def process_events(eventgroup, active, published):
- """go through the events in an event group in time-order, add to active
- list upon Activate event, add to published list upon Publish event,
- remove from active list upon Inactive event, and remove from published
- upon Delete event. Emit warnings when inconsistant states are reached"""
- for event in eventgroup:
- if event.what == "Activate":
- active.add(event.keyid)
- elif event.what == "Publish":
- published.add(event.keyid)
- elif event.what == "Inactive":
- if event.keyid not in active:
- vspace()
- print ("\tWARNING: %s (%s) scheduled to become inactive "
- "before it is active" %
- (event.showkey(), event.showkeytype()))
- else:
- active.remove(event.keyid)
- elif event.what == "Delete":
- if event.keyid in published:
- published.remove(event.keyid)
- else:
- vspace()
- print ("WARNING: key %s (%s) is scheduled for deletion before "
- "it is published, at %s" %
- (event.showkey(), event.showkeytype()))
- elif event.what == "Revoke":
- # We don't need to worry about the logic of this one;
- # just stop counting this key as either active or published
- if event.keyid in published:
- published.remove(event.keyid)
- if event.keyid in active:
- active.remove(event.keyid)
-
- return (active, published)
-
-############################################################################
-# check_events:
-############################################################################
-def check_events(eventsList, ksk):
- """create lists of events happening at the same time, check for
- inconsistancies"""
- active = set()
- published = set()
- eventgroups = list()
- eventgroup = list()
- keytype = ("KSK" if ksk else "ZSK")
-
- # collect up all events that have the same time
- eventsfound = False
- for event in eventsList:
- # if checking ZSKs, skip KSKs, and vice versa
- if (ksk and not event.sep) or (event.sep and not ksk):
- continue
-
- # we found an appropriate (ZSK or KSK event)
- eventsfound = True
-
- # add event to current eventgroup
- if (not eventgroup or eventgroup[0].when == event.when):
- eventgroup.append(event)
-
- # if we're at the end of the list, we're done. if
- # we've found an event with a later time, start a new
- # eventgroup
- if (eventgroup[0].when != event.when):
- eventgroups.append(eventgroup)
- eventgroup = list()
- eventgroup.append(event)
-
- if eventgroup:
- eventgroups.append(eventgroup)
-
- for eventgroup in eventgroups:
- if (args.checklimit and
- calendar.timegm(eventgroup[0].when) > args.checklimit):
- print("Ignoring events after %s" %
- time.strftime("%a %b %d %H:%M:%S UTC %Y",
- time.gmtime(args.checklimit)))
- return True
-
- (active, published) = \
- process_events(eventgroup, active, published)
-
- list_events(eventgroup)
-
- # and then check for inconsistencies:
- if len(active) == 0:
- print ("ERROR: No %s's are active after this event" % keytype)
- return False
- elif len(published) == 0:
- sys.stdout.write("ERROR: ")
- print ("ERROR: No %s's are published after this event" % keytype)
- return False
- elif len(published.intersection(active)) == 0:
- sys.stdout.write("ERROR: ")
- print (("ERROR: No %s's are both active and published " +
- "after this event") % keytype)
- return False
-
- if not eventsfound:
- print ("ERROR: No %s events found in '%s'" %
- (keytype, args.path))
- return False
-
- return True
-
-############################################################################
-# check_zones:
-# ############################################################################
-def check_zones(eventsList):
- """scan events per zone, algorithm, and key type, in order of occurrance,
- noting inconsistent states when found"""
- global foundprob
-
- foundprob = False
- zonesfound = False
- for zone in eventsList:
- if args.zone and zone != args.zone:
- continue
-
- zonesfound = True
- for alg in eventsList[zone]:
- if not args.no_ksk:
- vspace()
- print("Checking scheduled KSK events for zone %s, algorithm %s..." %
- (zone, algname(alg)))
- if not check_events(eventsList[zone][alg], True):
- foundprob = True
- else:
- print ("No errors found")
-
- if not args.no_zsk:
- vspace()
- print("Checking scheduled ZSK events for zone %s, algorithm %s..." %
- (zone, algname(alg)))
- if not check_events(eventsList[zone][alg], False):
- foundprob = True
- else:
- print ("No errors found")
-
- if not zonesfound:
- print("ERROR: No key events found for %s in '%s'" %
- (args.zone, args.path))
- exit(1)
-
-############################################################################
-# fill_eventsList:
-############################################################################
-def fill_eventsList(eventsList):
- """populate the list of events"""
- for zone, algorithms in keyDict.items():
- for alg, keys in algorithms.items():
- for keyid, keydata in keys.items():
- if("Publish" in keydata.metadata):
- eventsList[zone][alg].append(Event("Publish", keydata))
- if("Activate" in keydata.metadata):
- eventsList[zone][alg].append(Event("Activate", keydata))
- if("Inactive" in keydata.metadata):
- eventsList[zone][alg].append(Event("Inactive", keydata))
- if("Delete" in keydata.metadata):
- eventsList[zone][alg].append(Event("Delete", keydata))
-
- eventsList[zone][alg] = sorted(eventsList[zone][alg],
- key=lambda event: event.when)
-
- foundprob = False
- if not keyDict:
- print("ERROR: No key events found in '%s'" % args.path)
- exit(1)
-
-############################################################################
-# set_path:
-############################################################################
-def set_path(command, default=None):
- """find the location of a specified command. if a default is supplied
- and it works, we use it; otherwise we search PATH for a match. If
- not found, error and exit"""
- fpath = default
- if not fpath or not os.path.isfile(fpath) or not os.access(fpath, os.X_OK):
- path = os.environ["PATH"]
- if not path:
- path = os.path.defpath
- for directory in path.split(os.pathsep):
- fpath = directory + os.sep + command
- if os.path.isfile(fpath) or os.access(fpath, os.X_OK):
- break
- fpath = None
-
- return fpath
-
-############################################################################
-# parse_args:
-############################################################################
-def parse_args():
- """Read command line arguments, set global 'args' structure"""
- global args
- compilezone = set_path('named-compilezone',
- os.path.join(prefix('bin'), 'named-compilezone'))
-
- parser = argparse.ArgumentParser(description=prog + ': checks future ' +
- 'DNSKEY coverage for a zone')
-
- parser.add_argument('zone', type=str, help='zone to check')
- parser.add_argument('-K', dest='path', default='.', type=str,
- help='a directory containing keys to process',
- metavar='dir')
- parser.add_argument('-f', dest='filename', type=str,
- help='zone master file', metavar='file')
- parser.add_argument('-m', dest='maxttl', type=str,
- help='the longest TTL in the zone(s)',
- metavar='time')
- parser.add_argument('-d', dest='keyttl', type=str,
- help='the DNSKEY TTL', metavar='time')
- parser.add_argument('-r', dest='resign', default='1944000',
- type=str, help='the RRSIG refresh interval '
- 'in seconds [default: 22.5 days]',
- metavar='time')
- parser.add_argument('-c', dest='compilezone',
- default=compilezone, type=str,
- help='path to \'named-compilezone\'',
- metavar='path')
- parser.add_argument('-l', dest='checklimit',
- type=str, default='0',
- help='Length of time to check for '
- 'DNSSEC coverage [default: 0 (unlimited)]',
- metavar='time')
- parser.add_argument('-z', dest='no_ksk',
- action='store_true', default=False,
- help='Only check zone-signing keys (ZSKs)')
- parser.add_argument('-k', dest='no_zsk',
- action='store_true', default=False,
- help='Only check key-signing keys (KSKs)')
- parser.add_argument('-D', '--debug', dest='debug_mode',
- action='store_true', default=False,
- help='Turn on debugging output')
- parser.add_argument('-v', '--version', action='version',
- version='@BIND9_VERSION@')
-
- args = parser.parse_args()
-
- if args.no_zsk and args.no_ksk:
- print("ERROR: -z and -k cannot be used together.");
- exit(1)
-
- # convert from time arguments to seconds
- try:
- if args.maxttl:
- m = parse_time(args.maxttl)
- args.maxttl = m
- except:
- pass
-
- try:
- if args.keyttl:
- k = parse_time(args.keyttl)
- args.keyttl = k
- except:
- pass
-
- try:
- if args.resign:
- r = parse_time(args.resign)
- args.resign = r
- except:
- pass
-
- try:
- if args.checklimit:
- lim = args.checklimit
- r = parse_time(args.checklimit)
- if r == 0:
- args.checklimit = None
- else:
- args.checklimit = time.time() + r
- except:
- pass
-
- # if we've got the values we need from the command line, stop now
- if args.maxttl and args.keyttl:
- return
-
- # load keyttl and maxttl data from zonefile
- if args.zone and args.filename:
- try:
- zone = Zone(args.zone)
- zone.load(args.filename)
- if not args.maxttl:
- args.maxttl = zone.maxttl
- if not args.keyttl:
- args.keyttl = zone.maxttl
- except Exception as e:
- print("Unable to load zone data from %s: " % args.filename, e)
-
- if not args.maxttl:
- vspace()
- print ("WARNING: Maximum TTL value was not specified. Using 1 week\n"
- "\t (604800 seconds); re-run with the -m option to get more\n"
- "\t accurate results.")
- args.maxttl = 604800
-
-############################################################################
-# Main
-############################################################################
-def main():
- global keyDict
-
- parse_args()
- path=args.path
-
- print ("PHASE 1--Loading keys to check for internal timing problems")
- keyDict = defaultdict(lambda : defaultdict(dict))
- files = glob.glob(os.path.join(path, '*.private'))
- for infile in files:
- key = Key(infile)
- if args.zone and key.zone != args.zone:
- continue
- keyDict[key.zone][key.alg][key.keyid] = key
- key.check_prepub()
- if key.sep:
- key.check_postpub()
- else:
- key.check_postpub(args.maxttl + args.resign)
-
- vspace()
- print ("PHASE 2--Scanning future key events for coverage failures")
- vreset()
-
- eventsList = defaultdict(lambda : defaultdict(list))
- fill_eventsList(eventsList)
- check_zones(eventsList)
-
- if foundprob:
- exit(1)
- else:
- exit(0)
+import isc.coverage
if __name__ == "__main__":
- main()
+ isc.coverage.main()
diff --git a/bin/python/isc/.gitignore b/bin/python/isc/.gitignore
new file mode 100644
index 0000000000..84554b8a90
--- /dev/null
+++ b/bin/python/isc/.gitignore
@@ -0,0 +1,3 @@
+utils.py
+parsetab.py
+parser.out
diff --git a/bin/python/isc/Makefile.in b/bin/python/isc/Makefile.in
new file mode 100644
index 0000000000..d5a45f546e
--- /dev/null
+++ b/bin/python/isc/Makefile.in
@@ -0,0 +1,58 @@
+# Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+@BIND9_MAKE_INCLUDES@
+
+SUBDIRS = tests
+
+PYTHON = @PYTHON@
+
+PYSRCS = __init__.py dnskey.py eventlist.py keydict.py \
+ keyevent.py keyzone.py
+ __init__.pyc dnskey.pyc eventlist.py keydict.py \
+ keyevent.pyc keyzone.pyc
+
+@BIND9_MAKE_RULES@
+
+%.pyc: %.py
+ $(PYTHON) -m compileall .
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}/isc
+
+install:: ${PYSRCS} installdirs
+ ${INSTALL_SCRIPT} __init__.py ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} __init__.pyc ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} dnskey.py ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} dnskey.pyc ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} eventlist.py ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} eventlist.pyc ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} keydict.py ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} keydict.pyc ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} keyevent.py ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} keyevent.pyc ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} keyzone.py ${DESTDIR}${libdir}
+ ${INSTALL_SCRIPT} keyzone.pyc ${DESTDIR}${libdir}
+
+check test: subdirs
+
+clean distclean::
+ rm -f *.pyc
+
+distclean::
+ rm -Rf utils.py
diff --git a/bin/python/isc/__init__.py b/bin/python/isc/__init__.py
new file mode 100644
index 0000000000..3bef6f36f1
--- /dev/null
+++ b/bin/python/isc/__init__.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2015 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+__all__ = ['dnskey', 'eventlist', 'keydict', 'keyevent', 'keyseries',
+ 'keyzone', 'utils']
+from isc.dnskey import *
+from isc.eventlist import *
+from isc.keydict import *
+from isc.keyevent import *
+from isc.keyseries import *
+from isc.keyzone import *
+from isc.utils import *
diff --git a/bin/python/isc/checkds.py b/bin/python/isc/checkds.py
new file mode 100644
index 0000000000..64ca12ebc6
--- /dev/null
+++ b/bin/python/isc/checkds.py
@@ -0,0 +1,189 @@
+############################################################################
+# Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+
+import argparse
+import os
+import sys
+from subprocess import Popen, PIPE
+
+from isc.utils import prefix,version
+
+prog = 'dnssec-checkds'
+
+
+############################################################################
+# SECRR class:
+# Class for DS/DLV resource record
+############################################################################
+class SECRR:
+ hashalgs = {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384'}
+ rrname = ''
+ rrclass = 'IN'
+ keyid = None
+ keyalg = None
+ hashalg = None
+ digest = ''
+ ttl = 0
+
+ def __init__(self, rrtext, dlvname = None):
+ if not rrtext:
+ raise Exception
+
+ fields = rrtext.split()
+ if len(fields) < 7:
+ raise Exception
+
+ if dlvname:
+ self.rrtype = "DLV"
+ self.dlvname = dlvname.lower()
+ parent = fields[0].lower().strip('.').split('.')
+ parent.reverse()
+ dlv = dlvname.split('.')
+ dlv.reverse()
+ while len(dlv) != 0 and len(parent) != 0 and parent[0] == dlv[0]:
+ parent = parent[1:]
+ dlv = dlv[1:]
+ if dlv:
+ raise Exception
+ parent.reverse()
+ self.parent = '.'.join(parent)
+ self.rrname = self.parent + '.' + self.dlvname + '.'
+ else:
+ self.rrtype = "DS"
+ self.rrname = fields[0].lower()
+
+ fields = fields[1:]
+ if fields[0].upper() in ['IN', 'CH', 'HS']:
+ self.rrclass = fields[0].upper()
+ fields = fields[1:]
+ else:
+ self.ttl = int(fields[0])
+ self.rrclass = fields[1].upper()
+ fields = fields[2:]
+
+ if fields[0].upper() != self.rrtype:
+ raise Exception
+
+ self.keyid, self.keyalg, self.hashalg = map(int, fields[1:4])
+ self.digest = ''.join(fields[4:]).upper()
+
+ def __repr__(self):
+ return '%s %s %s %d %d %d %s' % \
+ (self.rrname, self.rrclass, self.rrtype,
+ self.keyid, self.keyalg, self.hashalg, self.digest)
+
+ def __eq__(self, other):
+ return self.__repr__() == other.__repr__()
+
+
+############################################################################
+# check:
+# Fetch DS/DLV RRset for the given zone from the DNS; fetch DNSKEY
+# RRset from the masterfile if specified, or from DNS if not.
+# Generate a set of expected DS/DLV records from the DNSKEY RRset,
+# and report on congruency.
+############################################################################
+def check(zone, args, masterfile=None, lookaside=None):
+ rrlist = []
+ cmd = [args.dig, "+noall", "+answer", "-t", "dlv" if lookaside else "ds",
+ "-q", zone + "." + lookaside if lookaside else zone]
+ fp, _ = Popen(cmd, stdout=PIPE).communicate()
+
+ for line in fp.splitlines():
+ rrlist.append(SECRR(line, lookaside))
+ rrlist = sorted(rrlist, key=lambda rr: (rr.keyid, rr.keyalg, rr.hashalg))
+
+ klist = []
+
+ if masterfile:
+ cmd = [args.dsfromkey, "-f", masterfile]
+ if lookaside:
+ cmd += ["-l", lookaside]
+ cmd.append(zone)
+ fp, _ = Popen(cmd, stdout=PIPE).communicate()
+ else:
+ intods, _ = Popen([args.dig, "+noall", "+answer", "-t", "dnskey",
+ "-q", zone], stdout=PIPE).communicate()
+ cmd = [args.dsfromkey, "-f", "-"]
+ if lookaside:
+ cmd += ["-l", lookaside]
+ cmd.append(zone)
+ fp, _ = Popen(cmd, stdin=PIPE, stdout=PIPE).communicate(intods)
+
+ for line in fp.splitlines():
+ klist.append(SECRR(line, lookaside))
+
+ if len(klist) < 1:
+ print ("No DNSKEY records found in zone apex")
+ return False
+
+ found = False
+ for rr in klist:
+ if rr in rrlist:
+ print ("%s for KSK %s/%03d/%05d (%s) found in parent" %
+ (rr.rrtype, rr.rrname.strip('.'), rr.keyalg,
+ rr.keyid, SECRR.hashalgs[rr.hashalg]))
+ found = True
+ else:
+ print ("%s for KSK %s/%03d/%05d (%s) missing from parent" %
+ (rr.rrtype, rr.rrname.strip('.'), rr.keyalg,
+ rr.keyid, SECRR.hashalgs[rr.hashalg]))
+
+ if not found:
+ print ("No %s records were found for any DNSKEY" % ("DLV" if lookaside else "DS"))
+
+ return found
+
+############################################################################
+# parse_args:
+# Read command line arguments, set global 'args' structure
+############################################################################
+def parse_args():
+ parser = argparse.ArgumentParser(description=prog + ': checks DS coverage')
+
+ bindir = 'bin'
+ sbindir = 'bin' if os.name == 'nt' else 'sbin'
+
+ parser.add_argument('zone', type=str, help='zone to check')
+ parser.add_argument('-f', '--file', dest='masterfile', type=str,
+ help='zone master file')
+ parser.add_argument('-l', '--lookaside', dest='lookaside', type=str,
+ help='DLV lookaside zone')
+ parser.add_argument('-d', '--dig', dest='dig',
+ default=os.path.join(prefix(bindir), 'dig'),
+ type=str, help='path to \'dig\'')
+ parser.add_argument('-D', '--dsfromkey', dest='dsfromkey',
+ default=os.path.join(prefix(sbindir),
+ 'dnssec-dsfromkey'),
+ type=str, help='path to \'dig\'')
+ parser.add_argument('-v', '--version', action='version',
+ version=version)
+ args = parser.parse_args()
+
+ args.zone = args.zone.strip('.')
+ if args.lookaside:
+ args.lookaside = args.lookaside.strip('.')
+
+ return args
+
+
+############################################################################
+# Main
+############################################################################
+def main():
+ args = parse_args()
+ found = check(args.zone, args, args.masterfile, args.lookaside)
+ exit(0 if found else 1)
diff --git a/bin/python/isc/coverage.py b/bin/python/isc/coverage.py
new file mode 100644
index 0000000000..c9e89596f7
--- /dev/null
+++ b/bin/python/isc/coverage.py
@@ -0,0 +1,292 @@
+############################################################################
+# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+
+from __future__ import print_function
+import os
+import sys
+import argparse
+import glob
+import re
+import time
+import calendar
+import pprint
+from collections import defaultdict
+
+prog = 'dnssec-coverage'
+
+from isc import *
+from isc.utils import prefix
+
+
+############################################################################
+# print a fatal error and exit
+############################################################################
+def fatal(*args, **kwargs):
+ print(*args, **kwargs)
+ sys.exit(1)
+
+
+############################################################################
+# output:
+############################################################################
+_firstline = True
+def output(*args, **kwargs):
+ """output text, adding a vertical space this is *not* the first
+ first section being printed since a call to vreset()"""
+ global _firstline
+ if 'skip' in kwargs:
+ skip = kwargs['skip']
+ kwargs.pop('skip', None)
+ else:
+ skip = True
+ if _firstline:
+ _firstline = False
+ elif skip:
+ print('')
+ if args:
+ print(*args, **kwargs)
+
+
+def vreset():
+ """reset vertical spacing"""
+ global _firstline
+ _firstline = True
+
+
+############################################################################
+# parse_time
+############################################################################
+def parse_time(s):
+ """ convert a formatted time (e.g., 1y, 6mo, 15mi, etc) into seconds
+ :param s: String with some text representing a time interval
+ :return: Integer with the number of seconds in the time interval
+ """
+ s = s.strip()
+
+ # if s is an integer, we're done already
+ try:
+ return int(s)
+ except ValueError:
+ pass
+
+ # try to parse as a number with a suffix indicating unit of time
+ r = re.compile('([0-9][0-9]*)\s*([A-Za-z]*)')
+ m = r.match(s)
+ if not m:
+ raise ValueError("Cannot parse %s" % s)
+ n, unit = m.groups()
+ n = int(n)
+ unit = unit.lower()
+ if unit.startswith('y'):
+ return n * 31536000
+ elif unit.startswith('mo'):
+ return n * 2592000
+ elif unit.startswith('w'):
+ return n * 604800
+ elif unit.startswith('d'):
+ return n * 86400
+ elif unit.startswith('h'):
+ return n * 3600
+ elif unit.startswith('mi'):
+ return n * 60
+ elif unit.startswith('s'):
+ return n
+ else:
+ raise ValueError("Invalid suffix %s" % unit)
+
+
+############################################################################
+# set_path:
+############################################################################
+def set_path(command, default=None):
+ """ find the location of a specified command. if a default is supplied
+ and it works, we use it; otherwise we search PATH for a match.
+ :param command: string with a command to look for in the path
+ :param default: default location to use
+ :return: detected location for the desired command
+ """
+
+ fpath = default
+ if not fpath or not os.path.isfile(fpath) or not os.access(fpath, os.X_OK):
+ path = os.environ["PATH"]
+ if not path:
+ path = os.path.defpath
+ for directory in path.split(os.pathsep):
+ fpath = os.path.join(directory, command)
+ if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
+ break
+ fpath = None
+
+ return fpath
+
+
+############################################################################
+# parse_args:
+############################################################################
+def parse_args():
+ """Read command line arguments, set global 'args' structure"""
+ compilezone = set_path('named-compilezone',
+ os.path.join(prefix('sbin'), 'named-compilezone'))
+
+ parser = argparse.ArgumentParser(description=prog + ': checks future ' +
+ 'DNSKEY coverage for a zone')
+
+ parser.add_argument('zone', type=str, nargs='*', default=None,
+ help='zone(s) to check' +
+ '(default: all zones in the directory)')
+ parser.add_argument('-K', dest='path', default='.', type=str,
+ help='a directory containing keys to process',
+ metavar='dir')
+ parser.add_argument('-f', dest='filename', type=str,
+ help='zone master file', metavar='file')
+ parser.add_argument('-m', dest='maxttl', type=str,
+ help='the longest TTL in the zone(s)',
+ metavar='time')
+ parser.add_argument('-d', dest='keyttl', type=str,
+ help='the DNSKEY TTL', metavar='time')
+ parser.add_argument('-r', dest='resign', default='1944000',
+ type=str, help='the RRSIG refresh interval '
+ 'in seconds [default: 22.5 days]',
+ metavar='time')
+ parser.add_argument('-c', dest='compilezone',
+ default=compilezone, type=str,
+ help='path to \'named-compilezone\'',
+ metavar='path')
+ parser.add_argument('-l', dest='checklimit',
+ type=str, default='0',
+ help='Length of time to check for '
+ 'DNSSEC coverage [default: 0 (unlimited)]',
+ metavar='time')
+ parser.add_argument('-z', dest='no_ksk',
+ action='store_true', default=False,
+ help='Only check zone-signing keys (ZSKs)')
+ parser.add_argument('-k', dest='no_zsk',
+ action='store_true', default=False,
+ help='Only check key-signing keys (KSKs)')
+ parser.add_argument('-D', '--debug', dest='debug_mode',
+ action='store_true', default=False,
+ help='Turn on debugging output')
+ parser.add_argument('-v', '--version', action='version',
+ version=utils.version)
+
+ args = parser.parse_args()
+
+ if args.no_zsk and args.no_ksk:
+ fatal("ERROR: -z and -k cannot be used together.")
+ elif args.no_zsk or args.no_ksk:
+ args.keytype = "KSK" if args.no_zsk else "ZSK"
+ else:
+ args.keytype = None
+
+ if args.filename and len(args.zone) > 1:
+ fatal("ERROR: -f can only be used with one zone.")
+
+ # convert from time arguments to seconds
+ try:
+ if args.maxttl:
+ m = parse_time(args.maxttl)
+ args.maxttl = m
+ except ValueError:
+ pass
+
+ try:
+ if args.keyttl:
+ k = parse_time(args.keyttl)
+ args.keyttl = k
+ except ValueError:
+ pass
+
+ try:
+ if args.resign:
+ r = parse_time(args.resign)
+ args.resign = r
+ except ValueError:
+ pass
+
+ try:
+ if args.checklimit:
+ lim = args.checklimit
+ r = parse_time(args.checklimit)
+ if r == 0:
+ args.checklimit = None
+ else:
+ args.checklimit = time.time() + r
+ except ValueError:
+ pass
+
+ # if we've got the values we need from the command line, stop now
+ if args.maxttl and args.keyttl:
+ return args
+
+ # load keyttl and maxttl data from zonefile
+ if args.zone and args.filename:
+ try:
+ zone = keyzone(args.zone[0], args.filename, args.compilezone)
+ args.maxttl = args.maxttl or zone.maxttl
+ args.keyttl = args.maxttl or zone.keyttl
+ except Exception as e:
+ print("Unable to load zone data from %s: " % args.filename, e)
+
+ if not args.maxttl:
+ output("WARNING: Maximum TTL value was not specified. Using 1 week\n"
+ "\t (604800 seconds); re-run with the -m option to get more\n"
+ "\t accurate results.")
+ args.maxttl = 604800
+
+ return args
+
+############################################################################
+# Main
+############################################################################
+def main():
+ args = parse_args()
+
+ print("PHASE 1--Loading keys to check for internal timing problems")
+
+ try:
+ kd = keydict(path=args.path, zone=args.zone, keyttl=args.keyttl)
+ except Exception as e:
+ fatal('ERROR: Unable to build key dictionary: ' + str(e))
+
+ for key in kd:
+ key.check_prepub(output)
+ if key.sep:
+ key.check_postpub(output)
+ else:
+ key.check_postpub(output, args.maxttl + args.resign)
+
+ output("PHASE 2--Scanning future key events for coverage failures")
+ vreset()
+
+ try:
+ elist = eventlist(kd)
+ except Exception as e:
+ fatal('ERROR: Unable to build event list: ' + str(e))
+
+ errors = False
+ if not args.zone:
+ if not elist.coverage(None, args.keytype, args.checklimit, output):
+ errors = True
+ else:
+ for zone in args.zone:
+ try:
+ if not elist.coverage(zone, args.keytype,
+ args.checklimit, output):
+ errors = True
+ except:
+ output('ERROR: Coverage check failed for zone ' + zone)
+
+ sys.exit(1 if errors else 0)
diff --git a/bin/python/isc/dnskey.py b/bin/python/isc/dnskey.py
new file mode 100644
index 0000000000..f1559e7239
--- /dev/null
+++ b/bin/python/isc/dnskey.py
@@ -0,0 +1,504 @@
+############################################################################
+# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+
+import os
+import time
+import calendar
+from subprocess import Popen, PIPE
+
+########################################################################
+# Class dnskey
+########################################################################
+class TimePast(Exception):
+ def __init__(self, key, prop, value):
+ super(TimePast, self).__init__('%s time for key %s (%d) is already past'
+ % (prop, key, value))
+
+class dnskey:
+ """An individual DNSSEC key. Identified by path, name, algorithm, keyid.
+ Contains a dictionary of metadata events."""
+
+ _PROPS = ('Created', 'Publish', 'Activate', 'Inactive', 'Delete',
+ 'Revoke', 'DSPublish', 'SyncPublish', 'SyncDelete')
+ _OPTS = (None, '-P', '-A', '-I', '-D', '-R', None, '-Psync', '-Dsync')
+
+ _ALGNAMES = (None, 'RSAMD5', 'DH', 'DSA', 'ECC', 'RSASHA1',
+ 'NSEC3DSA', 'NSEC3RSASHA1', 'RSASHA256', None,
+ 'RSASHA512', None, 'ECCGOST', 'ECDSAP256SHA256',
+ 'ECDSAP384SHA384')
+
+ def __init__(self, key, directory=None, keyttl=None):
+ # this makes it possible to use algname as a class or instance method
+ if isinstance(key, tuple) and len(key) == 3:
+ self._dir = directory or '.'
+ (name, alg, keyid) = key
+ self.fromtuple(name, alg, keyid, keyttl)
+
+ self._dir = directory or os.path.dirname(key) or '.'
+ key = os.path.basename(key)
+
+ (name, alg, keyid) = key.split('+')
+ name = name[1:-1]
+ alg = int(alg)
+ keyid = int(keyid.split('.')[0])
+ self.fromtuple(name, alg, keyid, keyttl)
+
+ def fromtuple(self, name, alg, keyid, keyttl):
+ if name.endswith('.'):
+ fullname = name
+ name = name.rstrip('.')
+ else:
+ fullname = name + '.'
+
+ keystr = "K%s+%03d+%05d" % (fullname, alg, keyid)
+ key_file = self._dir + (self._dir and os.sep or '') + keystr + ".key"
+ private_file = (self._dir + (self._dir and os.sep or '') +
+ keystr + ".private")
+
+ self.keystr = keystr
+
+ self.name = name
+ self.alg = int(alg)
+ self.keyid = int(keyid)
+ self.fullname = fullname
+
+ kfp = open(key_file, "r")
+ for line in kfp:
+ if line[0] == ';':
+ continue
+ tokens = line.split()
+ if not tokens:
+ continue
+
+ if tokens[1].lower() in ('in', 'ch', 'hs'):
+ septoken = 3
+ self.ttl = keyttl
+ else:
+ septoken = 4
+ self.ttl = int(tokens[1]) if not keyttl else keyttl
+
+ if (int(tokens[septoken]) & 0x1) == 1:
+ self.sep = True
+ else:
+ self.sep = False
+ kfp.close()
+
+ pfp = open(private_file, "rU")
+
+ self.metadata = dict()
+ self._changed = dict()
+ self._delete = dict()
+ self._times = dict()
+ self._fmttime = dict()
+ self._timestamps = dict()
+ self._original = dict()
+ self._origttl = None
+
+ for line in pfp:
+ line = line.strip()
+ if not line or line[0] in ('!#'):
+ continue
+ punctuation = [line.find(c) for c in ':= '] + [len(line)]
+ found = min([pos for pos in punctuation if pos != -1])
+ name = line[:found].rstrip()
+ value = line[found:].lstrip(":= ").rstrip()
+ self.metadata[name] = value
+
+ for prop in dnskey._PROPS:
+ self._changed[prop] = False
+ if prop in self.metadata:
+ t = self.parsetime(self.metadata[prop])
+ self._times[prop] = t
+ self._fmttime[prop] = self.formattime(t)
+ self._timestamps[prop] = self.epochfromtime(t)
+ self._original[prop] = self._timestamps[prop]
+ else:
+ self._times[prop] = None
+ self._fmttime[prop] = None
+ self._timestamps[prop] = None
+ self._original[prop] = None
+
+ pfp.close()
+
+ def commit(self, settime_bin, **kwargs):
+ quiet = kwargs.get('quiet', False)
+ cmd = []
+ first = True
+
+ if self._origttl is not None:
+ cmd += ["-L", str(self.ttl)]
+
+ for prop, opt in zip(dnskey._PROPS, dnskey._OPTS):
+ if not opt or not self._changed[prop]:
+ continue
+
+ delete = False
+ if prop in self._delete and self._delete[prop]:
+ delete = True
+
+ when = 'none' if delete else self._fmttime[prop]
+ cmd += [opt, when]
+ first = False
+
+ if cmd:
+ fullcmd = [settime_bin, "-K", self._dir] + cmd + [self.keystr,]
+ if not quiet:
+ print('# ' + ' '.join(fullcmd))
+ try:
+ p = Popen(fullcmd, stdout=PIPE, stderr=PIPE)
+ stdout, stderr = p.communicate()
+ if stderr:
+ raise Exception(str(stderr))
+ except Exception as e:
+ raise Exception('unable to run %s: %s' %
+ (settime_bin, str(e)))
+ self._origttl = None
+ for prop in dnskey._PROPS:
+ self._original[prop] = self._timestamps[prop]
+ self._changed[prop] = False
+
+ @classmethod
+ def generate(cls, keygen_bin, keys_dir, name, alg, keysize, sep,
+ ttl, publish=None, activate=None, **kwargs):
+ quiet = kwargs.get('quiet', False)
+
+ keygen_cmd = [keygen_bin, "-q", "-K", keys_dir, "-L", str(ttl)]
+
+ if sep:
+ keygen_cmd.append("-fk")
+
+ if alg:
+ keygen_cmd += ["-a", alg]
+
+ if keysize:
+ keygen_cmd += ["-b", str(keysize)]
+
+ if publish:
+ t = dnskey.timefromepoch(publish)
+ keygen_cmd += ["-P", dnskey.formattime(t)]
+
+ if activate:
+ t = dnskey.timefromepoch(activate)
+ keygen_cmd += ["-A", dnskey.formattime(activate)]
+
+ keygen_cmd.append(name)
+
+ if not quiet:
+ print('# ' + ' '.join(keygen_cmd))
+
+ p = Popen(keygen_cmd, stdout=PIPE, stderr=PIPE)
+ stdout, stderr = p.communicate()
+ if stderr:
+ raise Exception('unable to generate key: ' + str(stderr))
+
+ try:
+ keystr = stdout.splitlines()[0]
+ newkey = dnskey(keystr, keys_dir, ttl)
+ return newkey
+ except Exception as e:
+ raise Exception('unable to generate key: %s' % str(e))
+
+ def generate_successor(self, keygen_bin, **kwargs):
+ quiet = kwargs.get('quiet', False)
+
+ if not self.inactive():
+ raise Exception("predecessor key %s has no inactive date" % self)
+
+ keygen_cmd = [keygen_bin, "-q", "-K", self._dir, "-S", self.keystr]
+
+ if self.ttl:
+ keygen_cmd += ["-L", str(self.ttl)]
+
+ if not quiet:
+ print('# ' + ' '.join(keygen_cmd))
+
+ p = Popen(keygen_cmd, stdout=PIPE, stderr=PIPE)
+ stdout, stderr = p.communicate()
+ if stderr:
+ raise Exception('unable to generate key: ' + stderr)
+
+ try:
+ keystr = stdout.splitlines()[0]
+ newkey = dnskey(keystr, self._dir, self.ttl)
+ return newkey
+ except:
+ raise Exception('unable to generate successor for key %s' % self)
+
+ @staticmethod
+ def algstr(alg):
+ name = None
+ if alg in range(len(dnskey._ALGNAMES)):
+ name = dnskey._ALGNAMES[alg]
+ return name if name else ("%03d" % alg)
+
+ @staticmethod
+ def algnum(alg):
+ if not alg:
+ return None
+ alg = alg.upper()
+ try:
+ return dnskey._ALGNAMES.index(alg)
+ except ValueError:
+ return None
+
+ def algname(self, alg=None):
+ return self.algstr(alg or self.alg)
+
+ @staticmethod
+ def timefromepoch(secs):
+ return time.gmtime(secs)
+
+ @staticmethod
+ def parsetime(string):
+ return time.strptime(string, "%Y%m%d%H%M%S")
+
+ @staticmethod
+ def epochfromtime(t):
+ return calendar.timegm(t)
+
+ @staticmethod
+ def formattime(t):
+ return time.strftime("%Y%m%d%H%M%S", t)
+
+ def setmeta(self, prop, secs, now, **kwargs):
+ force = kwargs.get('force', False)
+
+ if self._timestamps[prop] == secs:
+ return
+
+ if self._original[prop] is not None and \
+ self._original[prop] < now and not force:
+ raise TimePast(self, prop, self._original[prop])
+
+ if secs is None:
+ self._changed[prop] = False \
+ if self._original[prop] is None else True
+
+ self._delete[prop] = True
+ self._timestamps[prop] = None
+ self._times[prop] = None
+ self._fmttime[prop] = None
+ return
+
+ t = self.timefromepoch(secs)
+ self._timestamps[prop] = secs
+ self._times[prop] = t
+ self._fmttime[prop] = self.formattime(t)
+ self._changed[prop] = False if \
+ self._original[prop] == self._timestamps[prop] else True
+
+ def gettime(self, prop):
+ return self._times[prop]
+
+ def getfmttime(self, prop):
+ return self._fmttime[prop]
+
+ def gettimestamp(self, prop):
+ return self._timestamps[prop]
+
+ def created(self):
+ return self._timestamps["Created"]
+
+ def syncpublish(self):
+ return self._timestamps["SyncPublish"]
+
+ def setsyncpublish(self, secs, now=time.time(), **kwargs):
+ self.setmeta("SyncPublish", secs, now, **kwargs)
+
+ def publish(self):
+ return self._timestamps["Publish"]
+
+ def setpublish(self, secs, now=time.time(), **kwargs):
+ self.setmeta("Publish", secs, now, **kwargs)
+
+ def activate(self):
+ return self._timestamps["Activate"]
+
+ def setactivate(self, secs, now=time.time(), **kwargs):
+ self.setmeta("Activate", secs, now, **kwargs)
+
+ def revoke(self):
+ return self._timestamps["Revoke"]
+
+ def setrevoke(self, secs, now=time.time(), **kwargs):
+ self.setmeta("Revoke", secs, now, **kwargs)
+
+ def inactive(self):
+ return self._timestamps["Inactive"]
+
+ def setinactive(self, secs, now=time.time(), **kwargs):
+ self.setmeta("Inactive", secs, now, **kwargs)
+
+ def delete(self):
+ return self._timestamps["Delete"]
+
+ def setdelete(self, secs, now=time.time(), **kwargs):
+ self.setmeta("Delete", secs, now, **kwargs)
+
+ def syncdelete(self):
+ return self._timestamps["SyncDelete"]
+
+ def setsyncdelete(self, secs, now=time.time(), **kwargs):
+ self.setmeta("SyncDelete", secs, now, **kwargs)
+
+ def setttl(self, ttl):
+ if ttl is None or self.ttl == ttl:
+ return
+ elif self._origttl is None:
+ self._origttl = self.ttl
+ self.ttl = ttl
+ elif self._origttl == ttl:
+ self._origttl = None
+ self.ttl = ttl
+ else:
+ self.ttl = ttl
+
+ def keytype(self):
+ return ("KSK" if self.sep else "ZSK")
+
+ def __str__(self):
+ return ("%s/%s/%05d"
+ % (self.name, self.algname(), self.keyid))
+
+ def __repr__(self):
+ return ("%s/%s/%05d (%s)"
+ % (self.name, self.algname(), self.keyid,
+ ("KSK" if self.sep else "ZSK")))
+
+ def date(self):
+ return (self.activate() or self.publish() or self.created())
+
+ # keys are sorted first by zone name, then by algorithm. within
+ # the same name/algorithm, they are sorted according to their
+ # 'date' value: the activation date if set, OR the publication
+ # if set, OR the creation date.
+ def __lt__(self, other):
+ if self.name != other.name:
+ return self.name < other.name
+ if self.alg != other.alg:
+ return self.alg < other.alg
+ return self.date() < other.date()
+
+ def check_prepub(self, output=None):
+ def noop(*args, **kwargs): pass
+ if not output:
+ output = noop
+
+ now = int(time.time())
+ a = self.activate()
+ p = self.publish()
+
+ if not a:
+ return False
+
+ if not p:
+ if a > now:
+ output("WARNING: Key %s is scheduled for\n"
+ "\t activation but not for publication."
+ % repr(self))
+ return False
+
+ if p <= now and a <= now:
+ return True
+
+ if p == a:
+ output("WARNING: %s is scheduled to be\n"
+ "\t published and activated at the same time. This\n"
+ "\t could result in a coverage gap if the zone was\n"
+ "\t previously signed. Activation should be at least\n"
+ "\t %s after publication."
+ % (repr(self),
+ dnskey.duration(self.ttl) or 'one DNSKEY TTL'))
+ return True
+
+ if a < p:
+ output("WARNING: Key %s is active before it is published"
+ % repr(self))
+ return False
+
+ if self.ttl is not None and a - p < self.ttl:
+ output("WARNING: Key %s is activated too soon\n"
+ "\t after publication; this could result in coverage \n"
+ "\t gaps due to resolver caches containing old data.\n"
+ "\t Activation should be at least %s after\n"
+ "\t publication."
+ % (repr(self),
+ dnskey.duration(self.ttl) or 'one DNSKEY TTL'))
+ return False
+
+ return True
+
+ def check_postpub(self, output = None, timespan = None):
+ def noop(*args, **kwargs): pass
+ if output is None:
+ output = noop
+
+ if timespan is None:
+ timespan = self.ttl
+
+ now = time.time()
+ d = self.delete()
+ i = self.inactive()
+
+ if not d:
+ return False
+
+ if not i:
+ if d > now:
+ output("WARNING: Key %s is scheduled for\n"
+ "\t deletion but not for inactivation." % repr(self))
+ return False
+
+ if d < now and i < now:
+ return True
+
+ if d < i:
+ output("WARNING: Key %s is scheduled for\n"
+ "\t deletion before inactivation."
+ % repr(self))
+ return False
+
+ if d - i < timespan:
+ output("WARNING: Key %s scheduled for\n"
+ "\t deletion too soon after deactivation; this may \n"
+ "\t result in coverage gaps due to resolver caches\n"
+ "\t containing old data. Deletion should be at least\n"
+ "\t %s after inactivation."
+ % (repr(self), dnskey.duration(timespan)))
+ return False
+
+ return True
+
+ @staticmethod
+ def duration(secs):
+ if not secs:
+ return None
+
+ units = [("year", 60*60*24*365),
+ ("month", 60*60*24*30),
+ ("day", 60*60*24),
+ ("hour", 60*60),
+ ("minute", 60),
+ ("second", 1)]
+
+ output = []
+ for unit in units:
+ v, secs = secs // unit[1], secs % unit[1]
+ if v > 0:
+ output.append("%d %s%s" % (v, unit[0], "s" if v > 1 else ""))
+
+ return ", ".join(output)
+
diff --git a/bin/python/isc/eventlist.py b/bin/python/isc/eventlist.py
new file mode 100644
index 0000000000..4c91368f12
--- /dev/null
+++ b/bin/python/isc/eventlist.py
@@ -0,0 +1,171 @@
+############################################################################
+# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+
+from collections import defaultdict
+from .dnskey import *
+from .keydict import *
+from .keyevent import *
+
+
+class eventlist:
+ _K = defaultdict(lambda: defaultdict(list))
+ _Z = defaultdict(lambda: defaultdict(list))
+ _zones = set()
+ _kdict = None
+
+ def __init__(self, kdict):
+ properties = ["SyncPublish", "Publish", "SyncDelete",
+ "Activate", "Inactive", "Delete"]
+ self._kdict = kdict
+ for zone in kdict.zones():
+ self._zones.add(zone)
+ for alg, keys in kdict[zone].items():
+ for k in keys.values():
+ for prop in properties:
+ t = k.gettime(prop)
+ if not t:
+ continue
+ e = keyevent(prop, k, t)
+ if k.sep:
+ self._K[zone][alg].append(e)
+ else:
+ self._Z[zone][alg].append(e)
+
+ self._K[zone][alg] = sorted(self._K[zone][alg],
+ key=lambda event: event.when)
+ self._Z[zone][alg] = sorted(self._Z[zone][alg],
+ key=lambda event: event.when)
+
+ # scan events per zone, algorithm, and key type, in order of
+ # occurrance, noting inconsistent states when found
+ def coverage(self, zone, keytype, until, output = None):
+ def noop(*args, **kwargs): pass
+ if not output:
+ output = noop
+
+ no_zsk = True if (keytype and keytype == "KSK") else False
+ no_ksk = True if (keytype and keytype == "ZSK") else False
+ kok = zok = True
+ found = False
+
+ if zone and not zone in self._zones:
+ output("ERROR: No key events found for %s" % zone)
+ return False
+
+ if zone:
+ found = True
+ if not no_ksk:
+ kok = self.checkzone(zone, "KSK", until, output)
+ if not no_zsk:
+ zok = self.checkzone(zone, "ZSK", until, output)
+ else:
+ for z in self._zones:
+ if not no_ksk and z in self._K.keys():
+ found = True
+ kok = self.checkzone(z, "KSK", until, output)
+ if not no_zsk and z in self._Z.keys():
+ found = True
+ kok = self.checkzone(z, "ZSK", until, output)
+
+ if not found:
+ output("ERROR: No key events found")
+ return False
+
+ return (kok and zok)
+
+ def checkzone(self, zone, keytype, until, output):
+ allok = True
+ if keytype == "KSK":
+ kz = self._K[zone]
+ else:
+ kz = self._Z[zone]
+
+ for alg in kz.keys():
+ output("Checking scheduled %s events for zone %s, "
+ "algorithm %s..." %
+ (keytype, zone, dnskey.algstr(alg)))
+ ok = eventlist.checkset(kz[alg], keytype, until, output)
+ if ok:
+ output("No errors found")
+ allok = allok and ok
+
+ return allok
+
+ @staticmethod
+ def showset(eventset, output):
+ if not eventset:
+ return
+ output(" " + eventset[0].showtime() + ":", skip=False)
+ for event in eventset:
+ output(" %s: %s" % (event.what, repr(event.key)), skip=False)
+
+ @staticmethod
+ def checkset(eventset, keytype, until, output):
+ groups = list()
+ group = list()
+
+ # collect up all events that have the same time
+ eventsfound = False
+ for event in eventset:
+ # we found an event
+ eventsfound = True
+
+ # add event to current group
+ if (not group or group[0].when == event.when):
+ group.append(event)
+
+ # if we're at the end of the list, we're done. if
+ # we've found an event with a later time, start a new group
+ if (group[0].when != event.when):
+ groups.append(group)
+ group = list()
+ group.append(event)
+
+ if group:
+ groups.append(group)
+
+ if not eventsfound:
+ output("ERROR: No %s events found" % keytype)
+ return False
+
+ active = published = None
+ for group in groups:
+ if (until and calendar.timegm(group[0].when) > until):
+ output("Ignoring events after %s" %
+ time.strftime("%a %b %d %H:%M:%S UTC %Y",
+ time.gmtime(until)))
+ return True
+
+ for event in group:
+ (active, published) = event.status(active, published)
+
+ eventlist.showset(group, output)
+
+ # and then check for inconsistencies:
+ if not active:
+ output("ERROR: No %s's are active after this event" % keytype)
+ return False
+ elif not published:
+ output("ERROR: No %s's are published after this event"
+ % keytype)
+ return False
+ elif not published.intersection(active):
+ output("ERROR: No %s's are both active and published "
+ "after this event" % keytype)
+ return False
+
+ return True
+
diff --git a/bin/python/isc/keydict.py b/bin/python/isc/keydict.py
new file mode 100644
index 0000000000..cc73dc47ac
--- /dev/null
+++ b/bin/python/isc/keydict.py
@@ -0,0 +1,89 @@
+############################################################################
+# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+
+from collections import defaultdict
+from . import dnskey
+import os
+import glob
+
+
+########################################################################
+# Class keydict
+########################################################################
+class keydict:
+ """ A dictionary of keys, indexed by name, algorithm, and key id """
+
+ _keydict = defaultdict(lambda: defaultdict(dict))
+ _defttl = None
+ _missing = []
+
+ def __init__(self, dp=None, **kwargs):
+ self._defttl = kwargs.get('keyttl', None)
+ zones = kwargs.get('zones', None)
+
+ if not zones:
+ path = kwargs.get('path',None) or '.'
+ self.readall(path)
+ else:
+ for zone in zones:
+ if 'path' in kwargs and kwargs['path'] is not None:
+ path = kwargs['path']
+ else:
+ path = dp and dp.policy(zone).directory or '.'
+ if not self.readone(path, zone):
+ self._missing.append(zone)
+
+ def readall(self, path):
+ files = glob.glob(os.path.join(path, '*.private'))
+
+ for infile in files:
+ key = dnskey(infile, path, self._defttl)
+ self._keydict[key.name][key.alg][key.keyid] = key
+
+ def readone(self, path, zone):
+ match='K' + zone + '.+*.private'
+ files = glob.glob(os.path.join(path, match))
+
+ found = False
+ for infile in files:
+ key = dnskey(infile, path, self._defttl)
+ if key.name != zone: # shouldn't ever happen
+ continue
+ self._keydict[key.name][key.alg][key.keyid] = key
+ found = True
+
+ return found
+
+ def __iter__(self):
+ for zone, algorithms in self._keydict.items():
+ for alg, keys in algorithms.items():
+ for key in keys.values():
+ yield key
+
+ def __getitem__(self, name):
+ return self._keydict[name]
+
+ def zones(self):
+ return (self._keydict.keys())
+
+ def algorithms(self, zone):
+ return (self._keydict[zone].keys())
+
+ def keys(self, zone, alg):
+ return (self._keydict[zone][alg].keys())
+
+ def missing(self):
+ return (self._missing)
diff --git a/bin/python/isc/keyevent.py b/bin/python/isc/keyevent.py
new file mode 100644
index 0000000000..9025feec4a
--- /dev/null
+++ b/bin/python/isc/keyevent.py
@@ -0,0 +1,81 @@
+############################################################################
+# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+
+import time
+
+
+########################################################################
+# Class keyevent
+########################################################################
+class keyevent:
+ """ A discrete key event, e.g., Publish, Activate, Inactive, Delete,
+ etc. Stores the date of the event, and identifying information
+ about the key to which the event will occur."""
+
+ def __init__(self, what, key, when=None):
+ self.what = what
+ self.when = when or key.gettime(what)
+ self.key = key
+ self.sep = key.sep
+ self.zone = key.name
+ self.alg = key.alg
+ self.keyid = key.keyid
+
+ def __repr__(self):
+ return repr((self.when, self.what, self.keyid, self.sep,
+ self.zone, self.alg))
+
+ def showtime(self):
+ return time.strftime("%a %b %d %H:%M:%S UTC %Y", self.when)
+
+ # update sets of active and published keys, based on
+ # the contents of this keyevent
+ def status(self, active, published, output = None):
+ def noop(*args, **kwargs): pass
+ if not output:
+ output = noop
+
+ if not active:
+ active = set()
+ if not published:
+ published = set()
+
+ if self.what == "Activate":
+ active.add(self.keyid)
+ elif self.what == "Publish":
+ published.add(self.keyid)
+ elif self.what == "Inactive":
+ if self.keyid not in active:
+ output("\tWARNING: %s scheduled to become inactive "
+ "before it is active"
+ % repr(self.key))
+ else:
+ active.remove(self.keyid)
+ elif self.what == "Delete":
+ if self.keyid in published:
+ published.remove(self.keyid)
+ else:
+ output("WARNING: key %s is scheduled for deletion "
+ "before it is published" % repr(self.key))
+ elif self.what == "Revoke":
+ # We don't need to worry about the logic of this one;
+ # just stop counting this key as either active or published
+ if self.keyid in published:
+ published.remove(self.keyid)
+ if self.keyid in active:
+ active.remove(self.keyid)
+
+ return active, published
diff --git a/bin/python/isc/keyseries.py b/bin/python/isc/keyseries.py
new file mode 100644
index 0000000000..ed09f71fda
--- /dev/null
+++ b/bin/python/isc/keyseries.py
@@ -0,0 +1,194 @@
+############################################################################
+# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+
+from collections import defaultdict
+from .dnskey import *
+from .keydict import *
+from .keyevent import *
+from .policy import *
+import time
+
+
+class keyseries:
+ _K = defaultdict(lambda: defaultdict(list))
+ _Z = defaultdict(lambda: defaultdict(list))
+ _zones = set()
+ _kdict = None
+ _context = None
+
+ def __init__(self, kdict, now=time.time(), context=None):
+ self._kdict = kdict
+ self._context = context
+ self._zones = set(kdict.missing())
+
+ for zone in kdict.zones():
+ self._zones.add(zone)
+ for alg, keys in kdict[zone].items():
+ for k in keys.values():
+ if k.sep:
+ self._K[zone][alg].append(k)
+ else:
+ self._Z[zone][alg].append(k)
+
+ for group in [self._K[zone][alg], self._Z[zone][alg]]:
+ group.sort()
+ for k in group:
+ if k.delete() and k.delete() < now:
+ group.remove(k)
+
+ def __iter__(self):
+ for zone in self._zones:
+ for collection in [self._K, self._Z]:
+ if zone not in collection:
+ continue
+ for alg, keys in collection[zone].items():
+ for key in keys:
+ yield key
+
+ def dump(self):
+ for k in self:
+ print("%s" % repr(k))
+
+ def fixseries(self, keys, policy, now, **kwargs):
+ force = kwargs.get('force', False)
+ if not keys:
+ return
+
+ # handle the first key
+ key = keys[0]
+ if key.sep:
+ rp = policy.ksk_rollperiod
+ prepub = policy.ksk_prepublish or (30 * 86400)
+ postpub = policy.ksk_postpublish or (30 * 86400)
+ else:
+ rp = policy.zsk_rollperiod
+ prepub = policy.zsk_prepublish or (30 * 86400)
+ postpub = policy.zsk_postpublish or (30 * 86400)
+
+ # the first key should be published and active
+ p = key.publish()
+ a = key.activate()
+ if not p or p > now:
+ key.setpublish(now)
+ if not a or a > now:
+ key.setactivate(now)
+
+ if not rp:
+ key.setinactive(None, **kwargs)
+ key.setdelete(None, **kwargs)
+ else:
+ key.setinactive(a + rp, **kwargs)
+ key.setdelete(a + rp + postpub, **kwargs)
+
+ if policy.keyttl != key.ttl:
+ key.setttl(policy.keyttl)
+
+ # handle all the subsequent keys
+ prev = key
+ for key in keys[1:]:
+ # if no rollperiod, then all keys after the first in
+ # the series kept inactive.
+ # (XXX: we need to change this to allow standby keys)
+ if not rp:
+ key.setpublish(None, **kwargs)
+ key.setactivate(None, **kwargs)
+ key.setinactive(None, **kwargs)
+ key.setdelete(None, **kwargs)
+ if policy.keyttl != key.ttl:
+ key.setttl(policy.keyttl)
+ continue
+
+ # otherwise, ensure all dates are set correctly based on
+ # the initial key
+ a = prev.inactive()
+ p = a - prepub
+ key.setactivate(a, **kwargs)
+ key.setpublish(p, **kwargs)
+ key.setinactive(a + rp, **kwargs)
+ key.setdelete(a + rp + postpub, **kwargs)
+ prev.setdelete(a + postpub, **kwargs)
+ if policy.keyttl != key.ttl:
+ key.setttl(policy.keyttl)
+ prev = key
+
+ # if we haven't got sufficient coverage, create successor key(s)
+ while rp and prev.inactive() and \
+ prev.inactive() < now + policy.coverage:
+ # commit changes to predecessor: a successor can only be
+ # generated if Inactive has been set in the predecessor key
+ prev.commit(self._context['settime_path'], **kwargs)
+ key = prev.generate_successor(self._context['keygen_path'],
+ **kwargs)
+
+ key.setinactive(key.activate() + rp, **kwargs)
+ key.setdelete(key.inactive() + postpub, **kwargs)
+ keys.append(key)
+ prev = key
+
+ # last key? we already know we have sufficient coverage now, so
+ # disable the inactivation of the final key (if it was set),
+ # ensuring that if dnssec-keymgr isn't run again, the last key
+ # in the series will at least remain usable.
+ prev.setinactive(None, **kwargs)
+ prev.setdelete(None, **kwargs)
+
+ # commit changes
+ for key in keys:
+ key.commit(self._context['settime_path'], **kwargs)
+
+
+ def enforce_policy(self, policies, now=time.time(), **kwargs):
+ # If zones is provided as a parameter, use that list.
+ # If not, use what we have in this object
+ zones = kwargs.get('zones', self._zones)
+ keys_dir = kwargs.get('dir', self._context.get('keys_path', None))
+ force = kwargs.get('force', False)
+
+ for zone in zones:
+ collections = []
+ policy = policies.policy(zone)
+ keys_dir = keys_dir or policy.directory or '.'
+ alg = policy.algorithm
+ algnum = dnskey.algnum(alg)
+ if 'ksk' not in kwargs or not kwargs['ksk']:
+ if len(self._Z[zone][algnum]) == 0:
+ k = dnskey.generate(self._context['keygen_path'],
+ keys_dir, zone, alg,
+ policy.zsk_keysize, False,
+ policy.keyttl or 3600,
+ **kwargs)
+ self._Z[zone][algnum].append(k)
+ collections.append(self._Z[zone])
+
+ if 'zsk' not in kwargs or not kwargs['zsk']:
+ if len(self._K[zone][algnum]) == 0:
+ k = dnskey.generate(self._context['keygen_path'],
+ keys_dir, zone, alg,
+ policy.ksk_keysize, True,
+ policy.keyttl or 3600,
+ **kwargs)
+ self._K[zone][algnum].append(k)
+ collections.append(self._K[zone])
+
+ for collection in collections:
+ for algorithm, keys in collection.items():
+ if algorithm != algnum:
+ continue
+ try:
+ self.fixseries(keys, policy, now, **kwargs)
+ except Exception as e:
+ raise Exception('%s/%s: %s' %
+ (zone, dnskey.algstr(algnum), str(e)))
diff --git a/bin/python/isc/keyzone.py b/bin/python/isc/keyzone.py
new file mode 100644
index 0000000000..7dfb31ac55
--- /dev/null
+++ b/bin/python/isc/keyzone.py
@@ -0,0 +1,60 @@
+############################################################################
+# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+
+import os
+import sys
+import re
+from subprocess import Popen, PIPE
+
+########################################################################
+# Exceptions
+########################################################################
+class KeyZoneException(Exception):
+ pass
+
+########################################################################
+# class keyzone
+########################################################################
+class keyzone:
+ """reads a zone file to find data relevant to keys"""
+
+ def __init__(self, name, filename, czpath):
+ self.maxttl = None
+ self.keyttl = None
+
+ if not name:
+ return
+
+ if not czpath or not os.path.isfile(czpath) \
+ or not os.access(czpath, os.X_OK):
+ raise KeyZoneException('"named-compilezone" not found')
+ return
+
+ maxttl = keyttl = None
+
+ fp, _ = Popen([czpath, "-o", "-", name, filename],
+ stdout=PIPE, stderr=PIPE).communicate()
+ for line in fp.splitlines():
+ if re.search('^[:space:]*;', line):
+ continue
+ fields = line.split()
+ if not maxttl or int(fields[1]) > maxttl:
+ maxttl = int(fields[1])
+ if fields[3] == "DNSKEY":
+ keyttl = int(fields[1])
+
+ self.keyttl = keyttl
+ self.maxttl = maxttl
diff --git a/bin/python/isc/tests/Makefile.in b/bin/python/isc/tests/Makefile.in
new file mode 100644
index 0000000000..7c3e4f9fba
--- /dev/null
+++ b/bin/python/isc/tests/Makefile.in
@@ -0,0 +1,33 @@
+# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+@BIND9_MAKE_INCLUDES@
+
+PYTHON = @PYTHON@
+
+PYTESTS = dnskey_test.py
+
+@BIND9_MAKE_RULES@
+
+check test:
+ for test in $(PYTESTS); do \
+ $(PYTHON) $$test; \
+ done
+
+clean distclean::
+ rm -f *.pyc
diff --git a/bin/python/isc/tests/dnskey_test.py b/bin/python/isc/tests/dnskey_test.py
new file mode 100644
index 0000000000..2a63695ebe
--- /dev/null
+++ b/bin/python/isc/tests/dnskey_test.py
@@ -0,0 +1,57 @@
+############################################################################
+# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+
+import sys
+import unittest
+sys.path.append('../..')
+from isc import *
+
+kdict = None
+
+
+def getkey():
+ global kdict
+ if not kdict:
+ kd = keydict(path='testdata')
+ for key in kd:
+ return key
+
+
+class DnskeyTest(unittest.TestCase):
+ def test_metdata(self):
+ key = getkey()
+ self.assertEqual(key.created(), 1448055647)
+ self.assertEqual(key.publish(), 1445463714)
+ self.assertEqual(key.activate(), 1448055714)
+ self.assertEqual(key.revoke(), 1479591714)
+ self.assertEqual(key.inactive(), 1511127714)
+ self.assertEqual(key.delete(), 1542663714)
+ self.assertEqual(key.syncpublish(), 1442871714)
+ self.assertEqual(key.syncdelete(), 1448919714)
+
+ def test_fmttime(self):
+ key = getkey()
+ self.assertEqual(key.getfmttime('Created'), '20151120214047')
+ self.assertEqual(key.getfmttime('Publish'), '20151021214154')
+ self.assertEqual(key.getfmttime('Activate'), '20151120214154')
+ self.assertEqual(key.getfmttime('Revoke'), '20161119214154')
+ self.assertEqual(key.getfmttime('Inactive'), '20171119214154')
+ self.assertEqual(key.getfmttime('Delete'), '20181119214154')
+ self.assertEqual(key.getfmttime('SyncPublish'), '20150921214154')
+ self.assertEqual(key.getfmttime('SyncDelete'), '20151130214154')
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/bin/python/isc/tests/testdata/Kexample.com.+007+35529.key b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.key
new file mode 100644
index 0000000000..c5afbe27ed
--- /dev/null
+++ b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.key
@@ -0,0 +1,8 @@
+; This is a key-signing key, keyid 35529, for example.com.
+; Created: 20151120214047 (Fri Nov 20 13:40:47 2015)
+; Publish: 20151021214154 (Wed Oct 21 14:41:54 2015)
+; Activate: 20151120214154 (Fri Nov 20 13:41:54 2015)
+; Revoke: 20161119214154 (Sat Nov 19 13:41:54 2016)
+; Inactive: 20171119214154 (Sun Nov 19 13:41:54 2017)
+; Delete: 20181119214154 (Mon Nov 19 13:41:54 2018)
+example.com. IN DNSKEY 257 3 7 AwEAAbbJK96tY8d4sF6RLxh9SVIhho5s2ZhrcijT5j1SNLECen7QLutj VJPEiG8UgBLaJSGkxPDxOygYv4hwh4JXBSj89o9rNabAJtCa9XzIXSpt /cfiCfvqmcOZb9nepmDCXsC7gn/gbae/4Y5ym9XOiCp8lu+tlFWgRiJ+ kxDGN48rRPrGfpq+SfwM9NUtftVa7B0EFVzDkADKedRj0SSGYOqH+WYH CnWjhPFmgJoAw3/m4slTHW1l+mDwFvsCMjXopg4JV0CNnTybnOmyuIwO LWRhB3q8ze24sYBU1fpE9VAMxZ++4Kqh/2MZFeDAs7iPPKSmI3wkRCW5 pkwDLO5lJ9c=
diff --git a/bin/python/isc/tests/testdata/Kexample.com.+007+35529.private b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.private
new file mode 100644
index 0000000000..af22c6ad52
--- /dev/null
+++ b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.private
@@ -0,0 +1,18 @@
+Private-key-format: v1.3
+Algorithm: 7 (NSEC3RSASHA1)
+Modulus: tskr3q1jx3iwXpEvGH1JUiGGjmzZmGtyKNPmPVI0sQJ6ftAu62NUk8SIbxSAEtolIaTE8PE7KBi/iHCHglcFKPz2j2s1psAm0Jr1fMhdKm39x+IJ++qZw5lv2d6mYMJewLuCf+Btp7/hjnKb1c6IKnyW762UVaBGIn6TEMY3jytE+sZ+mr5J/Az01S1+1VrsHQQVXMOQAMp51GPRJIZg6of5ZgcKdaOE8WaAmgDDf+biyVMdbWX6YPAW+wIyNeimDglXQI2dPJuc6bK4jA4tZGEHerzN7bixgFTV+kT1UAzFn77gqqH/YxkV4MCzuI88pKYjfCREJbmmTAMs7mUn1w==
+PublicExponent: AQAB
+PrivateExponent: jfiM6YU1Rd6Y5qrPsK7HP1Ko54DmNbvmzI1hfGmYYZAyQsNCXjQloix5aAW9QGdNhecrzJUhxJAMXFZC+lrKuD5a56R25JDE1Sw21nft3SHXhuQrqw5Z5hIMTWXhRrBR1lMOFnLj2PJxqCmenp+vJYjl1z20RBmbv/keE15SExFRJIJ3G0lI4V0KxprY5rgsT/vID0pS32f7rmXhgEzyWDyuxceTMidBooD5BSeEmSTYa4rvCVZ2vgnzIGSxjYDPJE2rGve2dpvdXQuujRFaf4+/FzjaOgg35rTtUmC9klfB4D6KJIfc1PNUwcH7V0VJ2fFlgZgMYi4W331QORl9sQ==
+Prime1: 479rW3EeoBwHhUKDy5YeyfnMKjhaosrcYhW4resevLzatFrvS/n2KxJnsHoEzmGr2A13naI61RndgVBBOwNDWI3/tQ+aKvcr+V9m4omROV3xYa8s1FsDbEW0Z6G0UheaqRFir8WK98/Lj6Zht1uBXHSPPf91OW0qj+b5gbX7TK8=
+Prime2: zXXlxgIq+Ih6kxsUw4Ith0nd/d2P3d42QYPjxYjsg4xYicPAjva9HltnbBQ2lr4JEG9Yyb8KalSnJUSuvXtn7bGfBzLu8W6omCeVWXQVH4NIu9AjpO16NpMKWGRfiHHbbSYJs1daTZKHC2FEmi18MKX/RauHGGOakFQ/3A/GMVk=
+Exponent1: 0o9UQ1uHNAIWFedUEHJ/jr7LOrGVYnLpZCmu7+S0K0zzatGz8ets44+FnAyDywdUKFDzKSMm/4SFXRwE4vl2VzYZlp2RLG4PEuRYK9OCF6a6F1UsvjxTItQjIbjIDSnTjMINGnMps0lDa1EpgKsyI3eEQ46eI3TBZ//k6D6G0vM=
+Exponent2: d+CYJgXRyJzo17fvT3s+0TbaHWsOq+chROyNEw4m4UIbzpW2XjO8eF/gYgERMLbEVyCAb4XVr+CgfXArfEbqhpciMHMZUyi7mbtOupiuUmqpH1v70Bj3O6xjVtuJmfTEkFSnSEppV+VsgclI26Q6V7Ai1yWTdzl2T0u4zs8tVlE=
+Coefficient: E4EYw76gIChdQDn6+Uh44/xH9Uwmvq3OETR8w/kEZ0xQ8AkTdKFKUp84nlR6gN+ljb2mUxERKrVLwnBsU8EbUlo9UccMbBGkkZ/8MyfGCBb9nUyOFtOxdHY2M0MQadesRptXHt/m30XjdohwmT7qfSIENwtgUOHbwFnn7WPMc/k=
+Created: 20151120214047
+Publish: 20151021214154
+Activate: 20151120214154
+Revoke: 20161119214154
+Inactive: 20171119214154
+Delete: 20181119214154
+SyncPublish: 20150921214154
+SyncDelete: 20151130214154
diff --git a/bin/python/isc/utils.py.in b/bin/python/isc/utils.py.in
new file mode 100644
index 0000000000..48b9685f33
--- /dev/null
+++ b/bin/python/isc/utils.py.in
@@ -0,0 +1,57 @@
+############################################################################
+# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+############################################################################
+# utils.py
+# Grouping shared code in one place
+############################################################################
+
+import os
+
+# These routines permit platform-independent location of BIND 9 tools
+if os.name == 'nt':
+ import win32con
+ import win32api
+
+
+def prefix(bindir=''):
+ if os.name != 'nt':
+ return os.path.join('@prefix@', bindir)
+
+ bind_subkey = "Software\\ISC\\BIND"
+ h_key = None
+ key_found = True
+ try:
+ h_key = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, bind_subkey)
+ except:
+ key_found = False
+ if key_found:
+ try:
+ (named_base, _) = win32api.RegQueryValueEx(h_key, "InstallDir")
+ except:
+ key_found = False
+ win32api.RegCloseKey(h_key)
+ if key_found:
+ return os.path.join(named_base, bindir)
+ return os.path.join(win32api.GetSystemDirectory(), bindir)
+
+
+def shellquote(s):
+ if os.name == 'nt':
+ return '"' + s.replace('"', '"\\"') + '"'
+ return "'" + s.replace("'", "'\\''") + "'"
+
+
+version = '@BIND9_VERSION@'
+sysconfdir = '@expanded_sysconfdir@'
diff --git a/configure b/configure
index 6779cc31a0..eda6f68375 100755
--- a/configure
+++ b/configure
@@ -868,6 +868,7 @@ ISC_PLATFORM_NORETURN_POST
ISC_PLATFORM_NORETURN_PRE
ISC_PLATFORM_HAVELONGLONG
ISC_SOCKADDR_LEN_T
+expanded_sysconfdir
PYTHON_TOOLS
COVERAGE
CHECKDS
@@ -11795,8 +11796,10 @@ fi
python="python python3 python3.4 python3.3 python3.2 python3.1 python3.0 python2 python2.7 python2.6 python2.5 python2.4"
-testscript='try: import argparse
+
+testargparse='try: import argparse
except: exit(1)'
+
case "$use_python" in
no)
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for python support" >&5
@@ -11859,11 +11862,17 @@ done
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking python module 'argparse'" >&5
$as_echo_n "checking python module 'argparse'... " >&6; }
- if ${PYTHON:-false} -c "$testscript"; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: found, using $PYTHON" >&5
-$as_echo "found, using $PYTHON" >&6; }
- break
+ if ${PYTHON:-false} -c "$testargparse"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: found" >&5
+$as_echo "found" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not found" >&5
+$as_echo "not found" >&6; }
+ unset ac_cv_path_PYTHON
+ unset PYTHON
+ continue
fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: not found" >&5
$as_echo "not found" >&6; }
unset ac_cv_path_PYTHON
@@ -11939,7 +11948,7 @@ done
esac
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking python module 'argparse'" >&5
$as_echo_n "checking python module 'argparse'... " >&6; }
- if ${PYTHON:-false} -c "$testscript"; then
+ if ${PYTHON:-false} -c "$testargparse"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: found, using $PYTHON" >&5
$as_echo "found, using $PYTHON" >&6; }
break
@@ -11997,6 +12006,8 @@ case "$prefix" in
esac
;;
esac
+expanded_sysconfdir=`eval echo $sysconfdir`
+
#
# Make sure INSTALL uses an absolute path, else it will be wrong in all
@@ -22207,7 +22218,7 @@ ac_config_commands="$ac_config_commands chmod"
# elsewhere if there's a good reason for doing so.
#
-ac_config_files="$ac_config_files make/Makefile make/mkdep Makefile bin/Makefile bin/check/Makefile bin/confgen/Makefile bin/confgen/unix/Makefile bin/delv/Makefile bin/dig/Makefile bin/dnssec/Makefile bin/named/Makefile bin/named/unix/Makefile bin/nsupdate/Makefile bin/pkcs11/Makefile bin/python/Makefile bin/python/dnssec-checkds.py bin/python/dnssec-coverage.py bin/rndc/Makefile bin/tests/Makefile bin/tests/atomic/Makefile bin/tests/db/Makefile bin/tests/dst/Makefile bin/tests/dst/Kdh.+002+18602.key bin/tests/dst/Kdh.+002+18602.private bin/tests/dst/Kdh.+002+48957.key bin/tests/dst/Kdh.+002+48957.private bin/tests/dst/Ktest.+001+00002.key bin/tests/dst/Ktest.+001+54622.key bin/tests/dst/Ktest.+001+54622.private bin/tests/dst/Ktest.+003+23616.key bin/tests/dst/Ktest.+003+23616.private bin/tests/dst/Ktest.+003+49667.key bin/tests/dst/dst_2_data bin/tests/dst/t2_data_1 bin/tests/dst/t2_data_2 bin/tests/dst/t2_dsasig bin/tests/dst/t2_rsasig bin/tests/hashes/Makefile bin/tests/headerdep_test.sh bin/tests/master/Makefile bin/tests/mem/Makefile bin/tests/names/Makefile bin/tests/net/Makefile bin/tests/pkcs11/Makefile bin/tests/pkcs11/benchmarks/Makefile bin/tests/rbt/Makefile bin/tests/resolver/Makefile bin/tests/sockaddr/Makefile bin/tests/system/Makefile bin/tests/system/conf.sh bin/tests/system/builtin/Makefile bin/tests/system/dlz/prereq.sh bin/tests/system/dlzexternal/Makefile bin/tests/system/dlzexternal/ns1/named.conf bin/tests/system/dlzredir/prereq.sh bin/tests/system/fetchlimit/Makefile bin/tests/system/filter-aaaa/Makefile bin/tests/system/geoip/Makefile bin/tests/system/inline/checkdsa.sh bin/tests/system/lwresd/Makefile bin/tests/system/rpz/Makefile bin/tests/system/rsabigexponent/Makefile bin/tests/system/sit/prereq.sh bin/tests/system/statistics/Makefile bin/tests/system/tkey/Makefile bin/tests/system/tsiggss/Makefile bin/tests/tasks/Makefile bin/tests/timers/Makefile bin/tests/virtual-time/Makefile bin/tests/virtual-time/conf.sh bin/tools/Makefile contrib/scripts/check-secure-delegation.pl contrib/scripts/zone-edit.sh doc/Makefile doc/arm/Makefile doc/arm/noteversion.xml doc/arm/pkgversion.xml doc/arm/releaseinfo.xml doc/doxygen/Doxyfile doc/doxygen/Makefile doc/doxygen/doxygen-input-filter doc/misc/Makefile doc/tex/Makefile doc/tex/armstyle.sty doc/xsl/Makefile doc/xsl/isc-docbook-chunk.xsl doc/xsl/isc-docbook-html.xsl doc/xsl/isc-manpage.xsl doc/xsl/isc-notes-html.xsl isc-config.sh lib/Makefile lib/bind9/Makefile lib/bind9/include/Makefile lib/bind9/include/bind9/Makefile lib/dns/Makefile lib/dns/include/Makefile lib/dns/include/dns/Makefile lib/dns/include/dst/Makefile lib/dns/tests/Makefile lib/irs/Makefile lib/irs/include/Makefile lib/irs/include/irs/Makefile lib/irs/include/irs/netdb.h lib/irs/include/irs/platform.h lib/isc/$arch/Makefile lib/isc/$arch/include/Makefile lib/isc/$arch/include/isc/Makefile lib/isc/$thread_dir/Makefile lib/isc/$thread_dir/include/Makefile lib/isc/$thread_dir/include/isc/Makefile lib/isc/Makefile lib/isc/include/Makefile lib/isc/include/isc/Makefile lib/isc/include/isc/platform.h lib/isc/include/pk11/Makefile lib/isc/include/pkcs11/Makefile lib/isc/tests/Makefile lib/isc/nls/Makefile lib/isc/unix/Makefile lib/isc/unix/include/Makefile lib/isc/unix/include/isc/Makefile lib/isc/unix/include/pkcs11/Makefile lib/isccc/Makefile lib/isccc/include/Makefile lib/isccc/include/isccc/Makefile lib/isccfg/Makefile lib/isccfg/include/Makefile lib/isccfg/include/isccfg/Makefile lib/lwres/Makefile lib/lwres/include/Makefile lib/lwres/include/lwres/Makefile lib/lwres/include/lwres/netdb.h lib/lwres/include/lwres/platform.h lib/lwres/man/Makefile lib/lwres/tests/Makefile lib/lwres/unix/Makefile lib/lwres/unix/include/Makefile lib/lwres/unix/include/lwres/Makefile lib/tests/Makefile lib/tests/include/Makefile lib/tests/include/tests/Makefile lib/samples/Makefile lib/samples/Makefile-postinstall unit/Makefile unit/unittest.sh"
+ac_config_files="$ac_config_files make/Makefile make/mkdep Makefile bin/Makefile bin/check/Makefile bin/confgen/Makefile bin/confgen/unix/Makefile bin/delv/Makefile bin/dig/Makefile bin/dnssec/Makefile bin/named/Makefile bin/named/unix/Makefile bin/nsupdate/Makefile bin/pkcs11/Makefile bin/python/Makefile bin/python/isc/Makefile bin/python/isc/utils.py bin/python/isc/tests/Makefile bin/python/dnssec-checkds.py bin/python/dnssec-coverage.py bin/rndc/Makefile bin/tests/Makefile bin/tests/atomic/Makefile bin/tests/db/Makefile bin/tests/dst/Makefile bin/tests/dst/Kdh.+002+18602.key bin/tests/dst/Kdh.+002+18602.private bin/tests/dst/Kdh.+002+48957.key bin/tests/dst/Kdh.+002+48957.private bin/tests/dst/Ktest.+001+00002.key bin/tests/dst/Ktest.+001+54622.key bin/tests/dst/Ktest.+001+54622.private bin/tests/dst/Ktest.+003+23616.key bin/tests/dst/Ktest.+003+23616.private bin/tests/dst/Ktest.+003+49667.key bin/tests/dst/dst_2_data bin/tests/dst/t2_data_1 bin/tests/dst/t2_data_2 bin/tests/dst/t2_dsasig bin/tests/dst/t2_rsasig bin/tests/hashes/Makefile bin/tests/headerdep_test.sh bin/tests/master/Makefile bin/tests/mem/Makefile bin/tests/names/Makefile bin/tests/net/Makefile bin/tests/pkcs11/Makefile bin/tests/pkcs11/benchmarks/Makefile bin/tests/rbt/Makefile bin/tests/resolver/Makefile bin/tests/sockaddr/Makefile bin/tests/system/Makefile bin/tests/system/conf.sh bin/tests/system/builtin/Makefile bin/tests/system/dlz/prereq.sh bin/tests/system/dlzexternal/Makefile bin/tests/system/dlzexternal/ns1/named.conf bin/tests/system/dlzredir/prereq.sh bin/tests/system/fetchlimit/Makefile bin/tests/system/filter-aaaa/Makefile bin/tests/system/geoip/Makefile bin/tests/system/inline/checkdsa.sh bin/tests/system/lwresd/Makefile bin/tests/system/rpz/Makefile bin/tests/system/rsabigexponent/Makefile bin/tests/system/sit/prereq.sh bin/tests/system/statistics/Makefile bin/tests/system/tkey/Makefile bin/tests/system/tsiggss/Makefile bin/tests/tasks/Makefile bin/tests/timers/Makefile bin/tests/virtual-time/Makefile bin/tests/virtual-time/conf.sh bin/tools/Makefile contrib/scripts/check-secure-delegation.pl contrib/scripts/zone-edit.sh doc/Makefile doc/arm/Makefile doc/arm/noteversion.xml doc/arm/pkgversion.xml doc/arm/releaseinfo.xml doc/doxygen/Doxyfile doc/doxygen/Makefile doc/doxygen/doxygen-input-filter doc/misc/Makefile doc/tex/Makefile doc/tex/armstyle.sty doc/xsl/Makefile doc/xsl/isc-docbook-chunk.xsl doc/xsl/isc-docbook-html.xsl doc/xsl/isc-manpage.xsl doc/xsl/isc-notes-html.xsl isc-config.sh lib/Makefile lib/bind9/Makefile lib/bind9/include/Makefile lib/bind9/include/bind9/Makefile lib/dns/Makefile lib/dns/include/Makefile lib/dns/include/dns/Makefile lib/dns/include/dst/Makefile lib/dns/tests/Makefile lib/irs/Makefile lib/irs/include/Makefile lib/irs/include/irs/Makefile lib/irs/include/irs/netdb.h lib/irs/include/irs/platform.h lib/isc/$arch/Makefile lib/isc/$arch/include/Makefile lib/isc/$arch/include/isc/Makefile lib/isc/$thread_dir/Makefile lib/isc/$thread_dir/include/Makefile lib/isc/$thread_dir/include/isc/Makefile lib/isc/Makefile lib/isc/include/Makefile lib/isc/include/isc/Makefile lib/isc/include/isc/platform.h lib/isc/include/pk11/Makefile lib/isc/include/pkcs11/Makefile lib/isc/tests/Makefile lib/isc/nls/Makefile lib/isc/unix/Makefile lib/isc/unix/include/Makefile lib/isc/unix/include/isc/Makefile lib/isc/unix/include/pkcs11/Makefile lib/isccc/Makefile lib/isccc/include/Makefile lib/isccc/include/isccc/Makefile lib/isccfg/Makefile lib/isccfg/include/Makefile lib/isccfg/include/isccfg/Makefile lib/lwres/Makefile lib/lwres/include/Makefile lib/lwres/include/lwres/Makefile lib/lwres/include/lwres/netdb.h lib/lwres/include/lwres/platform.h lib/lwres/man/Makefile lib/lwres/tests/Makefile lib/lwres/unix/Makefile lib/lwres/unix/include/Makefile lib/lwres/unix/include/lwres/Makefile lib/tests/Makefile lib/tests/include/Makefile lib/tests/include/tests/Makefile lib/samples/Makefile lib/samples/Makefile-postinstall unit/Makefile unit/unittest.sh"
#
@@ -23216,6 +23227,9 @@ do
"bin/nsupdate/Makefile") CONFIG_FILES="$CONFIG_FILES bin/nsupdate/Makefile" ;;
"bin/pkcs11/Makefile") CONFIG_FILES="$CONFIG_FILES bin/pkcs11/Makefile" ;;
"bin/python/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/Makefile" ;;
+ "bin/python/isc/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/isc/Makefile" ;;
+ "bin/python/isc/utils.py") CONFIG_FILES="$CONFIG_FILES bin/python/isc/utils.py" ;;
+ "bin/python/isc/tests/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/isc/tests/Makefile" ;;
"bin/python/dnssec-checkds.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-checkds.py" ;;
"bin/python/dnssec-coverage.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-coverage.py" ;;
"bin/rndc/Makefile") CONFIG_FILES="$CONFIG_FILES bin/rndc/Makefile" ;;
diff --git a/configure.in b/configure.in
index b79aab0a2a..f0dbbdcce3 100644
--- a/configure.in
+++ b/configure.in
@@ -224,8 +224,10 @@ AC_ARG_WITH(python,
use_python="$withval", use_python="unspec")
python="python python3 python3.4 python3.3 python3.2 python3.1 python3.0 python2 python2.7 python2.6 python2.5 python2.4"
-testscript='try: import argparse
+
+testargparse='try: import argparse
except: exit(1)'
+
case "$use_python" in
no)
AC_MSG_CHECKING([for python support])
@@ -241,10 +243,15 @@ case "$use_python" in
continue;
fi
AC_MSG_CHECKING([python module 'argparse'])
- if ${PYTHON:-false} -c "$testscript"; then
- AC_MSG_RESULT([found, using $PYTHON])
- break
+ if ${PYTHON:-false} -c "$testargparse"; then
+ AC_MSG_RESULT([found])
+ else
+ AC_MSG_RESULT([not found])
+ unset ac_cv_path_PYTHON
+ unset PYTHON
+ continue
fi
+
AC_MSG_RESULT([not found])
unset ac_cv_path_PYTHON
unset PYTHON
@@ -272,7 +279,7 @@ case "$use_python" in
;;
esac
AC_MSG_CHECKING([python module 'argparse'])
- if ${PYTHON:-false} -c "$testscript"; then
+ if ${PYTHON:-false} -c "$testargparse"; then
AC_MSG_RESULT([found, using $PYTHON])
break
else
@@ -329,6 +336,8 @@ case "$prefix" in
esac
;;
esac
+expanded_sysconfdir=`eval echo $sysconfdir`
+AC_SUBST(expanded_sysconfdir)
#
# Make sure INSTALL uses an absolute path, else it will be wrong in all
@@ -4714,6 +4723,9 @@ AC_CONFIG_FILES([
bin/nsupdate/Makefile
bin/pkcs11/Makefile
bin/python/Makefile
+ bin/python/isc/Makefile
+ bin/python/isc/utils.py
+ bin/python/isc/tests/Makefile
bin/python/dnssec-checkds.py
bin/python/dnssec-coverage.py
bin/rndc/Makefile