parental-control: 1.1.0

This commit is contained in:
Mohd Mehdi 2025-02-08 06:15:20 +00:00 committed by IOPSYS Dev
parent 40251b2371
commit a2733b66b7
No known key found for this signature in database
7 changed files with 421 additions and 12 deletions

View file

@ -5,13 +5,13 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=parental-control
PKG_VERSION:=1.0.4
PKG_VERSION:=1.1.0
LOCAL_DEV:=0
ifneq ($(LOCAL_DEV),1)
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://dev.iopsys.eu/network/parental-control.git
PKG_SOURCE_VERSION:=eea7793e26b52f45f4e47e849894ac3f8cdc3747
PKG_SOURCE_VERSION:=6a70cb9e0a9d92952610589915b85ae91841cb7b
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz
PKG_MIRROR_HASH:=skip
endif
@ -59,8 +59,10 @@ endef
endif
define Package/parental-control/install
$(INSTALL_DIR) $(1)/etc/parentalcontrol
$(INSTALL_DIR) $(1)/lib/parentalcontrol
$(INSTALL_DATA) ./files/lib/parentalcontrol/parentalcontrol.sh $(1)/lib/parentalcontrol/
$(INSTALL_BIN) ./files/lib/parentalcontrol/sync_bundles.sh $(1)/lib/parentalcontrol/
$(INSTALL_DIR) $(1)/etc
$(INSTALL_DATA) ./files/etc/firewall.parentalcontrol $(1)/etc/
@ -78,11 +80,13 @@ define Package/parental-control/install
$(INSTALL_DATA) ./files/etc/uci-defaults/95-firewall_parentalcontrol.ucidefaults $(1)/etc/uci-defaults/
$(INSTALL_DATA) ./files/etc/uci-defaults/95-migrate_urlfilter.ucidefaults $(1)/etc/uci-defaults/
$(INSTALL_DIR) $(1)/lib/upgrade/keep.d
$(INSTALL_DATA) ./files/lib/upgrade/keep.d/parentalcontrol $(1)/lib/upgrade/keep.d/parentalcontrol
$(BBFDM_REGISTER_SERVICES) -v ${VENDOR_PREFIX} ./bbfdm_service.json $(1) parentalcontrol
ifeq ($(CONFIG_PARENTAL_CONTROL_INCLUDE_URLFILTER_BUNDLES),y)
$(INSTALL_DIR) $(1)/etc/parental-control
$(INSTALL_DATA) ./files/etc/parental-control/urlbundles.tar.xz $(1)/etc/parental-control/
$(INSTALL_DATA) ./files/etc/parentalcontrol/urlbundles.tar.xz $(1)/etc/parentalcontrol/
endif
endef

View file

@ -1,3 +1,93 @@
config globals 'globals'
option enable '0'
option loglevel '3'
option enable '0'
option loglevel '3'
config urlbundle 'urlbundle_1'
option enable '0'
option name 'Abuse'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/abuse-nl.txt'
config urlbundle 'urlbundle_2'
option enable '0'
option name 'Ads'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/ads-nl.txt'
config urlbundle 'urlbundle_3'
option enable '0'
option name 'Crypto'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/crypto-nl.txt'
config urlbundle 'urlbundle_4'
option enable '1'
option name 'Drugs'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/drugs-nl.txt'
config urlbundle 'urlbundle_5'
option enable '0'
option name 'Everything else'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/everything-nl.txt'
config urlbundle 'urlbundle_6'
option enable '1'
option name 'Facebook/Instagram'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/facebook-nl.txt'
config urlbundle 'urlbundle_7'
option enable '1'
option name 'Fraud'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/fraud-nl.txt'
config urlbundle 'urlbundle_8'
option enable '1'
option name 'Gambling'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/gambling-nl.txt'
config urlbundle 'urlbundle_9'
option enable '0'
option name 'Malware'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/malware-nl.txt'
config urlbundle 'urlbundle_10'
option enable '1'
option name 'Phishing'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/phishing-nl.txt'
config urlbundle 'urlbundle_11'
option enable '1'
option name 'Piracy'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/piracy-nl.txt'
config urlbundle 'urlbundle_12'
option enable '0'
option name 'Porn'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/porn-nl.txt'
config urlbundle 'urlbundle_13'
option enable '1'
option name 'Ransomware'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/ransomware-nl.txt'
config urlbundle 'urlbundle_14'
option enable '0'
option name 'Redirect'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/redirect-nl.txt'
config urlbundle 'urlbundle_15'
option enable '1'
option name 'Scam'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/scam-nl.txt'
config urlbundle 'urlbundle_16'
option enable '0'
option name 'TikTok'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/tiktok-nl.txt'
config urlbundle 'urlbundle_17'
option enable '0'
option name 'Torrent'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/torrent-nl.txt'
config urlbundle 'urlbundle_18'
option enable '0'
option name 'Tracking'
option download_url 'https://blocklistproject.github.io/Lists/alt-version/tracking-nl.txt'

