feat(interceptor): Add InterceptoR transparent traffic interception
The Gandalf Proxy - unified traffic interception with 5 pillars: New packages: - secubox-cookie-tracker: HTTP cookie classification with mitmproxy addon - SQLite database for cookie tracking - 100+ known tracker domains (Google Analytics, Facebook, etc.) - CLI: cookie-trackerctl status/list/block/report - luci-app-interceptor: Unified dashboard aggregating all pillars - Health score (0-100%) based on active pillars - Status cards: WPAD, mitmproxy, CDN Cache, Cookie Tracker, API Failover Enhanced modules: - luci-app-network-tweaks: WPAD enforcement via iptables redirect - setWpadEnforce/getWpadEnforce RPCD methods - Catches clients ignoring WPAD auto-discovery - luci-app-cdn-cache: API failover and offline mode - stale-if-error patterns for /api/ and .json endpoints - WAN hotplug script (99-cdn-offline) toggles offline mode - collapsed_forwarding for duplicate request handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8ef0c70d0f
commit
8055bca368
@ -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
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
17
package/secubox/luci-app-interceptor/Makefile
Normal file
17
package/secubox/luci-app-interceptor/Makefile
Normal file
@ -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 <contact@cybermind.fr>
|
||||
|
||||
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
|
||||
84
package/secubox/luci-app-interceptor/README.md
Normal file
84
package/secubox/luci-app-interceptor/README.md
Normal file
@ -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
|
||||
@ -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
|
||||
});
|
||||
@ -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
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
|
||||
@ -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"]
|
||||
|
||||
81
package/secubox/secubox-cookie-tracker/Makefile
Normal file
81
package/secubox/secubox-cookie-tracker/Makefile
Normal file
@ -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 <contact@cybermind.fr>
|
||||
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))
|
||||
150
package/secubox/secubox-cookie-tracker/README.md
Normal file
150
package/secubox/secubox-cookie-tracker/README.md
Normal file
@ -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 <domain>` | Show cookies for domain |
|
||||
| `classify <domain> <name> <cat>` | Manually classify cookie |
|
||||
| `block <domain>` | Block all cookies from domain |
|
||||
| `unblock <domain>` | Unblock domain |
|
||||
| `report [--json]` | Generate cookie report |
|
||||
| `export [file]` | Export database to CSV |
|
||||
| `import <file>` | 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
|
||||
@ -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'
|
||||
@ -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"
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
# SecuBox Cookie Tracker - Known Tracker Domains
|
||||
# Format: domain<TAB>category<TAB>source
|
||||
# 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
|
||||
|
Can't render this file because it has a wrong number of fields in line 6.
|
@ -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()]
|
||||
@ -0,0 +1,587 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# cookie-trackerctl - CLI controller for SecuBox Cookie Tracker
|
||||
#
|
||||
# Usage: cookie-trackerctl <command> [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 <<EOF
|
||||
SecuBox Cookie Tracker - HTTP cookie classification and tracking
|
||||
|
||||
Usage: cookie-trackerctl <command> [options]
|
||||
|
||||
Commands:
|
||||
status Show statistics summary
|
||||
init Initialize/reset database
|
||||
reload Reload tracker rules from UCI
|
||||
list [options] List cookies
|
||||
--domain <d> Filter by domain
|
||||
--category <c> Filter by category
|
||||
--limit <n> Limit results (default: 100)
|
||||
show <domain> Show cookies for domain
|
||||
classify <domain> <name> <category>
|
||||
Manually classify a cookie
|
||||
block <domain> Block all cookies from domain
|
||||
unblock <domain> Unblock domain
|
||||
report [--json] Generate cookie report
|
||||
export [file] Export database to CSV
|
||||
import <file> 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 <<EOF
|
||||
{
|
||||
"total_cookies": $total,
|
||||
"unique_domains": $domains,
|
||||
"blocked_cookies": $blocked,
|
||||
"known_trackers": $trackers,
|
||||
"blocked_domains": $blocked_domains,
|
||||
"categories": {
|
||||
"essential": $essential,
|
||||
"functional": $functional,
|
||||
"analytics": $analytics,
|
||||
"advertising": $advertising,
|
||||
"tracking": $tracking,
|
||||
"unknown": $unknown
|
||||
},
|
||||
"last_24h": {
|
||||
"new_cookies": $new_today,
|
||||
"seen_cookies": $seen_today
|
||||
}
|
||||
}
|
||||
EOF
|
||||
else
|
||||
echo "============================================"
|
||||
echo " SecuBox Cookie Tracker Status"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
printf "Total Cookies: %s\n" "$total"
|
||||
printf "Unique Domains: %s\n" "$domains"
|
||||
printf "Blocked Cookies: %s\n" "$blocked"
|
||||
printf "Known Trackers: %s\n" "$trackers"
|
||||
printf "Blocked Domains: %s\n" "$blocked_domains"
|
||||
echo ""
|
||||
echo "--- Category Breakdown ---"
|
||||
printf " Essential: ${GREEN}%s${NC}\n" "$essential"
|
||||
printf " Functional: ${BLUE}%s${NC}\n" "$functional"
|
||||
printf " Analytics: ${YELLOW}%s${NC}\n" "$analytics"
|
||||
printf " Advertising: ${RED}%s${NC}\n" "$advertising"
|
||||
printf " Tracking: ${RED}%s${NC}\n" "$tracking"
|
||||
printf " Unknown: %s\n" "$unknown"
|
||||
echo ""
|
||||
echo "--- Last 24 Hours ---"
|
||||
printf " New Cookies: %s\n" "$new_today"
|
||||
printf " Seen Cookies: %s\n" "$seen_today"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# List cookies
|
||||
list_cookies() {
|
||||
local domain="" category="" limit=100 json=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--domain) domain="$2"; shift 2 ;;
|
||||
--category) category="$2"; shift 2 ;;
|
||||
--limit) limit="$2"; shift 2 ;;
|
||||
--json) json="1"; shift ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local where=""
|
||||
[ -n "$domain" ] && where="WHERE domain LIKE '%$domain%'"
|
||||
[ -n "$category" ] && {
|
||||
[ -n "$where" ] && where="$where AND" || where="WHERE"
|
||||
where="$where category='$category'"
|
||||
}
|
||||
|
||||
if [ "$json" = "1" ]; then
|
||||
echo "["
|
||||
sqlite3 -json "$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;" 2>/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 <domain>"
|
||||
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 <domain> <name> <category>"
|
||||
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 <domain>"
|
||||
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 <domain>"
|
||||
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
|
||||
Loading…
Reference in New Issue
Block a user