AlkantarClanX12

Your IP : 3.144.46.90


Current Path : /opt/cloudlinux/venv/lib64/python3.11/site-packages/clcagefslib/selector/
Upload File :
Current File : //opt/cloudlinux/venv/lib64/python3.11/site-packages/clcagefslib/selector/configure.py

# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2024 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
import functools
import logging
import os
import sys

import yaml

import secureio
from clcagefslib.const import BASEDIR, ETC_CL_PHP_PATH, ETC_CL_ALT_PATH, ETC_CL_ALT_CAGEFS_PATH, SYMLINKS
from clcagefslib.io import make_userdir, switch_symlink
from clcagefslib.fs import get_user_prefix
from clcagefslib.selector.paths import get_alt_dirs
from clcommon import clcaptain, clconfpars
from clcommon.clcagefs import in_cagefs
from clcommon.utils import ExternalProgramFailed


@functools.cache
def is_ea4_enabled() -> bool:
    """
    Return True if cPanel EasyApache4 (MultiPHP feature) is enabled
    """
    return os.path.lexists('/etc/cpanel/ea4/is_ea4')


@functools.cache
def read_cpanel_ea4_php_conf() -> dict[str, str] | None:
    """
    Read /etc/cpanel/ea4/php.conf
    return something like {'default': 'ea-php54', 'ea-php56': 'suphp', 'ea-php54': 'cgi', 'ea-php55': 'suphp'}
    return None if error has occured
    """
    try:
        with open('/etc/cpanel/ea4/php.conf', 'r') as f:
            # conf = {'default': 'ea-php54', 'ea-php56': 'suphp', 'ea-php54': 'cgi', 'ea-php55': 'suphp'}
            return yaml.load(f, yaml.SafeLoader)
    except (yaml.YAMLError, IOError):
        return None


def multiphp_system_default_is_ea_php() -> bool:
    """
    Return True when default system php version selected via MultiPHP Manager in cPanel WHM is ea-php (not alt-php)
    For details see CAG-774
    """
    if is_ea4_enabled():
        conf = read_cpanel_ea4_php_conf()
        if conf:
            try:
                return conf['default'].startswith('ea-php')
            except KeyError:
                pass
    return True


@functools.cache
def selector_modules_must_be_used():
    """
    Return True if modules selected via PHP Selector (alt_php.ini) must be always used.
    Never use modules selected in cPanel MultiPHP Manager.
    See CAG-511 for details
    """
    symlinks_rules_path = f'{ETC_CL_ALT_PATH}/symlinks.rules'
    if in_cagefs():
        symlinks_rules_path = f'{ETC_CL_ALT_CAGEFS_PATH}/symlinks.rules'

    syml_rules = clconfpars.load_once(symlinks_rules_path, ignore_errors=True)
    try:
        return syml_rules['php.d.location'].lower() == 'selector'
    except KeyError:
        return False


# configure alt php - create .cagefs dir and create symlink
def configure_alt_php(pw, php_vers, write_log=True, drop_perm=True, force=True, configure_multiphp=True):
    """
    Create .cagefs directory in home directory of an user (if that dir does not exist),
    and create symlinks to modules for alt-php
    For details see CAG-447
    Also switch symlinks that are used for integration with cPanel MultiPHP
    For details please see CAG-445
    drop_perm should be True when called as root, otherwise drop_perm should be False
    Returns True if error has occured
    :param pw: password file entry for an user
    :type pw: as defined in standard pwd module
    :param php_vers: alt-php version selected for an user (for example 'native' or '5.6')
    :type php_vers: string
    :param write_log: write error messages to log or not
    :type write_log: bool
    :param force: recreate symlinks even when they exist
    :type force: bool
    """

    # create /home/user/.cagefs directory if it does not exist, set permissions/owner otherwise
    real_homepath = os.path.realpath(pw.pw_dir)
    path = os.path.join(pw.pw_dir, '.cagefs')
    if drop_perm:
        if make_userdir(path, 0o771, pw.pw_uid, pw.pw_gid, real_homepath):
            return True

    elif not os.path.lexists(path):
        try:
            clcaptain.mkdir(path, 0o771)
        except (OSError, ExternalProgramFailed) as e:
            msg = f'Error: failed to create directory {path} : {str(e).replace("Errno", "Err code")}'
            logging.error(msg, exc_info=e)
            print(msg, file=sys.stderr)
            return True
    if drop_perm:
        # drop privileges (switch to user)
        secureio.set_user_perm(pw.pw_uid, pw.pw_gid)

    error = _switch_symlink_for_alt_php_ini(php_vers, pw.pw_dir, write_log, force)
    if configure_multiphp:
        error = _switch_symlink_for_cpanel_multi_php(pw, php_vers, write_log, force) or error

    if drop_perm:
        # restore root privileges
        secureio.set_root_perm()

    return error


