Merge branch 'tempblocks' into 'master'

Merge Tempblocks

See merge request beardog/Onionr!11
This commit is contained in:
Kevin 2018-11-11 02:10:58 +00:00
commit 74ccca5ea4
40 changed files with 1633 additions and 443 deletions

2
.gitignore vendored
View File

@ -13,3 +13,5 @@ onionr/data-encrypted.dat
onionr/.onionr-lock onionr/.onionr-lock
core core
.vscode/* .vscode/*
venv/*
onionr/fs*

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "onionr/bitpeer"]
path = onionr/bitpeer
url = https://github.com/beardog108/bitpeer.py

View File

@ -4,7 +4,7 @@ FROM ubuntu:bionic
ENV HOME /root ENV HOME /root
#Install needed packages #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 && \ RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
locale-gen locale-gen

View File

@ -1,3 +1,4 @@
#!/bin/sh #!/bin/sh
cd "$(dirname "$0")"
cd onionr/ cd onionr/
./onionr.py "$@" ./onionr.py "$@"

View File

@ -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 This file handles all incoming http requests to the client, using Flask
''' '''
@ -20,9 +20,9 @@
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 multiprocessing import Process
from gevent.wsgi 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
from core import Core import core
from onionrblockapi import Block from onionrblockapi import Block
import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config 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 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: try:
if not hmac.compare_digest(self.clientToken, token): if not hmac.compare_digest(self.clientToken, token):
return False return False
@ -69,7 +72,7 @@ class API:
logger.debug('%s not in %s' % (path, mimetypes)) logger.debug('%s not in %s' % (path, mimetypes))
return 'text/plain' return 'text/plain'
def __init__(self, debug): def __init__(self, debug, API_VERSION):
''' '''
Initialize the api server, preping variables for later use Initialize the api server, preping variables for later use
@ -88,7 +91,7 @@ class API:
self.debug = debug self.debug = debug
self._privateDelayTime = 3 self._privateDelayTime = 3
self._core = Core() self._core = core.Core()
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__)
@ -102,7 +105,7 @@ class API:
self.mimeType = 'text/plain' self.mimeType = 'text/plain'
self.overrideCSP = False 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) bypass.write(self.timeBypassToken)
if not debug and not self._developmentMode: if not debug and not self._developmentMode:
@ -111,7 +114,7 @@ class API:
else: else:
self.host = '127.0.0.1' 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) file.write(self.host)
@app.before_request @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["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['server'] = 'Onionr' resp.headers['api'] = API_VERSION
# reset to text/plain to help prevent browser attacks # reset to text/plain to help prevent browser attacks
self.mimeType = 'text/plain' self.mimeType = 'text/plain'
self.overrideCSP = False self.overrideCSP = False
return resp return resp
@app.route('/www/private/<path:path>') @app.route('/www/private/<path:path>')
def www_private(path): def www_private(path):
startTime = math.floor(time.time()) startTime = math.floor(time.time())
@ -466,7 +469,7 @@ class API:
elif action == 'getData': elif action == 'getData':
resp = '' resp = ''
if self._utils.validateHash(data): 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) block = Block(hash=data.encode(), core=self._core)
resp = base64.b64encode(block.getRaw().encode()).decode() resp = base64.b64encode(block.getRaw().encode()).decode()
if len(resp) == 0: if len(resp) == 0:
@ -515,7 +518,7 @@ class API:
while len(self._core.hsAddress) == 0: while len(self._core.hsAddress) == 0:
self._core.refreshFirstStartVars() self._core.refreshFirstStartVars()
time.sleep(0.5) 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() self.http_server.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/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 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) 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 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, 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 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 from defusedxml import minidom
class OnionrCommunicatorDaemon: class OnionrCommunicatorDaemon:
@ -51,6 +52,8 @@ class OnionrCommunicatorDaemon:
# lists of connected peers and peers we know we can't reach currently # lists of connected peers and peers we know we can't reach currently
self.onlinePeers = [] self.onlinePeers = []
self.offlinePeers = [] self.offlinePeers = []
self.cooldownPeer = {}
self.connectTimes = {}
self.peerProfiles = [] # list of peer's profiles (onionrpeers.PeerProfile instances) self.peerProfiles = [] # list of peer's profiles (onionrpeers.PeerProfile instances)
# amount of threads running by name, used to prevent too many # 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.daemonTools = onionrdaemontools.DaemonTools(self) self.daemonTools = onionrdaemontools.DaemonTools(self)
self._chat = onionrchat.OnionrChat(self)
if debug or developmentMode: if debug or developmentMode:
OnionrCommunicatorTimers(self, self.heartbeat, 10) OnionrCommunicatorTimers(self, self.heartbeat, 30)
# Set timers, function reference, seconds # Set timers, function reference, seconds
# requiresPeer True means the timer function won't fire if we have no connected peers # 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.daemonCommands, 5)
OnionrCommunicatorTimers(self, self.detectAPICrash, 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.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.getBlocks, self._core.config.get('timers.getBlocks'), requiresPeer=True)
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58) OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58)
OnionrCommunicatorTimers(self, self.daemonTools.cleanOldBlocks, 65) OnionrCommunicatorTimers(self, self.daemonTools.cleanOldBlocks, 65)
OnionrCommunicatorTimers(self, self.lookupKeys, 60, requiresPeer=True)
OnionrCommunicatorTimers(self, self.lookupAdders, 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) netCheckTimer = OnionrCommunicatorTimers(self, self.daemonTools.netCheck, 600)
announceTimer = OnionrCommunicatorTimers(self, self.daemonTools.announceNode, 305, requiresPeer=True, maxThreads=1) announceTimer = OnionrCommunicatorTimers(self, self.daemonTools.announceNode, 305, requiresPeer=True, maxThreads=1)
cleanupTimer = OnionrCommunicatorTimers(self, self.peerCleanup, 300, requiresPeer=True) 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) # set loop to execute instantly to load up peer pool (replaced old pool init wait)
peerPoolTimer.count = (peerPoolTimer.frequency - 1) peerPoolTimer.count = (peerPoolTimer.frequency - 1)
cleanupTimer.count = (cleanupTimer.frequency - 60) cleanupTimer.count = (cleanupTimer.frequency - 60)
announceTimer.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 # Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking
try: try:
@ -114,21 +127,10 @@ class OnionrCommunicatorDaemon:
pass pass
logger.info('Goodbye.') logger.info('Goodbye.')
self._core.killSockets = True
self._core._utils.localCommand('shutdown') # shutdown the api self._core._utils.localCommand('shutdown') # shutdown the api
time.sleep(0.5) 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): def lookupAdders(self):
'''Lookup new peer addresses''' '''Lookup new peer addresses'''
logger.info('LOOKING UP NEW ADDRESSES') logger.info('LOOKING UP NEW ADDRESSES')
@ -147,10 +149,13 @@ class OnionrCommunicatorDaemon:
newBlocks = '' newBlocks = ''
existingBlocks = self._core.getBlockList() existingBlocks = self._core.getBlockList()
triedPeers = [] # list of peers we've tried this time around 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): for i in range(tryAmount):
# check if disk allocation is used if len(self.blockQueue) >= maxBacklog:
break
if not self.isOnline: if not self.isOnline:
break break
# check if disk allocation is used
if self._core._utils.storageCounter.isFull(): if self._core._utils.storageCounter.isFull():
logger.debug('Not looking up new blocks due to maximum amount of allowed disk space used') logger.debug('Not looking up new blocks due to maximum amount of allowed disk space used')
break break
@ -180,6 +185,7 @@ class OnionrCommunicatorDaemon:
if not i in existingBlocks: if not i in existingBlocks:
# if block does not exist on disk and is not already in block queue # 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): 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.blockQueue.append(i) # add blocks to download queue
self.decrementThreadCount('lookupBlocks') self.decrementThreadCount('lookupBlocks')
return return
@ -207,7 +213,7 @@ class OnionrCommunicatorDaemon:
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', data=blockHash) # block content from random peer (includes metadata)
if content != False: if content != False and len(content) > 0:
try: try:
content = content.encode() content = content.encode()
except AttributeError: except AttributeError:
@ -253,7 +259,10 @@ class OnionrCommunicatorDaemon:
onionrpeers.PeerProfiles(peerUsed, self._core).addScore(-50) onionrpeers.PeerProfiles(peerUsed, self._core).addScore(-50)
logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash) logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
if removeFromQueue: 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.currentDownloading.remove(blockHash)
self.decrementThreadCount('getBlocks') self.decrementThreadCount('getBlocks')
return return
@ -296,7 +305,7 @@ class OnionrCommunicatorDaemon:
'''Manages the self.onlinePeers attribute list, connects to more peers if we have none connected''' '''Manages the self.onlinePeers attribute list, connects to more peers if we have none connected'''
logger.info('Refreshing peer pool.') logger.info('Refreshing peer pool.')
maxPeers = 6 maxPeers = int(config.get('peers.maxConnect'))
needed = maxPeers - len(self.onlinePeers) needed = maxPeers - len(self.onlinePeers)
for i in range(needed): for i in range(needed):
@ -339,7 +348,7 @@ class OnionrCommunicatorDaemon:
for address in peerList: for address in peerList:
if not config.get('tor.v3onions') and len(address) == 62: if not config.get('tor.v3onions') and len(address) == 62:
continue 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 continue
if self.shutdown: if self.shutdown:
return return
@ -348,6 +357,7 @@ class OnionrCommunicatorDaemon:
time.sleep(0.1) time.sleep(0.1)
if address not in self.onlinePeers: if address not in self.onlinePeers:
self.onlinePeers.append(address) self.onlinePeers.append(address)
self.connectTimes[address] = self._core._utils.getEpoch()
retData = address retData = address
# add peer to profile list if they're not in it # add peer to profile list if they're not in it
@ -362,6 +372,17 @@ class OnionrCommunicatorDaemon:
logger.debug('Failed to connect to ' + address) logger.debug('Failed to connect to ' + address)
return retData 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): def peerCleanup(self):
'''This just calls onionrpeers.cleanupPeers, which removes dead or bad peers (offline too long, too slow)''' '''This just calls onionrpeers.cleanupPeers, which removes dead or bad peers (offline too long, too slow)'''
onionrpeers.peerCleanup(self._core) onionrpeers.peerCleanup(self._core)
@ -393,8 +414,9 @@ class OnionrCommunicatorDaemon:
if retData == False: if retData == False:
try: try:
self.getPeerProfileInstance(peer).addScore(-10) self.getPeerProfileInstance(peer).addScore(-10)
self.onlinePeers.remove(peer) self.removeOnlinePeer(peer)
self.getOnlinePeers() # Will only add a new peer to pool if needed if action != 'ping':
self.getOnlinePeers() # Will only add a new peer to pool if needed
except ValueError: except ValueError:
pass pass
else: else:
@ -430,16 +452,15 @@ class OnionrCommunicatorDaemon:
if cmd[0] == 'shutdown': if cmd[0] == 'shutdown':
self.shutdown = True self.shutdown = True
elif cmd[0] == 'announceNode': 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': elif cmd[0] == 'runCheck':
logger.debug('Status check; looks good.') logger.debug('Status check; looks good.')
open('data/.runcheck', 'w+').close() open(self._core.dataDir + '.runcheck', 'w+').close()
elif cmd[0] == 'connectedPeers': elif cmd[0] == 'connectedPeers':
self.printOnlinePeers() 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': elif cmd[0] == 'pex':
for i in self.timers: for i in self.timers:
if i.timerFunction.__name__ == 'lookupAdders': if i.timerFunction.__name__ == 'lookupAdders':
@ -447,6 +468,17 @@ class OnionrCommunicatorDaemon:
elif cmd[0] == 'uploadBlock': elif cmd[0] == 'uploadBlock':
self.blockToUpload = cmd[1] self.blockToUpload = cmd[1]
threading.Thread(target=self.uploadBlock).start() 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: else:
logger.info('Recieved daemonQueue command:' + cmd[0]) logger.info('Recieved daemonQueue command:' + cmd[0])
@ -476,9 +508,7 @@ class OnionrCommunicatorDaemon:
def announce(self, peer): def announce(self, peer):
'''Announce to peers our address''' '''Announce to peers our address'''
if self.daemonTools.announceNode(): if self.daemonTools.announceNode() == False:
logger.info('Successfully introduced node to ' + peer)
else:
logger.warn('Could not introduce node.') logger.warn('Could not introduce node.')
def detectAPICrash(self): def detectAPICrash(self):

View File

@ -20,7 +20,14 @@
import os, json, logger 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 = {} _config = {}
def get(key, default = None): def get(key, default = None):

View File

@ -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 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 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 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 from onionrblockapi import Block
import onionrutils, onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions, onionrvalues import onionrutils, onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions, onionrvalues
import onionrblacklist import onionrblacklist, onionrchat, onionrusers
import dbcreator import dbcreator
if sys.version_info < (3, 6): if sys.version_info < (3, 6):
try: try:
@ -35,34 +35,52 @@ class Core:
''' '''
Initialize Core Onionr library Initialize Core Onionr library
''' '''
try: try:
self.queueDB = 'data/queue.db' self.dataDir = os.environ['ONIONR_HOME']
self.peerDB = 'data/peers.db' if not self.dataDir.endswith('/'):
self.blockDB = 'data/blocks.db' self.dataDir += '/'
self.blockDataLocation = 'data/blocks/' except KeyError:
self.addressDB = 'data/address.db' 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.hsAddress = ''
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()
self.torPort = torPort self.torPort = torPort
self.dataNonceFile = 'data/block-nonces.dat' self.dataNonceFile = self.dataDir + 'block-nonces.dat'
self.dbCreate = dbcreator.DBCreator(self) 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.config = config
self.maxBlockSize = 10000000 # max block size in bytes self.maxBlockSize = 10000000 # max block size in bytes
if not os.path.exists('data/'): if not os.path.exists(self.dataDir):
os.mkdir('data/') os.mkdir(self.dataDir)
if not os.path.exists('data/blocks/'): if not os.path.exists(self.dataDir + 'blocks/'):
os.mkdir('data/blocks/') os.mkdir(self.dataDir + 'blocks/')
if not os.path.exists(self.blockDB): if not os.path.exists(self.blockDB):
self.createBlockDB() self.createBlockDB()
if not os.path.exists(self.forwardKeysFile):
self.dbCreate.createForwardKeyDB()
if os.path.exists('data/hs/hostname'): if os.path.exists(self.dataDir + '/hs/hostname'):
with open('data/hs/hostname', 'r') as hs: with open(self.dataDir + '/hs/hostname', 'r') as hs:
self.hsAddress = hs.read().strip() self.hsAddress = hs.read().strip()
# Load bootstrap address list # Load bootstrap address list
@ -87,8 +105,8 @@ class Core:
def refreshFirstStartVars(self): def refreshFirstStartVars(self):
'''Hack to refresh some vars which may not be set on first start''' '''Hack to refresh some vars which may not be set on first start'''
if os.path.exists('data/hs/hostname'): if os.path.exists(self.dataDir + '/hs/hostname'):
with open('data/hs/hostname', 'r') as hs: with open(self.dataDir + '/hs/hostname', 'r') as hs:
self.hsAddress = hs.read().strip() self.hsAddress = hs.read().strip()
def addPeer(self, peerID, powID, name=''): 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))) logger.warn("POW token for pubkey base64 representation exceeded 120 bytes, is " + str(sys.getsizeof(powID)))
return False 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) hashID = self._crypto.pubKeyHashID(peerID)
c = conn.cursor() 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 + "';"): for i in c.execute("SELECT * FROM PEERS where id = '" + peerID + "';"):
try: try:
@ -116,7 +136,7 @@ class Core:
pass pass
except IndexError: except IndexError:
pass 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.commit()
conn.close() conn.close()
@ -126,11 +146,11 @@ class Core:
''' '''
Add an address to the address database (only tor currently) 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 return False
if self._utils.validateID(address): if self._utils.validateID(address):
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB, timeout=10)
c = conn.cursor() c = conn.cursor()
# check if address is in database # 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 # 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 Remove an address from the address database
''' '''
if self._utils.validateID(address): if self._utils.validateID(address):
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB, timeout=10)
c = conn.cursor() c = conn.cursor()
t = (address,) t = (address,)
c.execute('Delete from adders where address=?;', t) c.execute('Delete from adders where address=?;', t)
@ -181,13 +201,13 @@ class Core:
**You may want blacklist.addToDB(blockHash) **You may want blacklist.addToDB(blockHash)
''' '''
if self._utils.validateHash(block): if self._utils.validateHash(block):
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB, timeout=10)
c = conn.cursor() c = conn.cursor()
t = (block,) t = (block,)
c.execute('Delete from hashes where hash=?;', t) c.execute('Delete from hashes where hash=?;', t)
conn.commit() conn.commit()
conn.close() conn.close()
blockFile = 'data/blocks/' + block + '.dat' blockFile = self.dataDir + '/blocks/' + block + '.dat'
dataSize = 0 dataSize = 0
try: try:
''' Get size of data when loaded as an object/var, rather than on disk, ''' 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') raise Exception('Block db does not exist')
if self._utils.hasBlock(newHash): if self._utils.hasBlock(newHash):
return return
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB, timeout=10)
c = conn.cursor() c = conn.cursor()
currentTime = self._utils.getEpoch() currentTime = self._utils.getEpoch()
if selfInsert or dataSaved: if selfInsert or dataSaved:
@ -256,14 +276,6 @@ class Core:
return data 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): def setData(self, data):
''' '''
Set the data assciated with a hash Set the data assciated with a hash
@ -274,7 +286,7 @@ class Core:
if not type(data) is bytes: if not type(data) is bytes:
data = data.encode() data = data.encode()
dataHash = self._getSha3Hash(data) dataHash = self._crypto.sha3Hash(data)
if type(dataHash) is bytes: if type(dataHash) is bytes:
dataHash = dataHash.decode() dataHash = dataHash.decode()
@ -287,7 +299,7 @@ class Core:
blockFile = open(blockFileName, 'wb') blockFile = open(blockFileName, 'wb')
blockFile.write(data) blockFile.write(data)
blockFile.close() blockFile.close()
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB, timeout=10)
c = conn.cursor() c = conn.cursor()
c.execute("UPDATE hashes SET dataSaved=1 WHERE hash = '" + dataHash + "';") c.execute("UPDATE hashes SET dataSaved=1 WHERE hash = '" + dataHash + "';")
conn.commit() conn.commit()
@ -299,42 +311,6 @@ class Core:
return dataHash 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): def daemonQueue(self):
''' '''
Gives commands to the communication proccess/daemon by reading an sqlite3 database Gives commands to the communication proccess/daemon by reading an sqlite3 database
@ -343,16 +319,16 @@ class Core:
''' '''
retData = False retData = False
if not os.path.exists(self.queueDB): if not os.path.exists(self.queueDB):
self.makeDaemonDB() self.dbCreate.createDaemonDB()
else: else:
conn = sqlite3.connect(self.queueDB) conn = sqlite3.connect(self.queueDB, timeout=10)
c = conn.cursor() c = conn.cursor()
try: try:
for row in c.execute('SELECT command, data, date, min(ID) FROM commands group by id'): for row in c.execute('SELECT command, data, date, min(ID) FROM commands group by id'):
retData = row retData = row
break break
except sqlite3.OperationalError: except sqlite3.OperationalError:
self.makeDaemonDB() self.dbCreate.createDaemonDB()
else: else:
if retData != False: if retData != False:
c.execute('DELETE FROM commands WHERE id=?;', (retData[3],)) c.execute('DELETE FROM commands WHERE id=?;', (retData[3],))
@ -363,38 +339,32 @@ class Core:
return retData 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=''): def daemonQueueAdd(self, command, data=''):
''' '''
Add a command to the daemon queue, used by the communication daemon (communicator.py) Add a command to the daemon queue, used by the communication daemon (communicator.py)
''' '''
retData = True
# Intended to be used by the web server # Intended to be used by the web server
date = self._utils.getEpoch() date = self._utils.getEpoch()
conn = sqlite3.connect(self.queueDB) conn = sqlite3.connect(self.queueDB, timeout=10)
c = conn.cursor() c = conn.cursor()
t = (command, data, date) t = (command, data, date)
c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t) try:
conn.commit() c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t)
conn.close() conn.commit()
conn.close()
except sqlite3.OperationalError:
retData = False
self.daemonQueue()
events.event('queue_push', data = {'command': command, 'data': data}, onionr = None) events.event('queue_push', data = {'command': command, 'data': data}, onionr = None)
return return retData
def clearDaemonQueue(self): def clearDaemonQueue(self):
''' '''
Clear the daemon queue (somewhat dangerous) Clear the daemon queue (somewhat dangerous)
''' '''
conn = sqlite3.connect(self.queueDB) conn = sqlite3.connect(self.queueDB, timeout=10)
c = conn.cursor() c = conn.cursor()
try: try:
c.execute('DELETE FROM commands;') c.execute('DELETE FROM commands;')
@ -410,7 +380,7 @@ class Core:
''' '''
Return a list of addresses Return a list of addresses
''' '''
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB, timeout=10)
c = conn.cursor() c = conn.cursor()
if randomOrder: if randomOrder:
addresses = c.execute('SELECT * FROM adders ORDER BY RANDOM();') addresses = c.execute('SELECT * FROM adders ORDER BY RANDOM();')
@ -422,19 +392,23 @@ class Core:
conn.close() conn.close()
return addressList 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) Return a list of public keys (misleading function name)
randomOrder determines if the list should be in a random order 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() c = conn.cursor()
payload = "" payload = ""
if trust not in (0, 1, 2):
logger.error('Tried to select invalid trust.')
return
if randomOrder: if randomOrder:
payload = 'SELECT * FROM peers ORDER BY RANDOM();' payload = 'SELECT * FROM peers where trust >= %s ORDER BY RANDOM();' % (trust,)
else: else:
payload = 'SELECT * FROM peers;' payload = 'SELECT * FROM peers where trust >= %s;' % (trust,)
peerList = [] peerList = []
for i in c.execute(payload): for i in c.execute(payload):
try: try:
@ -462,18 +436,17 @@ class Core:
id text 0 id text 0
name text, 1 name text, 1
adders text, 2 adders text, 2
forwardKey text, 3 dateSeen not null, 3
dateSeen not null, 4 bytesStored int, 4
bytesStored int, 5 trust int 5
trust int 6 pubkeyExchanged int 6
pubkeyExchanged int 7 hashID text 7
hashID text 8 pow text 8
pow text 9
''' '''
conn = sqlite3.connect(self.peerDB) conn = sqlite3.connect(self.peerDB, timeout=10)
c = conn.cursor() c = conn.cursor()
command = (peer,) 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] info = infoNumbers[info]
iterCount = 0 iterCount = 0
retVal = '' retVal = ''
@ -492,7 +465,7 @@ class Core:
''' '''
Update a peer for a key Update a peer for a key
''' '''
conn = sqlite3.connect(self.peerDB) conn = sqlite3.connect(self.peerDB, timeout=10)
c = conn.cursor() c = conn.cursor()
command = (data, peer) command = (data, peer)
# TODO: validate key on whitelist # TODO: validate key on whitelist
@ -516,7 +489,7 @@ class Core:
failure int 6 failure int 6
lastConnect 7 lastConnect 7
''' '''
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB, timeout=10)
c = conn.cursor() c = conn.cursor()
command = (address,) command = (address,)
infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'DBHash': 5, 'failure': 6, 'lastConnect': 7} 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 Update an address for a key
''' '''
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB, timeout=10)
c = conn.cursor() c = conn.cursor()
command = (data, address) command = (data, address)
# TODO: validate key on whitelist # TODO: validate key on whitelist
@ -553,7 +526,7 @@ class Core:
''' '''
Get list of our blocks Get list of our blocks
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB, timeout=10)
c = conn.cursor() c = conn.cursor()
if unsaved: if unsaved:
execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();' execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
@ -570,7 +543,7 @@ class Core:
''' '''
Returns the date a block was received Returns the date a block was received
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB, timeout=10)
c = conn.cursor() c = conn.cursor()
execute = 'SELECT dateReceived FROM hashes WHERE hash=?;' execute = 'SELECT dateReceived FROM hashes WHERE hash=?;'
args = (blockHash,) args = (blockHash,)
@ -584,7 +557,7 @@ class Core:
''' '''
Returns a list of blocks by the type Returns a list of blocks by the type
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB, timeout=10)
c = conn.cursor() c = conn.cursor()
if orderDate: if orderDate:
execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;' execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
@ -595,15 +568,28 @@ class Core:
for row in c.execute(execute, args): for row in c.execute(execute, args):
for i in row: for i in row:
rows.append(i) rows.append(i)
return rows 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): def setBlockType(self, hash, blockType):
''' '''
Sets the type of block Sets the type of block
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB, timeout=10)
c = conn.cursor() c = conn.cursor()
c.execute("UPDATE hashes SET dataType='" + blockType + "' WHERE hash = '" + hash + "';") c.execute("UPDATE hashes SET dataType='" + blockType + "' WHERE hash = '" + hash + "';")
conn.commit() conn.commit()
@ -623,12 +609,13 @@ class Core:
sig - optional signature by the author (not optional if author is specified) sig - optional signature by the author (not optional if author is specified)
author - multi-round partial sha3-256 hash of authors public key 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 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 return False
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB, timeout=10)
c = conn.cursor() c = conn.cursor()
args = (data, hash) args = (data, hash)
c.execute("UPDATE hashes SET " + key + " = ? where hash = ?;", args) c.execute("UPDATE hashes SET " + key + " = ? where hash = ?;", args)
@ -636,7 +623,7 @@ class Core:
conn.close() conn.close()
return True 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 Inserts a block into the network
encryptType must be specified to encrypt a block encryptType must be specified to encrypt a block
@ -673,8 +660,6 @@ class Core:
meta['type'] = header meta['type'] = header
meta['type'] = str(meta['type']) meta['type'] = str(meta['type'])
jsonMeta = json.dumps(meta)
if encryptType in ('asym', 'sym', ''): if encryptType in ('asym', 'sym', ''):
metadata['encryptType'] = encryptType metadata['encryptType'] = encryptType
else: else:
@ -684,7 +669,20 @@ class Core:
data = data.encode() data = data.encode()
except AttributeError: except AttributeError:
pass 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: if sign:
signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True) signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True)
signer = self._crypto.pubKey signer = self._crypto.pubKey
@ -692,8 +690,11 @@ class Core:
if len(jsonMeta) > 1000: if len(jsonMeta) > 1000:
raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes') raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes')
user = onionrusers.OnionrUser(self, symKey)
# encrypt block metadata/sig/content # encrypt block metadata/sig/content
if encryptType == 'sym': if encryptType == 'sym':
if len(symKey) < self.requirements.passwordLength: if len(symKey) < self.requirements.passwordLength:
raise onionrexceptions.SecurityError('Weak encryption key') raise onionrexceptions.SecurityError('Weak encryption key')
jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True).decode() 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() signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True).decode()
elif encryptType == 'asym': elif encryptType == 'asym':
if self._utils.validatePubKey(asymPeer): 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() jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True, anonymous=True).decode()
data = self._crypto.pubKeyEncrypt(data, 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() signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True, anonymous=True).decode()
@ -714,6 +717,11 @@ class Core:
metadata['sig'] = signature metadata['sig'] = signature
metadata['signer'] = signer metadata['signer'] = signer
metadata['time'] = str(self._utils.getEpoch()) 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 # send block data (and metadata) to POW module to get tokenized block data
proof = onionrproofs.POW(metadata, data) proof = onionrproofs.POW(metadata, data)
@ -721,7 +729,8 @@ class Core:
if payload != False: if payload != False:
retData = self.setData(payload) retData = self.setData(payload)
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.daemonQueueAdd('uploadBlock', retData) self.daemonQueueAdd('uploadBlock', retData)
if retData != False: if retData != False:

View File

@ -61,8 +61,6 @@ class DBCreator:
ID text not null, ID text not null,
name text, name text,
adders text, adders text,
blockDBHash text,
forwardKey text,
dateSeen not null, dateSeen not null,
bytesStored int, bytesStored int,
trust int, trust int,
@ -70,6 +68,12 @@ class DBCreator:
hashID text, hashID text,
pow text not null); 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.commit()
conn.close() conn.close()
return return
@ -87,6 +91,7 @@ class DBCreator:
sig - optional signature by the author (not optional if author is specified) sig - optional signature by the author (not optional if author is specified)
author - multi-round partial sha3-256 hash of authors public key 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 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): if os.path.exists(self.core.blockDB):
raise Exception("Block database already exists") raise Exception("Block database already exists")
@ -101,9 +106,42 @@ class DBCreator:
dataSaved int, dataSaved int,
sig text, sig text,
author text, author text,
dateClaimed int dateClaimed int,
expire int
); );
''') ''')
conn.commit() conn.commit()
conn.close() 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()

View File

@ -18,21 +18,37 @@
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 import subprocess, os, random, sys, logger, time, signal, config, base64
from stem.control import Controller
from onionrblockapi import Block from onionrblockapi import Block
from dependencies import secrets
class NetController: class NetController:
''' '''
This class handles hidden service setup on Tor and I2P This class handles hidden service setup on Tor and I2P
''' '''
def __init__(self, hsPort): 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.readyState = False
self.socksPort = random.randint(1024, 65535) self.socksPort = random.randint(1024, 65535)
self.hsPort = hsPort self.hsPort = hsPort
self._torInstnace = '' self._torInstnace = ''
self.myID = '' 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() config.reload()
''' '''
if os.path.exists(self.torConfigLocation): if os.path.exists(self.torConfigLocation):
@ -52,13 +68,33 @@ class NetController:
if config.get('tor.v3onions'): if config.get('tor.v3onions'):
hsVer = 'HiddenServiceVersion 3' hsVer = 'HiddenServiceVersion 3'
logger.info('Using v3 onions :)') logger.info('Using v3 onions :)')
if os.path.exists(self.torConfigLocation): if os.path.exists(self.torConfigLocation):
os.remove(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) + ''' torrcData = '''SocksPort ''' + str(self.socksPort) + '''
HiddenServiceDir data/hs/ HiddenServiceDir ''' + self.dataDir + '''hs/
\n''' + hsVer + '''\n \n''' + hsVer + '''\n
HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' 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 = open(self.torConfigLocation, 'w')
torrc.write(torrcData) torrc.write(torrcData)
@ -74,20 +110,20 @@ DataDirectory data/tordata/
self.generateTorrc() self.generateTorrc()
if os.path.exists('./tor'): if os.path.exists('./tor'):
torBinary = './tor' self.torBinary = './tor'
elif os.path.exists('/usr/bin/tor'): elif os.path.exists('/usr/bin/tor'):
torBinary = '/usr/bin/tor' self.torBinary = '/usr/bin/tor'
else: else:
torBinary = 'tor' self.torBinary = 'tor'
try: 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: except FileNotFoundError:
logger.fatal("Tor was not found in your path or the Onionr directory. Please install Tor and try again.") logger.fatal("Tor was not found in your path or the Onionr directory. Please install Tor and try again.")
sys.exit(1) sys.exit(1)
else: else:
# Test Tor Version # 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''): for line in iter(torVersion.stdout.readline, b''):
if 'Tor 0.2.' in line.decode(): if 'Tor 0.2.' in line.decode():
logger.warn("Running 0.2.x Tor series, no support for v3 onion peers") 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) logger.debug('Finished starting Tor.', timestamp=True)
self.readyState = True self.readyState = True
myID = open('data/hs/hostname', 'r') myID = open(self.dataDir + 'hs/hostname', 'r')
self.myID = myID.read().replace('\n', '') self.myID = myID.read().replace('\n', '')
myID.close() myID.close()
torPidFile = open('data/torPid.txt', 'w') torPidFile = open(self.dataDir + 'torPid.txt', 'w')
torPidFile.write(str(tor.pid)) torPidFile.write(str(tor.pid))
torPidFile.close() torPidFile.close()
@ -127,7 +163,7 @@ DataDirectory data/tordata/
''' '''
try: try:
pid = open('data/torPid.txt', 'r') pid = open(self.dataDir + 'torPid.txt', 'r')
pidN = pid.read() pidN = pid.read()
pid.close() pid.close()
except FileNotFoundError: except FileNotFoundError:
@ -140,7 +176,7 @@ DataDirectory data/tordata/
try: try:
os.kill(int(pidN), signal.SIGTERM) os.kill(int(pidN), signal.SIGTERM)
os.remove('data/torPid.txt') os.remove(self.dataDir + 'torPid.txt')
except ProcessLookupError: except ProcessLookupError:
pass pass
except FileNotFoundError: except FileNotFoundError:

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/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. 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+') print('Error, Onionr requires Python 3.4+')
sys.exit(1) sys.exit(1)
import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json, getpass, sqlite3 import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json, getpass, sqlite3
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
from onionrutils import OnionrUtils
from netcontroller import NetController from netcontroller import NetController
from onionrblockapi import Block from onionrblockapi import Block
import onionrproofs import onionrproofs, onionrexceptions, onionrusers
try: try:
from urllib3.contrib.socks import SOCKSProxyManager 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)") 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_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) 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. API_VERSION = '5' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes know how to communicate without learning too much information about you.
class Onionr: class Onionr:
def __init__(self): def __init__(self):
@ -50,23 +50,31 @@ class Onionr:
Main Onionr class. This is for the CLI program, and does not handle much of the logic. Main Onionr class. This is for the CLI program, and does not handle much of the logic.
In general, external programs and plugins should not use this class. In general, external programs and plugins should not use this class.
''' '''
self.userRunDir = os.getcwd() # Directory user runs the program from
try: try:
os.chdir(sys.path[0]) os.chdir(sys.path[0])
except FileNotFoundError: except FileNotFoundError:
pass pass
try:
self.dataDir = os.environ['ONIONR_HOME']
if not self.dataDir.endswith('/'):
self.dataDir += '/'
except KeyError:
self.dataDir = 'data/'
# Load global configuration data # Load global configuration data
data_exists = os.path.exists('data/') data_exists = os.path.exists(self.dataDir)
if not data_exists: if not data_exists:
os.mkdir('data/') os.mkdir(self.dataDir)
if os.path.exists('static-data/default_config.json'): if os.path.exists('static-data/default_config.json'):
config.set_config(json.loads(open('static-data/default_config.json').read())) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it config.set_config(json.loads(open('static-data/default_config.json').read())) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it
else: else:
# the default config file doesn't exist, try hardcoded config # the default config file doesn't exist, try hardcoded config
config.set_config({'dev_mode': True, 'log': {'file': {'output': True, 'path': 'data/output.log'}, 'console': {'output': True, 'color': True}}}) config.set_config({'dev_mode': True, 'log': {'file': {'output': True, 'path': self.dataDir + 'output.log'}, 'console': {'output': True, 'color': True}}})
if not data_exists: if not data_exists:
config.save() config.save()
config.reload() # this will read the configuration file into memory config.reload() # this will read the configuration file into memory
@ -78,7 +86,7 @@ class Onionr:
settings = settings | logger.OUTPUT_TO_CONSOLE settings = settings | logger.OUTPUT_TO_CONSOLE
if config.get('log.file.output', True): if config.get('log.file.output', True):
settings = settings | logger.OUTPUT_TO_FILE settings = settings | logger.OUTPUT_TO_FILE
logger.set_file(config.get('log.file.path', '/tmp/onionr.log')) logger.set_file(config.get('log.file.path', '/tmp/onionr.log').replace('data/', self.dataDir))
logger.set_settings(settings) logger.set_settings(settings)
if str(config.get('general.dev_mode', True)).lower() == 'true': if str(config.get('general.dev_mode', True)).lower() == 'true':
@ -89,37 +97,27 @@ class Onionr:
logger.set_level(logger.LEVEL_INFO) logger.set_level(logger.LEVEL_INFO)
self.onionrCore = core.Core() self.onionrCore = core.Core()
self.onionrUtils = OnionrUtils(self.onionrCore) self.onionrUtils = onionrutils.OnionrUtils(self.onionrCore)
# Handle commands # Handle commands
self.debug = False # Whole application debugging self.debug = False # Whole application debugging
if os.path.exists('data-encrypted.dat'): # If data folder does not exist
while True: if not data_exists:
print('Enter password to decrypt:') if not os.path.exists(self.dataDir + 'blocks/'):
password = getpass.getpass() os.mkdir(self.dataDir + 'blocks/')
result = self.onionrCore.dataDirDecrypt(password)
if os.path.exists('data/'):
break
else:
logger.error('Failed to decrypt: ' + result[1], timestamp = False)
else:
# If data folder does not exist
if not data_exists:
if not os.path.exists('data/blocks/'):
os.mkdir('data/blocks/')
# Copy default plugins into plugins folder # Copy default plugins into plugins folder
if not os.path.exists(plugins.get_plugins_folder()): if not os.path.exists(plugins.get_plugins_folder()):
if os.path.exists('static-data/default-plugins/'): if os.path.exists('static-data/default-plugins/'):
names = [f for f in os.listdir("static-data/default-plugins/") if not os.path.isfile(f)] names = [f for f in os.listdir("static-data/default-plugins/") if not os.path.isfile(f)]
shutil.copytree('static-data/default-plugins/', plugins.get_plugins_folder()) shutil.copytree('static-data/default-plugins/', plugins.get_plugins_folder())
# Enable plugins # Enable plugins
for name in names: for name in names:
if not name in plugins.get_enabled_plugins(): if not name in plugins.get_enabled_plugins():
plugins.enable(name, self) plugins.enable(name, self)
for name in plugins.get_enabled_plugins(): for name in plugins.get_enabled_plugins():
if not os.path.exists(plugins.get_plugin_data_folder(name)): if not os.path.exists(plugins.get_plugin_data_folder(name)):
@ -190,6 +188,10 @@ class Onionr:
'add-file': self.addFile, 'add-file': self.addFile,
'addfile': self.addFile, 'addfile': self.addFile,
'get-file': self.getFile,
'getfile': self.getFile,
'listconn': self.listConn, 'listconn': self.listConn,
'import-blocks': self.onionrUtils.importNewBlocks, 'import-blocks': self.onionrUtils.importNewBlocks,
@ -210,7 +212,11 @@ class Onionr:
'getpass': self.printWebPassword, 'getpass': self.printWebPassword,
'get-pass': self.printWebPassword, 'get-pass': self.printWebPassword,
'getpasswd': self.printWebPassword, 'getpasswd': self.printWebPassword,
'get-passwd': self.printWebPassword 'get-passwd': self.printWebPassword,
'chat': self.startChat,
'friend': self.friendCmd
} }
self.cmdhelp = { self.cmdhelp = {
@ -228,12 +234,14 @@ class Onionr:
'add-peer': 'Adds a peer to database', 'add-peer': 'Adds a peer to database',
'list-peers': 'Displays a list of peers', 'list-peers': 'Displays a list of peers',
'add-file': 'Create an Onionr block from a file', 'add-file': 'Create an Onionr block from a file',
'get-file': 'Get a file from Onionr blocks',
'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)', 'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)',
'listconn': 'list connected peers', 'listconn': 'list connected peers',
'kex': 'exchange keys with peers (done automatically)', 'kex': 'exchange keys with peers (done automatically)',
'pex': 'exchange addresses with peers (done automatically)', 'pex': 'exchange addresses with peers (done automatically)',
'blacklist-block': 'deletes a block by hash and permanently removes it from your node', 'blacklist-block': 'deletes a block by hash and permanently removes it from your node',
'introduce': 'Introduce your node to the public Onionr network', 'introduce': 'Introduce your node to the public Onionr network',
'friend': '[add|remove] [public key/id]'
} }
# initialize plugins # initialize plugins
@ -247,19 +255,64 @@ class Onionr:
finally: finally:
self.execute(command) self.execute(command)
if not self._developmentMode:
encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory: ')
self.onionrCore.dataDirEncrypt(encryptionPassword)
shutil.rmtree('data/')
return return
''' '''
THIS SECTION HANDLES THE COMMANDS THIS SECTION HANDLES THE COMMANDS
''' '''
def startChat(self):
try:
data = json.dumps({'peer': sys.argv[2], 'reason': 'chat'})
except IndexError:
logger.error('Must specify peer to chat with.')
else:
self.onionrCore.daemonQueueAdd('startSocket', data)
def getCommands(self): def getCommands(self):
return self.cmds return self.cmds
def friendCmd(self):
'''List, add, or remove friend(s)
Changes their peer DB entry.
'''
friend = ''
try:
# Get the friend command
action = sys.argv[2]
except IndexError:
logger.info('Syntax: friend add/remove/list [address]')
else:
action = action.lower()
if action == 'list':
# List out peers marked as our friend
for friend in self.onionrCore.listPeers(randomOrder=False, trust=1):
if friend == self.onionrCore._crypto.pubKey: # do not list our key
continue
friendProfile = onionrusers.OnionrUser(self.onionrCore, friend)
logger.info(friend + ' - ' + friendProfile.getName())
elif action in ('add', 'remove'):
try:
friend = sys.argv[3]
if not self.onionrUtils.validatePubKey(friend):
raise onionrexceptions.InvalidPubkey('Public key is invalid')
if friend not in self.onionrCore.listPeers():
raise onionrexceptions.KeyNotKnown
friend = onionrusers.OnionrUser(self.onionrCore, friend)
except IndexError:
logger.error('Friend ID is required.')
except onionrexceptions.KeyNotKnown:
logger.error('That peer is not in our database')
else:
if action == 'add':
friend.setTrust(1)
logger.info('Added %s as friend.' % (friend.publicKey,))
else:
friend.setTrust(0)
logger.info('Removed %s as friend.' % (friend.publicKey,))
else:
logger.info('Syntax: friend add/remove/list [address]')
def banBlock(self): def banBlock(self):
try: try:
@ -567,7 +620,7 @@ class Onionr:
''' '''
communicatorDaemon = './communicator2.py' communicatorDaemon = './communicator2.py'
apiThread = Thread(target=api.API, args=(self.debug,)) apiThread = Thread(target=api.API, args=(self.debug,API_VERSION))
apiThread.start() apiThread.start()
try: try:
time.sleep(3) time.sleep(3)
@ -587,7 +640,7 @@ class Onionr:
logger.info('Our Public key: ' + self.onionrCore._crypto.pubKey) logger.info('Our Public key: ' + self.onionrCore._crypto.pubKey)
time.sleep(1) time.sleep(1)
#TODO make runable on windows #TODO make runable on windows
subprocess.Popen([communicatorDaemon, "run", str(net.socksPort)]) communicatorProc = subprocess.Popen([communicatorDaemon, "run", str(net.socksPort)])
# Print nice header thing :) # Print nice header thing :)
if config.get('general.display_header', True): if config.get('general.display_header', True):
self.header() self.header()
@ -596,6 +649,9 @@ class Onionr:
try: try:
while True: while True:
time.sleep(5) time.sleep(5)
# Break if communicator process ends, so we don't have left over processes
if communicatorProc.poll() is not None:
break
except KeyboardInterrupt: except KeyboardInterrupt:
self.onionrCore.daemonQueueAdd('shutdown') self.onionrCore.daemonQueueAdd('shutdown')
self.onionrUtils.localCommand('shutdown') self.onionrUtils.localCommand('shutdown')
@ -630,26 +686,23 @@ class Onionr:
# define stats messages here # define stats messages here
totalBlocks = len(Block.getBlocks()) totalBlocks = len(Block.getBlocks())
signedBlocks = len(Block.getBlocks(signed = True)) signedBlocks = len(Block.getBlocks(signed = True))
powToken = self.onionrCore._crypto.pubKeyPowToken
messages = { messages = {
# info about local client # info about local client
'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if self.onionrUtils.isCommunicatorRunning(timeout = 2) else logger.colors.fg.red + 'Offline'), 'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if self.onionrUtils.isCommunicatorRunning(timeout = 9) else logger.colors.fg.red + 'Offline'),
'Public Key' : self.onionrCore._crypto.pubKey, 'Public Key' : self.onionrCore._crypto.pubKey,
'POW Token' : powToken,
'Combined' : self.onionrCore._crypto.pubKey + '-' + powToken,
'Human readable public key' : self.onionrCore._utils.getHumanReadableID(), 'Human readable public key' : self.onionrCore._utils.getHumanReadableID(),
'Node Address' : self.get_hostname(), 'Node Address' : self.get_hostname(),
# file and folder size stats # file and folder size stats
'div1' : True, # this creates a solid line across the screen, a div 'div1' : True, # this creates a solid line across the screen, a div
'Total Block Size' : onionrutils.humanSize(onionrutils.size('data/blocks/')), 'Total Block Size' : onionrutils.humanSize(onionrutils.size(self.dataDir + 'blocks/')),
'Total Plugin Size' : onionrutils.humanSize(onionrutils.size('data/plugins/')), 'Total Plugin Size' : onionrutils.humanSize(onionrutils.size(self.dataDir + 'plugins/')),
'Log File Size' : onionrutils.humanSize(onionrutils.size('data/output.log')), 'Log File Size' : onionrutils.humanSize(onionrutils.size(self.dataDir + 'output.log')),
# count stats # count stats
'div2' : True, 'div2' : True,
'Known Peers Count' : str(len(self.onionrCore.listPeers()) - 1), 'Known Peers Count' : str(len(self.onionrCore.listPeers()) - 1),
'Enabled Plugins Count' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir('data/plugins/'))), 'Enabled Plugins Count' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(self.dataDir + 'plugins/'))),
'Known Blocks Count' : str(totalBlocks), 'Known Blocks Count' : str(totalBlocks),
'Percent Blocks Signed' : str(round(100 * signedBlocks / max(totalBlocks, 1), 2)) + '%' 'Percent Blocks Signed' : str(round(100 * signedBlocks / max(totalBlocks, 1), 2)) + '%'
} }
@ -715,7 +768,7 @@ class Onionr:
def get_hostname(self): def get_hostname(self):
try: try:
with open('./data/hs/hostname', 'r') as hostname: with open('./' + self.dataDir + 'hs/hostname', 'r') as hostname:
return hostname.read().strip() return hostname.read().strip()
except Exception: except Exception:
return None return None
@ -735,6 +788,27 @@ class Onionr:
return columns return columns
def getFile(self):
'''
Get a file from onionr blocks
'''
try:
fileName = sys.argv[2]
bHash = sys.argv[3]
except IndexError:
logger.error("Syntax %s %s" % (sys.argv[0], '/path/to/filename <blockhash>'))
else:
print(fileName)
contents = None
if os.path.exists(fileName):
logger.error("File already exists")
return
if not self.onionrUtils.validateHash(bHash):
logger.error('Block hash is invalid')
return
Block.mergeChain(bHash, fileName)
return
def addFile(self): def addFile(self):
''' '''
Adds a file to the onionr network Adds a file to the onionr network
@ -745,8 +819,9 @@ class Onionr:
contents = None contents = None
if not os.path.exists(filename): if not os.path.exists(filename):
logger.warn('That file does not exist. Improper path?') logger.error('That file does not exist. Improper path (specify full path)?')
return
logger.info('Adding file... this might take a long time.')
try: try:
blockhash = Block.createChain(file = filename) blockhash = Block.createChain(file = filename)
logger.info('File %s saved in block %s.' % (filename, blockhash)) logger.info('File %s saved in block %s.' % (filename, blockhash))
@ -756,7 +831,6 @@ class Onionr:
logger.error('%s add-file <filename>' % sys.argv[0], timestamp = False) logger.error('%s add-file <filename>' % sys.argv[0], timestamp = False)
def openUI(self): def openUI(self):
import webbrowser
url = 'http://127.0.0.1:%s/ui/index.html?timingToken=%s' % (config.get('client.port', 59496), self.onionrUtils.getTimeBypassToken()) url = 'http://127.0.0.1:%s/ui/index.html?timingToken=%s' % (config.get('client.port', 59496), self.onionrUtils.getTimeBypassToken())
print('Opening %s ...' % url) print('Opening %s ...' % url)

View File

@ -1,5 +1,5 @@
''' '''
Onionr - P2P Microblogging Platform & Social network. Onionr - P2P Anonymous Storage Network
This file handles maintenence of a blacklist database, for blocks and peers This file handles maintenence of a blacklist database, for blocks and peers
''' '''
@ -20,7 +20,7 @@
import sqlite3, os, logger import sqlite3, os, logger
class OnionrBlackList: class OnionrBlackList:
def __init__(self, coreInst): def __init__(self, coreInst):
self.blacklistDB = 'data/blacklist.db' self.blacklistDB = coreInst.dataDir + 'blacklist.db'
self._core = coreInst self._core = coreInst
if not os.path.exists(self.blacklistDB): if not os.path.exists(self.blacklistDB):
@ -32,7 +32,8 @@ class OnionrBlackList:
retData = False retData = False
if not hashed.isalnum(): if not hashed.isalnum():
raise Exception("Hashed data is not alpha numeric") raise Exception("Hashed data is not alpha numeric")
if len(hashed) > 64:
raise Exception("Hashed data is too large")
for i in self._dbExecute("select * from blacklist where hash='%s'" % (hashed,)): for i in self._dbExecute("select * from blacklist where hash='%s'" % (hashed,)):
retData = True # this only executes if an entry is present by that hash retData = True # this only executes if an entry is present by that hash
break break
@ -95,9 +96,8 @@ class OnionrBlackList:
''' '''
# we hash the data so we can remove data entirely from our node's disk # we hash the data so we can remove data entirely from our node's disk
hashed = self._core._utils.bytesToStr(self._core._crypto.sha3Hash(data)) hashed = self._core._utils.bytesToStr(self._core._crypto.sha3Hash(data))
if len(hashed) > 64:
if self.inBlacklist(hashed): raise Exception("Hashed data is too large")
return
if not hashed.isalnum(): if not hashed.isalnum():
raise Exception("Hashed data is not alpha numeric") raise Exception("Hashed data is not alpha numeric")
@ -109,7 +109,8 @@ class OnionrBlackList:
int(expire) int(expire)
except ValueError: except ValueError:
raise Exception("expire is not int") raise Exception("expire is not int")
#TODO check for length sanity if self.inBlacklist(hashed):
return
insert = (hashed,) insert = (hashed,)
blacklistDate = self._core._utils.getEpoch() blacklistDate = self._core._utils.getEpoch()
self._dbExecute("insert into blacklist (hash, dataType, blacklistDate, expire) VALUES('%s', %s, %s, %s);" % (hashed, dataType, blacklistDate, expire)) self._dbExecute("insert into blacklist (hash, dataType, blacklistDate, expire) VALUES('%s', %s, %s, %s);" % (hashed, dataType, blacklistDate, expire))

View File

@ -1,5 +1,5 @@
''' '''
Onionr - P2P Microblogging Platform & Social network. Onionr - P2P Anonymous Storage Network
This class contains the OnionrBlocks class which is a class for working with Onionr blocks This class contains the OnionrBlocks class which is a class for working with Onionr blocks
''' '''
@ -18,14 +18,14 @@
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 core as onionrcore, logger, config, onionrexceptions, nacl.exceptions import core as onionrcore, logger, config, onionrexceptions, nacl.exceptions, onionrusers
import json, os, sys, datetime, base64 import json, os, sys, datetime, base64
class Block: class Block:
blockCacheOrder = list() # NEVER write your own code that writes to this! blockCacheOrder = list() # NEVER write your own code that writes to this!
blockCache = dict() # should never be accessed directly, look at Block.getCache() blockCache = dict() # should never be accessed directly, look at Block.getCache()
def __init__(self, hash = None, core = None, type = None, content = None): def __init__(self, hash = None, core = None, type = None, content = None, expire=None):
# take from arguments # take from arguments
# sometimes people input a bytes object instead of str in `hash` # sometimes people input a bytes object instead of str in `hash`
if (not hash is None) and isinstance(hash, bytes): if (not hash is None) and isinstance(hash, bytes):
@ -35,6 +35,7 @@ class Block:
self.core = core self.core = core
self.btype = type self.btype = type
self.bcontent = content self.bcontent = content
self.expire = expire
# initialize variables # initialize variables
self.valid = True self.valid = True
@ -90,9 +91,18 @@ class Block:
self.signature = core._crypto.pubKeyDecrypt(self.signature, anonymous=anonymous, encodedData=encodedData) self.signature = core._crypto.pubKeyDecrypt(self.signature, anonymous=anonymous, encodedData=encodedData)
self.signer = core._crypto.pubKeyDecrypt(self.signer, anonymous=anonymous, encodedData=encodedData) self.signer = core._crypto.pubKeyDecrypt(self.signer, anonymous=anonymous, encodedData=encodedData)
self.signedData = json.dumps(self.bmetadata) + self.bcontent.decode() self.signedData = json.dumps(self.bmetadata) + self.bcontent.decode()
try:
assert self.bmetadata['forwardEnc'] is True
except (AssertionError, KeyError) as e:
pass
else:
try:
self.bcontent = onionrusers.OnionrUser(self.getCore(), self.signer).forwardDecrypt(self.bcontent)
except (onionrexceptions.DecryptionError, nacl.exceptions.CryptoError) as e:
logger.error(str(e))
pass
except nacl.exceptions.CryptoError: except nacl.exceptions.CryptoError:
pass logger.debug('Could not decrypt block. Either invalid key or corrupted data')
#logger.debug('Could not decrypt block. Either invalid key or corrupted data')
else: else:
retData = True retData = True
self.decrypted = True self.decrypted = True
@ -149,7 +159,7 @@ class Block:
# read from file if it's still None # read from file if it's still None
if blockdata is None: if blockdata is None:
filelocation = 'data/blocks/%s.dat' % self.getHash() filelocation = self.core.dataDir + 'blocks/%s.dat' % self.getHash()
if readfile: if readfile:
with open(filelocation, 'rb') as f: with open(filelocation, 'rb') as f:
@ -177,6 +187,7 @@ class Block:
# signed data is jsonMeta + block content (no linebreak) # signed data is jsonMeta + block content (no linebreak)
self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent()) self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent())
self.date = self.getCore().getBlockDate(self.getHash()) self.date = self.getCore().getBlockDate(self.getHash())
self.claimedTime = self.getHeader('time', None)
if not self.getDate() is None: if not self.getDate() is None:
self.date = datetime.datetime.fromtimestamp(self.getDate()) self.date = datetime.datetime.fromtimestamp(self.getDate())
@ -226,7 +237,7 @@ class Block:
blockFile.write(self.getRaw().encode()) blockFile.write(self.getRaw().encode())
self.update() self.update()
else: else:
self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign) self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign, expire=self.getExpire())
self.update() self.update()
return self.getHash() return self.getHash()
@ -239,6 +250,15 @@ class Block:
# getters # getters
def getExpire(self):
'''
Returns the expire time for a block
Outputs:
- (int): the expire time for a block, or None
'''
return self.expire
def getHash(self): def getHash(self):
''' '''
Returns the hash of the block if saved to file Returns the hash of the block if saved to file
@ -726,7 +746,7 @@ class Block:
if type(hash) == Block: if type(hash) == Block:
blockfile = hash.getBlockFile() blockfile = hash.getBlockFile()
else: else:
blockfile = 'data/blocks/%s.dat' % hash blockfile = onionrcore.Core().dataDir + 'blocks/%s.dat' % hash
return os.path.exists(blockfile) and os.path.isfile(blockfile) return os.path.exists(blockfile) and os.path.isfile(blockfile)

49
onionr/onionrchat.py Normal file
View File

@ -0,0 +1,49 @@
'''
Onionr - P2P Anonymous Storage Network
Onionr Chat Messages
'''
'''
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 logger, time
class OnionrChat:
def __init__(self, communicatorInst):
'''OnionrChat uses onionrsockets (handled by the communicator) to exchange direct chat messages'''
self.communicator = communicatorInst
self._core = self.communicator._core
self._utils = self._core._utils
self.chats = {} # {'peer': {'date': date, message': message}}
self.chatSend = {}
def chatHandler(self):
while not self.communicator.shutdown:
for peer in self._core.socketServerConnData:
try:
assert self._core.socketReasons[peer] == "chat"
except (AssertionError, KeyError) as e:
logger.warn('Peer is not for chat')
continue
else:
self.chats[peer] = {'date': self._core.socketServerConnData[peer]['date'], 'data': self._core.socketServerConnData[peer]['data']}
logger.info("CHAT MESSAGE RECIEVED: %s" % self.chats[peer]['data'])
for peer in self.communicator.socketClient.sockets:
try:
logger.info(self.communicator.socketClient.connPool[peer]['data'])
self.communicator.socketClient.sendData(peer, "lol")
except:
pass
time.sleep(2)

View File

@ -1,5 +1,5 @@
''' '''
Onionr - P2P Microblogging Platform & Social network Onionr - P2P Anonymous Storage Network
This file handles Onionr's cryptography. This file handles Onionr's cryptography.
''' '''
@ -24,51 +24,32 @@ if sys.version_info[0] == 3 and sys.version_info[1] < 6:
from dependencies import secrets from dependencies import secrets
elif sys.version_info[0] == 3 and sys.version_info[1] >= 6: elif sys.version_info[0] == 3 and sys.version_info[1] >= 6:
import secrets import secrets
import config
class OnionrCrypto: class OnionrCrypto:
def __init__(self, coreInstance): def __init__(self, coreInstance):
config.reload()
self._core = coreInstance self._core = coreInstance
self._keyFile = 'data/keys.txt' self._keyFile = self._core.dataDir + 'keys.txt'
self.keyPowFile = 'data/keyPow.txt'
self.pubKey = None self.pubKey = None
self.privKey = None self.privKey = None
self.secrets = secrets self.secrets = secrets
self.pubKeyPowToken = None
#self.pubKeyPowHash = None
self.HASH_ID_ROUNDS = 2000 self.HASH_ID_ROUNDS = 2000
# Load our own pub/priv Ed25519 keys, gen & save them if they don't exist # Load our own pub/priv Ed25519 keys, gen & save them if they don't exist
if os.path.exists(self._keyFile): if os.path.exists(self._keyFile):
with open('data/keys.txt', 'r') as keys: with open(self._core.dataDir + 'keys.txt', 'r') as keys:
keys = keys.read().split(',') keys = keys.read().split(',')
self.pubKey = keys[0] self.pubKey = keys[0]
self.privKey = keys[1] self.privKey = keys[1]
try:
with open(self.keyPowFile, 'r') as powFile:
data = powFile.read()
self.pubKeyPowToken = data
except (FileNotFoundError, IndexError):
pass
else: else:
keys = self.generatePubKey() keys = self.generatePubKey()
self.pubKey = keys[0] self.pubKey = keys[0]
self.privKey = keys[1] self.privKey = keys[1]
with open(self._keyFile, 'w') as keyfile: with open(self._keyFile, 'w') as keyfile:
keyfile.write(self.pubKey + ',' + self.privKey) keyfile.write(self.pubKey + ',' + self.privKey)
with open(self.keyPowFile, 'w') as keyPowFile:
proof = onionrproofs.DataPOW(self.pubKey)
logger.info('Doing necessary work to insert our public key')
while True:
time.sleep(0.2)
powToken = proof.getResult()
if powToken != False:
break
keyPowFile.write(base64.b64encode(powToken[1]).decode())
self.pubKeyPowToken = powToken[1]
self.pubKeyPowHash = powToken[0]
return return
def edVerify(self, data, key, sig, encodedData=True): def edVerify(self, data, key, sig, encodedData=True):
@ -76,7 +57,10 @@ class OnionrCrypto:
try: try:
key = nacl.signing.VerifyKey(key=key, encoder=nacl.encoding.Base32Encoder) key = nacl.signing.VerifyKey(key=key, encoder=nacl.encoding.Base32Encoder)
except nacl.exceptions.ValueError: except nacl.exceptions.ValueError:
logger.warn('Signature by unknown key (cannot reverse hash)') #logger.debug('Signature by unknown key (cannot reverse hash)')
return False
except binascii.Error:
logger.warn('Could not load key for verification, invalid padding')
return False return False
retData = False retData = False
sig = base64.b64decode(sig) sig = base64.b64decode(sig)
@ -125,7 +109,7 @@ class OnionrCrypto:
encoding = nacl.encoding.RawEncoder encoding = nacl.encoding.RawEncoder
if self.privKey != None and not anonymous: if self.privKey != None and not anonymous:
ownKey = nacl.signing.SigningKey(seed=self.privKey, encoder=nacl.encoding.Base32Encoder) ownKey = nacl.signing.SigningKey(seed=self.privKey, encoder=nacl.encoding.Base32Encoder).to_curve25519_private_key()
key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key() key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key()
ourBox = nacl.public.Box(ownKey, key) ourBox = nacl.public.Box(ownKey, key)
retVal = ourBox.encrypt(data.encode(), encoder=encoding) retVal = ourBox.encrypt(data.encode(), encoder=encoding)
@ -139,9 +123,9 @@ class OnionrCrypto:
retVal = anonBox.encrypt(data, encoder=encoding) retVal = anonBox.encrypt(data, encoder=encoding)
return retVal return retVal
def pubKeyDecrypt(self, data, pubkey='', anonymous=False, encodedData=False): def pubKeyDecrypt(self, data, pubkey='', privkey='', anonymous=False, encodedData=False):
'''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)''' '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)'''
retVal = False decrypted = False
if encodedData: if encodedData:
encoding = nacl.encoding.Base64Encoder encoding = nacl.encoding.Base64Encoder
else: else:
@ -151,30 +135,14 @@ class OnionrCrypto:
ourBox = nacl.public.Box(ownKey, pubkey) ourBox = nacl.public.Box(ownKey, pubkey)
decrypted = ourBox.decrypt(data, encoder=encoding) decrypted = ourBox.decrypt(data, encoder=encoding)
elif anonymous: elif anonymous:
anonBox = nacl.public.SealedBox(ownKey) if self._core._utils.validatePubKey(privkey):
privkey = nacl.signing.SigningKey(seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key()
anonBox = nacl.public.SealedBox(privkey)
else:
anonBox = nacl.public.SealedBox(ownKey)
decrypted = anonBox.decrypt(data, encoder=encoding) decrypted = anonBox.decrypt(data, encoder=encoding)
return decrypted return decrypted
def symmetricPeerEncrypt(self, data, peer):
'''Salsa20 encrypt data to peer (with mac)
this function does not accept a key, it is a wrapper for encryption with a peer
'''
key = self._core.getPeerInfo(4)
if type(key) != bytes:
key = self._core.getPeerInfo(2)
encrypted = self.symmetricEncrypt(data, key, encodedKey=True)
return encrypted
def symmetricPeerDecrypt(self, data, peer):
'''Salsa20 decrypt data from peer (with mac)
this function does not accept a key, it is a wrapper for encryption with a peer
'''
key = self._core.getPeerInfo(4)
if type(key) != bytes:
key = self._core.getPeerInfo(2)
decrypted = self.symmetricDecrypt(data, key, encodedKey=True)
return decrypted
def symmetricEncrypt(self, data, key, encodedKey=False, returnEncoded=True): def symmetricEncrypt(self, data, key, encodedKey=False, returnEncoded=True):
'''Encrypt data to a 32-byte key (Salsa20-Poly1305 MAC)''' '''Encrypt data to a 32-byte key (Salsa20-Poly1305 MAC)'''
if encodedKey: if encodedKey:
@ -282,7 +250,8 @@ class OnionrCrypto:
pass pass
difficulty = math.floor(dataLen / 1000000) difficulty = math.floor(dataLen / 1000000)
if difficulty < int(config.get('general.minimum_block_pow')):
difficulty = int(config.get('general.minimum_block_pow'))
mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode() mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
puzzle = mainHash[:difficulty] puzzle = mainHash[:difficulty]

View File

@ -1,5 +1,5 @@
''' '''
Onionr - P2P Microblogging Platform & Social network. Onionr - P2P Anonymous Storage Network
Contains the CommunicatorUtils class which contains useful functions for the communicator daemon Contains the CommunicatorUtils class which contains useful functions for the communicator daemon
''' '''
@ -17,7 +17,8 @@
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 onionrexceptions, onionrpeers, onionrproofs, base64, logger import onionrexceptions, onionrpeers, onionrproofs, base64, logger, onionrusers, sqlite3
from dependencies import secrets
class DaemonTools: class DaemonTools:
def __init__(self, daemon): def __init__(self, daemon):
self.daemon = daemon self.daemon = daemon
@ -51,23 +52,80 @@ class DaemonTools:
logger.info('Announcing node to ' + url) logger.info('Announcing node to ' + url)
if self.daemon._core._utils.doPostRequest(url, data) == 'Success': if self.daemon._core._utils.doPostRequest(url, data) == 'Success':
logger.info('Successfully introduced node to ' + peer)
retData = True retData = True
self.daemon.decrementThreadCount('announceNode') self.daemon.decrementThreadCount('announceNode')
return retData return retData
def netCheck(self): def netCheck(self):
'''Check if we are connected to the internet or not when we can't connect to any peers''' '''Check if we are connected to the internet or not when we can't connect to any peers'''
if len(self.daemon.onlinePeers) != 0: if len(self.daemon.onlinePeers) == 0:
if not self.daemon._core._utils.checkNetwork(torPort=self.daemon.proxyPort): if not self.daemon._core._utils.checkNetwork(torPort=self.daemon.proxyPort):
logger.warn('Network check failed, are you connected to the internet?') logger.warn('Network check failed, are you connected to the internet?')
self.daemon.isOnline = False self.daemon.isOnline = False
self.daemon.decrementThreadCount('netCheck') self.daemon.decrementThreadCount('netCheck')
def cleanOldBlocks(self): def cleanOldBlocks(self):
'''Delete old blocks if our disk allocation is full/near full''' '''Delete old blocks if our disk allocation is full/near full, and also expired blocks'''
while self.daemon._core._utils.storageCounter.isFull(): while self.daemon._core._utils.storageCounter.isFull():
oldest = self.daemon._core.getBlockList()[0] oldest = self.daemon._core.getBlockList()[0]
self.daemon._core._blacklist.addToDB(oldest) self.daemon._core._blacklist.addToDB(oldest)
self.daemon._core.removeBlock(oldest) self.daemon._core.removeBlock(oldest)
logger.info('Deleted block: %s' % (oldest,)) logger.info('Deleted block: %s' % (oldest,))
self.daemon.decrementThreadCount('cleanOldBlocks') # Delete expired blocks
for bHash in self.daemon._core.getExpiredBlocks():
self.daemon._core._blacklist.addToDB(bHash)
self.daemon._core.removeBlock(bHash)
self.daemon.decrementThreadCount('cleanOldBlocks')
def cleanKeys(self):
'''Delete expired forward secrecy keys'''
conn = sqlite3.connect(self.daemon._core.peerDB, timeout=10)
c = conn.cursor()
time = self.daemon._core._utils.getEpoch()
deleteKeys = []
for entry in c.execute("SELECT * FROM forwardKeys where expire <= ?", (time,)):
logger.info(entry[1])
deleteKeys.append(entry[1])
for key in deleteKeys:
logger.info('Deleting forward key '+ key)
c.execute("DELETE from forwardKeys where forwardKey = ?", (key,))
conn.commit()
conn.close()
onionrusers.deleteExpiredKeys(self.daemon._core)
self.daemon.decrementThreadCount('cleanKeys')
def cooldownPeer(self):
'''Randomly add an online peer to cooldown, so we can connect a new one'''
onlinePeerAmount = len(self.daemon.onlinePeers)
minTime = 300
cooldownTime = 600
toCool = ''
tempConnectTimes = dict(self.daemon.connectTimes)
# Remove peers from cooldown that have been there long enough
tempCooldown = dict(self.daemon.cooldownPeer)
for peer in tempCooldown:
if (self.daemon._core._utils.getEpoch() - tempCooldown[peer]) >= cooldownTime:
del self.daemon.cooldownPeer[peer]
# Cool down a peer, if we have max connections alive for long enough
if onlinePeerAmount >= self.daemon._core.config.get('peers.maxConnect'):
finding = True
while finding:
try:
toCool = min(tempConnectTimes, key=tempConnectTimes.get)
if (self.daemon._core._utils.getEpoch() - tempConnectTimes[toCool]) < minTime:
del tempConnectTimes[toCool]
else:
finding = False
except ValueError:
break
else:
self.daemon.removeOnlinePeer(toCool)
self.daemon.cooldownPeer[toCool] = self.daemon._core._utils.getEpoch()
self.daemon.decrementThreadCount('cooldownPeer')

View File

@ -1,5 +1,5 @@
''' '''
Onionr - P2P Microblogging Platform & Social network. Onionr - P2P Anonymous Storage Network
This file contains exceptions for onionr This file contains exceptions for onionr
''' '''
@ -34,6 +34,12 @@ class OnlinePeerNeeded(Exception):
class InvalidPubkey(Exception): class InvalidPubkey(Exception):
pass pass
class KeyNotKnown(Exception):
pass
class DecryptionError(Exception):
pass
# block exceptions # block exceptions
class InvalidMetadata(Exception): class InvalidMetadata(Exception):
pass pass
@ -59,7 +65,15 @@ class MissingPort(Exception):
class InvalidAddress(Exception): class InvalidAddress(Exception):
pass pass
class InvalidAPIVersion(Exception):
pass
# file exceptions # file exceptions
class DiskAllocationReached(Exception): class DiskAllocationReached(Exception):
pass
# onionrsocket exceptions
class MissingAddress(Exception):
pass pass

View File

@ -1,19 +0,0 @@
'''
Onionr - P2P Microblogging Platform & Social network
Funcitons for talking to I2P
'''
'''
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/>.
'''

View File

@ -1,5 +1,5 @@
''' '''
Onionr - P2P Microblogging Platform & Social network. Onionr - P2P Anonymous Storage Network
This file contains both the PeerProfiles class for network profiling of Onionr nodes This file contains both the PeerProfiles class for network profiling of Onionr nodes
''' '''
@ -90,13 +90,15 @@ def peerCleanup(coreInst):
if PeerProfiles(address, coreInst).score < minScore: if PeerProfiles(address, coreInst).score < minScore:
coreInst.removeAddress(address) coreInst.removeAddress(address)
try: try:
if (coreInst._utils.getEpoch() - coreInst.getPeerInfo(address, 4)) >= 600: if (int(coreInst._utils.getEpoch()) - int(coreInst.getPeerInfo(address, 'dateSeen'))) >= 600:
expireTime = 600 expireTime = 600
else: else:
expireTime = 86400 expireTime = 86400
coreInst._blacklist.addToDB(address, dataType=1, expire=expireTime) coreInst._blacklist.addToDB(address, dataType=1, expire=expireTime)
except sqlite3.IntegrityError: #TODO just make sure its not a unique constraint issue except sqlite3.IntegrityError: #TODO just make sure its not a unique constraint issue
pass pass
except ValueError:
pass
logger.warn('Removed address ' + address + '.') logger.warn('Removed address ' + address + '.')
# Unban probably not malicious peers TODO improve # Unban probably not malicious peers TODO improve

View File

@ -21,7 +21,14 @@
import os, re, importlib, config, logger import os, re, importlib, config, logger
import onionrevents as events import onionrevents as events
_pluginsfolder = 'data/plugins/' try:
dataDir = os.environ['ONIONR_HOME']
if not dataDir.endswith('/'):
dataDir += '/'
except KeyError:
dataDir = 'data/'
_pluginsfolder = dataDir + 'plugins/'
_instances = dict() _instances = dict()
def reload(onionr = None, stop_event = True): def reload(onionr = None, stop_event = True):
@ -217,7 +224,7 @@ def get_plugin_data_folder(name, absolute = True):
Returns the location of a plugin's data folder Returns the location of a plugin's data folder
''' '''
return get_plugins_folder(name, absolute) + 'data/' return get_plugins_folder(name, absolute) + dataDir
def check(): def check():
''' '''

View File

@ -1,5 +1,5 @@
''' '''
Onionr - P2P Microblogging Platform & Social network Onionr - P2P Anonymous Storage Network
Proof of work module Proof of work module
''' '''
@ -19,7 +19,30 @@
''' '''
import nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, logger, sys, base64, json import nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, logger, sys, base64, json
import core import core, config
def getHashDifficulty(h):
'''
Return the amount of leading zeroes in a hex hash string (h)
'''
difficulty = 0
assert type(h) is str
for character in h:
if character == '0':
difficulty += 1
return difficulty
def hashMeetsDifficulty(h):
'''
Return bool for a hash string to see if it meets pow difficulty defined in config
'''
config.reload()
hashDifficulty = getHashDifficulty(h)
expected = int(config.get('minimum_block_pow'))
if hashDifficulty >= expected:
return True
else:
return False
class DataPOW: class DataPOW:
def __init__(self, data, forceDifficulty=0, threadCount = 5): def __init__(self, data, forceDifficulty=0, threadCount = 5):
@ -27,6 +50,7 @@ class DataPOW:
self.difficulty = 0 self.difficulty = 0
self.data = data self.data = data
self.threadCount = threadCount self.threadCount = threadCount
config.reload()
if forceDifficulty == 0: if forceDifficulty == 0:
dataLen = sys.getsizeof(data) dataLen = sys.getsizeof(data)
@ -77,7 +101,6 @@ class DataPOW:
endTime = math.floor(time.time()) endTime = math.floor(time.time())
if self.reporting: if self.reporting:
logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True) logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
logger.debug('Random value was: %s' % base64.b64encode(rand).decode())
self.result = (token, rand) self.result = (token, rand)
def shutdown(self): def shutdown(self):
@ -128,7 +151,7 @@ class POW:
dataLen = len(data) + len(json.dumps(metadata)) dataLen = len(data) + len(json.dumps(metadata))
self.difficulty = math.floor(dataLen / 1000000) self.difficulty = math.floor(dataLen / 1000000)
if self.difficulty <= 2: if self.difficulty <= 2:
self.difficulty = 4 self.difficulty = int(config.get('general.minimum_block_pow'))
try: try:
self.data = self.data.encode() self.data = self.data.encode()
@ -144,7 +167,7 @@ class POW:
for i in range(max(1, threadCount)): for i in range(max(1, threadCount)):
t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,myCore)) t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,myCore))
t.start() t.start()
self.myCore = myCore
return return
def pow(self, reporting = False, myCore = None): def pow(self, reporting = False, myCore = None):
@ -177,7 +200,6 @@ class POW:
endTime = math.floor(time.time()) endTime = math.floor(time.time())
if self.reporting: if self.reporting:
logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True) logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
logger.debug('Random value was: %s' % base64.b64encode(rand).decode())
def shutdown(self): def shutdown(self):
self.hashing = False self.hashing = False

171
onionr/onionrsockets.py Normal file
View File

@ -0,0 +1,171 @@
'''
Onionr - P2P Anonymous Storage Network
Onionr Socket interface
'''
'''
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 stem.control
import threading
import socks, config, uuid
import onionrexceptions, time, requests, onionrblockapi, logger
from dependencies import secrets
from gevent.pywsgi import WSGIServer
from flask import request, Response, abort
import flask
class OnionrSocketServer:
def __init__(self, coreInst):
self._core = coreInst
app = flask.Flask(__name__)
self._core.socketServerConnData = {}
self.bindPort = 0
self.sockets = {}
while self.bindPort < 1024:
self.bindPort = secrets.randbelow(65535)
self.responseData = {}
threading.Thread(target=self.detectShutdown).start()
threading.Thread(target=self.socketStarter).start()
app = flask.Flask(__name__)
self.http_server = WSGIServer(('127.0.0.1', self.bindPort), app)
self.http_server.serve_forever()
@app.route('/dc/', methods=['POST'])
def acceptConn(self):
data = request.form['data']
data = self._core._utils.bytesTorStr(data)
data = {'date': self._core._utils.getEpoch(), 'data': data}
myPeer = ''
retData = ''
for peer in self.sockets:
if self.sockets[peer] == request.host:
myPeer = peer
break
else:
return ""
if request.host in self.sockets:
self._core.socketServerConnData[myPeer].append(data)
else:
self._core.socketServerConnData[myPeer] = [data]
try:
retData = self._core.socketServerResponseData[myPeer]
except KeyError:
pass
else:
self._core.socketServerResponseData[myPeer] = ''
return retData
def socketStarter(self):
while not self._core.killSockets:
try:
self.addSocket(self._core.startSocket['peer'], reason=self._core.startSocket['reason'])
except KeyError:
pass
else:
logger.info('%s socket started with %s' % (self._core.startSocket['reason'], self._core.startSocket['peer']))
self._core.startSocket = {}
time.sleep(1)
def detectShutdown(self):
while not self._core.killSockets:
time.sleep(5)
logger.info('Killing socket server')
self.http_server.stop()
def addSocket(self, peer, reason=''):
bindPort = 1337
assert len(reason) <= 12
with stem.control.Controller.from_port(port=config.get('tor.controlPort')) as controller:
controller.authenticate(config.get('tor.controlpassword'))
socket = controller.create_ephemeral_hidden_service({80: bindPort}, await_publication = True)
self.sockets[peer] = socket.service_id + '.onion'
self.responseData[socket.service_id + '.onion'] = ''
self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason, 'address': socket.service_id + '.onion'})
self._core.socketReasons[peer] = reason
return
class OnionrSocketClient:
def __init__(self, coreInst):
self.sockets = {} # pubkey: tor address
self.connPool = {}
self.sendData = {}
self._core = coreInst
self.response = ''
self.request = ''
self.connected = False
self.killSocket = False
def startSocket(self, peer, reason):
address = ''
logger.info('Trying to find socket server for %s' % (peer,))
# Find the newest open socket for a given peer
for block in self._core.getBlocksByType('socket'):
block = onionrblockapi.Block(block, core=self._core)
if block.decrypt():
theSigner = block.signer
try:
theSigner = theSigner.decode()
except AttributeError:
pass
if block.verifySig() and theSigner == peer:
address = block.getMetadata('address')
if self._core._utils.validateID(address):
# If we got their address, it is valid, and verified, we can break out
if block.getMetadata('reason') == reason:
break
else:
logger.error('The socket the peer opened is not for %s' % (reason,))
else:
logger.error('Peer transport id is invalid for socket: %s' % (address,))
address = ''
else:
logger.warn('Block has invalid sig or id, was for %s' % (theSigner,))
if address != '':
logger.info('%s socket client started with %s' % (reason, peer))
self.sockets[peer] = address
data = 'hey'
while not self.killSocket:
try:
data = self.sendData[peer]
logger.info('Sending %s to %s' % (data, peer))
except KeyError:
pass
else:
self.sendData[peer] = ''
postData = {'data': data}
self.connPool[peer] = {'date': self._core._utils.getEpoch(), 'data': self._core._utils.doPostRequest('http://' + address + '/dc/', data=postData)}
time.sleep(2)
def getResponse(self, peer):
retData = ''
try:
retData = self.connPool[peer]
except KeyError:
pass
return
def sendData(self, peer, data):
self.sendData[peer] = data

189
onionr/onionrusers.py Normal file
View File

@ -0,0 +1,189 @@
'''
Onionr - P2P Anonymous Storage Network
Contains abstractions for interacting with users of Onionr
'''
'''
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 onionrblockapi, logger, onionrexceptions, json, sqlite3
import nacl.exceptions
def deleteExpiredKeys(coreInst):
# Fetch the keys we generated for the peer, that are still around
conn = sqlite3.connect(coreInst.forwardKeysFile, timeout=10)
c = conn.cursor()
curTime = coreInst._utils.getEpoch()
c.execute("DELETE from myForwardKeys where expire <= ?", (curTime,))
conn.commit()
conn.execute("VACUUM")
conn.close()
return
class OnionrUser:
def __init__(self, coreInst, publicKey):
self.trust = 0
self._core = coreInst
self.publicKey = publicKey
self.trust = self._core.getPeerInfo(self.publicKey, 'trust')
return
def setTrust(self, newTrust):
'''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate'''
self._core.setPeerInfo(self.publicKey, 'trust', newTrust)
def isFriend(self):
if self._core.getPeerInfo(self.publicKey, 'trust') == 1:
return True
return False
def getName(self):
retData = 'anonymous'
name = self._core.getPeerInfo(self.publicKey, 'name')
try:
if len(name) > 0:
retData = name
except ValueError:
pass
return retData
def encrypt(self, data):
encrypted = coreInst._crypto.pubKeyEncrypt(data, self.publicKey, encodedData=True)
return encrypted
def decrypt(self, data, anonymous=True):
decrypted = coreInst._crypto.pubKeyDecrypt(data, self.publicKey, encodedData=True)
return decrypted
def forwardEncrypt(self, data):
retData = ''
forwardKey = self._getLatestForwardKey()
#logger.info('using ' + forwardKey)
if self._core._utils.validatePubKey(forwardKey):
retData = self._core._crypto.pubKeyEncrypt(data, forwardKey, encodedData=True, anonymous=True)
else:
raise onionrexceptions.InvalidPubkey("No valid forward key available for this user")
#self.generateForwardKey()
return (retData, forwardKey)
def forwardDecrypt(self, encrypted):
retData = ""
#logger.error(self.publicKey)
#logger.error(self.getGeneratedForwardKeys(False))
for key in self.getGeneratedForwardKeys(False):
logger.info(encrypted)
try:
retData = self._core._crypto.pubKeyDecrypt(encrypted, privkey=key[1], anonymous=True, encodedData=True)
except nacl.exceptions.CryptoError:
retData = False
else:
break
else:
raise onionrexceptions.DecryptionError("Could not decrypt forward secrecy content")
return retData
def _getLatestForwardKey(self):
# Get the latest forward secrecy key for a peer
key = ""
conn = sqlite3.connect(self._core.peerDB, timeout=10)
c = conn.cursor()
for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ? order by date desc", (self.publicKey,)):
key = row[0]
break
conn.commit()
conn.close()
return key
def _getForwardKeys(self):
conn = sqlite3.connect(self._core.peerDB, timeout=10)
c = conn.cursor()
keyList = []
for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ? order by date desc", (self.publicKey,)):
key = row[0]
keyList.append(key)
conn.commit()
conn.close()
return list(keyList)
def generateForwardKey(self, expire=604800):
# Generate a forward secrecy key for the peer
conn = sqlite3.connect(self._core.forwardKeysFile, timeout=10)
c = conn.cursor()
# Prepare the insert
time = self._core._utils.getEpoch()
newKeys = self._core._crypto.generatePubKey()
newPub = self._core._utils.bytesToStr(newKeys[0])
newPriv = self._core._utils.bytesToStr(newKeys[1])
time = self._core._utils.getEpoch()
command = (self.publicKey, newPub, newPriv, time, expire + time)
c.execute("INSERT INTO myForwardKeys VALUES(?, ?, ?, ?, ?);", command)
conn.commit()
conn.close()
return newPub
def getGeneratedForwardKeys(self, genNew=True):
# Fetch the keys we generated for the peer, that are still around
conn = sqlite3.connect(self._core.forwardKeysFile, timeout=10)
c = conn.cursor()
pubkey = self.publicKey
pubkey = self._core._utils.bytesToStr(pubkey)
command = (pubkey,)
keyList = [] # list of tuples containing pub, private for peer
for result in c.execute("SELECT * FROM myForwardKeys where peer=?", command):
keyList.append((result[1], result[2]))
if len(keyList) == 0:
if genNew:
self.generateForwardKey()
keyList = self.getGeneratedForwardKeys()
return list(keyList)
def addForwardKey(self, newKey, expire=604800):
if not self._core._utils.validatePubKey(newKey):
raise onionrexceptions.InvalidPubkey
# Add a forward secrecy key for the peer
conn = sqlite3.connect(self._core.peerDB, timeout=10)
c = conn.cursor()
# Prepare the insert
time = self._core._utils.getEpoch()
command = (self.publicKey, newKey, time, time + expire)
c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?, ?);", command)
conn.commit()
conn.close()
return
def findAndSetID(self):
'''Find any info about the user from existing blocks and cache it to their DB entry'''
infoBlocks = []
for bHash in self._core.getBlocksByType('userInfo'):
block = onionrblockapi.Block(bHash, core=self._core)
if block.signer == self.publicKey:
if block.verifySig():
newName = block.getMetadata('name')
if newName.isalnum():
logger.info('%s is now using the name %s.' % (self.publicKey, self._core._utils.escapeAnsi(newName)))
self._core.setPeerInfo(self.publicKey, 'name', newName)
else:
raise onionrexceptions.InvalidPubkey

View File

@ -22,8 +22,10 @@ import getpass, sys, requests, os, socket, hashlib, logger, sqlite3, config, bin
import nacl.signing, nacl.encoding import nacl.signing, nacl.encoding
from onionrblockapi import Block from onionrblockapi import Block
import onionrexceptions import onionrexceptions
from onionr import API_VERSION
from defusedxml import minidom from defusedxml import minidom
import pgpwords, storagecounter import onionrevents
import pgpwords, onionrusers, storagecounter
if sys.version_info < (3, 6): if sys.version_info < (3, 6):
try: try:
import sha3 import sha3
@ -36,20 +38,23 @@ class OnionrUtils:
Various useful functions for validating things, etc functions, connectivity Various useful functions for validating things, etc functions, connectivity
''' '''
def __init__(self, coreInstance): def __init__(self, coreInstance):
self.fingerprintFile = 'data/own-fingerprint.txt' #self.fingerprintFile = 'data/own-fingerprint.txt' #TODO Remove since probably not needed
self._core = coreInstance self._core = coreInstance # onionr core instance
self.timingToken = '' self.timingToken = '' # for when we make local connections to our http api, to bypass timing attack defense mechanism
self.avoidDupe = [] # list used to prevent duplicate requests per peer for certain actions self.avoidDupe = [] # list used to prevent duplicate requests per peer for certain actions
self.peerProcessing = {} # dict of current peer actions: peer, actionList self.peerProcessing = {} # dict of current peer actions: peer, actionList
self.storageCounter = storagecounter.StorageCounter(self._core) self.storageCounter = storagecounter.StorageCounter(self._core) # used to keep track of how much data onionr is using on disk
config.reload() config.reload() # onionr config
return return
def getTimeBypassToken(self): def getTimeBypassToken(self):
'''
Load our timingToken from disk for faster local HTTP API
'''
try: try:
if os.path.exists('data/time-bypass.txt'): if os.path.exists(self._core.dataDir + 'time-bypass.txt'):
with open('data/time-bypass.txt', 'r') as bypass: with open(self._core.dataDir + 'time-bypass.txt', 'r') as bypass:
self.timingToken = bypass.read() self.timingToken = bypass.read()
except Exception as error: except Exception as error:
logger.error('Failed to fetch time bypass token.', error = error) logger.error('Failed to fetch time bypass token.', error = error)
@ -63,22 +68,6 @@ class OnionrUtils:
epoch = self.getEpoch() epoch = self.getEpoch()
return epoch - (epoch % roundS) return epoch - (epoch % roundS)
def incrementAddressSuccess(self, address):
'''
Increase the recorded sucesses for an address
'''
increment = self._core.getAddressInfo(address, 'success') + 1
self._core.setAddressInfo(address, 'success', increment)
return
def decrementAddressSuccess(self, address):
'''
Decrease the recorded sucesses for an address
'''
increment = self._core.getAddressInfo(address, 'success') - 1
self._core.setAddressInfo(address, 'success', increment)
return
def mergeKeys(self, newKeyList): def mergeKeys(self, newKeyList):
''' '''
Merge ed25519 key list to our database, comma seperated string Merge ed25519 key list to our database, comma seperated string
@ -88,6 +77,7 @@ class OnionrUtils:
if newKeyList != False: if newKeyList != False:
for key in newKeyList.split(','): for key in newKeyList.split(','):
key = key.split('-') key = key.split('-')
# Test if key is valid
try: try:
if len(key[0]) > 60 or len(key[1]) > 1000: if len(key[0]) > 60 or len(key[1]) > 1000:
logger.warn('%s or its pow value is too large.' % key[0]) logger.warn('%s or its pow value is too large.' % key[0])
@ -95,17 +85,24 @@ class OnionrUtils:
except IndexError: except IndexError:
logger.warn('No pow token') logger.warn('No pow token')
continue continue
#powHash = self._core._crypto.blake2bHash(base64.b64decode(key[1]) + self._core._crypto.blake2bHash(key[0].encode())) try:
value = base64.b64decode(key[1]) value = base64.b64decode(key[1])
except binascii.Error:
continue
# Load the pow token
hashedKey = self._core._crypto.blake2bHash(key[0]) hashedKey = self._core._crypto.blake2bHash(key[0])
powHash = self._core._crypto.blake2bHash(value + hashedKey) powHash = self._core._crypto.blake2bHash(value + hashedKey)
try: try:
powHash = powHash.encode() powHash = powHash.encode()
except AttributeError: except AttributeError:
pass pass
# if POW meets required difficulty, TODO make configurable/dynamic
if powHash.startswith(b'0000'): if powHash.startswith(b'0000'):
# if we don't already have the key and its not our key, add it.
if not key[0] in self._core.listPeers(randomOrder=False) and type(key) != None and key[0] != self._core._crypto.pubKey: if not key[0] in self._core.listPeers(randomOrder=False) and type(key) != None and key[0] != self._core._crypto.pubKey:
if self._core.addPeer(key[0], key[1]): if self._core.addPeer(key[0], key[1]):
# Check if the peer has a set username already
onionrusers.OnionrUser(self._core, key[0]).findAndSetID()
retVal = True retVal = True
else: else:
logger.warn("Failed to add key") logger.warn("Failed to add key")
@ -147,7 +144,7 @@ class OnionrUtils:
def getMyAddress(self): def getMyAddress(self):
try: try:
with open('./data/hs/hostname', 'r') as hostname: with open('./' + self._core.dataDir + 'hs/hostname', 'r') as hostname:
return hostname.read().strip() return hostname.read().strip()
except Exception as error: except Exception as error:
logger.error('Failed to read my address.', error = error) logger.error('Failed to read my address.', error = error)
@ -162,7 +159,7 @@ class OnionrUtils:
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.
try: try:
with open('data/host.txt', 'r') as host: with open(self._core.dataDir + 'host.txt', 'r') as host:
hostname = host.read() hostname = host.read()
except FileNotFoundError: except FileNotFoundError:
return False return False
@ -266,13 +263,38 @@ class OnionrUtils:
''' '''
myBlock = Block(blockHash, self._core) myBlock = Block(blockHash, self._core)
if myBlock.isEncrypted: if myBlock.isEncrypted:
myBlock.decrypt() #pass
blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks logger.warn(myBlock.decrypt())
try: if (myBlock.isEncrypted and myBlock.decrypted) or (not myBlock.isEncrypted):
if len(blockType) <= 10: blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks
self._core.updateBlockInfo(blockHash, 'dataType', blockType) signer = self.bytesToStr(myBlock.signer)
except TypeError: valid = myBlock.verifySig()
pass
logger.info('Checking for fs key')
if myBlock.getMetadata('newFSKey') is not None:
onionrusers.OnionrUser(self._core, signer).addForwardKey(myBlock.getMetadata('newFSKey'))
else:
logger.warn('FS not used for this encrypted block')
logger.info(myBlock.bmetadata)
try:
if len(blockType) <= 10:
self._core.updateBlockInfo(blockHash, 'dataType', blockType)
onionrevents.event('processblocks', data = {'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid}, onionr = None)
except TypeError:
logger.warn("Missing block information")
pass
# Set block expire time if specified
try:
expireTime = myBlock.getHeader('expire')
assert len(str(int(expireTime))) < 20 # test that expire time is an integer of sane length (for epoch)
except (AssertionError, ValueError, TypeError) as e:
pass
else:
self._core.updateBlockInfo(blockHash, 'expire', expireTime)
else:
logger.info(myBlock.isEncrypted)
logger.debug('Not processing metadata on encrypted block we cannot decrypt.')
def escapeAnsi(self, line): def escapeAnsi(self, line):
''' '''
@ -361,15 +383,26 @@ class OnionrUtils:
logger.warn('Block has invalid metadata key ' + i) logger.warn('Block has invalid metadata key ' + i)
break break
else: else:
if self._core.requirements.blockMetadataLengths[i] < len(metadata[i]): testData = metadata[i]
try:
testData = len(testData)
except (TypeError, AttributeError) as e:
testData = len(str(testData))
if self._core.requirements.blockMetadataLengths[i] < testData:
logger.warn('Block metadata key ' + i + ' exceeded maximum size') logger.warn('Block metadata key ' + i + ' exceeded maximum size')
break break
if i == 'time': if i == 'time':
if not self.isIntegerString(metadata[i]): if not self.isIntegerString(metadata[i]):
logger.warn('Block metadata time stamp is not integer string') logger.warn('Block metadata time stamp is not integer string')
break break
elif i == 'expire':
try:
assert int(metadata[i]) > self.getEpoch()
except AssertionError:
logger.warn('Block is expired')
break
else: else:
# if metadata loop gets no errors, it does not break, therefore metadata is valid # if metadata loop gets no errors, it does not break, therefore metadata is valid
# make sure we do not have another block with the same data content (prevent data duplication and replay attacks) # make sure we do not have another block with the same data content (prevent data duplication and replay attacks)
nonce = self._core._utils.bytesToStr(self._core._crypto.sha3Hash(blockData)) nonce = self._core._utils.bytesToStr(self._core._crypto.sha3Hash(blockData))
try: try:
@ -455,6 +488,12 @@ class OnionrUtils:
retVal = False retVal = False
if not idNoDomain.isalnum(): if not idNoDomain.isalnum():
retVal = False retVal = False
# Validate address is valid base32 (when capitalized and minus extension); v2/v3 onions and .b32.i2p use base32
try:
base64.b32decode(idNoDomain.upper().encode())
except binascii.Error:
retVal = False
return retVal return retVal
except: except:
@ -478,7 +517,7 @@ class OnionrUtils:
def isCommunicatorRunning(self, timeout = 5, interval = 0.1): def isCommunicatorRunning(self, timeout = 5, interval = 0.1):
try: try:
runcheck_file = 'data/.runcheck' runcheck_file = self._core.dataDir + '.runcheck'
if os.path.isfile(runcheck_file): if os.path.isfile(runcheck_file):
os.remove(runcheck_file) os.remove(runcheck_file)
@ -521,6 +560,7 @@ class OnionrUtils:
if self._core._crypto.sha3Hash(newBlock.read()) == block.replace('.dat', ''): if self._core._crypto.sha3Hash(newBlock.read()) == block.replace('.dat', ''):
self._core.addToBlockDB(block.replace('.dat', ''), dataSaved=True) self._core.addToBlockDB(block.replace('.dat', ''), dataSaved=True)
logger.info('Imported block %s.' % block) logger.info('Imported block %s.' % block)
self._core._utils.processBlockMetadata(block)
else: else:
logger.warn('Failed to verify hash for %s' % block) logger.warn('Failed to verify hash for %s' % block)
@ -586,13 +626,22 @@ class OnionrUtils:
try: try:
proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)} proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
r = requests.get(url, headers=headers, proxies=proxies, allow_redirects=False, timeout=(15, 30)) r = requests.get(url, headers=headers, proxies=proxies, allow_redirects=False, timeout=(15, 30))
# Check server is using same API version as us
try:
if r.headers['api'] != str(API_VERSION):
raise onionrexceptions.InvalidAPIVersion
except KeyError:
raise onionrexceptions.InvalidAPIVersion
retData = r.text retData = r.text
except KeyboardInterrupt: except KeyboardInterrupt:
raise KeyboardInterrupt raise KeyboardInterrupt
except ValueError as e: except ValueError as e:
logger.debug('Failed to make request', error = e) logger.debug('Failed to make request', error = e)
except onionrexceptions.InvalidAPIVersion:
logger.debug("Node is using different API version :(")
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.debug('Error: %s' % str(e)) if not 'ConnectTimeoutError' in str(e) and not 'Request rejected or failed' in str(e):
logger.debug('Error: %s' % str(e))
retData = False retData = False
return retData return retData

View File

@ -21,4 +21,4 @@
class OnionrValues: class OnionrValues:
def __init__(self): def __init__(self):
self.passwordLength = 20 self.passwordLength = 20
self.blockMetadataLengths = {'meta': 1000, 'sig': 200, 'signer': 200, 'time': 10, 'powRandomToken': 1000, 'encryptType': 4} #TODO properly refine values to minimum needed self.blockMetadataLengths = {'meta': 1000, 'sig': 200, 'signer': 200, 'time': 10, 'powRandomToken': 1000, 'encryptType': 4, 'expire': 14} #TODO properly refine values to minimum needed

View File

@ -1,2 +0,0 @@
onionragxuddecmg.onion
dgyllprmtmym4gbk.onion

View File

@ -0,0 +1,5 @@
{
"name" : "cliui",
"version" : "1.0",
"author" : "onionr"
}

View File

@ -0,0 +1,133 @@
'''
Onionr - P2P Anonymous Storage Network
This is an interactive menu-driven CLI interface for Onionr
'''
'''
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/>.
'''
# Imports some useful libraries
import logger, config, threading, time, uuid, subprocess
from onionrblockapi import Block
plugin_name = 'cliui'
PLUGIN_VERSION = '0.0.1'
class OnionrCLIUI:
def __init__(self, apiInst):
self.api = apiInst
self.myCore = apiInst.get_core()
return
def subCommand(self, command):
try:
subprocess.run(["./onionr.py", command])
except KeyboardInterrupt:
pass
def refresh(self):
for i in range(100):
print('')
def start(self):
'''Main CLI UI interface menu'''
showMenu = True
isOnline = "No"
firstRun = True
choice = ''
if self.myCore._utils.localCommand('ping') == 'pong':
firstRun = False
while showMenu:
if firstRun:
logger.info("please wait while Onionr starts...")
daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL)
time.sleep(30)
firstRun = False
if self.myCore._utils.localCommand('ping') == 'pong':
isOnline = "Yes"
else:
isOnline = "No"
print('''
Daemon Running: ''' + isOnline + '''
1. Flow (Anonymous public chat, use at your own risk)
2. Mail (Secure email-like service)
3. File Sharing
4. User Settings
5. Start/Stop Daemon
6. Quit (Does not shutdown daemon)
''')
try:
choice = input(">").strip().lower()
except (KeyboardInterrupt, EOFError):
choice = "quit"
if choice in ("flow", "1"):
self.subCommand("flow")
elif choice in ("2", "mail"):
self.subCommand("mail")
elif choice in ("3", "file sharing", "file"):
print("Not supported yet")
elif choice in ("4", "user settings", "settings"):
try:
self.setName()
except (KeyboardInterrupt, EOFError) as e:
pass
elif choice in ("5", "daemon"):
if isOnline == "Yes":
print("Onionr daemon will shutdown...")
self.myCore.daemonQueueAdd('shutdown')
try:
daemon.kill()
except UnboundLocalError:
pass
else:
print("Starting Daemon...")
daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
elif choice in ("6", "quit"):
showMenu = False
elif choice == "":
pass
else:
print("Invalid choice")
return
def setName(self):
try:
name = input("Enter your name: ")
if name != "":
self.myCore.insertBlock("userInfo-" + str(uuid.uuid1()), sign=True, header='userInfo', meta={'name': name})
except KeyboardInterrupt:
pass
return
def on_init(api, data = None):
'''
This event is called after Onionr is initialized, but before the command
inputted is executed. Could be called when daemon is starting or when
just the client is running.
'''
# Doing this makes it so that the other functions can access the api object
# by simply referencing the variable `pluginapi`.
pluginapi = api
ui = OnionrCLIUI(api)
api.commands.register('interactive', ui.start)
api.commands.register_help('interactive', 'Open the CLI interface')
return

View File

@ -0,0 +1,5 @@
{
"name" : "encrypt",
"version" : "1.0",
"author" : "onionr"
}

View File

@ -0,0 +1,117 @@
'''
Onionr - P2P Microblogging Platform & Social network
This default plugin allows users to encrypt/decrypt messages without using blocks
'''
'''
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/>.
'''
# Imports some useful libraries
import logger, config, threading, time, readline, datetime, sys, json
from onionrblockapi import Block
import onionrexceptions, onionrusers
import locale
locale.setlocale(locale.LC_ALL, '')
class PlainEncryption:
def __init__(self, api):
self.api = api
return
def encrypt(self):
# peer, data
plaintext = ""
encrypted = ""
# detect if signing is enabled
sign = True
try:
if sys.argv[3].lower() == 'false':
sign = False
except IndexError:
pass
try:
if not self.api.get_core()._utils.validatePubKey(sys.argv[2]):
raise onionrexceptions.InvalidPubkey
except (ValueError, IndexError) as e:
logger.error("Peer public key not specified")
except onionrexceptions.InvalidPubkey:
logger.error("Invalid public key")
else:
pubkey = sys.argv[2]
# Encrypt if public key is valid
logger.info("Please enter your message (ctrl-d or -q to stop):")
try:
for line in sys.stdin:
if line == '-q\n':
break
plaintext += line
except KeyboardInterrupt:
sys.exit(1)
# Build Message to encrypt
data = {}
myPub = self.api.get_core()._crypto.pubKey
if sign:
data['sig'] = self.api.get_core()._crypto.edSign(plaintext, key=self.api.get_core()._crypto.privKey, encodeResult=True)
data['sig'] = self.api.get_core()._utils.bytesToStr(data['sig'])
data['signer'] = myPub
data['data'] = plaintext
data = json.dumps(data)
plaintext = data
encrypted = self.api.get_core()._crypto.pubKeyEncrypt(plaintext, pubkey, anonymous=True, encodedData=True)
encrypted = self.api.get_core()._utils.bytesToStr(encrypted)
print('ONIONR ENCRYPTED DATA %s END ENCRYPTED DATA' % (encrypted,))
def decrypt(self):
plaintext = ""
data = ""
logger.info("Please enter your message (ctrl-d or -q to stop):")
try:
for line in sys.stdin:
if line == '-q\n':
break
data += line
except KeyboardInterrupt:
sys.exit(1)
if len(data) <= 1:
return
encrypted = data.replace('ONIONR ENCRYPTED DATA ', '').replace('END ENCRYPTED DATA', '')
myPub = self.api.get_core()._crypto.pubKey
decrypted = self.api.get_core()._crypto.pubKeyDecrypt(encrypted, privkey=self.api.get_core()._crypto.privKey, anonymous=True, encodedData=True)
if decrypted == False:
print("Decryption failed")
else:
data = json.loads(decrypted)
print(data['data'])
try:
logger.info("Signing public key: %s" % (data['signer'],))
assert self.api.get_core()._crypto.edVerify(data['data'], data['signer'], data['sig']) != False
except (AssertionError, KeyError) as e:
logger.warn("WARNING: THIS MESSAGE HAS A MISSING OR INVALID SIGNATURE")
else:
logger.info("Message has good signature.")
return
def on_init(api, data = None):
'''
This event is called after Onionr is initialized, but before the command
inputted is executed. Could be called when daemon is starting or when
just the client is running.
'''
pluginapi = api
encrypt = PlainEncryption(pluginapi)
api.commands.register(['encrypt'], encrypt.encrypt)
api.commands.register(['decrypt'], encrypt.decrypt)
return

View File

@ -45,9 +45,9 @@ class OnionrFlow:
self.flowRunning = False self.flowRunning = False
if message == "q": if message == "q":
self.flowRunning = False self.flowRunning = False
expireTime = self.myCore._utils.getEpoch() + 43200
if len(message) > 0: if len(message) > 0:
Block(content = message, type = 'txt', core = self.myCore).save() Block(content = message, type = 'txt', expire=expireTime, core = self.myCore).save()
logger.info("Flow is exiting, goodbye") logger.info("Flow is exiting, goodbye")
return return

View File

@ -0,0 +1,5 @@
{
"name" : "metadataprocessor",
"version" : "1.0",
"author" : "onionr"
}

View File

@ -0,0 +1,103 @@
'''
Onionr - P2P Anonymous Storage Network
This processes metadata for Onionr blocks
'''
'''
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/>.
'''
# useful libraries
import logger, config
import os, sys, json, time, random, shutil, base64, getpass, datetime, re
from onionrblockapi import Block
import onionrusers, onionrexceptions
plugin_name = 'metadataprocessor'
# event listeners
def _processUserInfo(api, newBlock):
'''
Set the username for a particular user, from a signed block by them
'''
myBlock = newBlock
peerName = myBlock.getMetadata('name')
try:
if len(peerName) > 20:
raise onionrexceptions.InvalidMetdata('Peer name specified is too large')
except TypeError:
pass
except onionrexceptions.InvalidMetadata:
pass
else:
api.get_core().setPeerInfo(signer, 'name', peerName)
logger.info('%s is now using the name %s.' % (signer, api.get_utils().escapeAnsi(peerName)))
def _processForwardKey(api, myBlock):
'''
Get the forward secrecy key specified by the user for us to use
'''
peer = onionrusers.OnionrUser(api.get_core(), myBlock.signer)
key = myBlock.getMetadata('newFSKey')
# We don't need to validate here probably, but it helps
if api.get_utils().validatePubKey(key):
peer.addForwardKey(key)
else:
raise onionrexceptions.InvalidPubkey("%s is nota valid pubkey key" % (key,))
def on_processblocks(api):
# Generally fired by utils.
myBlock = api.data['block']
blockType = api.data['type']
logger.info('blockType is ' + blockType)
# Process specific block types
# userInfo blocks, such as for setting username
if blockType == 'userInfo':
if api.data['validSig'] == True: # we use == True for type safety
_processUserInfo(api, myBlock)
# forwardKey blocks, add a new forward secrecy key for a peer
elif blockType == 'forwardKey':
if api.data['validSig'] == True:
_processForwardKey(api, myBlock)
# socket blocks
elif blockType == 'socket':
if api.data['validSig'] == True and myBlock.decrypted: # we check if it is decrypted as a way of seeing if it was for us
logger.info('Detected socket advertised to us...')
try:
address = myBlock.getMetadata('address')
except KeyError:
raise onionrexceptions.MissingAddress("Missing address for new socket")
try:
port = myBlock.getMetadata('port')
except KeyError:
raise ValueError("Missing port for new socket")
try:
reason = myBlock.getMetadata('reason')
except KeyError:
raise ValueError("Missing socket reason")
socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, 'create': False, 'reason': reason})
api.get_core().daemonQueueAdd('addSocket', socketInfo)
else:
logger.warn("socket is not for us or is invalid")
def on_init(api, data = None):
pluginapi = api
return

View File

@ -1,5 +1,5 @@
''' '''
Onionr - P2P Microblogging Platform & Social network Onionr - P2P Anonymous Storage Network
This default plugin handles private messages in an email like fashion This default plugin handles private messages in an email like fashion
''' '''
@ -21,10 +21,14 @@
# Imports some useful libraries # Imports some useful libraries
import logger, config, threading, time, readline, datetime import logger, config, threading, time, readline, datetime
from onionrblockapi import Block from onionrblockapi import Block
import onionrexceptions import onionrexceptions, onionrusers
import locale import locale, sys, os
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
import sentboxdb # import after path insert
plugin_name = 'pms' plugin_name = 'pms'
PLUGIN_VERSION = '0.0.1' PLUGIN_VERSION = '0.0.1'
@ -44,22 +48,23 @@ class MailStrings:
self.mailInstance = mailInstance self.mailInstance = mailInstance
self.programTag = 'OnionrMail v%s' % (PLUGIN_VERSION) self.programTag = 'OnionrMail v%s' % (PLUGIN_VERSION)
choices = ['view inbox', 'view sentbox', 'send message', 'help', 'quit'] choices = ['view inbox', 'view sentbox', 'send message', 'quit']
self.mainMenuChoices = choices self.mainMenuChoices = choices
self.mainMenu = '''\n self.mainMenu = '''\n
----------------- -----------------
1. %s 1. %s
2. %s 2. %s
3. %s 3. %s
4. %s 4. %s''' % (choices[0], choices[1], choices[2], choices[3])
5. %s''' % (choices[0], choices[1], choices[2], choices[3], choices[4])
class OnionrMail: class OnionrMail:
def __init__(self, pluginapi): def __init__(self, pluginapi):
self.myCore = pluginapi.get_core() self.myCore = pluginapi.get_core()
#self.dataFolder = pluginapi.get_data_folder()
self.strings = MailStrings(self) self.strings = MailStrings(self)
self.sentboxTools = sentboxdb.SentBox(self.myCore)
self.sentboxList = []
self.sentMessages = {}
return return
def inbox(self): def inbox(self):
@ -68,6 +73,7 @@ class OnionrMail:
pmBlocks = {} pmBlocks = {}
logger.info('Decrypting messages...') logger.info('Decrypting messages...')
choice = '' choice = ''
displayList = []
# this could use a lot of memory if someone has recieved a lot of messages # this could use a lot of memory if someone has recieved a lot of messages
for blockHash in self.myCore.getBlocksByType('pm'): for blockHash in self.myCore.getBlocksByType('pm'):
@ -81,9 +87,22 @@ class OnionrMail:
continue continue
blockCount += 1 blockCount += 1
pmBlockMap[blockCount] = blockHash pmBlockMap[blockCount] = blockHash
blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M")
print('%s. %s: %s' % (blockCount, blockDate, blockHash)) block = pmBlocks[blockHash]
senderKey = block.signer
try:
senderKey = senderKey.decode()
except AttributeError:
pass
senderDisplay = onionrusers.OnionrUser(self.myCore, senderKey).getName()
if senderDisplay == 'anonymous':
senderDisplay = senderKey
blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M")
displayList.append('%s. %s - %s: %s' % (blockCount, blockDate, senderDisplay[:12], blockHash))
#displayList.reverse()
for i in displayList:
print(i)
try: try:
choice = logger.readline('Enter a block number, -r to refresh, or -q to stop: ').strip().lower() choice = logger.readline('Enter a block number, -r to refresh, or -q to stop: ').strip().lower()
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
@ -110,15 +129,52 @@ class OnionrMail:
else: else:
cancel = '' cancel = ''
readBlock.verifySig() readBlock.verifySig()
print('Message recieved from %s' % (readBlock.signer,)) print('Message recieved from %s' % (self.myCore._utils.bytesToStr(readBlock.signer,)))
print('Valid signature:', readBlock.validSig) print('Valid signature:', readBlock.validSig)
if not readBlock.validSig: if not readBlock.validSig:
logger.warn('This message has an INVALID signature. ANYONE could have sent this message.') logger.warn('This message has an INVALID signature. ANYONE could have sent this message.')
cancel = logger.readline('Press enter to continue to message, or -q to not open the message (recommended).') cancel = logger.readline('Press enter to continue to message, or -q to not open the message (recommended).')
if cancel != '-q': if cancel != '-q':
print(draw_border(self.myCore._utils.escapeAnsi(readBlock.bcontent.decode().strip()))) print(draw_border(self.myCore._utils.escapeAnsi(readBlock.bcontent.decode().strip())))
input("Press enter to continue")
return return
def sentbox(self):
'''
Display sent mail messages
'''
entering = True
while entering:
self.getSentList()
print('Enter block number or -q to return')
try:
choice = input('>')
except (EOFError, KeyboardInterrupt) as e:
entering = False
else:
if choice == '-q':
entering = False
else:
try:
self.sentboxList[int(choice) - 1]
except IndexError:
print('Invalid block')
else:
logger.info('Sent to: ' + self.sentMessages[self.sentboxList[int(choice) - 1]][1])
# Print ansi escaped sent message
print(self.myCore._utils.escapeAnsi(self.sentMessages[self.sentboxList[int(choice) - 1]][0]))
input('Press enter to continue...')
return
def getSentList(self):
count = 1
for i in self.sentboxTools.listSent():
self.sentboxList.append(i['hash'])
self.sentMessages[i['hash']] = (i['message'], i['peer'])
print('%s. %s - %s - %s' % (count, i['hash'], i['peer'][:12], i['date']))
count += 1
def draftMessage(self): def draftMessage(self):
message = '' message = ''
newLine = '' newLine = ''
@ -155,8 +211,8 @@ class OnionrMail:
print('Inserting encrypted message as Onionr block....') print('Inserting encrypted message as Onionr block....')
self.myCore.insertBlock(message, header='pm', encryptType='asym', asymPeer=recip, sign=True) blockID = self.myCore.insertBlock(message, header='pm', encryptType='asym', asymPeer=recip, sign=True)
self.sentboxTools.addToSent(blockID, recip, message)
def menu(self): def menu(self):
choice = '' choice = ''
while True: while True:
@ -171,12 +227,10 @@ class OnionrMail:
if choice in (self.strings.mainMenuChoices[0], '1'): if choice in (self.strings.mainMenuChoices[0], '1'):
self.inbox() self.inbox()
elif choice in (self.strings.mainMenuChoices[1], '2'): elif choice in (self.strings.mainMenuChoices[1], '2'):
logger.warn('not implemented yet') self.sentbox()
elif choice in (self.strings.mainMenuChoices[2], '3'): elif choice in (self.strings.mainMenuChoices[2], '3'):
self.draftMessage() self.draftMessage()
elif choice in (self.strings.mainMenuChoices[3], '4'): elif choice in (self.strings.mainMenuChoices[3], '4'):
logger.warn('not implemented yet')
elif choice in (self.strings.mainMenuChoices[4], '5'):
logger.info('Goodbye.') logger.info('Goodbye.')
break break
elif choice == '': elif choice == '':

View File

@ -0,0 +1,62 @@
'''
Onionr - P2P Microblogging Platform & Social network
This file handles the sentbox for the mail plugin
'''
'''
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 sqlite3, os
import core
class SentBox:
def __init__(self, mycore):
assert isinstance(mycore, core.Core)
self.dbLocation = mycore.dataDir + 'sentbox.db'
if not os.path.exists(self.dbLocation):
self.createDB()
self.conn = sqlite3.connect(self.dbLocation)
self.cursor = self.conn.cursor()
self.core = mycore
return
def createDB(self):
conn = sqlite3.connect(self.dbLocation)
cursor = conn.cursor()
cursor.execute('''CREATE TABLE sent(
hash id not null,
peer text not null,
message text not null,
date int not null
);
''')
conn.commit()
return
def listSent(self):
retData = []
for entry in self.cursor.execute('SELECT * FROM sent;'):
retData.append({'hash': entry[0], 'peer': entry[1], 'message': entry[2], 'date': entry[3]})
return retData
def addToSent(self, blockID, peer, message):
args = (blockID, peer, message, self.core._utils.getEpoch())
self.cursor.execute('INSERT INTO sent VALUES(?, ?, ?, ?)', args)
self.conn.commit()
return
def removeSent(self, blockID):
args = (blockID,)
self.cursor.execute('DELETE FROM sent where hash=?', args)
self.conn.commit()
return

View File

@ -2,6 +2,8 @@
"general" : { "general" : {
"dev_mode": true, "dev_mode": true,
"display_header" : true, "display_header" : true,
"minimum_block_pow": 5,
"minimum_send_pow": 5,
"direct_connect" : { "direct_connect" : {
"respond" : true, "respond" : true,
@ -30,7 +32,7 @@
"log": { "log": {
"file": { "file": {
"output": true, "output": false,
"path": "data/output.log" "path": "data/output.log"
}, },
@ -59,7 +61,7 @@
"peers":{ "peers":{
"minimumScore": -100, "minimumScore": -100,
"maxStoredPeers": 5000, "maxStoredPeers": 5000,
"maxConnect": 5 "maxConnect": 10
}, },
"timers":{ "timers":{
"lookupBlocks": 25, "lookupBlocks": 25,

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 with the owner's knowledge.</p> <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>Onionr is a decentralized, distributed data storage system, that anyone can insert data into.</p> <p>Onionr is a decentralized data storage system that anyone can insert data into.</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

@ -14,7 +14,7 @@
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 unittest, sys, os, base64, tarfile, shutil, simplecrypt, logger import unittest, sys, os, base64, tarfile, shutil, logger
class OnionrTests(unittest.TestCase): class OnionrTests(unittest.TestCase):
def testPython3(self): def testPython3(self):
@ -61,36 +61,6 @@ class OnionrTests(unittest.TestCase):
else: else:
self.assertTrue(False) self.assertTrue(False)
def testData_b_Encrypt(self):
self.assertTrue(True)
return
logger.debug('-'*26 + '\n')
logger.info('Running data dir encrypt test...')
import core
myCore = core.Core()
myCore.dataDirEncrypt('password')
if os.path.exists('data-encrypted.dat'):
self.assertTrue(True)
else:
self.assertTrue(False)
def testData_a_Decrypt(self):
self.assertTrue(True)
return
logger.debug('-'*26 + '\n')
logger.info('Running data dir decrypt test...')
import core
myCore = core.Core()
myCore.dataDirDecrypt('password')
if os.path.exists('data/'):
self.assertTrue(True)
else:
self.assertTrue(False)
def testConfig(self): def testConfig(self):
logger.debug('-'*26 + '\n') logger.debug('-'*26 + '\n')
logger.info('Running simple configuration test...') logger.info('Running simple configuration test...')

View File

@ -1,9 +1,9 @@
urllib3==1.23 urllib3==1.23
requests==2.18.4 requests==2.20.0
PyNaCl==1.2.1 PyNaCl==1.2.1
gevent==1.2.2 gevent==1.3.6
sha3==0.2.1 sha3==0.2.1
defusedxml==0.5.0 defusedxml==0.5.0
simple_crypt==4.1.7
Flask==1.0.2 Flask==1.0.2
PySocks==1.6.8 PySocks==1.6.8
stem==1.6.0

7
setprofile.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
ONIONR_HOME=.
if [ $# -gt 0 ]; then
ONIONR_HOME=$1
export ONIONR_HOME
echo "set ONIONR_HOME to $ONIONR_HOME"
fi