#!/opt/cloudlinux/venv/bin/python3 -bb # coding=utf-8 # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT import logging import pwd import subprocess from clcommon.public_hooks.lib import ModifyDomainHook, ModifyUserHook from clcommon.lib.cledition import lve_supported_or_exit from websiteisolation.id_registry import registry_path_by_username log = logging.getLogger(__name__) class DomainLimitsHook(ModifyDomainHook, ModifyUserHook): """ Keep the LVD on-disk state (domains.json + binary id-registry) in sync with control-panel domain and user events so that per-domain limits keep working. """ LVECTL = '/usr/sbin/lvectl' @lve_supported_or_exit def post_modify_domain( self, username, domain, new_domain=None, include_subdomains=None, old_docroot=None, new_docroot=None, **kwargs): if not registry_path_by_username(username): return current_domain = new_domain or domain cmd = [ self.LVECTL, 'regenerate-domains', '--username', username, '--domain', current_domain, ] if new_domain: cmd += ['--old-domain', domain] if old_docroot: cmd += ['--old-docroot', old_docroot] result = subprocess.run(cmd, capture_output=True, check=False) if result.returncode != 0: log.warning( 'regenerate-domains failed (rc=%d): %s', result.returncode, result.stderr.decode(errors='replace').strip(), ) @lve_supported_or_exit def post_delete_domain(self, username, domain, **kwargs): """Drop the registrations the deleted domain leaves behind. Nothing else does: the registry entry is written by root, the panel never learns of it, and the single-domain regenerate path needs a docroot the panel can still resolve. Left alone the entry, its domains.json record and its kernel LVE outlive the domain, and every later `lvectl apply` reports the account as holding a registration the panel no longer lists (CLOS-7467). """ if not registry_path_by_username(username): return cmd = [ self.LVECTL, 'regenerate-domains', '--username', username, '--deleted-domain', domain, ] result = subprocess.run(cmd, capture_output=True, check=False) if result.returncode != 0: log.warning( 'regenerate-domains after domain deletion failed (rc=%d): %s', result.returncode, result.stderr.decode(errors='replace').strip(), ) @lve_supported_or_exit def pre_delete_user(self, username, **kwargs): """Tear down the per-domain state of an account being deleted. Runs before deletion because the marker is keyed by uid, which no longer resolves once the account is gone. Left alone, the marker, lvp and the domain LVEs outlive the account, and the marker keeps counting a dead uid as allowed (CLOS-7502). deny-domain-limits ends with an apply that recreates lve, and listener order is not fixed, so LveLifecycleHook may already have destroyed it; destroy it again here so this hook does not itself leave lve behind. """ if not registry_path_by_username(username): return uid = pwd.getpwnam(username).pw_uid for cmd in ([self.LVECTL, 'deny-domain-limits', username], [self.LVECTL, 'destroy', str(uid)]): result = subprocess.run(cmd, capture_output=True, check=False) if result.returncode != 0: log.warning( '%s before user deletion failed (rc=%d): %s', cmd[1], result.returncode, result.stderr.decode(errors='replace').strip(), ) @lve_supported_or_exit def post_modify_user(self, username, new_name=None, new_owner=None, **kwargs): if new_name is None: return if not registry_path_by_username(new_name): return cmd = [ self.LVECTL, 'regenerate-domains', '--username', new_name, ] result = subprocess.run(cmd, capture_output=True, check=False) if result.returncode != 0: log.warning( 'regenerate-domains after user rename failed (rc=%d): %s', result.returncode, result.stderr.decode(errors='replace').strip(), )