From 6e526bf629753b29d062d13c5383d85d8085567a Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 31 Jul 2019 00:10:28 -0500 Subject: [PATCH] progress removing onionr.py --- onionr/etc/cleanup/__init__.py | 10 + onionr/etc/onionrvalues.py | 1 + onionr/onionr-old.py | 344 ++++++++++++++++++++++ onionr/onionrcommands/__init__.py | 3 - onionr/onionrcommands/daemonlaunch.py | 70 ++--- onionr/onionrcommands/onionrstatistics.py | 9 +- onionr/onionrcommands/parser/__init__.py | 16 +- onionr/onionrcommands/parser/arguments.py | 22 ++ onionr/onionrcommands/parser/recommend.py | 15 + onionr/onionrcommands/version.py | 15 + 10 files changed, 444 insertions(+), 61 deletions(-) create mode 100644 onionr/etc/cleanup/__init__.py create mode 100755 onionr/onionr-old.py create mode 100644 onionr/onionrcommands/parser/recommend.py create mode 100644 onionr/onionrcommands/version.py diff --git a/onionr/etc/cleanup/__init__.py b/onionr/etc/cleanup/__init__.py new file mode 100644 index 00000000..7f72962a --- /dev/null +++ b/onionr/etc/cleanup/__init__.py @@ -0,0 +1,10 @@ +import os, filepaths +def delete_run_files(): + try: + os.remove(filepaths.public_API_host_file) + except FileNotFoundError: + pass + try: + os.remove(filepaths.private_API_host_file) + except FileNotFoundError: + pass \ No newline at end of file diff --git a/onionr/etc/onionrvalues.py b/onionr/etc/onionrvalues.py index 02f12551..ece721f2 100755 --- a/onionr/etc/onionrvalues.py +++ b/onionr/etc/onionrvalues.py @@ -25,6 +25,7 @@ ONIONR_VERSION = '0.0.0' # for debugging and stuff ONIONR_VERSION_TUPLE = tuple(ONIONR_VERSION.split('.')) # (MAJOR, MINOR, VERSION) API_VERSION = '0' # increments of 1; only change when something fundamental about how the API works changes. This way other nodes know how to communicate without learning too much information about you. MIN_PY_VERSION = 6 +DEVELOPMENT_MODE = True platform = platform.system() if platform == 'Windows': diff --git a/onionr/onionr-old.py b/onionr/onionr-old.py new file mode 100755 index 00000000..b2baefe4 --- /dev/null +++ b/onionr/onionr-old.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +''' + Onionr - Private P2P Communication + + This file initializes Onionr when ran to be a daemon or with commands + + Run with 'help' for usage. +''' +''' + 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 . +''' +import sys +from etc import onionrvalues +if sys.version_info[0] == 2 or sys.version_info[1] < MIN_PY_VERSION: + sys.stderr.write('Error, Onionr requires Python 3.%s+\n' % (MIN_PY_VERSION,)) + sys.exit(1) + +from utils import detectoptimization +if detectoptimization.detect_optimization(): + sys.stderr.write('Error, Onionr cannot be run with an optimized Python interpreter\n') + sys.exit(1) +from utils import createdirs +createdirs.create_dirs() +import os, base64, random, shutil, time, platform, signal +from threading import Thread +import config, logger, onionrplugins as plugins, onionrevents as events +import netcontroller +from onionrblockapi import Block +import onionrexceptions, communicator, setupconfig +import onionrcommands as commands # Many command definitions are here +from utils import identifyhome, hastor +from coredb import keydb +import filepaths + +try: + from urllib3.contrib.socks import SOCKSProxyManager +except ImportError: + raise ImportError("You need the PySocks module (for use with socks5 proxy to use Tor)") + +class Onionr: + def __init__(self): + ''' + 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. + ''' + self.API_VERSION = onionrvalues.API_VERSION + self.userRunDir = os.getcwd() # Directory user runs the program from + self.killed = False + self.config = config + + if sys.argv[0] == os.path.basename(__file__): + try: + os.chdir(sys.path[0]) + except FileNotFoundError: + pass + + # set data dir + self.dataDir = identifyhome.identify_home() + if not self.dataDir.endswith('/'): + self.dataDir += '/' + + # Load global configuration data + data_exists = Onionr.setupConfig(self) + + # Copy default plugins into plugins folder + if not os.path.exists(plugins.get_plugins_folder()): + if os.path.exists('static-data/default-plugins/'): + names = [f for f in os.listdir("static-data/default-plugins/")] + shutil.copytree('static-data/default-plugins/', plugins.get_plugins_folder()) + + # Enable plugins + for name in names: + if not name in plugins.get_enabled_plugins(): + plugins.enable(name, self) + + for name in plugins.get_enabled_plugins(): + if not os.path.exists(plugins.get_plugin_data_folder(name)): + try: + os.mkdir(plugins.get_plugin_data_folder(name)) + except Exception as e: + #logger.warn('Error enabling plugin: ' + str(e), terminal=True) + plugins.disable(name, onionr = self, stop_event = False) + + self.communicatorInst = None + #self.deleteRunFiles() + + self.clientAPIInst = '' # Client http api instance + self.publicAPIInst = '' # Public http api instance + + signal.signal(signal.SIGTERM, self.exitSigterm) + + # Handle commands + + self.debug = False # Whole application debugging + + # Get configuration + if type(config.get('client.webpassword')) is type(None): + config.set('client.webpassword', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True) + if type(config.get('client.client.port')) is type(None): + randomPort = netcontroller.get_open_port() + config.set('client.client.port', randomPort, savefile=True) + if type(config.get('client.public.port')) is type(None): + randomPort = netcontroller.get_open_port() + config.set('client.public.port', randomPort, savefile=True) + if type(config.get('client.api_version')) is type(None): + config.set('client.api_version', onionrvalues.API_VERSION, savefile=True) + + self.cmds = commands.get_commands(self) + self.cmdhelp = commands.cmd_help + + # initialize plugins + events.event('init', onionr = self, threaded = False) + + command = '' + try: + command = sys.argv[1].lower() + except IndexError: + command = '' + finally: + self.execute(command) + + os.chdir(self.userRunDir) + return + + def exitSigterm(self, signum, frame): + self.killed = True + + def doExport(self, bHash): + exportDir = self.dataDir + 'block-export/' + if not os.path.exists(exportDir): + if os.path.exists(self.dataDir): + os.mkdir(exportDir) + else: + logger.error('Onionr Not initialized') + data = onionrstorage.getData(bHash) + with open('%s/%s.dat' % (exportDir, bHash), 'wb') as exportFile: + exportFile.write(data) + + def deleteRunFiles(self): + try: + os.remove(filepaths.public_API_host_file) + except FileNotFoundError: + pass + try: + os.remove(filepaths.private_API_host_file) + except FileNotFoundError: + pass + + ''' + Handle command line commands + ''' + + def showDetails(self): + commands.onionrstatistics.show_details(self) + + def openHome(self): + commands.openwebinterface.open_home(self) + + def addID(self): + commands.pubkeymanager.add_ID(self) + + def changeID(self): + commands.pubkeymanager.change_ID(self) + + def getCommands(self): + return self.cmds + + def friendCmd(self): + '''List, add, or remove friend(s) + Changes their peer DB entry. + ''' + commands.pubkeymanager.friend_command(self) + + def banBlock(self): + commands.banblocks.ban_block(self) + + def listConn(self): + commands.onionrstatistics.show_peers(self) + + def listPeers(self): + logger.info('Peer transport address list:', terminal=True) + for i in keydb.listkeys.list_adders(): + logger.info(i, terminal=True) + + def getWebPassword(self): + return config.get('client.webpassword') + + def printWebPassword(self): + logger.info(self.getWebPassword(), terminal=True) + + def getHelp(self): + return self.cmdhelp + + def addCommand(self, command, function): + self.cmds[str(command).lower()] = function + + def addHelp(self, command, description): + self.cmdhelp[str(command).lower()] = str(description) + + def delCommand(self, command): + return self.cmds.pop(str(command).lower(), None) + + def delHelp(self, command): + return self.cmdhelp.pop(str(command).lower(), None) + + def execute(self, argument): + ''' + Executes a command + ''' + + argument = argument[argument.startswith('--') and len('--'):] # remove -- if it starts with it + + # define commands + commands = self.getCommands() + + command = commands.get(argument, self.notFound) + command() + + def listKeys(self): + ''' + Displays a list of keys (used to be called peers) (?) + ''' + logger.info('%sPublic keys in database: \n%s%s' % (logger.colors.fg.lightgreen, logger.colors.fg.green, '\n'.join(keydb.listkeys.list_peers()())), terminal=True) + + def addPeer(self): + ''' + Adds a peer (?) + ''' + commands.keyadders.add_peer(self) + + def addAddress(self): + ''' + Adds a Onionr node address + ''' + commands.keyadders.add_address(self) + + def enablePlugin(self): + ''' + Enables and starts the given plugin + ''' + commands.plugincommands.enable_plugin(self) + + def disablePlugin(self): + ''' + Disables and stops the given plugin + ''' + commands.plugincommands.disable_plugin(self) + + def reloadPlugin(self): + ''' + Reloads (stops and starts) all plugins, or the given plugin + ''' + commands.plugincommands.reload_plugin(self) + + def createPlugin(self): + ''' + Creates the directory structure for a plugin name + ''' + commands.plugincommands.create_plugin(self) + + def notFound(self): + ''' + Displays a "command not found" message + ''' + + logger.error('Invalid command.', timestamp = False, terminal=True) + + def showHelpSuggestion(self): + ''' + Displays a message suggesting help + ''' + logger.info('Do ' + logger.colors.bold + sys.argv[0] + ' --help' + logger.colors.reset + logger.colors.fg.green + ' for Onionr help.', terminal=True) + + def start(self, input = False, override = False): + ''' + Starts the Onionr daemon + ''' + if config.get('general.dev_mode', False): + override = True + commands.daemonlaunch.start(self, input, override) + + def setClientAPIInst(self, inst): + self.clientAPIInst = inst + + def getClientApi(self): + while self.clientAPIInst == '': + time.sleep(0.5) + return self.clientAPIInst + + def daemon(self): + ''' + Starts the Onionr communication daemon + ''' + commands.daemonlaunch.daemon(self) + + def killDaemon(self): + ''' + Shutdown the Onionr daemon + ''' + commands.daemonlaunch.kill_daemon(self) + + def showStats(self): + ''' + Displays statistics and exits + ''' + commands.onionrstatistics.show_stats(self) + + def showHelp(self, command = None): + ''' + Show help for Onionr + ''' + commands.show_help(self, command) + + def getFile(self): + ''' + Get a file from onionr blocks + ''' + commands.filecommands.getFile(self) + + def addWebpage(self): + ''' + Add a webpage to the onionr network + ''' + self.addFile(singleBlock=True, blockType='html') + + def addFile(self, singleBlock=False, blockType='bin'): + ''' + Adds a file to the onionr network + ''' + commands.filecommands.add_file(self, singleBlock, blockType) + +if __name__ == "__main__": + Onionr() diff --git a/onionr/onionrcommands/__init__.py b/onionr/onionrcommands/__init__.py index 7ec7c971..d6147309 100755 --- a/onionr/onionrcommands/__init__.py +++ b/onionr/onionrcommands/__init__.py @@ -44,9 +44,6 @@ def show_help(o_inst, command): def get_commands(onionr_inst): return {'': onionr_inst.showHelpSuggestion, 'help': onionr_inst.showHelp, - 'version': onionr_inst.version, - 'header': onionr_inst.cmdHeader, - 'config': onionr_inst.configure, 'start': onionr_inst.start, 'stop': onionr_inst.killDaemon, 'status': onionr_inst.showStats, diff --git a/onionr/onionrcommands/daemonlaunch.py b/onionr/onionrcommands/daemonlaunch.py index f693fcb2..15b9e1ce 100755 --- a/onionr/onionrcommands/daemonlaunch.py +++ b/onionr/onionrcommands/daemonlaunch.py @@ -20,20 +20,22 @@ import os, time, sys, platform, sqlite3, signal from threading import Thread -import onionr, apiservers, logger, communicator +import config, apiservers, logger, communicator import onionrevents as events from netcontroller import NetController from onionrutils import localcommand import filepaths from coredb import daemonqueue +from etc import onionrvalues, cleanup from onionrcrypto import getourkeypair from utils import hastor, logoheader +from . import version -def _proper_shutdown(o_inst): +def _proper_shutdown(): localcommand.local_command('shutdown') sys.exit(1) -def daemon(o_inst): +def daemon(): ''' Starts the Onionr communication daemon ''' @@ -46,7 +48,7 @@ def daemon(o_inst): logger.debug('Runcheck file found on daemon start, deleting in advance.') os.remove(filepaths.run_check_file) - Thread(target=apiservers.ClientAPI, args=(o_inst, o_inst.debug, onionr.API_VERSION), daemon=True).start() + Thread(target=apiservers.ClientAPI, args=(onionrvalues.API_VERSION), daemon=True).start() Thread(target=apiservers.PublicAPI, args=[o_inst.getClientApi()], daemon=True).start() apiHost = '' @@ -61,19 +63,19 @@ def daemon(o_inst): logger.raw('', terminal=True) # print nice header thing :) - if o_inst.config.get('general.display_header', True): + if config.get('general.display_header', True): logoheader.header() - o_inst.version(verbosity = 5, function = logger.info) + version.version(verbosity = 5, function = logger.info) logger.debug('Python version %s' % platform.python_version()) - if o_inst._developmentMode: + if onionrvalues.DEVELOPMENT_MODE: logger.warn('Development mode enabled', timestamp = False, terminal=True) - net = NetController(o_inst.config.get('client.public.port', 59497), apiServerIP=apiHost) + net = NetController(config.get('client.public.port', 59497), apiServerIP=apiHost) logger.info('Tor is starting...', terminal=True) if not net.startTor(): localcommand.local_command('shutdown') sys.exit(1) - if len(net.myID) > 0 and o_inst.config.get('general.security_level', 1) == 0: + if len(net.myID) > 0 and config.get('general.security_level', 1) == 0: logger.debug('Started .onion service: %s' % (logger.colors.underline + net.myID)) else: logger.debug('.onion service disabled') @@ -82,54 +84,30 @@ def daemon(o_inst): try: time.sleep(1) except KeyboardInterrupt: - _proper_shutdown(o_inst) - - o_inst.torPort = net.socksPort - communicatorThread = Thread(target=communicator.startCommunicator, args=(o_inst, str(net.socksPort)), daemon=True) - communicatorThread.start() + _proper_shutdown() - while o_inst.communicatorInst is None: - time.sleep(0.1) + events.event('daemon_start') + communicator.startCommunicator(str(net.socksPort)) - logger.debug('Started communicator.') - - events.event('daemon_start', onionr = o_inst) - while True: - try: - time.sleep(3) - except KeyboardInterrupt: - o_inst.communicatorInst.shutdown = True - finally: - # Debug to print out used FDs (regular and net) - #proc = psutil.Process() - #print('api-files:',proc.open_files(), len(psutil.net_connections())) - # Break if communicator process ends, so we don't have left over processes - if o_inst.communicatorInst.shutdown: - break - if o_inst.killed: - break # Break out if sigterm for clean exit - - signal.signal(signal.SIGINT, _ignore_sigint) - daemonqueue.daemon_queue_add('shutdown') localcommand.local_command('shutdown') net.killTor() time.sleep(5) # Time to allow threads to finish, if not any "daemon" threads will be slaughtered http://docs.python.org/library/threading.html#threading.Thread.daemon - o_inst.deleteRunFiles() - return + cleanup.delete_run_files() def _ignore_sigint(sig, frame): + '''This space intentionally left blank''' return -def kill_daemon(o_inst): +def kill_daemon(): ''' Shutdown the Onionr daemon ''' logger.warn('Stopping the running daemon...', timestamp = False, terminal=True) try: - events.event('daemon_stop', onionr = o_inst) - net = NetController(o_inst.config.get('client.port', 59496)) + events.event('daemon_stop') + net = NetController(config.get('client.port', 59496)) try: daemonqueue.daemon_queue_add('shutdown') except sqlite3.OperationalError: @@ -140,18 +118,16 @@ def kill_daemon(o_inst): logger.error('Failed to shutdown daemon: ' + str(e), error = e, timestamp = False, terminal=True) return -def start(o_inst, input = False, override = False): +def start(input = False, override = False): if os.path.exists('.onionr-lock') and not override: logger.fatal('Cannot start. Daemon is already running, or it did not exit cleanly.\n(if you are sure that there is not a daemon running, delete .onionr-lock & try again).', terminal=True) else: - if not o_inst.debug and not o_inst._developmentMode: + if not onionrvalues.DEVELOPMENT_MODE: lockFile = open('.onionr-lock', 'w') lockFile.write('') lockFile.close() - o_inst.running = True - o_inst.daemon() - o_inst.running = False - if not o_inst.debug and not o_inst._developmentMode: + daemon() + if not onionrvalues.DEVELOPMENT_MODE: try: os.remove('.onionr-lock') except FileNotFoundError: diff --git a/onionr/onionrcommands/onionrstatistics.py b/onionr/onionrcommands/onionrstatistics.py index b3883db5..89e7564a 100755 --- a/onionr/onionrcommands/onionrstatistics.py +++ b/onionr/onionrcommands/onionrstatistics.py @@ -20,11 +20,10 @@ import os, uuid, time import logger from onionrblockapi import Block -import onionr from onionrutils import checkcommunicator, mnemonickeys from utils import sizeutils, gethostname, getconsolewidth from coredb import blockmetadb, daemonqueue, keydb -import onionrcrypto +import onionrcrypto, config def show_stats(o_inst): try: # define stats messages here @@ -43,7 +42,7 @@ def show_stats(o_inst): # count stats 'div2' : True, 'Known Peers' : str(max(len(keydb.listkeys.list_peers()) - 1, 0)), - 'Enabled Plugins' : str(len(o_inst.config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(o_inst.dataDir + 'plugins/'))), + 'Enabled Plugins' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(o_inst.dataDir + 'plugins/'))), 'Stored Blocks' : str(totalBlocks), 'Percent Blocks Signed' : str(round(100 * signedBlocks / max(totalBlocks, 1), 2)) + '%' } @@ -84,10 +83,10 @@ def show_stats(o_inst): except Exception as e: logger.error('Failed to generate statistics table. ' + str(e), error = e, timestamp = False, terminal=True) -def show_details(o_inst): +def show_details(): details = { 'Node Address' : gethostname.get_hostname(), - 'Web Password' : o_inst.getWebPassword(), + 'Web Password' : config.get('client.webpassword'), 'Public Key' : onionrcrypto.pub_key, 'Human-readable Public Key' : mnemonickeys.get_human_readable_ID() } diff --git a/onionr/onionrcommands/parser/__init__.py b/onionr/onionrcommands/parser/__init__.py index 029cc3f3..d5d1bebe 100644 --- a/onionr/onionrcommands/parser/__init__.py +++ b/onionr/onionrcommands/parser/__init__.py @@ -17,11 +17,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import argparse, sys +import sys from etc import onionrvalues +import logger, onionrexceptions +from . import arguments, recommend def register(): - - parser = argparse.ArgumentParser(prog=onionrvalues.SCRIPT_NAME) - parser.add_argument("command", help="Onionr command to execute") - args = parser.parse_args() - print(args.start) \ No newline at end of file + cmd = sys.argv[1] + + try: + arguments.get_func(cmd)() + except onionrexceptions.NotFound: + recommend.recommend() + sys.exit(3) \ No newline at end of file diff --git a/onionr/onionrcommands/parser/arguments.py b/onionr/onionrcommands/parser/arguments.py index e69de29b..f9acb8a5 100644 --- a/onionr/onionrcommands/parser/arguments.py +++ b/onionr/onionrcommands/parser/arguments.py @@ -0,0 +1,22 @@ +from .. import onionrstatistics, version, daemonlaunch +import onionrexceptions +def get_arguments(): + '''This is a function because we need to be able to dynamically modify them with plugins''' + args = { + ('details', 'info'): onionrstatistics.show_details, + ('version'): version.version, + ('start', 'daemon'): daemonlaunch.start + } + return args + +def get_help(): + return + +def get_func(argument): + argument = argument.lower() + args = get_arguments() + + for arg in args.keys(): # Iterate command alias sets + if argument in arg: # If our argument is in the current alias set, return the command function + return args[arg] + raise onionrexceptions.NotFound \ No newline at end of file diff --git a/onionr/onionrcommands/parser/recommend.py b/onionr/onionrcommands/parser/recommend.py new file mode 100644 index 00000000..ba745d36 --- /dev/null +++ b/onionr/onionrcommands/parser/recommend.py @@ -0,0 +1,15 @@ +import sys +from difflib import SequenceMatcher +import logger +from . import arguments + +def recommend(): + tried = sys.argv[1] + args = arguments.get_arguments() + print_message = 'Command not found:' + for key in args.keys(): + for word in key: + if SequenceMatcher(None, tried, word).ratio() >= 0.75: + logger.warn('%s "%s", did you mean "%s"?' % (print_message, tried, word), terminal=True) + return + logger.error('%s "%s"' % (print_message, tried), terminal=True) \ No newline at end of file diff --git a/onionr/onionrcommands/version.py b/onionr/onionrcommands/version.py new file mode 100644 index 00000000..8162f321 --- /dev/null +++ b/onionr/onionrcommands/version.py @@ -0,0 +1,15 @@ +import platform +from utils import identifyhome +from etc import onionrvalues +import logger +def version(verbosity = 5, function = logger.info): + ''' + Displays the Onionr version + ''' + + function('Onionr v%s (%s) (API v%s)' % (onionrvalues.ONIONR_VERSION, platform.machine(), onionrvalues.API_VERSION), terminal=True) + if verbosity >= 1: + function(onionrvalues.ONIONR_TAGLINE, terminal=True) + if verbosity >= 2: + function('Running on %s %s' % (platform.platform(), platform.release()), terminal=True) + function('Onionr data dir: %s' % identifyhome.identify_home(), terminal=True) \ No newline at end of file