Пример #1
0
def ComputeChange(current, to_be_imported, replace_all=False):
    """Returns a change for importing the given record-sets.

  Args:
    current: dict, (name, type) keyed dict of current record-sets.
    to_be_imported: dict, (name, type) keyed dict of record-sets to be imported.
    replace_all: bool, Whether the record-sets to be imported should replace the
      current record-sets.

  Raises:
    ToolException: If conflicting CNAME records are found.

  Returns:
    A Change that describes the actions required to import the given
    record-sets.
  """
    change = messages.Change()
    change.additions = []
    change.deletions = []

    current_keys = set(current.keys())
    keys_to_be_imported = set(to_be_imported.keys())

    intersecting_keys = current_keys.intersection(keys_to_be_imported)
    if not replace_all and intersecting_keys:
        raise exceptions.ToolException(
            'Conflicting records for the following (name type): {0}'.format([
                _NameAndType(current[key]) for key in sorted(intersecting_keys)
            ]))

    for key in intersecting_keys:
        current_record = current[key]
        record_to_be_imported = to_be_imported[key]
        rdtype = rdatatype.from_text(key[1])
        replacement = _RDATA_REPLACEMENTS[rdtype](current_record,
                                                  record_to_be_imported)
        if replacement:
            change.deletions.append(current_record)
            change.additions.append(replacement)

    for key in keys_to_be_imported.difference(current_keys):
        change.additions.append(to_be_imported[key])

    for key in current_keys.difference(keys_to_be_imported):
        current_record = current[key]
        rdtype = rdatatype.from_text(key[1])
        if rdtype is rdatatype.SOA:
            change.deletions.append(current_record)
            change.additions.append(NextSOARecordSet(current_record))
        elif replace_all and rdtype is not rdatatype.NS:
            change.deletions.append(current_record)

    # If the only change is an SOA increment, there is nothing to be done.
    if IsOnlySOAIncrement(change):
        return None

    change.additions.sort(key=_NameAndType)
    change.deletions.sort(key=_NameAndType)
    return change
Пример #2
0
def ComputeChange(current, to_be_imported, replace_all=False):
  """Returns a change for importing the given record-sets.

  Args:
    current: dict, (name, type) keyed dict of current record-sets.
    to_be_imported: dict, (name, type) keyed dict of record-sets to be imported.
    replace_all: bool, Whether the record-sets to be imported should replace the
      current record-sets.

  Raises:
    ToolException: If conflicting CNAME records are found.

  Returns:
    A Change that describes the actions required to import the given
    record-sets.
  """
  change = messages.Change()
  change.additions = []
  change.deletions = []

  current_keys = set(current.keys())
  keys_to_be_imported = set(to_be_imported.keys())

  intersecting_keys = current_keys.intersection(keys_to_be_imported)
  if not replace_all and intersecting_keys:
    raise exceptions.ToolException(
        'Conflicting records for the following (name type): {0}'.format(
            [_NameAndType(current[key]) for key in sorted(intersecting_keys)]))

  for key in intersecting_keys:
    current_record = current[key]
    record_to_be_imported = to_be_imported[key]
    rdtype = rdatatype.from_text(key[1])
    replacement = _RDATA_REPLACEMENTS[rdtype](
        current_record, record_to_be_imported)
    if replacement:
      change.deletions.append(current_record)
      change.additions.append(replacement)

  for key in keys_to_be_imported.difference(current_keys):
    change.additions.append(to_be_imported[key])

  for key in current_keys.difference(keys_to_be_imported):
    current_record = current[key]
    rdtype = rdatatype.from_text(key[1])
    if rdtype is rdatatype.SOA:
      change.deletions.append(current_record)
      change.additions.append(NextSOARecordSet(current_record))
    elif replace_all and rdtype is not rdatatype.NS:
      change.deletions.append(current_record)

  # If the only change is an SOA increment, there is nothing to be done.
  if IsOnlySOAIncrement(change):
    return None

  change.additions.sort(key=_NameAndType)
  change.deletions.sort(key=_NameAndType)
  return change
Пример #3
0
def CreateRecordSetFromArgs(args, api_version='v1'):
  """Creates and returns a record-set from the given args.

  Args:
    args: The arguments to use to create the record-set.
    api_version: [str], the api version to use for creating the RecordSet.

  Raises:
    ToolException: If given record-set type is not supported

  Returns:
    ResourceRecordSet, the record-set created from the given args.
  """
  messages = apis.GetMessagesModule('dns', api_version)
  rd_type = rdatatype.from_text(args.type)
  if import_util.GetRdataTranslation(rd_type) is None:
    raise exceptions.ToolException(
        'unsupported record-set type [{0}]'.format(args.type))

  record_set = messages.ResourceRecordSet()
  # Need to assign kind to default value for useful equals comparisons.
  record_set.kind = record_set.kind
  record_set.name = util.AppendTrailingDot(args.name)
  record_set.ttl = args.ttl
  record_set.type = args.type
  record_set.rrdatas = args.data
  if rd_type is rdatatype.TXT or rd_type is rdatatype.SPF:
    record_set.rrdatas = [import_util.QuotedText(datum) for datum in args.data]
  return record_set
Пример #4
0
def CreateRecordSetFromArgs(args):
    """Creates and returns a record-set from the given args.

  Args:
    args: The arguments to use to create the record-set.

  Raises:
    ToolException: If given record-set type is not supported

  Returns:
    ResourceRecordSet, the record-set created from the given args.
  """
    rd_type = rdatatype.from_text(args.type)
    if import_util.GetRdataTranslation(rd_type) is None:
        raise exceptions.ToolException(
            'unsupported record-set type [{0}]'.format(args.type))

    record_set = messages.ResourceRecordSet()
    # Need to assign kind to default value for useful equals comparisons.
    record_set.kind = record_set.kind
    record_set.name = util.AppendTrailingDot(args.name)
    record_set.ttl = args.ttl
    record_set.type = args.type
    record_set.rrdatas = args.data
    if rd_type is rdatatype.TXT or rd_type is rdatatype.SPF:
        record_set.rrdatas = [
            import_util.QuotedText(datum) for datum in args.data
        ]
    return record_set
Пример #5
0
	def to_file(self, filename):
		today = datetime.date.today();
		curser = int(self._serial);
		newser = int('{}{}{}00'.format(today.year, str(today.month).zfill(2), str(today.day).zfill(2)));
		while newser <= curser:
			newser += 1;
		self._records['@']['SOA'][0].serial = newser;
		self._getSOA();
		origin = "{}.".format(self._zname)
		z = dns.zone.Zone(dns.name.from_text(origin));
		
		for rname in self._records:
			for rtype in self._records[rname]:
				dset = dns.rdataset.Rdataset(rdc.from_text('IN'), rdt.from_text(rtype));
				dset.ttl = 300;
				for r in self._records[rname][rtype]:
					dset.add(r.to_rdata(origin=origin));
				z.replace_rdataset(rname, dset);
		
		tmpfile = '/tmp/tutu-dns-tmp-{}'.format(self._zname);
		z.to_file(tmpfile);
		fr = open(tmpfile, 'r');
		with open(filename, 'wt') as fh:
				fh.write("$ORIGIN {}\n$TTL 300\n".format(origin));
				for line in fr.readlines():
					fh.write(line);