View file

@ -11,7 +11,8 @@ PROG=/usr/sbin/urlfilter
validate_global_section() {
uci_validate_section parentalcontrol globals globals \
'enable:bool:1' \
'loglevel:uinteger:3'
'loglevel:uinteger:3' \
'bundle_path:string'
}
remove_fw_rules() {
@ -47,19 +48,48 @@ configure_fw_rules() {
add_internet_schedule_rules
}
copy_dhcp_leases() {
src="/tmp/dhcp.leases"
dest="/etc/parentalcontrol/dhcp.leases"
dest_dir="/etc/parentalcontrol/"
# Ensure the destination directory exists
mkdir -p "$dest_dir" || { logger -p err "Failed to create directory $dest_dir."; return 1; }
# Check if the source file exists and is not empty
if [ -s "$src" ]; then
# Compare the content of the source and destination
if ! cmp -s "$src" "$dest"; then
# Use atomic copy to prevent partial writes
tmp_dest="${dest}.tmp"
cp "$src" "$tmp_dest" && mv "$tmp_dest" "$dest"
fi
fi
}
start_service() {
local enable loglevel
local enable loglevel bundle_path
config_load parentalcontrol
validate_global_section
[ -n "${bundle_path}" ] && mkdir -p ${bundle_path}
# add default bundles
process_default_bundles
# add firewall rules
configure_fw_rules
procd_open_instance parentalcontrol_dm
procd_set_param command ${PROG}
# if the router is, for example, upgraded and then it boots up
# then /tmp/dhcp.leases will be empty until clients try to get a lease,
# in that case, hostnames will not be processed by the daemon,
# for this we copy /tmp/dhcp.leases to /etc/parentalcontrol/dhcp.leases
# which will be persistent acrros reboots and upgrade where settings are kept
# and will be used as a backup in case /tmp/dhcp.leases is empty
copy_dhcp_leases
procd_open_instance "parentalcontrol_dm"
procd_set_param command nice -n 10 "${PROG}" # Lower priority
procd_append_param command -l ${loglevel}
procd_set_param respawn
procd_close_instance
@ -69,6 +99,7 @@ stop_service() {
# remove default bundles
remove_default_bundles
remove_fw_rules
copy_dhcp_leases
}
reload_service() {
@ -78,6 +109,7 @@ reload_service() {
start
else
configure_fw_rules
copy_dhcp_leases
ubus send parentalcontrol.reload
fi
}

View file

@ -13,8 +13,8 @@ IP_RULE=""
ACL_FILE=""
parentalcontrol_ipv4_forward=""
parentalcontrol_ipv6_forward=""
default_bundle_dir="/tmp/urlfilter/default/"
bundle_archive="/etc/parental-control/urlbundles.tar.xz"
default_bundle_dir="/tmp/parentalcontrol/default/"
bundle_archive="/etc/parentalcontrol/urlbundles.tar.xz"
log() {
echo "$*" |logger -t urlfilter.init -p debug

View file

@ -0,0 +1,282 @@
#!/bin/sh
. /lib/functions.sh
LOCKFILE="/tmp/sync_bundles.lock"
# this script handles syncing bundles
# if its a remote file, then it would be downloaded and placed in bundle_dir
bundle_path="$(uci -q get parentalcontrol.globals.bundle_path)"
if [ -z "${bundle_path}" ]; then
bundle_path="/tmp/parentalcontrol"
fi
stringstore_dir="${bundle_path}/stringstore"
bundle_dir="${bundle_path}/urlbundles"
bundle_sizes="${bundle_path}/bundle_sizes"
# Ensure required directories and files exist
initialize_environment() {
mkdir -p "$bundle_dir"
mkdir -p "$stringstore_dir"
[ ! -f "$bundle_sizes" ] && touch "$bundle_sizes"
}
# Function to sanitize URLs to avoid code injection and ensure safety
sanitize_url() {
local raw_url="$1"
echo "$raw_url" | sed 's/[^a-zA-Z0-9_.:/?-]//g'
}
update_bundle_file_from_url() {
local download_url="$1"
local bundle_file_name="$2"
local bundle_file_size="$3"
local bundle_name="$4"
local file_name="$5"
local available_memory
available_memory=$(df "$bundle_dir" | tail -n 1 | awk '{print $(NF-2)}') # Available memory in 1K blocks
local needed_blocks=$((bundle_file_size / 1024)) # Convert bundle_file_size to 1K blocks
local max_size=$((10 * 1024 * 1024)) # 10MB in bytes
if [ "$available_memory" -le "$needed_blocks" ]; then
logger -p info "Error: Not enough disk space for bundle: ${bundle_name}"
return 1
fi
if [ "$bundle_file_size" -gt "$max_size" ]; then
logger -p info "update_bundle_file_from_url: Error: File size for ${bundle_name} exceeds 10MB"
return 1
fi
# Determine file path
local file_path
if echo "$download_url" | grep -q "^file://"; then
file_path=${download_url#file://}
else
# Random delay (0-5s) before starting the download
local delay=$((RANDOM % 6))
logger -p info "update_bundle_file_from_url: Waiting ${delay}s before downloading..."
sleep "$delay"
# Retry logic with exponential backoff
local temp_file="${bundle_dir}/tmp_${file_name}"
local attempt=1
local success=0
while [ $attempt -le 3 ]; do
wget -q -O "$temp_file" "$download_url"
if [ $? -eq 0 ]; then
success=1
break
else
logger -p info "update_bundle_file_from_url: Download failed. Retrying $attempt ..."
local backoff=$(( (2 ** attempt) + (RANDOM % 3) )) # Exponential backoff + 0-2s jitter
sleep "$backoff"
fi
attempt=$(( attempt+1 ))
done
if [ $success -ne 1 ]; then
logger -p info "update_bundle_file_from_url: Failed to download bundle: ${bundle_name}"
rm -f "$temp_file"
return 1
fi
file_path="$temp_file"
fi
# Handle compressed files
local final_path="${bundle_dir}/${bundle_file_name}"
if [[ "$file_path" =~ \.xz$ ]]; then
if ! xz -dc "$file_path" > "$final_path"; then
logger -p info "update_bundle_file_from_url: Decompression failed."
rm -f "$final_path"
rm -f "$file_path"
return 1
fi
rm -f "$file_path"
elif [[ "$file_path" =~ \.gz$ ]]; then
if ! gzip -dc "$file_path" > "$final_path"; then
logger -p info "update_bundle_file_from_url: Decompression failed."
rm -f "$final_path"
rm -f "$file_path"
return 1
fi
rm -f "$file_path"
else
mv "$file_path" "$final_path"
fi
# file would have lines of the format: 0.0.0.0 www.facebook.com
# so we keep only the url part and remove duplicates
local processed_final_path="${final_path}_urls"
awk '{print $NF}' "$final_path" | sort -u > "$processed_final_path"
# delete unprocessed file
rm -rf "$final_path"
# Update the bundle size and send ubus event
echo "$bundle_file_name $bundle_file_size" >> "$bundle_sizes"
ubus send "parentalcontrol.bundle.update" "{\"bundle_file_path\":\"${processed_final_path}\",\"bundle_name\":\"${bundle_name}\"}"
return 0
}
handle_download_url() {
local raw_download_url="$1"
local bundle_name="$2"
local sanitized_url
sanitized_url=$(sanitize_url "$raw_download_url")
local file_name="${sanitized_url##*/}" # Get everything after the last '/'
local bundle_file_name="${file_name}.urlbundle"
local unprocessed_file=0
local file_path="${sanitized_url#file://}"
if echo "$sanitized_url" | grep -qE "^https?://|^file://"; then
local previous_bundle_size
previous_bundle_size=$(grep "^${bundle_file_name} " "$bundle_sizes" | awk '{print $2}')
# If the URL is HTTP, fetch the file size
local bundle_file_size
if echo "$sanitized_url" | grep -qE "^https?://"; then
bundle_file_size="$(curl -I "$sanitized_url" 2>&1 | grep -i 'content-length' | cut -d: -f2 | xargs)"
[ -z "$bundle_file_size" ] && bundle_file_size=0
else
# If it's a file:// URL, get the file size from the filesystem
bundle_file_size=$(ls -l "$file_path" 2>/dev/null | awk '{print $5}')
[ -z "$bundle_file_size" ] && bundle_file_size=0
fi
if [ -n "$previous_bundle_size" ] && [ "$bundle_file_size" -eq "$previous_bundle_size" ]; then
return
fi
if echo "$sanitized_url" | grep -q "^file://" && ! echo "$sanitized_url" | grep -Eq "\.(xz|gz)$"; then
# the file is not processed and hence not moved if it is a local uncompressed file
sed -i "/^${bundle_file_name} /d" "$bundle_sizes"
echo "$bundle_file_name $bundle_file_size" >> "$bundle_sizes"
ubus send "parentalcontrol.bundle.update" "{\"bundle_file_path\":\"${file_path}\",\"bundle_name\":\"${bundle_name}\"}"
return
fi
# Remove existing entries
if [ -n "$previous_bundle_size" ]; then
sed -i "/^${bundle_file_name} /d" "$bundle_sizes"
rm -f "${bundle_dir}/${bundle_file_name}"
fi
update_bundle_file_from_url "$sanitized_url" "$bundle_file_name" "$bundle_file_size" "$bundle_name" "$file_name"
else
logger -p info "Error: Unsupported URL format for ${bundle_file_name}"
fi
}
cleanup_bundle_files() {
local dir="$1"
[ -d "$dir" ] || return 1
# Collect all download_url entries using config_foreach
local urls=""
get_download_url() {
local section="$1"
config_get url "$section" download_url
config_get_bool enable "$1" enable 0
if [ "${enable}" -eq 0 ]; then
# bundle is disabled
return 0
fi
url="${url#file://}"
url="${url#https://}"
url="${url#http://}"
url="${url##*/}" # Get everything after the last '/'
urls="$urls $url"
}
config_load parentalcontrol
config_foreach get_download_url urlbundle
# Loop through all files in the directory
for file in "$dir"/*; do
[ -f "$file" ] || continue # Skip non-files
# Remove the suffix after the last dot
base_name="$(basename "$file")"
name="${base_name%.*}" # Removes the last dot and suffix
# Check if the name is present in the collected urls
if ! echo "$urls" | grep -q "$name"; then
rm -f "$file"
sed -i "/^${name} /d" "$bundle_sizes"
fi
done
}
# Main handler for all profile URL bundles
handle_filter_for_bundles() {
ubus -t 20 wait_for bbfdm.parentalcontrol
if [ "$?" -ne 0 ]; then
logger -p error "bbfdm.parentalcontrol object not found"
return
fi
initialize_environment
cleanup_bundle_files "$bundle_dir"
cleanup_bundle_files "$stringstore_dir"
config_load parentalcontrol
config_get_bool enable globals enable 0
if [ "${enable}" -eq 0 ]; then
# Parental control is disabled
return 0
fi
local profile enable bundles bundle_name download_url
check_bundle_exists() {
config_get name "$1" name
config_get_bool enable "$1" enable 0
config_get download_url "$1" download_url
if [ "${enable}" -eq 0 ]; then
# bundle is disabled
return 0
fi
if [ "$name" = "$2" ]; then
handle_download_url "$download_url" "$name"
fi
}
handle_bundle_from_profile() {
local bundle_name="$1"
config_foreach check_bundle_exists urlbundle "$bundle_name"
}
handle_profile() {
config_get_bool enable "$1" enable 0
[ "$enable" -ne 1 ] && return
config_list_foreach "$1" profile_urlbundle handle_bundle_from_profile
}
config_foreach handle_profile profile_urlfilter
}
# Open file descriptor 200 for locking
exec 200>"$LOCKFILE"
# Try to acquire an exclusive lock; exit if another instance is running
flock -n 200 || { logger -p info "sync_bundles.sh is already running, exiting."; exit 1; }
handle_filter_for_bundles

View file

@ -0,0 +1 @@
/etc/parentalcontrol/dhcp.leases