Mini Shell
# -*- 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 division
from __future__ import absolute_import
import sys
import os
import pwd
from future.moves import configparser as ConfigParser
import subprocess
import signal
import re
import tempfile
from builtins import map
from stat import S_IRUSR, S_IWUSR, S_IRGRP, S_IROTH
# To keep all configuration under one name
selector_conf = {
'ALTERNATIVES_CONFIG':'/etc/cl.selector/selector.conf',
'DEFAULTS_CONFIG':'/etc/cl.selector/defaults.cfg',
'CAGEFS_PATH':'/var/cagefs',
'NATIVE_PATH':'/usr/share/cagefs-skeleton/usr/selector',
'CAGEFS_CHECK_PATH':'/usr/share/cagefs-skeleton/bin',
'USER_SKELETON_PATH':'/usr/selector',
'SYS_ALT_PATH':'/opt/alt',
'PHP_INI':'alt_php.ini',
'NATIVE_CONF':'/etc/cl.selector/native.conf',
'NEW_INI_PATH':'/usr/share/cagefs-skeleton/usr/selector.etc/php.ini'
}
# Conflicting extesions that must not be enabled together
conflicts = {
'eaccelerator':set(['apc','xcache', 'xcache_3']),
'apc':set(['eaccelerator','xcache','zend_optimizer', 'xcache_3']),
'xcache':set(['apc','eaccelerator', 'xcache_3']),
'xcache_3':set(['apc','eaccelerator', 'xcache']),
'zend_optimizer':set(['apc']),
'idn':set(['intl']),
'intl':set(['idn']),
}
def user_check(username):
try:
pwd.getpwnam(username)
except KeyError:
return None
return str( pwd.getpwnam(username).pw_uid )[-2:]
def cagefs_user_check(username):
"""
Check that cagefs enabled for user
"""
LIBDIR = '/usr/share/cagefs'
sys.path.append(LIBDIR)
try:
import cagefsctl
except ImportError:
print('ERROR: CageFS not installed.')
sys.exit(1)
try:
return cagefsctl.is_user_enabled(username)
except AttributeError:
print('ERROR: CageFS version is unsupported. Please update CageFS.')
sys.exit(1)
def process_config(item):
"""
Parses selector config file and returns an installed
selector versions in the dictionary
config file format:
php 5.4 5.4.9 /opt/alt/php54/usr/bin/php-cgi
php-cli 5.4 5.4.9 /opt/alt/php54/usr/bin/php
php-fpm 5.4 5.4.9 /opt/alt/php54/usr/sbin/php-fpm
php.ini 5.4 5.4.9 /opt/alt/php54/etc/php.ini
"""
try:
data = getattr( process_config,'data' )
return data
except AttributeError:
pass
names = {}
if not os.path.isfile(selector_conf['ALTERNATIVES_CONFIG']):
print('WARN:Alternatives config not found. Using native version')
return names
for line in open(selector_conf['ALTERNATIVES_CONFIG']):
stripped_line = line.strip()
if stripped_line == "":
continue
subj, major, full, path = stripped_line.split() # parsing line by spaces
if item not in subj:
continue
if major not in names:
names[major] = {}
names[major]['full'] = full
if 'paths' not in names[major]:
names[major]['paths'] = {}
names[major]['paths'][subj] = path
process_config.data = names
return names
def process_defaults(item, version=None):
"""
Parses defaults config and returns state of
versions (enabled/disabled) and default version
"""
c = ConfigParser.ConfigParser(interpolation=None, strict=False)
config = c.read( [ selector_conf['DEFAULTS_CONFIG'] ] )
# if no defaults.cfg, native is assumed to be default
if not config:
if not version:
return {'native':set(['default'])}
return ''
if version:
if c.has_option(item + version, 'modules'):
return c.get(item + version, 'modules')
else:
return ''
default_version = 'native' # set default to native to start with
versions = {}
for i in c.sections():
if i.startswith(item):
#strip interpreter name from version
v = i[len(item):]
versions[v] = set()
# add 'disabled' flag to versions set if disabled
if c.has_option(i,'state') and c.get(i,'state') == 'disabled':
versions[v].add('disabled')
if c.has_option('versions',item):
default_version = c.get('versions',item)
if not default_version in versions:
versions[default_version] = set()
# mark default version
versions[default_version].add('default')
return versions
def process_conflicts(extlist):
"""
Get a comma-separated extensions string
and returns extensions set having taken
into account conficting extensions
"""
if not extlist:
return set()
if type(extlist) is str:
extlist = extlist.split(',')
# extensions to enable
extset = set()
for ext in extlist:
if ext in conflicts:
i = extset.intersection(conflicts[ext])
if not i: # no conflicts
extset.add(ext)
else: # print warnings and skip the extension
print('WARN:' + ext + ' skipped as conflicting (' + ','.join(i) + ').')
continue
else:
extset.add(ext)
return extset
def get_sys_alt_path(item, version=None):
"""
Returns system alternatives path
or error if it does not exist
"""
if not version:
print("ERROR:no version")
sys.exit(1)
else:
if '.' not in version:
print("ERROR:wrong version")
sys.exit(1)
module_dir = os.path.join(
selector_conf['SYS_ALT_PATH'],
item + version.replace( '.', '' ),
'etc',
item + '.d.all'
)
if not os.path.isdir( module_dir ):
print('ERROR:Alternatives directory not found.')
sys.exit(2)
return module_dir
skip_user_check = False
def get_user_ext_path(item, username, version):
cagefs_user_path = user_check(username)
if not cagefs_user_path:
print("ERROR:No such user")
return None
if not skip_user_check:
if not cagefs_user_check(username):
print("ERROR:User is not in cagefs")
return None
user_modules_dir = os.path.join(
selector_conf['CAGEFS_PATH'],
cagefs_user_path,
username,
'etc',
'cl.'+item+'.d'
)
if not os.path.isdir( user_modules_dir ):
print('ERROR:No user alternatives directory.')
return None
return os.path.join( user_modules_dir, 'alt-' + item + version.replace( '.', '' ) )
def get_user_path(username):
"""
Return user cl selector path
or None if a problem arises
"""
cagefs_user_path = user_check(username)
if not cagefs_user_path:
print("ERROR:No such user")
return None
if not skip_user_check:
if not cagefs_user_check(username):
print("ERROR:User is not in cagefs")
return None
# A given user ETC path
# (e.g. /var/cagefs/07/blissie/etc/cl.selector)
# for interpreter binaries symlinks
selector_path = os.path.join(
selector_conf['CAGEFS_PATH'],
cagefs_user_path,
username,
'etc',
'cl.selector'
)
return selector_path
def process_dependencies(item, version, ext):
"""
Finds dependencies for an extension
"""
try:
getattr( process_dependencies,'paths' )
except AttributeError:
process_dependencies.paths = {}
if version in process_dependencies.paths:
sys_alt_path = process_dependencies.paths[version]
else:
sys_alt_path = get_sys_alt_path( item, version )
process_dependencies.paths[version] = sys_alt_path
file = os.path.join( sys_alt_path, ext+'.ini' )
if not os.path.isfile(file):
print("WARN:No such extension (%s)" % (ext,))
return []
quirks = {'ixed':'sourceguardian'}
ini = open( file, 'r' )
extlist = []
extdict = {}
templist = []
for line in ini:
if line.startswith('\n'):
continue
if line.startswith(';') or line.startswith('#'):
continue
# We single out strings containing 'extension' word and
# try to figure out an extension name
if 'extension' in line:
if '/' in line:
extname = line[ line.rfind('/')+1:line.find('.') ]
else:
extname = line[ line.find('=')+1:line.find('.') ].lstrip(r' "')
if '-' in extname:
extname = extname[ :extname.rfind('-') ]
if extname in quirks:
extname = quirks[extname]
elif ext in extname:
extname = ext
elif ('_' in ext) and (''.join(map((lambda x:x.capitalize()),ext.split('_'))) == extname):
extname = ext
extlist.append(extname)
try:
extdict[extname].append(line)
except NameError:
templist.append(line)
except KeyError:
extdict[extname] = []
extdict[extname].append(';---'+extname+'---\n')
extdict[extname].extend(templist)
templist = []
extdict[extname].append(line)
ini.close()
final_list = []
for ext in extlist:
final_list.append( {'name':ext,'data':extdict[ext]} )
return final_list
def update_defaults(item, version, modules=None, action=None):
"""
Updates default interpreter version and moduleset
for a version indefaults.cfg
"""
if os.geteuid() != 0:
print("ERROR:Superuser privileges required")
sys.exit(1)
if not version:
print("ERROR:Version must be specified")
return
data = {}
if os.path.isfile( selector_conf['DEFAULTS_CONFIG'] ):
cfg=open(selector_conf['DEFAULTS_CONFIG'],'r')
for line in cfg:
if line.startswith('\n'):
continue
if line.startswith(';') or line.startswith('#'):
continue
if '[' in line and ']' in line:
section = line[line.find('[')+1:line.rfind(']')].strip()
if section not in data:
data[section] = {}
continue
if item in line:
try:
data[section][item] = line[line.index('=')+1:].strip()
except ValueError:
pass
continue
if 'modules' in line:
try:
modlist = line[line.index('=')+1:].strip()
except ValueError:
continue
try:
data[section]['modules'] = modlist
except (NameError, KeyError):
pass
continue
if 'state' in line and 'disabled' in line:
try:
data[section]['state'] = 'disabled'
except (NameError, KeyError):
pass
cfg.close()
if 'versions' not in data: # no file
data['versions'] = {}
data['versions'][item] = 'native'
# update modules list taking into account
# conflicts and dependencies
version_key = item + version
if modules:
extlist = []
extset = set()
for ext in process_conflicts(modules):
for _e in process_dependencies(item, version, ext):
if _e['name'] in extset: # this module has already been enabled
continue
if _e['name'] != ext and _e['name'] not in modules:
# this is a dependency. Inform about it
print("WARN:%s enabled as dependency (%s)" % (_e['name'], ext))
extset.add(_e['name'])
extlist.append(_e['name'])
modules = ','.join(extlist)
if version_key not in data:
data[version_key] = {}
data[version_key]['modules'] = modules
elif action:
if action == 'disable':
if version_key not in data:
data[version_key] = {}
data[version_key]['state'] = 'disabled'
else:
if version_key in data and 'state' in data[version_key]:
del data[version_key]['state']
if 'native' in version_key:
del data[version_key]
else:
data['versions'][item] = version
cfg=open(selector_conf['DEFAULTS_CONFIG'],'w')
v = data.pop('versions') # versions section must be on top
if item in v:
cfg.write( "[versions]\n%s=%s\n\n" % ( item, v[item] ) )
for d in sorted(data.keys()):
options = []
for o in data[d]:
options.append( o + '=' + data[d][o] )
cfg.write( "[%s]\n%s\n\n" % ( d, '\n'.join(options) ) )
cfg.close()
def get_alternatives( item, username ):
"""
Returns currently selected interpreter version
of a binary for a specified user
"""
entries = process_config(item)
if not username:
defaults = process_defaults(item)
if defaults:
default = list(filter((lambda x:'default' in defaults[x]),list(defaults.keys())))[0]
if default in entries:
return {
'major': default,
'full': entries[default]['full'],
'paths': list(map(
(lambda x: entries[default]['paths'][x]),
list(entries[default]['paths'].keys())
))
}
else:
return {'major': default, 'full': default, 'paths': []}
return {}
selector_path = get_user_path(username)
if not selector_path:
sys.exit(1)
if os.path.isdir(selector_path):
for obj in os.listdir(selector_path):
if not obj.startswith(item):
continue
link_to_alternative = os.path.join( selector_path, obj )
if os.path.islink( link_to_alternative ):
link_destination = os.readlink( link_to_alternative )
if item == obj and os.path.dirname(link_destination) == selector_conf['USER_SKELETON_PATH']:
return { 'major':'native', 'full':'native', 'paths':[link_destination] }
for key in entries:
if item in entries[key]['paths'] and entries[key]['paths'][item] == link_destination:
return {
'major':key,
'full':entries[key]['full'],
'paths':list(map(
(lambda x:entries[key]['paths'][x]),
list(entries[key]['paths'].keys())
))
}
return {}
def get_alternatives_summary( item, username ):
"""
Returns alternatives set and marks selected alternative
"""
entries = process_config(item)
# get defaults as a dictionary
defaults = process_defaults(item)
for v in defaults.keys():
if 'default' in defaults[v]:
default_version = v
output = {}
# native version is always present
output['native'] = set()
if 'native' in defaults:
output['native'].update(defaults['native'])
for key in entries:
if key not in output:
output[key] = set()
if key in defaults:
output[key].update(defaults[key])
if not username:
return output
selector_path = get_user_path(username)
if not selector_path:
sys.exit(1)
if os.path.isdir(selector_path):
for obj in os.listdir(selector_path):
if not obj.startswith(item):
continue
link_to_alternative = os.path.join( selector_path, obj )
if os.path.islink( link_to_alternative ):
link_destination = os.readlink( link_to_alternative )
if item == obj and os.path.dirname(link_destination) == selector_conf['USER_SKELETON_PATH']:
if 'disabled' in output['native']:
print('WARN:Native version is disabled. Defaults used')
set_alternatives(item, default_version, username, True)
output[default_version].add('selected')
return output
else:
output['native'].add('selected')
return output
for key in entries:
if item in entries[key]['paths'] and entries[key]['paths'][item] == link_destination:
if 'disabled' in output[key]:
print('WARN:Version %s is disabled. Defaults used' % (key,))
set_alternatives(item, default_version, username, True)
output[default_version].add('selected')
return output
else:
output[key].add('selected')
return output
def set_alternatives( item, version, username, backup=False ):
if not os.path.isdir(selector_conf['CAGEFS_CHECK_PATH']):
print('ERROR:CageFS not found.')
sys.exit(2)
new_ini_set = False
entries = process_config(item)
if not entries:
return
selector_path = get_user_path(username)
if not selector_path:
sys.exit(1)
if not os.path.isdir( selector_path ):
print('ERROR:No user selector directory.')
sys.exit(2)
for file in os.listdir( selector_path ):
if item not in file:
continue
link_to_alternative = os.path.join( selector_path, file )
if os.path.islink( link_to_alternative ) or os.path.isfile( link_to_alternative ):
try:
os.unlink( link_to_alternative )
except OSError as e:
if e.errno == 2:
pass
else:
print("ERROR: %s %s" % ( e.strerror, file ))
sys.exit(2)
if version == 'native':
if not os.path.isdir(selector_conf['NATIVE_PATH']):
print('ERROR:No directory for native binary.')
sys.exit(2)
if os.path.exists(selector_conf['NEW_INI_PATH']):
try:
target = os.path.basename(selector_conf['NEW_INI_PATH'])
source = os.path.join(
selector_conf['USER_SKELETON_PATH']+'.etc', target)
destination = os.path.join(selector_path, target)
os.symlink(source, destination)
new_ini_set = True
except OSError as e:
print("Could not create a symlink: %s" % ( e.strerror, ))
sys.exit(1)
for binary in os.listdir( selector_conf['NATIVE_PATH'] ):
if item not in binary:
continue
if binary.endswith('.ini') and new_ini_set:
continue
destination = os.path.join( selector_path, binary )
source = os.path.join( selector_conf['USER_SKELETON_PATH'], binary )
try:
os.symlink( source, destination )
except OSError as e:
print("Could not create a symlink: %s" % ( e.strerror, ))
sys.exit(1)
else:
for source in entries[version]['paths'].keys():
destination = os.path.join( selector_path, source )
try:
os.symlink( entries[version]['paths'][source], destination )
except OSError as e:
print("Could not create a symlink: %s" % ( e.strerror, ))
sys.exit(1)
# Rewrite user php.ini
if item == 'php':
is_error = False
try:
from clcagefslib.selector.panel.da import da_change_user_php_ini
from clcagefslib.selector.panel.isp import ispmanager_create_user_wrapper
except ImportError as e:
print("Could not import cagefs module: %s" % str(e))
is_error = True
if not is_error:
da_change_user_php_ini(username, version)
ispmanager_create_user_wrapper(username, version)
from clselect.cluserselect import ClUserSelect
pw = pwd.getpwnam(username)
ClUserSelect.switch_symlink_for_alt_php(version, pw, exit_on_error=False)
if backup:
save_config(item, username)
reload_processes(item, username)
def list_extensions( item, username, version=None, showall=False ):
"""
Collects info about enabled and all installed selector extensions
for a specified (or currently selected version) and returns it
in ordered structure for further processing
"""
if version:
if version == 'native':
if username:
print("WARN:No extensions for native binary")
sys.exit(0)
else:
return dict.fromkeys(get_builtins(item,version),-1)
else:
if not '.' in version:
print('ERROR:Wrong version format')
sys.exit(1)
else:
if username:
version = get_alternatives( item, username )['major']
else:
config_data = process_defaults(item)
for v in config_data.keys():
if 'default' in config_data[v]:
version = v
break
enabled = {}
if username:
user_dir = get_user_ext_path(item, username, version)
if not user_dir:
sys.exit(2);
user_path = os.path.join(user_dir, selector_conf['PHP_INI'])
if os.path.isfile(user_path):
ini_file = open(user_path, 'r')
for line in ini_file:
if line.startswith(';---'):
module_name = line[ 4 : line.rfind('---') ]
enabled[module_name] = 1
ini_file.close()
if not showall:
return [ enabled ]
ini_dir = get_sys_alt_path( item, version )
modules = {}
for entry in os.listdir( ini_dir ):
if not entry.endswith('.ini'):
continue
modules[ entry[ : entry.find('.ini') ] ] = 0
if username:
return [ enabled, modules ]
enabled = process_defaults(item, version)
for ext in enabled.split(','):
if ext in modules:
modules[ext] = 1
modules.update( dict.fromkeys( get_builtins( item, version ), -1 ) )
return modules
def enable_extension( item, usernames, extstring, version=None, backup=False ):
"""
Enable comma-separated list of extensions for comma-separated list of users
Enabling extesions is made by means of regenerating one .ini file for enabled
modules
"""
if version:
if (version == 'native'):
print("WARN:Operation not supported")
sys.exit(0)
else:
if not '.' in version:
print("ERROR:Wrong version (" + version + ")")
sys.exit(1)
for username in usernames.split(','):
if not version:
version = get_alternatives( item, username)['major']
user_dir = get_user_ext_path(item, username, version)
if not user_dir:
continue
user_path = os.path.join(user_dir, selector_conf['PHP_INI'])
ini_dir = get_sys_alt_path(item, version)
# Use _load_ini_contents to read extensions and options
from clselect.cluserextselect import ClUserExtSelect
content, extlist, extdict = ClUserExtSelect._load_ini_contents(user_path)
checklist = extlist[:]
checklist.extend(extstring.split(','))
checked_set = process_conflicts(checklist)
for ext in extstring.split(','):
if ext not in checked_set:
continue
for _e in process_dependencies(item, version, ext):
if _e['name'] in extdict: # this module has already been enabled
continue
if _e['name'] != ext: # this is a dependency. Inform about it
print("WARN:%s enabled as dependency (%s)" % (_e['name'], ext))
extlist.append(_e['name'])
extdict[_e['name']] = _e['data']
if 'ioncube_loader' in extdict:
content.append( ''.join( extdict['ioncube_loader'] ) )
del extdict['ioncube_loader']
for ext in extlist:
if ext in extdict:
content.append( ''.join( extdict[ext] ) )
process_path( username, user_dir, selector_conf['PHP_INI'], "\n".join(content)+"\n" )
if backup:
save_config( item, username )
reload_processes( item, username )
def disable_extension( item, usernames, extnames, version=None, backup=False ):
if version:
if (version == 'native'):
print("WARN:Operation not supported")
sys.exit(0)
else:
if not '.' in version:
print("ERROR:Wrong version (" + version + ")")
sys.exit(1)
for username in usernames.split(','):
if not version:
version = get_alternatives( item, username)['major']
user_dir = get_user_ext_path(item, username, version)
if not user_dir:
continue
if not os.path.isdir( user_dir ):
continue
user_path = os.path.join(user_dir, selector_conf['PHP_INI'])
if not os.path.isfile(user_path):
continue
ini_dir = get_sys_alt_path(item, version)
# Use _load_ini_contents to read extensions and options
from clselect.cluserextselect import ClUserExtSelect
content, extlist, extdict = ClUserExtSelect._load_ini_contents(user_path)
exclusions_set = set( extnames.split(',') )
depdict = {}
for ext in extlist:
extset = set(map((lambda x:x['name']), process_dependencies(item, version, ext)))
if len(extset) > 1: # there are dependencies
extset.discard(ext) # leave only dependencies in set
for s in extset:
if s not in depdict:
depdict[s] = set()
depdict[s].add(ext) # to form dependencies dictionary
for ext in extlist:
if ext in exclusions_set: # if we want to remove extension
if ext in depdict: # but if we see it is a dependency
if set(extlist).intersection(depdict[ext]) and exclusions_set.intersection(depdict[ext]) != depdict[ext]:
print("WARN:%s left as dependency (%s)" % (ext, ','.join(depdict[ext])))
continue # we skip it if dependant modules are to be left
del extdict[ext] # else remove it
for ext in extlist:
if ext in extdict:
content.append( ''.join( extdict[ext] ) )
process_path( username, user_dir, selector_conf['PHP_INI'], "\n".join(content)+"\n" )
if backup:
save_config( item, username )
reload_processes( item, username )
# ToDo: not used ?
def update_extensions( item, usernames, extstring, version=None, backup=False ):
"""
Enable comma-separated list of extensions for comma-separated list of users
Enabling extesions is made by means of regenerating one .ini file for enabled
modules
"""
if version:
if (version == 'native'):
print("WARN:Operation not supported")
sys.exit(0)
else:
if not '.' in version:
print("ERROR:Wrong version (" + version + ")")
sys.exit(1)
for username in usernames.split(','):
if not version:
version = get_alternatives( item, username)['major']
user_dir = get_user_ext_path(item, username, version)
if not user_dir:
continue
if not os.path.isdir( user_dir ):
continue
user_path = os.path.join(user_dir, selector_conf['PHP_INI'])
if not os.path.isfile(user_path):
continue
ini_dir = get_sys_alt_path(item, version)
extdict = {}
extlist = []
content = []
ini_file = open(user_path, 'r')
is_content = False
for line in ini_file:
# we want to preserve stuff in this section
# Check if it is content block
if line.startswith(';>==='):
is_content = True
content.append(line)
# Processing content
elif is_content:
# Skip comments
if line.startswith(';') and not line.startswith(';<==='):
continue
# Append until the end of block ('<===')
content.append(line)
if line.startswith(';<==='):
is_content = False
ini_file.close()
for ext in process_conflicts(extstring):
for _e in process_dependencies(item, version, ext):
if _e['name'] in extdict: # this module has already been enabled
continue
if _e['name'] != ext and _e['name'] not in extstring: # this is a dependency. Inform about it
print("WARN:%s enabled as dependency (%s)" % (_e['name'], ext))
extlist.append(_e['name'])
extdict[_e['name']] = _e['data']
if 'ioncube_loader' in extdict:
content.append( ''.join( extdict['ioncube_loader'] ) )
del extdict['ioncube_loader']
for ext in extlist:
if ext in extdict:
content.append( ''.join( extdict[ext] ) )
process_path( username, user_dir, selector_conf['PHP_INI'], "\n".join(content)+"\n" )
if backup:
save_config( item, username )
reload_processes( item, username )
def save_config( item, username ):
config = {}
userindex = user_check(username)
path = os.path.join('/var/cagefs', userindex, username, 'etc/cl.selector')
modpath = os.path.join('/var/cagefs',userindex, username, 'etc/cl.php.d' )
config_path = os.path.join( pwd.getpwnam(username).pw_dir, '.cl.selector' )
if not os.path.isdir(path):
return
else:
for i in ['php', 'php-cli', 'lsphp', 'php-fpm']:
fullpath = os.path.join(path,i)
if not os.path.islink(fullpath):
continue
linkpath = os.path.realpath(fullpath)
if linkpath.startswith(selector_conf['USER_SKELETON_PATH']):
config['versions'] = item + '=native'
break
elif linkpath.startswith(selector_conf['SYS_ALT_PATH']):
try:
# extract string like 'php54' from path like /opt/alt/php54/etc/php.d.all
version_string = linkpath[ linkpath.index( '/',
len(selector_conf['SYS_ALT_PATH']) )+1 : linkpath.index( '/',
len(selector_conf['SYS_ALT_PATH'])+1 ) ]
version_string = version_string[len(item):]
config['versions'] = '='.join( ( item,
'.'.join( (version_string[:1], version_string[1:],) ) ) )
break
except ValueError:
continue
# traverse subdirectories and extract versions
# from directory names strings like 'alt-php54'
if os.path.isdir(modpath):
for version in os.listdir(modpath):
version_path = os.path.join(modpath,version)
try:
digits = version[ version.index(item)+len(item): ]
# Handle incorrect paths
if digits.find('\\') >= 0:
continue
section = item + '.'.join( ( digits[:1], digits[1:] ) )
config[section] = []
except ValueError:
continue
content = [ "[versions]\n" + config.pop('versions') + "\n" ]
for v in config:
content.append('[' + v + "]\nmodules=" + ','.join(list(list_extensions(item,
username,
v[len(item):])[0].keys())) + "\n")
process_path( username, config_path, 'defaults.cfg', "\n".join(content)+"\n" )
def process_path(username, path, filename=None, filecontent=None):
"""
writes files changing to target user uid/gid
"""
obj_uid = pwd.getpwnam(username).pw_uid
obj_gid = pwd.getpwnam(username).pw_gid
euid_is_changed = False
subj_euid = os.geteuid()
subj_egid = os.getegid()
filepath = os.path.join(path, filename)
if os.path.islink(filepath):
try:
os.unlink(filepath)
except OSError:
print("ERROR:cannot delete symlink in user home")
sys.exit(13)
if subj_euid != obj_uid:
try:
os.setegid(obj_gid)
os.seteuid(obj_uid)
euid_is_changed = True
except OSError:
print("ERROR:Failed to change to %s EUID" % (username,))
sys.exit(1)
if not os.path.isdir(path):
try:
os.mkdir(path)
except OSError:
pass
if filename and filecontent:
file_path = os.path.join( path, filename )
try:
fd, temp_path = tempfile.mkstemp( prefix='lvetmp_', dir=path )
file = os.fdopen(fd, 'w')
file.write(filecontent)
file.close()
except (IOError, OSError):
try:
if os.path.exists(temp_path):
os.unlink(temp_path)
except:
pass
else:
try:
mask = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
os.rename(temp_path, file_path)
os.chmod(file_path, mask)
except OSError:
pass
if euid_is_changed:
os.setegid(subj_egid)
os.seteuid(subj_euid)
def reload_processes( item, username ):
ps = subprocess.Popen(['/bin/ps','-U',username,'-u',username],stdout=subprocess.PIPE)
lines = ps.communicate()[0].split("\n")
processes = []
for row in lines:
parts = row.rstrip().split()
try:
parts[-1].index(item)
os.kill( int(parts[0]), signal.SIGHUP )
except IndexError:
continue
except ValueError:
continue
def get_builtins(item, version):
data = process_config(item)
binary = item + '-cli'
if version in data:
path = data[version]['paths'][binary]
else:
path = get_native_path(item)
list = subprocess.Popen([path,'-qm'],stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
lines = list.communicate()[0]
zend_index = lines.find('[Zend')
patt = re.compile(r"^\w")
stripped = lines[:((zend_index >= 0 and zend_index) or len(lines))]
builtins = []
for ext in stripped.split("\n"):
if not patt.match(ext):
continue
builtins.append("_".join( re.split( "\s+", ext.lower() ) ) )
return builtins
def get_native_path(item):
"""
Returns native interpreter binary path
"""
if os.path.isfile(selector_conf['NATIVE_CONF']):
cfg = open(selector_conf['NATIVE_CONF'])
binary = item + '-cli'
binary_paths = []
for line in cfg:
if line.startswith('#'):
continue
if binary in line:
path = line[line.find(binary)+len(binary)+1:].strip('\'"=\n ')
binary_paths.append(path)
for p in binary_paths:
if os.path.isfile(p) and not os.path.islink(p):
return p
# if not found suppose cpanel cli binary
return '/usr/bin/php'
Zerion Mini Shell 1.0