-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsnakeoil.py
201 lines (166 loc) · 6.41 KB
/
snakeoil.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
from smtpd import *
import asyncore
import email
import hashlib
import slack
import os
import sys
import re
from bs4 import BeautifulSoup
class SnakeOil(SMTPServer):
def __init__(self, localaddr, remoteaddr, token):
"""Initializes the smtpd server
Args:
localaddr: address to host the server on
remoteaddr: remote address to forward email to
token: slack token used to connect to slack channel
"""
super().__init__(localaddr, remoteaddr)
self.client = slack.WebClient(token=token)
def get_parts(self, e):
"""Gets different parts of the email we want to parse out.
Args:
e: Email object created by smtpd library
Returns:
dict: object containing different parts of the email including
body_text, body_html, and attachments
"""
parts = {'body_html':None,
'body_text':None}
attachments = {}
parts['Reply-To'] = e.get('Reply-To', '')
for p in e.walk():
if parts['Reply-To'] == '':
parts['Reply-To'] = p.get('Reply-To', '')
f = p.get_filename()
content_type = p.get_content_type()
if f:
attachments[f] = p.get_payload(decode=True)
continue
if content_type == 'text/html':
parts['body_html'] = p.get_payload(decode=True)
if content_type == 'text/text':
parts['body_text'] = p.get_payload(decode=True)
parts['attachments'] = attachments
return parts
def write_attachments(self, attachments):
"""Writes attachments to disk.
Args:
attachments: dict where key is the attachment name and value is the
data in the file attachment
"""
for a in attachments.keys():
dat = attachments[a]
with open(a, 'wb') as f:
f.write(dat)
f.close()
def upload_attachments(self, attachments):
"""Uploads attachments to Slack.
Args:
attachments: dict where key is the attachment name and value is the
data in the file attachment
"""
for a in attachments.keys():
dat = attachments[a]
md5 = hashlib.md5(dat).hexdigest()
self.client.files_upload(channels='#general',
initial_comment=md5,
filename=a,
file=a)
os.remove(a)
def get_links_html(self, body):
"""Gets links within html content.
Args:
body: html body of an email
Returns:
list: List of links in the html blob
"""
links = []
soup = BeautifulSoup(body)
a_tag = soup.findAll('a', attrs={'href': re.compile('^http.*://')})
for tag in a_tag:
links.append(tag.get('href'))
return links
def get_links_text(self, body):
"""Gets links within a blob of plaintext.
Args:
body: text blob of the body of an email
Returns:
list: List of links in the text blob
"""
links = re.findall(r'https?://[A-Za-z0-9\.\/]+',
body)
return links
def upload_links(self, links):
"""Uploads links to Slack.
Args:
links: List of links that were parsed out
"""
for link in links:
self.client.chat_postMessage(channel='#general',
text=link)
def upload_email(self, data, subject):
"""Uploads email object to Slack.
Args:
data: email data from smtpd library
subject: email subject, also used as file name
"""
with open('{}.msg'.format(subject), 'wb') as f:
f.write(data)
f.close()
self.client.files_upload(channels='#general',
filename='{}.msg'.format(subject),
file='{}.msg'.format(subject))
os.remove('{}.msg'.format(subject))
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
"""Inherited class from smtpd library that processes incoming email.
Args:
peer: tuple containing the incoming IP and the port it is coming
from
mailfrom: email address that the email is coming from
rcpttos: recipients to receive the email
data: overall email data
Returns:
None: Must return something to send an OK to the original client
"""
try:
e = email.message_from_bytes(data)
headers = {'IP':peer,
'From':mailfrom,
'Recipients':','.join(rcpttos),
'Subject':e['Subject']
}
parts = self.get_parts(e)
links = []
if parts['body_html']:
links = self.get_links_html(parts['body_html'])
elif parts['body_text']:
links = self.get_links_text(parts['body_text'])
msg_text = ('IP : {}\nFrom : {}\n'.format(headers['IP'],
headers['From']) +
'Rcpts : {}\nSubject : {}\n'.format(headers['Recipients'],
headers['Subject']) +
'Reply-To: {}\n'.format(parts['Reply-To']) +
'Num Links : {}\nNum Att : {}'.format(len(links),
len(parts['attachments'])))
self.client.chat_postMessage(channel='#general',
text=msg_text)
if len(links) > 0:
self.upload_links(links)
if len(parts['attachments']) > 0:
self.write_attachments(parts['attachments'])
self.upload_attachments(parts['attachments'])
self.upload_email(data, e['Subject'])
except Exception as e:
print(e)
return None
if __name__ == '__main__':
args = sys.argv
if len(args) < 2:
print('Usage: python snakeoil.py <slack_token>')
sys.exit()
h = SnakeOil(('0.0.0.0', 2525), None, args[1])
try:
asyncore.loop()
except Exception as e:
print(e)