def CreateRecordSetFromArgs(args, api_version='v1'):
    """Creates and returns a record-set from the given args.

  Args:
    args: The arguments to use to create the record-set.
    api_version: [str], the api version to use for creating the RecordSet.

  Raises:
    UnsupportedRecordType: If given record-set type is not supported

  Returns:
    ResourceRecordSet, the record-set created from the given args.
  """
    messages = apis.GetMessagesModule('dns', api_version)
    rd_type = rdatatype.from_text(args.type)
    if import_util.GetRdataTranslation(rd_type) is None:
        raise UnsupportedRecordType('Unsupported record-set type [{0}]'.format(
            args.type))

    record_set = messages.ResourceRecordSet()
    # Need to assign kind to default value for useful equals comparisons.
    record_set.kind = record_set.kind
    record_set.name = util.AppendTrailingDot(args.name)
    record_set.ttl = args.ttl
    record_set.type = args.type
    record_set.rrdatas = args.data
    if rd_type is rdatatype.TXT or rd_type is rdatatype.SPF:
        record_set.rrdatas = [
            import_util.QuotedText(datum) for datum in args.data
        ]
    return record_set
Пример #7
0
    def canonical_presentation_format(any_presentation_format, type_):
        """
        Converts any valid presentation format for a RR into it's canonical presentation format.
        Raises if provided presentation format is invalid.
        """
        rdtype = rdatatype.from_text(type_)

        try:
            # Convert to wire format, ensuring input validation.
            cls = RR._type_map.get(rdtype, dns.rdata)
            wire = cls.from_text(
                rdclass=rdataclass.IN,
                rdtype=rdtype,
                tok=dns.tokenizer.Tokenizer(any_presentation_format),
                relativize=False).to_digestable()

            if len(wire) > 64000:
                raise ValidationError(
                    f'Ensure this value has no more than 64000 byte in wire format (it has {len(wire)}).'
                )

            parser = dns.wire.Parser(wire, current=0)
            with parser.restrict_to(len(wire)):
                rdata = cls.from_wire_parser(rdclass=rdataclass.IN,
                                             rdtype=rdtype,
                                             parser=parser)

            # Convert to canonical presentation format, disable chunking of records.
            # Exempt types which have chunksize hardcoded (prevents "got multiple values for keyword argument 'chunksize'").
            chunksize_exception_types = (dns.rdatatype.OPENPGPKEY,
                                         dns.rdatatype.EUI48,
                                         dns.rdatatype.EUI64)
            if rdtype in chunksize_exception_types:
                return rdata.to_text()
            else:
                return rdata.to_text(chunksize=0)
        except binascii.Error:
            # e.g., odd-length string
            raise ValueError(
                'Cannot parse hexadecimal or base64 record contents')
        except dns.exception.SyntaxError as e:
            # e.g., A/127.0.0.999
            if 'quote' in e.args[0]:
                raise ValueError(
                    f'Data for {type_} records must be given using quotation marks.'
                )
            else:
                raise ValueError(
                    f'Record content for type {type_} malformed: {",".join(e.args)}'
                )
        except dns.name.NeedAbsoluteNameOrOrigin:
            raise ValueError(
                'Hostname must be fully qualified (i.e., end in a dot: "example.com.")'
            )
        except ValueError as ex:
            # e.g., string ("asdf") cannot be parsed into int on base 10
            raise ValueError(f'Cannot parse record contents: {ex}')
        except Exception as e:
            # TODO see what exceptions raise here for faulty input
            raise e
def CreateRecordSetFromArgs(args):
  """Creates and returns a record-set from the given args.

  Args:
    args: The arguments to use to create the record-set.

  Raises:
    ToolException: If given record-set type is not supported

  Returns:
    ResourceRecordSet, the record-set created from the given args.
  """
  rd_type = rdatatype.from_text(args.type)
  if rd_type not in import_util.RDATA_TRANSLATIONS:
    raise exceptions.ToolException(
        'unsupported record-set type [{0}]'.format(args.type))

  record_set = messages.ResourceRecordSet()
  # Need to assign kind to default value for useful equals comparisons.
  record_set.kind = record_set.kind
  record_set.name = util.AppendTrailingDot(args.name)
  record_set.ttl = args.ttl
  record_set.type = args.type
  record_set.rrdatas = args.data
  if rd_type is rdatatype.TXT or rd_type is rdatatype.SPF:
    record_set.rrdatas = [import_util.QuotedText(datum) for datum in args.data]
  return record_set
Пример #9
0
	def to_rdata(self, origin=dns.name.root):
		if not isinstance(origin, dns.name.Name):
			if type(origin) == str:
				origin = dns.name.from_text(origin);
		params = {};
		for attrib in self._slots:
			if attrib in ('mname', 'rname', 'target', 'exchange'):
				params[attrib] = dns.name.from_text(getattr(self, attrib), origin);
			elif attrib in ('serial', 'refresh', 'retry', 'expire',
										'minimum', 'priority', 'preference', 'port'):
				params[attrib] = int(getattr(self, attrib));
			else:
				params[attrib] = getattr(self, attrib);
		params['rdclass'] = rdc.from_text(self._rclass);
		params['rdtype'] = rdt.from_text(self._rtype);
		rdata = dns.rdata.get_rdata_class(rdc.from_text(self._rclass), rdt.from_text(self._rtype))(**params);
		return rdata;
Пример #10
0
    def resolve(self, name, qtype):

        ctx = ub_ctx()
        ctx.resolvconf(self.resolv_conf)

        if not os.path.isfile(self.dnssec_root_key):
            raise Exception('Trust anchor is missing or inaccessible')
        else:
            ctx.add_ta_file(self.dnssec_root_key)

        status, result = ctx.resolve(name, rdatatype.from_text(qtype),
                                     RR_CLASS_IN)
        if status != 0:
            raise WalletNameLookupError

        if not result.secure or result.bogus:
            raise WalletNameLookupInsecureError
        elif not result.havedata:
            return None
        else:
            # We got data
            txt = result.data.as_domain_list()

            # Reference implementation for serving BIP32 and BIP70 requests
            try:
                # BIP32/BIP70 URL will be base64 encoded. Some wallet addresses fail decode.
                # If it fails, assume wallet address or unknown and return
                b64txt = b64decode(txt[0])
            except:
                return txt[0]

            # Fully qualified bitcoin URI, return as is
            if b64txt.startswith('bitcoin:'):
                return b64txt
            elif re.match(r'^https?:\/\/', b64txt):
                try:
                    # Identify localhost or link_local/multicast/private IPs and return without issuing a GET.
                    lookup_url, return_data = WalletNameResolver.get_endpoint_host(
                        b64txt)

                    if return_data:
                        return return_data

                    # Try the URL. Returning response.text and expect a Bitcoin URI as delivered from Addressimo.
                    response = requests.get(lookup_url,
                                            headers={
                                                'X-Forwarded-For':
                                                '%s' % request.access_route[0]
                                            })
                    return response.text
                except Exception:
                    # Return base64 decoded value if we cannot perform a GET on the URL to allow requester to handle.
                    return b64txt
            else:
                # If you made it this far, assume wallet address and return
                return txt[0]
