secubox-openwrt/package/secubox/luci-app-client-guardian/root/usr/libexec/rpcd/luci.client-guardian
CyberMind-FR 31a87c5d7a feat(structure): reorganize luci-app packages into package/secubox/ + appstore migration
Major structural reorganization and feature additions:

## Folder Reorganization
- Move 17 luci-app-* packages to package/secubox/ (except luci-app-secubox core hub)
- Update all tooling to support new structure:
  - secubox-tools/quick-deploy.sh: search both locations
  - secubox-tools/validate-modules.sh: validate both directories
  - secubox-tools/fix-permissions.sh: fix permissions in both locations
  - .github/workflows/test-validate.yml: build from both paths
- Update README.md links to new package/secubox/ paths

## AppStore Migration (Complete)
- Add catalog entries for all remaining luci-app packages:
  - network-tweaks.json: Network optimization tools
  - secubox-bonus.json: Documentation & demos hub
- Total: 24 apps in AppStore catalog (22 existing + 2 new)
- New category: 'documentation' for docs/demos/tutorials

## VHost Manager v2.0 Enhancements
- Add profile activation system for Internal Services and Redirects
- Implement createVHost() API wrapper for template-based deployment
- Fix Virtual Hosts view rendering with proper LuCI patterns
- Fix RPCD backend shell script errors (remove invalid local declarations)
- Extend backend validation for nginx return directives (redirect support)
- Add section_id parameter for named VHost profiles
- Add Remove button to Redirects page for feature parity
- Update README to v2.0 with comprehensive feature documentation

## Network Tweaks Dashboard
- Close button added to component details modal

Files changed: 340+ (336 renames with preserved git history)
Packages affected: 19 luci-app, 2 secubox-app, 1 theme, 4 tools

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-01 14:59:38 +01:00

