#!/usr/bin/perl -w
#
# This plugin is for querying Knuerr RMS Compact II Rack Monitoring Units
# by SNMP.
#
# Michael Meier <michael.meier@fau.de>
#
# 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; version 2 dated June,
# 1991.
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
#
# Usage:
# ln -s /usr/share/munin/node/plugins-auto/snmp__rms2_ /etc/munin/node.d/snmp_HOSTNAME_rms2_MODE
# available MODEs:
#   temp     for temperature sensors
#   humidity for humidity sensors (untested!)
#   contact  for contact sensors
#
# Note: Due to shortcomings of munin-node-configure, it is not currently
# possible to make this plugin properly autodetect/autoconfigure. That is
# because for SNMP plugins that need more than just a hostname, only numbers
# that come from an SNMP tree can be the second parameter. Parameters like
# "temp" do not exist in the short-sighted world of munin-node-configure.
# Combining SNMP with the "suggest" plugin-feature is currently not possible.

#%# family=snmpauto
#%# capabilities=snmpconf

use strict;
use Net::SNMP;

my $DEBUG = 0;

my $host      = $ENV{host}      || undef;
my $port      = $ENV{port}      || 161;
my $community = $ENV{community} || "public";
my $mode      = $ENV{mode}      || undef;

my $response;

my $graphs = {
    'temp' => {     'title'   => 'Temperatures',
                    'unit'    => 'degC', # This can actually be overwritten later with retard-units (F)
                    'scale'   => 0.1,
                    'thatype' => 2,
                    'list'    => '.1.3.6.1.4.1.3711.24.1.1.1.2.2.1.1' },
    'humidity' => { 'title'   => 'Humidity',
                    'unit'    => '%',
                    'scale'   => 0.1,  # FIXME?
                    'thatype' => 3,
                    'list'    => '.1.3.6.1.4.1.3711.24.1.1.1.2.2.1.1' },
    'contact' => {  'title'   => 'Contact sensors',
                    'unit'    => 'n',
                    'list'    => '.1.3.6.1.4.1.3711.24.1.1.1.3.1.1.1' },
};

if (defined($ARGV[0]) && ($ARGV[0] eq "snmpconf")) {
    print "require .1.3.6.1.4.1.3711.24.1.1.99.1.0\n"; # The inventory tree
    exit(0);
}

if ($0 =~ /^(?:|.*\/)snmp_([^_]+)_rms2_(.+)$/) {
    $host  = $1;
    $mode = $2;
    if ($host =~ /^([^:]+):(\d+)$/) {
        $host = $1;
        $port = $2;
    }
} elsif (!defined($host)) {
    print "# Debug: $0 -- $1 -- $2\n" if $DEBUG;
    print("# Error: couldn't understand what I'm supposed to monitor.\n");
    print("# You need to either name this plugin properly in the scheme\n");
    print("#  snmp_HOSTNAME_rms2_MODE\n");
    print("# or alternatively set the environment variables 'host' and 'mode'\n");
    exit(1);
}

my ($session, $error) = Net::SNMP->session(
                -hostname  => $host,
                -community => $community,
                -port      => $port,
                -version   => 'snmpv2c',
                -timeout   => 1.0
);

if (!defined ($session)) {
    print("# Failed to open SNMP session: $error");
    exit(1);
}

unless (defined($graphs->{$mode}->{'title'})) {
  print("# Unknown mode '$mode'! Available modes are:\n");
  foreach my $m (keys(%{$graphs})) {
    if (defined($graphs->{$m}->{'title'})) {
      printf("#  %-18s %s\n", $m, $graphs->{$m}->{'title'});
    }
  }
  exit(1);
}

# This is mostly copy&paste from the snmpwalk.pl-example in the Net::SNMP sourcecode.
# I'm not really sure this is the simplest way to do this, but could not find a
# better WORKING example.
my $last_oid = $graphs->{$mode}->{'list'};
my @indexes = ( );
my @args = ( -varbindlist => [ $last_oid ] );
push(@args, -maxrepetitions => 2);
GET_BULK: while (defined $session->get_bulk_request(@args)) {
  my @oids = $session->var_bind_names();
  if (!scalar @oids) {
    print("# ERROR: Empty SNMP tree\n");
    exit(1);
  }
  foreach my $oid (@oids) {
    # Make sure we have not hit the end of the MIB.
    if ($session->var_bind_types()->{$oid} == ENDOFMIBVIEW) {
      $oid =~ s/.*\.//g;
      push(@indexes, $oid);
      last GET_BULK;
    }
    if (substr($oid, 0, length($graphs->{$mode}->{'list'})) ne $graphs->{$mode}->{'list'}) {
      last GET_BULK;
    }
    $last_oid = $oid;
    $oid =~ s/.*\.//g;
    push(@indexes, $oid);
  }
  @args = (-maxrepetitions => 2, -varbindlist => [ $last_oid ]);
}

