Sync 2026-08-09 20:41:06

This commit is contained in:
github-actions[bot]
2026-08-09 20:41:06 +08:00
parent 48000bd00d
commit a19a34b456
14 changed files with 196 additions and 88 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=gecoosac
PKG_VERSION:=2.2.20251015
PKG_RELEASE:=18
PKG_RELEASE:=19
PKG_MAINTAINER:=Roc Lai <laipeng668@qq.com>
PKG_LICENSE:=AGPL-3.0-only
+50 -3
View File
@@ -86,6 +86,29 @@ normalize_path() {
printf '%s\n' "$normalized"
}
path_uses_clear_stage() {
local path="$1" resolved
path="$(normalize_path "$path")" || return 1
case "$path" in
/.gecoosac-clear.*|/.gecoosac-clear.*/*|*/.gecoosac-clear.*|*/.gecoosac-clear.*/*)
return 0
;;
esac
if [ -e "$1" ] || [ -L "$1" ]; then
resolved="$(readlink -f "$1" 2>/dev/null)" || return 1
resolved="$(normalize_path "$resolved")" || return 1
case "$resolved" in
/.gecoosac-clear.*|/.gecoosac-clear.*/*|*/.gecoosac-clear.*|*/.gecoosac-clear.*/*)
return 0
;;
esac
fi
return 1
}
is_safe_upload_dir() {
local path physical
@@ -468,12 +491,20 @@ normalize_conf() {
logger -t gecoosac "refusing unsupported upload directory: $upload_dir"
return 1
fi
if path_uses_clear_stage "$upload_dir"; then
logger -t gecoosac "refusing reserved upload cleanup stage path: $upload_dir"
return 1
fi
if is_safe_db_dir "$db_dir" "$upload_dir"; then
db_dir="$(normalize_path "$db_dir")"
else
logger -t gecoosac "refusing unsupported database directory: $db_dir"
return 1
fi
if path_uses_clear_stage "$db_dir"; then
logger -t gecoosac "refusing reserved upload cleanup stage path: $db_dir"
return 1
fi
if [ "$isonlyoneprot" = "0" ] && [ "$https" = "1" ]; then
is_abs_path "$crt_file" || {
logger -t gecoosac "refusing non-absolute certificate path: $crt_file"
@@ -483,6 +514,10 @@ normalize_conf() {
logger -t gecoosac "refusing non-absolute key path: $key_file"
return 1
}
if path_uses_clear_stage "$crt_file" || path_uses_clear_stage "$key_file"; then
logger -t gecoosac "refusing reserved upload cleanup stage certificate path"
return 1
fi
fi
if is_safe_pid_dir "$piddir" "$upload_dir"; then
piddir="$(normalize_path "$piddir")"
@@ -490,6 +525,10 @@ normalize_conf() {
logger -t gecoosac "refusing unsupported PID directory: $piddir"
return 1
fi
if path_uses_clear_stage "$piddir"; then
logger -t gecoosac "refusing reserved upload cleanup stage path: $piddir"
return 1
fi
is_port "$port" || {
logger -t gecoosac "refusing invalid interface port: $port"
return 1
@@ -659,8 +698,16 @@ start_prepared_service() {
}
start_service() {
prepare_service || exit 1
start_prepared_service
service_prepare_failed=0
if ! prepare_service; then
service_prepare_failed=1
return 0
fi
start_prepared_service || service_prepare_failed=1
}
service_started() {
[ "${service_prepare_failed:-0}" = "0" ]
}
service_running_state() {
@@ -709,7 +756,7 @@ wait_service_stopped() {
state="$?"
case "$state" in
0) return 0 ;;
1)
1|2)
[ "$i" -ge 5 ] && return 1
sleep 1
i=$((i + 1))
+1 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-gecoosac
PKG_VERSION:=2.2
PKG_RELEASE:=18
PKG_RELEASE:=19
LUCI_TITLE:=LuCI Support for gecoosac
LUCI_DEPENDS:=+luci-base +gecoosac +gecoosac-files
@@ -14,6 +14,7 @@ const DEFAULT_PID_DIR = '/var/run';
const CONFIG_BACKUP_DIR = '/etc/gecoosac';
const DB_DIR_PREFIXES = [ '/etc/gecoosac', '/tmp/gecoosac', '/var/lib/gecoosac' ];
const PID_DIR_PREFIXES = [ '/var/run', '/tmp/gecoosac' ];
const CLEAR_STAGE_PATH_ERROR = _('Paths under .gecoosac-clear.* are reserved for upload cleanup.');
let statusPollRegistered = false;
@@ -92,7 +93,12 @@ function validateCertificatePath(section_id, value, singlePortOption, httpsOptio
if (singlePortOption.formvalue(section_id) !== '0' || httpsOption.formvalue(section_id) !== '1' || !value)
return true;
return String(value).charAt(0) === '/' ? true : _('Expecting an absolute path');
if (String(value).charAt(0) !== '/')
return _('Expecting an absolute path');
return usesClearStagePath(value)
? CLEAR_STAGE_PATH_ERROR
: true;
}
function triggerActiveValidation(section_id, options) {
@@ -134,6 +140,17 @@ function normalizePath(value) {
return '/' + parts.join('/');
}
function usesClearStagePath(value) {
const path = normalizePath(value);
const segments = path === null ? [] : path.split('/');
for (const segment of segments)
if (segment.indexOf('.gecoosac-clear.') === 0)
return true;
return false;
}
function managedPath(value, policy) {
const path = normalizePath(value);
@@ -247,14 +264,14 @@ function clientHost() {
return host;
}
function clientUrl() {
const singlePort = uci.get('gecoosac', 'config', 'isonlyoneprot') !== '0';
const https = uci.get('gecoosac', 'config', 'https') === '1';
const port = singlePort
? validPort(uci.get('gecoosac', 'config', 'port'), '60650')
: validPort(uci.get('gecoosac', 'config', 'm_port'), '8080');
function clientUrl(status) {
const protocol = status && status.protocol;
const port = validPort(status && status.port, null);
return (singlePort || !https ? 'http://' : 'https://') + clientHost() + ':' + port;
if ((protocol === 'http' || protocol === 'https') && port !== null)
return protocol + '://' + clientHost() + ':' + port;
return null;
}
function renderStatusContent(status) {
@@ -263,12 +280,13 @@ function renderStatusContent(status) {
(RPC_ERROR_MESSAGES[status.error] || _('Unable to query service status')));
const running = serviceRunning(status);
const url = running ? clientUrl(status) : null;
const text = running
? _('The GecoosAC service is running.')
: _('The GecoosAC service is not running.');
const state = E('span', { 'class': running ? 'gecoosac-running' : 'gecoosac-stopped' }, text);
if (!running)
if (!running || !url)
return E('p', {}, state);
return E('p', {}, [
@@ -276,7 +294,7 @@ function renderStatusContent(status) {
E('button', {
'class': 'cbi-button cbi-button-reload',
'click': function() {
const client = window.open(clientUrl(), '_blank', 'noopener');
const client = window.open(url, '_blank', 'noopener');
if (client)
client.opener = null;
}
@@ -442,6 +460,8 @@ return view.extend({
o.validate = function(section_id, value) {
if (usesManagedPath(value) && !pathPolicy)
return _('Unable to validate /var paths on this system.');
if (usesClearStagePath(value))
return CLEAR_STAGE_PATH_ERROR;
return validUploadDir(value, pathPolicy)
? true
@@ -458,6 +478,8 @@ return view.extend({
const uploadDir = uploadDirOption.formvalue(section_id) || DEFAULT_UPLOAD_DIR;
if ((usesManagedPath(value) || usesManagedPath(uploadDir)) && !pathPolicy)
return _('Unable to validate /var paths on this system.');
if (usesClearStagePath(value))
return CLEAR_STAGE_PATH_ERROR;
if (!validPathPrefix(value, DB_DIR_PREFIXES))
return _('Database directory must be under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac.');
@@ -477,6 +499,8 @@ return view.extend({
const uploadDir = uploadDirOption.formvalue(section_id) || DEFAULT_UPLOAD_DIR;
if ((usesManagedPath(value) || usesManagedPath(uploadDir)) && !pathPolicy)
return _('Unable to validate /var paths on this system.');
if (usesClearStagePath(value))
return CLEAR_STAGE_PATH_ERROR;
if (!validPathPrefix(value, PID_DIR_PREFIXES))
return _('PID directory must be under /var/run or /tmp/gecoosac.');
+3
View File
@@ -193,6 +193,9 @@ msgstr "无法验证上传目录清理暂存区"
msgid "Upload cleanup stage contains a configured protected path"
msgstr "上传目录清理暂存区包含已配置的受保护路径"
msgid "Paths under .gecoosac-clear.* are reserved for upload cleanup."
msgstr ".gecoosac-clear.* 下的路径保留用于上传目录清理。"
msgid "Unable to recreate upload directory"
msgstr "无法重新创建上传目录"
+3
View File
@@ -193,6 +193,9 @@ msgstr "無法驗證上傳目錄清理暫存區"
msgid "Upload cleanup stage contains a configured protected path"
msgstr "上傳目錄清理暫存區包含已設定的受保護路徑"
msgid "Paths under .gecoosac-clear.* are reserved for upload cleanup."
msgstr ".gecoosac-clear.* 下的路徑保留供上傳目錄清理使用。"
msgid "Unable to recreate upload directory"
msgstr "無法重新建立上傳目錄"
@@ -243,20 +243,78 @@ configured_path_in_upload() {
}
status_result() {
local ok="$1" running="$2" message="$3"
local ok="$1" running="$2" message="$3" protocol="$4" port="$5"
json_init
json_add_boolean ok "$ok"
json_add_boolean running "$running"
[ -n "$message" ] && json_add_string error "$message"
[ -n "$protocol" ] && json_add_string protocol "$protocol"
[ -n "$port" ] && json_add_string port "$port"
json_dump
json_cleanup
}
runtime_endpoint() {
local keys key argument pending interface_port management_port
local single_port https_enabled
status_protocol=http
status_port=
pending=
interface_port=
management_port=
single_port=1
https_enabled=0
json_select command 2>/dev/null || return 1
json_get_keys keys
for key in $keys; do
json_get_var argument "$key"
if [ -n "$pending" ]; then
case "$pending" in
interface) interface_port="$argument" ;;
management) management_port="$argument" ;;
single) single_port="$argument" ;;
https) https_enabled="$argument" ;;
esac
pending=
continue
fi
case "$argument" in
-p) pending=interface ;;
-mp) pending=management ;;
-isonlyoneprot) pending=single ;;
-https) pending=https ;;
esac
done
json_select ..
case "$single_port" in
0)
[ -n "$management_port" ] || return 1
status_port="$management_port"
[ "$https_enabled" = "1" ] && status_protocol=https
;;
1)
status_port="$interface_port"
;;
*) return 1 ;;
esac
case "$status_port" in
""|*[!0-9]*) return 1 ;;
esac
[ "$status_port" -ge 1 ] 2>/dev/null && [ "$status_port" -le 65535 ]
}
service_status() {
local data instances instance running state
local data instances instance running state status_protocol status_port
running=0
status_protocol=
status_port=
data="$(ubus call service list '{"name":"gecoosac"}' 2>/dev/null)" || {
status_result 0 0 "Unable to query service status"
return
@@ -272,17 +330,18 @@ service_status() {
for instance in $instances; do
json_select "$instance" 2>/dev/null || continue
json_get_var state running
json_select ..
if [ "$state" = "1" ]; then
running=1
runtime_endpoint
break
fi
json_select ..
done
fi
fi
json_cleanup
status_result 1 "$running"
status_result 1 "$running" "" "$status_protocol" "$status_port"
}
path_policy() {
+1 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.8.7
PKG_RELEASE:=73
PKG_RELEASE:=74
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -24,24 +24,16 @@ for k, e in ipairs(api.get_valid_nodes()) do
end
local dynamicList_write = function(self, section, value)
local t = {}
local t2 = {}
local new_t = {}
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
new_t = api.table_remove_duplicates(value)
else
t = { value }
new_t = { value }
end
t = table.concat(t, " ")
return DynamicList.write(self, section, t)
local new_val = table.concat(new_t, " ")
return DynamicList.write(self, section, new_val)
end
local doh_validate = function(self, value, t)
if value ~= "" then
local flag = 0
@@ -168,23 +168,14 @@ source.validate = function(self, value, t)
end
local dynamicList_write = function(self, section, value)
local t = {}
local t2 = {}
local new_t = {}
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
new_t = api.table_remove_duplicates(value)
else
t = { value }
new_t = { value }
end
t = table.concat(t, " ")
return DynamicList.write(self, section, t)
local new_val = table.concat(new_t, " ")
return DynamicList.write(self, section, new_val)
end
source.write = dynamicList_write
@@ -534,22 +534,13 @@ o:value("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
o:value("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
function o.custom_write(self, section, value)
local t = {}
local t2 = {}
local new_t
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
new_t = api.table_remove_duplicates(value)
else
t = { value }
new_t = { value }
end
m:set(section, self.option:sub(1 + #option_prefix), t)
m:set(section, self.option:sub(1 + #option_prefix), new_t)
end
o = s:option(TextValue, _n("reality_mldsa65Verify"), "ML-DSA-65 " .. translate("Public key"))
@@ -590,22 +590,13 @@ o:value("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
o:value("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
o:depends({ [_n("tls")] = true })
function o.custom_write(self, section, value)
local t = {}
local t2 = {}
local new_t
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
new_t = api.table_remove_duplicates(value)
else
t = { value }
new_t = { value }
end
m:set(section, self.option:sub(1 + #option_prefix), t)
m:set(section, self.option:sub(1 + #option_prefix), new_t)
end
o = s:option(Flag, _n("ech"), translate("ECH"))
@@ -218,23 +218,14 @@ o:depends({ [_n("reality")] = true })
o = s:option(DynamicList, _n("reality_serverNames"), translate("serverNames"))
o:depends({ [_n("reality")] = true })
function o.write(self, section, value)
local t = {}
local t2 = {}
function o.custom_write(self, section, value)
local new_t = {}
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
new_t = api.table_remove_duplicates(value)
else
t = { value }
new_t = { value }
end
return DynamicList.write(self, section, t)
m:set(section, self.option:sub(1 + #option_prefix), new_t)
end
o = s:option(ListValue, _n("alpn"), translate("alpn"))
@@ -1863,3 +1863,19 @@ function get_network_devices()
table.sort(_devices, function(a, b) return a.sort < b.sort end)
return _devices
end
function table_remove_duplicates(t)
if not t or #t == 0 then return nil end
local t_lookup = {}
local new_t = {}
local x
for _, x in ipairs(t) do
if x and #x > 0 then
if not t_lookup[x] then
t_lookup[x] = x
new_t[#new_t+1] = x
end
end
end
return new_t
end