Merge branch 'new-pm' into 'master'

New Plugins, bug fixes

See merge request beardog/Onionr!2
master
Kevin 2018-07-20 04:33:47 +00:00
commit eb00904fde
14 changed files with 358 additions and 294 deletions

View File

@ -25,7 +25,6 @@ from defusedxml import minidom
class OnionrCommunicatorDaemon:
def __init__(self, debug, developmentMode):
logger.warn('New (unstable) communicator is being used.')
# list of timer instances
self.timers = []
@ -57,6 +56,9 @@ class OnionrCommunicatorDaemon:
# list of new blocks to download, added to when new block lists are fetched from peers
self.blockQueue = []
# list of blocks currently downloading, avoid s
self.currentDownloading = []
# Clear the daemon queue for any dead messages
if os.path.exists(self._core.queueDB):
self._core.clearDaemonQueue()
@ -77,9 +79,9 @@ class OnionrCommunicatorDaemon:
peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60)
OnionrCommunicatorTimers(self, self.lookupBlocks, 7, requiresPeer=True)
OnionrCommunicatorTimers(self, self.getBlocks, 10, requiresPeer=True)
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 120)
OnionrCommunicatorTimers(self, self.lookupKeys, 125, requiresPeer=True)
OnionrCommunicatorTimers(self, self.lookupAdders, 600, requiresPeer=True)
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58)
OnionrCommunicatorTimers(self, self.lookupKeys, 60, requiresPeer=True)
OnionrCommunicatorTimers(self, self.lookupAdders, 60, requiresPeer=True)
# set loop to execute instantly to load up peer pool (replaced old pool init wait)
peerPoolTimer.count = (peerPoolTimer.frequency - 1)
@ -122,8 +124,7 @@ class OnionrCommunicatorDaemon:
peer = self.pickOnlinePeer()
newAdders = self.peerAction(peer, action='pex')
self._core._utils.mergeAdders(newAdders)
self.decrementThreadCount('lookupKeys')
self.decrementThreadCount('lookupAdders')
def lookupBlocks(self):
'''Lookup new blocks & add them to download queue'''
@ -154,6 +155,10 @@ class OnionrCommunicatorDaemon:
def getBlocks(self):
'''download new blocks in queue'''
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)
content = self.peerAction(self.pickOnlinePeer(), 'getData', data=blockHash) # block content from random peer (includes metadata)
if content != False:
@ -171,7 +176,7 @@ class OnionrCommunicatorDaemon:
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
metadata = metas[0]
meta = metas[1]
#meta = metas[1]
if self._core._utils.validateMetadata(metadata): # check if metadata is valid
if self._core._crypto.verifyPow(content): # check if POW is enough/correct
logger.info('Block passed proof, saving.')
@ -191,6 +196,7 @@ class OnionrCommunicatorDaemon:
pass
logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
self.blockQueue.remove(blockHash) # remove from block queue both if success or false
self.currentDownloading.remove(blockHash)
self.decrementThreadCount('getBlocks')
return

View File

@ -628,13 +628,16 @@ class Core:
return None
def getBlocksByType(self, blockType):
def getBlocksByType(self, blockType, orderDate=True):
'''
Returns a list of blocks by the type
'''
conn = sqlite3.connect(self.blockDB)
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,)
rows = list()
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
if sign:
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:
raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes')
@ -728,15 +731,16 @@ class Core:
if encryptType == 'sym':
if len(symKey) < self.requirements.passwordLength:
raise onionrexceptions.SecurityError('Weak encryption key')
jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True)
data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True)
signature = self._crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True)
signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True)
jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True).decode()
data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True).decode()
signature = self._crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True).decode()
signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True).decode()
elif encryptType == 'asym':
if self._utils.validatePubKey(asymPeer):
jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True)
data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True)
signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True)
jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True, anonymous=True).decode()
data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True, anonymous=True).decode()
signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True, anonymous=True).decode()
signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True, anonymous=True).decode()
else:
raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key')

View File

@ -134,7 +134,7 @@ def raw(data, fd = sys.stdout):
with open(_outputfile, "a+") as f:
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
prefix : The prefix to the output
@ -145,7 +145,7 @@ def log(prefix, data, color = '', timestamp=True, fd = sys.stdout):
if timestamp:
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:
output = colors.filter(output)
@ -201,31 +201,37 @@ def confirm(default = 'y', message = 'Are you sure %s? '):
return default == 'y'
# 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:
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
def info(data, timestamp=False):
def info(data, timestamp = False, prompt = True):
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
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:
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
def error(data, error=None, timestamp=True):
def error(data, error = None, timestamp = True, prompt = True):
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:
debug('Error: ' + str(error) + parse_error())
# 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:
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
def parse_error():

View File

