2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
39 import cmk
.utils
.password_store
41 cmk
.utils
.password_store
.replace_passwords()
44 def parse_exception(exc
):
47 exc
= "%d - %s" % ast
.literal_eval(exc
).values()[0]
51 def bail_out(rc
, s
, perfdata
=None):
55 stxt
= ['OK', 'WARN', 'CRIT', 'UNKNOWN'][rc
]
56 sys
.stdout
.write('%s - %s' % (stxt
, s
))
59 ' | %s' % (' '.join(['%s=%s' % (p
[0], ';'.join(map(str, p
[1:]))) for p
in perfdata
])))
60 sys
.stdout
.write('\n')
66 sys
.stderr
.write('ERROR: %s\n' % msg
)
68 USAGE: check_mail [OPTIONS]
71 --protocol PROTO Set to "IMAP" or "POP3", depending on your mailserver
73 --server ADDRESS Host address of the IMAP/POP3 server hosting your mailbox
74 --port PORT IMAP or POP3 port
75 (defaults to 110 for POP3 and 995 for POP3 with SSL and
76 143 for IMAP and 993 for IMAP with SSL)
77 --username USER Username to use for IMAP/POP3
78 --password PW Password to use for IMAP/POP3
79 --ssl Use SSL for feching the mailbox (disabled by default)
80 --connect-timeout Timeout in seconds for network connects (defaults to 10)
82 --forward-ec Forward matched mails to the event console (EC)
83 --forward-method M Configure how to connect to the event console to forward
84 the messages to. Can be configured to:
85 udp,<ADDR>,<PORT> - Connect to remove EC via UDP
86 tcp,<ADDR>,<PORT> - Connect to remove EC via TCP
87 spool: - Write to site local spool directory
88 spool:/path/to/spooldir - Spool to given directory
89 /path/to/pipe - Write to given EC event pipe
90 Defaults to use the event console of the local OMD sites.
91 --forward-facility F Syslog facility to use for forwarding (Defaults to "2" -> mail)
92 --forward-app APP Specify which string to use for the syslog application field
93 when forwarding to the event console. You can specify macros like
94 \1 or \2 when you specified "--match-subject" with regex groups.
95 (Defaults to use the whole subject of the e mail)
96 --forward-host HOST Hostname to use for the generated events
97 --body-limit NUM Limit the number of characters of the body to forward
99 --match-subject REGEX Use this option to not process all messages found in the inbox,
100 but only the whones whose subject matches the given regular expression.
101 --cleanup METHOD Delete processed messages (see --match-subject) or move to subfolder a
102 matching the given path. This is configured with the following METHOD:
103 delete - Simply delete mails
104 path/to/subfolder - Move to this folder (Only supported with IMAP)
105 By default the mails are not cleaned up, which might make your mailbox
106 grow when you not clean it up manually.
108 -d, --debug Enable debug mode
109 -h, --help Show this help message and exit
143 opts
, args
= getopt
.getopt(sys
.argv
[1:], short_options
, long_options
)
144 except getopt
.GetoptError
, err
:
145 sys
.stderr
.write("%s\n" % err
)
156 cleanup_messages
= ""
158 forward_facility
= 16 # default to "mail" (2 << 3)
160 forward_method
= None # local event console
169 if o
in ['-h', '--help']:
171 elif o
in ['-d', '--debug']:
173 elif o
== '--protocol':
175 elif o
== '--server':
179 elif o
== '--username':
181 elif o
== '--password':
185 elif o
== '--connect-timeout':
186 conn_timeout
= int(a
)
187 elif o
== '--cleanup':
189 elif o
== '--forward-ec':
191 elif o
== '--match-subject':
192 match_subject
= re
.compile(a
)
193 elif o
== '--forward-facility':
194 forward_facility
= int(a
) << 3
195 elif o
== '--forward-app':
197 elif o
== '--forward-method':
199 forward_method
= tuple(a
.split(','))
202 elif o
== '--forward-host':
204 elif o
== '--body-limit':
207 param_names
= dict(opts
).keys()
208 for param_name
in required_params
:
209 if '--' + param_name
not in param_names
:
210 usage('The needed parameter --%s is missing' % param_name
)
212 if fetch_proto
not in ['IMAP', 'POP3']:
213 usage('The given protocol is not supported.')
215 if fetch_port
is None:
216 if fetch_proto
== 'POP3':
217 fetch_port
= 995 if fetch_ssl
else 110
219 fetch_port
= 993 if fetch_ssl
else 143
225 if fetch_proto
== 'POP3':
226 fetch_class
= poplib
.POP3_SSL
if fetch_ssl
else poplib
.POP3
227 g_M
= fetch_class(fetch_server
, fetch_port
)
229 g_M
.pass_(fetch_pass
)
231 fetch_class
= imaplib
.IMAP4_SSL
if fetch_ssl
else imaplib
.IMAP4
232 g_M
= fetch_class(fetch_server
, fetch_port
)
233 g_M
.login(fetch_user
, fetch_pass
)
234 g_M
.select('INBOX', readonly
=False) # select INBOX
238 bail_out(3, 'Failed connect to %s:%d: %s' % (fetch_server
, fetch_port
, parse_exception(e
)))
244 # Get mails from mailbox
245 if fetch_proto
== 'POP3':
246 num_messages
= len(g_M
.list()[1])
247 for i
in range(num_messages
):
249 lines
= g_M
.retr(index
)[1]
250 mails
[i
] = email
.message_from_string("\n".join(lines
))
252 retcode
, messages
= g_M
.search(None, 'NOT', 'DELETED')
253 if retcode
== 'OK' and messages
[0].strip():
254 for num
in messages
[0].split(' '):
256 data
= g_M
.fetch(num
, '(RFC822)')[1]
257 mails
[num
] = email
.message_from_string(data
[0][1])
259 raise Exception('Failed to fetch mail %s (%s). Available messages: %r' %
260 (num
, parse_exception(e
), messages
))
263 # Now filter out the messages not wanted to be handled by this check
264 for index
, msg
in mails
.items():
265 matches
= match_subject
.match(msg
.get('Subject', ''))
273 bail_out(3, 'Failed to check for mails: %s' % parse_exception(e
))
276 def cleanup_mailbox():
278 return # do not deal with mailbox when none sent yet
280 # Do not delete all messages in the inbox. Only the ones which were
281 # processed before! In the meantime there might be occured new ones.
282 for index
in g_forwarded
:
283 if fetch_proto
== 'POP3':
284 if cleanup_messages
== 'delete':
285 response
= g_M
.dele(index
+ 1)
286 if not response
.startswith("+OK"):
287 raise Exception("Response from server: [%s]" % response
)
289 if cleanup_messages
!= 'delete':
290 # The user wants the message to be moved to the folder
291 # refered by the string stored in "cleanup_messages"
292 folder
= cleanup_messages
.strip('/')
294 # Create maybe missing folder hierarchy
296 for level
in folder
.split('/'):
297 target
+= "%s/" % level
301 ty
, data
= g_M
.copy(str(index
), folder
)
303 raise Exception("Response from server: [%s]" % data
)
305 # Now delete the mail
306 ty
, data
= g_M
.store(index
, '+FLAGS', '\\Deleted')
308 raise Exception("Response from server: [%s]" % data
)
310 if fetch_proto
== 'IMAP':
315 bail_out(2, 'Failed to delete mail: %s' % parse_exception(e
))
320 return # do not deal with mailbox when none sent yet
321 if fetch_proto
== 'POP3':
329 localtime
= time
.localtime()
330 day
= int(time
.strftime("%d", localtime
)) # strip leading 0
331 value
= time
.strftime("%b %%d %H:%M:%S", localtime
)
335 def forward_to_ec(mails
):
336 # create syslog message from each mail
337 # <128> Oct 24 10:44:27 Klappspaten /var/log/syslog: Oct 24 10:44:27 Klappspaten logger: asdasdad as
338 # <facility+priority> timestamp hostname application: message
340 cur_time
= syslog_time()
342 for index
, msg
in mails
.items():
343 subject
= msg
.get('Subject', 'None')
344 encoding
= msg
.get('Content-Transfer-Encoding', 'None')
346 # Now add the body to the event
347 if msg
.is_multipart():
348 # only care for the first text/plain element
349 for part
in msg
.walk():
350 content_type
= part
.get_content_type()
351 disposition
= str(part
.get('Content-Disposition'))
352 encoding
= part
.get('Content-Transfer-Encoding', 'None')
353 if content_type
== 'text/plain' and 'attachment' not in disposition
:
354 payload
= part
.get_payload()
355 if encoding
== "base64":
356 payload
= base64
.b64decode(payload
)
357 log_line
+= '|' + payload
[:body_limit
]
360 payload
= msg
.get_payload()
361 if encoding
== "base64":
362 payload
= base64
.b64decode(payload
)
363 log_line
+= '|' + payload
[:body_limit
]
365 log_line
= log_line
.replace('\r\n', '\0')
366 log_line
= log_line
.replace('\n', '\0')
368 # replace match groups in "forward_app"
370 application
= forward_app
371 matches
= match_subject
.match(subject
)
372 for num
, match
in enumerate(matches
.groups()):
373 application
= application
.replace('\\%d' % (num
+ 1,), match
)
375 application
= subject
.replace('\n', '')
377 # Construct the final syslog message
378 log
= '<%d>%s' % (forward_facility
+ priority
, cur_time
)
379 log
+= ' %s %s: %s' % (forward_host
or fetch_server
, application
, log_line
)
381 g_forwarded
.append(index
)
383 # send lines to event console
384 # a) local in same omd site
388 global forward_method
389 if not forward_method
:
390 forward_method
= os
.getenv('OMD_ROOT') + "/tmp/run/mkeventd/eventsocket"
391 elif forward_method
== 'spool:':
392 forward_method
+= os
.getenv('OMD_ROOT') + "/var/mkeventd/spool"
396 if isinstance(forward_method
, tuple):
397 # connect either via tcp or udp
398 if forward_method
[0] == 'udp':
399 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
)
401 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
402 sock
.connect((forward_method
[1], forward_method
[2]))
403 for message
in messages
:
404 sock
.send(message
+ "\n")
407 elif not forward_method
.startswith('spool:'): # pylint: disable=no-member
408 # write into local event pipe
409 # Important: When the event daemon is stopped, then the pipe
410 # is *not* existing! This prevents us from hanging in such
411 # situations. So we must make sure that we do not create a file
412 # instead of the pipe!
413 sock
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
414 sock
.connect(forward_method
)
415 sock
.send('\n'.join(messages
) + '\n')
419 # Spool the log messages to given spool directory.
420 # First write a file which is not read into ec, then
421 # perform the move to make the file visible for ec
422 spool_path
= forward_method
[6:]
423 file_name
= '.%s_%d_%d' % (forward_host
, os
.getpid(), time
.time())
424 if not os
.path
.exists(spool_path
):
425 os
.makedirs(spool_path
)
426 file('%s/%s' % (spool_path
, file_name
), 'w').write('\n'.join(messages
) + '\n')
427 os
.rename('%s/%s' % (spool_path
, file_name
), '%s/%s' % (spool_path
, file_name
[1:]))
432 bail_out(0, 'Forwarded %d messages to event console' % len(messages
),
433 [('messages', len(messages
))])
436 3, 'Unable to forward messages to event console (%s). Left %d messages untouched.' %
441 # Enable showing protocol messages of imap for debugging
448 forward_to_ec(fetch_mails())
450 bail_out(0, 'Successfully logged in to mailbox')
455 socket
.setdefaulttimeout(conn_timeout
)
462 bail_out(2, 'Unhandled exception: %s' % parse_exception(e
))