diff --git a/onionr/communicator.py b/onionr/communicator.py index d59b76da..57917b9a 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -27,6 +27,7 @@ from communicatorutils import downloadblocks, lookupblocks, lookupadders from communicatorutils import servicecreator, connectnewpeers, uploadblocks from communicatorutils import daemonqueuehandler, announcenode, deniableinserts from communicatorutils import cooldownpeer, housekeeping, netcheck +from onionrutils import localcommand from etc import humanreadabletime import onionrservices, onionr, onionrproofs @@ -184,7 +185,7 @@ class OnionrCommunicatorDaemon: else: for server in self.service_greenlets: server.stop() - self._core._utils.localCommand('shutdown') # shutdown the api + localcommand.local_command(self._core, 'shutdown') # shutdown the api time.sleep(0.5) def lookupAdders(self): @@ -364,9 +365,9 @@ class OnionrCommunicatorDaemon: def detectAPICrash(self): '''exit if the api server crashes/stops''' - if self._core._utils.localCommand('ping', silent=False) not in ('pong', 'pong!'): + if localcommand.local_command(self._core, 'ping', silent=False) not in ('pong', 'pong!'): for i in range(300): - if self._core._utils.localCommand('ping') in ('pong', 'pong!') or self.shutdown: + if localcommand.local_command(self._core, 'ping') in ('pong', 'pong!') or self.shutdown: break # break for loop time.sleep(1) else: diff --git a/onionr/communicatorutils/connectnewpeers.py b/onionr/communicatorutils/connectnewpeers.py index 96e4ecac..25929c24 100755 --- a/onionr/communicatorutils/connectnewpeers.py +++ b/onionr/communicatorutils/connectnewpeers.py @@ -20,6 +20,7 @@ import time, sys import onionrexceptions, logger, onionrpeers from utils import networkmerger +from onionrutils import stringvalidators # 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 @@ -30,7 +31,7 @@ def connect_new_peer_to_communicator(comm_inst, peer='', useBootstrap=False): retData = False tried = comm_inst.offlinePeers if peer != '': - if comm_inst._core._utils.validateID(peer): + if stringvalidators.validate_transport(peer): peerList = [peer] else: raise onionrexceptions.InvalidAddress('Will not attempt connection test to invalid address') diff --git a/onionr/communicatorutils/daemonqueuehandler.py b/onionr/communicatorutils/daemonqueuehandler.py index ddbb0783..c11cc929 100755 --- a/onionr/communicatorutils/daemonqueuehandler.py +++ b/onionr/communicatorutils/daemonqueuehandler.py @@ -19,6 +19,7 @@ ''' import logger import onionrevents as events +from onionrutils import localcommand def handle_daemon_commands(comm_inst): cmd = comm_inst._core.daemonQueue() response = '' @@ -39,7 +40,7 @@ def handle_daemon_commands(comm_inst): if response == '': response = 'none' elif cmd[0] == 'localCommand': - response = comm_inst._core._utils.localCommand(cmd[1]) + response = localcommand.local_command(comm_inst._core, cmd[1]) elif cmd[0] == 'pex': for i in comm_inst.timers: if i.timerFunction.__name__ == 'lookupAdders': @@ -49,7 +50,7 @@ def handle_daemon_commands(comm_inst): if cmd[0] not in ('', None): if response != '': - comm_inst._core._utils.localCommand('queueResponseAdd/' + cmd[4], post=True, postData={'data': response}) + localcommand.local_command(comm_inst._core, 'queueResponseAdd/' + cmd[4], post=True, postData={'data': response}) response = '' comm_inst.decrementThreadCount('daemonCommands') \ No newline at end of file diff --git a/onionr/communicatorutils/lookupadders.py b/onionr/communicatorutils/lookupadders.py index 4f861682..fc4527dc 100755 --- a/onionr/communicatorutils/lookupadders.py +++ b/onionr/communicatorutils/lookupadders.py @@ -18,6 +18,7 @@ along with this program. If not, see . ''' import logger +from onionrutils import stringvalidators def lookup_new_peer_transports_with_communicator(comm_inst): logger.info('Looking up new addresses...') @@ -39,7 +40,7 @@ def lookup_new_peer_transports_with_communicator(comm_inst): invalid = [] for x in newPeers: x = x.strip() - if not comm_inst._core._utils.validateID(x) or x in comm_inst.newPeers or x == comm_inst._core.hsAddress: + if not stringvalidators.validate_transport(x) or x in comm_inst.newPeers or x == comm_inst._core.hsAddress: # avoid adding if its our address invalid.append(x) for x in invalid: diff --git a/onionr/communicatorutils/netcheck.py b/onionr/communicatorutils/netcheck.py index c5e95906..688feeea 100755 --- a/onionr/communicatorutils/netcheck.py +++ b/onionr/communicatorutils/netcheck.py @@ -20,17 +20,19 @@ ''' import logger from utils import netutils +from onionrutils import localcommand def net_check(comm_inst): '''Check if we are connected to the internet or not when we can't connect to any peers''' rec = False # for detecting if we have received incoming connections recently + c = comm_inst._core if len(comm_inst.onlinePeers) == 0: try: - if (comm_inst._core._utils.getEpoch() - int(comm_inst._core._utils.localCommand('/lastconnect'))) <= 60: + if (c._utils.getEpoch() - int(localcommand.local_command(c, '/lastconnect'))) <= 60: comm_inst.isOnline = True rec = True except ValueError: pass - if not rec and not netutils.checkNetwork(comm_inst._core._utils, torPort=comm_inst.proxyPort): + if not rec and not netutils.checkNetwork(c._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 diff --git a/onionr/communicatorutils/servicecreator.py b/onionr/communicatorutils/servicecreator.py index c1f2c7e2..94fc51c8 100755 --- a/onionr/communicatorutils/servicecreator.py +++ b/onionr/communicatorutils/servicecreator.py @@ -18,6 +18,8 @@ along with this program. If not, see . ''' import communicator, onionrblockapi +from onionrutils import stringvalidators + def service_creator(daemon): assert isinstance(daemon, communicator.OnionrCommunicatorDaemon) core = daemon._core @@ -30,7 +32,7 @@ def service_creator(daemon): if not b in daemon.active_services: bl = onionrblockapi.Block(b, core=core, decrypt=True) bs = utils.bytesToStr(bl.bcontent) + '.onion' - if utils.validatePubKey(bl.signer) and utils.validateID(bs): + if utils.validatePubKey(bl.signer) and stringvalidators.validate_transport(bs): signer = utils.bytesToStr(bl.signer) daemon.active_services.append(b) daemon.active_services.append(signer) diff --git a/onionr/communicatorutils/uploadblocks.py b/onionr/communicatorutils/uploadblocks.py index 69d0353d..497e07d5 100755 --- a/onionr/communicatorutils/uploadblocks.py +++ b/onionr/communicatorutils/uploadblocks.py @@ -20,6 +20,7 @@ import logger from communicatorutils import proxypicker import onionrblockapi as block +from onionrutils import localcommand def upload_blocks_from_communicator(comm_inst): # when inserting a block, we try to upload it to a few peers to add some deniability @@ -42,7 +43,7 @@ def upload_blocks_from_communicator(comm_inst): proxyType = proxypicker.pick_proxy(peer) logger.info("Uploading block to " + peer) if not comm_inst._core._utils.doPostRequest(url, data=data, proxyType=proxyType) == False: - comm_inst._core._utils.localCommand('waitforshare/' + bl, post=True) + localcommand.local_command(comm_inst._core, 'waitforshare/' + bl, post=True) finishedUploads.append(bl) for x in finishedUploads: try: diff --git a/onionr/core.py b/onionr/core.py index ebc35e51..53bf0b4c 100755 --- a/onionr/core.py +++ b/onionr/core.py @@ -28,6 +28,7 @@ from onionrusers import onionrusers from onionrstorage import removeblock, setdata import dbcreator, onionrstorage, serializeddata, subprocesspow from etc import onionrvalues, powchoice +from onionrutils import localcommand class Core: def __init__(self, torPort=0): @@ -433,8 +434,8 @@ class Core: retData = False else: # Tell the api server through localCommand to wait for the daemon to upload this block to make statistical analysis more difficult - if self._utils.localCommand('/ping', maxWait=10) == 'pong!': - self._utils.localCommand('/waitforshare/' + retData, post=True, maxWait=5) + if localcommand.local_command(self, '/ping', maxWait=10) == 'pong!': + localcommand.local_command(self, '/waitforshare/' + retData, post=True, maxWait=5) self.daemonQueueAdd('uploadBlock', retData) self.addToBlockDB(retData, selfInsert=True, dataSaved=True) self._utils.processBlockMetadata(retData) @@ -450,7 +451,7 @@ class Core: ''' Introduces our node into the network by telling X many nodes our HS address ''' - if self._utils.localCommand('/ping', maxWait=10) == 'pong!': + if localcommand.local_command(self, '/ping', maxWait=10) == 'pong!': self.daemonQueueAdd('announceNode') logger.info('Introduction command will be processed.', terminal=True) else: diff --git a/onionr/coredb/daemonqueue/__init__.py b/onionr/coredb/daemonqueue/__init__.py index 8bd21cfd..a2f7a483 100644 --- a/onionr/coredb/daemonqueue/__init__.py +++ b/onionr/coredb/daemonqueue/__init__.py @@ -1,5 +1,7 @@ import sqlite3, os import onionrevents as events +from onionrutils import localcommand + def daemon_queue(core_inst): ''' Gives commands to the communication proccess/daemon by reading an sqlite3 database @@ -55,7 +57,7 @@ def daemon_queue_get_response(core_inst, responseID=''): Get a response sent by communicator to the API, by requesting to the API ''' assert len(responseID) > 0 - resp = core_inst._utils.localCommand('queueResponse/' + responseID) + resp = localcommand.local_command(core_inst, 'queueResponse/' + responseID) return resp def clear_daemon_queue(core_inst): diff --git a/onionr/coredb/keydb/addkeys.py b/onionr/coredb/keydb/addkeys.py index ac219b1a..716581cd 100644 --- a/onionr/coredb/keydb/addkeys.py +++ b/onionr/coredb/keydb/addkeys.py @@ -1,5 +1,7 @@ import sqlite3 -import onionrevents as events, config +import onionrevents as events +from onionrutils import stringvalidators + def add_peer(core_inst, peerID, name=''): ''' Adds a public key to the key database (misleading function name) @@ -40,8 +42,8 @@ def add_address(core_inst, address): if type(address) is None or len(address) == 0: return False - if core_inst._utils.validateID(address): - if address == config.get('i2p.ownAddr', None) or address == core_inst.hsAddress: + if stringvalidators.validate_transport(address): + if address == core_inst.config.get('i2p.ownAddr', None) or address == core_inst.hsAddress: return False conn = sqlite3.connect(core_inst.addressDB, timeout=30) c = conn.cursor() diff --git a/onionr/coredb/keydb/removekeys.py b/onionr/coredb/keydb/removekeys.py index 10f44a1b..ce4ba5fc 100644 --- a/onionr/coredb/keydb/removekeys.py +++ b/onionr/coredb/keydb/removekeys.py @@ -1,11 +1,13 @@ import sqlite3 import onionrevents as events +from onionrutils import stringvalidators + def remove_address(core_inst, address): ''' Remove an address from the address database ''' - if core_inst._utils.validateID(address): + if stringvalidators.validate_transport(address): conn = sqlite3.connect(core_inst.addressDB, timeout=30) c = conn.cursor() t = (address,) diff --git a/onionr/httpapi/miscpublicapi/announce.py b/onionr/httpapi/miscpublicapi/announce.py index 8a25b635..f17c8d83 100755 --- a/onionr/httpapi/miscpublicapi/announce.py +++ b/onionr/httpapi/miscpublicapi/announce.py @@ -21,6 +21,8 @@ import base64 from flask import Response import logger from etc import onionrvalues +from onionrutils import stringvalidators + def handle_announce(clientAPI, request): ''' accept announcement posts, validating POW @@ -52,7 +54,7 @@ def handle_announce(clientAPI, request): pass if powHash.startswith('0' * onionrvalues.OnionrValues().announce_pow): newNode = clientAPI._core._utils.bytesToStr(newNode) - if clientAPI._core._utils.validateID(newNode) and not newNode in clientAPI._core.onionrInst.communicatorInst.newPeers: + if stringvalidators.validate_transport(newNode) and not newNode in clientAPI._core.onionrInst.communicatorInst.newPeers: clientAPI._core.onionrInst.communicatorInst.newPeers.append(newNode) resp = 'Success' else: diff --git a/onionr/onionrcommands/__init__.py b/onionr/onionrcommands/__init__.py index 3a50d59d..59c9d6bc 100755 --- a/onionr/onionrcommands/__init__.py +++ b/onionr/onionrcommands/__init__.py @@ -22,6 +22,7 @@ import webbrowser, sys import logger from . import pubkeymanager, onionrstatistics, daemonlaunch, filecommands, plugincommands, keyadders from . import banblocks, exportblocks, openwebinterface, resettor +from onionrutils import importnewblocks def show_help(o_inst, command): @@ -110,8 +111,8 @@ def get_commands(onionr_inst): 'listconn': onionr_inst.listConn, 'list-conn': onionr_inst.listConn, - 'import-blocks': onionr_inst.onionrUtils.importNewBlocks, - 'importblocks': onionr_inst.onionrUtils.importNewBlocks, + 'import-blocks': importnewblocks.import_new_blocks, + 'importblocks': importnewblocks.import_new_blocks, 'introduce': onionr_inst.onionrCore.introduceNode, 'pex': onionr_inst.doPEX, diff --git a/onionr/onionrcommands/daemonlaunch.py b/onionr/onionrcommands/daemonlaunch.py index 1aaf7230..28202c10 100755 --- a/onionr/onionrcommands/daemonlaunch.py +++ b/onionr/onionrcommands/daemonlaunch.py @@ -23,9 +23,10 @@ from threading import Thread import onionr, api, logger, communicator import onionrevents as events from netcontroller import NetController +from onionrutils import localcommand def _proper_shutdown(o_inst): - o_inst.onionrUtils.localCommand('shutdown') + localcommand.local_command(o_inst.onionrCore, 'shutdown') sys.exit(1) def daemon(o_inst): @@ -63,7 +64,7 @@ def daemon(o_inst): net = NetController(o_inst.onionrCore.config.get('client.public.port', 59497), apiServerIP=apiHost) logger.info('Tor is starting...', terminal=True) if not net.startTor(): - o_inst.onionrUtils.localCommand('shutdown') + localcommand.local_command(o_inst.onionrCore, 'shutdown') sys.exit(1) if len(net.myID) > 0 and o_inst.onionrCore.config.get('general.security_level', 1) == 0: logger.debug('Started .onion service: %s' % (logger.colors.underline + net.myID)) @@ -103,10 +104,10 @@ def daemon(o_inst): signal.signal(signal.SIGINT, _ignore_sigint) o_inst.onionrCore.daemonQueueAdd('shutdown') - o_inst.onionrUtils.localCommand('shutdown') + localcommand.local_command(o_inst.onionrCore, 'shutdown') net.killTor() - time.sleep(8) # Time to allow threads to finish, if not any "daemon" threads will be slaughtered http://docs.python.org/library/threading.html#threading.Thread.daemon + time.sleep(5) # Time to allow threads to finish, if not any "daemon" threads will be slaughtered http://docs.python.org/library/threading.html#threading.Thread.daemon o_inst.deleteRunFiles() return diff --git a/onionr/onionrcommands/onionrstatistics.py b/onionr/onionrcommands/onionrstatistics.py index a28fb7db..2f50c6e7 100755 --- a/onionr/onionrcommands/onionrstatistics.py +++ b/onionr/onionrcommands/onionrstatistics.py @@ -21,6 +21,7 @@ import os, uuid, time import logger, onionrutils from onionrblockapi import Block import onionr +from onionrutils import checkcommunicator def show_stats(o_inst): try: @@ -29,7 +30,7 @@ def show_stats(o_inst): signedBlocks = len(Block.getBlocks(signed = True)) messages = { # info about local client - 'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if o_inst.onionrUtils.isCommunicatorRunning(timeout = 9) else logger.colors.fg.red + 'Offline'), + 'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if checkcommunicator.is_communicator_running(o_inst.onionrCore, timeout = 9) else logger.colors.fg.red + 'Offline'), # file and folder size stats 'div1' : True, # this creates a solid line across the screen, a div diff --git a/onionr/onionrcommands/openwebinterface.py b/onionr/onionrcommands/openwebinterface.py index 8ce71744..42c80b57 100755 --- a/onionr/onionrcommands/openwebinterface.py +++ b/onionr/onionrcommands/openwebinterface.py @@ -19,9 +19,10 @@ ''' import webbrowser import logger +from onionrutils import getclientapiserver def open_home(o_inst): try: - url = o_inst.onionrUtils.getClientAPIServer() + url = getclientapiserver.get_client_API_server(o_inst.onionrCore) except FileNotFoundError: logger.error('Onionr seems to not be running (could not get api host)', terminal=True) else: diff --git a/onionr/onionrcommands/resettor.py b/onionr/onionrcommands/resettor.py index cd7c51d2..e327bccc 100755 --- a/onionr/onionrcommands/resettor.py +++ b/onionr/onionrcommands/resettor.py @@ -19,11 +19,13 @@ ''' import os, shutil import logger, core +from onionrutils import localcommand + def reset_tor(): c = core.Core() tor_dir = c.dataDir + 'tordata' if os.path.exists(tor_dir): - if c._utils.localCommand('/ping') == 'pong!': + if localcommand.local_command(c, '/ping') == 'pong!': logger.warn('Cannot delete Tor data while Onionr is running', terminal=True) else: shutil.rmtree(tor_dir) \ No newline at end of file diff --git a/onionr/onionrpluginapi.py b/onionr/onionrpluginapi.py index 78c8a008..24ba4f12 100755 --- a/onionr/onionrpluginapi.py +++ b/onionr/onionrpluginapi.py @@ -19,6 +19,7 @@ ''' import onionrplugins, core as onionrcore, logger +from onionrutils import localcommand class DaemonAPI: def __init__(self, pluginapi): @@ -40,7 +41,7 @@ class DaemonAPI: return def local_command(self, command): - return self.pluginapi.get_utils().localCommand(self, command) + return localcommand.local_command(self.pluginapi.get_core(), command) def queue_pop(self): return self.get_core().daemonQueue() diff --git a/onionr/onionrservices/__init__.py b/onionr/onionrservices/__init__.py index 2792f7e3..1c23e5fb 100755 --- a/onionr/onionrservices/__init__.py +++ b/onionr/onionrservices/__init__.py @@ -21,6 +21,7 @@ import time import stem import core from . import connectionserver, bootstrapservice +from onionrutils import stringvalidators class OnionrServices: ''' @@ -39,7 +40,7 @@ class OnionrServices: When a client wants to connect, contact their bootstrap address and tell them our ephemeral address for our service by creating a new ConnectionServer instance ''' - assert self._core._utils.validateID(address) + assert stringvalidators.validate_transport(address) BOOTSTRAP_TRIES = 10 # How many times to attempt contacting the bootstrap server TRY_WAIT = 3 # Seconds to wait before trying bootstrap again # HTTP is fine because .onion/i2p is encrypted/authenticated diff --git a/onionr/onionrservices/bootstrapservice.py b/onionr/onionrservices/bootstrapservice.py index 077b4657..e06300e6 100755 --- a/onionr/onionrservices/bootstrapservice.py +++ b/onionr/onionrservices/bootstrapservice.py @@ -24,6 +24,7 @@ from flask import Flask, Response import core from netcontroller import getOpenPort from . import httpheaders +from onionrutils import stringvalidators def bootstrap_client_service(peer, core_inst=None, bootstrap_timeout=300): ''' @@ -61,7 +62,7 @@ def bootstrap_client_service(peer, core_inst=None, bootstrap_timeout=300): @bootstrap_app.route('/bs/
', methods=['POST']) def get_bootstrap(address): - if core_inst._utils.validateID(address + '.onion'): + if stringvalidators.validate_transport(address + '.onion'): # Set the bootstrap address then close the server bootstrap_address = address + '.onion' core_inst.keyStore.put(bs_id, bootstrap_address) diff --git a/onionr/onionrutils/__init__.py b/onionr/onionrutils/__init__.py index a7efbae7..23907a00 100755 --- a/onionr/onionrutils/__init__.py +++ b/onionr/onionrutils/__init__.py @@ -29,6 +29,7 @@ import storagecounter from etc import pgpwords, onionrvalues from onionrusers import onionrusers from . import localcommand, blockmetadata, validatemetadata, basicrequests +from . import stringvalidators config.reload() class OnionrUtils: @@ -50,23 +51,6 @@ class OnionrUtils: ''' epoch = self.getEpoch() return epoch - (epoch % roundS) - - def getClientAPIServer(self): - retData = '' - try: - with open(self._core.privateApiHostFile, 'r') as host: - hostname = host.read() - except FileNotFoundError: - raise FileNotFoundError - else: - retData += '%s:%s' % (hostname, config.get('client.client.port')) - return retData - - def localCommand(self, command, data='', silent = True, post=False, postData = {}, maxWait=20): - ''' - Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers. - ''' - return localcommand.local_command(self, command, data, silent, post, postData, maxWait) def getHumanReadableID(self, pub=''): '''gets a human readable ID from a public key''' @@ -132,19 +116,7 @@ class OnionrUtils: ''' Validate if a string is a valid hash hex digest (does not compare, just checks length and charset) ''' - retVal = True - if data == False or data == True: - return False - data = data.strip() - if len(data) != length: - retVal = False - else: - try: - int(data, 16) - except ValueError: - retVal = False - - return retVal + return stringvalidators.validate_hash(self, data, length) def validateMetadata(self, metadata, blockData): '''Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string''' @@ -154,129 +126,7 @@ class OnionrUtils: ''' Validate if a string is a valid base32 encoded Ed25519 key ''' - if type(key) is type(None): - return False - # Accept keys that have no = padding - key = unpaddedbase32.repad(self.strToBytes(key)) - - retVal = False - try: - nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) - except nacl.exceptions.ValueError: - pass - except base64.binascii.Error as err: - pass - else: - retVal = True - return retVal - - @staticmethod - def validateID(id): - ''' - Validate if an address is a valid tor or i2p hidden service - ''' - try: - idLength = len(id) - retVal = True - idNoDomain = '' - peerType = '' - # i2p b32 addresses are 60 characters long (including .b32.i2p) - if idLength == 60: - peerType = 'i2p' - if not id.endswith('.b32.i2p'): - retVal = False - else: - idNoDomain = id.split('.b32.i2p')[0] - # Onion v2's are 22 (including .onion), v3's are 62 with .onion - elif idLength == 22 or idLength == 62: - peerType = 'onion' - if not id.endswith('.onion'): - retVal = False - else: - idNoDomain = id.split('.onion')[0] - else: - retVal = False - if retVal: - if peerType == 'i2p': - try: - id.split('.b32.i2p')[2] - except: - pass - else: - retVal = False - elif peerType == 'onion': - try: - id.split('.onion')[2] - except: - pass - else: - retVal = False - if not idNoDomain.isalnum(): - retVal = False - - # Validate address is valid base32 (when capitalized and minus extension); v2/v3 onions and .b32.i2p use base32 - for x in idNoDomain.upper(): - if x not in string.ascii_uppercase and x not in '234567': - retVal = False - - return retVal - except: - return False - - @staticmethod - def isIntegerString(data): - '''Check if a string is a valid base10 integer (also returns true if already an int)''' - try: - int(data) - except (ValueError, TypeError) as e: - return False - else: - return True - - def isCommunicatorRunning(self, timeout = 5, interval = 0.1): - try: - runcheck_file = self._core.dataDir + '.runcheck' - - if not os.path.isfile(runcheck_file): - open(runcheck_file, 'w+').close() - - # self._core.daemonQueueAdd('runCheck') # deprecated - starttime = time.time() - - while True: - time.sleep(interval) - - if not os.path.isfile(runcheck_file): - return True - elif time.time() - starttime >= timeout: - return False - except: - return False - - def importNewBlocks(self, scanDir=''): - ''' - This function is intended to scan for new blocks ON THE DISK and import them - ''' - blockList = self._core.getBlockList() - exist = False - if scanDir == '': - scanDir = self._core.blockDataLocation - if not scanDir.endswith('/'): - scanDir += '/' - for block in glob.glob(scanDir + "*.dat"): - if block.replace(scanDir, '').replace('.dat', '') not in blockList: - exist = True - logger.info('Found new block on dist %s' % block) - with open(block, 'rb') as newBlock: - block = block.replace(scanDir, '').replace('.dat', '') - if self._core._crypto.sha3Hash(newBlock.read()) == block.replace('.dat', ''): - self._core.addToBlockDB(block.replace('.dat', ''), dataSaved=True) - logger.info('Imported block %s.' % block) - self._core._utils.processBlockMetadata(block) - else: - logger.warn('Failed to verify hash for %s' % block) - if not exist: - logger.info('No blocks found to import') + return stringvalidators.validate_pub_key(self, key) def getEpoch(self): '''returns epoch''' diff --git a/onionr/onionrutils/checkcommunicator.py b/onionr/onionrutils/checkcommunicator.py new file mode 100644 index 00000000..07f8dc1a --- /dev/null +++ b/onionr/onionrutils/checkcommunicator.py @@ -0,0 +1,19 @@ +import time, os +def is_communicator_running(core_inst, timeout = 5, interval = 0.1): + try: + runcheck_file = core_inst.dataDir + '.runcheck' + + if not os.path.isfile(runcheck_file): + open(runcheck_file, 'w+').close() + + starttime = time.time() + + while True: + time.sleep(interval) + + if not os.path.isfile(runcheck_file): + return True + elif time.time() - starttime >= timeout: + return False + except: + return False \ No newline at end of file diff --git a/onionr/onionrutils/getclientapiserver.py b/onionr/onionrutils/getclientapiserver.py new file mode 100644 index 00000000..23020f66 --- /dev/null +++ b/onionr/onionrutils/getclientapiserver.py @@ -0,0 +1,10 @@ +def get_client_API_server(core_inst): + retData = '' + try: + with open(core_inst.privateApiHostFile, 'r') as host: + hostname = host.read() + except FileNotFoundError: + raise FileNotFoundError + else: + retData += '%s:%s' % (hostname, core_inst.config.get('client.client.port')) + return retData \ No newline at end of file diff --git a/onionr/onionrutils/importnewblocks.py b/onionr/onionrutils/importnewblocks.py new file mode 100644 index 00000000..a2c1e518 --- /dev/null +++ b/onionr/onionrutils/importnewblocks.py @@ -0,0 +1,28 @@ +import glob +import logger, core +def import_new_blocks(core_inst=None, scanDir=''): + ''' + This function is intended to scan for new blocks ON THE DISK and import them + ''' + if core_inst is None: + core_inst = core.Core() + blockList = core_inst.getBlockList() + exist = False + if scanDir == '': + scanDir = core_inst.blockDataLocation + if not scanDir.endswith('/'): + scanDir += '/' + for block in glob.glob(scanDir + "*.dat"): + if block.replace(scanDir, '').replace('.dat', '') not in blockList: + exist = True + logger.info('Found new block on dist %s' % block) + with open(block, 'rb') as newBlock: + block = block.replace(scanDir, '').replace('.dat', '') + if core_inst._crypto.sha3Hash(newBlock.read()) == block.replace('.dat', ''): + core_inst.addToBlockDB(block.replace('.dat', ''), dataSaved=True) + logger.info('Imported block %s.' % block) + core_inst._utils.processBlockMetadata(block) + else: + logger.warn('Failed to verify hash for %s' % block) + if not exist: + logger.info('No blocks found to import') \ No newline at end of file diff --git a/onionr/onionrutils/localcommand.py b/onionr/onionrutils/localcommand.py index fff050c8..90b0d7ac 100644 --- a/onionr/onionrutils/localcommand.py +++ b/onionr/onionrutils/localcommand.py @@ -1,6 +1,7 @@ import urllib, requests, time import logger -def local_command(utils_inst, command, data='', silent = True, post=False, postData = {}, maxWait=20): +from onionrutils import getclientapiserver +def local_command(core_inst, command, data='', silent = True, post=False, postData = {}, maxWait=20): ''' Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers. ''' @@ -9,7 +10,7 @@ def local_command(utils_inst, command, data='', silent = True, post=False, postD waited = 0 while hostname == '': try: - hostname = utils_inst.getClientAPIServer() + hostname = getclientapiserver.get_client_API_server(core_inst) except FileNotFoundError: time.sleep(1) waited += 1 diff --git a/onionr/onionrutils/stringvalidators.py b/onionr/onionrutils/stringvalidators.py new file mode 100644 index 00000000..49f2c33a --- /dev/null +++ b/onionr/onionrutils/stringvalidators.py @@ -0,0 +1,97 @@ +import base64, string +import unpaddedbase32, nacl.signing, nacl.encoding +def validate_hash(utils_inst, data, length=64): + ''' + Validate if a string is a valid hash hex digest (does not compare, just checks length and charset) + ''' + retVal = True + if data == False or data == True: + return False + data = data.strip() + if len(data) != length: + retVal = False + else: + try: + int(data, 16) + except ValueError: + retVal = False + + return retVal + +def validate_pub_key(utils_inst, key): + ''' + Validate if a string is a valid base32 encoded Ed25519 key + ''' + if type(key) is type(None): + return False + # Accept keys that have no = padding + key = unpaddedbase32.repad(utils_inst.strToBytes(key)) + + retVal = False + try: + nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) + except nacl.exceptions.ValueError: + pass + except base64.binascii.Error as err: + pass + else: + retVal = True + return retVal + +def validate_transport(id): + try: + idLength = len(id) + retVal = True + idNoDomain = '' + peerType = '' + # i2p b32 addresses are 60 characters long (including .b32.i2p) + if idLength == 60: + peerType = 'i2p' + if not id.endswith('.b32.i2p'): + retVal = False + else: + idNoDomain = id.split('.b32.i2p')[0] + # Onion v2's are 22 (including .onion), v3's are 62 with .onion + elif idLength == 22 or idLength == 62: + peerType = 'onion' + if not id.endswith('.onion'): + retVal = False + else: + idNoDomain = id.split('.onion')[0] + else: + retVal = False + if retVal: + if peerType == 'i2p': + try: + id.split('.b32.i2p')[2] + except: + pass + else: + retVal = False + elif peerType == 'onion': + try: + id.split('.onion')[2] + except: + pass + else: + retVal = False + if not idNoDomain.isalnum(): + retVal = False + + # Validate address is valid base32 (when capitalized and minus extension); v2/v3 onions and .b32.i2p use base32 + for x in idNoDomain.upper(): + if x not in string.ascii_uppercase and x not in '234567': + retVal = False + + return retVal + except Exception as e: + return False + +def is_integer_string(data): + '''Check if a string is a valid base10 integer (also returns true if already an int)''' + try: + int(data) + except (ValueError, TypeError) as e: + return False + else: + return True diff --git a/onionr/onionrutils/validatemetadata.py b/onionr/onionrutils/validatemetadata.py index c4bcc4a8..8feb9859 100644 --- a/onionr/onionrutils/validatemetadata.py +++ b/onionr/onionrutils/validatemetadata.py @@ -1,6 +1,7 @@ import json import logger, onionrexceptions from etc import onionrvalues +from onionrutils import stringvalidators def validate_metadata(utils_inst, metadata, blockData): '''Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string''' # TODO, make this check sane sizes @@ -15,7 +16,7 @@ def validate_metadata(utils_inst, metadata, blockData): pass # Validate metadata dict for invalid keys to sizes that are too large - maxAge = utils_inst._coreconfig.get("general.max_block_age", onionrvalues.OnionrValues().default_expire) + maxAge = utils_inst._core.config.get("general.max_block_age", onionrvalues.OnionrValues().default_expire) if type(metadata) is dict: for i in metadata: try: @@ -33,7 +34,7 @@ def validate_metadata(utils_inst, metadata, blockData): logger.warn('Block metadata key ' + i + ' exceeded maximum size') break if i == 'time': - if not utils_inst.isIntegerString(metadata[i]): + if not stringvalidators.is_integer_string(metadata[i]): logger.warn('Block metadata time stamp is not integer string or int') break isFuture = (metadata[i] - utils_inst.getEpoch()) diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index f6a0b906..c65917c8 100755 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -23,6 +23,7 @@ import threading, time, uuid, subprocess, sys import config, logger from onionrblockapi import Block import onionrplugins +from onionrutils import localcommand plugin_name = 'cliui' PLUGIN_VERSION = '0.0.1' @@ -48,7 +49,7 @@ class OnionrCLIUI: def isRunning(self): while not self.shutdown: - if self.myCore._utils.localCommand('ping', maxWait=5) == 'pong!': + if localcommand.local_command(self.myCore, 'ping', maxWait=5) == 'pong!': self.running = 'Yes' else: self.running = 'No' diff --git a/onionr/static-data/default-plugins/esoteric/peerserver.py b/onionr/static-data/default-plugins/esoteric/peerserver.py index b3ae423e..f48ba0be 100755 --- a/onionr/static-data/default-plugins/esoteric/peerserver.py +++ b/onionr/static-data/default-plugins/esoteric/peerserver.py @@ -19,6 +19,7 @@ ''' import sys, os, json import core +from onionrutils import localcommand from flask import Response, request, redirect, Blueprint, abort, g sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) direct_blueprint = Blueprint('esoteric', __name__) @@ -47,9 +48,9 @@ def sendto(): msg = '' else: msg = json.dumps(msg) - core_inst._utils.localCommand('/esoteric/addrec/%s' % (g.peer,), post=True, postData=msg) + localcommand.local_command(core_inst, '/esoteric/addrec/%s' % (g.peer,), post=True, postData=msg) return Response('success') @direct_blueprint.route('/esoteric/poll') def poll_chat(): - return Response(core_inst._utils.localCommand('/esoteric/gets/%s' % (g.peer,))) \ No newline at end of file + return Response(localcommand.local_command(core_inst, '/esoteric/gets/%s' % (g.peer,))) \ No newline at end of file diff --git a/onionr/static-data/default-plugins/pluginmanager/main.py b/onionr/static-data/default-plugins/pluginmanager/main.py index d4feb69d..2f261bbf 100755 --- a/onionr/static-data/default-plugins/pluginmanager/main.py +++ b/onionr/static-data/default-plugins/pluginmanager/main.py @@ -22,6 +22,7 @@ import logger, config import os, sys, json, time, random, shutil, base64, getpass, datetime, re from onionrblockapi import Block +from onionrutils import importnewblocks plugin_name = 'pluginmanager' @@ -236,7 +237,7 @@ def pluginToBlock(plugin, import_block = True): # hash = pluginapi.get_core().insertBlock(, header = 'plugin', sign = True) if import_block: - pluginapi.get_utils().importNewBlocks() + importnewblocks.import_new_blocks(pluginapi.get_core()) return hash else: diff --git a/onionr/tests/test_stringvalidations.py b/onionr/tests/test_stringvalidations.py index caac34df..4eb7f113 100755 --- a/onionr/tests/test_stringvalidations.py +++ b/onionr/tests/test_stringvalidations.py @@ -6,6 +6,7 @@ TEST_DIR = 'testdata/%s-%s' % (uuid.uuid4(), os.path.basename(__file__)) + '/' print("Test directory:", TEST_DIR) os.environ["ONIONR_HOME"] = TEST_DIR import core, onionr +from onionrutils import stringvalidators core.Core() @@ -13,7 +14,6 @@ class OnionrValidations(unittest.TestCase): def test_peer_validator(self): # Test hidden service domain validities - c = core.Core() valid = ['facebookcorewwwi.onion', 'vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion', '5bvb5ncnfr4dlsfriwczpzcvo65kn7fnnlnt2ln7qvhzna2xaldq.b32.i2p'] @@ -21,11 +21,11 @@ class OnionrValidations(unittest.TestCase): for x in valid: print('testing', x) - self.assertTrue(c._utils.validateID(x)) + self.assertTrue(stringvalidators.validate_transport(x)) for x in invalid: print('testing', x) - self.assertFalse(c._utils.validateID(x)) + self.assertFalse(stringvalidators.validate_transport(x)) def test_pubkey_validator(self): # Test ed25519 public key validity @@ -44,14 +44,13 @@ class OnionrValidations(unittest.TestCase): def test_integer_string(self): valid = ["1", "100", 100, "-5", -5] invalid = ['test', "1d3434", "1e100", None] - c = core.Core() for x in valid: #print('testing', x) - self.assertTrue(c._utils.isIntegerString(x)) + self.assertTrue(stringvalidators.is_integer_string(x)) for x in invalid: #print('testing', x) - self.assertFalse(c._utils.isIntegerString(x)) + self.assertFalse(stringvalidators.is_integer_string(x)) unittest.main() \ No newline at end of file