晋太元中,武陵人捕鱼为业。缘溪行,忘路之远近。忽逢桃花林,夹岸数百步,中无杂树,芳草鲜美,落英缤纷。渔人甚异之,复前行,欲穷其林。   林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗。土地平旷,屋舍俨然,有良田、美池、桑竹之属。阡陌交通,鸡犬相闻。其中往来种作,男女衣着,悉如外人。黄发垂髫,并怡然自乐。   见渔人,乃大惊,问所从来。具答之。便要还家,设酒杀鸡作食。村中闻有此人,咸来问讯。自云先世避秦时乱,率妻子邑人来此绝境,不复出焉,遂与外人间隔。问今是何世,乃不知有汉,无论魏晋。此人一一为具言所闻,皆叹惋。余人各复延至其家,皆出酒食。停数日,辞去。此中人语云:“不足为外人道也。”(间隔 一作:隔绝)   既出,得其船,便扶向路,处处志之。及郡下,诣太守,说如此。太守即遣人随其往,寻向所志,遂迷,不复得路。   南阳刘子骥,高尚士也,闻之,欣然规往。未果,寻病终。后遂无问津者。 sh-3ll

HOME


sh-3ll 1.0
DIR:/opt/cloudlinux/venv/lib64/python3.11/site-packages/clcagefslib/webisolation/crontab/
Upload File :
Current File : //opt/cloudlinux/venv/lib64/python3.11/site-packages/clcagefslib/webisolation/crontab/processor.py
# -*- coding: utf-8 -*-
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2025 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENCE.TXT
#
"""Processing functions for crontab operations."""

import os
import pwd
import shlex
import subprocess
import sys
from typing import BinaryIO, Callable, Optional

from clcagefslib.domain import is_isolation_enabled

from .constants import ISOLATION_WRAPPER
from .parser import (
    parse_crontab_structure,
    write_crontab_structure,
    entries_to_str_list,
)
from .structure import EnvAssignmentLine, ParsedCrontabLine
from .utils import get_document_root

CRONTAB_BIN = "/usr/bin/crontab"


def process_list(stdout: Optional[BinaryIO] = None, stderr: Optional[BinaryIO] = None) -> int:
    """
    Process CRONTAB_LIST command.

    Runs 'crontab -l' to get current crontab entries. When isolation is active
    (PROXYEXEC_DOCUMENT_ROOT is set), only shows entries for the current
    document root. Removes isolation prefixes from output.

    Args:
        stdout: Output stream buffer (defaults to sys.stdout.buffer)
        stderr: Error stream buffer (defaults to sys.stderr.buffer)

    Returns:
        int: Exit code from crontab command, or 1 on error
    """
    stdout = stdout or sys.stdout.buffer
    stderr = stderr or sys.stderr.buffer

    # Vixie/cronie derives the caller name from getpwuid(getuid()) itself
    # and rejects ``-u`` under a setuid'd caller, so match its semantics.
    username = pwd.getpwuid(os.getuid()).pw_name
    result = subprocess.run(
        [CRONTAB_BIN, "-l"],
        capture_output=True,
    )

    # early exit in case site isolation is not turned on
    if not is_isolation_enabled(username):
        stdout.write(result.stdout)
        stderr.write(result.stderr)
        return result.returncode

    if result.returncode != 0:
        # Pass through stderr from crontab
        stderr.write(result.stderr)
        return result.returncode

    document_root = get_document_root()

    # Parse structure and pick entries to show
    structure = parse_crontab_structure(result.stdout)

    if document_root is not None:
        entries_to_show = structure.docroot_sections.get(document_root, [])
    else:
        entries_to_show = structure.global_records

    # Convert selected entries to bytes, removing wrapper prefixes if isolation is active
    result_parts = entries_to_str_list(entries_to_show, without_wrapper=bool(document_root))
    output_data = b"".join(result_parts)
    stdout.write(output_data)
    return 0


