VaKeR CYBER ARMY
Logo of a company Server : Apache
System : Linux host44.registrar-servers.com 4.18.0-513.18.1.lve.2.el8.x86_64 #1 SMP Sat Mar 30 15:36:11 UTC 2024 x86_64
User : vapecompany ( 2719)
PHP Version : 7.4.33
Disable Function : NONE
Directory :  /opt/cloudlinux/venv/lib/python3.11/site-packages/clselect/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //opt/cloudlinux/venv/lib/python3.11/site-packages/clselect/clselectctlphp.py
#!/opt/cloudlinux/venv/bin/python3 -bb
# -*- 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 absolute_import
from __future__ import division

import subprocess
import sys
import getopt
import os
import clcommon
import traceback
import simplejson as json
import shutil
import pwd

from builtins import map
from .clselect import ClSelect
from .clextselect import ClExtSelect, depend_modules_dict
from .cluserselect import ClUserSelect
from .cluserextselect import ClUserExtSelect
from .cluseroptselect import ClUserOptSelect
from .clselectprint import clprint
from clcommon import ClPwd
from clcommon.const import Feature
from clcommon.cpapi import is_panel_feature_supported
from clcommon.sysctl import SysCtlConf, SYSCTL_CL_CONF_FILE
from clcommon.utils import run_command, ExternalProgramFailed
from clcommon.lib.cledition import is_ubuntu
from .utils import in_cagefs, make_symlink
from .clselectexcept import ClSelectExcept, BaseClSelectException


# This is the oldest version with all bad things
API_0 = 0

# This version replaces hardcoded interpreter name in utility output with more
# generic key names that will be correctly understood by cloudlinux-selector
API_1 = 1

# Path to cagefs command
CAGEFSCTL_COMMAND = '/usr/sbin/cagefsctl'


def usage():
    print(' -v | --version                   : Specify alternative version')
    print(' -u | --user                      : Username')
    print(' -l | --list                      : List alternatives for interpreter')
    print(' -G | --list-extensions           : List global set of extensions for alternative')
    print(' -S | --summary                   : List summary of alternatives')
    print(' -s | --user-summary              : List user summary of alternatives')
    print(' -C | --current                   : Show currently selected alternative')
    print(' -c | --user-current              : Show currently selected alternative for a user')
    print(' -B | --set-current               : Set alternative as global default')
    print(' -b | --set-user-current          : Set alternative as user default')
    print(' -Y | --enable-alternative        : Enable alternative globally')
    print(' -N | --disable-alternative       : Disable alternative globally')
    print(' -E | --enable-extensions         : Enable comma-separated list of extensions globally for a version')
    print(' -D | --disable-extensions        : Disable comma-separated list of extensions globally for a version')
    print(' -R | --replace-extensions        : Replace extensions with comma-separated list of extensions ')
    print('                                    for a version globally')
    print(' -e | --enable-user-extensions    : Enable comma-separated list of extensions for a user')
    print(' -d | --disable-user-extensions   : Disable comma-separated list of extensions for a user')
    print(' -r | --replace-user-extensions   : Replace user extensions with comma-separated list of extensions')
    print(' -t | --reset-user-extensions     : Replace user extensions with version default extensions')
    print(' -g | --list-user-extensions      : List enabled extensions for a user. With key --all shows all extensions')
    print(' -a | --all                       : Show all extensions')
    print(' -p | --print-summary             : If specified along with setting an alternative prints user summary')
    print(' -V | --show-native-version       : Shows native version while showing summary or selected version')
    print(' -L | --list-users                : List users who use a specified alternative')
    print(' -T | --change-to-version         : Changes to a specified version all users who have a certain version')
    print(' -k | --add-options               : Add comma-separated list options for a user')
    print(' -m | --replace-options           : Replace user options with comma-separated list of options')
    print(' -x | --delete-options            : Delete comma-separated list options for a user')
    print(' -Q | --base64                    : Expects data as comma-separated base64-encoded string')
    print(' -q | --quiet                     : Suppress errors messages for wrong input')
    print(' -P | --print-options             : Prints user options. By default prints as plain text')
    print(' --print-options-safe             : Prints user options. By default prints as plain text (safe strings)')
    print(' --apply-symlinks-rules           : Recreate symlinks to php extensions for all users ')
    print('                                    based on /etc/cl.selector/symlinks.rules file')
    print(' --exclude-pid-list               : Specify list of PIDs of processes that should not be signaled by SIGHUP')
    print(' -j | --json                      : Print data as JSON')
    print(' -w | --csv                       : Print data as CSV')
    print(' -W | --perl                      : Print data as perl structure')
    print(' --api-version                    : Integer, representing specific api version to use. ')
    print('                                    Defaults to {}'.format(API_0))
    print(' -z | --reset-options             : Deletes all user custom options. Range can be narrowed with user ')
    print('                                    or version options')
    print(' --update-backup                  : write settings to backup')
    print(' --apply-global-php-ini           : use with 0, 1 or 2 arguments from the list: error_log, date.timezone')
    print('                                    without arguments applies all global php options including two above')
    print(' --setup-without-cagefs           : setup PHP Selector without CageFS')
    print(' --revert-to-cagefs               : revert to default setup of PHP Selector (with CageFS)')
    print(' --for-all-users                  : applies specified action for all users in CageFS. ')
    print('                                    Available only for enable/disable user extensions')


