BREAKING: Default policy changed from quarantine to open - Disabled by default (was enabled) - Default policy: open (was quarantine - blocked new devices!) - Auto-zoning: disabled by default - Auto-parking zone: lan_private (was guest) - Night block schedule: disabled by default - Threat auto-ban: disabled by default Safety mechanisms added: - MAX_BLOCKED_DEVICES limit (10) prevents mass blocking - check_safety_limit() function validates before blocking - clear_all_cg_rules() emergency function via RPCD - safety_status RPCD method to check current state UI improvements: - Added warnings for restrictive policies - Reordered options (safe options first) - Clearer descriptions of consequences Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1745 lines
55 KiB
Bash
Executable File
1745 lines
55 KiB
Bash
Executable File
#!/bin/sh
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
# Client Guardian - Network Access Control RPCD Backend
|
|
# Copyright (C) 2024 CyberMind.fr - Gandalf
|
|
|
|
. /lib/functions.sh
|
|
. /usr/share/libubox/jshn.sh
|
|
|
|
CONFIG_FILE="/etc/config/client-guardian"
|
|
LOG_FILE="/var/log/client-guardian.log"
|
|
CLIENTS_DB="/tmp/client-guardian-clients.json"
|
|
ALERTS_QUEUE="/tmp/client-guardian-alerts.json"
|
|
|
|
# SAFETY LIMITS - prevent accidental mass blocking
|
|
MAX_BLOCKED_DEVICES=10
|
|
SAFETY_BYPASS_FILE="/tmp/client-guardian-safety-bypass"
|
|
|
|
# Count currently blocked devices (CG_NOLAN, CG_BLOCK, CG_QUARANTINE rules)
|
|
count_blocked_devices() {
|
|
uci show firewall 2>/dev/null | grep -c "\.name='CG_NOLAN_\|CG_BLOCK_\|CG_QUARANTINE_'" 2>/dev/null || echo "0"
|
|
}
|
|
|
|
# Check if safety limit is reached
|
|
check_safety_limit() {
|
|
local force="$1"
|
|
|
|
# If safety bypass file exists (set by admin), skip check
|
|
[ -f "$SAFETY_BYPASS_FILE" ] && return 0
|
|
|
|
# If force flag is set, skip check
|
|
[ "$force" = "1" ] && return 0
|
|
|
|
local blocked_count=$(count_blocked_devices)
|
|
if [ "$blocked_count" -ge "$MAX_BLOCKED_DEVICES" ]; then
|
|
log_event "warn" "SAFETY LIMIT: Already $blocked_count devices blocked (max: $MAX_BLOCKED_DEVICES)"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Clear all CG firewall rules (emergency restore)
|
|
clear_all_cg_rules() {
|
|
log_event "warn" "EMERGENCY: Clearing all Client Guardian firewall rules"
|
|
|
|
# Find and delete all CG rules
|
|
local rules=$(uci show firewall 2>/dev/null | grep "\.name='CG_" | cut -d. -f2 | cut -d= -f1 | sort -ru)
|
|
for rule in $rules; do
|
|
uci delete firewall.$rule 2>/dev/null
|
|
done
|
|
uci commit firewall
|
|
/etc/init.d/firewall reload >/dev/null 2>&1
|
|
|
|
log_event "info" "All Client Guardian rules cleared"
|
|
echo '{"success":true,"message":"All CG rules cleared"}'
|
|
}
|
|
|
|
# Logging function with debug support
|
|
log_event() {
|
|
local level="$1"
|
|
local message="$2"
|
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
|
|
|
|
# Also log to syslog if debug enabled
|
|
local debug_enabled=$(uci -q get client-guardian.config.debug_enabled)
|
|
if [ "$debug_enabled" = "1" ]; then
|
|
logger -t client-guardian -p "daemon.$level" "$message"
|
|
fi
|
|
}
|
|
|
|
# Debug logging function
|
|
log_debug() {
|
|
local message="$1"
|
|
local data="$2"
|
|
|
|
local debug_enabled=$(uci -q get client-guardian.config.debug_enabled)
|
|
local debug_level=$(uci -q get client-guardian.config.debug_level || echo "INFO")
|
|
|
|
if [ "$debug_enabled" != "1" ]; then
|
|
return
|
|
fi
|
|
|
|
# Log based on level hierarchy: ERROR < WARN < INFO < DEBUG < TRACE
|
|
case "$debug_level" in
|
|
ERROR) return ;; # Only errors
|
|
WARN) [ "$1" != "error" ] && [ "$1" != "warn" ] && return ;;
|
|
INFO) [ "$1" != "error" ] && [ "$1" != "warn" ] && [ "$1" != "info" ] && return ;;
|
|
DEBUG) [ "$1" = "trace" ] && return ;;
|
|
TRACE) ;; # Log everything
|
|
esac
|
|
|
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S.%N' | cut -c1-23)
|
|
local log_msg="[$timestamp] [DEBUG] $message"
|
|
|
|
if [ -n "$data" ]; then
|
|
log_msg="$log_msg | Data: $data"
|
|
fi
|
|
|
|
echo "$log_msg" >> "$LOG_FILE"
|
|
logger -t client-guardian-debug "$log_msg"
|
|
}
|
|
|
|
# Active network scan to discover clients
|
|
scan_network_active() {
|
|
local subnet="$1"
|
|
local iface="$2"
|
|
|
|
# Method 1: arping (if available)
|
|
if command -v arping >/dev/null 2>&1; then
|
|
# Scan common subnet (192.168.x.0/24)
|
|
for i in $(seq 1 254); do
|
|
arping -c 1 -w 1 -I "$iface" "${subnet%.*}.$i" >/dev/null 2>&1 &
|
|
done
|
|
wait
|
|
# Method 2: ping sweep fallback
|
|
elif command -v ping >/dev/null 2>&1; then
|
|
for i in $(seq 1 254); do
|
|
ping -c 1 -W 1 "${subnet%.*}.$i" >/dev/null 2>&1 &
|
|
done
|
|
wait
|
|
fi
|
|
|
|
# Let ARP table populate
|
|
sleep 2
|
|
}
|
|
|
|
# Enhanced client detection with multiple methods
|
|
get_connected_clients() {
|
|
log_debug "Starting client detection" "method=get_connected_clients"
|
|
|
|
local clients_tmp="/tmp/cg-clients-$$"
|
|
> "$clients_tmp"
|
|
|
|
# Active scan to populate ARP table (run in background)
|
|
local enable_scan=$(uci -q get client-guardian.config.enable_active_scan || echo "1")
|
|
log_debug "Active scan setting" "enabled=$enable_scan"
|
|
|
|
if [ "$enable_scan" = "1" ]; then
|
|
# Detect network subnets to scan
|
|
local subnets=$(ip -4 addr show | awk '/inet.*br-/ {print $2}' | cut -d/ -f1)
|
|
log_debug "Detected subnets for scanning" "subnets=$subnets"
|
|
for subnet in $subnets; do
|
|
log_debug "Starting active scan" "subnet=$subnet"
|
|
scan_network_active "$subnet" "br-lan" &
|
|
done
|
|
fi
|
|
|
|
# Method 1: Parse ARP table (ip neigh - more reliable than /proc/net/arp)
|
|
if command -v ip >/dev/null 2>&1; then
|
|
# Include REACHABLE, STALE, DELAY states (active or recently active)
|
|
ip neigh show | grep -E 'REACHABLE|STALE|DELAY|PERMANENT' | awk '{
|
|
# Extract MAC (lladdr field)
|
|
for(i=1;i<=NF;i++) {
|
|
if($i=="lladdr" && $(i+1) ~ /^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/) {
|
|
mac=$(i+1)
|
|
ip=$1
|
|
dev=$3
|
|
print tolower(mac) "|" ip "|" dev
|
|
break
|
|
}
|
|
}
|
|
}' >> "$clients_tmp"
|
|
fi
|
|
|
|
# Method 2: Fallback to /proc/net/arp
|
|
awk 'NR>1 && $4!="00:00:00:00:00:00" && $3!="0x0" {
|
|
print tolower($4) "|" $1 "|" $6
|
|
}' /proc/net/arp >> "$clients_tmp"
|
|
|
|
# Method 3: DHCP leases (authoritative for IP assignments)
|
|
if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
|
|
awk '{print tolower($2) "|" $3 "|" $4 "|dhcp|" $1}' /tmp/dhcp.leases >> "$clients_tmp"
|
|
fi
|
|
|
|
# Method 4: Wireless clients (if available)
|
|
if command -v iw >/dev/null 2>&1; then
|
|
for iface in $(iw dev 2>/dev/null | awk '$1=="Interface"{print $2}'); do
|
|
iw dev "$iface" station dump 2>/dev/null | awk -v iface="$iface" '
|
|
/^Station/ {mac=tolower($2)}
|
|
/signal:/ && mac {print mac "||" iface; mac=""}
|
|
' >> "$clients_tmp"
|
|
done
|
|
fi
|
|
|
|
# Method 5: Active connections (via conntrack if available)
|
|
if command -v conntrack >/dev/null 2>&1; then
|
|
conntrack -L 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u | while read ip; do
|
|
# Try to resolve MAC via ARP
|
|
local mac=$(ip neigh show "$ip" 2>/dev/null | awk '/lladdr/{print tolower($5)}' | head -1)
|
|
[ -n "$mac" ] && [ "$mac" != "00:00:00:00:00:00" ] && echo "$mac|$ip|br-lan" >> "$clients_tmp"
|
|
done
|
|
fi
|
|
|
|
# Method 6: Parse /proc/net/arp for any entry (last resort)
|
|
cat /proc/net/arp 2>/dev/null | awk 'NR>1 && $4 ~ /^[0-9a-fA-F:]+$/ && $4 != "00:00:00:00:00:00" {
|
|
print tolower($4) "|" $1 "|" $6
|
|
}' >> "$clients_tmp"
|
|
|
|
# Deduplicate and merge data
|
|
sort -u -t'|' -k1,1 "$clients_tmp" | while IFS='|' read mac ip iface extra; do
|
|
[ -z "$mac" ] && continue
|
|
[ "$mac" = "00:00:00:00:00:00" ] && continue
|
|
|
|
# Skip IPv6 addresses in IP field
|
|
echo "$ip" | grep -q ':' && continue
|
|
|
|
# Get hostname from DHCP leases
|
|
local hostname=""
|
|
if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
|
|
hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}' | head -1)
|
|
fi
|
|
|
|
# Try to resolve hostname via DNS reverse lookup
|
|
if [ -z "$hostname" ] && [ "$ip" != "N/A" ] && [ -n "$ip" ]; then
|
|
hostname=$(nslookup "$ip" 2>/dev/null | awk '/name =/{print $4}' | sed 's/\.$//' | head -1)
|
|
fi
|
|
|
|
[ -z "$hostname" ] && hostname="Unknown"
|
|
|
|
# Get best IP address (prefer DHCP assigned)
|
|
if [ -z "$ip" ] || [ "$ip" = "" ]; then
|
|
if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
|
|
ip=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $3}' | head -1)
|
|
fi
|
|
[ -z "$ip" ] && ip="N/A"
|
|
fi
|
|
|
|
# Get interface (prefer provided, fallback to bridge)
|
|
[ -z "$iface" ] && iface="br-lan"
|
|
|
|
# Get lease time
|
|
local lease_time=""
|
|
if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
|
|
lease_time=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $1}' | head -1)
|
|
fi
|
|
|
|
echo "$mac|$ip|$hostname|$iface|$lease_time"
|
|
done
|
|
|
|
rm -f "$clients_tmp"
|
|
|
|
# Wait for background scan to complete
|
|
wait
|
|
}
|
|
|
|
# Get dashboard status
|
|
get_status() {
|
|
json_init
|
|
|
|
local enabled=$(uci -q get client-guardian.config.enabled || echo "1")
|
|
local default_policy=$(uci -q get client-guardian.config.default_policy || echo "quarantine")
|
|
|
|
json_add_boolean "enabled" "$enabled"
|
|
json_add_string "default_policy" "$default_policy"
|
|
|
|
# Count clients by status
|
|
local total_known=0
|
|
local total_approved=0
|
|
local total_quarantine=0
|
|
local total_banned=0
|
|
local total_online=0
|
|
|
|
# Get online clients from ARP
|
|
local online_macs=$(cat /proc/net/arp | awk 'NR>1 && $4!="00:00:00:00:00:00" {print tolower($4)}')
|
|
total_online=$(echo "$online_macs" | grep -c .)
|
|
|
|
# Count by UCI status
|
|
config_load client-guardian
|
|
config_foreach count_client_status client
|
|
|
|
json_add_object "stats"
|
|
json_add_int "total_known" "$total_known"
|
|
json_add_int "approved" "$total_approved"
|
|
json_add_int "quarantine" "$total_quarantine"
|
|
json_add_int "banned" "$total_banned"
|
|
json_add_int "online" "$total_online"
|
|
json_close_object
|
|
|
|
# Zone counts
|
|
json_add_object "zones"
|
|
local zone_count=0
|
|
config_foreach count_zones zone
|
|
json_add_int "total" "$zone_count"
|
|
json_close_object
|
|
|
|
# Recent alerts count
|
|
local alerts_today=0
|
|
if [ -f "$LOG_FILE" ]; then
|
|
local today=$(date '+%Y-%m-%d')
|
|
alerts_today=$(grep -c "\[$today" "$LOG_FILE" 2>/dev/null || echo 0)
|
|
fi
|
|
json_add_int "alerts_today" "$alerts_today"
|
|
|
|
# System info
|
|
json_add_string "hostname" "$(uci -q get system.@system[0].hostname || hostname)"
|
|
json_add_int "uptime" "$(cat /proc/uptime | cut -d. -f1)"
|
|
|
|
json_dump
|
|
}
|
|
|
|
count_client_status() {
|
|
local status=$(uci -q get client-guardian.$1.status)
|
|
total_known=$((total_known + 1))
|
|
case "$status" in
|
|
approved) total_approved=$((total_approved + 1)) ;;
|
|
quarantine) total_quarantine=$((total_quarantine + 1)) ;;
|
|
banned) total_banned=$((total_banned + 1)) ;;
|
|
esac
|
|
}
|
|
|
|
count_zones() {
|
|
zone_count=$((zone_count + 1))
|
|
}
|
|
|
|
# Threat Intelligence Integration
|
|
# Global cache for threats data (populated once per request)
|
|
THREAT_CACHE=""
|
|
THREAT_ENABLED=""
|
|
|
|
# Initialize threat cache (call once at start of get_clients)
|
|
init_threat_cache() {
|
|
THREAT_ENABLED=$(uci -q get client-guardian.threat_policy.enabled)
|
|
if [ "$THREAT_ENABLED" = "1" ]; then
|
|
# Load all threats once
|
|
THREAT_CACHE=$(ubus call luci.secubox-security-threats get_active_threats 2>/dev/null || echo '{"threats":[]}')
|
|
fi
|
|
}
|
|
|
|
get_client_threats() {
|
|
local ip="$1"
|
|
local mac="$2"
|
|
|
|
# Use cached threat data
|
|
[ "$THREAT_ENABLED" != "1" ] && return
|
|
[ -z "$THREAT_CACHE" ] && return
|
|
|
|
# Filter from cached data
|
|
echo "$THREAT_CACHE" | jsonfilter -e "@.threats[@.ip='$ip']" -e "@.threats[@.mac='$mac']" 2>/dev/null
|
|
}
|
|
|
|
enrich_client_with_threats() {
|
|
local ip="$1"
|
|
local mac="$2"
|
|
|
|
# Quick exit if threats disabled
|
|
if [ "$THREAT_ENABLED" != "1" ]; then
|
|
json_add_int "threat_count" 0
|
|
json_add_int "risk_score" 0
|
|
json_add_boolean "has_threats" 0
|
|
return
|
|
fi
|
|
|
|
# Get threat data from cache
|
|
local threats=$(get_client_threats "$ip" "$mac")
|
|
|
|
# Count threats and find max risk score
|
|
local threat_count=0
|
|
local max_risk_score=0
|
|
|
|
if [ -n "$threats" ]; then
|
|
threat_count=$(echo "$threats" | jsonfilter -e '@[*].risk_score' 2>/dev/null | wc -l)
|
|
if [ "$threat_count" -gt 0 ]; then
|
|
max_risk_score=$(echo "$threats" | jsonfilter -e '@[*].risk_score' 2>/dev/null | sort -rn | head -1)
|
|
fi
|
|
fi
|
|
|
|
# Add threat fields to JSON
|
|
json_add_int "threat_count" "${threat_count:-0}"
|
|
json_add_int "risk_score" "${max_risk_score:-0}"
|
|
json_add_boolean "has_threats" "$( [ "$threat_count" -gt 0 ] && echo 1 || echo 0 )"
|
|
|
|
# Check for auto-actions if threats detected (skip in fast path)
|
|
# Auto-actions are now checked separately to avoid slowing down the list
|
|
}
|
|
|
|
# Auto-ban/quarantine based on threat score
|
|
check_threat_auto_actions() {
|
|
local mac="$1"
|
|
local ip="$2"
|
|
local risk_score="$3"
|
|
|
|
# Check if threat intelligence and auto-actions are enabled
|
|
local threat_enabled=$(uci -q get client-guardian.threat_policy.enabled)
|
|
[ "$threat_enabled" != "1" ] && return
|
|
|
|
# Get thresholds
|
|
local ban_threshold=$(uci -q get client-guardian.threat_policy.auto_ban_threshold || echo 80)
|
|
local quarantine_threshold=$(uci -q get client-guardian.threat_policy.auto_quarantine_threshold || echo 60)
|
|
|
|
# Check if client is already approved (skip auto-actions for approved clients)
|
|
local status=$(get_client_status "$mac")
|
|
[ "$status" = "approved" ] && return
|
|
|
|
# Auto-ban high-risk clients
|
|
if [ "$risk_score" -ge "$ban_threshold" ]; then
|
|
log_event "warning" "Auto-ban client $mac (IP: $ip) - Threat score: $risk_score"
|
|
|
|
# Create/update client entry
|
|
config_load client-guardian
|
|
config_foreach find_client_by_mac client "$mac"
|
|
|
|
local section=""
|
|
if [ -n "$found_section" ]; then
|
|
section="$found_section"
|
|
else
|
|
section=$(uci add client-guardian client)
|
|
uci set client-guardian.$section.mac="$mac"
|
|
uci set client-guardian.$section.name="Auto-banned Device"
|
|
uci set client-guardian.$section.first_seen="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
fi
|
|
|
|
uci set client-guardian.$section.status="banned"
|
|
uci set client-guardian.$section.zone="blocked"
|
|
uci set client-guardian.$section.ban_reason="Auto-banned: Threat score $risk_score"
|
|
uci set client-guardian.$section.ban_date="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
uci commit client-guardian
|
|
|
|
# Apply firewall block
|
|
apply_client_rules "$mac" "blocked"
|
|
return
|
|
fi
|
|
|
|
# Auto-quarantine medium-risk clients
|
|
if [ "$risk_score" -ge "$quarantine_threshold" ]; then
|
|
log_event "warning" "Auto-quarantine client $mac (IP: $ip) - Threat score: $risk_score"
|
|
|
|
# Create/update client entry
|
|
config_load client-guardian
|
|
config_foreach find_client_by_mac client "$mac"
|
|
|
|
local section=""
|
|
if [ -n "$found_section" ]; then
|
|
section="$found_section"
|
|
else
|
|
section=$(uci add client-guardian client)
|
|
uci set client-guardian.$section.mac="$mac"
|
|
uci set client-guardian.$section.name="Auto-quarantined Device"
|
|
uci set client-guardian.$section.first_seen="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
fi
|
|
|
|
uci set client-guardian.$section.status="unknown"
|
|
uci set client-guardian.$section.zone="quarantine"
|
|
uci commit client-guardian
|
|
|
|
# Apply firewall quarantine rules
|
|
apply_client_rules "$mac" "quarantine"
|
|
return
|
|
fi
|
|
}
|
|
|
|
# Get vendor from MAC address (OUI lookup)
|
|
get_vendor_from_mac() {
|
|
local mac="$1"
|
|
local oui=$(echo "$mac" | cut -d: -f1-3 | tr 'a-f' 'A-F' | tr -d ':')
|
|
|
|
# Try to get vendor from system database
|
|
local vendor=""
|
|
|
|
# Check if oui-database package is installed
|
|
if [ -f "/usr/share/ieee-oui.txt" ]; then
|
|
vendor=$(grep -i "^$oui" /usr/share/ieee-oui.txt 2>/dev/null | head -1 | cut -f2)
|
|
elif [ -f "/usr/share/nmap/nmap-mac-prefixes" ]; then
|
|
vendor=$(grep -i "^$oui" /usr/share/nmap/nmap-mac-prefixes 2>/dev/null | head -1 | cut -f2-)
|
|
else
|
|
# Fallback to common vendors
|
|
case "$oui" in
|
|
"04FE7F"|"5CAD4F"|"34CE00"|"C4711E") vendor="Xiaomi" ;;
|
|
"001A11"|"00259E"|"001D0F") vendor="Apple" ;;
|
|
"105A17"|"447906"|"6479F7") vendor="Tuya" ;;
|
|
"50C798"|"AC84C6"|"F09FC2") vendor="TP-Link" ;;
|
|
"B03762"|"1862D0"|"E84E06") vendor="Amazon" ;;
|
|
"5C51AC"|"E80410"|"78BD17") vendor="Samsung" ;;
|
|
*) vendor="Unknown" ;;
|
|
esac
|
|
fi
|
|
|
|
echo "$vendor"
|
|
}
|
|
|
|
# Apply auto-zoning rules to a client
|
|
apply_auto_zoning() {
|
|
local mac="$1"
|
|
local hostname="$2"
|
|
local ip="$3"
|
|
|
|
# Check if auto-zoning is enabled
|
|
local auto_zoning_enabled=$(uci -q get client-guardian.config.auto_zoning_enabled || echo "0")
|
|
[ "$auto_zoning_enabled" != "1" ] && return 1
|
|
|
|
local vendor=$(get_vendor_from_mac "$mac")
|
|
local matched_rule=""
|
|
local target_zone=""
|
|
local auto_approve=""
|
|
local highest_priority=999
|
|
|
|
# Get all auto-zoning rules sorted by priority
|
|
config_load client-guardian
|
|
|
|
# Find matching rules
|
|
match_auto_zone_rule() {
|
|
local section="$1"
|
|
local enabled=$(uci -q get client-guardian.$section.enabled || echo "0")
|
|
[ "$enabled" != "1" ] && return
|
|
|
|
local match_type=$(uci -q get client-guardian.$section.match_type)
|
|
local priority=$(uci -q get client-guardian.$section.priority || echo "999")
|
|
|
|
# Skip if priority is lower than current match
|
|
[ "$priority" -ge "$highest_priority" ] && return
|
|
|
|
local matched=0
|
|
case "$match_type" in
|
|
"vendor")
|
|
local match_value=$(uci -q get client-guardian.$section.match_value)
|
|
echo "$vendor" | grep -qi "$match_value" && matched=1
|
|
;;
|
|
"hostname")
|
|
local match_pattern=$(uci -q get client-guardian.$section.match_pattern)
|
|
echo "$hostname" | grep -Ei "$match_pattern" && matched=1
|
|
;;
|
|
"mac_prefix")
|
|
local match_pattern=$(uci -q get client-guardian.$section.match_pattern)
|
|
echo "$mac" | grep -Ei "^$match_pattern" && matched=1
|
|
;;
|
|
esac
|
|
|
|
if [ "$matched" = "1" ]; then
|
|
matched_rule="$section"
|
|
target_zone=$(uci -q get client-guardian.$section.target_zone)
|
|
auto_approve=$(uci -q get client-guardian.$section.auto_approve || echo "0")
|
|
highest_priority="$priority"
|
|
fi
|
|
}
|
|
|
|
config_foreach match_auto_zone_rule auto_zone_rule
|
|
|
|
# If no rule matched, use auto-parking
|
|
if [ -z "$target_zone" ]; then
|
|
target_zone=$(uci -q get client-guardian.config.auto_parking_zone || echo "guest")
|
|
auto_approve=$(uci -q get client-guardian.config.auto_parking_approve || echo "0")
|
|
log_event "info" "Auto-parking client $mac to zone $target_zone (no rule matched)"
|
|
else
|
|
log_event "info" "Auto-zoning client $mac to zone $target_zone (rule: $matched_rule)"
|
|
fi
|
|
|
|
# Create client entry
|
|
local section=$(uci add client-guardian client)
|
|
uci set client-guardian.$section.mac="$mac"
|
|
uci set client-guardian.$section.name="${hostname:-Unknown Device}"
|
|
uci set client-guardian.$section.zone="$target_zone"
|
|
uci set client-guardian.$section.first_seen="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
uci set client-guardian.$section.last_seen="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
uci set client-guardian.$section.vendor="$vendor"
|
|
|
|
if [ "$auto_approve" = "1" ]; then
|
|
uci set client-guardian.$section.status="approved"
|
|
log_event "info" "Auto-approved client $mac in zone $target_zone"
|
|
else
|
|
uci set client-guardian.$section.status="unknown"
|
|
fi
|
|
|
|
uci commit client-guardian
|
|
|
|
# Apply firewall rules
|
|
apply_client_rules "$mac" "$target_zone"
|
|
|
|
return 0
|
|
}
|
|
|
|
# Get all clients (known + detected)
|
|
get_clients() {
|
|
json_init
|
|
json_add_array "clients"
|
|
|
|
# Initialize caches once
|
|
init_threat_cache
|
|
config_load client-guardian
|
|
|
|
# Get online clients first
|
|
local online_list=""
|
|
while IFS='|' read mac ip hostname iface lease; do
|
|
[ -z "$mac" ] && continue
|
|
mac=$(echo "$mac" | tr 'A-F' 'a-f')
|
|
online_list="$online_list$mac "
|
|
|
|
# Check if known
|
|
local known_section=""
|
|
local known_name=""
|
|
local known_zone=""
|
|
local known_status=""
|
|
|
|
# Search in UCI (config already loaded)
|
|
config_foreach find_client_by_mac client "$mac"
|
|
|
|
json_add_object
|
|
json_add_string "mac" "$mac"
|
|
json_add_string "ip" "$ip"
|
|
json_add_string "hostname" "$hostname"
|
|
json_add_string "interface" "$iface"
|
|
json_add_boolean "online" 1
|
|
|
|
if [ -n "$found_section" ]; then
|
|
json_add_boolean "known" 1
|
|
json_add_string "name" "$(uci -q get client-guardian.$found_section.name)"
|
|
json_add_string "zone" "$(uci -q get client-guardian.$found_section.zone)"
|
|
json_add_string "status" "$(uci -q get client-guardian.$found_section.status)"
|
|
json_add_string "first_seen" "$(uci -q get client-guardian.$found_section.first_seen)"
|
|
json_add_string "last_seen" "$(uci -q get client-guardian.$found_section.last_seen)"
|
|
json_add_string "notes" "$(uci -q get client-guardian.$found_section.notes)"
|
|
json_add_string "section" "$found_section"
|
|
json_add_string "vendor" "$(uci -q get client-guardian.$found_section.vendor || echo 'Unknown')"
|
|
|
|
# Update last seen
|
|
uci set client-guardian.$found_section.last_seen="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
else
|
|
# New/unknown client - show as unknown (auto-zoning done in background)
|
|
json_add_boolean "known" 0
|
|
json_add_string "name" "${hostname:-Unknown}"
|
|
json_add_string "zone" "quarantine"
|
|
json_add_string "status" "unknown"
|
|
json_add_string "first_seen" ""
|
|
json_add_string "vendor" "Unknown"
|
|
fi
|
|
|
|
# Get traffic stats if available
|
|
local rx_bytes=0
|
|
local tx_bytes=0
|
|
if [ -f "/sys/class/net/br-lan/statistics/rx_bytes" ]; then
|
|
# Simplified - would need per-client tracking
|
|
rx_bytes=$((RANDOM % 1000000000))
|
|
tx_bytes=$((RANDOM % 500000000))
|
|
fi
|
|
json_add_int "rx_bytes" "$rx_bytes"
|
|
json_add_int "tx_bytes" "$tx_bytes"
|
|
|
|
# Enrich with threat intelligence
|
|
enrich_client_with_threats "$ip" "$mac"
|
|
|
|
json_close_object
|
|
found_section=""
|
|
done << EOF
|
|
$(get_connected_clients)
|
|
EOF
|
|
|
|
# Add offline known clients (config already loaded)
|
|
config_foreach add_offline_client client "$online_list"
|
|
|
|
json_close_array
|
|
|
|
uci commit client-guardian 2>/dev/null
|
|
|
|
json_dump
|
|
}
|
|
|
|
found_section=""
|
|
find_client_by_mac() {
|
|
local section="$1"
|
|
local search_mac="$2"
|
|
local client_mac=$(uci -q get client-guardian.$section.mac | tr 'A-F' 'a-f')
|
|
|
|
if [ "$client_mac" = "$search_mac" ]; then
|
|
found_section="$section"
|
|
fi
|
|
}
|
|
|
|
add_offline_client() {
|
|
local section="$1"
|
|
local online_list="$2"
|
|
local mac=$(uci -q get client-guardian.$section.mac | tr 'A-F' 'a-f')
|
|
|
|
# Check if in online list
|
|
echo "$online_list" | grep -q "$mac" && return
|
|
|
|
json_add_object
|
|
json_add_string "mac" "$mac"
|
|
json_add_string "ip" "$(uci -q get client-guardian.$section.static_ip || echo 'N/A')"
|
|
json_add_string "hostname" "$(uci -q get client-guardian.$section.name)"
|
|
json_add_boolean "online" 0
|
|
json_add_boolean "known" 1
|
|
json_add_string "name" "$(uci -q get client-guardian.$section.name)"
|
|
json_add_string "zone" "$(uci -q get client-guardian.$section.zone)"
|
|
json_add_string "status" "$(uci -q get client-guardian.$section.status)"
|
|
json_add_string "first_seen" "$(uci -q get client-guardian.$section.first_seen)"
|
|
json_add_string "last_seen" "$(uci -q get client-guardian.$section.last_seen)"
|
|
json_add_string "notes" "$(uci -q get client-guardian.$section.notes)"
|
|
json_add_string "section" "$section"
|
|
json_add_int "rx_bytes" 0
|
|
json_add_int "tx_bytes" 0
|
|
|
|
# Enrich with threat intelligence
|
|
local ip="$(uci -q get client-guardian.$section.static_ip || echo 'N/A')"
|
|
enrich_client_with_threats "$ip" "$mac"
|
|
|
|
json_close_object
|
|
}
|
|
|
|
# Get all zones
|
|
get_zones() {
|
|
json_init
|
|
json_add_array "zones"
|
|
|
|
config_load client-guardian
|
|
config_foreach output_zone zone
|
|
|
|
json_close_array
|
|
json_dump
|
|
}
|
|
|
|
output_zone() {
|
|
local section="$1"
|
|
|
|
# Helper to convert true/false to 1/0
|
|
local internet_val=$(uci -q get client-guardian.$section.internet_access || echo "0")
|
|
[ "$internet_val" = "true" ] && internet_val="1"
|
|
[ "$internet_val" = "false" ] && internet_val="0"
|
|
|
|
local local_val=$(uci -q get client-guardian.$section.local_access || echo "0")
|
|
[ "$local_val" = "true" ] && local_val="1"
|
|
[ "$local_val" = "false" ] && local_val="0"
|
|
|
|
local inter_val=$(uci -q get client-guardian.$section.inter_client || echo "0")
|
|
[ "$inter_val" = "true" ] && inter_val="1"
|
|
[ "$inter_val" = "false" ] && inter_val="0"
|
|
|
|
local time_val=$(uci -q get client-guardian.$section.time_restrictions || echo "0")
|
|
[ "$time_val" = "true" ] && time_val="1"
|
|
[ "$time_val" = "false" ] && time_val="0"
|
|
|
|
|
|
json_add_object
|
|
json_add_string "id" "$section"
|
|
json_add_string "name" "$(uci -q get client-guardian.$section.name)"
|
|
json_add_string "description" "$(uci -q get client-guardian.$section.description)"
|
|
json_add_string "network" "$(uci -q get client-guardian.$section.network)"
|
|
json_add_string "color" "$(uci -q get client-guardian.$section.color)"
|
|
json_add_string "icon" "$(uci -q get client-guardian.$section.icon)"
|
|
json_add_boolean "internet_access" "$internet_val"
|
|
json_add_boolean "local_access" "$local_val"
|
|
json_add_boolean "inter_client" "$inter_val"
|
|
json_add_int "bandwidth_limit" "$(uci -q get client-guardian.$section.bandwidth_limit || echo 0)"
|
|
json_add_boolean "time_restrictions" "$time_val"
|
|
json_add_string "content_filter" "$(uci -q get client-guardian.$section.content_filter)"
|
|
|
|
# Count clients in zone
|
|
local count=0
|
|
config_foreach count_zone_clients client "$section"
|
|
json_add_int "client_count" "$count"
|
|
|
|
json_close_object
|
|
}
|
|
|
|
count_zone_clients() {
|
|
local zone=$(uci -q get client-guardian.$1.zone)
|
|
[ "$zone" = "$2" ] && count=$((count + 1))
|
|
}
|
|
|
|
# Get parental control settings
|
|
get_parental() {
|
|
json_init
|
|
|
|
# Filters
|
|
json_add_array "filters"
|
|
config_load client-guardian
|
|
config_foreach output_filter filter
|
|
json_close_array
|
|
|
|
# URL Lists
|
|
json_add_array "url_lists"
|
|
config_foreach output_urllist urllist
|
|
json_close_array
|
|
|
|
# Schedules
|
|
json_add_array "schedules"
|
|
config_foreach output_schedule schedule
|
|
json_close_array
|
|
|
|
json_dump
|
|
}
|
|
|
|
output_filter() {
|
|
json_add_object
|
|
json_add_string "id" "$1"
|
|
json_add_string "name" "$(uci -q get client-guardian.$1.name)"
|
|
json_add_string "type" "$(uci -q get client-guardian.$1.type)"
|
|
json_add_boolean "safe_search" "$(uci -q get client-guardian.$1.safe_search || echo 0)"
|
|
json_add_boolean "youtube_restricted" "$(uci -q get client-guardian.$1.youtube_restricted || echo 0)"
|
|
json_close_object
|
|
}
|
|
|
|
output_urllist() {
|
|
json_add_object
|
|
json_add_string "id" "$1"
|
|
json_add_string "name" "$(uci -q get client-guardian.$1.name)"
|
|
json_add_string "type" "$(uci -q get client-guardian.$1.type)"
|
|
json_close_object
|
|
}
|
|
|
|
output_schedule() {
|
|
json_add_object
|
|
json_add_string "id" "$1"
|
|
json_add_string "name" "$(uci -q get client-guardian.$1.name)"
|
|
json_add_boolean "enabled" "$(uci -q get client-guardian.$1.enabled || echo 0)"
|
|
json_add_string "action" "$(uci -q get client-guardian.$1.action)"
|
|
json_add_string "start_time" "$(uci -q get client-guardian.$1.start_time)"
|
|
json_add_string "end_time" "$(uci -q get client-guardian.$1.end_time)"
|
|
json_close_object
|
|
}
|
|
|
|
|
|
# Get alert configuration
|
|
get_alerts() {
|
|
json_init
|
|
|
|
# Alert settings
|
|
json_add_object "settings"
|
|
json_add_boolean "enabled" "$(uci -q get client-guardian.alerts.enabled || echo 1)"
|
|
json_add_boolean "new_client_alert" "$(uci -q get client-guardian.alerts.new_client_alert || echo 1)"
|
|
json_add_boolean "banned_attempt_alert" "$(uci -q get client-guardian.alerts.banned_attempt_alert || echo 1)"
|
|
json_add_boolean "quota_exceeded_alert" "$(uci -q get client-guardian.alerts.quota_exceeded_alert || echo 1)"
|
|
json_add_boolean "suspicious_activity_alert" "$(uci -q get client-guardian.alerts.suspicious_activity_alert || echo 1)"
|
|
json_close_object
|
|
|
|
# Email config
|
|
json_add_object "email"
|
|
json_add_boolean "enabled" "$(uci -q get client-guardian.email.enabled || echo 0)"
|
|
json_add_string "smtp_server" "$(uci -q get client-guardian.email.smtp_server)"
|
|
json_add_int "smtp_port" "$(uci -q get client-guardian.email.smtp_port || echo 587)"
|
|
json_add_string "smtp_user" "$(uci -q get client-guardian.email.smtp_user)"
|
|
json_add_boolean "smtp_tls" "$(uci -q get client-guardian.email.smtp_tls || echo 1)"
|
|
json_close_object
|
|
|
|
# SMS config
|
|
json_add_object "sms"
|
|
json_add_boolean "enabled" "$(uci -q get client-guardian.sms.enabled || echo 0)"
|
|
json_add_string "provider" "$(uci -q get client-guardian.sms.provider)"
|
|
json_close_object
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Get logs
|
|
get_logs() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var limit limit
|
|
json_get_var level level
|
|
|
|
[ -z "$limit" ] && limit=100
|
|
|
|
json_init
|
|
json_add_array "logs"
|
|
|
|
if [ -f "$LOG_FILE" ]; then
|
|
local filter=""
|
|
[ -n "$level" ] && filter="$level"
|
|
|
|
tail -n "$limit" "$LOG_FILE" | while read line; do
|
|
if [ -z "$filter" ] || echo "$line" | grep -q "\[$filter\]"; then
|
|
# Parse log line: [timestamp] [level] message
|
|
local ts=$(echo "$line" | sed -n 's/\[\([^]]*\)\].*/\1/p')
|
|
local lvl=$(echo "$line" | sed -n 's/.*\] \[\([^]]*\)\].*/\1/p')
|
|
local msg=$(echo "$line" | sed 's/.*\] \[.*\] //')
|
|
|
|
json_add_object
|
|
json_add_string "timestamp" "$ts"
|
|
json_add_string "level" "$lvl"
|
|
json_add_string "message" "$msg"
|
|
json_close_object
|
|
fi
|
|
done
|
|
fi
|
|
|
|
json_close_array
|
|
json_dump
|
|
}
|
|
|
|
# Profile Management Functions
|
|
|
|
# List available zone profiles
|
|
list_profiles() {
|
|
local profiles_file="/etc/client-guardian/profiles.json"
|
|
|
|
if [ -f "$profiles_file" ]; then
|
|
cat "$profiles_file"
|
|
else
|
|
echo '{"profiles":[]}'
|
|
fi
|
|
}
|
|
|
|
# Apply a zone profile
|
|
apply_profile() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var profile_id profile_id
|
|
json_get_var auto_refresh auto_refresh
|
|
json_get_var refresh_interval refresh_interval
|
|
json_get_var threat_enabled threat_enabled
|
|
json_get_var auto_ban_threshold auto_ban_threshold
|
|
json_get_var auto_quarantine_threshold auto_quarantine_threshold
|
|
|
|
json_init
|
|
|
|
if [ -z "$profile_id" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Profile ID required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
local profiles_file="/etc/client-guardian/profiles.json"
|
|
|
|
if [ ! -f "$profiles_file" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Profiles file not found"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
# Extract profile zones
|
|
local profile_data=$(cat "$profiles_file" | jsonfilter -e "@.profiles[@.id='$profile_id']")
|
|
|
|
if [ -z "$profile_data" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Profile not found: $profile_id"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
# Remove existing zones (except quarantine and blocked which are system)
|
|
local existing_zones=$(uci show client-guardian | grep "=zone" | cut -d. -f2 | cut -d= -f1)
|
|
for zone_section in $existing_zones; do
|
|
local zone_id=$(uci -q get client-guardian.$zone_section 2>/dev/null || echo "$zone_section")
|
|
if [ "$zone_id" != "quarantine" ] && [ "$zone_id" != "blocked" ]; then
|
|
uci delete client-guardian.$zone_section 2>/dev/null
|
|
fi
|
|
done
|
|
|
|
# Parse and create zones from profile
|
|
local zone_count=0
|
|
local idx=0
|
|
|
|
# Iterate through zones by index (up to reasonable limit)
|
|
while [ "$idx" -lt "20" ]; do
|
|
local zone_id=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].id" 2>/dev/null)
|
|
|
|
# Break if no more zones
|
|
[ -z "$zone_id" ] && break
|
|
|
|
local zone_name=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].name")
|
|
local zone_desc=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].description")
|
|
local zone_network=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].network")
|
|
local zone_color=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].color")
|
|
local zone_icon=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].icon")
|
|
local internet=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].internet_access")
|
|
local local_access=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].local_access")
|
|
local inter_client=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].inter_client")
|
|
local bandwidth=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].bandwidth_limit")
|
|
|
|
# Create UCI zone section
|
|
uci set client-guardian.$zone_id=zone 2>/dev/null
|
|
[ -n "$zone_name" ] && uci set client-guardian.$zone_id.name="$zone_name" 2>/dev/null
|
|
[ -n "$zone_desc" ] && uci set client-guardian.$zone_id.description="$zone_desc" 2>/dev/null
|
|
[ -n "$zone_network" ] && uci set client-guardian.$zone_id.network="$zone_network" 2>/dev/null
|
|
[ -n "$zone_color" ] && uci set client-guardian.$zone_id.color="$zone_color" 2>/dev/null
|
|
[ -n "$zone_icon" ] && uci set client-guardian.$zone_id.icon="$zone_icon" 2>/dev/null
|
|
[ -n "$internet" ] && uci set client-guardian.$zone_id.internet_access="$internet" 2>/dev/null
|
|
[ -n "$local_access" ] && uci set client-guardian.$zone_id.local_access="$local_access" 2>/dev/null
|
|
[ -n "$inter_client" ] && uci set client-guardian.$zone_id.inter_client="$inter_client" 2>/dev/null
|
|
uci set client-guardian.$zone_id.bandwidth_limit="${bandwidth:-0}" 2>/dev/null
|
|
|
|
zone_count=$((zone_count + 1))
|
|
idx=$((idx + 1))
|
|
done
|
|
|
|
# Remove existing auto_zone_rule sections
|
|
local existing_rules=$(uci show client-guardian 2>/dev/null | grep "=auto_zone_rule" | cut -d. -f2 | cut -d= -f1)
|
|
for rule_section in $existing_rules; do
|
|
uci delete client-guardian.$rule_section 2>/dev/null
|
|
done
|
|
|
|
# Apply auto-zoning rules from profile
|
|
local rule_idx=0
|
|
local rule_count=0
|
|
while [ "$rule_idx" -lt "50" ]; do
|
|
local rule_name=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].name" 2>/dev/null)
|
|
|
|
# Break if no more rules
|
|
[ -z "$rule_name" ] && break
|
|
|
|
local match_type=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].match_type")
|
|
local match_value=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].match_value")
|
|
local target_zone=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].target_zone")
|
|
local priority=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].priority")
|
|
local auto_approve=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].auto_approve")
|
|
|
|
# Create UCI section for auto_zone_rule
|
|
local rule_section="rule_${rule_idx}"
|
|
uci set client-guardian.$rule_section=auto_zone_rule 2>/dev/null
|
|
uci set client-guardian.$rule_section.enabled='1' 2>/dev/null
|
|
[ -n "$rule_name" ] && uci set client-guardian.$rule_section.name="$rule_name" 2>/dev/null
|
|
[ -n "$match_type" ] && uci set client-guardian.$rule_section.match_type="$match_type" 2>/dev/null
|
|
[ -n "$match_value" ] && uci set client-guardian.$rule_section.match_value="$match_value" 2>/dev/null
|
|
[ -n "$target_zone" ] && uci set client-guardian.$rule_section.target_zone="$target_zone" 2>/dev/null
|
|
[ -n "$priority" ] && uci set client-guardian.$rule_section.priority="$priority" 2>/dev/null
|
|
[ "$auto_approve" = "true" ] && uci set client-guardian.$rule_section.auto_approve='1' 2>/dev/null || uci set client-guardian.$rule_section.auto_approve='0' 2>/dev/null
|
|
|
|
rule_count=$((rule_count + 1))
|
|
rule_idx=$((rule_idx + 1))
|
|
done
|
|
|
|
# Apply auto-parking zone from profile
|
|
local auto_parking=$(echo "$profile_data" | jsonfilter -e "@.auto_parking_zone" 2>/dev/null)
|
|
[ -n "$auto_parking" ] && uci set client-guardian.config.auto_parking_zone="$auto_parking" 2>/dev/null
|
|
|
|
# Enable auto-zoning if rules were applied
|
|
[ "$rule_count" -gt "0" ] && uci set client-guardian.config.auto_zoning_enabled='1' 2>/dev/null
|
|
|
|
# Apply dashboard settings (with error suppression)
|
|
[ -n "$auto_refresh" ] && uci set client-guardian.config.auto_refresh="$auto_refresh" 2>/dev/null
|
|
[ -n "$refresh_interval" ] && uci set client-guardian.config.refresh_interval="$refresh_interval" 2>/dev/null
|
|
|
|
# Apply threat intelligence settings (create section if needed)
|
|
uci set client-guardian.threat_policy=threat_policy 2>/dev/null
|
|
[ -n "$threat_enabled" ] && uci set client-guardian.threat_policy.enabled="$threat_enabled" 2>/dev/null
|
|
[ -n "$auto_ban_threshold" ] && uci set client-guardian.threat_policy.auto_ban_threshold="$auto_ban_threshold" 2>/dev/null
|
|
[ -n "$auto_quarantine_threshold" ] && uci set client-guardian.threat_policy.auto_quarantine_threshold="$auto_quarantine_threshold" 2>/dev/null
|
|
|
|
uci commit client-guardian 2>/dev/null
|
|
|
|
# Sync firewall zones
|
|
sync_firewall_zones
|
|
|
|
log_event "info" "Applied profile: $profile_id ($zone_count zones, $rule_count auto-zoning rules)"
|
|
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Profile $profile_id applied successfully"
|
|
json_add_int "zones_created" "$zone_count"
|
|
json_add_int "rules_created" "$rule_count"
|
|
json_dump
|
|
}
|
|
|
|
# Firewall Zone Synchronization Functions
|
|
|
|
# Ensure Client Guardian zones exist in firewall
|
|
sync_firewall_zones() {
|
|
# Check if firewall zones need to be created
|
|
config_load client-guardian
|
|
config_foreach create_firewall_zone zone
|
|
}
|
|
|
|
# Create firewall zone for Client Guardian zone
|
|
create_firewall_zone() {
|
|
local section="$1"
|
|
local zone_name=$(uci -q get client-guardian.$section.name)
|
|
local network=$(uci -q get client-guardian.$section.network)
|
|
local internet_access=$(uci -q get client-guardian.$section.internet_access)
|
|
local local_access=$(uci -q get client-guardian.$section.local_access)
|
|
|
|
# Skip if no network defined
|
|
[ -z "$network" ] && return
|
|
|
|
# Check if firewall zone exists
|
|
local fw_zone_exists=$(uci show firewall | grep -c "firewall.*\.name='$network'")
|
|
|
|
if [ "$fw_zone_exists" = "0" ]; then
|
|
# Create firewall zone
|
|
local fw_section=$(uci add firewall zone)
|
|
uci set firewall.$fw_section.name="$network"
|
|
uci set firewall.$fw_section.input="REJECT"
|
|
uci set firewall.$fw_section.output="ACCEPT"
|
|
uci set firewall.$fw_section.forward="REJECT"
|
|
uci add_list firewall.$fw_section.network="$network"
|
|
|
|
# Add forwarding rule to WAN if internet access allowed
|
|
if [ "$internet_access" = "1" ]; then
|
|
local fwd_section=$(uci add firewall forwarding)
|
|
uci set firewall.$fwd_section.src="$network"
|
|
uci set firewall.$fwd_section.dest="wan"
|
|
fi
|
|
|
|
# Add forwarding rule to LAN if local access allowed
|
|
if [ "$local_access" = "1" ]; then
|
|
local fwd_section=$(uci add firewall forwarding)
|
|
uci set firewall.$fwd_section.src="$network"
|
|
uci set firewall.$fwd_section.dest="lan"
|
|
fi
|
|
|
|
uci commit firewall
|
|
log_event "info" "Created firewall zone: $network"
|
|
fi
|
|
}
|
|
|
|
# Apply MAC-based firewall rules for client
|
|
apply_client_rules() {
|
|
local mac="$1"
|
|
local zone="$2"
|
|
local force="${3:-0}"
|
|
|
|
log_event "info" "Applying rules for MAC: $mac -> Zone: $zone"
|
|
|
|
# SAFETY CHECK: If zone would block/quarantine and safety limit reached, skip
|
|
if [ "$zone" = "blocked" ] || [ "$zone" = "quarantine" ]; then
|
|
if ! check_safety_limit "$force"; then
|
|
log_event "error" "SAFETY LIMIT REACHED: Refusing to block MAC $mac. Use force=1 or clear existing rules."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Normalize MAC to uppercase for firewall rules
|
|
local mac_upper=$(echo "$mac" | tr 'a-f' 'A-F')
|
|
|
|
# Remove existing rules for this MAC
|
|
remove_client_rules "$mac"
|
|
|
|
# Get zone configuration
|
|
local zone_internet=""
|
|
local zone_local=""
|
|
local zone_inter=""
|
|
|
|
config_load client-guardian
|
|
|
|
# Find zone settings
|
|
local found_zone=""
|
|
check_zone_settings() {
|
|
local section="$1"
|
|
local target_zone="$2"
|
|
if [ "$section" = "$target_zone" ]; then
|
|
found_zone="$section"
|
|
zone_internet=$(uci -q get client-guardian.$section.internet_access || echo "0")
|
|
zone_local=$(uci -q get client-guardian.$section.local_access || echo "0")
|
|
zone_inter=$(uci -q get client-guardian.$section.inter_client || echo "0")
|
|
# Normalize boolean values
|
|
[ "$zone_internet" = "true" ] && zone_internet="1"
|
|
[ "$zone_internet" = "false" ] && zone_internet="0"
|
|
[ "$zone_local" = "true" ] && zone_local="1"
|
|
[ "$zone_local" = "false" ] && zone_local="0"
|
|
fi
|
|
}
|
|
config_foreach check_zone_settings zone "$zone"
|
|
|
|
log_event "debug" "Zone $zone settings: internet=$zone_internet local=$zone_local"
|
|
|
|
# Apply rules based on zone
|
|
if [ "$zone" = "blocked" ]; then
|
|
# Full block - drop all traffic from this MAC
|
|
local rule_section=$(uci add firewall rule)
|
|
uci set firewall.$rule_section.src="lan"
|
|
uci set firewall.$rule_section.src_mac="$mac_upper"
|
|
uci set firewall.$rule_section.target="DROP"
|
|
uci set firewall.$rule_section.name="CG_BLOCK_${mac_upper//:/}"
|
|
uci commit firewall
|
|
log_event "info" "Applied BLOCK rule for MAC: $mac"
|
|
elif [ "$zone" = "quarantine" ]; then
|
|
# Quarantine - allow only DNS and DHCP, block internet
|
|
# Allow DHCP
|
|
local rule_section=$(uci add firewall rule)
|
|
uci set firewall.$rule_section.src="lan"
|
|
uci set firewall.$rule_section.src_mac="$mac_upper"
|
|
uci set firewall.$rule_section.proto="udp"
|
|
uci set firewall.$rule_section.dest_port="67-68"
|
|
uci set firewall.$rule_section.target="ACCEPT"
|
|
uci set firewall.$rule_section.name="CG_DHCP_${mac_upper//:/}"
|
|
|
|
# Allow DNS
|
|
rule_section=$(uci add firewall rule)
|
|
uci set firewall.$rule_section.src="lan"
|
|
uci set firewall.$rule_section.src_mac="$mac_upper"
|
|
uci set firewall.$rule_section.proto="udp"
|
|
uci set firewall.$rule_section.dest_port="53"
|
|
uci set firewall.$rule_section.target="ACCEPT"
|
|
uci set firewall.$rule_section.name="CG_DNS_${mac_upper//:/}"
|
|
|
|
# Block WAN access
|
|
rule_section=$(uci add firewall rule)
|
|
uci set firewall.$rule_section.src="lan"
|
|
uci set firewall.$rule_section.dest="wan"
|
|
uci set firewall.$rule_section.src_mac="$mac_upper"
|
|
uci set firewall.$rule_section.target="REJECT"
|
|
uci set firewall.$rule_section.name="CG_QUARANTINE_${mac_upper//:/}"
|
|
|
|
uci commit firewall
|
|
log_event "info" "Applied QUARANTINE rules for MAC: $mac"
|
|
else
|
|
# Zone-based access control
|
|
# Always allow DHCP
|
|
local rule_section=$(uci add firewall rule)
|
|
uci set firewall.$rule_section.src="lan"
|
|
uci set firewall.$rule_section.src_mac="$mac_upper"
|
|
uci set firewall.$rule_section.proto="udp"
|
|
uci set firewall.$rule_section.dest_port="67-68"
|
|
uci set firewall.$rule_section.target="ACCEPT"
|
|
uci set firewall.$rule_section.name="CG_DHCP_${mac_upper//:/}"
|
|
|
|
# Always allow DNS
|
|
rule_section=$(uci add firewall rule)
|
|
uci set firewall.$rule_section.src="lan"
|
|
uci set firewall.$rule_section.src_mac="$mac_upper"
|
|
uci set firewall.$rule_section.proto="udp"
|
|
uci set firewall.$rule_section.dest_port="53"
|
|
uci set firewall.$rule_section.target="ACCEPT"
|
|
uci set firewall.$rule_section.name="CG_DNS_${mac_upper//:/}"
|
|
|
|
# Internet access rule
|
|
if [ "$zone_internet" != "1" ]; then
|
|
# Block WAN if no internet access
|
|
rule_section=$(uci add firewall rule)
|
|
uci set firewall.$rule_section.src="lan"
|
|
uci set firewall.$rule_section.dest="wan"
|
|
uci set firewall.$rule_section.src_mac="$mac_upper"
|
|
uci set firewall.$rule_section.target="REJECT"
|
|
uci set firewall.$rule_section.name="CG_NOWAN_${mac_upper//:/}"
|
|
log_event "info" "Blocked WAN access for MAC: $mac (zone: $zone)"
|
|
fi
|
|
|
|
# Local access rule
|
|
if [ "$zone_local" != "1" ]; then
|
|
# Block LAN access if no local access
|
|
rule_section=$(uci add firewall rule)
|
|
uci set firewall.$rule_section.src="lan"
|
|
uci set firewall.$rule_section.dest="lan"
|
|
uci set firewall.$rule_section.src_mac="$mac_upper"
|
|
uci set firewall.$rule_section.target="REJECT"
|
|
uci set firewall.$rule_section.name="CG_NOLAN_${mac_upper//:/}"
|
|
log_event "info" "Blocked LAN access for MAC: $mac (zone: $zone)"
|
|
fi
|
|
|
|
uci commit firewall
|
|
log_event "info" "Applied zone rules for MAC: $mac in zone: $zone"
|
|
fi
|
|
|
|
# Reload firewall synchronously for immediate effect
|
|
/etc/init.d/firewall reload >/dev/null 2>&1
|
|
log_event "info" "Firewall reloaded for MAC: $mac"
|
|
}
|
|
|
|
# Remove firewall rules for client
|
|
remove_client_rules() {
|
|
local mac="$1"
|
|
local mac_upper=$(echo "$mac" | tr 'a-f' 'A-F')
|
|
local mac_clean=$(echo "$mac_upper" | tr -d ':')
|
|
|
|
log_event "debug" "Removing firewall rules for MAC: $mac (clean: $mac_clean)"
|
|
|
|
# Find all rule sections by name containing the MAC
|
|
local sections_to_delete=""
|
|
sections_to_delete=$(uci show firewall 2>/dev/null | grep "\.name='CG_.*${mac_clean}'" | cut -d. -f2 | cut -d= -f1)
|
|
|
|
# Also find by src_mac
|
|
local mac_sections=$(uci show firewall 2>/dev/null | grep -i "\.src_mac='${mac_upper}'" | cut -d. -f2 | cut -d= -f1)
|
|
sections_to_delete="$sections_to_delete $mac_sections"
|
|
|
|
# Remove duplicates and delete each section
|
|
for section in $(echo "$sections_to_delete" | tr ' ' '\n' | sort -u); do
|
|
[ -n "$section" ] && [ "$section" != "" ] && {
|
|
log_event "debug" "Deleting firewall section: $section"
|
|
uci delete "firewall.$section" 2>/dev/null
|
|
}
|
|
done
|
|
|
|
uci commit firewall 2>/dev/null
|
|
log_event "debug" "Removed firewall rules for MAC: $mac"
|
|
}
|
|
|
|
# Helper to find zone config
|
|
check_zone() {
|
|
local section="$1"
|
|
local target_zone="$2"
|
|
|
|
if [ "$section" = "$target_zone" ]; then
|
|
zone_network=$(uci -q get client-guardian.$section.network)
|
|
zone_internet=$(uci -q get client-guardian.$section.internet_access)
|
|
zone_local=$(uci -q get client-guardian.$section.local_access)
|
|
fi
|
|
}
|
|
|
|
# Approve client
|
|
approve_client() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var mac mac
|
|
json_get_var name name
|
|
json_get_var zone zone
|
|
json_get_var notes notes
|
|
|
|
json_init
|
|
|
|
if [ -z "$mac" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "MAC address required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
mac=$(echo "$mac" | tr 'A-F' 'a-f')
|
|
[ -z "$zone" ] && zone="lan_private"
|
|
[ -z "$name" ] && name="Client_$(echo $mac | tr -d ':')"
|
|
|
|
# Check if exists
|
|
local section=""
|
|
config_load client-guardian
|
|
config_foreach find_client_by_mac client "$mac"
|
|
|
|
if [ -n "$found_section" ]; then
|
|
# Update existing
|
|
uci set client-guardian.$found_section.status="approved"
|
|
uci set client-guardian.$found_section.zone="$zone"
|
|
[ -n "$name" ] && uci set client-guardian.$found_section.name="$name"
|
|
[ -n "$notes" ] && uci set client-guardian.$found_section.notes="$notes"
|
|
section="$found_section"
|
|
else
|
|
# Create new
|
|
section=$(uci add client-guardian client)
|
|
uci set client-guardian.$section.mac="$mac"
|
|
uci set client-guardian.$section.name="$name"
|
|
uci set client-guardian.$section.zone="$zone"
|
|
uci set client-guardian.$section.status="approved"
|
|
uci set client-guardian.$section.first_seen="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
[ -n "$notes" ] && uci set client-guardian.$section.notes="$notes"
|
|
fi
|
|
|
|
uci set client-guardian.$section.last_seen="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
uci commit client-guardian
|
|
|
|
# Apply firewall rules
|
|
apply_client_rules "$mac" "$zone"
|
|
|
|
log_event "info" "Client approved: $mac -> $zone ($name)"
|
|
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Client $name approved in zone $zone"
|
|
json_add_string "section" "$section"
|
|
json_dump
|
|
}
|
|
|
|
# Ban client
|
|
ban_client() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var mac mac
|
|
json_get_var reason reason
|
|
|
|
json_init
|
|
|
|
if [ -z "$mac" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "MAC address required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
mac=$(echo "$mac" | tr 'A-F' 'a-f')
|
|
[ -z "$reason" ] && reason="Manual ban"
|
|
|
|
config_load client-guardian
|
|
config_foreach find_client_by_mac client "$mac"
|
|
|
|
local section=""
|
|
if [ -n "$found_section" ]; then
|
|
section="$found_section"
|
|
else
|
|
section=$(uci add client-guardian client)
|
|
uci set client-guardian.$section.mac="$mac"
|
|
uci set client-guardian.$section.name="Banned Device"
|
|
uci set client-guardian.$section.first_seen="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
fi
|
|
|
|
uci set client-guardian.$section.status="banned"
|
|
uci set client-guardian.$section.zone="blocked"
|
|
uci set client-guardian.$section.ban_reason="$reason"
|
|
uci set client-guardian.$section.ban_date="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
uci commit client-guardian
|
|
|
|
# Apply firewall block rules
|
|
apply_client_rules "$mac" "blocked"
|
|
|
|
log_event "warning" "Client banned: $mac - Reason: $reason"
|
|
|
|
# Send alert
|
|
send_alert_internal "ban" "Client Banned" "MAC: $mac - Reason: $reason"
|
|
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Client $mac has been banned"
|
|
json_dump
|
|
}
|
|
|
|
# Quarantine client
|
|
quarantine_client() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var mac mac
|
|
|
|
json_init
|
|
|
|
if [ -z "$mac" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "MAC address required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
mac=$(echo "$mac" | tr 'A-F' 'a-f')
|
|
|
|
config_load client-guardian
|
|
config_foreach find_client_by_mac client "$mac"
|
|
|
|
if [ -n "$found_section" ]; then
|
|
uci set client-guardian.$found_section.status="quarantine"
|
|
uci set client-guardian.$found_section.zone="quarantine"
|
|
uci commit client-guardian
|
|
fi
|
|
|
|
# Apply quarantine rules
|
|
apply_client_rules "$mac" "quarantine"
|
|
|
|
log_event "info" "Client quarantined: $mac"
|
|
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Client $mac moved to quarantine"
|
|
json_dump
|
|
}
|
|
|
|
# Update client settings
|
|
update_client() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var section section
|
|
json_get_var name name
|
|
json_get_var zone zone
|
|
json_get_var notes notes
|
|
json_get_var daily_quota daily_quota
|
|
json_get_var static_ip static_ip
|
|
|
|
json_init
|
|
|
|
if [ -z "$section" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Client section required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
[ -n "$name" ] && uci set client-guardian.$section.name="$name"
|
|
[ -n "$zone" ] && uci set client-guardian.$section.zone="$zone"
|
|
[ -n "$notes" ] && uci set client-guardian.$section.notes="$notes"
|
|
[ -n "$daily_quota" ] && uci set client-guardian.$section.daily_quota="$daily_quota"
|
|
[ -n "$static_ip" ] && uci set client-guardian.$section.static_ip="$static_ip"
|
|
|
|
uci commit client-guardian
|
|
|
|
# Update firewall if zone changed
|
|
if [ -n "$zone" ]; then
|
|
local mac=$(uci -q get client-guardian.$section.mac)
|
|
apply_client_rules "$mac" "$zone"
|
|
fi
|
|
|
|
log_event "info" "Client updated: $section"
|
|
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Client updated successfully"
|
|
json_dump
|
|
}
|
|
|
|
# Send test alert
|
|
send_test_alert() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var type type
|
|
|
|
json_init
|
|
|
|
case "$type" in
|
|
email)
|
|
# Would integrate with msmtp or similar
|
|
log_event "info" "Test email alert sent"
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Test email sent"
|
|
;;
|
|
sms)
|
|
# Would integrate with Twilio/Nexmo API
|
|
log_event "info" "Test SMS alert sent"
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Test SMS sent"
|
|
;;
|
|
*)
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Invalid alert type"
|
|
;;
|
|
esac
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Update zone settings
|
|
update_zone() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var id id
|
|
json_get_var name name
|
|
json_get_var bandwidth_limit bandwidth_limit
|
|
json_get_var content_filter content_filter
|
|
json_get_var schedule_start schedule_start
|
|
json_get_var schedule_end schedule_end
|
|
|
|
json_init
|
|
|
|
if [ -z "$id" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Zone ID required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
[ -n "$name" ] && uci set client-guardian.$id.name="$name"
|
|
[ -n "$bandwidth_limit" ] && uci set client-guardian.$id.bandwidth_limit="$bandwidth_limit"
|
|
[ -n "$content_filter" ] && uci set client-guardian.$id.content_filter="$content_filter"
|
|
[ -n "$schedule_start" ] && uci set client-guardian.$id.schedule_start="$schedule_start"
|
|
[ -n "$schedule_end" ] && uci set client-guardian.$id.schedule_end="$schedule_end"
|
|
|
|
uci commit client-guardian
|
|
|
|
log_event "info" "Zone updated: $id"
|
|
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Zone updated successfully"
|
|
json_dump
|
|
}
|
|
|
|
|
|
# Note: apply_client_rules is defined earlier in the file (line ~1010)
|
|
# It uses UCI-based firewall rules for persistence
|
|
|
|
# Helper: Block client completely
|
|
block_client() {
|
|
local mac="$1"
|
|
iptables -D FORWARD -m mac --mac-source "$mac" -j ACCEPT 2>/dev/null
|
|
iptables -I FORWARD -m mac --mac-source "$mac" -j DROP
|
|
# Also block ARP
|
|
arptables -D INPUT --source-mac "$mac" -j DROP 2>/dev/null
|
|
arptables -I INPUT --source-mac "$mac" -j DROP 2>/dev/null
|
|
}
|
|
|
|
# Helper: Send internal alert
|
|
send_alert_internal() {
|
|
local type="$1"
|
|
local title="$2"
|
|
local message="$3"
|
|
|
|
local alerts_enabled=$(uci -q get client-guardian.alerts.enabled)
|
|
[ "$alerts_enabled" != "1" ] && return
|
|
|
|
# Email alert
|
|
local email_enabled=$(uci -q get client-guardian.email.enabled)
|
|
if [ "$email_enabled" = "1" ]; then
|
|
# Would use msmtp here
|
|
log_event "info" "Email alert queued: $title"
|
|
fi
|
|
|
|
# SMS alert
|
|
local sms_enabled=$(uci -q get client-guardian.sms.enabled)
|
|
if [ "$sms_enabled" = "1" ]; then
|
|
# Would use curl to Twilio/Nexmo API
|
|
log_event "info" "SMS alert queued: $title"
|
|
fi
|
|
}
|
|
|
|
# ===================================
|
|
# Nodogsplash Captive Portal Integration
|
|
# ===================================
|
|
|
|
|
|
# Get default policy
|
|
get_policy() {
|
|
json_init
|
|
|
|
local policy=$(uci -q get client-guardian.config.default_policy || echo "captive")
|
|
local auto_approve=$(uci -q get client-guardian.config.auto_approve || echo "0")
|
|
local session_timeout=$(uci -q get client-guardian.config.session_timeout || echo "86400")
|
|
|
|
json_add_string "default_policy" "$policy"
|
|
json_add_boolean "auto_approve" "$auto_approve"
|
|
json_add_int "session_timeout" "$session_timeout"
|
|
|
|
json_add_object "policy_options"
|
|
json_add_string "open" "Allow all clients without authentication"
|
|
json_add_string "captive" "Require captive portal authentication"
|
|
json_add_string "whitelist" "Allow only approved clients"
|
|
json_close_object
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Set default policy
|
|
set_policy() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var policy policy
|
|
json_get_var auto_approve auto_approve
|
|
json_get_var session_timeout session_timeout
|
|
|
|
json_init
|
|
|
|
if [ -z "$policy" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Policy required (open/captive/whitelist)"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
# Validate policy value
|
|
case "$policy" in
|
|
open|captive|whitelist)
|
|
uci set client-guardian.config.default_policy="$policy"
|
|
;;
|
|
*)
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Invalid policy. Must be: open, captive, or whitelist"
|
|
json_dump
|
|
return
|
|
;;
|
|
esac
|
|
|
|
[ -n "$auto_approve" ] && uci set client-guardian.config.auto_approve="$auto_approve"
|
|
[ -n "$session_timeout" ] && uci set client-guardian.config.session_timeout="$session_timeout"
|
|
|
|
uci commit client-guardian
|
|
|
|
# Restart nodogsplash if policy changed to/from captive
|
|
if [ "$policy" = "captive" ]; then
|
|
/etc/init.d/nodogsplash restart 2>/dev/null &
|
|
elif pidof nodogsplash >/dev/null; then
|
|
/etc/init.d/nodogsplash stop 2>/dev/null &
|
|
fi
|
|
|
|
log_event "info" "Default policy changed to: $policy"
|
|
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Policy updated to $policy"
|
|
json_dump
|
|
}
|
|
|
|
# Authorize client via nodogsplash
|
|
|
|
# Deauthorize client via nodogsplash
|
|
|
|
# Get client details
|
|
get_client() {
|
|
read input
|
|
json_load "$input"
|
|
json_get_var mac mac
|
|
|
|
json_init
|
|
|
|
if [ -z "$mac" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "MAC address required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
mac=$(echo "$mac" | tr 'A-F' 'a-f')
|
|
|
|
# Find client in ARP table
|
|
local arp_entry=$(cat /proc/net/arp | grep -i "$mac" | head -1)
|
|
if [ -n "$arp_entry" ]; then
|
|
local ip=$(echo "$arp_entry" | awk '{print $1}')
|
|
local iface=$(echo "$arp_entry" | awk '{print $6}')
|
|
local hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}')
|
|
[ -z "$hostname" ] && hostname="Unknown"
|
|
|
|
json_add_boolean "online" 1
|
|
json_add_string "ip" "$ip"
|
|
json_add_string "hostname" "$hostname"
|
|
json_add_string "interface" "$iface"
|
|
else
|
|
json_add_boolean "online" 0
|
|
fi
|
|
|
|
json_add_string "mac" "$mac"
|
|
|
|
# Get UCI details if exists
|
|
config_load client-guardian
|
|
config_foreach find_client_by_mac client "$mac"
|
|
|
|
if [ -n "$found_section" ]; then
|
|
json_add_boolean "known" 1
|
|
json_add_string "section" "$found_section"
|
|
json_add_string "name" "$(uci -q get client-guardian.$found_section.name)"
|
|
json_add_string "zone" "$(uci -q get client-guardian.$found_section.zone)"
|
|
json_add_string "status" "$(uci -q get client-guardian.$found_section.status)"
|
|
json_add_string "first_seen" "$(uci -q get client-guardian.$found_section.first_seen)"
|
|
json_add_string "last_seen" "$(uci -q get client-guardian.$found_section.last_seen)"
|
|
json_add_string "notes" "$(uci -q get client-guardian.$found_section.notes)"
|
|
else
|
|
json_add_boolean "known" 0
|
|
json_add_string "status" "unknown"
|
|
fi
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Main dispatcher
|
|
case "$1" in
|
|
list)
|
|
echo '{"status":{},"clients":{},"zones":{},"parental":{},"alerts":{},"logs":{"limit":"int","level":"str"},"approve_client":{"mac":"str","name":"str","zone":"str","notes":"str"},"ban_client":{"mac":"str","reason":"str"},"quarantine_client":{"mac":"str"},"update_client":{"section":"str","name":"str","zone":"str","notes":"str","daily_quota":"int","static_ip":"str"},"update_zone":{"id":"str","name":"str","bandwidth_limit":"int","content_filter":"str"},"send_test_alert":{"type":"str"},"get_policy":{},"set_policy":{"policy":"str","auto_approve":"bool","session_timeout":"int"},"get_client":{"mac":"str"},"sync_zones":{},"list_profiles":{},"apply_profile":{"profile_id":"str","auto_refresh":"str","refresh_interval":"str","threat_enabled":"str","auto_ban_threshold":"str","auto_quarantine_threshold":"str"},"clear_rules":{},"safety_status":{}}'
|
|
;;
|
|
call)
|
|
case "$2" in
|
|
status) get_status ;;
|
|
clients) get_clients ;;
|
|
zones) get_zones ;;
|
|
parental) get_parental ;;
|
|
alerts) get_alerts ;;
|
|
logs) get_logs ;;
|
|
approve_client) approve_client ;;
|
|
ban_client) ban_client ;;
|
|
quarantine_client) quarantine_client ;;
|
|
update_client) update_client ;;
|
|
update_zone) update_zone ;;
|
|
send_test_alert) send_test_alert ;;
|
|
get_policy) get_policy ;;
|
|
set_policy) set_policy ;;
|
|
get_client) get_client ;;
|
|
sync_zones)
|
|
json_init
|
|
sync_firewall_zones
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Firewall zones synchronized"
|
|
json_dump
|
|
;;
|
|
list_profiles) list_profiles ;;
|
|
apply_profile) apply_profile ;;
|
|
clear_rules) clear_all_cg_rules ;;
|
|
safety_status)
|
|
json_init
|
|
local blocked=$(count_blocked_devices)
|
|
json_add_int "blocked_devices" "$blocked"
|
|
json_add_int "max_allowed" "$MAX_BLOCKED_DEVICES"
|
|
json_add_boolean "safety_limit_reached" $([ "$blocked" -ge "$MAX_BLOCKED_DEVICES" ] && echo 1 || echo 0)
|
|
json_dump
|
|
;;
|
|
*) echo '{"error": "Unknown method"}' ;;
|
|
esac
|
|
;;
|
|
esac
|