torswitch/torswitch_openrc.py

361 lines
10 KiB
Python
Executable File

#!/usr/bin/python3
"""TorSwitch configures the onion router to redirect all internet traffic through SOCKS5 Tor proxy. This is version for OpenRC. Warning! It is mostly untested!
Licensed under GNU GPLv3+ terms.
(c) 2023, xxx_stroboscope_420_xxx
"""
import os, sys
import getopt
import requests
import subprocess
import time
import random
import signal
from stem import Signal
from stem.control import Controller
from packaging import version
VERSION = "1.2"
IP_API = "https://api.ipify.org/?format=json"
TOR_CHECK = "https://check.torproject.org"
UA = "Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0"
class font:
# Colors of foreground
RED = "\033[31m"
LIGHTRED = "\033[91m"
YELLOW = "\033[93m"
GREEN = "\033[92m"
BLUE = "\033[34m"
LIGHTBLUE = "\033[94m"
GRAY = "\033[90m"
WHITE = "\033[97m"
# Colors of background
BG_RED = "\033[41m"
BG_BLUE = "\033[104m"
# Special
CRIT = BG_RED + WHITE # Critical error
ERR = RED # Just error
WARN = YELLOW # Warning
TIME = GRAY # Timestamp
EXEC = LIGHTBLUE # Executed command
# Not colors
BOLD = "\033[1m"
ENDC = "\033[0m"
LOGO = f"""{font.RED + font.BOLD}
_____
|_ _|__ _ __
| |/ _ \| '__|
| | (_) | |
|_|\___/|_| SWITCH v{VERSION}
( OpenRC edition )
{font.ENDC}"""
USAGE = f"""
{font.BOLD}Usage:{font.ENDC}
-s, --start
Setup Tor as system-wide proxy
-r, --switch
Request new Tor exit node
-x, --stop
Shut down onion router and restore system defaults
-i, --info
Show information about current connection
-h, --help
Show this text and exit
"""
TOR_TRANS_PORT = 9040
TOR_SOCKS_PORT = 9050
TOR_CONTROL_PORT = 9051
TOR_DNS_PORT = 9053
TOR_HTTP_PORT = 9080
TOR_USER = "tor"
NON_TOR_RNG = "192.168.1.0/24 192.168.0.0/24"
#FileConfigTorrcReal = "/etc/tor/torrc"
FileConfigTorrcTorswitcher = "/etc/tor/torswitcherrc"
FileConfigResolv = "/etc/resolv.conf"
FileLogTorswitcher = "./torswitcher.log"
FileLogTor = "/var/log/tor/notices.log"
DirTorData = "/var/lib/tor"
StringConfigTorrcTorSwitcher = f"""
SOCKSPort {TOR_SOCKS_PORT}
HTTPTunnelPort {TOR_HTTP_PORT}
Log notice file {FileLogTor}
DataDirectory {DirTorData}
VirtualAddrNetwork 10.0.0.0/10
AutomapHostsOnResolve 1
TransPort {TOR_TRANS_PORT}
DNSPort {TOR_DNS_PORT}
ControlPort {TOR_CONTROL_PORT}
RunAsDaemon 1
"""
StringConfigResolv = "nameserver 127.0.0.1"
LogToFile = False
# Remove any colors from text
def strip_colors(text):
return text.replace(font.RED,"").replace(font.LIGHTRED,"").replace(font.YELLOW,"").replace(font.GREEN,"").replace(font.BLUE,"")\
.replace(font.LIGHTBLUE,"").replace(font.GRAY,"").replace(font.WHITE,"").replace(font.BG_RED,"").replace(font.CRIT,"")\
.replace(font.ERR,"").replace(font.WARN,"").replace(font.TIME,"").replace(font.EXEC,"").replace(font.BOLD,"").replace(font.ENDC,"")\
.replace(font.BG_BLUE,"")
# Print log line
def log(text, endt="\n"):
global LogToFile
global FileLogTorswitcher
now = time.strftime("%H:%M:%S", time.localtime())
print(f"{font.TIME}[{now}]{font.ENDC} {text}", end=endt)
if LogToFile:
try:
with open(FileLogTorswitcher, "at") as fd:
fd.write(f"[{now}] {strip_colors(text)}{endt}")
except Exception as exc:
print(f"{font.ERR}Error: can not write log line to file '{FileLogTorswitcher}'{font.ENDC}")
# Print log line w/o time stamp
def logapp(text, endt="\n"):
global LogToFile
global FileLogTorswitcher
print(f"{text}", end=endt)
if LogToFile:
try:
with open(FileLogTorswitcher, "at") as fd:
fd.write(f"{strip_colors(text)}{endt}")
except Exception as exc:
print(f"{font.ERR}Error: can not write log line to file '{FileLogTorswitcher}'{font.ENDC}")
# Handler for interrupt signal
def sigint_handler(signum, frame):
log(f"{font.WARN}User interrupt ! shutting down{font.ENDC}")
stop_tor_proxy()
# Execute command with printing it at terminal
def execute(cmd):
log(f"{font.EXEC}Executing '{cmd}'...{font.ENDC}")
os.system(cmd)
# Get current public IP
def ip():
retries = 20
while retries:
retries -= 1
try:
jsonRes = requests.get(IP_API,headers={"User-Agent":UA}).json()
return jsonRes["ip"]
except:
log(f"{font.ERR}Error: cant fetch IP{font.ENDC}")
continue
return "cant fetch ip address"
# Check if we connected via Tor network
def check_tor():
retries = 20
while retries:
retries -= 1
try:
resp = requests.get(TOR_CHECK)
if resp.status_code != 200:
log(f"{font.ERR}Error: cant access check.torproject.org{font.ENDC}")
continue
if "Congratulations. This browser is configured to use Tor." in resp.text:
return True
else:
return False
except:
log(f"{font.ERR}Error: something went wrong while trying to access check.torproject.org{font.ENDC}")
continue
log(f"{font.ERR}Error: retries limit exceeded{font.ENDC}")
return False
# Check if we running as root
def check_root():
if os.geteuid() != 0:
log(f"{font.CRIT}CRITICAL: you must be root, say the magic word 'sudo'. Aborting...{font.ENDC}")
sys.exit(1)
# Check if file contains supplied string
def file_contains(path, text):
try:
with open(path, "rt") as fd:
buff = fd.read()
return (text in buff)
except Exception as exc:
log(f"{font.WARN}Warning: error occured while trying to read file '{path}': {exc}{font.ENDC}")
return False
# Print logo
def logo():
print(LOGO) # TODO: make this look better
# Print usage text
def usage():
logo()
print(USAGE)
sys.exit(0)
def setup_tor_proxy():
log(f"{font.GREEN}Trying to setup onion router as system-wide proxy{font.ENDC}")
check_root()
if os.path.exists(FileConfigTorrcTorswitcher) and file_contains(FileConfigTorrcTorswitcher, StringConfigTorrcTorSwitcher):
log(f"Torrc file ('{FileConfigTorrcTorswitcher}') already configured")
else:
log("Writing torcc file... ", "")
with open(FileConfigTorrcTorswitcher, "wt") as fd:
fd.write(StringConfigTorrcTorSwitcher)
logapp(f"{font.GREEN}[done]{font.ENDC}")
if file_contains(FileConfigResolv, StringConfigResolv):
log(f"DNS '{FileConfigResolv}' file already configured")
else:
log(f"Saving original DNS '{FileConfigResolv}' file")
execute(f"sudo cp '{FileConfigResolv}' '{FileConfigResolv}.bak'")
log("Now creating our new... ", "")
with open(FileConfigResolv, "wt") as fd:
fd.write(StringConfigResolv)
logapp(f"{font.GREEN}[done]{font.ENDC}")
log("Stopping tor service")
execute("sudo rc-service tor stop")
log("Freeing tor control port")
execute(f"sudo fuser -k {TOR_CONTROL_PORT}/tcp > /dev/null 2>&1")
log("Starting new tor daemon")
execute(f"sudo -u {TOR_USER} tor -f {FileConfigTorrcTorswitcher} > /dev/null")
log("Setting up iptables rules")
iptables_rules = f"""
NON_TOR="{NON_TOR_RNG}"
TOR_UID={subprocess.getoutput(f'id -ur {TOR_USER}')}
TRANS_PORT="{TOR_TRANS_PORT}"
iptables -F
iptables -t nat -F
iptables -t nat -A OUTPUT -m owner --uid-owner $TOR_UID -j RETURN
iptables -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-ports {TOR_DNS_PORT}
for NET in $NON_TOR 127.0.0.0/9 127.128.0.0/10; do
iptables -t nat -A OUTPUT -d $NET -j RETURN
done
iptables -t nat -A OUTPUT -p tcp --syn -j REDIRECT --to-ports $TRANS_PORT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
for NET in $NON_TOR 127.0.0.0/8; do
iptables -A OUTPUT -d $NET -j ACCEPT
done
iptables -A OUTPUT -m owner --uid-owner $TOR_UID -j ACCEPT
iptables -A OUTPUT -j REJECT
"""
execute(iptables_rules)
log("Are we connected to Tor?.. ", "")
if not check_tor():
logapp(f"{font.RED}[no]{font.ENDC}")
log(f"{font.CRIT}CRITICAL: we are NOT connected to Tor network! Reverting changes...{font.ENDC}")
stop_tor_proxy()
return False
logapp(f"{font.GREEN}[yes]{font.ENDC}")
log("Fetching current IP")
log(f"Current IP is {font.GREEN}{ip()}{font.ENDC}")
return True
def stop_tor_proxy():
log(f"{font.RED}Restoring system defaults and shutting down onion router{font.ENDC}")
check_root()
log(f"Restoring DNS '{FileConfigResolv}' file")
execute(f"mv '{FileConfigResolv}.bak' '{FileConfigResolv}'")
log(f"Flushing iptables, resetting to default")
IpFlush = """
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -t nat -F
iptables -t mangle -F
iptables -F
iptables -X
"""
execute(IpFlush)
log("Freeing tor control port")
execute(f"sudo fuser -k {TOR_CONTROL_PORT}/tcp > /dev/null 2>&1")
# TODO: stop tor?
# TODO: V check if it really necessary V
#log("Restarting Network Manager")
#execute('rc-service NetworkManager restart')
time.sleep(3)
# R u rly want make request 2 some proprietary service without any proxying?
#log("Fetching current IP")
#log(f"Current IP is {font.GREEN}{ip()}{font.ENDC}")
def switch_exit_node():
log(f"{font.YELLOW}Requesting new Tor exit node{font.ENDC}")
check_root()
if not check_tor():
log(f"{font.CRIT}CRITICAL: you are not connected to Tor network{font.ENDC}")
return
log("Fetching current IP")
log(f"Current IP is {font.GREEN}{ip()}{font.ENDC}")
log("Checking tor pid... ", "")
if not subprocess.getoutput(f'id -ur {TOR_USER}').isdigit():
log(f"{font.CRIT}seems like there is no tor process running! Aborting...{font.ENDC}")
sys.exit(2)
logapp(f"{font.GREEN}[OK]{font.ENDC}")
log("Please wait...")
time.sleep(7)
log("Requesting new circuit... ", "")
with Controller.from_port(port=TOR_CONTROL_PORT) as controller:
controller.authenticate()
controller.signal(Signal.NEWNYM)
logapp(f"{font.GREEN}[done]{font.ENDC}")
log("Fetching updated IP")
log(f"New IP is {font.GREEN}{ip()}{font.ENDC}")
def show_connection_info():
logo()
log(f"{font.BG_BLUE + font.WHITE}Tor status:{font.ENDC} ", "")
if not check_tor():
logapp(f"{font.RED}DISCONNECTED{font.ENDC}")
return
logapp(f"{font.GREEN}CONNECTED{font.ENDC}")
log(f"{font.BG_BLUE + font.WHITE}IP:{font.ENDC} {font.GREEN}{ip()}{font.ENDC}\n")
if __name__ == "__main__":
signal.signal(signal.SIGINT, sigint_handler)
if len(sys.argv) <= 1:
usage()
try:
(opts, args) = getopt.getopt(sys.argv[1:], "sxrih", [
"start", "stop", "switch", "info", "help"])
except:
usage()
sys.exit(3)
if not opts:
usage()
sys.exit(3)
for (o, a) in opts:
if o in ("-s", "--start"):
if setup_tor_proxy():
log(f"{font.BLUE}>>>{font.ENDC} {font.GREEN}Now you have +100 anonimity points!{font.ENDC} {font.BLUE}<<<{font.ENDC}")
elif o in ("-x", "--stop"):
stop_tor_proxy()
log(f"{font.BLUE}>>>{font.ENDC} {font.RED}Bye, anonymity, bye!{font.ENDC} {font.BLUE}<<<{font.ENDC}")
elif o in ("-r", "--switch"):
switch_exit_node()
elif o in ("-i", "--info"):
show_connection_info()
elif o in ("-h", "--help"):
usage()
else:
usage()