- removed onionrdaemontools (split into many files, mostly into communicatorutils)
- removed included secrets.py since 3.6 is required now anyways
This commit is contained in:
parent
3d93a37d0c
commit
64944f6f7c
@ -22,10 +22,12 @@
|
||||
import sys, os, time
|
||||
import core, config, logger, onionr
|
||||
import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block
|
||||
from communicatorutils import onionrdaemontools, servicecreator, onionrcommunicatortimers
|
||||
from communicatorutils import servicecreator, onionrcommunicatortimers
|
||||
from communicatorutils import downloadblocks, lookupblocks, lookupadders
|
||||
from communicatorutils import servicecreator, connectnewpeers, uploadblocks
|
||||
from communicatorutils import daemonqueuehandler, announcenode
|
||||
from communicatorutils import daemonqueuehandler, announcenode, deniableinserts
|
||||
from communicatorutils import cooldownpeer, housekeeping, netcheck
|
||||
from etc import humanreadabletime
|
||||
import onionrservices, onionr, onionrproofs
|
||||
|
||||
OnionrCommunicatorTimers = onionrcommunicatortimers.OnionrCommunicatorTimers
|
||||
@ -88,10 +90,6 @@ class OnionrCommunicatorDaemon:
|
||||
# Loads in and starts the enabled plugins
|
||||
plugins.reload()
|
||||
|
||||
# daemon tools are misc daemon functions, e.g. announce to online peers
|
||||
# intended only for use by OnionrCommunicatorDaemon
|
||||
self.daemonTools = onionrdaemontools.DaemonTools(self)
|
||||
|
||||
# time app started running for info/statistics purposes
|
||||
self.startTime = self._core._utils.getEpoch()
|
||||
|
||||
@ -111,13 +109,13 @@ class OnionrCommunicatorDaemon:
|
||||
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58)
|
||||
|
||||
# Timer to cleanup old blocks
|
||||
blockCleanupTimer = OnionrCommunicatorTimers(self, self.daemonTools.cleanOldBlocks, 65)
|
||||
blockCleanupTimer = OnionrCommunicatorTimers(self, housekeeping.clean_old_blocks, 65, myArgs=[self])
|
||||
|
||||
# Timer to discover new peers
|
||||
OnionrCommunicatorTimers(self, self.lookupAdders, 60, requiresPeer=True)
|
||||
|
||||
# Timer for adjusting which peers we actively communicate to at any given time, to avoid over-using peers
|
||||
OnionrCommunicatorTimers(self, self.daemonTools.cooldownPeer, 30, requiresPeer=True)
|
||||
OnionrCommunicatorTimers(self, cooldownpeer.cooldown_peer, 30, myArgs=[self], requiresPeer=True)
|
||||
|
||||
# Timer to read the upload queue and upload the entries to peers
|
||||
OnionrCommunicatorTimers(self, self.uploadBlock, 10, requiresPeer=True, maxThreads=1)
|
||||
@ -133,17 +131,17 @@ class OnionrCommunicatorDaemon:
|
||||
self.services = onionrservices.OnionrServices(self._core)
|
||||
self.active_services = []
|
||||
self.service_greenlets = []
|
||||
OnionrCommunicatorTimers(self, servicecreator.service_creator, 5, maxThreads=50, myArgs=(self,))
|
||||
OnionrCommunicatorTimers(self, servicecreator.service_creator, 5, maxThreads=50, myArgs=[self])
|
||||
else:
|
||||
self.services = None
|
||||
|
||||
# This timer creates deniable blocks, in an attempt to further obfuscate block insertion metadata
|
||||
if config.get('general.insert_deniable_blocks', True):
|
||||
deniableBlockTimer = OnionrCommunicatorTimers(self, self.daemonTools.insertDeniableBlock, 180, requiresPeer=True, maxThreads=1)
|
||||
deniableBlockTimer = OnionrCommunicatorTimers(self, deniableinserts.insert_deniable_block, 180, myArgs=[self], requiresPeer=True, maxThreads=1)
|
||||
deniableBlockTimer.count = (deniableBlockTimer.frequency - 175)
|
||||
|
||||
# Timer to check for connectivity, through Tor to various high-profile onion services
|
||||
netCheckTimer = OnionrCommunicatorTimers(self, self.daemonTools.netCheck, 600)
|
||||
netCheckTimer = OnionrCommunicatorTimers(self, netcheck.net_check, 600, myArgs=[self])
|
||||
|
||||
# Announce the public API server transport address to other nodes if security level allows
|
||||
if config.get('general.security_level', 1) == 0:
|
||||
@ -157,7 +155,7 @@ class OnionrCommunicatorDaemon:
|
||||
cleanupTimer = OnionrCommunicatorTimers(self, self.peerCleanup, 300, requiresPeer=True)
|
||||
|
||||
# Timer to cleanup dead ephemeral forward secrecy keys
|
||||
forwardSecrecyTimer = OnionrCommunicatorTimers(self, self.daemonTools.cleanKeys, 15, maxThreads=1)
|
||||
forwardSecrecyTimer = OnionrCommunicatorTimers(self, housekeeping.clean_keys, 15, myArgs=[self], maxThreads=1)
|
||||
|
||||
# Adjust initial timer triggers
|
||||
peerPoolTimer.count = (peerPoolTimer.frequency - 1)
|
||||
@ -255,7 +253,7 @@ class OnionrCommunicatorDaemon:
|
||||
break
|
||||
else:
|
||||
if len(self.onlinePeers) == 0:
|
||||
logger.debug('Couldn\'t connect to any peers.' + (' Last node seen %s ago.' % self.daemonTools.humanReadableTime(time.time() - self.lastNodeSeen) if not self.lastNodeSeen is None else ''))
|
||||
logger.debug('Couldn\'t connect to any peers.' + (' Last node seen %s ago.' % humanreadabletime.human_readable_time(time.time() - self.lastNodeSeen) if not self.lastNodeSeen is None else ''))
|
||||
else:
|
||||
self.lastNodeSeen = time.time()
|
||||
self.decrementThreadCount('getOnlinePeers')
|
||||
@ -346,7 +344,7 @@ class OnionrCommunicatorDaemon:
|
||||
|
||||
def heartbeat(self):
|
||||
'''Show a heartbeat debug message'''
|
||||
logger.debug('Heartbeat. Node running for %s.' % self.daemonTools.humanReadableTime(self.getUptime()))
|
||||
logger.debug('Heartbeat. Node running for %s.' % humanreadabletime.human_readable_time(self.getUptime()))
|
||||
self.decrementThreadCount('heartbeat')
|
||||
|
||||
def daemonCommands(self):
|
||||
@ -379,10 +377,17 @@ class OnionrCommunicatorDaemon:
|
||||
self.decrementThreadCount('detectAPICrash')
|
||||
|
||||
def runCheck(self):
|
||||
if self.daemonTools.runCheck():
|
||||
if run_file_exists(self):
|
||||
logger.debug('Status check; looks good.')
|
||||
|
||||
self.decrementThreadCount('runCheck')
|
||||
|
||||
def startCommunicator(onionrInst, proxyPort):
|
||||
OnionrCommunicatorDaemon(onionrInst, proxyPort)
|
||||
|
||||
def run_file_exists(daemon):
|
||||
if os.path.isfile(daemon._core.dataDir + '.runcheck'):
|
||||
os.remove(daemon._core.dataDir + '.runcheck')
|
||||
return True
|
||||
|
||||
return False
|
@ -8,15 +8,23 @@ announcenode.py: Uses a communicator instance to announce our transport address
|
||||
|
||||
connectnewpeers.py: takes a communicator instance and has it connect to as many peers as needed, and/or to a new specified peer.
|
||||
|
||||
cooldownpeer.py: randomly selects a connected peer in a communicator and disconnects them for the purpose of security and network balancing.
|
||||
|
||||
daemonqueuehandler.py: checks for new commands in the daemon queue and processes them accordingly.
|
||||
|
||||
deniableinserts.py: insert fake blocks with the communicator for plausible deniability
|
||||
|
||||
downloadblocks.py: iterates a communicator instance's block download queue and attempts to download the blocks from online peers
|
||||
|
||||
housekeeping.py: cleans old blocks and forward secrecy keys
|
||||
|
||||
lookupadders.py: ask connected peers to share their list of peer transport addresses
|
||||
|
||||
onionrcommunicataortimers.py: create a timer for a function to be launched on an interval. Control how many possible instances of a timer may be running a function at once and control if the timer should be ran in a thread or not.
|
||||
lookupblocks.py: lookup new blocks from connected peers from the communicator
|
||||
|
||||
onionrdaemontools.py: contains the DaemonTools class which has a lot of etc functions useful for the communicator. Deprecated.
|
||||
netcheck.py: check if the node is online based on communicator status and onion server ping results
|
||||
|
||||
onionrcommunicataortimers.py: create a timer for a function to be launched on an interval. Control how many possible instances of a timer may be running a function at once and control if the timer should be ran in a thread or not.
|
||||
|
||||
proxypicker.py: returns a string name for the appropriate proxy to be used with a particular peer transport address.
|
||||
|
||||
|
@ -80,5 +80,5 @@ def announce_node(daemon):
|
||||
retData = True
|
||||
daemon._core.setAddressInfo(peer, 'introduced', 1)
|
||||
daemon._core.setAddressInfo(peer, 'powValue', data['random'])
|
||||
daemon.decrementThreadCount('announceNode')
|
||||
daemon.decrementThreadCount('announce_node')
|
||||
return retData
|
51
onionr/communicatorutils/cooldownpeer.py
Normal file
51
onionr/communicatorutils/cooldownpeer.py
Normal file
@ -0,0 +1,51 @@
|
||||
'''
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Select a random online peer in a communicator instance and have them "cool down"
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
def cooldown_peer(comm_inst):
|
||||
'''Randomly add an online peer to cooldown, so we can connect a new one'''
|
||||
onlinePeerAmount = len(comm_inst.onlinePeers)
|
||||
minTime = 300
|
||||
cooldownTime = 600
|
||||
toCool = ''
|
||||
tempConnectTimes = dict(comm_inst.connectTimes)
|
||||
|
||||
# Remove peers from cooldown that have been there long enough
|
||||
tempCooldown = dict(comm_inst.cooldownPeer)
|
||||
for peer in tempCooldown:
|
||||
if (comm_inst._core._utils.getEpoch() - tempCooldown[peer]) >= cooldownTime:
|
||||
del comm_inst.cooldownPeer[peer]
|
||||
|
||||
# Cool down a peer, if we have max connections alive for long enough
|
||||
if onlinePeerAmount >= comm_inst._core.config.get('peers.max_connect', 10, save = True):
|
||||
finding = True
|
||||
|
||||
while finding:
|
||||
try:
|
||||
toCool = min(tempConnectTimes, key=tempConnectTimes.get)
|
||||
if (comm_inst._core._utils.getEpoch() - tempConnectTimes[toCool]) < minTime:
|
||||
del tempConnectTimes[toCool]
|
||||
else:
|
||||
finding = False
|
||||
except ValueError:
|
||||
break
|
||||
else:
|
||||
comm_inst.removeOnlinePeer(toCool)
|
||||
comm_inst.cooldownPeer[toCool] = comm_inst._core._utils.getEpoch()
|
||||
|
||||
comm_inst.decrementThreadCount('cooldown_peer')
|
31
onionr/communicatorutils/deniableinserts.py
Normal file
31
onionr/communicatorutils/deniableinserts.py
Normal file
@ -0,0 +1,31 @@
|
||||
'''
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Use the communicator to insert fake mail messages
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import secrets
|
||||
from etc import onionrvalues
|
||||
def insert_deniable_block(comm_inst):
|
||||
'''Insert a fake block in order to make it more difficult to track real blocks'''
|
||||
fakePeer = ''
|
||||
chance = 10
|
||||
if secrets.randbelow(chance) == (chance - 1):
|
||||
# This assumes on the libsodium primitives to have key-privacy
|
||||
fakePeer = onionrvalues.DENIABLE_PEER_ADDRESS
|
||||
data = secrets.token_hex(secrets.randbelow(1024) + 1)
|
||||
comm_inst._core.insertBlock(data, header='pm', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'})
|
||||
comm_inst.decrementThreadCount('insert_deniable_block')
|
59
onionr/communicatorutils/housekeeping.py
Normal file
59
onionr/communicatorutils/housekeeping.py
Normal file
@ -0,0 +1,59 @@
|
||||
'''
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Cleanup old Onionr blocks and forward secrecy keys using the communicator. Ran from a timer usually
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import sqlite3
|
||||
import logger
|
||||
from onionrusers import onionrusers
|
||||
def clean_old_blocks(comm_inst):
|
||||
'''Delete old blocks if our disk allocation is full/near full, and also expired blocks'''
|
||||
|
||||
# Delete expired blocks
|
||||
for bHash in comm_inst._core.getExpiredBlocks():
|
||||
comm_inst._core._blacklist.addToDB(bHash)
|
||||
comm_inst._core.removeBlock(bHash)
|
||||
logger.info('Deleted block: %s' % (bHash,))
|
||||
|
||||
while comm_inst._core._utils.storageCounter.isFull():
|
||||
oldest = comm_inst._core.getBlockList()[0]
|
||||
comm_inst._core._blacklist.addToDB(oldest)
|
||||
comm_inst._core.removeBlock(oldest)
|
||||
logger.info('Deleted block: %s' % (oldest,))
|
||||
|
||||
comm_inst.decrementThreadCount('clean_old_blocks')
|
||||
|
||||
def clean_keys(comm_inst):
|
||||
'''Delete expired forward secrecy keys'''
|
||||
conn = sqlite3.connect(comm_inst._core.peerDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
time = comm_inst._core._utils.getEpoch()
|
||||
deleteKeys = []
|
||||
|
||||
for entry in c.execute("SELECT * FROM forwardKeys WHERE expire <= ?", (time,)):
|
||||
logger.debug('Forward key: %s' % entry[1])
|
||||
deleteKeys.append(entry[1])
|
||||
|
||||
for key in deleteKeys:
|
||||
logger.debug('Deleting forward key %s' % key)
|
||||
c.execute("DELETE from forwardKeys where forwardKey = ?", (key,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
onionrusers.deleteExpiredKeys(comm_inst._core)
|
||||
|
||||
comm_inst.decrementThreadCount('clean_keys')
|
32
onionr/communicatorutils/netcheck.py
Normal file
32
onionr/communicatorutils/netcheck.py
Normal file
@ -0,0 +1,32 @@
|
||||
'''
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Determine if our node is able to use Tor based on the status of a communicator instance
|
||||
and the result of pinging onion http servers
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import logger
|
||||
from utils import netutils
|
||||
def net_check(comm_inst):
|
||||
'''Check if we are connected to the internet or not when we can't connect to any peers'''
|
||||
if len(comm_inst.onlinePeers) == 0:
|
||||
if not netutils.checkNetwork(comm_inst._core._utils, torPort=comm_inst.proxyPort):
|
||||
if not comm_inst.shutdown:
|
||||
logger.warn('Network check failed, are you connected to the Internet, and is Tor working?')
|
||||
comm_inst.isOnline = False
|
||||
else:
|
||||
comm_inst.isOnline = True
|
||||
comm_inst.decrementThreadCount('net_check')
|
@ -1,158 +0,0 @@
|
||||
'''
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Contains the DaemonTools class
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
# MODULE DEPRECATED
|
||||
|
||||
import onionrexceptions, onionrpeers, onionrproofs, logger
|
||||
import base64, sqlite3, os
|
||||
from dependencies import secrets
|
||||
from utils import netutils
|
||||
from onionrusers import onionrusers
|
||||
from etc import onionrvalues
|
||||
ov = onionrvalues.OnionrValues()
|
||||
|
||||
class DaemonTools:
|
||||
'''
|
||||
Class intended for use by Onionr Communicator
|
||||
'''
|
||||
def __init__(self, daemon):
|
||||
self.daemon = daemon
|
||||
self.announceProgress = {}
|
||||
self.announceCache = {}
|
||||
|
||||
def netCheck(self):
|
||||
'''Check if we are connected to the internet or not when we can't connect to any peers'''
|
||||
if len(self.daemon.onlinePeers) == 0:
|
||||
if not netutils.checkNetwork(self.daemon._core._utils, torPort=self.daemon.proxyPort):
|
||||
if not self.daemon.shutdown:
|
||||
logger.warn('Network check failed, are you connected to the Internet, and is Tor working?')
|
||||
self.daemon.isOnline = False
|
||||
else:
|
||||
self.daemon.isOnline = True
|
||||
self.daemon.decrementThreadCount('netCheck')
|
||||
|
||||
def cleanOldBlocks(self):
|
||||
'''Delete old blocks if our disk allocation is full/near full, and also expired blocks'''
|
||||
|
||||
# Delete expired blocks
|
||||
for bHash in self.daemon._core.getExpiredBlocks():
|
||||
self.daemon._core._blacklist.addToDB(bHash)
|
||||
self.daemon._core.removeBlock(bHash)
|
||||
logger.info('Deleted block: %s' % (bHash,))
|
||||
|
||||
while self.daemon._core._utils.storageCounter.isFull():
|
||||
oldest = self.daemon._core.getBlockList()[0]
|
||||
self.daemon._core._blacklist.addToDB(oldest)
|
||||
self.daemon._core.removeBlock(oldest)
|
||||
logger.info('Deleted block: %s' % (oldest,))
|
||||
|
||||
self.daemon.decrementThreadCount('cleanOldBlocks')
|
||||
|
||||
def cleanKeys(self):
|
||||
'''Delete expired forward secrecy keys'''
|
||||
conn = sqlite3.connect(self.daemon._core.peerDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
time = self.daemon._core._utils.getEpoch()
|
||||
deleteKeys = []
|
||||
|
||||
for entry in c.execute("SELECT * FROM forwardKeys WHERE expire <= ?", (time,)):
|
||||
logger.debug('Forward key: %s' % entry[1])
|
||||
deleteKeys.append(entry[1])
|
||||
|
||||
for key in deleteKeys:
|
||||
logger.debug('Deleting forward key %s' % key)
|
||||
c.execute("DELETE from forwardKeys where forwardKey = ?", (key,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
onionrusers.deleteExpiredKeys(self.daemon._core)
|
||||
|
||||
self.daemon.decrementThreadCount('cleanKeys')
|
||||
|
||||
def cooldownPeer(self):
|
||||
'''Randomly add an online peer to cooldown, so we can connect a new one'''
|
||||
onlinePeerAmount = len(self.daemon.onlinePeers)
|
||||
minTime = 300
|
||||
cooldownTime = 600
|
||||
toCool = ''
|
||||
tempConnectTimes = dict(self.daemon.connectTimes)
|
||||
|
||||
# Remove peers from cooldown that have been there long enough
|
||||
tempCooldown = dict(self.daemon.cooldownPeer)
|
||||
for peer in tempCooldown:
|
||||
if (self.daemon._core._utils.getEpoch() - tempCooldown[peer]) >= cooldownTime:
|
||||
del self.daemon.cooldownPeer[peer]
|
||||
|
||||
# Cool down a peer, if we have max connections alive for long enough
|
||||
if onlinePeerAmount >= self.daemon._core.config.get('peers.max_connect', 10, save = True):
|
||||
finding = True
|
||||
|
||||
while finding:
|
||||
try:
|
||||
toCool = min(tempConnectTimes, key=tempConnectTimes.get)
|
||||
if (self.daemon._core._utils.getEpoch() - tempConnectTimes[toCool]) < minTime:
|
||||
del tempConnectTimes[toCool]
|
||||
else:
|
||||
finding = False
|
||||
except ValueError:
|
||||
break
|
||||
else:
|
||||
self.daemon.removeOnlinePeer(toCool)
|
||||
self.daemon.cooldownPeer[toCool] = self.daemon._core._utils.getEpoch()
|
||||
|
||||
self.daemon.decrementThreadCount('cooldownPeer')
|
||||
|
||||
def runCheck(self):
|
||||
if os.path.isfile(self.daemon._core.dataDir + '.runcheck'):
|
||||
os.remove(self.daemon._core.dataDir + '.runcheck')
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def humanReadableTime(self, seconds):
|
||||
build = ''
|
||||
|
||||
units = {
|
||||
'year' : 31557600,
|
||||
'month' : (31557600 / 12),
|
||||
'day' : 86400,
|
||||
'hour' : 3600,
|
||||
'minute' : 60,
|
||||
'second' : 1
|
||||
}
|
||||
|
||||
for unit in units:
|
||||
amnt_unit = int(seconds / units[unit])
|
||||
if amnt_unit >= 1:
|
||||
seconds -= amnt_unit * units[unit]
|
||||
build += '%s %s' % (amnt_unit, unit) + ('s' if amnt_unit != 1 else '') + ' '
|
||||
|
||||
return build.strip()
|
||||
|
||||
def insertDeniableBlock(self):
|
||||
'''Insert a fake block in order to make it more difficult to track real blocks'''
|
||||
fakePeer = ''
|
||||
chance = 10
|
||||
if secrets.randbelow(chance) == (chance - 1):
|
||||
# This assumes on the libsodium primitives to have key-privacy
|
||||
fakePeer = 'OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA===='
|
||||
data = secrets.token_hex(secrets.randbelow(1024) + 1)
|
||||
self.daemon._core.insertBlock(data, header='pm', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'})
|
||||
self.daemon.decrementThreadCount('insertDeniableBlock')
|
@ -808,7 +808,7 @@ class Core:
|
||||
self._utils.processBlockMetadata(retData)
|
||||
|
||||
if retData != False:
|
||||
if plaintextPeer == 'OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA====':
|
||||
if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS:
|
||||
events.event('insertdeniable', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer)}, onionr = self.onionrInst, threaded = True)
|
||||
else:
|
||||
events.event('insertblock', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer)}, onionr = self.onionrInst, threaded = True)
|
||||
|
@ -1,331 +0,0 @@
|
||||
"""Generate cryptographically strong pseudo-random numbers suitable for
|
||||
managing secrets such as account authentication, tokens, and similar.
|
||||
|
||||
See PEP 506 for more information.
|
||||
https://www.python.org/dev/peps/pep-0506/
|
||||
|
||||
|
||||
A. HISTORY OF THE SOFTWARE
|
||||
==========================
|
||||
|
||||
Python was created in the early 1990s by Guido van Rossum at Stichting
|
||||
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
|
||||
as a successor of a language called ABC. Guido remains Python's
|
||||
principal author, although it includes many contributions from others.
|
||||
|
||||
In 1995, Guido continued his work on Python at the Corporation for
|
||||
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
|
||||
in Reston, Virginia where he released several versions of the
|
||||
software.
|
||||
|
||||
In May 2000, Guido and the Python core development team moved to
|
||||
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
||||
year, the PythonLabs team moved to Digital Creations, which became
|
||||
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
||||
https://www.python.org/psf/) was formed, a non-profit organization
|
||||
created specifically to own Python-related Intellectual Property.
|
||||
Zope Corporation was a sponsoring member of the PSF.
|
||||
|
||||
All Python releases are Open Source (see http://www.opensource.org for
|
||||
the Open Source Definition). Historically, most, but not all, Python
|
||||
releases have also been GPL-compatible; the table below summarizes
|
||||
the various releases.
|
||||
|
||||
Release Derived Year Owner GPL-
|
||||
from compatible? (1)
|
||||
|
||||
0.9.0 thru 1.2 1991-1995 CWI yes
|
||||
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
||||
1.6 1.5.2 2000 CNRI no
|
||||
2.0 1.6 2000 BeOpen.com no
|
||||
1.6.1 1.6 2001 CNRI yes (2)
|
||||
2.1 2.0+1.6.1 2001 PSF no
|
||||
2.0.1 2.0+1.6.1 2001 PSF yes
|
||||
2.1.1 2.1+2.0.1 2001 PSF yes
|
||||
2.1.2 2.1.1 2002 PSF yes
|
||||
2.1.3 2.1.2 2002 PSF yes
|
||||
2.2 and above 2.1.1 2001-now PSF yes
|
||||
|
||||
Footnotes:
|
||||
|
||||
(1) GPL-compatible doesn't mean that we're distributing Python under
|
||||
the GPL. All Python licenses, unlike the GPL, let you distribute
|
||||
a modified version without making your changes open source. The
|
||||
GPL-compatible licenses make it possible to combine Python with
|
||||
other software that is released under the GPL; the others don't.
|
||||
|
||||
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
||||
because its license has a choice of law clause. According to
|
||||
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
||||
is "not incompatible" with the GPL.
|
||||
|
||||
Thanks to the many outside volunteers who have worked under Guido's
|
||||
direction to make these releases possible.
|
||||
|
||||
|
||||
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
||||
===============================================================
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use Python alone or in any derivative version,
|
||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All
|
||||
Rights Reserved" are retained in Python alone or in any derivative version
|
||||
prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
||||
-------------------------------------------
|
||||
|
||||
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
||||
|
||||
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
||||
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
||||
Individual or Organization ("Licensee") accessing and otherwise using
|
||||
this software in source or binary form and its associated
|
||||
documentation ("the Software").
|
||||
|
||||
2. Subject to the terms and conditions of this BeOpen Python License
|
||||
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
||||
royalty-free, world-wide license to reproduce, analyze, test, perform
|
||||
and/or display publicly, prepare derivative works, distribute, and
|
||||
otherwise use the Software alone or in any derivative version,
|
||||
provided, however, that the BeOpen Python License is retained in the
|
||||
Software, alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
||||
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
||||
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
||||
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
||||
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
5. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
6. This License Agreement shall be governed by and interpreted in all
|
||||
respects by the law of the State of California, excluding conflict of
|
||||
law provisions. Nothing in this License Agreement shall be deemed to
|
||||
create any relationship of agency, partnership, or joint venture
|
||||
between BeOpen and Licensee. This License Agreement does not grant
|
||||
permission to use BeOpen trademarks or trade names in a trademark
|
||||
sense to endorse or promote products or services of Licensee, or any
|
||||
third party. As an exception, the "BeOpen Python" logos available at
|
||||
http://www.pythonlabs.com/logos.html may be used according to the
|
||||
permissions granted on that web page.
|
||||
|
||||
7. By copying, installing or otherwise using the software, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
||||
---------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Corporation for National
|
||||
Research Initiatives, having an office at 1895 Preston White Drive,
|
||||
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
||||
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
||||
source or binary form and its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, CNRI
|
||||
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||
license to reproduce, analyze, test, perform and/or display publicly,
|
||||
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
||||
alone or in any derivative version, provided, however, that CNRI's
|
||||
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
||||
1995-2001 Corporation for National Research Initiatives; All Rights
|
||||
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
||||
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
||||
Agreement, Licensee may substitute the following text (omitting the
|
||||
quotes): "Python 1.6.1 is made available subject to the terms and
|
||||
conditions in CNRI's License Agreement. This Agreement together with
|
||||
Python 1.6.1 may be located on the Internet using the following
|
||||
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
||||
Agreement may also be obtained from a proxy server on the Internet
|
||||
using the following URL: http://hdl.handle.net/1895.22/1013".
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python 1.6.1.
|
||||
|
||||
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
||||
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. This License Agreement shall be governed by the federal
|
||||
intellectual property law of the United States, including without
|
||||
limitation the federal copyright law, and, to the extent such
|
||||
U.S. federal law does not apply, by the law of the Commonwealth of
|
||||
Virginia, excluding Virginia's conflict of law provisions.
|
||||
Notwithstanding the foregoing, with regard to derivative works based
|
||||
on Python 1.6.1 that incorporate non-separable material that was
|
||||
previously distributed under the GNU General Public License (GPL), the
|
||||
law of the Commonwealth of Virginia shall govern this License
|
||||
Agreement only as to issues arising under or with respect to
|
||||
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
||||
License Agreement shall be deemed to create any relationship of
|
||||
agency, partnership, or joint venture between CNRI and Licensee. This
|
||||
License Agreement does not grant permission to use CNRI trademarks or
|
||||
trade name in a trademark sense to endorse or promote products or
|
||||
services of Licensee, or any third party.
|
||||
|
||||
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
||||
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
||||
bound by the terms and conditions of this License Agreement.
|
||||
|
||||
ACCEPT
|
||||
|
||||
|
||||
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
||||
--------------------------------------------------
|
||||
|
||||
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
||||
The Netherlands. All rights reserved.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appear in all copies and that
|
||||
both that copyright notice and this permission notice appear in
|
||||
supporting documentation, and that the name of Stichting Mathematisch
|
||||
Centrum or CWI not be used in advertising or publicity pertaining to
|
||||
distribution of the software without specific, written prior
|
||||
permission.
|
||||
|
||||
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
||||
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
__all__ = ['choice', 'randbelow', 'randbits', 'SystemRandom',
|
||||
'token_bytes', 'token_hex', 'token_urlsafe',
|
||||
'compare_digest',
|
||||
]
|
||||
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import os
|
||||
|
||||
from hmac import compare_digest
|
||||
from random import SystemRandom
|
||||
|
||||
_sysrand = SystemRandom()
|
||||
|
||||
randbits = _sysrand.getrandbits
|
||||
choice = _sysrand.choice
|
||||
|
||||
def randbelow(exclusive_upper_bound):
|
||||
"""Return a random int in the range [0, n)."""
|
||||
if exclusive_upper_bound <= 0:
|
||||
raise ValueError("Upper bound must be positive.")
|
||||
return _sysrand._randbelow(exclusive_upper_bound)
|
||||
|
||||
DEFAULT_ENTROPY = 32 # number of bytes to return by default
|
||||
|
||||
def token_bytes(nbytes=None):
|
||||
"""Return a random byte string containing *nbytes* bytes.
|
||||
|
||||
If *nbytes* is ``None`` or not supplied, a reasonable
|
||||
default is used.
|
||||
|
||||
>>> token_bytes(16) #doctest:+SKIP
|
||||
b'\\xebr\\x17D*t\\xae\\xd4\\xe3S\\xb6\\xe2\\xebP1\\x8b'
|
||||
|
||||
"""
|
||||
if nbytes is None:
|
||||
nbytes = DEFAULT_ENTROPY
|
||||
return os.urandom(nbytes)
|
||||
|
||||
def token_hex(nbytes=None):
|
||||
"""Return a random text string, in hexadecimal.
|
||||
|
||||
The string has *nbytes* random bytes, each byte converted to two
|
||||
hex digits. If *nbytes* is ``None`` or not supplied, a reasonable
|
||||
default is used.
|
||||
|
||||
>>> token_hex(16) #doctest:+SKIP
|
||||
'f9bf78b9a18ce6d46a0cd2b0b86df9da'
|
||||
|
||||
"""
|
||||
return binascii.hexlify(token_bytes(nbytes)).decode('ascii')
|
||||
|
||||
def token_urlsafe(nbytes=None):
|
||||
"""Return a random URL-safe text string, in Base64 encoding.
|
||||
|
||||
The string has *nbytes* random bytes. If *nbytes* is ``None``
|
||||
or not supplied, a reasonable default is used.
|
||||
|
||||
>>> token_urlsafe(16) #doctest:+SKIP
|
||||
'Drmhze6EPcv0fN_81Bj-nA'
|
||||
|
||||
"""
|
||||
tok = token_bytes(nbytes)
|
||||
return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii')
|
||||
|
38
onionr/etc/humanreadabletime.py
Normal file
38
onionr/etc/humanreadabletime.py
Normal file
@ -0,0 +1,38 @@
|
||||
'''
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
human_readable_time takes integer seconds and returns a human readable string
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
def human_readable_time(seconds):
|
||||
build = ''
|
||||
|
||||
units = {
|
||||
'year' : 31557600,
|
||||
'month' : (31557600 / 12),
|
||||
'day' : 86400,
|
||||
'hour' : 3600,
|
||||
'minute' : 60,
|
||||
'second' : 1
|
||||
}
|
||||
|
||||
for unit in units:
|
||||
amnt_unit = int(seconds / units[unit])
|
||||
if amnt_unit >= 1:
|
||||
seconds -= amnt_unit * units[unit]
|
||||
build += '%s %s' % (amnt_unit, unit) + ('s' if amnt_unit != 1 else '') + ' '
|
||||
|
||||
return build.strip()
|
@ -17,7 +17,7 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
DENIABLE_PEER_ADDRESS = "OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA===="
|
||||
class OnionrValues:
|
||||
def __init__(self):
|
||||
self.passwordLength = 20
|
||||
|
@ -17,11 +17,10 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import subprocess, os, random, sys, time, signal, base64, socket
|
||||
import subprocess, os, sys, time, signal, base64, socket
|
||||
from shutil import which
|
||||
import logger, config
|
||||
from onionrblockapi import Block
|
||||
from dependencies import secrets
|
||||
|
||||
def getOpenPort():
|
||||
# taken from (but modified) https://stackoverflow.com/a/2838309 by https://stackoverflow.com/users/133374/albert ccy-by-sa-3 https://creativecommons.org/licenses/by-sa/3.0/
|
||||
|
@ -17,15 +17,10 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import os, binascii, base64, hashlib, time, sys, hmac
|
||||
import os, binascii, base64, hashlib, time, sys, hmac, secrets
|
||||
import nacl.signing, nacl.encoding, nacl.public, nacl.hash, nacl.pwhash, nacl.utils, nacl.secret
|
||||
import logger, onionrproofs
|
||||
import onionrexceptions, keymanager, core
|
||||
# secrets module was added into standard lib in 3.6+
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] < 6:
|
||||
from dependencies import secrets
|
||||
elif sys.version_info[0] == 3 and sys.version_info[1] >= 6:
|
||||
import secrets
|
||||
import config
|
||||
|
||||
class OnionrCrypto:
|
||||
|
Loading…
Reference in New Issue
Block a user