Merge branch 'wot' into 'master'
Merge wot See merge request beardog/Onionr!14
This commit is contained in:
commit
c0bfe102d5
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@ __pycache__/
|
|||||||
onionr/data/config.ini
|
onionr/data/config.ini
|
||||||
onionr/data/*.db
|
onionr/data/*.db
|
||||||
onionr/data-old/*
|
onionr/data-old/*
|
||||||
|
onionr/data*
|
||||||
onionr/*.pyc
|
onionr/*.pyc
|
||||||
onionr/*.log
|
onionr/*.log
|
||||||
onionr/data/hs/hostname
|
onionr/data/hs/hostname
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
# Contributing to Onionr
|
# Contributing to Onionr
|
||||||
One of the great things about open source projects is that they allow for many people to contribute to the project. This file should serve as a guideline when contributing to Onionr.
|
|
||||||
|
One of the great things about open source projects is that they allow for many people to contribute to the project.
|
||||||
|
|
||||||
|
This file serves to provide guidelines on how to successfully contribute to Onionr.
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
@ -7,16 +10,29 @@ See our [Code of Conduct](https://github.com/beardog108/onionr/blob/master/CODE_
|
|||||||
|
|
||||||
## Reporting Bugs
|
## Reporting Bugs
|
||||||
|
|
||||||
Bugs can be reported using GitHub issues.
|
Bugs can be reported using GitLab issues. Please try to see if an issue is already opened for a particular thing.
|
||||||
|
|
||||||
TODO
|
Please provide the following information when reporting a bug:
|
||||||
|
|
||||||
|
* Operating system
|
||||||
|
* Python version
|
||||||
|
* Onionr version
|
||||||
|
* Onionr logs or output with errors, any possible relevant information.
|
||||||
|
* A description of what you were doing before the bug was experienced
|
||||||
|
* Screenshots can often be helpful, but videos are rarely helpful.
|
||||||
|
|
||||||
|
If a bug is a security issue, please contact us privately.
|
||||||
|
|
||||||
|
And most importantly, please be patient. Onionr is an open source project done by volunteers.
|
||||||
|
|
||||||
## Asking Questions
|
## Asking Questions
|
||||||
|
|
||||||
TODO
|
If you need help with Onionr, you can ask in our
|
||||||
|
|
||||||
## Contributing Code
|
## Contributing Code
|
||||||
|
|
||||||
TODO
|
For any non-trivial changes, please get in touch with us first to discuss your plans.
|
||||||
|
|
||||||
|
Please try to use a similar coding style as the project.
|
||||||
|
|
||||||
**Thanks for contributing to Onionr!**
|
**Thanks for contributing to Onionr!**
|
||||||
|
6
Makefile
6
Makefile
@ -18,7 +18,7 @@ uninstall:
|
|||||||
rm -f $(DESTDIR)$(PREFIX)/bin/onionr
|
rm -f $(DESTDIR)$(PREFIX)/bin/onionr
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@./RUN-LINUX.sh stop
|
@./run-linux stop
|
||||||
@sleep 1
|
@sleep 1
|
||||||
@rm -rf onionr/data-backup
|
@rm -rf onionr/data-backup
|
||||||
@mv onionr/data onionr/data-backup | true > /dev/null 2>&1
|
@mv onionr/data onionr/data-backup | true > /dev/null 2>&1
|
||||||
@ -29,7 +29,7 @@ test:
|
|||||||
soft-reset:
|
soft-reset:
|
||||||
@echo "Soft-resetting Onionr..."
|
@echo "Soft-resetting Onionr..."
|
||||||
rm -f onionr/data/blocks/*.dat onionr/data/*.db onionr/data/block-nonces.dat | true > /dev/null 2>&1
|
rm -f onionr/data/blocks/*.dat onionr/data/*.db onionr/data/block-nonces.dat | true > /dev/null 2>&1
|
||||||
@./RUN-LINUX.sh version | grep -v "Failed" --color=always
|
@./run-linux version | grep -v "Failed" --color=always
|
||||||
|
|
||||||
reset:
|
reset:
|
||||||
@echo "Hard-resetting Onionr..."
|
@echo "Hard-resetting Onionr..."
|
||||||
@ -40,4 +40,4 @@ reset:
|
|||||||
plugins-reset:
|
plugins-reset:
|
||||||
@echo "Resetting plugins..."
|
@echo "Resetting plugins..."
|
||||||
rm -rf onionr/data/plugins/ | true > /dev/null 2>&1
|
rm -rf onionr/data/plugins/ | true > /dev/null 2>&1
|
||||||
@./RUN-LINUX.sh version | grep -v "Failed" --color=always
|
@./run-linux version | grep -v "Failed" --color=always
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
![Onionr logo](./docs/onionr-logo.png)
|
![Onionr logo](./docs/onionr-logo.png)
|
||||||
|
|
||||||
v0.3.0 (***experimental, not safe or easy to use yet***)
|
(***experimental, not safe or easy to use yet***)
|
||||||
|
|
||||||
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
|
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
|
||||||
|
|
||||||
|
@ -86,21 +86,18 @@ class API:
|
|||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
bindPort = int(config.get('client.port', 59496))
|
bindPort = int(config.get('client.port', 59496))
|
||||||
self.bindPort = bindPort
|
self.bindPort = bindPort
|
||||||
self.clientToken = config.get('client.hmac')
|
self.clientToken = config.get('client.webpassword')
|
||||||
self.timeBypassToken = base64.b16encode(os.urandom(32)).decode()
|
self.timeBypassToken = base64.b16encode(os.urandom(32)).decode()
|
||||||
|
|
||||||
self.i2pEnabled = config.get('i2p.host', False)
|
self.i2pEnabled = config.get('i2p.host', False)
|
||||||
|
|
||||||
self.mimeType = 'text/plain'
|
|
||||||
self.overrideCSP = False
|
|
||||||
|
|
||||||
self.hideBlocks = [] # Blocks to be denied sharing
|
self.hideBlocks = [] # Blocks to be denied sharing
|
||||||
|
|
||||||
with open(self._core.dataDir + '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:
|
||||||
hostOctets = [127, random.randint(0x02, 0xFF), random.randint(0x02, 0xFF), random.randint(0x02, 0xFF)]
|
hostOctets = [str(127), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF))]
|
||||||
self.host = '.'.join(hostOctets)
|
self.host = '.'.join(hostOctets)
|
||||||
else:
|
else:
|
||||||
self.host = '127.0.0.1'
|
self.host = '127.0.0.1'
|
||||||
@ -122,19 +119,26 @@ class API:
|
|||||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||||
#else:
|
#else:
|
||||||
# resp.headers['server'] = 'Onionr'
|
# resp.headers['server'] = 'Onionr'
|
||||||
resp.headers['Content-Type'] = self.mimeType
|
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'"
|
||||||
if not self.overrideCSP:
|
|
||||||
resp.headers["Content-Security-Policy"] = "default-src 'none'; script-src 'none'; object-src 'none'; style-src data: 'unsafe-inline'; img-src data:; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'none'"
|
|
||||||
resp.headers['X-Frame-Options'] = 'deny'
|
resp.headers['X-Frame-Options'] = 'deny'
|
||||||
resp.headers['X-Content-Type-Options'] = "nosniff"
|
resp.headers['X-Content-Type-Options'] = "nosniff"
|
||||||
resp.headers['X-API'] = API_VERSION
|
resp.headers['X-API'] = API_VERSION
|
||||||
|
resp.headers['Date'] = 'Thu, 1 Jan 1970 00:00:00 GMT' # Clock info is probably useful to attackers. Set to unix epoch.
|
||||||
# reset to text/plain to help prevent browser attacks
|
|
||||||
self.mimeType = 'text/plain'
|
|
||||||
self.overrideCSP = False
|
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
@app.route('/site/<path:block>')
|
||||||
|
def site(block):
|
||||||
|
self.validateHost('private')
|
||||||
|
bHash = block
|
||||||
|
resp = 'Not Found'
|
||||||
|
if self._core._utils.validateHash(bHash):
|
||||||
|
resp = Block(bHash).bcontent
|
||||||
|
try:
|
||||||
|
resp = base64.b64decode(resp)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return Response(resp)
|
||||||
|
|
||||||
@app.route('/www/private/<path:path>')
|
@app.route('/www/private/<path:path>')
|
||||||
def www_private(path):
|
def www_private(path):
|
||||||
startTime = math.floor(time.time())
|
startTime = math.floor(time.time())
|
||||||
@ -149,9 +153,6 @@ class API:
|
|||||||
|
|
||||||
self.validateHost('private')
|
self.validateHost('private')
|
||||||
|
|
||||||
if config.get('www.public.guess_mime', True):
|
|
||||||
self.mimeType = API.guessMime(path)
|
|
||||||
|
|
||||||
endTime = math.floor(time.time())
|
endTime = math.floor(time.time())
|
||||||
elapsed = endTime - startTime
|
elapsed = endTime - startTime
|
||||||
|
|
||||||
@ -168,9 +169,6 @@ class API:
|
|||||||
|
|
||||||
self.validateHost('public')
|
self.validateHost('public')
|
||||||
|
|
||||||
if config.get('www.public.guess_mime', True):
|
|
||||||
self.mimeType = API.guessMime(path)
|
|
||||||
|
|
||||||
return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path)
|
return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path)
|
||||||
|
|
||||||
@app.route('/ui/<path:path>')
|
@app.route('/ui/<path:path>')
|
||||||
@ -201,12 +199,11 @@ class API:
|
|||||||
time.sleep(self._privateDelayTime - elapsed)
|
time.sleep(self._privateDelayTime - elapsed)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.mimeType = API.guessMime(path)
|
mime = API.guessMime(path)
|
||||||
self.overrideCSP = True
|
|
||||||
|
|
||||||
logger.debug('Serving %s (mime: %s)' % (path, self.mimeType))
|
logger.debug('Serving %s (mime: %s)' % (path, mime))
|
||||||
|
|
||||||
return send_from_directory('static-data/www/ui/dist/', path, mimetype = API.guessMime(path))
|
return send_from_directory('static-data/www/ui/dist/', path)
|
||||||
|
|
||||||
@app.route('/client/')
|
@app.route('/client/')
|
||||||
def private_handler():
|
def private_handler():
|
||||||
@ -233,6 +230,8 @@ class API:
|
|||||||
self.validateHost('private')
|
self.validateHost('private')
|
||||||
if action == 'hello':
|
if action == 'hello':
|
||||||
resp = Response('Hello, World! ' + request.host)
|
resp = Response('Hello, World! ' + request.host)
|
||||||
|
elif action == 'getIP':
|
||||||
|
resp = Response(self.host)
|
||||||
elif action == 'waitForShare':
|
elif action == 'waitForShare':
|
||||||
if self._core._utils.validateHash(data):
|
if self._core._utils.validateHash(data):
|
||||||
if data not in self.hideBlocks:
|
if data not in self.hideBlocks:
|
||||||
@ -248,16 +247,8 @@ class API:
|
|||||||
resp = Response('Goodbye')
|
resp = Response('Goodbye')
|
||||||
elif action == 'ping':
|
elif action == 'ping':
|
||||||
resp = Response('pong')
|
resp = Response('pong')
|
||||||
elif action == 'site':
|
|
||||||
block = data
|
|
||||||
siteData = self._core.getData(data)
|
|
||||||
response = 'not found'
|
|
||||||
if siteData != '' and siteData != False:
|
|
||||||
self.mimeType = 'text/html'
|
|
||||||
response = siteData.split(b'-', 2)[-1]
|
|
||||||
resp = Response(response)
|
|
||||||
elif action == 'info':
|
elif action == 'info':
|
||||||
resp = Response(json.dumps({'pubkey' : self._core._crypto.pubKey, 'host' : self._core.hsAddress}))
|
resp = Response(json.dumps({'pubkey' : self._core._crypto.pubKey, 'host' : self._core.hsAddress}), mimetype='text/plain')
|
||||||
elif action == "insertBlock":
|
elif action == "insertBlock":
|
||||||
response = {'success' : False, 'reason' : 'An unknown error occurred'}
|
response = {'success' : False, 'reason' : 'An unknown error occurred'}
|
||||||
|
|
||||||
@ -368,12 +359,12 @@ class API:
|
|||||||
else:
|
else:
|
||||||
response = {'success' : False, 'reason' : 'Missing `data` parameter.', 'blocks' : {}}
|
response = {'success' : False, 'reason' : 'Missing `data` parameter.', 'blocks' : {}}
|
||||||
|
|
||||||
resp = Response(json.dumps(response))
|
resp = Response(json.dumps(response), mimetype='text/plain')
|
||||||
|
|
||||||
elif action in API.callbacks['private']:
|
elif action in API.callbacks['private']:
|
||||||
resp = Response(str(getCallback(action, scope = 'private')(request)))
|
resp = Response(str(getCallback(action, scope = 'private')(request)), mimetype='text/plain')
|
||||||
else:
|
else:
|
||||||
resp = Response('(O_o) Dude what? (invalid command)')
|
resp = Response('invalid command')
|
||||||
endTime = math.floor(time.time())
|
endTime = math.floor(time.time())
|
||||||
elapsed = endTime - startTime
|
elapsed = endTime - startTime
|
||||||
|
|
||||||
@ -386,11 +377,10 @@ class API:
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def banner():
|
def banner():
|
||||||
self.mimeType = 'text/html'
|
|
||||||
self.validateHost('public')
|
self.validateHost('public')
|
||||||
try:
|
try:
|
||||||
with open('static-data/index.html', 'r') as html:
|
with open('static-data/index.html', 'r') as html:
|
||||||
resp = Response(html.read())
|
resp = Response(html.read(), mimetype='text/html')
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
resp = Response("")
|
resp = Response("")
|
||||||
return resp
|
return resp
|
||||||
@ -461,6 +451,8 @@ class API:
|
|||||||
def public_handler():
|
def public_handler():
|
||||||
# Public means it is publicly network accessible
|
# Public means it is publicly network accessible
|
||||||
self.validateHost('public')
|
self.validateHost('public')
|
||||||
|
if config.get('general.security_level') != 0:
|
||||||
|
abort(403)
|
||||||
action = request.args.get('action')
|
action = request.args.get('action')
|
||||||
requestingPeer = request.args.get('myID')
|
requestingPeer = request.args.get('myID')
|
||||||
data = request.args.get('data')
|
data = request.args.get('data')
|
||||||
@ -489,9 +481,10 @@ class API:
|
|||||||
elif action == 'getData':
|
elif action == 'getData':
|
||||||
resp = ''
|
resp = ''
|
||||||
if self._utils.validateHash(data):
|
if self._utils.validateHash(data):
|
||||||
if os.path.exists(self._core.dataDir + 'blocks/' + data + '.dat'):
|
if data not in self.hideBlocks:
|
||||||
block = Block(hash=data.encode(), core=self._core)
|
if os.path.exists(self._core.dataDir + 'blocks/' + data + '.dat'):
|
||||||
resp = base64.b64encode(block.getRaw().encode()).decode()
|
block = Block(hash=data.encode(), core=self._core)
|
||||||
|
resp = base64.b64encode(block.getRaw().encode()).decode()
|
||||||
if len(resp) == 0:
|
if len(resp) == 0:
|
||||||
abort(404)
|
abort(404)
|
||||||
resp = ""
|
resp = ""
|
||||||
@ -531,8 +524,8 @@ class API:
|
|||||||
resp = Response("Invalid request")
|
resp = Response("Invalid request")
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
if not os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
|
||||||
logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...', timestamp=False)
|
logger.info('Starting client on ' + self.host + ':' + str(bindPort), timestamp=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while len(self._core.hsAddress) == 0:
|
while len(self._core.hsAddress) == 0:
|
||||||
@ -545,7 +538,6 @@ class API:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
logger.fatal('Failed to start client on ' + self.host + ':' + str(bindPort) + ', exiting...')
|
logger.fatal('Failed to start client on ' + self.host + ':' + str(bindPort) + ', exiting...')
|
||||||
exit(1)
|
|
||||||
|
|
||||||
def validateHost(self, hostType):
|
def validateHost(self, hostType):
|
||||||
'''
|
'''
|
||||||
@ -571,13 +563,16 @@ class API:
|
|||||||
if not self.i2pEnabled and request.host.endswith('i2p'):
|
if not self.i2pEnabled and request.host.endswith('i2p'):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
'''
|
||||||
if not self._developmentMode:
|
if not self._developmentMode:
|
||||||
try:
|
try:
|
||||||
request.headers['X-Requested-With']
|
request.headers['X-Requested-With']
|
||||||
except:
|
except:
|
||||||
# we exit rather than abort to avoid fingerprinting
|
pass
|
||||||
logger.debug('Avoiding fingerprinting, exiting...')
|
# we exit rather than abort to avoid fingerprinting
|
||||||
sys.exit(1)
|
logger.debug('Avoiding fingerprinting, exiting...')
|
||||||
|
#sys.exit(1)
|
||||||
|
'''
|
||||||
|
|
||||||
def setCallback(action, callback, scope = 'public'):
|
def setCallback(action, callback, scope = 'public'):
|
||||||
if not scope in API.callbacks:
|
if not scope in API.callbacks:
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
'''
|
'''
|
||||||
import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid
|
import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid
|
||||||
import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block
|
import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block
|
||||||
import onionrdaemontools, onionrsockets, onionrchat, onionr
|
import onionrdaemontools, onionrsockets, onionrchat, onionr, onionrproofs
|
||||||
from dependencies import secrets
|
from dependencies import secrets
|
||||||
from defusedxml import minidom
|
from defusedxml import minidom
|
||||||
|
|
||||||
@ -70,6 +70,9 @@ class OnionrCommunicatorDaemon:
|
|||||||
# list of blocks currently downloading, avoid s
|
# list of blocks currently downloading, avoid s
|
||||||
self.currentDownloading = []
|
self.currentDownloading = []
|
||||||
|
|
||||||
|
# timestamp when the last online node was seen
|
||||||
|
self.lastNodeSeen = None
|
||||||
|
|
||||||
# Clear the daemon queue for any dead messages
|
# Clear the daemon queue for any dead messages
|
||||||
if os.path.exists(self._core.queueDB):
|
if os.path.exists(self._core.queueDB):
|
||||||
self._core.clearDaemonQueue()
|
self._core.clearDaemonQueue()
|
||||||
@ -98,23 +101,31 @@ class OnionrCommunicatorDaemon:
|
|||||||
OnionrCommunicatorTimers(self, self.lookupAdders, 60, requiresPeer=True)
|
OnionrCommunicatorTimers(self, self.lookupAdders, 60, requiresPeer=True)
|
||||||
OnionrCommunicatorTimers(self, self.daemonTools.cooldownPeer, 30, requiresPeer=True)
|
OnionrCommunicatorTimers(self, self.daemonTools.cooldownPeer, 30, requiresPeer=True)
|
||||||
OnionrCommunicatorTimers(self, self.uploadBlock, 10, requiresPeer=True, maxThreads=1)
|
OnionrCommunicatorTimers(self, self.uploadBlock, 10, requiresPeer=True, maxThreads=1)
|
||||||
|
OnionrCommunicatorTimers(self, self.daemonCommands, 6, maxThreads=1)
|
||||||
|
deniableBlockTimer = OnionrCommunicatorTimers(self, self.daemonTools.insertDeniableBlock, 180, requiresPeer=True, maxThreads=1)
|
||||||
|
|
||||||
netCheckTimer = OnionrCommunicatorTimers(self, self.daemonTools.netCheck, 600)
|
netCheckTimer = OnionrCommunicatorTimers(self, self.daemonTools.netCheck, 600)
|
||||||
announceTimer = OnionrCommunicatorTimers(self, self.daemonTools.announceNode, 305, requiresPeer=True, maxThreads=1)
|
if config.get('general.security_level') == 0:
|
||||||
|
announceTimer = OnionrCommunicatorTimers(self, self.daemonTools.announceNode, 86400, requiresPeer=True, maxThreads=1)
|
||||||
|
announceTimer.count = (announceTimer.frequency - 120)
|
||||||
|
else:
|
||||||
|
logger.debug('Will not announce node.')
|
||||||
cleanupTimer = OnionrCommunicatorTimers(self, self.peerCleanup, 300, requiresPeer=True)
|
cleanupTimer = OnionrCommunicatorTimers(self, self.peerCleanup, 300, requiresPeer=True)
|
||||||
forwardSecrecyTimer = OnionrCommunicatorTimers(self, self.daemonTools.cleanKeys, 15)
|
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)
|
deniableBlockTimer.count = (deniableBlockTimer.frequency - 175)
|
||||||
#forwardSecrecyTimer.count = (forwardSecrecyTimer.frequency - 990)
|
#forwardSecrecyTimer.count = (forwardSecrecyTimer.frequency - 990)
|
||||||
|
|
||||||
self.socketServer = threading.Thread(target=onionrsockets.OnionrSocketServer, args=(self._core,))
|
if config.get('general.socket_servers'):
|
||||||
self.socketServer.start()
|
self.socketServer = threading.Thread(target=onionrsockets.OnionrSocketServer, args=(self._core,))
|
||||||
self.socketClient = onionrsockets.OnionrSocketClient(self._core)
|
self.socketServer.start()
|
||||||
|
self.socketClient = onionrsockets.OnionrSocketClient(self._core)
|
||||||
|
|
||||||
# Loads chat messages into memory
|
# Loads chat messages into memory
|
||||||
threading.Thread(target=self._chat.chatHandler).start()
|
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:
|
||||||
@ -187,8 +198,8 @@ 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
|
if onionrproofs.hashMeetsDifficulty(i):
|
||||||
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
|
||||||
|
|
||||||
@ -230,7 +241,6 @@ class OnionrCommunicatorDaemon:
|
|||||||
content = content.decode() # decode here because sha3Hash needs bytes above
|
content = content.decode() # decode here because sha3Hash needs bytes above
|
||||||
metas = self._core._utils.getBlockMetadataFromData(content) # returns tuple(metadata, meta), meta is also in metadata
|
metas = self._core._utils.getBlockMetadataFromData(content) # returns tuple(metadata, meta), meta is also in metadata
|
||||||
metadata = metas[0]
|
metadata = metas[0]
|
||||||
#meta = metas[1]
|
|
||||||
if self._core._utils.validateMetadata(metadata, metas[2]): # check if metadata is valid, and verify nonce
|
if self._core._utils.validateMetadata(metadata, metas[2]): # check if metadata is valid, and verify nonce
|
||||||
if self._core._crypto.verifyPow(content): # check if POW is enough/correct
|
if self._core._crypto.verifyPow(content): # check if POW is enough/correct
|
||||||
logger.info('Attempting to save block %s...' % blockHash)
|
logger.info('Attempting to save block %s...' % blockHash)
|
||||||
@ -317,11 +327,14 @@ class OnionrCommunicatorDaemon:
|
|||||||
self.connectNewPeer(useBootstrap=True)
|
self.connectNewPeer(useBootstrap=True)
|
||||||
else:
|
else:
|
||||||
self.connectNewPeer()
|
self.connectNewPeer()
|
||||||
|
|
||||||
if self.shutdown:
|
if self.shutdown:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
if len(self.onlinePeers) == 0:
|
if len(self.onlinePeers) == 0:
|
||||||
logger.debug('Couldn\'t connect to any peers.')
|
logger.debug('Couldn\'t connect to any peers.' + (' Last node seen %s ago.' % self.daemonTools.humanReadableTime(time.time() - self.lastNodeSeen) if not self.lastNodeSeen is None else ''))
|
||||||
|
else:
|
||||||
|
self.lastNodeSeen = time.time()
|
||||||
self.decrementThreadCount('getOnlinePeers')
|
self.decrementThreadCount('getOnlinePeers')
|
||||||
|
|
||||||
def addBootstrapListToPeerList(self, peerList):
|
def addBootstrapListToPeerList(self, peerList):
|
||||||
@ -352,7 +365,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
self.addBootstrapListToPeerList(peerList)
|
self.addBootstrapListToPeerList(peerList)
|
||||||
|
|
||||||
for address in peerList:
|
for address in peerList:
|
||||||
if not config.get('tor.v3_onions') 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 or address in self.cooldownPeer:
|
if len(address) == 0 or address in tried or address in self.onlinePeers or address in self.cooldownPeer:
|
||||||
continue
|
continue
|
||||||
@ -445,7 +458,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
def heartbeat(self):
|
def heartbeat(self):
|
||||||
'''Show a heartbeat debug message'''
|
'''Show a heartbeat debug message'''
|
||||||
currentTime = self._core._utils.getEpoch() - self.startTime
|
currentTime = self._core._utils.getEpoch() - self.startTime
|
||||||
logger.debug('Heartbeat. Node online for %s.' % self.daemonTools.humanReadableTime(currentTime))
|
logger.debug('Heartbeat. Node running for %s.' % self.daemonTools.humanReadableTime(currentTime))
|
||||||
self.decrementThreadCount('heartbeat')
|
self.decrementThreadCount('heartbeat')
|
||||||
|
|
||||||
def daemonCommands(self):
|
def daemonCommands(self):
|
||||||
@ -456,14 +469,13 @@ class OnionrCommunicatorDaemon:
|
|||||||
|
|
||||||
if cmd is not False:
|
if cmd is not False:
|
||||||
events.event('daemon_command', onionr = None, data = {'cmd' : cmd})
|
events.event('daemon_command', onionr = None, data = {'cmd' : cmd})
|
||||||
|
|
||||||
if cmd[0] == 'shutdown':
|
if cmd[0] == 'shutdown':
|
||||||
self.shutdown = True
|
self.shutdown = True
|
||||||
elif cmd[0] == 'announceNode':
|
elif cmd[0] == 'announceNode':
|
||||||
if len(self.onlinePeers) > 0:
|
if len(self.onlinePeers) > 0:
|
||||||
self.announce(cmd[1])
|
self.announce(cmd[1])
|
||||||
else:
|
else:
|
||||||
logger.warn("Not introducing, since I have no connected nodes.")
|
logger.debug("No nodes connected. Will not introduce node.")
|
||||||
elif cmd[0] == 'runCheck': # deprecated
|
elif cmd[0] == 'runCheck': # deprecated
|
||||||
logger.debug('Status check; looks good.')
|
logger.debug('Status check; looks good.')
|
||||||
open(self._core.dataDir + '.runcheck', 'w+').close()
|
open(self._core.dataDir + '.runcheck', 'w+').close()
|
||||||
@ -584,7 +596,7 @@ class OnionrCommunicatorTimers:
|
|||||||
if self.makeThread:
|
if self.makeThread:
|
||||||
for i in range(self.threadAmount):
|
for i in range(self.threadAmount):
|
||||||
if self.daemonInstance.threadCounts[self.timerFunction.__name__] >= self.maxThreads:
|
if self.daemonInstance.threadCounts[self.timerFunction.__name__] >= self.maxThreads:
|
||||||
logger.warn('%s is currently using the maximum number of threads, not starting another.' % self.timerFunction.__name__)
|
logger.debug('%s is currently using the maximum number of threads, not starting another.' % self.timerFunction.__name__)
|
||||||
else:
|
else:
|
||||||
self.daemonInstance.threadCounts[self.timerFunction.__name__] += 1
|
self.daemonInstance.threadCounts[self.timerFunction.__name__] += 1
|
||||||
newThread = threading.Thread(target=self.timerFunction)
|
newThread = threading.Thread(target=self.timerFunction)
|
||||||
|
@ -30,7 +30,7 @@ except KeyError:
|
|||||||
_configfile = os.path.abspath(dataDir + 'config.json')
|
_configfile = os.path.abspath(dataDir + 'config.json')
|
||||||
_config = {}
|
_config = {}
|
||||||
|
|
||||||
def get(key, default = None):
|
def get(key, default = None, save = False):
|
||||||
'''
|
'''
|
||||||
Gets the key from configuration, or returns `default`
|
Gets the key from configuration, or returns `default`
|
||||||
'''
|
'''
|
||||||
@ -46,6 +46,8 @@ def get(key, default = None):
|
|||||||
data = data[item]
|
data = data[item]
|
||||||
|
|
||||||
if not last in data:
|
if not last in data:
|
||||||
|
if save:
|
||||||
|
set(key, default, savefile = True)
|
||||||
return default
|
return default
|
||||||
|
|
||||||
return data[last]
|
return data[last]
|
||||||
|
@ -113,7 +113,7 @@ class Core:
|
|||||||
with open(self.dataDir + '/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, name=''):
|
||||||
'''
|
'''
|
||||||
Adds a public key to the key database (misleading function name)
|
Adds a public key to the key database (misleading function name)
|
||||||
'''
|
'''
|
||||||
@ -121,16 +121,13 @@ class Core:
|
|||||||
# This function simply adds a peer to the DB
|
# This function simply adds a peer to the DB
|
||||||
if not self._utils.validatePubKey(peerID):
|
if not self._utils.validatePubKey(peerID):
|
||||||
return False
|
return False
|
||||||
if sys.getsizeof(powID) > 120:
|
|
||||||
logger.warn("POW token for pubkey base64 representation exceeded 120 bytes, is " + str(sys.getsizeof(powID)))
|
|
||||||
return False
|
|
||||||
|
|
||||||
events.event('pubkey_add', data = {'key': peerID}, onionr = None)
|
events.event('pubkey_add', data = {'key': peerID}, onionr = None)
|
||||||
|
|
||||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
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, 0)
|
t = (peerID, name, 'unknown', hashID, 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:
|
||||||
@ -141,7 +138,7 @@ class Core:
|
|||||||
pass
|
pass
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
c.execute('INSERT INTO peers (id, name, dateSeen, pow, hashID, trust) VALUES(?, ?, ?, ?, ?, ?);', t)
|
c.execute('INSERT INTO peers (id, name, dateSeen, hashID, trust) VALUES(?, ?, ?, ?, ?);', t)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@ -154,6 +151,8 @@ class Core:
|
|||||||
|
|
||||||
if address == config.get('i2p.ownAddr', None) or address == self.hsAddress:
|
if address == config.get('i2p.ownAddr', None) or address == self.hsAddress:
|
||||||
return False
|
return False
|
||||||
|
if type(address) is type(None) or len(address) == 0:
|
||||||
|
return False
|
||||||
if self._utils.validateID(address):
|
if self._utils.validateID(address):
|
||||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
@ -231,21 +230,18 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Generate the address database
|
Generate the address database
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.dbCreate.createAddressDB()
|
self.dbCreate.createAddressDB()
|
||||||
|
|
||||||
def createPeerDB(self):
|
def createPeerDB(self):
|
||||||
'''
|
'''
|
||||||
Generate the peer sqlite3 database and populate it with the peers table.
|
Generate the peer sqlite3 database and populate it with the peers table.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.dbCreate.createPeerDB()
|
self.dbCreate.createPeerDB()
|
||||||
|
|
||||||
def createBlockDB(self):
|
def createBlockDB(self):
|
||||||
'''
|
'''
|
||||||
Create a database for blocks
|
Create a database for blocks
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.dbCreate.createBlockDB()
|
self.dbCreate.createBlockDB()
|
||||||
|
|
||||||
def addToBlockDB(self, newHash, selfInsert=False, dataSaved=False):
|
def addToBlockDB(self, newHash, selfInsert=False, dataSaved=False):
|
||||||
@ -374,7 +370,6 @@ class Core:
|
|||||||
retData = False
|
retData = False
|
||||||
self.daemonQueue()
|
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 retData
|
return retData
|
||||||
|
|
||||||
def clearDaemonQueue(self):
|
def clearDaemonQueue(self):
|
||||||
@ -464,18 +459,14 @@ class Core:
|
|||||||
name text, 1
|
name text, 1
|
||||||
adders text, 2
|
adders text, 2
|
||||||
dateSeen not null, 3
|
dateSeen not null, 3
|
||||||
bytesStored int, 4
|
trust int 4
|
||||||
trust int 5
|
hashID text 5
|
||||||
pubkeyExchanged int 6
|
|
||||||
hashID text 7
|
|
||||||
pow text 8
|
|
||||||
'''
|
'''
|
||||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
conn = sqlite3.connect(self.peerDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
command = (peer,)
|
command = (peer,)
|
||||||
|
infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'dateSeen': 3, 'trust': 4, 'hashID': 5}
|
||||||
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 = ''
|
||||||
@ -503,7 +494,7 @@ class Core:
|
|||||||
command = (data, peer)
|
command = (data, peer)
|
||||||
|
|
||||||
# TODO: validate key on whitelist
|
# TODO: validate key on whitelist
|
||||||
if key not in ('id', 'name', 'pubkey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'):
|
if key not in ('id', 'name', 'pubkey', 'forwardKey', 'dateSeen', 'trust'):
|
||||||
raise Exception("Got invalid database key when setting peer info")
|
raise Exception("Got invalid database key when setting peer info")
|
||||||
|
|
||||||
c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command)
|
c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command)
|
||||||
@ -524,13 +515,15 @@ class Core:
|
|||||||
DBHash text, 5
|
DBHash text, 5
|
||||||
failure int 6
|
failure int 6
|
||||||
lastConnect 7
|
lastConnect 7
|
||||||
|
trust 8
|
||||||
|
introduced 9
|
||||||
'''
|
'''
|
||||||
|
|
||||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
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, 'trust': 8, 'introduced': 9}
|
||||||
info = infoNumbers[info]
|
info = infoNumbers[info]
|
||||||
iterCount = 0
|
iterCount = 0
|
||||||
retVal = ''
|
retVal = ''
|
||||||
@ -555,9 +548,8 @@ class Core:
|
|||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
command = (data, address)
|
command = (data, address)
|
||||||
|
|
||||||
# TODO: validate key on whitelist
|
if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'DBHash', 'failure', 'lastConnect', 'lastConnectAttempt', 'trust', 'introduced'):
|
||||||
if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'DBHash', 'failure', 'lastConnect', 'lastConnectAttempt'):
|
|
||||||
raise Exception("Got invalid database key when setting address info")
|
raise Exception("Got invalid database key when setting address info")
|
||||||
else:
|
else:
|
||||||
c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command)
|
c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command)
|
||||||
@ -679,7 +671,7 @@ class Core:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def insertBlock(self, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = None, expire=None):
|
def insertBlock(self, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = {}, 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
|
||||||
@ -710,9 +702,8 @@ class Core:
|
|||||||
# metadata is full block metadata, meta is internal, user specified metadata
|
# metadata is full block metadata, meta is internal, user specified metadata
|
||||||
|
|
||||||
# only use header if not set in provided meta
|
# only use header if not set in provided meta
|
||||||
if not header is None:
|
|
||||||
meta['type'] = header
|
meta['type'] = str(header)
|
||||||
meta['type'] = str(meta['type'])
|
|
||||||
|
|
||||||
if encryptType in ('asym', 'sym', ''):
|
if encryptType in ('asym', 'sym', ''):
|
||||||
metadata['encryptType'] = encryptType
|
metadata['encryptType'] = encryptType
|
||||||
@ -731,8 +722,6 @@ class Core:
|
|||||||
meta['forwardEnc'] = True
|
meta['forwardEnc'] = True
|
||||||
except onionrexceptions.InvalidPubkey:
|
except onionrexceptions.InvalidPubkey:
|
||||||
onionrusers.OnionrUser(self, asymPeer).generateForwardKey()
|
onionrusers.OnionrUser(self, asymPeer).generateForwardKey()
|
||||||
else:
|
|
||||||
logger.info(forwardEncrypted)
|
|
||||||
onionrusers.OnionrUser(self, asymPeer).generateForwardKey()
|
onionrusers.OnionrUser(self, asymPeer).generateForwardKey()
|
||||||
fsKey = onionrusers.OnionrUser(self, asymPeer).getGeneratedForwardKeys()[0]
|
fsKey = onionrusers.OnionrUser(self, asymPeer).getGeneratedForwardKeys()[0]
|
||||||
meta['newFSKey'] = fsKey[0]
|
meta['newFSKey'] = fsKey[0]
|
||||||
@ -763,6 +752,7 @@ class Core:
|
|||||||
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()
|
||||||
signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True, anonymous=True).decode()
|
signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True, anonymous=True).decode()
|
||||||
|
onionrusers.OnionrUser(self, asymPeer, saveUser=True)
|
||||||
else:
|
else:
|
||||||
raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key')
|
raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key')
|
||||||
|
|
||||||
@ -770,7 +760,7 @@ class Core:
|
|||||||
metadata['meta'] = jsonMeta
|
metadata['meta'] = jsonMeta
|
||||||
metadata['sig'] = signature
|
metadata['sig'] = signature
|
||||||
metadata['signer'] = signer
|
metadata['signer'] = signer
|
||||||
metadata['time'] = str(self._utils.getEpoch())
|
metadata['time'] = self._utils.getRoundedEpoch() + self._crypto.secrets.randbelow(301)
|
||||||
|
|
||||||
# ensure expire is integer and of sane length
|
# ensure expire is integer and of sane length
|
||||||
if type(expire) is not type(None):
|
if type(expire) is not type(None):
|
||||||
@ -798,7 +788,7 @@ class Core:
|
|||||||
Introduces our node into the network by telling X many nodes our HS address
|
Introduces our node into the network by telling X many nodes our HS address
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if(self._utils.isCommunicatorRunning()):
|
if(self._utils.isCommunicatorRunning(timeout=30)):
|
||||||
announceAmount = 2
|
announceAmount = 2
|
||||||
nodeList = self.listAdders()
|
nodeList = self.listAdders()
|
||||||
|
|
||||||
|
@ -44,7 +44,8 @@ class DBCreator:
|
|||||||
failure int,
|
failure int,
|
||||||
lastConnect int,
|
lastConnect int,
|
||||||
lastConnectAttempt int,
|
lastConnectAttempt int,
|
||||||
trust int
|
trust int,
|
||||||
|
introduced int
|
||||||
);
|
);
|
||||||
''')
|
''')
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@ -62,11 +63,8 @@ class DBCreator:
|
|||||||
name text,
|
name text,
|
||||||
adders text,
|
adders text,
|
||||||
dateSeen not null,
|
dateSeen not null,
|
||||||
bytesStored int,
|
|
||||||
trust int,
|
trust int,
|
||||||
pubkeyExchanged int,
|
hashID text);
|
||||||
hashID text,
|
|
||||||
pow text not null);
|
|
||||||
''')
|
''')
|
||||||
c.execute('''CREATE TABLE forwardKeys(
|
c.execute('''CREATE TABLE forwardKeys(
|
||||||
peerKey text not null,
|
peerKey text not null,
|
||||||
|
80
onionr/keymanager.py
Normal file
80
onionr/keymanager.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
'''
|
||||||
|
Onionr - P2P Anonymous Storage Network
|
||||||
|
|
||||||
|
Load, save, and delete the user's public key pairs (does not handle peer keys)
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
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 onionrcrypto
|
||||||
|
class KeyManager:
|
||||||
|
def __init__(self, crypto):
|
||||||
|
assert isinstance(crypto, onionrcrypto.OnionrCrypto)
|
||||||
|
self._core = crypto._core
|
||||||
|
self._utils = self._core._utils
|
||||||
|
self.keyFile = crypto._keyFile
|
||||||
|
self.crypto = crypto
|
||||||
|
|
||||||
|
def addKey(self, pubKey=None, privKey=None):
|
||||||
|
if type(pubKey) is type(None) and type(privKey) is type(None):
|
||||||
|
pubKey, privKey = self.crypto.generatePubKey()
|
||||||
|
pubKey = self.crypto._core._utils.bytesToStr(pubKey)
|
||||||
|
privKey = self.crypto._core._utils.bytesToStr(privKey)
|
||||||
|
try:
|
||||||
|
if pubKey in self.getPubkeyList():
|
||||||
|
raise ValueError('Pubkey already in list: %s' % (pubKey,))
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
with open(self.keyFile, "a") as keyFile:
|
||||||
|
keyFile.write(pubKey + ',' + privKey + '\n')
|
||||||
|
return (pubKey, privKey)
|
||||||
|
|
||||||
|
def removeKey(self, pubKey):
|
||||||
|
'''Remove a key pair by pubkey'''
|
||||||
|
keyList = self.getPubkeyList()
|
||||||
|
keyData = ''
|
||||||
|
try:
|
||||||
|
keyList.remove(pubKey)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
keyData = ','.join(keyList)
|
||||||
|
with open(self.keyFile, "w") as keyFile:
|
||||||
|
keyFile.write(keyData)
|
||||||
|
|
||||||
|
def getPubkeyList(self):
|
||||||
|
'''Return a list of the user's keys'''
|
||||||
|
keyList = []
|
||||||
|
with open(self.keyFile, "r") as keyFile:
|
||||||
|
keyData = keyFile.read()
|
||||||
|
keyData = keyData.split('\n')
|
||||||
|
for pair in keyData:
|
||||||
|
if len(pair) > 0: keyList.append(pair.split(',')[0])
|
||||||
|
return keyList
|
||||||
|
|
||||||
|
def getPrivkey(self, pubKey):
|
||||||
|
privKey = None
|
||||||
|
with open(self.keyFile, "r") as keyFile:
|
||||||
|
keyData = keyFile.read()
|
||||||
|
for pair in keyData.split('\n'):
|
||||||
|
if pubKey in pair:
|
||||||
|
privKey = pair.split(',')[1]
|
||||||
|
return privKey
|
||||||
|
|
||||||
|
def changeActiveKey(self, pubKey):
|
||||||
|
'''Change crypto.pubKey and crypto.privKey to a given key pair by specifying the public key'''
|
||||||
|
if not pubKey in self.getPubkeyList():
|
||||||
|
raise ValueError('That pubkey does not exist')
|
||||||
|
self.crypto.pubKey = pubKey
|
||||||
|
self.crypto.privKey = self.getPrivkey(pubKey)
|
@ -27,7 +27,7 @@ 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, apiServerIP='127.0.0.1'):
|
||||||
try:
|
try:
|
||||||
self.dataDir = os.environ['ONIONR_HOME']
|
self.dataDir = os.environ['ONIONR_HOME']
|
||||||
if not self.dataDir.endswith('/'):
|
if not self.dataDir.endswith('/'):
|
||||||
@ -41,6 +41,7 @@ class NetController:
|
|||||||
self.hsPort = hsPort
|
self.hsPort = hsPort
|
||||||
self._torInstnace = ''
|
self._torInstnace = ''
|
||||||
self.myID = ''
|
self.myID = ''
|
||||||
|
self.apiServerIP = apiServerIP
|
||||||
|
|
||||||
if os.path.exists('./tor'):
|
if os.path.exists('./tor'):
|
||||||
self.torBinary = './tor'
|
self.torBinary = './tor'
|
||||||
@ -65,9 +66,9 @@ class NetController:
|
|||||||
Generate a torrc file for our tor instance
|
Generate a torrc file for our tor instance
|
||||||
'''
|
'''
|
||||||
hsVer = '# v2 onions'
|
hsVer = '# v2 onions'
|
||||||
if config.get('tor.v3_onions'):
|
if config.get('tor.v3onions'):
|
||||||
hsVer = 'HiddenServiceVersion 3'
|
hsVer = 'HiddenServiceVersion 3'
|
||||||
logger.info('Using v3 onions :)')
|
logger.debug('Using v3 onions :)')
|
||||||
|
|
||||||
if os.path.exists(self.torConfigLocation):
|
if os.path.exists(self.torConfigLocation):
|
||||||
os.remove(self.torConfigLocation)
|
os.remove(self.torConfigLocation)
|
||||||
@ -88,14 +89,16 @@ class NetController:
|
|||||||
break
|
break
|
||||||
|
|
||||||
torrcData = '''SocksPort ''' + str(self.socksPort) + '''
|
torrcData = '''SocksPort ''' + str(self.socksPort) + '''
|
||||||
HiddenServiceDir ''' + self.dataDir + '''hs/
|
|
||||||
\n''' + hsVer + '''\n
|
|
||||||
HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + '''
|
|
||||||
DataDirectory ''' + self.dataDir + '''tordata/
|
DataDirectory ''' + self.dataDir + '''tordata/
|
||||||
CookieAuthentication 1
|
CookieAuthentication 1
|
||||||
ControlPort ''' + str(controlPort) + '''
|
ControlPort ''' + str(controlPort) + '''
|
||||||
HashedControlPassword ''' + str(password) + '''
|
HashedControlPassword ''' + str(password) + '''
|
||||||
'''
|
'''
|
||||||
|
if config.get('general.security_level') == 0:
|
||||||
|
torrcData += '''\nHiddenServiceDir ''' + self.dataDir + '''hs/
|
||||||
|
\n''' + hsVer + '''\n
|
||||||
|
HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
|
||||||
|
|
||||||
torrc = open(self.torConfigLocation, 'w')
|
torrc = open(self.torConfigLocation, 'w')
|
||||||
torrc.write(torrcData)
|
torrc.write(torrcData)
|
||||||
torrc.close()
|
torrc.close()
|
||||||
@ -143,13 +146,16 @@ HashedControlPassword ''' + str(password) + '''
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.fatal('Got keyboard interrupt.', timestamp = false, level = logger.LEVEL_IMPORTANT)
|
logger.fatal('Got keyboard interrupt.', timestamp = false, level = logger.LEVEL_IMPORTANT)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.debug('Finished starting Tor.', timestamp=True)
|
logger.debug('Finished starting Tor.', timestamp=True)
|
||||||
self.readyState = True
|
self.readyState = True
|
||||||
|
|
||||||
myID = open(self.dataDir + 'hs/hostname', 'r')
|
try:
|
||||||
self.myID = myID.read().replace('\n', '')
|
myID = open(self.dataDir + 'hs/hostname', 'r')
|
||||||
myID.close()
|
self.myID = myID.read().replace('\n', '')
|
||||||
|
myID.close()
|
||||||
|
except FileNotFoundError:
|
||||||
|
self.myID = ""
|
||||||
|
|
||||||
torPidFile = open(self.dataDir + 'torPid.txt', 'w')
|
torPidFile = open(self.dataDir + 'torPid.txt', 'w')
|
||||||
torPidFile.write(str(tor.pid))
|
torPidFile.write(str(tor.pid))
|
||||||
|
106
onionr/onionr.py
106
onionr/onionr.py
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
if sys.version_info[0] == 2 or sys.version_info[1] < 5:
|
if sys.version_info[0] == 2 or sys.version_info[1] < 5:
|
||||||
print('Error, Onionr requires Python 3.4+')
|
print('Error, Onionr requires Python 3.5+')
|
||||||
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
|
import webbrowser
|
||||||
@ -40,7 +40,7 @@ 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.3.2' # for debugging and stuff
|
ONIONR_VERSION = '0.5.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 = '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.
|
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.
|
||||||
|
|
||||||
@ -103,8 +103,8 @@ class Onionr:
|
|||||||
self.onionrCore.createAddressDB()
|
self.onionrCore.createAddressDB()
|
||||||
|
|
||||||
# Get configuration
|
# Get configuration
|
||||||
if type(config.get('client.hmac')) is type(None):
|
if type(config.get('client.webpassword')) is type(None):
|
||||||
config.set('client.hmac', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True)
|
config.set('client.webpassword', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True)
|
||||||
if type(config.get('client.port')) is type(None):
|
if type(config.get('client.port')) is type(None):
|
||||||
randomPort = 0
|
randomPort = 0
|
||||||
while randomPort < 1024:
|
while randomPort < 1024:
|
||||||
@ -115,7 +115,6 @@ class Onionr:
|
|||||||
if type(config.get('client.api_version')) is type(None):
|
if type(config.get('client.api_version')) is type(None):
|
||||||
config.set('client.api_version', API_VERSION, savefile=True)
|
config.set('client.api_version', API_VERSION, savefile=True)
|
||||||
|
|
||||||
|
|
||||||
self.cmds = {
|
self.cmds = {
|
||||||
'': self.showHelpSuggestion,
|
'': self.showHelpSuggestion,
|
||||||
'help': self.showHelp,
|
'help': self.showHelp,
|
||||||
@ -168,6 +167,10 @@ class Onionr:
|
|||||||
|
|
||||||
'add-file': self.addFile,
|
'add-file': self.addFile,
|
||||||
'addfile': self.addFile,
|
'addfile': self.addFile,
|
||||||
|
'addhtml': self.addWebpage,
|
||||||
|
'add-html': self.addWebpage,
|
||||||
|
'add-site': self.addWebpage,
|
||||||
|
'addsite': self.addWebpage,
|
||||||
|
|
||||||
'get-file': self.getFile,
|
'get-file': self.getFile,
|
||||||
'getfile': self.getFile,
|
'getfile': self.getFile,
|
||||||
@ -197,7 +200,9 @@ class Onionr:
|
|||||||
|
|
||||||
'chat': self.startChat,
|
'chat': self.startChat,
|
||||||
|
|
||||||
'friend': self.friendCmd
|
'friend': self.friendCmd,
|
||||||
|
'add-id': self.addID,
|
||||||
|
'change-id': self.changeID
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cmdhelp = {
|
self.cmdhelp = {
|
||||||
@ -226,7 +231,9 @@ class Onionr:
|
|||||||
'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]'
|
'friend': '[add|remove] [public key/id]',
|
||||||
|
'add-id': 'Generate a new ID (key pair)',
|
||||||
|
'change-id': 'Change active ID'
|
||||||
}
|
}
|
||||||
|
|
||||||
# initialize plugins
|
# initialize plugins
|
||||||
@ -257,6 +264,48 @@ class Onionr:
|
|||||||
for detail in details:
|
for detail in details:
|
||||||
logger.info('%s%s: \n%s%s\n' % (logger.colors.fg.lightgreen, detail, logger.colors.fg.green, details[detail]), sensitive = True)
|
logger.info('%s%s: \n%s%s\n' % (logger.colors.fg.lightgreen, detail, logger.colors.fg.green, details[detail]), sensitive = True)
|
||||||
|
|
||||||
|
def addID(self):
|
||||||
|
try:
|
||||||
|
sys.argv[2]
|
||||||
|
assert sys.argv[2] == 'true'
|
||||||
|
except (IndexError, AssertionError) as e:
|
||||||
|
newID = self.onionrCore._crypto.keyManager.addKey()[0]
|
||||||
|
else:
|
||||||
|
logger.warn('Deterministic keys require random and long passphrases.')
|
||||||
|
logger.warn('If a good password is not used, your key can be easily stolen.')
|
||||||
|
pass1 = getpass.getpass(prompt='Enter at least %s characters: ' % (self.onionrCore._crypto.deterministicRequirement,))
|
||||||
|
pass2 = getpass.getpass(prompt='Confirm entry: ')
|
||||||
|
if self.onionrCore._crypto.safeCompare(pass1, pass2):
|
||||||
|
try:
|
||||||
|
logger.info('Generating deterministic key. This can take a while.')
|
||||||
|
newID, privKey = self.onionrCore._crypto.generateDeterministic(pass1)
|
||||||
|
except onionrexceptions.PasswordStrengthError:
|
||||||
|
logger.error('Must use at least 25 characters.')
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
logger.error('Passwords do not match.')
|
||||||
|
sys.exit(1)
|
||||||
|
self.onionrCore._crypto.keyManager.addKey(pubKey=newID,
|
||||||
|
privKey=privKey)
|
||||||
|
logger.info('Added ID: %s' % (self.onionrUtils.bytesToStr(newID),))
|
||||||
|
|
||||||
|
def changeID(self):
|
||||||
|
try:
|
||||||
|
key = sys.argv[2]
|
||||||
|
except IndexError:
|
||||||
|
logger.error('Specify pubkey to use')
|
||||||
|
else:
|
||||||
|
if self.onionrUtils.validatePubKey(key):
|
||||||
|
if key in self.onionrCore._crypto.keyManager.getPubkeyList():
|
||||||
|
config.set('general.public_key', key)
|
||||||
|
config.save()
|
||||||
|
logger.info('Set active key to: %s' % (key,))
|
||||||
|
logger.info('Restart Onionr if it is running.')
|
||||||
|
else:
|
||||||
|
logger.error('That key does not exist')
|
||||||
|
else:
|
||||||
|
logger.error('Invalid key %s' % (key,))
|
||||||
|
|
||||||
def startChat(self):
|
def startChat(self):
|
||||||
try:
|
try:
|
||||||
data = json.dumps({'peer': sys.argv[2], 'reason': 'chat'})
|
data = json.dumps({'peer': sys.argv[2], 'reason': 'chat'})
|
||||||
@ -334,13 +383,9 @@ class Onionr:
|
|||||||
friend = sys.argv[3]
|
friend = sys.argv[3]
|
||||||
if not self.onionrUtils.validatePubKey(friend):
|
if not self.onionrUtils.validatePubKey(friend):
|
||||||
raise onionrexceptions.InvalidPubkey('Public key is invalid')
|
raise onionrexceptions.InvalidPubkey('Public key is invalid')
|
||||||
if friend not in self.onionrCore.listPeers():
|
|
||||||
raise onionrexceptions.KeyNotKnown
|
|
||||||
friend = onionrusers.OnionrUser(self.onionrCore, friend)
|
friend = onionrusers.OnionrUser(self.onionrCore, friend)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logger.error('Friend ID is required.')
|
logger.error('Friend ID is required.')
|
||||||
except onionrexceptions.KeyNotKnown:
|
|
||||||
logger.error('That peer is not in our database')
|
|
||||||
else:
|
else:
|
||||||
if action == 'add':
|
if action == 'add':
|
||||||
friend.setTrust(1)
|
friend.setTrust(1)
|
||||||
@ -381,7 +426,7 @@ class Onionr:
|
|||||||
logger.info(i)
|
logger.info(i)
|
||||||
|
|
||||||
def getWebPassword(self):
|
def getWebPassword(self):
|
||||||
return config.get('client.hmac')
|
return config.get('client.webpassword')
|
||||||
|
|
||||||
def printWebPassword(self):
|
def printWebPassword(self):
|
||||||
logger.info(self.getWebPassword(), sensitive = True)
|
logger.info(self.getWebPassword(), sensitive = True)
|
||||||
@ -506,6 +551,7 @@ class Onionr:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
newAddress = sys.argv[2]
|
newAddress = sys.argv[2]
|
||||||
|
newAddress = newAddress.replace('http:', '').replace('/', '')
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@ -589,7 +635,7 @@ class Onionr:
|
|||||||
|
|
||||||
if len(sys.argv) >= 3:
|
if len(sys.argv) >= 3:
|
||||||
try:
|
try:
|
||||||
plugin_name = re.sub('[^0-9a-zA-Z]+', '', str(sys.argv[2]).lower())
|
plugin_name = re.sub('[^0-9a-zA-Z_]+', '', str(sys.argv[2]).lower())
|
||||||
|
|
||||||
if not plugins.exists(plugin_name):
|
if not plugins.exists(plugin_name):
|
||||||
logger.info('Creating plugin "%s"...' % plugin_name)
|
logger.info('Creating plugin "%s"...' % plugin_name)
|
||||||
@ -672,17 +718,26 @@ class Onionr:
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.onionrUtils.localCommand('shutdown')
|
self.onionrUtils.localCommand('shutdown')
|
||||||
else:
|
else:
|
||||||
|
apiHost = '127.0.0.1'
|
||||||
if apiThread.isAlive():
|
if apiThread.isAlive():
|
||||||
# configure logger and stuff
|
try:
|
||||||
|
with open(self.onionrCore.dataDir + 'host.txt', 'r') as hostFile:
|
||||||
|
apiHost = hostFile.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
Onionr.setupConfig('data/', self = self)
|
Onionr.setupConfig('data/', self = self)
|
||||||
|
|
||||||
if self._developmentMode:
|
if self._developmentMode:
|
||||||
logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)', timestamp = False)
|
logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)', timestamp = False)
|
||||||
net = NetController(config.get('client.port', 59496))
|
net = NetController(config.get('client.port', 59496), apiServerIP=apiHost)
|
||||||
logger.debug('Tor is starting...')
|
logger.debug('Tor is starting...')
|
||||||
if not net.startTor():
|
if not net.startTor():
|
||||||
|
self.onionrUtils.localCommand('shutdown')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
logger.debug('Started .onion service: %s' % (logger.colors.underline + net.myID))
|
if len(net.myID) > 0 and config.get('general.security_level') == 0:
|
||||||
|
logger.debug('Started .onion service: %s' % (logger.colors.underline + net.myID))
|
||||||
|
else:
|
||||||
|
logger.debug('.onion service disabled')
|
||||||
logger.debug('Using public key: %s' % (logger.colors.underline + self.onionrCore._crypto.pubKey))
|
logger.debug('Using public key: %s' % (logger.colors.underline + self.onionrCore._crypto.pubKey))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
@ -717,7 +772,7 @@ class Onionr:
|
|||||||
Shutdown the Onionr daemon
|
Shutdown the Onionr daemon
|
||||||
'''
|
'''
|
||||||
|
|
||||||
logger.warn('Killing the running daemon...', timestamp = False)
|
logger.warn('Stopping the running daemon...', timestamp = False)
|
||||||
try:
|
try:
|
||||||
events.event('daemon_stop', onionr = self)
|
events.event('daemon_stop', onionr = self)
|
||||||
net = NetController(config.get('client.port', 59496))
|
net = NetController(config.get('client.port', 59496))
|
||||||
@ -729,7 +784,6 @@ class Onionr:
|
|||||||
net.killTor()
|
net.killTor()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Failed to shutdown daemon.', error = e, timestamp = False)
|
logger.error('Failed to shutdown daemon.', error = e, timestamp = False)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def showStats(self):
|
def showStats(self):
|
||||||
@ -822,6 +876,8 @@ class Onionr:
|
|||||||
try:
|
try:
|
||||||
with open('./' + self.dataDir + 'hs/hostname', 'r') as hostname:
|
with open('./' + self.dataDir + 'hs/hostname', 'r') as hostname:
|
||||||
return hostname.read().strip()
|
return hostname.read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return "Not Generated"
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -863,7 +919,13 @@ class Onionr:
|
|||||||
Block.mergeChain(bHash, fileName)
|
Block.mergeChain(bHash, fileName)
|
||||||
return
|
return
|
||||||
|
|
||||||
def addFile(self):
|
def addWebpage(self):
|
||||||
|
'''
|
||||||
|
Add a webpage to the onionr network
|
||||||
|
'''
|
||||||
|
self.addFile(singleBlock=True, blockType='html')
|
||||||
|
|
||||||
|
def addFile(self, singleBlock=False, blockType='txt'):
|
||||||
'''
|
'''
|
||||||
Adds a file to the onionr network
|
Adds a file to the onionr network
|
||||||
'''
|
'''
|
||||||
@ -877,7 +939,11 @@ class Onionr:
|
|||||||
return
|
return
|
||||||
logger.info('Adding file... this might take a long time.')
|
logger.info('Adding file... this might take a long time.')
|
||||||
try:
|
try:
|
||||||
blockhash = Block.createChain(file = filename)
|
if singleBlock:
|
||||||
|
with open(filename, 'rb') as singleFile:
|
||||||
|
blockhash = self.onionrCore.insertBlock(base64.b64encode(singleFile.read()), header=blockType)
|
||||||
|
else:
|
||||||
|
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))
|
||||||
except:
|
except:
|
||||||
logger.error('Failed to save file in block.', timestamp = False)
|
logger.error('Failed to save file in block.', timestamp = False)
|
||||||
|
@ -17,8 +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 nacl.signing, nacl.encoding, nacl.public, nacl.hash, nacl.secret, os, binascii, base64, hashlib, logger, onionrproofs, time, math, sys
|
import nacl.signing, nacl.encoding, nacl.public, nacl.hash, nacl.pwhash, nacl.utils, nacl.secret, os, binascii, base64, hashlib, logger, onionrproofs, time, math, sys, hmac
|
||||||
|
import onionrexceptions, keymanager
|
||||||
# secrets module was added into standard lib in 3.6+
|
# secrets module was added into standard lib in 3.6+
|
||||||
if sys.version_info[0] == 3 and sys.version_info[1] < 6:
|
if sys.version_info[0] == 3 and sys.version_info[1] < 6:
|
||||||
from dependencies import secrets
|
from dependencies import secrets
|
||||||
@ -36,20 +36,22 @@ class OnionrCrypto:
|
|||||||
|
|
||||||
self.secrets = secrets
|
self.secrets = secrets
|
||||||
|
|
||||||
|
self.deterministicRequirement = 25 # Min deterministic password/phrase length
|
||||||
self.HASH_ID_ROUNDS = 2000
|
self.HASH_ID_ROUNDS = 2000
|
||||||
|
self.keyManager = keymanager.KeyManager(self)
|
||||||
|
|
||||||
# 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(self._core.dataDir + 'keys.txt', 'r') as keys:
|
if len(config.get('general.public_key', '')) > 0:
|
||||||
keys = keys.read().split(',')
|
self.pubKey = config.get('general.public_key')
|
||||||
self.pubKey = keys[0]
|
else:
|
||||||
self.privKey = keys[1]
|
self.pubKey = self.keyManager.getPubkeyList()[0]
|
||||||
|
self.privKey = self.keyManager.getPrivkey(self.pubKey)
|
||||||
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:
|
self.keyManager.addKey(self.pubKey, self.privKey)
|
||||||
keyfile.write(self.pubKey + ',' + self.privKey)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def edVerify(self, data, key, sig, encodedData=True):
|
def edVerify(self, data, key, sig, encodedData=True):
|
||||||
@ -196,6 +198,27 @@ class OnionrCrypto:
|
|||||||
private_key = nacl.signing.SigningKey.generate()
|
private_key = nacl.signing.SigningKey.generate()
|
||||||
public_key = private_key.verify_key.encode(encoder=nacl.encoding.Base32Encoder())
|
public_key = private_key.verify_key.encode(encoder=nacl.encoding.Base32Encoder())
|
||||||
return (public_key.decode(), private_key.encode(encoder=nacl.encoding.Base32Encoder()).decode())
|
return (public_key.decode(), private_key.encode(encoder=nacl.encoding.Base32Encoder()).decode())
|
||||||
|
|
||||||
|
def generateDeterministic(self, passphrase, bypassCheck=False):
|
||||||
|
'''Generate a Ed25519 public key pair from a password'''
|
||||||
|
passStrength = self.deterministicRequirement
|
||||||
|
passphrase = self._core._utils.strToBytes(passphrase) # Convert to bytes if not already
|
||||||
|
# Validate passphrase length
|
||||||
|
if not bypassCheck:
|
||||||
|
if len(passphrase) < passStrength:
|
||||||
|
raise onionrexceptions.PasswordStrengthError("Passphase must be at least %s characters" % (passStrength,))
|
||||||
|
# KDF values
|
||||||
|
kdf = nacl.pwhash.argon2id.kdf
|
||||||
|
salt = b"U81Q7llrQcdTP0Ux" # Does not need to be unique or secret, but must be 16 bytes
|
||||||
|
ops = nacl.pwhash.argon2id.OPSLIMIT_SENSITIVE
|
||||||
|
mem = nacl.pwhash.argon2id.MEMLIMIT_SENSITIVE
|
||||||
|
|
||||||
|
key = kdf(nacl.secret.SecretBox.KEY_SIZE, passphrase, salt, opslimit=ops, memlimit=mem)
|
||||||
|
key = nacl.public.PrivateKey(key, nacl.encoding.RawEncoder())
|
||||||
|
publicKey = key.public_key
|
||||||
|
|
||||||
|
return (publicKey.encode(encoder=nacl.encoding.Base32Encoder()),
|
||||||
|
key.encode(encoder=nacl.encoding.Base32Encoder()))
|
||||||
|
|
||||||
def pubKeyHashID(self, pubkey=''):
|
def pubKeyHashID(self, pubkey=''):
|
||||||
'''Accept a ed25519 public key, return a truncated result of X many sha3_256 hash rounds'''
|
'''Accept a ed25519 public key, return a truncated result of X many sha3_256 hash rounds'''
|
||||||
@ -262,3 +285,6 @@ class OnionrCrypto:
|
|||||||
logger.debug("Invalid token, bad proof")
|
logger.debug("Invalid token, bad proof")
|
||||||
|
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
|
def safeCompare(self, one, two):
|
||||||
|
return hmac.compare_digest(one, two)
|
||||||
|
@ -23,6 +23,9 @@ import base64, sqlite3, os
|
|||||||
from dependencies import secrets
|
from dependencies import secrets
|
||||||
|
|
||||||
class DaemonTools:
|
class DaemonTools:
|
||||||
|
'''
|
||||||
|
Class intended for use by Onionr Communicator
|
||||||
|
'''
|
||||||
def __init__(self, daemon):
|
def __init__(self, daemon):
|
||||||
self.daemon = daemon
|
self.daemon = daemon
|
||||||
self.announceCache = {}
|
self.announceCache = {}
|
||||||
@ -30,7 +33,8 @@ class DaemonTools:
|
|||||||
def announceNode(self):
|
def announceNode(self):
|
||||||
'''Announce our node to our peers'''
|
'''Announce our node to our peers'''
|
||||||
retData = False
|
retData = False
|
||||||
|
announceFail = False
|
||||||
|
|
||||||
# Announce to random online peers
|
# Announce to random online peers
|
||||||
for i in self.daemon.onlinePeers:
|
for i in self.daemon.onlinePeers:
|
||||||
if not i in self.announceCache:
|
if not i in self.announceCache:
|
||||||
@ -50,14 +54,21 @@ class DaemonTools:
|
|||||||
data['random'] = self.announceCache[peer]
|
data['random'] = self.announceCache[peer]
|
||||||
else:
|
else:
|
||||||
proof = onionrproofs.DataPOW(combinedNodes, forceDifficulty=4)
|
proof = onionrproofs.DataPOW(combinedNodes, forceDifficulty=4)
|
||||||
data['random'] = base64.b64encode(proof.waitForResult()[1])
|
try:
|
||||||
self.announceCache[peer] = data['random']
|
data['random'] = base64.b64encode(proof.waitForResult()[1])
|
||||||
|
except TypeError:
|
||||||
logger.info('Announcing node to ' + url)
|
# Happens when we failed to produce a proof
|
||||||
if self.daemon._core._utils.doPostRequest(url, data) == 'Success':
|
logger.error("Failed to produce a pow for announcing to " + peer)
|
||||||
logger.info('Successfully introduced node to ' + peer)
|
announceFail = True
|
||||||
retData = True
|
else:
|
||||||
self.daemon.decrementThreadCount('announceNode')
|
self.announceCache[peer] = data['random']
|
||||||
|
if not announceFail:
|
||||||
|
logger.info('Announcing node to ' + url)
|
||||||
|
if self.daemon._core._utils.doPostRequest(url, data) == 'Success':
|
||||||
|
logger.info('Successfully introduced node to ' + peer)
|
||||||
|
retData = True
|
||||||
|
self.daemon._core.setAddressInfo(peer, 'introduced', 1)
|
||||||
|
self.daemon.decrementThreadCount('announceNode')
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
def netCheck(self):
|
def netCheck(self):
|
||||||
@ -66,6 +77,8 @@ class DaemonTools:
|
|||||||
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
|
||||||
|
else:
|
||||||
|
self.daemon.isOnline = True
|
||||||
self.daemon.decrementThreadCount('netCheck')
|
self.daemon.decrementThreadCount('netCheck')
|
||||||
|
|
||||||
def cleanOldBlocks(self):
|
def cleanOldBlocks(self):
|
||||||
@ -92,11 +105,11 @@ class DaemonTools:
|
|||||||
deleteKeys = []
|
deleteKeys = []
|
||||||
|
|
||||||
for entry in c.execute("SELECT * FROM forwardKeys WHERE expire <= ?", (time,)):
|
for entry in c.execute("SELECT * FROM forwardKeys WHERE expire <= ?", (time,)):
|
||||||
logger.info(entry[1])
|
logger.debug('Forward key: %s' % entry[1])
|
||||||
deleteKeys.append(entry[1])
|
deleteKeys.append(entry[1])
|
||||||
|
|
||||||
for key in deleteKeys:
|
for key in deleteKeys:
|
||||||
logger.info('Deleting forward key '+ key)
|
logger.debug('Deleting forward key %s' % key)
|
||||||
c.execute("DELETE from forwardKeys where forwardKey = ?", (key,))
|
c.execute("DELETE from forwardKeys where forwardKey = ?", (key,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@ -120,7 +133,7 @@ class DaemonTools:
|
|||||||
del self.daemon.cooldownPeer[peer]
|
del self.daemon.cooldownPeer[peer]
|
||||||
|
|
||||||
# Cool down a peer, if we have max connections alive for long enough
|
# Cool down a peer, if we have max connections alive for long enough
|
||||||
if onlinePeerAmount >= self.daemon._core.config.get('peers.max_connect', 10):
|
if onlinePeerAmount >= self.daemon._core.config.get('peers.max_connect', 10, save = True):
|
||||||
finding = True
|
finding = True
|
||||||
|
|
||||||
while finding:
|
while finding:
|
||||||
@ -164,3 +177,13 @@ class DaemonTools:
|
|||||||
build += '%s %s' % (amnt_unit, unit) + ('s' if amnt_unit != 1 else '') + ' '
|
build += '%s %s' % (amnt_unit, unit) + ('s' if amnt_unit != 1 else '') + ' '
|
||||||
|
|
||||||
return build.strip()
|
return build.strip()
|
||||||
|
|
||||||
|
def insertDeniableBlock(self):
|
||||||
|
'''Insert a fake block in order to make it more difficult to track real blocks'''
|
||||||
|
fakePeer = self.daemon._core._crypto.generatePubKey()[0]
|
||||||
|
chance = 10
|
||||||
|
if secrets.randbelow(chance) == (chance - 1):
|
||||||
|
data = secrets.token_hex(secrets.randbelow(500) + 1)
|
||||||
|
self.daemon._core.insertBlock(data, header='pm', encryptType='asym', asymPeer=fakePeer)
|
||||||
|
self.daemon.decrementThreadCount('insertDeniableBlock')
|
||||||
|
return
|
@ -40,6 +40,9 @@ class KeyNotKnown(Exception):
|
|||||||
class DecryptionError(Exception):
|
class DecryptionError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class PasswordStrengthError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
# block exceptions
|
# block exceptions
|
||||||
class InvalidMetadata(Exception):
|
class InvalidMetadata(Exception):
|
||||||
pass
|
pass
|
||||||
|
58
onionr/onionrgui.py
Executable file
58
onionr/onionrgui.py
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from tkinter import *
|
||||||
|
import core
|
||||||
|
class OnionrGUI:
|
||||||
|
def __init__(self):
|
||||||
|
self.dataDir = "/programming/onionr/data/"
|
||||||
|
self.root = Tk()
|
||||||
|
self.root.geometry("450x250")
|
||||||
|
self.core = core.Core()
|
||||||
|
menubar = Menu(self.root)
|
||||||
|
|
||||||
|
# create a pulldown menu, and add it to the menu bar
|
||||||
|
filemenu = Menu(menubar, tearoff=0)
|
||||||
|
filemenu.add_command(label="Open", command=None)
|
||||||
|
filemenu.add_command(label="Save", command=None)
|
||||||
|
filemenu.add_separator()
|
||||||
|
filemenu.add_command(label="Exit", command=self.root.quit)
|
||||||
|
menubar.add_cascade(label="File", menu=filemenu)
|
||||||
|
|
||||||
|
settingsmenu = Menu(menubar, tearoff=0)
|
||||||
|
menubar.add_cascade(label="Settings", menu=settingsmenu)
|
||||||
|
|
||||||
|
helpmenu = Menu(menubar, tearoff=0)
|
||||||
|
menubar.add_cascade(label="Help", menu=helpmenu)
|
||||||
|
|
||||||
|
self.root.config(menu=menubar)
|
||||||
|
|
||||||
|
self.menuFrame = Frame(self.root)
|
||||||
|
self.mainButton = Button(self.menuFrame, text="Main View")
|
||||||
|
self.mainButton.grid(row=0, column=0, padx=0, pady=2, sticky=N+W)
|
||||||
|
self.tabButton1 = Button(self.menuFrame, text="Mail")
|
||||||
|
self.tabButton1.grid(row=0, column=1, padx=0, pady=2, sticky=N+W)
|
||||||
|
self.tabButton2 = Button(self.menuFrame, text="Message Flow")
|
||||||
|
self.tabButton2.grid(row=0, column=3, padx=0, pady=2, sticky=N+W)
|
||||||
|
|
||||||
|
self.menuFrame.grid(row=0, column=0, padx=2, pady=0, sticky=N+W)
|
||||||
|
|
||||||
|
|
||||||
|
self.idFrame = Frame(self.root)
|
||||||
|
|
||||||
|
self.ourIDLabel = Label(self.idFrame, text="ID: ")
|
||||||
|
self.ourIDLabel.grid(row=2, column=0, padx=1, pady=1, sticky=N+W)
|
||||||
|
self.ourID = Entry(self.idFrame)
|
||||||
|
self.ourID.insert(0, self.core._crypto.pubKey)
|
||||||
|
self.ourID.grid(row=2, column=1, padx=1, pady=1, sticky=N+W)
|
||||||
|
self.ourID.config(state='readonly')
|
||||||
|
self.idFrame.grid(row=1, column=0, padx=2, pady=2, sticky=N+W)
|
||||||
|
|
||||||
|
self.syncStatus = Label(self.root, text="Sync Status: 15/100")
|
||||||
|
self.syncStatus.place(relx=1.0, rely=1.0, anchor=S+E)
|
||||||
|
self.peerCount = Label(self.root, text="Connected Peers: 3")
|
||||||
|
self.peerCount.place(relx=0.0, rely=1.0, anchor='sw')
|
||||||
|
|
||||||
|
self.root.wm_title("Onionr")
|
||||||
|
self.root.mainloop()
|
||||||
|
return
|
||||||
|
|
||||||
|
OnionrGUI()
|
@ -192,7 +192,7 @@ def get_enabled_plugins():
|
|||||||
|
|
||||||
config.reload()
|
config.reload()
|
||||||
|
|
||||||
return config.get('plugins.enabled', list())
|
return list(config.get('plugins.enabled', list()))
|
||||||
|
|
||||||
def is_enabled(name):
|
def is_enabled(name):
|
||||||
'''
|
'''
|
||||||
@ -212,7 +212,7 @@ def get_plugins_folder(name = None, absolute = True):
|
|||||||
path = _pluginsfolder
|
path = _pluginsfolder
|
||||||
else:
|
else:
|
||||||
# only allow alphanumeric characters
|
# only allow alphanumeric characters
|
||||||
path = _pluginsfolder + re.sub('[^0-9a-zA-Z]+', '', str(name).lower())
|
path = _pluginsfolder + re.sub('[^0-9a-zA-Z_]+', '', str(name).lower())
|
||||||
|
|
||||||
if absolute is True:
|
if absolute is True:
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
|
@ -30,6 +30,8 @@ def getHashDifficulty(h):
|
|||||||
for character in h:
|
for character in h:
|
||||||
if character == '0':
|
if character == '0':
|
||||||
difficulty += 1
|
difficulty += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
return difficulty
|
return difficulty
|
||||||
|
|
||||||
def hashMeetsDifficulty(h):
|
def hashMeetsDifficulty(h):
|
||||||
@ -38,7 +40,10 @@ def hashMeetsDifficulty(h):
|
|||||||
'''
|
'''
|
||||||
config.reload()
|
config.reload()
|
||||||
hashDifficulty = getHashDifficulty(h)
|
hashDifficulty = getHashDifficulty(h)
|
||||||
expected = int(config.get('minimum_block_pow'))
|
try:
|
||||||
|
expected = int(config.get('general.minimum_block_pow'))
|
||||||
|
except TypeError:
|
||||||
|
raise ValueError('Missing general.minimum_block_pow config')
|
||||||
if hashDifficulty >= expected:
|
if hashDifficulty >= expected:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -33,11 +33,20 @@ def deleteExpiredKeys(coreInst):
|
|||||||
return
|
return
|
||||||
|
|
||||||
class OnionrUser:
|
class OnionrUser:
|
||||||
def __init__(self, coreInst, publicKey):
|
def __init__(self, coreInst, publicKey, saveUser=False):
|
||||||
|
'''
|
||||||
|
OnionrUser is an abstraction for "users" of the network.
|
||||||
|
|
||||||
|
Takes an instance of onionr core, a base32 encoded ed25519 public key, and a bool saveUser
|
||||||
|
saveUser determines if we should add a user to our peer database or not.
|
||||||
|
'''
|
||||||
self.trust = 0
|
self.trust = 0
|
||||||
self._core = coreInst
|
self._core = coreInst
|
||||||
self.publicKey = publicKey
|
self.publicKey = publicKey
|
||||||
|
|
||||||
|
if saveUser:
|
||||||
|
self._core.addPeer(publicKey)
|
||||||
|
|
||||||
self.trust = self._core.getPeerInfo(self.publicKey, 'trust')
|
self.trust = self._core.getPeerInfo(self.publicKey, 'trust')
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -71,7 +80,6 @@ class OnionrUser:
|
|||||||
def forwardEncrypt(self, data):
|
def forwardEncrypt(self, data):
|
||||||
retData = ''
|
retData = ''
|
||||||
forwardKey = self._getLatestForwardKey()
|
forwardKey = self._getLatestForwardKey()
|
||||||
#logger.info('using ' + forwardKey)
|
|
||||||
if self._core._utils.validatePubKey(forwardKey):
|
if self._core._utils.validatePubKey(forwardKey):
|
||||||
retData = self._core._crypto.pubKeyEncrypt(data, forwardKey, encodedData=True, anonymous=True)
|
retData = self._core._crypto.pubKeyEncrypt(data, forwardKey, encodedData=True, anonymous=True)
|
||||||
else:
|
else:
|
||||||
@ -81,10 +89,7 @@ class OnionrUser:
|
|||||||
|
|
||||||
def forwardDecrypt(self, encrypted):
|
def forwardDecrypt(self, encrypted):
|
||||||
retData = ""
|
retData = ""
|
||||||
#logger.error(self.publicKey)
|
|
||||||
#logger.error(self.getGeneratedForwardKeys(False))
|
|
||||||
for key in self.getGeneratedForwardKeys(False):
|
for key in self.getGeneratedForwardKeys(False):
|
||||||
logger.info(encrypted)
|
|
||||||
try:
|
try:
|
||||||
retData = self._core._crypto.pubKeyDecrypt(encrypted, privkey=key[1], anonymous=True, encodedData=True)
|
retData = self._core._crypto.pubKeyDecrypt(encrypted, privkey=key[1], anonymous=True, encodedData=True)
|
||||||
except nacl.exceptions.CryptoError:
|
except nacl.exceptions.CryptoError:
|
||||||
|
@ -125,11 +125,11 @@ class OnionrUtils:
|
|||||||
for adder in newAdderList.split(','):
|
for adder in newAdderList.split(','):
|
||||||
adder = adder.strip()
|
adder = adder.strip()
|
||||||
if not adder in self._core.listAdders(randomOrder = False) and adder != self.getMyAddress() and not self._core._blacklist.inBlacklist(adder):
|
if not adder in self._core.listAdders(randomOrder = False) and adder != self.getMyAddress() and not self._core._blacklist.inBlacklist(adder):
|
||||||
if not config.get('tor.v3_onions') and len(adder) == 62:
|
if not config.get('tor.v3onions') and len(adder) == 62:
|
||||||
continue
|
continue
|
||||||
if self._core.addAddress(adder):
|
if self._core.addAddress(adder):
|
||||||
# Check if we have the maxmium amount of allowed stored peers
|
# Check if we have the maxmium amount of allowed stored peers
|
||||||
if config.get('peers.max_stored') > len(self._core.listAdders()):
|
if config.get('peers.max_stored_peers') > len(self._core.listAdders()):
|
||||||
logger.info('Added %s to db.' % adder, timestamp = True)
|
logger.info('Added %s to db.' % adder, timestamp = True)
|
||||||
retVal = True
|
retVal = True
|
||||||
else:
|
else:
|
||||||
@ -146,6 +146,8 @@ class OnionrUtils:
|
|||||||
try:
|
try:
|
||||||
with open('./' + self._core.dataDir + 'hs/hostname', 'r') as hostname:
|
with open('./' + self._core.dataDir + 'hs/hostname', 'r') as hostname:
|
||||||
return hostname.read().strip()
|
return hostname.read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return ""
|
||||||
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)
|
||||||
return None
|
return None
|
||||||
@ -163,7 +165,7 @@ class OnionrUtils:
|
|||||||
hostname = host.read()
|
hostname = host.read()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return False
|
return False
|
||||||
payload = 'http://%s:%s/client/?action=%s&token=%s&timingToken=%s' % (hostname, config.get('client.port'), command, config.get('client.hmac'), self.timingToken)
|
payload = 'http://%s:%s/client/?action=%s&token=%s&timingToken=%s' % (hostname, config.get('client.port'), command, config.get('client.webpassword'), self.timingToken)
|
||||||
if data != '':
|
if data != '':
|
||||||
payload += '&data=' + urllib.parse.quote_plus(data)
|
payload += '&data=' + urllib.parse.quote_plus(data)
|
||||||
try:
|
try:
|
||||||
@ -265,20 +267,14 @@ class OnionrUtils:
|
|||||||
'''
|
'''
|
||||||
myBlock = Block(blockHash, self._core)
|
myBlock = Block(blockHash, self._core)
|
||||||
if myBlock.isEncrypted:
|
if myBlock.isEncrypted:
|
||||||
#pass
|
myBlock.decrypt()
|
||||||
logger.warn(myBlock.decrypt())
|
|
||||||
if (myBlock.isEncrypted and myBlock.decrypted) or (not myBlock.isEncrypted):
|
if (myBlock.isEncrypted and myBlock.decrypted) or (not myBlock.isEncrypted):
|
||||||
blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks
|
blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks
|
||||||
signer = self.bytesToStr(myBlock.signer)
|
signer = self.bytesToStr(myBlock.signer)
|
||||||
valid = myBlock.verifySig()
|
valid = myBlock.verifySig()
|
||||||
|
|
||||||
logger.info('Checking for fs key')
|
|
||||||
if myBlock.getMetadata('newFSKey') is not None:
|
if myBlock.getMetadata('newFSKey') is not None:
|
||||||
onionrusers.OnionrUser(self._core, signer).addForwardKey(myBlock.getMetadata('newFSKey'))
|
onionrusers.OnionrUser(self._core, signer).addForwardKey(myBlock.getMetadata('newFSKey'))
|
||||||
else:
|
|
||||||
logger.warn('FS not used for this encrypted block')
|
|
||||||
logger.info(myBlock.bmetadata)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(blockType) <= 10:
|
if len(blockType) <= 10:
|
||||||
self._core.updateBlockInfo(blockHash, 'dataType', blockType)
|
self._core.updateBlockInfo(blockHash, 'dataType', blockType)
|
||||||
@ -295,7 +291,6 @@ class OnionrUtils:
|
|||||||
else:
|
else:
|
||||||
self._core.updateBlockInfo(blockHash, 'expire', expireTime)
|
self._core.updateBlockInfo(blockHash, 'expire', expireTime)
|
||||||
else:
|
else:
|
||||||
logger.info(myBlock.isEncrypted)
|
|
||||||
logger.debug('Not processing metadata on encrypted block we cannot decrypt.')
|
logger.debug('Not processing metadata on encrypted block we cannot decrypt.')
|
||||||
|
|
||||||
def escapeAnsi(self, line):
|
def escapeAnsi(self, line):
|
||||||
@ -616,7 +611,7 @@ class OnionrUtils:
|
|||||||
retData = False
|
retData = False
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
def doGetRequest(self, url, port=0, proxyType='tor'):
|
def doGetRequest(self, url, port=0, proxyType='tor', ignoreAPI=False):
|
||||||
'''
|
'''
|
||||||
Do a get request through a local tor or i2p instance
|
Do a get request through a local tor or i2p instance
|
||||||
'''
|
'''
|
||||||
@ -635,12 +630,13 @@ class OnionrUtils:
|
|||||||
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
|
# Check server is using same API version as us
|
||||||
try:
|
if not ignoreAPI:
|
||||||
response_headers = r.headers
|
try:
|
||||||
if r.headers['X-API'] != str(API_VERSION):
|
response_headers = r.headers
|
||||||
|
if r.headers['X-API'] != str(API_VERSION):
|
||||||
|
raise onionrexceptions.InvalidAPIVersion
|
||||||
|
except KeyError:
|
||||||
raise onionrexceptions.InvalidAPIVersion
|
raise onionrexceptions.InvalidAPIVersion
|
||||||
except KeyError:
|
|
||||||
raise onionrexceptions.InvalidAPIVersion
|
|
||||||
retData = r.text
|
retData = r.text
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
@ -701,7 +697,7 @@ class OnionrUtils:
|
|||||||
connectURLs = connectTest.read().split(',')
|
connectURLs = connectTest.read().split(',')
|
||||||
|
|
||||||
for url in connectURLs:
|
for url in connectURLs:
|
||||||
if self.doGetRequest(url, port=torPort) != False:
|
if self.doGetRequest(url, port=torPort, ignoreAPI=True) != False:
|
||||||
retData = True
|
retData = True
|
||||||
break
|
break
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
@ -33,7 +33,8 @@ class OnionrCLIUI:
|
|||||||
|
|
||||||
def subCommand(self, command):
|
def subCommand(self, command):
|
||||||
try:
|
try:
|
||||||
subprocess.run(["./onionr.py", command])
|
#subprocess.run(["./onionr.py", command])
|
||||||
|
subprocess.Popen(['./onionr.py', command], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -29,13 +29,19 @@ class OnionrFlow:
|
|||||||
self.myCore = pluginapi.get_core()
|
self.myCore = pluginapi.get_core()
|
||||||
self.alreadyOutputed = []
|
self.alreadyOutputed = []
|
||||||
self.flowRunning = False
|
self.flowRunning = False
|
||||||
|
self.channel = None
|
||||||
return
|
return
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
logger.warn("Please note: everything said here is public, even if a random channel name is used.")
|
||||||
message = ""
|
message = ""
|
||||||
self.flowRunning = True
|
self.flowRunning = True
|
||||||
newThread = threading.Thread(target=self.showOutput)
|
newThread = threading.Thread(target=self.showOutput)
|
||||||
newThread.start()
|
newThread.start()
|
||||||
|
try:
|
||||||
|
self.channel = logger.readline("Enter a channel name or none for default:")
|
||||||
|
except (KeyboardInterrupt, EOFError) as e:
|
||||||
|
self.flowRunning = False
|
||||||
while self.flowRunning:
|
while self.flowRunning:
|
||||||
try:
|
try:
|
||||||
message = logger.readline('\nInsert message into flow:').strip().replace('\n', '\\n').replace('\r', '\\r')
|
message = logger.readline('\nInsert message into flow:').strip().replace('\n', '\\n').replace('\r', '\\r')
|
||||||
@ -43,33 +49,39 @@ class OnionrFlow:
|
|||||||
pass
|
pass
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.flowRunning = False
|
self.flowRunning = False
|
||||||
if message == "q":
|
else:
|
||||||
self.flowRunning = False
|
if message == "q":
|
||||||
expireTime = self.myCore._utils.getEpoch() + 43200
|
self.flowRunning = False
|
||||||
if len(message) > 0:
|
expireTime = self.myCore._utils.getEpoch() + 43200
|
||||||
Block(content = message, type = 'txt', expire=expireTime, core = self.myCore).save()
|
if len(message) > 0:
|
||||||
|
insertBL = Block(content = message, type = 'txt', expire=expireTime, core = self.myCore)
|
||||||
|
insertBL.setMetadata('ch', self.channel)
|
||||||
|
insertBL.save()
|
||||||
|
|
||||||
logger.info("Flow is exiting, goodbye")
|
logger.info("Flow is exiting, goodbye")
|
||||||
return
|
return
|
||||||
|
|
||||||
def showOutput(self):
|
def showOutput(self):
|
||||||
while self.flowRunning:
|
while type(self.channel) is type(None) and self.flowRunning:
|
||||||
for block in Block.getBlocks(type = 'txt', core = self.myCore):
|
time.sleep(1)
|
||||||
if block.getHash() in self.alreadyOutputed:
|
try:
|
||||||
continue
|
while self.flowRunning:
|
||||||
if not self.flowRunning:
|
for block in Block.getBlocks(type = 'txt', core = self.myCore):
|
||||||
break
|
if block.getMetadata('ch') != self.channel:
|
||||||
logger.info('\n------------------------', prompt = False)
|
continue
|
||||||
content = block.getContent()
|
if block.getHash() in self.alreadyOutputed:
|
||||||
# Escape new lines, remove trailing whitespace, and escape ansi sequences
|
continue
|
||||||
content = self.myCore._utils.escapeAnsi(content.replace('\n', '\\n').replace('\r', '\\r').strip())
|
if not self.flowRunning:
|
||||||
logger.info(block.getDate().strftime("%m/%d %H:%M") + ' - ' + logger.colors.reset + content, prompt = False)
|
break
|
||||||
self.alreadyOutputed.append(block.getHash())
|
logger.info('\n------------------------', prompt = False)
|
||||||
try:
|
content = block.getContent()
|
||||||
time.sleep(5)
|
# Escape new lines, remove trailing whitespace, and escape ansi sequences
|
||||||
except KeyboardInterrupt:
|
content = self.myCore._utils.escapeAnsi(content.replace('\n', '\\n').replace('\r', '\\r').strip())
|
||||||
self.flowRunning = False
|
logger.info(block.getDate().strftime("%m/%d %H:%M") + ' - ' + logger.colors.reset + content, prompt = False)
|
||||||
pass
|
self.alreadyOutputed.append(block.getHash())
|
||||||
|
time.sleep(5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.flowRunning = False
|
||||||
|
|
||||||
def on_init(api, data = None):
|
def on_init(api, data = None):
|
||||||
'''
|
'''
|
||||||
|
@ -42,8 +42,9 @@ def _processUserInfo(api, newBlock):
|
|||||||
except onionrexceptions.InvalidMetadata:
|
except onionrexceptions.InvalidMetadata:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
api.get_core().setPeerInfo(signer, 'name', peerName)
|
if signer in self.api.get_core().listPeers():
|
||||||
logger.info('%s is now using the name %s.' % (signer, api.get_utils().escapeAnsi(peerName)))
|
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):
|
def _processForwardKey(api, myBlock):
|
||||||
'''
|
'''
|
||||||
|
@ -151,7 +151,7 @@ def check():
|
|||||||
# plugin management
|
# plugin management
|
||||||
|
|
||||||
def sanitize(name):
|
def sanitize(name):
|
||||||
return re.sub('[^0-9a-zA-Z]+', '', str(name).lower())[:255]
|
return re.sub('[^0-9a-zA-Z_]+', '', str(name).lower())[:255]
|
||||||
|
|
||||||
def blockToPlugin(block):
|
def blockToPlugin(block):
|
||||||
try:
|
try:
|
||||||
|
@ -79,28 +79,26 @@ class OnionrMail:
|
|||||||
for blockHash in self.myCore.getBlocksByType('pm'):
|
for blockHash in self.myCore.getBlocksByType('pm'):
|
||||||
pmBlocks[blockHash] = Block(blockHash, core=self.myCore)
|
pmBlocks[blockHash] = Block(blockHash, core=self.myCore)
|
||||||
pmBlocks[blockHash].decrypt()
|
pmBlocks[blockHash].decrypt()
|
||||||
|
|
||||||
while choice not in ('-q', 'q', 'quit'):
|
|
||||||
blockCount = 0
|
blockCount = 0
|
||||||
for blockHash in pmBlocks:
|
for blockHash in pmBlocks:
|
||||||
if not pmBlocks[blockHash].decrypted:
|
if not pmBlocks[blockHash].decrypted:
|
||||||
continue
|
continue
|
||||||
blockCount += 1
|
blockCount += 1
|
||||||
pmBlockMap[blockCount] = blockHash
|
pmBlockMap[blockCount] = blockHash
|
||||||
|
|
||||||
block = pmBlocks[blockHash]
|
block = pmBlocks[blockHash]
|
||||||
senderKey = block.signer
|
senderKey = block.signer
|
||||||
try:
|
try:
|
||||||
senderKey = senderKey.decode()
|
senderKey = senderKey.decode()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
senderDisplay = onionrusers.OnionrUser(self.myCore, senderKey).getName()
|
senderDisplay = onionrusers.OnionrUser(self.myCore, senderKey).getName()
|
||||||
if senderDisplay == 'anonymous':
|
if senderDisplay == 'anonymous':
|
||||||
senderDisplay = senderKey
|
senderDisplay = senderKey
|
||||||
|
|
||||||
blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M")
|
blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M")
|
||||||
displayList.append('%s. %s - %s: %s' % (blockCount, blockDate, senderDisplay[:12], blockHash))
|
displayList.append('%s. %s - %s: %s' % (blockCount, blockDate, senderDisplay[:12], blockHash))
|
||||||
#displayList.reverse()
|
while choice not in ('-q', 'q', 'quit'):
|
||||||
for i in displayList:
|
for i in displayList:
|
||||||
logger.info(i)
|
logger.info(i)
|
||||||
try:
|
try:
|
||||||
@ -138,7 +136,9 @@ class OnionrMail:
|
|||||||
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())))
|
||||||
logger.readline("Press enter to continue")
|
reply = logger.readline("Press enter to continue, or enter %s to reply" % ("-r",))
|
||||||
|
if reply == "-r":
|
||||||
|
self.draftMessage(self.myCore._utils.bytesToStr(readBlock.signer,))
|
||||||
return
|
return
|
||||||
|
|
||||||
def sentbox(self):
|
def sentbox(self):
|
||||||
@ -148,29 +148,36 @@ class OnionrMail:
|
|||||||
entering = True
|
entering = True
|
||||||
while entering:
|
while entering:
|
||||||
self.getSentList()
|
self.getSentList()
|
||||||
logger.info('Enter block number or -q to return')
|
logger.info('Enter a block number or -q to return')
|
||||||
try:
|
try:
|
||||||
choice = input('>')
|
choice = input('>')
|
||||||
except (EOFError, KeyboardInterrupt) as e:
|
except (EOFError, KeyboardInterrupt) as e:
|
||||||
entering = False
|
entering = False
|
||||||
else:
|
else:
|
||||||
if choice == '-q':
|
try:
|
||||||
entering = False
|
choice = int(choice) - 1
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.sentboxList[int(choice) - 1]
|
self.sentboxList[int(choice)]
|
||||||
except IndexError:
|
except (IndexError, ValueError) as e:
|
||||||
logger.warn('Invalid block.')
|
logger.warn('Invalid block.')
|
||||||
else:
|
else:
|
||||||
logger.info('Sent to: ' + self.sentMessages[self.sentboxList[int(choice) - 1]][1])
|
logger.info('Sent to: ' + self.sentMessages[self.sentboxList[int(choice)]][1])
|
||||||
# Print ansi escaped sent message
|
# Print ansi escaped sent message
|
||||||
logger.info(self.myCore._utils.escapeAnsi(self.sentMessages[self.sentboxList[int(choice) - 1]][0]))
|
logger.info(self.myCore._utils.escapeAnsi(self.sentMessages[self.sentboxList[int(choice)]][0]))
|
||||||
input('Press enter to continue...')
|
input('Press enter to continue...')
|
||||||
|
finally:
|
||||||
|
if choice == '-q':
|
||||||
|
entering = False
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def getSentList(self):
|
def getSentList(self):
|
||||||
count = 1
|
count = 1
|
||||||
|
self.sentboxList = []
|
||||||
|
self.sentMessages = {}
|
||||||
for i in self.sentboxTools.listSent():
|
for i in self.sentboxTools.listSent():
|
||||||
self.sentboxList.append(i['hash'])
|
self.sentboxList.append(i['hash'])
|
||||||
self.sentMessages[i['hash']] = (i['message'], i['peer'])
|
self.sentMessages[i['hash']] = (i['message'], i['peer'])
|
||||||
@ -178,28 +185,28 @@ class OnionrMail:
|
|||||||
logger.info('%s. %s - %s - %s' % (count, i['hash'], i['peer'][:12], i['date']))
|
logger.info('%s. %s - %s - %s' % (count, i['hash'], i['peer'][:12], i['date']))
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
def draftMessage(self):
|
def draftMessage(self, recip=''):
|
||||||
message = ''
|
message = ''
|
||||||
newLine = ''
|
newLine = ''
|
||||||
recip = ''
|
entering = False
|
||||||
entering = True
|
if len(recip) == 0:
|
||||||
|
entering = True
|
||||||
while entering:
|
while entering:
|
||||||
try:
|
try:
|
||||||
recip = logger.readline('Enter peer address, or q to stop:').strip()
|
recip = logger.readline('Enter peer address, or -q to stop:').strip()
|
||||||
if recip in ('-q', 'q'):
|
if recip in ('-q', 'q'):
|
||||||
raise EOFError
|
raise EOFError
|
||||||
if not self.myCore._utils.validatePubKey(recip):
|
if not self.myCore._utils.validatePubKey(recip):
|
||||||
raise onionrexceptions.InvalidPubkey('Must be a valid ed25519 base32 encoded public key')
|
raise onionrexceptions.InvalidPubkey('Must be a valid ed25519 base32 encoded public key')
|
||||||
except onionrexceptions.InvalidPubkey:
|
except onionrexceptions.InvalidPubkey:
|
||||||
logger.warn('Invalid public key')
|
logger.warn('Invalid public key')
|
||||||
except (KeyboardInterrupt, EOFError):
|
except (KeyboardInterrupt, EOFError):
|
||||||
entering = False
|
entering = False
|
||||||
|
else:
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
break
|
# if -q or ctrl-c/d, exit function here, otherwise we successfully got the public key
|
||||||
else:
|
return
|
||||||
# if -q or ctrl-c/d, exit function here, otherwise we successfully got the public key
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info('Enter your message, stop by entering -q on a new line.')
|
logger.info('Enter your message, stop by entering -q on a new line.')
|
||||||
while newLine != '-q':
|
while newLine != '-q':
|
||||||
|
@ -29,7 +29,7 @@ class SentBox:
|
|||||||
self.cursor = self.conn.cursor()
|
self.cursor = self.conn.cursor()
|
||||||
self.core = mycore
|
self.core = mycore
|
||||||
return
|
return
|
||||||
|
|
||||||
def createDB(self):
|
def createDB(self):
|
||||||
conn = sqlite3.connect(self.dbLocation)
|
conn = sqlite3.connect(self.dbLocation)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@ -48,15 +48,15 @@ class SentBox:
|
|||||||
for entry in self.cursor.execute('SELECT * FROM sent;'):
|
for entry in self.cursor.execute('SELECT * FROM sent;'):
|
||||||
retData.append({'hash': entry[0], 'peer': entry[1], 'message': entry[2], 'date': entry[3]})
|
retData.append({'hash': entry[0], 'peer': entry[1], 'message': entry[2], 'date': entry[3]})
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
def addToSent(self, blockID, peer, message):
|
def addToSent(self, blockID, peer, message):
|
||||||
args = (blockID, peer, message, self.core._utils.getEpoch())
|
args = (blockID, peer, message, self.core._utils.getEpoch())
|
||||||
self.cursor.execute('INSERT INTO sent VALUES(?, ?, ?, ?)', args)
|
self.cursor.execute('INSERT INTO sent VALUES(?, ?, ?, ?)', args)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
return
|
return
|
||||||
|
|
||||||
def removeSent(self, blockID):
|
def removeSent(self, blockID):
|
||||||
args = (blockID,)
|
args = (blockID,)
|
||||||
self.cursor.execute('DELETE FROM sent where hash=?', args)
|
self.cursor.execute('DELETE FROM sent where hash=?', args)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
return
|
return
|
||||||
|
@ -4,14 +4,9 @@
|
|||||||
"display_header" : true,
|
"display_header" : true,
|
||||||
"minimum_block_pow": 5,
|
"minimum_block_pow": 5,
|
||||||
"minimum_send_pow": 5,
|
"minimum_send_pow": 5,
|
||||||
|
"socket_servers": false,
|
||||||
"minimum_block_pow": 5,
|
"security_level": 0,
|
||||||
"minimum_send_pow": 5,
|
"public_key": ""
|
||||||
|
|
||||||
"direct_connect" : {
|
|
||||||
"respond" : true,
|
|
||||||
"execute_callbacks" : true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"www" : {
|
"www" : {
|
||||||
@ -52,7 +47,7 @@
|
|||||||
"verbosity" : "default",
|
"verbosity" : "default",
|
||||||
|
|
||||||
"file": {
|
"file": {
|
||||||
"output": false,
|
"output": true,
|
||||||
"path": "data/output.log"
|
"path": "data/output.log"
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -63,7 +58,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"tor" : {
|
"tor" : {
|
||||||
"v3onions" : false
|
"v3onions" : true
|
||||||
},
|
},
|
||||||
|
|
||||||
"i2p" : {
|
"i2p" : {
|
||||||
@ -86,7 +81,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"timers" : {
|
"timers" : {
|
||||||
"lookup_blocks" : 25,
|
"lookupBlocks" : 25,
|
||||||
"get_blocks" : 30
|
"getBlocks" : 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user