Files
op-packages/openlist2/files/openlist2.common
T

260 lines
10 KiB
Bash

#!/bin/sh
# Shared validation for the service and its limited LuCI helper.
openlist2_error() {
printf 'openlist2: %s\n' "$*" >&2
logger -t openlist2 "$*" 2>/dev/null
return 1
}
openlist2_read_scalar() {
# Preserve the value itself, including any trailing newline. Remove only
# the one output terminator emitted by uci/jsonfilter.
OPENLIST2_VALUE="$("$@"; printf '.')"
OPENLIST2_VALUE="${OPENLIST2_VALUE%.}"
OPENLIST2_VALUE="${OPENLIST2_VALUE%'
'}"
}
openlist2_port() {
local value="$1"
[ "$value" = "-1" ] && [ "${2:-0}" = "1" ] && { printf '%s\n' -1; return 0; }
case "$value" in ''|*[!0-9]*) return 1 ;; esac
value="$(printf '%s' "$value" | sed 's/^0*//')"
[ -n "$value" ] && [ "${#value}" -le 5 ] || return 1
[ "$value" -ge 1 ] && [ "$value" -le 65535 ] || return 1
printf '%s\n' "$value"
}
openlist2_safe_log_path() {
local filename="${1##*/}"
[ "$1" = "/var/log/$filename" ] || return 1
case "$filename" in ''|*[!A-Za-z0-9._-]*) return 1 ;; esac
printf '%s\n' "$1" | grep -Eq '^/var/log/openlist2([._-][A-Za-z0-9._-]+)?$' || return 1
# Do not let a root helper follow a user-created log file symlink.
[ ! -L "$1" ] && { [ ! -e "$1" ] || [ -f "$1" ]; }
}
openlist2_normalize_dir() {
local path="$1" suffix="" part
case "$path" in /*) ;; *) return 1 ;; esac
case "$path" in *'
'*|*' '*|*"$(printf '\r')"*) return 1 ;; esac
if printf '%s' "$path" | LC_ALL=C grep -q '[[:cntrl:]]'; then return 1; fi
path="$(printf '%s' "$path" | sed 's://*:/:g; s:/*$::')"
[ -n "$path" ] || path="/"
case "$path/" in */../*|*/./*) return 1 ;; esac
while [ ! -d "$path" ]; do
[ ! -e "$path" ] && [ ! -L "$path" ] || return 1
part="${path##*/}"
suffix="/$part$suffix"
path="${path%/*}"
[ -n "$path" ] || path="/"
done
path="$(CDPATH='' cd "$path" 2>/dev/null && pwd -P && printf '.')" || return 1
path="${path%.}"
path="${path%'
'}"
case "$path" in *'
'*) return 1 ;; esac
if printf '%s' "$path" | LC_ALL=C grep -q '[[:cntrl:]]'; then return 1; fi
[ "$path" != "/" ] || [ -n "$suffix" ] || { printf '/\n'; return 0; }
printf '%s\n' "${path%/}$suffix"
}
openlist2_contains_path() {
case "$2/" in "$1/"*) return 0 ;; esac
return 1
}
openlist2_broad_dir() {
case "$1" in
''|/|/bin|/boot|/dev|/etc|/home|/lib|/lib64|/media|/mnt|/opt|/overlay|/proc|/root|/rom|/run|/sbin|/srv|/sys|/tmp|/usr|/var|/www|/tmp/log|/tmp/run|/usr/*|/bin/*|/sbin/*|/lib/*|/lib64/*|/dev/*|/proc/*|/sys/*|/rom/*|/overlay/*|/etc/config|/etc/config/*|/etc/dropbear|/etc/dropbear/*|/etc/ssl|/etc/ssl/*|/tmp/log/*|/www/*)
return 0 ;;
esac
return 1
}
openlist2_mountpoint() {
[ -d "$1" ] || return 1
if command -v mountpoint >/dev/null 2>&1; then
mountpoint -q "$1"
return $?
fi
awk -v target="$1" '
{
p = $5
gsub(/\\040/, " ", p)
gsub(/\\134/, "\\", p)
if (p == target) found = 1
}
END { exit !found }
' /proc/self/mountinfo
}
openlist2_validate_dirs() {
data_dir="$(openlist2_normalize_dir "$data_dir")" ||
{ openlist2_error "Data directory must be an absolute directory path without dot segments."; return 1; }
temp_dir="$(openlist2_normalize_dir "$temp_dir")" ||
{ openlist2_error "Cache directory must be an absolute directory path without dot segments."; return 1; }
bleve_dir="$(openlist2_normalize_dir "${bleve_dir:-$data_dir/bleve}")" ||
{ openlist2_error "Invalid Bleve index directory."; return 1; }
local path protected
for path in "$data_dir" "$temp_dir" "$bleve_dir"; do
if openlist2_broad_dir "$path" || openlist2_mountpoint "$path"; then
openlist2_error "Use a dedicated subdirectory, not a system directory or mount root: $path"
return 1
fi
done
case "$temp_dir" in
/mnt/*|/media/*)
path="${temp_dir#/}"; path="${path#*/}"
case "$path" in */*) ;; *)
openlist2_error "Use a cache subdirectory below the disk mount: $temp_dir"; return 1 ;;
esac ;;
esac
if openlist2_contains_path "$temp_dir" "$data_dir" ||
openlist2_contains_path "$bleve_dir" "$data_dir" ||
openlist2_contains_path "$temp_dir" "$bleve_dir" ||
openlist2_contains_path "$bleve_dir" "$temp_dir"; then
openlist2_error "Cache and index directories must be separate and must not contain the data directory."
return 1
fi
if [ -n "$dist_dir" ]; then
dist_dir="$(openlist2_normalize_dir "$dist_dir")" || return 1
if openlist2_broad_dir "$dist_dir" || openlist2_mountpoint "$dist_dir" ||
openlist2_contains_path "$dist_dir" "$data_dir" ||
openlist2_contains_path "$dist_dir" "$temp_dir" ||
openlist2_contains_path "$temp_dir" "$dist_dir" ||
openlist2_contains_path "$dist_dir" "$bleve_dir" ||
openlist2_contains_path "$bleve_dir" "$dist_dir"; then
openlist2_error "Frontend files must be separate from private data, cache and index files."
return 1
fi
[ -f "$dist_dir/index.html" ] && [ -r "$dist_dir/index.html" ] ||
{ openlist2_error "The frontend directory must contain a readable index.html."; return 1; }
fi
# shellcheck disable=SC2154
for path in "$ssl_cert" "$ssl_key"; do
[ -n "$path" ] || continue
protected="$(readlink -f "$path" 2>/dev/null)" || protected="$path"
if openlist2_contains_path "$temp_dir" "$protected" ||
{ [ -n "$dist_dir" ] && openlist2_contains_path "$dist_dir" "$protected"; }; then
openlist2_error "TLS files must not be inside the cache or public frontend directory."
return 1
fi
done
}
openlist2_prepare_dirs() {
local entry identity previous registered nonempty=0 marker="$data_dir/.temp-dir"
umask 077
mkdir -p "$data_dir" "$temp_dir" "$bleve_dir" ||
{ openlist2_error "Unable to create data, cache or index directory."; return 1; }
[ ! -L "$data_dir/config.json" ] && [ ! -L "$marker" ] ||
{ openlist2_error "Config and cache ownership files must not be symlinks."; return 1; }
[ ! -e "$data_dir/config.json" ] || [ -f "$data_dir/config.json" ] ||
{ openlist2_error "config.json must be a regular file."; return 1; }
[ ! -e "$marker" ] || [ -f "$marker" ] ||
{ openlist2_error "Cache ownership marker must be a regular file."; return 1; }
# OpenWrt's BusyBox find may omit -quit. Include hidden entries and
# dangling symlinks without depending on optional find applets.
for entry in "$temp_dir"/* "$temp_dir"/.[!.]* "$temp_dir"/..?*; do
[ -e "$entry" ] || [ -L "$entry" ] || continue
nonempty=1
break
done
identity="$(stat -c '%d:%i' "$temp_dir")" ||
{ openlist2_error "Unable to identify the cache directory; ensure coreutils-stat is installed."; return 1; }
registered="$(cat "$marker" 2>/dev/null)"
if [ "$nonempty" = "1" ] && [ "$registered" != "$(printf '%s\n%s' "$temp_dir" "$identity")" ]; then
# An existing config identifies the old cache on the first upgrade.
# Once registered, an inode/device change must never inherit ownership.
previous=""
if [ ! -e "$marker" ] && [ -s "$data_dir/config.json" ]; then
openlist2_read_scalar jsonfilter -qi "$data_dir/config.json" -e '@.temp_dir' 2>/dev/null
previous="$OPENLIST2_VALUE"
previous="$(openlist2_normalize_dir "$previous")" || previous=""
fi
[ "$previous" = "$temp_dir" ] ||
{ openlist2_error "Refusing to clear a non-empty, unregistered cache directory. Choose a new empty subdirectory: $temp_dir"; return 1; }
fi
chmod 700 "$data_dir" "$temp_dir" "$bleve_dir" || return 1
# Keep the marker outside the cache: OpenList clears cache contents.
(
local marker_tmp
marker_tmp="$(mktemp "$data_dir/.temp-dir.XXXXXX")" || exit 1
trap 'rm -f "$marker_tmp"' 0
printf '%s\n%s\n' "$temp_dir" "$identity" > "$marker_tmp" &&
chmod 600 "$marker_tmp" && mv -f "$marker_tmp" "$marker"
) || { openlist2_error "Unable to record cache directory ownership."; return 1; }
}
openlist2_migrate_config() {
local prefix='openlist2.@openlist2[0]' old_port old_ssl http https changed=0 size backups schema legacy
uci -q get "$prefix" >/dev/null || return 0
old_port="$(uci -q get "$prefix.port")"
old_ssl="$(uci -q get "$prefix.ssl")"
http="$(uci -q get "$prefix.listen_http_port")"
https="$(uci -q get "$prefix.listen_https_port")"
if { [ -z "$http" ] || [ -z "$https" ]; } && { [ -n "$old_port" ] || [ -n "$old_ssl" ]; }; then
old_port="$(openlist2_port "${old_port:-5244}")" ||
{ openlist2_error "Invalid legacy listen port; migration stopped."; return 1; }
case "$old_ssl" in 1|on|true|yes|enabled) http="${http:--1}"; https="${https:-$old_port}" ;; *)
http="${http:-$old_port}"; https="${https:--1}" ;;
esac
uci set "$prefix.listen_http_port=$http" &&
uci set "$prefix.listen_https_port=$https" || return 1
changed=1
fi
if [ -n "$http" ] || [ -n "$https" ]; then
legacy="$(openlist2_port "$old_port" 2>/dev/null)" || legacy=""
if [ -n "$legacy" ]; then
uci set "$prefix.legacy_firewall_port=$legacy" || return 1
fi
if [ -n "$old_port" ]; then uci -q delete "$prefix.port" || return 1; changed=1; fi
if [ -n "$old_ssl" ]; then uci -q delete "$prefix.ssl" || return 1; changed=1; fi
fi
schema="$(uci -q get "$prefix.config_version")"
if [ "$schema" != "2" ]; then
size="$(uci -q get "$prefix.log_max_size")"
backups="$(uci -q get "$prefix.log_max_backups")"
case "$size:$backups" in
50:30|10:5)
uci set "$prefix.log_max_size=1" && uci set "$prefix.log_max_backups=3" || return 1 ;;
esac
# These switches have no implementation in OpenList 4.2.6.
uci -q delete "$prefix.upload_task_persistant"
uci -q delete "$prefix.decompress_upload_task_persistant"
uci set "$prefix.config_version=2" || return 1
changed=1
fi
# Retry a previously staged migration if its commit failed.
if [ "$changed" = "1" ] || uci -q changes openlist2 | grep -q '\.config_version='; then
uci commit openlist2 || { openlist2_error "Unable to save migrated UCI configuration."; return 1; }
fi
return 0
}
openlist2_pasv_ports() {
local values="$1" item first last result="" oldifs="$IFS" noglob=0
case "$values" in *[!0-9,\ -]*) return 1 ;; esac
case "$-" in *f*) noglob=1 ;; *) set -f ;; esac
IFS=' ,'
for item in $values; do
IFS="$oldifs"
first="${item%%-*}"; last="${item#*-}"
first="$(openlist2_port "$first")" && last="$(openlist2_port "$last")" &&
[ "$first" -ge 1024 ] && [ "$first" -le "$last" ] || {
[ "$noglob" = "1" ] || set +f
return 1
}
if [ "$first" = "$last" ]; then item="$first"; else item="$first-$last"; fi
result="${result:+$result }$item"
IFS=' ,'
done
IFS="$oldifs"
[ "$noglob" = "1" ] || set +f
printf '%s\n' "$result"
}