diff --git a/7z/Makefile b/7z/Makefile index 34e1ba11..6ab84547 100644 --- a/7z/Makefile +++ b/7z/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=7z -PKG_VERSION:=26.01 -PKG_RELEASE:=2 +PKG_VERSION:=26.02 +PKG_RELEASE:=3 PKG_SOURCE:=$(PKG_NAME)$(subst .,,$(PKG_VERSION))-src.tar.xz PKG_SOURCE_URL:=https://7-zip.org/a/ diff --git a/baidudrive/Makefile b/baidudrive/Makefile index 37ac6339..384be4ed 100644 --- a/baidudrive/Makefile +++ b/baidudrive/Makefile @@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk PKG_ARCH_BAIDUDRIVE:=$(ARCH) PKG_NAME:=baidudrive -PKG_VERSION:=linkease-runtime-v1.7.5 -PKG_RELEASE:=6 +PKG_VERSION:=linkeasefull-runtime-v3.0.5 +PKG_RELEASE:=7 PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/baidudrive-runtime-v$(PKG_VERSION)/ PKG_HASH:=skip diff --git a/gecoosac/Makefile b/gecoosac/Makefile index 56923199..89eb090d 100644 --- a/gecoosac/Makefile +++ b/gecoosac/Makefile @@ -2,9 +2,9 @@ include $(TOPDIR)/rules.mk PKG_NAME:=gecoosac PKG_VERSION:=2.2.20251015 -PKG_RELEASE:=14 +PKG_RELEASE:=15 -PKG_MAINTAINER:=lwb1978 +PKG_MAINTAINER:=Roc Lai PKG_LICENSE:=AGPL-3.0-only PKG_LICENSE_FILES:=LICENSE diff --git a/gecoosac/files/etc/config/gecoosac b/gecoosac/files/etc/config/gecoosac index 3192f3e1..838e41d6 100644 --- a/gecoosac/files/etc/config/gecoosac +++ b/gecoosac/files/etc/config/gecoosac @@ -1,4 +1,5 @@ config gecoosac 'config' + option config_compat '2' option enabled '0' option port '60650' option isonlyoneprot '1' diff --git a/gecoosac/files/etc/init.d/gecoosac b/gecoosac/files/etc/init.d/gecoosac index 580e5cf0..e91c30b9 100644 --- a/gecoosac/files/etc/init.d/gecoosac +++ b/gecoosac/files/etc/init.d/gecoosac @@ -101,6 +101,49 @@ is_path_in_dir() { [ "${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 @@ -154,7 +197,6 @@ run_with_timeout() { kill "$pid" >/dev/null 2>&1 sleep 1 kill -9 "$pid" >/dev/null 2>&1 - wait "$pid" 2>/dev/null return 1 fi sleep 1 @@ -164,6 +206,42 @@ run_with_timeout() { 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)" || return 1 + is_readable_regular_file "$cert_file" || return 1 + tmp_dir="$(mktemp -d /tmp/gecoosac-cert.XXXXXX)" || return 1 + if ! run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/san" \ + openssl x509 -in "$cert_file" -noout -ext subjectAltName /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 @@ -208,13 +292,22 @@ cert_matches_san() { } cert_matches_key() { - local cert_file="$1" - local key_file="$2" - local cert_pub key_pub + local cert_file="$1" key_file="$2" + local cert_pub key_pub tmp_dir - [ -s "$cert_file" ] && [ -s "$key_file" ] || return 1 - cert_pub="$(openssl x509 -in "$cert_file" -noout -pubkey 2>/dev/null)" || return 1 - key_pub="$(openssl pkey -in "$key_file" -pubout 2>/dev/null)" || return 1 + 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 + if ! run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/cert.pub" \ + openssl x509 -in "$cert_file" -noout -pubkey /dev/null)" + key_pub="$(cat "$tmp_dir/key.pub" 2>/dev/null)" + rm -rf "$tmp_dir" [ -n "$cert_pub" ] && [ "$cert_pub" = "$key_pub" ] } @@ -235,7 +328,7 @@ generate_default_cert() { cert_ip="$(uci -q get network.lan.ipaddr)" is_ipv4 "$cert_ip" || cert_ip="" - if [ -s "$DEFAULT_KEY_FILE" ] && cert_matches_san "$DEFAULT_CRT_FILE" "$cert_host" "$cert_ip" && cert_matches_key "$DEFAULT_CRT_FILE" "$DEFAULT_KEY_FILE"; then + 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 return 0 fi @@ -262,7 +355,7 @@ generate_default_cert() { -addext "basicConstraints=critical,CA:FALSE" \ -addext "keyUsage=digitalSignature" \ -addext "extendedKeyUsage=serverAuth" \ - -addext "subjectAltName=$cert_san" + -addext "subjectAltName=$cert_san" /dev/null; then + logger -t gecoosac "interface port and management port must be different" + return 1 + fi + + ensure_dirs || return 1 + if [ "$isonlyoneprot" = "0" ] && [ "$https" = "1" ]; then + if [ "$crt_file" = "$DEFAULT_CRT_FILE" ] && [ "$key_file" = "$DEFAULT_KEY_FILE" ]; then + generate_default_cert || return 1 + fi + + 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 + fi + fi } start_service() { local runtime_upload_dir runtime_db_dir runtime_piddir - init_conf - normalize_conf - + prepare_service || return 1 [ "$enabled" = "1" ] || return 0 - if [ ! -x "$PROG" ]; then - logger -t gecoosac "program not found: $PROG" - return 1 - fi - - if [ "$isonlyoneprot" = "0" ] && [ "$port" = "$m_port" ]; then - logger -t gecoosac "interface port and management port must be different" - return 1 - fi - - ensure_dirs || return 1 runtime_upload_dir="${upload_dir%/}/" runtime_db_dir="${db_dir%/}/" runtime_piddir="${piddir%/}/" - if [ "$isonlyoneprot" = "0" ] && [ "$https" = "1" ]; then - if [ "$crt_file" = "$DEFAULT_CRT_FILE" ] && [ "$key_file" = "$DEFAULT_KEY_FILE" ]; then - generate_default_cert || return 1 - fi - - if [ ! -r "$crt_file" ] || [ ! -r "$key_file" ]; then - logger -t gecoosac "HTTPS is enabled but certificate or key file is missing" - return 1 - fi - - if ! cert_matches_key "$crt_file" "$key_file"; then - logger -t gecoosac "HTTPS certificate and key do not match" - return 1 - fi - fi - procd_open_instance gecoosac procd_set_param command "$PROG" procd_append_param command -f "$runtime_upload_dir" @@ -387,8 +524,28 @@ start_service() { procd_close_instance } +wait_service_stopped() { + local i + + if ! type service_running >/dev/null 2>&1; then + return 0 + fi + + i=0 + while [ "$i" -lt 5 ]; do + service_running gecoosac || return 0 + sleep 1 + i=$((i + 1)) + done + return 1 +} + reload_service() { - stop + prepare_service || return 1 + if ! stop || ! wait_service_stopped; then + logger -t gecoosac "unable to stop the existing service for reload" + return 1 + fi start } diff --git a/gecoosac/files/etc/uci-defaults/gecoosac b/gecoosac/files/etc/uci-defaults/gecoosac index 768217fe..ec236b19 100644 --- a/gecoosac/files/etc/uci-defaults/gecoosac +++ b/gecoosac/files/etc/uci-defaults/gecoosac @@ -8,6 +8,7 @@ DEFAULT_KEY_FILE=/etc/gecoosac/tls/gecoosac.key DEFAULT_PID_DIR=/var/run OLD_CRT_FILE=/etc/gecoosac/tls/1.crt OLD_KEY_FILE=/etc/gecoosac/tls/1.key +CONFIG_COMPAT=2 ensure_section() { uci -q get gecoosac.config >/dev/null && return 0 @@ -128,7 +129,7 @@ normalize_upload_dir() { if is_safe_upload_dir "$upload_dir"; then normalized="$(normalize_path "$upload_dir")" else - normalized="$DEFAULT_UPLOAD_DIR" + return 0 fi [ "$upload_dir" = "$normalized" ] && return 0 @@ -138,7 +139,6 @@ normalize_upload_dir() { normalize_dir_option() { local option="$1" - local default="$2" local validator="$3" local value normalized upload_dir @@ -150,7 +150,7 @@ normalize_dir_option() { if "$validator" "$value" "$upload_dir"; then normalized="$(normalize_path "$value")" else - normalized="$default" + return 0 fi [ "$value" = "$normalized" ] && return 0 @@ -159,30 +159,71 @@ normalize_dir_option() { } migrate_legacy_cert_paths() { - local crt_file key_file - local migrate + local crt_file key_file migrate crt_file="$(uci -q get gecoosac.config.crt_file)" key_file="$(uci -q get gecoosac.config.key_file)" migrate=0 - if [ "$crt_file" = "$OLD_CRT_FILE" ]; then - if [ -z "$key_file" ] || [ "$key_file" = "$OLD_KEY_FILE" ]; then - migrate=1 - fi + if [ "$crt_file" = "$OLD_CRT_FILE" ] && [ -z "$key_file" ]; then + uci -q set "gecoosac.config.key_file=${OLD_KEY_FILE}" || return 1 + key_file="$OLD_KEY_FILE" + changed=1 elif [ -z "$crt_file" ] && [ "$key_file" = "$OLD_KEY_FILE" ]; then + uci -q set "gecoosac.config.crt_file=${OLD_CRT_FILE}" || return 1 + crt_file="$OLD_CRT_FILE" + changed=1 + fi + + if [ "$crt_file" = "$OLD_CRT_FILE" ] && [ "$key_file" = "$OLD_KEY_FILE" ]; then + migrate=1 + elif [ -z "$crt_file" ] && [ -z "$key_file" ]; then + [ -f "$OLD_CRT_FILE" ] && [ -f "$OLD_KEY_FILE" ] || return 0 migrate=1 fi - if [ "$migrate" = "1" ]; then - uci -q set "gecoosac.config.crt_file=${DEFAULT_CRT_FILE}" - uci -q set "gecoosac.config.key_file=${DEFAULT_KEY_FILE}" - changed=1 + [ "$migrate" = "1" ] || return 0 + [ -f "$OLD_CRT_FILE" ] && [ ! -L "$OLD_CRT_FILE" ] || return 0 + [ -f "$OLD_KEY_FILE" ] && [ ! -L "$OLD_KEY_FILE" ] || return 0 + [ ! -e "$DEFAULT_CRT_FILE" ] && [ ! -L "$DEFAULT_CRT_FILE" ] || return 0 + [ ! -e "$DEFAULT_KEY_FILE" ] && [ ! -L "$DEFAULT_KEY_FILE" ] || return 0 + + mkdir -p /etc/gecoosac/tls || { + logger -t gecoosac "unable to prepare TLS directory for legacy certificate migration" + return 1 + } + if ! mv "$OLD_CRT_FILE" "$DEFAULT_CRT_FILE"; then + logger -t gecoosac "unable to migrate legacy certificate" + return 1 fi + if ! mv "$OLD_KEY_FILE" "$DEFAULT_KEY_FILE"; then + mv "$DEFAULT_CRT_FILE" "$OLD_CRT_FILE" 2>/dev/null + logger -t gecoosac "unable to migrate legacy private key" + return 1 + fi + if ! chmod 644 "$DEFAULT_CRT_FILE" || ! chmod 600 "$DEFAULT_KEY_FILE" || \ + ! uci -q set "gecoosac.config.crt_file=${DEFAULT_CRT_FILE}" || \ + ! uci -q set "gecoosac.config.key_file=${DEFAULT_KEY_FILE}"; then + mv "$DEFAULT_KEY_FILE" "$OLD_KEY_FILE" 2>/dev/null + mv "$DEFAULT_CRT_FILE" "$OLD_CRT_FILE" 2>/dev/null + logger -t gecoosac "unable to complete legacy certificate migration" + return 1 + fi + changed=1 +} + +migrate_config() { + local compat + + compat="$(uci -q get gecoosac.config.config_compat)" + [ "$compat" = "$CONFIG_COMPAT" ] && return 0 + migrate_legacy_cert_paths || return 1 + uci -q set "gecoosac.config.config_compat=${CONFIG_COMPAT}" || return 1 + changed=1 } ensure_section -migrate_legacy_cert_paths +migrate_config || exit 1 normalize_upload_dir normalize_dir_option db_dir "$DEFAULT_DB_DIR" is_safe_db_dir normalize_dir_option piddir "$DEFAULT_PID_DIR" is_safe_pid_dir diff --git a/hysteria/Makefile b/hysteria/Makefile index 2534a990..3202c148 100644 --- a/hysteria/Makefile +++ b/hysteria/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=hysteria -PKG_VERSION:=2.11.0 -PKG_RELEASE:=13 +PKG_VERSION:=2.12.0 +PKG_RELEASE:=14 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/app/v$(PKG_VERSION)? diff --git a/linkease-common-bin/Makefile b/linkease-common-bin/Makefile index d244a3b5..c3c9ac9d 100644 --- a/linkease-common-bin/Makefile +++ b/linkease-common-bin/Makefile @@ -10,9 +10,9 @@ include $(TOPDIR)/rules.mk PKG_ARCH_LINKEASE:=$(ARCH) PKG_NAME:=linkease-common-bin -PKG_VERSION:=linkease-runtime-v1.7.5 -PKG_SOURCE_DATE:=$(PKG_VERSION) -PKG_RELEASE:=2 +# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable +PKG_SOURCE_DATE:=1.7.5 +PKG_RELEASE:=3 ARCH_HEXCODE:= ifeq ($(ARCH),x86_64) @@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel) ARCH_HEXCODE=1b0c endif -PKG_SOURCE_VERSION:=f6d705f77dade7d57c1bb24334da4028279689b9 +PKG_SOURCE_VERSION:=8895348606dc6cab9b25a75576e5142f553dfa82 PKG_SOURCE:=linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/ PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE) diff --git a/linkease/Makefile b/linkease/Makefile index ca359d9d..d7cc56c3 100644 --- a/linkease/Makefile +++ b/linkease/Makefile @@ -10,9 +10,9 @@ include $(TOPDIR)/rules.mk PKG_ARCH_LINKEASE:=$(ARCH) PKG_NAME:=linkease -PKG_VERSION:=linkease-runtime-v1.7.5 -PKG_SOURCE_DATE:=$(PKG_VERSION) -PKG_RELEASE:=5 +# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable +PKG_SOURCE_DATE:=1.7.5 +PKG_RELEASE:=6 ARCH_HEXCODE:= ifeq ($(ARCH),x86_64) @@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel) ARCH_HEXCODE=1b0c endif -PKG_SOURCE_VERSION:=f6d705f77dade7d57c1bb24334da4028279689b9 +PKG_SOURCE_VERSION:=8895348606dc6cab9b25a75576e5142f553dfa82 PKG_SOURCE:=linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/ PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE) diff --git a/linkeasefull/Makefile b/linkeasefull/Makefile index b5ebc475..46988917 100644 --- a/linkeasefull/Makefile +++ b/linkeasefull/Makefile @@ -6,19 +6,25 @@ include $(TOPDIR)/rules.mk -PKG_NAME:=linkeasefull -PKG_VERSION:=linkease-runtime-v1.7.5 -PKG_SOURCE_DATE:=$(PKG_VERSION) -PKG_RELEASE:=4 +PKG_ARCH_LINKEASE:=$(ARCH) +PKG_NAME:=linkeasefull +# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable +PKG_SOURCE_DATE:=3.0.5 +PKG_RELEASE:=5 + +ARCH_HEXCODE:= ifeq ($(ARCH),x86_64) +ARCH_HEXCODE=8664 LINKEASE_RUNTIME_ARCH:=amd64 PKG_HASH:=skip else ifeq ($(ARCH),aarch64) +ARCH_HEXCODE=aa64 LINKEASE_RUNTIME_ARCH:=arm64 PKG_HASH:=skip endif +PKG_SOURCE_VERSION:=8895348606dc6cab9b25a75576e5142f553dfa82 PKG_SOURCE:=linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH).tar.gz PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkeasefull-runtime-v$(PKG_SOURCE_DATE)/ PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH) @@ -34,6 +40,7 @@ define Package/$(PKG_NAME) SUBMENU:=Web Servers/Proxies TITLE:=LinkEase Full - the local web desktop DEPENDS:=@(x86_64||aarch64) +linkease-common-bin +ca-bundle + PKGARCH:=all URL:=https://www.linkease.com/ endef diff --git a/luci-app-gecoosac/Makefile b/luci-app-gecoosac/Makefile index 61f79aef..a3be5869 100644 --- a/luci-app-gecoosac/Makefile +++ b/luci-app-gecoosac/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-gecoosac PKG_VERSION:=2.2 -PKG_RELEASE:=14 +PKG_RELEASE:=15 LUCI_TITLE:=LuCI Support for gecoosac LUCI_DEPENDS:=+luci-base +gecoosac diff --git a/luci-app-gecoosac/htdocs/luci-static/resources/view/gecoosac.js b/luci-app-gecoosac/htdocs/luci-static/resources/view/gecoosac.js index 7adcabd0..5dcf92ba 100644 --- a/luci-app-gecoosac/htdocs/luci-static/resources/view/gecoosac.js +++ b/luci-app-gecoosac/htdocs/luci-static/resources/view/gecoosac.js @@ -20,7 +20,8 @@ let statusPollRegistered = false; const callServiceStatus = rpc.declare({ object: 'luci.gecoosac', method: 'status', - expect: { '': {} } + expect: { '': {} }, + reject: true }); const callClearUpload = rpc.declare({ @@ -34,6 +35,26 @@ function validPort(value, defaultValue) { return Number.isInteger(port) && port >= 1 && port <= 65535 ? String(port) : defaultValue; } +function validPortValue(value) { + const text = String(value || ''); + const port = Number(text); + + return /^[0-9]+$/.test(text) && Number.isSafeInteger(port) && port >= 1 && port <= 65535; +} + +function validatePortValue(section_id, value, otherOption, singlePortOption) { + if (!validPortValue(value)) + return _('Port must be an integer between 1 and 65535.'); + + const singlePort = singlePortOption.formvalue(section_id); + const otherValue = otherOption.formvalue(section_id); + + if (singlePort === '0' && validPortValue(otherValue) && Number(value) === Number(otherValue)) + return _('Interface port and management port must be different.'); + + return true; +} + function normalizePath(value) { const path = String(value || ''); @@ -99,6 +120,9 @@ function serviceRunning(status) { const service = status && status.gecoosac; const instances = service && service.instances; + if (status && status.ok === false) + return false; + if (status && status.running === true) return true; @@ -112,6 +136,13 @@ function serviceRunning(status) { return false; } +function statusFailure() { + return { + ok: false, + error: _('Unable to query service status') + }; +} + function clientHost() { let host = window.location.hostname; @@ -132,6 +163,9 @@ function clientUrl() { } function renderStatusContent(status) { + if (status && status.error) + return E('p', { 'class': 'gecoosac-stopped' }, _('Service status unavailable') + ': ' + _(status.error)); + const running = serviceRunning(status); const text = running ? _('The GecoosAC service is running.') @@ -174,12 +208,15 @@ return view.extend({ load() { return Promise.all([ uci.load('gecoosac'), - L.resolveDefault(callServiceStatus(), {}) + callServiceStatus().catch(function() { + return statusFailure(); + }) ]); }, render(data) { let m, s, o, uploadDirOption; + let portOption, managementPortOption, singlePortOption; m = new form.Map('gecoosac', _('Gecoos AC'), _('Only supports Gecoos AP firmware 7.6 and above.') + '
' + @@ -190,7 +227,9 @@ return view.extend({ s.render = function() { if (!statusPollRegistered) { poll.add(function() { - return L.resolveDefault(callServiceStatus(), {}).then(updateStatus); + return callServiceStatus().then(updateStatus).catch(function() { + updateStatus(statusFailure()); + }); }, 3); statusPollRegistered = true; } @@ -212,23 +251,55 @@ return view.extend({ o = s.option(form.Flag, 'enabled', _('Enabled AC')); o.rmempty = false; - o = s.option(form.Value, 'port', _('Set interface port')); + portOption = s.option(form.Value, 'port', _('Set interface port')); + o = portOption; o.placeholder = '60650'; o.default = '60650'; o.datatype = 'port'; o.rmempty = false; - o = s.option(form.Flag, 'isonlyoneprot', _('Single Port Mode'), + singlePortOption = s.option(form.Flag, 'isonlyoneprot', _('Single Port Mode'), _('Do not enable the independent management port, only use one port for management.')); + o = singlePortOption; o.default = '1'; o.rmempty = false; - o = s.option(form.Value, 'm_port', _('Set management port')); + managementPortOption = s.option(form.Value, 'm_port', _('Set management port')); + o = managementPortOption; o.placeholder = '8080'; o.default = '8080'; o.datatype = 'port'; o.depends('isonlyoneprot', '0'); + portOption.validate = function(section_id, value) { + return validatePortValue(section_id, value, managementPortOption, singlePortOption); + }; + managementPortOption.validate = function(section_id, value) { + return validatePortValue(section_id, value, portOption, singlePortOption); + }; + singlePortOption.validate = function(section_id, value) { + if (value === '0') { + const port = portOption.formvalue(section_id); + const managementPort = managementPortOption.formvalue(section_id); + + if (validPortValue(port) && validPortValue(managementPort) && Number(port) === Number(managementPort)) + return _('Interface port and management port must be different.'); + } + + return true; + }; + const revalidatePorts = function(_event, section_id) { + for (const option of [ portOption, managementPortOption, singlePortOption ]) { + const element = option.getUIElement(section_id); + + if (element) + element.triggerValidation(); + } + }; + portOption.onchange = revalidatePorts; + managementPortOption.onchange = revalidatePorts; + singlePortOption.onchange = revalidatePorts; + o = 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.')); o.default = '0'; @@ -320,11 +391,11 @@ return view.extend({ if (!confirm(_('Really clear the saved upload directory?'))) return Promise.resolve(); - return callClearUpload().then(function(res) { - if (res && res.result === true) + return callClearUpload().then(function() { + if (arguments[0] && arguments[0].result === true) ui.addNotification(null, E('p', {}, _('Saved upload directory cleared'))); else - ui.addNotification(null, E('p', {}, clearUploadError(res)), 'danger'); + ui.addNotification(null, E('p', {}, clearUploadError(arguments[0])), 'danger'); }); }; diff --git a/luci-app-gecoosac/po/zh_Hans/gecoosac.po b/luci-app-gecoosac/po/zh_Hans/gecoosac.po index e378c378..e557ad2e 100644 --- a/luci-app-gecoosac/po/zh_Hans/gecoosac.po +++ b/luci-app-gecoosac/po/zh_Hans/gecoosac.po @@ -147,3 +147,36 @@ msgstr "无法删除上传目录内容" msgid "Upload directory contains configured runtime paths" msgstr "上传目录包含已配置的运行目录" + +msgid "Port must be an integer between 1 and 65535." +msgstr "端口必须是 1 到 65535 之间的整数。" + +msgid "Interface port and management port must be different." +msgstr "接口端口和管理端口必须不同。" + +msgid "Unable to query service status" +msgstr "无法查询服务状态" + +msgid "Service status unavailable" +msgstr "服务状态不可用" + +msgid "Invalid service status response" +msgstr "服务状态响应无效" + +msgid "Unable to resolve upload directory" +msgstr "无法解析上传目录" + +msgid "Upload directory contains a configured protected path" +msgstr "上传目录包含已配置的受保护路径" + +msgid "Unable to validate configured paths" +msgstr "无法验证已配置的路径" + +msgid "Upload directory or its parent is not root-owned and private" +msgstr "上传目录或其父目录不是 root 所有且为私有目录" + +msgid "Unable to prepare upload directory cleanup" +msgstr "无法准备上传目录清理" + +msgid "Unable to recreate upload directory" +msgstr "无法重新创建上传目录" diff --git a/luci-app-gecoosac/po/zh_Hant/gecoosac.po b/luci-app-gecoosac/po/zh_Hant/gecoosac.po index 607f2678..a164cfcd 100644 --- a/luci-app-gecoosac/po/zh_Hant/gecoosac.po +++ b/luci-app-gecoosac/po/zh_Hant/gecoosac.po @@ -147,3 +147,36 @@ msgstr "無法移除上傳目錄內容" msgid "Upload directory contains configured runtime paths" msgstr "上傳目錄包含已設定的執行時路徑" + +msgid "Port must be an integer between 1 and 65535." +msgstr "連接埠必須是 1 到 65535 之間的整數。" + +msgid "Interface port and management port must be different." +msgstr "介面連接埠和管理連接埠必須不同。" + +msgid "Unable to query service status" +msgstr "無法查詢服務狀態" + +msgid "Service status unavailable" +msgstr "服務狀態無法使用" + +msgid "Invalid service status response" +msgstr "服務狀態回應無效" + +msgid "Unable to resolve upload directory" +msgstr "無法解析上傳目錄" + +msgid "Upload directory contains a configured protected path" +msgstr "上傳目錄包含已設定的受保護路徑" + +msgid "Unable to validate configured paths" +msgstr "無法驗證已設定的路徑" + +msgid "Upload directory or its parent is not root-owned and private" +msgstr "上傳目錄或其父目錄不是 root 所有且為私有目錄" + +msgid "Unable to prepare upload directory cleanup" +msgstr "無法準備上傳目錄清理" + +msgid "Unable to recreate upload directory" +msgstr "無法重新建立上傳目錄" diff --git a/luci-app-gecoosac/root/usr/libexec/rpcd/luci.gecoosac b/luci-app-gecoosac/root/usr/libexec/rpcd/luci.gecoosac index 2e997018..0c9f1a6a 100755 --- a/luci-app-gecoosac/root/usr/libexec/rpcd/luci.gecoosac +++ b/luci-app-gecoosac/root/usr/libexec/rpcd/luci.gecoosac @@ -80,31 +80,96 @@ path_in_dir() { [ "${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 +} + configured_path_in_upload() { local option="$1" - local upload_path="$2" - local path real_path + local upload_path="$2" path real_path path="$(uci -q get "gecoosac.config.${option}")" [ -n "$path" ] || return 1 - case "$path" in - /*) ;; - *) return 1 ;; - esac + path="$(normalize_path "$path")" || return 2 + path_in_dir "$path" "$upload_path" && return 0 - real_path="$(readlink -f "$path" 2>/dev/null)" - [ -n "$real_path" ] || real_path="$(normalize_path "$path")" || return 1 + 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" "$upload_path" } +status_result() { + local ok="$1" running="$2" message="$3" + + json_init + json_add_boolean ok "$ok" + json_add_boolean running "$running" + [ -n "$message" ] && json_add_string error "$message" + json_dump + json_cleanup +} + service_status() { local data instances instance running state running=0 - data="$(ubus call service list '{"name":"gecoosac"}' 2>/dev/null)" - if json_load "$data" 2>/dev/null && json_select gecoosac 2>/dev/null && json_select instances 2>/dev/null; then + data="$(ubus call service list '{"name":"gecoosac"}' 2>/dev/null)" || { + status_result 0 0 "Unable to query service status" + return + } + json_load "$data" 2>/dev/null || { + status_result 0 0 "Invalid service status response" + return + } + + if json_select gecoosac 2>/dev/null; then + if ! json_select instances 2>/dev/null; then + json_cleanup + status_result 0 0 "Invalid service status response" + return + fi json_get_keys instances for instance in $instances; do json_select "$instance" 2>/dev/null || continue @@ -117,14 +182,12 @@ service_status() { done fi - json_init - json_add_boolean running "$running" - json_dump json_cleanup + status_result 1 "$running" } clear_upload() { - local path real entry + local path real parent stage option path="$(uci -q get gecoosac.config.upload_dir)" [ -n "$path" ] || path="$DEFAULT_UPLOAD_DIR" @@ -134,31 +197,69 @@ clear_upload() { *) json_result 0 "Expecting an absolute path"; return ;; esac - path="$(normalize_path "$path")" + path="$(normalize_path "$path")" || { json_result 0 "Expecting an absolute path"; return; } real="$(readlink -f "$path" 2>/dev/null)" - [ -n "$real" ] || real="$path" + [ -n "$real" ] || { json_result 0 "Unable to resolve upload directory" "$path"; return; } real="$(normalize_path "$real")" || { json_result 0 "Expecting an absolute path"; return; } - if ! safe_upload_path "$real"; then + if [ "$real" != "$path" ] || ! safe_upload_path "$real"; then json_result 0 "Only Gecoos upload directories can be cleared" "$real" return fi - - if configured_path_in_upload db_dir "$real" || configured_path_in_upload piddir "$real"; then - json_result 0 "Upload directory contains configured runtime paths" "$real" + if [ ! -e "$real" ] && [ ! -L "$real" ]; then + json_result 1 "" "$real" + return + fi + if ! is_secure_upload_dir "$real"; then + json_result 0 "Upload directory or its parent is not root-owned and private" "$real" return fi - [ -d "$real" ] || { json_result 1 "" "$real"; return; } + for option in db_dir piddir crt_file key_file; do + configured_path_in_upload "$option" "$real" + case "$?" in + 0) json_result 0 "Upload directory contains a configured protected path" "$real"; return ;; + 1) ;; + *) json_result 0 "Unable to validate configured paths" "$real"; return ;; + esac + done - for entry in "$real"/* "$real"/.[!.]* "$real"/..?*; do - [ -e "$entry" ] || [ -L "$entry" ] || continue + parent="${real%/*}" + stage="$(mktemp -d "$parent/.gecoosac-clear.XXXXXX" 2>/dev/null)" || { + json_result 0 "Unable to prepare upload directory cleanup" "$real" + return + } + if ! chmod 0700 "$stage"; then + rm -rf "$stage" + json_result 0 "Unable to prepare upload directory cleanup" "$real" + return + fi - if ! rm -rf "$entry"; then - json_result 0 "Unable to remove upload directory contents" "$real" + if ! mv "$real" "$stage/upload"; then + rm -rf "$stage" + json_result 0 "Unable to prepare upload directory cleanup" "$real" + return + fi + + if ! rm -rf "$stage/upload"; then + if [ ! -e "$real" ] && [ ! -L "$real" ]; then + mkdir "$real" 2>/dev/null && chmod 0750 "$real" 2>/dev/null + fi + logger -t gecoosac "upload cleanup retained staged data at $stage" + json_result 0 "Unable to remove upload directory contents" "$real" + return + fi + + if [ ! -d "$real" ]; then + if ! mkdir "$real" && { [ ! -d "$real" ] || [ -L "$real" ]; }; then + json_result 0 "Unable to recreate upload directory" "$real" return fi - done + fi + if [ -L "$real" ] || ! chmod 0750 "$real" || ! is_secure_upload_dir "$real" || ! rmdir "$stage"; then + json_result 0 "Unable to recreate upload directory" "$real" + return + fi json_result 1 "" "$real" } diff --git a/luci-app-kodexplorer/Makefile b/luci-app-kodexplorer/Makefile index fa6d7d1e..2b1065a4 100644 --- a/luci-app-kodexplorer/Makefile +++ b/luci-app-kodexplorer/Makefile @@ -1,15 +1,46 @@ -# Copyright (C) 2018-2022 Lienol +# Copyright (C) 2018-2026 Lienol # # This is free software, licensed under the GNU General Public License v3. # include $(TOPDIR)/rules.mk -LUCI_TITLE:=LuCI support for KodExplorer -LUCI_DEPENDS:=+nginx-ssl +unzip +zoneinfo-asia +php8 +php8-fastcgi +php8-fpm +php8-mod-curl +php8-mod-dom +php8-mod-gd +php8-mod-iconv +php8-mod-mbstring +php8-mod-opcache +php8-mod-session +php8-mod-zip +php8-mod-sqlite3 +php8-mod-pdo +php8-mod-pdo-sqlite +php8-mod-pdo-mysql +php8-mod-xml +php8-mod-xmlreader +php8-mod-xmlwriter -LUCI_PKGARCH:=all +PKG_NAME:=luci-app-kodexplorer PKG_VERSION:=20220109 -PKG_RELEASE:=3 +PKG_RELEASE:=4 + +LUCI_TITLE:=LuCI support for KodExplorer +LUCI_PKGARCH:=all + +define Package/$(PKG_NAME)/config + +config PACKAGE_$(PKG_NAME)_depends + depends on PACKAGE_$(PKG_NAME) + bool + default y + select PACKAGE_nginx-ssl + select PACKAGE_unzip + select PACKAGE_zoneinfo-asia + select PACKAGE_php8 + select PACKAGE_php8-fastcgi + select PACKAGE_php8-fpm + select PACKAGE_php8-mod-curl + select PACKAGE_php8-mod-dom + select PACKAGE_php8-mod-gd + select PACKAGE_php8-mod-iconv + select PACKAGE_php8-mod-mbstring + select PACKAGE_php8-mod-opcache + select PACKAGE_php8-mod-pdo + select PACKAGE_php8-mod-pdo-mysql + select PACKAGE_php8-mod-pdo-sqlite + select PACKAGE_php8-mod-session + select PACKAGE_php8-mod-sqlite3 + select PACKAGE_php8-mod-xml + select PACKAGE_php8-mod-xmlreader + select PACKAGE_php8-mod-xmlwriter + select PACKAGE_php8-mod-zip + +endef include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-app-linkeaselite/Makefile b/luci-app-linkeaselite/Makefile deleted file mode 100644 index beba9b14..00000000 --- a/luci-app-linkeaselite/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (C) 2016 Openwrt.org -# -# This is free software, licensed under the Apache License, Version 2.0 . -# - -include $(TOPDIR)/rules.mk - -LUCI_TITLE:=LuCI support for linkeaselite -LUCI_DEPENDS:=+linkeaselite -LUCI_PKGARCH:=all -PKG_VERSION:=1.0.0 -PKG_RELEASE:=3 -LUCI_MINIFY_CSS:=0 -LUCI_MINIFY_JS:=0 - -include $(TOPDIR)/feeds/luci/luci.mk - -# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-linkeaselite/luasrc/controller/linkeaselite.lua b/luci-app-linkeaselite/luasrc/controller/linkeaselite.lua deleted file mode 100644 index b66b26c7..00000000 --- a/luci-app-linkeaselite/luasrc/controller/linkeaselite.lua +++ /dev/null @@ -1,31 +0,0 @@ -module("luci.controller.linkeaselite", package.seeall) - -function index() - if not nixio.fs.access("/etc/config/linkeaselite") then - return - end - - local page = entry({"admin", "services", "linkeaselite"}, firstchild(), _("LinkEaseLite"), 20) - page.dependent = true - entry({"admin", "services", "linkeaselite", "config"}, cbi("linkeaselite"), _("Settings"), 10).leaf = true - entry({"admin", "services", "linkeaselite_status"}, call("linkeaselite_status")) - entry({"admin", "services", "linkeaselite", "file"}, call("linkeaselite_file_removed")).leaf = true -end - -function linkeaselite_status() - local sys = require "luci.sys" - local uci = require "luci.model.uci".cursor() - local port = tonumber(uci:get_first("linkeaselite", "linkeaselite", "port")) - - local status = { - running = (sys.call("pidof linkease-lite >/dev/null") == 0), - port = (port or 8897) - } - - luci.http.prepare_content("application/json") - luci.http.write_json(status) -end - -function linkeaselite_file_removed() - luci.http.status(404, "Not Found") -end diff --git a/luci-app-linkeaselite/luasrc/model/cbi/linkeaselite.lua b/luci-app-linkeaselite/luasrc/model/cbi/linkeaselite.lua deleted file mode 100644 index c1387f8b..00000000 --- a/luci-app-linkeaselite/luasrc/model/cbi/linkeaselite.lua +++ /dev/null @@ -1,24 +0,0 @@ -local m, s -local sys = require "luci.sys" - -m = Map("linkeaselite", translate("LinkEaseLite"), translate("LinkEaseLite is an efficient data transfer tool for small-memory devices.")) -m:section(SimpleSection).template = "linkeaselite_status" - -m.on_after_commit = function(self) - sys.call("/etc/init.d/linkeaselite restart >/dev/null 2>&1") -end - -s = m:section(TypedSection, "linkeaselite", translate("Global settings")) -s.addremove = false -s.anonymous = true - -s:option(Flag, "enabled", translate("Enable")).rmempty = false - -local port = s:option(Value, "port", translate("Port")) -port.rmempty = false -port.datatype = "port" -port.default = "8897" - -s:option(Flag, "allowPublic", translate("AllowPublic"), translate("Allowing access via public IP addresses can lead to insufficient security.")).rmempty = false - -return m diff --git a/luci-app-linkeaselite/luasrc/view/linkeaselite_status.htm b/luci-app-linkeaselite/luasrc/view/linkeaselite_status.htm deleted file mode 100644 index 34a0e0c3..00000000 --- a/luci-app-linkeaselite/luasrc/view/linkeaselite_status.htm +++ /dev/null @@ -1,42 +0,0 @@ - - -
- <%:LinkEaseLite Status%> -

- <%:Collecting data...%> -

-
diff --git a/luci-app-linkeaselite/po/zh-cn/linkeaselite.po b/luci-app-linkeaselite/po/zh-cn/linkeaselite.po deleted file mode 100644 index 93b611b7..00000000 --- a/luci-app-linkeaselite/po/zh-cn/linkeaselite.po +++ /dev/null @@ -1,35 +0,0 @@ -msgid "LinkEaseLite" -msgstr "易有云Lite" - -msgid "LinkEaseLite is an efficient data transfer tool for small-memory devices." -msgstr "易有云Lite是面向小内存设备的轻量数据传输工具。" - -msgid "Global settings" -msgstr "全局设置" - -msgid "Enable" -msgstr "启用" - -msgid "Port" -msgstr "端口" - -msgid "AllowPublic" -msgstr "公网访问" - -msgid "Allowing access via public IP addresses can lead to insufficient security." -msgstr "允许公网IP访问,会导致不够安全。" - -msgid "Click to open LinkEaseLite" -msgstr "点击打开易有云Lite" - -msgid "The LinkEaseLite service is running." -msgstr "易有云Lite服务已启动" - -msgid "The LinkEaseLite service is not running." -msgstr "易有云Lite服务未启动" - -msgid "LinkEaseLite Status" -msgstr "易有云Lite服务状态" - -msgid "Collecting data..." -msgstr "收集数据..." diff --git a/luci-app-linkeaselite/po/zh_Hans b/luci-app-linkeaselite/po/zh_Hans deleted file mode 120000 index 41451e4a..00000000 --- a/luci-app-linkeaselite/po/zh_Hans +++ /dev/null @@ -1 +0,0 @@ -zh-cn \ No newline at end of file diff --git a/luci-app-linkeaselite/root/etc/uci-defaults/50_luci-linkeaselite b/luci-app-linkeaselite/root/etc/uci-defaults/50_luci-linkeaselite deleted file mode 100644 index 048f3caf..00000000 --- a/luci-app-linkeaselite/root/etc/uci-defaults/50_luci-linkeaselite +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -rm -f /tmp/luci-indexcache /tmp/luci-indexcache.* -exit 0 diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index eff0e476..7ba8a155 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall PKG_VERSION:=26.8.1 -PKG_RELEASE:=202 +PKG_RELEASE:=203 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/luci-app-passwall/luasrc/passwall/util_sing-box.lua index 550b96f3..be04bf81 100644 --- a/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -1105,6 +1105,9 @@ function gen_config(var) local dns_socks_address = var["dns_socks_address"] local dns_socks_port = var["dns_socks_port"] local no_run = var["no_run"] + local use_proxy_list = var["use_proxy_list"] + local use_gfw_list = var["use_gfw_list"] + local chn_list = var["chn_list"] local dns_domain_rules = {} local dns = nil @@ -1552,7 +1555,50 @@ function gen_config(var) end --shunt rule - uci:foreach(appname, "shunt_rules", function(e) + local function foreach_shunt_rule(callback) + uci:foreach(appname, "shunt_rules", callback) + + if use_gfw_list ~= "1" or chn_list ~= "0" then return end + + -- GFW 模式下使用分流节点时添加特定规则 + local function read_proxy_list(path) + if use_proxy_list ~= "1" then return "" end + local map, list = {}, {} + local f = io.open(path) + if f then + for line in f:lines() do + if line ~= "" and not line:find("#", 1, true) and not map[line] then + map[line] = 1 + list[#list + 1] = line + end + end + f:close() + end + return table.concat(list, "\n") + end + + local domain_list = read_proxy_list("/usr/share/passwall/rules/proxy_host") + local ip_list = read_proxy_list("/usr/share/passwall/rules/proxy_ip") + + local bin = api.finded_com("geoview") + if bin then + local geo_file = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat" + if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then + domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw" + end + end + + if domain_list ~= "" or ip_list ~= "" then + node["GFW_Mode_List"] = "_default" + callback({ + [".name"] = "GFW_Mode_List", + remarks = "GFW_Mode_List", + domain_list = (domain_list ~= "") and domain_list or nil, + ip_list = (ip_list ~= "") and ip_list or nil + }) + end + end + foreach_shunt_rule(function(e) local outboundTag = gen_shunt_node(e[".name"]) if outboundTag and e.remarks then if outboundTag == "default" then @@ -1755,6 +1801,14 @@ function gen_config(var) table.insert(rules, rule) end end) + + if use_gfw_list == "1" and chn_list == "0" then -- GFW 模式下使用分流节点时添加兜底规则 + table.insert(rules, { + action = "route", + port_range = { "0:65535" }, + outbound = "direct" + }) + end else COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node or node_id, nil, { fragment = singbox_settings.fragment == "1" or nil, diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua index 8ded862f..bb93f10b 100644 --- a/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -868,6 +868,9 @@ function gen_config(var) local dns_socks_port = var["dns_socks_port"] local loglevel = var["loglevel"] or "warning" local no_run = var["no_run"] + local use_proxy_list = var["use_proxy_list"] + local use_gfw_list = var["use_gfw_list"] + local chn_list = var["chn_list"] local dns_domain_rules = {} local dns = nil @@ -1363,7 +1366,50 @@ function gen_config(var) end --shunt rule - uci:foreach(appname, "shunt_rules", function(e) + local function foreach_shunt_rule(callback) + uci:foreach(appname, "shunt_rules", callback) + + if use_gfw_list ~= "1" or chn_list ~= "0" then return end + + -- GFW 模式下使用分流节点时添加特定规则 + local function read_proxy_list(path) + if use_proxy_list ~= "1" then return "" end + local map, list = {}, {} + local f = io.open(path) + if f then + for line in f:lines() do + if line ~= "" and not line:find("#", 1, true) and not map[line] then + map[line] = 1 + list[#list + 1] = line + end + end + f:close() + end + return table.concat(list, "\n") + end + + local domain_list = read_proxy_list("/usr/share/passwall/rules/proxy_host") + local ip_list = read_proxy_list("/usr/share/passwall/rules/proxy_ip") + + local bin = api.finded_com("geoview") + if bin then + local geo_file = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat" + if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then + domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw" + end + end + + if domain_list ~= "" or ip_list ~= "" then + node["GFW_Mode_List"] = "_default" + callback({ + [".name"] = "GFW_Mode_List", + remarks = "GFW_Mode_List", + domain_list = (domain_list ~= "") and domain_list or nil, + ip_list = (ip_list ~= "") and ip_list or nil + }) + end + end + foreach_shunt_rule(function(e) local outbound_tag = gen_shunt_node(e[".name"]) if outbound_tag and e.remarks then if outbound_tag == "default" then @@ -1463,6 +1509,15 @@ function gen_config(var) end end) + if use_gfw_list == "1" and chn_list == "0" then -- GFW 模式下使用分流节点时添加兜底规则 + table.insert(rules, { + ruleTag = "GFW_Mode_Default", + outboundTag = "direct", + port = (node.domainStrategy == "IPIfNonMatch") and "1-65535" or nil, + network = (node.domainStrategy ~= "IPIfNonMatch") and "tcp,udp" or nil + }) + end + table.insert(rules, { outboundTag = "direct", ip = { "geoip:private" } diff --git a/luci-app-passwall/root/usr/share/passwall/app.sh b/luci-app-passwall/root/usr/share/passwall/app.sh index e1337dd8..a6e26174 100755 --- a/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/luci-app-passwall/root/usr/share/passwall/app.sh @@ -99,7 +99,7 @@ run_ipt2socks() { run_singbox() { local flag type node tcp_redir_port tcp_proxy_way udp_redir_port socks_address socks_port socks_username socks_password http_address http_port http_username http_password local dns_listen_port direct_dns_query_strategy direct_dns_port direct_dns_udp_server direct_dns_tcp_server remote_dns_protocol remote_dns_udp_server remote_dns_tcp_server remote_dns_doh remote_dns_client_ip remote_fakedns remote_dns_query_strategy dns_cache dns_socks_address dns_socks_port - local loglevel log_file config_file server_host server_port no_run + local loglevel log_file config_file server_host server_port no_run use_proxy_list use_gfw_list chn_list eval_set_val "$@" [ -z "$type" ] && { type=$(echo $(config_n_get $node type) | tr 'A-Z' 'a-z') @@ -120,6 +120,9 @@ run_singbox() { [ -n "$flag" ] && json_add_string "flag" "$flag" [ -n "$node" ] && json_add_string "node" "$node" + [ -n "$use_proxy_list" ] && json_add_string "use_proxy_list" "$use_proxy_list" + [ -n "$use_gfw_list" ] && json_add_string "use_gfw_list" "$use_gfw_list" + [ -n "$chn_list" ] && json_add_string "chn_list" "$chn_list" [ -n "$server_host" ] && json_add_string "server_host" "$server_host" [ -n "$server_port" ] && json_add_string "server_port" "$server_port" [ -n "$tcp_redir_port" ] && json_add_string "tcp_redir_port" "$tcp_redir_port" @@ -197,7 +200,7 @@ run_singbox() { run_xray() { local flag type node tcp_redir_port tcp_proxy_way udp_redir_port socks_address socks_port socks_username socks_password http_address http_port http_username http_password local dns_listen_port direct_dns_query_strategy direct_dns_port direct_dns_udp_server direct_dns_tcp_server remote_dns_protocol remote_dns_udp_server remote_dns_tcp_server remote_dns_doh remote_dns_client_ip remote_fakedns remote_dns_query_strategy dns_cache dns_socks_address dns_socks_port - local loglevel log_file config_file server_host server_port no_run + local loglevel log_file config_file server_host server_port no_run use_proxy_list use_gfw_list chn_list eval_set_val "$@" [ -z "$type" ] && { type=$(echo $(config_n_get $node type) | tr 'A-Z' 'a-z') @@ -209,6 +212,9 @@ run_xray() { [ -z "$loglevel" ] && local loglevel=$(config_t_get global loglevel "warning") [ -n "$flag" ] && json_add_string "flag" "$flag" [ -n "$node" ] && json_add_string "node" "$node" + [ -n "$use_proxy_list" ] && json_add_string "use_proxy_list" "$use_proxy_list" + [ -n "$use_gfw_list" ] && json_add_string "use_gfw_list" "$use_gfw_list" + [ -n "$chn_list" ] && json_add_string "chn_list" "$chn_list" [ -n "$server_host" ] && json_add_string "server_host" "$server_host" [ -n "$server_port" ] && json_add_string "server_port" "$server_port" [ -n "$tcp_redir_port" ] && json_add_string "tcp_redir_port" "$tcp_redir_port" @@ -760,6 +766,7 @@ run_redir() { } NEXT_DNS_LISTEN_PORT=$(expr $NEXT_DNS_LISTEN_PORT + 1) } + _args="${_args} use_proxy_list=$USE_PROXY_LIST use_gfw_list=$USE_GFW_LIST chn_list=$CHN_LIST" run_singbox flag=$_flag node=$node tcp_redir_port=$local_port tcp_proxy_way=$TCP_PROXY_WAY config_file=$config_file log_file=$log_file ${_args} ;; xray) @@ -846,6 +853,7 @@ run_redir() { } NEXT_DNS_LISTEN_PORT=$(expr $NEXT_DNS_LISTEN_PORT + 1) } + _args="${_args} use_proxy_list=$USE_PROXY_LIST use_gfw_list=$USE_GFW_LIST chn_list=$CHN_LIST" run_xray flag=$_flag node=$node tcp_redir_port=$local_port tcp_proxy_way=$TCP_PROXY_WAY config_file=$config_file log_file=$log_file ${_args} ;; naiveproxy) @@ -1751,6 +1759,7 @@ acl_app() { } config_file="$TMP_PATH/$config_file" [ "${type}" = "sing-box" ] && type="singbox" + _extra_param="${_extra_param} use_proxy_list=$use_proxy_list use_gfw_list=$use_gfw_list chn_list=$chn_list" run_${type} flag=$tcp_node node=$tcp_node tcp_redir_port=$redir_port ${_extra_param} config_file=$config_file log_file=$log_file loglevel=$loglevel else config_file="acl/${tcp_node}_SOCKS_${socks_port}.json" diff --git a/luci-app-passwall/root/usr/share/passwall/iptables.sh b/luci-app-passwall/root/usr/share/passwall/iptables.sh index 40bddf26..41e90b23 100755 --- a/luci-app-passwall/root/usr/share/passwall/iptables.sh +++ b/luci-app-passwall/root/usr/share/passwall/iptables.sh @@ -1398,8 +1398,8 @@ del_firewall_rule() { done for ipt in "$ipt_f" "$ip6t_f"; do for chain in "FORWARD" "INPUT" "OUTPUT"; do - for i in $(seq 1 $($ipt -nL $chain | grep -c PSW)); do - local index=$($ipt --line-number -nL $chain | grep PSW | head -1 | awk '{print $1}') + for i in $(seq 1 $($ipt -nL $chain | grep -c PSW_REJECT)); do + local index=$($ipt --line-number -nL $chain | grep PSW_REJECT | head -1 | awk '{print $1}') $ipt -D $chain $index 2>/dev/null done done diff --git a/luci-app-store/Makefile b/luci-app-store/Makefile index 70378a9e..603458f8 100644 --- a/luci-app-store/Makefile +++ b/luci-app-store/Makefile @@ -8,7 +8,6 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=LuCI based ipk/apk store LUCI_DESCRIPTION:=luci-app-store is a ipk/apk store developed by LinkEase team LUCI_DEPENDS:=@(x86_64||aarch64) +curl +tar +libuci-lua +mount-utils +luci-lib-taskd -LUCI_DEPENDS+=$(if $(CONFIG_USE_APK),+apk +luci-compat,+opkg) LUCI_EXTRA_DEPENDS:=luci-lib-taskd (>=1.0.19) LUCI_PKGARCH:=all diff --git a/natflow/Makefile b/natflow/Makefile index d19e0fc5..561b319e 100644 --- a/natflow/Makefile +++ b/natflow/Makefile @@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk PKG_NAME:=natflow PKG_VERSION:=20260531 -PKG_RELEASE:=60 +PKG_RELEASE:=61 PKG_SOURCE:=$(PKG_VERSION).tar.xz PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git PKG_SOURCE_PROTO:=git -PKG_SOURCE_VERSION:=c4c0370953045bac23728fbeff456fc584cd9c80 +PKG_SOURCE_VERSION:=4e62e176dee1ee5d2bea09c40f5d515d6b8a3655 PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_MAINTAINER:=Chen Minqiang PKG_LICENSE:=GPL-2.0 diff --git a/openwrt-fastfetch/Makefile b/openwrt-fastfetch/Makefile index bd737262..cedf67fb 100644 --- a/openwrt-fastfetch/Makefile +++ b/openwrt-fastfetch/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=fastfetch -PKG_VERSION:=2.66.0 -PKG_RELEASE:=12 +PKG_VERSION:=2.67.0 +PKG_RELEASE:=13 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/fastfetch-cli/fastfetch/tar.gz/$(PKG_VERSION)?