Streamlit/MetaBlogizer: - Add 'gitea push <name>' command to both streamlitctl and metablogizerctl - Auto-creates Gitea repo via API if it doesn't exist - Initializes git, commits all files, and pushes to Gitea - Stores repo reference in UCI for future syncs Tor Shield: - Add 'wan_input_allow' option for server preset - Server mode now properly allows WAN inbound (ports 80, 443, 8443) - Uses nftables rules to integrate with OpenWrt firewall4 - Outbound traffic still routed through Tor (kill_switch) - Cleanup nftables rules on stop/disable Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
534 lines
12 KiB
Bash
534 lines
12 KiB
Bash
#!/bin/sh
|
|
# SecuBox Tor Shield - CLI management tool
|
|
# Copyright (C) 2025 CyberMind.fr
|
|
|
|
CONFIG="tor-shield"
|
|
TOR_CONTROL="/var/run/tor/control"
|
|
TOR_DATA="/var/lib/tor"
|
|
|
|
. /lib/functions.sh
|
|
|
|
usage() {
|
|
cat << EOF
|
|
Usage: torctl <command> [options]
|
|
|
|
Commands:
|
|
status Show Tor Shield status
|
|
enable [preset] Enable Tor Shield (presets: anonymous, selective, censored, server)
|
|
disable Disable Tor Shield
|
|
restart Restart Tor Shield
|
|
identity Get new Tor identity (new circuits)
|
|
circuits Show active circuits
|
|
exit-ip Show current exit IP address
|
|
leak-test Test for DNS/IP leaks
|
|
bridges Manage bridge configuration
|
|
hidden Manage hidden services
|
|
|
|
Options:
|
|
-h, --help Show this help
|
|
|
|
Examples:
|
|
torctl enable anonymous Enable with full anonymity preset
|
|
torctl enable server Enable server mode (public IP + Tor outbound)
|
|
torctl status Show current status
|
|
torctl identity Request new circuits
|
|
torctl exit-ip Show Tor exit IP
|
|
EOF
|
|
}
|
|
|
|
# Send command to Tor control socket
|
|
tor_control() {
|
|
if [ ! -S "$TOR_CONTROL" ]; then
|
|
echo "Error: Tor control socket not available"
|
|
return 1
|
|
fi
|
|
echo -e "$1" | nc -U "$TOR_CONTROL" 2>/dev/null
|
|
}
|
|
|
|
# Get bootstrap percentage
|
|
get_bootstrap() {
|
|
local status=$(tor_control "GETINFO status/bootstrap-phase")
|
|
echo "$status" | grep "PROGRESS=" | sed 's/.*PROGRESS=\([0-9]*\).*/\1/'
|
|
}
|
|
|
|
# Check if Tor is running
|
|
is_running() {
|
|
pgrep tor >/dev/null 2>&1
|
|
}
|
|
|
|
# Get current exit IP
|
|
get_exit_ip() {
|
|
# Try multiple services to get external IP through Tor
|
|
local socks_port=$(uci -q get tor-shield.socks.port || echo "9050")
|
|
local ip=""
|
|
|
|
# Try check.torproject.org first
|
|
ip=$(curl -s --socks5-hostname 127.0.0.1:$socks_port https://check.torproject.org/api/ip 2>/dev/null | jsonfilter -e '@.IP' 2>/dev/null)
|
|
|
|
if [ -z "$ip" ]; then
|
|
# Fallback to ipinfo.io
|
|
ip=$(curl -s --socks5-hostname 127.0.0.1:$socks_port https://ipinfo.io/ip 2>/dev/null)
|
|
fi
|
|
|
|
echo "${ip:-unknown}"
|
|
}
|
|
|
|
# Get real IP (without Tor)
|
|
get_real_ip() {
|
|
local ip=$(curl -s --max-time 5 https://ipinfo.io/ip 2>/dev/null)
|
|
echo "${ip:-unknown}"
|
|
}
|
|
|
|
# Status command
|
|
cmd_status() {
|
|
local enabled mode bootstrap exit_ip circuit_count
|
|
|
|
config_load "$CONFIG"
|
|
config_get enabled main enabled '0'
|
|
config_get mode main mode 'transparent'
|
|
|
|
echo "Tor Shield Status"
|
|
echo "================="
|
|
|
|
if [ "$enabled" != "1" ]; then
|
|
echo "Status: DISABLED"
|
|
echo ""
|
|
echo "Enable with: torctl enable"
|
|
return 0
|
|
fi
|
|
|
|
if ! is_running; then
|
|
echo "Status: ENABLED but NOT RUNNING"
|
|
echo ""
|
|
echo "Start with: /etc/init.d/tor-shield start"
|
|
return 1
|
|
fi
|
|
|
|
bootstrap=$(get_bootstrap)
|
|
bootstrap=${bootstrap:-0}
|
|
|
|
echo "Status: ACTIVE"
|
|
echo "Mode: $mode"
|
|
echo "Bootstrap: ${bootstrap}%"
|
|
|
|
if [ "$bootstrap" -ge 100 ]; then
|
|
exit_ip=$(get_exit_ip)
|
|
echo "Exit IP: $exit_ip"
|
|
|
|
# Get circuit count
|
|
circuit_count=$(tor_control "GETINFO circuit-status" | grep -c "BUILT")
|
|
echo "Circuits: ${circuit_count:-0}"
|
|
else
|
|
echo "Exit IP: (bootstrapping...)"
|
|
fi
|
|
|
|
# Show config details
|
|
local dns_over_tor kill_switch wan_input_allow
|
|
config_get dns_over_tor main dns_over_tor '1'
|
|
config_get kill_switch main kill_switch '1'
|
|
config_get wan_input_allow main wan_input_allow '0'
|
|
|
|
echo ""
|
|
echo "Configuration:"
|
|
echo " DNS over Tor: $([ "$dns_over_tor" = "1" ] && echo "Yes" || echo "No")"
|
|
echo " Kill Switch: $([ "$kill_switch" = "1" ] && echo "Yes" || echo "No")"
|
|
echo " WAN Input Allow: $([ "$wan_input_allow" = "1" ] && echo "Yes (Server Mode)" || echo "No")"
|
|
|
|
# Bridge status
|
|
local bridges_enabled
|
|
config_get bridges_enabled bridges enabled '0'
|
|
if [ "$bridges_enabled" = "1" ]; then
|
|
local bridge_type
|
|
config_get bridge_type bridges type 'obfs4'
|
|
echo " Bridges: $bridge_type"
|
|
fi
|
|
}
|
|
|
|
# Enable command
|
|
cmd_enable() {
|
|
local preset="${1:-anonymous}"
|
|
|
|
echo "Enabling Tor Shield with preset: $preset"
|
|
|
|
# Load preset configuration
|
|
config_load "$CONFIG"
|
|
|
|
local preset_mode preset_dns preset_kill preset_bridges preset_lan_proxy preset_wan_input
|
|
config_get preset_mode "$preset" mode 'transparent'
|
|
config_get preset_dns "$preset" dns_over_tor '1'
|
|
config_get preset_kill "$preset" kill_switch '1'
|
|
config_get preset_bridges "$preset" use_bridges '0'
|
|
config_get preset_lan_proxy "$preset" lan_proxy '0'
|
|
config_get preset_wan_input "$preset" wan_input_allow '0'
|
|
|
|
# Apply preset settings
|
|
uci set tor-shield.main.enabled='1'
|
|
uci set tor-shield.main.mode="$preset_mode"
|
|
uci set tor-shield.main.dns_over_tor="$preset_dns"
|
|
uci set tor-shield.main.kill_switch="$preset_kill"
|
|
uci set tor-shield.main.wan_input_allow="$preset_wan_input"
|
|
uci set tor-shield.trans.lan_proxy="$preset_lan_proxy"
|
|
|
|
if [ "$preset_bridges" = "1" ]; then
|
|
uci set tor-shield.bridges.enabled='1'
|
|
fi
|
|
|
|
uci commit tor-shield
|
|
|
|
# Start service
|
|
/etc/init.d/tor-shield restart
|
|
|
|
echo "Tor Shield enabled. Waiting for bootstrap..."
|
|
|
|
# Wait for bootstrap
|
|
local count=0
|
|
while [ $count -lt 60 ]; do
|
|
if is_running; then
|
|
local progress=$(get_bootstrap)
|
|
if [ -n "$progress" ] && [ "$progress" -ge 100 ]; then
|
|
echo "Bootstrap complete!"
|
|
cmd_status
|
|
return 0
|
|
fi
|
|
echo "Bootstrap: ${progress:-0}%"
|
|
fi
|
|
sleep 2
|
|
count=$((count + 1))
|
|
done
|
|
|
|
echo "Warning: Bootstrap taking longer than expected"
|
|
cmd_status
|
|
}
|
|
|
|
# Disable command
|
|
cmd_disable() {
|
|
echo "Disabling Tor Shield..."
|
|
|
|
uci set tor-shield.main.enabled='0'
|
|
uci commit tor-shield
|
|
|
|
/etc/init.d/tor-shield stop
|
|
|
|
echo "Tor Shield disabled."
|
|
}
|
|
|
|
# Restart command
|
|
cmd_restart() {
|
|
echo "Restarting Tor Shield..."
|
|
/etc/init.d/tor-shield restart
|
|
sleep 2
|
|
cmd_status
|
|
}
|
|
|
|
# New identity command
|
|
cmd_identity() {
|
|
if ! is_running; then
|
|
echo "Error: Tor is not running"
|
|
return 1
|
|
fi
|
|
|
|
echo "Requesting new identity..."
|
|
|
|
# Send NEWNYM signal
|
|
local result=$(tor_control "SIGNAL NEWNYM")
|
|
|
|
if echo "$result" | grep -q "250 OK"; then
|
|
echo "New identity requested successfully."
|
|
echo "New circuits will be established shortly."
|
|
sleep 3
|
|
echo ""
|
|
echo "New exit IP: $(get_exit_ip)"
|
|
else
|
|
echo "Failed to request new identity"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Circuits command
|
|
cmd_circuits() {
|
|
if ! is_running; then
|
|
echo "Error: Tor is not running"
|
|
return 1
|
|
fi
|
|
|
|
echo "Active Circuits"
|
|
echo "==============="
|
|
|
|
local circuits=$(tor_control "GETINFO circuit-status")
|
|
|
|
echo "$circuits" | grep "BUILT" | while read line; do
|
|
# Parse circuit info
|
|
local id=$(echo "$line" | awk '{print $1}')
|
|
local status=$(echo "$line" | awk '{print $2}')
|
|
local path=$(echo "$line" | awk '{print $3}')
|
|
|
|
if [ -n "$path" ]; then
|
|
# Extract node names/fingerprints
|
|
echo "Circuit $id: $path"
|
|
fi
|
|
done
|
|
|
|
# Get circuit count
|
|
local count=$(echo "$circuits" | grep -c "BUILT")
|
|
echo ""
|
|
echo "Total built circuits: ${count:-0}"
|
|
}
|
|
|
|
# Exit IP command
|
|
cmd_exit_ip() {
|
|
if ! is_running; then
|
|
echo "Error: Tor is not running"
|
|
return 1
|
|
fi
|
|
|
|
local bootstrap=$(get_bootstrap)
|
|
if [ -z "$bootstrap" ] || [ "$bootstrap" -lt 100 ]; then
|
|
echo "Tor is still bootstrapping (${bootstrap:-0}%)"
|
|
return 1
|
|
fi
|
|
|
|
local exit_ip=$(get_exit_ip)
|
|
local real_ip=$(get_real_ip)
|
|
|
|
echo "Real IP: $real_ip"
|
|
echo "Tor Exit IP: $exit_ip"
|
|
|
|
if [ "$exit_ip" != "$real_ip" ] && [ "$exit_ip" != "unknown" ]; then
|
|
echo "Status: PROTECTED"
|
|
else
|
|
echo "Status: WARNING - IPs match or unknown"
|
|
fi
|
|
}
|
|
|
|
# Leak test command
|
|
cmd_leak_test() {
|
|
if ! is_running; then
|
|
echo "Error: Tor is not running"
|
|
return 1
|
|
fi
|
|
|
|
echo "Running leak test..."
|
|
echo ""
|
|
|
|
local socks_port=$(uci -q get tor-shield.socks.port || echo "9050")
|
|
local leaks=0
|
|
|
|
# Test 1: IP leak
|
|
echo "1. IP Leak Test"
|
|
local tor_ip=$(get_exit_ip)
|
|
local real_ip=$(get_real_ip)
|
|
|
|
if [ "$tor_ip" = "$real_ip" ] || [ "$tor_ip" = "unknown" ]; then
|
|
echo " FAIL: IP may be leaking"
|
|
leaks=$((leaks + 1))
|
|
else
|
|
echo " PASS: Tor IP ($tor_ip) differs from real IP ($real_ip)"
|
|
fi
|
|
|
|
# Test 2: DNS leak (via Tor check)
|
|
echo ""
|
|
echo "2. Tor Detection Test"
|
|
local tor_check=$(curl -s --socks5-hostname 127.0.0.1:$socks_port https://check.torproject.org/api/ip 2>/dev/null)
|
|
local is_tor=$(echo "$tor_check" | jsonfilter -e '@.IsTor' 2>/dev/null)
|
|
|
|
if [ "$is_tor" = "true" ]; then
|
|
echo " PASS: Traffic confirmed going through Tor"
|
|
else
|
|
echo " FAIL: Traffic may not be going through Tor"
|
|
leaks=$((leaks + 1))
|
|
fi
|
|
|
|
# Test 3: DNS resolution through Tor
|
|
echo ""
|
|
echo "3. DNS Over Tor Test"
|
|
local dns_over_tor=$(uci -q get tor-shield.main.dns_over_tor)
|
|
if [ "$dns_over_tor" = "1" ]; then
|
|
# Try resolving a .onion address (only works through Tor)
|
|
local dns_test=$(curl -s --socks5-hostname 127.0.0.1:$socks_port http://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/ 2>/dev/null | head -c 100)
|
|
if [ -n "$dns_test" ]; then
|
|
echo " PASS: DNS resolution working through Tor"
|
|
else
|
|
echo " WARNING: Could not verify DNS over Tor"
|
|
fi
|
|
else
|
|
echo " SKIP: DNS over Tor is disabled"
|
|
fi
|
|
|
|
echo ""
|
|
echo "===================="
|
|
if [ $leaks -eq 0 ]; then
|
|
echo "Result: ALL TESTS PASSED"
|
|
else
|
|
echo "Result: $leaks POTENTIAL LEAK(S) DETECTED"
|
|
fi
|
|
}
|
|
|
|
# Bridges command
|
|
cmd_bridges() {
|
|
local action="${1:-status}"
|
|
|
|
case "$action" in
|
|
status)
|
|
local enabled type
|
|
config_load "$CONFIG"
|
|
config_get enabled bridges enabled '0'
|
|
config_get type bridges type 'obfs4'
|
|
|
|
echo "Bridge Configuration"
|
|
echo "===================="
|
|
echo "Enabled: $([ "$enabled" = "1" ] && echo "Yes" || echo "No")"
|
|
echo "Type: $type"
|
|
echo ""
|
|
echo "Bridge lines:"
|
|
config_list_foreach bridges bridge_lines echo_bridge
|
|
;;
|
|
enable)
|
|
uci set tor-shield.bridges.enabled='1'
|
|
uci commit tor-shield
|
|
echo "Bridges enabled. Restart Tor Shield to apply."
|
|
;;
|
|
disable)
|
|
uci set tor-shield.bridges.enabled='0'
|
|
uci commit tor-shield
|
|
echo "Bridges disabled. Restart Tor Shield to apply."
|
|
;;
|
|
add)
|
|
shift
|
|
local bridge_line="$*"
|
|
if [ -n "$bridge_line" ]; then
|
|
uci add_list tor-shield.bridges.bridge_lines="$bridge_line"
|
|
uci commit tor-shield
|
|
echo "Bridge added. Restart Tor Shield to apply."
|
|
else
|
|
echo "Usage: torctl bridges add <bridge_line>"
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Usage: torctl bridges [status|enable|disable|add <line>]"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
echo_bridge() {
|
|
echo " $1"
|
|
}
|
|
|
|
# Hidden services command
|
|
cmd_hidden() {
|
|
local action="${1:-list}"
|
|
|
|
case "$action" in
|
|
list)
|
|
echo "Hidden Services"
|
|
echo "==============="
|
|
config_load "$CONFIG"
|
|
config_foreach list_hidden_service hidden_service
|
|
;;
|
|
add)
|
|
shift
|
|
local name="$1"
|
|
local local_port="${2:-80}"
|
|
local virtual_port="${3:-80}"
|
|
|
|
if [ -z "$name" ]; then
|
|
echo "Usage: torctl hidden add <name> [local_port] [virtual_port]"
|
|
return 1
|
|
fi
|
|
|
|
# Create new hidden service config
|
|
uci set tor-shield.hs_$name=hidden_service
|
|
uci set tor-shield.hs_$name.name="$name"
|
|
uci set tor-shield.hs_$name.enabled='1'
|
|
uci set tor-shield.hs_$name.local_port="$local_port"
|
|
uci set tor-shield.hs_$name.virtual_port="$virtual_port"
|
|
uci commit tor-shield
|
|
|
|
echo "Hidden service '$name' created."
|
|
echo "Restart Tor Shield to generate .onion address."
|
|
;;
|
|
remove)
|
|
local name="$2"
|
|
if [ -z "$name" ]; then
|
|
echo "Usage: torctl hidden remove <name>"
|
|
return 1
|
|
fi
|
|
|
|
uci delete tor-shield.hs_$name 2>/dev/null
|
|
uci commit tor-shield
|
|
|
|
echo "Hidden service '$name' removed."
|
|
;;
|
|
*)
|
|
echo "Usage: torctl hidden [list|add|remove]"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
list_hidden_service() {
|
|
local cfg="$1"
|
|
local enabled name local_port virtual_port
|
|
|
|
config_get enabled "$cfg" enabled '0'
|
|
config_get name "$cfg" name "$cfg"
|
|
config_get local_port "$cfg" local_port '80'
|
|
config_get virtual_port "$cfg" virtual_port '80'
|
|
|
|
local status="disabled"
|
|
[ "$enabled" = "1" ] && status="enabled"
|
|
|
|
local hostname_file="$TOR_DATA/hidden_service_$name/hostname"
|
|
local onion_addr="(not generated)"
|
|
if [ -f "$hostname_file" ]; then
|
|
onion_addr=$(cat "$hostname_file")
|
|
fi
|
|
|
|
echo ""
|
|
echo "Service: $name ($status)"
|
|
echo " Address: $onion_addr"
|
|
echo " Port: $virtual_port -> 127.0.0.1:$local_port"
|
|
}
|
|
|
|
# Main dispatcher
|
|
case "$1" in
|
|
status)
|
|
cmd_status
|
|
;;
|
|
enable)
|
|
shift
|
|
cmd_enable "$@"
|
|
;;
|
|
disable)
|
|
cmd_disable
|
|
;;
|
|
restart)
|
|
cmd_restart
|
|
;;
|
|
identity|new-identity|newnym)
|
|
cmd_identity
|
|
;;
|
|
circuits|circuit)
|
|
cmd_circuits
|
|
;;
|
|
exit-ip|ip)
|
|
cmd_exit_ip
|
|
;;
|
|
leak-test|leaks|test)
|
|
cmd_leak_test
|
|
;;
|
|
bridges|bridge)
|
|
shift
|
|
cmd_bridges "$@"
|
|
;;
|
|
hidden|hs|onion)
|
|
shift
|
|
cmd_hidden "$@"
|
|
;;
|
|
-h|--help|help)
|
|
usage
|
|
;;
|
|
*)
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|