summaryrefslogtreecommitdiffstats
path: root/dmarcpipe.py
diff options
context:
space:
mode:
authorTomasz Kramkowski <tk@the-tk.com>2016-12-18 21:31:26 +0000
committerTomasz Kramkowski <tk@the-tk.com>2016-12-18 21:31:26 +0000
commit446baea9b316b0d00ed947280ac712d70bbb7443 (patch)
treed724a55f50cf5c0ff6bb8523074e1a73db80d957 /dmarcpipe.py
downloaddmarcpipe-446baea9b316b0d00ed947280ac712d70bbb7443.tar.gz
dmarcpipe-446baea9b316b0d00ed947280ac712d70bbb7443.tar.xz
dmarcpipe-446baea9b316b0d00ed947280ac712d70bbb7443.zip
Init commit
Diffstat (limited to 'dmarcpipe.py')
-rwxr-xr-xdmarcpipe.py92
1 files changed, 92 insertions, 0 deletions
diff --git a/dmarcpipe.py b/dmarcpipe.py
new file mode 100755
index 0000000..5e26d34
--- /dev/null
+++ b/dmarcpipe.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+
+from dmarc import parse_dmarc
+from getopt import gnu_getopt
+from gzip import decompress as gz_decompress
+from io import BytesIO
+from re import compile as re_compile
+from sql import store_dmarc
+from sys import stdin, stdout, argv
+from zipfile import ZipFile
+import email
+
+default_enc = 'US-ASCII'
+policy_domain = 'the-tk.com'
+re_valid_filename = re_compile('^[^\\s!]+![^\\s!]+![0-9]+![0-9]+(![^\\s!]+)?.(xml(.gz)?|zip)$')
+logfile = None
+dbfile = 'dmarc.db'
+
+class FalseReportException(Exception):
+ pass
+
+def is_valid_filename(f):
+ if re_valid_filename.match(f) is not None:
+ return True
+ return False
+
+def zip2xml(data):
+ z = ZipFile(BytesIO(data))
+ n = z.namelist()
+ if len(n) != 1 or not is_valid_filename(n[0]):
+ raise FalseReportException('zip2xml: broken zip len({})'.format(len(n)))
+ return z.open(n[0]).read()
+
+gzip2xml = lambda data: gz_decompress(data)
+
+decoders = {
+ 'application/zip': zip2xml,
+ 'application/x-zip': zip2xml,
+ 'application/x-zip-compressed': zip2xml,
+ 'application/zlib': gzip2xml,
+ 'application/gzip': gzip2xml,
+ 'text/xml': lambda data: data,
+ }
+
+def process_message(m):
+ if m.get_content_maintype() != 'multipart' and \
+ m.get_content_type() not in decoders.keys():
+ raise FalseReportException('invalid content type: {}'.format(m.get_content_type()))
+
+ att = None
+ inv_fn = False
+
+ for part in m.walk():
+ if part.get_content_maintype() == 'multipart':
+ continue
+ if part.get_content_type() not in decoders.keys():
+ continue
+ if part.get('Content-Disposition') is None:
+ continue
+ if not is_valid_filename(part.get_filename()):
+ inv_fn = True
+ continue
+ att = part
+ break
+
+ if att is None:
+ raise FalseReportException('attachment not found{}'.format(inv_fn == True and ' (invalid filename)' or ''))
+
+ xml = decoders[att.get_content_type()](att.get_payload(decode=True))
+ dmarc = parse_dmarc(xml)
+ store_dmarc(dbfile, dmarc)
+
+def main():
+ global logfile, dbfile
+ opts, args = gnu_getopt(argv, 'l:')
+ for o in opts:
+ if o[0] == '-l':
+ logfile = o[1]
+ if len(args) == 2:
+ print(args[1])
+ dbfile = args[1]
+ m = email.message_from_file(stdin)
+ try:
+ process_message(m)
+ m['X-DMARC-Report'] = 'True'
+ except Exception as e:
+ m['X-DMARC-Report'] = 'False; {}'.format(repr(e))
+ finally:
+ stdout.write(m.as_string())
+
+if __name__ == '__main__':
+ main()