Improve support for plugins

This commit is contained in:
Arinerron 2018-04-20 20:10:50 -07:00
parent 8d8f167d7f
commit 098abb8e55
No known key found for this signature in database
GPG Key ID: 99383627861C62F0
8 changed files with 241 additions and 135 deletions

View File

@ -22,7 +22,7 @@ import sqlite3, os, sys, time, math, base64, tarfile, getpass, simplecrypt, hash
#from Crypto import Random #from Crypto import Random
import netcontroller import netcontroller
import onionrutils, onionrcrypto, btc import onionrutils, onionrcrypto, btc, onionrevents as events
if sys.version_info < (3, 6): if sys.version_info < (3, 6):
try: try:
@ -89,10 +89,13 @@ class Core:
c.execute('INSERT INTO peers (id, name, dateSeen) VALUES(?, ?, ?);', t) c.execute('INSERT INTO peers (id, name, dateSeen) VALUES(?, ?, ?);', t)
conn.commit() conn.commit()
conn.close() conn.close()
return True return True
def addAddress(self, address): def addAddress(self, address):
'''Add an address to the address database (only tor currently)''' '''
Add an address to the address database (only tor currently)
'''
if self._utils.validateID(address): if self._utils.validateID(address):
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB)
c = conn.cursor() c = conn.cursor()
@ -114,12 +117,17 @@ class Core:
c.execute('INSERT INTO adders (address, type) VALUES(?, ?);', t) c.execute('INSERT INTO adders (address, type) VALUES(?, ?);', t)
conn.commit() conn.commit()
conn.close() conn.close()
events.event('address_add', data = {'address': address}, onionr = None)
return True return True
else: else:
return False return False
def removeAddress(self, address): def removeAddress(self, address):
'''Remove an address from the address database''' '''
Remove an address from the address database
'''
if self._utils.validateID(address): if self._utils.validateID(address):
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB)
c = conn.cursor() c = conn.cursor()
@ -127,6 +135,9 @@ class Core:
c.execute('Delete from adders where address=?;', t) c.execute('Delete from adders where address=?;', t)
conn.commit() conn.commit()
conn.close() conn.close()
events.event('address_remove', data = {'address': address}, onionr = None)
return True return True
else: else:
return False return False
@ -330,6 +341,8 @@ class Core:
conn.commit() conn.commit()
conn.close() conn.close()
events.event('queue_pop', data = {'data': retData}, onionr = None)
return retData return retData
def daemonQueueAdd(self, command, data=''): def daemonQueueAdd(self, command, data=''):
@ -345,6 +358,8 @@ class Core:
conn.commit() conn.commit()
conn.close() conn.close()
events.event('queue_push', data = {'command': command, 'data': data}, onionr = None)
return return
def clearDaemonQueue(self): def clearDaemonQueue(self):
@ -354,11 +369,12 @@ class Core:
conn = sqlite3.connect(self.queueDB) conn = sqlite3.connect(self.queueDB)
c = conn.cursor() c = conn.cursor()
try: try:
c.execute('delete from commands;') c.execute('DELETE FROM commands;')
conn.commit() conn.commit()
except: except:
pass pass
conn.close() conn.close()
events.event('queue_clear', onionr = None)
return return
@ -564,4 +580,5 @@ class Core:
announceAmount = len(nodeList) announceAmount = len(nodeList)
for i in range(announceAmount): for i in range(announceAmount):
self.daemonQueueAdd('announceNode', nodeList[i]) self.daemonQueueAdd('announceNode', nodeList[i])
events.event('introduction', onionr = None)
return return

41
onionr/default_plugin.txt Normal file
View File

@ -0,0 +1,41 @@
'''
Default plugin template file
Generated on $date by $user.
'''
# Imports some useful libraries
import logger, config
def on_init(api, data = None):
'''
This event is called after Onionr is initialized, but before the command
inputted is executed. Could be called when daemon is starting or when
just the client is running.
'''
# Doing this makes it so that the other functions can access the api object
# by simply referencing the variable `pluginapi`.
global pluginapi
pluginapi = api
return
def on_start(api, data = None):
'''
This event can be called for multiple reasons:
1) The daemon is starting
2) The user called `onionr --start-plugins` or `onionr --reload-plugins`
3) For whatever reason, the plugins are reloading
'''
return
def on_stop(api, data = None):
'''
This event can be called for multiple reasons:
1) The daemon is stopping
2) The user called `onionr --stop-plugins` or `onionr --reload-plugins`
3) For whatever reason, the plugins are reloading
'''
return