if (defined($graphs->{$mode}->{'thatype'})) { # Analogue sensor requested
  if ($graphs->{$mode}->{'thatype'} == 2) { # Temperature-Sensor
    if (defined($response = $session->get_request(".1.3.6.1.4.1.3711.24.1.1.1.2.1"))) {
      my $tempscale = $response->{".1.3.6.1.4.1.3711.24.1.1.1.2.1"} eq '2';
      if ($tempscale == 2) { $graphs->{$mode}->{'unit'} = 'degF'; }
      if ($tempscale == 3) { $graphs->{$mode}->{'unit'} = 'K'; }
    }
  }
}

# Now this is a little cheating: We "calculate" various other start OIDs from
# the list OID.
my $nameoid = my $stateoid = my $valueoid = $graphs->{$mode}->{'list'};
# The start OID for names just ends with .3 instead of .1
$nameoid =~ s/1$/3/;
# the one for input status ends with .2 instead of .1
$stateoid =~ s/1$/2/;
# the start OID for the value ends with .7 instead of .1
$valueoid =~ s/1$/7/;

# Now check which of the inputs we detected are valid
my @validindexes = ( );
foreach my $i (@indexes) {
  unless (defined($response = $session->get_request($stateoid . "." . $i))) {
    next;
  }
  if ($response->{$stateoid . "." . $i} ne '1') { # 1 means active and valid.
    next;
  }
  if (defined($graphs->{$mode}->{'thatype'})) { # Analogue sensor requested
    my $typeoid = $graphs->{$mode}->{'list'};
    $typeoid =~ s/1$/6/;
    unless (defined($response = $session->get_request($typeoid . "." . $i))) {
      next;
    }
    if ($response->{$typeoid . "." . $i} ne $graphs->{$mode}->{'thatype'}) {
      next; # Not the correct type (humidity vs. temperature)
    }
  }
  push(@validindexes, $i);
}

if ($ARGV[0] and $ARGV[0] eq "config") {
  print("host_name $host\n");
  printf("graph_title %s\n", $graphs->{$mode}->{'title'});
  # print "graph_args --base 1000\n";
  print("graph_category envsensor\n");
  printf("graph_vlabel %s\n", $graphs->{$mode}->{'unit'});
  unless (defined($graphs->{$mode}->{'thatype'})) {
    print("graph_info For contact sensors, the values mean: 1 = open, 2 = closed, 3 = armed, 4 = triggered\n");
  }

  foreach my $i (@validindexes) {
    if (defined($response = $session->get_request($nameoid . "." . $i))) {
      printf("%s%u.label %s\n", $mode, $i, $response->{$nameoid . "." . $i});
    } else {
      printf("%s%u.label Port %u\n", $mode, $i, $i);
    }
    if (defined($response = $session->get_request($graphs->{$mode}->{'list'} . "." . $i))) {
      printf("%s%u.info Port %s\n", $mode, $i, $response->{$graphs->{$mode}->{'list'} . "." . $i});
    }
    printf("%s%u.type GAUGE\n", $mode, $i);
    if (defined($ENV{"${mode}${i}.warning"})) { printf("%s%s.warning %s\n", $mode, $i, $ENV{"${mode}${i}.warning"}); }
    if (defined($ENV{"${mode}${i}.critical"})) { printf("%s%s.critical %s\n", $mode, $i, $ENV{"${mode}${i}.critical"}); }
  }
  exit(0);
}

foreach my $i (@validindexes) {
  if (defined($response = $session->get_request($valueoid . "." . $i))) {
    my $v = $response->{$valueoid . "." . $i};
    if (defined($graphs->{$mode}->{'scale'})) {
      $v = $v * $graphs->{$mode}->{'scale'};
      printf("%s%u.value %.1f\n", $mode, $i, $v);
    } else {
      printf("%s%u.value %u\n", $mode, $i, $v);
    }
  } else {
    printf("%s%u.value U\n", $mode, $i);
  }
}
