From 7a0cfe34f39d5cfe3dfdc68f9035363515a9a0d3 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 26 Aug 2018 22:44:32 -0500 Subject: [PATCH 001/101] work on adding/removing friends, user info --- onionr/communicator2.py | 2 +- onionr/core.py | 16 ++++++----- onionr/dbcreator.py | 1 - onionr/onionr.py | 44 +++++++++++++++++++++++++++--- onionr/onionrblacklist.py | 2 +- onionr/onionrblockapi.py | 2 +- onionr/onionrcrypto.py | 20 -------------- onionr/onionrdaemontools.py | 2 +- onionr/onionrexceptions.py | 5 +++- onionr/onionrpeers.py | 2 +- onionr/onionrusers.py | 51 +++++++++++++++++++++++++++++++++++ onionr/onionrutils.py | 15 +++++++++++ onionr/static-data/index.html | 2 +- 13 files changed, 127 insertions(+), 37 deletions(-) create mode 100644 onionr/onionrusers.py diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 38ba2692..9c44ed21 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 ''' - Onionr - P2P Microblogging Platform & Social network. + Onionr - P2P Anonymous Storage Network This file contains both the OnionrCommunicate class for communcating with peers and code to operate as a daemon, getting commands from the command queue database (see core.Core.daemonQueue) diff --git a/onionr/core.py b/onionr/core.py index 9d97e3f1..f7d36072 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -1,5 +1,5 @@ ''' - Onionr - P2P Microblogging Platform & Social network + Onionr - P2P Anonymous Storage Network Core Onionr library, useful for external programs. Handles peer & data processing ''' @@ -102,7 +102,7 @@ class Core: conn = sqlite3.connect(self.peerDB) hashID = self._crypto.pubKeyHashID(peerID) c = conn.cursor() - t = (peerID, name, 'unknown', hashID, powID) + t = (peerID, name, 'unknown', hashID, powID, 0) for i in c.execute("SELECT * FROM PEERS where id = '" + peerID + "';"): try: @@ -113,7 +113,7 @@ class Core: pass except IndexError: pass - c.execute('INSERT INTO peers (id, name, dateSeen, pow, hashID) VALUES(?, ?, ?, ?, ?);', t) + c.execute('INSERT INTO peers (id, name, dateSeen, pow, hashID, trust) VALUES(?, ?, ?, ?, ?, ?);', t) conn.commit() conn.close() @@ -403,19 +403,23 @@ class Core: conn.close() return addressList - def listPeers(self, randomOrder=True, getPow=False): + def listPeers(self, randomOrder=True, getPow=False, trust=0): ''' Return a list of public keys (misleading function name) randomOrder determines if the list should be in a random order + trust sets the minimum trust to list ''' conn = sqlite3.connect(self.peerDB) c = conn.cursor() payload = "" + if trust not in (0, 1, 2): + logger.error('Tried to select invalid trust.') + return if randomOrder: - payload = 'SELECT * FROM peers ORDER BY RANDOM();' + payload = 'SELECT * FROM peers where trust >= %s ORDER BY RANDOM();' % (trust,) else: - payload = 'SELECT * FROM peers;' + payload = 'SELECT * FROM peers where trust >= %s;' % (trust,) peerList = [] for i in c.execute(payload): try: diff --git a/onionr/dbcreator.py b/onionr/dbcreator.py index 19a9a7bd..5f3d2c79 100644 --- a/onionr/dbcreator.py +++ b/onionr/dbcreator.py @@ -61,7 +61,6 @@ class DBCreator: ID text not null, name text, adders text, - blockDBHash text, forwardKey text, dateSeen not null, bytesStored int, diff --git a/onionr/onionr.py b/onionr/onionr.py index 1736c3f9..5408b2c4 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 ''' - Onionr - P2P Microblogging Platform & Social network. + Onionr - P2P Anonymous Storage Network Onionr is the name for both the protocol and the original/reference software. @@ -32,7 +32,7 @@ import onionrutils from onionrutils import OnionrUtils from netcontroller import NetController from onionrblockapi import Block -import onionrproofs +import onionrproofs, onionrexceptions, onionrusers try: from urllib3.contrib.socks import SOCKSProxyManager @@ -210,7 +210,9 @@ class Onionr: 'getpass': self.printWebPassword, 'get-pass': self.printWebPassword, 'getpasswd': self.printWebPassword, - 'get-passwd': self.printWebPassword + 'get-passwd': self.printWebPassword, + + 'friend': self.friendCmd } self.cmdhelp = { @@ -234,6 +236,7 @@ class Onionr: 'pex': 'exchange addresses with peers (done automatically)', 'blacklist-block': 'deletes a block by hash and permanently removes it from your node', 'introduce': 'Introduce your node to the public Onionr network', + 'friend': '[add|remove] [public key/id]' } # initialize plugins @@ -260,6 +263,41 @@ class Onionr: def getCommands(self): return self.cmds + + def friendCmd(self): + '''List, add, or remove friend(s)''' + friend = '' + try: + action = sys.argv[2] + except IndexError: + logger.info('Syntax: friend add/remove/list [address]') + else: + action = action.lower() + if action == 'list': + for friend in self.onionrCore.listPeers(randomOrder=False, trust=1): + logger.info(friend) + elif action in ('add', 'remove'): + try: + friend = sys.argv[3] + if not self.onionrUtils.validatePubKey(friend): + raise onionrexceptions.InvalidPubkey('Public key is invalid') + if friend not in self.onionrCore.listPeers(): + raise onionrexceptions.KeyNotKnown + friend = onionrusers.OnionrUser(self.onionrCore, friend) + except IndexError: + logger.error('Friend ID is required.') + except onionrexceptions.KeyNotKnown: + logger.error('That peer is not in our database') + else: + if action == 'add': + friend.setTrust(1) + logger.info('Added %s as friend.' % (friend.publicKey,)) + else: + friend.setTrust(0) + logger.info('Removed %s as friend.' % (friend.publicKey,)) + else: + logger.info('Syntax: friend add/remove/list [address]') + def banBlock(self): try: diff --git a/onionr/onionrblacklist.py b/onionr/onionrblacklist.py index 86823283..863ddc37 100644 --- a/onionr/onionrblacklist.py +++ b/onionr/onionrblacklist.py @@ -1,5 +1,5 @@ ''' - Onionr - P2P Microblogging Platform & Social network. + Onionr - P2P Anonymous Storage Network This file handles maintenence of a blacklist database, for blocks and peers ''' diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index e255c0ae..97b34730 100644 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -1,5 +1,5 @@ ''' - Onionr - P2P Microblogging Platform & Social network. + Onionr - P2P Anonymous Storage Network This class contains the OnionrBlocks class which is a class for working with Onionr blocks ''' diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 00c5a604..9e055716 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -155,26 +155,6 @@ class OnionrCrypto: decrypted = anonBox.decrypt(data, encoder=encoding) return decrypted - def symmetricPeerEncrypt(self, data, peer): - '''Salsa20 encrypt data to peer (with mac) - this function does not accept a key, it is a wrapper for encryption with a peer - ''' - key = self._core.getPeerInfo(4) - if type(key) != bytes: - key = self._core.getPeerInfo(2) - encrypted = self.symmetricEncrypt(data, key, encodedKey=True) - return encrypted - - def symmetricPeerDecrypt(self, data, peer): - '''Salsa20 decrypt data from peer (with mac) - this function does not accept a key, it is a wrapper for encryption with a peer - ''' - key = self._core.getPeerInfo(4) - if type(key) != bytes: - key = self._core.getPeerInfo(2) - decrypted = self.symmetricDecrypt(data, key, encodedKey=True) - return decrypted - def symmetricEncrypt(self, data, key, encodedKey=False, returnEncoded=True): '''Encrypt data to a 32-byte key (Salsa20-Poly1305 MAC)''' if encodedKey: diff --git a/onionr/onionrdaemontools.py b/onionr/onionrdaemontools.py index 8410cb80..c7b738e1 100644 --- a/onionr/onionrdaemontools.py +++ b/onionr/onionrdaemontools.py @@ -1,5 +1,5 @@ ''' - Onionr - P2P Microblogging Platform & Social network. + Onionr - P2P Anonymous Storage Network Contains the CommunicatorUtils class which contains useful functions for the communicator daemon ''' diff --git a/onionr/onionrexceptions.py b/onionr/onionrexceptions.py index b26a97d7..dbdab69a 100644 --- a/onionr/onionrexceptions.py +++ b/onionr/onionrexceptions.py @@ -1,5 +1,5 @@ ''' - Onionr - P2P Microblogging Platform & Social network. + Onionr - P2P Anonymous Storage Network This file contains exceptions for onionr ''' @@ -34,6 +34,9 @@ class OnlinePeerNeeded(Exception): class InvalidPubkey(Exception): pass +class KeyNotKnown(Exception): + pass + # block exceptions class InvalidMetadata(Exception): pass diff --git a/onionr/onionrpeers.py b/onionr/onionrpeers.py index 710f698d..6a34adf7 100644 --- a/onionr/onionrpeers.py +++ b/onionr/onionrpeers.py @@ -1,5 +1,5 @@ ''' - Onionr - P2P Microblogging Platform & Social network. + Onionr - P2P Anonymous Storage Network This file contains both the PeerProfiles class for network profiling of Onionr nodes ''' diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py new file mode 100644 index 00000000..9138e1ce --- /dev/null +++ b/onionr/onionrusers.py @@ -0,0 +1,51 @@ +''' + Onionr - P2P Anonymous Storage Network + + Contains abstractions for interacting with users of Onionr +''' +''' + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' +class OnionrUser: + def __init__(self, coreInst, publicKey): + self.trust = 0 + self._core = coreInst + self.publicKey = publicKey + + self.trust = self._core.getPeerInfo(self.publicKey, 'trust') + return + + def setTrust(self, newTrust): + '''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate''' + self._core.setPeerInfo(self.publicKey, 'trust', newTrust) + + def isFriend(self): + if self._core.getPeerInfo(self.publicKey, 6) == 1: + return True + return False + + def encrypt(self, data): + encrypted = coreInst._crypto.pubKeyEncrypt(data, self.publicKey, encodedData=True) + return encrypted + + def decrypt(self, data): + decrypted = coreInst._crypto.pubKeyDecrypt(data, self.publicKey, encodedData=True) + return decrypted + + def forwardEncrypt(self, data): + return + + def forwardDecrypt(self, encrypted): + return + \ No newline at end of file diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 6d22992c..8e8f5ab8 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -264,9 +264,24 @@ class OnionrUtils: if myBlock.isEncrypted: myBlock.decrypt() blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks + signer = myBlock.getSigner() + try: if len(blockType) <= 10: self._core.updateBlockInfo(blockHash, 'dataType', blockType) + + if blockType == 'userInfo': + if myBlock.verifySig(): + peerName = myBlock.getMetadata('name') + try: + if len(peerName) > 20: + raise onionrexceptions.InvalidMetdata('Peer name specified is too large') + except TypeError: + pass + except onionrexceptions.InvalidMetadata: + pass + else: + self._core.setPeerInfo(signer, 'name', peerName) except TypeError: pass diff --git a/onionr/static-data/index.html b/onionr/static-data/index.html index 93e48beb..a48dad37 100644 --- a/onionr/static-data/index.html +++ b/onionr/static-data/index.html @@ -1,6 +1,6 @@

This is an Onionr Node

-

The content on this server is not necessarily created by the server owner, and was not necessarily stored with the owner's knowledge.

+

The content on this server is not necessarily created by the server owner, and was not necessarily stored specifically with the owner's knowledge of its contents.

Onionr is a decentralized, distributed data storage system, that anyone can insert data into.

From c907558dd147d3a17ffa6aec3efbf04efd87a39b Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 27 Aug 2018 23:45:31 -0500 Subject: [PATCH 002/101] more work on user info --- onionr/onionr.py | 5 +- onionr/onionrusers.py | 12 ++++- onionr/onionrutils.py | 2 +- .../default-plugins/cliui/info.json | 5 ++ .../static-data/default-plugins/cliui/main.py | 51 +++++++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 onionr/static-data/default-plugins/cliui/info.json create mode 100644 onionr/static-data/default-plugins/cliui/main.py diff --git a/onionr/onionr.py b/onionr/onionr.py index 5408b2c4..333340ad 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -275,7 +275,10 @@ class Onionr: action = action.lower() if action == 'list': for friend in self.onionrCore.listPeers(randomOrder=False, trust=1): - logger.info(friend) + if friend == self.onionrCore._crypto.pubKey: + continue + friendProfile = onionrusers.OnionrUser(self.onionrCore, friend) + logger.info(friend + ' - ' + friendProfile.getName()) elif action in ('add', 'remove'): try: friend = sys.argv[3] diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py index 9138e1ce..d258a8da 100644 --- a/onionr/onionrusers.py +++ b/onionr/onionrusers.py @@ -31,9 +31,19 @@ class OnionrUser: self._core.setPeerInfo(self.publicKey, 'trust', newTrust) def isFriend(self): - if self._core.getPeerInfo(self.publicKey, 6) == 1: + if self._core.getPeerInfo(self.publicKey, 'trust') == 1: return True return False + + def getName(self): + retData = 'anonymous' + name = self._core.getPeerInfo(self.publicKey, 'name') + try: + if len(name) > 0: + retData = name + except ValueError: + pass + return retData def encrypt(self, data): encrypted = coreInst._crypto.pubKeyEncrypt(data, self.publicKey, encodedData=True) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 8e8f5ab8..81ebf44a 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -264,7 +264,7 @@ class OnionrUtils: if myBlock.isEncrypted: myBlock.decrypt() blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks - signer = myBlock.getSigner() + signer = myBlock.signer try: if len(blockType) <= 10: diff --git a/onionr/static-data/default-plugins/cliui/info.json b/onionr/static-data/default-plugins/cliui/info.json new file mode 100644 index 00000000..e06855f6 --- /dev/null +++ b/onionr/static-data/default-plugins/cliui/info.json @@ -0,0 +1,5 @@ +{ + "name" : "cliui", + "version" : "1.0", + "author" : "onionr" +} diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py new file mode 100644 index 00000000..40bcfd6f --- /dev/null +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -0,0 +1,51 @@ +''' + Onionr - P2P Anonymous Storage Network + + This is an interactive menu-driven CLI interface for Onionr +''' +''' + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' + +# Imports some useful libraries +import logger, config, threading, time +from onionrblockapi import Block + +plugin_name = 'cliui' +PLUGIN_VERSION = '0.0.1' + +class OnionrCLIUI: + def __init__(self, apiInst): + self.api = apiInst + self.myCore = apiInst.get_core() + return + def start(self): + name = input("Enter your name") + self.myCore.insertBlock("userInfo", sign=True, header='userInfo', meta={'name': name}) + return + +def on_init(api, data = None): + ''' + This event is called after Onionr is initialized, but before the command + inputted is executed. Could be called when daemon is starting or when + just the client is running. + ''' + + # Doing this makes it so that the other functions can access the api object + # by simply referencing the variable `pluginapi`. + pluginapi = api + ui = OnionrCLIUI(api) + api.commands.register('interactive', ui.start) + api.commands.register_help('interactive', 'Open the CLI interface') + return From c0c0f838b6371ca8d6818104ad519a56ee1e591a Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 28 Aug 2018 20:09:27 -0500 Subject: [PATCH 003/101] better user info syncing and show names in mail --- .gitmodules | 3 --- onionr/onionr.py | 8 ++++++-- onionr/onionrusers.py | 14 +++++++++++++- onionr/onionrutils.py | 5 +++-- onionr/static-data/default-plugins/cliui/main.py | 4 ++-- onionr/static-data/default-plugins/pms/main.py | 11 +++++++++-- 6 files changed, 33 insertions(+), 12 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f2ab3397..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "onionr/bitpeer"] - path = onionr/bitpeer - url = https://github.com/beardog108/bitpeer.py diff --git a/onionr/onionr.py b/onionr/onionr.py index 333340ad..dcf76318 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -265,17 +265,21 @@ class Onionr: return self.cmds def friendCmd(self): - '''List, add, or remove friend(s)''' + '''List, add, or remove friend(s) + Changes their peer DB entry. + ''' friend = '' try: + # Get the friend command action = sys.argv[2] except IndexError: logger.info('Syntax: friend add/remove/list [address]') else: action = action.lower() if action == 'list': + # List out peers marked as our friend for friend in self.onionrCore.listPeers(randomOrder=False, trust=1): - if friend == self.onionrCore._crypto.pubKey: + if friend == self.onionrCore._crypto.pubKey: # do not list our key continue friendProfile = onionrusers.OnionrUser(self.onionrCore, friend) logger.info(friend + ' - ' + friendProfile.getName()) diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py index d258a8da..03807ea9 100644 --- a/onionr/onionrusers.py +++ b/onionr/onionrusers.py @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' +import onionrblockapi, logger class OnionrUser: def __init__(self, coreInst, publicKey): self.trust = 0 @@ -58,4 +59,15 @@ class OnionrUser: def forwardDecrypt(self, encrypted): return - \ No newline at end of file + + def findAndSetID(self): + '''Find any info about the user from existing blocks and cache it to their DB entry''' + infoBlocks = [] + for bHash in self._core.getBlocksByType('userInfo'): + block = onionrblockapi.Block(bHash, core=self._core) + if block.signer == self.publicKey: + if block.verifySig(): + newName = block.getMetadata('name') + if newName.isalnum(): + logger.info('%s is now using the name %s.' % (self.publicKey, newName)) + self._core.setPeerInfo(self.publicKey, 'name', newName) \ No newline at end of file diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 81ebf44a..075077e5 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -23,7 +23,7 @@ import nacl.signing, nacl.encoding from onionrblockapi import Block import onionrexceptions from defusedxml import minidom -import pgpwords +import pgpwords, onionrusers if sys.version_info < (3, 6): try: import sha3 @@ -95,7 +95,6 @@ class OnionrUtils: except IndexError: logger.warn('No pow token') continue - #powHash = self._core._crypto.blake2bHash(base64.b64decode(key[1]) + self._core._crypto.blake2bHash(key[0].encode())) value = base64.b64decode(key[1]) hashedKey = self._core._crypto.blake2bHash(key[0]) powHash = self._core._crypto.blake2bHash(value + hashedKey) @@ -106,6 +105,7 @@ class OnionrUtils: if powHash.startswith(b'0000'): if not key[0] in self._core.listPeers(randomOrder=False) and type(key) != None and key[0] != self._core._crypto.pubKey: if self._core.addPeer(key[0], key[1]): + onionrusers.OnionrUser(self._core, key[0]).findAndSetID() retVal = True else: logger.warn("Failed to add key") @@ -282,6 +282,7 @@ class OnionrUtils: pass else: self._core.setPeerInfo(signer, 'name', peerName) + logger.info('%s is now using the name %s.' % (signer, peerName)) except TypeError: pass diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index 40bcfd6f..1d0db8b0 100644 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -19,7 +19,7 @@ ''' # Imports some useful libraries -import logger, config, threading, time +import logger, config, threading, time, uuid from onionrblockapi import Block plugin_name = 'cliui' @@ -32,7 +32,7 @@ class OnionrCLIUI: return def start(self): name = input("Enter your name") - self.myCore.insertBlock("userInfo", sign=True, header='userInfo', meta={'name': name}) + self.myCore.insertBlock("userInfo-" + str(uuid.uuid1()), sign=True, header='userInfo', meta={'name': name}) return def on_init(api, data = None): diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py index 27f56438..dc57a82c 100644 --- a/onionr/static-data/default-plugins/pms/main.py +++ b/onionr/static-data/default-plugins/pms/main.py @@ -21,7 +21,7 @@ # Imports some useful libraries import logger, config, threading, time, readline, datetime from onionrblockapi import Block -import onionrexceptions +import onionrexceptions, onionrusers import locale locale.setlocale(locale.LC_ALL, '') @@ -81,8 +81,15 @@ class OnionrMail: continue blockCount += 1 pmBlockMap[blockCount] = blockHash + + block = Block(blockHash, core=self.myCore) + senderKey = block.getMetadata('signer') + senderDisplay = onionrusers.OnionrUser(self.myCore, senderKey) + if senderDisplay == 'anonymous': + senderDisplay = senderKey + blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M") - print('%s. %s: %s' % (blockCount, blockDate, blockHash)) + print('%s. %s - %s: %s' % (blockCount, blockDate, senderDisplay, blockHash)) try: choice = logger.readline('Enter a block number, -r to refresh, or -q to stop: ').strip().lower() From f4cc1a6f8f195112354a8c29bebdbf9a853fa530 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 28 Aug 2018 22:02:32 -0500 Subject: [PATCH 004/101] updated dockerfile, fixed bug in showing name in mail --- Dockerfile | 2 +- onionr/onionrusers.py | 9 ++++++--- onionr/onionrutils.py | 10 ++++++---- onionr/static-data/default-plugins/cliui/main.py | 2 +- onionr/static-data/default-plugins/pms/main.py | 11 ++++++++--- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index c83de87d..e6132726 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:bionic ENV HOME /root #Install needed packages -RUN apt update && apt install -y python3 python3-dev python3-pip tor locales nano +RUN apt update && apt install -y python3 python3-dev python3-pip tor locales nano sqlite3 RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ locale-gen diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py index 03807ea9..208419aa 100644 --- a/onionr/onionrusers.py +++ b/onionr/onionrusers.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import onionrblockapi, logger +import onionrblockapi, logger, onionrexceptions class OnionrUser: def __init__(self, coreInst, publicKey): self.trust = 0 @@ -69,5 +69,8 @@ class OnionrUser: if block.verifySig(): newName = block.getMetadata('name') if newName.isalnum(): - logger.info('%s is now using the name %s.' % (self.publicKey, newName)) - self._core.setPeerInfo(self.publicKey, 'name', newName) \ No newline at end of file + logger.info('%s is now using the name %s.' % (self.publicKey, self._core._utils.escapeAnsi(newName))) + self._core.setPeerInfo(self.publicKey, 'name', newName) + print("DEBUG PLS") + else: + raise onionrexceptions.InvalidPubkey \ No newline at end of file diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 075077e5..29d3f350 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -95,7 +95,10 @@ class OnionrUtils: except IndexError: logger.warn('No pow token') continue - value = base64.b64decode(key[1]) + try: + value = base64.b64decode(key[1]) + except binascii.Error: + continue hashedKey = self._core._crypto.blake2bHash(key[0]) powHash = self._core._crypto.blake2bHash(value + hashedKey) try: @@ -264,8 +267,7 @@ class OnionrUtils: if myBlock.isEncrypted: myBlock.decrypt() blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks - signer = myBlock.signer - + signer = self.bytesToStr(myBlock.signer) try: if len(blockType) <= 10: self._core.updateBlockInfo(blockHash, 'dataType', blockType) @@ -282,7 +284,7 @@ class OnionrUtils: pass else: self._core.setPeerInfo(signer, 'name', peerName) - logger.info('%s is now using the name %s.' % (signer, peerName)) + logger.info('%s is now using the name %s.' % (signer, self.escapeAnsi(peerName))) except TypeError: pass diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index 1d0db8b0..4d4adb3d 100644 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -31,7 +31,7 @@ class OnionrCLIUI: self.myCore = apiInst.get_core() return def start(self): - name = input("Enter your name") + name = input("Enter your name: ") self.myCore.insertBlock("userInfo-" + str(uuid.uuid1()), sign=True, header='userInfo', meta={'name': name}) return diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py index dc57a82c..46a614b7 100644 --- a/onionr/static-data/default-plugins/pms/main.py +++ b/onionr/static-data/default-plugins/pms/main.py @@ -82,9 +82,14 @@ class OnionrMail: blockCount += 1 pmBlockMap[blockCount] = blockHash - block = Block(blockHash, core=self.myCore) - senderKey = block.getMetadata('signer') - senderDisplay = onionrusers.OnionrUser(self.myCore, senderKey) + block = pmBlocks[blockHash] + senderKey = block.signer + try: + senderKey = senderKey.decode() + except AttributeError: + pass + print("DEBUG:", senderKey) + senderDisplay = onionrusers.OnionrUser(self.myCore, senderKey).getName() if senderDisplay == 'anonymous': senderDisplay = senderKey From 53505832cc8d6a9f2e7aa89d790eab921c86f596 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 29 Aug 2018 23:05:38 -0500 Subject: [PATCH 005/101] preparing for config merge --- onionr/communicator2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 9c44ed21..9e4c70f5 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -84,7 +84,6 @@ class OnionrCommunicatorDaemon: # Set timers, function reference, seconds # requiresPeer True means the timer function won't fire if we have no connected peers - # TODO: make some of these timer counts configurable OnionrCommunicatorTimers(self, self.daemonCommands, 5) OnionrCommunicatorTimers(self, self.detectAPICrash, 5) peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60) @@ -277,7 +276,7 @@ class OnionrCommunicatorDaemon: '''Manages the self.onlinePeers attribute list, connects to more peers if we have none connected''' logger.info('Refreshing peer pool.') - maxPeers = 6 + maxPeers = config.get('peers.maxConnect') needed = maxPeers - len(self.onlinePeers) for i in range(needed): From 716fb7335c4f61bf9a160097f97b76f7d9f9ccbd Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 29 Aug 2018 23:49:28 -0500 Subject: [PATCH 006/101] removed debug statements --- onionr/onionrpeers.py | 2 +- onionr/onionrusers.py | 1 - onionr/static-data/default-plugins/pms/main.py | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/onionr/onionrpeers.py b/onionr/onionrpeers.py index 207f5825..041a69b9 100644 --- a/onionr/onionrpeers.py +++ b/onionr/onionrpeers.py @@ -90,7 +90,7 @@ def peerCleanup(coreInst): if PeerProfiles(address, coreInst).score < minScore: coreInst.removeAddress(address) try: - if (coreInst._utils.getEpoch() - coreInst.getPeerInfo(address, 4)) >= 600: + if (coreInst._utils.getEpoch() - coreInst.getPeerInfo(address, 'dateSeen')) >= 600: expireTime = 600 else: expireTime = 86400 diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py index 208419aa..29b9375b 100644 --- a/onionr/onionrusers.py +++ b/onionr/onionrusers.py @@ -71,6 +71,5 @@ class OnionrUser: if newName.isalnum(): logger.info('%s is now using the name %s.' % (self.publicKey, self._core._utils.escapeAnsi(newName))) self._core.setPeerInfo(self.publicKey, 'name', newName) - print("DEBUG PLS") else: raise onionrexceptions.InvalidPubkey \ No newline at end of file diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py index 46a614b7..f569ec9b 100644 --- a/onionr/static-data/default-plugins/pms/main.py +++ b/onionr/static-data/default-plugins/pms/main.py @@ -88,13 +88,12 @@ class OnionrMail: senderKey = senderKey.decode() except AttributeError: pass - print("DEBUG:", senderKey) senderDisplay = onionrusers.OnionrUser(self.myCore, senderKey).getName() if senderDisplay == 'anonymous': senderDisplay = senderKey blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M") - print('%s. %s - %s: %s' % (blockCount, blockDate, senderDisplay, blockHash)) + print('%s. %s - %s: %s' % (blockCount, blockDate, senderDisplay[:12], blockHash)) try: choice = logger.readline('Enter a block number, -r to refresh, or -q to stop: ').strip().lower() From 1b16c809fd318e22980915a5a86a921ad69be10e Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 31 Aug 2018 17:53:48 -0500 Subject: [PATCH 007/101] work on user connections --- onionr/communicator2.py | 23 +++++++++++++++++++---- onionr/onionrdaemontools.py | 35 +++++++++++++++++++++++++++++++++-- onionr/onionrpeers.py | 4 +++- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 168499ee..2b8667be 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -51,6 +51,8 @@ class OnionrCommunicatorDaemon: # lists of connected peers and peers we know we can't reach currently self.onlinePeers = [] self.offlinePeers = [] + self.cooldownPeer = {} + self.connectTimes = {} self.peerProfiles = [] # list of peer's profiles (onionrpeers.PeerProfile instances) # amount of threads running by name, used to prevent too many @@ -84,13 +86,14 @@ class OnionrCommunicatorDaemon: # requiresPeer True means the timer function won't fire if we have no connected peers OnionrCommunicatorTimers(self, self.daemonCommands, 5) OnionrCommunicatorTimers(self, self.detectAPICrash, 5) - peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60) + peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60, maxThreads=1) OnionrCommunicatorTimers(self, self.lookupBlocks, self._core.config.get('timers.lookupBlocks'), requiresPeer=True, maxThreads=1) OnionrCommunicatorTimers(self, self.getBlocks, self._core.config.get('timers.getBlocks'), requiresPeer=True) OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58) OnionrCommunicatorTimers(self, self.daemonTools.cleanOldBlocks, 65) OnionrCommunicatorTimers(self, self.lookupKeys, 60, requiresPeer=True) OnionrCommunicatorTimers(self, self.lookupAdders, 60, requiresPeer=True) + OnionrCommunicatorTimers(self, self.daemonTools.cooldownPeer, 30, requiresPeer=True) netCheckTimer = OnionrCommunicatorTimers(self, self.daemonTools.netCheck, 600) announceTimer = OnionrCommunicatorTimers(self, self.daemonTools.announceNode, 305, requiresPeer=True, maxThreads=1) cleanupTimer = OnionrCommunicatorTimers(self, self.peerCleanup, 300, requiresPeer=True) @@ -295,7 +298,7 @@ class OnionrCommunicatorDaemon: '''Manages the self.onlinePeers attribute list, connects to more peers if we have none connected''' logger.info('Refreshing peer pool.') - maxPeers = config.get('peers.maxConnect') + maxPeers = int(config.get('peers.maxConnect')) needed = maxPeers - len(self.onlinePeers) for i in range(needed): @@ -338,7 +341,7 @@ class OnionrCommunicatorDaemon: for address in peerList: if not config.get('tor.v3onions') and len(address) == 62: continue - if len(address) == 0 or address in tried or address in self.onlinePeers: + if len(address) == 0 or address in tried or address in self.onlinePeers or address in self.cooldownPeer: continue if self.shutdown: return @@ -347,6 +350,7 @@ class OnionrCommunicatorDaemon: time.sleep(0.1) if address not in self.onlinePeers: self.onlinePeers.append(address) + self.connectTimes[address] = self._core._utils.getEpoch() retData = address # add peer to profile list if they're not in it @@ -361,6 +365,17 @@ class OnionrCommunicatorDaemon: logger.debug('Failed to connect to ' + address) return retData + def removeOnlinePeer(self, peer): + '''Remove an online peer''' + try: + del self.connectTimes[peer] + except KeyError: + pass + try: + self.onlinePeers.remove(peer) + except ValueError: + pass + def peerCleanup(self): '''This just calls onionrpeers.cleanupPeers, which removes dead or bad peers (offline too long, too slow)''' onionrpeers.peerCleanup(self._core) @@ -392,7 +407,7 @@ class OnionrCommunicatorDaemon: if retData == False: try: self.getPeerProfileInstance(peer).addScore(-10) - self.onlinePeers.remove(peer) + self.removeOnlinePeer(peer) self.getOnlinePeers() # Will only add a new peer to pool if needed except ValueError: pass diff --git a/onionr/onionrdaemontools.py b/onionr/onionrdaemontools.py index 7c6fc62e..bc3e7d9e 100644 --- a/onionr/onionrdaemontools.py +++ b/onionr/onionrdaemontools.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 onionrexceptions, onionrpeers, onionrproofs, base64, logger +import onionrexceptions, onionrpeers, onionrproofs, base64, logger, secrets class DaemonTools: def __init__(self, daemon): self.daemon = daemon @@ -70,4 +70,35 @@ class DaemonTools: self.daemon._core._blacklist.addToDB(oldest) self.daemon._core.removeBlock(oldest) logger.info('Deleted block: %s' % (oldest,)) - self.daemon.decrementThreadCount('cleanOldBlocks') \ No newline at end of file + self.daemon.decrementThreadCount('cleanOldBlocks') + + def cooldownPeer(self): + '''Randomly add an online peer to cooldown, so we can connect a new one''' + onlinePeerAmount = len(self.daemon.onlinePeers) + minTime = 300 + cooldownTime = 600 + toCool = '' + tempConnectTimes = dict(self.daemon.connectTimes) + + # Remove peers from cooldown that have been there long enough + tempCooldown = dict(self.daemon.cooldownPeer) + for peer in tempCooldown: + if (self.daemon._core._utils.getEpoch() - tempCooldown[peer]) >= cooldownTime: + del self.daemon.cooldownPeer[peer] + + # Cool down a peer, if we have max connections alive for long enough + if onlinePeerAmount >= self.daemon._core.config.get('peers.maxConnect'): + finding = True + while finding: + try: + toCool = min(tempConnectTimes, key=tempConnectTimes.get) + if (self.daemon._core._utils.getEpoch() - tempConnectTimes[toCool]) < minTime: + del tempConnectTimes[toCool] + else: + finding = False + except ValueError: + break + else: + self.daemon.removeOnlinePeer(toCool) + self.daemon.cooldownPeer[toCool] = self.daemon._core._utils.getEpoch() + self.daemon.decrementThreadCount('cooldownPeer') \ No newline at end of file diff --git a/onionr/onionrpeers.py b/onionr/onionrpeers.py index 041a69b9..322db9ba 100644 --- a/onionr/onionrpeers.py +++ b/onionr/onionrpeers.py @@ -90,13 +90,15 @@ def peerCleanup(coreInst): if PeerProfiles(address, coreInst).score < minScore: coreInst.removeAddress(address) try: - if (coreInst._utils.getEpoch() - coreInst.getPeerInfo(address, 'dateSeen')) >= 600: + if (int(coreInst._utils.getEpoch()) - int(coreInst.getPeerInfo(address, 'dateSeen'))) >= 600: expireTime = 600 else: expireTime = 86400 coreInst._blacklist.addToDB(address, dataType=1, expire=expireTime) except sqlite3.IntegrityError: #TODO just make sure its not a unique constraint issue pass + except ValueError: + pass logger.warn('Removed address ' + address + '.') # Unban probably not malicious peers TODO improve From bed6475e1b5272166b8f48f69bb58db5055b0e49 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 31 Aug 2018 19:51:14 -0500 Subject: [PATCH 008/101] use secrets from dependencies --- onionr/onionrdaemontools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionr/onionrdaemontools.py b/onionr/onionrdaemontools.py index bc3e7d9e..bbd7af64 100644 --- a/onionr/onionrdaemontools.py +++ b/onionr/onionrdaemontools.py @@ -17,7 +17,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import onionrexceptions, onionrpeers, onionrproofs, base64, logger, secrets +import onionrexceptions, onionrpeers, onionrproofs, base64, logger +from dependencies import secrets class DaemonTools: def __init__(self, daemon): self.daemon = daemon From 1203bb2b7a5d672dbc06335bb40cdf06165ed6e9 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 31 Aug 2018 22:29:57 -0500 Subject: [PATCH 009/101] * Adjusted connect configuration * work on cli-ui --- onionr/communicator2.py | 3 +- .../static-data/default-plugins/cliui/main.py | 37 ++++++++++++++++++- onionr/static-data/default_config.json | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 2b8667be..f203dd70 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -408,7 +408,8 @@ class OnionrCommunicatorDaemon: try: self.getPeerProfileInstance(peer).addScore(-10) self.removeOnlinePeer(peer) - self.getOnlinePeers() # Will only add a new peer to pool if needed + if action != 'ping': + self.getOnlinePeers() # Will only add a new peer to pool if needed except ValueError: pass else: diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index 4d4adb3d..dcf370b5 100644 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -19,7 +19,7 @@ ''' # Imports some useful libraries -import logger, config, threading, time, uuid +import logger, config, threading, time, uuid, subprocess from onionrblockapi import Block plugin_name = 'cliui' @@ -30,7 +30,42 @@ class OnionrCLIUI: self.api = apiInst self.myCore = apiInst.get_core() return + + def subCommand(self, command): + try: + subprocess.run(["./onionr.py", command]) + except KeyboardInterrupt: + pass + def start(self): + '''Main CLI UI interface menu''' + showMenu = True + while showMenu: + print('''\n1. Flow (Anonymous public chat, use at your own risk) +2. Mail (Secure email-like service) +3. File Sharing +4. User Settings +5. Quit + ''') + try: + choice = input(">").strip().lower() + except KeyboardInterrupt: + choice = "quit" + + if choice in ("flow", "1"): + self.subCommand("flow") + elif choice in ("2", "mail"): + self.subCommand("mail") + elif choice in ("4", "user settings", "settings"): + try: + self.setName() + except (KeyboardInterrupt, EOFError) as e: + pass + elif choice in ("5", "quit"): + showMenu = False + return + + def setName(self): name = input("Enter your name: ") self.myCore.insertBlock("userInfo-" + str(uuid.uuid1()), sign=True, header='userInfo', meta={'name': name}) return diff --git a/onionr/static-data/default_config.json b/onionr/static-data/default_config.json index 6e35245e..5ebcfa4a 100644 --- a/onionr/static-data/default_config.json +++ b/onionr/static-data/default_config.json @@ -59,7 +59,7 @@ "peers":{ "minimumScore": -100, "maxStoredPeers": 5000, - "maxConnect": 5 + "maxConnect": 10 }, "timers":{ "lookupBlocks": 25, From 9fd985ea49f25b25eb7ffd57f59fbd6bcd662951 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 1 Sep 2018 15:38:44 -0500 Subject: [PATCH 010/101] work on the cliui --- .../static-data/default-plugins/cliui/main.py | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index dcf370b5..0f0953c2 100644 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -37,19 +37,37 @@ class OnionrCLIUI: except KeyboardInterrupt: pass + def refresh(self): + for i in range(100): + print('') + def start(self): '''Main CLI UI interface menu''' showMenu = True + isOnline = "No" + firstRun = True + while showMenu: - print('''\n1. Flow (Anonymous public chat, use at your own risk) + if firstRun: + print("please wait while Onionr starts...") + subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + time.sleep(30) + firstRun = False + + if self.myCore._utils.localCommand('ping') == 'pong': + isOnline = "Yes" + print('''Online Status: ''' + isOnline + ''' + +1. Flow (Anonymous public chat, use at your own risk) 2. Mail (Secure email-like service) 3. File Sharing 4. User Settings -5. Quit +5. Start/Stop Daemon +6. Quit ''') try: choice = input(">").strip().lower() - except KeyboardInterrupt: + except (KeyboardInterrupt, EOFError): choice = "quit" if choice in ("flow", "1"): @@ -61,8 +79,20 @@ class OnionrCLIUI: self.setName() except (KeyboardInterrupt, EOFError) as e: pass - elif choice in ("5", "quit"): + elif choice in ("5", "daemon"): + if isOnline == "Yes": + print("Onionr daemon will shutdown...") + #self.myCore._utils.localCommand("shutdown") + self.myCore.daemonQueueAdd('shutdown') + else: + print("Starting Daemon...") + subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + elif choice in ("6", "quit"): showMenu = False + elif choice == "": + pass + else: + print("Invalid choice") return def setName(self): From c142ab770a03668bf76d0b9e217795d3effbdb83 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 1 Sep 2018 21:59:03 -0500 Subject: [PATCH 011/101] work on cliui --- onionr/core.py | 13 ++++++---- .../static-data/default-plugins/cliui/main.py | 24 +++++++++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index ca9e6c7f..9cd06043 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -377,18 +377,21 @@ class Core: ''' Add a command to the daemon queue, used by the communication daemon (communicator.py) ''' + retData = True # Intended to be used by the web server date = self._utils.getEpoch() conn = sqlite3.connect(self.queueDB) c = conn.cursor() t = (command, data, date) - c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t) - conn.commit() - conn.close() - + try: + c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t) + conn.commit() + conn.close() + except sqlite3.OperationalError: + retData = False events.event('queue_push', data = {'command': command, 'data': data}, onionr = None) - return + return retData def clearDaemonQueue(self): ''' diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index 0f0953c2..81af13c2 100644 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -47,6 +47,9 @@ class OnionrCLIUI: isOnline = "No" firstRun = True + if self.myCore._utils.localCommand('ping') == 'pong': + firstRun = False + while showMenu: if firstRun: print("please wait while Onionr starts...") @@ -56,14 +59,18 @@ class OnionrCLIUI: if self.myCore._utils.localCommand('ping') == 'pong': isOnline = "Yes" - print('''Online Status: ''' + isOnline + ''' + else: + isOnline = "No" + + print(''' +Daemon Running: ''' + isOnline + ''' 1. Flow (Anonymous public chat, use at your own risk) 2. Mail (Secure email-like service) 3. File Sharing 4. User Settings 5. Start/Stop Daemon -6. Quit +6. Quit (Does not shutdown daemon) ''') try: choice = input(">").strip().lower() @@ -74,6 +81,8 @@ class OnionrCLIUI: self.subCommand("flow") elif choice in ("2", "mail"): self.subCommand("mail") + elif choice in ("3", "file sharing", "file"): + print("Not supported yet") elif choice in ("4", "user settings", "settings"): try: self.setName() @@ -84,6 +93,9 @@ class OnionrCLIUI: print("Onionr daemon will shutdown...") #self.myCore._utils.localCommand("shutdown") self.myCore.daemonQueueAdd('shutdown') + while self.myCore._utils.localCommand('ping') == 'pong': + self.myCore.daemonQueueAdd('shutdown') + time.sleep(8) else: print("Starting Daemon...") subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) @@ -96,8 +108,12 @@ class OnionrCLIUI: return def setName(self): - name = input("Enter your name: ") - self.myCore.insertBlock("userInfo-" + str(uuid.uuid1()), sign=True, header='userInfo', meta={'name': name}) + try: + name = input("Enter your name: ") + if name != "": + self.myCore.insertBlock("userInfo-" + str(uuid.uuid1()), sign=True, header='userInfo', meta={'name': name}) + except KeyboardInterrupt: + pass return def on_init(api, data = None): From 083003191f2dc1c36464ef3dd767adf9adbc5da6 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 1 Sep 2018 23:55:24 -0500 Subject: [PATCH 012/101] work on cliui --- onionr/core.py | 1 + onionr/static-data/default-plugins/cliui/main.py | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index 9cd06043..ab8b640b 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -389,6 +389,7 @@ class Core: conn.close() except sqlite3.OperationalError: retData = False + self.daemonQueue() events.event('queue_push', data = {'command': command, 'data': data}, onionr = None) return retData diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index 81af13c2..961bbddb 100644 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -53,7 +53,7 @@ class OnionrCLIUI: while showMenu: if firstRun: print("please wait while Onionr starts...") - subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) time.sleep(30) firstRun = False @@ -93,12 +93,13 @@ Daemon Running: ''' + isOnline + ''' print("Onionr daemon will shutdown...") #self.myCore._utils.localCommand("shutdown") self.myCore.daemonQueueAdd('shutdown') - while self.myCore._utils.localCommand('ping') == 'pong': - self.myCore.daemonQueueAdd('shutdown') - time.sleep(8) + try: + daemon.kill() + except UnboundLocalError: + pass else: print("Starting Daemon...") - subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) elif choice in ("6", "quit"): showMenu = False elif choice == "": From 8cbc16224d27f067b5d3b6734793846bf43b6156 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 2 Sep 2018 15:18:53 -0500 Subject: [PATCH 013/101] work on cliui --- onionr/static-data/default-plugins/cliui/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index 961bbddb..4095f814 100644 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -91,7 +91,6 @@ Daemon Running: ''' + isOnline + ''' elif choice in ("5", "daemon"): if isOnline == "Yes": print("Onionr daemon will shutdown...") - #self.myCore._utils.localCommand("shutdown") self.myCore.daemonQueueAdd('shutdown') try: daemon.kill() From 6b33749b37d078b8a9123078d65c9cc96c4c9ea0 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 2 Sep 2018 15:19:27 -0500 Subject: [PATCH 014/101] adding board plugin --- .../default-plugins/boards/info.json | 5 ++ .../default-plugins/boards/main.py | 62 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 onionr/static-data/default-plugins/boards/info.json create mode 100644 onionr/static-data/default-plugins/boards/main.py diff --git a/onionr/static-data/default-plugins/boards/info.json b/onionr/static-data/default-plugins/boards/info.json new file mode 100644 index 00000000..f3e80e1c --- /dev/null +++ b/onionr/static-data/default-plugins/boards/info.json @@ -0,0 +1,5 @@ +{ + "name" : "boards", + "version" : "1.0", + "author" : "onionr" +} diff --git a/onionr/static-data/default-plugins/boards/main.py b/onionr/static-data/default-plugins/boards/main.py new file mode 100644 index 00000000..61d66378 --- /dev/null +++ b/onionr/static-data/default-plugins/boards/main.py @@ -0,0 +1,62 @@ +''' + Onionr - P2P Anonymous Storage Network + + This is an interactive menu-driven CLI interface for Onionr +''' +''' + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' + +# Imports some useful libraries +import logger, config, sys +from onionrblockapi import Block +try: + import tkinter +except (ModuleNotFoundError, ImportError) as e: + TK_ENABLED = False +else: + TK_ENABLED = True + + +plugin_name = 'cliui' +PLUGIN_VERSION = '0.0.1' + +class OnionrBoards: + def __init__(self, apiInst): + self.api = apiInst + self.myCore = apiInst.get_core() + + self.gui = tkinter.Tk() + + return + + def start(self): + return + +def on_init(api, data = None): + ''' + This event is called after Onionr is initialized, but before the command + inputted is executed. Could be called when daemon is starting or when + just the client is running. + ''' + + # Doing this makes it so that the other functions can access the api object + # by simply referencing the variable `pluginapi`. + pluginapi = api + ui = OnionrBoards(api) + api.commands.register('boards', ui.start) + api.commands.register_help('boards', 'Open the board viewer') + + + return From b01184d1510bb045d2cc94e55cde86f12720fa74 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sun, 2 Sep 2018 15:44:23 -0700 Subject: [PATCH 015/101] add logos --- onionr/api.py | 2 + onionr/static-data/www/ui/dist/index.html | 2 +- onionr/static-data/www/ui/dist/js/main.js | 32 +++++++++- onionr/static-data/www/ui/dist/js/timeline.js | 63 ++++++++++++++++--- onionr/static-data/www/ui/src/index.html | 2 +- onionr/static-data/www/ui/src/js/main.js | 32 +++++++++- onionr/static-data/www/ui/src/js/timeline.js | 62 +++++++++++++++--- 7 files changed, 173 insertions(+), 22 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 3ccebff5..4780034f 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -249,6 +249,8 @@ class API: self.mimeType = 'text/html' response = siteData.split(b'-', 2)[-1] resp = Response(response) + elif action == 'info': + resp = new Response(json.dumps({'id' : 'not yet implemented'})) elif action == "insertBlock": response = {'success' : False, 'reason' : 'An unknown error occurred'} diff --git a/onionr/static-data/www/ui/dist/index.html b/onionr/static-data/www/ui/dist/index.html index 6efaac1b..02ee47b4 100644 --- a/onionr/static-data/www/ui/dist/index.html +++ b/onionr/static-data/www/ui/dist/index.html @@ -43,7 +43,7 @@
-

