Mini Shell
Direktori : /opt/sharedrads/ |
|
Current File : //opt/sharedrads/cmspass.py |
#!/opt/imh-python/bin/python3
"""Scans a cPanel account for WordPress and Joomla! configuration files, then
uses whmapi1 to reset database passwords to match what was found in them.
This script logs to /var/log/messages"""
import subprocess
import re
import sys
from argparse import ArgumentParser
from pathlib import Path
from typing import Union
import rads
import logging
from logging.handlers import SysLogHandler
from cpapis import whmapi1, CpAPIError
WP_CONF = re.compile(r"DB_(USER|PASSWORD).*['\"](.+)['\"]")
JM_CONF = re.compile(r"\b(user|password) = '(.+)'")
def setup_logging() -> logging.Logger:
logger = logging.getLogger('cmspass.py')
logger.setLevel(logging.DEBUG)
out_fmt = logging.Formatter(fmt='%(levelname)s: %(message)s')
log_fmt = logging.Formatter(fmt='cmspass.py: %(levelname)s: %(message)s')
stdout = logging.StreamHandler(stream=sys.stdout)
stdout.setFormatter(out_fmt)
stdout.setLevel(logging.DEBUG)
logger.addHandler(stdout)
syslog = SysLogHandler(address='/dev/log')
syslog.setFormatter(log_fmt)
syslog.setLevel(logging.WARNING)
logger.addHandler(syslog)
return logger
def conf_parse(
regex: re.Pattern, path: Path
) -> tuple[Union[str, None], Union[str, None]]:
"""Will parse db conf variables"""
db_user, db_pass = None, None
with open(path, encoding='utf-8') as file:
for line in file:
if match := regex.search(line):
key, val = match.groups()
key: str
val: str
if key.lower() == 'user':
db_user = val
elif key.lower() == 'password':
db_pass = val
return db_user, db_pass
def set_pass(logger: logging.Logger, cpuser: str, db_user: str, db_pass: str):
"""Will pass the reset variables to the cPanel API"""
if db_user is None or db_pass is None:
return
try:
whmapi1(
'set_mysql_password',
args={'cpuser': cpuser, 'user': db_user, 'password': db_pass},
check=True,
)
except CpAPIError as exc:
logger.error("Failed to reset password for %s: %s", db_user, exc)
else:
logger.warning("Reset password for %s", db_user)
def parse_args() -> str:
"""Will parse input for user and ensure the user exists"""
parser = ArgumentParser(description=__doc__)
parser.add_argument('-u', '--user', required=True, help='cPanel username')
user = parser.parse_args().user
if not rads.cpuser_safe(user):
sys.exit(f"{user} does not exist or is restricted")
return user
def find_files(homedir: str) -> list[Path]:
"""Find all config files"""
# fmt: off
cmd = [
'find', homedir, '-not', '(', '-path', f"{homedir}/mail", '-prune', ')',
'(', '-name', 'wp-config.php', '-o', '-name', 'configuration.php', ')',
'-type', 'f', '-print0',
]
# fmt: on
ret = subprocess.run(
cmd, stdout=subprocess.PIPE, encoding='utf-8', check=False
)
return [Path(x) for x in ret.stdout.split('\0') if x]
def conf_is_joomla(path: Path):
if path.name != 'configuration.php':
return False
with open(path, encoding='utf-8') as conf:
for line in conf:
if 'class JConfig' in line:
return True
return False
def main():
user = parse_args()
logger = setup_logging()
try:
homedir = rads.get_homedir(user)
except rads.CpuserError as exc:
sys.exit(exc)
for path in find_files(homedir):
db_user, db_pass = None, None
if path.name == 'wp-config.php':
logger.debug('Scanning %s', path)
db_user, db_pass = conf_parse(WP_CONF, path)
elif conf_is_joomla(path):
logger.debug('Scanning %s', path)
db_user, db_pass = conf_parse(JM_CONF, path)
if db_user and db_pass:
set_pass(logger, user, db_user, db_pass)
if __name__ == "__main__":
main()
Zerion Mini Shell 1.0