Files

327 lines
10 KiB
Plaintext
Raw Permalink Normal View History

#!/system/bin/sh
# cellguard — CLI driver for the cellguard kernel module.
#
# Invoked by the KernelSU-Next WebUI through ksu.exec("cellguard <cmd>").
# Every subcommand prints a single JSON object on stdout so the WebUI
# can JSON.parse() the result directly.
CONFIG_DIR="/data/adb/cellguard"
CONFIG_FILE="$CONFIG_DIR/config.sh"
DEV=/dev/cellguard
mkdir -p "$CONFIG_DIR"
[ -f "$CONFIG_FILE" ] || cat > "$CONFIG_FILE" << 'EOF'
ENABLED=1
BLOCK_2G=1
BLOCK_3G=1
BLOCK_NULL_CIPHER=1
POLL_MS=1500
CARRIER=none
EOF
. "$CONFIG_FILE"
[ -z "$CARRIER" ] && CARRIER=none
VALID_CARRIERS="none tmobile verizon att uscellular dish cricket metro googlefi generic_us"
# JSON escaping — strings are put through sed to escape \ and "
j_esc() {
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' \
-e ':a;N;$!ba;s/\n/\\n/g'
}
emit_error() {
printf '{"ok":false,"error":"%s"}\n' "$(j_esc "$1")"
exit 1
}
require_dev() {
[ -c "$DEV" ] || emit_error "kernel module not loaded (/dev/cellguard missing)"
}
save_config() {
cat > "$CONFIG_FILE" << EOF
ENABLED=$ENABLED
BLOCK_2G=$BLOCK_2G
BLOCK_3G=$BLOCK_3G
BLOCK_NULL_CIPHER=$BLOCK_NULL_CIPHER
POLL_MS=$POLL_MS
CARRIER=$CARRIER
EOF
}
#####################################################################
# status — read /proc/cellguard/status and reformat as JSON
#####################################################################
cmd_status() {
require_dev
if [ ! -f /proc/cellguard/status ]; then
emit_error "/proc/cellguard/status missing"
fi
# /proc/cellguard/status is key: value, one per line
awk -v cfg2g="$BLOCK_2G" -v cfg3g="$BLOCK_3G" \
-v cfgnull="$BLOCK_NULL_CIPHER" -v cfgpoll="$POLL_MS" '
BEGIN { FS=":"; printf "{" ; first=1 }
{
k=$1; sub(/^ +/,"",k); sub(/ +$/,"",k)
v=$2; for (i=3;i<=NF;i++) v=v ":" $i
sub(/^ +/,"",v); sub(/ +$/,"",v)
gsub(/\\/,"\\\\",v); gsub(/"/,"\\\"",v)
if (!first) printf ","
first=0
# numeric fields
if (k=="enabled" || k=="rat_code" || k=="cipher_known" || \
k=="cipher_null" || k=="block_2g" || k=="block_3g" || \
k=="block_null" || k=="poll_ms" || k=="rsrp_dbm" || \
k=="tx_pwr_dbm" || \
k=="downgrades" || k=="relocks" || k=="polls" || \
k=="bands_locked") {
# rat field might be mixed — strip trailing junk
n=v; sub(/[^-0-9].*/,"",n); if (n=="") n=0
printf "\"%s\":%s", k, n
} else if (k=="modem" || k=="rat" || k=="operator" || \
k=="carrier" || k=="bands_method") {
printf "\"%s\":\"%s\"", k, v
} else {
printf "\"%s\":\"%s\"", k, v
}
}
END { printf ",\"ok\":true}" }
' /proc/cellguard/status
echo
}
#####################################################################
# on / off — flip the guard, persist to config
#####################################################################
cmd_on() {
require_dev
echo "on" > "$DEV" 2>/dev/null || emit_error "write /dev/cellguard failed"
ENABLED=1
save_config
printf '{"ok":true,"enabled":1}\n'
}
cmd_off() {
require_dev
echo "off" > "$DEV" 2>/dev/null || emit_error "write /dev/cellguard failed"
ENABLED=0
save_config
printf '{"ok":true,"enabled":0}\n'
}
#####################################################################
# apply-config — push saved policy, then on/off accordingly
# (called by service.sh at boot, and by any config-change flow)
#####################################################################
cmd_apply_config() {
require_dev
# Select the carrier profile first so that when we flip the guard on,
# the kernel's full push_lock_policy() applies WS46 + band-lock in
# one CFUN cycle. When the guard is off we still record the carrier
# for next enable.
if [ -n "$CARRIER" ] && [ "$CARRIER" != "none" ]; then
echo "carrier $CARRIER" > "$DEV" 2>/dev/null
fi
if [ "$ENABLED" = "1" ]; then
echo "on" > "$DEV" 2>/dev/null
else
echo "off" > "$DEV" 2>/dev/null
fi
printf '{"ok":true,"applied":true,"enabled":%s,"carrier":"%s"}\n' \
"$ENABLED" "$CARRIER"
}
#####################################################################
# relock — force an immediate policy push even if state looks fine
#####################################################################
cmd_relock() {
require_dev
echo "relock" > "$DEV" 2>/dev/null || emit_error "relock write failed"
printf '{"ok":true,"relocked":true}\n'
}
#####################################################################
# scan — trigger AT+COPS=?, parse result, emit JSON list
#
# AT+COPS=? response:
# +COPS: (stat,"long","short","numeric",AcT),(...),...
# stat: 0=unknown 1=available 2=current 3=forbidden
# AcT : 0/1/3/8 = 2G, 2/4-6 = 3G, 7/9/10 = LTE, 11/13 = NR
#####################################################################
cmd_scan() {
require_dev
# Kick the scan and wait. AT+COPS=? can take up to ~180s on a cold
# SIM; the module runs it synchronously in the ioctl path.
echo "scan" > "$DEV" 2>/dev/null || emit_error "scan write failed"
# Poll /proc/cellguard/cells until it has a +COPS: line or we time out
n=0
while [ $n -lt 200 ]; do
if grep -q "+COPS:" /proc/cellguard/cells 2>/dev/null; then
break
fi
sleep 1
n=$((n + 1))
done
raw=$(cat /proc/cellguard/cells 2>/dev/null)
[ -z "$raw" ] && emit_error "scan produced no output"
printf '%s' "$raw" | awk '
BEGIN {
printf "{\"ok\":true,\"cells\":["
first=1
}
{
line=$0
# Work through each (...) tuple
while (match(line, /\([^()]+\)/)) {
tup=substr(line, RSTART+1, RLENGTH-2)
line=substr(line, RSTART+RLENGTH)
# Split tup on commas, but ignore commas inside quotes
n=0
buf=""
inq=0
for (i=1;i<=length(tup);i++) {
c=substr(tup,i,1)
if (c=="\"") { inq=1-inq; continue }
if (c=="," && !inq) { n++; f[n]=buf; buf=""; continue }
buf = buf c
}
n++; f[n]=buf
# A scan tuple has 5 fields: stat,long,short,numeric,act
# The trailing "supported modes" / "supported formats" tuples
# have 1-2 numeric fields — skip those.
if (n < 4) continue
stat=f[1]; longn=f[2]; shortn=f[3]; num=f[4]; act=f[5]
if (stat !~ /^[0-9]+$/) continue
# Map AcT to RAT name and a security-ranking score
act_n = act + 0
rat="?"; score=0
if (act_n==0||act_n==1||act_n==3||act_n==8) { rat="2G"; score=1 }
else if (act_n==2||act_n==4||act_n==5||act_n==6) { rat="3G"; score=2 }
else if (act_n==7||act_n==9||act_n==10) { rat="LTE"; score=3 }
else if (act_n==11||act_n==13) { rat="5G-SA"; score=4 }
avail="unknown"
if (stat=="0") avail="unknown"
else if (stat=="1") avail="available"
else if (stat=="2") avail="current"
else if (stat=="3") avail="forbidden"
# Escape
gsub(/\\/,"\\\\",longn); gsub(/"/,"\\\"",longn)
gsub(/\\/,"\\\\",shortn); gsub(/"/,"\\\"",shortn)
gsub(/\\/,"\\\\",num); gsub(/"/,"\\\"",num)
if (!first) printf ","
first=0
printf "{\"operator\":\"%s\",\"short\":\"%s\",\"mcc_mnc\":\"%s\",\"rat\":\"%s\",\"act\":%d,\"avail\":\"%s\",\"score\":%d}", \
longn, shortn, num, rat, act_n, avail, score
}
}
END { printf "]}\n" }
'
}
#####################################################################
# carrier — set the band-lock profile
# cellguard carrier list
# cellguard carrier <name>
#####################################################################
cmd_carrier() {
name="$1"
if [ -z "$name" ] || [ "$name" = "list" ]; then
printf '{"ok":true,"current":"%s","carriers":[' "$CARRIER"
first=1
for c in $VALID_CARRIERS; do
if [ "$first" = "1" ]; then first=0; else printf ','; fi
printf '"%s"' "$c"
done
printf ']}\n'
return
fi
ok=0
for c in $VALID_CARRIERS; do
[ "$c" = "$name" ] && ok=1
done
[ "$ok" = "1" ] || emit_error "unknown carrier: $name"
require_dev
echo "carrier $name" > "$DEV" 2>/dev/null || emit_error "write failed"
CARRIER="$name"
save_config
printf '{"ok":true,"carrier":"%s"}\n' "$name"
}
#####################################################################
# config — read/write persisted policy from the WebUI
#
# Usage:
# cellguard config get
# cellguard config set block_2g=1 block_3g=0 block_null_cipher=1 poll_ms=2000
#####################################################################
cmd_config() {
sub="$1"; shift
case "$sub" in
get|"")
printf '{"ok":true,"enabled":%s,"block_2g":%s,"block_3g":%s,"block_null_cipher":%s,"poll_ms":%s,"carrier":"%s"}\n' \
"$ENABLED" "$BLOCK_2G" "$BLOCK_3G" "$BLOCK_NULL_CIPHER" "$POLL_MS" "$CARRIER"
;;
set)
while [ -n "$1" ]; do
k="${1%%=*}"
v="${1#*=}"
case "$k" in
enabled) ENABLED="$v" ;;
block_2g) BLOCK_2G="$v" ;;
block_3g) BLOCK_3G="$v" ;;
block_null_cipher) BLOCK_NULL_CIPHER="$v" ;;
poll_ms) POLL_MS="$v" ;;
*) emit_error "unknown key: $k" ;;
esac
shift
done
save_config
cmd_apply_config
;;
*)
emit_error "config: unknown subcommand '$sub'"
;;
esac
}
#####################################################################
# dispatch
#####################################################################
case "$1" in
status) cmd_status ;;
on) cmd_on ;;
off) cmd_off ;;
relock) cmd_relock ;;
scan) cmd_scan ;;
carrier) shift; cmd_carrier "$@" ;;
apply-config) cmd_apply_config ;;
config) shift; cmd_config "$@" ;;
*)
cat << USAGE
cellguard — driver for /dev/cellguard
cellguard status show current RAT, operator, cipher, counters
cellguard on enable the guard (force LTE/NR, re-lock on drop)
cellguard off disable and release the modem
cellguard relock force one relock pass now
cellguard scan full AT+COPS=? scan, JSON list of visible networks
cellguard config get|set read or update persisted policy
All subcommands emit a single-line JSON object on stdout.
USAGE
;;
esac