View File

@ -20,8 +20,8 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
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 sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform import sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re
import api, core, config, logger, onionrplugins as plugins import api, core, config, logger, onionrplugins as plugins, onionrevents as events
from onionrutils import OnionrUtils from onionrutils import OnionrUtils
from netcontroller import NetController from netcontroller import NetController
@ -137,6 +137,9 @@ class Onionr:
'reloadplugin': self.reloadPlugin, 'reloadplugin': self.reloadPlugin,
'reload-plugins': self.reloadPlugin, 'reload-plugins': self.reloadPlugin,
'reloadplugins': self.reloadPlugin, 'reloadplugins': self.reloadPlugin,
'create-plugin': self.createPlugin,
'createplugin': self.createPlugin,
'plugin-create': self.createPlugin,
'listkeys': self.listKeys, 'listkeys': self.listKeys,
'list-keys': self.listKeys, 'list-keys': self.listKeys,
@ -159,6 +162,7 @@ class Onionr:
'addaddr': self.addAddress, 'addaddr': self.addAddress,
'addaddress': self.addAddress, 'addaddress': self.addAddress,
'introduce': self.onionrCore.introduceNode,
'connect': self.addAddress 'connect': self.addAddress
} }
@ -172,14 +176,19 @@ class Onionr:
'enable-plugin': 'Enables and starts a plugin', 'enable-plugin': 'Enables and starts a plugin',
'disable-plugin': 'Disables and stops a plugin', 'disable-plugin': 'Disables and stops a plugin',
'reload-plugin': 'Reloads a plugin', 'reload-plugin': 'Reloads a plugin',
'create-plugin': 'Creates directory structure for a plugin',
'add-peer': 'Adds a peer (?)', 'add-peer': 'Adds a peer (?)',
'list-peers': 'Displays a list of peers', 'list-peers': 'Displays a list of peers',
'add-msg': 'Broadcasts a message to the Onionr network', 'add-msg': 'Broadcasts a message to the Onionr network',
'pm': 'Adds a private message to block', 'pm': 'Adds a private message to block',
'get-pms': 'Shows private messages sent to you', 'get-pms': 'Shows private messages sent to you',
'gui': 'Opens a graphical interface for Onionr' 'gui': 'Opens a graphical interface for Onionr',
'introduce': 'Introduce your node to the public Onionr network (DAEMON MUST BE RUNNING)',
} }
# initialize plugins
events.event('init', onionr = self)
command = '' command = ''
try: try:
command = sys.argv[1].lower() command = sys.argv[1].lower()
@ -238,6 +247,7 @@ class Onionr:
''' '''
Executes a command Executes a command
''' '''
argument = argument[argument.startswith('--') and len('--'):] # remove -- if it starts with it argument = argument[argument.startswith('--') and len('--'):] # remove -- if it starts with it
# define commands # define commands
@ -256,6 +266,7 @@ class Onionr:
''' '''
Displays the Onionr version Displays the Onionr version
''' '''
logger.info('Onionr ' + ONIONR_VERSION + ' (' + platform.machine() + ') - API v' + API_VERSION) logger.info('Onionr ' + ONIONR_VERSION + ' (' + platform.machine() + ') - API v' + API_VERSION)
if verbosity >= 1: if verbosity >= 1:
logger.info(ONIONR_TAGLINE) logger.info(ONIONR_TAGLINE)
@ -268,6 +279,7 @@ class Onionr:
''' '''
Create a private message and send it Create a private message and send it
''' '''
invalidID = True invalidID = True
while invalidID: while invalidID:
try: try:
@ -404,6 +416,34 @@ class Onionr:
return return
def createPlugin(self):
'''
Creates the directory structure for a plugin name
'''
if len(sys.argv) >= 3:
try:
plugin_name = re.sub('[^0-9a-zA-Z]+', '', str(sys.argv[2]).lower())
if not plugins.exists(plugin_name):
logger.info('Creating plugin \"' + plugin_name + '\"...')
os.makedirs(plugins.get_plugins_folder(plugin_name))
with open(plugins.get_plugins_folder(plugin_name) + '/main.py', 'a') as main:
main.write(open('default_plugin.txt').read().replace('$user', os.getlogin()).replace('$date', datetime.datetime.now().strftime('%Y-%m-%d')))
logger.info('Enabling plugin \"' + plugin_name + '\"...')
plugins.enable(plugin_name, self)
else:
logger.warn('Cannot create plugin directory structure; plugin "' + plugin_name + '" exists.')
except Exception as e:
logger.error('Failed to create plugin directory structure.', e)
else:
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
return
def notFound(self): def notFound(self):
''' '''
Displays a "command not found" message Displays a "command not found" message
@ -451,6 +491,7 @@ class Onionr:
time.sleep(1) time.sleep(1)
subprocess.Popen(["./communicator.py", "run", str(net.socksPort)]) subprocess.Popen(["./communicator.py", "run", str(net.socksPort)])
logger.debug('Started communicator') logger.debug('Started communicator')
events.event('daemon_start', onionr = self)
api.API(self.debug) api.API(self.debug)
return return
@ -461,6 +502,7 @@ class Onionr:
''' '''
logger.warn('Killing the running daemon') logger.warn('Killing the running daemon')
events.event('daemon_stop', onionr = self)
net = NetController(config.get('client')['port']) net = NetController(config.get('client')['port'])
try: try:
self.onionrUtils.localCommand('shutdown') self.onionrUtils.localCommand('shutdown')