def print_error_and_exit(message, prefix=None):
    """
    Prints to stderr
    @param message: string
    """
    fmt = "%s\n"
    if prefix:
        fmt = "%s:%s\n" % (prefix, '%s')
    sys.stderr.write(fmt % message)
    sys.exit(1)


def check_args_presence():
    """
    Checks presence of command line arguments
    and exits with usage info if missing
    """
    if len(sys.argv) == 1:
        print_error_and_exit(
            "Command line arguments expected. "
            "For help use '-h' or '--help' options")


def get_name_modifier(version):
    """
    """
    BASE_ALT_DIR = '/opt/alt/php'
    ver = version.replace('.','')
    name_modifier_file = BASE_ALT_DIR + ver + '/name_modifier'
    name_modifier = ''
    if os.path.isfile(name_modifier_file):
        try:
            name_modifier = open(name_modifier_file,'r').readline().strip()
        except (OSError, IOError):
            return ''
    return name_modifier


def letter_to_status(letter):
    if letter == '-':
        return 'disabled'
    elif letter == 'e':
        return 'enabled'


def format_summary(data, format='text', api_version=API_0):
    if api_version == API_0:
        available_versions_key = 'PHPConfiguration'
        default_version_key = 'defaultPHPversion'
    else:
        available_versions_key = 'available_versions'
        default_version_key = 'default_version'

    states = ['e', 'd', 's']
    text_lines = []
    json_dict = {
            available_versions_key: [],
    }
    for alt in data:
        row_length = len(alt[1])
        fmt = ' '.join(['%s'] * (row_length+1))
        row_data = [alt[0]]
        row_data.extend(list(map(
            (lambda i: ((alt[1][i] and states[i]) or '-')),
            range(row_length))))  # pylint: disable=range-builtin-not-iterating
        name_modifier = get_name_modifier(alt[0])
        if name_modifier != "":
            fmt = ' '.join(['%s'] * (row_length+2))
            row_data.append(name_modifier)
        if format == 'text':
            text_lines.append(fmt % tuple(row_data))
        if 'd' in row_data:
            json_dict[default_version_key] = row_data[0]
        json_dict[available_versions_key].append({
            'version': row_data[0],
            'status': letter_to_status(row_data[1]),
            'name_modifier': name_modifier,
        })
    if format == 'json':
        return json.dumps(json_dict)
    elif format == 'text':
        return '\n'.join(text_lines)


def print_summary(data, format='text', api_version=API_0):
    """
    Prints alternatives summary
    """
    data = format_summary(data, format, api_version)
    print(data)

def check_params(config, param_list):
    """
    Check that config has param_list and this params not None
    """
    for param in param_list:
        if param not in config or config[param] == None:
            print_error_and_exit("Error: %s must be specified" % param)


def ext_letter_to_status(letter):
    if letter == '~':
        return 'build-in'
    elif letter == '+':
        return 'enabled'
    else:
        return 'disabled'


def fill_descriptions(tmp_list):
    descr_file = '/etc/cl.selector.conf.d/phpextdesc.txt' if in_cagefs() else '/etc/cl.selector/phpextdesc.txt'
    with open(descr_file) as f:
        desct_content = f.readlines()
    full_desct_dict = {}
    for line in desct_content:
        line_parts = line.split('=')
        full_desct_dict[line_parts[0]] = line_parts[1].strip()
    for item in tmp_list:
        try:
            item['description'] = full_desct_dict[item['name']]
        except KeyError: # skip extention without description
            pass
    return tmp_list


def print_json_status_ok():
    result_dict = {'status': 'ok'}
    print(json.dumps(result_dict))


def get_cpanel_user():
    """
    Return user (name of cpanel account) for PHP Selector without CageFS feature
    """
    if os.path.isfile(ClSelect.USER_CONF):
        with open(ClSelect.USER_CONF, 'r') as f:
            return f.read().strip()
    for user in ClPwd().get_user_dict():
        if os.path.exists('/var/cpanel/users/'+user):
            return user
    return None


def set_cpanel_user(user):
    """
    Set user (name of cpanel account) for PHP Selector without CageFS feature
    """
    with open(ClSelect.USER_CONF, 'w') as f:
        f.write(user)
    os.chmod(ClSelect.USER_CONF, 0o644)


def switch_linksafe(enable=False):
    if not is_panel_feature_supported(Feature.LVE):
        return
    new_conf = '/etc/sysctl.d/cloudlinux-linksafe.conf'
    if os.path.isfile(new_conf):
        conf = new_conf
    else:
        conf = SYSCTL_CL_CONF_FILE
    sysctl_cfg = SysCtlConf(config_file=conf)
    sysctl_cfg.set('fs.protected_symlinks_create', str(int(enable)))
    sysctl_cfg.set('fs.protected_hardlinks_create', str(int(enable)))
    if subprocess.call('sysctl --system &>/dev/null', shell=True, executable='/bin/bash') != 0:
        print('Error while executing: sysctl --system')
        sys.exit(1)


