diff --git a/package/secubox/luci-app-cdn-cache/Makefile b/package/secubox/luci-app-cdn-cache/Makefile index 1c99175e..7f1ae18c 100644 --- a/package/secubox/luci-app-cdn-cache/Makefile +++ b/package/secubox/luci-app-cdn-cache/Makefile @@ -19,7 +19,8 @@ LUCI_PKGARCH:=all # - Helper scripts: 755 (if executable) # - Config files: 644 (readable by all, writable by root) # - CSS/JS files: 644 (set automatically by luci.mk) -PKG_FILE_MODES:=/usr/libexec/rpcd/luci.cdn-cache:root:root:755 +PKG_FILE_MODES:=/usr/libexec/rpcd/luci.cdn-cache:root:root:755 \ + /etc/hotplug.d/iface/99-cdn-offline:root:root:755 include $(TOPDIR)/feeds/luci/luci.mk diff --git a/package/secubox/luci-app-cdn-cache/root/etc/config/cdn-cache b/package/secubox/luci-app-cdn-cache/root/etc/config/cdn-cache index 03b1bff8..d282fa34 100644 --- a/package/secubox/luci-app-cdn-cache/root/etc/config/cdn-cache +++ b/package/secubox/luci-app-cdn-cache/root/etc/config/cdn-cache @@ -87,3 +87,19 @@ config exclusion 'bypass_streaming' config statistics 'stats' option retention_days '30' option sample_interval '60' + +# API Failover configuration - serve stale content on backend errors +config api_failover 'api_failover' + option enabled '1' + # Serve stale content for this many seconds after backend failure + option stale_if_error '86400' + # Offline mode - serve stale content for all requests (set by hotplug) + option offline_mode '0' + # Enable collapsed forwarding (combine duplicate requests) + option collapsed_forwarding '1' + # API URL patterns to cache aggressively + list api_patterns '/api/' + list api_patterns '.json' + # Connection timeouts (seconds) + option connect_timeout '5' + option read_timeout '30' diff --git a/package/secubox/luci-app-cdn-cache/root/etc/hotplug.d/iface/99-cdn-offline b/package/secubox/luci-app-cdn-cache/root/etc/hotplug.d/iface/99-cdn-offline new file mode 100644 index 00000000..afb9f35d --- /dev/null +++ b/package/secubox/luci-app-cdn-cache/root/etc/hotplug.d/iface/99-cdn-offline @@ -0,0 +1,39 @@ +#!/bin/sh +# CDN Cache Offline Mode - Auto-detect WAN connectivity +# Enables offline mode (serve stale) when WAN goes down +# Disables offline mode when WAN comes back up + +# Only handle WAN interface events +[ "$INTERFACE" = "wan" ] || [ "$INTERFACE" = "wan6" ] || exit 0 + +# Check if API failover is enabled +api_enabled=$(uci -q get cdn-cache.api_failover.enabled) +[ "$api_enabled" = "1" ] || exit 0 + +case "$ACTION" in + ifdown) + # WAN went down - enable offline mode + logger -t cdn-cache "WAN down ($INTERFACE) - enabling offline mode" + uci set cdn-cache.api_failover.offline_mode='1' + uci commit cdn-cache + + # Reload Squid to apply offline mode + if [ -x /etc/init.d/cdn-cache ]; then + /etc/init.d/cdn-cache reload 2>/dev/null & + fi + ;; + + ifup) + # WAN came back up - disable offline mode + logger -t cdn-cache "WAN up ($INTERFACE) - disabling offline mode" + uci set cdn-cache.api_failover.offline_mode='0' + uci commit cdn-cache + + # Reload Squid to disable offline mode + if [ -x /etc/init.d/cdn-cache ]; then + /etc/init.d/cdn-cache reload 2>/dev/null & + fi + ;; +esac + +exit 0 diff --git a/package/secubox/luci-app-cdn-cache/root/etc/init.d/cdn-cache b/package/secubox/luci-app-cdn-cache/root/etc/init.d/cdn-cache index 5299f843..bac94571 100755 --- a/package/secubox/luci-app-cdn-cache/root/etc/init.d/cdn-cache +++ b/package/secubox/luci-app-cdn-cache/root/etc/init.d/cdn-cache @@ -251,10 +251,48 @@ cache_log $LOG_DIR/cache.log cache_store_log none debug_options ALL,$log_level +# === API FAILOVER === +EOF + + # Add API failover configuration if enabled + local api_enabled stale_if_error offline_mode collapsed_forwarding connect_timeout read_timeout + api_enabled=$(uci -q get cdn-cache.api_failover.enabled || echo "1") + stale_if_error=$(uci -q get cdn-cache.api_failover.stale_if_error || echo "86400") + offline_mode=$(uci -q get cdn-cache.api_failover.offline_mode || echo "0") + collapsed_forwarding=$(uci -q get cdn-cache.api_failover.collapsed_forwarding || echo "1") + connect_timeout=$(uci -q get cdn-cache.api_failover.connect_timeout || echo "5") + read_timeout=$(uci -q get cdn-cache.api_failover.read_timeout || echo "30") + + if [ "$api_enabled" = "1" ]; then + cat >> "$SQUID_CONF" << EOF +# API Failover - serve stale content on backend errors +refresh_pattern -i /api/ 1 20% 4320 stale-if-error=$stale_if_error ignore-no-store +refresh_pattern -i \.json\$ 1 20% 4320 stale-if-error=$stale_if_error +EOF + + if [ "$collapsed_forwarding" = "1" ]; then + cat >> "$SQUID_CONF" << EOF + +# Collapsed forwarding - combine duplicate requests during backend fetch +collapsed_forwarding on +EOF + fi + + if [ "$offline_mode" = "1" ]; then + cat >> "$SQUID_CONF" << EOF + +# Offline mode - serve stale content for all requests +offline_mode on +EOF + fi + fi + + cat >> "$SQUID_CONF" << EOF + # === PERFORMANCE === dns_nameservers 8.8.8.8 8.8.4.4 -connect_timeout 30 seconds -read_timeout 90 seconds +connect_timeout $connect_timeout seconds +read_timeout $read_timeout seconds request_timeout 60 seconds client_lifetime 1 day half_closed_clients off @@ -262,7 +300,7 @@ pconn_timeout 60 seconds quick_abort_min 0 KB quick_abort_max 0 KB quick_abort_pct 95 -negative_ttl 5 minutes +negative_ttl 30 seconds # === CACHE MANAGER === cache_mgr secubox@local diff --git a/package/secubox/luci-app-interceptor/Makefile b/package/secubox/luci-app-interceptor/Makefile new file mode 100644 index 00000000..3e084d05 --- /dev/null +++ b/package/secubox/luci-app-interceptor/Makefile @@ -0,0 +1,17 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-interceptor +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_ARCH:=all +PKG_LICENSE:=GPL-3.0 +PKG_MAINTAINER:=CyberMind + +LUCI_TITLE:=SecuBox InterceptoR - Unified Traffic Interception Dashboard +LUCI_DESCRIPTION:=Unified dashboard for WPAD, mitmproxy, CDN Cache, Cookie Tracker, and API Failover +LUCI_DEPENDS:=+luci-base +rpcd +LUCI_PKGARCH:=all + +PKG_FILE_MODES:=/usr/libexec/rpcd/luci.interceptor:root:root:755 + +include $(TOPDIR)/feeds/luci/luci.mk diff --git a/package/secubox/luci-app-interceptor/README.md b/package/secubox/luci-app-interceptor/README.md new file mode 100644 index 00000000..fee377e6 --- /dev/null +++ b/package/secubox/luci-app-interceptor/README.md @@ -0,0 +1,84 @@ +# LuCI InterceptoR Dashboard + +Unified dashboard for SecuBox InterceptoR - "The Gandalf Proxy" transparent traffic interception system. + +## Features + +- **Health Score** - Overall interception coverage (0-100%) +- **5 Pillar Status Cards**: + - WPAD Redirector - Auto-proxy discovery status + - MITM Proxy - Threat detection and connection stats + - CDN Cache - Hit ratio and bandwidth savings + - Cookie Tracker - Tracking cookie detection + - API Failover - Stale content serving status +- **Quick Links** - Direct access to individual module dashboards + +## Installation + +```bash +opkg install luci-app-interceptor +``` + +## Menu Location + +SecuBox > InterceptoR > Overview + +## Architecture + +InterceptoR aggregates status from 5 interception pillars: + +``` + +-------------------+ + | InterceptoR | + | Dashboard | + +-------------------+ + | + +------+------+--------+--------+------+ + | | | | | | + WPAD MITM CDN Cookie API + Proxy Proxy Cache Tracker Failover +``` + +### Pillar Modules + +| Pillar | Package | Function | +|--------|---------|----------| +| WPAD | luci-app-network-tweaks | Auto-proxy via DHCP/DNS | +| MITM | secubox-app-mitmproxy | HTTPS inspection, threat detection | +| CDN Cache | luci-app-cdn-cache | Content caching, bandwidth savings | +| Cookie Tracker | secubox-cookie-tracker | Cookie classification, tracking | +| API Failover | luci-app-cdn-cache | Stale-if-error, offline mode | + +## RPCD Methods + +| Method | Description | +|--------|-------------| +| `status` | Aggregated status from all pillars | +| `getPillarStatus` | Status for specific pillar | + +## Health Score Calculation + +- 20 points: WPAD enabled or enforcement active +- 20 points: mitmproxy running +- 20 points: CDN Cache (Squid) running +- 20 points: Cookie Tracker enabled +- 20 points: API Failover enabled + +## Public Access + +The `status` method is available to unauthenticated users for monitoring dashboards. + +## Dependencies + +- luci-base +- rpcd + +Optional (for full functionality): +- luci-app-network-tweaks +- secubox-app-mitmproxy +- luci-app-cdn-cache +- secubox-cookie-tracker + +## License + +GPL-3.0 diff --git a/package/secubox/luci-app-interceptor/htdocs/luci-static/resources/view/interceptor/overview.js b/package/secubox/luci-app-interceptor/htdocs/luci-static/resources/view/interceptor/overview.js new file mode 100644 index 00000000..22612292 --- /dev/null +++ b/package/secubox/luci-app-interceptor/htdocs/luci-static/resources/view/interceptor/overview.js @@ -0,0 +1,183 @@ +'use strict'; +'require view'; +'require rpc'; +'require poll'; + +var callGetStatus = rpc.declare({ + object: 'luci.interceptor', + method: 'status', + expect: {} +}); + +var PILLAR_ICONS = { + wpad: '🌐', // Globe for WPAD + mitm: '🛡', // Shield for mitmproxy + cdn_cache: '💾', // Disk for CDN Cache + cookie_tracker: '🍪', // Cookie for Cookie Tracker + api_failover: '⚡' // Lightning for API Failover +}; + +var PILLAR_NAMES = { + wpad: 'WPAD Redirector', + mitm: 'MITM Proxy', + cdn_cache: 'CDN Cache', + cookie_tracker: 'Cookie Tracker', + api_failover: 'API Failover' +}; + +return view.extend({ + load: function() { + return callGetStatus(); + }, + + renderHealthScore: function(data) { + var summary = data.summary || {}; + var score = summary.health_score || 0; + var pillars_active = summary.pillars_active || 0; + var pillars_total = summary.pillars_total || 5; + + var scoreColor = score >= 80 ? '#4caf50' : score >= 50 ? '#ff9800' : '#f44336'; + + return E('div', { 'class': 'cbi-section', 'style': 'text-align: center; padding: 30px;' }, [ + E('div', { 'style': 'font-size: 64px; margin-bottom: 10px;' }, [ + E('span', { 'style': 'color: ' + scoreColor + '; font-weight: bold;' }, score + '%') + ]), + E('div', { 'style': 'font-size: 18px; color: #888;' }, + 'InterceptoR Health Score'), + E('div', { 'style': 'font-size: 14px; color: #666; margin-top: 10px;' }, + pillars_active + ' of ' + pillars_total + ' pillars active') + ]); + }, + + renderPillarCard: function(id, data, name, icon) { + var pillarData = data[id] || {}; + var enabled = pillarData.enabled || false; + var running = pillarData.running !== undefined ? pillarData.running : enabled; + + var statusColor = running ? '#4caf50' : '#f44336'; + var statusText = running ? 'Active' : 'Inactive'; + + var statsHtml = []; + + // Build stats based on pillar type + switch(id) { + case 'wpad': + if (pillarData.dhcp_configured) { + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' }, + 'DHCP: Configured')); + } + if (pillarData.enforce_enabled) { + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #4caf50;' }, + 'Enforcement: ON')); + } + break; + + case 'mitm': + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' }, + 'Threats Today: ' + (pillarData.threats_today || 0))); + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' }, + 'Active: ' + (pillarData.active_connections || 0))); + break; + + case 'cdn_cache': + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' }, + 'Hit Ratio: ' + (pillarData.hit_ratio || 0) + '%')); + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' }, + 'Saved: ' + (pillarData.saved_mb || 0) + ' MB')); + if (pillarData.offline_mode) { + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #ff9800;' }, + 'OFFLINE MODE')); + } + break; + + case 'cookie_tracker': + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' }, + 'Cookies: ' + (pillarData.total_cookies || 0))); + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #f44336;' }, + 'Trackers: ' + (pillarData.trackers_detected || 0))); + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' }, + 'Blocked: ' + (pillarData.blocked || 0))); + break; + + case 'api_failover': + statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' }, + 'Stale Serves: ' + (pillarData.stale_serves || 0))); + break; + } + + return E('div', { + 'style': 'background: #222; border-radius: 8px; padding: 20px; margin: 10px; ' + + 'min-width: 200px; flex: 1; text-align: center; ' + + 'border-left: 4px solid ' + statusColor + ';' + }, [ + E('div', { 'style': 'font-size: 32px; margin-bottom: 10px;' }, icon), + E('div', { 'style': 'font-size: 16px; font-weight: bold; margin-bottom: 5px;' }, name), + E('div', { 'style': 'font-size: 12px; color: ' + statusColor + '; margin-bottom: 10px;' }, + statusText), + E('div', {}, statsHtml) + ]); + }, + + render: function(data) { + if (!data || !data.success) { + return E('div', { 'class': 'alert-message warning' }, + 'Failed to load InterceptoR status'); + } + + var pillars = [ + { id: 'wpad', name: PILLAR_NAMES.wpad, icon: PILLAR_ICONS.wpad }, + { id: 'mitm', name: PILLAR_NAMES.mitm, icon: PILLAR_ICONS.mitm }, + { id: 'cdn_cache', name: PILLAR_NAMES.cdn_cache, icon: PILLAR_ICONS.cdn_cache }, + { id: 'cookie_tracker', name: PILLAR_NAMES.cookie_tracker, icon: PILLAR_ICONS.cookie_tracker }, + { id: 'api_failover', name: PILLAR_NAMES.api_failover, icon: PILLAR_ICONS.api_failover } + ]; + + var cards = pillars.map(function(p) { + return this.renderPillarCard(p.id, data, p.name, p.icon); + }, this); + + return E('div', { 'class': 'cbi-map' }, [ + E('h2', { 'style': 'margin-bottom: 5px;' }, 'SecuBox InterceptoR'), + E('p', { 'style': 'color: #888; margin-bottom: 20px;' }, + 'The Gandalf Proxy - Transparent traffic interception and protection'), + + // Health Score + this.renderHealthScore(data), + + // Pillars Grid + E('h3', { 'style': 'margin-top: 30px;' }, 'Interception Pillars'), + E('div', { + 'style': 'display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 15px;' + }, cards), + + // Quick Links + E('h3', { 'style': 'margin-top: 30px;' }, 'Quick Links'), + E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px;' }, [ + E('a', { + 'href': '/cgi-bin/luci/admin/secubox/network-tweaks', + 'class': 'cbi-button', + 'style': 'text-decoration: none;' + }, 'Network Tweaks (WPAD)'), + E('a', { + 'href': '/cgi-bin/luci/admin/secubox/mitmproxy/overview', + 'class': 'cbi-button', + 'style': 'text-decoration: none;' + }, 'mitmproxy'), + E('a', { + 'href': '/cgi-bin/luci/admin/secubox/cdn-cache/overview', + 'class': 'cbi-button', + 'style': 'text-decoration: none;' + }, 'CDN Cache'), + E('a', { + 'href': '/cgi-bin/luci/admin/secubox/crowdsec/overview', + 'class': 'cbi-button', + 'style': 'text-decoration: none;' + }, 'CrowdSec') + ]) + ]); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-interceptor/root/usr/libexec/rpcd/luci.interceptor b/package/secubox/luci-app-interceptor/root/usr/libexec/rpcd/luci.interceptor new file mode 100644 index 00000000..ae5b857c --- /dev/null +++ b/package/secubox/luci-app-interceptor/root/usr/libexec/rpcd/luci.interceptor @@ -0,0 +1,253 @@ +#!/bin/sh +# RPCD backend for SecuBox InterceptoR Dashboard +# Aggregates status from all interception pillars + +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +# Helper to safely get JSON from ubus +get_ubus_json() { + local result + result=$(ubus call "$1" "$2" 2>/dev/null) + [ -z "$result" ] && result='{}' + echo "$result" +} + +# Get WPAD pillar status +get_wpad_status() { + local enabled=0 dhcp_configured=0 enforce_enabled=0 pac_url="" + + # Check PAC file + [ -f /www/wpad.dat ] && enabled=1 + + # Check DHCP option 252 + local dhcp_opt=$(uci -q get dhcp.lan.dhcp_option_force) + if echo "$dhcp_opt" | grep -q "252"; then + dhcp_configured=1 + pac_url=$(echo "$dhcp_opt" | sed 's/252,//') + fi + + # Check WPAD enforcement + enforce_enabled=$(uci -q get network_tweaks.global.wpad_enforce || echo "0") + + json_add_object "wpad" + json_add_boolean "enabled" "$enabled" + json_add_boolean "dhcp_configured" "$dhcp_configured" + json_add_boolean "enforce_enabled" "$enforce_enabled" + json_add_string "pac_url" "$pac_url" + json_close_object +} + +# Get mitmproxy pillar status +get_mitm_status() { + local enabled=0 running=0 threats_today=0 connections=0 + + # Check UCI config + enabled=$(uci -q get mitmproxy.main.enabled || echo "0") + + # Check if LXC container is running + if command -v lxc-ls >/dev/null 2>&1; then + lxc-ls --running 2>/dev/null | grep -q "secbx-mitmproxy" && running=1 + fi + + # Count today's threats from log + local today=$(date +%Y-%m-%d) + if [ -f /srv/mitmproxy/threats.log ]; then + threats_today=$(grep -c "$today" /srv/mitmproxy/threats.log 2>/dev/null || echo "0") + fi + + # Get connection count from mitmproxy stats if available + if [ "$running" = "1" ]; then + # Try to get stats via mitmproxyctl if available + if [ -x /usr/sbin/mitmproxyctl ]; then + connections=$(/usr/sbin/mitmproxyctl stats 2>/dev/null | jsonfilter -e '@.active_connections' 2>/dev/null || echo "0") + fi + fi + + json_add_object "mitm" + json_add_boolean "enabled" "$enabled" + json_add_boolean "running" "$running" + json_add_int "threats_today" "${threats_today:-0}" + json_add_int "active_connections" "${connections:-0}" + json_close_object +} + +# Get CDN Cache pillar status +get_cdn_status() { + local enabled=0 running=0 hit_ratio=0 saved_mb=0 offline_mode=0 + + # Check UCI config + enabled=$(uci -q get cdn-cache.main.enabled || echo "0") + offline_mode=$(uci -q get cdn-cache.api_failover.offline_mode || echo "0") + + # Check if Squid is running + if pgrep squid >/dev/null 2>&1; then + running=1 + fi + + # Get cache stats from Squid if running + if [ "$running" = "1" ] && command -v squidclient >/dev/null 2>&1; then + local stats=$(squidclient -h 127.0.0.1 mgr:info 2>/dev/null) + if [ -n "$stats" ]; then + # Parse hit ratio + hit_ratio=$(echo "$stats" | grep -o 'Request Hit Ratios:.*' | grep -oP '\d+' | head -1) + [ -z "$hit_ratio" ] && hit_ratio=0 + + # Estimate bandwidth saved (simplified) + local cache_hits=$(echo "$stats" | grep -o 'Cache Hits:.*' | grep -oP '\d+' | head -1) + [ -n "$cache_hits" ] && saved_mb=$((cache_hits / 1024)) + fi + fi + + json_add_object "cdn_cache" + json_add_boolean "enabled" "$enabled" + json_add_boolean "running" "$running" + json_add_boolean "offline_mode" "$offline_mode" + json_add_int "hit_ratio" "${hit_ratio:-0}" + json_add_int "saved_mb" "${saved_mb:-0}" + json_close_object +} + +# Get Cookie Tracker pillar status +get_cookie_status() { + local enabled=0 total_cookies=0 trackers=0 blocked=0 + + # Check UCI config + enabled=$(uci -q get cookie-tracker.main.enabled || echo "0") + + # Get stats from database + local db="/var/lib/cookie-tracker/cookies.db" + if [ -f "$db" ]; then + total_cookies=$(sqlite3 "$db" "SELECT COUNT(*) FROM cookies;" 2>/dev/null || echo "0") + trackers=$(sqlite3 "$db" "SELECT COUNT(*) FROM cookies WHERE category IN ('tracking', 'advertising');" 2>/dev/null || echo "0") + blocked=$(sqlite3 "$db" "SELECT COUNT(*) FROM cookies WHERE blocked=1;" 2>/dev/null || echo "0") + fi + + json_add_object "cookie_tracker" + json_add_boolean "enabled" "$enabled" + json_add_int "total_cookies" "${total_cookies:-0}" + json_add_int "trackers_detected" "${trackers:-0}" + json_add_int "blocked" "${blocked:-0}" + json_close_object +} + +# Get API Failover pillar status +get_failover_status() { + local enabled=0 stale_serves=0 failed_origins=0 + + # Check UCI config + enabled=$(uci -q get cdn-cache.api_failover.enabled || echo "0") + + # Count stale serves from Squid log (last 24h) + local yesterday=$(date -d "yesterday" +%Y/%m/%d 2>/dev/null || date -d "-1 day" +%Y/%m/%d 2>/dev/null) + if [ -f /var/log/cdn-cache/access.log ] && [ -n "$yesterday" ]; then + stale_serves=$(grep -c "TCP_STALE_HIT" /var/log/cdn-cache/access.log 2>/dev/null || echo "0") + fi + + json_add_object "api_failover" + json_add_boolean "enabled" "$enabled" + json_add_int "stale_serves" "${stale_serves:-0}" + json_add_int "failed_origins" "${failed_origins:-0}" + json_close_object +} + +case "$1" in + list) + json_init + json_add_object "status" + json_close_object + json_add_object "getPillarStatus" + json_add_string "pillar" "string" + json_close_object + json_dump + ;; + + call) + case "$2" in + status) + # Aggregate all pillar statuses + json_init + json_add_boolean "success" 1 + + # Get all pillar statuses + get_wpad_status + get_mitm_status + get_cdn_status + get_cookie_status + get_failover_status + + # Overall health score (0-100) + local score=0 + local pillars_active=0 + + # WPAD + [ "$(uci -q get network_tweaks.global.wpad_enforce)" = "1" ] || [ -f /www/wpad.dat ] && { + score=$((score + 20)) + pillars_active=$((pillars_active + 1)) + } + + # mitmproxy running + pgrep -x mitmproxy >/dev/null 2>&1 || lxc-ls --running 2>/dev/null | grep -q "secbx-mitmproxy" && { + score=$((score + 20)) + pillars_active=$((pillars_active + 1)) + } + + # CDN Cache running + pgrep squid >/dev/null 2>&1 && { + score=$((score + 20)) + pillars_active=$((pillars_active + 1)) + } + + # Cookie Tracker enabled + [ "$(uci -q get cookie-tracker.main.enabled)" = "1" ] && { + score=$((score + 20)) + pillars_active=$((pillars_active + 1)) + } + + # API Failover enabled + [ "$(uci -q get cdn-cache.api_failover.enabled)" = "1" ] && { + score=$((score + 20)) + pillars_active=$((pillars_active + 1)) + } + + json_add_object "summary" + json_add_int "health_score" "$score" + json_add_int "pillars_active" "$pillars_active" + json_add_int "pillars_total" "5" + json_close_object + + json_dump + ;; + + getPillarStatus) + read input + json_load "$input" + json_get_var pillar pillar + + json_init + json_add_boolean "success" 1 + + case "$pillar" in + wpad) get_wpad_status ;; + mitm) get_mitm_status ;; + cdn) get_cdn_status ;; + cookie) get_cookie_status ;; + failover) get_failover_status ;; + *) + json_add_boolean "success" 0 + json_add_string "error" "Unknown pillar: $pillar" + ;; + esac + + json_dump + ;; + + *) + json_init + json_add_boolean "success" 0 + json_add_string "error" "Unknown method" + json_dump + ;; + esac + ;; +esac diff --git a/package/secubox/luci-app-interceptor/root/usr/share/luci/menu.d/luci-app-interceptor.json b/package/secubox/luci-app-interceptor/root/usr/share/luci/menu.d/luci-app-interceptor.json new file mode 100644 index 00000000..3d0fd5a3 --- /dev/null +++ b/package/secubox/luci-app-interceptor/root/usr/share/luci/menu.d/luci-app-interceptor.json @@ -0,0 +1,21 @@ +{ + "admin/secubox/interceptor": { + "title": "InterceptoR", + "order": 35, + "action": { + "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-interceptor"], + "uci": {} + } + }, + "admin/secubox/interceptor/overview": { + "title": "Overview", + "order": 1, + "action": { + "type": "view", + "path": "interceptor/overview" + } + } +} diff --git a/package/secubox/luci-app-interceptor/root/usr/share/rpcd/acl.d/luci-app-interceptor.json b/package/secubox/luci-app-interceptor/root/usr/share/rpcd/acl.d/luci-app-interceptor.json new file mode 100644 index 00000000..86d9804d --- /dev/null +++ b/package/secubox/luci-app-interceptor/root/usr/share/rpcd/acl.d/luci-app-interceptor.json @@ -0,0 +1,24 @@ +{ + "luci-app-interceptor": { + "description": "Grant access to InterceptoR Dashboard", + "read": { + "ubus": { + "luci.interceptor": ["status", "getPillarStatus"] + }, + "uci": ["network_tweaks", "mitmproxy", "cdn-cache", "cookie-tracker"] + }, + "write": { + "ubus": { + "luci.interceptor": [] + } + } + }, + "unauthenticated": { + "description": "Public access to InterceptoR status", + "read": { + "ubus": { + "luci.interceptor": ["status"] + } + } + } +} diff --git a/package/secubox/luci-app-network-tweaks/root/etc/config/network_tweaks b/package/secubox/luci-app-network-tweaks/root/etc/config/network_tweaks index a3ba6165..0b94afbc 100644 --- a/package/secubox/luci-app-network-tweaks/root/etc/config/network_tweaks +++ b/package/secubox/luci-app-network-tweaks/root/etc/config/network_tweaks @@ -5,3 +5,5 @@ config global 'global' option sync_dnsmasq '1' option lan_interface 'lan' option default_ip '' + # WPAD enforcement - redirect HTTP/HTTPS from non-WPAD-compliant clients to proxy + option wpad_enforce '0' diff --git a/package/secubox/luci-app-network-tweaks/root/usr/libexec/rpcd/luci.network-tweaks b/package/secubox/luci-app-network-tweaks/root/usr/libexec/rpcd/luci.network-tweaks index 81033415..eb58628b 100755 --- a/package/secubox/luci-app-network-tweaks/root/usr/libexec/rpcd/luci.network-tweaks +++ b/package/secubox/luci-app-network-tweaks/root/usr/libexec/rpcd/luci.network-tweaks @@ -345,6 +345,11 @@ case "$1" in json_add_string "url" "string" json_add_string "timeout" "integer" json_close_object + json_add_object "getWpadEnforce" + json_close_object + json_add_object "setWpadEnforce" + json_add_string "enabled" "boolean" + json_close_object json_dump ;; @@ -843,6 +848,86 @@ OPKG_EOF json_dump ;; + getWpadEnforce) + # Get WPAD enforcement (iptables redirect) status + json_init + json_add_boolean "success" 1 + + # Check if iptables rules exist for WPAD enforcement + enforce_http=0 + enforce_https=0 + + if iptables -t nat -L PREROUTING -n 2>/dev/null | grep -q "SECUBOX_WPAD_HTTP"; then + enforce_http=1 + fi + if iptables -t nat -L PREROUTING -n 2>/dev/null | grep -q "SECUBOX_WPAD_HTTPS"; then + enforce_https=1 + fi + + # Check UCI config + config_load "$CONFIG" + config_get wpad_enforce global wpad_enforce "0" + + json_add_object "wpad_enforce" + json_add_boolean "enabled" "$wpad_enforce" + json_add_boolean "http_active" "$enforce_http" + json_add_boolean "https_active" "$enforce_https" + json_close_object + + json_dump + ;; + + setWpadEnforce) + # Enable/disable WPAD enforcement (iptables redirect for non-WPAD clients) + read input + json_load "$input" + + json_get_var enabled enabled + + # Get router IP and proxy port + lan_ip=$(uci get network.lan.ipaddr 2>/dev/null | cut -d'/' -f1) + [ -z "$lan_ip" ] && lan_ip="192.168.255.1" + proxy_port=$(uci -q get cdn-cache.main.listen_port || echo "3128") + lan_iface="br-lan" + + if [ "$enabled" = "1" ] || [ "$enabled" = "true" ]; then + # Store setting in UCI + uci set ${CONFIG}.global.wpad_enforce='1' + uci commit "$CONFIG" + + # Remove existing WPAD enforce rules if any + iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 80 -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port" 2>/dev/null + iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 443 -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port" 2>/dev/null + + # Add iptables rules to redirect HTTP/HTTPS to proxy + # Skip traffic destined to the router itself + iptables -t nat -A PREROUTING -i "$lan_iface" -p tcp --dport 80 ! -d "$lan_ip" -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port" + iptables -t nat -A PREROUTING -i "$lan_iface" -p tcp --dport 443 ! -d "$lan_ip" -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port" + + logger -t network-tweaks "WPAD enforcement enabled: redirecting HTTP/HTTPS to port $proxy_port" + + json_init + json_add_boolean "success" 1 + json_add_string "message" "WPAD enforcement enabled - traffic redirected to proxy port $proxy_port" + else + # Store setting in UCI + uci set ${CONFIG}.global.wpad_enforce='0' + uci commit "$CONFIG" + + # Remove iptables rules + iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 80 -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port" 2>/dev/null + iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 443 -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port" 2>/dev/null + + logger -t network-tweaks "WPAD enforcement disabled" + + json_init + json_add_boolean "success" 1 + json_add_string "message" "WPAD enforcement disabled" + fi + + json_dump + ;; + *) json_init json_add_boolean "success" 0 diff --git a/package/secubox/luci-app-network-tweaks/root/usr/sbin/network-tweaks-sync b/package/secubox/luci-app-network-tweaks/root/usr/sbin/network-tweaks-sync index 68100291..d9cf8065 100755 --- a/package/secubox/luci-app-network-tweaks/root/usr/sbin/network-tweaks-sync +++ b/package/secubox/luci-app-network-tweaks/root/usr/sbin/network-tweaks-sync @@ -149,6 +149,28 @@ reload_services() { fi } +setup_wpad_enforce() { + local enforce + enforce=$(uci -q get ${CONFIG_TWEAKS}.global.wpad_enforce) + [ "$enforce" != "1" ] && return 0 + + local lan_ip proxy_port lan_iface + lan_ip=$(uci -q get network.lan.ipaddr | cut -d'/' -f1) + [ -z "$lan_ip" ] && lan_ip="192.168.255.1" + proxy_port=$(uci -q get cdn-cache.main.listen_port || echo "3128") + lan_iface="br-lan" + + # Remove existing rules if any + iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 80 -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port" 2>/dev/null + iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 443 -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port" 2>/dev/null + + # Add iptables rules + iptables -t nat -A PREROUTING -i "$lan_iface" -p tcp --dport 80 ! -d "$lan_ip" -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port" + iptables -t nat -A PREROUTING -i "$lan_iface" -p tcp --dport 443 ! -d "$lan_ip" -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port" + + log_msg "WPAD enforcement enabled: redirecting HTTP/HTTPS to port $proxy_port" +} + cleanup_entries() { log_msg "Cleaning up managed entries..." @@ -199,6 +221,9 @@ sync_all() { reload_services "$reload_needed" + # Setup WPAD enforcement iptables rules if enabled + setup_wpad_enforce + log_msg "Synchronization complete" } diff --git a/package/secubox/luci-app-network-tweaks/root/usr/share/rpcd/acl.d/luci-app-network-tweaks.json b/package/secubox/luci-app-network-tweaks/root/usr/share/rpcd/acl.d/luci-app-network-tweaks.json index 36335a5e..5aad709c 100644 --- a/package/secubox/luci-app-network-tweaks/root/usr/share/rpcd/acl.d/luci-app-network-tweaks.json +++ b/package/secubox/luci-app-network-tweaks/root/usr/share/rpcd/acl.d/luci-app-network-tweaks.json @@ -3,14 +3,14 @@ "description": "Grant access to Network Tweaks", "read": { "ubus": { - "luci.network-tweaks": ["getStatus", "getConfig", "getNetworkComponents", "getCumulativeImpact", "getWpadStatus", "getProxyStatus"], + "luci.network-tweaks": ["getStatus", "getConfig", "getNetworkComponents", "getCumulativeImpact", "getWpadStatus", "getProxyStatus", "getOpkgSettings", "testUrl", "getWpadEnforce"], "luci.cdn-cache": ["status", "stats"] }, "uci": ["network_tweaks", "vhosts", "cdn-cache", "dhcp"] }, "write": { "ubus": { - "luci.network-tweaks": ["syncNow", "setConfig", "setComponentEnabled", "setWpadEnabled", "setAdGuardEnabled"], + "luci.network-tweaks": ["syncNow", "setConfig", "setComponentEnabled", "setWpadEnabled", "setAdGuardEnabled", "setOpkgIpv4", "setWpadEnforce"], "luci.cdn-cache": ["set_enabled", "restart"] }, "uci": ["network_tweaks", "dhcp", "adguardhome"] diff --git a/package/secubox/secubox-cookie-tracker/Makefile b/package/secubox/secubox-cookie-tracker/Makefile new file mode 100644 index 00000000..0df9d271 --- /dev/null +++ b/package/secubox/secubox-cookie-tracker/Makefile @@ -0,0 +1,81 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=secubox-cookie-tracker +PKG_RELEASE:=1 +PKG_VERSION:=1.0.0 +PKG_ARCH:=all +PKG_MAINTAINER:=CyberMind Studio +PKG_LICENSE:=GPL-3.0 + +include $(INCLUDE_DIR)/package.mk + +define Package/secubox-cookie-tracker + SECTION:=utils + CATEGORY:=Utilities + PKGARCH:=all + SUBMENU:=SecuBox Apps + TITLE:=SecuBox Cookie Tracker - HTTP cookie classification and tracking + DEPENDS:=+sqlite3-cli +jsonfilter +endef + +define Package/secubox-cookie-tracker/description +Cookie Tracker for SecuBox InterceptoR. + +Features: +- Track and classify HTTP cookies by category +- Categories: essential, functional, analytics, advertising, tracking +- SQLite database for persistent storage +- mitmproxy addon for real-time cookie extraction +- Known tracker domain database +- CLI for cookie management and reporting +- Integration with Vortex Firewall for blocking + +Works with secubox-app-mitmproxy for transparent interception. +endef + +define Package/secubox-cookie-tracker/conffiles +/etc/config/cookie-tracker +endef + +define Build/Compile +endef + +define Package/secubox-cookie-tracker/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/config/cookie-tracker $(1)/etc/config/cookie-tracker + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./root/etc/init.d/cookie-tracker $(1)/etc/init.d/cookie-tracker + + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) ./root/usr/sbin/cookie-trackerctl $(1)/usr/sbin/cookie-trackerctl + + $(INSTALL_DIR) $(1)/usr/lib/secubox/cookie-tracker + $(INSTALL_DATA) ./root/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py $(1)/usr/lib/secubox/cookie-tracker/ + $(INSTALL_DATA) ./root/usr/lib/secubox/cookie-tracker/known-trackers.tsv $(1)/usr/lib/secubox/cookie-tracker/ + + $(INSTALL_DIR) $(1)/var/lib/cookie-tracker +endef + +define Package/secubox-cookie-tracker/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + echo "" + echo "Cookie Tracker installed." + echo "" + echo "Initialize database:" + echo " cookie-trackerctl init" + echo "" + echo "Enable mitmproxy addon (add to /etc/config/mitmproxy):" + echo " option addon_script '/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py'" + echo "" + echo "Commands:" + echo " cookie-trackerctl status # Statistics" + echo " cookie-trackerctl list # List cookies" + echo " cookie-trackerctl report # Generate report" + echo "" +} +exit 0 +endef + +$(eval $(call BuildPackage,secubox-cookie-tracker)) diff --git a/package/secubox/secubox-cookie-tracker/README.md b/package/secubox/secubox-cookie-tracker/README.md new file mode 100644 index 00000000..990b467f --- /dev/null +++ b/package/secubox/secubox-cookie-tracker/README.md @@ -0,0 +1,150 @@ +# SecuBox Cookie Tracker + +HTTP cookie classification and tracking for SecuBox InterceptoR. + +## Features + +- **Cookie Extraction** - Capture cookies from HTTP traffic via mitmproxy +- **Auto-Classification** - Categorize cookies as essential, functional, analytics, advertising, or tracking +- **SQLite Database** - Persistent storage with search and filtering +- **Known Tracker Database** - 100+ pre-configured tracker domains +- **Vortex Integration** - Feed blocked domains to Vortex Firewall +- **CLI Management** - Full command-line interface for cookie management + +## Installation + +```bash +opkg install secubox-cookie-tracker +``` + +Requires `secubox-app-mitmproxy` for traffic interception. + +## Quick Start + +```bash +# Initialize database +cookie-trackerctl init + +# View status +cookie-trackerctl status + +# List cookies +cookie-trackerctl list + +# Block a tracking domain +cookie-trackerctl block doubleclick.net +``` + +## CLI Commands + +| Command | Description | +|---------|-------------| +| `status [--json]` | Show statistics summary | +| `init [force]` | Initialize/reset database | +| `reload` | Reload tracker rules from UCI | +| `list [options]` | List cookies with filters | +| `show ` | Show cookies for domain | +| `classify ` | Manually classify cookie | +| `block ` | Block all cookies from domain | +| `unblock ` | Unblock domain | +| `report [--json]` | Generate cookie report | +| `export [file]` | Export database to CSV | +| `import ` | Import tracker rules from TSV | +| `feed-vortex` | Feed blocked domains to Vortex | +| `stats` | Detailed statistics | + +## Cookie Categories + +| Category | Description | Default Action | +|----------|-------------|----------------| +| `essential` | Required for site functionality | Allow | +| `functional` | User preferences, settings | Allow | +| `analytics` | Usage tracking for site improvement | Alert | +| `advertising` | Ad targeting and retargeting | Block | +| `tracking` | Cross-site tracking, fingerprinting | Block | +| `unknown` | Not yet classified | Allow | + +## mitmproxy Integration + +Add the addon to your mitmproxy configuration: + +```bash +# /etc/config/mitmproxy +config filtering 'filtering' + option addon_script '/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py' +``` + +Or load alongside the main analytics addon: + +```bash +mitmdump -s /usr/lib/secubox/cookie-tracker/mitmproxy-addon.py \ + -s /srv/mitmproxy/addons/secubox_analytics.py +``` + +## UCI Configuration + +``` +# /etc/config/cookie-tracker +config cookie_tracker 'main' + option enabled '1' + option auto_classify '1' + option block_tracking '0' + option block_advertising '0' + +config tracker_rule 'custom' + option pattern '_my_tracker' + option category 'tracking' +``` + +## Database Schema + +```sql +CREATE TABLE cookies ( + id INTEGER PRIMARY KEY, + domain TEXT NOT NULL, + name TEXT NOT NULL, + category TEXT DEFAULT 'unknown', + first_seen INTEGER, + last_seen INTEGER, + count INTEGER DEFAULT 1, + client_mac TEXT, + blocked INTEGER DEFAULT 0, + UNIQUE(domain, name) +); + +CREATE TABLE tracker_domains ( + domain TEXT PRIMARY KEY, + category TEXT, + source TEXT +); +``` + +## Examples + +```bash +# List all tracking cookies +cookie-trackerctl list --category tracking + +# List cookies from a specific domain +cookie-trackerctl list --domain google.com + +# Generate JSON report for dashboard +cookie-trackerctl report --json + +# Export all data +cookie-trackerctl export /tmp/cookies.csv + +# Block and sync to Vortex +cookie-trackerctl block ads.example.com +cookie-trackerctl feed-vortex +``` + +## Dependencies + +- secubox-app-mitmproxy (for traffic interception) +- sqlite3-cli +- jsonfilter + +## License + +GPL-3.0 diff --git a/package/secubox/secubox-cookie-tracker/files/config/cookie-tracker b/package/secubox/secubox-cookie-tracker/files/config/cookie-tracker new file mode 100644 index 00000000..e95137c6 --- /dev/null +++ b/package/secubox/secubox-cookie-tracker/files/config/cookie-tracker @@ -0,0 +1,84 @@ +config cookie_tracker 'main' + option enabled '1' + # Database path + option db_path '/var/lib/cookie-tracker/cookies.db' + # Auto-classify cookies using known tracker database + option auto_classify '1' + # Block tracking cookies (requires Vortex integration) + option block_tracking '0' + # Block advertising cookies + option block_advertising '0' + # Log level: debug, info, warn, error + option log_level 'info' + +# Cookie categories and their defaults +config category 'essential' + option description 'Required for basic site functionality' + option block '0' + option alert '0' + +config category 'functional' + option description 'Enhance user experience (preferences, languages)' + option block '0' + option alert '0' + +config category 'analytics' + option description 'Track user behavior for site improvement' + option block '0' + option alert '1' + +config category 'advertising' + option description 'Used for targeted advertising' + option block '1' + option alert '1' + +config category 'tracking' + option description 'Cross-site tracking and fingerprinting' + option block '1' + option alert '1' + +# Known tracker patterns (extend with custom rules) +config tracker_rule 'google_analytics' + option pattern '_ga|_gid|_gat|__utm' + option category 'analytics' + option source 'builtin' + +config tracker_rule 'facebook' + option pattern '_fbp|_fbc|fr|datr' + option category 'advertising' + option source 'builtin' + +config tracker_rule 'doubleclick' + option domain_pattern 'doubleclick\.net|googlesyndication\.com' + option category 'advertising' + option source 'builtin' + +config tracker_rule 'segment' + option pattern 'ajs_user_id|ajs_anonymous_id' + option category 'analytics' + option source 'builtin' + +config tracker_rule 'mixpanel' + option pattern 'mp_.*_mixpanel' + option category 'analytics' + option source 'builtin' + +config tracker_rule 'hubspot' + option pattern '__hs.*|hubspotutk' + option category 'analytics' + option source 'builtin' + +# Whitelist - never block these cookies +config whitelist 'trusted' + list domain '' + list cookie_name '' + +# Integration settings +config integration 'mitmproxy' + option enabled '1' + option addon_path '/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py' + +config integration 'vortex' + option enabled '0' + # Feed blocked tracker domains to Vortex Firewall + option feed_blocklist '0' diff --git a/package/secubox/secubox-cookie-tracker/root/etc/init.d/cookie-tracker b/package/secubox/secubox-cookie-tracker/root/etc/init.d/cookie-tracker new file mode 100644 index 00000000..af22aee3 --- /dev/null +++ b/package/secubox/secubox-cookie-tracker/root/etc/init.d/cookie-tracker @@ -0,0 +1,33 @@ +#!/bin/sh /etc/rc.common + +START=95 +STOP=10 +USE_PROCD=1 + +DB_PATH="/var/lib/cookie-tracker/cookies.db" + +start_service() { + local enabled + config_load cookie-tracker + config_get enabled main enabled '0' + + [ "$enabled" = "1" ] || return 0 + + # Initialize database if needed + /usr/sbin/cookie-trackerctl init 2>/dev/null + + logger -t cookie-tracker "Cookie Tracker service started" +} + +stop_service() { + logger -t cookie-tracker "Cookie Tracker service stopped" +} + +reload_service() { + # Reload tracker rules from UCI + /usr/sbin/cookie-trackerctl reload +} + +service_triggers() { + procd_add_reload_trigger "cookie-tracker" +} diff --git a/package/secubox/secubox-cookie-tracker/root/usr/lib/secubox/cookie-tracker/known-trackers.tsv b/package/secubox/secubox-cookie-tracker/root/usr/lib/secubox/cookie-tracker/known-trackers.tsv new file mode 100644 index 00000000..817e180d --- /dev/null +++ b/package/secubox/secubox-cookie-tracker/root/usr/lib/secubox/cookie-tracker/known-trackers.tsv @@ -0,0 +1,140 @@ +# SecuBox Cookie Tracker - Known Tracker Domains +# Format: domaincategorysource +# Categories: analytics, advertising, tracking +# +# Google Analytics +google-analytics.com analytics builtin +analytics.google.com analytics builtin +googletagmanager.com analytics builtin +# +# Google Ads / DoubleClick +doubleclick.net advertising builtin +googlesyndication.com advertising builtin +googleadservices.com advertising builtin +pagead2.googlesyndication.com advertising builtin +# +# Facebook +pixel.facebook.com tracking builtin +connect.facebook.net tracking builtin +facebook.com advertising builtin +# +# Microsoft / Bing +bat.bing.com advertising builtin +clarity.ms analytics builtin +# +# LinkedIn +ads.linkedin.com advertising builtin +px.ads.linkedin.com tracking builtin +snap.licdn.com tracking builtin +# +# Twitter / X +ads.twitter.com advertising builtin +analytics.twitter.com analytics builtin +t.co tracking builtin +# +# Pinterest +ct.pinterest.com tracking builtin +ads.pinterest.com advertising builtin +# +# TikTok +analytics.tiktok.com analytics builtin +ads.tiktok.com advertising builtin +# +# Hotjar +hotjar.com analytics builtin +static.hotjar.com analytics builtin +# +# Segment +segment.io analytics builtin +segment.com analytics builtin +cdn.segment.com analytics builtin +# +# Mixpanel +mixpanel.com analytics builtin +api.mixpanel.com analytics builtin +# +# HubSpot +hubspot.com analytics builtin +hs-analytics.net analytics builtin +hs-scripts.com analytics builtin +# +# Amplitude +amplitude.com analytics builtin +api.amplitude.com analytics builtin +# +# Heap +heap.io analytics builtin +heapanalytics.com analytics builtin +# +# FullStory +fullstory.com analytics builtin +rs.fullstory.com analytics builtin +# +# Criteo +criteo.com advertising builtin +criteo.net advertising builtin +# +# Taboola +taboola.com advertising builtin +trc.taboola.com advertising builtin +# +# Outbrain +outbrain.com advertising builtin +widgets.outbrain.com advertising builtin +# +# Ad Networks +adnxs.com advertising builtin +adsrvr.org advertising builtin +rubiconproject.com advertising builtin +pubmatic.com advertising builtin +openx.net advertising builtin +casalemedia.com advertising builtin +quantserve.com advertising builtin +advertising.com advertising builtin +# +# Matomo / Piwik +matomo.cloud analytics builtin +# +# Adobe Analytics +omtrdc.net analytics builtin +demdex.net tracking builtin +# +# Optimizely +optimizely.com analytics builtin +# +# VWO +visualwebsiteoptimizer.com analytics builtin +# +# Inspectlet +inspectlet.com analytics builtin +# +# Reddit +redditstatic.com tracking builtin +# +# Snap +sc-static.net tracking builtin +# +# Yahoo / Verizon Media +ads.yahoo.com advertising builtin +# +# Amazon +amazon-adsystem.com advertising builtin +# +# Quantcast +quantcast.com tracking builtin +# +# Scorecard Research +scorecardresearch.com tracking builtin +# +# Comscore +comscore.com analytics builtin +# +# New Relic +newrelic.com analytics builtin +nr-data.net analytics builtin +# +# Datadog +datadoghq.com analytics builtin +# +# Sentry +sentry.io analytics builtin diff --git a/package/secubox/secubox-cookie-tracker/root/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py b/package/secubox/secubox-cookie-tracker/root/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py new file mode 100644 index 00000000..ee3ddf80 --- /dev/null +++ b/package/secubox/secubox-cookie-tracker/root/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +""" +SecuBox Cookie Tracker Addon for mitmproxy +Extracts, classifies, and tracks HTTP cookies passing through the proxy. +Integrates with the cookie-tracker SQLite database. +""" + +import sqlite3 +import os +import re +import time +from datetime import datetime +from mitmproxy import http, ctx + +# Database path +DB_PATH = "/var/lib/cookie-tracker/cookies.db" + +# Known tracking cookie patterns +TRACKING_PATTERNS = { + 'analytics': [ + r'^_ga$', r'^_gid$', r'^_gat', r'^__utm', # Google Analytics + r'^_hjid', r'^_hjSession', # Hotjar + r'^ajs_user_id', r'^ajs_anonymous_id', # Segment + r'^mp_.*_mixpanel', # Mixpanel + r'^__hssc', r'^__hssrc', r'^__hstc', r'^hubspotutk', # HubSpot + r'^_pk_id', r'^_pk_ses', # Matomo/Piwik + r'^amplitude_id', # Amplitude + r'^_clck', r'^_clsk', # Microsoft Clarity + r'^__insp_', # Inspectlet + r'^_vwo_', # VWO + r'^optimizelyEndUserId', # Optimizely + ], + 'advertising': [ + r'^_fbp$', r'^_fbc$', r'^fr$', r'^datr$', # Facebook + r'^IDE$', r'^NID$', r'^ANID$', # Google Ads/DoubleClick + r'^_gcl_', # Google Conversion Linker + r'^_uetsid', r'^_uetvid', # Microsoft Ads/Bing + r'^_pin_unauth', # Pinterest + r'^__pdst', # Pardot + r'^li_sugr', r'^bcookie', r'^bscookie', # LinkedIn + r'^_tt_enable_cookie', r'^_ttp', # TikTok + r'^cto_bundle', # Criteo + r'^taboola_', # Taboola + r'^outbrain_', # Outbrain + ], + 'tracking': [ + r'^__cfduid', # Cloudflare (legacy) + r'^_dc_gtm_', # Google Tag Manager + r'^_gac_', # Google Ads conversion + r'^uuid$', r'^visitor_id', # Generic tracking + r'^_parsely', # Parse.ly + r'^__gads', # Google Ads + r'^_rdt_uuid', # Reddit + r'^_scid', # Snap + r'^_twclid', # Twitter + r'^_derived_epik', # Pinterest + ], + 'functional': [ + r'^lang$', r'^locale$', r'^language$', + r'^timezone$', r'^tz$', + r'^theme$', r'^dark_mode$', + r'^remember_token$', r'^user_pref', + r'^cookie_consent', r'^gdpr', + ], + 'essential': [ + r'^session', r'^sess_', r'^PHPSESSID$', r'^JSESSIONID$', + r'^csrf', r'^_csrf', r'^XSRF-TOKEN', + r'^auth', r'^token$', r'^jwt$', + r'^__Secure-', r'^__Host-', + ], +} + +# Known tracker domains +TRACKER_DOMAINS = { + 'analytics': [ + 'google-analytics.com', 'analytics.google.com', + 'hotjar.com', 'segment.io', 'segment.com', + 'mixpanel.com', 'hubspot.com', 'hs-analytics.net', + 'matomo.cloud', 'amplitude.com', 'clarity.ms', + 'inspectlet.com', 'visualwebsiteoptimizer.com', + 'optimizely.com', 'fullstory.com', 'heap.io', + ], + 'advertising': [ + 'doubleclick.net', 'googlesyndication.com', 'googleadservices.com', + 'facebook.com', 'facebook.net', 'fbcdn.net', + 'ads.linkedin.com', 'ads.twitter.com', 'ads.pinterest.com', + 'criteo.com', 'criteo.net', 'taboola.com', 'outbrain.com', + 'adsrvr.org', 'adnxs.com', 'rubiconproject.com', + 'pubmatic.com', 'openx.net', 'casalemedia.com', + 'advertising.com', 'quantserve.com', + ], + 'tracking': [ + 'pixel.facebook.com', 'bat.bing.com', 'px.ads.linkedin.com', + 't.co', 'analytics.tiktok.com', 'sc-static.net', + 'ct.pinterest.com', 'snap.licdn.com', + ], +} + + +class CookieTracker: + def __init__(self): + self.db_initialized = False + self._init_db() + ctx.log.info("Cookie Tracker addon loaded") + + def _init_db(self): + """Initialize database connection and create tables if needed.""" + try: + # Ensure directory exists + os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) + + conn = sqlite3.connect(DB_PATH) + conn.execute(""" + CREATE TABLE IF NOT EXISTS cookies ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + domain TEXT NOT NULL, + name TEXT NOT NULL, + category TEXT DEFAULT 'unknown', + first_seen INTEGER DEFAULT (strftime('%s', 'now')), + last_seen INTEGER DEFAULT (strftime('%s', 'now')), + count INTEGER DEFAULT 1, + client_mac TEXT, + blocked INTEGER DEFAULT 0, + UNIQUE(domain, name) + ) + """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS tracker_domains ( + domain TEXT PRIMARY KEY, + category TEXT NOT NULL, + source TEXT DEFAULT 'manual', + added INTEGER DEFAULT (strftime('%s', 'now')) + ) + """) + conn.execute("CREATE INDEX IF NOT EXISTS idx_cookies_domain ON cookies(domain)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_cookies_category ON cookies(category)") + conn.commit() + conn.close() + self.db_initialized = True + ctx.log.info(f"Cookie Tracker database ready: {DB_PATH}") + except Exception as e: + ctx.log.error(f"Failed to initialize database: {e}") + + def _classify_cookie(self, domain: str, name: str) -> str: + """Classify a cookie based on name patterns and domain.""" + name_lower = name.lower() + domain_lower = domain.lower() + + # Check name patterns first (most specific) + for category, patterns in TRACKING_PATTERNS.items(): + for pattern in patterns: + if re.match(pattern, name, re.IGNORECASE): + return category + + # Check domain against known trackers + for category, domains in TRACKER_DOMAINS.items(): + for tracker_domain in domains: + if tracker_domain in domain_lower: + return category + + # Check database for custom tracker domains + try: + conn = sqlite3.connect(DB_PATH) + cursor = conn.execute( + "SELECT category FROM tracker_domains WHERE ? LIKE '%' || domain || '%'", + (domain_lower,) + ) + row = cursor.fetchone() + conn.close() + if row: + return row[0] + except Exception: + pass + + return 'unknown' + + def _parse_set_cookie(self, header: str) -> dict: + """Parse Set-Cookie header into components.""" + parts = header.split(';') + if not parts: + return None + + # First part is name=value + name_value = parts[0].strip() + if '=' not in name_value: + return None + + name, value = name_value.split('=', 1) + name = name.strip() + value = value.strip() + + cookie = { + 'name': name, + 'value': value[:100], # Truncate value + 'attributes': {} + } + + # Parse attributes + for part in parts[1:]: + part = part.strip() + if '=' in part: + key, val = part.split('=', 1) + cookie['attributes'][key.lower().strip()] = val.strip() + else: + cookie['attributes'][part.lower()] = True + + return cookie + + def _record_cookie(self, domain: str, cookie: dict, client_ip: str = None): + """Record a cookie in the database.""" + if not self.db_initialized: + return + + name = cookie['name'] + category = self._classify_cookie(domain, name) + + try: + conn = sqlite3.connect(DB_PATH) + cursor = conn.execute(""" + INSERT INTO cookies (domain, name, category, client_mac) + VALUES (?, ?, ?, ?) + ON CONFLICT(domain, name) DO UPDATE SET + last_seen = strftime('%s', 'now'), + count = count + 1, + category = CASE + WHEN excluded.category != 'unknown' THEN excluded.category + ELSE cookies.category + END + """, (domain, name, category, client_ip)) + conn.commit() + conn.close() + + # Log tracking cookies + if category in ('tracking', 'advertising'): + ctx.log.warn(f"TRACKING COOKIE: {domain} - {name} [{category}]") + else: + ctx.log.debug(f"Cookie recorded: {domain}/{name} [{category}]") + + except Exception as e: + ctx.log.error(f"Failed to record cookie: {e}") + + def response(self, flow: http.HTTPFlow): + """Process response and extract Set-Cookie headers.""" + if not flow.response: + return + + # Get domain from request + domain = flow.request.host + + # Get client IP for tracking + client_ip = None + if flow.client_conn and flow.client_conn.peername: + client_ip = flow.client_conn.peername[0] + + # Check for Set-Cookie headers + cookies = flow.response.headers.get_all('set-cookie') + if not cookies: + return + + for cookie_header in cookies: + cookie = self._parse_set_cookie(cookie_header) + if cookie: + self._record_cookie(domain, cookie, client_ip) + + def request(self, flow: http.HTTPFlow): + """Process request and extract Cookie header.""" + if not flow.request: + return + + domain = flow.request.host + client_ip = None + if flow.client_conn and flow.client_conn.peername: + client_ip = flow.client_conn.peername[0] + + # Get Cookie header + cookie_header = flow.request.headers.get('cookie') + if not cookie_header: + return + + # Parse cookies from request + for cookie_str in cookie_header.split(';'): + cookie_str = cookie_str.strip() + if '=' in cookie_str: + name, value = cookie_str.split('=', 1) + cookie = { + 'name': name.strip(), + 'value': value.strip()[:100], + 'attributes': {} + } + self._record_cookie(domain, cookie, client_ip) + + +addons = [CookieTracker()] diff --git a/package/secubox/secubox-cookie-tracker/root/usr/sbin/cookie-trackerctl b/package/secubox/secubox-cookie-tracker/root/usr/sbin/cookie-trackerctl new file mode 100644 index 00000000..92ec3572 --- /dev/null +++ b/package/secubox/secubox-cookie-tracker/root/usr/sbin/cookie-trackerctl @@ -0,0 +1,587 @@ +#!/bin/sh +# +# cookie-trackerctl - CLI controller for SecuBox Cookie Tracker +# +# Usage: cookie-trackerctl [options] +# + +. /lib/functions.sh + +DB_PATH="/var/lib/cookie-tracker/cookies.db" +TRACKER_TSV="/usr/lib/secubox/cookie-tracker/known-trackers.tsv" +VORTEX_DB="/var/lib/vortex-firewall/blocklist.db" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +usage() { + cat < [options] + +Commands: + status Show statistics summary + init Initialize/reset database + reload Reload tracker rules from UCI + list [options] List cookies + --domain Filter by domain + --category Filter by category + --limit Limit results (default: 100) + show Show cookies for domain + classify + Manually classify a cookie + block Block all cookies from domain + unblock Unblock domain + report [--json] Generate cookie report + export [file] Export database to CSV + import Import tracker rules from TSV + feed-vortex Feed blocked domains to Vortex Firewall + stats Detailed statistics + +Categories: + essential Required for site functionality + functional User preferences/settings + analytics Usage tracking + advertising Ad targeting + tracking Cross-site tracking + +Examples: + cookie-trackerctl list --category tracking + cookie-trackerctl classify google.com _ga analytics + cookie-trackerctl block doubleclick.net + cookie-trackerctl report --json + +EOF + exit 1 +} + +log_info() { + logger -t cookie-tracker -p info "$1" +} + +log_warn() { + logger -t cookie-tracker -p warn "$1" +} + +log_error() { + logger -t cookie-tracker -p err "$1" +} + +# Initialize database +init_db() { + local force="$1" + + mkdir -p "$(dirname "$DB_PATH")" + + if [ -f "$DB_PATH" ] && [ "$force" != "force" ]; then + echo "Database exists at $DB_PATH" + echo "Use 'init force' to reset" + return 0 + fi + + rm -f "$DB_PATH" + + sqlite3 "$DB_PATH" <<-EOF + CREATE TABLE IF NOT EXISTS cookies ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + domain TEXT NOT NULL, + name TEXT NOT NULL, + category TEXT DEFAULT 'unknown', + first_seen INTEGER DEFAULT (strftime('%s', 'now')), + last_seen INTEGER DEFAULT (strftime('%s', 'now')), + count INTEGER DEFAULT 1, + client_mac TEXT, + blocked INTEGER DEFAULT 0, + UNIQUE(domain, name) + ); + + CREATE TABLE IF NOT EXISTS tracker_domains ( + domain TEXT PRIMARY KEY, + category TEXT NOT NULL, + source TEXT DEFAULT 'manual', + added INTEGER DEFAULT (strftime('%s', 'now')) + ); + + CREATE TABLE IF NOT EXISTS blocked_domains ( + domain TEXT PRIMARY KEY, + reason TEXT, + blocked_at INTEGER DEFAULT (strftime('%s', 'now')) + ); + + CREATE INDEX IF NOT EXISTS idx_cookies_domain ON cookies(domain); + CREATE INDEX IF NOT EXISTS idx_cookies_category ON cookies(category); + CREATE INDEX IF NOT EXISTS idx_cookies_last_seen ON cookies(last_seen); + EOF + + echo "Database initialized at $DB_PATH" + + # Import known trackers + if [ -f "$TRACKER_TSV" ]; then + import_trackers "$TRACKER_TSV" + fi + + log_info "Database initialized" +} + +# Import tracker rules from TSV +import_trackers() { + local file="${1:-$TRACKER_TSV}" + + if [ ! -f "$file" ]; then + echo "File not found: $file" + return 1 + fi + + local count=0 + while IFS=$'\t' read -r domain category source; do + # Skip comments and empty lines + case "$domain" in + "#"*|"") continue ;; + esac + + sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO tracker_domains (domain, category, source) VALUES ('$domain', '$category', '$source');" + count=$((count + 1)) + done < "$file" + + echo "Imported $count tracker domains" + log_info "Imported $count tracker domains from $file" +} + +# Reload UCI rules into database +reload_rules() { + config_load cookie-tracker + + local count=0 + + reload_tracker_rule() { + local section="$1" + local pattern domain_pattern category source + + config_get pattern "$section" pattern + config_get domain_pattern "$section" domain_pattern + config_get category "$section" category "tracking" + config_get source "$section" source "uci" + + # For domain patterns, extract and add domains + if [ -n "$domain_pattern" ]; then + # Convert regex pattern to domain list (simplified) + local domains=$(echo "$domain_pattern" | sed 's/\\\././' | tr '|' '\n') + for d in $domains; do + sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO tracker_domains (domain, category, source) VALUES ('$d', '$category', '$source');" + count=$((count + 1)) + done + fi + } + + config_foreach reload_tracker_rule tracker_rule + + echo "Reloaded $count tracker rules from UCI" + log_info "Reloaded $count tracker rules" +} + +# Show status/statistics +show_status() { + local json="$1" + + if [ ! -f "$DB_PATH" ]; then + echo "Database not initialized. Run: cookie-trackerctl init" + return 1 + fi + + local total=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies;") + local domains=$(sqlite3 "$DB_PATH" "SELECT COUNT(DISTINCT domain) FROM cookies;") + local blocked=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE blocked=1;") + local trackers=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM tracker_domains;") + local blocked_domains=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM blocked_domains;") + + # Category breakdown + local essential=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='essential';") + local functional=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='functional';") + local analytics=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='analytics';") + local advertising=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='advertising';") + local tracking=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='tracking';") + local unknown=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='unknown';") + + # Last 24h activity + local today=$(date +%s) + local yesterday=$((today - 86400)) + local new_today=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE first_seen > $yesterday;") + local seen_today=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen > $yesterday;") + + if [ "$json" = "--json" ]; then + cat </dev/null || \ + sqlite3 "$DB_PATH" "SELECT domain, name, category, count, blocked, datetime(last_seen, 'unixepoch') as last_seen FROM cookies $where ORDER BY last_seen DESC LIMIT $limit;" | \ + awk -F'|' 'BEGIN{first=1} { + if(!first) print "," + first=0 + printf "{\"domain\":\"%s\",\"name\":\"%s\",\"category\":\"%s\",\"count\":%s,\"blocked\":%s,\"last_seen\":\"%s\"}", $1, $2, $3, $4, $5, $6 + }' + echo "]" + else + printf "%-30s %-25s %-12s %6s %s\n" "DOMAIN" "COOKIE" "CATEGORY" "COUNT" "BLOCKED" + echo "--------------------------------------------------------------------------------" + sqlite3 "$DB_PATH" "SELECT domain, name, category, count, CASE WHEN blocked=1 THEN 'YES' ELSE '' END FROM cookies $where ORDER BY last_seen DESC LIMIT $limit;" | \ + while IFS='|' read -r d n c cnt b; do + printf "%-30s %-25s %-12s %6s %s\n" "${d:0:30}" "${n:0:25}" "$c" "$cnt" "$b" + done + fi +} + +# Show cookies for specific domain +show_domain() { + local domain="$1" + + if [ -z "$domain" ]; then + echo "Usage: cookie-trackerctl show " + return 1 + fi + + echo "Cookies for domain: $domain" + echo "========================================" + + sqlite3 "$DB_PATH" "SELECT name, category, count, blocked, datetime(first_seen, 'unixepoch'), datetime(last_seen, 'unixepoch') FROM cookies WHERE domain LIKE '%$domain%' ORDER BY count DESC;" | \ + while IFS='|' read -r name cat cnt blocked first last; do + printf "\n Name: %s\n" "$name" + printf " Category: %s\n" "$cat" + printf " Count: %s\n" "$cnt" + printf " Blocked: %s\n" "$([ "$blocked" = "1" ] && echo "Yes" || echo "No")" + printf " First seen: %s\n" "$first" + printf " Last seen: %s\n" "$last" + done +} + +# Manually classify a cookie +classify_cookie() { + local domain="$1" + local name="$2" + local category="$3" + + if [ -z "$domain" ] || [ -z "$name" ] || [ -z "$category" ]; then + echo "Usage: cookie-trackerctl classify " + return 1 + fi + + # Validate category + case "$category" in + essential|functional|analytics|advertising|tracking|unknown) ;; + *) echo "Invalid category: $category"; return 1 ;; + esac + + sqlite3 "$DB_PATH" "UPDATE cookies SET category='$category' WHERE domain='$domain' AND name='$name';" + + if [ $? -eq 0 ]; then + echo "Cookie classified: $domain/$name -> $category" + log_info "Cookie classified: $domain/$name -> $category" + else + echo "Failed to classify cookie" + return 1 + fi +} + +# Block domain +block_domain() { + local domain="$1" + local reason="${2:-manual}" + + if [ -z "$domain" ]; then + echo "Usage: cookie-trackerctl block " + return 1 + fi + + sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO blocked_domains (domain, reason) VALUES ('$domain', '$reason');" + sqlite3 "$DB_PATH" "UPDATE cookies SET blocked=1 WHERE domain LIKE '%$domain%';" + + echo "Domain blocked: $domain" + log_info "Domain blocked: $domain" +} + +# Unblock domain +unblock_domain() { + local domain="$1" + + if [ -z "$domain" ]; then + echo "Usage: cookie-trackerctl unblock " + return 1 + fi + + sqlite3 "$DB_PATH" "DELETE FROM blocked_domains WHERE domain='$domain';" + sqlite3 "$DB_PATH" "UPDATE cookies SET blocked=0 WHERE domain LIKE '%$domain%';" + + echo "Domain unblocked: $domain" + log_info "Domain unblocked: $domain" +} + +# Generate report +generate_report() { + local json="$1" + + # Top 10 domains by cookie count + local top_domains=$(sqlite3 "$DB_PATH" "SELECT domain, COUNT(*) as cnt FROM cookies GROUP BY domain ORDER BY cnt DESC LIMIT 10;") + + # Top tracking domains + local top_trackers=$(sqlite3 "$DB_PATH" "SELECT domain, COUNT(*) as cnt FROM cookies WHERE category IN ('tracking', 'advertising') GROUP BY domain ORDER BY cnt DESC LIMIT 10;") + + # Recent cookies + local recent=$(sqlite3 "$DB_PATH" "SELECT domain, name, category FROM cookies ORDER BY last_seen DESC LIMIT 10;") + + if [ "$json" = "--json" ]; then + # Build JSON report + echo "{" + echo " \"generated\": \"$(date -Iseconds)\"," + + # Top domains + echo " \"top_domains\": [" + echo "$top_domains" | awk -F'|' 'BEGIN{first=1} { + if(!first) print "," + first=0 + printf " {\"domain\":\"%s\",\"count\":%s}", $1, $2 + }' + echo "" + echo " ]," + + # Top trackers + echo " \"top_trackers\": [" + echo "$top_trackers" | awk -F'|' 'BEGIN{first=1} { + if(!first) print "," + first=0 + printf " {\"domain\":\"%s\",\"count\":%s}", $1, $2 + }' + echo "" + echo " ]," + + # Recent + echo " \"recent\": [" + echo "$recent" | awk -F'|' 'BEGIN{first=1} { + if(!first) print "," + first=0 + printf " {\"domain\":\"%s\",\"name\":\"%s\",\"category\":\"%s\"}", $1, $2, $3 + }' + echo "" + echo " ]" + echo "}" + else + echo "============================================" + echo " Cookie Tracker Report" + echo " Generated: $(date)" + echo "============================================" + echo "" + echo "--- Top 10 Domains by Cookie Count ---" + echo "$top_domains" | while IFS='|' read -r d c; do + printf " %-40s %s cookies\n" "$d" "$c" + done + echo "" + echo "--- Top Tracking/Advertising Domains ---" + echo "$top_trackers" | while IFS='|' read -r d c; do + printf " ${RED}%-40s${NC} %s cookies\n" "$d" "$c" + done + echo "" + echo "--- Recently Seen Cookies ---" + echo "$recent" | while IFS='|' read -r d n c; do + printf " %-30s %-20s [%s]\n" "$d" "$n" "$c" + done + echo "" + fi +} + +# Export to CSV +export_csv() { + local file="${1:-/tmp/cookies-export.csv}" + + echo "domain,name,category,count,blocked,first_seen,last_seen" > "$file" + sqlite3 -csv "$DB_PATH" "SELECT domain, name, category, count, blocked, datetime(first_seen, 'unixepoch'), datetime(last_seen, 'unixepoch') FROM cookies ORDER BY domain, name;" >> "$file" + + echo "Exported to $file" +} + +# Feed blocked domains to Vortex Firewall +feed_vortex() { + if [ ! -f "$VORTEX_DB" ]; then + echo "Vortex Firewall database not found: $VORTEX_DB" + echo "Make sure secubox-vortex-firewall is installed" + return 1 + fi + + local count=0 + + # Get blocked domains + sqlite3 "$DB_PATH" "SELECT domain FROM blocked_domains;" | while read -r domain; do + sqlite3 "$VORTEX_DB" "INSERT OR IGNORE INTO domains (domain, category, source) VALUES ('$domain', 'cookie_tracker', 'cookie-tracker');" + count=$((count + 1)) + done + + # Get tracking/advertising domains + sqlite3 "$DB_PATH" "SELECT DISTINCT domain FROM cookies WHERE category IN ('tracking', 'advertising') AND blocked=1;" | while read -r domain; do + sqlite3 "$VORTEX_DB" "INSERT OR IGNORE INTO domains (domain, category, source) VALUES ('$domain', 'cookie_tracker', 'cookie-tracker');" + count=$((count + 1)) + done + + echo "Fed $count domains to Vortex Firewall" + log_info "Fed blocked domains to Vortex Firewall" + + # Reload Vortex if available + [ -x /etc/init.d/vortex-firewall ] && /etc/init.d/vortex-firewall reload +} + +# Detailed statistics +detailed_stats() { + echo "============================================" + echo " Cookie Tracker Detailed Statistics" + echo "============================================" + echo "" + + echo "--- Cookies by Category ---" + sqlite3 "$DB_PATH" "SELECT category, COUNT(*) as cnt, SUM(count) as total FROM cookies GROUP BY category ORDER BY cnt DESC;" | \ + while IFS='|' read -r cat cnt total; do + printf " %-15s %5s cookies, %6s total hits\n" "$cat" "$cnt" "$total" + done + + echo "" + echo "--- Top 20 Most Frequent Cookies ---" + sqlite3 "$DB_PATH" "SELECT domain, name, count, category FROM cookies ORDER BY count DESC LIMIT 20;" | \ + while IFS='|' read -r d n c cat; do + printf " %-30s %-20s %6s [%s]\n" "${d:0:30}" "${n:0:20}" "$c" "$cat" + done + + echo "" + echo "--- Cookie Age Distribution ---" + local now=$(date +%s) + local hour=$((now - 3600)) + local day=$((now - 86400)) + local week=$((now - 604800)) + + local last_hour=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen > $hour;") + local last_day=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen > $day;") + local last_week=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen > $week;") + local older=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen <= $week;") + + printf " Last hour: %5s\n" "$last_hour" + printf " Last day: %5s\n" "$last_day" + printf " Last week: %5s\n" "$last_week" + printf " Older: %5s\n" "$older" +} + +# Main command dispatcher +case "${1:-status}" in + status) + show_status "$2" + ;; + init) + init_db "$2" + ;; + reload) + reload_rules + ;; + list) + shift + list_cookies "$@" + ;; + show) + show_domain "$2" + ;; + classify) + classify_cookie "$2" "$3" "$4" + ;; + block) + block_domain "$2" "$3" + ;; + unblock) + unblock_domain "$2" + ;; + report) + generate_report "$2" + ;; + export) + export_csv "$2" + ;; + import) + import_trackers "$2" + ;; + feed-vortex) + feed_vortex + ;; + stats) + detailed_stats + ;; + -h|--help|help) + usage + ;; + *) + echo "Unknown command: $1" + usage + ;; +esac