diff --git a/src/netcontroller/__init__.py b/src/netcontroller/__init__.py index c9a0337e..bf912b74 100755 --- a/src/netcontroller/__init__.py +++ b/src/netcontroller/__init__.py @@ -1,4 +1,5 @@ -from . import torbinary, getopenport, netcontrol -tor_binary = torbinary.tor_binary +from . import getopenport, torcontrol +from . import torcontrol +tor_binary = torcontrol.torbinary.tor_binary get_open_port = getopenport.get_open_port -NetController = netcontrol.NetController \ No newline at end of file +NetController = torcontrol.NetController \ No newline at end of file diff --git a/src/netcontroller/netcontrol.py b/src/netcontroller/torcontrol/__init__.py similarity index 57% rename from src/netcontroller/netcontrol.py rename to src/netcontroller/torcontrol/__init__.py index d2eba18a..919b1c9b 100644 --- a/src/netcontroller/netcontrol.py +++ b/src/netcontroller/torcontrol/__init__.py @@ -1,9 +1,27 @@ -''' +""" Onionr - Private P2P Communication - Netcontroller library, used to control/work with Tor/I2P and send requests through them -''' -''' + Netcontroller library, used to control/work with Tor and send requests through them +""" +import os +import base64 +import subprocess +import signal +import time +import multiprocessing +import platform # For windows sigkill workaround + +from onionrtypes import BooleanSuccessState +import config +import logger +from .. import getopenport +from .. import watchdog +from . import customtorrc +from . import gentorrc +from . import addbridges +from . import torbinary +from utils import identifyhome +""" 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 @@ -16,112 +34,36 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -''' -import os, sys, base64, subprocess, signal, time -import multiprocessing -import platform # For windows sigkill workaround -import config, logger -from . import getopenport -from . import watchdog -from . import customtorrc -from utils import identifyhome +""" config.reload() TOR_KILL_WAIT = 3 +addbridges = addbridges.add_bridges -def add_bridges(torrc: str)->str: - """Configure tor to use a bridge using Onionr config keys""" - if config.get('tor.use_bridge', False) == True: - bridge = config.get('tor.bridge_ip', None) - if not bridge is None: - fingerprint = config.get('tor.bridge_fingerprint', '') # allow blank fingerprint purposefully - torrc += '\nUseBridges 1\nBridge %s %s\n' % (bridge, fingerprint) - else: - logger.warn('bridge was enabled but not specified in config') - - return torrc class NetController: - ''' - This class handles hidden service setup on Tor and I2P - ''' + """ + This class handles hidden service setup on Tor + """ def __init__(self, hsPort, apiServerIP='127.0.0.1'): # set data dir self.dataDir = identifyhome.identify_home() - + self.socksPort = getopenport.get_open_port() self.torConfigLocation = self.dataDir + 'torrc' self.readyState = False - self.socksPort = getopenport.get_open_port() self.hsPort = hsPort self._torInstnace = '' self.myID = '' self.apiServerIP = apiServerIP + self.torBinary = torbinary.tor_binary() - if os.path.exists('./tor'): - self.torBinary = './tor' - elif os.path.exists('/usr/bin/tor'): - self.torBinary = '/usr/bin/tor' - else: - self.torBinary = 'tor' - - def generateTorrc(self): - ''' - Generate a torrc file for our tor instance - ''' - hsVer = '# v2 onions' - if config.get('tor.v3onions'): - hsVer = 'HiddenServiceVersion 3' - - if os.path.exists(self.torConfigLocation): - os.remove(self.torConfigLocation) - - # Set the Tor control password. Meant to make it harder to manipulate our Tor instance - plaintext = base64.b64encode(os.urandom(50)).decode() - config.set('tor.controlpassword', plaintext, savefile=True) - config.set('tor.socksport', self.socksPort, savefile=True) - - controlPort = getopenport.get_open_port() - - config.set('tor.controlPort', controlPort, savefile=True) - - hashedPassword = subprocess.Popen([self.torBinary, '--hash-password', plaintext], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - for line in iter(hashedPassword.stdout.readline, b''): - password = line.decode() - if 'warn' not in password: - break - - torrcData = '''SocksPort ''' + str(self.socksPort) + ''' OnionTrafficOnly -DataDirectory ''' + self.dataDir + '''tordata/ -CookieAuthentication 1 -KeepalivePeriod 40 -CircuitsAvailableTimeout 86400 -ControlPort ''' + str(controlPort) + ''' -HashedControlPassword ''' + str(password) + ''' - ''' - if config.get('general.security_level', 1) == 0: - torrcData += '''\nHiddenServiceDir ''' + self.dataDir + '''hs/ -\n''' + hsVer + '''\n -HiddenServiceNumIntroductionPoints 6 -HiddenServiceMaxStreams 100 -HiddenServiceMaxStreamsCloseCircuit 1 -HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort) - - torrcData = add_bridges(torrcData) - - torrcData += customtorrc.get_custom_torrc() - - torrc = open(self.torConfigLocation, 'w') - torrc.write(torrcData) - torrc.close() - return - - def startTor(self, gen_torrc=True): - ''' + def startTor(self, gen_torrc=True) -> BooleanSuccessState: + """ Start Tor with onion service on port 80 & socks proxy on random port - ''' + """ if gen_torrc: - self.generateTorrc() + gentorrc.generate_torrc(self, self.apiServerIP) if os.path.exists('./tor'): self.torBinary = './tor' @@ -137,7 +79,9 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort) return False else: # Test Tor Version - torVersion = subprocess.Popen([self.torBinary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + torVersion = subprocess.Popen([self.torBinary, '--version'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) for line in iter(torVersion.stdout.readline, b''): if 'Tor 0.2.' in line.decode(): logger.fatal('Tor 0.3+ required', terminal=True) @@ -177,18 +121,18 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort) except FileNotFoundError: self.myID = "" - torPidFile = open(self.dataDir + 'torPid.txt', 'w') - torPidFile.write(str(tor.pid)) - torPidFile.close() + with open(self.dataDir + 'torPid.txt', 'w') as tor_pid_file: + tor_pid_file.write(str(tor.pid)) - multiprocessing.Process(target=watchdog.watchdog, args=[os.getpid(), tor.pid]).start() + multiprocessing.Process(target=watchdog.watchdog, + args=[os.getpid(), tor.pid]).start() return True def killTor(self): - ''' + """ Properly kill tor based on pid saved to file - ''' + """ try: pid = open(self.dataDir + 'torPid.txt', 'r') @@ -199,7 +143,7 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort) try: int(pidN) - except: + except ValueError: return try: @@ -220,12 +164,12 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort) pass if 'windows' == platform.system().lower(): - os.system('taskkill /PID %s /F' % (pidN,)) + os.system(f'taskkill /PID {pidN} /F') time.sleep(0.5) return try: os.kill(int(pidN), signal.SIGKILL) - except (ProcessLookupError, PermissionError) as e: + except (ProcessLookupError, PermissionError): pass try: os.remove(self.dataDir + 'tordata/lock') diff --git a/src/netcontroller/torcontrol/addbridges.py b/src/netcontroller/torcontrol/addbridges.py new file mode 100644 index 00000000..4ac69cc1 --- /dev/null +++ b/src/netcontroller/torcontrol/addbridges.py @@ -0,0 +1,35 @@ +""" + Onionr - Private P2P Communication + + Add bridge info to torrc configuration string +""" +import config +import logger +""" + 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 . +""" + + +def add_bridges(torrc: str) -> str: + """Configure tor to use a bridge using Onionr config keys""" + if config.get('tor.use_bridge', False) is True: + bridge = config.get('tor.bridge_ip', None) + if bridge is not None: + # allow blank fingerprint purposefully + fingerprint = config.get('tor.bridge_fingerprint', '') + torrc += '\nUseBridges 1\nBridge %s %s\n' % (bridge, fingerprint) + else: + logger.warn('bridge was enabled but not specified in config') + + return torrc diff --git a/src/netcontroller/customtorrc.py b/src/netcontroller/torcontrol/customtorrc.py similarity index 100% rename from src/netcontroller/customtorrc.py rename to src/netcontroller/torcontrol/customtorrc.py diff --git a/src/netcontroller/torcontrol/gentorrc.py b/src/netcontroller/torcontrol/gentorrc.py new file mode 100644 index 00000000..916ab854 --- /dev/null +++ b/src/netcontroller/torcontrol/gentorrc.py @@ -0,0 +1,71 @@ +import base64 +import os +import subprocess + +from .. import getopenport +from . import customtorrc +from . import addbridges +from . import torbinary +from utils import identifyhome +import config + +add_bridges = addbridges.add_bridges + + +def generate_torrc(net_controller, api_server_ip): + """ + Generate a torrc file for our tor instance + """ + socks_port = net_controller.socksPort + hs_ver = '# v2 onions' + home_dir = identifyhome.identify_home() + tor_config_location = home_dir + '/torrc' + + if config.get('tor.v3onions'): + hs_ver = 'HiddenServiceVersion 3' + + """ + Set the Tor control password. + Meant to make it harder to manipulate our Tor instance + """ + plaintext = base64.b64encode(os.urandom(50)).decode() + config.set('tor.controlpassword', plaintext, savefile=True) + config.set('tor.socksport', socks_port, savefile=True) + + controlPort = getopenport.get_open_port() + + config.set('tor.controlPort', controlPort, savefile=True) + + hashedPassword = subprocess.Popen([torbinary.tor_binary(), + '--hash-password', + plaintext], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + for line in iter(hashedPassword.stdout.readline, b''): + password = line.decode() + if 'warn' not in password: + break + + torrc_data = """SocksPort """ + str(socks_port) + """ OnionTrafficOnly +DataDirectory """ + home_dir + """tordata/ +CookieAuthentication 1 +KeepalivePeriod 40 +CircuitsAvailableTimeout 86400 +ControlPort """ + str(controlPort) + """ +HashedControlPassword """ + str(password) + """ + """ + if config.get('general.security_level', 1) == 0: + torrc_data += """\nHiddenServiceDir """ + home_dir + """hs/ +\n""" + hs_ver + """\n +HiddenServiceNumIntroductionPoints 6 +HiddenServiceMaxStreams 100 +HiddenServiceMaxStreamsCloseCircuit 1 +HiddenServicePort 80 """ + api_server_ip + """:""" + str(socks_port) + + torrc_data = add_bridges(torrc_data) + + torrc_data += customtorrc.get_custom_torrc() + + torrc = open(tor_config_location, 'w') + torrc.write(torrc_data) + torrc.close() diff --git a/src/netcontroller/rebuildtor.py b/src/netcontroller/torcontrol/rebuildtor.py similarity index 100% rename from src/netcontroller/rebuildtor.py rename to src/netcontroller/torcontrol/rebuildtor.py diff --git a/src/netcontroller/torbinary.py b/src/netcontroller/torcontrol/torbinary.py similarity index 90% rename from src/netcontroller/torbinary.py rename to src/netcontroller/torcontrol/torbinary.py index fb61e2d6..c3b7d0bf 100644 --- a/src/netcontroller/torbinary.py +++ b/src/netcontroller/torcontrol/torbinary.py @@ -1,9 +1,11 @@ -''' +""" Onionr - Private P2P Communication get the tor binary path -''' -''' +""" +import os +from shutil import which +""" 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 @@ -16,13 +18,12 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -''' -import os -from shutil import which +""" + def tor_binary(): - '''Return tor binary path or none if not exists''' + """Return tor binary path or none if not exists""" tor_path = './tor' if not os.path.exists(tor_path): tor_path = which('tor') - return tor_path \ No newline at end of file + return tor_path diff --git a/src/netcontroller/torcontroller.py b/src/netcontroller/torcontrol/torcontroller.py similarity index 99% rename from src/netcontroller/torcontroller.py rename to src/netcontroller/torcontrol/torcontroller.py index 201121d7..f3dba225 100644 --- a/src/netcontroller/torcontroller.py +++ b/src/netcontroller/torcontrol/torcontroller.py @@ -2,6 +2,7 @@ from stem.control import Controller import config + def get_controller(): c = Controller.from_port(port=config.get('tor.controlPort')) c.authenticate(config.get('tor.controlpassword')) diff --git a/src/onionrtypes/__init__.py b/src/onionrtypes/__init__.py index 37cf3c51..a8075498 100644 --- a/src/onionrtypes/__init__.py +++ b/src/onionrtypes/__init__.py @@ -9,4 +9,9 @@ BlockHash = NewType('BlockHash', str) OnboardingConfig = NewType('OnboardingConfig', str) +# JSON serializable string. e.g. no raw bytes JSONSerializable = NewType('JSONSerializable', str) + +# Return value of some functions or methods, denoting operation success +# Do not use for new code +BooleanSuccessState = NewType('BooleanSuccessState', bool) diff --git a/tests/test_custom_torrc.py b/tests/test_custom_torrc.py index 02b33cdd..bdf4892c 100644 --- a/tests/test_custom_torrc.py +++ b/tests/test_custom_torrc.py @@ -8,7 +8,7 @@ print("Test directory:", TEST_DIR) os.environ["ONIONR_HOME"] = TEST_DIR from utils import createdirs, identifyhome import onionrsetup as setup -from netcontroller import customtorrc +from netcontroller.torcontrol import customtorrc createdirs.create_dirs() setup.setup_config()