def _daemon_command(self, cmd: str):
        if not os.path.exists(self.md_path):
            log_err("monerod not found in path \'%s\'" % self.md_path)
            return

        log_msg("Spawning daemon; executing command \'%s\'" % cmd)

        # build proc args
        args = [
            '--rpc-bind-ip', self.md_daemon_addr,
            '--rpc-bind-port', str(self.md_daemon_port),
        ]
        if self.md_daemon_auth:
            args.extend(['--rpc-login', self.md_daemon_auth])
        args.append(cmd)

        try:
            process = Popen([self.md_path, *args],
                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                            universal_newlines=True, bufsize=1)
            output, err = process.communicate(timeout=10)
            if not output:
                log_err("No output from monerod")
            return output
        except Exception as ex:
            log_err('Could not spawn \'%s %s\': %s' % (
                self.md_path, ' '.join(args), str(ex)
            ))
        finally:
            # cleanup
            process.kill()
Example #2
0
    def cache_read(path):
        """
        Reads nodes from the nodes cache file.
        :return: List of RpcNode objects
        """
        log_msg('Reading \'%s\'' % path)

        try:
            f = open(path, 'r')
            blob = json.loads(f.read())
            f.close()
        except Exception as ex:
            log_err('Reading \'%s\' failed' % path)
            return RpcNodeList()

        if not isinstance(blob, list):
            return RpcNodeList()

        nodes = RpcNodeList()
        for node in blob:
            dt = dateutil_parse(node.pop('dt'))
            if (datetime.now() - dt).total_seconds(
            ) < CONFIG['scan_interval'] and 'address' in node:
                nodes.append(RpcNode(**node))

        log_msg('Loaded %d nodes from \'%s\'' % (len(nodes), path))
        return nodes
    def __init__(self, dns_provider: DnsProvider, md_address: str = '127.0.0.1', md_port: int = 18081,
                 md_auth: str = 'not:used', md_path: str = 'monerod.exe',
                 md_height_discovery_method: str = 'xmrchain', ban_list_path: str = ''):
        self.dns_provider = dns_provider

        self.md_path = md_path
        self.md_daemon_addr = md_address
        self.md_daemon_port = md_port
        self.md_daemon_auth = md_auth
        if md_height_discovery_method not in ['xmrchain', 'monerod', 'compare', 'moneroblocks']:
            log_err('bad height_discovery_method option', fatal=True)
        self.md_height_discovery_method = md_height_discovery_method

        # default Monero RPC port
        self._m_rpc_port = 18089
        self._blockchain_height = None

        self.last_mass_scan_time = 0

        if not os.path.isfile(PATH_CACHE):
            log_msg("Auto creating \'%s\'" % PATH_CACHE)
            f = open(PATH_CACHE, 'a')
            f.write('[]')
            f.close()
        
        if ban_list_path != '':
            ban_list = parse_ban_list(ban_list_path)
            log_msg('Load %d nodes from %s'%(len(ban_list), ban_list_path))
            self.ban_list = ban_list
        else:
            self.ban_list = []

        self.monerod_check()
Example #4
0
    def cache_read(path):
        """
        Reads nodes from the nodes cache file.
        :return: List of RpcNode objects
        """
        log_msg('Reading \'%s\'' % path)

        try:
            f = open(path, 'r')
            blob = json.loads(f.read())
            f.close()
        except Exception as ex:
            log_err('Reading \'%s\' failed' % path)
            return RpcNodeList()

        if not isinstance(blob, list):
            return RpcNodeList()

        nodes = RpcNodeList()
        for node in blob:
            dt = dateutil_parse(node['dt'])
            if 'address' in node:
                nodes.append(RpcNode(**node))

        log_msg('Loaded %d nodes from \'%s\'' % (len(nodes), path))
        return nodes
    def scan(self, nodes: RpcNodeList, remove_invalid=False):
        """
        Start threads checking known nodes to see if they're alive.
        :param nodes:
        :param remove_invalid: only return valid nodes when set to True
        :return: valid nodes
        """
        if len(nodes) == 0:
            return nodes

        now = datetime.now()
        log_msg(
            'Scanning %d node(s) on port %d. This can take several minutes. Let it run.'
            % (len(nodes), self._m_rpc_port))

        pool = Pool(processes=CONFIG['concurrent_scans'])
        nodes = RpcNodeList.from_list(
            pool.map(partial(RpcNode.is_valid, self._blockchain_height),
                     nodes))
        pool.close()
        pool.join()

        log_msg('Scanning %d node(s) done after %d seconds, found %d valid' %
                (len(nodes), (datetime.now() - now).total_seconds(),
                 len(nodes.valid(valid=True))))

        if remove_invalid:
            nodes = nodes.valid(valid=True)

        return nodes