View File

@ -20,19 +20,20 @@
import config, logger, onionrplugins as plugins, onionrpluginapi as pluginapi import config, logger, onionrplugins as plugins, onionrpluginapi as pluginapi
def get_pluginapi(onionr): def get_pluginapi(onionr, data):
return pluginapi.PluginAPI(onionr) return pluginapi.pluginapi(onionr, data)
def event(event_name, data = None, onionr = None): def event(event_name, data = {}, onionr = None):
''' '''
Calls an event on all plugins (if defined) Calls an event on all plugins (if defined)
''' '''
for plugin in plugins.get_enabled_plugins(): for plugin in plugins.get_enabled_plugins():
try: try:
call(plugins.get_plugin(plugin), event_name, data, self.get_pluginapi(onionr)) call(plugins.get_plugin(plugin), event_name, data, get_pluginapi(onionr, data))
except: except Exception as e:
logger.warn('Event \"' + event_name + '\" failed for plugin \"' + plugin + '\".') logger.warn('Event \"' + event_name + '\" failed for plugin \"' + plugin + '\".')
logger.debug(str(e))
def call(plugin, event_name, data = None, pluginapi = None): def call(plugin, event_name, data = None, pluginapi = None):
''' '''
@ -45,12 +46,12 @@ def call(plugin, event_name, data = None, pluginapi = None):
# TODO: Use multithreading perhaps? # TODO: Use multithreading perhaps?
if hasattr(plugin, attribute): if hasattr(plugin, attribute):
logger.debug('Calling event ' + str(event_name)) #logger.debug('Calling event ' + str(event_name))
getattr(plugin, attribute)(pluginapi, data) getattr(plugin, attribute)(pluginapi)
return True return True
except: except Exception as e:
logger.warn('Failed to call event ' + str(event_name) + ' on module.') logger.debug(str(e))
return False return False
else: else:
return True return True

View File

