AlkantarClanX12

Your IP : 18.118.144.98


Current Path : /opt/cloudlinux/venv/lib64/python3.11/site-packages/clcommon/public_hooks/bundle/cpanel/
Upload File :
Current File : //opt/cloudlinux/venv/lib64/python3.11/site-packages/clcommon/public_hooks/bundle/cpanel/lib.py

# -*- coding: utf-8 -*-
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2018 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
#

import json
import os
import subprocess
import logging

import raven

from clcommon.public_hooks import (
    POST_MODIFY_DOMAIN,
    PRE_MODIFY_USER,
    POST_MODIFY_USER
)
from clcommon.utils import run_command, ExternalProgramFailed, is_user_present
from secureio import makedirs_secure, write_file_secure, read_file_secure

EXIT_NO_USER_FOUND = 1
ERR_NO_USER_FOUND = 'Unable to find username in hook cmdline'
WHMAPI1 = '/usr/sbin/whmapi1'
LVE_DIR = '/var/lve'
TMP_DIR = os.path.join(LVE_DIR, 'tmp')

logger = logging.getLogger(__name__)


def print_response(hook_name, success=True):
    """
    cPanel expects that each custom hook
    prints two values in the end of the execution:
     - status, where 1 means success
     - message, which explains non-1 statuses
    otherwise nothing really breaks, but logs
    are full of "script returned invalid response" msgs
    :param hook_name: name, path or anything else to fill
                      message with in order to understand
                      what exactly failed
    :param success: is it everything ended successfully?
    :return: Nothing
    """
    if not success:
        print(0, f"Failed to execute hook {hook_name}; you can find logs in "
                 "/var/log/cloudlinux/hooks/info.log "
                 "and contact CloudLinux Support if you need "
                 "help with the issue.")
    else:
        print(1, "Ok")


# ATTENTION: we use this function in
# processpaneluserspackages and lvemanager
def call_sync_map():
    """
    Run lvectl sync-map and log possible stdout|err in case of errors.
    :return: None
    """
    with subprocess.Popen(
        ['lvectl', 'sync-map'],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    ) as proc:
        stdout, stderr = proc.communicate()
        if proc.returncode != 0:
            logger.error('Error during "lvectl sync-map", code: %s, '
                         'stderr: `%s`, stdout: `%s`. Reseller limits '
                         'kernel mapping might be not synchronized.'
                         'Contact CloudLinux Support for help.'
                         '', proc.returncode, stdout, stderr)


def cpanel_postwwwacct_main(data):
    """
    Post create account hook of cPanel
    :return: None
    """
    user = data.get('user', None)
    owner = data.get('owner', None)
    if not user:
        logger.warning(ERR_NO_USER_FOUND)
        return EXIT_NO_USER_FOUND
    return subprocess.call([
        POST_MODIFY_USER, 'create',
        '--username', user, '--owner', owner],
        env={'CPANEL_RESTORE': str(data.get('is_restore', 0))})


def cpanel_prekillacct_main(data):
    """
    Pre kill account hook of cPanel
    :return: None
    """
    #  It's necessary destroy lve before remove user home directory,
    # otherwise will be error due busy mount points of cagefs
    user = data.get('user', None)
    if not user:
        logger.warning(ERR_NO_USER_FOUND)
        return EXIT_NO_USER_FOUND

    if not is_user_present(user):
        logger.warning('User %s does not present in the system, skip hook', user)
        return 0

    return subprocess.call([
        PRE_MODIFY_USER, 'delete',
        '--username', user])


def cpanel_postkillacct_main(data):
    """
    Post kill account hook of cPanel
    :return: None
    """

    user = data.get('user', None)
    if not user:
        logger.warning(ERR_NO_USER_FOUND)
        return EXIT_NO_USER_FOUND

    return subprocess.call([
        POST_MODIFY_USER, 'delete',
        '--username', user])


def _read_old_domain(user):
    """
    Read old domain for modified account
    :param user: name of user
    :return: old domain
    :rtype: str
    """

    domain = None
    filename = os.path.join(TMP_DIR, user)

    try:
        content = read_file_secure(filename, uid=0, gid=0, exit_on_error=False,
                                   # This log is intended to be used only by
                                   # cagefs update command
                                   write_log=False)
        domain = content[0]
    except (IndexError, OSError, IOError):
        # use Raven carefully and only in places where
        # you sure that sentry is already initialized
        raven.base.Raven.captureException(
            message='failed to read old domain for user (pre hook no called?)')

    return domain


def cpanel_postmodifyacct_main(data):
    """
    Post modify account hook of cPanel
    :return: None
    """
    user = data.get('user', None)
    new_user = data.get('newuser', None)
    domain = data.get('domain', None)
    exit_code = 0

    # changing owner of user
    # FIXME: this check does not work because cpanel sends `owner` always
    new_owner = data.get('owner', data.get('OWNER'))
    args = [POST_MODIFY_USER, 'modify', '-u', user]
    if new_owner:
        args += ['--new-owner', new_owner]
    if all((user, new_user,)) and user != new_user:
        args += ['--new-username', new_user]

    exit_code += subprocess.call(args)

    old_domain = _read_old_domain(user)
    if domain is not None and domain != old_domain:
        # looks like domain is renamed
        exit_code += subprocess.call([
            POST_MODIFY_DOMAIN,
            'modify', '--username', user if new_user is None else new_user,
            '--domain', old_domain, '--new-domain', domain,
            '--include-subdomains'])
    return exit_code


def cpanel_postrestoreacct_main(data):
    """
    Post restore account hook of cPanel
    :return: None
    """

    user = data.get('user', None)
    if not user:
        logger.warning(ERR_NO_USER_FOUND)
        return EXIT_NO_USER_FOUND
    return subprocess.call([
        POST_MODIFY_USER, 'restore',
        '--username', user])


def _get_old_domain(user):
    """
    Get old domain for modified account
    :param user: name of user
    :return: old domain
    :rtype: str
    """
    domain = None
    try:
        cmd = [
            WHMAPI1,
            'listaccts',
            f'search={user}',
            'searchtype=user',
            'searchmethod=exact',
            'want=domain',
            '--output=json'
        ]

        std_out = run_command(cmd, return_full_output=True)[1] # take only std_out, ignore std_err
        data = json.loads(std_out)

        domain = data['data']['acct'][0]['domain']
    except (ExternalProgramFailed, IndexError, KeyError):
        # use Raven carefully and only in places where
        # you sure that sentry is already initialized
        raven.base.Raven.captureException(message='failed to get old domain for user from cpanel')

    return domain


def cpanel_premodifyacct_main(data):
    """
    Pre modify account hook of cPanel
    :return: None
    """
    user = data.get('user')

    # getting old domain
    # TODO: why not cpapi?
    domain = _get_old_domain(user)
    if domain is None:
        return 0

    # save old domain to file
    filename = os.path.join(TMP_DIR, user)
    makedirs_secure(TMP_DIR, perm=0o750, uid=0, gid=0, parent_path=LVE_DIR)
    write_file_secure([domain], filename, uid=0, gid=0, perm=0o700)
    return 0