Sindbad~EG File Manager
# coding=utf-8
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
from __future__ import absolute_import, division
from typing import Optional, Dict, AnyStr, Union, List
from clcommon.lib.consts import DEFAULT_JWT_ES_TOKEN_PATH
from clcommon.utils import process_is_running, get_cl_version
from jwt import decode, exceptions
import configparser
import io
import os
import json
import fcntl
import struct
import time
from clcommon.clcaptain import mkdir as mkdir_p
from cldetectlib import CL_CONFIG_FILE
from secureio import write_file_via_tempfile
from clconfig.cagefs_statistics_config import check_cagefs_initialized
from clcommon.lib.whmapi_lib import WhmApiRequest, WhmApiError
def dummy_none_function(*a, **kw):
return None
try:
from clselect.clselectctl import interpreter_versions_short_summary
from clselector.clpassenger_detectlib import is_clpassenger_active
except ImportError:
interpreter_versions_short_summary = dummy_none_function
is_clpassenger_active = dummy_none_function
_CL_STATISTICS_SECTION = "license_check"
_CL_STATISTICS_COLLECT_STATE_OPTION = "cl_statistics_enabled"
_CL_STATISTICS_COLLECT_RPM_STATE_OPTION = "cl_statistics_rpm_enabled"
_CL_STATISTICS_DIR = '/var/lve'
_CL_STATISTICS_SEND_STATUS_FILE = os.path.join(_CL_STATISTICS_DIR, 'summary_status.json')
_CL_STATISTICS_LOCK_PATH = '/var/run/cloudlinux_summary.send.lock'
_CL_STATISTICS_LOCK_FILE = None
CL_PLUS_SENDER_FILE_PATH = '/usr/share/cloudlinux/cl_plus/clplus_sender.py'
# Path for virtualenv bin, if it's installed on CL8
ALT_PYTHON_VIRTUALENV_BIN = '/opt/alt/python37/bin/virtualenv'
class SummaryStatus(object):
"""
Status of both, collecting and sending statistics
If process still collects statistics -> IN_PROGRESS
If statistics collected and sent correctly -> SUCCESS
If any error during collecting or sending -> FAILED
"""
SUCCESS = 'success'
IN_PROGRESS = 'in_progress'
FAILED = 'failed'
def is_virtualenv_installed():
"""
Checks is virtualenv installed
CL8 uses another venv path, so this check is version-dependent
:return: True/False - installed or not
"""
return os.path.exists(ALT_PYTHON_VIRTUALENV_BIN)
def is_locked(lock_file):
"""
Check if file is locked by another process without acquiring lock.
IMPORTANT! This function should NOT be used to check lock acquired by the
same process that executes the is_locked() function. For example, when
process executes fcntl.lockf(LOCK_FILE), and then the same process executes
is_locked(LOCK_FILE), the is_locked(LOCK_FILE) call returns False.
Use is_locked() function to check lock acquired by another process only.
:param lock_file: file to check lock on
:type lock_file: file object or descriptor
"""
lock_data = struct.pack("hhllhh", fcntl.F_WRLCK, 0, 0, 0, 0, 0)
try:
lock_query = fcntl.fcntl(lock_file, fcntl.F_GETLK, lock_data)
lock_status = struct.unpack("hhllhh", lock_query)[0]
except (OSError, IOError):
# should never happen
return False
return lock_status != fcntl.F_UNLCK
def is_sending_process_running(acquire_lock=False):
"""
Check if processes collecting stats are running already (with --send option in command line)
:param acquire_lock: True = acquire lock when possible
:type acquire_lock: bool
:return bool: True = Processes are running
"""
global _CL_STATISTICS_LOCK_FILE
_CL_STATISTICS_LOCK_FILE = open(_CL_STATISTICS_LOCK_PATH, 'w')
if not acquire_lock:
return is_locked(_CL_STATISTICS_LOCK_FILE)
try:
fcntl.lockf(_CL_STATISTICS_LOCK_FILE, fcntl.LOCK_EX | fcntl.LOCK_NB)
except (OSError, IOError):
return True
return False
def _get_config():
"""
Retrieves ConfigParser object for /etc/sysconfig/cloudlinux file
:return:
"""
config = configparser.ConfigParser(interpolation=None,
strict=True)
config.optionxform = str # make config case sensitive
config.read(CL_CONFIG_FILE)
return config
def _write_config(config):
"""
write config to /etc/sysconfig/cloudlinux file
:param config: configParser object to write
return boolean: True|False
"""
try:
string_fp = io.StringIO()
config.write(string_fp)
content = string_fp.getvalue()
write_file_via_tempfile(content, CL_CONFIG_FILE, 0o644, prefix='cloudlinux_')
except (OSError, IOError):
return False
return True
def _get_config_value(parameter: str, default: bool = True) -> bool:
"""
Retrieves parameter's value from /etc/sysconfig/cloudlinux file, _CL_STATISTICS_SECTION section
"""
config = _get_config()
res = default
try:
res = config.getboolean(_CL_STATISTICS_SECTION, parameter)
except (configparser.NoSectionError, configparser.NoOptionError, ValueError):
# Treat absent/missing value as default
pass
return res
def _set_config_value(parameter: str, value: bool) -> None:
"""
Sets parameter's value to /etc/sysconfig/cloudlinux file, _CL_STATISTICS_SECTION section
"""
config = _get_config()
config.set(_CL_STATISTICS_SECTION, parameter, str(int(value)))
_write_config(config)
def is_statistic_enabled() -> bool:
"""
Retrieves statistic collection status from /etc/sysconfig/cloudlinux file
:return: True/False - enabled/disabled
"""
return _get_config_value(_CL_STATISTICS_COLLECT_STATE_OPTION)
def is_statistic_rpm_enabled() -> bool:
"""
Retrieves rpm statistic collection status from /etc/sysconfig/cloudlinux file
:return: True/False - enabled/disabled
"""
return _get_config_value(_CL_STATISTICS_COLLECT_RPM_STATE_OPTION)
def set_statistic_collection_enabled(is_enabled: bool) -> None:
"""
Set statistic collection status to /etc/sysconfig/cloudlinux file
:param is_enabled: True/False - enabled/disabled
"""
_set_config_value(_CL_STATISTICS_COLLECT_STATE_OPTION, is_enabled)
def set_statistic_rpm_collection_enabled(is_enabled: bool) -> None:
"""
Set rpm statistic collection status to /etc/sysconfig/cloudlinux file
:param is_enabled: True/False - enabled/disabled
"""
_set_config_value(_CL_STATISTICS_COLLECT_RPM_STATE_OPTION, is_enabled)
def write_statistics_send_status_to_file(status_dict):
"""
Writes statistics send status to file /var/lve/summary_status.json
:param status_dict: status dictionary for write to file
:return:
"""
try:
if not os.path.exists(_CL_STATISTICS_DIR):
mkdir_p(_CL_STATISTICS_DIR)
content = json.dumps(status_dict)
# Write to file readable only for root
write_file_via_tempfile(content, _CL_STATISTICS_SEND_STATUS_FILE, 0o600, prefix='cloudlinux_')
except (OSError, IOError):
pass
def get_statistics_send_status_from_file():
"""
Retrieves statistics send status from file /var/lve/summary_status.json
:return: Dictionary with last send status. None if any error
"""
status_dict = None
try:
with open(_CL_STATISTICS_SEND_STATUS_FILE) as f:
s_content = f.read()
status_dict = json.loads(s_content)
if status_dict['result'] == SummaryStatus.IN_PROGRESS \
and not is_sending_process_running():
# something went wrong during collection
status_dict['result'] = SummaryStatus.FAILED
status_dict['reason'] = 'Collecting statistics was failed. Error ' \
'report has been sent to developers and will be fixed soon'
except (OSError, IOError, ValueError, AttributeError, TypeError):
pass
return status_dict
def installed_interpreters_list(interpreter):
"""
Returns list of installed interpreters
:param interpreter: str - name of interpreter
:rtype: List of InterpreterSummary
"""
return [i for i in interpreter_versions_short_summary(interpreter) if i.installed]
def is_python_selector_installed():
"""
Checks that python selector is installed
Installed if:
- ea-apache24-mod-alt-passenger or alt-mod-passenger is installed
- alt-python-virtualenv is installed
:rtype: bool
"""
return is_clpassenger_active() and is_virtualenv_installed()
def is_ruby_selector_installed():
"""
Checks that ruby selector is installed
Installed if:
- ea-apache24-mod-alt-passenger or alt-mod-passenger is installed
- alt-python-virtualenv is installed
:rtype: bool
"""
return is_clpassenger_active() and is_virtualenv_installed()
def is_nodejs_selector_installed():
"""
Checks that nodejs selector is installed
Installed if:
- ea-apache24-mod-alt-passenger or alt-mod-passenger is installed
- At least one version is installed
:rtype: bool
"""
return is_clpassenger_active() and bool(installed_interpreters_list('nodejs'))
def is_php_selector_installed():
"""
Checks that php selector is installed
Installed if:
- CageFS is initialized
:rtype: bool
"""
return bool(check_cagefs_initialized())
def get_packages_with_lve_extensions():
"""
Gets packages with set lve limits via extension
"""
try:
result = WhmApiRequest('listpkgs').call()
except WhmApiError:
return []
lve_extensions_packages = [item['name'] for item in result['pkg']
if '_PACKAGE_EXTENSIONS' in item and item['_PACKAGE_EXTENSIONS'] == 'lve']
return lve_extensions_packages
def get_client_data_from_jwt_token(check_expiration=True) -> Optional[Dict[AnyStr, Union[AnyStr, bool]]]:
"""
Gets (if any) fields cl_plus and client_id from jwt token
:return: decoded jwt_token value, None if error
jwt_token: result of the successful decoding
"""
try:
with open(DEFAULT_JWT_ES_TOKEN_PATH, mode='rb') as file:
file_content = file.read().strip()
except (OSError, IOError):
return None
# JWT read success
try:
jwt_token = decode(file_content, algorithms=['HS256'],
options={'require_exp': True,
"verify_exp": check_expiration,
"verify_iss": True,
'verify_signature': False},
issuer='CloudLinux')
return jwt_token
# JWT format error
except exceptions.PyJWTError:
return None
def is_active_cloudlinux_license(token_data):
"""
Checks whether license ts is expired
"""
if not token_data:
return None
if not token_data.get('exp'):
return None
return int(time.time()) < token_data.get('exp')
def get_cl_plus_sender_status() -> Optional[AnyStr]:
"""
Retrieves data from status of cl_plus_sender service
:return: status of service, Optional[AnyStr]
- 'active'
- 'inactive'
"""
try:
result = process_is_running(CL_PLUS_SENDER_FILE_PATH, False)
except FileNotFoundError:
result = False
return 'active' if result else 'inactive'
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists