refactored netcontroller and split module

master
Kevin Froman 2019-12-12 02:47:33 -06:00
parent 6b0c88aff9
commit bb87bc192e
10 changed files with 171 additions and 113 deletions

View File

@ -1,4 +1,5 @@
from . import torbinary, getopenport, netcontrol from . import getopenport, torcontrol
tor_binary = torbinary.tor_binary from . import torcontrol
tor_binary = torcontrol.torbinary.tor_binary
get_open_port = getopenport.get_open_port get_open_port = getopenport.get_open_port
NetController = netcontrol.NetController NetController = torcontrol.NetController

View File

@ -1,9 +1,27 @@
''' """
Onionr - Private P2P Communication 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 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or 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 You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
''' """
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() config.reload()
TOR_KILL_WAIT = 3 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: 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'): def __init__(self, hsPort, apiServerIP='127.0.0.1'):
# set data dir # set data dir
self.dataDir = identifyhome.identify_home() self.dataDir = identifyhome.identify_home()
self.socksPort = getopenport.get_open_port()
self.torConfigLocation = self.dataDir + 'torrc' self.torConfigLocation = self.dataDir + 'torrc'
self.readyState = False self.readyState = False
self.socksPort = getopenport.get_open_port()
self.hsPort = hsPort self.hsPort = hsPort
self._torInstnace = '' self._torInstnace = ''
self.myID = '' self.myID = ''
self.apiServerIP = apiServerIP self.apiServerIP = apiServerIP
self.torBinary = torbinary.tor_binary()
if os.path.exists('./tor'): def startTor(self, gen_torrc=True) -> BooleanSuccessState:
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):
'''
Start Tor with onion service on port 80 & socks proxy on random port Start Tor with onion service on port 80 & socks proxy on random port
''' """
if gen_torrc: if gen_torrc:
self.generateTorrc() gentorrc.generate_torrc(self, self.apiServerIP)
if os.path.exists('./tor'): if os.path.exists('./tor'):
self.torBinary = './tor' self.torBinary = './tor'
@ -137,7 +79,9 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
return False return False
else: else:
# Test Tor Version # 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''): for line in iter(torVersion.stdout.readline, b''):
if 'Tor 0.2.' in line.decode(): if 'Tor 0.2.' in line.decode():
logger.fatal('Tor 0.3+ required', terminal=True) logger.fatal('Tor 0.3+ required', terminal=True)
@ -177,18 +121,18 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
except FileNotFoundError: except FileNotFoundError:
self.myID = "" self.myID = ""
torPidFile = open(self.dataDir + 'torPid.txt', 'w') with open(self.dataDir + 'torPid.txt', 'w') as tor_pid_file:
torPidFile.write(str(tor.pid)) tor_pid_file.write(str(tor.pid))
torPidFile.close()
multiprocessing.Process(target=watchdog.watchdog, args=[os.getpid(), tor.pid]).start() multiprocessing.Process(target=watchdog.watchdog,
args=[os.getpid(), tor.pid]).start()
return True return True
def killTor(self): def killTor(self):
''' """
Properly kill tor based on pid saved to file Properly kill tor based on pid saved to file
''' """
try: try:
pid = open(self.dataDir + 'torPid.txt', 'r') pid = open(self.dataDir + 'torPid.txt', 'r')
@ -199,7 +143,7 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
try: try:
int(pidN) int(pidN)
except: except ValueError:
return return
try: try:
@ -220,12 +164,12 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
pass pass
if 'windows' == platform.system().lower(): if 'windows' == platform.system().lower():
os.system('taskkill /PID %s /F' % (pidN,)) os.system(f'taskkill /PID {pidN} /F')
time.sleep(0.5) time.sleep(0.5)
return return
try: try:
os.kill(int(pidN), signal.SIGKILL) os.kill(int(pidN), signal.SIGKILL)
except (ProcessLookupError, PermissionError) as e: except (ProcessLookupError, PermissionError):
pass pass
try: try:
os.remove(self.dataDir + 'tordata/lock') os.remove(self.dataDir + 'tordata/lock')

View File

@ -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 <https://www.gnu.org/licenses/>.
"""
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

View File

@ -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()

View File

@ -1,9 +1,11 @@
''' """
Onionr - Private P2P Communication Onionr - Private P2P Communication
get the tor binary path get the tor binary path
''' """
''' import os
from shutil import which
"""
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or 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 You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
''' """
import os
from shutil import which
def tor_binary(): def tor_binary():
'''Return tor binary path or none if not exists''' """Return tor binary path or none if not exists"""
tor_path = './tor' tor_path = './tor'
if not os.path.exists(tor_path): if not os.path.exists(tor_path):
tor_path = which('tor') tor_path = which('tor')
return tor_path return tor_path

View File

@ -2,6 +2,7 @@ from stem.control import Controller
import config import config
def get_controller(): def get_controller():
c = Controller.from_port(port=config.get('tor.controlPort')) c = Controller.from_port(port=config.get('tor.controlPort'))
c.authenticate(config.get('tor.controlpassword')) c.authenticate(config.get('tor.controlpassword'))

View File

@ -9,4 +9,9 @@ BlockHash = NewType('BlockHash', str)
OnboardingConfig = NewType('OnboardingConfig', str) OnboardingConfig = NewType('OnboardingConfig', str)
# JSON serializable string. e.g. no raw bytes
JSONSerializable = NewType('JSONSerializable', str) JSONSerializable = NewType('JSONSerializable', str)
# Return value of some functions or methods, denoting operation success
# Do not use for new code
BooleanSuccessState = NewType('BooleanSuccessState', bool)

View File

@ -8,7 +8,7 @@ print("Test directory:", TEST_DIR)
os.environ["ONIONR_HOME"] = TEST_DIR os.environ["ONIONR_HOME"] = TEST_DIR
from utils import createdirs, identifyhome from utils import createdirs, identifyhome
import onionrsetup as setup import onionrsetup as setup
from netcontroller import customtorrc from netcontroller.torcontrol import customtorrc
createdirs.create_dirs() createdirs.create_dirs()
setup.setup_config() setup.setup_config()