@ -50,7 +50,6 @@ class Onionr:
Main Onionr class. This is for the CLI program, and does not handle much of the logic.
In general, external programs and plugins should not use this class.
'''
try:
os.chdir(sys.path[0])
except FileNotFoundError:
@ -181,15 +180,6 @@ class Onionr:
'listkeys': 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,
'add-peer': self.addPeer,
'add-address': self.addAddress,
@ -226,9 +216,6 @@ class Onionr:
'create-plugin': 'Creates directory structure for a plugin',
'add-peer': 'Adds a peer to database',
'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',
'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)',
'listconn': 'list connected peers',
@ -341,32 +328,6 @@ class Onionr:
logger.info('Sending kex to command queue...')
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):
'''
Displays a list of keys (used to be called peers) (?)
@ -447,13 +408,6 @@ class Onionr:
logger.error('Failed to insert block.', timestamp = False)
return
def getPMs(self):
'''
display PMs sent to us
'''
self.onionrUtils.loadPMs()
def enablePlugin(self):
'''
Enables and starts the given plugin

View File

@ -18,7 +18,7 @@
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
class Block:
@ -42,8 +42,6 @@ class Block:
# initialize variables
self.valid = True
self.raw = None
self.powHash = None
self.powToken = None
self.signed = False
self.signature = None
self.signedData = None
@ -51,23 +49,69 @@ class Block:
self.parent = None
self.bheader = {}
self.bmetadata = {}
self.isEncrypted = False
self.decrypted = False
self.signer = None
self.validSig = False
# handle arguments
if self.getCore() is None:
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
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())
else:
logger.debug('Did not update block')
pass
#logger.debug('Did not update block.')
# 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):
'''
Loads data from a block in to the current object.
@ -118,14 +162,19 @@ 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:]
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.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') != '')
# TODO: detect if signer is hash of pubkey or not
self.signer = self.getHeader('signer', 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())
if not self.getDate() is None:
@ -214,7 +263,6 @@ class Block:
Outputs:
- (str): the type of the block
'''
return self.btype
def getRaw(self):
@ -471,6 +519,8 @@ class Block:
if not signer is None:
if isinstance(signer, (str,)):
signer = [signer]
if isinstance(signer, (bytes,)):
signer = [signer.decode()]
isSigner = False
for key in signer:
@ -483,12 +533,13 @@ class Block:
if relevant:
relevant_blocks.append(block)
if bool(reverse):
relevant_blocks.reverse()
return relevant_blocks
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()

View File

@ -114,6 +114,11 @@ class OnionrCrypto:
'''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)'''
retVal = ''
try:
pubkey = pubkey.encode()
except AttributeError:
pass
if encodedData:
encoding = nacl.encoding.Base64Encoder
else:
@ -127,7 +132,11 @@ class OnionrCrypto:
elif anonymous:
key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key()
anonBox = nacl.public.SealedBox(key)
retVal = anonBox.encrypt(data.encode(), encoder=encoding)
try:
data = data.encode()
except AttributeError:
pass
retVal = anonBox.encrypt(data, encoder=encoding)
return retVal
def pubKeyDecrypt(self, data, pubkey='', anonymous=False, encodedData=False):

View File

@ -159,7 +159,11 @@ class POW:
self.metadata['powRandomToken'] = base64.b64encode(rand).decode()
payload = json.dumps(self.metadata).encode() + b'\n' + self.data
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]:
self.hashing = False
iFound = True

View File

@ -59,7 +59,7 @@ class OnionrUtils:
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
@ -117,7 +117,8 @@ class OnionrUtils:
else:
logger.warn("Failed to add key")
else:
logger.warn('%s pow failed' % key[0])
pass
#logger.debug('%s pow failed' % key[0])
return retVal
except Exception as error:
logger.error('Failed to merge keys.', error=error)
@ -137,7 +138,8 @@ class OnionrUtils:
logger.info('Added %s to db.' % adder, timestamp = True)
retVal = True
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
except Exception as error:
logger.error('Failed to merge adders.', error = error)
@ -204,18 +206,23 @@ class OnionrUtils:
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:
blockData = blockData.encode()
except AttributeError:
pass
metadata = json.loads(blockData[:blockData.find(b'\n')].decode())
data = blockData[blockData.find(b'\n'):].decode()
try:
meta = json.loads(metadata['meta'])
except KeyError:
meta = {}
if not metadata['encryptType'] in ('asym', 'sym'):
try:
meta = json.loads(metadata['meta'])
except KeyError:
pass
meta = metadata['meta']
return (metadata, meta, data)
def checkPort(self, port, host=''):
@ -251,7 +258,14 @@ class OnionrUtils:
Read metadata from a block and cache it to the block database
'''
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):
'''
@ -426,52 +440,6 @@ class OnionrUtils:
except:
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):
'''
Return the pubkey of the user if known from the hash

View File

@ -21,4 +21,4 @@
class OnionrValues:
def __init__(self):
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

View File

@ -47,25 +47,24 @@ class OnionrFlow:
self.flowRunning = False
if len(message) > 0:
self.myCore.insertBlock(message)
Block(content = message, type = 'txt', core = self.myCore).save()
logger.info("Flow is exiting, goodbye")
return
def showOutput(self):
while self.flowRunning:
for blockHash in self.myCore.getBlocksByType('txt'):
if blockHash in self.alreadyOutputed:
for block in Block.getBlocks(type = 'txt', core = self.myCore):
if block.getHash() in self.alreadyOutputed:
continue
if not self.flowRunning:
break
logger.info('\n------------------------')
block = Block(blockHash, self.myCore)
logger.info('\n------------------------', prompt = False)
content = block.getContent()
# Escape new lines, remove trailing whitespace, and escape ansi sequences
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)
self.alreadyOutputed.append(blockHash)
logger.info(block.getDate().strftime("%m/%d %H:%M") + ' - ' + logger.colors.reset + content, prompt = False)
self.alreadyOutputed.append(block.getHash())
try:
time.sleep(5)
except KeyboardInterrupt:
@ -84,6 +83,6 @@ def on_init(api, data = None):
global pluginapi
pluginapi = api
flow = OnionrFlow()
api.commands.register(['flow'], flow.start)
api.commands.register('flow', flow.start)
api.commands.register_help('flow', 'Open the flow messaging interface')
return

View File

@ -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

View File

@ -1,5 +1,5 @@
{
"name" : "gui",
"name" : "pms",
"version" : "1.0",
"author" : "onionr"
}

View File

@ -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

View File

@ -1,6 +1,5 @@
![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/)
@ -10,10 +9,11 @@ Major work in progress.
***THIS SOFTWARE IS NOT USABLE OR SECURE YET.***
**The main repo for this software is at https://gitlab.com/beardog/Onionr/**
**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] High level of anonymity