/opt/dedrads
NameSizeModeActions
account_review/-0555rm
check_software_mods/-0555rm
cms_tools/-0555rm
etc/-0555rm
extras/-0555rm
mailparse/-0555rm
mysql/-0555rm
nlp_scripts/-0555rm
oldrads/-0555rm
perl/-0555rm
provision/-0555rm
python/-0555rm
suspended/-0555rm
__pycache__/-0555rm
account-review7990555editdlrm
allfw62590555editdlrm
alp.py165530555editdlrm
autossl_sync62670555editdlrm
check_apache92050555editdlrm
check_bandwidth45460555editdlrm
check_boxtrapper16840555editdlrm
check_cpu122120555editdlrm
check_crons18270555editdlrm
check_dcpumon19730555editdlrm
check_dns18270555editdlrm
check_domlogs610555editdlrm
check_exim37310555editdlrm
check_hacks226040555editdlrm
check_imap52490555editdlrm
check_io66350555editdlrm
check_max_children163410555editdlrm
check_mem16600555editdlrm
check_misc15100555editdlrm
check_pacct52010555editdlrm
check_php43440555editdlrm
check_pop322950555editdlrm
check_prov.py47140555editdlrm
check_server6960555editdlrm
check_size4900555editdlrm
check_software98490555editdlrm
check_spamd24260555editdlrm
check_traffic19330555editdlrm
check_user18870555editdlrm
clean_exim.py63900555editdlrm
clusterfix53430555editdlrm
cmspass.py39700555editdlrm
cms_check.py98250555editdlrm
cms_counter.py367340555editdlrm
cms_creds7130555editdlrm
cms_dumpdb44850555editdlrm
cms_pw14160555editdlrm
cpanel-api70400555editdlrm
cpanel_postgres_manager.py290040555editdlrm
cpmerge46310555editdlrm
cpumon21950555editdlrm
dcpumon.pl12390555editdlrm
dedcheck181270555editdlrm
dns-sync143860555editdlrm
docroot.py44250555editdlrm
du-tree74500555editdlrm
envinfo.py66330555editdlrm
exclude_rbl.py52490555editdlrm
extract-vhost26910555editdlrm
filescan35660555editdlrm
find_warez14600555editdlrm
first_setup.py191200555editdlrm
fixwpcron.py48220555editdlrm
fix_dns_cluster3530555editdlrm
goaccess12177200555editdlrm
hostsfilemods44670555editdlrm
imap_io23230555editdlrm
innodb_converter.py93430555editdlrm
killall9115300555editdlrm
killdns9410555editdlrm
lastcommcache.sh13740555editdlrm
legal_lock_down.sh74500555editdlrm
lil-cpanel520020555editdlrm
listacct45750555editdlrm
mailscan45440555editdlrm
mail_sources.py74070555editdlrm
megaclisas-status386640555editdlrm
migrate2central.sh144180555editdlrm
modify-account406310555editdlrm
modsec_disable.py114610555editdlrm
monarxctl89120555editdlrm
msp.pl277140555editdlrm
mysql_dstat4740555editdlrm
mysql_selector.py731020555editdlrm
nlp45820555editdlrm
procscrape18630555editdlrm
quarantine110830555editdlrm
radsfunctions.sh8630555editdlrm
recent-cp161480555editdlrm
remote_dump183470555editdlrm
reset_cpanel32050555editdlrm
server-load21920555editdlrm
show-conns201050555editdlrm
show-conns-adv.py546670555editdlrm
sparta.py3824980555editdlrm
sqltop271290555editdlrm
telcheck24110555editdlrm
temprootreset1860555editdlrm
unsuspend_user13070555editdlrm
unsusprunner.sh18400555editdlrm
updatednsadmin5260555editdlrm
update_spf149720555editdlrm
upgrade-check92160555editdlrm
vhost_data.py62210555editdlrm
wp-xray.sh858160555editdlrm
Edit: /opt/dedrads/clean_exim.py (6390B)
#!/usr/lib/rads/venv/bin/python3 """Python exim cleanup script to reduce the urge to run hacky oneliners""" import argparse import re import subprocess import sys from collections import defaultdict # compile regex once and recycle them throughout the script for efficiency EXIM_BP_RE = re.compile( r'\s*(?P[0-9]+[mhd])\s+[0-9\.]+[M|K]? ' # 5m 15K r'(?P[a-zA-Z0-9\-]+)\s+' # 1XEhho-0006AU-Tx r'\<(?P.*)\>' # ) def run(command: list[str] | str, shell=False) -> subprocess.CompletedProcess: """ Run a process with arguments. Optionally, as a shell command returns CompletedProcess. """ args = command result = subprocess.run( args, capture_output=True, shell=shell, errors="surrogateescape", encoding="utf-8", check=False, ) return result def get_queue(exclude_bounces=False, bounces_only=False): """Get current exim queue as a list of dicts, each dict containing a single message, with keys 'age', 'id', and 'sender'""" queue = [] out = run(["exim", "-bp"]) queue_lines = out.stdout.splitlines() for line in queue_lines: match = EXIM_BP_RE.match(line) if match is None: continue groupdict = match.groupdict() if exclude_bounces and groupdict['sender'] == '': continue if bounces_only and groupdict['sender'] != '': continue groupdict['age'] = exim_age_to_secs(groupdict['age']) if groupdict['age'] > 3600: queue.append(groupdict) return queue def exim_age_to_secs(age): """convert exim age to seconds""" conversions = {'m': 60, 'h': 3600, 'd': 86400} # find the multiplier based on above conversions multiplier = conversions[age[-1]] # typecast to int and use multiplier above return int(age.rstrip('mhd')) * multiplier def is_boxtrapper(msg_id): """Determine if a message ID is a boxtrapper msg in queue""" try: out = run(['exim', '-Mvh', msg_id]) out.check_returncode() head = out.stdout.strip() except subprocess.CalledProcessError: return False # likely no longer in queue return 'Subject: Your email requires verification verify#' in head def remove_msg(msg_id): """Here, msg_id may be a list or just one string""" if isinstance(msg_id, list): msg_id = ' '.join(msg_id) try: run(['exim', '-Mrm', msg_id]).check_returncode() except subprocess.CalledProcessError: pass # may have already been removed from queue or sent def print_removed(removed): """Given a dict of user:count mappings, print removed""" if len(list(removed.keys())) == 0: print('None to remove') else: print('Removed:') for user, count in removed.items(): print(' %s: %d' % (user, count)) def clean_boxtrapper(): """Remove old boxtrapper messages from queue. The theory is that if still stuck in queue after min_age_secs, they were likely sent from an illigitimate email address and will never clear from queue normally""" print('Removing old boxtrapper messages...') removed = defaultdict(int) queue = get_queue(exclude_bounces=True) queue = [x for x in queue if is_boxtrapper(x['id'])] for msg in queue: remove_msg(msg['id']) removed[msg['sender']] += 1 print_removed(removed) def get_full_quota_user(msg_id): """Get the user for a message which is at full quota, or None""" try: out = run(['exim', '-Mvb', msg_id]) out.check_returncode() body = out.stdout.strip() except subprocess.CalledProcessError: return None if not ' Mailbox quota exceeded' in body: return None body = body.splitlines() addr_line = -1 for line_num, line in enumerate(body): if ' Mailbox quota exceeded' in line: addr_line = line_num - 1 break if addr_line >= 0: return body[addr_line].strip() return None def clean_full_inbox_bounces(): """Remove "mailbox quota exceeded" bounces from queue""" print('Removing old full inbox bounces...') removed = defaultdict(int) queue = get_queue(bounces_only=True) for msg in queue: user = get_full_quota_user(msg['id']) if user is None: continue remove_msg(msg['id']) removed[user] += 1 print_removed(removed) def clean_autoresponders(): """Remove old auto-responder emails from queue which are likely responses to spam if they have been stuck in queue""" print('Removing old auto-responses...') removed = defaultdict(int) queue = get_queue(exclude_bounces=True) for msg in queue: try: out = run(['exim', '-Mvh', msg['id']]) out.check_returncode() headers = out.stdout.strip() except subprocess.CalledProcessError: continue if 'X-Autorespond:' in headers and 'auto_reply' in headers: remove_msg(msg['id']) removed[msg['sender']] += 1 print_removed(removed) def _parse_args(): """Parse commandline arguments""" parser = argparse.ArgumentParser( description="Exim cleanup tool", epilog="One and only one option is allowed", ) parser.add_argument( '-a', '--all', action='store_true', help='Do all cleanup procedures' ) parser.add_argument( '-b', '--boxtrapper', action='store_true', help='Remove old boxtrapper messages', ) parser.add_argument( '-r', '--autorespond', action='store_true', help='Remove old auto-responder messages', ) parser.add_argument( '-f', '--full', action='store_true', help='Remove old bounces for full inbox', ) args = parser.parse_args() chosen = [key for key, value in vars(args).items() if value is True] if len(chosen) != 1: parser.print_help() sys.exit(1) return args def main(): """Main logic""" args = _parse_args() if args.boxtrapper or args.all: clean_boxtrapper() if args.full or args.all: clean_full_inbox_bounces() if args.autorespond or args.all: clean_autoresponders() if __name__ == '__main__': main()