renamed onionr dir and bugfixes/linting progress

This commit is contained in:
Kevin Froman 2019-11-20 04:52:50 -06:00
parent 2b996da17f
commit 720efe4fca
226 changed files with 179 additions and 142 deletions

29
src/onionrcommands/README.md Executable file
View file

@ -0,0 +1,29 @@
# onionrcommands
This module contains handlers/functions for Onionr cli interface commands.
## Files
parser/: Registers and handles Onionr CLI commands
__init__.py: stores the command references (aside from plugins) and help info.
banblocks.py: command handler for manually removing blocks from one's node
daemonlaunch.py: command to run Onionr (start the api servers, tor and communicator)
exportblocks.py: command to export an onionr block to the export folder. Exported blocks can be manually shared outside of the Onionr network
filecommands.py commands to insert and fetch files from the Onionr network
keyadders.py: commands to add an onionr user key or transport address
onionrstatistics.py: commands to print out various information about one's Onionr node
openwebinterface.py: command to open the web interface (useful because it requires a randomly generated token)
plugincommands.py: commands to enable/disable/reload plugins
pubkeymanager.py: commands to generate a new onionr user id, change the active id, or add/remove/list friends
resettor.py: command to delete the Tor data directory

0
src/onionrcommands/__init__.py Executable file
View file

53
src/onionrcommands/banblocks.py Executable file
View file

@ -0,0 +1,53 @@
'''
Onionr - Private P2P Communication
This file contains the command for banning blocks from the node
'''
'''
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/>.
'''
import sys
import logger
from onionrutils import stringvalidators
from onionrstorage import removeblock
from onionrstorage import deleteBlock
from onionrblocks import onionrblacklist
from utils import reconstructhash
def ban_block():
"""Deletes a block, permanently blacklisting it"""
blacklist = onionrblacklist.OnionrBlackList()
try:
ban = sys.argv[2]
except IndexError:
# Get the hash if its not provided as a CLI argument
ban = logger.readline('Enter a block hash:').strip()
# Make sure the hash has no truncated zeroes
ban = reconstructhash.reconstruct_hash(ban)
if stringvalidators.validate_hash(ban):
if not blacklist.inBlacklist(ban):
try:
blacklist.addToDB(ban)
removeblock.remove_block(ban)
deleteBlock(ban)
except Exception as error:
logger.error('Could not blacklist block', error=error, terminal=True)
else:
logger.info('Block blacklisted', terminal=True)
else:
logger.warn('That block is already blacklisted', terminal=True)
else:
logger.error('Invalid block hash', terminal=True)
ban_block.onionr_help = "<block hash>: deletes and blacklists a block"

View file

