/
msh.py
328 lines (284 loc) · 11.8 KB
/
msh.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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#!/usr/bin/env python
from multiprocessing import Process, Lock
import smtplib
import poplib
import imaplib
import os
import sys
import types
import threading
import subprocess
from ConfigParser import ConfigParser, NoOptionError
from email.parser import Parser
from datetime import datetime
from Users import Users
from FilterList import FilterList
from unbuffered import Unbuffered
def is_enabled(switch):
return "no" != switch.lower()
#def
class msh:
def __init__(self, config_path):
if not os.path.exists(config_path):
raise ValueError("Can't find config file \"" + config_path + "\"")
#if
sys.stdout = Unbuffered(sys.stdout)
sys.stderr = Unbuffered(sys.stderr)
self.__config = ConfigParser()
self.__config.read(config_path)
self.__active_mails_count = 0
self.__send_lock = Lock()
self.__users_db = Users(self.get_param_str('Main', 'ACCESS_FILE_PATH'))
self.__whitelist = FilterList(self.get_param_str('Main', 'WHITELIST_SENDERS'))
self.__blacklist = FilterList(self.get_param_str('Main', 'BLACKLIST_SENDERS'))
#def
def _send_response(self, email_from, msg):
self.__send_lock.acquire()
if not msg is None:
print "[%s] Sending response to '%s'" % (datetime.today().strftime('%d/%m/%y %H:%M'), email_from)
recipients = [email_from, self.get_param_str('Mail', 'SEND_COPY_TO')]
message = "%s%s%s\n%s" % ('From: %s \n' % (self.get_param_str('Main', 'BOT_NAME')),
'To: %s \n' % (email_from),
'Subject: Report %s \n' % (datetime.today().strftime('%d/%m/%y %H:%M')),
msg)
if is_enabled(self.get_param_str("Mail", "USE_SSL")):
session = smtplib.SMTP_SSL(self.get_param_str("Mail", "SMTP_SERVER"),
self.get_param_int("Mail", "SMTP_SSL_PORT"))
else:
session = smtplib.SMTP(self.get_param_str("Mail", "SMTP_SERVER"),
self.get_param_int("Mail", "SMTP_PORT"))
#if
if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")):
session.set_debuglevel(100)
#if
session.login(self.get_param_str("Mail", "EMAIL_USER"),
self.get_param_str("Mail", "EMAIL_PASS"))
session.sendmail(self.get_param_str("Mail", "EMAIL_USER"),
recipients,
message)
session.quit()
#if
self.__send_lock.release()
#def
def _check_pop(self):
print "[%s] Going to get messages by POP" % (datetime.today().strftime('%d/%m/%y %H:%M'))
if is_enabled(self.get_param_str("Mail", "USE_SSL")):
session = poplib.POP3_SSL(self.get_param_str("Mail", "POP_SERVER"),
self.get_param_int("Mail", "POP_SSL_PORT"))
else:
session = poplib.POP3(self.get_param_str("Mail", "POP_SERVER"),
self.get_param_int("Mail", "POP_PORT"))
#if
if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")):
session.set_debuglevel(100)
#if
try:
session.user(self.get_param_str("Mail", "EMAIL_USER"))
session.pass_(self.get_param_str("Mail", "EMAIL_PASS"))
except poplib.error_proto as e:
sys.stderr.write("Got an error while conencting to POP server: '%s'\n" % (e))
return False
#try
numMessages = len(session.list()[1])
for i in range(numMessages):
m_parsed = Parser().parsestr("\n".join(session.top(i+1, 0)[1]))
if self.get_param_str('Main', 'SUBJECT_CODE_PHRASE') == m_parsed['subject']:
#Looks like valid cmd for bot, continue
if self._process_msg("\n".join(session.retr(i+1)[1])):
session.dele(i+1)
#if
#if
#for
session.quit()
#def
def _check_imap(self):
print "[%s] Going to get messages by IMAP" % (datetime.today().strftime('%d/%m/%y %H:%M'))
if is_enabled(self.get_param_str("Mail", "USE_SSL")):
session = imaplib.IMAP4_SSL(self.get_param_str("Mail", "IMAP_SERVER"),
self.get_param_int("Mail", "IMAP_SSL_PORT"))
else:
session = imaplib.IMAP4(self.get_param_str("Mail", "IMAP_SERVER"),
self.get_param_int("Mail", "IMAP_PORT"))
#if
if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")):
session.debug = 100
#if
try:
session.login(self.get_param_str("Mail", "EMAIL_USER"),
self.get_param_str("Mail", "EMAIL_PASS"))
except imaplib.IMAP4.error as e:
sys.stderr.write("Got an error while connecting to IMAP server: '%s'\n" % (e))
return False
#try
session.select(self.get_param_str('Mail', 'IMAP_MAILBOX_NAME'))
typ, data = session.search(None,
'SUBJECT', self.get_param_str("Main", "SUBJECT_CODE_PHRASE"))
# 'UNSEEN')
if not data[0] is None:
for num in data[0].split():
typ, data = session.fetch(num, '(RFC822)')
if self._process_msg(data[0][1]):
session.store(num, '+FLAGS', '\\Deleted')
# session.store(num, '+FLAGS', '\\Seen')
#if
#for
#if
session.expunge()
session.close()
session.logout()
#def
def _process_msg(self, raw_email):
if not type(raw_email) is types.StringType:
return True
#if
m_parsed = Parser().parsestr(raw_email)
m_from = m_parsed['from']
if self.__active_mails_count > self.get_param_int('Main', 'NUMBER_OF_PROCESSING_THREADS'):
print "[%s] All processing threads are busy. Will try in next iteratio" % (datetime.today().strftime('%d/%m/%y %H:%M'))
return False
#if
print "[%s] Processing message from user '%s'" % (datetime.today().strftime('%d/%m/%y %H:%M'), m_from)
#check it in white/black lists
if not self.__whitelist.has_elem(m_from, is_enabled(self.get_param_str('Main', 'USE_REGEXP_IN_FILTERING'))):
print "Got command from unknown email (%s)" % (m_from)
return True
#if
if self.__blacklist.has_elem(m_from, is_enabled(self.get_param_str('Main', 'USE_REGEXP_IN_FILTERING'))):
print "Got command from blacklisted email (%s)" % (m_from)
return True
#if
self.__active_mails_count += 1
for m_body in self._get_payload_from_msg(m_parsed):
if self._check_user(m_body[0]):
for i in xrange(1, len(m_body)):
#self._thread_routine(ddm_from, m_body[i].strip())
Process(target=self._thread_routine, args=(m_from, m_body[i].strip())).start()
#for
else:
print "Received an incorrect pair of user+password!"
#if
#for
return True
#def
def _get_payload_from_msg(self, parsed_email):
"""
Generator for extracting all message boundles from email
"""
if parsed_email.is_multipart():
for msg_from_multipart in parsed_email.get_payload():
# proccesing recursively all multipart messages
for sub_msg in self._get_payload_from_msg(msg_from_multipart):
yield sub_msg
#for
#for
else:
yield parsed_email.get_payload().split("\n")
#if
#def
def _thread_routine(self, m_from, command):
self._send_response(m_from, self._perform_user_cmd(command))
self.__active_mails_count -= 1
#def
def _check_user(self, credentials_str):
try:
user, passwd = credentials_str.strip().split(":")
return self.__users_db.check(user, passwd)
except ValueError:
return False;
#try
#def
def _perform_user_cmd(self, command):
if 0 >= len(command):
return None
#if
print "[%s] Going to process command '%s'" % (datetime.today().strftime('%d/%m/%y %H:%M'), command)
buff = "[%s] %s \r\n" % (datetime.today().strftime('%d/%m/%y %H:%M'), command)
if ":list" == command:
pass
elif command.startswith(":stop"):
id = 0
try:
id = int(command[5:])
pass
except ValueError as e:
buf += "Error! Got incorrect parameter for 'stop' command '%s'\n" % (command[5:])
sys.stderr.write("Error! Got incorrect parameter for 'stop' command '%s'\n" % (command[5:]))
#try
else:
time = None
if command.startswith(":time="):
try:
ind = command.index(" ")
try:
time = int(command[6:ind])
command = command[ind+1:]
except ValueError as e:
buff += "Error! Got incorrect parameter for 'time' command '%s'\n" % (command[6:ind])
sys.stderr.write("Error! Got incorrect parameter for 'time' command '%s'\n" % (command[6:ind]))
return buff
#try
except ValueError as e:
buff += "Error! Wrong format of 'time' command '%s'\n" % (command[6:])
sys.stderr.write("Error! Wrong format of 'time' command '%s'\n" % (command[6:]))
return buff
#try
#if
out_buf = []
t = threading.Thread(target=self._run_popen, args=(command, out_buf))
t.start()
t.join(time)
buff += "\n".join(out_buf)
if t.isAlive():
try:
t._Thread__stop()
buff += "Thread has timeouted after timeout = %i!" % (time)
except:
sys.stream.write("Thread with command '%s' could not be terminated" % (command))
#try
#if
#if
return buff
#def
def _run_popen(self, cmd, out_buf_list):
get_rev = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
get_rev.wait()
for str in get_rev.stdout.readlines():
out_buf_list.append(str)
#for
out_buf_list.append("retcode = %d\r\n" % get_rev.returncode)
#def
def check_for_new_commands(self):
print "[%s] Checking for new commands\n" % (datetime.today().strftime('%d/%m/%y %H:%M'))
if is_enabled(self.get_param_str('Mail', 'USE_IMAP')):
return self._check_imap()
else:
return self._check_pop()
#if
#def
def add_new_user(self, user, passwd):
return self.__users_db.add(user, passwd)
#def
def _get_param(self, sect, param):
res = None
try:
res = self.__config.get(sect, param)
except NoOptionError as e:
sys.stderr.write("%s\n" % (str(e)))
#try
return res
#def
def get_param_int(self, sect, param):
res = 0
try:
res = int(self._get_param(sect, param))
except ValueError as e:
sys.stderr.write("%s\n" % (str(e)))
except TypeError as e:
sys.stderr.write("%s\n" % (str(e)))
#try
return res
#def
def get_param_str(self, sect, param):
return self._get_param(sect, param)
#def
#class