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-06-13 06:58:17 +00:00
import subprocess , os , sys , time , signal , base64 , socket
2019-03-18 05:22:31 +00:00
from shutil import which
import logger , config
2019-06-16 06:06:32 +00:00
config . reload ( )
2018-12-18 23:48:17 +00:00
def getOpenPort ( ) :
2019-05-11 18:32:56 +00:00
# taken from (but modified) https://stackoverflow.com/a/2838309 by https://stackoverflow.com/users/133374/albert ccy-by-sa-3 https://creativecommons.org/licenses/by-sa/3.0/
# changes from source: import moved to top of file, bind specifically to localhost
2018-12-18 23:48:17 +00:00
s = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
s . bind ( ( " 127.0.0.1 " , 0 ) )
s . listen ( 1 )
port = s . getsockname ( ) [ 1 ]
s . close ( )
return port
2019-01-18 05:34:13 +00:00
def torBinary ( ) :
''' Return tor binary path or none if not exists '''
torPath = ' ./tor '
if not os . path . exists ( torPath ) :
torPath = which ( ' tor ' )
return torPath
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
self . dataDir = os . environ . get ( ' ONIONR_HOME ' , os . environ . get ( ' DATA_DIR ' , ' data/ ' ) )
if not self . dataDir . endswith ( ' / ' ) :
self . dataDir + = ' / '
2018-09-26 04:58:11 +00:00
self . torConfigLocation = self . dataDir + ' torrc '
2018-01-18 23:25:10 +00:00
self . readyState = False
2018-12-18 23:48:17 +00:00
self . socksPort = getOpenPort ( )
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
2018-12-18 23:48:17 +00:00
controlPort = getOpenPort ( )
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
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
HiddenServicePort 80 ''' + self.apiServerIP + ''' : ''' + str(self.hsPort)
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
def startTor ( self ) :
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
'''
2018-03-03 07:35:13 +00:00
2018-01-19 21:28:34 +00:00
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 )
2018-01-28 01:53:24 +00:00
sys . exit ( 1 )
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-03-07 04:58:21 +00:00
sys . exit ( 1 )
2018-02-22 09:33:30 +00:00
break
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 ' , ' ' ) )
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-06-19 20:29:27 +00:00
logger . fatal ( ' Got keyboard interrupt. Onionr will exit soon. ' , timestamp = False , level = logger . LEVEL_IMPORTANT , 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 )
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
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
2018-02-04 03:44:29 +00:00
return