@ -0,0 +1,164 @@
'''
Onionr - Private P2P Communication
launch the api servers and communicator
'''
'''
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/>.
'''
import os, time, sys, platform, sqlite3, signal
from threading import Thread
import toomanyobjs
import config, apiservers, logger, communicator
from onionrplugins 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
import serializeddata
import runtests
def _proper_shutdown():
localcommand.local_command('shutdown')
sys.exit(1)
def daemon():
'''
Starts the Onionr communication daemon
'''
if not hastor.has_tor():
logger.error("Tor is not present in system path or Onionr directory", terminal=True)
sys.exit(1)
# remove runcheck if it exists
if os.path.isfile(filepaths.run_check_file):
logger.debug('Runcheck file found on daemon start, deleting in advance.')
os.remove(filepaths.run_check_file)
# Create shared objects
shared_state = toomanyobjs.TooMany()
Thread(target=shared_state.get(apiservers.ClientAPI).start, daemon=True, name='client HTTP API').start()
Thread(target=shared_state.get(apiservers.PublicAPI).start, daemon=True, name='public HTTP API').start()
# Init run time tester (ensures Onionr is running right, for testing purposes)
shared_state.get(runtests.OnionrRunTestManager)
shared_state.get(serializeddata.SerializedData)
shared_state.share_object() # share the parent object to the threads
apiHost = ''
while apiHost == '':
try:
with open(filepaths.public_API_host_file, 'r') as hostFile:
apiHost = hostFile.read()
except FileNotFoundError:
pass
time.sleep(0.5)
logger.raw('', terminal=True)
# print nice header thing :)
if config.get('general.display_header', True):
logoheader.header()
version.version(verbosity = 5, function = logger.info)
logger.debug('Python version %s' % platform.python_version())
if onionrvalues.DEVELOPMENT_MODE:
logger.warn('Development mode enabled', timestamp = False, terminal=True)
net = NetController(config.get('client.public.port', 59497), apiServerIP=apiHost)
shared_state.add(net)
logger.info('Tor is starting...', terminal=True)
if not net.startTor():
localcommand.local_command('shutdown')
cleanup.delete_run_files()
sys.exit(1)
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')
logger.info('Using public key: %s' % (logger.colors.underline + getourkeypair.get_keypair()[0][:52]))
try:
time.sleep(1)
except KeyboardInterrupt:
pass
events.event('init', threaded = False)
events.event('daemon_start')
communicator.startCommunicator(shared_state)
localcommand.local_command('shutdown')
net.killTor()
try:
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
except KeyboardInterrupt:
pass
cleanup.delete_run_files()
def _ignore_sigint(sig, frame):
'''This space intentionally left blank'''
return
def kill_daemon():
'''
Shutdown the Onionr daemon (communicator)
'''
logger.warn('Stopping the running daemon...', timestamp = False, terminal=True)
try:
# On platforms where we can, fork out to prevent locking
try:
pid = os.fork()
if pid != 0: return
except (AttributeError, OSError) as e: pass
events.event('daemon_stop')
net = NetController(config.get('client.port', 59496))
try:
daemonqueue.daemon_queue_add('shutdown')
except sqlite3.OperationalError:
pass
net.killTor()
except Exception as e:
logger.error('Failed to shutdown daemon: ' + str(e), error = e, timestamp = False, terminal=True)
return
kill_daemon.onionr_help = "Gracefully stops the Onionr API servers"
def start(input: bool = False, override: bool = False):
"""If no lock file, make one and start onionr, error if there is and its not overridden"""
if os.path.exists(filepaths.lock_file) 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 onionrvalues.DEVELOPMENT_MODE:
lockFile = open(filepaths.lock_file, 'w')
lockFile.write('delete at your own risk')
lockFile.close()
daemon()
try:
os.remove(filepaths.lock_file)
except FileNotFoundError:
pass
start.onionr_help = "Start Onionr node (public and clients API servers)"

View file

@ -0,0 +1,43 @@
'''
Onionr - Private P2P Communication
This file handles the command for exporting blocks to disk
'''
'''
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/>.
'''
import sys, os
import logger, onionrstorage
from utils import createdirs
from onionrutils import stringvalidators
import filepaths
def doExport(bHash):
createdirs.create_dirs()
data = onionrstorage.getData(bHash)
with open('%s/%s.dat' % (filepaths.export_location, bHash), 'wb') as exportFile:
exportFile.write(data)
logger.info('Block exported as file', terminal=True)
def export_block():
exportDir = filepaths.export_location
try:
if not stringvalidators.validate_hash(sys.argv[2]): raise ValueError
except (IndexError, ValueError):
logger.error('No valid block hash specified.', terminal=True)
sys.exit(1)
else:
bHash = sys.argv[2]
doExport(bHash)
export_block.onionr_help = "<block hash>: Export an Onionr block to a file. Export directory is in the Onionr data directory under block-export/"

View file

