2 ############################################################################
3 # Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
5 # Permission to use, copy, modify, and/or distribute this software for any
6 # purpose with or without fee is hereby granted, provided that the above
7 # copyright notice and this permission notice appear in all copies.
9 # THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 # AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 # PERFORMANCE OF THIS SOFTWARE.
16 ############################################################################
24 # These routines permit platform-independent location of BIND 9 tools
29 def prefix(bindir
= ''):
31 return os
.path
.join('@prefix@', bindir
)
33 bind_subkey
= "Software\\ISC\\BIND"
37 hKey
= win32api
.RegOpenKeyEx(win32con
.HKEY_LOCAL_MACHINE
, bind_subkey
)
42 (namedBase
, _
) = win32api
.RegQueryValueEx(hKey
, "InstallDir")
45 win32api
.RegCloseKey(hKey
)
47 return os
.path
.join(namedBase
, bindir
)
48 return os
.path
.join(win32api
.GetSystemDirectory(), bindir
)
52 return '"' + s
.replace('"', '"\\"') + '"'
53 return "'" + s
.replace("'", "'\\''") + "'"
55 ############################################################################
57 # Delegation Signer (DS) resource record
58 ############################################################################
60 hashalgs
= {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384' }
70 def __init__(self
, rrtext
):
74 fields
= rrtext
.split()
78 self
.rrname
= fields
[0].lower()
80 if fields
[0].upper() in ['IN','CH','HS']:
81 self
.rrclass
= fields
[0].upper()
84 self
.ttl
= int(fields
[0])
85 self
.rrclass
= fields
[1].upper()
88 if fields
[0].upper() != 'DS':
92 self
.keyid
= int(fields
[1])
93 self
.keyalg
= int(fields
[2])
94 self
.hashalg
= int(fields
[3])
95 self
.digest
= ''.join(fields
[4:]).upper()
98 return('%s %s %s %d %d %d %s' %
99 (self
.rrname
, self
.rrclass
, self
.rrtype
, self
.keyid
,
100 self
.keyalg
, self
.hashalg
, self
.digest
))
102 def __eq__(self
, other
):
103 return self
.__repr
__() == other
.__repr
__()
105 ############################################################################
107 # DNSSEC Lookaside Validation (DLV) resource record
108 ############################################################################
110 hashalgs
= {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384' }
122 def __init__(self
, rrtext
, dlvname
):
126 fields
= rrtext
.split()
130 self
.dlvname
= dlvname
.lower()
131 parent
= fields
[0].lower().strip('.').split('.')
133 dlv
= dlvname
.split('.')
135 while len(dlv
) != 0 and len(parent
) != 0 and parent
[0] == dlv
[0]:
141 self
.parent
= '.'.join(parent
)
142 self
.rrname
= self
.parent
+ '.' + self
.dlvname
+ '.'
145 if fields
[0].upper() in ['IN','CH','HS']:
146 self
.rrclass
= fields
[0].upper()
149 self
.ttl
= int(fields
[0])
150 self
.rrclass
= fields
[1].upper()
153 if fields
[0].upper() != 'DLV':
157 self
.keyid
= int(fields
[1])
158 self
.keyalg
= int(fields
[2])
159 self
.hashalg
= int(fields
[3])
160 self
.digest
= ''.join(fields
[4:]).upper()
163 return('%s %s %s %d %d %d %s' %
164 (self
.rrname
, self
.rrclass
, self
.rrtype
,
165 self
.keyid
, self
.keyalg
, self
.hashalg
, self
.digest
))
167 def __eq__(self
, other
):
168 return self
.__repr
__() == other
.__repr
__()
170 ############################################################################
172 # Fetch DS RRset for the given zone from the DNS; fetch DNSKEY
173 # RRset from the masterfile if specified, or from DNS if not.
174 # Generate a set of expected DS records from the DNSKEY RRset,
175 # and report on congruency.
176 ############################################################################
177 def checkds(zone
, masterfile
= None):
179 fp
=os
.popen("%s +noall +answer -t ds -q %s" %
180 (shellquote(args
.dig
), shellquote(zone
)))
182 dslist
.append(DSRR(line
))
183 dslist
= sorted(dslist
, key
=lambda ds
: (ds
.keyid
, ds
.keyalg
, ds
.hashalg
))
189 fp
= os
.popen("%s -f %s %s " %
190 (shellquote(args
.dsfromkey
), shellquote(masterfile
),
193 fp
= os
.popen("%s +noall +answer -t dnskey -q %s | %s -f - %s" %
194 (shellquote(args
.dig
), shellquote(zone
),
195 shellquote(args
.dsfromkey
), shellquote(zone
)))
198 dsklist
.append(DSRR(line
))
202 if (len(dsklist
) < 1):
203 print ("No DNSKEY records found in zone apex")
209 print ("DS for KSK %s/%03d/%05d (%s) found in parent" %
210 (ds
.rrname
.strip('.'), ds
.keyalg
,
211 ds
.keyid
, DSRR
.hashalgs
[ds
.hashalg
]))
214 print ("DS for KSK %s/%03d/%05d (%s) missing from parent" %
215 (ds
.rrname
.strip('.'), ds
.keyalg
,
216 ds
.keyid
, DSRR
.hashalgs
[ds
.hashalg
]))
219 print ("No DS records were found for any DNSKEY")
223 ############################################################################
225 # Fetch DLV RRset for the given zone from the DNS; fetch DNSKEY
226 # RRset from the masterfile if specified, or from DNS if not.
227 # Generate a set of expected DLV records from the DNSKEY RRset,
228 # and report on congruency.
229 ############################################################################
230 def checkdlv(zone
, lookaside
, masterfile
= None):
232 fp
=os
.popen("%s +noall +answer -t dlv -q %s" %
233 (shellquote(args
.dig
), shellquote(zone
+ '.' + lookaside
)))
235 dlvlist
.append(DLVRR(line
, lookaside
))
236 dlvlist
= sorted(dlvlist
,
237 key
=lambda dlv
: (dlv
.keyid
, dlv
.keyalg
, dlv
.hashalg
))
241 # Fetch DNSKEY records from DNS and generate DLV records from them
245 fp
= os
.popen("%s -f %s -l %s %s " %
246 (args
.dsfromkey
, masterfile
, lookaside
, zone
))
248 fp
= os
.popen("%s +noall +answer -t dnskey %s | %s -f - -l %s %s"
249 % (shellquote(args
.dig
), shellquote(zone
),
250 shellquote(args
.dsfromkey
), shellquote(lookaside
),
254 dlvklist
.append(DLVRR(line
, lookaside
))
258 if (len(dlvklist
) < 1):
259 print ("No DNSKEY records found in zone apex")
265 print ("DLV for KSK %s/%03d/%05d (%s) found in %s" %
266 (dlv
.parent
, dlv
.keyalg
, dlv
.keyid
,
267 DLVRR
.hashalgs
[dlv
.hashalg
], dlv
.dlvname
))
270 print ("DLV for KSK %s/%03d/%05d (%s) missing from %s" %
271 (dlv
.parent
, dlv
.keyalg
, dlv
.keyid
,
272 DLVRR
.hashalgs
[dlv
.hashalg
], dlv
.dlvname
))
275 print ("No DLV records were found for any DNSKEY")
280 ############################################################################
282 # Read command line arguments, set global 'args' structure
283 ############################################################################
286 parser
= argparse
.ArgumentParser(description
=prog
+ ': checks DS coverage')
294 parser
.add_argument('zone', type=str, help='zone to check')
295 parser
.add_argument('-f', '--file', dest
='masterfile', type=str,
296 help='zone master file')
297 parser
.add_argument('-l', '--lookaside', dest
='lookaside', type=str,
298 help='DLV lookaside zone')
299 parser
.add_argument('-d', '--dig', dest
='dig',
300 default
=os
.path
.join(prefix(bindir
), 'dig'),
301 type=str, help='path to \'dig\'')
302 parser
.add_argument('-D', '--dsfromkey', dest
='dsfromkey',
303 default
=os
.path
.join(prefix(sbindir
),
305 type=str, help='path to \'dig\'')
306 parser
.add_argument('-v', '--version', action
='version', version
='9.9.1')
307 args
= parser
.parse_args()
309 args
.zone
= args
.zone
.strip('.')
311 lookaside
= args
.lookaside
.strip('.')
313 ############################################################################
315 ############################################################################
320 found
= checkdlv(args
.zone
, args
.lookaside
, args
.masterfile
)
322 found
= checkds(args
.zone
, args
.masterfile
)
324 exit(0 if found
else 1)
326 if __name__
== "__main__":