Пример #11
0
def create_challenge_responses_in_dns(zones, fqdn_challenges):
    """
    Create the expected challenge response in dns

    @param zones:           dict of zones, where each zone has a list of fqdns
                            as values
    @type zones:            dict()
    @param fqdn_challenges: dict of zones, containing challenge response
                            (key) of zone
    @type fqdn_challenges:  dict()
    @rtype:                 None
    @exceptions             Can''t parse ddns key or
                            DNS update failed for zone {} with rcode: {}
    """

    if Misc.LE_ZONE_UPDATE_METHOD == 'zone_file':

        for zone in zones.keys():
            dest = str(Pathes.zone_file_root / zone /
                       Pathes.zone_file_include_name)
            lines = []
            for fqdn in zones[zone]:
                sld('fqdn: {}'.format(fqdn))
                lines.append(
                    str('_acme-challenge.{}.  IN TXT  \"{}\"\n'.format(
                        fqdn, fqdn_challenges[fqdn].key)))
            sli('Writing RRs: {}'.format(lines))
            with open(dest, 'w') as file:
                file.writelines(lines)
                ##os.chmod(file.fileno(), Pathes.zone_tlsa_inc_mode)
                ##os.chown(file.fileno(), pathes.zone_tlsa_inc_uid, pathes.zone_tlsa_inc_gid)
            updateZoneCache(zone)
        updateSOAofUpdatedZones()

    elif Misc.LE_ZONE_UPDATE_METHOD == 'ddns':

        txt_datatape = rdatatype.from_text('TXT')
        for zone in zones.keys():
            the_update = ddns_update(zone)
            for fqdn in zones[zone]:
                the_update.delete('_acme-challenge.{}.'.format(fqdn),
                                  txt_datatape)
                the_update.add('_acme-challenge.{}.'.format(fqdn), 60,
                               txt_datatape, fqdn_challenges[fqdn].key)
                sld('DNS update of RR: {}'.format(
                    '_acme-challenge.{}.  60 TXT  \"{}\"'.format(
                        fqdn, fqdn_challenges[fqdn].key)))
            response = dns_query.tcp(the_update, '127.0.0.1', timeout=10)
            sld('DNS update delete/add returned response: {}'.format(response))
            rc = response.rcode()
            if rc != 0:
                sle('DNS delete failed for zone {} with rcode: {}:\n{}'.format(
                    zone, rc.to_text(rc), rc))
                raise Exception(
                    'DNS update failed for zone {} with rcode: {}'.format(
                        zone, rc.to_text(rc)))
Пример #12
0
	def __init__(self, rtype, rclass='IN', ttl=300):
		self._rtype = rtype;
		self._rclass = rclass;
		self._ttl = ttl;
		
		if not self._check_type(rtype):
			raise NotImplementedError('Not a valid type ({})'.format(rtype));
		
		if not self._check_class(rclass):
			raise NotImplementedError('Not a valid class ({})'.format(rclass));
		
		self._slots = dns.rdata.get_rdata_class(rdc.from_text(rclass), rdt.from_text(rtype)).__slots__;
Пример #13
0
def IsOnlySOAIncrement(change):
  """Returns True if the change only contains an SOA increment, False otherwise.

  Args:
    change: Change, the change to be checked

  Returns:
    True if the change only contains an SOA increment, False otherwise.
  """
  return (len(change.additions) == len(change.deletions) == 1 and
          rdatatype.from_text(change.deletions[0].type) is rdatatype.SOA and
          NextSOARecordSet(change.deletions[0]) == change.additions[0])
Пример #14
0
def IsOnlySOAIncrement(change):
    """Returns True if the change only contains an SOA increment, False otherwise.

  Args:
    change: Change, the change to be checked

  Returns:
    True if the change only contains an SOA increment, False otherwise.
  """
    return (len(change.additions) == len(change.deletions) == 1
            and rdatatype.from_text(change.deletions[0].type) is rdatatype.SOA
            and NextSOARecordSet(change.deletions[0]) == change.additions[0])
Пример #15
0
def IsOnlySOAIncrement(change, api_version='v1'):
    """Returns True if the change only contains an SOA increment, False otherwise.

  Args:
    change: Change, the change to be checked
    api_version: [str], the api version to use for creating the records.

  Returns:
    True if the change only contains an SOA increment, False otherwise.
  """
    return (len(change.additions) == len(change.deletions) == 1
            and rdatatype.from_text(change.deletions[0].type) is rdatatype.SOA
            and NextSOARecordSet(change.deletions[0],
                                 api_version) == change.additions[0])
Пример #16
0
def IsOnlySOAIncrement(change, api_version='v1'):
  """Returns True if the change only contains an SOA increment, False otherwise.

  Args:
    change: Change, the change to be checked
    api_version: [str], the api version to use for creating the records.

  Returns:
    True if the change only contains an SOA increment, False otherwise.
  """
  return (
      len(change.additions) == len(change.deletions) == 1 and
      rdatatype.from_text(change.deletions[0].type) is rdatatype.SOA and
      NextSOARecordSet(change.deletions[0], api_version) == change.additions[0])
def _ToStandardEnumTypeSafe(string_type):
    """Converts string_type to an RdataType enum value if it is a standard type.

  Only standard record types can be converted to a RdataType, all other types
  will cause an exception. This method allow getting the standard enum type if
  available without throwing an exception if an extended type is provided.

  Args:
    string_type: [str] The record type as a string.

  Returns:
    The record type as an RdataType enum or None if the type is not a standard
    DNS type.
  """
    if string_type in record_types.CLOUD_DNS_EXTENDED_TYPES:
        return None
    return rdatatype.from_text(string_type)
def _TryParseRRTypeFromString(type_str):
    """Tries to parse the rrtype wire value from the given string.

  Args:
    type_str: The record type as a string (e.g. "A", "MX"...).

  Raises:
    UnsupportedRecordType: If given record-set type is not supported

  Returns:
    The wire value rrtype as an int or rdatatype enum.
  """
    rd_type = rdatatype.from_text(type_str)
    if rd_type not in record_types.SUPPORTED_TYPES:
        raise UnsupportedRecordType('Unsupported record-set type [%s]' %
                                    type_str)
    return rd_type
Пример #19
0
def WriteToZoneFile(zone_file, record_sets, domain):
    """Writes the given record-sets in zone file format to the given file.

  Args:
    zone_file: file, File into which the records should be written.
    record_sets: list, ResourceRecordSets to be written out.
    domain: str, The origin domain for the zone file.
  """
    zone_contents = zone.Zone(name.from_text(domain))
    for record_set in record_sets:
        rdset = zone_contents.get_rdataset(record_set.name,
                                           record_set.type,
                                           create=True)
        for rrdata in record_set.rrdatas:
            rdset.add(rdata.from_text(rdataclass.IN,
                                      rdatatype.from_text(record_set.type),
                                      str(rrdata),
                                      origin=zone_contents.origin),
                      ttl=record_set.ttl)
    zone_contents.to_file(zone_file, relativize=False)