@ -0,0 +1,89 @@
"""
Onionr - Private P2P Communication
This file handles the commands for adding and getting files from the Onionr 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/>.
"""
import base64, sys, os
import logger
from onionrblocks.onionrblockapi import Block
import onionrexceptions
from onionrutils import stringvalidators
from etc import onionrvalues
from onionrblocks import insert
_ORIG_DIR = onionrvalues.ORIG_RUN_DIR_ENV_VAR
def _get_dir(path: str)->str:
if not os.getenv(_ORIG_DIR) is None: return os.getenv(_ORIG_DIR) + '/' + path
else: return path
def add_html(singleBlock=True, blockType='html'):
add_file(singleBlock, blockType)
add_html.onionr_help = "Adds an HTML file into Onionr. Does not currently include dependant resources"
def add_file(singleBlock=False, blockType='bin'):
"""
Adds a file to the onionr network
"""
if len(sys.argv) >= 3:
filename = sys.argv[2]
contents = None
if not os.path.exists(_get_dir(filename)):
logger.error('That file does not exist. Improper path (specify full path)?', terminal=True)
return
logger.info('Adding file... this might take a long time.', terminal=True)
try:
with open(_get_dir(filename), 'rb') as singleFile:
blockhash = insert(singleFile.read(), header=blockType)
if len(blockhash) > 0:
logger.info('File %s saved in block %s' % (filename, blockhash), terminal=True)
except Exception as e:
logger.error('Failed to save file in block ' + str(e), timestamp = False, terminal=True)
else:
logger.error('%s add-file <filename>' % sys.argv[0], timestamp = False, terminal=True)
add_file.onionr_help = "<file path> Add a file into the Onionr network"
def get_file():
"""
Get a file from onionr blocks
"""
try:
fileName = _get_dir(sys.argv[2])
bHash = sys.argv[3]
except IndexError:
logger.error("Syntax %s %s" % (sys.argv[0], '/path/to/filename <blockhash>'), terminal=True)
else:
logger.info(fileName, terminal=True)
contents = None
if os.path.exists(fileName):
logger.error("File already exists", terminal=True)
return
if not stringvalidators.validate_hash(bHash):
logger.error('Block hash is invalid', terminal=True)
return
try:
with open(fileName, 'wb') as myFile:
myFile.write(Block(bHash).bcontent)
except onionrexceptions.NoDataAvailable:
logger.error('That block is not available. Trying again later may work.', terminal=True)
get_file.onionr_help = "<file path> <block hash>: Download a file from the onionr network."

37
src/onionrcommands/keyadders.py Executable file
View file

@ -0,0 +1,37 @@
'''
Onionr - Private P2P Communication
add keys (transport and pubkey)
'''
'''
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/>.
'''
import sys
import logger
from coredb import keydb
def add_address():
try:
newAddress = sys.argv[2]
newAddress = newAddress.replace('http:', '').replace('/', '')
except IndexError:
pass
else:
logger.info("Adding address: " + logger.colors.underline + newAddress, terminal=True)
if keydb.addkeys.add_address(newAddress):
logger.info("Successfully added address.", terminal=True)
else:
logger.warn("Unable to add address.", terminal=True)
add_address.onionr_help = "Adds a node transport address to the local node list"

View file

@ -0,0 +1,15 @@
import onionrblocks
def motd_creator():
"""Create a new MOTD message for the Onionr network"""
motd = ''
new = ''
print('Enter a new MOTD, quit on a new line:')
while new != 'quit':
new = input()
if new != 'quit':
motd += new
bl = onionrblocks.insert(motd, header='motd', sign=True)
print(f"inserted in {bl}")
motd_creator.onionr_help = "Create a new MOTD message for the onionr network"

View file

