From ee5c620cc60fdcd71e521221bebc62860e9c3a30 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 19 Feb 2019 16:14:06 -0600 Subject: [PATCH 01/35] small fixes and work on tests --- onionr/onionr.py | 43 +++++++++++++++------------- onionr/onionrusers/onionrusers.py | 14 --------- onionr/tests/test_highlevelcrypto.py | 30 +++++++++++++++++-- onionr/tests/test_onionrusers.py | 2 +- run_tests.sh | 6 ++-- 5 files changed, 55 insertions(+), 40 deletions(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index c79b6488..13e6e47b 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -54,10 +54,12 @@ class Onionr: ''' self.userRunDir = os.getcwd() # Directory user runs the program from self.killed = False - try: - os.chdir(sys.path[0]) - except FileNotFoundError: - pass + + if sys.argv[0] == os.path.basename(__file__): + try: + os.chdir(sys.path[0]) + except FileNotFoundError: + pass try: self.dataDir = os.environ['ONIONR_HOME'] @@ -267,6 +269,17 @@ class Onionr: THIS SECTION HANDLES THE COMMANDS ''' + def doExport(self, bHash): + exportDir = self.dataDir + 'block-export/' + if not os.path.exists(exportDir): + if os.path.exists(self.dataDir): + os.mkdir(exportDir) + else: + logger.error('Onionr Not initialized') + data = onionrstorage.getData(self.onionrCore, bHash) + with open('%s/%s.dat' % (exportDir, bHash), 'wb') as exportFile: + exportFile.write(data) + def exportBlock(self): exportDir = self.dataDir + 'block-export/' try: @@ -276,19 +289,7 @@ class Onionr: sys.exit(1) else: bHash = sys.argv[2] - try: - path = sys.argv[3] - except (IndexError): - if not os.path.exists(exportDir): - if os.path.exists(self.dataDir): - os.mkdir(exportDir) - else: - logger.error('Onionr not initialized') - sys.exit(1) - path = exportDir - data = onionrstorage.getData(self.onionrCore, bHash) - with open('%s/%s.dat' % (exportDir, bHash), 'wb') as exportFile: - exportFile.write(data) + self.doExport(bHash) def showDetails(self): details = { @@ -691,8 +692,8 @@ class Onionr: ''' Displays a message suggesting help ''' - - logger.info('Do ' + logger.colors.bold + sys.argv[0] + ' --help' + logger.colors.reset + logger.colors.fg.green + ' for Onionr help.') + if __name__ == '__main__': + logger.info('Do ' + logger.colors.bold + sys.argv[0] + ' --help' + logger.colors.reset + logger.colors.fg.green + ' for Onionr help.') def start(self, input = False, override = False): ''' @@ -993,7 +994,9 @@ class Onionr: os.mkdir(dataDir) if os.path.exists('static-data/default_config.json'): - config.set_config(json.loads(open('static-data/default_config.json').read())) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it + # 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!') diff --git a/onionr/onionrusers/onionrusers.py b/onionr/onionrusers/onionrusers.py index ab14a5a0..e57c45c0 100755 --- a/onionr/onionrusers/onionrusers.py +++ b/onionr/onionrusers/onionrusers.py @@ -190,17 +190,3 @@ class OnionrUser: 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 diff --git a/onionr/tests/test_highlevelcrypto.py b/onionr/tests/test_highlevelcrypto.py index 67236b45..fa4334f6 100644 --- a/onionr/tests/test_highlevelcrypto.py +++ b/onionr/tests/test_highlevelcrypto.py @@ -1,13 +1,13 @@ #!/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 @@ -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() \ No newline at end of file diff --git a/onionr/tests/test_onionrusers.py b/onionr/tests/test_onionrusers.py index 152c5882..0c9bbaac 100644 --- a/onionr/tests/test_onionrusers.py +++ b/onionr/tests/test_onionrusers.py @@ -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) diff --git a/run_tests.sh b/run_tests.sh index d0a60b5b..c59e6b3a 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,9 +1,10 @@ #!/bin/bash cd onionr; +rm -rf testdata; mkdir testdata; ran=0 - +SECONDS=0 ; close () { rm -rf testdata; exit 10; @@ -13,5 +14,4 @@ for f in tests/*.py; do python3 "$f" || close # if needed let "ran++" done -rm -rf testdata; -echo "ran $ran test files successfully" \ No newline at end of file +echo "ran $ran test files successfully in $SECONDS seconds" \ No newline at end of file From 651e2b173b5f7ab9d7c0542ec1bb9fee95f126db Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 20 Feb 2019 00:09:18 -0600 Subject: [PATCH 02/35] fixing up forward secrecy more, added test for it --- onionr/onionrusers/onionrusers.py | 5 ++-- onionr/tests/test_forward_secrecy.py | 41 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 onionr/tests/test_forward_secrecy.py diff --git a/onionr/onionrusers/onionrusers.py b/onionr/onionrusers/onionrusers.py index e57c45c0..2df81319 100755 --- a/onionr/onionrusers/onionrusers.py +++ b/onionr/onionrusers/onionrusers.py @@ -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 @@ -189,4 +190,4 @@ class OnionrUser: conn.commit() conn.close() - return + return True diff --git a/onionr/tests/test_forward_secrecy.py b/onionr/tests/test_forward_secrecy.py new file mode 100644 index 00000000..9c802b7e --- /dev/null +++ b/onionr/tests/test_forward_secrecy.py @@ -0,0 +1,41 @@ +#!/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_decrypt(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(3): + 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.assertTrue(decrypted == message.encode()) + time.sleep(1) + return + +unittest.main() \ No newline at end of file From bcb0af2e549a220cdeb2927e6494b814e518d9c6 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 20 Feb 2019 17:12:11 -0600 Subject: [PATCH 03/35] fixed forward secrecy time conflicts, adjusted tests --- onionr/api.py | 2 +- onionr/onionrusers/onionrusers.py | 32 ++++++++++++++++++---------- onionr/tests/test_blocks.py | 1 + onionr/tests/test_forward_secrecy.py | 7 +++--- onionr/tests/test_highlevelcrypto.py | 20 ++++++++--------- onionr/tests/test_onionrusers.py | 15 +++++++++---- 6 files changed, 47 insertions(+), 30 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 2e6ff46d..e98142ad 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -230,7 +230,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() diff --git a/onionr/onionrusers/onionrusers.py b/onionr/onionrusers/onionrusers.py index 2df81319..83c0e277 100755 --- a/onionr/onionrusers/onionrusers.py +++ b/onionr/onionrusers/onionrusers.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -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): @@ -127,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() @@ -176,15 +175,26 @@ 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) diff --git a/onionr/tests/test_blocks.py b/onionr/tests/test_blocks.py index 257238b1..e0c699e9 100644 --- a/onionr/tests/test_blocks.py +++ b/onionr/tests/test_blocks.py @@ -13,6 +13,7 @@ c = core.Core() class OnionrBlockTests(unittest.TestCase): def test_plaintext_insert(self): + return message = 'hello world' c.insertBlock(message) diff --git a/onionr/tests/test_forward_secrecy.py b/onionr/tests/test_forward_secrecy.py index 9c802b7e..0110be12 100644 --- a/onionr/tests/test_forward_secrecy.py +++ b/onionr/tests/test_forward_secrecy.py @@ -15,7 +15,7 @@ class OnionrForwardSecrecyTests(unittest.TestCase): Tests both the onionrusers class and the contactmanager (which inherits it) ''' - def test_forward_decrypt(self): + def test_forward_encrypt(self): os.environ["ONIONR_HOME"] = TEST_DIR_1 o = onionr.Onionr() @@ -23,7 +23,7 @@ class OnionrForwardSecrecyTests(unittest.TestCase): friendUser = onionrusers.OnionrUser(o.onionrCore, friend[0], saveUser=True) - for x in range(3): + for x in range(5): message = 'hello world %s' % (random.randint(1, 1000)) forwardKey = friendUser.generateForwardKey() @@ -34,8 +34,7 @@ class OnionrForwardSecrecyTests(unittest.TestCase): encrypted = friendUser.forwardEncrypt(message) decrypted = o.onionrCore._crypto.pubKeyDecrypt(encrypted[0], privkey=fakeForwardPair[1], encodedData=True) - self.assertTrue(decrypted == message.encode()) - time.sleep(1) + self.assertEqual(decrypted, message.encode()) return unittest.main() \ No newline at end of file diff --git a/onionr/tests/test_highlevelcrypto.py b/onionr/tests/test_highlevelcrypto.py index fa4334f6..0a675ed7 100644 --- a/onionr/tests/test_highlevelcrypto.py +++ b/onionr/tests/test_highlevelcrypto.py @@ -14,10 +14,10 @@ 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() diff --git a/onionr/tests/test_onionrusers.py b/onionr/tests/test_onionrusers.py index 0c9bbaac..5b98a2f5 100644 --- a/onionr/tests/test_onionrusers.py +++ b/onionr/tests/test_onionrusers.py @@ -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] From 4f39c5792ae28fccb5d48c85a3d04d8434262be2 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 21 Feb 2019 14:25:45 -0600 Subject: [PATCH 04/35] work on UI friends manager --- onionr/api.py | 19 ++++++-- onionr/communicator.py | 5 +- onionr/core.py | 5 -- onionr/httpapi/friendsapi/__init__.py | 48 +++++++++++++++++++ .../default-plugins/contactmanager/info.json | 5 ++ .../default-plugins/contactmanager/main.py | 39 +++++++++++++++ onionr/static-data/www/friends/friends.js | 21 ++++++++ onionr/static-data/www/friends/index.html | 32 +++++++++++++ onionr/static-data/www/friends/style.css | 15 ++++++ onionr/static-data/www/private/index.html | 7 +-- onionr/tests/test_blocks.py | 1 - 11 files changed, 183 insertions(+), 14 deletions(-) create mode 100644 onionr/httpapi/friendsapi/__init__.py create mode 100644 onionr/static-data/default-plugins/contactmanager/info.json create mode 100644 onionr/static-data/default-plugins/contactmanager/main.py create mode 100644 onionr/static-data/www/friends/friends.js create mode 100644 onionr/static-data/www/friends/index.html create mode 100644 onionr/static-data/www/friends/style.css diff --git a/onionr/api.py b/onionr/api.py index e98142ad..5db29a9b 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -21,10 +21,12 @@ 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 +from httpapi import friendsapi +import onionr class FDSafeHandler(WSGIHandler): '''Our WSGI handler. Doesn't do much non-default except timeouts''' @@ -250,7 +252,7 @@ class API: ''' # assert isinstance(onionrInst, onionr.Onionr) # configure logger and stuff - onionr.Onionr.setupConfig('data/', self = self) + #onionr.Onionr.setupConfig('data/', self = self) self.debug = debug self._core = onionrInst.onionrCore @@ -262,7 +264,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 +278,7 @@ class API: self.pluginResponses = {} # Responses for plugin endpoints self.queueResponse = {} onionrInst.setClientAPIInst(self) + app.register_blueprint(friendsapi.friends) @app.before_request def validateRequest(): @@ -315,6 +318,14 @@ class API: @app.route('/mail/', endpoint='mailindex') def loadMailIndex(): return send_from_directory('static-data/www/mail/', 'index.html') + + @app.route('/friends/', 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/', endpoint='boardContent') def boardContent(path): diff --git a/onionr/communicator.py b/onionr/communicator.py index b6847422..6af66fac 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -171,7 +171,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,6 +432,8 @@ 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: diff --git a/onionr/core.py b/onionr/core.py index c1b743e4..b135c4f2 100755 --- a/onionr/core.py +++ b/onionr/core.py @@ -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() @@ -597,10 +596,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() diff --git a/onionr/httpapi/friendsapi/__init__.py b/onionr/httpapi/friendsapi/__init__.py new file mode 100644 index 00000000..bca0ad92 --- /dev/null +++ b/onionr/httpapi/friendsapi/__init__.py @@ -0,0 +1,48 @@ +''' + 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 . +''' +import core +from onionrusers import contactmanager +from flask import Blueprint, Response, request, abort + +friends = Blueprint('friends', __name__) + +@friends.route('/friends/add/', methods=['POST']) +def add_friend(pubkey): + contactmanager.ContactManager(core.Core(), pubkey, saveUser=True).setTrust(1) + return 'success' + +@friends.route('/friends/remove/', methods=['POST']) +def remove_friend(pubkey): + contactmanager.ContactManager(core.Core(), pubkey).setTrust(0) + return 'success' + +@friends.route('/friends/setinfo//', methods=['POST']) +def set_info(pubkey, key): + data = request.form['data'] + contactmanager.ContactManager(core.Core(), pubkey).set_info(key, data) + return 'success' + +@friends.route('/friends/getinfo//') +def get_info(pubkey, key): + retData = contactmanager.ContactManager(core.Core(), pubkey).get_info(key) + if retData is None: + abort(404) + else: + return retData \ No newline at end of file diff --git a/onionr/static-data/default-plugins/contactmanager/info.json b/onionr/static-data/default-plugins/contactmanager/info.json new file mode 100644 index 00000000..60967d52 --- /dev/null +++ b/onionr/static-data/default-plugins/contactmanager/info.json @@ -0,0 +1,5 @@ +{ + "name" : "contactmanager", + "version" : "1.0", + "author" : "onionr" +} diff --git a/onionr/static-data/default-plugins/contactmanager/main.py b/onionr/static-data/default-plugins/contactmanager/main.py new file mode 100644 index 00000000..2aaa423a --- /dev/null +++ b/onionr/static-data/default-plugins/contactmanager/main.py @@ -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 . +''' + +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 = OnionrCLIUI(api) + #api.commands.register('interactive', ui.start) + return diff --git a/onionr/static-data/www/friends/friends.js b/onionr/static-data/www/friends/friends.js new file mode 100644 index 00000000..b8c673f3 --- /dev/null +++ b/onionr/static-data/www/friends/friends.js @@ -0,0 +1,21 @@ +/* + 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 . +*/ + +friendListDisplay = document.getElementById('friendList') + diff --git a/onionr/static-data/www/friends/index.html b/onionr/static-data/www/friends/index.html new file mode 100644 index 00000000..265eb9ed --- /dev/null +++ b/onionr/static-data/www/friends/index.html @@ -0,0 +1,32 @@ + + + + + + Onionr + + + + + + +
+
+

Your node will shutdown. Thank you for using Onionr.

+
+
+
+ + Onionr Web Control Panel +

Friend Manager

+
+ + + +
+
+
+ + + + \ No newline at end of file diff --git a/onionr/static-data/www/friends/style.css b/onionr/static-data/www/friends/style.css new file mode 100644 index 00000000..37e4ede0 --- /dev/null +++ b/onionr/static-data/www/friends/style.css @@ -0,0 +1,15 @@ +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; +} \ No newline at end of file diff --git a/onionr/static-data/www/private/index.html b/onionr/static-data/www/private/index.html index 80da5cdc..0b61cd45 100755 --- a/onionr/static-data/www/private/index.html +++ b/onionr/static-data/www/private/index.html @@ -14,11 +14,12 @@

Your node will shutdown. Thank you for using Onionr.

- - Onionr Web Control Panel
+ + Onionr Web Control Panel +

-

Mail +

Mail - Friend Manager

Stats

Uptime:

Last Received Connection: Unknown

diff --git a/onionr/tests/test_blocks.py b/onionr/tests/test_blocks.py index e0c699e9..257238b1 100644 --- a/onionr/tests/test_blocks.py +++ b/onionr/tests/test_blocks.py @@ -13,7 +13,6 @@ c = core.Core() class OnionrBlockTests(unittest.TestCase): def test_plaintext_insert(self): - return message = 'hello world' c.insertBlock(message) From 30a2ae8d06da70d721f1b2c4fd98ea4da9e4ae50 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 21 Feb 2019 19:55:13 -0600 Subject: [PATCH 05/35] work on UI friends manager --- onionr/core.py | 8 ------- onionr/httpapi/friendsapi/__init__.py | 10 ++++++++- onionr/onionr.py | 7 ++---- onionr/onionrusers/onionrusers.py | 7 ++++++ .../default-plugins/contactmanager/main.py | 2 +- onionr/static-data/www/friends/friends.js | 22 +++++++++++++++++++ onionr/static-data/www/friends/index.html | 8 ++----- 7 files changed, 43 insertions(+), 21 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index b135c4f2..396ebca4 100755 --- a/onionr/core.py +++ b/onionr/core.py @@ -467,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 diff --git a/onionr/httpapi/friendsapi/__init__.py b/onionr/httpapi/friendsapi/__init__.py index bca0ad92..80ade1f4 100644 --- a/onionr/httpapi/friendsapi/__init__.py +++ b/onionr/httpapi/friendsapi/__init__.py @@ -17,12 +17,20 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import core +import core, json from onionrusers import contactmanager from flask import Blueprint, Response, request, abort 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/', methods=['POST']) def add_friend(pubkey): contactmanager.ContactManager(core.Core(), pubkey, saveUser=True).setTrust(1) diff --git a/onionr/onionr.py b/onionr/onionr.py index 13e6e47b..7742f632 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -370,11 +370,8 @@ class Onionr: action = action.lower() if action == 'list': # List out peers marked as our friend - for friend in self.onionrCore.listPeers(randomOrder=False, trust=1): - if friend == self.onionrCore._crypto.pubKey: # do not list our key - continue - friendProfile = onionrusers.OnionrUser(self.onionrCore, friend) - logger.info(friend + ' - ' + friendProfile.getName()) + for friend in onionrusers.OnionrUser.list_friends(self.onionrCore): + logger.info(friend.publicKey + ' - ' + friend.getName()) elif action in ('add', 'remove'): try: friend = sys.argv[3] diff --git a/onionr/onionrusers/onionrusers.py b/onionr/onionrusers/onionrusers.py index 83c0e277..b680acd7 100755 --- a/onionr/onionrusers/onionrusers.py +++ b/onionr/onionrusers/onionrusers.py @@ -201,3 +201,10 @@ class OnionrUser: conn.commit() conn.close() return True + + @classmethod + def list_friends(cls, coreInst): + friendList = [] + for x in coreInst.listPeers(trust=1): + friendList.append(cls(coreInst, x)) + return list(friendList) \ No newline at end of file diff --git a/onionr/static-data/default-plugins/contactmanager/main.py b/onionr/static-data/default-plugins/contactmanager/main.py index 2aaa423a..bdf6ac5b 100644 --- a/onionr/static-data/default-plugins/contactmanager/main.py +++ b/onionr/static-data/default-plugins/contactmanager/main.py @@ -34,6 +34,6 @@ def on_init(api, data = None): # Doing this makes it so that the other functions can access the api object # by simply referencing the variable `pluginapi`. pluginapi = api - ui = OnionrCLIUI(api) + ui = OnionrContactManager(api) #api.commands.register('interactive', ui.start) return diff --git a/onionr/static-data/www/friends/friends.js b/onionr/static-data/www/friends/friends.js index b8c673f3..313e1bb8 100644 --- a/onionr/static-data/www/friends/friends.js +++ b/onionr/static-data/www/friends/friends.js @@ -19,3 +19,25 @@ friendListDisplay = document.getElementById('friendList') +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) + for (var i = 0; i < keys.length; i++){ + friendListDisplay.innerText = '' + var peer = keys[i] + var name = resp[keys[i]]['name'] + if (name === null || name === ''){ + name = 'Anonymous' + } + var entry = document.createElement('div') + entry.style.paddingTop = '8px' + entry.innerText = name + ' - ' + peer + friendListDisplay.appendChild(entry) + } + }) \ No newline at end of file diff --git a/onionr/static-data/www/friends/index.html b/onionr/static-data/www/friends/index.html index 265eb9ed..e45bf6ce 100644 --- a/onionr/static-data/www/friends/index.html +++ b/onionr/static-data/www/friends/index.html @@ -10,11 +10,6 @@ -
-
-