Example #6
0
    def __init__(self, **kwargs):
        super(Cloudflare, self).__init__(**kwargs)

        self.headers = {
            'Content-Type': 'application/json',
            'X-Auth-Email': kwargs['api_email'],
            'X-Auth-Key': kwargs['api_key'],
            'User-Agent': random_user_agent()
        }
        self.api_base = 'https://api.cloudflare.com/client/v4/zones'
        self.zone_id = None

        # zone_id is required and will be detected via Cloudflare API
        if not self.zone_id:
            log_msg('Determining zone_id; looking for \'%s\'' %
                    self.domain_name)
            result = make_json_request(url=self.api_base, headers=self.headers)
            try:
                zones = result.get('result')
                self.zone_id = next(
                    zone.get('id') for zone in zones
                    if zone.get('name') == self.domain_name)
            except StopIteration:
                log_err(
                    'could not determine zone_id. Is your Cloudflare domain correct?',
                    fatal=True)
            log_msg('Cloudflare zone_id \'%s\' matched to \'%s\'' %
                    (self.zone_id, self.domain_name))
Example #7
0
    def get_records(self):
        max_retries = 5
        nodes = RpcNodeList()
        log_msg('Fetching existing record(s) (%s.%s)' %
                (self.subdomain_name, self.domain_name))

        retries = 0
        while (True):
            try:
                result = make_json_request(
                    '%s/%s/dns_records/?type=A&name=%s.%s' %
                    (self.api_base, self.zone_id, self.subdomain_name,
                     self.domain_name),
                    headers=self.headers)
                records = result.get('result')

                # filter on A records / subdomain
                for record in records:
                    if record.get('type') != 'A' or record.get(
                            'name') != self.fulldomain_name:
                        continue

                    node = RpcNode(address=record.get('content'),
                                   uid=record.get('id'))
                    nodes.append(node)
                    log_msg('> A %s %s' %
                            (record.get('name'), record.get('content')))
                return nodes

            except Exception as ex:
                log_err("Cloudflare record fetching failed: %s" % (str(ex)))
                retries += 1
                time.sleep(1)
                if retries > max_retries:
                    return None
    def __init__(self,
                 dns_provider: DnsProvider,
                 md_address: str = '127.0.0.1',
                 md_port: int = 18081,
                 md_auth: str = 'not:used',
                 md_path: str = 'monerod.exe',
                 md_height_discovery_method: str = 'xmrchain'):
        self.dns_provider = dns_provider

        self.md_path = md_path
        self.md_daemon_addr = md_address
        self.md_daemon_port = md_port
        self.md_daemon_auth = md_auth
        if md_height_discovery_method not in [
                'xmrchain', 'monerod', 'compare', 'moneroblocks'
        ]:
            log_err('bad height_discovery_method option', fatal=True)
        self.md_height_discovery_method = md_height_discovery_method

        # default Monero RPC port
        self._m_rpc_port = 18089
        self._blockchain_height = None

        if not os.path.isfile(PATH_CACHE):
            log_msg("Auto creating \'%s\'" % PATH_CACHE)
            f = open(PATH_CACHE, 'a')
            f.write('[]')
            f.close()

        self.monerod_check()
    def delete_record(self, node: RpcNode):
        # Delete DNS Record
        log_msg('Cloudflare record deletion: %s' % node.address)

        try:
            url = '%s/%s/dns_records/%s' % (self.api_base, self.zone_id, node.uid)
            data = make_json_request(url=url, method='DELETE', headers=self.headers)
            assert data.get('success') is True
            return data.get('result')
        except Exception as ex:
            log_err("Record (%s) deletion failed: %s" % (node.address, str(ex)))
    def add_record(self, node: RpcNode):
        log_msg('Record insertion: %s' % node.address)

        try:
            url = '%s/%s/dns_records' % (self.api_base, self.zone_id)
            make_json_request(url=url, method='POST', headers=self.headers, json={
                'name': self.subdomain_name,
                'content': node.address,
                'type': 'A',
                'ttl': 120
            })
        except Exception as ex:
            log_err("Cloudflare record (%s) insertion failed: %s" % (node.address, str(ex)))
    def monerod_get_height(self, method='compare'):
        """
        Gets the current top block on the chain
        :param method: 'monerod' will use only monerod to fetch the height.
        'xmrchain' will only use xmrchain. 'both' will query both and compare.
        :return:
        """
        data = {}
        xmrchain_height = 0
        max_retries = 5

        if method == ['compare', 'monerod']:
            output = self._daemon_command(cmd="print_height")
            if isinstance(output, str) and output.startswith('Error') or not output:
                log_err("monerod output: %s" % output)
            elif isinstance(output, str):
                data['md_height'] = int(re.sub('[^0-9]', '', output.splitlines()[1]))
                log_msg('monerod height is %d' % data['md_height'])
                if method == 'monerod':
                    return data['md_height']

        if method in ['compare', 'moneroblocks']:
            retries = 0
            while True:
                if retries > max_retries:
                    break
                try:
                    blob = make_json_request('https://moneroblocks.info/api/get_stats/', timeout=5, verify=True)
                    data['moneroblocks'] = blob.get('height')
                    break
                except Exception as ex:
                    log_msg('Fetching moneroblocks JSON has failed. Retrying.')
                    retries += 1
                    time.sleep(1)

        if method in ['compare', 'xmrchain']:
            retries = 0
            while True:
                if retries > max_retries:
                    break
                try:
                    blob = make_json_request('https://xmrchain.net/api/networkinfo', timeout=5, verify=True)
                    assert blob.get('status') == 'success'
                    data['xmrchain_height'] = blob.get('data', {}).get('height')
                    assert isinstance(data['xmrchain_height'], int)
                    log_msg('xmrchain height is %d' % data['xmrchain_height'])
                    if method == 'xmrchain':
                        return data['xmrchain_height']
                    break
                except Exception as ex:
                    log_msg('Fetching xmrchain JSON has failed. Retrying.')
                    retries += 1
                    time.sleep(1)
                    continue

        if data:
            return max(data.values())
        log_err('Unable to obtain blockheight.')
    def get_records(self):
        nodes = RpcNodeList()
        log_msg('Fetching existing record(s) (%s.%s)' % (self.subdomain_name, self.domain_name))

        result = make_json_request('%s/%s/dns_records/?type=A&name=%s.%s' % (
            self.api_base, self.zone_id,
            self.subdomain_name, self.domain_name), headers=self.headers)
        records = result.get('result')

        # filter on A records / subdomain
        for record in records:
            if record.get('type') != 'A' or record.get('name') != self.fulldomain_name:
                continue

            node = RpcNode(address=record.get('content'), uid=record.get('id'))
            nodes.append(node)
            log_msg('> A %s %s' % (record.get('name'), record.get('content')))
        return nodes
    def monerod_get_peers(self):
        """Gets the last known peers from monerod"""
        nodes = RpcNodeList()
        output = self._daemon_command("print_pl")
        if not output:
            return nodes

        regex = r"(gray|white)\s+(\w+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5})"
        matches = re.finditer(regex, output)

        for i, match in enumerate(matches):
            if match.group(1) != 'white':
                continue

            address = match.group(3)
            nodes.append(RpcNode(address=address))

        log_msg('Got peers from RPC: %d node(s)' % len(nodes))
        return nodes