Пример #20
0
def WriteToZoneFile(zone_file, record_sets, domain):
  """Writes the given record-sets in zone file format to the given file.

  Args:
    zone_file: file, File into which the records should be written.
    record_sets: list, ResourceRecordSets to be written out.
    domain: str, The origin domain for the zone file.
  """
  zone_contents = zone.Zone(name.from_text(domain))
  for record_set in record_sets:
    rdset = zone_contents.get_rdataset(record_set.name,
                                       record_set.type,
                                       create=True)
    for rrdata in record_set.rrdatas:
      rdset.add(rdata.from_text(rdataclass.IN,
                                rdatatype.from_text(record_set.type),
                                str(rrdata),
                                origin=zone_contents.origin),
                ttl=record_set.ttl)
  zone_contents.to_file(zone_file, relativize=False)
Пример #21
0
def RecordSetsFromYamlFile(yaml_file, api_version='v1'):
    """Returns record-sets read from the given yaml file.

  Args:
    yaml_file: file, A yaml file with records.
    api_version: [str], the api version to use for creating the records.

  Returns:
    A (name, type) keyed dict of ResourceRecordSets that were obtained from the
    yaml file. Note that only A, AAAA, CNAME, MX, PTR, SOA, SPF, SRV, and TXT
    record-sets are retrieved. Other record-set types are not supported by Cloud
    DNS. Also, the master NS field for SOA records is discarded since that is
    provided by Cloud DNS.
  """
    record_sets = {}
    messages = core_apis.GetMessagesModule('dns', api_version)

    yaml_record_sets = yaml.load_all(yaml_file)
    for yaml_record_set in yaml_record_sets:
        rdata_type = rdatatype.from_text(yaml_record_set['type'])
        if GetRdataTranslation(rdata_type) is None:
            continue

        record_set = messages.ResourceRecordSet()
        # Need to assign kind to default value for useful equals comparisons.
        record_set.kind = record_set.kind
        record_set.name = yaml_record_set['name']
        record_set.ttl = yaml_record_set['ttl']
        record_set.type = yaml_record_set['type']
        record_set.rrdatas = yaml_record_set['rrdatas']

        if rdata_type is rdatatype.SOA:
            # Make master NS name substitutable.
            record_set.rrdatas[0] = re.sub(r'\S+',
                                           '{0}',
                                           record_set.rrdatas[0],
                                           count=1)

        record_sets[(record_set.name, record_set.type)] = record_set

    return record_sets
Пример #22
0
def RecordSetsFromYamlFile(yaml_file):
    """Returns record-sets read from the given yaml file.

  Args:
    yaml_file: file, A yaml file with records.

  Returns:
    A (name, type) keyed dict of ResourceRecordSets that were obtained from the
    yaml file. Note that only A, AAAA, CNAME, MX, PTR, SOA, SPF, SRV, and TXT
    record-sets are retrieved. Other record-set types are not supported by Cloud
    DNS. Also, the master NS field for SOA records is discarded since that is
    provided by Cloud DNS.
  """
    record_sets = {}

    yaml_record_sets = yaml.safe_load_all(yaml_file)
    for yaml_record_set in yaml_record_sets:
        rdata_type = rdatatype.from_text(yaml_record_set['type'])
        if rdata_type not in _RDATA_TRANSLATIONS:
            continue

        record_set = messages.ResourceRecordSet()
        # Need to assign kind to default value for useful equals comparisons.
        record_set.kind = record_set.kind
        record_set.name = yaml_record_set['name']
        record_set.ttl = yaml_record_set['ttl']
        record_set.type = yaml_record_set['type']
        record_set.rrdatas = yaml_record_set['rrdatas']

        if rdata_type is rdatatype.SOA:
            # Make master NS name substitutable.
            rdata_parts = record_set.rrdatas[0].split()
            rdata_parts[0] = '{0}'
            record_set.rrdatas[0] = ' '.join(rdata_parts)
        elif rdata_type is rdatatype.SPF or rdata_type is rdatatype.TXT:
            # Escape text strings to prevent tokenization by the service
            record_set.rrdatas = [_EscapedText(x) for x in record_set.rrdatas]

        record_sets[(record_set.name, record_set.type)] = record_set

    return record_sets
Пример #23
0
def delete_challenge_responses_in_dns(zones):
    """
    Delete the challenge response in dns, created by
                            create_challenge_responses_in_dns()

    @param zones:           dict of zones, where each zone has a list of fqdns
                            as values
    @type zones:            dict()
    @rtype:                 None
    @exceptions             Can''t parse ddns key or
                            DNS update failed for zone {} with rcode: {}
    """

    if Misc.LE_ZONE_UPDATE_METHOD == 'zone_file':

        for zone in zones.keys():
            dest = str(Pathes.zone_file_root / zone /
                       Pathes.zone_file_include_name)
            with open(dest, 'w') as file:
                file.writelines(('', ))
            updateZoneCache(zone)
        updateSOAofUpdatedZones()

    elif Misc.LE_ZONE_UPDATE_METHOD == 'ddns':

        txt_datatape = rdatatype.from_text('TXT')
        for zone in zones.keys():
            the_update = ddns_update(zone)
            for fqdn in zones[zone]:
                the_update.delete('_acme-challenge.{}.'.format(fqdn),
                                  txt_datatape)
            response = dns_query.tcp(the_update, '127.0.0.1', timeout=10)
            sld('DNS update delete/add returned response: {}'.format(response))
            rc = response.rcode()
            if rc != 0:
                sle('DNS update failed for zone {} with rcode: {}:\n{}'.format(
                    zone, rc.to_text(rc), rc))
                raise Exception(
                    'DNS update failed for zone {} with rcode: {}'.format(
                        zone, rc.to_text(rc)))
Пример #24
0
def RecordSetsFromYamlFile(yaml_file, api_version='v1'):
  """Returns record-sets read from the given yaml file.

  Args:
    yaml_file: file, A yaml file with records.
    api_version: [str], the api version to use for creating the records.

  Returns:
    A (name, type) keyed dict of ResourceRecordSets that were obtained from the
    yaml file. Note that only A, AAAA, CNAME, MX, PTR, SOA, SPF, SRV, and TXT
    record-sets are retrieved. Other record-set types are not supported by Cloud
    DNS. Also, the master NS field for SOA records is discarded since that is
    provided by Cloud DNS.
  """
  record_sets = {}
  messages = core_apis.GetMessagesModule('dns', api_version)

  yaml_record_sets = yaml.load_all(yaml_file)
  for yaml_record_set in yaml_record_sets:
    rdata_type = rdatatype.from_text(yaml_record_set['type'])
    if GetRdataTranslation(rdata_type) is None:
      continue

    record_set = messages.ResourceRecordSet()
    # Need to assign kind to default value for useful equals comparisons.
    record_set.kind = record_set.kind
    record_set.name = yaml_record_set['name']
    record_set.ttl = yaml_record_set['ttl']
    record_set.type = yaml_record_set['type']
    record_set.rrdatas = yaml_record_set['rrdatas']

    if rdata_type is rdatatype.SOA:
      # Make master NS name substitutable.
      record_set.rrdatas[0] = re.sub(r'\S+', '{0}', record_set.rrdatas[0],
                                     count=1)

    record_sets[(record_set.name, record_set.type)] = record_set

  return record_sets