1065 lines
31 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"
# Logging function
log_event() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
}
# Get real-time client list from ARP and DHCP
get_connected_clients() {
local clients=""
# Parse ARP table
while read ip type flags mac mask iface; do
[ -z "$mac" ] && continue
[ "$mac" = "00:00:00:00:00:00" ] && continue
# Get hostname from DHCP leases
local hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}')
[ -z "$hostname" ] && hostname="Unknown"
# Get lease expiry
local lease_time=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $1}')
echo "$mac|$ip|$hostname|$iface|$lease_time"
done < /proc/net/arp
}
# 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")
local portal_enabled=$(uci -q get client-guardian.portal.enabled || echo "1")
json_add_boolean "enabled" "$enabled"
json_add_string "default_policy" "$default_policy"
json_add_boolean "portal_enabled" "$portal_enabled"
# 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))
}
# Get all clients (known + detected)
get_clients() {
json_init
json_add_array "clients"
# 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_load client-guardian
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"
# Update last seen
uci set client-guardian.$found_section.last_seen="$(date '+%Y-%m-%d %H:%M:%S')"
else
json_add_boolean "known" 0
json_add_string "name" "$hostname"
json_add_string "zone" "quarantine"
json_add_string "status" "unknown"
json_add_string "first_seen" "$(date '+%Y-%m-%d %H:%M:%S')"
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"
json_close_object
found_section=""
done << EOF
$(get_connected_clients)
EOF
# Add offline known clients
config_load client-guardian
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
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"
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" "$(uci -q get client-guardian.$section.internet_access || echo 0)"
json_add_boolean "local_access" "$(uci -q get client-guardian.$section.local_access || echo 0)"
json_add_boolean "inter_client" "$(uci -q get client-guardian.$section.inter_client || echo 0)"
json_add_int "bandwidth_limit" "$(uci -q get client-guardian.$section.bandwidth_limit || echo 0)"
json_add_boolean "time_restrictions" "$(uci -q get client-guardian.$section.time_restrictions || echo 0)"
json_add_string "content_filter" "$(uci -q get client-guardian.$section.content_filter)"
json_add_boolean "portal_required" "$(uci -q get client-guardian.$section.portal_required || echo 0)"
# 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 portal configuration
get_portal() {
json_init
json_add_boolean "enabled" "$(uci -q get client-guardian.portal.enabled || echo 1)"
json_add_string "title" "$(uci -q get client-guardian.portal.title)"
json_add_string "subtitle" "$(uci -q get client-guardian.portal.subtitle)"
json_add_string "logo" "$(uci -q get client-guardian.portal.logo)"
json_add_string "background_color" "$(uci -q get client-guardian.portal.background_color)"
json_add_string "accent_color" "$(uci -q get client-guardian.portal.accent_color)"
json_add_boolean "require_terms" "$(uci -q get client-guardian.portal.require_terms || echo 0)"
json_add_string "auth_method" "$(uci -q get client-guardian.portal.auth_method)"
json_add_boolean "allow_registration" "$(uci -q get client-guardian.portal.allow_registration || echo 0)"
json_add_boolean "registration_approval" "$(uci -q get client-guardian.portal.registration_approval || echo 1)"
json_add_boolean "show_bandwidth_info" "$(uci -q get client-guardian.portal.show_bandwidth_info || echo 0)"
# Active sessions count
local sessions=0
[ -f "/tmp/client-guardian-sessions" ] && sessions=$(wc -l < /tmp/client-guardian-sessions)
json_add_int "active_sessions" "$sessions"
json_dump
}
# 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
}
# 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
# Block in firewall
block_client "$mac"
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
}
# Update portal settings
update_portal() {
read input
json_load "$input"
json_get_var title title
json_get_var subtitle subtitle
json_get_var accent_color accent_color
json_get_var auth_method auth_method
json_get_var guest_password guest_password
json_init
[ -n "$title" ] && uci set client-guardian.portal.title="$title"
[ -n "$subtitle" ] && uci set client-guardian.portal.subtitle="$subtitle"
[ -n "$accent_color" ] && uci set client-guardian.portal.accent_color="$accent_color"
[ -n "$auth_method" ] && uci set client-guardian.portal.auth_method="$auth_method"
[ -n "$guest_password" ] && uci set client-guardian.portal.guest_password="$guest_password"
uci commit client-guardian
log_event "info" "Portal settings updated"
json_add_boolean "success" 1
json_add_string "message" "Portal updated successfully"
json_dump
}
# Helper: Apply client firewall rules
apply_client_rules() {
local mac="$1"
local zone="$2"
# Remove existing rules for this MAC
iptables -D FORWARD -m mac --mac-source "$mac" -j DROP 2>/dev/null
iptables -D FORWARD -m mac --mac-source "$mac" -j ACCEPT 2>/dev/null
case "$zone" in
blocked)
iptables -I FORWARD -m mac --mac-source "$mac" -j DROP
;;
quarantine)
# Only portal access
iptables -I FORWARD -m mac --mac-source "$mac" -j DROP
iptables -I FORWARD -m mac --mac-source "$mac" -p tcp --dport 80 -d $(uci -q get network.lan.ipaddr || echo "192.168.1.1") -j ACCEPT
;;
*)
# Allow based on zone settings
iptables -I FORWARD -m mac --mac-source "$mac" -j ACCEPT
;;
esac
}
# 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
# ===================================
# List active captive portal sessions (nodogsplash)
list_sessions() {
json_init
json_add_array "sessions"
# Check if nodogsplash is running
if pidof nodogsplash >/dev/null; then
# Get sessions from ndsctl
ndsctl status 2>/dev/null | grep -A 100 "Client" | while read line; do
# Parse ndsctl output
# Format: IP MAC Duration Download Upload
if echo "$line" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"; then
local ip=$(echo "$line" | awk '{print $1}')
local mac=$(echo "$line" | awk '{print $2}' | tr 'A-F' 'a-f')
local duration=$(echo "$line" | awk '{print $3}')
local downloaded=$(echo "$line" | awk '{print $4}')
local uploaded=$(echo "$line" | awk '{print $5}')
# Get hostname
local hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}')
[ -z "$hostname" ] && hostname="Unknown"
json_add_object
json_add_string "ip" "$ip"
json_add_string "mac" "$mac"
json_add_string "hostname" "$hostname"
json_add_int "duration" "$duration"
json_add_int "downloaded" "$downloaded"
json_add_int "uploaded" "$uploaded"
json_add_string "state" "authenticated"
json_close_object
fi
done
fi
json_close_array
# Add nodogsplash status
json_add_object "nodogsplash"
if pidof nodogsplash >/dev/null; then
json_add_boolean "running" 1
json_add_string "status" "active"
else
json_add_boolean "running" 0
json_add_string "status" "stopped"
fi
json_close_object
json_dump
}
# Get default policy
get_policy() {
json_init
local policy=$(uci -q get client-guardian.config.default_policy || echo "captive")
local portal_enabled=$(uci -q get client-guardian.portal.enabled || echo "1")
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 "portal_enabled" "$portal_enabled"
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 portal_enabled portal_enabled
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 "$portal_enabled" ] && uci set client-guardian.portal.enabled="$portal_enabled"
[ -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
authorize_client() {
read input
json_load "$input"
json_get_var mac mac
json_get_var ip ip
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')
# Use ndsctl to authorize
if pidof nodogsplash >/dev/null; then
if [ -n "$ip" ]; then
# Authorize by IP if provided
ndsctl auth "$ip" 2>&1
else
# Find IP by MAC
local client_ip=$(cat /proc/net/arp | grep -i "$mac" | awk '{print $1}' | head -1)
if [ -n "$client_ip" ]; then
ndsctl auth "$client_ip" 2>&1
ip="$client_ip"
else
json_add_boolean "success" 0
json_add_string "error" "Client not found or offline"
json_dump
return
fi
fi
log_event "info" "Client authorized via nodogsplash: $mac ($ip)"
json_add_boolean "success" 1
json_add_string "message" "Client $mac authorized"
json_add_string "ip" "$ip"
else
json_add_boolean "success" 0
json_add_string "error" "Nodogsplash not running"
fi
json_dump
}
# Deauthorize client via nodogsplash
deauthorize_client() {
read input
json_load "$input"
json_get_var mac mac
json_get_var ip ip
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')
# Use ndsctl to deauthorize
if pidof nodogsplash >/dev/null; then
if [ -n "$ip" ]; then
ndsctl deauth "$ip" 2>&1
else
# Find IP by MAC
local client_ip=$(cat /proc/net/arp | grep -i "$mac" | awk '{print $1}' | head -1)
if [ -n "$client_ip" ]; then
ndsctl deauth "$client_ip" 2>&1
ip="$client_ip"
else
json_add_boolean "success" 0
json_add_string "error" "Client not found in active sessions"
json_dump
return
fi
fi
log_event "info" "Client deauthorized via nodogsplash: $mac ($ip)"
json_add_boolean "success" 1
json_add_string "message" "Client $mac deauthorized"
else
json_add_boolean "success" 0
json_add_string "error" "Nodogsplash not running"
fi
json_dump
}
# 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":{},"portal":{},"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"},"update_portal":{"title":"str","subtitle":"str","accent_color":"str"},"send_test_alert":{"type":"str"},"list_sessions":{},"get_policy":{},"set_policy":{"policy":"str","portal_enabled":"bool","auto_approve":"bool","session_timeout":"int"},"authorize_client":{"mac":"str","ip":"str"},"deauthorize_client":{"mac":"str","ip":"str"},"get_client":{"mac":"str"}}'
;;
call)
case "$2" in
status) get_status ;;
clients) get_clients ;;
zones) get_zones ;;
parental) get_parental ;;
portal) get_portal ;;
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 ;;
update_portal) update_portal ;;
send_test_alert) send_test_alert ;;
list_sessions) list_sessions ;;
get_policy) get_policy ;;
set_policy) set_policy ;;
authorize_client) authorize_client ;;
deauthorize_client) deauthorize_client ;;
get_client) get_client ;;
*) echo '{"error": "Unknown method"}' ;;
esac
;;
esac