Merge Tempblocks
This commit is contained in:
parent
f15d00f61f
commit
44d545684a
2
.gitignore
vendored
2
.gitignore
vendored
@ -13,3 +13,5 @@ onionr/data-encrypted.dat
|
||||
onionr/.onionr-lock
|
||||
core
|
||||
.vscode/*
|
||||
venv/*
|
||||
onionr/fs*
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "onionr/bitpeer"]
|
||||
path = onionr/bitpeer
|
||||
url = https://github.com/beardog108/bitpeer.py
|
@ -4,7 +4,7 @@ FROM ubuntu:bionic
|
||||
ENV HOME /root
|
||||
|
||||
#Install needed packages
|
||||
RUN apt update && apt install -y python3 python3-dev python3-pip tor locales nano
|
||||
RUN apt update && apt install -y python3 python3-dev python3-pip tor locales nano sqlite3
|
||||
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
locale-gen
|
||||
|
@ -1,3 +1,4 @@
|
||||
#!/bin/sh
|
||||
cd "$(dirname "$0")"
|
||||
cd onionr/
|
||||
./onionr.py "$@"
|
||||
|
@ -1,5 +1,5 @@
|
||||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This file handles all incoming http requests to the client, using Flask
|
||||
'''
|
||||
@ -20,9 +20,9 @@
|
||||
import flask
|
||||
from flask import request, Response, abort, send_from_directory
|
||||
from multiprocessing import Process
|
||||
from gevent.wsgi import WSGIServer
|
||||
from gevent.pywsgi import WSGIServer
|
||||
import sys, random, threading, hmac, hashlib, base64, time, math, os, json
|
||||
from core import Core
|
||||
import core
|
||||
from onionrblockapi import Block
|
||||
import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config
|
||||
|
||||
@ -37,6 +37,9 @@ class API:
|
||||
'''
|
||||
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
|
||||
@ -69,7 +72,7 @@ class API:
|
||||
logger.debug('%s not in %s' % (path, mimetypes))
|
||||
return 'text/plain'
|
||||
|
||||
def __init__(self, debug):
|
||||
def __init__(self, debug, API_VERSION):
|
||||
'''
|
||||
Initialize the api server, preping variables for later use
|
||||
|
||||
@ -88,7 +91,7 @@ class API:
|
||||
|
||||
self.debug = debug
|
||||
self._privateDelayTime = 3
|
||||
self._core = Core()
|
||||
self._core = core.Core()
|
||||
self._crypto = onionrcrypto.OnionrCrypto(self._core)
|
||||
self._utils = onionrutils.OnionrUtils(self._core)
|
||||
app = flask.Flask(__name__)
|
||||
@ -102,7 +105,7 @@ class API:
|
||||
self.mimeType = 'text/plain'
|
||||
self.overrideCSP = False
|
||||
|
||||
with open('data/time-bypass.txt', 'w') as bypass:
|
||||
with open(self._core.dataDir + 'time-bypass.txt', 'w') as bypass:
|
||||
bypass.write(self.timeBypassToken)
|
||||
|
||||
if not debug and not self._developmentMode:
|
||||
@ -111,7 +114,7 @@ class API:
|
||||
else:
|
||||
self.host = '127.0.0.1'
|
||||
|
||||
with open('data/host.txt', 'w') as file:
|
||||
with open(self._core.dataDir + 'host.txt', 'w') as file:
|
||||
file.write(self.host)
|
||||
|
||||
@app.before_request
|
||||
@ -133,14 +136,14 @@ class API:
|
||||
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['server'] = 'Onionr'
|
||||
resp.headers['api'] = API_VERSION
|
||||
|
||||
# reset to text/plain to help prevent browser attacks
|
||||
self.mimeType = 'text/plain'
|
||||
self.overrideCSP = False
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
@app.route('/www/private/<path:path>')
|
||||
def www_private(path):
|
||||
startTime = math.floor(time.time())
|
||||
@ -466,7 +469,7 @@ class API:
|
||||
elif action == 'getData':
|
||||
resp = ''
|
||||
if self._utils.validateHash(data):
|
||||
if os.path.exists('data/blocks/' + data + '.dat'):
|
||||
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:
|
||||
@ -515,7 +518,7 @@ class API:
|
||||
while len(self._core.hsAddress) == 0:
|
||||
self._core.refreshFirstStartVars()
|
||||
time.sleep(0.5)
|
||||
self.http_server = WSGIServer((self.host, bindPort), app)
|
||||
self.http_server = WSGIServer((self.host, bindPort), app, log=None)
|
||||
self.http_server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network.
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This file contains both the OnionrCommunicate class for communcating with peers
|
||||
and code to operate as a daemon, getting commands from the command queue database (see core.Core.daemonQueue)
|
||||
@ -19,9 +19,10 @@
|
||||
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 sys, os, core, config, json, requests, time, logger, threading, base64, onionr
|
||||
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
|
||||
import onionrdaemontools, onionrsockets, onionrchat
|
||||
from dependencies import secrets
|
||||
from defusedxml import minidom
|
||||
|
||||
class OnionrCommunicatorDaemon:
|
||||
@ -51,6 +52,8 @@ class OnionrCommunicatorDaemon:
|
||||
# lists of connected peers and peers we know we can't reach currently
|
||||
self.onlinePeers = []
|
||||
self.offlinePeers = []
|
||||
self.cooldownPeer = {}
|
||||
self.connectTimes = {}
|
||||
self.peerProfiles = [] # list of peer's profiles (onionrpeers.PeerProfile instances)
|
||||
|
||||
# amount of threads running by name, used to prevent too many
|
||||
@ -77,29 +80,39 @@ class OnionrCommunicatorDaemon:
|
||||
#self.daemonTools = onionrdaemontools.DaemonTools(self)
|
||||
self.daemonTools = onionrdaemontools.DaemonTools(self)
|
||||
|
||||
self._chat = onionrchat.OnionrChat(self)
|
||||
|
||||
if debug or developmentMode:
|
||||
OnionrCommunicatorTimers(self, self.heartbeat, 10)
|
||||
OnionrCommunicatorTimers(self, self.heartbeat, 30)
|
||||
|
||||
# Set timers, function reference, seconds
|
||||
# requiresPeer True means the timer function won't fire if we have no connected peers
|
||||
# TODO: make some of these timer counts configurable
|
||||
OnionrCommunicatorTimers(self, self.daemonCommands, 5)
|
||||
OnionrCommunicatorTimers(self, self.detectAPICrash, 5)
|
||||
peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60)
|
||||
peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60, maxThreads=1)
|
||||
OnionrCommunicatorTimers(self, self.lookupBlocks, self._core.config.get('timers.lookupBlocks'), requiresPeer=True, maxThreads=1)
|
||||
OnionrCommunicatorTimers(self, self.getBlocks, self._core.config.get('timers.getBlocks'), requiresPeer=True)
|
||||
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58)
|
||||
OnionrCommunicatorTimers(self, self.daemonTools.cleanOldBlocks, 65)
|
||||
OnionrCommunicatorTimers(self, self.lookupKeys, 60, requiresPeer=True)
|
||||
OnionrCommunicatorTimers(self, self.lookupAdders, 60, requiresPeer=True)
|
||||
OnionrCommunicatorTimers(self, self.daemonTools.cooldownPeer, 30, requiresPeer=True)
|
||||
netCheckTimer = OnionrCommunicatorTimers(self, self.daemonTools.netCheck, 600)
|
||||
announceTimer = OnionrCommunicatorTimers(self, self.daemonTools.announceNode, 305, requiresPeer=True, maxThreads=1)
|
||||
cleanupTimer = OnionrCommunicatorTimers(self, self.peerCleanup, 300, requiresPeer=True)
|
||||
forwardSecrecyTimer = OnionrCommunicatorTimers(self, self.daemonTools.cleanKeys, 15)
|
||||
|
||||
# set loop to execute instantly to load up peer pool (replaced old pool init wait)
|
||||
peerPoolTimer.count = (peerPoolTimer.frequency - 1)
|
||||
cleanupTimer.count = (cleanupTimer.frequency - 60)
|
||||
announceTimer.count = (cleanupTimer.frequency - 60)
|
||||
#forwardSecrecyTimer.count = (forwardSecrecyTimer.frequency - 990)
|
||||
|
||||
self.socketServer = threading.Thread(target=onionrsockets.OnionrSocketServer, args=(self._core,))
|
||||
self.socketServer.start()
|
||||
self.socketClient = onionrsockets.OnionrSocketClient(self._core)
|
||||
|
||||
# Loads chat messages into memory
|
||||
threading.Thread(target=self._chat.chatHandler).start()
|
||||
|
||||
# Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking
|
||||
try:
|
||||
@ -114,21 +127,10 @@ class OnionrCommunicatorDaemon:
|
||||
pass
|
||||
|
||||
logger.info('Goodbye.')
|
||||
self._core.killSockets = True
|
||||
self._core._utils.localCommand('shutdown') # shutdown the api
|
||||
time.sleep(0.5)
|
||||
|
||||
def lookupKeys(self):
|
||||
'''Lookup new keys'''
|
||||
logger.debug('Looking up new keys...')
|
||||
tryAmount = 1
|
||||
for i in range(tryAmount): # amount of times to ask peers for new keys
|
||||
# Download new key list from random online peers
|
||||
peer = self.pickOnlinePeer()
|
||||
newKeys = self.peerAction(peer, action='kex')
|
||||
self._core._utils.mergeKeys(newKeys)
|
||||
self.decrementThreadCount('lookupKeys')
|
||||
return
|
||||
|
||||
def lookupAdders(self):
|
||||
'''Lookup new peer addresses'''
|
||||
logger.info('LOOKING UP NEW ADDRESSES')
|
||||
@ -147,10 +149,13 @@ class OnionrCommunicatorDaemon:
|
||||
newBlocks = ''
|
||||
existingBlocks = self._core.getBlockList()
|
||||
triedPeers = [] # list of peers we've tried this time around
|
||||
maxBacklog = 1560 # Max amount of *new* block hashes to have already in queue, to avoid memory exhaustion
|
||||
for i in range(tryAmount):
|
||||
# check if disk allocation is used
|
||||
if len(self.blockQueue) >= maxBacklog:
|
||||
break
|
||||
if not self.isOnline:
|
||||
break
|
||||
# check if disk allocation is used
|
||||
if self._core._utils.storageCounter.isFull():
|
||||
logger.debug('Not looking up new blocks due to maximum amount of allowed disk space used')
|
||||
break
|
||||
@ -180,6 +185,7 @@ class OnionrCommunicatorDaemon:
|
||||
if not i in existingBlocks:
|
||||
# if block does not exist on disk and is not already in block queue
|
||||
if i not in self.blockQueue and not self._core._blacklist.inBlacklist(i):
|
||||
# TODO ensure block starts with minimum difficulty before adding to queue
|
||||
self.blockQueue.append(i) # add blocks to download queue
|
||||
self.decrementThreadCount('lookupBlocks')
|
||||
return
|
||||
@ -207,7 +213,7 @@ class OnionrCommunicatorDaemon:
|
||||
logger.info("Attempting to download %s..." % blockHash)
|
||||
peerUsed = self.pickOnlinePeer()
|
||||
content = self.peerAction(peerUsed, 'getData', data=blockHash) # block content from random peer (includes metadata)
|
||||
if content != False:
|
||||
if content != False and len(content) > 0:
|
||||
try:
|
||||
content = content.encode()
|
||||
except AttributeError:
|
||||
@ -253,7 +259,10 @@ class OnionrCommunicatorDaemon:
|
||||
onionrpeers.PeerProfiles(peerUsed, self._core).addScore(-50)
|
||||
logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
|
||||
if removeFromQueue:
|
||||
self.blockQueue.remove(blockHash) # remove from block queue both if success or false
|
||||
try:
|
||||
self.blockQueue.remove(blockHash) # remove from block queue both if success or false
|
||||
except ValueError:
|
||||
pass
|
||||
self.currentDownloading.remove(blockHash)
|
||||
self.decrementThreadCount('getBlocks')
|
||||
return
|
||||
@ -296,7 +305,7 @@ class OnionrCommunicatorDaemon:
|
||||
'''Manages the self.onlinePeers attribute list, connects to more peers if we have none connected'''
|
||||
|
||||
logger.info('Refreshing peer pool.')
|
||||
maxPeers = 6
|
||||
maxPeers = int(config.get('peers.maxConnect'))
|
||||
needed = maxPeers - len(self.onlinePeers)
|
||||
|
||||
for i in range(needed):
|
||||
@ -339,7 +348,7 @@ class OnionrCommunicatorDaemon:
|
||||
for address in peerList:
|
||||
if not config.get('tor.v3onions') and len(address) == 62:
|
||||
continue
|
||||
if len(address) == 0 or address in tried or address in self.onlinePeers:
|
||||
if len(address) == 0 or address in tried or address in self.onlinePeers or address in self.cooldownPeer:
|
||||
continue
|
||||
if self.shutdown:
|
||||
return
|
||||
@ -348,6 +357,7 @@ class OnionrCommunicatorDaemon:
|
||||
time.sleep(0.1)
|
||||
if address not in self.onlinePeers:
|
||||
self.onlinePeers.append(address)
|
||||
self.connectTimes[address] = self._core._utils.getEpoch()
|
||||
retData = address
|
||||
|
||||
# add peer to profile list if they're not in it
|
||||
@ -362,6 +372,17 @@ class OnionrCommunicatorDaemon:
|
||||
logger.debug('Failed to connect to ' + address)
|
||||
return retData
|
||||
|
||||
def removeOnlinePeer(self, peer):
|
||||
'''Remove an online peer'''
|
||||
try:
|
||||
del self.connectTimes[peer]
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
self.onlinePeers.remove(peer)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def peerCleanup(self):
|
||||
'''This just calls onionrpeers.cleanupPeers, which removes dead or bad peers (offline too long, too slow)'''
|
||||
onionrpeers.peerCleanup(self._core)
|
||||
@ -393,8 +414,9 @@ class OnionrCommunicatorDaemon:
|
||||
if retData == False:
|
||||
try:
|
||||
self.getPeerProfileInstance(peer).addScore(-10)
|
||||
self.onlinePeers.remove(peer)
|
||||
self.getOnlinePeers() # Will only add a new peer to pool if needed
|
||||
self.removeOnlinePeer(peer)
|
||||
if action != 'ping':
|
||||
self.getOnlinePeers() # Will only add a new peer to pool if needed
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
@ -430,16 +452,15 @@ class OnionrCommunicatorDaemon:
|
||||
if cmd[0] == 'shutdown':
|
||||
self.shutdown = True
|
||||
elif cmd[0] == 'announceNode':
|
||||
self.announce(cmd[1])
|
||||
if len(self.onlinePeers) > 0:
|
||||
self.announce(cmd[1])
|
||||
else:
|
||||
logger.warn("Not introducing, since I have no connected nodes.")
|
||||
elif cmd[0] == 'runCheck':
|
||||
logger.debug('Status check; looks good.')
|
||||
open('data/.runcheck', 'w+').close()
|
||||
open(self._core.dataDir + '.runcheck', 'w+').close()
|
||||
elif cmd[0] == 'connectedPeers':
|
||||
self.printOnlinePeers()
|
||||
elif cmd[0] == 'kex':
|
||||
for i in self.timers:
|
||||
if i.timerFunction.__name__ == 'lookupKeys':
|
||||
i.count = (i.frequency - 1)
|
||||
elif cmd[0] == 'pex':
|
||||
for i in self.timers:
|
||||
if i.timerFunction.__name__ == 'lookupAdders':
|
||||
@ -447,6 +468,17 @@ class OnionrCommunicatorDaemon:
|
||||
elif cmd[0] == 'uploadBlock':
|
||||
self.blockToUpload = cmd[1]
|
||||
threading.Thread(target=self.uploadBlock).start()
|
||||
elif cmd[0] == 'startSocket':
|
||||
# Create our own socket server
|
||||
socketInfo = json.loads(cmd[1])
|
||||
socketInfo['id'] = uuid.uuid4()
|
||||
self._core.startSocket = socketInfo
|
||||
elif cmd[0] == 'addSocket':
|
||||
# Socket server was created for us
|
||||
socketInfo = json.loads(cmd[1])
|
||||
peer = socketInfo['peer']
|
||||
reason = socketInfo['reason']
|
||||
threading.Thread(target=self.socketClient.startSocket, args=(peer, reason)).start()
|
||||
else:
|
||||
logger.info('Recieved daemonQueue command:' + cmd[0])
|
||||
|
||||
@ -476,9 +508,7 @@ class OnionrCommunicatorDaemon:
|
||||
|
||||
def announce(self, peer):
|
||||
'''Announce to peers our address'''
|
||||
if self.daemonTools.announceNode():
|
||||
logger.info('Successfully introduced node to ' + peer)
|
||||
else:
|
||||
if self.daemonTools.announceNode() == False:
|
||||
logger.warn('Could not introduce node.')
|
||||
|
||||
def detectAPICrash(self):
|
||||
|
@ -20,7 +20,14 @@
|
||||
|
||||
import os, json, logger
|
||||
|
||||
_configfile = os.path.abspath('data/config.json')
|
||||
try:
|
||||
dataDir = os.environ['ONIONR_HOME']
|
||||
if not dataDir.endswith('/'):
|
||||
dataDir += '/'
|
||||
except KeyError:
|
||||
dataDir = 'data/'
|
||||
|
||||
_configfile = os.path.abspath(dataDir + 'config.json')
|
||||
_config = {}
|
||||
|
||||
def get(key, default = None):
|
||||
|
253
onionr/core.py
253
onionr/core.py
@ -1,5 +1,5 @@
|
||||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
Core Onionr library, useful for external programs. Handles peer & data processing
|
||||
'''
|
||||
@ -17,11 +17,11 @@
|
||||
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 sqlite3, os, sys, time, math, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger, json, netcontroller, math, config
|
||||
import sqlite3, os, sys, time, math, base64, tarfile, nacl, logger, json, netcontroller, math, config
|
||||
from onionrblockapi import Block
|
||||
|
||||
import onionrutils, onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions, onionrvalues
|
||||
import onionrblacklist
|
||||
import onionrblacklist, onionrchat, onionrusers
|
||||
import dbcreator
|
||||
if sys.version_info < (3, 6):
|
||||
try:
|
||||
@ -35,34 +35,52 @@ class Core:
|
||||
'''
|
||||
Initialize Core Onionr library
|
||||
'''
|
||||
|
||||
try:
|
||||
self.queueDB = 'data/queue.db'
|
||||
self.peerDB = 'data/peers.db'
|
||||
self.blockDB = 'data/blocks.db'
|
||||
self.blockDataLocation = 'data/blocks/'
|
||||
self.addressDB = 'data/address.db'
|
||||
self.dataDir = os.environ['ONIONR_HOME']
|
||||
if not self.dataDir.endswith('/'):
|
||||
self.dataDir += '/'
|
||||
except KeyError:
|
||||
self.dataDir = 'data/'
|
||||
|
||||
try:
|
||||
self.queueDB = self.dataDir + 'queue.db'
|
||||
self.peerDB = self.dataDir + 'peers.db'
|
||||
self.blockDB = self.dataDir + 'blocks.db'
|
||||
self.blockDataLocation = self.dataDir + 'blocks/'
|
||||
self.addressDB = self.dataDir + 'address.db'
|
||||
self.hsAddress = ''
|
||||
self.bootstrapFileLocation = 'static-data/bootstrap-nodes.txt'
|
||||
self.bootstrapList = []
|
||||
self.requirements = onionrvalues.OnionrValues()
|
||||
self.torPort = torPort
|
||||
self.dataNonceFile = 'data/block-nonces.dat'
|
||||
self.dataNonceFile = self.dataDir + 'block-nonces.dat'
|
||||
self.dbCreate = dbcreator.DBCreator(self)
|
||||
self.forwardKeysFile = self.dataDir + 'forward-keys.db'
|
||||
|
||||
self.usageFile = 'data/disk-usage.txt'
|
||||
# Socket data, defined here because of multithreading constraints with gevent
|
||||
self.killSockets = False
|
||||
self.startSocket = {}
|
||||
self.socketServerConnData = {}
|
||||
self.socketReasons = {}
|
||||
self.socketServerResponseData = {}
|
||||
|
||||
self.usageFile = self.dataDir + 'disk-usage.txt'
|
||||
self.config = config
|
||||
|
||||
self.maxBlockSize = 10000000 # max block size in bytes
|
||||
|
||||
if not os.path.exists('data/'):
|
||||
os.mkdir('data/')
|
||||
if not os.path.exists('data/blocks/'):
|
||||
os.mkdir('data/blocks/')
|
||||
if not os.path.exists(self.dataDir):
|
||||
os.mkdir(self.dataDir)
|
||||
if not os.path.exists(self.dataDir + 'blocks/'):
|
||||
os.mkdir(self.dataDir + 'blocks/')
|
||||
if not os.path.exists(self.blockDB):
|
||||
self.createBlockDB()
|
||||
if not os.path.exists(self.forwardKeysFile):
|
||||
self.dbCreate.createForwardKeyDB()
|
||||
|
||||
if os.path.exists('data/hs/hostname'):
|
||||
with open('data/hs/hostname', 'r') as hs:
|
||||
if os.path.exists(self.dataDir + '/hs/hostname'):
|
||||
with open(self.dataDir + '/hs/hostname', 'r') as hs:
|
||||
self.hsAddress = hs.read().strip()
|
||||
|
||||
# Load bootstrap address list
|
||||
@ -87,8 +105,8 @@ class Core:
|
||||
|
||||
def refreshFirstStartVars(self):
|
||||
'''Hack to refresh some vars which may not be set on first start'''
|
||||
if os.path.exists('data/hs/hostname'):
|
||||
with open('data/hs/hostname', 'r') as hs:
|
||||
if os.path.exists(self.dataDir + '/hs/hostname'):
|
||||
with open(self.dataDir + '/hs/hostname', 'r') as hs:
|
||||
self.hsAddress = hs.read().strip()
|
||||
|
||||
def addPeer(self, peerID, powID, name=''):
|
||||
@ -102,10 +120,12 @@ class Core:
|
||||
logger.warn("POW token for pubkey base64 representation exceeded 120 bytes, is " + str(sys.getsizeof(powID)))
|
||||
return False
|
||||
|
||||
conn = sqlite3.connect(self.peerDB)
|
||||
events.event('pubkey_add', data = {'key': peerID}, onionr = None)
|
||||
|
||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
||||
hashID = self._crypto.pubKeyHashID(peerID)
|
||||
c = conn.cursor()
|
||||
t = (peerID, name, 'unknown', hashID, powID)
|
||||
t = (peerID, name, 'unknown', hashID, powID, 0)
|
||||
|
||||
for i in c.execute("SELECT * FROM PEERS where id = '" + peerID + "';"):
|
||||
try:
|
||||
@ -116,7 +136,7 @@ class Core:
|
||||
pass
|
||||
except IndexError:
|
||||
pass
|
||||
c.execute('INSERT INTO peers (id, name, dateSeen, pow, hashID) VALUES(?, ?, ?, ?, ?);', t)
|
||||
c.execute('INSERT INTO peers (id, name, dateSeen, pow, hashID, trust) VALUES(?, ?, ?, ?, ?, ?);', t)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@ -126,11 +146,11 @@ class Core:
|
||||
'''
|
||||
Add an address to the address database (only tor currently)
|
||||
'''
|
||||
if address == config.get('i2p.ownAddr', None):
|
||||
if address == config.get('i2p.ownAddr', None) or address == self.hsAddress:
|
||||
|
||||
return False
|
||||
if self._utils.validateID(address):
|
||||
conn = sqlite3.connect(self.addressDB)
|
||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
# check if address is in database
|
||||
# this is safe to do because the address is validated above, but we strip some chars here too just in case
|
||||
@ -162,7 +182,7 @@ class Core:
|
||||
Remove an address from the address database
|
||||
'''
|
||||
if self._utils.validateID(address):
|
||||
conn = sqlite3.connect(self.addressDB)
|
||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
t = (address,)
|
||||
c.execute('Delete from adders where address=?;', t)
|
||||
@ -181,13 +201,13 @@ class Core:
|
||||
**You may want blacklist.addToDB(blockHash)
|
||||
'''
|
||||
if self._utils.validateHash(block):
|
||||
conn = sqlite3.connect(self.blockDB)
|
||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
t = (block,)
|
||||
c.execute('Delete from hashes where hash=?;', t)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
blockFile = 'data/blocks/' + block + '.dat'
|
||||
blockFile = self.dataDir + '/blocks/' + block + '.dat'
|
||||
dataSize = 0
|
||||
try:
|
||||
''' Get size of data when loaded as an object/var, rather than on disk,
|
||||
@ -228,7 +248,7 @@ class Core:
|
||||
raise Exception('Block db does not exist')
|
||||
if self._utils.hasBlock(newHash):
|
||||
return
|
||||
conn = sqlite3.connect(self.blockDB)
|
||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
currentTime = self._utils.getEpoch()
|
||||
if selfInsert or dataSaved:
|
||||
@ -256,14 +276,6 @@ class Core:
|
||||
|
||||
return data
|
||||
|
||||
def _getSha3Hash(self, data):
|
||||
hasher = hashlib.sha3_256()
|
||||
if not type(data) is bytes:
|
||||
data = data.encode()
|
||||
hasher.update(data)
|
||||
dataHash = hasher.hexdigest()
|
||||
return dataHash
|
||||
|
||||
def setData(self, data):
|
||||
'''
|
||||
Set the data assciated with a hash
|
||||
@ -274,7 +286,7 @@ class Core:
|
||||
if not type(data) is bytes:
|
||||
data = data.encode()
|
||||
|
||||
dataHash = self._getSha3Hash(data)
|
||||
dataHash = self._crypto.sha3Hash(data)
|
||||
|
||||
if type(dataHash) is bytes:
|
||||
dataHash = dataHash.decode()
|
||||
@ -287,7 +299,7 @@ class Core:
|
||||
blockFile = open(blockFileName, 'wb')
|
||||
blockFile.write(data)
|
||||
blockFile.close()
|
||||
conn = sqlite3.connect(self.blockDB)
|
||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
c.execute("UPDATE hashes SET dataSaved=1 WHERE hash = '" + dataHash + "';")
|
||||
conn.commit()
|
||||
@ -299,42 +311,6 @@ class Core:
|
||||
|
||||
return dataHash
|
||||
|
||||
def dataDirEncrypt(self, password):
|
||||
'''
|
||||
Encrypt the data directory on Onionr shutdown
|
||||
'''
|
||||
if os.path.exists('data.tar'):
|
||||
os.remove('data.tar')
|
||||
tar = tarfile.open("data.tar", "w")
|
||||
for name in ['data']:
|
||||
tar.add(name)
|
||||
tar.close()
|
||||
tarData = open('data.tar', 'r', encoding = "ISO-8859-1").read()
|
||||
encrypted = simplecrypt.encrypt(password, tarData)
|
||||
open('data-encrypted.dat', 'wb').write(encrypted)
|
||||
os.remove('data.tar')
|
||||
|
||||
return
|
||||
|
||||
def dataDirDecrypt(self, password):
|
||||
'''
|
||||
Decrypt the data directory on startup
|
||||
'''
|
||||
if not os.path.exists('data-encrypted.dat'):
|
||||
return (False, 'encrypted archive does not exist')
|
||||
data = open('data-encrypted.dat', 'rb').read()
|
||||
try:
|
||||
decrypted = simplecrypt.decrypt(password, data)
|
||||
except simplecrypt.DecryptionException:
|
||||
return (False, 'wrong password (or corrupted archive)')
|
||||
else:
|
||||
open('data.tar', 'wb').write(decrypted)
|
||||
tar = tarfile.open('data.tar')
|
||||
tar.extractall()
|
||||
tar.close()
|
||||
|
||||
return (True, '')
|
||||
|
||||
def daemonQueue(self):
|
||||
'''
|
||||
Gives commands to the communication proccess/daemon by reading an sqlite3 database
|
||||
@ -343,16 +319,16 @@ class Core:
|
||||
'''
|
||||
retData = False
|
||||
if not os.path.exists(self.queueDB):
|
||||
self.makeDaemonDB()
|
||||
self.dbCreate.createDaemonDB()
|
||||
else:
|
||||
conn = sqlite3.connect(self.queueDB)
|
||||
conn = sqlite3.connect(self.queueDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
try:
|
||||
for row in c.execute('SELECT command, data, date, min(ID) FROM commands group by id'):
|
||||
retData = row
|
||||
break
|
||||
except sqlite3.OperationalError:
|
||||
self.makeDaemonDB()
|
||||
self.dbCreate.createDaemonDB()
|
||||
else:
|
||||
if retData != False:
|
||||
c.execute('DELETE FROM commands WHERE id=?;', (retData[3],))
|
||||
@ -363,38 +339,32 @@ class Core:
|
||||
|
||||
return retData
|
||||
|
||||
def makeDaemonDB(self):
|
||||
'''generate the daemon queue db'''
|
||||
conn = sqlite3.connect(self.queueDB)
|
||||
c = conn.cursor()
|
||||
# Create table
|
||||
c.execute('''CREATE TABLE commands
|
||||
(id integer primary key autoincrement, command text, data text, date text)''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def daemonQueueAdd(self, command, data=''):
|
||||
'''
|
||||
Add a command to the daemon queue, used by the communication daemon (communicator.py)
|
||||
'''
|
||||
retData = True
|
||||
# Intended to be used by the web server
|
||||
date = self._utils.getEpoch()
|
||||
conn = sqlite3.connect(self.queueDB)
|
||||
conn = sqlite3.connect(self.queueDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
t = (command, data, date)
|
||||
c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
try:
|
||||
c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except sqlite3.OperationalError:
|
||||
retData = False
|
||||
self.daemonQueue()
|
||||
events.event('queue_push', data = {'command': command, 'data': data}, onionr = None)
|
||||
|
||||
return
|
||||
return retData
|
||||
|
||||
def clearDaemonQueue(self):
|
||||
'''
|
||||
Clear the daemon queue (somewhat dangerous)
|
||||
'''
|
||||
conn = sqlite3.connect(self.queueDB)
|
||||
conn = sqlite3.connect(self.queueDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
try:
|
||||
c.execute('DELETE FROM commands;')
|
||||
@ -410,7 +380,7 @@ class Core:
|
||||
'''
|
||||
Return a list of addresses
|
||||
'''
|
||||
conn = sqlite3.connect(self.addressDB)
|
||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
if randomOrder:
|
||||
addresses = c.execute('SELECT * FROM adders ORDER BY RANDOM();')
|
||||
@ -422,19 +392,23 @@ class Core:
|
||||
conn.close()
|
||||
return addressList
|
||||
|
||||
def listPeers(self, randomOrder=True, getPow=False):
|
||||
def listPeers(self, randomOrder=True, getPow=False, trust=0):
|
||||
'''
|
||||
Return a list of public keys (misleading function name)
|
||||
|
||||
randomOrder determines if the list should be in a random order
|
||||
trust sets the minimum trust to list
|
||||
'''
|
||||
conn = sqlite3.connect(self.peerDB)
|
||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
payload = ""
|
||||
if trust not in (0, 1, 2):
|
||||
logger.error('Tried to select invalid trust.')
|
||||
return
|
||||
if randomOrder:
|
||||
payload = 'SELECT * FROM peers ORDER BY RANDOM();'
|
||||
payload = 'SELECT * FROM peers where trust >= %s ORDER BY RANDOM();' % (trust,)
|
||||
else:
|
||||
payload = 'SELECT * FROM peers;'
|
||||
payload = 'SELECT * FROM peers where trust >= %s;' % (trust,)
|
||||
peerList = []
|
||||
for i in c.execute(payload):
|
||||
try:
|
||||
@ -462,18 +436,17 @@ class Core:
|
||||
id text 0
|
||||
name text, 1
|
||||
adders text, 2
|
||||
forwardKey text, 3
|
||||
dateSeen not null, 4
|
||||
bytesStored int, 5
|
||||
trust int 6
|
||||
pubkeyExchanged int 7
|
||||
hashID text 8
|
||||
pow text 9
|
||||
dateSeen not null, 3
|
||||
bytesStored int, 4
|
||||
trust int 5
|
||||
pubkeyExchanged int 6
|
||||
hashID text 7
|
||||
pow text 8
|
||||
'''
|
||||
conn = sqlite3.connect(self.peerDB)
|
||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
command = (peer,)
|
||||
infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'forwardKey': 3, 'dateSeen': 4, 'bytesStored': 5, 'trust': 6, 'pubkeyExchanged': 7, 'hashID': 8}
|
||||
infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'dateSeen': 3, 'bytesStored': 4, 'trust': 5, 'pubkeyExchanged': 6, 'hashID': 7}
|
||||
info = infoNumbers[info]
|
||||
iterCount = 0
|
||||
retVal = ''
|
||||
@ -492,7 +465,7 @@ class Core:
|
||||
'''
|
||||
Update a peer for a key
|
||||
'''
|
||||
conn = sqlite3.connect(self.peerDB)
|
||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
command = (data, peer)
|
||||
# TODO: validate key on whitelist
|
||||
@ -516,7 +489,7 @@ class Core:
|
||||
failure int 6
|
||||
lastConnect 7
|
||||
'''
|
||||
conn = sqlite3.connect(self.addressDB)
|
||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
command = (address,)
|
||||
infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'DBHash': 5, 'failure': 6, 'lastConnect': 7}
|
||||
@ -537,7 +510,7 @@ class Core:
|
||||
'''
|
||||
Update an address for a key
|
||||
'''
|
||||
conn = sqlite3.connect(self.addressDB)
|
||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
command = (data, address)
|
||||
# TODO: validate key on whitelist
|
||||
@ -553,7 +526,7 @@ class Core:
|
||||
'''
|
||||
Get list of our blocks
|
||||
'''
|
||||
conn = sqlite3.connect(self.blockDB)
|
||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
if unsaved:
|
||||
execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
|
||||
@ -570,7 +543,7 @@ class Core:
|
||||
'''
|
||||
Returns the date a block was received
|
||||
'''
|
||||
conn = sqlite3.connect(self.blockDB)
|
||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
execute = 'SELECT dateReceived FROM hashes WHERE hash=?;'
|
||||
args = (blockHash,)
|
||||
@ -584,7 +557,7 @@ class Core:
|
||||
'''
|
||||
Returns a list of blocks by the type
|
||||
'''
|
||||
conn = sqlite3.connect(self.blockDB)
|
||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
if orderDate:
|
||||
execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
|
||||
@ -595,15 +568,28 @@ class Core:
|
||||
for row in c.execute(execute, args):
|
||||
for i in row:
|
||||
rows.append(i)
|
||||
|
||||
return rows
|
||||
|
||||
def getExpiredBlocks(self):
|
||||
'''Returns a list of expired blocks'''
|
||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
date = int(self._utils.getEpoch())
|
||||
|
||||
execute = 'SELECT hash FROM hashes WHERE expire <= %s ORDER BY dateReceived;' % (date,)
|
||||
|
||||
rows = list()
|
||||
for row in c.execute(execute):
|
||||
for i in row:
|
||||
rows.append(i)
|
||||
return rows
|
||||
|
||||
def setBlockType(self, hash, blockType):
|
||||
'''
|
||||
Sets the type of block
|
||||
'''
|
||||
|
||||
conn = sqlite3.connect(self.blockDB)
|
||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
c.execute("UPDATE hashes SET dataType='" + blockType + "' WHERE hash = '" + hash + "';")
|
||||
conn.commit()
|
||||
@ -623,12 +609,13 @@ class Core:
|
||||
sig - optional signature by the author (not optional if author is specified)
|
||||
author - multi-round partial sha3-256 hash of authors public key
|
||||
dateClaimed - timestamp claimed inside the block, only as trustworthy as the block author is
|
||||
expire - expire date for a block
|
||||
'''
|
||||
|
||||
if key not in ('dateReceived', 'decrypted', 'dataType', 'dataFound', 'dataSaved', 'sig', 'author', 'dateClaimed'):
|
||||
if key not in ('dateReceived', 'decrypted', 'dataType', 'dataFound', 'dataSaved', 'sig', 'author', 'dateClaimed', 'expire'):
|
||||
return False
|
||||
|
||||
conn = sqlite3.connect(self.blockDB)
|
||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
args = (data, hash)
|
||||
c.execute("UPDATE hashes SET " + key + " = ? where hash = ?;", args)
|
||||
@ -636,7 +623,7 @@ class Core:
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
def insertBlock(self, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = None):
|
||||
def insertBlock(self, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = None, expire=None):
|
||||
'''
|
||||
Inserts a block into the network
|
||||
encryptType must be specified to encrypt a block
|
||||
@ -673,8 +660,6 @@ class Core:
|
||||
meta['type'] = header
|
||||
meta['type'] = str(meta['type'])
|
||||
|
||||
jsonMeta = json.dumps(meta)
|
||||
|
||||
if encryptType in ('asym', 'sym', ''):
|
||||
metadata['encryptType'] = encryptType
|
||||
else:
|
||||
@ -684,7 +669,20 @@ class Core:
|
||||
data = data.encode()
|
||||
except AttributeError:
|
||||
pass
|
||||
# sign before encrypt, as unauthenticated crypto should not be a problem here
|
||||
|
||||
if encryptType == 'asym':
|
||||
try:
|
||||
forwardEncrypted = onionrusers.OnionrUser(self, asymPeer).forwardEncrypt(data)
|
||||
data = forwardEncrypted[0]
|
||||
meta['forwardEnc'] = True
|
||||
except onionrexceptions.InvalidPubkey:
|
||||
onionrusers.OnionrUser(self, asymPeer).generateForwardKey()
|
||||
else:
|
||||
logger.info(forwardEncrypted)
|
||||
onionrusers.OnionrUser(self, asymPeer).generateForwardKey()
|
||||
fsKey = onionrusers.OnionrUser(self, asymPeer).getGeneratedForwardKeys()[0]
|
||||
meta['newFSKey'] = fsKey[0]
|
||||
jsonMeta = json.dumps(meta)
|
||||
if sign:
|
||||
signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True)
|
||||
signer = self._crypto.pubKey
|
||||
@ -692,8 +690,11 @@ class Core:
|
||||
if len(jsonMeta) > 1000:
|
||||
raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes')
|
||||
|
||||
user = onionrusers.OnionrUser(self, symKey)
|
||||
|
||||
# encrypt block metadata/sig/content
|
||||
if encryptType == 'sym':
|
||||
|
||||
if len(symKey) < self.requirements.passwordLength:
|
||||
raise onionrexceptions.SecurityError('Weak encryption key')
|
||||
jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True).decode()
|
||||
@ -702,6 +703,8 @@ class Core:
|
||||
signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True).decode()
|
||||
elif encryptType == 'asym':
|
||||
if self._utils.validatePubKey(asymPeer):
|
||||
# Encrypt block data with forward secrecy key first, but not meta
|
||||
jsonMeta = json.dumps(meta)
|
||||
jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True, anonymous=True).decode()
|
||||
data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True, anonymous=True).decode()
|
||||
signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True, anonymous=True).decode()
|
||||
@ -714,6 +717,11 @@ class Core:
|
||||
metadata['sig'] = signature
|
||||
metadata['signer'] = signer
|
||||
metadata['time'] = str(self._utils.getEpoch())
|
||||
|
||||
# ensure expire is integer and of sane length
|
||||
if type(expire) is not type(None):
|
||||
assert len(str(int(expire))) < 14
|
||||
metadata['expire'] = expire
|
||||
|
||||
# send block data (and metadata) to POW module to get tokenized block data
|
||||
proof = onionrproofs.POW(metadata, data)
|
||||
@ -721,7 +729,8 @@ class Core:
|
||||
if payload != False:
|
||||
retData = self.setData(payload)
|
||||
self.addToBlockDB(retData, selfInsert=True, dataSaved=True)
|
||||
self.setBlockType(retData, meta['type'])
|
||||
#self.setBlockType(retData, meta['type'])
|
||||
self._utils.processBlockMetadata(retData)
|
||||
self.daemonQueueAdd('uploadBlock', retData)
|
||||
|
||||
if retData != False:
|
||||
|
@ -61,8 +61,6 @@ class DBCreator:
|
||||
ID text not null,
|
||||
name text,
|
||||
adders text,
|
||||
blockDBHash text,
|
||||
forwardKey text,
|
||||
dateSeen not null,
|
||||
bytesStored int,
|
||||
trust int,
|
||||
@ -70,6 +68,12 @@ class DBCreator:
|
||||
hashID text,
|
||||
pow text not null);
|
||||
''')
|
||||
c.execute('''CREATE TABLE forwardKeys(
|
||||
peerKey text not null,
|
||||
forwardKey text not null,
|
||||
date int not null,
|
||||
expire int not null
|
||||
);''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
@ -87,6 +91,7 @@ class DBCreator:
|
||||
sig - optional signature by the author (not optional if author is specified)
|
||||
author - multi-round partial sha3-256 hash of authors public key
|
||||
dateClaimed - timestamp claimed inside the block, only as trustworthy as the block author is
|
||||
expire int - block expire date in epoch
|
||||
'''
|
||||
if os.path.exists(self.core.blockDB):
|
||||
raise Exception("Block database already exists")
|
||||
@ -101,9 +106,42 @@ class DBCreator:
|
||||
dataSaved int,
|
||||
sig text,
|
||||
author text,
|
||||
dateClaimed int
|
||||
dateClaimed int,
|
||||
expire int
|
||||
);
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
return
|
||||
|
||||
def createForwardKeyDB(self):
|
||||
'''
|
||||
Create the forward secrecy key db (*for *OUR* keys*)
|
||||
'''
|
||||
if os.path.exists(self.core.forwardKeysFile):
|
||||
raise Exception("Block database already exists")
|
||||
conn = sqlite3.connect(self.core.forwardKeysFile)
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE myForwardKeys(
|
||||
peer text not null,
|
||||
publickey text not null,
|
||||
privatekey text not null,
|
||||
date int not null,
|
||||
expire int not null
|
||||
);
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def createDaemonDB(self):
|
||||
'''
|
||||
Create the daemon queue database
|
||||
'''
|
||||
conn = sqlite3.connect(self.core.queueDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
# Create table
|
||||
c.execute('''CREATE TABLE commands
|
||||
(id integer primary key autoincrement, command text, data text, date text)''')
|
||||
conn.commit()
|
||||
conn.close()
|
@ -18,21 +18,37 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import subprocess, os, random, sys, logger, time, signal, config
|
||||
import subprocess, os, random, sys, logger, time, signal, config, base64
|
||||
from stem.control import Controller
|
||||
from onionrblockapi import Block
|
||||
|
||||
from dependencies import secrets
|
||||
class NetController:
|
||||
'''
|
||||
This class handles hidden service setup on Tor and I2P
|
||||
'''
|
||||
|
||||
def __init__(self, hsPort):
|
||||
self.torConfigLocation = 'data/torrc'
|
||||
try:
|
||||
self.dataDir = os.environ['ONIONR_HOME']
|
||||
if not self.dataDir.endswith('/'):
|
||||
self.dataDir += '/'
|
||||
except KeyError:
|
||||
self.dataDir = 'data/'
|
||||
|
||||
self.torConfigLocation = self.dataDir + 'torrc'
|
||||
self.readyState = False
|
||||
self.socksPort = random.randint(1024, 65535)
|
||||
self.hsPort = hsPort
|
||||
self._torInstnace = ''
|
||||
self.myID = ''
|
||||
|
||||
if os.path.exists('./tor'):
|
||||
self.torBinary = './tor'
|
||||
elif os.path.exists('/usr/bin/tor'):
|
||||
self.torBinary = '/usr/bin/tor'
|
||||
else:
|
||||
self.torBinary = 'tor'
|
||||
|
||||
config.reload()
|
||||
'''
|
||||
if os.path.exists(self.torConfigLocation):
|
||||
@ -52,13 +68,33 @@ class NetController:
|
||||
if config.get('tor.v3onions'):
|
||||
hsVer = 'HiddenServiceVersion 3'
|
||||
logger.info('Using v3 onions :)')
|
||||
|
||||
if os.path.exists(self.torConfigLocation):
|
||||
os.remove(self.torConfigLocation)
|
||||
|
||||
# Set the Tor control password. Meant to make it harder to manipulate our Tor instance
|
||||
plaintext = base64.b64encode(os.urandom(50)).decode()
|
||||
config.set('tor.controlpassword', plaintext, savefile=True)
|
||||
config.set('tor.socksport', self.socksPort, savefile=True)
|
||||
|
||||
controlPort = random.randint(1025, 65535)
|
||||
|
||||
config.set('tor.controlPort', controlPort, savefile=True)
|
||||
|
||||
hashedPassword = subprocess.Popen([self.torBinary, '--hash-password', plaintext], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
for line in iter(hashedPassword.stdout.readline, b''):
|
||||
password = line.decode()
|
||||
if 'warn' not in password:
|
||||
break
|
||||
|
||||
torrcData = '''SocksPort ''' + str(self.socksPort) + '''
|
||||
HiddenServiceDir data/hs/
|
||||
HiddenServiceDir ''' + self.dataDir + '''hs/
|
||||
\n''' + hsVer + '''\n
|
||||
HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + '''
|
||||
DataDirectory data/tordata/
|
||||
DataDirectory ''' + self.dataDir + '''tordata/
|
||||
CookieAuthentication 1
|
||||
ControlPort ''' + str(controlPort) + '''
|
||||
HashedControlPassword ''' + str(password) + '''
|
||||
'''
|
||||
torrc = open(self.torConfigLocation, 'w')
|
||||
torrc.write(torrcData)
|
||||
@ -74,20 +110,20 @@ DataDirectory data/tordata/
|
||||
self.generateTorrc()
|
||||
|
||||
if os.path.exists('./tor'):
|
||||
torBinary = './tor'
|
||||
self.torBinary = './tor'
|
||||
elif os.path.exists('/usr/bin/tor'):
|
||||
torBinary = '/usr/bin/tor'
|
||||
self.torBinary = '/usr/bin/tor'
|
||||
else:
|
||||
torBinary = 'tor'
|
||||
self.torBinary = 'tor'
|
||||
|
||||
try:
|
||||
tor = subprocess.Popen([torBinary, '-f', self.torConfigLocation], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
tor = subprocess.Popen([self.torBinary, '-f', self.torConfigLocation], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except FileNotFoundError:
|
||||
logger.fatal("Tor was not found in your path or the Onionr directory. Please install Tor and try again.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Test Tor Version
|
||||
torVersion = subprocess.Popen([torBinary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
torVersion = subprocess.Popen([self.torBinary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
for line in iter(torVersion.stdout.readline, b''):
|
||||
if 'Tor 0.2.' in line.decode():
|
||||
logger.warn("Running 0.2.x Tor series, no support for v3 onion peers")
|
||||
@ -111,11 +147,11 @@ DataDirectory data/tordata/
|
||||
logger.debug('Finished starting Tor.', timestamp=True)
|
||||
self.readyState = True
|
||||
|
||||
myID = open('data/hs/hostname', 'r')
|
||||
myID = open(self.dataDir + 'hs/hostname', 'r')
|
||||
self.myID = myID.read().replace('\n', '')
|
||||
myID.close()
|
||||
|
||||
torPidFile = open('data/torPid.txt', 'w')
|
||||
torPidFile = open(self.dataDir + 'torPid.txt', 'w')
|
||||
torPidFile.write(str(tor.pid))
|
||||
torPidFile.close()
|
||||
|
||||
@ -127,7 +163,7 @@ DataDirectory data/tordata/
|
||||
'''
|
||||
|
||||
try:
|
||||
pid = open('data/torPid.txt', 'r')
|
||||
pid = open(self.dataDir + 'torPid.txt', 'r')
|
||||
pidN = pid.read()
|
||||
pid.close()
|
||||
except FileNotFoundError:
|
||||
@ -140,7 +176,7 @@ DataDirectory data/tordata/
|
||||
|
||||
try:
|
||||
os.kill(int(pidN), signal.SIGTERM)
|
||||
os.remove('data/torPid.txt')
|
||||
os.remove(self.dataDir + 'torPid.txt')
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
|
180
onionr/onionr.py
180
onionr/onionr.py
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network.
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
Onionr is the name for both the protocol and the original/reference software.
|
||||
|
||||
@ -26,13 +26,13 @@ if sys.version_info[0] == 2 or sys.version_info[1] < 5:
|
||||
print('Error, Onionr requires Python 3.4+')
|
||||
sys.exit(1)
|
||||
import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json, getpass, sqlite3
|
||||
import webbrowser
|
||||
from threading import Thread
|
||||
import api, core, config, logger, onionrplugins as plugins, onionrevents as events
|
||||
import onionrutils
|
||||
from onionrutils import OnionrUtils
|
||||
from netcontroller import NetController
|
||||
from onionrblockapi import Block
|
||||
import onionrproofs
|
||||
import onionrproofs, onionrexceptions, onionrusers
|
||||
|
||||
try:
|
||||
from urllib3.contrib.socks import SOCKSProxyManager
|
||||
@ -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.2.0' # for debugging and stuff
|
||||
ONIONR_VERSION = '0.3.0' # 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 know how to communicate without learning too much information about you.
|
||||