Пример #25
0
	def delete(self):
			session = self.request.session;
			try:
				rzone = session['rzone'];
			except NameError:
				return HTTPFound('/dns/zones');
			
			try:
				oname = session['rname'];
				rtype = session['rtype'];
				odata = session['rdata'];
				rclass = session['rclass'];
			except NameError:
				return HTTPFound('/dns/zone/{}'.format(rzone));
			
			namedconf = tutuconfig.get('namedconf', 'dnsbind');
			ncp = NamedConfParser();
			ncp.from_file(namedconf);
			zonefile = ncp.find_zone_file(rzone);
			
			z = zone.from_file(zonefile);
			
			dset = z.get_rdataset(oname, rtype);
			newdset = dns.rdataset.Rdataset(rdc.from_text(rclass), rdt.from_text(rtype));
			newdset.ttl = 300;
			if rtype == 'SOA':
				newdset.add(rdata);
			else:
				for rd in dset:
					if odata == rd.to_text():
						pass
					else:
						newdset.add(rd);
			z.replace_rdataset(oname, newdset);
			
			tutuzone.save_zone(z, zonefile);
			
			return HTTPFound('/dns/zone/{}'.format(rzone));
# vim: set ts=2:
Пример #26
0
    def resolve(self, name, qtype):

        ctx = ub_ctx()
        ctx.resolvconf(self.resolv_conf)

        if not os.path.isfile(self.dnssec_root_key):
            raise Exception('Trust anchor is missing or inaccessible')
        else:
            ctx.add_ta_file(self.dnssec_root_key)

        status, result = ctx.resolve(name, rdatatype.from_text(qtype), RR_CLASS_IN)
        if status != 0:
            raise WalletNameLookupError

        if not result.secure or result.bogus:
            raise WalletNameLookupInsecureError
        elif not result.havedata:
            return None
        else:
            # We got data
            txt = result.data.as_domain_list()
            return txt[0]
Пример #27
0
def RecordSetsFromYamlFile(yaml_file):
  """Returns record-sets read from the given yaml file.

  Args:
    yaml_file: file, A yaml file with records.

  Returns:
    A (name, type) keyed dict of ResourceRecordSets that were obtained from the
    yaml file. Note that only A, AAAA, CNAME, MX, PTR, SOA, SPF, SRV, and TXT
    record-sets are retrieved. Other record-set types are not supported by Cloud
    DNS. Also, the main NS field for SOA records is discarded since that is
    provided by Cloud DNS.
  """
  record_sets = {}

  yaml_record_sets = yaml.safe_load_all(yaml_file)
  for yaml_record_set in yaml_record_sets:
    rdata_type = rdatatype.from_text(yaml_record_set['type'])
    if rdata_type not in RDATA_TRANSLATIONS:
      continue

    record_set = messages.ResourceRecordSet()
    # Need to assign kind to default value for useful equals comparisons.
    record_set.kind = record_set.kind
    record_set.name = yaml_record_set['name']
    record_set.ttl = yaml_record_set['ttl']
    record_set.type = yaml_record_set['type']
    record_set.rrdatas = yaml_record_set['rrdatas']

    if rdata_type is rdatatype.SOA:
      # Make main NS name substitutable.
      record_set.rrdatas[0] = re.sub(r'\S+', '{0}', record_set.rrdatas[0],
                                     count=1)

    record_sets[(record_set.name, record_set.type)] = record_set

  return record_sets
Пример #28
0
def ComputeChange(current,
                  to_be_imported,
                  replace_all=False,
                  origin=None,
                  replace_origin_ns=False,
                  api_version='v1'):
    """Returns a change for importing the given record-sets.

  Args:
    current: dict, (name, type) keyed dict of current record-sets.
    to_be_imported: dict, (name, type) keyed dict of record-sets to be imported.
    replace_all: bool, Whether the record-sets to be imported should replace the
      current record-sets.
    origin: string, the name of the apex zone ex. "foo.com"
    replace_origin_ns: bool, Whether origin NS records should be imported.
    api_version: [str], the api version to use for creating the records.

  Raises:
    ConflictingRecordsFound: If conflicting records are found.

  Returns:
    A Change that describes the actions required to import the given
    record-sets.
  """
    messages = core_apis.GetMessagesModule('dns', api_version)
    change = messages.Change()
    change.additions = []
    change.deletions = []

    current_keys = set(current.keys())
    keys_to_be_imported = set(to_be_imported.keys())

    intersecting_keys = current_keys.intersection(keys_to_be_imported)
    if not replace_all and intersecting_keys:
        raise ConflictingRecordsFound(
            'The following records (name type) already exist: {0}'.format([
                _NameAndType(current[key]) for key in sorted(intersecting_keys)
            ]))

    for key in intersecting_keys:
        current_record = current[key]
        record_to_be_imported = to_be_imported[key]
        rdtype = rdatatype.from_text(key[1])
        if not _FilterOutRecord(current_record.name, rdtype, origin,
                                replace_origin_ns):
            replacement = _RDATA_REPLACEMENTS[rdtype](current_record,
                                                      record_to_be_imported,
                                                      api_version=api_version)
            if replacement:
                change.deletions.append(current_record)
                change.additions.append(replacement)

    for key in keys_to_be_imported.difference(current_keys):
        change.additions.append(to_be_imported[key])

    for key in current_keys.difference(keys_to_be_imported):
        current_record = current[key]
        rdtype = rdatatype.from_text(key[1])
        if rdtype is rdatatype.SOA:
            change.deletions.append(current_record)
            change.additions.append(
                NextSOARecordSet(current_record, api_version))
        elif replace_all and not _FilterOutRecord(current_record.name, rdtype,
                                                  origin, replace_origin_ns):
            change.deletions.append(current_record)

    # If the only change is an SOA increment, there is nothing to be done.
    if IsOnlySOAIncrement(change, api_version):
        return None

    change.additions.sort(key=_NameAndType)
    change.deletions.sort(key=_NameAndType)
    return change
Пример #29
0
def ComputeChange(current, to_be_imported, replace_all=False,
                  origin=None, replace_origin_ns=False, api_version='v1'):
  """Returns a change for importing the given record-sets.

  Args:
    current: dict, (name, type) keyed dict of current record-sets.
    to_be_imported: dict, (name, type) keyed dict of record-sets to be imported.
    replace_all: bool, Whether the record-sets to be imported should replace the
      current record-sets.
    origin: string, the name of the apex zone ex. "foo.com"
    replace_origin_ns: bool, Whether origin NS records should be imported.
    api_version: [str], the api version to use for creating the records.

  Raises:
    ToolException: If conflicting CNAME records are found.

  Returns:
    A Change that describes the actions required to import the given
    record-sets.
  """
  messages = core_apis.GetMessagesModule('dns', api_version)
  change = messages.Change()
  change.additions = []
  change.deletions = []

  current_keys = set(current.keys())
  keys_to_be_imported = set(to_be_imported.keys())

  intersecting_keys = current_keys.intersection(keys_to_be_imported)
  if not replace_all and intersecting_keys:
    raise exceptions.ToolException(
        'Conflicting records for the following (name type): {0}'.format(
            [_NameAndType(current[key]) for key in sorted(intersecting_keys)]))

  for key in intersecting_keys:
    current_record = current[key]
    record_to_be_imported = to_be_imported[key]
    rdtype = rdatatype.from_text(key[1])
    if not _FilterOutRecord(current_record.name,
                            rdtype,
                            origin,
                            replace_origin_ns):
      replacement = _RDATA_REPLACEMENTS[rdtype](
          current_record, record_to_be_imported, api_version=api_version)
      if replacement:
        change.deletions.append(current_record)
        change.additions.append(replacement)

  for key in keys_to_be_imported.difference(current_keys):
    change.additions.append(to_be_imported[key])

  for key in current_keys.difference(keys_to_be_imported):
    current_record = current[key]
    rdtype = rdatatype.from_text(key[1])
    if rdtype is rdatatype.SOA:
      change.deletions.append(current_record)
      change.additions.append(NextSOARecordSet(current_record, api_version))
    elif replace_all and not _FilterOutRecord(current_record.name,
                                              rdtype,
                                              origin,
                                              replace_origin_ns):
      change.deletions.append(current_record)

  # If the only change is an SOA increment, there is nothing to be done.
  if IsOnlySOAIncrement(change, api_version):
    return None

  change.additions.sort(key=_NameAndType)
  change.deletions.sort(key=_NameAndType)
  return change
