230 lines
7.3 KiB
Python
230 lines
7.3 KiB
Python
|
#!/usr/bin/env python3
|
||
|
|
||
|
from datetime import datetime
|
||
|
import email
|
||
|
import os
|
||
|
import sys
|
||
|
import stat
|
||
|
import subprocess
|
||
|
|
||
|
GNUPG_DIRECTORY = "~/.gnupg-checker"
|
||
|
SOFT_MAX_SUBMISSION = None
|
||
|
HARD_MAX_SUBMISSION = None
|
||
|
FROM = "Automatic VIRLI Mail Checker <virli@nemunai.re>"
|
||
|
SEND_TO_REALUSER = False
|
||
|
BETA = False
|
||
|
ALTERNATE_RESOLUTIONS = False
|
||
|
|
||
|
|
||
|
import archive
|
||
|
import envelope
|
||
|
import late
|
||
|
import login
|
||
|
import signature
|
||
|
from test import MailTest
|
||
|
|
||
|
def signcheck(data):
|
||
|
yield MailTest("Those tests are limited to signature checking. THIS IS NOT THE SUBMISSION INTERFACE.", 2)
|
||
|
yield data
|
||
|
|
||
|
|
||
|
def gen_checks(submissions_dir, check_content=False):
|
||
|
if HARD_MAX_SUBMISSION is not None and check_content:
|
||
|
yield (late.check, [HARD_MAX_SUBMISSION, SOFT_MAX_SUBMISSION])
|
||
|
else:
|
||
|
yield signcheck
|
||
|
yield (envelope.check, [GNUPG_DIRECTORY, BETA])
|
||
|
yield (signature.check, [GNUPG_DIRECTORY])
|
||
|
yield (login.check, ["/home/nemunaire/workspace/check_mail/SRS2017.csv"])
|
||
|
if check_content:
|
||
|
yield archive.find
|
||
|
yield ( archive.hash_archive, [submissions_dir] )
|
||
|
yield archive.guess_mime
|
||
|
yield ( archive.extract, [submissions_dir] )
|
||
|
|
||
|
|
||
|
def respondmail(to, subject, ref, checks):
|
||
|
from email.message import EmailMessage
|
||
|
|
||
|
if not isinstance(checks, list):
|
||
|
checks = [c for c in checks]
|
||
|
|
||
|
# Show only the first message if there is one ACCEPT
|
||
|
if len(checks) > 1 and not (ALTERNATE_RESOLUTIONS and BETA):
|
||
|
maxitem = checks[0]
|
||
|
for item in checks:
|
||
|
lvl, tests, final_decision = item
|
||
|
if maxitem[0] < lvl:
|
||
|
maxitem = item
|
||
|
if final_decision == "ACCEPT" or final_decision == "SKIP":
|
||
|
return respondmail(to, subject, ref, [(lvl, tests, final_decision)])
|
||
|
# Display the most upper error
|
||
|
if not ALTERNATE_RESOLUTIONS:
|
||
|
return respondmail(to, subject, ref, [maxitem])
|
||
|
|
||
|
msg = EmailMessage()
|
||
|
msg["X-loop"] = "virli"
|
||
|
msg["From"] = FROM
|
||
|
msg["To"] = to
|
||
|
if ref is not None:
|
||
|
msg["References"] = ref
|
||
|
msg["In-Reply-To"] = ref
|
||
|
msg["Subject"] = ("Re: " if not subject.lower().find("re: ") >= 0 else "") + subject
|
||
|
|
||
|
test = None
|
||
|
final_decision = "REJECT"
|
||
|
fmt = ""
|
||
|
for lvl, tests, final_decision in checks:
|
||
|
if fmt != "":
|
||
|
fmt += '\n============ There is also another resolution: ============\n\n'
|
||
|
for test in tests:
|
||
|
fmt += test.valuestr
|
||
|
fmt += test.title
|
||
|
fmt += '\n'
|
||
|
if test is not None and test.details is not None and test.details != "":
|
||
|
fmt += '\n'
|
||
|
fmt += test.details.strip()
|
||
|
fmt += '\n'
|
||
|
|
||
|
if final_decision == "SKIP":
|
||
|
fmt += '\nI stopped here the email analysis.'
|
||
|
else:
|
||
|
fmt += '\nAfter analyzing your e-mail, I\'ve decided to ' + final_decision + ' it.'
|
||
|
|
||
|
msg.set_content("""Hi!
|
||
|
|
||
|
This is the automatic e-mail analyzer in charge of checking your work.
|
||
|
|
||
|
Here is the detailed report for your submission:
|
||
|
|
||
|
""" + fmt + """
|
||
|
|
||
|
Sincerely,
|
||
|
|
||
|
-- \nAutomatic e-mail checker
|
||
|
running for nemunaire@nemunai.re""")
|
||
|
|
||
|
import smtplib
|
||
|
with smtplib.SMTP("localhost") as smtp:
|
||
|
if SEND_TO_REALUSER:
|
||
|
smtp.send_message(msg)
|
||
|
smtp.send_message(msg, to_addrs=["virli-report@nemunai.re"])
|
||
|
else:
|
||
|
print(msg.as_string())
|
||
|
|
||
|
|
||
|
def readmail(fp):
|
||
|
cnt = email.message_from_binary_file(fp)
|
||
|
frm = cnt.get("From") or "someone"
|
||
|
subject = cnt.get("Subject") or "your mail"
|
||
|
ref = cnt.get("Message-ID") or ""
|
||
|
|
||
|
return cnt, frm, subject, ref
|
||
|
|
||
|
|
||
|
def check_mail(cnt, submissions_dir, check_content=False):
|
||
|
results = []
|
||
|
|
||
|
# sentinel
|
||
|
results.append([(None, [cnt])])
|
||
|
|
||
|
lvl = 0
|
||
|
for check in gen_checks(submissions_dir=submissions_dir, check_content=check_content):
|
||
|
lvl += 1
|
||
|
curr = []
|
||
|
curc = []
|
||
|
|
||
|
for parent in range(len(results[-1])):
|
||
|
cnt = results[-1][parent][1][-1]
|
||
|
|
||
|
if cnt is True or cnt is None or cnt is False:
|
||
|
res = []
|
||
|
for i in range(len(results) - 1, 0, -1):
|
||
|
for m in reversed(results[i][parent][1][:-1]):
|
||
|
res.append(m)
|
||
|
parent = results[i][parent][0]
|
||
|
res.reverse()
|
||
|
if cnt is None: final = "REJECT"
|
||
|
elif cnt is False: final = "SKIP"
|
||
|
else: final = "ACCEPT"
|
||
|
yield lvl, res, final
|
||
|
continue
|
||
|
|
||
|
if isinstance(check, tuple):
|
||
|
chk, params = check
|
||
|
res = chk(cnt, *params)
|
||
|
else:
|
||
|
res = check(cnt)
|
||
|
|
||
|
for r in res:
|
||
|
curc.append(r)
|
||
|
if not isinstance(r, MailTest):
|
||
|
curr.append((parent, curc))
|
||
|
curc = []
|
||
|
|
||
|
if len(curc) > 0:
|
||
|
curc.append(None)
|
||
|
curr.append((parent, curc))
|
||
|
curc = []
|
||
|
results.append(curr)
|
||
|
|
||
|
for parent in range(len(results[-1])):
|
||
|
res = []
|
||
|
cnt = results[-1][parent][1][-1]
|
||
|
for i in range(len(results) - 1, 0, -1):
|
||
|
for m in reversed(results[i][parent][1][:-1]):
|
||
|
res.append(m)
|
||
|
parent = results[i][parent][0]
|
||
|
res.reverse()
|
||
|
if cnt is None: final = "REJECT"
|
||
|
elif cnt is False: final = "SKIP"
|
||
|
else: final = "ACCEPT"
|
||
|
yield len(results), res, final
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
os.umask(~(stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR))
|
||
|
GNUPG_DIRECTORY = os.path.expanduser(GNUPG_DIRECTORY)
|
||
|
if not os.path.isdir(GNUPG_DIRECTORY):
|
||
|
os.mkdir(GNUPG_DIRECTORY)
|
||
|
|
||
|
# Parse command line arguments
|
||
|
import argparse
|
||
|
parser = argparse.ArgumentParser()
|
||
|
|
||
|
parser.add_argument("-s", "--sign", action="store_true",
|
||
|
help="limit check to signature")
|
||
|
|
||
|
parser.add_argument("--real-send", action="store_true",
|
||
|
help="Effectively sent mail to real users")
|
||
|
|
||
|
parser.add_argument('--soft-max-submission', default="thursday 8:42",
|
||
|
help="allow submission until this hour")
|
||
|
|
||
|
parser.add_argument('--hard-max-submission', default="thursday 9:21",
|
||
|
help="allow submission until this hour")
|
||
|
|
||
|
parser.add_argument('--submissions', default="/tmp/rendus",
|
||
|
help="directory where store submissions")
|
||
|
|
||
|
parser.add_argument('--beta', action="store_true",
|
||
|
help="enable beta features")
|
||
|
|
||
|
parser.add_argument('--alternate-resolutions', action="store_true",
|
||
|
help="enable if you want to display alternate resolutions")
|
||
|
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
with subprocess.Popen(["date", "-d", args.soft_max_submission, "-u", "-Iseconds"], stdout=subprocess.PIPE) as f:
|
||
|
SOFT_MAX_SUBMISSION = datetime.strptime(f.stdout.read().strip().decode(), "%Y-%m-%dT%H:%M:%S%z")
|
||
|
|
||
|
with subprocess.Popen(["date", "-d", args.hard_max_submission, "-u", "-Iseconds"], stdout=subprocess.PIPE) as f:
|
||
|
HARD_MAX_SUBMISSION = datetime.strptime(f.stdout.read().strip().decode(), "%Y-%m-%dT%H:%M:%S%z")
|
||
|
|
||
|
ALTERNATE_RESOLUTIONS = args.alternate_resolutions
|
||
|
SEND_TO_REALUSER = args.real_send
|
||
|
BETA = args.beta
|
||
|
|
||
|
cnt, frm, subject, ref = readmail(sys.stdin.buffer)
|
||
|
respondmail(frm, subject, ref, [c for c in check_mail(cnt, submissions_dir=args.submissions, check_content=not args.sign)])
|