/
opt
/
cloudlinux
/
venv
/
lib
/
python3.11
/
site-packages
/
clcagefslib
/
selector
/
Upload Filee
HOME
# 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