-
Notifications
You must be signed in to change notification settings - Fork 14
/
incoming.py
283 lines (259 loc) · 8.9 KB
/
incoming.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
import socket
import threading
import email.mime.text
import email.mime.image
import email.mime.multipart
import email.header
import bminterface
import re
import select
import logging
class ChatterboxConnection(object):
END = "\r\n"
def __init__(self, conn):
self.conn = conn
def __getattr__(self, name):
return getattr(self.conn, name)
def sendall(self, data, END=END):
data += END
self.conn.sendall(data)
def recvall(self, END=END):
data = []
while True:
chunk = self.conn.recv(4096)
if END in chunk:
data.append(chunk[:chunk.index(END)])
break
data.append(chunk)
if len(data) > 1:
pair = data[-2] + data[-1]
if END in pair:
data[-2] = pair[:pair.index(END)]
data.pop()
break
return "".join(data)
def handleUser(data):
d = data.split()
logging.debug("data:%s" % d)
username = d[-1]
if username[:3] == 'BM-':
logging.debug("Only showing messages for %s" % username)
bminterface.registerAddress(username)
else:
logging.debug("Showing all messages in the inbox")
bminterface.registerAddress(None)
return "+OK user accepted"
def handlePass(data):
return "+OK pass accepted"
def _getMsgSizes():
msgCount = bminterface.listMsgs()
msgSizes = []
for msgID in range(msgCount):
logging.debug("Parsing msg %i of %i" % (msgID+1, msgCount))
dateTime, toAddress, fromAddress, subject, body = bminterface.get(msgID)
msgSizes.append(len(makeEmail(dateTime, toAddress, fromAddress, subject, body)))
return msgSizes
def handleStat(data):
msgSizes = _getMsgSizes()
msgCount = len(msgSizes)
msgSizeTotal = 0
for msgSize in msgSizes:
msgSizeTotal += msgSize
returnData = '+OK %i %i' % (msgCount, msgSizeTotal)
logging.debug("Answering STAT: %i %i" % (msgCount, msgSizeTotal))
return returnData
def handleList(data):
dataList = data.split()
cmd = dataList[0]
msgSizes = _getMsgSizes()
if len(dataList) > 1:
msgId = dataList[1]
# means the server wants a single message response
i = int(msgId) - 1
if i >= len(msgSizes):
return "-ERR no such message"
else:
msgSize = msgSizes[i]
return "+OK %s %s" % (msgId, msgSize)
msgCount = 0
returnDataPart2 = ''
msgSizeTotal = 0
for msgSize in msgSizes:
msgSizeTotal += msgSize
msgCount += 1
returnDataPart2 += '%i %i\r\n' % (msgCount, msgSize)
returnDataPart2 += '.'
returnDataPart1 = '+OK %i messages (%i octets)\r\n' % (msgCount, msgSizeTotal)
returnData = returnDataPart1 + returnDataPart2
logging.debug("Answering LIST: %i %i" % (msgCount, msgSizeTotal))
logging.debug(returnData)
return returnData
def handleTop(data):
msg = 'test'
logging.debug(data.split())
cmd, msgID, lines = data.split()
msgID = int(msgID)-1
lines = int(lines)
logging.debug(lines)
dateTime, toAddress, fromAddress, subject, body = bminterface.get(msgID)
logging.debug(subject)
msg = makeEmail(dateTime, toAddress, fromAddress, subject, body)
top, bot = msg.split("\n\n", 1)
#text = top + "\r\n\r\n" + "\r\n".join(bot[:lines])
return "+OK top of message follows\r\n%s\r\n." % top
def handleRetr(data):
logging.debug(data.split())
msgID = int(data.split()[1])-1
dateTime, toAddress, fromAddress, subject, body = bminterface.get(msgID)
msg = makeEmail(dateTime, toAddress, fromAddress, subject, body)
return "+OK %i octets\r\n%s\r\n." % (len(msg), msg)
def handleDele(data):
msgID = int(data.split()[1])-1
bminterface.markForDelete(msgID)
return "+OK I'll try..."
def handleNoop(data):
return "+OK"
def handleQuit(data):
bminterface.cleanup()
return "+OK just pretend I'm gone"
def handleCapa(data):
returnData = "+OK List of capabilities follows\r\n"
for k in dispatch:
returnData += "%s\r\n" % k
returnData += "."
return returnData
def handleUIDL(data):
data = data.split()
logging.debug(data)
if len(data) == 1:
refdata = bminterface.getUIDLforAll()
logging.debug(refdata)
returnData = '+OK\r\n'
for msgID, d in enumerate(refdata):
returnData += "%s %s\r\n" % (msgID+1, d)
returnData += '.'
else:
refdata = bminterface.getUIDLforSingle(int(data[1])-1)
logging.debug(refdata)
returnData = '+OK ' + data[0] + str(refdata[0])
return returnData
def makeEmail(dateTime, toAddress, fromAddress, subject, body):
body = parseBody(body)
msgType = len(body)
if msgType == 1:
msg = email.mime.text.MIMEText(body[0], 'plain', 'UTF-8')
else:
msg = email.mime.multipart.MIMEMultipart('mixed')
bodyText = email.mime.text.MIMEText(body[0], 'plain', 'UTF-8')
body = body[1:]
msg.attach(bodyText)
for item in body:
img = 0
itemType, itemData = [0], [0]
try:
itemType, itemData = item.split(';', 1)
itemType = itemType.split('/', 1)
except:
logging.warning("Could not parse message type")
pass
if itemType[0] == 'image':
try:
itemDataFinal = itemData.lstrip('base64,').strip(' ').strip('\n').decode('base64')
img = email.mime.image.MIMEImage(itemDataFinal)
except:
#Some images don't auto-detect type correctly with email.mime.image
#Namely, jpegs with embeded color profiles look problematic
#Trying to set it manually...
try:
itemDataFinal = itemData.lstrip('base64,').strip(' ').strip('\n').decode('base64')
img = email.mime.image.MIMEImage(itemDataFinal, _subtype=itemType[1])
except:
logging.warning("Failed to parse image data. This could be an image.")
logging.warning("This could be from an image tag filled with junk data.")
logging.warning("It could also be a python email.mime.image problem.")
if img:
img.add_header('Content-Disposition', 'attachment')
msg.attach(img)
msg['To'] = toAddress
msg['From'] = fromAddress
msg['Subject'] = email.header.Header(subject, 'UTF-8')
msg['Date'] = dateTime
return msg.as_string()
def parseBody(body):
returnData = []
text = ''
searchString = '<img[^>]*'
attachment = re.search(searchString, body)
while attachment:
imageCode = body[attachment.start():attachment.end()]
imageDataRange = re.search('src=[\"\'][^\"\']*[\"\']', imageCode)
imageData=''
if imageDataRange:
try:
imageData = imageCode[imageDataRange.start()+5:imageDataRange.end()-1].lstrip('data:')
except:
pass
if imageData:
returnData.append(imageData)
body = body[:attachment.start()] + body[attachment.end()+1:]
attachment = re.search(searchString, body)
text = body
returnData = [text] + returnData
return returnData
dispatch = dict(
USER=handleUser,
PASS=handlePass,
STAT=handleStat,
LIST=handleList,
TOP=handleTop,
RETR=handleRetr,
DELE=handleDele,
NOOP=handleNoop,
QUIT=handleQuit,
CAPA=handleCapa,
UIDL=handleUIDL,
)
def incomingServer(host, port, run_event):
popthread = threading.Thread(target=incomingServer_main, args=(host, port, run_event))
popthread.daemon = True
popthread.start()
return popthread
def incomingServer_main(host, port, run_event):
sock = None
try:
while run_event.is_set():
if sock is None:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen(1)
ready = select.select([sock], [], [], .2)
if ready[0]:
conn, addr = sock.accept()
# stop listening, one client only
sock.close()
sock = None
try:
conn = ChatterboxConnection(conn)
conn.sendall("+OK server ready")
while run_event.is_set():
data = conn.recvall()
logging.debug("Answering %s" % data)
command = data.split(None, 1)[0]
try:
cmd = dispatch[command]
except KeyError:
conn.sendall("-ERR unknown command")
else:
conn.sendall(cmd(data))
if cmd is handleQuit:
break
finally:
conn.close()
except (SystemExit, KeyboardInterrupt):
pass
except Exception, ex:
raise
finally:
if sock is not None:
sock.close()