%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /usr/share/lve/modlscapi/user/
Upload File :
Create Path :
Current File : //usr/share/lve/modlscapi/user/lsphpchecker.py

#!/usr/bin/python3

# Copyright (c) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2018 All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#coding:utf-8
import glob
import os
import pwd
import sys
from subprocess import Popen, PIPE


CHECK = {
    "gt": lambda x, y: x > y,
    "gte": lambda x, y: x >= y,
    "lt": lambda x, y: x < y,
    "lte": lambda x, y: x <= y,
    "eq": lambda x, y: x == y,
}
CHECK_KEYS = {"gt": "more", "gte": "more or equal", "lt": "less",
              "lte": "less or equal", "eq": "equal"}
BYTES_CONVERSION_TABLE = {'M': 1, 'G': 1024, 'T': 1024 * 1024}

quick = 0


def log_error(msg):
    """
    Wrapper for logging errors.
    Simple logging to stderr.
    """
    print(msg, file=sys.stderr)


class Cast:
    """
    Class with functions for cast to any type
    """
    @staticmethod
    def to_bool(_v):
        return {"Off": 0, "On": 1}.get(_v, Cast.to_number(_v))

    @staticmethod
    def to_mb(_v):
        if not len(_v):
            return None

        if _v[-1].isdigit():
            _v = "%sM" % _v

        _num = Cast.to_number(_v[:-1])
        if _num is None:
            return None

        return _num * BYTES_CONVERSION_TABLE.get(_v[-1].upper(), 1)

    @staticmethod
    def to_number(_v):
        try:
            return int(_v)
        except (ValueError, TypeError):
            return None


class PhpChecker:
    """
    docstring for PhpChecker
    """
    SAMPLE_CONFIG_PATH = "/usr/share/lve/modlscapi/user/lsphpchecker.ini"
    __php_binary = None
    __sample = None

    def __init__(self):
        """
        Initialize php versions list
        """
        global quick
        super(PhpChecker, self).__init__()
        php_list = glob.glob("/usr/local/bin/lsphp")
        if quick != 1:
            php_list.extend(glob.glob("/opt/alt/php*/usr/bin/lsphp"))
            php_list.extend(glob.glob("/opt/cpanel/ea-php*/root/usr/bin/lsphp"))
            php_list.extend(glob.glob("/usr/bin/lsphp"))
            php_list = sorted(php_list)
        self.__php_binary = php_list
        
        self._load_sample_options()

    def check_user(self, user):
        """
        Check configurations for user
        """
        for php_path in self.__php_binary:
            if os.path.exists(php_path):
                check_result = self._check_php_options(php_path, user)
                if check_result:
                    for message in check_result:
                        print("%s: %s: %s" % (user, php_path, message))

    def _check_php_options(self, php_path, user):
        """
        Load and check specified php version options
        @param `php_path` str: path to php binary
        @param `user` str: username
        """
        warnings = []
        options = self._load_php_options(php_path, user)
        modules = self._detect_danger_modules(php_path, user)

        warnings += self._check_options(options, "apc")
        warnings += self._check_options(options, "suhosin")
        #if "zend_guard_loader" in modules:
        #    warnings.append("Unstable module Zend Guard detected. Please disable module")

        #if "ioncube_loader" in modules:
        #    warnings.append("Unstable module ionCube detected. Please disable module")

        return warnings

    def _load_php_options(self, php_path, user):
        """
        Load php options from CLI phpinfo output
        """
        #print "%s: %s" % (user, php_path)
        if user == "":
            p = Popen([php_path,  "-i"], stdout=PIPE,
                  stderr=PIPE)
        else:
            p = Popen(["/bin/su", user, "-c", "[ ! -f %s ] || %s -i" % (php_path, php_path)], stdout=PIPE,
                  stderr=PIPE)
        out, err = p.communicate()

        #if p.returncode != 0:
        #    log_error(err)

        options = {}
        option_value = False
        for line in out.decode().split("\n"):
            if "Directive => Local Value => Master Value" == line:
                option_value = True
                continue

            if option_value:
                if not line:
                    option_value = False
                    continue

                values = line.split(" => ")
                if len(values) < 3:
                    log_error("Invalid option line - %s" % line)
                    continue

                if "." not in values[0]:
                    module = "__common__"
                    key = values[0]
                else:
                    module, key = values[0].split(".", 1)

                if module not in options:
                    options[module] = {}

                options[module][values[0]] = values[1]

        return options

    def _detect_danger_modules(self, php_path, user):
        """
        Detect unstable and potential danger php modules from php version output
        """
        if user == "":
            p = Popen([php_path, "-i"], stdout=PIPE,
                  stderr=PIPE)
        else:
            p = Popen(["/bin/su", user, "-c", "[ ! -f %s ] || %s -i" % (php_path, php_path)], stdout=PIPE,
                  stderr=PIPE)
        out, err = p.communicate()

        #if p.returncode != 0:
        #    log_error(err)

        modules = {}
        for line in out.decode().split("\n"):
            line = line.strip()
            if line.startswith("with the ionCube PHP Loader"):
                modules["ioncube_loader"] = True
            elif line.startswith("with Zend Guard Loader"):
                modules["zend_guard_loader"] = True

        return modules

    def _check_options(self, config, module):
        """
        Check php options based on sample config
        """
        if not config or not isinstance(config, dict) or \
                not isinstance(module, str) or \
                not isinstance(config.get(module), dict):
            return []

        if module not in self.__sample:
            return []

        result = []
        options = config[module]
        for key, check_info in self.__sample[module].items():
            if key in options and not self._validate_value(options[key], check_info):
                result.append("%s must be %s %s (current value: %s) (no value means Off)" %
                    (key, CHECK_KEYS.get(check_info["check"], ""),
                        check_info["value"], options[key]))

        if len(result):
            result.insert(0, "[%s]" % module)
            result.insert(0, "change %s options to default" % module)

        return result

    def _load_sample_options(self):
        """
        Load sample options
        """
        self.__sample = {}
        try:
            f = open(self.SAMPLE_CONFIG_PATH, "r")
            for line in f:
                line = line.strip()
                value_type = "number"
                check_type = "gte"
                try:
                    key, value = line.split("=", 1)
                    if "," in key:
                        # get additional information about value type and check method
                        type_info, key = key.split(",")
                        value_type, check_type = type_info.split(":")
                    module = key.split(".", 1)[0]
                except (ValueError, IndexError):
                    print("Invalid sample string %s" % line)
                    continue
    
                if module not in self.__sample:
                    self.__sample[module] = {}
                self.__sample[module][key.strip()] = {"value": value.strip(),
                    "type": value_type, "check": check_type}
            f.close()
        except (OSError, IOError):
            log_error("Error read %s" % self.SAMPLE_CONFIG_PATH)

    def _validate_value(self, value, rule):
        """
        Validate option value.
        @param value_type `str`|default:"number" : value type
        @param value1 `str|int`: value1 for compare
        @param value2 `str|int`: value2 for compare
        @return int : -1, 0, 1
        """
        if not isinstance(rule, dict):
            return False

        value_type = rule.get("type", "number")
        check_type = rule.get("check", "gte")
        check_value = rule["value"]

        cast_func = "to_%s" % value_type
        if hasattr(Cast, cast_func):
            value = getattr(Cast, cast_func)(value)
            check_value = getattr(Cast, cast_func)(check_value)

        if check_type not in CHECK:
            return False

        return CHECK[check_type](value, check_value)