@ -20,131 +20,135 @@
import onionrplugins as plugins, logger import onionrplugins as plugins, logger
class DaemonAPI:
def __init__(self, pluginapi):
self.pluginapi = pluginapi
def start(self):
self.pluginapi.get_onionr().daemon()
return
def stop(self):
self.pluginapi.get_onionr().killDaemon()
return
def queue(self, command, data = ''):
self.pluginapi.get_core().daemonQueueAdd(command, data)
return
def local_command(self, command):
self.pluginapi.get_utils().localCommand(self, command)
return
def queue_pop(self):
return self.get_core().daemonQueue()
class PluginAPI: class PluginAPI:
def __init__(self, onionr): def __init__(self, pluginapi):
self.pluginapi = pluginapi
def start(self, name):
plugins.start(name)
def stop(self, name):
plugins.stop(name)
def reload(self, name):
plugins.reload(name)
def enable(self, name):
plugins.enable(name)
def disable(self, name):
plugins.disable(name)
def is_enabled(self, name):
return plugins.is_enabled(name)
def get_enabled_plugins(self):
return plugins.get_enabled_plugins()
class CommandAPI:
def __init__(self, pluginapi):
self.pluginapi = pluginapi
def register(self, names, call = None):
if isinstance(names, str):
names = [names]
for name in names:
self.pluginapi.get_onionr().addCommand(name, call)
return
def unregister(self, names):
if isinstance(names, str):
names = [names]
for name in names:
self.pluginapi.get_onionr().delCommand(name)
return
def register_help(self, names, description):
if isinstance(names, str):
names = [names]
for name in names:
self.pluginapi.get_onionr().addHelp(name, description)
return
def unregister_help(self, names):
if isinstance(names, str):
names = [names]
for name in names:
self.pluginapi.get_onionr().delHelp(name)
return
def call(self, name):
self.pluginapi.get_onionr().execute(name)
return
def get_commands(self):
return self.pluginapi.get_onionr().getCommands()
class pluginapi:
def __init__(self, onionr, data):
self.onionr = onionr self.onionr = onionr
self.data = data
self.daemon = DaemonAPI(self) self.daemon = DaemonAPI(self)
self.plugin = PluginAPI(self) self.plugins = PluginAPI(self)
self.command = CommandAPI(self) self.commands = CommandAPI(self)
def get_onionr(self): def get_onionr(self):
return self.onionr return self.onionr
def get_data(self):
return self.data
def get_core(self): def get_core(self):
return self.get_onionr().onionrCore return self.get_onionr().onionrCore
def get_utils(self): def get_utils(self):
return self.get_onionr().onionrUtils return self.get_onionr().onionrUtils
def get_daemonapi(self): def get_daemonapi(self):
return self.daemon return self.daemon
def get_pluginapi(self): def get_pluginapi(self):
return self.plugin return self.plugins
def get_commandapi(self): def get_commandapi(self):
return self.command return self.commands
def is_development_mode(self): def is_development_mode(self):
return self.get_onionr()._developmentMode return self.get_onionr()._developmentMode
class DaemonAPI:
def __init__(self, pluginapi):
self.pluginapi = pluginapi
def start(self):
self.pluginapi.get_onionr().daemon()
return
def stop(self):
self.pluginapi.get_onionr().killDaemon()
return
def queue(self, command, data = ''):
self.pluginapi.get_core().daemonQueueAdd(command, data)
return
def local_command(self, command):
self.pluginapi.get_utils().localCommand(self, command)
return
def queue_pop(self):
return self.get_core().daemonQueue()
class PluginAPI:
def __init__(self, pluginapi):
self.pluginapi = pluginapi
def start(self, name):
plugins.start(name)
def stop(self, name):
plugins.stop(name)
def reload(self, name):
plugins.reload(name)
def enable(self, name):
plugins.enable(name)
def disable(self, name):
plugins.disable(name)
def is_enabled(self, name):
return plugins.is_enabled(name)
def get_enabled_plugins(self):
return plugins.get_enabled_plugins()
class CommandAPI:
def __init__(self, pluginapi):
self.pluginapi = pluginapi
def register(self, names, call = None):
if isinstance(names, str):
names = [names]
for name in names:
self.pluginapi.get_onionr().addCommand(name, call)
return
def unregister(self, names):
if isinstance(names, str):
names = [names]
for name in names:
self.pluginapi.get_onionr().delCommand(name)
return
def register_help(self, names, description):
if isinstance(names, str):
names = [names]
for name in names:
self.pluginapi.get_onionr().addHelp(name, description)
return
def unregister_help(self, names):
if isinstance(names, str):
names = [names]
for name in names:
self.pluginapi.get_onionr().delHelp(name)
return
def call(self, name):
self.pluginapi.get_onionr().execute(name)
return
def get_commands(self):
return self.pluginapi.get_onionr().getCommands()

View File

@ -225,5 +225,5 @@ def check():
if not os.path.exists(os.path.dirname(get_plugins_folder())): if not os.path.exists(os.path.dirname(get_plugins_folder())):
logger.debug('Generating plugin data folder...') logger.debug('Generating plugin data folder...')
os.makedirs(os.path.dirname(get_plugins_folder())) os.makedirs(os.path.dirname(get_plugins_folder()))
return return

View File

@ -129,7 +129,7 @@ class OnionrUtils:
logger.error('Failed to read my address.', error=error) logger.error('Failed to read my address.', error=error)
return '' return ''
def localCommand(self, command): def localCommand(self, command, silent = True):
''' '''
Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers. Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers.
''' '''
@ -140,7 +140,8 @@ class OnionrUtils:
try: try:
retData = requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('client')['port']) + '/client/?action=' + command + '&token=' + str(config.get('client')['client_hmac']) + '&timingToken=' + self.timingToken).text retData = requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('client')['port']) + '/client/?action=' + command + '&token=' + str(config.get('client')['client_hmac']) + '&timingToken=' + self.timingToken).text
except Exception as error: except Exception as error:
logger.error('Failed to make local request (command: ' + str(command) + ').', error=error) if not silent:
logger.error('Failed to make local request (command: ' + str(command) + ').', error=error)
retData = False retData = False
return retData return retData