@ -0,0 +1,103 @@
'''
Onionr - Private P2P Communication
This module defines commands to show stats/details about the local node
'''
'''
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/>.
'''
import os, uuid, time
import logger
from onionrblocks import onionrblockapi
from onionrblocks import onionrblacklist
from onionrutils import checkcommunicator, mnemonickeys
from utils import sizeutils, gethostname, getconsolewidth, identifyhome
from coredb import blockmetadb, daemonqueue, keydb
import onionrcrypto, config
from etc import onionrvalues
def show_stats():
try:
# define stats messages here
totalBlocks = len(blockmetadb.get_block_list())
home = identifyhome.identify_home()
signedBlocks = len(onionrblockapi.Block.getBlocks(signed = True))
totalBanned = len(onionrblacklist.OnionrBlackList().getList())
messages = {
# info about local client
'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if checkcommunicator.is_communicator_running(timeout = 9) else logger.colors.fg.red + 'Offline'),
# file and folder size stats
'div1' : True, # this creates a solid line across the screen, a div
'Total Block Size' : sizeutils.human_size(sizeutils.size(home + 'blocks/')),
'Total Plugin Size' : sizeutils.human_size(sizeutils.size(home + 'plugins/')),
'Log File Size' : sizeutils.human_size(sizeutils.size(home + 'output.log')),
# count stats
'div2' : True,
'Known Peers (nodes)' : str(max(len(keydb.listkeys.list_adders()) - 1, 0)),
'Enabled Plugins' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(home + 'plugins/'))),
'Stored Blocks' : str(totalBlocks),
'Deleted Blocks' : str(totalBanned),
'Percent Blocks Signed' : str(round(100 * signedBlocks / max(totalBlocks, 1), 2)) + '%'
}
# color configuration
colors = {
'title' : logger.colors.bold,
'key' : logger.colors.fg.lightgreen,
'val' : logger.colors.fg.green,
'border' : logger.colors.fg.lightblue,
'reset' : logger.colors.reset
}
# pre-processing
maxlength = 0
width = getconsolewidth.get_console_width()
for key, val in messages.items():
if not (type(val) is bool and val is True):
maxlength = max(len(key), maxlength)
prewidth = maxlength + len(' | ')
groupsize = width - prewidth - len('[+] ')
# generate stats table
logger.info(colors['title'] + 'Onionr v%s Statistics' % onionrvalues.ONIONR_VERSION + colors['reset'], terminal=True)
logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'], terminal=True)
for key, val in messages.items():
if not (type(val) is bool and val is True):
val = [str(val)[i:i + groupsize] for i in range(0, len(str(val)), groupsize)]
logger.info(colors['key'] + str(key).rjust(maxlength) + colors['reset'] + colors['border'] + ' | ' + colors['reset'] + colors['val'] + str(val.pop(0)) + colors['reset'], terminal=True)
for value in val:
logger.info(' ' * maxlength + colors['border'] + ' | ' + colors['reset'] + colors['val'] + str(value) + colors['reset'], terminal=True)
else:
logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'], terminal=True)
logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'], terminal=True)
except Exception as e:
logger.error('Failed to generate statistics table. ' + str(e), error = e, timestamp = False, terminal=True)
def show_details():
details = {
'Node Address' : gethostname.get_hostname(),
'Public Key' : onionrcrypto.pub_key.replace('=', ''),
'Human-readable Public Key' : mnemonickeys.get_human_readable_ID()
}
for detail in details:
logger.info('%s%s: \n%s%s\n' % (logger.colors.fg.lightgreen, detail, logger.colors.fg.green, details[detail]), terminal = True)
show_details.onionr_help = "Shows relevant information for your Onionr install: node address, and active public key."
show_stats.onionr_help = "Shows statistics for your Onionr node. Slow if Onionr is not running"

View file

@ -0,0 +1,48 @@
'''
Onionr - Private P2P Communication
Open the web interface properly into a web browser
'''
'''
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/>.
'''
import webbrowser
import logger
from onionrutils import getclientapiserver
import config
def get_url():
try:
url = getclientapiserver.get_client_API_server()
except FileNotFoundError:
url = ""
logger.error('Onionr seems to not be running (could not get api host)', terminal=True)
else:
url = 'http://%s/#%s' % (url, config.get('client.webpassword'))
logger.info('Onionr web interface URL: ' + url, terminal=True)
return url
get_url.onionr_help = "Shows the Onionr web interface URL with API key"
def open_home():
try:
url = getclientapiserver.get_client_API_server()
except FileNotFoundError:
logger.error('Onionr seems to not be running (could not get api host)', terminal=True)
else:
url = 'http://%s/#%s' % (url, config.get('client.webpassword'))
logger.info('If Onionr does not open automatically, use this URL: ' + url, terminal=True)
webbrowser.open_new_tab(url)
open_home.onionr_help = "Opens the Onionr web UI in the default browser. Node must be running."

View file

