API reformat

This commit is contained in:
Kevin 2019-01-08 05:51:39 +00:00
parent 66da0b87cf
commit 5dd4022684
14 changed files with 541 additions and 619 deletions

View File

@ -19,40 +19,16 @@
''' '''
import flask import flask
from flask import request, Response, abort, send_from_directory from flask import request, Response, abort, send_from_directory
from multiprocessing import Process
from gevent.pywsgi import WSGIServer from gevent.pywsgi import WSGIServer
import sys, random, threading, hmac, hashlib, base64, time, math, os, json import sys, random, threading, hmac, hashlib, base64, time, math, os, json
import core import core
from onionrblockapi import Block from onionrblockapi import Block
import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config, onionr import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config, onionr
class API: def guessMime(path):
''' '''
Main HTTP API (Flask) Guesses the mime type of a file from the input filename
''' '''
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 = { mimetypes = {
'html' : 'text/html', 'html' : 'text/html',
'js' : 'application/javascript', 'js' : 'application/javascript',
@ -67,7 +43,178 @@ class API:
return 'text/plain' return 'text/plain'
def __init__(self, debug, API_VERSION): 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)
self.torAdder = clientAPI._core.hsAddress
self.i2pAdder = clientAPI._core.i2pAddress
self.bindPort = config.get('client.public.port')
logger.info('Running public api on %s:%s' % (self.host, self.bindPort))
@app.before_request
def validateRequest():
'''Validate request has the correct hostname'''
if type(self.torAdder) is None and type(self.i2pAdder) is None:
# abort if our hs addresses are not known
abort(403)
if request.host not in (self.i2pAdder, self.torAdder):
abort(403)
@app.after_request
def sendHeaders(resp):
'''Send api, access control headers'''
resp.headers['Date'] = 'Thu, 1 Jan 1970 00:00:00 GMT' # Clock info is probably useful to attackers. Set to unix epoch.
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'] = onionr.API_VERSION
return resp
@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
@app.route('/getblocklist')
def getBlockList():
bList = clientAPI._core.getBlockList()
for b in self.hideBlocks:
if b in bList:
bList.remove(b)
return Response('\n'.join(bList))
@app.route('/getdata/<name>')
def getBlockData(name):
resp = ''
data = name
if clientAPI._utils.validateHash(data):
if data not in self.hideBlocks:
if os.path.exists(clientAPI._core.dataDir + 'blocks/' + data + '.dat'):
block = Block(hash=data.encode(), core=clientAPI._core)
resp = base64.b64encode(block.getRaw().encode()).decode()
if len(resp) == 0:
abort(404)
resp = ""
return Response(resp)
@app.route('/www/<path:path>')
def wwwPublic(path):
if not config.get("www.public.run", True):
abort(403)
return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path)
@app.route('/ping')
def ping():
return Response("pong!")
@app.route('/getdbhash')
def getDBHash():
return Response(clientAPI._utils.getBlockDBHash())
@app.route('/pex')
def peerExchange():
response = ','.join(clientAPI._core.listAdders())
if len(response) == 0:
response = 'none'
return Response(response)
@app.route('/announce', methods=['post'])
def acceptAnnounce():
resp = 'failure'
powHash = ''
randomData = ''
newNode = ''
ourAdder = clientAPI._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 + clientAPI._core.hsAddress.encode()
nodes = clientAPI._core._crypto.blake2bHash(nodes)
powHash = clientAPI._core._crypto.blake2bHash(randomData + nodes)
try:
powHash = powHash.decode()
except AttributeError:
pass
if powHash.startswith('0000'):
try:
newNode = newNode.decode()
except AttributeError:
pass
if clientAPI._core.addAddress(newNode):
resp = 'Success'
else:
logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash)
resp = Response(resp)
return resp
@app.route('/upload', methods=['post'])
def upload():
resp = 'failure'
try:
data = request.form['block']
except KeyError:
logger.warn('No block specified for upload')
pass
else:
if sys.getsizeof(data) < 100000000:
try:
if blockimporter.importBlockFromData(data, clientAPI._core):
resp = 'success'
else:
logger.warn('Error encountered importing uploaded block')
except onionrexceptions.BlacklistedBlock:
logger.debug('uploaded block is blacklisted')
pass
if resp == 'failure':
abort(400)
resp = Response(resp)
return resp
clientAPI.setPublicAPIInstance(self)
while self.torAdder == '':
clientAPI._core.refreshFirstStartVars()
self.torAdder = clientAPI._core.hsAddress
time.sleep(1)
self.httpServer = WSGIServer((self.host, self.bindPort), app, log=None)
self.httpServer.serve_forever()
class API:
'''
Client HTTP api
'''
callbacks = {'public' : {}, 'private' : {}}
def __init__(self, onionrInst, debug, API_VERSION):
''' '''
Initialize the api server, preping variables for later use Initialize the api server, preping variables for later use
@ -84,51 +231,50 @@ class API:
self._crypto = onionrcrypto.OnionrCrypto(self._core) self._crypto = onionrcrypto.OnionrCrypto(self._core)
self._utils = onionrutils.OnionrUtils(self._core) self._utils = onionrutils.OnionrUtils(self._core)
app = flask.Flask(__name__) app = flask.Flask(__name__)
bindPort = int(config.get('client.port', 59496)) bindPort = int(config.get('client.client.port', 59496))
self.bindPort = bindPort self.bindPort = bindPort
self.clientToken = config.get('client.webpassword') self.clientToken = config.get('client.webpassword')
self.timeBypassToken = base64.b16encode(os.urandom(32)).decode() self.timeBypassToken = base64.b16encode(os.urandom(32)).decode()
self.i2pEnabled = config.get('i2p.host', False) 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.hideBlocks = [] # Blocks to be denied sharing self.host = setBindIP(self._core.privateApiHostFile)
logger.info('Running api on %s:%s' % (self.host, self.bindPort))
with open(self._core.dataDir + 'time-bypass.txt', 'w') as bypass: self.httpServer = ''
bypass.write(self.timeBypassToken) onionrInst.setClientAPIInst(self)
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 @app.before_request
def beforeReq(): def validateRequest():
''' '''Validate request has set password and is the correct hostname'''
Simply define the request as not having yet failed, before every request. if request.host != '%s:%s' % (self.host, self.bindPort):
''' abort(403)
self.requestFailed = False try:
return if not hmac.compare_digest(request.headers['token'], self.clientToken):
abort(403)
except KeyError:
abort(403)
@app.after_request @app.after_request
def afterReq(resp): 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["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-Frame-Options'] = 'deny'
resp.headers['X-Content-Type-Options'] = "nosniff" resp.headers['X-Content-Type-Options'] = "nosniff"
resp.headers['X-API'] = API_VERSION resp.headers['X-API'] = onionr.API_VERSION
resp.headers['Server'] = ''
resp.headers['Date'] = 'Thu, 1 Jan 1970 00:00:00 GMT' # Clock info is probably useful to attackers. Set to unix epoch. resp.headers['Date'] = 'Thu, 1 Jan 1970 00:00:00 GMT' # Clock info is probably useful to attackers. Set to unix epoch.
return resp return resp
@app.route('/site/<path:block>') @app.route('/ping')
def site(block): def ping():
self.validateHost('private') return Response("pong!")
@app.route('/')
def hello():
return Response("hello client")
@app.route('/site/<name>')
def site():
bHash = block bHash = block
resp = 'Not Found' resp = 'Not Found'
if self._core._utils.validateHash(bHash): if self._core._utils.validateHash(bHash):
@ -137,467 +283,47 @@ class API:
resp = base64.b64decode(resp) resp = base64.b64decode(resp)
except: except:
pass pass
if resp == 'Not Found':
abourt(404)
return Response(resp) return Response(resp)
@app.route('/www/private/<path:path>') @app.route('/waitforshare/<name>', methods=['post'])
def www_private(path): def waitforshare():
startTime = math.floor(time.time()) assert name.isalnum()
if name in self.publicAPI.hideBlocks:
if request.args.get('timingToken') is None: self.publicAPI.hideBlocks.remove(name)
timingToken = '' return Response("removed")
else: else:
timingToken = request.args.get('timingToken') self.publicAPI.hideBlocks.append(name)
return Response("added")
if not config.get("www.private.run", True): @app.route('/shutdown')
abort(403) def shutdown():
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/<path:path>')
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/<path:path>')
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: try:
data = data self.publicAPI.httpServer.stop()
except: self.httpServer.stop()
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
@app.route('/')
def banner():
self.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
@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')
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: except AttributeError:
pass pass
if powHash.startswith('0000'): return Response("bye")
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/') self.httpServer = WSGIServer((self.host, bindPort), app, log=None)
def public_handler(): self.httpServer.serve_forever()
# 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}) def setPublicAPIInstance(self, inst):
assert isinstance(inst, PublicAPI)
self.publicAPI = inst
if action == 'firstConnect': def validateToken(self, token):
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)
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: Validate that the client token matches the given token
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: if len(self.clientToken) == 0:
return logger.error("client password needs to be set")
# 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 return False
try:
API.callbacks[scope][action] = callback if not hmac.compare_digest(self.clientToken, token):
return True
def removeCallback(action, scope = 'public'):
if (not scope in API.callbacks) or (not action in API.callbacks[scope]):
return False return False
else:
del API.callbacks[scope][action]
return True return True
except TypeError:
def getCallback(action, scope = 'public'): return False
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

