| Server IP : 213.136.93.164 / Your IP : 216.73.216.20 Web Server : Apache System : Linux m14200.contabo.net 5.14.0-611.54.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 6 18:03:03 EDT 2026 x86_64 User : ki692510 ( 1047) PHP Version : 7.4.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /opt/cloudlinux/venv/lib/python3.11/site-packages/clcagefslib/webisolation/ |
Upload File : |
# -*- coding: utf-8 -*-
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENCE.TXT
#
import os
import pathlib
import secrets
import shutil
from pathlib import Path
from clcommon import ClPwd
from clcommon.clpwd import drop_privileges
from clcagefslib.fs import get_user_var_cagefs_path
from clcagefslib.io import apply_metadata_nofollow, write_via_tmp
# FNV-1a 64-bit hash constants (must match jail C implementation)
_FNV_OFFSET_BASIS = 14695981039346656037
_FNV_PRIME = 1099511628211
def get_jail_config_path(user):
cagefs_dir = get_user_var_cagefs_path(user)
return pathlib.Path(f"{cagefs_dir}/.cagefs/isolates.mounts")
def get_website_id(document_root: str):
"""
Generates unique id for an isolate website using FNV-1a 64-bit hash.
FNV-1a has excellent avalanche properties and distribution.
Must match the docroot_hash() function in jail C code.
"""
hash_value = _FNV_OFFSET_BASIS
for char in document_root.encode("utf-8"):
hash_value ^= char
hash_value = (hash_value * _FNV_PRIME) & 0xFFFFFFFFFFFFFFFF # Keep 64-bit
return f"{hash_value:016x}"
def create_website_token_directory(user: str, document_root: str):
"""
Create website token directory structure and files in /var/cagefs.
Creates:
- /var/cagefs/<user>/.cagefs/website/<website_id>/ - token directory
"""
pw = ClPwd().get_pw_by_name(user)
website_id = get_website_id(document_root)
# Token directory in /var/cagefs
website_base_dir = Path(get_user_var_cagefs_path(user)) / ".cagefs/website"
website_dir = website_base_dir / website_id
# mode to prevent directory listing by other users
website_base_dir.mkdir(exist_ok=True, parents=True, mode=0o751)
# mode to allow user list /var/.cagefs when it's mounted for website
website_dir.mkdir(exist_ok=True, mode=0o755)
# Force-correct ownership and mode in case the directory was created
# earlier by the per-domain ns-cache code with a different uid/gid context
# (e.g. as root:nobody from an Apache-spawned lsphp request that hit the
# jail before this site's isolation was enabled). mkdir(exist_ok=True)
# is a no-op for an existing directory and would not heal those perms.
# Use the O_NOFOLLOW helper so a symlink swapped in at <website_dir>
# between mkdir and chmod/chown raises ELOOP rather than being followed.
apply_metadata_nofollow(str(website_dir), 0o755, 0, 0)
token = _generate_password(32)
# create token file read-only for owner, others cannot read it
# this replicates behavior of regular token
# note: user can still use chown to change it
# token with NON 400 mode would be rejected by cagefs.server.c:check_tokenfile_perms()
token_file_path = f"{website_dir}/.cagefs.token"
write_via_tmp(website_dir, token_file_path, token)
apply_metadata_nofollow(token_file_path, 0o400, pw.pw_uid, 0)
# create file with document root path
# that we can use as trusted source in proxyexec
docroot_file_path = f"{website_dir}/.cagefs.website"
write_via_tmp(website_dir, docroot_file_path, document_root)
# only read permissions, without modification
# we use this marker as a trusted source of document root
# it should not be modifiable by user in any way
apply_metadata_nofollow(docroot_file_path, 0o444, 0, 0)
def create_overlay_storage_directory(user: str, document_root: str):
"""
Create overlay storage directory in user's home.
Creates:
- <homedir>/.cagefs/websites/<website_id>/ - storage base for overlays
Drops privileges to user before creating to ensure proper ownership.
"""
pw = ClPwd().get_pw_by_name(user)
if not Path(pw.pw_dir).exists():
return
storage_base = Path(full_website_path(pw.pw_dir, document_root))
with drop_privileges(user):
storage_base.mkdir(exist_ok=True, parents=True, mode=0o750)
def remove_website_token_directory(user: str, document_root: str):
"""
Remove website token directory structure and files.
"""
website_base_dir = Path(get_user_var_cagefs_path(user)) / ".cagefs/website"
website_dir = website_base_dir / get_website_id(document_root)
if website_dir.exists():
shutil.rmtree(website_dir)
def website_suffix_with_hash(docroot):
"""
Returns path: websites/<document_root_hash>
"""
return os.path.join("websites", get_website_id(docroot))
def full_website_path(homedir, docroot):
"""
Returns <homedir>/.cagefs/websites/<document_root_hash>
"""
return os.path.join(homedir, ".cagefs", website_suffix_with_hash(docroot))
def invalidate_ns_cache(user: str, document_root: str):
"""
Removes cached namespace from disk
"""
website_base_dir = Path(get_user_var_cagefs_path(user)) / ".cagefs/website"
website_dir = website_base_dir / get_website_id(document_root)
(website_dir / ".cagefs.mnt").unlink(missing_ok=True)
def _generate_password(length):
"""
Generate a random password/token using the same algorithm as the C function.
Uses cryptographically secure random bytes and converts them to alphanumeric characters.
"""
if length == 0 or length > 256:
raise ValueError("Invalid buffer length requested")
charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
charset_size = len(charset)
# Generate random bytes
random_bytes = secrets.token_bytes(length)
# Convert bytes to alphanumeric characters
result = "".join(charset[b % charset_size] for b in random_bytes)
return result