Example #14
0
    def cache_write(self):
        """Writes a cache file of valid nodes"""
        now = datetime.now()
        data = []

        for node in self.nodes:
            if node.valid:
                data.append({
                    'address': node.address,
                    'port': node.port,
                    'dt': now.strftime('%Y-%m-%d %H:%M:%S')
                })
        try:
            f = open(PATH_CACHE, 'w')
            f.write(json.dumps(data, indent=4))
            f.close()
        except Exception as ex:
            log_err('Writing \'%s\' failed' % PATH_CACHE)
            raise
        log_msg('Written \'%s\' with %d nodes' % (PATH_CACHE, len(data)))
Example #15
0
def cli(monerod_path, monerod_address, monerod_port, monerod_auth,
        blockheight_discovery, dns_provider, domain, subdomain, api_key,
        api_email, max_records, loop_interval, concurrent_scans, scan_interval,
        ban_list, from_config):
    from moneriote import CONFIG
    from moneriote.moneriote import Moneriote
    from moneriote.utils import log_err, log_msg, banner, parse_ini

    banner()

    if from_config:
        md, dns, ban = parse_ini(from_config)
        monerod_path = md['path']
        monerod_address = md['address']
        monerod_auth = md['auth']
        monerod_port = md['port']
        api_email = dns['api_email']
        api_key = dns['api_key']
        domain = dns['domain_name']
        subdomain = dns['subdomain_name']
        max_records = int(dns['max_records'])
        dns_provider = dns['provider']
        ban_list = ban['ban_list_path']

    if not api_email:
        log_err('Parameter api_email is required', fatal=True)

    if not api_key:
        log_err('Parameter api_key is required', fatal=True)

    if not domain:
        log_err('Parametre domain is required', fatal=True)

    CONFIG['concurrent_scans'] = concurrent_scans
    CONFIG['scan_interval'] = scan_interval

    if dns_provider == 'cloudflare':
        from moneriote.dns.cloudflare import Cloudflare
        dns_provider = Cloudflare(domain_name=domain,
                                  subdomain_name=subdomain,
                                  api_key=api_key,
                                  api_email=api_email,
                                  max_records=max_records)
    elif dns_provider == 'transip':
        from moneriote.dns.transip import TransIP
        dns_provider = TransIP(api_email=api_email,
                               api_key=api_key,
                               subdomain_name=subdomain,
                               domain_name=domain,
                               max_records=max_records)
    else:
        log_err("Unknown DNS provider \'%s\'" % dns_provider, fatal=True)

    mon = Moneriote(dns_provider=dns_provider,
                    md_path=monerod_path,
                    md_address=monerod_address,
                    md_port=monerod_port,
                    md_auth=monerod_auth,
                    md_height_discovery_method=blockheight_discovery,
                    ban_list_path=ban_list)

    while True:
        mon.main()
        log_msg('Sleeping for %d seconds' % loop_interval)
        sleep(loop_interval)