From 098abb8e552e6ad84eed036f2951ea00e5d09679 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Fri, 20 Apr 2018 20:10:50 -0700 Subject: [PATCH] Improve support for plugins --- onionr/core.py | 25 ++++- onionr/default_plugin.txt | 41 +++++++ onionr/onionr.py | 48 +++++++- onionr/onionrevents.py | 19 ++-- onionr/onionrpluginapi.py | 230 +++++++++++++++++++------------------- onionr/onionrplugins.py | 2 +- onionr/onionrutils.py | 5 +- onionr/tests.py | 6 +- 8 files changed, 241 insertions(+), 135 deletions(-) create mode 100644 onionr/default_plugin.txt diff --git a/onionr/core.py b/onionr/core.py index 3b1bde67..f363d8bc 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -22,7 +22,7 @@ import sqlite3, os, sys, time, math, base64, tarfile, getpass, simplecrypt, hash #from Crypto import Random import netcontroller -import onionrutils, onionrcrypto, btc +import onionrutils, onionrcrypto, btc, onionrevents as events if sys.version_info < (3, 6): try: @@ -89,10 +89,13 @@ class Core: c.execute('INSERT INTO peers (id, name, dateSeen) VALUES(?, ?, ?);', t) conn.commit() conn.close() + return True 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): conn = sqlite3.connect(self.addressDB) c = conn.cursor() @@ -114,12 +117,17 @@ class Core: c.execute('INSERT INTO adders (address, type) VALUES(?, ?);', t) conn.commit() conn.close() + + events.event('address_add', data = {'address': address}, onionr = None) + return True else: return False def removeAddress(self, address): - '''Remove an address from the address database''' + ''' + Remove an address from the address database + ''' if self._utils.validateID(address): conn = sqlite3.connect(self.addressDB) c = conn.cursor() @@ -127,6 +135,9 @@ class Core: c.execute('Delete from adders where address=?;', t) conn.commit() conn.close() + + events.event('address_remove', data = {'address': address}, onionr = None) + return True else: return False @@ -330,6 +341,8 @@ class Core: conn.commit() conn.close() + events.event('queue_pop', data = {'data': retData}, onionr = None) + return retData def daemonQueueAdd(self, command, data=''): @@ -345,6 +358,8 @@ class Core: conn.commit() conn.close() + events.event('queue_push', data = {'command': command, 'data': data}, onionr = None) + return def clearDaemonQueue(self): @@ -354,11 +369,12 @@ class Core: conn = sqlite3.connect(self.queueDB) c = conn.cursor() try: - c.execute('delete from commands;') + c.execute('DELETE FROM commands;') conn.commit() except: pass conn.close() + events.event('queue_clear', onionr = None) return @@ -564,4 +580,5 @@ class Core: announceAmount = len(nodeList) for i in range(announceAmount): self.daemonQueueAdd('announceNode', nodeList[i]) + events.event('introduction', onionr = None) return diff --git a/onionr/default_plugin.txt b/onionr/default_plugin.txt new file mode 100644 index 00000000..edd8247f --- /dev/null +++ b/onionr/default_plugin.txt @@ -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 diff --git a/onionr/onionr.py b/onionr/onionr.py index 6cb15404..bcd71849 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -20,8 +20,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform -import api, core, config, logger, onionrplugins as plugins +import sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re +import api, core, config, logger, onionrplugins as plugins, onionrevents as events from onionrutils import OnionrUtils from netcontroller import NetController @@ -137,6 +137,9 @@ class Onionr: 'reloadplugin': self.reloadPlugin, 'reload-plugins': self.reloadPlugin, 'reloadplugins': self.reloadPlugin, + 'create-plugin': self.createPlugin, + 'createplugin': self.createPlugin, + 'plugin-create': self.createPlugin, 'listkeys': self.listKeys, 'list-keys': self.listKeys, @@ -159,6 +162,7 @@ class Onionr: 'addaddr': self.addAddress, 'addaddress': self.addAddress, + 'introduce': self.onionrCore.introduceNode, 'connect': self.addAddress } @@ -172,14 +176,19 @@ class Onionr: 'enable-plugin': 'Enables and starts a plugin', 'disable-plugin': 'Disables and stops a plugin', 'reload-plugin': 'Reloads a plugin', + 'create-plugin': 'Creates directory structure for a plugin', 'add-peer': 'Adds a peer (?)', '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', - '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 = '' try: command = sys.argv[1].lower() @@ -238,6 +247,7 @@ class Onionr: ''' Executes a command ''' + argument = argument[argument.startswith('--') and len('--'):] # remove -- if it starts with it # define commands @@ -256,6 +266,7 @@ class Onionr: ''' Displays the Onionr version ''' + logger.info('Onionr ' + ONIONR_VERSION + ' (' + platform.machine() + ') - API v' + API_VERSION) if verbosity >= 1: logger.info(ONIONR_TAGLINE) @@ -268,6 +279,7 @@ class Onionr: ''' Create a private message and send it ''' + invalidID = True while invalidID: try: @@ -404,6 +416,34 @@ class Onionr: 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] + ' ') + + return + def notFound(self): ''' Displays a "command not found" message @@ -451,6 +491,7 @@ class Onionr: time.sleep(1) subprocess.Popen(["./communicator.py", "run", str(net.socksPort)]) logger.debug('Started communicator') + events.event('daemon_start', onionr = self) api.API(self.debug) return @@ -461,6 +502,7 @@ class Onionr: ''' logger.warn('Killing the running daemon') + events.event('daemon_stop', onionr = self) net = NetController(config.get('client')['port']) try: self.onionrUtils.localCommand('shutdown') diff --git a/onionr/onionrevents.py b/onionr/onionrevents.py index 9b386a31..61005b98 100644 --- a/onionr/onionrevents.py +++ b/onionr/onionrevents.py @@ -20,19 +20,20 @@ import config, logger, onionrplugins as plugins, onionrpluginapi as pluginapi -def get_pluginapi(onionr): - return pluginapi.PluginAPI(onionr) +def get_pluginapi(onionr, data): + 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) ''' for plugin in plugins.get_enabled_plugins(): try: - call(plugins.get_plugin(plugin), event_name, data, self.get_pluginapi(onionr)) - except: + call(plugins.get_plugin(plugin), event_name, data, get_pluginapi(onionr, data)) + except Exception as e: logger.warn('Event \"' + event_name + '\" failed for plugin \"' + plugin + '\".') + logger.debug(str(e)) 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? if hasattr(plugin, attribute): - logger.debug('Calling event ' + str(event_name)) - getattr(plugin, attribute)(pluginapi, data) + #logger.debug('Calling event ' + str(event_name)) + getattr(plugin, attribute)(pluginapi) return True - except: - logger.warn('Failed to call event ' + str(event_name) + ' on module.') + except Exception as e: + logger.debug(str(e)) return False else: return True diff --git a/onionr/onionrpluginapi.py b/onionr/onionrpluginapi.py index 67d37694..c6d68f31 100644 --- a/onionr/onionrpluginapi.py +++ b/onionr/onionrpluginapi.py @@ -20,131 +20,135 @@ 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: - 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.data = data + self.daemon = DaemonAPI(self) - self.plugin = PluginAPI(self) - self.command = CommandAPI(self) - + self.plugins = PluginAPI(self) + self.commands = CommandAPI(self) + def get_onionr(self): return self.onionr - + + def get_data(self): + return self.data + def get_core(self): return self.get_onionr().onionrCore - + def get_utils(self): return self.get_onionr().onionrUtils - + def get_daemonapi(self): return self.daemon - + def get_pluginapi(self): - return self.plugin - + return self.plugins + def get_commandapi(self): - return self.command - + return self.commands + def is_development_mode(self): 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() diff --git a/onionr/onionrplugins.py b/onionr/onionrplugins.py index f75836cd..13ca3b95 100644 --- a/onionr/onionrplugins.py +++ b/onionr/onionrplugins.py @@ -225,5 +225,5 @@ def check(): if not os.path.exists(os.path.dirname(get_plugins_folder())): logger.debug('Generating plugin data folder...') os.makedirs(os.path.dirname(get_plugins_folder())) - + return diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 142d2666..634770c4 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -129,7 +129,7 @@ class OnionrUtils: logger.error('Failed to read my address.', error=error) 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. ''' @@ -140,7 +140,8 @@ class OnionrUtils: 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 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 return retData diff --git a/onionr/tests.py b/onionr/tests.py index 74027b87..122433bf 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -134,7 +134,7 @@ class OnionrTests(unittest.TestCase): if not onionrplugins.exists('test'): os.makedirs(onionrplugins.get_plugins_folder('test')) 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') try: @@ -152,7 +152,7 @@ class OnionrTests(unittest.TestCase): if not onionrplugins.exists('test'): os.makedirs(onionrplugins.get_plugins_folder('test')) 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') try: @@ -171,7 +171,7 @@ class OnionrTests(unittest.TestCase): if not plugins.exists('test'): os.makedirs(plugins.get_plugins_folder('test')) 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')