notify_email plugin: Add plugin to send notifications via email.
authorOleg King <king2@kaluga.ru>
Wed, 4 Jun 2008 13:22:45 +0000 (15:22 +0200)
committerFlorian Forster <octo@leeloo.lan.home.verplant.org>
Wed, 4 Jun 2008 13:31:08 +0000 (15:31 +0200)
Hello, Collectd.

I wrote a new plugin - notify_email.

This plugin will notify you when threshold conditions are met - one email per
one threshold notify. Email can be sent more then one recipient. Plugin does
this by initiating SMTP session to specified SMTP server (it can also do SMTP
AUTH if needed).

Plugin uses libesmtp library.

Please note:
  1. SMTPFrom value sometimes is checked by SMTP server - you
     can get failure from SMTP server if you are using not-existent
     email.

  2. SMTPSubject can contain two '%s' - first for severity and
     second for hostname.

  3. Hostname used in SMTP connection - is hostname_g, hostname
     that is used for all collectd host identification. If it cannot
     be resolved by internet DNS - you can expect problems when sending
     emails.

Patch and new file are included as attach.

configure.in
src/Makefile.am
src/collectd.conf.in
src/notify_email.c [new file with mode: 0644]

index cac633b..856aa82 100644 (file)
@@ -1489,6 +1489,44 @@ AC_DEFINE_UNQUOTED(COLLECT_LIBPCAP, [$collect_libpcap],
        [Wether or not to use the pcap library])
 AM_CONDITIONAL(BUILD_WITH_LIBPCAP, test "x$with_libpcap" = "xyes")
 
