Merge branch 'master' of gitlab.com:beardog/Onionr
This commit is contained in:
commit
1303edc2fa
2
.gitignore
vendored
2
.gitignore
vendored
@ -13,3 +13,5 @@ onionr/data-encrypted.dat
|
|||||||
onionr/.onionr-lock
|
onionr/.onionr-lock
|
||||||
core
|
core
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
venv/*
|
||||||
|
onionr/fs*
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "onionr/bitpeer"]
|
|
||||||
path = onionr/bitpeer
|
|
||||||
url = https://github.com/beardog108/bitpeer.py
|
|
@ -4,7 +4,7 @@ FROM ubuntu:bionic
|
|||||||
ENV HOME /root
|
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
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
cd "$(dirname "$0")"
|
||||||
cd onionr/
|
cd onionr/
|
||||||
./onionr.py "$@"
|
./onionr.py "$@"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
'''
|
'''
|
||||||
Onionr - P2P Microblogging Platform & Social network
|
Onionr - P2P Anonymous Storage Network
|
||||||
|
|
||||||
This file handles all incoming http requests to the client, using Flask
|
This file handles all incoming http requests to the client, using Flask
|
||||||
'''
|
'''
|
||||||
@ -22,7 +22,7 @@ from flask import request, Response, abort, send_from_directory
|
|||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
from gevent.pywsgi import WSGIServer
|
from gevent.pywsgi import WSGIServer
|
||||||
import sys, random, threading, hmac, hashlib, base64, time, math, os, json
|
import sys, random, threading, hmac, hashlib, base64, time, math, os, json
|
||||||
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,7 +136,7 @@ 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'
|
||||||
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
251
onionr/core.py
251
onionr/core.py
@ -1,5 +1,5 @@
|
|||||||
'''
|
'''
|
||||||
Onionr - P2P Microblogging Platform & Social network
|
Onionr - P2P Anonymous Storage Network
|
||||||
|
|
||||||
Core Onionr library, useful for external programs. Handles peer & data processing
|
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,7 +568,20 @@ 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
|
||||||
|
|
||||||
|
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
|
return rows
|
||||||
|
|
||||||
def setBlockType(self, hash, blockType):
|
def setBlockType(self, hash, blockType):
|
||||||
@ -603,7 +589,7 @@ class Core:
|
|||||||
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()
|
||||||
@ -715,13 +718,19 @@ class Core:
|
|||||||
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)
|
||||||
payload = proof.waitForResult()
|
payload = proof.waitForResult()
|
||||||
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:
|
||||||
|
@ -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()
|
@ -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:
|
||||||
|
180
onionr/onionr.py
180
onionr/onionr.py
@ -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,20 +255,65 @@ 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:
|
||||||
ban = sys.argv[2]
|
ban = sys.argv[2]
|
||||||
@ -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)
|
||||||
|
@ -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))
|
||||||
|
@ -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
49
onionr/onionrchat.py
Normal 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)
|
@ -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]
|
||||||
|
|
||||||
|
@ -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,))
|
||||||
|
# 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')
|
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')
|
@ -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
|
pass
|
||||||
|
|
||||||
|
# onionrsocket exceptions
|
||||||
|
|
||||||
|
class MissingAddress(Exception):
|
||||||
|
pass
|
@ -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/>.
|
|
||||||
'''
|
|
@ -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
|
||||||
|
@ -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():
|
||||||
'''
|
'''
|
||||||
|
@ -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
171
onionr/onionrsockets.py
Normal 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
189
onionr/onionrusers.py
Normal 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
|
@ -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,13 +383,24 @@ 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)
|
||||||
@ -456,6 +489,12 @@ class OnionrUtils:
|
|||||||
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:
|
||||||
return False
|
return False
|
||||||
@ -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
|
||||||
|
|
||||||
|
@ -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
|
@ -1,2 +0,0 @@
|
|||||||
onionragxuddecmg.onion
|
|
||||||
dgyllprmtmym4gbk.onion
|
|
5
onionr/static-data/default-plugins/cliui/info.json
Normal file
5
onionr/static-data/default-plugins/cliui/info.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name" : "cliui",
|
||||||
|
"version" : "1.0",
|
||||||
|
"author" : "onionr"
|
||||||
|
}
|
133
onionr/static-data/default-plugins/cliui/main.py
Normal file
133
onionr/static-data/default-plugins/cliui/main.py
Normal 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
|
5
onionr/static-data/default-plugins/encrypt/info.json
Normal file
5
onionr/static-data/default-plugins/encrypt/info.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name" : "encrypt",
|
||||||
|
"version" : "1.0",
|
||||||
|
"author" : "onionr"
|
||||||
|
}
|
117
onionr/static-data/default-plugins/encrypt/main.py
Normal file
117
onionr/static-data/default-plugins/encrypt/main.py
Normal 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
|
@ -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
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name" : "metadataprocessor",
|
||||||
|
"version" : "1.0",
|
||||||
|
"author" : "onionr"
|
||||||
|
}
|
103
onionr/static-data/default-plugins/metadataprocessor/main.py
Normal file
103
onionr/static-data/default-plugins/metadataprocessor/main.py
Normal 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
|
@ -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 == '':
|
||||||
|
62
onionr/static-data/default-plugins/pms/sentboxdb.py
Normal file
62
onionr/static-data/default-plugins/pms/sentboxdb.py
Normal 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
|
@ -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,
|
||||||
|
@ -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>
|
||||||
|
@ -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...')
|
||||||
|
@ -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.3.6
|
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
7
setprofile.sh
Executable 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
|
Loading…
Reference in New Issue
Block a user