# scanner-triage: docroot authz and local uid drop both close upstream.
# get_document_root (in .utils) authenticates the caller against
# userdomains(username), and PROXYEXEC_DOCUMENT_ROOT itself is set
# server-side from a validated .cagefs.website token — not from the
# tenant's env. The local uid drop is done by the proxyexec dispatcher:
# CRONTAB_* aliases carry `:secure:noproceed` (never `root:`), so
# setuid(pw_uid) always fires before execv and this code runs as the
# caller. Refile if any CRONTAB_* alias ever acquires a `root:` prefix,
# or if an admin-side caller imports process_save with EUID 0.
def process_save(
    stdin: Optional[BinaryIO] = None,
    stdout: Optional[BinaryIO] = None,
    stderr: Optional[BinaryIO] = None,
    run_func: Optional[Callable] = None,
) -> int:
    """
    Process CRONTAB_SAVE command.

    Reads crontab entries from stdin. If isolation is active:
    1. Gets the current full crontab
    2. Removes entries for the current document root
    3. Adds new entries to the current document root section
    4. Merges and saves the result in new format

    If isolation is not active:
    1. Gets the current full crontab
    2. Replaces user records section with new entries
    3. Preserves all docroot sections
    4. Saves in new format

    This ensures entries for other document roots are preserved.

    Args:
        stdin: Input stream buffer (defaults to sys.stdin.buffer)
        stdout: Output stream buffer (defaults to sys.stdout.buffer)
        stderr: Error stream buffer (defaults to sys.stderr.buffer)
        run_func: Function to run subprocess (defaults to subprocess.run)

    Returns:
        int: Exit code from crontab command, or 1 on error
    """

    stdin = stdin or sys.stdin.buffer
    stdout = stdout or sys.stdout.buffer
    stderr = stderr or sys.stderr.buffer
    run_func = run_func or subprocess.run

    # scanner-triage: this runs after the proxyexec dispatcher has entered
    # the caller's LVE and setuid'd to their UID (CRONTAB_SAVE has no
    # `nolve` flag), so every byte read is charged to the caller's own
    # LVE — kernel SIGKILLs on overrun. Blowing up your own LVE is
    # user-to-self, not a cross-tenant DoS.
    input_data = stdin.read()
    document_root = get_document_root()

    if document_root is not None and ('\n' in document_root or '\r' in document_root):
        raise ValueError(f'Invalid document root: {document_root!r}')

    username = pwd.getpwuid(os.getuid()).pw_name
    if is_isolation_enabled(username):
        # Get current crontab to preserve entries. No ``-u``: Vixie/cronie
        # rejects it under a setuid'd caller.
        list_result = run_func(
            [CRONTAB_BIN, "-l"],
            capture_output=True,
        )

        # No existing crontab or error - start fresh
        # Note: returncode 1 typically means "no crontab for user", which is acceptable
        existing_data = list_result.stdout if list_result.returncode == 0 else b""

        # Parse existing crontab into structure
        existing_structure = parse_crontab_structure(existing_data)

        # Parse new input (filtered data, no section markers)
        # input_data is already bytes from stdin.read()
        new_structure = parse_crontab_structure(input_data)

        # Extract entries and add wrapper prefixes for docroot sections
        parsed_entries = []
        for entry in new_structure.global_records:
            # Drop env-assignment lines (`SHELL=`, `PATH=`, `HOME=`, `MAILTO=`,
            # ...) from per-site sections — crond honours them in textual
            # order and would apply them to following job lines, running an
            # attacker-controlled SHELL before the isolation wrapper and
            # bypassing per-docroot CageFS scope. Env assignments are only
            # safe in the user's global section, never inside an isolated
            # docroot block. F-09 (CLOS-5947): the classifier now also
            # recognises the `"NAME"=` and `'NAME'=` quoted forms that
            # vixie-cron's load_env accepts, so those forms are dropped
            # here too.
            if document_root and isinstance(entry, EnvAssignmentLine):
                continue
            if isinstance(entry, ParsedCrontabLine) and document_root:
                # Decode command to string for shlex.quote()
                command_bytes = entry.command
                has_newline = command_bytes.endswith(b"\n")
                command_str = command_bytes.rstrip(b"\n").decode("utf-8", errors="replace")

                # Use shlex.quote() to properly quote the command for bash -c
                quoted_command = shlex.quote(command_str)

                # Build the wrapped command: wrapper path docroot bash -c "quoted_command"
                # Use shlex.join() for the prefix to handle paths with spaces, then add quoted command
                prefix_parts = shlex.join([ISOLATION_WRAPPER, document_root, "bash", "-c"])
                wrapped_command_str = f"{prefix_parts} {quoted_command}"
                wrapped_command = wrapped_command_str.encode("utf-8")
                if has_newline:
                    wrapped_command += b"\n"

                parsed_entries.append(
                    ParsedCrontabLine(schedule=entry.schedule, command=wrapped_command)
                )
            else:
                # Keep as-is for comments and when document_root is None (may have wrapper prefixes)
                parsed_entries.append(entry)

        if document_root:
            # Isolation active: replace docroot section with new entries
            existing_structure.docroot_sections[document_root] = parsed_entries
        else:
            existing_structure.global_records = parsed_entries

        # Write in new format
        modified_data = write_crontab_structure(existing_structure)
    else:
        # pass through
        modified_data = input_data

    result = run_func(
        [CRONTAB_BIN, "-"],
        input=modified_data,
        capture_output=True,
    )

    # Pass through any output from crontab
    if result.stdout:
        stdout.write(result.stdout)
    if result.stderr:
        stderr.write(result.stderr)

    return result.returncode