Mini Shell

Direktori : /usr/share/l.v.e-manager/utils/
Upload File :
Current File : //usr/share/l.v.e-manager/utils/libcloudlinux.py

# 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 print_function
from __future__ import division
from __future__ import absolute_import
import copy
import sys
import json
import argparse
import base64
import os
import subprocess
import re

from past.builtins import basestring, unicode  # noqa
from future.utils import iteritems
from clcommon.utils import silence_stdout_until_process_exit
from cllicense import CloudlinuxLicenseLib
from cpanel_api import get_cpanel_api_class

LVEMANAGER_PLUGIN_NAMES = {
    'python_selector': 'Python Selector',
    'nodejs_selector': 'Node.js Selector',
    'php_selector': 'PHP Selector',
    'resource_usage': 'Resource Usage'
}
PASSENGER_DEPEND_PLUGINS = ['python_selector', 'nodejs_selector']
DEFAULT_PLUGIN_NAME = 'LVE Manager'


def is_json(data):
    try:
        json.loads(data)
        return True
    except ValueError as error:
        return False


class CloudlinuxCliBase(object):

    request_data = {}
    result = None
    available_request_params = [
        'owner', 'command', 'method', 'params', 'user_info', 'mockJson', 'attachments', 'plugin_name'
    ]
    NOT_FLAGGED_PARAMS = ['config-files', 'content', 'passenger-log-file', 'ignore-list']
    license_is_checked = False
    current_plugin_name = ''
    licence = CloudlinuxLicenseLib()

    def __init__(self):
        self.user_info = {}
        self.parsing_request_data()
        self.check_xss()
        self.drop_permission()
        self.command_methods = {
            'spa-ping': self.spa_ping,
            'cloudlinux-top': self.cl_top,
            'cloudlinux-selector': self.cl_selector,
            'cloudlinux-statistics': self.cl_statistics,
            'cloudlinux-charts': self.cl_chart,
            'cloudlinux-quota': self.cl_quota,
            'cpanel-api': self.cpanel_api,
            'cloudlinux-xray-user-manager': self.cl_xray_user_manager
        }

    def check_xss(self):
        for key in self.request_data.keys():
            if key not in self.available_request_params:
                self.exit_with_error('BAD REQUEST 1:' + key)

        for name, val in iteritems(self.request_data):
            if isinstance(val, dict): # if post key is "params"
                for key, inner_value in iteritems(val):
                    self.check_param_key(key)
                    if self.request_data['command'] == 'cloudlinux-packages' \
                            and name == 'params' \
                            and key == 'package':
                        self.request_data[name][key] = self.escape_param_value(inner_value)
                    elif self.request_data['command'] == 'cloudlinux-support':
                        pass
                    elif self.request_data['command'] == 'cloudlinux-selector' \
                            and name == 'params' \
                            and key == 'options':
                        pass
                    elif self.request_data['command'] == 'lvectl' \
                         and name == 'params' \
                         and key == 'stdin':
                        pass
                    elif self.request_data['command'] == 'cloudlinux-selector' \
                             and name == 'params' \
                             and key == 'env-vars':
                        pass
                    elif self.request_data['command'] == 'cloudlinux-xray-manager' \
                             and name == 'params' \
                             and key == 'url':
                        pass
                    elif self.request_data['command'] == 'cloudlinux-xray-user-manager' \
                             and name == 'params' \
                             and key == 'url':
                        pass
                    else:
                        self.check_param_value(inner_value)
            else:
                self.check_param_value(val)

    def check_param_key(self, key):
        if not re.search('^[\w\-]+$', key):
            self.exit_with_error('BAD REQUEST 2')

    def check_param_value(self, val):
        if isinstance(val, basestring):
            if re.search('[`\|\$;&\n]', val, re.M):
                self.exit_with_error('BAD REQUEST 3')

    def escape_param_value(self, val):
        chars = "\\\"\'"
        for c in chars:
            val = val.replace(c, "\\" + c)
        return val

    def main(self):
        command = self.request_data['command']
        endpoint = self.command_methods.get(command)
        if endpoint:
            if not self.license_is_checked and command != 'cloudlinux-license':
                self.check_license()
            if 'mockJson' in self.request_data:
                self.spa_mock(self.request_data['mockJson'])
            endpoint()
        else:
            if command:
                self.exit_with_error("No such module " + command)
            else:
                self.exit_with_error("Command not defined")

    def parsing_request_data(self):
        """
        parsing entry data, encode it from base64 to dictionary
        :return:
        """
        parser = argparse.ArgumentParser()
        parser.add_argument('--data')
        try:
            arguments = parser.parse_args()
        except:
            self.exit_with_error("Unknown param in request")

        if arguments.data:
            data_in_base64 = arguments.data
            data_in_json = base64.b64decode(data_in_base64).decode("utf-8")
            try:
                self.request_data = json.loads(data_in_json)
            except ValueError:
                self.exit_with_error("Need json-array")
            self.user_info = self.request_data.get('user_info') or {}
            self.define_current_plugin()
        else:
            self.exit_with_error("No --data param in request")

    def cl_top(self):
        # This imports from other package (cagefs), so we turn off pylint import checker for this line
        from lvestats.lib.info.cloudlinux_top import CloudLinuxTop          #pylint: disable=E0401
        import lvestats.lib.config as config                                #pylint: disable=E0401

        list_to_request = self.prepair_params_for_command()
        result = ''
        try:
            result, exitcode = CloudLinuxTop(config.read_config()).main(*list_to_request)
        except config.ConfigError as ce:
            ce.log_and_exit()
            self.exit_with_error(str(ce))

        if self.request_data.get('owner') == 'user':
            json_result = {}
            try:
                json_result = json.loads(result)
            except:
                self.exit_with_error(result)

            if json_result.get('result') != 'success':
                self.exit_with_error(json_result.get('result'), json_result.get('context'), ignore_errors=True)
        print(result)
        silence_stdout_until_process_exit()
        sys.exit(exitcode)

    def cl_quota(self):
        list_to_request = self.prepair_params_for_command()
        result = self.run_util('cl-quota', *list_to_request, ignore_errors=True)
        print(result)

    def cl_xray_user_manager(self):
        list_to_request = self.prepair_params_for_command()
        list_to_request.remove("--json")
        result = self.run_util('/opt/alt/php-xray/cloudlinux-xray-user-manager', *list_to_request, ignore_errors=False)
        print(result)

    def cpanel_api(self):
        owner = self.request_data.get('owner')
        method = self.request_data.pop('method')
        list_to_request = self.prepair_params_for_command(with_json=False, add_dash=False)
        cpanel_api = get_cpanel_api_class(owner)
        self.exit_with_success({'data': cpanel_api.run(method, list_to_request)})

    def cl_chart(self):
        list_to_request = self.prepair_params_for_command()
        try:
            list_to_request.remove("--json")
        except ValueError:
            pass
        for param in list_to_request:
            if  param.startswith('--output'):
                self.exit_with_error('BAD REQUEST 2')
        list_to_request.insert(0, '/usr/sbin/lvechart')
        response = subprocess.check_output(list_to_request, shell=False, text=True)
        print(json.dumps({"result": "success", "chart": response}))
        silence_stdout_until_process_exit()
        sys.exit(0)

    def drop_permission(self):
        """
        Drop permission to users, if owner of script is user
        :return:
        """
        data = self.request_data
        if data['owner'] in ['reseller', 'user'] and\
                ('lve-id' not in self.user_info or
                 'username' not in self.user_info):
            self.exit_with_error("User id does not specified")

    def prepair_params_for_command(self, with_json=True, escaped_strings=False, add_dash=True):
        """
        Method that converts given dict of parameters
        into list of strings that should be passed
        as arguments command-line application
        :param with_json: add --json argument
        :param escaped_strings: ONLY FOR BACKWARDS COMPATIBILITY!
                                SHOULD BE False FOR ALL NEW METHODS!
        :param add_dash: if we need to add dashes to params
        :return:
        """
        value_template = "--{0}={1}" if add_dash else "{0}={1}"
        data = self.request_data
        list_to_request = []
        if "method" in data:
            for method in data["method"].split(' '):
                list_to_request.append(method)

        if "params" not in data:
            data['params'] = {}
        if "json" not in data['params'] and with_json:
            data['params']['json'] = ''

        for param, value in iteritems(data['params']):
            if param != 'additional-params':
                # TODO: looks like we can remove option escaped_strings
                # and always use value.encode('utf-8') here
                # same goal may be reached using utils.byteify(json.loads(...))
                # but to do that, we need some tests covering unicode params
                # (especially for cloudlinux-packages)
                # unfortunately, we do not have one ;(
                # THIS IS NEEDED ONLY FOR CL-PACKAGES UTILITY
                if value and escaped_strings is True:
                    list_to_request.append(value_template.format(param, value.encode('unicode-escape').decode()))
                elif (value or param in self.NOT_FLAGGED_PARAMS) and escaped_strings is False:
                    list_to_request.append(value_template.format(param, value))
                else:
                    list_to_request.append("--{0}".format(param))
        if self.request_data['owner'] == 'reseller':
            list_to_request.append('--for-reseller={0}'.format(self.user_info['username']))

        if 'additional-params' in data['params'] \
                and data['params']['additional-params'] != '':
            list_to_request.append("--")
            for param in data['params']['additional-params'].split():
                list_to_request.append("{0}".format(param))

        return list_to_request

    def update_license(self):
        # Register by broken license
        with open(os.devnull, 'w') as devnull:
            subprocess.call(['/usr/sbin/clnreg_ks', '--force'], stderr=devnull, stdout=devnull, shell=False)
            subprocess.call(['/usr/bin/cldetect', '--update-license'], stderr=devnull, stdout=devnull, shell=False)

        self.check_license(False)

    def check_license(self, with_recovery=True):
        if not self.kernel_is_supported():
            if self.request_data['owner'] in ['reseller']:
                self.exit_with_error(
                    code=503,
                    error_id='ERROR.not_available_plugin',
                    context={'pluginName': LVEMANAGER_PLUGIN_NAMES.get(self.current_plugin_name, DEFAULT_PLUGIN_NAME)},
                    icon='disabled')
            elif self.request_data['owner'] in ['admin']:
                self.exit_with_error('Kernel is not supported')
        if not self.licence.get_license_status():
            if self.request_data['owner'] in ['reseller', 'user']:
                interpreter = 'nodejs'
                if self.request_data.get('params') \
                        and self.request_data['params'].get('interpreter'):
                    interpreter = self.request_data['params']['interpreter']
                pluginNames = {
                    'reseller': 'LVE Manager',
                    'user': {'python': 'Python Selector', 'nodejs':'Node.js Selector'}
                        .get(interpreter, 'Node.js Selector')
                }
                self.exit_with_error(
                    code=503,
                    error_id='ERROR.not_available_plugin',
                    context={'pluginName': LVEMANAGER_PLUGIN_NAMES.get(self.current_plugin_name, DEFAULT_PLUGIN_NAME)},
                    icon='disabled')
            else:
                if with_recovery:
                    self.update_license()
                else:
                    self.exit_with_error('License is not valid')
        else:
            self.license_is_checked = True

    def exit_with_error(self, error_string='', context=None,
                        code=None, error_id=None, icon=None, ignore_errors=False):
        result = {"result": error_string}
        if context:
            result['context'] = context
        if code:
            result['code'] = code
        if error_id:
            result['error_id'] = error_id
        if icon:
            result['icon'] = icon
        if ignore_errors:
            result['ignore'] = ignore_errors
        print(json.dumps(result))
        sys.exit(1)

    def exit_with_success(self, response=None):
        data = copy.deepcopy(response) if response else {}
        data['result'] = 'success'
        print(json.dumps(data))
        sys.exit(0)

    def cl_statistics(self):
        # This imports from other package (cagefs), so we turn off pylint import checker for this line
        from lvestats.lib.cloudlinux_statistics import main             #pylint: disable=E0401
        import lvestats.lib.config as config                            #pylint: disable=E0401
        from lvestats.lib.dbengine import make_db_engine                #pylint: disable=E0401

        list_to_request = self.prepair_params_for_command()
        try:
            cnf = config.read_config()
            dbengine = make_db_engine(cnf)
            main(dbengine, argv_=list_to_request,server_id=cnf.get('server_id', 'localhost'))
            silence_stdout_until_process_exit()
            sys.exit(0)
        except config.ConfigError as ce:
            ce.log_and_exit()
            self.exit_with_error(ce)

    def spa_mock(self, file):
        file_path = '/usr/share/l.v.e-manager/spa/src/jsons/%s.json' % (file)
        # check if passed file param doesn't use relative path. E.g.: '../../file'
        if os.path.realpath(file_path) != file_path:
            self.exit_with_error('BAD REQUEST 3')
        with open(file_path, 'r') as f:
            print(f.read())
        sys.exit(0)

    def get_lve_version(self):
        try:
            ver = subprocess.check_output(
                'cat /proc/lve/list | grep -Po \'^\d{1,2}:\'',
                shell=True, text=True
            ).strip()
            return int(ver[:-1])
        except:
            return 0

    def get_cloudlinux_version(self):
        return subprocess.check_output(
            'uname -r | grep -Po \'el\d\w?\'',
            shell=True, text=True
        ).strip()

    # Common methods

    def spa_ping(self):
        self.exit_with_success()

    def cl_selector(self):
        try:
            from clselector.cl_selector import CloudlinuxSelector
        except:
            self.exit_with_error('Module unavailable')
        if self.user_info.get('username') and 'interpreter' in self.request_data['params']\
                and self.request_data['params']['interpreter'] == 'php':
            self.check_php_selector_user_availablility()

        list_to_request = self.prepair_params_for_command()
        cll = CloudlinuxSelector()
        cll.run(list_to_request)

    def check_php_selector_user_availablility(self):
        """
        Additional check only for php selector
        :return:
        """
        try:
            LIBDIR = '/usr/share/cagefs'
            sys.path.append(LIBDIR)
            import cagefsctl
            if not cagefsctl.cagefs_is_enabled or \
                    not cagefsctl.is_user_enabled(self.user_info['username']):
                raise RuntimeError('Cagefs is disabled or missing')

        except (ImportError, RuntimeError):
            self.exit_with_error(
                code=503,
                error_id='ERROR.cagefsDisabled',
            )
        from clselect.clselectexcept import BaseClSelectException
        try:
            from clselect import ClSelect
            ClSelect.check_multiphp_system_default_version()
        except (BaseClSelectException):
            self.exit_with_error(
                code=503,
                error_id='ERROR.systemVersionAltPHP',
            )

    def define_current_plugin(self):
        self.current_plugin_name = self.request_data.get('plugin_name')

    def is_error_response_default(self, json_result):
        return json_result.get('result') != 'success' and json_result.get('success') != 1


    def run_util(self, name, *args, **kwargs):
        command = [name] + list(args)
        error_checker = kwargs.get('error_checker', self.is_error_response_default)
        try:
            p = subprocess.Popen(
                command,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            (result, err) = p.communicate(kwargs.pop('stdin', None))
            is_error = p.returncode != 0 or not is_json(result)
            if not is_error:
                json_result = json.loads(result)

                is_error = error_checker(json_result)
            if is_error:
                result = result + err
                if is_json(result):
                    json_result = json.loads(result)
                    if json_result.get('message'):
                        json_result['result'] = json_result.pop('message')
                        result = json.dumps(json_result)
                if kwargs.get("ignore_errors", False):
                    # Check new result concatenated with error
                    if is_json(result):
                        result = json.loads(result)
                        result['ignore'] = True
                        result = json.dumps(result)
                    else:
                        result = self.ignored_error_message(result)
                print(result)
                exit(1)
            return result
        except Exception as e:
            self.exit_with_error("Can't run %(command)s", context={'command': ' '.join(command)}, ignore_errors=True)

    def ignored_error_message(self, message):
        return json.dumps({
            "result": message,
            "ignore": True
        })

    def kernel_is_supported(self):
        try:
            f = open('/proc/lve/list', 'r')
            line = f.readline()
            f.close()
            return bool(line)
        except IOError:
            return False

Zerion Mini Shell 1.0