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