@ -0,0 +1,109 @@
'''
Onionr - Private P2P Communication
This module loads in the Onionr arguments and their help messages
'''
'''
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/>.
'''
import sys
import os
from etc import onionrvalues
import logger, onionrexceptions
import onionrplugins
from onionrplugins import onionrpluginapi
from . import arguments, recommend
plugin_command = lambda cmd: 'on_%s_cmd' % (cmd,)
def register_plugin_commands(cmd)->bool:
plugin_cmd = plugin_command(cmd)
for pl in onionrplugins.get_enabled_plugins():
pl = onionrplugins.get_plugin(pl)
if hasattr(pl, plugin_cmd):
getattr(pl, plugin_cmd)(onionrpluginapi.PluginAPI)
return True
return False
def register():
"""Registers commands and handles help command processing"""
def get_help_message(cmd: str, default: str = 'No help available for this command'):
"""Return help message for a given command, supports plugin commands"""
pl_cmd = plugin_command(cmd)
for pl in onionrplugins.get_enabled_plugins():
pl = onionrplugins.get_plugin(pl)
if hasattr(pl, pl_cmd):
try:
return getattr(pl, pl_cmd).onionr_help
except AttributeError:
pass
for i in arguments.get_arguments():
for alias in i:
try:
return arguments.get_help(cmd)
except AttributeError:
pass
return default # Return the help string
PROGRAM_NAME = "onionr"
# Get the command
try:
cmd = sys.argv[1]
except IndexError:
logger.debug("Detected Onionr run with no commands specified")
return
is_help_cmd = False
if cmd.replace('--', '').lower() == 'help': is_help_cmd = True
try:
try:
if not cmd in ('start', 'details', 'show-details') : os.chdir(os.environ['ORIG_ONIONR_RUN_DIR'])
except KeyError: pass
try:
arguments.get_func(cmd)()
except KeyboardInterrupt: pass
except onionrexceptions.NotFound:
if not register_plugin_commands(cmd) and not is_help_cmd:
recommend.recommend()
sys.exit(3)
if is_help_cmd:
try:
sys.argv[2]
except IndexError:
for i in arguments.get_arguments():
logger.info('%s <%s>: %s' % (PROGRAM_NAME, '/'.join(i), get_help_message(i[0])), terminal=True)
for pl in onionrplugins.get_enabled_plugins():
pl = onionrplugins.get_plugin(pl)
if hasattr(pl, 'ONIONR_COMMANDS'):
print('')
try:
logger.info('%s commands:' % (pl.plugin_name,), terminal=True)
except AttributeError:
logger.info('%s commands:' % (pl.__name__,), terminal=True)
for plugin_cmd in pl.ONIONR_COMMANDS:
logger.info('%s %s: %s' % (PROGRAM_NAME, plugin_cmd, get_help_message(plugin_cmd)), terminal=True)
print('')
else:
try:
logger.info('%s %s: %s' % (PROGRAM_NAME, sys.argv[2], get_help_message(sys.argv[2])), terminal=True)
except KeyError:
logger.error('%s: command does not exist.' % [sys.argv[2]], terminal=True)
sys.exit(3)
return

View file