Пример #30
0
    def clean_records(self, records_presentation_format):
        """
        Validates the records belonging to this set. Validation rules follow the DNS specification; some types may
        incur additional validation rules.

        Raises ValidationError if violation of DNS specification is found.

        Returns a set of records in canonical presentation format.

        :param records_presentation_format: iterable of records in presentation format
        """
        rdtype = rdatatype.from_text(self.type)
        errors = []

        def _error_msg(record, detail):
            return f'Record content of {self.type} {self.name} invalid: \'{record}\': {detail}'

        records_canonical_format = set()
        for r in records_presentation_format:
            try:
                r_canonical_format = RR.canonical_presentation_format(
                    r, rdtype)
            except binascii.Error:
                # e.g., odd-length string
                errors.append(
                    _error_msg(
                        r,
                        'Cannot parse hexadecimal or base64 record contents'))
            except dns.exception.SyntaxError as e:
                # e.g., A/127.0.0.999
                if 'quote' in e.args[0]:
                    errors.append(
                        _error_msg(
                            r,
                            f'Data for {self.type} records must be given using quotation marks.'
                        ))
                else:
                    errors.append(
                        _error_msg(
                            r,
                            f'Record content malformed: {",".join(e.args)}'))
            except dns.name.NeedAbsoluteNameOrOrigin:
                errors.append(
                    _error_msg(
                        r,
                        'Hostname must be fully qualified (i.e., end in a dot: "example.com.")'
                    ))
            except ValueError:
                # e.g., string ("asdf") cannot be parsed into int on base 10
                errors.append(_error_msg(r, 'Cannot parse record contents'))
            except Exception as e:
                # TODO see what exceptions raise here for faulty input
                raise e
            else:
                if r_canonical_format in records_canonical_format:
                    errors.append(
                        _error_msg(
                            r,
                            f'Duplicate record content: this is identical to '
                            f'\'{r_canonical_format}\''))
                else:
                    records_canonical_format.add(r_canonical_format)

        if any(errors):
            raise ValidationError(errors)

        return records_canonical_format
Пример #31
0
	def create(self):
		if self.posted():
			session = self.request.session;
			try:
				rzone = session['rzone'];
			except NameError:
				return HTTPFound('/dns/zones');
			
			try:
				oname = session['rname'];
				rtype = session['rtype'];
				odata = session['rdata'];
				rclass = session['rclass'];
			except NameError:
				return HTTPFound('/dns/zone/{}'.format(rzone));
			
			errors = {};
			errors['name'] = 0;
			rname = self.request.POST['name'];
			if len(rname) == 0:
				errors['name'] = 1;

			params = {};
			record = [];

			for data in self.request.POST:
				tmprec = {};
				tmprec['name'] = data;
				tmprec['value'] = self.request.POST[data];
				if data != 'name':
					record.append(tmprec);

				errors[data] = 0;

				if len(self.request.POST[data]) == 0:
					errors[data] = 1;
					continue;
				if data in ('serial', 'refresh', 'retry', 'expire',
											'minimum', 'priority', 'preference', 'port'):
					params[data] = int(self.request.POST[data]);
				elif data != 'name':
					params[data] = self.request.POST[data];
			

			errorcount = 0;
			for error in errors:
				errorcount += errors[error];
			if errorcount > 0:
				if rname == '@':
					pname = rzone;
				else:
					pname = "{}.{}".format(rname, rzone);

				return {'rname': rname, 'pname':pname, 'rzone': rzone, 'rtype': rtype, 
					'record': record, 'helpers':tuturecord.helpers, 'errors': errors};

			r = tuturecord.Record(rtype, rclass);
			for param in params:
				setattr(r, param, params[param]);
			
			z = tutuzone.Zone(rzone);
			z.load();
			
			z.replace_record(oname, odata, rname, r);
			
			z.save();
			
			return HTTPFound(location='/dns/zone/{}'.format(rzone));
		rzone = self.request.params.get('zone', None);
		
		if rzone is None:
			return HTTPFound(location='/dns/zones');
		
		rtype = self.request.params.get('type', None);
		if rtype is None:
			return HTTPFound(location='/dns/zone/{}'.format(rzone));
		
		z = tutuzone.Zone(rzone);
		
		if z.is_reverse():
			types = tuturecord.Record.reverse_supported_types;
		else:
			types = tuturecord.Record.forward_supported_types;
		
		if not rtype in types:
			return HTTPFound(location='/dns/zone/{}'.format(rzone));
		
		session = self.request.session;
		session['rzone'] = rzone;
		session['rname'] = None;
		session['rtype'] = rtype;
		session['rdata'] = None;
		session['rclass'] = 'IN';
		session.changed();
		
		slots = dns.rdata.get_rdata_class(rdc.from_text('IN'), rdt.from_text(rtype)).__slots__;
		
		record = [];
		errors = {};
		
		for slot in slots:
			errors[slot] = 0;
			record.append({'name': slot, 'value': ''});
		
		return {'rname': '', 'pname':'', 'rzone': rzone, 'rtype': rtype,
						'record': record, 'helpers':tuturecord.helpers, 'errors': errors, 'newrecord': True};
Пример #32
0
	def from_text(self, val):
		rdata = dns.rdata.from_text(rdc.from_text(self._rclass), rdt.from_text(self._rtype), val);
		self.from_rdata(rdata);
