Mini Shell
#!/opt/imh-python/bin/python3
"""Wrapper around maldet"""
# author: Kyle Yetter
import sys
import os
import re
import pwd
import subprocess
from optparse import OptionParser
VERSION = '1.4.1'
# Top level constant definition for nice command pamaraters
IO_NICE_LEVEL = '6'
NICE_LEVEL = '10'
MALDET_PATH = '/opt/maldetect/maldet'
###############################################################################
############################## Utility Functions ##############################
###############################################################################
def shell_escape(token):
"""escapes special characters in shell tokens"""
token = str(token)
if len(token) == 0:
return "''"
token = re.sub(r'([^A-Za-z0-9_=\-\.,:\/@\n])', r'\\\1', token)
token = re.sub('\n', r'\n', token)
return token
def shell_join(*tokens):
"""Join a list of strings while shell scaping each"""
return ' '.join([shell_escape(t) for t in tokens])
def which(program):
"""Return path to executable based on env $PATH or None on error"""
search_path = os.environ.get('PATH', '').split(os.pathsep)
for directory in search_path:
exec_path = os.path.join(directory, program)
if os.path.isfile(exec_path) and os.access(exec_path, os.X_OK):
return os.path.abspath(exec_path)
return None
def scan_yaml(yaml_file, key):
"""Kyleism of yaml.load()"""
pattern = fr'^\s*({re.escape(key)})\s*:\s*(\S.*)$'
with open(yaml_file) as handle:
lines = handle.readlines()
for line in lines:
match = re.match(pattern, line.strip())
if match:
return match.group(2)
return None
def find_doc_root(user):
"""Find primary domain docroot"""
cpanel_userdata_file = os.path.join(
"/var/cpanel/userdata",
user.pw_name,
"main"
)
if os.path.isfile(cpanel_userdata_file):
main_domain = scan_yaml(cpanel_userdata_file, 'main_domain')
if main_domain:
domain_config_file = os.path.join(
"/var/cpanel/userdata",
user.pw_name,
main_domain
)
if os.path.isfile(domain_config_file):
doc_root = scan_yaml(domain_config_file, 'documentroot')
if doc_root and os.path.isdir(doc_root):
return doc_root
public_html_path = os.path.join(user.pw_dir, 'public_html')
if os.path.isdir(public_html_path):
return public_html_path
def fail(message):
"""Kyleism of sys.exit(str)"""
print("\033[31m%s\033[0m" % message, file=sys.stderr)
option_parser.print_help(sys.stderr)
sys.exit(1)
#
# These constants hold the full paths to ionice, nice, and sudo -
# used to determine whether each command is available
#
IO_NICE = which('ionice')
NICE = which('nice')
SUDO = which('sudo')
###############################################################################
######################### Command Line Option Parsing #########################
###############################################################################
usage = """
%prog [--scan-recent] (~user/public_html/path/... | userna5)
%prog --report SCANID userna5
%prog --quarantine SCANID userna5
%prog --clean SCANID userna5
%prog --log userna5
"""
option_parser = OptionParser(usage=usage)
option_parser.add_option(
'-q', '--quarantine',
dest="quarantine",
metavar="SCANID",
help="Quarantine any infected files found during the scan"
)
option_parser.add_option(
'-e', '--report',
dest="report",
metavar="SCANID",
help="View report from the previous scan given by SCANID"
)
option_parser.add_option(
'-s', '--restore',
dest="restore",
metavar="SCANID",
help="Restore files quarantined from a previous scan given by SCANID"
)
option_parser.add_option(
"-n", "--clean",
dest="clean",
metavar="SCANID",
help="Try to clean & restore malware hits from report SCANID"
)
option_parser.add_option(
'-l', '--log',
dest="log",
action="store_true",
help="View maldetect log file events"
)
option_parser.add_option(
"-r", "--scan-recent",
dest="recent",
action="store_true",
help="Scan files created/modified in the last X days "
"(default: 7d, wildcard: ?)"
)
option_parser.add_option(
"-p", "--purge",
dest="purge",
action="store_true",
help="Clear logs, quarantine queue, session and temporary data."
)
option_parser.add_option(
"-c", "--no-clam",
dest="clam",
action="store_true",
help="Disable the clam engine for scanning"
)
###############################################################################
############################## Main Script Action #############################
###############################################################################
# target_path contains an absolute path to a directory to scan
target_path = os.path.realpath('.')
# target_user is the system passwd entry for the user that
# the scan will execute as
target_user = pwd.getpwuid(os.stat('.').st_uid)
options, args = option_parser.parse_args()
if len(args) > 0:
arg = args[0]
if os.path.exists(arg):
target_path = os.path.realpath(arg)
target_user = pwd.getpwuid(os.stat(target_path).st_uid)
else:
try:
target_user = pwd.getpwnam(arg)
except KeyError as e:
fail(
"cannot locate a user or path "
"associated with argument %r" % (arg,)
)
target_path = find_doc_root(target_user)
if target_user.pw_uid == 0:
fail("mal-scan cannot act on paths owned by root")
if target_user.pw_uid < 500:
fail("mal-scan cannot act on paths owned by privileged system users")
command_parts = []
if NICE:
command_parts.extend([NICE, '-n', NICE_LEVEL])
if IO_NICE:
return_val = os.system("ionice -c 2 -n 4 echo >/dev/null 2>&1")
if return_val == 0:
command_parts.extend([IO_NICE, '-c', '2', '-n', IO_NICE_LEVEL])
command_parts.extend([SUDO, '-u', target_user.pw_name])
command_parts.append(MALDET_PATH)
scan_argument = None
if os.path.isdir(target_user.pw_dir):
os.chdir(target_user.pw_dir)
if options.report:
command_parts.extend(['--report', options.report])
elif options.quarantine:
command_parts.extend(['--quarantine', options.quarantine])
elif options.clean:
command_parts.extend(['--clean', options.clean])
elif options.restore:
command_parts.extend(['--restore', options.restore])
elif options.log:
command_parts.extend(['--log'])
elif options.purge:
command_parts.extend(['--purge'])
elif os.path.isdir(target_path):
scan_argument = os.path.realpath(target_path)
elif os.path.isfile(target_path):
scan_argument = os.path.realpath(target_path)
if options.clam:
command_parts.extend(['--config-option', 'clamav_scan=0'])
if scan_argument:
print("\033[32mScanning %s\033[0m" % target_path)
if options.recent:
command_parts.extend(['--scan-recent', scan_argument])
else:
command_parts.extend(['--scan-all', scan_argument])
# update signatures
subprocess.call([MALDET_PATH, '--update'])
# Run the scan. os.execvp replaces this python process with maldet itself,
# recycling the PID as well.
os.execvp(command_parts[0], command_parts)
Zerion Mini Shell 1.0