arinerron

+

arinerron

diff --git a/onionr/static-data/www/ui/dist/js/main.js b/onionr/static-data/www/ui/dist/js/main.js index 75cb7f81..6bbbdb27 100644 --- a/onionr/static-data/www/ui/dist/js/main.js +++ b/onionr/static-data/www/ui/dist/js/main.js @@ -17,6 +17,13 @@ function remove(key) { return localStorage.removeItem(key); } +function getParameter(name) { + var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search); + return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); +} + +/* usermap localStorage stuff */ + var usermap = JSON.parse(get('usermap', '{}')); function getUserMap() { @@ -24,11 +31,29 @@ function getUserMap() { } function deserializeUser(id) { + if(!(id in getUserMap())) + return null; + var serialized = getUserMap()[id] var user = new User(); + user.setName(serialized['name']); user.setID(serialized['id']); user.setIcon(serialized['icon']); + + return user; +} + +function serializeUser(user) { + if(user !== null && user !== undefined) { + var serialized = {'name' : user.getName(), 'id' : user.getID(), 'icon' : user.getIcon()}; + + usermap[user.getID()] = serialized; + + set('usermap', JSON.stringify(getUserMap())); + + return serialized; + } } /* returns a relative date format, e.g. "5 minutes" */ @@ -219,7 +244,7 @@ class Post { // postTemplate = postTemplate.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().split('-').slice(0, 4).join('-'))); postTemplate = postTemplate.replaceAll('$user-id', Sanitize.html(this.getUser().getID())); - postTemplate = postTemplate.replaceAll('$user-image', Sanitize.html(this.getUser().getIcon())); + postTemplate = postTemplate.replaceAll('$user-image', "data:image/jpeg;base64," + Sanitize.html(this.getUser().getIcon())); postTemplate = postTemplate.replaceAll('$content', Sanitize.html(this.getContent())); postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : '')); postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString()); @@ -449,3 +474,8 @@ if(getWebPassword() === null) { setWebPassword(password); window.location.reload(true); } + +var tt = getParameter("timingToken"); +if(tt !== null && tt !== undefined) { + setTimingToken(tt); +} diff --git a/onionr/static-data/www/ui/dist/js/timeline.js b/onionr/static-data/www/ui/dist/js/timeline.js index 0dbd634a..e7a3c472 100644 --- a/onionr/static-data/www/ui/dist/js/timeline.js +++ b/onionr/static-data/www/ui/dist/js/timeline.js @@ -1,21 +1,57 @@ +function getUserInfo(id, callback) { + var user = deserializeUser(id); + if(user === null) { + Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { + if(data.length !== 0) { + try { + user = new User(); + + var userInfo = JSON.parse(data[0].getContent()); + + if(userInfo['id'] === id) { + user.setName(userInfo['name']); + user.setIcon(userInfo['icon']); + user.setID(id); + + serializeUser(user); + + return callback(user); + } + } catch(e) { + console.log(e); + return callback(null); + } + } else { + return callback(null); + } + }); + } else { + return callback(user); + } +} + /* just for testing rn */ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, function(data) { for(var i = 0; i < data.length; i++) { try { var block = data[i]; - var post = new Post(); - var user = new User(); + var finished = false; + getUserInfo(new String(block.getHeader('signer', 'unknown')), function(user) { + var post = new Post(); - var blockContent = JSON.parse(block.getContent()); + var blockContent = JSON.parse(block.getContent()); - user.setName('unknown'); - user.setID(new String(block.getHeader('signer', 'unknown'))); - post.setContent(blockContent['content']); - post.setPostDate(block.getDate()); - post.setUser(user); + post.setContent(blockContent['content']); + post.setPostDate(block.getDate()); + post.setUser(user); - document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML(); + document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML(); + + finished = true; + }); + + while(!finished); } catch(e) { console.log(e); } @@ -23,5 +59,14 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun }); function viewProfile(id, name) { + id = decodeURIComponent(id); document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name)); + + getUserInfo(id, function(data) { + if(data !== null) { + document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(data.getName()); + document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID()); + document.getElementById("onionr-profile-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(data.getIcon()); + } + }); } diff --git a/onionr/static-data/www/ui/src/index.html b/onionr/static-data/www/ui/src/index.html index e5791cb9..f34e8605 100644 --- a/onionr/static-data/www/ui/src/index.html +++ b/onionr/static-data/www/ui/src/index.html @@ -13,7 +13,7 @@
-

arinerron

+

arinerron

diff --git a/onionr/static-data/www/ui/src/js/main.js b/onionr/static-data/www/ui/src/js/main.js index a99c187b..50738052 100644 --- a/onionr/static-data/www/ui/src/js/main.js +++ b/onionr/static-data/www/ui/src/js/main.js @@ -17,6 +17,13 @@ function remove(key) { return localStorage.removeItem(key); } +function getParameter(name) { + var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search); + return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); +} + +/* usermap localStorage stuff */ + var usermap = JSON.parse(get('usermap', '{}')); function getUserMap() { @@ -24,11 +31,29 @@ function getUserMap() { } function deserializeUser(id) { + if(!(id in getUserMap())) + return null; + var serialized = getUserMap()[id] var user = new User(); + user.setName(serialized['name']); user.setID(serialized['id']); user.setIcon(serialized['icon']); + + return user; +} + +function serializeUser(user) { + if(user !== null && user !== undefined) { + var serialized = {'name' : user.getName(), 'id' : user.getID(), 'icon' : user.getIcon()}; + + usermap[user.getID()] = serialized; + + set('usermap', JSON.stringify(getUserMap())); + + return serialized; + } } /* returns a relative date format, e.g. "5 minutes" */ @@ -187,7 +212,7 @@ class Post { // postTemplate = postTemplate.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().split('-').slice(0, 4).join('-'))); postTemplate = postTemplate.replaceAll('$user-id', Sanitize.html(this.getUser().getID())); - postTemplate = postTemplate.replaceAll('$user-image', Sanitize.html(this.getUser().getIcon())); + postTemplate = postTemplate.replaceAll('$user-image', "data:image/jpeg;base64," + Sanitize.html(this.getUser().getIcon())); postTemplate = postTemplate.replaceAll('$content', Sanitize.html(this.getContent())); postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : '')); postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString()); @@ -417,3 +442,8 @@ if(getWebPassword() === null) { setWebPassword(password); window.location.reload(true); } + +var tt = getParameter("timingToken"); +if(tt !== null && tt !== undefined) { + setTimingToken(tt); +} diff --git a/onionr/static-data/www/ui/src/js/timeline.js b/onionr/static-data/www/ui/src/js/timeline.js index 763fc7d0..e7a3c472 100644 --- a/onionr/static-data/www/ui/src/js/timeline.js +++ b/onionr/static-data/www/ui/src/js/timeline.js @@ -1,3 +1,34 @@ +function getUserInfo(id, callback) { + var user = deserializeUser(id); + if(user === null) { + Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { + if(data.length !== 0) { + try { + user = new User(); + + var userInfo = JSON.parse(data[0].getContent()); + + if(userInfo['id'] === id) { + user.setName(userInfo['name']); + user.setIcon(userInfo['icon']); + user.setID(id); + + serializeUser(user); + + return callback(user); + } + } catch(e) { + console.log(e); + return callback(null); + } + } else { + return callback(null); + } + }); + } else { + return callback(user); + } +} /* just for testing rn */ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, function(data) { @@ -5,18 +36,22 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun try { var block = data[i]; - var post = new Post(); - var user = new User(); + var finished = false; + getUserInfo(new String(block.getHeader('signer', 'unknown')), function(user) { + var post = new Post(); - var blockContent = JSON.parse(block.getContent()); + var blockContent = JSON.parse(block.getContent()); - user.setName('unknown'); - user.setID(new String(block.getHeader('signer', 'unknown'))); - post.setContent(blockContent['content']); - post.setPostDate(block.getDate()); - post.setUser(user); + post.setContent(blockContent['content']); + post.setPostDate(block.getDate()); + post.setUser(user); - document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML(); + document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML(); + + finished = true; + }); + + while(!finished); } catch(e) { console.log(e); } @@ -24,5 +59,14 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun }); function viewProfile(id, name) { + id = decodeURIComponent(id); document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name)); + + getUserInfo(id, function(data) { + if(data !== null) { + document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(data.getName()); + document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID()); + document.getElementById("onionr-profile-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(data.getIcon()); + } + }); } From ecefa417926ab70b0608cd70c72564853d6b2527 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sun, 2 Sep 2018 22:08:12 -0700 Subject: [PATCH 016/101] Complete first part of work on onionrui --- onionr/api.py | 2 +- onionr/static-data/bootstrap-nodes.txt | 1 + .../www/ui/common/default-icon.html | 1 + onionr/static-data/www/ui/common/footer.html | 17 +- onionr/static-data/www/ui/dist/css/main.css | 33 +++ .../www/ui/dist/css/themes/dark.css | 29 +++ onionr/static-data/www/ui/dist/index.html | 51 ++++- onionr/static-data/www/ui/dist/js/main.js | 203 ++++++++++++++++-- onionr/static-data/www/ui/dist/js/timeline.js | 123 ++++++++--- onionr/static-data/www/ui/lang.json | 13 +- onionr/static-data/www/ui/src/css/main.css | 33 +++ .../www/ui/src/css/themes/dark.css | 29 +++ onionr/static-data/www/ui/src/index.html | 36 +++- onionr/static-data/www/ui/src/js/main.js | 202 +++++++++++++++-- onionr/static-data/www/ui/src/js/timeline.js | 127 ++++++++--- 15 files changed, 797 insertions(+), 103 deletions(-) create mode 100644 onionr/static-data/www/ui/common/default-icon.html diff --git a/onionr/api.py b/onionr/api.py index 4780034f..0f3bde41 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -250,7 +250,7 @@ class API: response = siteData.split(b'-', 2)[-1] resp = Response(response) elif action == 'info': - resp = new Response(json.dumps({'id' : 'not yet implemented'})) + resp = Response(json.dumps({'pubkey' : self._core._crypto.pubKey, 'host' : self._core.hsAdder})) elif action == "insertBlock": response = {'success' : False, 'reason' : 'An unknown error occurred'} diff --git a/onionr/static-data/bootstrap-nodes.txt b/onionr/static-data/bootstrap-nodes.txt index 5473550f..67459d90 100644 --- a/onionr/static-data/bootstrap-nodes.txt +++ b/onionr/static-data/bootstrap-nodes.txt @@ -1,2 +1,3 @@ onionragxuddecmg.onion dgyllprmtmym4gbk.onion +eczfevdpirhvbniy.onion diff --git a/onionr/static-data/www/ui/common/default-icon.html b/onionr/static-data/www/ui/common/default-icon.html new file mode 100644 index 00000000..86ccf773 --- /dev/null +++ b/onionr/static-data/www/ui/common/default-icon.html @@ -0,0 +1 @@ +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 diff --git a/onionr/static-data/www/ui/common/footer.html b/onionr/static-data/www/ui/common/footer.html index 6b5cfb06..0143c2d8 100644 --- a/onionr/static-data/www/ui/common/footer.html +++ b/onionr/static-data/www/ui/common/footer.html @@ -1,4 +1,19 @@ - + + + + diff --git a/onionr/static-data/www/ui/dist/css/main.css b/onionr/static-data/www/ui/dist/css/main.css index dab080ef..a69216cb 100644 --- a/onionr/static-data/www/ui/dist/css/main.css +++ b/onionr/static-data/www/ui/dist/css/main.css @@ -60,6 +60,35 @@ body { width: 100%; } +.onionr-post-creator { + padding: 1rem; + margin-bottom: 1rem; + + width: 100%; +} + +.onionr-post-creator-user-name { + display: inline; +} + +.onionr-post-creator-user-id:before { content: "("; } +.onionr-post-creator-user-id:after { content: ")"; } + +.onionr-post-creator-content { + word-wrap: break-word; + width: 100%; +} + +.onionr-post-creator-user-icon { + border-radius: 100%; + width: 100%; +} + +.onionr-post-creator-create { + width: 100%; + text-align: center; +} + .h-divider { margin: 5px 15px; height: 1px; @@ -77,3 +106,7 @@ body { .onionr-profile-username { text-align: center; } + +.onionr-profile-save { + width: 100%; +} diff --git a/onionr/static-data/www/ui/dist/css/themes/dark.css b/onionr/static-data/www/ui/dist/css/themes/dark.css index b0473390..5ff6cc40 100644 --- a/onionr/static-data/www/ui/dist/css/themes/dark.css +++ b/onionr/static-data/www/ui/dist/css/themes/dark.css @@ -31,6 +31,35 @@ body { font-size: 15pt; } +.onionr-post-creator { + border: 1px solid black; + border-radius: 1rem; + + background-color: lightgray; +} + +.onionr-post-creator-user-name { + color: green; + font-weight: bold; +} + +.onionr-post-creator-user-id { + color: gray; +} + +.onionr-post-creator-date { + color: gray; +} + +.onionr-post-creator-content { + font-family: sans-serif, serif; + border-top: 1px solid black; + font-size: 15pt; + background-color: lightgray; + color: black; + border-width: 0px; +} + .h-divider { border-top:1px solid gray; } diff --git a/onionr/static-data/www/ui/dist/index.html b/onionr/static-data/www/ui/dist/index.html index 02ee47b4..ba0c31d4 100644 --- a/onionr/static-data/www/ui/dist/index.html +++ b/onionr/static-data/www/ui/dist/index.html @@ -40,10 +40,16 @@
- +
-

arinerron

+

+
+
+ +
+
+
@@ -52,6 +58,32 @@
+
+ +
+
+
+
+ +
+
+
+
+ + you +
+
+ + + + +
+
+
+
+ +
+
@@ -69,6 +101,21 @@
+ + + diff --git a/onionr/static-data/www/ui/dist/js/main.js b/onionr/static-data/www/ui/dist/js/main.js index 6bbbdb27..71c80266 100644 --- a/onionr/static-data/www/ui/dist/js/main.js +++ b/onionr/static-data/www/ui/dist/js/main.js @@ -44,16 +44,17 @@ function deserializeUser(id) { return user; } -function serializeUser(user) { - if(user !== null && user !== undefined) { - var serialized = {'name' : user.getName(), 'id' : user.getID(), 'icon' : user.getIcon()}; +function getCurrentUser() { + var user = get('currentUser', null); - usermap[user.getID()] = serialized; + if(user === null) + return null; - set('usermap', JSON.stringify(getUserMap())); + return User.getUser(user, function() {}); +} - return serialized; - } +function setCurrentUser(user) { + set('currentUser', user.getID()); } /* returns a relative date format, e.g. "5 minutes" */ @@ -131,6 +132,11 @@ class Sanitize { static url(url) { return encodeURIComponent(url); } + + /* usernames */ + static username(username) { + return String(username).replace(/[\W_]+/g, " ").substring(0, 25); + } } /* config stuff */ @@ -182,18 +188,75 @@ class User { return this.image; } + setDescription(description) { + this.description = description; + } + + getDescription() { + return this.description; + } + serialize() { return { 'name' : this.getName(), 'id' : this.getID(), - 'icon' : this.getIcon() + 'icon' : this.getIcon(), + 'description' : this.getDescription() }; } + /* save in usermap */ remember() { usermap[this.getID()] = this.serialize(); set('usermap', JSON.stringify(usermap)); } + + /* save as a block */ + save(callback) { + var block = new Block(); + + block.setType('onionr-user'); + block.setContent(JSON.stringify(this.serialize())); + + return block.save(true, callback); + } + + static getUser(id, callback) { + var user = deserializeUser(id); + if(user === null) { + Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { + if(data.length !== 0) { + try { + user = new User(); + + var userInfo = JSON.parse(data[0].getContent()); + + if(userInfo['id'] === id) { + user.setName(userInfo['name']); + user.setIcon(userInfo['icon']); + user.setID(id); + + user.remember(); + + callback(user); + return user; + } + } catch(e) { + console.log(e); + + callback(null); + return null; + } + } else { + callback(null); + return null; + } + }); + } else { + callback(user); + return user; + } + } } /* post class */ @@ -278,6 +341,35 @@ class Post { getPostDate() { return this.date; } + + save(callback) { + var args = {'type' : 'onionr-post', 'sign' : true, 'content' : JSON.stringify({'content' : this.getContent()})}; + + var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); + + console.log(url); + + var http = new XMLHttpRequest(); + + if(callback !== undefined) { + // async + + http.addEventListener('load', function() { + callback(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + }, false); + + http.open('GET', url, true); + http.timeout = 5000; + http.send(null); + } else { + // sync + + http.open('GET', url, false); + http.send(null); + + return Block.parseBlockArray(JSON.parse(http.responseText)['hash']); + } + } } /* block class */ @@ -380,6 +472,52 @@ class Block { return !(this.hash === null || this.hash === undefined); } + // saves the block, returns the hash + save(sign, callback) { + var type = this.getType(); + var content = this.getContent(); + var parent = this.getParent(); + + if(content !== undefined && content !== null && type !== '') { + var args = {'content' : content}; + + if(type !== undefined && type !== null && type !== '') + args['type'] = type; + if(parent !== undefined && parent !== null && parent.getHash() !== undefined && parent.getHash() !== null && parent.getHash() !== '') + args['parent'] = parent.getHash(); + if(sign !== undefined && sign !== null) + args['sign'] = String(sign) !== 'false' + + + var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); + + console.log(url); + + var http = new XMLHttpRequest(); + + if(callback !== undefined) { + // async + + http.addEventListener('load', function() { + callback(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + }, false); + + http.open('GET', url, true); + http.timeout = 5000; + http.send(null); + } else { + // sync + + http.open('GET', url, false); + http.send(null); + + return Block.parseBlockArray(JSON.parse(http.responseText)['hash']); + } + } + + return false; + } + /* static functions */ // recreates a block by hash @@ -463,19 +601,56 @@ class Block { /* temporary code */ +var tt = getParameter("timingToken"); +if(tt !== null && tt !== undefined) { + setTimingToken(tt); +} + if(getWebPassword() === null) { var password = ""; while(password.length != 64) { password = prompt("Please enter the web password (run `./RUN-LINUX.sh --get-password`)"); } - setTimingToken(prompt("Please enter the timing token (optional)")); - setWebPassword(password); - window.location.reload(true); } -var tt = getParameter("timingToken"); -if(tt !== null && tt !== undefined) { - setTimingToken(tt); +if(getCurrentUser() === null) { + var url = '/client/?action=info&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); + + console.log(url); + + var http = new XMLHttpRequest(); + + // sync + + http.open('GET', url, false); + http.send(null); + + var id = JSON.parse(http.responseText)['pubkey']; + + User.getUser(id, function(data) { + if(data === null || data === undefined) { + jQuery('#modal').modal('show'); + + var user = new User(); + + user.setName('New User'); + user.setID(id); + user.setIcon('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\ +'); + user.setDescription('A new OnionrUI user'); + + user.remember(); + user.save(); + + setCurrentUser(user); + + window.location.reload(); + } else { + setCurrentUser(data); + } + }); } + +currentUser = getCurrentUser(); diff --git a/onionr/static-data/www/ui/dist/js/timeline.js b/onionr/static-data/www/ui/dist/js/timeline.js index e7a3c472..af554f80 100644 --- a/onionr/static-data/www/ui/dist/js/timeline.js +++ b/onionr/static-data/www/ui/dist/js/timeline.js @@ -1,35 +1,3 @@ -function getUserInfo(id, callback) { - var user = deserializeUser(id); - if(user === null) { - Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { - if(data.length !== 0) { - try { - user = new User(); - - var userInfo = JSON.parse(data[0].getContent()); - - if(userInfo['id'] === id) { - user.setName(userInfo['name']); - user.setIcon(userInfo['icon']); - user.setID(id); - - serializeUser(user); - - return callback(user); - } - } catch(e) { - console.log(e); - return callback(null); - } - } else { - return callback(null); - } - }); - } else { - return callback(user); - } -} - /* just for testing rn */ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, function(data) { for(var i = 0; i < data.length; i++) { @@ -37,7 +5,7 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun var block = data[i]; var finished = false; - getUserInfo(new String(block.getHeader('signer', 'unknown')), function(user) { + User.getUser(new String(block.getHeader('signer', 'unknown')), function(user) { var post = new Post(); var blockContent = JSON.parse(block.getContent()); @@ -53,6 +21,7 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun while(!finished); } catch(e) { + console.log('Troublemaker block: ' + data[i].getHash()); console.log(e); } } @@ -62,11 +31,97 @@ function viewProfile(id, name) { id = decodeURIComponent(id); document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name)); - getUserInfo(id, function(data) { + User.getUser(id, function(data) { if(data !== null) { document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(data.getName()); document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID()); document.getElementById("onionr-profile-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(data.getIcon()); + document.getElementById("onionr-profile-user-icon").b64 = Sanitize.html(data.getIcon()); } }); } + +function updateUser() { + toggleSaveButton(false); + + jQuery('#modal').modal('show'); + + var name = jQuery('#onionr-profile-username').text(); + var id = document.getElementById("onionr-profile-username").title; + var icon = document.getElementById("onionr-profile-user-icon").b64; + var description = 'todo'; + + var user = new User(); + + user.setName(name); + user.setID(id); + user.setIcon(icon); + user.setDescription(description); + + user.remember(); + user.save(); + + window.location.reload(); +} + +function cancelUpdate() { + toggleSaveButton(false); + + var name = jQuery('#onionr-profile-username').text(); + var id = document.getElementById("onionr-profile-username").title; + + viewProfile(id, name); +} + +function toggleSaveButton(show) { + document.getElementById("onionr-profile-save").style.display = (show ? 'block' : 'none'); + document.getElementById("onionr-profile-cancel").style.display = (show ? 'block' : 'none'); +} + +function makePost() { + var content = document.getElementById("onionr-post-creator-content").value; + + var post = new Post(); + + post.setUser(getCurrentUser()); + post.setContent(content); + post.setPostDate(new Date()); + + post.save(function(data) {}); // async, but no function + + document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML; + + document.getElementById("onionr-post-creator-content").value = ""; +} + +$('body').on('click', '[data-editable]', function() { + var el = jQuery(this); + var txt = el.text(); + + var input = jQuery('').val(txt); + el.replaceWith(input); + + var save = function() { + var newTxt = input.val(); + var p = el.text(Sanitize.username(newTxt)); + input.replaceWith(p); + + if(newTxt !== txt) + toggleSaveButton(true); + }; + + input.one('blur', save).focus(); +}); + +document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName()); +document.getElementById("onionr-post-creator-user-id").innerHTML = "you"; +document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); +document.getElementById("onionr-post-creator-user-id").title = currentUser.getID(); +document.getElementById("onionr-post-creator-content").placeholder = "Enter a message here..."; + +viewCurrentProfile = function() { + viewProfile(encodeURIComponent(currentUser.getID()), encodeURIComponent(currentUser.getName())); +} + +document.getElementById("onionr-post-creator-user-id").onclick = viewCurrentProfile; +document.getElementById("onionr-post-creator-user-name").onclick = viewCurrentProfile; diff --git a/onionr/static-data/www/ui/lang.json b/onionr/static-data/www/ui/lang.json index 28606a56..872a68cf 100644 --- a/onionr/static-data/www/ui/lang.json +++ b/onionr/static-data/www/ui/lang.json @@ -6,10 +6,21 @@ "NOTIFICATIONS" : "Notifications", "MESSAGES" : "Messages", + "LATEST" : "Latest...", "TRENDING" : "Trending", + "MODAL_TITLE" : "Loading...", + "MODAL_MESSAGE" : "Onionr has begun performing a CPU-intensive operation. If this operation does not complete in the next 10 seconds, try reloading the page.", + "POST_LIKE" : "like", - "POST_REPLY" : "reply" + "POST_REPLY" : "reply", + + "POST_CREATOR_YOU" : "you", + "POST_CREATOR_PLACEHOLDER" : "Enter a message here...", + "POST_CREATOR_CREATE" : "Create post", + + "PROFILE_EDIT_SAVE" : "Save", + "PROFILE_EDIT_CANCEL" : "Cancel" }, "spa" : { diff --git a/onionr/static-data/www/ui/src/css/main.css b/onionr/static-data/www/ui/src/css/main.css index dab080ef..a69216cb 100644 --- a/onionr/static-data/www/ui/src/css/main.css +++ b/onionr/static-data/www/ui/src/css/main.css @@ -60,6 +60,35 @@ body { width: 100%; } +.onionr-post-creator { + padding: 1rem; + margin-bottom: 1rem; + + width: 100%; +} + +.onionr-post-creator-user-name { + display: inline; +} + +.onionr-post-creator-user-id:before { content: "("; } +.onionr-post-creator-user-id:after { content: ")"; } + +.onionr-post-creator-content { + word-wrap: break-word; + width: 100%; +} + +.onionr-post-creator-user-icon { + border-radius: 100%; + width: 100%; +} + +.onionr-post-creator-create { + width: 100%; + text-align: center; +} + .h-divider { margin: 5px 15px; height: 1px; @@ -77,3 +106,7 @@ body { .onionr-profile-username { text-align: center; } + +.onionr-profile-save { + width: 100%; +} diff --git a/onionr/static-data/www/ui/src/css/themes/dark.css b/onionr/static-data/www/ui/src/css/themes/dark.css index b0473390..5ff6cc40 100644 --- a/onionr/static-data/www/ui/src/css/themes/dark.css +++ b/onionr/static-data/www/ui/src/css/themes/dark.css @@ -31,6 +31,35 @@ body { font-size: 15pt; } +.onionr-post-creator { + border: 1px solid black; + border-radius: 1rem; + + background-color: lightgray; +} + +.onionr-post-creator-user-name { + color: green; + font-weight: bold; +} + +.onionr-post-creator-user-id { + color: gray; +} + +.onionr-post-creator-date { + color: gray; +} + +.onionr-post-creator-content { + font-family: sans-serif, serif; + border-top: 1px solid black; + font-size: 15pt; + background-color: lightgray; + color: black; + border-width: 0px; +} + .h-divider { border-top:1px solid gray; } diff --git a/onionr/static-data/www/ui/src/index.html b/onionr/static-data/www/ui/src/index.html index f34e8605..99463d27 100644 --- a/onionr/static-data/www/ui/src/index.html +++ b/onionr/static-data/www/ui/src/index.html @@ -10,10 +10,16 @@
- +
-

arinerron

+

+
+
+ +
+
+
@@ -22,6 +28,32 @@
+
+ +
+
+
+
+ +
+
+
+
+ + you +
+
+ + + + +
+
+
+
+ +
+
diff --git a/onionr/static-data/www/ui/src/js/main.js b/onionr/static-data/www/ui/src/js/main.js index 50738052..ca2c5214 100644 --- a/onionr/static-data/www/ui/src/js/main.js +++ b/onionr/static-data/www/ui/src/js/main.js @@ -44,16 +44,17 @@ function deserializeUser(id) { return user; } -function serializeUser(user) { - if(user !== null && user !== undefined) { - var serialized = {'name' : user.getName(), 'id' : user.getID(), 'icon' : user.getIcon()}; +function getCurrentUser() { + var user = get('currentUser', null); - usermap[user.getID()] = serialized; + if(user === null) + return null; - set('usermap', JSON.stringify(getUserMap())); + return User.getUser(user, function() {}); +} - return serialized; - } +function setCurrentUser(user) { + set('currentUser', user.getID()); } /* returns a relative date format, e.g. "5 minutes" */ @@ -131,6 +132,11 @@ class Sanitize { static url(url) { return encodeURIComponent(url); } + + /* usernames */ + static username(username) { + return String(username).replace(/[\W_]+/g, " ").substring(0, 25); + } } /* config stuff */ @@ -182,18 +188,75 @@ class User { return this.image; } + setDescription(description) { + this.description = description; + } + + getDescription() { + return this.description; + } + serialize() { return { 'name' : this.getName(), 'id' : this.getID(), - 'icon' : this.getIcon() + 'icon' : this.getIcon(), + 'description' : this.getDescription() }; } + /* save in usermap */ remember() { usermap[this.getID()] = this.serialize(); set('usermap', JSON.stringify(usermap)); } + + /* save as a block */ + save(callback) { + var block = new Block(); + + block.setType('onionr-user'); + block.setContent(JSON.stringify(this.serialize())); + + return block.save(true, callback); + } + + static getUser(id, callback) { + var user = deserializeUser(id); + if(user === null) { + Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { + if(data.length !== 0) { + try { + user = new User(); + + var userInfo = JSON.parse(data[0].getContent()); + + if(userInfo['id'] === id) { + user.setName(userInfo['name']); + user.setIcon(userInfo['icon']); + user.setID(id); + + user.remember(); + + callback(user); + return user; + } + } catch(e) { + console.log(e); + + callback(null); + return null; + } + } else { + callback(null); + return null; + } + }); + } else { + callback(user); + return user; + } + } } /* post class */ @@ -246,6 +309,35 @@ class Post { getPostDate() { return this.date; } + + save(callback) { + var args = {'type' : 'onionr-post', 'sign' : true, 'content' : JSON.stringify({'content' : this.getContent()})}; + + var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); + + console.log(url); + + var http = new XMLHttpRequest(); + + if(callback !== undefined) { + // async + + http.addEventListener('load', function() { + callback(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + }, false); + + http.open('GET', url, true); + http.timeout = 5000; + http.send(null); + } else { + // sync + + http.open('GET', url, false); + http.send(null); + + return Block.parseBlockArray(JSON.parse(http.responseText)['hash']); + } + } } /* block class */ @@ -348,6 +440,52 @@ class Block { return !(this.hash === null || this.hash === undefined); } + // saves the block, returns the hash + save(sign, callback) { + var type = this.getType(); + var content = this.getContent(); + var parent = this.getParent(); + + if(content !== undefined && content !== null && type !== '') { + var args = {'content' : content}; + + if(type !== undefined && type !== null && type !== '') + args['type'] = type; + if(parent !== undefined && parent !== null && parent.getHash() !== undefined && parent.getHash() !== null && parent.getHash() !== '') + args['parent'] = parent.getHash(); + if(sign !== undefined && sign !== null) + args['sign'] = String(sign) !== 'false' + + + var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); + + console.log(url); + + var http = new XMLHttpRequest(); + + if(callback !== undefined) { + // async + + http.addEventListener('load', function() { + callback(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + }, false); + + http.open('GET', url, true); + http.timeout = 5000; + http.send(null); + } else { + // sync + + http.open('GET', url, false); + http.send(null); + + return Block.parseBlockArray(JSON.parse(http.responseText)['hash']); + } + } + + return false; + } + /* static functions */ // recreates a block by hash @@ -431,19 +569,55 @@ class Block { /* temporary code */ +var tt = getParameter("timingToken"); +if(tt !== null && tt !== undefined) { + setTimingToken(tt); +} + if(getWebPassword() === null) { var password = ""; while(password.length != 64) { password = prompt("Please enter the web password (run `./RUN-LINUX.sh --get-password`)"); } - setTimingToken(prompt("Please enter the timing token (optional)")); - setWebPassword(password); - window.location.reload(true); } -var tt = getParameter("timingToken"); -if(tt !== null && tt !== undefined) { - setTimingToken(tt); +if(getCurrentUser() === null) { + var url = '/client/?action=info&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); + + console.log(url); + + var http = new XMLHttpRequest(); + + // sync + + http.open('GET', url, false); + http.send(null); + + var id = JSON.parse(http.responseText)['pubkey']; + + User.getUser(id, function(data) { + if(data === null || data === undefined) { + jQuery('#modal').modal('show'); + + var user = new User(); + + user.setName('New User'); + user.setID(id); + user.setIcon('<$= Template.jsTemplate("default-icon") $>'); + user.setDescription('A new OnionrUI user'); + + user.remember(); + user.save(); + + setCurrentUser(user); + + window.location.reload(); + } else { + setCurrentUser(data); + } + }); } + +currentUser = getCurrentUser(); diff --git a/onionr/static-data/www/ui/src/js/timeline.js b/onionr/static-data/www/ui/src/js/timeline.js index e7a3c472..c411f41c 100644 --- a/onionr/static-data/www/ui/src/js/timeline.js +++ b/onionr/static-data/www/ui/src/js/timeline.js @@ -1,35 +1,3 @@ -function getUserInfo(id, callback) { - var user = deserializeUser(id); - if(user === null) { - Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { - if(data.length !== 0) { - try { - user = new User(); - - var userInfo = JSON.parse(data[0].getContent()); - - if(userInfo['id'] === id) { - user.setName(userInfo['name']); - user.setIcon(userInfo['icon']); - user.setID(id); - - serializeUser(user); - - return callback(user); - } - } catch(e) { - console.log(e); - return callback(null); - } - } else { - return callback(null); - } - }); - } else { - return callback(user); - } -} - /* just for testing rn */ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, function(data) { for(var i = 0; i < data.length; i++) { @@ -37,7 +5,7 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun var block = data[i]; var finished = false; - getUserInfo(new String(block.getHeader('signer', 'unknown')), function(user) { + User.getUser(new String(block.getHeader('signer', 'unknown')), function(user) { var post = new Post(); var blockContent = JSON.parse(block.getContent()); @@ -53,6 +21,7 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun while(!finished); } catch(e) { + console.log('Troublemaker block: ' + data[i].getHash()); console.log(e); } } @@ -62,11 +31,101 @@ function viewProfile(id, name) { id = decodeURIComponent(id); document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name)); - getUserInfo(id, function(data) { + User.getUser(id, function(data) { if(data !== null) { document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(data.getName()); document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID()); document.getElementById("onionr-profile-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(data.getIcon()); + document.getElementById("onionr-profile-user-icon").b64 = Sanitize.html(data.getIcon()); } }); } + +function updateUser() { + toggleSaveButton(false); + + jQuery('#modal').modal('show'); + + var name = jQuery('#onionr-profile-username').text(); + var id = document.getElementById("onionr-profile-username").title; + var icon = document.getElementById("onionr-profile-user-icon").b64; + var description = 'todo'; + + var user = new User(); + + user.setName(name); + user.setID(id); + user.setIcon(icon); + user.setDescription(description); + + user.remember(); + user.save(); + + window.location.reload(); +} + +function cancelUpdate() { + toggleSaveButton(false); + + var name = jQuery('#onionr-profile-username').text(); + var id = document.getElementById("onionr-profile-username").title; + + viewProfile(id, name); +} + +function toggleSaveButton(show) { + document.getElementById("onionr-profile-save").style.display = (show ? 'block' : 'none'); + document.getElementById("onionr-profile-cancel").style.display = (show ? 'block' : 'none'); +} + +function makePost() { + var content = document.getElementById("onionr-post-creator-content").value; + + if(content.trim() !== '') { + var post = new Post(); + + post.setUser(getCurrentUser()); + post.setContent(content); + post.setPostDate(new Date()); + + post.save(function(data) {}); // async, but no function + + document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML; + + document.getElementById("onionr-post-creator-content").value = ""; + } else { + console.log('Not making empty post.'); + } +} + +$('body').on('click', '[data-editable]', function() { + var el = jQuery(this); + var txt = el.text(); + + var input = jQuery('').val(txt); + el.replaceWith(input); + + var save = function() { + var newTxt = input.val(); + var p = el.text(Sanitize.username(newTxt)); + input.replaceWith(p); + + if(newTxt !== txt) + toggleSaveButton(true); + }; + + input.one('blur', save).focus(); +}); + +document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName()); +document.getElementById("onionr-post-creator-user-id").innerHTML = "<$= LANG.POST_CREATOR_YOU $>"; +document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); +document.getElementById("onionr-post-creator-user-id").title = currentUser.getID(); +document.getElementById("onionr-post-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>"; + +viewCurrentProfile = function() { + viewProfile(encodeURIComponent(currentUser.getID()), encodeURIComponent(currentUser.getName())); +} + +document.getElementById("onionr-post-creator-user-id").onclick = viewCurrentProfile; +document.getElementById("onionr-post-creator-user-name").onclick = viewCurrentProfile; From da3e0fdc4ec3ddf53f660839fa1352d00953f210 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 3 Sep 2018 22:28:56 -0500 Subject: [PATCH 017/101] better catch tkinter import --- onionr/static-data/default-plugins/boards/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/static-data/default-plugins/boards/main.py b/onionr/static-data/default-plugins/boards/main.py index 61d66378..3f00098a 100644 --- a/onionr/static-data/default-plugins/boards/main.py +++ b/onionr/static-data/default-plugins/boards/main.py @@ -23,7 +23,7 @@ import logger, config, sys from onionrblockapi import Block try: import tkinter -except (ModuleNotFoundError, ImportError) as e: +except (ModuleNotFoundError, ImportError, NameError) as e: TK_ENABLED = False else: TK_ENABLED = True From 0050b60f1a4ac951eb25aff0e8ba15b9d30da541 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 3 Sep 2018 22:30:15 -0500 Subject: [PATCH 018/101] better catch tkinter import --- onionr/static-data/default-plugins/boards/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/static-data/default-plugins/boards/main.py b/onionr/static-data/default-plugins/boards/main.py index 3f00098a..1b8b71b2 100644 --- a/onionr/static-data/default-plugins/boards/main.py +++ b/onionr/static-data/default-plugins/boards/main.py @@ -23,7 +23,7 @@ import logger, config, sys from onionrblockapi import Block try: import tkinter -except (ModuleNotFoundError, ImportError, NameError) as e: +except (ImportError, NameError) as e: TK_ENABLED = False else: TK_ENABLED = True From c1d4040807a11aa2fab6b0b668fa2c76e242f49e Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 3 Sep 2018 22:38:08 -0500 Subject: [PATCH 019/101] better catch tkinter import --- onionr/static-data/default-plugins/boards/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionr/static-data/default-plugins/boards/main.py b/onionr/static-data/default-plugins/boards/main.py index 1b8b71b2..573e6237 100644 --- a/onionr/static-data/default-plugins/boards/main.py +++ b/onionr/static-data/default-plugins/boards/main.py @@ -37,7 +37,8 @@ class OnionrBoards: self.api = apiInst self.myCore = apiInst.get_core() - self.gui = tkinter.Tk() + if TK_ENABLED: + self.gui = tkinter.Tk() return From cf37823fd7747c1d3acae3c1a2866196e0234060 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 4 Sep 2018 13:56:05 -0500 Subject: [PATCH 020/101] removed board plugin for now, added getfile command --- RUN-LINUX.sh | 1 + onionr/onionr.py | 29 ++++++++- onionr/onionrblockapi.py | 1 + onionr/onionrusers.py | 2 +- .../default-plugins/boards/info.json | 5 -- .../default-plugins/boards/main.py | 63 ------------------- 6 files changed, 30 insertions(+), 71 deletions(-) delete mode 100644 onionr/static-data/default-plugins/boards/info.json delete mode 100644 onionr/static-data/default-plugins/boards/main.py diff --git a/RUN-LINUX.sh b/RUN-LINUX.sh index 8f9a4b37..286a0f7f 100755 --- a/RUN-LINUX.sh +++ b/RUN-LINUX.sh @@ -1,3 +1,4 @@ #!/bin/sh +cd "$(dirname "$0")" cd onionr/ ./onionr.py "$@" diff --git a/onionr/onionr.py b/onionr/onionr.py index 21b3bd20..c8bb2873 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -50,6 +50,7 @@ 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. ''' + self.userRunDir = os.getcwd() # Directory user runs the program from try: os.chdir(sys.path[0]) except FileNotFoundError: @@ -190,6 +191,10 @@ class Onionr: 'add-file': self.addFile, 'addfile': self.addFile, + + 'get-file': self.getFile, + 'getfile': self.getFile, + 'listconn': self.listConn, 'import-blocks': self.onionrUtils.importNewBlocks, @@ -230,6 +235,7 @@ class Onionr: 'add-peer': 'Adds a peer to database', 'list-peers': 'Displays a list of peers', 'add-file': 'Create an Onionr block from a file', + 'get-file': 'Get a file from Onionr blocks', 'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)', 'listconn': 'list connected peers', 'kex': 'exchange keys with peers (done automatically)', @@ -780,6 +786,24 @@ class Onionr: return columns + def getFile(self): + ''' + Get a file from onionr blocks + ''' + if len(sys.argv) >= 3: + fileName = sys.argv[2] + print(fileName) + contents = None + bHash = sys.argv[3] + if os.path.exists(fileName): + logger.error("File already exists") + return + if not self.onionrUtils.validateHash(bHash): + logger.error('Block hash is invalid') + return + Block.mergeChain(bHash, fileName) + return + def addFile(self): ''' Adds a file to the onionr network @@ -790,8 +814,9 @@ class Onionr: contents = None if not os.path.exists(filename): - logger.warn('That file does not exist. Improper path?') - + logger.error('That file does not exist. Improper path (specify full path)?') + return + logger.info('Adding file... this might take a long time.') try: blockhash = Block.createChain(file = filename) logger.info('File %s saved in block %s.' % (filename, blockhash)) diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index 97b34730..78d3e71e 100644 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -177,6 +177,7 @@ class Block: # signed data is jsonMeta + block content (no linebreak) self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent()) self.date = self.getCore().getBlockDate(self.getHash()) + self.claimedTime = self.getHeader('time', None) if not self.getDate() is None: self.date = datetime.datetime.fromtimestamp(self.getDate()) diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py index 29b9375b..7340fed3 100644 --- a/onionr/onionrusers.py +++ b/onionr/onionrusers.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import onionrblockapi, logger, onionrexceptions +import onionrblockapi, logger, onionrexceptions, json class OnionrUser: def __init__(self, coreInst, publicKey): self.trust = 0 diff --git a/onionr/static-data/default-plugins/boards/info.json b/onionr/static-data/default-plugins/boards/info.json deleted file mode 100644 index f3e80e1c..00000000 --- a/onionr/static-data/default-plugins/boards/info.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name" : "boards", - "version" : "1.0", - "author" : "onionr" -} diff --git a/onionr/static-data/default-plugins/boards/main.py b/onionr/static-data/default-plugins/boards/main.py deleted file mode 100644 index 573e6237..00000000 --- a/onionr/static-data/default-plugins/boards/main.py +++ /dev/null @@ -1,63 +0,0 @@ -''' - Onionr - P2P Anonymous Storage Network - - This is an interactive menu-driven CLI interface for Onionr -''' -''' - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -''' - -# Imports some useful libraries -import logger, config, sys -from onionrblockapi import Block -try: - import tkinter -except (ImportError, NameError) as e: - TK_ENABLED = False -else: - TK_ENABLED = True - - -plugin_name = 'cliui' -PLUGIN_VERSION = '0.0.1' - -class OnionrBoards: - def __init__(self, apiInst): - self.api = apiInst - self.myCore = apiInst.get_core() - - if TK_ENABLED: - self.gui = tkinter.Tk() - - return - - def start(self): - return - -def on_init(api, data = None): - ''' - This event is called after Onionr is initialized, but before the command - inputted is executed. Could be called when daemon is starting or when - just the client is running. - ''' - - # Doing this makes it so that the other functions can access the api object - # by simply referencing the variable `pluginapi`. - pluginapi = api - ui = OnionrBoards(api) - api.commands.register('boards', ui.start) - api.commands.register_help('boards', 'Open the board viewer') - - - return From 67be0bebc2fccb47dbf9d4f867898ae205b693b9 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 4 Sep 2018 23:06:17 -0500 Subject: [PATCH 021/101] added tor control and stem --- onionr/netcontroller.py | 35 +++++++++++++++---- .../static-data/default-plugins/cliui/main.py | 1 + requirements.txt | 1 + 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 3749ce7a..28214b95 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -18,7 +18,8 @@ along with this program. If not, see . ''' -import subprocess, os, random, sys, logger, time, signal, config +import subprocess, os, random, sys, logger, time, signal, config, base64 +from stem.control import Controller from onionrblockapi import Block class NetController: @@ -33,6 +34,14 @@ class NetController: self.hsPort = hsPort self._torInstnace = '' self.myID = '' + + if os.path.exists('./tor'): + self.torBinary = './tor' + elif os.path.exists('/usr/bin/tor'): + self.torBinary = '/usr/bin/tor' + else: + self.torBinary = 'tor' + config.reload() ''' if os.path.exists(self.torConfigLocation): @@ -52,13 +61,27 @@ class NetController: if config.get('tor.v3onions'): hsVer = 'HiddenServiceVersion 3' logger.info('Using v3 onions :)') + if os.path.exists(self.torConfigLocation): os.remove(self.torConfigLocation) + + # Set the Tor control password. Meant to make it harder to manipulate our Tor instance + plaintext = base64.b64encode(os.urandom(50)).decode() + config.set('tor.controlpassword', plaintext, savefile=True) + + hashedPassword = subprocess.Popen([self.torBinary, '--hash-password', plaintext], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + for line in iter(hashedPassword.stdout.readline, b''): + password = line.decode() + break + torrcData = '''SocksPort ''' + str(self.socksPort) + ''' HiddenServiceDir data/hs/ \n''' + hsVer + '''\n HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' DataDirectory data/tordata/ +CookieAuthentication 1 +ControlPort 9051 +HashedControlPassword ''' + str(password) + ''' ''' torrc = open(self.torConfigLocation, 'w') torrc.write(torrcData) @@ -74,20 +97,20 @@ DataDirectory data/tordata/ self.generateTorrc() if os.path.exists('./tor'): - torBinary = './tor' + self.torBinary = './tor' elif os.path.exists('/usr/bin/tor'): - torBinary = '/usr/bin/tor' + self.torBinary = '/usr/bin/tor' else: - torBinary = 'tor' + self.torBinary = 'tor' try: - tor = subprocess.Popen([torBinary, '-f', self.torConfigLocation], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + tor = subprocess.Popen([self.torBinary, '-f', self.torConfigLocation], stdout=subprocess.PIPE, stderr=subprocess.PIPE) except FileNotFoundError: 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) + torVersion = subprocess.Popen([self.torBinary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) for line in iter(torVersion.stdout.readline, b''): if 'Tor 0.2.' in line.decode(): logger.warn("Running 0.2.x Tor series, no support for v3 onion peers") diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index 4095f814..e56f39df 100644 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -46,6 +46,7 @@ class OnionrCLIUI: showMenu = True isOnline = "No" firstRun = True + choice = '' if self.myCore._utils.localCommand('ping') == 'pong': firstRun = False diff --git a/requirements.txt b/requirements.txt index 97f8969e..69322e22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ defusedxml==0.5.0 simple_crypt==4.1.7 Flask==1.0.2 PySocks==1.6.8 +stem==1.6.0 From 516d965ad359f9df9c843174d695fd52337a9137 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 6 Sep 2018 21:37:39 -0700 Subject: [PATCH 022/101] Add work --- onionr/static-data/www/ui/dist/js/timeline.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/onionr/static-data/www/ui/dist/js/timeline.js b/onionr/static-data/www/ui/dist/js/timeline.js index af554f80..73e46559 100644 --- a/onionr/static-data/www/ui/dist/js/timeline.js +++ b/onionr/static-data/www/ui/dist/js/timeline.js @@ -81,17 +81,21 @@ function toggleSaveButton(show) { function makePost() { var content = document.getElementById("onionr-post-creator-content").value; - var post = new Post(); + if(content.trim() !== '') { + var post = new Post(); - post.setUser(getCurrentUser()); - post.setContent(content); - post.setPostDate(new Date()); + post.setUser(getCurrentUser()); + post.setContent(content); + post.setPostDate(new Date()); - post.save(function(data) {}); // async, but no function + post.save(function(data) {}); // async, but no function - document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML; + document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML; - document.getElementById("onionr-post-creator-content").value = ""; + document.getElementById("onionr-post-creator-content").value = ""; + } else { + console.log('Not making empty post.'); + } } $('body').on('click', '[data-editable]', function() { From f10e077896b09ec034fcbb5b8605f01705614e4b Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 6 Sep 2018 21:46:56 -0700 Subject: [PATCH 023/101] Make console output less verbose for api --- onionr/api.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 0f3bde41..a36e6001 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -59,14 +59,9 @@ class API: } for mimetype in mimetypes: - logger.debug(path + ' endswith .' + mimetype + '?') if path.endswith('.%s' % mimetype): - logger.debug('- True!') return mimetypes[mimetype] - else: - logger.debug('- no') - logger.debug('%s not in %s' % (path, mimetypes)) return 'text/plain' def __init__(self, debug): @@ -203,11 +198,11 @@ class API: time.sleep(self._privateDelayTime - elapsed) ''' - logger.debug('Serving %s' % path) - self.mimeType = API.guessMime(path) self.overrideCSP = True + logger.debug('Serving %s (mime: %s)' % (path, self.mimeType)) + return send_from_directory('static-data/www/ui/dist/', path, mimetype = API.guessMime(path)) @app.route('/client/') From 7ec869a36f02c9a42daa9e2140ef9cae8946cd92 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 6 Sep 2018 21:57:10 -0700 Subject: [PATCH 024/101] Increment version number --- readme.md => README.md | 6 +++--- onionr/onionr.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename readme.md => README.md (96%) diff --git a/readme.md b/README.md similarity index 96% rename from readme.md rename to README.md index 7486bcac..a01a65b2 100644 --- a/readme.md +++ b/README.md @@ -1,12 +1,12 @@ ![Onionr logo](./docs/onionr-logo.png) +v0.3.0 (***experimental, not safe or easy to use yet***) + [![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/) Anonymous P2P platform, using Tor & I2P. -***Experimental, not safe or easy to use yet*** -
**The main repo for this software is at https://gitlab.com/beardog/Onionr/** @@ -45,4 +45,4 @@ Bitcoin/Bitcoin Cash: 1onion55FXzm6h8KQw3zFw2igpHcV7LPq 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. \ No newline at end of file +The badges (besides travis-ci build) are by Maik Ellerbrock is licensed under a Creative Commons Attribution 4.0 International License. diff --git a/onionr/onionr.py b/onionr/onionr.py index 21b3bd20..8530b038 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -40,7 +40,7 @@ except ImportError: raise Exception("You need the PySocks module (for use with socks5 proxy to use Tor)") ONIONR_TAGLINE = 'Anonymous P2P Platform - GPLv3 - https://Onionr.VoidNet.Tech' -ONIONR_VERSION = '0.2.0' # for debugging and stuff +ONIONR_VERSION = '0.3.0' # for debugging and stuff ONIONR_VERSION_TUPLE = tuple(ONIONR_VERSION.split('.')) # (MAJOR, MINOR, VERSION) API_VERSION = '4' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes know how to communicate without learning too much information about you. @@ -146,7 +146,7 @@ class Onionr: config.set('client.participate', True, savefile=True) if type(config.get('client.api_version')) is type(None): config.set('client.api_version', API_VERSION, savefile=True) - + self.cmds = { '': self.showHelpSuggestion, @@ -263,7 +263,7 @@ class Onionr: def getCommands(self): return self.cmds - + def friendCmd(self): '''List, add, or remove friend(s) Changes their peer DB entry. From a44d511e1da06cc81ea0dafb93d16e0ef4d7b6ba Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 7 Sep 2018 13:57:20 -0500 Subject: [PATCH 025/101] bind to random control port --- onionr/netcontroller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 28214b95..74430bee 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -21,7 +21,7 @@ import subprocess, os, random, sys, logger, time, signal, config, base64 from stem.control import Controller from onionrblockapi import Block - +from dependencies import secrets class NetController: ''' This class handles hidden service setup on Tor and I2P @@ -69,6 +69,8 @@ class NetController: plaintext = base64.b64encode(os.urandom(50)).decode() config.set('tor.controlpassword', plaintext, savefile=True) + controlPort = random.randint(1025, 65535) + hashedPassword = subprocess.Popen([self.torBinary, '--hash-password', plaintext], stdout=subprocess.PIPE, stderr=subprocess.PIPE) for line in iter(hashedPassword.stdout.readline, b''): password = line.decode() @@ -80,7 +82,7 @@ HiddenServiceDir data/hs/ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' DataDirectory data/tordata/ CookieAuthentication 1 -ControlPort 9051 +ControlPort ''' + str(controlPort) + ''' HashedControlPassword ''' + str(password) + ''' ''' torrc = open(self.torConfigLocation, 'w') From 9d4675770bdb78da3ebba5c6842dfcb60ebd6dff Mon Sep 17 00:00:00 2001 From: Arinerron Date: Fri, 7 Sep 2018 22:47:56 -0700 Subject: [PATCH 026/101] Set a maximum character limit --- .../www/ui/common/onionr-timeline-post.html | 4 +- onionr/static-data/www/ui/compile.py | 14 ++--- onionr/static-data/www/ui/dist/index.html | 10 +++- onionr/static-data/www/ui/dist/js/main.js | 28 ++++++--- onionr/static-data/www/ui/dist/js/timeline.js | 57 +++++++++++++++++-- onionr/static-data/www/ui/lang.json | 4 ++ onionr/static-data/www/ui/src/index.html | 12 +++- onionr/static-data/www/ui/src/js/main.js | 24 ++++++-- onionr/static-data/www/ui/src/js/timeline.js | 57 +++++++++++++++++-- 9 files changed, 178 insertions(+), 32 deletions(-) diff --git a/onionr/static-data/www/ui/common/onionr-timeline-post.html b/onionr/static-data/www/ui/common/onionr-timeline-post.html index 68440a01..05259139 100644 --- a/onionr/static-data/www/ui/common/onionr-timeline-post.html +++ b/onionr/static-data/www/ui/common/onionr-timeline-post.html @@ -22,8 +22,8 @@
- <$= LANG.POST_LIKE $> - <$= LANG.POST_REPLY $> + <$= LANG.POST_LIKE $> + <$= LANG.POST_REPLY $>
diff --git a/onionr/static-data/www/ui/compile.py b/onionr/static-data/www/ui/compile.py index 2667b210..e991af08 100755 --- a/onionr/static-data/www/ui/compile.py +++ b/onionr/static-data/www/ui/compile.py @@ -41,16 +41,16 @@ LANG = type('LANG', (), langmap) # templating class Template: - def jsTemplate(template): + def jsTemplate(template, filename = ''): with open('common/%s.html' % template, 'r') as file: - return Template.parseTags(file.read().replace('\\', '\\\\').replace('\'', '\\\'').replace('\n', "\\\n")) + return Template.parseTags(file.read().replace('\\', '\\\\').replace('\'', '\\\'').replace('\n', "\\\n"), filename) - def htmlTemplate(template): + def htmlTemplate(template, filename = ''): with open('common/%s.html' % template, 'r') as file: - return Template.parseTags(file.read()) + return Template.parseTags(file.read(), filename) # tag parser - def parseTags(contents): + def parseTags(contents, filename = ''): # <$ logic $> for match in re.findall(r'(<\$(?!=)(.*?)\$>)', contents): try: @@ -66,7 +66,7 @@ class Template: try: out = eval(match[1].strip()) contents = contents.replace(match[0], '' if out is None else str(out)) - except NameError as e: + except (NameError, AttributeError) as e: name = match[1].strip() print('Warning: %s does not exist, treating as an str' % name) contents = contents.replace(match[0], name) @@ -118,7 +118,7 @@ def iterate(directory): # do python tags if settings['python_tags']: - contents = Template.parseTags(contents) + contents = Template.parseTags(contents, filename) # write file file.write(contents) diff --git a/onionr/static-data/www/ui/dist/index.html b/onionr/static-data/www/ui/dist/index.html index ba0c31d4..756f8107 100644 --- a/onionr/static-data/www/ui/dist/index.html +++ b/onionr/static-data/www/ui/dist/index.html @@ -59,6 +59,12 @@
+
+
+

Timeline

+
+
+
@@ -74,7 +80,9 @@
- + + +
diff --git a/onionr/static-data/www/ui/dist/js/main.js b/onionr/static-data/www/ui/dist/js/main.js index 71c80266..561dafb2 100644 --- a/onionr/static-data/www/ui/dist/js/main.js +++ b/onionr/static-data/www/ui/dist/js/main.js @@ -115,10 +115,10 @@ function timeSince(date, size) { } /* replace all instances of string */ -String.prototype.replaceAll = function(search, replacement) { +String.prototype.replaceAll = function(search, replacement, limit) { // taken from https://stackoverflow.com/a/17606289/3678023 var target = this; - return target.split(search).join(replacement); + return target.split(search, limit).join(replacement); }; /* useful functions to sanitize data */ @@ -287,8 +287,8 @@ class Post {
\ \
\ - like\ - reply\ + like\ + reply\
\ \ \ @@ -308,7 +308,8 @@ class Post { postTemplate = postTemplate.replaceAll('$user-id', Sanitize.html(this.getUser().getID())); postTemplate = postTemplate.replaceAll('$user-image', "data:image/jpeg;base64," + Sanitize.html(this.getUser().getIcon())); - postTemplate = postTemplate.replaceAll('$content', Sanitize.html(this.getContent())); + postTemplate = postTemplate.replaceAll('$content', Sanitize.html(this.getContent()).replaceAll('\n', '
', 16)); // Maximum of 16 lines + postTemplate = postTemplate.replaceAll('$post-hash', this.getHash()); postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : '')); postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString()); @@ -342,6 +343,14 @@ class Post { return this.date; } + setHash(hash) { + this.hash = hash; + } + + getHash() { + return this.hash; + } + save(callback) { var args = {'type' : 'onionr-post', 'sign' : true, 'content' : JSON.stringify({'content' : this.getContent()})}; @@ -354,8 +363,11 @@ class Post { if(callback !== undefined) { // async + var thisObject = this; + http.addEventListener('load', function() { - callback(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + thisObject.setHash(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + callback(thisObject.getHash()); }, false); http.open('GET', url, true); @@ -367,7 +379,9 @@ class Post { http.open('GET', url, false); http.send(null); - return Block.parseBlockArray(JSON.parse(http.responseText)['hash']); + this.setHash(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + + return this.getHash(); } } } diff --git a/onionr/static-data/www/ui/dist/js/timeline.js b/onionr/static-data/www/ui/dist/js/timeline.js index 73e46559..e77b3b47 100644 --- a/onionr/static-data/www/ui/dist/js/timeline.js +++ b/onionr/static-data/www/ui/dist/js/timeline.js @@ -10,11 +10,16 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun var blockContent = JSON.parse(block.getContent()); - post.setContent(blockContent['content']); - post.setPostDate(block.getDate()); - post.setUser(user); + // just ignore anything shorter than 280 characters + if(String(blockContent['content']).length <= 280) { + post.setContent(blockContent['content']); + post.setPostDate(block.getDate()); + post.setUser(user); - document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML(); + post.setHash(block.getHash()); + + document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML(); + } finished = true; }); @@ -27,6 +32,47 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun } }); +function postCreatorChange() { + var content = document.getElementById('onionr-post-creator-content').value; + var message = ''; + + var disable = true; + + if(content.length !== 0) { + if(content.length - content.replaceAll('\n', '').length > 16) { + // 16 max newlines + message = 'Please use less than 16 newlines'; + } else if(content.length <= 280) { + // 280 max characters + message = '%s characters remaining'.replaceAll('%s', (280 - content.length)); + disable = false; + } else { + message = '%s characters over maximum'.replaceAll('%s', (content.length - 280)); + } + } + + var element = document.getElementById('onionr-post-creator-content-message'); + var button = document.getElementById("onionr-post-creator-create"); + + if(message === '') + element.style.display = 'none'; + else { + element.style.display = 'block'; + + element.innerHTML = message; + + if(disable) + element.style.color = 'red'; + else + element.style.color = 'gray'; + } + + if(disable) + button.disabled = true; + else + button.disabled = false; +} + function viewProfile(id, name) { id = decodeURIComponent(id); document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name)); @@ -129,3 +175,6 @@ viewCurrentProfile = function() { document.getElementById("onionr-post-creator-user-id").onclick = viewCurrentProfile; document.getElementById("onionr-post-creator-user-name").onclick = viewCurrentProfile; + +// on some browsers it saves the user input on reload. So, it should also recheck the input. +postCreatorChange(); diff --git a/onionr/static-data/www/ui/lang.json b/onionr/static-data/www/ui/lang.json index 872a68cf..06e02194 100644 --- a/onionr/static-data/www/ui/lang.json +++ b/onionr/static-data/www/ui/lang.json @@ -19,6 +19,10 @@ "POST_CREATOR_PLACEHOLDER" : "Enter a message here...", "POST_CREATOR_CREATE" : "Create post", + "POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES" : "Please use less than 16 newlines", + "POST_CREATOR_MESSAGE_REMAINING" : "%s characters remaining", + "POST_CREATOR_MESSAGE_OVER" : "%s characters over maximum", + "PROFILE_EDIT_SAVE" : "Save", "PROFILE_EDIT_CANCEL" : "Cancel" }, diff --git a/onionr/static-data/www/ui/src/index.html b/onionr/static-data/www/ui/src/index.html index 99463d27..d64a35d0 100644 --- a/onionr/static-data/www/ui/src/index.html +++ b/onionr/static-data/www/ui/src/index.html @@ -29,6 +29,12 @@
+
+
+

<$= LANG.TIMELINE $>

+
+
+
@@ -40,11 +46,13 @@ - + + +
diff --git a/onionr/static-data/www/ui/src/js/main.js b/onionr/static-data/www/ui/src/js/main.js index ca2c5214..808809ba 100644 --- a/onionr/static-data/www/ui/src/js/main.js +++ b/onionr/static-data/www/ui/src/js/main.js @@ -115,10 +115,10 @@ function timeSince(date, size) { } /* replace all instances of string */ -String.prototype.replaceAll = function(search, replacement) { +String.prototype.replaceAll = function(search, replacement, limit) { // taken from https://stackoverflow.com/a/17606289/3678023 var target = this; - return target.split(search).join(replacement); + return target.split(search, limit).join(replacement); }; /* useful functions to sanitize data */ @@ -276,7 +276,8 @@ class Post { postTemplate = postTemplate.replaceAll('$user-id', Sanitize.html(this.getUser().getID())); postTemplate = postTemplate.replaceAll('$user-image', "data:image/jpeg;base64," + Sanitize.html(this.getUser().getIcon())); - postTemplate = postTemplate.replaceAll('$content', Sanitize.html(this.getContent())); + postTemplate = postTemplate.replaceAll('$content', Sanitize.html(this.getContent()).replaceAll('\n', '
', 16)); // Maximum of 16 lines + postTemplate = postTemplate.replaceAll('$post-hash', this.getHash()); postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : '')); postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString()); @@ -310,6 +311,14 @@ class Post { return this.date; } + setHash(hash) { + this.hash = hash; + } + + getHash() { + return this.hash; + } + save(callback) { var args = {'type' : 'onionr-post', 'sign' : true, 'content' : JSON.stringify({'content' : this.getContent()})}; @@ -322,8 +331,11 @@ class Post { if(callback !== undefined) { // async + var thisObject = this; + http.addEventListener('load', function() { - callback(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + thisObject.setHash(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + callback(thisObject.getHash()); }, false); http.open('GET', url, true); @@ -335,7 +347,9 @@ class Post { http.open('GET', url, false); http.send(null); - return Block.parseBlockArray(JSON.parse(http.responseText)['hash']); + this.setHash(Block.parseBlockArray(JSON.parse(http.responseText)['hash'])); + + return this.getHash(); } } } diff --git a/onionr/static-data/www/ui/src/js/timeline.js b/onionr/static-data/www/ui/src/js/timeline.js index c411f41c..0aea4f28 100644 --- a/onionr/static-data/www/ui/src/js/timeline.js +++ b/onionr/static-data/www/ui/src/js/timeline.js @@ -10,11 +10,16 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun var blockContent = JSON.parse(block.getContent()); - post.setContent(blockContent['content']); - post.setPostDate(block.getDate()); - post.setUser(user); + // just ignore anything shorter than 280 characters + if(String(blockContent['content']).length <= 280) { + post.setContent(blockContent['content']); + post.setPostDate(block.getDate()); + post.setUser(user); - document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML(); + post.setHash(block.getHash()); + + document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML(); + } finished = true; }); @@ -27,6 +32,47 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun } }); +function postCreatorChange() { + var content = document.getElementById('onionr-post-creator-content').value; + var message = ''; + + var disable = true; + + if(content.length !== 0) { + if(content.length - content.replaceAll('\n', '').length > 16) { + // 16 max newlines + message = '<$= LANG.POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES $>'; + } else if(content.length <= 280) { + // 280 max characters + message = '<$= LANG.POST_CREATOR_MESSAGE_REMAINING $>'.replaceAll('%s', (280 - content.length)); + disable = false; + } else { + message = '<$= LANG.POST_CREATOR_MESSAGE_OVER $>'.replaceAll('%s', (content.length - 280)); + } + } + + var element = document.getElementById('onionr-post-creator-content-message'); + var button = document.getElementById("onionr-post-creator-create"); + + if(message === '') + element.style.display = 'none'; + else { + element.style.display = 'block'; + + element.innerHTML = message; + + if(disable) + element.style.color = 'red'; + else + element.style.color = 'gray'; + } + + if(disable) + button.disabled = true; + else + button.disabled = false; +} + function viewProfile(id, name) { id = decodeURIComponent(id); document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name)); @@ -129,3 +175,6 @@ viewCurrentProfile = function() { document.getElementById("onionr-post-creator-user-id").onclick = viewCurrentProfile; document.getElementById("onionr-post-creator-user-name").onclick = viewCurrentProfile; + +// on some browsers it saves the user input on reload. So, it should also recheck the input. +postCreatorChange(); From 0f91bf7018548d2b5cffc9555f60c591e4034d49 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Fri, 7 Sep 2018 23:45:33 -0700 Subject: [PATCH 027/101] Add liking feature --- onionr/api.py | 12 +++++++- .../www/ui/common/onionr-timeline-post.html | 2 +- onionr/static-data/www/ui/dist/js/main.js | 19 +++++++++++- onionr/static-data/www/ui/dist/js/timeline.js | 29 +++++++++++++++++-- onionr/static-data/www/ui/lang.json | 1 + onionr/static-data/www/ui/src/js/main.js | 17 +++++++++++ onionr/static-data/www/ui/src/js/timeline.js | 29 +++++++++++++++++-- 7 files changed, 102 insertions(+), 7 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 625e083c..761bb0a0 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -234,6 +234,16 @@ class API: resp = Response('Goodbye') elif action == 'ping': resp = Response('pong') + elif action == 'site': + block = data + siteData = self._core.getData(data) + response = 'not found' + if siteData != '' and siteData != False: + self.mimeType = 'text/html' + response = siteData.split(b'-', 2)[-1] + resp = Response(response) + elif action == 'info': + resp = Response(json.dumps({'pubkey' : self._core._crypto.pubKey, 'host' : self._core.hsAddress})) elif action == "insertBlock": response = {'success' : False, 'reason' : 'An unknown error occurred'} @@ -431,7 +441,7 @@ class API: else: logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash) resp = Response(resp) - return resp + return resp @app.route('/public/') def public_handler(): diff --git a/onionr/static-data/www/ui/common/onionr-timeline-post.html b/onionr/static-data/www/ui/common/onionr-timeline-post.html index 05259139..a6873abe 100644 --- a/onionr/static-data/www/ui/common/onionr-timeline-post.html +++ b/onionr/static-data/www/ui/common/onionr-timeline-post.html @@ -22,7 +22,7 @@
- <$= LANG.POST_LIKE $> + $liked <$= LANG.POST_REPLY $>
diff --git a/onionr/static-data/www/ui/dist/js/main.js b/onionr/static-data/www/ui/dist/js/main.js index 561dafb2..864b146a 100644 --- a/onionr/static-data/www/ui/dist/js/main.js +++ b/onionr/static-data/www/ui/dist/js/main.js @@ -25,11 +25,22 @@ function getParameter(name) { /* usermap localStorage stuff */ var usermap = JSON.parse(get('usermap', '{}')); +var postmap = JSON.parse(get('postmap', '{}')) function getUserMap() { return usermap; } +function getPostMap(hash) { + if(hash !== undefined) { + if(hash in postmap) + return postmap[hash]; + return null; + } + + return postmap; +} + function deserializeUser(id) { if(!(id in getUserMap())) return null; @@ -287,7 +298,7 @@ class Post {
\ \
\ - like\ + $liked\ reply\
\ \ @@ -313,6 +324,12 @@ class Post { postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : '')); postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString()); + if(this.getHash() in getPostMap() && getPostMap()[this.getHash()]['liked']) { + postTemplate = postTemplate.replaceAll('$liked', 'unlike'); + } else { + postTemplate = postTemplate.replaceAll('$liked', 'like'); + } + return postTemplate; } diff --git a/onionr/static-data/www/ui/dist/js/timeline.js b/onionr/static-data/www/ui/dist/js/timeline.js index e77b3b47..7e90d962 100644 --- a/onionr/static-data/www/ui/dist/js/timeline.js +++ b/onionr/static-data/www/ui/dist/js/timeline.js @@ -32,6 +32,28 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun } }); +function toggleLike(hash) { + var post = getPostMap(hash); + if(post === null || !getPostMap()[hash]['liked']) { + console.log('Liking ' + hash + '...'); + + if(post === null) + getPostMap()[hash] = {}; + + getPostMap()[hash]['liked'] = true; + + set('postmap', JSON.stringify(getPostMap())); + + var block = new Block(); + + block.setType('onionr-post-like'); + block.setContent(JSON.stringify({'hash' : hash})); + block.save(true, function(hash) {}); + } else { + console.log('Unliking ' + hash + '...'); + } +} + function postCreatorChange() { var content = document.getElementById('onionr-post-creator-content').value; var message = ''; @@ -55,9 +77,9 @@ function postCreatorChange() { var button = document.getElementById("onionr-post-creator-create"); if(message === '') - element.style.display = 'none'; + element.style.visibility = 'hidden'; else { - element.style.display = 'block'; + element.style.visibility = 'visible'; element.innerHTML = message; @@ -139,6 +161,7 @@ function makePost() { document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML; document.getElementById("onionr-post-creator-content").value = ""; + postCreatorChange(); } else { console.log('Not making empty post.'); } @@ -163,6 +186,8 @@ $('body').on('click', '[data-editable]', function() { input.one('blur', save).focus(); }); +currentUser = getCurrentUser(); + document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName()); document.getElementById("onionr-post-creator-user-id").innerHTML = "you"; document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); diff --git a/onionr/static-data/www/ui/lang.json b/onionr/static-data/www/ui/lang.json index 06e02194..5e1f0790 100644 --- a/onionr/static-data/www/ui/lang.json +++ b/onionr/static-data/www/ui/lang.json @@ -13,6 +13,7 @@ "MODAL_MESSAGE" : "Onionr has begun performing a CPU-intensive operation. If this operation does not complete in the next 10 seconds, try reloading the page.", "POST_LIKE" : "like", + "POST_UNLIKE" : "unlike", "POST_REPLY" : "reply", "POST_CREATOR_YOU" : "you", diff --git a/onionr/static-data/www/ui/src/js/main.js b/onionr/static-data/www/ui/src/js/main.js index 808809ba..a69b31ea 100644 --- a/onionr/static-data/www/ui/src/js/main.js +++ b/onionr/static-data/www/ui/src/js/main.js @@ -25,11 +25,22 @@ function getParameter(name) { /* usermap localStorage stuff */ var usermap = JSON.parse(get('usermap', '{}')); +var postmap = JSON.parse(get('postmap', '{}')) function getUserMap() { return usermap; } +function getPostMap(hash) { + if(hash !== undefined) { + if(hash in postmap) + return postmap[hash]; + return null; + } + + return postmap; +} + function deserializeUser(id) { if(!(id in getUserMap())) return null; @@ -281,6 +292,12 @@ class Post { postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : '')); postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString()); + if(this.getHash() in getPostMap() && getPostMap()[this.getHash()]['liked']) { + postTemplate = postTemplate.replaceAll('$liked', '<$= LANG.POST_UNLIKE $>'); + } else { + postTemplate = postTemplate.replaceAll('$liked', '<$= LANG.POST_LIKE $>'); + } + return postTemplate; } diff --git a/onionr/static-data/www/ui/src/js/timeline.js b/onionr/static-data/www/ui/src/js/timeline.js index 0aea4f28..9bcef854 100644 --- a/onionr/static-data/www/ui/src/js/timeline.js +++ b/onionr/static-data/www/ui/src/js/timeline.js @@ -32,6 +32,28 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun } }); +function toggleLike(hash) { + var post = getPostMap(hash); + if(post === null || !getPostMap()[hash]['liked']) { + console.log('Liking ' + hash + '...'); + + if(post === null) + getPostMap()[hash] = {}; + + getPostMap()[hash]['liked'] = true; + + set('postmap', JSON.stringify(getPostMap())); + + var block = new Block(); + + block.setType('onionr-post-like'); + block.setContent(JSON.stringify({'hash' : hash})); + block.save(true, function(hash) {}); + } else { + console.log('Unliking ' + hash + '...'); + } +} + function postCreatorChange() { var content = document.getElementById('onionr-post-creator-content').value; var message = ''; @@ -55,9 +77,9 @@ function postCreatorChange() { var button = document.getElementById("onionr-post-creator-create"); if(message === '') - element.style.display = 'none'; + element.style.visibility = 'hidden'; else { - element.style.display = 'block'; + element.style.visibility = 'visible'; element.innerHTML = message; @@ -139,6 +161,7 @@ function makePost() { document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML; document.getElementById("onionr-post-creator-content").value = ""; + postCreatorChange(); } else { console.log('Not making empty post.'); } @@ -163,6 +186,8 @@ $('body').on('click', '[data-editable]', function() { input.one('blur', save).focus(); }); +currentUser = getCurrentUser(); + document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName()); document.getElementById("onionr-post-creator-user-id").innerHTML = "<$= LANG.POST_CREATOR_YOU $>"; document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); From 151b12424cd33fb6788c5fe1dcc479414bf8e501 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 9 Sep 2018 00:12:41 -0500 Subject: [PATCH 028/101] work on block processing module --- onionr/core.py | 2 ++ onionr/netcontroller.py | 3 ++- onionr/onionrutils.py | 16 +++------------- onionr/static-data/default-plugins/cliui/main.py | 2 +- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index ab8b640b..97b822af 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -102,6 +102,8 @@ class Core: logger.warn("POW token for pubkey base64 representation exceeded 120 bytes, is " + str(sys.getsizeof(powID))) return False + events.event('pubkey_add', data = {'key': peerID}, onionr = None) + conn = sqlite3.connect(self.peerDB) hashID = self._crypto.pubKeyHashID(peerID) c = conn.cursor() diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 74430bee..b3691956 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -74,7 +74,8 @@ class NetController: hashedPassword = subprocess.Popen([self.torBinary, '--hash-password', plaintext], stdout=subprocess.PIPE, stderr=subprocess.PIPE) for line in iter(hashedPassword.stdout.readline, b''): password = line.decode() - break + if 'warn' not in password: + break torrcData = '''SocksPort ''' + str(self.socksPort) + ''' HiddenServiceDir data/hs/ diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index a383684c..470dc125 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -23,6 +23,7 @@ import nacl.signing, nacl.encoding from onionrblockapi import Block import onionrexceptions from defusedxml import minidom +import onionrevents import pgpwords, onionrusers, storagecounter if sys.version_info < (3, 6): try: @@ -276,19 +277,8 @@ class OnionrUtils: if len(blockType) <= 10: self._core.updateBlockInfo(blockHash, 'dataType', blockType) - if blockType == 'userInfo': - if myBlock.verifySig(): - peerName = myBlock.getMetadata('name') - try: - if len(peerName) > 20: - raise onionrexceptions.InvalidMetdata('Peer name specified is too large') - except TypeError: - pass - except onionrexceptions.InvalidMetadata: - pass - else: - self._core.setPeerInfo(signer, 'name', peerName) - logger.info('%s is now using the name %s.' % (signer, self.escapeAnsi(peerName))) + onionrevents.event('processBlocks', data = {'block': myBlock, 'type': blockType}, onionr = None) + except TypeError: pass diff --git a/onionr/static-data/default-plugins/cliui/main.py b/onionr/static-data/default-plugins/cliui/main.py index e56f39df..5fc24385 100644 --- a/onionr/static-data/default-plugins/cliui/main.py +++ b/onionr/static-data/default-plugins/cliui/main.py @@ -54,7 +54,7 @@ class OnionrCLIUI: while showMenu: if firstRun: print("please wait while Onionr starts...") - daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL) time.sleep(30) firstRun = False From ce2423e6d9ac70e04d99e9902ccd45fde933fd2d Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 10 Sep 2018 00:02:28 -0500 Subject: [PATCH 029/101] * moved metadata processor to its own module * improved some comments * removed defunct utils functions --- onionr/blockprocessor.py | 0 onionr/onionrsockets.py | 19 +++++++ onionr/onionrutils.py | 34 +++++------- .../metadataprocessor/info.json | 5 ++ .../default-plugins/metadataprocessor/main.py | 52 +++++++++++++++++++ 5 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 onionr/blockprocessor.py create mode 100644 onionr/onionrsockets.py create mode 100644 onionr/static-data/default-plugins/metadataprocessor/info.json create mode 100644 onionr/static-data/default-plugins/metadataprocessor/main.py diff --git a/onionr/blockprocessor.py b/onionr/blockprocessor.py new file mode 100644 index 00000000..e69de29b diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py new file mode 100644 index 00000000..df4a1ad7 --- /dev/null +++ b/onionr/onionrsockets.py @@ -0,0 +1,19 @@ +''' + Onionr - P2P Anonymous Storage Network + + Onionr Socket interface +''' +''' + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 470dc125..6449e152 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -37,17 +37,20 @@ class OnionrUtils: Various useful functions for validating things, etc functions, connectivity ''' def __init__(self, coreInstance): - self.fingerprintFile = 'data/own-fingerprint.txt' - self._core = coreInstance + #self.fingerprintFile = 'data/own-fingerprint.txt' #TODO Remove since probably not needed + self._core = coreInstance # onionr core instance - self.timingToken = '' + self.timingToken = '' # for when we make local connections to our http api, to bypass timing attack defense mechanism self.avoidDupe = [] # list used to prevent duplicate requests per peer for certain actions self.peerProcessing = {} # dict of current peer actions: peer, actionList - self.storageCounter = storagecounter.StorageCounter(self._core) - config.reload() + self.storageCounter = storagecounter.StorageCounter(self._core) # used to keep track of how much data onionr is using on disk + config.reload() # onionr config return def getTimeBypassToken(self): + ''' + Load our timingToken from disk for faster local HTTP API + ''' try: if os.path.exists('data/time-bypass.txt'): with open('data/time-bypass.txt', 'r') as bypass: @@ -64,22 +67,6 @@ class OnionrUtils: epoch = self.getEpoch() return epoch - (epoch % roundS) - def incrementAddressSuccess(self, address): - ''' - Increase the recorded sucesses for an address - ''' - increment = self._core.getAddressInfo(address, 'success') + 1 - self._core.setAddressInfo(address, 'success', increment) - return - - def decrementAddressSuccess(self, address): - ''' - Decrease the recorded sucesses for an address - ''' - increment = self._core.getAddressInfo(address, 'success') - 1 - self._core.setAddressInfo(address, 'success', increment) - return - def mergeKeys(self, newKeyList): ''' Merge ed25519 key list to our database, comma seperated string @@ -89,6 +76,7 @@ class OnionrUtils: if newKeyList != False: for key in newKeyList.split(','): key = key.split('-') + # Test if key is valid try: if len(key[0]) > 60 or len(key[1]) > 1000: logger.warn('%s or its pow value is too large.' % key[0]) @@ -100,15 +88,19 @@ class OnionrUtils: value = base64.b64decode(key[1]) except binascii.Error: continue + # Load the pow token hashedKey = self._core._crypto.blake2bHash(key[0]) powHash = self._core._crypto.blake2bHash(value + hashedKey) try: powHash = powHash.encode() except AttributeError: pass + # if POW meets required difficulty, TODO make configurable/dynamic if powHash.startswith(b'0000'): + # if we don't already have the key and its not our key, add it. if not key[0] in self._core.listPeers(randomOrder=False) and type(key) != None and key[0] != self._core._crypto.pubKey: if self._core.addPeer(key[0], key[1]): + # Check if the peer has a set username already onionrusers.OnionrUser(self._core, key[0]).findAndSetID() retVal = True else: diff --git a/onionr/static-data/default-plugins/metadataprocessor/info.json b/onionr/static-data/default-plugins/metadataprocessor/info.json new file mode 100644 index 00000000..355d98f1 --- /dev/null +++ b/onionr/static-data/default-plugins/metadataprocessor/info.json @@ -0,0 +1,5 @@ +{ + "name" : "metadataprocessor", + "version" : "1.0", + "author" : "onionr" +} diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py new file mode 100644 index 00000000..842eaf88 --- /dev/null +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -0,0 +1,52 @@ +''' + Onionr - P2P Anonymous Storage Network + + This processes metadata for Onionr blocks +''' +''' + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' + +# useful libraries +import logger, config +import os, sys, json, time, random, shutil, base64, getpass, datetime, re +from onionrblockapi import Block + +plugin_name = 'metadataprocessor' + +# event listeners + +def on_processBlocks(api): + myBlock = api.data['block'] + blockType = api.data['type'] + print('blockType is ' + blockType) + if blockType == 'userInfo': + if myBlock.verifySig(): + peerName = myBlock.getMetadata('name') + try: + if len(peerName) > 20: + raise onionrexceptions.InvalidMetdata('Peer name specified is too large') + except TypeError: + pass + except onionrexceptions.InvalidMetadata: + pass + else: + api.get_core().setPeerInfo(signer, 'name', peerName) + logger.info('%s is now using the name %s.' % (signer, api.get_utils().escapeAnsi(peerName))) + +def on_init(api, data = None): + + pluginapi = api + + return From d151e0d3023bafe676c8d639110e2be1b002c6b5 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 11 Sep 2018 14:45:06 -0500 Subject: [PATCH 030/101] work on forward secrecy --- .gitignore | 1 + onionr/core.py | 15 +++---- onionr/dbcreator.py | 5 ++- onionr/onionrusers.py | 16 ++++++- .../default-plugins/metadataprocessor/main.py | 44 ++++++++++++++----- 5 files changed, 60 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 6edc23ff..26e43b0e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ onionr/data-encrypted.dat onionr/.onionr-lock core .vscode/* +venv/* diff --git a/onionr/core.py b/onionr/core.py index 97b822af..c1edf921 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -472,18 +472,17 @@ class Core: id text 0 name text, 1 adders text, 2 - forwardKey text, 3 - dateSeen not null, 4 - bytesStored int, 5 - trust int 6 - pubkeyExchanged int 7 - hashID text 8 - pow text 9 + dateSeen not null, 3 + bytesStored int, 4 + trust int 5 + pubkeyExchanged int 6 + hashID text 7 + pow text 8 ''' conn = sqlite3.connect(self.peerDB) c = conn.cursor() command = (peer,) - infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'forwardKey': 3, 'dateSeen': 4, 'bytesStored': 5, 'trust': 6, 'pubkeyExchanged': 7, 'hashID': 8} + infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'dateSeen': 3, 'bytesStored': 4, 'trust': 5, 'pubkeyExchanged': 6, 'hashID': 7} info = infoNumbers[info] iterCount = 0 retVal = '' diff --git a/onionr/dbcreator.py b/onionr/dbcreator.py index 5f3d2c79..05ea796e 100644 --- a/onionr/dbcreator.py +++ b/onionr/dbcreator.py @@ -61,7 +61,6 @@ class DBCreator: ID text not null, name text, adders text, - forwardKey text, dateSeen not null, bytesStored int, trust int, @@ -69,6 +68,10 @@ class DBCreator: hashID text, pow text not null); ''') + c.execute('''CREATE TABLE forwardKeys( + peerKey text not null, + forwardKey text not null, + date int not null);''') conn.commit() conn.close() return diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py index 7340fed3..10e2be33 100644 --- a/onionr/onionrusers.py +++ b/onionr/onionrusers.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import onionrblockapi, logger, onionrexceptions, json +import onionrblockapi, logger, onionrexceptions, json, sqlite3 class OnionrUser: def __init__(self, coreInst, publicKey): self.trust = 0 @@ -60,6 +60,20 @@ class OnionrUser: def forwardDecrypt(self, encrypted): return + def addForwardKey(self, newKey): + # Add a forward secrecy key for the peer + conn = sqlite3.connect(self._core.peerDB) + c = conn.cursor() + # Prepare the insert + time = self._core._utils.getEpoch() + command = (self.publicKey, newKey, time) + + c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?);", command) + + conn.commit() + conn.close() + return + def findAndSetID(self): '''Find any info about the user from existing blocks and cache it to their DB entry''' infoBlocks = [] diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 842eaf88..994bf818 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -22,28 +22,50 @@ import logger, config import os, sys, json, time, random, shutil, base64, getpass, datetime, re from onionrblockapi import Block +import onionrusers plugin_name = 'metadataprocessor' # event listeners +def _processUserInfo(api, newBlock): + ''' + Set the username for a particular user, from a signed block by them + ''' + myBlock = newBlock + peerName = myBlock.getMetadata('name') + try: + if len(peerName) > 20: + raise onionrexceptions.InvalidMetdata('Peer name specified is too large') + except TypeError: + pass + except onionrexceptions.InvalidMetadata: + pass + else: + api.get_core().setPeerInfo(signer, 'name', peerName) + logger.info('%s is now using the name %s.' % (signer, api.get_utils().escapeAnsi(peerName))) + +def _processForwardKey(api, myBlock): + ''' + Get the forward secrecy key specified by the user for us to use + ''' + peer = onionrusers.OnionrUser(self.api.get_core(), myBlock.signer) + def on_processBlocks(api): myBlock = api.data['block'] blockType = api.data['type'] print('blockType is ' + blockType) + + # Process specific block types + + # userInfo blocks, such as for setting username if blockType == 'userInfo': if myBlock.verifySig(): - peerName = myBlock.getMetadata('name') - try: - if len(peerName) > 20: - raise onionrexceptions.InvalidMetdata('Peer name specified is too large') - except TypeError: - pass - except onionrexceptions.InvalidMetadata: - pass - else: - api.get_core().setPeerInfo(signer, 'name', peerName) - logger.info('%s is now using the name %s.' % (signer, api.get_utils().escapeAnsi(peerName))) + _processUserInfo(api, myBlock) + # forwardKey blocks + elif blockType == 'forwardKey': + if myBlock.verifySig(): + _processForwardKey(api, myBlock) def on_init(api, data = None): From c4dcd89dfe8622fff43dd14973d86cba10c6a6ae Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 11 Sep 2018 21:58:51 -0500 Subject: [PATCH 031/101] + added methods to import and select new forward secrecy keys --- onionr/core.py | 2 +- onionr/onionrusers.py | 22 +++++++++++++++++-- .../default-plugins/metadataprocessor/main.py | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index c1edf921..15c862c5 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -693,7 +693,7 @@ class Core: data = data.encode() except AttributeError: pass - # sign before encrypt, as unauthenticated crypto should not be a problem here + if sign: signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True) signer = self._crypto.pubKey diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py index 10e2be33..4b74258e 100644 --- a/onionr/onionrusers.py +++ b/onionr/onionrusers.py @@ -50,7 +50,7 @@ class OnionrUser: encrypted = coreInst._crypto.pubKeyEncrypt(data, self.publicKey, encodedData=True) return encrypted - def decrypt(self, data): + def decrypt(self, data, anonymous=True): decrypted = coreInst._crypto.pubKeyDecrypt(data, self.publicKey, encodedData=True) return decrypted @@ -59,8 +59,26 @@ class OnionrUser: def forwardDecrypt(self, encrypted): return - + + def _getLatestForwardKey(self): + # Get the latest forward secrecy key for a peer + conn = sqlite3.connect(self._core.peerDB) + c = conn.cursor() + # Prepare the insert + time = self._core._utils.getEpoch() + key = '' + + for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE DATE=(SELECT max(date) FROM forwardKeys);"): + key = row[0] + break + + conn.commit() + conn.close() + return key + def addForwardKey(self, newKey): + if not self._core._utils.validatePubKey(newKey): + raise onionrexceptions.InvalidPubkey # Add a forward secrecy key for the peer conn = sqlite3.connect(self._core.peerDB) c = conn.cursor() diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 994bf818..74397c12 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -54,7 +54,7 @@ def _processForwardKey(api, myBlock): def on_processBlocks(api): myBlock = api.data['block'] blockType = api.data['type'] - print('blockType is ' + blockType) + logger.info('blockType is ' + blockType) # Process specific block types From 1c2a8a2f400fc7f6f83ed19071c932239c352584 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 12 Sep 2018 20:23:50 -0500 Subject: [PATCH 032/101] work on forward secrecy --- onionr/onionrusers.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py index 4b74258e..898f9ae4 100644 --- a/onionr/onionrusers.py +++ b/onionr/onionrusers.py @@ -55,26 +55,43 @@ class OnionrUser: return decrypted def forwardEncrypt(self, data): + retData = '' + forwardKey = self._getLatestForwardKey() + if self._core._utils.validatePubKey(forwardKey): + encrypted = self._core._crypto.pubKeyEncrypt(data, forwardKey, encodedData=True) + else: + raise Exception("No valid forward key available for this user") return def forwardDecrypt(self, encrypted): + retData = '' return def _getLatestForwardKey(self): # Get the latest forward secrecy key for a peer conn = sqlite3.connect(self._core.peerDB) c = conn.cursor() - # Prepare the insert - time = self._core._utils.getEpoch() - key = '' - for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE DATE=(SELECT max(date) FROM forwardKeys);"): + for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ? AND date=(SELECT max(date) FROM forwardKeys)", (self.publicKey,)): key = row[0] break conn.commit() conn.close() return key + + def _getForwardKeys(self): + conn = sqlite3.connect(self._core.peerDB) + c = conn.cursor() + keyList = [] + for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ?", (self.publicKey,)): + key = row[0] + keyList.append(key) + + conn.commit() + conn.close() + + return list(keyList) def addForwardKey(self, newKey): if not self._core._utils.validatePubKey(newKey): From 7738de1c289c4bfa32fec4039b713d745cfba502 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Wed, 12 Sep 2018 22:30:49 -0700 Subject: [PATCH 033/101] Add notifications --- onionr/static-data/www/ui/dist/index.html | 18 +++-- onionr/static-data/www/ui/dist/js/main.js | 53 ++++++++------ onionr/static-data/www/ui/dist/js/timeline.js | 69 +++++++++++++------ onionr/static-data/www/ui/src/index.html | 18 +++-- onionr/static-data/www/ui/src/js/main.js | 53 ++++++++------ onionr/static-data/www/ui/src/js/timeline.js | 69 +++++++++++++------ 6 files changed, 188 insertions(+), 92 deletions(-) diff --git a/onionr/static-data/www/ui/dist/index.html b/onionr/static-data/www/ui/dist/index.html index 756f8107..90f9a30d 100644 --- a/onionr/static-data/www/ui/dist/index.html +++ b/onionr/static-data/www/ui/dist/index.html @@ -43,13 +43,21 @@
-

+

-
- +
+

-
- + +
diff --git a/onionr/static-data/www/ui/dist/js/main.js b/onionr/static-data/www/ui/dist/js/main.js index 864b146a..261322eb 100644 --- a/onionr/static-data/www/ui/dist/js/main.js +++ b/onionr/static-data/www/ui/dist/js/main.js @@ -51,6 +51,7 @@ function deserializeUser(id) { user.setName(serialized['name']); user.setID(serialized['id']); user.setIcon(serialized['icon']); + user.setDescription(serialized['description']); return user; } @@ -148,6 +149,11 @@ class Sanitize { static username(username) { return String(username).replace(/[\W_]+/g, " ").substring(0, 25); } + + /* profile descriptions */ + static description(description) { + return String(description).substring(0, 128); + } } /* config stuff */ @@ -233,6 +239,7 @@ class User { } static getUser(id, callback) { + console.log(callback); var user = deserializeUser(id); if(user === null) { Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { @@ -245,10 +252,11 @@ class User { if(userInfo['id'] === id) { user.setName(userInfo['name']); user.setIcon(userInfo['icon']); + user.setDescription(userInfo['description']); user.setID(id); user.remember(); - + console.log(callback); callback(user); return user; } @@ -264,6 +272,7 @@ class User { } }); } else { + console.log(callback); callback(user); return user; } @@ -647,6 +656,8 @@ if(getWebPassword() === null) { } if(getCurrentUser() === null) { + jQuery('#modal').modal('show'); + var url = '/client/?action=info&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); console.log(url); @@ -655,33 +666,33 @@ if(getCurrentUser() === null) { // sync - http.open('GET', url, false); - http.send(null); + http.addEventListener('load', function() { + var id = JSON.parse(http.responseText)['pubkey']; - var id = JSON.parse(http.responseText)['pubkey']; + User.getUser(id, function(data) { + if(data === null || data === undefined) { + var user = new User(); - User.getUser(id, function(data) { - if(data === null || data === undefined) { - jQuery('#modal').modal('show'); - - var user = new User(); - - user.setName('New User'); - user.setID(id); - user.setIcon('/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAcFBQYFBAcGBQYIBwcIChELCgkJChUPEAwRGBUaGRgVGBcbHichGx0lHRcYIi4iJSgpKywrGiAvMy8qMicqKyr/2wBDAQcICAoJChQLCxQqHBgcKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKir/wAARCACAAIADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDrtTvrlL51jlkyGPANUZNSuvJZ2uJFYHjB6UmpTE6jcZUH5iCR0FQQLHvww3An8K8jmuz0lHQvwXV1gNLcSBmGcZqcXtwo/wBe/X1rzqw1e/stWmaTdKpcl1Le9dqmoJc2qupxnoCOauUWkOzRpnULhsATMPXmoptSuFGPPfjvms8Xew4OaY7NOSEyAT3rK9w5bFn+0rlmCrPIvqc9KRL+9UGVrr5ew39aoN5qkRhjt9Vp0Vv5bFmHJ6Z7Ucz2KsjXi1K4kUYmk6Z61Ot1Owz5z9OOayYcquGZgw59sVaikZ1OSQB0FUmQ0XftVwP+WznjoDS/bZx83msBjpmqobb1IBPv1prOpGD+lVzE2LP9ozEHEznPvTDe3JBImbaO4NZ0jlfliGM52jHWlW2nEO6eRuBnCU7jsXft068+dIR9amtLycupaduvOTWH/aIPyqjxkHBDd/pV2BiZEYdAacZJ7Eyi0QXC7dVn3Nw0hzxxTRPCgAXAZucY+9RewzDUpjuYp5h7VGLZW+VAVJ6Fj0rn5pX2Nkkc/qFuV1KbdGHiLb1ZcZTPYj61JazNbNtfJib+HofqD6ioPEQ+y6lAQziTZ9/djvwM0z7XfSRhJj8hxnzAMj8a9CDUqepErp6G0uriOdYNQOQRmKZRw49x2PrWnHd2/lZDqufeuIulcWpjlYb433IR0B6EUnmyMu55AFiHrzz0rzpO0rI6uRNXO08yNySGVv8AgXWpTKEXaRg+9cLZvIzM7s+M/L61Oby5+0eXG7ZXknqFHqTSE6Z10ksUMZknJVR7Vg3viCV/3dngAHl/Wsh759QuPKDmSJT8x3Ec1pRQReSViKMf7prtp0rq7MZWi9SvpmsTvrEKTuWDNt4OcZrs1kaBVcweYpPU1w2n2Dt4mtsqFAffgH0rugSr4Y7j168fhWdRcrKmlpYJJy2H2IHHpwB/9eoxO5G0ZxjpnrSGNpW5ZVGePb1p3ynKMPn6ZHGKzWpGiIVt/mwycjJPrVi2ZvMA3dcAEelOAYEHBdTwfWnwxATgldqE9B1FaqyehndvcsXSk6hNzxuNRpFuyCQO/Spr35b6Tp944xVaeby4GkH8Kkn8BUDOU8QvG2p+Qy7wqjk96rtes0KJsGMYBI6j0qCwf+0J2u7hgCx+X3H9K1xpp+0RkkFO/wDhVXk1ZGlktzAu1kdyMLleFyeuapSWbrsjYnO4Bs9/f+laNxKsk7vkeX9q8pCO2AS1XNMRbtby5lTekOGII5J7AD8BWPLd2OhSsiitnLDeFGUkeSD+JNWEQ7Xixt3dcHPNS7ZVvnWQ7p3jDOPTvj9f0pwTeBwQwPPHSp21HqzIltDY3BZdylz8oUEnP4VBHqzyXot7uHysdJGOOfwroy7iP5iQBxkHFYl/YWzXsZZXJZhliMd+wrtp1FYx5XzanQ+F7b/iZXHmIS6fL5jd/YVu3cLxyBdzZP3eM8VBpMUYdjHn52GPwAH9K6aS0ElqCy/Mo4qV+8bMqsuV3MJLVduJJMfhxVxYovL/ANpeMFeKx7vXLSzmZJHbKHoqGs6TxZBI22KOV29+AKy5lHcPZylsdMu9EG3I5zjFQ/a1imXzWyVG3k5rlf7bvLudU8zyYs8hD1/Gty3jWSNORjjrVKd9gdNrc0bqVRfT7sg7yR71A7edGYzIoDqRyarXjeXfzebwd7Z+b+lQM7KodcMvrjFLqI4nSbC0ivpoNQmdGZiI8OVxg+orJ1TWfEfhnWnS2uWuLYPgRSLv3Iff1966LUlP26RGVnw+QpH3gecg+orS06yTVLHyNRtvtEUYIVnOGQezDqK0pvldmrlzXNG9zmtK1F7qGxIiPlM7srP1Vxncp/xr0bw7p6WukzvMhKzPuxj0rz2ztxb3I06yiZktbh5mbOQC+Bt/nXsNor23h2NLeESXZjPlRFgNx9ee3rWlOMXN2MqspKKPOb3WtN0fxRevqd2tv5qKkKYLMeOTgdPTmtC31PQ7qEraXsbSYztbgn35FUNS+FGq3zTSzzQzSXMnmyT7yrof6/hWtpGk6f4dR4riJr27nULLM6YUAdFGf51M6UILUuNRyegxHhnUhWXHoCDzSWwAkwyrwepHSobnQ3l1BrvRIjbso+ZcYVqYL1kcCdfKlxhlYYFcTTTOlNNaHWaU5MyIETIPUADFdVJgx9O1cl4fuFuSNrAleu2uivL1Le3LyHAArtwzsmzhxGskjzPxNCiazOqdM5xXOBGWZiMDNdLqRW7ee+bA3EhQeuPWsA8MecZAwDXFLWbZ6MNIpMnhV2ZWD9+wrr7fKRxqik9Msa4pYmEyMsyo2eATj8q6XT7i8QoG2FOxV60j3M6hraope/n3cfOcVnOpPVsj0ra1CaJLybC7iXOfasm6dWUBAMk5JxitNDlVzF1SEZEykgrwR6irtjqiW9jLFIhTzY9qHHU9qrXQzCQ+CD2z0rHMrO3llyjKeCDgNWsJWE1cTw8IvtVw8r+XN5xUknJ4PP416DHq9/N4hguLOAyW1nH5LZHDEj9DivOprSCTWreUymJLg7bkL1YAdRjuRxXrGk6jZWemx29lHEkCjIG4j8+DzWkKbfWxVapFJaXZuvdo8AK4BK52nqPwrnbyO3aYyttYHtkirrXkNxC7K0cbKM8S5H6isKQSSyHy1+U9HByK2l7y1OOF4vQs7UuWCGFfL6Ehzx9BTH0C2m/ds8j+m4D5adZRT+Z8rAj124rSMqW6Evkc4Yk1HJF7ov2klsS2Gn22nW4SHC+9YXiW+MrpZqQQxwxq7qWpR2tqXLowYcDPWuBe9ka/M4PsFNYV5KEeWJvQg5y5mXtYmiW1WJChGduB1Fc+qqyyZDGMdDnIzVnU7mUzfOHiOPmJHWpI4zHpOIwu5upyOfwriWrO/ZGZmeGeNjHuGeAB1H41vWOpxzypKgGeCV2jqD6VzpNzGwLOjKrZGByv4VVe6aG+Zo+CjBgQB0zyPpWiFJXPStSnAv5wso3Bzxj3rOkkWUAnBZOQ2/vUWpysdTuBk7jKw+ZfeqsjfZ1KzEH3XmtDjK9/MkYGZD83UA9KxXuEfnd0PBPU1ZvZYip2tgnqCKwHlJuRGjBueMVSd9CraHS209tKuJEUnP0zWxDIkIAhuJl7gbyRXHrbzBgcEt2UdquwSTRnbI/19q2i2ZyR2UF7JwJJGYdAM5ratImMW/hRn5lHQ++K5Ow1BWVGdduBxkdTWtDqbvKY4+MdDWqZhJHUxyxqgCcMOfrVHVb9LG1eWTDs3QepAqhHelbd5ZjsYfpXHarq8mpzkI5WIEhlz0/zioqVOVF0qTm9SeXUXv7kmRwEY/Lt4zUkNsC4D4Ii+Y4PSqVqMN5eBmQcAdh/StC4aKzsGRGUsfbOa86TcnqeitNEOkmWexkbbjnA2nkfUVlqkluoizhX5GcYp8DkgPIrbT97aMg1JcwRuRK67oiOuc4pLUrYytSiSJlAJGeSFPzL/jVJ2TIlz5xAABC4P196u3EUN8PsxfKKcod2CtVLqBrKQwsS2xcHPXkitVawtUdfqrSrq9y4XOJG4P1rLuJywbcu3nBGK6HUS51OcKgZfMJJU/55rB1CN47dmdl3ZzgNyKlSVznsc/qW5d25+f7tcxevKkwaMmNvXPSuqvNQiVSmGP8As7OWFcve/vWLRmTrjb6VvTbuElodf4Zu7K5gSLzmaVR8+/qa61dPhdQFA/DvXkmibk1EiaM8rwFOP1r0zQL47VXb06sZQ1dCkk7HPOLtdGoukKu2RsEpyoPAzVqCwWNshwWI9OTVuEedbl5BgnocVCJJJJTHEOFOGOcYrTQx1ZmeIbxljW1TgyfKNo6+9cwbRYju3bvJBL55AP8A9aut1C1Es8sqSbzCm3IHAJ6gfQVyt/GttGyI24bcEeue3+NcdS97s7aVrWQtpKyTGaTkdFGT+dTXd5PecYQRn1BzWPNMYLZVQkZASPPrV7S5fMuxFNs3Rgbmc8A/Tua52n0OlW3Ztmymi0pXhypx36H61n263NwxiWIKD1y/BrohLatbiOWcOcemB+QrHvI5EkAt5EKj+HdjH4UnsTGWupYTwzEyF5QEkHO5Gzj8KwdVsmtroywskoAGec47YI96s3M1+8Yj3TADoyAisW6hvba4WWVXKS8MfU9Rk+tVFodn1Z3Gp3jf2ldCRWwJWGBxnmqYjLJlFRycnkcj610F/pmL6Yht+ZCeVqmbGRCHji3EDjCmqtbY5eY5q90gSqBMCfRvSufutJ8uQkKMDuetd5LDPtIuEIwOMLjNY1xGskb79yH+4y0RZdzj7C2WfWI43Xf2KkYr1LTdOe1t1Nv5MSD0QH/CuDhtY49YjZgwU8Y3EE16JptneXMai2sGSMfxyMR+ldtOKauc9WTNq3wIgWcE46CnSBHGSvBGOKsJaSR24MsRYrztVMVMLSQrkLhupXHGD6VvZnNc5XVLdrUSiHJSQ5Cgd65i+tp4dKedQiTsdoLjhfU4716LqGnuVw6MD1VgOlchqFgyXkT3GXVHyA+dufeuedNPU6adS2hxtxFOIS3lsZZASiMvfoGqlNb31g0dtnZu+ZnH3vr9a7V7iKW6WK0ge7nkON5Xauf8BVTW7CSDT5jdkRSS5LSY5I/oPaudw5TrjUuZOnX9lt2G4leUDBO7j8RWxaX1urj/AEWE+jp6+4NcCYDcaiyWaKijptX5vwPua0H0y/gVZcXicfeLZFZSj5mySZ6OmpwiEyRLl1+9C67SP8+tYuo61a6nFJAEktpPQ9DWXpFprGqbbd/MaMcFmToPr1rpD4OijVTN50zDH3RyfxqbtbE8sYvU/9k=\ + user.setName('New User'); + user.setID(id); + user.setIcon('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\ '); - user.setDescription('A new OnionrUI user'); + user.setDescription('A new OnionrUI user'); - user.remember(); - user.save(); + user.remember(); + user.save(); - setCurrentUser(user); + setCurrentUser(user); + } else { + setCurrentUser(data); + } window.location.reload(); - } else { - setCurrentUser(data); - } - }); + }); + }, false); + + http.open('GET', url, true); + http.send(null); } currentUser = getCurrentUser(); diff --git a/onionr/static-data/www/ui/dist/js/timeline.js b/onionr/static-data/www/ui/dist/js/timeline.js index 7e90d962..2712cde8 100644 --- a/onionr/static-data/www/ui/dist/js/timeline.js +++ b/onionr/static-data/www/ui/dist/js/timeline.js @@ -58,18 +58,25 @@ function postCreatorChange() { var content = document.getElementById('onionr-post-creator-content').value; var message = ''; + var maxlength = 280; + var disable = true; + var warn = false; if(content.length !== 0) { if(content.length - content.replaceAll('\n', '').length > 16) { // 16 max newlines message = 'Please use less than 16 newlines'; - } else if(content.length <= 280) { + } else if(content.length <= maxlength) { // 280 max characters message = '%s characters remaining'.replaceAll('%s', (280 - content.length)); disable = false; + + if(maxlength - content.length < maxlength / 4) { + warn = true; + } } else { - message = '%s characters over maximum'.replaceAll('%s', (content.length - 280)); + message = '%s characters over maximum'.replaceAll('%s', (content.length - maxlength)); } } @@ -85,6 +92,8 @@ function postCreatorChange() { if(disable) element.style.color = 'red'; + else if(warn) + element.style.color = '#FF8C00'; else element.style.color = 'gray'; } @@ -101,10 +110,11 @@ function viewProfile(id, name) { User.getUser(id, function(data) { if(data !== null) { - document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(data.getName()); - document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID()); document.getElementById("onionr-profile-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(data.getIcon()); document.getElementById("onionr-profile-user-icon").b64 = Sanitize.html(data.getIcon()); + document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(Sanitize.username(data.getName())); + document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID()); + document.getElementById("onionr-profile-description").innerHTML = Sanitize.html(Sanitize.description(data.getDescription())); } }); } @@ -112,24 +122,26 @@ function viewProfile(id, name) { function updateUser() { toggleSaveButton(false); - jQuery('#modal').modal('show'); + // jQuery('#modal').modal('show'); var name = jQuery('#onionr-profile-username').text(); var id = document.getElementById("onionr-profile-username").title; var icon = document.getElementById("onionr-profile-user-icon").b64; - var description = 'todo'; + var description = jQuery("#onionr-profile-description").text(); var user = new User(); user.setName(name); user.setID(id); user.setIcon(icon); - user.setDescription(description); + user.setDescription(Sanitize.description(description)); user.remember(); - user.save(); + user.save(function() { + setCurrentUser(user); - window.location.reload(); + window.location.reload(); + }); } function cancelUpdate() { @@ -142,8 +154,7 @@ function cancelUpdate() { } function toggleSaveButton(show) { - document.getElementById("onionr-profile-save").style.display = (show ? 'block' : 'none'); - document.getElementById("onionr-profile-cancel").style.display = (show ? 'block' : 'none'); + document.getElementById("onionr-profile-edit").style.display = (show ? 'block' : 'none'); } function makePost() { @@ -161,38 +172,56 @@ function makePost() { document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML; document.getElementById("onionr-post-creator-content").value = ""; + document.getElementById("onionr-post-creator-content").focus(); postCreatorChange(); } else { console.log('Not making empty post.'); } } -$('body').on('click', '[data-editable]', function() { +jQuery('body').on('click', '[data-editable]', function() { var el = jQuery(this); var txt = el.text(); + var maxlength = el.attr("maxlength"); var input = jQuery('').val(txt); + input.attr('maxlength', maxlength); el.replaceWith(input); var save = function() { var newTxt = input.val(); - var p = el.text(Sanitize.username(newTxt)); + + if(el.attr('id') === 'onionr-profile-username') + newTxt = Sanitize.username(newTxt); + if(el.attr('id') === 'onionr-profile-description') + newTxt = Sanitize.description(newTxt); + + var p = el.text(newTxt); + input.replaceWith(p); if(newTxt !== txt) toggleSaveButton(true); }; - input.one('blur', save).focus(); + var saveEnter = function(event) { + console.log(event); + console.log(event.keyCode); + if (event.keyCode === 13) + save(); + }; + + input.one('blur', save).bind('keyup', saveEnter).focus(); }); currentUser = getCurrentUser(); - -document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName()); -document.getElementById("onionr-post-creator-user-id").innerHTML = "you"; -document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); -document.getElementById("onionr-post-creator-user-id").title = currentUser.getID(); -document.getElementById("onionr-post-creator-content").placeholder = "Enter a message here..."; +if(currentUser !== undefined && currentUser !== null) { + document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName()); + document.getElementById("onionr-post-creator-user-id").innerHTML = "you"; + document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); + document.getElementById("onionr-post-creator-user-id").title = currentUser.getID(); + document.getElementById("onionr-post-creator-content").placeholder = "Enter a message here..."; +} viewCurrentProfile = function() { viewProfile(encodeURIComponent(currentUser.getID()), encodeURIComponent(currentUser.getName())); diff --git a/onionr/static-data/www/ui/src/index.html b/onionr/static-data/www/ui/src/index.html index d64a35d0..b794a912 100644 --- a/onionr/static-data/www/ui/src/index.html +++ b/onionr/static-data/www/ui/src/index.html @@ -13,13 +13,21 @@
-

+

-
- +
+

-
- + +
diff --git a/onionr/static-data/www/ui/src/js/main.js b/onionr/static-data/www/ui/src/js/main.js index a69b31ea..7a67f78f 100644 --- a/onionr/static-data/www/ui/src/js/main.js +++ b/onionr/static-data/www/ui/src/js/main.js @@ -51,6 +51,7 @@ function deserializeUser(id) { user.setName(serialized['name']); user.setID(serialized['id']); user.setIcon(serialized['icon']); + user.setDescription(serialized['description']); return user; } @@ -148,6 +149,11 @@ class Sanitize { static username(username) { return String(username).replace(/[\W_]+/g, " ").substring(0, 25); } + + /* profile descriptions */ + static description(description) { + return String(description).substring(0, 128); + } } /* config stuff */ @@ -233,6 +239,7 @@ class User { } static getUser(id, callback) { + console.log(callback); var user = deserializeUser(id); if(user === null) { Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { @@ -245,10 +252,11 @@ class User { if(userInfo['id'] === id) { user.setName(userInfo['name']); user.setIcon(userInfo['icon']); + user.setDescription(userInfo['description']); user.setID(id); user.remember(); - + console.log(callback); callback(user); return user; } @@ -264,6 +272,7 @@ class User { } }); } else { + console.log(callback); callback(user); return user; } @@ -615,6 +624,8 @@ if(getWebPassword() === null) { } if(getCurrentUser() === null) { + jQuery('#modal').modal('show'); + var url = '/client/?action=info&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); console.log(url); @@ -623,32 +634,32 @@ if(getCurrentUser() === null) { // sync - http.open('GET', url, false); - http.send(null); + http.addEventListener('load', function() { + var id = JSON.parse(http.responseText)['pubkey']; - var id = JSON.parse(http.responseText)['pubkey']; + User.getUser(id, function(data) { + if(data === null || data === undefined) { + var user = new User(); - User.getUser(id, function(data) { - if(data === null || data === undefined) { - jQuery('#modal').modal('show'); + user.setName('New User'); + user.setID(id); + user.setIcon('<$= Template.jsTemplate("default-icon") $>'); + user.setDescription('A new OnionrUI user'); - var user = new User(); + user.remember(); + user.save(); - user.setName('New User'); - user.setID(id); - user.setIcon('<$= Template.jsTemplate("default-icon") $>'); - user.setDescription('A new OnionrUI user'); - - user.remember(); - user.save(); - - setCurrentUser(user); + setCurrentUser(user); + } else { + setCurrentUser(data); + } window.location.reload(); - } else { - setCurrentUser(data); - } - }); + }); + }, false); + + http.open('GET', url, true); + http.send(null); } currentUser = getCurrentUser(); diff --git a/onionr/static-data/www/ui/src/js/timeline.js b/onionr/static-data/www/ui/src/js/timeline.js index 9bcef854..a5af6d61 100644 --- a/onionr/static-data/www/ui/src/js/timeline.js +++ b/onionr/static-data/www/ui/src/js/timeline.js @@ -58,18 +58,25 @@ function postCreatorChange() { var content = document.getElementById('onionr-post-creator-content').value; var message = ''; + var maxlength = 280; + var disable = true; + var warn = false; if(content.length !== 0) { if(content.length - content.replaceAll('\n', '').length > 16) { // 16 max newlines message = '<$= LANG.POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES $>'; - } else if(content.length <= 280) { + } else if(content.length <= maxlength) { // 280 max characters message = '<$= LANG.POST_CREATOR_MESSAGE_REMAINING $>'.replaceAll('%s', (280 - content.length)); disable = false; + + if(maxlength - content.length < maxlength / 4) { + warn = true; + } } else { - message = '<$= LANG.POST_CREATOR_MESSAGE_OVER $>'.replaceAll('%s', (content.length - 280)); + message = '<$= LANG.POST_CREATOR_MESSAGE_OVER $>'.replaceAll('%s', (content.length - maxlength)); } } @@ -85,6 +92,8 @@ function postCreatorChange() { if(disable) element.style.color = 'red'; + else if(warn) + element.style.color = '#FF8C00'; else element.style.color = 'gray'; } @@ -101,10 +110,11 @@ function viewProfile(id, name) { User.getUser(id, function(data) { if(data !== null) { - document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(data.getName()); - document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID()); document.getElementById("onionr-profile-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(data.getIcon()); document.getElementById("onionr-profile-user-icon").b64 = Sanitize.html(data.getIcon()); + document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(Sanitize.username(data.getName())); + document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID()); + document.getElementById("onionr-profile-description").innerHTML = Sanitize.html(Sanitize.description(data.getDescription())); } }); } @@ -112,24 +122,26 @@ function viewProfile(id, name) { function updateUser() { toggleSaveButton(false); - jQuery('#modal').modal('show'); + // jQuery('#modal').modal('show'); var name = jQuery('#onionr-profile-username').text(); var id = document.getElementById("onionr-profile-username").title; var icon = document.getElementById("onionr-profile-user-icon").b64; - var description = 'todo'; + var description = jQuery("#onionr-profile-description").text(); var user = new User(); user.setName(name); user.setID(id); user.setIcon(icon); - user.setDescription(description); + user.setDescription(Sanitize.description(description)); user.remember(); - user.save(); + user.save(function() { + setCurrentUser(user); - window.location.reload(); + window.location.reload(); + }); } function cancelUpdate() { @@ -142,8 +154,7 @@ function cancelUpdate() { } function toggleSaveButton(show) { - document.getElementById("onionr-profile-save").style.display = (show ? 'block' : 'none'); - document.getElementById("onionr-profile-cancel").style.display = (show ? 'block' : 'none'); + document.getElementById("onionr-profile-edit").style.display = (show ? 'block' : 'none'); } function makePost() { @@ -161,38 +172,56 @@ function makePost() { document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML; document.getElementById("onionr-post-creator-content").value = ""; + document.getElementById("onionr-post-creator-content").focus(); postCreatorChange(); } else { console.log('Not making empty post.'); } } -$('body').on('click', '[data-editable]', function() { +jQuery('body').on('click', '[data-editable]', function() { var el = jQuery(this); var txt = el.text(); + var maxlength = el.attr("maxlength"); var input = jQuery('').val(txt); + input.attr('maxlength', maxlength); el.replaceWith(input); var save = function() { var newTxt = input.val(); - var p = el.text(Sanitize.username(newTxt)); + + if(el.attr('id') === 'onionr-profile-username') + newTxt = Sanitize.username(newTxt); + if(el.attr('id') === 'onionr-profile-description') + newTxt = Sanitize.description(newTxt); + + var p = el.text(newTxt); + input.replaceWith(p); if(newTxt !== txt) toggleSaveButton(true); }; - input.one('blur', save).focus(); + var saveEnter = function(event) { + console.log(event); + console.log(event.keyCode); + if (event.keyCode === 13) + save(); + }; + + input.one('blur', save).bind('keyup', saveEnter).focus(); }); currentUser = getCurrentUser(); - -document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName()); -document.getElementById("onionr-post-creator-user-id").innerHTML = "<$= LANG.POST_CREATOR_YOU $>"; -document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); -document.getElementById("onionr-post-creator-user-id").title = currentUser.getID(); -document.getElementById("onionr-post-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>"; +if(currentUser !== undefined && currentUser !== null) { + document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName()); + document.getElementById("onionr-post-creator-user-id").innerHTML = "<$= LANG.POST_CREATOR_YOU $>"; + document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); + document.getElementById("onionr-post-creator-user-id").title = currentUser.getID(); + document.getElementById("onionr-post-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>"; +} viewCurrentProfile = function() { viewProfile(encodeURIComponent(currentUser.getID()), encodeURIComponent(currentUser.getName())); From ee2a74380b02f23c91ed77dc21939dca3976706e Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 13 Sep 2018 12:26:22 -0500 Subject: [PATCH 034/101] work on metadata, forward secrecy, and starting on sockets --- onionr/core.py | 1 + onionr/dbcreator.py | 24 ++++++++++++++++++- onionr/onionrusers.py | 20 ++++++++++++++++ onionr/onionrutils.py | 3 ++- .../default-plugins/metadataprocessor/main.py | 19 +++++++++++---- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index 15c862c5..3ce4bbfd 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -48,6 +48,7 @@ class Core: self.torPort = torPort self.dataNonceFile = 'data/block-nonces.dat' self.dbCreate = dbcreator.DBCreator(self) + self.forwardKeysFile = 'data/forward-keys.db' self.usageFile = 'data/disk-usage.txt' self.config = config diff --git a/onionr/dbcreator.py b/onionr/dbcreator.py index 05ea796e..f9838342 100644 --- a/onionr/dbcreator.py +++ b/onionr/dbcreator.py @@ -71,7 +71,9 @@ class DBCreator: c.execute('''CREATE TABLE forwardKeys( peerKey text not null, forwardKey text not null, - date int not null);''') + date int not null, + expire int not null + );''') conn.commit() conn.close() return @@ -108,4 +110,24 @@ class DBCreator: ''') conn.commit() conn.close() + return + + def createForwardKeyDB(self): + ''' + Create the forward secrecy key db (*for *OUR* keys*) + ''' + if os.path.exists(self.core.forwardKeysFile): + raise Exception("Block database already exists") + conn = sqlite3.connect(self.core.forwardKeysFile) + c = conn.cursor() + c.execute('''CREATE TABLE myForwardKeys( + peer text not null, + public key text not null, + private key text not null, + date int not null, + expire int not null + ); + ''') + conn.commit() + conn.close() return \ No newline at end of file diff --git a/onionr/onionrusers.py b/onionr/onionrusers.py index 898f9ae4..1e8cdf29 100644 --- a/onionr/onionrusers.py +++ b/onionr/onionrusers.py @@ -93,6 +93,26 @@ class OnionrUser: return list(keyList) + def generateForwardKey(self, expire=432000): + + # Generate a forward secrecy key for the peer + conn = sqlite3.connect(self._core.forwardKeysFile) + c = conn.cursor() + # Prepare the insert + time = self._core._utils.getEpoch() + newKeys = self._core._crypto.generatePubKey() + newPub = newKeys[0] + newPriv = newKeys[1] + + time = self._core._utils.getEpoch() + command = (self.publicKey, newPub, newPriv, time, expire) + + c.execute("INSERT INTO myForwardKeys VALUES(?, ?, ?, ?);", command) + + conn.commit() + conn.close() + + def addForwardKey(self, newKey): if not self._core._utils.validatePubKey(newKey): raise onionrexceptions.InvalidPubkey diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 6449e152..b5fc10c1 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -265,11 +265,12 @@ class OnionrUtils: myBlock.decrypt() blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks signer = self.bytesToStr(myBlock.signer) + valid = myBlock.verifySig() try: if len(blockType) <= 10: self._core.updateBlockInfo(blockHash, 'dataType', blockType) - onionrevents.event('processBlocks', data = {'block': myBlock, 'type': blockType}, onionr = None) + onionrevents.event('processBlocks', data = {'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid}, onionr = None) except TypeError: pass diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 74397c12..5145d911 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -22,7 +22,7 @@ import logger, config import os, sys, json, time, random, shutil, base64, getpass, datetime, re from onionrblockapi import Block -import onionrusers +import onionrusers, onionrexceptions plugin_name = 'metadataprocessor' @@ -50,6 +50,13 @@ def _processForwardKey(api, myBlock): Get the forward secrecy key specified by the user for us to use ''' peer = onionrusers.OnionrUser(self.api.get_core(), myBlock.signer) + key = myBlock.getMetadata('newFSKey') + + # We don't need to validate here probably, but it helps + if api.get_utils().validatePubKey(key): + peer.addForwardKey(key) + else: + raise onionrexceptions.InvalidPubkey("%s is nota valid pubkey key" % (key,)) def on_processBlocks(api): myBlock = api.data['block'] @@ -60,12 +67,16 @@ def on_processBlocks(api): # userInfo blocks, such as for setting username if blockType == 'userInfo': - if myBlock.verifySig(): + if api.data['validSig']: _processUserInfo(api, myBlock) - # forwardKey blocks + # forwardKey blocks, add a new forward secrecy key for a peer elif blockType == 'forwardKey': - if myBlock.verifySig(): + if api.data['validSig']: _processForwardKey(api, myBlock) + # socket blocks + elif blockType == 'openSocket': + if api.data['validSig']: + pass def on_init(api, data = None): From e0fbe2033e2df596c197effba21cca0e8af6b6e8 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 14 Sep 2018 20:05:25 -0500 Subject: [PATCH 035/101] work on sockets --- onionr/communicator2.py | 8 +++++- onionr/onionrexceptions.py | 5 ++++ onionr/onionrsockets.py | 25 +++++++++++++++++++ .../default-plugins/metadataprocessor/main.py | 8 +++++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index f203dd70..e6d49d28 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -21,7 +21,7 @@ ''' import sys, os, core, config, json, requests, time, logger, threading, base64, onionr import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block -import onionrdaemontools +import onionrdaemontools, onionrsockets from defusedxml import minidom class OnionrCommunicatorDaemon: @@ -79,6 +79,9 @@ class OnionrCommunicatorDaemon: #self.daemonTools = onionrdaemontools.DaemonTools(self) self.daemonTools = onionrdaemontools.DaemonTools(self) + # Active sockets for direct connections + self.sockets = [] + if debug or developmentMode: OnionrCommunicatorTimers(self, self.heartbeat, 10) @@ -462,6 +465,9 @@ class OnionrCommunicatorDaemon: elif cmd[0] == 'uploadBlock': self.blockToUpload = cmd[1] threading.Thread(target=self.uploadBlock).start() + elif cmd[0] == 'createSocket': + # Create a socket + self.onionrsockets.append(onionrsockets.OnionrSockets(self._core, startData)) else: logger.info('Recieved daemonQueue command:' + cmd[0]) diff --git a/onionr/onionrexceptions.py b/onionr/onionrexceptions.py index c32fbbb4..4954550e 100644 --- a/onionr/onionrexceptions.py +++ b/onionr/onionrexceptions.py @@ -65,4 +65,9 @@ class InvalidAddress(Exception): # file exceptions class DiskAllocationReached(Exception): + pass + +# onionrsocket exceptions + +class MissingAddress(Exception): pass \ No newline at end of file diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index df4a1ad7..0f00519f 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -17,3 +17,28 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' +import stem +import onionrexceptions +from dependencies import secrets + +class OnionrSockets: + def __init__(self, coreInst, socketInfo): + '''Create a new Socket object. This interface is named a bit misleadingly + and does not actually forward network requests. + + Accepts coreInst, an instance of Onionr core library, and socketInfo, a dict with these values: + 'peer': peer master public key + 'address': string, if we're connecting to a socket, this is the address we connect to. Not applicable if we're creating our own + create: bool + ''' + self.socketID = secrets.token_hex(32) # Generate an ID for this socket + self._core = coreInst + + # Make sure socketInfo provides all necessary values + for i in ('peer', 'address', 'create'): + try: + socketInfo[i] + except KeyError: + raise ValueError('Must provide peer, address, and create in socketInfo dict argument') + + \ No newline at end of file diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 5145d911..4d23fa9a 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -59,6 +59,7 @@ def _processForwardKey(api, myBlock): raise onionrexceptions.InvalidPubkey("%s is nota valid pubkey key" % (key,)) def on_processBlocks(api): + # Generally fired by utils. myBlock = api.data['block'] blockType = api.data['type'] logger.info('blockType is ' + blockType) @@ -76,7 +77,12 @@ def on_processBlocks(api): # socket blocks elif blockType == 'openSocket': if api.data['validSig']: - pass + try: + address = api.data['address'] + except KeyError: + raise onionrexceptions.MissingAddress("Missing address for new socket") + socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, create = False}) + api.get_core().daemonQueueAdd('createSocket', socketInfo) def on_init(api, data = None): From d80e72d18cf097663d2285ee858c683ec5b726de Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 14 Sep 2018 23:48:48 -0500 Subject: [PATCH 036/101] work on sockets --- onionr/communicator2.py | 4 ++-- onionr/onionrsockets.py | 11 +++++++++-- .../default-plugins/metadataprocessor/main.py | 8 ++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index e6d49d28..c0fbb698 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -465,8 +465,8 @@ class OnionrCommunicatorDaemon: elif cmd[0] == 'uploadBlock': self.blockToUpload = cmd[1] threading.Thread(target=self.uploadBlock).start() - elif cmd[0] == 'createSocket': - # Create a socket + elif cmd[0] == 'startSocket': + # Create a socket or connect to one self.onionrsockets.append(onionrsockets.OnionrSockets(self._core, startData)) else: logger.info('Recieved daemonQueue command:' + cmd[0]) diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 0f00519f..f80b1637 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -33,6 +33,7 @@ class OnionrSockets: ''' self.socketID = secrets.token_hex(32) # Generate an ID for this socket self._core = coreInst + self.socketInfo = socketInfo # Make sure socketInfo provides all necessary values for i in ('peer', 'address', 'create'): @@ -40,5 +41,11 @@ class OnionrSockets: socketInfo[i] except KeyError: raise ValueError('Must provide peer, address, and create in socketInfo dict argument') - - \ No newline at end of file + + self.isServer = socketInfo['create'] + + self.serverKey = socketInfo['peer'] + self.serverAddress = socketInfo['address'] + + def createServer(self): + return \ No newline at end of file diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 4d23fa9a..199c8821 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -68,21 +68,21 @@ def on_processBlocks(api): # userInfo blocks, such as for setting username if blockType == 'userInfo': - if api.data['validSig']: + if api.data['validSig'] == True: # we use == True for type safety _processUserInfo(api, myBlock) # forwardKey blocks, add a new forward secrecy key for a peer elif blockType == 'forwardKey': - if api.data['validSig']: + if api.data['validSig'] == True: _processForwardKey(api, myBlock) # socket blocks elif blockType == 'openSocket': - if api.data['validSig']: + if api.data['validSig'] == True: try: address = api.data['address'] except KeyError: raise onionrexceptions.MissingAddress("Missing address for new socket") socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, create = False}) - api.get_core().daemonQueueAdd('createSocket', socketInfo) + api.get_core().daemonQueueAdd('startSocket', socketInfo) def on_init(api, data = None): From 620897a2ebcf33f93eede43c029975b6af5fdd2e Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 15 Sep 2018 11:13:03 -0500 Subject: [PATCH 037/101] work on sockets --- onionr/onionrsockets.py | 27 ++++++++++++++++--- .../default-plugins/metadataprocessor/main.py | 9 +++++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index f80b1637..972ef328 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -17,7 +17,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import stem +import stem.control +import socket, selectors import onionrexceptions from dependencies import secrets @@ -36,16 +37,34 @@ class OnionrSockets: self.socketInfo = socketInfo # Make sure socketInfo provides all necessary values - for i in ('peer', 'address', 'create'): + for i in ('peer', 'address', 'create', 'port'): try: socketInfo[i] except KeyError: raise ValueError('Must provide peer, address, and create in socketInfo dict argument') - self.isServer = socketInfo['create'] + self.isServer = socketInfo['create'] # if we are the one creating the service - self.serverKey = socketInfo['peer'] + self.remotePeer = socketInfo['peer'] + self.socketPort = socketInfo['port'] self.serverAddress = socketInfo['address'] + + if self.isServer: + self.createServer() def createServer(self): + # Create our HS and advertise it via a block + dataID = uuid.uuid4().hex + ourAddress = '' + ourPort = 1337 + ourInternalPort = 1338 + + # Setup the empheral HS + with stem.control.Controller.from_port() as controller: + controller.authenticate() + socketHS = controller.create_ephemeral_hidden_service({ourPort: ourInternalPort}, await_publication = True) + ourAddress = socketHS.service_id + + meta = {'address': ourAddress, 'port': ourPort} + self._core.insertBlock(dataID, header='openSocket', encryptType='asym', asymPeer=self.remotePeer, sign=True, meta=meta) return \ No newline at end of file diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 199c8821..3b0191ae 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -76,12 +76,17 @@ def on_processBlocks(api): _processForwardKey(api, myBlock) # socket blocks elif blockType == 'openSocket': - if api.data['validSig'] == True: + if api.data['validSig'] == True and myBlock.decrypted: # we check if it is decrypted as a way of seeing if it was for us try: address = api.data['address'] except KeyError: raise onionrexceptions.MissingAddress("Missing address for new socket") - socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, create = False}) + try: + port = api.data['port'] + except KeyError: + raise ValueError("Missing port for new socket") + + socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, create = False}) api.get_core().daemonQueueAdd('startSocket', socketInfo) def on_init(api, data = None): From 1d7fd65f381f3a4c71bc5ddb661a05b1fbe4dc69 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 17 Sep 2018 00:02:16 -0500 Subject: [PATCH 038/101] work on sockets --- onionr/onionrsockets.py | 58 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 972ef328..8111f039 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -19,8 +19,9 @@ ''' import stem.control import socket, selectors -import onionrexceptions +import onionrexceptions, time from dependencies import secrets +sel = selectors.DefaultSelector() class OnionrSockets: def __init__(self, coreInst, socketInfo): @@ -48,6 +49,9 @@ class OnionrSockets: self.remotePeer = socketInfo['peer'] self.socketPort = socketInfo['port'] self.serverAddress = socketInfo['address'] + self.connected = False + self.segment = 0 + self.connData = {} if self.isServer: self.createServer() @@ -65,6 +69,52 @@ class OnionrSockets: socketHS = controller.create_ephemeral_hidden_service({ourPort: ourInternalPort}, await_publication = True) ourAddress = socketHS.service_id - meta = {'address': ourAddress, 'port': ourPort} - self._core.insertBlock(dataID, header='openSocket', encryptType='asym', asymPeer=self.remotePeer, sign=True, meta=meta) - return \ No newline at end of file + # Advertise the server + meta = {'address': ourAddress, 'port': ourPort} + self._core.insertBlock(dataID, header='openSocket', encryptType='asym', asymPeer=self.remotePeer, sign=True, meta=meta) + + # Build the socket server + sock = socket.socket() + sock.bind(('127.0.0.1', ourInternalPort)) + sock.listen(100) + sock.setblocking(False) + sel.register(sock, selectors.EVENT_READ, self._accept) + + while True: + events = sel.select() + for key, mask in events: + callback = key.data + callback(key.fileobj, mask) + + return + + def connectServer(self): + return + + def _accept(self, sock, mask): + # Just accept the connection and pass it to our handler + conn, addr = sock.accept() + conn.setblocking(False) + sel.register(conn, selectors.EVENT_READ, self._read) + + def _read(self, conn, mask): + data = conn.recv(1000).decode() + if data: + self.segment += 1 + self.connData[self.segment] = data + conn.send(data) + else: + sel.unregister(conn) + conn.close() + + def readConnection(self): + if not self.connected: + raise Exception("Connection closed") + count = 0 + while self.connected: + try: + yield self.connData[count] + count += 1 + except KeyError: + pass + time.sleep(0.01) From f8b10cfe124ef67b0becebe3c7a27dc3266fa32a Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 19 Sep 2018 23:35:26 -0500 Subject: [PATCH 039/101] a lot of work on sockets, and added chat module --- onionr/communicator2.py | 8 ++- onionr/core.py | 3 +- onionr/netcontroller.py | 1 + onionr/onionrchat.py | 30 +++++++++ onionr/onionrsockets.py | 63 ++++++++++++------- .../default-plugins/metadataprocessor/main.py | 6 +- 6 files changed, 83 insertions(+), 28 deletions(-) create mode 100644 onionr/onionrchat.py diff --git a/onionr/communicator2.py b/onionr/communicator2.py index c0fbb698..01af7270 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -466,8 +466,12 @@ class OnionrCommunicatorDaemon: self.blockToUpload = cmd[1] threading.Thread(target=self.uploadBlock).start() elif cmd[0] == 'startSocket': - # Create a socket or connect to one - self.onionrsockets.append(onionrsockets.OnionrSockets(self._core, startData)) + # Create a socket or connect to one. + # The socket handler (such as the plugin or app using it) is specified in startData['reason] + startData = json.loads(cmd[1]) + rCallback = onionrsockets.getSocketCallbackRecieveHandler(self._core, startData['reason'], startData['create']) + sCallback = onionrsockets.getSocketCallbackSendHandler(self._core, startData['reason'], startData['create']) + self.onionrsockets.append(onionrsockets.OnionrSockets(self._core, startData, recieveCallback=rCallback, sendCallback=sCallback)) else: logger.info('Recieved daemonQueue command:' + cmd[0]) diff --git a/onionr/core.py b/onionr/core.py index 3ce4bbfd..54219390 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -21,7 +21,7 @@ import sqlite3, os, sys, time, math, base64, tarfile, getpass, simplecrypt, hash from onionrblockapi import Block import onionrutils, onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions, onionrvalues -import onionrblacklist +import onionrblacklist, onionrchat import dbcreator if sys.version_info < (3, 6): try: @@ -79,6 +79,7 @@ class Core: # Initialize the crypto object self._crypto = onionrcrypto.OnionrCrypto(self) self._blacklist = onionrblacklist.OnionrBlackList(self) + self.chatInst = onionrchat.OnionrChat(self) except Exception as error: logger.error('Failed to initialize core Onionr library.', error=error) diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index b3691956..56b12573 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -68,6 +68,7 @@ class NetController: # Set the Tor control password. Meant to make it harder to manipulate our Tor instance plaintext = base64.b64encode(os.urandom(50)).decode() config.set('tor.controlpassword', plaintext, savefile=True) + config.set('tor.socksport', self.socksPort, savefile=True) controlPort = random.randint(1025, 65535) diff --git a/onionr/onionrchat.py b/onionr/onionrchat.py new file mode 100644 index 00000000..a4051828 --- /dev/null +++ b/onionr/onionrchat.py @@ -0,0 +1,30 @@ +''' + Onionr - P2P Anonymous Storage Network + + Onionr Chat Messages +''' +''' + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' +import logger +class OnionrChat: + def __init__(self, coreInst): + return + + def recieveMessage(self, socketInst, data): + logger.info('Got %s' % (data,)) + return '' + + def sendMessage(self, socketInst, data): + return "Hello" \ No newline at end of file diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 8111f039..2a63613c 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -18,13 +18,25 @@ along with this program. If not, see . ''' import stem.control -import socket, selectors -import onionrexceptions, time +import socket, selectors, socks, config +import onionrexceptions, time, onionrchat from dependencies import secrets sel = selectors.DefaultSelector() +def getSocketCallbackRecieveHandler(coreInst, reason, create): + '''Return the recieve handler function for a given socket reason''' + retData = '' + if startData == 'chat': + retData = coreInst.chatInst.recieveMessage + +def getSocketCallbackSendHandler(coreInst, reason, create): + '''Return the send handler function for a given socket reason''' + retData = '' + if startData == 'chat': + retData = coreInst.chatInst.sendMessage + class OnionrSockets: - def __init__(self, coreInst, socketInfo): + def __init__(self, coreInst, socketInfo, recieveCallback=None, sendCallback=None): '''Create a new Socket object. This interface is named a bit misleadingly and does not actually forward network requests. @@ -36,6 +48,12 @@ class OnionrSockets: self.socketID = secrets.token_hex(32) # Generate an ID for this socket self._core = coreInst self.socketInfo = socketInfo + + if not callable(sendCallback) or not callable(recieveCallback) + raise ValueError("callback must be a function") + + self.sendCallback = sendCallback + self.recieveCallback = recieveCallback # Make sure socketInfo provides all necessary values for i in ('peer', 'address', 'create', 'port'): @@ -50,11 +68,11 @@ class OnionrSockets: self.socketPort = socketInfo['port'] self.serverAddress = socketInfo['address'] self.connected = False - self.segment = 0 - self.connData = {} if self.isServer: self.createServer() + else: + self.connectServer() def createServer(self): # Create our HS and advertise it via a block @@ -87,34 +105,31 @@ class OnionrSockets: callback(key.fileobj, mask) return - - def connectServer(self): - return def _accept(self, sock, mask): # Just accept the connection and pass it to our handler conn, addr = sock.accept() conn.setblocking(False) sel.register(conn, selectors.EVENT_READ, self._read) + self.connected = True def _read(self, conn, mask): - data = conn.recv(1000).decode() + data = conn.recv(1024) if data: - self.segment += 1 - self.connData[self.segment] = data - conn.send(data) + data = data.decode() + self.callback(self, data) else: sel.unregister(conn) conn.close() - - def readConnection(self): - if not self.connected: - raise Exception("Connection closed") - count = 0 - while self.connected: - try: - yield self.connData[count] - count += 1 - except KeyError: - pass - time.sleep(0.01) + + def connectServer(self): + # Set the Tor proxy + socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', config.get('tor.socksport'), rdns=True) + socket.socket = socks.socksocket + remoteSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + with remoteSocket as s: + s.connect((self.serverAddress, self.port)) + data = s.recv(1024) + data.send(self.sendCallback(self, data.decode())) + return \ No newline at end of file diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 3b0191ae..940dc424 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -85,8 +85,12 @@ def on_processBlocks(api): port = api.data['port'] except KeyError: raise ValueError("Missing port for new socket") + try: + reason = api.data['reason'] + except KeyError: + raise ValueError("Missing socket reason") - socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, create = False}) + socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, 'create' = False, 'reason': reason}) api.get_core().daemonQueueAdd('startSocket', socketInfo) def on_init(api, data = None): From 557afb8d9a7869c976a6a87fc5fbfadd45299949 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 19 Sep 2018 23:36:59 -0500 Subject: [PATCH 040/101] a lot of work on sockets, and added chat module --- onionr/static-data/default-plugins/metadataprocessor/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 940dc424..b4065f11 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -90,7 +90,7 @@ def on_processBlocks(api): except KeyError: raise ValueError("Missing socket reason") - socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, 'create' = False, 'reason': reason}) + socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, 'create': False, 'reason': reason}) api.get_core().daemonQueueAdd('startSocket', socketInfo) def on_init(api, data = None): From 7baa7d5d5f7d9c5535419130087bb26ee0cb2d71 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 20 Sep 2018 00:13:26 -0500 Subject: [PATCH 041/101] work on sockets --- onionr/communicator2.py | 4 +--- onionr/onionr.py | 4 +++- onionr/onionrsockets.py | 31 ++++++++++++++++++++++--------- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 01af7270..e8f5e7f3 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -469,9 +469,7 @@ class OnionrCommunicatorDaemon: # Create a socket or connect to one. # The socket handler (such as the plugin or app using it) is specified in startData['reason] startData = json.loads(cmd[1]) - rCallback = onionrsockets.getSocketCallbackRecieveHandler(self._core, startData['reason'], startData['create']) - sCallback = onionrsockets.getSocketCallbackSendHandler(self._core, startData['reason'], startData['create']) - self.onionrsockets.append(onionrsockets.OnionrSockets(self._core, startData, recieveCallback=rCallback, sendCallback=sCallback)) + self.onionrsockets.append(onionrsockets.OnionrSockets(self._core, startData)) else: logger.info('Recieved daemonQueue command:' + cmd[0]) diff --git a/onionr/onionr.py b/onionr/onionr.py index c8bb2873..1a50783b 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -26,6 +26,7 @@ if sys.version_info[0] == 2 or sys.version_info[1] < 5: print('Error, Onionr requires Python 3.4+') sys.exit(1) import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json, getpass, sqlite3 +import webbrowser from threading import Thread import api, core, config, logger, onionrplugins as plugins, onionrevents as events import onionrutils @@ -217,6 +218,8 @@ class Onionr: 'getpasswd': self.printWebPassword, 'get-passwd': self.printWebPassword, + 'chat': self.onionrCore.chatInst.connect() + 'friend': self.friendCmd } @@ -826,7 +829,6 @@ class Onionr: logger.error('%s add-file ' % sys.argv[0], timestamp = False) def openUI(self): - import webbrowser url = 'http://127.0.0.1:%s/ui/index.html?timingToken=%s' % (config.get('client.port', 59496), self.onionrUtils.getTimeBypassToken()) print('Opening %s ...' % url) diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 2a63613c..610074c6 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -36,7 +36,7 @@ def getSocketCallbackSendHandler(coreInst, reason, create): retData = coreInst.chatInst.sendMessage class OnionrSockets: - def __init__(self, coreInst, socketInfo, recieveCallback=None, sendCallback=None): + def __init__(self, coreInst, socketInfo): '''Create a new Socket object. This interface is named a bit misleadingly and does not actually forward network requests. @@ -48,12 +48,6 @@ class OnionrSockets: self.socketID = secrets.token_hex(32) # Generate an ID for this socket self._core = coreInst self.socketInfo = socketInfo - - if not callable(sendCallback) or not callable(recieveCallback) - raise ValueError("callback must be a function") - - self.sendCallback = sendCallback - self.recieveCallback = recieveCallback # Make sure socketInfo provides all necessary values for i in ('peer', 'address', 'create', 'port'): @@ -69,6 +63,9 @@ class OnionrSockets: self.serverAddress = socketInfo['address'] self.connected = False + self.readData = [] + self.sendData = 0 + if self.isServer: self.createServer() else: @@ -117,11 +114,25 @@ class OnionrSockets: data = conn.recv(1024) if data: data = data.decode() - self.callback(self, data) + self.readData.append(data) else: sel.unregister(conn) conn.close() + def sendData(self, data): + try: + data = data.encode() + except AttributeError: + pass + self.sendData = data + + def readData(self): + try: + data = self.readData.pop(0) + except IndexError: + data = '' + return data + def connectServer(self): # Set the Tor proxy socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', config.get('tor.socksport'), rdns=True) @@ -131,5 +142,7 @@ class OnionrSockets: with remoteSocket as s: s.connect((self.serverAddress, self.port)) data = s.recv(1024) - data.send(self.sendCallback(self, data.decode())) + if self.sendData != 0: + s.send(self.sendData) + self.sendData = 0 return \ No newline at end of file From e826bca19ef974db931e345173f971b451acaf5b Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 20 Sep 2018 12:04:58 -0500 Subject: [PATCH 042/101] work on sockets --- onionr/communicator2.py | 23 ++++++++++++++++++++--- onionr/core.py | 1 - onionr/onionr.py | 7 ++++++- onionr/onionrchat.py | 20 ++++++++++---------- onionr/onionrsockets.py | 15 ++------------- 5 files changed, 38 insertions(+), 28 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index e8f5e7f3..36b5794d 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -21,7 +21,8 @@ ''' import sys, os, core, config, json, requests, time, logger, threading, base64, onionr import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block -import onionrdaemontools, onionrsockets +import onionrdaemontools, onionrsockets, onionrchat +from dependencies import secrets from defusedxml import minidom class OnionrCommunicatorDaemon: @@ -80,7 +81,8 @@ class OnionrCommunicatorDaemon: self.daemonTools = onionrdaemontools.DaemonTools(self) # Active sockets for direct connections - self.sockets = [] + self.sockets = {} + self.socketExchange = {} # Socket ID exchange if debug or developmentMode: OnionrCommunicatorTimers(self, self.heartbeat, 10) @@ -469,12 +471,27 @@ class OnionrCommunicatorDaemon: # Create a socket or connect to one. # The socket handler (such as the plugin or app using it) is specified in startData['reason] startData = json.loads(cmd[1]) - self.onionrsockets.append(onionrsockets.OnionrSockets(self._core, startData)) + threading.Thread(target=self.startSocket, args=(startData,)).start() else: logger.info('Recieved daemonQueue command:' + cmd[0]) self.decrementThreadCount('daemonCommands') + def startSocket(self, startData): + # Start a socket client + mySocket = onionrsockets.OnionrSockets(self._core, startData)) + self.sockets[mySocket.socketID] = mySocket + + sockProgram = '' # Function for socket handler (application) + + if startData['reason'] == 'chat': + sockProgram = onionrchat.OnionrChat + else: + del self.sockets[mySocket.socketID] # Delete socket if we have no handler for it + + threading.Thread(target=sockProgram, args=(self, mySocket)).start() + mySocket.startConn() + def uploadBlock(self): '''Upload our block to a few peers''' # when inserting a block, we try to upload it to a few peers to add some deniability diff --git a/onionr/core.py b/onionr/core.py index 54219390..f6cb88b6 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -79,7 +79,6 @@ class Core: # Initialize the crypto object self._crypto = onionrcrypto.OnionrCrypto(self) self._blacklist = onionrblacklist.OnionrBlackList(self) - self.chatInst = onionrchat.OnionrChat(self) except Exception as error: logger.error('Failed to initialize core Onionr library.', error=error) diff --git a/onionr/onionr.py b/onionr/onionr.py index 1a50783b..26edf57d 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -218,7 +218,7 @@ class Onionr: 'getpasswd': self.printWebPassword, 'get-passwd': self.printWebPassword, - 'chat': self.onionrCore.chatInst.connect() + 'chat': self.startChat(), 'friend': self.friendCmd } @@ -270,6 +270,11 @@ class Onionr: THIS SECTION HANDLES THE COMMANDS ''' + def startChat(self): + self.onionrCore.daemonQueueAdd() + socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, 'create': True, 'reason': reason}) + self.onionrCore.daemonQueueAdd('startSocket', socketInfo) + def getCommands(self): return self.cmds diff --git a/onionr/onionrchat.py b/onionr/onionrchat.py index a4051828..f37f270e 100644 --- a/onionr/onionrchat.py +++ b/onionr/onionrchat.py @@ -17,14 +17,14 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import logger +import logger, time class OnionrChat: - def __init__(self, coreInst): - return - - def recieveMessage(self, socketInst, data): - logger.info('Got %s' % (data,)) - return '' - - def sendMessage(self, socketInst, data): - return "Hello" \ No newline at end of file + def __init__(self, communicatorInst, socketInst): + self.communicator = communicatorInst + self.socket = socketInst + + while True: + time.sleep(2) + logger.info(self.socket.readData()) + self.socket.sendData('rekt') + return \ No newline at end of file diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 610074c6..740c9054 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -23,18 +23,6 @@ import onionrexceptions, time, onionrchat from dependencies import secrets sel = selectors.DefaultSelector() -def getSocketCallbackRecieveHandler(coreInst, reason, create): - '''Return the recieve handler function for a given socket reason''' - retData = '' - if startData == 'chat': - retData = coreInst.chatInst.recieveMessage - -def getSocketCallbackSendHandler(coreInst, reason, create): - '''Return the send handler function for a given socket reason''' - retData = '' - if startData == 'chat': - retData = coreInst.chatInst.sendMessage - class OnionrSockets: def __init__(self, coreInst, socketInfo): '''Create a new Socket object. This interface is named a bit misleadingly @@ -64,8 +52,9 @@ class OnionrSockets: self.connected = False self.readData = [] - self.sendData = 0 + self.sendData = 0 + def startConn(): if self.isServer: self.createServer() else: From 55879b71a5dbde1439f58548921dfecadd83771d Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 20 Sep 2018 12:05:44 -0500 Subject: [PATCH 043/101] work on sockets --- onionr/onionr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index 26edf57d..f7224ddc 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -271,7 +271,6 @@ class Onionr: ''' def startChat(self): - self.onionrCore.daemonQueueAdd() socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, 'create': True, 'reason': reason}) self.onionrCore.daemonQueueAdd('startSocket', socketInfo) From c2b0277612e0d247d3050fe05a527cc5c7b22164 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 20 Sep 2018 12:07:50 -0500 Subject: [PATCH 044/101] work on sockets --- onionr/communicator2.py | 2 +- onionr/onionr.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 36b5794d..6716dcbe 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -479,7 +479,7 @@ class OnionrCommunicatorDaemon: def startSocket(self, startData): # Start a socket client - mySocket = onionrsockets.OnionrSockets(self._core, startData)) + mySocket = onionrsockets.OnionrSockets(self._core, startData) self.sockets[mySocket.socketID] = mySocket sockProgram = '' # Function for socket handler (application) diff --git a/onionr/onionr.py b/onionr/onionr.py index f7224ddc..8843aab9 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -218,7 +218,7 @@ class Onionr: 'getpasswd': self.printWebPassword, 'get-passwd': self.printWebPassword, - 'chat': self.startChat(), + 'chat': self.startChat, 'friend': self.friendCmd } From 2164ded6793b6ba3e3281bbc760eb8c65acbd8d3 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 20 Sep 2018 12:15:08 -0500 Subject: [PATCH 045/101] work on sockets --- onionr/onionr.py | 2 +- onionr/static-data/default-plugins/metadataprocessor/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index 8843aab9..adf247b3 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -271,7 +271,7 @@ class Onionr: ''' def startChat(self): - socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, 'create': True, 'reason': reason}) + socketInfo = json.dumps({'peer': '', 'address': '', 'port': port, 'create': True, 'reason': 'chat'}) self.onionrCore.daemonQueueAdd('startSocket', socketInfo) def getCommands(self): diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index b4065f11..759db071 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -49,7 +49,7 @@ def _processForwardKey(api, myBlock): ''' Get the forward secrecy key specified by the user for us to use ''' - peer = onionrusers.OnionrUser(self.api.get_core(), myBlock.signer) + peer = onionrusers.OnionrUser(api.get_core(), myBlock.signer) key = myBlock.getMetadata('newFSKey') # We don't need to validate here probably, but it helps From 7fa41f31e726d240611468b39e86701ac62b1749 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 20 Sep 2018 12:16:37 -0500 Subject: [PATCH 046/101] work on sockets --- onionr/onionr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index adf247b3..bb2114ca 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -271,7 +271,7 @@ class Onionr: ''' def startChat(self): - socketInfo = json.dumps({'peer': '', 'address': '', 'port': port, 'create': True, 'reason': 'chat'}) + socketInfo = json.dumps({'peer': '', 'address': '', 'port': 1337, 'create': True, 'reason': 'chat'}) self.onionrCore.daemonQueueAdd('startSocket', socketInfo) def getCommands(self): From d3f4e912f97c7c79d454a0ab22d5c403d085d096 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 20 Sep 2018 12:41:34 -0500 Subject: [PATCH 047/101] work on sockets --- onionr/communicator2.py | 2 +- onionr/netcontroller.py | 2 ++ onionr/onionr.py | 3 ++- onionr/onionrchat.py | 9 +++++---- onionr/onionrsockets.py | 13 +++++++------ 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 6716dcbe..7c8b6c0b 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -489,7 +489,7 @@ class OnionrCommunicatorDaemon: else: del self.sockets[mySocket.socketID] # Delete socket if we have no handler for it - threading.Thread(target=sockProgram, args=(self, mySocket)).start() + threading.Thread(target=sockProgram, args=(self, mySocket.socketID)).start() mySocket.startConn() def uploadBlock(self): diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 56b12573..69dd8aeb 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -72,6 +72,8 @@ class NetController: controlPort = random.randint(1025, 65535) + config.set('tor.controlPort', controlPort, savefile=True) + hashedPassword = subprocess.Popen([self.torBinary, '--hash-password', plaintext], stdout=subprocess.PIPE, stderr=subprocess.PIPE) for line in iter(hashedPassword.stdout.readline, b''): password = line.decode() diff --git a/onionr/onionr.py b/onionr/onionr.py index bb2114ca..12e9b2fd 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -271,7 +271,8 @@ class Onionr: ''' def startChat(self): - socketInfo = json.dumps({'peer': '', 'address': '', 'port': 1337, 'create': True, 'reason': 'chat'}) + peer = sys.argv[2] + socketInfo = json.dumps({'peer': '', 'address': peer, 'port': 1337, 'create': True, 'reason': 'chat'}) self.onionrCore.daemonQueueAdd('startSocket', socketInfo) def getCommands(self): diff --git a/onionr/onionrchat.py b/onionr/onionrchat.py index f37f270e..33981d41 100644 --- a/onionr/onionrchat.py +++ b/onionr/onionrchat.py @@ -19,12 +19,13 @@ ''' import logger, time class OnionrChat: - def __init__(self, communicatorInst, socketInst): + def __init__(self, communicatorInst, socketID): self.communicator = communicatorInst - self.socket = socketInst + self.socket = self.communicator.sockets[socketID] while True: time.sleep(2) - logger.info(self.socket.readData()) - self.socket.sendData('rekt') + logger.info('Chat: got %s' % (self.socket.getReadData(),)) + time.sleep(1) + self.socket.addSendData('rekt') return \ No newline at end of file diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 740c9054..dc9702c1 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -18,7 +18,7 @@ along with this program. If not, see . ''' import stem.control -import socket, selectors, socks, config +import socket, selectors, socks, config, uuid import onionrexceptions, time, onionrchat from dependencies import secrets sel = selectors.DefaultSelector() @@ -53,8 +53,9 @@ class OnionrSockets: self.readData = [] self.sendData = 0 + config.reload() - def startConn(): + def startConn(self): if self.isServer: self.createServer() else: @@ -68,8 +69,8 @@ class OnionrSockets: ourInternalPort = 1338 # Setup the empheral HS - with stem.control.Controller.from_port() as controller: - controller.authenticate() + with stem.control.Controller.from_port(port=config.get('tor.controlPort')) as controller: + controller.authenticate(config.get('tor.controlpassword')) socketHS = controller.create_ephemeral_hidden_service({ourPort: ourInternalPort}, await_publication = True) ourAddress = socketHS.service_id @@ -108,14 +109,14 @@ class OnionrSockets: sel.unregister(conn) conn.close() - def sendData(self, data): + def addSendData(self, data): try: data = data.encode() except AttributeError: pass self.sendData = data - def readData(self): + def getReadData(self): try: data = self.readData.pop(0) except IndexError: From 4e8f7e2761b6dd1adbfa0f7538d0077f7925b9f3 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 20 Sep 2018 23:47:40 -0500 Subject: [PATCH 048/101] work on sockets --- onionr/api.py | 2 +- onionr/communicator2.py | 26 +-- onionr/onionrsockets.py | 149 ++++++------------ .../default-plugins/metadataprocessor/main.py | 2 +- 4 files changed, 56 insertions(+), 123 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index adfabf88..21ba7a66 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -140,7 +140,7 @@ class API: self.overrideCSP = False return resp - + @app.route('/www/private/') def www_private(path): startTime = math.floor(time.time()) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 7c8b6c0b..56a8e8e6 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -108,6 +108,8 @@ class OnionrCommunicatorDaemon: cleanupTimer.count = (cleanupTimer.frequency - 60) announceTimer.count = (cleanupTimer.frequency - 60) + self.socketServer = onionrsockets.OnionrSocketServer(self._core) + # Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking try: while not self.shutdown: @@ -467,31 +469,15 @@ class OnionrCommunicatorDaemon: elif cmd[0] == 'uploadBlock': self.blockToUpload = cmd[1] threading.Thread(target=self.uploadBlock).start() - elif cmd[0] == 'startSocket': - # Create a socket or connect to one. - # The socket handler (such as the plugin or app using it) is specified in startData['reason] - startData = json.loads(cmd[1]) - threading.Thread(target=self.startSocket, args=(startData,)).start() + elif cmd[0] == 'addSocket': + socketInfo = json.loads(cmd[1]) + if socketInfo['reason'] in ('chat'): + onionrsockets.OnionrSocketClient(self._core, socketInfo['peer']) else: logger.info('Recieved daemonQueue command:' + cmd[0]) self.decrementThreadCount('daemonCommands') - def startSocket(self, startData): - # Start a socket client - mySocket = onionrsockets.OnionrSockets(self._core, startData) - self.sockets[mySocket.socketID] = mySocket - - sockProgram = '' # Function for socket handler (application) - - if startData['reason'] == 'chat': - sockProgram = onionrchat.OnionrChat - else: - del self.sockets[mySocket.socketID] # Delete socket if we have no handler for it - - threading.Thread(target=sockProgram, args=(self, mySocket.socketID)).start() - mySocket.startConn() - def uploadBlock(self): '''Upload our block to a few peers''' # when inserting a block, we try to upload it to a few peers to add some deniability diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index dc9702c1..bb505c8c 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -18,121 +18,68 @@ along with this program. If not, see . ''' import stem.control -import socket, selectors, socks, config, uuid -import onionrexceptions, time, onionrchat +import socks, config, uuid +import onionrexceptions, time, requests from dependencies import secrets -sel = selectors.DefaultSelector() +from flask import request, Response, abort -class OnionrSockets: - def __init__(self, coreInst, socketInfo): - '''Create a new Socket object. This interface is named a bit misleadingly - and does not actually forward network requests. - - Accepts coreInst, an instance of Onionr core library, and socketInfo, a dict with these values: - 'peer': peer master public key - 'address': string, if we're connecting to a socket, this is the address we connect to. Not applicable if we're creating our own - create: bool - ''' - self.socketID = secrets.token_hex(32) # Generate an ID for this socket +class OnionrSocketServer: + def __init__(self, coreInst): + self.sockets = {} # pubkey: tor address + self.connPool = {} + self.bindPort = 1337 self._core = coreInst - self.socketInfo = socketInfo + self.responseData = {} + self.killSocket = False + app = flask.Flask(__name__) - # Make sure socketInfo provides all necessary values - for i in ('peer', 'address', 'create', 'port'): - try: - socketInfo[i] - except KeyError: - raise ValueError('Must provide peer, address, and create in socketInfo dict argument') + http_server = WSGIServer((socket.service_id, bindPort), app) + http_server.serve_forever() - self.isServer = socketInfo['create'] # if we are the one creating the service + @app.route('/dc/', methods=['POST']) + def acceptConn(self): + data = request.form['data'] + data = self._core._utils.bytesTorStr(data) - self.remotePeer = socketInfo['peer'] - self.socketPort = socketInfo['port'] - self.serverAddress = socketInfo['address'] - self.connected = False - - self.readData = [] - self.sendData = 0 - config.reload() - - def startConn(self): - if self.isServer: - self.createServer() + if request.host in self.connPool: + self.connPool[request.host].append(data) else: - self.connectServer() - - def createServer(self): - # Create our HS and advertise it via a block - dataID = uuid.uuid4().hex - ourAddress = '' - ourPort = 1337 - ourInternalPort = 1338 + self.connPool[request.host] = [data] - # Setup the empheral HS + retData = self.responseData[request.host] + + self.responseData[request.host] = '' + + return retData + + def setResponseData(self, host, data): + self.responseData[host] = data + + def addSocket(self, peer): + bindPort = 1337 with stem.control.Controller.from_port(port=config.get('tor.controlPort')) as controller: controller.authenticate(config.get('tor.controlpassword')) - socketHS = controller.create_ephemeral_hidden_service({ourPort: ourInternalPort}, await_publication = True) - ourAddress = socketHS.service_id - # Advertise the server - meta = {'address': ourAddress, 'port': ourPort} - self._core.insertBlock(dataID, header='openSocket', encryptType='asym', asymPeer=self.remotePeer, sign=True, meta=meta) + socket = controller.create_ephemeral_hidden_service({80: bindPort}, await_publication = True) + self.sockets[peer] = socket.service_id - # Build the socket server - sock = socket.socket() - sock.bind(('127.0.0.1', ourInternalPort)) - sock.listen(100) - sock.setblocking(False) - sel.register(sock, selectors.EVENT_READ, self._accept) + self.responseData[socket.service_id] = '' - while True: - events = sel.select() - for key, mask in events: - callback = key.data - callback(key.fileobj, mask) + self._core.insertBlock(uuid.uuid4(), header='startSocket', sign=True, encryptType='asym', asymPeer=peer, meta={}) + while not self.killSocket: + time.sleep(3) return - - def _accept(self, sock, mask): - # Just accept the connection and pass it to our handler - conn, addr = sock.accept() - conn.setblocking(False) - sel.register(conn, selectors.EVENT_READ, self._read) - self.connected = True - - def _read(self, conn, mask): - data = conn.recv(1024) - if data: - data = data.decode() - self.readData.append(data) - else: - sel.unregister(conn) - conn.close() - - def addSendData(self, data): - try: - data = data.encode() - except AttributeError: - pass - self.sendData = data - def getReadData(self): - try: - data = self.readData.pop(0) - except IndexError: - data = '' - return data +class OnionrSocketClient: + def __init__(self, coreInst): + self.sockets = {} # pubkey: tor address + self.connPool = {} + self.bindPort = 1337 + self._core = coreInst + self.response = '' + self.request = '' + self.connected = False - def connectServer(self): - # Set the Tor proxy - socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', config.get('tor.socksport'), rdns=True) - socket.socket = socks.socksocket - remoteSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - with remoteSocket as s: - s.connect((self.serverAddress, self.port)) - data = s.recv(1024) - if self.sendData != 0: - s.send(self.sendData) - self.sendData = 0 - return \ No newline at end of file + def getResponse(self, peer): + self._core._utils.doPostRequest(self.) \ No newline at end of file diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 759db071..85b7d9de 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -91,7 +91,7 @@ def on_processBlocks(api): raise ValueError("Missing socket reason") socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, 'create': False, 'reason': reason}) - api.get_core().daemonQueueAdd('startSocket', socketInfo) + api.get_core().daemonQueueAdd('addSocket', socketInfo) def on_init(api, data = None): From 759da5509427d60e4dfa0b1b9cba80f5b79b0c33 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 22 Sep 2018 00:01:17 -0500 Subject: [PATCH 049/101] work on sockets --- onionr/communicator2.py | 10 ++++++--- onionr/onionr.py | 5 ++--- onionr/onionrsockets.py | 46 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 56a8e8e6..108ecf0d 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -109,6 +109,7 @@ class OnionrCommunicatorDaemon: announceTimer.count = (cleanupTimer.frequency - 60) self.socketServer = onionrsockets.OnionrSocketServer(self._core) + self.socketClient = onionrsockets.OnionrSocketClient(self._core) # Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking try: @@ -469,10 +470,13 @@ class OnionrCommunicatorDaemon: elif cmd[0] == 'uploadBlock': self.blockToUpload = cmd[1] threading.Thread(target=self.uploadBlock).start() - elif cmd[0] == 'addSocket': + elif cmd[0] == 'startSocket': socketInfo = json.loads(cmd[1]) - if socketInfo['reason'] in ('chat'): - onionrsockets.OnionrSocketClient(self._core, socketInfo['peer']) + peer = socketInfo['peer'] + reason = socketInfo['reason'] + self.socketServer.addSocket(peer, reason) + elif cmd[0] == 'connectSocket': + pass else: logger.info('Recieved daemonQueue command:' + cmd[0]) diff --git a/onionr/onionr.py b/onionr/onionr.py index 12e9b2fd..c1c07628 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -271,9 +271,8 @@ class Onionr: ''' def startChat(self): - peer = sys.argv[2] - socketInfo = json.dumps({'peer': '', 'address': peer, 'port': 1337, 'create': True, 'reason': 'chat'}) - self.onionrCore.daemonQueueAdd('startSocket', socketInfo) + data = json.dumps({'peer': sys.argv[2], 'reason': 'chat'}) + self.onionrCore.daemonQueueAdd('startSocket', data) def getCommands(self): return self.cmds diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index bb505c8c..73920f27 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -19,7 +19,7 @@ ''' import stem.control import socks, config, uuid -import onionrexceptions, time, requests +import onionrexceptions, time, requests, onionrblockapi from dependencies import secrets from flask import request, Response, abort @@ -27,10 +27,12 @@ class OnionrSocketServer: def __init__(self, coreInst): self.sockets = {} # pubkey: tor address self.connPool = {} + self.bindPort = 1337 self._core = coreInst self.responseData = {} self.killSocket = False + app = flask.Flask(__name__) http_server = WSGIServer((socket.service_id, bindPort), app) @@ -55,7 +57,7 @@ class OnionrSocketServer: def setResponseData(self, host, data): self.responseData[host] = data - def addSocket(self, peer): + def addSocket(self, peer, reason=''): bindPort = 1337 with stem.control.Controller.from_port(port=config.get('tor.controlPort')) as controller: controller.authenticate(config.get('tor.controlpassword')) @@ -65,7 +67,7 @@ class OnionrSocketServer: self.responseData[socket.service_id] = '' - self._core.insertBlock(uuid.uuid4(), header='startSocket', sign=True, encryptType='asym', asymPeer=peer, meta={}) + self._core.insertBlock(uuid.uuid4(), header='startSocket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason}) while not self.killSocket: time.sleep(3) @@ -75,11 +77,47 @@ class OnionrSocketClient: def __init__(self, coreInst): self.sockets = {} # pubkey: tor address self.connPool = {} + self.sendData = {} self.bindPort = 1337 self._core = coreInst self.response = '' self.request = '' self.connected = False + self.killSocket = False + def startSocket(self, peer): + address = '' + # Find the newest open socket for a given peer + for block in self._core.getBlocksByType('openSocket'): + block = onionrblockapi.Block(block, core=self._myCore) + if block.decrypt(): + if block.verifySig() and block.signer == peer: + address = block.getMetadata('address') + if self._core._utils.validateID(address): + # If we got their address, it is valid, and verified, we can break out + break + else: + address = '' + if address != '': + self.sockets[peer] = address + data = '' + while not self.killSocket: + try: + data = self.sendData[peer] + except KeyError: + pass + else: + self.sendData[peer] = '' + postData = {'data': data} + self.connPool[peer] = self._core._utils.doPostRequest('http://' + address + '/dc/', data=postData) + def getResponse(self, peer): - self._core._utils.doPostRequest(self.) \ No newline at end of file + retData = '' + try: + retData = self.connPool[peer] + except KeyError: + pass + return + + def sendData(self, peer, data): + self.sendData[peer] = data \ No newline at end of file From 70e2ccbc0a6c27d94cdfc8ca6bc73d4be3c82c40 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 22 Sep 2018 20:21:39 -0500 Subject: [PATCH 050/101] work on sockets --- onionr/communicator2.py | 13 +++++++++---- onionr/onionrsockets.py | 7 ++++--- .../default-plugins/metadataprocessor/main.py | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 108ecf0d..00e80e13 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -19,7 +19,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sys, os, core, config, json, requests, time, logger, threading, base64, onionr +import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block import onionrdaemontools, onionrsockets, onionrchat from dependencies import secrets @@ -471,12 +471,17 @@ class OnionrCommunicatorDaemon: self.blockToUpload = cmd[1] threading.Thread(target=self.uploadBlock).start() elif cmd[0] == 'startSocket': + # Create our own socket server socketInfo = json.loads(cmd[1]) peer = socketInfo['peer'] reason = socketInfo['reason'] - self.socketServer.addSocket(peer, reason) - elif cmd[0] == 'connectSocket': - pass + threading.Thread(target=self.socketServer.addSocket, args=(peer, reason)).start() + elif cmd[0] == 'addSocket': + # Socket server was created for us + socketInfo = json.loads(cmd[1]) + peer = socketInfo['peer'] + reason = socketInfo['reason'] + threading.Thread(target=self.socketClient.startSocket, args=(peer, reason)).start() else: logger.info('Recieved daemonQueue command:' + cmd[0]) diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 73920f27..735b22c6 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -18,6 +18,7 @@ along with this program. If not, see . ''' import stem.control +import threading import socks, config, uuid import onionrexceptions, time, requests, onionrblockapi from dependencies import secrets @@ -36,7 +37,7 @@ class OnionrSocketServer: app = flask.Flask(__name__) http_server = WSGIServer((socket.service_id, bindPort), app) - http_server.serve_forever() + threading.Thread(target=http_server.serve_forever).start() @app.route('/dc/', methods=['POST']) def acceptConn(self): @@ -67,7 +68,7 @@ class OnionrSocketServer: self.responseData[socket.service_id] = '' - self._core.insertBlock(uuid.uuid4(), header='startSocket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason}) + self._core.insertBlock(uuid.uuid4(), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason}) while not self.killSocket: time.sleep(3) @@ -85,7 +86,7 @@ class OnionrSocketClient: self.connected = False self.killSocket = False - def startSocket(self, peer): + def startSocket(self, peer, reason): address = '' # Find the newest open socket for a given peer for block in self._core.getBlocksByType('openSocket'): diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 85b7d9de..2b4deb86 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -75,7 +75,7 @@ def on_processBlocks(api): if api.data['validSig'] == True: _processForwardKey(api, myBlock) # socket blocks - elif blockType == 'openSocket': + elif blockType == 'socket': if api.data['validSig'] == True and myBlock.decrypted: # we check if it is decrypted as a way of seeing if it was for us try: address = api.data['address'] From ad3d7940f5009a77bddcea58304fa76eeb7d3556 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 22 Sep 2018 23:53:09 -0500 Subject: [PATCH 051/101] work on sockets --- onionr/communicator2.py | 9 ++++--- onionr/core.py | 3 +++ onionr/onionr.py | 8 ++++-- onionr/onionrsockets.py | 56 ++++++++++++++++++++++++++--------------- onionr/onionrutils.py | 3 ++- 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 00e80e13..3cf69513 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -108,7 +108,8 @@ class OnionrCommunicatorDaemon: cleanupTimer.count = (cleanupTimer.frequency - 60) announceTimer.count = (cleanupTimer.frequency - 60) - self.socketServer = onionrsockets.OnionrSocketServer(self._core) + self.socketServer = threading.Thread(target=onionrsockets.OnionrSocketServer, args=(self._core,)) + self.socketServer.start() self.socketClient = onionrsockets.OnionrSocketClient(self._core) # Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking @@ -124,6 +125,7 @@ class OnionrCommunicatorDaemon: pass logger.info('Goodbye.') + self._core.killSockets = True self._core._utils.localCommand('shutdown') # shutdown the api time.sleep(0.5) @@ -473,9 +475,8 @@ class OnionrCommunicatorDaemon: elif cmd[0] == 'startSocket': # Create our own socket server socketInfo = json.loads(cmd[1]) - peer = socketInfo['peer'] - reason = socketInfo['reason'] - threading.Thread(target=self.socketServer.addSocket, args=(peer, reason)).start() + socketInfo['id'] = uuid.uuid4() + self._core.startSocket = socketInfo elif cmd[0] == 'addSocket': # Socket server was created for us socketInfo = json.loads(cmd[1]) diff --git a/onionr/core.py b/onionr/core.py index f6cb88b6..17036e30 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -50,6 +50,9 @@ class Core: self.dbCreate = dbcreator.DBCreator(self) self.forwardKeysFile = 'data/forward-keys.db' + self.killSockets = False + self.startSocket = {} + self.usageFile = 'data/disk-usage.txt' self.config = config diff --git a/onionr/onionr.py b/onionr/onionr.py index c1c07628..593055ef 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -271,8 +271,12 @@ class Onionr: ''' def startChat(self): - data = json.dumps({'peer': sys.argv[2], 'reason': 'chat'}) - self.onionrCore.daemonQueueAdd('startSocket', data) + try: + data = json.dumps({'peer': sys.argv[2], 'reason': 'chat'}) + except IndexError: + logger.error('Must specify peer to chat with.') + else: + self.onionrCore.daemonQueueAdd('startSocket', data) def getCommands(self): return self.cmds diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 735b22c6..4d16ea7b 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -20,41 +20,59 @@ import stem.control import threading import socks, config, uuid -import onionrexceptions, time, requests, onionrblockapi +import onionrexceptions, time, requests, onionrblockapi, logger from dependencies import secrets +from gevent.pywsgi import WSGIServer from flask import request, Response, abort - +import flask class OnionrSocketServer: def __init__(self, coreInst): + app = flask.Flask(__name__) self.sockets = {} # pubkey: tor address self.connPool = {} self.bindPort = 1337 self._core = coreInst self.responseData = {} - self.killSocket = False + threading.Thread(target=self.detectShutdown).start() + threading.Thread(target=self.socketStarter).start() app = flask.Flask(__name__) - - http_server = WSGIServer((socket.service_id, bindPort), app) - threading.Thread(target=http_server.serve_forever).start() + self.http_server = WSGIServer(('127.0.0.1', self.bindPort), app) + self.http_server.serve_forever() - @app.route('/dc/', methods=['POST']) - def acceptConn(self): - data = request.form['data'] - data = self._core._utils.bytesTorStr(data) + @app.route('/dc/', methods=['POST']) + def acceptConn(self): + data = request.form['data'] + data = self._core._utils.bytesTorStr(data) - if request.host in self.connPool: - self.connPool[request.host].append(data) - else: - self.connPool[request.host] = [data] + if request.host in self.connPool: + self.connPool[request.host].append(data) + else: + self.connPool[request.host] = [data] - retData = self.responseData[request.host] + retData = self.responseData[request.host] - self.responseData[request.host] = '' + self.responseData[request.host] = '' - return retData + return retData + def socketStarter(self): + while not self._core.killSockets: + try: + self.addSocket(self._core.startSocket['peer'], reason=self._core.startSocket['reason']) + except KeyError: + pass + else: + logger.info('%s socket started with %s' % (self._core.startSocket['reason'], self._core.startSocket['peer'])) + self._core.startSocket = {} + + def detectShutdown(self): + while not self._core.killSockets: + time.sleep(5) + logger.info('Killing socket server') + self.http_server.stop() + def setResponseData(self, host, data): self.responseData[host] = data @@ -68,10 +86,8 @@ class OnionrSocketServer: self.responseData[socket.service_id] = '' - self._core.insertBlock(uuid.uuid4(), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason}) + self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason}) - while not self.killSocket: - time.sleep(3) return class OnionrSocketClient: diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index b5fc10c1..15bbc866 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -593,7 +593,8 @@ class OnionrUtils: except ValueError as e: logger.debug('Failed to make request', error = e) except requests.exceptions.RequestException as e: - logger.debug('Error: %s' % str(e)) + if not 'ConnectTimeoutError' in str(e): + logger.debug('Error: %s' % str(e)) retData = False return retData From 711cf3f2d3b01eabea6f3c6af2d5967b6687112f Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 23 Sep 2018 20:47:27 -0500 Subject: [PATCH 052/101] work on sockets --- onionr/communicator2.py | 7 +++--- onionr/core.py | 4 +++ onionr/onionrchat.py | 29 ++++++++++++++-------- onionr/onionrsockets.py | 54 ++++++++++++++++++++++++++--------------- requirements.txt | 1 + 5 files changed, 63 insertions(+), 32 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 3cf69513..24f64c09 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -80,9 +80,7 @@ class OnionrCommunicatorDaemon: #self.daemonTools = onionrdaemontools.DaemonTools(self) self.daemonTools = onionrdaemontools.DaemonTools(self) - # Active sockets for direct connections - self.sockets = {} - self.socketExchange = {} # Socket ID exchange + self._chat = onionrchat.OnionrChat(self) if debug or developmentMode: OnionrCommunicatorTimers(self, self.heartbeat, 10) @@ -112,6 +110,9 @@ class OnionrCommunicatorDaemon: self.socketServer.start() self.socketClient = onionrsockets.OnionrSocketClient(self._core) + # Loads chat messages into memory + threading.Thread(target=self._chat.chatHandler).start() + # Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking try: while not self.shutdown: diff --git a/onionr/core.py b/onionr/core.py index 17036e30..ac1c4902 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -50,8 +50,12 @@ class Core: self.dbCreate = dbcreator.DBCreator(self) self.forwardKeysFile = 'data/forward-keys.db' + # Socket data, defined here because of multithreading constraints with gevent self.killSockets = False self.startSocket = {} + self.socketServerConnData = {} + self.socketReasons = {} + self.socketServerResponseData = {} self.usageFile = 'data/disk-usage.txt' self.config = config diff --git a/onionr/onionrchat.py b/onionr/onionrchat.py index 33981d41..6d8fafa6 100644 --- a/onionr/onionrchat.py +++ b/onionr/onionrchat.py @@ -18,14 +18,23 @@ along with this program. If not, see . ''' import logger, time -class OnionrChat: - def __init__(self, communicatorInst, socketID): - self.communicator = communicatorInst - self.socket = self.communicator.sockets[socketID] - while True: - time.sleep(2) - logger.info('Chat: got %s' % (self.socket.getReadData(),)) - time.sleep(1) - self.socket.addSendData('rekt') - return \ No newline at end of file +class OnionrChat: + def __init__(self, communicatorInst): + '''OnionrChat uses onionrsockets (handled by the communicator) to exchange direct chat messages''' + self.communicator = communicatorInst + self._core = self.communicator._core + self._utils = self._core._utils + + self.chats = {} # {'peer': {'date': date, message': message}} + + def chatHandler(self): + while not self.communicator.shutdown: + for peer in self._core.socketServerConnData: + try: + assert self._core.socketReasons[peer] == "chat" + except (AssertionError, KeyError) as e: + continue + else: + self.chats[peer] = {'date': self._core.socketServerConnData[peer]['date'], 'data': self._core.socketServerConnData[peer]['data']} + logger.info("CHAT MESSAGE RECIEVED: %s" % self.chats[peer]['data']) \ No newline at end of file diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 4d16ea7b..72bdbe38 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -27,12 +27,16 @@ from flask import request, Response, abort import flask class OnionrSocketServer: def __init__(self, coreInst): - app = flask.Flask(__name__) - self.sockets = {} # pubkey: tor address - self.connPool = {} - - self.bindPort = 1337 self._core = coreInst + app = flask.Flask(__name__) + self._core.socketServerConnData = {} + self.bindPort = 0 + + self.sockets = {} + + while self.bindPort < 1024: + self.bindPort = secrets.randbelow(65535) + self.responseData = {} threading.Thread(target=self.detectShutdown).start() @@ -45,15 +49,27 @@ class OnionrSocketServer: def acceptConn(self): data = request.form['data'] data = self._core._utils.bytesTorStr(data) - - if request.host in self.connPool: - self.connPool[request.host].append(data) + data = {'date': self._core._utils.getEpoch(), 'data': data} + myPeer = '' + retData = '' + for peer in self.sockets: + if self.sockets[peer] == request.host: + myPeer = peer + break else: - self.connPool[request.host] = [data] + return "" - retData = self.responseData[request.host] + if request.host in self.sockets: + self._core.socketServerConnData[myPeer].append(data) + else: + self._core.socketServerConnData[myPeer] = [data] - self.responseData[request.host] = '' + try: + retData = self._core.socketServerResponseData[myPeer] + except KeyError: + pass + + self._core.socketServerConnData[myPeer] = '' return retData @@ -66,6 +82,7 @@ class OnionrSocketServer: else: logger.info('%s socket started with %s' % (self._core.startSocket['reason'], self._core.startSocket['peer'])) self._core.startSocket = {} + time.sleep(1) def detectShutdown(self): while not self._core.killSockets: @@ -73,11 +90,11 @@ class OnionrSocketServer: logger.info('Killing socket server') self.http_server.stop() - def setResponseData(self, host, data): - self.responseData[host] = data - def addSocket(self, peer, reason=''): bindPort = 1337 + + assert len(reason) <= 12 + with stem.control.Controller.from_port(port=config.get('tor.controlPort')) as controller: controller.authenticate(config.get('tor.controlpassword')) @@ -86,8 +103,8 @@ class OnionrSocketServer: self.responseData[socket.service_id] = '' - self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason}) - + self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPee=peer, meta={'reason': reason}) + self._core.socketReasons[peer] = reason return class OnionrSocketClient: @@ -95,7 +112,6 @@ class OnionrSocketClient: self.sockets = {} # pubkey: tor address self.connPool = {} self.sendData = {} - self.bindPort = 1337 self._core = coreInst self.response = '' self.request = '' @@ -117,7 +133,7 @@ class OnionrSocketClient: address = '' if address != '': self.sockets[peer] = address - data = '' + data = 'hey' while not self.killSocket: try: data = self.sendData[peer] @@ -126,7 +142,7 @@ class OnionrSocketClient: else: self.sendData[peer] = '' postData = {'data': data} - self.connPool[peer] = self._core._utils.doPostRequest('http://' + address + '/dc/', data=postData) + self.connPool[peer] = {'date': self._core._utils.getEpoch(), 'data': self._core._utils.doPostRequest('http://' + address + '/dc/', data=postData)} def getResponse(self, peer): retData = '' diff --git a/requirements.txt b/requirements.txt index 69322e22..05797aed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ simple_crypt==4.1.7 Flask==1.0.2 PySocks==1.6.8 stem==1.6.0 +ntfy==2.6.0 From 49aae74e721a261545e4cf60434c93728c5ab5d8 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 23 Sep 2018 21:02:39 -0500 Subject: [PATCH 053/101] work on sockets --- onionr/onionrsockets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 72bdbe38..27296a58 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -103,7 +103,7 @@ class OnionrSocketServer: self.responseData[socket.service_id] = '' - self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPee=peer, meta={'reason': reason}) + self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason}) self._core.socketReasons[peer] = reason return From 67b9f6e51fbae49829792b0bbfa5c7b09b58e83c Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 24 Sep 2018 16:13:40 -0500 Subject: [PATCH 054/101] work on sockets --- onionr/onionrsockets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 27296a58..d58a6d21 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -132,6 +132,7 @@ class OnionrSocketClient: else: address = '' if address != '': + logger.info('%s socket client started with %s' % (reason, peer)) self.sockets[peer] = address data = 'hey' while not self.killSocket: From fa701f37dc21969c1d1a3b80bf7930aa22b213bc Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 24 Sep 2018 16:21:59 -0500 Subject: [PATCH 055/101] work on sockets --- onionr/onionrsockets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index d58a6d21..fb96281c 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -121,7 +121,7 @@ class OnionrSocketClient: def startSocket(self, peer, reason): address = '' # Find the newest open socket for a given peer - for block in self._core.getBlocksByType('openSocket'): + for block in self._core.getBlocksByType('socket'): block = onionrblockapi.Block(block, core=self._myCore) if block.decrypt(): if block.verifySig() and block.signer == peer: From 8b4105fac4df74c7b512137b9d7ee34aaaf7139d Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 24 Sep 2018 17:04:17 -0500 Subject: [PATCH 056/101] work on sockets --- onionr/onionrsockets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index fb96281c..726cb169 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -120,6 +120,7 @@ class OnionrSocketClient: def startSocket(self, peer, reason): address = '' + logger.info('Trying to find socket server for %s' % (peer,)) # Find the newest open socket for a given peer for block in self._core.getBlocksByType('socket'): block = onionrblockapi.Block(block, core=self._myCore) @@ -128,7 +129,8 @@ class OnionrSocketClient: address = block.getMetadata('address') if self._core._utils.validateID(address): # If we got their address, it is valid, and verified, we can break out - break + if block.getMetadata('reason') == 'chat': + break else: address = '' if address != '': From 6e55636e781b289ff8dd59798218c7a223daee41 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Mon, 24 Sep 2018 16:48:00 -0700 Subject: [PATCH 057/101] test --- onionr/api.py | 12 +++-- onionr/communicator2.py | 18 ++++---- onionr/core.py | 8 ++-- onionr/netcontroller.py | 2 +- onionr/onionrblockapi.py | 2 +- onionr/onionrdaemontools.py | 8 ++-- onionr/onionrpeers.py | 8 ++-- onionr/onionrutils.py | 12 ++--- onionr/static-data/default_config.json | 61 ++++++++++++++------------ 9 files changed, 72 insertions(+), 59 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index 761bb0a0..ec540814 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -150,14 +150,17 @@ class API: self.validateHost('private') + if config.get('www.public.guess_mime', True): + self.mimeType = API.guessMime(path) + endTime = math.floor(time.time()) elapsed = endTime - startTime if not hmac.compare_digest(timingToken, self.timeBypassToken): - if elapsed < self._privateDelayTime: + if (elapsed < self._privateDelayTime) and config.get('www.private.timing_protection', True): time.sleep(self._privateDelayTime - elapsed) - return send_from_directory('static-data/www/private/', path) + return send_from_directory(config.get('www.private.path', 'static-data/www/private/'), path) @app.route('/www/public/') def www_public(path): @@ -166,7 +169,10 @@ class API: self.validateHost('public') - return send_from_directory('static-data/www/public/', path) + if config.get('www.public.guess_mime', True): + self.mimeType = API.guessMime(path) + + return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path) @app.route('/ui/') def ui_private(path): diff --git a/onionr/communicator2.py b/onionr/communicator2.py index f203dd70..4a80e6f7 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -87,8 +87,8 @@ class OnionrCommunicatorDaemon: OnionrCommunicatorTimers(self, self.daemonCommands, 5) OnionrCommunicatorTimers(self, self.detectAPICrash, 5) peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60, maxThreads=1) - OnionrCommunicatorTimers(self, self.lookupBlocks, self._core.config.get('timers.lookupBlocks'), requiresPeer=True, maxThreads=1) - OnionrCommunicatorTimers(self, self.getBlocks, self._core.config.get('timers.getBlocks'), requiresPeer=True) + OnionrCommunicatorTimers(self, self.lookupBlocks, self._core.config.get('timers.lookup_blocks'), requiresPeer=True, maxThreads=1) + OnionrCommunicatorTimers(self, self.getBlocks, self._core.config.get('timers.get_blocks'), requiresPeer=True) OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58) OnionrCommunicatorTimers(self, self.daemonTools.cleanOldBlocks, 65) OnionrCommunicatorTimers(self, self.lookupKeys, 60, requiresPeer=True) @@ -252,7 +252,7 @@ class OnionrCommunicatorDaemon: except AttributeError: pass # Punish peer for sharing invalid block (not always malicious, but is bad regardless) - onionrpeers.PeerProfiles(peerUsed, self._core).addScore(-50) + onionrpeers.PeerProfiles(peerUsed, self._core).addScore(-50) logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash) if removeFromQueue: self.blockQueue.remove(blockHash) # remove from block queue both if success or false @@ -298,7 +298,7 @@ class OnionrCommunicatorDaemon: '''Manages the self.onlinePeers attribute list, connects to more peers if we have none connected''' logger.info('Refreshing peer pool.') - maxPeers = int(config.get('peers.maxConnect')) + maxPeers = int(config.get('peers.max_connect', 10)) needed = maxPeers - len(self.onlinePeers) for i in range(needed): @@ -331,7 +331,7 @@ class OnionrCommunicatorDaemon: raise onionrexceptions.InvalidAddress('Will not attempt connection test to invalid address') else: peerList = self._core.listAdders() - + peerList = onionrpeers.getScoreSortedPeerList(self._core) if len(peerList) == 0 or useBootstrap: @@ -339,7 +339,7 @@ class OnionrCommunicatorDaemon: self.addBootstrapListToPeerList(peerList) for address in peerList: - if not config.get('tor.v3onions') and len(address) == 62: + if not config.get('tor.v3_onions') and len(address) == 62: continue if len(address) == 0 or address in tried or address in self.onlinePeers or address in self.cooldownPeer: continue @@ -352,7 +352,7 @@ class OnionrCommunicatorDaemon: self.onlinePeers.append(address) self.connectTimes[address] = self._core._utils.getEpoch() retData = address - + # add peer to profile list if they're not in it for profile in self.peerProfiles: if profile.address == address: @@ -416,7 +416,7 @@ class OnionrCommunicatorDaemon: self._core.setAddressInfo(peer, 'lastConnect', self._core._utils.getEpoch()) self.getPeerProfileInstance(peer).addScore(1) return retData - + def getPeerProfileInstance(self, peer): '''Gets a peer profile instance from the list of profiles, by address name''' for i in self.peerProfiles: @@ -543,7 +543,7 @@ class OnionrCommunicatorTimers: if self.makeThread: for i in range(self.threadAmount): if self.daemonInstance.threadCounts[self.timerFunction.__name__] >= self.maxThreads: - logger.warn(self.timerFunction.__name__ + ' has too many current threads to start anymore.') + logger.warn('%s is currently using the maximum number of threads, not starting another.' % self.timerFunction.__name__) else: self.daemonInstance.threadCounts[self.timerFunction.__name__] += 1 newThread = threading.Thread(target=self.timerFunction) diff --git a/onionr/core.py b/onionr/core.py index ab8b640b..bff27e45 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -126,7 +126,7 @@ class Core: ''' Add an address to the address database (only tor currently) ''' - if address == config.get('i2p.ownAddr', None): + if address == config.get('i2p.own_addr', None): return False if self._utils.validateID(address): @@ -190,7 +190,7 @@ class Core: blockFile = 'data/blocks/' + block + '.dat' dataSize = 0 try: - ''' Get size of data when loaded as an object/var, rather than on disk, + ''' Get size of data when loaded as an object/var, rather than on disk, to avoid conflict with getsizeof when saving blocks ''' with open(blockFile, 'r') as data: @@ -273,7 +273,7 @@ class Core: if not type(data) is bytes: data = data.encode() - + dataHash = self._getSha3Hash(data) if type(dataHash) is bytes: @@ -722,7 +722,7 @@ class Core: metadata['sig'] = signature metadata['signer'] = signer metadata['time'] = str(self._utils.getEpoch()) - + # send block data (and metadata) to POW module to get tokenized block data proof = onionrproofs.POW(metadata, data) payload = proof.waitForResult() diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 3749ce7a..75034f27 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -49,7 +49,7 @@ class NetController: Generate a torrc file for our tor instance ''' hsVer = '# v2 onions' - if config.get('tor.v3onions'): + if config.get('tor.v3_onions'): hsVer = 'HiddenServiceVersion 3' logger.info('Using v3 onions :)') if os.path.exists(self.torConfigLocation): diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index 97b34730..825e61e5 100644 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -762,7 +762,7 @@ class Block: return False # dump old cached blocks if the size exeeds the maximum - if sys.getsizeof(Block.blockCacheOrder) >= config.get('allocations.blockCacheTotal', 50000000): # 50MB default cache size + if sys.getsizeof(Block.blockCacheOrder) >= config.get('allocations.block_cache_total', 50000000): # 50MB default cache size del Block.blockCache[blockCacheOrder.pop(0)] # cache block content diff --git a/onionr/onionrdaemontools.py b/onionr/onionrdaemontools.py index bbd7af64..f2c53fa7 100644 --- a/onionr/onionrdaemontools.py +++ b/onionr/onionrdaemontools.py @@ -63,14 +63,14 @@ class DaemonTools: logger.warn('Network check failed, are you connected to the internet?') self.daemon.isOnline = False self.daemon.decrementThreadCount('netCheck') - + def cleanOldBlocks(self): '''Delete old blocks if our disk allocation is full/near full''' while self.daemon._core._utils.storageCounter.isFull(): oldest = self.daemon._core.getBlockList()[0] self.daemon._core._blacklist.addToDB(oldest) self.daemon._core.removeBlock(oldest) - logger.info('Deleted block: %s' % (oldest,)) + logger.info('Deleted block: %s' % (oldest,)) self.daemon.decrementThreadCount('cleanOldBlocks') def cooldownPeer(self): @@ -88,7 +88,7 @@ class DaemonTools: del self.daemon.cooldownPeer[peer] # Cool down a peer, if we have max connections alive for long enough - if onlinePeerAmount >= self.daemon._core.config.get('peers.maxConnect'): + if onlinePeerAmount >= self.daemon._core.config.get('peers.max_connect', 10): finding = True while finding: try: @@ -102,4 +102,4 @@ class DaemonTools: else: self.daemon.removeOnlinePeer(toCool) self.daemon.cooldownPeer[toCool] = self.daemon._core._utils.getEpoch() - self.daemon.decrementThreadCount('cooldownPeer') \ No newline at end of file + self.daemon.decrementThreadCount('cooldownPeer') diff --git a/onionr/onionrpeers.py b/onionr/onionrpeers.py index 322db9ba..62716eee 100644 --- a/onionr/onionrpeers.py +++ b/onionr/onionrpeers.py @@ -44,7 +44,7 @@ class PeerProfiles: except (TypeError, ValueError) as e: self.success = 0 self.score = self.success - + def saveScore(self): '''Save the node's score to the database''' self.coreInst.setAddressInfo(self.address, 'success', self.score) @@ -79,8 +79,8 @@ def peerCleanup(coreInst): logger.info('Cleaning peers...') config.reload() - minScore = int(config.get('peers.minimumScore')) - maxPeers = int(config.get('peers.maxStoredPeers')) + minScore = int(config.get('peers.minimum_score', -100)) + maxPeers = int(config.get('peers.max_stored', 5000)) adders = getScoreSortedPeerList(coreInst) adders.reverse() @@ -102,4 +102,4 @@ def peerCleanup(coreInst): logger.warn('Removed address ' + address + '.') # Unban probably not malicious peers TODO improve - coreInst._blacklist.deleteExpired(dataType=1) \ No newline at end of file + coreInst._blacklist.deleteExpired(dataType=1) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index a383684c..8f0ebcca 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -131,11 +131,11 @@ class OnionrUtils: for adder in newAdderList.split(','): adder = adder.strip() if not adder in self._core.listAdders(randomOrder = False) and adder != self.getMyAddress() and not self._core._blacklist.inBlacklist(adder): - if not config.get('tor.v3onions') and len(adder) == 62: + if not config.get('tor.v3_onions') and len(adder) == 62: continue if self._core.addAddress(adder): # Check if we have the maxmium amount of allowed stored peers - if config.get('peers.maxStoredPeers') > len(self._core.listAdders()): + if config.get('peers.max_stored') > len(self._core.listAdders()): logger.info('Added %s to db.' % adder, timestamp = True) retVal = True else: @@ -635,7 +635,7 @@ class OnionrUtils: else: self.powSalt = retData return retData - + def strToBytes(self, data): try: data = data.encode() @@ -648,7 +648,7 @@ class OnionrUtils: except AttributeError: pass return data - + def checkNetwork(self, torPort=0): '''Check if we are connected to the internet (through Tor)''' retData = False @@ -656,7 +656,7 @@ class OnionrUtils: try: with open('static-data/connect-check.txt', 'r') as connectTest: connectURLs = connectTest.read().split(',') - + for url in connectURLs: if self.doGetRequest(url, port=torPort) != False: retData = True @@ -689,4 +689,4 @@ def humanSize(num, suffix='B'): if abs(num) < 1024.0: return "%.1f %s%s" % (num, unit, suffix) num /= 1024.0 - return "%.1f %s%s" % (num, 'Yi', suffix) \ No newline at end of file + return "%.1f %s%s" % (num, 'Yi', suffix) diff --git a/onionr/static-data/default_config.json b/onionr/static-data/default_config.json index 5ebcfa4a..a902469e 100644 --- a/onionr/static-data/default_config.json +++ b/onionr/static-data/default_config.json @@ -1,6 +1,6 @@ { "general" : { - "dev_mode": true, + "dev_mode" : true, "display_header" : true, "direct_connect" : { @@ -11,11 +11,16 @@ "www" : { "public" : { - "run" : true + "run" : true, + "path" : "static-data/www/public/", + "guess_mime" : true }, "private" : { - "run" : true + "run" : true, + "path" : "static-data/www/private/", + "guess_mime" : true, + "timing_protection" : true }, "ui" : { @@ -28,41 +33,43 @@ }, - "log": { - "file": { - "output": true, - "path": "data/output.log" + "log" : { + "file" : { + "output" : true, + "path" : "data/output.log" }, - "console": { - "output": true, - "color": true + "console" : { + "output" : true, + "color" : true } }, "tor" : { - "v3onions": false + "v3onions" : false }, - "i2p":{ - "host": false, - "connect": true, - "ownAddr": "" + "i2p" : { + "host" : false, + "connect" : true, + "own_addr" : "" }, - "allocations":{ - "disk": 10000000000, - "netTotal": 1000000000, - "blockCache": 5000000, - "blockCacheTotal": 50000000 + "allocations" : { + "disk" : 10000000000, + "net_total" : 1000000000, + "blockCache" : 5000000, + "blockCacheTotal" : 50000000 }, - "peers":{ - "minimumScore": -100, - "maxStoredPeers": 5000, - "maxConnect": 10 + + "peers" : { + "minimum_score" : -100, + "max_stored_peers" : 5000, + "max_connect" : 10 }, - "timers":{ - "lookupBlocks": 25, - "getBlocks": 30 + + "timers" : { + "lookup_blocks" : 25, + "get_blocks" : 30 } } From d7392213cb0fd8b71b8299cc92c448e34a7445a3 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Mon, 24 Sep 2018 21:16:51 -0700 Subject: [PATCH 058/101] Slight changes, I forgot --- Makefile | 1 + onionr/onionrblockapi.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 13d9c0f9..1550e078 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ soft-reset: reset: @echo "Hard-resetting Onionr..." rm -rf onionr/data/ | true > /dev/null 2>&1 + cd onionr/static-data/www/ui/; rm -rf ./dist; python compile.py #@./RUN-LINUX.sh version | grep -v "Failed" --color=always plugins-reset: diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index 825e61e5..87ca2984 100644 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -188,7 +188,11 @@ class Block: return True except Exception as e: - logger.error('Failed to update block data.', error = e, timestamp = False) + logger.error('Failed to parse block %s.' % self.getHash(), error = e, timestamp = False) + + # if block can't be parsed, it's a waste of precious space. Throw it away. + if not self.delete(): + logger.error('Failed to delete invalid block %s.' % self.getHash(), error = e) self.valid = False return False From 3711b02924eff9ca27159ea6df587f42b5fb1c80 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Mon, 24 Sep 2018 21:26:16 -0700 Subject: [PATCH 059/101] oh makefile... --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1550e078..9ee7df8d 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ soft-reset: reset: @echo "Hard-resetting Onionr..." rm -rf onionr/data/ | true > /dev/null 2>&1 - cd onionr/static-data/www/ui/; rm -rf ./dist; python compile.py + cd onionr/static-data/www/ui/; rm -rf ./dist; python compile.py #@./RUN-LINUX.sh version | grep -v "Failed" --color=always plugins-reset: From 1a856c365f6a9b94ebd736f91d74b85644ef1f58 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 25 Sep 2018 23:58:11 -0500 Subject: [PATCH 060/101] work on sockets, added profile setter script --- onionr/api.py | 6 +-- onionr/blockprocessor.py | 0 onionr/communicator2.py | 2 +- onionr/config.py | 9 +++- onionr/core.py | 44 +++++++++++-------- onionr/netcontroller.py | 21 ++++++--- onionr/onionr.py | 35 +++++++++------ onionr/onionrblacklist.py | 2 +- onionr/onionrblockapi.py | 4 +- onionr/onionrchat.py | 11 ++++- onionr/onionrcrypto.py | 6 +-- onionr/onionrplugins.py | 11 ++++- onionr/onionrsockets.py | 28 ++++++++---- onionr/onionrutils.py | 15 ++++--- .../default-plugins/metadataprocessor/main.py | 11 +++-- setprofile.sh | 7 +++ 16 files changed, 140 insertions(+), 72 deletions(-) delete mode 100644 onionr/blockprocessor.py create mode 100755 setprofile.sh diff --git a/onionr/api.py b/onionr/api.py index 21ba7a66..79df9451 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -102,7 +102,7 @@ class API: self.mimeType = 'text/plain' self.overrideCSP = False - with open('data/time-bypass.txt', 'w') as bypass: + with open(self._core.dataDir + 'time-bypass.txt', 'w') as bypass: bypass.write(self.timeBypassToken) if not debug and not self._developmentMode: @@ -111,7 +111,7 @@ class API: else: self.host = '127.0.0.1' - with open('data/host.txt', 'w') as file: + with open(self._core.dataDir + 'host.txt', 'w') as file: file.write(self.host) @app.before_request @@ -466,7 +466,7 @@ class API: elif action == 'getData': resp = '' if self._utils.validateHash(data): - if os.path.exists('data/blocks/' + data + '.dat'): + if os.path.exists(self._core.dataDir + 'blocks/' + data + '.dat'): block = Block(hash=data.encode(), core=self._core) resp = base64.b64encode(block.getRaw().encode()).decode() if len(resp) == 0: diff --git a/onionr/blockprocessor.py b/onionr/blockprocessor.py deleted file mode 100644 index e69de29b..00000000 diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 24f64c09..fc62fe44 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -459,7 +459,7 @@ class OnionrCommunicatorDaemon: self.announce(cmd[1]) elif cmd[0] == 'runCheck': logger.debug('Status check; looks good.') - open('data/.runcheck', 'w+').close() + open(self._core.dataDir + '.runcheck', 'w+').close() elif cmd[0] == 'connectedPeers': self.printOnlinePeers() elif cmd[0] == 'kex': diff --git a/onionr/config.py b/onionr/config.py index 880b4dba..1e782dbe 100644 --- a/onionr/config.py +++ b/onionr/config.py @@ -20,7 +20,14 @@ import os, json, logger -_configfile = os.path.abspath('data/config.json') +try: + dataDir = os.environ['ONIONR_HOME'] + if not dataDir.endswith('/'): + dataDir += '/' +except KeyError: + dataDir = 'data/' + +_configfile = os.path.abspath(dataDir + 'config.json') _config = {} def get(key, default = None): diff --git a/onionr/core.py b/onionr/core.py index ac1c4902..6bd88eaa 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -35,20 +35,28 @@ class Core: ''' Initialize Core Onionr library ''' + try: - self.queueDB = 'data/queue.db' - self.peerDB = 'data/peers.db' - self.blockDB = 'data/blocks.db' - self.blockDataLocation = 'data/blocks/' - self.addressDB = 'data/address.db' + self.dataDir = os.environ['ONIONR_HOME'] + if not self.dataDir.endswith('/'): + self.dataDir += '/' + except KeyError: + self.dataDir = 'data/' + + try: + self.queueDB = self.dataDir + 'queue.db' + self.peerDB = self.dataDir + 'peers.db' + self.blockDB = self.dataDir + 'blocks.db' + self.blockDataLocation = self.dataDir + 'blocks/' + self.addressDB = self.dataDir + 'address.db' self.hsAddress = '' self.bootstrapFileLocation = 'static-data/bootstrap-nodes.txt' self.bootstrapList = [] self.requirements = onionrvalues.OnionrValues() self.torPort = torPort - self.dataNonceFile = 'data/block-nonces.dat' + self.dataNonceFile = self.dataDir + 'block-nonces.dat' self.dbCreate = dbcreator.DBCreator(self) - self.forwardKeysFile = 'data/forward-keys.db' + self.forwardKeysFile = self.dataDir + 'forward-keys.db' # Socket data, defined here because of multithreading constraints with gevent self.killSockets = False @@ -57,20 +65,20 @@ class Core: self.socketReasons = {} self.socketServerResponseData = {} - self.usageFile = 'data/disk-usage.txt' + self.usageFile = self.dataDir + 'disk-usage.txt' self.config = config self.maxBlockSize = 10000000 # max block size in bytes - 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.dataDir): + os.mkdir(self.dataDir) + if not os.path.exists(self.dataDir + 'blocks/'): + os.mkdir(self.dataDir + 'blocks/') if not os.path.exists(self.blockDB): self.createBlockDB() - if os.path.exists('data/hs/hostname'): - with open('data/hs/hostname', 'r') as hs: + if os.path.exists(self.dataDir + '/hs/hostname'): + with open(self.dataDir + '/hs/hostname', 'r') as hs: self.hsAddress = hs.read().strip() # Load bootstrap address list @@ -95,8 +103,8 @@ class Core: def refreshFirstStartVars(self): '''Hack to refresh some vars which may not be set on first start''' - if os.path.exists('data/hs/hostname'): - with open('data/hs/hostname', 'r') as hs: + if os.path.exists(self.dataDir + '/hs/hostname'): + with open(self.dataDir + '/hs/hostname', 'r') as hs: self.hsAddress = hs.read().strip() def addPeer(self, peerID, powID, name=''): @@ -136,7 +144,7 @@ class Core: ''' Add an address to the address database (only tor currently) ''' - if address == config.get('i2p.ownAddr', None): + if address == config.get('i2p.ownAddr', None) or address == self.hsAddress: return False if self._utils.validateID(address): @@ -197,7 +205,7 @@ class Core: c.execute('Delete from hashes where hash=?;', t) conn.commit() conn.close() - blockFile = 'data/blocks/' + block + '.dat' + blockFile = self.dataDir + '/blocks/' + block + '.dat' dataSize = 0 try: ''' Get size of data when loaded as an object/var, rather than on disk, diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 69dd8aeb..97e73296 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -28,7 +28,14 @@ class NetController: ''' def __init__(self, hsPort): - self.torConfigLocation = 'data/torrc' + try: + self.dataDir = os.environ['ONIONR_HOME'] + if not self.dataDir.endswith('/'): + self.dataDir += '/' + except KeyError: + self.dataDir = 'data/' + + self.torConfigLocation = self.dataDir + 'torrc' self.readyState = False self.socksPort = random.randint(1024, 65535) self.hsPort = hsPort @@ -81,10 +88,10 @@ class NetController: break torrcData = '''SocksPort ''' + str(self.socksPort) + ''' -HiddenServiceDir data/hs/ +HiddenServiceDir ''' + self.dataDir + '''hs/ \n''' + hsVer + '''\n HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' -DataDirectory data/tordata/ +DataDirectory ''' + self.dataDir + '''tordata/ CookieAuthentication 1 ControlPort ''' + str(controlPort) + ''' HashedControlPassword ''' + str(password) + ''' @@ -140,11 +147,11 @@ HashedControlPassword ''' + str(password) + ''' logger.debug('Finished starting Tor.', timestamp=True) self.readyState = True - myID = open('data/hs/hostname', 'r') + myID = open(self.dataDir + 'hs/hostname', 'r') self.myID = myID.read().replace('\n', '') myID.close() - torPidFile = open('data/torPid.txt', 'w') + torPidFile = open(self.dataDir + 'torPid.txt', 'w') torPidFile.write(str(tor.pid)) torPidFile.close() @@ -156,7 +163,7 @@ HashedControlPassword ''' + str(password) + ''' ''' try: - pid = open('data/torPid.txt', 'r') + pid = open(self.dataDir + 'torPid.txt', 'r') pidN = pid.read() pid.close() except FileNotFoundError: @@ -169,7 +176,7 @@ HashedControlPassword ''' + str(password) + ''' try: os.kill(int(pidN), signal.SIGTERM) - os.remove('data/torPid.txt') + os.remove(self.dataDir + 'torPid.txt') except ProcessLookupError: pass except FileNotFoundError: diff --git a/onionr/onionr.py b/onionr/onionr.py index 593055ef..c0c39d23 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -57,18 +57,25 @@ class Onionr: except FileNotFoundError: pass + try: + self.dataDir = os.environ['ONIONR_HOME'] + if not self.dataDir.endswith('/'): + self.dataDir += '/' + except KeyError: + self.dataDir = 'data/' + # Load global configuration data - data_exists = os.path.exists('data/') + data_exists = os.path.exists(self.dataDir) if not data_exists: - os.mkdir('data/') + os.mkdir(self.dataDir) if os.path.exists('static-data/default_config.json'): config.set_config(json.loads(open('static-data/default_config.json').read())) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it else: # the default config file doesn't exist, try hardcoded config - config.set_config({'dev_mode': True, 'log': {'file': {'output': True, 'path': 'data/output.log'}, 'console': {'output': True, 'color': True}}}) + config.set_config({'dev_mode': True, 'log': {'file': {'output': True, 'path': self.dataDir + 'output.log'}, 'console': {'output': True, 'color': True}}}) if not data_exists: config.save() config.reload() # this will read the configuration file into memory @@ -80,7 +87,7 @@ class Onionr: settings = settings | logger.OUTPUT_TO_CONSOLE if config.get('log.file.output', True): settings = settings | logger.OUTPUT_TO_FILE - logger.set_file(config.get('log.file.path', '/tmp/onionr.log')) + logger.set_file(config.get('log.file.path', '/tmp/onionr.log').replace('data/', self.dataDir)) logger.set_settings(settings) if str(config.get('general.dev_mode', True)).lower() == 'true': @@ -102,15 +109,15 @@ class Onionr: print('Enter password to decrypt:') password = getpass.getpass() result = self.onionrCore.dataDirDecrypt(password) - if os.path.exists('data/'): + if os.path.exists(self.dataDir): break else: logger.error('Failed to decrypt: ' + result[1], timestamp = False) else: # If data folder does not exist if not data_exists: - if not os.path.exists('data/blocks/'): - os.mkdir('data/blocks/') + if not os.path.exists(self.dataDir + 'blocks/'): + os.mkdir(self.dataDir + 'blocks/') # Copy default plugins into plugins folder if not os.path.exists(plugins.get_plugins_folder()): @@ -262,7 +269,7 @@ class Onionr: if not self._developmentMode: encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory: ') self.onionrCore.dataDirEncrypt(encryptionPassword) - shutil.rmtree('data/') + shutil.rmtree(self.dataDir) return @@ -695,7 +702,7 @@ class Onionr: powToken = self.onionrCore._crypto.pubKeyPowToken messages = { # info about local client - 'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if self.onionrUtils.isCommunicatorRunning(timeout = 2) else logger.colors.fg.red + 'Offline'), + 'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if self.onionrUtils.isCommunicatorRunning(timeout = 9) else logger.colors.fg.red + 'Offline'), 'Public Key' : self.onionrCore._crypto.pubKey, 'POW Token' : powToken, 'Combined' : self.onionrCore._crypto.pubKey + '-' + powToken, @@ -704,14 +711,14 @@ class Onionr: # file and folder size stats 'div1' : True, # this creates a solid line across the screen, a div - 'Total Block Size' : onionrutils.humanSize(onionrutils.size('data/blocks/')), - 'Total Plugin Size' : onionrutils.humanSize(onionrutils.size('data/plugins/')), - 'Log File Size' : onionrutils.humanSize(onionrutils.size('data/output.log')), + 'Total Block Size' : onionrutils.humanSize(onionrutils.size(self.dataDir + 'blocks/')), + 'Total Plugin Size' : onionrutils.humanSize(onionrutils.size(self.dataDir + 'plugins/')), + 'Log File Size' : onionrutils.humanSize(onionrutils.size(self.dataDir + 'output.log')), # count stats 'div2' : True, 'Known Peers Count' : str(len(self.onionrCore.listPeers()) - 1), - 'Enabled Plugins Count' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir('data/plugins/'))), + 'Enabled Plugins Count' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(self.dataDir + 'plugins/'))), 'Known Blocks Count' : str(totalBlocks), 'Percent Blocks Signed' : str(round(100 * signedBlocks / max(totalBlocks, 1), 2)) + '%' } @@ -777,7 +784,7 @@ class Onionr: def get_hostname(self): try: - with open('./data/hs/hostname', 'r') as hostname: + with open('./' + self.dataDir + 'hs/hostname', 'r') as hostname: return hostname.read().strip() except Exception: return None diff --git a/onionr/onionrblacklist.py b/onionr/onionrblacklist.py index 863ddc37..f87ccd65 100644 --- a/onionr/onionrblacklist.py +++ b/onionr/onionrblacklist.py @@ -20,7 +20,7 @@ import sqlite3, os, logger class OnionrBlackList: def __init__(self, coreInst): - self.blacklistDB = 'data/blacklist.db' + self.blacklistDB = coreInst.dataDir + 'blacklist.db' self._core = coreInst if not os.path.exists(self.blacklistDB): diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index 78d3e71e..78b4b5bc 100644 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -149,7 +149,7 @@ class Block: # read from file if it's still None if blockdata is None: - filelocation = 'data/blocks/%s.dat' % self.getHash() + filelocation = self.core.dataDir + 'blocks/%s.dat' % self.getHash() if readfile: with open(filelocation, 'rb') as f: @@ -727,7 +727,7 @@ class Block: if type(hash) == Block: blockfile = hash.getBlockFile() else: - blockfile = 'data/blocks/%s.dat' % hash + blockfile = onionrcore.Core().dataDir + 'blocks/%s.dat' % hash return os.path.exists(blockfile) and os.path.isfile(blockfile) diff --git a/onionr/onionrchat.py b/onionr/onionrchat.py index 6d8fafa6..80569e6e 100644 --- a/onionr/onionrchat.py +++ b/onionr/onionrchat.py @@ -27,6 +27,7 @@ class OnionrChat: self._utils = self._core._utils self.chats = {} # {'peer': {'date': date, message': message}} + self.chatSend = {} def chatHandler(self): while not self.communicator.shutdown: @@ -34,7 +35,15 @@ class OnionrChat: try: assert self._core.socketReasons[peer] == "chat" except (AssertionError, KeyError) as e: + logger.warn('Peer is not for chat') continue else: self.chats[peer] = {'date': self._core.socketServerConnData[peer]['date'], 'data': self._core.socketServerConnData[peer]['data']} - logger.info("CHAT MESSAGE RECIEVED: %s" % self.chats[peer]['data']) \ No newline at end of file + logger.info("CHAT MESSAGE RECIEVED: %s" % self.chats[peer]['data']) + for peer in self.communicator.socketClient.sockets: + try: + logger.info(self.communicator.socketClient.connPool[peer]['data']) + self.communicator.socketClient.sendData(peer, "lol") + except: + pass + time.sleep(2) \ No newline at end of file diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 9e055716..340f8530 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -28,8 +28,8 @@ elif sys.version_info[0] == 3 and sys.version_info[1] >= 6: class OnionrCrypto: def __init__(self, coreInstance): self._core = coreInstance - self._keyFile = 'data/keys.txt' - self.keyPowFile = 'data/keyPow.txt' + self._keyFile = self._core.dataDir + 'keys.txt' + self.keyPowFile = self._core.dataDir + 'keyPow.txt' self.pubKey = None self.privKey = None @@ -42,7 +42,7 @@ class OnionrCrypto: # 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: + with open(self._core.dataDir + 'keys.txt', 'r') as keys: keys = keys.read().split(',') self.pubKey = keys[0] self.privKey = keys[1] diff --git a/onionr/onionrplugins.py b/onionr/onionrplugins.py index a699433c..ce038856 100644 --- a/onionr/onionrplugins.py +++ b/onionr/onionrplugins.py @@ -21,7 +21,14 @@ import os, re, importlib, config, logger import onionrevents as events -_pluginsfolder = 'data/plugins/' +try: + dataDir = os.environ['ONIONR_HOME'] + if not dataDir.endswith('/'): + dataDir += '/' +except KeyError: + dataDir = 'data/' + +_pluginsfolder = dataDir + 'plugins/' _instances = dict() def reload(onionr = None, stop_event = True): @@ -217,7 +224,7 @@ def get_plugin_data_folder(name, absolute = True): Returns the location of a plugin's data folder ''' - return get_plugins_folder(name, absolute) + 'data/' + return get_plugins_folder(name, absolute) + dataDir def check(): ''' diff --git a/onionr/onionrsockets.py b/onionr/onionrsockets.py index 726cb169..f20ad4f8 100644 --- a/onionr/onionrsockets.py +++ b/onionr/onionrsockets.py @@ -68,8 +68,8 @@ class OnionrSocketServer: retData = self._core.socketServerResponseData[myPeer] except KeyError: pass - - self._core.socketServerConnData[myPeer] = '' + else: + self._core.socketServerResponseData[myPeer] = '' return retData @@ -99,11 +99,11 @@ class OnionrSocketServer: controller.authenticate(config.get('tor.controlpassword')) socket = controller.create_ephemeral_hidden_service({80: bindPort}, await_publication = True) - self.sockets[peer] = socket.service_id + self.sockets[peer] = socket.service_id + '.onion' - self.responseData[socket.service_id] = '' + self.responseData[socket.service_id + '.onion'] = '' - self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason}) + self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason, 'address': socket.service_id + '.onion'}) self._core.socketReasons[peer] = reason return @@ -123,16 +123,26 @@ class OnionrSocketClient: logger.info('Trying to find socket server for %s' % (peer,)) # Find the newest open socket for a given peer for block in self._core.getBlocksByType('socket'): - block = onionrblockapi.Block(block, core=self._myCore) + block = onionrblockapi.Block(block, core=self._core) if block.decrypt(): - if block.verifySig() and block.signer == peer: + theSigner = block.signer + try: + theSigner = theSigner.decode() + except AttributeError: + pass + if block.verifySig() and theSigner == peer: address = block.getMetadata('address') if self._core._utils.validateID(address): # If we got their address, it is valid, and verified, we can break out - if block.getMetadata('reason') == 'chat': + if block.getMetadata('reason') == reason: break + else: + logger.error('The socket the peer opened is not for %s' % (reason,)) else: + logger.error('Peer transport id is invalid for socket: %s' % (address,)) address = '' + else: + logger.warn('Block has invalid sig or id, was for %s' % (theSigner,)) if address != '': logger.info('%s socket client started with %s' % (reason, peer)) self.sockets[peer] = address @@ -140,12 +150,14 @@ class OnionrSocketClient: while not self.killSocket: try: data = self.sendData[peer] + logger.info('Sending %s to %s' % (data, peer)) except KeyError: pass else: self.sendData[peer] = '' postData = {'data': data} self.connPool[peer] = {'date': self._core._utils.getEpoch(), 'data': self._core._utils.doPostRequest('http://' + address + '/dc/', data=postData)} + time.sleep(2) def getResponse(self, peer): retData = '' diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 15bbc866..ba8d6b42 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -52,8 +52,8 @@ class OnionrUtils: Load our timingToken from disk for faster local HTTP API ''' try: - if os.path.exists('data/time-bypass.txt'): - with open('data/time-bypass.txt', 'r') as bypass: + if os.path.exists(self._core.dataDir + 'time-bypass.txt'): + with open(self._core.dataDir + 'time-bypass.txt', 'r') as bypass: self.timingToken = bypass.read() except Exception as error: logger.error('Failed to fetch time bypass token.', error = error) @@ -143,7 +143,7 @@ class OnionrUtils: def getMyAddress(self): try: - with open('./data/hs/hostname', 'r') as hostname: + with open('./' + self._core.dataDir + 'hs/hostname', 'r') as hostname: return hostname.read().strip() except Exception as error: logger.error('Failed to read my address.', error = error) @@ -158,7 +158,7 @@ class OnionrUtils: self.getTimeBypassToken() # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless. try: - with open('data/host.txt', 'r') as host: + with open(self._core.dataDir + 'host.txt', 'r') as host: hostname = host.read() except FileNotFoundError: return False @@ -270,9 +270,10 @@ class OnionrUtils: if len(blockType) <= 10: self._core.updateBlockInfo(blockHash, 'dataType', blockType) - onionrevents.event('processBlocks', data = {'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid}, onionr = None) + onionrevents.event('processblocks', data = {'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid}, onionr = None) except TypeError: + logger.warn("Missing block information") pass def escapeAnsi(self, line): @@ -479,7 +480,7 @@ class OnionrUtils: def isCommunicatorRunning(self, timeout = 5, interval = 0.1): try: - runcheck_file = 'data/.runcheck' + runcheck_file = self._core.dataDir + '.runcheck' if os.path.isfile(runcheck_file): os.remove(runcheck_file) @@ -593,7 +594,7 @@ class OnionrUtils: except ValueError as e: logger.debug('Failed to make request', error = e) except requests.exceptions.RequestException as e: - if not 'ConnectTimeoutError' in str(e): + if not 'ConnectTimeoutError' in str(e) and not 'Request rejected or failed' in str(e): logger.debug('Error: %s' % str(e)) retData = False return retData diff --git a/onionr/static-data/default-plugins/metadataprocessor/main.py b/onionr/static-data/default-plugins/metadataprocessor/main.py index 2b4deb86..6b0b5a28 100644 --- a/onionr/static-data/default-plugins/metadataprocessor/main.py +++ b/onionr/static-data/default-plugins/metadataprocessor/main.py @@ -58,7 +58,7 @@ def _processForwardKey(api, myBlock): else: raise onionrexceptions.InvalidPubkey("%s is nota valid pubkey key" % (key,)) -def on_processBlocks(api): +def on_processblocks(api): # Generally fired by utils. myBlock = api.data['block'] blockType = api.data['type'] @@ -77,21 +77,24 @@ def on_processBlocks(api): # socket blocks elif blockType == 'socket': if api.data['validSig'] == True and myBlock.decrypted: # we check if it is decrypted as a way of seeing if it was for us + logger.info('Detected socket advertised to us...') try: - address = api.data['address'] + address = myBlock.getMetadata('address') except KeyError: raise onionrexceptions.MissingAddress("Missing address for new socket") try: - port = api.data['port'] + port = myBlock.getMetadata('port') except KeyError: raise ValueError("Missing port for new socket") try: - reason = api.data['reason'] + reason = myBlock.getMetadata('reason') except KeyError: raise ValueError("Missing socket reason") socketInfo = json.dumps({'peer': api.data['signer'], 'address': address, 'port': port, 'create': False, 'reason': reason}) api.get_core().daemonQueueAdd('addSocket', socketInfo) + else: + logger.warn("socket is not for us or is invalid") def on_init(api, data = None): diff --git a/setprofile.sh b/setprofile.sh new file mode 100755 index 00000000..3541a652 --- /dev/null +++ b/setprofile.sh @@ -0,0 +1,7 @@ +#!/bin/bash +ONIONR_HOME=. +if [ $# -gt 0 ]; then + ONIONR_HOME=$1 +export ONIONR_HOME +echo "set ONIONR_HOME to $ONIONR_HOME" +fi From 8dbaac21980e73075fbb2e7663e10901bd4be99a Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 26 Sep 2018 18:40:33 -0500 Subject: [PATCH 061/101] catch signature failure better --- onionr/onionrcrypto.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 340f8530..8953bf8f 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -78,6 +78,9 @@ class OnionrCrypto: except nacl.exceptions.ValueError: logger.warn('Signature by unknown key (cannot reverse hash)') return False + except binascii.Error: + logger.warn('Could not load key for verification, invalid padding') + return False retData = False sig = base64.b64decode(sig) try: From 0c9847fbecb23f539266c5e80028a7a4a8bd067f Mon Sep 17 00:00:00 2001 From: Arinerron Date: Wed, 26 Sep 2018 17:40:02 -0700 Subject: [PATCH 062/101] Misc changes --- onionr/core.py | 5 +---- onionr/onionrblockapi.py | 8 +++++--- .../www/ui/common/onionr-timeline-post.html | 6 +++--- onionr/static-data/www/ui/dist/css/main.css | 2 ++ onionr/static-data/www/ui/dist/index.html | 14 ++++++++++++++ onionr/static-data/www/ui/dist/js/main.js | 6 +++--- onionr/static-data/www/ui/dist/js/timeline.js | 10 ++++++++++ onionr/static-data/www/ui/src/css/main.css | 2 ++ onionr/static-data/www/ui/src/index.html | 14 ++++++++++++++ onionr/static-data/www/ui/src/js/timeline.js | 10 ++++++++++ 10 files changed, 64 insertions(+), 13 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index bff27e45..e1f38fa8 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -644,7 +644,7 @@ class Core: conn.close() return True - def insertBlock(self, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = None): + def insertBlock(self, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = dict()): ''' Inserts a block into the network encryptType must be specified to encrypt a block @@ -663,9 +663,6 @@ class Core: with open(self.dataNonceFile, 'a') as nonceFile: nonceFile.write(dataNonce + '\n') - if meta is None: - meta = dict() - if type(data) is bytes: data = data.decode() data = str(data) diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index 87ca2984..b656bd43 100644 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -163,7 +163,7 @@ class Block: self.raw = str(blockdata) self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')]) self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:] - if self.bheader['encryptType'] in ('asym', 'sym'): + if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')): self.bmetadata = self.getHeader('meta', None) self.isEncrypted = True else: @@ -193,6 +193,8 @@ class Block: # if block can't be parsed, it's a waste of precious space. Throw it away. if not self.delete(): logger.error('Failed to delete invalid block %s.' % self.getHash(), error = e) + else: + logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False) self.valid = False return False @@ -207,7 +209,7 @@ class Block: if self.exists(): os.remove(self.getBlockFile()) - removeBlock(self.getHash()) + self.getCore().removeBlock(self.getHash()) return True return False @@ -230,7 +232,7 @@ class Block: blockFile.write(self.getRaw().encode()) self.update() else: - self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign) + self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign, meta = self.getMetadata()) self.update() return self.getHash() diff --git a/onionr/static-data/www/ui/common/onionr-timeline-post.html b/onionr/static-data/www/ui/common/onionr-timeline-post.html index a6873abe..67ec158c 100644 --- a/onionr/static-data/www/ui/common/onionr-timeline-post.html +++ b/onionr/static-data/www/ui/common/onionr-timeline-post.html @@ -1,6 +1,6 @@
-
+
@@ -8,8 +8,8 @@
diff --git a/onionr/static-data/www/ui/dist/css/main.css b/onionr/static-data/www/ui/dist/css/main.css index a69216cb..4b9cfe8d 100644 --- a/onionr/static-data/www/ui/dist/css/main.css +++ b/onionr/static-data/www/ui/dist/css/main.css @@ -41,6 +41,8 @@ body { padding: 1rem; margin-bottom: 1rem; + cursor: pointer; + width: 100%; } diff --git a/onionr/static-data/www/ui/dist/index.html b/onionr/static-data/www/ui/dist/index.html index 90f9a30d..080c67eb 100644 --- a/onionr/static-data/www/ui/dist/index.html +++ b/onionr/static-data/www/ui/dist/index.html @@ -117,6 +117,20 @@
+ + +