def modify_search_path_in_bashrc(user, homedir, obj=None, add=True):
    """
    Add path to PATH variable in ~/.bashrc
    :param user: name of user
    :type user: string
    :param homedir: path to home directory
    :type homedir: string
    :param obj: instance of ClUserSelect class
    :type obj: instance of ClUserSelect class
    :param add: add path to .bashrc when True, remove otherwise
    :type obj: bool
    """
    if obj is None:
        obj = ClUserSelect('php')
    cur_user = obj._change_uid(user)
    # On Ubuntu we are using ~/.profile
    # On CL - ~/.bashrc
    if is_ubuntu():
        bashrc = homedir + '/.profile'
    else:
        bashrc = homedir + '/.bashrc'
    line = 'PATH=$HOME/'+ClUserSelect.SELECTOR2_DIR+':$HOME/.cl.selector:$PATH'
    with open(bashrc, 'r') as f:
        found = line+'\n' in f
    if add:
        if not found:
            with open(bashrc, 'a') as f:
                f.write('\n'+line+'\n')
    else:
        if found:
            clcommon.utils.delete_line_from_file(bashrc, line)
    obj._restore_uid(cur_user)


def restore_settings_from_backup(user, homedir, uid, alt_dirs, obj=None):
    """
    Restore (apply) settings for PHP Selector from backup
    :param user: name of user
    :type user: string
    :param homedir: path to user's home directory
    :type homedir: string
    :param uid: user's uid
    :type uid: int
    :param alt_dirs: list of alt-php directories like ['php51', 'php52']
    :type alt_dirs: list
    :param obj: instance of ClUserSelect class
    :type obj: ClUserSelect object
    """
    def cleanup():
        # Delete unneeded files
        shutil.rmtree(os.path.join('/var/cagefs', str(uid)[-2:]), True)
        shutil.rmtree('/usr/share/cagefs/etc', True)
        shutil.rmtree('/usr/share/cagefs/etc.new', True)

    if obj is None:
        obj = ClUserSelect('php')
    base_dest_path = homedir + '/.cl.selector'
    cleanup()
    # Generate alt_php.ini for all versions using cagefsctl
    if subprocess.call('/usr/sbin/cagefsctl --silent --force-update-etc ' + user,
                      shell=True, executable='/bin/bash') != 0:
        sys.exit(1)
    cur_user = obj._change_uid(user)
    # Copy generated alt_php.ini files to new location in user's home directory
    base_src_path = os.path.join('/var/cagefs', str(uid)[-2:], user, 'etc', 'cl.php.d')
    for alt_dir in alt_dirs:
        src_path = base_src_path + '/alt-' + alt_dir + '/alt_php.ini'
        dest_path = base_dest_path + '/alt_' + alt_dir + '.ini'
        shutil.copy(src_path, dest_path)
    # Select php version from backup (or default when backup does not exist)
    obj.set_version_from_backup(user)
    shutil.rmtree(homedir+'/.cagefs', True)
    obj._restore_uid(cur_user)
    cleanup()


def disable_cagefs_service():
    if os.path.isfile('/usr/bin/systemctl'):
        subprocess.run('/usr/bin/systemctl disable cagefs', shell=True, executable='/bin/bash')
        subprocess.run('/usr/bin/systemctl stop cagefs', shell=True, executable='/bin/bash')
        subprocess.run('/usr/bin/systemctl mask cagefs', shell=True, executable='/bin/bash')
    else:
        subprocess.run('/sbin/service cagefs stop &> /dev/null', shell=True, executable='/bin/bash')
        subprocess.run('/sbin/chkconfig cagefs off', shell=True, executable='/bin/bash')


def enable_cagefs_service():
    if os.path.isfile('/usr/bin/systemctl'):
        subprocess.run('/usr/bin/systemctl unmask cagefs', shell=True, executable='/bin/bash')
        subprocess.run('/usr/bin/systemctl enable cagefs', shell=True, executable='/bin/bash')
        subprocess.run('/usr/bin/systemctl start cagefs', shell=True, executable='/bin/bash')
    else:
        subprocess.run('/sbin/chkconfig cagefs on', shell=True, executable='/bin/bash')
        subprocess.run('/sbin/service cagefs start &> /dev/null', shell=True, executable='/bin/bash')


