diff --git a/onionr/api.py b/onionr/api.py index afe63d82..e2a6604c 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -64,19 +64,99 @@ class 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') + self.torAdder = clientAPI._core.hsAddress + self.i2pAdder = clientAPI._core.i2pAddress + self.bindPort = config.get('client.public.port') + + @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'] = API_VERSION + return resp @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 + + @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/') + def getBlockData(): + resp = '' + 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('/ping') + def ping(): + return Response("pong!") + + @app.route('/getdbhash') + def getDBHash(): + return Response(clientAPI._utils.getBlockDBHash()) + + @app.route('/pex') + def peerExchange(): + response = ','.join(self._core.listAdders()) + if len(response) == 0: + response = 'none' + resp = Response(response) + + @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) - self.httpServer = WSGIServer((self.host, bindPort), app, log=None) + self.httpServer = WSGIServer((self.host, self.bindPort), app, log=None) self.httpServer.serve_forever() class API: @@ -143,8 +223,22 @@ class API: @app.route('/') def hello(): return Response("hello client") - - @app.route('/waitforshare/', methods='post') + + @app.route('/site/') + def site(): + bHash = block + resp = 'Not Found' + if self._core._utils.validateHash(bHash): + resp = Block(bHash).bcontent + try: + resp = base64.b64decode(resp) + except: + pass + if resp == 'Not Found': + abourt(404) + return Response(resp) + + @app.route('/waitforshare/', methods=['post']) def waitforshare(): assert name.isalnum() if name in self.publicAPI.hideBlocks: diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 5b6f8ba9..fa14864e 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -180,14 +180,14 @@ class OnionrCommunicatorDaemon: break else: 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): continue # if request failed, restart loop (peer is added to offline peers automatically) triedPeers.append(peer) if newDBHash != self._core.getAddressInfo(peer, 'DBHash'): self._core.setAddressInfo(peer, 'DBHash', newDBHash) 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: logger.warn('Could not get new blocks from %s.' % peer, error = error) newBlocks = False @@ -226,7 +226,7 @@ class OnionrCommunicatorDaemon: self.currentDownloading.append(blockHash) # So we can avoid concurrent downloading in other threads of same block logger.info("Attempting to download %s..." % blockHash) 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: try: content = content.encode() @@ -423,7 +423,7 @@ class OnionrCommunicatorDaemon: if len(peer) == 0: return False #logger.debug('Performing ' + action + ' with ' + peer + ' on port ' + str(self.proxyPort)) - url = 'http://' + peer + '/public/?action=' + action + url = 'http://' + peer + '/' + action if len(data) > 0: url += '&data=' + data @@ -520,7 +520,7 @@ class OnionrCommunicatorDaemon: if peer in triedPeers: continue triedPeers.append(peer) - url = 'http://' + peer + '/public/upload/' + url = 'http://' + peer + '/upload' data = {'block': block.Block(bl).getRaw()} proxyType = '' if peer.endswith('.onion'): @@ -529,7 +529,7 @@ class OnionrCommunicatorDaemon: proxyType = 'i2p' logger.info("Uploading block to " + peer) 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) break for x in finishedUploads: diff --git a/onionr/core.py b/onionr/core.py index f463dd35..d2d55811 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -53,6 +53,7 @@ class Core: self.privateApiHostFile = self.dataDir + 'private-host.txt' self.addressDB = self.dataDir + 'address.db' self.hsAddress = '' + self.i2pAddress = config.get('i2p.ownAddr', None) self.bootstrapFileLocation = 'static-data/bootstrap-nodes.txt' self.bootstrapList = [] self.requirements = onionrvalues.OnionrValues() @@ -775,7 +776,7 @@ class Core: if payload != False: 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 - self._utils.localCommand('waitForShare', data=retData) + self._utils.localCommand('waitforshare/' + retData) self.addToBlockDB(retData, selfInsert=True, dataSaved=True) #self.setBlockType(retData, meta['type']) self._utils.processBlockMetadata(retData)