Cleanup config.nodes_of
[check_mk.git] / active_checks / check_mailboxes
blob08c885d536961c5a87382a28ccd7fd66b24e16f9
1 #!/usr/bin/env python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
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.
27 import sys
28 import time
29 import imaplib
30 import email
31 import email.utils
32 import re
33 import socket
34 import traceback
35 from optparse import OptionParser
37 import cmk.utils.render
38 import cmk.utils.password_store
39 cmk.utils.password_store.replace_passwords()
42 class Mail(object):
43 def __init__(self, conn, mail_id):
44 self.__mail = None
45 self.__timestamp = None
46 self.__conn = conn
47 self.__mail_id = mail_id
49 def __resolve_mail(self):
50 if self.__mail is None:
51 msg = self.__conn.fetch(self.__mail_id, "(RFC822)")
52 self.__mail = email.message_from_string(msg[1][0][1])
53 self.__timestamp = email.utils.mktime_tz(email.utils.parsedate_tz(self.__mail["DATE"]))
55 def timestamp(self):
56 if self.__timestamp is None:
57 # only resolve the internal date as this is quicker if we need nothing
58 # else from the mail
59 msg = self.__conn.fetch(self.__mail_id, "INTERNALDATE")
60 self.__timestamp = time.mktime(imaplib.Internaldate2tuple(msg[1][0]))
61 return self.__timestamp
64 class MailList(object):
65 def __init__(self, conn, id_list):
66 super(MailList, self).__init__()
67 self.__conn = conn
68 self.__id_list = id_list
70 def __getitem__(self, idx):
71 if isinstance(idx, slice):
72 return MailList(self.__conn, self.__id_list[idx])
73 return self.__get_by_index(self.__id_list[idx])
75 def __get_by_index(self, idx):
76 return Mail(self.__conn, idx)
78 def __len__(self):
79 return len(self.__id_list)
81 def empty(self):
82 return not self.__id_list
85 class LoginError(Exception):
86 pass
89 class ImapWrapper(object):
90 def __init__(self, host, user, password, use_ssl):
91 super(ImapWrapper, self).__init__()
92 if use_ssl:
93 self.__conn = imaplib.IMAP4_SSL(host)
94 else:
95 self.__conn = imaplib.IMAP4(host)
96 res = self.__conn.login(user, password)
97 if res[0].lower() != 'ok':
98 raise LoginError("Login as user \"%s\" failed" % user)
100 def mailboxes(self):
101 mb_list = self.__conn.list()[1]
102 pattern = re.compile(r'\((.*?)\) "(.*)" (.*)')
103 return [pattern.search(mb).group(3).strip("\"") for mb in mb_list]
105 def __format_date(self, timestamp):
106 return time.strftime("%d-%b-%Y", time.gmtime(timestamp))
107 #return time.strftime("%Y-%m-%d", time.gmtime(timestamp))
108 #return email.utils.formatdate(timestamp)
110 def mails(self, mailbox, before=None, after=None):
112 retrieve mails from a mailbox
113 before: if set, mails before that timestamp (rounded down to days)
114 are returned
116 self.__conn.select(mailbox)
118 if before is not None:
119 ids = self.__conn.search(None, "SENTBEFORE",
120 email.utils.encode_rfc2231(self.__format_date(before)))
121 elif after is not None:
122 ids = self.__conn.search(None, "SENTSINCE",
123 email.utils.encode_rfc2231(self.__format_date(after)))
124 else:
125 ids = self.__conn.search(None, "ALL")
127 return MailList(self.__conn, ids[1][0].split())
130 def output(message):
131 sys.stdout.write(message + "\n")
134 def parse_args():
135 parser = OptionParser()
136 parser.add_option(
137 "-d",
138 "--debug",
139 action="store_true",
140 default=False,
141 help="print debugging information",
143 parser.add_option(
144 "--server",
145 help="the imap server to connect to (hostname or ip)",
147 parser.add_option(
148 "--port",
149 type="int",
150 help="tcp port of the imap server (default is 143 for unencrypted and 993 for SSL)",
152 parser.add_option(
153 "--ssl",
154 action="store_true",
155 default=False,
156 help="enables ssl encrypted communication with the server",
158 parser.add_option(
159 "--username",
160 help="Username to use for IMAP",
162 parser.add_option(
163 "--password",
164 help="Password to use for IMAP",
166 parser.add_option(
167 "--timeout",
168 type="int",
169 default=10,
170 help="Timeout in seconds for network connects",
173 parser.add_option(
174 "--warn-age",
175 type="int",
176 help="age (in seconds) of mails above which the check will warn",
178 parser.add_option(
179 "--crit-age",
180 type="int",
181 help="age (in seconds) of mails above which the check will become critical",
184 parser.add_option(
185 "--warn-new",
186 type="int",
187 help="warn if the newest message is older than this value (in seconds)",
189 parser.add_option(
190 "--crit-new",
191 type="int",
192 help="crit if the newest message is older than this value (in seconds)",
195 parser.add_option(
196 "--warn-count",
197 type="int",
198 help="number of mails above which the check will warn",
200 parser.add_option(
201 "--crit-count",
202 type="int",
203 help="number of mails above which the check will become critical",
206 parser.add_option(
207 "--mailbox",
208 dest="mailboxes",
209 action="append",
210 help="mailbox to check. Can appear repeatedly to monitor multiple mailboxes",
213 parser.add_option(
214 "--retrieve-max",
215 type="int",
216 default=5,
217 help="limit the number of mails retrieved per mailbox. Only relevant when checking age",
220 options = parser.parse_args()[0]
222 for mandatory in ["server", "username"]:
223 if getattr(options, mandatory) is None:
224 parser.error("--%s not set" % mandatory)
225 sys.exit(1)
227 return options
230 def main(options):
231 if options.password is None:
232 from getpass import getpass
233 options.password = getpass()
235 socket.setdefaulttimeout(options.timeout)
237 try:
238 imap = ImapWrapper(options.server, options.username, options.password, True)
239 except LoginError:
240 output("login failed")
241 sys.exit(2)
243 messages = []
244 now = time.time()
246 for mb in imap.mailboxes():
247 if options.mailboxes and mb not in options.mailboxes:
248 continue
250 if options.crit_count and options.warn_count:
251 count = len(imap.mails(mb))
252 if count >= options.crit_count:
253 messages.append((2, "%s has %d messages (warn/crit at %d/%d)" % \
254 (mb, count, options.warn_count, options.crit_count)))
255 elif count >= options.warn_count:
256 messages.append((1, "%s has %d messages (warn/crit at %d/%d)" % \
257 (mb, count, options.warn_count, options.crit_count)))
259 if options.crit_age and options.warn_age:
260 # we want to filter mails by their age in at least minute precision,
261 # but imap search doesn't allow more than day-precision, so we have to
262 # retrieve all mails from the day before the relevant age and
263 # filter the result
264 old_mails = imap.mails(mb, before=(now - options.warn_age) + 86400)
265 if old_mails:
266 old_mails = old_mails[:options.retrieve_max]
267 status = 0
268 status_icon = ""
269 for mail in old_mails:
270 age = cmk.utils.render.Age(now - mail.timestamp())
271 if float(age) >= options.crit_age:
272 report_age = age
273 status = 2
274 status_icon = "(!!) "
275 break
276 elif float(age) >= options.warn_age:
277 report_age = age
278 status = 1
279 status_icon = "(!) "
281 if status > 0:
282 messages.append(
283 (status,
284 "oldest mail in %s is at least %s old %s(warn/crit at %s/%s)" %\
285 (mb, report_age, status_icon,
286 cmk.utils.render.Age(options.warn_age), cmk.utils.render.Age(options.crit_age))))
288 if options.crit_new and options.warn_new:
289 new_mails = imap.mails(mb, after=(now - options.crit_new))
290 status = 2
291 report_age = cmk.utils.render.Age(options.crit_new)
292 if new_mails:
293 new_mails = new_mails[options.retrieve_max * -1:]
294 for mail in reversed(new_mails):
295 age = cmk.utils.render.Age(now - mail.timestamp())
296 if float(age) < options.warn_new:
297 status = 0
298 break
299 elif float(age) < options.crit_new and float(age) < float(report_age):
300 report_age = age
301 status = 1
302 if status == 2:
303 status_icon = "(!!) "
304 elif status == 1:
305 status_icon = "(!) "
307 if status > 0:
308 messages.append(
309 (status, "newest mail in %s is at least %s old %s(warn/crit at %s/%s)" %\
310 (mb, report_age, status_icon, cmk.utils.render.Age(options.warn_new), cmk.utils.render.Age(options.crit_new))))
312 if messages:
313 status = max([m[0] for m in messages])
314 output(", ".join([m[1] for m in messages]))
315 sys.exit(status)
316 else:
317 output("all mailboxes fine")
318 sys.exit(0)
321 if __name__ == "__main__":
322 opts = parse_args()
323 try:
324 main(opts)
325 except Exception, e:
326 output("An exception occured (Run in debug mode for details): %s" % e)
327 if opts.debug:
328 output(traceback.format_exc())
329 sys.exit(3)