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