????
Current Path : /usr/bin/ |
Current File : //usr/bin/cldiag |
#!/opt/cloudlinux/venv/bin/python3 -bb # coding=utf-8 # # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2018 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENCE.TXT # from __future__ import print_function from __future__ import absolute_import import datetime import getopt import socket import sys import os import stat import tempfile import subprocess import urllib.request from email import message_from_string from email.mime.text import MIMEText from typing import AnyStr # NOQA from clcommon.utils import is_root_or_exit from clcommon.mail_helper import MailHelper, MailSendFailed import cldetectlib as detect from cldiaglib import ( runner, fake_cagefs_checker, check_cp_diag, check_symlinksifowner, check_suexec, check_suphp, check_use_pam, check_symlinkowngid, check_existence_of_all_users_packages, check_da_resellers_packages_files, check_phpselector, check_php_conf, check_defaults_cfg, check_lve_limits, check_cagefs_partition_disk_quota, is_email_notification_enabled, check_domains_compatibility, check_hidepid, get_list_of_disabled_cron_checkers, set_list_of_disabled_cron_checkers, cron_cldiag_checkers_param_name, check_jwt_token, check_cl_plus_sender_service, check_node_exporter_service, check_cmt_packages, check_lvestats_service, check_low_pmem_limits ) CAGEFS_SOURCES = '/usr/share/cagefs' CAGEFS_INSTALLED = detect.is_cagefs_installed() if CAGEFS_INSTALLED: sys.path.append(CAGEFS_SOURCES) try: from sanity_check import ( check_cagefs_mount_points_exists, check_cagefs_enabled_users_isdir, check_cagefs_disabled_users_isdir, check_cagefs_disabled_etcfs_exists, check_users_can_enter_cagefs, check_proxy_commands_configs_are_parsable, check_all_virt_mp_files_syntax, ) except ImportError: check_cagefs_mount_points_exists = None check_cagefs_enabled_users_isdir = None check_cagefs_disabled_users_isdir = None check_cagefs_disabled_etcfs_exists = None check_users_can_enter_cagefs = None check_proxy_commands_configs_are_parsable = None check_all_virt_mp_files_syntax = None cron_checkers = { check_symlinksifowner: 'check-symlinksifowner', check_use_pam: 'check-usepam', check_symlinkowngid: 'check-symlinkowngid', check_existence_of_all_users_packages: 'check-cpanel-packages', check_da_resellers_packages_files: 'check-da-packages-lists', check_lve_limits: 'check-lve-limits', check_cagefs_mount_points_exists: 'check-cagefs-mount-points-exist', check_cagefs_enabled_users_isdir: 'check-cagefs-enabled-users-is-dir', check_cagefs_disabled_users_isdir: 'check-cagefs-disabled-users-is-dir', check_cagefs_disabled_etcfs_exists: 'check-cagefs-disabled-etc-fs-exists', check_users_can_enter_cagefs: 'check-users-can-enter-cagefs', check_proxy_commands_configs_are_parsable: 'check-proxy-commands-configs-are-parsable', check_all_virt_mp_files_syntax: 'check-all-virtmp-files-syntax', check_hidepid: 'check-hidepid', check_jwt_token: 'check-jwt-token', check_cl_plus_sender_service: 'check-cl-plus-sender-service', check_node_exporter_service: 'check-node-exporter-service', check_cmt_packages: 'check-cmt-packages', check_lvestats_service: 'check-lvestats-service', check_cagefs_partition_disk_quota: 'check_cagefs_partition_disk_quota', check_low_pmem_limits: 'check_low_pmem_limits', } RUN_BY_CRON_SCOPE = 'run_by_cron' CAGEFS_SCOPE = 'cagefs' CMT_SCOPE = 'cmt' ALL_SCOPE = 'all' # TODO: we should consider about changing "FROM" field # now chances that this mail will go to spam are almost 100% EMAIL_TPL = """\ Subject: Your CloudLinux Server has issues: [{host}] - {date} From: noreply@{host} To: {to} <html> <head></head> <body> <p> We have detected several CloudLinux configuration issues on your system. </p> <ul> <li>You may ignore some or all issues if you know that they don't cause you any troubles or they are expected or already scheduled to be fixed </li> <li>You may contact <a target="_blank" href="https://cloudlinux.zendesk.com/">CloudLinux support</a> to help to resolve issues that are hard to resolve yourself </li> <li>You may disable this cron checks permanently See <a target="_blank" href="https://docs.cloudlinux.com/cldiag.html">docs</a> for steps </li> </ul> <p> See report below. </p> <pre> {content} </pre> </body> </html> """ # Show help def print_usage(): print('-h | --help shows this message') print('--json prints output as json') print('-a | --all prints all diag') print(" --diag-cp prints control panel and its version (CP_NAME, CP_VERSION)") print(" --doctor runs the cldoctor script and displays its output") print(' --symlinksifowner prints fs.enforce_symlinksifowner from sysctl conf') print(' --check-cm-all runs all of checkers for Centralized Monitoring') print(' --check-jwt-token prints warnings if JWT token has any error') print(' --check-suexec prints warning if suexec binary without CageFS') print(' --check-suphp prints warning if suphp binary without CageFS') print(' --check-usepam prints warnings if SSHd UsePAM from /etc/ssh/sshd_config') print(' --check-phpselector prints warnings if current PHP engine is not supported by CloudLinux PHP Selector') print(' --check-multi-php prints warnings if current system version is alt-php') print(' --check-domains-compatibility prints warnings if domains are not compatible') print(" --check-cagefs runs same checks as cagefsctl --sanity-check and print it's result") print(" --check-symlinkowngid prints warning if Apache user is not protected with fs.enforce_symlinksifowner") print(' --check-cpanel-packages prints packages from /var/cpanel/users that do not exist in /var/cpanel/packages') print(' --check-da-packages-lists check packages files encoding from /usr/local/directadmin/data/users/') print(' --check-php-conf prints warnings if /etc/cl.selector/php.conf has wrong format') print(' --check-defaults-cfg prints warnings if /etc/cl.selector/defaults.cfg has wrong format') print(' --check-lve-limits prints warnings if lve limits isn\'t valid on the server') print(' --check-hidepid prints warning if hidepid protection is disabled on the server') print(' --check-cagefs-quota prints warning if /var/cagefs is located on partition with disk quota disabled') print(' --check-low-pmem-limits prints warning if low PMEM limits present') print(' --generate-email will run most of the checks, generate HTML report and send it via email to admin.') print(' Default recipient email is root@localhost.localdomain (Can be affected by mail server settings).') print(' To change default email add/edit control panel getemail script from /etc/sysconfig/cloudlinux.') print(' --cron-check Do the same as --generate-email but taking into account /etc/sysconfig/cloudlinux ENABLE_CLDIAG setting') print(' --disable-cron-checkers [c1,c2] You can disable a few cron checkers passing on list of checkers separated by comma.') print(' Full list of cron checkers: {}'.format(', '.join(cron_checkers.values()))) print(' If you want to enable all checkers - call without args') print('') def _filter_checkers_list(checkers): """ Remove from list checkers which was disabled by client """ disabled_checkers = get_list_of_disabled_cron_checkers() enabled_checkers = [checker for checker in checkers if checker.public_name not in disabled_checkers] return enabled_checkers def check_and_send_notification(cron_mode: bool, scope: AnyStr=ALL_SCOPE) -> None: """ Run checkers that are present in given scope. Also send email to servers administrator if some of checkers fail. :param cron_mode: True - started by cron, False - started manually :param scope: the param defines set of checkers """ checkers = prepare_checkers(scope=scope, cron_mode=cron_mode) if cron_mode: checkers = _filter_checkers_list(checkers) errors, output = runner(checkers, do_exit=False) if not errors: return mail_helper = MailHelper() message = EMAIL_TPL.format( host=socket.gethostname(), date=datetime.date.today(), to=detect.getCPAdminEmail(), content=''.join(output), cldiag_param=cron_cldiag_checkers_param_name, cl_config=detect.CL_CONFIG_FILE, ) headers = message_from_string(message) msg = MIMEText(headers.get_payload(), 'html') msg['Subject'] = headers['Subject'] msg['From'] = headers['From'] msg['To'] = headers['To'] # TODO: probably we can change sendmail signature to just msg argument try: mail_helper.sendmail(msg['From'], msg['To'], msg=msg) except MailSendFailed as e: error_msg = "cldiag is unable to send email notification " \ "about server issues; the error is '%s'\n" \ "Check your mail server settings or contact " \ "CloudLinux support for help" % e if scope == RUN_BY_CRON_SCOPE: error_msg += "\nAlso you can disable automatic notifications. See " \ "https://docs.cloudlinux.com/cldiag.html for steps." print(error_msg) def _add_public_name_to_cron_checkers(checkers): """ Write additional field with public name to each cron checker. """ for checker in checkers: checker.public_name = cron_checkers[checker] def prepare_checkers(scope=ALL_SCOPE, cron_mode=False): all_scopes = ( ALL_SCOPE, CAGEFS_SCOPE, RUN_BY_CRON_SCOPE, CMT_SCOPE, ) if scope not in all_scopes: raise ValueError('Unknown checkers scope') cmt_checkers = ( check_jwt_token, check_cl_plus_sender_service, check_node_exporter_service, check_cmt_packages, check_lvestats_service, ) all_checkers = ( check_cp_diag, check_symlinksifowner, check_suexec, check_suphp, check_use_pam, check_symlinkowngid, check_existence_of_all_users_packages, check_da_resellers_packages_files, check_phpselector, check_defaults_cfg, check_php_conf, check_lve_limits, check_hidepid, check_cagefs_partition_disk_quota, check_low_pmem_limits, ) cagefs_checkers = ( fake_cagefs_checker, ) checkers_run_by_cron = ( check_symlinksifowner, check_use_pam, check_symlinkowngid, check_existence_of_all_users_packages, check_da_resellers_packages_files, check_lve_limits, check_hidepid, check_cagefs_partition_disk_quota, check_low_pmem_limits, *cmt_checkers, ) if CAGEFS_INSTALLED: try: from sanity_check import CAGEFS_CHECKERS cagefs_checkers = tuple(CAGEFS_CHECKERS) except ImportError: pass all_checkers = all_checkers + cagefs_checkers if scope == ALL_SCOPE: return all_checkers if scope == CAGEFS_SCOPE and CAGEFS_INSTALLED: return cagefs_checkers if scope == RUN_BY_CRON_SCOPE: if CAGEFS_INSTALLED: try: from sanity_check import ( check_multiphp_system_default, ) checkers_run_by_cron += tuple( checker for checker in cagefs_checkers # LU-919; We exclude cagefs multiphp checker for checkers which run by cron if checker != check_multiphp_system_default ) except ImportError: pass if cron_mode: _add_public_name_to_cron_checkers(checkers_run_by_cron) return checkers_run_by_cron if scope == CMT_SCOPE: return cmt_checkers return [] def run_cldoctor(): """ Downloads the cldoctor script from the fixed URL to a temp file and runs it. Prints the output. :raises CalledProcessError: When the script returns a non-zero return code. """ doctor_url = r"https://repo.cloudlinux.com/cloudlinux/cldoctor/cldoctor.sh" doctor_filename = os.path.join("/root/", "tmp_cldoctor.sh") print(f"Downloading cldoctor script from {doctor_url}") urllib.request.urlretrieve(doctor_url, doctor_filename) # `check` will raise an exception if the script exited with a non-zero code. subprocess.run(["/bin/bash", doctor_filename], shell=False, text=True, check=True, cwd="/root") # Wipe the script after execution, don't wait for tempdir cleanup. os.remove(doctor_filename) def main(): is_root_or_exit() try: opts, args = getopt.getopt(sys.argv[1:], 'ha', [ 'help', 'all', 'diag-cp', 'symlinksifowner', 'check-suexec', 'check-suphp', 'check-usepam', 'generate-email', 'cron-check', 'check-cagefs', 'check-symlinkowngid', 'check-cpanel-packages', 'check-da-packages-lists', 'check-phpselector', 'check-multi-php', 'check-domains-compatibility', 'check-hidepid', 'check-cagefs-quota', 'check-low-pmem-limits', 'json', 'check-php-conf', 'check-defaults-cfg', 'check-lve-limits', 'add-comment-to-config-about-cron-checkers', 'disable-cron-checkers', 'check-cm-all', 'check-jwt-token', 'doctor', ]) except getopt.GetoptError: print('error: unknown command') print_usage() sys.exit(1) executed = False to_json = False if ('--json', '') in opts: to_json = True scheduled_checkers = [] for o, a in opts: if o in ('--help', '-h'): executed = True print_usage() elif o in('--check-cm-all',): executed = True checkers = prepare_checkers(scope=CMT_SCOPE) for checker in checkers: scheduled_checkers.append(checker) elif o in ('--all', '-a'): executed = True checkers = prepare_checkers() runner(checkers, to_json) elif o in ('--diag-cp', ): scheduled_checkers.append(check_cp_diag) elif o in ('--symlinksifowner', ): scheduled_checkers.append(check_symlinksifowner) elif o in ('--check-suexec', ): scheduled_checkers.append(check_suexec) elif o in ('--check-suphp', ): scheduled_checkers.append(check_suphp) elif o in ('--check-usepam',): scheduled_checkers.append(check_use_pam) elif o in ('--generate-email', ): executed = True check_and_send_notification(scope=ALL_SCOPE, cron_mode=False) elif o in ('--check-cagefs',): if not CAGEFS_INSTALLED: print('Cagefs is not installed. Skipping check') exit(0) checkers = prepare_checkers(CAGEFS_SCOPE) for checker in checkers: scheduled_checkers.append(checker) elif o in ('--check-low-pmem-limits',): scheduled_checkers.append(check_low_pmem_limits) elif o in ('--check-symlinkowngid',): scheduled_checkers.append(check_symlinkowngid) elif o in ('--cron-check',): executed = True if is_email_notification_enabled(): check_and_send_notification(cron_mode=True, scope=RUN_BY_CRON_SCOPE) elif o in ('--check-phpselector',): scheduled_checkers.append(check_phpselector) elif o in ('--check-multi-php',): executed = True if CAGEFS_INSTALLED: from sanity_check import check_multiphp_system_default scheduled_checkers.append(check_multiphp_system_default) elif o in ('--check-domains-compatibility',): scheduled_checkers.append(check_domains_compatibility) elif o in ('--check-cpanel-packages',): scheduled_checkers.append(check_existence_of_all_users_packages) elif o in ('--check-da-packages-lists',): scheduled_checkers.append(check_da_resellers_packages_files) elif o in ('--check-php-conf',): scheduled_checkers.append(check_php_conf) elif o in ('--check-defaults-cfg',): scheduled_checkers.append(check_defaults_cfg) elif o in ('--check-lve-limits',): scheduled_checkers.append(check_lve_limits) elif o in ('--disable-cron-checkers',): if args: disabled_cron_checkers = [item.strip() for item in str(args[0]).strip().split(',')] else: disabled_cron_checkers = [] for checker in disabled_cron_checkers: if checker not in list(cron_checkers.values()): print(f'error: wrong name of the checker: {checker}') print(f'Full list of existing checkers: ' f'{", ".join(cron_checkers.values())}') sys.exit(1) set_list_of_disabled_cron_checkers(disabled_cron_checkers) executed = True elif o in ('--check-hidepid',): scheduled_checkers.append(check_hidepid) elif o in ('--check-jwt-token',): scheduled_checkers.append(check_jwt_token) elif o in ('--check-cagefs-quota',): scheduled_checkers.append(check_cagefs_partition_disk_quota) elif o in ('--doctor',): executed = True run_cldoctor() if scheduled_checkers or to_json: executed = True runner(scheduled_checkers, to_json) if not executed: print('error: argument required') print_usage() sys.exit(1) if __name__ == "__main__": main()