403Webshell
Server IP : 142.11.234.102  /  Your IP : 216.73.217.33
Web Server : Apache
System : Linux dal-shared-66.hostwindsdns.com 4.18.0-513.24.1.lve.1.el8.x86_64 #1 SMP Thu May 9 15:10:09 UTC 2024 x86_64
User : krnuyqrm ( 1183)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /opt/cloudlinux/venv/lib/python3.11/site-packages/clcagefslib/webisolation/crontab/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /opt/cloudlinux/venv/lib/python3.11/site-packages/clcagefslib/webisolation/crontab/parser.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
#
"""Parsing and serialization functions for crontab entries."""

from .constants import (
    CRON_ENV_ASSIGNMENT_PATTERN,
    CRON_NICKNAME_PATTERN,
    CRON_SCHEDULE_PATTERN,
    WEBSITE_CRON_BEGIN_PATTERN,
    WEBSITE_CRON_END_PATTERN,
)
from .structure import (
    BeginWebsite,
    CommentLine,
    CrontabEntry,
    CrontabStructure,
    EndWebsite,
    EnvAssignmentLine,
    ParsedCrontabLine,
)


# Defense-in-depth reset lines emitted at the top of every docroot section
# whenever the global scope carries any env-assignment. crond honours
# env-assignment lines in textual order (per crontab(5)) for all following
# jobs, so a user-supplied `SHELL=/path/to/fake` in global_records would apply
# to the wrapped docroot jobs below it and let a caller-controlled interpreter
# defeat the isolation wrapper. Reset the interpreter-selection knobs to safe
# defaults at the top of each wrapped section so the global assignment cannot
# reach the docroot jobs textually below. See F-31 / CLOS-5418.
_DOCROOT_ENV_RESET_LINES: tuple[bytes, ...] = (
    b"SHELL=/bin/bash\n",
    b"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin\n",
    b"BASH_ENV=\n",
)


def parse_crontab_line(line: bytes) -> CrontabEntry:
    """
    Parse a crontab line into a cron entry, comment, or website marker.

    Args:
        line: A single line from crontab output (bytes)

    """
    stripped = line.strip()

    # Check for website cron begin marker
    begin_match = WEBSITE_CRON_BEGIN_PATTERN.match(stripped)
    if begin_match:
        docroot_bytes = begin_match.group(1).strip()
        docroot = docroot_bytes.decode("utf-8", errors="replace")
        return BeginWebsite(docroot=docroot)

    # Check for website cron end marker
    if WEBSITE_CRON_END_PATTERN.match(stripped):
        return EndWebsite()

    # Handle empty lines and comments
    if not stripped:
        return CommentLine(content=line)
    if stripped.startswith(b"#"):
        return CommentLine(content=line)

    # Environment-assignment line (`name = value`) — must be classified
    # distinctly from comments so the wrap step can drop these in per-site
    # sections; otherwise crond honours them and a user-supplied `SHELL=`
    # would run before the isolation wrapper for following job lines.
    if CRON_ENV_ASSIGNMENT_PATTERN.match(stripped):
        return EnvAssignmentLine(content=line)

    # Nickname schedule (`@hourly command`, `@reboot command`, ...) — a
    # single-token cron(5) schedule form the 5-field CRON_SCHEDULE_PATTERN
    # below never matches. Without this branch such lines fall through to
    # CommentLine and skip the wrap loop in process_save, letting a
    # site-scoped caller plant a nickname job that crond executes without
    # the cagefs_enter_site confinement. Classify as ParsedCrontabLine so
    # the wrap loop treats it like any other schedule/command pair.
    nickname_match = CRON_NICKNAME_PATTERN.match(stripped)
    if nickname_match:
        schedule = nickname_match.group(1)
        command = nickname_match.group(2)
        if line.endswith(b"\n"):
            command += b"\n"
        return ParsedCrontabLine(schedule=schedule, command=command)

    # Try to match cron schedule + command
    schedule_match = CRON_SCHEDULE_PATTERN.match(stripped)
    if not schedule_match:
        return CommentLine(content=line)

    schedule = schedule_match.group(1)
    command = schedule_match.group(2)

    if line.endswith(b"\n"):
        command += b"\n"

    return ParsedCrontabLine(
        schedule=schedule,
        command=command,
    )


