summaryrefslogtreecommitdiffstats
path: root/network/tor_manager.py
diff options
context:
space:
mode:
authorGab <24553253+gabrix73@users.noreply.github.com>2025-03-08 14:59:35 +0100
committerGitHub <noreply@github.com>2025-03-08 14:59:35 +0100
commit4a3ae9929f4657a0f2e9ccf393205655895bb501 (patch)
treecc4e8089f178b791875a9a3b34f842cf7466ba4f /network/tor_manager.py
parent489f247cffd1a63936a884c6d336b318179b39d8 (diff)
downloadsecurewhisper-4a3ae9929f4657a0f2e9ccf393205655895bb501.tar.gz
securewhisper-4a3ae9929f4657a0f2e9ccf393205655895bb501.tar.xz
securewhisper-4a3ae9929f4657a0f2e9ccf393205655895bb501.zip
Add files via upload
Diffstat (limited to 'network/tor_manager.py')
-rw-r--r--network/tor_manager.py196
1 files changed, 196 insertions, 0 deletions
diff --git a/network/tor_manager.py b/network/tor_manager.py
new file mode 100644
index 0000000..8f91b68
--- /dev/null
+++ b/network/tor_manager.py
@@ -0,0 +1,196 @@
+import os
+import subprocess
+import asyncio
+import socket
+import socks
+from typing import Optional
+import shutil
+import platform
+import sys
+import stat
+
+class TorManager:
+ def __init__(self):
+ self.tor_process: Optional[subprocess.Popen] = None
+ self.onion_address: Optional[str] = None
+ self.socks_port = 9052
+ self.control_port = 9053
+ self.base_dir = self._get_base_dir()
+
+ def _get_base_dir(self) -> str:
+ """Get the portable base directory for Tor files"""
+ if getattr(sys, 'frozen', False):
+ # If running as compiled executable
+ base_path = os.path.dirname(sys.executable)
+ else:
+ # If running from source
+ base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+ # Use a hidden directory in the user's home
+ return os.path.join(os.path.expanduser("~"), ".tormesh")
+
+ def _secure_dir_permissions(self, path: str):
+ """Set secure permissions on directory"""
+ os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) # 700
+
+ def _get_tor_path(self) -> str:
+ """Get platform-specific Tor binary path with fallbacks"""
+ if platform.system() == 'Windows':
+ tor_names = ['tor.exe']
+ else:
+ tor_names = ['tor']
+
+ # Search in system paths first
+ search_paths = [
+ "/usr/bin",
+ "/usr/local/bin",
+ "/opt/homebrew/bin", # For MacOS
+ os.path.join(self.base_dir, "bin")
+ ]
+
+ # Add PATH directories
+ if os.environ.get('PATH'):
+ search_paths.extend(os.environ['PATH'].split(os.pathsep))
+
+ for path in search_paths:
+ for name in tor_names:
+ tor_path = os.path.join(path, name)
+ if os.path.exists(tor_path) and os.access(tor_path, os.X_OK):
+ return tor_path
+
+ raise FileNotFoundError(
+ "Tor binary not found. Please ensure Tor is installed."
+ )
+
+ async def start(self):
+ """Start Tor with robust error handling and retries"""
+ # Create base directory with secure permissions
+ os.makedirs(self.base_dir, exist_ok=True)
+ self._secure_dir_permissions(self.base_dir)
+
+ # Create necessary subdirectories
+ data_dir = os.path.join(self.base_dir, "data")
+ hidden_service_dir = os.path.join(self.base_dir, "hidden_service")
+
+ for directory in [data_dir, hidden_service_dir]:
+ os.makedirs(directory, exist_ok=True)
+ self._secure_dir_permissions(directory)
+
+ # Create torrc configuration
+ torrc_path = os.path.join(self.base_dir, "torrc")
+ with open(torrc_path, "w") as f:
+ f.write(f"""
+SocksPort 127.0.0.1:{self.socks_port}
+ControlPort 127.0.0.1:{self.control_port}
+DataDirectory {data_dir}
+HiddenServiceDir {hidden_service_dir}
+HiddenServicePort 12345 127.0.0.1:12345
+# Improve anonymity
+UseEntryGuards 1
+NumEntryGuards 4
+# Improve resilience
+CircuitBuildTimeout 60
+LearnCircuitBuildTimeout 1
+MaxCircuitDirtiness 600
+NewCircuitPeriod 300
+""")
+ self._secure_dir_permissions(os.path.dirname(torrc_path))
+
+ # Start Tor process with retries
+ max_retries = 3
+ retry_delay = 2
+
+ for attempt in range(max_retries):
+ try:
+ tor_path = self._get_tor_path()
+ print(f"Starting Tor from: {tor_path}")
+
+ self.tor_process = subprocess.Popen(
+ [tor_path, "-f", torrc_path],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE
+ )
+
+ # Wait for Tor to start and verify it's running
+ if await self._wait_for_tor():
+ print(f"Tor started successfully on attempt {attempt + 1}")
+ return
+
+ except Exception as e:
+ print(f"Tor start attempt {attempt + 1} failed: {str(e)}")
+ if self.tor_process:
+ self.tor_process.terminate()
+ await asyncio.sleep(1)
+
+ if attempt < max_retries - 1:
+ await asyncio.sleep(retry_delay)
+ else:
+ raise Exception(f"Failed to start Tor after {max_retries} attempts")
+
+ async def _wait_for_tor(self) -> bool:
+ """Wait for Tor to start with improved verification"""
+ hostname_file = os.path.join(
+ self.base_dir, "hidden_service", "hostname"
+ )
+
+ for _ in range(30): # Wait up to 30 seconds
+ # Check if process is still running
+ if self.tor_process and self.tor_process.poll() is not None:
+ if self.tor_process.stderr:
+ stderr = self.tor_process.stderr.read().decode()
+ raise Exception(f"Tor process died: {stderr}")
+ raise Exception("Tor process died unexpectedly")
+
+ # Check for hostname file
+ if os.path.exists(hostname_file):
+ try:
+ with open(hostname_file, "r") as f:
+ self.onion_address = f.read().strip()
+
+ # Verify SOCKS port is listening
+ sock = socket.socket()
+ try:
+ sock.connect(("127.0.0.1", self.socks_port))
+ sock.close()
+ return True
+ except:
+ pass
+ except:
+ pass
+
+ await asyncio.sleep(1)
+
+ return False
+
+ async def stop(self):
+ """Stop Tor with graceful shutdown"""
+ if self.tor_process:
+ try:
+ self.tor_process.terminate()
+ try:
+ await asyncio.wait_for(
+ asyncio.create_task(self.tor_process.wait()),
+ timeout=5
+ )
+ except asyncio.TimeoutError:
+ self.tor_process.kill()
+
+ # Clear sensitive data
+ if self.onion_address:
+ self.onion_address = None
+
+ # Clean up temporary files securely
+ if os.path.exists(self.base_dir):
+ try:
+ # Securely delete sensitive files
+ for root, dirs, files in os.walk(self.base_dir):
+ for f in files:
+ path = os.path.join(root, f)
+ with open(path, 'wb') as fd:
+ fd.write(os.urandom(os.path.getsize(path)))
+ # Remove directory tree
+ shutil.rmtree(self.base_dir)
+ except:
+ pass
+ except:
+ pass # Ensure cleanup doesn't raise errors