2018-01-18 23:25:10 +00:00
'''
2019-06-15 04:26:10 +00:00
Onionr - Private P2P Communication
2018-01-18 23:25:10 +00:00
Netcontroller library , used to control / work with Tor / I2P and send requests through them
'''
'''
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 / > .
'''
2019-08-11 18:53:38 +00:00
import os , sys , base64 , subprocess , signal , time
2019-12-09 11:02:37 +00:00
import multiprocessing
2019-08-28 02:15:56 +00:00
import platform # For windows sigkill workaround
2019-07-14 06:51:43 +00:00
import config , logger
from . import getopenport
2019-07-15 03:01:56 +00:00
from utils import identifyhome
2019-12-09 11:02:37 +00:00
from . import watchdog
2019-06-16 06:06:32 +00:00
config . reload ( )
2019-08-11 18:53:38 +00:00
TOR_KILL_WAIT = 3
2019-09-06 09:31:13 +00:00
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 + = ' \n UseBridges 1 \n Bridge %s %s \n ' % ( bridge , fingerprint )
else :
logger . warn ( ' bridge was enabled but not specified in config ' )
return torrc
2018-01-18 23:25:10 +00:00
class NetController :
2018-02-04 03:44:29 +00:00
'''
This class handles hidden service setup on Tor and I2P
2018-01-18 23:25:10 +00:00
'''
2018-03-03 07:35:13 +00:00
2018-12-09 17:29:39 +00:00
def __init__ ( self , hsPort , apiServerIP = ' 127.0.0.1 ' ) :
2019-03-29 03:33:14 +00:00
# set data dir
2019-07-15 03:01:56 +00:00
self . dataDir = identifyhome . identify_home ( )
2018-09-26 04:58:11 +00:00
self . torConfigLocation = self . dataDir + ' torrc '
2018-01-18 23:25:10 +00:00
self . readyState = False
2019-07-14 06:51:43 +00:00
self . socksPort = getopenport . get_open_port ( )
2018-01-19 09:16:38 +00:00
self . hsPort = hsPort
2018-01-27 08:43:36 +00:00
self . _torInstnace = ' '
2018-01-19 21:28:34 +00:00
self . myID = ' '
2018-12-09 17:29:39 +00:00
self . apiServerIP = apiServerIP
2018-09-05 04:06:17 +00:00
if os . path . exists ( ' ./tor ' ) :
self . torBinary = ' ./tor '
elif os . path . exists ( ' /usr/bin/tor ' ) :
self . torBinary = ' /usr/bin/tor '
else :
self . torBinary = ' tor '
2018-01-18 23:25:10 +00:00
def generateTorrc ( self ) :
2018-02-04 03:44:29 +00:00
'''
Generate a torrc file for our tor instance
'''
2018-08-19 04:07:09 +00:00
hsVer = ' # v2 onions '
2018-12-09 17:29:39 +00:00
if config . get ( ' tor.v3onions ' ) :
2018-08-19 04:07:09 +00:00
hsVer = ' HiddenServiceVersion 3 '
2018-09-05 04:06:17 +00:00
2018-01-19 09:16:38 +00:00
if os . path . exists ( self . torConfigLocation ) :
os . remove ( self . torConfigLocation )
2018-09-05 04:06:17 +00:00
# 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 )
2018-09-20 04:35:26 +00:00
config . set ( ' tor.socksport ' , self . socksPort , savefile = True )
2018-09-05 04:06:17 +00:00
2019-07-14 06:51:43 +00:00
controlPort = getopenport . get_open_port ( )
2018-09-07 18:57:20 +00:00
2018-09-20 17:41:34 +00:00
config . set ( ' tor.controlPort ' , controlPort , savefile = True )
2018-09-05 04:06:17 +00:00
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 ( )
2018-09-09 05:12:41 +00:00
if ' warn ' not in password :
break
2018-09-05 04:06:17 +00:00
2019-05-09 05:27:15 +00:00
torrcData = ''' SocksPort ''' + str ( self . socksPort ) + ''' OnionTrafficOnly
2018-09-26 04:58:11 +00:00
DataDirectory ''' + self.dataDir + ''' tordata /
2018-09-05 04:06:17 +00:00
CookieAuthentication 1
2019-08-11 00:42:47 +00:00
KeepalivePeriod 40
CircuitsAvailableTimeout 86400
2018-09-07 18:57:20 +00:00
ControlPort ''' + str(controlPort) + '''
2018-09-05 04:06:17 +00:00
HashedControlPassword ''' + str(password) + '''
2018-01-18 23:25:10 +00:00
'''
2019-05-15 23:25:36 +00:00
if config . get ( ' general.security_level ' , 1 ) == 0 :
2018-12-09 17:29:39 +00:00
torrcData + = ''' \n HiddenServiceDir ''' + self . dataDir + ''' hs/
\n ''' + hsVer + ''' \n
2019-08-11 20:44:56 +00:00
HiddenServiceNumIntroductionPoints 6
HiddenServiceMaxStreams 100
HiddenServiceMaxStreamsCloseCircuit 1
2018-12-09 17:29:39 +00:00
HiddenServicePort 80 ''' + self.apiServerIP + ''' : ''' + str(self.hsPort)
2019-09-06 09:31:13 +00:00
torrcData = add_bridges ( torrcData )
2018-01-19 09:16:38 +00:00
torrc = open ( self . torConfigLocation , ' w ' )
torrc . write ( torrcData )
torrc . close ( )
2018-01-18 23:25:10 +00:00
return
2019-08-10 01:04:56 +00:00
def startTor ( self , gen_torrc = True ) :
2018-02-04 03:44:29 +00:00
'''
Start Tor with onion service on port 80 & socks proxy on random port
2018-01-20 00:59:05 +00:00
'''
2019-08-10 01:04:56 +00:00
if gen_torrc :
self . generateTorrc ( )
2018-03-03 07:35:13 +00:00
2018-01-28 01:53:24 +00:00
if os . path . exists ( ' ./tor ' ) :
2018-09-05 04:06:17 +00:00
self . torBinary = ' ./tor '
2018-03-03 07:35:13 +00:00
elif os . path . exists ( ' /usr/bin/tor ' ) :
2018-09-05 04:06:17 +00:00
self . torBinary = ' /usr/bin/tor '
2018-01-28 01:53:24 +00:00
else :
2018-09-05 04:06:17 +00:00
self . torBinary = ' tor '
2018-03-03 07:35:13 +00:00
2018-01-28 01:53:24 +00:00
try :
2018-09-05 04:06:17 +00:00
tor = subprocess . Popen ( [ self . torBinary , ' -f ' , self . torConfigLocation ] , stdout = subprocess . PIPE , stderr = subprocess . PIPE )
2018-01-28 01:53:24 +00:00
except FileNotFoundError :
2019-06-19 20:29:27 +00:00
logger . fatal ( " Tor was not found in your path or the Onionr directory. Please install Tor and try again. " , terminal = True )
2019-10-08 22:26:44 +00:00
return False
2018-02-22 09:33:30 +00:00
else :
# Test Tor Version
2018-09-05 04:06:17 +00:00
torVersion = subprocess . Popen ( [ self . torBinary , ' --version ' ] , stdout = subprocess . PIPE , stderr = subprocess . PIPE )
2018-02-22 09:33:30 +00:00
for line in iter ( torVersion . stdout . readline , b ' ' ) :
if ' Tor 0.2. ' in line . decode ( ) :
2019-06-19 20:29:27 +00:00
logger . fatal ( ' Tor 0.3+ required ' , terminal = True )
2019-10-08 22:26:44 +00:00
return False
2018-02-22 09:33:30 +00:00
torVersion . kill ( )
2018-01-20 00:59:05 +00:00
# wait for tor to get to 100% bootstrap
2018-05-18 06:22:16 +00:00
try :
for line in iter ( tor . stdout . readline , b ' ' ) :
2019-06-14 18:02:02 +00:00
if ' bootstrapped 100 ' in line . decode ( ) . lower ( ) :
2019-06-19 20:29:27 +00:00
logger . info ( line . decode ( ) )
2018-05-18 06:22:16 +00:00
break
2019-06-14 18:02:02 +00:00
elif ' opening socks listener ' in line . decode ( ) . lower ( ) :
2018-05-18 06:22:16 +00:00
logger . debug ( line . decode ( ) . replace ( ' \n ' , ' ' ) )
2019-10-08 22:26:44 +00:00
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 ' , ' ' ) )
2018-05-18 06:22:16 +00:00
else :
2019-06-19 20:29:27 +00:00
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 )
2018-05-18 06:22:16 +00:00
return False
except KeyboardInterrupt :
2019-07-20 00:01:16 +00:00
logger . fatal ( ' Got keyboard interrupt. Onionr will exit soon. ' , timestamp = False , terminal = True )
2018-01-28 01:53:24 +00:00
return False
2019-03-29 03:33:14 +00:00
2019-06-19 20:29:27 +00:00
logger . info ( ' Finished starting Tor. ' , terminal = True )
2019-07-22 23:59:48 +00:00
2018-01-19 21:28:34 +00:00
self . readyState = True
2018-03-03 07:35:13 +00:00
2018-12-09 17:29:39 +00:00
try :
myID = open ( self . dataDir + ' hs/hostname ' , ' r ' )
self . myID = myID . read ( ) . replace ( ' \n ' , ' ' )
myID . close ( )
except FileNotFoundError :
self . myID = " "
2018-03-03 07:35:13 +00:00
2018-09-26 04:58:11 +00:00
torPidFile = open ( self . dataDir + ' torPid.txt ' , ' w ' )
2018-01-27 08:43:36 +00:00
torPidFile . write ( str ( tor . pid ) )
torPidFile . close ( )
2018-02-04 03:44:29 +00:00
2019-12-09 11:02:37 +00:00
multiprocessing . Process ( target = watchdog . watchdog , args = [ os . getpid ( ) , tor . pid ] ) . start ( )
2018-01-28 01:53:24 +00:00
return True
2018-02-04 03:44:29 +00:00
2018-01-27 08:43:36 +00:00
def killTor ( self ) :
2018-02-04 03:44:29 +00:00
'''
Properly kill tor based on pid saved to file
'''
2018-03-03 07:35:13 +00:00
2018-01-27 08:43:36 +00:00
try :
2018-09-26 04:58:11 +00:00
pid = open ( self . dataDir + ' torPid.txt ' , ' r ' )
2018-01-27 08:43:36 +00:00
pidN = pid . read ( )
pid . close ( )
except FileNotFoundError :
return
2018-03-03 07:35:13 +00:00
2018-01-27 08:43:36 +00:00
try :
int ( pidN )
except :
return
2018-03-03 07:35:13 +00:00
2018-02-21 02:44:56 +00:00
try :
2019-06-15 01:31:01 +00:00
try :
os . kill ( int ( pidN ) , signal . SIGTERM )
except PermissionError :
# seems to happen on win 10
pass
2018-09-26 04:58:11 +00:00
os . remove ( self . dataDir + ' torPid.txt ' )
2018-02-21 02:44:56 +00:00
except ProcessLookupError :
pass
except FileNotFoundError :
pass
2019-09-05 09:40:31 +00:00
try :
time . sleep ( TOR_KILL_WAIT )
except KeyboardInterrupt :
pass
2019-08-28 02:15:56 +00:00
if ' windows ' == platform . system ( ) . lower ( ) :
os . system ( ' taskkill /PID %s /F ' % ( pidN , ) )
time . sleep ( 0.5 )
return
2019-08-11 18:53:38 +00:00
try :
os . kill ( int ( pidN ) , signal . SIGKILL )
except ( ProcessLookupError , PermissionError ) as e :
pass
2019-10-08 22:26:44 +00:00
try :
os . remove ( self . dataDir + ' tordata/lock ' )
except FileNotFoundError :
pass