def parse_crontab_structure(input_data: bytes) -> CrontabStructure:
    """
    Parse crontab file into structured format with user records and docroot sections.

    Supports both old format (with isolation wrapper prefixes) and new format
    (with section markers).

    Args:
        input_data: bytes containing crontab entries

    Returns:
        CrontabStructure: Structure containing global_records and docroot_sections
    """

    lines = input_data.splitlines(keepends=True)

    crontab_records: dict[
        str | None,
        list[CrontabEntry],
    ] = {}
    current_docroot: str | None = None

    for line in lines:
        parsed = parse_crontab_line(line)

        if isinstance(parsed, BeginWebsite):
            current_docroot = parsed.docroot
            continue

        if isinstance(parsed, EndWebsite):
            current_docroot = None
            continue

        crontab_records.setdefault(current_docroot, []).append(parsed)

    global_records = crontab_records.pop(None, [])
    return CrontabStructure(
        global_records=global_records,
        docroot_sections=crontab_records,
    )


def write_crontab_structure(structure: CrontabStructure) -> bytes:
    """
    Write crontab structure to bytes in new format.

    Format:
    - User records at the top
    - Then docroot sections with markers:
      ## WEBSITE CRON BEGIN /path/to/docroot
      ... entries ...
      ## WEBSITE CRON END

    Args:
        structure: CrontabStructure with user_records and docroot_sections

    Returns:
        bytes: Crontab content in new format
    """
    result_parts = entries_to_str_list(structure.global_records)

    # F-31: only pay the reset when a global env-assignment is actually
    # present to bleed. Without one, crond has nothing to carry across the
    # section marker and the reset would be visible noise inside `crontab -l`
    # for docroot cages.
    needs_env_reset = any(
        isinstance(entry, EnvAssignmentLine) for entry in structure.global_records
    )

    # Add docroot sections (preserve insertion order)
    for docroot in structure.docroot_sections.keys():
        # Strip any EnvAssignmentLine entries from the section body: process_save
        # already refuses to persist them from tenant input, and stripping here
        # keeps the serializer idempotent — otherwise a section that we
        # ourselves reset-prefixed on a previous write would re-parse those
        # reset lines as section EnvAssignmentLine entries, and each subsequent
        # write would prepend another set, growing without bound.
        entries = [
            entry
            for entry in structure.docroot_sections[docroot]
            if not isinstance(entry, EnvAssignmentLine)
        ]
        if not entries:
            continue
        if '\n' in docroot or '\r' in docroot:
            raise ValueError(f'Invalid docroot: {docroot!r}')
        result_parts.append(f"## WEBSITE CRON BEGIN {docroot}\n".encode("utf-8"))
        if needs_env_reset:
            result_parts.extend(_DOCROOT_ENV_RESET_LINES)
        result_parts.extend(entries_to_str_list(entries))
        result_parts.append(b"## WEBSITE CRON END\n")

    return b"".join(result_parts)


def entries_to_str_list(entries: list[CrontabEntry], without_wrapper: bool = False) -> list[bytes]:
    """
    Converts parsed crontab entries into a string list.
    """
    lines = []
    for entry in entries:
        if isinstance(entry, ParsedCrontabLine):
            line = (
                entry.schedule
                + b" "
                + (entry.get_clean_command() if without_wrapper else entry.command)
            )
            if not line.endswith(b"\n"):
                line += b"\n"
            lines.append(line)
        elif isinstance(entry, CommentLine):
            lines.append(entry.content)
        elif isinstance(entry, EnvAssignmentLine):
            lines.append(entry.content)
    return lines

Youez - 2016 - github.com/yon3zu
LinuXploit