| Server IP : 51.79.230.26 / Your IP : 216.73.217.128 Web Server : LiteSpeed System : Linux sg2.exonhost.com 5.14.0-611.55.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue May 19 15:19:29 EDT 2026 x86_64 User : mukutpub ( 1151) PHP Version : 8.2.33 Disable Function : eval, show_source, system, shell_exec, passthru, exec, popen, proc_open, allow_url_fopen, symlink, mail MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /proc/1758489/root/usr/local/lsws/lsns/bin/ |
Upload File : |
#!/bin/bash
# Starts/stops redis user services
# Parameters start|stop $USER
if (( $# < 2 )); then
echo "You must specify an action and a user"
exit 1
fi
ACTION=$1
USER=$2
HOMEDIR=$(/usr/bin/getent passwd "$USER" | /usr/bin/cut -d: -f6)
# Cage-dir selection: resolve the same host classification the writer's
# get_dir_filenames() reaches, but from user space. This unit runs as the
# tenant (User=%i in user-redis@.service), so it cannot read the root-owned
# httpd_config.xml the writer greps for <namespace>2</namespace>: that grep
# silently fails on permission here and would always fall back to ~/.cagefs,
# breaking LiteSpeed Containers hosts (namespace=2, no CageFS) that must use
# ~/.lsns. The absence of /etc/cagefs/cagefs.mp is a root-independent,
# user-readable signal that distinguishes the two mutually exclusive host
# types: LiteSpeed Containers (no CageFS) -> ~/.lsns; CloudLinux CageFS -> ~/.cagefs.
#
# This cagefs.mp proxy only agrees with the writer under the environmental
# invariant that CloudLinux CageFS and LiteSpeed Containers are mutually
# exclusive: cagefs.mp absent <=> namespace=2. On such supported hosts the
# reader and writer always resolve the same cage. Off that invariant they
# diverge in BOTH directions, and each is an unsupported CloudLinux+Containers
# configuration:
# - both markers (cagefs.mp present AND namespace=2): writer picks ~/.lsns,
# this reader picks ~/.cagefs.
# - neither marker (cagefs.mp absent AND namespace!=2): writer picks
# ~/.cagefs, this reader falls back to ~/.lsns.
# Note redis_cache.sh's validate_environment() keys off a different pair of
# markers (cagefs.mp and the container_file), so it is not a guarantee that
# this reader and the namespace-based writer stay in sync.
if [[ ! -e "/etc/cagefs/cagefs.mp" ]] ; then
SOCKDIR1="${HOMEDIR}/.lsns"
SOCKDIR="${HOMEDIR}/.lsns/tmp"
REDISDIR="${HOMEDIR}/.lsns/tmp/redis"
OTHERREDISDIR="${HOMEDIR}/.cagefs/tmp/redis"
else
SOCKDIR1="${HOMEDIR}/.cagefs"
SOCKDIR="${HOMEDIR}/.cagefs/tmp"
REDISDIR="${HOMEDIR}/.cagefs/tmp/redis"
OTHERREDISDIR="${HOMEDIR}/.lsns/tmp/redis"
fi
# Cage-divergence assertion (availability diagnostic, not a security control):
# this unit selects the cage from cagefs.mp, but the root writer selects it from
# the httpd_config.xml namespace. Those signals only agree under the supported
# invariant (cagefs.mp absent <=> namespace=2); off it, the writer serves this
# user from the OTHER cage and our redis-server publishes its socket where no
# poller/client looks, so cache/flush silently no-ops. We cannot re-read the
# root-owned httpd_config.xml from tenant space, but the writer's root-owned
# (uid 0) redis.size/redis.package land in the cage IT chose -- so a root-owned
# marker in OTHERREDISDIR is a user-readable footprint of a divergent decision.
# Warn loudly (journal) instead of failing silently; do not refuse to start,
# since a genuine start is still preferable to none and this may be transient
# during a config change.
#
# Stale-marker suppression via mtime, NOT mere presence. The writer's markers
# are immutable (FS_IOC_SETFLAGS) and the cleanup paths only ever touch the
# CURRENT cage, so a host that migrated cage types (e.g. Containers -> CageFS)
# keeps an undeletable stale marker in the now-unused cage forever. We must not
# warn about that leftover -- but we MUST still warn about a live divergence.
#
# Presence alone cannot tell the two apart: a live divergent writer keeps
# writing fresh markers into the OTHER cage (its chosen cage) on every
# Accounts::Modify / Accounts::Remove hook and plugin upgrade, because the root
# writer runs from a shell path (validate_redis.sh -> do_syncpkgfiles ->
# write_user_package_file) that is NOT gated by the WHM-layer
# redis_users_supported() dual-marker guard -- that guard only blocks the PHP
# management flows, not the periodic reconcile. So an OTHER-cage marker can be
# either a pre-migration leftover OR a currently-writing divergent writer.
#
# We distinguish them by recency: suppress only when OUR cage holds a root-owned
# marker at least as new as the newest OTHER-cage one. On a genuine migration
# the writer now agrees with us and writes only OUR cage, so our marker is the
# newer (and the other is a frozen leftover); on a live divergence the writer
# keeps refreshing the OTHER cage, so the other marker is strictly newer and we
# warn. mtime is second-resolution; ties suppress (treated as "not newer"),
# which is safe -- a real divergence keeps re-writing and wins on a later start.
#
# Residual gap (cannot be closed from tenant space): this only fires when the
# writer left a root-owned in-cage marker at all. A divergent user sized purely
# from the root-owned, out-of-cage maps (redis_size.conf / redis_pkg_user_size
# .conf, consulted below) can start with NO in-cage marker in either cage, so
# the "neither marker" divergence is invisible here. Detecting it would require
# reading the root-owned httpd_config.xml, which this unit (User=%i) cannot; the
# WHM-side redis_users_supported() dual-marker guard is the intended net for it.
#
# Every probe requires a NON-SYMLINK regular file ([ ! -L ] && [ -f ]) before
# reading owner or mtime: the cage dirs are tenant-owned (0700), so a tenant
# could otherwise plant redis.size as a symlink to any root-owned file to forge
# a uid-0 verdict -- spuriously firing the warning (OTHER cage) or spuriously
# suppressing a real one (OUR cage). This mirrors the symlink-safe check used by
# rm_size_dir_file/rm_package_dir_file in lsns_common.sh.
_newest_root_marker_mtime() {
# $1 = redis dir. Prints the greatest mtime (epoch secs) among its root-owned
# (uid 0) regular markers, or nothing if it holds none.
local _d=$1 _f _mt _best=""
for _f in redis.size redis.package; do
if [ ! -L "${_d}/${_f}" ] && [ -f "${_d}/${_f}" ] \
&& [ "$(stat -c '%u' "${_d}/${_f}" 2>/dev/null)" = "0" ]; then
_mt=$(stat -c '%Y' "${_d}/${_f}" 2>/dev/null)
if [ -n "${_mt}" ] && { [ -z "${_best}" ] || [ "${_mt}" -gt "${_best}" ]; }; then
_best="${_mt}"
fi
fi
done
[ -n "${_best}" ] && echo "${_best}"
}
_our_marker_mtime=$(_newest_root_marker_mtime "${REDISDIR}")
for _m in redis.size redis.package; do
if [ ! -L "${OTHERREDISDIR}/${_m}" ] && [ -f "${OTHERREDISDIR}/${_m}" ] \
&& [ "$(stat -c '%u' "${OTHERREDISDIR}/${_m}" 2>/dev/null)" = "0" ]; then
_other_mtime=$(stat -c '%Y' "${OTHERREDISDIR}/${_m}" 2>/dev/null)
# Suppress only when our cage has an at-least-as-new marker (writer now
# agrees with us; the other is a frozen migration leftover). Warn when our
# cage has none, or the other-cage marker is strictly newer (live writer
# still refreshing the wrong cage).
if [ -z "${_our_marker_mtime}" ] || { [ -n "${_other_mtime}" ] \
&& [ "${_other_mtime}" -gt "${_our_marker_mtime}" ]; }; then
echo "WARNING: cage divergence for ${USER}: root-owned ${OTHERREDISDIR}/${_m} exists but this service uses ${REDISDIR}. The root writer and this service disagree on the cage (cagefs.mp vs httpd_config.xml namespace); the redis socket will be published where clients do not look and caching will not work until the CageFS/LiteSpeed Containers configuration is made consistent." >&2
break
fi
fi
done
if [ "$ACTION" == "start" ]; then
# V12 (CWE-276): ensure per-user redis runtime dirs exist and are 0700.
# mkdir -p is a no-op when the dir already exists, so this also remediates
# pre-existing 0777 dirs created before this hardening was applied.
mkdir -p "$SOCKDIR1" -m 0700
chown "$USER" "$SOCKDIR1"
chmod 0700 "$SOCKDIR1"
mkdir -p "$SOCKDIR" -m 0700
chown "$USER" "$SOCKDIR"
chmod 0700 "$SOCKDIR"
mkdir -p "$REDISDIR" -m 0700
chown "$USER" "$REDISDIR"
chmod 0700 "$REDISDIR"
DIR="$(dirname "$0")"
PACKAGE_SIZES="${DIR}/../conf/redis_package_size.conf"
if [ -e "${DIR}/../conf/use-valkey" ]; then
REDIS="valkey"
else
REDIS="redis"
fi
# §7b: reject size/package files not owned by root so a tenant cannot forge
# their memory cap by creating a competing redis.size in the tenant-owned dir.
_redis_size_owner() { stat -c '%u' "$1" 2>/dev/null; }
# §13b: two-tier authoritative lookup — both files are root-owned,
# out-of-cage, on a root-controlled ancestor path.
#
# 1. Explicit per-user admin override (redis_size.conf / CONF): set by
# direct admin action (update_user_size_conf) for users with a custom
# size, independent of package membership.
# 2. Package-derived per-user map (redis_pkg_user_size.conf): populated by
# do_syncpkgfiles / do_pkg for every user in a sized package. Kept
# separate so package reconciliation never clobbers explicit overrides.
#
# A tenant can hide the in-cage redis.size/redis.package by renaming the
# tenant-owned REDISDIR, but they cannot rename or forge either of these
# host-path files whose entire ancestor chain is root-controlled.
SIZEMB=""
_SIZE_CONF="${DIR}/../conf/redis_size.conf"
if [ -r "${_SIZE_CONF}" ]; then
_auth_size=$(awk -F'"' -v u="${USER}" '$2 == u { print $4; exit }' "${_SIZE_CONF}" 2>/dev/null)
if [[ "${_auth_size}" =~ ^[1-9][0-9]*$ ]]; then
SIZEMB="${_auth_size}"
echo "SIZEMB from authoritative size conf (${USER}): ${SIZEMB}"
fi
fi
if [ -z "${SIZEMB}" ]; then
_PKG_SIZE_CONF="${DIR}/../conf/redis_pkg_user_size.conf"
if [ -r "${_PKG_SIZE_CONF}" ]; then
_pkg_auth_size=$(awk -F'"' -v u="${USER}" '$2 == u { print $4; exit }' "${_PKG_SIZE_CONF}" 2>/dev/null)
if [[ "${_pkg_auth_size}" =~ ^[1-9][0-9]*$ ]]; then
SIZEMB="${_pkg_auth_size}"
echo "SIZEMB from package-derived size conf (${USER}): ${SIZEMB}"
fi
fi
fi
if [ -z "${SIZEMB}" ] && [ -e "${REDISDIR}/redis.size" ]; then
_owner=$(_redis_size_owner "${REDISDIR}/redis.size")
if [ "${_owner}" = "0" ]; then
SIZEMB=$(cat "${REDISDIR}/redis.size" 2>/dev/null)
echo "SIZEMB from size file: ${SIZEMB}"
else
echo "redis.size not root-owned (uid=${_owner}); ignoring"
fi
fi
if [ -z "${SIZEMB}" ] && [ -e "${REDISDIR}/redis.package" ]; then
_owner=$(_redis_size_owner "${REDISDIR}/redis.package")
if [ "${_owner}" = "0" ]; then
PACKAGE=$(cat "${REDISDIR}/redis.package" 2>/dev/null)
PKGLINE=$(grep "${PACKAGE}", < "${PACKAGE_SIZES}" 2>/dev/null)
SIZEMB=$(echo "${PKGLINE}" | cut -d',' -f2 | tr -d '\r')
echo "SIZEMB from package ${PACKAGE}: ${SIZEMB}"
else
echo "redis.package not root-owned (uid=${_owner}); ignoring"
fi
fi
if [ -z "${SIZEMB}" ] && SIZEMB=$("cat" "${DIR}/../conf/redis_default_size.conf" 2>/dev/null); then
echo "SIZEMB from default file: ${SIZEMB}"
fi
if [ -z "${SIZEMB}" ]; then
SIZEMB="64"
echo "SIZEMB final default: ${SIZEMB}"
fi
# §7 (defense-in-depth): clamp to a strictly-numeric, sane value before
# passing to --maxmemory. A non-numeric or empty value would produce a
# malformed "mb" argument; a zero/negative would disable the limit.
# Accept only positive integers; fall back to the built-in default on any
# non-conforming value.
if ! [[ "${SIZEMB}" =~ ^[1-9][0-9]*$ ]]; then
echo "SIZEMB '${SIZEMB}' is not a valid positive integer; using default 64"
SIZEMB="64"
fi
rm -f "${REDISDIR}"/*.rdb
"/usr/bin/${REDIS}-server" --port 0 --unixsocketperm 700 --unixsocket "${SOCKDIR}"/redis.sock --dir "${REDISDIR}" --maxmemory "${SIZEMB}"mb --supervised systemd
elif [ "$ACTION" == "stop" ]; then
echo "Stopping redis for ${USER}"
else
echo "Undefined action: ${ACTION}"
exit 1
fi