+AC_ARG_WITH(libesmtp, [AS_HELP_STRING([--with-libesmtp@<:@=PREFIX@:>@], [Path to libesmtp.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               LDFLAGS="$LDFLAGS -L$withval/lib"
+               CPPFLAGS="$CPPFLAGS -I$withval/include -D_THREAD_SAFE"
+               with_libesmtp="yes"
+       else
+               with_libesmtp="$withval"
+       fi
+],
+[
+       with_libesmtp="yes"
+])
+if test "x$with_libesmtp" = "xyes"
+then
+       AC_CHECK_LIB(esmtp, smtp_create_session,
+       [
+               AC_DEFINE(HAVE_LIBESMTP, 1, [Define to 1 if you have the esmtp library (-lesmtp).])
+       ], [with_libesmtp="no (libesmtp not found)"])
+fi
+if test "x$with_libesmtp" = "xyes"
+then
+       AC_CHECK_HEADERS(libesmtp.h,
+       [
+               AC_DEFINE(HAVE_LIBESMTP_H, 1, [Define to 1 if you have the <libesmtp.h> header file.])
+       ], [with_libesmtp="no (libesmtp.h not found)"])
+fi
+if test "x$with_libesmtp" = "xyes"
+then
+       collect_libesmtp=1
+else
+       collect_libesmtp=0
+fi
+AC_DEFINE_UNQUOTED(COLLECT_LIBESMTP, [$collect_libesmtp],
+       [Wether or not to use the esmtp library])
+AM_CONDITIONAL(BUILD_WITH_LIBESMTP, test "x$with_libesmtp" = "xyes")
+
 perl_interpreter="perl"
 AC_ARG_WITH(libperl, [AS_HELP_STRING([--with-libperl@<:@=PREFIX@:>@], [Path to libperl.])],
 [
@@ -2499,6 +2537,7 @@ AC_PLUGIN([netlink],     [$with_libnetlink],   [Enhanced Linux network statistic
 AC_PLUGIN([network],     [yes],                [Network communication plugin])
 AC_PLUGIN([nfs],         [$plugin_nfs],        [NFS statistics])
 AC_PLUGIN([nginx],       [$with_libcurl],      [nginx statistics])
+AC_PLUGIN([notify_email], [$with_libesmtp],    [Email notifier])
 AC_PLUGIN([ntpd],        [yes],                [NTPd statistics])
 AC_PLUGIN([nut],         [$with_libupsclient], [Network UPS tools statistics])
 AC_PLUGIN([perl],        [$plugin_perl],       [Embed a Perl interpreter])
@@ -2660,6 +2699,7 @@ Configuration:
     network . . . . . . . $enable_network
     nfs . . . . . . . . . $enable_nfs
     nginx . . . . . . . . $enable_nginx
+    notify_email  . . . . $enable_notify_email
     ntpd  . . . . . . . . $enable_ntpd
     nut . . . . . . . . . $enable_nut
     perl  . . . . . . . . $enable_perl
index 42a1124..149ecb3 100644 (file)
@@ -478,6 +478,14 @@ collectd_LDADD += "-dlopen" nginx.la
 collectd_DEPENDENCIES += nginx.la
 endif
 
+if BUILD_PLUGIN_NOTIFY_EMAIL
+pkglib_LTLIBRARIES += notify_email.la
+notify_email_la_SOURCES = notify_email.c
+notify_email_la_LDFLAGS = -L/usr/local/lib -lesmtp -lssl -lcrypto -pthread -module -avoid-version
+collectd_LDADD += "-dlopen" notify_email.la
+collectd_DEPENDENCIES += notify_email.la
+endif
+
 if BUILD_PLUGIN_NTPD
 pkglib_LTLIBRARIES += ntpd.la
 ntpd_la_SOURCES = ntpd.c
index 25bc09e..06f9a92 100644 (file)
@@ -46,6 +46,7 @@ FQDNLookup   true
 @BUILD_PLUGIN_NETWORK_TRUE@LoadPlugin network
 @BUILD_PLUGIN_NFS_TRUE@LoadPlugin nfs
 @BUILD_PLUGIN_NGINX_TRUE@LoadPlugin nginx
+@BUILD_PLUGIN_NOTIFY_EMAIL_TRUE@LoadPlugin notify_email
 @BUILD_PLUGIN_NTPD_TRUE@LoadPlugin ntpd
 @BUILD_PLUGIN_NUT_TRUE@LoadPlugin nut
 @BUILD_PLUGIN_PERL_TRUE@LoadPlugin perl
@@ -211,6 +212,17 @@ FQDNLookup   true
 #      CACert "/etc/ssl/ca.crt"
 #</Plugin>
 
+#<Plugin notify_email>
+#       SMTPHost "localhost"
+#      SMTPPort 25
+#      SMTPSubject "Aaaaaa!! %s on %s!!!!!"   # <WARNING/FAILURE/OK> on <hostname>. beware! do not use not more than two %s in this string!!!
+#      SMTPFrom "collectd@main0server.com"
+#      SMTPUser "my-username"
+#      SMTPPassword "my-password"
+#      EmailTo "email1@domain1.net"
+#      EmailTo "email2@domain2.com"
+#</Plugin>
+
 #<Plugin ntpd>
 #      Host "localhost"
 #      Port 123
diff --git a/src/notify_email.c b/src/notify_email.c
new file mode 100644 (file)
index 0000000..f6c628e
--- /dev/null
@@ -0,0 +1,276 @@
+/**
+ * collectd - src/notify_email.c
+ * Copyright (C) 2008  Oleg King
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Oleg King <king2 at kaluga.ru>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <auth-client.h>
+#include <libesmtp.h>
+
+#define MAXSTRING               256
+
+static const char *config_keys[] =
+{
+  "SMTPHost",
+  "SMTPPort",
+  "SMTPUser",
+  "SMTPPassword",
+  "SMTPFrom",
+  "SMTPSubject",
+  "EmailTo"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static char **emails;
+static int emails_len = 0;
+
+static smtp_session_t session;
+static smtp_message_t message;
+static auth_context_t authctx = NULL;
+
+static int smtp_port = 25;
+static char *smtp_host = NULL;
+static char *smtp_user = NULL;
+static char *smtp_password = NULL;
+static char *smtp_from = NULL;
+static char *smtp_subject = NULL;
+
+#define DEFAULT_SMTP_HOST      "localhost"
+#define DEFAULT_SMTP_FROM      "root@localhost"
+#define DEFAULT_SMTP_SUBJECT   "Collectd notify: %s@%s"
+
+
+/* Callback to get username and password */
+static int authinteract (auth_client_request_t request, char **result, int fields, void *arg)
+{               
+  int i;
+  for (i = 0; i < fields; i++)
+  {
+    if (request[i].flags & AUTH_USER)
+      result[i] = smtp_user;
+    else if (request[i].flags & AUTH_PASS)
+      result[i] = smtp_password;
+    else
+      return 0;
+  }
+  return 1;
+}
+
+/* Callback to print the recipient status */
+static void print_recipient_status (smtp_recipient_t recipient, const char *mailbox, void *arg)
+{
+  const smtp_status_t *status;
+
+  status = smtp_recipient_status (recipient);
+  if (status->text[strlen(status->text) - 2] == '\r')
+    status->text[strlen(status->text) - 2] = 0;
+  INFO ("notify_email: notify sent to %s: %d %s", mailbox, status->code, status->text);
+}
+
+/* Callback to monitor SMTP activity */
+static void monitor_cb (const char *buf, int buflen, int writing, void *arg)
+{
+  char log_str[MAXSTRING];
+
+  strncpy(log_str, buf, buflen);
+  if (buflen > 2)
+    log_str[buflen - 2] = 0; /* replace \n with \0 */
+
+  if (writing == SMTP_CB_HEADERS) {
+    DEBUG ("SMTP --- H: %s", log_str);
+    return;
+  }
+  DEBUG (writing ? "SMTP >>> C: %s" : "SMTP <<< S: %s", log_str);
+}
+
+static int notify_email_init()
+{
+  char server[MAXSTRING];
+
+  auth_client_init();
+  if ( !(session = smtp_create_session()) ) {
+    ERROR ("notify_email plugin: cannot create SMTP session");
+    return (-1);
+  }
+
+  smtp_set_monitorcb (session, monitor_cb, NULL, 1);
+  smtp_set_hostname (session, hostname_g);
+  sprintf(server, "%s:%i", smtp_host == NULL ? DEFAULT_SMTP_HOST : smtp_host, smtp_port);
+  smtp_set_server (session, server);
+
+  if (smtp_user && smtp_password) {
+    authctx = auth_create_context ();
+    auth_set_mechanism_flags (authctx, AUTH_PLUGIN_PLAIN, 0);
+    auth_set_interact_cb (authctx, authinteract, NULL);
+  }
+
+  if ( !smtp_auth_set_context (session, authctx)) {
+    ERROR ("notify_email plugin: cannot set SMTP auth context");
+    return (-1);   
+  }
+
+  return (0);
+}
+
+
+static int notify_email_shutdown()
+{
+  smtp_destroy_session (session);
+  auth_destroy_context (authctx);
+  auth_client_exit();
+  return (0);
+}
+
+
+static int notify_email_config (const char *key, const char *value)
+{
+  if (strcasecmp (key, "EmailTo") == 0)
+  {
+    char **tmp;
+
+    tmp = (char **) realloc ((void *) emails, (emails_len + 1) * sizeof (char *));
+    if (tmp == NULL) {
+      ERROR ("notify_email: realloc failed.");
+      return (-1);
+    }
+
+    emails = tmp;
+    emails[emails_len] = strdup (value);
+    if (emails[emails_len] == NULL) {
+      ERROR ("notify_email: strdup failed.");
+      return (-1);
+    }
+    emails_len++;
+  }
+  else if (0 == strcasecmp (key, "SMTPHost")) {
+    sfree (smtp_host);
+    smtp_host = strdup (value);
+  }
+  else if (0 == strcasecmp (key, "SMTPPort")) {
+    int port_tmp = atoi (value);
+    if (port_tmp < 1 || port_tmp > 65535)
+    {
+      WARNING ("notify_email plugin: Invalid SMTP port: %i", port_tmp);
+      return (1);
+    }
+    smtp_port = port_tmp;
+  }
+  else if (0 == strcasecmp (key, "SMTPUser")) {
+    sfree (smtp_user);
+    smtp_user = strdup (value);
+  }
+  else if (0 == strcasecmp (key, "SMTPPassword")) {
+    sfree (smtp_password);
+    smtp_password = strdup (value);
+  }
+  else if (0 == strcasecmp (key, "SMTPFrom")) {
+    sfree (smtp_from);
+    smtp_from = strdup (value);
+  }
+  else if (0 == strcasecmp (key, "SMTPSubject")) {
+    sfree (smtp_subject);
+    smtp_subject = strdup (value);
+  }
+  else {
+    return -1;
+  }
+  return 0;
+} /* int notify_email_config (const char *, const char *) */
+
+
+static int notify_email_notification (const notification_t *n)
+{
+  smtp_recipient_t recipient;
+
+  struct tm timestamp_tm;
+  char timestamp_str[64];
+
+  char severity[MAXSTRING];
+  char subject[MAXSTRING];
+
+  char buf[4096] = "";
+  int  buf_len = sizeof (buf);
+  int i;
+
+  sprintf (severity, "%s",
+      (n->severity == NOTIF_FAILURE) ? "FAILURE"
+      : ((n->severity == NOTIF_WARNING) ? "WARNING"
+        : ((n->severity == NOTIF_OKAY) ? "OKAY" : "UNKNOWN")));
+
+  sprintf (subject, smtp_subject == NULL ? DEFAULT_SMTP_SUBJECT : smtp_subject, severity, n->host);
+
+  localtime_r (&n->time, &timestamp_tm);
+  strftime (timestamp_str, sizeof (timestamp_str), "%Y-%m-%d %H:%M:%S", &timestamp_tm);
+  timestamp_str[sizeof (timestamp_str) - 1] = '\0';
+
+  /* Let's make RFC822 message text with \r\n EOLs */
+  snprintf (buf, buf_len,
+      "MIME-Version: 1.0\r\n"
+      "Content-Type: text/plain;\r\n"
+      "Content-Transfer-Encoding: 8bit\r\n"
+      "Subject: %s\r\n"
+      "\r\n"
+      "%s - %s@%s\r\n"
+      "\r\n"
+      "Message: %s",
+      subject,
+      timestamp_str,
+      severity,
+      n->host,
+      n->message);
+
+  if ( !(message = smtp_add_message (session))) {
+    ERROR ("notify_email plugin: cannot set SMTP message");
+    return (-1);   
+  }
+  smtp_set_reverse_path (message, smtp_from);
+  smtp_set_header (message, "To", NULL, NULL);
+  smtp_set_message_str (message, buf);
+
+  for (i = 0; i < emails_len; i++)
+    recipient = smtp_add_recipient (message, emails[i]);
+
+  /* Initiate a connection to the SMTP server and transfer the message. */
+  if (!smtp_start_session (session)) {
+    char buf[MAXSTRING];
+    ERROR ("SMTP server problem: %s", smtp_strerror (smtp_errno (), buf, sizeof buf));
+    return (-1);
+  } else {
+    const smtp_status_t *status;
+    /* Report on the success or otherwise of the mail transfer. */
+    status = smtp_message_transfer_status (message);
+    DEBUG ("SMTP server report: %d %s", status->code, (status->text != NULL) ? status->text : "\n");
+    smtp_enumerate_recipients (message, print_recipient_status, NULL);
+  }
+
+  return (0);
+} /* int notify_email_notification */
+
+void module_register (void)
+{
+  plugin_register_init ("notify_email", notify_email_init);
+  plugin_register_shutdown ("notify_email", notify_email_shutdown);
+  plugin_register_config ("notify_email", notify_email_config,
+      config_keys, config_keys_num);
+  plugin_register_notification ("notify_email", notify_email_notification);
+} /* void module_register (void) */