def setup_without_cagefs(args):
    """
    Setup PHP Selector without CageFS
    """
    sys.path.append('/usr/share/cagefs')
    try:
        import cagefslib
    except ImportError:
        print('Error: CageFS is not installed')
        sys.exit(1)
    # alt-php versions are installed ?
    alt_dirs = cagefslib.get_alt_dirs()
    if not alt_dirs:
        print('alt-php not found')
        sys.exit(1)
    # detect cpanel user
    if args:
        user = args[0]
        set_cpanel_user(user)
    else:
        user = get_cpanel_user()
        if not user:
            print('Error: failed to detect cpanel account. Please specify name of an account as argument:')
            print('selectorctl --setup-without-cagefs USER')
            sys.exit(1)
        if not os.path.exists(ClSelect.USER_CONF):
            set_cpanel_user(user)
    # disable linksafe protection
    switch_linksafe()
    pw = pwd.getpwnam(user)
    homedir = pw.pw_dir
    # create symlinks to user's alt_php.ini files
    for alt_dir in alt_dirs:
        alt_path = '/opt/alt/' + alt_dir + '/link/conf/alt_php.ini'
        user_path = homedir + '/.cl.selector/alt_' + alt_dir + '.ini'
        make_symlink(user_path, alt_path)
    obj = ClUserSelect('php')
    restore_settings_from_backup(user, homedir, pw.pw_uid, alt_dirs, obj)
    obj.create_selector_symlinks(user)
    modify_search_path_in_bashrc(user, homedir, obj)
    disable_cagefs_service()
    # kill user's processes in LVE
    subprocess.run('/usr/sbin/lvectl destroy ' + str(pw.pw_uid) + ' &>/dev/null; /usr/sbin/lvectl apply '
                   + str(pw.pw_uid) + ' &>/dev/null', shell=True, executable='/bin/bash')


def revert_to_cagefs():
    """
    Revert to default PHP Selector setup with CageFS
    """
    if not os.path.exists(ClSelect.USER_CONF):
        print('PHP Selector is in default mode already ("with CageFS" mode)')
        sys.exit(1)
    sys.path.append('/usr/share/cagefs')
    try:
        import cagefslib
    except ImportError:
        print('Error: CageFS is not installed')
        sys.exit(1)
    # alt-php versions are installed ?
    alt_dirs = cagefslib.get_alt_dirs()
    if not alt_dirs:
        print('alt-php not found')
        sys.exit(1)
    switch_linksafe(enable=True)
    # delete symlinks to user's alt_php.ini files
    for alt_dir in alt_dirs:
        alt_path = '/opt/alt/' + alt_dir + '/link/conf/alt_php.ini'
        if os.path.islink(alt_path):
            os.unlink(alt_path)
    user = get_cpanel_user()
    if not user:
        print('Error: failed to detect user')
        sys.exit(1)
    pw = pwd.getpwnam(user)
    homedir = pw.pw_dir
    obj = ClUserSelect('php')
    modify_search_path_in_bashrc(user, homedir, obj, add=False)
    # remove config file (file-switch) for "without CageFS" mode
    os.unlink(ClSelect.USER_CONF)
    # Generate alt_php.ini for all versions using cagefsctl
    subprocess.run('/usr/sbin/cagefsctl --silent --force-update-etc ' + user, shell=True, executable='/bin/bash')
    enable_cagefs_service()
    # kill user's processes in LVE
    subprocess.run('/usr/sbin/lvectl destroy ' + str(pw.pw_uid) + ' &>/dev/null; /usr/sbin/lvectl apply ' +
                   str(pw.pw_uid) + ' &>/dev/null', shell=True, executable='/bin/bash')


def apply_global_php_ini(args):
    """
    Apply "global" php.ini settings to all alt-php versions
    :param args: list of command line parameters (names of php.ini options)
    :type args: list
    """
    sys.path.append('/usr/share/cagefs')
    try:
        import cagefslib
        import cagefsreconfigure
    except ImportError:
        print('Error: CageFS is not installed')
        sys.exit(1)
    # alt-php versions are installed ?
    if cagefslib.get_alt_versions():
        cagefsreconfigure.replace_alt_settings(options=args)


def _check_depencies_and_print_message(print_format, print_message):
    if len(depend_modules_dict):
        # Warning - blocked modules present
        modules_list = list()
        for module_name, dep_module in depend_modules_dict.items():
            modules_list.append(" '%s' is required for '%s'" % (module_name, dep_module))
        clprint.print_diag(print_format, {'status': 'WARN',
                                          'message': print_message + ','.join(modules_list)})


def get_extensions(interpreter, version, fmt='text'):
    ext_list = ClExtSelect(interpreter).list_extensions(version)
    return parse_extensions(ext_list, version, fmt)


def parse_extensions(ext_list, version, fmt):
    json_list = []
    for ext in ext_list:
        action = '~'
        if ext[1] is True:
            action = '+'
        elif ext[1] is False:
            action = '-'
        if fmt == 'text':
            json_list.append((action, ext[0]))
        else:
            json_list.append({'name': ext[0], 'description': '', 'state': ext_letter_to_status(action)})
            json_list = fill_descriptions(json_list)
    if fmt == 'json':
        result_dict = {'version': version, 'extensions': json_list}
        return result_dict
    return json_list


