PHP 8.3.31
Preview: post_modify_domain.py Size: 6.10 KB
/proc/thread-self/root/opt/cloudlinux/venv/lib/python3.11/site-packages/clcommon/public_hooks/bin/post_modify_domain.py

#!/opt/cloudlinux/venv/bin/python3 -bb
# -*- 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
#
"""
Domain POST modification hook.
This script must be called AFTER different domain-related
changes in control panel. See --help and docs for detailed
description of actions and parameters.
"""
import argparse
import os
import re
import sys

from clcommon.public_hooks.lib import ModifyDomainHook
from clcommon.public_hooks import setup_logger_and_sentry
# Use the shared username regex (allows dots) — bugbot finding a39cebf7
from clcommon.public_hooks.lib.helpers import _VALID_NAME_RE as _VALID_USERNAME_RE


# Domain-name validator. Unicode-aware: each label may contain letters and
# digits in any script (so IDN domains like `реселлер41.com` or `日本.jp`
# pass through). Plesk fires `phys_hosting_create` with the Unicode form,
# not the Punycode form (CLOS-4470); the prior strict-ASCII regex stalled
# `_wait_for_plesk_hooks` on i18n test fixtures because the dispatch to
# this script bailed before logging `ended post_create_domain ... <domain>`.
#
# Structural rules per RFC 1035/5890:
#   - 1..63 chars per label
#   - label starts and ends with a letter/digit (no leading/trailing `-`)
#   - labels joined by literal `.`
# Total length bound (1..253) is checked separately by `_validate_domain`.
#
# Forbidden chars (shell metas, path separators, control chars, whitespace)
# are naturally excluded because they're neither word chars nor `-`.
_DOMAIN_LABEL = r'[^\W_](?:[\w-]{0,61}[^\W_])?'
_VALID_DOMAIN_RE = re.compile(rf'\A{_DOMAIN_LABEL}(?:\.{_DOMAIN_LABEL})*\Z')


def _validate_username(value):
    if not value or len(value) > 32 or not _VALID_USERNAME_RE.fullmatch(value):
        sys.exit(f'Invalid username: {value!r}')


def _validate_domain(value):
    if not value or len(value) > 253 or not _VALID_DOMAIN_RE.fullmatch(value):
        sys.exit(f'Invalid domain: {value!r}')


def _validate_docroot(value):
    # NOTE(CLOS-4280): the AI-generated hardening in b525447 also rejected
    # non-absolute paths, but the cPanel SubDomain::changedocroot post-hook
    # legitimately forwards `dir` as a path relative to the user's home
    # (see cpanel_postchangesubdomaindocroot_main). Requiring isabs() here
    # broke the crontab-migration chain wired up in CLOS-3765 and caused
    # clsolo.python_tests.test_isolated_domains_hooks::test_crontab_migration_and_cleanup
    # to fail (Tests-all #7057). Rejecting NUL and any `..` segment is
    # sufficient: this script never opens the path — it only forwards it
    # to listeners that resolve docroots against the user's home.
    if not value or '\0' in value or '..' in value.split(os.sep):
        sys.exit(f'Invalid docroot path: {value!r}')


if __name__ == '__main__':
    setup_logger_and_sentry()

    parser = argparse.ArgumentParser(description=__doc__)
    subparsers = parser.add_subparsers(dest='command')

    create_domain = subparsers.add_parser(
        'create', help="[NOT FOR VENDORS PANELS INTEGRATION] Call when domain record is created.")
    create_domain.add_argument('--username', '-u', required=True, help='User that owns domain.')
    create_domain.add_argument('--domain', '-ds', required=True,
                               help='Domain name e.g.: `userdomain.com`')

    modify_domain = subparsers.add_parser(
        'modify', help="Call when domain record is updated (e.g "
                       "all sites that have worked on old domain "
                       "now work on a new one).")
    modify_domain.add_argument('--username', '-u', required=True, help='User that owns domain.')
    modify_domain.add_argument('--domain', '-ds', required=True,
                               help='Domain name before rename, e.g.: `userdomain.com`')
    modify_domain.add_argument('--new-domain', '-dt',
                               help='Domain name after rename, e.g.: `userdomain2.eu`')
    # TODO: how to make this better?'
    modify_domain.add_argument(
        '--include-subdomains', required=False, default=False, action='store_true',
        help='If set, we will also process all domains '
             'that end with domain name (subdomains). '
             'E.g. when renaming olddomain.com -> newdomain.eu '
             'we will also rename sub.olddomain.com to sub.newdomain.eu')
    modify_domain.add_argument('--old-docroot',
                               help='Old document root path for the domain.')
    modify_domain.add_argument('--new-docroot',
                               help='New document root path for the domain.')

    modify_domain = subparsers.add_parser(
        'delete', help="[NOT FOR INTEGRATED CONTROL PANELS] Call when domain record is deleted.")
    modify_domain.add_argument('--username', '-u', required=True, help='User that owns domain.')
    modify_domain.add_argument('--domain', '-ds', required=True,
                               help='Domain name before rename, e.g.: `userdomain.com`')

    args = parser.parse_args()

    hook = ModifyDomainHook()
    if args.command == 'create':
        _validate_username(args.username)
        _validate_domain(args.domain)
        hook.post_create_domain(
            args.username, args.domain)
    elif args.command == 'modify':
        _validate_username(args.username)
        _validate_domain(args.domain)
        if args.new_domain:
            _validate_domain(args.new_domain)
        if args.old_docroot:
            _validate_docroot(args.old_docroot)
        if args.new_docroot:
            _validate_docroot(args.new_docroot)
        kwargs = {'include_subdomains': args.include_subdomains}
        if args.old_docroot:
            kwargs['old_docroot'] = args.old_docroot
        if args.new_docroot:
            kwargs['new_docroot'] = args.new_docroot
        hook.post_modify_domain(
            args.username, args.domain, args.new_domain, **kwargs)
    elif args.command == 'delete':
        _validate_username(args.username)
        _validate_domain(args.domain)
        hook.post_delete_domain(args.username, args.domain)
    else:
        raise NotImplementedError

Directory Contents

Dirs: 1 × Files: 5

Name Size Perms Modified Actions
- drwxr-xr-x 2026-06-23 06:30:22
Edit Download
1.42 KB lrwxr-xr-x 2026-06-03 13:38:12
Edit Download
6.10 KB lrwxr-xr-x 2026-06-03 13:38:12
Edit Download
1.65 KB lrwxr-xr-x 2026-06-03 13:38:12
Edit Download
3.56 KB lrwxr-xr-x 2026-06-03 13:38:12
Edit Download
1.24 KB lrwxr-xr-x 2026-06-03 13:38:12
Edit Download

If ZipArchive is unavailable, a .tar will be created (no compression).