#!/opt/cloudlinux/venv/bin/python3 -bb
import stat
import subprocess, os
import pathlib

HOOKS_DIR = '/opt/cloudlinux/rhn_hooks/post.d'
JWT_TOKEN = '/etc/sysconfig/rhn/jwt.token'

def _update_edition_user_file():
    """
    Save information about current edition in specific
    location available for users
    """
    if not os.path.exists('/usr/bin/cldetect') or not os.path.exists('/opt/cloudlinux/'):
        return

    p = subprocess.Popen(['/usr/bin/cldetect', '--detect-edition'],
        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = p.communicate()
    if p.returncode == 0:
        with open('/opt/cloudlinux/cl_edition.tmp', 'wb') as f:
            f.write(stdout)
        os.rename('/opt/cloudlinux/cl_edition.tmp', '/opt/cloudlinux/cl_edition')
        os.chmod('/opt/cloudlinux/cl_edition', 0o644)

def _is_safe_to_execute(path):
    """
    Returns True only if the given path is owned by root, is not a symlink,
    and is not writable by group or others. Used as a single gate immediately
    before invoking root-privileged code on the path, to narrow the TOCTOU
    window between validation and use.
    """
    try:
        st = os.lstat(path)
    except OSError:
        return False
    if stat.S_ISLNK(st.st_mode):
        return False
    if st.st_uid != 0:
        return False
    if st.st_mode & 0o022:
        return False
    return True


def _trigger_universal_hooks():
    """
    Discovers executable files in directory and triggers them all together
    Executable files are brought by different rpm packages, depending on which one
    requires the action
    """
    if not os.path.exists(HOOKS_DIR):
        return

    if not _is_safe_to_execute(HOOKS_DIR):
        return

    hooks = [os.path.join(HOOKS_DIR, f) for f in os.listdir(HOOKS_DIR)
             if os.path.isfile(os.path.join(HOOKS_DIR, f))]

    for hook in hooks:
        if not _is_safe_to_execute(hook):
            continue
        process = subprocess.Popen([hook],
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        stdout, stderr = process.communicate()


def _write_token_var(token, varpath):
    """
    Atomically write the token into a yum vars file.
    """
    varpath = pathlib.Path(varpath)
    tmppath = varpath.with_suffix('.tmp')
    try:
        os.unlink(str(tmppath))
    except OSError:
        pass
    fd = os.open(str(tmppath),
                 os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW | os.O_CLOEXEC,
                 0o600)
    try:
        os.write(fd, token.encode())
    finally:
        os.close(fd)
    os.replace(str(tmppath), str(varpath))


def _create_repo_auth_vars():
    """
    Provide the els repos authentication token as yum repo variables.
    CL7 uses yum, which reads repo variables from /etc/yum/vars/ and
    silently skips symlinks there — so unlike the CL8+ hook (which
    symlinks the token under /etc/dnf/vars/), the token value is copied
    into regular files. This hook runs on every token update, so the
    copies stay current.
    """
    token = pathlib.Path(JWT_TOKEN).read_text()
    pathlib.Path('/etc/yum/vars').mkdir(parents=True, exist_ok=True)
    for name in ('phpelstoken', 'altpythonelstoken',
                 'altrubyelstoken', 'altnodejselstoken'):
        _write_token_var(token, '/etc/yum/vars/' + name)


def main():
    """
    You add your other actions here, but don't forget to handle errors.
    """
    _update_edition_user_file()
    _create_repo_auth_vars()
    _trigger_universal_hooks()


if __name__ == '__main__':
    main()
