#!/usr/bin/perl -w
# -*- perl -*-
# vim: ft=perl

# Copyright Quentin Smith <quentin@mit.edu>
# and Bjorn Ruberg <bjorn@ruberg.no>
# Licenced under GPL v2
#

# We use one script for all monitoring.
# This script may be symlinked with several names, all
# performing different functions:
# 389ds_statistics_bytes
# 389ds_statistics_pdu
# 389ds_statistics_referrals
# 389ds_statistics_entries
# 389ds_connections
# 389ds_waiters
# 389ds_operations
# 389ds_operations_diff

# Magic markers
#%# family=auto
#%# capabilities=autoconf suggest

use strict;

my $ret = '';

if (! eval "require Net::LDAP;") {
   $ret = "Net::LDAP not found";
}

use vars qw ( $config $param $act $scope $descr $cn $vlabel
	      $info $title $label);

# Change these to reflect your LDAP ACL. The given DN must have
# read access to the Monitor branch.
my $basedn = "cn=Monitor";
my $server = ($ENV{'server'} || 'localhost');
my $userdn = ($ENV{'binddn'} || '');
my $userpw = ($ENV{'bindpw'} || '');

# Remember: connections, bytes, pdu needs scope=base

# http://www.icir.org/fenner/mibs/extracted/DIRECTORY-SERVER-MIB-rfc2605.txt

# The possible measurements
my %ops =
    (
     # Only read Total
     'connections' 
     => {
         'search' => 'cn=monitor',
	 'searchattr' => 'totalconnections',
         'desc'   => 'The number of connections',
         'label'  => 'connections',
         'vlabel' => 'connections/${graph_period}',
         'title'  => 'Connection rate',
         'info'   => 'Rate of connections to the LDAP server',
         'scope'  => "base"
         },
     'connections_active' 
     => {
         'search' => 'cn=monitor',
	 'searchattr' => 'currentconnections',
         'desc'   => 'The number of connections',
         'label'  => 'connections',
         'vlabel' => 'connections',
	 'type'   => 'GAUGE',
         'title'  => 'Active connections',
         'info'   => 'Number of connections to the LDAP server',
         'scope'  => "base"
         },
     'binds'
     => {
	 'search' => 'cn=snmp,cn=monitor',
         'label2' => {
	     'anonymousbinds' => 'Anonymous',
	     'unauthbinds'  => 'Unauthenticated',
	     'simpleauthbinds' => 'Simple authentication',
	     'strongauthbinds' => 'Strong authentication',
	     'bindsecurityerrors' => 'Errors',
	 },
	 'desc'   => 'The number of binds',
	 'vlabel' => 'binds/${graph_period}',
	 'type'   => 'DERIVE',
	 'title'  => 'Binds',
	 'info'   => 'Number of binds to the LDAP server',
	 'scope'  => "base"
         },	 
     'statistics_bytes'
     => {
         'search' => "cn=monitor",
	 'searchattr' => 'bytessent',
         'desc'   => "The number of bytes sent by the LDAP server.",
         'vlabel' => 'bytes/${graph_period}',
         'label'  => 'bytes',
         'title'  => "Number of bytes sent",
         'info'   => "The graph shows the number of bytes sent",
	 'scope'  => "base"
         },
     # Entries
     'statistics_entries'
     => {
         'search' => "cn=monitor",
	 'searchattr' => 'entriessent',
         'desc'   => "The number of entries sent by the LDAP server.",
         'vlabel' => 'entries/${graph_period}',
         'label'  => 'entries',
         'title'  => "Number of LDAP Entries",
         'info'   => "The graph shows the number of entries sent",
	 'scope'  => "base"
         },
     'operations'
     => {
	 'search' => 'cn=snmp,cn=monitor',
         'label2' => {
	     readops        => 'Read',
	     compareops     => 'Compare',
	     addentryops    => 'Add entry',
	     removeentryops => 'Remove entry',
	     modifyentryops => 'Modify entry',
	     modifyrdnops   => 'Modify RDN',
	     listops        => 'List',
	     searchops      => 'Search',
	     onelevelsearchops => 'One-level search',
	     wholesubtreesearchops => 'Subtree search',
	     errors         => 'Error',
	     securityerrors => 'Security error',
	 },
	 'desc'   => 'The number of operations',
	 'vlabel' => 'ops/${graph_period}',
	 'type'   => 'DERIVE',
	 'title'  => 'Operations',
	 'info'   => 'Number of completed LDAP operations',
	 'scope'  => "base"
         },
     );

