Mini Shell
# 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
Zerion Mini Shell 1.0