def get_cagefs_users():
    """
    Return list of users that are in CageFS
    If CageFS is not installed or initialized
    throws exception and prints it
    :return:
    """
    not_installed_msg = 'No such file or directory'
    not_initialized_msg = 'CageFS is not initialized'
    try:
        users = run_command([CAGEFSCTL_COMMAND, '--list-enabled']).strip()
        if users == '':
            return []
        return users.split('\n')[1:]  # First element shows number of users
    except ExternalProgramFailed as e:
        if not_installed_msg in str(e):
            print_error_and_exit('ERROR: CageFS not installed.')
        elif not_initialized_msg in str(e):
            print_error_and_exit('Error: CageFS is not initialized. '
                                 'Use "/usr/sbin/cagefsctl --init" to initialize CageFS')
        print_error_and_exit(e)


def verify_options_in_cagefs(options, supported_options):
    """
    Verifies if the provided options are supported in CageFS
    and appends the current user option if user-related options are absent
    """
    input_options = {opt for opt, _ in options}

    if not input_options & set(supported_options):
        print_error_and_exit('Error: This command is not supported in CageFS')

    if '--user' not in input_options and '-u' not in input_options:
        current_user_name = pwd.getpwuid(os.geteuid()).pw_name
        options.append(('--user', current_user_name))


