"""Onionr - Private P2P Communication. Netcontroller library, used to control/work with Tor and send requests through """ import os import subprocess import signal import time import multiprocessing from onionrtypes import BooleanSuccessState 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 (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 . """ TOR_KILL_WAIT = 3 addbridges = addbridges.add_bridges class NetController: """Handle Tor daemon and onion 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.hsPort = hsPort self._torInstnace = '' self.myID = '' self.apiServerIP = apiServerIP self.torBinary = torbinary.tor_binary() def startTor(self, gen_torrc=True) -> BooleanSuccessState: """ Start Tor with onion service on port 80 & socks proxy on random port """ if gen_torrc: gentorrc.generate_torrc(self, self.apiServerIP) if os.path.exists('./tor'): self.torBinary = './tor' elif os.path.exists('/usr/bin/tor'): self.torBinary = '/usr/bin/tor' else: self.torBinary = 'tor' try: tor = subprocess.Popen([self.torBinary, '-f', self.torConfigLocation], stdout=subprocess.PIPE, stderr=subprocess.PIPE) except FileNotFoundError: logger.fatal("Tor was not found in your path or the Onionr directory. Please install Tor and try again.", terminal=True) return False else: # Test Tor Version 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) return False torVersion.kill() # wait for tor to get to 100% bootstrap try: for line in iter(tor.stdout.readline, b''): for word in ('bootstrapped', '%'): if word not in line.decode().lower(): break else: if '100' not in line.decode(): logger.info(line.decode().strip(), terminal=True) if 'bootstrapped 100' in line.decode().lower(): logger.info(line.decode()) break elif 'opening socks listener' in line.decode().lower(): logger.debug(line.decode().replace('\n', '')) else: if 'err' in line.decode(): logger.error(line.decode().replace('\n', '')) elif 'warn' in line.decode(): logger.warn(line.decode().replace('\n', '')) else: logger.debug(line.decode().replace('\n', '')) else: logger.fatal('Failed to start Tor. Maybe a stray instance of Tor used by Onionr is still running? This can also be a result of file permissions being too open', terminal=True) return False except KeyboardInterrupt: logger.fatal('Got keyboard interrupt. Onionr will exit soon.', timestamp = False, terminal=True) return False try: myID = open(self.dataDir + 'hs/hostname', 'r') self.myID = myID.read().replace('\n', '') myID.close() except FileNotFoundError: self.myID = "" 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], daemon=True).start() logger.info('Finished starting Tor.', terminal=True) self.readyState = True return True def killTor(self): """Properly kill tor based on pid saved to file.""" try: with open(self.dataDir + 'torPid.txt', 'r') as torPid: pidN = torPid.read() except FileNotFoundError: return try: try: # Extra int() os.kill(int(pidN), signal.SIGTERM) except PermissionError: # seems to happen on win 10 pass except ValueError: # Happens if int() check is not valid logger.error("torPid.txt contained invalid integer. " + "This indicates corruption " + "and should not be bypassed for security reasons") return os.remove(self.dataDir + 'torPid.txt') except ProcessLookupError: pass except FileNotFoundError: pass try: time.sleep(TOR_KILL_WAIT) except KeyboardInterrupt: pass try: os.kill(int(pidN), signal.SIGKILL) except (ProcessLookupError, PermissionError): pass try: os.remove(self.dataDir + 'tordata/lock') except FileNotFoundError: pass