Файловый менеджер - Редактировать - /home/skymarketplace/public_html/uploads/manager.zip
Назад
PK �b�Zd,�� � __init__.pynu �[��� # -*- 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 os from .base import BaseManager from .cpanel import CPanelManager from .custom import CustomManager from .directadmin import DirectAdminManager from .plesk import PleskManager from ..internal.utils import read_jwt_token def initialize_manager(system_id: str) -> 'Manager instance': """ Factory function for appropriate manager initialization :return: appropriate manager instance """ # check validity of JWT token as early as possible read_jwt_token() if os.path.isfile('/usr/local/cpanel/cpanel'): m = CPanelManager elif os.path.isfile('/usr/local/psa/version'): m = PleskManager elif os.path.isdir('/usr/local/directadmin') and os.path.isfile( '/usr/local/directadmin/custombuild/build'): m = DirectAdminManager else: m = CustomManager is_phpinfo_mode = os.path.exists('/opt/cloudlinux/flags/enabled-flags.d/' 'xray-per-domain-php-version-mode.flag') return m(system_id, phpinfo_mode=is_phpinfo_mode) PK �b�Z�0��Gz Gz base.pynu �[��� # -*- 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 and helper functions """ import json import logging import os import pwd import shutil import signal import subprocess import sys import typing from datetime import datetime, timedelta from glob import glob from typing import Iterator, Optional, Tuple import psutil from clcommon.const import Feature from clcommon.cpapi import ( is_panel_feature_supported, get_installed_php_versions ) from clwpos.papi import php_get_vhost_versions, is_wpos_supported from clcommon.utils import is_litespeed_running from clcommon.clpwd import drop_privileges from secureio import disable_quota from xray import gettext as _ from ..adviser.clwpos_get import ClWposGetter from ..apiclient import get_client from ..continuous.manager import ContinuousManager from ..internal.exceptions import ( XRayManagerError, XRayAPIError, XRayMissingDomain, XRayManagerExit, XRayManagerExitPHPUnsupported ) from ..internal.fpm_utils import FPMReloadController from ..internal.local_counters import open_local_storage from ..internal.nginx_utils import NginxUserCache from ..internal.types import DomainInfo, Task, url_split from ..internal.user_manager import ManageUserAgent from ..internal.advanced_metrics import AdvancedMetrics from ..internal.user_plugin_utils import ( get_xray_exec_user, user_mode_restricted, nginx_user_cache ) from ..internal.utils import ( no_active_tasks, switch_schedstats, ssa_disabled, _cagefsctl_remount, set_privileges, is_file_recently_modified ) from ..reconfiguration.global_ini import ( create_ini_files, remove_ini_files, is_global_ini_mode, remove_global_ini_mode_marker, create_global_ini_mode_marker ) if typing.TYPE_CHECKING: from ..apiclient.api_client import APIClient class BaseManager: """ Basic manager abstract class. Implements methods common for all managers. Requires managers to implement their custom methods. """ DAEMON_PIDFILE = '/var/run/xray-agent.pid' VERSIONS = { 'alt-php54': '/opt/alt/php54/link/conf', 'alt-php55': '/opt/alt/php55/link/conf', 'alt-php56': '/opt/alt/php56/link/conf', 'alt-php70': '/opt/alt/php70/link/conf', 'alt-php71': '/opt/alt/php71/link/conf', 'alt-php72': '/opt/alt/php72/link/conf', 'alt-php73': '/opt/alt/php73/link/conf', 'alt-php74': '/opt/alt/php74/link/conf', 'alt-php80': '/opt/alt/php80/link/conf', 'alt-php81': '/opt/alt/php81/link/conf', 'alt-php82': '/opt/alt/php82/link/conf', 'alt-php83': '/opt/alt/php83/link/conf', 'alt-php84': '/opt/alt/php84/link/conf', } def __init__(self, system_id: str, phpinfo_mode: bool = False): # FIXME: there is no reason to keep class instead of instance # refactor this once it causes issues self.api_client_class: type[APIClient] = get_client() self.sys_id = system_id self.logger = logging.getLogger('manager') self.continuous_monitoring = ContinuousManager() self.manage_user_agent = ManageUserAgent() self.phpinfo_mode = phpinfo_mode self.ui_api_client = get_client('lists')(system_id=self.sys_id) def _get_daemon_pid(self) -> Optional[int]: try: with open(self.DAEMON_PIDFILE) as f: return int(f.read()) except (OSError, IOError): logging.warning('Unable to read daemon pid from pidfile.') def supported_versions(self) -> dict: """ Get supported PHP versions :return: a dict with supported versions """ return self.VERSIONS def is_version_supported(self, php_version: str) -> bool: """ Check if given PHP version is supported :param php_version: PHP version, e.g. 'alt-phpXY' :return: True if version is supported, False otherwise """ return php_version in self.supported_versions() def _ini_path(self, domain_info: DomainInfo) -> str: """ Get a path to directory for additional ini file for based on panel-set version """ return self.supported_versions().get(domain_info.panel_php_version) 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 :param domain_info: a DomainInfo object :return: path to directory for ini files """ ini_path = self._ini_path(domain_info) try: if domain_info.php_ini_scan_dir: ini_path = domain_info.php_ini_scan_dir elif self.is_selector_enabled(domain_info): ini_path = domain_info.selector_ini_path else: ini_path = domain_info.phpd_location_ini_path or ini_path except ValueError: # failed to resolve CageFS prefix for user pass self.logger.info('Ini path resolved as %s', ini_path) return ini_path def get_php_version(self, domain_info: DomainInfo) -> str: """ Get PHP version which serves given domain :param domain_info: a DomainInfo object, including user of domain and PHP version set in control panel environment :return: real php version of domain (selector or panel one) """ if not domain_info.is_selector_applied and \ self.is_selector_enabled(domain_info): self.logger.info('Selector is enabled for user %s', domain_info.user) current_version = domain_info.selector_php_version else: current_version = domain_info.panel_php_version self.logger.info('PHP version detected as %s', current_version) return current_version def is_selector_enabled(self, domain_info: DomainInfo) -> bool: """ Is selector enabled for given domain :param domain_info: a DomainInfo object, including user of domain and PHP version set in control panel :return: True if selector is enabled for domain, False otherwise """ if not is_panel_feature_supported(Feature.PHP_SELECTOR): return False if domain_info.selector_php_version is None: return False return self.panel_specific_selector_enabled(domain_info) def php_procs_reload(self, domain_info: DomainInfo) -> None: """ Reload FPM service or kill all *php* processes of user :param domain_info: a ready-to-use DomainInfo object """ self.reset_criu_imgs(domain_info.name) if domain_info.panel_fpm: self.restart_fpm_service(domain_info) elif is_litespeed_running(): self.gracefully_restart_litespeed(domain_info.user) else: self.kill_user_php(domain_info.user) def gracefully_restart_litespeed(self, username): """ Litespeed's graceful restart. https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:php:detached_mode#for_a_user """ pw = pwd.getpwnam(username) with drop_privileges(username), disable_quota(): # just change it's modification date try: open(os.path.join(pw.pw_dir, '.lsphp_restart.txt'), 'w').close() except (FileNotFoundError, PermissionError): self.logger.warning('Unable to restart lsws after changes', exc_info=True) def kill_user_php(self, username: str) -> list: """ Kill all PHP processes, which belong to given username :param username: name of user :return: list of pids of successfully killed processes """ killed_procs = list() for proc in self.user_procs(username): if 'php' in proc.info['name']: proc.send_signal(signal.SIGHUP) killed_procs.append(proc.info['pid']) return killed_procs @staticmethod def user_procs(user_name: str) -> Iterator[psutil.Process]: """ Generator yields processes, which belong to given user_name. Processes are checked using UID, not user_name :param user_name: user name :return: a generator object """ uid_by_name = pwd.getpwnam(user_name).pw_uid attrs = ['name', 'pid', 'uids'] for p in psutil.process_iter(attrs): if uid_by_name in (p.info['uids'].real, p.info['uids'].effective): yield p def reset_criu_imgs(self, domain: str) -> None: """ Reset criu images if any found for given domain in order to enable/disable X Ray correctly """ criu_imgs_dir = glob(f'/var/run/mod_lsapi/*{domain}_criu_imgs') if criu_imgs_dir: shutil.rmtree(criu_imgs_dir[0]) self.logger.info('criu images in %s dropped', criu_imgs_dir[0]) def _domain_info_by_url(self, url: str) -> DomainInfo: """ DomainInfo retrieving based on url """ domain_name, _ = url_split(url) # get_domain_info includes validation of domain existence return self.get_domain_info(domain_name) def get_domain_info(self, domain_name: str) -> DomainInfo: """ Retrieve information about given domain from control panel environment Required to be implemented by child classes :param domain_name: name of domain :return: a DomainInfo object """ raise NotImplementedError( _('Manager should implement retrieving domain info!')) def panel_specific_selector_enabled(self, domain_info: DomainInfo) -> bool: """ Check if selector is enabled specifically for panel Required to be implemented by child classes :param domain_info: a DomainInfo object :return: True if yes, False otherwise """ raise NotImplementedError( _('Manager should implement specific panel check for selector enabled!')) def fpm_service_name(self, dom_info: DomainInfo) -> str: """ Get FPM service name for particular panel :param dom_info: DomainInfo object :return: name of FPM service """ raise NotImplementedError( _('Manager should implement FPM service name retrieving!')) def restart_fpm_service(self, dom_info: DomainInfo) -> None: """ Restart FPM service for particular version :param dom_info: DomainInfo object """ fpm_serv = self.fpm_service_name(dom_info) try: subprocess.run( ['/sbin/service', fpm_serv, 'reload'], capture_output=True, text=True, check=True) self.logger.info('Service %s reloaded', fpm_serv) except (OSError, ValueError, subprocess.SubprocessError) as e: self.logger.error('Failed to reload FPM service', extra={'err': str(e), 'info': dom_info}) else: FPMReloadController(fpm_serv).save_latest_reload() @staticmethod def prepare_wpos_info_path(username: str) -> Tuple[str, int]: """Resolve path for user and prepare directory if needed""" _upwd = pwd.getpwnam(username) _path = f'/var/clwpos/uids/{_upwd.pw_uid}/info.json' if not os.path.isdir(os.path.dirname(_path)): os.makedirs(os.path.dirname(_path)) _cagefsctl_remount(username) return _path, _upwd.pw_gid def write_wpos_info(self, user: str) -> None: """Prepare and write info.json file for WPOS (AWP) utilities""" if not is_wpos_supported(): return dest, user_gid = self.prepare_wpos_info_path(user) if is_file_recently_modified(dest): self.logger.info('File %s modified recently, skip generating', dest) return working_dest = dest + '.tmp' to_write = dict( vhost_versions=php_get_vhost_versions(user), installed_versions=get_installed_php_versions() ) self.logger.debug('Going to write file %s with %s', dest, to_write) with set_privileges(target_uid=0, target_gid=user_gid, mask=0o137): try: with open(working_dest, 'w') as info_json: json.dump(to_write, info_json) shutil.move(working_dest, dest) self.logger.info('WPOS data written successfully') except OSError as e: self.logger.error('Unable to write info for WPOS due to %s', str(e)) @staticmethod def response(**kwargs) -> str: """ Create JSON response message with result field == success and given keyword arguments in other fields :return: json packed string """ initial = {'result': 'success'} if kwargs: initial.update(kwargs) return json.dumps(dict(sorted(initial.items()))) def response_continuous(self, **kwargs) -> 'json str': """ Create JSON response message for continuous actions. Extends action definition with 'continuous' addition :return: json packed string """ if kwargs.get('action'): kwargs['action'] = f"{kwargs['action']} continuous" return self.response(**kwargs) @user_mode_restricted def start(self, *, url: str, client_ip: str, tracing_by: str, tracing_count: int, auto_task: bool = False, autotracing: bool = False) -> 'json str': """ Start monitoring of given URL. Arguments are only allowed by keyword :param url: URL to monitor :param client_ip: a client IP address :param tracing_by: time or qty :param tracing_count: a number of minutes|requests to monitor :param auto_task: if a task is an auto created one (x-ray 2.0) :param autotracing: task is an auto created with user marked as *autotracing* :return: JSON encoded result of start action """ # domain existence validation inside domain_info = self._domain_info_by_url(url) self.write_wpos_info(domain_info.user) real_php_version = self.get_php_version(domain_info) if self.is_version_supported(real_php_version): ini_files_location = self.get_ini_path(domain_info) else: self.logger.warning('PHP version is unsupported', extra={'version': real_php_version}) raise XRayManagerExitPHPUnsupported(url, real_php_version) # --- ask redis for tracing task id --- client = self.api_client_class(system_id=self.sys_id) user = '*autotracing*' if autotracing else get_xray_exec_user() tracing_task = Task(url=url, client_ip=client_ip, tracing_by=tracing_by, tracing_count=tracing_count, ini_location=ini_files_location, auto_task=auto_task, user=user, domain_owner=domain_info.user) tracing_task.task_id = client.create(tracing_task) # --- tracing_task.add(php_version=real_php_version) # --- if time, add cron job tracing_task.set_cronjob(system_id=self.sys_id) # --- # --- update task status in redis db client.update(tracing_task.starttime) # --- self.php_procs_reload(domain_info) switch_schedstats(enabled=True) try: ClWposGetter().post_metadata(domain_info.user, domain_info.name) except XRayAPIError as e: logging.warning('Failed to send metadata to Smart Advice with: %s', e.reason) NginxUserCache(domain_info.user).disable() # automatically create missing ini files # in cagefs and other places if we use global mode if is_global_ini_mode(): create_ini_files() return self.response(action='start', tracing_task_id=tracing_task.task_id) def start_auto(self, *, url: str) -> 'json str': """ Start continuous monitoring of given URL (auto task). Arguments are only allowed by keyword :param url: URL to monitor :return: JSON encoded result of start action """ return self.start(url=url, client_ip='*', tracing_by='time', tracing_count=1430, auto_task=True) def start_autotracing(self, *, url: str, tracing_count: int = 20) -> 'json str': """ Start monitoring of given URL (autotracing task). Note, user is overridden as *autotracing* Arguments are only allowed by keyword :param url: URL to monitor :param tracing_count: count of requests to capture :return: JSON encoded result of start action """ return self.start(url=url, client_ip='*', tracing_by='request_qty', tracing_count=tracing_count, auto_task=True, autotracing=True) def stop(self, tracing_task_id: str) -> 'json str': """ Stop monitoring of given task ID :param tracing_task_id: an ID of task to stop :return: JSON encoded result of stop action """ client = self.api_client_class(system_id=self.sys_id, tracing_task_id=tracing_task_id) tracing_task = client.get_task() self._request_daemon_storage_flush() with open_local_storage(tracing_task.fake_id) as storage: tracing_task.update_with_local_data(next_request_id=storage.next_request_id) try_to_complete_statuses = ['running', 'stopped', 'completed', 'hold'] if tracing_task.status not in try_to_complete_statuses: raise XRayManagerExit( _("Cannot stop task with status '%s'") % str(tracing_task.status)) # for FPM pre-reload check try: domain_info = self._domain_info_by_url(tracing_task.url) tracing_task.set_domain_owner(domain_info.user) except XRayMissingDomain: self.logger.info('Stopping task for an already deleted domain') domain_info = None tracing_task.remove() # --- if time, remove cron job tracing_task.drop_cronjob() # --- # --- recalculate remaining count remaining_count = tracing_task.recalculate_counts() # --- # --- stop or complete task if remaining_count > 0: client.stop(remaining_count) else: self._complete(tracing_task, client) # --- if domain_info is not None: NginxUserCache(domain_info.user).restore() self.php_procs_reload(domain_info) if no_active_tasks() and ssa_disabled(): switch_schedstats(enabled=False) return self.response(action='stop', tracing_task_id=tracing_task.task_id) def _request_daemon_storage_flush(self): """ Request daemon to flush it's in-memory storage on disk using SIGUSR2 and wait for getting SIGUSR2 back that daemon sends when it successfully flushed data on disk and we are ready to proceed. """ # don't process SIGUSR2 with default handler signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGUSR2}) daemon_pid = self._get_daemon_pid() if daemon_pid: try: os.kill(daemon_pid, signal.SIGUSR2) except OSError: self.logger.warning('Unable to send daemon signal. Some stats will not be flushed. ' 'Maybe daemon is stopped?') self.logger.info('Waiting for daemon to signal back about flush end.') siginfo = signal.sigtimedwait({signal.SIGUSR2}, 15.0) if siginfo is None: logging.warning('Daemon did not signal back in given timeout. ' 'Some stats may not be flushed. Continue.') # and restore default signal handler signal.pthread_sigmask(signal.SIG_DFL, {signal.SIGUSR2}) @user_mode_restricted def continue_(self, tracing_task_id: str) -> 'json str': """ Continue monitoring of given task ID :param tracing_task_id: an ID of task to continue :return: JSON encoded result of continue action """ client = self.api_client_class(system_id=self.sys_id, tracing_task_id=tracing_task_id) tracing_task = client.get_task() if tracing_task.status in ('running', 'completed'): raise XRayManagerExit( _("Cannot continue task with status '%s'") % str(tracing_task.status)) # for FPM pre-reload check domain_info = self._domain_info_by_url(tracing_task.url) tracing_task.set_domain_owner(domain_info.user) if '54' in self.supported_versions(): # for custom panels only real_php_version = self.get_php_version(domain_info) else: # no need in redundant call for non-custom panels real_php_version = None tracing_task.add(php_version=real_php_version) # --- if time, add cron job tracing_task.set_cronjob(system_id=self.sys_id) # --- # --- update task status in redis db client.update(tracing_task.starttime) # --- NginxUserCache(domain_info.user).disable() self.php_procs_reload(domain_info) switch_schedstats(enabled=True) return self.response(action='continue', tracing_task_id=tracing_task.task_id) def complete(self, tracing_task_id: str) -> 'json str': """ Complete given tak ID :param tracing_task_id: an ID of task to complete :return: JSON encoded result of complete action """ client = self.api_client_class(system_id=self.sys_id, tracing_task_id=tracing_task_id) tracing_task = client.get_task() if tracing_task.status in ('running', 'completed'): raise XRayManagerExit( _("Cannot complete task with status '%s'") % str(tracing_task.status)) self._complete(tracing_task, client) return self.response(action='complete', tracing_task_id=tracing_task.task_id) @staticmethod def _is_to_complete(task: dict) -> bool: """ Returns True if task should be completed. False otherwise. """ now = datetime.now() tracing_by = task['tracing_by'] inception = task['starttime'] or task['createtime'] task_start_time = datetime.fromtimestamp(inception) if tracing_by == 'request_qty' and task_start_time + timedelta(days=2) < now: return True if tracing_by == 'time' and task_start_time + timedelta(minutes=task['tracing_count']) < now: return True return False def autocomplete_tasks(self) -> 'json str': """ Gets all server tasks and complete those which match following criteria: - traced_by requests_qty: if task started 2 days ago - complete - traced_by time: if task runs longer than start time + tracing time - complete """ all_server_tasks = self.ui_api_client.get_task_list().get('result', []) self.logger.info('Check tasks for autocompleting...%s', str(all_server_tasks)) for task_item in all_server_tasks: if not self._is_to_complete(task_item): continue logging.info('Going to complete task with id: %s', str(task_item['tracing_task_id'])) self.stop(task_item['tracing_task_id']) return self.response(action='autocomplete-tasks') @staticmethod def _complete(t_task: Task, client: 'APIClient'): """ Common complete actions: - delete cron job, - erase request id file - send 'complete' status to mongo :param t_task: a tracing task object :param client: an APIClient object """ t_task.drop_cronjob() t_task.erase_request_id_storage() client.complete() if t_task.auto_task: client.share() def delete(self, tracing_task_id: str) -> 'json str': """ Delete given task ID :param tracing_task_id: an ID of task to delete :return: JSON encoded result of delete action """ client = self.api_client_class(system_id=self.sys_id, tracing_task_id=tracing_task_id) tracing_task = client.get_task() if tracing_task.status == 'running': raise XRayManagerExit( _("Cannot delete task with status '%s'") % str(tracing_task.status)) client.delete() return self.response(action='delete', tracing_task_id=tracing_task.task_id) def enable_continuous(self, url: str, email: str) -> 'json str': """ Enable continuous monitoring for given URL :param url: URL to monitor :param email: email to send reports to :return: JSON encoded result of enable action """ # get_domain_info throws an exception in case of non-existent domain d_info = self._domain_info_by_url(url) self.continuous_monitoring.enable(d_info.name, url, email) return self.response_continuous(action='enable', url=url) def disable_continuous(self, url: str) -> 'json str': """ Disable continuous monitoring for given URL :param url: URL to monitor :return: JSON encoded result of disable action """ domain_name, _ = url_split(url) self.continuous_monitoring.disable(domain_name) return self.response_continuous(action='disable', url=url) def start_continuous(self, url: str) -> 'json str': """ Start continuous monitoring for given URL :param url: URL to monitor :return: JSON encoded result of start action """ domain_name, _ = url_split(url) self.continuous_monitoring.start(domain_name) return self.response_continuous(action='start', url=url) def stop_continuous(self, url: str) -> 'json str': """ Stop continuous monitoring for given URL :param url: URL to monitor :return: JSON encoded result of stop action """ domain_name, _ = url_split(url) self.continuous_monitoring.stop(domain_name) return self.response_continuous(action='stop', url=url) def continuous_tracing_list(self) -> 'json str': """ Get list of continuous monitoring tasks :return: JSON encoded result of get list action """ tracing_list = self.continuous_monitoring.get_tracing_list() return self.response(action='get continuous list', data=tracing_list) def tasks_list(self) -> 'json str': """ Get list of tasks """ return self.response(action='tasks-list', data=self.ui_api_client.get_task_list()) def requests_list(self, task_id: str) -> 'json str': """ Get list of requests for given tracing task id """ # needed for user verification client = self.api_client_class(system_id=self.sys_id, tracing_task_id=task_id) _ = client.get_task() return self.response(action='requests-list', data=self.ui_api_client.get_request_list(task_id)) def request_data(self, task_id: str, request_id: int) -> 'json str': """ Get collected statistics for request ID of given tracing task """ # needed for user verification client = self.api_client_class(system_id=self.sys_id, tracing_task_id=task_id) _ = client.get_task() return self.response(action='request-data', data=self.ui_api_client.get_request_data( task_id, request_id)) def enable_user_agent(self) -> 'json str': """ Enable X-Ray User Agent: start or restart service if it is accidentally already running For systemd systems -- start socket unit only For SysV -- start the entire service :return: JSON encoded result of enable action """ self.manage_user_agent.enable() return self.response(action='enable-user-agent') def disable_user_agent(self) -> 'json str': """ Disable X-Ray User Agent: stop the entire service or do nothing if it is accidentally not running For systemd systems -- also check if socket unit is running and stop it too :return: JSON encoded result of disable action """ self.manage_user_agent.disable() return self.response(action='disable-user-agent') def user_agent_status(self) -> 'json str': """ Get status of X-Ray User Agent service :return: JSON encoded result of status action """ agent_status = self.manage_user_agent.status() return self.response(action='user-agent-status', status=agent_status, user_nginx_cache=nginx_user_cache()) def advanced_metrics(self, args) -> 'json str': """ Advanced metrics tool :return: JSON encoded result of status action """ am = AdvancedMetrics() if args['enable'] is True: am.enable() elif args['disable'] is True: am.disable() elif args['status'] is True: status = am.status() return self.response(status=status) return self.response() def enable_serverwide_mode(self, args) -> 'json str': """ Advanced metrics tool :return: JSON encoded result of status action """ create_global_ini_mode_marker() create_ini_files() return self.response() def disable_serverwide_mode(self, args) -> 'json str': """ Advanced metrics tool :return: JSON encoded result of status action """ remove_ini_files() remove_global_ini_mode_marker() return self.response() PK �b�Zyp�! ! cpanel.pynu �[��� # -*- 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 cPanel """ import json import os import subprocess from collections import ChainMap from pipes import quote from typing import Optional from clcommon.lib.whmapi_lib import WhmApiError, WhmApiRequest from clcommon.utils import get_cl_version, is_litespeed_running from xray.internal import phpinfo_utils from ..internal.exceptions import XRayManagerError, XRayMissingDomain from ..internal.types import DomainInfo from ..internal.user_plugin_utils import ( user_mode_verification, with_fpm_reload_restricted, ) from .base import BaseManager class CPanelManager(BaseManager): """ Class implementing an X-Ray manager behaviour for cPanel """ VERSIONS_cPanel = { 'ea-php54': '/opt/cpanel/ea-php54/root/etc/php.d', 'ea-php55': '/opt/cpanel/ea-php55/root/etc/php.d', 'ea-php56': '/opt/cpanel/ea-php56/root/etc/php.d', 'ea-php70': '/opt/cpanel/ea-php70/root/etc/php.d', 'ea-php71': '/opt/cpanel/ea-php71/root/etc/php.d', 'ea-php72': '/opt/cpanel/ea-php72/root/etc/php.d', 'ea-php73': '/opt/cpanel/ea-php73/root/etc/php.d', 'ea-php74': '/opt/cpanel/ea-php74/root/etc/php.d' } try: cl_version = get_cl_version() if cl_version != 'cl6': VERSIONS_cPanel['ea-php80'] = '/opt/cpanel/ea-php80/root/etc/php.d' VERSIONS_cPanel['ea-php81'] = '/opt/cpanel/ea-php81/root/etc/php.d' VERSIONS_cPanel['ea-php82'] = '/opt/cpanel/ea-php82/root/etc/php.d' VERSIONS_cPanel['ea-php83'] = '/opt/cpanel/ea-php83/root/etc/php.d' # cl7, cl7h if 'cl7' not in cl_version: VERSIONS_cPanel['ea-php84'] = '/opt/cpanel/ea-php84/root/etc/php.d' except TypeError: # primarily for the test run on the build system pass def supported_versions(self) -> ChainMap: """ Get supported PHP versions :return: a chained map with basic supported versions and cPanel supported versions """ return ChainMap(self.VERSIONS, self.VERSIONS_cPanel) def resolve_alias(self, domain_name: str) -> Optional[str]: """ Try to resolve domain_name if it is an alias :param domain_name: original domain name :return: resolved domain name alias """ try: result = WhmApiRequest( 'domainuserdata' ).with_arguments( domain=domain_name ).call() except WhmApiError as e: if 'system does not have a domain named' in str(e): self.logger.warning('Domain does not exist on the server', extra={'domain_name': domain_name}) raise XRayMissingDomain(domain_name) from e # this None was here before this patch, just non-explicitly return None else: return result['userdata']['servername'] def check_domain(self, name: str, domains_data: list, original_name=None) -> DomainInfo: """ Try to find given name among known domains :param name: name of domain to find :param domains_data: list of known domains :param original_name: original domain name (in case of alias resolving) :return: a DomainInfo object """ domain = next(( item for item in domains_data if item['vhost'] == name ), None) if domain is None: self.logger.warning('Domain does not exist on the server', extra={'domain_name': name}) raise XRayMissingDomain(name) if self.phpinfo_mode: config = phpinfo_utils.get_php_configuration( domain['account'], domain=original_name if original_name else name) return DomainInfo( name=original_name if original_name else name, panel_php_version=config.get_full_php_version('ea-php'), 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['account'], panel_fpm=config.is_php_fpm, ) else: return DomainInfo( name=original_name if original_name else name, panel_php_version=domain['version'], user=domain['account'], panel_fpm=domain[ 'php_fpm'] if not is_litespeed_running() else False, is_selector_applied=False ) @user_mode_verification @with_fpm_reload_restricted def get_domain_info(self, domain_name: str) -> DomainInfo: """ Retrieve information about given domain from control panel environment: PHP version, user of domain. Try to resolve alias if domain was not found in API response :param domain_name: name of domain :return: a DomainInfo object """ result = WhmApiRequest( 'php_get_vhost_versions' ).call() domain_php = result['versions'] try: _info = self.check_domain(domain_name, domain_php) except XRayManagerError: alias = self.resolve_alias(domain_name) _info = self.check_domain(alias, domain_php, original_name=domain_name) return _info def domain_default_version_allows_selector(self, domain_php_version: str) -> bool: """ Check if given domain uses system default version. And system default is not alt-php. If yes, then it means that selector could be applied for given domain :param domain_php_version: PHP version of domain :return: True if yes, False otherwise """ result = WhmApiRequest( 'php_get_system_default_version' ).call() default_php = result['version'] return 'alt-php' not in default_php and default_php == domain_php_version def panel_specific_selector_enabled(self, domain_info: DomainInfo) -> bool: """ Check if selector is enabled specifically for panel Required to be implemented by child classes :param domain_info: a DomainInfo object :return: True if yes, False otherwise """ domain_php_version = domain_info.panel_php_version return ( # NOTE(vlebedev): CloudLinux PHP Selector in CPanel is considered to be enabled in two cases: # 1. system defaul ea-php version is set as native for domain via MultiPHP Manager, # 2. the same alt-php version is set for domain both via MultiPHP and via CL Selector. # # In the first case, the default ea-php version binaries are replaced with symlinks to /etc/cl.selector # folder entries (which in turn contains symlinks to binaries of currently-chosen in CL Selector alt-php # version). # # In the second case, not binaries but only PHP config folders are switched inside of cagefs: # * for the alt-php version selected both via MultiPHP and CL Selector, /opt/alt/phpXX/link/conf is a # symlink to /etc/cl.php.d/alt-php74 (/var/cagefs/<uid>/<user>/etc/cl.php.d/alt-phpXX outside of cagefs), # * other alt-php versions are still symlinked to /opt/alt/phpXX/etc/php.d (unless overriden by # /etc/cl.selector/symlinks.rules) self.domain_default_version_allows_selector(domain_php_version) or domain_php_version == domain_info.selector_php_version ) and not domain_info.panel_fpm def fpm_service_name(self, dom_info: DomainInfo) -> str: """ Get cPanel FPM service name :param dom_info: a DomainInfo object :return: FPM service name """ return f'{dom_info.panel_php_version}-php-fpm' PK �b�Z>R�� � custom.pynu �[��� # -*- 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 """ 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.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, } 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) @user_mode_verification @with_fpm_reload_restricted 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) 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: 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=domain_conf.php.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 _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 PK �b�Zs�3 3 directadmin.pynu �[��� # -*- 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 DirectAdmin """ import os import re import subprocess import urllib.parse from collections import ChainMap from glob import glob import chardet from xray import gettext as _ from xray.internal import phpinfo_utils from .base import BaseManager from ..internal.exceptions import XRayManagerError, XRayMissingDomain, XRayManagerExit from ..internal.types import DomainInfo from ..internal.user_plugin_utils import ( user_mode_verification, with_fpm_reload_restricted ) class DirectAdminManager(BaseManager): """ Class implementing an X-Ray manager behaviour for DirectAdmin """ da_options_conf = '/usr/local/directadmin/custombuild/options.conf' da_domain_pattern = '/usr/local/directadmin/data/users/*/domains/*.conf' da_subdomain_pattern = '/usr/local/directadmin/data/users/*/domains/*.subdomains' da_alias_pattern = '/usr/local/directadmin/data/users/*/domains/*.pointers' da_docroot_override_pattern = '/usr/local/directadmin/data/users/*/domains/*.subdomains.docroot.override' VERSIONS_DA = { 'php54': '/usr/local/php54/lib/php.conf.d', 'php55': '/usr/local/php55/lib/php.conf.d', 'php56': '/usr/local/php56/lib/php.conf.d', 'php70': '/usr/local/php70/lib/php.conf.d', 'php71': '/usr/local/php71/lib/php.conf.d', 'php72': '/usr/local/php72/lib/php.conf.d', 'php73': '/usr/local/php73/lib/php.conf.d', 'php74': '/usr/local/php74/lib/php.conf.d', 'php80': '/usr/local/php80/lib/php.conf.d', 'php81': '/usr/local/php81/lib/php.conf.d', 'php82': '/usr/local/php82/lib/php.conf.d', 'php83': '/usr/local/php83/lib/php.conf.d', 'php84': '/usr/local/php84/lib/php.conf.d', } def supported_versions(self) -> ChainMap: """ Get supported PHP versions :return: a chained map with basic supported versions and DirectAdmin supported versions """ return ChainMap(self.VERSIONS, self.VERSIONS_DA) def file_readlines(self, filename: str) -> list: """ Read lines from file :param filename: a name of file to read :return: list of stripped lines """ def get_file_encoding(): """ Retrieve file encoding """ with open(filename, 'rb') as f: result = chardet.detect(f.read()) return result['encoding'] try: with open(filename, encoding=get_file_encoding()) as f: return [line.strip() for line in f.readlines()] except OSError as e: self.logger.error('Failed to read [DA conf] file', extra={'fname': filename, 'err': str(e)}) raise XRayManagerExit(_('Failed to read file %s') % filename) from e @property def php_options(self) -> dict: """ Retrieve DirectAdmin PHP settings :return: dict of format {'1': {ver, fpm}, '2': {ver, fpm}...} where '1', '2' etc is an ordinal number of a handler as it is defined in options.conf """ parsed_options = dict() opts = self.file_readlines(self.da_options_conf) def inner_filter(seq, marker): """ Filter PHP release|mode items in seq by marker :param seq: initial sequence :param marker: should be contained in seq item :return: all items from seq containing marker """ return [l for l in seq if marker in l and 'php' in l and not l.startswith('#')] for index, o in enumerate(zip(inner_filter(opts, 'release'), inner_filter(opts, 'mode')), start=1): release, mode = o if 'no' not in release: parsed_options[str(index)] = { 'ver': f"php{''.join(release.split('=')[-1].split('.'))}", 'fpm': 'fpm' in mode, 'handler': mode.split('=')[-1] } return parsed_options @property def main_domains(self) -> dict: """ Retrieve main domains configuration files """ domains = dict() for dom_conf in glob(self.da_domain_pattern): name = os.path.basename(dom_conf).split('.conf')[0] domains[name] = dom_conf return domains @property def subdomains(self) -> dict: """ Retrieve subdomains configuration files """ subdomains = dict() for sub_conf in glob(self.da_subdomain_pattern): for subdom in self.file_readlines(sub_conf): sub_parent = f"{os.path.basename(sub_conf).split('.subdomains')[0]}" sub_name = f"{subdom}.{sub_parent}" subdomains[ sub_name] = f"{sub_conf.split('.subdomains')[0]}.conf" return subdomains @property def aliases(self) -> dict: """ Retrieve aliases configuration files """ aliases = dict() for alias_conf in glob(self.da_alias_pattern): parent_domain_name = alias_conf.split('.pointers')[0] for alias in self.file_readlines(alias_conf): alias_info = alias.split('=') alias_name = alias_info[0] _type = alias_info[-1] if _type == 'pointer': # pointers are not considered as domains, # because they just perform a redirect to parent domain continue aliases[alias_name] = f"{parent_domain_name}.conf" try: for sub in self.file_readlines( f"{parent_domain_name}.subdomains"): aliases[ f"{sub}.{alias_name}"] = f"{parent_domain_name}.conf" except XRayManagerError: # there could be no subdomains pass return aliases @property def subdomains_php_settings(self) -> dict: """ Retrieve subdomains_docroot_override configuration files """ sub_php_set = dict() for sub_doc_override in glob(self.da_docroot_override_pattern): for subdomline in self.file_readlines(sub_doc_override): subdompart, data = urllib.parse.unquote( subdomline).split('=', maxsplit=1) php_select_value = re.search(r'(?<=php1_select=)\d(?=&)', data) if php_select_value is not None: domname = f"{os.path.basename(sub_doc_override).split('.subdomains.docroot.override')[0]}" subdomname = f"{subdompart}.{domname}" sub_php_set[subdomname] = php_select_value.group() return sub_php_set @property def all_sites(self) -> dict: """ Retrieve all domains and subdomains, existing on DA server, including aliases in the form of dict {domain_name: domain_config} :return: {domain_name: domain_config} including subdomains """ da_sites = dict() for bunch in self.main_domains, self.subdomains, self.aliases: da_sites.update(bunch) return da_sites @user_mode_verification @with_fpm_reload_restricted def get_domain_info(self, domain_name: str) -> DomainInfo: """ Retrieve information about given domain from control panel environment: PHP version, user of domain, fpm status :param domain_name: name of domain :return: a DomainInfo object """ try: domain_conf = self.all_sites[domain_name] except KeyError: self.logger.warning( 'Domain does not exist on the server or is a pointer (no task allowed for pointers)', extra={'domain_name': domain_name}) raise XRayMissingDomain(domain_name, message=_("Domain '%(domain_name)s' does not exist on this server " "or is a pointer (no task allowed for pointers)")) data = self.file_readlines(domain_conf) def find_item(item: str) -> str: """ Get config value of item (e.g. item=value) :param item: key to get value of :return: value of item """ found = [line.strip() for line in data if item in line] try: return found[0].split('=')[-1] except IndexError: return '1' opts = self.php_options # Trying to get the subdomain handler first, # get main domain handler if nothing is set for subdomain php_selected = self.subdomains_php_settings.get( domain_name) or find_item('php1_select') # if user config has disabled php version selected -> # DirectAdmin uses the 1st one # grep php1 /usr/local/directadmin/data/users/auser/domains/auser.com.conf # php1_select=2 # cat /usr/local/directadmin/custombuild/options.conf | grep -i php # # PHP Settings # php1_release=7.4 # php1_mode=lsphp # php2_release=no if not opts.get(php_selected): php_selected = '1' if self.phpinfo_mode: config = phpinfo_utils.get_php_configuration( find_item('username'), domain=domain_name) domain_info = DomainInfo( name=domain_name, panel_php_version=config.get_full_php_version('php'), 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=find_item('username'), panel_fpm=config.is_php_fpm, handler=php_selected ) else: domain_info = DomainInfo(name=domain_name, panel_php_version=opts[php_selected]['ver'], user=find_item('username'), panel_fpm=opts[php_selected]['fpm'], handler=php_selected) self.logger.info( 'Retrieved domain info: domain %s owned by %s uses php version %s', domain_name, domain_info.user, domain_info.handler) return domain_info def panel_specific_selector_enabled(self, domain_info: DomainInfo) -> bool: """ Check if selector is enabled specifically for DirectAdmin Required to be implemented by child classes :param domain_info: a DomainInfo object :return: True if yes, False otherwise """ compatible_handlers = ('suphp', 'lsphp', 'fastcgi') current_handler = self.php_options[domain_info.handler]['handler'] return domain_info.handler == '1' and current_handler in compatible_handlers def fpm_service_name(self, dom_info: DomainInfo) -> str: """ Get DirectAdmin FPM service name :param dom_info: a DomainInfo object :return: FPM service name """ return f'php-fpm{dom_info.panel_php_version[-2:]}' def php_procs_reload(self, domain_info: DomainInfo) -> None: """ Copy xray.so for current version, create ini_location directory Reload FPM service or kill all *php* processes of user :param domain_info: a DomainInfo object """ try: subprocess.run(['/usr/share/alt-php-xray/da_cp_xray', domain_info.panel_php_version[-2:]], capture_output=True, text=True) except self.subprocess_errors as e: self.logger.error('Failed to copy xray.so', extra={'err': str(e), 'info': domain_info}) if self.php_options[domain_info.handler]['handler'] == 'mod_php': try: subprocess.run(['/usr/sbin/service', 'httpd', 'restart'], capture_output=True, text=True) self.logger.info('httpd restarted') except self.subprocess_errors as e: self.logger.error('Failed to restart httpd', extra={'err': str(e), 'info': domain_info}) super().php_procs_reload(domain_info) PK �b�Z���80 0 plesk.pynu �[��� # -*- 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 Plesk """ import os import subprocess import xml.etree.ElementTree as ET from collections import ChainMap from xray.internal import phpinfo_utils from .base import BaseManager 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 PleskManager(BaseManager): """ Class implementing an X-Ray manager behaviour for Plesk """ VERSIONS_Plesk = { 'plesk-php54': '/opt/plesk/php/5.4/etc/php.d', 'plesk-php55': '/opt/plesk/php/5.5/etc/php.d', 'plesk-php56': '/opt/plesk/php/5.6/etc/php.d', 'plesk-php70': '/opt/plesk/php/7.0/etc/php.d', 'plesk-php71': '/opt/plesk/php/7.1/etc/php.d', 'plesk-php72': '/opt/plesk/php/7.2/etc/php.d', 'plesk-php73': '/opt/plesk/php/7.3/etc/php.d', 'plesk-php74': '/opt/plesk/php/7.4/etc/php.d', 'plesk-php80': '/opt/plesk/php/8.0/etc/php.d', 'plesk-php81': '/opt/plesk/php/8.1/etc/php.d', 'plesk-php82': '/opt/plesk/php/8.2/etc/php.d', 'plesk-php83': '/opt/plesk/php/8.3/etc/php.d', 'plesk-php84': '/opt/plesk/php/8.4/etc/php.d', } def supported_versions(self) -> ChainMap: """ Get supported PHP versions :return: a chained map with basic supported versions and Plesk supported versions """ return ChainMap(self.VERSIONS, self.VERSIONS_Plesk) @user_mode_verification @with_fpm_reload_restricted def get_domain_info(self, domain_name: str) -> DomainInfo: """ Retrieve information about given domain from control panel environment: PHP version, user of domain, fpm status :param domain_name: name of domain :return: a DomainInfo object """ def resolve_lsphp_version(h): """ Resolve version for lsphp handlers :param h: original Plesk php handler id :return: resolved alt-php* version """ if 'lsphp-custom' in h: ver = 'alt-php56' elif 'lsphp' in h: ver = f"alt-php{h.split('-')[-1]}" else: ver = '-'.join(h.split('-')[:2]) return ver domain_data = next((item for item in self.query_db() if item[0] == domain_name), None) if domain_data is None: self.logger.warning('Domain does not exist on the server', extra={'domain_name': domain_name}) raise XRayMissingDomain(domain_name) domain, user, handler = domain_data self.logger.info( 'Retrieved domain info: domain %s owned by %s uses php version %s', domain_name, user, handler) if self.phpinfo_mode: config = phpinfo_utils.get_php_configuration( user, domain=domain_name) return DomainInfo( name=domain_name, panel_php_version=config.get_full_php_version('plesk-php'), 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=user, panel_fpm=config.is_php_fpm, handler=handler) else: return DomainInfo( name=domain_name, panel_php_version=resolve_lsphp_version(handler), user=user, panel_fpm='fpm' in handler, handler=handler) @staticmethod def query_db() -> tuple: """ Query Plesk database through plesk db utility and yeild parsed xml result :return: tuple(domain_name, domain_user, domain_handler) """ def check_path_env(): """ plesk db utility needs to be able to find mysql executable, which resides in /usr/bin. If we do not have it in PATH, the error will be thrown: 'exec: "mysql": executable file not found in $PATH' """ if '/usr/bin' not in os.environ.get('PATH', ''): return {'PATH': '/usr/bin'} else: return None query = """select d.name,s.login,h.php_handler_id from (select id, name from domains union select dom_id, name from domain_aliases) d join hosting h on d.id=h.dom_id join sys_users s on h.sys_user_id=s.id""" result = subprocess.run(['/usr/sbin/plesk', 'db', query, '--xml'], capture_output=True, text=True, env=check_path_env()) try: root = ET.fromstring(''.join(result.stdout)) for row in root.iter('row'): domain_name = row.find("./field[@name='name']").text user_name = row.find("./field[@name='login']").text handler = row.find("./field[@name='php_handler_id']").text yield domain_name, user_name, handler except ET.ParseError as e: raise XRayManagerError( _('Failed to parse XML from plesk db output: %s') % str(result.stdout)) from e def panel_specific_selector_enabled(self, domain_info: DomainInfo) -> bool: """ Check if selector is enabled specifically for Plesk panel :param domain_info: a DomainInfo object :return: True if yes, False otherwise """ def same_php_in_both_selectors(): """ Checks if php selector and cloudlinux selector have the same php version. :param domain_info: a DomainInfo object :return: digits as string or None """ if domain_info.selector_php_version: return domain_info.selector_php_version[ -2:] in domain_info.handler return False if 'lsphp' in domain_info.handler: return ('custom' in domain_info.handler or same_php_in_both_selectors()) return not domain_info.panel_fpm def fpm_service_name(self, dom_info: DomainInfo) -> str: """ Get Plesk FPM service name :param dom_info: a DomainInfo object :return: FPM service name """ return dom_info.handler PK �b�Z�9�P $ __pycache__/__init__.cpython-311.pycnu �[��� � �ؠg� � �f � d dl Z ddlmZ ddlmZ ddlmZ ddlmZ ddl m Z dd lmZ d e ddfd �ZdS )� N� )�BaseManager)� CPanelManager)� CustomManager)�DirectAdminManager)�PleskManager� )�read_jwt_token� system_id�returnzManager instancec � � t � � t j � d� � rt }ntt j � d� � rt }nMt j � d� � r't j � d� � rt }nt }t j � d� � } || |�� � S )zk Factory function for appropriate manager initialization :return: appropriate manager instance z/usr/local/cpanel/cpanelz/usr/local/psa/versionz/usr/local/directadminz(/usr/local/directadmin/custombuild/buildzK/opt/cloudlinux/flags/enabled-flags.d/xray-per-domain-php-version-mode.flag)�phpinfo_mode) r �os�path�isfiler r �isdirr r �exists)r �m�is_phpinfo_modes �L/opt/cloudlinux/venv/lib64/python3.11/site-packages/xray/manager/__init__.py�initialize_managerr s� � � ���� �w�~�~�0�1�1� ���� ����0� 1� 1� ���� ����/� 0� 0� �R�W�^�^�6�68� 68� �������g�n�n� &M� N� N�O��1�Y�_�5�5�5�5� )r �baser �cpanelr �customr �directadminr �pleskr �internal.utilsr �strr � r r �<module>r! s� �� � � � � � � � � � � !� !� !� !� !� !� !� !� !� !� !� !� +� +� +� +� +� +� � � � � � � +� +� +� +� +� +�6�#� 6�*<� 6� 6� 6� 6� 6� 6r PK �b�Z�A��O� O� __pycache__/base.cpython-311.pycnu �[��� � �ؠgGz � � � d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl m Z mZ ddlmZ ddl m Z mZmZ ddlZddlmZ ddlmZmZ ddlmZmZ dd lmZ dd lmZ ddlmZ ddlm Z! d dl"m#Z# d dl$m%Z% d dl&m'Z' d dl(m)Z)m*Z*m+Z+m,Z,m-Z- d dl.m/Z/ d dl0m1Z1 d dl2m3Z3 d dl4m5Z5m6Z6m7Z7 d dl8m9Z9 d dl:m;Z; d dl<m=Z=m>Z>m?Z? d dl@mAZAmBZBmCZCmDZDmEZEmFZF d dlGmHZHmIZImJZJmKZKmLZL e jM rd dlNmOZO G d� d� � ZPdS )zX This module contains classes implementing X-Ray Manager behaviour and helper functions � N)�datetime� timedelta)�glob)�Iterator�Optional�Tuple)�Feature)�is_panel_feature_supported�get_installed_php_versions)�php_get_vhost_versions�is_wpos_supported)�is_litespeed_running)�drop_privileges)� disable_quota)�gettext� )�ClWposGetter)� get_client)�ContinuousManager)�XRayManagerError�XRayAPIError�XRayMissingDomain�XRayManagerExit�XRayManagerExitPHPUnsupported)�FPMReloadController)�open_local_storage)�NginxUserCache)� DomainInfo�Task� url_split)�ManageUserAgent)�AdvancedMetrics)�get_xray_exec_user�user_mode_restricted�nginx_user_cache)�no_active_tasks�switch_schedstats�ssa_disabled�_cagefsctl_remount�set_privileges�is_file_recently_modified)�create_ini_files�remove_ini_files�is_global_ini_mode�remove_global_ini_mode_marker�create_global_ini_mode_marker)� APIClientc � � e Zd ZdZdZddddddd d ddd ddd� Zd_dedefd�Zde e fd�Zdefd�Z dedefd�Zdedefd�Zdedefd�Zdedefd�Zdedefd�Zdeddfd �Zd!� Zd"edefd#�Zed$edeej fd%�� � Zd&eddfd'�Zd(edefd)�Zd*edefd+�Zdedefd,�Z d-edefd.�Z!d-eddfd/�Z"ed"ede#ee f fd0�� � Z$d1eddfd2�Z%edefd3�� � Z&d`d5�Z'e(ddd6�d(ed7ed8ed9e d:ed;edd4fd<�� � Z)d(edd4fd=�Z*d>d?�d(ed9e dd4fd@�Z+dAedd4fdB�Z,dC� Z-e(dAedd4fdD�� � Z.dAedd4fdE�Z/edFedefdG�� � Z0d`dH�Z1edIe2dJdKfdL�� � Z3dAedd4fdM�Z4d(edNedd4fdO�Z5d(edd4fdP�Z6d(edd4fdQ�Z7d(edd4fdR�Z8d`dS�Z9d`dT�Z:dUedd4fdV�Z;dUedWe dd4fdX�Z<d`dY�Z=d`dZ�Z>d`d[�Z?d`d\�Z@d`d]�ZAd`d^�ZBdS )a�BaseManagerz� Basic manager abstract class. Implements methods common for all managers. Requires managers to implement their custom methods. z/var/run/xray-agent.pidz/opt/alt/php54/link/confz/opt/alt/php55/link/confz/opt/alt/php56/link/confz/opt/alt/php70/link/confz/opt/alt/php71/link/confz/opt/alt/php72/link/confz/opt/alt/php73/link/confz/opt/alt/php74/link/confz/opt/alt/php80/link/confz/opt/alt/php81/link/confz/opt/alt/php82/link/confz/opt/alt/php83/link/confz/opt/alt/php84/link/conf) z alt-php54z alt-php55z alt-php56z alt-php70z alt-php71z alt-php72z alt-php73z alt-php74z alt-php80z alt-php81z alt-php82z alt-php83z alt-php84F� system_id�phpinfo_modec � � t � � | _ || _ t j d� � | _ t � � | _ t � � | _ || _ t d� � | j �� � | _ d S )N�manager�lists�r4 )r �api_client_class�sys_id�logging� getLogger�loggerr �continuous_monitoringr! �manage_user_agentr5 � ui_api_client)�selfr4 r5 s �H/opt/cloudlinux/venv/lib64/python3.11/site-packages/xray/manager/base.py�__init__zBaseManager.__init__d sr � � 2<���������'� �2�2���%6�%8�%8��"�!0�!2�!2���(���0�Z��0�0�4�;�G�G�G����� �returnc � � t | j � � 5 }t |� � � � � cd d d � � S # 1 swxY w Y d S # t t f$ r t j d� � Y d S w xY w)Nz'Unable to read daemon pid from pidfile.)�open�DAEMON_PIDFILE�int�read�OSError�IOErrorr<