Пример #33
0
    def resolve(self, name, qtype):
        '''

        Resolves a Blockchain-based (Namecoin) DNS Name via 2 step process using DNSSEC

        Step 1:
        -------
        Get NS and DS records for the Namecoin name (for example: www.mattdavid.bit) from the Namecoin Client

        Step 2:
        -------
        For each listed nameserver:

            - Create a temporary config file for use with unbound
            - Set Unbound's Trust Anchor to be the given DS records for the Namecoin-based domain name
            - Do DNSSEC-enabled DNS resolution for the given name / qtype


        :param name: DNS Record Name Query (for example: www.mattdavid.bit)
        :param qtype: String representation of query type (for example: A, AAAA, TXT, NS, SOA, etc...)
        :return: Resolved value if successful, None if un-successful
        '''

        name = name.rstrip('.')
        if not name.endswith('.bit'):
            raise ValueError('This is not a valid .bit domain')

        domains = name.split('.')
        domains.reverse()
        if len(domains) < 2:
            raise ValueError('At least SLD Required')

        # Get Namecoin-based Domain Info from Namecoin Blockchain
        nc_domain = self.nc_name_resolver.name_show(domains[1])
        if not nc_domain or not nc_domain.get('value'):
            log.error(
                'No Name Value Data Found for Namecoin-based Domain Name: d/%s'
                % domains[1])
            raise NamecoinValueException('No Name Value Data Found for: d/%s' %
                                         domains[1])

        nc_value = json.loads(nc_domain.get('value', '{}').replace('\'', '"'))
        if not nc_value.get('ds'):
            log.error(
                'No DS Records Present for Namecoin-based Domain Name: %s' %
                name)
            raise NoDSRecordException()

        if not nc_value.get('ns'):
            log.error(
                'No NS Records Present for Namecoin-based Domain Name: %s' %
                name)
            raise NoNameserverException()

        sld = '%s.%s.' % (domains[1], domains[0])
        ds_record = ' '.join([str(x) for x in nc_value['ds'][0][0:3]])

        # Handle both Hex and Base64 encoding (Base64 is the preferred encoding) per:
        # https://wiki.namecoin.info/index.php?title=Domain_Name_Specification
        if re.match('^[0-9a-fA-F]*$', nc_value['ds'][0][3]):
            ds_record += ' %s' % nc_value['ds'][0][3]
        else:
            ds_record += ' %s' % base64.b64decode(
                nc_value['ds'][0][3]).encode('hex').upper()

        ds_ta = '%s IN DS %s' % (sld, ds_record)

        ns_ctx = ub_ctx()
        ns_ctx.resolvconf(self.resolv_conf)

        if not os.path.isfile(self.dnssec_root_key):
            log.error("Trust anchor missing or inaccessible")
            raise Exception("Trust anchor is missing or inaccessible: %s" %
                            self.dnssec_root_key)
        else:
            ns_ctx.add_ta_file(self.dnssec_root_key)

        last_error = None
        for ns in nc_value.get('ns', []):

            lookup_value = None

            status, result = ns_ctx.resolve(ns, rdatatype.from_text('A'),
                                            rdataclass.from_text('IN'))

            # NOTE: We do not require secure DNS resolution here because the Blockchain-stored DS records work as the trust anchor
            # and the signed RRSIG DNS results from the final DNS+DNSSEC lookup will be able to complete the chain of trust
            if status == 0 and result and result.data and not result.bogus:
                tmp_config_file = self._build_temp_unbound_config(
                    sld,
                    result.data.as_address_list()[0])
            else:
                last_error = InvalidNameserverException()
                log.warn('No or Invalid Resolution Result for Nameserver: %s' %
                         ns)
                continue

            ctx = ub_ctx()
            ctx.config(tmp_config_file)
            ctx.add_ta(str(ds_ta))

            _qtype = None
            try:
                _qtype = rdatatype.from_text(qtype)
            except Exception as e:
                log.error(
                    'Unable to get RDATAType for Given Query Type [%s]: %s' %
                    (qtype, str(e)))
                raise ValueError('Unable to get RDATAType for Query Type %s' %
                                 qtype)

            status, result = ctx.resolve(name, _qtype,
                                         rdataclass.from_text('IN'))
            if status != 0:
                log.info("DNS Resolution Failed: %s [%s]" % (name, _qtype))
            elif status == 0:

                if not result.secure:
                    log.info(
                        "DNS Resolution Returned Insecure Result: %s [%s]" %
                        (name, qtype))
                    last_error = InsecureResultException()

                elif result.bogus:
                    log.info("DNS Resolution Returned Bogus Result: %s [%s]" %
                             (name, qtype))
                    last_error = BogusResultException()

                elif not result.havedata:
                    log.info("DNS Resolution Returned Empty Result: %s [%s]" %
                             (name, qtype))
                    last_error = EmptyResultException()

                else:
                    # Get appropriate data by query type
                    if qtype in ['A', 'AAAA']:
                        lookup_value = result.data.as_address_list()
                    elif qtype in ['CNAME', 'TXT']:
                        lookup_value = result.data.as_domain_list()
                    elif qtype in ['MX']:
                        lookup_value = result.data.as_mx_list()
                    else:
                        last_error = NotImplementedError(
                            'Unsupported DNS Query Type: %s' % qtype)

            self._delete_temp_unbound_config(tmp_config_file)

            if lookup_value:
                return lookup_value[0]

            if last_error and isinstance(last_error, NotImplementedError):
                raise last_error

        log.error('DNS Resolution Failed: %s [%s]' % (name, qtype))
        if last_error:
            raise last_error

        return None
Пример #34
0
def distribute_tlsa_rrs(cert_meta: Certificate,
                        hashes: Union[Tuple[str], List[str]]) -> None:
    """
    Distribute TLSA RR.
    Puts TLSA RR fqdn into DNS zone, by dynamic dns or editing zone file and updating zone cache.
    If cert has altnames, one set of TLSA RRs is inserted per altname and per TLSA prefix.
    :param cert_meta:
    :param hashes: list of hashes, may include active and prepublishes hashes for all algos
    :return:
    """

    if len(cert_meta.tlsaprefixes) == 0: return

    sli('Distributing TLSA RRs for DANE.')

    if Pathes.tlsa_dns_master == '':  # DNS master on local host

        if Misc.LE_ZONE_UPDATE_METHOD == 'zone_file':

            for (zone, fqdn) in zone_and_FQDN_from_altnames(cert_meta):
                filename = fqdn + '.tlsa'
                dest = str(Pathes.zone_file_root / zone / filename)
                sli('{} => {}'.format(filename, dest))
                tlsa_lines = []
                for prefix in cert_meta.tlsaprefixes.keys():
                    for hash in hashes:
                        tlsa_lines.append(
                            str(prefix.format(fqdn) + ' ' + hash + '\n'))
                with open(dest, 'w') as fd:
                    fd.writelines(tlsa_lines)
                updateZoneCache(zone)

        elif Misc.LE_ZONE_UPDATE_METHOD == 'ddns':

            tlsa_datatype = rdatatype.from_text('TLSA')
            zones = {}
            for (zone, fqdn) in cert_meta.zone_and_FQDN_from_altnames():
                if zone in zones:
                    if fqdn not in zones[zone]: zones[zone].append(fqdn)
                else:
                    zones[zone] = [fqdn]
            for zone in zones:
                the_update = ddns_update(zone)
                for fqdn in zones[zone]:
                    for prefix in cert_meta.tlsaprefixes.keys():
                        pf_with_fqdn = str(prefix.format(fqdn))
                        fields = pf_with_fqdn.split(maxsplit=4)
                        sld('Deleting possible old TLSAs: {}'.format(
                            fields[0]))
                        the_update.delete(fields[0], tlsa_datatype)

                        for hash in hashes:
                            sld('Adding TLSA: {} {} {} {}'.format(
                                fields[0], int(fields[1]), fields[3],
                                fields[4] + ' ' + hash))
                            the_update.add(fields[0], int(fields[1]),
                                           fields[3], fields[4] + ' ' + hash)

                response = dns_query.tcp(the_update, '127.0.0.1', timeout=10)
                rc = response.rcode()
                if rc != 0:
                    sle('DNS update failed for zone {} with rcode: {}:\n{}'.
                        format(zone, response.rcode.to_text(rc),
                               response.rcode))
                    raise Exception(
                        'DNS update add failed for zone {} with rcode: {}'.
                        format(zone, response.rcode.to_text(rc)))

    else:  # remote DNS master ( **INCOMPLETE**)
        sle('Remote DNS master server is currently not supported. Must be on same host as this script.'
            )
        exit(1)
        with ssh_connection(Pathes.tlsa_dns_master) as client:
            with client.open_sftp() as sftp:
                chdir(str(Pathes.work_tlsa))
                p = Path('.')
                sftp.chdir(str(Pathes.zone_file_root))

                for child_dir in p.iterdir():
                    for child in child_dir.iterdir():
                        sli('{} => {}:{}'.format(child, Pathes.tlsa_dns_master,
                                                 child))
                        fat = sftp.put(str(child), str(child), confirm=True)
                        sld('size={}, uid={}, gid={}, mtime={}'.format(
                            fat.st_size, fat.st_uid, fat.st_gid, fat.st_mtime))
