finish merging new-main
This commit is contained in:
commit
97e0945e12
76 changed files with 2456 additions and 1058 deletions
0
onionr/__init__.py
Normal file → Executable file
0
onionr/__init__.py
Normal file → Executable file
|
@ -21,10 +21,13 @@ from gevent.pywsgi import WSGIServer, WSGIHandler
|
|||
from gevent import Timeout
|
||||
import flask, cgi, uuid
|
||||
from flask import request, Response, abort, send_from_directory
|
||||
import sys, random, threading, hmac, hashlib, base64, time, math, os, json, socket
|
||||
import sys, random, threading, hmac, base64, time, os, json, socket
|
||||
import core
|
||||
from onionrblockapi import Block
|
||||
import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config, onionr
|
||||
import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config
|
||||
import httpapi
|
||||
from httpapi import friendsapi, simplecache
|
||||
import onionr
|
||||
|
||||
class FDSafeHandler(WSGIHandler):
|
||||
'''Our WSGI handler. Doesn't do much non-default except timeouts'''
|
||||
|
@ -38,22 +41,22 @@ class FDSafeHandler(WSGIHandler):
|
|||
|
||||
def setBindIP(filePath):
|
||||
'''Set a random localhost IP to a specified file (intended for private or public API localhost IPs)'''
|
||||
hostOctets = [str(127), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF))]
|
||||
data = '.'.join(hostOctets)
|
||||
|
||||
# Try to bind IP. Some platforms like Mac block non normal 127.x.x.x
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.bind((data, 0))
|
||||
except OSError:
|
||||
# if mac/non-bindable, show warning and default to 127.0.0.1
|
||||
logger.warn('Your platform appears to not support random local host addresses 127.x.x.x. Falling back to 127.0.0.1.')
|
||||
if config.get('general.random_bind_ip', True):
|
||||
hostOctets = [str(127), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF)), str(random.randint(0x02, 0xFF))]
|
||||
data = '.'.join(hostOctets)
|
||||
# Try to bind IP. Some platforms like Mac block non normal 127.x.x.x
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.bind((data, 0))
|
||||
except OSError:
|
||||
# if mac/non-bindable, show warning and default to 127.0.0.1
|
||||
logger.warn('Your platform appears to not support random local host addresses 127.x.x.x. Falling back to 127.0.0.1.')
|
||||
data = '127.0.0.1'
|
||||
s.close()
|
||||
else:
|
||||
data = '127.0.0.1'
|
||||
s.close()
|
||||
|
||||
with open(filePath, 'w') as bindFile:
|
||||
bindFile.write(data)
|
||||
|
||||
return data
|
||||
|
||||
class PublicAPI:
|
||||
|
@ -173,7 +176,7 @@ class PublicAPI:
|
|||
try:
|
||||
newNode = request.form['node'].encode()
|
||||
except KeyError:
|
||||
logger.warn('No block specified for upload')
|
||||
logger.warn('No node specified for upload')
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
|
@ -230,7 +233,7 @@ class PublicAPI:
|
|||
while self.torAdder == '':
|
||||
clientAPI._core.refreshFirstStartVars()
|
||||
self.torAdder = clientAPI._core.hsAddress
|
||||
time.sleep(1)
|
||||
time.sleep(0.1)
|
||||
self.httpServer = WSGIServer((self.host, self.bindPort), app, log=None, handler_class=FDSafeHandler)
|
||||
self.httpServer.serve_forever()
|
||||
|
||||
|
@ -248,9 +251,6 @@ class API:
|
|||
This initilization defines all of the API entry points and handlers for the endpoints and errors
|
||||
This also saves the used host (random localhost IP address) to the data folder in host.txt
|
||||
'''
|
||||
# assert isinstance(onionrInst, onionr.Onionr)
|
||||
# configure logger and stuff
|
||||
onionr.Onionr.setupConfig('data/', self = self)
|
||||
|
||||
self.debug = debug
|
||||
self._core = onionrInst.onionrCore
|
||||
|
@ -262,7 +262,7 @@ class API:
|
|||
self.bindPort = bindPort
|
||||
|
||||
# Be extremely mindful of this. These are endpoints available without a password
|
||||
self.whitelistEndpoints = ('site', 'www', 'onionrhome', 'board', 'boardContent', 'sharedContent', 'mail', 'mailindex')
|
||||
self.whitelistEndpoints = ('site', 'www', 'onionrhome', 'board', 'boardContent', 'sharedContent', 'mail', 'mailindex', 'friends', 'friendsindex')
|
||||
|
||||
self.clientToken = config.get('client.webpassword')
|
||||
self.timeBypassToken = base64.b16encode(os.urandom(32)).decode()
|
||||
|
@ -276,6 +276,9 @@ class API:
|
|||
self.pluginResponses = {} # Responses for plugin endpoints
|
||||
self.queueResponse = {}
|
||||
onionrInst.setClientAPIInst(self)
|
||||
app.register_blueprint(friendsapi.friends)
|
||||
app.register_blueprint(simplecache.simplecache)
|
||||
httpapi.load_plugin_blueprints(app)
|
||||
|
||||
@app.before_request
|
||||
def validateRequest():
|
||||
|
@ -287,9 +290,11 @@ class API:
|
|||
return
|
||||
try:
|
||||
if not hmac.compare_digest(request.headers['token'], self.clientToken):
|
||||
abort(403)
|
||||
if not hmac.compare_digest(request.form['token'], self.clientToken):
|
||||
abort(403)
|
||||
except KeyError:
|
||||
abort(403)
|
||||
if not hmac.compare_digest(request.form['token'], self.clientToken):
|
||||
abort(403)
|
||||
|
||||
@app.after_request
|
||||
def afterReq(resp):
|
||||
|
@ -315,6 +320,14 @@ class API:
|
|||
@app.route('/mail/', endpoint='mailindex')
|
||||
def loadMailIndex():
|
||||
return send_from_directory('static-data/www/mail/', 'index.html')
|
||||
|
||||
@app.route('/friends/<path:path>', endpoint='friends')
|
||||
def loadContacts(path):
|
||||
return send_from_directory('static-data/www/friends/', path)
|
||||
|
||||
@app.route('/friends/', endpoint='friendsindex')
|
||||
def loadContacts():
|
||||
return send_from_directory('static-data/www/friends/', 'index.html')
|
||||
|
||||
@app.route('/board/<path:path>', endpoint='boardContent')
|
||||
def boardContent(path):
|
||||
|
@ -406,14 +419,16 @@ class API:
|
|||
if self._core._utils.validateHash(bHash):
|
||||
try:
|
||||
resp = Block(bHash).bcontent
|
||||
except onionrexceptions.NoDataAvailable:
|
||||
abort(404)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
resp = base64.b64decode(resp)
|
||||
except:
|
||||
pass
|
||||
if resp == 'Not Found':
|
||||
abourt(404)
|
||||
if resp == 'Not Found' or not resp:
|
||||
abort(404)
|
||||
return Response(resp)
|
||||
|
||||
@app.route('/waitforshare/<name>', methods=['post'])
|
||||
|
@ -512,7 +527,6 @@ class API:
|
|||
data = subpath.split('/')
|
||||
if len(data) > 1:
|
||||
plName = data[0]
|
||||
|
||||
events.event('pluginRequest', {'name': plName, 'path': subpath, 'pluginResponse': pluginResponseCode, 'postData': postData}, onionr=onionrInst)
|
||||
while True:
|
||||
try:
|
||||
|
|
|
@ -19,13 +19,15 @@
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid
|
||||
import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block
|
||||
import onionrdaemontools, onionrsockets, onionr, onionrproofs
|
||||
import binascii
|
||||
import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid, binascii
|
||||
from dependencies import secrets
|
||||
from defusedxml import minidom
|
||||
from utils import networkmerger
|
||||
import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block
|
||||
from communicatorutils import onionrdaemontools
|
||||
import onionrsockets, onionr, onionrproofs
|
||||
from communicatorutils import onionrcommunicatortimers, proxypicker
|
||||
|
||||
OnionrCommunicatorTimers = onionrcommunicatortimers.OnionrCommunicatorTimers
|
||||
|
||||
config.reload()
|
||||
class OnionrCommunicatorDaemon:
|
||||
|
@ -44,10 +46,6 @@ class OnionrCommunicatorDaemon:
|
|||
self.proxyPort = proxyPort
|
||||
self._core = onionrInst.onionrCore
|
||||
|
||||
# initialize NIST beacon salt and time
|
||||
self.nistSaltTimestamp = 0
|
||||
self.powSalt = 0
|
||||
|
||||
self.blocksToUpload = []
|
||||
|
||||
# loop time.sleep delay in seconds
|
||||
|
@ -171,7 +169,8 @@ class OnionrCommunicatorDaemon:
|
|||
# Validate new peers are good format and not already in queue
|
||||
invalid = []
|
||||
for x in newPeers:
|
||||
if not self._core._utils.validateID(x) or x in self.newPeers:
|
||||
x = x.strip()
|
||||
if not self._core._utils.validateID(x) or x in self.newPeers or x == self._core.hsAddress:
|
||||
invalid.append(x)
|
||||
for x in invalid:
|
||||
newPeers.remove(x)
|
||||
|
@ -431,16 +430,18 @@ class OnionrCommunicatorDaemon:
|
|||
for address in peerList:
|
||||
if not config.get('tor.v3onions') and len(address) == 62:
|
||||
continue
|
||||
if address == self._core.hsAddress:
|
||||
continue
|
||||
if len(address) == 0 or address in tried or address in self.onlinePeers or address in self.cooldownPeer:
|
||||
continue
|
||||
if self.shutdown:
|
||||
return
|
||||
if self.peerAction(address, 'ping') == 'pong!':
|
||||
logger.info('Connected to ' + address)
|
||||
time.sleep(0.1)
|
||||
if address not in mainPeerList:
|
||||
networkmerger.mergeAdders(address, self._core)
|
||||
if address not in self.onlinePeers:
|
||||
logger.info('Connected to ' + address)
|
||||
self.onlinePeers.append(address)
|
||||
self.connectTimes[address] = self._core._utils.getEpoch()
|
||||
retData = address
|
||||
|
@ -487,7 +488,7 @@ class OnionrCommunicatorDaemon:
|
|||
score = str(self.getPeerProfileInstance(i).score)
|
||||
logger.info(i + ', score: ' + score)
|
||||
|
||||
def peerAction(self, peer, action, data=''):
|
||||
def peerAction(self, peer, action, data='', returnHeaders=False):
|
||||
'''Perform a get request to a peer'''
|
||||
if len(peer) == 0:
|
||||
return False
|
||||
|
@ -511,7 +512,7 @@ class OnionrCommunicatorDaemon:
|
|||
else:
|
||||
self._core.setAddressInfo(peer, 'lastConnect', self._core._utils.getEpoch())
|
||||
self.getPeerProfileInstance(peer).addScore(1)
|
||||
return retData
|
||||
return retData # If returnHeaders, returns tuple of data, headers. if not, just data string
|
||||
|
||||
def getPeerProfileInstance(self, peer):
|
||||
'''Gets a peer profile instance from the list of profiles, by address name'''
|
||||
|
@ -603,11 +604,7 @@ class OnionrCommunicatorDaemon:
|
|||
triedPeers.append(peer)
|
||||
url = 'http://' + peer + '/upload'
|
||||
data = {'block': block.Block(bl).getRaw()}
|
||||
proxyType = ''
|
||||
if peer.endswith('.onion'):
|
||||
proxyType = 'tor'
|
||||
elif peer.endswith('.i2p'):
|
||||
proxyType = 'i2p'
|
||||
proxyType = proxypicker.pick_proxy(peer)
|
||||
logger.info("Uploading block to " + peer)
|
||||
if not self._core._utils.doPostRequest(url, data=data, proxyType=proxyType) == False:
|
||||
self._core._utils.localCommand('waitforshare/' + bl, post=True)
|
||||
|
@ -644,48 +641,5 @@ class OnionrCommunicatorDaemon:
|
|||
|
||||
self.decrementThreadCount('runCheck')
|
||||
|
||||
class OnionrCommunicatorTimers:
|
||||
def __init__(self, daemonInstance, timerFunction, frequency, makeThread=True, threadAmount=1, maxThreads=5, requiresPeer=False):
|
||||
self.timerFunction = timerFunction
|
||||
self.frequency = frequency
|
||||
self.threadAmount = threadAmount
|
||||
self.makeThread = makeThread
|
||||
self.requiresPeer = requiresPeer
|
||||
self.daemonInstance = daemonInstance
|
||||
self.maxThreads = maxThreads
|
||||
self._core = self.daemonInstance._core
|
||||
|
||||
self.daemonInstance.timers.append(self)
|
||||
self.count = 0
|
||||
|
||||
def processTimer(self):
|
||||
|
||||
# mark how many instances of a thread we have (decremented at thread end)
|
||||
try:
|
||||
self.daemonInstance.threadCounts[self.timerFunction.__name__]
|
||||
except KeyError:
|
||||
self.daemonInstance.threadCounts[self.timerFunction.__name__] = 0
|
||||
|
||||
# execute thread if it is time, and we are not missing *required* online peer
|
||||
if self.count == self.frequency:
|
||||
try:
|
||||
if self.requiresPeer and len(self.daemonInstance.onlinePeers) == 0:
|
||||
raise onionrexceptions.OnlinePeerNeeded
|
||||
except onionrexceptions.OnlinePeerNeeded:
|
||||
pass
|
||||
else:
|
||||
if self.makeThread:
|
||||
for i in range(self.threadAmount):
|
||||
if self.daemonInstance.threadCounts[self.timerFunction.__name__] >= self.maxThreads:
|
||||
logger.debug('%s is currently using the maximum number of threads, not starting another.' % self.timerFunction.__name__)
|
||||
else:
|
||||
self.daemonInstance.threadCounts[self.timerFunction.__name__] += 1
|
||||
newThread = threading.Thread(target=self.timerFunction)
|
||||
newThread.start()
|
||||
else:
|
||||
self.timerFunction()
|
||||
self.count = -1 # negative 1 because its incremented at bottom
|
||||
self.count += 1
|
||||
|
||||
def startCommunicator(onionrInst, proxyPort):
|
||||
OnionrCommunicatorDaemon(onionrInst, proxyPort)
|
63
onionr/communicatorutils/onionrcommunicatortimers.py
Executable file
63
onionr/communicatorutils/onionrcommunicatortimers.py
Executable file
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python3
|
||||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This file contains timer control for the communicator
|
||||
'''
|
||||
'''
|
||||
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 threading, onionrexceptions, logger
|
||||
class OnionrCommunicatorTimers:
|
||||
def __init__(self, daemonInstance, timerFunction, frequency, makeThread=True, threadAmount=1, maxThreads=5, requiresPeer=False):
|
||||
self.timerFunction = timerFunction
|
||||
self.frequency = frequency
|
||||
self.threadAmount = threadAmount
|
||||
self.makeThread = makeThread
|
||||
self.requiresPeer = requiresPeer
|
||||
self.daemonInstance = daemonInstance
|
||||
self.maxThreads = maxThreads
|
||||
self._core = self.daemonInstance._core
|
||||
|
||||
self.daemonInstance.timers.append(self)
|
||||
self.count = 0
|
||||
|
||||
def processTimer(self):
|
||||
|
||||
# mark how many instances of a thread we have (decremented at thread end)
|
||||
try:
|
||||
self.daemonInstance.threadCounts[self.timerFunction.__name__]
|
||||
except KeyError:
|
||||
self.daemonInstance.threadCounts[self.timerFunction.__name__] = 0
|
||||
|
||||
# execute thread if it is time, and we are not missing *required* online peer
|
||||
if self.count == self.frequency and not self.daemonInstance.shutdown:
|
||||
try:
|
||||
if self.requiresPeer and len(self.daemonInstance.onlinePeers) == 0:
|
||||
raise onionrexceptions.OnlinePeerNeeded
|
||||
except onionrexceptions.OnlinePeerNeeded:
|
||||
pass
|
||||
else:
|
||||
if self.makeThread:
|
||||
for i in range(self.threadAmount):
|
||||
if self.daemonInstance.threadCounts[self.timerFunction.__name__] >= self.maxThreads:
|
||||
logger.debug('%s is currently using the maximum number of threads, not starting another.' % self.timerFunction.__name__)
|
||||
else:
|
||||
self.daemonInstance.threadCounts[self.timerFunction.__name__] += 1
|
||||
newThread = threading.Thread(target=self.timerFunction)
|
||||
newThread.start()
|
||||
else:
|
||||
self.timerFunction()
|
||||
self.count = -1 # negative 1 because its incremented at bottom
|
||||
self.count += 1
|
|
@ -30,16 +30,22 @@ class DaemonTools:
|
|||
'''
|
||||
def __init__(self, daemon):
|
||||
self.daemon = daemon
|
||||
self.announceProgress = {}
|
||||
self.announceCache = {}
|
||||
|
||||
def announceNode(self):
|
||||
'''Announce our node to our peers'''
|
||||
retData = False
|
||||
announceFail = False
|
||||
|
||||
# Do not let announceCache get too large
|
||||
if len(self.announceCache) >= 10000:
|
||||
self.announceCache.popitem()
|
||||
|
||||
if self.daemon._core.config.get('general.security_level', 0) == 0:
|
||||
# Announce to random online peers
|
||||
for i in self.daemon.onlinePeers:
|
||||
if not i in self.announceCache:
|
||||
if not i in self.announceCache and not i in self.announceProgress:
|
||||
peer = i
|
||||
break
|
||||
else:
|
||||
|
@ -66,7 +72,9 @@ class DaemonTools:
|
|||
elif len(existingRand) > 0:
|
||||
data['random'] = existingRand
|
||||
else:
|
||||
self.announceProgress[peer] = True
|
||||
proof = onionrproofs.DataPOW(combinedNodes, forceDifficulty=4)
|
||||
del self.announceProgress[peer]
|
||||
try:
|
||||
data['random'] = base64.b64encode(proof.waitForResult()[1])
|
||||
except TypeError:
|
||||
|
@ -89,7 +97,8 @@ class DaemonTools:
|
|||
'''Check if we are connected to the internet or not when we can't connect to any peers'''
|
||||
if len(self.daemon.onlinePeers) == 0:
|
||||
if not netutils.checkNetwork(self.daemon._core._utils, torPort=self.daemon.proxyPort):
|
||||
logger.warn('Network check failed, are you connected to the internet?')
|
||||
if not self.daemon.shutdown:
|
||||
logger.warn('Network check failed, are you connected to the internet?')
|
||||
self.daemon.isOnline = False
|
||||
else:
|
||||
self.daemon.isOnline = True
|
||||
|
@ -197,7 +206,7 @@ class DaemonTools:
|
|||
fakePeer = ''
|
||||
chance = 10
|
||||
if secrets.randbelow(chance) == (chance - 1):
|
||||
fakePeer = self.daemon._core._crypto.generatePubKey()[0]
|
||||
fakePeer = 'OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA===='
|
||||
data = secrets.token_hex(secrets.randbelow(500) + 1)
|
||||
self.daemon._core.insertBlock(data, header='pm', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'})
|
||||
self.daemon.decrementThreadCount('insertDeniableBlock')
|
25
onionr/communicatorutils/proxypicker.py
Normal file
25
onionr/communicatorutils/proxypicker.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
Just picks a proxy
|
||||
'''
|
||||
'''
|
||||
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/>.
|
||||
'''
|
||||
|
||||
def pick_proxy(peer_address):
|
||||
if peer_address.endswith('.onion'):
|
||||
return 'tor'
|
||||
elif peer_address.endswith('.i2p'):
|
||||
return 'i2p'
|
|
@ -19,11 +19,11 @@
|
|||
'''
|
||||
import sqlite3, os, sys, time, math, base64, tarfile, nacl, logger, json, netcontroller, math, config, uuid
|
||||
from onionrblockapi import Block
|
||||
|
||||
import deadsimplekv as simplekv
|
||||
import onionrutils, onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions
|
||||
import onionrblacklist
|
||||
from onionrusers import onionrusers
|
||||
import dbcreator, onionrstorage, serializeddata
|
||||
import dbcreator, onionrstorage, serializeddata, subprocesspow
|
||||
from etc import onionrvalues
|
||||
|
||||
if sys.version_info < (3, 6):
|
||||
|
@ -65,6 +65,7 @@ class Core:
|
|||
self.dataNonceFile = self.dataDir + 'block-nonces.dat'
|
||||
self.dbCreate = dbcreator.DBCreator(self)
|
||||
self.forwardKeysFile = self.dataDir + 'forward-keys.db'
|
||||
self.keyStore = simplekv.DeadSimpleKV(self.dataDir + 'cachedstorage.dat', refresh_seconds=5)
|
||||
|
||||
# Socket data, defined here because of multithreading constraints with gevent
|
||||
self.killSockets = False
|
||||
|
@ -105,7 +106,6 @@ class Core:
|
|||
logger.warn('Warning: address bootstrap file not found ' + self.bootstrapFileLocation)
|
||||
|
||||
self._utils = onionrutils.OnionrUtils(self)
|
||||
self.blockCache = onionrstorage.BlockCache()
|
||||
# Initialize the crypto object
|
||||
self._crypto = onionrcrypto.OnionrCrypto(self)
|
||||
self._blacklist = onionrblacklist.OnionrBlackList(self)
|
||||
|
@ -121,7 +121,6 @@ class Core:
|
|||
'''
|
||||
Hack to refresh some vars which may not be set on first start
|
||||
'''
|
||||
|
||||
if os.path.exists(self.dataDir + '/hs/hostname'):
|
||||
with open(self.dataDir + '/hs/hostname', 'r') as hs:
|
||||
self.hsAddress = hs.read().strip()
|
||||
|
@ -468,14 +467,6 @@ class Core:
|
|||
except TypeError:
|
||||
pass
|
||||
|
||||
if getPow:
|
||||
try:
|
||||
peerList.append(self._crypto.pubKey + '-' + self._crypto.pubKeyPowToken)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
peerList.append(self._crypto.pubKey)
|
||||
|
||||
conn.close()
|
||||
|
||||
return peerList
|
||||
|
@ -597,10 +588,6 @@ class Core:
|
|||
conn = sqlite3.connect(self.blockDB, timeout=30)
|
||||
c = conn.cursor()
|
||||
|
||||
# if unsaved:
|
||||
# execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
|
||||
# else:
|
||||
# execute = 'SELECT hash FROM hashes ORDER BY dateReceived ASC;'
|
||||
execute = 'SELECT hash FROM hashes WHERE dateReceived >= ? ORDER BY dateReceived ASC;'
|
||||
args = (dateRec,)
|
||||
rows = list()
|
||||
|
@ -702,6 +689,8 @@ class Core:
|
|||
return False
|
||||
retData = False
|
||||
|
||||
createTime = self._utils.getRoundedEpoch()
|
||||
|
||||
# check nonce
|
||||
dataNonce = self._utils.bytesToStr(self._crypto.sha3Hash(data))
|
||||
try:
|
||||
|
@ -719,10 +708,7 @@ class Core:
|
|||
data = str(data)
|
||||
plaintext = data
|
||||
plaintextMeta = {}
|
||||
|
||||
# Convert asym peer human readable key to base32 if set
|
||||
if ' ' in asymPeer.strip():
|
||||
asymPeer = self._utils.convertHumanReadableID(asymPeer)
|
||||
plaintextPeer = asymPeer
|
||||
|
||||
retData = ''
|
||||
signature = ''
|
||||
|
@ -745,6 +731,7 @@ class Core:
|
|||
pass
|
||||
|
||||
if encryptType == 'asym':
|
||||
meta['rply'] = createTime # Duplicate the time in encrypted messages to prevent replays
|
||||
if not disableForward and sign and asymPeer != self._crypto.pubKey:
|
||||
try:
|
||||
forwardEncrypted = onionrusers.OnionrUser(self, asymPeer).forwardEncrypt(data)
|
||||
|
@ -792,7 +779,7 @@ class Core:
|
|||
metadata['meta'] = jsonMeta
|
||||
metadata['sig'] = signature
|
||||
metadata['signer'] = signer
|
||||
metadata['time'] = self._utils.getRoundedEpoch()
|
||||
metadata['time'] = createTime
|
||||
|
||||
# ensure expire is integer and of sane length
|
||||
if type(expire) is not type(None):
|
||||
|
@ -800,8 +787,7 @@ class Core:
|
|||
metadata['expire'] = expire
|
||||
|
||||
# send block data (and metadata) to POW module to get tokenized block data
|
||||
proof = onionrproofs.POW(metadata, data)
|
||||
payload = proof.waitForResult()
|
||||
payload = subprocesspow.SubprocessPOW(data, metadata, self).start()
|
||||
if payload != False:
|
||||
try:
|
||||
retData = self.setData(payload)
|
||||
|
@ -817,7 +803,10 @@ class Core:
|
|||
self.daemonQueueAdd('uploadBlock', retData)
|
||||
|
||||
if retData != False:
|
||||
events.event('insertblock', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer)}, onionr = self.onionrInst, threaded = True)
|
||||
if plaintextPeer == 'OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA====':
|
||||
events.event('insertdeniable', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer)}, onionr = self.onionrInst, threaded = True)
|
||||
else:
|
||||
events.event('insertblock', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer)}, onionr = self.onionrInst, threaded = True)
|
||||
return retData
|
||||
|
||||
def introduceNode(self):
|
||||
|
|
29
onionr/httpapi/__init__.py
Executable file
29
onionr/httpapi/__init__.py
Executable file
|
@ -0,0 +1,29 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This file registers plugin's flask blueprints for the client http server
|
||||
'''
|
||||
'''
|
||||
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 onionrplugins
|
||||
|
||||
def load_plugin_blueprints(flaskapp):
|
||||
'''Iterate enabled plugins and load any http endpoints they have'''
|
||||
for plugin in onionrplugins.get_enabled_plugins():
|
||||
plugin = onionrplugins.get_plugin(plugin)
|
||||
try:
|
||||
flaskapp.register_blueprint(getattr(plugin, 'flask_blueprint'))
|
||||
except AttributeError:
|
||||
pass
|
56
onionr/httpapi/friendsapi/__init__.py
Executable file
56
onionr/httpapi/friendsapi/__init__.py
Executable file
|
@ -0,0 +1,56 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This file creates http endpoints for friend management
|
||||
'''
|
||||
'''
|
||||
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 core, json
|
||||
from onionrusers import contactmanager
|
||||
from flask import Blueprint, Response, request, abort, redirect
|
||||
|
||||
friends = Blueprint('friends', __name__)
|
||||
|
||||
@friends.route('/friends/list')
|
||||
def list_friends():
|
||||
pubkey_list = {}
|
||||
friend_list = contactmanager.ContactManager.list_friends(core.Core())
|
||||
for friend in friend_list:
|
||||
pubkey_list[friend.publicKey] = {'name': friend.get_info('name')}
|
||||
return json.dumps(pubkey_list)
|
||||
|
||||
@friends.route('/friends/add/<pubkey>', methods=['POST'])
|
||||
def add_friend(pubkey):
|
||||
contactmanager.ContactManager(core.Core(), pubkey, saveUser=True).setTrust(1)
|
||||
return redirect(request.referrer + '#' + request.form['token'])
|
||||
|
||||
@friends.route('/friends/remove/<pubkey>', methods=['POST'])
|
||||
def remove_friend(pubkey):
|
||||
contactmanager.ContactManager(core.Core(), pubkey).setTrust(0)
|
||||
return redirect(request.referrer + '#' + request.form['token'])
|
||||
|
||||
@friends.route('/friends/setinfo/<pubkey>/<key>', methods=['POST'])
|
||||
def set_info(pubkey, key):
|
||||
data = request.form['data']
|
||||
contactmanager.ContactManager(core.Core(), pubkey).set_info(key, data)
|
||||
return redirect(request.referrer + '#' + request.form['token'])
|
||||
|
||||
@friends.route('/friends/getinfo/<pubkey>/<key>')
|
||||
def get_info(pubkey, key):
|
||||
retData = contactmanager.ContactManager(core.Core(), pubkey).get_info(key)
|
||||
if retData is None:
|
||||
abort(404)
|
||||
else:
|
||||
return retData
|
31
onionr/httpapi/simplecache/__init__.py
Executable file
31
onionr/httpapi/simplecache/__init__.py
Executable file
|
@ -0,0 +1,31 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This file creates http endpoints for friend management
|
||||
'''
|
||||
'''
|
||||
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 core
|
||||
from flask import Blueprint, Response, request, abort
|
||||
|
||||
simplecache = Blueprint('simplecache', __name__)
|
||||
|
||||
@simplecache.route('/get/<key>')
|
||||
def get_key(key):
|
||||
return
|
||||
|
||||
@simplecache.route('/set/<key>', methods=['POST'])
|
||||
def set_key(key):
|
||||
return
|
|
@ -147,7 +147,8 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
|
|||
torVersion = subprocess.Popen([self.torBinary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
for line in iter(torVersion.stdout.readline, b''):
|
||||
if 'Tor 0.2.' in line.decode():
|
||||
logger.warn("Running 0.2.x Tor series, no support for v3 onion peers")
|
||||
logger.error('Tor 0.3+ required')
|
||||
sys.exit(1)
|
||||
break
|
||||
torVersion.kill()
|
||||
|
||||
|
@ -162,7 +163,7 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
|
|||
logger.fatal('Failed to start Tor. Maybe a stray instance of Tor used by Onionr is still running?')
|
||||
return False
|
||||
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
|
||||
|
||||
logger.debug('Finished starting Tor.', timestamp=True)
|
||||
|
|
840
onionr/onionr.py
840
onionr/onionr.py
File diff suppressed because it is too large
Load diff
|
@ -26,7 +26,7 @@ class Block:
|
|||
blockCacheOrder = list() # NEVER write your own code that writes to this!
|
||||
blockCache = dict() # should never be accessed directly, look at Block.getCache()
|
||||
|
||||
def __init__(self, hash = None, core = None, type = None, content = None, expire=None, decrypt=False):
|
||||
def __init__(self, hash = None, core = None, type = None, content = None, expire=None, decrypt=False, bypassReplayCheck=False):
|
||||
# take from arguments
|
||||
# sometimes people input a bytes object instead of str in `hash`
|
||||
if (not hash is None) and isinstance(hash, bytes):
|
||||
|
@ -37,6 +37,7 @@ class Block:
|
|||
self.btype = type
|
||||
self.bcontent = content
|
||||
self.expire = expire
|
||||
self.bypassReplayCheck = bypassReplayCheck
|
||||
|
||||
# initialize variables
|
||||
self.valid = True
|
||||
|
@ -84,6 +85,20 @@ class Block:
|
|||
self.signer = core._crypto.pubKeyDecrypt(self.signer, encodedData=encodedData)
|
||||
self.bheader['signer'] = self.signer.decode()
|
||||
self.signedData = json.dumps(self.bmetadata) + self.bcontent.decode()
|
||||
|
||||
# Check for replay attacks
|
||||
try:
|
||||
if self.core._utils.getEpoch() - self.core.getBlockDate(self.hash) < 60:
|
||||
assert self.core._crypto.replayTimestampValidation(self.bmetadata['rply'])
|
||||
except (AssertionError, KeyError) as e:
|
||||
if not self.bypassReplayCheck:
|
||||
# Zero out variables to prevent reading of replays
|
||||
self.bmetadata = {}
|
||||
self.signer = ''
|
||||
self.bheader['signer'] = ''
|
||||
self.signedData = ''
|
||||
self.signature = ''
|
||||
raise onionrexceptions.ReplayAttack('Signature is too old. possible replay attack')
|
||||
try:
|
||||
assert self.bmetadata['forwardEnc'] is True
|
||||
except (AssertionError, KeyError) as e:
|
||||
|
@ -97,6 +112,8 @@ class Block:
|
|||
except nacl.exceptions.CryptoError:
|
||||
pass
|
||||
#logger.debug('Could not decrypt block. Either invalid key or corrupted data')
|
||||
except onionrexceptions.ReplayAttack:
|
||||
logger.warn('%s is possibly a replay attack' % (self.hash,))
|
||||
else:
|
||||
retData = True
|
||||
self.decrypted = True
|
||||
|
@ -139,32 +156,10 @@ class Block:
|
|||
|
||||
# import from file
|
||||
if blockdata is None:
|
||||
blockdata = onionrstorage.getData(self.core, self.getHash()).decode()
|
||||
'''
|
||||
|
||||
filelocation = file
|
||||
|
||||
readfile = True
|
||||
|
||||
if filelocation is None:
|
||||
if self.getHash() is None:
|
||||
return False
|
||||
elif self.getHash() in Block.getCache():
|
||||
# get the block from cache, if it's in it
|
||||
blockdata = Block.getCache(self.getHash())
|
||||
readfile = False
|
||||
|
||||
# read from file if it's still None
|
||||
if blockdata is None:
|
||||
filelocation = self.core.dataDir + 'blocks/%s.dat' % self.getHash()
|
||||
|
||||
if readfile:
|
||||
try:
|
||||
blockdata = onionrstorage.getData(self.core, self.getHash()).decode()
|
||||
#with open(filelocation, 'rb') as f:
|
||||
#blockdata = f.read().decode()
|
||||
|
||||
self.blockFile = filelocation
|
||||
'''
|
||||
except AttributeError:
|
||||
raise onionrexceptions.NoDataAvailable('Block does not exist')
|
||||
else:
|
||||
self.blockFile = None
|
||||
# parse block
|
||||
|
@ -200,11 +195,11 @@ class Block:
|
|||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error('Failed to parse block %s.' % self.getHash(), error = e, timestamp = False)
|
||||
logger.warn('Failed to parse block %s.' % self.getHash(), error = e, timestamp = False)
|
||||
|
||||
# if block can't be parsed, it's a waste of precious space. Throw it away.
|
||||
if not self.delete():
|
||||
logger.error('Failed to delete invalid block %s.' % self.getHash(), error = e)
|
||||
logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
|
||||
else:
|
||||
logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)
|
||||
|
||||
|
|
171
onionr/onionrcommands/__init__.py
Normal file
171
onionr/onionrcommands/__init__.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This module defines commands for CLI usage
|
||||
'''
|
||||
'''
|
||||
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 webbrowser, sys
|
||||
import logger
|
||||
from . import pubkeymanager, onionrstatistics, daemonlaunch, filecommands, plugincommands, keyadders
|
||||
|
||||
def show_help(o_inst, command):
|
||||
|
||||
helpmenu = o_inst.getHelp()
|
||||
|
||||
if command is None and len(sys.argv) >= 3:
|
||||
for cmd in sys.argv[2:]:
|
||||
o_inst.showHelp(cmd)
|
||||
elif not command is None:
|
||||
if command.lower() in helpmenu:
|
||||
logger.info(logger.colors.bold + command + logger.colors.reset + logger.colors.fg.blue + ' : ' + logger.colors.reset + helpmenu[command.lower()], timestamp = False)
|
||||
else:
|
||||
logger.warn(logger.colors.bold + command + logger.colors.reset + logger.colors.fg.blue + ' : ' + logger.colors.reset + 'No help menu entry was found', timestamp = False)
|
||||
else:
|
||||
o_inst.version(0)
|
||||
for command, helpmessage in helpmenu.items():
|
||||
o_inst.showHelp(command)
|
||||
|
||||
def open_home(o_inst):
|
||||
try:
|
||||
url = o_inst.onionrUtils.getClientAPIServer()
|
||||
except FileNotFoundError:
|
||||
logger.error('Onionr seems to not be running (could not get api host)')
|
||||
else:
|
||||
url = 'http://%s/#%s' % (url, o_inst.onionrCore.config.get('client.webpassword'))
|
||||
print('If Onionr does not open automatically, use this URL:', url)
|
||||
webbrowser.open_new_tab(url)
|
||||
|
||||
def get_commands(onionr_inst):
|
||||
return {'': onionr_inst.showHelpSuggestion,
|
||||
'help': onionr_inst.showHelp,
|
||||
'version': onionr_inst.version,
|
||||
'config': onionr_inst.configure,
|
||||
'start': onionr_inst.start,
|
||||
'stop': onionr_inst.killDaemon,
|
||||
'status': onionr_inst.showStats,
|
||||
'statistics': onionr_inst.showStats,
|
||||
'stats': onionr_inst.showStats,
|
||||
'details' : onionr_inst.showDetails,
|
||||
'detail' : onionr_inst.showDetails,
|
||||
'show-details' : onionr_inst.showDetails,
|
||||
'show-detail' : onionr_inst.showDetails,
|
||||
'showdetails' : onionr_inst.showDetails,
|
||||
'showdetail' : onionr_inst.showDetails,
|
||||
'get-details' : onionr_inst.showDetails,
|
||||
'get-detail' : onionr_inst.showDetails,
|
||||
'getdetails' : onionr_inst.showDetails,
|
||||
'getdetail' : onionr_inst.showDetails,
|
||||
|
||||
'enable-plugin': onionr_inst.enablePlugin,
|
||||
'enplugin': onionr_inst.enablePlugin,
|
||||
'enableplugin': onionr_inst.enablePlugin,
|
||||
'enmod': onionr_inst.enablePlugin,
|
||||
'disable-plugin': onionr_inst.disablePlugin,
|
||||
'displugin': onionr_inst.disablePlugin,
|
||||
'disableplugin': onionr_inst.disablePlugin,
|
||||
'dismod': onionr_inst.disablePlugin,
|
||||
'reload-plugin': onionr_inst.reloadPlugin,
|
||||
'reloadplugin': onionr_inst.reloadPlugin,
|
||||
'reload-plugins': onionr_inst.reloadPlugin,
|
||||
'reloadplugins': onionr_inst.reloadPlugin,
|
||||
'create-plugin': onionr_inst.createPlugin,
|
||||
'createplugin': onionr_inst.createPlugin,
|
||||
'plugin-create': onionr_inst.createPlugin,
|
||||
|
||||
'listkeys': onionr_inst.listKeys,
|
||||
'list-keys': onionr_inst.listKeys,
|
||||
|
||||
'addpeer': onionr_inst.addPeer,
|
||||
'add-peer': onionr_inst.addPeer,
|
||||
'add-address': onionr_inst.addAddress,
|
||||
'add-addr': onionr_inst.addAddress,
|
||||
'addaddr': onionr_inst.addAddress,
|
||||
'addaddress': onionr_inst.addAddress,
|
||||
'list-peers': onionr_inst.listPeers,
|
||||
|
||||
'blacklist-block': onionr_inst.banBlock,
|
||||
|
||||
'add-file': onionr_inst.addFile,
|
||||
'addfile': onionr_inst.addFile,
|
||||
'addhtml': onionr_inst.addWebpage,
|
||||
'add-html': onionr_inst.addWebpage,
|
||||
'add-site': onionr_inst.addWebpage,
|
||||
'addsite': onionr_inst.addWebpage,
|
||||
|
||||
'openhome': onionr_inst.openHome,
|
||||
'open-home': onionr_inst.openHome,
|
||||
|
||||
'export-block': onionr_inst.exportBlock,
|
||||
'exportblock': onionr_inst.exportBlock,
|
||||
|
||||
'get-file': onionr_inst.getFile,
|
||||
'getfile': onionr_inst.getFile,
|
||||
|
||||
'listconn': onionr_inst.listConn,
|
||||
'list-conn': onionr_inst.listConn,
|
||||
|
||||
'import-blocks': onionr_inst.onionrUtils.importNewBlocks,
|
||||
'importblocks': onionr_inst.onionrUtils.importNewBlocks,
|
||||
|
||||
'introduce': onionr_inst.onionrCore.introduceNode,
|
||||
'pex': onionr_inst.doPEX,
|
||||
|
||||
'getpassword': onionr_inst.printWebPassword,
|
||||
'get-password': onionr_inst.printWebPassword,
|
||||
'getpwd': onionr_inst.printWebPassword,
|
||||
'get-pwd': onionr_inst.printWebPassword,
|
||||
'getpass': onionr_inst.printWebPassword,
|
||||
'get-pass': onionr_inst.printWebPassword,
|
||||
'getpasswd': onionr_inst.printWebPassword,
|
||||
'get-passwd': onionr_inst.printWebPassword,
|
||||
|
||||
'friend': onionr_inst.friendCmd,
|
||||
'addid': onionr_inst.addID,
|
||||
'add-id': onionr_inst.addID,
|
||||
'change-id': onionr_inst.changeID
|
||||
}
|
||||
|
||||
cmd_help = {
|
||||
'help': 'Displays this Onionr help menu',
|
||||
'version': 'Displays the Onionr version',
|
||||
'config': 'Configures something and adds it to the file',
|
||||
|
||||
'start': 'Starts the Onionr daemon',
|
||||
'stop': 'Stops the Onionr daemon',
|
||||
|
||||
'stats': 'Displays node statistics',
|
||||
'details': 'Displays the web password, public key, and human readable public key',
|
||||
|
||||
'enable-plugin': 'Enables and starts a plugin',
|
||||
'disable-plugin': 'Disables and stops a plugin',
|
||||
'reload-plugin': 'Reloads a plugin',
|
||||
'create-plugin': 'Creates directory structure for a plugin',
|
||||
|
||||
'add-peer': 'Adds a peer to database',
|
||||
'list-peers': 'Displays a list of peers',
|
||||
'add-file': 'Create an Onionr block from a file',
|
||||
'get-file': 'Get a file from Onionr blocks',
|
||||
'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)',
|
||||
'listconn': 'list connected peers',
|
||||
'pex': 'exchange addresses with peers (done automatically)',
|
||||
'blacklist-block': 'deletes a block by hash and permanently removes it from your node',
|
||||
'introduce': 'Introduce your node to the public Onionr network',
|
||||
'friend': '[add|remove] [public key/id]',
|
||||
'add-id': 'Generate a new ID (key pair)',
|
||||
'change-id': 'Change active ID',
|
||||
'open-home': 'Open your node\'s home/info screen'
|
||||
}
|
142
onionr/onionrcommands/daemonlaunch.py
Normal file
142
onionr/onionrcommands/daemonlaunch.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
launch the api server and communicator
|
||||
'''
|
||||
'''
|
||||
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 os, time, sys, platform, sqlite3
|
||||
from threading import Thread
|
||||
import onionr, api, logger, communicator
|
||||
import onionrevents as events
|
||||
from netcontroller import NetController
|
||||
def daemon(o_inst):
|
||||
'''
|
||||
Starts the Onionr communication daemon
|
||||
'''
|
||||
|
||||
# remove runcheck if it exists
|
||||
if os.path.isfile('data/.runcheck'):
|
||||
logger.debug('Runcheck file found on daemon start, deleting in advance.')
|
||||
os.remove('data/.runcheck')
|
||||
|
||||
Thread(target=api.API, args=(o_inst, o_inst.debug, onionr.API_VERSION)).start()
|
||||
Thread(target=api.PublicAPI, args=[o_inst.getClientApi()]).start()
|
||||
try:
|
||||
time.sleep(0)
|
||||
except KeyboardInterrupt:
|
||||
logger.debug('Got keyboard interrupt, shutting down...')
|
||||
time.sleep(1)
|
||||
o_inst.onionrUtils.localCommand('shutdown')
|
||||
|
||||
apiHost = ''
|
||||
while apiHost == '':
|
||||
try:
|
||||
with open(o_inst.onionrCore.publicApiHostFile, 'r') as hostFile:
|
||||
apiHost = hostFile.read()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
time.sleep(0.5)
|
||||
onionr.Onionr.setupConfig('data/', self = o_inst)
|
||||
|
||||
if o_inst._developmentMode:
|
||||
logger.warn('DEVELOPMENT MODE ENABLED (NOT RECOMMENDED)', timestamp = False)
|
||||
net = NetController(o_inst.onionrCore.config.get('client.public.port', 59497), apiServerIP=apiHost)
|
||||
logger.debug('Tor is starting...')
|
||||
if not net.startTor():
|
||||
o_inst.onionrUtils.localCommand('shutdown')
|
||||
sys.exit(1)
|
||||
if len(net.myID) > 0 and o_inst.onionrCore.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 + o_inst.onionrCore._crypto.pubKey))
|
||||
time.sleep(1)
|
||||
|
||||
o_inst.onionrCore.torPort = net.socksPort
|
||||
communicatorThread = Thread(target=communicator.startCommunicator, args=(o_inst, str(net.socksPort)))
|
||||
communicatorThread.start()
|
||||
|
||||
while o_inst.communicatorInst is None:
|
||||
time.sleep(0.1)
|
||||
|
||||
# print nice header thing :)
|
||||
if o_inst.onionrCore.config.get('general.display_header', True):
|
||||
o_inst.header()
|
||||
|
||||
# print out debug info
|
||||
o_inst.version(verbosity = 5, function = logger.debug)
|
||||
logger.debug('Python version %s' % platform.python_version())
|
||||
|
||||
logger.debug('Started communicator.')
|
||||
|
||||
events.event('daemon_start', onionr = o_inst)
|
||||
try:
|
||||
while True:
|
||||
time.sleep(3)
|
||||
# Debug to print out used FDs (regular and net)
|
||||
#proc = psutil.Process()
|
||||
#print('api-files:',proc.open_files(), len(psutil.net_connections()))
|
||||
# Break if communicator process ends, so we don't have left over processes
|
||||
if o_inst.communicatorInst.shutdown:
|
||||
break
|
||||
if o_inst.killed:
|
||||
break # Break out if sigterm for clean exit
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
o_inst.onionrCore.daemonQueueAdd('shutdown')
|
||||
o_inst.onionrUtils.localCommand('shutdown')
|
||||
net.killTor()
|
||||
time.sleep(3)
|
||||
o_inst.deleteRunFiles()
|
||||
return
|
||||
|
||||
def kill_daemon(o_inst):
|
||||
'''
|
||||
Shutdown the Onionr daemon
|
||||
'''
|
||||
|
||||
logger.warn('Stopping the running daemon...', timestamp = False)
|
||||
try:
|
||||
events.event('daemon_stop', onionr = o_inst)
|
||||
net = NetController(o_inst.onionrCore.config.get('client.port', 59496))
|
||||
try:
|
||||
o_inst.onionrCore.daemonQueueAdd('shutdown')
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
|
||||
net.killTor()
|
||||
except Exception as e:
|
||||
logger.error('Failed to shutdown daemon.', error = e, timestamp = False)
|
||||
return
|
||||
|
||||
def start(o_inst, input = False, override = False):
|
||||
if os.path.exists('.onionr-lock') and not override:
|
||||
logger.fatal('Cannot start. Daemon is already running, or it did not exit cleanly.\n(if you are sure that there is not a daemon running, delete .onionr-lock & try again).')
|
||||
else:
|
||||
if not o_inst.debug and not o_inst._developmentMode:
|
||||
lockFile = open('.onionr-lock', 'w')
|
||||
lockFile.write('')
|
||||
lockFile.close()
|
||||
o_inst.running = True
|
||||
o_inst.daemon()
|
||||
o_inst.running = False
|
||||
if not o_inst.debug and not o_inst._developmentMode:
|
||||
try:
|
||||
os.remove('.onionr-lock')
|
||||
except FileNotFoundError:
|
||||
pass
|
49
onionr/onionrcommands/filecommands.py
Normal file
49
onionr/onionrcommands/filecommands.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
import base64, sys, os
|
||||
import logger
|
||||
from onionrblockapi import Block
|
||||
def add_file(o_inst, singleBlock=False, blockType='bin'):
|
||||
'''
|
||||
Adds a file to the onionr network
|
||||
'''
|
||||
|
||||
if len(sys.argv) >= 3:
|
||||
filename = sys.argv[2]
|
||||
contents = None
|
||||
|
||||
if not os.path.exists(filename):
|
||||
logger.error('That file does not exist. Improper path (specify full path)?')
|
||||
return
|
||||
logger.info('Adding file... this might take a long time.')
|
||||
try:
|
||||
with open(filename, 'rb') as singleFile:
|
||||
blockhash = o_inst.onionrCore.insertBlock(base64.b64encode(singleFile.read()), header=blockType)
|
||||
if len(blockhash) > 0:
|
||||
logger.info('File %s saved in block %s' % (filename, blockhash))
|
||||
except:
|
||||
logger.error('Failed to save file in block.', timestamp = False)
|
||||
else:
|
||||
logger.error('%s add-file <filename>' % sys.argv[0], timestamp = False)
|
||||
|
||||
def getFile(o_inst):
|
||||
'''
|
||||
Get a file from onionr blocks
|
||||
'''
|
||||
try:
|
||||
fileName = sys.argv[2]
|
||||
bHash = sys.argv[3]
|
||||
except IndexError:
|
||||
logger.error("Syntax %s %s" % (sys.argv[0], '/path/to/filename <blockhash>'))
|
||||
else:
|
||||
logger.info(fileName)
|
||||
|
||||
contents = None
|
||||
if os.path.exists(fileName):
|
||||
logger.error("File already exists")
|
||||
return
|
||||
if not o_inst.onionrUtils.validateHash(bHash):
|
||||
logger.error('Block hash is invalid')
|
||||
return
|
||||
|
||||
with open(fileName, 'wb') as myFile:
|
||||
myFile.write(base64.b64decode(Block(bHash, core=o_inst.onionrCore).bcontent))
|
||||
return
|
49
onionr/onionrcommands/keyadders.py
Normal file
49
onionr/onionrcommands/keyadders.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
add keys (transport and pubkey)
|
||||
'''
|
||||
'''
|
||||
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 sys
|
||||
import logger
|
||||
def add_peer(o_inst):
|
||||
try:
|
||||
newPeer = sys.argv[2]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if o_inst.onionrUtils.hasKey(newPeer):
|
||||
logger.info('We already have that key')
|
||||
return
|
||||
logger.info("Adding peer: " + logger.colors.underline + newPeer)
|
||||
try:
|
||||
if o_inst.onionrCore.addPeer(newPeer):
|
||||
logger.info('Successfully added key')
|
||||
except AssertionError:
|
||||
logger.error('Failed to add key')
|
||||
|
||||
def add_address(o_inst):
|
||||
try:
|
||||
newAddress = sys.argv[2]
|
||||
newAddress = newAddress.replace('http:', '').replace('/', '')
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
logger.info("Adding address: " + logger.colors.underline + newAddress)
|
||||
if o_inst.onionrCore.addAddress(newAddress):
|
||||
logger.info("Successfully added address.")
|
||||
else:
|
||||
logger.warn("Unable to add address.")
|
110
onionr/onionrcommands/onionrstatistics.py
Normal file
110
onionr/onionrcommands/onionrstatistics.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This module defines commands to show stats/details about the local node
|
||||
'''
|
||||
'''
|
||||
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 os, uuid, time
|
||||
import logger, onionrutils
|
||||
from onionrblockapi import Block
|
||||
import onionr
|
||||
|
||||
def show_stats(o_inst):
|
||||
try:
|
||||
# define stats messages here
|
||||
totalBlocks = len(o_inst.onionrCore.getBlockList())
|
||||
signedBlocks = len(Block.getBlocks(signed = True))
|
||||
messages = {
|
||||
# info about local client
|
||||
'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if o_inst.onionrUtils.isCommunicatorRunning(timeout = 9) else logger.colors.fg.red + 'Offline'),
|
||||
|
||||
# file and folder size stats
|
||||
'div1' : True, # this creates a solid line across the screen, a div
|
||||
'Total Block Size' : onionrutils.humanSize(onionrutils.size(o_inst.dataDir + 'blocks/')),
|
||||
'Total Plugin Size' : onionrutils.humanSize(onionrutils.size(o_inst.dataDir + 'plugins/')),
|
||||
'Log File Size' : onionrutils.humanSize(onionrutils.size(o_inst.dataDir + 'output.log')),
|
||||
|
||||
# count stats
|
||||
'div2' : True,
|
||||
'Known Peers' : str(len(o_inst.onionrCore.listPeers()) - 1),
|
||||
'Enabled Plugins' : str(len(o_inst.onionrCore.config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(o_inst.dataDir + 'plugins/'))),
|
||||
'Stored Blocks' : str(totalBlocks),
|
||||
'Percent Blocks Signed' : str(round(100 * signedBlocks / max(totalBlocks, 1), 2)) + '%'
|
||||
}
|
||||
|
||||
# color configuration
|
||||
colors = {
|
||||
'title' : logger.colors.bold,
|
||||
'key' : logger.colors.fg.lightgreen,
|
||||
'val' : logger.colors.fg.green,
|
||||
'border' : logger.colors.fg.lightblue,
|
||||
|
||||
'reset' : logger.colors.reset
|
||||
}
|
||||
|
||||
# pre-processing
|
||||
maxlength = 0
|
||||
width = o_inst.getConsoleWidth()
|
||||
for key, val in messages.items():
|
||||
if not (type(val) is bool and val is True):
|
||||
maxlength = max(len(key), maxlength)
|
||||
prewidth = maxlength + len(' | ')
|
||||
groupsize = width - prewidth - len('[+] ')
|
||||
|
||||
# generate stats table
|
||||
logger.info(colors['title'] + 'Onionr v%s Statistics' % onionr.ONIONR_VERSION + colors['reset'])
|
||||
logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'])
|
||||
for key, val in messages.items():
|
||||
if not (type(val) is bool and val is True):
|
||||
val = [str(val)[i:i + groupsize] for i in range(0, len(str(val)), groupsize)]
|
||||
|
||||
logger.info(colors['key'] + str(key).rjust(maxlength) + colors['reset'] + colors['border'] + ' | ' + colors['reset'] + colors['val'] + str(val.pop(0)) + colors['reset'])
|
||||
|
||||
for value in val:
|
||||
logger.info(' ' * maxlength + colors['border'] + ' | ' + colors['reset'] + colors['val'] + str(value) + colors['reset'])
|
||||
else:
|
||||
logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'])
|
||||
logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'])
|
||||
except Exception as e:
|
||||
logger.error('Failed to generate statistics table.', error = e, timestamp = False)
|
||||
|
||||
def show_details(o_inst):
|
||||
details = {
|
||||
'Node Address' : o_inst.get_hostname(),
|
||||
'Web Password' : o_inst.getWebPassword(),
|
||||
'Public Key' : o_inst.onionrCore._crypto.pubKey,
|
||||
'Human-readable Public Key' : o_inst.onionrCore._utils.getHumanReadableID()
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
def show_peers(o_inst):
|
||||
randID = str(uuid.uuid4())
|
||||
o_inst.onionrCore.daemonQueueAdd('connectedPeers', responseID=randID)
|
||||
while True:
|
||||
try:
|
||||
time.sleep(3)
|
||||
peers = o_inst.onionrCore.daemonQueueGetResponse(randID)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
if not type(peers) is None:
|
||||
if peers not in ('', 'failure', None):
|
||||
if peers != False:
|
||||
print(peers)
|
||||
else:
|
||||
print('Daemon probably not running. Unable to list connected peers.')
|
||||
break
|
88
onionr/onionrcommands/plugincommands.py
Normal file
88
onionr/onionrcommands/plugincommands.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
plugin CLI commands
|
||||
'''
|
||||
'''
|
||||
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 sys
|
||||
import logger, onionrplugins as plugins
|
||||
|
||||
def enable_plugin(o_inst):
|
||||
if len(sys.argv) >= 3:
|
||||
plugin_name = sys.argv[2]
|
||||
logger.info('Enabling plugin "%s"...' % plugin_name)
|
||||
plugins.enable(plugin_name, o_inst)
|
||||
else:
|
||||
logger.info('%s %s <plugin>' % (sys.argv[0], sys.argv[1]))
|
||||
|
||||
def disable_plugin(o_inst):
|
||||
|
||||
if len(sys.argv) >= 3:
|
||||
plugin_name = sys.argv[2]
|
||||
logger.info('Disabling plugin "%s"...' % plugin_name)
|
||||
plugins.disable(plugin_name, o_inst)
|
||||
else:
|
||||
logger.info('%s %s <plugin>' % (sys.argv[0], sys.argv[1]))
|
||||
|
||||
def reload_plugin(o_inst):
|
||||
'''
|
||||
Reloads (stops and starts) all plugins, or the given plugin
|
||||
'''
|
||||
|
||||
if len(sys.argv) >= 3:
|
||||
plugin_name = sys.argv[2]
|
||||
logger.info('Reloading plugin "%s"...' % plugin_name)
|
||||
plugins.stop(plugin_name, o_inst)
|
||||
plugins.start(plugin_name, o_inst)
|
||||
else:
|
||||
logger.info('Reloading all plugins...')
|
||||
plugins.reload(o_inst)
|
||||
|
||||
|
||||
def create_plugin(o_inst):
|
||||
'''
|
||||
Creates the directory structure for a plugin name
|
||||
'''
|
||||
|
||||
if len(sys.argv) >= 3:
|
||||
try:
|
||||
plugin_name = re.sub('[^0-9a-zA-Z_]+', '', str(sys.argv[2]).lower())
|
||||
|
||||
if not plugins.exists(plugin_name):
|
||||
logger.info('Creating plugin "%s"...' % plugin_name)
|
||||
|
||||
os.makedirs(plugins.get_plugins_folder(plugin_name))
|
||||
with open(plugins.get_plugins_folder(plugin_name) + '/main.py', 'a') as main:
|
||||
contents = ''
|
||||
with open('static-data/default_plugin.py', 'rb') as file:
|
||||
contents = file.read().decode()
|
||||
|
||||
# TODO: Fix $user. os.getlogin() is B U G G Y
|
||||
main.write(contents.replace('$user', 'some random developer').replace('$date', datetime.datetime.now().strftime('%Y-%m-%d')).replace('$name', plugin_name))
|
||||
|
||||
with open(plugins.get_plugins_folder(plugin_name) + '/info.json', 'a') as main:
|
||||
main.write(json.dumps({'author' : 'anonymous', 'description' : 'the default description of the plugin', 'version' : '1.0'}))
|
||||
|
||||
logger.info('Enabling plugin "%s"...' % plugin_name)
|
||||
plugins.enable(plugin_name, o_inst)
|
||||
else:
|
||||
logger.warn('Cannot create plugin directory structure; plugin "%s" exists.' % plugin_name)
|
||||
|
||||
except Exception as e:
|
||||
logger.error('Failed to create plugin directory structure.', e)
|
||||
else:
|
||||
logger.info('%s %s <plugin>' % (sys.argv[0], sys.argv[1]))
|
101
onionr/onionrcommands/pubkeymanager.py
Normal file
101
onionr/onionrcommands/pubkeymanager.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This module defines ID-related CLI commands
|
||||
'''
|
||||
'''
|
||||
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 sys
|
||||
import logger
|
||||
from onionrusers import onionrusers
|
||||
def add_ID(o_inst):
|
||||
try:
|
||||
sys.argv[2]
|
||||
assert sys.argv[2] == 'true'
|
||||
except (IndexError, AssertionError) as e:
|
||||
newID = o_inst.onionrCore._crypto.keyManager.addKey()[0]
|
||||
else:
|
||||
logger.warn('Deterministic keys require random and long passphrases.')
|
||||
logger.warn('If a good passphrase is not used, your key can be easily stolen.')
|
||||
logger.warn('You should use a series of hard to guess words, see this for reference: https://www.xkcd.com/936/')
|
||||
pass1 = getpass.getpass(prompt='Enter at least %s characters: ' % (o_inst.onionrCore._crypto.deterministicRequirement,))
|
||||
pass2 = getpass.getpass(prompt='Confirm entry: ')
|
||||
if o_inst.onionrCore._crypto.safeCompare(pass1, pass2):
|
||||
try:
|
||||
logger.info('Generating deterministic key. This can take a while.')
|
||||
newID, privKey = o_inst.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)
|
||||
o_inst.onionrCore._crypto.keyManager.addKey(pubKey=newID,
|
||||
privKey=privKey)
|
||||
logger.info('Added ID: %s' % (o_inst.onionrUtils.bytesToStr(newID),))
|
||||
|
||||
def change_ID(o_inst):
|
||||
try:
|
||||
key = sys.argv[2]
|
||||
except IndexError:
|
||||
logger.error('Specify pubkey to use')
|
||||
else:
|
||||
if o_inst.onionrUtils.validatePubKey(key):
|
||||
if key in o_inst.onionrCore._crypto.keyManager.getPubkeyList():
|
||||
o_inst.onionrCore.config.set('general.public_key', key)
|
||||
o_inst.onionrCore.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 friend_command(o_inst):
|
||||
friend = ''
|
||||
try:
|
||||
# Get the friend command
|
||||
action = sys.argv[2]
|
||||
except IndexError:
|
||||
logger.info('Syntax: friend add/remove/list [address]')
|
||||
else:
|
||||
action = action.lower()
|
||||
if action == 'list':
|
||||
# List out peers marked as our friend
|
||||
for friend in onionrusers.OnionrUser.list_friends(o_inst.onionrCore):
|
||||
logger.info(friend.publicKey + ' - ' + friend.getName())
|
||||
elif action in ('add', 'remove'):
|
||||
try:
|
||||
friend = sys.argv[3]
|
||||
if not o_inst.onionrUtils.validatePubKey(friend):
|
||||
raise onionrexceptions.InvalidPubkey('Public key is invalid')
|
||||
if friend not in o_inst.onionrCore.listPeers():
|
||||
raise onionrexceptions.KeyNotKnown
|
||||
friend = onionrusers.OnionrUser(o_inst.onionrCore, friend)
|
||||
except IndexError:
|
||||
logger.error('Friend ID is required.')
|
||||
except onionrexceptions.KeyNotKnown:
|
||||
o_inst.onionrCore.addPeer(friend)
|
||||
friend = onionrusers.OnionrUser(o_inst.onionrCore, friend)
|
||||
finally:
|
||||
if action == 'add':
|
||||
friend.setTrust(1)
|
||||
logger.info('Added %s as friend.' % (friend.publicKey,))
|
||||
else:
|
||||
friend.setTrust(0)
|
||||
logger.info('Removed %s as friend.' % (friend.publicKey,))
|
||||
else:
|
||||
logger.info('Syntax: friend add/remove/list [address]')
|
|
@ -264,6 +264,13 @@ class OnionrCrypto:
|
|||
|
||||
return retData
|
||||
|
||||
@staticmethod
|
||||
def replayTimestampValidation(timestamp):
|
||||
if core.Core()._utils.getEpoch() - int(timestamp) > 2419200:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def safeCompare(one, two):
|
||||
# Do encode here to avoid spawning core
|
||||
|
|
|
@ -67,7 +67,6 @@ def call(plugin, event_name, data = None, pluginapi = None):
|
|||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(str(e))
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
|
|
@ -45,6 +45,9 @@ class PasswordStrengthError(Exception):
|
|||
|
||||
# block exceptions
|
||||
|
||||
class ReplayAttack(Exception):
|
||||
pass
|
||||
|
||||
class DifficultyTooLarge(Exception):
|
||||
pass
|
||||
|
||||
|
|
0
onionr/onionrfragment.py → onionr/onionrfragment/__init__.py
Normal file → Executable file
0
onionr/onionrfragment.py → onionr/onionrfragment/__init__.py
Normal file → Executable file
|
@ -59,7 +59,6 @@ def reload(onionr = None, stop_event = True):
|
|||
|
||||
return False
|
||||
|
||||
|
||||
def enable(name, onionr = None, start_event = True):
|
||||
'''
|
||||
Enables a plugin
|
||||
|
@ -73,6 +72,8 @@ def enable(name, onionr = None, start_event = True):
|
|||
try:
|
||||
events.call(get_plugin(name), 'enable', onionr)
|
||||
except ImportError: # Was getting import error on Gitlab CI test "data"
|
||||
# NOTE: If you are experiencing issues with plugins not being enabled, it might be this resulting from an error in the module
|
||||
# can happen inconsistenly (especially between versions)
|
||||
return False
|
||||
else:
|
||||
enabled_plugins.append(name)
|
||||
|
|
|
@ -17,10 +17,8 @@
|
|||
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 nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, logger, sys, base64, json
|
||||
import core, onionrutils, config
|
||||
import onionrblockapi
|
||||
import multiprocessing, nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, sys, base64, json
|
||||
import core, onionrutils, config, logger, onionrblockapi
|
||||
|
||||
def getDifficultyModifier(coreOrUtilsInst=None):
|
||||
'''Accepts a core or utils instance returns
|
||||
|
@ -101,7 +99,7 @@ def hashMeetsDifficulty(h):
|
|||
return False
|
||||
|
||||
class DataPOW:
|
||||
def __init__(self, data, forceDifficulty=0, threadCount = 5):
|
||||
def __init__(self, data, forceDifficulty=0, threadCount = 1):
|
||||
self.foundHash = False
|
||||
self.difficulty = 0
|
||||
self.data = data
|
||||
|
@ -200,7 +198,7 @@ class DataPOW:
|
|||
return result
|
||||
|
||||
class POW:
|
||||
def __init__(self, metadata, data, threadCount = 5, forceDifficulty=0, coreInst=None):
|
||||
def __init__(self, metadata, data, threadCount = 1, forceDifficulty=0, coreInst=None):
|
||||
self.foundHash = False
|
||||
self.difficulty = 0
|
||||
self.data = data
|
||||
|
@ -246,6 +244,7 @@ class POW:
|
|||
answer = ''
|
||||
hbCount = 0
|
||||
nonce = int(binascii.hexlify(nacl.utils.random(2)), 16)
|
||||
startNonce = nonce
|
||||
while self.hashing:
|
||||
#token = nacl.hash.blake2b(rand + self.data).decode()
|
||||
self.metadata['powRandomToken'] = nonce
|
||||
|
@ -260,6 +259,7 @@ class POW:
|
|||
self.hashing = False
|
||||
iFound = True
|
||||
self.result = payload
|
||||
print('count', nonce - startNonce)
|
||||
break
|
||||
nonce += 1
|
||||
|
||||
|
|
|
@ -21,13 +21,6 @@ import core, sys, sqlite3, os, dbcreator
|
|||
|
||||
DB_ENTRY_SIZE_LIMIT = 10000 # Will be a config option
|
||||
|
||||
class BlockCache:
|
||||
def __init__(self):
|
||||
self.blocks = {}
|
||||
def cleanCache(self):
|
||||
while sys.getsizeof(self.blocks) > 100000000:
|
||||
self.blocks.pop(list(self.blocks.keys())[0])
|
||||
|
||||
def dbCreate(coreInst):
|
||||
try:
|
||||
dbcreator.DBCreator(coreInst).createBlockDataDB()
|
||||
|
@ -84,7 +77,6 @@ def store(coreInst, data, blockHash=''):
|
|||
else:
|
||||
with open('%s/%s.dat' % (coreInst.blockDataLocation, blockHash), 'wb') as blockFile:
|
||||
blockFile.write(data)
|
||||
coreInst.blockCache.cleanCache()
|
||||
|
||||
def getData(coreInst, bHash):
|
||||
assert isinstance(coreInst, core.Core)
|
||||
|
|
0
onionr/onionrusers/contactmanager.py
Normal file → Executable file
0
onionr/onionrusers/contactmanager.py
Normal file → Executable file
|
@ -17,7 +17,7 @@
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import onionrblockapi, logger, onionrexceptions, json, sqlite3
|
||||
import onionrblockapi, logger, onionrexceptions, json, sqlite3, time
|
||||
import nacl.exceptions
|
||||
|
||||
def deleteExpiredKeys(coreInst):
|
||||
|
@ -76,11 +76,11 @@ class OnionrUser:
|
|||
return retData
|
||||
|
||||
def encrypt(self, data):
|
||||
encrypted = coreInst._crypto.pubKeyEncrypt(data, self.publicKey, encodedData=True)
|
||||
encrypted = self._core._crypto.pubKeyEncrypt(data, self.publicKey, encodedData=True)
|
||||
return encrypted
|
||||
|
||||
def decrypt(self, data):
|
||||
decrypted = coreInst._crypto.pubKeyDecrypt(data, self.publicKey, encodedData=True)
|
||||
decrypted = self._core._crypto.pubKeyDecrypt(data, self.publicKey, encodedData=True)
|
||||
return decrypted
|
||||
|
||||
def forwardEncrypt(self, data):
|
||||
|
@ -112,7 +112,8 @@ class OnionrUser:
|
|||
conn = sqlite3.connect(self._core.peerDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
|
||||
for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ? ORDER BY date DESC", (self.publicKey,)):
|
||||
# TODO: account for keys created at the same time (same epoch)
|
||||
for row in c.execute("SELECT forwardKey, max(DATE) FROM forwardKeys WHERE peerKey = ?", (self.publicKey,)):
|
||||
key = row[0]
|
||||
break
|
||||
|
||||
|
@ -126,9 +127,8 @@ class OnionrUser:
|
|||
c = conn.cursor()
|
||||
keyList = []
|
||||
|
||||
for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ? ORDER BY date DESC", (self.publicKey,)):
|
||||
key = row[0]
|
||||
keyList.append(key)
|
||||
for row in c.execute("SELECT forwardKey, date FROM forwardKeys WHERE peerKey = ? ORDER BY date DESC", (self.publicKey,)):
|
||||
keyList.append((row[0], row[1]))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
@ -175,32 +175,36 @@ class OnionrUser:
|
|||
|
||||
def addForwardKey(self, newKey, expire=604800):
|
||||
if not self._core._utils.validatePubKey(newKey):
|
||||
# Do not add if something went wrong with the key
|
||||
raise onionrexceptions.InvalidPubkey(newKey)
|
||||
if newKey in self._getForwardKeys():
|
||||
return False
|
||||
# Add a forward secrecy key for the peer
|
||||
|
||||
conn = sqlite3.connect(self._core.peerDB, timeout=10)
|
||||
c = conn.cursor()
|
||||
|
||||
# Get the time we're inserting the key at
|
||||
timeInsert = self._core._utils.getEpoch()
|
||||
|
||||
# Look at our current keys for duplicate key data or time
|
||||
for entry in self._getForwardKeys():
|
||||
if entry[0] == newKey:
|
||||
return False
|
||||
if entry[1] == timeInsert:
|
||||
timeInsert += 1
|
||||
time.sleep(1) # Sleep if our time is the same in order to prevent duplicate time records
|
||||
|
||||
# Add a forward secrecy key for the peer
|
||||
# Prepare the insert
|
||||
time = self._core._utils.getEpoch()
|
||||
command = (self.publicKey, newKey, time, time + expire)
|
||||
command = (self.publicKey, newKey, timeInsert, timeInsert + expire)
|
||||
|
||||
c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?, ?);", command)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def findAndSetID(self):
|
||||
'''Find any info about the user from existing blocks and cache it to their DB entry'''
|
||||
infoBlocks = []
|
||||
for bHash in self._core.getBlocksByType('userInfo'):
|
||||
block = onionrblockapi.Block(bHash, core=self._core)
|
||||
if block.signer == self.publicKey:
|
||||
if block.verifySig():
|
||||
newName = block.getMetadata('name')
|
||||
if newName.isalnum():
|
||||
logger.info('%s is now using the name %s.' % (self.publicKey, self._core._utils.escapeAnsi(newName)))
|
||||
self._core.setPeerInfo(self.publicKey, 'name', newName)
|
||||
else:
|
||||
raise onionrexceptions.InvalidPubkey
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def list_friends(cls, coreInst):
|
||||
friendList = []
|
||||
for x in coreInst.listPeers(trust=1):
|
||||
friendList.append(cls(coreInst, x))
|
||||
return list(friendList)
|
|
@ -278,12 +278,19 @@ class OnionrUtils:
|
|||
break
|
||||
if (self.getEpoch() - metadata[i]) > maxAge:
|
||||
logger.warn('Block is outdated: %s' % (metadata[i],))
|
||||
break
|
||||
elif i == 'expire':
|
||||
try:
|
||||
assert int(metadata[i]) > self.getEpoch()
|
||||
except AssertionError:
|
||||
logger.warn('Block is expired')
|
||||
break
|
||||
elif i == 'encryptType':
|
||||
try:
|
||||
assert metadata[i] in ('asym', 'sym', '')
|
||||
except AssertionError:
|
||||
logger.warn('Invalid encryption mode')
|
||||
break
|
||||
else:
|
||||
# if metadata loop gets no errors, it does not break, therefore metadata is valid
|
||||
# make sure we do not have another block with the same data content (prevent data duplication and replay attacks)
|
||||
|
@ -408,12 +415,14 @@ class OnionrUtils:
|
|||
This function is intended to scan for new blocks ON THE DISK and import them
|
||||
'''
|
||||
blockList = self._core.getBlockList()
|
||||
exist = False
|
||||
if scanDir == '':
|
||||
scanDir = self._core.blockDataLocation
|
||||
if not scanDir.endswith('/'):
|
||||
scanDir += '/'
|
||||
for block in glob.glob(scanDir + "*.dat"):
|
||||
if block.replace(scanDir, '').replace('.dat', '') not in blockList:
|
||||
exist = True
|
||||
logger.info('Found new block on dist %s' % block)
|
||||
with open(block, 'rb') as newBlock:
|
||||
block = block.replace(scanDir, '').replace('.dat', '')
|
||||
|
@ -423,6 +432,8 @@ class OnionrUtils:
|
|||
self._core._utils.processBlockMetadata(block)
|
||||
else:
|
||||
logger.warn('Failed to verify hash for %s' % block)
|
||||
if not exist:
|
||||
print('No blocks found to import')
|
||||
|
||||
def progressBar(self, value = 0, endvalue = 100, width = None):
|
||||
'''
|
||||
|
@ -469,7 +480,7 @@ class OnionrUtils:
|
|||
retData = False
|
||||
return retData
|
||||
|
||||
def doGetRequest(self, url, port=0, proxyType='tor', ignoreAPI=False):
|
||||
def doGetRequest(self, url, port=0, proxyType='tor', ignoreAPI=False, returnHeaders=False):
|
||||
'''
|
||||
Do a get request through a local tor or i2p instance
|
||||
'''
|
||||
|
@ -509,7 +520,10 @@ class OnionrUtils:
|
|||
if not 'ConnectTimeoutError' in str(e) and not 'Request rejected or failed' in str(e):
|
||||
logger.debug('Error: %s' % str(e))
|
||||
retData = False
|
||||
return retData
|
||||
if returnHeaders:
|
||||
return (retData, response_headers)
|
||||
else:
|
||||
return retData
|
||||
|
||||
def strToBytes(self, data):
|
||||
try:
|
||||
|
|
69
onionr/setupconfig.py
Normal file
69
onionr/setupconfig.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
import os, json
|
||||
import config, logger
|
||||
|
||||
def setup_config(dataDir, o_inst = None):
|
||||
data_exists = os.path.exists(dataDir)
|
||||
|
||||
if not data_exists:
|
||||
os.mkdir(dataDir)
|
||||
|
||||
if os.path.exists('static-data/default_config.json'):
|
||||
# this is the default config, it will be overwritten if a config file already exists. Else, it saves it
|
||||
with open('static-data/default_config.json', 'r') as configReadIn:
|
||||
config.set_config(json.loads(configReadIn.read()))
|
||||
else:
|
||||
# the default config file doesn't exist, try hardcoded config
|
||||
logger.warn('Default configuration file does not exist, switching to hardcoded fallback configuration!')
|
||||
config.set_config({'dev_mode': True, 'log': {'file': {'output': True, 'path': dataDir + 'output.log'}, 'console': {'output': True, 'color': True}}})
|
||||
if not data_exists:
|
||||
config.save()
|
||||
config.reload() # this will read the configuration file into memory
|
||||
|
||||
settings = 0b000
|
||||
if config.get('log.console.color', True):
|
||||
settings = settings | logger.USE_ANSI
|
||||
if config.get('log.console.output', True):
|
||||
settings = settings | logger.OUTPUT_TO_CONSOLE
|
||||
if config.get('log.file.output', True):
|
||||
settings = settings | logger.OUTPUT_TO_FILE
|
||||
logger.set_settings(settings)
|
||||
|
||||
if not o_inst is None:
|
||||
if str(config.get('general.dev_mode', True)).lower() == 'true':
|
||||
o_inst._developmentMode = True
|
||||
logger.set_level(logger.LEVEL_DEBUG)
|
||||
else:
|
||||
o_inst._developmentMode = False
|
||||
logger.set_level(logger.LEVEL_INFO)
|
||||
|
||||
verbosity = str(config.get('log.verbosity', 'default')).lower().strip()
|
||||
if not verbosity in ['default', 'null', 'none', 'nil']:
|
||||
map = {
|
||||
str(logger.LEVEL_DEBUG) : logger.LEVEL_DEBUG,
|
||||
'verbose' : logger.LEVEL_DEBUG,
|
||||
'debug' : logger.LEVEL_DEBUG,
|
||||
str(logger.LEVEL_INFO) : logger.LEVEL_INFO,
|
||||
'info' : logger.LEVEL_INFO,
|
||||
'information' : logger.LEVEL_INFO,
|
||||
str(logger.LEVEL_WARN) : logger.LEVEL_WARN,
|
||||
'warn' : logger.LEVEL_WARN,
|
||||
'warning' : logger.LEVEL_WARN,
|
||||
'warnings' : logger.LEVEL_WARN,
|
||||
str(logger.LEVEL_ERROR) : logger.LEVEL_ERROR,
|
||||
'err' : logger.LEVEL_ERROR,
|
||||
'error' : logger.LEVEL_ERROR,
|
||||
'errors' : logger.LEVEL_ERROR,
|
||||
str(logger.LEVEL_FATAL) : logger.LEVEL_FATAL,
|
||||
'fatal' : logger.LEVEL_FATAL,
|
||||
str(logger.LEVEL_IMPORTANT) : logger.LEVEL_IMPORTANT,
|
||||
'silent' : logger.LEVEL_IMPORTANT,
|
||||
'quiet' : logger.LEVEL_IMPORTANT,
|
||||
'important' : logger.LEVEL_IMPORTANT
|
||||
}
|
||||
|
||||
if verbosity in map:
|
||||
logger.set_level(map[verbosity])
|
||||
else:
|
||||
logger.warn('Verbosity level %s is not valid, using default verbosity.' % verbosity)
|
||||
|
||||
return data_exists
|
|
@ -1 +0,0 @@
|
|||
dd3llxdp5q6ak3zmmicoy3jnodmroouv2xr7whkygiwp3rl7nf23gdad.onion
|
|
@ -19,8 +19,10 @@
|
|||
'''
|
||||
|
||||
# Imports some useful libraries
|
||||
import logger, config, threading, time, uuid, subprocess, sys
|
||||
import threading, time, uuid, subprocess, sys
|
||||
import config, logger
|
||||
from onionrblockapi import Block
|
||||
import onionrplugins
|
||||
|
||||
plugin_name = 'cliui'
|
||||
PLUGIN_VERSION = '0.0.1'
|
||||
|
@ -29,7 +31,11 @@ class OnionrCLIUI:
|
|||
def __init__(self, apiInst):
|
||||
self.api = apiInst
|
||||
self.myCore = apiInst.get_core()
|
||||
return
|
||||
self.shutdown = False
|
||||
self.running = 'undetermined'
|
||||
enabled = onionrplugins.get_enabled_plugins()
|
||||
self.mail_enabled = 'pms' in enabled
|
||||
self.flow_enabled = 'flow' in enabled
|
||||
|
||||
def subCommand(self, command, args=None):
|
||||
try:
|
||||
|
@ -41,6 +47,14 @@ class OnionrCLIUI:
|
|||
subprocess.call(['./onionr.py', command])
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
def isRunning(self):
|
||||
while not self.shutdown:
|
||||
if self.myCore._utils.localCommand('ping', maxWait=5) == 'pong!':
|
||||
self.running = 'Yes'
|
||||
else:
|
||||
self.running = 'No'
|
||||
time.sleep(5)
|
||||
|
||||
def refresh(self):
|
||||
print('\n' * 80 + logger.colors.reset)
|
||||
|
@ -48,20 +62,13 @@ class OnionrCLIUI:
|
|||
def start(self):
|
||||
'''Main CLI UI interface menu'''
|
||||
showMenu = True
|
||||
isOnline = 'No'
|
||||
firstRun = True
|
||||
choice = ''
|
||||
if self.myCore._utils.localCommand('ping', maxWait=10) == 'pong!':
|
||||
firstRun = False
|
||||
threading.Thread(target=self.isRunning).start()
|
||||
|
||||
while showMenu:
|
||||
if self.myCore._utils.localCommand('ping', maxWait=2) == 'pong!':
|
||||
isOnline = "Yes"
|
||||
else:
|
||||
isOnline = "No"
|
||||
|
||||
print('''Daemon Running: ''' + isOnline + '''
|
||||
1. Flow (Anonymous public chat, use at your own risk)
|
||||
print('Onionr\n------')
|
||||
print('''Daemon Running: ''' + self.running + '''
|
||||
1. Flow (Anonymous public shout box, use at your own risk)
|
||||
2. Mail (Secure email-like service)
|
||||
3. File Sharing
|
||||
4. Quit (Does not shutdown daemon)
|
||||
|
@ -72,21 +79,27 @@ class OnionrCLIUI:
|
|||
choice = "quit"
|
||||
|
||||
if choice in ("flow", "1"):
|
||||
self.subCommand("flow")
|
||||
if self.flow_enabled:
|
||||
self.subCommand("flow")
|
||||
else:
|
||||
print('Plugin not enabled')
|
||||
elif choice in ("2", "mail"):
|
||||
self.subCommand("mail")
|
||||
if self.mail_enabled:
|
||||
self.subCommand("mail")
|
||||
else:
|
||||
print('Plugin not enabled')
|
||||
elif choice in ("3", "file sharing", "file"):
|
||||
filename = input("Enter full path to file: ").strip()
|
||||
self.subCommand("addfile", filename)
|
||||
elif choice in ("4", "quit"):
|
||||
showMenu = False
|
||||
self.shutdown = True
|
||||
elif choice == "":
|
||||
pass
|
||||
else:
|
||||
logger.error("Invalid choice")
|
||||
return
|
||||
|
||||
|
||||
def on_init(api, data = None):
|
||||
'''
|
||||
This event is called after Onionr is initialized, but before the command
|
||||
|
|
5
onionr/static-data/default-plugins/contactmanager/info.json
Executable file
5
onionr/static-data/default-plugins/contactmanager/info.json
Executable file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name" : "contactmanager",
|
||||
"version" : "1.0",
|
||||
"author" : "onionr"
|
||||
}
|
39
onionr/static-data/default-plugins/contactmanager/main.py
Executable file
39
onionr/static-data/default-plugins/contactmanager/main.py
Executable file
|
@ -0,0 +1,39 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This is an interactive menu-driven CLI interface for Onionr
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
plugin_name = 'contactmanager'
|
||||
|
||||
class OnionrContactManager:
|
||||
def __init__(self, api):
|
||||
return
|
||||
|
||||
def on_init(api, data = None):
|
||||
'''
|
||||
This event is called after Onionr is initialized, but before the command
|
||||
inputted is executed. Could be called when daemon is starting or when
|
||||
just the client is running.
|
||||
'''
|
||||
|
||||
# Doing this makes it so that the other functions can access the api object
|
||||
# by simply referencing the variable `pluginapi`.
|
||||
pluginapi = api
|
||||
ui = OnionrContactManager(api)
|
||||
#api.commands.register('interactive', ui.start)
|
||||
return
|
|
@ -19,7 +19,7 @@
|
|||
'''
|
||||
|
||||
# Imports some useful libraries
|
||||
import logger, config, threading, time, readline, datetime, sys, json
|
||||
import logger, config, threading, time, datetime, sys, json
|
||||
from onionrblockapi import Block
|
||||
import onionrexceptions, onionrusers
|
||||
import locale
|
||||
|
|
13
onionr/static-data/default-plugins/pms/loadinbox.py
Normal file
13
onionr/static-data/default-plugins/pms/loadinbox.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import onionrblockapi
|
||||
def load_inbox(myCore):
|
||||
inbox_list = []
|
||||
deleted = myCore.keyStore.get('deleted_mail')
|
||||
if deleted is None:
|
||||
deleted = []
|
||||
|
||||
for blockHash in myCore.getBlocksByType('pm'):
|
||||
block = onionrblockapi.Block(blockHash, core=myCore)
|
||||
block.decrypt()
|
||||
if block.decrypted and blockHash not in deleted:
|
||||
inbox_list.append(blockHash)
|
||||
return inbox_list
|
65
onionr/static-data/default-plugins/pms/mailapi.py
Normal file
65
onionr/static-data/default-plugins/pms/mailapi.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
'''
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
HTTP endpoints for mail plugin.
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import sys, os, json
|
||||
from flask import Response, request, redirect, Blueprint, abort
|
||||
import core
|
||||
from onionrusers import contactmanager
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
||||
import loadinbox, sentboxdb
|
||||
|
||||
flask_blueprint = Blueprint('mail', __name__)
|
||||
c = core.Core()
|
||||
kv = c.keyStore
|
||||
|
||||
@flask_blueprint.route('/mail/ping')
|
||||
def mail_ping():
|
||||
return 'pong!'
|
||||
|
||||
@flask_blueprint.route('/mail/deletemsg/<block>', methods=['POST'])
|
||||
def mail_delete(block):
|
||||
if not c._utils.validateHash(block):
|
||||
abort(504)
|
||||
existing = kv.get('deleted_mail')
|
||||
if existing is None:
|
||||
existing = []
|
||||
if block not in existing:
|
||||
existing.append(block)
|
||||
kv.put('deleted_mail', existing)
|
||||
return 'success'
|
||||
|
||||
@flask_blueprint.route('/mail/getinbox')
|
||||
def list_inbox():
|
||||
return ','.join(loadinbox.load_inbox(c))
|
||||
|
||||
@flask_blueprint.route('/mail/getsentbox')
|
||||
def list_sentbox():
|
||||
sentbox_list = sentboxdb.SentBox(c).listSent()
|
||||
sentbox_list_copy = list(sentbox_list)
|
||||
deleted = kv.get('deleted_mail')
|
||||
if deleted is None:
|
||||
deleted = []
|
||||
for x in range(len(sentbox_list_copy) - 1):
|
||||
if sentbox_list_copy[x]['hash'] in deleted:
|
||||
x -= 1
|
||||
sentbox_list.pop(x)
|
||||
else:
|
||||
sentbox_list[x]['name'] = contactmanager.ContactManager(c, sentbox_list_copy[x]['peer'], saveUser=False).get_info('name')
|
||||
|
||||
return json.dumps(sentbox_list)
|
|
@ -19,7 +19,7 @@
|
|||
'''
|
||||
|
||||
# Imports some useful libraries
|
||||
import logger, config, threading, time, readline, datetime
|
||||
import logger, config, threading, time, datetime
|
||||
from onionrblockapi import Block
|
||||
import onionrexceptions
|
||||
from onionrusers import onionrusers
|
||||
|
@ -27,12 +27,13 @@ import locale, sys, os, json
|
|||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
||||
import sentboxdb # import after path insert
|
||||
|
||||
plugin_name = 'pms'
|
||||
PLUGIN_VERSION = '0.0.1'
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
||||
import sentboxdb, mailapi, loadinbox # import after path insert
|
||||
flask_blueprint = mailapi.flask_blueprint
|
||||
|
||||
def draw_border(text):
|
||||
#https://stackoverflow.com/a/20757491
|
||||
lines = text.splitlines()
|
||||
|
@ -43,7 +44,6 @@ def draw_border(text):
|
|||
res.append('└' + '─' * width + '┘')
|
||||
return '\n'.join(res)
|
||||
|
||||
|
||||
class MailStrings:
|
||||
def __init__(self, mailInstance):
|
||||
self.mailInstance = mailInstance
|
||||
|
@ -78,7 +78,7 @@ class OnionrMail:
|
|||
displayList = []
|
||||
subject = ''
|
||||
|
||||
# this could use a lot of memory if someone has recieved a lot of messages
|
||||
# this could use a lot of memory if someone has received a lot of messages
|
||||
for blockHash in self.myCore.getBlocksByType('pm'):
|
||||
pmBlocks[blockHash] = Block(blockHash, core=self.myCore)
|
||||
pmBlocks[blockHash].decrypt()
|
||||
|
@ -191,7 +191,6 @@ class OnionrMail:
|
|||
finally:
|
||||
if choice == '-q':
|
||||
entering = False
|
||||
|
||||
return
|
||||
|
||||
def get_sent_list(self, display=True):
|
||||
|
@ -294,26 +293,20 @@ class OnionrMail:
|
|||
logger.warn('Invalid choice.')
|
||||
return
|
||||
|
||||
def add_deleted(keyStore, bHash):
|
||||
existing = keyStore.get('deleted_mail')
|
||||
if existing is None:
|
||||
existing = []
|
||||
else:
|
||||
if bHash in existing:
|
||||
return
|
||||
keyStore.put('deleted_mail', existing.append(bHash))
|
||||
|
||||
def on_insertblock(api, data={}):
|
||||
sentboxTools = sentboxdb.SentBox(api.get_core())
|
||||
meta = json.loads(data['meta'])
|
||||
sentboxTools.addToSent(data['hash'], data['peer'], data['content'], meta['subject'])
|
||||
|
||||
def on_pluginrequest(api, data=None):
|
||||
resp = ''
|
||||
subject = ''
|
||||
recip = ''
|
||||
message = ''
|
||||
postData = {}
|
||||
blockID = ''
|
||||
sentboxTools = sentboxdb.SentBox(api.get_core())
|
||||
if data['name'] == 'mail':
|
||||
path = data['path']
|
||||
cmd = path.split('/')[1]
|
||||
if cmd == 'sentbox':
|
||||
resp = OnionrMail(api).get_sent_list(display=False)
|
||||
if resp != '':
|
||||
api.get_onionr().clientAPIInst.pluginResponses[data['pluginResponse']] = resp
|
||||
|
||||
def on_init(api, data = None):
|
||||
'''
|
||||
|
|
|
@ -25,10 +25,15 @@ class SentBox:
|
|||
self.dbLocation = mycore.dataDir + 'sentbox.db'
|
||||
if not os.path.exists(self.dbLocation):
|
||||
self.createDB()
|
||||
self.conn = sqlite3.connect(self.dbLocation)
|
||||
self.cursor = self.conn.cursor()
|
||||
self.core = mycore
|
||||
return
|
||||
|
||||
def connect(self):
|
||||
self.conn = sqlite3.connect(self.dbLocation)
|
||||
self.cursor = self.conn.cursor()
|
||||
|
||||
def close(self):
|
||||
self.conn.close()
|
||||
|
||||
def createDB(self):
|
||||
conn = sqlite3.connect(self.dbLocation)
|
||||
|
@ -42,22 +47,29 @@ class SentBox:
|
|||
);
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def listSent(self):
|
||||
self.connect()
|
||||
retData = []
|
||||
for entry in self.cursor.execute('SELECT * FROM sent;'):
|
||||
retData.append({'hash': entry[0], 'peer': entry[1], 'message': entry[2], 'subject': entry[3], 'date': entry[4]})
|
||||
self.close()
|
||||
return retData
|
||||
|
||||
def addToSent(self, blockID, peer, message, subject=''):
|
||||
self.connect()
|
||||
args = (blockID, peer, message, subject, self.core._utils.getEpoch())
|
||||
self.cursor.execute('INSERT INTO sent VALUES(?, ?, ?, ?, ?)', args)
|
||||
self.conn.commit()
|
||||
self.close()
|
||||
return
|
||||
|
||||
def removeSent(self, blockID):
|
||||
self.connect()
|
||||
args = (blockID,)
|
||||
self.cursor.execute('DELETE FROM sent where hash=?', args)
|
||||
self.conn.commit()
|
||||
self.close()
|
||||
return
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
"security_level": 0,
|
||||
"max_block_age": 2678400,
|
||||
"bypass_tor_check": false,
|
||||
"public_key": ""
|
||||
"public_key": "",
|
||||
"random_bind_ip": true
|
||||
},
|
||||
|
||||
"www" : {
|
||||
|
@ -48,7 +49,7 @@
|
|||
"verbosity" : "default",
|
||||
|
||||
"file": {
|
||||
"output": true,
|
||||
"output": false,
|
||||
"path": "output.log"
|
||||
},
|
||||
|
||||
|
|
97
onionr/static-data/www/friends/friends.js
Executable file
97
onionr/static-data/www/friends/friends.js
Executable file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
Onionr - P2P Anonymous Storage Network
|
||||
|
||||
This file handles the UI for managing friends/contacts
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
friendListDisplay = document.getElementById('friendList')
|
||||
addForm = document.getElementById('addFriend')
|
||||
|
||||
function removeFriend(pubkey){
|
||||
post_to_url('/friends/remove/' + pubkey, {'token': webpass})
|
||||
}
|
||||
|
||||
addForm.onsubmit = function(){
|
||||
var friend = document.getElementsByName('addKey')[0]
|
||||
var alias = document.getElementsByName('data')[0]
|
||||
|
||||
fetch('/friends/add/' + friend.value, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"token": webpass
|
||||
}}).then(function(data) {
|
||||
if (alias.value.trim().length > 0){
|
||||
post_to_url('/friends/setinfo/' + friend.value + '/name', {'data': alias.value, 'token': webpass})
|
||||
}
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fetch('/friends/list', {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.json()) // Transform the data into json
|
||||
.then(function(resp) {
|
||||
var keys = [];
|
||||
for(var k in resp) keys.push(k);
|
||||
console.log(keys)
|
||||
friendListDisplay.innerHTML = 'Click name to view info<br><br>'
|
||||
for (var i = 0; i < keys.length; i++){
|
||||
var peer = keys[i]
|
||||
var name = resp[keys[i]]['name']
|
||||
if (name === null || name === ''){
|
||||
name = peer
|
||||
}
|
||||
var entry = document.createElement('div')
|
||||
var nameText = document.createElement('input')
|
||||
removeButton = document.createElement('button')
|
||||
removeButton.classList.add('friendRemove')
|
||||
removeButton.classList.add('dangerBtn')
|
||||
entry.setAttribute('data-pubkey', peer)
|
||||
removeButton.innerText = 'X'
|
||||
nameText.value = name
|
||||
nameText.readOnly = true
|
||||
nameText.style.fontStyle = "italic"
|
||||
entry.style.paddingTop = '8px'
|
||||
entry.appendChild(removeButton)
|
||||
entry.appendChild(nameText)
|
||||
friendListDisplay.appendChild(entry)
|
||||
entry.onclick = (function(entry, nameText, peer) {return function() {
|
||||
if (nameText.length == 0){
|
||||
nameText = 'Anonymous'
|
||||
}
|
||||
document.getElementById('friendPubkey').value = peer
|
||||
document.getElementById('friendName').innerText = nameText
|
||||
overlay('friendInfo')
|
||||
};})(entry, nameText.value, peer);
|
||||
}
|
||||
// If friend delete buttons are pressed
|
||||
|
||||
var friendRemoveBtns = document.getElementsByClassName('friendRemove')
|
||||
|
||||
for (var x = 0; x < friendRemoveBtns.length; x++){
|
||||
var friendKey = friendRemoveBtns[x].parentElement.getAttribute('data-pubkey')
|
||||
friendRemoveBtns[x].onclick = function(){
|
||||
removeFriend(friendKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('defriend').onclick = function(){
|
||||
removeFriend(document.getElementById('friendPubkey').value)
|
||||
}
|
39
onionr/static-data/www/friends/index.html
Executable file
39
onionr/static-data/www/friends/index.html
Executable file
File diff suppressed because one or more lines are too long
41
onionr/static-data/www/friends/style.css
Executable file
41
onionr/static-data/www/friends/style.css
Executable file
|
@ -0,0 +1,41 @@
|
|||
h2, h3{
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
form{
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
padding: 1em;
|
||||
margin-right: 10%;
|
||||
}
|
||||
form label{
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#friendList{
|
||||
display: inline;
|
||||
}
|
||||
#friendList span{
|
||||
text-align: center;
|
||||
}
|
||||
#friendList button{
|
||||
display: inline;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#friendInfo .overlayContent{
|
||||
background-color: lightgray;
|
||||
border: 3px solid black;
|
||||
border-radius: 3px;
|
||||
color: black;
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
min-height: 100%;
|
||||
padding: 1em;
|
||||
margin: 1em;
|
||||
}
|
||||
#defriend{
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
}
|
|
@ -16,7 +16,13 @@
|
|||
<div class='content'>
|
||||
<img class='logo' src='/shared/onionr-icon.png' alt='onionr logo'>
|
||||
<span class='logoText'>Onionr Mail ✉️</span>
|
||||
<div>Current Used Identity: <input class='myPub' type='text' readonly> <button class='refresh'>Refresh Page</button></div>
|
||||
<br><br>
|
||||
<div><a href='/' class='idLink'>Home</a> <button class='refresh'>Refresh Page</button></div>
|
||||
<div class='mailPing'>
|
||||
API server either shutdown, has disabled mail, or has experienced a bug.
|
||||
</div>
|
||||
<br>
|
||||
<div>Current Used Identity: <input class='myPub' type='text' readonly></div>
|
||||
<br><br>
|
||||
<div class="btn-group" id='tabBtns'>
|
||||
<button class='activeTab'>Inbox</button><button>Sentbox</button><button>Send Message</button>
|
||||
|
@ -30,6 +36,9 @@
|
|||
<div>
|
||||
From: <input type='text' id='fromUser' readonly> Signature: <span id='sigValid'></span>
|
||||
</div>
|
||||
<div>
|
||||
<button id='replyBtn' class='primaryBtn'>Reply</button>
|
||||
</div>
|
||||
<div id='signatureValidity'></div>
|
||||
<div id='threadDisplay' class='pre messageContent'>
|
||||
</div>
|
||||
|
@ -45,6 +54,7 @@
|
|||
</div>
|
||||
<div id='sendMessage' class='overlay'>
|
||||
<div class='overlayContent'>
|
||||
<label>Select friend: <select id='friendSelect'></select></label>
|
||||
<form method='post' action='/apipoints/mail/send' id='sendForm' enctype="application/x-www-form-urlencoded">
|
||||
<span class='closeOverlay' overlay='sendMessage'></span>
|
||||
To: <input id='draftID' type='text' name='to' placeholder='pubkey' required>
|
||||
|
|
|
@ -53,6 +53,11 @@ input{
|
|||
margin: 1em;
|
||||
}
|
||||
|
||||
.mailPing{
|
||||
display: none;
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.danger{
|
||||
color: red;
|
||||
}
|
||||
|
@ -87,6 +92,17 @@ input{
|
|||
color: black;
|
||||
}
|
||||
|
||||
#replyBtn{
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.primaryBtn{
|
||||
border-radius: 3px;
|
||||
padding: 3px;
|
||||
color: black;
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
.successBtn{
|
||||
background-color: #28a745;
|
||||
border-radius: 3px;
|
||||
|
|
|
@ -24,8 +24,27 @@ threadPlaceholder = document.getElementById('threadPlaceholder')
|
|||
tabBtns = document.getElementById('tabBtns')
|
||||
threadContent = {}
|
||||
myPub = httpGet('/getActivePubkey')
|
||||
replyBtn = document.getElementById('replyBtn')
|
||||
|
||||
function openThread(bHash, sender, date, sigBool){
|
||||
function openReply(bHash){
|
||||
var inbox = document.getElementsByClassName('threadEntry')
|
||||
var entry = ''
|
||||
var friendName = ''
|
||||
var key = ''
|
||||
for(var i = 0; i < inbox.length; i++) {
|
||||
if (inbox[i].getAttribute('data-hash') === bHash){
|
||||
entry = inbox[i]
|
||||
}
|
||||
}
|
||||
if (entry.getAttribute('data-nameSet') == 'true'){
|
||||
document.getElementById('friendSelect').value = entry.getElementsByTagName('input')[0].value
|
||||
}
|
||||
key = entry.getAttribute('data-pubkey')
|
||||
document.getElementById('draftID').value = key
|
||||
setActiveTab('send message')
|
||||
}
|
||||
|
||||
function openThread(bHash, sender, date, sigBool, pubkey){
|
||||
var messageDisplay = document.getElementById('threadDisplay')
|
||||
var blockContent = httpGet('/getblockbody/' + bHash)
|
||||
document.getElementById('fromUser').value = sender
|
||||
|
@ -38,18 +57,22 @@ function openThread(bHash, sender, date, sigBool){
|
|||
sigEl.classList.remove('danger')
|
||||
}
|
||||
else{
|
||||
sigMsg = 'Bad/no ' + sigMsg + ' (message could be fake)'
|
||||
sigMsg = 'Bad/no ' + sigMsg + ' (message could be impersonating someone)'
|
||||
sigEl.classList.add('danger')
|
||||
replyBtn.style.display = 'none'
|
||||
}
|
||||
sigEl.innerText = sigMsg
|
||||
overlay('messageDisplay')
|
||||
replyBtn.onclick = function(){
|
||||
openReply(bHash)
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveTab(tabName){
|
||||
threadPart.innerHTML = ""
|
||||
switch(tabName){
|
||||
case 'inbox':
|
||||
getInbox()
|
||||
refreshPms()
|
||||
break
|
||||
case 'sentbox':
|
||||
getSentbox()
|
||||
|
@ -60,7 +83,39 @@ function setActiveTab(tabName){
|
|||
}
|
||||
}
|
||||
|
||||
function loadInboxEntrys(bHash){
|
||||
function deleteMessage(bHash){
|
||||
fetch('/mail/deletemsg/' + bHash, {
|
||||
"method": "post",
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.text()) // Transform the data into json
|
||||
.then(function(resp) {
|
||||
})
|
||||
}
|
||||
|
||||
function mailPing(){
|
||||
fetch('/mail/ping', {
|
||||
"method": "get",
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then(function(resp) {
|
||||
var pings = document.getElementsByClassName('mailPing')
|
||||
if (resp.ok){
|
||||
for (var i=0; i < pings.length; i++){
|
||||
pings[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
else{
|
||||
for (var i=0; i < pings.length; i++){
|
||||
pings[i].style.display = 'block';
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadInboxEntries(bHash){
|
||||
fetch('/getblockheader/' + bHash, {
|
||||
headers: {
|
||||
"token": webpass
|
||||
|
@ -74,26 +129,31 @@ function loadInboxEntrys(bHash){
|
|||
var subjectLine = document.createElement('span')
|
||||
var dateStr = document.createElement('span')
|
||||
var validSig = document.createElement('span')
|
||||
var deleteBtn = document.createElement('button')
|
||||
var humanDate = new Date(0)
|
||||
var metadata = resp['metadata']
|
||||
humanDate.setUTCSeconds(resp['meta']['time'])
|
||||
validSig.style.display = 'none'
|
||||
if (resp['meta']['signer'] != ''){
|
||||
senderInput.value = httpGet('/getHumanReadable/' + resp['meta']['signer'])
|
||||
senderInput.value = httpGet('/friends/getinfo/' + resp['meta']['signer'] + '/name')
|
||||
}
|
||||
if (resp['meta']['validSig']){
|
||||
validSig.innerText = 'Signature Validity: Good'
|
||||
}
|
||||
else{
|
||||
if (! resp['meta']['validSig']){
|
||||
validSig.style.display = 'inline'
|
||||
validSig.innerText = 'Signature Validity: Bad'
|
||||
validSig.style.color = 'red'
|
||||
}
|
||||
entry.setAttribute('data-nameSet', true)
|
||||
if (senderInput.value == ''){
|
||||
senderInput.value = 'Anonymous'
|
||||
senderInput.value = resp['meta']['signer']
|
||||
entry.setAttribute('data-nameSet', false)
|
||||
}
|
||||
bHashDisplay.innerText = bHash.substring(0, 10)
|
||||
entry.setAttribute('hash', bHash)
|
||||
entry.setAttribute('data-hash', bHash)
|
||||
entry.setAttribute('data-pubkey', resp['meta']['signer'])
|
||||
senderInput.readOnly = true
|
||||
dateStr.innerText = humanDate.toString()
|
||||
deleteBtn.innerText = 'X'
|
||||
deleteBtn.classList.add('dangerBtn', 'deleteBtn')
|
||||
if (metadata['subject'] === undefined || metadata['subject'] === null) {
|
||||
subjectLine.innerText = '()'
|
||||
}
|
||||
|
@ -102,15 +162,24 @@ function loadInboxEntrys(bHash){
|
|||
}
|
||||
//entry.innerHTML = 'sender ' + resp['meta']['signer'] + ' - ' + resp['meta']['time']
|
||||
threadPart.appendChild(entry)
|
||||
entry.appendChild(deleteBtn)
|
||||
entry.appendChild(bHashDisplay)
|
||||
entry.appendChild(senderInput)
|
||||
entry.appendChild(validSig)
|
||||
entry.appendChild(subjectLine)
|
||||
entry.appendChild(dateStr)
|
||||
entry.appendChild(validSig)
|
||||
entry.classList.add('threadEntry')
|
||||
|
||||
entry.onclick = function(){
|
||||
openThread(entry.getAttribute('hash'), senderInput.value, dateStr.innerText, resp['meta']['validSig'])
|
||||
entry.onclick = function(event){
|
||||
if (event.target.classList.contains('deleteBtn')){
|
||||
return
|
||||
}
|
||||
openThread(entry.getAttribute('data-hash'), senderInput.value, dateStr.innerText, resp['meta']['validSig'], entry.getAttribute('data-pubkey'))
|
||||
}
|
||||
|
||||
deleteBtn.onclick = function(){
|
||||
entry.parentNode.removeChild(entry);
|
||||
deleteMessage(entry.getAttribute('data-hash'))
|
||||
}
|
||||
|
||||
}.bind(bHash))
|
||||
|
@ -127,7 +196,7 @@ function getInbox(){
|
|||
threadPlaceholder.style.display = 'none'
|
||||
showed = true
|
||||
}
|
||||
loadInboxEntrys(pms[i])
|
||||
loadInboxEntries(pms[i])
|
||||
}
|
||||
if (! showed){
|
||||
threadPlaceholder.style.display = 'block'
|
||||
|
@ -135,7 +204,7 @@ function getInbox(){
|
|||
}
|
||||
|
||||
function getSentbox(){
|
||||
fetch('/apipoints/mail/sentbox', {
|
||||
fetch('/mail/getsentbox', {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
|
@ -143,25 +212,49 @@ function getSentbox(){
|
|||
.then(function(resp) {
|
||||
var keys = [];
|
||||
var entry = document.createElement('div')
|
||||
var entryUsed;
|
||||
for(var k in resp) keys.push(k);
|
||||
if (keys.length == 0){
|
||||
threadPart.innerHTML = "nothing to show here yet."
|
||||
}
|
||||
for (var i = 0; i < keys.length; i++){
|
||||
var entry = document.createElement('div')
|
||||
var obj = resp[i];
|
||||
var obj = resp[i]
|
||||
var toLabel = document.createElement('span')
|
||||
toLabel.innerText = 'To: '
|
||||
var toEl = document.createElement('input')
|
||||
var sentDate = document.createElement('span')
|
||||
var humanDate = new Date(0)
|
||||
humanDate.setUTCSeconds(resp[i]['date'])
|
||||
var preview = document.createElement('span')
|
||||
var deleteBtn = document.createElement('button')
|
||||
var message = resp[i]['message']
|
||||
deleteBtn.classList.add('deleteBtn', 'dangerBtn')
|
||||
deleteBtn.innerText = 'X'
|
||||
toEl.readOnly = true
|
||||
toEl.value = resp[keys[i]][1]
|
||||
preview.innerText = '(' + resp[keys[i]][2] + ')'
|
||||
sentDate.innerText = humanDate
|
||||
if (resp[i]['name'] == null){
|
||||
toEl.value = resp[i]['peer']
|
||||
}
|
||||
else{
|
||||
toEl.value = resp[i]['name']
|
||||
}
|
||||
preview.innerText = '(' + resp[i]['subject'] + ')'
|
||||
entry.setAttribute('data-hash', resp[i]['hash'])
|
||||
entry.appendChild(deleteBtn)
|
||||
entry.appendChild(toLabel)
|
||||
entry.appendChild(toEl)
|
||||
entry.appendChild(preview)
|
||||
entryUsed = resp[keys[i]]
|
||||
entry.onclick = function(){
|
||||
entry.appendChild(sentDate)
|
||||
entry.onclick = (function(tree, el, msg) {return function() {
|
||||
console.log(resp)
|
||||
showSentboxWindow(toEl.value, entryUsed[0])
|
||||
if (! entry.classList.contains('deleteBtn')){
|
||||
showSentboxWindow(el.value, msg)
|
||||
}
|
||||
};})(entry, toEl, message);
|
||||
|
||||
deleteBtn.onclick = function(){
|
||||
entry.parentNode.removeChild(entry);
|
||||
deleteMessage(entry.getAttribute('data-hash'))
|
||||
}
|
||||
threadPart.appendChild(entry)
|
||||
}
|
||||
|
@ -175,15 +268,17 @@ function showSentboxWindow(to, content){
|
|||
overlay('sentboxDisplay')
|
||||
}
|
||||
|
||||
fetch('/getblocksbytype/pm', {
|
||||
function refreshPms(){
|
||||
fetch('/mail/getinbox', {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.text()) // Transform the data into json
|
||||
.then(function(data) {
|
||||
pms = data.split(',')
|
||||
setActiveTab('inbox')
|
||||
getInbox()
|
||||
})
|
||||
}
|
||||
|
||||
tabBtns.onclick = function(event){
|
||||
var children = tabBtns.children
|
||||
|
@ -196,13 +291,12 @@ tabBtns.onclick = function(event){
|
|||
}
|
||||
|
||||
var idStrings = document.getElementsByClassName('myPub')
|
||||
var myHumanReadable = httpGet('/getHumanReadable/' + myPub)
|
||||
for (var i = 0; i < idStrings.length; i++){
|
||||
if (idStrings[i].tagName.toLowerCase() == 'input'){
|
||||
idStrings[i].value = myHumanReadable
|
||||
idStrings[i].value = myPub
|
||||
}
|
||||
else{
|
||||
idStrings[i].innerText = myHumanReadable
|
||||
idStrings[i].innerText = myPub
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,9 +304,39 @@ for (var i = 0; i < document.getElementsByClassName('refresh').length; i++){
|
|||
document.getElementsByClassName('refresh')[i].style.float = 'right'
|
||||
}
|
||||
|
||||
for (var i = 0; i < document.getElementsByClassName('closeOverlay').length; i++){
|
||||
document.getElementsByClassName('closeOverlay')[i].onclick = function(e){
|
||||
document.getElementById(e.target.getAttribute('overlay')).style.visibility = 'hidden'
|
||||
}
|
||||
}
|
||||
|
||||
fetch('/friends/list', {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.json()) // Transform the data into json
|
||||
.then(function(resp) {
|
||||
var friendSelectParent = document.getElementById('friendSelect')
|
||||
var keys = [];
|
||||
var friend
|
||||
for(var k in resp) keys.push(k);
|
||||
|
||||
friendSelectParent.appendChild(document.createElement('option'))
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var option = document.createElement("option")
|
||||
var name = resp[keys[i]]['name']
|
||||
option.value = keys[i]
|
||||
if (name.length == 0){
|
||||
option.text = keys[i]
|
||||
}
|
||||
else{
|
||||
option.text = name
|
||||
}
|
||||
friendSelectParent.appendChild(option)
|
||||
}
|
||||
|
||||
for (var i = 0; i < keys.length; i++){
|
||||
|
||||
//friendSelectParent
|
||||
//alert(resp[keys[i]]['name'])
|
||||
}
|
||||
})
|
||||
setActiveTab('inbox')
|
||||
|
||||
setInterval(function(){mailPing()}, 10000)
|
||||
mailPing()
|
28
onionr/static-data/www/mail/sendmail.js
Normal file → Executable file
28
onionr/static-data/www/mail/sendmail.js
Normal file → Executable file
|
@ -18,11 +18,16 @@
|
|||
*/
|
||||
|
||||
var sendbutton = document.getElementById('sendMail')
|
||||
messageContent = document.getElementById('draftText')
|
||||
to = document.getElementById('draftID')
|
||||
subject = document.getElementById('draftSubject')
|
||||
friendPicker = document.getElementById('friendSelect')
|
||||
|
||||
function sendMail(to, message, subject){
|
||||
//postData = {"postData": '{"to": "' + to + '", "message": "' + message + '"}'} // galaxy brain
|
||||
postData = {'message': message, 'to': to, 'type': 'pm', 'encrypt': true, 'meta': JSON.stringify({'subject': subject})}
|
||||
postData = JSON.stringify(postData)
|
||||
sendForm.style.display = 'none'
|
||||
fetch('/insertblock', {
|
||||
method: 'POST',
|
||||
body: postData,
|
||||
|
@ -32,14 +37,23 @@ function sendMail(to, message, subject){
|
|||
}})
|
||||
.then((resp) => resp.text()) // Transform the data into json
|
||||
.then(function(data) {
|
||||
sendForm.style.display = 'block'
|
||||
alert('Queued for sending!')
|
||||
})
|
||||
}
|
||||
|
||||
sendForm.onsubmit = function(){
|
||||
var messageContent = document.getElementById('draftText')
|
||||
var to = document.getElementById('draftID')
|
||||
var subject = document.getElementById('draftSubject')
|
||||
|
||||
sendMail(to.value, messageContent.value, subject.value)
|
||||
return false;
|
||||
var friendPicker = document.getElementById('friendSelect')
|
||||
friendPicker.onchange = function(){
|
||||
to.value = friendPicker.value
|
||||
}
|
||||
|
||||
sendForm.onsubmit = function(){
|
||||
if (friendPicker.value.trim().length !== 0 && to.value.trim().length !== 0){
|
||||
if (friendPicker.value !== to.value){
|
||||
alert('You have selected both a friend and entered a public key manually.')
|
||||
return false
|
||||
}
|
||||
}
|
||||
sendMail(to.value, messageContent.value, subject.value)
|
||||
return false
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -151,3 +151,31 @@ body{
|
|||
content: '❌';
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.btn, .warnBtn, .dangerBtn, .successBtn{
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
border: 2px solid black;
|
||||
}
|
||||
.warnBtn{
|
||||
background-color: orange;
|
||||
color: black;
|
||||
}
|
||||
.dangerBtn{
|
||||
background-color: #f44336;
|
||||
color: black;
|
||||
}
|
||||
.successBtn{
|
||||
background-color: #4CAF50;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.primaryBtn{
|
||||
background-color:#396BAC;
|
||||
}
|
||||
|
||||
.openSiteBtn{
|
||||
padding: 5px;
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,25 @@
|
|||
webpass = document.location.hash.replace('#', '')
|
||||
nowebpass = false
|
||||
|
||||
function post_to_url(path, params) {
|
||||
|
||||
var form = document.createElement("form")
|
||||
|
||||
form.setAttribute("method", "POST")
|
||||
form.setAttribute("action", path)
|
||||
|
||||
for(var key in params) {
|
||||
var hiddenField = document.createElement("input")
|
||||
hiddenField.setAttribute("type", "hidden")
|
||||
hiddenField.setAttribute("name", key)
|
||||
hiddenField.setAttribute("value", params[key])
|
||||
form.appendChild(hiddenField)
|
||||
}
|
||||
|
||||
document.body.appendChild(form)
|
||||
form.submit()
|
||||
}
|
||||
|
||||
if (typeof webpass == "undefined"){
|
||||
webpass = localStorage['webpass']
|
||||
}
|
||||
|
@ -67,3 +86,9 @@ for(var i = 0; i < refreshLinks.length; i++) {
|
|||
location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < document.getElementsByClassName('closeOverlay').length; i++){
|
||||
document.getElementsByClassName('closeOverlay')[i].onclick = function(e){
|
||||
document.getElementById(e.target.getAttribute('overlay')).style.visibility = 'hidden'
|
||||
}
|
||||
}
|
18
onionr/static-data/www/shared/sites.js
Normal file
18
onionr/static-data/www/shared/sites.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
function checkHex(str) {
|
||||
regexp = /^[0-9a-fA-F]+$/
|
||||
if (regexp.test(str)){
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
document.getElementById('openSite').onclick = function(){
|
||||
var hash = document.getElementById('siteViewer').value
|
||||
|
||||
if (checkHex(hash) && hash.length == 64){
|
||||
window.location.href = '/site/' + hash
|
||||
}
|
||||
else{
|
||||
alert('Invalid site hash')
|
||||
}
|
||||
}
|
92
onionr/subprocesspow.py
Executable file
92
onionr/subprocesspow.py
Executable file
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
import subprocess, sys, os
|
||||
import multiprocessing, threading, time, json, math, binascii
|
||||
from multiprocessing import Pipe, Process
|
||||
import core, onionrblockapi, config, onionrutils, logger, onionrproofs
|
||||
|
||||
class SubprocessPOW:
|
||||
def __init__(self, data, metadata, core_inst=None, subprocCount=None):
|
||||
if core_inst is None:
|
||||
core_inst = core.Core()
|
||||
if subprocCount is None:
|
||||
subprocCount = os.cpu_count()
|
||||
self.subprocCount = subprocCount
|
||||
self.result = ''
|
||||
self.shutdown = False
|
||||
self.core_inst = core_inst
|
||||
self.data = data
|
||||
self.metadata = metadata
|
||||
|
||||
dataLen = len(data) + len(json.dumps(metadata))
|
||||
|
||||
#if forceDifficulty > 0:
|
||||
# self.difficulty = forceDifficulty
|
||||
#else:
|
||||
# Calculate difficulty. Dumb for now, may use good algorithm in the future.
|
||||
self.difficulty = onionrproofs.getDifficultyForNewBlock(dataLen)
|
||||
|
||||
try:
|
||||
self.data = self.data.encode()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
logger.info('Computing POW (difficulty: %s)...' % self.difficulty)
|
||||
|
||||
self.mainHash = '0' * 64
|
||||
self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]
|
||||
self.shutdown = False
|
||||
self.payload = None
|
||||
|
||||
def start(self):
|
||||
startTime = self.core_inst._utils.getEpoch()
|
||||
for x in range(self.subprocCount):
|
||||
threading.Thread(target=self._spawn_proc).start()
|
||||
while True:
|
||||
if self.payload is None:
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
self.shutdown = True
|
||||
return self.payload
|
||||
|
||||
def _spawn_proc(self):
|
||||
parent_conn, child_conn = Pipe()
|
||||
p = Process(target=self.do_pow, args=(child_conn,))
|
||||
p.start()
|
||||
p.join()
|
||||
payload = None
|
||||
try:
|
||||
while True:
|
||||
data = parent_conn.recv()
|
||||
if len(data) >= 1:
|
||||
payload = data
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
parent_conn.send('shutdown')
|
||||
self.payload = payload
|
||||
|
||||
def do_pow(self, pipe):
|
||||
nonce = int(binascii.hexlify(os.urandom(2)), 16)
|
||||
nonceStart = nonce
|
||||
data = self.data
|
||||
metadata = self.metadata
|
||||
puzzle = self.puzzle
|
||||
difficulty = self.difficulty
|
||||
mcore = core.Core()
|
||||
while True:
|
||||
metadata['powRandomToken'] = nonce
|
||||
payload = json.dumps(metadata).encode() + b'\n' + data
|
||||
token = mcore._crypto.sha3Hash(payload)
|
||||
try:
|
||||
# on some versions, token is bytes
|
||||
token = token.decode()
|
||||
except AttributeError:
|
||||
pass
|
||||
if pipe.poll() and pipe.recv() == 'shutdown':
|
||||
break
|
||||
if puzzle == token[0:difficulty]:
|
||||
pipe.send(payload)
|
||||
break
|
||||
nonce += 1
|
||||
|
0
onionr/tests/test_blocks.py
Normal file → Executable file
0
onionr/tests/test_blocks.py
Normal file → Executable file
40
onionr/tests/test_database_actions.py
Executable file
40
onionr/tests/test_database_actions.py
Executable file
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
import sys, os
|
||||
sys.path.append(".")
|
||||
import unittest, uuid, sqlite3
|
||||
TEST_DIR = 'testdata/%s-%s' % (uuid.uuid4(), os.path.basename(__file__)) + '/'
|
||||
print("Test directory:", TEST_DIR)
|
||||
os.environ["ONIONR_HOME"] = TEST_DIR
|
||||
from urllib.request import pathname2url
|
||||
import core, onionr
|
||||
|
||||
c = core.Core()
|
||||
|
||||
class OnionrTests(unittest.TestCase):
|
||||
|
||||
def test_address_add(self):
|
||||
testAddresses = ['facebookcorewwwi.onion', '56kmnycrvepfarolhnx6t2dvmldfeyg7jdymwgjb7jjzg47u2lqw2sad.onion', '5bvb5ncnfr4dlsfriwczpzcvo65kn7fnnlnt2ln7qvhzna2xaldq.b32.i2p']
|
||||
for address in testAddresses:
|
||||
c.addAddress(address)
|
||||
dbAddresses = c.listAdders()
|
||||
for address in testAddresses:
|
||||
self.assertIn(address, dbAddresses)
|
||||
|
||||
invalidAddresses = [None, '', ' ', '\t', '\n', ' test ', 24, 'fake.onion', 'fake.b32.i2p']
|
||||
for address in invalidAddresses:
|
||||
try:
|
||||
c.addAddress(address)
|
||||
except TypeError:
|
||||
pass
|
||||
dbAddresses = c.listAdders()
|
||||
for address in invalidAddresses:
|
||||
self.assertNotIn(address, dbAddresses)
|
||||
|
||||
def test_address_info(self):
|
||||
adder = 'nytimes3xbfgragh.onion'
|
||||
c.addAddress(adder)
|
||||
self.assertNotEqual(c.getAddressInfo(adder, 'success'), 1000)
|
||||
c.setAddressInfo(adder, 'success', 1000)
|
||||
self.assertEqual(c.getAddressInfo(adder, 'success'), 1000)
|
||||
|
||||
unittest.main()
|
0
onionr/tests/test_database_creation.py
Normal file → Executable file
0
onionr/tests/test_database_creation.py
Normal file → Executable file
40
onionr/tests/test_forward_secrecy.py
Executable file
40
onionr/tests/test_forward_secrecy.py
Executable file
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
import sys, os, random
|
||||
sys.path.append(".")
|
||||
import unittest, uuid
|
||||
TEST_DIR_1 = 'testdata/%s-%s' % (uuid.uuid4(), os.path.basename(__file__)) + '/'
|
||||
TEST_DIR_2 = 'testdata/%s-%s' % (uuid.uuid4(), os.path.basename(__file__)) + '/'
|
||||
import core, onionr, time
|
||||
|
||||
import onionrexceptions
|
||||
from onionrusers import onionrusers
|
||||
from onionrusers import contactmanager
|
||||
|
||||
class OnionrForwardSecrecyTests(unittest.TestCase):
|
||||
'''
|
||||
Tests both the onionrusers class and the contactmanager (which inherits it)
|
||||
'''
|
||||
|
||||
def test_forward_encrypt(self):
|
||||
os.environ["ONIONR_HOME"] = TEST_DIR_1
|
||||
o = onionr.Onionr()
|
||||
|
||||
friend = o.onionrCore._crypto.generatePubKey()
|
||||
|
||||
friendUser = onionrusers.OnionrUser(o.onionrCore, friend[0], saveUser=True)
|
||||
|
||||
for x in range(5):
|
||||
message = 'hello world %s' % (random.randint(1, 1000))
|
||||
forwardKey = friendUser.generateForwardKey()
|
||||
|
||||
fakeForwardPair = o.onionrCore._crypto.generatePubKey()
|
||||
|
||||
self.assertTrue(friendUser.addForwardKey(fakeForwardPair[0]))
|
||||
|
||||
encrypted = friendUser.forwardEncrypt(message)
|
||||
|
||||
decrypted = o.onionrCore._crypto.pubKeyDecrypt(encrypted[0], privkey=fakeForwardPair[1], encodedData=True)
|
||||
self.assertEqual(decrypted, message.encode())
|
||||
return
|
||||
|
||||
unittest.main()
|
50
onionr/tests/test_highlevelcrypto.py
Normal file → Executable file
50
onionr/tests/test_highlevelcrypto.py
Normal file → Executable file
|
@ -1,23 +1,23 @@
|
|||
#!/usr/bin/env python3
|
||||
import sys, os
|
||||
sys.path.append(".")
|
||||
import unittest, uuid, hashlib
|
||||
import unittest, uuid, hashlib, base64
|
||||
import nacl.exceptions
|
||||
import nacl.signing, nacl.hash, nacl.encoding
|
||||
TEST_DIR = 'testdata/%s-%s' % (uuid.uuid4(), os.path.basename(__file__)) + '/'
|
||||
print("Test directory:", TEST_DIR)
|
||||
os.environ["ONIONR_HOME"] = TEST_DIR
|
||||
import core, onionr
|
||||
import core, onionr, onionrexceptions
|
||||
|
||||
c = core.Core()
|
||||
crypto = c._crypto
|
||||
class OnionrCryptoTests(unittest.TestCase):
|
||||
|
||||
def test_blake2b(self):
|
||||
self.assertTrue(crypto.blake2bHash('test') == crypto.blake2bHash(b'test'))
|
||||
self.assertTrue(crypto.blake2bHash(b'test') == crypto.blake2bHash(b'test'))
|
||||
self.assertEqual(crypto.blake2bHash('test'), crypto.blake2bHash(b'test'))
|
||||
self.assertEqual(crypto.blake2bHash(b'test'), crypto.blake2bHash(b'test'))
|
||||
|
||||
self.assertFalse(crypto.blake2bHash('') == crypto.blake2bHash(b'test'))
|
||||
self.assertNotEqual(crypto.blake2bHash(''), crypto.blake2bHash(b'test'))
|
||||
try:
|
||||
crypto.blake2bHash(None)
|
||||
except nacl.exceptions.TypeError:
|
||||
|
@ -25,14 +25,14 @@ class OnionrCryptoTests(unittest.TestCase):
|
|||
else:
|
||||
self.assertTrue(False)
|
||||
|
||||
self.assertTrue(nacl.hash.blake2b(b'test') == crypto.blake2bHash(b'test'))
|
||||
self.assertEqual(nacl.hash.blake2b(b'test'), crypto.blake2bHash(b'test'))
|
||||
|
||||
def test_sha3256(self):
|
||||
hasher = hashlib.sha3_256()
|
||||
self.assertTrue(crypto.sha3Hash('test') == crypto.sha3Hash(b'test'))
|
||||
self.assertTrue(crypto.sha3Hash(b'test') == crypto.sha3Hash(b'test'))
|
||||
self.assertEqual(crypto.sha3Hash('test'), crypto.sha3Hash(b'test'))
|
||||
self.assertEqual(crypto.sha3Hash(b'test'), crypto.sha3Hash(b'test'))
|
||||
|
||||
self.assertFalse(crypto.sha3Hash('') == crypto.sha3Hash(b'test'))
|
||||
self.assertNotEqual(crypto.sha3Hash(''), crypto.sha3Hash(b'test'))
|
||||
try:
|
||||
crypto.sha3Hash(None)
|
||||
except TypeError:
|
||||
|
@ -42,7 +42,7 @@ class OnionrCryptoTests(unittest.TestCase):
|
|||
|
||||
hasher.update(b'test')
|
||||
normal = hasher.hexdigest()
|
||||
self.assertTrue(crypto.sha3Hash(b'test') == normal)
|
||||
self.assertEqual(crypto.sha3Hash(b'test'), normal)
|
||||
|
||||
def valid_default_id(self):
|
||||
self.assertTrue(c._utils.validatePubKey(crypto.pubKey))
|
||||
|
@ -73,8 +73,8 @@ class OnionrCryptoTests(unittest.TestCase):
|
|||
# Small chance that the randomized list will be same. Rerun test a couple times if it fails
|
||||
startList = ['cat', 'dog', 'moose', 'rabbit', 'monkey', 'crab', 'human', 'dolphin', 'whale', 'etc'] * 10
|
||||
|
||||
self.assertFalse(startList == list(crypto.randomShuffle(startList)))
|
||||
self.assertTrue(len(startList) == len(startList))
|
||||
self.assertNotEqual(startList, list(crypto.randomShuffle(startList)))
|
||||
self.assertTrue(len(list(crypto.randomShuffle(startList))) == len(startList))
|
||||
|
||||
def test_asymmetric(self):
|
||||
keyPair = crypto.generatePubKey()
|
||||
|
@ -126,5 +126,31 @@ class OnionrCryptoTests(unittest.TestCase):
|
|||
pass
|
||||
else:
|
||||
self.assertFalse(True)
|
||||
|
||||
def test_deterministic(self):
|
||||
password = os.urandom(32)
|
||||
gen = crypto.generateDeterministic(password)
|
||||
self.assertTrue(c._utils.validatePubKey(gen[0]))
|
||||
try:
|
||||
crypto.generateDeterministic('weakpassword')
|
||||
except onionrexceptions.PasswordStrengthError:
|
||||
pass
|
||||
else:
|
||||
self.assertFalse(True)
|
||||
try:
|
||||
crypto.generateDeterministic(None)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.assertFalse(True)
|
||||
|
||||
gen = crypto.generateDeterministic('weakpassword', bypassCheck=True)
|
||||
|
||||
password = base64.b64encode(os.urandom(32))
|
||||
gen1 = crypto.generateDeterministic(password)
|
||||
gen2 = crypto.generateDeterministic(password)
|
||||
self.assertFalse(gen == gen1)
|
||||
self.assertTrue(gen1 == gen2)
|
||||
self.assertTrue(c._utils.validatePubKey(gen1[0]))
|
||||
|
||||
unittest.main()
|
17
onionr/tests/test_onionrusers.py
Normal file → Executable file
17
onionr/tests/test_onionrusers.py
Normal file → Executable file
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
import sys, os
|
||||
sys.path.append(".")
|
||||
import unittest, uuid, hashlib
|
||||
import unittest, uuid
|
||||
import json
|
||||
TEST_DIR = 'testdata/%s-%s' % (uuid.uuid4(), os.path.basename(__file__)) + '/'
|
||||
print("Test directory:", TEST_DIR)
|
||||
|
@ -44,7 +44,7 @@ class OnionrUserTests(unittest.TestCase):
|
|||
data = data.read()
|
||||
|
||||
data = json.loads(data)
|
||||
self.assertTrue(data['alias'] == 'bob')
|
||||
self.assertEqual(data['alias'], 'bob')
|
||||
|
||||
def test_contact_get_info(self):
|
||||
contact = c._crypto.generatePubKey()[0]
|
||||
|
@ -54,9 +54,16 @@ class OnionrUserTests(unittest.TestCase):
|
|||
with open(fileLocation, 'w') as contactFile:
|
||||
contactFile.write('{"alias": "bob"}')
|
||||
|
||||
self.assertTrue(contact.get_info('alias', forceReload=True) == 'bob')
|
||||
self.assertTrue(contact.get_info('fail', forceReload=True) == None)
|
||||
self.assertTrue(contact.get_info('fail') == None)
|
||||
self.assertEqual(contact.get_info('alias', forceReload=True), 'bob')
|
||||
self.assertEqual(contact.get_info('fail', forceReload=True), None)
|
||||
self.assertEqual(contact.get_info('fail'), None)
|
||||
|
||||
def test_encrypt(self):
|
||||
contactPair = c._crypto.generatePubKey()
|
||||
contact = contactmanager.ContactManager(c, contactPair[0], saveUser=True)
|
||||
encrypted = contact.encrypt('test')
|
||||
decrypted = c._crypto.pubKeyDecrypt(encrypted, privkey=contactPair[1], encodedData=True).decode()
|
||||
self.assertEqual('test', decrypted)
|
||||
|
||||
def test_delete_contact(self):
|
||||
contact = c._crypto.generatePubKey()[0]
|
||||
|
|
0
onionr/tests/test_stringvalidations.py
Normal file → Executable file
0
onionr/tests/test_stringvalidations.py
Normal file → Executable file
0
onionr/utils/netutils.py
Normal file → Executable file
0
onionr/utils/netutils.py
Normal file → Executable file
0
onionr/utils/networkmerger.py
Normal file → Executable file
0
onionr/utils/networkmerger.py
Normal file → Executable file
Loading…
Add table
Add a link
Reference in a new issue