1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
2
3# Copyright (C) 2001-2017 Nominum, Inc.
4#
5# Permission to use, copy, modify, and distribute this software and its
6# documentation for any purpose with or without fee is hereby granted,
7# provided that the above copyright notice and this permission notice
8# appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
18"""DNS rdata."""
19
20from importlib import import_module
21import base64
22import binascii
23import io
24import inspect
25import itertools
26
27import dns.wire
28import dns.exception
29import dns.name
30import dns.rdataclass
31import dns.rdatatype
32import dns.tokenizer
33
34_chunksize = 32
35
36
37def _wordbreak(data, chunksize=_chunksize):
38    """Break a binary string into chunks of chunksize characters separated by
39    a space.
40    """
41
42    if not chunksize:
43        return data.decode()
44    return b' '.join([data[i:i + chunksize]
45                      for i
46                      in range(0, len(data), chunksize)]).decode()
47
48
49def _hexify(data, chunksize=_chunksize):
50    """Convert a binary string into its hex encoding, broken up into chunks
51    of chunksize characters separated by a space.
52    """
53
54    return _wordbreak(binascii.hexlify(data), chunksize)
55
56
57def _base64ify(data, chunksize=_chunksize):
58    """Convert a binary string into its base64 encoding, broken up into chunks
59    of chunksize characters separated by a space.
60    """
61
62    return _wordbreak(base64.b64encode(data), chunksize)
63
64__escaped = b'"\\'
65
66def _escapify(qstring):
67    """Escape the characters in a quoted string which need it."""
68
69    if isinstance(qstring, str):
70        qstring = qstring.encode()
71    if not isinstance(qstring, bytearray):
72        qstring = bytearray(qstring)
73
74    text = ''
75    for c in qstring:
76        if c in __escaped:
77            text += '\\' + chr(c)
78        elif c >= 0x20 and c < 0x7F:
79            text += chr(c)
80        else:
81            text += '\\%03d' % c
82    return text
83
84
85def _truncate_bitmap(what):
86    """Determine the index of greatest byte that isn't all zeros, and
87    return the bitmap that contains all the bytes less than that index.
88    """
89
90    for i in range(len(what) - 1, -1, -1):
91        if what[i] != 0:
92            return what[0: i + 1]
93    return what[0:1]
94
95def _constify(o):
96    """
97    Convert mutable types to immutable types.
98    """
99    if isinstance(o, bytearray):
100        return bytes(o)
101    if isinstance(o, tuple):
102        try:
103            hash(o)
104            return o
105        except Exception:
106            return tuple(_constify(elt) for elt in o)
107    if isinstance(o, list):
108        return tuple(_constify(elt) for elt in o)
109    return o
110
111class Rdata:
112    """Base class for all DNS rdata types."""
113
114    __slots__ = ['rdclass', 'rdtype']
115
116    def __init__(self, rdclass, rdtype):
117        """Initialize an rdata.
118
119        *rdclass*, an ``int`` is the rdataclass of the Rdata.
120
121        *rdtype*, an ``int`` is the rdatatype of the Rdata.
122        """
123
124        object.__setattr__(self, 'rdclass', rdclass)
125        object.__setattr__(self, 'rdtype', rdtype)
126
127    def __setattr__(self, name, value):
128        # Rdatas are immutable
129        raise TypeError("object doesn't support attribute assignment")
130
131    def __delattr__(self, name):
132        # Rdatas are immutable
133        raise TypeError("object doesn't support attribute deletion")
134
135    def _get_all_slots(self):
136        return itertools.chain.from_iterable(getattr(cls, '__slots__', [])
137                                             for cls in self.__class__.__mro__)
138
139    def __getstate__(self):
140        # We used to try to do a tuple of all slots here, but it
141        # doesn't work as self._all_slots isn't available at
142        # __setstate__() time.  Before that we tried to store a tuple
143        # of __slots__, but that didn't work as it didn't store the
144        # slots defined by ancestors.  This older way didn't fail
145        # outright, but ended up with partially broken objects, e.g.
146        # if you unpickled an A RR it wouldn't have rdclass and rdtype
147        # attributes, and would compare badly.
148        state = {}
149        for slot in self._get_all_slots():
150            state[slot] = getattr(self, slot)
151        return state
152
153    def __setstate__(self, state):
154        for slot, val in state.items():
155            object.__setattr__(self, slot, val)
156
157    def covers(self):
158        """Return the type a Rdata covers.
159
160        DNS SIG/RRSIG rdatas apply to a specific type; this type is
161        returned by the covers() function.  If the rdata type is not
162        SIG or RRSIG, dns.rdatatype.NONE is returned.  This is useful when
163        creating rdatasets, allowing the rdataset to contain only RRSIGs
164        of a particular type, e.g. RRSIG(NS).
165
166        Returns an ``int``.
167        """
168
169        return dns.rdatatype.NONE
170
171    def extended_rdatatype(self):
172        """Return a 32-bit type value, the least significant 16 bits of
173        which are the ordinary DNS type, and the upper 16 bits of which are
174        the "covered" type, if any.
175
176        Returns an ``int``.
177        """
178
179        return self.covers() << 16 | self.rdtype
180
181    def to_text(self, origin=None, relativize=True, **kw):
182        """Convert an rdata to text format.
183
184        Returns a ``str``.
185        """
186
187        raise NotImplementedError
188
189    def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
190        raise NotImplementedError
191
192    def to_wire(self, file=None, compress=None, origin=None,
193                canonicalize=False):
194        """Convert an rdata to wire format.
195
196        Returns a ``bytes`` or ``None``.
197        """
198
199        if file:
200            return self._to_wire(file, compress, origin, canonicalize)
201        else:
202            f = io.BytesIO()
203            self._to_wire(f, compress, origin, canonicalize)
204            return f.getvalue()
205
206    def to_generic(self, origin=None):
207        """Creates a dns.rdata.GenericRdata equivalent of this rdata.
208
209        Returns a ``dns.rdata.GenericRdata``.
210        """
211        return dns.rdata.GenericRdata(self.rdclass, self.rdtype,
212                                      self.to_wire(origin=origin))
213
214    def to_digestable(self, origin=None):
215        """Convert rdata to a format suitable for digesting in hashes.  This
216        is also the DNSSEC canonical form.
217
218        Returns a ``bytes``.
219        """
220
221        return self.to_wire(origin=origin, canonicalize=True)
222
223    def __repr__(self):
224        covers = self.covers()
225        if covers == dns.rdatatype.NONE:
226            ctext = ''
227        else:
228            ctext = '(' + dns.rdatatype.to_text(covers) + ')'
229        return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \
230               dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \
231               str(self) + '>'
232
233    def __str__(self):
234        return self.to_text()
235
236    def _cmp(self, other):
237        """Compare an rdata with another rdata of the same rdtype and
238        rdclass.
239
240        Return < 0 if self < other in the DNSSEC ordering, 0 if self
241        == other, and > 0 if self > other.
242
243        """
244        our = self.to_digestable(dns.name.root)
245        their = other.to_digestable(dns.name.root)
246        if our == their:
247            return 0
248        elif our > their:
249            return 1
250        else:
251            return -1
252
253    def __eq__(self, other):
254        if not isinstance(other, Rdata):
255            return False
256        if self.rdclass != other.rdclass or self.rdtype != other.rdtype:
257            return False
258        return self._cmp(other) == 0
259
260    def __ne__(self, other):
261        if not isinstance(other, Rdata):
262            return True
263        if self.rdclass != other.rdclass or self.rdtype != other.rdtype:
264            return True
265        return self._cmp(other) != 0
266
267    def __lt__(self, other):
268        if not isinstance(other, Rdata) or \
269                self.rdclass != other.rdclass or self.rdtype != other.rdtype:
270
271            return NotImplemented
272        return self._cmp(other) < 0
273
274    def __le__(self, other):
275        if not isinstance(other, Rdata) or \
276                self.rdclass != other.rdclass or self.rdtype != other.rdtype:
277            return NotImplemented
278        return self._cmp(other) <= 0
279
280    def __ge__(self, other):
281        if not isinstance(other, Rdata) or \
282                self.rdclass != other.rdclass or self.rdtype != other.rdtype:
283            return NotImplemented
284        return self._cmp(other) >= 0
285
286    def __gt__(self, other):
287        if not isinstance(other, Rdata) or \
288                self.rdclass != other.rdclass or self.rdtype != other.rdtype:
289            return NotImplemented
290        return self._cmp(other) > 0
291
292    def __hash__(self):
293        return hash(self.to_digestable(dns.name.root))
294
295    @classmethod
296    def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
297                  relativize_to=None):
298        raise NotImplementedError
299
300    @classmethod
301    def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
302        raise NotImplementedError
303
304    def replace(self, **kwargs):
305        """
306        Create a new Rdata instance based on the instance replace was
307        invoked on. It is possible to pass different parameters to
308        override the corresponding properties of the base Rdata.
309
310        Any field specific to the Rdata type can be replaced, but the
311        *rdtype* and *rdclass* fields cannot.
312
313        Returns an instance of the same Rdata subclass as *self*.
314        """
315
316        # Get the constructor parameters.
317        parameters = inspect.signature(self.__init__).parameters
318
319        # Ensure that all of the arguments correspond to valid fields.
320        # Don't allow rdclass or rdtype to be changed, though.
321        for key in kwargs:
322            if key not in parameters:
323                raise AttributeError("'{}' object has no attribute '{}'"
324                                     .format(self.__class__.__name__, key))
325            if key in ('rdclass', 'rdtype'):
326                raise AttributeError("Cannot overwrite '{}' attribute '{}'"
327                                     .format(self.__class__.__name__, key))
328
329        # Construct the parameter list.  For each field, use the value in
330        # kwargs if present, and the current value otherwise.
331        args = (kwargs.get(key, getattr(self, key)) for key in parameters)
332
333        # Create, validate, and return the new object.
334        #
335        # Note that if we make constructors do validation in the future,
336        # this validation can go away.
337        rd = self.__class__(*args)
338        dns.rdata.from_text(rd.rdclass, rd.rdtype, rd.to_text())
339        return rd
340
341
342class GenericRdata(Rdata):
343
344    """Generic Rdata Class
345
346    This class is used for rdata types for which we have no better
347    implementation.  It implements the DNS "unknown RRs" scheme.
348    """
349
350    __slots__ = ['data']
351
352    def __init__(self, rdclass, rdtype, data):
353        super().__init__(rdclass, rdtype)
354        object.__setattr__(self, 'data', data)
355
356    def to_text(self, origin=None, relativize=True, **kw):
357        return r'\# %d ' % len(self.data) + _hexify(self.data)
358
359    @classmethod
360    def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
361                  relativize_to=None):
362        token = tok.get()
363        if not token.is_identifier() or token.value != r'\#':
364            raise dns.exception.SyntaxError(
365                r'generic rdata does not start with \#')
366        length = tok.get_int()
367        chunks = []
368        while 1:
369            token = tok.get()
370            if token.is_eol_or_eof():
371                break
372            chunks.append(token.value.encode())
373        hex = b''.join(chunks)
374        data = binascii.unhexlify(hex)
375        if len(data) != length:
376            raise dns.exception.SyntaxError(
377                'generic rdata hex data has wrong length')
378        return cls(rdclass, rdtype, data)
379
380    def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
381        file.write(self.data)
382
383    @classmethod
384    def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
385        return cls(rdclass, rdtype, parser.get_remaining())
386
387_rdata_classes = {}
388_module_prefix = 'dns.rdtypes'
389
390def get_rdata_class(rdclass, rdtype):
391    cls = _rdata_classes.get((rdclass, rdtype))
392    if not cls:
393        cls = _rdata_classes.get((dns.rdatatype.ANY, rdtype))
394        if not cls:
395            rdclass_text = dns.rdataclass.to_text(rdclass)
396            rdtype_text = dns.rdatatype.to_text(rdtype)
397            rdtype_text = rdtype_text.replace('-', '_')
398            try:
399                mod = import_module('.'.join([_module_prefix,
400                                              rdclass_text, rdtype_text]))
401                cls = getattr(mod, rdtype_text)
402                _rdata_classes[(rdclass, rdtype)] = cls
403            except ImportError:
404                try:
405                    mod = import_module('.'.join([_module_prefix,
406                                                  'ANY', rdtype_text]))
407                    cls = getattr(mod, rdtype_text)
408                    _rdata_classes[(dns.rdataclass.ANY, rdtype)] = cls
409                    _rdata_classes[(rdclass, rdtype)] = cls
410                except ImportError:
411                    pass
412    if not cls:
413        cls = GenericRdata
414        _rdata_classes[(rdclass, rdtype)] = cls
415    return cls
416
417
418def from_text(rdclass, rdtype, tok, origin=None, relativize=True,
419              relativize_to=None, idna_codec=None):
420    """Build an rdata object from text format.
421
422    This function attempts to dynamically load a class which
423    implements the specified rdata class and type.  If there is no
424    class-and-type-specific implementation, the GenericRdata class
425    is used.
426
427    Once a class is chosen, its from_text() class method is called
428    with the parameters to this function.
429
430    If *tok* is a ``str``, then a tokenizer is created and the string
431    is used as its input.
432
433    *rdclass*, an ``int``, the rdataclass.
434
435    *rdtype*, an ``int``, the rdatatype.
436
437    *tok*, a ``dns.tokenizer.Tokenizer`` or a ``str``.
438
439    *origin*, a ``dns.name.Name`` (or ``None``), the
440    origin to use for relative names.
441
442    *relativize*, a ``bool``.  If true, name will be relativized.
443
444    *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use
445    when relativizing names.  If not set, the *origin* value will be used.
446
447    *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
448    encoder/decoder to use if a tokenizer needs to be created.  If
449    ``None``, the default IDNA 2003 encoder/decoder is used.  If a
450    tokenizer is not created, then the codec associated with the tokenizer
451    is the one that is used.
452
453    Returns an instance of the chosen Rdata subclass.
454
455    """
456
457    if isinstance(tok, str):
458        tok = dns.tokenizer.Tokenizer(tok, idna_codec=idna_codec)
459    rdclass = dns.rdataclass.RdataClass.make(rdclass)
460    rdtype = dns.rdatatype.RdataType.make(rdtype)
461    cls = get_rdata_class(rdclass, rdtype)
462    if cls != GenericRdata:
463        # peek at first token
464        token = tok.get()
465        tok.unget(token)
466        if token.is_identifier() and \
467           token.value == r'\#':
468            #
469            # Known type using the generic syntax.  Extract the
470            # wire form from the generic syntax, and then run
471            # from_wire on it.
472            #
473            rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
474                                           relativize, relativize_to)
475            return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
476                             origin)
477    return cls.from_text(rdclass, rdtype, tok, origin, relativize,
478                         relativize_to)
479
480
481def from_wire_parser(rdclass, rdtype, parser, origin=None):
482    """Build an rdata object from wire format
483
484    This function attempts to dynamically load a class which
485    implements the specified rdata class and type.  If there is no
486    class-and-type-specific implementation, the GenericRdata class
487    is used.
488
489    Once a class is chosen, its from_wire() class method is called
490    with the parameters to this function.
491
492    *rdclass*, an ``int``, the rdataclass.
493
494    *rdtype*, an ``int``, the rdatatype.
495
496    *parser*, a ``dns.wire.Parser``, the parser, which should be
497    restricted to the rdata length.
498
499    *origin*, a ``dns.name.Name`` (or ``None``).  If not ``None``,
500    then names will be relativized to this origin.
501
502    Returns an instance of the chosen Rdata subclass.
503    """
504
505    rdclass = dns.rdataclass.RdataClass.make(rdclass)
506    rdtype = dns.rdatatype.RdataType.make(rdtype)
507    cls = get_rdata_class(rdclass, rdtype)
508    return cls.from_wire_parser(rdclass, rdtype, parser, origin)
509
510
511def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None):
512    """Build an rdata object from wire format
513
514    This function attempts to dynamically load a class which
515    implements the specified rdata class and type.  If there is no
516    class-and-type-specific implementation, the GenericRdata class
517    is used.
518
519    Once a class is chosen, its from_wire() class method is called
520    with the parameters to this function.
521
522    *rdclass*, an ``int``, the rdataclass.
523
524    *rdtype*, an ``int``, the rdatatype.
525
526    *wire*, a ``bytes``, the wire-format message.
527
528    *current*, an ``int``, the offset in wire of the beginning of
529    the rdata.
530
531    *rdlen*, an ``int``, the length of the wire-format rdata
532
533    *origin*, a ``dns.name.Name`` (or ``None``).  If not ``None``,
534    then names will be relativized to this origin.
535
536    Returns an instance of the chosen Rdata subclass.
537    """
538    parser = dns.wire.Parser(wire, current)
539    with parser.restrict_to(rdlen):
540        return from_wire_parser(rdclass, rdtype, parser, origin)
541
542
543class RdatatypeExists(dns.exception.DNSException):
544    """DNS rdatatype already exists."""
545    supp_kwargs = {'rdclass', 'rdtype'}
546    fmt = "The rdata type with class {rdclass} and rdtype {rdtype} " + \
547        "already exists."
548
549
550def register_type(implementation, rdtype, rdtype_text, is_singleton=False,
551                  rdclass=dns.rdataclass.IN):
552    """Dynamically register a module to handle an rdatatype.
553
554    *implementation*, a module implementing the type in the usual dnspython
555    way.
556
557    *rdtype*, an ``int``, the rdatatype to register.
558
559    *rdtype_text*, a ``str``, the textual form of the rdatatype.
560
561    *is_singleton*, a ``bool``, indicating if the type is a singleton (i.e.
562    RRsets of the type can have only one member.)
563
564    *rdclass*, the rdataclass of the type, or ``dns.rdataclass.ANY`` if
565    it applies to all classes.
566    """
567
568    existing_cls = get_rdata_class(rdclass, rdtype)
569    if existing_cls != GenericRdata or dns.rdatatype.is_metatype(rdtype):
570        raise RdatatypeExists(rdclass=rdclass, rdtype=rdtype)
571    try:
572        if dns.rdatatype.RdataType(rdtype).name != rdtype_text:
573            raise RdatatypeExists(rdclass=rdclass, rdtype=rdtype)
574    except ValueError:
575        pass
576    _rdata_classes[(rdclass, rdtype)] = getattr(implementation,
577                                                rdtype_text.replace('-', '_'))
578    dns.rdatatype.register_type(rdtype, rdtype_text, is_singleton)
579