# Copyright (c) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2018 All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import string import sys import os import traceback import json import configparser from io import StringIO from pipes import quote import exec_command class StatUtilsException(Exception): pass def cpanel_whmapi(cmd, **kwargs): """ Perform cPanel WHM API console request and return data from result :param cmd: whm api command :return: data dict from result """ joined_kwargs = ' '.join([quote('{0}={1}'.format(k, v)) for k, v in kwargs.items()]) result = exec_command.exec_command("/usr/sbin/whmapi1 {cmd} {kw} --output json".format(cmd=cmd, kw=joined_kwargs)) try: dict_result = json.loads(''.join(result)) except ValueError: # if result is not JSON raise StatUtilsException( "Failed to get JSON from this API request: {0} {1}; output: {2}".format(cmd, joined_kwargs, ''.join(result))) try: return dict_result['data'] except KeyError: # if there are no data field in received JSON raise StatUtilsException("Failed to get data from this API result: {0}".format(dict_result)) def plesk_bin_php_handler(cmd, **kwargs): """ Perform Plesk php_handler utility console request and return result :param cmd: php_handler command :return: dict result """ # need to quote only value (-k 'v'), quoting full params ('-k v') causes API failure joined_kwargs = ' '.join(['-{0} {1}'.format(k, quote(v)) for k, v in kwargs.items()]) result = exec_command.exec_command("/usr/local/psa/bin/php_handler --{cmd} {kw} -json true".format(cmd=cmd, kw=joined_kwargs)) try: return json.loads(''.join(result)) except ValueError: raise StatUtilsException( "Failed to get JSON from this API request: php_handler {0} {1}; output: {2}".format(cmd, joined_kwargs, ''.join(result))) def get_da_domains(): """ Get domains per user :return: dict( user: list of domains ) """ da_users_path = '/usr/local/directadmin/data/users' da_domains_path = '/usr/local/directadmin/data/users/{user}/domains.list' da_users = os.listdir(da_users_path) domains = dict.fromkeys(da_users) try: for user in da_users: with open(da_domains_path.format(user=user), 'r') as domains_list_file: domains[user] = set([l.strip() for l in domains_list_file.readlines()]) except (OSError, IOError): raise StatUtilsException(''.join(traceback.format_exc().split('\n'))) return domains def get_da_php_options(): """ Get php settings from options.conf :return: dict( first php setting: {version, mode}, second php setting: {version, mode}, ) """ options_path = '/usr/local/directadmin/custombuild/options.conf' try: config_parser, global_section = read_da_config(options_path) php1_ver = config_parser.get(global_section, 'php1_release') php2_ver = config_parser.get(global_section, 'php2_release') php1_handler = config_parser.get(global_section, 'php1_mode') php2_handler = config_parser.get(global_section, 'php2_mode') except configparser.NoOptionError: raise StatUtilsException('No option found: {0}'.format(''.join(traceback.format_exc().split('\n')))) return { 1: { 'version': php1_ver, 'handler': 'lsapi' if php1_handler == 'lsphp' else php1_handler }, 2: { 'version': php2_ver, 'handler': 'lsapi' if php2_handler == 'lsphp' else php2_handler } } def read_da_config(conf_file, append_section_name='dummy_section'): """ Read DA config file with ConfigParser. Need to add dummy section for success :param conf_file: config file name :param append_section_name: name of section to place in the beginning of file :return: RawConfigParser instance """ try: with open(conf_file) as f: file_content = StringIO('[{s}]\n'.format(s=append_section_name) + f.read()) config_parser = configparser.RawConfigParser(strict=False) config_parser.read_file(file_content) except (OSError, IOError): raise StatUtilsException(''.join(traceback.format_exc().split('\n'))) return config_parser, append_section_name def pretty_version_keys(php_ver, pre='php'): """ Convert simple php versions to pretty format :param php_ver: {major}.{minor} version :param pre: desired key start :return: alt-php{major}{minor} or desired `pre`{major}{minor} """ template = '{0}%s%s'.format(pre) try: return template % tuple(php_ver.split('.')) except Exception: return php_ver def dump_lsapi(ctl_path='/usr/sbin/httpd'): """ Get `httpd -t -D DUMP_RUN_LSAPI` info For httpd24 this default path is `/opt/rh/httpd24/root/usr/sbin/httpd`, generated in make_from_templates.sh script :param ctl_path: path to httpd (also apachectl may be used) :return: dict( lsapi_option: value ) """ apache_conf_data = exec_command.exec_command('{ctl} -t -D DUMP_RUN_LSAPI'.format(ctl=ctl_path)) try: return dict([l.lower().split(' ') for l in apache_conf_data]) except: return dict() def dump_loaded_modules(ctl_path='/usr/sbin/httpd'): """ Get `httpd -M` For httpd24 this default path is `/opt/rh/httpd24/root/usr/sbin/httpd`, generated in make_from_templates.sh script :param ctl_path: path to httpd (also apachectl may be used) :return: dict( apache_module: value ) """ apache_modules = exec_command.exec_command('{ctl} -M'.format(ctl=ctl_path)) try: return dict([l.lower().split(' ') for l in apache_modules]) except: return dict() def liblsapi_path(): """ Retrieve path to liblsapi, depends on arch :return: path to liblsapi """ is_64bits = sys.maxsize > 2**32 return '/usr/lib{a}/liblscapi.so'.format(a='64' if is_64bits else '') def rpm_query(pkg): """ Get version-release from rpm -q `pkg` :param pkg: package name to query :return: version-release """ try: ver, rel = ''.join(exec_command.exec_command('/bin/rpm -q ' + pkg + ' --qf %{v}-%{r}')).split('-') return '{ver}-{rel}'.format(ver=ver, rel=rel.split('.')[0]) except ValueError: return None def query_strings(fname, template): """ Filter strings by given template Also split string upon given template :param fname: path to file :param template: template to find in string :return: first template occurrence splitted by template """ try: return [l for l in strings(fname) if template in l][0].split(template)[1].strip() except (IndexError, IOError, OSError): return None def strings(fname, n=6): """ Strings utility analog. Finds printable strings in executable :param fname: path to file :param n: minimum string length :return: generator, yeilds string """ with open(fname, errors='ignore') as f: result = "" for c in f.read(): if c in string.printable: result += c continue if len(result) >= n: yield result result = "" if len(result) >= n: # catch result at EOF yield result def count_domains(handler_struct, default_keys, only_lsapi=True): """ Count domains :param handler_struct: handler: version: set_of_domains structure :param default_keys: sequence of keys to add as default if no `lsapi` found :param only_lsapi: return only lsapi statistics :return: statistics - number of lsapi domains per version if only_lsapi=True number of lsapi domains per version per handler otherwise """ result_stat = dict() for h, data in handler_struct.items(): result_stat[h] = dict((k, len(v)) for k, v in data.items()) # if there are no lsapi handler domains, return 0 per each installed version try: return result_stat['lsapi'] if only_lsapi else result_stat except KeyError: return dict.fromkeys([x for x in default_keys if x != 'no'], 0)