Your node will shutdown. Thank you for using Onionr.

-
-
Onionr Web Control Panel @@ -24,7 +19,8 @@ -
+

Friend List:

+
None Yet :(
From c61c8336587d8c8166b93f831fb5db4a3b11279d Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 22 Feb 2019 15:04:03 -0600 Subject: [PATCH 06/35] work on UI friends manager --- onionr/api.py | 12 ++++--- onionr/httpapi/friendsapi/__init__.py | 4 +-- onionr/onionrblockapi.py | 32 +++--------------- onionr/onionrdaemontools.py | 3 +- onionr/static-data/www/friends/friends.js | 34 ++++++++++++++++++-- onionr/static-data/www/friends/index.html | 4 +-- onionr/static-data/www/friends/style.css | 11 +++++++ onionr/static-data/www/shared/main/style.css | 20 +++++++++++- onionr/static-data/www/shared/misc.js | 20 ++++++++++++ 9 files changed, 100 insertions(+), 40 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 5db29a9b..402622d9 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -290,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): @@ -417,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/', methods=['post']) diff --git a/onionr/httpapi/friendsapi/__init__.py b/onionr/httpapi/friendsapi/__init__.py index 80ade1f4..d6a3c2f5 100644 --- a/onionr/httpapi/friendsapi/__init__.py +++ b/onionr/httpapi/friendsapi/__init__.py @@ -19,7 +19,7 @@ ''' import core, json from onionrusers import contactmanager -from flask import Blueprint, Response, request, abort +from flask import Blueprint, Response, request, abort, redirect friends = Blueprint('friends', __name__) @@ -45,7 +45,7 @@ def remove_friend(pubkey): def set_info(pubkey, key): data = request.form['data'] contactmanager.ContactManager(core.Core(), pubkey).set_info(key, data) - return 'success' + return redirect(request.referrer + '#' + request.form['token']) @friends.route('/friends/getinfo//') def get_info(pubkey, key): diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index e9724b03..6921ab90 100755 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -139,32 +139,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 +178,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) diff --git a/onionr/onionrdaemontools.py b/onionr/onionrdaemontools.py index dace5b06..bae9031e 100755 --- a/onionr/onionrdaemontools.py +++ b/onionr/onionrdaemontools.py @@ -89,7 +89,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 diff --git a/onionr/static-data/www/friends/friends.js b/onionr/static-data/www/friends/friends.js index 313e1bb8..51e5fc21 100644 --- a/onionr/static-data/www/friends/friends.js +++ b/onionr/static-data/www/friends/friends.js @@ -18,6 +18,24 @@ */ friendListDisplay = document.getElementById('friendList') +addForm = document.getElementById('addFriend') + +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: { @@ -28,16 +46,26 @@ fetch('/friends/list', { var keys = []; for(var k in resp) keys.push(k); console.log(keys) + friendListDisplay.innerHTML = 'Click name to view info

' for (var i = 0; i < keys.length; i++){ - friendListDisplay.innerText = '' var peer = keys[i] var name = resp[keys[i]]['name'] if (name === null || name === ''){ - name = 'Anonymous' + 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.innerText = name + ' - ' + peer + entry.appendChild(removeButton) + entry.appendChild(nameText) friendListDisplay.appendChild(entry) } }) \ No newline at end of file diff --git a/onionr/static-data/www/friends/index.html b/onionr/static-data/www/friends/index.html index e45bf6ce..6110a29d 100644 --- a/onionr/static-data/www/friends/index.html +++ b/onionr/static-data/www/friends/index.html @@ -16,8 +16,8 @@

Friend Manager

- - + +

Friend List:

None Yet :(
diff --git a/onionr/static-data/www/friends/style.css b/onionr/static-data/www/friends/style.css index 37e4ede0..663c4b4e 100644 --- a/onionr/static-data/www/friends/style.css +++ b/onionr/static-data/www/friends/style.css @@ -12,4 +12,15 @@ 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; } \ No newline at end of file diff --git a/onionr/static-data/www/shared/main/style.css b/onionr/static-data/www/shared/main/style.css index 96cb5e88..65c378eb 100755 --- a/onionr/static-data/www/shared/main/style.css +++ b/onionr/static-data/www/shared/main/style.css @@ -150,4 +150,22 @@ body{ .closeOverlay:after{ content: '❌'; padding: 5px; - } \ No newline at end of file + } + + .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; +} \ No newline at end of file diff --git a/onionr/static-data/www/shared/misc.js b/onionr/static-data/www/shared/misc.js index d4322887..bdde2f84 100755 --- a/onionr/static-data/www/shared/misc.js +++ b/onionr/static-data/www/shared/misc.js @@ -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,4 @@ for(var i = 0; i < refreshLinks.length; i++) { location.reload() } } + From 31039861c20d167bafcfa8396eb12d7fe393fec2 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 23 Feb 2019 18:11:43 -0600 Subject: [PATCH 07/35] bind ip config option, use different type for deniable block, mostly finished friends UI --- onionr/api.py | 26 +++++++++++------------ onionr/httpapi/friendsapi/__init__.py | 4 ++-- onionr/onionr.py | 4 +++- onionr/onionrdaemontools.py | 2 +- onionr/static-data/default_config.json | 5 +++-- onionr/static-data/www/friends/friends.js | 14 ++++++++++++ onionr/static-data/www/friends/index.html | 3 +++ onionr/static-data/www/mail/index.html | 5 ++++- onionr/static-data/www/private/index.html | 1 + 9 files changed, 44 insertions(+), 20 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 402622d9..6590a258 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -40,22 +40,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: diff --git a/onionr/httpapi/friendsapi/__init__.py b/onionr/httpapi/friendsapi/__init__.py index d6a3c2f5..c935ded5 100644 --- a/onionr/httpapi/friendsapi/__init__.py +++ b/onionr/httpapi/friendsapi/__init__.py @@ -34,12 +34,12 @@ def list_friends(): @friends.route('/friends/add/', methods=['POST']) def add_friend(pubkey): contactmanager.ContactManager(core.Core(), pubkey, saveUser=True).setTrust(1) - return 'success' + return redirect(request.referrer + '#' + request.form['token']) @friends.route('/friends/remove/', methods=['POST']) def remove_friend(pubkey): contactmanager.ContactManager(core.Core(), pubkey).setTrust(0) - return 'success' + return redirect(request.referrer + '#' + request.form['token']) @friends.route('/friends/setinfo//', methods=['POST']) def set_info(pubkey, key): diff --git a/onionr/onionr.py b/onionr/onionr.py index 7742f632..1a16742b 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -308,7 +308,9 @@ class Onionr: except FileNotFoundError: logger.error('Onionr seems to not be running (could not get api host)') else: - webbrowser.open_new_tab('http://%s/#%s' % (url, config.get('client.webpassword'))) + url = 'http://%s/#%s' % (url, config.get('client.webpassword')) + print('If Onionr does not open automatically, use this URL:', url) + webbrowser.open_new_tab(url) def addID(self): try: diff --git a/onionr/onionrdaemontools.py b/onionr/onionrdaemontools.py index bae9031e..73cdf0f1 100755 --- a/onionr/onionrdaemontools.py +++ b/onionr/onionrdaemontools.py @@ -200,6 +200,6 @@ class DaemonTools: if secrets.randbelow(chance) == (chance - 1): fakePeer = self.daemon._core._crypto.generatePubKey()[0] data = secrets.token_hex(secrets.randbelow(500) + 1) - self.daemon._core.insertBlock(data, header='pm', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'}) + self.daemon._core.insertBlock(data, header='db', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'}) self.daemon.decrementThreadCount('insertDeniableBlock') return \ No newline at end of file diff --git a/onionr/static-data/default_config.json b/onionr/static-data/default_config.json index ba5e5566..86261502 100755 --- a/onionr/static-data/default_config.json +++ b/onionr/static-data/default_config.json @@ -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" }, diff --git a/onionr/static-data/www/friends/friends.js b/onionr/static-data/www/friends/friends.js index 51e5fc21..68cc8661 100644 --- a/onionr/static-data/www/friends/friends.js +++ b/onionr/static-data/www/friends/friends.js @@ -20,6 +20,10 @@ 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] @@ -68,4 +72,14 @@ fetch('/friends/list', { entry.appendChild(nameText) friendListDisplay.appendChild(entry) } + // 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) + } + } }) \ No newline at end of file diff --git a/onionr/static-data/www/friends/index.html b/onionr/static-data/www/friends/index.html index 6110a29d..8c1074fe 100644 --- a/onionr/static-data/www/friends/index.html +++ b/onionr/static-data/www/friends/index.html @@ -5,6 +5,7 @@ Onionr + @@ -13,6 +14,8 @@
Onionr Web Control Panel +

+ Home

Friend Manager

diff --git a/onionr/static-data/www/mail/index.html b/onionr/static-data/www/mail/index.html index 03b0b8ec..1b1c0e84 100755 --- a/onionr/static-data/www/mail/index.html +++ b/onionr/static-data/www/mail/index.html @@ -16,7 +16,10 @@
Onionr Mail ✉️ -
Current Used Identity:
+

+
Home
+
+
Current Used Identity:


