2018-07-16 07:40:58 +00:00
|
|
|
'''
|
2018-11-04 16:06:24 +00:00
|
|
|
Onionr - P2P Anonymous Storage Network
|
2018-07-16 07:40:58 +00:00
|
|
|
|
|
|
|
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
|
2018-07-18 07:33:23 +00:00
|
|
|
import logger, config, threading, time, readline, datetime
|
2018-07-16 07:40:58 +00:00
|
|
|
from onionrblockapi import Block
|
2019-02-17 20:21:03 +00:00
|
|
|
import onionrexceptions
|
|
|
|
from onionrusers import onionrusers
|
2019-02-08 18:53:28 +00:00
|
|
|
import locale, sys, os, json
|
2018-11-03 03:24:14 +00:00
|
|
|
|
2018-08-17 21:50:16 +00:00
|
|
|
locale.setlocale(locale.LC_ALL, '')
|
2018-07-16 07:40:58 +00:00
|
|
|
|
2019-03-02 06:22:59 +00:00
|
|
|
plugin_name = 'pms'
|
|
|
|
PLUGIN_VERSION = '0.0.1'
|
|
|
|
|
2018-11-03 03:24:14 +00:00
|
|
|
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
2019-03-02 19:17:18 +00:00
|
|
|
import sentboxdb, mailapi, loadinbox # import after path insert
|
|
|
|
flask_blueprint = mailapi.flask_blueprint
|
2018-07-16 07:40:58 +00:00
|
|
|
|
2018-07-19 07:08:51 +00:00
|
|
|
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)
|
|
|
|
|
2018-07-16 07:40:58 +00:00
|
|
|
class MailStrings:
|
|
|
|
def __init__(self, mailInstance):
|
|
|
|
self.mailInstance = mailInstance
|
|
|
|
|
|
|
|
self.programTag = 'OnionrMail v%s' % (PLUGIN_VERSION)
|
2019-02-05 18:47:11 +00:00
|
|
|
choices = ['view inbox', 'view sentbox', 'send message', 'toggle pseudonymity', 'quit']
|
2018-07-16 07:40:58 +00:00
|
|
|
self.mainMenuChoices = choices
|
2019-02-05 18:47:11 +00:00
|
|
|
self.mainMenu = '''-----------------
|
|
|
|
1. %s
|
|
|
|
2. %s
|
|
|
|
3. %s
|
|
|
|
4. %s
|
|
|
|
5. %s''' % (choices[0], choices[1], choices[2], choices[3], choices[4])
|
2018-07-16 07:40:58 +00:00
|
|
|
|
|
|
|
class OnionrMail:
|
|
|
|
def __init__(self, pluginapi):
|
|
|
|
self.myCore = pluginapi.get_core()
|
|
|
|
self.strings = MailStrings(self)
|
|
|
|
|
2018-11-03 03:24:14 +00:00
|
|
|
self.sentboxTools = sentboxdb.SentBox(self.myCore)
|
|
|
|
self.sentboxList = []
|
|
|
|
self.sentMessages = {}
|
2019-02-05 18:47:11 +00:00
|
|
|
self.doSigs = True
|
2018-07-16 07:40:58 +00:00
|
|
|
return
|
2018-11-11 03:25:40 +00:00
|
|
|
|
2018-07-16 07:40:58 +00:00
|
|
|
def inbox(self):
|
2018-07-17 07:18:17 +00:00
|
|
|
blockCount = 0
|
|
|
|
pmBlockMap = {}
|
2018-07-18 07:33:23 +00:00
|
|
|
pmBlocks = {}
|
|
|
|
logger.info('Decrypting messages...')
|
|
|
|
choice = ''
|
2018-10-02 16:45:56 +00:00
|
|
|
displayList = []
|
2019-02-01 19:55:59 +00:00
|
|
|
subject = ''
|
2018-07-17 07:18:17 +00:00
|
|
|
|
2019-03-02 19:17:18 +00:00
|
|
|
# this could use a lot of memory if someone has received a lot of messages
|
2018-07-16 07:40:58 +00:00
|
|
|
for blockHash in self.myCore.getBlocksByType('pm'):
|
2018-07-18 07:33:23 +00:00
|
|
|
pmBlocks[blockHash] = Block(blockHash, core=self.myCore)
|
|
|
|
pmBlocks[blockHash].decrypt()
|
|
|
|
blockCount = 0
|
2018-12-09 17:29:39 +00:00
|
|
|
for blockHash in pmBlocks:
|
|
|
|
if not pmBlocks[blockHash].decrypted:
|
|
|
|
continue
|
|
|
|
blockCount += 1
|
|
|
|
pmBlockMap[blockCount] = blockHash
|
|
|
|
|
|
|
|
block = pmBlocks[blockHash]
|
|
|
|
senderKey = block.signer
|
|
|
|
try:
|
|
|
|
senderKey = senderKey.decode()
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
senderDisplay = onionrusers.OnionrUser(self.myCore, senderKey).getName()
|
|
|
|
if senderDisplay == 'anonymous':
|
|
|
|
senderDisplay = senderKey
|
2018-08-29 01:09:27 +00:00
|
|
|
|
2018-12-09 17:29:39 +00:00
|
|
|
blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M")
|
2019-02-01 19:55:59 +00:00
|
|
|
try:
|
|
|
|
subject = pmBlocks[blockHash].bmetadata['subject']
|
|
|
|
except KeyError:
|
|
|
|
subject = ''
|
|
|
|
|
|
|
|
displayList.append('%s. %s - %s - <%s>: %s' % (blockCount, blockDate, senderDisplay[:12], subject[:10], blockHash))
|
2018-12-09 17:29:39 +00:00
|
|
|
while choice not in ('-q', 'q', 'quit'):
|
2018-10-02 16:45:56 +00:00
|
|
|
for i in displayList:
|
2018-11-11 03:25:40 +00:00
|
|
|
logger.info(i)
|
2018-07-18 07:33:23 +00:00
|
|
|
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:
|
2018-08-18 19:38:15 +00:00
|
|
|
cancel = ''
|
2018-07-18 07:33:23 +00:00
|
|
|
readBlock.verifySig()
|
2019-02-05 18:47:11 +00:00
|
|
|
senderDisplay = self.myCore._utils.bytesToStr(readBlock.signer)
|
|
|
|
if len(senderDisplay.strip()) == 0:
|
|
|
|
senderDisplay = 'Anonymous'
|
|
|
|
logger.info('Message received from %s' % (senderDisplay,))
|
2018-11-11 03:25:40 +00:00
|
|
|
logger.info('Valid signature: %s' % readBlock.validSig)
|
2018-11-17 07:23:10 +00:00
|
|
|
|
2018-07-19 07:08:51 +00:00
|
|
|
if not readBlock.validSig:
|
2019-02-05 18:47:11 +00:00
|
|
|
logger.warn('This message has an INVALID/NO signature. ANYONE could have sent this message.')
|
2018-08-18 04:42:30 +00:00
|
|
|
cancel = logger.readline('Press enter to continue to message, or -q to not open the message (recommended).')
|
2019-02-17 20:44:51 +00:00
|
|
|
print('')
|
2018-08-18 04:42:30 +00:00
|
|
|
if cancel != '-q':
|
2019-02-02 23:10:04 +00:00
|
|
|
try:
|
|
|
|
print(draw_border(self.myCore._utils.escapeAnsi(readBlock.bcontent.decode().strip())))
|
|
|
|
except ValueError:
|
|
|
|
logger.warn('Error presenting message. This is usually due to a malformed or blank message.')
|
|
|
|
pass
|
2019-02-17 20:44:51 +00:00
|
|
|
if readBlock.validSig:
|
|
|
|
reply = logger.readline("Press enter to continue, or enter %s to reply" % ("-r",))
|
|
|
|
print('')
|
|
|
|
if reply == "-r":
|
|
|
|
self.draft_message(self.myCore._utils.bytesToStr(readBlock.signer,))
|
|
|
|
else:
|
|
|
|
logger.readline("Press enter to continue")
|
|
|
|
print('')
|
2018-07-16 07:40:58 +00:00
|
|
|
return
|
2018-11-11 03:25:40 +00:00
|
|
|
|
2018-11-01 04:56:59 +00:00
|
|
|
def sentbox(self):
|
|
|
|
'''
|
|
|
|
Display sent mail messages
|
|
|
|
'''
|
|
|
|
entering = True
|
|
|
|
while entering:
|
2019-02-08 18:53:28 +00:00
|
|
|
self.get_sent_list()
|
2018-12-09 17:29:39 +00:00
|
|
|
logger.info('Enter a block number or -q to return')
|
2018-11-01 04:56:59 +00:00
|
|
|
try:
|
2018-11-03 03:24:14 +00:00
|
|
|
choice = input('>')
|
|
|
|
except (EOFError, KeyboardInterrupt) as e:
|
2018-11-01 04:56:59 +00:00
|
|
|
entering = False
|
|
|
|
else:
|
2018-12-09 17:29:39 +00:00
|
|
|
try:
|
|
|
|
choice = int(choice) - 1
|
|
|
|
except ValueError:
|
|
|
|
pass
|
2018-11-01 04:56:59 +00:00
|
|
|
else:
|
2018-11-03 03:24:14 +00:00
|
|
|
try:
|
2018-12-09 17:29:39 +00:00
|
|
|
self.sentboxList[int(choice)]
|
|
|
|
except (IndexError, ValueError) as e:
|
2018-11-11 03:25:40 +00:00
|
|
|
logger.warn('Invalid block.')
|
2018-11-03 03:24:14 +00:00
|
|
|
else:
|
2018-12-09 17:29:39 +00:00
|
|
|
logger.info('Sent to: ' + self.sentMessages[self.sentboxList[int(choice)]][1])
|
2018-11-03 03:24:14 +00:00
|
|
|
# Print ansi escaped sent message
|
2018-12-09 17:29:39 +00:00
|
|
|
logger.info(self.myCore._utils.escapeAnsi(self.sentMessages[self.sentboxList[int(choice)]][0]))
|
2018-11-03 03:24:14 +00:00
|
|
|
input('Press enter to continue...')
|
2018-12-09 17:29:39 +00:00
|
|
|
finally:
|
|
|
|
if choice == '-q':
|
|
|
|
entering = False
|
2018-11-01 04:56:59 +00:00
|
|
|
return
|
2018-11-11 03:25:40 +00:00
|
|
|
|
2019-02-08 18:53:28 +00:00
|
|
|
def get_sent_list(self, display=True):
|
2018-11-03 03:24:14 +00:00
|
|
|
count = 1
|
2018-12-09 17:29:39 +00:00
|
|
|
self.sentboxList = []
|
|
|
|
self.sentMessages = {}
|
2018-11-03 03:24:14 +00:00
|
|
|
for i in self.sentboxTools.listSent():
|
|
|
|
self.sentboxList.append(i['hash'])
|
2019-02-11 22:36:43 +00:00
|
|
|
self.sentMessages[i['hash']] = (self.myCore._utils.bytesToStr(i['message']), i['peer'], i['subject'])
|
2019-02-08 18:53:28 +00:00
|
|
|
if display:
|
2019-02-11 22:36:43 +00:00
|
|
|
logger.info('%s. %s - %s - (%s) - %s' % (count, i['hash'], i['peer'][:12], i['subject'], i['date']))
|
2018-11-03 03:24:14 +00:00
|
|
|
count += 1
|
2019-02-08 18:53:28 +00:00
|
|
|
return json.dumps(self.sentMessages)
|
2018-11-01 04:56:59 +00:00
|
|
|
|
2019-02-08 18:53:28 +00:00
|
|
|
def draft_message(self, recip=''):
|
2018-07-16 07:40:58 +00:00
|
|
|
message = ''
|
|
|
|
newLine = ''
|
2019-02-01 19:55:59 +00:00
|
|
|
subject = ''
|
2018-12-09 17:29:39 +00:00
|
|
|
entering = False
|
|
|
|
if len(recip) == 0:
|
|
|
|
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
|
2018-07-16 07:40:58 +00:00
|
|
|
else:
|
2018-12-09 17:29:39 +00:00
|
|
|
# if -q or ctrl-c/d, exit function here, otherwise we successfully got the public key
|
|
|
|
return
|
2019-02-01 19:55:59 +00:00
|
|
|
try:
|
|
|
|
subject = logger.readline('Message subject: ')
|
|
|
|
except (KeyboardInterrupt, EOFError):
|
|
|
|
pass
|
2019-01-22 03:29:29 +00:00
|
|
|
|
|
|
|
cancelEnter = False
|
|
|
|
logger.info('Enter your message, stop by entering -q on a new line. -c to cancel')
|
2018-07-16 07:40:58 +00:00
|
|
|
while newLine != '-q':
|
|
|
|
try:
|
|
|
|
newLine = input()
|
|
|
|
except (KeyboardInterrupt, EOFError):
|
2019-01-22 03:29:29 +00:00
|
|
|
cancelEnter = True
|
|
|
|
if newLine == '-c':
|
|
|
|
cancelEnter = True
|
|
|
|
break
|
2018-07-16 07:40:58 +00:00
|
|
|
if newLine == '-q':
|
|
|
|
continue
|
|
|
|
newLine += '\n'
|
|
|
|
message += newLine
|
2018-07-17 07:18:17 +00:00
|
|
|
|
2019-01-22 03:29:29 +00:00
|
|
|
if not cancelEnter:
|
|
|
|
logger.info('Inserting encrypted message as Onionr block....')
|
2018-07-16 07:40:58 +00:00
|
|
|
|
2019-02-05 18:47:11 +00:00
|
|
|
blockID = self.myCore.insertBlock(message, header='pm', encryptType='asym', asymPeer=recip, sign=self.doSigs, meta={'subject': subject})
|
|
|
|
|
2019-02-08 18:53:28 +00:00
|
|
|
def toggle_signing(self):
|
2019-02-05 18:47:11 +00:00
|
|
|
self.doSigs = not self.doSigs
|
|
|
|
|
2018-07-16 07:40:58 +00:00
|
|
|
def menu(self):
|
|
|
|
choice = ''
|
|
|
|
while True:
|
2019-02-05 18:47:11 +00:00
|
|
|
sigMsg = 'Message Signing: %s'
|
2018-07-16 07:40:58 +00:00
|
|
|
|
2019-02-05 18:47:11 +00:00
|
|
|
logger.info(self.strings.programTag + '\n\nUser ID: ' + self.myCore._crypto.pubKey)
|
|
|
|
if self.doSigs:
|
|
|
|
sigMsg = sigMsg % ('enabled',)
|
|
|
|
else:
|
|
|
|
sigMsg = sigMsg % ('disabled (Your messages cannot be trusted)',)
|
|
|
|
if self.doSigs:
|
|
|
|
logger.info(sigMsg)
|
|
|
|
else:
|
|
|
|
logger.warn(sigMsg)
|
|
|
|
logger.info(self.strings.mainMenu.title()) # print out main menu
|
2018-07-16 07:40:58 +00:00
|
|
|
try:
|
2018-07-17 07:18:17 +00:00
|
|
|
choice = logger.readline('Enter 1-%s:\n' % (len(self.strings.mainMenuChoices))).lower().strip()
|
2018-07-16 07:40:58 +00:00
|
|
|
except (KeyboardInterrupt, EOFError):
|
|
|
|
choice = '5'
|
|
|
|
|
|
|
|
if choice in (self.strings.mainMenuChoices[0], '1'):
|
|
|
|
self.inbox()
|
2018-07-19 07:08:51 +00:00
|
|
|
elif choice in (self.strings.mainMenuChoices[1], '2'):
|
2018-11-01 04:56:59 +00:00
|
|
|
self.sentbox()
|
2018-07-16 07:40:58 +00:00
|
|
|
elif choice in (self.strings.mainMenuChoices[2], '3'):
|
2019-02-08 18:53:28 +00:00
|
|
|
self.draft_message()
|
2018-07-19 07:08:51 +00:00
|
|
|
elif choice in (self.strings.mainMenuChoices[3], '4'):
|
2019-02-08 18:53:28 +00:00
|
|
|
self.toggle_signing()
|
2019-02-05 18:47:11 +00:00
|
|
|
elif choice in (self.strings.mainMenuChoices[4], '5'):
|
2018-07-16 07:40:58 +00:00
|
|
|
logger.info('Goodbye.')
|
|
|
|
break
|
2018-07-17 07:18:17 +00:00
|
|
|
elif choice == '':
|
|
|
|
pass
|
2018-07-16 07:40:58 +00:00
|
|
|
else:
|
|
|
|
logger.warn('Invalid choice.')
|
|
|
|
return
|
|
|
|
|
2019-03-02 06:22:59 +00:00
|
|
|
def add_deleted(keyStore, bHash):
|
|
|
|
existing = keyStore.get('deleted_mail')
|
|
|
|
if existing is None:
|
|
|
|
existing = []
|
|
|
|
else:
|
|
|
|
if bHash in existing:
|
|
|
|
return
|
|
|
|
keyStore.put('deleted_mail', existing.append(bHash))
|
|
|
|
|
2019-02-10 18:43:45 +00:00
|
|
|
def on_insertblock(api, data={}):
|
|
|
|
sentboxTools = sentboxdb.SentBox(api.get_core())
|
2019-02-11 22:36:43 +00:00
|
|
|
meta = json.loads(data['meta'])
|
|
|
|
sentboxTools.addToSent(data['hash'], data['peer'], data['content'], meta['subject'])
|
2019-02-10 18:43:45 +00:00
|
|
|
|
2018-07-16 07:40:58 +00:00
|
|
|
|
|
|
|
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')
|
2018-11-11 03:25:40 +00:00
|
|
|
return
|