#!/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()