View File

@ -134,7 +134,7 @@ class OnionrTests(unittest.TestCase):
if not onionrplugins.exists('test'): if not onionrplugins.exists('test'):
os.makedirs(onionrplugins.get_plugins_folder('test')) os.makedirs(onionrplugins.get_plugins_folder('test'))
with open(onionrplugins.get_plugins_folder('test') + '/main.py', 'a') as main: with open(onionrplugins.get_plugins_folder('test') + '/main.py', 'a') as main:
main.write("print('Running')\n\ndef on_test(onionr = None, data = None):\n print('received test event!')\n return True\n\ndef on_start(onionr = None, data = None):\n print('start event called')\n\ndef on_stop(onionr = None, data = None):\n print('stop event called')\n\ndef on_enable(onionr = None, data = None):\n print('enable event called')\n\ndef on_disable(onionr = None, data = None):\n print('disable event called')\n") main.write("print('Running')\n\ndef on_test(pluginapi, data = None):\n print('received test event!')\n return True\n\ndef on_start(pluginapi, data = None):\n print('start event called')\n\ndef on_stop(pluginapi, data = None):\n print('stop event called')\n\ndef on_enable(pluginapi, data = None):\n print('enable event called')\n\ndef on_disable(pluginapi, data = None):\n print('disable event called')\n")
onionrplugins.enable('test') onionrplugins.enable('test')
try: try:
@ -152,7 +152,7 @@ class OnionrTests(unittest.TestCase):
if not onionrplugins.exists('test'): if not onionrplugins.exists('test'):
os.makedirs(onionrplugins.get_plugins_folder('test')) os.makedirs(onionrplugins.get_plugins_folder('test'))
with open(onionrplugins.get_plugins_folder('test') + '/main.py', 'a') as main: with open(onionrplugins.get_plugins_folder('test') + '/main.py', 'a') as main:
main.write("print('Running')\n\ndef on_test(onionr = None, data = None):\n print('received test event!')\n return True\n\ndef on_start(onionr = None, data = None):\n print('start event called')\n\ndef on_stop(onionr = None, data = None):\n print('stop event called')\n\ndef on_enable(onionr = None, data = None):\n print('enable event called')\n\ndef on_disable(onionr = None, data = None):\n print('disable event called')\n") main.write("print('Running')\n\ndef on_test(pluginapi, data = None):\n print('received test event!')\n return True\n\ndef on_start(pluginapi, data = None):\n print('start event called')\n\ndef on_stop(pluginapi, data = None):\n print('stop event called')\n\ndef on_enable(pluginapi, data = None):\n print('enable event called')\n\ndef on_disable(pluginapi, data = None):\n print('disable event called')\n")
onionrplugins.enable('test') onionrplugins.enable('test')
try: try:
@ -171,7 +171,7 @@ class OnionrTests(unittest.TestCase):
if not plugins.exists('test'): if not plugins.exists('test'):
os.makedirs(plugins.get_plugins_folder('test')) os.makedirs(plugins.get_plugins_folder('test'))
with open(plugins.get_plugins_folder('test') + '/main.py', 'a') as main: with open(plugins.get_plugins_folder('test') + '/main.py', 'a') as main:
main.write("print('Running')\n\ndef on_test(onionr = None, data = None):\n print('received test event!')\n return True\n\ndef on_start(onionr = None, data = None):\n print('start event called')\n\ndef on_stop(onionr = None, data = None):\n print('stop event called')\n\ndef on_enable(onionr = None, data = None):\n print('enable event called')\n\ndef on_disable(onionr = None, data = None):\n print('disable event called')\n") main.write("print('Running')\n\ndef on_test(pluginapi, data = None):\n print('received test event!')\n return True\n\ndef on_start(pluginapi, data = None):\n print('start event called')\n\ndef on_stop(pluginapi, data = None):\n print('stop event called')\n\ndef on_enable(pluginapi, data = None):\n print('enable event called')\n\ndef on_disable(pluginapi, data = None):\n print('disable event called')\n")
plugins.enable('test') plugins.enable('test')