- Add get_web_token to RPCD ACL permissions (was missing, causing 403) - Add fallback token retrieval from container via lxc-attach - Improve token capture regex to support alphanumeric tokens - Fix startup script with background process + tee for reliable capture - Add IP forwarding enablement for transparent proxy mode - Fix bypass rule for traffic destined to router itself Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
559 lines
15 KiB
Bash
Executable File
559 lines
15 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# RPCD backend for mitmproxy LuCI interface
|
|
# Copyright (C) 2025 CyberMind.fr (SecuBox)
|
|
#
|
|
|
|
. /lib/functions.sh
|
|
|
|
DATA_DIR=$(uci -q get mitmproxy.main.data_path || echo "/srv/mitmproxy")
|
|
LXC_NAME="mitmproxy"
|
|
CONF_DIR="$DATA_DIR"
|
|
LOG_FILE="$DATA_DIR/requests.log"
|
|
FLOW_FILE="$DATA_DIR/flows.bin"
|
|
|
|
# Get service status
|
|
get_status() {
|
|
local running=0
|
|
local pid=""
|
|
local mode="unknown"
|
|
local web_url=""
|
|
local lxc_state=""
|
|
local nft_active="false"
|
|
|
|
# Check LXC container status
|
|
if command -v lxc-info >/dev/null 2>&1; then
|
|
lxc_state=$(lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -oE 'RUNNING|STOPPED' || echo "UNKNOWN")
|
|
if [ "$lxc_state" = "RUNNING" ]; then
|
|
running=1
|
|
mode="mitmweb"
|
|
pid=$(lxc-info -n "$LXC_NAME" -p 2>/dev/null | grep -oE '[0-9]+' || echo "0")
|
|
fi
|
|
fi
|
|
|
|
# Fallback: check for direct process
|
|
if [ "$running" = "0" ]; then
|
|
if pgrep mitmweb >/dev/null 2>&1; then
|
|
running=1
|
|
pid=$(pgrep mitmweb | head -1)
|
|
mode="mitmweb"
|
|
elif pgrep mitmdump >/dev/null 2>&1; then
|
|
running=1
|
|
pid=$(pgrep mitmdump | head -1)
|
|
mode="mitmdump"
|
|
fi
|
|
fi
|
|
|
|
# Check nftables rules
|
|
if command -v nft >/dev/null 2>&1; then
|
|
nft list table inet mitmproxy >/dev/null 2>&1 && nft_active="true"
|
|
fi
|
|
|
|
local enabled=$(uci -q get mitmproxy.main.enabled || echo "0")
|
|
local proxy_port=$(uci -q get mitmproxy.main.proxy_port || echo "8080")
|
|
local web_port=$(uci -q get mitmproxy.main.web_port || echo "8081")
|
|
local proxy_mode=$(uci -q get mitmproxy.main.mode || echo "regular")
|
|
local filtering_enabled=$(uci -q get mitmproxy.filtering.enabled || echo "0")
|
|
local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
|
|
|
|
[ "$running" = "1" ] && [ "$mode" = "mitmweb" ] && web_url="http://${router_ip}:${web_port}"
|
|
|
|
cat <<EOF
|
|
{
|
|
"running": $([ "$running" = "1" ] && echo "true" || echo "false"),
|
|
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
|
"pid": ${pid:-0},
|
|
"mode": "$mode",
|
|
"proxy_mode": "$proxy_mode",
|
|
"lxc_state": "$lxc_state",
|
|
"proxy_port": $proxy_port,
|
|
"web_port": $web_port,
|
|
"web_url": "$web_url",
|
|
"ca_installed": $([ -f "$CONF_DIR/mitmproxy-ca-cert.pem" ] && echo "true" || echo "false"),
|
|
"nft_active": $nft_active,
|
|
"filtering_enabled": $([ "$filtering_enabled" = "1" ] && echo "true" || echo "false")
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Get main configuration
|
|
get_config() {
|
|
local enabled=$(uci -q get mitmproxy.main.enabled || echo "0")
|
|
local mode=$(uci -q get mitmproxy.main.mode || echo "regular")
|
|
local proxy_port=$(uci -q get mitmproxy.main.proxy_port || echo "8080")
|
|
local web_host=$(uci -q get mitmproxy.main.web_host || echo "0.0.0.0")
|
|
local web_port=$(uci -q get mitmproxy.main.web_port || echo "8081")
|
|
local data_path=$(uci -q get mitmproxy.main.data_path || echo "/srv/mitmproxy")
|
|
local memory_limit=$(uci -q get mitmproxy.main.memory_limit || echo "256M")
|
|
local ssl_insecure=$(uci -q get mitmproxy.main.ssl_insecure || echo "0")
|
|
local anticache=$(uci -q get mitmproxy.main.anticache || echo "0")
|
|
local anticomp=$(uci -q get mitmproxy.main.anticomp || echo "0")
|
|
local flow_detail=$(uci -q get mitmproxy.main.flow_detail || echo "1")
|
|
|
|
cat <<EOF
|
|
{
|
|
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
|
"mode": "$mode",
|
|
"proxy_port": $proxy_port,
|
|
"web_host": "$web_host",
|
|
"web_port": $web_port,
|
|
"data_path": "$data_path",
|
|
"memory_limit": "$memory_limit",
|
|
"ssl_insecure": $([ "$ssl_insecure" = "1" ] && echo "true" || echo "false"),
|
|
"anticache": $([ "$anticache" = "1" ] && echo "true" || echo "false"),
|
|
"anticomp": $([ "$anticomp" = "1" ] && echo "true" || echo "false"),
|
|
"flow_detail": $flow_detail
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Get transparent mode configuration
|
|
get_transparent_config() {
|
|
local enabled=$(uci -q get mitmproxy.transparent.enabled || echo "0")
|
|
local interface=$(uci -q get mitmproxy.transparent.interface || echo "br-lan")
|
|
local redirect_http=$(uci -q get mitmproxy.transparent.redirect_http || echo "1")
|
|
local redirect_https=$(uci -q get mitmproxy.transparent.redirect_https || echo "1")
|
|
local http_port=$(uci -q get mitmproxy.transparent.http_port || echo "80")
|
|
local https_port=$(uci -q get mitmproxy.transparent.https_port || echo "443")
|
|
|
|
cat <<EOF
|
|
{
|
|
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
|
"interface": "$interface",
|
|
"redirect_http": $([ "$redirect_http" = "1" ] && echo "true" || echo "false"),
|
|
"redirect_https": $([ "$redirect_https" = "1" ] && echo "true" || echo "false"),
|
|
"http_port": $http_port,
|
|
"https_port": $https_port
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Get whitelist configuration
|
|
get_whitelist_config() {
|
|
local enabled=$(uci -q get mitmproxy.whitelist.enabled || echo "1")
|
|
|
|
# Get bypass_ip list
|
|
local bypass_ips=$(uci -q get mitmproxy.whitelist.bypass_ip 2>/dev/null | tr ' ' '\n' | while read ip; do
|
|
[ -n "$ip" ] && printf '"%s",' "$ip"
|
|
done | sed 's/,$//')
|
|
|
|
# Get bypass_domain list
|
|
local bypass_domains=$(uci -q get mitmproxy.whitelist.bypass_domain 2>/dev/null | tr ' ' '\n' | while read domain; do
|
|
[ -n "$domain" ] && printf '"%s",' "$domain"
|
|
done | sed 's/,$//')
|
|
|
|
cat <<EOF
|
|
{
|
|
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
|
"bypass_ip": [${bypass_ips}],
|
|
"bypass_domain": [${bypass_domains}]
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Get filtering configuration
|
|
get_filtering_config() {
|
|
local enabled=$(uci -q get mitmproxy.filtering.enabled || echo "0")
|
|
local log_requests=$(uci -q get mitmproxy.filtering.log_requests || echo "1")
|
|
local filter_cdn=$(uci -q get mitmproxy.filtering.filter_cdn || echo "0")
|
|
local filter_media=$(uci -q get mitmproxy.filtering.filter_media || echo "0")
|
|
local block_ads=$(uci -q get mitmproxy.filtering.block_ads || echo "0")
|
|
local addon_script=$(uci -q get mitmproxy.filtering.addon_script || echo "/etc/mitmproxy/addons/secubox_filter.py")
|
|
|
|
cat <<EOF
|
|
{
|
|
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
|
"log_requests": $([ "$log_requests" = "1" ] && echo "true" || echo "false"),
|
|
"filter_cdn": $([ "$filter_cdn" = "1" ] && echo "true" || echo "false"),
|
|
"filter_media": $([ "$filter_media" = "1" ] && echo "true" || echo "false"),
|
|
"block_ads": $([ "$block_ads" = "1" ] && echo "true" || echo "false"),
|
|
"addon_script": "$addon_script"
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Get all configuration in one call
|
|
get_all_config() {
|
|
cat <<EOF
|
|
{
|
|
"main": $(get_config),
|
|
"transparent": $(get_transparent_config),
|
|
"whitelist": $(get_whitelist_config),
|
|
"filtering": $(get_filtering_config)
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Get statistics
|
|
get_stats() {
|
|
local total_requests=0
|
|
local unique_hosts=0
|
|
local flow_size="0"
|
|
local cdn_requests=0
|
|
local media_requests=0
|
|
local blocked_ads=0
|
|
|
|
if [ -f "$LOG_FILE" ]; then
|
|
total_requests=$(wc -l < "$LOG_FILE" 2>/dev/null || echo "0")
|
|
# Use jsonfilter for parsing (OpenWrt native)
|
|
if command -v jsonfilter >/dev/null 2>&1; then
|
|
unique_hosts=$(cat "$LOG_FILE" 2>/dev/null | while read line; do
|
|
echo "$line" | jsonfilter -e '@.request.host' 2>/dev/null
|
|
done | sort -u | wc -l)
|
|
cdn_requests=$(grep -c '"category":"cdn"' "$LOG_FILE" 2>/dev/null || echo "0")
|
|
media_requests=$(grep -c '"category":"media"' "$LOG_FILE" 2>/dev/null || echo "0")
|
|
blocked_ads=$(grep -c '"category":"blocked_ad"' "$LOG_FILE" 2>/dev/null || echo "0")
|
|
fi
|
|
fi
|
|
|
|
if [ -f "$FLOW_FILE" ]; then
|
|
flow_size=$(ls -l "$FLOW_FILE" 2>/dev/null | awk '{print $5}' || echo "0")
|
|
fi
|
|
|
|
cat <<EOF
|
|
{
|
|
"total_requests": $total_requests,
|
|
"unique_hosts": $unique_hosts,
|
|
"flow_file_size": $flow_size,
|
|
"cdn_requests": $cdn_requests,
|
|
"media_requests": $media_requests,
|
|
"blocked_ads": $blocked_ads
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Get recent requests
|
|
get_requests() {
|
|
local limit="${1:-50}"
|
|
local category="${2:-}"
|
|
|
|
if [ ! -f "$LOG_FILE" ]; then
|
|
echo '{"requests":[]}'
|
|
return
|
|
fi
|
|
|
|
# Filter by category if specified
|
|
if [ -n "$category" ] && [ "$category" != "all" ]; then
|
|
echo '{"requests":['
|
|
grep "\"category\":\"$category\"" "$LOG_FILE" 2>/dev/null | tail -"$limit" | \
|
|
awk 'BEGIN{first=1}{if(!first)printf ",";first=0;print}' 2>/dev/null || echo ""
|
|
echo ']}'
|
|
else
|
|
echo '{"requests":['
|
|
tail -"$limit" "$LOG_FILE" 2>/dev/null | \
|
|
awk 'BEGIN{first=1}{if(!first)printf ",";first=0;print}' 2>/dev/null || echo ""
|
|
echo ']}'
|
|
fi
|
|
}
|
|
|
|
# Get top hosts
|
|
get_top_hosts() {
|
|
local limit="${1:-20}"
|
|
|
|
if [ ! -f "$LOG_FILE" ]; then
|
|
echo '{"hosts":[]}'
|
|
return
|
|
fi
|
|
|
|
echo '{"hosts":['
|
|
# Parse JSON using grep/sed for compatibility
|
|
grep -o '"host":"[^"]*"' "$LOG_FILE" 2>/dev/null | \
|
|
sed 's/"host":"//;s/"$//' | \
|
|
sort | uniq -c | sort -rn | head -"$limit" | \
|
|
awk 'BEGIN{first=1} {
|
|
if(!first) printf ",";
|
|
first=0;
|
|
gsub(/"/, "\\\"", $2);
|
|
printf "{\"host\":\"%s\",\"count\":%d}", $2, $1
|
|
}'
|
|
echo ']}'
|
|
}
|
|
|
|
# Service control
|
|
service_start() {
|
|
/etc/init.d/mitmproxy start >/dev/null 2>&1
|
|
sleep 2
|
|
get_status
|
|
}
|
|
|
|
service_stop() {
|
|
/etc/init.d/mitmproxy stop >/dev/null 2>&1
|
|
sleep 1
|
|
get_status
|
|
}
|
|
|
|
service_restart() {
|
|
/etc/init.d/mitmproxy restart >/dev/null 2>&1
|
|
sleep 2
|
|
get_status
|
|
}
|
|
|
|
# Setup firewall rules
|
|
firewall_setup() {
|
|
/usr/sbin/mitmproxyctl firewall-setup 2>&1
|
|
local result=$?
|
|
if [ $result -eq 0 ]; then
|
|
echo '{"success":true,"message":"Firewall rules applied"}'
|
|
else
|
|
echo '{"success":false,"message":"Failed to apply firewall rules"}'
|
|
fi
|
|
}
|
|
|
|
# Clear firewall rules
|
|
firewall_clear() {
|
|
/usr/sbin/mitmproxyctl firewall-clear 2>&1
|
|
local result=$?
|
|
if [ $result -eq 0 ]; then
|
|
echo '{"success":true,"message":"Firewall rules cleared"}'
|
|
else
|
|
echo '{"success":false,"message":"Failed to clear firewall rules"}'
|
|
fi
|
|
}
|
|
|
|
# Set configuration
|
|
set_config() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local section="main"
|
|
|
|
case "$key" in
|
|
save_flows|capture_*)
|
|
section="capture"
|
|
;;
|
|
redirect_*|interface|http_port|https_port)
|
|
section="transparent"
|
|
;;
|
|
bypass_ip|bypass_domain)
|
|
section="whitelist"
|
|
;;
|
|
filter_*|log_requests|block_ads|addon_script)
|
|
section="filtering"
|
|
;;
|
|
esac
|
|
|
|
# Handle boolean conversion
|
|
case "$value" in
|
|
true) value="1" ;;
|
|
false) value="0" ;;
|
|
esac
|
|
|
|
uci set "mitmproxy.$section.$key=$value"
|
|
uci commit mitmproxy
|
|
echo '{"success":true}'
|
|
}
|
|
|
|
# Add to list (for bypass_ip, bypass_domain)
|
|
add_to_list() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local section="whitelist"
|
|
|
|
uci add_list "mitmproxy.$section.$key=$value"
|
|
uci commit mitmproxy
|
|
echo '{"success":true}'
|
|
}
|
|
|
|
# Remove from list
|
|
remove_from_list() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local section="whitelist"
|
|
|
|
uci del_list "mitmproxy.$section.$key=$value"
|
|
uci commit mitmproxy
|
|
echo '{"success":true}'
|
|
}
|
|
|
|
# Clear captured data
|
|
clear_data() {
|
|
rm -f "$DATA_DIR"/*.log "$DATA_DIR"/*.bin 2>/dev/null
|
|
echo '{"success":true,"message":"Captured data cleared"}'
|
|
}
|
|
|
|
# Get CA certificate info
|
|
get_ca_info() {
|
|
local cert="$CONF_DIR/mitmproxy-ca-cert.pem"
|
|
local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
|
|
local web_port=$(uci -q get mitmproxy.main.web_port || echo "8081")
|
|
|
|
if [ -f "$cert" ]; then
|
|
local subject=$(openssl x509 -in "$cert" -noout -subject 2>/dev/null | sed 's/subject=//')
|
|
local expires=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | sed 's/notAfter=//')
|
|
|
|
cat <<EOF
|
|
{
|
|
"installed": true,
|
|
"path": "$cert",
|
|
"subject": "$subject",
|
|
"expires": "$expires",
|
|
"download_url": "http://$router_ip:$web_port/cert"
|
|
}
|
|
EOF
|
|
else
|
|
cat <<EOF
|
|
{
|
|
"installed": false,
|
|
"path": "$cert",
|
|
"download_url": ""
|
|
}
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
get_web_token() {
|
|
# Token is written to /data/.mitmproxy_token inside container
|
|
# /data is bind-mounted to DATA_DIR on host
|
|
local token_file="$DATA_DIR/.mitmproxy_token"
|
|
local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
|
|
local web_port=$(uci -q get mitmproxy.main.web_port || echo "8081")
|
|
local token=""
|
|
|
|
# Try reading token from host-mounted path
|
|
if [ -f "$token_file" ]; then
|
|
token=$(cat "$token_file" 2>/dev/null | tr -d '\n\r')
|
|
fi
|
|
|
|
# Fallback: read token directly from container if host file is missing/empty
|
|
if [ -z "$token" ] && command -v lxc-attach >/dev/null 2>&1; then
|
|
if lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING"; then
|
|
token=$(lxc-attach -n "$LXC_NAME" -- cat /data/.mitmproxy_token 2>/dev/null | tr -d '\n\r')
|
|
fi
|
|
fi
|
|
|
|
# Second fallback: parse token from mitmweb log inside container
|
|
if [ -z "$token" ] && command -v lxc-attach >/dev/null 2>&1; then
|
|
if lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING"; then
|
|
token=$(lxc-attach -n "$LXC_NAME" -- grep -o 'token=[a-zA-Z0-9_-]*' /tmp/mitmweb.log 2>/dev/null | head -1 | cut -d= -f2)
|
|
fi
|
|
fi
|
|
|
|
# Construct URL - only add token parameter if token exists
|
|
local web_url="http://$router_ip:$web_port"
|
|
local web_url_with_token="$web_url"
|
|
if [ -n "$token" ]; then
|
|
web_url_with_token="$web_url/?token=$token"
|
|
fi
|
|
|
|
cat <<EOF
|
|
{
|
|
"token": "$token",
|
|
"web_url": "$web_url",
|
|
"web_url_with_token": "$web_url_with_token"
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# RPCD list method
|
|
case "$1" in
|
|
list)
|
|
cat <<EOF
|
|
{
|
|
"get_status": {},
|
|
"get_config": {},
|
|
"get_transparent_config": {},
|
|
"get_whitelist_config": {},
|
|
"get_filtering_config": {},
|
|
"get_all_config": {},
|
|
"get_stats": {},
|
|
"get_requests": {"limit": 50, "category": "all"},
|
|
"get_top_hosts": {"limit": 20},
|
|
"get_ca_info": {},
|
|
"get_web_token": {},
|
|
"service_start": {},
|
|
"service_stop": {},
|
|
"service_restart": {},
|
|
"firewall_setup": {},
|
|
"firewall_clear": {},
|
|
"set_config": {"key": "string", "value": "string"},
|
|
"add_to_list": {"key": "string", "value": "string"},
|
|
"remove_from_list": {"key": "string", "value": "string"},
|
|
"clear_data": {}
|
|
}
|
|
EOF
|
|
;;
|
|
call)
|
|
case "$2" in
|
|
get_status)
|
|
get_status
|
|
;;
|
|
get_config)
|
|
get_config
|
|
;;
|
|
get_transparent_config)
|
|
get_transparent_config
|
|
;;
|
|
get_whitelist_config)
|
|
get_whitelist_config
|
|
;;
|
|
get_filtering_config)
|
|
get_filtering_config
|
|
;;
|
|
get_all_config)
|
|
get_all_config
|
|
;;
|
|
get_stats)
|
|
get_stats
|
|
;;
|
|
get_requests)
|
|
read -r input
|
|
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null || echo "50")
|
|
category=$(echo "$input" | jsonfilter -e '@.category' 2>/dev/null || echo "all")
|
|
get_requests "$limit" "$category"
|
|
;;
|
|
get_top_hosts)
|
|
read -r input
|
|
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null || echo "20")
|
|
get_top_hosts "$limit"
|
|
;;
|
|
get_ca_info)
|
|
get_ca_info
|
|
;;
|
|
get_web_token)
|
|
get_web_token
|
|
;;
|
|
service_start)
|
|
service_start
|
|
;;
|
|
service_stop)
|
|
service_stop
|
|
;;
|
|
service_restart)
|
|
service_restart
|
|
;;
|
|
firewall_setup)
|
|
firewall_setup
|
|
;;
|
|
firewall_clear)
|
|
firewall_clear
|
|
;;
|
|
set_config)
|
|
read -r input
|
|
key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null)
|
|
value=$(echo "$input" | jsonfilter -e '@.value' 2>/dev/null)
|
|
set_config "$key" "$value"
|
|
;;
|
|
add_to_list)
|
|
read -r input
|
|
key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null)
|
|
value=$(echo "$input" | jsonfilter -e '@.value' 2>/dev/null)
|
|
add_to_list "$key" "$value"
|
|
;;
|
|
remove_from_list)
|
|
read -r input
|
|
key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null)
|
|
value=$(echo "$input" | jsonfilter -e '@.value' 2>/dev/null)
|
|
remove_from_list "$key" "$value"
|
|
;;
|
|
clear_data)
|
|
clear_data
|
|
;;
|
|
*)
|
|
echo '{"error":"Unknown method"}'
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
echo '{"error":"Unknown command"}'
|
|
;;
|
|
esac
|