Hacked By AnonymousFox
Current Path : /usr/share/cagefs/ |
|
Current File : //usr/share/cagefs/cagefsctl.py |
#!/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
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from future import standard_library
from typing import Dict, Optional, List
standard_library.install_aliases()
from builtins import *
from future.utils import native_str
import copy
import errno
import os
import locale
import psutil
import grp
import sys
import configparser
import getopt
import string
import shutil
import subprocess
import secureio
import stat
import time
import fcntl
import random
import struct
import signal
import pickle
import cagefs_da_lib
import re
import yaml
import glob
from collections import defaultdict
from enum import Enum
from cldetectlib import is_plesk, is_cpanel
from clcommon.utils import (
ExternalProgramFailed,
create_symlink,
is_socket_file,
is_may_detach_mounts_enabled,
mod_makedirs,
)
from clcommon.clproc import ProcLve
from clcommon import ClPwd, reload_processes, clconfpars, clcaptain
from clcommon.clfunc import unicodeify, byteify
from cagefslib import (
stripslash,
CageFSException,
SYSTEMD_JOURNAL_SOCKET,
is_new_syslog_socket_used,
relative_symlink,
is_running_without_lve,
)
from logs import logger
import cagefshooks
import tempfile
import unshare
import cagefs_without_lve_lib
import cagefs_universal_hook_lib
LVECTL = '/usr/sbin/lvectl'
if is_running_without_lve():
# mock lvectl, because it will not be used in containers probably
LVECTL = '/bin/true'
UMOUNT = '/bin/umount'
MOUNT = '/bin/mount'
LVE_UMOUNT = "/bin/lve_umount"
BASEDIR = '/var/cagefs'
BASEDIR_UID = '/var/cagefs.uid'
SKELETON = '/usr/share/cagefs-skeleton'
SKELETON_NAME = '/cagefs-skeleton/'
LIBDIR = '/usr/share/cagefs'
INIPREFIX = '/etc/cagefs/'
MP_PREFIX = '/usr/share/cagefs/'
CONFIG_DIR = '/etc/cagefs/conf.d/'
ETC_MPFILE = INIPREFIX + 'cagefs.mp'
PREV_MPFILE = MP_PREFIX + 'cagefs.mp.prev'
LOCKNAME = '/usr/share/cagefs/.lock'
FUSE_WHITE_LIST = '/etc/cagefs/etc.safe/etc.system'
FUSE_SAFE_LIST = '/etc/cagefs/etc.safe/etc.safe'
FUSE_DIR = '/etc/cagefs/etc.safe'
FILES_LIST = '/usr/share/cagefs/skeleton.files.list'
LIBS_LIST = '/usr/share/cagefs/skeleton.libs.list'
PASSWD_CACHE = '/usr/share/cagefs/passwd.cache'
WORK_CONFIG_DIR = "/usr/share/cagefs/conf.d"
EXCLUDE_PATH = '/etc/cagefs/exclude'
EXCLUDE_SAVE_PATH = '/usr/share/cagefs/exclude'
MIN_UID = 500
MIN_UID_FILENAME = "/etc/cagefs/cagefs.min.uid"
SERVICE_CAGEFS_LOCK = "/var/lock/subsys/cagefs"
DISABLE_ETCFS = "/etc/cagefs/etc.safe/disable.etcfs"
DIFF = "/usr/bin/diff"
PROXYEXEC_SOCKET_DIR_OLD = "/var/run/proxyexec/cagefs.sock"
PROXYEXEC_SOCKET_DIR = "/var/lib/proxyexec/cagefs.sock"
BLACK_LIST_FILE = "/etc/cagefs/black.list"
PLUGIN_STATE = "/usr/share/cagefs-plugins/install-cagefs-plugin.py"
EMPTY_DIR = '/usr/share/cagefs/.cagefs.empty'
STD_PACKAGES_FILE = "/usr/share/cagefs/exclude.packages"
PROXY_COMMANDS = "/etc/cagefs/proxy.commands"
REMOUNT_FLAG = '/usr/share/cagefs/need.remount'
INFO_LOG_FILE = "/var/log/cagefs.log"
LICENSE_TIMESTAMP_FILE = '/var/lve/lveinfo.ver'
SELECTOR_CONF_DIR_TEMPLATE = '/usr/share/l.v.e-manager/cl.{}'
DEV_SHM_OPTIONS = "/etc/cagefs/dev.shm.options"
DEBUG_CAGEFS_MARKER = '/etc/cagefs/enabled_debug'
disabled_dir = INIPREFIX + 'users.disabled'
enabled_dir = INIPREFIX + 'users.enabled'
SKELETON_INITIALIZED = 'Initialized'
SKELETON_NOT_INITIALIZED = 'Not initialized'
kernel_header = os.uname().release
# Default RPM packages
class DefaultPackages(Enum):
"""Default packages, used in cagefs --init"""
common_packages = [
'tcl',
'cpp',
'gcc',
'automake',
'autoconf',
'm4',
'mc',
'ghostscript',
'fontconfig',
'aspell',
'aspell-en',
'hunspell',
'coreutils',
'python3-virtualenv',
'libxml2',
'recode',
'crypto-policies',
'snmptrapd',
'unixodbc',
'openssl',
'alt-libicu',
'enchant',
'curl',
'cpanel-git', # git +
'git',
]
ubuntu = [
'imagemagick',
'libmagick++-dev',
'perlmagick',
'expat',
'libexpat1-dev',
'libltdl7',
'libnss3',
'build-essential',
f'linux-headers-{kernel_header}',
'gfortran',
'lib32gcc-10-dev',
'g++',
'libtext-pdf-perl',
'libedit2',
'hunspell-en-us',
'libcogl-pango-dev',
'python3.8',
'libc-client2007e',
'libodbc1',
'libmhash2',
'libmcrypt4',
'libxslt1.1',
'libtidy5deb1',
'libicu66',
'libicu-dev',
'tmpreaper',
'libgpg-error0',
'postgresql',
'postgresql-contrib',
'libpng-dev',
'libgmp3-dev',
'libpam-modules',
'bzip2',
'libpam-cracklib',
'ncdu',
'libidn11',
'libc-client2007e',
'db5.3-util',
'libncurses6',
'slapd',
'libxpm4',
'libgcrypt20',
'libsasl2-2',
'zlib1g',
'snmpd',
'snmp',
'libsnmp-dev',
'libmm-dev',
'libfreetype6',
'libfreetype6-dev',
'libssh2-1',
'geoip-database',
'ffmpeg',
'dnsutils',
'libgs9',
'libgs-dev',
'libgs9-common',
]
centos = [
'ImageMagick',
'ImageMagick-c++',
'ImageMagick-c++-devel',
'ImageMagick-devel',
'ImageMagick-perl',
'cloudlinux-ImageMagick',
'cloudlinux-ImageMagick-c++',
'cloudlinux-ImageMagick-c++-devel',
'cloudlinux-ImageMagick-devel',
'expat',
'expat-devel',
'libtool-ltdl',
'nss',
'nss-softokn', # -
'compat-glibc-headers', # not exist in centos
'glibc-headers',
'kernel-headers',
'compat-libgcc-296', # -
'gcc-gfortran',
'compat-gcc-34-c++', # -
'compat-gcc-34-g77', # -
'libgcc',
'gcc-c++',
'compat-gcc-34', # -
'redhat-rpm-config',
'fontpackages-filesystem', # -
'perl-Text-PDF',
'pdf-tools', # -
'perl-PDF-Reuse', # -
'libedit',
'hunspell-en',
'git-core',
'pango',
'mktemp',
'scl-utils', # -
'python36', # + python3.8
'libc-client-2007e',
'unixODBC-libs',
'mhash',
'tcp_wrappers', # -
'compat-libstdc++', # -
'libmcrypt',
'libxslt',
'libtidy',
'libicu',
'libicu-devel',
'tmpwatch',
'net-snmp',
'libgpg-error',
'postgresql-libs',
'libpng',
'gmp',
'pam',
'bzip2-libs',
'cracklib',
'ncurses',
'libidn',
'libc-client-2004g',
'db4',
'ncurses-libs',
'openldap',
'libXpm',
'libgcrypt',
'cyrus-sasl-lib',
'zlib',
'net-snmp-libs',
'libmm',
'freetype',
'freetype-devel',
'curl-devel', # -
'libssh2',
'GeoIP',
'cyrus-sasl',
'ffmpeg-libs',
'termcap', # -
'bind-utils',
'libgs',
'libgs-devel'
]
if 'ubuntu' in os.uname().version.lower():
STD_PACKAGES = DefaultPackages.common_packages.value + DefaultPackages.ubuntu.value
else:
STD_PACKAGES = DefaultPackages.common_packages.value + DefaultPackages.centos.value
# This line is changed in %install section of securelve.spec
cagefs_version = '7.6.12-1.el8.cloudlinux'
sys.path.append(LIBDIR)
import cagefslib
import repair_homes
from signals_handlers import sigterm_check
from cagefslib import is_ea4_enabled
cagefslib.FUSE_WHITE_LIST = FUSE_WHITE_LIST
cagefslib.FUSE_SAFE_LIST = FUSE_SAFE_LIST
cagefslib.SKELETON = SKELETON
# List of spamassasin dirs for add to Cagefs
SPAMASSASSIN_DIRS_FOR_CAGEFS = ['/usr/local/cpanel/3rdparty/bin', '/var/lib/spamassassin']
def save_passwd_cache(pw=None):
if pw == None:
pw = secureio.get_pwd_dict()
try:
umask_saved = os.umask(0o77)
pf = open(PASSWD_CACHE, 'wb')
# byteify because python2 uses bytes
# and we in python3 use unicode
pickle.dump(byteify(pw), pf, protocol=2)
pf.close()
os.umask(umask_saved)
os.chmod(PASSWD_CACHE, 0o600)
except Exception as err:
secureio.print_error("saving", PASSWD_CACHE, '-', err)
def load_passwd_cache():
pw = {}
if os.path.isfile(PASSWD_CACHE):
try:
pf = open(PASSWD_CACHE, 'rb')
# unicodeify because python2 saves bytes
# into this file and we use unicode in python3
pw = unicodeify(pickle.load(pf, encoding=locale.getpreferredencoding()))
pf.close()
except Exception as err:
secureio.print_error("loading", PASSWD_CACHE, '-', err)
return pw
# Returns list of users whose passwd entry has been changed
def get_modified_users(pw_old=None, pw_new=None):
if pw_old == None:
pw_old = load_passwd_cache()
if pw_new == None:
pw_new = secureio.get_pwd_dict()
users = []
for user in pw_new:
try:
if pw_new[user] != pw_old[user]:
users.append(user)
except KeyError:
users.append(user)
continue
save_passwd_cache(pw_new)
return users
def create_empty_dir():
sigterm_check()
if not os.path.lexists(EMPTY_DIR):
try:
mod_makedirs(EMPTY_DIR, 0o755)
except (IOError, OSError):
secureio.logging('Error: failed to create ' + EMPTY_DIR, SILENT, 1)
sys.exit(1)
else:
try:
os.chmod(EMPTY_DIR, 0o755)
except (IOError, OSError):
secureio.logging('Error: failed to set permissions to ' + EMPTY_DIR, SILENT, 1)
sys.exit(1)
def mount_empty_dir(path):
dest = SKELETON+path
# parent dir exists ?
if os.path.isdir(dest):
# mount empty dir over parent dir
for cmd in ([MOUNT, "-n", "-o", "nosuid", "--bind", EMPTY_DIR, dest], [MOUNT, "-n", "-o", "remount,ro,nosuid,bind", EMPTY_DIR, dest]):
ret = subprocess.call(cmd)
if ret != 0:
secureio.print_error("failed to mount", EMPTY_DIR, '->', dest)
sys.exit(1)
def etcfs_is_disabled():
return os.path.exists(DISABLE_ETCFS)
def get_etc_version(path):
fpath = path + cagefslib.ETC_VERSION
if os.path.isfile(fpath):
try:
f = open(fpath, "r")
ver = f.readline()
f.close()
except (IOError, OSError):
return 0
ver = ver.rstrip()
try:
# return version
return int(ver)
except ValueError:
# return 0 as version
return 0
else:
# return 0 as version
return 0
def set_etc_version(path, ver):
fpath = path + cagefslib.ETC_VERSION
umask_saved = os.umask(0o77)
try:
f = open(fpath, "w")
f.write("%d\n" % ver)
f.close()
except (IOError, OSError):
secureio.logging('Error: failed to write ' + fpath, SILENT, 1)
sys.exit(1)
os.umask(umask_saved)
def copy_etc_version(src, dst):
srcpath = src + cagefslib.ETC_VERSION
dstpath = dst + cagefslib.ETC_VERSION
try:
# copy file and metadata
shutil.copy2(srcpath, dstpath)
except (OSError, IOError, shutil.Error):
pass
LOGFILE = "/var/log/cagefs-update.log"
SILENT = 0
VERBOSE = 0
def remove_log_file():
if os.path.isdir(LOGFILE):
secureio.print_error(LOGFILE, "is a directory")
sys.exit(1)
elif os.path.isfile(LOGFILE):
os.remove(LOGFILE)
MPDIRS = ['/var/lib/mysql', '/var/lib/dav', '/var/www/cgi-bin', '/opt', '/var/www/php-bin', '/dev/shm', '/var/www/html',
'/var/run/pgsql', '/var/passenger', '/dev/pts', '/usr/local/apache/domlogs', '/proc', PROXYEXEC_SOCKET_DIR,
'/var/spool/at', '/var/run/dbus', '/usr/local/cpanel/var', '/var/run/nscd', SELECTOR_CONF_DIR_TEMPLATE.format('nodejs'),
SELECTOR_CONF_DIR_TEMPLATE.format('python')]
MYSQL_SOCK_DIR='/var/lib/mysql'
MYSQL_SOCK='/var/lib/mysql/mysql.sock'
LITESPEED='/usr/local/lsws'
READ_ONLY_MOUNTS = [
'/lib',
'/usr/lib',
'/lib64',
'/usr/lib64',
'/usr/include',
'/usr/share/locale',
'/usr/share/terminfo',
'/usr/share/zoneinfo',
'/usr/share/vim',
'/usr/local/lib/perl5',
'/usr/local/lib/php',
'/usr/local/cpanel/etc',
'/usr/local/cpanel/Cpanel',
'/usr/local/cpanel/3rdparty/perl',
'/usr/local/cpanel/3rdparty/lib',
'/usr/local/cpanel/3rdparty/lib64',
'/usr/local/cpanel/3rdparty/share',
'/usr/local/cpanel/3rdparty/php',
'/usr/local/cpanel/install',
'/usr/local/cpanel/lib',
'/usr/local/cpanel/htdocs',
'/usr/local/cpanel/shared',
'/usr/local/cpanel/whostmgr',
'/usr/local/cpanel/share',
'/usr/local/cpanel/php',
'/usr/local/cpanel/libexec',
'/usr/local/cpanel/lang',
'/usr/local/cpanel/cgi-priv',
'/usr/local/cpanel/cpaddons',
'/usr/local/cpanel/Whostmgr',
'/usr/local/cpanel/img-sys',
'/usr/local/cpanel/modules-install',
'/usr/local/cpanel/locale',
'/usr/local/cpanel/scripts',
'/usr/local/cpanel/sbin',
'/usr/local/cpanel/base',
'/usr/local/cpanel/hooks',
'/usr/java',
'/usr/saase',
'/usr/local/easy',
'/var/cpanel/ea4',
'/usr/share/man',
]
SPLITTED_MOUNTS = ['/var/cpanel/userdata']
SPLITTED_UID_MOUNTS = ['/var/clwpos/uids', '/usr/share/alt-php-xray-tasks']
def is_dev_shm_isolated():
"""
Return True when /dev/shm isolation is enabled
see CAG-954 for details
"""
return os.path.isfile(DEV_SHM_OPTIONS)
def is_outside_mp_path(path):
path = cagefslib.addslash(path)
for tmpdir in MPDIRS:
if tmpdir[0] == '/' and path.startswith(tmpdir+'/'):
return False
return True
def save_cagefs_mp_backup():
try:
shutil.copyfile(ETC_MPFILE, PREV_MPFILE)
os.chmod(PREV_MPFILE, 0o600)
except:
secureio.print_error('copying', ETC_MPFILE, 'to', PREV_MPFILE)
def create_mp(force, exit_on_error=False):
if not force and os.path.isfile(ETC_MPFILE):
print(ETC_MPFILE, "exists")
return
umask_saved = os.umask(0o22)
f = open(ETC_MPFILE, 'w')
f.write('# Lines, which start with "/", specify mounts, that are common for all users:\n')
for tmpdir in MPDIRS:
if is_plesk() and tmpdir == "/var/www/cgi-bin":
continue
if tmpdir == '/dev/shm' and is_dev_shm_isolated():
# CAG-954: do not add /dev/shm mount to cagefs.mp when /dev/shm isolation is enabled
continue
if tmpdir[0] == '/' and os.path.isdir(tmpdir):
f.write(tmpdir)
f.write('\n')
# /var/run/postgres should be used on CL5, CL6; /var/run/postgresql should be used on CL7
# for details plz see CAG-593, CAG-528
from cagefsreconfigure import POSTGRES_CL7_FOLDER, POSTGRES_CONF, DEFAULT_POSTGRES_FOLDER
if os.path.isdir(POSTGRES_CONF):
if os.path.isdir(DEFAULT_POSTGRES_FOLDER): # CL5, CL6
f.write('%s\n' % DEFAULT_POSTGRES_FOLDER)
elif os.path.isdir(POSTGRES_CL7_FOLDER): # should never happen
f.write('%s\n' % POSTGRES_CL7_FOLDER)
else:
if os.path.isdir(POSTGRES_CL7_FOLDER): # CL7
f.write('%s\n' % POSTGRES_CL7_FOLDER)
elif os.path.isdir(DEFAULT_POSTGRES_FOLDER): # should never happen
f.write('%s\n' % DEFAULT_POSTGRES_FOLDER)
# detect if MYSQL socket is outside of cagefs
if os.path.realpath(MYSQL_SOCK) != MYSQL_SOCK:
rpath = os.path.realpath(MYSQL_SOCK)
rdir = os.path.dirname(rpath)
if rdir == '/tmp':
print('Warning: MySQL socket is located in /tmp directory: path', MYSQL_SOCK, 'points to', rpath)
print('This is not compatible with CageFS.')
print('Please move socket outside of /tmp directory by changing socket= directive in /etc/my.cnf file and restart MySQL.')
print('Then execute cagefsctl --create-mp')
print('Default socket location -', MYSQL_SOCK)
if exit_on_error:
f.close()
os.unlink(ETC_MPFILE)
sys.exit(1)
elif rdir != '/' and os.path.isdir(rdir) and is_outside_mp_path(rdir):
f.write(rdir)
f.write('\n')
if os.path.isdir(LITESPEED):
f.write(LITESPEED)
f.write('\n')
f.write('# You can add personal (individual) mounts for users, like below.\n')
f.write('# Please, start line with "@" symbol, and then specify path and permissions (comma separated).\n')
f.write('# These directories will be virtualized for each user.\n')
f.write('@/var/spool/cron,700\n')
f.write('@/var/run/screen,777\n')
f.write('@'+cagefslib.VAR_RUN_CAGEFS+',700\n')
f.write('@/var/cache/php-eaccelerator,777\n')
f.write('@/var/php/apm/db,777\n')
f.write('# Please add exclamation sign at the beginning of the line if you want to mount path read-only, like below.\n')
for tmpdir in READ_ONLY_MOUNTS:
if tmpdir[0] == '/' and os.path.isdir(tmpdir):
f.write('!'+tmpdir+'\n')
f.write('# Please add "%" sign at the beginning of the line if you want to "split" mount by username, like below.\n')
for tmpdir in SPLITTED_MOUNTS:
if tmpdir[0] == '/' and os.path.isdir(tmpdir):
f.write('%'+tmpdir+'\n')
f.write('# Please add "*" sign at the beginning of the line if you want to "split" mount by UID, like below.\n')
for tmpdir in SPLITTED_UID_MOUNTS:
if tmpdir[0] == '/' and os.path.isdir(tmpdir):
f.write('*'+tmpdir+'\n')
if is_cpanel():
# Setup the spamassasin directories to CageFs on cPanel
f.write('\n')
#SPAMASSASSIN_DIRS_FOR_CAGEFS = ['/usr/local/cpanel/3rdparty/bin', '/var/lib/spamassassin']
from cagefsreconfigure import BOX_TRAPPER_DIR
for line in SPAMASSASSIN_DIRS_FOR_CAGEFS:
if os.path.isdir(line):
f.write('!'+line+'\n')
if os.path.isdir(BOX_TRAPPER_DIR):
f.write('!'+BOX_TRAPPER_DIR+'\n')
f.write('\n')
f.close()
os.umask(umask_saved)
os.chmod(ETC_MPFILE, 0o600)
add_mounts_for_php_selector()
add_mounts_for_ea_php_sessions()
if is_plesk():
from cagefsreconfigure import add_php_session_dir_plesk
add_php_session_dir_plesk()
# copy cagefs.mp to cagefs.mp.prev (in order to detect changes of cagefs.mp file in the future)
if not os.path.isfile(PREV_MPFILE):
save_cagefs_mp_backup()
def remove_mount_points(mounts, old_mounts, base_path = SKELETON):
# Remove unused mount points
for mount in old_mounts:
if mount not in mounts:
mount = mount.rstrip()
try:
os.removedirs(base_path + mount)
except (OSError, IOError):
pass
def remove_unused_mount_points():
# Previous mpfile exists ?
if os.path.isfile(PREV_MPFILE):
# Read previous mount points
mp_config_old = MountpointConfig(path=PREV_MPFILE,
skip_errors=True,
skip_cpanel_check=True)
# Reading of old mpfile is successful ?
if mp_config_old.common_mounts:
# Read current mp-file
mp_config = MountpointConfig()
# Remove common mount points which are not used (from cagefs-skeleton)
remove_mount_points(mp_config.common_mounts, mp_config_old.common_mounts)
# Remove personal mount points which are not used (from cagefs-skeleton)
remove_mount_points(mp_config.personal_mounts, mp_config_old.personal_mounts)
# Remove splitted by name mount points which are not used (from cagefs-skeleton)
remove_mount_points(mp_config.splitted_by_username_mounts,
mp_config_old.splitted_by_username_mounts)
# Remove splitted by UID mount points which are not used (from cagefs-skeleton)
remove_mount_points(mp_config.splitted_by_uid_mounts,
mp_config_old.splitted_by_uid_mounts)
# Remove personal mount points (which are not used) from home dirs
pw = secureio.clpwd.get_user_dict()
for user in pw:
line = pw[user]
homepath = cagefslib.stripslash(line.pw_dir)
cagefspath = homepath + '/.cagefs'
secureio.set_user_perm(line.pw_uid, line.pw_gid)
remove_mount_points(mp_config.personal_mounts,
mp_config_old.personal_mounts,
cagefspath)
secureio.set_root_perm()
save_cagefs_mp_backup()
def dirinjail(testdir, jail):
if (testdir[-1]!= '/'):
testdir = testdir+'/'
return (jail == testdir[:len(jail)])
def user_exists(user):
return user in secureio.clpwd.get_user_dict()
save_postfix = '.save'
def get_user_mode():
if os.path.isdir(disabled_dir):
if os.path.isdir(enabled_dir):
return 'Error'
else:
return 'Enable All'
elif os.path.isdir(enabled_dir):
return 'Disable All'
else:
return 'Not Initialized'
def check_mode_error(mode = None, raise_exception = False):
if mode == None:
mode = get_user_mode()
if mode == 'Error':
if raise_exception:
raise CageFSException
secureio.print_error('both directories', enabled_dir, 'and', disabled_dir, 'exist.\n',
'Please, run one of the following commands:\n',
sys.argv[0], '--enable-all\n',
'to enable all users, except specified in', disabled_dir,'\n',
'or\n',
sys.argv[0], '--disable-all\n',
'to disable all users, except specified in', enabled_dir)
sys.exit(1)
elif mode == 'Not Initialized':
if raise_exception:
raise CageFSException
if os.path.isdir(SKELETON+'/bin'):
secureio.print_error('mode has not been selected yet.\n',
'Please, run one of the following commands:\n',
sys.argv[0], '--enable-all\n',
'to enable all users, except specified in', disabled_dir,
'\nor\n',
sys.argv[0], '--disable-all\n',
'to disable all users, except specified in', enabled_dir)
else:
secureio.print_error('CageFS is not initialized. Use "'+sys.argv[0]+' --init" to initialize CageFS')
sys.exit(1)
def cagefs_is_enabled():
return os.path.isdir(disabled_dir) or os.path.isdir(enabled_dir)
def save_dir_exists():
return os.path.isdir(disabled_dir+save_postfix) or os.path.isdir(enabled_dir+save_postfix)
def save_dir(_dir):
if os.path.isdir(_dir):
if os.path.isdir(_dir+save_postfix):
# This should never happen
secureio.logging('Error : directory %s already exists' % (_dir+save_postfix))
else:
try:
os.rename(_dir, _dir+save_postfix)
except (OSError, IOError):
secureio.print_error('failed to rename', _dir, 'to', _dir+save_postfix)
def restore_dir(_dir):
if os.path.isdir(_dir+save_postfix):
if os.path.isdir(_dir):
# This should never happen
secureio.logging('Error : directory %s already exists' % _dir)
else:
try:
os.rename(_dir+save_postfix, _dir)
except (OSError, IOError):
secureio.print_error('failed to rename', _dir+save_postfix, 'to', _dir)
def disable_cagefs():
if not cagefs_is_enabled():
print('CageFS is disabled')
return
check_mode_error()
save_dir(disabled_dir)
save_dir(enabled_dir)
if not cagefs_is_enabled():
print('CageFS has been disabled')
def enable_cagefs():
if cagefs_is_enabled():
print('CageFS is enabled')
return
restore_dir(disabled_dir)
restore_dir(enabled_dir)
check_mode_error()
if cagefs_is_enabled():
print('CageFS has been enabled')
def check_save_dir(raise_exception=False):
if save_dir_exists():
if cagefs_is_enabled():
# This should never happen
if raise_exception:
raise CageFSException
secureio.print_error('CageFS is enabled, but "saved" lists of users exist\n',
'Please, remove '+INIPREFIX+'*'+save_postfix)
sys.exit(1)
else:
if raise_exception:
raise CageFSException
# This message is parsed in CageFS plugins for control panels
print('CageFS is disabled.')
print('Please, run "cagefsctl --enable-cagefs" to enable CageFS.')
sys.exit(1)
def print_user_mode():
check_save_dir()
mode = get_user_mode()
print('Mode:', mode)
def set_user_mode(enable_all = True):
check_save_dir()
# clear permissions (delete both enabled_dir and disabled_dir)
try:
shutil.rmtree(enabled_dir, False)
except (OSError, IOError, shutil.Error):
pass
try:
shutil.rmtree(disabled_dir, False)
except (OSError, IOError, shutil.Error):
pass
if enable_all:
# enable all users except specified in disabled_dir
try:
mod_makedirs(disabled_dir, 0o751)
except OSError:
pass
else:
# disable all users except specified in enabled_dir
try:
mod_makedirs(enabled_dir, 0o751)
except OSError:
pass
# Exclude system users
check_exclude()
print_user_mode()
exclude_user_list_cache = {}
def get_exclude_user_list(exclude_path = EXCLUDE_PATH):
global exclude_user_list_cache
if exclude_path in exclude_user_list_cache:
return exclude_user_list_cache[exclude_path]
user_list = []
if os.path.isdir(exclude_path):
for exclude_file_path in os.listdir(exclude_path):
path = os.path.join(exclude_path, exclude_file_path)
if os.path.isfile(path) and exclude_file_path != '.htaccess':
try:
f = open(path, "r")
for line in f.readlines():
line = line.rstrip()
if line == '':
continue
user_list.append(line)
f.close()
except IOError:
secureio.print_error("reading", exclude_file_path)
exclude_user_list_cache[exclude_path] = user_list
return user_list
def filter_users(user_list):
return list(set(user_list) - set(get_exclude_user_list()))
def get_user_prefix(username) -> str:
base = 100
try:
uid = secureio.clpwd.get_uid(username)
except ClPwd.NoSuchUserException:
secureio.print_error('user %s not found' % username)
sys.exit(1)
b = uid % base
prefix = "%02d" % b
return prefix
def toggle_file(_dir, username, enable, prefix=None):
if prefix == None:
prefix = get_user_prefix(username)
fname = '/'+prefix+'/'+username
if enable:
try:
os.remove(_dir + fname)
except (IOError, OSError):
pass
remove_htaccess(_dir + '/'+prefix)
try:
os.rmdir(_dir + '/'+prefix)
except (IOError, OSError):
pass
else:
try:
mod_makedirs(_dir+'/'+prefix, 0o751)
except (IOError, OSError):
pass
try:
open(_dir + fname, 'w').close()
os.chmod(_dir + fname, 0o644)
except (IOError, OSError):
pass
def toggle_user(username, enable):
check_save_dir()
mode = get_user_mode()
check_mode_error(mode)
try:
pw = secureio.clpwd.get_pw_by_name(username)
except ClPwd.NoSuchUserException:
secureio.print_error('user', username, 'does not exist')
return
if pw.pw_uid < MIN_UID:
secureio.print_error('user', username, 'should have UID >=', MIN_UID)
return
username_list = secureio.clpwd.get_names(pw.pw_uid)
if mode == 'Enable All':
for tmp_username in username_list:
toggle_file(disabled_dir, tmp_username, enable)
elif mode == 'Disable All':
for tmp_username in username_list:
toggle_file(enabled_dir, tmp_username, not enable)
def print_users(users, users_per_line = 5, message = 'users'):
users_count = len(users)
if users_count != 0:
users.sort()
print(users_count, message)
name = -1
for name in range(users_count // users_per_line):
print('\t'.join(users[name*users_per_line:(name+1)*users_per_line]))
print('\t'.join(users[(name+1)*users_per_line:]))
# Returns list of users from configuration directory
def get_list_of_users_from_config_dir(_dir):
users = []
pw = secureio.clpwd.get_user_dict()
for subdir in os.listdir(_dir):
if os.path.isdir(os.path.join(_dir, subdir)):
for _file in os.listdir(os.path.join(_dir, subdir)):
if (_file in pw) and (subdir == get_user_prefix(_file)):
users.append(_file)
return users
# _dir == configuration directory (users.enabled for DISABLE_ALL mode, and users.disabled for ENABLE_ALL mode)
def get_list_of_users_from_passwd(_dir):
# get all users from /etc/passwd
users = set(secureio.clpwd.get_user_dict())
# exclude users specified in config dir
exc_users = set(get_list_of_users_from_config_dir(_dir))
return list(users - exc_users)
# enabled == True : return list of enabled users
# enabled == False : return list of disabled users
def get_list_of_users(enabled, raise_exception=False):
check_save_dir(raise_exception)
mode = get_user_mode()
check_mode_error(mode, raise_exception)
if mode == 'Enable All':
if enabled:
return get_list_of_users_from_passwd(disabled_dir)
else:
return get_list_of_users_from_config_dir(disabled_dir)
elif mode == 'Disable All':
if enabled:
return get_list_of_users_from_config_dir(enabled_dir)
else:
return get_list_of_users_from_passwd(enabled_dir)
def get_enabled_users():
if cagefs_is_enabled():
return get_list_of_users(True)
return []
# enabled == True : list enabled users
# enabled == False : list disabled users
def list_users(enabled):
check_save_dir()
mode = get_user_mode()
check_mode_error(mode)
if mode == 'Enable All':
if enabled:
print_users(get_list_of_users_from_passwd(disabled_dir), 1, 'enabled user(s)')
else:
print_users(filter_users(get_list_of_users_from_config_dir(disabled_dir)), 1, 'disabled user(s)')
elif mode == 'Disable All':
if enabled:
print_users(get_list_of_users_from_config_dir(enabled_dir), 1, 'enabled user(s)')
else:
print_users(filter_users(get_list_of_users_from_passwd(enabled_dir)), 1, 'disabled user(s)')
def check_exclude(ex_list = None):
# check if CageFS is disabled
if save_dir_exists():
if cagefs_is_enabled():
# This should never happen
secureio.print_error('CageFS is enabled, but "saved" lists of users exist\n',
'Please, remove '+INIPREFIX+'*'+save_postfix)
sys.exit(1)
else:
# CageFS is disabled
return
mode = get_user_mode()
# Read "new" exclude list
if ex_list == None:
ex_list = get_exclude_user_list()
# get all users from /etc/passwd
pw = secureio.clpwd.get_user_dict()
# Disable users in "new" exclude list
for username in ex_list:
if username in pw:
if mode == 'Enable All':
toggle_file(disabled_dir, username, False)
elif mode == 'Disable All':
toggle_file(enabled_dir, username, True)
# Read "old" saved copy of exclude list
old_ex_list = get_exclude_user_list(EXCLUDE_SAVE_PATH)
# Enable users from "old" list that do not exist in "new" exclude list
for username in old_ex_list:
if username not in ex_list and username in pw:
if mode == 'Enable All':
toggle_file(disabled_dir, username, True)
elif mode == 'Disable All':
toggle_file(enabled_dir, username, False)
# Save "new" exclude list (with concurrency in mind)
if os.path.isdir(EXCLUDE_PATH):
if not os.path.isdir(EXCLUDE_SAVE_PATH):
try:
mod_makedirs(EXCLUDE_SAVE_PATH, 0o750)
except OSError:
pass
tmp_dir = None
try:
for f in os.listdir(EXCLUDE_SAVE_PATH):
path = os.path.join(EXCLUDE_SAVE_PATH, f)
orig_path = os.path.join(EXCLUDE_PATH, f)
if os.path.isfile(path) and not os.path.isfile(orig_path):
try:
os.unlink(path)
except OSError:
pass
tmp_dir = tempfile.mkdtemp(dir=EXCLUDE_SAVE_PATH)
for f in os.listdir(EXCLUDE_PATH):
tmp_path = os.path.join(tmp_dir, f)
shutil.copy(os.path.join(EXCLUDE_PATH, f), tmp_path)
os.rename(tmp_path, os.path.join(EXCLUDE_SAVE_PATH, f))
except (OSError, IOError, shutil.Error):
secureio.print_error("copying", EXCLUDE_PATH, "to", EXCLUDE_SAVE_PATH)
finally:
if tmp_dir:
shutil.rmtree(tmp_dir, True)
def clean_var_cagefs():
bdir = '/var/cagefs'
pw_db = secureio.clpwd.get_user_dict()
if os.path.isdir(bdir):
for prefix in os.listdir(bdir):
if os.path.isdir(os.path.join(bdir, prefix)):
for username in os.listdir(os.path.join(bdir, prefix)):
path = os.path.join(bdir, prefix, username)
if os.path.islink(path) or ((not path.endswith('.lock')) and os.path.isfile(path)):
try:
os.remove(path)
except (OSError, IOError):
pass
elif os.path.isfile(path):
username = username[:-len('.lock')]
if (username not in pw_db) or (prefix != get_user_prefix(username)):
try:
os.remove(path)
except (OSError, IOError):
pass
elif os.path.isdir(path):
if (username not in pw_db) or (prefix != get_user_prefix(username)):
shutil.rmtree(path, True)
try:
os.rmdir(os.path.join(bdir, prefix))
except (IOError, OSError):
pass
def clean_config_dir(_dir):
ex_list = get_exclude_user_list()
pw_db = secureio.clpwd.get_user_dict()
for subdir in os.listdir(_dir):
if os.path.isdir(os.path.join(_dir, subdir)):
for _file in os.listdir(os.path.join(_dir, subdir)):
if (_file not in ex_list) and ( (_file not in pw_db) or (subdir != get_user_prefix(_file)) ):
toggle_file(_dir, _file, True, subdir)
# Remove files that correspond to non-existing users or users with UID < MIN_UID
def clean_config_dirs():
if os.path.isdir(disabled_dir):
clean_config_dir(disabled_dir)
if os.path.isdir(enabled_dir):
clean_config_dir(enabled_dir)
# Returns random string
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
def migrate_config_dir(_dir):
temp_dir = _dir + '.' + id_generator()
try:
os.rename(_dir, temp_dir)
except (OSError, IOError):
secureio.print_error('failed to rename', _dir, 'to', temp_dir)
sys.exit(1)
try:
mod_makedirs(_dir, 0o751)
except (OSError, IOError):
secureio.print_error('failed to create', _dir)
sys.exit(1)
pw_db = secureio.clpwd.get_user_dict()
for subdir in os.listdir(temp_dir):
if os.path.isdir(os.path.join(temp_dir, subdir)):
for _file in os.listdir(os.path.join(temp_dir, subdir)):
if _file in pw_db:
# create prefix directory and _file for user
toggle_file(_dir, _file, False)
# remove temp dir
shutil.rmtree(temp_dir, True)
# Returns True if new prefixes are used
def new_prefixes_are_used():
pw_db = secureio.clpwd.get_user_dict()
for _dir in [enabled_dir, disabled_dir, enabled_dir+save_postfix, disabled_dir+save_postfix]:
if os.path.isdir(_dir):
for subdir in os.listdir(_dir):
if os.path.isdir(os.path.join(_dir, subdir)):
for _file in os.listdir(os.path.join(_dir, subdir)):
if (_file in pw_db) and (subdir != get_user_prefix(_file)):
return False
return True
def migrate_to_new_prefixes():
if not new_prefixes_are_used():
if os.path.isdir(disabled_dir):
migrate_config_dir(disabled_dir)
if os.path.isdir(enabled_dir):
migrate_config_dir(enabled_dir)
if os.path.isdir(disabled_dir+save_postfix):
migrate_config_dir(disabled_dir+save_postfix)
if os.path.isdir(enabled_dir+save_postfix):
migrate_config_dir(enabled_dir+save_postfix)
BASEDIRS_FILE = '/etc/cagefs/cagefs.base.home.dirs'
basedirs = None
def mount_base_dir_enabled():
global basedirs
if os.path.isfile(BASEDIRS_FILE):
if basedirs == None:
basedirs = cagefslib.read_file(BASEDIRS_FILE)
if basedirs[0].rstrip() == "mount_basedir=1":
return True
return False
def get_base_dir(homepath):
for reg_exp in basedirs:
reg_exp = reg_exp.rstrip()
if reg_exp == "mount_basedir=1" or reg_exp == "mount_basedir=0":
continue
m = re.search(reg_exp, homepath)
if m != None:
return m.group()
return ''
def get_mounted_users_old(fix_permissions=False):
"""
Returns list of users which are currently mounted in CageFS.
Used when /proc/sys/fs/may_detach_mounts set to 0 (disabled) or does not exist
:param fix_permissions: when True == fix permissions of directories (mount points) for users' home directories inside /var/cagefs
:type fix_permissions: bool
"""
base_dir_flag = mount_base_dir_enabled()
pw_db = secureio.clpwd.get_user_dict()
res = set()
# scan directories of users in /var/cagefs
if os.path.isdir(BASEDIR):
for prefix in os.listdir(BASEDIR):
if os.path.isdir(os.path.join(BASEDIR, prefix)):
for user in os.listdir(os.path.join(BASEDIR, prefix)):
try:
pw = pw_db[user]
except KeyError:
# user does not exist
continue
if base_dir_flag:
base_dir = get_base_dir(pw.pw_dir)
if base_dir == '':
continue
mount_point_path = os.path.join(BASEDIR, prefix, user) + base_dir
else:
mount_point_path = os.path.join(BASEDIR, prefix, user) + pw.pw_dir
if os.path.isdir(mount_point_path):
if fix_permissions:
try:
os.chmod(mount_point_path, 0o755)
except OSError as e:
print('Error: failed to set permissions to directory', mount_point_path, ':', str(e))
else:
remove_htaccess(mount_point_path)
try:
os.rmdir(mount_point_path)
except OSError:
# mount point is busy - user is mounted
for user2 in cagefslib.get_all_users_with_uid(pw.pw_uid):
res.add(user2)
continue
# recreate mount point
try:
umask_saved = os.umask(0)
os.mkdir(mount_point_path, 0o755)
os.umask(umask_saved)
except OSError:
pass
return list(res)
def get_mounted_users_new():
"""
Returns list of users which are currently mounted in CageFS.
Used when /proc/sys/fs/may_detach_mounts set to 1 (enabled)
"""
users = get_enabled_users()
pw_db = secureio.clpwd.get_user_dict()
mounted_users = []
for user in users:
res = subprocess.run(['/bin/lve_suwrapper', '-meck', str(pw_db[user].pw_uid),
'/usr/bin/stat', '/var/.cagefs'], capture_output=True, cwd='/')
if res.returncode == 0:
mounted_users.append(user)
return mounted_users
def get_mounted_users(fix_permissions=False):
"""
Returns list of users which are currently mounted in CageFS.
"""
if is_may_detach_mounts_enabled():
return get_mounted_users_new()
return get_mounted_users_old(fix_permissions)
def get_logged_in_users():
try:
pl = subprocess.Popen(['/bin/ps','aux'], stdout=subprocess.PIPE, text=True).communicate()[0]
except OSError:
secureio.print_error('failed to run', 'ps', "aux")
sys.exit(1)
pattern = re.compile(r'sshd:[a-z_][a-z0-9_-]*[$]?@pts',re.IGNORECASE)
lst = []
for i in pl.split('\n'):
line = i.split()
#line format
#USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
#0 1 2 3 4 5 6 7 8 9 10+
if len(line) > 0:
command = ''.join(line[10:])
# looking where command start with sshd:USERNAME@pts
if pattern.match(command):
try:
#check if name so long, and ps show UID
uid = int(line[0])
lst.extend(secureio.clpwd.get_names(uid))
except ValueError:
#nope.. ps show username
lst.append(line[0])
pass
except ClPwd.NoSuchUserException:
secureio.print_error('Can`t get user name for UID ',uid )
sshd_set = set(lst)
mounted_set = set(get_mounted_users())
result_set = sshd_set & mounted_set
return list(result_set)
def get_lve_list() -> List[int]:
"""
Return list of id's for existing LVEs
"""
lve_list = []
proc_lve = ProcLve()
for lve_id in proc_lve.lve_id_list():
lve_list.append(lve_id)
if proc_lve.resellers_supported():
for lvp_id in proc_lve.lvp_id_list():
for lve_id in proc_lve.lve_id_list(lvp_id=lvp_id):
lve_list.append(lve_id)
return lve_list
# Returns True if error has occured
def umount_list(_list):
_list.sort()
_list.reverse()
error = False
for line in _list:
if len(line) > 0 and line[0] == '/':
line = line.rstrip()
try:
# run the "umount" command and suppress it's output
p = subprocess.Popen([UMOUNT, "-l", line],\
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.communicate()
# check return code of the child
if p.returncode != 0:
error = True
except OSError:
secureio.print_error('failed to run', UMOUNT, "-l", line)
error = True
return error
# Returns True if error has occured
def umount_dir(path):
try:
ret = subprocess.call([UMOUNT, "-l", SKELETON+path])
if ret != 0:
secureio.print_error("failed to unmount", SKELETON+path)
return True
except OSError:
secureio.print_error('failed to run', UMOUNT, "-l", SKELETON+path)
return True
return False
def apply_all():
"""
Run lvectl apply all
Returns True if error has occured
"""
ATTEMPTS = 3
for _ in range(ATTEMPTS):
error = False
try:
# run the command and suppress it's output
p = subprocess.Popen([LVECTL, "apply", "all", "--force"],\
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.communicate()
# check return code of the child
if p.returncode != 0:
error = True
else:
break
except OSError:
secureio.print_error('failed to run', LVECTL, "apply all")
error = True
if error:
secureio.print_error(LVECTL, "apply all failed")
return error
def destroy_all():
"""
Destroy all LVEs
Returns True if error has occured
"""
ATTEMPTS = 3
for _ in range(ATTEMPTS):
error = False
try:
# run the command and suppress it's output
p = subprocess.Popen([LVECTL, "destroy", "all", "--force"],\
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.communicate()
# check return code of the child
if p.returncode != 0:
error = True
else:
break
except OSError:
secureio.print_error('failed to run', LVECTL, "destroy all")
error = True
if error:
secureio.print_error(LVECTL, "destroy all failed")
return error
def destroy_lve(uids):
"""
Run lvectl destroy for specified uids
:param uids: list of integers (UIDs)
:type uids: iterable
Returns True if error has occured
"""
error = False
# create input string for subprocess (lvectl)
s = ''
for uid in uids:
s = s + str(uid) + '\n'
try:
# run the command and suppress it's output
p = subprocess.Popen([LVECTL, "destroy-many"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
# send input string to suprocess
p.communicate(s)
except OSError:
secureio.print_error('failed to run', LVECTL, "destroy-many")
error = True
return error
def apply_lve(uids):
"""
Run lvectl apply for specified uids
:param uids: list of integers (UIDs)
:type uids: iterable
Returns True if error has occured
"""
error = False
# create input string for subprocess (lvectl)
s = ''
for uid in uids:
s = s + str(uid) + '\n'
try:
# run the command and suppress it's output
p = subprocess.Popen([LVECTL, "apply-many"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
# send input string to suprocess
p.communicate(s)
except OSError:
secureio.print_error('failed to run', LVECTL, "apply-many")
error = True
return error
def get_uids(users):
pw_db = secureio.clpwd.get_user_dict()
uids = []
for user in users:
try:
uids.append(pw_db[user].pw_uid)
except KeyError:
continue
return uids
def remove_duplicates(_list):
res = []
for i in _list:
if i not in res:
res.append(i)
return res
def remount(users):
"""
Remount list of users. Skeleton should be mounted/unmounted before call of this function
Returns True if error has occured
:param users: list of usernames
:type users: iterable
"""
error = False
if is_running_without_lve():
if delete_namespaces(users):
error = True
if create_namespaces(users, do_mount_skel=False):
error = True
return error
# Get UIDs of users
uids = get_uids(users)
uids = remove_duplicates(uids)
if destroy_lve(uids):
error = True
time.sleep(1)
if apply_lve(uids):
error = True
return error
# Remount all users. Skeleton should be mounted/unmounted before call of this function
# Returns True if error has occured
def remount_all(enabled_users_only = False):
error = False
if enabled_users_only:
# Get list of enabled users
enabled_users = get_list_of_users(True)
if remount(enabled_users):
error = True
return error
if is_running_without_lve():
if delete_namespaces():
error = True
if create_namespaces(do_mount_skel=False):
error = True
return error
if destroy_all():
error = True
time.sleep(1)
if apply_all():
error = True
return error
def files_exist(_list):
for _file in _list:
if not os.path.exists(SKELETON+_file):
return False
return True
# Returns True if there is any mounted directory in cagefs-skeleton
def skeleton_is_mounted(skeleton=None):
if skeleton is None:
skeleton = SKELETON
mounts = open("/proc/mounts", "r")
while True:
line = mounts.readline()
if line == '':
break
if line.find(skeleton+'/') != -1:
mounts.close()
return True
mounts.close()
return False
def cagefs_fuse_is_mounted():
if etcfs_is_disabled():
return files_exist(['/var/log/messages'])
else:
return files_exist(['/etc/passwd', '/var/log/messages'])
# Runs "service cagefs-fuse" with specified command ("start", "restart" or "stop")
# Returns True if error has occured
def cagefs_fuse(command):
ATTEMPTS = 3
for _ in range(ATTEMPTS):
error = False
try:
# run the command and suppress it's output
p = subprocess.Popen(["/sbin/service", "cagefs-fuse", command],\
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.communicate()
# check return code of the child
if p.returncode != 0:
error = True
except OSError:
secureio.print_error('failed to run "service cagefs-fuse '+command+'"')
error = True
if command == 'start':
if cagefs_fuse_is_mounted():
error = False
break
else:
command = 'restart'
error = True
elif command == 'restart':
if cagefs_fuse_is_mounted():
error = False
break
else:
error = True
elif command == 'stop':
if not cagefs_fuse_is_mounted():
error = False
break
else:
error = True
else:
break
if error:
secureio.print_error("executing", '"service cagefs-fuse', command+'"')
return error
def proxyexecd_is_socket():
return os.path.lexists(os.path.join(PROXYEXEC_SOCKET_DIR, 'socket'))
def cagefs_proxyexecd(command):
error = False
try:
# run the command and suppress it's output
p = subprocess.Popen(["/sbin/service", "proxyexecd", command],\
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
p.communicate()
if p.returncode != 0:
error = True
if command == 'start' or command == 'restart':
p = subprocess.Popen(["/sbin/service", "proxyexecd", "status"],\
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
p.communicate()
if p.returncode != 0:
error = True
except OSError:
secureio.print_error('failed to run "service proxyexecd '+command+'"')
error = True
if error:
secureio.print_error("executing", '"service proxyexecd', command+'"')
return error
def create_mount_points(_list, mode = 0o755):
umask_saved = os.umask(0)
for path in _list:
if os.path.islink(SKELETON+path):
try:
os.unlink(SKELETON+path)
except (IOError, OSError):
pass
if not os.path.isdir(SKELETON+path):
try:
mod_makedirs(SKELETON+path, mode)
except (IOError, OSError):
pass
os.umask(umask_saved)
def check_mp_file():
if not os.path.isfile(ETC_MPFILE):
if not SILENT:
secureio.print_error('file', ETC_MPFILE, 'not found\n','Please, run\n',sys.argv[0], '--create-mp')
sys.exit(1)
mp_file = cagefslib.read_file(ETC_MPFILE)
if add_new_line(mp_file):
cagefslib.write_file(ETC_MPFILE, mp_file)
def remove_service_lockfile():
try:
os.remove(SERVICE_CAGEFS_LOCK)
except (IOError, OSError):
pass
def create_service_lockfile():
try:
open(SERVICE_CAGEFS_LOCK, 'w').close()
except (IOError, OSError):
pass
# Returns True if error has occured
def umount_skeleton(save_mounts = True, all_cagefs_mounts = False, current_namespace_only=False, all_namespaces=False):
def unmount():
error = True
for _ in range(10):
mounts = cagefslib.get_mounted_dirs(all_cagefs_mounts)
if not mounts:
error = False
break
umount_list(mounts)
umount_list([SKELETON])
return error
if not current_namespace_only:
remove_service_lockfile()
error = unmount()
if save_mounts:
lvectl_start()
# CAG-749: unmount CageFS mounts in all mount namespaces (resolve conflict with systemd)
if os.path.isfile('/usr/bin/systemctl'):
if current_namespace_only:
if error:
subprocess.run('/bin/umount -l /usr &>/dev/null', shell=True, executable='/bin/bash')
error = unmount() or error
elif all_namespaces:
destroy_all()
time.sleep(1)
for pid in Execute('/bin/ps --no-headers -xao pid').split():
if pid:
subprocess.run("/usr/bin/nsenter -m -t " + pid +
" /bin/bash -c 'if /bin/grep -q cagefs /proc/mounts; then" +
" /usr/sbin/cagefsctl --unmount-cur-ns; fi' &>/dev/null",
shell=True, executable='/bin/bash')
return error
def unlock(lockfile, lockname = LOCKNAME):
try:
fcntl.lockf(lockfile, fcntl.LOCK_UN)
except (IOError, OSError):
secureio.print_error('failed to unlock', lockname)
try:
lockfile.close()
except (IOError, OSError):
pass
try:
os.unlink(lockname)
except (IOError, OSError):
pass
def Execute(command):
proc = subprocess.Popen(command,
shell=True,
executable='/bin/bash',
stdout=subprocess.PIPE,
text=True,
bufsize=-1)
return proc.communicate()[0]
def get_parents(process: psutil.Process):
"""
Helper to get all parents list
"""
parents = []
process = process.parent()
while process is not None:
parents.append(process)
process = process.parent()
return parents
def save_processes(file_like_io):
"""
Saves info about parent processes to lock file
"""
try:
current_process = psutil.Process(os.getpid())
cmd_line = current_process.cmdline()
parents = get_parents(current_process)
except Exception:
parents = []
cmd_line = []
file_like_io.write(f'Command line: {" ".join(cmd_line)}\n')
for process in parents:
file_like_io.write(f'pid: "{process.pid}", name: "{process.name()}", command line "{process.cmdline()}"\n')
file_like_io.flush()
def print_lock_data(lockname):
"""
Prints to stdout info from lockfile
"""
if os.path.exists(lockname):
with open(lockname, 'r') as f:
lock_content = f.readlines()
secureio.print_error('Currently running cagefsctl process info:\n{}'.format('\n'.join(lock_content)))
# Acquire lock
def acquire_lock(lockname=LOCKNAME, wait=False, quiet=False):
try:
if not os.path.exists(lockname):
open(lockname, 'w').close()
lockfile = open(lockname, 'r+')
if wait:
print('Acquiring lock... Please wait... ')
if not wait:
fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
fcntl.lockf(lockfile, fcntl.LOCK_EX)
if os.path.exists(DEBUG_CAGEFS_MARKER):
lockfile.truncate(0)
save_processes(lockfile)
if wait:
print('Lock acquired')
return lockfile
except (IOError, OSError):
if not quiet:
if (not wait) and len(Execute('ps aux | grep cagefsctl').split('\n')) > 1:
secureio.print_error('cagefsctl is already running. please try again later.')
if os.path.exists(DEBUG_CAGEFS_MARKER):
secureio.print_error('current cagefsctl process information')
save_processes(sys.stdout)
print_lock_data(lockname)
else:
secureio.print_error('failed to acquire lock file', lockname)
sys.exit(1)
def mount_dir(line, read_only = False, ignore_errors = False):
if len(line) > 0 and line[0] == '/':
line = line.rstrip()
if not os.path.isdir(line):
secureio.print_error(ETC_MPFILE, "file contains incorrect path -", line, "is NOT a directory or does NOT exist")
if ignore_errors:
return
sys.exit(1)
create_mount_points([line])
ret = subprocess.call([MOUNT, "-n", "-o", "nosuid", "--rbind", line, SKELETON+line])
if read_only:
if ret == 0:
ret = subprocess.call([MOUNT, "-n", "-o", "remount,ro,nosuid,bind", line, SKELETON+line])
else:
if ret == 0:
if line == '/dev/shm':
# CAG-812: mount /dev/shm with noexec option in CageFS
ret = subprocess.call([MOUNT, "-n", "-o", "remount,nosuid,noexec,nodev,bind", line, SKELETON+line])
else:
ret = subprocess.call([MOUNT, "-n", "-o", "remount,nosuid,bind", line, SKELETON+line])
if ret != 0:
secureio.print_error("failed to mount", line)
sys.exit(1)
def print_cpanel_home_warning():
secureio.logging('Please ensure that the following option in cPanel/WHM is set to blank value (not default "home"):', SILENT, 1)
secureio.logging('WHM -> Server Configuration -> Basic cPanel/WHM Setup -> Basic Config -> Additional home directories', SILENT, 1)
secureio.logging('When this option is set to "home", cPanel can create home directories in incorrect places.', SILENT, 1)
class MountpointType(Enum):
COMMON = '/'
PERSONAL = '@'
SPLITTED_BY_USERNAME = '%'
SPLITTED_BY_UID = '*'
READ_ONLY = '!'
class MountpointConfig:
# mpconfig_cache = {
# 'path_to_mp_file1': {
# MountpointType.COMMON.name: [ ... list of mount points ... ],
# MountpointType.PERSONAL.name: [ ... ],
# MountpointType.SPLITTED_BY_USERNAME.name: [ ... ],
# ...
# }
# 'path_to_mp_file2': {
# ...
# }
# }
mpconfig_cache: Dict[str, Dict[str, List[str]]] = {}
def __init__(self,
path: str = ETC_MPFILE,
skip_errors: bool = False,
skip_cpanel_check: bool = False,
ignore_cache: bool = False):
self.path = path
self.skip_errors = skip_errors
self.skip_cpanel_check = skip_cpanel_check
self.ignore_cache = ignore_cache
self.data = self._load()
def _load(self) -> Dict[str, List[str]]:
"""
Load a list of mount points from the config file.
Use cached value if exists unless special option specified.
"""
if self.ignore_cache or self.path not in self.mpconfig_cache:
self.mpconfig_cache[self.path] = self._read_config()
return copy.deepcopy(self.mpconfig_cache[self.path])
def _read_config(self) -> Dict[str, List[str]]:
"""
Read the config file and construct
a complete list of mount points.
"""
mounts = defaultdict(list)
try:
with open(self.path) as f:
for line in f:
self._process_mount_line(line, mounts)
except OSError:
if self.skip_errors:
return mounts
secureio.print_error('failed to read', self.path)
sys.exit(1)
if not self.skip_cpanel_check and is_cpanel():
self._process_cpanel_mounts(mounts)
self._process_proxyexec_socket_mounts(mounts)
return mounts
def _process_mount_line(self, line: str, mounts: Dict[str, List[str]]) -> None:
"""
Process the line specifying a mount point.
Determine whether the line belongs to
any of predefined types and process it accordingly.
"""
if line.startswith('#'): # skip comments
return
line = line.rstrip()
# Process each line based on its mount point type
for mount_type in MountpointType:
if line.startswith(mount_type.value):
self._process_line(line, mount_type, mounts)
break
def _process_line(self,
line: str,
mount_type: MountpointType,
mounts: Dict[str, List[str]]) -> None:
# For all mounts other than common cut the first symbol,
# for personal mounts also cut the part after the comma
start = 0 if mount_type == MountpointType.COMMON else 1
end = line.rfind(',') if mount_type == MountpointType.PERSONAL else -1
path = line[start:end] if end != -1 else line[start:]
path = path.rstrip()
if self._is_invalid_mount_point(path):
if self.skip_errors:
return
secureio.print_error('Invalid mount point', line, 'in file', self.path)
sys.exit(1)
# For some reason, we add common mounts with '\n' at the end
if mount_type == MountpointType.COMMON:
mounts[mount_type.name].append(path + '\n')
else:
mounts[mount_type.name].append(path)
# Read only mounts are also being added to common mounts
if mount_type == MountpointType.READ_ONLY:
mounts[MountpointType.COMMON.name].append(path + '\n')
def _is_invalid_mount_point(self, path: str) -> bool:
"""
Check if a given path is an invalid mount path.
"""
return path == '/' \
or not path.startswith('/') \
or '/../' in path \
or path.endswith('/..')
def _process_cpanel_mounts(self, mounts: Dict[str, List[str]]) -> None:
"""
Check invalid paths for cPanel.
"""
common_mounts = mounts[MountpointType.COMMON.name]
for line in common_mounts:
line = line.rstrip()
if 'home' in line and invalid_homes_exist():
secureio.logging(f'Warning: file {self.path} contains line "{line}"', SILENT, 1)
print_cpanel_home_warning()
break
def _process_proxyexec_socket_mounts(self, mounts: Dict[str, List[str]]) -> None:
"""
Add correct path to the proxyexec socket file.
"""
common_mounts = mounts[MountpointType.COMMON.name]
proxyexec_socket_dir_old_line = f'{PROXYEXEC_SOCKET_DIR_OLD}\n'
proxyexec_socket_dir_line = f'{PROXYEXEC_SOCKET_DIR}\n'
if proxyexec_socket_dir_old_line in common_mounts:
common_mounts.remove(proxyexec_socket_dir_old_line)
if proxyexec_socket_dir_line not in common_mounts:
common_mounts.append(proxyexec_socket_dir_line)
@property
def all_mounts(self) -> Dict[str, List[str]]:
return self.data
@property
def common_mounts(self) -> List[str]:
return self.data[MountpointType.COMMON.name]
@property
def personal_mounts(self):
return self.data[MountpointType.PERSONAL.name]
@property
def splitted_by_username_mounts(self):
return self.data[MountpointType.SPLITTED_BY_USERNAME.name]
@property
def splitted_by_uid_mounts(self):
return self.data[MountpointType.SPLITTED_BY_UID.name]
@property
def read_only_mounts(self):
return self.data[MountpointType.READ_ONLY.name]
# Save mounts in default VE
def lvectl_start():
Execute(LVECTL+' start > /dev/null 2>&1')
def mount_should_be_readonly(path, read_only_mounts):
"""
Return True when path is included in one of the read-only paths
:param path: mount path to check
:type path: string
:param read_only_mounts: list of read-only mounts from cagefs.mp file
:type read_only_mounts: list
"""
if path.startswith('/opt/cpanel/ea-php'):
return True
path = cagefslib.addslash(path)
for mount in read_only_mounts:
mount = cagefslib.addslash(mount)
if path.startswith(mount):
return True
return False
def unsafe_mounts_exist():
"""
Search CageFS for mounts that do not have 'nosuid' option
Also search for read-write mounts that should be read-only
Return True when found, False otherwise
For details see CAG-526, CAG-634
"""
no_suid_dirs = cagefslib.get_mounted_dirs(without_nosuid = True)
no_suid_dirs = list(filter(lambda x: '/proc/sys/fs/binfmt_misc' not in x, no_suid_dirs))
if no_suid_dirs:
return True
mp_config = MountpointConfig()
rw_mounts = cagefslib.get_mounted_dirs(rw_mounts_only = True)
for mount in rw_mounts:
path = cagefslib.strip_path(mount)
if mount_should_be_readonly(path, mp_config.read_only_mounts):
return True
return False
def remount_unsafe_mounts(read_only_mounts):
"""
Remount all CageFS "unsafe" mounts, so that they become "safe".
Make all mounts "nosuid", and make some mounts "read-only" (when needed)
For details see CAG-526, CAG-634
"""
def remount_dir(old, new, read_only=False):
if read_only:
ret = subprocess.call([MOUNT, "-n", "-o", "remount,ro,nosuid,bind", '/usr/share/cagefs/not-existing-directory'+old, new])
else:
ret = subprocess.call([MOUNT, "-n", "-o", "remount,nosuid,bind", '/usr/share/cagefs/not-existing-directory'+old, new])
if ret != 0:
secureio.print_error("failed to mount", old)
remount_dir(SKELETON, SKELETON)
wo_nosuid = set(cagefslib.get_mounted_dirs(without_nosuid = True))
for path_new in wo_nosuid:
path_old = cagefslib.strip_path(path_new)
remount_dir(path_old, path_new, read_only=mount_should_be_readonly(path_old, read_only_mounts))
rw_mounts = set(cagefslib.get_mounted_dirs(rw_mounts_only = True)) - wo_nosuid
for path_new in rw_mounts:
path_old = cagefslib.strip_path(path_new)
if mount_should_be_readonly(path_old, read_only_mounts):
remount_dir(path_old, path_new, read_only=True)
# Personal (private) mount points for user
MOUNT_POINTS = [
'/etc',
'/var/log',
'/var/run/screen',
cagefslib.VAR_RUN_CAGEFS,
'/var/spool/cron',
'/var/cache/php-eaccelerator',
'/var/.cagefs'
]
def read_symlink(path):
"""
Return value of symlink or None when error occurs
:param path: path to symlink
:type path: string
"""
try:
return os.readlink(path)
except OSError:
pass
return None
def mount_file(path, do_mount=False, read_only=False):
"""
Mount one separate file to CageFS using hardlink & mount
:param path: path to file
:type path: string
:param do_mount: when True mount directory with hardlink to CageFS
:type do_mount: bool
:param read_only: when True mount read-only, read-write otherwise
:type read_only: bool
"""
if not os.path.isfile(path) and not is_socket_file(path):
return
path = os.path.realpath(path)
skel_path = SKELETON + path
filename = os.path.basename(path)
dir_path = path + '.cagefs'
hardlink_path = os.path.join(dir_path, filename)
cagefslib.make_dir(dir_path, 0o755, allow_symlink=False)
if not os.path.lexists(hardlink_path) or not os.path.samefile(path, hardlink_path):
cagefslib.unlink(hardlink_path)
try:
os.link(path, hardlink_path)
except OSError as e:
if not os.path.isfile(hardlink_path) or not os.path.samefile(path, hardlink_path):
secureio.logging('Error: failed to create hardlink ' + hardlink_path + ' to ' + path + ' : ' + str(e), SILENT, 1)
return
cagefslib.make_dir(SKELETON + dir_path, 0o755, allow_symlink=False, update_perm=False)
spath = read_symlink(skel_path)
if spath != hardlink_path:
cagefslib.unlink(skel_path)
try:
os.symlink(hardlink_path, skel_path)
except OSError as e:
spath = read_symlink(skel_path)
if spath != hardlink_path:
secureio.logging('Error: failed to create symlink ' + skel_path + ' to ' + hardlink_path + ' : ' + str(e), SILENT, 1)
return
if do_mount:
mount_dir(dir_path, read_only)
def _mount_systemd_journal_socket(do_mount: bool) -> None:
"""
Mount socket of systemd-journal into CageFS
:param do_mount: when True mount directory with hardlink to CageFS
"""
# Check that /dev/log is really symlink and
# real file is socket of systemd-journal
if not is_new_syslog_socket_used():
return
mount_file(SYSTEMD_JOURNAL_SOCKET, do_mount=do_mount)
# Directory `SKELETON/dev dir` may be absent
# at the moment of CageFS initialization
skeleton_dev_dir = os.path.join(
SKELETON,
'dev',
)
if not os.path.exists(skeleton_dev_dir):
cagefslib.make_dir(
path=skeleton_dev_dir,
perm=0o755,
allow_symlink=False,
update_perm=False,
)
# Create symlink SKELETON/dev/log -> socket of systemd-journal
# like as in real fs
create_symlink(
SYSTEMD_JOURNAL_SOCKET,
os.path.join(
skeleton_dev_dir,
'log',
),
)
def mount_skeleton(remount_users = False):
"""
Function remounts skeleton and all users
!!WARNING!!: part of this logic is duplicated in jail.c from kmoc-lve project
:param remount_users: when True, destroy&create LVE&namespaces for all users
:type remount_users: bool
"""
# Ensure that mp-file exists
check_mp_file()
Execute('/bin/mount --make-rprivate / >/dev/null 2>&1')
# Create mount points in skeleton
create_mount_points(MOUNT_POINTS)
# --remount_all option is used or cagefs-fuse is not running ?
# if remount_users or (not cagefs_fuse_is_mounted()):
# Restart cagefs-fuse service
# if cagefs_fuse('restart'):
# sys.exit(1)
if remount_users or (not proxyexecd_is_socket()):
# Restart proxyexecd service
if cagefs_proxyexecd('restart'):
sys.exit(1)
# Create mount point for tmp directory
create_mount_points(['/tmp'])
umount_skeleton(save_mounts = False)
ret = subprocess.call([MOUNT, "-n", "-o", "nosuid", "--rbind", SKELETON, SKELETON])
if ret != 0:
secureio.print_error("failed to mount", SKELETON)
# Read mp-file
mp_config = MountpointConfig()
read_only_mounts = mp_config.read_only_mounts
personal_mounts = mp_config.personal_mounts
cagefslib.mounts = mp_config.common_mounts
create_mount_points(personal_mounts)
umask_saved = os.umask(0)
# Mount directories specified in mp-file
# we sort mounts because of CAG-709 (cagefs mounts should not break systemd services with directives like ProtectSystem=full)
for line in sorted(cagefslib.mounts):
line = line.rstrip()
if line != '/proc' and line != '/tmp' and not line.startswith('/tmp/'):
mount_dir(line, read_only = (line in read_only_mounts), ignore_errors = True)
# Mount empty dir over user-defined dirs + default /opt/suphp/sbin see CAG-999
try:
emptied_dirs_path = "/etc/cagefs/empty.dirs"
for filename in os.listdir(emptied_dirs_path):
emptied_config = os.path.join(emptied_dirs_path, filename)
if os.path.isfile(emptied_config):
with open(emptied_config, "r") as emptied_dirs_file:
for emptied_dir in emptied_dirs_file:
mount_empty_dir(emptied_dir.rstrip())
except IOError as e:
secureio.print_error("Error while reading file.", e)
setup_cpanel_multiphp(do_mount=True)
# LU-640: mount license file to CageFS (needed by cloudlinux-selector to check license)
mount_file(LICENSE_TIMESTAMP_FILE, do_mount=True, read_only=True)
_mount_systemd_journal_socket(do_mount=True)
# Mount /proc directory last
mount_dir('/proc')
remount_unsafe_mounts(read_only_mounts)
# Ensure that mount points exist after mounting of skeleton
create_mount_points(MOUNT_POINTS)
create_mount_points(personal_mounts)
os.umask(umask_saved)
# Save mounts in default VE
lvectl_start()
if remount_users:
# Exclude system users BEFORE remounting skeleton
check_exclude()
# Remount all users
remount_all(enabled_users_only = False)
remove_unused_mount_points()
remove_remount_flag()
create_service_lockfile()
def verify_paths(paths):
for path in paths:
path2 = os.path.realpath(path)
path2 = path2 + '/'
if path2.startswith(SKELETON+'/'):
secureio.print_error("path", path, "is incorrect")
path2 = cagefslib.stripslash(path2)
if path2 != path:
secureio.print_error("(it refers to", path2, ")")
sys.exit(1)
class cagefs_init(object):
def __init__(self):
self.didfiles = []
self.didsections = []
self.diddevices = []
self.didusers = []
self.didgroups = []
def update_paths(self, config, chroot, paths, try_glob = 0):
if paths:
verify_paths(paths)
self.didfiles = cagefslib.copy_binaries_and_libs(chroot, paths, config['force'], config['verbose'], check_libs=1,\
try_hardlink=config['hardlink'], retain_owner=1, try_glob_matching=try_glob, handledfiles=self.didfiles, update=config['update'])
def update_alt_php_libs(self, config, chroot):
paths = cagefslib.get_alt_php_libs()
self.update_paths(config, chroot, paths)
def handle_cfg_section(self,config,chroot,cfg,section):
if(chroot[-1] == '/'):
chroot = chroot[:-1]
# first create the chroot jail itself if it does not yet exist
if (not os.path.exists(chroot)):
print('Creating jail '+chroot)
mod_makedirs(chroot, 0o755)
# if the parent is setuid or setgid that is not covered by the umask set above, so we remove that
os.chmod(chroot, 0o755)
sections = cagefslib.config_get_option_as_list(cfg,section,'includesections')
for tmp in sections:
sigterm_check()
if (tmp not in self.didsections):
self.handle_cfg_section(config,chroot,cfg,tmp)
self.didsections.append(tmp)
#libraries, executables, regularfiles and directories are now all handled as 'paths'
paths = cagefslib.config_get_option_as_list(cfg,section,'paths')
# CAG-936: remove invalid path /usr/local/awstats/ that is added by /usr/local/directadmin/scripts/awstats_process.sh script
if section == 'directadmin':
try:
paths.remove('/usr/local/awstats/')
except ValueError:
pass
paths = paths + cagefslib.config_get_option_as_list(cfg,section,'libraries')
paths = paths + cagefslib.config_get_option_as_list(cfg,section,'executables')
paths = paths + cagefslib.config_get_option_as_list(cfg,section,'regularfiles')
paths = paths + cagefslib.config_get_option_as_list(cfg,section,'directories')
self.update_paths(config, chroot, paths, try_glob = 1)
paths_w_owner = cagefslib.config_get_option_as_list(cfg,section,'paths_w_owner')
self.update_paths(config, chroot, paths_w_owner, try_glob = 1)
emptydirs = cagefslib.config_get_option_as_list(cfg,section,'emptydirs')
for edir in emptydirs:
cagefslib.create_parent_path(chroot,edir, config['verbose'], copy_permissions=1, allow_suid=0, copy_ownership=1)
users = []
groups = []
tmplist = cagefslib.config_get_option_as_list(cfg,section,'users')
for tmp in tmplist:
if (tmp not in self.didusers):
users.append(tmp)
tmplist = cagefslib.config_get_option_as_list(cfg,section,'groups')
for tmp in tmplist:
if (tmp not in self.didusers):
groups.append(tmp)
cagefslib.init_passwd_and_group(FUSE_DIR, users, groups, config['verbose'])
cagefslib.init_safe_users_and_groups(FUSE_DIR, users, groups, config['verbose'])
cagefslib.init_passwd_and_group(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', users, groups, config['verbose'])
cagefslib.init_shadow(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', users, config['verbose'])
self.didusers = self.didusers + users
self.didgroups = self.didusers + groups
devices = cagefslib.config_get_option_as_list(cfg,section,'devices')
for tmp in devices:
if (tmp not in self.diddevices):
cagefslib.create_parent_path(chroot,os.path.dirname(tmp), config['verbose'], copy_permissions=1, allow_suid=0, copy_ownership=1)
cagefslib.copy_device(chroot,tmp,config['verbose'])
self.diddevices.append(tmp)
def update_etc_paths(self, paths):
if paths:
verify_paths(paths)
cagefslib.copy_to_etc(paths)
def update_etc_from_section(self, config, cfg, section):
sections = cagefslib.config_get_option_as_list(cfg,section,'includesections')
for tmp in sections:
if (tmp not in self.didsections):
self.update_etc_from_section(config,cfg,tmp)
self.didsections.append(tmp)
#libraries, executables, regularfiles and directories are now all handled as 'paths'
paths = cagefslib.config_get_option_as_list(cfg,section,'paths')
paths = paths + cagefslib.config_get_option_as_list(cfg,section,'libraries')
paths = paths + cagefslib.config_get_option_as_list(cfg,section,'executables')
paths = paths + cagefslib.config_get_option_as_list(cfg,section,'regularfiles')
paths = paths + cagefslib.config_get_option_as_list(cfg,section,'directories')
self.update_etc_paths(paths)
paths_w_owner = cagefslib.config_get_option_as_list(cfg,section,'paths_w_owner')
self.update_etc_paths(paths_w_owner)
users = []
groups = []
tmplist = cagefslib.config_get_option_as_list(cfg,section,'users')
for tmp in tmplist:
if (tmp not in self.didusers):
users.append(tmp)
tmplist = cagefslib.config_get_option_as_list(cfg,section,'groups')
for tmp in tmplist:
if (tmp not in self.didusers):
groups.append(tmp)
cagefslib.init_passwd_and_group(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', users, groups, config['verbose'])
cagefslib.init_shadow(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', users, config['verbose'])
self.didusers = self.didusers + users
self.didgroups = self.didusers + groups
# Return True if mount points are busy (are used by cagefs-fuse)
def mount_points_busy(_list):
for path in _list:
if os.path.isdir(SKELETON+path) and (not os.path.islink(SKELETON+path)):
try:
shutil.rmtree(SKELETON+path, False)
except (IOError, OSError, shutil.Error):
return True
# recreate mount point
try:
umask_saved = os.umask(0)
os.mkdir(SKELETON+path)
os.umask(umask_saved)
except (IOError, OSError):
pass
return False
def check_skeleton_not_busy():
# Check that skeleton is unmounted (mount points are not busy)
if mount_points_busy(['/tmp']):
secureio.print_error('failed to unmount CageFS - skeleton directory is busy.')
sys.exit(1)
# Stops cagefs-fuse service, unmounts skeleton and all users
def unmount_all(remount_users = False, check_busy = False, all_cagefs_mounts = False):
if is_running_without_lve():
delete_namespaces()
umount_skeleton(all_cagefs_mounts = all_cagefs_mounts, all_namespaces = True)
# cagefs_fuse('stop')
# CAG-440
# cagefs_proxyexecd('stop')
time.sleep(1)
if skeleton_is_mounted():
secureio.print_error('failed to unmount cagefs-skeleton')
sys.exit(1)
if remount_users:
if not is_running_without_lve() and remount_all():
sys.exit(1)
remove_unused_mount_points()
time.sleep(1)
if check_busy:
check_skeleton_not_busy()
# NOTE: path should include path to cagefs-skeleton
def mounts_are_found(path, proc_mounts = None, comparator = cagefslib.mounts_are_found_comparator):
cagefslib.mounts = MountpointConfig().common_mounts
if cagefslib.mounts_are_found(path, comparator):
return True
if proc_mounts == None:
proc_mounts = cagefslib.get_mounted_dirs()
path2 = cagefslib.addslash(os.path.realpath(path))
for mount in proc_mounts:
mount = cagefslib.addslash(mount)
if comparator(path2, mount):
return True
return False
# NOTE: path should include path to cagefs-skeleton
def path_includes_mount_point(path, proc_mounts = None):
return mounts_are_found(path, proc_mounts = proc_mounts, comparator = cagefslib.path_includes_mount_point_comparator)
# NOTE: path should include path to cagefs-skeleton
def path_is_mounted(path, proc_mounts = None):
return mounts_are_found(path, proc_mounts = proc_mounts, comparator = cagefslib.path_is_mounted_comparator)
def create_remount_flag():
try:
open(REMOUNT_FLAG, 'w').close()
except IOError as e:
secureio.print_error("failed to create", REMOUNT_FLAG, str(e))
def remove_remount_flag():
try:
os.unlink(REMOUNT_FLAG)
except OSError:
pass
def home_dirs_search_is_disabled():
return os.path.isfile('/etc/cagefs/disable.home.dirs.search')
def create_homeN_dirs_in_skeleton():
"""
Create /usr/share/cagefs-skeleton/home* directories and symlinks when needed,
so they have the same meaning as in real file system. Make all symlinks relative to /usr/share/cagefs-skeleton.
Create need.remount flag when needed. Return True if remount is needed.
"""
if mount_base_dir_enabled():
return False
homes = cagefslib.get_homeN_dirs()
if not homes:
return False
proc_mounts = cagefslib.get_mounted_dirs()
always_home_mounting_mode_enabled = home_dirs_search_is_disabled()
unmounted = False
for home in homes:
path = SKELETON + home
try:
if always_home_mounting_mode_enabled:
if os.path.lexists(path):
if os.path.islink(path):
if home == '/home':
if not unmounted:
unmount_all(remount_users = True, check_busy = False)
unmounted = True
os.unlink(path)
os.mkdir(path, 0o755)
else:
skel_link_to = os.readlink(path)
if skel_link_to != 'home':
if not unmounted:
unmount_all(remount_users = True, check_busy = False)
unmounted = True
os.unlink(path)
os.symlink('home', path)
elif home == '/home':
if not os.path.isdir(path):
os.unlink(path)
os.mkdir(path, 0o755)
elif not mounts_are_found(path, proc_mounts):
if not unmounted:
unmount_all(remount_users = True, check_busy = False)
unmounted = True
cagefslib.remove_file_or_dir(path)
os.symlink('home', path)
else:
os.symlink('home', path)
else:
if os.path.islink(home):
link_to = os.path.realpath(home)
link_to = link_to[1:] # remove leading slash - make path relative
if os.path.lexists(path):
if os.path.islink(path):
skel_link_to = stripslash(os.readlink(path))
if skel_link_to != link_to:
os.unlink(path)
os.symlink(link_to, path)
elif os.path.isdir(path):
if not mounts_are_found(path, proc_mounts):
if not unmounted:
unmount_all(remount_users = True, check_busy = False)
unmounted = True
shutil.rmtree(path, True)
os.symlink(link_to, path)
else: # path is file or socket or device or named pipe
os.unlink(path)
os.symlink(link_to, path)
else:
os.symlink(link_to, path)
elif os.path.isdir(home):
if os.path.lexists(path):
if os.path.islink(path):
if not unmounted:
unmount_all(remount_users = True, check_busy = False)
unmounted = True
os.unlink(path)
os.mkdir(path, 0o755)
elif os.path.isdir(path):
continue
else: # path is file or socket or device or named pipe
os.unlink(path)
os.mkdir(path, 0o755)
else:
os.mkdir(path, 0o755)
except OSError as err:
secureio.print_error('failed to create', path, ':', str(err))
if unmounted:
create_remount_flag()
return unmounted
def update_homeN_symlinks_in_skeleton():
"""
Update symlinks /usr/share/cagefs-skeleton/home*, so they point to the same location as in real file system
"""
if not os.path.isdir(SKELETON) or mount_base_dir_enabled() or home_dirs_search_is_disabled() or not cagefslib.get_homeN_dirs():
return
homes = cagefslib.get_homeN_dirs(use_glob=True)
for home in homes:
path = SKELETON + home
try:
if os.path.islink(home) and os.path.islink(path):
link_to = os.path.realpath(home)
link_to = link_to[1:] # remove leading slash - make path relative
skel_link_to = stripslash(os.readlink(path))
if skel_link_to != link_to:
os.unlink(path)
os.symlink(link_to, path)
except OSError as err:
secureio.print_error('failed to process symlink', path, ':', str(err))
def add_parents(list_of_files, set_of_files):
""" Add parent directories to list_of_files if they do not present in set_of_files """
is_dict = True
if not isinstance(set_of_files, dict):
is_dict = False
if not isinstance(set_of_files, set):
set_of_files = set(set_of_files)
parents = set()
for filename in list_of_files:
# remove redundant separators in order to prevent infinite loop
filename = os.path.normpath(filename)
if filename.startswith('/'):
parent = os.path.dirname(filename)
while parent != '/':
if (parent not in set_of_files) and (parent not in parents):
parents.add(parent)
if is_dict:
set_of_files[parent] = 1
else:
set_of_files.add(parent)
parent = os.path.dirname(parent)
list_of_files.extend(list(parents))
# Creates (overwrites) file of white list for cagefs-fuse
# (white list of files in system's /etc directory)
def save_etc_white_list():
sigterm_check()
white_list = {}
white_list_copy = list(cagefslib.white_list)
for ind in range(len(white_list_copy)):
white_list_copy[ind] = white_list_copy[ind].replace('/etc', '', 1)
white_list[white_list_copy[ind]] = 1
add_parents(white_list_copy, white_list)
white_list_copy.sort()
umask_saved = os.umask(0o22)
_file = open(FUSE_WHITE_LIST, 'w')
for filename in white_list_copy:
_file.write('%s\n' % filename)
_file.close()
os.umask(umask_saved)
os.chmod(FUSE_WHITE_LIST, 0o600)
list_copy = []
files_list = {}
# Function adds parent directories to (global) list_copy and files_list
def add_parents_to_lists():
global list_copy, files_list
list_copy = list(cagefslib.files_list)
files_list = cagefslib.files_list
add_parents(list_copy, files_list)
# Function add_parents_to_lists() should be called before call of this function
def save_list_of_files_in_skeleton(files_list = None):
sigterm_check()
if files_list is None:
files_list = list_copy
files_list.sort()
umask_saved = os.umask(0o77)
try:
f = open(FILES_LIST, 'w')
for filename in files_list:
f.write('%s\n' % filename)
f.close()
except IOError as e:
secureio.print_error('Failed to write ' + FILES_LIST + ' : ' + str(e))
os.umask(umask_saved)
try:
os.chmod(FILES_LIST, 0o600)
except OSError as e:
secureio.print_error('Failed to change permissions of ' + FILES_LIST + ' : ' + str(e))
# Load list of files in skeleton
def load_list(filename, _list):
try:
_file = open(filename, 'r')
except IOError:
return
while True:
line = _file.readline()
if line == '':
break
if line[0] != '\n':
line = line.rstrip()
if line != '' and line[0] == '/':
_list.append(line)
else:
secureio.print_error('path', line, 'is relative')
_file.close()
# Function compares lists of files in skeleton
# Returns list of files (or dirs) that present in old list and do not present in new list
def compare_lists(old, new, ignore_realpath = False):
diff = []
old_len = len(old)
item = 0
while item < old_len:
line = old[item]
if line not in new:
if not ignore_realpath:
path = os.path.realpath(line)
if ignore_realpath or (path not in new):
diff.append(line)
item += 1
while (item < old_len) and old[item].startswith(line+'/'):
diff.append(old[item])
item += 1
continue
item += 1
return diff
# Function add_parents_to_lists() should be called before call of this function
def delete_files_from_skeleton(config):
sigterm_check()
if config['reinit'] == 1 or config['init'] == 1:
return
old_list = []
load_list(FILES_LIST, old_list)
files_to_delete = compare_lists(old_list, files_list)
proc_mounts = cagefslib.get_mounted_dirs()
for _file in files_to_delete:
sigterm_check()
cagefslib.del_libs_from_list(_file)
file2 = cagefslib.addslash(_file)
path = SKELETON + _file
if (not file2.startswith('/dev/')) and (not mounts_are_found(path, proc_mounts)):
if config['dont-clean'] == 1:
secureio.logging("Skipping "+ path,SILENT,config['verbose'])
continue
if os.path.isdir(path) and (not os.path.islink(path)):
try:
shutil.rmtree(path, False)
secureio.logging("Removed directory "+ path,SILENT,1)
except (OSError, IOError, shutil.Error):
secureio.logging('Error while removing directory '+ path,SILENT,1)
elif os.path.lexists(path):
try:
os.unlink(path)
secureio.logging('Removed file '+ path,SILENT,1)
except (OSError, IOError):
secureio.logging('Error while removing file '+ path,SILENT,1)
# Function sends SIGUSR1 signal to cagefs-fuse in order to reload cagefs-fuse config files
def reload_fuse_conf():
for pid in Execute("/sbin/pidof cagefs-fuse").split():
os.kill(int(pid), signal.SIGUSR1)
# Compare dirs
# Returns True if dirs are equal, False otherwise
def are_dirs_equal(dir1, dir2):
if (not os.path.isdir(dir1)) or (not os.path.isdir(dir2)):
return False
try:
# run the "diff" command and suppress it's output
p = subprocess.Popen([DIFF, "-r", dir1, dir2],\
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.communicate()
# check return code of the child
if p.returncode == 0:
# dirs are equal
return True
except OSError:
secureio.logging('failed to run ' + DIFF + " -r " + dir1 + " " + dir2, SILENT, 1)
# some differences were found
return False
def create_symlink_for_selector_config_dir(selector_name):
"""
Create symlink to NodeJS/Python/etc selector config directory
inside template for user etc directory
For details see CAG-797, CAG-828
:param selector_name: name of selector: nodejs, python, etc
"""
# construct symlink path inside etc template (we ommit leading slash)
temp_path = os.path.join(cagefslib.ETC_TEMPLATE_NEW_DIR, 'etc/cl.{}'.format(selector_name))
link_to = SELECTOR_CONF_DIR_TEMPLATE.format(selector_name)
try:
create_symlink(link_to, temp_path)
except OSError as e:
secureio.logging('Error while creating symlink ' + temp_path + ' to ' + link_to + str(e), SILENT, 1)
def compare_etc_templates(force_update_etc = False):
# Remove /etc/mail from template of etc directory (/etc/mail is mounted from real system)
shutil.rmtree(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc/mail', True)
create_symlink_for_selector_config_dir('nodejs')
create_symlink_for_selector_config_dir('python')
# Remove blacklisted files & dirs from skeleton
remove_blacklisted_files()
# Old template exists ?
if os.path.isfile(cagefslib.ETC_TEMPLATE_DIR+'/etc/passwd'):
# Compare old and new templates of etc directory. Increase version if not equal
old_etc_version = get_etc_version(cagefslib.ETC_TEMPLATE_DIR+'/etc')
# set_etc_version(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', old_etc_version)
copy_etc_version(cagefslib.ETC_TEMPLATE_DIR+'/etc', cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc')
if (not force_update_etc) and cagefslib.are_dirs_equal(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', cagefslib.ETC_TEMPLATE_DIR+'/etc', shallow = False):
# Do not replace etc template (templates are equal)
return
else:
set_etc_version(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', old_etc_version+1)
else:
# Do not compare old and new templates of etc directory (old template does not exist)
# Set version of new template to 1
set_etc_version(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', 1)
# Remove old etc template
shutil.rmtree(cagefslib.ETC_TEMPLATE_DIR+'/etc', True)
# Move new etc template to proper location
try:
os.rename(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', cagefslib.ETC_TEMPLATE_DIR+'/etc')
except (OSError, IOError):
secureio.logging("Error moving "+cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc to '+cagefslib.ETC_TEMPLATE_DIR+'/etc', SILENT, 1)
sys.exit(1)
def remove_nested_skeleton():
sigterm_check()
# Hide (override) global SKELETON variable (latter can be changed if /usr/share/cagefs-skeleton is symlink)
SKELETON = '/usr/share/cagefs-skeleton'
if os.path.lexists(SKELETON+SKELETON):
try:
if os.path.isdir(SKELETON+SKELETON) and (not os.path.islink(SKELETON+SKELETON)):
shutil.rmtree(SKELETON+SKELETON, False)
else:
os.unlink(SKELETON+SKELETON)
except (OSError, IOError, shutil.Error) as e:
secureio.logging("Error: failed to remove "+SKELETON+SKELETON+" : "+str(e), SILENT, 1)
if os.path.lexists(SKELETON+SKELETON):
secureio.logging("Error: "+SKELETON+SKELETON+" exists. Please remove manually.", SILENT, 1)
sys.exit(1)
bdir = SKELETON+'/var/cagefs'
if os.path.lexists(bdir):
try:
if os.path.isdir(bdir) and (not os.path.islink(bdir)):
shutil.rmtree(bdir, False)
else:
os.unlink(bdir)
except (OSError, IOError, shutil.Error) as e:
secureio.logging("Error: failed to remove "+bdir+" : "+str(e), SILENT, 1)
if os.path.lexists(bdir):
secureio.logging("Error: "+bdir+" exists. Please remove manually.", SILENT, 1)
def update_etc_only(config, users = None, print_selector_errors = False):
remove_log_file()
ci = cagefs_init()
cfg = read_config()
cagefslib.read_native_conf()
load_black_list()
umask_saved = os.umask(0)
# Create empty directory for template of etc directory
shutil.rmtree(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', True)
if not os.path.isdir(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc'):
try:
mod_makedirs(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', 0o755)
except OSError:
secureio.print_error('creating', cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc')
sys.exit(1)
# Build or update template of etc directory
for section in cfg.sections():
ci.update_etc_from_section(config, cfg, section)
remove_nested_skeleton()
# Update native php files in template of etc directory (if needed)
ci.update_etc_paths(list(cagefslib.orig_binaries.values())) # pylint: disable=dict-values-not-iterating
if print_selector_errors:
for alias, orig_path in cagefslib.orig_binaries.items():
orig_path2 = os.path.realpath(orig_path)
if orig_path2.startswith('/etc/'):
if (not cagefslib.move_to_alternatives(orig_path2, etc = True)) and cagefslib.is_mandatory(alias):
secureio.print_error("CloudLinux Selector setup, path:", orig_path)
return True
cagefslib.remove_unwanted_users_from_groups()
os.umask(umask_saved)
create_files_for_symlink_protection()
create_dirs_for_symlink_protection()
compare_etc_templates(force_update_etc = config['force-update-etc'])
if users == None:
# Create etc for all users > MIN_UID (ignore users.enabled directory)
update_etc(config, all_users = True)
else:
# update etc directory for specified users
update_etc(config, users = users)
return False
# /etc/cagefs/proxy.commands format:
# SENDMAIL=/usr/sbin/sendmail
# SENDMAILQ=/var/qmail/bin/sendmail
# MAIMANCPAN=/usr/local/cpanel/3rdparty/mailman/mail/mailman
# CRONTAB_LIST:proxy.crontab.cagefs=root:/usr/bin/crontab
# CRONTAB_SAVE:noproceed=root:/usr/bin/crontab
def load_wrappers(update_wrappers = False):
wrappers, wrappers_names = build_wrappers_dicts()
cagefslib.wrappers.update(wrappers)
cagefslib.wrappers_names.update(wrappers_names)
if update_wrappers:
cagefslib.mounts = MountpointConfig().common_mounts
proc_mounts = cagefslib.get_mounted_dirs()
# Install wrappers
for _file in cagefslib.wrappers:
if os.path.isfile(_file):
path = SKELETON+_file
if not path_is_mounted(path, proc_mounts):
if os.path.isfile(path):
cagefslib.install_wrapper(_file)
elif not os.path.exists(path):
cagefslib.create_parent_path(SKELETON, os.path.dirname(_file), copy_permissions=1, copy_ownership=1)
cagefslib.install_wrapper(_file)
def check_separator(separator, line):
if separator not in line:
return None, None # main separator not found
line_parts = line.split(separator)
if len(line_parts) != 2:
return None, None # too many main separators in line
return line_parts[0], line_parts[1]
def validate_with_regex(regex, part, can_be_none):
if part is not None:
regexp_comp = re.compile(regex)
p1 = regexp_comp.match(part)
if p1 is None:
return False # username is not valid
return True
if can_be_none:
return True
return False
def check_proxy_line(line):
"""
Return False if line is corrupted
"""
if line.startswith('#') or line.isspace(): # skip comments and empty lines
return True
alias_wrapper, user_command = check_separator('=', line)
if alias_wrapper is None or user_command is None:
return False
if ':' in alias_wrapper:
alias, wrapper = check_separator(':', alias_wrapper)
else:
alias = alias_wrapper
wrapper = None
if ':' in user_command:
user, command = check_separator(':', user_command)
else:
command = user_command
user = None
if alias is None or command is None:
return False
# check that last param is valid asb path
if not os.path.isabs(command):
return False
if command.strip()[-1] == '/':
return False
# check that string is MAYBE username
if not validate_with_regex(r'^[a-z][-a-z0-9]*$', user, can_be_none = True):
return False
# check alias
if not validate_with_regex(r'^[a-zA-Z][-a-zA-Z0-9_]*$', alias, can_be_none = False):
return False
#check wrapper
if not validate_with_regex(r'^[a-zA-Z.][-a-zA-Z0-9_.]*$', wrapper, can_be_none = True):
return False
return True
def build_wrappers_dicts(raise_exception = False):
DEFAULT_PROXY_NAME = "cagefs.proxy.program"
commands = load_wrappers_commands()
# Build hashes
wrappers = {}
wrappers_names = {}
for line in commands:
if not check_proxy_line(line):
if raise_exception:
raise Exception('Warning: Found corrupted line:' + str(line) + '. Skip line.')
else:
print('Warning: Found corrupted line:' + str(line) + '. Skip line.')
continue
if line.startswith('#'):
continue
words = line.strip().split('=', 1)
if len(words) == 2:
words_left = words[0].strip().split(':', 1)
if len(words_left) == 2 and words_left[1] == 'noproceed':
continue
alias = words_left[0].strip()
if len(words_left) == 2:
wrapper_name = words_left[1].strip()
else:
wrapper_name = DEFAULT_PROXY_NAME
if words[1].find(':') == -1:
command = words[1].strip()
else:
words_right = words[1].strip().split(':', 1)
command = words_right[1].strip()
if command not in wrappers:
wrappers[command] = alias
if command not in wrappers_names:
wrappers_names[command] = wrapper_name
return wrappers, wrappers_names
def load_wrappers_commands():
proxy_commands_dir = os.path.dirname(PROXY_COMMANDS)
proxy_commands_name = os.path.basename(PROXY_COMMANDS)
# Load user command files first
filenames = sorted([
filename for filename in os.listdir(proxy_commands_dir)
if filename.endswith(proxy_commands_name) and filename != proxy_commands_name
])
filenames.append(proxy_commands_name)
commands = []
for filename in filenames:
path = os.path.join(proxy_commands_dir, filename)
if os.path.isfile(path):
commands.extend(cagefslib.read_file(path))
return commands
# ETC_MPFILE should be read to cagefslib.mounts before call of this function
def remove_blacklisted_files():
proc_mounts = cagefslib.get_mounted_dirs()
for black_list_file in cagefslib.black_list:
sigterm_check()
# Do not delete jailshell because it should be replaces with symlink to /bin/bash
if black_list_file == '/usr/local/cpanel/bin/jailshell':
continue
is_in_etc = black_list_file.startswith('/etc/')
if is_in_etc:
path = cagefslib.ETC_TEMPLATE_NEW_DIR + black_list_file
else:
path = SKELETON + black_list_file
if os.path.lexists(path):
if not is_in_etc:
if path_is_mounted(path, proc_mounts):
secureio.logging("Warning: blacklisted path "+black_list_file+" is mounted", SILENT, 1)
continue
if path_includes_mount_point(path, proc_mounts):
secureio.logging("Warning: blacklisted path "+black_list_file+" includes mount point", SILENT, 1)
continue
try:
if os.path.isdir(path) and (not os.path.islink(path)):
shutil.rmtree(path, False)
else:
os.unlink(path)
secureio.logging("Removed "+path, SILENT, VERBOSE)
except (OSError, IOError, shutil.Error) as e:
secureio.logging("Error: failed to remove "+path+" : "+str(e), SILENT, 1)
if os.path.lexists(path):
secureio.logging("Warning: blacklisted path "+path+" exists. Please remove manually.", SILENT, 1)
def load_black_list(remove = False):
black_list_dir = os.path.dirname(BLACK_LIST_FILE)
black_list_name = os.path.basename(BLACK_LIST_FILE)
cagefslib.black_list = []
for filename in os.listdir(black_list_dir):
path = os.path.join(black_list_dir, filename)
if filename.endswith(black_list_name) and os.path.isfile(path):
# Read content of black list file, remove path to skeleton and trailing newlines
black_list = cagefslib.read_file(path)
for line in black_list:
line = cagefslib.strip_path(line.rstrip())
if line.startswith('/'):
if line =="/" or line.find("/../") != -1 or line.endswith("/.."):
secureio.print_error("Invalid path", line, "in file", path)
continue
if not line.startswith('/etc/'):
line = os.path.realpath(line)
if line not in cagefslib.black_list:
cagefslib.black_list.append(line)
cagefslib.mounts = MountpointConfig().common_mounts
if remove:
remove_blacklisted_files()
def replace_jailshell():
bin_list = ['/usr/local/cpanel/bin/jailshell', '/usr/local/psa/bin/chrootsh']
dest = '/bin/bash'
for bin_name in bin_list:
parent_dir = SKELETON + os.path.dirname(bin_name)
link_name = SKELETON + bin_name
if os.path.isdir(parent_dir) and (not os.path.islink(link_name)):
if os.path.lexists(link_name):
cagefslib.remove_file_or_dir(link_name, check_mounts = True)
try:
os.symlink(dest, link_name)
except (OSError, IOError):
secureio.print_error('creating symlink', link_name, 'to', dest)
sys.exit(1)
def copy_options(src, section, dst, new_section):
for option in src.options(section):
dst.set(new_section, option, src.get(section, option))
def copy_section(src, dst, section):
if src.has_section(section):
if (not dst.has_section(section)) and (section.lower() != 'default'):
dst.add_section(section)
copy_options(src, section, dst, section)
else:
error = True
for num in range(100):
new_section = section + str(num)
if not dst.has_section(new_section):
error = False
break
if error:
new_section = section + id_generator()
dst.add_section(new_section)
copy_options(src, section, dst, new_section)
def read_config(fail_if_sections_are_duplicated = False):
cfg = configparser.RawConfigParser(strict=False)
# section -> filepath
sections = {}
# Read base config files
for _file in os.listdir(CONFIG_DIR):
if _file.endswith('.cfg'):
path = CONFIG_DIR+_file
tmp = configparser.RawConfigParser(strict=False)
tmp.read(path)
if fail_if_sections_are_duplicated:
for section in tmp.sections():
if section in sections:
secureio.logging("Error: duplicated section ["+section+"] in files "+sections[section]+" and "+path, SILENT, 1)
sys.exit(1)
else:
sections[section] = path
for section in tmp.sections():
copy_section(tmp, cfg, section)
# Read config files for packages that are installed by user
if os.path.isdir(WORK_CONFIG_DIR):
for _file in os.listdir(WORK_CONFIG_DIR):
if _file.endswith('.work'):
path = os.path.join(WORK_CONFIG_DIR, _file)
tmp = configparser.RawConfigParser(strict=False)
tmp.read(path)
if fail_if_sections_are_duplicated:
for section in tmp.sections():
if section in sections:
secureio.logging("Error: duplicated section ["+section+"] in files "+sections[section]+" and "+path, SILENT, 1)
sys.exit(1)
else:
sections[section] = path
for section in tmp.sections():
copy_section(tmp, cfg, section)
return cfg
def create_symlinks_in_skeleton():
"""
Create symlinks in CageFS skeleton
"""
symlinks = {
SKELETON+'/var/tmp' : '../tmp',
SKELETON+'/var/run' : '../run'
}
cagefslib.write_symlinks(symlinks)
def update_cagefs(config):
if (config['update'] == 1) and (config['force-update'] == 0):
if not cagefslib.update_of_cagefs_skeleton_is_needed():
if not SILENT:
print("cagefs-skeleton has been updated recently, if you want to force the update, please run:")
print('"cagefsctl --force-update"')
return
update_rpm_packages()
add_default_rpm_packages_to_cagefs()
remove_log_file()
ci = cagefs_init()
# create cagefs.mp
if config['init'] == 1:
create_mp(False, exit_on_error=True)
cagefslib.mounts = MountpointConfig().common_mounts
cfg = read_config()
cagefslib.read_native_conf()
create_files_for_symlink_protection()
create_dirs_for_symlink_protection()
if config['reinit'] == 0 and config['init'] == 0:
# Load list of libraries in skeleton
cagefslib.load_libs(LIBS_LIST)
# Debugging, checks...
if cagefslib.debug_option and cagefslib.libs_list and os.path.isfile(LIBDIR+'/libs.dat'):
print('Loading libs.dat', end=' ', flush=True)
rtf = open(LIBDIR+'/libs.dat', 'r')
td = eval(rtf.read())
rtf.close()
print("Done")
if td:
print("Pickle len", len(cagefslib.libs_list))
print("Eval len", len(td))
print('Comparing', end=' ', flush=True)
try:
for key in td:
for item in td[key]:
if item not in cagefslib.libs_list[key]:
secureio.print_error('line', cagefslib.lineno(), ' : NOT EQUAL', key)
break
except KeyError as e:
secureio.print_error('line', cagefslib.lineno(), " : Key Error", e)
try:
for key in cagefslib.libs_list:
for item in cagefslib.libs_list[key]:
if item not in td[key]:
secureio.print_error('line', cagefslib.lineno(), ' : NOT EQUAL', key)
break
except KeyError as e:
secureio.print_error('line', cagefslib.lineno(), " : Key Error", e)
print("Done")
load_wrappers(True)
load_black_list()
umask_saved = os.umask(0)
# Create empty directory for template of etc directory
sigterm_check()
shutil.rmtree(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', True)
if not os.path.isdir(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc'):
try:
mod_makedirs(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', 0o755)
except OSError:
secureio.print_error('creating', cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc')
sys.exit(1)
# Build or update skeleton
for section in cfg.sections():
sigterm_check()
ci.handle_cfg_section(config, SKELETON, cfg, section)
remove_nested_skeleton()
# Update native php files in cagefs-skeleton and in template of etc directory
ci.update_paths(config, SKELETON, list(cagefslib.orig_binaries.values())) # pylint: disable=dict-values-not-iterating
ci.update_alt_php_libs(config, SKELETON)
cagefslib.remove_unwanted_users_from_groups()
# Create mount points in skeleton
create_mount_points(MOUNT_POINTS)
os.umask(umask_saved)
# CAG-993: create /root directory inside CageFS, because
# home directory for root user is needed when running cagefsctl --enter
cagefslib.make_dir(SKELETON+'/root', 0o550, allow_symlink=False, update_perm=True)
cagefslib.set_owner(SKELETON+'/root', 0, 0)
cagefslib.save_etc_safe_list(['/passwd', '/group'])
# Add parent directories to lists
add_parents_to_lists()
# Load old list of files, compare to current list,
# delete files from skeleton, delete appropriate libs from cagefslib.libs_list
delete_files_from_skeleton(config)
replace_jailshell()
create_symlinks_in_skeleton()
cagefs_da_lib.create_symlink_to_php_ini_for_DA(SKELETON)
# Save new list of files
save_list_of_files_in_skeleton()
# Save list of libraries in skeleton
cagefslib.save_libs(LIBS_LIST)
if cagefslib.debug_option:
tf2 = open(LIBDIR+'/libs.txt', 'w')
for key in sorted(cagefslib.libs_list):
tf2.write("%s : %s\n" % (key, " ".join(cagefslib.libs_list[key])))
tf2.close()
print('Saving libs.dat', end=' ', flush=True)
tf = open(LIBDIR+'/libs.dat', 'w')
tf.write(repr(cagefslib.libs_list))
tf.close()
print("Done")
# Create (overwrite) white list for cagefs-fuse
save_etc_white_list()
compare_etc_templates()
if etcfs_is_disabled():
# CageFS is enabled ?
if (not save_dir_exists()):
update_etc(config)
else:
# Update etc for all users > MIN_UID
update_etc(config, all_users = True)
elif config['update'] == 1:
reload_fuse_conf()
if (config['reinit'] == 1) and ('cagefs_was_enabled' in config):
enable_cagefs()
create_empty_dir()
# CAG-706: setup emulation for /var/run/utmp
cagefslib.create_utmp_in_skeleton()
setup_cpanel_multiphp(do_mount=False)
# LU-640: update license file inside CageFS (needed by cloudlinux-selector to check license)
mount_file(LICENSE_TIMESTAMP_FILE, do_mount=False)
cagefslib.add_syslog_socket()
# CAG-1062: [CL8.2] Mount socket of systemd-journal into CageFS
_mount_systemd_journal_socket(do_mount=False)
unmounted = create_homeN_dirs_in_skeleton()
update_homeN_symlinks_in_skeleton()
from cagefsreconfigure import add_mounts_for_passenger
add_mounts_for_passenger()
# CageFS is enabled and init (or reinit) command is running ?
if (config['reinit'] == 1 or config['init'] == 1) and (not save_dir_exists()):
# Remount skeleton and all users
mount_skeleton(True)
unmounted = False
if os.path.isfile('/usr/bin/systemctl'):
Execute('/usr/bin/systemctl start cagefs')
if unmounted:
mount_skeleton(True)
# Update statuses of users (needed for PHP Selector)
if cagefs_is_enabled():
secureio.logging("Updating statuses of users ...", SILENT, 1)
update_users_status()
cagefslib.save_last_update_time()
do_not_ask_option = False
def confirm(message):
print(message, end=' ', flush=True)
if do_not_ask_option:
print('yes')
return
while True:
line = sys.stdin.readline()
if line == "yes\n":
break
elif line == "no\n":
print("Aborting")
sys.exit(1)
print("Please, reply with yes or no")
def unmount_skeleton_in_all_namespaces():
print("Unmounting skeleton ", end=' ', flush=True)
if umount_skeleton(all_cagefs_mounts=True, all_namespaces=True):
secureio.print_error('unmounting skeleton')
sys.exit(1)
time.sleep(1)
print("[DONE]")
def remove_all():
confirm("WARNING: If you continue, CageFS will be disabled, and all "
"related files and directories will be removed. Do you want to continue (yes/no)? ")
print("Disabling CageFS ", end=' ', flush=True)
shutil.rmtree(INIPREFIX+'users.disabled', True)
shutil.rmtree(INIPREFIX+'users.enabled', True)
update_users_status(disable_all = True)
print("[DONE]")
if is_running_without_lve():
print("Unmounting users ", end=' ', flush=True)
if delete_namespaces():
secureio.print_error('unmounting users')
sys.exit(1)
time.sleep(1)
print("[DONE]")
unmount_skeleton_in_all_namespaces()
else:
unmount_skeleton_in_all_namespaces()
# CAG-440 - do not stop proxyexecd service because it is needed for cagefs_enter
# print "Stopping proxyexecd service ",
# cagefs_proxyexecd('stop')
# time.sleep(1)
# print "[DONE]"
print("Unmounting users ", end=' ', flush=True)
if remount_all():
secureio.print_error('unmounting users')
sys.exit(1)
time.sleep(1)
print("[DONE]")
check_skeleton_not_busy()
if skeleton_is_mounted():
secureio.print_error('failed to unmount cagefs-skeleton')
sys.exit(1)
if repair_homes.invalid_homes_exist():
print('Users with invalid pathes to home directories exist! DO NOT REMOVE /var/cagefs !')
else:
print("Removing "+BASEDIR, end=' ', flush=True)
shutil.rmtree(BASEDIR, True)
print(" [DONE]")
print("Removing "+SKELETON, end=' ', flush=True)
shutil.rmtree(SKELETON, True)
print(" [DONE]")
old_skel = SKELETON + '.old'
if os.path.isdir(old_skel) and not skeleton_is_mounted(old_skel):
print("Removing "+old_skel, end=' ', flush=True)
shutil.rmtree(old_skel, True)
print(" [DONE]")
def usage():
print('')
print('Use following syntax to manage CageFS:')
print(sys.argv[0]+" [OPTIONS]")
print('Options:')
print(" -i | --init : initialize CageFS (create CageFS if it does not exist)")
print(" -r | --reinit : reinitialize CageFS (make backup and recreate CageFS)")
print(" -u | --update : update files in CageFS (add new and modified files to CageFS,")
print(" remove unneeded files)")
print(" -f | --force : recreate CageFS (do not make backup, overwrite existing files)")
print(' -d | --dont-clean : do not delete any files from skeleton (use with --update option)')
print(" -k | --hardlink : use hardlinks if possible")
print(' --create-mp : Recreates /etc/cagefs/cagefs.mp file with default set of mount points.')
print(' WARNING: Any previous changes made to file by admin or by any software will be lost')
print(' --mount-skel : mount CageFS skeleton directory')
print(" --unmount-skel : unmount CageFS skeleton directory")
print(' --remove-all : disable CageFS, remove templates and /var/cagefs directory')
print(' --sanity-check : perform basic self-diagnistics of common cagefs-related issues(mostly useful for support)')
print(' --addrpm : add rpm-packages into CageFS (run "cagefsctl --update" in order to apply changes)')
print(' : only package name should be specified (without package version and release)')
print(' : example: cagefsctl --addrpm ImageMagick')
print(' --delrpm : remove rpm-packages from CageFS (run "cagefsctl --update" in order to apply changes)')
print(' --list-rpm : list rpm-packages that are installed in CageFS')
print(" -e | --enter : enter into user's CageFS as root")
print(' --update-list : update specified files only (paths are read from stdin)')
print(' --update-etc : update etc directory of all or specified users')
print(' --set-update-period : set min period of update of CageFS in days (default = 1 day)')
print(' --force-update : force update of CageFS (ignore period of update)')
print(' --force-update-etc : force update of /etc directories for users in CageFS')
print(' --reconfigure-cagefs : configure CageFS integration with other software (control panels,')
print(' database servers, etc)')
print('')
print('Use following syntax to manage users:')
print(sys.argv[0]+' [OPTIONS] username [more usernames]')
print('Options:')
print(' -m | --remount : remount specified user(s)')
print(' -M | --remount-all : remount CageFS skeleton directory and all users')
print(' (use this each time you have changed cagefs.mp file)')
print(' -w | --unmount : unmount specified user(s)')
print(' | --unmount-dir : unmount specified dir in all mount namespaces')
print(' -W | --unmount-all : unmount CageFS skeleton directory and all users')
print(' -l | --list : list users that entered in CageFS')
print(' --list-logged-in : list users that entered in CageFS via SSH')
print(' --enable : enable CageFS for the user')
print(' --disable : disable CageFS for the user')
print(' --enable-all : enable all users, except specified in', disabled_dir)
print(' --disable-all : disable all users, except specified in', enabled_dir)
print(' --display-user-mode : display current mode ("Enable All" or "Disable All")')
print(' --toggle-mode : toggle mode saving current lists of users')
print(' (lists of enabled and disabled users remain unchanged)')
print(' --list-enabled : list enabled users')
print(' --list-disabled : list disabled users')
print(' --user-status : print status of specified user (enabled or disabled)')
print(" --getprefix : display prefix for user")
print('')
print('PHP Selector related options:')
print(' --setup-cl-selector : setup PHP Selector or register new alt-php versions')
print(' --remove-cl-selector : unregister alt-php versions, switch users to default php version when needed')
print(' --rebuild-alt-php-ini : rebuild alt_php.ini file for specified users (or all users if none specified)')
print(' --validate-alt-php-ini : same as --rebuild-alt-php-ini but also validates alt_php.ini options ')
print(' --cl-selector-reset-versions: reset php version for specifed users to default (or all users if none specified)')
print(' --cl-selector-reset-modules : reset php modules (extensions) for specific users to defaults (or all users if none specified)')
print(' --create-virt-mp : create virtual mount points for the user')
print(' --create-virt-mp-all : create virtual mount points for all users')
print(' --remount-virtmp : create virtual mount points and remount user')
print(' --apply-global-php-ini : use with 0, 1 or 2 arguments from the list: error_log, date.timezone')
print(' without arguments applies all global php options including two above')
print('')
print('Common options:')
print(' --enable-cagefs : enable CageFS')
print(' --disable-cagefs : disable CageFS')
print(' --cagefs-status : print CageFS status (enabled or disabled)')
print(' --check-cagefs-initialized : properly checks whether CageFS is initialized and print result')
print(' --set-min-uid : Set min UID')
print(' --get-min-uid : Display current MIN_UID setting')
print(' --print-suids : Print list of SUID and SGID programs in skeleton')
print(' --do-not-ask : assume "yes" in all queries (should be the first option in command)')
print(' --clean-var-cagefs : clean /var/cagefs directory (remove data of non-existent users)')
print(' --set-tmpwatch : set tmpwatch command and parameters (save to '+cagefslib.CAGEFS_INI+' file)')
print(' --tmpwatch : execute tmpwatch (remove outdated files in tmp directories in CageFS for all users)')
print(" --toggle-plugin : disable/enable CageFS plugin")
if is_running_without_lve():
print(" --create-namespace USER : create namespace for the USER (only for containers)")
print(" --create-namespaces : create namespaces for all users (only for containers)")
print(" --delete-namespace USER : delete namespace or the USER (only for containers)")
print(" --delete-namespaces : delete namespaces for all users (only for containers)")
print(' -v | --verbose : verbose output')
print(' --wait-lock : wait for end of execution of other cagefsctl processes (when needed) before execution of the command')
print(' -h | --help : this message')
print('')
def check_skeleton():
if not check_cagefs_skeleton():
secureio.print_error('directory', SKELETON, 'does NOT exist or is empty.')
secureio.print_error('Use "'+sys.argv[0]+' --init" to create CageFS')
sys.exit(1)
os.chmod(SKELETON, 0o755)
def remove_parent_dirs(paths):
result = []
for path in paths:
spath = cagefslib.addslash(path)
parent = False
for path2 in paths:
if path2.startswith(spath):
parent = True
break
if not parent:
result.append(path)
return result
def addrpm(pkg_name, silent = False):
WORK_DIR = WORK_CONFIG_DIR
from simple_rpm import get_package_files
package_files = get_package_files(pkg_name)
if package_files is None:
if not silent:
print("Package %s not installed" % pkg_name)
return
if not os.path.lexists(WORK_DIR):
try:
mod_makedirs(WORK_DIR, 0o700)
except (OSError, IOError):
secureio.print_error("failed to create", WORK_DIR)
sys.exit(1)
elif not os.path.isdir(WORK_DIR):
secureio.print_error("path", WORK_DIR, "should be directory")
sys.exit(1)
umask_saved = os.umask(0o77)
WORK_FILE = os.path.join(WORK_DIR, pkg_name+".work")
aFile = open ( WORK_FILE, 'w' )
aFile.write("["+pkg_name+"]\n")
aFile.write("paths=")
i = 0
package_files = remove_duplicates(package_files)
package_files = remove_parent_dirs(package_files)
for b in package_files:
if not b.startswith("/usr/share/man/") and not b.startswith("/usr/share/locale/") and \
not b.startswith("/usr/share/doc/") and not b.startswith("/usr/share/info/") and \
not b.startswith("/usr/lib/.build-id/") and not b.startswith("/usr/share/licenses/"):
if (i != 0):
aFile.write(", "+b)
else:
aFile.write(b)
i = i + 1
aFile.write("\n")
aFile.close()
os.umask(umask_saved)
def delrpm(pkg_name, silent = False):
WORK_DIR = WORK_CONFIG_DIR
WORK_FILE = os.path.join(WORK_DIR, pkg_name+".work")
if not os.path.lexists(WORK_FILE):
if not silent:
print("Rpm %s is not installed in CageFS" % pkg_name)
elif os.path.islink(WORK_FILE) or (not os.path.isfile(WORK_FILE)):
secureio.print_error(WORK_FILE, "should be regular file")
else:
try:
os.remove(WORK_FILE)
except (OSError, IOError):
secureio.print_error("failed to remove", WORK_FILE)
# if this is standard packet, save it
if pkg_name in STD_PACKAGES:
try:
# Add package to file '/usr/share/cagefs/exclude.packages'
# read old list
packagesToExclude = []
if os.path.exists ( STD_PACKAGES_FILE ):
packagesToExclude = cagefslib.read_file ( STD_PACKAGES_FILE )
# Append to exclude list, if it is not there
if (pkg_name+'\n') not in packagesToExclude:
packagesToExclude.append ( pkg_name+'\n' )
cagefslib.write_file ( STD_PACKAGES_FILE, packagesToExclude, False )
except (OSError, IOError):
secureio.print_error("failed to write package list")
def list_rpm(silent = False):
WORK_DIR = WORK_CONFIG_DIR
rpms = []
if os.path.isdir(WORK_DIR):
for work in os.listdir(WORK_DIR):
file_name = work[:work.rfind(".")]
rpms.append(file_name)
rpms.sort()
if not silent:
for package in rpms:
print(package)
return rpms
def add_rpm_packages_to_cagefs(args, overwrite = False):
for rpm in args:
sigterm_check()
if overwrite or (not os.path.isfile(os.path.join(WORK_CONFIG_DIR, rpm + '.work'))):
addrpm(rpm, silent = True)
def remove_rpm_packages_from_cagefs(args):
for rpm in args:
delrpm(rpm, silent = True)
def add_default_rpm_packages_to_cagefs():
# standard packages - STD_PACKAGES
# packages to exclude (file '/usr/share/cagefs/exclude.packages')
packagesToExclude = []
if os.path.exists ( STD_PACKAGES_FILE ):
packagesToExclude = cagefslib.read_file ( STD_PACKAGES_FILE )
packagesToAdd = []
for packName in STD_PACKAGES:
if (packName+'\n') not in packagesToExclude:
packagesToAdd.append ( packName )
# Add to cageFs
add_rpm_packages_to_cagefs ( packagesToAdd )
def update_rpm_packages():
sigterm_check()
rpms = list_rpm(silent = True)
add_rpm_packages_to_cagefs(rpms, overwrite = True)
# Write MIN_UID to file
def set_min_uid(value):
global MIN_UID
buf_val = int(value)
if buf_val < 100:
secureio.print_error("MIN UID should be >= 100")
sys.exit(1)
MIN_UID = buf_val
try:
binfile = open(MIN_UID_FILENAME, 'wb')
data = struct.pack('i', MIN_UID)
binfile.write(data)
binfile.close()
except:
secureio.print_error("writting MIN UID to file", MIN_UID_FILENAME)
sys.exit(1)
# Read MIN_UID from file
def get_min_uid():
global MIN_UID
try:
uid = read_min_uid()
except ValueError as e:
secureio.print_error(str(e), MIN_UID_FILENAME)
sys.exit(1)
if uid is not None:
MIN_UID = uid
def read_min_uid():
"""
Gets minuid from file and returns
unpacked value if no errors happened
otherwise None
"""
if not os.path.isfile(MIN_UID_FILENAME):
return None
try:
binfile = open(MIN_UID_FILENAME, 'rb')
intsize = struct.calcsize('i')
data = binfile.read(intsize)
binfile.close()
except:
raise ValueError('failed to read MIN UID from file')
if len(data) != intsize:
raise ValueError('reading MIN UID from file')
num = struct.unpack('i', data)
if len(num) > 0 and num[0] >= 100:
return num[0]
return None
# toggle mode saving current lists of users
# (lists of enabled and disabled users remain unchanged)
def toggle_mode():
check_save_dir()
mode = get_user_mode()
check_mode_error(mode)
if mode == 'Enable All':
# Get list of enabled users
enabled_users = get_list_of_users(True)
# Set mode "Disable All"
set_user_mode(False)
for user in enabled_users:
# Enable user
toggle_user(user, True)
elif mode == 'Disable All':
# Get list of disabled users
disabled_users = filter_users(get_list_of_users(False))
# Set mode "Enable All"
set_user_mode(True)
for user in disabled_users:
# Disable user
toggle_user(user, False)
def addgrouptojail(bdir, group_id, user, config):
bdir = cagefslib.stripslash(bdir)
try:
gr = grp.getgrgid(group_id)
except:
secureio.logging('Warning: getgrgid() failed for group id '+str(group_id)+" skipping...", SILENT, 1)
return 0
if (not cagefslib.test_group_exist(gr.gr_name, bdir+'/etc/group')):
_file = bdir+'/etc/group'
secureio.logging('adding group '+gr[0]+' to '+_file, SILENT, config['verbose'])
try:
tmp = gr[0]+':x:'+str(gr[2])+':'
if (type(user)==str and len(user)>0):
tmp = tmp + user
tmp = tmp + '\n'
fd = open(_file, 'a')
fd.write(tmp)
fd.close()
except IOError:
secureio.logging('ERROR: failed to write group '+gr[0]+' to '+_file, SILENT, 1)
return 0
return 1
def addusertogroupinjail(bdir, group_id, user, config):
ret = addgrouptojail(bdir, group_id, user, config)
if (ret == 0):
return 0
try:
_file = bdir+'/etc/group'
fd = open(_file, 'r+')
line = fd.readline()
while (len(line)>0):
splitted = line.split(':')
if (len(splitted)==4 and int(splitted[2]) == group_id):
users = splitted[3][:-1].split(',')
if (user in users):
fd.close()
return 1
else :
secureio.logging('Adding user '+user+' to group '+splitted[0], SILENT, config['verbose'])
pos = fd.tell()
buf = fd.read()
fd.seek(pos-len(line))
if (len(users)==1 and users[0] == ''):
users = [user]
else:
users.append(user)
tmp = splitted[0]+':x:'+splitted[2]+':'
tmp2 = ','.join(users)
tmp += tmp2+'\n'+buf
fd.write(tmp)
fd.close()
return 1
line = fd.readline()
except IOError:
secureio.logging('ERROR: failed to add user '+user+' to group '+str(group_id)+' in '+_file, SILENT, 1)
return 0
return 0
#Create .htaccess
def create_htaccess(path):
file_name = path + "/.htaccess"
try:
_file = open(file_name, 'w')
_file.write('#CageFS autogenerated file\n')
_file.write('deny from all\n')
_file.close()
os.chmod(file_name, 0o644)
except (IOError, OSError):
pass
def remove_htaccess(path):
file_name = path + "/.htaccess"
try:
os.remove(file_name)
except (IOError, OSError):
pass
domlogs_found = None
def copyetc(user, config, ignore_errors = False, recreate = False, passwd_only = False, custom_etc_files = None):
global domlogs_found, SPECIAL_PATHS
pw_line = secureio.clpwd.get_pw_by_name(user)
prefix = get_user_prefix(user)
etcskel = cagefslib.ETC_TEMPLATE_DIR + '/etc'
bdir = BASEDIR + '/' + prefix + '/' + user + '/'
etcuser = bdir + 'etc'
if passwd_only:
for cf in ['/passwd', '/shadow']:
if cagefslib.copy_file(etcskel+cf, etcuser+cf, create_parent_dir = False) == 1:
secureio.logging("Error copying "+etcskel+cf+' to '+etcuser+cf, SILENT, 1)
if not ignore_errors:
sys.exit(2)
else:
if custom_etc_files == None:
custom_etc_files = cagefslib.get_additional_etc_files_for_user(user, etcuser)
if (config['init'] == 1) or (config['reinit'] == 1) or recreate:
try:
shutil.rmtree(etcuser, True)
if cagefslib.copytree(etcskel, etcuser, True, skip_dst_files = custom_etc_files) == 1:
raise Exception('copytree() failed')
if create_etc_alternatives(users = [user]):
raise Exception('Failed to setup cl-selector for user '+user)
except Exception as e:
secureio.logging("Error while copying "+etcskel+' to '+etcuser+': '+str(e), SILENT, 1)
if not ignore_errors:
sys.exit(2)
else:
if (cagefslib.copytree(etcskel, etcuser, True, update = True, skip_dst_files = custom_etc_files) == 1) or \
create_etc_alternatives(users = [user]):
secureio.logging("Error copying "+etcskel+' to '+etcuser, SILENT, 1)
if not ignore_errors:
sys.exit(2)
if domlogs_found is None:
domlogs_found = os.path.isdir('/usr/local/apache/domlogs') and os.path.isdir('/etc/apache2/logs/domlogs')
SPECIAL_PATHS.append('/apache2/')
if domlogs_found:
try:
logs_dir_path = etcuser + '/apache2/logs'
domlogs_path = etcuser + '/apache2/logs/domlogs'
if not os.path.lexists(domlogs_path):
if not os.path.lexists(logs_dir_path):
mod_makedirs(logs_dir_path, 0o755)
relative_symlink('/usr/local/apache/domlogs', domlogs_path)
except OSError as e:
secureio.logging("Error while creating "+domlogs_path+' : '+str(e), SILENT, 1)
if not ignore_errors:
sys.exit(2)
# get all entries for users with uid
pw_db = secureio.clpwd.get_pw_by_uid(pw_line.pw_uid)
# get /etc/group database
groups = grp.getgrall()
# Process all users with uid
for pw in pw_db:
# add user to passwd file
try:
fd = open(etcuser + '/passwd', 'a')
fd.write(pw.pw_name+':x:'+str(pw[2])+':'+str(pw[3])+':'+pw[4]+':'+pw[5]+':'+pw[6]+'\n')
fd.close()
except:
secureio.logging('Error while adding user '+pw.pw_name+' to passwd file', SILENT, 1)
if not ignore_errors:
sys.exit(2)
# lookup the primary group and make sure it also exists in the jail
if not addgrouptojail(bdir, pw[3], None, config):
if not ignore_errors:
sys.exit(2)
# look up all other groups
for gr in groups:
if (pw.pw_name in gr.gr_mem):
ret = addusertogroupinjail(bdir, gr.gr_gid, pw.pw_name, config)
if not ret:
if not ignore_errors:
sys.exit(2)
cagefslib.add_user_to_shadow(etcuser, pw.pw_name, config['verbose'])
def remove_file_or_directory(path):
try:
sbuf = os.lstat(path)
except (OSError, IOError):
return
if stat.S_ISDIR(sbuf.st_mode):
try:
shutil.rmtree(path, False)
secureio.logging("Removed directory "+ path,SILENT,1)
except (OSError, IOError, shutil.Error):
secureio.logging('Error while removing directory '+ path,SILENT,1)
else:
try:
os.unlink(path)
secureio.logging('Removed file '+ path,SILENT,1)
except (OSError, IOError):
secureio.logging('Error while removing file '+ path,SILENT,1)
# etc_user_version -> files_to_delete
files_to_delete_cache = {}
SPECIAL_PATHS = ['/mail/', '/'+cagefslib.CL_ALT_NAME+'/', '/'+cagefslib.CL_PHP_DIR_NAME+'/',
'/'+cagefslib.CL_ALT_NAME+'/']
def check_special_paths(path):
for spec_path in SPECIAL_PATHS:
if path.startswith(spec_path):
return False
return True
def clean_etc(user, userdir, etc_skel, config, etc_user_version, custom_etc_files = None):
global files_to_delete_cache
if cagefslib.custom_etc_present() or (etc_user_version not in files_to_delete_cache):
# Get list of files in etc in userdir
etc_user = {}
cagefslib.add_tree_to_list(userdir+'/etc', etc_user, cut_path = userdir+'/etc')
etc_user_list = list(etc_user)
etc_user_list.sort()
files_to_delete = compare_lists(etc_user_list, etc_skel)
files_to_delete_cache[etc_user_version] = files_to_delete
else:
files_to_delete = files_to_delete_cache[etc_user_version]
if custom_etc_files is None:
custom_etc_files = cagefslib.get_additional_etc_files_for_user(user, userdir+'/etc')
for _file in files_to_delete:
file2 = cagefslib.addslash(_file)
path = userdir + '/etc' + _file
if path not in custom_etc_files and check_special_paths(file2):
if config['dont-clean'] == 1:
secureio.logging("Skipping "+ path, SILENT, config['verbose'])
continue
remove_file_or_directory(path)
def get_all_users_from_passwd():
# get all users from /etc/passwd
return list(secureio.clpwd.get_user_dict())
def update_etc(config, users=None, ignore_errors=True, all_users=False):
secureio.logging("Updating users ..." , SILENT, 1)
users = get_cagefs_users(users, all_users)
# Get list of users whose passwd entry has been changed
modified_users = get_modified_users()
# Get version of etc skeleton
etc_skel_version = get_etc_version(cagefslib.ETC_TEMPLATE_DIR+'/etc')
cagefslib.make_dir(BASEDIR, 0o751, allow_symlink = True)
if is_running_without_lve():
cagefslib.make_dir(BASEDIR_UID, 0o751, allow_symlink=True)
etc_skel = None
for user in users:
prefix = get_user_prefix(user)
prefixdir = BASEDIR + '/' + prefix
dirname = '/' + prefix + '/' + user
userdir = BASEDIR + dirname
user_etc_path = userdir + '/etc'
if cagefslib.make_dir(prefixdir, 0o751):
# Error
continue
if cagefslib.make_dir(userdir, 0o751):
# Error
continue
custom_etc_files = cagefslib.get_additional_etc_files_for_user(user, user_etc_path)
custom_etc_files2 = cagefslib.cut_path(custom_etc_files, user_etc_path)
# Get version of etc of user
etc_user_version = get_etc_version(user_etc_path)
if config['force-update-etc'] or (etc_skel_version > etc_user_version):
secureio.logging("Updating user " + user + " ...", SILENT, 1)
if (not os.path.isfile(userdir+'/etc/passwd')) or (config['init'] == 1) or (config['reinit'] == 1):
copyetc(user, config, ignore_errors, custom_etc_files = custom_etc_files)
else:
copyetc(user, config, ignore_errors, custom_etc_files = custom_etc_files)
if etc_skel == None:
# Get list of files in etc template
etc_skel = {}
cagefslib.add_tree_to_list(cagefslib.ETC_TEMPLATE_DIR+'/etc', etc_skel, cut_path = cagefslib.ETC_TEMPLATE_DIR+'/etc')
clean_etc(user, userdir, etc_skel, config, etc_user_version, custom_etc_files = custom_etc_files)
else:
message_printed = False
if user in modified_users:
secureio.logging("Updating user " + user + " ...", SILENT, 1)
message_printed = True
copyetc(user, config, ignore_errors, passwd_only = True)
custom_files_to_delete = cagefslib.get_custom_etc_files_to_delete(user, custom_etc_files2)
if custom_files_to_delete:
if not message_printed:
secureio.logging("Updating user " + user + " ...", SILENT, 1)
if etc_skel == None:
# Get list of files in etc template
etc_skel = {}
cagefslib.add_tree_to_list(cagefslib.ETC_TEMPLATE_DIR+'/etc', etc_skel, cut_path = cagefslib.ETC_TEMPLATE_DIR+'/etc')
copyetc_needed = False
for path in custom_files_to_delete:
if path not in etc_skel:
fullpath = user_etc_path + path
if config['dont-clean'] == 1:
secureio.logging("Skipping "+ fullpath, SILENT, config['verbose'])
continue
remove_file_or_directory(fullpath)
else:
copyetc_needed = True
if copyetc_needed:
copyetc(user, config, ignore_errors, custom_etc_files = custom_etc_files)
cagefslib.update_custom_etc_files_for_user(user, user_etc_path)
cagefslib.save_custom_etc_log(user, custom_etc_files2)
cagefslib.create_utmp_for_user(user, exit_on_error=False)
def enable_cagefs_for_users_with_duplicate_uids(enabled_users = None):
if enabled_users is None:
enabled_users = filter_users(get_list_of_users(True))
for user in enabled_users:
toggle_user(user, True)
def update_users_status(disable_all=False, users=None, status=None, fix_owner=False, old_enabled_users=None):
if disable_all:
users = get_all_users_from_passwd()
cagefslib.update_status(users, False)
elif users is not None and status is not None:
if status:
users = filter_users(users)
cagefslib.update_status(users, status)
else:
enabled_users = filter_users(get_list_of_users(True))
cagefslib.update_status(enabled_users, True, fix_owner=fix_owner)
if fix_owner:
# process users with duplicate uids also
enable_cagefs_for_users_with_duplicate_uids(enabled_users)
disabled_users = get_list_of_users(False)
cagefslib.update_status(disabled_users, False, fix_owner=fix_owner)
# for reinit case we do not want to reload php process
# because reinit clear skeleton and we do not need this extra action
if old_enabled_users is not None:
# it is enough to know only list of enabled users before status change
reload_php_for_users_with_changed_status(old_enabled_users)
def reload_php_for_users_with_changed_status(old_enabled_users):
"""
Filter users and reload php process only if status was REALLY changed
:param old_enabled_users: enabled users before any status change
:return:
"""
new_enabled_users = get_enabled_users()
# old enabled users: u1, u2
# we want to enable: u2, u3
# php process will be reloaded only for u1, u3
# the same with disable status
users_to_kill_process = list(set(old_enabled_users).symmetric_difference(new_enabled_users))
reload_php_for_users(users_to_kill_process)
def get_cagefs_users(users = None, all_users = False, raise_exception = False):
if users is None:
if all_users:
users = get_all_users_from_passwd()
else:
# Get list of enabled users
users = get_list_of_users(True, raise_exception)
users = filter_users(users)
return users
def create_utmp_for_all_users():
"""
Create user's personal /home/user/.cagefs/var/run/cagefs/utmp
file for all users
For details see CAG-706
"""
for user in get_cagefs_users(all_users=True):
cagefslib.create_utmp_for_user(user, exit_on_error=False)
def reload_php_for_users(users = None, all_users = False):
users = get_cagefs_users(users, all_users)
for user in users:
reload_processes('php', user)
def switch_symlink(dest_path, link_name, write_log = True, force = True):
if force or not os.path.islink(link_name):
try:
os.unlink(link_name)
except OSError as e:
if e.errno == errno.ENOENT: # No such file error
logger.info(f'Symlink {link_name} does not exist')
else:
logger.error(f'Error: Unable to remove symlink {link_name}', exc_info=e)
try:
clcaptain.symlink(dest_path, link_name)
except (OSError, ExternalProgramFailed) as e:
msg = f'Error: failed to create symlink {link_name} to {dest_path} : {str(e).replace("Errno", "Err code")}'
logger.error(msg, exc_info=e)
if write_log:
secureio.logging(msg, SILENT, 1)
else:
print(msg, file=sys.stderr)
return True
return False
use_selector = None
def selector_modules_must_be_used():
"""
Return True if modules selected via PHP Selector (alt_php.ini) must be used always (never use modules selected in cPanel MultiPHP Manager)
See CAG-511 for details
"""
global use_selector
if use_selector is None:
syml_rules = clconfpars.load_once('/etc/cl.selector/symlinks.rules', ignore_errors = True)
try:
val = syml_rules['php.d.location']
except KeyError:
use_selector = False
return False
use_selector = val.lower() == 'selector'
return use_selector
def multiphp_system_default_is_ea_php():
"""
Return True when default system php version selected via MultiPHP Manager in cPanel WHM is ea-php (not alt-php)
For details see CAG-774
"""
if is_ea4_enabled():
conf = read_cpanel_ea4_php_conf()
if conf:
try:
return conf['default'].startswith('ea-php')
except KeyError:
pass
return True
def switch_symlink_for_alt_php_ini(php_vers, homedir, write_log = True, force = True):
"""
Switch symlink so it will point to directory with modules for alt-php
For details see CAG-447
Returns True if error has occured
Should be called as user (not root)!
:param php_vers: alt-php version selected for an user (for example 'native' or '5.6')
:type php_vers: string
:param force: recreate symlinks even when they exist
:type force: bool
"""
def switch_symlink_for_dir(php_dir):
# create path to link, like /home/$USER/.cagefs/opt/alt/php55/link/conf
link_path = os.path.join(homedir, '.cagefs/opt/alt', php_dir, 'link/conf')
dir_path = os.path.dirname(link_path)
if not os.path.lexists(dir_path):
try:
# os.makedirs(dir_path, 0700)
clcaptain.mkdir(dir_path, 0o700, recursive=True)
except (OSError, ExternalProgramFailed):
pass
selected_php_dir = 'php'+php_vers.replace('.','')
if not selector_modules_must_be_used() and (selected_php_dir != php_dir or not multiphp_system_default_is_ea_php()):
# path to default alt-php modules
link_to = '/opt/alt/%s/etc/php.d' % php_dir
else:
# path to user's custom modules selected via CloudLinux PHP Selector - like /etc/cl.php.d/alt-php55
link_to = os.path.join(cagefslib.ETC_CL_PHP_PATH, 'alt-'+php_dir)
return switch_symlink(link_to, link_path, write_log, force)
error = False
# get dirnames of all alt-php dirs as list
alt_php_dirs = cagefslib.get_alt_dirs()
# switch symlinks for ALL alt-php versions
for php_dir in alt_php_dirs:
if switch_symlink_for_dir(php_dir):
error = True
return error
EA4_PHP_CONF = '/etc/cpanel/ea4/php.conf'
EA4_PHP_CONF_CACHE = None
def read_cpanel_ea4_php_conf():
"""
Read /etc/cpanel/ea4/php.conf, return something like {'default': 'ea-php54', 'ea-php56': 'suphp', 'ea-php54': 'cgi', 'ea-php55': 'suphp'}
Return None if error has occured
"""
global EA4_PHP_CONF_CACHE
if EA4_PHP_CONF_CACHE is None:
try:
f = open(EA4_PHP_CONF, 'r')
# conf = {'default': 'ea-php54', 'ea-php56': 'suphp', 'ea-php54': 'cgi', 'ea-php55': 'suphp'}
EA4_PHP_CONF_CACHE = yaml.load(f, yaml.SafeLoader)
f.close()
except (yaml.YAMLError, IOError):
EA4_PHP_CONF_CACHE = {}
try:
f.close()
except:
pass
return None
return EA4_PHP_CONF_CACHE
SYMLINKS = {'%s/etc/cl.selector/ea-php' : ('php','/opt/cpanel/%s/root/usr/bin/php-cgi.cagefs'),
'%s/etc/cl.selector/ea-php-cli' : ('php-cli','/opt/cpanel/%s/root/usr/bin/php.cagefs'),
'%s/etc/cl.selector/ea-php.ini' : ('php.ini','/opt/cpanel/%s/root/etc/php.ini.cagefs'),
'%s/etc/cl.selector/ea-lsphp' : ('lsphp','/opt/cpanel/%s/root/usr/bin/lsphp.cagefs')}
def create_files_for_symlink_protection():
"""
Configure symlink protection for symlinks created for integration with cPanel MultiPHP
Return True if error has occured
"""
if is_running_without_lve():
return False
if not is_ea4_enabled():
return False
try:
linksafe_gid = grp.getgrnam('linksafe').gr_gid
except KeyError:
# linksafe group not found - symlink protection is not configured properly
return False
conf = read_cpanel_ea4_php_conf()
if not conf:
return False
error = False
umask_old = os.umask(0o22)
for alias in conf:
if alias.startswith('ea-php'):
for path in SYMLINKS.values():
try:
file_path = path[1] % alias
f = open(file_path, 'w')
f.write('CageFS integration for cPanel MultiPHP\n')
f.close()
os.chown(file_path, 0, linksafe_gid)
except OSError as e:
secureio.print_error('failed to create file', file_path, ':', str(e))
error = True
os.umask(umask_old)
return error
def switch_symlink_for_cpanel_multi_php(pw, selected_php_vers, write_log = True, force = True):
"""
Switch symlinks that are used for integration with cPanel MultiPHP:
when selected_php_vers == alt-php version, then create symlinks like /etc/cl.selector/ea-php -> php;
when selected_php_vers == native version, then create symlinks like /etc/cl.selector/ea-php -> /opt/cpanel/ea-phpXX/root/usr/bin/php.cagefs;
For details please see CAG-445
Return True if error has occured
:param pw: password file entry for an user
:type pw: as defined in standard pwd module
:param selected_php_vers: alt-php version selected for an user (for example 'native' or '5.6')
:type selected_php_vers: string
:param write_log: write error messages to log or not
:type write_log: bool
:param force: recreate symlinks even when they exist
:type force: bool
"""
def get_default_native_version_selected():
"""
Return string like ea-phpXX when symlinks have been created already and native version is selected
Return None otherwise
"""
try:
link_to = os.readlink('%s/etc/cl.selector/ea-php.ini' % user_cagefs_path)
except OSError:
return None
if link_to.startswith('/opt/cpanel/ea-php'):
return link_to.split('/')[3]
return None
if not is_ea4_enabled():
return False
conf = read_cpanel_ea4_php_conf()
if not conf:
return False
try:
# get default system php version selected via MultiPHP Manager in cPanel WHM
default_php = conf['default']
except KeyError:
return True
# LVEMAN-1170: do not configure PHP Selector when system default version is alt-php
if not default_php.startswith('ea-php'):
return False
username = pw.pw_name
user_cagefs_path = BASEDIR + '/' + get_user_prefix(username) + '/' + username
if not force:
old_eaphp_default = get_default_native_version_selected()
if old_eaphp_default is not None:
selected_php_vers = 'native'
if old_eaphp_default != default_php:
# we should recreate symlinks when native version is selected actually
# and when default ea-php version is changed via cPanel MultiPHP
force = True
error = False
for sympath, link_to in SYMLINKS.items():
link_path = sympath % user_cagefs_path
if selected_php_vers == 'native':
error = switch_symlink(link_to[1] % default_php, link_path, write_log, force) or error
else:
error = switch_symlink(link_to[0], link_path, write_log, force) or error
return error
def configure_alt_php(pw, php_vers, write_log = True, drop_perm = True, force = True, configure_multiphp = True):
"""
Create .cagefs directory in home directory of an user (if that dir does not exist),
and create symlinks to modules for alt-php
For details see CAG-447
Also switch symlinks that are used for integration with cPanel MultiPHP
For details please see CAG-445
drop_perm should be True when called as root, otherwise drop_perm should be False
Returns True if error has occured
:param pw: password file entry for an user
:type pw: as defined in standard pwd module
:param php_vers: alt-php version selected for an user (for example 'native' or '5.6')
:type php_vers: string
:param write_log: write error messages to log or not
:type write_log: bool
:param force: recreate symlinks even when they exist
:type force: bool
"""
# create /home/user/.cagefs directory if it does not exist, set permissions/owner otherwise
real_homepath = os.path.realpath(pw.pw_dir)
path = os.path.join(pw.pw_dir, '.cagefs')
if drop_perm:
if cagefslib.make_userdir(path, 0o771, pw.pw_uid, pw.pw_gid, real_homepath):
return True
elif not os.path.lexists(path):
try:
clcaptain.mkdir(path, 0o771)
except (OSError, ExternalProgramFailed) as e:
msg = f'Error: failed to create directory {path} : {str(e).replace("Errno", "Err code")}'
logger.error(msg, exc_info=e)
print(msg, file=sys.stderr)
return True
if drop_perm:
# drop privileges (switch to user)
secureio.set_user_perm(pw.pw_uid, pw.pw_gid)
error = switch_symlink_for_alt_php_ini(php_vers, pw.pw_dir, write_log, force)
if configure_multiphp:
error = switch_symlink_for_cpanel_multi_php(pw, php_vers, write_log, force) or error
if drop_perm:
# restore root privileges
secureio.set_root_perm()
return error
def php_version_is_removed(php_vers):
if php_vers == 'native':
return False
return php_vers not in cagefslib.get_alt_versions()
def php_version_is_disabled(php_vers, cl_alt_def_php_state):
return (cl_alt_def_php_state != None) and (php_vers in cl_alt_def_php_state) and (not cl_alt_def_php_state[php_vers])
# Returns True if error has occurred
def create_etc_alternatives(users=None, all_users=False, repair_symlinks=False,
reset_modules_to_default=False, rebuild_alt_php_ini=False):
users = get_cagefs_users(users, all_users)
error = False
umask_saved = os.umask(0)
for user in users:
pw = secureio.clpwd.get_pw_by_name(user)
prefix = get_user_prefix(user)
dirname = '/'+prefix+'/'+user
userdir = BASEDIR + dirname
# userdir = /var/cagefs/<prefix>/<user>
if os.path.isdir(userdir):
LINK_DIR = cagefslib.ETC_CL_ALT_PATH
link_dir = userdir + LINK_DIR
# cannot drop permissions because root only can write to /var/cagefs/prefix/user/etc
if cagefslib.make_dir(link_dir, 0o755):
continue
cagefslib.set_owner(link_dir, pw.pw_uid, pw.pw_gid)
# cannot drop permissions because root only can write to /var/cagefs/prefix/user/etc
user_php_dir = userdir + cagefslib.ETC_CL_PHP_PATH
# user_php_dir = /var/cagefs/prefix/user/etc/cl.php.d
if cagefslib.make_dir(user_php_dir, 0o755):
continue
cagefslib.set_owner(user_php_dir, pw.pw_uid, pw.pw_gid)
cl_alt_def_vers, cl_alt_def_modules, cl_alt_def_php_state, cl_alt_def_other = cagefslib.read_cl_alt_defaults() # @UnusedVariable
def_vers, php_modules, php_state_ignored, other_ignored = cagefslib.read_cl_alt_backup_as_user(pw.pw_dir, pw.pw_uid, pw.pw_gid) # @UnusedVariable
def_vers_old = def_vers
# Backup is absent or backup exists and php version (from backup) is disabled or removed?
if (def_vers == None) or ((def_vers != None) and
(php_version_is_removed(def_vers) or php_version_is_disabled(def_vers, cl_alt_def_php_state))):
# global defaults are absent or global defaults exist and default php version is disabled or removed?
if (cl_alt_def_vers == None) or ((cl_alt_def_vers != None) and
(php_version_is_removed(cl_alt_def_vers) or php_version_is_disabled(cl_alt_def_vers, cl_alt_def_php_state))):
def_vers = 'native'
else:
def_vers = cl_alt_def_vers
changed = False
for alias in cagefslib.orig_binaries:
# orig_path = cagefslib.orig_binaries[alias]
filename = alias
if def_vers == 'native':
LINK_TO = cagefslib.get_usr_selector_path(alias)
else:
alt_path = cagefslib.get_alt_conf(def_vers, alias)
if alt_path != None:
LINK_TO = alt_path
else:
LINK_TO = cagefslib.get_usr_selector_path(alias)
LINK_NAME = cagefslib.ETC_CL_ALT_PATH+'/'+filename
link_name = userdir + LINK_NAME
if not os.path.islink(link_name):
cagefslib.remove_file_or_dir(link_name)
try:
os.symlink(LINK_TO, link_name)
if alias == 'php.ini':
changed = True
except OSError as e:
msg = f'Error: failed to create symlink {link_name} : {str(e).replace("Errno", "Err code")}'
logger.error(msg, exc_info=e)
secureio.logging(msg, SILENT, 1)
error = True
elif repair_symlinks:
try:
link_to = os.readlink(link_name)
except OSError as e:
msg = f'Error: failed to read symlink {link_name} : {str(e).replace("Errno", "Err code")}'
logger.error(msg, exc_info=e)
secureio.logging(msg, SILENT, 1)
error = True
continue
if link_to.startswith('/opt/alt/php'):
link_to_dirname, link_to_filename = os.path.split(link_to)
_dirname, _filename = os.path.split(LINK_TO)
repaired = None
if link_to_dirname == _dirname and link_to_filename != _filename:
repaired = LINK_TO
if repaired != None:
try:
os.unlink(link_name)
os.symlink(repaired, link_name)
except (OSError, IOError) as e:
msg = f'Error: failed to create symlink {link_name} : {str(e).replace("Errno", "Err code")}'
logger.error(msg, exc_info=e)
secureio.logging(msg, SILENT, 1)
error = True
cagefslib.select_default_php_modules(user_php_dir,
pw.pw_dir,
pw.pw_uid,
pw.pw_gid,
def_vers,
cl_alt_def_modules,
php_modules,
changed,
def_vers_old,
reset_modules_to_default,
rebuild_alt_php_ini,
user)
if cagefs_da_lib.create_php_ini_for_DA(userdir, user, def_vers, pw.pw_uid, pw.pw_gid):
error = True
# Switch symlink so it will point to directory with modules for alt-php
# For details see CAG-447
if configure_alt_php(pw, def_vers, force=changed):
error = True
os.umask(umask_saved)
return error
def reset_modules_to_default(users = None):
if not users:
users = None
create_etc_alternatives(users = users, all_users = True, reset_modules_to_default = True)
reload_php_for_users(users = users)
def rebuild_alt_php_ini(users = None):
if not users:
users = None
create_etc_alternatives(users = users, all_users = True, rebuild_alt_php_ini = True)
reload_php_for_users(users = users)
def check_php_ini_options(users=None):
if not users:
users = None
cagefslib.validate_alt_php_ini = True
create_etc_alternatives(users=users, all_users=True, rebuild_alt_php_ini=True)
reload_php_for_users(users=users)
def add_new_line(lines):
# file is not empty and last line in the file does not end with new line symbol ?
if lines and lines[0] != '' and lines[-1][-1] != '\n':
lines[-1] += '\n'
return True
return False
def write_cagefs_mp(new_lines):
# write cagefs.mp file if needed
if new_lines:
lines = cagefslib.read_file(ETC_MPFILE)
add_new_line(lines)
lines.extend(new_lines)
cagefslib.write_file(ETC_MPFILE, lines)
os.chmod(ETC_MPFILE, 0o600)
create_remount_flag()
def add_mounts_for_ea_php_sessions():
"""
Add mount points like "@/var/cpanel/php/sessions/ea-php56,700" to /etc/cagefs/cagefs.mp file
"""
if os.path.isdir('/var/cpanel/php/sessions') and os.path.isfile(ETC_MPFILE):
mp_config = MountpointConfig(
skip_errors=True,
skip_cpanel_check=True,
ignore_cache=True,
)
personal_mounts = mp_config.personal_mounts
new_lines = []
# get dirnames of all alt-php dirs as list
php_dirs = glob.glob('/var/cpanel/php/sessions/ea*')
for php_dir in php_dirs:
if php_dir not in personal_mounts and os.path.isdir(php_dir):
mount_str = '@%s,700\n' % php_dir
new_lines.append(mount_str)
write_cagefs_mp(new_lines)
def add_mounts_for_php_selector():
"""
Add mount points for php selector and alt-php to /etc/cagefs/cagefs.mp file
"""
mp_config = MountpointConfig(
skip_errors=True,
skip_cpanel_check=True,
ignore_cache = True,
)
cagefslib.mounts = mp_config.common_mounts
personal_mounts = mp_config.personal_mounts
new_lines = []
# Add '/opt/alt' to cagefs.mp if needed
if ('/opt/alt\n' not in cagefslib.mounts) and ('/opt\n' not in cagefslib.mounts):
cagefslib.mounts.append('/opt/alt\n')
new_lines.append('/opt/alt\n')
# get dirnames of all alt-php dirs as list
alt_php_dirs = cagefslib.get_alt_dirs()
for php_dir in alt_php_dirs:
# something like /opt/alt/php55/link
mount_path = '/opt/alt/%s/link' % php_dir
mount_str = '@/opt/alt/%s/link,700\n' % php_dir
if mount_path not in personal_mounts:
new_lines.append(mount_str)
mount_path = '/opt/alt/%s/var/lib/php/session' % php_dir
mount_str = '@/opt/alt/%s/var/lib/php/session,700\n' % php_dir
if mount_path not in personal_mounts:
new_lines.append(mount_str)
# add php-newrelic logs path, CAG-1118
mount_path = '/var/log/alt-%s-newrelic' % php_dir
mount_str = '@/var/log/alt-%s-newrelic,700\n' % php_dir
if mount_path not in personal_mounts:
new_lines.append(mount_str)
write_cagefs_mp(new_lines)
def remove_mounts_for_php_selector():
"""
Remove mount points for uninstalled alt-php versions from /etc/cagefs/cagefs.mp file
"""
php_alt_dirs = cagefslib.get_alt_dirs()
needed_mounts = set(['/opt/alt/%s/link' % php_dir for php_dir in php_alt_dirs])
needed_mounts.update(['/opt/alt/%s/var/lib/php/session' % php_dir for php_dir in php_alt_dirs])
needed_mounts.update(['/var/log/alt-%s-newrelic' % php_dir for php_dir in php_alt_dirs])
lines = cagefslib.read_file(ETC_MPFILE)
new_lines = []
pattern = re.compile(r'@(/opt/alt/php\d\d/link|/opt/alt/php\d\d/var/lib/php/session|/var/log/alt-php\d\d-newrelic),')
changed = False
for line in lines:
m = pattern.match(line)
if m:
if m.group(1) in needed_mounts:
new_lines.append(line)
else:
changed = True
else:
new_lines.append(line)
if changed:
cagefslib.write_file(ETC_MPFILE, new_lines)
os.chmod(ETC_MPFILE, 0o600)
create_remount_flag()
def add_mount_for_php_apm():
"""
Adds mount point for default location of PHP APM DB
:return:
"""
if not os.path.isfile(ETC_MPFILE):
# do nothing, if cagefs.mp is absent
return
path_to_add = '/var/php/apm/db'
personal_mounts = []
mp_config = MountpointConfig(
skip_errors=True,
skip_cpanel_check=True,
)
mounts = mp_config.common_mounts
personal_mounts = mp_config.personal_mounts
if path_to_add not in personal_mounts and path_to_add not in mounts:
# Path not found, add it
lines = cagefslib.read_file(ETC_MPFILE)
add_new_line(lines)
lines.append('@%s,777\n' % path_to_add)
cagefslib.write_file(ETC_MPFILE, lines)
os.chmod(ETC_MPFILE, 0o600)
create_remount_flag()
def create_dirs_for_symlink_protection():
"""
Cretate /etc/cl.php.d/alt-phpNN directories for all alt-php versions (in real filesystem) with group owner 'linksafe'
for details see CAG-532, CAG-454
Return True if error has occured
"""
if is_running_without_lve():
return False
try:
linksafe_gid = grp.getgrnam('linksafe').gr_gid
except KeyError:
# linksafe group not found - symlink protection is not configured properly
return False
alt_php_dirs = cagefslib.get_alt_dirs()
error = False
for php_dir in alt_php_dirs:
etc_php_dir = '/etc/cl.php.d/alt-%s' % php_dir
alt_php_ini = '/etc/cl.php.d/alt-%s/alt_php.ini' % php_dir
try:
if not os.path.exists(etc_php_dir):
mod_makedirs(etc_php_dir, 0o755)
os.chown(etc_php_dir, 0, linksafe_gid)
open(alt_php_ini, 'w').close()
os.chown(alt_php_ini, 0, linksafe_gid)
except OSError as e:
secureio.print_error('failed to configure linksafe', ':', str(e))
error = True
return error
def clean_dir_recursive(cagefs_dir, orig_dir):
"""
Delete from cagefs_dir files/dirs that do not exist in orig_dir
:param cagefs_dir: path to dir in CageFS (dir to delete files from)
:type cagefs_dir: string
:param orig_dir: path to original dir
:type orig_dir: string
"""
for file_name in os.listdir(cagefs_dir):
if file_name.endswith('.cagefs'):
continue
orig_path = os.path.join(orig_dir, file_name)
cagefs_path = os.path.join(cagefs_dir, file_name)
if not os.path.lexists(orig_path):
remove_file_or_directory(cagefs_path)
elif os.path.isdir(cagefs_path) and not os.path.islink(cagefs_path): # cagefs_path is a directory ?
clean_dir_recursive(cagefs_path, orig_path)
def setup_cpanel_multiphp(do_mount=False, config=None):
"""
Setup CageFS for integration with cPanel MultiPHP
For details please see CAG-445
:param do_mount: when True, do mounting; when False, do copying files to CageFS
:type do_mount: bool
"""
if not is_ea4_enabled():
# EasyApache4 is not setup
return
SYMLINK_NAMES = {'php-cgi':'/etc/cl.selector/ea-php',
'php':'/etc/cl.selector/ea-php-cli',
'php.ini':'/etc/cl.selector/ea-php.ini',
'lsphp':'/etc/cl.selector/ea-lsphp'}
CAGEFS_PHP_BASEDIR = '/usr/share/cagefs/.cpanel.multiphp'
DIRS_TO_MOUNT = ('/opt/cpanel/%s/root/usr/bin', '/opt/cpanel/%s/root/etc')
conf = read_cpanel_ea4_php_conf()
if not conf:
return
try:
# get default system php version selected via MultiPHP Manager in cPanel WHM
default_php = conf['default']
except KeyError:
return
for alias in conf:
if alias.startswith('ea-php'):
for path in DIRS_TO_MOUNT:
optdir = path % alias
cagefs_optdir = '%s%s' % (SKELETON, optdir)
cagefs_dir = '%s%s' % (CAGEFS_PHP_BASEDIR, optdir)
if os.path.isdir(optdir):
if not os.path.isdir(cagefs_dir):
create_remount_flag()
if cagefslib.make_dir(cagefs_dir, 0o755):
# failed to create directory. so, we remove need.remount flag because we should not do remount in such case
remove_remount_flag()
sys.exit(1)
if do_mount:
ret = subprocess.call([MOUNT, "-n", "-o", "nosuid", "--rbind", cagefs_dir, cagefs_optdir])
if ret == 0:
ret = subprocess.call([MOUNT, "-n", "-o", "remount,ro,nosuid,bind", cagefs_dir, cagefs_optdir])
if ret != 0:
secureio.print_error("failed to mount", cagefs_dir)
sys.exit(1)
else:
# copy php files to cagefs
for file_name in os.listdir(optdir):
if file_name.endswith('.cagefs'):
continue
orig_path = os.path.join(optdir, file_name)
dest_file = os.path.join(cagefs_dir, file_name)
if os.path.isdir(orig_path):
if cagefslib.copytree(orig_path, dest_file, update=True):
secureio.logging('Error copying '+orig_path+' to '+dest_file, SILENT, 1)
sys.exit(1)
continue
if alias == default_php and file_name in SYMLINK_NAMES:
if switch_symlink(SYMLINK_NAMES[file_name], dest_file):
cagefslib.kill_php(file_name)
if switch_symlink(SYMLINK_NAMES[file_name], dest_file):
sys.exit(1)
dest_file += '.cagefs'
if cagefslib.copy_file(orig_path, dest_file, create_parent_dir=False, update=True):
# Error has occured - kill php processes and try copy again
cagefslib.kill_php(file_name)
if cagefslib.copy_file(orig_path, dest_file, create_parent_dir=False):
secureio.logging('Error copying '+orig_path+' to '+dest_file, SILENT, 1)
sys.exit(1)
if not do_mount:
# delete old (unneeded) files
clean_dir_recursive(CAGEFS_PHP_BASEDIR+'/opt/cpanel', '/opt/cpanel')
# Compare php.conf files and update php.conf file in CageFS when needed
if (config is not None) and (not do_mount):
php_conf = cagefslib.ETC_TEMPLATE_DIR + EA4_PHP_CONF
if not os.path.isfile(php_conf) or cagefslib.is_update_needed(EA4_PHP_CONF, php_conf, use_cache=False):
update_etc_only(config)
def setup_cl_alt(config, options=None):
check_skeleton()
error = create_files_for_symlink_protection()
error = create_dirs_for_symlink_protection() or error
cagefs_da_lib.create_symlink_to_php_ini_for_DA(SKELETON)
cagefs_da_lib.configure_selector_for_directadmin()
# copy lsphp binary if needed
if not os.path.isfile('/usr/local/bin/lsphp'):
if os.path.isfile('/usr/local/lsws/fcgi-bin/lsphp5'):
lsphp_path = os.path.realpath('/usr/local/lsws/fcgi-bin/lsphp5')
cagefslib.copy_file(lsphp_path, '/usr/local/bin/lsphp')
# alt-php versions are installed ?
alt = cagefslib.get_alt_versions()
if alt:
from cagefsreconfigure import litespeed_configure_selector, replace_alt_settings
litespeed_configure_selector()
replace_alt_settings(options)
# Create /opt/alt directory if needed
if not os.path.lexists('/opt/alt'):
try:
mod_makedirs('/opt/alt', 0o755)
create_remount_flag()
except (OSError, IOError):
secureio.print_error('failed to create directory /opt/alt')
sys.exit(1)
add_mounts_for_php_selector()
add_mounts_for_ea_php_sessions()
# Read paths to php binaries from config file
cagefslib.read_native_conf()
# Create directories of CL Selector in etc and convert modules from symlinks to single ini file (if needed)
create_etc_alternatives(all_users = True, repair_symlinks = True)
# Update native php files in etc directory
if cagefslib.is_etc_in_native_conf():
error = update_etc_only(config, print_selector_errors = True) or error
# Update native php files in cagefs-skeleton
for alias, orig_path in cagefslib.orig_binaries.items():
orig_path2 = os.path.realpath(orig_path)
if (not orig_path.startswith('/etc/') and os.path.islink(orig_path) and
not cagefslib.path_is_mounted(orig_path) and cagefslib.copy_file(orig_path, SKELETON + orig_path) == 1):
error = True
if is_ea4_enabled() and alias in ('php', 'php-cli', 'lsphp', 'php.ini'):
# Update php file inside cagefs without replacing it with symlink
if (not orig_path2.startswith('/etc/') and os.path.exists(orig_path2) and
not cagefslib.path_is_mounted(orig_path2) and cagefslib.copy_file(orig_path2, SKELETON + orig_path2) == 1):
error = True
# Create stubs for php files to make selectorctl, cl-selector work as before
if cagefslib.create_php_stub(alias):
error = True
continue
if not orig_path2.startswith('/etc/'):
if cagefslib.path_is_mounted(orig_path2):
secureio.print_error(orig_path2, 'is mounted to CageFS. CloudLinux Selector will not be available.')
error = True
elif not cagefslib.move_to_alternatives(orig_path2, etc = False) and cagefslib.is_mandatory(alias):
secureio.print_error("CloudLinux Selector setup error for path:", orig_path)
error = True
# alt-php versions are installed ?
if alt and (not error):
import cagefs_ispmanager_lib
cagefs_ispmanager_lib.configure_selector_for_ispmanager()
setup_cpanel_multiphp(do_mount=False, config=config)
if not config['skip-php-reload']:
reload_php_for_users()
if not error:
print("CloudLinux Selector setup: successful")
# Checks symlinks in /etc/cl.selector for users and replaces broken symlinks (i.e. symlinks
# to non-existent alternatives) with symlinks to native (original) binaries.
# when force == True, replaces ALL symlinks with symlinks to native (original) binaries
# (i.e. reset "alternatives" settings for users to "native")
# Returns True if error has occured
def remove_etc_alternatives(users = None, all_users = False, force = False):
users = get_cagefs_users(users, all_users)
cl_alt_def_vers, cl_alt_def_modules, cl_alt_def_php_state, cl_alt_def_other = cagefslib.read_cl_alt_defaults()
alt_versions = cagefslib.get_alt_versions()
alt_paths = cagefslib.get_alt_paths(cl_alt_def_php_state)
if cl_alt_def_vers == None:
# global defaults are not set (link files to native binary)
dest_vers = 'native'
elif cl_alt_def_vers == 'native':
# global default version is native (link files to native binary)
dest_vers = 'native'
elif (cl_alt_def_vers not in alt_versions) or ((cl_alt_def_vers in cl_alt_def_php_state) and (not cl_alt_def_php_state[cl_alt_def_vers])):
# default version has been removed or disabled
# link files to native binary
# set global default version to native
dest_vers = 'native'
# write global defaults (owner - root)
cagefslib.write_cl_alt_to_backup(None, dest_vers, cl_alt_def_modules, 0, 0, cl_alt_def_php_state, cl_alt_def_other)
else:
# link files to global default binary (global defaults are set already)
dest_vers = cl_alt_def_vers
native_php_is_disabled = False
if (cl_alt_def_php_state != None) and ('native' in cl_alt_def_php_state) and (not cl_alt_def_php_state['native']):
native_php_is_disabled = True
error = False
for user in users:
pw = secureio.clpwd.get_pw_by_name(user)
prefix = get_user_prefix(user)
dirname = '/'+prefix+'/'+user
userdir = BASEDIR + dirname
LINK_DIR = cagefslib.ETC_CL_ALT_PATH
link_dir = userdir + LINK_DIR
if os.path.isdir(link_dir):
changed = False
# for filename in os.listdir(link_dir):
for filename in cagefslib.get_alt_aliases(dest_vers):
native_path = cagefslib.get_usr_selector_path(filename)
if dest_vers == 'native':
dest_path = native_path
else:
dest_path = cagefslib.get_alt_conf(dest_vers, filename)
if dest_path == None:
continue
link_name = link_dir+'/'+filename
if not os.path.islink(link_name):
if filename == 'php.ini':
changed = True
cagefslib.remove_file_or_dir(link_name)
try:
os.symlink(dest_path, link_name)
except (OSError, IOError):
secureio.logging('Error while creating symlink ' + link_name, SILENT, 1)
error = True
else:
try:
link_to = os.readlink(link_name)
except (OSError, IOError):
secureio.logging('Error: failed to read symlink ' + link_name, SILENT, 1)
error = True
continue
# Switch symlink if native php is disabled
if (link_to == native_path) and (dest_vers != 'native') and (native_php_is_disabled or force):
if filename == 'php.ini':
changed = True
if switch_symlink(dest_path, link_name):
error = True
# Path to alternative (link_to) must exist in real system. So path to skeleton is not added to os.path.lexists(link_to)
# Skeleton may be unmounted, so path SKELETON+link_to may not exist
# Switch symlink if alt-php version has been removed (or disabled)
elif (link_to not in (native_path, dest_path)) and ( force or (link_to not in alt_paths) or (not os.path.lexists(link_to)) ):
if filename == 'php.ini':
changed = True
if switch_symlink(dest_path, link_name):
error = True
# Switch symlink so it will point to directory with modules for alt-php
# For details see CAG-447
if configure_alt_php(pw, dest_vers, force=changed):
error = True
if changed:
reload_processes('php', user)
# read user's backup
def_vers, php_modules, php_state_ignored, other_ignored = cagefslib.read_cl_alt_backup_as_user(pw.pw_dir, pw.pw_uid, pw.pw_gid) # @UnusedVariable
if (def_vers == None or def_vers != dest_vers) and changed:
# Save backup
cagefslib.write_cl_alt_to_backup(pw.pw_dir, dest_vers, php_modules, pw.pw_uid, pw.pw_gid)
return error
def kernel_is_supported():
if is_running_without_lve():
return True
try:
f = open('/proc/lve/list', 'r')
line = f.readline()
f.close()
return bool(line)
except IOError:
return False
def check_kernel():
if not kernel_is_supported():
remove_log_file()
secureio.logging('Error: current running kernel is NOT supported')
sys.exit(1)
config_copy = {}
def do_profiling():
update_cagefs(config_copy)
def run_toggle_plugin_utility():
if not os.path.isfile(PLUGIN_STATE):
return False
try:
ret = subprocess.call([PLUGIN_STATE, "--toggle-plugin"])
if ret != 0:
secureio.logging('Error: '+PLUGIN_STATE, SILENT, 1)
return True
except OSError:
secureio.logging('Error: failed to run '+PLUGIN_STATE, SILENT, 1)
return True
return False
def toggle_plugin():
run_toggle_plugin_utility()
def print_user_status(user):
if cagefs_is_enabled():
if user in get_list_of_users(True):
print('Enabled')
sys.exit(0)
print('Disabled')
sys.exit(1)
def print_cagefs_status():
if cagefs_is_enabled():
print('Enabled')
sys.exit(0)
print('Disabled')
sys.exit(1)
def read_paths_from_file(filename):
""" Read paths separated by commas from a file """
cfg = configparser.ConfigParser(interpolation=None, strict=False)
try:
cfg.read(filename)
except configparser.Error:
return []
return cagefslib.config_get_option_as_list(cfg, filename, 'paths')
def write_paths_to_file(filename, paths, comment = None):
""" Write paths from list to a file """
paths = remove_duplicates(paths)
paths = remove_parent_dirs(paths)
umask_saved = os.umask(0o77)
try:
f = open(filename, 'w')
f.write("[%s]\n" % filename)
if comment:
f.write("comment=%s\n" % comment)
f.write("paths=")
for index in range(len(paths)):
if index > 0:
f.write(", " + paths[index])
else:
f.write(paths[index])
f.write("\n")
f.close()
except IOError as e:
secureio.print_error('Failed to write ' + filename + ' : ' + str(e))
os.umask(umask_saved)
def update_list(config):
""" Read paths from stdin and updates appropriate files in cagefs-skeleton """
check_skeleton()
if not os.path.isdir(cagefslib.ETC_TEMPLATE_DIR+'/etc'):
secureio.print_error('skeleton of etc directory is not found')
sys.exit(1)
cagefslib.mounts = MountpointConfig().common_mounts
files = sys.stdin.readlines()
etc_found = False
for filename in files:
if filename.startswith('/etc/'):
etc_found = True
break
if etc_found:
# Create empty directory for template of etc directory
shutil.rmtree(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', True)
if not os.path.isdir(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc'):
try:
mod_makedirs(cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', 0o755)
except OSError:
secureio.print_error('creating', cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc')
sys.exit(1)
if cagefslib.copytree(cagefslib.ETC_TEMPLATE_DIR+'/etc', cagefslib.ETC_TEMPLATE_NEW_DIR+'/etc', True) == 1:
secureio.print_error('Error while creating skeleton of etc directory')
sys.exit(1)
etc_modified = False
copied_files = []
for filename in files:
filename = filename.strip()
if filename.startswith('/') and (os.path.isfile(filename) or os.path.islink(filename)):
if filename.startswith('/etc/'):
if not cagefslib.copy_file(filename, cagefslib.ETC_TEMPLATE_NEW_DIR+filename, create_parent_dir = True):
print(filename)
etc_modified = True
copied_files.append(filename)
elif (not cagefslib.path_is_mounted(filename)):
if not cagefslib.copy_file(filename, SKELETON+filename, create_parent_dir = True):
print(filename)
copied_files.append(filename)
if etc_modified:
compare_etc_templates()
# CageFS is enabled ?
if (not save_dir_exists()) and etcfs_is_disabled():
update_etc(config)
# Create cfg-file that contains copied paths (needed for cagefsctl --update to update those paths)
UPDATE_LIST_CFG_FILE = CONFIG_DIR + 'cagefsctl-update-list.cfg'
cfg = read_paths_from_file(UPDATE_LIST_CFG_FILE)
cfg.extend(copied_files)
write_paths_to_file(UPDATE_LIST_CFG_FILE, cfg, 'Files added by "cagefsctl --update-list" command')
# Update list of files stored in cagefs-skeleton in order to handle deletion of files in cagefs-skeleton properly
list_of_files = []
load_list(FILES_LIST, list_of_files)
add_parents(copied_files, copied_files)
list_of_files.extend(copied_files)
save_list_of_files_in_skeleton(list_of_files)
def demote(uid, gid):
def func():
os.setgid(gid)
os.setuid(uid)
return func
def tmpwatch():
pw = secureio.clpwd.get_user_dict()
tmpwatch_command = cagefslib.get_tmpwatch_params()
tmpwatch_dirs = set(cagefslib.get_tmpwatch_dirs())
tmpwatch_dirs.add('/var/cache/php-eaccelerator')
if os.path.isdir('/var/lib/php/session'):
tmpwatch_dirs.add('/var/lib/php/session')
if not is_cpanel() and not is_plesk():
# clean php sessions for all alt-php versions (for CP other than cPanel:
# on cPanel sessions are cleaned by /usr/share/cagefs/clean_user_php_sessions script
# on Plesk sessions are cleaned by /usr/share/cagefs/clean_user_alt_php_sessions_plesk script
# executed via /etc/cron.d/cpanel_php_sessions_cron)
alt_php_dirs = cagefslib.get_alt_dirs()
for php_dir in alt_php_dirs:
tmpwatch_dirs.add('/opt/alt/%s/var/lib/php/session' % php_dir)
dev_null = open('/dev/null', 'w')
for user in pw:
cmd = tmpwatch_command.split()
line = pw[user]
tmp_path = os.path.join(line.pw_dir, '.cagefs/tmp')
# we assume that tmp directory always exists when cagefs is enabled
# whenever there aren't tmp dir, we assume that cagefs isn't enabled
if os.path.isdir(tmp_path):
cmd.append(tmp_path)
for dir_name in tmpwatch_dirs:
dir_path = os.path.join(line.pw_dir, '.cagefs') + dir_name
if os.path.isdir(dir_path):
cmd.append(dir_path)
subprocess.call(cmd, stderr=dev_null, stdout=dev_null, preexec_fn=demote(line.pw_uid, line.pw_gid), cwd=line.pw_dir)
def invalid_homes_exist():
# get all users from /etc/passwd
pw = secureio.get_pwd_dict()
for user in pw:
if pw[user].pw_dir.find('cagefs-skeleton') != -1:
return True
return False
def get_users_from_args(args):
users = []
for username in args:
if user_exists(username):
users.append(username)
else:
secureio.print_error('user', username, 'does not exist')
sys.exit(1)
return users
def is_user_enabled(username):
"""
Check that cagefs enabled for user
"""
try:
uid = secureio.clpwd.get_uid(username)
except ClPwd.NoSuchUserException:
return False
user_prefix = get_user_prefix(username)
get_min_uid()
if uid >= MIN_UID:
if os.path.exists(enabled_dir) and os.path.exists(enabled_dir + '/' + str(user_prefix) + '/' + username):
return True
if os.path.exists(disabled_dir) and not os.path.exists(disabled_dir + '/' + str(user_prefix) + '/' + username):
return True
return False
def cpetc_for_user(username, config):
user_etc_path = BASEDIR + '/' + get_user_prefix(username) + '/' + username + '/etc'
custom_etc_files = cagefslib.get_additional_etc_files_for_user(username, user_etc_path)
custom_etc_files2 = cagefslib.cut_path(custom_etc_files, user_etc_path)
copyetc(username, config, ignore_errors = False, recreate = False, custom_etc_files = custom_etc_files)
cagefslib.update_custom_etc_files_for_user(username, user_etc_path)
cagefslib.save_custom_etc_log(username, custom_etc_files2)
# Add the spamassassin directories to CageFs in cPanel
def add_spamassassin_dirs_cpanel():
# Add spamassassin directories to cagefs.mp file
try:
# exit, if cagefs.mp absent
if not os.path.isfile(ETC_MPFILE):
return
# 1. Read cagefs.mp file
f = open(ETC_MPFILE, 'r')
cagefs_mp_lines = f.readlines()
f.close()
cagefs_mp_lines = [l.strip() for l in cagefs_mp_lines]
# 2. Modify cagefs.mp contents
for line in SPAMASSASSIN_DIRS_FOR_CAGEFS:
line_to_check_and_write = '!'+line
if line_to_check_and_write not in cagefs_mp_lines and os.path.isdir(line):
cagefs_mp_lines.append(line_to_check_and_write)
cagefs_mp_lines = [l+'\n' for l in cagefs_mp_lines]
# 3. Write cagefs.mp back
f = open(ETC_MPFILE, 'w')
f.writelines(cagefs_mp_lines)
f.close()
except OSError as e:
print('Error:', str(e), file=sys.stderr)
def unmount_dir_in_lve(path: str, lve_list: List[int]) -> None:
"""
Unmount path in all LVE namespaces.
Enter to LVE and unmount directory without destroying LVE.
:param: path `str` path for unmount
:param: lve_list `list` list of id's for existing LVEs
:return: None
"""
for lve_id in lve_list:
p = subprocess.Popen([LVE_UMOUNT, str(lve_id), path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
_, strerr = p.communicate()
if VERBOSE:
if p.returncode:
# error occured
secureio.print_error('LVE', lve_id, strerr.strip())
else:
print('Unmount for LVE', lve_id, 'succeeded')
def unmount_dir_for_all_processes(path: str) -> None:
"""
Unmount directory in all mount namespaces of all processes running in a system
:param path: absolute path to directory to unmount
"""
# get list of PIDs of all running processes
ps_cmd = ['/bin/ps', '--no-headers', '-xao', 'pid']
p = subprocess.run(ps_cmd, capture_output=True, text=True)
if p.returncode:
# error occured
secureio.print_error('failed to execute:', *ps_cmd,
'return code:', p.returncode, 'stderr:', str(p.stderr).strip())
sys.exit(1)
pids = p.stdout.split()
for pid in pids:
if pid:
# enter mount namespace and unmount directory ignoring errors
p = subprocess.run(['/usr/bin/nsenter', '-m', '-t', pid, UMOUNT, '-l', path],
capture_output=True, text=True)
if VERBOSE:
if p.returncode:
# error occured
secureio.print_error('PID', pid, 'stderr:', str(p.stderr).strip())
else:
print('Unmount for PID', pid, 'succeeded')
def unmount_dir(dir_list: List[str]) -> None:
"""
Unmount directories from list in all mount namespaces
:param dir_list: list of paths to directories for unmounting
"""
# check if all directories are unmounted in real FS
mounted_dirs = cagefslib.get_mounted_dirs(all_mounts=True)
found = False
for directory in dir_list:
if os.path.realpath(directory) in mounted_dirs:
found = True
secureio.print_error('directory', directory, 'is mounted. ',
'Please unmount the directory before running this command.')
if found:
sys.exit(1)
# update namespace template using "lvectl start", so subsequent enters
# to CageFS/namespace will be with updated set of mounts
lvectl_start()
for directory in dir_list:
# unmount directory in all existing LVE/namespaces
unmount_dir_in_lve(directory, get_lve_list())
unmount_dir_for_all_processes(directory)
def unmount_user(user_name):
"""
Unmount CageFS for user. Return True if error has occured
:param user_name: name of user
:type user_name: str
"""
try:
pw = secureio.clpwd.get_pw_by_name(user_name)
except ClPwd.NoSuchUserException:
secureio.print_error("User", user_name, "does not exists")
return True
if is_running_without_lve():
return cagefs_without_lve_lib._delete_namespace_user(user_name)
# acqire lock
prefix = get_user_prefix(user_name)
lock_path = os.path.join(BASEDIR, prefix, user_name+'.lock')
dir_path = os.path.dirname(lock_path)
cagefslib.make_dir(dir_path, 0o751)
_ = acquire_lock(lock_path, wait=True, quiet=True)
# destroy and recreate LVE in order to remove effect of chroot/pivot_root syscalls
# (otherwise we will be not able to unmount all CageFS mounts)
if remount([user_name]):
secureio.print_error("Failed to destroy/apply LVE for user", user_name)
return True
# enter LVE/namespace and unmount all CageFS mounts
# use '-mek' options for lve_suwrapper in order to disable PMEM, NPROC, EP limits
# and also prevent killing cagefsctl process inside LVE by lvectl destroy
cmd = ["/bin/lve_suwrapper", '-mek', str(pw.pw_uid), "/usr/sbin/cagefsctl", "--unmount-cur-ns"]
try:
subprocess.call(cmd, shell=False)
except OSError:
secureio.print_error(*cmd)
return True
return False
def check_cagefs_skeleton():
"""
Checks that cagefs skeleton exists and is not empty
"""
return os.path.isdir(os.path.join(SKELETON, 'bin'))
def print_cagefs_skeleton_status():
if not check_cagefs_skeleton():
print(SKELETON_NOT_INITIALIZED)
sys.exit(1)
print(SKELETON_INITIALIZED)
def main():
import syslog
syslog.openlog(native_str('cagefsctl'))
try:
main_func()
except SystemExit as e:
sys.exit(int(str(e)))
except Exception as e:
cagefslib.print_exception(level = syslog.LOG_ERR, includetraceback = True)
sys.exit(1)
def init_min_uid():
# Read MIN_UID from file
get_min_uid()
# Copy MIN_UID to securelve module
secureio.MIN_UID = MIN_UID
# create ClPwd instance
secureio.clpwd = ClPwd(min_uid = MIN_UID)
init_min_uid()
def _get_username_list_from_args(args: List[str]) -> List[str]:
"""
Retrives users list from cmd line
:param args: args list
:return: users list
"""
users_list = []
for username in args:
try:
pw_db = secureio.clpwd.get_pw_by_uid(int(username))
for pw in pw_db:
users_list.append(pw.pw_name)
except (ValueError, ClPwd.NoSuchUserException):
if user_exists(username):
users_list.append(username)
else:
secureio.print_error('user or UID', username, 'does not exist')
sys.exit(1)
return users_list
def exit_if_lve_supported():
"""
Print error and exit when LVE is supported
"""
if not is_running_without_lve():
print("ERROR: This command is workable only in environments without LVE support")
sys.exit(1)
def exit_if_lve_not_supported():
"""
Print error and exit when LVE is not supported
"""
if is_running_without_lve():
print("ERROR: This command is not supported in environments without LVE support")
sys.exit(1)
def create_namespaces(users : Optional[List[str]] = None, do_mount_skel : bool = False) -> bool:
exit_if_lve_supported()
check_skeleton()
remove_nested_skeleton()
if do_mount_skel and not skeleton_is_mounted():
mount_skeleton()
if users is None:
users = get_enabled_users()
return cagefs_without_lve_lib.create_namespace_user_list(users)
def delete_namespaces(users : Optional[List[str]] = None) -> bool:
exit_if_lve_supported()
if users is None:
users = get_cagefs_users(all_users=True)
return cagefs_without_lve_lib.delete_namespace_user_list(users)
def clean_without_lve_environment() -> int:
exit_if_lve_supported()
rc = 0
rc += delete_namespaces()
cagefs_without_lve_lib.restore_httpd_php_fpm_services()
cagefs_universal_hook_lib.remove_without_lve_universal_hooks()
return rc
def main_func():
global SKELETON, do_not_ask_option, config_copy, VERBOSE
try:
options_list = ["update", "dont-clean", "reinit", "help", 'version',
"verbose", "force", 'hardlink', 'init', 'remove-all', 'set-tmpwatch=', 'tmpwatch',
'list', 'help', 'unmount', 'unmount-dir', 'unmount-all', 'unmount-really-all', 'enable', 'disable',
'enable-all', 'disable-all', 'display-user-mode', 'list-enabled', 'wait-lock',
'list-disabled', 'create-mp', 'check-mp', 'mount-skel', 'unmount-skel', 'remount-all', 'remount',
'addrpm','delrpm', 'enter=', 'enable-cagefs', 'disable-cagefs','do-not-ask',
'debug', 'profiling', 'migrate-prefixes', 'getprefix=', 'list-rpm', 'apply-global-php-ini',
'set-min-uid=', 'get-min-uid', 'toggle-mode', 'silent', 'cpetc', 'update-etc', 'force-update-etc',
'check-kernel-version', 'update-wrappers', 'remove-blacklisted', 'detect-postgres', "toggle-plugin", 'print-suids',
'hook-install', 'hook-remove', 'reconfigure-cagefs', 'configure-litespeed',
'clean-var-cagefs', 'user-status=', 'cagefs-status', 'update-list', 'rebuild-alt-php-ini',
'validate-alt-php-ini', 'setup-cl-selector', 'skip-php-reload', 'check-for-unsafe-mounts',
'remove-cl-selector', 'cl-selector-reset-versions', 'setup-cl-alt', 'remove-cl-alt', 'cl-selector-reset-modules',
'update-users-status', "update-users-status-fix-owner", 'set-default-user-status', 'remove-unused-mount-points',
'create-homeN-dirs-in-skeleton', 'unmount-cur-ns', 'configure-openlitespeed',
'enable-cagefs-without-etc-update', 'without-lock', 'set-update-period=', 'force-update', 'add-default-rpm-packages',
'create-virt-mp', 'create-virt-mp-all', 'remount-virtmp', 'list-logged-in', 'clean-config-dirs',
'create-dirs-for-symlink-protection', 'sanity-check', 'check-cagefs-initialized'
]
if is_running_without_lve():
options_list.extend(['create-namespace', 'create-namespaces',
'delete-namespace', 'delete-namespaces',
'clean-without-lve-environment'])
opts, args = getopt.getopt(sys.argv[1:], "ihvVfurdkwW?lmMe:", options_list)
except getopt.GetoptError as e:
usage()
print("\nError:", str(e))
sys.exit(1)
import cagefsreconfigure
import virtmp_mount
# Logging all args
from clcommon import ClAuditLog
log = ClAuditLog ( INFO_LOG_FILE )
log.info_log_write ()
config = {}
config['verbose'] = 0
for o, a in opts:
if o in ("-h", "-?", "--help"):
usage()
sys.exit(0)
elif o in ("-V", "--version"):
print(cagefs_version)
sys.exit(0)
elif o in ("--check-kernel-version",):
check_kernel()
sys.exit(0)
elif o in ("-v", "--verbose"):
config['verbose'] = 1
VERBOSE = 1
cagefslib.VERBOSE_FLAG = 1
elif o in ("--silent",):
global SILENT
SILENT=1
secureio.SILENT_FLAG = 1
if (os.geteuid()!=0):
secureio.print_error('root privileges required. Abort.')
sys.exit(5)
# remove possible symlinks from path to cagefs-skeleton
try:
SKELETON = os.path.realpath(SKELETON)
cagefslib.SKELETON = SKELETON
except:
sys.stderr.write('Error while determining real path to skeleton directory '+SKELETON+'\n')
sys.exit(1)
config['dry-run'] = 0
config['interactive'] = 0
config['unmount'] = 0
config['enable'] = 0
config['disable'] = 0
config['force'] = 0
config['update'] = 0
config['reinit'] = 0
config['dont-clean'] = 0
config['hardlink'] = 0
config['init'] = 0
config['remount'] = 0
config['profiling'] = 0
config['force-update'] = 0
config['force-update-etc'] = False
config['skip-php-reload'] = False
manage_user_flag = False
build_jail_flag = False
lock_is_required = True
wait_lock = False
for o, a in opts:
if o in ('--getprefix',):
print(get_user_prefix(a))
sys.exit(0)
elif o in ("-d", "--dont-clean"):
config['dont-clean'] = 1
build_jail_flag = True
elif o in ("--cpetc",):
if (len(args) == 0):
secureio.print_error('no username or UID specified')
sys.exit(2)
for username in args:
try:
pw_db = secureio.clpwd.get_pw_by_uid(int(username))
for pw in pw_db:
cpetc_for_user(pw.pw_name, config)
cagefslib.create_utmp_for_user(pw.pw_name)
except (ValueError, ClPwd.NoSuchUserException):
if user_exists(username):
cpetc_for_user(username, config)
cagefslib.create_utmp_for_user(username)
else:
secureio.print_error('user or UID', username, 'does not exist')
sys.exit(1)
sys.exit(0)
elif o in ('--clean-config-dirs',):
clean_config_dirs()
sys.exit(0)
elif o in ('--skip-php-reload',):
config['skip-php-reload'] = True
elif o in ('--create-dirs-for-symlink-protection',):
create_dirs_for_symlink_protection()
create_files_for_symlink_protection()
sys.exit(0)
elif o in ('--sanity-check',):
import sanity_check
sanity_check.check()
sys.exit(0)
elif o in ('--hook-install',):
cagefshooks.HooksInstall()
sys.exit(0)
elif o in ('--hook-remove',):
cagefshooks.HooksRemove()
sys.exit(0)
elif o in ('--reconfigure-cagefs',):
cagefsreconfigure.reconfigure_cagefs()
sys.exit(0)
elif o in ('--configure-litespeed',):
cagefsreconfigure.litespeed_configure()
sys.exit(0)
elif o in ('--configure-openlitespeed',):
cagefsreconfigure.configure_open_litespeed()
sys.exit(0)
elif o in ('--list-enabled', '--list-disabled'):
check_exclude()
list_users(o == '--list-enabled')
sys.exit(0)
elif o in ("--cl-selector-reset-modules",):
users = get_users_from_args(args)
reset_modules_to_default(users)
sys.exit(0)
elif o in ("--rebuild-alt-php-ini",):
# Retrive user names from args
users = get_users_from_args(args)
rebuild_alt_php_ini(users)
sys.exit(0)
elif o in ("--validate-alt-php-ini",):
# Retrive user names from args
users = get_users_from_args(args)
check_php_ini_options(users)
sys.exit(0)
elif o in ("--cl-selector-reset-versions", "--remove-cl-selector", "--remove-cl-alt"):
users = get_users_from_args(args)
if users:
remove_etc_alternatives(users = users, force = (o == "--cl-selector-reset-versions"))
else:
remove_etc_alternatives(all_users = True, force = (o == "--cl-selector-reset-versions"))
remove_mounts_for_php_selector()
sys.exit(0)
elif o in ('-W', '--unmount-all', '--unmount-really-all'):
# Unmounting for CageFS 2.0 is needed ?
if (not os.path.exists('/etc/cagefs/etc.safe')) and (not os.path.exists(PROXY_COMMANDS)):
repair_homes.umount_all()
else:
# Do unmounting for CageFS 3.0
unmount_all(remount_users = True, check_busy = False, all_cagefs_mounts = (o == '--unmount-really-all'))
sys.exit(0)
elif o in ("--unmount-dir",):
exit_if_lve_not_supported()
# check directory in args list existing
if (len(args) == 0):
secureio.print_error('no directory to unmount specified')
sys.exit(2)
unmount_dir(args)
sys.exit(0)
elif o in ("--migrate-prefixes",):
migrate_to_new_prefixes()
sys.exit(0)
elif o in ('--set-min-uid',):
set_min_uid(a)
check_exclude()
sys.exit(0)
elif o in ('--get-min-uid',):
print(MIN_UID)
sys.exit(0)
elif o in ('--set-update-period',):
cagefslib.set_update_period(a)
sys.exit(0)
elif o in ('--force-update',):
config['force-update'] = 1
config['update'] = 1
build_jail_flag = True
check_skeleton()
elif o in ('--clean-var-cagefs',):
clean_var_cagefs()
sys.exit(0)
elif o in ('--update-wrappers',):
load_wrappers(update_wrappers = True)
sys.exit(0)
elif o in ('--remove-blacklisted',):
load_black_list(remove = True)
sys.exit(0)
elif o in ('--detect-postgres',):
cagefslib.detect_postgres()
sys.exit(0)
elif o in ('--print-suids',):
cagefslib.mounts = MountpointConfig().common_mounts
# Print list of SUID and SGID programs in skeleton
cagefslib.print_suids(SKELETON)
sys.exit(0)
elif o in ('--toggle-plugin',):
toggle_plugin()
sys.exit(0)
elif o in ('--display-user-mode',):
print_user_mode()
sys.exit(0)
elif o in ('--user-status',):
check_exclude()
print_user_status(a)
sys.exit(0)
elif o in ('--cagefs-status',):
print_cagefs_status()
sys.exit(0)
elif o in ('--check-cagefs-initialized',):
print_cagefs_skeleton_status()
sys.exit(0)
elif o in ("--disable-cagefs",):
old_enabled_users = get_enabled_users()
disable_cagefs()
update_users_status(disable_all=True, old_enabled_users=old_enabled_users)
remount_all()
sys.exit(0)
elif o in ("--update-users-status", "--update-users-status-fix-owner"):
if o == "--update-users-status-fix-owner":
get_mounted_users(fix_permissions=True)
if cagefs_is_enabled():
old_enabled_users = get_enabled_users()
check_exclude()
update_users_status(fix_owner=(o == "--update-users-status-fix-owner"),
old_enabled_users=old_enabled_users)
sys.exit(0)
elif o in ("--set-default-user-status",):
# For use in postwwwacct hook only
if args:
mode = get_user_mode()
if mode == 'Enable All':
old_enabled_users = get_enabled_users()
for username in args:
toggle_user(username, True)
update_users_status(users=args, status=True, old_enabled_users=old_enabled_users)
elif mode == 'Disable All':
old_enabled_users = get_enabled_users()
for username in args:
toggle_user(username, False)
update_users_status(users=args, status=False, old_enabled_users=old_enabled_users)
sys.exit(0)
elif o in ("--remove-unused-mount-points",):
remove_unused_mount_points()
sys.exit(0)
elif o in ('--create-homeN-dirs-in-skeleton',):
check_skeleton()
cagefslib.mounts = MountpointConfig().common_mounts
create_homeN_dirs_in_skeleton()
update_homeN_symlinks_in_skeleton()
sys.exit(0)
elif o in ("--mount-skel", "--unmount-skel"):
lock_is_required = False
elif o in ("-w", "--unmount", "-m", "--remount", "--enable", "--disable"):
if is_running_without_lve():
wait_lock = True
else:
lock_is_required = False
elif o in ("--without-lock",):
lock_is_required = False
elif o in ('--wait-lock',):
wait_lock = True
elif o in ('--enable-cagefs-without-etc-update',):
old_enabled_users = get_enabled_users()
enable_cagefs()
enable_cagefs_for_users_with_duplicate_uids()
update_users_status(old_enabled_users=old_enabled_users)
check_skeleton()
remove_nested_skeleton()
# Remount skeleton and all users
mount_skeleton(True)
sys.exit(0)
elif o in ("--add-default-rpm-packages",):
add_default_rpm_packages_to_cagefs()
sys.exit(0)
elif o in ('--tmpwatch',):
tmpwatch()
sys.exit(0)
elif o in ('--set-tmpwatch',):
cagefslib.set_tmpwatch_params(a)
sys.exit(0)
elif o in ('--create-virt-mp',):
if len(sys.argv) != 3:
secureio.print_error("No username provided")
sys.exit(1)
virtmp_mount.create_virtmp(sys.argv[2])
sys.exit(0)
elif o in ('--create-virt-mp-all',):
virtmp_mount.create_virtmp()
sys.exit(0)
elif o in ('--remount-virtmp',):
if len(args) != 1:
secureio.print_error("No username provided")
sys.exit(1)
virtmp_mount.create_virtmp(args[0])
# --remount USER
check_save_dir()
check_skeleton()
remove_nested_skeleton()
config['remount'] = 1
manage_user_flag = True
elif o in ('--enter','-e'):
check_save_dir()
check_skeleton()
check_kernel()
remove_nested_skeleton()
print('You are entering to CageFS for user', a, 'as superuser (root).')
print('NOTE: You can use "su -s /bin/bash - {}" instead to enter to CageFS as user {}'.format(a, a))
try:
subprocess.call(["/sbin/cagefs_enter_user", "--root", a, "/bin/bash"], shell=False)
except Exception:
secureio.print_error('executing /sbin/cagefs_enter_user')
sys.exit(1)
sys.exit(0)
elif o in ("--check-mp",):
check_mp_file()
sys.exit(0)
elif o in ("--check-for-unsafe-mounts",):
if unsafe_mounts_exist():
create_remount_flag()
sys.exit(0)
elif o in ('-l', '--list'):
exit_if_lve_not_supported()
users = get_mounted_users()
print_users(users, 1, 'CageFS currently mounted for users:')
sys.exit(0)
elif o in ('--list-logged-in',):
exit_if_lve_not_supported()
users = get_logged_in_users()
print_users(users, 1, 'Users currently logged in CageFS via ssh:')
sys.exit(0)
elif o in ('--unmount-cur-ns',):
# CAG-749: unmount CageFS mounts in all mount namespaces (resolve conflict with systemd)
umount_skeleton(save_mounts = False, all_cagefs_mounts = True, current_namespace_only=True)
sys.exit(0)
elif o in ('--delete-namespace',):
username_list = _get_username_list_from_args(args)
sys.exit(int(delete_namespaces(username_list)))
elif o in ('--delete-namespaces',):
sys.exit(int(delete_namespaces()))
elif o in ('--clean-without-lve-environment',):
sys.exit(clean_without_lve_environment())
check_kernel()
if lock_is_required:
# Acquire lock
lockfile = acquire_lock(wait=wait_lock) # @UnusedVariable
ex_list = get_exclude_user_list()
check_exclude(ex_list)
if SKELETON.find('home') != -1:
if os.path.isdir('/var/cpanel'):
if invalid_homes_exist():
print_cpanel_home_warning()
for o, a in opts:
if o in ('--mount-skel',):
check_skeleton()
remove_nested_skeleton()
mount_skeleton()
sys.exit(0)
elif o in ('--unmount-skel',):
check_skeleton()
umount_skeleton()
# cagefs_fuse('stop')
sys.exit(0)
elif o in ("--debug",):
cagefslib.debug_option = True
elif o in ("--profiling",):
config['profiling'] = 1
build_jail_flag = True
elif o in ("--do-not-ask",):
do_not_ask_option = True
elif o in ("-w", "--unmount"):
check_save_dir()
config['unmount'] = 1
manage_user_flag = True
elif o in ("--enable-cagefs",):
old_enabled_users = get_enabled_users()
enable_cagefs()
enable_cagefs_for_users_with_duplicate_uids()
update_users_status(old_enabled_users=old_enabled_users)
check_skeleton()
remove_nested_skeleton()
if etcfs_is_disabled():
update_etc(config)
# Remount skeleton and all users
mount_skeleton(True)
sys.exit(0)
elif o in ('--enable',):
check_save_dir()
check_skeleton()
remove_nested_skeleton()
config['enable'] = 1
manage_user_flag = True
elif o in ('--disable',):
check_save_dir()
config['disable'] = 1
manage_user_flag = True
elif o in ('--remove-all',):
remove_all()
sys.exit(0)
elif o in ('-M', '--remount-all'):
check_save_dir()
check_skeleton()
remove_nested_skeleton()
# Remount skeleton and all users
mount_skeleton(True)
sys.exit(0)
elif o in ("-m", "--remount"):
check_save_dir()
check_skeleton()
remove_nested_skeleton()
config['remount'] = 1
manage_user_flag = True
elif o in ('--enable-all',):
old_enabled_users = get_enabled_users()
check_skeleton()
remove_nested_skeleton()
set_user_mode(True)
update_users_status(old_enabled_users=old_enabled_users)
if etcfs_is_disabled():
update_etc(config)
# Remount skeleton and all users
mount_skeleton(True)
sys.exit(0)
elif o in ('--disable-all',):
old_enabled_users = get_enabled_users()
set_user_mode(False)
update_users_status(old_enabled_users=old_enabled_users)
remount_all()
sys.exit(0)
elif o in ('--toggle-mode',):
toggle_mode()
sys.exit(0)
elif o in ("-f", "--force"):
config['force'] = 1
build_jail_flag = True
check_skeleton()
elif o in ("-u", "--update"):
config['update'] = 1
build_jail_flag = True
check_skeleton()
elif o in ("-i", "--init"):
if os.path.isdir(SKELETON+'/bin'):
secureio.logging('Error : directory %s already exists.\nUse "%s --reinit" if you want to reinitialize CageFS' % (SKELETON, sys.argv[0]))
sys.exit(1)
config['init'] = 1
build_jail_flag = True
user_mode = get_user_mode()
# mode is not set yet and CageFS is enabled ?
if (user_mode == 'Error' or user_mode == 'Not Initialized') and (not save_dir_exists()):
# set mode to "Disable All"
set_user_mode(False)
elif o in ("-r", "--reinit"):
config['reinit'] = 1
build_jail_flag = True
check_skeleton()
elif o in ("-k", "--hardlink"):
config['hardlink'] = 1
build_jail_flag = True
elif o in ("--create-mp",):
create_mp(True)
sys.exit(0)
elif o in ("--addrpm",):
for rpm in args:
addrpm(rpm)
sys.exit(0)
elif o in ("--delrpm",):
for rpm in args:
delrpm(rpm)
sys.exit(0)
elif o in ("--list-rpm",):
list_rpm()
sys.exit(0)
elif o in ('--update-list',):
update_list(config)
sys.exit(0)
elif o in ("--update-etc", '--force-update-etc'):
config['force-update-etc'] = (o == '--force-update-etc')
users = get_users_from_args(args)
if users:
update_etc_only(config, users = users)
else:
update_etc_only(config)
sys.exit(0)
elif o in ("--setup-cl-selector", "--setup-cl-alt"):
setup_cl_alt(config)
sys.exit(0)
elif o in ("--apply-global-php-ini",):
# alt-php versions are installed ?
if cagefslib.get_alt_versions():
cagefsreconfigure.replace_alt_settings(options=args)
sys.exit(0)
elif o in ('--create-namespace',): # for USER
username_list = _get_username_list_from_args(args)
sys.exit(int(create_namespaces(username_list, do_mount_skel=True)))
elif o in ('--create-namespaces',): # For all users
sys.exit(int(create_namespaces(do_mount_skel=True)))
if manage_user_flag and build_jail_flag:
usage()
print("\nError: incompatible options specified")
sys.exit(1)
if manage_user_flag:
if (len(args)==0):
print()
print('aborted, no username specified')
sys.exit(2)
if config['unmount'] != 1 and not is_running_without_lve():
# CAG-770: unshare mount namespace, so mounting/unmounting cagefs-skeleton will not affect all system
unshare.unshare(unshare.CLONE_NEWNS)
try:
if (config['unmount'] == 1):
error = False
for username in args:
error = unmount_user(username) or error
sys.exit(int(error))
elif (config['disable'] == 1):
old_enabled_users = get_enabled_users()
usernames = []
for username in args:
if user_exists(username):
toggle_user(username, False)
usernames.append(username)
else:
secureio.print_error('user '+username+' does not exist')
update_users_status(users=usernames, status=False, old_enabled_users=old_enabled_users)
if is_running_without_lve():
delete_namespaces(usernames)
else:
remount(usernames)
elif (config['remount'] == 1) or (config['enable'] == 1):
old_enabled_users = get_enabled_users()
mount_skeleton()
usernames = []
for username in args:
if user_exists(username):
if username not in ex_list:
if (config['enable'] == 1):
toggle_user(username, True)
if etcfs_is_disabled():
update_etc(config, [username], False)
usernames.append(username)
elif config['enable'] == 1:
secureio.print_error('user '+username+' is excluded')
else:
secureio.print_error('user '+username+' does not exist')
if config['enable'] == 1:
update_users_status(users=usernames, status=True, old_enabled_users=old_enabled_users)
remount(usernames)
else:
secureio.print_error('No options specified. Nothing to do...')
sys.exit(2)
except KeyboardInterrupt:
print()
print('aborted.. ')
sys.exit(1)
elif build_jail_flag:
jail = SKELETON
if (config['dont-clean'] and (config['init'] or config['reinit'])):
secureio.print_error('cannot specify --dont-clean with --init or --reinit options')
sys.exit(1)
count_of_modes = config['init'] + config['reinit'] + config['update'] + config['force']
if config['profiling'] == 0:
if count_of_modes != 1:
secureio.print_error('you should specify one of the --init, --reinit, --update or --force options\n')
sys.exit(1)
if config['reinit'] == 1:
users = get_mounted_users()
users_count = len(users)
if users_count != 0:
print('WARNING: ', users_count, 'CageFS currently mounted.')
print('If you proceed, CageFS will be temporarily disabled and unmounted.')
confirm("Do you want to continue (yes/no)? ")
if cagefs_is_enabled():
config['cagefs_was_enabled'] = 1
disable_cagefs()
# update ISP manager user wrappers
update_users_status(disable_all=True)
unmount_all(remount_users=True, check_busy=False)
try:
jbuf = os.lstat(jail+'.old')
if stat.S_ISDIR(jbuf[stat.ST_MODE]):
shutil.rmtree(jail+'.old')
else:
os.unlink(jail+'.old')
except (OSError, IOError, shutil.Error):
pass
try:
os.rename(jail, jail+'.old')
except (OSError, IOError):
pass
try:
mod_makedirs(jail, 0o755)
except (OSError, IOError):
pass
if config['profiling'] == 1:
config_copy = config
# one of the init, reinit, update or force options is specified ?
if count_of_modes == 1:
import profile
profile.run('cagefsctl.do_profiling()', LIBDIR+'/profiling.log')
import pstats
p = pstats.Stats(LIBDIR+'/profiling.log')
print()
print('--------------------------------------')
print('Cumulative time:')
p.sort_stats('cumulative').print_stats(20)
print()
print('--------------------------------------')
print('Total time:')
p.sort_stats('time').print_stats(20)
else:
update_cagefs(config)
if config['init'] or config['reinit']:
config['init'] = config['reinit'] = 0
setup_cl_alt(config)
else:
usage()
sys.exit(1)
if __name__ == "__main__":
main()
Hacked By AnonymousFox1.0, Coded By AnonymousFox