@ -0,0 +1,88 @@
'''
Onionr - Private P2P Communication
Sets CLI arguments for Onionr
'''
'''
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/>.
'''
from typing import Callable
from .. import onionrstatistics, version, daemonlaunch, keyadders, openwebinterface
from .. import banblocks # Command to blacklist a block by its hash
from .. import filecommands # commands to share files with onionr
from .. import exportblocks # commands to export blocks
from .. import pubkeymanager # commands to add or change id
from .. import resettor # commands to reset the tor data directory or transport keypair
from .. import resetplugins # command to reinstall default plugins
from .. import softreset # command to delete onionr blocks
from .. import restartonionr # command to restart Onionr
from .. import runtimetestcmd
from .. import motdcreator
from .. import sitecreator
import onionrexceptions
from onionrutils import importnewblocks # func to import new blocks
from onionrplugins import onionrevents as events
def get_arguments()->dict:
"""This is a function because we need to be able to dynamically modify them with plugins"""
args = {
('blacklist', 'blacklist-block', 'remove-block', 'removeblock', 'banblock', 'ban-block'): banblocks.ban_block,
('details', 'info'): onionrstatistics.show_details,
('stats', 'statistics'): onionrstatistics.show_stats,
('version',): version.version,
('start', 'daemon'): daemonlaunch.start,
('stop', 'kill'): daemonlaunch.kill_daemon,
('restart',): restartonionr.restart,
('add-address', 'addaddress', 'addadder'): keyadders.add_address,
('openhome', 'gui', 'openweb', 'open-home', 'open-web'): openwebinterface.open_home,
('get-url', 'url', 'get-web'): openwebinterface.get_url,
('addhtml', 'add-html'): filecommands.add_html,
('addsite', 'add-site', 'update-site', 'updatesite'): sitecreator.create_multipage_site,
('addfile', 'add-file'): filecommands.add_file,
('get-file', 'getfile'): filecommands.get_file,
('export-block', 'exportblock'): exportblocks.export_block,
('importblocks', 'import-blocks', 'import-block'): importnewblocks.import_new_blocks,
('addid', 'add-id'): pubkeymanager.add_ID,
('changeid', 'change-id'): pubkeymanager.change_ID,
('add-vanity', 'addvanity'): pubkeymanager.add_vanity,
('resettor', 'reset-tor'): resettor.reset_tor,
('resetplugins', 'reset-plugins'): resetplugins.reset,
('reset-tor-node-transport',): resettor.reset_tor_key_pair,
('soft-reset', 'softreset'): softreset.soft_reset,
('runtime-test', 'runtimetest'): runtimetestcmd.do_runtime_test,
('makemotd', 'make-motd'): motdcreator.motd_creator
}
return args
def get_help(arg: str) -> str:
"""Returns the help info string from a given command"""
arguments = get_arguments()
# Iterate the command alias tuples
for argument in arguments:
# Return the help message if its found in a command alias tuple
if arg in argument: return arguments[argument].onionr_help
raise KeyError
def get_func(argument: str) -> Callable:
"""Returns the function for a given command 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

View file

@ -0,0 +1,15 @@
import sys
from difflib import SequenceMatcher
import logger
from . import arguments
def recommend(print_default: bool = True):
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
if print_default: logger.error('%s "%s"' % (print_message, tried), terminal=True)

View file

@ -0,0 +1,119 @@
'''
Onionr - Private P2P Communication
This module defines user ID-related CLI commands
'''
'''
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/>.
'''
import sys, getpass
import unpaddedbase32
import niceware
import vanityonionr
import logger, onionrexceptions
from onionrutils import stringvalidators, bytesconverter
from onionrusers import onionrusers, contactmanager
import config
from coredb import keydb
import keymanager, onionrcrypto
from etc import onionrvalues
DETERMINISTIC_REQUIREMENT = onionrvalues.PASSWORD_LENGTH
def add_ID():
key_manager = keymanager.KeyManager()
try:
sys.argv[2]
if not sys.argv[2].lower() == 'true': raise ValueError
except (IndexError, ValueError) as e:
newID = key_manager.addKey()[0]
else:
logger.warn('Deterministic keys require random and long passphrases.', terminal=True)
logger.warn('If a good passphrase is not used, your key can be easily stolen.', terminal=True)
logger.warn('You should use a series of hard to guess words, see this for reference: https://www.xkcd.com/936/', terminal=True)
try:
pass1 = getpass.getpass(prompt='Enter at least %s characters: ' % (DETERMINISTIC_REQUIREMENT,))
pass2 = getpass.getpass(prompt='Confirm entry: ')
except KeyboardInterrupt:
sys.exit(42)
if onionrcrypto.cryptoutils.safe_compare(pass1, pass2):
try:
logger.info('Generating deterministic key. This can take a while.', terminal=True)
newID, privKey = onionrcrypto.generate_deterministic(pass1)
except onionrexceptions.PasswordStrengthError:
logger.error('Passphrase must use at least %s characters.' % (DETERMINISTIC_REQUIREMENT,), terminal=True)
sys.exit(1)
else:
logger.error('Passwords do not match.', terminal=True)
sys.exit(1)
try:
key_manager.addKey(pubKey=newID,
privKey=privKey)
except ValueError:
logger.error('That ID is already available, you can change to it with the change-id command.', terminal=True)
return
logger.info('Added ID: %s' % (bytesconverter.bytes_to_str(newID),), terminal=True)
add_ID.onionr_help = "If the first argument is true, Onionr will show a deterministic generation prompt. Otherwise it will generate & save a new random key pair."
def change_ID():
key_manager = keymanager.KeyManager()
try:
key = sys.argv[2]
key = unpaddedbase32.repad(key.encode()).decode()
except IndexError:
logger.warn('Specify pubkey to use', terminal=True)
else:
if stringvalidators.validate_pub_key(key):
key_list = key_manager.getPubkeyList()
if key in key_list or key.replace('=', '') in key_list:
config.set('general.public_key', key)
config.save()
logger.info('Set active key to: %s' % (key,), terminal=True)
logger.info('Restart Onionr if it is running.', terminal=True)
else:
logger.warn('That key does not exist', terminal=True)
else:
logger.warn('Invalid key %s' % (key,), terminal=True)
change_ID.onionr_help = "<pubkey>: Switches Onionr to use a different user ID key. You should immediately restart Onionr if it is running."
def add_vanity():
key_manager = keymanager.KeyManager()
tell = lambda tell: logger.info(tell, terminal=True)
words = ''
length = len(sys.argv) - 2
if length == 0: return
for i in range(2, len(sys.argv)):
words += ' '
words += sys.argv[i]
try:
if length == 1:
tell('Finding vanity, this should only take a few moments.')
else:
tell('Finding vanity, this will probably take a really long time.')
try:
vanity = vanityonionr.find_multiprocess(words)
except ValueError:
logger.warn('Vanity words must be valid english bip39', terminal=True)
else:
b32_pub = unpaddedbase32.b32encode(vanity[0])
tell('Found vanity address:\n' + niceware.bytes_to_passphrase(vanity[0]))
tell('Base32 Public key: %s' % (b32_pub.decode(),))
key_manager.addKey(b32_pub, unpaddedbase32.b32encode(vanity[1]))
except KeyboardInterrupt:
pass
add_vanity.onionr_help = "<space separated words> - Generates and stores an Onionr vanity address (see https://github.com/moreati/python-niceware/blob/master/niceware/wordlist.py)"

View file

@ -0,0 +1,37 @@
'''
Onionr - Private P2P Communication
Reset default plugins from source
'''
'''
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/>.
'''
import os
import shutil
from utils import identifyhome
from onionrsetup import setup_default_plugins
import logger
def reset():
"""Reinstalls Onionr default plugins"""
home = identifyhome.identify_home()
plugin_dir = home + '/plugins/'
if not os.path.exists(home): return
if os.path.exists(plugin_dir): shutil.rmtree(plugin_dir)
setup_default_plugins()
logger.info('Default plugins have been reset.', terminal=True)
reset.onionr_help = "reinstalls default Onionr plugins (e.g. mail). Should be done after git pulls or plugin modification."

42
src/onionrcommands/resettor.py Executable file
View file

@ -0,0 +1,42 @@
'''
Onionr - Private P2P Communication
Command to delete the Tor data directory if its safe to do so
'''
'''
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/>.
'''
import os, shutil
import logger
from onionrutils import localcommand
from utils import identifyhome
def __delete(directory):
tor_dir = '%s/%s/' % (identifyhome.identify_home(), directory)
if os.path.exists(tor_dir):
if localcommand.local_command('/ping') == 'pong!':
logger.warn('Cannot delete Tor data while Onionr is running', terminal=True)
else:
shutil.rmtree(tor_dir)
logger.info('Tor reset', terminal=True)
def reset_tor():
__delete('tordata')
reset_tor.onionr_help = "Deletes Onionr's Tor data directory. Only do this as a last resort if you have serious Tor issues."
def reset_tor_key_pair():
__delete('hs')
reset_tor_key_pair.onionr_help = "Delete's your Tor node address permanently. Note that through fingerprinting attackers may be able to know that your new generated node address belongs to the same node as the deleted one."

View file

@ -0,0 +1,56 @@
"""
Onionr - Private P2P Communication
Command to restart Onionr
"""
"""
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/>.
"""
import time
import os
import subprocess
import platform
from etc import onionrvalues
from etc import cleanup
from onionrutils import localcommand
import logger
import filepaths
from . import daemonlaunch
SCRIPT_NAME = os.path.dirname(os.path.realpath(__file__)) + f'/../../{onionrvalues.SCRIPT_NAME}'
def restart():
logger.info('Restarting Onionr', terminal=True)
# On platforms where we can, fork out to prevent locking
try:
pid = os.fork()
if pid != 0: return
except (AttributeError, OSError) as e:
if platform.platform() != 'Windows':
logger.warn('Could not fork on restart')
daemonlaunch.kill_daemon()
while localcommand.local_command('ping', maxWait=8) == 'pong!':
time.sleep(0.3)
time.sleep(15)
while os.path.exists(filepaths.private_API_host_file) or os.path.exists(filepaths.daemon_mark_file):
time.sleep(1)
cleanup.delete_run_files()
subprocess.Popen([SCRIPT_NAME, 'start'])
restart.onionr_help = 'Gracefully restart Onionr'

View file

@ -0,0 +1,6 @@
from coredb import daemonqueue
def do_runtime_test():
daemonqueue.daemon_queue_add("runtimeTest")
do_runtime_test.onionr_help = "If Onionr is running, initialize run time tests (check logs)"

View file

@ -0,0 +1,38 @@
import sys
import getpass
from httpapi import onionrsitesapi
import onionrexceptions
import logger
from etc import onionrvalues
def create_multipage_site():
error_encountered = False
try:
directory = sys.argv[2]
except IndexError:
directory = '.'
try:
passphrase = sys.argv[3]
except IndexError:
logger.warn('''It is critical that this passphrase is long.
If you want to update your site later you must remember the passphrase.''', terminal=True)
passphrase = getpass.getpass(f'Please enter a site passphrase of at least {onionrvalues.PASSWORD_LENGTH} characters.')
confirm = getpass.getpass('Confirm passphrase:')
if passphrase != confirm:
logger.error('Passphrases do not match', terminal=True)
error_encountered = True
if len(passphrase) < onionrvalues.PASSWORD_LENGTH:
error_encountered = True
logger.error(f'Passphrase must be at least {onionrvalues.PASSWORD_LENGTH} characters.', terminal=True)
if error_encountered:
sys.exit(1)
results = onionrsitesapi.sitefiles.create_site(passphrase, directory=directory)
results = (results[0].replace('=', ''), results[1])
logger.info(f'Site address {results[0]}', terminal=True)
logger.info(f'Block for this version {results[1]}', terminal=True)
create_multipage_site.onionr_help = "[directory path (default relative)] - packages a whole directory and makes it available as an Onionr site."

View file

@ -0,0 +1,46 @@
"""
Onionr - Private P2P Communication
Command to soft-reset Onionr (deletes blocks)
"""
"""
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/>.
"""
import os
import shutil
from onionrutils import localcommand
from coredb import dbfiles
import filepaths
from onionrplugins import onionrevents
import logger
def _ignore_not_found_delete(path):
try:
os.remove(path)
except FileNotFoundError:
pass
def soft_reset():
if localcommand.local_command('/ping') == 'pong!':
logger.warn('Cannot soft reset while Onionr is running', terminal=True)
return
path = filepaths.block_data_location
shutil.rmtree(path)
_ignore_not_found_delete(dbfiles.block_meta_db)
_ignore_not_found_delete(filepaths.upload_list)
onionrevents.event('softreset')
logger.info("Soft reset Onionr", terminal=True)
soft_reset.onionr_help = "Deletes Onionr blocks and their associated metadata, except for any exported block files. Does NOT delete data on other nodes in the network."

View file

@ -0,0 +1,21 @@
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:
pf = platform.platform()
release = platform.release()
python_imp = platform.python_implementation()
python_version = platform.python_version()
function(f'{python_imp} {python_version} on {pf} {release}', terminal=True)
function('Onionr data dir: %s' % identifyhome.identify_home(), terminal=True)
version.onionr_help = 'Shows environment details including Onionr version & data directory, OS and Python version'