75
onionr/apimanager.py Normal file
View File

@ -0,0 +1,75 @@
'''
Onionr - P2P Anonymous Storage Network
Handles api data exchange, interfaced by both public and client http api
'''
'''
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 config, apipublic, apiprivate, core, socket, random, threading, time
config.reload()
PRIVATE_API_VERSION = 0
PUBLIC_API_VERSION = 1
DEV_MODE = config.get('general.dev_mode')
def getOpenPort():
'''Get a random open port'''
p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.bind(("127.0.0.1",0))
p.listen(1)
port = p.getsockname()[1]
p.close()
return port
def getRandomLocalIP():
'''Get a random local ip address'''
hostOctets = [str(127), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF))]
host = '.'.join(hostOctets)
return host
class APIManager:
def __init__(self, coreInst):
assert isinstance(coreInst, core.Core)
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
publicIP = '127.0.0.1'
privateIP = '127.0.0.1'
if DEV_MODE:
# set private and local api servers bind IPs to random localhost (127.x.x.x), make sure not the same
privateIP = getRandomLocalIP()
while True:
publicIP = getRandomLocalIP()
if publicIP != privateIP:
break
# Make official the IPs and Ports
self.publicIP = publicIP
self.privateIP = privateIP
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)
self.privateAPI = apiprivate.APIPrivate(self)
threading.Thread(target=self.publicAPI.run).start()
threading.Thread(target=self.privateAPI.run).start()
while not self.shutdown:
time.sleep(1)

32
onionr/apiprivate.py Normal file
View File

@ -0,0 +1,32 @@
'''
Onionr - P2P Anonymous Storage Network
Handle incoming commands from the client. Intended for localhost use
'''
'''
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 flask, apimanager
from flask import request, Response, abort, send_from_directory
from gevent.pywsgi import WSGIServer
class APIPrivate:
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.privateIP, managerInst.privatePort), self.app, log=None)
def run(self):
self.httpServer.serve_forever()
return

