dns.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import struct
  2. from ipaddress import IPv6Address
  3. import dns
  4. import dns.rdtypes.txtbase, dns.rdtypes.svcbbase
  5. import dns.rdtypes.ANY.CDS, dns.rdtypes.ANY.DLV, dns.rdtypes.ANY.DS
  6. import dns.rdtypes.IN.AAAA
  7. def _strip_quotes_decorator(func):
  8. return lambda *args, **kwargs: func(*args, **kwargs)[1:-1]
  9. # Ensure that dnspython agrees with pdns' expectations for SVCB / HTTPS parameters.
  10. # WARNING: This is a global side-effect. It can't be done by extending a class, because dnspython hardcodes the use of
  11. # their dns.rdtypes.svcbbase.*Param classes in the global dns.rdtypes.svcbbase._class_for_key dictionary. We either have
  12. # to globally mess with that dict and insert our custom class, or we just mess with their classes directly.
  13. dns.rdtypes.svcbbase.ALPNParam.to_text = _strip_quotes_decorator(dns.rdtypes.svcbbase.ALPNParam.to_text)
  14. dns.rdtypes.svcbbase.IPv4HintParam.to_text = _strip_quotes_decorator(dns.rdtypes.svcbbase.IPv4HintParam.to_text)
  15. dns.rdtypes.svcbbase.IPv6HintParam.to_text = _strip_quotes_decorator(dns.rdtypes.svcbbase.IPv6HintParam.to_text)
  16. dns.rdtypes.svcbbase.MandatoryParam.to_text = _strip_quotes_decorator(dns.rdtypes.svcbbase.MandatoryParam.to_text)
  17. dns.rdtypes.svcbbase.PortParam.to_text = _strip_quotes_decorator(dns.rdtypes.svcbbase.PortParam.to_text)
  18. @dns.immutable.immutable
  19. class AAAA(dns.rdtypes.IN.AAAA.AAAA):
  20. def to_text(self, origin=None, relativize=True, **kw):
  21. address = super().to_text(origin, relativize, **kw)
  22. return IPv6Address(address).compressed
  23. @dns.immutable.immutable
  24. class LongQuotedTXT(dns.rdtypes.txtbase.TXTBase):
  25. """
  26. A TXT record like RFC 1035, but
  27. - allows arbitrarily long tokens, and
  28. - all tokens must be quoted.
  29. """
  30. def __init__(self, rdclass, rdtype, strings):
  31. # Same as in parent class, but with max_length=None. Note that we are calling __init__ from the grandparent.
  32. super(dns.rdtypes.txtbase.TXTBase, self).__init__(rdclass, rdtype)
  33. self.strings = self._as_tuple(strings,
  34. lambda x: self._as_bytes(x, True, max_length=None))
  35. @classmethod
  36. def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
  37. strings = []
  38. for token in tok.get_remaining():
  39. token = token.unescape_to_bytes()
  40. # The 'if' below is always true in the current code, but we
  41. # are leaving this check in in case things change some day.
  42. if not token.is_quoted_string():
  43. raise dns.exception.SyntaxError("Content must be quoted.")
  44. strings.append(token.value)
  45. if len(strings) == 0:
  46. raise dns.exception.UnexpectedEnd
  47. return cls(rdclass, rdtype, strings)
  48. def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
  49. for long_s in self.strings:
  50. for s in [long_s[i:i+255] for i in range(0, max(len(long_s), 1), 255)]:
  51. l = len(s)
  52. assert l < 256
  53. file.write(struct.pack('!B', l))
  54. file.write(s)
  55. # TODO remove when https://github.com/rthalley/dnspython/pull/625 is in the main codebase
  56. class _DigestLengthMixin:
  57. _digest_length_by_type = {
  58. 1: 20, # SHA-1, RFC 3658 Sec. 2.4
  59. 2: 32, # SHA-256, RFC 4509 Sec. 2.2
  60. 3: 32, # GOST R 34.11-94, RFC 5933 Sec. 4 in conjunction with RFC 4490 Sec. 2.1
  61. 4: 48, # SHA-384, RFC 6605 Sec. 2
  62. }
  63. def __init__(self, *args, **kwargs):
  64. super().__init__(*args, **kwargs)
  65. try:
  66. if self.digest_type == 0: # reserved, RFC 3658 Sec. 2.4
  67. raise ValueError('digest type 0 is reserved')
  68. expected_length = _DigestLengthMixin._digest_length_by_type[self.digest_type]
  69. except KeyError:
  70. raise ValueError('unknown digest type')
  71. if len(self.digest) != expected_length:
  72. raise ValueError('digest length inconsistent with digest type')
  73. @dns.immutable.immutable
  74. class CDS(_DigestLengthMixin, dns.rdtypes.ANY.CDS.CDS):
  75. pass
  76. @dns.immutable.immutable
  77. class DLV(_DigestLengthMixin, dns.rdtypes.ANY.DLV.DLV):
  78. pass
  79. @dns.immutable.immutable
  80. class DS(_DigestLengthMixin, dns.rdtypes.ANY.DS.DS):
  81. pass