Hi all,I revamped attachments.py in order to catch Javascript Trojans inside a zip, which were driving me crazy. While I added that, I removed the configurable archive. The attached flavor of the filter rejects just the extensions hardcoded in the source.
Enjoy Ale
#!/usr/bin/python # attachments -- Courier filter which blocks specified attachment types # Copyright (C) 2005-2008 Robert Penz <rob...@penz.name> # hacked (H) 2017 ale # # This file is part of pythonfilter. # # pythonfilter is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # pythonfilter is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with pythonfilter. If not, see <http://www.gnu.org/licenses/>. import sys from email.message import Message, _unquotevalue import email.utils from zipfile import ZipFile from StringIO import StringIO # https://support.google.com/mail/answer/6590?hl=en # http://www.theverge.com/2017/1/25/14391462/gmail-javascript-block-file-attachments-malware-security blocked_extensions = ('.js', '.ade', '.adp', '.bat', '.chm', '.cmd', '.com', '.cpl', '.exe', '.hta', '.ins', '.isp', '.jar', '.jse', '.lib', '.lnk', '.mde', '.msc', '.msp', '.mst', '.pif', '.scr', '.sct', '.shb', '.sys', '.vb', '.vbe', '.vbs', '.vxd', '.wsc', '.wsf', '.wsh') def de_comment(field): """Parse a header field fragment and remove comments. copied from AddrlistClass.getdelimited() in email/_parseaddr.py """ slist = [''] quote = False pos = 0 depth = 0 while pos < len(field): if quote: quote = False elif field[pos] == '(': depth += 1 elif field[pos] == ')': depth = max(depth - 1, 0) pos += 1 continue elif field[pos] == '\\': quote = True if depth == 0: slist.append(field[pos]) pos += 1 return ''.join(slist) def is_quoted(value): """ Check whether a value (string or tuple) is quoted """ if isinstance(value, tuple): return value[2].startswith('"') else: return value.startswith('"') class MyMessage(Message): """Email message with comments stripped """ def __init__(self, *args, **kwargs): Message.__init__(self, *args, **kwargs) def get_filename(self, failobj=None): """Return the filename associated with the payload if present. The filename is extracted from the Content-Disposition header's `filename' parameter. If that header is missing the `filename' parameter, this method falls back to looking for the `name' parameter. """ # changed from original: get the unquoted string missing = object() filename = self.get_param('filename', missing, 'content-disposition', unquote=False) if filename is missing: filename = self.get_param('name', missing, 'content-type', unquote=False) if filename is missing: return failobj # added to original: non quoted comments are removed bare = is_quoted(filename) if not bare: filename = _unquotevalue(filename) filename = email.utils.collapse_rfc2231_value(filename) if bare and '(' in filename: filename = de_comment(filename) return filename.strip().lower() def test_fname(filename): """ filename defined and lower().strip() """ if filename.endswith(".gz"): filename = filename[0:len(filename)-3] return filename.endswith(blocked_extensions) def doFilter(bodyFile, controlFileList): try: msg = email.message_from_file(open(bodyFile), _class=MyMessage) block = False for part in msg.walk(): try: # multipart/* are just containers if part.get_content_maintype() == 'multipart': continue # get_filename() is subclassed filename = part.get_filename() if filename: if filename.endswith(".zip"): mzip = ZipFile(StringIO(part.get_payload(decode=True))) for fn in mzip.namelist(): block = test_fname(fn) if block: break else: block = test_fname(filename) except: continue if block: return "550 Attachment rejected for policy reasons" except Exception as e: sys.stderr.write('attachments ' + type(e).__name__ + ': ' + str(e) + '\n') # nothing found --> to the next filter return '' if __name__ == '__main__': # For debugging, you can create a file that contains a message # body, possibly including attachments. # Run this script with the name of that file as an argument, # and it'll print either a permanent failure code to indicate # that the message would be rejected, or print nothing to # indicate that the remaining filters would be run. if len(sys.argv) != 2: print "Usage: attachments.py <message_body_file>" sys.exit(0) print doFilter(sys.argv[1], [])
------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, SlashDot.org! http://sdm.link/slashdot
_______________________________________________ courier-users mailing list courier-users@lists.sourceforge.net Unsubscribe: https://lists.sourceforge.net/lists/listinfo/courier-users