Your IP : 13.58.119.73


Current Path : /proc/self/root/proc/self/root/lib64/nagios/plugins/nccustom/
Upload File :
Current File : //proc/self/root/proc/self/root/lib64/nagios/plugins/nccustom/check_extra_accts.py

#!/usr/libexec/platform-python
"""
Checking accounts that were left in the system after incorrect removing from cPanel
Version: 2.0 (25 Feb 2025)
Author: Bogdan Kukharskiy, bogdan.kukharskiy@namecheap.com
Based on script v.1.0-3 2017/12/05 by Vladimir Burov, vladimir.burov@namecheap.com
Package: nc-nrpe-check-extra-accts
"""

import configparser
import argparse
import re
import os
import pwd
import sys
import subprocess

VERSION = '2.0 2025/02/25'
HOSTNAME = os.uname()[1]
CONFIG_FILES = [
    '/usr/share/nc_nagios/check_extra_accts/check_extra_accts_ignore.conf',
    '/usr/share/nc_nagios/check_extra_accts/check_extra_accts_regex_ignore.conf'
]


def load_exclusions(config_files: list, hostname: str):
    """Load exclusions and regex exclusions from configuration files."""
    config = configparser.ConfigParser(allow_no_value=True)
    existing_files = [f for f in config_files if os.path.exists(f)]
    config.read(existing_files)
    # Load the static exclusions from the 'default' section
    exclude = []
    if config.has_section('default'):
        exclude = [key for key, _ in config.items('default')]
    # Load regex exclusions if defined.
    re_exclude = []
    if config.has_section('regex'):
        re_exclude = [key for key, _ in config.items('regex')]
    # For any additional section (named with a regex pattern),
    # if the section name matches the hostname, add its keys to exclusions.
    for section in config.sections():
        if section not in ['default', 'regex']:
            if re.match(section, hostname):
                exclude += [key for key, _ in config.items(section)]
    return exclude, re_exclude


def get_system_accounts() -> list:
    """Return a sorted list of system account names using the pwd module."""
    return sorted({p.pw_name for p in pwd.getpwall()})


def get_cpanel_accounts() -> list:
    """Return a list of cPanel accounts.
    The output is processed to remove leading/trailing spaces and any surrounding single quotes.
    """
    try:
        # Run the whmapi1 command without using shell and process the output in Python.
        result = subprocess.run(
            ['/usr/sbin/whmapi1', 'listaccts', 'want=user'],
            check=True, encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE
        )
        output = result.stdout.strip()
    except subprocess.CalledProcessError:
        output = ""
    # Filter lines that contain "user:" and extract the account name.
    raw_lines = output.splitlines()
    accounts = []
    for line in raw_lines:
        if "user:" in line:
            parts = line.split(":", 1)
            if len(parts) > 1:
                accounts.append(parts[1].strip().strip("'"))
    return [acct for acct in accounts if acct]


def check_home_directories(cpanel_accts: list) -> (list, list):
    """Return a tuple of (homedirs owned by root, homedirs with no valid owner)."""
    cpanel_homedirs = [os.path.expanduser(f'~{acct}') for acct in cpanel_accts]
    homes_owned_by_root = []
    homes_with_no_user = []
    for homedir in cpanel_homedirs:
        try:
            stat_info = os.stat(homedir)
            owner = pwd.getpwuid(stat_info.st_uid).pw_name
            if owner == 'root':
                homes_owned_by_root.append(homedir)
        except (FileNotFoundError, KeyError):
            homes_with_no_user.append(homedir)
    return homes_owned_by_root, homes_with_no_user


def print_list(header: str, items: list):
    """Helper function to print a header and its sorted list items, one per line."""
    print(f"{header}:")
    for item in sorted(items):
        print(item)
    print()  # Blank line for separation


def main():
    description = (
        f"Checking accounts that left in the system after incorrect removing from cPanel\n"
        f"Config files: {CONFIG_FILES}\nVersion {VERSION} bogdan.kukharskiy@namecheap.com"
    )
    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=description
    )
    parser.add_argument("-v", "--verbose", action="store_true", help="more verbose output")
    args = parser.parse_args()

    # Load exclusions from config files.
    exclude, re_exclude = load_exclusions(CONFIG_FILES, HOSTNAME)

    system_accounts = get_system_accounts()
    cpanel_accounts = get_cpanel_accounts()

    # Identify extra accounts: those in the system that are not present in cPanel and are not excluded.
    excess = [acct for acct in system_accounts if acct not in cpanel_accounts and acct not in exclude]
    if re_exclude:
        regex_pattern = re.compile('|'.join(re_exclude))
        excess = [acct for acct in excess if not regex_pattern.match(acct)]

    # Check /var/cpanel/users directory.
    try:
        var_cpanel_users = os.listdir('/var/cpanel/users')
    except OSError:
        var_cpanel_users = []

    excess_var_cpanel_users = [
        acct for acct in var_cpanel_users
        if acct not in cpanel_accounts and acct not in exclude and acct != 'system'
    ]

    homes_owned_by_root, homes_with_no_user = check_home_directories(cpanel_accounts)

    if args.verbose:
        print_list("Exclude list", exclude)
        print_list("RE Exclude list", re_exclude)
        print_list("System accounts", system_accounts)
        print_list("cPanel accounts", cpanel_accounts)
        print_list("/var/cpanel/users", var_cpanel_users)

    warnings = []
    if excess:
        warnings.append(f"Extra accounts: {' '.join(excess)};")
        if args.verbose:
            print(f"Extra accounts: {' '.join(excess)}")
    if excess_var_cpanel_users:
        warnings.append(f"Extra /var/cpanel/users/<file>: {' '.join(excess_var_cpanel_users)};")
        if args.verbose:
            print(f"cPanel account files exist without corresponding accounts: {' '.join(excess_var_cpanel_users)}")
    if homes_owned_by_root:
        warnings.append(f"Account home directory owned by root: {' '.join(homes_owned_by_root)};")
        if args.verbose:
            print(f"cPanel account home directories owned by root: {' '.join(homes_owned_by_root)}")
    if homes_with_no_user:
        warnings.append(f"Account home directory with no valid owner or not exist: {' '.join(homes_with_no_user)};")
        if args.verbose:
            print(f"cPanel account home directories with no valid owner or not exits: {' '.join(homes_with_no_user)}")

    if warnings:
        print("CRITICAL - " + ' '.join(warnings))
        sys.exit(2)
    else:
        print("OK - There's no extra accounts")
        sys.exit(0)

if __name__ == '__main__':
    main()

?>