Mini Shell
#!/opt/imh-python/bin/python
import sys
import re
import json
import argparse
if sys.stdout.isatty():
ANSI = {'bold_red': '[1;31m', 'reset': '[0m', 'bold': '[1m'}
else:
ANSI = {'bold_red': '', 'reset': '', 'bold': ''}
def load_sa_db():
"""Load SpamAssassin test info"""
with open('/opt/spamassassin/etc/spam_tests.json', 'r') as db:
sa_data = json.loads(db.read())
try:
with open('/etc/mail/spamassassin/imh_custom.cf') as custom_rules:
for line in custom_rules.read().splitlines():
if line.startswith('score'):
try:
discard, id, score = line.split()
except ValueError:
continue
try:
sa_data[id.strip()]['score'] = score.strip()
except KeyError:
sa_data[id.strip()] = {'score': score.strip()}
except IOError:
pass
return sa_data
def lookup_sa_id(id):
"""Prints info about a specific SpamAssassin test"""
nodata = "No Data"
print "{bold}{0}{reset}".format(id, bold=ANSI['bold'], reset=ANSI['reset'])
if id in SA_DATA:
entry = SA_DATA[id]
print "{:>15}".format("Area Scanned:"),
try:
print entry['area']
except KeyError:
print nodata
print "{:>15}".format("Description:"),
try:
print entry['desc']
except KeyError:
print nodata
print "{:>15}".format("Score (Max):"),
try:
print entry['score']
except KeyError:
print nodata
if 'wiki' in entry and entry['wiki']:
print "{:>15} {}".format("More Info:",
"%s/%s" % (SA_DATA['wiki_url'], id))
else:
print "\n".join((" No entry found in the database for %s." % id,
" Information may be available from the SpamAssassin wiki",
"{:>15} {}".format("More Info:",
"%s/%s" % (SA_DATA['wiki_url'], id))))
print
def summarize_rejected_mail(address):
addr = re.compile('(?i)F=<%s>' % address)
new_record = re.compile('^\d{4}-\d{2}-\d{2}')
messages = []
num = 0
append = False
cur_record = None
hr = "".join(['=' for i in xrange(80)])
with open('/var/log/exim_rejectlog') as rejectlog:
for line in rejectlog:
if addr.search(line):
messages.append({'header': [line]})
append = True
cur_record = messages[num]
num += 1
continue
if new_record.search(line):
append = False
continue
elif append and cur_record:
cur_record['header'].append(line)
if line.startswith('I '):
msg_id = line.split()[-1]
cur_record['id'] = msg_id
if not messages:
return
with open('/var/log/maillog') as maillog:
for line in maillog:
if not 'user=cpaneleximscanner' in line:
continue
for message in messages:
try:
if 'mid=%s' % message['id'] in line:
log_chunks = line.split()
message['score'] = log_chunks[8]
message['flags'] = log_chunks[10]
break
except Exception:
pass
if messages:
print "%s= Summary of unique rejected outbound mail for %s =%s" % \
(ANSI['bold'], address, ANSI['reset'])
count = 1
for message in messages:
if 'flags' in message:
print "%s== Headers from message %d ==%s\n" % \
(ANSI['bold'], count, ANSI['reset'])
print "".join(message['header'])
print "%s== Summary of SpamAssassin tests ==%s" % \
(ANSI['bold'], ANSI['reset'])
print " Spam Score: %s\n" % message['score']
for flag in message['flags'].split(','):
lookup_sa_id(flag)
count += 1
print hr, "\n"
if __name__ == '__main__':
global SA_DATA
SA_DATA = load_sa_db()
parser = argparse.ArgumentParser(
description='Aids in understanding why a message has been flagged as spam',
epilog='To look up multiple rule IDs or email addresses, separate each with a space.')
optgroup = parser.add_mutually_exclusive_group(required=True)
optgroup.add_argument('-r', '--rule', metavar='RULE', type=str, nargs='+',
help='SpamAssassin rule ID(s) to look up')
optgroup.add_argument('-a', '--address', metavar='EMAIL', type=str, nargs='+',
dest='address', help='search for bounces from a specific email address or addresses')
options = parser.parse_args()
if options.address:
for address in options.address:
summarize_rejected_mail(address)
elif options.rule:
for rule in options.rule:
lookup_sa_id(rule.upper())
Zerion Mini Shell 1.0