Merge branch 'new-pm' into 'master'
New Plugins, bug fixes See merge request beardog/Onionr!2master
commit
eb00904fde
|
@ -25,7 +25,6 @@ from defusedxml import minidom
|
||||||
|
|
||||||
class OnionrCommunicatorDaemon:
|
class OnionrCommunicatorDaemon:
|
||||||
def __init__(self, debug, developmentMode):
|
def __init__(self, debug, developmentMode):
|
||||||
logger.warn('New (unstable) communicator is being used.')
|
|
||||||
|
|
||||||
# list of timer instances
|
# list of timer instances
|
||||||
self.timers = []
|
self.timers = []
|
||||||
|
@ -57,6 +56,9 @@ class OnionrCommunicatorDaemon:
|
||||||
# list of new blocks to download, added to when new block lists are fetched from peers
|
# list of new blocks to download, added to when new block lists are fetched from peers
|
||||||
self.blockQueue = []
|
self.blockQueue = []
|
||||||
|
|
||||||
|
# list of blocks currently downloading, avoid s
|
||||||
|
self.currentDownloading = []
|
||||||
|
|
||||||
# Clear the daemon queue for any dead messages
|
# Clear the daemon queue for any dead messages
|
||||||
if os.path.exists(self._core.queueDB):
|
if os.path.exists(self._core.queueDB):
|
||||||
self._core.clearDaemonQueue()
|
self._core.clearDaemonQueue()
|
||||||
|
@ -77,9 +79,9 @@ class OnionrCommunicatorDaemon:
|
||||||
peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60)
|
peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60)
|
||||||
OnionrCommunicatorTimers(self, self.lookupBlocks, 7, requiresPeer=True)
|
OnionrCommunicatorTimers(self, self.lookupBlocks, 7, requiresPeer=True)
|
||||||
OnionrCommunicatorTimers(self, self.getBlocks, 10, requiresPeer=True)
|
OnionrCommunicatorTimers(self, self.getBlocks, 10, requiresPeer=True)
|
||||||
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 120)
|
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58)
|
||||||
OnionrCommunicatorTimers(self, self.lookupKeys, 125, requiresPeer=True)
|
OnionrCommunicatorTimers(self, self.lookupKeys, 60, requiresPeer=True)
|
||||||
OnionrCommunicatorTimers(self, self.lookupAdders, 600, requiresPeer=True)
|
OnionrCommunicatorTimers(self, self.lookupAdders, 60, requiresPeer=True)
|
||||||
|
|
||||||
# set loop to execute instantly to load up peer pool (replaced old pool init wait)
|
# set loop to execute instantly to load up peer pool (replaced old pool init wait)
|
||||||
peerPoolTimer.count = (peerPoolTimer.frequency - 1)
|
peerPoolTimer.count = (peerPoolTimer.frequency - 1)
|
||||||
|
@ -122,8 +124,7 @@ class OnionrCommunicatorDaemon:
|
||||||
peer = self.pickOnlinePeer()
|
peer = self.pickOnlinePeer()
|
||||||
newAdders = self.peerAction(peer, action='pex')
|
newAdders = self.peerAction(peer, action='pex')
|
||||||
self._core._utils.mergeAdders(newAdders)
|
self._core._utils.mergeAdders(newAdders)
|
||||||
|
self.decrementThreadCount('lookupAdders')
|
||||||
self.decrementThreadCount('lookupKeys')
|
|
||||||
|
|
||||||
def lookupBlocks(self):
|
def lookupBlocks(self):
|
||||||
'''Lookup new blocks & add them to download queue'''
|
'''Lookup new blocks & add them to download queue'''
|
||||||
|
@ -154,6 +155,10 @@ class OnionrCommunicatorDaemon:
|
||||||
def getBlocks(self):
|
def getBlocks(self):
|
||||||
'''download new blocks in queue'''
|
'''download new blocks in queue'''
|
||||||
for blockHash in self.blockQueue:
|
for blockHash in self.blockQueue:
|
||||||
|
if blockHash in self.currentDownloading:
|
||||||
|
logger.debug('ALREADY DOWNLOADING ' + blockHash)
|
||||||
|
continue
|
||||||
|
self.currentDownloading.append(blockHash)
|
||||||
logger.info("Attempting to download %s..." % blockHash)
|
logger.info("Attempting to download %s..." % blockHash)
|
||||||
content = self.peerAction(self.pickOnlinePeer(), 'getData', data=blockHash) # block content from random peer (includes metadata)
|
content = self.peerAction(self.pickOnlinePeer(), 'getData', data=blockHash) # block content from random peer (includes metadata)
|
||||||
if content != False:
|
if content != False:
|
||||||
|
@ -171,7 +176,7 @@ class OnionrCommunicatorDaemon:
|
||||||
content = content.decode() # decode here because sha3Hash needs bytes above
|
content = content.decode() # decode here because sha3Hash needs bytes above
|
||||||
metas = self._core._utils.getBlockMetadataFromData(content) # returns tuple(metadata, meta), meta is also in metadata
|
metas = self._core._utils.getBlockMetadataFromData(content) # returns tuple(metadata, meta), meta is also in metadata
|
||||||
metadata = metas[0]
|
metadata = metas[0]
|
||||||
meta = metas[1]
|
#meta = metas[1]
|
||||||
if self._core._utils.validateMetadata(metadata): # check if metadata is valid
|
if self._core._utils.validateMetadata(metadata): # check if metadata is valid
|
||||||
if self._core._crypto.verifyPow(content): # check if POW is enough/correct
|
if self._core._crypto.verifyPow(content): # check if POW is enough/correct
|
||||||
logger.info('Block passed proof, saving.')
|
logger.info('Block passed proof, saving.')
|
||||||
|
@ -191,6 +196,7 @@ class OnionrCommunicatorDaemon:
|
||||||
pass
|
pass
|
||||||
logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
|
logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
|
||||||
self.blockQueue.remove(blockHash) # remove from block queue both if success or false
|
self.blockQueue.remove(blockHash) # remove from block queue both if success or false
|
||||||
|
self.currentDownloading.remove(blockHash)
|
||||||
self.decrementThreadCount('getBlocks')
|
self.decrementThreadCount('getBlocks')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -628,13 +628,16 @@ class Core:
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getBlocksByType(self, blockType):
|
def getBlocksByType(self, blockType, orderDate=True):
|
||||||
'''
|
'''
|
||||||
Returns a list of blocks by the type
|
Returns a list of blocks by the type
|
||||||
'''
|
'''
|
||||||
conn = sqlite3.connect(self.blockDB)
|
conn = sqlite3.connect(self.blockDB)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
|
if orderDate:
|
||||||
|
execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
|
||||||
|
else:
|
||||||
|
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
|
||||||
args = (blockType,)
|
args = (blockType,)
|
||||||
rows = list()
|
rows = list()
|
||||||
for row in c.execute(execute, args):
|
for row in c.execute(execute, args):
|
||||||
|
@ -719,7 +722,7 @@ class Core:
|
||||||
# sign before encrypt, as unauthenticated crypto should not be a problem here
|
# sign before encrypt, as unauthenticated crypto should not be a problem here
|
||||||
if sign:
|
if sign:
|
||||||
signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True)
|
signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True)
|
||||||
signer = self._crypto.pubKeyHashID()
|
signer = self._crypto.pubKey
|
||||||
|
|
||||||
if len(jsonMeta) > 1000:
|
if len(jsonMeta) > 1000:
|
||||||
raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes')
|
raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes')
|
||||||
|
@ -728,15 +731,16 @@ class Core:
|
||||||
if encryptType == 'sym':
|
if encryptType == 'sym':
|
||||||
if len(symKey) < self.requirements.passwordLength:
|
if len(symKey) < self.requirements.passwordLength:
|
||||||
raise onionrexceptions.SecurityError('Weak encryption key')
|
raise onionrexceptions.SecurityError('Weak encryption key')
|
||||||
jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True)
|
jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True).decode()
|
||||||
data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True)
|
data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True).decode()
|
||||||
signature = self._crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True)
|
signature = self._crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True).decode()
|
||||||
signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True)
|
signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True).decode()
|
||||||
elif encryptType == 'asym':
|
elif encryptType == 'asym':
|
||||||
if self._utils.validatePubKey(asymPeer):
|
if self._utils.validatePubKey(asymPeer):
|
||||||
jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True)
|
jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True, anonymous=True).decode()
|
||||||
data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True)
|
data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True, anonymous=True).decode()
|
||||||
signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True)
|
signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True, anonymous=True).decode()
|
||||||
|
signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True, anonymous=True).decode()
|
||||||
else:
|
else:
|
||||||
raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key')
|
raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key')
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ def raw(data, fd = sys.stdout):
|
||||||
with open(_outputfile, "a+") as f:
|
with open(_outputfile, "a+") as f:
|
||||||
f.write(colors.filter(data) + '\n')
|
f.write(colors.filter(data) + '\n')
|
||||||
|
|
||||||
def log(prefix, data, color = '', timestamp=True, fd = sys.stdout):
|
def log(prefix, data, color = '', timestamp=True, fd = sys.stdout, prompt = True):
|
||||||
'''
|
'''
|
||||||
Logs the data
|
Logs the data
|
||||||
prefix : The prefix to the output
|
prefix : The prefix to the output
|
||||||
|
@ -145,7 +145,7 @@ def log(prefix, data, color = '', timestamp=True, fd = sys.stdout):
|
||||||
if timestamp:
|
if timestamp:
|
||||||
curTime = time.strftime("%m-%d %H:%M:%S") + ' '
|
curTime = time.strftime("%m-%d %H:%M:%S") + ' '
|
||||||
|
|
||||||
output = colors.reset + str(color) + '[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' + curTime + str(data) + colors.reset
|
output = colors.reset + str(color) + ('[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' if prompt is True else '') + curTime + str(data) + colors.reset
|
||||||
if not get_settings() & USE_ANSI:
|
if not get_settings() & USE_ANSI:
|
||||||
output = colors.filter(output)
|
output = colors.filter(output)
|
||||||
|
|
||||||
|
@ -201,31 +201,37 @@ def confirm(default = 'y', message = 'Are you sure %s? '):
|
||||||
return default == 'y'
|
return default == 'y'
|
||||||
|
|
||||||
# debug: when there is info that could be useful for debugging purposes only
|
# debug: when there is info that could be useful for debugging purposes only
|
||||||
def debug(data, timestamp=True):
|
def debug(data, error = None, timestamp = True, prompt = True):
|
||||||
if get_level() <= LEVEL_DEBUG:
|
if get_level() <= LEVEL_DEBUG:
|
||||||
log('/', data, timestamp=timestamp)
|
log('/', data, timestamp=timestamp, prompt = prompt)
|
||||||
|
if not error is None:
|
||||||
|
debug('Error: ' + str(error) + parse_error())
|
||||||
|
|
||||||
# info: when there is something to notify the user of, such as the success of a process
|
# info: when there is something to notify the user of, such as the success of a process
|
||||||
def info(data, timestamp=False):
|
def info(data, timestamp = False, prompt = True):
|
||||||
if get_level() <= LEVEL_INFO:
|
if get_level() <= LEVEL_INFO:
|
||||||
log('+', data, colors.fg.green, timestamp=timestamp)
|
log('+', data, colors.fg.green, timestamp = timestamp, prompt = prompt)
|
||||||
|
|
||||||
# warn: when there is a potential for something bad to happen
|
# warn: when there is a potential for something bad to happen
|
||||||
def warn(data, timestamp=True):
|
def warn(data, error = None, timestamp = True, prompt = True):
|
||||||
|
if not error is None:
|
||||||
|
debug('Error: ' + str(error) + parse_error())
|
||||||
if get_level() <= LEVEL_WARN:
|
if get_level() <= LEVEL_WARN:
|
||||||
log('!', data, colors.fg.orange, timestamp=timestamp)
|
log('!', data, colors.fg.orange, timestamp = timestamp, prompt = prompt)
|
||||||
|
|
||||||
# error: when only one function, module, or process of the program encountered a problem and must stop
|
# error: when only one function, module, or process of the program encountered a problem and must stop
|
||||||
def error(data, error=None, timestamp=True):
|
def error(data, error = None, timestamp = True, prompt = True):
|
||||||
if get_level() <= LEVEL_ERROR:
|
if get_level() <= LEVEL_ERROR:
|
||||||
log('-', data, colors.fg.red, timestamp=timestamp, fd = sys.stderr)
|
log('-', data, colors.fg.red, timestamp = timestamp, fd = sys.stderr, prompt = prompt)
|
||||||
if not error is None:
|
if not error is None:
|
||||||
debug('Error: ' + str(error) + parse_error())
|
debug('Error: ' + str(error) + parse_error())
|
||||||
|
|
||||||
# fatal: when the something so bad has happened that the program must stop
|
# fatal: when the something so bad has happened that the program must stop
|
||||||
def fatal(data, timestamp=True):
|
def fatal(data, error = None, timestamp=True, prompt = True):
|
||||||
|
if not error is None:
|
||||||
|
debug('Error: ' + str(error) + parse_error())
|
||||||
if get_level() <= LEVEL_FATAL:
|
if get_level() <= LEVEL_FATAL:
|
||||||
log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp=timestamp, fd = sys.stderr)
|
log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp=timestamp, fd = sys.stderr, prompt = prompt)
|
||||||
|
|
||||||
# returns a formatted error message
|
# returns a formatted error message
|
||||||
def parse_error():
|
def parse_error():
|
||||||
|
|
|
@ -50,7 +50,6 @@ class Onionr:
|
||||||
Main Onionr class. This is for the CLI program, and does not handle much of the logic.
|
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.
|
In general, external programs and plugins should not use this class.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.chdir(sys.path[0])
|
os.chdir(sys.path[0])
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
@ -181,15 +180,6 @@ class Onionr:
|
||||||
'listkeys': self.listKeys,
|
'listkeys': self.listKeys,
|
||||||
'list-keys': self.listKeys,
|
'list-keys': self.listKeys,
|
||||||
|
|
||||||
'addmsg': self.addMessage,
|
|
||||||
'addmessage': self.addMessage,
|
|
||||||
'add-msg': self.addMessage,
|
|
||||||
'add-message': self.addMessage,
|
|
||||||
'pm': self.sendEncrypt,
|
|
||||||
|
|
||||||
'getpms': self.getPMs,
|
|
||||||
'get-pms': self.getPMs,
|
|
||||||
|
|
||||||
'addpeer': self.addPeer,
|
'addpeer': self.addPeer,
|
||||||
'add-peer': self.addPeer,
|
'add-peer': self.addPeer,
|
||||||
'add-address': self.addAddress,
|
'add-address': self.addAddress,
|
||||||
|
@ -226,9 +216,6 @@ class Onionr:
|
||||||
'create-plugin': 'Creates directory structure for a plugin',
|
'create-plugin': 'Creates directory structure for a plugin',
|
||||||
'add-peer': 'Adds a peer to database',
|
'add-peer': 'Adds a peer to database',
|
||||||
'list-peers': 'Displays a list of peers',
|
'list-peers': 'Displays a list of peers',
|
||||||
'add-msg': 'Broadcasts a message to the Onionr network',
|
|
||||||
'pm': 'Adds a private message to block',
|
|
||||||
'get-pms': 'Shows private messages sent to you',
|
|
||||||
'add-file': 'Create an Onionr block from a file',
|
'add-file': 'Create an Onionr block from a file',
|
||||||
'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)',
|
'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)',
|
||||||
'listconn': 'list connected peers',
|
'listconn': 'list connected peers',
|
||||||
|
@ -341,32 +328,6 @@ class Onionr:
|
||||||
logger.info('Sending kex to command queue...')
|
logger.info('Sending kex to command queue...')
|
||||||
self.onionrCore.daemonQueueAdd('kex')
|
self.onionrCore.daemonQueueAdd('kex')
|
||||||
|
|
||||||
def sendEncrypt(self):
|
|
||||||
'''
|
|
||||||
Create a private message and send it
|
|
||||||
'''
|
|
||||||
|
|
||||||
invalidID = True
|
|
||||||
while invalidID:
|
|
||||||
try:
|
|
||||||
peer = logger.readline('Peer to send to: ')
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if self.onionrUtils.validatePubKey(peer):
|
|
||||||
invalidID = False
|
|
||||||
else:
|
|
||||||
logger.error('Invalid peer ID')
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
message = logger.readline("Enter a message: ")
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
logger.info("Sending message to: " + logger.colors.underline + peer)
|
|
||||||
self.onionrUtils.sendPM(peer, message)
|
|
||||||
|
|
||||||
|
|
||||||
def listKeys(self):
|
def listKeys(self):
|
||||||
'''
|
'''
|
||||||
Displays a list of keys (used to be called peers) (?)
|
Displays a list of keys (used to be called peers) (?)
|
||||||
|
@ -447,13 +408,6 @@ class Onionr:
|
||||||
logger.error('Failed to insert block.', timestamp = False)
|
logger.error('Failed to insert block.', timestamp = False)
|
||||||
return
|
return
|
||||||
|
|
||||||
def getPMs(self):
|
|
||||||
'''
|
|
||||||
display PMs sent to us
|
|
||||||
'''
|
|
||||||
|
|
||||||
self.onionrUtils.loadPMs()
|
|
||||||
|
|
||||||
def enablePlugin(self):
|
def enablePlugin(self):
|
||||||
'''
|
'''
|
||||||
Enables and starts the given plugin
|
Enables and starts the given plugin
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import core as onionrcore, logger, config, onionrexceptions
|
import core as onionrcore, logger, config, onionrexceptions, nacl.exceptions
|
||||||
import json, os, sys, datetime, base64
|
import json, os, sys, datetime, base64
|
||||||
|
|
||||||
class Block:
|
class Block:
|
||||||
|
@ -42,8 +42,6 @@ class Block:
|
||||||
# initialize variables
|
# initialize variables
|
||||||
self.valid = True
|
self.valid = True
|
||||||
self.raw = None
|
self.raw = None
|
||||||
self.powHash = None
|
|
||||||
self.powToken = None
|
|
||||||
self.signed = False
|
self.signed = False
|
||||||
self.signature = None
|
self.signature = None
|
||||||
self.signedData = None
|
self.signedData = None
|
||||||
|
@ -51,23 +49,69 @@ class Block:
|
||||||
self.parent = None
|
self.parent = None
|
||||||
self.bheader = {}
|
self.bheader = {}
|
||||||
self.bmetadata = {}
|
self.bmetadata = {}
|
||||||
|
self.isEncrypted = False
|
||||||
|
self.decrypted = False
|
||||||
|
self.signer = None
|
||||||
|
self.validSig = False
|
||||||
|
|
||||||
# handle arguments
|
# handle arguments
|
||||||
if self.getCore() is None:
|
if self.getCore() is None:
|
||||||
self.core = onionrcore.Core()
|
self.core = onionrcore.Core()
|
||||||
|
|
||||||
if not self.core._utils.validateHash(self.hash):
|
|
||||||
raise onionrexceptions.InvalidHexHash('specified block hash is not valid')
|
|
||||||
|
|
||||||
# update the blocks' contents if it exists
|
# update the blocks' contents if it exists
|
||||||
if not self.getHash() is None:
|
if not self.getHash() is None:
|
||||||
if not self.update():
|
if not self.core._utils.validateHash(self.hash):
|
||||||
|
logger.debug('Block hash %s is invalid.' % self.getHash())
|
||||||
|
raise onionrexceptions.InvalidHexHash('Block hash is invalid.')
|
||||||
|
elif not self.update():
|
||||||
logger.debug('Failed to open block %s.' % self.getHash())
|
logger.debug('Failed to open block %s.' % self.getHash())
|
||||||
else:
|
else:
|
||||||
logger.debug('Did not update block')
|
pass
|
||||||
|
#logger.debug('Did not update block.')
|
||||||
|
|
||||||
# logic
|
# logic
|
||||||
|
|
||||||
|
def decrypt(self, anonymous=True, encodedData=True):
|
||||||
|
'''Decrypt a block, loading decrypted data into their vars'''
|
||||||
|
if self.decrypted:
|
||||||
|
return True
|
||||||
|
retData = False
|
||||||
|
core = self.getCore()
|
||||||
|
# decrypt data
|
||||||
|
if self.getHeader('encryptType') == 'asym':
|
||||||
|
try:
|
||||||
|
self.bcontent = core._crypto.pubKeyDecrypt(self.bcontent, anonymous=anonymous, encodedData=encodedData)
|
||||||
|
bmeta = core._crypto.pubKeyDecrypt(self.bmetadata, anonymous=anonymous, encodedData=encodedData)
|
||||||
|
try:
|
||||||
|
bmeta = bmeta.decode()
|
||||||
|
except AttributeError:
|
||||||
|
# yet another bytes fix
|
||||||
|
pass
|
||||||
|
self.bmetadata = json.loads(bmeta)
|
||||||
|
self.signature = core._crypto.pubKeyDecrypt(self.signature, anonymous=anonymous, encodedData=encodedData)
|
||||||
|
self.signer = core._crypto.pubKeyDecrypt(self.signer, anonymous=anonymous, encodedData=encodedData)
|
||||||
|
self.signedData = json.dumps(self.bmetadata) + self.bcontent.decode()
|
||||||
|
except nacl.exceptions.CryptoError:
|
||||||
|
pass
|
||||||
|
#logger.debug('Could not decrypt block. Either invalid key or corrupted data')
|
||||||
|
else:
|
||||||
|
retData = True
|
||||||
|
self.decrypted = True
|
||||||
|
else:
|
||||||
|
logger.warn('symmetric decryption is not yet supported by this API')
|
||||||
|
return retData
|
||||||
|
|
||||||
|
def verifySig(self):
|
||||||
|
'''Verify if a block's signature is signed by its claimed signer'''
|
||||||
|
core = self.getCore()
|
||||||
|
|
||||||
|
if core._crypto.edVerify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
|
||||||
|
self.validSig = True
|
||||||
|
else:
|
||||||
|
self.validSig = False
|
||||||
|
return self.validSig
|
||||||
|
|
||||||
|
|
||||||
def update(self, data = None, file = None):
|
def update(self, data = None, file = None):
|
||||||
'''
|
'''
|
||||||
Loads data from a block in to the current object.
|
Loads data from a block in to the current object.
|
||||||
|
@ -118,14 +162,19 @@ class Block:
|
||||||
self.raw = str(blockdata)
|
self.raw = str(blockdata)
|
||||||
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
|
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
|
||||||
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
|
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
|
||||||
self.bmetadata = json.loads(self.getHeader('meta', None))
|
if self.bheader['encryptType'] in ('asym', 'sym'):
|
||||||
|
self.bmetadata = self.getHeader('meta', None)
|
||||||
|
self.isEncrypted = True
|
||||||
|
else:
|
||||||
|
self.bmetadata = json.loads(self.getHeader('meta', None))
|
||||||
self.parent = self.getMetadata('parent', None)
|
self.parent = self.getMetadata('parent', None)
|
||||||
self.btype = self.getMetadata('type', None)
|
self.btype = self.getMetadata('type', None)
|
||||||
self.powHash = self.getMetadata('powHash', None)
|
|
||||||
self.powToken = self.getMetadata('powToken', None)
|
|
||||||
self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
|
self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
|
||||||
|
# TODO: detect if signer is hash of pubkey or not
|
||||||
|
self.signer = self.getHeader('signer', None)
|
||||||
self.signature = self.getHeader('sig', None)
|
self.signature = self.getHeader('sig', None)
|
||||||
self.signedData = (None if not self.isSigned() else self.getHeader('meta') + '\n' + self.getContent())
|
# 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.date = self.getCore().getBlockDate(self.getHash())
|
||||||
|
|
||||||
if not self.getDate() is None:
|
if not self.getDate() is None:
|
||||||
|
@ -214,7 +263,6 @@ class Block:
|
||||||
Outputs:
|
Outputs:
|
||||||
- (str): the type of the block
|
- (str): the type of the block
|
||||||
'''
|
'''
|
||||||
|
|
||||||
return self.btype
|
return self.btype
|
||||||
|
|
||||||
def getRaw(self):
|
def getRaw(self):
|
||||||
|
@ -471,6 +519,8 @@ class Block:
|
||||||
if not signer is None:
|
if not signer is None:
|
||||||
if isinstance(signer, (str,)):
|
if isinstance(signer, (str,)):
|
||||||
signer = [signer]
|
signer = [signer]
|
||||||
|
if isinstance(signer, (bytes,)):
|
||||||
|
signer = [signer.decode()]
|
||||||
|
|
||||||
isSigner = False
|
isSigner = False
|
||||||
for key in signer:
|
for key in signer:
|
||||||
|
@ -483,12 +533,13 @@ class Block:
|
||||||
|
|
||||||
if relevant:
|
if relevant:
|
||||||
relevant_blocks.append(block)
|
relevant_blocks.append(block)
|
||||||
|
|
||||||
if bool(reverse):
|
if bool(reverse):
|
||||||
relevant_blocks.reverse()
|
relevant_blocks.reverse()
|
||||||
|
|
||||||
return relevant_blocks
|
return relevant_blocks
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(('Failed to get blocks: %s' % str(e)) + logger.parse_error())
|
logger.debug('Failed to get blocks.', error = e)
|
||||||
|
|
||||||
return list()
|
return list()
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,11 @@ class OnionrCrypto:
|
||||||
'''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)'''
|
'''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)'''
|
||||||
retVal = ''
|
retVal = ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
pubkey = pubkey.encode()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
if encodedData:
|
if encodedData:
|
||||||
encoding = nacl.encoding.Base64Encoder
|
encoding = nacl.encoding.Base64Encoder
|
||||||
else:
|
else:
|
||||||
|
@ -127,7 +132,11 @@ class OnionrCrypto:
|
||||||
elif anonymous:
|
elif anonymous:
|
||||||
key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key()
|
key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key()
|
||||||
anonBox = nacl.public.SealedBox(key)
|
anonBox = nacl.public.SealedBox(key)
|
||||||
retVal = anonBox.encrypt(data.encode(), encoder=encoding)
|
try:
|
||||||
|
data = data.encode()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
retVal = anonBox.encrypt(data, encoder=encoding)
|
||||||
return retVal
|
return retVal
|
||||||
|
|
||||||
def pubKeyDecrypt(self, data, pubkey='', anonymous=False, encodedData=False):
|
def pubKeyDecrypt(self, data, pubkey='', anonymous=False, encodedData=False):
|
||||||
|
|
|
@ -159,7 +159,11 @@ class POW:
|
||||||
self.metadata['powRandomToken'] = base64.b64encode(rand).decode()
|
self.metadata['powRandomToken'] = base64.b64encode(rand).decode()
|
||||||
payload = json.dumps(self.metadata).encode() + b'\n' + self.data
|
payload = json.dumps(self.metadata).encode() + b'\n' + self.data
|
||||||
token = myCore._crypto.sha3Hash(payload)
|
token = myCore._crypto.sha3Hash(payload)
|
||||||
#print(token)
|
try:
|
||||||
|
# on some versions, token is bytes
|
||||||
|
token = token.decode()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
if self.puzzle == token[0:self.difficulty]:
|
if self.puzzle == token[0:self.difficulty]:
|
||||||
self.hashing = False
|
self.hashing = False
|
||||||
iFound = True
|
iFound = True
|
||||||
|
|
|
@ -59,7 +59,7 @@ class OnionrUtils:
|
||||||
High level function to encrypt a message to a peer and insert it as a block
|
High level function to encrypt a message to a peer and insert it as a block
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self._core.insertBlock(message, header='pm', sign=True, encryptType='sym', symKey=pubkey)
|
self._core.insertBlock(message, header='pm', sign=True, encryptType='asym', asymPeer=pubkey)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -117,7 +117,8 @@ class OnionrUtils:
|
||||||
else:
|
else:
|
||||||
logger.warn("Failed to add key")
|
logger.warn("Failed to add key")
|
||||||
else:
|
else:
|
||||||
logger.warn('%s pow failed' % key[0])
|
pass
|
||||||
|
#logger.debug('%s pow failed' % key[0])
|
||||||
return retVal
|
return retVal
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error('Failed to merge keys.', error=error)
|
logger.error('Failed to merge keys.', error=error)
|
||||||
|
@ -137,7 +138,8 @@ class OnionrUtils:
|
||||||
logger.info('Added %s to db.' % adder, timestamp = True)
|
logger.info('Added %s to db.' % adder, timestamp = True)
|
||||||
retVal = True
|
retVal = True
|
||||||
else:
|
else:
|
||||||
logger.debug('%s is either our address or already in our DB' % adder)
|
pass
|
||||||
|
#logger.debug('%s is either our address or already in our DB' % adder)
|
||||||
return retVal
|
return retVal
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error('Failed to merge adders.', error = error)
|
logger.error('Failed to merge adders.', error = error)
|
||||||
|
@ -204,18 +206,23 @@ class OnionrUtils:
|
||||||
|
|
||||||
def getBlockMetadataFromData(self, blockData):
|
def getBlockMetadataFromData(self, blockData):
|
||||||
'''
|
'''
|
||||||
accepts block contents as string and returns a tuple of metadata, meta (meta being internal metadata)
|
accepts block contents as string, returns a tuple of metadata, meta (meta being internal metadata, which will be returned as an encrypted base64 string if it is encrypted, dict if not).
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
meta = {}
|
||||||
try:
|
try:
|
||||||
blockData = blockData.encode()
|
blockData = blockData.encode()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
metadata = json.loads(blockData[:blockData.find(b'\n')].decode())
|
metadata = json.loads(blockData[:blockData.find(b'\n')].decode())
|
||||||
data = blockData[blockData.find(b'\n'):].decode()
|
data = blockData[blockData.find(b'\n'):].decode()
|
||||||
try:
|
|
||||||
meta = json.loads(metadata['meta'])
|
if not metadata['encryptType'] in ('asym', 'sym'):
|
||||||
except KeyError:
|
try:
|
||||||
meta = {}
|
meta = json.loads(metadata['meta'])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
meta = metadata['meta']
|
||||||
return (metadata, meta, data)
|
return (metadata, meta, data)
|
||||||
|
|
||||||
def checkPort(self, port, host=''):
|
def checkPort(self, port, host=''):
|
||||||
|
@ -251,7 +258,14 @@ class OnionrUtils:
|
||||||
Read metadata from a block and cache it to the block database
|
Read metadata from a block and cache it to the block database
|
||||||
'''
|
'''
|
||||||
myBlock = Block(blockHash, self._core)
|
myBlock = Block(blockHash, self._core)
|
||||||
self._core.updateBlockInfo(blockHash, 'dataType', myBlock.getType())
|
if myBlock.isEncrypted:
|
||||||
|
myBlock.decrypt()
|
||||||
|
blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks
|
||||||
|
try:
|
||||||
|
if len(blockType) <= 10:
|
||||||
|
self._core.updateBlockInfo(blockHash, 'dataType', blockType)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
def escapeAnsi(self, line):
|
def escapeAnsi(self, line):
|
||||||
'''
|
'''
|
||||||
|
@ -426,52 +440,6 @@ class OnionrUtils:
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def loadPMs(self):
|
|
||||||
'''
|
|
||||||
Find, decrypt, and return array of PMs (array of dictionary, {from, text})
|
|
||||||
'''
|
|
||||||
blocks = Block.getBlocks(type = 'pm', core = self._core)
|
|
||||||
message = ''
|
|
||||||
sender = ''
|
|
||||||
for i in blocks:
|
|
||||||
try:
|
|
||||||
blockContent = i.getContent()
|
|
||||||
|
|
||||||
try:
|
|
||||||
message = self._core._crypto.pubKeyDecrypt(blockContent, encodedData=True, anonymous=True)
|
|
||||||
except nacl.exceptions.CryptoError as e:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
message = message.decode()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
message = json.loads(message)
|
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
logger.debug('Decrypted %s:' % i.getHash())
|
|
||||||
logger.info(message["msg"])
|
|
||||||
|
|
||||||
signer = message["id"]
|
|
||||||
sig = message["sig"]
|
|
||||||
|
|
||||||
if self.validatePubKey(signer):
|
|
||||||
if self._core._crypto.edVerify(message["msg"], signer, sig, encodedData=True):
|
|
||||||
logger.info("Good signature by %s" % signer)
|
|
||||||
else:
|
|
||||||
logger.warn("Bad signature by %s" % signer)
|
|
||||||
else:
|
|
||||||
logger.warn('Bad sender id: %s' % signer)
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
except Exception as error:
|
|
||||||
logger.error('Failed to open block %s.' % i, error=error)
|
|
||||||
return
|
|
||||||
|
|
||||||
def getPeerByHashId(self, hash):
|
def getPeerByHashId(self, hash):
|
||||||
'''
|
'''
|
||||||
Return the pubkey of the user if known from the hash
|
Return the pubkey of the user if known from the hash
|
||||||
|
|
|
@ -21,4 +21,4 @@
|
||||||
class OnionrValues:
|
class OnionrValues:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.passwordLength = 20
|
self.passwordLength = 20
|
||||||
self.blockMetadataLengths = {'meta': 1000, 'sig': 88, 'signer': 64, 'time': 10, 'powRandomToken': 1000, 'encryptType': 4}
|
self.blockMetadataLengths = {'meta': 1000, 'sig': 200, 'signer': 200, 'time': 10, 'powRandomToken': 1000, 'encryptType': 4} #TODO properly refine values to minimum needed
|
|
@ -47,25 +47,24 @@ class OnionrFlow:
|
||||||
self.flowRunning = False
|
self.flowRunning = False
|
||||||
|
|
||||||
if len(message) > 0:
|
if len(message) > 0:
|
||||||
self.myCore.insertBlock(message)
|
Block(content = message, type = 'txt', core = self.myCore).save()
|
||||||
|
|
||||||
logger.info("Flow is exiting, goodbye")
|
logger.info("Flow is exiting, goodbye")
|
||||||
return
|
return
|
||||||
|
|
||||||
def showOutput(self):
|
def showOutput(self):
|
||||||
while self.flowRunning:
|
while self.flowRunning:
|
||||||
for blockHash in self.myCore.getBlocksByType('txt'):
|
for block in Block.getBlocks(type = 'txt', core = self.myCore):
|
||||||
if blockHash in self.alreadyOutputed:
|
if block.getHash() in self.alreadyOutputed:
|
||||||
continue
|
continue
|
||||||
if not self.flowRunning:
|
if not self.flowRunning:
|
||||||
break
|
break
|
||||||
logger.info('\n------------------------')
|
logger.info('\n------------------------', prompt = False)
|
||||||
block = Block(blockHash, self.myCore)
|
|
||||||
content = block.getContent()
|
content = block.getContent()
|
||||||
# Escape new lines, remove trailing whitespace, and escape ansi sequences
|
# Escape new lines, remove trailing whitespace, and escape ansi sequences
|
||||||
content = self.myCore._utils.escapeAnsi(content.replace('\n', '\\n').replace('\r', '\\r').strip())
|
content = self.myCore._utils.escapeAnsi(content.replace('\n', '\\n').replace('\r', '\\r').strip())
|
||||||
logger.info("\n" + block.getDate().strftime("%m/%d %H:%M") + ' - ' + '\033[0;0m' + content)
|
logger.info(block.getDate().strftime("%m/%d %H:%M") + ' - ' + logger.colors.reset + content, prompt = False)
|
||||||
self.alreadyOutputed.append(blockHash)
|
self.alreadyOutputed.append(block.getHash())
|
||||||
try:
|
try:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
@ -84,6 +83,6 @@ def on_init(api, data = None):
|
||||||
global pluginapi
|
global pluginapi
|
||||||
pluginapi = api
|
pluginapi = api
|
||||||
flow = OnionrFlow()
|
flow = OnionrFlow()
|
||||||
api.commands.register(['flow'], flow.start)
|
api.commands.register('flow', flow.start)
|
||||||
api.commands.register_help('flow', 'Open the flow messaging interface')
|
api.commands.register_help('flow', 'Open the flow messaging interface')
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
'''
|
|
||||||
Onionr - P2P Microblogging Platform & Social network
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Imports some useful libraries
|
|
||||||
import logger, config, core
|
|
||||||
import os, sqlite3, threading
|
|
||||||
from onionrblockapi import Block
|
|
||||||
|
|
||||||
plugin_name = 'gui'
|
|
||||||
|
|
||||||
def send():
|
|
||||||
global message
|
|
||||||
block = Block()
|
|
||||||
block.setType('txt')
|
|
||||||
block.setContent(message)
|
|
||||||
logger.debug('Sent message in block %s.' % block.save(sign = True))
|
|
||||||
|
|
||||||
|
|
||||||
def sendMessage():
|
|
||||||
global sendEntry
|
|
||||||
|
|
||||||
global message
|
|
||||||
message = sendEntry.get()
|
|
||||||
|
|
||||||
t = threading.Thread(target = send)
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
sendEntry.delete(0, len(message))
|
|
||||||
|
|
||||||
def update():
|
|
||||||
global listedBlocks, listbox, runningCheckDelayCount, runningCheckDelay, root, daemonStatus
|
|
||||||
|
|
||||||
for i in Block.getBlocks(type = 'txt'):
|
|
||||||
if i.getContent().strip() == '' or i.getHash() in listedBlocks:
|
|
||||||
continue
|
|
||||||
listbox.insert(99999, str(i.getContent()))
|
|
||||||
listedBlocks.append(i.getHash())
|
|
||||||
listbox.see(99999)
|
|
||||||
|
|
||||||
runningCheckDelayCount += 1
|
|
||||||
|
|
||||||
if runningCheckDelayCount == runningCheckDelay:
|
|
||||||
resp = pluginapi.daemon.local_command('ping')
|
|
||||||
if resp == 'pong':
|
|
||||||
daemonStatus.config(text = "Onionr Daemon Status: Running")
|
|
||||||
else:
|
|
||||||
daemonStatus.config(text = "Onionr Daemon Status: Not Running")
|
|
||||||
runningCheckDelayCount = 0
|
|
||||||
root.after(10000, update)
|
|
||||||
|
|
||||||
|
|
||||||
def reallyOpenGUI():
|
|
||||||
import tkinter
|
|
||||||
global root, runningCheckDelay, runningCheckDelayCount, scrollbar, listedBlocks, nodeInfo, keyInfo, idText, idEntry, pubKeyEntry, listbox, daemonStatus, sendEntry
|
|
||||||
|
|
||||||
root = tkinter.Tk()
|
|
||||||
|
|
||||||
root.title("Onionr GUI")
|
|
||||||
|
|
||||||
runningCheckDelay = 5
|
|
||||||
runningCheckDelayCount = 4
|
|
||||||
|
|
||||||
scrollbar = tkinter.Scrollbar(root)
|
|
||||||
scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
|
|
||||||
|
|
||||||
listedBlocks = []
|
|
||||||
|
|
||||||
nodeInfo = tkinter.Frame(root)
|
|
||||||
keyInfo = tkinter.Frame(root)
|
|
||||||
|
|
||||||
hostname = pluginapi.get_onionr().get_hostname()
|
|
||||||
logger.debug('Onionr Hostname: %s' % hostname)
|
|
||||||
idText = hostname
|
|
||||||
|
|
||||||
idEntry = tkinter.Entry(nodeInfo)
|
|
||||||
tkinter.Label(nodeInfo, text = "Node Address: ").pack(side=tkinter.LEFT)
|
|
||||||
idEntry.pack()
|
|
||||||
idEntry.insert(0, idText.strip())
|
|
||||||
idEntry.configure(state="readonly")
|
|
||||||
|
|
||||||
nodeInfo.pack()
|
|
||||||
|
|
||||||
pubKeyEntry = tkinter.Entry(keyInfo)
|
|
||||||
|
|
||||||
tkinter.Label(keyInfo, text="Public key: ").pack(side=tkinter.LEFT)
|
|
||||||
|
|
||||||
pubKeyEntry.pack()
|
|
||||||
pubKeyEntry.insert(0, pluginapi.get_core()._crypto.pubKey)
|
|
||||||
pubKeyEntry.configure(state="readonly")
|
|
||||||
|
|
||||||
keyInfo.pack()
|
|
||||||
|
|
||||||
sendEntry = tkinter.Entry(root)
|
|
||||||
sendBtn = tkinter.Button(root, text='Send Message', command=sendMessage)
|
|
||||||
sendEntry.pack(side=tkinter.TOP, pady=5)
|
|
||||||
sendBtn.pack(side=tkinter.TOP)
|
|
||||||
|
|
||||||
listbox = tkinter.Listbox(root, yscrollcommand=tkinter.Scrollbar.set, height=15)
|
|
||||||
|
|
||||||
listbox.pack(fill=tkinter.BOTH, pady=25)
|
|
||||||
|
|
||||||
daemonStatus = tkinter.Label(root, text="Onionr Daemon Status: unknown")
|
|
||||||
daemonStatus.pack()
|
|
||||||
|
|
||||||
scrollbar.config(command=tkinter.Listbox.yview)
|
|
||||||
root.after(2000, update)
|
|
||||||
root.mainloop()
|
|
||||||
|
|
||||||
def openGUI():
|
|
||||||
t = threading.Thread(target = reallyOpenGUI)
|
|
||||||
t.daemon = False
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
def on_init(api, data = None):
|
|
||||||
global pluginapi
|
|
||||||
pluginapi = api
|
|
||||||
|
|
||||||
api.commands.register(['gui', 'launch-gui', 'open-gui'], openGUI)
|
|
||||||
api.commands.register_help('gui', 'Opens a graphical interface for Onionr')
|
|
||||||
|
|
||||||
return
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name" : "gui",
|
"name" : "pms",
|
||||||
"version" : "1.0",
|
"version" : "1.0",
|
||||||
"author" : "onionr"
|
"author" : "onionr"
|
||||||
}
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
'''
|
||||||
|
Onionr - P2P Microblogging Platform & Social network
|
||||||
|
|
||||||
|
This default plugin handles private messages in an email like fashion
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Imports some useful libraries
|
||||||
|
import logger, config, threading, time, readline, datetime
|
||||||
|
from onionrblockapi import Block
|
||||||
|
import onionrexceptions
|
||||||
|
|
||||||
|
plugin_name = 'pms'
|
||||||
|
PLUGIN_VERSION = '0.0.1'
|
||||||
|
|
||||||
|
def draw_border(text):
|
||||||
|
#https://stackoverflow.com/a/20757491
|
||||||
|
lines = text.splitlines()
|
||||||
|
width = max(len(s) for s in lines)
|
||||||
|
res = ['┌' + '─' * width + '┐']
|
||||||
|
for s in lines:
|
||||||
|
res.append('│' + (s + ' ' * width)[:width] + '│')
|
||||||
|
res.append('└' + '─' * width + '┘')
|
||||||
|
return '\n'.join(res)
|
||||||
|
|
||||||
|
|
||||||
|
class MailStrings:
|
||||||
|
def __init__(self, mailInstance):
|
||||||
|
self.mailInstance = mailInstance
|
||||||
|
|
||||||
|
self.programTag = 'OnionrMail v%s' % (PLUGIN_VERSION)
|
||||||
|
choices = ['view inbox', 'view sentbox', 'send message', 'help', 'quit']
|
||||||
|
self.mainMenuChoices = choices
|
||||||
|
self.mainMenu = '''\n
|
||||||
|
-----------------
|
||||||
|
1. %s
|
||||||
|
2. %s
|
||||||
|
3. %s
|
||||||
|
4. %s
|
||||||
|
5. %s''' % (choices[0], choices[1], choices[2], choices[3], choices[4])
|
||||||
|
|
||||||
|
class OnionrMail:
|
||||||
|
def __init__(self, pluginapi):
|
||||||
|
self.myCore = pluginapi.get_core()
|
||||||
|
#self.dataFolder = pluginapi.get_data_folder()
|
||||||
|
self.strings = MailStrings(self)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def inbox(self):
|
||||||
|
blockCount = 0
|
||||||
|
pmBlockMap = {}
|
||||||
|
pmBlocks = {}
|
||||||
|
logger.info('Decrypting messages...')
|
||||||
|
choice = ''
|
||||||
|
|
||||||
|
# this could use a lot of memory if someone has recieved a lot of messages
|
||||||
|
for blockHash in self.myCore.getBlocksByType('pm'):
|
||||||
|
pmBlocks[blockHash] = Block(blockHash, core=self.myCore)
|
||||||
|
pmBlocks[blockHash].decrypt()
|
||||||
|
|
||||||
|
while choice not in ('-q', 'q', 'quit'):
|
||||||
|
blockCount = 0
|
||||||
|
for blockHash in pmBlocks:
|
||||||
|
if not pmBlocks[blockHash].decrypted:
|
||||||
|
continue
|
||||||
|
blockCount += 1
|
||||||
|
pmBlockMap[blockCount] = blockHash
|
||||||
|
blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M")
|
||||||
|
print('%s. %s: %s' % (blockCount, blockDate, blockHash))
|
||||||
|
|
||||||
|
try:
|
||||||
|
choice = logger.readline('Enter a block number, -r to refresh, or -q to stop: ').strip().lower()
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
choice = '-q'
|
||||||
|
|
||||||
|
if choice in ('-q', 'q', 'quit'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if choice in ('-r', 'r', 'refresh'):
|
||||||
|
# dirty hack
|
||||||
|
self.inbox()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
choice = int(choice)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
pmBlockMap[choice]
|
||||||
|
readBlock = pmBlocks[pmBlockMap[choice]]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
readBlock.verifySig()
|
||||||
|
print('Message recieved from', readBlock.signer)
|
||||||
|
print('Valid signature:', readBlock.validSig)
|
||||||
|
if not readBlock.validSig:
|
||||||
|
logger.warn('This message has an INVALID signature. Anyone could have sent this message.')
|
||||||
|
logger.readline('Press enter to continue to message.')
|
||||||
|
|
||||||
|
print(draw_border(self.myCore._utils.escapeAnsi(readBlock.bcontent.decode().strip())))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def draftMessage(self):
|
||||||
|
message = ''
|
||||||
|
newLine = ''
|
||||||
|
recip = ''
|
||||||
|
entering = True
|
||||||
|
|
||||||
|
while entering:
|
||||||
|
try:
|
||||||
|
recip = logger.readline('Enter peer address, or q to stop:').strip()
|
||||||
|
if recip in ('-q', 'q'):
|
||||||
|
raise EOFError
|
||||||
|
if not self.myCore._utils.validatePubKey(recip):
|
||||||
|
raise onionrexceptions.InvalidPubkey('Must be a valid ed25519 base32 encoded public key')
|
||||||
|
except onionrexceptions.InvalidPubkey:
|
||||||
|
logger.warn('Invalid public key')
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
entering = False
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# if -q or ctrl-c/d, exit function here, otherwise we successfully got the public key
|
||||||
|
return
|
||||||
|
|
||||||
|
print('Enter your message, stop by entering -q on a new line.')
|
||||||
|
while newLine != '-q':
|
||||||
|
try:
|
||||||
|
newLine = input()
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
pass
|
||||||
|
if newLine == '-q':
|
||||||
|
continue
|
||||||
|
newLine += '\n'
|
||||||
|
message += newLine
|
||||||
|
|
||||||
|
print('Inserting encrypted message as Onionr block....')
|
||||||
|
|
||||||
|
self.myCore.insertBlock(message, header='pm', encryptType='asym', asymPeer=recip, sign=True)
|
||||||
|
|
||||||
|
def menu(self):
|
||||||
|
choice = ''
|
||||||
|
while True:
|
||||||
|
|
||||||
|
print(self.strings.programTag + '\n\nOur ID: ' + self.myCore._crypto.pubKey + self.strings.mainMenu.title()) # print out main menu
|
||||||
|
|
||||||
|
try:
|
||||||
|
choice = logger.readline('Enter 1-%s:\n' % (len(self.strings.mainMenuChoices))).lower().strip()
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
choice = '5'
|
||||||
|
|
||||||
|
if choice in (self.strings.mainMenuChoices[0], '1'):
|
||||||
|
self.inbox()
|
||||||
|
elif choice in (self.strings.mainMenuChoices[1], '2'):
|
||||||
|
logger.warn('not implemented yet')
|
||||||
|
elif choice in (self.strings.mainMenuChoices[2], '3'):
|
||||||
|
self.draftMessage()
|
||||||
|
elif choice in (self.strings.mainMenuChoices[3], '4'):
|
||||||
|
logger.warn('not implemented yet')
|
||||||
|
elif choice in (self.strings.mainMenuChoices[4], '5'):
|
||||||
|
logger.info('Goodbye.')
|
||||||
|
break
|
||||||
|
elif choice == '':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
logger.warn('Invalid choice.')
|
||||||
|
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.
|
||||||
|
'''
|
||||||
|
|
||||||
|
pluginapi = api
|
||||||
|
mail = OnionrMail(pluginapi)
|
||||||
|
api.commands.register(['mail'], mail.menu)
|
||||||
|
api.commands.register_help('mail', 'Interact with OnionrMail')
|
||||||
|
return
|
|
@ -1,6 +1,5 @@
|
||||||
![Onionr logo](./docs/onionr-logo.png)
|
![Onionr logo](./docs/onionr-logo.png)
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/beardog108/onionr.svg?branch=master)](https://travis-ci.org/beardog108/onionr)
|
|
||||||
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
|
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,10 +9,11 @@ Major work in progress.
|
||||||
|
|
||||||
***THIS SOFTWARE IS NOT USABLE OR SECURE YET.***
|
***THIS SOFTWARE IS NOT USABLE OR SECURE YET.***
|
||||||
|
|
||||||
|
**The main repo for this software is at https://gitlab.com/beardog/Onionr/**
|
||||||
|
|
||||||
**Roadmap/features:**
|
**Roadmap/features:**
|
||||||
|
|
||||||
Check the [GitHub Project](https://github.com/beardog108/onionr/projects/1) to see progress towards the alpha release.
|
Check the [Gitlab Project](https://gitlab.com/beardog/Onionr/milestones/1) to see progress towards the alpha release.
|
||||||
|
|
||||||
* [X] Fully p2p/decentralized, no trackers or other single points of failure
|
* [X] Fully p2p/decentralized, no trackers or other single points of failure
|
||||||
* [X] High level of anonymity
|
* [X] High level of anonymity
|
||||||
|
|
Loading…
Reference in New Issue