def _switch_symlink_for_alt_php_ini(php_vers, homedir, write_log=True, force=True):
    """
    Switch symlink so it will point to directory with modules for alt-php
    For details see CAG-447
    Returns True if error has occured
    Should be called as user (not root)!
    :param php_vers: alt-php version selected for an user (for example 'native' or '5.6')
    :type php_vers: string
    :param force: recreate symlinks even when they exist
    :type force: bool
    """

    def _switch_symlink_for_dir(php_dir):
        # create path to link, like /home/$USER/.cagefs/opt/alt/php55/link/conf
        link_path = os.path.join(homedir, '.cagefs/opt/alt', php_dir, 'link/conf')
        dir_path = os.path.dirname(link_path)
        if not os.path.lexists(dir_path):
            try:
                # os.makedirs(dir_path, 0700)
                clcaptain.mkdir(dir_path, 0o700, recursive=True)
            except (OSError, ExternalProgramFailed):
                pass
        selected_php_dir = 'php'+php_vers.replace('.', '')
        if not selector_modules_must_be_used() \
                and (selected_php_dir != php_dir or not multiphp_system_default_is_ea_php()):
            # path to default alt-php modules
            link_to = '/opt/alt/%s/etc/php.d' % php_dir
        else:
            # path to user's custom modules selected via CloudLinux PHP Selector - like /etc/cl.php.d/alt-php55
            link_to = os.path.join(ETC_CL_PHP_PATH, 'alt-' + php_dir)
        return switch_symlink(link_to, link_path, write_log, force)

    error = False
    # get dirnames of all alt-php dirs as list
    alt_php_dirs = get_alt_dirs()
    # switch symlinks for ALL alt-php versions
    for php_dir in alt_php_dirs:
        if _switch_symlink_for_dir(php_dir):
            error = True
    return error


def _get_default_native_version_selected(user_cagefs_path: str):
    """
    Return string like ea-phpXX when symlinks have been created already and native version is selected
    Return None otherwise
    """
    try:
        link_to = os.readlink(f'{user_cagefs_path}/etc/cl.selector/ea-php.ini')
    except OSError:
        return None
    if link_to.startswith('/opt/cpanel/ea-php'):
        return link_to.split('/')[3]
    return None


def _switch_symlink_for_cpanel_multi_php(pw, selected_php_vers, write_log: bool = True, force: bool = True):
    """
    Switch symlinks that are used for integration with cPanel MultiPHP:
    when selected_php_vers == alt-php version, then create symlinks like /etc/cl.selector/ea-php -> php;
    when selected_php_vers == native version, then create symlinks like
        /etc/cl.selector/ea-php -> /opt/cpanel/ea-phpXX/root/usr/bin/php.cagefs;
    For details please see CAG-445
    Return True if error has occured
    :param pw: password file entry for an user
    :type pw: as defined in standard pwd module
    :param selected_php_vers: alt-php version selected for an user (for example 'native' or '5.6')
    :type selected_php_vers: string
    :param write_log: write error messages to log or not
    :type write_log: bool
    :param force: recreate symlinks even when they exist
    :type force: bool
    """
    if not is_ea4_enabled():
        return False

    conf = read_cpanel_ea4_php_conf()
    if not conf:
        return False

    try:
        # get default system php version selected via MultiPHP Manager in cPanel WHM
        default_php = conf['default']
    except KeyError:
        return True
    # LVEMAN-1170: do not configure PHP Selector when system default version is alt-php
    if not default_php.startswith('ea-php'):
        return False
    username = pw.pw_name
    user_cagefs_path = '/' if os.path.exists('/var/.cagefs') else os.path.join(BASEDIR,
                                                                               get_user_prefix(username),
                                                                               username)

    if not force:
        old_eaphp_default = _get_default_native_version_selected(user_cagefs_path)
        if old_eaphp_default is not None:
            selected_php_vers = 'native'
            if old_eaphp_default != default_php:
                # we should recreate symlinks when native version is selected actually
                # and when default ea-php version is changed via cPanel MultiPHP
                force = True

    error = False
    for sympath, link_to in SYMLINKS.items():
        link_path = sympath % user_cagefs_path
        if selected_php_vers == 'native':
            error = switch_symlink(link_to[1] % default_php, link_path, write_log, force) or error
        else:
            error = switch_symlink(link_to[0], link_path, write_log, force) or error
    return error