# Config subroutine
sub config {
    my $action = shift;
    if(!exists $ops{$action}) {
	die "Unknown action specified: $action";
    }
    print <<EOF;
graph_args --base 1000 -l 0
graph_vlabel $ops{$action}->{'vlabel'}
graph_title $ops{$action}->{'title'}
graph_category 389-ds
graph_info $ops{$action}->{'info'}
EOF
    
    if ($ops{$action}->{'label2'}) {
        while (my ($key, $val) = each (%{$ops{$action}->{'label2'}})) {
          my $name = $action . "_" . $key;
          print "$name.label $val\n";
          print "$name.type ",$ops{$action}->{'type'}||"DERIVE","\n";
        }
    } else {
        print "$action.label $ops{$action}->{'label'}\n";
        print "$action.type ",$ops{$action}->{'type'}||"DERIVE","\n";
        print "$action.min 0\n";
    }
}

sub autoconf {
    # Check for Net::LDAP
    if ($ret) {
	print "no ($ret)\n";
	exit 0;
    }

    # Check for LDAP version 3
    my $ldap = Net::LDAP->new ($server, version => 3)
        or do { print "no ($@)\n"; exit 0; };

    my $mesg;
    if ($userdn ne '') {
      $mesg = $ldap->bind ($userdn, password => $userpw)
        or do { print "no ($@)\n"; exit 0; };
    } else {
      $mesg = $ldap->bind
        or do { print "no ($@)\n"; exit 0; };
    }
    if ($mesg->code) {
      print "no (" . $mesg->error . ")\n";
      exit 0;
    }

    $mesg =
        $ldap->search (
                       base   => $basedn,
                       scope  => 'one',
                       filter => '(objectClass=monitorServer)',
                       attrs  => 'cn',
                       );
    if ($mesg->code) {
      print "no (" . $mesg->error . ")\n";
      exit 0;
    }
    print "yes\n";
    exit 0;
}

# Determine action based on filename first

if ($ARGV[0]) {
    if ($ARGV[0] eq 'autoconf') {
	autoconf();
    } elsif ($ARGV[0] eq "suggest") {
        print "$0\n";
    } elsif ($ARGV[0] eq "config") {
	foreach my $action (keys %ops) {
	    print "multigraph 389ds_", $action, "\n";
	    &config ($action);
	}
    }
    exit 0;
}

# Net::LDAP variant
my $ldap = Net::LDAP->new ($server, version => 3)
    or die "Failed to connect to server $server: $@";
my $mesg;
if ($userdn ne '') {
  $mesg = $ldap->bind ($userdn, password => $userpw)
      or die "Failed to bind with $userdn: $@";
} else {
  $mesg = $ldap->bind
      or die "Failed to bind anonymously: $@";
}
if ($mesg->code) {
  die "Failed to bind: " . $mesg->error;
}

foreach my $action (keys %ops) {
    print "multigraph 389ds_", $action, "\n";

    # Default scope for LDAP searches. We'll change to other scopes if
    # necessary.
    $scope = "one";

    my $searchdn = $ops{$action}->{'search'};
    my $searchattrs;

    if ($ops{$action}->{'label2'}) {
        $searchattrs = [keys %{$ops{$action}->{'label2'}}];
    } else {
        $searchattrs = [$ops{$action}->{'searchattr'} || 'monitorCounter', 'cn'];
    }

    my $filter;
    if ($ops{$action}->{'filter'}) {
      $filter = "(&(objectclass=*)" . $ops{$action}->{'filter'} . ")";
    } else {
      $filter = "(objectClass=*)";
    }

    if ($ops{$action}->{'scope'}) {
      $scope = $ops{$action}->{'scope'};
    }

    my @search = (
                       base   => $searchdn,
                       scope  => $scope,
                       filter => $filter,
                       attrs  => $searchattrs,
        );

    #use Data::Dumper; print Dumper({@search});

    $mesg =
        $ldap->search (@search);

    $mesg->code && die $mesg->error;

    my $max = $mesg->count;

    for (my $i = 0 ; $i < $max ; $i++) {
        my $entry = $mesg->entry ($i);
        my $cn = $entry->get_value('cn');
        if ($ops{$action}->{'label2'}) {
    	foreach my $attr (keys %{$ops{$action}->{'label2'}}) {
    	    print lc ("${action}_${attr}.value ");
    	    print $entry->get_value($attr), "\n";
    	}
        } else {
    	print lc ("${action}.value ");
    	print $entry->get_value($ops{$action}->{'searchattr'} || 'monitorCounter'), "\n";
        }
    }
}
$ldap->unbind;
