PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /opt/maint/bin/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //opt/maint/bin/check_db_owners.py |
#!/opt/imh-python/bin/python3 """Check for orphaned databases""" from argparse import ArgumentParser import json import os import pwd import platform from contextlib import closing from multiprocessing import Queue, Process import pymysql from pymysql.cursors import Cursor from pymysql.optionfile import Parser as PyMySQLParser import psycopg2 from rads import send_email DEST_EMAIL = "reclamations@imhadmin.net" TOOL_URL = 'https://docs.cpanel.net/whm/scripts/the-dbmaptool-script/' DBINDEX = '/var/cpanel/databases/dbindex.db.json' ROOT_MYSQL = { 'leechprotect', 'cphulkd', 'modsec', 'mysql', 'horde', 'sys', 'information_schema', 'performance_schema', 'perl5', } ROOT_PGSQL = {'nagios_check', 'postgres'} def read_dbmap() -> tuple[dict[str, str], dict[str, str]]: # The backup scheduler already runs /usr/local/cpanel/bin/dbindex, # so no need to run that again before checking. with open(DBINDEX, encoding='utf-8') as file: dbindex = {k.lower(): v for k, v in json.load(file).items()} return dbindex.get('mysql', {}), dbindex.get('pgsql', {}) def mysql_conn(**kwargs) -> pymysql.Connection: """Open and return a MySQL connection""" try: cfg = PyMySQLParser(strict=False) cfg.read('/root/.my.cnf') unix_socket = cfg.get('client', 'socket') except Exception: unix_socket = '/var/lib/mysql/mysql.sock' return pymysql.connect( read_default_file='/root/.my.cnf', unix_socket=unix_socket, **kwargs, ) def list_mysql_dbs() -> set[str]: with mysql_conn() as conn, conn.cursor() as cur: cur: Cursor cur.execute('SHOW DATABASES') return {x[0] for x in cur.fetchall() if x[0] not in ROOT_MYSQL} def pgsql_query(*args) -> list[tuple]: """Execute a pgsql query""" pg_uid = pwd.getpwnam('postgres').pw_uid with closing(Queue()) as queue: kwargs = { 'environ': {'PGPASSFILE': '/var/lib/pgsql/.pgpass'}, 'queue': queue, 'pg_uid': pg_uid, } proc = Process( target=_setuid_pgsql_query, args=args, kwargs=kwargs, daemon=True, ) proc.start() ret = queue.get() proc.join(timeout=1) if proc.is_alive(): proc.kill() if isinstance(ret, list): return ret raise ret # if it's not a list, it's an exception object def _setuid_pgsql_query( *args, environ: dict, queue: Queue, pg_uid: int, ) -> None: """Do not call this directly. See ``pgsql_query``""" # By dropping privs to the postgres user, this forked process will be able # to access pgsql using "peer" auth. # See: https://www.postgresql.org/docs/9.1/auth-methods.html os.setuid(pg_uid) os.environ.update(environ) try: conn = psycopg2.connect(connect_timeout=3) cur = conn.cursor() try: cur.execute(*args) queue.put(cur.fetchall()) queue.put([]) return finally: cur.close() conn.close() except Exception as exc: queue.put(exc) def list_pgsql_dbs() -> set[str]: rows = pgsql_query( 'SELECT datname FROM pg_database WHERE datistemplate = false' ) return {row[0] for row in rows if row[0] not in ROOT_PGSQL} def parse_args(): parser = ArgumentParser(description=__doc__) group = parser.add_mutually_exclusive_group(required=True) # fmt: off group.add_argument( '--cron', action='store_true', help=f"Email results to {DEST_EMAIL}" ) group.add_argument( '--mysql', action='store_true', help="Print orphaned MySQL dbs to stdout", ) group.add_argument( '--pgsql', action='store_true', help="Print orphaned PostgreSQL dbs to stdout", ) # fmt: on return parser.parse_args() def main(): """Check for orphaned databases""" args = parse_args() mysql_owners, pgsql_owners = read_dbmap() if args.cron or args.mysql: extra_mysql = list_mysql_dbs().difference(mysql_owners.keys()) if args.cron or args.pgsql: extra_pgsql = list_pgsql_dbs().difference(pgsql_owners.keys()) if args.cron: alert(extra_mysql, 'MySQL') alert(extra_pgsql, 'PostgreSQL') if args.mysql: if extra_mysql: print(*sorted(extra_mysql), sep='\n') if args.pgsql: if extra_pgsql: print(*sorted(extra_pgsql), sep='\n') def alert(extras: set[str], db_type: str): if not extras: return lines = '\n'.join(extras) host = platform.node().split('.', maxsplit=1)[0] send_email( to_addr=DEST_EMAIL, errs=True, subject=f"Orphaned {db_type} databases on {host}", body=f"""The following databases were found in {db_type} on {host}, but aren't associated with a cPanel user in {DBINDEX}. If the cPanel user still exists, you can associate it with the user using cPanel's dbmaptool {TOOL_URL} Then run /usr/local/cpanel/bin/dbindex to update the ownership file. Otherwise, it might be a database left behind after removeacct failed to delete it, in which case you can drop the database. Orphaned {db_type} databases found: {lines} """, ) if __name__ == '__main__': main()