Пример #35
0
    def resolve(self, name, qtype):
        '''

        Resolves a Blockchain-based (Namecoin) DNS Name via 2 step process using DNSSEC

        Step 1:
        -------
        Get NS and DS records for the Namecoin name (for example: www.mattdavid.bit) from the Namecoin Client

        Step 2:
        -------
        For each listed nameserver:

            - Create a temporary config file for use with unbound
            - Set Unbound's Trust Anchor to be the given DS records for the Namecoin-based domain name
            - Do DNSSEC-enabled DNS resolution for the given name / qtype


        :param name: DNS Record Name Query (for example: www.mattdavid.bit)
        :param qtype: String representation of query type (for example: A, AAAA, TXT, NS, SOA, etc...)
        :return: Resolved value if successful, None if un-successful
        '''

        name = name.rstrip('.')
        if not name.endswith('.bit'):
            raise ValueError('This is not a valid .bit domain')

        domains = name.split('.')
        domains.reverse()
        if len(domains) < 2:
            raise ValueError('At least SLD Required')

        # Get Namecoin-based Domain Info from Namecoin Blockchain
        nc_domain = self.nc_name_resolver.name_show(domains[1])
        if not nc_domain or not nc_domain.get('value'):
            log.error('No Name Value Data Found for Namecoin-based Domain Name: d/%s' % domains[1])
            raise NamecoinValueException('No Name Value Data Found for: d/%s' % domains[1])

        nc_value = json.loads(nc_domain.get('value', '{}').replace('\'','"'))
        if not nc_value.get('ds'):
            log.error('No DS Records Present for Namecoin-based Domain Name: %s' % name)
            raise NoDSRecordException()

        if not nc_value.get('ns'):
            log.error('No NS Records Present for Namecoin-based Domain Name: %s' % name)
            raise NoNameserverException()

        sld = '%s.%s.' % (domains[1], domains[0])
        ds_record = ' '.join([str(x) for x in nc_value['ds'][0][0:3]])

        # Handle both Hex and Base64 encoding (Base64 is the preferred encoding) per:
        # https://wiki.namecoin.info/index.php?title=Domain_Name_Specification
        if re.match('^[0-9a-fA-F]*$', nc_value['ds'][0][3]):
            ds_record += ' %s' % nc_value['ds'][0][3]
        else:
            ds_record += ' %s' % base64.b64decode(nc_value['ds'][0][3]).encode('hex').upper()

        if qtype in ['DS']:
            return ds_record
        ds_ta = '%s IN DS %s' % (sld, ds_record)

        ns_ctx = ub_ctx()
        ns_ctx.resolvconf(self.resolv_conf)

        if not os.path.isfile(self.dnssec_root_key):
            log.error("Trust anchor missing or inaccessible")
            raise Exception("Trust anchor is missing or inaccessible: %s" % self.dnssec_root_key)
        else:
            ns_ctx.add_ta_file(self.dnssec_root_key)

        last_error = None
        for ns in nc_value.get('ns', []):

            lookup_value = None

            status, result = ns_ctx.resolve(ns, rdatatype.from_text('A'), rdataclass.from_text('IN'))

            # NOTE: We do not require secure DNS resolution here because the Blockchain-stored DS records work as the trust anchor
            # and the signed RRSIG DNS results from the final DNS+DNSSEC lookup will be able to complete the chain of trust
            if status == 0 and result and result.data and not result.bogus:
                tmp_config_file = self._build_temp_unbound_config(sld, result.data.as_address_list()[0])
            else:
                last_error = InvalidNameserverException()
                log.warn('No or Invalid Resolution Result for Nameserver: %s' % ns)
                continue

            ctx = ub_ctx()
            ctx.config(tmp_config_file)
            ctx.add_ta(str(ds_ta))

            _qtype = None
            try:
                _qtype = rdatatype.from_text(qtype)
            except Exception as e:
                log.error('Unable to get RDATAType for Given Query Type [%s]: %s' % (qtype, str(e)))
                raise ValueError('Unable to get RDATAType for Query Type %s' % qtype)

            status, result = ctx.resolve(name, _qtype, rdataclass.from_text('IN'))
            if status != 0:
                log.info("DNS Resolution Failed: %s [%s]" % (name, _qtype))
            elif status == 0:

                if not result.secure:
                    log.info("DNS Resolution Returned Insecure Result: %s [%s]" % (name, qtype))
                    last_error = InsecureResultException()

                elif result.bogus:
                    log.info("DNS Resolution Returned Bogus Result: %s [%s]" % (name, qtype))
                    last_error = BogusResultException()

                elif not result.havedata:
                    log.info("DNS Resolution Returned Empty Result: %s [%s]" % (name, qtype))
                    last_error = EmptyResultException()

                else:
                    # Get appropriate data by query type
                    # TODO: Add SOA Record Processing
                    if qtype in ['A','AAAA']:
                        lookup_value = result.data.as_address_list()
                    elif qtype in ['CNAME','TXT','PTR','SPF','CAA','NAPTR']:
                        lookup_value = result.data.as_domain_list()
                    elif qtype in ['MX']:
                        lookup_value = result.data.as_mx_list()
                    elif qtype in ['NS']:
                        lookup_value = nc_value.get('ns', [])
                    elif qtype in ['TLSA']:
                        lookup_value = [NamecoinResolver.tlsa_convert(result.rawdata[0])]
                    else:
                        last_error = NotImplementedError('Unsupported DNS Query Type: %s' % qtype)

            self._delete_temp_unbound_config(tmp_config_file)

            if lookup_value:
                return lookup_value[0]

            if last_error and isinstance(last_error, NotImplementedError):
                raise last_error

        log.error('DNS Resolution Failed: %s [%s]' % (name, qtype))
        if last_error:
            raise last_error

        return None