Mini Shell
# -*- 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
import logging
import os
import re
import pwd
from contextlib import suppress
from glob import iglob
from typing import Tuple
from secureio import disable_quota
from xray.internal.utils import user_context
logger = logging.getLogger()
GLOBAL_INI_MARKER = '/opt/cloudlinux/flags/enabled-flags.d/xray-ini-global-mode.flag'
# directories that don't matter for us
# php is either outdated or just internal
_EXCLUDE_DIR_PATHS = (
'php44', 'php51', 'php52', 'php53',
'php\d+-imunify', 'php-internal'
)
# global set of ini locations where php
# usually loads configuration files from
# some of them may be missing, like /opt/plesk
# which only exists on plesk
_INI_LOCATIONS = (
'/opt/alt/php[0-9][0-9]/link/conf',
'/opt/cpanel/ea-php[0-9][0-9]/root/etc/php.d',
'/opt/plesk/php/[0-9].[0-9]/etc/php.d',
'/usr/local/php[0-9][0-9]/lib/php.conf.d',
'/usr/share/cagefs/.cpanel.multiphp/opt/cpanel/ea-php[0-9][0-9]/root/etc/php.d',
'/usr/share/cagefs-skeleton/usr/local/php[0-9][0-9]/lib/php.conf.d'
)
# same as above, but ini which are writeable by users
_INI_USER_LOCATIONS = (
dict(path='/var/cagefs/*/*/etc/cl.php.d/alt-php[0-9][0-9]',
user=lambda path: pwd.getpwnam(path.split('/')[4])),
)
def _is_excluded_path(dir_path: str) -> list:
"""
Check if given path is in exclude list.
"""
res = [substring for substring in _EXCLUDE_DIR_PATHS
if re.search(substring, dir_path)]
return res
def _iter_existing_ini_locations() -> Tuple[Tuple[int, int], str]:
"""
Generator of existing paths (matching known wildcard locations)
for additional ini files
Returns tuple of (uid, gid) and path.
"""
for location in _INI_LOCATIONS:
for dir_path in iglob(location):
if _is_excluded_path(dir_path):
continue
yield (0, 0), dir_path
for location in _INI_USER_LOCATIONS:
for dir_path in iglob(location['path']):
if _is_excluded_path(dir_path):
continue
try:
pw_record = location['user'](dir_path)
except:
logger.info('Unable to get information about user '
'owning %s directory (maybe he`s already terminated?), '
'skip updating', dir_path)
continue
else:
yield (pw_record.pw_uid, pw_record.pw_gid), dir_path
def _create_single_ini(uid: int, gid: int, ini_path: str):
# write counter of tasks so during mode switch
# we can still safely cleanup ini files not
# bound to any exiting tasks
ini_content = ';xray.tasks=0\nextension=xray.so'
path = os.path.join(ini_path, 'xray.ini')
if os.path.exists(path):
return
with user_context(uid, gid), \
disable_quota(), \
open(path, 'w') as ini:
logger.info('Generating %s file...', path)
ini.write(ini_content)
def is_global_ini_mode():
return os.path.exists(GLOBAL_INI_MARKER)
def create_global_ini_mode_marker():
open(GLOBAL_INI_MARKER, 'w').close()
def remove_global_ini_mode_marker():
with suppress(FileNotFoundError):
os.remove(GLOBAL_INI_MARKER)
def create_ini_files() -> None:
"""
Place xray.ini into each existing Additional ini path,
including cagefs ones.
"""
logger.info('Generating xray.ini files...')
for (uid, gid), ini_path in _iter_existing_ini_locations():
try:
_create_single_ini(uid, gid, ini_path)
except PermissionError:
logger.warning('Unable to update file %s, '
'possible permission misconfiguration', ini_path)
continue
except Exception as e:
logger.warning('Unexpected error happened during file processing: '
'"%s", error: "%s"', ini_path, str(e), exc_info=True)
continue
logger.info('Finished!')
def remove_ini_files() -> None:
"""
Remove all gathered clos_ssa.ini files
"""
logger.info('Removing clos_ssa.ini files...')
for (uid, gid), clos_ini_dir in _iter_existing_ini_locations():
ini_file = os.path.join(clos_ini_dir, 'xray.ini')
try:
with user_context(uid, gid), open(ini_file) as f:
contents = f.read()
# unlink file only if there are no linked tasks
# case with minus sign covers negative values
if "xray.tasks=0" in contents or \
"xray.tasks=-" in contents:
os.unlink(ini_file)
except FileNotFoundError:
continue
except Exception as e:
logger.warning('Unable to remove file: "%s", error: "%s"', ini_file, str(e))
continue
logger.info('Finished!')
Zerion Mini Shell 1.0