diff --git a/onionr/api.py b/onionr/api.py index 705a4781..e9ecd475 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -24,7 +24,7 @@ from gevent.wsgi import WSGIServer import sys, random, threading, hmac, hashlib, base64, time, math, os, logger, config from core import Core from onionrblockapi import Block -import onionrutils, onionrcrypto, blockimporter +import onionrutils, onionrcrypto, blockimporter, onionrevents as events class API: ''' @@ -94,6 +94,7 @@ class API: ''' Simply define the request as not having yet failed, before every request. ''' + self.requestFailed = False return @@ -119,16 +120,24 @@ class API: @app.route('/client/ui/') def webUI(path): startTime = math.floor(time.time()) + if request.args.get('timingToken') is None: timingToken = '' else: timingToken = request.args.get('timingToken') - self.validateHost('private') + + if not config.get("onionr_ui.run", True): + abort(403) + if config.get("onionr_ui.private_only", True): + self.validateHost('private') + endTime = math.floor(time.time()) elapsed = endTime - startTime + if not hmac.compare_digest(timingToken, self.timeBypassToken): if elapsed < self._privateDelayTime: time.sleep(self._privateDelayTime - elapsed) + return send_from_directory('static-data/ui/dist/', path) @app.route('/client/') @@ -150,6 +159,9 @@ class API: if not self.validateToken(token): abort(403) + + events.event('webapi_private', onionr = None, data = {'action' : action, 'data' : data, 'timingToken' : timingToken, 'token' : token}) + self.validateHost('private') if action == 'hello': resp = Response('Hello, World! ' + request.host) @@ -198,12 +210,12 @@ class API: response['hash'] = hash response['reason'] = 'Successfully wrote block to file' else: - response['reason'] = 'Faield to save the block' + response['reason'] = 'Failed to save the block' except Exception as e: logger.debug('insertBlock api request failed', error = e) resp = Response(json.dumps(response)) - elif action in callbacks['private']: + elif action in API.callbacks['private']: resp = Response(str(getCallback(action, scope = 'private')(request))) else: resp = Response('(O_o) Dude what? (invalid command)') @@ -257,6 +269,10 @@ class API: data = data except: data = '' + + + events.event('webapi_public', onionr = None, data = {'action' : action, 'data' : data, 'requestingPeer' : requestingPeer, 'request' : request}) + if action == 'firstConnect': pass elif action == 'ping': @@ -299,7 +315,7 @@ class API: peers = self._core.listPeers(getPow=True) response = ','.join(peers) resp = Response(response) - elif action in callbacks['public']: + elif action in API.callbacks['public']: resp = Response(str(getCallback(action, scope = 'public')(request))) else: resp = Response("") @@ -373,29 +389,29 @@ class API: sys.exit(1) def setCallback(action, callback, scope = 'public'): - if not scope in callbacks: + if not scope in API.callbacks: return False - callbacks[scope][action] = callback + API.callbacks[scope][action] = callback return True def removeCallback(action, scope = 'public'): - if (not scope in callbacks) or (not action in callbacks[scope]): + if (not scope in API.callbacks) or (not action in API.callbacks[scope]): return False - del callbacks[scope][action] + del API.callbacks[scope][action] return True def getCallback(action, scope = 'public'): - if (not scope in callbacks) or (not action in callbacks[scope]): + if (not scope in API.callbacks) or (not action in API.callbacks[scope]): return None - return callbacks[scope][action] + return API.callbacks[scope][action] def getCallbacks(scope = None): - if (not scope is None) and (scope in callbacks): - return callbacks[scope] + if (not scope is None) and (scope in API.callbacks): + return API.callbacks[scope] - return callbacks + return API.callbacks diff --git a/onionr/communicator2.py b/onionr/communicator2.py index b420fcba..894ea094 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -19,8 +19,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sys, os, core, config, json, onionrblockapi as block, requests, time, logger, threading, onionrplugins as plugins, base64, onionr -import onionrexceptions, onionrpeers +import sys, os, core, config, json, requests, time, logger, threading, base64, onionr +import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block from defusedxml import minidom class OnionrCommunicatorDaemon: @@ -337,6 +337,8 @@ class OnionrCommunicatorDaemon: cmd = self._core.daemonQueue() if cmd is not False: + events.event('daemon_command', onionr = None, data = {'cmd' : cmd}) + if cmd[0] == 'shutdown': self.shutdown = True elif cmd[0] == 'announceNode': @@ -355,6 +357,7 @@ class OnionrCommunicatorDaemon: threading.Thread(target=self.uploadBlock).start() else: logger.info('Recieved daemonQueue command:' + cmd[0]) + self.decrementThreadCount('daemonCommands') def uploadBlock(self): @@ -401,6 +404,7 @@ class OnionrCommunicatorDaemon: time.sleep(1) else: # This executes if the api is NOT detected to be running + events.event('daemon_crash', onionr = None, data = {}) logger.error('Daemon detected API crash (or otherwise unable to reach API after long time), stopping...') self.shutdown = True self.decrementThreadCount('detectAPICrash') diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 7ad74cc0..386c334b 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -100,7 +100,7 @@ DataDirectory data/tordata/ logger.fatal('Failed to start Tor. Try killing any other Tor processes owned by this user.') return False except KeyboardInterrupt: - logger.fatal("Got keyboard interrupt") + logger.fatal("Got keyboard interrupt.") return False logger.debug('Finished starting Tor.', timestamp=True) diff --git a/onionr/onionr.py b/onionr/onionr.py index ec3ec468..acf14374 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -40,9 +40,9 @@ except ImportError: raise Exception("You need the PySocks module (for use with socks5 proxy to use Tor)") ONIONR_TAGLINE = 'Anonymous P2P Platform - GPLv3 - https://Onionr.VoidNet.Tech' -ONIONR_VERSION = '0.1.0' # for debugging and stuff +ONIONR_VERSION = '0.1.1' # for debugging and stuff ONIONR_VERSION_TUPLE = tuple(ONIONR_VERSION.split('.')) # (MAJOR, MINOR, VERSION) -API_VERSION = '4' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes knows how to communicate without learning too much information about you. +API_VERSION = '4' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes know how to communicate without learning too much information about you. class Onionr: def __init__(self): diff --git a/onionr/onionrevents.py b/onionr/onionrevents.py index 9ecc552f..26fdc093 100644 --- a/onionr/onionrevents.py +++ b/onionr/onionrevents.py @@ -33,10 +33,10 @@ def __event_caller(event_name, data = {}, onionr = None): try: call(plugins.get_plugin(plugin), event_name, data, get_pluginapi(onionr, data)) except ModuleNotFoundError as e: - logger.warn('Disabling nonexistant plugin \"' + plugin + '\"...') + logger.warn('Disabling nonexistant plugin "%s"...' % plugin) plugins.disable(plugin, onionr, stop_event = False) except Exception as e: - logger.warn('Event \"' + event_name + '\" failed for plugin \"' + plugin + '\".') + logger.warn('Event "%s" failed for plugin "%s".' % (event_name, plugin)) logger.debug(str(e)) diff --git a/onionr/onionrexceptions.py b/onionr/onionrexceptions.py index d0a6d248..2817d419 100644 --- a/onionr/onionrexceptions.py +++ b/onionr/onionrexceptions.py @@ -49,5 +49,6 @@ class InvalidProof(Exception): # network level exceptions class MissingPort(Exception): pass + class InvalidAddress(Exception): pass diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 2b6c6e5a..105519e9 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -199,7 +199,7 @@ class OnionrUtils: def getBlockMetadataFromData(self, blockData): ''' accepts block contents as string, returns a tuple of metadata, meta (meta being internal metadata, which will be returned as an encrypted base64 string if it is encrypted, dict if not). - + ''' meta = {} metadata = {} @@ -208,7 +208,7 @@ class OnionrUtils: blockData = blockData.encode() except AttributeError: pass - + try: metadata = json.loads(blockData[:blockData.find(b'\n')].decode()) except json.decoder.JSONDecodeError: @@ -221,7 +221,7 @@ class OnionrUtils: meta = json.loads(metadata['meta']) except KeyError: pass - meta = metadata['meta'] + meta = metadata['meta'] return (metadata, meta, data) def checkPort(self, port, host=''): @@ -251,7 +251,7 @@ class OnionrUtils: return False else: return True - + def processBlockMetadata(self, blockHash): ''' Read metadata from a block and cache it to the block database @@ -269,7 +269,7 @@ class OnionrUtils: def escapeAnsi(self, line): ''' Remove ANSI escape codes from a string with regex - + taken or adapted from: https://stackoverflow.com/a/38662876 ''' ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]') @@ -331,12 +331,12 @@ class OnionrUtils: retVal = False return retVal - + def validateMetadata(self, metadata): '''Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string''' # TODO, make this check sane sizes retData = False - + # convert to dict if it is json string if type(metadata) is str: try: @@ -382,7 +382,7 @@ class OnionrUtils: else: retVal = True return retVal - + def isIntegerString(self, data): '''Check if a string is a valid base10 integer''' try: diff --git a/onionr/static-data/default_config.json b/onionr/static-data/default_config.json index 3a08f7db..663bd732 100644 --- a/onionr/static-data/default_config.json +++ b/onionr/static-data/default_config.json @@ -2,8 +2,16 @@ "general" : { "dev_mode": true, "display_header" : true, - "dc_response": true, - "dc_execcallbacks" : true + + "direct_connect" : { + "respond" : true, + "execute_callbacks" : true + } + }, + + "onionr_ui" : { + "run" : true, + "private_only" : true }, "client" : {