403Webshell
Server IP : 142.11.234.102  /  Your IP : 216.73.217.70
Web Server : Apache
System : Linux dal-shared-66.hostwindsdns.com 4.18.0-513.24.1.lve.1.el8.x86_64 #1 SMP Thu May 9 15:10:09 UTC 2024 x86_64
User : krnuyqrm ( 1183)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /proc/self/root/opt/cloudlinux/venv/lib64/python3.11/site-packages/xray/manager/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /proc/self/root/opt/cloudlinux/venv/lib64/python3.11/site-packages/xray/manager//custom.py
# -*- 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/LICENSE.TXT

"""
This module contains classes implementing X-Ray Manager behaviour
for custom integration
"""

import os
from collections import ChainMap
from typing import Optional

from clcommon.cpapi.plugins.vendors import PublicApi
from xray import gettext as _
from xray.internal import phpinfo_utils

from .base import BaseManager
from ..internal.constants import is_allowed_ini_path
from ..internal.exceptions import XRayManagerError, XRayMissingDomain
from ..internal.types import DomainInfo
from ..internal.user_plugin_utils import user_mode_verification, with_fpm_reload_restricted


class CustomManager(BaseManager):
    """
    Manager supporting integration scripts
    """

    VERSIONS_CUSTOM = {
        '54': None,
        '55': None,
        '56': None,
        '70': None,
        '71': None,
        '72': None,
        '73': None,
        '74': None,
        '80': None,
        '81': None,
        '82': None,
        '83': None,
        '84': None,
        '85': None,
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.api = PublicApi()
        if self.is_xray_integrated:
            self.all_domains = self.get_all_domains()
        else:
            raise XRayManagerError(_('X-Ray is not supported by control panel vendor'))

    def supported_versions(self) -> ChainMap:
        """
        Get supported PHP versions
        :return: dict with custom supported versions
        """
        return ChainMap(self.VERSIONS, self.VERSIONS_CUSTOM)

    @property
    def is_xray_integrated(self):
        """
        Check the X-Ray feature status through the panel_info script
        """
        _info = self.api.panel_info()  # PanelInfo instance
        features = _info.supported_cl_features
        if features is None:
            return True

        return features.get('xray', False)

    def get_all_domains(self) -> dict:
        """
        Collect domains from integration script
        """
        return self.api.domains(with_php=True)

    @with_fpm_reload_restricted
    @user_mode_verification
    def get_domain_info(self, domain_name: str) -> DomainInfo:
        """
        Retrieve PHP setting for given domain_name
        """
        try:
            domain_conf = self.all_domains[domain_name]  # DomainData instance
        except KeyError:
            self.logger.warning('Domain does not exist on the server', extra={'domain_name': domain_name})
            raise XRayMissingDomain(domain_name)

        if self.phpinfo_mode:
            config = phpinfo_utils.get_php_configuration(
                domain_conf.owner,
                domain=domain_name,
                # Trusted (server-side) PHP version for this domain. The custom
                # vendor API reports a BARE number ('80') that carries no distro
                # family, so the global per-distro+version INI_LOCATION cannot be
                # uniquely derived: the global accept branch (3) default-DENIES
                # for custom (own-CageFS / own-/etc/users dirs still accepted).
                # The probe value is attacker-controlled and must not be an anchor.
                trusted_php_version=domain_conf.php.version,
            )

            return DomainInfo(
                name=domain_name,
                panel_php_version=config.get_full_php_version(''),
                php_ini_scan_dir=config.absolute_ini_scan_dir,
                # indicates that there is no need to apply selector
                # and try to resolve php version, the one given in
                # php_version is final one
                is_selector_applied=True,
                user=domain_conf.owner,
                panel_fpm=config.is_php_fpm,
            )
        else:
            ini_path = self._validate_ini_path(domain_conf.php.ini_path, domain_conf.owner)
            domain_info = DomainInfo(
                name=domain_name,
                user=domain_conf.owner,
                panel_php_version=domain_conf.php.version,
                panel_fpm=domain_conf.php.fpm,
                is_native=domain_conf.php.is_native,
                ini_path=ini_path,
            )

        self.logger.info(
            'Retrieved domain info: domain %s owned by %s uses php version %s',
            domain_name,
            domain_info.user,
            domain_info.panel_php_version,
        )
        return domain_info

    def panel_specific_selector_enabled(self, domain_info: DomainInfo) -> bool:
        """
        Check if selector is enabled specifically for custom panels
        Required to be implemented by child classes
        :param domain_info: a DomainInfo object
        :return: True if yes, False otherwise
        """
        return domain_info.is_native and not domain_info.panel_fpm

    def fpm_service_name(self, dom_info: DomainInfo) -> Optional[str]:
        """
        Retrieve FPM service name
        """
        return dom_info.panel_fpm

    def _validate_ini_path(self, ini_path: str, owner: str) -> str:
        """Owner-bind the vendor-supplied ini_path before it steers a root write.

        In non-phpinfo mode the custom integration reports ini_path verbatim and
        the only downstream guard is the broad is_allowed_ini_path prefix
        allowlist, which admits serverwide (/etc/php.d) and per-tenant
        (/var/cagefs/..., /etc/users/<user>) trees. Without owner-binding an
        allowlisted-but-foreign path would let the root xray.ini write land in
        another tenant's jail. Accept the path only when it is the owner's own
        subtree or an operator-controlled root-owned INI directory; reject a path
        that resolves outside the allowlist, into a foreign tenant's per-user
        tree, or onto a dir owned by a different unprivileged user.

        Returns the CANONICAL (realpath'd) directory, not the raw vendor string.
        The write-time owner binding (Task._ini_dir_validator / unified_write)
        classifies ini_location as per-tenant vs global from this value and then
        re-checks the PINNED inode against it, so it must be canonical here — a
        raw symlink path would misclassify and either over-reject a legitimate
        own-subtree symlink or fail to bind a per-tenant location.
        """
        if not ini_path:
            return ini_path
        resolved = os.path.realpath(ini_path)
        if not is_allowed_ini_path(resolved):
            self.logger.warning('Rejected custom ini_path outside allowlist: %s', ini_path)
            raise XRayManagerError(_('ini_location outside allowed paths: %s') % ini_path)
        # The owner's OWN CageFS subtree or per-domain /etc/users/<owner> dir.
        if phpinfo_utils._is_own_cagefs_subtree(owner, resolved) or phpinfo_utils._is_within(
            phpinfo_utils._USERS_INI_ROOT + owner, resolved
        ):
            return resolved
        # Per-tenant roots are legitimate only as the owner's own subtree
        # (accepted above); any other /var/cagefs or /etc/users target is a
        # foreign tenant's jail.
        if phpinfo_utils._is_within(phpinfo_utils._CAGEFS_ROOT, resolved) or phpinfo_utils._is_within(
            phpinfo_utils._USERS_INI_ROOT, resolved
        ):
            self.logger.warning('Rejected custom ini_path bound to a foreign tenant: %s', ini_path)
            raise XRayManagerError(_('ini_location outside allowed paths: %s') % ini_path)
        # A global, operator-controlled allowlist dir (/opt/alt/phpNN,
        # /usr/local/phpNN, /etc/php.d, ...). If present, require root ownership;
        # a dir owned by an unprivileged user here is unexpected. If absent (the
        # handler is not installed on this server) it is not a foreign tenant's
        # existing subtree, so accept it under the allowlist bound.
        try:
            st_uid = os.stat(resolved).st_uid
        except OSError:
            return resolved
        if st_uid == 0:
            return resolved
        self.logger.warning('Rejected custom ini_path owned by uid %s (not root): %s', st_uid, ini_path)
        raise XRayManagerError(_('ini_location outside allowed paths: %s') % ini_path)

    def _ini_path(self, domain_info: DomainInfo) -> str:
        """
        Path to additional .ini files specific custom panel getter
        """
        return domain_info.ini_path

    def get_ini_path(self, domain_info: DomainInfo) -> str:
        """
        Resolve a path to directory for additional ini file.
        It depends on version set for domain and on selector
        NOTE:
        This method is overrided to manage php.d.location=selector resolving.
        In custom integration we do not know if PHP version is alt or not,
        it is set as just two digits.
        Thus, we only could rely on resolved path -- if it is '/opt/alt'.
        :param domain_info: a DomainInfo object
        :return: path to directory for ini files
        """
        if domain_info.php_ini_scan_dir:
            return domain_info.php_ini_scan_dir

        # here follows the hack to resolve php.d.location=selector
        # for custom integration
        ini_path = super().get_ini_path(domain_info)
        if ini_path.startswith('/opt/alt') and not domain_info.panel_php_version.startswith('alt-php'):
            saved_panel_php = domain_info.panel_php_version
            domain_info.panel_php_version = f'alt-php{domain_info.panel_php_version}'
            try:
                ini_path = domain_info.phpd_location_ini_path or ini_path
            except ValueError:
                # failed to resolve CageFS prefix for user
                pass
            domain_info.panel_php_version = saved_panel_php
            self.logger.info('Ini path re-resolved as %s', ini_path)
        return ini_path

Youez - 2016 - github.com/yon3zu
LinuXploit