1# 2# This file is a part of DNSViz, a tool suite for DNS/DNSSEC monitoring, 3# analysis, and visualization. 4# Created by Casey Deccio (casey@deccio.net) 5# 6# Copyright 2012-2014 Sandia Corporation. Under the terms of Contract 7# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains 8# certain rights in this software. 9# 10# Copyright 2014-2016 VeriSign, Inc. 11# 12# Copyright 2016-2021 Casey Deccio 13# 14# DNSViz is free software; you can redistribute it and/or modify 15# it under the terms of the GNU General Public License as published by 16# the Free Software Foundation; either version 2 of the License, or 17# (at your option) any later version. 18# 19# DNSViz is distributed in the hope that it will be useful, 20# but WITHOUT ANY WARRANTY; without even the implied warranty of 21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22# GNU General Public License for more details. 23# 24# You should have received a copy of the GNU General Public License along 25# with DNSViz. If not, see <http://www.gnu.org/licenses/>. 26# 27 28from __future__ import unicode_literals 29 30import copy 31import errno 32import logging 33 34# minimal support for python2.6 35try: 36 from collections import OrderedDict 37except ImportError: 38 from ordereddict import OrderedDict 39 40import dns.flags, dns.rcode, dns.rdataclass, dns.rdatatype 41 42from dnsviz import crypto 43import dnsviz.format as fmt 44from dnsviz.ipaddr import * 45import dnsviz.query as Q 46from dnsviz import response as Response 47from dnsviz.util import tuple_to_dict 48lb2s = fmt.latin1_binary_to_string 49 50from . import errors as Errors 51from .online import OnlineDomainNameAnalysis, \ 52 ANALYSIS_TYPE_AUTHORITATIVE, ANALYSIS_TYPE_RECURSIVE, ANALYSIS_TYPE_CACHE 53from . import status as Status 54 55DNS_PROCESSED_VERSION = '1.0' 56 57#XXX (this needs to be updated if new specification ever updates 58# RFC 6891) 59EDNS_DEFINED_FLAGS = dns.flags.DO 60 61DNSSEC_KEY_LENGTHS_BY_ALGORITHM = { 62 12: 512, 13: 512, 14: 768, 15: 256, 16: 456, 63} 64DNSSEC_KEY_LENGTH_ERRORS = { 65 12: Errors.DNSKEYBadLengthGOST, 13: Errors.DNSKEYBadLengthECDSA256, 66 14: Errors.DNSKEYBadLengthECDSA384, 15: Errors.DNSKEYBadLengthEd25519, 67 16: Errors.DNSKEYBadLengthEd448, 68} 69 70_logger = logging.getLogger(__name__) 71 72class FoundYXDOMAIN(Exception): 73 pass 74 75class CNAMELoopDetected(Exception): 76 pass 77 78class AggregateResponseInfo(object): 79 def __init__(self, qname, rdtype, name_obj, zone_obj): 80 self.qname = qname 81 self.rdtype = rdtype 82 self.name_obj = name_obj 83 self.zone_obj = zone_obj 84 self.response_info_list = [] 85 86 def __repr__(self): 87 return '<%s %s/%s>' % (self.__class__.__name__, self.qname, dns.rdatatype.to_text(self.rdtype)) 88 89 def add_response_info(self, response_info, cname_info): 90 self.response_info_list.append((response_info, cname_info)) 91 92class OfflineDomainNameAnalysis(OnlineDomainNameAnalysis): 93 RDTYPES_ALL = 0 94 RDTYPES_ALL_SAME_NAME = 1 95 RDTYPES_NS_TARGET = 2 96 RDTYPES_SECURE_DELEGATION = 3 97 RDTYPES_DELEGATION = 4 98 99 QUERY_CLASS = Q.TTLDistinguishingMultiQueryAggregateDNSResponse 100 101 def __init__(self, *args, **kwargs): 102 103 self._strict_cookies = kwargs.pop('strict_cookies', False) 104 self._allow_private = kwargs.pop('allow_private', False) 105 106 super(OfflineDomainNameAnalysis, self).__init__(*args, **kwargs) 107 108 if self.analysis_type != ANALYSIS_TYPE_AUTHORITATIVE: 109 self._query_cls = Q.MultiQueryAggregateDNSResponse 110 111 # Shortcuts to the values in the SOA record. 112 self.serial = None 113 self.rname = None 114 self.mname = None 115 116 self.dnssec_algorithms_in_dnskey = set() 117 self.dnssec_algorithms_in_ds = set() 118 self.dnssec_algorithms_in_dlv = set() 119 self.dnssec_algorithms_digest_in_ds = set() 120 self.dnssec_algorithms_digest_in_dlv = set() 121 122 self.status = None 123 self.yxdomain = None 124 self.yxrrset = None 125 self.yxrrset_proper = None 126 self.nxrrset = None 127 self.rrset_warnings = None 128 self.rrset_errors = None 129 self.rrsig_status = None 130 self.response_component_status = None 131 self.wildcard_status = None 132 self.dname_status = None 133 self.nxdomain_status = None 134 self.nxdomain_warnings = None 135 self.nxdomain_errors = None 136 self.nodata_status = None 137 self.nodata_warnings = None 138 self.nodata_errors = None 139 self.response_errors = None 140 self.response_warnings = None 141 142 self.ds_status_by_ds = None 143 self.ds_status_by_dnskey = None 144 145 self.zone_errors = None 146 self.zone_warnings = None 147 self.zone_status = None 148 149 self.delegation_warnings = None 150 self.delegation_errors = None 151 self.delegation_status = None 152 153 self.published_keys = None 154 self.revoked_keys = None 155 self.zsks = None 156 self.ksks = None 157 self.dnskey_with_ds = None 158 159 self._dnskey_sets = None 160 self._dnskeys = None 161 162 def _signed(self): 163 return bool(self.dnssec_algorithms_in_dnskey or self.dnssec_algorithms_in_ds or self.dnssec_algorithms_in_dlv) 164 signed = property(_signed) 165 166 def _handle_soa_response(self, rrset): 167 '''Indicate that there exists an SOA record for the name which is the 168 subject of this analysis, and save the relevant parts.''' 169 170 self.has_soa = True 171 if self.serial is None or rrset[0].serial > self.serial: 172 self.serial = rrset[0].serial 173 self.rname = rrset[0].rname 174 self.mname = rrset[0].mname 175 176 def _handle_dnskey_response(self, rrset): 177 for dnskey in rrset: 178 self.dnssec_algorithms_in_dnskey.add(dnskey.algorithm) 179 180 def _handle_ds_response(self, rrset): 181 if rrset.rdtype == dns.rdatatype.DS: 182 dnssec_algs = self.dnssec_algorithms_in_ds 183 digest_algs = self.dnssec_algorithms_digest_in_ds 184 else: 185 dnssec_algs = self.dnssec_algorithms_in_dlv 186 digest_algs = self.dnssec_algorithms_digest_in_dlv 187 for ds in rrset: 188 dnssec_algs.add(ds.algorithm) 189 digest_algs.add((ds.algorithm, ds.digest_type)) 190 191 def _process_response_answer_rrset(self, rrset, query, response): 192 super(OfflineDomainNameAnalysis, self)._process_response_answer_rrset(rrset, query, response) 193 if query.qname in (self.name, self.dlv_name): 194 if rrset.rdtype == dns.rdatatype.SOA: 195 self._handle_soa_response(rrset) 196 elif rrset.rdtype == dns.rdatatype.DNSKEY: 197 self._handle_dnskey_response(rrset) 198 elif rrset.rdtype in (dns.rdatatype.DS, dns.rdatatype.DLV): 199 self._handle_ds_response(rrset) 200 201 def _index_dnskeys(self): 202 if self._dnskey_sets is not None: 203 return 204 205 self._dnskey_sets = [] 206 self._dnskeys = {} 207 if (self.name, dns.rdatatype.DNSKEY) not in self.queries: 208 return 209 for dnskey_info in self.queries[(self.name, dns.rdatatype.DNSKEY)].answer_info: 210 # there are CNAMEs that show up here... 211 if not (dnskey_info.rrset.name == self.name and dnskey_info.rrset.rdtype == dns.rdatatype.DNSKEY): 212 continue 213 dnskey_set = set() 214 for dnskey_rdata in dnskey_info.rrset: 215 if dnskey_rdata not in self._dnskeys: 216 self._dnskeys[dnskey_rdata] = Response.DNSKEYMeta(dnskey_info.rrset.name, dnskey_rdata, dnskey_info.rrset.ttl) 217 self._dnskeys[dnskey_rdata].rrset_info.append(dnskey_info) 218 self._dnskeys[dnskey_rdata].servers_clients.update(dnskey_info.servers_clients) 219 dnskey_set.add(self._dnskeys[dnskey_rdata]) 220 221 self._dnskey_sets.append((dnskey_set, dnskey_info)) 222 223 def get_dnskey_sets(self): 224 if not hasattr(self, '_dnskey_sets') or self._dnskey_sets is None: 225 self._index_dnskeys() 226 return self._dnskey_sets 227 228 def get_dnskeys(self): 229 if not hasattr(self, '_dnskeys') or self._dnskeys is None: 230 self._index_dnskeys() 231 return list(self._dnskeys.values()) 232 233 def potential_trusted_keys(self): 234 active_ksks = self.ksks.difference(self.zsks).difference(self.revoked_keys) 235 if active_ksks: 236 return active_ksks 237 return self.ksks.difference(self.revoked_keys) 238 239 def _create_response_info_recursive(self, name, rdtype, name_to_info_mapping, rrset_to_cname_mapping, trace=None): 240 zone_obj = self.get_name(name).zone 241 info_obj = AggregateResponseInfo(name, rdtype, self, zone_obj) 242 243 if trace is None: 244 trace = [name] 245 246 for info in name_to_info_mapping[name]: 247 if info in rrset_to_cname_mapping: 248 target = info.rrset[0].target 249 if target not in trace: 250 cname_info = self._create_response_info_recursive(rrset_to_cname_mapping[info], rdtype, name_to_info_mapping, rrset_to_cname_mapping, trace=trace + [target]) 251 else: 252 cname_info = None 253 else: 254 cname_info = None 255 info_obj.add_response_info(info, cname_info) 256 return info_obj 257 258 def _get_response_info(self, name, rdtype): 259 #XXX there are reasons for this (e.g., NXDOMAIN, after which no further 260 # queries are made), but it would be good to have a sanity check, so 261 # we don't simply produce an incomplete output. 262 # see also: dnsviz.viz.dnssec.graph_rrset_auth() 263 if (name, rdtype) not in self.queries: 264 return None 265 266 query = self.queries[(name, rdtype)] 267 name_to_info_mapping = {} 268 rrset_to_cname_mapping = {} 269 270 name_to_info_mapping[name] = [] 271 272 for rrset_info in query.answer_info: 273 274 # only do qname, unless analysis type is recursive 275 if not (rrset_info.rrset.name == name or self.analysis_type == ANALYSIS_TYPE_RECURSIVE): 276 continue 277 278 # if this is a CNAME record, create an info-to-target mapping 279 if rrset_info.rrset.rdtype == dns.rdatatype.CNAME: 280 rrset_to_cname_mapping[rrset_info] = rrset_info.rrset[0].target 281 282 # map name to info and name_obj 283 if rrset_info.rrset.name not in name_to_info_mapping: 284 name_to_info_mapping[rrset_info.rrset.name] = [] 285 name_to_info_mapping[rrset_info.rrset.name].append(rrset_info) 286 287 for neg_response_info in query.nxdomain_info + query.nodata_info: 288 # only do qname, unless analysis type is recursive 289 if not (neg_response_info.qname == name or self.analysis_type == ANALYSIS_TYPE_RECURSIVE): 290 continue 291 292 # make sure this query was made to a server designated as 293 # authoritative 294 z_obj = self.zone 295 if self.is_zone() and neg_response_info.rdtype == dns.rdatatype.DS: 296 z_obj = self.zone.parent 297 if not set([s for (s,c) in neg_response_info.servers_clients]).intersection(z_obj.get_auth_or_designated_servers()): 298 continue 299 300 if neg_response_info.qname not in name_to_info_mapping: 301 name_to_info_mapping[neg_response_info.qname] = [] 302 name_to_info_mapping[neg_response_info.qname].append(neg_response_info) 303 304 for error in self.response_errors[query]: 305 name_to_info_mapping[name].append(error) 306 307 for warning in self.response_warnings[query]: 308 name_to_info_mapping[name].append(warning) 309 310 info_obj = AggregateResponseInfo(name, rdtype, self, self.zone) 311 for info in name_to_info_mapping[name]: 312 if info in rrset_to_cname_mapping: 313 if self.analysis_type == ANALYSIS_TYPE_RECURSIVE: 314 cname_info = self._create_response_info_recursive(rrset_to_cname_mapping[info], rdtype, name_to_info_mapping, rrset_to_cname_mapping) 315 else: 316 cname_obj = self.get_name(rrset_to_cname_mapping[info]) 317 cname_info = cname_obj.get_response_info(rrset_to_cname_mapping[info], rdtype) 318 else: 319 cname_info = None 320 info_obj.add_response_info(info, cname_info) 321 322 return info_obj 323 324 def get_response_info(self, name, rdtype): 325 if not hasattr(self, '_response_info') or self._response_info is None: 326 self._response_info = {} 327 if (name, rdtype) not in self._response_info: 328 self._response_info[(name, rdtype)] = None 329 self._response_info[(name, rdtype)] = self._get_response_info(name, rdtype) 330 return self._response_info[(name, rdtype)] 331 332 def _serialize_nsec_set_simple(self, nsec_set_info, neg_status, response_info): 333 nsec_tup = [] 334 if neg_status[nsec_set_info]: 335 for nsec_status in neg_status[nsec_set_info]: 336 # assign the "overall" status of the NSEC proof, based on both 337 # the correctness of the NSEC proof as well as the 338 # authentication status of the collective records comprising 339 # the proof. 340 # 341 # if the proof is not valid, then use the validity status of 342 # the proof as the overall status. 343 if nsec_status.validation_status != Status.NSEC_STATUS_VALID: 344 status = Status.nsec_status_mapping[nsec_status.validation_status] 345 # else (the NSEC proof is valid) 346 else: 347 # if there is a component status, then set the overall 348 # status to the authentication status of collective records 349 # comprising the proof (the proof is only as good as it is 350 # authenticated). 351 if self.response_component_status is not None: 352 status = Status.rrset_status_mapping[self.response_component_status[nsec_status.nsec_set_info]] 353 # otherwise, set the overall status to insecure 354 else: 355 status = Status.rrset_status_mapping[Status.RRSET_STATUS_INSECURE] 356 357 warnings = [w.terse_description for w in nsec_status.warnings] 358 errors = [e.terse_description for e in nsec_status.errors] 359 360 children = [] 361 for nsec_rrset_info in nsec_status.nsec_set_info.rrsets.values(): 362 children.append(self._serialize_response_component_simple(nsec_rrset_info.rrset.rdtype, response_info, nsec_rrset_info, True)) 363 364 nsec_tup.append(('PROOF', status, [], [], [(Status.nsec_status_mapping[nsec_status.validation_status], warnings, errors, '')], children)) 365 366 return nsec_tup 367 368 def _serialize_rrsig_simple(self, name_obj, rrset_info): 369 rrsig_tup = [] 370 if name_obj.rrsig_status[rrset_info]: 371 rrsigs = list(name_obj.rrsig_status[rrset_info].keys()) 372 rrsigs.sort() 373 for rrsig in rrsigs: 374 dnskeys = list(name_obj.rrsig_status[rrset_info][rrsig].keys()) 375 dnskeys.sort() 376 for dnskey in dnskeys: 377 rrsig_status = name_obj.rrsig_status[rrset_info][rrsig][dnskey] 378 379 # assign the "overall" status of the RRSIG, based on both 380 # the validity of the RRSIG as well as the authentication 381 # status of the DNSKEY with which it is validated 382 # 383 # if the RRSIG is not valid, then use the RRSIG status as 384 # the overall status 385 if rrsig_status.validation_status != Status.RRSIG_STATUS_VALID: 386 status = Status.rrsig_status_mapping[rrsig_status.validation_status] 387 # else (the status of the RRSIG is valid) 388 else: 389 # if there is a component status, then set the overall 390 # status to that of the status of the DNSKEY (an RRSIG 391 # is only as authentic as the DNSKEY that signs it) 392 if self.response_component_status is not None: 393 status = Status.rrset_status_mapping[self.response_component_status[dnskey]] 394 # otherwise, set the overall status to insecure 395 else: 396 status = Status.rrset_status_mapping[Status.RRSET_STATUS_INSECURE] 397 398 warnings = [w.terse_description for w in rrsig_status.warnings] 399 errors = [e.terse_description for e in rrsig_status.errors] 400 rrsig_tup.append(('RRSIG', status, [], [], [(Status.rrsig_status_mapping[rrsig_status.validation_status], warnings, errors, '%s/%s/%s (%s - %s)' % \ 401 (fmt.humanize_name(rrsig.signer), rrsig.algorithm, rrsig.key_tag, fmt.timestamp_to_str(rrsig.inception)[:10], fmt.timestamp_to_str(rrsig.expiration)[:10]))], [])) 402 return rrsig_tup 403 404 def _serialize_response_component_simple(self, rdtype, response_info, info, show_neg_response, dname_status=None): 405 rdata = [] 406 if isinstance(info, Errors.DomainNameAnalysisError): 407 query = response_info.name_obj.queries[(response_info.qname, response_info.rdtype)] 408 if info in response_info.name_obj.response_warnings[query]: 409 status = 'WARNING' 410 else: 411 status = 'ERROR' 412 else: 413 if self.response_component_status is not None: 414 status = Status.rrset_status_mapping[self.response_component_status[info]] 415 else: 416 status = Status.rrset_status_mapping[Status.RRSET_STATUS_INSECURE] 417 418 rdata_tup = [] 419 children = [] 420 if isinstance(info, Response.RRsetInfo): 421 if info.rrset.rdtype == dns.rdatatype.CNAME: 422 rdata_tup.append((None, [], [], 'CNAME %s' % (lb2s(info.rrset[0].target.to_text())))) 423 elif rdtype == dns.rdatatype.DNSKEY: 424 for d in info.rrset: 425 dnskey_meta = response_info.name_obj._dnskeys[d] 426 warnings = [w.terse_description for w in dnskey_meta.warnings] 427 errors = [e.terse_description for e in dnskey_meta.errors] 428 rdata_tup.append(('VALID', warnings, errors, '%d/%d/%d' % (d.algorithm, dnskey_meta.key_tag, d.flags))) 429 elif rdtype == dns.rdatatype.DS: 430 dss = list(response_info.name_obj.ds_status_by_ds[dns.rdatatype.DS].keys()) 431 dss.sort() 432 for ds in dss: 433 # only show the DS if in the RRset in question 434 if ds not in info.rrset: 435 continue 436 dnskeys = list(response_info.name_obj.ds_status_by_ds[rdtype][ds].keys()) 437 dnskeys.sort() 438 for dnskey in dnskeys: 439 ds_status = response_info.name_obj.ds_status_by_ds[rdtype][ds][dnskey] 440 warnings = [w.terse_description for w in ds_status.warnings] 441 errors = [e.terse_description for e in ds_status.errors] 442 rdata_tup.append((Status.ds_status_mapping[ds_status.validation_status], warnings, errors, '%d/%d/%d' % (ds.algorithm, ds.key_tag, ds.digest_type))) 443 elif rdtype == dns.rdatatype.NSEC3: 444 rdata_tup.append((None, [], [], '%s %s' % (fmt.format_nsec3_name(info.rrset.name), fmt.format_nsec3_rrset_text(info.rrset[0].to_text())))) 445 elif rdtype == dns.rdatatype.NSEC: 446 rdata_tup.append((None, [], [], '%s %s' % (lb2s(info.rrset.name.to_text()), info.rrset[0].to_text()))) 447 elif rdtype == dns.rdatatype.DNAME: 448 warnings = [w.terse_description for w in dname_status.warnings] 449 errors = [e.terse_description for e in dname_status.errors] 450 rdata_tup.append((Status.dname_status_mapping[dname_status.validation_status], warnings, errors, info.rrset[0].to_text())) 451 else: 452 rdata_tup.extend([(None, [], [], r.to_text()) for r in info.rrset]) 453 454 warnings = [w.terse_description for w in response_info.name_obj.rrset_warnings[info]] 455 errors = [e.terse_description for e in response_info.name_obj.rrset_errors[info]] 456 457 children.extend(self._serialize_rrsig_simple(response_info.name_obj, info)) 458 for wildcard_name in info.wildcard_info: 459 children.extend(self._serialize_nsec_set_simple(info.wildcard_info[wildcard_name], response_info.name_obj.wildcard_status, response_info)) 460 461 if info in response_info.name_obj.dname_status: 462 for dname_status in response_info.name_obj.dname_status[info]: 463 children.append(self._serialize_response_component_simple(dns.rdatatype.DNAME, response_info, dname_status.synthesized_cname.dname_info, True, dname_status)) 464 465 elif isinstance(info, Errors.DomainNameAnalysisError): 466 warnings = [] 467 errors = [] 468 rdata_tup.append((None, [], [], '%s' % (info.terse_description))) 469 470 elif info in self.nodata_status: 471 warnings = [w.terse_description for w in response_info.name_obj.nodata_warnings[info]] 472 errors = [e.terse_description for e in response_info.name_obj.nodata_errors[info]] 473 474 # never show the negative response if show_neg_response is False 475 if show_neg_response is False: 476 return None 477 # only show the negative response if there is a corresponding 478 # status or show_neg_response is True 479 if not self.nodata_status[info] and not show_neg_response: 480 return None 481 rdata_tup.append((None, [], [], 'NODATA')) 482 for soa_rrset_info in info.soa_rrset_info: 483 children.append(self._serialize_response_component_simple(dns.rdatatype.SOA, response_info, soa_rrset_info, True)) 484 children.extend(self._serialize_nsec_set_simple(info, response_info.name_obj.nodata_status, response_info)) 485 486 elif info in self.nxdomain_status: 487 warnings = [w.terse_description for w in response_info.name_obj.nxdomain_warnings[info]] 488 errors = [e.terse_description for e in response_info.name_obj.nxdomain_errors[info]] 489 490 # never show the negative response if show_neg_response is False 491 if show_neg_response is False: 492 return None 493 # only show the negative response if there is a corresponding 494 # status or show_neg_response is True 495 if not self.nxdomain_status[info] and not show_neg_response: 496 return None 497 rdata_tup.append((None, [], [], 'NXDOMAIN')) 498 for soa_rrset_info in info.soa_rrset_info: 499 children.append(self._serialize_response_component_simple(dns.rdatatype.SOA, response_info, soa_rrset_info, True)) 500 children.extend(self._serialize_nsec_set_simple(info, response_info.name_obj.nxdomain_status, response_info)) 501 502 return (dns.rdatatype.to_text(rdtype), status, warnings, errors, rdata_tup, children) 503 504 def _serialize_response_component_list_simple(self, rdtype, response_info, show_neg_response): 505 tup = [] 506 for info, cname_chain_info in response_info.response_info_list: 507 val = self._serialize_response_component_simple(rdtype, response_info, info, show_neg_response) 508 # this might not return a non-empty value for a negative response, 509 # so we check for a non-empty value before appending it 510 if val: 511 tup.append(val) 512 return tup 513 514 def _serialize_status_simple(self, response_info_list, processed): 515 tup = [] 516 cname_info_map = OrderedDict() 517 518 # just get the first one since the names are all supposed to be the 519 # same 520 response_info = response_info_list[0] 521 522 # first build the ancestry in reverse order 523 ancestry = [] 524 parent_obj = response_info.zone_obj 525 while parent_obj is not None: 526 ancestry.insert(0, parent_obj) 527 parent_obj = parent_obj.parent 528 529 name_tup = None 530 531 # now process the DS and DNSKEY for each name in the ancestry 532 for parent_obj in ancestry: 533 if (parent_obj.name, -1) in processed: 534 continue 535 processed.add((parent_obj.name, -1)) 536 537 if parent_obj.stub: 538 continue 539 540 zone_status = None 541 zone_warnings = [] 542 zone_errors = [] 543 delegation_status = None 544 delegation_warnings = [] 545 delegation_errors = [] 546 547 if parent_obj.is_zone(): 548 if self.response_component_status is not None: 549 zone_status = Status.delegation_status_mapping[self.response_component_status[parent_obj]] 550 else: 551 zone_status = Status.delegation_status_mapping[Status.DELEGATION_STATUS_INSECURE] 552 zone_warnings = [w.terse_description for w in parent_obj.zone_warnings] 553 zone_errors = [e.terse_description for e in parent_obj.zone_errors] 554 if parent_obj.parent is not None: 555 delegation_status = Status.delegation_status_mapping[parent_obj.delegation_status[dns.rdatatype.DS]] 556 delegation_warnings = [w.terse_description for w in parent_obj.delegation_warnings[dns.rdatatype.DS]] 557 delegation_errors = [e.terse_description for e in parent_obj.delegation_errors[dns.rdatatype.DS]] 558 if parent_obj.parent is not None: 559 ds_response_info = parent_obj.get_response_info(parent_obj.name, dns.rdatatype.DS) 560 else: 561 ds_response_info = None 562 563 name_tup = (fmt.humanize_name(parent_obj.name), zone_status, zone_warnings, zone_errors, delegation_status, delegation_warnings, delegation_errors, []) 564 tup.append(name_tup) 565 566 if ds_response_info is not None: 567 name_tup[7].extend(parent_obj._serialize_response_component_list_simple(dns.rdatatype.DS, ds_response_info, None)) 568 569 # if we only care about DS for the name itself, then don't 570 # serialize the DNSKEY response 571 if response_info.rdtype == dns.rdatatype.DS and parent_obj.name == response_info.qname: 572 pass 573 # if the servers were unresponsive, then it's possible that no 574 # DNSKEY query was issued 575 elif (parent_obj.name, dns.rdatatype.DNSKEY) not in parent_obj.queries: 576 pass 577 else: 578 dnskey_response_info = parent_obj.get_response_info(parent_obj.name, dns.rdatatype.DNSKEY) 579 name_tup[7].extend(parent_obj._serialize_response_component_list_simple(dns.rdatatype.DNSKEY, dnskey_response_info, False)) 580 581 parent_is_signed = parent_obj.signed 582 583 # handle nxdomain_ancestor 584 nxdomain_ancestor = response_info.name_obj.nxdomain_ancestor 585 if nxdomain_ancestor is not None and \ 586 (nxdomain_ancestor.name, -1) not in processed: 587 processed.add((nxdomain_ancestor.name, -1)) 588 589 name_tup = (fmt.humanize_name(nxdomain_ancestor.name), None, [], [], None, [], [], []) 590 tup.append(name_tup) 591 592 name_tup[7].extend(nxdomain_ancestor._serialize_response_component_list_simple(nxdomain_ancestor.referral_rdtype, nxdomain_ancestor.get_response_info(nxdomain_ancestor.name, nxdomain_ancestor.referral_rdtype), True)) 593 594 # in recursive analysis, if we don't contact any servers that are 595 # valid and responsive, then we get a zone_obj (and thus 596 # parent_obj, in this case) that is None (because we couldn't 597 # detect any NS records in the ancestry) 598 # 599 # in this case, or in the case where the name is not a zone (and 600 # thus changes), we create a new tuple. 601 if parent_obj is None or response_info.qname != parent_obj.name or name_tup is None: 602 name_tup = (fmt.humanize_name(response_info.qname), None, [], [], None, [], [], []) 603 tup.append(name_tup) 604 605 for response_info in response_info_list: 606 # if we've already done this one (above) then just move along. 607 # These were only done if the name is a zone. 608 if response_info.name_obj.is_zone() and \ 609 response_info.rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS): 610 continue 611 612 name_tup[7].extend(response_info.name_obj._serialize_response_component_list_simple(response_info.rdtype, response_info, True)) 613 614 # queue the cnames for later serialization 615 for info, cname_info in response_info.response_info_list: 616 if cname_info is None: 617 continue 618 if cname_info.qname not in cname_info_map: 619 cname_info_map[cname_info.qname] = [] 620 cname_info_map[cname_info.qname].append(cname_info) 621 622 # now serialize the cnames 623 for qname in cname_info_map: 624 tup.extend(self._serialize_status_simple(cname_info_map[qname], processed)) 625 626 return tup 627 628 def serialize_status_simple(self, rdtypes=None, processed=None): 629 if processed is None: 630 processed = set() 631 632 response_info_map = {} 633 for qname, rdtype in self.queries: 634 if rdtypes is None: 635 # if rdtypes was not specified, then serialize all, with some exceptions 636 if rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): 637 continue 638 else: 639 # if rdtypes was specified, then only serialize rdtypes that 640 # were specified 641 if qname != self.name or rdtype not in rdtypes: 642 continue 643 if qname not in response_info_map: 644 response_info_map[qname] = {} 645 response_info_map[qname][rdtype] = self.get_response_info(qname, rdtype) 646 647 tuples = [] 648 qnames = list(response_info_map.keys()) 649 qnames.sort() 650 for qname in qnames: 651 rdtypes = list(response_info_map[qname].keys()) 652 rdtypes.sort() 653 response_info_list = [response_info_map[qname][r] for r in rdtypes] 654 tuples.extend(self._serialize_status_simple(response_info_list, processed)) 655 656 return tuples 657 658 def _rdtypes_for_analysis_level(self, level): 659 rdtypes = set([self.referral_rdtype, dns.rdatatype.NS]) 660 if level == self.RDTYPES_DELEGATION: 661 return rdtypes 662 rdtypes.update([dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV]) 663 if level == self.RDTYPES_SECURE_DELEGATION: 664 return rdtypes 665 rdtypes.update([dns.rdatatype.A, dns.rdatatype.AAAA]) 666 if level == self.RDTYPES_NS_TARGET: 667 return rdtypes 668 return None 669 670 def _server_responsive_with_condition(self, server, client, tcp, response_test): 671 for query in self.queries.values(): 672 for query1 in query.queries.values(): 673 try: 674 if client is None: 675 clients = list(query1.responses[server].keys()) 676 else: 677 clients = (client,) 678 except KeyError: 679 continue 680 681 for c in clients: 682 try: 683 response = query1.responses[server][client] 684 except KeyError: 685 continue 686 # if tcp is specified, then only follow through if the 687 # query was ultimately issued according to that value 688 if tcp is not None: 689 if tcp and not response.effective_tcp: 690 continue 691 if not tcp and response.effective_tcp: 692 continue 693 if response_test(response): 694 return True 695 return False 696 697 def server_responsive_for_action(self, server, client, tcp, action, action_arg, require_valid): 698 '''Return True if at least one (optionally valid) response was returned 699 by the server without the specified action. This action is the value 700 of the responsive_cause_index in the response's history.''' 701 702 if action == Q.RETRY_ACTION_NO_CHANGE: 703 return True 704 705 elif action == Q.RETRY_ACTION_CHANGE_SPORT: 706 return True 707 708 elif action == Q.RETRY_ACTION_SET_FLAG: 709 return self._server_responsive_with_condition(server, client, tcp, 710 lambda x: not (x.effective_flags & action_arg) and \ 711 712 ((x.effective_tcp and x.tcp_responsive) or \ 713 (not x.effective_tcp and x.udp_responsive)) and \ 714 (not require_valid or x.is_valid_response())) 715 716 elif action == Q.RETRY_ACTION_CLEAR_FLAG: 717 return self._server_responsive_with_condition(server, client, tcp, 718 lambda x: x.effective_flags & action_arg and \ 719 720 ((x.effective_tcp and x.tcp_responsive) or \ 721 (not x.effective_tcp and x.udp_responsive)) and \ 722 (not require_valid or x.is_valid_response())) 723 724 elif action == Q.RETRY_ACTION_DISABLE_EDNS: 725 return self._server_responsive_with_condition(server, client, tcp, 726 lambda x: x.effective_edns >= 0 and \ 727 728 ((x.effective_tcp and x.tcp_responsive) or \ 729 (not x.effective_tcp and x.udp_responsive)) and \ 730 (not require_valid or x.is_valid_response())) 731 732 elif action == Q.RETRY_ACTION_CHANGE_UDP_MAX_PAYLOAD: 733 return self._server_responsive_with_condition(server, client, tcp, 734 lambda x: x.effective_edns >= 0 and \ 735 x.effective_edns_max_udp_payload > action_arg and \ 736 x.msg_size > action_arg and \ 737 738 ((x.effective_tcp and x.tcp_responsive) or \ 739 (not x.effective_tcp and x.udp_responsive)) and \ 740 (not require_valid or x.is_valid_response())) 741 742 elif action == Q.RETRY_ACTION_SET_EDNS_FLAG: 743 return self._server_responsive_with_condition(server, client, tcp, 744 lambda x: x.effective_edns >= 0 and \ 745 not (x.effective_edns_flags & action_arg) and \ 746 747 ((x.effective_tcp and x.tcp_responsive) or \ 748 (not x.effective_tcp and x.udp_responsive)) and \ 749 (not require_valid or x.is_valid_response())) 750 751 elif action == Q.RETRY_ACTION_CLEAR_EDNS_FLAG: 752 return self._server_responsive_with_condition(server, client, tcp, 753 lambda x: x.effective_edns >= 0 and \ 754 x.effective_edns_flags & action_arg and \ 755 756 ((x.effective_tcp and x.tcp_responsive) or \ 757 (not x.effective_tcp and x.udp_responsive)) and \ 758 (not require_valid or x.is_valid_response())) 759 760 elif action == Q.RETRY_ACTION_ADD_EDNS_OPTION: 761 return self._server_responsive_with_condition(server, client, tcp, 762 lambda x: x.effective_edns >= 0 and \ 763 not [y for y in x.effective_edns_options if action_arg == y.otype] and \ 764 765 ((x.effective_tcp and x.tcp_responsive) or \ 766 (not x.effective_tcp and x.udp_responsive)) and \ 767 (not require_valid or x.is_valid_response())) 768 769 elif action == Q.RETRY_ACTION_REMOVE_EDNS_OPTION: 770 return self._server_responsive_with_condition(server, client, tcp, 771 lambda x: x.effective_edns >= 0 and \ 772 [y for y in x.effective_edns_options if action_arg == y.otype] and \ 773 774 ((x.effective_tcp and x.tcp_responsive) or \ 775 (not x.effective_tcp and x.udp_responsive)) and \ 776 (not require_valid or x.is_valid_response())) 777 778 elif action == Q.RETRY_ACTION_CHANGE_EDNS_VERSION: 779 return self._server_responsive_with_condition(server, client, tcp, 780 lambda x: x.effective_edns == action_arg and \ 781 782 ((x.effective_tcp and x.tcp_responsive) or \ 783 (not x.effective_tcp and x.udp_responsive)) and \ 784 (not require_valid or x.is_valid_response())) 785 786 else: 787 return False 788 789 def server_responsive_with_do(self, server, client, tcp, require_valid): 790 return self._server_responsive_with_condition(server, client, tcp, 791 lambda x: x.effective_edns >= 0 and \ 792 x.effective_edns_flags & dns.flags.DO and \ 793 794 ((x.effective_tcp and x.tcp_responsive) or \ 795 (not x.effective_tcp and x.udp_responsive)) and \ 796 (not require_valid or x.is_valid_response())) 797 798 def _populate_status(self, trusted_keys, supported_algs=None, supported_digest_algs=None, is_dlv=False, trace=None, follow_mx=True): 799 if trace is None: 800 trace = [] 801 802 # avoid loops 803 if self in trace: 804 self._populate_name_status() 805 return 806 807 # if status has already been populated, then don't reevaluate 808 if self.rrsig_status is not None: 809 return 810 811 # if we're a stub, there's nothing to evaluate 812 if self.stub: 813 return 814 815 # populate status of dependencies 816 for cname in self.cname_targets: 817 for target, cname_obj in self.cname_targets[cname].items(): 818 if cname_obj is not None: 819 cname_obj._populate_status(trusted_keys, supported_algs, supported_digest_algs, trace=trace + [self]) 820 if follow_mx: 821 for target, mx_obj in self.mx_targets.items(): 822 if mx_obj is not None: 823 mx_obj._populate_status(trusted_keys, supported_algs, supported_digest_algs, trace=trace + [self], follow_mx=False) 824 for signer, signer_obj in self.external_signers.items(): 825 if signer_obj is not None: 826 signer_obj._populate_status(trusted_keys, supported_algs, supported_digest_algs, trace=trace + [self]) 827 for target, ns_obj in self.ns_dependencies.items(): 828 if ns_obj is not None: 829 ns_obj._populate_status(trusted_keys, supported_algs, supported_digest_algs, trace=trace + [self]) 830 831 # populate status of ancestry 832 if self.nxdomain_ancestor is not None: 833 self.nxdomain_ancestor._populate_status(trusted_keys, supported_algs, supported_digest_algs, trace=trace + [self]) 834 if self.parent is not None: 835 self.parent._populate_status(trusted_keys, supported_algs, supported_digest_algs, trace=trace + [self]) 836 if self.dlv_parent is not None: 837 self.dlv_parent._populate_status(trusted_keys, supported_algs, supported_digest_algs, is_dlv=True, trace=trace + [self]) 838 839 _logger.debug('Assessing status of %s...' % (fmt.humanize_name(self.name))) 840 self._populate_name_status() 841 self._index_dnskeys() 842 self._populate_rrsig_status_all(supported_algs) 843 self._populate_nodata_status(supported_algs) 844 self._populate_nxdomain_status(supported_algs) 845 self._populate_inconsistent_negative_dnssec_responses_all() 846 self._finalize_key_roles() 847 if not is_dlv: 848 self._populate_delegation_status(supported_algs, supported_digest_algs) 849 if self.dlv_parent is not None: 850 self._populate_ds_status(dns.rdatatype.DLV, supported_algs, supported_digest_algs) 851 self._populate_dnskey_status(trusted_keys) 852 853 def populate_status(self, trusted_keys, supported_algs=None, supported_digest_algs=None, is_dlv=False, follow_mx=True, validate_prohibited_algs=False): 854 # identify supported algorithms as intersection of explicitly supported 855 # and software supported 856 if supported_algs is not None: 857 supported_algs.intersection_update(crypto._supported_algs) 858 else: 859 supported_algs = copy.copy(crypto._supported_algs) 860 if supported_digest_algs is not None: 861 supported_digest_algs.intersection_update(crypto._supported_digest_algs) 862 else: 863 supported_digest_algs = copy.copy(crypto._supported_digest_algs) 864 865 # unless we are overriding, mark prohibited algorithms as not supported 866 if not validate_prohibited_algs: 867 supported_algs.difference_update(Status.DNSKEY_ALGS_VALIDATION_PROHIBITED) 868 supported_digest_algs.difference_update(Status.DS_DIGEST_ALGS_VALIDATION_PROHIBITED) 869 870 self._populate_status(trusted_keys, supported_algs, supported_digest_algs, is_dlv, None, follow_mx) 871 872 def _populate_name_status(self, trace=None): 873 # using trace allows _populate_name_status to be called independent of 874 # populate_status 875 if trace is None: 876 trace = [] 877 878 # avoid loops 879 if self in trace: 880 return 881 882 self.status = Status.NAME_STATUS_INDETERMINATE 883 self.yxdomain = set() 884 self.yxrrset_proper = set() 885 self.yxrrset = set() 886 self.nxrrset = set() 887 888 bailiwick_map, default_bailiwick = self.get_bailiwick_mapping() 889 890 for (qname, rdtype), query in self.queries.items(): 891 892 qname_obj = self.get_name(qname) 893 if rdtype == dns.rdatatype.DS and \ 894 qname_obj.name == qname and qname_obj.is_zone(): 895 qname_obj = qname_obj.parent 896 elif rdtype == dns.rdatatype.DLV and qname == qname_obj.dlv_name: 897 qname_obj = qname_obj.dlv_parent 898 899 for rrset_info in query.answer_info: 900 self.yxdomain.add(rrset_info.rrset.name) 901 # for ALL types, add the name and type to yxrrset 902 self.yxrrset.add((rrset_info.rrset.name, rrset_info.rrset.rdtype)) 903 # for all types EXCEPT where the record is a CNAME record 904 # synthesized from a DNAME record, add the name and type to 905 # yxrrset_proper 906 if not (rrset_info.rrset.rdtype == dns.rdatatype.CNAME and rrset_info.cname_info_from_dname): 907 self.yxrrset_proper.add((rrset_info.rrset.name, rrset_info.rrset.rdtype)) 908 if rrset_info.dname_info is not None: 909 self.yxrrset.add((rrset_info.dname_info.rrset.name, rrset_info.dname_info.rrset.rdtype)) 910 for cname_rrset_info in rrset_info.cname_info_from_dname: 911 self.yxrrset.add((cname_rrset_info.dname_info.rrset.name, cname_rrset_info.dname_info.rrset.rdtype)) 912 self.yxrrset.add((cname_rrset_info.rrset.name, cname_rrset_info.rrset.rdtype)) 913 for neg_response_info in query.nodata_info: 914 for (server,client) in neg_response_info.servers_clients: 915 for response in neg_response_info.servers_clients[(server,client)]: 916 if neg_response_info.qname == qname or response.recursion_desired_and_available(): 917 if not response.is_upward_referral(qname_obj.zone.name): 918 self.yxdomain.add(neg_response_info.qname) 919 self.nxrrset.add((neg_response_info.qname, neg_response_info.rdtype)) 920 for neg_response_info in query.nxdomain_info: 921 for (server,client) in neg_response_info.servers_clients: 922 for response in neg_response_info.servers_clients[(server,client)]: 923 if neg_response_info.qname == qname or response.recursion_desired_and_available(): 924 self.nxrrset.add((neg_response_info.qname, neg_response_info.rdtype)) 925 926 # now check referrals (if name hasn't already been identified as YXDOMAIN) 927 if self.name == qname and self.name not in self.yxdomain: 928 if rdtype not in (self.referral_rdtype, dns.rdatatype.NS): 929 continue 930 try: 931 for query1 in query.queries.values(): 932 for server in query1.responses: 933 bailiwick = bailiwick_map.get(server, default_bailiwick) 934 for client in query1.responses[server]: 935 if query1.responses[server][client].is_referral(self.name, rdtype, query.rdclass, bailiwick, proper=True): 936 self.yxdomain.add(self.name) 937 raise FoundYXDOMAIN 938 except FoundYXDOMAIN: 939 pass 940 941 # now add the values of CNAMEs 942 for cname in self.cname_targets: 943 for target, cname_obj in self.cname_targets[cname].items(): 944 if cname_obj is self: 945 continue 946 if cname_obj is None: 947 continue 948 if cname_obj.yxrrset is None: 949 cname_obj._populate_name_status(trace=trace + [self]) 950 for name, rdtype in cname_obj.yxrrset: 951 if name == target: 952 self.yxrrset.add((cname,rdtype)) 953 954 if self.name in self.yxdomain: 955 self.status = Status.NAME_STATUS_NOERROR 956 957 if self.status == Status.NAME_STATUS_INDETERMINATE: 958 for (qname, rdtype), query in self.queries.items(): 959 if rdtype == dns.rdatatype.DS: 960 continue 961 if [x for x in query.nxdomain_info if x.qname == qname]: 962 self.status = Status.NAME_STATUS_NXDOMAIN 963 break 964 965 def _populate_responsiveness_errors(self, qname_obj, response, server, client, warnings, errors): 966 # if we had to make some change to elicit a response, find out why that 967 # was 968 change_err = None 969 if response.responsive_cause_index is not None: 970 retry = response.history[response.responsive_cause_index] 971 972 cause_err_class = None 973 action_err_class = None 974 975 cause_err_kwargs = { 'tcp': response.responsive_cause_index_tcp } 976 action_err_kwargs = {} 977 978 require_valid = False 979 dnssec_downgrade_class = None 980 981 #TODO - look for success ratio to servers due to timeout or network 982 # error, for better determining if a problem is intermittent 983 984 #################### 985 # CAUSES 986 # 987 # Network error - kwargs: errno; don't require a valid response 988 if retry.cause == Q.RETRY_CAUSE_NETWORK_ERROR: 989 cause_err_class = Errors.NetworkError 990 cause_err_kwargs['errno'] = errno.errorcode.get(retry.cause_arg, 'UNKNOWN') 991 require_valid = False 992 993 # Malformed response - kwargs: msg_size; require a valid response 994 elif retry.cause == Q.RETRY_CAUSE_FORMERR: 995 cause_err_class = Errors.FormError 996 cause_err_kwargs['msg_size'] = response.msg_size 997 require_valid = True 998 999 # Timeout - kwargs: attempts; don't require a valid response 1000 elif retry.cause == Q.RETRY_CAUSE_TIMEOUT: 1001 cause_err_class = Errors.Timeout 1002 cause_err_kwargs['attempts'] = response.responsive_cause_index+1 1003 require_valid = False 1004 1005 # Invalid RCODE - kwargs: rcode; require a valid response 1006 elif retry.cause == Q.RETRY_CAUSE_RCODE: 1007 # If the RCODE was FORMERR, SERVFAIL, or NOTIMP, then this is a 1008 # signal to the client that the server doesn't support EDNS. 1009 # Thus, *independent of action*, we mark this as a DNSSEC 1010 # downgrade, if the zone is signed. 1011 if retry.cause_arg in (dns.rcode.FORMERR, dns.rcode.SERVFAIL, dns.rcode.NOTIMP) and \ 1012 qname_obj is not None and qname_obj.zone.signed: 1013 dnssec_downgrade_class = Errors.DNSSECDowngradeEDNSDisabled 1014 1015 # if the RCODE was FORMERR, SERVFAIL, or NOTIMP, and the 1016 # corresponding action was to disable EDNS, then this was a 1017 # reasonable response from a server that doesn't support EDNS, 1018 # but it's only innocuous if the zone is not signed. 1019 if retry.cause_arg in (dns.rcode.FORMERR, dns.rcode.SERVFAIL, dns.rcode.NOTIMP) and \ 1020 retry.action == Q.RETRY_ACTION_DISABLE_EDNS and \ 1021 not (qname_obj is not None and qname_obj.zone.signed): 1022 pass 1023 1024 # or if the RCODE was BADVERS, and the corresponding action was 1025 # to change EDNS version, then this was a reasonable response 1026 # from a server that doesn't support the EDNS version 1027 elif retry.cause_arg == dns.rcode.BADVERS and \ 1028 retry.action == Q.RETRY_ACTION_CHANGE_EDNS_VERSION: 1029 pass 1030 1031 # or if the RCODE was SERVFAIL, and the corresponding action was 1032 # to set the CD flag, then this was a reasonable response 1033 # from a server that couldn't validate the query 1034 elif retry.cause_arg == dns.rcode.SERVFAIL and \ 1035 retry.action == Q.RETRY_ACTION_SET_FLAG and \ 1036 retry.action_arg == dns.flags.CD: 1037 pass 1038 1039 # or if the RCODE was BADCOOKIE, and the COOKIE opt we sent 1040 # contained only a client cookie or an invalid server cookie, 1041 # then this was a reasonable response from a server that 1042 # supports cookies 1043 elif retry.cause_arg == 23 and \ 1044 response.server_cookie_status in (Q.DNS_COOKIE_CLIENT_COOKIE_ONLY, Q.DNS_COOKIE_SERVER_COOKIE_BAD) and \ 1045 retry.action == Q.RETRY_ACTION_UPDATE_DNS_COOKIE: 1046 pass 1047 1048 # or if the RCODE was FORMERR, and the COOKIE opt we sent 1049 # contained a malformed cookie, then this was a reasonable 1050 # response from a server that supports cookies 1051 if retry.cause_arg == dns.rcode.FORMERR and \ 1052 response.server_cookie_status == Q.DNS_COOKIE_IMPROPER_LENGTH and \ 1053 (retry.action == Q.RETRY_ACTION_DISABLE_EDNS or \ 1054 (retry.action == Q.RETRY_ACTION_REMOVE_EDNS_OPTION and retry.action_arg == 10)): 1055 pass 1056 1057 # otherwise, set the error class and instantiation kwargs 1058 # appropriately 1059 else: 1060 cause_err_class = Errors.InvalidRcode 1061 cause_err_kwargs['rcode'] = dns.rcode.to_text(retry.cause_arg) 1062 require_valid = True 1063 1064 # Other errors 1065 elif retry.cause == Q.RETRY_CAUSE_OTHER: 1066 require_valid = True 1067 1068 # by default, use the action argument as the argument 1069 action_arg = retry.action_arg 1070 1071 #################### 1072 # ACTIONS 1073 # 1074 # No change was made; a valid response was received when the query 1075 # was issued again 1076 if retry.action == Q.RETRY_ACTION_NO_CHANGE: 1077 pass 1078 1079 # Only the source port was changed; a valid response was received 1080 # when the query was issued again 1081 elif retry.action == Q.RETRY_ACTION_CHANGE_SPORT: 1082 pass 1083 1084 # A flag was set to elicit a response; kwargs: flag 1085 elif retry.action == Q.RETRY_ACTION_SET_FLAG: 1086 action_err_class = Errors.ResponseErrorWithoutRequestFlag 1087 action_err_kwargs['flag'] = dns.flags.to_text(retry.action_arg) 1088 if not action_err_kwargs['flag']: 1089 action_err_kwargs['flag'] = retry.action_arg 1090 1091 # A flag was cleared to elicit a response; kwargs: flag 1092 elif retry.action == Q.RETRY_ACTION_CLEAR_FLAG: 1093 action_err_class = Errors.ResponseErrorWithRequestFlag 1094 action_err_kwargs['flag'] = dns.flags.to_text(retry.action_arg) 1095 if not action_err_kwargs['flag']: 1096 action_err_kwargs['flag'] = retry.action_arg 1097 1098 # EDNS was disabled to elicit a response; kwargs: None 1099 elif retry.action == Q.RETRY_ACTION_DISABLE_EDNS: 1100 action_err_class = Errors.ResponseErrorWithEDNS 1101 1102 # DNSSEC was downgraded because DO bit is no longer available 1103 dnssec_downgrade_class = Errors.DNSSECDowngradeEDNSDisabled 1104 1105 # The EDNS UDP max payload size was changed to elicit a response; 1106 # kwargs: pmtu_lower_bound, pmtu_upper_bound 1107 elif retry.action == Q.RETRY_ACTION_CHANGE_UDP_MAX_PAYLOAD: 1108 action_err_class = Errors.PMTUExceeded 1109 #TODO need bounding here 1110 action_err_kwargs['pmtu_lower_bound'] = None 1111 action_err_kwargs['pmtu_upper_bound'] = None 1112 1113 # An EDNS flag was set to elicit a response; kwargs: flag 1114 elif retry.action == Q.RETRY_ACTION_SET_EDNS_FLAG: 1115 action_err_class = Errors.ResponseErrorWithoutEDNSFlag 1116 action_err_kwargs['flag'] = dns.flags.edns_to_text(retry.action_arg) 1117 if not action_err_kwargs['flag']: 1118 action_err_kwargs['flag'] = retry.action_arg 1119 1120 # An EDNS flag was cleared to elicit a response; kwargs: flag 1121 elif retry.action == Q.RETRY_ACTION_CLEAR_EDNS_FLAG: 1122 action_err_class = Errors.ResponseErrorWithEDNSFlag 1123 action_err_kwargs['flag'] = dns.flags.edns_to_text(retry.action_arg) 1124 if not action_err_kwargs['flag']: 1125 action_err_kwargs['flag'] = retry.action_arg 1126 1127 # if this was the DO flag, then DNSSEC was downgraded 1128 if retry.action_arg == dns.flags.DO: 1129 dnssec_downgrade_class = Errors.DNSSECDowngradeDOBitCleared 1130 1131 # An EDNS option was added to elicit a response; kwargs: option 1132 elif retry.action == Q.RETRY_ACTION_ADD_EDNS_OPTION: 1133 action_err_class = Errors.ResponseErrorWithoutEDNSOption 1134 #TODO convert numeric option ID to text 1135 action_err_kwargs['option'] = fmt.EDNS_OPT_DESCRIPTIONS.get(retry.action_arg, retry.action_arg) 1136 1137 # An EDNS option was removed to elicit a response; kwargs: option 1138 elif retry.action == Q.RETRY_ACTION_REMOVE_EDNS_OPTION: 1139 action_err_class = Errors.ResponseErrorWithEDNSOption 1140 #TODO convert numeric option ID to text 1141 action_err_kwargs['option'] = fmt.EDNS_OPT_DESCRIPTIONS.get(retry.action_arg, retry.action_arg) 1142 1143 # The EDNS version was changed to elicit a response; kwargs: 1144 # edns_old, edns_new 1145 elif retry.action == Q.RETRY_ACTION_CHANGE_EDNS_VERSION: 1146 action_err_class = Errors.ResponseErrorWithEDNSVersion 1147 action_err_kwargs['edns_old'] = response.query.edns 1148 action_err_kwargs['edns_new'] = retry.action_arg 1149 1150 # if this was about changing EDNS version, then use the 1151 # original version number as the argument 1152 action_arg = response.query.edns 1153 1154 if cause_err_class is not None and action_err_class is not None: 1155 if qname_obj is not None and qname_obj.zone.server_responsive_for_action(server, client, response.responsive_cause_index_tcp, \ 1156 retry.action, action_arg, require_valid): 1157 query_specific = True 1158 else: 1159 query_specific = False 1160 cause_err = cause_err_class(**cause_err_kwargs) 1161 change_err = action_err_class(response_error=cause_err, query_specific=query_specific, **action_err_kwargs) 1162 1163 if change_err is not None: 1164 # if the error really matters (e.g., due to DNSSEC), note an error 1165 if dnssec_downgrade_class is not None and qname_obj is not None and qname_obj.zone.signed: 1166 Errors.DomainNameAnalysisError.insert_into_list(change_err, errors, server, client, response) 1167 Errors.DomainNameAnalysisError.insert_into_list(dnssec_downgrade_class(response_error=cause_err), errors, server, client, response) 1168 # otherwise, warn 1169 else: 1170 Errors.DomainNameAnalysisError.insert_into_list(change_err, warnings, server, client, response) 1171 1172 def _populate_edns_errors(self, qname_obj, response, server, client, warnings, errors): 1173 1174 # if we actually got a message response (as opposed to timeout, network 1175 # error, form error, etc.) 1176 if response.message is None: 1177 return 1178 1179 edns_errs = [] 1180 1181 # if the effective request used EDNS 1182 if response.effective_edns >= 0: 1183 # if the message response didn't use EDNS, then create an error 1184 if response.message.edns < 0: 1185 # if there were indicators that the server supported EDNS 1186 # (e.g., by RRSIGs in the answer), then report it as such 1187 if [x for x in response.message.answer if x.rdtype == dns.rdatatype.RRSIG]: 1188 edns_errs.append(Errors.EDNSSupportNoOpt()) 1189 # otherwise, simply report it as a server not responding 1190 # properly to EDNS requests 1191 else: 1192 edns_errs.append(Errors.EDNSIgnored()) 1193 1194 # the message response did use EDNS 1195 else: 1196 if response.message.rcode() == dns.rcode.BADVERS: 1197 # if the message response code was BADVERS, then the EDNS 1198 # version in the response should have been less than 1199 # that of the request 1200 if response.message.edns >= response.effective_edns: 1201 edns_errs.append(Errors.ImplementedEDNSVersionNotProvided(request_version=response.effective_edns, response_version=response.message.edns)) 1202 1203 # if the message response used a version of EDNS other than 1204 # that requested, then create an error (should have been 1205 # answered with BADVERS) 1206 elif response.message.edns != response.effective_edns: 1207 edns_errs.append(Errors.EDNSVersionMismatch(request_version=response.effective_edns, response_version=response.message.edns)) 1208 1209 # check that all EDNS flags are all zero, except for DO 1210 undefined_edns_flags_set = (response.message.ednsflags & 0xffff) & ~EDNS_DEFINED_FLAGS 1211 if undefined_edns_flags_set: 1212 edns_errs.append(Errors.EDNSUndefinedFlagsSet(flags=undefined_edns_flags_set)) 1213 1214 else: 1215 # if the effective request didn't use EDNS, and we got a 1216 # message response with an OPT record 1217 if response.message.edns >= 0: 1218 edns_errs.append(Errors.GratuitousOPT()) 1219 1220 for edns_err in edns_errs: 1221 Errors.DomainNameAnalysisError.insert_into_list(edns_err, warnings, server, client, response) 1222 1223 def _populate_cookie_errors(self, qname_obj, response, server, client, warnings, errors): 1224 1225 if response.message is None: 1226 return 1227 1228 cookie_errs = [] 1229 1230 try: 1231 cookie_opt = [o for o in response.effective_edns_options if o.otype == 10][0] 1232 except IndexError: 1233 cookie_opt = None 1234 1235 try: 1236 cookie_opt_from_server = [o for o in response.message.options if o.otype == 10][0] 1237 except IndexError: 1238 cookie_opt_from_server = None 1239 1240 # supports_cookies is a boolean value that indicates whether the server 1241 # supports DNS cookies. Note that we are not looking for the value of 1242 # the server cookie itself, only whether the server supports cookies, 1243 # so we don't need to use get_cookie_jar_mapping(). 1244 supports_cookies = qname_obj is not None and server in qname_obj.cookie_jar 1245 1246 # RFC 7873: 5.2.1. No OPT RR or No COOKIE Option 1247 if response.query.edns < 0 or cookie_opt is None: # response.effective_server_cookie_status == Q.DNS_COOKIE_NO_COOKIE 1248 if cookie_opt_from_server is not None: 1249 cookie_errs.append(Errors.GratuitousCookie()) 1250 1251 elif supports_cookies: 1252 # The following are scenarios for DNS cookies. 1253 1254 # RFC 7873: 5.2.2. Malformed COOKIE Option 1255 if response.server_cookie_status == Q.DNS_COOKIE_IMPROPER_LENGTH: 1256 1257 issued_formerr = False 1258 if response.effective_server_cookie_status == Q.DNS_COOKIE_IMPROPER_LENGTH: 1259 if response.message.rcode() == dns.rcode.FORMERR: 1260 # The query resulting in the response we got was sent 1261 # with a COOKIE option with improper length, and the 1262 # return code for the response was FORMERR. 1263 issued_formerr = True 1264 elif response.responsive_cause_index is not None: 1265 retry = response.history[response.responsive_cause_index] 1266 if retry.cause == Q.RETRY_CAUSE_RCODE and \ 1267 retry.cause_arg == dns.rcode.FORMERR and \ 1268 (retry.action == Q.RETRY_ACTION_DISABLE_EDNS or \ 1269 (retry.action == Q.RETRY_ACTION_REMOVE_EDNS_OPTION and retry.action_arg == 10)): 1270 # We started with a COOKIE opt with improper length, 1271 # and, in response to FORMERR, from the server, we 1272 # changed EDNS behavior either by disabling EDNS or 1273 # removing the DNS COOKIE OPT, which resulted in us 1274 # getting a legitimate response. 1275 issued_formerr = True 1276 if not issued_formerr: 1277 cookie_errs.append(Errors.MalformedCookieWithoutFORMERR()) 1278 1279 # RFC 7873: 5.2.3. Only a Client Cookie 1280 # RFC 7873: 5.2.4. A Client Cookie and an Invalid Server Cookie 1281 if response.server_cookie_status in (Q.DNS_COOKIE_CLIENT_COOKIE_ONLY, Q.DNS_COOKIE_SERVER_COOKIE_BAD): 1282 if response.server_cookie_status == Q.DNS_COOKIE_CLIENT_COOKIE_ONLY: 1283 err_cls = Errors.NoServerCookieWithoutBADCOOKIE 1284 else: 1285 err_cls = Errors.InvalidServerCookieWithoutBADCOOKIE 1286 1287 issued_badcookie = False 1288 if response.effective_server_cookie_status in (Q.DNS_COOKIE_CLIENT_COOKIE_ONLY, Q.DNS_COOKIE_SERVER_COOKIE_BAD): 1289 # The query resulting in the response we got was sent with 1290 # a bad server cookie. 1291 if cookie_opt_from_server is None: 1292 cookie_errs.append(Errors.NoCookieOption()) 1293 elif len(cookie_opt_from_server.data) == 8: 1294 cookie_errs.append(Errors.NoServerCookie()) 1295 1296 if response.message.rcode() == 23: 1297 # The query resulting in the response we got was sent 1298 # with an invalid server cookie, and the result was 1299 # BADCOOKIE. 1300 issued_badcookie = True 1301 1302 elif response.responsive_cause_index is not None: 1303 retry = response.history[response.responsive_cause_index] 1304 if retry.cause == Q.RETRY_CAUSE_RCODE and \ 1305 retry.cause_arg == 23 and \ 1306 retry.action == Q.RETRY_ACTION_UPDATE_DNS_COOKIE: 1307 # We started with a COOKIE opt with an invalid server 1308 # cookie, and, in response to a BADCOOKIE response from 1309 # the server, we updated to a fresh DNS server cookie, 1310 # which resulted in us getting a legitimate response. 1311 issued_badcookie = True 1312 1313 if self._strict_cookies and not issued_badcookie: 1314 cookie_errs.append(err_cls()) 1315 1316 # RFC 7873: 5.2.5. A Client Cookie and a Valid Server Cookie 1317 if response.effective_server_cookie_status == Q.DNS_COOKIE_SERVER_COOKIE_FRESH: 1318 # The query resulting in the response we got was sent with only 1319 # a client cookie. 1320 if cookie_opt_from_server is None: 1321 cookie_errs.append(Errors.NoCookieOption()) 1322 elif len(cookie_opt_from_server.data) == 8: 1323 cookie_errs.append(Errors.NoServerCookie()) 1324 1325 if cookie_opt is not None and cookie_opt_from_server is not None: 1326 # RFC 7873: 5.3. Client cookie does not match 1327 if len(cookie_opt_from_server.data) >= 8 and \ 1328 cookie_opt_from_server.data[:8] != cookie_opt.data[:8]: 1329 cookie_errs.append(Errors.ClientCookieMismatch()) 1330 1331 # RFC 7873: 5.3. Client cookie has and invalid length 1332 if len(cookie_opt_from_server.data) < 8 or \ 1333 len(cookie_opt_from_server.data) > 40: 1334 cookie_errs.append(Errors.CookieInvalidLength(length=len(cookie_opt_from_server.data))) 1335 1336 for cookie_err in cookie_errs: 1337 Errors.DomainNameAnalysisError.insert_into_list(cookie_err, warnings, server, client, response) 1338 1339 def _populate_response_errors(self, qname_obj, response, server, client, warnings, errors): 1340 query = response.query 1341 1342 if qname_obj is not None: 1343 # if the response was complete (not truncated), then mark any 1344 # response flag issues as errors. Otherwise, mark them as 1345 # warnings. 1346 if response.is_complete_response(): 1347 group = errors 1348 else: 1349 group = warnings 1350 if qname_obj.analysis_type == ANALYSIS_TYPE_AUTHORITATIVE: 1351 if not response.is_authoritative(): 1352 ds_referral = False 1353 if query.rdtype == dns.rdatatype.DS: 1354 # handle DS as a special case 1355 if response.is_referral(query.qname, query.rdtype, query.rdclass, qname_obj.name): 1356 ds_referral = True 1357 1358 if ds_referral: 1359 Errors.DomainNameAnalysisError.insert_into_list(Errors.ReferralForDSQuery(parent=fmt.humanize_name(qname_obj.name)), group, server, client, response) 1360 else: 1361 Errors.DomainNameAnalysisError.insert_into_list(Errors.NotAuthoritative(), group, server, client, response) 1362 1363 elif qname_obj.analysis_type == ANALYSIS_TYPE_RECURSIVE: 1364 if response.recursion_desired() and not response.recursion_available(): 1365 Errors.DomainNameAnalysisError.insert_into_list(Errors.RecursionNotAvailable(), group, server, client, response) 1366 1367 # check for NOERROR, inconsistent with NXDOMAIN in ancestor 1368 if response.is_complete_response() and response.message.rcode() == dns.rcode.NOERROR and qname_obj.nxdomain_ancestor is not None: 1369 Errors.DomainNameAnalysisError.insert_into_list(Errors.InconsistentNXDOMAINAncestry(qname=fmt.humanize_name(response.query.qname), ancestor_qname=fmt.humanize_name(qname_obj.nxdomain_ancestor.name)), errors, server, client, response) 1370 1371 def _populate_foreign_class_warnings(self, qname_obj, response, server, client, warnings, errors): 1372 query = response.query 1373 cls = query.rdclass 1374 1375 if response.message is None: 1376 return 1377 1378 # if there was foriegn class data, then warn about it 1379 ans_cls = [r.rdclass for r in response.message.answer if r.rdclass != cls] 1380 auth_cls = [r.rdclass for r in response.message.authority if r.rdclass != cls] 1381 add_cls = [r.rdclass for r in response.message.additional if r.rdclass != cls] 1382 if ans_cls: 1383 Errors.DomainNameAnalysisError.insert_into_list(Errors.ForeignClassDataAnswer(cls=dns.rdataclass.to_text(ans_cls[0])), warnings, server, client, response) 1384 if auth_cls: 1385 Errors.DomainNameAnalysisError.insert_into_list(Errors.ForeignClassDataAuthority(cls=dns.rdataclass.to_text(auth_cls[0])), warnings, server, client, response) 1386 if add_cls: 1387 Errors.DomainNameAnalysisError.insert_into_list(Errors.ForeignClassDataAdditional(cls=dns.rdataclass.to_text(add_cls[0])), warnings, server, client, response) 1388 1389 def _populate_case_preservation_warnings(self, qname_obj, response, server, client, warnings, errors): 1390 query = response.query 1391 msg = response.message 1392 1393 # if there was a case mismatch, then warn about it 1394 if msg.question and query.qname.to_text() != msg.question[0].name.to_text(): 1395 Errors.DomainNameAnalysisError.insert_into_list(Errors.CasePreservationError(qname=fmt.humanize_name(query.qname, canonicalize=False)), warnings, server, client, response) 1396 1397 def _populate_wildcard_status(self, query, rrset_info, qname_obj, supported_algs): 1398 for wildcard_name in rrset_info.wildcard_info: 1399 if qname_obj is None: 1400 zone_name = wildcard_name.parent() 1401 else: 1402 zone_name = qname_obj.zone.name 1403 1404 servers_missing_nsec = set() 1405 for server, client in rrset_info.wildcard_info[wildcard_name].servers_clients: 1406 for response in rrset_info.wildcard_info[wildcard_name].servers_clients[(server,client)]: 1407 servers_missing_nsec.add((server,client,response)) 1408 1409 statuses = [] 1410 status_by_response = {} 1411 for nsec_set_info in rrset_info.wildcard_info[wildcard_name].nsec_set_info: 1412 if nsec_set_info.use_nsec3: 1413 status = Status.NSEC3StatusWildcard(rrset_info.rrset.name, wildcard_name, rrset_info.rrset.rdtype, zone_name, False, nsec_set_info) 1414 else: 1415 status = Status.NSECStatusWildcard(rrset_info.rrset.name, wildcard_name, rrset_info.rrset.rdtype, zone_name, False, nsec_set_info) 1416 1417 for nsec_rrset_info in nsec_set_info.rrsets.values(): 1418 self._populate_rrsig_status(query, nsec_rrset_info, qname_obj, supported_algs) 1419 1420 if status.validation_status == Status.NSEC_STATUS_VALID: 1421 if status not in statuses: 1422 statuses.append(status) 1423 1424 for server, client in nsec_set_info.servers_clients: 1425 for response in nsec_set_info.servers_clients[(server,client)]: 1426 if (server,client,response) in servers_missing_nsec: 1427 servers_missing_nsec.remove((server,client,response)) 1428 if status.validation_status == Status.NSEC_STATUS_VALID: 1429 if (server,client,response) in status_by_response: 1430 del status_by_response[(server,client,response)] 1431 else: 1432 status_by_response[(server,client,response)] = status 1433 1434 for (server,client,response), status in status_by_response.items(): 1435 if status not in statuses: 1436 statuses.append(status) 1437 1438 self.wildcard_status[rrset_info.wildcard_info[wildcard_name]] = statuses 1439 1440 for server, client, response in servers_missing_nsec: 1441 # by definition, DNSSEC was requested (otherwise we 1442 # wouldn't know this was a wildcard), so no need to 1443 # check for DO bit in request 1444 Errors.DomainNameAnalysisError.insert_into_list(Errors.MissingNSECForWildcard(), self.rrset_errors[rrset_info], server, client, response) 1445 1446 def _detect_cname_loop(self, name, trace=None): 1447 if name not in self.cname_targets: 1448 return 1449 if trace is None: 1450 trace = [] 1451 if name in trace: 1452 raise CNAMELoopDetected() 1453 1454 for target, cname_obj in self.cname_targets[name].items(): 1455 if cname_obj is not None: 1456 cname_obj._detect_cname_loop(target, trace=trace + [name]) 1457 1458 def _populate_cname_status(self, rrset_info): 1459 if rrset_info.rrset.rdtype == dns.rdatatype.CNAME: 1460 rdtypes = [r for (n, r) in self.yxrrset_proper if n == rrset_info.rrset.name and r != dns.rdatatype.CNAME] 1461 if rdtypes: 1462 Errors.DomainNameAnalysisError.insert_into_list(Errors.CNAMEWithOtherData(name=fmt.humanize_name(rrset_info.rrset.name)), self.rrset_warnings[rrset_info], None, None, None) 1463 1464 try: 1465 self._detect_cname_loop(rrset_info.rrset.name) 1466 except CNAMELoopDetected: 1467 Errors.DomainNameAnalysisError.insert_into_list(Errors.CNAMELoop(), self.rrset_errors[rrset_info], None, None, None) 1468 1469 def _initialize_rrset_status(self, rrset_info): 1470 self.rrset_warnings[rrset_info] = [] 1471 self.rrset_errors[rrset_info] = [] 1472 self.rrsig_status[rrset_info] = {} 1473 1474 def _populate_rrsig_status(self, query, rrset_info, qname_obj, supported_algs, populate_response_errors=True): 1475 self._initialize_rrset_status(rrset_info) 1476 1477 if qname_obj is None: 1478 zone_name = None 1479 else: 1480 zone_name = qname_obj.zone.name 1481 1482 if qname_obj is None: 1483 dnssec_algorithms_in_dnskey = set() 1484 dnssec_algorithms_in_ds = set() 1485 dnssec_algorithms_in_dlv = set() 1486 else: 1487 dnssec_algorithms_in_dnskey = qname_obj.zone.dnssec_algorithms_in_dnskey 1488 if query.rdtype == dns.rdatatype.DLV: 1489 dnssec_algorithms_in_ds = set() 1490 dnssec_algorithms_in_dlv = set() 1491 else: 1492 dnssec_algorithms_in_ds = qname_obj.zone.dnssec_algorithms_in_ds 1493 dnssec_algorithms_in_dlv = qname_obj.zone.dnssec_algorithms_in_dlv 1494 1495 # handle DNAMEs 1496 has_dname = set() 1497 if rrset_info.rrset.rdtype == dns.rdatatype.CNAME: 1498 if rrset_info.dname_info is not None: 1499 dname_info_list = [rrset_info.dname_info] 1500 dname_status = Status.CNAMEFromDNAMEStatus(rrset_info, None) 1501 elif rrset_info.cname_info_from_dname: 1502 dname_info_list = [c.dname_info for c in rrset_info.cname_info_from_dname] 1503 dname_status = Status.CNAMEFromDNAMEStatus(rrset_info.cname_info_from_dname[0], rrset_info) 1504 else: 1505 dname_info_list = [] 1506 dname_status = None 1507 1508 if dname_info_list: 1509 for dname_info in dname_info_list: 1510 for server, client in dname_info.servers_clients: 1511 has_dname.update([(server,client,response) for response in dname_info.servers_clients[(server,client)]]) 1512 1513 if rrset_info not in self.dname_status: 1514 self.dname_status[rrset_info] = [] 1515 self.dname_status[rrset_info].append(dname_status) 1516 1517 algs_signing_rrset = {} 1518 if dnssec_algorithms_in_dnskey or dnssec_algorithms_in_ds or dnssec_algorithms_in_dlv: 1519 for server, client in rrset_info.servers_clients: 1520 for response in rrset_info.servers_clients[(server, client)]: 1521 if (server, client, response) not in has_dname: 1522 algs_signing_rrset[(server, client, response)] = set() 1523 1524 for rrsig in rrset_info.rrsig_info: 1525 self.rrsig_status[rrset_info][rrsig] = {} 1526 1527 signer = self.get_name(rrsig.signer) 1528 1529 #XXX 1530 if signer is not None: 1531 1532 if signer.stub: 1533 continue 1534 1535 for server, client in rrset_info.rrsig_info[rrsig].servers_clients: 1536 for response in rrset_info.rrsig_info[rrsig].servers_clients[(server,client)]: 1537 if (server,client,response) not in algs_signing_rrset: 1538 continue 1539 algs_signing_rrset[(server,client,response)].add(rrsig.algorithm) 1540 if not dnssec_algorithms_in_dnskey.difference(algs_signing_rrset[(server,client,response)]) and \ 1541 not dnssec_algorithms_in_ds.difference(algs_signing_rrset[(server,client,response)]) and \ 1542 not dnssec_algorithms_in_dlv.difference(algs_signing_rrset[(server,client,response)]): 1543 del algs_signing_rrset[(server,client,response)] 1544 1545 # define self-signature 1546 self_sig = rrset_info.rrset.rdtype == dns.rdatatype.DNSKEY and rrsig.signer == rrset_info.rrset.name 1547 1548 checked_keys = set() 1549 for dnskey_set, dnskey_meta in signer.get_dnskey_sets(): 1550 validation_status_mapping = { True: set(), False: set(), None: set() } 1551 for dnskey in dnskey_set: 1552 # if we've already checked this key (i.e., in 1553 # another DNSKEY RRset) then continue 1554 if dnskey in checked_keys: 1555 continue 1556 # if this is a RRSIG over DNSKEY RRset, then make sure we're validating 1557 # with a DNSKEY that is actually in the set 1558 if self_sig and dnskey.rdata not in rrset_info.rrset: 1559 continue 1560 checked_keys.add(dnskey) 1561 if not (dnskey.rdata.protocol == 3 and \ 1562 rrsig.key_tag in (dnskey.key_tag, dnskey.key_tag_no_revoke) and \ 1563 rrsig.algorithm == dnskey.rdata.algorithm): 1564 continue 1565 rrsig_status = Status.RRSIGStatus(rrset_info, rrsig, dnskey, zone_name, fmt.datetime_to_timestamp(self.analysis_end), supported_algs) 1566 validation_status_mapping[rrsig_status.signature_valid].add(rrsig_status) 1567 1568 # if we got results for multiple keys, then just select the one that validates 1569 for status in True, False, None: 1570 if validation_status_mapping[status]: 1571 for rrsig_status in validation_status_mapping[status]: 1572 self.rrsig_status[rrsig_status.rrset][rrsig_status.rrsig][rrsig_status.dnskey] = rrsig_status 1573 1574 if self.is_zone() and rrset_info.rrset.name == self.name and \ 1575 rrset_info.rrset.rdtype != dns.rdatatype.DS and \ 1576 rrsig_status.dnskey is not None: 1577 if rrset_info.rrset.rdtype == dns.rdatatype.DNSKEY: 1578 self.ksks.add(rrsig_status.dnskey) 1579 else: 1580 self.zsks.add(rrsig_status.dnskey) 1581 1582 key = rrsig_status.rrset, rrsig_status.rrsig 1583 break 1584 1585 # no corresponding DNSKEY 1586 if not self.rrsig_status[rrset_info][rrsig]: 1587 rrsig_status = Status.RRSIGStatus(rrset_info, rrsig, None, zone_name, fmt.datetime_to_timestamp(self.analysis_end), supported_algs) 1588 self.rrsig_status[rrsig_status.rrset][rrsig_status.rrsig][None] = rrsig_status 1589 1590 # list errors for rrsets with which no RRSIGs were returned or not all algorithms were accounted for 1591 for server,client,response in algs_signing_rrset: 1592 # if DNSSEC was not requested (e.g., for diagnostics purposes), 1593 # then don't report an issue 1594 if not (response.query.edns >= 0 and response.query.edns_flags & dns.flags.DO): 1595 continue 1596 1597 errors = self.rrset_errors[rrset_info] 1598 # report an error if all RRSIGs are missing 1599 if not algs_signing_rrset[(server,client,response)]: 1600 if response.dnssec_requested(): 1601 Errors.DomainNameAnalysisError.insert_into_list(Errors.MissingRRSIG(), errors, server, client, response) 1602 elif qname_obj is not None and qname_obj.zone.server_responsive_with_do(server,client,response.effective_tcp,True): 1603 Errors.DomainNameAnalysisError.insert_into_list(Errors.UnableToRetrieveDNSSECRecords(), errors, server, client, response) 1604 else: 1605 # report an error if RRSIGs for one or more algorithms are missing 1606 for alg in dnssec_algorithms_in_dnskey.difference(algs_signing_rrset[(server,client,response)]): 1607 Errors.DomainNameAnalysisError.insert_into_list(Errors.MissingRRSIGForAlgDNSKEY(algorithm=alg), errors, server, client, response) 1608 for alg in dnssec_algorithms_in_ds.difference(algs_signing_rrset[(server,client,response)]): 1609 Errors.DomainNameAnalysisError.insert_into_list(Errors.MissingRRSIGForAlgDS(algorithm=alg), errors, server, client, response) 1610 for alg in dnssec_algorithms_in_dlv.difference(algs_signing_rrset[(server,client,response)]): 1611 Errors.DomainNameAnalysisError.insert_into_list(Errors.MissingRRSIGForAlgDLV(algorithm=alg), errors, server, client, response) 1612 1613 self._populate_wildcard_status(query, rrset_info, qname_obj, supported_algs) 1614 self._populate_cname_status(rrset_info) 1615 1616 if populate_response_errors: 1617 for server,client in rrset_info.servers_clients: 1618 for response in rrset_info.servers_clients[(server,client)]: 1619 self._populate_responsiveness_errors(qname_obj, response, server, client, self.rrset_warnings[rrset_info], self.rrset_errors[rrset_info]) 1620 self._populate_response_errors(qname_obj, response, server, client, self.rrset_warnings[rrset_info], self.rrset_errors[rrset_info]) 1621 self._populate_edns_errors(qname_obj, response, server, client, self.rrset_warnings[rrset_info], self.rrset_errors[rrset_info]) 1622 self._populate_cookie_errors(qname_obj, response, server, client, self.rrset_warnings[rrset_info], self.rrset_errors[rrset_info]) 1623 self._populate_foreign_class_warnings(qname_obj, response, server, client, self.rrset_warnings[rrset_info], self.rrset_errors[rrset_info]) 1624 self._populate_case_preservation_warnings(qname_obj, response, server, client, self.rrset_warnings[rrset_info], self.rrset_errors[rrset_info]) 1625 1626 def _populate_invalid_response_status(self, query): 1627 self.response_errors[query] = [] 1628 for error_info in query.error_info: 1629 for server, client in error_info.servers_clients: 1630 for response in error_info.servers_clients[(server, client)]: 1631 if error_info.code == Q.RESPONSE_ERROR_NETWORK_ERROR: 1632 Errors.DomainNameAnalysisError.insert_into_list(Errors.NetworkError(tcp=response.effective_tcp, errno=errno.errorcode.get(error_info.arg, 'UNKNOWN')), self.response_errors[query], server, client, response) 1633 if error_info.code == Q.RESPONSE_ERROR_FORMERR: 1634 #TODO determine if this was related to truncation; 1635 #TODO add EDNS opt missing error, as appropriate 1636 Errors.DomainNameAnalysisError.insert_into_list(Errors.FormError(tcp=response.effective_tcp, msg_size=response.msg_size), self.response_errors[query], server, client, response) 1637 elif error_info.code == Q.RESPONSE_ERROR_TIMEOUT: 1638 attempts = 1 1639 for i in range(len(response.history) - 1, -1, -1): 1640 if response.history[i].action in (Q.RETRY_ACTION_USE_TCP, Q.RETRY_ACTION_USE_UDP): 1641 break 1642 attempts += 1 1643 Errors.DomainNameAnalysisError.insert_into_list(Errors.Timeout(tcp=response.effective_tcp, attempts=attempts), self.response_errors[query], server, client, response) 1644 elif error_info.code == Q.RESPONSE_ERROR_INVALID_RCODE: 1645 # if we used EDNS, the response did not, and the RCODE 1646 # was FORMERR, SERVFAIL, or NOTIMP, then this is a 1647 # legitimate reason for the RCODE 1648 if response.effective_edns >= 0 and response.message.edns < 0 and \ 1649 response.message.rcode() in (dns.rcode.FORMERR, dns.rcode.SERVFAIL, dns.rcode.NOTIMP): 1650 pass 1651 # if we used EDNS, the response also used EDNS, and the 1652 # RCODE was BADVERS, then this is a legitimate reason 1653 # for the RCODE 1654 elif response.effective_edns >= 0 and response.message.edns >= 0 and \ 1655 response.message.rcode() == dns.rcode.BADVERS: 1656 pass 1657 else: 1658 Errors.DomainNameAnalysisError.insert_into_list(Errors.InvalidRcode(tcp=response.effective_tcp, rcode=dns.rcode.to_text(response.message.rcode())), self.response_errors[query], server, client, response) 1659 elif error_info.code == Q.RESPONSE_ERROR_OTHER: 1660 Errors.DomainNameAnalysisError.insert_into_list(Errors.UnknownResponseError(tcp=response.effective_tcp), self.response_errors[query], server, client, response) 1661 1662 self.response_warnings[query] = [] 1663 for referral_info in query.referral_info: 1664 for server, client in referral_info.servers_clients: 1665 for response in referral_info.servers_clients[(server, client)]: 1666 if response.is_authoritative(): 1667 Errors.DomainNameAnalysisError.insert_into_list(Errors.AuthoritativeReferral(), self.response_warnings[query], server, client, response) 1668 1669 for truncated_info in query.truncated_info: 1670 for server, client in truncated_info.servers_clients: 1671 for response in truncated_info.servers_clients[(server, client)]: 1672 self._populate_responsiveness_errors(self, response, server, client, self.response_warnings[query], self.response_errors[query]) 1673 self._populate_response_errors(self, response, server, client, self.response_warnings[query], self.response_errors[query]) 1674 self._populate_edns_errors(self, response, server, client, self.response_warnings[query], self.response_errors[query]) 1675 self._populate_cookie_errors(self, response, server, client, self.response_warnings[query], self.response_errors[query]) 1676 self._populate_foreign_class_warnings(self, response, server, client, self.response_warnings[query], self.response_errors[query]) 1677 self._populate_case_preservation_warnings(self, response, server, client, self.response_warnings[query], self.response_errors[query]) 1678 1679 def _populate_rrsig_status_all(self, supported_algs): 1680 self.rrset_warnings = {} 1681 self.rrset_errors = {} 1682 self.rrsig_status = {} 1683 self.dname_status = {} 1684 self.wildcard_status = {} 1685 self.response_errors = {} 1686 self.response_warnings = {} 1687 1688 if self.is_zone(): 1689 self.zsks = set() 1690 self.ksks = set() 1691 1692 _logger.debug('Assessing RRSIG status of %s...' % (fmt.humanize_name(self.name))) 1693 for (qname, rdtype), query in self.queries.items(): 1694 1695 items_to_validate = [] 1696 for rrset_info in query.answer_info: 1697 items_to_validate.append(rrset_info) 1698 if rrset_info.dname_info is not None: 1699 items_to_validate.append(rrset_info.dname_info) 1700 for cname_rrset_info in rrset_info.cname_info_from_dname: 1701 items_to_validate.append(cname_rrset_info.dname_info) 1702 items_to_validate.append(cname_rrset_info) 1703 1704 for rrset_info in items_to_validate: 1705 qname_obj = self.get_name(rrset_info.rrset.name) 1706 if rdtype == dns.rdatatype.DS and \ 1707 qname_obj.name == rrset_info.rrset.name and qname_obj.is_zone(): 1708 qname_obj = qname_obj.parent 1709 elif rdtype == dns.rdatatype.DLV: 1710 qname_obj = qname_obj.dlv_parent 1711 1712 self._populate_rrsig_status(query, rrset_info, qname_obj, supported_algs) 1713 1714 self._populate_invalid_response_status(query) 1715 1716 def _finalize_key_roles(self): 1717 if self.is_zone(): 1718 self.published_keys = set(self.get_dnskeys()).difference(self.zsks.union(self.ksks)) 1719 self.revoked_keys = set([x for x in self.get_dnskeys() if x.rdata.flags & fmt.DNSKEY_FLAGS['revoke']]) 1720 1721 def _populate_ns_status(self, warn_no_ipv4=True, warn_no_ipv6=False): 1722 if not self.is_zone(): 1723 return 1724 1725 if self.parent is None: 1726 return 1727 1728 if self.analysis_type != ANALYSIS_TYPE_AUTHORITATIVE: 1729 return 1730 1731 if self.explicit_delegation: 1732 return 1733 1734 all_names = self.get_ns_names() 1735 names_from_child = self.get_ns_names_in_child() 1736 names_from_parent = self.get_ns_names_in_parent() 1737 1738 auth_ns_response = self.queries[(self.name, dns.rdatatype.NS)].is_valid_complete_authoritative_response_any() 1739 1740 glue_mapping = self.get_glue_ip_mapping() 1741 auth_mapping = self.get_auth_ns_ip_mapping() 1742 1743 ns_names_not_in_child = [] 1744 ns_names_not_in_parent = [] 1745 names_error_resolving = [] 1746 names_with_glue_mismatch_ipv4 = [] 1747 names_with_glue_mismatch_ipv6 = [] 1748 names_with_no_glue_ipv4 = [] 1749 names_with_no_glue_ipv6 = [] 1750 names_with_no_auth_ipv4 = [] 1751 names_with_no_auth_ipv6 = [] 1752 names_missing_glue = [] 1753 names_missing_auth = [] 1754 1755 names_auth_private = set() 1756 names_auth_zero = set() 1757 names_glue_private = set() 1758 names_glue_zero = set() 1759 1760 for name in all_names: 1761 # if name resolution resulted in an error (other than NXDOMAIN) 1762 if name not in auth_mapping: 1763 auth_addrs = set() 1764 names_error_resolving.append(name) 1765 else: 1766 auth_addrs = auth_mapping[name] 1767 # if name resolution completed successfully, but the response was 1768 # negative for both A and AAAA (NXDOMAIN or NODATA) 1769 if not auth_mapping[name]: 1770 names_missing_auth.append(name) 1771 1772 for addr in auth_addrs: 1773 if LOOPBACK_IPV4_RE.match(addr) or addr == LOOPBACK_IPV6 or \ 1774 RFC_1918_RE.match(addr) or LINK_LOCAL_RE.match(addr) or UNIQ_LOCAL_RE.match(addr): 1775 names_auth_private.add(name) 1776 if ZERO_SLASH8_RE.search(addr): 1777 names_auth_zero.add(name) 1778 1779 if names_from_parent: 1780 name_in_parent = name in names_from_parent 1781 elif self.delegation_status == Status.DELEGATION_STATUS_INCOMPLETE: 1782 name_in_parent = False 1783 else: 1784 name_in_parent = None 1785 1786 if name_in_parent: 1787 # if glue is required and not supplied 1788 if name.is_subdomain(self.name) and not glue_mapping[name]: 1789 names_missing_glue.append(name) 1790 1791 for addr in glue_mapping[name]: 1792 if LOOPBACK_IPV4_RE.match(addr) or addr == LOOPBACK_IPV6 or \ 1793 RFC_1918_RE.match(addr) or LINK_LOCAL_RE.match(addr) or UNIQ_LOCAL_RE.match(addr): 1794 names_glue_private.add(name) 1795 if ZERO_SLASH8_RE.search(addr): 1796 names_glue_zero.add(name) 1797 1798 # if there are both glue and authoritative addresses supplied, check that it matches the authoritative response 1799 if glue_mapping[name] and auth_addrs: 1800 # there are authoritative address records either of type A 1801 # or AAAA and also glue records of either type A or AAAA 1802 1803 glue_addrs_ipv4 = set([x for x in glue_mapping[name] if x.version == 4]) 1804 glue_addrs_ipv6 = set([x for x in glue_mapping[name] if x.version == 6]) 1805 auth_addrs_ipv4 = set([x for x in auth_addrs if x.version == 4]) 1806 auth_addrs_ipv6 = set([x for x in auth_addrs if x.version == 6]) 1807 1808 if auth_addrs_ipv4: 1809 # there are authoritative A records for the name... 1810 if not glue_addrs_ipv4: 1811 # ...but no A glue 1812 names_with_no_glue_ipv4.append(name) 1813 elif glue_addrs_ipv4 != auth_addrs_ipv4: 1814 # ...but the A glue does not match 1815 names_with_glue_mismatch_ipv4.append((name, glue_addrs_ipv4, auth_addrs_ipv4)) 1816 elif glue_addrs_ipv4: 1817 # there are A glue records for the name 1818 # but no authoritative A records. 1819 names_with_no_auth_ipv4.append(name) 1820 1821 if auth_addrs_ipv6: 1822 # there are authoritative AAAA records for the name 1823 if not glue_addrs_ipv6: 1824 # ...but no AAAA glue 1825 names_with_no_glue_ipv6.append(name) 1826 elif glue_addrs_ipv6 != auth_addrs_ipv6: 1827 # ...but the AAAA glue does not match 1828 names_with_glue_mismatch_ipv6.append((name, glue_addrs_ipv6, auth_addrs_ipv6)) 1829 elif glue_addrs_ipv6: 1830 # there are AAAA glue records for the name 1831 # but no authoritative AAAA records. 1832 names_with_no_auth_ipv6.append(name) 1833 1834 elif name_in_parent is False: 1835 ns_names_not_in_parent.append(name) 1836 1837 if name not in names_from_child and auth_ns_response: 1838 ns_names_not_in_child.append(name) 1839 1840 if ns_names_not_in_child: 1841 ns_names_not_in_child.sort() 1842 self.delegation_warnings[dns.rdatatype.DS].append(Errors.NSNameNotInChild(names=[fmt.humanize_name(x) for x in ns_names_not_in_child], parent=fmt.humanize_name(self.parent_name()))) 1843 1844 if ns_names_not_in_parent: 1845 ns_names_not_in_child.sort() 1846 self.delegation_warnings[dns.rdatatype.DS].append(Errors.NSNameNotInParent(names=[fmt.humanize_name(x) for x in ns_names_not_in_parent], parent=fmt.humanize_name(self.parent_name()))) 1847 1848 if names_error_resolving: 1849 names_error_resolving.sort() 1850 self.zone_errors.append(Errors.ErrorResolvingNSName(names=[fmt.humanize_name(x) for x in names_error_resolving])) 1851 1852 if not self._allow_private: 1853 if names_auth_private: 1854 names_auth_private = list(names_auth_private) 1855 names_auth_private.sort() 1856 self.zone_errors.append(Errors.NSNameResolvesToPrivateIP(names=[fmt.humanize_name(x) for x in names_auth_private])) 1857 1858 if names_glue_private: 1859 names_glue_private = list(names_glue_private) 1860 names_glue_private.sort() 1861 self.delegation_errors[dns.rdatatype.DS].append(Errors.GlueReferencesPrivateIP(names=[fmt.humanize_name(x) for x in names_glue_private])) 1862 1863 if names_with_no_glue_ipv4: 1864 names_with_no_glue_ipv4.sort() 1865 for name in names_with_no_glue_ipv4: 1866 self.delegation_warnings[dns.rdatatype.DS].append(Errors.MissingGlueIPv4(name=fmt.humanize_name(name))) 1867 1868 if names_with_no_glue_ipv6: 1869 names_with_no_glue_ipv6.sort() 1870 for name in names_with_no_glue_ipv6: 1871 self.delegation_warnings[dns.rdatatype.DS].append(Errors.MissingGlueIPv6(name=fmt.humanize_name(name))) 1872 1873 if names_with_no_auth_ipv4: 1874 names_with_no_auth_ipv4.sort() 1875 for name in names_with_no_auth_ipv4: 1876 self.delegation_warnings[dns.rdatatype.DS].append(Errors.ExtraGlueIPv4(name=fmt.humanize_name(name))) 1877 1878 if names_with_no_auth_ipv6: 1879 names_with_no_auth_ipv6.sort() 1880 for name in names_with_no_auth_ipv6: 1881 self.delegation_warnings[dns.rdatatype.DS].append(Errors.ExtraGlueIPv6(name=fmt.humanize_name(name))) 1882 1883 if names_with_glue_mismatch_ipv4: 1884 names_with_glue_mismatch_ipv4.sort() 1885 for name, glue_addrs, auth_addrs in names_with_glue_mismatch_ipv4: 1886 glue_addrs = list(glue_addrs) 1887 glue_addrs.sort() 1888 auth_addrs = list(auth_addrs) 1889 auth_addrs.sort() 1890 self.delegation_warnings[dns.rdatatype.DS].append(Errors.GlueMismatchError(name=fmt.humanize_name(name), glue_addresses=glue_addrs, auth_addresses=auth_addrs)) 1891 1892 if names_with_glue_mismatch_ipv6: 1893 names_with_glue_mismatch_ipv6.sort() 1894 for name, glue_addrs, auth_addrs in names_with_glue_mismatch_ipv6: 1895 glue_addrs = list(glue_addrs) 1896 glue_addrs.sort() 1897 auth_addrs = list(auth_addrs) 1898 auth_addrs.sort() 1899 self.delegation_warnings[dns.rdatatype.DS].append(Errors.GlueMismatchError(name=fmt.humanize_name(name), glue_addresses=glue_addrs, auth_addresses=auth_addrs)) 1900 1901 if names_missing_glue: 1902 names_missing_glue.sort() 1903 self.delegation_warnings[dns.rdatatype.DS].append(Errors.MissingGlueForNSName(names=[fmt.humanize_name(x) for x in names_missing_glue])) 1904 1905 if names_missing_auth: 1906 names_missing_auth.sort() 1907 self.zone_errors.append(Errors.NoAddressForNSName(names=[fmt.humanize_name(x) for x in names_missing_auth])) 1908 1909 ips_from_parent = self.get_servers_in_parent() 1910 ips_from_parent_ipv4 = [x for x in ips_from_parent if x.version == 4] 1911 ips_from_parent_ipv6 = [x for x in ips_from_parent if x.version == 6] 1912 1913 ips_from_child = self.get_servers_in_child() 1914 ips_from_child_ipv4 = [x for x in ips_from_child if x.version == 4] 1915 ips_from_child_ipv6 = [x for x in ips_from_child if x.version == 6] 1916 1917 if not (ips_from_parent_ipv4 or ips_from_child_ipv4) and warn_no_ipv4: 1918 if ips_from_parent_ipv4: 1919 reference = 'child' 1920 elif ips_from_child_ipv4: 1921 reference = 'parent' 1922 else: 1923 reference = 'parent or child' 1924 self.zone_warnings.append(Errors.NoNSAddressesForIPv4(reference=reference)) 1925 1926 if not (ips_from_parent_ipv6 or ips_from_child_ipv6) and warn_no_ipv6: 1927 if ips_from_parent_ipv6: 1928 reference = 'child' 1929 elif ips_from_child_ipv6: 1930 reference = 'parent' 1931 else: 1932 reference = 'parent or child' 1933 self.zone_warnings.append(Errors.NoNSAddressesForIPv6(reference=reference)) 1934 1935 def _populate_delegation_status(self, supported_algs, supported_digest_algs): 1936 self.ds_status_by_ds = {} 1937 self.ds_status_by_dnskey = {} 1938 self.zone_errors = [] 1939 self.zone_warnings = [] 1940 self.zone_status = [] 1941 self.delegation_errors = {} 1942 self.delegation_warnings = {} 1943 self.delegation_status = {} 1944 self.dnskey_with_ds = set() 1945 1946 self._populate_ds_status(dns.rdatatype.DS, supported_algs, supported_digest_algs) 1947 if self.dlv_parent is not None: 1948 self._populate_ds_status(dns.rdatatype.DLV, supported_algs, supported_digest_algs) 1949 self._populate_ns_status() 1950 self._populate_server_status() 1951 1952 def _populate_ds_status(self, rdtype, supported_algs, supported_digest_algs): 1953 if rdtype not in (dns.rdatatype.DS, dns.rdatatype.DLV): 1954 raise ValueError('Type can only be DS or DLV.') 1955 if self.parent is None: 1956 return 1957 if rdtype == dns.rdatatype.DLV: 1958 name = self.dlv_name 1959 if name is None: 1960 raise ValueError('No DLV specified for DomainNameAnalysis object.') 1961 else: 1962 name = self.name 1963 1964 _logger.debug('Assessing delegation status of %s...' % (fmt.humanize_name(self.name))) 1965 self.ds_status_by_ds[rdtype] = {} 1966 self.ds_status_by_dnskey[rdtype] = {} 1967 self.delegation_warnings[rdtype] = [] 1968 self.delegation_errors[rdtype] = [] 1969 self.delegation_status[rdtype] = None 1970 1971 try: 1972 ds_rrset_answer_info = self.queries[(name, rdtype)].answer_info 1973 except KeyError: 1974 # zones should have DS queries 1975 if self.is_zone(): 1976 raise 1977 else: 1978 return 1979 1980 ds_rrset_exists = False 1981 secure_path = False 1982 1983 bailiwick_map, default_bailiwick = self.get_bailiwick_mapping() 1984 1985 if (self.name, dns.rdatatype.DNSKEY) in self.queries: 1986 dnskey_multiquery = self.queries[(self.name, dns.rdatatype.DNSKEY)] 1987 else: 1988 dnskey_multiquery = self._query_cls(self.name, dns.rdatatype.DNSKEY, dns.rdataclass.IN) 1989 1990 # populate all the servers queried for DNSKEYs to determine 1991 # what problems there were with regard to DS records and if 1992 # there is at least one match 1993 dnskey_server_client_responses = set() 1994 for dnskey_query in dnskey_multiquery.queries.values(): 1995 # for responsive servers consider only those designated as 1996 # authoritative 1997 for server in set(dnskey_query.responses).intersection(self.zone.get_auth_or_designated_servers()): 1998 bailiwick = bailiwick_map.get(server, default_bailiwick) 1999 for client in dnskey_query.responses[server]: 2000 response = dnskey_query.responses[server][client] 2001 if response.is_valid_response() and response.is_complete_response() and not response.is_referral(self.name, dns.rdatatype.DNSKEY, dnskey_query.rdclass, bailiwick): 2002 dnskey_server_client_responses.add((server,client,response)) 2003 2004 for ds_rrset_info in ds_rrset_answer_info: 2005 # there are CNAMEs that show up here... 2006 if not (ds_rrset_info.rrset.name == name and ds_rrset_info.rrset.rdtype == rdtype): 2007 continue 2008 ds_rrset_exists = True 2009 2010 # for each set of DS records provided by one or more servers, 2011 # identify the set of DNSSEC algorithms and the set of digest 2012 # algorithms per algorithm/key tag combination 2013 ds_algs = set() 2014 supported_ds_algs = set() 2015 for ds_rdata in ds_rrset_info.rrset: 2016 if ds_rdata.algorithm in supported_algs and ds_rdata.digest_type in supported_digest_algs: 2017 supported_ds_algs.add(ds_rdata.algorithm) 2018 ds_algs.add(ds_rdata.algorithm) 2019 2020 if supported_ds_algs: 2021 secure_path = True 2022 2023 algs_signing_sep = {} 2024 algs_validating_sep = {} 2025 for server,client,response in dnskey_server_client_responses: 2026 algs_signing_sep[(server,client,response)] = set() 2027 algs_validating_sep[(server,client,response)] = set() 2028 2029 for ds_rdata in ds_rrset_info.rrset: 2030 self.ds_status_by_ds[rdtype][ds_rdata] = {} 2031 2032 for dnskey_info in dnskey_multiquery.answer_info: 2033 # there are CNAMEs that show up here... 2034 if not (dnskey_info.rrset.name == self.name and dnskey_info.rrset.rdtype == dns.rdatatype.DNSKEY): 2035 continue 2036 2037 validation_status_mapping = { True: set(), False: set(), None: set() } 2038 for dnskey_rdata in dnskey_info.rrset: 2039 dnskey = self._dnskeys[dnskey_rdata] 2040 2041 if dnskey not in self.ds_status_by_dnskey[rdtype]: 2042 self.ds_status_by_dnskey[rdtype][dnskey] = {} 2043 2044 # if the key tag doesn't match, then go any farther 2045 if not (ds_rdata.key_tag in (dnskey.key_tag, dnskey.key_tag_no_revoke) and \ 2046 ds_rdata.algorithm == dnskey.rdata.algorithm): 2047 continue 2048 2049 # check if the digest is a match 2050 ds_status = Status.DSStatus(ds_rdata, ds_rrset_info, dnskey, supported_digest_algs) 2051 validation_status_mapping[ds_status.digest_valid].add(ds_status) 2052 2053 # if dnskey exists, then add to dnskey_with_ds 2054 if ds_status.validation_status not in \ 2055 (Status.DS_STATUS_INDETERMINATE_NO_DNSKEY, Status.DS_STATUS_INDETERMINATE_MATCH_PRE_REVOKE): 2056 self.dnskey_with_ds.add(dnskey) 2057 2058 for rrsig in dnskey_info.rrsig_info: 2059 # move along if DNSKEY is not self-signing 2060 if dnskey not in self.rrsig_status[dnskey_info][rrsig]: 2061 continue 2062 2063 # move along if key tag is not the same (i.e., revoke) 2064 if dnskey.key_tag != rrsig.key_tag: 2065 continue 2066 2067 for (server,client) in dnskey_info.rrsig_info[rrsig].servers_clients: 2068 for response in dnskey_info.rrsig_info[rrsig].servers_clients[(server,client)]: 2069 if (server,client,response) in algs_signing_sep: 2070 # note that this algorithm is part of a self-signing DNSKEY 2071 algs_signing_sep[(server,client,response)].add(rrsig.algorithm) 2072 if not ds_algs.difference(algs_signing_sep[(server,client,response)]): 2073 del algs_signing_sep[(server,client,response)] 2074 2075 if (server,client,response) in algs_validating_sep: 2076 # retrieve the status of the DNSKEY RRSIG 2077 rrsig_status = self.rrsig_status[dnskey_info][rrsig][dnskey] 2078 2079 # if the DS digest and the RRSIG are both valid, and the digest algorithm 2080 # is not deprecated then mark it as a SEP 2081 if ds_status.validation_status == Status.DS_STATUS_VALID and \ 2082 rrsig_status.validation_status == Status.RRSIG_STATUS_VALID: 2083 # note that this algorithm is part of a successful self-signing DNSKEY 2084 algs_validating_sep[(server,client,response)].add(rrsig.algorithm) 2085 if not ds_algs.difference(algs_validating_sep[(server,client,response)]): 2086 del algs_validating_sep[(server,client,response)] 2087 2088 # if we got results for multiple keys, then just select the one that validates 2089 for status in True, False, None: 2090 if validation_status_mapping[status]: 2091 for ds_status in validation_status_mapping[status]: 2092 self.ds_status_by_ds[rdtype][ds_status.ds][ds_status.dnskey] = ds_status 2093 self.ds_status_by_dnskey[rdtype][ds_status.dnskey][ds_status.ds] = ds_status 2094 break 2095 2096 # no corresponding DNSKEY 2097 if not self.ds_status_by_ds[rdtype][ds_rdata]: 2098 ds_status = Status.DSStatus(ds_rdata, ds_rrset_info, None, supported_digest_algs) 2099 self.ds_status_by_ds[rdtype][ds_rdata][None] = ds_status 2100 if None not in self.ds_status_by_dnskey[rdtype]: 2101 self.ds_status_by_dnskey[rdtype][None] = {} 2102 self.ds_status_by_dnskey[rdtype][None][ds_rdata] = ds_status 2103 2104 if dnskey_server_client_responses: 2105 if not algs_validating_sep: 2106 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_SECURE 2107 else: 2108 for server,client,response in dnskey_server_client_responses: 2109 if (server,client,response) not in algs_validating_sep or \ 2110 supported_ds_algs.intersection(algs_validating_sep[(server,client,response)]): 2111 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_SECURE 2112 elif supported_ds_algs: 2113 Errors.DomainNameAnalysisError.insert_into_list(Errors.NoSEP(source=dns.rdatatype.to_text(rdtype)), self.delegation_errors[rdtype], server, client, response) 2114 2115 # report an error if one or more algorithms are incorrectly validated 2116 for (server,client,response) in algs_signing_sep: 2117 for alg in ds_algs.difference(algs_signing_sep[(server,client,response)]): 2118 Errors.DomainNameAnalysisError.insert_into_list(Errors.MissingSEPForAlg(algorithm=alg, source=dns.rdatatype.to_text(rdtype)), self.delegation_errors[rdtype], server, client, response) 2119 else: 2120 Errors.DomainNameAnalysisError.insert_into_list(Errors.NoSEP(source=dns.rdatatype.to_text(rdtype)), self.delegation_errors[rdtype], None, None, None) 2121 2122 if self.delegation_status[rdtype] is None: 2123 if ds_rrset_answer_info: 2124 if ds_rrset_exists: 2125 # DS RRs exist 2126 if secure_path: 2127 # If any DNSSEC algorithms are supported, then status 2128 # is bogus because there should have been matching KSK. 2129 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_BOGUS 2130 else: 2131 # If no algorithms are supported, then this is a 2132 # provably insecure delegation. 2133 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_INSECURE 2134 else: 2135 # Only CNAME returned for DS query. With no DS records and 2136 # no valid non-existence proof, the delegation is bogus. 2137 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_BOGUS 2138 elif self.parent.signed: 2139 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_BOGUS 2140 for nsec_status_list in [self.nxdomain_status[n] for n in self.nxdomain_status if n.qname == name and n.rdtype == dns.rdatatype.DS] + \ 2141 [self.nodata_status[n] for n in self.nodata_status if n.qname == name and n.rdtype == dns.rdatatype.DS]: 2142 for nsec_status in nsec_status_list: 2143 if nsec_status.validation_status == Status.NSEC_STATUS_VALID: 2144 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_INSECURE 2145 break 2146 else: 2147 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_INSECURE 2148 2149 # if no servers (designated or stealth authoritative) respond or none 2150 # respond authoritatively, then make the delegation as lame 2151 if not self.get_auth_or_designated_servers(): 2152 if self.delegation_status[rdtype] == Status.DELEGATION_STATUS_INSECURE: 2153 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_LAME 2154 elif not self.get_responsive_auth_or_designated_servers(): 2155 if self.delegation_status[rdtype] == Status.DELEGATION_STATUS_INSECURE: 2156 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_LAME 2157 elif not self.get_valid_auth_or_designated_servers(): 2158 if self.delegation_status[rdtype] == Status.DELEGATION_STATUS_INSECURE: 2159 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_LAME 2160 elif self.analysis_type == ANALYSIS_TYPE_AUTHORITATIVE and not self._auth_servers_clients: 2161 if self.delegation_status[rdtype] == Status.DELEGATION_STATUS_INSECURE: 2162 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_LAME 2163 2164 if rdtype == dns.rdatatype.DS: 2165 try: 2166 ds_nxdomain_info = [x for x in self.queries[(name, rdtype)].nxdomain_info if x.qname == name and x.rdtype == dns.rdatatype.DS][0] 2167 except IndexError: 2168 pass 2169 else: 2170 if self.referral_rdtype is not None: 2171 # now check if there is a parent server that is providing an 2172 # NXDOMAIN for the referral. If so, this is due to the 2173 # delegation not being found on all servers. 2174 try: 2175 delegation_nxdomain_info = [x for x in self.queries[(name, self.referral_rdtype)].nxdomain_info if x.qname == name and x.rdtype == self.referral_rdtype][0] 2176 except IndexError: 2177 # if there were not NXDOMAINs received in response to the 2178 # referral query, then use all the servers/clients 2179 servers_clients = ds_nxdomain_info.servers_clients 2180 else: 2181 # if there were NXDOMAINs received in response to the 2182 # referral query, then filter those out 2183 servers_clients = set(ds_nxdomain_info.servers_clients).difference(delegation_nxdomain_info.servers_clients) 2184 else: 2185 # if there was no referral query, then use all the 2186 # servers/clients 2187 servers_clients = ds_nxdomain_info.servers_clients 2188 2189 # if there were any remaining NXDOMAIN responses, then add the 2190 # error 2191 if servers_clients: 2192 err = Errors.NoNSInParent(parent=fmt.humanize_name(self.parent_name())) 2193 for server, client in servers_clients: 2194 for response in ds_nxdomain_info.servers_clients[(server, client)]: 2195 err.add_server_client(server, client, response) 2196 self.delegation_errors[rdtype].append(err) 2197 if self.delegation_status[rdtype] == Status.DELEGATION_STATUS_INSECURE: 2198 self.delegation_status[rdtype] = Status.DELEGATION_STATUS_INCOMPLETE 2199 2200 def _populate_server_status(self): 2201 if not self.is_zone(): 2202 return 2203 2204 if self.parent is None: 2205 return 2206 2207 designated_servers = self.get_designated_servers() 2208 servers_queried_udp = set([x for x in self._all_servers_clients_queried if x[0] in designated_servers]) 2209 servers_queried_tcp = set([x for x in self._all_servers_clients_queried_tcp if x[0] in designated_servers]) 2210 servers_queried = servers_queried_udp.union(servers_queried_tcp) 2211 2212 unresponsive_udp = servers_queried_udp.difference(self._responsive_servers_clients_udp) 2213 unresponsive_tcp = servers_queried_tcp.difference(self._responsive_servers_clients_tcp) 2214 invalid_response_udp = servers_queried.intersection(self._responsive_servers_clients_udp).difference(self._valid_servers_clients_udp) 2215 invalid_response_tcp = servers_queried.intersection(self._responsive_servers_clients_tcp).difference(self._valid_servers_clients_tcp) 2216 not_authoritative = servers_queried.intersection(self._valid_servers_clients_udp.union(self._valid_servers_clients_tcp)).difference(self._auth_servers_clients) 2217 2218 if unresponsive_udp: 2219 err = Errors.ServerUnresponsiveUDP() 2220 for server, client in unresponsive_udp: 2221 err.add_server_client(server, client, None) 2222 self.zone_errors.append(err) 2223 2224 if unresponsive_tcp: 2225 err = Errors.ServerUnresponsiveTCP() 2226 for server, client in unresponsive_tcp: 2227 err.add_server_client(server, client, None) 2228 self.zone_errors.append(err) 2229 2230 if invalid_response_udp: 2231 err = Errors.ServerInvalidResponseUDP() 2232 for server, client in invalid_response_udp: 2233 err.add_server_client(server, client, None) 2234 self.zone_errors.append(err) 2235 2236 if invalid_response_tcp: 2237 err = Errors.ServerInvalidResponseTCP() 2238 for server, client in invalid_response_tcp: 2239 err.add_server_client(server, client, None) 2240 self.zone_errors.append(err) 2241 2242 if self.analysis_type == ANALYSIS_TYPE_AUTHORITATIVE: 2243 if not_authoritative: 2244 err = Errors.ServerNotAuthoritative() 2245 for server, client in not_authoritative: 2246 err.add_server_client(server, client, None) 2247 self.zone_errors.append(err) 2248 2249 def _populate_negative_response_status(self, query, neg_response_info, \ 2250 bad_soa_error_cls, missing_soa_error_cls, upward_referral_error_cls, missing_nsec_error_cls, \ 2251 nsec_status_cls, nsec3_status_cls, warnings, errors, supported_algs): 2252 2253 qname_obj = self.get_name(neg_response_info.qname) 2254 is_zone = qname_obj.name == neg_response_info.qname and qname_obj.is_zone() 2255 if query.rdtype == dns.rdatatype.DS and is_zone: 2256 qname_obj = qname_obj.parent 2257 2258 soa_owner_name_for_servers = {} 2259 servers_without_soa = set() 2260 servers_missing_nsec = set() 2261 2262 #TODO Handle the case where a parent server sends NXDOMAIN for a 2263 # delegated child, even when other parent servers, send a proper 2264 # referral. 2265 2266 # populate NXDOMAIN status for only those responses that are from 2267 # servers authoritative or designated as such 2268 auth_servers = qname_obj.zone.get_auth_or_designated_servers() 2269 for server, client in neg_response_info.servers_clients: 2270 if server not in auth_servers: 2271 continue 2272 for response in neg_response_info.servers_clients[(server, client)]: 2273 servers_without_soa.add((server, client, response)) 2274 servers_missing_nsec.add((server, client, response)) 2275 2276 self._populate_responsiveness_errors(qname_obj, response, server, client, warnings, errors) 2277 self._populate_response_errors(qname_obj, response, server, client, warnings, errors) 2278 self._populate_edns_errors(qname_obj, response, server, client, warnings, errors) 2279 self._populate_cookie_errors(qname_obj, response, server, client, warnings, errors) 2280 self._populate_foreign_class_warnings(qname_obj, response, server, client, warnings, errors) 2281 self._populate_case_preservation_warnings(qname_obj, response, server, client, warnings, errors) 2282 2283 for soa_rrset_info in neg_response_info.soa_rrset_info: 2284 soa_owner_name = soa_rrset_info.rrset.name 2285 2286 self._populate_rrsig_status(query, soa_rrset_info, self.get_name(soa_owner_name), supported_algs, populate_response_errors=False) 2287 2288 # make sure this query was made to a server designated as 2289 # authoritative 2290 if not set([s for (s,c) in soa_rrset_info.servers_clients]).intersection(auth_servers): 2291 continue 2292 2293 if soa_owner_name != qname_obj.zone.name: 2294 err = Errors.DomainNameAnalysisError.insert_into_list(bad_soa_error_cls(soa_owner_name=fmt.humanize_name(soa_owner_name), zone_name=fmt.humanize_name(qname_obj.zone.name)), errors, None, None, None) 2295 else: 2296 err = None 2297 2298 for server, client in soa_rrset_info.servers_clients: 2299 if server not in auth_servers: 2300 continue 2301 for response in soa_rrset_info.servers_clients[(server, client)]: 2302 servers_without_soa.remove((server, client, response)) 2303 soa_owner_name_for_servers[(server,client,response)] = soa_owner_name 2304 2305 if err is not None: 2306 if neg_response_info.qname == query.qname or response.recursion_desired_and_available(): 2307 err.add_server_client(server, client, response) 2308 2309 for server,client,response in servers_without_soa: 2310 if neg_response_info.qname == query.qname or response.recursion_desired_and_available(): 2311 # check for an upward referral 2312 if upward_referral_error_cls is not None and response.is_upward_referral(qname_obj.zone.name): 2313 Errors.DomainNameAnalysisError.insert_into_list(upward_referral_error_cls(), errors, server, client, response) 2314 else: 2315 ds_referral = False 2316 if query.rdtype == dns.rdatatype.DS: 2317 # handle DS as a special case 2318 if response.is_referral(query.qname, query.rdtype, query.rdclass, qname_obj.name): 2319 ds_referral = True 2320 2321 if not ds_referral: 2322 Errors.DomainNameAnalysisError.insert_into_list(missing_soa_error_cls(), errors, server, client, response) 2323 2324 if upward_referral_error_cls is not None: 2325 try: 2326 index = errors.index(upward_referral_error_cls()) 2327 except ValueError: 2328 pass 2329 else: 2330 upward_referral_error = errors[index] 2331 for notices in errors, warnings: 2332 not_auth_notices = [x for x in notices if isinstance(x, Errors.NotAuthoritative)] 2333 for notice in not_auth_notices: 2334 for server, client in upward_referral_error.servers_clients: 2335 for response in upward_referral_error.servers_clients[(server, client)]: 2336 notice.remove_server_client(server, client, response) 2337 if not notice.servers_clients: 2338 notices.remove(notice) 2339 2340 statuses = [] 2341 status_by_response = {} 2342 for nsec_set_info in neg_response_info.nsec_set_info: 2343 status_by_soa_name = {} 2344 2345 for nsec_rrset_info in nsec_set_info.rrsets.values(): 2346 self._populate_rrsig_status(query, nsec_rrset_info, qname_obj, supported_algs, populate_response_errors=False) 2347 2348 for server, client in nsec_set_info.servers_clients: 2349 if server not in auth_servers: 2350 continue 2351 for response in nsec_set_info.servers_clients[(server,client)]: 2352 soa_owner_name = soa_owner_name_for_servers.get((server,client,response), qname_obj.zone.name) 2353 if soa_owner_name not in status_by_soa_name: 2354 if nsec_set_info.use_nsec3: 2355 status = nsec3_status_cls(neg_response_info.qname, query.rdtype, \ 2356 soa_owner_name, is_zone, nsec_set_info) 2357 else: 2358 status = nsec_status_cls(neg_response_info.qname, query.rdtype, \ 2359 soa_owner_name, is_zone, nsec_set_info) 2360 if status.validation_status == Status.NSEC_STATUS_VALID: 2361 if status not in statuses: 2362 statuses.append(status) 2363 status_by_soa_name[soa_owner_name] = status 2364 status = status_by_soa_name[soa_owner_name] 2365 2366 if (server,client,response) in servers_missing_nsec: 2367 servers_missing_nsec.remove((server,client,response)) 2368 if status.validation_status == Status.NSEC_STATUS_VALID: 2369 if (server,client,response) in status_by_response: 2370 del status_by_response[(server,client,response)] 2371 elif neg_response_info.qname == query.qname or response.recursion_desired_and_available(): 2372 status_by_response[(server,client,response)] = status 2373 2374 for (server,client,response), status in status_by_response.items(): 2375 if status not in statuses: 2376 statuses.append(status) 2377 2378 for server, client, response in servers_missing_nsec: 2379 # if DNSSEC was not requested (e.g., for diagnostics purposes), 2380 # then don't report an issue 2381 if not (response.query.edns >= 0 and response.query.edns_flags & dns.flags.DO): 2382 continue 2383 2384 # report that no NSEC(3) records were returned 2385 if qname_obj.zone.signed and (neg_response_info.qname == query.qname or response.recursion_desired_and_available()): 2386 if response.dnssec_requested(): 2387 Errors.DomainNameAnalysisError.insert_into_list(missing_nsec_error_cls(), errors, server, client, response) 2388 elif qname_obj is not None and qname_obj.zone.server_responsive_with_do(server,client,response.effective_tcp,True): 2389 Errors.DomainNameAnalysisError.insert_into_list(Errors.UnableToRetrieveDNSSECRecords(), errors, server, client, response) 2390 2391 return statuses 2392 2393 def _populate_nxdomain_status(self, supported_algs): 2394 self.nxdomain_status = {} 2395 self.nxdomain_warnings = {} 2396 self.nxdomain_errors = {} 2397 2398 _logger.debug('Assessing NXDOMAIN response status of %s...' % (fmt.humanize_name(self.name))) 2399 for (qname, rdtype), query in self.queries.items(): 2400 2401 for neg_response_info in query.nxdomain_info: 2402 self.nxdomain_warnings[neg_response_info] = [] 2403 self.nxdomain_errors[neg_response_info] = [] 2404 self.nxdomain_status[neg_response_info] = \ 2405 self._populate_negative_response_status(query, neg_response_info, \ 2406 Errors.SOAOwnerNotZoneForNXDOMAIN, Errors.MissingSOAForNXDOMAIN, None, \ 2407 Errors.MissingNSECForNXDOMAIN, Status.NSECStatusNXDOMAIN, Status.NSEC3StatusNXDOMAIN, \ 2408 self.nxdomain_warnings[neg_response_info], self.nxdomain_errors[neg_response_info], \ 2409 supported_algs) 2410 2411 # check for NOERROR/NXDOMAIN inconsistencies 2412 if neg_response_info.qname in self.yxdomain and rdtype not in (dns.rdatatype.DS, dns.rdatatype.DLV): 2413 for (qname2, rdtype2), query2 in self.queries.items(): 2414 if rdtype2 in (dns.rdatatype.DS, dns.rdatatype.DLV): 2415 continue 2416 2417 for rrset_info in [x for x in query2.answer_info if x.rrset.name == neg_response_info.qname]: 2418 shared_servers_clients = set(rrset_info.servers_clients).intersection(neg_response_info.servers_clients) 2419 if shared_servers_clients: 2420 err1 = Errors.DomainNameAnalysisError.insert_into_list(Errors.InconsistentNXDOMAIN(qname=fmt.humanize_name(neg_response_info.qname), rdtype_nxdomain=dns.rdatatype.to_text(rdtype), rdtype_noerror=dns.rdatatype.to_text(query2.rdtype)), self.nxdomain_warnings[neg_response_info], None, None, None) 2421 err2 = Errors.DomainNameAnalysisError.insert_into_list(Errors.InconsistentNXDOMAIN(qname=fmt.humanize_name(neg_response_info.qname), rdtype_nxdomain=dns.rdatatype.to_text(rdtype), rdtype_noerror=dns.rdatatype.to_text(query2.rdtype)), self.rrset_warnings[rrset_info], None, None, None) 2422 for server, client in shared_servers_clients: 2423 for response in neg_response_info.servers_clients[(server, client)]: 2424 err1.add_server_client(server, client, response) 2425 err2.add_server_client(server, client, response) 2426 2427 for neg_response_info2 in [x for x in query2.nodata_info if x.qname == neg_response_info.qname]: 2428 shared_servers_clients = set(neg_response_info2.servers_clients).intersection(neg_response_info.servers_clients) 2429 if shared_servers_clients: 2430 err1 = Errors.DomainNameAnalysisError.insert_into_list(Errors.InconsistentNXDOMAIN(qname=fmt.humanize_name(neg_response_info.qname), rdtype_nxdomain=dns.rdatatype.to_text(rdtype), rdtype_noerror=dns.rdatatype.to_text(query2.rdtype)), self.nxdomain_warnings[neg_response_info], None, None, None) 2431 err2 = Errors.DomainNameAnalysisError.insert_into_list(Errors.InconsistentNXDOMAIN(qname=fmt.humanize_name(neg_response_info.qname), rdtype_nxdomain=dns.rdatatype.to_text(rdtype), rdtype_noerror=dns.rdatatype.to_text(query2.rdtype)), self.nodata_warnings[neg_response_info2], None, None, None) 2432 for server, client in shared_servers_clients: 2433 for response in neg_response_info.servers_clients[(server, client)]: 2434 err1.add_server_client(server, client, response) 2435 err2.add_server_client(server, client, response) 2436 2437 def _populate_nodata_status(self, supported_algs): 2438 self.nodata_status = {} 2439 self.nodata_warnings = {} 2440 self.nodata_errors = {} 2441 2442 _logger.debug('Assessing NODATA response status of %s...' % (fmt.humanize_name(self.name))) 2443 for (qname, rdtype), query in self.queries.items(): 2444 2445 for neg_response_info in query.nodata_info: 2446 self.nodata_warnings[neg_response_info] = [] 2447 self.nodata_errors[neg_response_info] = [] 2448 self.nodata_status[neg_response_info] = \ 2449 self._populate_negative_response_status(query, neg_response_info, \ 2450 Errors.SOAOwnerNotZoneForNODATA, Errors.MissingSOAForNODATA, Errors.UpwardReferral, \ 2451 Errors.MissingNSECForNODATA, Status.NSECStatusNODATA, Status.NSEC3StatusNODATA, \ 2452 self.nodata_warnings[neg_response_info], self.nodata_errors[neg_response_info], \ 2453 supported_algs) 2454 2455 def _populate_inconsistent_negative_dnssec_responses(self, neg_response_info, neg_status): 2456 for nsec_status in neg_status[neg_response_info]: 2457 queries_by_error = { 2458 Errors.ExistingTypeNotInBitmapNSEC3: [], 2459 Errors.ExistingTypeNotInBitmapNSEC: [], 2460 Errors.ExistingCoveredNSEC3: [], 2461 Errors.ExistingCoveredNSEC: [], 2462 } 2463 nsec_set_info = nsec_status.nsec_set_info 2464 for (qname, rdtype) in self.yxrrset_proper: 2465 if rdtype in (dns.rdatatype.DS, dns.rdatatype.DLV): 2466 continue 2467 if nsec_set_info.use_nsec3: 2468 status = Status.NSEC3StatusNXDOMAIN(qname, rdtype, nsec_status.origin, nsec_status.is_zone, nsec_set_info) 2469 err_cls = Errors.ExistingCoveredNSEC3 2470 else: 2471 status = Status.NSECStatusNXDOMAIN(qname, rdtype, nsec_status.origin, nsec_status.is_zone, nsec_set_info) 2472 err_cls = Errors.ExistingCoveredNSEC 2473 2474 if status.validation_status == Status.NSEC_STATUS_VALID and not status.opt_out: 2475 queries_by_error[err_cls].append((qname, rdtype)) 2476 2477 if nsec_set_info.use_nsec3: 2478 status = Status.NSEC3StatusNODATA(qname, rdtype, nsec_status.origin, nsec_status.is_zone, nsec_set_info) 2479 err_cls = Errors.ExistingTypeNotInBitmapNSEC3 2480 else: 2481 status = Status.NSECStatusNODATA(qname, rdtype, nsec_status.origin, nsec_status.is_zone, nsec_set_info, sname_must_match=True) 2482 err_cls = Errors.ExistingTypeNotInBitmapNSEC 2483 2484 if status.validation_status == Status.NSEC_STATUS_VALID and not status.opt_out: 2485 queries_by_error[err_cls].append((qname, rdtype)) 2486 2487 for err_cls in queries_by_error: 2488 if not queries_by_error[err_cls]: 2489 continue 2490 queries = [(fmt.humanize_name(qname), dns.rdatatype.to_text(rdtype)) for qname, rdtype in queries_by_error[err_cls]] 2491 err = Errors.DomainNameAnalysisError.insert_into_list(err_cls(queries=queries), nsec_status.errors, None, None, None) 2492 2493 def _populate_inconsistent_negative_dnssec_responses_all(self): 2494 2495 _logger.debug('Looking for negative responses that contradict positive responses (%s)...' % (fmt.humanize_name(self.name))) 2496 for (qname, rdtype), query in self.queries.items(): 2497 if rdtype in (dns.rdatatype.DS, dns.rdatatype.DLV): 2498 continue 2499 for neg_response_info in query.nodata_info: 2500 self._populate_inconsistent_negative_dnssec_responses(neg_response_info, self.nodata_status) 2501 for neg_response_info in query.nxdomain_info: 2502 self._populate_inconsistent_negative_dnssec_responses(neg_response_info, self.nxdomain_status) 2503 2504 def _populate_dnskey_status(self, trusted_keys): 2505 if (self.name, dns.rdatatype.DNSKEY) not in self.queries: 2506 return 2507 2508 trusted_keys_rdata = set([k for z, k in trusted_keys if z == self.name]) 2509 trusted_keys_self_signing = set() 2510 2511 # buid a list of responsive servers 2512 bailiwick_map, default_bailiwick = self.get_bailiwick_mapping() 2513 servers_responsive = set() 2514 servers_authoritative = self.zone.get_auth_or_designated_servers() 2515 # only consider those servers that are supposed to answer authoritatively 2516 for query in self.queries[(self.name, dns.rdatatype.DNSKEY)].queries.values(): 2517 servers_responsive.update([(server,client,query.responses[server][client]) for (server,client) in query.servers_with_valid_complete_response(bailiwick_map, default_bailiwick) if server in servers_authoritative]) 2518 2519 # any errors point to their own servers_clients value 2520 for dnskey in self.get_dnskeys(): 2521 if dnskey.rdata in trusted_keys_rdata and dnskey in self.ksks: 2522 trusted_keys_self_signing.add(dnskey) 2523 if dnskey in self.revoked_keys and dnskey not in self.ksks: 2524 err = Errors.RevokedNotSigning() 2525 err.servers_clients = dnskey.servers_clients 2526 dnskey.errors.append(err) 2527 if not self.is_zone(): 2528 err = Errors.DNSKEYNotAtZoneApex(zone=fmt.humanize_name(self.zone.name), name=fmt.humanize_name(self.name)) 2529 err.servers_clients = dnskey.servers_clients 2530 dnskey.errors.append(err) 2531 2532 # if there were servers responsive for the query but that didn't return the dnskey 2533 servers_with_dnskey = set() 2534 for (server,client) in dnskey.servers_clients: 2535 for response in dnskey.servers_clients[(server,client)]: 2536 servers_with_dnskey.add((server,client,response)) 2537 servers_clients_without = servers_responsive.difference(servers_with_dnskey) 2538 if servers_clients_without: 2539 err = Errors.DNSKEYMissingFromServers() 2540 # if the key is shown to be signing anything other than the 2541 # DNSKEY RRset, or if it associated with a DS or trust anchor, 2542 # then mark it as an error; otherwise, mark it as a warning. 2543 if dnskey in self.zsks or dnskey in self.dnskey_with_ds or dnskey.rdata in trusted_keys_rdata: 2544 dnskey.errors.append(err) 2545 else: 2546 dnskey.warnings.append(err) 2547 for (server,client,response) in servers_clients_without: 2548 err.add_server_client(server, client, response) 2549 2550 if not dnskey.rdata.key: 2551 dnskey.errors.append(Errors.DNSKEYZeroLength()) 2552 elif dnskey.rdata.algorithm in DNSSEC_KEY_LENGTHS_BY_ALGORITHM and \ 2553 dnskey.key_len != DNSSEC_KEY_LENGTHS_BY_ALGORITHM[dnskey.rdata.algorithm]: 2554 dnskey.errors.append(DNSSEC_KEY_LENGTH_ERRORS[dnskey.rdata.algorithm](length=dnskey.key_len)) 2555 2556 if trusted_keys_rdata and not trusted_keys_self_signing: 2557 self.zone_errors.append(Errors.NoTrustAnchorSigning(zone=fmt.humanize_name(self.zone.name))) 2558 2559 def populate_response_component_status(self, G): 2560 response_component_status = {} 2561 for obj in G.node_reverse_mapping: 2562 if isinstance(obj, (Response.DNSKEYMeta, Response.RRsetInfo, Response.NSECSet, Response.NegativeResponseInfo, self.__class__)): 2563 node_str = G.node_reverse_mapping[obj] 2564 status = G.status_for_node(node_str) 2565 response_component_status[obj] = status 2566 2567 if isinstance(obj, Response.DNSKEYMeta): 2568 for rrset_info in obj.rrset_info: 2569 if rrset_info in G.secure_dnskey_rrsets: 2570 response_component_status[rrset_info] = Status.RRSET_STATUS_SECURE 2571 else: 2572 response_component_status[rrset_info] = status 2573 2574 # Mark each individual NSEC in the set 2575 elif isinstance(obj, Response.NSECSet): 2576 for nsec_name in obj.rrsets: 2577 nsec_name_str = lb2s(nsec_name.canonicalize().to_text()).replace(r'"', r'\"') 2578 response_component_status[obj.rrsets[nsec_name]] = G.status_for_node(node_str, nsec_name_str) 2579 2580 elif isinstance(obj, Response.NegativeResponseInfo): 2581 # the following two cases are only for zones 2582 if G.is_invis(node_str): 2583 # A negative response info for a DS query points to the 2584 # "top node" of a zone in the graph. If this "top node" is 2585 # colored "insecure", then it indicates that the negative 2586 # response has been authenticated. To reflect this 2587 # properly, we change the status to "secure". 2588 if obj.rdtype == dns.rdatatype.DS: 2589 if status == Status.RRSET_STATUS_INSECURE: 2590 if G.secure_nsec_nodes_covering_node(node_str): 2591 response_component_status[obj] = Status.RRSET_STATUS_SECURE 2592 2593 # for non-DNSKEY responses, verify that the negative 2594 # response is secure by checking that the SOA is also 2595 # secure (the fact that it is marked "secure" indicates 2596 # that the NSEC proof was already authenticated) 2597 if obj.rdtype != dns.rdatatype.DNSKEY: 2598 # check for secure opt out 2599 opt_out_secure = bool(G.secure_nsec3_optout_nodes_covering_node(node_str)) 2600 if status == Status.RRSET_STATUS_SECURE or \ 2601 (status == Status.RRSET_STATUS_INSECURE and opt_out_secure): 2602 soa_secure = False 2603 for soa_rrset in obj.soa_rrset_info: 2604 if G.status_for_node(G.node_reverse_mapping[soa_rrset]) == Status.RRSET_STATUS_SECURE: 2605 soa_secure = True 2606 if not soa_secure: 2607 response_component_status[obj] = Status.RRSET_STATUS_BOGUS 2608 2609 self._set_response_component_status(response_component_status) 2610 2611 def _set_response_component_status(self, response_component_status, is_dlv=False, trace=None, follow_mx=True): 2612 if trace is None: 2613 trace = [] 2614 2615 # avoid loops 2616 if self in trace: 2617 return 2618 2619 # populate status of dependencies 2620 for cname in self.cname_targets: 2621 for target, cname_obj in self.cname_targets[cname].items(): 2622 if cname_obj is not None: 2623 cname_obj._set_response_component_status(response_component_status, trace=trace + [self]) 2624 if follow_mx: 2625 for target, mx_obj in self.mx_targets.items(): 2626 if mx_obj is not None: 2627 mx_obj._set_response_component_status(response_component_status, trace=trace + [self], follow_mx=False) 2628 for signer, signer_obj in self.external_signers.items(): 2629 if signer_obj is not None: 2630 signer_obj._set_response_component_status(response_component_status, trace=trace + [self]) 2631 for target, ns_obj in self.ns_dependencies.items(): 2632 if ns_obj is not None: 2633 ns_obj._set_response_component_status(response_component_status, trace=trace + [self]) 2634 2635 # populate status of ancestry 2636 if self.nxdomain_ancestor is not None: 2637 self.nxdomain_ancestor._set_response_component_status(response_component_status, trace=trace + [self]) 2638 if self.parent is not None: 2639 self.parent._set_response_component_status(response_component_status, trace=trace + [self]) 2640 if self.dlv_parent is not None: 2641 self.dlv_parent._set_response_component_status(response_component_status, is_dlv=True, trace=trace + [self]) 2642 2643 self.response_component_status = response_component_status 2644 2645 def _serialize_rrset_info(self, rrset_info, consolidate_clients=False, show_servers=True, show_server_meta=True, loglevel=logging.DEBUG, html_format=False): 2646 d = OrderedDict() 2647 2648 rrsig_list = [] 2649 if self.rrsig_status[rrset_info]: 2650 rrsigs = list(self.rrsig_status[rrset_info].keys()) 2651 rrsigs.sort() 2652 for rrsig in rrsigs: 2653 dnskeys = list(self.rrsig_status[rrset_info][rrsig].keys()) 2654 dnskeys.sort() 2655 for dnskey in dnskeys: 2656 rrsig_status = self.rrsig_status[rrset_info][rrsig][dnskey] 2657 rrsig_serialized = rrsig_status.serialize(consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format, map_ip_to_ns_name=self.zone.get_ns_name_for_ip) 2658 if rrsig_serialized: 2659 rrsig_list.append(rrsig_serialized) 2660 2661 dname_list = [] 2662 if rrset_info in self.dname_status: 2663 for dname_status in self.dname_status[rrset_info]: 2664 dname_serialized = dname_status.serialize(self._serialize_rrset_info, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format, map_ip_to_ns_name=self.zone.get_ns_name_for_ip) 2665 if dname_serialized: 2666 dname_list.append(dname_serialized) 2667 2668 wildcard_proof_list = OrderedDict() 2669 if rrset_info.wildcard_info: 2670 wildcard_names = list(rrset_info.wildcard_info.keys()) 2671 wildcard_names.sort() 2672 for wildcard_name in wildcard_names: 2673 wildcard_name_str = lb2s(wildcard_name.canonicalize().to_text()) 2674 wildcard_proof_list[wildcard_name_str] = [] 2675 for nsec_status in self.wildcard_status[rrset_info.wildcard_info[wildcard_name]]: 2676 nsec_serialized = nsec_status.serialize(self._serialize_rrset_info, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format, map_ip_to_ns_name=self.zone.get_ns_name_for_ip) 2677 if nsec_serialized: 2678 wildcard_proof_list[wildcard_name_str].append(nsec_serialized) 2679 if not wildcard_proof_list[wildcard_name_str]: 2680 del wildcard_proof_list[wildcard_name_str] 2681 2682 show_id = loglevel <= logging.INFO or \ 2683 (self.rrset_warnings[rrset_info] and loglevel <= logging.WARNING) or \ 2684 (self.rrset_errors[rrset_info] and loglevel <= logging.ERROR) or \ 2685 (rrsig_list or dname_list or wildcard_proof_list) 2686 2687 if show_id: 2688 if rrset_info.rrset.rdtype == dns.rdatatype.NSEC3: 2689 d['id'] = '%s/%s/%s' % (fmt.format_nsec3_name(rrset_info.rrset.name), dns.rdataclass.to_text(rrset_info.rrset.rdclass), dns.rdatatype.to_text(rrset_info.rrset.rdtype)) 2690 else: 2691 d['id'] = '%s/%s/%s' % (lb2s(rrset_info.rrset.name.canonicalize().to_text()), dns.rdataclass.to_text(rrset_info.rrset.rdclass), dns.rdatatype.to_text(rrset_info.rrset.rdtype)) 2692 2693 if loglevel <= logging.DEBUG: 2694 d['description'] = str(rrset_info) 2695 d.update(rrset_info.serialize(consolidate_clients=consolidate_clients, show_servers=False, html_format=html_format, map_ip_to_ns_name=self.zone.get_ns_name_for_ip)) 2696 2697 if rrsig_list: 2698 d['rrsig'] = rrsig_list 2699 2700 if dname_list: 2701 d['dname'] = dname_list 2702 2703 if wildcard_proof_list: 2704 d['wildcard_proof'] = wildcard_proof_list 2705 2706 if loglevel <= logging.INFO and self.response_component_status is not None: 2707 d['status'] = Status.rrset_status_mapping[self.response_component_status[rrset_info]] 2708 2709 if loglevel <= logging.INFO and show_servers: 2710 servers = tuple_to_dict(rrset_info.servers_clients) 2711 server_list = list(servers) 2712 server_list.sort() 2713 if consolidate_clients: 2714 servers = server_list 2715 d['servers'] = servers 2716 2717 ns_names = list(set([lb2s(self.zone.get_ns_name_for_ip(s)[0][0].canonicalize().to_text()) for s in servers])) 2718 ns_names.sort() 2719 d['ns_names'] = ns_names 2720 2721 if show_server_meta: 2722 tags = set() 2723 nsids = set() 2724 cookie_tags = {} 2725 for server,client in rrset_info.servers_clients: 2726 for response in rrset_info.servers_clients[(server,client)]: 2727 tags.add(response.effective_query_tag()) 2728 nsid = response.nsid_val() 2729 if nsid is not None: 2730 nsids.add(nsid) 2731 cookie_tags[server] = OrderedDict(( 2732 ('request', response.request_cookie_tag()), 2733 ('response', response.response_cookie_tag()), 2734 )) 2735 2736 if nsids: 2737 d['nsid_values'] = list(nsids) 2738 d['nsid_values'].sort() 2739 2740 d['query_options'] = list(tags) 2741 d['query_options'].sort() 2742 2743 cookie_tag_mapping = OrderedDict() 2744 for server in server_list: 2745 cookie_tag_mapping[server] = cookie_tags[server] 2746 d['cookie_status'] = cookie_tag_mapping 2747 2748 if self.rrset_warnings[rrset_info] and loglevel <= logging.WARNING: 2749 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.rrset_warnings[rrset_info]] 2750 2751 if self.rrset_errors[rrset_info] and loglevel <= logging.ERROR: 2752 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.rrset_errors[rrset_info]] 2753 2754 return d 2755 2756 def _serialize_negative_response_info(self, neg_response_info, neg_status, warnings, errors, consolidate_clients=False, loglevel=logging.DEBUG, html_format=False): 2757 d = OrderedDict() 2758 2759 proof_list = [] 2760 for nsec_status in neg_status[neg_response_info]: 2761 nsec_serialized = nsec_status.serialize(self._serialize_rrset_info, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format, map_ip_to_ns_name=self.zone.get_ns_name_for_ip) 2762 if nsec_serialized: 2763 proof_list.append(nsec_serialized) 2764 2765 soa_list = [] 2766 for soa_rrset_info in neg_response_info.soa_rrset_info: 2767 rrset_serialized = self._serialize_rrset_info(soa_rrset_info, consolidate_clients=consolidate_clients, show_server_meta=False, loglevel=loglevel, html_format=html_format) 2768 if rrset_serialized: 2769 soa_list.append(rrset_serialized) 2770 2771 show_id = loglevel <= logging.INFO or \ 2772 (warnings[neg_response_info] and loglevel <= logging.WARNING) or \ 2773 (errors[neg_response_info] and loglevel <= logging.ERROR) or \ 2774 (proof_list or soa_list) 2775 2776 if show_id: 2777 d['id'] = '%s/%s/%s' % (lb2s(neg_response_info.qname.canonicalize().to_text()), 'IN', dns.rdatatype.to_text(neg_response_info.rdtype)) 2778 2779 if proof_list: 2780 d['proof'] = proof_list 2781 2782 if soa_list: 2783 d['soa'] = soa_list 2784 2785 if loglevel <= logging.INFO and self.response_component_status is not None: 2786 d['status'] = Status.rrset_status_mapping[self.response_component_status[neg_response_info]] 2787 2788 if loglevel <= logging.INFO: 2789 servers = tuple_to_dict(neg_response_info.servers_clients) 2790 server_list = list(servers) 2791 server_list.sort() 2792 if consolidate_clients: 2793 servers = server_list 2794 d['servers'] = servers 2795 2796 ns_names = list(set([lb2s(self.zone.get_ns_name_for_ip(s)[0][0].canonicalize().to_text()) for s in servers])) 2797 ns_names.sort() 2798 d['ns_names'] = ns_names 2799 2800 tags = set() 2801 nsids = set() 2802 cookie_tags = {} 2803 for server,client in neg_response_info.servers_clients: 2804 for response in neg_response_info.servers_clients[(server,client)]: 2805 tags.add(response.effective_query_tag()) 2806 nsid = response.nsid_val() 2807 if nsid is not None: 2808 nsids.add(nsid) 2809 cookie_tags[server] = OrderedDict(( 2810 ('request', response.request_cookie_tag()), 2811 ('response', response.response_cookie_tag()), 2812 )) 2813 2814 if nsids: 2815 d['nsid_values'] = list(nsids) 2816 d['nsid_values'].sort() 2817 2818 d['query_options'] = list(tags) 2819 d['query_options'].sort() 2820 2821 cookie_tag_mapping = OrderedDict() 2822 for server in server_list: 2823 cookie_tag_mapping[server] = cookie_tags[server] 2824 d['cookie_status'] = cookie_tag_mapping 2825 2826 if warnings[neg_response_info] and loglevel <= logging.WARNING: 2827 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in warnings[neg_response_info]] 2828 2829 if errors[neg_response_info] and loglevel <= logging.ERROR: 2830 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in errors[neg_response_info]] 2831 2832 return d 2833 2834 def _serialize_query_status(self, query, consolidate_clients=False, loglevel=logging.DEBUG, html_format=False): 2835 d = OrderedDict() 2836 d['answer'] = [] 2837 d['nxdomain'] = [] 2838 d['nodata'] = [] 2839 d['error'] = [] 2840 d['warning'] = [] 2841 2842 for rrset_info in query.answer_info: 2843 if rrset_info.rrset.name == query.qname or self.analysis_type == ANALYSIS_TYPE_RECURSIVE: 2844 rrset_serialized = self._serialize_rrset_info(rrset_info, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format) 2845 if rrset_serialized: 2846 d['answer'].append(rrset_serialized) 2847 2848 for neg_response_info in query.nxdomain_info: 2849 # make sure this query was made to a server designated as 2850 # authoritative 2851 if not set([s for (s,c) in neg_response_info.servers_clients]).intersection(self.zone.get_auth_or_designated_servers()): 2852 continue 2853 # only look at qname 2854 if neg_response_info.qname == query.qname or self.analysis_type == ANALYSIS_TYPE_RECURSIVE: 2855 neg_response_serialized = self._serialize_negative_response_info(neg_response_info, self.nxdomain_status, self.nxdomain_warnings, self.nxdomain_errors, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format) 2856 if neg_response_serialized: 2857 d['nxdomain'].append(neg_response_serialized) 2858 2859 for neg_response_info in query.nodata_info: 2860 # only look at qname 2861 if neg_response_info.qname == query.qname or self.analysis_type == ANALYSIS_TYPE_RECURSIVE: 2862 neg_response_serialized = self._serialize_negative_response_info(neg_response_info, self.nodata_status, self.nodata_warnings, self.nodata_errors, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format) 2863 if neg_response_serialized: 2864 d['nodata'].append(neg_response_serialized) 2865 2866 if loglevel <= logging.WARNING: 2867 for warning in self.response_warnings[query]: 2868 warning_serialized = warning.serialize(consolidate_clients=consolidate_clients, html_format=html_format) 2869 if warning_serialized: 2870 d['warning'].append(warning_serialized) 2871 2872 for error in self.response_errors[query]: 2873 error_serialized = error.serialize(consolidate_clients=consolidate_clients, html_format=html_format) 2874 if error_serialized: 2875 d['error'].append(error_serialized) 2876 2877 if not d['answer']: del d['answer'] 2878 if not d['nxdomain']: del d['nxdomain'] 2879 if not d['nodata']: del d['nodata'] 2880 if not d['error']: del d['error'] 2881 if not d['warning']: del d['warning'] 2882 2883 return d 2884 2885 def _serialize_dnskey_status(self, consolidate_clients=False, loglevel=logging.DEBUG, html_format=False): 2886 d = [] 2887 2888 for dnskey in self.get_dnskeys(): 2889 dnskey_serialized = dnskey.serialize(consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format, map_ip_to_ns_name=self.zone.get_ns_name_for_ip) 2890 if dnskey_serialized: 2891 if loglevel <= logging.INFO and self.response_component_status is not None: 2892 dnskey_serialized['status'] = Status.rrset_status_mapping[self.response_component_status[dnskey]] 2893 d.append(dnskey_serialized) 2894 2895 return d 2896 2897 def _serialize_delegation_status(self, rdtype, consolidate_clients=False, loglevel=logging.DEBUG, html_format=False): 2898 d = OrderedDict() 2899 2900 dss = list(self.ds_status_by_ds[rdtype].keys()) 2901 d['ds'] = [] 2902 dss.sort() 2903 for ds in dss: 2904 dnskeys = list(self.ds_status_by_ds[rdtype][ds].keys()) 2905 dnskeys.sort() 2906 for dnskey in dnskeys: 2907 ds_status = self.ds_status_by_ds[rdtype][ds][dnskey] 2908 ds_serialized = ds_status.serialize(consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format, map_ip_to_ns_name=self.zone.get_ns_name_for_ip) 2909 if ds_serialized: 2910 d['ds'].append(ds_serialized) 2911 if not d['ds']: 2912 del d['ds'] 2913 2914 try: 2915 neg_response_info = [x for x in self.nodata_status if x.qname == self.name and x.rdtype == rdtype][0] 2916 status = self.nodata_status 2917 except IndexError: 2918 try: 2919 neg_response_info = [x for x in self.nxdomain_status if x.qname == self.name and x.rdtype == rdtype][0] 2920 status = self.nxdomain_status 2921 except IndexError: 2922 neg_response_info = None 2923 2924 if neg_response_info is not None: 2925 d['insecurity_proof'] = [] 2926 for nsec_status in status[neg_response_info]: 2927 nsec_serialized = nsec_status.serialize(self._serialize_rrset_info, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format, map_ip_to_ns_name=self.zone.get_ns_name_for_ip) 2928 if nsec_serialized: 2929 d['insecurity_proof'].append(nsec_serialized) 2930 if not d['insecurity_proof']: 2931 del d['insecurity_proof'] 2932 2933 erroneous_status = self.delegation_status[rdtype] not in (Status.DELEGATION_STATUS_SECURE, Status.DELEGATION_STATUS_INSECURE) 2934 2935 if loglevel <= logging.INFO or erroneous_status: 2936 d['status'] = Status.delegation_status_mapping[self.delegation_status[rdtype]] 2937 2938 if self.delegation_warnings[rdtype] and loglevel <= logging.WARNING: 2939 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.delegation_warnings[rdtype]] 2940 2941 if self.delegation_errors[rdtype] and loglevel <= logging.ERROR: 2942 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.delegation_errors[rdtype]] 2943 2944 return d 2945 2946 def _serialize_zone_status(self, consolidate_clients=False, loglevel=logging.DEBUG, html_format=False): 2947 d = OrderedDict() 2948 2949 if loglevel <= logging.DEBUG: 2950 glue_ip_mapping = self.get_glue_ip_mapping() 2951 auth_ns_ip_mapping = self.get_auth_ns_ip_mapping() 2952 d['servers'] = OrderedDict() 2953 names = list(self.get_ns_names()) 2954 names.sort() 2955 for name in names: 2956 name_str = lb2s(name.canonicalize().to_text()) 2957 d['servers'][name_str] = OrderedDict() 2958 if name in glue_ip_mapping and glue_ip_mapping[name]: 2959 servers = list(glue_ip_mapping[name]) 2960 servers.sort() 2961 d['servers'][name_str]['glue'] = servers 2962 if name in auth_ns_ip_mapping and auth_ns_ip_mapping[name]: 2963 servers = list(auth_ns_ip_mapping[name]) 2964 servers.sort() 2965 d['servers'][name_str]['auth'] = servers 2966 if not d['servers']: 2967 del d['servers'] 2968 2969 stealth_servers = self.get_stealth_servers() 2970 if stealth_servers: 2971 stealth_mapping = {} 2972 for server in stealth_servers: 2973 names, ancestor_name = self.get_ns_name_for_ip(server) 2974 for name in names: 2975 if name not in stealth_mapping: 2976 stealth_mapping[name] = [] 2977 stealth_mapping[name].append(server) 2978 2979 names = list(stealth_mapping) 2980 names.sort() 2981 for name in names: 2982 name_str = lb2s(name.canonicalize().to_text()) 2983 servers = stealth_mapping[name] 2984 servers.sort() 2985 d['servers'][name_str] = OrderedDict(( 2986 ('stealth', servers), 2987 )) 2988 2989 if loglevel <= logging.INFO and self.response_component_status is not None: 2990 d['status'] = Status.delegation_status_mapping[self.response_component_status[self]] 2991 2992 if self.zone_warnings and loglevel <= logging.WARNING: 2993 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.zone_warnings] 2994 2995 if self.zone_errors and loglevel <= logging.ERROR: 2996 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.zone_errors] 2997 2998 return d 2999 3000 def serialize_status(self, d=None, is_dlv=False, loglevel=logging.DEBUG, ancestry_only=False, level=RDTYPES_ALL, trace=None, follow_mx=True, html_format=False): 3001 if d is None: 3002 d = OrderedDict() 3003 3004 if trace is None: 3005 trace = [] 3006 3007 # avoid loops 3008 if self in trace: 3009 return d 3010 3011 # if we're a stub, there's no status to serialize 3012 if self.stub: 3013 return d 3014 3015 name_str = lb2s(self.name.canonicalize().to_text()) 3016 if name_str in d: 3017 return d 3018 3019 cname_ancestry_only = self.analysis_type == ANALYSIS_TYPE_RECURSIVE 3020 3021 # serialize status of dependencies first because their version of the 3022 # analysis might be the most complete (considering re-dos) 3023 if level <= self.RDTYPES_NS_TARGET: 3024 for cname in self.cname_targets: 3025 for target, cname_obj in self.cname_targets[cname].items(): 3026 if cname_obj is not None: 3027 cname_obj.serialize_status(d, loglevel=loglevel, ancestry_only=cname_ancestry_only, level=max(self.RDTYPES_ALL_SAME_NAME, level), trace=trace + [self], html_format=html_format) 3028 if follow_mx: 3029 for target, mx_obj in self.mx_targets.items(): 3030 if mx_obj is not None: 3031 mx_obj.serialize_status(d, loglevel=loglevel, level=max(self.RDTYPES_ALL_SAME_NAME, level), trace=trace + [self], follow_mx=False, html_format=html_format) 3032 if level <= self.RDTYPES_SECURE_DELEGATION: 3033 for signer, signer_obj in self.external_signers.items(): 3034 signer_obj.serialize_status(d, loglevel=loglevel, level=self.RDTYPES_SECURE_DELEGATION, trace=trace + [self], html_format=html_format) 3035 for target, ns_obj in self.ns_dependencies.items(): 3036 if ns_obj is not None: 3037 ns_obj.serialize_status(d, loglevel=loglevel, level=self.RDTYPES_NS_TARGET, trace=trace + [self], html_format=html_format) 3038 3039 # serialize status of ancestry 3040 if level <= self.RDTYPES_SECURE_DELEGATION: 3041 if self.nxdomain_ancestor is not None: 3042 self.nxdomain_ancestor.serialize_status(d, loglevel=loglevel, level=self.RDTYPES_ALL_SAME_NAME, trace=trace + [self], html_format=html_format) 3043 if self.parent is not None: 3044 self.parent.serialize_status(d, loglevel=loglevel, level=self.RDTYPES_SECURE_DELEGATION, trace=trace + [self], html_format=html_format) 3045 if self.dlv_parent is not None: 3046 self.dlv_parent.serialize_status(d, is_dlv=True, loglevel=loglevel, level=self.RDTYPES_SECURE_DELEGATION, trace=trace + [self], html_format=html_format) 3047 3048 # if we're only looking for the secure ancestry of a name, and not the 3049 # name itself (i.e., because this is a subsequent name in a CNAME 3050 # chain) 3051 if ancestry_only: 3052 3053 # only proceed if the name is a zone (and thus as DNSKEY, DS, etc.) 3054 if not self.is_zone(): 3055 return d 3056 3057 # explicitly set the level to self.RDTYPES_SECURE_DELEGATION, so 3058 # the other query types aren't retrieved. 3059 level = self.RDTYPES_SECURE_DELEGATION 3060 3061 consolidate_clients = self.single_client() 3062 3063 erroneous_status = self.status not in (Status.NAME_STATUS_NOERROR, Status.NAME_STATUS_NXDOMAIN) 3064 3065 d[name_str] = OrderedDict() 3066 if loglevel <= logging.INFO or erroneous_status: 3067 d[name_str]['status'] = Status.name_status_mapping[self.status] 3068 3069 d[name_str]['queries'] = OrderedDict() 3070 query_keys = list(self.queries.keys()) 3071 query_keys.sort() 3072 required_rdtypes = self._rdtypes_for_analysis_level(level) 3073 3074 # don't serialize NS data in names for which delegation-only 3075 # information is required 3076 if level >= self.RDTYPES_SECURE_DELEGATION: 3077 required_rdtypes.difference_update([self.referral_rdtype, dns.rdatatype.NS]) 3078 3079 for (qname, rdtype) in query_keys: 3080 3081 if level > self.RDTYPES_ALL and qname not in (self.name, self.dlv_name): 3082 continue 3083 3084 if required_rdtypes is not None and rdtype not in required_rdtypes: 3085 continue 3086 3087 query_serialized = self._serialize_query_status(self.queries[(qname, rdtype)], consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format) 3088 if query_serialized: 3089 qname_type_str = '%s/%s/%s' % (lb2s(qname.canonicalize().to_text()), dns.rdataclass.to_text(dns.rdataclass.IN), dns.rdatatype.to_text(rdtype)) 3090 d[name_str]['queries'][qname_type_str] = query_serialized 3091 3092 if not d[name_str]['queries']: 3093 del d[name_str]['queries'] 3094 3095 if level <= self.RDTYPES_SECURE_DELEGATION and (self.name, dns.rdatatype.DNSKEY) in self.queries: 3096 dnskey_serialized = self._serialize_dnskey_status(consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format) 3097 if dnskey_serialized: 3098 d[name_str]['dnskey'] = dnskey_serialized 3099 3100 if self.is_zone(): 3101 zone_serialized = self._serialize_zone_status(consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format) 3102 if zone_serialized: 3103 d[name_str]['zone'] = zone_serialized 3104 3105 if self.parent is not None and not is_dlv: 3106 delegation_serialized = self._serialize_delegation_status(dns.rdatatype.DS, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format) 3107 if delegation_serialized: 3108 d[name_str]['delegation'] = delegation_serialized 3109 3110 if self.dlv_parent is not None: 3111 if (self.dlv_name, dns.rdatatype.DLV) in self.queries: 3112 delegation_serialized = self._serialize_delegation_status(dns.rdatatype.DLV, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format) 3113 if delegation_serialized: 3114 d[name_str]['dlv'] = delegation_serialized 3115 3116 if not d[name_str]: 3117 del d[name_str] 3118 3119 return d 3120 3121class TTLAgnosticOfflineDomainNameAnalysis(OfflineDomainNameAnalysis): 3122 QUERY_CLASS = Q.MultiQueryAggregateDNSResponse 3123