def main():
    config = {}
    config['interpreter'] = 'php'
    config['version'] = None
    config['show-all'] = False
    config['print-summary'] = False
    config['show-native-version'] = False
    config['decoder'] = 'plain'
    config['quiet'] = False
    config['format'] = 'text'
    config['api-version'] = API_0

    actions = {}
    exclude_pid_list = []

    check_args_presence()

    try:
        opts, args = getopt.getopt(
            sys.argv[1:],
            'hi:lSsCcB:Y:N:E:D:R:v:Gu:b:ge:d:r:atpVLT:k:m:x:QqPjwWz',
            ['help',
             'setup-without-cagefs',
             'revert-to-cagefs',
             'interpreter=',
             'list',
             'summary',
             'user-summary',
             'current',
             'user-current',
             'set-current=',
             'enable-alternative=',
             'disable-alternative=',
             'enable-extensions=',
             'disable-extensions=',
             'replace-extensions=',
             'version=',
             'list-extensions',
             'user=',
             'set-user-current=',
             'list-user-extensions',
             'enable-user-extensions=',
             'disable-user-extensions=',
             'replace-user-extensions=',
             'all',
             'reset-user-extensions',
             'print-summary',
             'show-native-version',
             'list-users',
             'change-to-version=',
             'add-options=',
             'replace-options=',
             'delete-options=',
             'base64',
             'apply-symlinks-rules',
             'quiet',
             'print-options',
             'print-options-safe',
             'json',
             'csv',
             'perl',
             'api-version=',
             'reset-options',
             'update-backup',
             'apply-global-php-ini',
             'exclude-pid-list=',
             'for-all-users'
             ])
    except getopt.GetoptError:
        usage()
        sys.exit(1)

    opts_supported_in_cagefs = [
        '-l', '--list',
        '-s', '--user-summary',
        '-c', '--user-current',
        '-b', '--set-user-current',
        '-e', '--enable-user-extensions',
        '-d', '--disable-user-extensions',
        '-r', '--replace-user-extensions',
        '-t', '--reset-user-extensions',
        '-g', '--list-user-extensions',
        '-k', '--add-options',
        '-m', '--replace-options',
        '-x', '--delete-options',
        '-P', '--print-options',
        '--print-options-safe',
        '-z', '--reset-options',
    ]

    if in_cagefs():
        verify_options_in_cagefs(opts, opts_supported_in_cagefs)

    for o, a in opts:
        if o in ['-h', '--help']:
            usage()
            sys.exit(0)
        elif o in ['--exclude-pid-list']:
            for pid in a.split(','):
                try:
                    exclude_pid_list.append(int(pid))
                except ValueError:
                    continue
        elif o in ['--apply-symlinks-rules']:
            actions['apply-symlinks-rules'] = True
        elif o in ['--setup-without-cagefs']:
            setup_without_cagefs(args)
            sys.exit(0)
        elif o in ['--revert-to-cagefs']:
            revert_to_cagefs()
            sys.exit(0)
        elif o in ("--apply-global-php-ini",):
            apply_global_php_ini(args)
            sys.exit(0)
        elif o in ['-l', '--list']:
            actions['list-alternatives'] = True
        elif o in ['-S', '--summary']:
            actions['show-summary'] = True
        elif o in ['-s', '--user-summary']:
            actions['show-user-summary'] = True
        elif o in ['-C', '--current']:
            actions['show-current'] = True
        elif o in ['-c', '--user-current']:
            actions['show-user-current'] = True
        elif o in ['-a', '--all']:
            config['show-all'] = True
        elif o in ['-v', '--version']:
            config['version'] = a
        elif o in ['-u', '--user']:
            config['user'] = a
            clpwd = ClPwd()
            if ClSelect.work_without_cagefs():
                uid = clpwd.get_uid(a)
            else:
                users = a.split(',')
                user_list = list()
                try:
                    if len(users) == 1:
                        uid = clpwd.get_uid(a)
                        if os.geteuid() == 0:
                            for user in clpwd.get_names(uid):
                                ClUserSelect().cagefs_copy_etc(user)
                    else:
                        for user in users:
                            if user not in user_list:
                                user_list += clpwd.get_names(clpwd.get_uid(user))
                        config['user'] = ','.join(user_list)
                except ClPwd.NoSuchUserException as e:
                    sys.stderr.write(str(e)+'\n')
                    sys.exit(1)
        elif o in ['-B', '--set-current']:
            actions['set-current'] = a
        elif o in ['-b', '--set-user-current']:
            actions['set-user-current'] = a
        elif o in ['-Y', '--enable-alternative']:
            actions['enable-alternative'] = a
        elif o in ['-N', '--disable-alternative']:
            actions['disable-alternative'] = a
        elif o in ['-G', '--list-extensions']:
            actions['list-extensions'] = True
        elif o in ['-g', '--list-user-extensions']:
            actions['list-user-extensions'] = True
        elif o in ['-E', '--enable-extensions']:
            actions['enable-extensions'] = a
        elif o in ['-D', '--disable-extensions']:
            actions['disable-extensions'] = a
        elif o in ['-R', '--replace-extensions']:
            actions['replace-extensions'] = a
        elif o in ['-e', '--enable-user-extensions']:
            actions['enable-user-extensions'] = a
        elif o in ['-d', '--disable-user-extensions']:
            actions['disable-user-extensions'] = a
        elif o in ['-r', '--replace-user-extensions']:
            actions['replace-user-extensions'] = a
        elif o in ['-t', '--reset-user-extensions']:
            actions['reset-user-extensions'] = True
        elif o in ['-p', '--print-summary']:
            config['print-summary'] = True
        elif o in ['-V', '--show-native-version']:
            config['show-native-version'] = True
        elif o in ['-L', '--list-users']:
            actions['list-users'] = True
        elif o in ['-T', '--change-to-version']:
            actions['change-to-version'] = a
        elif o in ['-k', '--add-options']:
            actions['add-options'] = a
        elif o in ['-m', '--replace-options']:
            actions['replace-options'] = a
        elif o in ['-x', '--delete-options']:
            actions['delete-options'] = a
        elif o in ['-Q', '--base64']:
            config['decoder'] = 'base64'
        elif o in ['-q', '--quiet']:
            config['quiet'] = True
        elif o in ['-P', '--print-options']:
            actions['print-options'] = True
        elif o in ['--print-options-safe']:
            actions['print-options-safe'] = True
        elif o in ['-j', '--json']:
            config['format'] = 'json'
        elif o in ['-w', '--csv']:
            config['format'] = 'csv'
        elif o in ['--api-version']:
            config['api-version'] = int(a)
        elif o in ['-W', '--perl']:
            config['format'] = 'perl'
        elif o in ['-z', '--reset-options']:
            actions['reset-options'] = True
        elif o in ['--update-backup']:
            actions['update-backup'] = True
        elif o in ['--for-all-users']:
            if 'user' in config:
                print_error_and_exit("--for-all-users and --user options are mutually"
                                     " exclusive options and cannot be used simultaneously."
                                     "\nUse --for-all-user OR --user instead.")
            users = get_cagefs_users()
            if not users:
                print_error_and_exit("No changes were made: there are no users with cagefs enabled ")
            clpwd = ClPwd()
            user_list = list()
            for user in users:
                if user not in user_list:
                    user_list += clpwd.get_names(clpwd.get_uid(user))
            config['user'] = ','.join(user_list)
    if len(actions) != 1:
        if len(actions) == 0 and config['show-native-version']:
            try:
                print(ClSelect(config['interpreter']).get_native_version()[0])
            except TypeError:
                pass
        else:
            print_error_and_exit("Wrong set of options", 'ERROR')

    try:
        # check if we are able to do anything before actually parsing options

        # this two exceptions check for native version inside
        # this is done because selectorctl is called in alt-php spec in cycle
        # and we should avoid printing lot of messages there
        if 'set-user-current' not in actions and \
                'show-user-current' not in actions:
            ClSelect().check_requirements()

        if 'list-alternatives' in actions:
            if config["format"] != "json":
                for alt in ClSelect(config['interpreter']).list_alternatives():
                    print("%s\t%s\t%s" % (alt))
            else:
                alternatives_dict = {'status': 'ok', 'data': []}
                for alt in ClSelect(config['interpreter']).list_alternatives():
                    alternatives_dict['data'].append({'short': alt[0], 'full': alt[1], 'path': alt[2]})
                print(json.dumps(alternatives_dict))
        elif 'show-summary' in actions:
            data = ClSelect(config['interpreter']).get_summary(
                config['show-native-version'])
            print_summary(data, config['format'], config['api-version'])
        elif 'show-current' in actions:
            print("%s\t%s\t%s" % ClSelect(config['interpreter']).get_version(
                config['show-native-version']))
        elif 'set-current' in actions:
            ClSelect(config['interpreter']).set_version(actions['set-current'])
            if config['format'] == 'json':
                print_json_status_ok()
        elif 'enable-alternative' in actions:
            ClSelect(config['interpreter']).enable_version(actions['enable-alternative'])
            if config['format'] == 'json':
                print_json_status_ok()
        elif 'disable-alternative' in actions:
            ClSelect(config['interpreter']).disable_version(actions['disable-alternative'])
            if config['format'] == 'json':
                print_json_status_ok()
        elif 'list-extensions' in actions:
            check_params(config, ('interpreter','version'))
            ext_list = get_extensions(config['interpreter'], config['version'], config['format'])
            if config['format'] == 'text':
                for item in ext_list:
                    print("%s %s" % item)
            elif config['format'] == 'json':
                print(json.dumps(ext_list))
        elif 'enable-extensions' in actions:
            check_params(config, ('interpreter', 'version'))
            ClExtSelect(config['interpreter']).enable_extensions(
                config['version'],
                list(map((lambda i: i.strip()), actions['enable-extensions'].split(','))))
        elif 'disable-extensions' in actions:
            check_params(config, ('interpreter', 'version'))
            ClExtSelect(config['interpreter']).disable_extensions(
                config['version'],
                list(map((lambda i: i.strip()), actions['disable-extensions'].split(','))))
            if len(depend_modules_dict):
                # Warning - blocked modules present
                _check_depencies_and_print_message(config['format'], 'Modules left by dependencies:')
        elif 'replace-extensions' in actions:
            check_params(config, ('interpreter', 'version'))
            ClExtSelect(config['interpreter']).replace_extensions(
                config['version'],
                list(map((lambda i: i.strip()), actions['replace-extensions'].split(','))))
            if len(depend_modules_dict):
                # Warning - blocked modules present
                _check_depencies_and_print_message(config['format'], 'Modules left/added by dependencies:')
            elif config['format'] == 'json':
                print_json_status_ok()
        elif 'show-user-summary' in actions:
            check_params(config, ('interpreter', 'user'))
            data = ClUserSelect(config['interpreter'], exclude_pid_list).get_summary(
                config['user'],
                config['show-native-version'])
            print_summary(data)
        elif 'show-user-current' in actions:
            check_params(config, ('interpreter', 'user'))
            print("%s\t%s\t%s" % ClUserSelect(
                config['interpreter'], exclude_pid_list).get_version(
                config['user'],
                config['show-native-version']))
        elif 'apply-symlinks-rules' in actions:
            check_params(config, ('interpreter',))
            ClUserSelect(config['interpreter'], exclude_pid_list).apply_symlinks_rules()
        elif 'set-user-current' in actions:
            check_params(config, ('interpreter', 'user'))
            # hack for alt-php spec where we read and re-apply
            # php version for each user in system
            # in order not to bump deps, we just silently
            # ignore version set requests for 'native'
            # (which should be set for all users on server
            # because web ui does not work)
            try:
                ClSelect().check_requirements()
            except ClSelectExcept.NativeNotInstalled:
                if actions['set-user-current'] != 'native':
                    raise
                exit(0)

            # we intentionally use first user cause set_version has workaround for multiple same uid users
            # LVEMAN-1670
            user = clpwd.get_names(uid)[0]
            c = ClUserSelect(config['interpreter'], exclude_pid_list)
            data = c.set_version(user, actions['set-user-current'],
                config['print-summary'], config['show-native-version'])
            c.clean_crui_images(clpwd.get_names(uid))
            if config['print-summary']:
                print_summary(data)
        elif 'list-user-extensions' in actions:
            check_params(config, ('interpreter', 'user'))
            if config['show-all']:
                for ext in ClUserExtSelect(config['interpreter'], exclude_pid_list).list_all_extensions(
                        config['user'],
                        config['version']):
                    action = '-'
                    if ext[1] is None:
                        action = '~'
                    elif ext[1] is True:
                        action = '+'
                    print("%s %s" % (action, ext[0]))
            else:
                for ext in ClUserExtSelect(config['interpreter'], exclude_pid_list).list_enabled_extensions(
                        config['user'],
                        config['version']):
                    print(ext[0])
        elif 'enable-user-extensions' in actions:
            check_params(config, ('interpreter', 'version', 'user'))
            ClSelect.check_multiphp_system_default_version()
            users = config['user'].split(',')
            for user in users:
                c = ClUserExtSelect(config['interpreter'], exclude_pid_list)
                c.bulk_enable_extensions(
                    user=user,
                    version=config['version'],
                    ext_list=list(map((lambda i: i.strip()), actions['enable-user-extensions'].split(','))),
                    check_ext=True)
            c.clean_crui_images(users)
        elif 'disable-user-extensions' in actions:
            check_params(config, ('interpreter', 'version', 'user'))
            ClSelect.check_multiphp_system_default_version()
            users = config['user'].split(',')
            for user in users:
                c = ClUserExtSelect(config['interpreter'], exclude_pid_list)
                c.bulk_disable_extensions(
                    user,
                    config['version'],
                    list(map((lambda i: i.strip()), actions['disable-user-extensions'].split(','))))
            c.clean_crui_images(users)
        elif 'replace-user-extensions' in actions:
            check_params(config, ('interpreter', 'version', 'user'))
            ClSelect.check_multiphp_system_default_version()
            user = clpwd.get_names(uid)[0]
            c = ClUserExtSelect(config['interpreter'], exclude_pid_list)
            c.replace_extensions(
                user,
                config['version'],
                list(map((lambda i: i.strip()), actions['replace-user-extensions'].split(','))))
            c.clean_crui_images(clpwd.get_names(uid))
        elif 'reset-user-extensions' in actions:
            check_params(config, ('interpreter', 'version', 'user'))
            ClSelect.check_multiphp_system_default_version()
            user = clpwd.get_names(uid)[0]
            c = ClUserExtSelect(config['interpreter'], exclude_pid_list)
            extensions = c.reset_extensions(user, config['version'])
            c.clean_crui_images(clpwd.get_names(uid))
            print(','.join(extensions))
        elif 'list-users' in actions:
            check_params(config, ('interpreter', 'version'))
            users = ClUserSelect(config['interpreter'], exclude_pid_list).list_users(
                config['version'])
            print(','.join(users))
        elif 'change-to-version' in actions:
            check_params(config, ('interpreter', 'version'))
            ClUserSelect(config['interpreter'], exclude_pid_list).change_to_version(
                actions['change-to-version'],
                config['version'])
        elif 'add-options' in actions:
            check_params(config, ('interpreter', 'version', 'user'))
            ClSelect.check_multiphp_system_default_version()
            user = clpwd.get_names(uid)[0]
            c = ClUserOptSelect(config['interpreter'], exclude_pid_list)
            c.insert_options(
                user,
                config['version'],
                actions['add-options'],
                config['decoder'],
                True,
                config['quiet'])
            c.clean_crui_images(clpwd.get_names(uid))
            if config['format'] == 'json':
                clprint.print_data(config['format'], {})
        elif 'replace-options' in actions:
            check_params(config, ('interpreter', 'version', 'user'))
            ClSelect.check_multiphp_system_default_version()
            user = clpwd.get_names(uid)[0]
            c = ClUserOptSelect(config['interpreter'], exclude_pid_list)
            c.insert_options(
                user,
                config['version'],
                actions['replace-options'],
                config['decoder'],
                False,
                config['quiet'])
            c.clean_crui_images(clpwd.get_names(uid))
            if config['format'] == 'json':
                clprint.print_data(config['format'], {})
        elif 'delete-options' in actions:
            check_params(config, ('interpreter', 'version', 'user'))
            ClSelect.check_multiphp_system_default_version()
            user = clpwd.get_names(uid)[0]
            c = ClUserOptSelect(config['interpreter'], exclude_pid_list)
            c.delete_options(
                user,
                config['version'],
                actions['delete-options'],
                config['decoder'],
                config['quiet'])
            c.clean_crui_images(clpwd.get_names(uid))
            if config['format'] == 'json':
                clprint.print_data(config['format'], {})
        elif 'reset-options' in actions:
            user = None
            version = None
            if 'user' in config:
                user = config['user'].split(',')
            if config['version']:
                version = config['version'].split(',')
            c = ClUserOptSelect(config['interpreter'], exclude_pid_list)
            c.reset_options(user, version)
            c.clean_crui_images(user)
            if config['format'] == 'json':
                clprint.print_data(config['format'], {})
        elif 'print-options' in actions:
            check_params(config, ('interpreter', 'user'))
            clprint.print_data(config['format'],
                ClUserOptSelect(config['interpreter'], exclude_pid_list).get_options(
                    config['user'],
                    config['version']))
        elif 'print-options-safe' in actions:
            check_params(config, ('interpreter', 'user'))
            clprint.print_data(
                config['format'],
                ClUserOptSelect(config['interpreter'], exclude_pid_list).get_options(
                    config['user'],
                    config['version']
                ),
                escape=True
            )
        elif 'update-backup' in actions:
            clpwd = ClPwd()
            for user in clpwd.get_user_dict().keys():
                try:
                    ClUserSelect()._check_user_in_cagefs(user)
                    ClUserSelect()._backup_settings(user)
                    ClUserOptSelect().backup_php_options(user)
                except ClSelectExcept.NotCageFSUser:
                    pass #SKIP user with disabled cagefs
                except ClSelectExcept.UnableToSaveData as e:
                    if not config['quiet']:
                        clprint.print_diag(
                            config['format'],
                            {'status': 'ERROR', 'message': str(e)})
                    pass  #SKIP user with errors
    except ClSelectExcept.NativeNotInstalled as e:
        clprint.print_diag(config['format'], {
            'status': 'WARNING',
            'message': str(e),
            'details': e.details,
            'context': e.context
        })
        sys.exit(1)
    except BaseClSelectException as e:
        clprint.print_diag(config['format'], {
            'status': 'ERROR',
            'message': str(e),
            'details': e.details,
            'context': e.context
        })
        sys.exit(1)
    except (KeyError, UnboundLocalError):
        print_error_and_exit("Incomplete or incorrect set of arguments")
    except Exception as e:
        msg = traceback.format_exc()
        clprint.print_diag(
            config['format'],
            {'status': 'ERROR', 'message': msg})

if __name__ == '__main__':
    main()

VaKeR 2022