diff --git a/onionr/static-data/www/private/index.html b/onionr/static-data/www/private/index.html index 0b61cd45..8d5d5861 100755 --- a/onionr/static-data/www/private/index.html +++ b/onionr/static-data/www/private/index.html @@ -5,6 +5,7 @@ Onionr + From 651fc8c43c0c21be86b17937c93923339cd7fe7e Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 25 Feb 2019 22:19:37 -0600 Subject: [PATCH 08/35] prevent replay of very old encrypted data --- docs/onionr-logo.png~ | Bin 195499 -> 0 bytes docs/whitepaper.md | 20 ++++++++- onionr/core.py | 15 ++++--- onionr/onionrblockapi.py | 18 +++++++- onionr/onionrcrypto.py | 7 +++ onionr/onionrdaemontools.py | 4 +- onionr/onionrexceptions.py | 3 ++ onionr/onionrutils.py | 6 +++ .../static-data/default-plugins/pms/main.py | 1 - onionr/static-data/www/mail/index.html | 1 + onionr/static-data/www/mail/mail.js | 42 +++++++++++++++--- onionr/static-data/www/mail/sendmail.js | 26 ++++++++--- 12 files changed, 119 insertions(+), 24 deletions(-) delete mode 100755 docs/onionr-logo.png~ diff --git a/docs/onionr-logo.png~ b/docs/onionr-logo.png~ deleted file mode 100755 index 5e15d42f73b97d6dd0368883938d64ca53aa3945..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 195499 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 diff --git a/docs/whitepaper.md b/docs/whitepaper.md index 8c5d0f96..f7ed9fa8 100755 --- a/docs/whitepaper.md +++ b/docs/whitepaper.md @@ -91,6 +91,22 @@ In addition, randomness beacons such as the one operated by [NIST](https://beaco # Direct Connections -We propose a method of using Onionr's block sync system to enable direct connections between peers by having one peer request to connect to another using the peer's public key. Since the request is within a standard block, proof of work must be used to request connection. If the requested peer is available and wishes to accept the connection,Onionr will generate a temporary .onion address for the other peer to connect to. Alternatively, a reverse connection may be formed, which is faster to establish but requires a message brokering system instead of a standard socket. +We propose a method of using Onionr's block sync system to enable direct connections between peers by having one peer request to connect to another using the peer's public key. Since the request is within a standard block, proof of work must be used to request connection. If the requested peer is available and wishes to accept the connection, Onionr will generate a temporary .onion address for the other peer to connect to. Alternatively, a reverse connection may be formed, which is faster to establish but requires a message brokering system instead of a standard socket. -The benefits of such a system are increased privacy, and the ability to anonymously communicate from multiple devices at once. In a traditional onion service, one's online status can be monitored and more easily correlated. \ No newline at end of file +The benefits of such a system are increased privacy, and the ability to anonymously communicate from multiple devices at once. In a traditional onion service, one's online status can be monitored and more easily correlated. + +# Threat Model + +The goal of Onionr is to provide a method of distributing information in a manner in which the difficulty of discovering the identity of those sending and receiving the information is greatly increased. In this section we detail what information we want to protect and who we're protecting it from. + +In this threat model, "protected" means available in plaintext only to those which it was intended, and regardless non-malleable + +## Threat Actors + +Onionr assumes that traffic/data is being surveilled by a multitude of actors on every level but the local machine. Some examples of threat actors that we seek to protect against include Internet service providers, local area network administrators, + +## Assumptions + +We assume that Tor onion services (v3) and I2P services cannot be trivially deanonymized, and that the cryptographic algorithms we employ cannot be broken in any manner faster than brute force unless a quantum computer is used. + +Once supposed quantum safe algorithms are more mature and have relatively high level libraries, they will be deployed. \ No newline at end of file diff --git a/onionr/core.py b/onionr/core.py index 396ebca4..6b2e1505 100755 --- a/onionr/core.py +++ b/onionr/core.py @@ -689,6 +689,8 @@ class Core: return False retData = False + createTime = self._utils.getRoundedEpoch() + # check nonce dataNonce = self._utils.bytesToStr(self._crypto.sha3Hash(data)) try: @@ -706,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 = '' @@ -732,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) @@ -779,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): @@ -804,7 +804,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): diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index 6921ab90..caa9bee1 100755 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -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,19 @@ 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: + 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 +111,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 diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 8f6f8e7a..4b5a72e1 100755 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -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 diff --git a/onionr/onionrdaemontools.py b/onionr/onionrdaemontools.py index 73cdf0f1..70b405f7 100755 --- a/onionr/onionrdaemontools.py +++ b/onionr/onionrdaemontools.py @@ -198,8 +198,8 @@ 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='db', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'}) + self.daemon._core.insertBlock(data, header='pm', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'}) self.daemon.decrementThreadCount('insertDeniableBlock') return \ No newline at end of file diff --git a/onionr/onionrexceptions.py b/onionr/onionrexceptions.py index 757091db..5bb82c6c 100755 --- a/onionr/onionrexceptions.py +++ b/onionr/onionrexceptions.py @@ -45,6 +45,9 @@ class PasswordStrengthError(Exception): # block exceptions +class ReplayAttack(Exception): + pass + class DifficultyTooLarge(Exception): pass diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index afe834e8..23d265d7 100755 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -284,6 +284,12 @@ class OnionrUtils: 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) diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py index a5efab12..d0552a94 100755 --- a/onionr/static-data/default-plugins/pms/main.py +++ b/onionr/static-data/default-plugins/pms/main.py @@ -43,7 +43,6 @@ def draw_border(text): res.append('└' + '─' * width + '┘') return '\n'.join(res) - class MailStrings: def __init__(self, mailInstance): self.mailInstance = mailInstance diff --git a/onionr/static-data/www/mail/index.html b/onionr/static-data/www/mail/index.html index 1b1c0e84..53530968 100755 --- a/onionr/static-data/www/mail/index.html +++ b/onionr/static-data/www/mail/index.html @@ -48,6 +48,7 @@
+ To: diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index 4513ce9b..c7f266ce 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -78,7 +78,7 @@ function loadInboxEntrys(bHash){ var metadata = resp['metadata'] humanDate.setUTCSeconds(resp['meta']['time']) 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' @@ -88,7 +88,7 @@ function loadInboxEntrys(bHash){ validSig.style.color = 'red' } if (senderInput.value == ''){ - senderInput.value = 'Anonymous' + senderInput.value = resp['meta']['signer'] } bHashDisplay.innerText = bHash.substring(0, 10) entry.setAttribute('hash', bHash) @@ -195,17 +195,18 @@ tabBtns.onclick = function(event){ setActiveTab(event.target.innerText.toLowerCase()) } + 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 } } + for (var i = 0; i < document.getElementsByClassName('refresh').length; i++){ document.getElementsByClassName('refresh')[i].style.float = 'right' } @@ -216,3 +217,34 @@ for (var i = 0; i < document.getElementsByClassName('closeOverlay').length; i++) } } +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']) + } +}) \ No newline at end of file diff --git a/onionr/static-data/www/mail/sendmail.js b/onionr/static-data/www/mail/sendmail.js index 945c7792..abece988 100644 --- a/onionr/static-data/www/mail/sendmail.js +++ b/onionr/static-data/www/mail/sendmail.js @@ -18,6 +18,10 @@ */ 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 @@ -35,11 +39,19 @@ function sendMail(to, message, subject){ }) } -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 } From 14e9332b90dc56448a5e42a892700e7a1f458530 Mon Sep 17 00:00:00 2001 From: KF Date: Mon, 25 Feb 2019 23:40:05 -0600 Subject: [PATCH 09/35] do not enforce replay protection on old blocks we're just opening again --- onionr/onionrblockapi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index caa9bee1..e7a4395b 100755 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -88,7 +88,8 @@ class Block: # Check for replay attacks try: - assert self.core._crypto.replayTimestampValidation(self.bmetadata['rply']) + 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 From 9a728fb1f20408c5bd7040d07de45444a69e39e4 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 26 Feb 2019 12:33:47 -0600 Subject: [PATCH 10/35] updated whitepaper and allow reading of old encrypted blocks --- docs/whitepaper.md | 25 +++++++++++++++++++++++-- onionr/communicator.py | 2 +- onionr/static-data/bootstrap-nodes.txt | 2 +- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/whitepaper.md b/docs/whitepaper.md index f7ed9fa8..fae80b15 100755 --- a/docs/whitepaper.md +++ b/docs/whitepaper.md @@ -103,10 +103,31 @@ In this threat model, "protected" means available in plaintext only to those whi ## Threat Actors -Onionr assumes that traffic/data is being surveilled by a multitude of actors on every level but the local machine. Some examples of threat actors that we seek to protect against include Internet service providers, local area network administrators, +Onionr assumes that traffic/data is being surveilled by powerful actors on every level but the user's device. + +We also assume that the actors are capable of the following: + +* Running tens of thousands of Onionr nodes +* Surveiling most of the Tor and I2P networks + +## Protected Data + +We seek to protect the following information: + +* Contents of private data. E.g. 'mail' messages and secret files +* Relationship metadata. Unless something is desired to be published publicly, we seek to hide the creator and recipients of such data. +* Physical location/IP address of nodes on the network +* All block data from tampering + +### Data we cannot or do not protect + +* Data specifically inserted as plaintext is available to the public +* The public key of signed plaintext blocks +* The fact that one is using Tor or I2P + * The fact that one is using Onionr can likely be discovered using long term traffic analysis ## Assumptions We assume that Tor onion services (v3) and I2P services cannot be trivially deanonymized, and that the cryptographic algorithms we employ cannot be broken in any manner faster than brute force unless a quantum computer is used. -Once supposed quantum safe algorithms are more mature and have relatively high level libraries, they will be deployed. \ No newline at end of file +Once quantum safe algorithms are more mature and have relatively high level libraries, they will be deployed. diff --git a/onionr/communicator.py b/onionr/communicator.py index 6af66fac..b4e68b68 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -670,7 +670,7 @@ class OnionrCommunicatorTimers: 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: + if self.count == self.frequency and not self.daemonInstance.shutdown: try: if self.requiresPeer and len(self.daemonInstance.onlinePeers) == 0: raise onionrexceptions.OnlinePeerNeeded diff --git a/onionr/static-data/bootstrap-nodes.txt b/onionr/static-data/bootstrap-nodes.txt index fe11c63d..82cc2a2d 100755 --- a/onionr/static-data/bootstrap-nodes.txt +++ b/onionr/static-data/bootstrap-nodes.txt @@ -1 +1 @@ -yjlbrt6ins7rhacaapk4a4rfwf3dqh55merbaobxd7qumuok6j6vd7yd.onion \ No newline at end of file +3xudvnmedfkkw6zisfrmm76ovrnmcil3hmah7kcxruv37glxizfxiuqd.onion \ No newline at end of file From 11625b297adedd30354567c9b028373388701cf0 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 27 Feb 2019 21:02:44 -0600 Subject: [PATCH 11/35] refactoring and work on mail --- onionr/api.py | 2 +- onionr/communicator.py | 49 ++------------- .../onionrcommunicatortimers.py | 63 +++++++++++++++++++ .../onionrdaemontools.py | 0 onionr/onionrevents.py | 1 - onionr/static-data/www/mail/index.html | 3 + onionr/static-data/www/mail/mail.css | 11 ++++ onionr/static-data/www/mail/mail.js | 39 ++++++++++-- onionr/static-data/www/shared/main/style.css | 4 ++ onionr/tests/test_database_actions.py | 40 ++++++++++++ 10 files changed, 160 insertions(+), 52 deletions(-) create mode 100644 onionr/communicatorutils/onionrcommunicatortimers.py rename onionr/{ => communicatorutils}/onionrdaemontools.py (100%) create mode 100644 onionr/tests/test_database_actions.py diff --git a/onionr/api.py b/onionr/api.py index 6590a258..647d7635 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -175,7 +175,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: diff --git a/onionr/communicator.py b/onionr/communicator.py index b4e68b68..54a512ac 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -21,12 +21,16 @@ ''' 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 +from communicatorutils import onionrdaemontools +import onionrsockets, onionr, onionrproofs import binascii +from communicatorutils import onionrcommunicatortimers from dependencies import secrets from defusedxml import minidom from utils import networkmerger +OnionrCommunicatorTimers = onionrcommunicatortimers.OnionrCommunicatorTimers + config.reload() class OnionrCommunicatorDaemon: def __init__(self, onionrInst, proxyPort, developmentMode=config.get('general.dev_mode', False)): @@ -647,48 +651,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 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 - def startCommunicator(onionrInst, proxyPort): OnionrCommunicatorDaemon(onionrInst, proxyPort) \ No newline at end of file diff --git a/onionr/communicatorutils/onionrcommunicatortimers.py b/onionr/communicatorutils/onionrcommunicatortimers.py new file mode 100644 index 00000000..c15f20fc --- /dev/null +++ b/onionr/communicatorutils/onionrcommunicatortimers.py @@ -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 . +''' +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 diff --git a/onionr/onionrdaemontools.py b/onionr/communicatorutils/onionrdaemontools.py similarity index 100% rename from onionr/onionrdaemontools.py rename to onionr/communicatorutils/onionrdaemontools.py diff --git a/onionr/onionrevents.py b/onionr/onionrevents.py index 0a2c48f1..3301a3ac 100755 --- a/onionr/onionrevents.py +++ b/onionr/onionrevents.py @@ -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 diff --git a/onionr/static-data/www/mail/index.html b/onionr/static-data/www/mail/index.html index 53530968..16b95db9 100755 --- a/onionr/static-data/www/mail/index.html +++ b/onionr/static-data/www/mail/index.html @@ -33,6 +33,9 @@
From: Signature:
+
+ +
diff --git a/onionr/static-data/www/mail/mail.css b/onionr/static-data/www/mail/mail.css index a8d27120..0334de30 100755 --- a/onionr/static-data/www/mail/mail.css +++ b/onionr/static-data/www/mail/mail.css @@ -87,6 +87,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; diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index c7f266ce..71b75c6c 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -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 @@ -43,6 +62,9 @@ function openThread(bHash, sender, date, sigBool){ } sigEl.innerText = sigMsg overlay('messageDisplay') + replyBtn.onclick = function(){ + openReply(bHash) + } } function setActiveTab(tabName){ @@ -60,7 +82,7 @@ function setActiveTab(tabName){ } } -function loadInboxEntrys(bHash){ +function loadInboxEntries(bHash){ fetch('/getblockheader/' + bHash, { headers: { "token": webpass @@ -87,11 +109,14 @@ function loadInboxEntrys(bHash){ validSig.innerText = 'Signature Validity: Bad' validSig.style.color = 'red' } + entry.setAttribute('data-nameSet', true) if (senderInput.value == ''){ 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() if (metadata['subject'] === undefined || metadata['subject'] === null) { @@ -110,7 +135,7 @@ function loadInboxEntrys(bHash){ entry.classList.add('threadEntry') entry.onclick = function(){ - openThread(entry.getAttribute('hash'), senderInput.value, dateStr.innerText, resp['meta']['validSig']) + openThread(entry.getAttribute('data-hash'), senderInput.value, dateStr.innerText, resp['meta']['validSig'], entry.getAttribute('data-pubkey')) } }.bind(bHash)) @@ -127,7 +152,7 @@ function getInbox(){ threadPlaceholder.style.display = 'none' showed = true } - loadInboxEntrys(pms[i]) + loadInboxEntries(pms[i]) } if (! showed){ threadPlaceholder.style.display = 'block' @@ -145,6 +170,9 @@ function getSentbox(){ 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]; @@ -195,7 +223,6 @@ tabBtns.onclick = function(event){ setActiveTab(event.target.innerText.toLowerCase()) } - var idStrings = document.getElementsByClassName('myPub') for (var i = 0; i < idStrings.length; i++){ if (idStrings[i].tagName.toLowerCase() == 'input'){ diff --git a/onionr/static-data/www/shared/main/style.css b/onionr/static-data/www/shared/main/style.css index 65c378eb..ae4d87db 100755 --- a/onionr/static-data/www/shared/main/style.css +++ b/onionr/static-data/www/shared/main/style.css @@ -168,4 +168,8 @@ body{ .successBtn{ background-color: #4CAF50; color: black; +} + +.primaryBtn{ + background-color:#396BAC; } \ No newline at end of file diff --git a/onionr/tests/test_database_actions.py b/onionr/tests/test_database_actions.py new file mode 100644 index 00000000..76a1350c --- /dev/null +++ b/onionr/tests/test_database_actions.py @@ -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() \ No newline at end of file From c89bf5b430cc6054c969a3e42096995a4bb799b6 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 2 Mar 2019 00:22:59 -0600 Subject: [PATCH 12/35] work on plugins doing http endpoints --- .gitlab-ci.yml | 0 Makefile | 3 ++ onionr/__init__.py | 0 onionr/api.py | 7 +++-- .../onionrcommunicatortimers.py | 0 onionr/core.py | 4 +-- onionr/httpapi/__init__.py | 10 ++++++ onionr/httpapi/friendsapi/__init__.py | 0 onionr/httpapi/simplecache/__init__.py | 31 +++++++++++++++++++ onionr/onionrfragment.py | 0 onionr/onionrplugins.py | 1 - onionr/onionrstorage.py | 8 ----- onionr/onionrusers/contactmanager.py | 0 .../default-plugins/contactmanager/info.json | 0 .../default-plugins/contactmanager/main.py | 0 .../static-data/default-plugins/pms/main.py | 27 ++++++++++++++-- onionr/static-data/www/friends/friends.js | 0 onionr/static-data/www/friends/index.html | 0 onionr/static-data/www/friends/style.css | 0 onionr/static-data/www/mail/mail.js | 1 - onionr/static-data/www/mail/sendmail.js | 0 onionr/tests/test_blocks.py | 0 onionr/tests/test_database_actions.py | 0 onionr/tests/test_database_creation.py | 0 onionr/tests/test_forward_secrecy.py | 0 onionr/tests/test_highlevelcrypto.py | 0 onionr/tests/test_onionrusers.py | 0 onionr/tests/test_stringvalidations.py | 0 onionr/utils/netutils.py | 0 onionr/utils/networkmerger.py | 0 requirements.txt | 1 + 31 files changed, 76 insertions(+), 17 deletions(-) mode change 100644 => 100755 .gitlab-ci.yml mode change 100644 => 100755 onionr/__init__.py mode change 100644 => 100755 onionr/communicatorutils/onionrcommunicatortimers.py create mode 100755 onionr/httpapi/__init__.py mode change 100644 => 100755 onionr/httpapi/friendsapi/__init__.py create mode 100755 onionr/httpapi/simplecache/__init__.py mode change 100644 => 100755 onionr/onionrfragment.py mode change 100644 => 100755 onionr/onionrusers/contactmanager.py mode change 100644 => 100755 onionr/static-data/default-plugins/contactmanager/info.json mode change 100644 => 100755 onionr/static-data/default-plugins/contactmanager/main.py mode change 100644 => 100755 onionr/static-data/www/friends/friends.js mode change 100644 => 100755 onionr/static-data/www/friends/index.html mode change 100644 => 100755 onionr/static-data/www/friends/style.css mode change 100644 => 100755 onionr/static-data/www/mail/sendmail.js mode change 100644 => 100755 onionr/tests/test_blocks.py mode change 100644 => 100755 onionr/tests/test_database_actions.py mode change 100644 => 100755 onionr/tests/test_database_creation.py mode change 100644 => 100755 onionr/tests/test_forward_secrecy.py mode change 100644 => 100755 onionr/tests/test_highlevelcrypto.py mode change 100644 => 100755 onionr/tests/test_onionrusers.py mode change 100644 => 100755 onionr/tests/test_stringvalidations.py mode change 100644 => 100755 onionr/utils/netutils.py mode change 100644 => 100755 onionr/utils/networkmerger.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml old mode 100644 new mode 100755 diff --git a/Makefile b/Makefile index 4721e97c..4fdae5b8 100755 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ PREFIX = /usr/local .DEFAULT_GOAL := setup +SHELL := env ONIONR_HOME=$(ONIONR_HOME) $(SHELL) +ONIONR_HOME ?= "data" + setup: sudo pip3 install -r requirements.txt -@cd onionr/static-data/ui/; ./compile.py diff --git a/onionr/__init__.py b/onionr/__init__.py old mode 100644 new mode 100755 diff --git a/onionr/api.py b/onionr/api.py index 647d7635..baf60023 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -25,7 +25,8 @@ 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 -from httpapi import friendsapi +import httpapi +from httpapi import friendsapi, simplecache import onionr class FDSafeHandler(WSGIHandler): @@ -279,6 +280,8 @@ class API: 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(): @@ -521,13 +524,13 @@ class API: responseTimeout = 20 startTime = self._core._utils.getEpoch() postData = {} + print('spth', subpath) if request.method == 'POST': postData = request.form['postData'] if len(subpath) > 1: 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: diff --git a/onionr/communicatorutils/onionrcommunicatortimers.py b/onionr/communicatorutils/onionrcommunicatortimers.py old mode 100644 new mode 100755 diff --git a/onionr/core.py b/onionr/core.py index 6b2e1505..73e237ea 100755 --- a/onionr/core.py +++ b/onionr/core.py @@ -19,7 +19,7 @@ ''' 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 @@ -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) diff --git a/onionr/httpapi/__init__.py b/onionr/httpapi/__init__.py new file mode 100755 index 00000000..137fe137 --- /dev/null +++ b/onionr/httpapi/__init__.py @@ -0,0 +1,10 @@ +from flask import request +import onionrplugins + +def load_plugin_blueprints(flaskapp): + for plugin in onionrplugins.get_enabled_plugins(): + plugin = onionrplugins.get_plugin(plugin) + try: + flaskapp.register_blueprint(getattr(plugin, 'flask_blueprint')) + except AttributeError: + pass \ No newline at end of file diff --git a/onionr/httpapi/friendsapi/__init__.py b/onionr/httpapi/friendsapi/__init__.py old mode 100644 new mode 100755 diff --git a/onionr/httpapi/simplecache/__init__.py b/onionr/httpapi/simplecache/__init__.py new file mode 100755 index 00000000..75a645a0 --- /dev/null +++ b/onionr/httpapi/simplecache/__init__.py @@ -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 . +''' +import core +from flask import Blueprint, Response, request, abort + +simplecache = Blueprint('simplecache', __name__) + +@simplecache.route('/get/') +def get_key(key): + return + +@simplecache.route('/set/', methods=['POST']) +def set_key(key): + return \ No newline at end of file diff --git a/onionr/onionrfragment.py b/onionr/onionrfragment.py old mode 100644 new mode 100755 diff --git a/onionr/onionrplugins.py b/onionr/onionrplugins.py index 7d81e08c..198e58da 100755 --- a/onionr/onionrplugins.py +++ b/onionr/onionrplugins.py @@ -59,7 +59,6 @@ def reload(onionr = None, stop_event = True): return False - def enable(name, onionr = None, start_event = True): ''' Enables a plugin diff --git a/onionr/onionrstorage.py b/onionr/onionrstorage.py index 63aa150d..65fe6757 100755 --- a/onionr/onionrstorage.py +++ b/onionr/onionrstorage.py @@ -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) diff --git a/onionr/onionrusers/contactmanager.py b/onionr/onionrusers/contactmanager.py old mode 100644 new mode 100755 diff --git a/onionr/static-data/default-plugins/contactmanager/info.json b/onionr/static-data/default-plugins/contactmanager/info.json old mode 100644 new mode 100755 diff --git a/onionr/static-data/default-plugins/contactmanager/main.py b/onionr/static-data/default-plugins/contactmanager/main.py old mode 100644 new mode 100755 diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py index d0552a94..8afd099e 100755 --- a/onionr/static-data/default-plugins/pms/main.py +++ b/onionr/static-data/default-plugins/pms/main.py @@ -23,15 +23,21 @@ import logger, config, threading, time, readline, datetime from onionrblockapi import Block import onionrexceptions from onionrusers import onionrusers +from flask import Response, request, redirect, Blueprint import locale, sys, os, json locale.setlocale(locale.LC_ALL, '') +plugin_name = 'pms' +PLUGIN_VERSION = '0.0.1' +flask_blueprint = Blueprint('mail', __name__) + 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' +@flask_blueprint.route('/mailhello') +def mailhello(): + return "hello world from mail" def draw_border(text): #https://stackoverflow.com/a/20757491 @@ -190,7 +196,6 @@ class OnionrMail: finally: if choice == '-q': entering = False - return def get_sent_list(self, display=True): @@ -293,6 +298,15 @@ 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']) @@ -306,11 +320,17 @@ def on_pluginrequest(api, data=None): postData = {} blockID = '' sentboxTools = sentboxdb.SentBox(api.get_core()) + keyStore = api.get_core().keyStore if data['name'] == 'mail': path = data['path'] + print(cmd) cmd = path.split('/')[1] if cmd == 'sentbox': resp = OnionrMail(api).get_sent_list(display=False) + elif cmd == 'deletemsg': + print('path', data['path']) + #add_deleted(keyStore, data['path'].split('/')[2]) + resp = 'success' if resp != '': api.get_onionr().clientAPIInst.pluginResponses[data['pluginResponse']] = resp @@ -325,4 +345,5 @@ def on_init(api, data = None): mail = OnionrMail(pluginapi) api.commands.register(['mail'], mail.menu) api.commands.register_help('mail', 'Interact with OnionrMail') + print("YEET2") return diff --git a/onionr/static-data/www/friends/friends.js b/onionr/static-data/www/friends/friends.js old mode 100644 new mode 100755 diff --git a/onionr/static-data/www/friends/index.html b/onionr/static-data/www/friends/index.html old mode 100644 new mode 100755 diff --git a/onionr/static-data/www/friends/style.css b/onionr/static-data/www/friends/style.css old mode 100644 new mode 100755 diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index 71b75c6c..d057099b 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -233,7 +233,6 @@ for (var i = 0; i < idStrings.length; i++){ } } - for (var i = 0; i < document.getElementsByClassName('refresh').length; i++){ document.getElementsByClassName('refresh')[i].style.float = 'right' } diff --git a/onionr/static-data/www/mail/sendmail.js b/onionr/static-data/www/mail/sendmail.js old mode 100644 new mode 100755 diff --git a/onionr/tests/test_blocks.py b/onionr/tests/test_blocks.py old mode 100644 new mode 100755 diff --git a/onionr/tests/test_database_actions.py b/onionr/tests/test_database_actions.py old mode 100644 new mode 100755 diff --git a/onionr/tests/test_database_creation.py b/onionr/tests/test_database_creation.py old mode 100644 new mode 100755 diff --git a/onionr/tests/test_forward_secrecy.py b/onionr/tests/test_forward_secrecy.py old mode 100644 new mode 100755 diff --git a/onionr/tests/test_highlevelcrypto.py b/onionr/tests/test_highlevelcrypto.py old mode 100644 new mode 100755 diff --git a/onionr/tests/test_onionrusers.py b/onionr/tests/test_onionrusers.py old mode 100644 new mode 100755 diff --git a/onionr/tests/test_stringvalidations.py b/onionr/tests/test_stringvalidations.py old mode 100644 new mode 100755 diff --git a/onionr/utils/netutils.py b/onionr/utils/netutils.py old mode 100644 new mode 100755 diff --git a/onionr/utils/networkmerger.py b/onionr/utils/networkmerger.py old mode 100644 new mode 100755 diff --git a/requirements.txt b/requirements.txt index 6bceb49a..6b1cb663 100755 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ defusedxml==0.5.0 Flask==1.0.2 PySocks==1.6.8 stem==1.6.0 +deadsimplekv==0.0.1 \ No newline at end of file From c45c016123789ed354993ac6fa401364fd13804a Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 2 Mar 2019 13:17:18 -0600 Subject: [PATCH 13/35] moving mail over to blueprint system --- Makefile | 3 -- onionr/api.py | 4 -- onionr/core.py | 2 +- onionr/httpapi/__init__.py | 21 ++++++++- .../default-plugins/pms/loadinbox.py | 13 ++++++ .../default-plugins/pms/mailapi.py | 43 +++++++++++++++++++ .../static-data/default-plugins/pms/main.py | 16 ++----- onionr/static-data/www/mail/mail.js | 2 +- 8 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 onionr/static-data/default-plugins/pms/loadinbox.py create mode 100644 onionr/static-data/default-plugins/pms/mailapi.py diff --git a/Makefile b/Makefile index 4fdae5b8..4721e97c 100755 --- a/Makefile +++ b/Makefile @@ -2,9 +2,6 @@ PREFIX = /usr/local .DEFAULT_GOAL := setup -SHELL := env ONIONR_HOME=$(ONIONR_HOME) $(SHELL) -ONIONR_HOME ?= "data" - setup: sudo pip3 install -r requirements.txt -@cd onionr/static-data/ui/; ./compile.py diff --git a/onionr/api.py b/onionr/api.py index baf60023..79694238 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -251,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 @@ -524,7 +521,6 @@ class API: responseTimeout = 20 startTime = self._core._utils.getEpoch() postData = {} - print('spth', subpath) if request.method == 'POST': postData = request.form['postData'] if len(subpath) > 1: diff --git a/onionr/core.py b/onionr/core.py index 73e237ea..c0682a98 100755 --- a/onionr/core.py +++ b/onionr/core.py @@ -65,7 +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) + self.keyStore = simplekv.DeadSimpleKV(self.dataDir + 'cachedstorage.dat', refresh_seconds=5) # Socket data, defined here because of multithreading constraints with gevent self.killSockets = False diff --git a/onionr/httpapi/__init__.py b/onionr/httpapi/__init__.py index 137fe137..018d3abb 100755 --- a/onionr/httpapi/__init__.py +++ b/onionr/httpapi/__init__.py @@ -1,7 +1,26 @@ -from flask import request +''' + 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 . +''' 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: diff --git a/onionr/static-data/default-plugins/pms/loadinbox.py b/onionr/static-data/default-plugins/pms/loadinbox.py new file mode 100644 index 00000000..996d8f06 --- /dev/null +++ b/onionr/static-data/default-plugins/pms/loadinbox.py @@ -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 \ No newline at end of file diff --git a/onionr/static-data/default-plugins/pms/mailapi.py b/onionr/static-data/default-plugins/pms/mailapi.py new file mode 100644 index 00000000..7ef49bb4 --- /dev/null +++ b/onionr/static-data/default-plugins/pms/mailapi.py @@ -0,0 +1,43 @@ +''' + 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 . +''' +import sys, os +from flask import Response, request, redirect, Blueprint +import core +sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) +import loadinbox + +flask_blueprint = Blueprint('mail', __name__) +c = core.Core() +kv = c.keyStore + +@flask_blueprint.route('/mail/deletemsg/', methods=['POST']) +def mail_delete(block): + assert c._utils.validateHash(block) + 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)) \ No newline at end of file diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py index 8afd099e..5af4a828 100755 --- a/onionr/static-data/default-plugins/pms/main.py +++ b/onionr/static-data/default-plugins/pms/main.py @@ -23,21 +23,16 @@ import logger, config, threading, time, readline, datetime from onionrblockapi import Block import onionrexceptions from onionrusers import onionrusers -from flask import Response, request, redirect, Blueprint import locale, sys, os, json locale.setlocale(locale.LC_ALL, '') plugin_name = 'pms' PLUGIN_VERSION = '0.0.1' -flask_blueprint = Blueprint('mail', __name__) sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) -import sentboxdb # import after path insert - -@flask_blueprint.route('/mailhello') -def mailhello(): - return "hello world from mail" +import sentboxdb, mailapi, loadinbox # import after path insert +flask_blueprint = mailapi.flask_blueprint def draw_border(text): #https://stackoverflow.com/a/20757491 @@ -83,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() @@ -327,10 +322,6 @@ def on_pluginrequest(api, data=None): cmd = path.split('/')[1] if cmd == 'sentbox': resp = OnionrMail(api).get_sent_list(display=False) - elif cmd == 'deletemsg': - print('path', data['path']) - #add_deleted(keyStore, data['path'].split('/')[2]) - resp = 'success' if resp != '': api.get_onionr().clientAPIInst.pluginResponses[data['pluginResponse']] = resp @@ -345,5 +336,4 @@ def on_init(api, data = None): mail = OnionrMail(pluginapi) api.commands.register(['mail'], mail.menu) api.commands.register_help('mail', 'Interact with OnionrMail') - print("YEET2") return diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index d057099b..81350907 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -203,7 +203,7 @@ function showSentboxWindow(to, content){ overlay('sentboxDisplay') } -fetch('/getblocksbytype/pm', { +fetch('/mail/getinbox', { headers: { "token": webpass }}) From 4798308ccda7d300d1b62102d2f6c26e674fb3c9 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 3 Mar 2019 00:26:55 -0600 Subject: [PATCH 14/35] work on mail ui deletion of items --- .../default-plugins/pms/mailapi.py | 30 +++++++++-- .../static-data/default-plugins/pms/main.py | 17 ------ .../default-plugins/pms/sentboxdb.py | 16 +++++- onionr/static-data/www/mail/mail.js | 54 ++++++++++++++++--- 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/onionr/static-data/default-plugins/pms/mailapi.py b/onionr/static-data/default-plugins/pms/mailapi.py index 7ef49bb4..cdc77094 100644 --- a/onionr/static-data/default-plugins/pms/mailapi.py +++ b/onionr/static-data/default-plugins/pms/mailapi.py @@ -17,11 +17,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sys, os -from flask import Response, request, redirect, Blueprint +import sys, os, json +from flask import Response, request, redirect, Blueprint, abort import core sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) -import loadinbox +import loadinbox, sentboxdb flask_blueprint = Blueprint('mail', __name__) c = core.Core() @@ -29,7 +29,8 @@ kv = c.keyStore @flask_blueprint.route('/mail/deletemsg/', methods=['POST']) def mail_delete(block): - assert c._utils.validateHash(block) + if not c._utils.validateHash(block): + abort(504) existing = kv.get('deleted_mail') if existing is None: existing = [] @@ -40,4 +41,23 @@ def mail_delete(block): @flask_blueprint.route('/mail/getinbox') def list_inbox(): - return ','.join(loadinbox.load_inbox(c)) \ No newline at end of file + 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)): + if sentbox_list_copy[x]['hash'] in deleted: + sentbox_list.pop(x) + + ''' + hash_list = [] + for x in sentbox_list: + hash_list.append({x['hash']) + return ','.join(hash_list) + ''' + return json.dumps(sentbox_list) \ No newline at end of file diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py index 5af4a828..5c558a55 100755 --- a/onionr/static-data/default-plugins/pms/main.py +++ b/onionr/static-data/default-plugins/pms/main.py @@ -307,23 +307,6 @@ def on_insertblock(api, data={}): 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()) - keyStore = api.get_core().keyStore - if data['name'] == 'mail': - path = data['path'] - print(cmd) - 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): ''' diff --git a/onionr/static-data/default-plugins/pms/sentboxdb.py b/onionr/static-data/default-plugins/pms/sentboxdb.py index f00accb3..28a5de6f 100755 --- a/onionr/static-data/default-plugins/pms/sentboxdb.py +++ b/onionr/static-data/default-plugins/pms/sentboxdb.py @@ -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 diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index 81350907..1629f3d3 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -71,6 +71,7 @@ function setActiveTab(tabName){ threadPart.innerHTML = "" switch(tabName){ case 'inbox': + refreshPms() getInbox() break case 'sentbox': @@ -82,6 +83,17 @@ function setActiveTab(tabName){ } } +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 loadInboxEntries(bHash){ fetch('/getblockheader/' + bHash, { headers: { @@ -96,6 +108,7 @@ function loadInboxEntries(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']) @@ -119,6 +132,8 @@ function loadInboxEntries(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 = '()' } @@ -127,6 +142,7 @@ function loadInboxEntries(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) @@ -134,9 +150,17 @@ function loadInboxEntries(bHash){ entry.appendChild(dateStr) entry.classList.add('threadEntry') - entry.onclick = function(){ + 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)) } @@ -160,7 +184,7 @@ function getInbox(){ } function getSentbox(){ - fetch('/apipoints/mail/sentbox', { + fetch('/mail/getsentbox', { headers: { "token": webpass }}) @@ -180,16 +204,27 @@ function getSentbox(){ toLabel.innerText = 'To: ' var toEl = document.createElement('input') var preview = document.createElement('span') + var deleteBtn = document.createElement('button') + deleteBtn.classList.add('deleteBtn', 'dangerBtn') + deleteBtn.innerText = 'X' toEl.readOnly = true - toEl.value = resp[keys[i]][1] - preview.innerText = '(' + resp[keys[i]][2] + ')' + toEl.value = resp[i]['peer'] + 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]] + entryUsed = resp[i]['message'] entry.onclick = function(){ console.log(resp) - showSentboxWindow(toEl.value, entryUsed[0]) + if (! entry.target.classList.contains('deleteBtn')){ + showSentboxWindow(toEl.value, entryUsed) + } + } + deleteBtn.onclick = function(){ + entry.parentNode.removeChild(entry); + deleteMessage(entry.getAttribute('data-hash')) } threadPart.appendChild(entry) } @@ -203,6 +238,7 @@ function showSentboxWindow(to, content){ overlay('sentboxDisplay') } +function refreshPms(){ fetch('/mail/getinbox', { headers: { "token": webpass @@ -210,8 +246,8 @@ fetch('/mail/getinbox', { .then((resp) => resp.text()) // Transform the data into json .then(function(data) { pms = data.split(',') - setActiveTab('inbox') }) +} tabBtns.onclick = function(event){ var children = tabBtns.children @@ -273,4 +309,6 @@ fetch('/friends/list', { //friendSelectParent //alert(resp[keys[i]]['name']) } -}) \ No newline at end of file +}) + +setActiveTab('inbox') \ No newline at end of file From 45221291fa193df518f3697261ddf57e18974442 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 4 Mar 2019 13:03:35 -0600 Subject: [PATCH 15/35] work on friends and mail UI --- onionr/onionr.py | 6 +++--- onionr/static-data/default-plugins/pms/mailapi.py | 7 +------ onionr/static-data/www/friends/friends.js | 3 ++- onionr/static-data/www/friends/index.html | 2 +- onionr/static-data/www/mail/mail.js | 15 +++++++-------- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index 1a16742b..8e64214b 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -844,9 +844,9 @@ class Onionr: # count stats 'div2' : True, - 'Known Peers Count' : str(len(self.onionrCore.listPeers()) - 1), - 'Enabled Plugins Count' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(self.dataDir + 'plugins/'))), - 'Known Blocks Count' : str(totalBlocks), + 'Known Peers' : str(len(self.onionrCore.listPeers()) - 1), + 'Enabled Plugins' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(self.dataDir + 'plugins/'))), + 'Stored Blocks' : str(totalBlocks), 'Percent Blocks Signed' : str(round(100 * signedBlocks / max(totalBlocks, 1), 2)) + '%' } diff --git a/onionr/static-data/default-plugins/pms/mailapi.py b/onionr/static-data/default-plugins/pms/mailapi.py index cdc77094..31f6f18e 100644 --- a/onionr/static-data/default-plugins/pms/mailapi.py +++ b/onionr/static-data/default-plugins/pms/mailapi.py @@ -52,12 +52,7 @@ def list_sentbox(): deleted = [] for x in range(len(sentbox_list_copy)): if sentbox_list_copy[x]['hash'] in deleted: + x -= 1 sentbox_list.pop(x) - ''' - hash_list = [] - for x in sentbox_list: - hash_list.append({x['hash']) - return ','.join(hash_list) - ''' return json.dumps(sentbox_list) \ No newline at end of file diff --git a/onionr/static-data/www/friends/friends.js b/onionr/static-data/www/friends/friends.js index 68cc8661..c18da68b 100755 --- a/onionr/static-data/www/friends/friends.js +++ b/onionr/static-data/www/friends/friends.js @@ -33,8 +33,9 @@ addForm.onsubmit = function(){ 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}) + post_to_url('/friends/setinfo/' + friend.value + '/name', {'data': alias.value, 'token': webpass}) } }) diff --git a/onionr/static-data/www/friends/index.html b/onionr/static-data/www/friends/index.html index 8c1074fe..3d214274 100755 --- a/onionr/static-data/www/friends/index.html +++ b/onionr/static-data/www/friends/index.html @@ -18,7 +18,7 @@ Home

Friend Manager

- + diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index 1629f3d3..8a4b2057 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -57,8 +57,9 @@ function openThread(bHash, sender, date, sigBool, pubkey){ 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') @@ -72,7 +73,6 @@ function setActiveTab(tabName){ switch(tabName){ case 'inbox': refreshPms() - getInbox() break case 'sentbox': getSentbox() @@ -112,13 +112,12 @@ function loadInboxEntries(bHash){ 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('/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' } @@ -145,9 +144,9 @@ function loadInboxEntries(bHash){ 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(event){ @@ -246,6 +245,7 @@ fetch('/mail/getinbox', { .then((resp) => resp.text()) // Transform the data into json .then(function(data) { pms = data.split(',') + getInbox() }) } @@ -310,5 +310,4 @@ fetch('/friends/list', { //alert(resp[keys[i]]['name']) } }) - setActiveTab('inbox') \ No newline at end of file From 9b6553511baf7b39c815a7718602f9beb1d16603 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 4 Mar 2019 16:29:44 -0600 Subject: [PATCH 16/35] bug fixes --- onionr/communicator.py | 20 +++------ onionr/communicatorutils/onionrdaemontools.py | 10 ++++- onionr/communicatorutils/proxypicker.py | 25 +++++++++++ onionr/onionr.py | 4 +- .../__init__.py} | 0 .../static-data/default-plugins/cliui/main.py | 45 ++++++++++++------- 6 files changed, 70 insertions(+), 34 deletions(-) create mode 100644 onionr/communicatorutils/proxypicker.py rename onionr/{onionrfragment.py => onionrfragment/__init__.py} (100%) diff --git a/onionr/communicator.py b/onionr/communicator.py index 54a512ac..14817a07 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -19,15 +19,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid +import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid, binascii +from dependencies import secrets +from utils import networkmerger import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block from communicatorutils import onionrdaemontools import onionrsockets, onionr, onionrproofs -import binascii -from communicatorutils import onionrcommunicatortimers -from dependencies import secrets -from defusedxml import minidom -from utils import networkmerger +from communicatorutils import onionrcommunicatortimers, proxypicker OnionrCommunicatorTimers = onionrcommunicatortimers.OnionrCommunicatorTimers @@ -48,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 @@ -610,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) diff --git a/onionr/communicatorutils/onionrdaemontools.py b/onionr/communicatorutils/onionrdaemontools.py index 70b405f7..d2b9ef17 100755 --- a/onionr/communicatorutils/onionrdaemontools.py +++ b/onionr/communicatorutils/onionrdaemontools.py @@ -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: diff --git a/onionr/communicatorutils/proxypicker.py b/onionr/communicatorutils/proxypicker.py new file mode 100644 index 00000000..7e1d1e38 --- /dev/null +++ b/onionr/communicatorutils/proxypicker.py @@ -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 . +''' + +def pick_proxy(peer_address): + if peer_address.endswith('.onion'): + return 'tor' + elif peer_address.endswith('.i2p'): + return 'i2p' \ No newline at end of file diff --git a/onionr/onionr.py b/onionr/onionr.py index 8e64214b..7acd2564 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -42,9 +42,9 @@ except ImportError: raise Exception("You need the PySocks module (for use with socks5 proxy to use Tor)") ONIONR_TAGLINE = 'Anonymous P2P Platform - GPLv3 - https://Onionr.net' -ONIONR_VERSION = '0.5.0' # for debugging and stuff +ONIONR_VERSION = '0.0.0' # for debugging and stuff ONIONR_VERSION_TUPLE = tuple(ONIONR_VERSION.split('.')) # (MAJOR, MINOR, VERSION) -API_VERSION = '5' # increments of 1; only change when something fundamental about how the API works changes. This way other nodes know how to communicate without learning too much information about you. +API_VERSION = '0' # increments of 1; only change when something fundamental about how the API works changes. This way other nodes know how to communicate without learning too much information about you. class Onionr: def __init__(self): diff --git a/onionr/onionrfragment.py b/onionr/onionrfragment/__init__.py similarity index 100% rename from onionr/onionrfragment.py rename to onionr/onionrfragment/__init__.py diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index 59b59b0c..e1398a13 100755 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -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 From c262b67626758268dff411b24de36f8c8a22c64b Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 4 Mar 2019 21:16:33 -0600 Subject: [PATCH 17/35] fixed UI sentbox --- .../default-plugins/pms/mailapi.py | 3 +++ onionr/static-data/www/mail/mail.js | 21 ++++++++++++------- onionr/static-data/www/mail/sendmail.js | 4 +++- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/onionr/static-data/default-plugins/pms/mailapi.py b/onionr/static-data/default-plugins/pms/mailapi.py index 31f6f18e..766ae724 100644 --- a/onionr/static-data/default-plugins/pms/mailapi.py +++ b/onionr/static-data/default-plugins/pms/mailapi.py @@ -20,6 +20,7 @@ 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 @@ -54,5 +55,7 @@ def list_sentbox(): if sentbox_list_copy[x]['hash'] in deleted: x -= 1 sentbox_list.pop(x) + else: + sentbox_list[x]['name'] = contactmanager.ContactManager(c, sentbox_list[x]['peer'], saveUser=False).get_info('name') return json.dumps(sentbox_list) \ No newline at end of file diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index 8a4b2057..57c5fa40 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -191,36 +191,41 @@ 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 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[i]['peer'] + 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[i]['message'] - entry.onclick = function(){ + entry.onclick = (function(tree, el, msg) {return function() { console.log(resp) - if (! entry.target.classList.contains('deleteBtn')){ - showSentboxWindow(toEl.value, entryUsed) + if (! entry.classList.contains('deleteBtn')){ + showSentboxWindow(el.value, msg) } - } + };})(entry, toEl, message); + deleteBtn.onclick = function(){ entry.parentNode.removeChild(entry); deleteMessage(entry.getAttribute('data-hash')) diff --git a/onionr/static-data/www/mail/sendmail.js b/onionr/static-data/www/mail/sendmail.js index abece988..704574e6 100755 --- a/onionr/static-data/www/mail/sendmail.js +++ b/onionr/static-data/www/mail/sendmail.js @@ -27,6 +27,7 @@ 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, @@ -36,6 +37,8 @@ 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!') }) } @@ -51,7 +54,6 @@ sendForm.onsubmit = function(){ return false } } - sendMail(to.value, messageContent.value, subject.value) return false } From b74315f75acf393ef768badbdffcd7948b683839 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 4 Mar 2019 21:17:49 -0600 Subject: [PATCH 18/35] new bootstrap address --- onionr/static-data/bootstrap-nodes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/static-data/bootstrap-nodes.txt b/onionr/static-data/bootstrap-nodes.txt index 82cc2a2d..d009c6b8 100755 --- a/onionr/static-data/bootstrap-nodes.txt +++ b/onionr/static-data/bootstrap-nodes.txt @@ -1 +1 @@ -3xudvnmedfkkw6zisfrmm76ovrnmcil3hmah7kcxruv37glxizfxiuqd.onion \ No newline at end of file +gknkjxwjc3xhao6hcrtauipilhnbhtoiz6d4cgbdgy4fqzjdt76jwvad.onion From 456e3f68ad1624989e2c65292286cae897c9f374 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 5 Mar 2019 00:06:49 -0600 Subject: [PATCH 19/35] updated readme and whitepaper --- README.md | 44 ++++++++++++++++++++++++++++++++++---------- docs/onionr-1.png | Bin 0 -> 45130 bytes docs/onionr-2.png | Bin 0 -> 30790 bytes docs/onionr-3.png | Bin 0 -> 41028 bytes docs/whitepaper.md | 13 +++++++------ 5 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 docs/onionr-1.png create mode 100644 docs/onionr-2.png create mode 100644 docs/onionr-3.png diff --git a/README.md b/README.md index 7cc1a597..6ae2e9c8 100755 --- a/README.md +++ b/README.md @@ -31,15 +31,29 @@ Onionr can be used for mail, as a social network, instant messenger, file sharin ## Main Features -* [X] Fully p2p/decentralized, no trackers or other single points of failure -* [X] End to end encryption of user data -* [X] Optional non-encrypted blocks, useful for blog posts or public file sharing -* [X] Easy API system for integration to websites -* [X] Metadata analysis resistance -* [X] Transport agnosticism (no internet required) +* [X] 🌐 Fully p2p/decentralized, no trackers or other single points of failure +* [X] 🔒 End to end encryption of user data +* [X] 📢 Optional non-encrypted blocks, useful for blog posts or public file sharing +* [X] 👩🏾‍💻 Easy API system for integration to websites +* [X] 🕵️ Metadata analysis resistance and anonymity +* [X] 📡 Transport agnosticism (no internet required) **Onionr API and functionality is subject to non-backwards compatible change during pre-alpha development** +# Screenshots + +Node statistics page screenshot + +Node statistics + +Friend/contact manager screenshot + +Friend/contact manager + +Encrypted, metadata-masking mail application screenshot + +Encrypted, metadata-masking mail application. + # Install and Run on Linux The following applies to Ubuntu Bionic. Other distros may have different package or command names. @@ -54,17 +68,27 @@ The following applies to Ubuntu Bionic. Other distros may have different package Everyone is welcome to help out. Help is wanted for the following: * Development (Get in touch first) - * Creation of a lib for use from other languages and faster proof-of-work + * Creation of a shared lib for use from other languages and faster proof-of-work * Android and IOS development - * Windows and Mac support + * Windows and Mac support (already partially supported, testers needed) * General bug fixes and development of new features * Testing +* UI/UX design * Running stable nodes * Security review/audit * Automatic I2P setup -Bitcoin: [1onion55FXzm6h8KQw3zFw2igpHcV7LPq](bitcoin:1onion55FXzm6h8KQw3zFw2igpHcV7LPq) -USD: [Ko-Fi](https://www.ko-fi.com/beardogkf) +Contribute money: + +Donating at least $5 gets you cool Onionr stickers. Get in touch if you want them. + +Bitcoin: [1onion55FXzm6h8KQw3zFw2igpHcV7LPq](bitcoin:1onion55FXzm6h8KQw3zFw2igpHcV7LPq) (Contact us for privacy coins like Monero) + +USD (Card/Paypal): [Ko-Fi](https://www.ko-fi.com/beardogkf) + +## Contact + +beardog [ at ] mailbox.org ## Disclaimer diff --git a/docs/onionr-1.png b/docs/onionr-1.png new file mode 100644 index 0000000000000000000000000000000000000000..c498fe8fa1ce4a32c83eee4cda4527e505a7a4d5 GIT binary patch literal 45130 zcmeFZWmHwsyFQ9Z3DT&D2m;b5DUF0khje$BbRz;%0wP_~%?1%SjTm%FH;8n1*L}CY z|GDRUy5rt&=fmB@;U4U@)?9PV`S$xfD?~v~0tf3J78)8Fj^s-*B{Z~aAJNdRYGYi5 zU#`Eq#R*^697QCRF)%R3XXO8&p*=*C6nn1hma;bKs!e!!iN4ii@&WIm^>yR?D*LR= ze3j>`#h#J8yv5F12rVtG$3q%6NLxgc4$`M+$nuBc^~Zmwu6o9r2~o^ B4Q)tPxvQZd$Lil7zeV+zQL{or+Y`0Ef%<+R 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 Date: Tue, 5 Mar 2019 00:08:09 -0600 Subject: [PATCH 20/35] updated readme and whitepaper --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ae2e9c8..f22c3d89 100755 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Onionr can be used for mail, as a social network, instant messenger, file sharin * [X] 🌐 Fully p2p/decentralized, no trackers or other single points of failure * [X] 🔒 End to end encryption of user data * [X] 📢 Optional non-encrypted blocks, useful for blog posts or public file sharing -* [X] 👩🏾‍💻 Easy API system for integration to websites +* [X] 💻 Easy API system for integration to websites * [X] 🕵️ Metadata analysis resistance and anonymity * [X] 📡 Transport agnosticism (no internet required) From d4eb3f43efe3a1e248c328fecad3159172e7d324 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 5 Mar 2019 09:57:07 -0600 Subject: [PATCH 21/35] adjusted readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f22c3d89..f5b2681e 100755 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ Bitcoin: [1onion55FXzm6h8KQw3zFw2igpHcV7LPq](bitcoin:1onion55FXzm6h8KQw3zFw2igpH USD (Card/Paypal): [Ko-Fi](https://www.ko-fi.com/beardogkf) +Note: probably not tax deductible + ## Contact beardog [ at ] mailbox.org From 930d825ebce89dc4f07850df69c2379b3b9236a2 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 5 Mar 2019 11:11:49 -0600 Subject: [PATCH 22/35] fix makefile and broken sentbox --- Makefile | 3 +++ onionr/static-data/default-plugins/pms/mailapi.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4721e97c..f3ba33cb 100755 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +ONIONR_HOME ?= data +all:;: '$(ONIONR_HOME)' + PREFIX = /usr/local .DEFAULT_GOAL := setup diff --git a/onionr/static-data/default-plugins/pms/mailapi.py b/onionr/static-data/default-plugins/pms/mailapi.py index 766ae724..c8966112 100644 --- a/onionr/static-data/default-plugins/pms/mailapi.py +++ b/onionr/static-data/default-plugins/pms/mailapi.py @@ -51,11 +51,11 @@ def list_sentbox(): deleted = kv.get('deleted_mail') if deleted is None: deleted = [] - for x in range(len(sentbox_list_copy)): + 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[x]['peer'], saveUser=False).get_info('name') + sentbox_list[x]['name'] = contactmanager.ContactManager(c, sentbox_list_copy[x]['peer'], saveUser=False).get_info('name') return json.dumps(sentbox_list) \ No newline at end of file From ecad9ac207cbe4b9e229e5ab045ff9ccdb38f977 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 5 Mar 2019 12:07:13 -0600 Subject: [PATCH 23/35] False not false in keyboard interrupt handler --- onionr/netcontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index c415c1a9..b4053bbe 100755 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -162,7 +162,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) From 7b635c4fc95bcdc6195790adfdd7291a2d9c95a0 Mon Sep 17 00:00:00 2001 From: KF Date: Wed, 6 Mar 2019 16:39:46 -0600 Subject: [PATCH 24/35] fixed import error in plugins being slient, affecting plugins not being enabled on some platforms/versions --- onionr/onionrplugins.py | 2 ++ onionr/static-data/default-plugins/encrypt/main.py | 2 +- onionr/static-data/default-plugins/pms/main.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/onionr/onionrplugins.py b/onionr/onionrplugins.py index 198e58da..a7560957 100755 --- a/onionr/onionrplugins.py +++ b/onionr/onionrplugins.py @@ -72,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) diff --git a/onionr/static-data/default-plugins/encrypt/main.py b/onionr/static-data/default-plugins/encrypt/main.py index 7ea5b446..f917d72a 100755 --- a/onionr/static-data/default-plugins/encrypt/main.py +++ b/onionr/static-data/default-plugins/encrypt/main.py @@ -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 diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py index 5c558a55..363c1259 100755 --- a/onionr/static-data/default-plugins/pms/main.py +++ b/onionr/static-data/default-plugins/pms/main.py @@ -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 From 26f25f8fe42a3427ed707b0612e98f45443555b6 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 6 Mar 2019 22:58:21 -0600 Subject: [PATCH 25/35] do not save expired blocks --- onionr/netcontroller.py | 3 ++- onionr/onionr.py | 46 ++++++++++++++++++++--------------------- onionr/onionrutils.py | 1 + 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index b4053bbe..2898256c 100755 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -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() diff --git a/onionr/onionr.py b/onionr/onionr.py index 7acd2564..1d822515 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -75,6 +75,29 @@ class Onionr: logger.error('Tor is not installed') sys.exit(1) + # If data folder does not exist + if not data_exists: + if not os.path.exists(self.dataDir + 'blocks/'): + os.mkdir(self.dataDir + 'blocks/') + + # Copy default plugins into plugins folder + if not os.path.exists(plugins.get_plugins_folder()): + if os.path.exists('static-data/default-plugins/'): + names = [f for f in os.listdir("static-data/default-plugins/")] + shutil.copytree('static-data/default-plugins/', plugins.get_plugins_folder()) + + # Enable plugins + for name in names: + if not name in plugins.get_enabled_plugins(): + plugins.enable(name, self) + + for name in plugins.get_enabled_plugins(): + if not os.path.exists(plugins.get_plugin_data_folder(name)): + try: + os.mkdir(plugins.get_plugin_data_folder(name)) + except: + plugins.disable(name, onionr = self, stop_event = False) + self.communicatorInst = None self.onionrCore = core.Core() self.onionrCore.onionrInst = self @@ -90,29 +113,6 @@ class Onionr: self.debug = False # Whole application debugging - # If data folder does not exist - if not data_exists: - if not os.path.exists(self.dataDir + 'blocks/'): - os.mkdir(self.dataDir + 'blocks/') - - # Copy default plugins into plugins folder - if not os.path.exists(plugins.get_plugins_folder()): - if os.path.exists('static-data/default-plugins/'): - names = [f for f in os.listdir("static-data/default-plugins/") if not os.path.isfile(f)] - shutil.copytree('static-data/default-plugins/', plugins.get_plugins_folder()) - - # Enable plugins - for name in names: - if not name in plugins.get_enabled_plugins(): - plugins.enable(name, self) - - for name in plugins.get_enabled_plugins(): - if not os.path.exists(plugins.get_plugin_data_folder(name)): - try: - os.mkdir(plugins.get_plugin_data_folder(name)) - except: - plugins.disable(name, onionr = self, stop_event = False) - # Get configuration if type(config.get('client.webpassword')) is type(None): config.set('client.webpassword', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 23d265d7..a5cfdc84 100755 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -278,6 +278,7 @@ 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() From 4d2a1cab1ee45752249e4d18d5f405b2a758b19f Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 6 Mar 2019 23:43:32 -0600 Subject: [PATCH 26/35] use only one thread in proof of work by default, because of GIL --- onionr/onionrproofs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionr/onionrproofs.py b/onionr/onionrproofs.py index 81a189a9..040d810f 100755 --- a/onionr/onionrproofs.py +++ b/onionr/onionrproofs.py @@ -101,7 +101,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 +200,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 From e0a18e2adf22bf0d4aebce653da47ccaa0579963 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 7 Mar 2019 19:08:06 -0600 Subject: [PATCH 27/35] in process of refactoring onionr.py --- onionr/onionr.py | 414 +--------------------- onionr/onionrcommands/__init__.py | 155 ++++++++ onionr/onionrcommands/daemonlaunch.py | 105 ++++++ onionr/onionrcommands/filecommands.py | 24 ++ onionr/onionrcommands/onionrstatistics.py | 93 +++++ onionr/onionrcommands/pubkeymanager.py | 101 ++++++ 6 files changed, 491 insertions(+), 401 deletions(-) create mode 100644 onionr/onionrcommands/__init__.py create mode 100644 onionr/onionrcommands/daemonlaunch.py create mode 100644 onionr/onionrcommands/filecommands.py create mode 100644 onionr/onionrcommands/onionrstatistics.py create mode 100644 onionr/onionrcommands/pubkeymanager.py diff --git a/onionr/onionr.py b/onionr/onionr.py index 1d822515..8e56e8db 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -35,6 +35,7 @@ from netcontroller import NetController from onionrblockapi import Block import onionrproofs, onionrexceptions, communicator from onionrusers import onionrusers +import onionrcommands as commands # Many command definitions are here try: from urllib3.contrib.socks import SOCKSProxyManager @@ -128,126 +129,8 @@ class Onionr: if type(config.get('client.api_version')) is type(None): config.set('client.api_version', API_VERSION, savefile=True) - self.cmds = { - '': self.showHelpSuggestion, - 'help': self.showHelp, - 'version': self.version, - 'config': self.configure, - 'start': self.start, - 'stop': self.killDaemon, - 'status': self.showStats, - 'statistics': self.showStats, - 'stats': self.showStats, - 'details' : self.showDetails, - 'detail' : self.showDetails, - 'show-details' : self.showDetails, - 'show-detail' : self.showDetails, - 'showdetails' : self.showDetails, - 'showdetail' : self.showDetails, - 'get-details' : self.showDetails, - 'get-detail' : self.showDetails, - 'getdetails' : self.showDetails, - 'getdetail' : self.showDetails, - - 'enable-plugin': self.enablePlugin, - 'enplugin': self.enablePlugin, - 'enableplugin': self.enablePlugin, - 'enmod': self.enablePlugin, - 'disable-plugin': self.disablePlugin, - 'displugin': self.disablePlugin, - 'disableplugin': self.disablePlugin, - 'dismod': self.disablePlugin, - 'reload-plugin': self.reloadPlugin, - 'reloadplugin': self.reloadPlugin, - 'reload-plugins': self.reloadPlugin, - 'reloadplugins': self.reloadPlugin, - 'create-plugin': self.createPlugin, - 'createplugin': self.createPlugin, - 'plugin-create': self.createPlugin, - - 'listkeys': self.listKeys, - 'list-keys': self.listKeys, - - 'addpeer': self.addPeer, - 'add-peer': self.addPeer, - 'add-address': self.addAddress, - 'add-addr': self.addAddress, - 'addaddr': self.addAddress, - 'addaddress': self.addAddress, - 'list-peers': self.listPeers, - - 'blacklist-block': self.banBlock, - - 'add-file': self.addFile, - 'addfile': self.addFile, - 'addhtml': self.addWebpage, - 'add-html': self.addWebpage, - 'add-site': self.addWebpage, - 'addsite': self.addWebpage, - - 'openhome': self.openHome, - 'open-home': self.openHome, - - 'export-block': self.exportBlock, - 'exportblock': self.exportBlock, - - 'get-file': self.getFile, - 'getfile': self.getFile, - - 'listconn': self.listConn, - 'list-conn': self.listConn, - - 'import-blocks': self.onionrUtils.importNewBlocks, - 'importblocks': self.onionrUtils.importNewBlocks, - - 'introduce': self.onionrCore.introduceNode, - 'connect': self.addAddress, - 'pex': self.doPEX, - - 'getpassword': self.printWebPassword, - 'get-password': self.printWebPassword, - 'getpwd': self.printWebPassword, - 'get-pwd': self.printWebPassword, - 'getpass': self.printWebPassword, - 'get-pass': self.printWebPassword, - 'getpasswd': self.printWebPassword, - 'get-passwd': self.printWebPassword, - - 'friend': self.friendCmd, - 'add-id': self.addID, - 'change-id': self.changeID - } - - self.cmdhelp = { - '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' - } + self.cmds = commands.get_commands(self) + self.cmdhelp = commands.cmd_help # initialize plugins events.event('init', onionr = self, threaded = False) @@ -292,68 +175,16 @@ class Onionr: self.doExport(bHash) def showDetails(self): - details = { - 'Node Address' : self.get_hostname(), - 'Web Password' : self.getWebPassword(), - 'Public Key' : self.onionrCore._crypto.pubKey, - 'Human-readable Public Key' : self.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) - + commands.onionrstatistics.show_details(self) + def openHome(self): - try: - url = self.onionrUtils.getClientAPIServer() - except FileNotFoundError: - logger.error('Onionr seems to not be running (could not get api host)') - else: - url = 'http://%s/#%s' % (url, config.get('client.webpassword')) - print('If Onionr does not open automatically, use this URL:', url) - webbrowser.open_new_tab(url) + commands.open_home(self) def addID(self): - try: - sys.argv[2] - assert sys.argv[2] == 'true' - except (IndexError, AssertionError) as e: - newID = self.onionrCore._crypto.keyManager.addKey()[0] - else: - logger.warn('Deterministic keys require random and long passphrases.') - logger.warn('If a good 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: ' % (self.onionrCore._crypto.deterministicRequirement,)) - pass2 = getpass.getpass(prompt='Confirm entry: ') - if self.onionrCore._crypto.safeCompare(pass1, pass2): - try: - logger.info('Generating deterministic key. This can take a while.') - newID, privKey = self.onionrCore._crypto.generateDeterministic(pass1) - except onionrexceptions.PasswordStrengthError: - logger.error('Must use at least 25 characters.') - sys.exit(1) - else: - logger.error('Passwords do not match.') - sys.exit(1) - self.onionrCore._crypto.keyManager.addKey(pubKey=newID, - privKey=privKey) - logger.info('Added ID: %s' % (self.onionrUtils.bytesToStr(newID),)) + commands.pubkeymanager.add_ID(self) def changeID(self): - try: - key = sys.argv[2] - except IndexError: - logger.error('Specify pubkey to use') - else: - if self.onionrUtils.validatePubKey(key): - if key in self.onionrCore._crypto.keyManager.getPubkeyList(): - config.set('general.public_key', key) - config.save() - logger.info('Set active key to: %s' % (key,)) - logger.info('Restart Onionr if it is running.') - else: - logger.error('That key does not exist') - else: - logger.error('Invalid key %s' % (key,)) + commands.pubkeymanager.change_ID(self) def getCommands(self): return self.cmds @@ -362,40 +193,7 @@ class Onionr: '''List, add, or remove friend(s) Changes their peer DB entry. ''' - friend = '' - try: - # Get the friend command - action = sys.argv[2] - except IndexError: - logger.info('Syntax: friend add/remove/list [address]') - else: - action = action.lower() - if action == 'list': - # List out peers marked as our friend - for friend in onionrusers.OnionrUser.list_friends(self.onionrCore): - logger.info(friend.publicKey + ' - ' + friend.getName()) - elif action in ('add', 'remove'): - try: - friend = sys.argv[3] - if not self.onionrUtils.validatePubKey(friend): - raise onionrexceptions.InvalidPubkey('Public key is invalid') - if friend not in self.onionrCore.listPeers(): - raise onionrexceptions.KeyNotKnown - friend = onionrusers.OnionrUser(self.onionrCore, friend) - except IndexError: - logger.error('Friend ID is required.') - except onionrexceptions.KeyNotKnown: - self.onionrCore.addPeer(friend) - friend = onionrusers.OnionrUser(self.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]') + commands.pubkeymanager.friend_command(self) def deleteRunFiles(self): try: @@ -579,27 +377,6 @@ class Onionr: logger.warn("Unable to add address.") return - def addMessage(self, header="txt"): - ''' - Broadcasts a message to the Onionr network - ''' - - while True: - try: - messageToAdd = logger.readline('Broadcast message to network: ') - if len(messageToAdd) >= 1: - break - except KeyboardInterrupt: - return - - #addedHash = Block(type = 'txt', content = messageToAdd).save() - addedHash = self.onionrCore.insertBlock(messageToAdd) - if addedHash != None and addedHash != False and addedHash != "": - logger.info("Message inserted as as block %s" % addedHash) - else: - logger.error('Failed to insert block.', timestamp = False) - return - def enablePlugin(self): ''' Enables and starts the given plugin @@ -726,167 +503,19 @@ class Onionr: ''' 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=(self, self.debug, API_VERSION)).start() - Thread(target=api.PublicAPI, args=[self.getClientApi()]).start() - try: - time.sleep(0) - except KeyboardInterrupt: - logger.debug('Got keyboard interrupt, shutting down...') - time.sleep(1) - self.onionrUtils.localCommand('shutdown') - - apiHost = '' - while apiHost == '': - try: - with open(self.onionrCore.publicApiHostFile, 'r') as hostFile: - apiHost = hostFile.read() - except FileNotFoundError: - pass - time.sleep(0.5) - Onionr.setupConfig('data/', self = self) - - if self._developmentMode: - logger.warn('DEVELOPMENT MODE ENABLED (NOT RECOMMENDED)', timestamp = False) - net = NetController(config.get('client.public.port', 59497), apiServerIP=apiHost) - logger.debug('Tor is starting...') - if not net.startTor(): - self.onionrUtils.localCommand('shutdown') - sys.exit(1) - if len(net.myID) > 0 and config.get('general.security_level') == 0: - logger.debug('Started .onion service: %s' % (logger.colors.underline + net.myID)) - else: - logger.debug('.onion service disabled') - logger.debug('Using public key: %s' % (logger.colors.underline + self.onionrCore._crypto.pubKey)) - time.sleep(1) - - self.onionrCore.torPort = net.socksPort - communicatorThread = Thread(target=communicator.startCommunicator, args=(self, str(net.socksPort))) - communicatorThread.start() - - while self.communicatorInst is None: - time.sleep(0.1) - - # print nice header thing :) - if config.get('general.display_header', True): - self.header() - - # print out debug info - self.version(verbosity = 5, function = logger.debug) - logger.debug('Python version %s' % platform.python_version()) - - logger.debug('Started communicator.') - - events.event('daemon_start', onionr = self) - 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 self.communicatorInst.shutdown: - break - if self.killed: - break # Break out if sigterm for clean exit - except KeyboardInterrupt: - pass - finally: - self.onionrCore.daemonQueueAdd('shutdown') - self.onionrUtils.localCommand('shutdown') - net.killTor() - time.sleep(3) - self.deleteRunFiles() - return + commands.daemonlaunch.daemon(self) def killDaemon(self): ''' Shutdown the Onionr daemon ''' - - logger.warn('Stopping the running daemon...', timestamp = False) - try: - events.event('daemon_stop', onionr = self) - net = NetController(config.get('client.port', 59496)) - try: - self.onionrCore.daemonQueueAdd('shutdown') - except sqlite3.OperationalError: - pass - - net.killTor() - except Exception as e: - logger.error('Failed to shutdown daemon.', error = e, timestamp = False) - return + commands.daemonlaunch.kill_daemon(self) def showStats(self): ''' Displays statistics and exits ''' - - try: - # define stats messages here - totalBlocks = len(self.onionrCore.getBlockList()) - signedBlocks = len(Block.getBlocks(signed = True)) - messages = { - # info about local client - 'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if self.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(self.dataDir + 'blocks/')), - 'Total Plugin Size' : onionrutils.humanSize(onionrutils.size(self.dataDir + 'plugins/')), - 'Log File Size' : onionrutils.humanSize(onionrutils.size(self.dataDir + 'output.log')), - - # count stats - 'div2' : True, - 'Known Peers' : str(len(self.onionrCore.listPeers()) - 1), - 'Enabled Plugins' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(self.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 = self.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_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) - - return + commands.onionrstatistics.show_stats(self) def showHelp(self, command = None): ''' @@ -967,24 +596,7 @@ class Onionr: ''' 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 = self.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 ' % sys.argv[0], timestamp = False) + commands.filecommands.add_file(self, singleBlock, blockType) def setupConfig(dataDir, self = None): data_exists = os.path.exists(dataDir) diff --git a/onionr/onionrcommands/__init__.py b/onionr/onionrcommands/__init__.py new file mode 100644 index 00000000..b631f31b --- /dev/null +++ b/onionr/onionrcommands/__init__.py @@ -0,0 +1,155 @@ +''' + 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 . +''' + +import webbrowser +import logger +from . import pubkeymanager, onionrstatistics, daemonlaunch, filecommands + +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, 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, + 'connect': onionr_inst.addAddress, + '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' + } \ No newline at end of file diff --git a/onionr/onionrcommands/daemonlaunch.py b/onionr/onionrcommands/daemonlaunch.py new file mode 100644 index 00000000..4ee4c26f --- /dev/null +++ b/onionr/onionrcommands/daemonlaunch.py @@ -0,0 +1,105 @@ +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 \ No newline at end of file diff --git a/onionr/onionrcommands/filecommands.py b/onionr/onionrcommands/filecommands.py new file mode 100644 index 00000000..e059ebb5 --- /dev/null +++ b/onionr/onionrcommands/filecommands.py @@ -0,0 +1,24 @@ +import base64, sys, os +import logger +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 ' % sys.argv[0], timestamp = False) \ No newline at end of file diff --git a/onionr/onionrcommands/onionrstatistics.py b/onionr/onionrcommands/onionrstatistics.py new file mode 100644 index 00000000..beb6790d --- /dev/null +++ b/onionr/onionrcommands/onionrstatistics.py @@ -0,0 +1,93 @@ +''' + 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 . +''' +import os +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) \ No newline at end of file diff --git a/onionr/onionrcommands/pubkeymanager.py b/onionr/onionrcommands/pubkeymanager.py new file mode 100644 index 00000000..941bbb98 --- /dev/null +++ b/onionr/onionrcommands/pubkeymanager.py @@ -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 . +''' + +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]') \ No newline at end of file From 236edac25795892c1f939a4850d73d39fa549e6b Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 8 Mar 2019 00:30:14 -0600 Subject: [PATCH 28/35] more refactoring --- onionr/onionr.py | 70 +++--------------------------------------------- 1 file changed, 4 insertions(+), 66 deletions(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index 8e56e8db..58dd45c5 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -236,21 +236,7 @@ class Onionr: return def listConn(self): - randID = str(uuid.uuid4()) - self.onionrCore.daemonQueueAdd('connectedPeers', responseID=randID) - while True: - try: - time.sleep(3) - peers = self.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 + commands.onionrstatistics.show_peers(self) def listPeers(self): logger.info('Peer transport address list:') @@ -294,7 +280,6 @@ class Onionr: logger.info(logger.colors.bold + 'Get a value: ' + logger.colors.reset + sys.argv[0] + ' ' + sys.argv[1] + ' ') logger.info(logger.colors.bold + 'Set a value: ' + logger.colors.reset + sys.argv[0] + ' ' + sys.argv[1] + ' ') - def execute(self, argument): ''' Executes a command @@ -475,22 +460,8 @@ class Onionr: ''' Starts the Onionr daemon ''' + commands.daemonlaunch.start(self, input, override) - 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 self.debug and not self._developmentMode: - lockFile = open('.onionr-lock', 'w') - lockFile.write('') - lockFile.close() - self.running = True - self.daemon() - self.running = False - if not self.debug and not self._developmentMode: - try: - os.remove('.onionr-lock') - except FileNotFoundError: - pass def setClientAPIInst(self, inst): self.clientAPIInst = inst @@ -521,22 +492,7 @@ class Onionr: ''' Show help for Onionr ''' - - helpmenu = self.getHelp() - - if command is None and len(sys.argv) >= 3: - for cmd in sys.argv[2:]: - self.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: - self.version(0) - for command, helpmessage in helpmenu.items(): - self.showHelp(command) - return + commands.show_help(self, command) def get_hostname(self): try: @@ -566,25 +522,7 @@ class Onionr: ''' 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 ')) - else: - logger.info(fileName) - - contents = None - if os.path.exists(fileName): - logger.error("File already exists") - return - if not self.onionrUtils.validateHash(bHash): - logger.error('Block hash is invalid') - return - - with open(fileName, 'wb') as myFile: - myFile.write(base64.b64decode(Block(bHash, core=self.onionrCore).bcontent)) - return + commands.filecommands.getFile(self) def addWebpage(self): ''' From 1562848999186ab7efba708c4cb421f0fbc51345 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 8 Mar 2019 19:57:44 -0600 Subject: [PATCH 29/35] more refactoring and secured requirements.txt --- README.md | 2 +- onionr/onionr.py | 274 +++++----------------- onionr/onionrcommands/__init__.py | 23 +- onionr/onionrcommands/daemonlaunch.py | 19 +- onionr/onionrcommands/filecommands.py | 27 ++- onionr/onionrcommands/keyadders.py | 31 +++ onionr/onionrcommands/onionrstatistics.py | 21 +- onionr/onionrcommands/plugincommands.py | 68 ++++++ onionr/onionrproofs.py | 6 +- onionr/setupconfig.py | 69 ++++++ requirements.in | 9 + requirements.txt | 205 +++++++++++++++- 12 files changed, 513 insertions(+), 241 deletions(-) create mode 100644 onionr/onionrcommands/keyadders.py create mode 100644 onionr/onionrcommands/plugincommands.py create mode 100644 onionr/setupconfig.py create mode 100755 requirements.in mode change 100755 => 100644 requirements.txt diff --git a/README.md b/README.md index f5b2681e..3a4ab52d 100755 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Encrypted, metadata-masking mail application. The following applies to Ubuntu Bionic. Other distros may have different package or command names. -* Have python3.5+, python3-pip, Tor (daemon, not browser) installed (python3-dev recommended) +* Have python3.6+, python3-pip, Tor (daemon, not browser) installed (python3-dev recommended) * Clone the git repo: `$ git clone https://gitlab.com/beardog/onionr` * cd into install direction: `$ cd onionr/` * Install the Python dependencies ([virtualenv strongly recommended](https://virtualenv.pypa.io/en/stable/userguide/)): `$ pip3 install -r requirements.txt` diff --git a/onionr/onionr.py b/onionr/onionr.py index 58dd45c5..fb9d7f20 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -33,7 +33,7 @@ import onionrutils import netcontroller, onionrstorage from netcontroller import NetController from onionrblockapi import Block -import onionrproofs, onionrexceptions, communicator +import onionrproofs, onionrexceptions, communicator, setupconfig from onionrusers import onionrusers import onionrcommands as commands # Many command definitions are here @@ -148,9 +148,15 @@ class Onionr: def exitSigterm(self, signum, frame): self.killed = True - ''' - THIS SECTION HANDLES THE COMMANDS - ''' + def setupConfig(dataDir, self = None): + setupconfig.setup_config(dataDir, self) + + def header(self, message = logger.colors.fg.pink + logger.colors.bold + 'Onionr' + logger.colors.reset + logger.colors.fg.pink + ' has started.'): + if os.path.exists('static-data/header.txt') and logger.get_level() <= logger.LEVEL_INFO: + with open('static-data/header.txt', 'rb') as file: + # only to stdout, not file or log or anything + sys.stderr.write(file.read().decode().replace('P', logger.colors.fg.pink).replace('W', logger.colors.reset + logger.colors.bold).replace('G', logger.colors.fg.green).replace('\n', logger.colors.reset + '\n').replace('B', logger.colors.bold).replace('A', '%s' % API_VERSION).replace('V', ONIONR_VERSION)) + logger.info(logger.colors.fg.lightgreen + '-> ' + str(message) + logger.colors.reset + logger.colors.fg.lightgreen + ' <-\n') def doExport(self, bHash): exportDir = self.dataDir + 'block-export/' @@ -163,6 +169,44 @@ class Onionr: with open('%s/%s.dat' % (exportDir, bHash), 'wb') as exportFile: exportFile.write(data) + def deleteRunFiles(self): + try: + os.remove(self.onionrCore.publicApiHostFile) + except FileNotFoundError: + pass + try: + os.remove(self.onionrCore.privateApiHostFile) + except FileNotFoundError: + pass + + def get_hostname(self): + try: + with open('./' + self.dataDir + 'hs/hostname', 'r') as hostname: + return hostname.read().strip() + except FileNotFoundError: + return "Not Generated" + except Exception: + return None + + def getConsoleWidth(self): + ''' + Returns an integer, the width of the terminal/cmd window + ''' + + columns = 80 + + try: + columns = int(os.popen('stty size', 'r').read().split()[1]) + except: + # if it errors, it's probably windows, so default to 80. + pass + + return columns + + ''' + THIS SECTION HANDLES THE COMMANDS + ''' + def exportBlock(self): exportDir = self.dataDir + 'block-export/' try: @@ -195,26 +239,6 @@ class Onionr: ''' commands.pubkeymanager.friend_command(self) - def deleteRunFiles(self): - try: - os.remove(self.onionrCore.publicApiHostFile) - except FileNotFoundError: - pass - try: - os.remove(self.onionrCore.privateApiHostFile) - except FileNotFoundError: - pass - - def deleteRunFiles(self): - try: - os.remove(self.onionrCore.publicApiHostFile) - except FileNotFoundError: - pass - try: - os.remove(self.onionrCore.privateApiHostFile) - except FileNotFoundError: - pass - def banBlock(self): try: ban = sys.argv[2] @@ -233,7 +257,6 @@ class Onionr: logger.warn('That block is already blacklisted') else: logger.error('Invalid block hash') - return def listConn(self): commands.onionrstatistics.show_peers(self) @@ -293,12 +316,6 @@ class Onionr: command = commands.get(argument, self.notFound) command() - return - - ''' - THIS SECTION DEFINES THE COMMANDS - ''' - def version(self, verbosity = 5, function = logger.info): ''' Displays the Onionr version @@ -310,8 +327,6 @@ class Onionr: if verbosity >= 2: function('Running on %s %s' % (platform.platform(), platform.release())) - return - def doPEX(self): '''make communicator do pex''' logger.info('Sending pex to command queue...') @@ -321,126 +336,43 @@ class Onionr: ''' Displays a list of keys (used to be called peers) (?) ''' - logger.info('%sPublic keys in database: \n%s%s' % (logger.colors.fg.lightgreen, logger.colors.fg.green, '\n'.join(self.onionrCore.listPeers()))) def addPeer(self): ''' Adds a peer (?) ''' - try: - newPeer = sys.argv[2] - except: - pass - else: - if self.onionrUtils.hasKey(newPeer): - logger.info('We already have that key') - return - logger.info("Adding peer: " + logger.colors.underline + newPeer) - try: - if self.onionrCore.addPeer(newPeer): - logger.info('Successfully added key') - except AssertionError: - logger.error('Failed to add key') - return + commands.keyadders.add_peer(self) def addAddress(self): ''' Adds a Onionr node address ''' - - try: - newAddress = sys.argv[2] - newAddress = newAddress.replace('http:', '').replace('/', '') - except: - pass - else: - logger.info("Adding address: " + logger.colors.underline + newAddress) - if self.onionrCore.addAddress(newAddress): - logger.info("Successfully added address.") - else: - logger.warn("Unable to add address.") - return + commands.keyadders.add_address(self) def enablePlugin(self): ''' Enables and starts the given plugin ''' - - if len(sys.argv) >= 3: - plugin_name = sys.argv[2] - logger.info('Enabling plugin "%s"...' % plugin_name) - plugins.enable(plugin_name, self) - else: - logger.info('%s %s ' % (sys.argv[0], sys.argv[1])) - - return + commands.plugincommands.enable_plugin(self) def disablePlugin(self): ''' Disables and stops the given plugin ''' - - if len(sys.argv) >= 3: - plugin_name = sys.argv[2] - logger.info('Disabling plugin "%s"...' % plugin_name) - plugins.disable(plugin_name, self) - else: - logger.info('%s %s ' % (sys.argv[0], sys.argv[1])) - - return + commands.plugincommands.disable_plugin(self) def reloadPlugin(self): ''' 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, self) - plugins.start(plugin_name, self) - else: - logger.info('Reloading all plugins...') - plugins.reload(self) - - return + commands.plugincommands.reload_plugin(self) def createPlugin(self): ''' 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, self) - 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 ' % (sys.argv[0], sys.argv[1])) - - return + commands.plugincommands.create_plugin(self) def notFound(self): ''' @@ -494,30 +426,6 @@ class Onionr: ''' commands.show_help(self, command) - def get_hostname(self): - try: - with open('./' + self.dataDir + 'hs/hostname', 'r') as hostname: - return hostname.read().strip() - except FileNotFoundError: - return "Not Generated" - except Exception: - return None - - def getConsoleWidth(self): - ''' - Returns an integer, the width of the terminal/cmd window - ''' - - columns = 80 - - try: - columns = int(os.popen('stty size', 'r').read().split()[1]) - except: - # if it errors, it's probably windows, so default to 80. - pass - - return columns - def getFile(self): ''' Get a file from onionr blocks @@ -536,79 +444,5 @@ class Onionr: ''' commands.filecommands.add_file(self, singleBlock, blockType) - def setupConfig(dataDir, self = 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 self is None: - if str(config.get('general.dev_mode', True)).lower() == 'true': - self._developmentMode = True - logger.set_level(logger.LEVEL_DEBUG) - else: - self._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 - - def header(self, message = logger.colors.fg.pink + logger.colors.bold + 'Onionr' + logger.colors.reset + logger.colors.fg.pink + ' has started.'): - if os.path.exists('static-data/header.txt') and logger.get_level() <= logger.LEVEL_INFO: - with open('static-data/header.txt', 'rb') as file: - # only to stdout, not file or log or anything - sys.stderr.write(file.read().decode().replace('P', logger.colors.fg.pink).replace('W', logger.colors.reset + logger.colors.bold).replace('G', logger.colors.fg.green).replace('\n', logger.colors.reset + '\n').replace('B', logger.colors.bold).replace('A', '%s' % API_VERSION).replace('V', ONIONR_VERSION)) - logger.info(logger.colors.fg.lightgreen + '-> ' + str(message) + logger.colors.reset + logger.colors.fg.lightgreen + ' <-\n') - if __name__ == "__main__": Onionr() diff --git a/onionr/onionrcommands/__init__.py b/onionr/onionrcommands/__init__.py index b631f31b..35de3107 100644 --- a/onionr/onionrcommands/__init__.py +++ b/onionr/onionrcommands/__init__.py @@ -18,9 +18,26 @@ along with this program. If not, see . ''' -import webbrowser +import webbrowser, sys import logger -from . import pubkeymanager, onionrstatistics, daemonlaunch, filecommands +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: @@ -28,7 +45,7 @@ def open_home(o_inst): except FileNotFoundError: logger.error('Onionr seems to not be running (could not get api host)') else: - url = 'http://%s/#%s' % (url, config.get('client.webpassword')) + 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) diff --git a/onionr/onionrcommands/daemonlaunch.py b/onionr/onionrcommands/daemonlaunch.py index 4ee4c26f..b7452a46 100644 --- a/onionr/onionrcommands/daemonlaunch.py +++ b/onionr/onionrcommands/daemonlaunch.py @@ -102,4 +102,21 @@ def kill_daemon(o_inst): net.killTor() except Exception as e: logger.error('Failed to shutdown daemon.', error = e, timestamp = False) - return \ No newline at end of file + 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 \ No newline at end of file diff --git a/onionr/onionrcommands/filecommands.py b/onionr/onionrcommands/filecommands.py index e059ebb5..f9d05f01 100644 --- a/onionr/onionrcommands/filecommands.py +++ b/onionr/onionrcommands/filecommands.py @@ -1,5 +1,6 @@ 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 @@ -21,4 +22,28 @@ def add_file(o_inst, singleBlock=False, blockType='bin'): except: logger.error('Failed to save file in block.', timestamp = False) else: - logger.error('%s add-file ' % sys.argv[0], timestamp = False) \ No newline at end of file + logger.error('%s add-file ' % 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 ')) + 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 \ No newline at end of file diff --git a/onionr/onionrcommands/keyadders.py b/onionr/onionrcommands/keyadders.py new file mode 100644 index 00000000..5004afb3 --- /dev/null +++ b/onionr/onionrcommands/keyadders.py @@ -0,0 +1,31 @@ +import sys +import logger + +def add_peer(o_inst): + try: + newPeer = sys.argv[2] + except: + 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: + pass + else: + logger.info("Adding address: " + logger.colors.underline + newAddress) + if self.onionrCore.addAddress(newAddress): + logger.info("Successfully added address.") + else: + logger.warn("Unable to add address.") \ No newline at end of file diff --git a/onionr/onionrcommands/onionrstatistics.py b/onionr/onionrcommands/onionrstatistics.py index beb6790d..04264655 100644 --- a/onionr/onionrcommands/onionrstatistics.py +++ b/onionr/onionrcommands/onionrstatistics.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import os +import os, uuid, time import logger, onionrutils from onionrblockapi import Block import onionr @@ -90,4 +90,21 @@ def show_details(o_inst): } 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) \ No newline at end of file + 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 \ No newline at end of file diff --git a/onionr/onionrcommands/plugincommands.py b/onionr/onionrcommands/plugincommands.py new file mode 100644 index 00000000..5978f039 --- /dev/null +++ b/onionr/onionrcommands/plugincommands.py @@ -0,0 +1,68 @@ +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 ' % (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 ' % (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 ' % (sys.argv[0], sys.argv[1])) \ No newline at end of file diff --git a/onionr/onionrproofs.py b/onionr/onionrproofs.py index 040d810f..496573d0 100755 --- a/onionr/onionrproofs.py +++ b/onionr/onionrproofs.py @@ -17,10 +17,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' - -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 diff --git a/onionr/setupconfig.py b/onionr/setupconfig.py new file mode 100644 index 00000000..1229faeb --- /dev/null +++ b/onionr/setupconfig.py @@ -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 \ No newline at end of file diff --git a/requirements.in b/requirements.in new file mode 100755 index 00000000..6b1cb663 --- /dev/null +++ b/requirements.in @@ -0,0 +1,9 @@ +urllib3==1.23 +requests==2.20.0 +PyNaCl==1.2.1 +gevent==1.3.6 +defusedxml==0.5.0 +Flask==1.0.2 +PySocks==1.6.8 +stem==1.6.0 +deadsimplekv==0.0.1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt old mode 100755 new mode 100644 index 6b1cb663..b2c93d7c --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,196 @@ -urllib3==1.23 -requests==2.20.0 -PyNaCl==1.2.1 -gevent==1.3.6 -defusedxml==0.5.0 -Flask==1.0.2 -PySocks==1.6.8 -stem==1.6.0 -deadsimplekv==0.0.1 \ No newline at end of file +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --generate-hashes --output-file requirements.txt requirements.in +# +certifi==2018.11.29 \ + --hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \ + --hash=sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033 \ + # via requests +cffi==1.12.2 \ + --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ + --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ + --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ + --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ + --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ + --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ + --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ + --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ + --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ + --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ + --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ + --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ + --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ + --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ + --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ + --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ + --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ + --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ + --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ + --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ + --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ + --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ + --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ + --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ + --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ + --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ + --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ + --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 \ + # via pynacl +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ + # via requests +click==7.0 \ + --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \ + --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 \ + # via flask +deadsimplekv==0.0.1 \ + --hash=sha256:1bb78e4feb01d975e89e81cac7b0141666a14ebefa06fffc1c2d86c3308e3930 +defusedxml==0.5.0 \ + --hash=sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4 \ + --hash=sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20 +flask==1.0.2 \ + --hash=sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48 \ + --hash=sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05 +gevent==1.3.6 \ + --hash=sha256:03d03ea4f33e535b0a99b6be2696fde9c7417022b8ee67fb15b78f47672a0b86 \ + --hash=sha256:13a0e74432ede9efdad5fd9aed73bd30bcfc73ddcbffe719849210f4546db833 \ + --hash=sha256:23d623b41a431e04a9410b046520778517f5304dfbb9bfd3b1bbcc722eeaeea5 \ + --hash=sha256:2f82d8b4d09285ca4aef34ae5c093ccf966da90e7db3bd34764ffb014c8bfa68 \ + --hash=sha256:3223eb697d819d73dedc9a55b3dfa0cc1931e6459df4f0bf83c7c27ca256a3bd \ + --hash=sha256:3c00ade4ae707dd6a17d6d56ebac689dc56719b83389f9aeb6a10b1e01326177 \ + --hash=sha256:652bdd59afb330ad95550bda6864b87876e977aa4e48b9216235d932368e1987 \ + --hash=sha256:7b413c391e8ad6607b7f7540d698a94349abd64e4935184c595f7cdcc69904c6 \ + --hash=sha256:7feaf556fe2dc94340b603a3bfb00fbb526aaafcb8d804960939244ace4a262f \ + --hash=sha256:810ae07c1baee83cb3d54f7dca236803554659dc00ef662ac962e4e4fd3e79bb \ + --hash=sha256:86fa642228b8fc6a8fa268efab20440bb26599d28814e8dcd99af5dc92da10d7 \ + --hash=sha256:a9a0a61f8dc652b3dd5dc8b9f5f9ace5d2f91f5e81f368e9ef180c8eec968234 \ + --hash=sha256:ac3d258521b1056acb922b3aa77031a64888bb8cda1f7f6f370692cf3e224761 \ + --hash=sha256:af7b0d16541dea42f1eceac4a02815ea3ebd8fe1eb6fc714c81ab1842ec259d4 \ + --hash=sha256:bafef5a426473b52648c25d0ff9027aa8806982b57f8bc03abcc5f4669bfe19f \ + --hash=sha256:bc31cdec2e584106c026a4fd24f800cb575ea8ebfcce7974b630b65d61cf36df \ + --hash=sha256:cc42af305cb7bf1766b0084011520a81e56315dcc5b7662c209ef71a00764634 \ + --hash=sha256:e01223b43b2e9d92733ab9953038c7a99b9c3cdb32dc865b9ce94f03a2199f96 \ + --hash=sha256:e57f9d267b45ef9e3eb0e234307faaffa5a79cdb1477afa1befbf04de0cd8cbe \ + --hash=sha256:e9e2942704f7fe75064ef0bc17ba46b097a57ec0e70eca1d790d5a3edb691628 \ + --hash=sha256:f2ca6fc669def8e622b4a10809f6f6a4b6a822a1cc1175b89ad8eb34235aaa2e \ + --hash=sha256:f456a6321f0955e802e305946ce7e7d672a7da313417ea4b4add6809630d3b0e \ + --hash=sha256:ff8e09696a8c9100b1c88066ee44b50fbbea367ae91d830910561c902d1e7f3c +greenlet==0.4.15 \ + --hash=sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0 \ + --hash=sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28 \ + --hash=sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8 \ + --hash=sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304 \ + --hash=sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0 \ + --hash=sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214 \ + --hash=sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043 \ + --hash=sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6 \ + --hash=sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625 \ + --hash=sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc \ + --hash=sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638 \ + --hash=sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163 \ + --hash=sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4 \ + --hash=sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490 \ + --hash=sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248 \ + --hash=sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939 \ + --hash=sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87 \ + --hash=sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720 \ + --hash=sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656 \ + # via gevent +idna==2.7 \ + --hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e \ + --hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16 \ + # via requests +itsdangerous==1.1.0 \ + --hash=sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19 \ + --hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749 \ + # via flask +jinja2==2.10 \ + --hash=sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd \ + --hash=sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4 \ + # via flask +markupsafe==1.1.1 \ + --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ + --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ + --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ + --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ + --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ + --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ + --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ + --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ + --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ + --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ + --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ + --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ + --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ + --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ + --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ + --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ + --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ + --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ + --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ + --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ + --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ + --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ + --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ + --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ + --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ + --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ + --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ + --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ + # via jinja2 +pycparser==2.19 \ + --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 \ + # via cffi +pynacl==1.2.1 \ + --hash=sha256:04e30e5bdeeb2d5b34107f28cd2f5bbfdc6c616f3be88fc6f53582ff1669eeca \ + --hash=sha256:0bfa0d94d2be6874e40f896e0a67e290749151e7de767c5aefbad1121cad7512 \ + --hash=sha256:11aa4e141b2456ce5cecc19c130e970793fa3a2c2e6fbb8ad65b28f35aa9e6b6 \ + --hash=sha256:13bdc1fe084ff9ac7653ae5a924cae03bf4bb07c6667c9eb5b6eb3c570220776 \ + --hash=sha256:14339dc233e7a9dda80a3800e64e7ff89d0878ba23360eea24f1af1b13772cac \ + --hash=sha256:1d33e775fab3f383167afb20b9927aaf4961b953d76eeb271a5703a6d756b65b \ + --hash=sha256:2a42b2399d0428619e58dac7734838102d35f6dcdee149e0088823629bf99fbb \ + --hash=sha256:2dce05ac8b3c37b9e2f65eab56c544885607394753e9613fd159d5e2045c2d98 \ + --hash=sha256:63cfccdc6217edcaa48369191ae4dca0c390af3c74f23c619e954973035948cd \ + --hash=sha256:6453b0dae593163ffc6db6f9c9c1597d35c650598e2c39c0590d1757207a1ac2 \ + --hash=sha256:73a5a96fb5fbf2215beee2353a128d382dbca83f5341f0d3c750877a236569ef \ + --hash=sha256:8abb4ef79161a5f58848b30ab6fb98d8c466da21fdd65558ce1d7afc02c70b5f \ + --hash=sha256:8ac1167195b32a8755de06efd5b2d2fe76fc864517dab66aaf65662cc59e1988 \ + --hash=sha256:8f505f42f659012794414fa57c498404e64db78f1d98dfd40e318c569f3c783b \ + --hash=sha256:9c8a06556918ee8e3ab48c65574f318f5a0a4d31437fc135da7ee9d4f9080415 \ + --hash=sha256:a1e25fc5650cf64f01c9e435033e53a4aca9de30eb9929d099f3bb078e18f8f2 \ + --hash=sha256:be71cd5fce04061e1f3d39597f93619c80cdd3558a6c9ba99a546f144a8d8101 \ + --hash=sha256:c5b1a7a680218dee9da0f1b5e24072c46b3c275d35712bc1d505b85bb03441c0 \ + --hash=sha256:cb785db1a9468841a1265c9215c60fe5d7af2fb1b209e3316a152704607fc582 \ + --hash=sha256:cf6877124ae6a0698404e169b3ba534542cfbc43f939d46b927d956daf0a373a \ + --hash=sha256:d0eb5b2795b7ee2cbcfcadacbe95a13afbda048a262bd369da9904fecb568975 \ + --hash=sha256:d3a934e2b9f20abac009d5b6951067cfb5486889cb913192b4d8288b216842f1 \ + --hash=sha256:d795f506bcc9463efb5ebb0f65ed77921dcc9e0a50499dedd89f208445de9ecb \ + --hash=sha256:d8aaf7e5d6b0e0ef7d6dbf7abeb75085713d0100b4eb1a4e4e857de76d77ac45 \ + --hash=sha256:de2aaca8386cf4d70f1796352f2346f48ddb0bed61dc43a3ce773ba12e064031 \ + --hash=sha256:e0d38fa0a75f65f556fb912f2c6790d1fa29b7dd27a1d9cc5591b281321eaaa9 \ + --hash=sha256:eb2acabbd487a46b38540a819ef67e477a674481f84a82a7ba2234b9ba46f752 \ + --hash=sha256:eeee629828d0eb4f6d98ac41e9a3a6461d114d1d0aa111a8931c049359298da0 \ + --hash=sha256:f5836463a3c0cca300295b229b6c7003c415a9d11f8f9288ddbd728e2746524c \ + --hash=sha256:f5ce9e26d25eb0b2d96f3ef0ad70e1d3ae89b5d60255c462252a3e456a48c053 \ + --hash=sha256:fabf73d5d0286f9e078774f3435601d2735c94ce9e514ac4fb945701edead7e4 +pysocks==1.6.8 \ + --hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672 +requests==2.20.0 \ + --hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \ + --hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279 +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 \ + # via pynacl +stem==1.6.0 \ + --hash=sha256:d7fe1fb13ed5a94d610b5ad77e9f1b3404db0ca0586ded7a34afd323e3b849ed +urllib3==1.23 \ + --hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \ + --hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5 +werkzeug==0.14.1 \ + --hash=sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c \ + --hash=sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b \ + # via flask From 137d6f39dd5fe81d73f1e481500a6b85ab577577 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 9 Mar 2019 00:37:57 -0600 Subject: [PATCH 30/35] refactoring, work on friends ui, bug fixes and whitepaper work --- .gitmodules | 0 README.md | 8 ++++++-- docs/whitepaper.md | 14 +++++++------- onionr/onionr.py | 2 +- onionr/onionrcommands/__init__.py | 1 - onionr/onionrcommands/daemonlaunch.py | 20 ++++++++++++++++++++ onionr/onionrcommands/keyadders.py | 22 +++++++++++++++++++--- onionr/onionrcommands/plugincommands.py | 20 ++++++++++++++++++++ onionr/onionrutils.py | 4 ++++ onionr/static-data/www/friends/friends.js | 3 +++ onionr/static-data/www/friends/index.html | 6 ++++++ onionr/static-data/www/friends/style.css | 10 ++++++++++ 12 files changed, 96 insertions(+), 14 deletions(-) delete mode 100755 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100755 index e69de29b..00000000 diff --git a/README.md b/README.md index 3a4ab52d..119f5567 100755 --- a/README.md +++ b/README.md @@ -61,7 +61,9 @@ The following applies to Ubuntu Bionic. Other distros may have different package * Have python3.6+, python3-pip, Tor (daemon, not browser) installed (python3-dev recommended) * Clone the git repo: `$ git clone https://gitlab.com/beardog/onionr` * cd into install direction: `$ cd onionr/` -* Install the Python dependencies ([virtualenv strongly recommended](https://virtualenv.pypa.io/en/stable/userguide/)): `$ pip3 install -r requirements.txt` +* Install the Python dependencies ([virtualenv strongly recommended](https://virtualenv.pypa.io/en/stable/userguide/)): `$ pip3 install --require-hashes -r requirements.txt` + +(--require-hashes is intended to prevent exploitation via compromise of Pypi/CA certificates) ## Help out @@ -94,6 +96,8 @@ beardog [ at ] mailbox.org ## Disclaimer -The Tor Project, I2P developers, and anyone else do not own, create, or endorse this project, and are not otherwise involved. +The Tor Project and I2P developers do not own, create, or endorse this project, and are not otherwise involved. + +Tor is a trademark for the Tor Project. We do not own it. The 'open source badge' is by Maik Ellerbrock and is licensed under a Creative Commons Attribution 4.0 International License. \ No newline at end of file diff --git a/docs/whitepaper.md b/docs/whitepaper.md index 66bddad6..29f23bed 100755 --- a/docs/whitepaper.md +++ b/docs/whitepaper.md @@ -1,25 +1,25 @@

- <h1>Onionr</h1> + <h1>Onionr</h1>

Anonymous, Decentralized, Distributed Network

# Introduction -One of the most important things in the modern world is information. The ability to communicate freely with others is crucial for maintaining societal and personal liberty. The internet has provided humanity with the ability to spread information globally, but there are many people who try (and sometimes succeed) to stifle the flow of information. +We believe that the ability to communicate freely with others is crucial for maintaining societal and personal liberty. The internet has provided humanity with the ability to spread information globally, but there are many persons and organizations who try to stifle the flow of information, sometimes with success. Internet censorship comes in many forms, state censorship, corporate consolidation of media, threats of violence, network exploitation (e.g. denial of service attacks) and other threats. -To prevent censorship and loss of information, these measures must be in place: +We hold that in order to protect individual privacy, users must have the ability to communicate anonymously and with decentralization. + +We believe that in order to prevent censorship and loss of information, these measures must be in place: * Resistance to censorship of underlying infrastructure or of particular network hosts * Anonymization of users by default - * The Inability to coerce human users (personal threats/"doxxing", or totalitarian regime censorship) + * The Inability to coerce users (personal threats/"doxxing", or totalitarian regime censorship) * Economic availability. A system should not rely on a single device to be constantly online, and should not be overly expensive to use. The majority of people in the world own cell phones, but comparatively few own personal computers, particularly in developing countries. Internet connectivity can be slow or spotty in many areas. -There are many great projects that tackle decentralization and privacy issues, but there are none which tackle all of the above issues. Some of the existing networks have also not worked well in practice, or are more complicated than they need to be. - # Onionr Design Goals When designing Onionr we had these main goals in mind: @@ -131,4 +131,4 @@ We seek to protect the following information: We assume that Tor onion services (v3) and I2P services cannot be trivially deanonymized, and that the underlying cryptographic primitives we employ cannot be broken in any manner faster than brute force unless a quantum computer is used. -Once quantum safe algorithms are more mature and have relatively high level libraries, they will be deployed. +Once quantum safe algorithms are more mature and have decent high level libraries, they will be deployed. diff --git a/onionr/onionr.py b/onionr/onionr.py index fb9d7f20..3dff5cc3 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -156,7 +156,7 @@ class Onionr: with open('static-data/header.txt', 'rb') as file: # only to stdout, not file or log or anything sys.stderr.write(file.read().decode().replace('P', logger.colors.fg.pink).replace('W', logger.colors.reset + logger.colors.bold).replace('G', logger.colors.fg.green).replace('\n', logger.colors.reset + '\n').replace('B', logger.colors.bold).replace('A', '%s' % API_VERSION).replace('V', ONIONR_VERSION)) - logger.info(logger.colors.fg.lightgreen + '-> ' + str(message) + logger.colors.reset + logger.colors.fg.lightgreen + ' <-\n') + logger.info(logger.colors.fg.lightgreen + '-> ' + str(message) + logger.colors.reset + logger.colors.fg.lightgreen + ' <-\n', sensitive=True) def doExport(self, bHash): exportDir = self.dataDir + 'block-export/' diff --git a/onionr/onionrcommands/__init__.py b/onionr/onionrcommands/__init__.py index 35de3107..2889f182 100644 --- a/onionr/onionrcommands/__init__.py +++ b/onionr/onionrcommands/__init__.py @@ -122,7 +122,6 @@ def get_commands(onionr_inst): 'importblocks': onionr_inst.onionrUtils.importNewBlocks, 'introduce': onionr_inst.onionrCore.introduceNode, - 'connect': onionr_inst.addAddress, 'pex': onionr_inst.doPEX, 'getpassword': onionr_inst.printWebPassword, diff --git a/onionr/onionrcommands/daemonlaunch.py b/onionr/onionrcommands/daemonlaunch.py index b7452a46..b5334068 100644 --- a/onionr/onionrcommands/daemonlaunch.py +++ b/onionr/onionrcommands/daemonlaunch.py @@ -1,3 +1,23 @@ +''' + 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 . +''' + import os, time, sys, platform, sqlite3 from threading import Thread import onionr, api, logger, communicator diff --git a/onionr/onionrcommands/keyadders.py b/onionr/onionrcommands/keyadders.py index 5004afb3..48374fb9 100644 --- a/onionr/onionrcommands/keyadders.py +++ b/onionr/onionrcommands/keyadders.py @@ -1,6 +1,22 @@ -import sys -import logger +''' + 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 . +''' def add_peer(o_inst): try: newPeer = sys.argv[2] @@ -25,7 +41,7 @@ def add_address(o_inst): pass else: logger.info("Adding address: " + logger.colors.underline + newAddress) - if self.onionrCore.addAddress(newAddress): + if o_inst.onionrCore.addAddress(newAddress): logger.info("Successfully added address.") else: logger.warn("Unable to add address.") \ No newline at end of file diff --git a/onionr/onionrcommands/plugincommands.py b/onionr/onionrcommands/plugincommands.py index 5978f039..c357956f 100644 --- a/onionr/onionrcommands/plugincommands.py +++ b/onionr/onionrcommands/plugincommands.py @@ -1,3 +1,23 @@ +''' + 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 . +''' + import sys import logger, onionrplugins as plugins diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index a5cfdc84..f3d47b73 100755 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -415,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', '') @@ -430,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): ''' diff --git a/onionr/static-data/www/friends/friends.js b/onionr/static-data/www/friends/friends.js index c18da68b..71f20f98 100755 --- a/onionr/static-data/www/friends/friends.js +++ b/onionr/static-data/www/friends/friends.js @@ -72,6 +72,9 @@ fetch('/friends/list', { entry.appendChild(removeButton) entry.appendChild(nameText) friendListDisplay.appendChild(entry) + entry.onclick = (function(entry, nameText, peer) {return function() { + overlay('friendInfo') + };})(entry, nameText, peer); } // If friend delete buttons are pressed diff --git a/onionr/static-data/www/friends/index.html b/onionr/static-data/www/friends/index.html index 3d214274..ebcbff7e 100755 --- a/onionr/static-data/www/friends/index.html +++ b/onionr/static-data/www/friends/index.html @@ -11,6 +11,12 @@ +
+
+ + +
+
Onionr Web Control Panel diff --git a/onionr/static-data/www/friends/style.css b/onionr/static-data/www/friends/style.css index 663c4b4e..46affdd9 100755 --- a/onionr/static-data/www/friends/style.css +++ b/onionr/static-data/www/friends/style.css @@ -23,4 +23,14 @@ form label{ #friendList button{ display: inline; margin-right: 10px; +} + +#friendInfo .overlayContent{ + padding: 1em; + text-align: center; +} +#defriend{ + display: block; + margin-left: 50%; + margin-bottom: 1em; } \ No newline at end of file From 4fb01bac0efe54cf7de220baa8cdbd82860e3036 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 9 Mar 2019 12:01:57 -0600 Subject: [PATCH 31/35] work on friends ui and bug fixes --- onionr/onionrcommands/keyadders.py | 6 ++++-- onionr/static-data/www/friends/friends.js | 14 +++++++++++--- onionr/static-data/www/friends/index.html | 3 ++- onionr/static-data/www/friends/style.css | 11 ++++++++--- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/onionr/onionrcommands/keyadders.py b/onionr/onionrcommands/keyadders.py index 48374fb9..d52b81f9 100644 --- a/onionr/onionrcommands/keyadders.py +++ b/onionr/onionrcommands/keyadders.py @@ -17,10 +17,12 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' +import sys +import logger def add_peer(o_inst): try: newPeer = sys.argv[2] - except: + except IndexError: pass else: if o_inst.onionrUtils.hasKey(newPeer): @@ -37,7 +39,7 @@ def add_address(o_inst): try: newAddress = sys.argv[2] newAddress = newAddress.replace('http:', '').replace('/', '') - except: + except IndexError: pass else: logger.info("Adding address: " + logger.colors.underline + newAddress) diff --git a/onionr/static-data/www/friends/friends.js b/onionr/static-data/www/friends/friends.js index 71f20f98..cbefae4d 100755 --- a/onionr/static-data/www/friends/friends.js +++ b/onionr/static-data/www/friends/friends.js @@ -33,7 +33,6 @@ addForm.onsubmit = function(){ 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}) } @@ -73,8 +72,13 @@ fetch('/friends/list', { 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, peer); + };})(entry, nameText.value, peer); } // If friend delete buttons are pressed @@ -86,4 +90,8 @@ fetch('/friends/list', { removeFriend(friendKey) } } - }) \ No newline at end of file + }) + + document.getElementById('defriend').onclick = function(){ + removeFriend(document.getElementById('friendPubkey').value) + } \ No newline at end of file diff --git a/onionr/static-data/www/friends/index.html b/onionr/static-data/www/friends/index.html index ebcbff7e..8ddd77f9 100755 --- a/onionr/static-data/www/friends/index.html +++ b/onionr/static-data/www/friends/index.html @@ -13,8 +13,9 @@
+
Name:
+ -
diff --git a/onionr/static-data/www/friends/style.css b/onionr/static-data/www/friends/style.css index 46affdd9..34bf64db 100755 --- a/onionr/static-data/www/friends/style.css +++ b/onionr/static-data/www/friends/style.css @@ -26,11 +26,16 @@ form label{ } #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; - text-align: center; + margin: 1em; } #defriend{ display: block; - margin-left: 50%; - margin-bottom: 1em; + margin-top: 1em; } \ No newline at end of file From 800c061e948fd796935f5a12b5d18147f48f3557 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 11 Mar 2019 00:10:37 -0500 Subject: [PATCH 32/35] ui changes and work on whitepaper --- docs/network-comparison.png | Bin 0 -> 64942 bytes docs/whitepaper.md | 10 ++++++ onionr/communicator.py | 4 +-- onionr/onionrutils.py | 7 ++-- .../default-plugins/pms/mailapi.py | 4 +++ onionr/static-data/www/mail/index.html | 3 ++ onionr/static-data/www/mail/mail.css | 5 +++ onionr/static-data/www/mail/mail.js | 31 +++++++++++++++++- onionr/static-data/www/private/index.html | 4 +++ onionr/static-data/www/shared/main/style.css | 6 ++++ 10 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 docs/network-comparison.png diff --git a/docs/network-comparison.png b/docs/network-comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..0d1f42e6c8d331379fc7764a9ee0ddee41c7c962 GIT binary patch literal 64942 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 Q5d', methods=['POST']) def mail_delete(block): if not c._utils.validateHash(block): diff --git a/onionr/static-data/www/mail/index.html b/onionr/static-data/www/mail/index.html index 16b95db9..8af6f035 100755 --- a/onionr/static-data/www/mail/index.html +++ b/onionr/static-data/www/mail/index.html @@ -18,6 +18,9 @@ Onionr Mail ✉️

Home
+
+ API server either shutdown, has disabled mail, or has experienced a bug. +

Current Used Identity:


diff --git a/onionr/static-data/www/mail/mail.css b/onionr/static-data/www/mail/mail.css index 0334de30..af80deb1 100755 --- a/onionr/static-data/www/mail/mail.css +++ b/onionr/static-data/www/mail/mail.css @@ -53,6 +53,11 @@ input{ margin: 1em; } + .mailPing{ + display: none; + color: orange; + } + .danger{ color: red; } diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index 57c5fa40..24a74602 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -94,6 +94,27 @@ function deleteMessage(bHash){ }) } +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: { @@ -201,12 +222,16 @@ function getSentbox(){ 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 + sentDate.innerText = humanDate if (resp[i]['name'] == null){ toEl.value = resp[i]['peer'] } @@ -219,6 +244,7 @@ function getSentbox(){ entry.appendChild(toLabel) entry.appendChild(toEl) entry.appendChild(preview) + entry.appendChild(sentDate) entry.onclick = (function(tree, el, msg) {return function() { console.log(resp) if (! entry.classList.contains('deleteBtn')){ @@ -315,4 +341,7 @@ fetch('/friends/list', { //alert(resp[keys[i]]['name']) } }) -setActiveTab('inbox') \ No newline at end of file +setActiveTab('inbox') + +setInterval(function(){mailPing()}, 10000) +mailPing() \ No newline at end of file diff --git a/onionr/static-data/www/private/index.html b/onionr/static-data/www/private/index.html index 8d5d5861..8d35f070 100755 --- a/onionr/static-data/www/private/index.html +++ b/onionr/static-data/www/private/index.html @@ -20,6 +20,9 @@ Onionr Web Control Panel

+

+ +


Mail - Friend Manager

Stats

Uptime:

@@ -32,5 +35,6 @@ + \ No newline at end of file diff --git a/onionr/static-data/www/shared/main/style.css b/onionr/static-data/www/shared/main/style.css index ae4d87db..8ac6c459 100755 --- a/onionr/static-data/www/shared/main/style.css +++ b/onionr/static-data/www/shared/main/style.css @@ -172,4 +172,10 @@ body{ .primaryBtn{ background-color:#396BAC; +} + +.openSiteBtn{ + padding: 5px; + border: 1px solid black; + border-radius: 5px; } \ No newline at end of file From fdd3ff0a9d896dff4ad2190abd987cae267b1533 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 12 Mar 2019 13:23:46 -0500 Subject: [PATCH 33/35] multiprocess pow progress --- onionr/core.py | 5 +- onionr/onionrproofs.py | 2 + onionr/static-data/www/shared/sites.js | 18 +++++ onionr/subprocesspow.py | 92 ++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 onionr/static-data/www/shared/sites.js create mode 100755 onionr/subprocesspow.py diff --git a/onionr/core.py b/onionr/core.py index c0682a98..2794aba4 100755 --- a/onionr/core.py +++ b/onionr/core.py @@ -23,7 +23,7 @@ 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): @@ -787,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) diff --git a/onionr/onionrproofs.py b/onionr/onionrproofs.py index 496573d0..62dc215c 100755 --- a/onionr/onionrproofs.py +++ b/onionr/onionrproofs.py @@ -244,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 @@ -258,6 +259,7 @@ class POW: self.hashing = False iFound = True self.result = payload + print('count', nonce - startNonce) break nonce += 1 diff --git a/onionr/static-data/www/shared/sites.js b/onionr/static-data/www/shared/sites.js new file mode 100644 index 00000000..ab5c01a8 --- /dev/null +++ b/onionr/static-data/www/shared/sites.js @@ -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') + } +} \ No newline at end of file diff --git a/onionr/subprocesspow.py b/onionr/subprocesspow.py new file mode 100755 index 00000000..a2fee7c6 --- /dev/null +++ b/onionr/subprocesspow.py @@ -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 + \ No newline at end of file From 5d45db8f8dd7236d064e96cf96d9731df0d76c42 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 12 Mar 2019 23:56:26 -0500 Subject: [PATCH 34/35] small refactor in js and added exit btn to friend info overlay --- onionr/static-data/www/friends/index.html | 1 + onionr/static-data/www/mail/mail.js | 5 ----- onionr/static-data/www/shared/misc.js | 5 +++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/onionr/static-data/www/friends/index.html b/onionr/static-data/www/friends/index.html index 8ddd77f9..8e38ac39 100755 --- a/onionr/static-data/www/friends/index.html +++ b/onionr/static-data/www/friends/index.html @@ -13,6 +13,7 @@
+
Name:
diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index 24a74602..302c84f6 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -304,11 +304,6 @@ 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: { diff --git a/onionr/static-data/www/shared/misc.js b/onionr/static-data/www/shared/misc.js index bdde2f84..3c960c8f 100755 --- a/onionr/static-data/www/shared/misc.js +++ b/onionr/static-data/www/shared/misc.js @@ -87,3 +87,8 @@ for(var i = 0; i < refreshLinks.length; i++) { } } +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' + } +} \ No newline at end of file From 0660e5f77f62c89e50690545822379f0df0c45e4 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 13 Mar 2019 16:10:11 -0500 Subject: [PATCH 35/35] do not spam connection messages --- LICENSE.txt | 3 +-- README.md | 8 +++++++- docs/whitepaper.md | 2 +- onionr/communicator.py | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index fdc16448..7d2d0a0e 100755 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,4 @@ -Onionr Logo is licensed under Creative Commons Attribution-Share Alike 3.0 Unported -https://creativecommons.org/licenses/by-sa/4.0/ +The Onionr logo was created by [Anhar Ismail](https://github.com/anharismail) under the [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/). GNU GENERAL PUBLIC LICENSE diff --git a/README.md b/README.md index 119f5567..8b0de5c2 100755 --- a/README.md +++ b/README.md @@ -100,4 +100,10 @@ The Tor Project and I2P developers do not own, create, or endorse this project, Tor is a trademark for the Tor Project. We do not own it. -The 'open source badge' is by Maik Ellerbrock and is licensed under a Creative Commons Attribution 4.0 International License. \ No newline at end of file +The 'open source badge' is by Maik Ellerbrock and is licensed under a Creative Commons Attribution 4.0 International License. + +## Logo + +The Onionr logo was created by [Anhar Ismail](https://github.com/anharismail) under the [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/). + +If you modify and redistribute our code ("forking"), please use a different logo and project name to avoid confusion. Please do not use our logo in a way that makes it seem like we endorse you without permission. \ No newline at end of file diff --git a/docs/whitepaper.md b/docs/whitepaper.md index 8428b8a6..ca2eb080 100755 --- a/docs/whitepaper.md +++ b/docs/whitepaper.md @@ -135,7 +135,7 @@ Once quantum safe algorithms are more mature and have decent high level librarie # Comparisons to other P2P software -Since Onionr is far from the first to implement many of these ideas (on their own), this section compares Onionr to other networks +Since Onionr is far from the first to implement many of these ideas (on their own), this section compares Onionr to other networks, using points we consider to be the most important. ![network comparison image](network-comparison.png) diff --git a/onionr/communicator.py b/onionr/communicator.py index c2528cef..263328f4 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -437,11 +437,11 @@ class OnionrCommunicatorDaemon: 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