From a8f8aea35f505d408e6f5b803ff3c0415cca9006 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 18 Dec 2018 17:48:17 -0600 Subject: [PATCH] work on revising api --- onionr/api.py | 632 ++++--------------------- onionr/apimanager.py | 16 +- onionr/apipublic.py | 15 +- onionr/communicator2.py | 2 + onionr/core.py | 2 + onionr/netcontroller.py | 15 +- onionr/onionr.py | 27 +- onionr/onionrutils.py | 10 +- onionr/static-data/default_config.json | 3 +- 9 files changed, 147 insertions(+), 575 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 94cf2cd9..6499b4d3 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -19,54 +19,71 @@ ''' import flask from flask import request, Response, abort, send_from_directory -from multiprocessing import Process from gevent.pywsgi import WSGIServer import sys, random, threading, hmac, hashlib, base64, time, math, os, json import core from onionrblockapi import Block import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config, onionr +def guessMime(path): + ''' + Guesses the mime type from the input filename + ''' + mimetypes = { + 'html' : 'text/html', + 'js' : 'application/javascript', + 'css' : 'text/css', + 'png' : 'image/png', + 'jpg' : 'image/jpeg' + } + + for mimetype in mimetypes: + if path.endswith('.%s' % mimetype): + return mimetypes[mimetype] + + return 'text/plain' + +def setBindIP(filePath): + '''Set a random localhost IP to a specified file (intended for private or public API localhost IPs)''' + hostOctets = [str(127), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF))] + data = '.'.join(hostOctets) + + with open(filePath, 'w') as bindFile: + bindFile.write(data) + return data + +class PublicAPI: + ''' + The new client api server, isolated from the public api + ''' + def __init__(self, clientAPI): + assert isinstance(clientAPI, API) + app = flask.Flask('PublicAPI') + self.i2pEnabled = config.get('i2p.host', False) + self.hideBlocks = [] # Blocks to be denied sharing + self.host = setBindIP(clientAPI._core.publicApiHostFile) + bindPort = config.get('client.public.port') + + @app.route('/') + def banner(): + #validateHost('public') + try: + with open('static-data/index.html', 'r') as html: + resp = Response(html.read(), mimetype='text/html') + except FileNotFoundError: + resp = Response("") + return resp + clientAPI.setPublicAPIInstance(self) + self.httpServer = WSGIServer((self.host, bindPort), app, log=None) + self.httpServer.serve_forever() + class API: ''' - Main HTTP API (Flask) + Client HTTP api ''' callbacks = {'public' : {}, 'private' : {}} - def validateToken(self, token): - ''' - Validate that the client token matches the given token - ''' - if len(self.clientToken) == 0: - logger.error("client password needs to be set") - return False - try: - if not hmac.compare_digest(self.clientToken, token): - return False - else: - return True - except TypeError: - return False - - def guessMime(path): - ''' - Guesses the mime type from the input filename - ''' - - mimetypes = { - 'html' : 'text/html', - 'js' : 'application/javascript', - 'css' : 'text/css', - 'png' : 'image/png', - 'jpg' : 'image/jpeg' - } - - for mimetype in mimetypes: - if path.endswith('.%s' % mimetype): - return mimetypes[mimetype] - - return 'text/plain' - def __init__(self, debug, API_VERSION): ''' Initialize the api server, preping variables for later use @@ -84,520 +101,49 @@ class API: self._crypto = onionrcrypto.OnionrCrypto(self._core) self._utils = onionrutils.OnionrUtils(self._core) app = flask.Flask(__name__) - bindPort = int(config.get('client.port', 59496)) + bindPort = int(config.get('client.client.port', 59496)) self.bindPort = bindPort + self.clientToken = config.get('client.webpassword') self.timeBypassToken = base64.b16encode(os.urandom(32)).decode() - self.i2pEnabled = config.get('i2p.host', False) - - self.hideBlocks = [] # Blocks to be denied sharing - - with open(self._core.dataDir + 'time-bypass.txt', 'w') as bypass: - bypass.write(self.timeBypassToken) - - if not debug and not self._developmentMode: - hostOctets = [str(127), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF))] - self.host = '.'.join(hostOctets) - else: - self.host = '127.0.0.1' - - with open(self._core.dataDir + 'host.txt', 'w') as file: - file.write(self.host) - - @app.before_request - def beforeReq(): - ''' - Simply define the request as not having yet failed, before every request. - ''' - self.requestFailed = False - return - - @app.after_request - def afterReq(resp): - if not self.requestFailed: - resp.headers['Access-Control-Allow-Origin'] = '*' - #else: - # resp.headers['server'] = 'Onionr' - resp.headers["Content-Security-Policy"] = "default-src 'none'; script-src 'none'; object-src 'none'; style-src data: 'unsafe-inline'; img-src data:; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'none'" - resp.headers['X-Frame-Options'] = 'deny' - resp.headers['X-Content-Type-Options'] = "nosniff" - resp.headers['X-API'] = API_VERSION - resp.headers['Date'] = 'Thu, 1 Jan 1970 00:00:00 GMT' # Clock info is probably useful to attackers. Set to unix epoch. - return resp - - @app.route('/site/') - def site(block): - self.validateHost('private') - bHash = block - resp = 'Not Found' - if self._core._utils.validateHash(bHash): - resp = Block(bHash).bcontent - try: - resp = base64.b64decode(resp) - except: - pass - return Response(resp) - - @app.route('/www/private/') - def www_private(path): - startTime = math.floor(time.time()) - - if request.args.get('timingToken') is None: - timingToken = '' - else: - timingToken = request.args.get('timingToken') - - if not config.get("www.private.run", True): - abort(403) - - self.validateHost('private') - - endTime = math.floor(time.time()) - elapsed = endTime - startTime - - if not hmac.compare_digest(timingToken, self.timeBypassToken): - if (elapsed < self._privateDelayTime) and config.get('www.private.timing_protection', True): - time.sleep(self._privateDelayTime - elapsed) - - return send_from_directory(config.get('www.private.path', 'static-data/www/private/'), path) - - @app.route('/www/public/') - def www_public(path): - if not config.get("www.public.run", True): - abort(403) - - self.validateHost('public') - - return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path) - - @app.route('/ui/') - def ui_private(path): - startTime = math.floor(time.time()) - - ''' - if request.args.get('timingToken') is None: - timingToken = '' - else: - timingToken = request.args.get('timingToken') - ''' - - if not config.get("www.ui.run", True): - abort(403) - - if config.get("www.ui.private", True): - self.validateHost('private') - else: - self.validateHost('public') - - ''' - 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) - ''' - - mime = API.guessMime(path) - - logger.debug('Serving %s (mime: %s)' % (path, mime)) - - return send_from_directory('static-data/www/ui/dist/', path) - - @app.route('/client/') - def private_handler(): - if request.args.get('timingToken') is None: - timingToken = '' - else: - timingToken = request.args.get('timingToken') - data = request.args.get('data') - try: - data = data - except: - data = '' - startTime = math.floor(time.time()) - - action = request.args.get('action') - #if not self.debug: - token = request.args.get('token') - - 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) - elif action == 'getIP': - resp = Response(self.host) - elif action == 'waitForShare': - if self._core._utils.validateHash(data): - if data not in self.hideBlocks: - self.hideBlocks.append(data) - else: - self.hideBlocks.remove(data) - resp = "success" - else: - resp = "failed to validate hash" - elif action == 'shutdown': - # request.environ.get('werkzeug.server.shutdown')() - self.http_server.stop() - resp = Response('Goodbye') - elif action == 'ping': - resp = Response('pong') - elif action == 'info': - resp = Response(json.dumps({'pubkey' : self._core._crypto.pubKey, 'host' : self._core.hsAddress}), mimetype='text/plain') - elif action == "insertBlock": - response = {'success' : False, 'reason' : 'An unknown error occurred'} - - if not ((data is None) or (len(str(data).strip()) == 0)): - try: - decoded = json.loads(data) - - block = Block() - - sign = False - - for key in decoded: - val = decoded[key] - - key = key.lower() - - if key == 'type': - block.setType(val) - elif key in ['body', 'content']: - block.setContent(val) - elif key == 'parent': - block.setParent(val) - elif key == 'sign': - sign = (str(val).lower() == 'true') - - hash = block.save(sign = sign) - - if not hash is False: - response['success'] = True - response['hash'] = hash - response['reason'] = 'Successfully wrote block to file' - else: - response['reason'] = 'Failed to save the block' - except Exception as e: - logger.warn('insertBlock api request failed', error = e) - logger.debug('Here\'s the request: %s' % data) - else: - response = {'success' : False, 'reason' : 'Missing `data` parameter.', 'blocks' : {}} - - resp = Response(json.dumps(response)) - elif action == 'searchBlocks': - response = {'success' : False, 'reason' : 'An unknown error occurred', 'blocks' : {}} - - if not ((data is None) or (len(str(data).strip()) == 0)): - try: - decoded = json.loads(data) - - type = None - signer = None - signed = None - parent = None - reverse = False - limit = None - - for key in decoded: - val = decoded[key] - - key = key.lower() - - if key == 'type': - type = str(val) - elif key == 'signer': - if isinstance(val, list): - signer = val - else: - signer = str(val) - elif key == 'signed': - signed = (str(val).lower() == 'true') - elif key == 'parent': - parent = str(val) - elif key == 'reverse': - reverse = (str(val).lower() == 'true') - elif key == 'limit': - limit = 10000 - - if val is None: - val = limit - - limit = min(limit, int(val)) - - blockObjects = Block.getBlocks(type = type, signer = signer, signed = signed, parent = parent, reverse = reverse, limit = limit) - - logger.debug('%s results for query %s' % (len(blockObjects), decoded)) - - blocks = list() - - for block in blockObjects: - blocks.append({ - 'hash' : block.getHash(), - 'type' : block.getType(), - 'content' : block.getContent(), - 'signature' : block.getSignature(), - 'signedData' : block.getSignedData(), - 'signed' : block.isSigned(), - 'valid' : block.isValid(), - 'date' : (int(block.getDate().strftime("%s")) if not block.getDate() is None else None), - 'parent' : (block.getParent().getHash() if not block.getParent() is None else None), - 'metadata' : block.getMetadata(), - 'header' : block.getHeader() - }) - - response['success'] = True - response['blocks'] = blocks - response['reason'] = 'Success' - except Exception as e: - logger.warn('searchBlock api request failed', error = e) - logger.debug('Here\'s the request: %s' % data) - else: - response = {'success' : False, 'reason' : 'Missing `data` parameter.', 'blocks' : {}} - - resp = Response(json.dumps(response), mimetype='text/plain') - - elif action in API.callbacks['private']: - resp = Response(str(getCallback(action, scope = 'private')(request)), mimetype='text/plain') - else: - resp = Response('invalid command') - endTime = math.floor(time.time()) - elapsed = endTime - startTime - - # if bypass token not used, delay response to prevent timing attacks - if not hmac.compare_digest(timingToken, self.timeBypassToken): - if elapsed < self._privateDelayTime: - time.sleep(self._privateDelayTime - elapsed) - - return resp + self.publicAPI = None # gets set when the thread calls our setter... bad hack but kinda necessary with flask + threading.Thread(target=PublicAPI, args=(self,)).start() + self.host = setBindIP(self._core.privateApiHostFile) + logger.info('Running api on %s:%s' % (self.host, self.bindPort)) + self.httpServer = '' @app.route('/') - def banner(): - self.validateHost('public') + def hello(): + return Response("hello client") + + @app.route('/shutdown') + def shutdown(): try: - with open('static-data/index.html', 'r') as html: - resp = Response(html.read(), mimetype='text/html') - except FileNotFoundError: - resp = Response("") - return resp - - @app.route('/public/upload/', methods=['POST']) - def blockUpload(): - self.validateHost('public') - resp = 'failure' - try: - data = request.form['block'] - except KeyError: - logger.warn('No block specified for upload') + self.publicAPI.httpServer.stop() + self.httpServer.stop() + except AttributeError: pass - else: - if sys.getsizeof(data) < 100000000: - try: - if blockimporter.importBlockFromData(data, self._core): - resp = 'success' - else: - logger.warn('Error encountered importing uploaded block') - except onionrexceptions.BlacklistedBlock: - logger.debug('uploaded block is blacklisted') - pass - - resp = Response(resp) - return resp - - @app.route('/public/announce/', methods=['POST']) - def acceptAnnounce(): - self.validateHost('public') - resp = 'failure' - powHash = '' - randomData = '' - newNode = '' - ourAdder = self._core.hsAddress.encode() - try: - newNode = request.form['node'].encode() - except KeyError: - logger.warn('No block specified for upload') - pass - else: - try: - randomData = request.form['random'] - randomData = base64.b64decode(randomData) - except KeyError: - logger.warn('No random data specified for upload') - else: - nodes = newNode + self._core.hsAddress.encode() - nodes = self._core._crypto.blake2bHash(nodes) - powHash = self._core._crypto.blake2bHash(randomData + nodes) - try: - powHash = powHash.decode() - except AttributeError: - pass - if powHash.startswith('0000'): - try: - newNode = newNode.decode() - except AttributeError: - pass - if self._core.addAddress(newNode): - resp = 'Success' - else: - logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash) - resp = Response(resp) - return resp - - @app.route('/public/') - def public_handler(): - # Public means it is publicly network accessible - self.validateHost('public') - if config.get('general.security_level') != 0: - abort(403) - action = request.args.get('action') - requestingPeer = request.args.get('myID') - data = request.args.get('data') - try: - 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': - resp = Response("pong!") - elif action == 'getSymmetric': - resp = Response(self._crypto.generateSymmetric()) - elif action == 'getDBHash': - resp = Response(self._utils.getBlockDBHash()) - elif action == 'getBlockHashes': - bList = self._core.getBlockList() - for b in self.hideBlocks: - if b in bList: - bList.remove(b) - resp = Response('\n'.join(bList)) - # setData should be something the communicator initiates, not this api - elif action == 'getData': - resp = '' - if self._utils.validateHash(data): - if data not in self.hideBlocks: - if os.path.exists(self._core.dataDir + 'blocks/' + data + '.dat'): - block = Block(hash=data.encode(), core=self._core) - resp = base64.b64encode(block.getRaw().encode()).decode() - if len(resp) == 0: - abort(404) - resp = "" - resp = Response(resp) - elif action == 'pex': - response = ','.join(self._core.listAdders()) - if len(response) == 0: - response = 'none' - resp = Response(response) - elif action == 'kex': - peers = self._core.listPeers(getPow=True) - response = ','.join(peers) - resp = Response(response) - elif action in API.callbacks['public']: - resp = Response(str(getCallback(action, scope = 'public')(request))) - else: - resp = Response("") - - return resp - - @app.errorhandler(404) - def notfound(err): - self.requestFailed = True - resp = Response("") - - return resp - - @app.errorhandler(403) - def authFail(err): - self.requestFailed = True - resp = Response("403") - return resp - - @app.errorhandler(401) - def clientError(err): - self.requestFailed = True - resp = Response("Invalid request") - - return resp - - logger.info('Starting client on ' + self.host + ':' + str(bindPort), timestamp=False) + return Response("bye") + + self.httpServer = WSGIServer((self.host, bindPort), app, log=None) + self.httpServer.serve_forever() + + def setPublicAPIInstance(self, inst): + assert isinstance(inst, PublicAPI) + self.publicAPI = inst + def validateToken(self, token): + ''' + Validate that the client token matches the given token + ''' + if len(self.clientToken) == 0: + logger.error("client password needs to be set") + return False try: - while len(self._core.hsAddress) == 0: - self._core.refreshFirstStartVars() - time.sleep(0.5) - self.http_server = WSGIServer((self.host, bindPort), app, log=None) - self.http_server.serve_forever() - except KeyboardInterrupt: - pass - except Exception as e: - logger.error(str(e)) - logger.fatal('Failed to start client on ' + self.host + ':' + str(bindPort) + ', exiting...') - - def validateHost(self, hostType): - ''' - Validate various features of the request including: - - If private (/client/), is the host header local? - If public (/public/), is the host header onion or i2p? - - Was X-Request-With used? - ''' - if self.debug: - return - # Validate host header, to protect against DNS rebinding attacks - host = self.host - if hostType == 'private': - if not request.host.startswith('127') and not self._utils.checkIsIP(request.host): - abort(403) - elif hostType == 'public': - if not request.host.endswith('onion') and not request.host.endswith('i2p'): - abort(403) - # Validate x-requested-with, to protect against CSRF/metadata leaks - - if not self.i2pEnabled and request.host.endswith('i2p'): - abort(403) - - ''' - if not self._developmentMode: - try: - request.headers['X-Requested-With'] - except: - pass - # we exit rather than abort to avoid fingerprinting - logger.debug('Avoiding fingerprinting, exiting...') - #sys.exit(1) - ''' - - def setCallback(action, callback, scope = 'public'): - if not scope in API.callbacks: - return False - - API.callbacks[scope][action] = callback - - return True - - def removeCallback(action, scope = 'public'): - if (not scope in API.callbacks) or (not action in API.callbacks[scope]): - return False - - del API.callbacks[scope][action] - - return True - - def getCallback(action, scope = 'public'): - if (not scope in API.callbacks) or (not action in API.callbacks[scope]): - return None - - return API.callbacks[scope][action] - - def getCallbacks(scope = None): - if (not scope is None) and (scope in API.callbacks): - return API.callbacks[scope] - - return API.callbacks + if not hmac.compare_digest(self.clientToken, token): + return False + else: + return True + except TypeError: + return False \ No newline at end of file diff --git a/onionr/apimanager.py b/onionr/apimanager.py index 05a1ba91..44737097 100644 --- a/onionr/apimanager.py +++ b/onionr/apimanager.py @@ -30,7 +30,7 @@ def getOpenPort(): p = socket.socket(socket.AF_INET, socket.SOCK_STREAM) p.bind(("127.0.0.1",0)) p.listen(1) - port = s.getsockname()[1] + port = p.getsockname()[1] p.close() return port @@ -43,9 +43,9 @@ def getRandomLocalIP(): class APIManager: def __init__(self, coreInst): assert isinstance(coreInst, core.Core) - self.core = core - self.utils = core._utils - self.crypto = core._crypto + self.core = coreInst + self.utils = coreInst._utils + self.crypto = coreInst._crypto # if this gets set to true, both the public and private apis will shutdown self.shutdown = False @@ -63,12 +63,12 @@ class APIManager: # Make official the IPs and Ports self.publicIP = publicIP self.privateIP = privateIP - self.publicPort = getOpenPort() - self.privatePort = getOpenPort() + self.publicPort = config.get('client.port', 59496) + self.privatePort = config.get('client.port', 59496) # Run the API servers in new threads - self.publicAPI = apipublic.APIPublic() - self.privateAPI = apiprivate.privateAPI() + self.publicAPI = apipublic.APIPublic(self) + self.privateAPI = apiprivate.APIPrivate(self) threading.Thread(target=self.publicAPI.run).start() threading.Thread(target=self.privateAPI.run).start() while not self.shutdown: diff --git a/onionr/apipublic.py b/onionr/apipublic.py index 110a6934..9308f50a 100644 --- a/onionr/apipublic.py +++ b/onionr/apipublic.py @@ -21,12 +21,21 @@ import flask, apimanager from flask import request, Response, abort, send_from_directory from gevent.pywsgi import WSGIServer + class APIPublic: def __init__(self, managerInst): assert isinstance(managerInst, apimanager.APIManager) - self.app = flask.Flask(__name__) # The flask application, which recieves data from the greenlet wsgiserver - self.httpServer = WSGIServer((managerInst.publicIP, managerInst.publicPort), self.app, log=None) + app = flask.Flask(__name__) + @app.route('/') + def banner(): + try: + with open('static-data/index.html', 'r') as html: + resp = Response(html.read(), mimetype='text/html') + except FileNotFoundError: + resp = Response("") + return resp + self.httpServer = WSGIServer((managerInst.publicIP, managerInst.publicPort), app) def run(self): self.httpServer.serve_forever() - return \ No newline at end of file + return diff --git a/onionr/communicator2.py b/onionr/communicator2.py index bddb21da..5b6f8ba9 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -19,6 +19,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' +import gevent.monkey +gevent.monkey.patch_all() import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block import onionrdaemontools, onionrsockets, onionrchat, onionr, onionrproofs diff --git a/onionr/core.py b/onionr/core.py index 59e802a5..f463dd35 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -49,6 +49,8 @@ class Core: self.peerDB = self.dataDir + 'peers.db' self.blockDB = self.dataDir + 'blocks.db' self.blockDataLocation = self.dataDir + 'blocks/' + self.publicApiHostFile = self.dataDir + 'public-host.txt' + self.privateApiHostFile = self.dataDir + 'private-host.txt' self.addressDB = self.dataDir + 'address.db' self.hsAddress = '' self.bootstrapFileLocation = 'static-data/bootstrap-nodes.txt' diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 76d8ece6..7566623e 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -18,10 +18,19 @@ along with this program. If not, see . ''' -import subprocess, os, random, sys, logger, time, signal, config, base64 +import subprocess, os, random, sys, logger, time, signal, config, base64, socket from stem.control import Controller from onionrblockapi import Block from dependencies import secrets + +def getOpenPort(): + # taken from (but modified) https://stackoverflow.com/a/2838309 + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(("127.0.0.1",0)) + s.listen(1) + port = s.getsockname()[1] + s.close() + return port class NetController: ''' This class handles hidden service setup on Tor and I2P @@ -37,7 +46,7 @@ class NetController: self.torConfigLocation = self.dataDir + 'torrc' self.readyState = False - self.socksPort = random.randint(1024, 65535) + self.socksPort = getOpenPort() self.hsPort = hsPort self._torInstnace = '' self.myID = '' @@ -78,7 +87,7 @@ class NetController: config.set('tor.controlpassword', plaintext, savefile=True) config.set('tor.socksport', self.socksPort, savefile=True) - controlPort = random.randint(1025, 65535) + controlPort = getOpenPort() config.set('tor.controlPort', controlPort, savefile=True) diff --git a/onionr/onionr.py b/onionr/onionr.py index ad6499e4..1d47972a 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -20,7 +20,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' - +import gevent.monkey +gevent.monkey.patch_all() import sys if sys.version_info[0] == 2 or sys.version_info[1] < 5: print('Error, Onionr requires Python 3.5+') @@ -28,8 +29,9 @@ if sys.version_info[0] == 2 or sys.version_info[1] < 5: import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json, getpass, sqlite3 import webbrowser from threading import Thread -import api, apimanager, core, config, logger, onionrplugins as plugins, onionrevents as events +import api, core, config, logger, onionrplugins as plugins, onionrevents as events import onionrutils +import netcontroller from netcontroller import NetController from onionrblockapi import Block import onionrproofs, onionrexceptions, onionrusers @@ -105,11 +107,12 @@ class Onionr: # Get configuration if type(config.get('client.webpassword')) is type(None): config.set('client.webpassword', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True) - if type(config.get('client.port')) is type(None): - randomPort = 0 - while randomPort < 1024: - randomPort = self.onionrCore._crypto.secrets.randbelow(65535) - config.set('client.port', randomPort, savefile=True) + if type(config.get('client.client.port')) is type(None): + randomPort = netcontroller.getOpenPort() + config.set('client.client.port', randomPort, savefile=True) + if type(config.get('client.public.port')) is type(None): + randomPort = netcontroller.getOpenPort() + config.set('client.public.port', randomPort, savefile=True) if type(config.get('client.participate')) is type(None): config.set('client.participate', True, savefile=True) if type(config.get('client.api_version')) is type(None): @@ -705,11 +708,7 @@ class Onionr: os.remove('data/.runcheck') apiTarget = api.API - if config.get('general.use_new_api_server', False): - apiTarget = apimanager.APIManager - apiThread = Thread(target = apiTarget, args = (self.onionrCore)) - else: - apiThread = Thread(target = apiTarget, args = (self.debug, API_VERSION)) + apiThread = Thread(target = apiTarget, args = (self.debug, API_VERSION)) apiThread.start() try: @@ -722,7 +721,7 @@ class Onionr: apiHost = '127.0.0.1' if apiThread.isAlive(): try: - with open(self.onionrCore.dataDir + 'host.txt', 'r') as hostFile: + with open(self.onionrCore.publicApiHostFile, 'r') as hostFile: apiHost = hostFile.read() except FileNotFoundError: pass @@ -730,7 +729,7 @@ class Onionr: if self._developmentMode: logger.warn('DEVELOPMENT MODE ENABLED (LESS SECURE)', timestamp = False) - net = NetController(config.get('client.port', 59496), apiServerIP=apiHost) + net = NetController(config.get('client.public.port', 59497), apiServerIP=apiHost) logger.debug('Tor is starting...') if not net.startTor(): self.onionrUtils.localCommand('shutdown') diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 948563e3..6c8ba3e4 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -160,13 +160,17 @@ class OnionrUtils: self.getTimeBypassToken() # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless. try: - with open(self._core.dataDir + 'host.txt', 'r') as host: + with open(self._core.privateApiHostFile, 'r') as host: hostname = host.read() except FileNotFoundError: return False - payload = 'http://%s:%s/client/?action=%s&token=%s&timingToken=%s' % (hostname, config.get('client.port'), command, config.get('client.webpassword'), self.timingToken) if data != '': - payload += '&data=' + urllib.parse.quote_plus(data) + data = '&data=' + urllib.parse.quote_plus(data) + payload = 'http://%s:%s/%s%s' % (hostname, config.get('client.client.port'), command, data) + logger.info(payload) + #payload = 'http://%s:%s/client/?action=%s&token=%s&timingToken=%s' % (hostname, config.get('client.client.port'), command, config.get('client.webpassword'), self.timingToken) + #if data != '': + # payload += '&data=' + urllib.parse.quote_plus(data) try: retData = requests.get(payload).text except Exception as error: diff --git a/onionr/static-data/default_config.json b/onionr/static-data/default_config.json index 5532797d..13436852 100644 --- a/onionr/static-data/default_config.json +++ b/onionr/static-data/default_config.json @@ -7,7 +7,8 @@ "socket_servers": false, "security_level": 0, "max_block_age": 2678400, - "public_key": "" + "public_key": "", + "use_new_api_server": false }, "www" : {