mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-03 10:43:56 +08:00
💐 Sync 2026-09-13 10:49:47
This commit is contained in:
1 parent
70d35bab31
commit
7614ee5c63
21 files changed
+881
-807
No files matched your search
+5
-3
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=gecoosac
|
PKG_NAME:=gecoosac
|
||||||
PKG_VERSION:=2.2.20251015
|
PKG_VERSION:=2.2.20251015
|
||||||
PKG_RELEASE:=20
|
PKG_RELEASE:=21
|
||||||
|
|
||||||
PKG_MAINTAINER:=Roc Lai <laipeng668@qq.com>
|
PKG_MAINTAINER:=Roc Lai <laipeng668@qq.com>
|
||||||
PKG_LICENSE:=AGPL-3.0-only
|
PKG_LICENSE:=AGPL-3.0-only
|
||||||
@@ -43,8 +43,8 @@ define Package/$(PKG_NAME)
|
|||||||
CATEGORY:=Network
|
CATEGORY:=Network
|
||||||
TITLE:=gecoosac server (version $(PKG_VERSION))
|
TITLE:=gecoosac server (version $(PKG_VERSION))
|
||||||
URL:=http://www.cnrouter.com/
|
URL:=http://www.cnrouter.com/
|
||||||
DEPENDS:=@(aarch64||arm||i386||mips||mipsel||x86_64) +openssl-util
|
DEPENDS:=@(aarch64||arm||i386||mips||mipsel||x86_64) +openssl-util +coreutils-timeout
|
||||||
PROVIDES:=gecoosac-files
|
PROVIDES:=gecoosac-files gecoosac-common
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/$(PKG_NAME)/conffiles
|
define Package/$(PKG_NAME)/conffiles
|
||||||
@@ -66,11 +66,13 @@ define Package/$(PKG_NAME)/install
|
|||||||
$(INSTALL_DIR) $(1)/etc/gecoosac/tls
|
$(INSTALL_DIR) $(1)/etc/gecoosac/tls
|
||||||
$(INSTALL_DIR) $(1)/etc/init.d
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/gecoosac
|
||||||
|
|
||||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin
|
||||||
$(INSTALL_BIN) ./files/etc/init.d/gecoosac $(1)/etc/init.d/gecoosac
|
$(INSTALL_BIN) ./files/etc/init.d/gecoosac $(1)/etc/init.d/gecoosac
|
||||||
$(INSTALL_BIN) ./files/etc/uci-defaults/gecoosac $(1)/etc/uci-defaults/gecoosac
|
$(INSTALL_BIN) ./files/etc/uci-defaults/gecoosac $(1)/etc/uci-defaults/gecoosac
|
||||||
$(INSTALL_CONF) ./files/etc/config/gecoosac $(1)/etc/config/gecoosac
|
$(INSTALL_CONF) ./files/etc/config/gecoosac $(1)/etc/config/gecoosac
|
||||||
|
$(INSTALL_DATA) ./files/usr/share/gecoosac/common.sh $(1)/usr/share/gecoosac/common.sh
|
||||||
endef
|
endef
|
||||||
|
|
||||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
. /lib/functions.sh
|
. /lib/functions.sh
|
||||||
. /lib/functions/procd.sh
|
. /lib/functions/procd.sh
|
||||||
|
. /usr/share/gecoosac/common.sh
|
||||||
|
|
||||||
USE_PROCD=1
|
USE_PROCD=1
|
||||||
|
|
||||||
@@ -10,13 +11,9 @@ START=90
|
|||||||
STOP=10
|
STOP=10
|
||||||
|
|
||||||
PROG=/usr/bin/gecoosac
|
PROG=/usr/bin/gecoosac
|
||||||
DEFAULT_DB_DIR=/etc/gecoosac
|
|
||||||
DEFAULT_UPLOAD_DIR=/tmp/gecoosac/upload
|
|
||||||
DEFAULT_CRT_FILE=/etc/gecoosac/tls/gecoosac.crt
|
|
||||||
DEFAULT_KEY_FILE=/etc/gecoosac/tls/gecoosac.key
|
|
||||||
DEFAULT_PID_DIR=/var/run
|
|
||||||
DEFAULT_LANG=zh
|
DEFAULT_LANG=zh
|
||||||
CERT_TIMEOUT=60
|
CERT_TIMEOUT=60
|
||||||
|
CERT_RENEW_BEFORE=2592000
|
||||||
|
|
||||||
init_conf() {
|
init_conf() {
|
||||||
local section_type
|
local section_type
|
||||||
@@ -47,193 +44,6 @@ init_conf() {
|
|||||||
config_get "log" "config" "log" "0"
|
config_get "log" "config" "log" "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
is_abs_path() {
|
|
||||||
case "$1" in
|
|
||||||
/*) return 0 ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize_path() {
|
|
||||||
local path="$1"
|
|
||||||
local part normalized parent
|
|
||||||
|
|
||||||
is_abs_path "$path" || return 1
|
|
||||||
normalized="/"
|
|
||||||
path="${path#/}"
|
|
||||||
|
|
||||||
while [ -n "$path" ]; do
|
|
||||||
part="${path%%/*}"
|
|
||||||
if [ "$part" = "$path" ]; then
|
|
||||||
path=""
|
|
||||||
else
|
|
||||||
path="${path#*/}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$part" in
|
|
||||||
""|.) ;;
|
|
||||||
..)
|
|
||||||
if [ "$normalized" != "/" ]; then
|
|
||||||
parent="${normalized%/*}"
|
|
||||||
[ -n "$parent" ] || parent="/"
|
|
||||||
normalized="$parent"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*) normalized="${normalized%/}/$part" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
printf '%s\n' "$normalized"
|
|
||||||
}
|
|
||||||
|
|
||||||
path_has_clear_stage_component() {
|
|
||||||
local path="$1" part rest
|
|
||||||
|
|
||||||
path="$(normalize_path "$path")" || return 1
|
|
||||||
rest="${path#/}"
|
|
||||||
while [ -n "$rest" ]; do
|
|
||||||
part="${rest%%/*}"
|
|
||||||
if [ "$part" = "$rest" ]; then
|
|
||||||
rest=""
|
|
||||||
else
|
|
||||||
rest="${rest#*/}"
|
|
||||||
fi
|
|
||||||
case "$part" in
|
|
||||||
.gecoosac-clear.*) return 0 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
path_uses_clear_stage() {
|
|
||||||
local path="$1" resolved
|
|
||||||
|
|
||||||
path_has_clear_stage_component "$path" && return 0
|
|
||||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
||||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
|
||||||
path_has_clear_stage_component "$resolved" && return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
is_supported_upload_path() {
|
|
||||||
local path="$1" storage
|
|
||||||
|
|
||||||
path="$(normalize_path "$path")" || return 1
|
|
||||||
path_uses_clear_stage "$path" && return 1
|
|
||||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
|
||||||
|
|
||||||
case "$path" in
|
|
||||||
/mnt/*/gecoosac/upload)
|
|
||||||
storage="${path#/mnt/}"
|
|
||||||
storage="${storage%/gecoosac/upload}"
|
|
||||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
|
||||||
;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
is_safe_upload_dir() {
|
|
||||||
local path physical
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
physical="$(managed_dir_path upload "$path")" || return 1
|
|
||||||
is_supported_upload_path "$path" || return 1
|
|
||||||
is_supported_upload_path "$physical"
|
|
||||||
}
|
|
||||||
|
|
||||||
is_path_in_dir() {
|
|
||||||
local path root
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
root="$(normalize_path "$2")" || return 1
|
|
||||||
|
|
||||||
[ "$root" != "/" ] || return 1
|
|
||||||
[ "$path" = "$root" ] && return 0
|
|
||||||
[ "${path#"$root"/}" != "$path" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
is_secure_dir() {
|
|
||||||
local allow_sticky="$2" owner permissions metadata
|
|
||||||
|
|
||||||
[ -d "$1" ] && [ ! -L "$1" ] || return 1
|
|
||||||
metadata="$(ls -ldn "$1" 2>/dev/null)" || return 1
|
|
||||||
set -- $metadata
|
|
||||||
permissions="$1"
|
|
||||||
owner="$3"
|
|
||||||
[ "$owner" = "0" ] || return 1
|
|
||||||
case "$permissions" in
|
|
||||||
d?????????) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ "$(printf '%s' "$permissions" | cut -c6)" = "w" ] || \
|
|
||||||
[ "$(printf '%s' "$permissions" | cut -c9)" = "w" ]; then
|
|
||||||
[ "$allow_sticky" = "1" ] && [ "$(printf '%s' "$permissions" | cut -c10)" = "t" ] || return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
is_secure_upload_dir() {
|
|
||||||
local path current part rest
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
[ -d "$path" ] && [ ! -L "$path" ] || return 1
|
|
||||||
current="/"
|
|
||||||
rest="${path#/}"
|
|
||||||
|
|
||||||
while [ -n "$rest" ]; do
|
|
||||||
part="${rest%%/*}"
|
|
||||||
if [ "$part" = "$rest" ]; then
|
|
||||||
rest=""
|
|
||||||
else
|
|
||||||
rest="${rest#*/}"
|
|
||||||
fi
|
|
||||||
current="${current%/}/$part"
|
|
||||||
case "$current" in
|
|
||||||
/tmp) is_secure_dir "$current" 1 || return 1 ;;
|
|
||||||
*) is_secure_dir "$current" || return 1 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
is_safe_db_dir() {
|
|
||||||
local path upload_root physical physical_upload_root
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
|
||||||
|
|
||||||
case "$path" in
|
|
||||||
/etc/gecoosac|/etc/gecoosac/*|/tmp/gecoosac|/tmp/gecoosac/*|/var/lib/gecoosac|/var/lib/gecoosac/*) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
is_path_in_dir "$path" "$upload_root" && return 1
|
|
||||||
physical="$(managed_dir_path db "$path")" || return 1
|
|
||||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
|
||||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
is_safe_pid_dir() {
|
|
||||||
local path upload_root physical physical_upload_root
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
|
||||||
|
|
||||||
case "$path" in
|
|
||||||
/var/run|/var/run/*|/tmp/gecoosac|/tmp/gecoosac/*) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
is_path_in_dir "$path" "$upload_root" && return 1
|
|
||||||
physical="$(managed_dir_path pid "$path")" || return 1
|
|
||||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
|
||||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
is_port() {
|
is_port() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
""|*[!0-9]*) return 1 ;;
|
""|*[!0-9]*) return 1 ;;
|
||||||
@@ -242,66 +52,6 @@ is_port() {
|
|||||||
[ "$1" -ge 1 ] 2>/dev/null && [ "$1" -le 65535 ]
|
[ "$1" -ge 1 ] 2>/dev/null && [ "$1" -le 65535 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
run_with_timeout() {
|
|
||||||
local timeout pid i
|
|
||||||
|
|
||||||
timeout="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
"$@" >/dev/null 2>&1 &
|
|
||||||
pid="$!"
|
|
||||||
i=0
|
|
||||||
|
|
||||||
while kill -0 "$pid" >/dev/null 2>&1; do
|
|
||||||
if [ "$i" -ge "$timeout" ]; then
|
|
||||||
kill "$pid" >/dev/null 2>&1
|
|
||||||
sleep 1
|
|
||||||
kill -9 "$pid" >/dev/null 2>&1
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
wait "$pid"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_with_timeout_output() {
|
|
||||||
local timeout output pid i
|
|
||||||
|
|
||||||
timeout="$1"
|
|
||||||
output="$2"
|
|
||||||
shift 2
|
|
||||||
|
|
||||||
"$@" >"$output" 2>/dev/null &
|
|
||||||
pid="$!"
|
|
||||||
i=0
|
|
||||||
|
|
||||||
while kill -0 "$pid" >/dev/null 2>&1; do
|
|
||||||
if [ "$i" -ge "$timeout" ]; then
|
|
||||||
kill "$pid" >/dev/null 2>&1
|
|
||||||
kill -9 "$pid" >/dev/null 2>&1
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
wait "$pid"
|
|
||||||
}
|
|
||||||
|
|
||||||
is_readable_regular_file() {
|
|
||||||
local path="$1" tmp_dir status
|
|
||||||
|
|
||||||
tmp_dir="$(mktemp -d /tmp/gecoosac-file.XXXXXX)" || return 1
|
|
||||||
run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/check" \
|
|
||||||
/bin/sh -c '[ -f "$1" ] && [ -r "$1" ] && [ -s "$1" ]' \
|
|
||||||
gecoosac-file-check "$path" </dev/null
|
|
||||||
status="$?"
|
|
||||||
rm -rf "$tmp_dir"
|
|
||||||
return "$status"
|
|
||||||
}
|
|
||||||
|
|
||||||
is_ipv4() {
|
is_ipv4() {
|
||||||
local value="$1"
|
local value="$1"
|
||||||
local part count
|
local part count
|
||||||
@@ -328,48 +78,55 @@ san_has_entry() {
|
|||||||
local san="$1"
|
local san="$1"
|
||||||
local entry="$2"
|
local entry="$2"
|
||||||
|
|
||||||
echo "$san" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -F -x -q "$entry"
|
printf '%s\n' "$san" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -F -x -q "$entry"
|
||||||
}
|
}
|
||||||
|
|
||||||
cert_matches_san() {
|
run_with_timeout_output() {
|
||||||
local cert_file="$1" cert_host="$2" cert_ip="$3"
|
local limit="$1" output="$2"
|
||||||
local san tmp_dir
|
shift 2
|
||||||
|
|
||||||
is_readable_regular_file "$cert_file" || return 1
|
# coreutils-timeout waits for completion directly and also bounds commands
|
||||||
tmp_dir="$(mktemp -d /tmp/gecoosac-cert.XXXXXX)" || return 1
|
# which ignore SIGTERM; no polling delay or orphaned sleep process is needed.
|
||||||
if ! run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/san" \
|
timeout -k 1 "$limit" "$@" >"$output" 2>/dev/null
|
||||||
openssl x509 -in "$cert_file" -noout -ext subjectAltName </dev/null; then
|
|
||||||
rm -rf "$tmp_dir"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
san="$(cat "$tmp_dir/san" 2>/dev/null)"
|
|
||||||
rm -rf "$tmp_dir"
|
|
||||||
[ -n "$san" ] || return 1
|
|
||||||
san_has_entry "$san" "DNS:${cert_host}" || return 1
|
|
||||||
[ -z "$cert_ip" ] || san_has_entry "$san" "IP Address:${cert_ip}" || return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cert_matches_key() {
|
run_with_timeout() {
|
||||||
local cert_file="$1" key_file="$2"
|
local limit="$1"
|
||||||
local cert_pub key_pub tmp_dir
|
shift
|
||||||
|
run_with_timeout_output "$limit" /dev/null "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
cert_pair_valid() {
|
||||||
|
local cert_file="$1" key_file="$2" cert_host="$3" cert_ip="$4"
|
||||||
|
local min_lifetime="${5:-0}" tmp_dir cert_pub key_pub san status
|
||||||
|
|
||||||
is_readable_regular_file "$cert_file" || return 1
|
|
||||||
is_readable_regular_file "$key_file" || return 1
|
|
||||||
tmp_dir="$(mktemp -d /tmp/gecoosac-cert.XXXXXX)" || return 1
|
tmp_dir="$(mktemp -d /tmp/gecoosac-cert.XXXXXX)" || return 1
|
||||||
if ! run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/cert.pub" \
|
status=1
|
||||||
openssl x509 -in "$cert_file" -noout -pubkey </dev/null || \
|
if run_with_timeout "$CERT_TIMEOUT" /bin/sh -c '
|
||||||
! run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/key.pub" \
|
[ -f "$1" ] && [ -r "$1" ] && [ -s "$1" ] &&
|
||||||
openssl pkey -in "$key_file" -pubout </dev/null; then
|
[ -f "$2" ] && [ -r "$2" ] && [ -s "$2" ]
|
||||||
rm -rf "$tmp_dir"
|
' gecoosac-file-check "$cert_file" "$key_file" </dev/null && \
|
||||||
return 1
|
run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/cert" \
|
||||||
|
openssl x509 -in "$cert_file" -noout -pubkey -ext subjectAltName -checkend "$min_lifetime" </dev/null && \
|
||||||
|
run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/key.pub" \
|
||||||
|
openssl pkey -in "$key_file" -pubout </dev/null && \
|
||||||
|
run_with_timeout "$CERT_TIMEOUT" \
|
||||||
|
openssl verify -partial_chain -trusted "$cert_file" "$cert_file" </dev/null; then
|
||||||
|
# Trust the configured leaf only for the local validity-period check;
|
||||||
|
# browser trust still depends on the certificate installed by the user.
|
||||||
|
cert_pub="$(sed -n '/^-----BEGIN PUBLIC KEY-----$/,/^-----END PUBLIC KEY-----$/p' "$tmp_dir/cert")"
|
||||||
|
key_pub="$(cat "$tmp_dir/key.pub")"
|
||||||
|
if [ -n "$cert_pub" ] && [ "$cert_pub" = "$key_pub" ]; then
|
||||||
|
status=0
|
||||||
|
if [ -n "$cert_host" ]; then
|
||||||
|
san="$(cat "$tmp_dir/cert")"
|
||||||
|
san_has_entry "$san" "DNS:${cert_host}" || status=1
|
||||||
|
[ -z "$cert_ip" ] || san_has_entry "$san" "IP Address:${cert_ip}" || status=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
cert_pub="$(cat "$tmp_dir/cert.pub" 2>/dev/null)"
|
|
||||||
key_pub="$(cat "$tmp_dir/key.pub" 2>/dev/null)"
|
|
||||||
rm -rf "$tmp_dir"
|
rm -rf "$tmp_dir"
|
||||||
|
return "$status"
|
||||||
[ -n "$cert_pub" ] && [ "$cert_pub" = "$key_pub" ]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_default_cert() {
|
generate_default_cert() {
|
||||||
@@ -389,7 +146,7 @@ generate_default_cert() {
|
|||||||
cert_ip="$(uci -q get network.lan.ipaddr)"
|
cert_ip="$(uci -q get network.lan.ipaddr)"
|
||||||
is_ipv4 "$cert_ip" || cert_ip=""
|
is_ipv4 "$cert_ip" || cert_ip=""
|
||||||
|
|
||||||
if is_readable_regular_file "$DEFAULT_KEY_FILE" && cert_matches_san "$DEFAULT_CRT_FILE" "$cert_host" "$cert_ip" && cert_matches_key "$DEFAULT_CRT_FILE" "$DEFAULT_KEY_FILE"; then
|
if cert_pair_valid "$DEFAULT_CRT_FILE" "$DEFAULT_KEY_FILE" "$cert_host" "$cert_ip" "$CERT_RENEW_BEFORE"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -423,9 +180,7 @@ generate_default_cert() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -s "$tmp_crt" ] || [ ! -s "$tmp_key" ] || \
|
if ! cert_pair_valid "$tmp_crt" "$tmp_key" "$cert_host" "$cert_ip" "$CERT_RENEW_BEFORE" || \
|
||||||
! cert_matches_san "$tmp_crt" "$cert_host" "$cert_ip" || \
|
|
||||||
! cert_matches_key "$tmp_crt" "$tmp_key" || \
|
|
||||||
! chmod 600 "$tmp_key" || ! chmod 644 "$tmp_crt"; then
|
! chmod 600 "$tmp_key" || ! chmod 644 "$tmp_crt"; then
|
||||||
rm -f "$tmp_crt" "$tmp_key"
|
rm -f "$tmp_crt" "$tmp_key"
|
||||||
logger -t gecoosac "failed to validate default HTTPS certificate"
|
logger -t gecoosac "failed to validate default HTTPS certificate"
|
||||||
@@ -468,10 +223,8 @@ generate_default_cert() {
|
|||||||
old_key=1
|
old_key=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if mv "$tmp_key" "$DEFAULT_KEY_FILE" && \
|
# Both temporary files were validated above; rename preserves their contents.
|
||||||
mv "$tmp_crt" "$DEFAULT_CRT_FILE" && \
|
if mv "$tmp_key" "$DEFAULT_KEY_FILE" && mv "$tmp_crt" "$DEFAULT_CRT_FILE"; then
|
||||||
cert_matches_san "$DEFAULT_CRT_FILE" "$cert_host" "$cert_ip" && \
|
|
||||||
cert_matches_key "$DEFAULT_CRT_FILE" "$DEFAULT_KEY_FILE"; then
|
|
||||||
rm -rf "$backup_dir"
|
rm -rf "$backup_dir"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -601,31 +354,6 @@ ensure_secure_dir_tree() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_dir_path() {
|
|
||||||
local role="$1" path anchor
|
|
||||||
|
|
||||||
case "$role" in
|
|
||||||
upload|db|pid|file|tls) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
path="$(normalize_path "$2")" || return 1
|
|
||||||
case "$path" in
|
|
||||||
/var/run|/var/run/*)
|
|
||||||
anchor="$(readlink -f /var/run 2>/dev/null)" || return 1
|
|
||||||
[ "$anchor" = "/tmp/run" ] || return 1
|
|
||||||
printf '%s%s\n' "$anchor" "${path#/var/run}"
|
|
||||||
;;
|
|
||||||
/var|/var/*)
|
|
||||||
anchor="$(readlink -f /var 2>/dev/null)" || return 1
|
|
||||||
case "$anchor" in
|
|
||||||
/var|/tmp) printf '%s%s\n' "$anchor" "${path#/var}" ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
*) printf '%s\n' "$path" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_upload_storage_root() {
|
ensure_upload_storage_root() {
|
||||||
local path="$1" root
|
local path="$1" root
|
||||||
|
|
||||||
@@ -677,10 +405,8 @@ prepare_service() {
|
|||||||
if [ "$isonlyoneprot" = "0" ] && [ "$https" = "1" ]; then
|
if [ "$isonlyoneprot" = "0" ] && [ "$https" = "1" ]; then
|
||||||
if [ "$crt_file" = "$DEFAULT_CRT_FILE" ] && [ "$key_file" = "$DEFAULT_KEY_FILE" ]; then
|
if [ "$crt_file" = "$DEFAULT_CRT_FILE" ] && [ "$key_file" = "$DEFAULT_KEY_FILE" ]; then
|
||||||
generate_default_cert || return 1
|
generate_default_cert || return 1
|
||||||
fi
|
elif ! cert_pair_valid "$crt_file" "$key_file"; then
|
||||||
|
logger -t gecoosac "HTTPS certificate is not currently valid, or its private key cannot be read or does not match"
|
||||||
if ! cert_matches_key "$crt_file" "$key_file"; then
|
|
||||||
logger -t gecoosac "HTTPS certificate and key do not match or cannot be read"
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
. /usr/share/gecoosac/common.sh
|
||||||
|
|
||||||
changed=0
|
changed=0
|
||||||
DEFAULT_DB_DIR=/etc/gecoosac
|
|
||||||
DEFAULT_UPLOAD_DIR=/tmp/gecoosac/upload
|
|
||||||
LEGACY_UPLOAD_DIR=/etc/gecoosac/upload
|
LEGACY_UPLOAD_DIR=/etc/gecoosac/upload
|
||||||
DEFAULT_CRT_FILE=/etc/gecoosac/tls/gecoosac.crt
|
|
||||||
DEFAULT_KEY_FILE=/etc/gecoosac/tls/gecoosac.key
|
|
||||||
DEFAULT_PID_DIR=/var/run
|
|
||||||
OLD_CRT_FILE=/etc/gecoosac/tls/1.crt
|
OLD_CRT_FILE=/etc/gecoosac/tls/1.crt
|
||||||
OLD_KEY_FILE=/etc/gecoosac/tls/1.key
|
OLD_KEY_FILE=/etc/gecoosac/tls/1.key
|
||||||
CONFIG_COMPAT=2
|
CONFIG_COMPAT=2
|
||||||
@@ -26,192 +23,6 @@ set_default() {
|
|||||||
changed=1
|
changed=1
|
||||||
}
|
}
|
||||||
|
|
||||||
normalize_path() {
|
|
||||||
local path="$1"
|
|
||||||
local part normalized parent
|
|
||||||
|
|
||||||
is_abs_path "$path" || return 1
|
|
||||||
normalized="/"
|
|
||||||
path="${path#/}"
|
|
||||||
|
|
||||||
while [ -n "$path" ]; do
|
|
||||||
part="${path%%/*}"
|
|
||||||
if [ "$part" = "$path" ]; then
|
|
||||||
path=""
|
|
||||||
else
|
|
||||||
path="${path#*/}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$part" in
|
|
||||||
""|.) ;;
|
|
||||||
..)
|
|
||||||
if [ "$normalized" != "/" ]; then
|
|
||||||
parent="${normalized%/*}"
|
|
||||||
[ -n "$parent" ] || parent="/"
|
|
||||||
normalized="$parent"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*) normalized="${normalized%/}/$part" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
printf '%s\n' "$normalized"
|
|
||||||
}
|
|
||||||
|
|
||||||
is_abs_path() {
|
|
||||||
case "$1" in
|
|
||||||
/*) return 0 ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
managed_dir_path() {
|
|
||||||
local role="$1" path anchor
|
|
||||||
|
|
||||||
case "$role" in
|
|
||||||
upload|db|pid|file) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
path="$(normalize_path "$2")" || return 1
|
|
||||||
case "$path" in
|
|
||||||
/var/run|/var/run/*)
|
|
||||||
anchor="$(readlink -f /var/run 2>/dev/null)" || return 1
|
|
||||||
[ "$anchor" = "/tmp/run" ] || return 1
|
|
||||||
printf '%s%s\n' "$anchor" "${path#/var/run}"
|
|
||||||
;;
|
|
||||||
/var|/var/*)
|
|
||||||
anchor="$(readlink -f /var 2>/dev/null)" || return 1
|
|
||||||
case "$anchor" in
|
|
||||||
/var|/tmp) printf '%s%s\n' "$anchor" "${path#/var}" ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
*) printf '%s\n' "$path" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
path_has_clear_stage_component() {
|
|
||||||
local path="$1" part rest
|
|
||||||
|
|
||||||
path="$(normalize_path "$path")" || return 1
|
|
||||||
rest="${path#/}"
|
|
||||||
while [ -n "$rest" ]; do
|
|
||||||
part="${rest%%/*}"
|
|
||||||
if [ "$part" = "$rest" ]; then
|
|
||||||
rest=""
|
|
||||||
else
|
|
||||||
rest="${rest#*/}"
|
|
||||||
fi
|
|
||||||
case "$part" in
|
|
||||||
.gecoosac-clear.*) return 0 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
path_uses_clear_stage() {
|
|
||||||
local path="$1" resolved
|
|
||||||
|
|
||||||
path_has_clear_stage_component "$path" && return 0
|
|
||||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
||||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
|
||||||
path_has_clear_stage_component "$resolved" && return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
is_supported_upload_path() {
|
|
||||||
local path="$1" storage
|
|
||||||
|
|
||||||
path="$(normalize_path "$path")" || return 1
|
|
||||||
path_uses_clear_stage "$path" && return 1
|
|
||||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
|
||||||
|
|
||||||
case "$path" in
|
|
||||||
/mnt/*/gecoosac/upload)
|
|
||||||
storage="${path#/mnt/}"
|
|
||||||
storage="${storage%/gecoosac/upload}"
|
|
||||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
|
||||||
;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
is_safe_upload_dir() {
|
|
||||||
local path physical
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
physical="$(managed_dir_path upload "$path")" || return 1
|
|
||||||
is_supported_upload_path "$path" || return 1
|
|
||||||
is_supported_upload_path "$physical"
|
|
||||||
}
|
|
||||||
|
|
||||||
is_path_in_dir() {
|
|
||||||
local path root
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
root="$(normalize_path "$2")" || return 1
|
|
||||||
|
|
||||||
[ "$root" != "/" ] || return 1
|
|
||||||
[ "$path" = "$root" ] && return 0
|
|
||||||
[ "${path#"$root"/}" != "$path" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
path_has_mount() {
|
|
||||||
local root line mount_path
|
|
||||||
|
|
||||||
root="$(normalize_path "$1")" || return 2
|
|
||||||
[ -r /proc/self/mountinfo ] || return 2
|
|
||||||
|
|
||||||
while IFS= read -r line; do
|
|
||||||
mount_path="$(printf '%s\n' "$line" | cut -d ' ' -f 5)" || return 2
|
|
||||||
[ -n "$mount_path" ] || return 2
|
|
||||||
mount_path="$(printf '%b\n' "$mount_path" 2>/dev/null)" || return 2
|
|
||||||
mount_path="$(normalize_path "$mount_path")" || return 2
|
|
||||||
is_path_in_dir "$mount_path" "$root" && return 0
|
|
||||||
done < /proc/self/mountinfo
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
is_safe_db_dir() {
|
|
||||||
local path upload_root physical physical_upload_root
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
|
||||||
|
|
||||||
case "$path" in
|
|
||||||
/etc/gecoosac|/etc/gecoosac/*|/tmp/gecoosac|/tmp/gecoosac/*|/var/lib/gecoosac|/var/lib/gecoosac/*) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
is_path_in_dir "$path" "$upload_root" && return 1
|
|
||||||
physical="$(managed_dir_path db "$path")" || return 1
|
|
||||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
|
||||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
is_safe_pid_dir() {
|
|
||||||
local path upload_root physical physical_upload_root
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
|
||||||
|
|
||||||
case "$path" in
|
|
||||||
/var/run|/var/run/*|/tmp/gecoosac|/tmp/gecoosac/*) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
is_path_in_dir "$path" "$upload_root" && return 1
|
|
||||||
physical="$(managed_dir_path pid "$path")" || return 1
|
|
||||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
|
||||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize_upload_dir() {
|
normalize_upload_dir() {
|
||||||
local upload_dir normalized
|
local upload_dir normalized
|
||||||
|
|
||||||
@@ -229,38 +40,45 @@ normalize_upload_dir() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configured_path_in_legacy_upload() {
|
configured_path_in_legacy_upload() {
|
||||||
local option path resolved
|
local physical="$1" option role path
|
||||||
|
|
||||||
for option in db_dir piddir crt_file key_file; do
|
for option in db_dir piddir crt_file key_file; do
|
||||||
path="$(uci -q get "gecoosac.config.${option}")"
|
path="$(uci -q get "gecoosac.config.${option}")"
|
||||||
[ -n "$path" ] || continue
|
[ -n "$path" ] || continue
|
||||||
path="$(normalize_path "$path")" || return 0
|
case "$option" in
|
||||||
is_path_in_dir "$path" "$LEGACY_UPLOAD_DIR" && return 0
|
db_dir) role=db ;;
|
||||||
|
piddir) role=pid ;;
|
||||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
*) role="file" ;;
|
||||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 0
|
esac
|
||||||
resolved="$(normalize_path "$resolved")" || return 0
|
protected_path_in_dir "$path" "$role" "$physical" "$LEGACY_UPLOAD_DIR" "$physical"
|
||||||
is_path_in_dir "$resolved" "$LEGACY_UPLOAD_DIR" && return 0
|
case "$?" in
|
||||||
fi
|
1) ;;
|
||||||
|
*) return 0 ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_legacy_upload_dir() {
|
cleanup_legacy_upload_dir() {
|
||||||
local mount_state
|
local physical mount_state
|
||||||
|
|
||||||
[ -e "$LEGACY_UPLOAD_DIR" ] || [ -L "$LEGACY_UPLOAD_DIR" ] || return 0
|
[ -e "$LEGACY_UPLOAD_DIR" ] || [ -L "$LEGACY_UPLOAD_DIR" ] || return 0
|
||||||
if [ ! -d "$LEGACY_UPLOAD_DIR" ] || [ -L "$LEGACY_UPLOAD_DIR" ]; then
|
if [ ! -d "$LEGACY_UPLOAD_DIR" ] || [ -L "$LEGACY_UPLOAD_DIR" ]; then
|
||||||
logger -t gecoosac "refusing to remove unsafe legacy upload path: $LEGACY_UPLOAD_DIR"
|
logger -t gecoosac "refusing to remove unsafe legacy upload path: $LEGACY_UPLOAD_DIR"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if configured_path_in_legacy_upload; then
|
physical="$(readlink -f "$LEGACY_UPLOAD_DIR" 2>/dev/null)" || {
|
||||||
|
logger -t gecoosac "unable to resolve legacy upload path: $LEGACY_UPLOAD_DIR"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
physical="$(normalize_path "$physical")" || return 0
|
||||||
|
if configured_path_in_legacy_upload "$physical"; then
|
||||||
logger -t gecoosac "preserving legacy upload path referenced by configuration: $LEGACY_UPLOAD_DIR"
|
logger -t gecoosac "preserving legacy upload path referenced by configuration: $LEGACY_UPLOAD_DIR"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
path_has_mount "$LEGACY_UPLOAD_DIR"
|
path_has_mount "$physical"
|
||||||
mount_state="$?"
|
mount_state="$?"
|
||||||
case "$mount_state" in
|
case "$mount_state" in
|
||||||
0)
|
0)
|
||||||
@@ -273,7 +91,13 @@ cleanup_legacy_upload_dir() {
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
rm -rf "$LEGACY_UPLOAD_DIR" || {
|
# Apply the same ownership and ancestor-symlink policy as RPC cleanup.
|
||||||
|
# Relocated legacy directories are preserved for manual review.
|
||||||
|
if ! is_secure_upload_dir "$LEGACY_UPLOAD_DIR" || [ "$physical" != "$LEGACY_UPLOAD_DIR" ]; then
|
||||||
|
logger -t gecoosac "refusing to remove unsafe legacy upload path: $LEGACY_UPLOAD_DIR"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
rm -rf "$physical" || {
|
||||||
logger -t gecoosac "failed to remove legacy upload path: $LEGACY_UPLOAD_DIR"
|
logger -t gecoosac "failed to remove legacy upload path: $LEGACY_UPLOAD_DIR"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -310,7 +134,7 @@ is_managed_cert_path() {
|
|||||||
|
|
||||||
is_regular_file_or_absent() {
|
is_regular_file_or_absent() {
|
||||||
[ ! -e "$1" ] && [ ! -L "$1" ] && return 0
|
[ ! -e "$1" ] && [ ! -L "$1" ] && return 0
|
||||||
[ -f "$1" ] && [ ! -L "$1" ]
|
[ -f "$1" ] && [ ! -L "$1" ] && is_secure_upload_dir "${1%/*}"
|
||||||
}
|
}
|
||||||
|
|
||||||
migrate_default_certificates() {
|
migrate_default_certificates() {
|
||||||
|
|||||||
@@ -0,0 +1,282 @@
|
|||||||
|
# Shared path policy for the service, configuration migration and LuCI RPC.
|
||||||
|
|
||||||
|
DEFAULT_DB_DIR=/etc/gecoosac
|
||||||
|
DEFAULT_UPLOAD_DIR=/tmp/gecoosac/upload
|
||||||
|
DEFAULT_CRT_FILE=/etc/gecoosac/tls/gecoosac.crt
|
||||||
|
DEFAULT_KEY_FILE=/etc/gecoosac/tls/gecoosac.key
|
||||||
|
DEFAULT_PID_DIR=/var/run
|
||||||
|
|
||||||
|
is_abs_path() {
|
||||||
|
case "$1" in
|
||||||
|
/*) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_path() {
|
||||||
|
local path="$1"
|
||||||
|
local part normalized parent
|
||||||
|
|
||||||
|
is_abs_path "$path" || return 1
|
||||||
|
normalized="/"
|
||||||
|
path="${path#/}"
|
||||||
|
|
||||||
|
while [ -n "$path" ]; do
|
||||||
|
part="${path%%/*}"
|
||||||
|
if [ "$part" = "$path" ]; then
|
||||||
|
path=""
|
||||||
|
else
|
||||||
|
path="${path#*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$part" in
|
||||||
|
""|.) ;;
|
||||||
|
..)
|
||||||
|
if [ "$normalized" != "/" ]; then
|
||||||
|
parent="${normalized%/*}"
|
||||||
|
[ -n "$parent" ] || parent="/"
|
||||||
|
normalized="$parent"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) normalized="${normalized%/}/$part" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "$normalized"
|
||||||
|
}
|
||||||
|
|
||||||
|
managed_dir_path() {
|
||||||
|
local role="$1" path anchor
|
||||||
|
|
||||||
|
case "$role" in
|
||||||
|
upload|db|pid|file|tls) ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
path="$(normalize_path "$2")" || return 1
|
||||||
|
case "$path" in
|
||||||
|
/var/run|/var/run/*)
|
||||||
|
anchor="$(readlink -f /var/run 2>/dev/null)" || return 1
|
||||||
|
[ "$anchor" = "/tmp/run" ] || return 1
|
||||||
|
printf '%s%s\n' "$anchor" "${path#/var/run}"
|
||||||
|
;;
|
||||||
|
/var|/var/*)
|
||||||
|
anchor="$(readlink -f /var 2>/dev/null)" || return 1
|
||||||
|
case "$anchor" in
|
||||||
|
/var|/tmp) printf '%s%s\n' "$anchor" "${path#/var}" ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*) printf '%s\n' "$path" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
path_has_clear_stage_component() {
|
||||||
|
local path="$1" part rest
|
||||||
|
|
||||||
|
path="$(normalize_path "$path")" || return 1
|
||||||
|
rest="${path#/}"
|
||||||
|
while [ -n "$rest" ]; do
|
||||||
|
part="${rest%%/*}"
|
||||||
|
if [ "$part" = "$rest" ]; then
|
||||||
|
rest=""
|
||||||
|
else
|
||||||
|
rest="${rest#*/}"
|
||||||
|
fi
|
||||||
|
case "$part" in
|
||||||
|
.gecoosac-clear.*) return 0 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
path_uses_clear_stage() {
|
||||||
|
local path="$1" resolved
|
||||||
|
|
||||||
|
path_has_clear_stage_component "$path" && return 0
|
||||||
|
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||||
|
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
||||||
|
path_has_clear_stage_component "$resolved" && return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
is_supported_upload_path() {
|
||||||
|
local path="$1" storage
|
||||||
|
|
||||||
|
path="$(normalize_path "$path")" || return 1
|
||||||
|
path_uses_clear_stage "$path" && return 1
|
||||||
|
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
||||||
|
|
||||||
|
case "$path" in
|
||||||
|
/mnt/*/gecoosac/upload)
|
||||||
|
storage="${path#/mnt/}"
|
||||||
|
storage="${storage%/gecoosac/upload}"
|
||||||
|
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
||||||
|
;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
is_safe_upload_dir() {
|
||||||
|
local path physical
|
||||||
|
|
||||||
|
path="$(normalize_path "$1")" || return 1
|
||||||
|
physical="$(managed_dir_path upload "$path")" || return 1
|
||||||
|
is_supported_upload_path "$path" || return 1
|
||||||
|
is_supported_upload_path "$physical"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_path_in_dir() {
|
||||||
|
local path root
|
||||||
|
|
||||||
|
path="$(normalize_path "$1")" || return 1
|
||||||
|
root="$(normalize_path "$2")" || return 1
|
||||||
|
|
||||||
|
[ "$root" != "/" ] || return 1
|
||||||
|
[ "$path" = "$root" ] && return 0
|
||||||
|
[ "${path#"$root"/}" != "$path" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
is_secure_dir() {
|
||||||
|
local allow_sticky="$2" owner permissions metadata
|
||||||
|
|
||||||
|
[ -d "$1" ] && [ ! -L "$1" ] || return 1
|
||||||
|
metadata="$(ls -ldn "$1" 2>/dev/null)" || return 1
|
||||||
|
set -- $metadata
|
||||||
|
permissions="$1"
|
||||||
|
owner="$3"
|
||||||
|
[ "$owner" = "0" ] || return 1
|
||||||
|
case "$permissions" in
|
||||||
|
d?????????) ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$(printf '%s' "$permissions" | cut -c6)" = "w" ] || \
|
||||||
|
[ "$(printf '%s' "$permissions" | cut -c9)" = "w" ]; then
|
||||||
|
[ "$allow_sticky" = "1" ] && [ "$(printf '%s' "$permissions" | cut -c10)" = "t" ] || return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_secure_upload_dir() {
|
||||||
|
local path current part rest
|
||||||
|
|
||||||
|
path="$(normalize_path "$1")" || return 1
|
||||||
|
[ -d "$path" ] && [ ! -L "$path" ] || return 1
|
||||||
|
current="/"
|
||||||
|
rest="${path#/}"
|
||||||
|
|
||||||
|
while [ -n "$rest" ]; do
|
||||||
|
part="${rest%%/*}"
|
||||||
|
if [ "$part" = "$rest" ]; then
|
||||||
|
rest=""
|
||||||
|
else
|
||||||
|
rest="${rest#*/}"
|
||||||
|
fi
|
||||||
|
current="${current%/}/$part"
|
||||||
|
case "$current" in
|
||||||
|
/tmp) is_secure_dir "$current" 1 || return 1 ;;
|
||||||
|
*) is_secure_dir "$current" || return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
is_safe_db_dir() {
|
||||||
|
local path upload_root physical physical_upload_root
|
||||||
|
|
||||||
|
path="$(normalize_path "$1")" || return 1
|
||||||
|
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
||||||
|
|
||||||
|
case "$path" in
|
||||||
|
/etc/gecoosac|/etc/gecoosac/*|/tmp/gecoosac|/tmp/gecoosac/*|/var/lib/gecoosac|/var/lib/gecoosac/*) ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
is_path_in_dir "$path" "$upload_root" && return 1
|
||||||
|
physical="$(managed_dir_path db "$path")" || return 1
|
||||||
|
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
||||||
|
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
is_safe_pid_dir() {
|
||||||
|
local path upload_root physical physical_upload_root
|
||||||
|
|
||||||
|
path="$(normalize_path "$1")" || return 1
|
||||||
|
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
||||||
|
|
||||||
|
case "$path" in
|
||||||
|
/var/run|/var/run/*|/tmp/gecoosac|/tmp/gecoosac/*) ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
is_path_in_dir "$path" "$upload_root" && return 1
|
||||||
|
physical="$(managed_dir_path pid "$path")" || return 1
|
||||||
|
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
||||||
|
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
path_has_mount() {
|
||||||
|
local root mount_id parent_id device mount_root mount_path rest
|
||||||
|
|
||||||
|
root="$(normalize_path "$1")" || return 2
|
||||||
|
[ -r /proc/self/mountinfo ] || return 2
|
||||||
|
|
||||||
|
while IFS=' ' read -r mount_id parent_id device mount_root mount_path rest; do
|
||||||
|
[ -n "$mount_path" ] && [ -n "$rest" ] || return 2
|
||||||
|
mount_path="$(printf '%b\n' "$mount_path" 2>/dev/null)" || return 2
|
||||||
|
mount_path="$(normalize_path "$mount_path")" || return 2
|
||||||
|
is_path_in_dir "$mount_path" "$root" && return 0
|
||||||
|
done < /proc/self/mountinfo
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# The checked root must be a resolved, validated directory. Return 0 for a
|
||||||
|
# protected path, 1 for an unrelated path, or 2 when validation is impossible.
|
||||||
|
# Keep the original spelling when resolving symlinks: link/../file and its
|
||||||
|
# lexical normalization can refer to different files.
|
||||||
|
protected_path_in_dir() {
|
||||||
|
local raw_path="$1" role="$2" checked_root="$3"
|
||||||
|
local live_logical="${4:-$3}" live_physical="${5:-$3}"
|
||||||
|
local path="$1" physical real_path suffix mapped
|
||||||
|
|
||||||
|
[ -n "$path" ] || return 1
|
||||||
|
|
||||||
|
path="$(normalize_path "$path")" || return 2
|
||||||
|
physical="$(managed_dir_path "$role" "$path")" || return 2
|
||||||
|
checked_root="$(normalize_path "$checked_root")" || return 2
|
||||||
|
live_logical="$(normalize_path "$live_logical")" || return 2
|
||||||
|
live_physical="$(normalize_path "$live_physical")" || return 2
|
||||||
|
|
||||||
|
is_path_in_dir "$path" "$checked_root" && return 0
|
||||||
|
is_path_in_dir "$physical" "$checked_root" && return 0
|
||||||
|
real_path="$(readlink -f "$raw_path" 2>/dev/null)" || return 2
|
||||||
|
[ -n "$real_path" ] || return 2
|
||||||
|
real_path="$(normalize_path "$real_path")" || return 2
|
||||||
|
is_path_in_dir "$real_path" "$checked_root" && return 0
|
||||||
|
|
||||||
|
[ "$checked_root" != "$live_physical" ] || {
|
||||||
|
is_path_in_dir "$path" "$live_logical" && return 0
|
||||||
|
is_path_in_dir "$physical" "$live_physical" && return 0
|
||||||
|
if [ -n "$real_path" ] && is_path_in_dir "$real_path" "$live_physical"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
suffix=
|
||||||
|
if is_path_in_dir "$path" "$live_logical"; then
|
||||||
|
suffix="${path#"$live_logical"}"
|
||||||
|
elif is_path_in_dir "$physical" "$live_physical"; then
|
||||||
|
suffix="${physical#"$live_physical"}"
|
||||||
|
elif [ -n "$real_path" ] && is_path_in_dir "$real_path" "$live_physical"; then
|
||||||
|
suffix="${real_path#"$live_physical"}"
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
mapped="${checked_root%/}${suffix}"
|
||||||
|
[ -e "$mapped" ] || [ -L "$mapped" ]
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=+luci-base +logd +jsonfilter
|
|||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
PKG_VERSION:=0.1.41
|
PKG_VERSION:=0.1.41
|
||||||
PKG_RELEASE:=13
|
PKG_RELEASE:=14
|
||||||
|
|
||||||
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
|
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
|
||||||
ifdef SOURCE_DATE_EPOCH
|
ifdef SOURCE_DATE_EPOCH
|
||||||
|
|||||||
+443
@@ -0,0 +1,443 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# Copyright 2025-2026 Lucas Albers <lucas.b.albers@gmail.com>
|
||||||
|
#
|
||||||
|
# Layer 1 adaptive poll cap (#306). Sourced by rpcd/fwlive.
|
||||||
|
# Always on unless test/triage override (no UCI / no product config):
|
||||||
|
# FWLIVE_ADAPTIVE=0|false|off|no
|
||||||
|
# or sentinel ${FWLIVE_ADAPTIVE_OFF_FILE:-<state-dir>/fwlive-adaptive-off}
|
||||||
|
# (default under /var/run next to state — not world-writable /tmp).
|
||||||
|
#
|
||||||
|
# State: ${FWLIVE_ADAPTIVE_STATE_FILE:-/var/run/fwlive-state.json}
|
||||||
|
# Lock: sibling ${FWLIVE_ADAPTIVE_LOCK_FILE:-$STATE.lock} — never the JSON
|
||||||
|
# inode (atomic mv replaces that inode; locking it would not serialize writers).
|
||||||
|
# Lock covers only short update — never held across ubus/filter work.
|
||||||
|
# Hot-path budget: ≤1 flock exec per update (release by closing the fd when the
|
||||||
|
# subshell exits — no flock -u). /proc/uptime + state I/O via shell builtins/
|
||||||
|
# redirects (no sed/cat/jsonfilter on the adaptive path). Fail-open on missing
|
||||||
|
# flock, lock busy, or corrupt state. Lock-busy ⇒ unlocked last-writer-wins is
|
||||||
|
# acceptable (state stays one valid JSON line; ordering is not guaranteed).
|
||||||
|
# Failed ubus log.read must NOT call record() — a ~0 ms failure is not "cold"
|
||||||
|
# health and must not clear an existing hot/shed cap (#329 Hermes Q1).
|
||||||
|
# Outside the measured duration interval: plan (pre), record/merge (post).
|
||||||
|
# messages_received is 0 in Layer 1 — do not ash-scan the filter JSON after
|
||||||
|
# end_cs (Grok #329 P1); accurate count belongs in the filter or a later layer.
|
||||||
|
|
||||||
|
FWLIVE_ADAPTIVE_STATE_FILE="${FWLIVE_ADAPTIVE_STATE_FILE:-/var/run/fwlive-state.json}"
|
||||||
|
# Optional overrides; when unset, lock/off paths are siblings of the current state.
|
||||||
|
# Do not bake STATE into LOCK/OFF at source time — tests may relocate STATE after source.
|
||||||
|
|
||||||
|
# Bucket thresholds (processing duration, ms).
|
||||||
|
FWLIVE_ADAPTIVE_COLD_MS=100
|
||||||
|
FWLIVE_ADAPTIVE_COOL_MS=200
|
||||||
|
FWLIVE_ADAPTIVE_WARM_MS=800
|
||||||
|
FWLIVE_ADAPTIVE_HOT_EXIT_MS=500
|
||||||
|
FWLIVE_ADAPTIVE_COOL_CAP=250
|
||||||
|
FWLIVE_ADAPTIVE_HOT_FLOOR=250
|
||||||
|
FWLIVE_ADAPTIVE_WARM_MIN=50
|
||||||
|
FWLIVE_ADAPTIVE_COOLDOWN_WARM_CS=300
|
||||||
|
FWLIVE_ADAPTIVE_COOLDOWN_HOT_CS=600
|
||||||
|
|
||||||
|
fwlive_adaptive_lock_path() {
|
||||||
|
if [ -n "${FWLIVE_ADAPTIVE_LOCK_FILE:-}" ]; then
|
||||||
|
printf '%s\n' "$FWLIVE_ADAPTIVE_LOCK_FILE"
|
||||||
|
else
|
||||||
|
printf '%s\n' "${FWLIVE_ADAPTIVE_STATE_FILE}.lock"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive_off_path() {
|
||||||
|
if [ -n "${FWLIVE_ADAPTIVE_OFF_FILE:-}" ]; then
|
||||||
|
printf '%s\n' "$FWLIVE_ADAPTIVE_OFF_FILE"
|
||||||
|
else
|
||||||
|
_dir=${FWLIVE_ADAPTIVE_STATE_FILE%/*}
|
||||||
|
[ "$_dir" = "$FWLIVE_ADAPTIVE_STATE_FILE" ] && _dir=/var/run
|
||||||
|
printf '%s\n' "$_dir/fwlive-adaptive-off"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive_enabled() {
|
||||||
|
case "${FWLIVE_ADAPTIVE:-1}" in
|
||||||
|
0|false|off|no|FALSE|OFF|NO) return 1 ;;
|
||||||
|
esac
|
||||||
|
[ -e "$(fwlive_adaptive_off_path)" ] && return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive_atoi() {
|
||||||
|
# Decimal int from digit string — avoids $((08)) octal errors under ash.
|
||||||
|
_s=$1
|
||||||
|
_n=0
|
||||||
|
case "$_s" in ''|*[!0-9]*) printf '0\n'; return 0 ;; esac
|
||||||
|
while [ -n "$_s" ]; do
|
||||||
|
_d=${_s%"${_s#?}"}
|
||||||
|
_s=${_s#?}
|
||||||
|
_n=$((_n * 10 + _d))
|
||||||
|
done
|
||||||
|
printf '%s\n' "$_n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive_clock_cs() {
|
||||||
|
# /proc/uptime centiseconds — BusyBox date lacks %N. Builtin read only.
|
||||||
|
_up=
|
||||||
|
read -r _up _ </proc/uptime 2>/dev/null || {
|
||||||
|
printf '%s\n' 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
_sec=${_up%.*}
|
||||||
|
_frac=${_up#*.}
|
||||||
|
[ "$_frac" = "$_up" ] && _frac=0
|
||||||
|
# At most two decimal digits (centiseconds), no cut(1).
|
||||||
|
case "$_frac" in
|
||||||
|
'') _frac=0 ;;
|
||||||
|
?) _frac="${_frac}0" ;;
|
||||||
|
??) ;;
|
||||||
|
*)
|
||||||
|
_a=${_frac%${_frac#?}}
|
||||||
|
_r=${_frac#?}
|
||||||
|
_b=${_r%${_r#?}}
|
||||||
|
_frac="${_a}${_b}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
_sec=$(fwlive_adaptive_atoi "$_sec")
|
||||||
|
_frac=$(fwlive_adaptive_atoi "$_frac")
|
||||||
|
printf '%s\n' $((_sec * 100 + _frac))
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive_state_dir_ok() {
|
||||||
|
_path=$FWLIVE_ADAPTIVE_STATE_FILE
|
||||||
|
_dir=${_path%/*}
|
||||||
|
[ -n "$_dir" ] || return 1
|
||||||
|
[ "$_dir" = "$_path" ] && return 1
|
||||||
|
[ -L "$_dir" ] && return 1
|
||||||
|
[ -d "$_dir" ] || return 1
|
||||||
|
# shellcheck disable=SC3067
|
||||||
|
[ -O "$_dir" ] || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract a decimal integer field from a one-line JSON object (builtin only).
|
||||||
|
# Prints default ($2) when missing/corrupt.
|
||||||
|
fwlive_adaptive_json_int() {
|
||||||
|
_blob=$1
|
||||||
|
_key=$2
|
||||||
|
_def=$3
|
||||||
|
case "$_blob" in
|
||||||
|
*"\"$_key\""*) ;;
|
||||||
|
*) printf '%s\n' "$_def"; return 0 ;;
|
||||||
|
esac
|
||||||
|
_rest=${_blob#*"\"$_key\""}
|
||||||
|
_rest=${_rest#*:}
|
||||||
|
while case "$_rest" in ' '*) true;; *) false;; esac; do
|
||||||
|
_rest=${_rest# }
|
||||||
|
done
|
||||||
|
_num=
|
||||||
|
while :; do
|
||||||
|
case "$_rest" in
|
||||||
|
'') break ;;
|
||||||
|
esac
|
||||||
|
_c=${_rest%"${_rest#?}"}
|
||||||
|
case "$_c" in
|
||||||
|
[0-9]) _num="${_num}${_c}"; _rest=${_rest#?} ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
case "$_num" in
|
||||||
|
''|*[!0-9]*) printf '%s\n' "$_def" ;;
|
||||||
|
*) printf '%s\n' "$_num" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract a lowercase alpha string field.
|
||||||
|
fwlive_adaptive_json_str() {
|
||||||
|
_blob=$1
|
||||||
|
_key=$2
|
||||||
|
_def=$3
|
||||||
|
case "$_blob" in
|
||||||
|
*"\"$_key\""*) ;;
|
||||||
|
*) printf '%s\n' "$_def"; return 0 ;;
|
||||||
|
esac
|
||||||
|
_rest=${_blob#*"\"$_key\""}
|
||||||
|
_rest=${_rest#*:}
|
||||||
|
while case "$_rest" in ' '*) true;; *) false;; esac; do
|
||||||
|
_rest=${_rest# }
|
||||||
|
done
|
||||||
|
case "$_rest" in
|
||||||
|
\"*) _rest=${_rest#\"}; _val=${_rest%%\"*} ;;
|
||||||
|
*) printf '%s\n' "$_def"; return 0 ;;
|
||||||
|
esac
|
||||||
|
case "$_val" in
|
||||||
|
*[!a-z]*|'') printf '%s\n' "$_def" ;;
|
||||||
|
*) printf '%s\n' "$_val" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print: duration_ms limit bucket warm_halved shed completed_cs
|
||||||
|
fwlive_adaptive_read_state() {
|
||||||
|
_d=0
|
||||||
|
_l="${POLL_LINES_MAX:-2000}"
|
||||||
|
_b=cold
|
||||||
|
_w=0
|
||||||
|
_s=0
|
||||||
|
_c=0
|
||||||
|
_raw=
|
||||||
|
if [ -f "$FWLIVE_ADAPTIVE_STATE_FILE" ]; then
|
||||||
|
IFS= read -r _raw <"$FWLIVE_ADAPTIVE_STATE_FILE" || _raw=
|
||||||
|
fi
|
||||||
|
if [ -n "$_raw" ]; then
|
||||||
|
_d=$(fwlive_adaptive_json_int "$_raw" duration_ms 0)
|
||||||
|
_l=$(fwlive_adaptive_json_int "$_raw" limit "${POLL_LINES_MAX:-2000}")
|
||||||
|
_b=$(fwlive_adaptive_json_str "$_raw" bucket cold)
|
||||||
|
_w=$(fwlive_adaptive_json_int "$_raw" warm_halved 0)
|
||||||
|
_s=$(fwlive_adaptive_json_int "$_raw" shed 0)
|
||||||
|
_c=$(fwlive_adaptive_json_int "$_raw" completed_cs 0)
|
||||||
|
case "$_w" in 0|1) ;; *) _w=0 ;; esac
|
||||||
|
case "$_s" in 0|1) ;; *) _s=0 ;; esac
|
||||||
|
fi
|
||||||
|
printf '%s %s %s %s %s %s\n' "$_d" "$_l" "$_b" "$_w" "$_s" "$_c"
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive_write_state() {
|
||||||
|
_d=$1
|
||||||
|
_l=$2
|
||||||
|
_b=$3
|
||||||
|
_w=$4
|
||||||
|
_s=$5
|
||||||
|
_c=$6
|
||||||
|
fwlive_adaptive_state_dir_ok || return 0
|
||||||
|
# Refuse to follow a symlinked state path (same discipline as logging.lock).
|
||||||
|
[ -L "$FWLIVE_ADAPTIVE_STATE_FILE" ] && return 0
|
||||||
|
_tmp="${FWLIVE_ADAPTIVE_STATE_FILE}.tmp.$$"
|
||||||
|
umask 077
|
||||||
|
printf '{"duration_ms":%s,"limit":%s,"bucket":"%s","warm_halved":%s,"shed":%s,"completed_cs":%s}\n' \
|
||||||
|
"$_d" "$_l" "$_b" "$_w" "$_s" "$_c" >"$_tmp" 2>/dev/null || {
|
||||||
|
rm -f "$_tmp"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
mv -f "$_tmp" "$FWLIVE_ADAPTIVE_STATE_FILE" 2>/dev/null || rm -f "$_tmp"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# One non-blocking flock on the persistent sibling lock file; fail-open if
|
||||||
|
# missing/busy. Release by exiting the subshell (closes fd 9) — no flock -u.
|
||||||
|
# Busy ⇒ run unlocked: last-writer-wins is acceptable (valid one-line JSON;
|
||||||
|
# ordering under contention is not guaranteed).
|
||||||
|
# Lock-open failure (redirection) also fail-opens unlocked so record() still runs.
|
||||||
|
fwlive_adaptive_with_lock() {
|
||||||
|
if ! command -v flock >/dev/null 2>&1; then
|
||||||
|
"$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
fwlive_adaptive_state_dir_ok || {
|
||||||
|
"$@"
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
_lock=$(fwlive_adaptive_lock_path)
|
||||||
|
# Symlinked or non-file lock path: do not create/follow; fail open unlocked.
|
||||||
|
if [ -L "$_lock" ] || [ -d "$_lock" ]; then
|
||||||
|
"$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
# Create lock at 0600 (logging.lock #167 — world-readable fd can take LOCK_EX).
|
||||||
|
if [ ! -e "$_lock" ]; then
|
||||||
|
if ! ( umask 077; : >"$_lock" ) 2>/dev/null; then
|
||||||
|
"$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
elif [ ! -w "$_lock" ]; then
|
||||||
|
"$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
(
|
||||||
|
if flock -n 9; then
|
||||||
|
"$@"
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
# Lock busy — fail open (unlocked).
|
||||||
|
"$@"
|
||||||
|
) 9>>"$_lock"
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive_bucket_for_ms() {
|
||||||
|
_ms=$1
|
||||||
|
case "$_ms" in ''|*[!0-9]*) _ms=0 ;; esac
|
||||||
|
if [ "$_ms" -lt "$FWLIVE_ADAPTIVE_COLD_MS" ]; then
|
||||||
|
printf '%s\n' cold
|
||||||
|
elif [ "$_ms" -lt "$FWLIVE_ADAPTIVE_COOL_MS" ]; then
|
||||||
|
printf '%s\n' cool
|
||||||
|
elif [ "$_ms" -le "$FWLIVE_ADAPTIVE_WARM_MS" ]; then
|
||||||
|
printf '%s\n' warm
|
||||||
|
else
|
||||||
|
printf '%s\n' hot
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Args: requested_limit duration_ms prev_limit prev_bucket prev_warm prev_completed_cs
|
||||||
|
# Prints next limit.
|
||||||
|
fwlive_adaptive_compute_limit() {
|
||||||
|
_req=$1
|
||||||
|
_ms=$2
|
||||||
|
_prev_l=$3
|
||||||
|
_prev_b=$4
|
||||||
|
_prev_w=$5
|
||||||
|
_prev_c=$6
|
||||||
|
_max="${POLL_LINES_MAX:-2000}"
|
||||||
|
_now=$(fwlive_adaptive_clock_cs)
|
||||||
|
_bucket=$(fwlive_adaptive_bucket_for_ms "$_ms")
|
||||||
|
_limit=$_max
|
||||||
|
_cd=0
|
||||||
|
|
||||||
|
case "$_prev_b" in
|
||||||
|
hot) _cd=$FWLIVE_ADAPTIVE_COOLDOWN_HOT_CS ;;
|
||||||
|
warm) _cd=$FWLIVE_ADAPTIVE_COOLDOWN_WARM_CS ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$_cd" -gt 0 ] && [ "$_prev_c" -gt 0 ] && \
|
||||||
|
[ "$((_now - _prev_c))" -lt "$_cd" ]; then
|
||||||
|
if [ "$_ms" -le "$FWLIVE_ADAPTIVE_HOT_EXIT_MS" ] && \
|
||||||
|
[ "$_bucket" != hot ]; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
_limit=$_prev_l
|
||||||
|
[ "$_limit" -gt "$_req" ] && _limit=$_req
|
||||||
|
[ "$_limit" -gt "$_max" ] && _limit=$_max
|
||||||
|
[ "$_limit" -lt 1 ] && _limit=1
|
||||||
|
printf '%s\n' "$_limit"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$_bucket" in
|
||||||
|
cold) _limit=$_max ;;
|
||||||
|
cool) _limit=$FWLIVE_ADAPTIVE_COOL_CAP ;;
|
||||||
|
warm)
|
||||||
|
if [ "$_prev_w" = 1 ] && [ "$_prev_b" = warm ]; then
|
||||||
|
_limit=$_prev_l
|
||||||
|
else
|
||||||
|
_base=$_prev_l
|
||||||
|
[ "$_base" -lt 1 ] && _base=$_max
|
||||||
|
_limit=$((_base / 2))
|
||||||
|
[ "$_limit" -lt "$FWLIVE_ADAPTIVE_WARM_MIN" ] && \
|
||||||
|
_limit=$FWLIVE_ADAPTIVE_WARM_MIN
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
hot) _limit=$FWLIVE_ADAPTIVE_HOT_FLOOR ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ "$_limit" -gt "$_req" ] && _limit=$_req
|
||||||
|
[ "$_limit" -gt "$_max" ] && _limit=$_max
|
||||||
|
[ "$_limit" -lt 1 ] && _limit=1
|
||||||
|
printf '%s\n' "$_limit"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Apply cap using *previous* poll duration. Prints: lines shed_flag bucket
|
||||||
|
# Unlocked read (stale OK) — flock reserved for record().
|
||||||
|
fwlive_adaptive_plan() {
|
||||||
|
_req=$1
|
||||||
|
_max="${POLL_LINES_MAX:-2000}"
|
||||||
|
if ! fwlive_adaptive_enabled; then
|
||||||
|
_l=$_req
|
||||||
|
[ "$_l" -gt "$_max" ] && _l=$_max
|
||||||
|
printf '%s 0 off\n' "$_l"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# shellcheck disable=SC2046
|
||||||
|
set -- $(fwlive_adaptive_read_state)
|
||||||
|
_d=$1
|
||||||
|
_prev_l=$2
|
||||||
|
_b=$3
|
||||||
|
_prev_w=$4
|
||||||
|
_s=$5
|
||||||
|
_prev_c=$6
|
||||||
|
_l=$(fwlive_adaptive_compute_limit "$_req" "$_d" "$_prev_l" "$_b" "$_prev_w" "$_prev_c")
|
||||||
|
_nb=$(fwlive_adaptive_bucket_for_ms "$_d")
|
||||||
|
_shed=0
|
||||||
|
[ "$_nb" = hot ] && _shed=1
|
||||||
|
[ "$_s" = 1 ] && [ "$_b" = hot ] && _shed=1
|
||||||
|
printf '%s %s %s\n' "$_l" "$_shed" "$_nb"
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive__record_body() {
|
||||||
|
_ms=$1
|
||||||
|
_served=$2
|
||||||
|
# shellcheck disable=SC2046
|
||||||
|
set -- $(fwlive_adaptive_read_state)
|
||||||
|
_prev_l=$2
|
||||||
|
_prev_b=$3
|
||||||
|
_prev_w=$4
|
||||||
|
_prev_c=$6
|
||||||
|
_bucket=$(fwlive_adaptive_bucket_for_ms "$_ms")
|
||||||
|
_now=$(fwlive_adaptive_clock_cs)
|
||||||
|
_limit=$(fwlive_adaptive_compute_limit "$_served" "$_ms" "$_prev_l" "$_prev_b" "$_prev_w" "$_prev_c")
|
||||||
|
_warm=0
|
||||||
|
_shed=0
|
||||||
|
case "$_bucket" in
|
||||||
|
warm) _warm=1 ;;
|
||||||
|
hot) _shed=1 ;;
|
||||||
|
esac
|
||||||
|
fwlive_adaptive_write_state "$_ms" "$_limit" "$_bucket" "$_warm" "$_shed" "$_now"
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive_record() {
|
||||||
|
_ms=$1
|
||||||
|
_served=$2
|
||||||
|
fwlive_adaptive_enabled || return 0
|
||||||
|
fwlive_adaptive_with_lock fwlive_adaptive__record_body "$_ms" "$_served"
|
||||||
|
}
|
||||||
|
|
||||||
|
fwlive_adaptive_is_hot() {
|
||||||
|
fwlive_adaptive_enabled || return 1
|
||||||
|
# shellcheck disable=SC2046
|
||||||
|
set -- $(fwlive_adaptive_read_state)
|
||||||
|
[ "$5" = 1 ] && return 0
|
||||||
|
[ "$3" = hot ] && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Merge adaptive siblings into a JSON object ending with }.
|
||||||
|
# Args: json_body shed_flag limit truncated messages_received
|
||||||
|
fwlive_adaptive_merge_reply() {
|
||||||
|
_body=$1
|
||||||
|
_shed=$2
|
||||||
|
_limit=$3
|
||||||
|
_trunc=$4
|
||||||
|
_msgs=$5
|
||||||
|
_adapt=1
|
||||||
|
fwlive_adaptive_enabled || _adapt=0
|
||||||
|
case "$_body" in
|
||||||
|
*\}) ;;
|
||||||
|
*) printf '%s' "$_body"; return 0 ;;
|
||||||
|
esac
|
||||||
|
_base=${_body%\}}
|
||||||
|
if [ "$_adapt" = 0 ]; then
|
||||||
|
printf '%s,"adaptive":0,"messages_received":%s}' "$_base" "$_msgs"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "$_shed" = 1 ]; then
|
||||||
|
printf '%s,"adaptive":1,"messages_received":%s,"truncated":%s,"shed":{"level":"hot","limit":%s}}' \
|
||||||
|
"$_base" "$_msgs" "$_trunc" "$_limit"
|
||||||
|
else
|
||||||
|
printf '%s,"adaptive":1,"messages_received":%s,"truncated":%s}' \
|
||||||
|
"$_base" "$_msgs" "$_trunc"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Count log[*] via "msg" keys — helper for tests / future filter-side count.
|
||||||
|
# NOT called on the poll hot path (Grok #329 P1): ash-scanning a 2000-entry
|
||||||
|
# reply after end_cs is unmeasured overhead that cannot shed itself.
|
||||||
|
fwlive_adaptive_count_log() {
|
||||||
|
_json=$1
|
||||||
|
_n=0
|
||||||
|
_rest=$_json
|
||||||
|
while :; do
|
||||||
|
case "$_rest" in
|
||||||
|
*'"msg"'*)
|
||||||
|
_n=$((_n + 1))
|
||||||
|
_rest=${_rest#*\"msg\"}
|
||||||
|
;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
printf '%s\n' "$_n"
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ FW4_TAG='!fw4: '
|
|||||||
LIBEXEC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
LIBEXEC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
FILTER_SH="$LIBEXEC_DIR/fwlive-log-filter.sh"
|
FILTER_SH="$LIBEXEC_DIR/fwlive-log-filter.sh"
|
||||||
LOGGING_SH="$LIBEXEC_DIR/fwlive-logging.sh"
|
LOGGING_SH="$LIBEXEC_DIR/fwlive-logging.sh"
|
||||||
|
ADAPTIVE_SH="$LIBEXEC_DIR/fwlive-adaptive-cap.sh"
|
||||||
# Max reverse-DNS lookups per resolve call (~32 names keeps nslookup load bounded on small routers).
|
# Max reverse-DNS lookups per resolve call (~32 names keeps nslookup load bounded on small routers).
|
||||||
RESOLVE_MAX=32
|
RESOLVE_MAX=32
|
||||||
# Per-lookup nslookup timeout (integer seconds — BusyBox `timeout` truncates
|
# Per-lookup nslookup timeout (integer seconds — BusyBox `timeout` truncates
|
||||||
@@ -44,6 +45,8 @@ RULESMAP_IPTABLES_FILE='/tmp/rulesmap'
|
|||||||
# json_escape lives in fwlive-logging.sh so prerm can source that file alone (#222).
|
# json_escape lives in fwlive-logging.sh so prerm can source that file alone (#222).
|
||||||
# shellcheck disable=SC1090,SC1091 # LOGGING_SH resolves to sibling ../fwlive-logging.sh
|
# shellcheck disable=SC1090,SC1091 # LOGGING_SH resolves to sibling ../fwlive-logging.sh
|
||||||
. "$LOGGING_SH"
|
. "$LOGGING_SH"
|
||||||
|
# shellcheck disable=SC1090,SC1091
|
||||||
|
. "$ADAPTIVE_SH"
|
||||||
|
|
||||||
slug_key() {
|
slug_key() {
|
||||||
echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' _' '--'
|
echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' _' '--'
|
||||||
@@ -425,12 +428,19 @@ poll_lines_from_input() {
|
|||||||
|
|
||||||
fetch_firewall_logs() {
|
fetch_firewall_logs() {
|
||||||
input="$1"
|
input="$1"
|
||||||
lines=$(poll_lines_from_input "$input")
|
requested=$(poll_lines_from_input "$input")
|
||||||
# lines is numeric-clamped; stream/oneshot are fixed (not caller-controlled).
|
# shellcheck disable=SC2046
|
||||||
# A dead logd must not look like an empty firewall table (#303): return a
|
set -- $(fwlive_adaptive_plan "$requested")
|
||||||
# structured error instead of filtering an empty fallback into {"log":[]}.
|
lines=$1
|
||||||
if ! raw=$(ubus call log read "{\"lines\":$lines,\"stream\":false,\"oneshot\":true}" 2>/dev/null); then
|
shed_flag=${2:-0}
|
||||||
printf '%s' '{"log":[],"error":"log_read_failed"}'
|
# Adaptive plan prints: lines shed bucket
|
||||||
|
start_cs=$(fwlive_adaptive_clock_cs)
|
||||||
|
# Drop dead oneshot flag — only consumed on logd's stream branch (#306).
|
||||||
|
if ! raw=$(ubus call log read "{\"lines\":$lines,\"stream\":false}" 2>/dev/null); then
|
||||||
|
# Do not record ~0 ms as cold: failure is not healthy fast processing
|
||||||
|
# and must not clear an existing hot/shed cap (#329 Hermes Q1).
|
||||||
|
out='{"log":[],"error":"log_read_failed"}'
|
||||||
|
fwlive_adaptive_merge_reply "$out" "$shed_flag" "$lines" 0 0
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -444,7 +454,17 @@ fetch_firewall_logs() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
[ -n "$out" ] || out='{"log":[],"error":"filter_empty"}'
|
[ -n "$out" ] || out='{"log":[],"error":"filter_empty"}'
|
||||||
printf '%s' "$out"
|
end_cs=$(fwlive_adaptive_clock_cs)
|
||||||
|
# Uptime wrap / skew: end < start — do not record as cold (Grok #329 P2).
|
||||||
|
if [ "$end_cs" -ge "$start_cs" ]; then
|
||||||
|
_dur=$(((end_cs - start_cs) * 10))
|
||||||
|
fwlive_adaptive_record "$_dur" "$lines"
|
||||||
|
fi
|
||||||
|
# Layer 1: messages_received stays 0 — no post-filter ash count (Grok #329 P1).
|
||||||
|
_trunc=0
|
||||||
|
[ "$shed_flag" = 1 ] && _trunc=1
|
||||||
|
[ "$lines" -lt "$requested" ] && _trunc=1
|
||||||
|
fwlive_adaptive_merge_reply "$out" "$shed_flag" "$lines" "$_trunc" 0
|
||||||
}
|
}
|
||||||
|
|
||||||
poll_logs() {
|
poll_logs() {
|
||||||
@@ -584,6 +604,12 @@ resolve_addresses() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Load shed: previous poll was hot (#306 Layer 1).
|
||||||
|
if fwlive_adaptive_is_hot; then
|
||||||
|
printf '{"names":{},"disabled":"load"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Wall-clock budget clock starts at the TRUE function entry (luna
|
# Wall-clock budget clock starts at the TRUE function entry (luna
|
||||||
# folds 2026-08-10): the budget must bound the whole call — including
|
# folds 2026-08-10): the budget must bound the whole call — including
|
||||||
# read_rpc_input (stalled stdin must not escape the budget) and json
|
# read_rpc_input (stalled stdin must not escape the budget) and json
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-gecoosac
|
PKG_NAME:=luci-app-gecoosac
|
||||||
PKG_VERSION:=2.2
|
PKG_VERSION:=2.2
|
||||||
PKG_RELEASE:=20
|
PKG_RELEASE:=21
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI Support for gecoosac
|
LUCI_TITLE:=LuCI Support for gecoosac
|
||||||
LUCI_DEPENDS:=+luci-base +gecoosac +gecoosac-files
|
LUCI_DEPENDS:=+luci-base +gecoosac +gecoosac-files +gecoosac-common
|
||||||
LUCI_EXTRA_DEPENDS:=gecoosac (>=2.2.20251015)
|
LUCI_EXTRA_DEPENDS:=gecoosac (>=2.2.20251015)
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
PKG_LICENSE:=AGPL-3.0-only
|
PKG_LICENSE:=AGPL-3.0-only
|
||||||
|
|||||||
@@ -403,6 +403,7 @@ return view.extend({
|
|||||||
o.default = '8080';
|
o.default = '8080';
|
||||||
o.datatype = 'port';
|
o.datatype = 'port';
|
||||||
o.depends('isonlyoneprot', '0');
|
o.depends('isonlyoneprot', '0');
|
||||||
|
o.retain = true;
|
||||||
|
|
||||||
portOption.validate = function(section_id, value) {
|
portOption.validate = function(section_id, value) {
|
||||||
return validatePortValue(section_id, value, managementPortOption, singlePortOption, true);
|
return validatePortValue(section_id, value, managementPortOption, singlePortOption, true);
|
||||||
@@ -422,10 +423,11 @@ return view.extend({
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
httpsOption = s.option(form.Flag, 'https', _('Enable HTTPS service'),
|
httpsOption = s.option(form.Flag, 'https', _('Enable HTTPS service'),
|
||||||
_('Default certificate files are generated when HTTPS starts; custom paths must point to a readable certificate and matching key.'));
|
_('When HTTPS starts, default certificates are generated or renewed if fewer than 30 days remain. Custom certificates must be currently valid and match their private keys.'));
|
||||||
o = httpsOption;
|
o = httpsOption;
|
||||||
o.default = '0';
|
o.default = '0';
|
||||||
o.depends('isonlyoneprot', '0');
|
o.depends('isonlyoneprot', '0');
|
||||||
|
o.retain = true;
|
||||||
|
|
||||||
certificateOption = s.option(form.Value, 'crt_file', _('Specify crt certificate file'));
|
certificateOption = s.option(form.Value, 'crt_file', _('Specify crt certificate file'));
|
||||||
o = certificateOption;
|
o = certificateOption;
|
||||||
@@ -433,6 +435,7 @@ return view.extend({
|
|||||||
o.default = DEFAULT_CRT_FILE;
|
o.default = DEFAULT_CRT_FILE;
|
||||||
o.datatype = 'file';
|
o.datatype = 'file';
|
||||||
o.depends({ isonlyoneprot: '0', https: '1' });
|
o.depends({ isonlyoneprot: '0', https: '1' });
|
||||||
|
o.retain = true;
|
||||||
o.validate = function(section_id, value) {
|
o.validate = function(section_id, value) {
|
||||||
return validateCertificatePath(section_id, value, singlePortOption, httpsOption);
|
return validateCertificatePath(section_id, value, singlePortOption, httpsOption);
|
||||||
};
|
};
|
||||||
@@ -443,6 +446,7 @@ return view.extend({
|
|||||||
o.default = DEFAULT_KEY_FILE;
|
o.default = DEFAULT_KEY_FILE;
|
||||||
o.datatype = 'file';
|
o.datatype = 'file';
|
||||||
o.depends({ isonlyoneprot: '0', https: '1' });
|
o.depends({ isonlyoneprot: '0', https: '1' });
|
||||||
|
o.retain = true;
|
||||||
o.validate = function(section_id, value) {
|
o.validate = function(section_id, value) {
|
||||||
return validateCertificatePath(section_id, value, singlePortOption, httpsOption);
|
return validateCertificatePath(section_id, value, singlePortOption, httpsOption);
|
||||||
};
|
};
|
||||||
@@ -481,7 +485,7 @@ return view.extend({
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.option(form.Value, 'db_dir', _('Database dir path'),
|
o = s.option(form.Value, 'db_dir', _('Database dir path'),
|
||||||
_('Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Do not place it inside the upload directory.'));
|
_('Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Only /etc/gecoosac persists across reboots on standard OpenWrt. Do not place it inside the upload directory.'));
|
||||||
o.placeholder = DEFAULT_DB_DIR;
|
o.placeholder = DEFAULT_DB_DIR;
|
||||||
o.default = DEFAULT_DB_DIR;
|
o.default = DEFAULT_DB_DIR;
|
||||||
o.datatype = 'directory';
|
o.datatype = 'directory';
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "Default certificate files are generated when HTTPS starts; custom paths must point to a readable certificate and matching key."
|
msgid "When HTTPS starts, default certificates are generated or renewed if fewer than 30 days remain. Custom certificates must be currently valid and match their private keys."
|
||||||
msgstr "默认证书文件会在 HTTPS 启动时生成;自定义路径必须指向可读取且相互匹配的证书和私钥。"
|
msgstr "HTTPS 启动时会生成默认证书,或在剩余有效期不足 30 天时更新。自定义证书必须在有效期内且与私钥匹配。"
|
||||||
|
|
||||||
msgid "Gecoos AC"
|
msgid "Gecoos AC"
|
||||||
msgstr "集客AC控制器"
|
msgstr "集客AC控制器"
|
||||||
@@ -68,8 +68,8 @@ msgstr "上传目录必须是 /tmp/gecoosac/upload 或 /mnt/storage-name/gecoosa
|
|||||||
msgid "Database dir path"
|
msgid "Database dir path"
|
||||||
msgstr "数据库目录"
|
msgstr "数据库目录"
|
||||||
|
|
||||||
msgid "Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Do not place it inside the upload directory."
|
msgid "Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Only /etc/gecoosac persists across reboots on standard OpenWrt. Do not place it inside the upload directory."
|
||||||
msgstr "将配置数据库存放在 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。不要放在上传目录内。"
|
msgstr "将配置数据库存放在 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。标准 OpenWrt 上只有 /etc/gecoosac 的数据会在重启后保留。不要放在上传目录内。"
|
||||||
|
|
||||||
msgid "Database directory must be under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac."
|
msgid "Database directory must be under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac."
|
||||||
msgstr "数据库目录必须位于 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。"
|
msgstr "数据库目录必须位于 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。"
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "Default certificate files are generated when HTTPS starts; custom paths must point to a readable certificate and matching key."
|
msgid "When HTTPS starts, default certificates are generated or renewed if fewer than 30 days remain. Custom certificates must be currently valid and match their private keys."
|
||||||
msgstr "預設憑證檔案會在 HTTPS 啟動時產生;自訂路徑必須指向可讀取且相互匹配的憑證與私鑰。"
|
msgstr "HTTPS 啟動時會產生預設憑證,或在剩餘有效期不足 30 天時更新。自訂憑證必須在有效期內且與私鑰相符。"
|
||||||
|
|
||||||
msgid "Gecoos AC"
|
msgid "Gecoos AC"
|
||||||
msgstr "集客 AC 控制器"
|
msgstr "集客 AC 控制器"
|
||||||
@@ -68,8 +68,8 @@ msgstr "上傳目錄必須是 /tmp/gecoosac/upload 或 /mnt/storage-name/gecoosa
|
|||||||
msgid "Database dir path"
|
msgid "Database dir path"
|
||||||
msgstr "資料庫目錄"
|
msgstr "資料庫目錄"
|
||||||
|
|
||||||
msgid "Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Do not place it inside the upload directory."
|
msgid "Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Only /etc/gecoosac persists across reboots on standard OpenWrt. Do not place it inside the upload directory."
|
||||||
msgstr "將設定資料庫存放在 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。不要放在上傳目錄內。"
|
msgstr "將設定資料庫存放在 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。標準 OpenWrt 上只有 /etc/gecoosac 的資料會在重新啟動後保留。不要放在上傳目錄內。"
|
||||||
|
|
||||||
msgid "Database directory must be under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac."
|
msgid "Database directory must be under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac."
|
||||||
msgstr "資料庫目錄必須位於 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。"
|
msgstr "資料庫目錄必須位於 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。"
|
||||||
|
|||||||
@@ -2,12 +2,7 @@
|
|||||||
|
|
||||||
. /usr/share/libubox/jshn.sh
|
. /usr/share/libubox/jshn.sh
|
||||||
. /lib/functions.sh
|
. /lib/functions.sh
|
||||||
|
. /usr/share/gecoosac/common.sh
|
||||||
DEFAULT_DB_DIR=/etc/gecoosac
|
|
||||||
DEFAULT_UPLOAD_DIR=/tmp/gecoosac/upload
|
|
||||||
DEFAULT_CRT_FILE=/etc/gecoosac/tls/gecoosac.crt
|
|
||||||
DEFAULT_KEY_FILE=/etc/gecoosac/tls/gecoosac.key
|
|
||||||
DEFAULT_PID_DIR=/var/run
|
|
||||||
|
|
||||||
json_result() {
|
json_result() {
|
||||||
local ok="$1"
|
local ok="$1"
|
||||||
@@ -22,116 +17,6 @@ json_result() {
|
|||||||
json_cleanup
|
json_cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
normalize_path() {
|
|
||||||
local path="$1"
|
|
||||||
local part normalized parent
|
|
||||||
|
|
||||||
case "$path" in
|
|
||||||
/*) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
normalized="/"
|
|
||||||
path="${path#/}"
|
|
||||||
|
|
||||||
while [ -n "$path" ]; do
|
|
||||||
part="${path%%/*}"
|
|
||||||
if [ "$part" = "$path" ]; then
|
|
||||||
path=""
|
|
||||||
else
|
|
||||||
path="${path#*/}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$part" in
|
|
||||||
""|.) ;;
|
|
||||||
..)
|
|
||||||
if [ "$normalized" != "/" ]; then
|
|
||||||
parent="${normalized%/*}"
|
|
||||||
[ -n "$parent" ] || parent="/"
|
|
||||||
normalized="$parent"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*) normalized="${normalized%/}/$part" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
printf '%s\n' "$normalized"
|
|
||||||
}
|
|
||||||
|
|
||||||
path_has_clear_stage_component() {
|
|
||||||
local path="$1" part rest
|
|
||||||
|
|
||||||
path="$(normalize_path "$path")" || return 1
|
|
||||||
rest="${path#/}"
|
|
||||||
while [ -n "$rest" ]; do
|
|
||||||
part="${rest%%/*}"
|
|
||||||
if [ "$part" = "$rest" ]; then
|
|
||||||
rest=""
|
|
||||||
else
|
|
||||||
rest="${rest#*/}"
|
|
||||||
fi
|
|
||||||
case "$part" in
|
|
||||||
.gecoosac-clear.*) return 0 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
path_uses_clear_stage() {
|
|
||||||
local path="$1" resolved
|
|
||||||
|
|
||||||
path_has_clear_stage_component "$path" && return 0
|
|
||||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
||||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
|
||||||
path_has_clear_stage_component "$resolved" && return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
is_supported_upload_path() {
|
|
||||||
local path="$1" storage
|
|
||||||
|
|
||||||
path="$(normalize_path "$path")" || return 1
|
|
||||||
path_uses_clear_stage "$path" && return 1
|
|
||||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
|
||||||
|
|
||||||
case "$path" in
|
|
||||||
/mnt/*/gecoosac/upload)
|
|
||||||
storage="${path#/mnt/}"
|
|
||||||
storage="${storage%/gecoosac/upload}"
|
|
||||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
|
||||||
;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
managed_dir_path() {
|
|
||||||
local role="$1" path anchor
|
|
||||||
|
|
||||||
case "$role" in
|
|
||||||
upload|db|pid|file) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
path="$(normalize_path "$2")" || return 1
|
|
||||||
case "$path" in
|
|
||||||
/var/run|/var/run/*)
|
|
||||||
anchor="$(readlink -f /var/run 2>/dev/null)" || return 1
|
|
||||||
[ "$anchor" = "/tmp/run" ] || return 1
|
|
||||||
printf '%s%s\n' "$anchor" "${path#/var/run}"
|
|
||||||
;;
|
|
||||||
/var|/var/*)
|
|
||||||
anchor="$(readlink -f /var 2>/dev/null)" || return 1
|
|
||||||
case "$anchor" in
|
|
||||||
/var|/tmp) printf '%s%s\n' "$anchor" "${path#/var}" ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
*) printf '%s\n' "$path" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
load_clear_config() {
|
load_clear_config() {
|
||||||
local section_type
|
local section_type
|
||||||
|
|
||||||
@@ -151,135 +36,17 @@ load_clear_config() {
|
|||||||
config_get clear_key_file config key_file "$DEFAULT_KEY_FILE"
|
config_get clear_key_file config key_file "$DEFAULT_KEY_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
safe_upload_path() {
|
|
||||||
local path
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
is_supported_upload_path "$path"
|
|
||||||
}
|
|
||||||
|
|
||||||
path_in_dir() {
|
|
||||||
local path root
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
root="$(normalize_path "$2")" || return 1
|
|
||||||
|
|
||||||
[ "$root" != "/" ] || return 1
|
|
||||||
[ "$path" = "$root" ] && return 0
|
|
||||||
[ "${path#"$root"/}" != "$path" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
path_has_mount() {
|
|
||||||
local root line mount_path
|
|
||||||
|
|
||||||
root="$(normalize_path "$1")" || return 2
|
|
||||||
[ -r /proc/self/mountinfo ] || return 2
|
|
||||||
|
|
||||||
while IFS= read -r line; do
|
|
||||||
mount_path="$(printf '%s\n' "$line" | cut -d ' ' -f 5)" || return 2
|
|
||||||
[ -n "$mount_path" ] || return 2
|
|
||||||
mount_path="$(printf '%b\n' "$mount_path" 2>/dev/null)" || return 2
|
|
||||||
mount_path="$(normalize_path "$mount_path")" || return 2
|
|
||||||
path_in_dir "$mount_path" "$root" && return 0
|
|
||||||
done < /proc/self/mountinfo
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
is_secure_dir() {
|
|
||||||
local allow_sticky="$2" owner permissions metadata
|
|
||||||
|
|
||||||
[ -d "$1" ] && [ ! -L "$1" ] || return 1
|
|
||||||
metadata="$(ls -ldn "$1" 2>/dev/null)" || return 1
|
|
||||||
set -- $metadata
|
|
||||||
permissions="$1"
|
|
||||||
owner="$3"
|
|
||||||
[ "$owner" = "0" ] || return 1
|
|
||||||
case "$permissions" in
|
|
||||||
d?????????) ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ "$(printf '%s' "$permissions" | cut -c6)" = "w" ] || \
|
|
||||||
[ "$(printf '%s' "$permissions" | cut -c9)" = "w" ]; then
|
|
||||||
[ "$allow_sticky" = "1" ] && [ "$(printf '%s' "$permissions" | cut -c10)" = "t" ] || return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
is_secure_upload_dir() {
|
|
||||||
local path current part rest
|
|
||||||
|
|
||||||
path="$(normalize_path "$1")" || return 1
|
|
||||||
[ -d "$path" ] && [ ! -L "$path" ] || return 1
|
|
||||||
current="/"
|
|
||||||
rest="${path#/}"
|
|
||||||
|
|
||||||
while [ -n "$rest" ]; do
|
|
||||||
part="${rest%%/*}"
|
|
||||||
if [ "$part" = "$rest" ]; then
|
|
||||||
rest=""
|
|
||||||
else
|
|
||||||
rest="${rest#*/}"
|
|
||||||
fi
|
|
||||||
current="${current%/}/$part"
|
|
||||||
case "$current" in
|
|
||||||
/tmp) is_secure_dir "$current" 1 || return 1 ;;
|
|
||||||
*) is_secure_dir "$current" || return 1 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
configured_path_in_upload() {
|
configured_path_in_upload() {
|
||||||
local option="$1" checked_root="$2"
|
local path role
|
||||||
local live_logical="${3:-$2}" live_physical="${4:-$2}"
|
|
||||||
local role path physical real_path suffix mapped
|
|
||||||
|
|
||||||
case "$option" in
|
case "$1" in
|
||||||
db_dir) path="$clear_db_dir"; role=db ;;
|
db_dir) path="$clear_db_dir"; role=db ;;
|
||||||
piddir) path="$clear_piddir"; role=pid ;;
|
piddir) path="$clear_piddir"; role=pid ;;
|
||||||
crt_file) path="$clear_crt_file"; role="file" ;;
|
crt_file) path="$clear_crt_file"; role="file" ;;
|
||||||
key_file) path="$clear_key_file"; role="file" ;;
|
key_file) path="$clear_key_file"; role="file" ;;
|
||||||
*) return 2 ;;
|
*) return 2 ;;
|
||||||
esac
|
esac
|
||||||
[ -n "$path" ] || return 1
|
protected_path_in_dir "$path" "$role" "$2" "${3:-$2}" "${4:-$2}"
|
||||||
|
|
||||||
path="$(normalize_path "$path")" || return 2
|
|
||||||
physical="$(managed_dir_path "$role" "$path")" || return 2
|
|
||||||
checked_root="$(normalize_path "$checked_root")" || return 2
|
|
||||||
live_logical="$(normalize_path "$live_logical")" || return 2
|
|
||||||
live_physical="$(normalize_path "$live_physical")" || return 2
|
|
||||||
|
|
||||||
path_in_dir "$path" "$checked_root" && return 0
|
|
||||||
path_in_dir "$physical" "$checked_root" && return 0
|
|
||||||
real_path=
|
|
||||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
||||||
real_path="$(readlink -f "$path" 2>/dev/null)" || return 2
|
|
||||||
[ -n "$real_path" ] || return 2
|
|
||||||
real_path="$(normalize_path "$real_path")" || return 2
|
|
||||||
path_in_dir "$real_path" "$checked_root" && return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ "$checked_root" != "$live_physical" ] || {
|
|
||||||
path_in_dir "$path" "$live_logical" && return 0
|
|
||||||
path_in_dir "$physical" "$live_physical" && return 0
|
|
||||||
if [ -n "$real_path" ] && path_in_dir "$real_path" "$live_physical"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
suffix=
|
|
||||||
if path_in_dir "$path" "$live_logical"; then
|
|
||||||
suffix="${path#"$live_logical"}"
|
|
||||||
elif path_in_dir "$physical" "$live_physical"; then
|
|
||||||
suffix="${physical#"$live_physical"}"
|
|
||||||
elif [ -n "$real_path" ] && path_in_dir "$real_path" "$live_physical"; then
|
|
||||||
suffix="${real_path#"$live_physical"}"
|
|
||||||
else
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
mapped="${checked_root%/}${suffix}"
|
|
||||||
[ -e "$mapped" ] || [ -L "$mapped" ]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
status_result() {
|
status_result() {
|
||||||
@@ -534,7 +301,7 @@ clear_upload() {
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
logical="$(normalize_path "$logical")" || { json_result 0 "Expecting an absolute path"; return; }
|
logical="$(normalize_path "$logical")" || { json_result 0 "Expecting an absolute path"; return; }
|
||||||
if ! safe_upload_path "$logical"; then
|
if ! is_supported_upload_path "$logical"; then
|
||||||
json_result 0 "Only Gecoos upload directories can be cleared" "$logical"
|
json_result 0 "Only Gecoos upload directories can be cleared" "$logical"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -542,7 +309,7 @@ clear_upload() {
|
|||||||
json_result 0 "Unable to resolve upload directory" "$logical"
|
json_result 0 "Unable to resolve upload directory" "$logical"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ! safe_upload_path "$physical"; then
|
if ! is_supported_upload_path "$physical"; then
|
||||||
json_result 0 "Only Gecoos upload directories can be cleared" "$logical"
|
json_result 0 "Only Gecoos upload directories can be cleared" "$logical"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-passwall
|
PKG_NAME:=luci-app-passwall
|
||||||
PKG_VERSION:=26.9.9
|
PKG_VERSION:=26.9.9
|
||||||
PKG_RELEASE:=269
|
PKG_RELEASE:=270
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -913,7 +913,7 @@ function gen_config_server(node)
|
|||||||
stream_receive_window = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil,
|
stream_receive_window = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil,
|
||||||
connection_receive_window = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil,
|
connection_receive_window = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil,
|
||||||
max_concurrent_streams = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil,
|
max_concurrent_streams = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil,
|
||||||
disable_path_mtu_discover = (node.hysteria_disable_mtu_discovery == "1") and true or false,
|
disable_path_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
|
||||||
tls = tls
|
tls = tls
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1098,7 +1098,7 @@ add_firewall_rule() {
|
|||||||
|
|
||||||
insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW"
|
insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW"
|
||||||
# Only TCP, UDP Invalid.
|
# Only TCP, UDP Invalid.
|
||||||
insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT"
|
insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket --transparent -j PSW_DIVERT"
|
||||||
|
|
||||||
$ipt_m -N PSW_OUTPUT
|
$ipt_m -N PSW_OUTPUT
|
||||||
$ipt_m -A PSW_OUTPUT $(dst $IPSET_LAN) -j RETURN
|
$ipt_m -A PSW_OUTPUT $(dst $IPSET_LAN) -j RETURN
|
||||||
@@ -1172,7 +1172,7 @@ add_firewall_rule() {
|
|||||||
|
|
||||||
insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW"
|
insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW"
|
||||||
# Only TCP, UDP Invalid.
|
# Only TCP, UDP Invalid.
|
||||||
insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT"
|
insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket --transparent -j PSW_DIVERT"
|
||||||
|
|
||||||
$ip6t_m -N PSW_OUTPUT
|
$ip6t_m -N PSW_OUTPUT
|
||||||
$ip6t_m -A PSW_OUTPUT -m mark --mark 0xff/0xff -j RETURN
|
$ip6t_m -A PSW_OUTPUT -m mark --mark 0xff/0xff -j RETURN
|
||||||
@@ -1477,7 +1477,7 @@ gen_include() {
|
|||||||
[ -z "${_ipt}" ] && return
|
[ -z "${_ipt}" ] && return
|
||||||
|
|
||||||
echo "*$2"
|
echo "*$2"
|
||||||
${_ipt}-save -t $2 | grep "PSW" | grep -v "\-j PSW$" | grep -v "mangle\-OUTPUT\-PSW" | grep -v "socket \-j PSW_DIVERT$" | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
|
${_ipt}-save -t $2 | grep "PSW" | grep -v "\-j PSW$" | grep -v "mangle\-OUTPUT\-PSW" | grep -v "\-m socket .*\-j PSW_DIVERT$" | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
|
||||||
echo 'COMMIT'
|
echo 'COMMIT'
|
||||||
}
|
}
|
||||||
local __ipt=""
|
local __ipt=""
|
||||||
@@ -1501,7 +1501,7 @@ gen_include() {
|
|||||||
[ -z "${is_tproxy}" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p tcp -j PSW")
|
[ -z "${is_tproxy}" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p tcp -j PSW")
|
||||||
|
|
||||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW")
|
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW")
|
||||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT")
|
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket --transparent -j PSW_DIVERT")
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1522,7 +1522,7 @@ gen_include() {
|
|||||||
[ "$accept_icmpv6" = "1" ] && $ip6t_n -A PREROUTING -p ipv6-icmp -j PSW
|
[ "$accept_icmpv6" = "1" ] && $ip6t_n -A PREROUTING -p ipv6-icmp -j PSW
|
||||||
|
|
||||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW")
|
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW")
|
||||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT")
|
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket --transparent -j PSW_DIVERT")
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-passwall2
|
PKG_NAME:=luci-app-passwall2
|
||||||
PKG_VERSION:=26.9.12
|
PKG_VERSION:=26.9.12
|
||||||
PKG_RELEASE:=119
|
PKG_RELEASE:=120
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -934,7 +934,7 @@ function gen_config_server(node)
|
|||||||
stream_receive_window = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil,
|
stream_receive_window = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil,
|
||||||
connection_receive_window = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil,
|
connection_receive_window = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil,
|
||||||
max_concurrent_streams = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil,
|
max_concurrent_streams = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil,
|
||||||
disable_path_mtu_discover = (node.hysteria_disable_mtu_discovery == "1") and true or false,
|
disable_path_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
|
||||||
tls = tls
|
tls = tls
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
+2
-2
@@ -14,8 +14,8 @@ MISE_HASH_x86_64:=3832f39c325e343f81fe3d92b2447c5d1a5eea1bc85092bb7b6c2580622264
|
|||||||
MISE_HASH_aarch64:=06186cfbfe947049b21d58575fb0ea800cc26ed1375f20f4b678cb3a9d679437
|
MISE_HASH_aarch64:=06186cfbfe947049b21d58575fb0ea800cc26ed1375f20f4b678cb3a9d679437
|
||||||
|
|
||||||
PKG_NAME:=mise
|
PKG_NAME:=mise
|
||||||
PKG_VERSION:=2026.9.5
|
PKG_VERSION:=2026.9.6
|
||||||
PKG_RELEASE:=11
|
PKG_RELEASE:=12
|
||||||
PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION)-linux-$(MISE_ARCH)-musl.tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION)-linux-$(MISE_ARCH)-musl.tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/jdx/mise/releases/download/v$(PKG_VERSION)/
|
PKG_SOURCE_URL:=https://github.com/jdx/mise/releases/download/v$(PKG_VERSION)/
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|||||||
+2
-2
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
|||||||
|
|
||||||
PKG_NAME:=natflow
|
PKG_NAME:=natflow
|
||||||
PKG_VERSION:=20260531
|
PKG_VERSION:=20260531
|
||||||
PKG_RELEASE:=83
|
PKG_RELEASE:=84
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_VERSION:=6f03a5d1e50438ff72b0c9f19a5c9c97c329c62c
|
PKG_SOURCE_VERSION:=36123df5df3371516b648e511dad66d5413b47aa
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||||
PKG_LICENSE:=GPL-2.0
|
PKG_LICENSE:=GPL-2.0
|
||||||
|
|||||||
+3
-3
@@ -8,12 +8,12 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=torrserver
|
PKG_NAME:=torrserver
|
||||||
PKG_VERSION:=MatriX.144.3
|
PKG_VERSION:=MatriX.144.4
|
||||||
PKG_RELEASE:=22
|
PKG_RELEASE:=23
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/YouROK/TorrServer.git
|
PKG_SOURCE_URL:=https://github.com/YouROK/TorrServer.git
|
||||||
PKG_SOURCE_VERSION:=80f50702c1e852dcad59cdd1399ac8aa7a092fee
|
PKG_SOURCE_VERSION:=215f6fcaa84685fc0263584748206fca0810a8b6
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||||
|
|||||||
+2
-2
@@ -49,8 +49,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=wwand
|
PKG_NAME:=wwand
|
||||||
PKG_VERSION:=1.6.6_p22
|
PKG_VERSION:=1.6.6_p24
|
||||||
PKG_RELEASE:=34
|
PKG_RELEASE:=35
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||||
|
|||||||
Reference in new issue
Block a user