Improving IMAP support in monit

For a while now I’ve been intending to make more use of a Kimsufi dedicated server I have, and making use of its datacentre-hosted location for remote connectivity testing seems like a good plan.

Although all the internal network services I have running are monitored from within the host network, I’ve so far had little external monitoring in place – WordPress’ Jetpack Plugin lets me know if this blog goes offline, and the excellent Andrews & Arnold send a variety of notifications if the VDSL connection to the router drops.

There are likely more suitable options available, but I chose monit to perform the monitoring from the remote server. Alternatives such as Nagios/Icinga are much more focussed on remotely posting locally-run test results, and monit does have the advantage of being (generally) quick and easy to setup.

There are, however, some caveats: monit can be configured to send email alerts, and will do so (unless the ‘SET ALERT recipient WITH REMINDER ON n CYCLES‘ configuration option is used) only on a state-change – from a test passing to failing, or from failing to passing. There is also the ‘EXEC‘ option to run an external script or binary – but this is actioned every single time the given test fails. When the command is used to tweet a notification that the service has failed (which can then be used as the basis for a free SMS alerting solution!) this can quickly lead to exceeding the twitter account quota without further rate-limiting. This form of rate-limiting is easily implemented, but it feels like something which monit should account for, rather than leaving this to the user. I’m considering patching monit to provide an ‘EXEC ON STATE CHANGE‘ option to provide equivalent functionality to ‘ALERT‘…

An additional problem I ran into is that monit’s protocol-specific tests can be a little inflexible: the IMAP test proceeds linearly through trying to opening a socket to the IMAP server, checking that a server-first banner is received, sending a ‘LOGOUT‘ command, checking for a response, and then confirming that the response contains the IMAP ‘* BYE‘ sign-off. All well and good. And, indeed, a Dovecot IMAP session on port 143 fits perfectly:

$ telnet <imap server> 143
Connected to <imap server>.
Escape character is '^]'.
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE STARTTLS LOGINDISABLED] Dovecot ready.
1 LOGOUT
* BYE Logging out
1 OK Logout completed.
Connection closed by foreign host.

… however, an IMAPS session to the same server instead results in:

$ openssl s_client -connect <imap server>:993
CONNECTED(00000003)
...
---
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN AUTH=LOGIN] Dovecot ready.
1 LOGOUT
closed

(… and interestingly, using STARTTLS over IMAP results in a server prompt of ‘. OK Pre-login capabilities listed, post-login capabilities have more.‘, and similarly no closing reply)

This causes monit to generate alerts with the somewhat self-contradicatory error message:

Description: failed protocol test [IMAP] at INET[<ip>:993] via TCPSSL -- IMAP: logout response read error -- Success

This is unfortunate, as it makes monitoring of secure IMAP services impossible with stock monit. Luckily, the check_imap() code in monit is pretty simple, and so a patched version which is tolerant to connections being dropped on logout (but which will still validate the response if one is provided) is now available from srcshelton/gentoo-ebuilds/app-admin/monit on GitHub. For other distributions or to manually build monit, the patch is at monit-5.11-imap.patch.

Enjoy 🙂