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.
35 from optparse
import OptionParser
37 import cmk
.utils
.render
38 import cmk
.utils
.password_store
39 cmk
.utils
.password_store
.replace_passwords()
43 def __init__(self
, conn
, mail_id
):
45 self
.__timestamp
= None
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"]))
56 if self
.__timestamp
is None:
57 # only resolve the internal date as this is quicker if we need nothing
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
__()
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
)
79 return len(self
.__id
_list
)
82 return not self
.__id
_list
85 class LoginError(Exception):
89 class ImapWrapper(object):
90 def __init__(self
, host
, user
, password
, use_ssl
):
91 super(ImapWrapper
, self
).__init
__()
93 self
.__conn
= imaplib
.IMAP4_SSL(host
)
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
)
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)
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
)))
125 ids
= self
.__conn
.search(None, "ALL")
127 return MailList(self
.__conn
, ids
[1][0].split())
131 sys
.stdout
.write(message
+ "\n")
135 parser
= OptionParser()
141 help="print debugging information",
145 help="the imap server to connect to (hostname or ip)",
150 help="tcp port of the imap server (default is 143 for unencrypted and 993 for SSL)",
156 help="enables ssl encrypted communication with the server",
160 help="Username to use for IMAP",
164 help="Password to use for IMAP",
170 help="Timeout in seconds for network connects",
176 help="age (in seconds) of mails above which the check will warn",
181 help="age (in seconds) of mails above which the check will become critical",
187 help="warn if the newest message is older than this value (in seconds)",
192 help="crit if the newest message is older than this value (in seconds)",
198 help="number of mails above which the check will warn",
203 help="number of mails above which the check will become critical",
210 help="mailbox to check. Can appear repeatedly to monitor multiple mailboxes",
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
)
231 if options
.password
is None:
232 from getpass
import getpass
233 options
.password
= getpass()
235 socket
.setdefaulttimeout(options
.timeout
)
238 imap
= ImapWrapper(options
.server
, options
.username
, options
.password
, True)
240 output("login failed")
246 for mb
in imap
.mailboxes():
247 if options
.mailboxes
and mb
not in options
.mailboxes
:
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
264 old_mails
= imap
.mails(mb
, before
=(now
- options
.warn_age
) + 86400)
266 old_mails
= old_mails
[:options
.retrieve_max
]
269 for mail
in old_mails
:
270 age
= cmk
.utils
.render
.Age(now
- mail
.timestamp())
271 if float(age
) >= options
.crit_age
:
274 status_icon
= "(!!) "
276 elif float(age
) >= options
.warn_age
:
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
))
291 report_age
= cmk
.utils
.render
.Age(options
.crit_new
)
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
:
299 elif float(age
) < options
.crit_new
and float(age
) < float(report_age
):
303 status_icon
= "(!!) "
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
))))
313 status
= max([m
[0] for m
in messages
])
314 output(", ".join([m
[1] for m
in messages
]))
317 output("all mailboxes fine")
321 if __name__
== "__main__":
326 output("An exception occured (Run in debug mode for details): %s" % e
)
328 output(traceback
.format_exc())