def load_min_max_uid():
    """
    Load min and max UID from /etc/login.defs config
    """
    min_uid = max_uid = None
    try:
        f = open("/etc/login.defs", "r")
        for line in f:
            if not line.startswith("UID_MIN") and not line.startswith("UID_MAX"):
                continue

            data = line.split()
            if not data:
                continue

            try:
                if data[0] == "UID_MIN":
                    min_uid = int(data[1])
                elif data[0] == "UID_MAX":
                    max_uid = int(data[1])
            except (ValueError, IndexError):
                log_error("Invalid UID_MIN/UID_MAX values")
                break

        f.close()
    except (IOError, OSError):
        print("Can`t read UID_MIN and UID_MAX from /etc/login.defs file", file=sys.stderr)

    return min_uid, max_uid


def main(users_list):
    """
    Run check
    """
    global quick
    
    if len(users_list)>0 and users_list[0] == "help":
        print("%s [help] [quick] [users list...]" % sys.argv[0])
        print("   help       - show this help")
        print("   fast       - check all lsphp without switching to user")
        print("   medium     - check all users but only /usr/local/bin/lsphp config")
        print("   users list - list of needed users or empty. i this case users list will be taken from passwd")
        return
    if len(users_list)>0 and (users_list[0] == "fast" or users_list[0] == "medium"):
        if users_list[0] == "fast":
            quick = 2
        else:
            quick = 1
        users_list = users_list[1:]
    checker = PhpChecker()
    if quick == 2:
        checker.check_user("")
    elif users_list:
        for username in users_list:
            try:
                user = pwd.getpwnam(username)
                checker.check_user(username)
            except KeyError:
                log_error("User %s doesn`t exists" % username)
    else:
        UID_MIN, UID_MAX = load_min_max_uid()
        # get username list
        for user in pwd.getpwall():
            if  user.pw_uid >= UID_MIN and user.pw_uid <= UID_MAX:
                # for each user run check_user
                checker.check_user(user.pw_name)


if "__main__" == __name__:
    main(sys.argv[1:])

Zerion Mini Shell 1.0