41
onionr/apipublic.py Normal file
View File

@ -0,0 +1,41 @@
'''
Onionr - P2P Anonymous Storage Network
Handle incoming commands from other Onionr nodes, over HTTP
'''
'''
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 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)
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

View File

@ -22,6 +22,7 @@
import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid 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 onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block
import onionrdaemontools, onionrsockets, onionrchat, onionr, onionrproofs import onionrdaemontools, onionrsockets, onionrchat, onionr, onionrproofs
import binascii
from dependencies import secrets from dependencies import secrets
from defusedxml import minidom from defusedxml import minidom
@ -101,6 +102,7 @@ class OnionrCommunicatorDaemon:
OnionrCommunicatorTimers(self, self.daemonTools.cooldownPeer, 30, requiresPeer=True) OnionrCommunicatorTimers(self, self.daemonTools.cooldownPeer, 30, requiresPeer=True)
OnionrCommunicatorTimers(self, self.uploadBlock, 10, requiresPeer=True, maxThreads=1) OnionrCommunicatorTimers(self, self.uploadBlock, 10, requiresPeer=True, maxThreads=1)
OnionrCommunicatorTimers(self, self.daemonCommands, 6, maxThreads=1) OnionrCommunicatorTimers(self, self.daemonCommands, 6, maxThreads=1)
OnionrCommunicatorTimers(self, self.detectAPICrash, 5, maxThreads=1)
deniableBlockTimer = OnionrCommunicatorTimers(self, self.daemonTools.insertDeniableBlock, 180, requiresPeer=True, maxThreads=1) deniableBlockTimer = OnionrCommunicatorTimers(self, self.daemonTools.insertDeniableBlock, 180, requiresPeer=True, maxThreads=1)
netCheckTimer = OnionrCommunicatorTimers(self, self.daemonTools.netCheck, 600) netCheckTimer = OnionrCommunicatorTimers(self, self.daemonTools.netCheck, 600)
@ -178,14 +180,14 @@ class OnionrCommunicatorDaemon:
break break
else: else:
continue continue
newDBHash = self.peerAction(peer, 'getDBHash') # get their db hash newDBHash = self.peerAction(peer, 'getdbhash') # get their db hash
if newDBHash == False or not self._core._utils.validateHash(newDBHash): if newDBHash == False or not self._core._utils.validateHash(newDBHash):
continue # if request failed, restart loop (peer is added to offline peers automatically) continue # if request failed, restart loop (peer is added to offline peers automatically)
triedPeers.append(peer) triedPeers.append(peer)
if newDBHash != self._core.getAddressInfo(peer, 'DBHash'): if newDBHash != self._core.getAddressInfo(peer, 'DBHash'):
self._core.setAddressInfo(peer, 'DBHash', newDBHash) self._core.setAddressInfo(peer, 'DBHash', newDBHash)
try: try:
newBlocks = self.peerAction(peer, 'getBlockHashes') # get list of new block hashes newBlocks = self.peerAction(peer, 'getblocklist') # get list of new block hashes
except Exception as error: except Exception as error:
logger.warn('Could not get new blocks from %s.' % peer, error = error) logger.warn('Could not get new blocks from %s.' % peer, error = error)
newBlocks = False newBlocks = False
@ -224,13 +226,16 @@ class OnionrCommunicatorDaemon:
self.currentDownloading.append(blockHash) # So we can avoid concurrent downloading in other threads of same block self.currentDownloading.append(blockHash) # So we can avoid concurrent downloading in other threads of same block
logger.info("Attempting to download %s..." % blockHash) logger.info("Attempting to download %s..." % blockHash)
peerUsed = self.pickOnlinePeer() peerUsed = self.pickOnlinePeer()
content = self.peerAction(peerUsed, 'getData', data=blockHash) # block content from random peer (includes metadata) content = self.peerAction(peerUsed, 'getdata/' + blockHash) # block content from random peer (includes metadata)
if content != False and len(content) > 0: if content != False and len(content) > 0:
try: try:
content = content.encode() content = content.encode()
except AttributeError: except AttributeError:
pass pass
try:
content = base64.b64decode(content) # content is base64 encoded in transport content = base64.b64decode(content) # content is base64 encoded in transport
except binascii.Error:
pass
realHash = self._core._crypto.sha3Hash(content) realHash = self._core._crypto.sha3Hash(content)
try: try:
realHash = realHash.decode() # bytes on some versions for some reason realHash = realHash.decode() # bytes on some versions for some reason
@ -421,7 +426,7 @@ class OnionrCommunicatorDaemon:
if len(peer) == 0: if len(peer) == 0:
return False return False
#logger.debug('Performing ' + action + ' with ' + peer + ' on port ' + str(self.proxyPort)) #logger.debug('Performing ' + action + ' with ' + peer + ' on port ' + str(self.proxyPort))
url = 'http://' + peer + '/public/?action=' + action url = 'http://%s/%s' % (peer, action)
if len(data) > 0: if len(data) > 0:
url += '&data=' + data url += '&data=' + data
@ -518,7 +523,7 @@ class OnionrCommunicatorDaemon:
if peer in triedPeers: if peer in triedPeers:
continue continue
triedPeers.append(peer) triedPeers.append(peer)
url = 'http://' + peer + '/public/upload/' url = 'http://' + peer + '/upload'
data = {'block': block.Block(bl).getRaw()} data = {'block': block.Block(bl).getRaw()}
proxyType = '' proxyType = ''
if peer.endswith('.onion'): if peer.endswith('.onion'):
@ -527,7 +532,7 @@ class OnionrCommunicatorDaemon:
proxyType = 'i2p' proxyType = 'i2p'
logger.info("Uploading block to " + peer) logger.info("Uploading block to " + peer)
if not self._core._utils.doPostRequest(url, data=data, proxyType=proxyType) == False: if not self._core._utils.doPostRequest(url, data=data, proxyType=proxyType) == False:
self._core._utils.localCommand('waitForShare', data=bl) self._core._utils.localCommand('waitforshare/' + bl)
finishedUploads.append(bl) finishedUploads.append(bl)
break break
for x in finishedUploads: for x in finishedUploads:
@ -544,9 +549,9 @@ class OnionrCommunicatorDaemon:
def detectAPICrash(self): def detectAPICrash(self):
'''exit if the api server crashes/stops''' '''exit if the api server crashes/stops'''
if self._core._utils.localCommand('ping', silent=False) != 'pong': if self._core._utils.localCommand('ping', silent=False) not in ('pong', 'pong!'):
for i in range(5): for i in range(5):
if self._core._utils.localCommand('ping') == 'pong': if self._core._utils.localCommand('ping') in ('pong', 'pong!'):
break # break for loop break # break for loop
time.sleep(1) time.sleep(1)
else: else:

View File

@ -49,8 +49,11 @@ class Core:
self.peerDB = self.dataDir + 'peers.db' self.peerDB = self.dataDir + 'peers.db'
self.blockDB = self.dataDir + 'blocks.db' self.blockDB = self.dataDir + 'blocks.db'
self.blockDataLocation = self.dataDir + 'blocks/' 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.addressDB = self.dataDir + 'address.db'
self.hsAddress = '' self.hsAddress = ''
self.i2pAddress = config.get('i2p.ownAddr', None)
self.bootstrapFileLocation = 'static-data/bootstrap-nodes.txt' self.bootstrapFileLocation = 'static-data/bootstrap-nodes.txt'
self.bootstrapList = [] self.bootstrapList = []
self.requirements = onionrvalues.OnionrValues() self.requirements = onionrvalues.OnionrValues()
@ -151,7 +154,7 @@ class Core:
if address == config.get('i2p.ownAddr', None) or address == self.hsAddress: if address == config.get('i2p.ownAddr', None) or address == self.hsAddress:
return False return False
if type(address) is type(None) or len(address) == 0: if type(address) is None or len(address) == 0:
return False return False
if self._utils.validateID(address): if self._utils.validateID(address):
conn = sqlite3.connect(self.addressDB, timeout=10) conn = sqlite3.connect(self.addressDB, timeout=10)
@ -257,7 +260,7 @@ class Core:
return return
conn = sqlite3.connect(self.blockDB, timeout=10) conn = sqlite3.connect(self.blockDB, timeout=10)
c = conn.cursor() c = conn.cursor()
currentTime = self._utils.getEpoch() currentTime = self._utils.getEpoch() + self._crypto.secrets.randbelow(301)
if selfInsert or dataSaved: if selfInsert or dataSaved:
selfInsert = 1 selfInsert = 1
else: else:
@ -760,7 +763,7 @@ class Core:
metadata['meta'] = jsonMeta metadata['meta'] = jsonMeta
metadata['sig'] = signature metadata['sig'] = signature
metadata['signer'] = signer metadata['signer'] = signer
metadata['time'] = self._utils.getRoundedEpoch() + self._crypto.secrets.randbelow(301) metadata['time'] = self._utils.getRoundedEpoch()
# ensure expire is integer and of sane length # ensure expire is integer and of sane length
if type(expire) is not type(None): if type(expire) is not type(None):
@ -773,7 +776,7 @@ class Core:
if payload != False: if payload != False:
retData = self.setData(payload) retData = self.setData(payload)
# Tell the api server through localCommand to wait for the daemon to upload this block to make stastical analysis more difficult # Tell the api server through localCommand to wait for the daemon to upload this block to make stastical analysis more difficult
self._utils.localCommand('waitForShare', data=retData) self._utils.localCommand('waitforshare/' + retData)
self.addToBlockDB(retData, selfInsert=True, dataSaved=True) self.addToBlockDB(retData, selfInsert=True, dataSaved=True)
#self.setBlockType(retData, meta['type']) #self.setBlockType(retData, meta['type'])
self._utils.processBlockMetadata(retData) self._utils.processBlockMetadata(retData)

View File

@ -18,10 +18,19 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
''' '''
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 stem.control import Controller
from onionrblockapi import Block from onionrblockapi import Block
from dependencies import secrets 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: class NetController:
''' '''
This class handles hidden service setup on Tor and I2P This class handles hidden service setup on Tor and I2P
@ -37,7 +46,7 @@ class NetController:
self.torConfigLocation = self.dataDir + 'torrc' self.torConfigLocation = self.dataDir + 'torrc'
self.readyState = False self.readyState = False
self.socksPort = random.randint(1024, 65535) self.socksPort = getOpenPort()
self.hsPort = hsPort self.hsPort = hsPort
self._torInstnace = '' self._torInstnace = ''
self.myID = '' self.myID = ''
@ -68,7 +77,7 @@ class NetController:
hsVer = '# v2 onions' hsVer = '# v2 onions'
if config.get('tor.v3onions'): if config.get('tor.v3onions'):
hsVer = 'HiddenServiceVersion 3' hsVer = 'HiddenServiceVersion 3'
logger.debug('Using v3 onions :)') logger.debug('Using v3 onions')
if os.path.exists(self.torConfigLocation): if os.path.exists(self.torConfigLocation):
os.remove(self.torConfigLocation) os.remove(self.torConfigLocation)
@ -78,7 +87,7 @@ class NetController:
config.set('tor.controlpassword', plaintext, savefile=True) config.set('tor.controlpassword', plaintext, savefile=True)
config.set('tor.socksport', self.socksPort, savefile=True) config.set('tor.socksport', self.socksPort, savefile=True)
controlPort = random.randint(1025, 65535) controlPort = getOpenPort()
config.set('tor.controlPort', controlPort, savefile=True) config.set('tor.controlPort', controlPort, savefile=True)

View File

@ -20,7 +20,6 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
''' '''
import sys import sys
if sys.version_info[0] == 2 or sys.version_info[1] < 5: if sys.version_info[0] == 2 or sys.version_info[1] < 5:
print('Error, Onionr requires Python 3.5+') print('Error, Onionr requires Python 3.5+')
@ -30,6 +29,7 @@ import webbrowser
from threading import Thread from threading import Thread
import api, core, config, logger, onionrplugins as plugins, onionrevents as events import api, core, config, logger, onionrplugins as plugins, onionrevents as events
import onionrutils import onionrutils
import netcontroller
from netcontroller import NetController from netcontroller import NetController
from onionrblockapi import Block from onionrblockapi import Block
import onionrproofs, onionrexceptions, onionrusers import onionrproofs, onionrexceptions, onionrusers
@ -67,8 +67,12 @@ class Onionr:
data_exists = Onionr.setupConfig(self.dataDir, self = self) data_exists = Onionr.setupConfig(self.dataDir, self = self)
self.onionrCore = core.Core() self.onionrCore = core.Core()
#self.deleteRunFiles()
self.onionrUtils = onionrutils.OnionrUtils(self.onionrCore) self.onionrUtils = onionrutils.OnionrUtils(self.onionrCore)
self.clientAPIInst = '' # Client http api instance
self.publicAPIInst = '' # Public http api instance
# Handle commands # Handle commands
self.debug = False # Whole application debugging self.debug = False # Whole application debugging
@ -105,11 +109,12 @@ class Onionr:
# Get configuration # Get configuration
if type(config.get('client.webpassword')) is type(None): if type(config.get('client.webpassword')) is type(None):
config.set('client.webpassword', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True) config.set('client.webpassword', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True)
if type(config.get('client.port')) is type(None): if type(config.get('client.client.port')) is type(None):
randomPort = 0 randomPort = netcontroller.getOpenPort()
while randomPort < 1024: config.set('client.client.port', randomPort, savefile=True)
randomPort = self.onionrCore._crypto.secrets.randbelow(65535) if type(config.get('client.public.port')) is type(None):
config.set('client.port', randomPort, savefile=True) randomPort = netcontroller.getOpenPort()
config.set('client.public.port', randomPort, savefile=True)
if type(config.get('client.participate')) is type(None): if type(config.get('client.participate')) is type(None):
config.set('client.participate', True, savefile=True) config.set('client.participate', True, savefile=True)
if type(config.get('client.api_version')) is type(None): if type(config.get('client.api_version')) is type(None):
@ -176,6 +181,7 @@ class Onionr:
'getfile': self.getFile, 'getfile': self.getFile,
'listconn': self.listConn, 'listconn': self.listConn,
'list-conn': self.listConn,
'import-blocks': self.onionrUtils.importNewBlocks, 'import-blocks': self.onionrUtils.importNewBlocks,
'importblocks': self.onionrUtils.importNewBlocks, 'importblocks': self.onionrUtils.importNewBlocks,
@ -395,6 +401,16 @@ class Onionr:
logger.info('Syntax: friend add/remove/list [address]') logger.info('Syntax: friend add/remove/list [address]')
def deleteRunFiles(self):
try:
os.remove(self.onionrCore.publicApiHostFile)
except FileNotFoundError:
pass
try:
os.remove(self.onionrCore.privateApiHostFile)
except FileNotFoundError:
pass
def banBlock(self): def banBlock(self):
try: try:
ban = sys.argv[2] ban = sys.argv[2]
@ -691,6 +707,13 @@ class Onionr:
os.remove('.onionr-lock') os.remove('.onionr-lock')
except FileNotFoundError: except FileNotFoundError:
pass pass
def setClientAPIInst(self, inst):
self.clientAPIInst = inst
def getClientApi(self):
while self.clientAPIInst == '':
time.sleep(0.5)
return self.clientAPIInst
def daemon(self): def daemon(self):
''' '''
@ -704,28 +727,28 @@ class Onionr:
logger.debug('Runcheck file found on daemon start, deleting in advance.') logger.debug('Runcheck file found on daemon start, deleting in advance.')
os.remove('data/.runcheck') os.remove('data/.runcheck')
apiThread = Thread(target = api.API, args = (self.debug, API_VERSION)) Thread(target=api.API, args=(self, self.debug, API_VERSION)).start()
apiThread.start() Thread(target=api.PublicAPI, args=[self.getClientApi()]).start()
try: try:
time.sleep(3) time.sleep(0)
except KeyboardInterrupt: except KeyboardInterrupt:
logger.debug('Got keyboard interrupt, shutting down...') logger.debug('Got keyboard interrupt, shutting down...')
time.sleep(1) time.sleep(1)
self.onionrUtils.localCommand('shutdown') self.onionrUtils.localCommand('shutdown')
else:
apiHost = '127.0.0.1' apiHost = ''
if apiThread.isAlive(): while apiHost == '':
try: try:
with open(self.onionrCore.dataDir + 'host.txt', 'r') as hostFile: with open(self.onionrCore.publicApiHostFile, 'r') as hostFile:
apiHost = hostFile.read() apiHost = hostFile.read()
except FileNotFoundError: except FileNotFoundError:
pass pass
time.sleep(0.5)
Onionr.setupConfig('data/', self = self) Onionr.setupConfig('data/', self = self)
if self._developmentMode: if self._developmentMode:
logger.warn('DEVELOPMENT MODE ENABLED (LESS SECURE)', timestamp = False) 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...') logger.debug('Tor is starting...')
if not net.startTor(): if not net.startTor():
self.onionrUtils.localCommand('shutdown') self.onionrUtils.localCommand('shutdown')
@ -761,6 +784,9 @@ class Onionr:
except KeyboardInterrupt: except KeyboardInterrupt:
self.onionrCore.daemonQueueAdd('shutdown') self.onionrCore.daemonQueueAdd('shutdown')
self.onionrUtils.localCommand('shutdown') self.onionrUtils.localCommand('shutdown')
time.sleep(3)
self.deleteRunFiles()
net.killTor()
return return
def killDaemon(self): def killDaemon(self):

View File

@ -45,7 +45,7 @@ class DaemonTools:
ourID = self.daemon._core.hsAddress.strip() ourID = self.daemon._core.hsAddress.strip()
url = 'http://' + peer + '/public/announce/' url = 'http://' + peer + '/announce'
data = {'node': ourID} data = {'node': ourID}
combinedNodes = ourID + peer combinedNodes = ourID + peer

View File

@ -79,12 +79,14 @@ def peerCleanup(coreInst):
logger.info('Cleaning peers...') logger.info('Cleaning peers...')
config.reload() config.reload()
minScore = int(config.get('peers.minimum_score', -100))
maxPeers = int(config.get('peers.max_stored', 5000))
adders = getScoreSortedPeerList(coreInst) adders = getScoreSortedPeerList(coreInst)
adders.reverse() adders.reverse()
if len(adders) > 1:
minScore = int(config.get('peers.minimum_score', -100))
maxPeers = int(config.get('peers.max_stored', 5000))
for address in adders: for address in adders:
# Remove peers that go below the negative score # Remove peers that go below the negative score
if PeerProfiles(address, coreInst).score < minScore: if PeerProfiles(address, coreInst).score < minScore:

View File

@ -159,16 +159,19 @@ class OnionrUtils:
config.reload() config.reload()
self.getTimeBypassToken() self.getTimeBypassToken()
# TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless. # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless.
hostname = ''
while hostname == '':
try: try:
with open(self._core.dataDir + 'host.txt', 'r') as host: with open(self._core.privateApiHostFile, 'r') as host:
hostname = host.read() hostname = host.read()
except FileNotFoundError: except FileNotFoundError:
return False print('wat')
payload = 'http://%s:%s/client/?action=%s&token=%s&timingToken=%s' % (hostname, config.get('client.port'), command, config.get('client.webpassword'), self.timingToken) time.sleep(1)
if data != '': 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)
try: try:
retData = requests.get(payload).text retData = requests.get(payload, headers={'token': config.get('client.webpassword')}).text
except Exception as error: except Exception as error:
if not silent: if not silent:
logger.error('Failed to make local request (command: %s):%s' % (command, error)) logger.error('Failed to make local request (command: %s):%s' % (command, error))

View File

@ -7,7 +7,8 @@
"socket_servers": false, "socket_servers": false,
"security_level": 0, "security_level": 0,
"max_block_age": 2678400, "max_block_age": 2678400,
"public_key": "" "public_key": "",
"use_new_api_server": false
}, },
"www" : { "www" : {

View File

@ -1,7 +1,7 @@
<h1>This is an Onionr Node</h1> <h1>This is an Onionr Node</h1>
<p>The content on this server is not necessarily created by the server owner, and was not necessarily stored specifically with the owner's knowledge of its contents.</p> <p>The content on this server was not necessarily intentionally stored or created by the owner(s) of this server.</p>
<p>Onionr is a decentralized data storage system that anyone can insert data into.</p> <p>Onionr is a decentralized peer-to-peer data storage system.</p>
<p>To learn more about Onionr, see the website at <a href="https://onionr.voidnet.tech/">https://Onionr.VoidNet.tech/</a></p> <p>To learn more about Onionr, see the website at <a href="https://onionr.voidnet.tech/">https://Onionr.VoidNet.tech/</a></p>

View File

@ -2,7 +2,6 @@ urllib3==1.23
requests==2.20.0 requests==2.20.0
PyNaCl==1.2.1 PyNaCl==1.2.1
gevent==1.3.6 gevent==1.3.6
sha3==0.2.1
defusedxml==0.5.0 defusedxml==0.5.0
Flask==1.0.2 Flask==1.0.2
PySocks==1.6.8 PySocks==1.6.8