From 63b4c88d064e7d1b966afa0c7d69b4668eb06226 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 15:59:13 -0600 Subject: [PATCH 01/89] moved processblocks to communicator and fixed bool error in validatehash --- onionr/communicator.py | 11 ++++++++++- onionr/core.py | 9 --------- onionr/onionr.py | 4 ++++ onionr/onionrutils.py | 2 ++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index a1416e5f..2eb81518 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -55,7 +55,7 @@ class OnionrCommunicate: heartBeatTimer = 0 if blockProcessTimer == blockProcessAmount: self.lookupBlocks() - self._core.processBlocks() + self.processBlocks() blockProcessTimer = 0 #logger.debug('Communicator daemon heartbeat') if command != False: @@ -115,6 +115,15 @@ class OnionrCommunicate: logger.debug('Adding ' + i + ' to hash database...') self._core.addToBlockDB(i) return + def processBlocks(self): + ''' + Work with the block database and download any missing blocks + This is meant to be called from the communicator daemon on its timer. + ''' + for i in self.getBlockList(True).split("\n"): + if i != "": + print('UNSAVED BLOCK:', i) + return def performGet(self, action, peer, data=None, type='tor'): '''Performs a request to a peer through Tor or i2p (currently only tor)''' diff --git a/onionr/core.py b/onionr/core.py index e52359c0..2c2ef72e 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -280,15 +280,6 @@ class Core: conn.close() return peerList - def processBlocks(self): - ''' - Work with the block database and download any missing blocks - This is meant to be called from the communicator daemon on its timer. - ''' - for i in self.getBlockList(True).split("\n"): - if i != "": - print('UNSAVED BLOCK:', i) - return def getPeerInfo(self, peer, info): ''' get info about a peer diff --git a/onionr/onionr.py b/onionr/onionr.py index b84d1633..a3d2bd4e 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -109,6 +109,10 @@ class Onionr: os.remove('.onionr-lock') elif command == 'stop': self.killDaemon() + elif command in ('listpeers', 'list-peers'): + logger.info('Peer list:\n') + for i in self.onionrCore.listPeers(): + logger.info(i) elif command in ('addmsg', 'addmessage'): while True: messageToAdd = input('Broadcast message to network: ') diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index d942d831..14ddca4b 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -99,6 +99,8 @@ class OnionrUtils: def validateHash(self, data, length=64): '''Validate if a string is a valid hex formatted hash''' retVal = True + if retVal == False: + return False if len(data) != length: retVal = False else: From cd82903db1bd42e29a78399e96f6377be6ac7c52 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:02:29 -0600 Subject: [PATCH 02/89] moved processblocks to communicator and fixed bool error in validatehash --- onionr/onionrutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 14ddca4b..95b7d7ad 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -99,7 +99,7 @@ class OnionrUtils: def validateHash(self, data, length=64): '''Validate if a string is a valid hex formatted hash''' retVal = True - if retVal == False: + if retVal == False or retVal: return False if len(data) != length: retVal = False From f4bb9ca093dff6a6d0d70043d812b0d626d5510b Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:03:55 -0600 Subject: [PATCH 03/89] fixed process blocks core call --- onionr/communicator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 2eb81518..0bd93657 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -120,7 +120,7 @@ class OnionrCommunicate: Work with the block database and download any missing blocks This is meant to be called from the communicator daemon on its timer. ''' - for i in self.getBlockList(True).split("\n"): + for i in self._core.getBlockList(True).split("\n"): if i != "": print('UNSAVED BLOCK:', i) return From 7f688e069697358cc7c965110e468922ae38444c Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:14:19 -0600 Subject: [PATCH 04/89] added downloadBlock function --- onionr/communicator.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 0bd93657..2dbec8b5 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -122,8 +122,22 @@ class OnionrCommunicate: ''' for i in self._core.getBlockList(True).split("\n"): if i != "": - print('UNSAVED BLOCK:', i) + logger.warn('UNSAVED BLOCK:', i) + data = self.downloadBlock(i) return + + def downloadBlock(self, hash): + peerList = self._core.listPeers() + blocks = '' + for i in peerList: + hasher = hashlib.sha3_256() + data = self.performGet('getData', i, hash) + if data == False or len(data) > 10000000: + continue + hasher.update(data.encode()) + if hasher.hexdigest() == hash: + self._core.setData(data) + logger.info('Successfully obtained data for ' + hash) def performGet(self, action, peer, data=None, type='tor'): '''Performs a request to a peer through Tor or i2p (currently only tor)''' From 692de7a1479eb2e26f8788ec46e84abc801db5ff Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:15:41 -0600 Subject: [PATCH 05/89] sigh --- onionr/communicator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 2dbec8b5..f5e7a152 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -122,7 +122,7 @@ class OnionrCommunicate: ''' for i in self._core.getBlockList(True).split("\n"): if i != "": - logger.warn('UNSAVED BLOCK:', i) + logger.warn('UNSAVED BLOCK: ' + i) data = self.downloadBlock(i) return From 172e709c46f0fd92deceabf0601497066958f76f Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:21:51 -0600 Subject: [PATCH 06/89] trim hash whitespace in validatehash --- onionr/onionrutils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 95b7d7ad..2d9b28a8 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -99,8 +99,9 @@ class OnionrUtils: def validateHash(self, data, length=64): '''Validate if a string is a valid hex formatted hash''' retVal = True - if retVal == False or retVal: + if retVal == False or retVal == True: return False + data = data.strip() if len(data) != length: retVal = False else: From 7533fda399c0f70e6c4d2c3572e371da47bb58b2 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:26:34 -0600 Subject: [PATCH 07/89] debugging blocks --- onionr/communicator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionr/communicator.py b/onionr/communicator.py index f5e7a152..c92a78eb 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -104,6 +104,7 @@ class OnionrCommunicate: self._core.setPeerInfo(i, "blockDBHash", currentDB) else: logger.warn("Peer " + i + " returned malformed hash") + logger.debug('BLOCKS: \n' + blocks) blockList = blocks.split('\n') for i in blockList: logger.debug('Exchanged block (blockList): ' + i) From d1faea25194978da060fb471f08098ca8892cd82 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:29:16 -0600 Subject: [PATCH 08/89] debugging blocks --- onionr/communicator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/onionr/communicator.py b/onionr/communicator.py index c92a78eb..b80cf6ff 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -95,6 +95,10 @@ class OnionrCommunicate: else: logger.debug('Fetching hash from ' + i + ', ' + lastDB + ' last known') currentDB = self.performGet('getDBHash', i) + if currentDB != False: + logger.debug(i + " hash db (from request): " + currentDB) + else: + logger.warn("Error getting hash db status for " + i) if currentDB != False: if lastDB != currentDB: logger.debug('Fetching hash from ' + i + ' - ' + currentDB + ' current hash.') From c6ad487c798d0f155cdedab6f64501b1e85a498d Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:38:10 -0600 Subject: [PATCH 09/89] fxied validateHash --- onionr/onionrutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 2d9b28a8..1f0190f1 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -99,7 +99,7 @@ class OnionrUtils: def validateHash(self, data, length=64): '''Validate if a string is a valid hex formatted hash''' retVal = True - if retVal == False or retVal == True: + if data == False or data == True: return False data = data.strip() if len(data) != length: From 1b0d5755752496eecfa4b02646235f43fb4870d4 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:41:49 -0600 Subject: [PATCH 10/89] fxied validateHash --- onionr/communicator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index b80cf6ff..65cb13b8 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -103,11 +103,8 @@ class OnionrCommunicate: if lastDB != currentDB: logger.debug('Fetching hash from ' + i + ' - ' + currentDB + ' current hash.') blocks += self.performGet('getBlockHashes', i) - if currentDB != lastDB: if self._utils.validateHash(currentDB): self._core.setPeerInfo(i, "blockDBHash", currentDB) - else: - logger.warn("Peer " + i + " returned malformed hash") logger.debug('BLOCKS: \n' + blocks) blockList = blocks.split('\n') for i in blockList: From 7acb695acd753798381acba831e7d5c89d69d546 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 16:45:22 -0600 Subject: [PATCH 11/89] added hash validation warning for blocks --- onionr/communicator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 65cb13b8..16a44b80 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -137,9 +137,11 @@ class OnionrCommunicate: if data == False or len(data) > 10000000: continue hasher.update(data.encode()) - if hasher.hexdigest() == hash: + if hasher.hexdigest() == hash.strip(): self._core.setData(data) logger.info('Successfully obtained data for ' + hash) + else: + logger.warn("Failed to validate " + hash) def performGet(self, action, peer, data=None, type='tor'): '''Performs a request to a peer through Tor or i2p (currently only tor)''' From dc65254e0a249b1aadeed6570ca1937477e4e8c4 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:02:16 -0600 Subject: [PATCH 12/89] now update block datasaved stat when saved --- onionr/communicator.py | 5 ++++- onionr/core.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 16a44b80..9bba7c6e 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -137,7 +137,10 @@ class OnionrCommunicate: if data == False or len(data) > 10000000: continue hasher.update(data.encode()) - if hasher.hexdigest() == hash.strip(): + digest = hasher.hexdigest() + if digest is bytes: + digest = digest.decode() + if digest == hash.strip(): self._core.setData(data) logger.info('Successfully obtained data for ' + hash) else: diff --git a/onionr/core.py b/onionr/core.py index 2c2ef72e..96c8a7f7 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -169,6 +169,13 @@ class Core: blockFile = open(blockFileName, 'w') blockFile.write(data.decode()) blockFile.close() + + conn = sqlite3.connect(self.blockDB) + c = conn.cursor() + c.execute("UPDATE hashes set dataSaved=1 where id = '" + dataHash + "';") + conn.commit() + conn.close() + return dataHash def dataDirEncrypt(self, password): From 8d41f9f3d58a2468f795482ca9fb8ba26393add0 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:08:28 -0600 Subject: [PATCH 13/89] now update block datasaved stat when saved --- onionr/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/core.py b/onionr/core.py index 96c8a7f7..ced0d72f 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -172,7 +172,7 @@ class Core: conn = sqlite3.connect(self.blockDB) c = conn.cursor() - c.execute("UPDATE hashes set dataSaved=1 where id = '" + dataHash + "';") + c.execute("UPDATE hashes set dataSaved=1 where hash = '" + dataHash + "';") conn.commit() conn.close() From 55aa889bd9c86e61eb805934d7022453bc07ddfe Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:12:36 -0600 Subject: [PATCH 14/89] now update block datasaved stat when saved --- onionr/communicator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 9bba7c6e..0c35fcf5 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -138,7 +138,7 @@ class OnionrCommunicate: continue hasher.update(data.encode()) digest = hasher.hexdigest() - if digest is bytes: + if type(digest) is bytes: digest = digest.decode() if digest == hash.strip(): self._core.setData(data) From 0ca84c9759f95e0b0f027f2132f740feada6c0c3 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:14:46 -0600 Subject: [PATCH 15/89] check for bytes hash type in setData --- onionr/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionr/core.py b/onionr/core.py index ced0d72f..46f2a4ac 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -162,6 +162,8 @@ class Core: hasher = hashlib.sha3_256() hasher.update(data) dataHash = hasher.hexdigest() + if type(dataHash) is bytes: + dataHash = dataHash.decode() blockFileName = self.blockDataLocation + dataHash + '.dat' if os.path.exists(blockFileName): raise Exception("Data is already set for " + dataHash) From da807236863db33fa93ee12b2b00e822232ef5ee Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:19:30 -0600 Subject: [PATCH 16/89] do not claim hash is invalid when there was no hash --- onionr/communicator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 0c35fcf5..422dbf9d 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -111,7 +111,8 @@ class OnionrCommunicate: logger.debug('Exchanged block (blockList): ' + i) if not self._utils.validateHash(i): # skip hash if it isn't valid - logger.warn('Hash ' + i + ' is not valid') + if len(i.strip()) != 0 + logger.warn('Hash ' + i + ' is not valid') continue else: logger.debug('Adding ' + i + ' to hash database...') From 8b3a09f5af85f5c38da2d3103471270c506c8ee3 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:20:24 -0600 Subject: [PATCH 17/89] do not claim hash is invalid when there was no hash --- onionr/communicator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 422dbf9d..d7d92065 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -111,7 +111,7 @@ class OnionrCommunicate: logger.debug('Exchanged block (blockList): ' + i) if not self._utils.validateHash(i): # skip hash if it isn't valid - if len(i.strip()) != 0 + if len(i.strip()) != 0: logger.warn('Hash ' + i + ' is not valid') continue else: From aaecd0adaff7418533e99c81ca7232dd1f08c992 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:30:31 -0600 Subject: [PATCH 18/89] added timeout to performget --- onionr/communicator.py | 2 +- onionr/core.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index d7d92065..bdc11256 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -159,7 +159,7 @@ class OnionrCommunicate: if data != None: url = url + '&data=' + data try: - r = requests.get(url, headers=headers, proxies=proxies) + r = requests.get(url, headers=headers, proxies=proxies, timeout=(5, 30)) except requests.exceptions.RequestException as e: logger.warn(action + " failed with peer " + peer + ": " + str(e)) return False diff --git a/onionr/core.py b/onionr/core.py index 46f2a4ac..5a751fe3 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -166,6 +166,7 @@ class Core: dataHash = dataHash.decode() blockFileName = self.blockDataLocation + dataHash + '.dat' if os.path.exists(blockFileName): + return # to do, properly check if block is already saved elsewhere raise Exception("Data is already set for " + dataHash) else: blockFile = open(blockFileName, 'w') From b4d61d35983d4140fb4718e7217e166b3c6cbffa Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:39:34 -0600 Subject: [PATCH 19/89] set dataSaved when it should have already been --- onionr/core.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index 5a751fe3..d0a9bf0e 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -166,18 +166,18 @@ class Core: dataHash = dataHash.decode() blockFileName = self.blockDataLocation + dataHash + '.dat' if os.path.exists(blockFileName): - return # to do, properly check if block is already saved elsewhere - raise Exception("Data is already set for " + dataHash) + pass # to do, properly check if block is already saved elsewhere + #raise Exception("Data is already set for " + dataHash) else: blockFile = open(blockFileName, 'w') blockFile.write(data.decode()) blockFile.close() - conn = sqlite3.connect(self.blockDB) - c = conn.cursor() - c.execute("UPDATE hashes set dataSaved=1 where hash = '" + dataHash + "';") - conn.commit() - conn.close() + conn = sqlite3.connect(self.blockDB) + c = conn.cursor() + c.execute("UPDATE hashes set dataSaved=1 where hash = '" + dataHash + "';") + conn.commit() + conn.close() return dataHash From 1c3e886ba078e8d9e99ec26c46d05eb7a00fa608 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:52:48 -0600 Subject: [PATCH 20/89] added hasBlock function --- onionr/communicator.py | 2 ++ onionr/onionrutils.py | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index bdc11256..ee0fed46 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -108,6 +108,8 @@ class OnionrCommunicate: logger.debug('BLOCKS: \n' + blocks) blockList = blocks.split('\n') for i in blockList: + if self._utils.hasBlock(i): + continue logger.debug('Exchanged block (blockList): ' + i) if not self._utils.validateHash(i): # skip hash if it isn't valid diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 1f0190f1..ec7517e2 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -18,7 +18,7 @@ along with this program. If not, see . ''' # Misc functions that do not fit in the main api, but are useful -import getpass, sys, requests, configparser, os, socket, gnupg, hashlib, logger +import getpass, sys, requests, configparser, os, socket, gnupg, hashlib, logger, sqlite3 if sys.version_info < (3, 6): try: import sha3 @@ -96,6 +96,22 @@ class OnionrUtils: dataHash = hasher.hexdigest() return dataHash + def hasBlock(self, hash): + '''detect if we have a block in the list or not''' + conn = sqlite3.connect(self._core.blockDB) + c = conn.cursor() + if not self.validateHash(hash): + raise Exception("Invalid hash") + for result in c.execute("SELECT COUNT() FROM hashes where hash='" + hash + "'"): + if result[0] >= 1: + conn.commit() + conn.close() + return True + else: + conn.commit() + conn.close() + return False + def validateHash(self, data, length=64): '''Validate if a string is a valid hex formatted hash''' retVal = True From 8c6a04f03dd1f1e9a29b33bebca5d9313a50fa18 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 20:54:39 -0600 Subject: [PATCH 21/89] moved length check in hash validation --- onionr/communicator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index ee0fed46..d6dc9bdb 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -108,13 +108,14 @@ class OnionrCommunicate: logger.debug('BLOCKS: \n' + blocks) blockList = blocks.split('\n') for i in blockList: + if len(i.strip()) == 0: + continue if self._utils.hasBlock(i): continue logger.debug('Exchanged block (blockList): ' + i) if not self._utils.validateHash(i): # skip hash if it isn't valid - if len(i.strip()) != 0: - logger.warn('Hash ' + i + ' is not valid') + logger.warn('Hash ' + i + ' is not valid') continue else: logger.debug('Adding ' + i + ' to hash database...') From e5a3a4650ebc2a4deeca12237537359d45d5e4c2 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 21:00:12 -0600 Subject: [PATCH 22/89] now display new block text if small in terminal --- onionr/communicator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionr/communicator.py b/onionr/communicator.py index d6dc9bdb..3be47f31 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -147,6 +147,8 @@ class OnionrCommunicate: if digest == hash.strip(): self._core.setData(data) logger.info('Successfully obtained data for ' + hash) + if len(data) < 120: + logger.debug('Block text:\n' + data) else: logger.warn("Failed to validate " + hash) From 71bff27245865f7b39be2a8ffd9bf43e994577a6 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 28 Jan 2018 21:45:43 -0600 Subject: [PATCH 23/89] do not add block in addblock function if it already is added --- onionr/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionr/core.py b/onionr/core.py index d0a9bf0e..6617f1e8 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -134,6 +134,8 @@ class Core: '''add a hash value to the block db (should be in hex format)''' if not os.path.exists(self.blockDB): raise Exception('Block db does not exist') + if self._utils.hasBlock(newHash): + return conn = sqlite3.connect(self.blockDB) c = conn.cursor() currentTime = math.floor(time.time()) From b3ea0e7b469d29ba69ccdfeadc120118522292dd Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sun, 28 Jan 2018 22:01:36 -0800 Subject: [PATCH 24/89] fix a few things --- onionr/logger.py | 8 +++++--- onionr/onionr.py | 4 ++-- onionr/onionrutils.py | 3 +-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/onionr/logger.py b/onionr/logger.py index 7577a6db..6a305bfe 100644 --- a/onionr/logger.py +++ b/onionr/logger.py @@ -131,7 +131,7 @@ def log(prefix, data, color = ''): Takes in input from the console, not stored in logs message: The message to display before taking input ''' -def input(message = 'Enter input: '): +def readline(message = 'Enter input: '): color = colors.fg.green + colors.bold output = colors.reset + str(color) + '... ' + colors.reset + str(message) + colors.reset @@ -139,7 +139,8 @@ def input(message = 'Enter input: '): output = colors.filter(output) sys.stdout.write(output) - return raw_input() + + return input() ''' Displays an "Are you sure" message, returns True for Y and False for N @@ -163,7 +164,8 @@ def confirm(default = 'y', message = 'Are you sure %s? '): output = colors.filter(output) sys.stdout.write(output.replace('%s', confirm)) - inp = raw_input().lower() + + inp = input().lower() if 'y' in inp: return True diff --git a/onionr/onionr.py b/onionr/onionr.py index a3d2bd4e..ef63ead1 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -115,7 +115,7 @@ class Onionr: logger.info(i) elif command in ('addmsg', 'addmessage'): while True: - messageToAdd = input('Broadcast message to network: ') + messageToAdd = logger.readline('Broadcast message to network: ') if len(messageToAdd) >= 1: break addedHash = self.onionrCore.setData(messageToAdd) @@ -165,4 +165,4 @@ class Onionr: def showHelp(self): '''Show help for Onionr''' return -Onionr() \ No newline at end of file +Onionr() diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index ec7517e2..1884bb5a 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -50,7 +50,7 @@ class OnionrUtils: pass2 = getpass.getpass() if pass1 != pass2: logger.error("Passwords do not match.") - input() + logger.readline() else: break else: @@ -166,4 +166,3 @@ class OnionrUtils: if not idNoDomain.isalnum(): retVal = False return retVal - From dd9a54f7c671dcdf089efcfbe193752a50ecbf2f Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 29 Jan 2018 01:05:02 -0600 Subject: [PATCH 25/89] added addpeer command and misc bug fixes --- onionr/communicator.py | 1 + onionr/core.py | 3 ++- onionr/onionr.py | 8 ++++++++ onionr/tests.py | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 3be47f31..cfd8588a 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -133,6 +133,7 @@ class OnionrCommunicate: return def downloadBlock(self, hash): + '''download a block from random order of peers''' peerList = self._core.listPeers() blocks = '' for i in peerList: diff --git a/onionr/core.py b/onionr/core.py index 6617f1e8..0627d365 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -329,7 +329,8 @@ class Core: c = conn.cursor() command = (data, peer) # TODO: validate key on whitelist - + if key not in ('id', 'text', 'name', 'pgpKey', 'hmacKey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'): + raise Exception("Got invalid database key when setting peer info") c.execute('UPDATE peers SET ' + key + ' = ? where id=?', command) conn.commit() conn.close() diff --git a/onionr/onionr.py b/onionr/onionr.py index a3d2bd4e..1d5e7e06 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -109,6 +109,14 @@ class Onionr: os.remove('.onionr-lock') elif command == 'stop': self.killDaemon() + elif command == 'addpeer': + try: + newPeer = sys.argv[2] + except: + pass + else: + logger.info("Adding peer.") + self.onionrCore.addPeer(newPeer) elif command in ('listpeers', 'list-peers'): logger.info('Peer list:\n') for i in self.onionrCore.listPeers(): diff --git a/onionr/tests.py b/onionr/tests.py index 8bfa971e..d296d948 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -51,7 +51,7 @@ class OnionrTests(unittest.TestCase): myCore = core.Core() if not os.path.exists('data/peers.db'): myCore.createPeerDB() - if myCore.addPeer('facebookcorewwwi.onion') and not myCore.addPeer('invalidpeer.onion'): + if myCore.addPeer('2ks5c5bm6zk3ejqg.onion') and not myCore.addPeer('invalidpeer.onion'): self.assertTrue(True) else: self.assertTrue(False) From 14d1fec3f3d803b283167fca8d0a00e94a436ea4 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 1 Feb 2018 16:45:15 -0600 Subject: [PATCH 26/89] started work on data encryption --- onionr/core.py | 2 +- onionr/onionrcrypto.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 onionr/onionrcrypto.py diff --git a/onionr/core.py b/onionr/core.py index 0627d365..fd1b56e0 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -1,7 +1,7 @@ ''' Onionr - P2P Microblogging Platform & Social network - Core Onionr library, useful for external programs. Handles peer processing and cryptography. + Core Onionr library, useful for external programs. Handles peer & data processing ''' ''' This program is free software: you can redistribute it and/or modify diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py new file mode 100644 index 00000000..b3303eab --- /dev/null +++ b/onionr/onionrcrypto.py @@ -0,0 +1,28 @@ +''' + Onionr - P2P Microblogging Platform & Social network + + This file handles Onionr's cryptography. +''' +''' + 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 nacl + +class OnionrCrypto: + def __init__(self): + return + def symmetricPeerEncrypt(self, data, key): + return + def symmetricPeerDecrypt(self, data, key): + return \ No newline at end of file From 70bc131aa6346a4b683555ae062ce5000561f2a3 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 1 Feb 2018 23:39:55 -0600 Subject: [PATCH 27/89] work on gui, blocks now have identifiers, work on crypto --- docs/api.md | 6 ++++++ onionr/core.py | 29 ++++++++++++++++++++++++++--- onionr/gui.py | 42 +++++++++++++++++++++++++++++++++++++++++- onionr/onionr.py | 5 ++++- onionr/onionrcrypto.py | 2 ++ 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/docs/api.md b/docs/api.md index ad45e88b..7f9128a5 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,3 +1,9 @@ +BLOCK HEADERS (simple ID system to identify block type) +----------------------------------------------- +-crypt- (encrypted block) +-bin- (binary file) +-txt- (plaintext) + HTTP API ------------------------------------------------ /client/ (Private info, not publicly accessible) diff --git a/onionr/core.py b/onionr/core.py index fd1b56e0..aa353ced 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -114,7 +114,9 @@ class Core: hash - the hash of a block dateReceived - the date the block was recieved, not necessarily when it was created decrypted - if we can successfully decrypt the block (does not describe its current state) - dataObtained - if the data has been obtained for the block + dataType - data type of the block + dataFound - if the data has been found for the block + dataSaved - if the data has been saved for the block ''' if os.path.exists(self.blockDB): raise Exception("Block database already exists") @@ -124,6 +126,7 @@ class Core: hash text not null, dateReceived int, decrypted int, + dataType text, dataFound int, dataSaved int ); @@ -143,8 +146,8 @@ class Core: selfInsert = 1 else: selfInsert = 0 - data = (newHash, currentTime, 0, 0, selfInsert) - c.execute('INSERT into hashes values(?, ?, ?, ?, ?);', data) + data = (newHash, currentTime, 0, '', 0, selfInsert) + c.execute('INSERT into hashes values(?, ?, ?, ?, ?, ?);', data) conn.commit() conn.close() @@ -348,3 +351,23 @@ class Core: for i in row: retData += i + "\n" return retData + + def getBlocksByType(self, blockType): + conn = sqlite3.connect(self.blockDB) + c = conn.cursor() + retData = '' + execute = 'SELECT hash FROM hashes where dataType=?' + args = (blockType,) + for row in c.execute(execute, args): + for i in row: + retData += i + "\n" + return retData.split('\n') + + def setBlockType(self, hash, blockType): + conn = sqlite3.connect(self.blockDB) + c = conn.cursor() + if blockType not in ("txt"): + return + c.execute("UPDATE hashes set dataType='" + blockType + "' where hash = '" + hash + "';") + conn.commit() + conn.close() \ No newline at end of file diff --git a/onionr/gui.py b/onionr/gui.py index 3c4b846f..ce31456c 100755 --- a/onionr/gui.py +++ b/onionr/gui.py @@ -13,4 +13,44 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -''' \ No newline at end of file +''' +from tkinter import * +import os, sqlite3, core +class OnionrGUI: + def __init__(self, myCore): + self.root = Tk() + self.myCore = myCore # onionr core + + w = Label(self.root, text="Onionr", width=10) + w.config(font=("Sans-Serif", 22)) + w.pack() + scrollbar = Scrollbar(self.root) + scrollbar.pack(side=RIGHT, fill=Y) + + self.listedBlocks = [] + + idText = open('./data/hs/hostname', 'r').read() + idLabel = Label(self.root, text="ID: " + idText) + idLabel.pack(pady=5) + + self.listbox = Listbox(self.root, yscrollcommand=scrollbar.set) + + #listbox.insert(END, str(i)) + self.listbox.pack(fill=BOTH) + + scrollbar.config(command=self.listbox.yview) + self.root.after(2000, self.update) + self.root.mainloop() + + def update(self): + for i in self.myCore.getBlocksByType('txt'): + if i.strip() == '' or i in self.listedBlocks: + continue + blockFile = open('./data/blocks/' + i + '.dat') + self.listbox.insert(END, str(blockFile.read().replace('-txt-', ''))) + blockFile.close() + self.listedBlocks.append(i) + blocksList = os.listdir('./data/blocks/') # dir is your directory path + number_blocks = len(blocksList) + + self.root.after(10000, self.update) diff --git a/onionr/onionr.py b/onionr/onionr.py index 702e8da7..79215ac9 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -123,13 +123,16 @@ class Onionr: logger.info(i) elif command in ('addmsg', 'addmessage'): while True: - messageToAdd = logger.readline('Broadcast message to network: ') + messageToAdd = '-txt-' + logger.readline('Broadcast message to network: ') if len(messageToAdd) >= 1: break addedHash = self.onionrCore.setData(messageToAdd) self.onionrCore.addToBlockDB(addedHash, selfInsert=True) + self.onionrCore.setBlockType(addedHash, 'txt') elif command == 'stats': self.showStats() + elif command == 'gui': + gui.OnionrGUI(self.onionrCore) elif command == 'help' or command == '--help': self.showHelp() elif command == '': diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index b3303eab..9624fb11 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -25,4 +25,6 @@ class OnionrCrypto: def symmetricPeerEncrypt(self, data, key): return def symmetricPeerDecrypt(self, data, key): + return + def rsaEncrypt(self, peer, data): return \ No newline at end of file From 38ad6559c392514b39db4997ac2a2ed65dd6dfd4 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 2 Feb 2018 02:30:50 -0600 Subject: [PATCH 28/89] gui can now send messages --- onionr/gui.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/onionr/gui.py b/onionr/gui.py index ce31456c..4bf5b0c1 100755 --- a/onionr/gui.py +++ b/onionr/gui.py @@ -20,6 +20,7 @@ class OnionrGUI: def __init__(self, myCore): self.root = Tk() self.myCore = myCore # onionr core + self.root.title("PyOnionr") w = Label(self.root, text="Onionr", width=10) w.config(font=("Sans-Serif", 22)) @@ -33,6 +34,11 @@ class OnionrGUI: idLabel = Label(self.root, text="ID: " + idText) idLabel.pack(pady=5) + self.sendEntry = Entry(self.root) + sendBtn = Button(self.root, text='Send Message', command=self.sendMessage) + self.sendEntry.pack() + sendBtn.pack() + self.listbox = Listbox(self.root, yscrollcommand=scrollbar.set) #listbox.insert(END, str(i)) @@ -42,6 +48,13 @@ class OnionrGUI: self.root.after(2000, self.update) self.root.mainloop() + def sendMessage(self): + messageToAdd = self.sendEntry.get() + addedHash = self.myCore.setData(messageToAdd) + self.myCore.addToBlockDB(addedHash, selfInsert=True) + self.myCore.setBlockType(addedHash, 'txt') + self.sendEntry.delete(0, END) + def update(self): for i in self.myCore.getBlocksByType('txt'): if i.strip() == '' or i in self.listedBlocks: From 2769673abd24832bbce79b5b5dd2411a4ea2ed8f Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 2 Feb 2018 03:15:28 -0600 Subject: [PATCH 29/89] hopefully fixed block issues --- onionr/communicator.py | 4 +++- onionr/core.py | 6 +++--- onionr/gui.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index cfd8588a..413cdfd3 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -147,6 +147,8 @@ class OnionrCommunicate: digest = digest.decode() if digest == hash.strip(): self._core.setData(data) + if data.startswith('-txt-'): + self._core.setBlockType(hash, 'txt') logger.info('Successfully obtained data for ' + hash) if len(data) < 120: logger.debug('Block text:\n' + data) @@ -165,7 +167,7 @@ class OnionrCommunicate: if data != None: url = url + '&data=' + data try: - r = requests.get(url, headers=headers, proxies=proxies, timeout=(5, 30)) + r = requests.get(url, headers=headers, proxies=proxies, timeout=(15, 30)) except requests.exceptions.RequestException as e: logger.warn(action + " failed with peer " + peer + ": " + str(e)) return False diff --git a/onionr/core.py b/onionr/core.py index aa353ced..5cfa2015 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -366,8 +366,8 @@ class Core: def setBlockType(self, hash, blockType): conn = sqlite3.connect(self.blockDB) c = conn.cursor() - if blockType not in ("txt"): - return - c.execute("UPDATE hashes set dataType='" + blockType + "' where hash = '" + hash + "';") + #if blockType not in ("txt"): + # return + c.execute("UPDATE hashes SET dataType='" + blockType + "' WHERE hash = '" + hash + "';") conn.commit() conn.close() \ No newline at end of file diff --git a/onionr/gui.py b/onionr/gui.py index 4bf5b0c1..5fa05331 100755 --- a/onionr/gui.py +++ b/onionr/gui.py @@ -49,7 +49,7 @@ class OnionrGUI: self.root.mainloop() def sendMessage(self): - messageToAdd = self.sendEntry.get() + messageToAdd = '-txt-' + self.sendEntry.get() addedHash = self.myCore.setData(messageToAdd) self.myCore.addToBlockDB(addedHash, selfInsert=True) self.myCore.setBlockType(addedHash, 'txt') From bdd1d9697b76acb8a1d48edc7883345277e5d481 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 3 Feb 2018 17:11:35 -0800 Subject: [PATCH 30/89] Refactor argument handler FOR THE FIFTH TIME --- onionr/onionr.py | 166 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 112 insertions(+), 54 deletions(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index 79215ac9..120280cb 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -20,8 +20,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sys, os, configparser, base64, random, getpass, shutil, subprocess, requests, time, logger -import gui, api, core +import sys, os, configparser, base64, random, getpass, shutil, subprocess, requests, time, logger, platform +import api, core from onionrutils import OnionrUtils from netcontroller import NetController @@ -30,16 +30,20 @@ try: except ImportError: raise Exception("You need the PySocks module (for use with socks5 proxy to use Tor)") +ONIONR_VERSION = '0.0.0' # for debugging and stuff +API_VERSION = '1' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes knows how to communicate without learning too much information about you. + class Onionr: def __init__(self): - '''Main Onionr class. This is for the CLI program, and does not handle much of the logic. - In general, external programs and plugins should not use this class. - + ''' + Main Onionr class. This is for the CLI program, and does not handle much of the logic. + In general, external programs and plugins should not use this class. ''' try: os.chdir(sys.path[0]) except FileNotFoundError: pass + if os.path.exists('dev-enabled'): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) @@ -87,67 +91,111 @@ class Onionr: randomPort = random.randint(1024, 65535) if self.onionrUtils.checkPort(randomPort): break - self.config['CLIENT'] = {'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': '0.0.0'} + self.config['CLIENT'] = {'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION} with open('data/config.ini', 'w') as configfile: self.config.write(configfile) + command = '' try: command = sys.argv[1].lower() except IndexError: command = '' finally: - if command == 'start': - if os.path.exists('.onionr-lock'): - 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.daemon() - if not self.debug and not self._developmentMode: - os.remove('.onionr-lock') - elif command == 'stop': - self.killDaemon() - elif command == 'addpeer': - try: - newPeer = sys.argv[2] - except: - pass - else: - logger.info("Adding peer.") - self.onionrCore.addPeer(newPeer) - elif command in ('listpeers', 'list-peers'): - logger.info('Peer list:\n') - for i in self.onionrCore.listPeers(): - logger.info(i) - elif command in ('addmsg', 'addmessage'): - while True: - messageToAdd = '-txt-' + logger.readline('Broadcast message to network: ') - if len(messageToAdd) >= 1: - break - addedHash = self.onionrCore.setData(messageToAdd) - self.onionrCore.addToBlockDB(addedHash, selfInsert=True) - self.onionrCore.setBlockType(addedHash, 'txt') - elif command == 'stats': - self.showStats() - elif command == 'gui': - gui.OnionrGUI(self.onionrCore) - elif command == 'help' or command == '--help': - self.showHelp() - elif command == '': - logger.info('Do ' + logger.colors.bold + sys.argv[0] + ' --help' + logger.colors.reset + logger.colors.fg.green + ' for Onionr help.') - else: - logger.error('Invalid command.') + self.execute(command) if not self._developmentMode: encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory: ') self.onionrCore.dataDirEncrypt(encryptionPassword) shutil.rmtree('data/') + return + + ''' + THIS SECTION HANDLES THE COMMANDS + ''' + + def getCommands(self): + return { + 'start': self.start, + 'stop': self.killDaemon, + 'version': self.version, + 'listpeers': self.listPeers, + 'list-peers': self.listPeers, + 'stats': self.showStats, + 'help': self.showHelp, + '': self.showHelpSuggestion, + 'addmsg': self.addMessage, + 'addmessage': self.addMessage, + 'add-msg': self.addMessage, + 'add-message': self.addMessage, + 'gui': self.openGUI, + 'addpeer': self.addPeer, + 'add-peer': self.addPeer + } + + def execute(self, argument): + argument = argument[argument.startswith('--') and len('--'):] # remove -- if it starts with it + + # define commands + commands = self.getCommands() + + command = commands.get(argument, self.notFound) + command() + + ''' + THIS SECTION DEFINES THE COMMANDS + ''' + + def version(self): + logger.info('Onionr ' + ONIONR_VERSION + ' (' + platform.machine() + ') : API v' + API_VERSION) + logger.info('Running on ' + platform.platform() + ' ' + platform.release()) + + def openGUI(self): + gui.OnionrGUI(self.onionrCore) + + def listPeers(self): + logger.info('Peer list:\n') + for i in self.onionrCore.listPeers(): + logger.info(i) + + def addPeer(self): + try: + newPeer = sys.argv[2] + except: + pass + else: + logger.info("Adding peer: " + logger.colors.underline + newPeer) + self.onionrCore.addPeer(newPeer) + + def addMessage(self): + while True: + messageToAdd = '-txt-' + logger.readline('Broadcast message to network: ') + if len(messageToAdd) >= 1: + break + addedHash = self.onionrCore.setData(messageToAdd) + self.onionrCore.addToBlockDB(addedHash, selfInsert=True) + self.onionrCore.setBlockType(addedHash, 'txt') + + def notFound(self): + logger.error('Command not found.') + + def showHelpSuggestion(self): + logger.info('Do ' + logger.colors.bold + sys.argv[0] + ' --help' + logger.colors.reset + logger.colors.fg.green + ' for Onionr help.') + + def start(self): + if os.path.exists('.onionr-lock'): + 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.daemon() + if not self.debug and not self._developmentMode: + os.remove('.onionr-lock') + def daemon(self): - ''' Start the Onionr communication daemon - ''' + ''' Start the Onionr communication daemon ''' if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": net = NetController(self.config['CLIENT']['PORT']) logger.info('Tor is starting...') @@ -158,9 +206,12 @@ class Onionr: subprocess.Popen(["./communicator.py", "run", str(net.socksPort)]) logger.debug('Started communicator') api.API(self.config, self.debug) + return + def killDaemon(self): - '''Shutdown the Onionr Daemon''' + ''' Shutdown the Onionr Daemon ''' + logger.warn('Killing the running daemon') net = NetController(self.config['CLIENT']['PORT']) try: @@ -169,11 +220,18 @@ class Onionr: pass self.onionrCore.daemonQueueAdd('shutdown') net.killTor() + return + def showStats(self): - '''Display statistics and exit''' + ''' Display statistics and exit ''' + return + def showHelp(self): - '''Show help for Onionr''' + ''' Show help for Onionr ''' + return + Onionr() + From 62cad7a6ea698fa1c9c757de65d7ddc75c74f41c Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 3 Feb 2018 19:44:29 -0800 Subject: [PATCH 31/89] Code consistency updates - Improved formatting - Added comments - URL encoded values in netcontroller.performGET - Kept SQL statement case consistency --- onionr/api.py | 28 ++++-- onionr/colors.py | 23 ----- onionr/communicator.py | 72 +++++++++++---- onionr/core.py | 190 +++++++++++++++++++++++++--------------- onionr/netcontroller.py | 32 ++++--- onionr/onionr.py | 1 - onionr/onionrcrypto.py | 5 +- onionr/onionrutils.py | 51 ++++++++--- onionr/tests.py | 9 ++ onionr/timedHmac.py | 11 +-- 10 files changed, 275 insertions(+), 147 deletions(-) delete mode 100644 onionr/colors.py diff --git a/onionr/api.py b/onionr/api.py index b361e959..a5e75e28 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -25,10 +25,12 @@ import configparser, sys, random, threading, hmac, hashlib, base64, time, math, from core import Core import onionrutils class API: - ''' Main http api (flask)''' + ''' + Main HTTP API (Flask) + ''' def validateToken(self, token): ''' - Validate if the client token (hmac) matches the given token + Validate that the client token (hmac) matches the given token ''' if self.clientToken != token: return False @@ -36,10 +38,11 @@ class API: return True def __init__(self, config, debug): - ''' Initialize the api server, preping variables for later use - This initilization defines all of the API entry points and handlers for the endpoints and errors + ''' + Initialize the api server, preping variables for later use - This also saves the used host (random localhost IP address) to the data folder in host.txt + 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 ''' if os.path.exists('dev-enabled'): self._developmentMode = True @@ -72,9 +75,10 @@ class API: @app.before_request def beforeReq(): ''' - Simply define the request as not having yet failed, before every request. + Simply define the request as not having yet failed, before every request. ''' self.requestFailed = False + return @app.after_request @@ -87,6 +91,7 @@ class API: resp.headers["Content-Security-Policy"] = "default-src 'none'" resp.headers['X-Frame-Options'] = 'deny' resp.headers['X-Content-Type-Options'] = "nosniff" + return resp @app.route('/client/') @@ -112,6 +117,7 @@ class API: elapsed = endTime - startTime if elapsed < self._privateDelayTime: time.sleep(self._privateDelayTime - elapsed) + return resp @app.route('/public/') @@ -149,17 +155,21 @@ class API: def notfound(err): self.requestFailed = True resp = Response("") - #resp.headers = getHeaders(resp) + return resp + @app.errorhandler(403) def authFail(err): self.requestFailed = True resp = Response("403") + return resp + @app.errorhandler(401) def clientError(err): self.requestFailed = True resp = Response("Invalid request") + return resp logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...') @@ -168,7 +178,9 @@ class API: app.run(host=self.host, port=bindPort, debug=True, threaded=True) def validateHost(self, hostType): - ''' Validate various features of the request including: + ''' + Validate various features of the request including: + If private (/client/), is the host header local? If public (/public/), is the host header onion or i2p? diff --git a/onionr/colors.py b/onionr/colors.py deleted file mode 100644 index bb3177f4..00000000 --- a/onionr/colors.py +++ /dev/null @@ -1,23 +0,0 @@ -''' -Simply define terminal control codes (mainly colors) -''' -class Colors: - def __init__(self): - ''' - PURPLE='\033[95m' - BLUE='\033[94m' - GREEN='\033[92m' - YELLOW='\033[93m' - RED='\033[91m' - BOLD='\033[1m' - UNDERLINE='\033[4m' - RESET="\x1B[m" - ''' - self.PURPLE='\033[95m' - self.BLUE='\033[94m' - self.GREEN='\033[92m' - self.YELLOW='\033[93m' - self.RED='\033[91m' - self.BOLD='\033[1m' - self.UNDERLINE='\033[4m' - self.RESET="\x1B[m" diff --git a/onionr/communicator.py b/onionr/communicator.py index 413cdfd3..9fd31550 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -19,13 +19,15 @@ and code to operate as a daemon, getting commands from the command queue databas You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sqlite3, requests, hmac, hashlib, time, sys, os, logger +import sqlite3, requests, hmac, hashlib, time, sys, os, logger, urllib.parse import core, onionrutils + class OnionrCommunicate: def __init__(self, debug, developmentMode): - ''' OnionrCommunicate + ''' + OnionrCommunicate - This class handles communication with nodes in the Onionr network. + This class handles communication with nodes in the Onionr network. ''' self._core = core.Core() self._utils = onionrutils.OnionrUtils(self._core) @@ -63,29 +65,46 @@ class OnionrCommunicate: logger.warn('Daemon recieved exit command.') break time.sleep(1) - return - def getRemotePeerKey(self, peerID): - '''This function contacts a peer and gets their main PGP key. - This is safe because Tor or I2P is used, but it does not ensure that the person is who they say they are + return + + def getRemotePeerKey(self, peerID): + ''' + This function contacts a peer and gets their main PGP key. + + This is safe because Tor or I2P is used, but it does not ensure that the person is who they say they are ''' url = 'http://' + peerID + '/public/?action=getPGP' r = requests.get(url, headers=headers) response = r.text + return response + def shareHMAC(self, peerID, key): - '''This function shares an HMAC key to a peer ''' + This function shares an HMAC key to a peer + ''' + return + def getPeerProof(self, peerID): - '''This function gets the current peer proof requirement''' + ''' + This function gets the current peer proof requirement + ''' + return + def sendPeerProof(self, peerID, data): - '''This function sends the proof result to a peer previously fetched with getPeerProof''' + ''' + This function sends the proof result to a peer previously fetched with getPeerProof + ''' + return def lookupBlocks(self): - '''Lookup blocks and merge new ones''' + ''' + Lookup blocks and merge new ones + ''' peerList = self._core.listPeers() blocks = '' for i in peerList: @@ -120,20 +139,26 @@ class OnionrCommunicate: else: logger.debug('Adding ' + i + ' to hash database...') self._core.addToBlockDB(i) + return + def processBlocks(self): ''' - Work with the block database and download any missing blocks - This is meant to be called from the communicator daemon on its timer. + Work with the block database and download any missing blocks + + This is meant to be called from the communicator daemon on its timer. ''' for i in self._core.getBlockList(True).split("\n"): if i != "": logger.warn('UNSAVED BLOCK: ' + i) data = self.downloadBlock(i) + return - + def downloadBlock(self, hash): - '''download a block from random order of peers''' + ''' + Download a block from random order of peers + ''' peerList = self._core.listPeers() blocks = '' for i in peerList: @@ -155,22 +180,33 @@ class OnionrCommunicate: else: logger.warn("Failed to validate " + hash) + return + + def urlencode(self, data): + ''' + URL encodes the data + ''' + return urllib.parse.quote_plus(data) + def performGet(self, action, peer, data=None, type='tor'): - '''Performs a request to a peer through Tor or i2p (currently only tor)''' + ''' + Performs a request to a peer through Tor or i2p (currently only Tor) + ''' if not peer.endswith('.onion') and not peer.endswith('.onion/'): raise PeerError('Currently only Tor .onion peers are supported. You must manually specify .onion') socksPort = sys.argv[2] '''We use socks5h to use tor as DNS''' proxies = {'http': 'socks5h://127.0.0.1:' + str(socksPort), 'https': 'socks5h://127.0.0.1:' + str(socksPort)} headers = {'user-agent': 'PyOnionr'} - url = 'http://' + peer + '/public/?action=' + action + url = 'http://' + peer + '/public/?action=' + urlencode(action) if data != None: - url = url + '&data=' + data + url = url + '&data=' + urlencode(data) try: r = requests.get(url, headers=headers, proxies=proxies, timeout=(15, 30)) except requests.exceptions.RequestException as e: logger.warn(action + " failed with peer " + peer + ": " + str(e)) return False + return r.text diff --git a/onionr/core.py b/onionr/core.py index 5cfa2015..d7ac589c 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -34,7 +34,7 @@ if sys.version_info < (3, 6): class Core: def __init__(self): ''' - Initialize Core Onionr library + Initialize Core Onionr library ''' self.queueDB = 'data/queue.db' self.peerDB = 'data/peers.db' @@ -54,87 +54,101 @@ class Core: return def generateMainPGP(self, myID): - ''' Generate the main PGP key for our client. Should not be done often. - Uses own PGP home folder in the data/ directory. ''' - # Generate main pgp key + ''' + Generate the main PGP key for our client. Should not be done often. + + Uses own PGP home folder in the data/ directory + ''' gpg = gnupg.GPG(homedir='./data/pgp/') input_data = gpg.gen_key_input(key_type="RSA", key_length=1024, name_real=myID, name_email='anon@onionr', testing=True) - #input_data = gpg.gen_key_input(key_type="RSA", key_length=1024) key = gpg.gen_key(input_data) logger.info("Generating PGP key, this will take some time..") while key.status != "key created": time.sleep(0.5) print(key.status) + logger.info("Finished generating PGP key") # Write the key myFingerpintFile = open('data/own-fingerprint.txt', 'w') myFingerpintFile.write(key.fingerprint) myFingerpintFile.close() + return def addPeer(self, peerID, name=''): - ''' Add a peer by their ID, with an optional name, to the peer database.''' - ''' DOES NO SAFETY CHECKS if the ID is valid, but prepares the insertion. ''' + ''' + Add a peer by their ID, with an optional name, to the peer database + + DOES NO SAFETY CHECKS if the ID is valid, but prepares the insertion + ''' # This function simply adds a peer to the DB if not self._utils.validateID(peerID): return False conn = sqlite3.connect(self.peerDB) c = conn.cursor() t = (peerID, name, 'unknown') - c.execute('insert into peers (id, name, dateSeen) values(?, ?, ?);', t) + c.execute('INSERT INTO peers (id, name, dateSeen) VALUES(?, ?, ?);', t) conn.commit() conn.close() return True def createPeerDB(self): ''' - Generate the peer sqlite3 database and populate it with the peers table. + Generate the peer sqlite3 database and populate it with the peers table. ''' # generate the peer database conn = sqlite3.connect(self.peerDB) c = conn.cursor() - c.execute(''' - create table peers( - ID text not null, - name text, - pgpKey text, - hmacKey text, - blockDBHash text, - forwardKey text, - dateSeen not null, - bytesStored int, - trust int); + c.execute('''CREATE TABLE peers( + ID text not null, + name text, + pgpKey text, + hmacKey text, + blockDBHash text, + forwardKey text, + dateSeen not null, + bytesStored int, + trust int); ''') conn.commit() conn.close() + + return + def createBlockDB(self): ''' - Create a database for blocks + Create a database for blocks - hash - the hash of a block - dateReceived - the date the block was recieved, not necessarily when it was created - decrypted - if we can successfully decrypt the block (does not describe its current state) - dataType - data type of the block - dataFound - if the data has been found for the block - dataSaved - if the data has been saved for the block + hash - the hash of a block + dateReceived - the date the block was recieved, not necessarily when it was created + decrypted - if we can successfully decrypt the block (does not describe its current state) + dataType - data type of the block + dataFound - if the data has been found for the block + dataSaved - if the data has been saved for the block ''' if os.path.exists(self.blockDB): raise Exception("Block database already exists") conn = sqlite3.connect(self.blockDB) c = conn.cursor() - c.execute('''create table hashes( + c.execute('''CREATE TABLE hashes( hash text not null, dateReceived int, decrypted int, dataType text, dataFound int, - dataSaved int - ); + dataSaved int); ''') conn.commit() conn.close() + + return + def addToBlockDB(self, newHash, selfInsert=False): - '''add a hash value to the block db (should be in hex format)''' + ''' + Add a hash value to the block db + + Should be in hex format! + ''' if not os.path.exists(self.blockDB): raise Exception('Block db does not exist') if self._utils.hasBlock(newHash): @@ -147,22 +161,29 @@ class Core: else: selfInsert = 0 data = (newHash, currentTime, 0, '', 0, selfInsert) - c.execute('INSERT into hashes values(?, ?, ?, ?, ?, ?);', data) + c.execute('INSERT INTO hashes VALUES(?, ?, ?, ?, ?, ?);', data) conn.commit() conn.close() + return + def getData(self,hash): - '''simply return the data associated to a hash''' + ''' + Simply return the data associated to a hash + ''' try: dataFile = open(self.blockDataLocation + hash + '.dat') data = dataFile.read() dataFile.close() except FileNotFoundError: data = False + return data def setData(self, data): - '''set the data assciated with a hash''' + ''' + Set the data assciated with a hash + ''' data = data.encode() hasher = hashlib.sha3_256() hasher.update(data) @@ -171,7 +192,7 @@ class Core: dataHash = dataHash.decode() blockFileName = self.blockDataLocation + dataHash + '.dat' if os.path.exists(blockFileName): - pass # to do, properly check if block is already saved elsewhere + pass # TODO: properly check if block is already saved elsewhere #raise Exception("Data is already set for " + dataHash) else: blockFile = open(blockFileName, 'w') @@ -180,7 +201,7 @@ class Core: conn = sqlite3.connect(self.blockDB) c = conn.cursor() - c.execute("UPDATE hashes set dataSaved=1 where hash = '" + dataHash + "';") + c.execute("UPDATE hashes SET dataSaved=1 WHERE hash = '" + dataHash + "';") conn.commit() conn.close() @@ -188,9 +209,8 @@ class Core: def dataDirEncrypt(self, password): ''' - Encrypt the data directory on Onionr shutdown + Encrypt the data directory on Onionr shutdown ''' - # Encrypt data directory (don't delete it in this function) if os.path.exists('data.tar'): os.remove('data.tar') tar = tarfile.open("data.tar", "w") @@ -201,12 +221,13 @@ class Core: encrypted = simplecrypt.encrypt(password, tarData) open('data-encrypted.dat', 'wb').write(encrypted) os.remove('data.tar') + return + def dataDirDecrypt(self, password): ''' - Decrypt the data directory on startup + Decrypt the data directory on startup ''' - # Decrypt data directory if not os.path.exists('data-encrypted.dat'): return (False, 'encrypted archive does not exist') data = open('data-encrypted.dat', 'rb').read() @@ -219,13 +240,15 @@ class Core: tar = tarfile.open('data.tar') tar.extractall() tar.close() + return (True, '') + def daemonQueue(self): ''' - Gives commands to the communication proccess/daemon by reading an sqlite3 database + Gives commands to the communication proccess/daemon by reading an sqlite3 database + + This function intended to be used by the client. Queue to exchange data between "client" and server. ''' - # This function intended to be used by the client - # Queue to exchange data between "client" and server. retData = False if not os.path.exists(self.queueDB): conn = sqlite3.connect(self.queueDB) @@ -241,7 +264,7 @@ class Core: retData = row break if retData != False: - c.execute('delete from commands where id = ?', (retData[3],)) + c.execute('DELETE FROM commands WHERE id=?;', (retData[3],)) conn.commit() conn.close() @@ -249,19 +272,23 @@ class Core: def daemonQueueAdd(self, command, data=''): ''' - Add a command to the daemon queue, used by the communication daemon (communicator.py) + Add a command to the daemon queue, used by the communication daemon (communicator.py) ''' # Intended to be used by the web server date = math.floor(time.time()) conn = sqlite3.connect(self.queueDB) c = conn.cursor() t = (command, data, date) - c.execute('INSERT into commands (command, data, date) values (?, ?, ?)', t) + c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t) conn.commit() conn.close() + return + def clearDaemonQueue(self): - '''clear the daemon queue (somewhat dangerousous)''' + ''' + Clear the daemon queue (somewhat dangerous) + ''' conn = sqlite3.connect(self.queueDB) c = conn.cursor() try: @@ -271,45 +298,49 @@ class Core: pass conn.close() - def generateHMAC(self): + return + + def generateHMAC(self, length=32): ''' - generate and return an HMAC key + Generate and return an HMAC key ''' - key = base64.b64encode(os.urandom(32)) + key = base64.b64encode(os.urandom(length)) + return key def listPeers(self, randomOrder=True): - '''Return a list of peers + ''' + Return a list of peers - randomOrder determines if the list should be in a random order + randomOrder determines if the list should be in a random order ''' conn = sqlite3.connect(self.peerDB) c = conn.cursor() if randomOrder: - peers = c.execute('SELECT * FROM peers order by RANDOM();') + peers = c.execute('SELECT * FROM peers ORDER BY RANDOM();') else: peers = c.execute('SELECT * FROM peers;') peerList = [] for i in peers: peerList.append(i[0]) conn.close() + return peerList def getPeerInfo(self, peer, info): ''' - get info about a peer + Get info about a peer from their database entry - id text 0 - name text, 1 - pgpKey text, 2 - hmacKey text, 3 - blockDBHash text, 4 - forwardKey text, 5 - dateSeen not null, 7 - bytesStored int, 8 - trust int 9 + id text 0 + name text, 1 + pgpKey text, 2 + hmacKey text, 3 + blockDBHash text, 4 + forwardKey text, 5 + dateSeen not null, 7 + bytesStored int, 8 + trust int 9 ''' - # Lookup something about a peer from their database entry conn = sqlite3.connect(self.peerDB) c = conn.cursor() command = (peer,) @@ -325,21 +356,29 @@ class Core: else: iterCount += 1 conn.close() + return retVal + def setPeerInfo(self, peer, key, data): - '''update a peer for a key''' + ''' + Update a peer for a key + ''' conn = sqlite3.connect(self.peerDB) c = conn.cursor() command = (data, peer) # TODO: validate key on whitelist if key not in ('id', 'text', 'name', 'pgpKey', 'hmacKey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'): raise Exception("Got invalid database key when setting peer info") - c.execute('UPDATE peers SET ' + key + ' = ? where id=?', command) + c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command) conn.commit() conn.close() + return + def getBlockList(self, unsaved=False): - '''get list of our blocks''' + ''' + Get list of our blocks + ''' conn = sqlite3.connect(self.blockDB) c = conn.cursor() retData = '' @@ -350,24 +389,33 @@ class Core: for row in c.execute(execute): for i in row: retData += i + "\n" + return retData def getBlocksByType(self, blockType): + ''' + Returns a list of blocks by the type + ''' conn = sqlite3.connect(self.blockDB) c = conn.cursor() retData = '' - execute = 'SELECT hash FROM hashes where dataType=?' + execute = 'SELECT hash FROM hashes WHERE dataType=?;' args = (blockType,) for row in c.execute(execute, args): for i in row: retData += i + "\n" + return retData.split('\n') def setBlockType(self, hash, blockType): + ''' + Sets the type of block + ''' + conn = sqlite3.connect(self.blockDB) c = conn.cursor() - #if blockType not in ("txt"): - # return c.execute("UPDATE hashes SET dataType='" + blockType + "' WHERE hash = '" + hash + "';") conn.commit() - conn.close() \ No newline at end of file + conn.close() + + return diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index a53d93cc..dcb3c3a5 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -19,8 +19,8 @@ ''' import subprocess, os, random, sys, logger, time, signal class NetController: - '''NetController - This class handles hidden service setup on Tor and I2P + ''' + This class handles hidden service setup on Tor and I2P ''' def __init__(self, hsPort): self.torConfigLocation = 'data/torrc' @@ -30,15 +30,19 @@ class NetController: self._torInstnace = '' self.myID = '' ''' - if os.path.exists(self.torConfigLocation): - torrc = open(self.torConfigLocation, 'r') - if not str(self.hsPort) in torrc.read(): - os.remove(self.torConfigLocation) - torrc.close() + if os.path.exists(self.torConfigLocation): + torrc = open(self.torConfigLocation, 'r') + if not str(self.hsPort) in torrc.read(): + os.remove(self.torConfigLocation) + torrc.close() ''' + return + def generateTorrc(self): - '''generate a torrc file for our tor instance''' + ''' + Generate a torrc file for our tor instance + ''' if os.path.exists(self.torConfigLocation): os.remove(self.torConfigLocation) torrcData = '''SocksPort ''' + str(self.socksPort) + ''' @@ -48,10 +52,12 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' torrc = open(self.torConfigLocation, 'w') torrc.write(torrcData) torrc.close() + return def startTor(self): - '''Start Tor with onion service on port 80 & socks proxy on random port + ''' + Start Tor with onion service on port 80 & socks proxy on random port ''' self.generateTorrc() if os.path.exists('./tor'): @@ -80,9 +86,13 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' torPidFile = open('data/torPid.txt', 'w') torPidFile.write(str(tor.pid)) torPidFile.close() + return True + def killTor(self): - '''properly kill tor based on pid saved to file''' + ''' + Properly kill tor based on pid saved to file + ''' try: pid = open('data/torPid.txt', 'r') pidN = pid.read() @@ -95,3 +105,5 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' return os.kill(int(pidN), signal.SIGTERM) os.remove('data/torPid.txt') + + return diff --git a/onionr/onionr.py b/onionr/onionr.py index 120280cb..48ffc758 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -234,4 +234,3 @@ class Onionr: return Onionr() - diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 9624fb11..fed23889 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -22,9 +22,12 @@ import nacl class OnionrCrypto: def __init__(self): return + def symmetricPeerEncrypt(self, data, key): return + def symmetricPeerDecrypt(self, data, key): return + def rsaEncrypt(self, peer, data): - return \ No newline at end of file + return diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 1884bb5a..ca575073 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -32,15 +32,22 @@ class OnionrUtils: self._core = coreInstance return def localCommand(self, command): - '''Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers.''' + ''' + Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers. + ''' config = configparser.ConfigParser() if os.path.exists('data/config.ini'): config.read('data/config.ini') else: return requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config['CLIENT']['PORT']) + '/client/?action=' + command + '&token=' + config['CLIENT']['CLIENT HMAC']) + + return + def getPassword(self, message='Enter password: ', confirm = True): - '''Get a password without showing the users typing and confirm the input''' + ''' + Get a password without showing the users typing and confirm the input + ''' # Get a password safely with confirmation and return it while True: print(message) @@ -55,9 +62,13 @@ class OnionrUtils: break else: break + return pass1 - def checkPort(self, port, host = ''): - '''Checks if a port is available, returns bool''' + + def checkPort(self, port, host=''): + ''' + Checks if a port is available, returns bool + ''' # inspired by https://www.reddit.com/r/learnpython/comments/2i4qrj/how_to_write_a_python_script_that_checks_to_see/ckzarux/ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) retVal = False @@ -68,36 +79,49 @@ class OnionrUtils: retVal = True finally: sock.close() + return retVal + def checkIsIP(self, ip): - '''Check if a string is a valid ipv4 address''' + ''' + Check if a string is a valid IPv4 address + ''' try: socket.inet_aton(ip) except: return False else: return True + def exportMyPubkey(self): - '''Export our PGP key if it exists''' + ''' + Export our PGP key if it exists + ''' if not os.path.exists(self.fingerprintFile): raise Exception("No fingerprint found, cannot export our PGP key.") gpg = gnupg.GPG(homedir='./data/pgp/') with open(self.fingerprintFile,'r') as f: fingerprint = f.read() ascii_armored_public_keys = gpg.export_keys(fingerprint) + return ascii_armored_public_keys def getBlockDBHash(self): - '''Return a sha3_256 hash of the blocks DB''' + ''' + Return a sha3_256 hash of the blocks DB + ''' with open(self._core.blockDB, 'rb') as data: data = data.read() hasher = hashlib.sha3_256() hasher.update(data) dataHash = hasher.hexdigest() + return dataHash def hasBlock(self, hash): - '''detect if we have a block in the list or not''' + ''' + Check for new block in the list + ''' conn = sqlite3.connect(self._core.blockDB) c = conn.cursor() if not self.validateHash(hash): @@ -113,7 +137,9 @@ class OnionrUtils: return False def validateHash(self, data, length=64): - '''Validate if a string is a valid hex formatted hash''' + ''' + Validate if a string is a valid hex formatted hash + ''' retVal = True if data == False or data == True: return False @@ -125,9 +151,13 @@ class OnionrUtils: int(data, 16) except ValueError: retVal = False + return retVal + def validateID(self, id): - '''validate if a user ID is a valid tor or i2p hidden service''' + ''' + Validate if a user ID is a valid tor or i2p hidden service + ''' idLength = len(id) retVal = True idNoDomain = '' @@ -165,4 +195,5 @@ class OnionrUtils: retVal = False if not idNoDomain.isalnum(): retVal = False + return retVal diff --git a/onionr/tests.py b/onionr/tests.py index d296d948..8babbdfd 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -23,6 +23,7 @@ class OnionrTests(unittest.TestCase): self.assertTrue(False) else: self.assertTrue(True) + def testNone(self): logger.debug('--------------------------') logger.info('Running simple program run test...') @@ -32,6 +33,7 @@ class OnionrTests(unittest.TestCase): self.assertTrue(False) else: self.assertTrue(True) + def testPeer_a_DBCreation(self): logger.debug('--------------------------') logger.info('Running peer db creation test...') @@ -44,6 +46,7 @@ class OnionrTests(unittest.TestCase): self.assertTrue(True) else: self.assertTrue(False) + def testPeer_b_addPeerToDB(self): logger.debug('--------------------------') logger.info('Running peer db insertion test...') @@ -55,6 +58,7 @@ class OnionrTests(unittest.TestCase): self.assertTrue(True) else: self.assertTrue(False) + def testData_b_Encrypt(self): self.assertTrue(True) return @@ -67,6 +71,7 @@ class OnionrTests(unittest.TestCase): self.assertTrue(True) else: self.assertTrue(False) + def testData_a_Decrypt(self): self.assertTrue(True) return @@ -79,6 +84,7 @@ class OnionrTests(unittest.TestCase): self.assertTrue(True) else: self.assertTrue(False) + def testPGPGen(self): logger.debug('--------------------------') logger.info('Running PGP key generation test...') @@ -93,6 +99,7 @@ class OnionrTests(unittest.TestCase): myCore.generateMainPGP(torID) if os.path.exists('data/pgp/'): self.assertTrue(True) + def testHMACGen(self): logger.debug('--------------------------') logger.info('Running HMAC generation test...') @@ -104,6 +111,7 @@ class OnionrTests(unittest.TestCase): self.assertTrue(True) else: self.assertTrue(False) + def testQueue(self): logger.debug('--------------------------') logger.info('Running daemon queue test...') @@ -124,4 +132,5 @@ class OnionrTests(unittest.TestCase): if command[0] == 'testCommand': if myCore.daemonQueue() == False: logger.info('Succesfully added and read command') + unittest.main() diff --git a/onionr/timedHmac.py b/onionr/timedHmac.py index d703a905..2f23317f 100644 --- a/onionr/timedHmac.py +++ b/onionr/timedHmac.py @@ -16,12 +16,12 @@ import hmac, base64, time, math class TimedHMAC: def __init__(self, base64Key, data, hashAlgo): ''' - base64Key = base64 encoded key - data = data to hash - expire = time expiry in epoch - hashAlgo = string in hashlib.algorithms_available + base64Key = base64 encoded key + data = data to hash + expire = time expiry in epoch + hashAlgo = string in hashlib.algorithms_available - Maximum of 10 seconds grace period + Maximum of 10 seconds grace period ''' self.data = data self.expire = math.floor(time.time()) @@ -30,6 +30,7 @@ class TimedHMAC: generatedHMAC = hmac.HMAC(base64.b64decode(base64Key).decode(), digestmod=self.hashAlgo) generatedHMAC.update(data + expire) self.HMACResult = generatedHMAC.hexdigest() + return def check(self, data): From 1cfb7796c57c9eba3ccb82332e4fe2958240a595 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 3 Feb 2018 20:30:16 -0800 Subject: [PATCH 32/89] Add Makefile for testing, installing, and uninstalling --- Makefile | 19 +++++++++++++++++++ test.sh | 3 --- 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 Makefile delete mode 100755 test.sh diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..193fbecf --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +.DEFAULT_GOAL := setup + +setup: + sudo pip3 install -r requirements.txt + sudo rm -rf /usr/share/onionr/ + sudo rm -f /usr/bin/onionr + +install: + sudo cp -rp ./onionr /usr/share/onionr + sudo sh -c "echo \"#!/bin/sh\ncd /usr/share/onionr/\n./onionr.py \\\"\\\$$@\\\"\" > /usr/bin/onionr" + sudo chmod +x /usr/bin/onionr + sudo chown -R `whoami` /usr/share/onionr/ + +uninstall: + sudo rm -rf /usr/share/onionr + sudo rm -f /usr/bin/onionr + +test: + @cd onionr; ./tests.py diff --git a/test.sh b/test.sh deleted file mode 100755 index 7d658faf..00000000 --- a/test.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -cd onionr -./tests.py From efd7c287b7851a9b4ce4deeea6e84a23469b3eb7 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 3 Feb 2018 20:33:47 -0800 Subject: [PATCH 33/89] Fix travisci --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5854d61e..e1cee1fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ python: install: - sudo apt install gnupg tor - pip install -r requirements.txt -script: ./test.sh +script: make test From c57bffcae83d6c6d07ae8b9e5f688ac9b9c313ca Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 3 Feb 2018 21:22:34 -0800 Subject: [PATCH 34/89] Fix two bugs --- onionr/communicator.py | 4 ++-- onionr/onionr.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 9fd31550..a1d840f9 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -198,9 +198,9 @@ class OnionrCommunicate: '''We use socks5h to use tor as DNS''' proxies = {'http': 'socks5h://127.0.0.1:' + str(socksPort), 'https': 'socks5h://127.0.0.1:' + str(socksPort)} headers = {'user-agent': 'PyOnionr'} - url = 'http://' + peer + '/public/?action=' + urlencode(action) + url = 'http://' + peer + '/public/?action=' + self.urlencode(action) if data != None: - url = url + '&data=' + urlencode(data) + url = url + '&data=' + self.urlencode(data) try: r = requests.get(url, headers=headers, proxies=proxies, timeout=(15, 30)) except requests.exceptions.RequestException as e: diff --git a/onionr/onionr.py b/onionr/onionr.py index 48ffc758..9c12b1b7 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -21,7 +21,7 @@ along with this program. If not, see . ''' import sys, os, configparser, base64, random, getpass, shutil, subprocess, requests, time, logger, platform -import api, core +import api, core, gui from onionrutils import OnionrUtils from netcontroller import NetController From 6ca70afb787942cc3eef1bebe5d187a28aff63f1 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 4 Feb 2018 03:20:43 -0600 Subject: [PATCH 35/89] we now temporarily keep track of peer connectivity history, eventually we will use this to ignore unstable/slow/offline peers --- onionr/communicator.py | 21 +++++++++++++++++---- onionr/gui.py | 3 ++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index a1d840f9..cd32d881 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -19,7 +19,7 @@ and code to operate as a daemon, getting commands from the command queue databas You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sqlite3, requests, hmac, hashlib, time, sys, os, logger, urllib.parse +import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse import core, onionrutils class OnionrCommunicate: @@ -38,6 +38,8 @@ class OnionrCommunicate: logger.debug('Communicator debugging enabled.') torID = open('data/hs/hostname').read() + self.peerData = {} # Session data for peers (recent reachability, speed, etc) + # get our own PGP fingerprint fingerprintFile = 'data/own-fingerprint.txt' if not os.path.exists(fingerprintFile): @@ -194,6 +196,11 @@ class OnionrCommunicate: ''' if not peer.endswith('.onion') and not peer.endswith('.onion/'): raise PeerError('Currently only Tor .onion peers are supported. You must manually specify .onion') + + # Store peer in peerData dictionary (non permanent) + if not peer in self.peerData: + self.peerData[peer] = {'connectCount': 0, 'failCount': 0, 'lastConnectTime': math.floor(time.time())} + socksPort = sys.argv[2] '''We use socks5h to use tor as DNS''' proxies = {'http': 'socks5h://127.0.0.1:' + str(socksPort), 'https': 'socks5h://127.0.0.1:' + str(socksPort)} @@ -203,11 +210,17 @@ class OnionrCommunicate: url = url + '&data=' + self.urlencode(data) try: r = requests.get(url, headers=headers, proxies=proxies, timeout=(15, 30)) + retData = r.text except requests.exceptions.RequestException as e: logger.warn(action + " failed with peer " + peer + ": " + str(e)) - return False - - return r.text + retData = False + + if not retData: + self.peerData[peer]['failCount'] += 1 + else: + self.peerData[peer]['connectCount'] += 1 + self.peerData[peer]['lastConnectTime'] = math.floor(time.time()) + return retData shouldRun = False diff --git a/onionr/gui.py b/onionr/gui.py index 5fa05331..3dd410ec 100755 --- a/onionr/gui.py +++ b/onionr/gui.py @@ -39,7 +39,7 @@ class OnionrGUI: self.sendEntry.pack() sendBtn.pack() - self.listbox = Listbox(self.root, yscrollcommand=scrollbar.set) + self.listbox = Listbox(self.root, yscrollcommand=scrollbar.set, height=15) #listbox.insert(END, str(i)) self.listbox.pack(fill=BOTH) @@ -63,6 +63,7 @@ class OnionrGUI: self.listbox.insert(END, str(blockFile.read().replace('-txt-', ''))) blockFile.close() self.listedBlocks.append(i) + self.listbox.see(END) blocksList = os.listdir('./data/blocks/') # dir is your directory path number_blocks = len(blocksList) From 494871290414f952120bdc5d566fb04140b85bd0 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 7 Feb 2018 03:04:58 -0600 Subject: [PATCH 36/89] work on peer encryption --- .gitignore | 1 + onionr/api.py | 7 +++++-- onionr/core.py | 10 ++++++---- onionr/onionrcrypto.py | 10 ++++++++-- onionr/onionrutils.py | 11 +++++++++++ readme.md | 2 ++ 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 0d9c0eda..2a7d956a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ onionr/*.pyc onionr/*.log onionr/data/hs/hostname onionr/data/* +onionr/gnupg/* diff --git a/onionr/api.py b/onionr/api.py index a5e75e28..efb420c6 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -23,7 +23,7 @@ from multiprocessing import Process import configparser, sys, random, threading, hmac, hashlib, base64, time, math, gnupg, os, logger from core import Core -import onionrutils +import onionrutils, onionrcrypto class API: ''' Main HTTP API (Flask) @@ -56,6 +56,7 @@ class API: self.debug = debug self._privateDelayTime = 3 self._core = Core() + self._crypto = onionrcrypto.OnionrCrypto(self._core) self._utils = onionrutils.OnionrUtils(self._core) app = flask.Flask(__name__) bindPort = int(self.config['CLIENT']['PORT']) @@ -131,7 +132,9 @@ class API: pass elif action == 'ping': resp = Response("pong!") - elif action == 'setHMAC': + elif action == 'getHMAC': + resp = Response(self._crypto.generateHMAC()) + elif action == 'getSymmetric': pass elif action == 'getDBHash': resp = Response(self._utils.getBlockDBHash()) diff --git a/onionr/core.py b/onionr/core.py index d7ac589c..93a1bc7c 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -18,11 +18,11 @@ along with this program. If not, see . ''' import sqlite3, os, sys, time, math, gnupg, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger -from Crypto.Cipher import AES -from Crypto import Random +#from Crypto.Cipher import AES +#from Crypto import Random import netcontroller -import onionrutils +import onionrutils, onionrcrypto if sys.version_info < (3, 6): try: @@ -41,7 +41,9 @@ class Core: self.ownPGPID = '' self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' + self.gpgHome = './data/pgp/' self._utils = onionrutils.OnionrUtils(self) + self._crypto = onionrcrypto.OnionrCrypto(self) if not os.path.exists('data/'): os.mkdir('data/') @@ -59,7 +61,7 @@ class Core: Uses own PGP home folder in the data/ directory ''' - gpg = gnupg.GPG(homedir='./data/pgp/') + gpg = gnupg.GPG(homedir=self.gpgHome) input_data = gpg.gen_key_input(key_type="RSA", key_length=1024, name_real=myID, name_email='anon@onionr', testing=True) key = gpg.gen_key(input_data) logger.info("Generating PGP key, this will take some time..") diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index fed23889..ccfcee7b 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -17,10 +17,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import nacl +import nacl, gnupg class OnionrCrypto: - def __init__(self): + def __init__(self, coreInstance): + self._core = coreInstance return def symmetricPeerEncrypt(self, data, key): @@ -31,3 +32,8 @@ class OnionrCrypto: def rsaEncrypt(self, peer, data): return + + def verifyPGP(self, peer, signature): + '''Verify PGP signed data''' + gpg = gnupg.GPG(homedir=self._core.gpgHome) + \ No newline at end of file diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index ca575073..14ca52f1 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -153,6 +153,17 @@ class OnionrUtils: retVal = False return retVal + + def getPeerPGPFingerprint(self, peer): + ''' + Get peer's PGP fingerprint + ''' + retData = '' + gpg = gnupg.GPG(homedir=self._core.gpgHome) + for i in gpg.list_keys(): + if peer in i['uids'][0]: + retData = i['fingerprint'] + return retData def validateID(self, id): ''' diff --git a/readme.md b/readme.md index 730d1270..fea71132 100644 --- a/readme.md +++ b/readme.md @@ -13,6 +13,8 @@ Major work in progress. This software is in heavy development. If for some reason you want to get involved, get in touch first. +**Onionr API and functionality is subject to non-backwards compatible change during development** + ## Disclaimer The Tor Project, I2P developers, and anyone else do not own, create, or endorse this project, and are not otherwise involved. From a0dc95c291a56029f83a94b544ed042dc5558226 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 8 Feb 2018 03:14:32 -0600 Subject: [PATCH 37/89] work on peer private messages (& crypto) --- onionr/communicator.py | 35 +---------------------------------- onionr/onionr.py | 13 +++++++++++++ onionr/onionrutils.py | 5 +++++ 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index cd32d881..2e383fb2 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -69,40 +69,7 @@ class OnionrCommunicate: time.sleep(1) return - - def getRemotePeerKey(self, peerID): - ''' - This function contacts a peer and gets their main PGP key. - - This is safe because Tor or I2P is used, but it does not ensure that the person is who they say they are - ''' - url = 'http://' + peerID + '/public/?action=getPGP' - r = requests.get(url, headers=headers) - response = r.text - - return response - - def shareHMAC(self, peerID, key): - ''' - This function shares an HMAC key to a peer - ''' - - return - - def getPeerProof(self, peerID): - ''' - This function gets the current peer proof requirement - ''' - - return - - def sendPeerProof(self, peerID, data): - ''' - This function sends the proof result to a peer previously fetched with getPeerProof - ''' - - return - + def lookupBlocks(self): ''' Lookup blocks and merge new ones diff --git a/onionr/onionr.py b/onionr/onionr.py index 9c12b1b7..c4d360af 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -128,6 +128,7 @@ class Onionr: 'addmessage': self.addMessage, 'add-msg': self.addMessage, 'add-message': self.addMessage, + 'pm': self.sendEncrypt, 'gui': self.openGUI, 'addpeer': self.addPeer, 'add-peer': self.addPeer @@ -150,6 +151,18 @@ class Onionr: logger.info('Onionr ' + ONIONR_VERSION + ' (' + platform.machine() + ') : API v' + API_VERSION) logger.info('Running on ' + platform.platform() + ' ' + platform.release()) + def sendEncrypt(self): + '''Create a private message and send it''' + while True: + peer = logger.readline('Peer to send to: ') + if self.onionrUtils.validateID(peer): + break + else: + logger.error('Invalid peer ID') + message = logger.readline("Enter a message: ") + logger.info("Sending message to " + peer) + + def openGUI(self): gui.OnionrGUI(self.onionrCore) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 14ca52f1..619e5927 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -208,3 +208,8 @@ class OnionrUtils: retVal = False return retVal + + def sendPM(self, peer, message): + '''Send an encrypted private message to a user''' + + return From 297cac81ae4b7e9e2a1e662d5f229e97a9dae5aa Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 8 Feb 2018 16:58:39 -0600 Subject: [PATCH 38/89] work on peer encryption --- onionr/api.py | 4 ++-- onionr/onionr.py | 1 + onionr/onionrcrypto.py | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index efb420c6..92ba30a1 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -133,9 +133,9 @@ class API: elif action == 'ping': resp = Response("pong!") elif action == 'getHMAC': - resp = Response(self._crypto.generateHMAC()) + resp = Response(self._crypto.generateSymmetric()) elif action == 'getSymmetric': - pass + resp = Response(self._crypto.generateSymmetric()) elif action == 'getDBHash': resp = Response(self._utils.getBlockDBHash()) elif action == 'getBlockHashes': diff --git a/onionr/onionr.py b/onionr/onionr.py index c4d360af..87b02442 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -161,6 +161,7 @@ class Onionr: logger.error('Invalid peer ID') message = logger.readline("Enter a message: ") logger.info("Sending message to " + peer) + self.onionrUtils.sendPM(peer, message) def openGUI(self): diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index ccfcee7b..79bd339e 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -36,4 +36,8 @@ class OnionrCrypto: def verifyPGP(self, peer, signature): '''Verify PGP signed data''' gpg = gnupg.GPG(homedir=self._core.gpgHome) - \ No newline at end of file + + def generateSymmetric(): + return + def generateHMAC(): + return \ No newline at end of file From 895b1919fd2ffe6b617e3277d76496f76369671c Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 15 Feb 2018 23:31:30 -0500 Subject: [PATCH 39/89] removed PGP --- docs/onionr-draft.md | 75 +++++++++++++++--------------------------- onionr/api.py | 4 +-- onionr/communicator.py | 7 ---- onionr/core.py | 34 +++---------------- onionr/onionrcrypto.py | 10 ++---- onionr/onionrutils.py | 26 +-------------- onionr/tests.py | 27 --------------- requirements.txt | 8 ++--- 8 files changed, 36 insertions(+), 155 deletions(-) diff --git a/docs/onionr-draft.md b/docs/onionr-draft.md index 26fbf18f..6fb79cbe 100644 --- a/docs/onionr-draft.md +++ b/docs/onionr-draft.md @@ -1,72 +1,49 @@ -# Onionr Protocol Spec +# Onionr Protocol Spec v2 -A social network/microblogging platform for Tor & I2P - -Draft Dec 25 2017 +A P2P platform for Tor & I2P # Overview Onionr is an encrypted microblogging & mailing system designed in the spirit of Twitter. There are no central servers and all traffic is peer to peer by default (routed via Tor or I2P). -User IDs are simply Tor onion service/I2P host id + PGP fingerprint. -Clients consolidate feeds from peers into 1 “timeline” using RSS format. -Private messages are only accessible by the intended peer based on the PGP id. -Onionr is not intended to be a replacement for Ricochet, OnionShare, or Briar. -All traffic is over onion/I2P because if only some was, then that would make that traffic inherently suspicious. +User IDs are simply Tor onion service/I2P host id + Ed25519 key fingerprint. +Private blocks are only able to be read by the intended peer. +All traffic is over Tor/I2P, connecting only to Tor onion and I2P hidden services. + ## Goals: - • Selective sharing of information with friends & public + • Selective sharing of information • Secure & semi-anonymous direct messaging • Forward secrecy • Defense in depth - • Data should be secure for years to come, quantum safe (though not necessarily every “layer”) + • Data should be secure for years to come • Decentralization * Avoid browser-based exploits that plague similar software * Avoid timing attacks & unexpected metadata leaks -## Assumptions: - • Tor & I2P’s transport protocols & AES-256 are not broken, sha3-512 2nd preimage attacks will remain infeasible indefinitely - • All traffic is logged indefinitely by powerful adversaries + ## Protocol -Clients MUST use HTTP(s) to communicate with one another to maintain compatibility cross platform. HTTPS is recommended, but HTTP is acceptable because Tor & I2P provide transport layer security. + +Onionr nodes use HTTP (over Tor/I2P) to exchange keys, metadata, and blocks. Blocks are identified by their sha3_256 hash. Nodes sync a table of blocks hashes and attempt to download blocks they do not yet have from random peers. + ## Connections - When a node first comes online, it attempts to bootstrap using a default list provided by a client. - When two peers connect, they exchange PGP public keys and then generate a shared AES-SHA3-512 HMAC token. These keys are stored in a peer database until expiry. - HMAC tokens are regenerated either every X many communications with a peer or every X minutes. Every 10MB or every 2 hours is a recommended default. - All valid requests with HMAC should be recorded until used HMAC's expiry to prevent replay attacks. - Peer Types - * Friends: - * Encrypted ‘friends only’ posts to one another - * Usually less strict rate & storage limits - * OPTIONALLY sign one another’s keys. Users may not want to do this in order to avoid exposing their entire friends list. - • Strangers: - * Used for storage of encrypted or public information - * Can only read public posts - * Usually stricter rate & storage limits -## Data Storage/Delivery - Posts (public or friends only) are stored across the network. - Private messages SHOULD be delivered directly if both peers are online, otherwise stored in the network. - Data SHOULD be stored in an entirely encrypted state when a client is offline, including metadata. Data SHOULD be stored in a minimal size with garbage data to ensure some level of plausible deniablity. - Data SHOULD be stored as long as the node’s user prefers and only erased once disk quota is reached due to new data. - Posts - Posts can contain text and images. All posts MUST be time stamped. - Images SHOULD not be displayed by non-friends by default, to prevent unwanted viewing of offensive material & to reduce attack surface. - All received posts must be verified to be stored and/or displayed to the user. +When a node first comes online, it attempts to bootstrap using a default list provided by a client. +When two peers connect, they exchange Ed25519 keys (if applicable) then Salsa20 keys. - All data being transfered MUST be encrypted to the end node receiving the data, then the data MUST be encrypted the node(s) transporting/storing the data, +Salsa20 keys are regenerated either every X many communications with a peer or every X minutes. - Posts have two settings: - • Friends only: - ◦ Posts MUST be encrypted to all trusted peers via AES256-HMAC-SHA256 and PGP signed (signed before encryption) and time stamped to prevent replaying. A temporary RSA key for use in every post (or message) is exchanged every X many configured post (or message), for use in addition with PGP and the HMAC. - • Public: - ◦ Posts MUST be PGP signed, and MUST NOT use any encryption. -## Private Messages +Every 100kb or every 2 hours is a recommended default. - Private messages are messages that can have attached images. They MUST be encrypted via AES256-HMAC-SHA256 and PGP signed (signed before encryption) and time stamped to prevent replaying. A temporary EdDSA key for use in every message is exchanged every X many configured messages (or posts), for use in addition with PGP and the HMAC. - When both peers are online messages SHOULD be dispatched directly between peers. - All messages must be verified prior to being displayed. +All valid requests with HMAC should be recorded until used HMAC's expiry to prevent replay attacks. +Peer Types + * Friends: + * Encrypted ‘friends only’ posts to one another + * Usually less strict rate & storage limits + * Strangers: + * Used for storage of encrypted or public information + * Can only read public posts + * Usually stricter rate & storage limits - Clients SHOULD allow configurable message padding. ## Spam mitigation To send or receive data, a node can optionally request that the other node generate a hash that when in hexadecimal representation contains a random string at a random location in the string. Clients will configure what difficulty to request, and what difficulty is acceptable for themselves to perform. Difficulty should correlate with recent network & disk usage and data size. Friends can be configured to have less strict (to non existent) limits, separately from strangers. (proof of work). -Rate limits can be strict, as Onionr is not intended to be an instant messaging application. +Rate limits can be strict, as Onionr is not intended to be an instant messaging application. \ No newline at end of file diff --git a/onionr/api.py b/onionr/api.py index 92ba30a1..73c65b48 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -20,7 +20,7 @@ import flask from flask import request, Response, abort from multiprocessing import Process -import configparser, sys, random, threading, hmac, hashlib, base64, time, math, gnupg, os, logger +import configparser, sys, random, threading, hmac, hashlib, base64, time, math, os, logger from core import Core import onionrutils, onionrcrypto @@ -140,8 +140,6 @@ class API: resp = Response(self._utils.getBlockDBHash()) elif action == 'getBlockHashes': resp = Response(self._core.getBlockList()) - elif action == 'getPGP': - resp = Response(self._utils.exportMyPubkey()) # setData should be something the communicator initiates, not this api elif action == 'getData': resp = self._core.getData(data) diff --git a/onionr/communicator.py b/onionr/communicator.py index 2e383fb2..7fe5ecd1 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -40,13 +40,6 @@ class OnionrCommunicate: self.peerData = {} # Session data for peers (recent reachability, speed, etc) - # get our own PGP fingerprint - fingerprintFile = 'data/own-fingerprint.txt' - if not os.path.exists(fingerprintFile): - self._core.generateMainPGP(torID) - with open(fingerprintFile,'r') as f: - self.pgpOwnFingerprint = f.read() - logger.info('My PGP fingerprint is ' + logger.colors.underline + self.pgpOwnFingerprint + logger.colors.reset + logger.colors.fg.green + '.') if os.path.exists(self._core.queueDB): self._core.clearDaemonQueue() while True: diff --git a/onionr/core.py b/onionr/core.py index 93a1bc7c..d1a03b31 100644 --- a/onionr/core.py +++ b/onionr/core.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 sqlite3, os, sys, time, math, gnupg, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger +import sqlite3, os, sys, time, math, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger #from Crypto.Cipher import AES #from Crypto import Random import netcontroller @@ -38,10 +38,8 @@ class Core: ''' self.queueDB = 'data/queue.db' self.peerDB = 'data/peers.db' - self.ownPGPID = '' self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' - self.gpgHome = './data/pgp/' self._utils = onionrutils.OnionrUtils(self) self._crypto = onionrcrypto.OnionrCrypto(self) @@ -55,28 +53,6 @@ class Core: return - def generateMainPGP(self, myID): - ''' - Generate the main PGP key for our client. Should not be done often. - - Uses own PGP home folder in the data/ directory - ''' - gpg = gnupg.GPG(homedir=self.gpgHome) - input_data = gpg.gen_key_input(key_type="RSA", key_length=1024, name_real=myID, name_email='anon@onionr', testing=True) - key = gpg.gen_key(input_data) - logger.info("Generating PGP key, this will take some time..") - while key.status != "key created": - time.sleep(0.5) - print(key.status) - - logger.info("Finished generating PGP key") - # Write the key - myFingerpintFile = open('data/own-fingerprint.txt', 'w') - myFingerpintFile.write(key.fingerprint) - myFingerpintFile.close() - - return - def addPeer(self, peerID, name=''): ''' Add a peer by their ID, with an optional name, to the peer database @@ -104,8 +80,7 @@ class Core: c.execute('''CREATE TABLE peers( ID text not null, name text, - pgpKey text, - hmacKey text, + pubkey text, blockDBHash text, forwardKey text, dateSeen not null, @@ -335,7 +310,6 @@ class Core: id text 0 name text, 1 - pgpKey text, 2 hmacKey text, 3 blockDBHash text, 4 forwardKey text, 5 @@ -346,7 +320,7 @@ class Core: conn = sqlite3.connect(self.peerDB) c = conn.cursor() command = (peer,) - infoNumbers = {'id': 0, 'name': 1, 'pgpKey': 2, 'hmacKey': 3, 'blockDBHash': 4, 'forwardKey': 5, 'dateSeen': 6, 'bytesStored': 7, 'trust': 8} + infoNumbers = {'id': 0, 'name': 1, 'hmacKey': 3, 'blockDBHash': 4, 'forwardKey': 5, 'dateSeen': 6, 'bytesStored': 7, 'trust': 8} info = infoNumbers[info] iterCount = 0 retVal = '' @@ -369,7 +343,7 @@ class Core: c = conn.cursor() command = (data, peer) # TODO: validate key on whitelist - if key not in ('id', 'text', 'name', 'pgpKey', 'hmacKey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'): + if key not in ('id', 'name', 'pubkey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'): raise Exception("Got invalid database key when setting peer info") c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command) conn.commit() diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 79bd339e..e9f89c7c 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.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 nacl, gnupg +import nacl class OnionrCrypto: def __init__(self, coreInstance): @@ -29,15 +29,9 @@ class OnionrCrypto: def symmetricPeerDecrypt(self, data, key): return - - def rsaEncrypt(self, peer, data): - return - - def verifyPGP(self, peer, signature): - '''Verify PGP signed data''' - gpg = gnupg.GPG(homedir=self._core.gpgHome) def generateSymmetric(): return + def generateHMAC(): return \ No newline at end of file diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 619e5927..ba082b98 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -18,7 +18,7 @@ along with this program. If not, see . ''' # Misc functions that do not fit in the main api, but are useful -import getpass, sys, requests, configparser, os, socket, gnupg, hashlib, logger, sqlite3 +import getpass, sys, requests, configparser, os, socket, hashlib, logger, sqlite3 if sys.version_info < (3, 6): try: import sha3 @@ -93,19 +93,6 @@ class OnionrUtils: else: return True - def exportMyPubkey(self): - ''' - Export our PGP key if it exists - ''' - if not os.path.exists(self.fingerprintFile): - raise Exception("No fingerprint found, cannot export our PGP key.") - gpg = gnupg.GPG(homedir='./data/pgp/') - with open(self.fingerprintFile,'r') as f: - fingerprint = f.read() - ascii_armored_public_keys = gpg.export_keys(fingerprint) - - return ascii_armored_public_keys - def getBlockDBHash(self): ''' Return a sha3_256 hash of the blocks DB @@ -153,17 +140,6 @@ class OnionrUtils: retVal = False return retVal - - def getPeerPGPFingerprint(self, peer): - ''' - Get peer's PGP fingerprint - ''' - retData = '' - gpg = gnupg.GPG(homedir=self._core.gpgHome) - for i in gpg.list_keys(): - if peer in i['uids'][0]: - retData = i['fingerprint'] - return retData def validateID(self, id): ''' diff --git a/onionr/tests.py b/onionr/tests.py index 8babbdfd..557e8885 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -85,33 +85,6 @@ class OnionrTests(unittest.TestCase): else: self.assertTrue(False) - def testPGPGen(self): - logger.debug('--------------------------') - logger.info('Running PGP key generation test...') - if os.path.exists('data/pgp/'): - self.assertTrue(True) - else: - import core, netcontroller - myCore = core.Core() - net = netcontroller.NetController(1337) - net.startTor() - torID = open('data/hs/hostname').read() - myCore.generateMainPGP(torID) - if os.path.exists('data/pgp/'): - self.assertTrue(True) - - def testHMACGen(self): - logger.debug('--------------------------') - logger.info('Running HMAC generation test...') - # Test if hmac key generation is working - import core - myCore = core.Core() - key = myCore.generateHMAC() - if len(key) > 10: - self.assertTrue(True) - else: - self.assertTrue(False) - def testQueue(self): logger.debug('--------------------------') logger.info('Running daemon queue test...') diff --git a/requirements.txt b/requirements.txt index da4c8869..7f937ff4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,6 @@ PyNaCl==1.2.1 -gnupg==2.3.1 +requests==2.12.4 Flask==0.12.2 -requests==2.18.4 -urllib3==1.22 simple_crypt==4.1.7 +urllib3==1.19.1 sha3==0.2.1 -pycrypto==2.6.1 -pynacl==1.2.1 -PySocks==1.6.8 From 38aa8620ae192e4c0622d797a849b9d9dd60d0b0 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 15 Feb 2018 23:39:34 -0500 Subject: [PATCH 40/89] removed PGP --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 7f937ff4..ea4566e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ Flask==0.12.2 simple_crypt==4.1.7 urllib3==1.19.1 sha3==0.2.1 +PySocks=1.6.8 From 15e0102be071861dbd77879ce0b9879629144269 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 15 Feb 2018 23:41:54 -0500 Subject: [PATCH 41/89] removed PGP --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ea4566e9..77e25537 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ Flask==0.12.2 simple_crypt==4.1.7 urllib3==1.19.1 sha3==0.2.1 -PySocks=1.6.8 +PySocks==1.6.8 From 586e9230cd61b3985c5bc60320eac87a98b51e9f Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 20 Feb 2018 20:44:56 -0600 Subject: [PATCH 42/89] fixed message spam, rewrote draft, work on crypto --- docs/onionr-draft.md | 2 ++ onionr/api.py | 8 ++++---- onionr/communicator.py | 3 ++- onionr/core.py | 3 ++- onionr/netcontroller.py | 9 +++++++-- onionr/onionr.py | 4 +++- onionr/onionrcrypto.py | 42 +++++++++++++++++++++++++++++++++++------ reset.sh | 5 +++++ 8 files changed, 61 insertions(+), 15 deletions(-) create mode 100755 reset.sh diff --git a/docs/onionr-draft.md b/docs/onionr-draft.md index 6fb79cbe..5ab91cb0 100644 --- a/docs/onionr-draft.md +++ b/docs/onionr-draft.md @@ -24,6 +24,8 @@ All traffic is over Tor/I2P, connecting only to Tor onion and I2P hidden service Onionr nodes use HTTP (over Tor/I2P) to exchange keys, metadata, and blocks. Blocks are identified by their sha3_256 hash. Nodes sync a table of blocks hashes and attempt to download blocks they do not yet have from random peers. +Blocks may be encrypted using Curve25519. + ## Connections When a node first comes online, it attempts to bootstrap using a default list provided by a client. diff --git a/onionr/api.py b/onionr/api.py index 73c65b48..666b7783 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -47,7 +47,7 @@ class API: if os.path.exists('dev-enabled'): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) - logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') + #logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') else: self._developmentMode = False logger.set_level(logger.LEVEL_INFO) @@ -172,9 +172,9 @@ class API: resp = Response("Invalid request") return resp - - logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...') - logger.debug('Client token: ' + logger.colors.underline + self.clientToken) + if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": + logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...') + logger.debug('Client token: ' + logger.colors.underline + self.clientToken) app.run(host=self.host, port=bindPort, debug=True, threaded=True) diff --git a/onionr/communicator.py b/onionr/communicator.py index 7fe5ecd1..4b6e49a8 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -20,7 +20,7 @@ and code to operate as a daemon, getting commands from the command queue databas along with this program. If not, see . ''' import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse -import core, onionrutils +import core, onionrutils, onionrcrypto class OnionrCommunicate: def __init__(self, debug, developmentMode): @@ -31,6 +31,7 @@ class OnionrCommunicate: ''' self._core = core.Core() self._utils = onionrutils.OnionrUtils(self._core) + self._crypto = onionrcrypto.OnionrCrypto(self._core) blockProcessTimer = 0 blockProcessAmount = 5 heartBeatTimer = 0 diff --git a/onionr/core.py b/onionr/core.py index d1a03b31..29d84a55 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -41,13 +41,14 @@ class Core: self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' self._utils = onionrutils.OnionrUtils(self) + + # Initialize the crypto object self._crypto = onionrcrypto.OnionrCrypto(self) if not os.path.exists('data/'): os.mkdir('data/') if not os.path.exists('data/blocks/'): os.mkdir('data/blocks/') - if not os.path.exists(self.blockDB): self.createBlockDB() diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index dcb3c3a5..058d8e4a 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -103,7 +103,12 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' int(pidN) except: return - os.kill(int(pidN), signal.SIGTERM) - os.remove('data/torPid.txt') + try: + os.kill(int(pidN), signal.SIGTERM) + os.remove('data/torPid.txt') + except ProcessLookupError: + pass + except FileNotFoundError: + pass return diff --git a/onionr/onionr.py b/onionr/onionr.py index 87b02442..d3c51a4f 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -47,7 +47,6 @@ class Onionr: if os.path.exists('dev-enabled'): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) - logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') else: self._developmentMode = False logger.set_level(logger.LEVEL_INFO) @@ -211,11 +210,14 @@ class Onionr: def daemon(self): ''' Start the Onionr communication daemon ''' if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": + if self._developmentMode: + logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') net = NetController(self.config['CLIENT']['PORT']) logger.info('Tor is starting...') if not net.startTor(): sys.exit(1) logger.info('Started Tor .onion service: ' + logger.colors.underline + net.myID) + logger.info('Our Public key: ' + self.onionrCore._crypto.pubKey) time.sleep(1) subprocess.Popen(["./communicator.py", "run", str(net.socksPort)]) logger.debug('Started communicator') diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index e9f89c7c..6b282341 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -17,21 +17,51 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import nacl +import nacl.signing, nacl.encoding, nacl.public, os class OnionrCrypto: def __init__(self, coreInstance): self._core = coreInstance + self._keyFile = 'data/keys.txt' + self.pubKey = None + self.privKey = None + + # Load our own pub/priv Ed25519 keys, gen & save them if they don't exist + if os.path.exists(self._keyFile): + with open('data/keys.txt', 'r') as keys: + keys = keys.read().split(',') + self.pubKey = keys[0] + self.privKey = keys[1] + else: + keys = self.generatePubKey() + self.pubKey = keys[0] + self.privKey = keys[1] + with open(self._keyFile, 'w') as keyfile: + keyfile.write(self.pubKey + ',' + self.privKey) return - def symmetricPeerEncrypt(self, data, key): + def pubKeyEncrypt(self, data, peer): + '''Encrypt to a peers public key (Curve25519, taken from Ed25519 pubkey)''' return - def symmetricPeerDecrypt(self, data, key): + def pubKeyEncrypt(self, data, peer): + '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)''' + return + + def symmetricPeerEncrypt(self, data): + '''Salsa20 encrypt data to peer (with mac)''' + return + + def symmetricPeerDecrypt(self, data, peer): + '''Salsa20 decrypt data from peer (with mac)''' return - def generateSymmetric(): + def generateSymmetric(self, data, peer): + '''Generate symmetric key''' return - def generateHMAC(): - return \ No newline at end of file + def generatePubKey(self): + '''Generate a Ed25519 public key pair, return tuple of base64encoded pubkey, privkey''' + private_key = nacl.signing.SigningKey.generate() + public_key = private_key.verify_key.encode(encoder=nacl.encoding.Base32Encoder()) + return (public_key.decode(), private_key.encode(encoder=nacl.encoding.Base32Encoder()).decode()) \ No newline at end of file diff --git a/reset.sh b/reset.sh new file mode 100755 index 00000000..eaf6641b --- /dev/null +++ b/reset.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "RESETING ONIONR" +rm onionr/data/blocks/*.dat +rm onionr/data/peers.db +rm onionr/data/blocks.db From 6beff53e458a91fe3f7cd55fe04a6b1eb4904e6d Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 20 Feb 2018 21:17:24 -0600 Subject: [PATCH 43/89] updated readme --- readme.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index fea71132..f143aec2 100644 --- a/readme.md +++ b/readme.md @@ -2,13 +2,21 @@ [![Build Status](https://travis-ci.org/beardog108/onionr.svg?branch=master)](https://travis-ci.org/beardog108/onionr) -P2P microblogging platform and social network, using Tor & I2P. +P2P platform, using Tor & I2P. Major work in progress. ***THIS SOFTWARE IS NOT USABLE OR SAFE YET.*** +**Roadmap/features:** + +* [X] Fully p2p/decentralized, no trackers or other single points of failure +* [X] High level of anonymity +* [] End to end encryption where applicable +* [X] Optional non-encrypted blocks, useful for blog posts or public file sharing +* [] Easy API system for integration to websites + # Development This software is in heavy development. If for some reason you want to get involved, get in touch first. From 916cb1f8acb62057cddbb764beb0e2c35dbdb778 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 20 Feb 2018 21:19:27 -0600 Subject: [PATCH 44/89] updated readme --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index f143aec2..c90492e9 100644 --- a/readme.md +++ b/readme.md @@ -13,9 +13,9 @@ Major work in progress. * [X] Fully p2p/decentralized, no trackers or other single points of failure * [X] High level of anonymity -* [] End to end encryption where applicable +* [ ] End to end encryption where applicable * [X] Optional non-encrypted blocks, useful for blog posts or public file sharing -* [] Easy API system for integration to websites +* [ ] Easy API system for integration to websites # Development From 38bfee5344f48532aa2e112a899b347b7be72c83 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 21 Feb 2018 03:32:31 -0600 Subject: [PATCH 45/89] work on seperating pubkey from tor/i2p --- onionr/core.py | 53 +++++++++++++++++++++++++++---------------- onionr/onionrutils.py | 20 ++++++++++------ onionr/tests.py | 2 +- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index 29d84a55..8e72520d 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -40,6 +40,7 @@ class Core: self.peerDB = 'data/peers.db' self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' + self.addressDB = 'data/address.db' self._utils = onionrutils.OnionrUtils(self) # Initialize the crypto object @@ -61,7 +62,7 @@ class Core: DOES NO SAFETY CHECKS if the ID is valid, but prepares the insertion ''' # This function simply adds a peer to the DB - if not self._utils.validateID(peerID): + if not self._utils.validatePubKey(peerID): return False conn = sqlite3.connect(self.peerDB) c = conn.cursor() @@ -70,6 +71,29 @@ class Core: conn.commit() conn.close() return True + + def createAddressDB(self): + ''' + Generate the address database + + types: + 1: I2P b32 address + 2: Tor v2 (like facebookcorewwwi.onion) + 3: Tor v3 + ''' + conn = sqlite3.connect(self.addressDB) + c = conn.cursor() + c.execute('''CREATE TABLE adders( + address text, + type int, + knownPeer text, + speed int, + success int, + failure int + ); + ''') + conn.commit() + conn.close() def createPeerDB(self): ''' @@ -81,7 +105,7 @@ class Core: c.execute('''CREATE TABLE peers( ID text not null, name text, - pubkey text, + adders text, blockDBHash text, forwardKey text, dateSeen not null, @@ -90,7 +114,6 @@ class Core: ''') conn.commit() conn.close() - return def createBlockDB(self): @@ -278,14 +301,6 @@ class Core: return - def generateHMAC(self, length=32): - ''' - Generate and return an HMAC key - ''' - key = base64.b64encode(os.urandom(length)) - - return key - def listPeers(self, randomOrder=True): ''' Return a list of peers @@ -300,7 +315,7 @@ class Core: peers = c.execute('SELECT * FROM peers;') peerList = [] for i in peers: - peerList.append(i[0]) + peerList.append(i[2]) conn.close() return peerList @@ -311,17 +326,17 @@ class Core: id text 0 name text, 1 - hmacKey text, 3 - blockDBHash text, 4 - forwardKey text, 5 - dateSeen not null, 7 - bytesStored int, 8 - trust int 9 + adders text, 2 + blockDBHash text, 3 + forwardKey text, 4 + dateSeen not null, 5 + bytesStored int, 6 + trust int 7 ''' conn = sqlite3.connect(self.peerDB) c = conn.cursor() command = (peer,) - infoNumbers = {'id': 0, 'name': 1, 'hmacKey': 3, 'blockDBHash': 4, 'forwardKey': 5, 'dateSeen': 6, 'bytesStored': 7, 'trust': 8} + infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'blockDBHash': 3, 'forwardKey': 4, 'dateSeen': 5, 'bytesStored': 6, 'trust': 7} info = infoNumbers[info] iterCount = 0 retVal = '' diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index ba082b98..ab57a288 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -19,6 +19,7 @@ ''' # Misc functions that do not fit in the main api, but are useful import getpass, sys, requests, configparser, os, socket, hashlib, logger, sqlite3 +import nacl.signing, nacl.encoding if sys.version_info < (3, 6): try: import sha3 @@ -140,10 +141,20 @@ class OnionrUtils: retVal = False return retVal + + def validatePubKey(self, key): + '''Validate if a string is a valid base32 encoded Ed25519 key''' + retVal = False + try: + nacl.signing.SigningKey(self, seed=key, encoder=nacl.encoding.Base32Encoder) + except nacl.exceptions.ValueError: + pass + return retVal + def validateID(self, id): ''' - Validate if a user ID is a valid tor or i2p hidden service + Validate if an address is a valid tor or i2p hidden service ''' idLength = len(id) retVal = True @@ -183,9 +194,4 @@ class OnionrUtils: if not idNoDomain.isalnum(): retVal = False - return retVal - - def sendPM(self, peer, message): - '''Send an encrypted private message to a user''' - - return + return retVal \ No newline at end of file diff --git a/onionr/tests.py b/onionr/tests.py index 557e8885..5728055c 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -54,7 +54,7 @@ class OnionrTests(unittest.TestCase): myCore = core.Core() if not os.path.exists('data/peers.db'): myCore.createPeerDB() - if myCore.addPeer('2ks5c5bm6zk3ejqg.onion') and not myCore.addPeer('invalidpeer.onion'): + if myCore.addPeer('6M5MXL237OK57ITHVYN5WGHANPGOMKS5C3PJLHBBNKFFJQOIDOJA====') and not myCore.addPeer('NFXHMYLMNFSAU==='): self.assertTrue(True) else: self.assertTrue(False) From 882f2e7020fe8c31e3795cac750f0cbef708a1c6 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 22 Feb 2018 00:02:29 -0600 Subject: [PATCH 46/89] fixed crypto object created before directories are generated --- onionr/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index 8e72520d..815bfd66 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -41,10 +41,6 @@ class Core: self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' self.addressDB = 'data/address.db' - self._utils = onionrutils.OnionrUtils(self) - - # Initialize the crypto object - self._crypto = onionrcrypto.OnionrCrypto(self) if not os.path.exists('data/'): os.mkdir('data/') @@ -52,6 +48,10 @@ class Core: os.mkdir('data/blocks/') if not os.path.exists(self.blockDB): self.createBlockDB() + + self._utils = onionrutils.OnionrUtils(self) + # Initialize the crypto object + self._crypto = onionrcrypto.OnionrCrypto(self) return From 15400432b1c548aa517b68cb1429eb2319034c04 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 22 Feb 2018 00:08:04 -0600 Subject: [PATCH 47/89] fixed pubkey validation not working --- onionr/onionrutils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index ab57a288..b000e799 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -146,9 +146,11 @@ class OnionrUtils: '''Validate if a string is a valid base32 encoded Ed25519 key''' retVal = False try: - nacl.signing.SigningKey(self, seed=key, encoder=nacl.encoding.Base32Encoder) + nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) except nacl.exceptions.ValueError: pass + else: + retVal = True return retVal From b0039f534c6a986ed47aac1d610ce2071148b199 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Wed, 21 Feb 2018 22:42:02 -0800 Subject: [PATCH 48/89] Changes, forgot --- onionr/communicator.py | 2 +- onionr/core.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index cd32d881..e7895f47 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -214,7 +214,7 @@ class OnionrCommunicate: except requests.exceptions.RequestException as e: logger.warn(action + " failed with peer " + peer + ": " + str(e)) retData = False - + if not retData: self.peerData[peer]['failCount'] += 1 else: diff --git a/onionr/core.py b/onionr/core.py index d7ac589c..d79712db 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -383,7 +383,7 @@ class Core: c = conn.cursor() retData = '' if unsaved: - execute = 'SELECT hash FROM hashes where dataSaved != 1;' + execute = 'SELECT hash FROM hashes WHERE dataSaved != 1;' else: execute = 'SELECT hash FROM hashes;' for row in c.execute(execute): From 8acef01b68ce8dcc64addfa42b1b056484ace5bd Mon Sep 17 00:00:00 2001 From: Arinerron Date: Wed, 21 Feb 2018 23:24:25 -0800 Subject: [PATCH 49/89] Add help menu, refactor code --- onionr/logger.py | 70 +++++++++++++++++++---------------- onionr/onionr.py | 95 +++++++++++++++++++++++++++++++++++++++++++----- onionr/tests.py | 4 +- 3 files changed, 127 insertions(+), 42 deletions(-) diff --git a/onionr/logger.py b/onionr/logger.py index 6a305bfe..96d30ad1 100644 --- a/onionr/logger.py +++ b/onionr/logger.py @@ -78,60 +78,67 @@ _type = OUTPUT_TO_CONSOLE | USE_ANSI # the default settings for logging _level = LEVEL_DEBUG # the lowest level to log _outputfile = './output.log' # the file to log to -''' - Set the settings for the logger using bitwise operators -''' def set_settings(type): + ''' + Set the settings for the logger using bitwise operators + ''' + global _type _type = type -''' - Get settings from the logger -''' def get_settings(): + ''' + Get settings from the logger + ''' + return _type -''' - Set the lowest log level to output -''' def set_level(level): + ''' + Set the lowest log level to output + ''' + global _level _level = level -''' - Get the lowest log level currently being outputted -''' def get_level(): + ''' + Get the lowest log level currently being outputted + ''' + return _level -''' - Outputs raw data to console without formatting -''' def raw(data): + ''' + Outputs raw data to console without formatting + ''' + if get_settings() & OUTPUT_TO_CONSOLE: print(data) if get_settings() & OUTPUT_TO_FILE: with open(_outputfile, "a+") as f: f.write(colors.filter(data) + '\n') -''' - Logs the data - prefix : The prefix to the output - data : The actual data to output - color : The color to output before the data -''' def log(prefix, data, color = ''): + ''' + Logs the data + prefix : The prefix to the output + data : The actual data to output + color : The color to output before the data + ''' + output = colors.reset + str(color) + '[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' + str(data) + colors.reset if not get_settings() & USE_ANSI: output = colors.filter(output) raw(output) -''' - Takes in input from the console, not stored in logs - message: The message to display before taking input -''' -def readline(message = 'Enter input: '): +def readline(message = ''): + ''' + Takes in input from the console, not stored in logs + message: The message to display before taking input + ''' + color = colors.fg.green + colors.bold output = colors.reset + str(color) + '... ' + colors.reset + str(message) + colors.reset @@ -142,12 +149,13 @@ def readline(message = 'Enter input: '): return input() -''' - Displays an "Are you sure" message, returns True for Y and False for N - message: The confirmation message, use %s for (y/n) - default: which to prefer-- y or n -''' def confirm(default = 'y', message = 'Are you sure %s? '): + ''' + Displays an "Are you sure" message, returns True for Y and False for N + message: The confirmation message, use %s for (y/n) + default: which to prefer-- y or n + ''' + color = colors.fg.green + colors.bold default = default.lower() diff --git a/onionr/onionr.py b/onionr/onionr.py index d3c51a4f..71545dc3 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -133,7 +133,24 @@ class Onionr: 'add-peer': self.addPeer } + def getHelp(self): + return { + 'help': 'Displays this Onionr help menu', + 'version': 'Displays the Onionr version', + 'start': 'Starts the Onionr daemon', + 'stop': 'Stops the Onionr daemon', + 'stats': 'Displays node statistics', + 'list-peers': 'Displays a list of peers (?)', + 'add-peer': 'Adds a peer (?)', + 'add-msg': 'Broadcasts a message to the Onionr network', + 'pm': 'Adds a private message (?)', + 'gui': 'Opens a graphical interface for Onionr' + } + def execute(self, argument): + ''' + Executes a command + ''' argument = argument[argument.startswith('--') and len('--'):] # remove -- if it starts with it # define commands @@ -146,12 +163,21 @@ class Onionr: THIS SECTION DEFINES THE COMMANDS ''' - def version(self): - logger.info('Onionr ' + ONIONR_VERSION + ' (' + platform.machine() + ') : API v' + API_VERSION) - logger.info('Running on ' + platform.platform() + ' ' + platform.release()) + def version(self, verbosity=5): + ''' + Displays the Onionr version + ''' + logger.info('Onionr ' + ONIONR_VERSION + ' (' + platform.machine() + ') - API v' + API_VERSION) + if verbosity >= 1: + logger.info('Anonymous P2P Platform - GPLv3 - onionr.voidnet.tech') + if verbosity >= 2: + logger.info('Running on ' + platform.platform() + ' ' + platform.release()) def sendEncrypt(self): - '''Create a private message and send it''' + ''' + Create a private message and send it + ''' + while True: peer = logger.readline('Peer to send to: ') if self.onionrUtils.validateID(peer): @@ -164,14 +190,26 @@ class Onionr: def openGUI(self): + ''' + Opens a graphical interface for Onionr + ''' + gui.OnionrGUI(self.onionrCore) def listPeers(self): + ''' + Displays a list of peers (?) + ''' + logger.info('Peer list:\n') for i in self.onionrCore.listPeers(): logger.info(i) def addPeer(self): + ''' + Adds a peer (?) + ''' + try: newPeer = sys.argv[2] except: @@ -181,21 +219,38 @@ class Onionr: self.onionrCore.addPeer(newPeer) def addMessage(self): + ''' + Broadcasts a message to the Onionr network + ''' + while True: messageToAdd = '-txt-' + logger.readline('Broadcast message to network: ') if len(messageToAdd) >= 1: break + addedHash = self.onionrCore.setData(messageToAdd) self.onionrCore.addToBlockDB(addedHash, selfInsert=True) self.onionrCore.setBlockType(addedHash, 'txt') def notFound(self): + ''' + Displays a "command not found" message + ''' + logger.error('Command not found.') def showHelpSuggestion(self): + ''' + 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.') def start(self): + ''' + Starts the Onionr daemon + ''' + if os.path.exists('.onionr-lock'): 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: @@ -208,7 +263,9 @@ class Onionr: os.remove('.onionr-lock') def daemon(self): - ''' Start the Onionr communication daemon ''' + ''' + Starts the Onionr communication daemon + ''' if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": if self._developmentMode: logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') @@ -226,7 +283,9 @@ class Onionr: return def killDaemon(self): - ''' Shutdown the Onionr Daemon ''' + ''' + Shutdown the Onionr daemon + ''' logger.warn('Killing the running daemon') net = NetController(self.config['CLIENT']['PORT']) @@ -240,13 +299,31 @@ class Onionr: return def showStats(self): - ''' Display statistics and exit ''' + ''' + Displays statistics and exits + ''' return - def showHelp(self): - ''' Show help for Onionr ''' + def showHelp(self, command = None): + ''' + 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()]) + else: + logger.warn(logger.colors.bold + command + logger.colors.reset + logger.colors.fg.blue + ' : ' + logger.colors.reset + 'No help menu entry was found') + else: + self.version(0) + for command, helpmessage in helpmenu.items(): + self.showHelp(command) return Onionr() diff --git a/onionr/tests.py b/onionr/tests.py index 5728055c..95c2ffff 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -28,7 +28,7 @@ class OnionrTests(unittest.TestCase): logger.debug('--------------------------') logger.info('Running simple program run test...') # Test just running ./onionr with no arguments - blank = os.system('./onionr.py') + blank = os.system('./onionr.py --version') if blank != 0: self.assertTrue(False) else: @@ -105,5 +105,5 @@ class OnionrTests(unittest.TestCase): if command[0] == 'testCommand': if myCore.daemonQueue() == False: logger.info('Succesfully added and read command') - + unittest.main() From 60c0f952ebeb03e6fdc0a03ca55093516a9f71a9 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Wed, 21 Feb 2018 23:31:04 -0800 Subject: [PATCH 50/89] Move reset.sh to Makefile --- Makefile | 7 +++++++ reset.sh | 5 ----- 2 files changed, 7 insertions(+), 5 deletions(-) delete mode 100755 reset.sh diff --git a/Makefile b/Makefile index 193fbecf..a3abcc78 100644 --- a/Makefile +++ b/Makefile @@ -17,3 +17,10 @@ uninstall: test: @cd onionr; ./tests.py + +reset: + echo "RESETING ONIONR" + rm -f onionr/data/blocks/*.dat | true > /dev/null 2>&1 + rm -f onionr/data/peers.db | true > /dev/null 2>&1 + rm -f onionr/data/blocks.db | true > /dev/null 2>&1 + diff --git a/reset.sh b/reset.sh deleted file mode 100755 index eaf6641b..00000000 --- a/reset.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -echo "RESETING ONIONR" -rm onionr/data/blocks/*.dat -rm onionr/data/peers.db -rm onionr/data/blocks.db From f39ab33517541f4ab555f6e1515f88d843def39b Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 22 Feb 2018 02:41:05 -0600 Subject: [PATCH 51/89] added hash validation test and cleaned up output --- onionr/api.py | 5 +++-- onionr/communicator.py | 5 +++-- onionr/tests.py | 12 +++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 666b7783..df5d2236 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -62,7 +62,8 @@ class API: bindPort = int(self.config['CLIENT']['PORT']) self.bindPort = bindPort self.clientToken = self.config['CLIENT']['CLIENT HMAC'] - logger.debug('Your HMAC token: ' + logger.colors.underline + self.clientToken) + if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": + logger.debug('Your HMAC token: ' + logger.colors.underline + self.clientToken) if not debug and not self._developmentMode: hostNums = [random.randint(1, 255), random.randint(1, 255), random.randint(1, 255)] @@ -174,7 +175,7 @@ class API: return resp if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...') - logger.debug('Client token: ' + logger.colors.underline + self.clientToken) + #logger.debug('Client token: ' + logger.colors.underline + self.clientToken) app.run(host=self.host, port=bindPort, debug=True, threaded=True) diff --git a/onionr/communicator.py b/onionr/communicator.py index 4b6e49a8..b11d2b12 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -35,7 +35,7 @@ class OnionrCommunicate: blockProcessTimer = 0 blockProcessAmount = 5 heartBeatTimer = 0 - heartBeatRate = 10 + heartBeatRate = 100 logger.debug('Communicator debugging enabled.') torID = open('data/hs/hostname').read() @@ -87,7 +87,8 @@ class OnionrCommunicate: blocks += self.performGet('getBlockHashes', i) if self._utils.validateHash(currentDB): self._core.setPeerInfo(i, "blockDBHash", currentDB) - logger.debug('BLOCKS: \n' + blocks) + if len(blocks.strip()) != 0: + logger.debug('BLOCKS:' + blocks) blockList = blocks.split('\n') for i in blockList: if len(i.strip()) == 0: diff --git a/onionr/tests.py b/onionr/tests.py index 5728055c..e74c1c5b 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -105,5 +105,15 @@ class OnionrTests(unittest.TestCase): if command[0] == 'testCommand': if myCore.daemonQueue() == False: logger.info('Succesfully added and read command') - + + def testHashValidation(self): + logger.debug('--------------------------') + logger.info('Running hash validation test...') + import core + myCore = core.Core() + if not myCore._utils.validateHash("$324dfgfdg") and myCore._utils.validateHash("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2") and not myCore._utils.validateHash("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd$"): + self.assertTrue(True) + else: + self.assertTrue(False) + unittest.main() From 155791be0366b13e003b533fd224b49f47fa2073 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 22 Feb 2018 03:33:30 -0600 Subject: [PATCH 52/89] test tor version, use address db for connections, create address db if it doesnt exist, more --- Makefile | 1 + onionr/communicator.py | 4 ++-- onionr/core.py | 16 ++++++++++++++++ onionr/netcontroller.py | 9 +++++++++ onionr/onionr.py | 6 ++++-- 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index a3abcc78..3e8c020e 100644 --- a/Makefile +++ b/Makefile @@ -23,4 +23,5 @@ reset: rm -f onionr/data/blocks/*.dat | true > /dev/null 2>&1 rm -f onionr/data/peers.db | true > /dev/null 2>&1 rm -f onionr/data/blocks.db | true > /dev/null 2>&1 + rm -rf onionr/data/address.db | true > /dev/null 2>&1 diff --git a/onionr/communicator.py b/onionr/communicator.py index 1f5c0634..6185b8dd 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -68,7 +68,7 @@ class OnionrCommunicate: ''' Lookup blocks and merge new ones ''' - peerList = self._core.listPeers() + peerList = self._core.listAdders() blocks = '' for i in peerList: lastDB = self._core.getPeerInfo(i, 'blockDBHash') @@ -123,7 +123,7 @@ class OnionrCommunicate: ''' Download a block from random order of peers ''' - peerList = self._core.listPeers() + peerList = self._core.listAdders() blocks = '' for i in peerList: hasher = hashlib.sha3_256() diff --git a/onionr/core.py b/onionr/core.py index e8090530..c1ff0457 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -300,6 +300,22 @@ class Core: conn.close() return + + def listAdders(self, randomOrder=True, i2p=True): + ''' + Return a list of addresses + ''' + conn = sqlite3.connect(self.addressDB) + c = conn.cursor() + if randomOrder: + addresses = c.execute('SELECT * FROM adders ORDER BY RANDOM();') + else: + addresses = c.execute('SELECT * FROM adders;') + addressList = [] + for i in addresses: + addressList.append(i[2]) + conn.close() + return addressList def listPeers(self, randomOrder=True): ''' diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 058d8e4a..f56f15e4 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -69,6 +69,15 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' except FileNotFoundError: logger.fatal("Tor was not found in your path or the Onionr directory. Please install Tor and try again.") sys.exit(1) + else: + # Test Tor Version + torVersion = subprocess.Popen([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") + break + torVersion.kill() + # wait for tor to get to 100% bootstrap for line in iter(tor.stdout.readline, b''): if 'Bootstrapped 100%: Done' in line.decode(): diff --git a/onionr/onionr.py b/onionr/onionr.py index 71545dc3..3098fe82 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -72,9 +72,11 @@ class Onionr: os.mkdir('data/') os.mkdir('data/blocks/') - if not os.path.exists('data/peers.db'): + if not os.path.exists(self.onionrCore.peerDB): self.onionrCore.createPeerDB() pass + if not os.path.exists(self.onionrCore.addressDB): + self.onionrCore.createAddressDB() # Get configuration self.config = configparser.ConfigParser() @@ -90,7 +92,7 @@ class Onionr: randomPort = random.randint(1024, 65535) if self.onionrUtils.checkPort(randomPort): break - self.config['CLIENT'] = {'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION} + self.config['CLIENT'] = {'participate': 'true', 'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION} with open('data/config.ini', 'w') as configfile: self.config.write(configfile) From b65c6305c5c5ad37b33c3c5aa1f48ed970057def Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 22 Feb 2018 16:55:42 -0600 Subject: [PATCH 53/89] added pow module (not used yet) --- onionr/pow.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 onionr/pow.py diff --git a/onionr/pow.py b/onionr/pow.py new file mode 100644 index 00000000..7398efb0 --- /dev/null +++ b/onionr/pow.py @@ -0,0 +1,62 @@ +''' + Onionr - P2P Microblogging Platform & Social network + + Proof of work module +''' +''' + 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 nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, logger +class POW: + def pow(self): + startTime = math.floor(time.time()) + self.hashing = True + iFound = False # if current thread is the one that found the answer + answer = '' + heartbeat = 200000 + hbCount = 0 + while self.hashing: + if hbCount == heartbeat: + logger.info('hb') + hbCount = 0 + hbCount += 1 + token = nacl.hash.blake2b(nacl.utils.random()).decode() + if self.mainHash[0:self.difficulty] == token[0:self.difficulty]: + self.hashing = False + iFound = True + break + if iFound: + logger.info('Found token ' + token) + endTime = math.floor(time.time()) + logger.info('took ' + str(endTime - startTime)) + + def __init__(self, difficulty): + self.foundHash = False + self.difficulty = difficulty + + logger.info('Computing difficulty of ' + str(self.difficulty)) + + self.mainHash = nacl.hash.blake2b(nacl.utils.random()).decode() + self.puzzle = self.mainHash[0:self.difficulty] + logger.info('trying to find ' + str(self.mainHash)) + tOne = threading.Thread(name='one', target=self.pow) + tTwo = threading.Thread(name='two', target=self.pow) + tThree = threading.Thread(name='three', target=self.pow) + tOne.start() + tTwo.start() + tThree.start() + return + + def shutdown(self): + self.hashing = False From 1a1317a7b623c02b118c8321976e6e5e99748e0b Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 22 Feb 2018 17:58:36 -0800 Subject: [PATCH 54/89] Refactor configuration management code --- onionr/api.py | 15 +++--- onionr/config.py | 107 ++++++++++++++++++++++++++++++++++++++++++ onionr/onionr.py | 31 ++++++------ onionr/onionrutils.py | 26 +++++----- requirements.txt | 1 + 5 files changed, 148 insertions(+), 32 deletions(-) create mode 100644 onionr/config.py diff --git a/onionr/api.py b/onionr/api.py index df5d2236..4175ecdf 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -20,7 +20,7 @@ import flask from flask import request, Response, abort from multiprocessing import Process -import configparser, sys, random, threading, hmac, hashlib, base64, time, math, os, logger +import sys, random, threading, hmac, hashlib, base64, time, math, os, logger, config from core import Core import onionrutils, onionrcrypto @@ -37,31 +37,32 @@ class API: else: return True - def __init__(self, config, debug): + def __init__(self, debug): ''' Initialize the api server, preping variables for later use 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 ''' - if os.path.exists('dev-enabled'): + + config.reload() + + if config.get('devmode', True): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) - #logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') else: self._developmentMode = False logger.set_level(logger.LEVEL_INFO) - self.config = config self.debug = debug self._privateDelayTime = 3 self._core = Core() self._crypto = onionrcrypto.OnionrCrypto(self._core) self._utils = onionrutils.OnionrUtils(self._core) app = flask.Flask(__name__) - bindPort = int(self.config['CLIENT']['PORT']) + bindPort = int(config.get('CLIENT')['PORT']) self.bindPort = bindPort - self.clientToken = self.config['CLIENT']['CLIENT HMAC'] + self.clientToken = config.get('CLIENT')['CLIENT HMAC'] if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": logger.debug('Your HMAC token: ' + logger.colors.underline + self.clientToken) diff --git a/onionr/config.py b/onionr/config.py new file mode 100644 index 00000000..81e8bbb0 --- /dev/null +++ b/onionr/config.py @@ -0,0 +1,107 @@ +''' + Onionr - P2P Microblogging Platform & Social network + + This file deals with configuration 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 os, json, logger + +_configfile = os.path.abspath('data/config.json') +_config = {} + +def get(key, default = None): + ''' + Gets the key from configuration, or returns `default` + ''' + if key in get_config(): + return get_config()[key] + return default + +def set(key, value = None, savefile = False): + ''' + Sets the key in configuration to `value` + ''' + + global _config + _config[key] = value + + if savefile: + save() + +def check(): + ''' + Checks if the configuration file exists, creates it if not + ''' + + try: + if not os.path.exists(os.path.dirname(get_config_file())): + os.path.mkdirs(os.path.dirname(get_config_file())) + if not os.path.isfile(get_config_file()): + open(get_config_file(), 'a', encoding="utf8").close() + save() + except: + logger.warn('Failed to check configuration file.') + +def save(): + ''' + Saves the configuration data to the configuration file + ''' + + check() + try: + with open(get_config_file(), 'w', encoding="utf8") as configfile: + json.dump(get_config(), configfile) + except: + logger.warn('Failed to write to configuration file.') + +def reload(): + ''' + Reloads the configuration data in memory from the file + ''' + + check() + try: + with open(get_config_file(), 'r', encoding="utf8") as configfile: + set_config(json.loads(configfile.read())) + except: + logger.warn('Failed to parse configuration file.') + +def get_config(): + ''' + Gets the entire configuration as an array + ''' + return _config + +def set_config(config): + ''' + Sets the configuration to the array in arguments + ''' + global _config + _config = config + +def get_config_file(): + ''' + Returns the absolute path to the configuration file + ''' + return _configfile + +def set_config_file(configfile): + ''' + Sets the path to the configuration file + ''' + global _configfile + _configfile = os.abs.abspath(configfile) diff --git a/onionr/onionr.py b/onionr/onionr.py index 3098fe82..ed260450 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -20,8 +20,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sys, os, configparser, base64, random, getpass, shutil, subprocess, requests, time, logger, platform -import api, core, gui +import sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform +import api, core, gui, config, logger from onionrutils import OnionrUtils from netcontroller import NetController @@ -39,12 +39,19 @@ class Onionr: Main Onionr class. This is for the CLI program, and does not handle much of the logic. In general, external programs and plugins should not use this class. ''' + try: os.chdir(sys.path[0]) except FileNotFoundError: pass - if os.path.exists('dev-enabled'): + # Load global configuration data + + exists = os.path.exists(config.get_config_file()) + config.set_config({'devmode': True}) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it + config.reload() # this will read the configuration file into memory + + if config.get('devmode', True): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) else: @@ -54,7 +61,7 @@ class Onionr: self.onionrCore = core.Core() self.onionrUtils = OnionrUtils(self.onionrCore) - # Get configuration and Handle commands + # Handle commands self.debug = False # Whole application debugging @@ -79,10 +86,8 @@ class Onionr: self.onionrCore.createAddressDB() # Get configuration - self.config = configparser.ConfigParser() - if os.path.exists('data/config.ini'): - self.config.read('data/config.ini') - else: + + if not exists: # Generate default config # Hostname should only be set if different from 127.x.x.x. Important for DNS rebinding attack prevention. if self.debug: @@ -92,9 +97,7 @@ class Onionr: randomPort = random.randint(1024, 65535) if self.onionrUtils.checkPort(randomPort): break - self.config['CLIENT'] = {'participate': 'true', 'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION} - with open('data/config.ini', 'w') as configfile: - self.config.write(configfile) + config.set('CLIENT', {'participate': 'true', 'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION}, True) command = '' try: @@ -271,7 +274,7 @@ class Onionr: if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": if self._developmentMode: logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') - net = NetController(self.config['CLIENT']['PORT']) + net = NetController(config.get('CLIENT')['PORT']) logger.info('Tor is starting...') if not net.startTor(): sys.exit(1) @@ -280,7 +283,7 @@ class Onionr: time.sleep(1) subprocess.Popen(["./communicator.py", "run", str(net.socksPort)]) logger.debug('Started communicator') - api.API(self.config, self.debug) + api.API(self.debug) return @@ -290,7 +293,7 @@ class Onionr: ''' logger.warn('Killing the running daemon') - net = NetController(self.config['CLIENT']['PORT']) + net = NetController(config.get('CLIENT')['PORT']) try: self.onionrUtils.localCommand('shutdown') except requests.exceptions.ConnectionError: diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index b000e799..35ba38e3 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -18,30 +18,34 @@ along with this program. If not, see . ''' # Misc functions that do not fit in the main api, but are useful -import getpass, sys, requests, configparser, os, socket, hashlib, logger, sqlite3 +import getpass, sys, requests, os, socket, hashlib, logger, sqlite3, config import nacl.signing, nacl.encoding + if sys.version_info < (3, 6): try: import sha3 except ModuleNotFoundError: logger.fatal('On Python 3 versions prior to 3.6.x, you need the sha3 module') sys.exit(1) + class OnionrUtils: - '''Various useful functions''' + ''' + Various useful function + ''' def __init__(self, coreInstance): self.fingerprintFile = 'data/own-fingerprint.txt' self._core = coreInstance return + def localCommand(self, command): ''' Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers. ''' - config = configparser.ConfigParser() - if os.path.exists('data/config.ini'): - config.read('data/config.ini') - else: - return - requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config['CLIENT']['PORT']) + '/client/?action=' + command + '&token=' + config['CLIENT']['CLIENT HMAC']) + + config.reload() + + # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless. + requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('CLIENT')['PORT']) + '/client/?action=' + command + '&token=' + config.get('CLIENT')['CLIENT HMAC']) return @@ -141,7 +145,7 @@ class OnionrUtils: retVal = False return retVal - + def validatePubKey(self, key): '''Validate if a string is a valid base32 encoded Ed25519 key''' retVal = False @@ -195,5 +199,5 @@ class OnionrUtils: retVal = False if not idNoDomain.isalnum(): retVal = False - - return retVal \ No newline at end of file + + return retVal diff --git a/requirements.txt b/requirements.txt index 77e25537..7338d372 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ simple_crypt==4.1.7 urllib3==1.19.1 sha3==0.2.1 PySocks==1.6.8 +urllib3 From 9998cf6a791af38e9cb67d0985b0d55e205e1487 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 22 Feb 2018 18:01:28 -0800 Subject: [PATCH 55/89] Fix requirements --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7338d372..77e25537 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,3 @@ simple_crypt==4.1.7 urllib3==1.19.1 sha3==0.2.1 PySocks==1.6.8 -urllib3 From c87bf1514679e363409fcd2a93ebd5a31e4a9e04 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 22 Feb 2018 18:25:05 -0800 Subject: [PATCH 56/89] Add config command --- onionr/config.py | 7 +++++-- onionr/onionr.py | 25 ++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/onionr/config.py b/onionr/config.py index 81e8bbb0..793d8b9b 100644 --- a/onionr/config.py +++ b/onionr/config.py @@ -27,7 +27,7 @@ def get(key, default = None): ''' Gets the key from configuration, or returns `default` ''' - if key in get_config(): + if is_set(key): return get_config()[key] return default @@ -42,6 +42,9 @@ def set(key, value = None, savefile = False): if savefile: save() +def is_set(key): + return key in get_config() and not get_config()[key] is None + def check(): ''' Checks if the configuration file exists, creates it if not @@ -64,7 +67,7 @@ def save(): check() try: with open(get_config_file(), 'w', encoding="utf8") as configfile: - json.dump(get_config(), configfile) + json.dump(get_config(), configfile, indent=2, sort_keys=True) except: logger.warn('Failed to write to configuration file.') diff --git a/onionr/onionr.py b/onionr/onionr.py index ed260450..f06d1268 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -120,13 +120,14 @@ class Onionr: def getCommands(self): return { + 'help': self.showHelp, + 'version': self.version, + 'config': self.configure, 'start': self.start, 'stop': self.killDaemon, - 'version': self.version, + 'stats': self.showStats, 'listpeers': self.listPeers, 'list-peers': self.listPeers, - 'stats': self.showStats, - 'help': self.showHelp, '': self.showHelpSuggestion, 'addmsg': self.addMessage, 'addmessage': self.addMessage, @@ -142,6 +143,7 @@ class Onionr: return { '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', @@ -152,6 +154,23 @@ class Onionr: 'gui': 'Opens a graphical interface for Onionr' } + def configure(self): + ''' + Displays something from the configuration file, or sets it + ''' + + if len(sys.argv) >= 4: + config.reload() + config.set(sys.argv[2], sys.argv[3], True) + logger.debug('Configuration file updated.') + elif len(sys.argv) >= 3: + config.reload() + logger.info(logger.colors.bold + sys.argv[2] + ': ' + logger.colors.reset + str(config.get(sys.argv[2], logger.colors.fg.red + 'Not set.'))) + else: + 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 From 39c81ae7d108ae0fed8a6c34d319d02c9475d4c3 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 22 Feb 2018 18:53:49 -0800 Subject: [PATCH 57/89] Add log configuration --- onionr/logger.py | 15 +++++++++++++++ onionr/onionr.py | 12 +++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/onionr/logger.py b/onionr/logger.py index 96d30ad1..12b61702 100644 --- a/onionr/logger.py +++ b/onionr/logger.py @@ -108,6 +108,21 @@ def get_level(): return _level +def set_file(outputfile): + ''' + Set the file to output to, if enabled + ''' + + global _outputfile + _outputfile = outputfile + +def get_file(): + ''' + Get the file to output to + ''' + + return _outputfile + def raw(data): ''' Outputs raw data to console without formatting diff --git a/onionr/onionr.py b/onionr/onionr.py index f06d1268..8591322b 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -48,9 +48,19 @@ class Onionr: # Load global configuration data exists = os.path.exists(config.get_config_file()) - config.set_config({'devmode': True}) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it + config.set_config({'devmode': True, 'log.file': True, 'log.console': True, 'log.outputfile': 'data/output.log', 'log.color': True}) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it config.reload() # this will read the configuration file into memory + settings = 0b000 + if config.get('log.color', True): + settings = settings | logger.USE_ANSI + if config.get('log.console', True): + settings = settings | logger.OUTPUT_TO_CONSOLE + if config.get('log.file', False): + settings = settings | logger.OUTPUT_TO_FILE + logger.set_file(config.get('log.outputfile', 'data/output.log')) + logger.set_settings(settings) + if config.get('devmode', True): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) From 152f9e7dee6346193777ab808fa4c756ffc42016 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 24 Feb 2018 15:43:10 -0600 Subject: [PATCH 58/89] improved tor support for bitcoin --- .gitmodules | 3 +++ onionr/bitpeer | 1 + onionr/btc.py | 5 +++-- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 160000 onionr/bitpeer diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..f2ab3397 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "onionr/bitpeer"] + path = onionr/bitpeer + url = https://github.com/beardog108/bitpeer.py diff --git a/onionr/bitpeer b/onionr/bitpeer new file mode 160000 index 00000000..d179f625 --- /dev/null +++ b/onionr/bitpeer @@ -0,0 +1 @@ +Subproject commit d179f625b3a1bf1b6fc544e65a81c103ab01ec7c diff --git a/onionr/btc.py b/onionr/btc.py index 03c449b6..47ad0acb 100644 --- a/onionr/btc.py +++ b/onionr/btc.py @@ -20,8 +20,9 @@ from bitpeer.node import * from bitpeer.storage.shelve import ShelveStorage import logging, time +import socks, sys class OnionrBTC: - def __init__(self, lastBlock='00000000000000000021ee6242d08e3797764c9258e54e686bc2afff51baf599', lastHeight=510613): + def __init__(self, lastBlock='00000000000000000021ee6242d08e3797764c9258e54e686bc2afff51baf599', lastHeight=510613, torP=9050): stream = logging.StreamHandler() logger = logging.getLogger('halfnode') logger.addHandler(stream) @@ -29,7 +30,7 @@ class OnionrBTC: LASTBLOCK = lastBlock LASTBLOCKINDEX = lastHeight - self.node = Node ('BTC', ShelveStorage ('./btc-blocks.db'), lastblockhash=LASTBLOCK, lastblockheight=LASTBLOCKINDEX) + self.node = Node ('BTC', ShelveStorage ('data/btc-blocks.db'), lastblockhash=LASTBLOCK, lastblockheight=LASTBLOCKINDEX, torPort=torP) self.node.bootstrap () self.node.connect () From cf3af5b8c6630bf1da0c98f5d67002e86b7bb161 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 25 Feb 2018 20:30:43 -0600 Subject: [PATCH 59/89] work on pow --- onionr/bitpeer | 2 +- onionr/btc.py | 6 ++++++ onionr/core.py | 2 +- onionr/pow.py | 28 +++++++++++++++++++++++----- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/onionr/bitpeer b/onionr/bitpeer index d179f625..90d5edad 160000 --- a/onionr/bitpeer +++ b/onionr/bitpeer @@ -1 +1 @@ -Subproject commit d179f625b3a1bf1b6fc544e65a81c103ab01ec7c +Subproject commit 90d5edad0c017cec1a3b190d9b0cd08321840ed8 diff --git a/onionr/btc.py b/onionr/btc.py index 47ad0acb..bf4549f2 100644 --- a/onionr/btc.py +++ b/onionr/btc.py @@ -36,3 +36,9 @@ class OnionrBTC: self.node.connect () self.node.loop () +if __name__ == "__main__": + torPort = int(sys.argv[1]) + bitcoin = OnionrBTC(torPort) + while True: + print(bitcoin.node.getBlockHash(bitcoin.node.getLastBlockHeight())) # Using print on purpose, do not change to logger + time.sleep(5) \ No newline at end of file diff --git a/onionr/core.py b/onionr/core.py index c1ff0457..e070218a 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -22,7 +22,7 @@ import sqlite3, os, sys, time, math, base64, tarfile, getpass, simplecrypt, hash #from Crypto import Random import netcontroller -import onionrutils, onionrcrypto +import onionrutils, onionrcrypto, btc if sys.version_info < (3, 6): try: diff --git a/onionr/pow.py b/onionr/pow.py index 7398efb0..3caefbd9 100644 --- a/onionr/pow.py +++ b/onionr/pow.py @@ -18,6 +18,7 @@ along with this program. If not, see . ''' import nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, logger +import btc class POW: def pow(self): startTime = math.floor(time.time()) @@ -27,11 +28,13 @@ class POW: heartbeat = 200000 hbCount = 0 while self.hashing: + block = self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight()) if hbCount == heartbeat: - logger.info('hb') + logger.debug('hb') + logger.debug('using bitcoin block: ' + block) hbCount = 0 hbCount += 1 - token = nacl.hash.blake2b(nacl.utils.random()).decode() + token = nacl.hash.blake2b(nacl.utils.random() + block.encode()).decode() if self.mainHash[0:self.difficulty] == token[0:self.difficulty]: self.hashing = False iFound = True @@ -40,16 +43,18 @@ class POW: logger.info('Found token ' + token) endTime = math.floor(time.time()) logger.info('took ' + str(endTime - startTime)) + self.result = token - def __init__(self, difficulty): + def __init__(self, difficulty, bitcoinNode): self.foundHash = False self.difficulty = difficulty - logger.info('Computing difficulty of ' + str(self.difficulty)) + logger.debug('Computing difficulty of ' + str(self.difficulty)) self.mainHash = nacl.hash.blake2b(nacl.utils.random()).decode() self.puzzle = self.mainHash[0:self.difficulty] - logger.info('trying to find ' + str(self.mainHash)) + self.bitcoinNode = bitcoinNode + logger.debug('trying to find ' + str(self.mainHash)) tOne = threading.Thread(name='one', target=self.pow) tTwo = threading.Thread(name='two', target=self.pow) tThree = threading.Thread(name='three', target=self.pow) @@ -60,3 +65,16 @@ class POW: def shutdown(self): self.hashing = False + self.puzzle = '' + + def changeDifficulty(self, newDiff): + self.difficulty = newDiff + + def getResult(self): + '''Returns the result then sets to false, useful to automatically clear the result''' + try: + retVal = self.result + except AttributeError: + retVal = False + self.result = False + return retVal \ No newline at end of file From 3033de1d9ef4ca249f746813608f084c21f39e99 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 27 Feb 2018 03:33:26 -0600 Subject: [PATCH 60/89] communicator now starts tor node --- onionr/communicator.py | 14 ++++++++++++-- onionr/pow.py | 24 +++++++++++++++--------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 6185b8dd..36f1108a 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -20,7 +20,7 @@ and code to operate as a daemon, getting commands from the command queue databas along with this program. If not, see . ''' import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse -import core, onionrutils, onionrcrypto +import core, onionrutils, onionrcrypto, pow, btc class OnionrCommunicate: def __init__(self, debug, developmentMode): @@ -32,10 +32,20 @@ class OnionrCommunicate: self._core = core.Core() self._utils = onionrutils.OnionrUtils(self._core) self._crypto = onionrcrypto.OnionrCrypto(self._core) + logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2])) + while True: + try: + self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2])) + except: + # ugly but needed + pass + else: + break + logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight())) blockProcessTimer = 0 blockProcessAmount = 5 heartBeatTimer = 0 - heartBeatRate = 100 + heartBeatRate = 5 logger.debug('Communicator debugging enabled.') torID = open('data/hs/hostname').read() diff --git a/onionr/pow.py b/onionr/pow.py index 3caefbd9..f996af9d 100644 --- a/onionr/pow.py +++ b/onionr/pow.py @@ -20,19 +20,24 @@ import nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, logger import btc class POW: - def pow(self): + def pow(self, reporting=False): startTime = math.floor(time.time()) self.hashing = True + self.reporting = reporting iFound = False # if current thread is the one that found the answer answer = '' heartbeat = 200000 hbCount = 0 + blockCheck = 300000 # How often the hasher should check if the bitcoin block is updated (slows hashing but prevents less wasted work) + blockCheckCount = 0 + block = self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight()) while self.hashing: - block = self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight()) - if hbCount == heartbeat: - logger.debug('hb') - logger.debug('using bitcoin block: ' + block) - hbCount = 0 + if blockCheckCount == blockCheck: + if self.reporting: + logger.debug('Refreshing Bitcoin block') + block = self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight()) + blockCheckCount = 0 + blockCheckCount += 1 hbCount += 1 token = nacl.hash.blake2b(nacl.utils.random() + block.encode()).decode() if self.mainHash[0:self.difficulty] == token[0:self.difficulty]: @@ -40,9 +45,10 @@ class POW: iFound = True break if iFound: - logger.info('Found token ' + token) endTime = math.floor(time.time()) - logger.info('took ' + str(endTime - startTime)) + if self.reporting: + logger.info('Found token ' + token) + logger.info('took ' + str(endTime - startTime)) self.result = token def __init__(self, difficulty, bitcoinNode): @@ -55,7 +61,7 @@ class POW: self.puzzle = self.mainHash[0:self.difficulty] self.bitcoinNode = bitcoinNode logger.debug('trying to find ' + str(self.mainHash)) - tOne = threading.Thread(name='one', target=self.pow) + tOne = threading.Thread(name='one', target=self.pow, args=(True,)) tTwo = threading.Thread(name='two', target=self.pow) tThree = threading.Thread(name='three', target=self.pow) tOne.start() From d0593ef30041ce638379736b9604453437b18fdb Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 27 Feb 2018 15:23:49 -0600 Subject: [PATCH 61/89] added address add command and test --- onionr/api.py | 5 +++++ onionr/bitpeer | 2 +- onionr/core.py | 28 +++++++++++++++++++++++++++- onionr/onionr.py | 18 +++++++++++++++++- onionr/tests.py | 14 +++++++++++++- 5 files changed, 63 insertions(+), 4 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 4175ecdf..7550f1c7 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -149,6 +149,11 @@ class API: abort(404) resp = "" resp = Response(resp) + elif action == 'pex': + response = ','.join(self._core.listAdders()) + if len(response) == 0: + response = 'none' + resp = Response(response) else: resp = Response("") diff --git a/onionr/bitpeer b/onionr/bitpeer index 90d5edad..a74e826e 160000 --- a/onionr/bitpeer +++ b/onionr/bitpeer @@ -1 +1 @@ -Subproject commit 90d5edad0c017cec1a3b190d9b0cd08321840ed8 +Subproject commit a74e826e9c69e643ead7950f9f76a05ab8664ddc diff --git a/onionr/core.py b/onionr/core.py index e070218a..cb52653c 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -71,7 +71,33 @@ class Core: conn.commit() conn.close() return True - + + def addAddress(self, address): + '''Add an address to the address database (only tor currently)''' + if self._utils.validateID(address): + conn = sqlite3.connect(self.addressDB) + c = conn.cursor() + t = (address, 1) + c.execute('INSERT INTO adders (address, type) VALUES(?, ?);', t) + conn.commit() + conn.close() + return True + else: + return False + + def removeAddress(self, address): + '''Remove an address from the address database''' + if self._utils.validateID(address): + conn = sqlite3.connect(self.addressDB) + c = conn.cursor() + t = (address,) + c.execute('Delete from adders where address=?;', t) + conn.commit() + conn.close() + return True + else: + return False + def createAddressDB(self): ''' Generate the address database diff --git a/onionr/onionr.py b/onionr/onionr.py index 8591322b..ee18f02c 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -146,7 +146,10 @@ class Onionr: 'pm': self.sendEncrypt, 'gui': self.openGUI, 'addpeer': self.addPeer, - 'add-peer': self.addPeer + 'add-peer': self.addPeer, + 'add-address': self.addAddress, + 'connect': self.addAddress, + 'addaddress': self.addAddress } def getHelp(self): @@ -251,6 +254,19 @@ class Onionr: else: logger.info("Adding peer: " + logger.colors.underline + newPeer) self.onionrCore.addPeer(newPeer) + + def addAddress(self): + '''Adds a Onionr node address''' + try: + newAddress = sys.argv[2] + 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") def addMessage(self): ''' diff --git a/onionr/tests.py b/onionr/tests.py index 82472333..6a70186c 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -115,5 +115,17 @@ class OnionrTests(unittest.TestCase): self.assertTrue(True) else: self.assertTrue(False) - + + def testAddAdder(self): + logger.debug('--------------------------') + logger.info('Running address add+remove test') + import core + myCore = core.Core() + if myCore.addAddress('facebookcorewwwi.onion') and not myCore.removeAddress('invalid'): + if myCore.removeAddress('facebookcorewwwi.onion'): + self.assertTrue(True) + else: + self.assertTrue(False) + else: + self.assertTrue(False) unittest.main() From fc5d702706b1a3fc2a67716692d3da96866a3dbb Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 27 Feb 2018 18:00:37 -0600 Subject: [PATCH 62/89] bug fixes --- onionr/communicator.py | 26 +++++++++---------- onionr/core.py | 59 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 36f1108a..54e9c512 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -33,14 +33,14 @@ class OnionrCommunicate: self._utils = onionrutils.OnionrUtils(self._core) self._crypto = onionrcrypto.OnionrCrypto(self._core) logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2])) - while True: - try: - self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2])) - except: + #while True: + #try: + self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2])) + #except: # ugly but needed - pass - else: - break + # pass + #else: + # break logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight())) blockProcessTimer = 0 blockProcessAmount = 5 @@ -81,11 +81,11 @@ class OnionrCommunicate: peerList = self._core.listAdders() blocks = '' for i in peerList: - lastDB = self._core.getPeerInfo(i, 'blockDBHash') + lastDB = self._core.getAddressInfo(i, 'DBHash') if lastDB == None: logger.debug('Fetching hash from ' + i + ' No previous known.') else: - logger.debug('Fetching hash from ' + i + ', ' + lastDB + ' last known') + logger.debug('Fetching hash from ' + str(i) + ', ' + lastDB + ' last known') currentDB = self.performGet('getDBHash', i) if currentDB != False: logger.debug(i + " hash db (from request): " + currentDB) @@ -96,7 +96,7 @@ class OnionrCommunicate: logger.debug('Fetching hash from ' + i + ' - ' + currentDB + ' current hash.') blocks += self.performGet('getBlockHashes', i) if self._utils.validateHash(currentDB): - self._core.setPeerInfo(i, "blockDBHash", currentDB) + self._core.setAddressInfo(i, "DBHash", currentDB) if len(blocks.strip()) != 0: logger.debug('BLOCKS:' + blocks) blockList = blocks.split('\n') @@ -162,7 +162,7 @@ class OnionrCommunicate: ''' return urllib.parse.quote_plus(data) - def performGet(self, action, peer, data=None, type='tor'): + def performGet(self, action, peer, data=None, peerType='tor'): ''' Performs a request to a peer through Tor or i2p (currently only Tor) ''' @@ -172,10 +172,10 @@ class OnionrCommunicate: # Store peer in peerData dictionary (non permanent) if not peer in self.peerData: self.peerData[peer] = {'connectCount': 0, 'failCount': 0, 'lastConnectTime': math.floor(time.time())} - socksPort = sys.argv[2] + logger.debug('Contacting ' + peer + ' on port ' + socksPort) '''We use socks5h to use tor as DNS''' - proxies = {'http': 'socks5h://127.0.0.1:' + str(socksPort), 'https': 'socks5h://127.0.0.1:' + str(socksPort)} + proxies = {'http': 'socks5://127.0.0.1:' + str(socksPort), 'https': 'socks5://127.0.0.1:' + str(socksPort)} headers = {'user-agent': 'PyOnionr'} url = 'http://' + peer + '/public/?action=' + self.urlencode(action) if data != None: diff --git a/onionr/core.py b/onionr/core.py index cb52653c..54ac195e 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -84,7 +84,7 @@ class Core: return True else: return False - + def removeAddress(self, address): '''Remove an address from the address database''' if self._utils.validateID(address): @@ -115,6 +115,7 @@ class Core: knownPeer text, speed int, success int, + DBHash text, failure int ); ''') @@ -339,7 +340,7 @@ class Core: addresses = c.execute('SELECT * FROM adders;') addressList = [] for i in addresses: - addressList.append(i[2]) + addressList.append(i[0]) conn.close() return addressList @@ -369,16 +370,15 @@ class Core: id text 0 name text, 1 adders text, 2 - blockDBHash text, 3 - forwardKey text, 4 - dateSeen not null, 5 - bytesStored int, 6 - trust int 7 + forwardKey text, 3 + dateSeen not null, 4 + bytesStored int, 5 + trust int 6 ''' conn = sqlite3.connect(self.peerDB) c = conn.cursor() command = (peer,) - infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'blockDBHash': 3, 'forwardKey': 4, 'dateSeen': 5, 'bytesStored': 6, 'trust': 7} + infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'forwardKey': 3, 'dateSeen': 4, 'bytesStored': 5, 'trust': 6} info = infoNumbers[info] iterCount = 0 retVal = '' @@ -406,7 +406,50 @@ class Core: c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command) conn.commit() conn.close() + return + def getAddressInfo(self, address, info): + ''' + Get info about an address from its database entry + + address text, 0 + type int, 1 + knownPeer text, 2 + speed int, 3 + success int, 4 + DBHash text, 5 + failure int 6 + ''' + conn = sqlite3.connect(self.addressDB) + c = conn.cursor() + command = (address,) + infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'DBHash': 5, 'failure': 6} + info = infoNumbers[info] + iterCount = 0 + retVal = '' + for row in c.execute('SELECT * from adders where address=?;', command): + for i in row: + if iterCount == info: + retVal = i + break + else: + iterCount += 1 + conn.close() + return retVal + + def setAddressInfo(self, address, key, data): + ''' + Update an address for a key + ''' + conn = sqlite3.connect(self.addressDB) + c = conn.cursor() + command = (data, address) + # TODO: validate key on whitelist + if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'DBHash', 'failure'): + raise Exception("Got invalid database key when setting address info") + c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command) + conn.commit() + conn.close() return def getBlockList(self, unsaved=False): From 32b8d9c1a768455f7eb283b2bc7a89314cd7a6d9 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 27 Feb 2018 18:18:48 -0600 Subject: [PATCH 63/89] fixed broken test --- onionr/tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionr/tests.py b/onionr/tests.py index 6a70186c..c7659cf1 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -121,6 +121,8 @@ class OnionrTests(unittest.TestCase): logger.info('Running address add+remove test') import core myCore = core.Core() + if not os.path.exists('data/address.db'): + myCore.createAddressDB() if myCore.addAddress('facebookcorewwwi.onion') and not myCore.removeAddress('invalid'): if myCore.removeAddress('facebookcorewwwi.onion'): self.assertTrue(True) From 5564d540cbdbef29a5555842cd5af43a8fe556b1 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 27 Feb 2018 19:44:00 -0600 Subject: [PATCH 64/89] renamed pow to onionrproofs since pow is a taken keyword --- onionr/communicator.py | 2 +- onionr/{pow.py => onionrproofs.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename onionr/{pow.py => onionrproofs.py} (100%) diff --git a/onionr/communicator.py b/onionr/communicator.py index 54e9c512..3fe60169 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -20,7 +20,7 @@ and code to operate as a daemon, getting commands from the command queue databas along with this program. If not, see . ''' import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse -import core, onionrutils, onionrcrypto, pow, btc +import core, onionrutils, onionrcrypto, onionrproofs, btc class OnionrCommunicate: def __init__(self, debug, developmentMode): diff --git a/onionr/pow.py b/onionr/onionrproofs.py similarity index 100% rename from onionr/pow.py rename to onionr/onionrproofs.py From 15aa395946256a3520352dd372a21672d49b9351 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 28 Feb 2018 03:06:02 -0600 Subject: [PATCH 65/89] work on crypto --- onionr/communicator.py | 2 ++ onionr/onionr.py | 21 +++++++++++++++------ onionr/onionrutils.py | 4 ++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 3fe60169..d1bc9ef4 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -46,6 +46,8 @@ class OnionrCommunicate: blockProcessAmount = 5 heartBeatTimer = 0 heartBeatRate = 5 + pexTimer = 900 # How often we should check for new peers + pexCount = 0 logger.debug('Communicator debugging enabled.') torID = open('data/hs/hostname').read() diff --git a/onionr/onionr.py b/onionr/onionr.py index ee18f02c..2792b9e1 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -216,14 +216,23 @@ class Onionr: ''' while True: - peer = logger.readline('Peer to send to: ') - if self.onionrUtils.validateID(peer): + try: + peer = logger.readline('Peer to send to: ') + except KeyboardInterrupt: break else: - logger.error('Invalid peer ID') - message = logger.readline("Enter a message: ") - logger.info("Sending message to " + peer) - self.onionrUtils.sendPM(peer, message) + if self.onionrUtils.validateID(peer): + break + else: + logger.error('Invalid peer ID') + else: + try: + message = logger.readline("Enter a message: ") + except KeyboardInterrupt: + pass + else: + logger.info("Sending message to " + peer) + self.onionrUtils.sendPM(peer, message) def openGUI(self): diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 35ba38e3..a0db7570 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -36,6 +36,10 @@ class OnionrUtils: self.fingerprintFile = 'data/own-fingerprint.txt' self._core = coreInstance return + + def sendPM(self, user, message): + '''High level function to encrypt a message to a peer and insert it as a block''' + return def localCommand(self, command): ''' From 17d1b9e340b9d9203d73b4bf86d9b829bc0e02bd Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 1 Mar 2018 03:20:57 -0600 Subject: [PATCH 66/89] work on pex --- onionr/communicator.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index d1bc9ef4..56d3bbd8 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -33,14 +33,7 @@ class OnionrCommunicate: self._utils = onionrutils.OnionrUtils(self._core) self._crypto = onionrcrypto.OnionrCrypto(self._core) logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2])) - #while True: - #try: self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2])) - #except: - # ugly but needed - # pass - #else: - # break logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight())) blockProcessTimer = 0 blockProcessAmount = 5 @@ -60,6 +53,9 @@ class OnionrCommunicate: # Process blocks based on a timer blockProcessTimer += 1 heartBeatTimer += 1 + pexCount += 1 + if pexTimer == pexCount: + self.getNewPeers() if heartBeatRate == heartBeatTimer: logger.debug('Communicator heartbeat') heartBeatTimer = 0 @@ -75,7 +71,13 @@ class OnionrCommunicate: time.sleep(1) return - + + def getNewPeers(self): + ''' + Get new peers + ''' + return + def lookupBlocks(self): ''' Lookup blocks and merge new ones From 5641651c85dc533b0a89ad1ef5ba91a9f9a21577 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 1 Mar 2018 13:59:07 -0600 Subject: [PATCH 67/89] added functions to verify and sign with ed25519 --- onionr/onionrcrypto.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 6b282341..22a542b8 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -40,6 +40,26 @@ class OnionrCrypto: keyfile.write(self.pubKey + ',' + self.privKey) return + def edVerify(self, data, key): + '''Verify signed data (combined in nacl) to an ed25519 key''' + key = nacl.signing.VerifyKey(key=key, encoder=nacl.encoding.Base32Encoder) + retData = '' + if encodeResult: + retData = key.verify(data.encode(), encoder=nacl.encoding.Base64Encoder) # .encode() is not the same as nacl.encoding + else: + retData = key.verify(data.encode()) + return retData + + def edSign(self, data, key, encodeResult=False): + '''Ed25519 sign data''' + key = nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) + retData = '' + if encodeResult: + retData = key.sign(data.encode(), encoder=nacl.encoding.Base64Encoder) # .encode() is not the same as nacl.encoding + else: + retData = key.sign(data.encode()) + return retData + def pubKeyEncrypt(self, data, peer): '''Encrypt to a peers public key (Curve25519, taken from Ed25519 pubkey)''' return From ab17e0d198ce71257aa0cd45a6023dad93b883d8 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Fri, 2 Mar 2018 20:19:01 -0800 Subject: [PATCH 68/89] Add plugin support --- onionr/api.py | 8 +- onionr/communicator.py | 26 ++++- onionr/config.py | 1 + onionr/onionr.py | 86 +++++++++++++-- onionr/onionrevents.py | 53 +++++++++ onionr/onionrplugins.py | 231 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 391 insertions(+), 14 deletions(-) create mode 100644 onionr/onionrevents.py create mode 100644 onionr/onionrplugins.py diff --git a/onionr/api.py b/onionr/api.py index 7550f1c7..229df87e 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -181,9 +181,13 @@ class API: return resp if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...') - #logger.debug('Client token: ' + logger.colors.underline + self.clientToken) - app.run(host=self.host, port=bindPort, debug=True, threaded=True) + try: + app.run(host=self.host, port=bindPort, debug=True, threaded=True) + except Exception as e: + logger.error(str(e)) + logger.fatal('Failed to start client on ' + self.host + ':' + str(bindPort) + ', exiting...') + exit(1) def validateHost(self, hostType): ''' diff --git a/onionr/communicator.py b/onionr/communicator.py index 56d3bbd8..802a6ab6 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -29,12 +29,19 @@ class OnionrCommunicate: This class handles communication with nodes in the Onionr network. ''' + self._core = core.Core() self._utils = onionrutils.OnionrUtils(self._core) self._crypto = onionrcrypto.OnionrCrypto(self._core) - logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2])) - self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2])) - logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight())) + + try: + logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2])) + self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2])) + logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight())) + except: + logger.fatal('Failed to start Bitcoin Node, exiting...') + exit(1) + blockProcessTimer = 0 blockProcessAmount = 5 heartBeatTimer = 0 @@ -48,6 +55,10 @@ class OnionrCommunicate: if os.path.exists(self._core.queueDB): self._core.clearDaemonQueue() + + # Loads in and starts the enabled plugins + plugins.reload() + while True: command = self._core.daemonQueue() # Process blocks based on a timer @@ -63,7 +74,6 @@ class OnionrCommunicate: self.lookupBlocks() self.processBlocks() blockProcessTimer = 0 - #logger.debug('Communicator daemon heartbeat') if command != False: if command[0] == 'shutdown': logger.warn('Daemon recieved exit command.') @@ -71,17 +81,19 @@ class OnionrCommunicate: time.sleep(1) return - + def getNewPeers(self): ''' Get new peers ''' + return def lookupBlocks(self): ''' Lookup blocks and merge new ones ''' + peerList = self._core.listAdders() blocks = '' for i in peerList: @@ -126,6 +138,7 @@ class OnionrCommunicate: This is meant to be called from the communicator daemon on its timer. ''' + for i in self._core.getBlockList(True).split("\n"): if i != "": logger.warn('UNSAVED BLOCK: ' + i) @@ -137,6 +150,7 @@ class OnionrCommunicate: ''' Download a block from random order of peers ''' + peerList = self._core.listAdders() blocks = '' for i in peerList: @@ -164,12 +178,14 @@ class OnionrCommunicate: ''' URL encodes the data ''' + return urllib.parse.quote_plus(data) def performGet(self, action, peer, data=None, peerType='tor'): ''' Performs a request to a peer through Tor or i2p (currently only Tor) ''' + if not peer.endswith('.onion') and not peer.endswith('.onion/'): raise PeerError('Currently only Tor .onion peers are supported. You must manually specify .onion') diff --git a/onionr/config.py b/onionr/config.py index 793d8b9b..27002d34 100644 --- a/onionr/config.py +++ b/onionr/config.py @@ -27,6 +27,7 @@ def get(key, default = None): ''' Gets the key from configuration, or returns `default` ''' + if is_set(key): return get_config()[key] return default diff --git a/onionr/onionr.py b/onionr/onionr.py index 2792b9e1..563fd777 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -21,7 +21,7 @@ along with this program. If not, see . ''' import sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform -import api, core, gui, config, logger +import api, core, gui, config, logger, onionrplugins as plugins from onionrutils import OnionrUtils from netcontroller import NetController @@ -130,26 +130,44 @@ class Onionr: def getCommands(self): return { + '': self.showHelpSuggestion, 'help': self.showHelp, 'version': self.version, 'config': self.configure, 'start': self.start, 'stop': self.killDaemon, 'stats': self.showStats, + + '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, + 'listpeers': self.listPeers, 'list-peers': self.listPeers, - '': self.showHelpSuggestion, + 'addmsg': self.addMessage, 'addmessage': self.addMessage, 'add-msg': self.addMessage, 'add-message': self.addMessage, 'pm': self.sendEncrypt, + 'gui': self.openGUI, + 'addpeer': self.addPeer, 'add-peer': self.addPeer, 'add-address': self.addAddress, - 'connect': self.addAddress, - 'addaddress': self.addAddress + 'addaddress': self.addAddress, + + 'connect': self.addAddress } def getHelp(self): @@ -160,10 +178,13 @@ class Onionr: 'start': 'Starts the Onionr daemon', 'stop': 'Stops the Onionr daemon', 'stats': 'Displays node statistics', - 'list-peers': 'Displays a list of peers (?)', + 'enable-plugin': 'Enables and starts a plugin', + 'disable-plugin': 'Disables and stops a plugin', + 'reload-plugin': 'Reloads a plugin', + 'list-peers': 'Displays a list of peers', 'add-peer': 'Adds a peer (?)', 'add-msg': 'Broadcasts a message to the Onionr network', - 'pm': 'Adds a private message (?)', + 'pm': 'Adds a private message to block', 'gui': 'Opens a graphical interface for Onionr' } @@ -263,7 +284,9 @@ class Onionr: else: logger.info("Adding peer: " + logger.colors.underline + newPeer) self.onionrCore.addPeer(newPeer) - + + return + def addAddress(self): '''Adds a Onionr node address''' try: @@ -277,6 +300,8 @@ class Onionr: else: logger.warn("Unable to add address") + return + def addMessage(self): ''' Broadcasts a message to the Onionr network @@ -291,6 +316,52 @@ class Onionr: self.onionrCore.addToBlockDB(addedHash, selfInsert=True) self.onionrCore.setBlockType(addedHash, 'txt') + return + + def enablePlugin(self): + ''' + Enables and starts the given plugin + ''' + + if len(sys.argv) >= 3: + plugin_name = sys.argv[2] + logger.info('Enabling plugin \"' + plugin_name + '\"...') + plugins.enable(plugin_name) + else: + logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' ') + + return + + def disablePlugin(self): + ''' + Disables and stops the given plugin + ''' + + if len(sys.argv) >= 3: + plugin_name = sys.argv[2] + logger.info('Disabling plugin \"' + plugin_name + '\"...') + plugins.disable(plugin_name) + else: + logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' ') + + return + + 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 \"' + plugin_name + '\"...') + plugins.stop(plugin_name) + plugins.start(plugin_name) + else: + logger.info('Reloading all plugins...') + plugins.reload() + + return + def notFound(self): ''' Displays a "command not found" message @@ -325,6 +396,7 @@ class Onionr: ''' Starts the Onionr communication daemon ''' + if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": if self._developmentMode: logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') diff --git a/onionr/onionrevents.py b/onionr/onionrevents.py new file mode 100644 index 00000000..2c148d8f --- /dev/null +++ b/onionr/onionrevents.py @@ -0,0 +1,53 @@ +''' + Onionr - P2P Microblogging Platform & Social network + + This file deals with configuration 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 config, logger, onionrplugins as plugins + +def event(event_name, data = None, onionr = None): + ''' + Calls an event on all plugins (if defined) + ''' + + for plugin in plugins.get_enabled_plugins(): + try: + call(plugins.get_plugin(plugin), event_name, data, onionr) + except: + logger.warn('Event \"' + event_name + '\" failed for plugin \"' + plugin + '\".') + +def call(plugin, event_name, data = None, onionr = None): + ''' + Calls an event on a plugin if one is defined + ''' + + if not plugin is None: + try: + attribute = 'on_' + str(event_name).lower() + + # TODO: Use multithreading perhaps? + if hasattr(plugin, attribute): + logger.debug('Calling event ' + str(event_name)) + getattr(plugin, attribute)(onionr, data) + + return True + except: + logger.warn('Failed to call event ' + str(event_name) + ' on module.') + return False + else: + return True diff --git a/onionr/onionrplugins.py b/onionr/onionrplugins.py new file mode 100644 index 00000000..d3c3aefb --- /dev/null +++ b/onionr/onionrplugins.py @@ -0,0 +1,231 @@ +''' + Onionr - P2P Microblogging Platform & Social network + + This file deals with management of modules/plugins. +''' +''' + 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, re, importlib, config, logger +import onionrevents as events + +_pluginsfolder = 'data/plugins/' +_instances = dict() + +def reload(stop_event = True): + ''' + Reloads all the plugins + ''' + + check() + + try: + enabled_plugins = get_enabled_plugins() + + if stop_event is True: + logger.debug('Reloading all plugins...') + else: + logger.debug('Loading all plugins...') + + if stop_event is True: + for plugin in enabled_plugins: + stop(plugin) + + for plugin in enabled_plugins: + start(plugin) + + return True + except: + logger.error('Failed to reload plugins.') + + return False + + +def enable(name, start_event = True): + ''' + Enables a plugin + ''' + + check() + + if exists(name): + enabled_plugins = get_enabled_plugins() + enabled_plugins.append(name) + config_plugins = config.get('plugins') + config_plugins['enabled'] = enabled_plugins + config.set('plugins', config_plugins, True) + + events.call(get_plugin(name), 'enable') + + if start_event is True: + start(name) + + return True + else: + logger.error('Failed to enable plugin \"' + name + '\", disabling plugin.') + disable(name) + + return False + + +def disable(name, stop_event = True): + ''' + Disables a plugin + ''' + + check() + + if is_enabled(name): + enabled_plugins = get_enabled_plugins() + enabled_plugins.remove(name) + config_plugins = config.get('plugins') + config_plugins['enabled'] = enabled_plugins + config.set('plugins', config_plugins, True) + + if exists(name): + events.call(get_plugin(name), 'disable') + + if stop_event is True: + stop(name) + +def start(name): + ''' + Starts the plugin + ''' + + check() + + if exists(name): + try: + plugin = get_plugin(name) + + if plugin is None: + raise Exception('Failed to import module.') + else: + events.call(plugin, 'start') + + return plugin + except: + logger.error('Failed to start module \"' + name + '\".') + else: + logger.error('Failed to start nonexistant module \"' + name + '\".') + + return None + +def stop(name): + ''' + Stops the plugin + ''' + + check() + + if exists(name): + try: + plugin = get_plugin(name) + + if plugin is None: + raise Exception('Failed to import module.') + else: + events.call(plugin, 'stop') + + return plugin + except: + logger.error('Failed to stop module \"' + name + '\".') + else: + logger.error('Failed to stop nonexistant module \"' + name + '\".') + + return None + +def get_plugin(name): + ''' + Returns the instance of a module + ''' + + check() + + if str(name).lower() in _instances: + return _instances[str(name).lower()] + else: + _instances[str(name).lower()] = importlib.import_module(get_plugins_folder(name, False).replace('/', '.') + 'main') + return get_plugin(name) + +def get_plugins(): + ''' + Returns a list of plugins (deprecated) + ''' + + return _instances + +def exists(name): + ''' + Return value indicates whether or not the plugin exists + ''' + + check() + + return os.path.isdir(get_plugins_folder(str(name).lower())) + +def get_enabled_plugins(): + ''' + Returns a list of the enabled plugins + ''' + + check() + + config.reload() + + return config.get('plugins')['enabled'] + +def is_enabled(name): + ''' + Return value indicates whether or not the plugin is enabled + ''' + + return name in get_enabled_plugins() + +def get_plugins_folder(name = None, absolute = True): + ''' + Returns the path to the plugins folder + ''' + + path = '' + + if name is None: + path = _pluginsfolder + else: + # only allow alphanumeric characters + path = _pluginsfolder + re.sub('[^0-9a-zA-Z]+', '', str(name).lower()) + '/' + + if absolute is True: + path = os.path.abspath(path) + + return path + +def check(): + ''' + Checks to make sure files exist + ''' + + config.reload() + + if not config.is_set('plugins'): + logger.debug('Generating plugin config data...') + config.set('plugins', {'enabled': []}, True) + + if not os.path.exists(os.path.dirname(get_plugins_folder())): + logger.debug('Generating plugin data folder...') + os.path.mkdirs(os.path.dirname(get_plugins_folder())) + + return From b04ea55e484165894eddff33ba16d21e04a0cfb1 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Fri, 2 Mar 2018 23:00:43 -0800 Subject: [PATCH 69/89] Add support for programmatic command creation --- onionr/onionr.py | 59 +++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index 563fd777..c564dcf2 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -34,6 +34,9 @@ ONIONR_VERSION = '0.0.0' # for debugging and stuff API_VERSION = '1' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes knows how to communicate without learning too much information about you. class Onionr: + cmds = {} + cmdhelp = {} + def __init__(self): ''' Main Onionr class. This is for the CLI program, and does not handle much of the logic. @@ -109,27 +112,7 @@ class Onionr: break config.set('CLIENT', {'participate': 'true', 'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION}, True) - command = '' - try: - command = sys.argv[1].lower() - except IndexError: - command = '' - finally: - self.execute(command) - - if not self._developmentMode: - encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory: ') - self.onionrCore.dataDirEncrypt(encryptionPassword) - shutil.rmtree('data/') - - return - - ''' - THIS SECTION HANDLES THE COMMANDS - ''' - - def getCommands(self): - return { + self.cmds = { '': self.showHelpSuggestion, 'help': self.showHelp, 'version': self.version, @@ -170,8 +153,7 @@ class Onionr: 'connect': self.addAddress } - def getHelp(self): - return { + self.cmdhelp = { 'help': 'Displays this Onionr help menu', 'version': 'Displays the Onionr version', 'config': 'Configures something and adds it to the file', @@ -188,6 +170,37 @@ class Onionr: 'gui': 'Opens a graphical interface for Onionr' } + command = '' + try: + command = sys.argv[1].lower() + except IndexError: + command = '' + finally: + self.execute(command) + + if not self._developmentMode: + encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory: ') + self.onionrCore.dataDirEncrypt(encryptionPassword) + shutil.rmtree('data/') + + return + + ''' + THIS SECTION HANDLES THE COMMANDS + ''' + + def getCommands(self): + return self.cmds + + def getHelp(self): + return self.cmdhelp + + def addCommand(self, command, function): + cmds[str(command).lower()] = function + + def addHelp(self, command, description): + cmdhelp[str(command).lower()] = str(description) + def configure(self): ''' Displays something from the configuration file, or sets it From 0cefcec11af618fb70c0872ef48fe1249345ba4d Mon Sep 17 00:00:00 2001 From: Arinerron Date: Fri, 2 Mar 2018 23:10:27 -0800 Subject: [PATCH 70/89] Add unit test --- onionr/onionr.py | 3 ++- onionr/tests.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index c564dcf2..fbb12bc3 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -30,6 +30,7 @@ try: except ImportError: raise Exception("You need the PySocks module (for use with socks5 proxy to use Tor)") +ONIONR_TAGLINE = 'Anonymous P2P Platform - GPLv3 - onionr.voidnet.tech' ONIONR_VERSION = '0.0.0' # for debugging and stuff API_VERSION = '1' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes knows how to communicate without learning too much information about you. @@ -240,7 +241,7 @@ class Onionr: ''' logger.info('Onionr ' + ONIONR_VERSION + ' (' + platform.machine() + ') - API v' + API_VERSION) if verbosity >= 1: - logger.info('Anonymous P2P Platform - GPLv3 - onionr.voidnet.tech') + logger.info(ONIONR_TAGLINE) if verbosity >= 2: logger.info('Running on ' + platform.platform() + ' ' + platform.release()) diff --git a/onionr/tests.py b/onionr/tests.py index c7659cf1..684640b5 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -85,6 +85,16 @@ class OnionrTests(unittest.TestCase): else: self.assertTrue(False) + def testPlugins(self): + logger.debug('--------------------------') + logger.info('Running simple plugin system test...') + import onionrplugins + try: + onionrplugins.reload('test') + self.assertTrue(True) + except: + self.assertTrue(False) + def testQueue(self): logger.debug('--------------------------') logger.info('Running daemon queue test...') From d5daeae5322d188a81aab4a33b98f0f594c9c284 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Fri, 2 Mar 2018 23:26:02 -0800 Subject: [PATCH 71/89] Refactor code --- onionr/api.py | 4 ++-- onionr/onionr.py | 16 ++++++++-------- onionr/onionrutils.py | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 229df87e..33b759a4 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -60,9 +60,9 @@ class API: self._crypto = onionrcrypto.OnionrCrypto(self._core) self._utils = onionrutils.OnionrUtils(self._core) app = flask.Flask(__name__) - bindPort = int(config.get('CLIENT')['PORT']) + bindPort = int(config.get('client')['port']) self.bindPort = bindPort - self.clientToken = config.get('CLIENT')['CLIENT HMAC'] + self.clientToken = config.get('client')['client_hmac'] if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": logger.debug('Your HMAC token: ' + logger.colors.underline + self.clientToken) diff --git a/onionr/onionr.py b/onionr/onionr.py index fbb12bc3..f0ddc7f7 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -52,17 +52,17 @@ class Onionr: # Load global configuration data exists = os.path.exists(config.get_config_file()) - config.set_config({'devmode': True, 'log.file': True, 'log.console': True, 'log.outputfile': 'data/output.log', 'log.color': True}) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it + config.set_config({'devmode': True, 'log': {'file': {'output': True, 'path': 'data/output.log'}, 'console': {'output': True, 'color': True}}}) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it config.reload() # this will read the configuration file into memory settings = 0b000 - if config.get('log.color', True): + if config.get('log', {'console': {'color': True}})['console']['color']: settings = settings | logger.USE_ANSI - if config.get('log.console', True): + if config.get('log', {'console': {'output': True}})['console']['output']: settings = settings | logger.OUTPUT_TO_CONSOLE - if config.get('log.file', False): + if config.get('log', {'file': {'output': True}})['file']['output']: settings = settings | logger.OUTPUT_TO_FILE - logger.set_file(config.get('log.outputfile', 'data/output.log')) + logger.set_file(config.get('log', {'file': {'path': 'data/output.log'}})['file']['path']) logger.set_settings(settings) if config.get('devmode', True): @@ -111,7 +111,7 @@ class Onionr: randomPort = random.randint(1024, 65535) if self.onionrUtils.checkPort(randomPort): break - config.set('CLIENT', {'participate': 'true', 'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION}, True) + config.set('client', {'participate': 'true', 'client_hmac': base64.b64encode(os.urandom(32)).decode('utf-8'), 'port': randomPort, 'api_version': API_VERSION}, True) self.cmds = { '': self.showHelpSuggestion, @@ -414,7 +414,7 @@ class Onionr: if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": if self._developmentMode: logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') - net = NetController(config.get('CLIENT')['PORT']) + net = NetController(config.get('client')['port']) logger.info('Tor is starting...') if not net.startTor(): sys.exit(1) @@ -433,7 +433,7 @@ class Onionr: ''' logger.warn('Killing the running daemon') - net = NetController(config.get('CLIENT')['PORT']) + net = NetController(config.get('client')['port']) try: self.onionrUtils.localCommand('shutdown') except requests.exceptions.ConnectionError: diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index a0db7570..50c973fb 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -36,7 +36,7 @@ class OnionrUtils: self.fingerprintFile = 'data/own-fingerprint.txt' self._core = coreInstance return - + def sendPM(self, user, message): '''High level function to encrypt a message to a peer and insert it as a block''' return @@ -49,7 +49,7 @@ class OnionrUtils: config.reload() # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless. - requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('CLIENT')['PORT']) + '/client/?action=' + command + '&token=' + config.get('CLIENT')['CLIENT HMAC']) + requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('client')['port']) + '/client/?action=' + command + '&token=' + str(config.get('client')['client_hmac'])) return From c2db59ba8b0843efd2d8d9a7f6494369af82b028 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Fri, 2 Mar 2018 23:35:13 -0800 Subject: [PATCH 72/89] Minor bug fixes, and more refactoring --- .gitignore | 1 + onionr/netcontroller.py | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2a7d956a..11ff0671 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ onionr/*.log onionr/data/hs/hostname onionr/data/* onionr/gnupg/* +run.sh diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index f56f15e4..76248beb 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -17,11 +17,14 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' + import subprocess, os, random, sys, logger, time, signal + class NetController: ''' This class handles hidden service setup on Tor and I2P ''' + def __init__(self, hsPort): self.torConfigLocation = 'data/torrc' self.readyState = False @@ -36,13 +39,14 @@ class NetController: os.remove(self.torConfigLocation) torrc.close() ''' - + return def generateTorrc(self): ''' Generate a torrc file for our tor instance ''' + if os.path.exists(self.torConfigLocation): os.remove(self.torConfigLocation) torrcData = '''SocksPort ''' + str(self.socksPort) + ''' @@ -59,11 +63,16 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' ''' Start Tor with onion service on port 80 & socks proxy on random port ''' + self.generateTorrc() + if os.path.exists('./tor'): torBinary = './tor' + elif os.path.exists('/usr/bin/tor'): + torBinary = '/usr/bin/tor' else: torBinary = 'tor' + try: tor = subprocess.Popen([torBinary, '-f', self.torConfigLocation], stdout=subprocess.PIPE, stderr=subprocess.PIPE) except FileNotFoundError: @@ -83,15 +92,18 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' if 'Bootstrapped 100%: Done' in line.decode(): break elif 'Opening Socks listener' in line.decode(): - logger.debug(line.decode()) + logger.debug(line.decode().replace('\n', '')) else: logger.fatal('Failed to start Tor. Try killing any other Tor processes owned by this user.') return False + logger.info('Finished starting Tor') self.readyState = True + myID = open('data/hs/hostname', 'r') - self.myID = myID.read() + self.myID = myID.read().replace('\n', '') myID.close() + torPidFile = open('data/torPid.txt', 'w') torPidFile.write(str(tor.pid)) torPidFile.close() @@ -102,16 +114,19 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' ''' Properly kill tor based on pid saved to file ''' + try: pid = open('data/torPid.txt', 'r') pidN = pid.read() pid.close() except FileNotFoundError: return + try: int(pidN) except: return + try: os.kill(int(pidN), signal.SIGTERM) os.remove('data/torPid.txt') From cc4687021ac60ae3100497f3e9a9833be3d85851 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Fri, 2 Mar 2018 23:37:46 -0800 Subject: [PATCH 73/89] Migrate from old dev-enabled to devmode --- onionr/communicator.py | 6 +++--- onionr/dev-enabled | 0 2 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 onionr/dev-enabled diff --git a/onionr/communicator.py b/onionr/communicator.py index 802a6ab6..8079e72c 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -20,7 +20,7 @@ and code to operate as a daemon, getting commands from the command queue databas along with this program. If not, see . ''' import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse -import core, onionrutils, onionrcrypto, onionrproofs, btc +import core, onionrutils, onionrcrypto, onionrproofs, btc, config class OnionrCommunicate: def __init__(self, debug, developmentMode): @@ -41,7 +41,7 @@ class OnionrCommunicate: except: logger.fatal('Failed to start Bitcoin Node, exiting...') exit(1) - + blockProcessTimer = 0 blockProcessAmount = 5 heartBeatTimer = 0 @@ -218,7 +218,7 @@ class OnionrCommunicate: shouldRun = False debug = True developmentMode = False -if os.path.exists('dev-enabled'): +if logger.get('devmode', True): developmentMode = True try: if sys.argv[1] == 'run': diff --git a/onionr/dev-enabled b/onionr/dev-enabled deleted file mode 100644 index e69de29b..00000000 From 3813ee56ef5991e04a580a65e4c7929cbedac2f6 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 3 Mar 2018 03:18:53 -0600 Subject: [PATCH 74/89] fixed issues from plugin addition --- onionr/communicator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 8079e72c..b9339860 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -20,7 +20,7 @@ and code to operate as a daemon, getting commands from the command queue databas along with this program. If not, see . ''' import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse -import core, onionrutils, onionrcrypto, onionrproofs, btc, config +import core, onionrutils, onionrcrypto, onionrproofs, btc, config, onionrplugins as plugins class OnionrCommunicate: def __init__(self, debug, developmentMode): @@ -76,7 +76,7 @@ class OnionrCommunicate: blockProcessTimer = 0 if command != False: if command[0] == 'shutdown': - logger.warn('Daemon recieved exit command.') + logger.info('Daemon recieved exit command.') break time.sleep(1) @@ -218,7 +218,7 @@ class OnionrCommunicate: shouldRun = False debug = True developmentMode = False -if logger.get('devmode', True): +if config.get('devmode', True): developmentMode = True try: if sys.argv[1] == 'run': From 262357140ff6edc3ff8fe0579765450ef9529e20 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 3 Mar 2018 03:31:57 -0600 Subject: [PATCH 75/89] added logo --- docs/onionr-logo.png | Bin 0 -> 56552 bytes readme.md | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/onionr-logo.png diff --git a/docs/onionr-logo.png b/docs/onionr-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec3979991acdf47a975fa70e3ee30aa89bd23e5 GIT binary patch literal 56552 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 Date: Sat, 3 Mar 2018 03:33:47 -0600 Subject: [PATCH 76/89] fixed logo --- docs/onionr-logo.png | Bin 56552 -> 53247 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/onionr-logo.png b/docs/onionr-logo.png index 2ec3979991acdf47a975fa70e3ee30aa89bd23e5..0f7130312d17bb52798aae94c40dc9eed3109274 100644 GIT binary patch literal 53247 zcmbSy1yh?{*L86BQlPlIyL)kWcPD6ZFHUiHDOOy9LvbihDMf?3yA}P?-p~67zH27K zkO^U*bGEF#);`gy$}(ukACLh60GgbvlsW(a4FUk5-Xp?8-tkx?1w($ox=YAuB0?U2 zM9U}ufD9lfC9dh6dlKN2Ni6;S&R{NJO7Xn}Hg-$>xm0>R4z?v06DHS<8T*}b zcfCFydmq=d*4xfdFzGiTDrB$>37&Vski))bG1S$Y|KLy^!llISd7^uBcW1uR=87-q z$uyimn$SG2kCH;8)W0*D!fyAAs=DX(k?)`|bSr@LpNS1IWZl0h(@~th-)j3FRg_=c z%&LG7muw1#wuZjwc+N6?OdyF<;$3~018gDSS@(B$?EbU2=r+#G?l52dU?}zPiNzeu z@Ek03JuJ=mC5a&%_UC$kJDN7X*TDIEb7KydZ7d-#x<`*~i&kVWAZ zWJLXY%pxA#;_)$SyC3a4p3XX5*8}d&UZ4MrT>D$`+J%!VX*hPCwk!f$SJ~Y66p4bK zFE9T@|M!UXg2Blge~$CXsZVpY?UUo~5ce z11V@?jbLzw;|KJ&=La9yEgFWuvE$F3h&vaDaDR-0N5tp#MU|B|K;I{lOAcm_Xw6`P zFBv3s?v8#H8u+D+a(=_yXQ1$Y`&Qa{dvfT@#iJm~<(PrIbo|p)aL7Vz$Q^>q;DI+u^VGCK<5?iuxaBEZ9B9hBF1MC>>k- ztQYOSqt##PACoD)V^34p$F`R(QL#j$m+lv@&KrqX2F9~nzE4iX>_uu()QI%!Qm|fq z0g$D{oZPkQdk6M0K7gy#1Frw=X-y%~aKpAdrp?($J2CbR_Njt~0b$q)V? z#|mqG#X&(IFnTUfNdhrVRt=f*U(2D#MUH!7rHkFOohC;_Wg7gQf(&uymeqbUhwbg|aw2 zA2ejPPu?wh9dS*p`1&b+Xr)P}g4!pt3yw_>5ewKGPCuyir zwV7PuD&ul*Li!`;%R0f$Z6=>9g)-kJtf5OE+@;=sdxl8XO(w%8a$BJ?atsn6H%+!$g~p2INUlc4 zx=NNaI2i^XAjW`=22gB`l*Bp-W5={GmBN%NJ^hUM)3eFxC6?WTghYNv+c`s>f;O5; zu3Aqzs485>*!zQ*$R(Ro&Ktnk-=eRVH8%8PaY%yy4rSZjvconf;lC8JN!E=oLl)1s zg_teq=&G$Y>?=%I|6B=-g@XJujB~l^%;%dgRKn(~80ARLA!}aFS zN3oaWX2_P1$5DkD@wg*Ni$FE;M};e*Osvp?q)J8_+4+A`Em6-#aCMGVhY3t&lkjXVbC~cBxK$qT2M!(is_3Qc zD_A6p`RZOVKICc#%HxPpb0nE5DHrd8(52f!)emQ_JB=vN#|#7A{K3)Yrnp+=Yl(WH z3ifed$N3S2xHC2QoA4|iyKX$`sSESQ+|I%ML=H4byNT*s4R{3uE0J%(1|&VC&Zra; z{Sf0W=IR1*pU!)N{NpJpAdj}eV8#YwFL@-91n;{4H3#uH{=iL5>pq5ytYG&LBnsM z<^o1d_Z5@#P*m-fNLa^7k;~wf#q#&5`yC%k5bw1-dE7^t0961&La_FHzw_H8e&F+l z@WTM73Jd6Z(Qp09_-|pq)caSEbeXYCf(P(PpB8@{3==vV(zt()c8DEd=fvoRSc#`G zpNmKiiwzX{Df=t=%3e%D8)LIr)f#fd-XWZbEaD-u2-ZtlMlVQgnwXxX?uNLPBh?Pt zrl~qXXN*l{%4e5y#r+(GC8+KR6Um7iLcnv4yq`tyDw)QU_U$=}`d=^Sk2Brhj541V zNi*bXtYSC67037gE%OD3|AKrD`ErOC)fEB=ewAhzcPPdifq`8F@uC};{-&*2rcxt~ zs3mfX-_(jz<)F4p>$M|PFI8i;jcJNDCKy^`N0x^_yN`q|6ZQIELuV*)$nj$ zY}UCCxU_%!6054Ny!xq~PSaQ{F9j2NMX8_6lAaz`#{Agp_+pd^Nw2ZOLo)Q%ih1R3 zF!W*b<@#br^lk0Z_sN#)1K(yCjtc~64BH6)J!wUe&wk#gG5Ye285vUIpRRVv8$X5D<8UxUpuvvy>~z!O63d$o7H{?v z4y8i$R=rn~Nb*Ik-b;$XijG>wiBd3WG4B(JR4$S_$VT`-lcoIoYAlKTP8$Bg3ABSuF1d@otfy^IBuOY5 zO-+q#QtHUl6A&6O#qaw8jq5|4`9ogJr`m~VebZD;^03K%E6VqLVe`}=8dR+Y+C}5<4DJ zf?wpIy=QD&T>S7XiL0cDY@4h#<|39N8{o8A({NJ9| z%UJ^s6}+z}c{@dDHi<}c+eMZ6p9dDb^KQwgaZ6-nZ&;1&gK*VjNXS}m7o$FVsX>Fy?zw4qF`7$t0YivC}( zB5cDK)8WpWF+M&0Dx@6|aq*ERI0)2⋘;6j_Cnl{!<&WnG!=D~JAqQ`E!LXD*^ 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 K52t!Z6z)I$Ch?a5 literal 56552 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 Date: Sat, 3 Mar 2018 03:36:00 -0600 Subject: [PATCH 77/89] fixed logo --- docs/onionr-logo.png | Bin 53247 -> 52812 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/onionr-logo.png b/docs/onionr-logo.png index 0f7130312d17bb52798aae94c40dc9eed3109274..f9a476397bd6c972ae82bd8ffc04d8183e72dbc1 100644 GIT binary patch literal 52812 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%0D78r2LyqO>6*CB`}kx$>=%YZqaG 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⋘;6j_Cnl{!<&WnG!=D~JAqQ`E!LXD*^ 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 K52t!Z6z)I$Ch?a5 From 3615390b4c73c14976e959ba50c845c3943545d9 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 3 Mar 2018 03:36:56 -0600 Subject: [PATCH 78/89] fixed logo --- docs/onionr-logo.png | Bin 52812 -> 9633 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/onionr-logo.png b/docs/onionr-logo.png index f9a476397bd6c972ae82bd8ffc04d8183e72dbc1..f52533f091c27640a81b34b5b156e64d7cf38f3e 100644 GIT binary patch literal 9633 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 XqTw6@OrUvD00000NkvXXu0mjfOThin literal 52812 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%0D78r2LyqO>6*CB`}kx$>=%YZqaG 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 m-+Dv`tdiwoIw7S`|1%c Date: Sat, 3 Mar 2018 03:38:27 -0600 Subject: [PATCH 79/89] fixed logo --- readme.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index b772d06e..8746ddb2 100644 --- a/readme.md +++ b/readme.md @@ -1,12 +1,11 @@ +![Onionr logo](./docs/onionr-logo.png) + # Onionr [![Build Status](https://travis-ci.org/beardog108/onionr.svg?branch=master)](https://travis-ci.org/beardog108/onionr) Anonymous P2P platform, using Tor & I2P. -![Onionr logo](./docs/onionr-logo.png) - - Major work in progress. ***THIS SOFTWARE IS NOT USABLE OR SAFE YET.*** From ee59b122385847a63503966e20da876b4afa4dba Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 3 Mar 2018 14:18:57 -0800 Subject: [PATCH 80/89] Add more unit tests --- .gitignore | 1 + Makefile | 16 ++++--- onionr/config.py | 7 ++- onionr/onionrplugins.py | 9 ++-- onionr/tests.py | 95 +++++++++++++++++++++++++++++++++++------ 5 files changed, 103 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 11ff0671..12f3a0fd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ onionr/*.pyc onionr/*.log onionr/data/hs/hostname onionr/data/* +onionr/data-backup/* onionr/gnupg/* run.sh diff --git a/Makefile b/Makefile index 3e8c020e..10ab5390 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,10 @@ setup: sudo pip3 install -r requirements.txt - sudo rm -rf /usr/share/onionr/ - sudo rm -f /usr/bin/onionr install: + sudo rm -rf /usr/share/onionr/ + sudo rm -f /usr/bin/onionr sudo cp -rp ./onionr /usr/share/onionr sudo sh -c "echo \"#!/bin/sh\ncd /usr/share/onionr/\n./onionr.py \\\"\\\$$@\\\"\" > /usr/bin/onionr" sudo chmod +x /usr/bin/onionr @@ -16,12 +16,14 @@ uninstall: sudo rm -f /usr/bin/onionr test: - @cd onionr; ./tests.py - + @rm -rf onionr/data-backup + @mv onionr/data onionr/data-backup | true > /dev/null 2>&1 + -@cd onionr; ./tests.py + @rm -rf onionr/data + @mv onionr/data-backup onionr/data | true > /dev/null 2>&1 + reset: - echo "RESETING ONIONR" rm -f onionr/data/blocks/*.dat | true > /dev/null 2>&1 rm -f onionr/data/peers.db | true > /dev/null 2>&1 rm -f onionr/data/blocks.db | true > /dev/null 2>&1 - rm -rf onionr/data/address.db | true > /dev/null 2>&1 - + rm -f onionr/data/address.db | true > /dev/null 2>&1 diff --git a/onionr/config.py b/onionr/config.py index 27002d34..fb8ac161 100644 --- a/onionr/config.py +++ b/onionr/config.py @@ -27,7 +27,7 @@ def get(key, default = None): ''' Gets the key from configuration, or returns `default` ''' - + if is_set(key): return get_config()[key] return default @@ -38,7 +38,10 @@ def set(key, value = None, savefile = False): ''' global _config - _config[key] = value + if value is None: + del _config[key] + else: + _config[key] = value if savefile: save() diff --git a/onionr/onionrplugins.py b/onionr/onionrplugins.py index d3c3aefb..ae9c8d8e 100644 --- a/onionr/onionrplugins.py +++ b/onionr/onionrplugins.py @@ -173,8 +173,6 @@ def exists(name): Return value indicates whether or not the plugin exists ''' - check() - return os.path.isdir(get_plugins_folder(str(name).lower())) def get_enabled_plugins(): @@ -226,6 +224,11 @@ def check(): if not os.path.exists(os.path.dirname(get_plugins_folder())): logger.debug('Generating plugin data folder...') - os.path.mkdirs(os.path.dirname(get_plugins_folder())) + os.makedirs(os.path.dirname(get_plugins_folder())) + if not exists('test'): + os.makedirs(get_plugins_folder('test')) + with open(get_plugins_folder('test') + '/main.py', 'a') as main: + main.write("print('Running')\n\ndef on_test(onionr = None, data = None):\n print('received test event!')\n return True\n\ndef on_start(onionr = None, data = None):\n print('start event called')\n\ndef on_stop(onionr = None, data = None):\n print('stop event called')\n\ndef on_enable(onionr = None, data = None):\n print('enable event called')\n\ndef on_disable(onionr = None, data = None):\n print('disable event called')\n") + enable('test') return diff --git a/onionr/tests.py b/onionr/tests.py index 684640b5..50a7f6b9 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import unittest, sys, os, base64, tarfile, shutil, simplecrypt, logger +import unittest, sys, os, base64, tarfile, shutil, simplecrypt, logger, btc class OnionrTests(unittest.TestCase): def testPython3(self): @@ -25,9 +25,9 @@ class OnionrTests(unittest.TestCase): self.assertTrue(True) def testNone(self): - logger.debug('--------------------------') + logger.debug('-'*26 + '\n') logger.info('Running simple program run test...') - # Test just running ./onionr with no arguments + blank = os.system('./onionr.py --version') if blank != 0: self.assertTrue(False) @@ -35,8 +35,9 @@ class OnionrTests(unittest.TestCase): self.assertTrue(True) def testPeer_a_DBCreation(self): - logger.debug('--------------------------') + logger.debug('-'*26 + '\n') logger.info('Running peer db creation test...') + if os.path.exists('data/peers.db'): os.remove('data/peers.db') import core @@ -48,8 +49,9 @@ class OnionrTests(unittest.TestCase): self.assertTrue(False) def testPeer_b_addPeerToDB(self): - logger.debug('--------------------------') + logger.debug('-'*26 + '\n') logger.info('Running peer db insertion test...') + import core myCore = core.Core() if not os.path.exists('data/peers.db'): @@ -62,8 +64,10 @@ class OnionrTests(unittest.TestCase): def testData_b_Encrypt(self): self.assertTrue(True) return - logger.debug('--------------------------') + + logger.debug('-'*26 + '\n') logger.info('Running data dir encrypt test...') + import core myCore = core.Core() myCore.dataDirEncrypt('password') @@ -75,8 +79,10 @@ class OnionrTests(unittest.TestCase): def testData_a_Decrypt(self): self.assertTrue(True) return - logger.debug('--------------------------') + + logger.debug('-'*26 + '\n') logger.info('Running data dir decrypt test...') + import core myCore = core.Core() myCore.dataDirDecrypt('password') @@ -85,9 +91,42 @@ class OnionrTests(unittest.TestCase): else: self.assertTrue(False) - def testPlugins(self): - logger.debug('--------------------------') - logger.info('Running simple plugin system test...') + def testConfig(self): + logger.debug('-'*26 + '\n') + logger.info('Running simple configuration test...') + + import config + + config.check() + config.reload() + configdata = str(config.get_config()) + + config.set('testval', 1337) + if not config.get('testval', None) is 1337: + self.assertTrue(False) + + config.set('testval') + if not config.get('testval', None) is None: + self.assertTrue(False) + + config.save() + config.reload() + + if not str(config.get_config()) == configdata: + self.assertTrue(False) + + self.assertTrue(True) + + def testBitcoinNode(self): + logger.debug('-'*26 + '\n') + logger.info('Running bitcoin node test...') + + sbitcoin = btc.OnionrBTC() + + def testPluginReload(self): + logger.debug('-'*26 + '\n') + logger.info('Running simple plugin reload test...') + import onionrplugins try: onionrplugins.reload('test') @@ -95,9 +134,36 @@ class OnionrTests(unittest.TestCase): except: self.assertTrue(False) + def testPluginStopStart(self): + logger.debug('-'*26 + '\n') + logger.info('Running simple plugin restart test...') + + import onionrplugins + try: + onionrplugins.start('test') + onionrplugins.stop('test') + self.assertTrue(True) + except: + self.assertTrue(False) + + def testPluginEvent(self): + logger.debug('-'*26 + '\n') + logger.info('Running plugin event test...') + + import onionrplugins as plugins, onionrevents as events + + plugins.start('test') + if not events.call(plugins.get_plugin('test'), 'test'): + self.assertTrue(False) + + events.event('test', data = {'tests': self}) + + self.assertTrue(True) + def testQueue(self): - logger.debug('--------------------------') + logger.debug('-'*26 + '\n') logger.info('Running daemon queue test...') + # test if the daemon queue can read/write data import core myCore = core.Core() @@ -117,8 +183,9 @@ class OnionrTests(unittest.TestCase): logger.info('Succesfully added and read command') def testHashValidation(self): - logger.debug('--------------------------') + logger.debug('-'*26 + '\n') logger.info('Running hash validation test...') + import core myCore = core.Core() if not myCore._utils.validateHash("$324dfgfdg") and myCore._utils.validateHash("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2") and not myCore._utils.validateHash("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd$"): @@ -127,8 +194,9 @@ class OnionrTests(unittest.TestCase): self.assertTrue(False) def testAddAdder(self): - logger.debug('--------------------------') + logger.debug('-'*26 + '\n') logger.info('Running address add+remove test') + import core myCore = core.Core() if not os.path.exists('data/address.db'): @@ -140,4 +208,5 @@ class OnionrTests(unittest.TestCase): self.assertTrue(False) else: self.assertTrue(False) + unittest.main() From 0fd9c3f6ab2b2e7930ac14c875e886edad16c800 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 3 Mar 2018 14:22:12 -0800 Subject: [PATCH 81/89] Disable bitcoin node unit test --- onionr/tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionr/tests.py b/onionr/tests.py index 50a7f6b9..7fd95ec0 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -118,6 +118,8 @@ class OnionrTests(unittest.TestCase): self.assertTrue(True) def testBitcoinNode(self): + # temporarily disabled- this takes a lot of time the CI doesn't have + self.assertTrue(True) logger.debug('-'*26 + '\n') logger.info('Running bitcoin node test...') From ca46ab4ffddc3bdc4c122650c580c766ddadbefc Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 3 Mar 2018 17:11:21 -0800 Subject: [PATCH 82/89] Display btc error if any --- onionr/communicator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index b9339860..3e7b6400 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -38,7 +38,8 @@ class OnionrCommunicate: logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2])) self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2])) logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight())) - except: + except Exception as e: + logger.error(str(e)) logger.fatal('Failed to start Bitcoin Node, exiting...') exit(1) From 9229fd99849e299a1db0d5caf2b5d25cb089f738 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 3 Mar 2018 20:28:17 -0600 Subject: [PATCH 83/89] disabled btc --- onionr/communicator.py | 16 ++++++++++------ onionr/onionrproofs.py | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index b9339860..413ee3ee 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -33,14 +33,17 @@ class OnionrCommunicate: self._core = core.Core() self._utils = onionrutils.OnionrUtils(self._core) self._crypto = onionrcrypto.OnionrCrypto(self._core) - + ''' + logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2])) try: - logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2])) self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2])) - logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight())) - except: - logger.fatal('Failed to start Bitcoin Node, exiting...') - exit(1) + except _gdbm.error: + pass + logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight())) + ''' + #except: + #logger.fatal('Failed to start Bitcoin Node, exiting...') + #exit(1) blockProcessTimer = 0 blockProcessAmount = 5 @@ -229,4 +232,5 @@ if shouldRun: try: OnionrCommunicate(debug, developmentMode) except KeyboardInterrupt: + sys.exit(1) pass diff --git a/onionr/onionrproofs.py b/onionr/onionrproofs.py index f996af9d..546e4449 100644 --- a/onionr/onionrproofs.py +++ b/onionr/onionrproofs.py @@ -30,12 +30,12 @@ class POW: hbCount = 0 blockCheck = 300000 # How often the hasher should check if the bitcoin block is updated (slows hashing but prevents less wasted work) blockCheckCount = 0 - block = self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight()) + block = ''#self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight()) while self.hashing: if blockCheckCount == blockCheck: if self.reporting: logger.debug('Refreshing Bitcoin block') - block = self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight()) + block = ''#self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight()) blockCheckCount = 0 blockCheckCount += 1 hbCount += 1 From cb3015652a0a896bd0c914cd66c6c45c4b8fa7b5 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 16 Mar 2018 10:35:37 -0500 Subject: [PATCH 84/89] peer/key exchange work (mostly done) --- onionr/communicator.py | 34 +++++++++++++++++++++++++++++++--- onionr/core.py | 4 ++-- onionr/onionrutils.py | 18 ++++++++++++++++++ onionr/tests.py | 6 +++--- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 413ee3ee..45bb8bae 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -19,7 +19,7 @@ and code to operate as a daemon, getting commands from the command queue databas You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse +import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse, random import core, onionrutils, onionrcrypto, onionrproofs, btc, config, onionrplugins as plugins class OnionrCommunicate: @@ -49,7 +49,7 @@ class OnionrCommunicate: blockProcessAmount = 5 heartBeatTimer = 0 heartBeatRate = 5 - pexTimer = 900 # How often we should check for new peers + pexTimer = 5 # How often we should check for new peers pexCount = 0 logger.debug('Communicator debugging enabled.') torID = open('data/hs/hostname').read() @@ -89,14 +89,42 @@ class OnionrCommunicate: ''' Get new peers ''' + peersCheck = 5 # Amount of peers to ask for new peers + keys + peersChecked = 0 + peerList = list(self._core.listAdders()) # random ordered list of peers + logger.warn(len(peerList)) + newKeys = [] + newAdders = [] + if len(peerList) > peersCheck: + peersCheck = len(peerList) + + while peersCheck > peersChecked: + i = random.randint(0, len(peerList)) + logger.info('Using ' + peerList[i] + ' to find new peers') + try: + newAdders = self.performGet('pex', peerList[i]) + self._utils.mergeAdders(newAdders) + except requests.exceptions.ConnectionError: + logger.info(peerList[i] + ' connection failed') + continue + else: + try: + logger.info('Using ' + peerList[i] + ' to find new keys') + newKeys = self.performGet('kex', peerList[i]) + # TODO: Require keys to come with POW token (very large amount of POW) + self._utils.mergeKeys(newKeys) + except requests.exceptions.ConnectionError: + logger.info(peerList[i] + ' connection failed') + continue + else: + peersChecked += 1 return def lookupBlocks(self): ''' Lookup blocks and merge new ones ''' - peerList = self._core.listAdders() blocks = '' for i in peerList: diff --git a/onionr/core.py b/onionr/core.py index 54ac195e..ef91549c 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -57,7 +57,7 @@ class Core: def addPeer(self, peerID, name=''): ''' - Add a peer by their ID, with an optional name, to the peer database + Adds a public key to the key database (misleading function name) DOES NO SAFETY CHECKS if the ID is valid, but prepares the insertion ''' @@ -346,7 +346,7 @@ class Core: def listPeers(self, randomOrder=True): ''' - Return a list of peers + Return a list of public keys (misleading function name) randomOrder determines if the list should be in a random order ''' diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 50c973fb..051ad193 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -41,6 +41,24 @@ class OnionrUtils: '''High level function to encrypt a message to a peer and insert it as a block''' return + def mergeKeys(self, newKeyList): + '''Merge ed25519 key list to our database''' + retVal = False + for key in newKeyList: + if not key in self._core.listPeers(randomOrder=False): + if self._core.addPeer(key): + retVal = True + return retVal + + def mergeAdders(self, newAdderList): + '''Merge peer adders list to our database''' + retVal = False + for adder in newAdderList: + if not adder in self._core.listAdders(randomOrder=False): + if self._core.addAddress(adder): + retVal = True + return retVal + def localCommand(self, command): ''' Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers. diff --git a/onionr/tests.py b/onionr/tests.py index 7fd95ec0..18e6187d 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -120,10 +120,10 @@ class OnionrTests(unittest.TestCase): def testBitcoinNode(self): # temporarily disabled- this takes a lot of time the CI doesn't have self.assertTrue(True) - logger.debug('-'*26 + '\n') - logger.info('Running bitcoin node test...') + #logger.debug('-'*26 + '\n') + #logger.info('Running bitcoin node test...') - sbitcoin = btc.OnionrBTC() + #sbitcoin = btc.OnionrBTC() def testPluginReload(self): logger.debug('-'*26 + '\n') From 24540abe6b52051633864e36fe6460bae2081f0f Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 16 Mar 2018 15:38:33 -0500 Subject: [PATCH 85/89] finished pub encrypt function --- onionr/api.py | 5 +++++ onionr/communicator.py | 9 +++++++-- onionr/onionrcrypto.py | 18 ++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 33b759a4..7ff3a002 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -154,6 +154,11 @@ class API: if len(response) == 0: response = 'none' resp = Response(response) + elif action == 'kex': + response = ','.join(self._core.listPeers()) + if len(response) == 0: + response = 'none' + resp = Response(response) else: resp = Response("") diff --git a/onionr/communicator.py b/onionr/communicator.py index 45bb8bae..9c2b88b7 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -70,6 +70,7 @@ class OnionrCommunicate: pexCount += 1 if pexTimer == pexCount: self.getNewPeers() + pexCount = 0 if heartBeatRate == heartBeatTimer: logger.debug('Communicator heartbeat') heartBeatTimer = 0 @@ -92,15 +93,19 @@ class OnionrCommunicate: peersCheck = 5 # Amount of peers to ask for new peers + keys peersChecked = 0 peerList = list(self._core.listAdders()) # random ordered list of peers - logger.warn(len(peerList)) newKeys = [] newAdders = [] + if len(peerList) > 0: + maxN = len(peerList) - 1 + else: + peersCheck = 0 + maxN = 0 if len(peerList) > peersCheck: peersCheck = len(peerList) while peersCheck > peersChecked: - i = random.randint(0, len(peerList)) + i = random.randint(0, maxN) logger.info('Using ' + peerList[i] + ' to find new peers') try: newAdders = self.performGet('pex', peerList[i]) diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 22a542b8..4000e272 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -60,11 +60,21 @@ class OnionrCrypto: retData = key.sign(data.encode()) return retData - def pubKeyEncrypt(self, data, peer): - '''Encrypt to a peers public key (Curve25519, taken from Ed25519 pubkey)''' - return + def pubKeyEncrypt(self, data, pubkey, anonymous=False): + '''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)''' + retVal = '' + if self.privKey != None and not anonymous: + ownKey = nacl.signing.SigningKey(seed=self.privKey, encoder=nacl.encoding.Base32Encoder()) + key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key() + ourBox = nacl.public.Box(ownKey, key) + retVal = ourBox.encrypt(data.encode(), encoder=nacl.encoding.RawEncoder) + elif anonymous: + key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key() + anonBox = nacl.public.SealedBox(key) + retVal = anonBox.encrypt(data.encode(), encoder=nacl.encoding.RawEncoder) + return retVal - def pubKeyEncrypt(self, data, peer): + def pubKeyDecrypt(self, data, peer): '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)''' return From 0b192ffb9be6d3eab00aba82e4e36c0025e9533d Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 1 Apr 2018 00:46:34 -0500 Subject: [PATCH 86/89] do not request offline peers too often and updated logo --- docs/onionr-logo.png | Bin 9633 -> 5147 bytes onionr/communicator.py | 16 ++++++++++------ onionr/onionrutils.py | 31 +++++++++++++++++++++++-------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/docs/onionr-logo.png b/docs/onionr-logo.png index f52533f091c27640a81b34b5b156e64d7cf38f3e..51060c6a81fcdc2b54203140de967c0336453be9 100644 GIT binary patch literal 5147 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 PJ~AVHGrd|JC)ED{v2xLs literal 9633 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 XqTw6@OrUvD00000NkvXXu0mjfOThin diff --git a/onionr/communicator.py b/onionr/communicator.py index 9c2b88b7..d42b70a4 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -108,7 +108,7 @@ class OnionrCommunicate: i = random.randint(0, maxN) logger.info('Using ' + peerList[i] + ' to find new peers') try: - newAdders = self.performGet('pex', peerList[i]) + newAdders = self.performGet('pex', peerList[i], skipHighFailureAddress=True) self._utils.mergeAdders(newAdders) except requests.exceptions.ConnectionError: logger.info(peerList[i] + ' connection failed') @@ -116,7 +116,7 @@ class OnionrCommunicate: else: try: logger.info('Using ' + peerList[i] + ' to find new keys') - newKeys = self.performGet('kex', peerList[i]) + newKeys = self.performGet('kex', peerList[i], skipHighFailureAddress=True) # TODO: Require keys to come with POW token (very large amount of POW) self._utils.mergeKeys(newKeys) except requests.exceptions.ConnectionError: @@ -217,7 +217,7 @@ class OnionrCommunicate: return urllib.parse.quote_plus(data) - def performGet(self, action, peer, data=None, peerType='tor'): + def performGet(self, action, peer, data=None, skipHighFailureAddress=False, peerType='tor'): ''' Performs a request to a peer through Tor or i2p (currently only Tor) ''' @@ -229,7 +229,6 @@ class OnionrCommunicate: if not peer in self.peerData: self.peerData[peer] = {'connectCount': 0, 'failCount': 0, 'lastConnectTime': math.floor(time.time())} socksPort = sys.argv[2] - logger.debug('Contacting ' + peer + ' on port ' + socksPort) '''We use socks5h to use tor as DNS''' proxies = {'http': 'socks5://127.0.0.1:' + str(socksPort), 'https': 'socks5://127.0.0.1:' + str(socksPort)} headers = {'user-agent': 'PyOnionr'} @@ -237,8 +236,13 @@ class OnionrCommunicate: if data != None: url = url + '&data=' + self.urlencode(data) try: - r = requests.get(url, headers=headers, proxies=proxies, timeout=(15, 30)) - retData = r.text + if skipHighFailureAddress and self.peerData[peer]['failCount'] > 10: + retData = False + logger.debug('Skipping ' + peer + ' because of high failure rate') + else: + logger.debug('Contacting ' + peer + ' on port ' + socksPort) + r = requests.get(url, headers=headers, proxies=proxies, timeout=(15, 30)) + retData = r.text except requests.exceptions.RequestException as e: logger.warn(action + " failed with peer " + peer + ": " + str(e)) retData = False diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 051ad193..c82a39e2 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -40,23 +40,38 @@ class OnionrUtils: def sendPM(self, user, message): '''High level function to encrypt a message to a peer and insert it as a block''' return + + def incrementAddressSuccess(self, address): + '''Increase the recorded sucesses for an address''' + increment = self._core.getAddressInfo(address, 'success') + 1 + self._core.setAddressInfo(address, 'success', increment) + return + + def decrementAddressSuccess(self, address): + '''Decrease the recorded sucesses for an address''' + increment = self._core.getAddressInfo(address, 'success') - 1 + self._core.setAddressInfo(address, 'success', increment) + return def mergeKeys(self, newKeyList): '''Merge ed25519 key list to our database''' retVal = False - for key in newKeyList: - if not key in self._core.listPeers(randomOrder=False): - if self._core.addPeer(key): - retVal = True + if newKeyList != False: + for key in newKeyList: + if not key in self._core.listPeers(randomOrder=False): + if self._core.addPeer(key): + retVal = True return retVal + def mergeAdders(self, newAdderList): '''Merge peer adders list to our database''' retVal = False - for adder in newAdderList: - if not adder in self._core.listAdders(randomOrder=False): - if self._core.addAddress(adder): - retVal = True + if newAdderList != False: + for adder in newAdderList: + if not adder in self._core.listAdders(randomOrder=False): + if self._core.addAddress(adder): + retVal = True return retVal def localCommand(self, command): From e96723cab42026ac9dfbab4bae191b06de80097d Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 1 Apr 2018 01:02:20 -0500 Subject: [PATCH 87/89] removed white background in logo --- docs/onionr-logo.png | Bin 5147 -> 5116 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/onionr-logo.png b/docs/onionr-logo.png index 51060c6a81fcdc2b54203140de967c0336453be9..f5a117a82cd7f7228efaa4d6ea15503160a0a1de 100644 GIT binary patch delta 5110 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 H9!35SzhcWO literal 5147 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 PJ~AVHGrd|JC)ED{v2xLs From 2608a72b32f8f2d18e0e95987f2ea51a9691f21b Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 1 Apr 2018 01:15:08 -0500 Subject: [PATCH 88/89] updated logo again --- docs/onionr-logo.png | Bin 5116 -> 4669 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/onionr-logo.png b/docs/onionr-logo.png index f5a117a82cd7f7228efaa4d6ea15503160a0a1de..db886d6638927fb68ecdfe92247658cea638151b 100644 GIT binary patch literal 4669 zcmai2_d6So^QSdJQK?;OuaLfpz142iu2hV+Y6YRxsMxi)mZJ9Ff~0CxjUx7_(ONO9 zs`g5e#`yRPzTemL+}&?CUiaMH^IV*XksdwmU0O0SGJ5DkZL@2Oz7`z-)wP}ppijLv z6yBOpbHFu%01mNaWIzH`Tf-ccyPJnZJIwjcgd#h~6k(@Jt<7H$g5ozgI3qP4#Dd+pCve&-{QR1No&$oSDR;nWqN@=)i6&?7i~+wO z$q{*jsGlc6NX=mdxQhpV^=bvNUym4Oo&+A*s$um;uliKzZB~tgm{|g$O zcHMxUhc0*r(Z$8cq9qu{L+9$pYITI-HG^X8a;q(+yBWbA@;yIb} zb0icqC0aWCa1&+2DxAuDs_n#>jSE75h_*V3=Zfi=Gw@T4w;jF+lG1P|>`D5KE{@Dt zlalYpl*8>>b!x|8<4_{>aw#SR9{QUt)RB3Y#`Zq-W_|ndM1}fUQ?r^1h<2WxcvL1H zv`fsfv2Jwhya0Brk*>g7Dr}tu!EdTWmXkUgEj+{f^wKOfgp9bNcy+v~2@DN1x6P9F zeG-6_gmvu@5|2GXn3n%`?5}!+?C{i<8R}9^PxgcysD>V`7Vl#C9a!}3Gw&7=SZjvX zEApNEuHG#UTUaj43jLrj=CJ?Dp(UA-nI0Jqs7!q<>{wwr&w`!MdyE*^_@ z0BTc(DGLC`5oLo}b#I*we4s8exAWye0Rm_7zFgfd|=)a6?|KKX#cawS#QB5+rOgDtJ`l4Ke3?<_I-Bv3xayo43Hpr z^XJ}f#Z2Z*TbkkQcT%4%C;9ryl*u!c7-P{den>QT!klrU)_$JLg?)WBq zUxkgnG6eDPZIDUmIh)!=^k1j#Sj9c6h2-X-<=X>uer-vk)MF{Zg%jEW@k%{1sB*_b zQQD6UXeq9;5e+dZ`M zR#}Sz;{4%VyI)r??o-~vINz;%)`}~u$p3xR`1Hxs?wGIBy1m3a!==|wIf3%RGSxdx z_bk}NJ6>aW*GJ~WFl+Q`i21sTZ*l)2lnPlMUc0QK&_`m%3R%D3m0Y3x6ZqtqQk@jn z>D>=M${Ec2`CZ=YoECZaPSEeeg2{@`Go9ZSb(1Q7>E`kV;T2-E2A&JeimR7%X*s)$ z4cv9nl;L&Tp0aKne7q=1vtndmDg$&QEk?zi%qBWcHkKs-hT}jtDhQw+*!}v`*J$6+ z!}&C~*?yOTlykL;AXI%*?^<2oaPd2xi;tuBYdV;sk1w|UaSHYlOz?c^t(nJ^?&kyX zGN~T`P`eu!+)0%Z(nQb=t4$zuOqJ5OE461ov6krHV)u|-vO^2OBdC*K`w~z44xJfJX=6qH9R}-W>aV( zaQz((b{4=l+~*EH)@s}A@>m_2Bh6;JPgoA8ug4onBPxV8=i4AbX!ID|PP3*?+^x^sziRbc{l-yHnOyBa7 z+h=7Z{5@MrsOC+LebFRp%FMId6M10tThwfHzMZfHFicv&c`0X6u}lD9S!al6X!Q! z*Kz?zHK6uI+bRg$hH(KQ1EIt++R{RI3&nFi!hs+;M(J9_J5W(!~bt)JB)bMH4iwMdK+wG zA%E%#YLL$JTvn}^Q)rCHF@pQ1h(;HSRaVnJhneR{JGAzubJxkLIs(kqc>7YdA97el z!JcG*fZpA|5_Nzu8HCx8GnFhAn+gnIklW805nBXSS+bJRnP30eV(6o1l)Cav90}R{ z*BweS4E`w}l2rUQZHlI0svq)*E3Y{EEH8`U!u> zXczWE>ibW55Czkkn7|MDUDLAC`yhF_%)Q*=%+btTH7x>zeAf8r%`s6OwkkKhp7=~@ zytVY!dhYced#Th~F47D%>mwJ3wW8U{BkYbaTaG%6k%EQLK~rqsiZ)<6wRfBl2L-^x z>RgiX2F>d)kLo1!7LdR@BlS;v{8H%GB#X#42FC0)!y<2Fx!G2OwjOmm47r(^1?b@$ zWrl@xo_yMdpQJeAHF#cu*6f5G{=82;#$>0%##K-=lN_{2?1Qa9yDw$E1Egwlg< zNg;N#Wt7m0sr$V60bkd6fDzVpEAX^)?qzJ4B&4`3msEqdS*8Y=Ez+EKzTSfjXKl}T zVBr6O-joo-dyC8WqNOwoG#c(usMvli9xw^7w1pkO+gK)RF%KH9liIv@kCBohf$7oS zK0{Z&&oyf>(~nXaygl)cw*(T1;hvtc;_<5`XxS=2mbuTCOdxuNjy*!KY?Eo4zLA=r3Q5%$tEI-1({_{VL0?&g=GgV^F*OXG zl~8JGf}s{rI>7T`2Uc%Mwa~f={G0ybW7!a<)>F($*c1e8!}9h>iT4%0;e`XykV*hF z(iz;&o{H7tMBfAkM8%FHij1?=K+~jamfS68px@;at~=f96OW%Q+$ZrReR2imFyw3y z*h}d=Jt+cXnB8OIJzKV1Ea)q0Uld2jv5O&I4Fbs^9EnBpnxA^UJky}^$WJ6kG9m|- zQu1_?y5h-!#dqzZE3>$m$a`Twu4`dA)^FQ{0xttlvy2CvkIgK3VR7~P=+oSjFfY@F z(Dl2cmq7o|-JenxL|B7=&b6)*b+kC8r>%siv5xnccrqhzAa^n6t`xZfkq=mCh51aV zj4hcBL|CRjTc_KEOK0#>z2mDXjK5pyWZmyxuIy!oI|;zBhF|=X(}bV`fYneP5zUgJ z46M&^HnCbrcmR^}<)JC_Y!dxT+m@xjK9-r$;dzXiEcwNBX|TB4FHqDYmNo!pM-3RW zd6BIZvzuYL0*?t0#~%LIz~(D^1T9>Ef@ijMzvG(0gd;2Mz*LG(R=(>g+*Y+pBVZ8? z;3~(ibYl3F<6wrw?1zjoK)UZ)ZkE-s10n`7yW!Pt=W5#^)>03MWdEGSR7>X=8H|6Y zja`ELB{$`ueWuCFUnyn;W=-%*77Q2(`Kt}fl{G;3*fEBgFoR{@Q-;e@*dHv>E5Z?;OJ;_&m$<;?p68diLC%F4GqR34 zbW{xGz;qF4;$}j*TIP^T2+QlFs2%@{T`IhiB1E-Uu+tV}{ zR1$|?u!2j5L!u}B(Xz3WhpcQ|h;Qnsg>nue)%6fjN~#8`my3ehdSV_hF+qfyYT~O- zwRsav;93-0XotufT4nk)?2!)i;=qh*ZH{;gxx}(NT70a?=mod7Mg+Psqgz321GHdr zbvL&JtioyiSdB0k>O13K^?u~~z^!&3gkY_$ak(HZGNg-Lw{rhnt)cMP#JWsrKDulI z$FF0arb%ZMBiR+l7dIY1StxkVl}1b&S2<>d%e1~l{iaa?arip+=?{+hZV2d}a)ndB zdqxTyMlPIkw#_qa=jOCkkmaNR7y5}pn6zV;(}9A3`Lr2a&!N(9^v!#|Lk}Iv?9GPB zMWv+@cKa%HiBxiT08XhG67evJkK>O}1Y6?Z$ZZCf<3BbtNsV`QtS=|29o7stiU1Ka zHTv^(dNt9t#VhA-eObk&lF{8t`Wpj)+K_hIN8`B@)`5(?RA?c^h^1C1WeTGYLY#bz zZ34=CmS05`{lLGeRs7cdPrl?Ijbzz34Gi3}%z@6#_|L?s^BV_kExQID^5^6{Mgv?r zcyDs@6~dtS*Mw9PY4fMGUb;9&hf2Ny?KTFF9^GgQM6}>np3%!*_{Zi-mih14Emww< zmtMy!fXA$DYlgQ?M}_;{ldmah>h$kCKKllee>u)`UtwXfzb`)y5Cs|8&gP`{&Tu!b zA1}j6>14A5S;SsZ*uz~)2RUS&X5-(l^7x=OG7a>bs^kBYR|#J8zg*e!&F}oImnZ$U zV-0wDPQCO^?`p6`Z>JSIee>wYw;HPv8%-zB3K_S*>ru z_QoHiSYK?ZbiorLGWrT+M-_;EF_%+IN_J%BI^RWNN!tA=U-`ie0sH<|@se#|?%GP{G{Mwgf z!{`eDLrygJff{2_7w$IGr!rjzyM7Jp@D%O<6>6@wOSjrK~fJY-Z|D?(PkKU z-}!I5bz!k&NxASD82VIx$81&xJ)iua$Wg84uJCIvxks-*=wgd#RL4lWPSZC0f5Vb4^Z)<= delta 5111 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 JR%t(u_#c5A%Ub{d From 49b7efacff20f663d98c7019a42ef54555d47c2c Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 1 Apr 2018 01:40:31 -0500 Subject: [PATCH 89/89] updated readme --- docs/onionr-logo.png | Bin 4669 -> 5344 bytes readme.md | 12 ++++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/onionr-logo.png b/docs/onionr-logo.png index db886d6638927fb68ecdfe92247658cea638151b..dade75ca8705da5b57bbbd11bae60a803343ed07 100644 GIT binary patch literal 5344 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 diff --git a/readme.md b/readme.md index 8746ddb2..c6c20a91 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,8 @@ ![Onionr logo](./docs/onionr-logo.png) -# Onionr - [![Build Status](https://travis-ci.org/beardog108/onionr.svg?branch=master)](https://travis-ci.org/beardog108/onionr) +[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/) + Anonymous P2P platform, using Tor & I2P. @@ -25,6 +25,14 @@ This software is in heavy development. If for some reason you want to get involv **Onionr API and functionality is subject to non-backwards compatible change during development** +# Donate + +Bitcoin/Bitcoin Cash: 1onion55FXzm6h8KQw3zFw2igpHcV7LPq + ## Disclaimer The Tor Project, I2P developers, and anyone else do not own, create, or endorse this project, and are not otherwise involved. + +The badges (besides travis-ci build) are by Maik Ellerbrock is licensed under a Creative Commons Attribution 4.0 International License. + +The onion in the Onionr logo is adapted from [this](https://commons.wikimedia.org/wiki/File:Red_Onion_on_White.JPG) image by Colin on Wikimedia under a Creative Commons Attribution-Share Alike 3.0 Unported license