From 5033b1a9ca3153b3ef18f55b684335c0928e118f Mon Sep 17 00:00:00 2001 From: seamus tuohy Date: Sat, 22 Oct 2016 17:13:20 -0400 Subject: [PATCH] Added email meta-data import module. This email meta-data import module collects basic meta-data from an e-mail and populates an event with it. It populates the email subject, source addresses, destination addresses, subject, and any attachment file names. This commit also contains unit-tests for this module as well as updates to the readme. Readme updates are additions aimed to make it easier for outsiders to build modules. --- README.md | 138 ++++++++++++++- .../modules/import_mod/email_import.py | 130 ++++++++++++++ tests/test.py | 35 +++- tests/test_attachment.eml | 167 ++++++++++++++++++ tests/test_no_attach.eml | 144 +++++++++++++++ 5 files changed, 606 insertions(+), 8 deletions(-) create mode 100644 misp_modules/modules/import_mod/email_import.py create mode 100644 tests/test_attachment.eml create mode 100644 tests/test_no_attach.eml diff --git a/README.md b/README.md index fbc9400..1575aae 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [OCR](misp_modules/modules/import_mod/ocr.py) Optical Character Recognition (OCR) module for MISP to import attributes from images, scan or faxes. * [stiximport](misp_modules/modules/import_mod/stiximport.py) - An import module to process STIX xml/json +* [Email Import](misp_modules/modules/import_mod/email_import.py) Email import module for MISP to import basic metadata. ## How to install and start MISP modules? @@ -52,7 +53,7 @@ sudo vi /etc/rc.local, add this line: `sudo -u www-data misp-modules -s &` ## How to add your own MISP modules? -Create your module in [misp_modules/modules/expansion/](misp_modules/modules/expansion/). The module should have at minimum three functions: +Create your module in [misp_modules/modules/expansion/](misp_modules/modules/expansion/), [misp_modules/modules/export_mod/](misp_modules/modules/export_mod/), or [misp_modules/modules/import_mod/](misp_modules/modules/import_mod/). The module should have at minimum three functions: * **introspection** function that returns a dict of the supported attributes (input and output) by your expansion module. * **handler** function which accepts a JSON document to expand the values and return a dictionary of the expanded values. @@ -60,7 +61,44 @@ Create your module in [misp_modules/modules/expansion/](misp_modules/modules/exp Don't forget to return an error key and value if an error is raised to propagate it to the MISP user-interface. -If your module requires additional configuration (to be exposed via the MISP user-interface), a config array is added to the meta-data output containing all the potential configuration values: +~~~python +... + # Checking for required value + if not request.get('ip-src'): + # Return an error message + return {'error': "A source IP is required"} +... +~~~ + + +### introspection + +The function that returns a dict of the supported attributes (input and output) by your expansion module. + +~~~python +mispattributes = {'input': ['link', 'url'], + 'output': ['attachment', 'malware-sample']} + +def introspection(): + return mispattributes +~~~ + +### version + +The function that returns a dict with the version and the associated meta-data including potential configurations required of the module. + +If your module requires additional configuration (to be exposed via the MISP user-interface), you can define those in the moduleconfig value returned by the version function. + +~~~python +# config fields that your code expects from the site admin +moduleconfig = ["apikey", "event_limit"] + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo +~~~ + +When you do this a config array is added to the meta-data output containing all the potential configuration values: ~~~ "meta": { @@ -77,12 +115,33 @@ If your module requires additional configuration (to be exposed via the MISP use ... ~~~ +### handler + +The function which accepts a JSON document to expand the values and return a dictionary of the expanded values. + +~~~python +def handler(q=False): + "Fully functional rot-13 encoder" + if q is False: + return False + request = json.loads(q) + src = request.get('ip-src') + if src is None: + # Return an error message + return {'error': "A source IP is required"} + else: + return {'results': + codecs.encode(src, "rot-13")} +~~~ + + ### Module type -A MISP module can be of two types: +A MISP module can be of three types: - **expansion** - service related to an attribute that can be used to extend and update an existing event. - **hover** - service related to an attribute to provide additional information to the users without updating the event. +- **import** - service related to importing and parsing an external object that can be used to extend an existing event. module-type is an array where the list of supported types can be added. @@ -173,7 +232,7 @@ Based on this information, a query can be built in a JSON format and saved as bo Then you can POST this JSON format query towards the MISP object server: -~~~ +~~~bash curl -s http://127.0.0.1:6666/query -H "Content-Type: application/json" --data @body.json -X POST ~~~ @@ -219,7 +278,78 @@ It is also possible to restrict the category options of the resolved attributes For both the type and the category lists, the first item in the list will be the default setting on the interface. +### Enable your module in the web interface + +For a module to be activated in the MISP web interface it must be enabled in the "Plugin Settings. + +Go to "Administration > Server Settings" in the top menu +- Go to "Plugin Settings" in the top "tab menu bar" +- Click on the name of the type of module you have created to expand the list of plugins to show your module. +- Find the name of your plugin's "enabled" value in the Setting Column. +"Plugin.[MODULE NAME]_enabled" +- Double click on its "Value" column + +~~~ +Priority Setting Value Description Error Message +Recommended Plugin.Import_ocr_enabled false Enable or disable the ocr module. Value not set. +~~~ + +- Use the drop-down to set the enabled value to 'true' + +~~~ +Priority Setting Value Description Error Message +Recommended Plugin.Import_ocr_enabled true Enable or disable the ocr module. Value not set. +~~~ + +### Set any other required settings for your module + +In this same menu set any other plugin settings that are required for testing. + + ## How to contribute your own module? Fork the project, add your module, test it and make a pull-request. Modules can be also private as you can add a module in your own MISP installation. + +## Tips for developers creating modules + +Download a pre-built virtual image from the [MISP training materials](https://www.circl.lu/services/misp-training-materials/). + +- Create a Host-Only adapter in VirtualBox +- Start the virtual machine +- SSH into the machine using the misp user +- Go into the misp-modules directory + +~~~bash +cd /usr/local/src/misp-modules +~~~ + +Set the git repo to your fork and checkout your development branch. If you SSH'ed in as the misp user you will have to use sudo. + +~~~bash +sudo git remote set-url origin https://github.com/YourRepo/misp-modules.git +sudo git pull +sudo git checkout MyModBranch +~~~ + +Remove the contents of the build directory and re-install misp-modules. + +~~~python +sudo rm -fr build/* +sudo pip3 install --upgrade . +~~~ + +SSH in with a different terminal and run `misp-modules` with debugging enabled. + +~~~python +misp-modules -d +~~~ + + +In your original terminal you can now run your tests manually and see any errors that arrive + +~~~bash +cd tests/ +curl -s http://127.0.0.1:6666/query -H "Content-Type: application/json" --data @MY_TEST_FILE.json -X POST +cd ../ +~~~ diff --git a/misp_modules/modules/import_mod/email_import.py b/misp_modules/modules/import_mod/email_import.py new file mode 100644 index 0000000..0d66330 --- /dev/null +++ b/misp_modules/modules/import_mod/email_import.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import json +import base64 +from email import message_from_bytes +from email.utils import parseaddr +import re + +misperrors = {'error': 'Error'} +userConfig = { } + +inputSource = ['file'] + +moduleinfo = {'version': '0.1', + 'author': 'Seamus Tuohy', + 'description': 'Email import module for MISP', + 'module-type': ['import']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + results = [] + + # Decode and parse email + request = json.loads(q) + # request data is always base 64 byte encoded + data = base64.b64decode(request["data"]) + message = message_from_bytes(data) + + # Extract header information + + # Subject + results.append({"values": message.get('Subject'), + "types": ['email-subject']}) + + # Source + from_addr = message.get('From') + results.append({"values": parseaddr(from_addr)[1], + "types": ['email-src'], + "comment": "From: {0}".format(from_addr)}) + + return_path = message.get('Return-Path') + results.append({"values": parseaddr(return_path)[1], + "types": ['email-src'], + "comment": "Return Path: {0}".format(return_path)}) + + # Destinations + ## Split and sort destination header values + recipient_headers = ['To', 'Cc', 'Bcc'] + destinations = {} + + for hdr_val in recipient_headers: + try: + addrs = message.get(hdr_val).split(',') + for addr in addrs: + ## Parse and add destination header values + parsed_addr = parseaddr(addr) + results.append({"values": parsed_addr[1], + "types": ["email-dst"], + "comment": "{0}: {1}".format(hdr_val, + addr)}) + except AttributeError: + continue + + # # TODO add 'email-dst-realname' value + # results.append({"values":parsed_addr[1], + # "types":["email-dst-realname"], + # "comment":"{0}: {1}".format(dst_type, + # addr)}) + + # Targets + # Get the addresses that received the email. + # As pulled from the Received header + received = message.get_all('received') + email_targets = set() + for rec in received: + try: + email_check = re.search("for\s(.*@.*);", rec).group(1) + email_check = email_check.strip(' <>') + email_targets.add(parseaddr(email_check)[1]) + except (AttributeError): + continue + for tar in email_targets: + results.append({"values": tar, + "types": ["target-email"], + "comment": "Extracted from email 'Received' header"}) + + ## TODO add 'email-received-path' value + # received_path = '\n'.join(received) + # results.append({"values":received_path, + # "types":["email-received-path"]}) + + # Attachments + # Get file names of attachments + for part in message.walk(): + filename = part.get_filename() + if filename is not None: + results.append({"values": filename, + "types": ["email-attachment"]}) + + r = {'results': results} + return r + + +def introspection(): + modulesetup = {} + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo + +if __name__ == '__main__': + with open('tests/test_no_attach.eml', 'r') as email_file: + handler(q=email_file.read()) diff --git a/tests/test.py b/tests/test.py index 2f000ec..bbd91bb 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- import unittest @@ -6,6 +6,8 @@ import requests import base64 import json import os +import urllib + class TestModules(unittest.TestCase): @@ -17,27 +19,51 @@ class TestModules(unittest.TestCase): def test_introspection(self): response = requests.get(self.url + "modules") print(response.json()) + response.connection.close() def test_cve(self): with open('tests/bodycve.json', 'r') as f: response = requests.post(self.url + "query", data=f.read()) print(response.json()) + response.connection.close() def test_dns(self): with open('tests/body.json', 'r') as f: response = requests.post(self.url + "query", data=f.read()) print(response.json()) + response.connection.close() with open('tests/body_timeout.json', 'r') as f: response = requests.post(self.url + "query", data=f.read()) print(response.json()) + response.connection.close() def test_stix(self): with open("tests/stix.xml", "r") as f: data = json.dumps({"module":"stiximport", - "data":str(base64.b64encode(bytes(f.read(), 'utf-8')), 'utf-8'), + "data":str(base64.b64encode(bytes(f.read(), 'utf-8'))), "config": {"max_size": "15000"}, }) + response = requests.post(self.url + "query", data=data) + response.connection.close() + print(response.json()) + + def test_email_headers(self): + with open("tests/test_no_attach.eml", "r") as f: + data = json.dumps({"module":"email_import", + "data":str(base64.b64encode(bytes(f.read(), 'utf8')), + 'utf8')}).encode('utf8') + response = requests.post(self.url + "query", data=data) + response.connection.close() + print(response.json()) + + def test_email_attachment(self): + with open("tests/test_attachment.eml", "r") as f: + data = json.dumps({"module":"email_import", + "data":str(base64.b64encode(bytes(f.read(), 'utf8')), + 'utf8')}).encode('utf8') + response = requests.post(self.url + "query", data=data) + response.connection.close() print(response.json()) def test_virustotal(self): @@ -47,10 +73,11 @@ class TestModules(unittest.TestCase): if not os.path.exists("tests/bodyvirustotal.json"): return - + with open("tests/bodyvirustotal.json", "r") as f: response = requests.post(self.url + "query", data=f.read()).json() assert(response) + response.connection.close() if __name__ == '__main__': - unittest.main() + unittest.main() diff --git a/tests/test_attachment.eml b/tests/test_attachment.eml new file mode 100644 index 0000000..1b52374 --- /dev/null +++ b/tests/test_attachment.eml @@ -0,0 +1,167 @@ +Received: via dmail-2008.19 for +INBOX; + Tue, 3 Feb 2009 19:29:12 -0600 (CST) +Received: from abc.luxsci.com ([10.10.10.10]) + by xyz.luxsci.com (8.13.7/8.13.7) with + ESMTP id n141TCa7022588 + for ; + Tue, 3 Feb 2009 19:29:12 -0600 +Return-Path: evil_spoofer@example.com +Received: from [192.168.0.3] (verizon.net [44.44.44.44]) + (user=test@sender.com mech=PLAIN bits=2) + by abc.luxsci.com (8.13.7/8.13.7) with + ESMTP id n141SAfo021855 + (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA + bits=256 verify=NOT) for ; + Tue, 3 Feb 2009 19:28:10 -0600 +Message-ID: <4988EF2D.40804@domain.com> +Date: Tue, 03 Feb 2009 20:28:13 -0500 +From: "Innocent Person" +User-Agent: Thunderbird 2.0.0.19 (Windows/20081209) +MIME-Version: 1.0 +To: "Testy Testerson" +Cc: "Second Person" , "Other Friend" , "Last One" +Subject: Example Message +Content-Type: multipart/mixed; boundary=047d7b2edc8d80dac9053f7a3f8d + +--047d7b2edc8d80dac9053f7a3f8d +Content-Type: multipart/alternative; boundary=047d7b2edc8d80dac4053f7a3f8b + +--047d7b2edc8d80dac4053f7a3f8b +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: base64 + +TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg +c2VkIGRvIGVpdXNtb2QNCnRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFn +bmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltDQp2ZW5pYW0sIHF1aXMgbm9zdHJ1ZCBleGVyY2l0 +YXRpb24gdWxsYW1jbyBsYWJvcmlzIG5pc2kgdXQgYWxpcXVpcCBleCBlYQ0KY29tbW9kbyBjb25z +ZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0 +ZQ0KdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhj +ZXB0ZXVyIHNpbnQgb2NjYWVjYXQNCmN1cGlkYXRhdCBub24gcHJvaWRlbnQsIHN1bnQgaW4gY3Vs +cGEgcXVpIG9mZmljaWEgZGVzZXJ1bnQgbW9sbGl0IGFuaW0gaWQNCmVzdCBsYWJvcnVtLg0KDQrQ +ndCw0Lwg0LvRjNCw0LHQvtGA0Y0g0L/QvtGI0LbQuNC8INC10LQsINC80Y3Qu9GMINC90L4g0L7Q +sdC70YzQudC60LLRjtGNINGN0YDRgNC+0YDQuNCx0YPQtyDQsNCx0YXQvtGA0YDRjdCw0L3Rgiwg +0LDRgiDQu9Cw0LHQvtGA0LDQvNGO0LcNCtCy0Y7Qu9GM0L/Rg9GC0LDRgtGLINCy0Y3Quy4g0JnQ +vSDRg9C90Y7QvCDRjtGA0LHQsNC90LnRgtCw0LYg0LLQtdC60LYsINC50L0g0Y3QvtC2INGB0YrR +jtC80LzQviDQtNC10LrRgtCw0LYuINCQ0LvQuNGRINC80Y7QvdGL0YDRjQ0K0ZHRg9C00ZHQutCw +0LHQtdGCINC90YvQuiDRjdGOLCDRg9GCINC30LDQu9GM0Ysg0L/QvtGA0YDQviDQtNC50LrQuNGC +INCy0LjQvC4g0J3QviDQv9C+0L3QtNGN0YDRjtC8INC30LrRgNC40L/RgtC+0YDRjdC8INGL0LDQ +vC4NCg0K54mh5pyI5YWD5L2P6YCj5Yud6KuW5pyd56Wd5aSJ6ZmN5b6X44CC5Lq65q2j6IGe5LqL +6KaW57aZ55S755m65p2l5Yui5bee5ZaE5pyA6JGJ6ICF55u444CC55+l6KW/5Zu95pKy55Sf5riI +5YWD5YCN56aP5Zuz57SE5pyI5YiG546L44CC5aWz5ryU6KaL5Y2U5rK75Yqg6K2w5b+F6YKj6KiY +6aOy5LiN5Z6L6KeS5rOo6YCy5q6L5LiW44CCDQroppbmlq3pn7PntLDooZfov5Hlkb3mlq3mpJzl +p7/nlJ/lhYXmsr/lpKfmsZDku67liIDjgILokZfoirjms5XmnaXploDlhYjlsJHlt53mtojnqJrn +ooHogZ7lrrnnrKznmYLmuKzlsI/nlbPokYnjgILmlZnmpJznkIPmraLmjZzluLjoq77npoHlspDk +u5Xph5HovInlkajmvZ/lhKrjgILnlLvoqq3otorooYDmpa3plbflgaXmj5DlsZ7pg6jkv53kuIfl +vqnkuIfnj77muIvoqKrlrq7lrrnov5HjgIINCuaYjuW/heWbsumDteaBteW6g+acgOa0l+Wunei8 +iei/lOmDqOOAgg0KDQrgpLngpYvgpJfgpL4g4KS44KSC4KSq4KS+4KSm4KSVIOCkheCkqOClgeCk +leClguCksiDgpLjgpL7gpLDgpY3gpLXgpJzgpKjgpL/gpJUg4KS14KS/4KSt4KS+4KSXIOCkhuCk +nOCkquCksCDgpLjgpYHgpJrgpKjgpL4g4KS44KWN4KSl4KS/4KSk4KS/IOCkteCkvuCksOCljeCk +pOCkvuCksuCkvuCkqiDgpKrgpYHgpLfgpY3gpJ/gpL/gpJXgpLDgpY3gpKTgpL4NCuCkruClgeCk +luCljeCkr+CkpOCkuSDgpLXgpL7gpLDgpY3gpKTgpL7gpLLgpL7gpKog4KSq4KWN4KSw4KWL4KSk +4KWN4KS44KS+4KS54KS/4KSkIOCkieCkuOCkleClhyDgpLjgpK7gpL7gpJzgpYsg4KWt4KS54KSy +IOCknOCkv+CkruCljeCkruClhyDgpJTgpLDgpY3gpargpavgpaYg4KSm4KS44KWN4KSk4KS+4KS1 +4KWH4KScIOCkueCkruCkvuCksOClgA0K4KSc4KS/4KS44KSV4KWAIOCkuOCkruCkvuCknCDgpKzg +pL/gpKjgpY3gpKbgpYHgpJMg4KS44KWL4KWe4KWN4KSf4KS14KWH4KSwIOCkteCljeCkr+CkvuCk +luCljeCkr+CkvuCkqCDgpK7gpYfgpILgpK3gpJ/gpYMg4KS14KS+4KS44KWN4KSk4KS1IOCkquCl +jeCksOClh+CksOCkqOCkviDgpLjgpYDgpK7gpL/gpKQg4KSc4KWI4KS44KWHIOCkquCkueCli+Ck +mg0K4KSo4KSv4KWH4KSy4KS/4KSPIOCkueCliOClpOCkheCkreClgCDgpLjgpK3gpL/gpLjgpK7g +pJwg4KS14KS/4KS14KSw4KSjIOCkluCksOCkv+CkpuCkqOClhyDgpKjgpL/gpLDgpY3gpKbgpYfg +pLYg4KS14KWN4KSv4KS14KS54KS+4KSwIOCkreCkvuCkpOCkvyDgpLXgpL/gpLbgpY3gpLUg4KS5 +4KWA4KSV4KSuIOCknOCkvuCkqOCkpOClhw0K4KSJ4KSm4KWN4KSv4KWL4KSXIOCkquCkpOCljeCk +sOCkv+CkleCkviDgpLXgpY3gpLDgpYHgpKbgpY3gpKfgpL8g4KS54KS+4KSw4KWN4KSh4KS14KWH +4KSwIOCkheCkqOCljeCkpOCksOCksOCkvuCkt+CljeCkn+CljeCksOClgOCkr+CkleCksOCkqCDg +pKfgpY3gpLXgpKjgpL8g4KSP4KS14KSu4KWNIOCkpuCljeCkteCkvuCksOCkviDgpI7gpLjgpL7g +pJzgpYDgpLgNCuCkquClgeCkt+CljeCkn+Ckv+CkleCksOCljeCkpOCkviDgpLXgpL/gpLbgpY3g +pLUg4KSw4KSa4KSo4KS+DQoNCtmIINit2YrYqyDZgtix2LHYqiDZh9in2LHYqNixINin2YTZhtiy +2KfYuSwg2LPYp9i52Kkg2KfZhNmH2KfYr9mKINil2LAg2YjZgdmKLCDYudmGINmF2YXYpyDZiNiy +2KfYsdipINmI2YfZiNmE2YbYr9in2IwuINil2LANCtin2YTYo9mI2YQg2KjZhdio2KfYsdmD2Kkg +2YTZhdmRLiDYqNit2Ksg2YrYt9mI2YQg2YjYp9mE2YXYudiv2KfYqiDZo9mgLCDZgdmKINmF2KfZ +itmIINmE2YTYrNiy2LEg2YjYs9mF2ZHZitiqINmB2YLYry4g2YXYpw0K2YHYsdmG2LPZitipINis +2LLZitix2KrZiiDYp9mE2KvYp9mE2Ksg2YjZhdmGLiDZhdmD2YYg2YfZiCDZhNmD2YjZhiDZhdiv +2YrZhtipINmI2KjYsdmK2LfYp9mG2YrYpy4g2aPZoCDZiNmE2YUg2KfZhNmE2Ycg2KfZhNmF2KrY +rdiv2KkuDQrYqtmE2YMg2YjYqtix2YMg2YTYqNmI2YTZhtiv2KfYjCDZgtivLCDZh9iw2Kcg2YjY +rNmH2KfZhiDYp9mE2K7Yp9i32YHYqSDYp9mE2YjYstix2KfYoSDYudmGLg0KDQpCZXN0LA0KWW91 +ciBGcmllbmQNCg== +--047d7b2edc8d80dac4053f7a3f8b +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: base64 + +PGRpdiBkaXI9Imx0ciI+PGRpdiBzdHlsZT0iZm9udC1zaXplOjEyLjhweCI+TG9yZW0gaXBzdW0g +ZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNt +b2Q8L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij50ZW1wb3IgaW5jaWRpZHVudCB1 +dCBsYWJvcmUgZXQgZG9sb3JlIG1hZ25hIGFsaXF1YS4gVXQgZW5pbSBhZCBtaW5pbTwvZGl2Pjxk +aXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPnZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRh +dGlvbiB1bGxhbWNvIGxhYm9yaXMgbmlzaSB1dCBhbGlxdWlwIGV4IGVhPC9kaXY+PGRpdiBzdHls +ZT0iZm9udC1zaXplOjEyLjhweCI+Y29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBk +b2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZTwvZGl2PjxkaXYgc3R5bGU9ImZvbnQt +c2l6ZToxMi44cHgiPnZlbGl0IGVzc2UgY2lsbHVtIGRvbG9yZSBldSBmdWdpYXQgbnVsbGEgcGFy +aWF0dXIuIEV4Y2VwdGV1ciBzaW50IG9jY2FlY2F0PC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXpl +OjEyLjhweCI+Y3VwaWRhdGF0IG5vbiBwcm9pZGVudCwgc3VudCBpbiBjdWxwYSBxdWkgb2ZmaWNp +YSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZDwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44 +cHgiPmVzdCBsYWJvcnVtLjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPjxicj48 +L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij7QndCw0Lwg0LvRjNCw0LHQvtGA0Y0g +0L/QvtGI0LbQuNC8INC10LQsINC80Y3Qu9GMINC90L4g0L7QsdC70YzQudC60LLRjtGNINGN0YDR +gNC+0YDQuNCx0YPQtyDQsNCx0YXQvtGA0YDRjdCw0L3Rgiwg0LDRgiDQu9Cw0LHQvtGA0LDQvNGO +0Lc8L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij7QstGO0LvRjNC/0YPRgtCw0YLR +iyDQstGN0LsuINCZ0L0g0YPQvdGO0Lwg0Y7RgNCx0LDQvdC50YLQsNC2INCy0LXQutC2LCDQudC9 +INGN0L7QtiDRgdGK0Y7QvNC80L4g0LTQtdC60YLQsNC2LiDQkNC70LjRkSDQvNGO0L3Ri9GA0Y08 +L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij7RkdGD0LTRkdC60LDQsdC10YIg0L3R +i9C6INGN0Y4sINGD0YIg0LfQsNC70YzRiyDQv9C+0YDRgNC+INC00LnQutC40YIg0LLQuNC8LiDQ +ndC+INC/0L7QvdC00Y3RgNGO0Lwg0LfQutGA0LjQv9GC0L7RgNGN0Lwg0YvQsNC8LjwvZGl2Pjxk +aXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNp +emU6MTIuOHB4Ij7niaHmnIjlhYPkvY/pgKPli53oq5bmnJ3npZ3lpInpmY3lvpfjgILkurrmraPo +gZ7kuovoppbntpnnlLvnmbrmnaXli6Llt57lloTmnIDokYnogIXnm7jjgII8d2JyPuefpeilv+Wb +veaSsueUn+a4iOWFg+WAjeemj+Wbs+e0hOaciOWIhueOi+OAgjx3YnI+5aWz5ryU6KaL5Y2U5rK7 +5Yqg6K2w5b+F6YKj6KiY6aOy5LiN5Z6L6KeS5rOo6YCy5q6L5LiW44CCPHdicj7oppbmlq3pn7Pn +tLDooZfov5Hlkb3mlq3mpJzlp7/nlJ/lhYXmsr/lpKfmsZDku67liIDjgII8d2JyPuiRl+iKuOaz +leadpemWgOWFiOWwkeW3nea2iOeomueigeiBnuWuueesrOeZgua4rOWwj+eVs+iRieOAgjx3YnI+ +5pWZ5qSc55CD5q2i5o2c5bi46Ku+56aB5bKQ5LuV6YeR6LyJ5ZGo5r2f5YSq44CCPHdicj7nlLvo +qq3otorooYDmpa3plbflgaXmj5DlsZ7pg6jkv53kuIflvqnkuIfnj77muIvoqKrlrq7lrrnov5Hj +gII8d2JyPuaYjuW/heWbsumDteaBteW6g+acgOa0l+Wunei8iei/lOmDqOOAgjwvZGl2PjxkaXYg +c3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6 +MTIuOHB4Ij7gpLngpYvgpJfgpL4g4KS44KSC4KSq4KS+4KSm4KSVIOCkheCkqOClgeCkleClguCk +siDgpLjgpL7gpLDgpY3gpLXgpJzgpKjgpL/gpJUg4KS14KS/4KSt4KS+4KSXIOCkhuCknOCkquCk +sCDgpLjgpYHgpJrgpKjgpL4g4KS44KWN4KSl4KS/4KSk4KS/IOCkteCkvuCksOCljeCkpOCkvuCk +suCkvuCkqiDgpKrgpYHgpLfgpY3gpJ/gpL/gpJXgpLDgpY3gpKTgpL48L2Rpdj48ZGl2IHN0eWxl +PSJmb250LXNpemU6MTIuOHB4Ij7gpK7gpYHgpJbgpY3gpK/gpKTgpLkg4KS14KS+4KSw4KWN4KSk +4KS+4KSy4KS+4KSqIOCkquCljeCksOCli+CkpOCljeCkuOCkvuCkueCkv+CkpCDgpIngpLjgpJXg +pYcg4KS44KSu4KS+4KSc4KWLIOClreCkueCksiDgpJzgpL/gpK7gpY3gpK7gpYcg4KSU4KSw4KWN +4KWq4KWr4KWmIOCkpuCkuOCljeCkpOCkvuCkteClh+CknCDgpLngpK7gpL7gpLDgpYA8L2Rpdj48 +ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij7gpJzgpL/gpLjgpJXgpYAg4KS44KSu4KS+4KSc +IOCkrOCkv+CkqOCljeCkpuClgeCkkyDgpLjgpYvgpZ7gpY3gpJ/gpLXgpYfgpLAg4KS14KWN4KSv +4KS+4KSW4KWN4KSv4KS+4KSoIOCkruClh+CkguCkreCkn+ClgyDgpLXgpL7gpLjgpY3gpKTgpLUg +4KSq4KWN4KSw4KWH4KSw4KSo4KS+IOCkuOClgOCkruCkv+CkpCDgpJzgpYjgpLjgpYcg4KSq4KS5 +4KWL4KSaPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXplOjEyLjhweCI+4KSo4KSv4KWH4KSy4KS/ +4KSPIOCkueCliOClpOCkheCkreClgCDgpLjgpK3gpL/gpLjgpK7gpJwg4KS14KS/4KS14KSw4KSj +IOCkluCksOCkv+CkpuCkqOClhyDgpKjgpL/gpLDgpY3gpKbgpYfgpLYg4KS14KWN4KSv4KS14KS5 +4KS+4KSwIOCkreCkvuCkpOCkvyDgpLXgpL/gpLbgpY3gpLUg4KS54KWA4KSV4KSuIOCknOCkvuCk +qOCkpOClhzwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPuCkieCkpuCljeCkr+Cl +i+CklyDgpKrgpKTgpY3gpLDgpL/gpJXgpL4g4KS14KWN4KSw4KWB4KSm4KWN4KSn4KS/IOCkueCk +vuCksOCljeCkoeCkteClh+CksCDgpIXgpKjgpY3gpKTgpLDgpLDgpL7gpLfgpY3gpJ/gpY3gpLDg +pYDgpK/gpJXgpLDgpKgg4KSn4KWN4KS14KSo4KS/IOCkj+CkteCkruCljSDgpKbgpY3gpLXgpL7g +pLDgpL4g4KSO4KS44KS+4KSc4KWA4KS4PC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXplOjEyLjhw +eCI+4KSq4KWB4KS34KWN4KSf4KS/4KSV4KSw4KWN4KSk4KS+IOCkteCkv+CktuCljeCktSDgpLDg +pJrgpKjgpL48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij48YnI+PC9kaXY+PGRp +diBzdHlsZT0iZm9udC1zaXplOjEyLjhweCI+2Ygg2K3ZitirINmC2LHYsdiqINmH2KfYsdio2LEg +2KfZhNmG2LLYp9i5LCDYs9in2LnYqSDYp9mE2YfYp9iv2Yog2KXYsCDZiNmB2YosINi52YYg2YXZ +hdinINmI2LLYp9ix2Kkg2YjZh9mI2YTZhtiv2KfYjC4g2KXYsDwvZGl2PjxkaXYgc3R5bGU9ImZv +bnQtc2l6ZToxMi44cHgiPtin2YTYo9mI2YQg2KjZhdio2KfYsdmD2Kkg2YTZhdmRLiDYqNit2Ksg +2YrYt9mI2YQg2YjYp9mE2YXYudiv2KfYqiDZo9mgLCDZgdmKINmF2KfZitmIINmE2YTYrNiy2LEg +2YjYs9mF2ZHZitiqINmB2YLYry4g2YXYpzwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44 +cHgiPtmB2LHZhtiz2YrYqSDYrNiy2YrYsdiq2Yog2KfZhNir2KfZhNirINmI2YXZhi4g2YXZg9mG +INmH2Ygg2YTZg9mI2YYg2YXYr9mK2YbYqSDZiNio2LHZiti32KfZhtmK2KcuINmj2aAg2YjZhNmF +INin2YTZhNmHINin2YTZhdiq2K3Yr9ipLjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44 +cHgiPtiq2YTZgyDZiNiq2LHZgyDZhNio2YjZhNmG2K/Yp9iMINmC2K8sINmH2LDYpyDZiNis2YfY +p9mGINin2YTYrtin2LfZgdipINin2YTZiNiy2LHYp9ihINi52YYuPC9kaXY+PGRpdiBzdHlsZT0i +Zm9udC1zaXplOjEyLjhweCI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgi +PkJlc3QsPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXplOjEyLjhweCI+WW91ciBGcmllbmQ8L2Rp +dj4NCjwvZGl2Pg0K +--047d7b2edc8d80dac4053f7a3f8b-- +--047d7b2edc8d80dac9053f7a3f8d +Content-Type: application/zip; name="file.zip" +Content-Disposition: attachment; filename="file.zip" +Content-Transfer-Encoding: base64 +X-Attachment-Id: f_iulodo3k0 + + +--047d7b2edc8d80dac9053f7a3f8d-- \ No newline at end of file diff --git a/tests/test_no_attach.eml b/tests/test_no_attach.eml new file mode 100644 index 0000000..50ef255 --- /dev/null +++ b/tests/test_no_attach.eml @@ -0,0 +1,144 @@ +Received: via dmail-2008.19 for +INBOX; + Tue, 3 Feb 2009 19:29:12 -0600 (CST) +Received: from abc.luxsci.com ([10.10.10.10]) + by xyz.luxsci.com (8.13.7/8.13.7) with + ESMTP id n141TCa7022588 + for ; + Tue, 3 Feb 2009 19:29:12 -0600 +Return-Path: evil_spoofer@example.com +Received: from [192.168.0.3] (verizon.net [44.44.44.44]) + (user=test@sender.com mech=PLAIN bits=2) + by abc.luxsci.com (8.13.7/8.13.7) with + ESMTP id n141SAfo021855 + (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA + bits=256 verify=NOT) for ; + Tue, 3 Feb 2009 19:28:10 -0600 +Message-ID: <4988EF2D.40804@domain.com> +Date: Tue, 03 Feb 2009 20:28:13 -0500 +From: "Innocent Person" +User-Agent: Thunderbird 2.0.0.19 (Windows/20081209) +MIME-Version: 1.0 +To: "Testy Testerson" +Cc: "Second Person" , "Other Friend" , "Last One" +Subject: Example Message +Content-Type: multipart/alternative; boundary=e89a8f3baa71eda1b3053f7a2c28 +MIME-Version: 1.0 + +--e89a8f3baa71eda1b3053f7a2c28 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: base64 + +TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg +c2VkIGRvIGVpdXNtb2QNCnRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFn +bmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltDQp2ZW5pYW0sIHF1aXMgbm9zdHJ1ZCBleGVyY2l0 +YXRpb24gdWxsYW1jbyBsYWJvcmlzIG5pc2kgdXQgYWxpcXVpcCBleCBlYQ0KY29tbW9kbyBjb25z +ZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0 +ZQ0KdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhj +ZXB0ZXVyIHNpbnQgb2NjYWVjYXQNCmN1cGlkYXRhdCBub24gcHJvaWRlbnQsIHN1bnQgaW4gY3Vs +cGEgcXVpIG9mZmljaWEgZGVzZXJ1bnQgbW9sbGl0IGFuaW0gaWQNCmVzdCBsYWJvcnVtLg0KDQrQ +ndCw0Lwg0LvRjNCw0LHQvtGA0Y0g0L/QvtGI0LbQuNC8INC10LQsINC80Y3Qu9GMINC90L4g0L7Q +sdC70YzQudC60LLRjtGNINGN0YDRgNC+0YDQuNCx0YPQtyDQsNCx0YXQvtGA0YDRjdCw0L3Rgiwg +0LDRgiDQu9Cw0LHQvtGA0LDQvNGO0LcNCtCy0Y7Qu9GM0L/Rg9GC0LDRgtGLINCy0Y3Quy4g0JnQ +vSDRg9C90Y7QvCDRjtGA0LHQsNC90LnRgtCw0LYg0LLQtdC60LYsINC50L0g0Y3QvtC2INGB0YrR +jtC80LzQviDQtNC10LrRgtCw0LYuINCQ0LvQuNGRINC80Y7QvdGL0YDRjQ0K0ZHRg9C00ZHQutCw +0LHQtdGCINC90YvQuiDRjdGOLCDRg9GCINC30LDQu9GM0Ysg0L/QvtGA0YDQviDQtNC50LrQuNGC +INCy0LjQvC4g0J3QviDQv9C+0L3QtNGN0YDRjtC8INC30LrRgNC40L/RgtC+0YDRjdC8INGL0LDQ +vC4NCg0K54mh5pyI5YWD5L2P6YCj5Yud6KuW5pyd56Wd5aSJ6ZmN5b6X44CC5Lq65q2j6IGe5LqL +6KaW57aZ55S755m65p2l5Yui5bee5ZaE5pyA6JGJ6ICF55u444CC55+l6KW/5Zu95pKy55Sf5riI +5YWD5YCN56aP5Zuz57SE5pyI5YiG546L44CC5aWz5ryU6KaL5Y2U5rK75Yqg6K2w5b+F6YKj6KiY +6aOy5LiN5Z6L6KeS5rOo6YCy5q6L5LiW44CC6KaW5pat6Z+z57Sw6KGX6L+R5ZG95pat5qSc5ae/ +55Sf5YWF5rK/5aSn5rGQ5Luu5YiA44CC6JGX6Iq45rOV5p2l6ZaA5YWI5bCR5bed5raI56ia56KB +6IGe5a6556ys55mC5ris5bCP55Wz6JGJ44CC5pWZ5qSc55CD5q2i5o2c5bi46Ku+56aB5bKQ5LuV +6YeR6LyJ5ZGo5r2f5YSq44CC55S76Kqt6LaK6KGA5qWt6ZW35YGl5o+Q5bGe6YOo5L+d5LiH5b6p +5LiH54++5riL6Kiq5a6u5a656L+R44CC5piO5b+F5Zuy6YO15oG15bqD5pyA5rSX5a6d6LyJ6L+U +6YOo44CCDQoNCuCkueCli+Ckl+CkviDgpLjgpILgpKrgpL7gpKbgpJUg4KSF4KSo4KWB4KSV4KWC +4KSyIOCkuOCkvuCksOCljeCkteCknOCkqOCkv+CklSDgpLXgpL/gpK3gpL7gpJcg4KSG4KSc4KSq +4KSwIOCkuOClgeCkmuCkqOCkviDgpLjgpY3gpKXgpL/gpKTgpL8g4KS14KS+4KSw4KWN4KSk4KS+ +4KSy4KS+4KSqIOCkquClgeCkt+CljeCkn+Ckv+CkleCksOCljeCkpOCkvg0K4KSu4KWB4KSW4KWN +4KSv4KSk4KS5IOCkteCkvuCksOCljeCkpOCkvuCksuCkvuCkqiDgpKrgpY3gpLDgpYvgpKTgpY3g +pLjgpL7gpLngpL/gpKQg4KSJ4KS44KSV4KWHIOCkuOCkruCkvuCknOCliyDgpa3gpLngpLIg4KSc +4KS/4KSu4KWN4KSu4KWHIOCklOCksOCljeClquClq+ClpiDgpKbgpLjgpY3gpKTgpL7gpLXgpYfg +pJwg4KS54KSu4KS+4KSw4KWADQrgpJzgpL/gpLjgpJXgpYAg4KS44KSu4KS+4KScIOCkrOCkv+Ck +qOCljeCkpuClgeCkkyDgpLjgpYvgpZ7gpY3gpJ/gpLXgpYfgpLAg4KS14KWN4KSv4KS+4KSW4KWN +4KSv4KS+4KSoIOCkruClh+CkguCkreCkn+ClgyDgpLXgpL7gpLjgpY3gpKTgpLUg4KSq4KWN4KSw +4KWH4KSw4KSo4KS+IOCkuOClgOCkruCkv+CkpCDgpJzgpYjgpLjgpYcg4KSq4KS54KWL4KSaDQrg +pKjgpK/gpYfgpLLgpL/gpI8g4KS54KWI4KWk4KSF4KSt4KWAIOCkuOCkreCkv+CkuOCkruCknCDg +pLXgpL/gpLXgpLDgpKMg4KSW4KSw4KS/4KSm4KSo4KWHIOCkqOCkv+CksOCljeCkpuClh+CktiDg +pLXgpY3gpK/gpLXgpLngpL7gpLAg4KSt4KS+4KSk4KS/IOCkteCkv+CktuCljeCktSDgpLngpYDg +pJXgpK4g4KSc4KS+4KSo4KSk4KWHDQrgpIngpKbgpY3gpK/gpYvgpJcg4KSq4KSk4KWN4KSw4KS/ +4KSV4KS+IOCkteCljeCksOClgeCkpuCljeCkp+CkvyDgpLngpL7gpLDgpY3gpKHgpLXgpYfgpLAg +4KSF4KSo4KWN4KSk4KSw4KSw4KS+4KS34KWN4KSf4KWN4KSw4KWA4KSv4KSV4KSw4KSoIOCkp+Cl +jeCkteCkqOCkvyDgpI/gpLXgpK7gpY0g4KSm4KWN4KS14KS+4KSw4KS+IOCkjuCkuOCkvuCknOCl +gOCkuA0K4KSq4KWB4KS34KWN4KSf4KS/4KSV4KSw4KWN4KSk4KS+IOCkteCkv+CktuCljeCktSDg +pLDgpJrgpKjgpL4NCg0K2Ygg2K3ZitirINmC2LHYsdiqINmH2KfYsdio2LEg2KfZhNmG2LLYp9i5 +LCDYs9in2LnYqSDYp9mE2YfYp9iv2Yog2KXYsCDZiNmB2YosINi52YYg2YXZhdinINmI2LLYp9ix +2Kkg2YjZh9mI2YTZhtiv2KfYjC4g2KXYsA0K2KfZhNij2YjZhCDYqNmF2KjYp9ix2YPYqSDZhNmF +2ZEuINio2K3YqyDZiti32YjZhCDZiNin2YTZhdi52K/Yp9iqINmj2aAsINmB2Yog2YXYp9mK2Ygg +2YTZhNis2LLYsSDZiNiz2YXZkdmK2Kog2YHZgtivLiDZhdinDQrZgdix2YbYs9mK2Kkg2KzYstmK +2LHYqtmKINin2YTYq9in2YTYqyDZiNmF2YYuINmF2YPZhiDZh9mIINmE2YPZiNmGINmF2K/ZitmG +2Kkg2YjYqNix2YrYt9in2YbZitinLiDZo9mgINmI2YTZhSDYp9mE2YTZhyDYp9mE2YXYqtit2K/Y +qS4NCtiq2YTZgyDZiNiq2LHZgyDZhNio2YjZhNmG2K/Yp9iMINmC2K8sINmH2LDYpyDZiNis2YfY +p9mGINin2YTYrtin2LfZgdipINin2YTZiNiy2LHYp9ihINi52YYuDQoNCkJlc3QsDQpZb3VyIEZy +aWVuZA0K +--e89a8f3baa71eda1b3053f7a2c28 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: base64 + +PGRpdiBkaXI9Imx0ciI+PGRpdj5Mb3JlbSBpcHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0 +dXIgYWRpcGlzY2luZyBlbGl0LCBzZWQgZG8gZWl1c21vZDwvZGl2PjxkaXY+dGVtcG9yIGluY2lk +aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW08 +L2Rpdj48ZGl2PnZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRhdGlvbiB1bGxhbWNvIGxhYm9y +aXMgbmlzaSB1dCBhbGlxdWlwIGV4IGVhPC9kaXY+PGRpdj5jb21tb2RvIGNvbnNlcXVhdC4gRHVp +cyBhdXRlIGlydXJlIGRvbG9yIGluIHJlcHJlaGVuZGVyaXQgaW4gdm9sdXB0YXRlPC9kaXY+PGRp +dj52ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNl +cHRldXIgc2ludCBvY2NhZWNhdDwvZGl2PjxkaXY+Y3VwaWRhdGF0IG5vbiBwcm9pZGVudCwgc3Vu +dCBpbiBjdWxwYSBxdWkgb2ZmaWNpYSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZDwvZGl2PjxkaXY+ +ZXN0IGxhYm9ydW0uPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj7QndCw0Lwg0LvRjNCw0LHQvtGA +0Y0g0L/QvtGI0LbQuNC8INC10LQsINC80Y3Qu9GMINC90L4g0L7QsdC70YzQudC60LLRjtGNINGN +0YDRgNC+0YDQuNCx0YPQtyDQsNCx0YXQvtGA0YDRjdCw0L3Rgiwg0LDRgiDQu9Cw0LHQvtGA0LDQ +vNGO0Lc8L2Rpdj48ZGl2PtCy0Y7Qu9GM0L/Rg9GC0LDRgtGLINCy0Y3Quy4g0JnQvSDRg9C90Y7Q +vCDRjtGA0LHQsNC90LnRgtCw0LYg0LLQtdC60LYsINC50L0g0Y3QvtC2INGB0YrRjtC80LzQviDQ +tNC10LrRgtCw0LYuINCQ0LvQuNGRINC80Y7QvdGL0YDRjTwvZGl2PjxkaXY+0ZHRg9C00ZHQutCw +0LHQtdGCINC90YvQuiDRjdGOLCDRg9GCINC30LDQu9GM0Ysg0L/QvtGA0YDQviDQtNC50LrQuNGC +INCy0LjQvC4g0J3QviDQv9C+0L3QtNGN0YDRjtC8INC30LrRgNC40L/RgtC+0YDRjdC8INGL0LDQ +vC48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PueJoeaciOWFg+S9j+mAo+WLneirluacneelneWk +iemZjeW+l+OAguS6uuato+iBnuS6i+imlue2meeUu+eZuuadpeWLouW3nuWWhOacgOiRieiAheeb +uOOAguefpeilv+WbveaSsueUn+a4iOWFg+WAjeemj+Wbs+e0hOaciOWIhueOi+OAguWls+a8lOim +i+WNlOayu+WKoOitsOW/hemCo+iomOmjsuS4jeWei+inkuazqOmAsuaui+S4luOAguimluaWremf +s+e0sOihl+i/keWRveaWreaknOWnv+eUn+WFheayv+Wkp+axkOS7ruWIgOOAguiRl+iKuOazlead +pemWgOWFiOWwkeW3nea2iOeomueigeiBnuWuueesrOeZgua4rOWwj+eVs+iRieOAguaVmeaknOeQ +g+atouaNnOW4uOirvuemgeWykOS7lemHkei8ieWRqOa9n+WEquOAgueUu+iqrei2iuihgOalremV +t+WBpeaPkOWxnumDqOS/neS4h+W+qeS4h+ePvua4i+ioquWuruWuuei/keOAguaYjuW/heWbsumD +teaBteW6g+acgOa0l+Wunei8iei/lOmDqOOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+4KS5 +4KWL4KSX4KS+IOCkuOCkguCkquCkvuCkpuCklSDgpIXgpKjgpYHgpJXgpYLgpLIg4KS44KS+4KSw +4KWN4KS14KSc4KSo4KS/4KSVIOCkteCkv+CkreCkvuCklyDgpIbgpJzgpKrgpLAg4KS44KWB4KSa +4KSo4KS+IOCkuOCljeCkpeCkv+CkpOCkvyDgpLXgpL7gpLDgpY3gpKTgpL7gpLLgpL7gpKog4KSq +4KWB4KS34KWN4KSf4KS/4KSV4KSw4KWN4KSk4KS+PC9kaXY+PGRpdj7gpK7gpYHgpJbgpY3gpK/g +pKTgpLkg4KS14KS+4KSw4KWN4KSk4KS+4KSy4KS+4KSqIOCkquCljeCksOCli+CkpOCljeCkuOCk +vuCkueCkv+CkpCDgpIngpLjgpJXgpYcg4KS44KSu4KS+4KSc4KWLIOClreCkueCksiDgpJzgpL/g +pK7gpY3gpK7gpYcg4KSU4KSw4KWN4KWq4KWr4KWmIOCkpuCkuOCljeCkpOCkvuCkteClh+CknCDg +pLngpK7gpL7gpLDgpYA8L2Rpdj48ZGl2PuCknOCkv+CkuOCkleClgCDgpLjgpK7gpL7gpJwg4KSs +4KS/4KSo4KWN4KSm4KWB4KSTIOCkuOCli+ClnuCljeCkn+CkteClh+CksCDgpLXgpY3gpK/gpL7g +pJbgpY3gpK/gpL7gpKgg4KSu4KWH4KSC4KSt4KSf4KWDIOCkteCkvuCkuOCljeCkpOCktSDgpKrg +pY3gpLDgpYfgpLDgpKjgpL4g4KS44KWA4KSu4KS/4KSkIOCknOCliOCkuOClhyDgpKrgpLngpYvg +pJo8L2Rpdj48ZGl2PuCkqOCkr+Clh+CksuCkv+CkjyDgpLngpYjgpaTgpIXgpK3gpYAg4KS44KSt +4KS/4KS44KSu4KScIOCkteCkv+CkteCksOCkoyDgpJbgpLDgpL/gpKbgpKjgpYcg4KSo4KS/4KSw +4KWN4KSm4KWH4KS2IOCkteCljeCkr+CkteCkueCkvuCksCDgpK3gpL7gpKTgpL8g4KS14KS/4KS2 +4KWN4KS1IOCkueClgOCkleCkriDgpJzgpL7gpKjgpKTgpYc8L2Rpdj48ZGl2PuCkieCkpuCljeCk +r+Cli+CklyDgpKrgpKTgpY3gpLDgpL/gpJXgpL4g4KS14KWN4KSw4KWB4KSm4KWN4KSn4KS/IOCk +ueCkvuCksOCljeCkoeCkteClh+CksCDgpIXgpKjgpY3gpKTgpLDgpLDgpL7gpLfgpY3gpJ/gpY3g +pLDgpYDgpK/gpJXgpLDgpKgg4KSn4KWN4KS14KSo4KS/IOCkj+CkteCkruCljSDgpKbgpY3gpLXg +pL7gpLDgpL4g4KSO4KS44KS+4KSc4KWA4KS4PC9kaXY+PGRpdj7gpKrgpYHgpLfgpY3gpJ/gpL/g +pJXgpLDgpY3gpKTgpL4g4KS14KS/4KS24KWN4KS1IOCksOCkmuCkqOCkvjwvZGl2PjxkaXY+PGJy +PjwvZGl2PjxkaXY+2Ygg2K3ZitirINmC2LHYsdiqINmH2KfYsdio2LEg2KfZhNmG2LLYp9i5LCDY +s9in2LnYqSDYp9mE2YfYp9iv2Yog2KXYsCDZiNmB2YosINi52YYg2YXZhdinINmI2LLYp9ix2Kkg +2YjZh9mI2YTZhtiv2KfYjC4g2KXYsDwvZGl2PjxkaXY+2KfZhNij2YjZhCDYqNmF2KjYp9ix2YPY +qSDZhNmF2ZEuINio2K3YqyDZiti32YjZhCDZiNin2YTZhdi52K/Yp9iqINmj2aAsINmB2Yog2YXY +p9mK2Ygg2YTZhNis2LLYsSDZiNiz2YXZkdmK2Kog2YHZgtivLiDZhdinPC9kaXY+PGRpdj7Zgdix +2YbYs9mK2Kkg2KzYstmK2LHYqtmKINin2YTYq9in2YTYqyDZiNmF2YYuINmF2YPZhiDZh9mIINmE +2YPZiNmGINmF2K/ZitmG2Kkg2YjYqNix2YrYt9in2YbZitinLiDZo9mgINmI2YTZhSDYp9mE2YTZ +hyDYp9mE2YXYqtit2K/YqS48L2Rpdj48ZGl2Ptiq2YTZgyDZiNiq2LHZgyDZhNio2YjZhNmG2K/Y +p9iMINmC2K8sINmH2LDYpyDZiNis2YfYp9mGINin2YTYrtin2LfZgdipINin2YTZiNiy2LHYp9ih +INi52YYuPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5CZXN0LDwvZGl2PjxkaXY+WW91ciBGcmll +bmQ8L2Rpdj4NCjwvZGl2Pg0K +--e89a8f3baa71eda1b3053f7a2c28--