diff --git a/.gitignore b/.gitignore index a25196b..22c1bd0 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,7 @@ theonionbox/theonionbox.ltd # Special to The Onion Box # Glide Library comes with a 'dist' directory that we need! !theonionbox/libs/glide-*/dist/ +/node_modules/ + +# Rapydscript +*.pyj-cached \ No newline at end of file diff --git a/LICENSE b/LICENSE index ab450ac..fafc7ad 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 - 2019 Ralph Wetzel +Copyright (c) 2015 - 2020 Ralph Wetzel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/cc/ControlCenter/dialogs/about.pyj b/cc/ControlCenter/dialogs/about.pyj index aa3ae9e..8ba79bb 100644 --- a/cc/ControlCenter/dialogs/about.pyj +++ b/cc/ControlCenter/dialogs/about.pyj @@ -53,7 +53,7 @@ class About(DialogBase): $.post({ 'url': self.base_path + "/" + self.session_id + "/cc/" + "about" - , 'timeout': 2000 + , 'timeout': 5000 }) .done(def(data): $('#about').removeClass('text-center').addClass('text-left') diff --git a/cc/ControlCenter/dialogs/launcher.pyj b/cc/ControlCenter/dialogs/launcher.pyj index 0f2abb8..304b341 100644 --- a/cc/ControlCenter/dialogs/launcher.pyj +++ b/cc/ControlCenter/dialogs/launcher.pyj @@ -27,7 +27,7 @@ class Launcher(DialogBase): diff --git a/setup.py b/setup.py index a54ac86..3ffb20f 100644 --- a/setup.py +++ b/setup.py @@ -302,14 +302,18 @@ def generate_data_files(data_files): } package_data = { - 'theonionbox': ['config/*', - 'css/*', - 'libs/*', - 'pages/*', - 'scripts/*', - 'sections/*', - 'tor/*' - ] + 'theonionbox': [ + 'config/*', + 'css/*', + 'libs/*', + 'pages/*', + 'scripts/*', + 'sections/*', + 'tor/*' + ], + 'theonionbox.tob.system.windows': [ + 'uptime/*' + ] } package_data_exclude = { @@ -326,13 +330,13 @@ def generate_data_files(data_files): ('service/FreeBSD', ['FreeBSD/theonionbox.sh']), ('service/init.d', ['init.d/theonionbox.sh']), ('service/systemd', ['systemd/theonionbox.service']), - ('service/Docker', ['Docker/Dockerfile', 'Docker/theonionbox.cfg']), - ('support', []), - ('support/osxtemp', []), - ('support/osxtemp/libsmc', ['support/osxtemp/libsmc/LICENSE', 'support/osxtemp/libsmc/Makefile']), - ('support/osxtemp/libsmc/include', ['support/osxtemp/libsmc/include/smc.h']), - ('support/osxtemp/libsmc/src', ['support/osxtemp/libsmc/src/smc.c']), - ('theonionbox/tob/system/windows/uptime', ['theonionbox/tob/system/windows/uptime/*.*']), + ('service/Docker', ['Docker/Dockerfile', 'Docker/theonionbox.cfg']) + # , ('support', []) + # , ('support/osxtemp', []) + # , ('support/osxtemp/libsmc', ['support/osxtemp/libsmc/LICENSE', 'support/osxtemp/libsmc/Makefile']) + # , ('support/osxtemp/libsmc/include', ['support/osxtemp/libsmc/include/smc.h']) + # , ('support/osxtemp/libsmc/src', ['support/osxtemp/libsmc/src/smc.c']) + # , ('theonionbox/tob/system/windows/uptime', ['theonionbox/tob/system/windows/uptime/*.*']), ] # print(generate_data_files(data_files)) @@ -439,6 +443,6 @@ def generate_data_files(data_files): 'Topic :: System :: Networking :: Monitoring', 'Topic :: Utilities', ], - platforms=['Linux', 'Windows', 'MacOS X', 'FreeBSD'], + platforms=['Linux', 'Windows', 'MacOSX', 'FreeBSD'], # ext_modules=extensions(), ) diff --git a/theonionbox/__main__.py b/theonionbox/__main__.py index cf50a7c..78d580c 100644 --- a/theonionbox/__main__.py +++ b/theonionbox/__main__.py @@ -1,29 +1,31 @@ #!/usr/bin/env python -from __future__ import absolute_import -import sys, os +import os +import pathlib +import site +import sys def main(): - # the import executes the scripting part... - if __name__ == '__main__': - # we can't use relative imports here!! - # get an absolute path to the directory that contains mypackage - this_dir = os.path.dirname(os.path.join(os.getcwd(), __file__)) - sys.path.append(os.path.normpath(os.path.join(this_dir, '..'))) + if __name__ == '__main__' and __package__ in ['', None]: - from theonionbox import main as onion_main + # Being __main__, we need to add the current dir to the site-dirs, to allow ABSOLUTE import + # We resolve this Path, as __file__ might be relative, if __name__ == __main__. + cp = pathlib.Path(__file__).resolve() + cp = cp.parent + assert cp.exists() + # Add the current dir to the site-dirs, to allow ABSOLUTE import + site.addsitedir(cp) + from theonionbox import main as onion_main else: + # we're in a package => RELATIVE should work. from .theonionbox import main as onion_main - # ... and main() launches the server! + # The scripting part of theonionbox is being executed as we 'import'. + # Now we launch the server: onion_main() if __name__ == '__main__': - - # args = sys.argv[1:] main() - - diff --git a/theonionbox/css/box.css b/theonionbox/css/box.css index 1644492..f091cb6 100644 --- a/theonionbox/css/box.css +++ b/theonionbox/css/box.css @@ -139,7 +139,9 @@ body { <% from bottle import TemplateError import logging + css_log = logging.getLogger('theonionbox') + for section in sections: special = section[0] diff --git a/theonionbox/pages/cc.html b/theonionbox/pages/cc.html index 4784c69..0ffd1f6 100644 --- a/theonionbox/pages/cc.html +++ b/theonionbox/pages/cc.html @@ -1,7 +1,12 @@ <% - from tob.template_tools import * + try: + from tob.template_tools import * + except ModuleNotFoundError: + from theonionbox.tob.template_tools import * + end + base_path = get('virtual_basepath', '') + '/' %> diff --git a/theonionbox/pages/index.html b/theonionbox/pages/index.html index 8571057..6cd0034 100644 --- a/theonionbox/pages/index.html +++ b/theonionbox/pages/index.html @@ -1,7 +1,11 @@ <% - from tob.template_tools import * + try: + from tob.template_tools import * + except ModuleNotFoundError: + from theonionbox.tob.template_tools import * + end base_path = get('virtual_basepath', '') + '/' session = get('session') @@ -42,6 +46,7 @@ import logging bL = logging.getLogger('theonionbox') div_open = False + for section in sections: if section == '-': @@ -69,16 +74,12 @@ file = 'sections/{0}/{0}.html'.format(section) - if os.path.exists(file): - try: - include(file) - except Exception as exc: - import sys, traceback - exc_type, exc_val, exc_tb = sys.exc_info() - bL.warning("While including '{}' into 'index_page.html':\n{}".format(file, traceback.format_exc())) - end - else: - bL.debug("While including '{0}' into 'index_page.html': '{0}' does not exist!".format(file)) + try: + include(file) + except Exception as exc: + import logging + bL = logging.getLogger('theonionbox') + bL.debug("While including '{}' into 'index_page.html': {}".format(file, exc)) end end out = ('<' + '/div>') if div_open else '' diff --git a/theonionbox/scripts/box.js b/theonionbox/scripts/box.js index f2dc357..8b30da6 100644 --- a/theonionbox/scripts/box.js +++ b/theonionbox/scripts/box.js @@ -7,7 +7,12 @@ <% base_path = get('virtual_basepath', '') + '/' sections = get('sections', []) - from tob.template_tools import * + + try: + from tob.template_tools import * + except ModuleNotFoundError: + from theonionbox.tob.template_tools import * + end login = get('box.js_login', False) token = get('token', 'ThisIsAnError') diff --git a/theonionbox/scripts/cc.js b/theonionbox/scripts/cc.js index 9956c0e..69b5345 100644 --- a/theonionbox/scripts/cc.js +++ b/theonionbox/scripts/cc.js @@ -8072,7 +8072,7 @@ return this.__repr__(); Launcher.prototype.create = function create() { var self = this; var html; - html = "\n
\n
\n
\n
\n
\n
\n
\n
\n {{stamp.__title__}} {{stamp.__version__}}\n
\n
\n {{stamp.__description__}}\n
\n
\n
\n
\n Copyright © 2015 - 2019 Ralph Wetzel\n
\n
\n
\n
\n
\n "; + html = "\n
\n
\n
\n
\n
\n
\n
\n
\n {{stamp.__title__}} {{stamp.__version__}}\n
\n
\n {{stamp.__description__}}\n
\n
\n
\n
\n Copyright © 2015 - 2020 Ralph Wetzel\n
\n
\n
\n
\n
\n "; DialogBase.prototype.create.call(self, html); }; Launcher.prototype.show = function show() { @@ -8222,7 +8222,7 @@ return this.__repr__(); $.post((function(){ var ρσ_d = {}; ρσ_d["url"] = self.base_path + "/" + self.session_id + "/cc/" + "about"; - ρσ_d["timeout"] = 2e3; + ρσ_d["timeout"] = 5e3; return ρσ_d; }).call(this)).done((function() { var ρσ_anonfunc = function (data) { diff --git a/theonionbox/sections/accounting/accounting.html b/theonionbox/sections/accounting/accounting.html index 0cddee6..fdf8a45 100644 --- a/theonionbox/sections/accounting/accounting.html +++ b/theonionbox/sections/accounting/accounting.html @@ -3,7 +3,11 @@ %# used for the scripting part of TheOnionBox! <% - from tob.template_tools import * + try: + from tob.template_tools import * + except ModuleNotFoundError: + from theonionbox.tob.template_tools import * + end #tor = get('tor', None) #accounting_on = tor.get_isAccountingEnabled() if tor else False diff --git a/theonionbox/sections/config/config.html b/theonionbox/sections/config/config.html index 938d87e..5d1ac68 100644 --- a/theonionbox/sections/config/config.html +++ b/theonionbox/sections/config/config.html @@ -5,12 +5,13 @@ # used for the scripting part of TheOnionBox! import os +import psutil -from tob.configcollector import ConfigCollector -cfgcoll = ConfigCollector(tor) +# from tob.configcollector import ConfigCollector +# cfgcoll = ConfigCollector(tor) -configs_used = [] +configs_used = get('configs_used', []) configs_all = [] try: @@ -56,7 +57,6 @@ {{!header_row('Tor', 'Configuration', 'config')}} <% - import psutil cmd_line = [] if tor.is_localhost(): @@ -138,7 +138,7 @@ <% - configs_used = cfgcoll.collect_configs_used() + # configs_used = cfgcoll.collect_configs_used() manpg = get('manpage', None) diff --git a/theonionbox/sections/license/license.html b/theonionbox/sections/license/license.html index a3b93d3..3e395fe 100644 --- a/theonionbox/sections/license/license.html +++ b/theonionbox/sections/license/license.html @@ -3,7 +3,12 @@ # It is intended to be a bottlepy - style template # used for the scripting part of TheOnionBox! - from tob.license import License + try: + from tob.license import License + except ModuleNotFoundError: + from theonionbox.tob.license import License + end + license = License() %>
@@ -12,7 +17,7 @@
The Onion Box | - Version v{{box_stamp}} | Copyright © 2015 - 2019 Ralph Wetzel | License: + Version v{{box_stamp}} | Copyright © 2015 - 2020 Ralph Wetzel | License:
@@ -60,7 +65,12 @@
{{!header_row('', license.get('header'))}} - {{!standard_row('', license.get('copyright'))}} +
+
+
+ {{!license.get('copyright')}} +
+
diff --git a/theonionbox/sections/monitor/monitor.js b/theonionbox/sections/monitor/monitor.js index 66cc96b..5242e7c 100644 --- a/theonionbox/sections/monitor/monitor.js +++ b/theonionbox/sections/monitor/monitor.js @@ -1,4 +1,11 @@ -% from tob.livedata import intervals +<% + try: + from tob.livedata import intervals + except ModuleNotFoundError: + from theonionbox.tob.livedata import intervals + end +%> + var monitor_intervals = {{!intervals}}; var monitor_keys = ['1s', '1m', '5m', '1h', '4h', 'Ch', 'm6', 'y1', 'y5']; diff --git a/theonionbox/sections/network/network.html b/theonionbox/sections/network/network.html index 728299c..e07de72 100644 --- a/theonionbox/sections/network/network.html +++ b/theonionbox/sections/network/network.html @@ -2,9 +2,15 @@ %# It is intended to be a bottlepy - style template %# used for the scripting part of TheOnionBox! -% from tob.log import sanitize_for_html - <% + + try: + from tob.log import sanitize_for_html + except ModuleNotFoundError: + from theonionbox.tob.log import sanitize_for_html + end + + tor = get('tor') if tor is None else tor oo_details = get('oo_details') if oo_details is None else oo_details oo_bw = get('oo_bw') diff --git a/theonionbox/sections/network_bandwidth/network_bandwidth.js b/theonionbox/sections/network_bandwidth/network_bandwidth.js index 5f16881..23718e0 100644 --- a/theonionbox/sections/network_bandwidth/network_bandwidth.js +++ b/theonionbox/sections/network_bandwidth/network_bandwidth.js @@ -1,4 +1,11 @@ -% from tob.livedata import intervals +<% + try: + from tob.livedata import intervals + except ModuleNotFoundError: + from theonionbox.tob.livedata import intervals + end +%> + var history_intervals = {{!intervals}}; var history_keys = ['1s', '1m', '5m', '1h', '4h', 'Ch', 'm6', 'y1', 'y5']; diff --git a/theonionbox/stamp.py b/theonionbox/stamp.py index fd5f792..5691f2d 100644 --- a/theonionbox/stamp.py +++ b/theonionbox/stamp.py @@ -1,4 +1,4 @@ __title__ = 'The Onion Box' __description__ = 'Dashboard to monitor Tor node operations.' -__version__ = '19.3' -__stamp__ = '20191229|232641' \ No newline at end of file +__version__ = '20.1' +__stamp__ = '20200105|230447' diff --git a/theonionbox/theonionbox.py b/theonionbox/theonionbox.py index 2739f04..cc90079 100644 --- a/theonionbox/theonionbox.py +++ b/theonionbox/theonionbox.py @@ -1,8 +1,11 @@ +#!/usr/bin/env python import click -import functools import configupdater +import functools import os +import pathlib import re +import site import sys # theonionbox.py -d --config filepath box --host 0.0.0.0 --port 8080 ... tor --host 0.0.0.0 --port ... proxy --host: ... @@ -89,42 +92,8 @@ def decorator(f): version=stamped_version, message='%(prog)s\nVersion %(version)s') @click.pass_context def main(ctx, debug, trace, config, cc, log): - - # We do all the stuff here to prepare the environment to run a Box - - ##### - # Common imports - - import os - import sys - - ##### - # Script directory detection - import inspect - - def get_script_dir(follow_symlinks=True): - if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze - path = os.path.abspath(sys.executable) - else: - path = inspect.getabsfile(get_script_dir) - if follow_symlinks: - path = os.path.realpath(path) - return os.path.dirname(path) - - # to ensure that our directory structure works as expected! - os.chdir(get_script_dir()) - - ##### - # TODO 20190812: Really necessary? - - import site - - for (dirpath, dirnames, filenames) in os.walk(get_script_dir()): - init_path = os.path.join(dirpath, '__init__.py') - if os.path.isfile(init_path): - site.addsitedir(dirpath) - # The Box will be launched by launcher() ... after all the subcommands returned. + pass @main.resultcallback() @@ -159,9 +128,25 @@ def launcher(results, debug, trace, config, cc, log): params['cc'] = cc params['log'] = log - from tob.box import Box - theonionbox = Box(params) - theonionbox.run() + # Next We do all the stuff to prepare the environment to run a Box + + # Provide the demanded CurrentWorkingDirectory ... without truely changing to it via os.chdir !! + # We resolve this Path, as __file__ might be relative, if __name__ == __main__. + cwd = pathlib.Path(__file__).resolve() + cwd = cwd.parent + assert cwd.exists() + params['cwd'] = cwd + + if __name__ == '__main__' or __package__ in [None, '']: + # Add the current dir to the site-dirs, to allow ABSOLUTE import + site.addsitedir(cwd) + from tob.box import Box + else: + # we're in a package => RELATIVE should work. + from .tob.box import Box + + tob = Box(params) + tob.run() @main.command('box', short_help='Options to configure your Onion Box.') @@ -246,8 +231,10 @@ def box(ctx, host, port, message_level, base_path, session_ttl, ssl_key, ssl_cer help='Local ControlSocket of the Tor node.') @click.option('--auth_cookie', 'cookie', default=None, metavar='COOKIE', show_default=True, help='Cookie necessary to support HiddenServiceAuthorizeClient.') +@click.option('--password', 'password', default=None, metavar='PASSWORD', show_default=True, + help='Password, necessary if this Tor node is guarded with a HashedControlPassword.') @click.pass_context -def tor(ctx, control, host, port, socket, cookie): +def tor(ctx, control, host, port, socket, cookie, password): """Settings to configure the connection to the Tor node to be monitored.""" if control in ['port', 'proxy']: @@ -272,6 +259,7 @@ def tor(ctx, control, host, port, socket, cookie): 'port': port, 'socket': socket, 'cookie': cookie, + 'password': password, 'label': None, # some additional properties, demanded by the cc 'connect': True }} @@ -312,8 +300,5 @@ def proxy(ctx, control, host, port, socket, proxy): }} -# if __name__ == '__main__': - # main(box_debug) -# main() - -# __all__ = ['main'] +if __name__ == '__main__': + main() diff --git a/theonionbox/tob/apps/base.py b/theonionbox/tob/apps/base.py index fd5a9b7..11f02a1 100644 --- a/theonionbox/tob/apps/base.py +++ b/theonionbox/tob/apps/base.py @@ -1,15 +1,15 @@ -import logging import contextlib +import logging from threading import RLock # from bottle import Bottle, HTTPError, redirect import bottle -from tob.session import SessionManager, Session, make_short_id -from tob.nodes import Manager as NodesManager -from tob.proxy import Proxy -from tob.utils import AttributedDict -from tob.version import VersionManager +from ..nodes import Manager as NodesManager +from ..proxy import Proxy +from ..session import SessionManager, Session, make_short_id +from ..utils import AttributedDict +from ..version import VersionManager class BaseApp: @@ -62,7 +62,7 @@ def path(self, url: str = '/'): ##### # Time Management # - from tob.deviation import getTimer + from ..deviation import getTimer self.time = getTimer() diff --git a/theonionbox/tob/apps/controlcenter.py b/theonionbox/tob/apps/controlcenter.py index a70666c..845d93d 100644 --- a/theonionbox/tob/apps/controlcenter.py +++ b/theonionbox/tob/apps/controlcenter.py @@ -1,21 +1,29 @@ import contextlib import json import logging +import pathlib import time import uuid import bottle -from tob.apps import BaseApp -from tob.ccfile import CCFile -from tob.nodes import Manager as NodesManager, AlreadyRegisteredError, Node, NotConnectedError -from tob.plugin.session import SessionPlugin -from tob.proxy import Proxy as TorProxy -from tob.session import SessionManager, Session -from tob.utils import AttributedDict -from tob.version import VersionManager -import stamp - +from ..apps import BaseApp +from ..ccfile import CCFile +from ..nodes import Manager as NodesManager, AlreadyRegisteredError, Node, NotConnectedError +from ..plugin.session import SessionPlugin +from ..proxy import Proxy as TorProxy +from ..session import SessionManager, Session +from ..utils import AttributedDict +from ..version import VersionManager + +# __package__ is either 'theonionbox.tob.apps' or 'tob.apps' +# If there're more than two levels, we try to import RELATIVEly. +# If it's only two level, we try ABSOLUTEly. +p = __package__.split('.') +if len(p) > 2: + from ... import stamp +else: + import stamp class CCError(bottle.HTTPError): @@ -46,6 +54,7 @@ def __init__(self self.cc = CCFile(self.config.cc) self.fingerprints = {} self.show_logout = None + self.cwd = pathlib.Path(self.config['cwd']) config = { 'no_session_redirect': self.redirect.path('/'), @@ -231,10 +240,11 @@ def get_cc(self, session): 'icon': self.icon, 'stamp': stamp, 'launcher': 1 if 'cards' not in session else 0 + , 'template_lookup': [str(self.cwd)] } - session['cc.js'] = bottle.template("scripts/cc.js", **params) - session['cc.css'] = bottle.template("css/cc.css", **params) + session['cc.js'] = bottle.template('scripts/cc.js', **params) + session['cc.css'] = bottle.template('css/cc.css', **params) return bottle.template('pages/cc.html', **params) @@ -288,16 +298,16 @@ def verify_port(port): try: if connect == 'port': - from tob.simplecontroller import SimplePort + from ..simplecontroller import SimplePort p = verify_port(port) sc = SimplePort(host, p) elif connect == 'socket': - from tob.simplecontroller import SimpleSocket + from ..simplecontroller import SimpleSocket sc = SimpleSocket(host) elif connect == 'proxy': - from tob.simplecontroller import SimpleProxy + from ..simplecontroller import SimpleProxy p = verify_port(port) if cookie and len(cookie) > 0: self.proxy.assure_cookie(host, cookie) @@ -449,8 +459,6 @@ def post_remove_node(self, session): def post_cc_ping(self, session): - import traceback - headers = { 'Last-Modified': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(self.cc.last_modified)) , 'Date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) @@ -744,8 +752,7 @@ def post_cc_connect(self, session): def post_cc_login(self, session): - from tob.authenticate import authenticate - import traceback + from ..authenticate import authenticate # print("post_cc_login") @@ -825,7 +832,7 @@ def post_position(self, session): # When the operator changes the position of a card via D & D, # it sends the session id of the card located before itself in the DOM tree. - from tob.ccfile import CCNode + from ..ccfile import CCNode before = bottle.request.forms.get('position', None) if before is None: @@ -867,7 +874,7 @@ def post_cc_edit(self, session): def post_cc_license(self, session): - from tob.license import License + from ..license import License l = License() @@ -926,7 +933,7 @@ def post_cc_about(self, session):
The Onion Box - | Copyright © 2015 - 2019 Ralph Wetzel | License: + | Copyright © 2015 - 2020 Ralph Wetzel | License: MIT
diff --git a/theonionbox/tob/apps/dashboard.py b/theonionbox/tob/apps/dashboard.py index 2abf4dc..b04396c 100644 --- a/theonionbox/tob/apps/dashboard.py +++ b/theonionbox/tob/apps/dashboard.py @@ -1,22 +1,22 @@ import contextlib -import uuid import json - +import pathlib from threading import Timer from typing import Optional +import uuid from bottle import Bottle, HTTPError, request, template, static_file -from tob.apps import BaseApp -from tob.geoip import GeoIPOO -from tob.nodes import Manager as NodesManager, Node -from tob.onionoo import getOnionoo -from tob.plugin import SessionPlugin -from tob.proxy import Proxy -from tob.session import SessionManager, Session, make_short_id -from tob.system import BaseSystem -from tob.utils import AttributedDict -from tob.version import VersionManager +from ..apps import BaseApp +from ..geoip import GeoIPOO +from ..nodes import Manager as NodesManager, Node +from ..onionoo import getOnionoo +from ..plugin import SessionPlugin +from ..proxy import Proxy +from ..session import SessionManager, Session, make_short_id +from ..system import BaseSystem +from ..utils import AttributedDict +from ..version import VersionManager # from tob.scheduler import Scheduler class Dashboard(BaseApp): @@ -34,6 +34,7 @@ def __init__(self config=config) self.system = system + self.cwd = pathlib.Path(self.config['cwd']) # ##### # # GeoIP2 interface @@ -44,8 +45,8 @@ def __init__(self ##### # TOR manpage Index Information - from tob.manpage import ManPage - self.manpage = ManPage('tor/tor.1.ndx') + from ..manpage import ManPage + self.manpage = ManPage(str(self.cwd / 'tor' / 'tor.1.ndx')) ##### # Page Construction @@ -147,6 +148,12 @@ def get_start(self): if session is None: raise HTTPError(404) + # This allows to login via a command line provided password (to address HashedControlPassword authmethod) + pwd = self.nodes['theonionbox'].config.password + if pwd is not None: + session['password'] = pwd + session['logout:show'] = False + return self.connect_session_to_node(session, 'theonionbox', proceed_to_page=self.default_page) def connect_session_to_node(self, session: Session, node_id: str, proceed_to_page: Optional[str] = None): @@ -199,6 +206,8 @@ def create_error_page(self, session: Session, display_error: BaseException): , 'section_config': section_config , 'error_msg': display_error , 'box.js_login': True # flag to manipulate the creation process of 'box.js' + , 'template_lookup': [str(self.cwd)] # search path for the template engine... + # , 'tools': template_tools.__name__ } # prepare the includes @@ -207,7 +216,7 @@ def create_error_page(self, session: Session, display_error: BaseException): # session['fonts.css'] = template('css/latolatinfonts.css', **params) # deliver the error page - return template("pages/index.html", **params) + return template('pages/index.html', **params) def create_login_page(self, session: Session, node: Node, proceed_to_page: str): @@ -244,6 +253,7 @@ def create_login_page(self, session: Session, node: Node, proceed_to_page: str): , 'section_config': section_config , 'proceed_to': proceed_to_page , 'box.js_login': True # flag to manipulate the creation process of 'box.js' + , 'template_lookup': [str(self.cwd)] } # prepare the includes @@ -255,16 +265,20 @@ def create_login_page(self, session: Session, node: Node, proceed_to_page: str): session['auth.js'] = template('scripts/authrequest_basic.js' , virtual_basepath=self.config.box.base_path , proceed_to = proceed_to_page - , session_id=session.id) + , session_id=session.id + , template_lookup=[str(self.cwd)] + ) else: # e.g. if login['auth'] == 'digest' session['auth.js'] = template('scripts/authrequest_digest.js' , virtual_basepath=self.config.box.base_path , proceed_to = proceed_to_page - , session_id=session.id) + , session_id=session.id + , template_lookup=[str(self.cwd)] + ) session['scripts'].append('md5.js') # deliver the login page - return template("pages/index.html", **params) + return template('pages/index.html', **params) def get_restart(self, session_id): @@ -311,7 +325,7 @@ def get_restart(self, session_id): def perform_login(self, login_id): - from tob.authenticate import authenticate + from ..authenticate import authenticate self.log.debug(f'Login Request: {make_short_id(login_id)}@{request.remote_addr} / {request.remote_route}') @@ -428,7 +442,7 @@ def get_index(self, session): # node.torLogMgr.add_client(session.id) # prepare the preserved events for hardcoded transfer - from tob.log import sanitize_for_html + from ..log import sanitize_for_html # p_ev = node.torLogMgr.get_events(session.id, encode=sanitize_for_html) p_ev = [] @@ -537,9 +551,15 @@ def get_index(self, session): except: at_location = 'Remote Location' + if session['logout:show'] is not None: + # 20200102: Currently only used if password for default node provided via command line. + show_logout = session['logout:show'] + else: + show_logout = session['status'] != 'auto' or session['password'] is not None + section_config = {} section_config['header'] = { - 'logout': session['status'] != 'auto' or session['password'] is not None, + 'logout': show_logout, 'title': tor.nickname, 'subtitle': f"Tor {tor.version_short} @ {at_location}
{fingerprint}", 'powered': f"monitored by The Onion Box v{self.config.stamped_version}" @@ -565,6 +585,12 @@ def get_lines(info): "7S62k1ELjuc7HcXKuzrkRG+aq1iT5Py+04sporrvvCPg8gRtSRxBY9qBgJcjqEviCBrTjn8Zfz6qPw4XX/o4HUH8jr5kANX2" \ "pkGbPBkHgK6fBmCantjx+5EL5oVDnqsHL/DYhRMxwWIAAAAASUVORK5CYII=" + # Preparation for the Configuration display + # 20200104: This used to be part of config.html - yet moved here due to (relative) import issues + from ..configcollector import ConfigCollector + cfgcoll = ConfigCollector(tor) + configs_used = cfgcoll.collect_configs_used() + # params initialized before for onionoo data params.update({ 'session': session @@ -596,7 +622,9 @@ def get_lines(info): # , 'controlled_nodes': box_cc , 'transport_status': transport , 'token': session['token'] - + , 'cwd': str(self.cwd) + , 'configs_used': configs_used + , 'template_lookup': [str(self.cwd)] }) # Test @@ -614,7 +642,7 @@ def get_lines(info): # session['fonts.css'] = template('css/latolatinfonts.css', **params) # create the dashboard - index = template("pages/index.html", **params) + index = template('pages/index.html', **params) # re-ping the session - to prevent accidential timeout self.sessions.get_session(session.id, request) @@ -718,7 +746,7 @@ def post_data(self, session): 'level': key, 'status': False}).start() - from tob.log import sanitize_for_html + from ..log import sanitize_for_html log_list = node.logs.get_events(session_id, encode=sanitize_for_html) if log_list and len(log_list) > 0: @@ -736,7 +764,7 @@ def post_data(self, session): # operations monitoring if 'monitor' in box_sections: - from tob.livedata import intervals + from ..livedata import intervals return_data_dict['mon'] = {} last_ts = None @@ -921,4 +949,4 @@ def get_logout(self, session_id): self.redirect('/') def get_manpage(self, session): - return static_file('tor.1.html', root='tor', mimetype='text/html') \ No newline at end of file + return static_file('tor.1.html', root=str(self.cwd / 'tor'), mimetype='text/html') \ No newline at end of file diff --git a/theonionbox/tob/authenticate.py b/theonionbox/tob/authenticate.py index 11cc238..ab4f07a 100644 --- a/theonionbox/tob/authenticate.py +++ b/theonionbox/tob/authenticate.py @@ -1,12 +1,12 @@ -from typing import Optional -from tob.session import Session -from bottle import HTTPError from base64 import b64decode from hashlib import md5 -from stem.connection import IncorrectPassword -from tob.nodes.node import Node from uuid import uuid1, uuid4 +from bottle import HTTPError + +from .nodes.node import Node +from .session import Session + def authenticate(session: Session, node: Node, header: str, method: str = "GET"): diff --git a/theonionbox/tob/box.py b/theonionbox/tob/box.py index a939d38..8714776 100644 --- a/theonionbox/tob/box.py +++ b/theonionbox/tob/box.py @@ -4,14 +4,26 @@ import inspect inspect.getargspec = inspect.getfullargspec +import logging import os +from pathlib import Path import sys -import stamp -import utils - import bottle +# __package__ is either 'theonionbox.tob' or 'tob' +# If there're more than one levels, we try to import RELATIVEly. +# If it's only one level, we try ABSOLUTEly. +p = __package__.split('.') +if len(p) > 1: + from .. import stamp +else: + import stamp + +from . import log +from .system import get_system_manager +from . import utils + ##### # Python version detection py = sys.version_info @@ -23,25 +35,24 @@ class Box: - def __init__(self, config): + def __init__(self, config: dict): self.config = utils.AttributedDict(config) self.config['stamped_version'] = stamped_version ##### # Host System data - from tob.system import get_system_manager + # from ..system import get_system_manager self.system = get_system_manager() ##### # Logging System: Part 1 - import logging - import tob.log + self.log = logging.getLogger('theonionbox') # We will Filter everything through the GlobalFilter # NOTSET + 1 just ensures that there IS a LEVEL set, even if we don't rely on it! self.log.setLevel(logging.NOTSET + 1) - boxLogGF = tob.log.getGlobalFilter() + boxLogGF = log.getGlobalFilter() boxLogGF.setLevel('NOTICE') self.log.addFilter(boxLogGF) @@ -51,11 +62,11 @@ def __init__(self, config): boxLogHandler = logging.StreamHandler(sys.stdout) if os.getenv('PYCHARM_RUNNING_TOB', None) == '1': - boxLogHandler.setFormatter(tob.log.PyCharmFormatter()) + boxLogHandler.setFormatter(log.PyCharmFormatter()) elif sys.stdout.isatty(): - boxLogHandler.setFormatter(tob.log.ConsoleFormatter()) + boxLogHandler.setFormatter(log.ConsoleFormatter()) else: - boxLogHandler.setFormatter(tob.log.LogFormatter()) + boxLogHandler.setFormatter(log.LogFormatter()) self.log.addHandler(boxLogHandler) @@ -66,7 +77,6 @@ def __init__(self, config): if self.config.log is not None: from logging.handlers import TimedRotatingFileHandler - from tob.log import FileFormatter boxLogPath = self.config.log @@ -77,7 +87,7 @@ def __init__(self, config): except Exception as exc: warn.append(f'Failed to create LogFile handler: {exc}') else: - boxLogFileHandler.setFormatter(FileFormatter()) + boxLogFileHandler.setFormatter(log.FileFormatter()) self.log.addHandler(boxLogFileHandler) else: warn.append(f"Failed to establish LogFile handler for directory '{self.config['log']}'.") @@ -130,7 +140,7 @@ def __init__(self, config): # Part 3 -> add_hook for PATH_INFO (around line 320) # stem: Forward DEBUG - stemFwrd = tob.log.ForwardHandler(level=logging_level(Runlevel.DEBUG), tag='stem') + stemFwrd = log.ForwardHandler(level=logging_level(Runlevel.DEBUG), tag='stem') elif self.config.debug: boxLogGF.setLevel('DEBUG') @@ -139,13 +149,13 @@ def __init__(self, config): bottle.debug(True) # stem: Forward NOTICE - stemFwrd = tob.log.ForwardHandler(level=logging_level(Runlevel.NOTICE), tag='stem') + stemFwrd = log.ForwardHandler(level=logging_level(Runlevel.NOTICE), tag='stem') else: bottle.debug(False) # stem: Forward WARNING - stemFwrd = tob.log.ForwardHandler(level=logging_level(Runlevel.WARN), tag='stem') + stemFwrd = log.ForwardHandler(level=logging_level(Runlevel.WARN), tag='stem') stemLog.addHandler(stemFwrd) stemFwrd.setTarget(self.log) @@ -153,18 +163,18 @@ def __init__(self, config): ##### # Data persistance management # ... has to be setup here to prepare for self.nodes - from tob.persistor import Storage + from .persistor import Storage self.storage = Storage(self.config.box.persistance_dir, self.system.user) ##### # SOCKS Proxy definition - from tob.proxy import Proxy - from tob.config import ProxyConfig + from .proxy import Proxy + from .config import ProxyConfig self.proxy = Proxy(ProxyConfig(self.config.proxy)) ##### # The Onionoo Interface - from tob.onionoo import getOnionoo + from .onionoo import getOnionoo # This creates __OnionooManager__ OnionooManager = getOnionoo() @@ -173,8 +183,8 @@ def __init__(self, config): ##### # Management of Nodes - from tob.nodes import Manager as NodesManager - from tob.config import DefaultNodeConfig + from .nodes import Manager as NodesManager + from .config import DefaultNodeConfig self.nodes = NodesManager(DefaultNodeConfig(self.config.tor), database=self.storage) # ##### @@ -188,17 +198,17 @@ def __init__(self, config): self.log.warning("Usage of a GeoIP2 City database demands availability of Python module 'geoip2'.") else: if os.path.exists(self.config.box.geoip2_city): - from tob.geoip import GeoIP2 + from .geoip import GeoIP2 self.geoip2 = GeoIP2(self.config.box.geoip2_city) self.log.notice("Operating with GeoIP Database '{}'.".format(self.config.box.geoip2_city)) if self.geoip2 is None: - from tob.geoip import GeoIPOO + from .geoip import GeoIPOO self.geoip2 = GeoIPOO() ##### # Our cron - from tob.scheduler import Scheduler + from .scheduler import Scheduler self.cron = Scheduler() # Check proper setting of Timezone @@ -216,7 +226,7 @@ def __init__(self, config): ##### # Time Management # - from tob.deviation import getTimer + from .deviation import getTimer self.time = getTimer(self.config.box.ntp_server or self.system.ntp) @@ -238,7 +248,7 @@ def update_time_deviation(): ##### # The Onion Box Version Service - from tob.version import VersionManager + from .version import VersionManager from datetime import datetime def check_version(checker, relaunch_job=False): @@ -272,7 +282,7 @@ def check_version(checker, relaunch_job=False): ##### # SESSION Management # from tob.session import SessionFactory, make_short_id - from tob.session import SessionManager, make_short_id + from .session import SessionManager, make_short_id # This function is called when a session is deleted. # We use it to ensure that the nodes for this session are closed properly! @@ -297,7 +307,7 @@ def del_session_callback(session_id): # Almost done; lets compose the app... # Single node dashboard = default application - from tob.apps import Dashboard + from .apps import Dashboard theonionbox = Dashboard(sessions=self.sessions, nodes=self.nodes, proxy=self.proxy, @@ -310,7 +320,7 @@ def del_session_callback(session_id): # Controlcenter if self.config.cc is not None: - from tob.apps import ControlCenter + from .apps import ControlCenter try: cc = ControlCenter(self.sessions, self.nodes, @@ -337,42 +347,42 @@ def debug_request(): # Static files - which are many meanwhile - from tob.static import SessionFileProvider - from pathlib import Path - boxLibsPath = 'libs' + from .static import SessionFileProvider + + boxLibsPath = Path(self.config['cwd']) / 'libs' - libs = SessionFileProvider(self.sessions, Path(boxLibsPath) / 'jquery-3.4.1' / 'jquery-3.4.1.min.js', '/jquery.js') + libs = SessionFileProvider(self.sessions, boxLibsPath / 'jquery-3.4.1' / 'jquery-3.4.1.min.js', '/jquery.js') - libs.add(Path(boxLibsPath) / 'bootstrap-4.3.1' / 'js' / 'bootstrap.bundle.min.js', '/bootstrap.js') - libs.add(Path(boxLibsPath) / 'bootstrap-4.3.1' / 'css' / 'bootstrap.min.css', '/bootstrap.css') + libs.add(boxLibsPath / 'bootstrap-4.3.1' / 'js' / 'bootstrap.bundle.min.js', '/bootstrap.js') + libs.add(boxLibsPath / 'bootstrap-4.3.1' / 'css' / 'bootstrap.min.css', '/bootstrap.css') - libs.add(Path(boxLibsPath) / 'glide-3.4.1' / 'dist' / 'glide.js', '/glide.js') - libs.add(Path(boxLibsPath) / 'glide-3.4.1' / 'dist' / 'css' / 'glide.core.css', '/glide.core.css') - libs.add(Path(boxLibsPath) / 'glide-3.4.1' / 'dist' / 'css' / 'glide.theme.css', '/glide.theme.css') + libs.add(boxLibsPath / 'glide-3.4.1' / 'dist' / 'glide.js', '/glide.js') + libs.add(boxLibsPath / 'glide-3.4.1' / 'dist' / 'css' / 'glide.core.css', '/glide.core.css') + libs.add(boxLibsPath / 'glide-3.4.1' / 'dist' / 'css' / 'glide.theme.css', '/glide.theme.css') - libs.add(Path(boxLibsPath) / 'scrollMonitor-1.2.4' / 'scrollMonitor.js', '/scrollMonitor.js') + libs.add(boxLibsPath / 'scrollMonitor-1.2.4' / 'scrollMonitor.js', '/scrollMonitor.js') - libs.add(Path(boxLibsPath) / 'smoothie-1.36' / 'smoothie.js', '/smoothie.js') + libs.add(boxLibsPath / 'smoothie-1.36' / 'smoothie.js', '/smoothie.js') - # libs.add(Path(boxLibsPath) / 'underscore-1.9.1' / 'underscore-min.js', '/underscore.js') + # libs.add(boxLibsPath / 'underscore-1.9.1' / 'underscore-min.js', '/underscore.js') - libs.add(Path(boxLibsPath) / 'js-md5-0.7.3' / 'md5.js', '/md5.js') + libs.add(boxLibsPath / 'js-md5-0.7.3' / 'md5.js', '/md5.js') - libs.add(Path(boxLibsPath) / 'jquery.pep-0.6.10' / 'jquery.pep.js', '/pep.js') + libs.add(boxLibsPath / 'jquery.pep-0.6.10' / 'jquery.pep.js', '/pep.js') - # libs.add(Path(boxLibsPath) / 'toggle-3.5.0' / 'js' / 'bootstrap4-toggle.min.js', '/toggle.js') - # libs.add(Path(boxLibsPath) / 'toggle-3.5.0' / 'css' / 'bootstrap4-toggle.min.css', '/toggle.css') + # libs.add(boxLibsPath / 'toggle-3.5.0' / 'js' / 'bootstrap4-toggle.min.js', '/toggle.js') + # libs.add(boxLibsPath / 'toggle-3.5.0' / 'css' / 'bootstrap4-toggle.min.css', '/toggle.css') - # libs.add(Path(boxLibsPath) / 'HackTimer-20181012' / 'HackTimer.js', '/hacktimer.js') + # libs.add(boxLibsPath / 'HackTimer-20181012' / 'HackTimer.js', '/hacktimer.js') # This is not really a library ... but the scheme works here as well. - libs.add(Path('scripts') / 'chart.js', 'chart.js') + libs.add(Path(self.config['cwd']) / 'scripts' / 'chart.js', 'chart.js') theonionbox.merge(libs) # Those are static files as well - yet created by a template run & stored into the session object - from tob.static import TemplateFileProvider + from .static import TemplateFileProvider templt = TemplateFileProvider(self.sessions, 'box.js', '/box.js') templt.add('box.css', '/box.css') @@ -383,13 +393,13 @@ def debug_request(): theonionbox.merge(templt) # LatoLatin - from tob.libraries import LatoLatin + from .libraries import LatoLatin libLatoLatin = LatoLatin(self.sessions, os.path.join(boxLibsPath, 'LatoLatin'), valid_status=['ok', 'auto', 'error', 'login', 'frame']) theonionbox.merge(libLatoLatin) # Fontawesome - from tob.libraries import FontAwesome + from .libraries import FontAwesome libFontAwesome = FontAwesome(self.sessions, os.path.join(boxLibsPath, 'fontawesome-free-5.11.2-web'), valid_status='frame') theonionbox.merge(libFontAwesome) @@ -430,7 +440,7 @@ def shutdown(self): def shutdown(self): - from tob.onionoo import getOnionoo + from .onionoo import getOnionoo self.log.propagate = False diff --git a/theonionbox/tob/ccfile.py b/theonionbox/tob/ccfile.py index 2e9950c..c39e20c 100644 --- a/theonionbox/tob/ccfile.py +++ b/theonionbox/tob/ccfile.py @@ -1,11 +1,11 @@ from typing import Optional, Union + import contextlib -from configupdater import ConfigUpdater, DuplicateSectionError -from configupdater.configupdater import Section -import uuid import time +import uuid -from tob.config import DefaultNodeConfig +from configupdater import ConfigUpdater, DuplicateSectionError +from configupdater.configupdater import Section def validate(section: Section, file=''): diff --git a/theonionbox/tob/config.py b/theonionbox/tob/config.py index 98d5e94..6ea864f 100644 --- a/theonionbox/tob/config.py +++ b/theonionbox/tob/config.py @@ -1,5 +1,5 @@ -import typing -from utils import AttributedDict + +from .utils import AttributedDict class NodeConfig(AttributedDict): diff --git a/theonionbox/tob/configcollector.py b/theonionbox/tob/configcollector.py index d5bb3d9..870f891 100644 --- a/theonionbox/tob/configcollector.py +++ b/theonionbox/tob/configcollector.py @@ -1,11 +1,11 @@ -from __future__ import absolute_import +# from __future__ import absolute_import import logging import sys -from manpage import ManPage +from .manpage import ManPage # file based special message filtering -from tob.log import getGlobalFilter +from .log import getGlobalFilter # getGlobalFilter().set_level_for_file(__file__, 'TRACE') ##### diff --git a/theonionbox/tob/connections.py b/theonionbox/tob/connections.py deleted file mode 100644 index 76e54f7..0000000 --- a/theonionbox/tob/connections.py +++ /dev/null @@ -1,16 +0,0 @@ -class connections(object): - - def __init__(self): - self.initialize() - - def initialize(self, status=None): - if status is None: - self.connections = 0 - else: - try: - lines = status.splitlines(keepends=False) - self.connections = len(lines) - except: - return False - - return True diff --git a/theonionbox/tob/connections.pyi b/theonionbox/tob/connections.pyi deleted file mode 100644 index dcc9977..0000000 --- a/theonionbox/tob/connections.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from typing import Optional, List - -class connections(object): - ... - def initialize(self, status: Optional[str] = None) -> bool: - ... \ No newline at end of file diff --git a/theonionbox/tob/deviation.py b/theonionbox/tob/deviation.py index cdd1425..cf2c376 100644 --- a/theonionbox/tob/deviation.py +++ b/theonionbox/tob/deviation.py @@ -1,9 +1,6 @@ -# from __future__ import absolute_import - -from socket import AF_INET, SOCK_DGRAM +import socket import struct from time import time -import socket # class TimeManager(object): @@ -107,7 +104,7 @@ def update_time_deviation(self): try: # connect to server - client = socket.socket(AF_INET, SOCK_DGRAM) + client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) except: return False diff --git a/theonionbox/tob/ini.py b/theonionbox/tob/ini.py deleted file mode 100644 index 7221525..0000000 --- a/theonionbox/tob/ini.py +++ /dev/null @@ -1,76 +0,0 @@ -##### -# Custom convenience interface to ConfigParser (python2) & configparser (python3) -# Intension is to eliminate the need to import 3rd party modules with further dependencies - -### Check for Python2 or Python 3 -import sys -py = sys.version_info -py30 = py >= (3, 0, 0) - -if py30: - from configparser import ConfigParser as ConfParser, DuplicateSectionError, NoOptionError -else: - from ConfigParser import RawConfigParser as ConfParser, DuplicateSectionError, NoOptionError - -__all__ = ['INIParser', 'DuplicateSectionError'] - - -class INIParser(object): - - class INISection(object): - - def __init__(self, parser, section): - self.parser = parser - self.section = section - - def key(self): - return self.section - - def getint(self, option, default=None): - try: - retval = self.parser.getint(self.section, option) - except (ValueError, KeyError, NoOptionError) as e: - retval = default - return retval - - def getboolean(self, option, default=None): - try: - retval = self.parser.getboolean(self.section, option) - except (ValueError, KeyError, NoOptionError) as e: - retval = default - return retval - - def get(self, option, default=None): - try: - retval = self.parser.get(self.section, option) - except (ValueError, KeyError, NoOptionError) as e: - retval = default - return retval - - def __init__(self, filename): - - self.cp = ConfParser() - self.file = filename - # This may raise an exception! - self.cp.read(filenames=filename) - - def filename(self): - return self.file - - def sections(self): - if self.cp is not None: - secs = self.cp.sections() - retval = [] - for sec in secs: - retval.append(self.INISection(self.cp, sec)) - return retval - - def keys(self): - if self.cp is not None: - return self.cp.sections() - - def __call__(self, key): - if key not in self.keys(): - return KeyError - - return self.INISection(self.cp, key) \ No newline at end of file diff --git a/theonionbox/tob/libraries/fontawesome.py b/theonionbox/tob/libraries/fontawesome.py index 03d3dd2..0fb2af8 100644 --- a/theonionbox/tob/libraries/fontawesome.py +++ b/theonionbox/tob/libraries/fontawesome.py @@ -1,9 +1,10 @@ -from typing import Optional -from bottle import Bottle, static_file, HTTPError -from plugin.session import SessionPlugin -from tob.session import SessionManager import os.path +from bottle import Bottle, static_file, HTTPError + +from ..plugin.session import SessionPlugin +from ..session import SessionManager + class FontAwesome(Bottle): diff --git a/theonionbox/tob/libraries/latolatin.py b/theonionbox/tob/libraries/latolatin.py index 236f9d1..a8512e7 100644 --- a/theonionbox/tob/libraries/latolatin.py +++ b/theonionbox/tob/libraries/latolatin.py @@ -1,9 +1,10 @@ -from typing import Optional -from bottle import Bottle, static_file, HTTPError -from plugin.session import SessionPlugin -from tob.session import SessionManager import os.path +from bottle import Bottle, static_file, HTTPError + +from ..plugin.session import SessionPlugin +from ..session import SessionManager + class LatoLatin(Bottle): diff --git a/theonionbox/tob/license.py b/theonionbox/tob/license.py index 6fe6581..51b681c 100644 --- a/theonionbox/tob/license.py +++ b/theonionbox/tob/license.py @@ -2,7 +2,7 @@ class License: license = { 'header': 'The MIT License (MIT)' - , 'copyright': 'Copyright © 2015 - 2019 Ralph Wetzel' + , 'copyright': 'Copyright © 2015 - 2020 Ralph Wetzel' , '1': """Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/theonionbox/tob/livedata/__init__.py b/theonionbox/tob/livedata/__init__.py index 3229e1d..eb1ca74 100644 --- a/theonionbox/tob/livedata/__init__.py +++ b/theonionbox/tob/livedata/__init__.py @@ -1,18 +1,15 @@ -from time import time from collections import deque -# from math import floor import itertools -# from tob_time import TimeManager -from threading import RLock -from tob.deviation import getTimer import logging -from .recorder import Recorder -from persistor import BandwidthPersistor - from sqlite3 import Row, Connection +from time import time +from threading import RLock from typing import Optional, List, Dict -import math +from ..deviation import getTimer +from ..persistor import BandwidthPersistor + +from .recorder import Recorder # class tob_list(list): # diff --git a/theonionbox/tob/livedata/recorder.py b/theonionbox/tob/livedata/recorder.py index 0075685..372fb90 100644 --- a/theonionbox/tob/livedata/recorder.py +++ b/theonionbox/tob/livedata/recorder.py @@ -1,8 +1,9 @@ from typing import Optional, Dict -from tob.deviation import getTimer + from math import floor from time import time +from ..deviation import getTimer class Recorder(object): diff --git a/theonionbox/tob/log.py b/theonionbox/tob/log.py index f56e328..5dc300b 100644 --- a/theonionbox/tob/log.py +++ b/theonionbox/tob/log.py @@ -1,17 +1,18 @@ from typing import Dict -# from __future__ import absolute_import -from time import time, strftime, mktime, localtime + from collections import deque -import uuid +import functools import logging from logging.handlers import MemoryHandler -import functools +from logging import Filter, _checkLevel +import os.path import sys -from threading import RLock +from time import time, strftime, mktime, localtime +import uuid + from stem import ProtocolError -from tob.deviation import getTimer -import os.path +from .deviation import getTimer normalize_level = {'DEBUG': 'DEBUG', 'INFO': 'INFO', @@ -722,7 +723,7 @@ def format(self, record): # add propper line breaks for nice output: try: - from terminalsize import get_terminal_size + from .terminalsize import get_terminal_size # Try to get the size of the terminal sx, sy = get_terminal_size() @@ -825,10 +826,6 @@ def sanitize_for_html(string): return out - -from logging import Filter, _checkLevel - - class FileBasedFilter(logging.Filter): def __init__(self, name='', level='NOTICE'): diff --git a/theonionbox/tob/manpage.py b/theonionbox/tob/manpage.py index e3e69c4..681a95a 100644 --- a/theonionbox/tob/manpage.py +++ b/theonionbox/tob/manpage.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import json import logging diff --git a/theonionbox/tob/nodes/manager.py b/theonionbox/tob/nodes/manager.py index 365d163..fe3954b 100644 --- a/theonionbox/tob/nodes/manager.py +++ b/theonionbox/tob/nodes/manager.py @@ -1,10 +1,12 @@ from typing import Optional, List -# from configuration import BaseNodeConfig, DefaultNodeConfig -from .node import Node -from persistor import Storage + import time -from tob.config import DefaultNodeConfig -from ccfile import CCNode + +from ..ccfile import CCNode +from ..config import DefaultNodeConfig +from ..persistor import Storage + +from .node import Node class Manager(object): diff --git a/theonionbox/tob/nodes/node.py b/theonionbox/tob/nodes/node.py index 0b0d69f..92198b6 100644 --- a/theonionbox/tob/nodes/node.py +++ b/theonionbox/tob/nodes/node.py @@ -1,25 +1,24 @@ -# from configuration import BaseNodeConfig from typing import Optional, Union -from stam.control import Controller, EventType -# from .worker import Worker -from persistor import Storage, BandwidthPersistor -import stem.response + +import contextlib +import functools import logging -from tob.log import LoggingManager, ForwardHandler -from tob.scheduler import Scheduler, SchedulerNotRunningError -import livedata from time import time, strftime -import functools -from tob.proxy import Proxy as TorProxy +import uuid -from tob.onionoo import getOnionoo, OnionooData +import stem.response -import tob.transportation -import contextlib +from ..ccfile import CCNode +from ..config import DefaultNodeConfig +from .. import livedata +from ..log import LoggingManager, ForwardHandler +from ..onionoo import getOnionoo, OnionooData +from ..persistor import Storage, BandwidthPersistor +from ..proxy import Proxy as TorProxy +from ..scheduler import Scheduler, SchedulerNotRunningError +from ..stam.control import Controller, EventType +from .. import transportation -from tob.config import DefaultNodeConfig -from tob.ccfile import CCNode -import uuid # Node # - Config @@ -64,9 +63,9 @@ def __init__(self, config: Union[DefaultNodeConfig, CCNode], database: Storage, # self.password = None - self.connections = tob.transportation.Connections() - self.circuits = tob.transportation.Circuits() - self.streams = tob.transportation.Streams() + self.connections = transportation.Connections() + self.circuits = transportation.Circuits() + self.streams = transportation.Streams() self._label = None diff --git a/theonionbox/tob/onionoo.py b/theonionbox/tob/onionoo.py index 36290c9..68f865a 100644 --- a/theonionbox/tob/onionoo.py +++ b/theonionbox/tob/onionoo.py @@ -1,20 +1,14 @@ -from __future__ import absolute_import -import contextlib - -import requests -from hashlib import sha1 from binascii import a2b_hex -from time import strptime, time, gmtime -from datetime import datetime from calendar import timegm +import contextlib +import hashlib import logging -import sys -from threading import RLock, Semaphore from math import log10, floor +import requests +import sys +from time import strptime, time, gmtime -from concurrent.futures import ThreadPoolExecutor as ThreadPoolExecutor_CF - -from tob.proxy import Proxy +from .proxy import Proxy ##### # Python version detection @@ -532,328 +526,6 @@ def consensus_weight(self, period=None): return None return self._document.get_chart('consensus_weight', period) - -class OnionOOFactory(object): - - # onionoo holds a dict of key:data pairs, with - # key = 'details' or 'bandwidth' or 'weights' + ':' + fingerprint - # data = onionoo.Document object holding the onionoo network response or None - onionoo = {} - executor = None - query_lock = None - nodes_lock = None - # proxy = Proxy('127.0.0.1', 'default') - futures = list() - - def __init__(self, proxy): - - self.onionoo = {} - self.proxy = proxy - - self.executor = ThreadPoolExecutor_CF(max_workers=30) # enough to query > 100 Tors at once... - self.query_lock = RLock() - self.nodes_lock = RLock() - self.hidden_index = 1 - self.is_refreshing = False - self.new_nodes = {} - - - def add(self, fingerprint): - - # there are currently three different documents we query from the onionoo db: - # Details, Bandwidth & Weights - # the key identifies the fingerprint as well as the document to allow storage in a flat dict. - check_key = ['details:' + fingerprint, 'bandwidth:' + fingerprint, 'weights:' + fingerprint] - - retval = False - - # if the key in question isn't in the dict - for key in check_key: - if key not in self.onionoo: - lgr = logging.getLogger('theonionbox') - lgr.debug('Adding fingerprint {} to onionoo query queue.'.format((fingerprint))) - - # ... add it (yet without document! This indicates that we have no data so far.) - self.nodes_lock.acquire() - self.new_nodes[key] = Document() - self.nodes_lock.release() - - retval = True - - return retval - - def remove(self, fingerprint): - - # to remove keys if demanded (which probably will happen rarely!) - check_key = [ 'details:' + fingerprint, 'bandwidth:' + fingerprint, 'weights:' + fingerprint] - - for key in check_key: - if key in self.onionoo: - del self.onionoo[key] - - if key in self.new_nodes: - self.nodes_lock.acquire() - del self.new_nodes[key] - self.nodes_lock.release() - - def refresh(self, only_keys_with_none_data=False, async_mode=True): - - self.is_refreshing = True - - lgr = logging.getLogger('theonionbox') - lgr.info('Refreshing onionoo data => Only New: {} | Async: {}'.format(only_keys_with_none_data, async_mode)) - - self.nodes_lock.acquire() - - self.onionoo.update(self.new_nodes) - self.new_nodes = {} - - self.nodes_lock.release() - - # run through the dict of keys and query onionoo for updated documents - for key in self.onionoo: - item = self.onionoo[key] - - if only_keys_with_none_data is True: - if item.has_document() is True: - continue - - try: - data_type, fp = key.split(':') - except ValueError: - # This definitely is weird! - continue - - # async_mode = True - - query_launched = False - - if async_mode is True: - try: - self.executor.submit(self.query, item, fp, data_type) - query_launched = True - except: - # In case of error, we try to continue in sync mode! - lgr.warning("Onionoo: Failed to launch thread to query for Tor network data.") - pass - - if query_launched is False: - try: - self.query(item, fp, data_type) - except: - # Ok. We silently swallow this... - pass - - # restart if meanwhile the landscape changed - if len(self.new_nodes) > 0: - self.refresh(True) - - self.is_refreshing = False - - def query(self, for_document, fingerprint, data_type): - - lgr = logging.getLogger('theonionbox') - - # https://trac.torproject.org/projects/tor/ticket/6320 - hash = sha1(a2b_hex(fingerprint)).hexdigest() - payload = {'lookup': hash} - - headers = {'accept-encoding': 'gzip'} - if len(for_document.ifModSince) > 0: - headers['if-modified-since'] = for_document.ifModSince - - proxy_address = self.proxy.address() - - if proxy_address is None: - proxies = {} - query_base = ONIONOO_OPEN - else: - proxies = { - 'http': 'socks5h://' + proxy_address, - 'https': 'socks5h://' + proxy_address - } - query_base = ONIONOO_HIDDEN[self.hidden_index] - - - # query_base = ONIONOO_OPEN - - query_address = query_base + '/' + data_type - - r = None - - # even when querying async, there's just one query performed at a time - # self.query_lock.acquire() - - lgr.debug("Onionoo: Launching query of '{}' for ${}.".format(query_address, fingerprint)) - - try: - r = requests.get(query_address, params=payload, headers=headers, proxies=proxies, timeout=10) - except requests.exceptions.ConnectTimeout: - lgr.info("Onionoo: Failed querying '{}' due to connection timeout. Switching to alternative service." - .format(query_base)) - # this is quite manual ... but asserts the right result - base_index = ONIONOO_HIDDEN.index(query_base) - self.hidden_index = (base_index + 1) % len(ONIONOO_HIDDEN) - # TODO: shall we restart the failed query here? - except Exception as exc: - lgr.warning("Onionoo: Failed querying '{}' -> {}".format(query_address, exc)) - else: - lgr.debug("Onionoo: Finished querying '{}' for ${} with status code {}: {} chars received." - .format(query_address, fingerprint, r.status_code, len(r.text))) - # if len(r.text) > 0: - # lgr.debug(("Onionoo: Received {} chars for ${}".format(len(r.text), fingerprint))) - - # Ok! Now the next query may be launched... - # self.query_lock.release() - - if r is None: - return - - for_document.ifModSince = r.headers['last-modified'] - if r.status_code == requests.codes.not_modified: - return - - if r.status_code != requests.codes.ok: - return - - # print(r) - - try: - data = r.json() - except Exception as exc: - lgr.debug("Onionoo: Failed to un-json network data; error code says '{}'.".format(exc)) - return - - # print(data) - - for_document.update(data) - - # ToDo: Where's the benefit doing it this way? - # - # if data_type == 'details': - # node_details = Details(for_document) - # - # do_refresh = False - # - # #try: - # fams = ['effective_family', 'alleged_family', 'indirect_family'] - # for fam in fams: - # fam_data = node_details(fam) - # if fam_data is not None: - # for fp in fam_data: - # if fp[0] is '$': - # do_refresh = self.add(fp[1:]) or do_refresh - # #except: - # # This probably wasn't a 'detail' document! - # # pass - # - # if do_refresh and not self.is_refreshing: - # self.refresh(True) - - return - # except Exception as exc: - # lgr.info("Onionoo: Failed to query '{}': {}".format(address, exc)) - # pass - - #return - - def details(self, fingerprint): - if len(fingerprint)> 0 and fingerprint[0] == '$': - fingerprint = fingerprint[1:] - key = 'details:' + fingerprint - if key in self.onionoo: - return Details(self.onionoo[key]) - return Details(Document()) - - - def bandwidth(self, fingerprint): - if len(fingerprint)> 0 and fingerprint[0] == '$': - fingerprint = fingerprint[1:] - key = 'bandwidth:' + fingerprint - if key in self.onionoo: - return Bandwidth(self.onionoo[key]) - return Bandwidth(Document()) - - def weights(self, fingerprint): - if len(fingerprint)> 0 and fingerprint[0] == '$': - fingerprint = fingerprint[1:] - key = 'weights:' + fingerprint - if key in self.onionoo: - return Weights(self.onionoo[key]) - return Weights(Document()) - - def shutdown(self): - self.executor.shutdown(True) - - def nickname2fingerprint(self, nickname): - - if nickname[0] == '#': - nickname = nickname[1:] - - data = self.search(nickname) or {} - - if 'relays' in data: - for relay in data['relays']: - if 'n' in relay and 'f' in relay: - if nickname == relay['n']: - return relay['f'] - - if 'bridges' in data: - for bridge in data['bridges']: - if 'n' in bridge and 'h' in bridge: - if nickname == bridge['n']: - return bridge['h'] - - return None - - def search(self, search_string, limit=None, offset=None): - - lgr = logging.getLogger('theonionbox') - - payload = {'search': search_string} - if limit and limit > 0: - payload['limit'] = limit - if offset and offset > 0: - payload['offset'] = offset - - headers = {'accept-encoding': 'gzip'} - - proxy_address = self.proxy.address() - - if proxy_address is None: - proxies = {} - query_base = ONIONOO_OPEN - else: - proxies = { - 'http': 'socks5h://' + proxy_address, - 'https': 'socks5h://' + proxy_address - } - query_base = ONIONOO_HIDDEN[self.hidden_index] - - query_address = query_base + '/summary' - - r = None - - try: - r = requests.get(query_address, params=payload, headers=headers, proxies=proxies, timeout=10) - except Exception as exc: - lgr.debug("Onionoo: Failed querying '{}' -> {}".format(query_address, exc)) - - if r is None: - return - - if r.status_code != requests.codes.ok: - return - - try: - data = r.json() - except Exception as exc: - lgr.debug("Onionoo: Failed to un-json network data; error code says '{}'.".format(exc)) - return - - return data - - # v5.x implementation from enum import Enum, auto @@ -912,7 +584,7 @@ def shutdown(self): def query(self, update: Document, document: OnionooDocument, fingerprint: str): # https://trac.torproject.org/projects/tor/ticket/6320 - hash = sha1(a2b_hex(fingerprint)).hexdigest() + hash = hashlib.sha1(a2b_hex(fingerprint)).hexdigest() payload = {'lookup': hash} headers = {'accept-encoding': 'gzip'} @@ -964,8 +636,6 @@ def query(self, update: Document, document: OnionooDocument, fingerprint: str): def register(self, fingerprint: str) -> OnionooData: - import hashlib - self.log.debug(f'Registering OoM|{fingerprint[:6]}.') documents = {} @@ -988,7 +658,6 @@ def register(self, fingerprint: str) -> OnionooData: return OnionooData(documents) def trigger(self, fingerprint: str): - import hashlib for d in OnionooDocument: hash = hashlib.sha256(f'{fingerprint.lower()}|{d.value}'.encode('UTF-8')).hexdigest() diff --git a/theonionbox/tob/persistor.py b/theonionbox/tob/persistor.py index bd8a3d9..69abb2d 100644 --- a/theonionbox/tob/persistor.py +++ b/theonionbox/tob/persistor.py @@ -1,10 +1,10 @@ from typing import Optional, List + import logging +import os import sqlite3 import tempfile -import os from time import time -from sqlite3 import Connection, Row class Storage(object): @@ -112,7 +112,7 @@ def __init__(self, storage: Storage, fingerprint: str): conn.close() - def open_connection(self, path: Optional[str] = None) -> Optional[Connection]: + def open_connection(self, path: Optional[str] = None) -> Optional[sqlite3.Connection]: if path is None: path = self.path @@ -130,7 +130,7 @@ def open_connection(self, path: Optional[str] = None) -> Optional[Connection]: # This does not commit! def persist(self, interval: str, timestamp: float, - read: Optional[int] = 0, write: Optional[int] = 0, connection: Optional[Connection] = None) -> bool: + read: Optional[int] = 0, write: Optional[int] = 0, connection: Optional[sqlite3.Connection] = None) -> bool: if self.fpid is None: return False @@ -152,7 +152,7 @@ def persist(self, interval: str, timestamp: float, # get the data back from the table def get(self, interval: str, js_timestamp: Optional[int] = int(time()*1000), limit: Optional[int] = -1, - offset: Optional[int] = 0, connection: Optional[Connection] = None) -> Optional[List[Row]]: + offset: Optional[int] = 0, connection: Optional[sqlite3.Connection] = None) -> Optional[List[sqlite3.Row]]: if connection is None: connection = self.open_connection() if connection is None: diff --git a/theonionbox/tob/plugin/session.py b/theonionbox/tob/plugin/session.py index a48721e..c291c6e 100644 --- a/theonionbox/tob/plugin/session.py +++ b/theonionbox/tob/plugin/session.py @@ -1,5 +1,6 @@ from bottle import request, HTTPError, redirect -from tob.session import SessionManager + +from ..session import SessionManager class SessionPlugin(object): diff --git a/theonionbox/tob/proxy.py b/theonionbox/tob/proxy.py index 29c8e08..87887fe 100644 --- a/theonionbox/tob/proxy.py +++ b/theonionbox/tob/proxy.py @@ -1,8 +1,13 @@ from typing import Optional + +from contextlib import closing import logging +from socket import AF_INET, SOCK_STREAM +from socks import socksocket from threading import RLock -# from tob.configuration import BaseNodeConfig -from tob.config import ProxyConfig + +from .config import ProxyConfig +from .stam.control import Controller class Proxy: @@ -19,9 +24,6 @@ def address(self) -> Optional[str]: ##### # Proxy configuration - from contextlib import closing - from socks import socksocket - from socket import AF_INET, SOCK_STREAM try: if self.config.proxy == 'auto': @@ -59,9 +61,6 @@ def port(self): def assure_cookie(self, host: str, cookie: str) -> bool: - from stam.control import Controller - import threading - if host is None or cookie is None: return True diff --git a/theonionbox/tob/scheduler.py b/theonionbox/tob/scheduler.py index 5db49b7..baee708 100644 --- a/theonionbox/tob/scheduler.py +++ b/theonionbox/tob/scheduler.py @@ -1,10 +1,10 @@ -# coding=UTF-8 - +from apscheduler.schedulers import SchedulerNotRunningError # used by node.py +from datetime import timedelta +import logging from pytz.tzinfo import NonExistentTimeError, AmbiguousTimeError -from apscheduler.schedulers import SchedulerNotRunningError +from tzlocal import get_localzone # to compensate for 'No handlers could be found for logger "apscheduler.scheduler"' message -import logging log = logging.getLogger('apscheduler.scheduler') log.addHandler(logging.NullHandler()) @@ -59,7 +59,6 @@ def add_job(self, func, trigger, args=None, kwargs=None, id=None, **trigger_args # Could happen at the beginning of DST period or at the end: # Add one hour to jump ot of the slushy zone if trigger is 'date': - from datetime import timedelta trigger_args['run_date'] += timedelta(hours=1) s = self.schedulr.add_job(func, trigger, id=id, replace_existing=True, args=args, kwargs=kwargs, **trigger_args) @@ -79,7 +78,6 @@ def add_job(self, func, trigger, args=None, kwargs=None, id=None, **trigger_args except (NonExistentTimeError, AmbiguousTimeError): # Could happen at the beginning of DST period or at the end: # Add one hour to jump ot of the slushy zone - from datetime import timedelta run_date += timedelta(hours=1) s = self.schedulr.add_date_job(func, run_date, name=id, # replace_existing=True, args=args, kwargs=kwargs) @@ -133,7 +131,6 @@ def shutdown(self): # https://github.com/ralphwetzel/theonionbox/issues/19#issuecomment-263110953 def check_tz(self): - from tzlocal import get_localzone try: # APScheduler 3.x diff --git a/theonionbox/tob/server.py b/theonionbox/tob/server.py deleted file mode 100644 index 2e6d773..0000000 --- a/theonionbox/tob/server.py +++ /dev/null @@ -1,17 +0,0 @@ -from wsgiserver import WSGIServer -import sys - - -class Server(WSGIServer): - - def error_log(self, msg="", level=20, traceback=False): - # Override this in subclasses as desired - import logging - lgr = logging.getLogger('theonionbox') - e = sys.exc_info()[1] - if e.args[1].find('UNKNOWN_CA') > 0: - lgr.warn("{} -> Your CA certificate could not be located or " - "couldn't be matched with a known, trusted CA.".format(e.args[1])) - else: - lgr.warn('HTTP Server: {}'.format(e.args[1])) - diff --git a/theonionbox/tob/session.py b/theonionbox/tob/session.py index b9164aa..8c915ae 100644 --- a/theonionbox/tob/session.py +++ b/theonionbox/tob/session.py @@ -1,8 +1,9 @@ -import uuid -import time -from bottle import BaseRequest from typing import Optional, Callable + +from bottle import BaseRequest import logging +import time +import uuid # This is the TTL (in seconds) of the Session on server side; # Accessing the session resets the counter! diff --git a/theonionbox/tob/simplecontroller.py b/theonionbox/tob/simplecontroller.py index 219db4c..313107d 100644 --- a/theonionbox/tob/simplecontroller.py +++ b/theonionbox/tob/simplecontroller.py @@ -1,6 +1,6 @@ +import socket +import socks import threading -# from socks import socksocket -from socket import socket, AF_INET, SOCK_STREAM # "Thanks" to stem! try: @@ -63,8 +63,7 @@ def msg(self, message): class SimplePort(SimpleController): def __init__(self, host, port): - - self._socket = socket(AF_INET, SOCK_STREAM) + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.settimeout(2) # This could raise an exception ... @@ -77,8 +76,7 @@ def __init__(self, host, port): class SimpleSocket(SimpleController): def __init__(self, socket_path): - from socket import AF_UNIX - self._socket = socket(AF_UNIX, SOCK_STREAM) + self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._socket.settimeout(2) # This could raise an exception ... @@ -91,10 +89,7 @@ def __init__(self, socket_path): class SimpleProxy(SimpleController): def __init__(self, host, port, proxy_host, proxy_port): - - import socks - - self._socket = socks.socksocket(AF_INET, SOCK_STREAM) + self._socket = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM) self._socket.settimeout(15) self._socket.set_proxy(socks.SOCKS5, proxy_host, proxy_port, rdns=True) diff --git a/theonionbox/tob/stam/control.py b/theonionbox/tob/stam/control.py index 9e40fb3..db24529 100644 --- a/theonionbox/tob/stam/control.py +++ b/theonionbox/tob/stam/control.py @@ -1,16 +1,22 @@ from typing import Callable, Optional, Union, List -import stem.control -import stem.util -from stam.socket import ControlPort, ControlSocketFile, ControlProxy -#from configuration.node import BaseNodeConfig -import logging + +from platform import system +import pytz + from stem import SocketError, UNDEFINED -from stem.response.protocolinfo import ProtocolInfoResponse from stem.connection import IncorrectPassword, IncorrectSocketType, AuthenticationFailure, AuthMethod, MissingPassword -from stem.control import EventType -from tob.proxy import Proxy as TorProxy -from tob.config import DefaultNodeConfig -from tob.ccfile import CCNode +import stem.control +from stem.control import EventType # used by node.py +from stem.response.protocolinfo import ProtocolInfoResponse +import stem.util + +# Check below! +# from ..proxy import Proxy as TorProxy + +from ..config import DefaultNodeConfig +from ..ccfile import CCNode + +from .socket import ControlPort, ControlSocketFile, ControlProxy ##### # Extensive monkey patching of stem.control.BaseController @@ -61,7 +67,7 @@ class Controller(stem.control.Controller): def from_port(address: Optional[str] = '127.0.0.1', port: Optional[Union[int, str]] = 'auto', timeout: Optional[int] = None) -> 'Controller': - import stem.connection + # import stem.connection # timeout management if timeout and timeout < 0: @@ -101,7 +107,11 @@ def from_port_via_proxy(address: str, port: int, proxy: Optional[str] = None, @staticmethod def from_config(config: Union[DefaultNodeConfig, CCNode], timeout: Optional[int] = None, - proxy: Optional[TorProxy] = None) -> 'Controller': + # The next line used to be + # proxy: Optional[TorProxy] = None) -> 'Controller': + # ... but this create a cyclic import. Yes, this is definitely a bad design! + # ToDo: Fix this orderly! + proxy = None) -> 'Controller': if config.control == 'socket': try: @@ -230,7 +240,6 @@ def isAuthCookie(self) -> bool: @property def user(self) -> str: - from platform import system # As stem's & Tor's implementations will return no valuable results on Windows # we diverge appropriately: @@ -309,8 +318,6 @@ def password(self) -> str: @stem.control.with_default() def get_accounting_stats(self, default=UNDEFINED): - import pytz - accs = super(Controller, self).get_accounting_stats() # stem returns a naive datetime object for interval_end diff --git a/theonionbox/tob/stam/socket.py b/theonionbox/tob/stam/socket.py index 944ed91..dd8211e 100644 --- a/theonionbox/tob/stam/socket.py +++ b/theonionbox/tob/stam/socket.py @@ -1,5 +1,8 @@ from typing import Optional + import socket +import socks + import stem.socket @@ -56,7 +59,6 @@ def __init__(self, address: str, port: int, proxy: Optional[str] = None, def _make_socket(self): - import socks control_socket = None try: diff --git a/theonionbox/tob/static.py b/theonionbox/tob/static.py index edd2810..5ed8ed3 100644 --- a/theonionbox/tob/static.py +++ b/theonionbox/tob/static.py @@ -1,11 +1,12 @@ from typing import Optional, Union, List + from pathlib import Path, PurePath from re import escape from bottle import Bottle, static_file, HTTPError, HTTPResponse -from tob.plugin.session import SessionPlugin -from tob.session import SessionManager +from .plugin.session import SessionPlugin +from .session import SessionManager class Base(Bottle): diff --git a/theonionbox/tob/system/__init__.py b/theonionbox/tob/system/__init__.py index 8f6cf34..69f33f9 100644 --- a/theonionbox/tob/system/__init__.py +++ b/theonionbox/tob/system/__init__.py @@ -3,13 +3,12 @@ import os import platform -from threading import RLock from collections import deque - import itertools from psutil import virtual_memory, cpu_percent # to readout the cpu load +from threading import RLock -from tob.deviation import getTimer +from ..deviation import getTimer class BaseSystem(object): @@ -141,6 +140,7 @@ def get_performance_data(self, after: int = None): def run(self, launch, stop): return launch() + def get_system_manager(system: str = platform.system()) -> BaseSystem: if system == 'Darwin': diff --git a/theonionbox/tob/system/darwin/__init__.py b/theonionbox/tob/system/darwin/__init__.py index 1d63411..f5e1652 100644 --- a/theonionbox/tob/system/darwin/__init__.py +++ b/theonionbox/tob/system/darwin/__init__.py @@ -6,8 +6,9 @@ import subprocess import time - from .. import BaseSystem + +from .osxtemp import Temperature, Sensors, Units from .systray import Icon @@ -30,7 +31,6 @@ def temperature(self) -> Optional[float]: if self.temp_function is None: try: - from .osxtemp import Temperature, Sensors, Units self.temp_function = Temperature(Sensors.CPU_0_PROXIMITY, Units.CELSIUS) except OSError: log = logging.getLogger('theonionbox') @@ -186,6 +186,7 @@ def ntp(self) -> Optional[str]: return self.__ntp + # This is Test code! def run_with_icon(self, launch, shutdown): from . import Icon diff --git a/theonionbox/tob/system/darwin/osxtemp/__init__.py b/theonionbox/tob/system/darwin/osxtemp/__init__.py index 7992c0b..858add4 100644 --- a/theonionbox/tob/system/darwin/osxtemp/__init__.py +++ b/theonionbox/tob/system/darwin/osxtemp/__init__.py @@ -1,7 +1,10 @@ -from ctypes import CDLL, c_int, byref, c_double -import sys, os, inspect from typing import Optional +from ctypes import CDLL, c_int, byref, c_double +import inspect +import sys +import os + def get_script_dir(follow_symlinks: bool=True) -> str: if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze diff --git a/theonionbox/tob/system/darwin/systray.py b/theonionbox/tob/system/darwin/systray.py index f69d803..2591443 100644 --- a/theonionbox/tob/system/darwin/systray.py +++ b/theonionbox/tob/system/darwin/systray.py @@ -1,8 +1,6 @@ -import threading -import signal - import AppKit import PyObjCTools.MachSignals +import signal from pystray._darwin import Icon as pystray_Icon, IconDelegate diff --git a/theonionbox/tob/system/linux.py b/theonionbox/tob/system/linux.py index a126fd6..d3a9f9d 100644 --- a/theonionbox/tob/system/linux.py +++ b/theonionbox/tob/system/linux.py @@ -6,7 +6,6 @@ import subprocess import time - from . import BaseSystem diff --git a/theonionbox/tob/template_tools.py b/theonionbox/tob/template_tools.py index c794424..4a647cf 100644 --- a/theonionbox/tob/template_tools.py +++ b/theonionbox/tob/template_tools.py @@ -1,6 +1,6 @@ import math import sys -from tob.log import sanitize_for_html +from .log import sanitize_for_html ##### # Python version detection diff --git a/theonionbox/tob/transportation.py b/theonionbox/tob/transportation.py index 005c3c5..00c7565 100644 --- a/theonionbox/tob/transportation.py +++ b/theonionbox/tob/transportation.py @@ -1,9 +1,7 @@ -import typing -import stem.response.events -import contextlib -import threading import collections +import contextlib +import stem.response.events class Base: diff --git a/theonionbox/tob/version.py b/theonionbox/tob/version.py index ca7c20e..4697618 100644 --- a/theonionbox/tob/version.py +++ b/theonionbox/tob/version.py @@ -1,6 +1,6 @@ -import uuid import requests -import typing +import uuid + import bottle