#!/usr/bin/python3 """TorSwitch configures the onion router to redirect all internet traffic through SOCKS5 Tor proxy. This is version for SystemD. Licensed under GNU GPLv3+ terms. (c) 2023, xxx_stroboscope_420_xxx """ import sys import os 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} {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 NON_TOR_RNG = "192.168.1.0/24 192.168.0.0/24" 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 systemctl stop tor") 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 debian-tor tor -f {FileConfigTorrcTorswitcher} > /dev/null") log("Setting up iptables rules") iptables_rules = f""" NON_TOR="{NON_TOR_RNG}" TOR_UID={subprocess.getoutput('id -ur debian-tor')} 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? log("Restarting Network Manager") execute('service network-manager 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('id -ur debian-tor').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()