--- a/check_ldap_repl.pl Tue Feb 16 10:03:05 2016 +0100
+++ b/check_ldap_repl.pl Tue Apr 19 13:34:06 2016 +0200
@@ -19,21 +19,18 @@
# Matthias Förste <foerste@schlittermann.de>
use strict;
+use warnings;
+
+# required? https://rt.cpan.org/Public/Bug/Display.html?id=95875
+use threads;
+
use File::Basename;
-use Getopt::Long;
-use Config::IniFiles;
+use AppConfig;
use Net::LDAP;
-use IO::Prompt;
use File::stat;
use Pod::Usage;
use if $ENV{DEBUG} => "Smart::Comments";
-sub version($$);
-sub read_config();
-sub ldap_object($);
-sub get_stamp($$);
-sub compare_results(%);
-
my %ERRORS = (
OK => 0,
WARNING => 1,
@@ -44,264 +41,42 @@
my $ME = basename $0;
my $NAME = "LDAPREPL";
-my $VERSION = "0.3.2";
-
-my $master_default = "ldap://ldap-master:389/";
-my $slave_default = "ldap://ldap-slave:389/";
-my $cn_default = "replcheck";
-
-my %opt = (
- init => 0,
- delete => 0,
- refresh => 0,
- cn => $cn_default,
- wait => 1,
- file => "/etc/nagios/ius/plugins/config/check_ldap_repl.cfg",
- master => $master_default,
- slave => $slave_default
-);
-
-MAIN: {
- Getopt::Long::Configure('bundling');
- GetOptions(
- "i|init" => \$opt{init},
- "d|delete" => \$opt{delete},
- "r|refresh" => \$opt{refresh},
- "b|binddn=s" => \$opt{binddn},
- "p|password=s" => \$opt{password},
- "c|cn=s" => \$opt{cn},
- "w|wait=i" => \$opt{wait},
- "M|master=s" => \$opt{master},
- "S|slave=s" => \$opt{slave},
- "f|file=s" => \$opt{file},
- "h|help" => sub { pod2usage( -verbose => 1, -exitval => $ERRORS{OK} ) },
- "m|man" => sub { pod2usage( -verbose => 2, -exitval => $ERRORS{OK} ) },
- "V|version" => sub { version( $ME, $VERSION ); exit $ERRORS{OK}; }
- ) or pod2usage( -verbose => 1, -exitval => $ERRORS{CRITICAL} );
-
- # init or delete the ldap object
- if ($opt{init}) {
- ldap_object("init");
- print "new object successfully initialized\n";
- exit $ERRORS{OK};
- } elsif ($opt{delete}) {
- ldap_object("delete");
- print "object successfully deleted\n";
- exit $ERRORS{OK};
- }
-
- # refresh our ldap object
- ldap_object("refresh") if ($opt{refresh});
-
- my ($master, $slave, $cn) = undef;
- my @slaves = ();
- my %results = ();
+my $VERSION = "0.3.3";
- # preparing for the comparison of ldap entries
- if (($opt{master} ne $master_default) || ($opt{slave} ne $slave_default) || ($opt{cn} ne $cn_default)) {
- $master = $opt{master};
- @slaves = split(/,/, $opt{slave});
- $cn = $opt{cn};
- } elsif (-r $opt{file}) {
- (undef, undef, $master, $slave, $cn) = read_config();
- @slaves = split(/,/, $slave);
- } else {
- $master = $opt{master};
- @slaves = split(/,/, $opt{slave});
- $cn = $opt{cn};
- }
-
- # get the values from the ldap
- $results{$master}{'master'} = get_stamp($master, $cn);
- sleep $opt{wait};
- foreach (@slaves) {
- $results{$_}{'slave'} = get_stamp($_, $cn);
- }
-
- # compare the time stamps and generate the output
- compare_results(\%results);
-}
+my $defaults = {
+ 'init|i!' => 0,
+ 'delete|d!' => 0,
+ 'refresh|r!' => 1,
+ 'dn=s' => undef,
+ 'binddn|D=s' => undef,
+ 'password=s' => undef,
+ 'wait|w=i' => 1,
+ 'config=s' => '/etc/nagios/ius/plugins/config/check_ldap_repl.cfg',
+ 'provider|p=s' => 'ldap://provider:389',
+ 'consumer|c=s@' => 'ldap://consumer:389',
+ 'help|h!' => sub { pod2usage(-verbose => 1, -exitval => $ERRORS{OK}) },
+ 'man|m!' => sub { pod2usage(-verbose => 2, -exitval => $ERRORS{OK}) },
+ 'version|V!' => sub { version($ME, $VERSION); exit $ERRORS{OK}; }
+};
-sub compare_results(%) {
- my @output = ();
- my %stamps = ();
- my (%results) = %{$_[0]};
- for my $server ( keys %results ) {
- for my $type ( keys %{ $results{$server} } ) {
- $stamps{$results{$server}{$type}} = "";
- push @output, "$type: $server = $results{$server}{$type}";
- }
- }
+my $attr = 'description';
+
+sub critical { print STDERR "$NAME CRITICAL: ", @_; exit $ERRORS{CRITICAL}; }
+ $SIG{__DIE__} = sub { print STDERR "$NAME UNKNOWN: ", @_; exit $ERRORS{UNKNOWN}; };
+
+sub stamp {
+ my ($u, $dn) = @_;
- @output = sort(@output);
- if (scalar(keys(%stamps)) != 1 ) {
- print "$NAME CRITICAL: server are not in sync @output\n";
- exit $ERRORS{CRITICAL};
- } else {
- print "$NAME OK: servers are in sync @output\n";
- exit $ERRORS{OK};
- }
-}
+ my $l = ref $u eq 'Net::LDAP' ? $u : Net::LDAP->new($u, onerror => 'die') or die "$@";
+ my $r = $l->search(base => $dn, scope => 'base', filter => '(objectClass=*)');
+ die "unexpected result count: ", $r->count unless $r->count == 1;
+ my @v = $r->entry(0)->get_value($attr);
+ die "unexpected value count [@v]" unless @v == 1;
+ return $v[0];
-sub read_config() {
- my ($binddn, $password, $cn, $master, $slave);
- my $cfg = new Config::IniFiles( -file => "$opt{file}");
- $binddn = $cfg->val('bind', 'dn')?$cfg->val('bind', 'dn'):$opt{binddn};
- $password = $cfg->val('bind', 'password')?$cfg->val('bind', 'password'):$opt{password};
- $master = $cfg->val('master', 'server')?$cfg->val('master', 'server'):$opt{master};
- $slave = $cfg->val('slave', 'server')?$cfg->val('slave', 'server'):$opt{slave};
- $cn = $cfg->val('object', 'cn')? $cfg->val('object', 'cn'):$opt{cn};
-
- return ($binddn, $password, $master, $slave, $cn);
}
-sub ldap_object($) {
- my $type = shift;
- my ($binddn, $password) = undef;
-
- my $master = $opt{master};
- my $cn = $opt{cn};
-
- # ldap object init/delete is only allowed at the prompt
- if ( ($type eq "init") || ($type eq "delete") ) {
- $binddn = prompt('BindDN: ');
- $password = prompt('Password: ', -e => '*');
- if (($opt{master} ne $master_default) || ($opt{cn} ne $cn_default)) {
- $master = $opt{master};
- $cn = $opt{cn};
- } elsif ( -r $opt{file} ) {
- (undef, undef, $master, undef, $cn) = read_config();
- }
- } else {
- if ($opt{binddn} && $opt{password}) {
- $binddn = $opt{binddn};
- $password = $opt{password};
- } elsif ( -r $opt{file} ) {
- ($binddn, $password, undef, undef, undef) = read_config();
- } else {
- $binddn = prompt('BindDN: ');
- $password = prompt('Password: ', -e => '*');
- }
-
- if (($opt{master} ne $master_default) || ($opt{cn} ne $cn_default)) {
- $master = $opt{master};
- $cn = $opt{cn};
- } elsif ( -r $opt{file} ) {
- (undef, undef, $master, undef, $cn) = read_config();
- }
- }
-
- my $ldap = Net::LDAP->new( $master );
-
- if (!$ldap) {
- print "$NAME CRITICAL: [$master] $!\n";
- exit $ERRORS{CRITICAL};
- }
-
- my $mesg = $ldap->bind("$binddn", password => $password);
- if ($mesg->code) {
- $ldap->unbind() if ($ldap);
- print "$NAME CRITICAL: " . $mesg->error . "\n";
- exit $ERRORS{CRITICAL};
- }
-
- # get ldap naming context
- my $dse = $ldap->root_dse();
- my $context = $dse->get_value('namingContexts');
-
- if (! defined $context) {
- print "$NAME CRITICAL: can't determine ldap 'naming context'\n";
- exit $ERRORS{CRITICAL};
- }
-
- if ($mesg->code) {
- print "$NAME CRITICAL: " . $mesg->error . "\n";
- exit $ERRORS{CRITICAL};
- }
-
- # initialize check object
- $mesg = $ldap->add(
- "cn=$cn,$context",
- attr => [
- 'objectclass' => [ 'top', 'person' ],
- 'cn' => "$cn",
- 'sn' => "$cn",
- 'description' => time()
- ],
- ) if ($type eq "init");
-
- # delete check object
- $mesg = $ldap->delete("cn=$cn,$context") if ($type eq "delete");
-
- # refresh check object
- $mesg = $ldap->modify(
- "cn=$cn,$context",
- replace => {
- description => time()
- }
- ) if ($opt{refresh});
-
- if ($mesg->code && ($type eq "delete" || $type eq "init" || $type eq "refresh")) {
- print "$NAME WARNING: [ldapt] " . $mesg->error . "\n";
- exit $ERRORS{WARNING};
- }
-
- $ldap->unbind() if ($ldap);
- return 0;
-}
-
-sub get_stamp($$) {
- my ($server, $cn) = @_;
-
- my $ldap = Net::LDAP->new( $server );
- if (!$ldap) {
- print "$NAME CRITICAL: [$server] $!\n";
- exit $ERRORS{CRITICAL};
- }
-
- my $mesg = $ldap->bind();
-
- if ($mesg->code) {
- $ldap->unbind() if ($ldap);
- print "$NAME CRITICAL: " . $mesg->error . "\n";
- exit $ERRORS{CRITICAL};
- }
-
- # get ldap naming context
- my $dse = $ldap->root_dse();
- my $context = $dse->get_value('namingContexts');
-
- if (! defined $context) {
- print "$NAME CRITICAL: can't determine ldap 'naming context'\n";
- exit $ERRORS{CRITICAL};
- }
-
- $mesg = $ldap->search(
- base => "cn=replcheck,$context",
- scope => "base",
- filter => "(cn=$cn)",
- attr => [ 'description' ]
-
- );
-
- if ($mesg->code) {
- $ldap->unbind() if ($ldap);
- print "$NAME CRITICAL: " . $mesg->error . "\n";
- exit $ERRORS{CRITICAL};
- }
-
- if ($mesg->count != 1) {
- print "$NAME CRITICAL: \n";
- exit $ERRORS{CRITICAL};
- }
-
- my $entry = $mesg->entry(0);
- return $entry->get_value("description");
-
- $ldap->unbind() if ($ldap);
-}
-
-sub version($$) {
+sub version {
my ( $progname, $version ) = @_;
print <<_VERSION;
@@ -315,6 +90,41 @@
_VERSION
}
+MAIN: {
+
+ my $c = AppConfig->new( { CASE => 1 },
+ map { $_, { ref $defaults->{$_} eq 'CODE' ? 'ACTION' : 'DEFAULT' => $defaults->{$_} } } keys %{$defaults}
+ ) or die "Can't initialize";
+
+ my $cf = $c->get('config');
+ # ignore default configuration file if it does not exist
+ $c->file($cf) if -e $cf;
+
+ # read configuration file if passed on command line
+ $c->getopt(qw(no_ignore_case));
+ $c->file($cf) if $cf ne ($cf = $c->get('config'));
+ # make sure that command line options override any config file options
+ $c->getopt;
+
+ my %o = $c->varlist('.');
+ my $t = time();
+
+ my $p = Net::LDAP->new($o{provider}, onerror => 'die' ) or die $@;
+ $p->bind($o{binddn}, password => $o{password});
+ $p->modify($o{dn}, replace => { $attr => $t });
+
+ my $tp = stamp($p, $o{dn});
+ if ($o{refresh}) {
+ die "Provider update failed for unknown reason\n" unless $tp == $t;
+ sleep $o{wait};
+ }
+ for (@{$o{consumer}}) { critical "'$_' out of sync\n" unless $tp == stamp($_, $o{dn}); }
+
+ print "$NAME OK: servers are in sync\n";
+ exit $ERRORS{OK};
+
+}
+
__END__
=head1 NAME
@@ -323,10 +133,7 @@
=head1 SYNOPSIS
-check_ldap_repl [-i|--init]
- [-d|--delete]
- [-r|--refresh]
- [-c|--cn string]
+check_ldap_repl [-c|--cn string]
[-b|--binddn string]
[-p|--password string]
[-f|--file string]
@@ -341,24 +148,6 @@
=over
-=item B<-i>|B<--init>
-
-Add the check object cn=replcheck,I<namingContext> to the master server if not exists. See also the B<--cn> option.
-You will ask for an B<binddn> and B<password>, if not given B<--binddn> and B<--password> options.
-LDAP object initialisation is only allowed at the prompt. Your B<binddn> must have write permission to the ldap master server.
-
-=item B<-d>|B<--delete>
-
-Delete the check object from the ldap master server if exists. See also the B<--cn> option.
-You will ask for an B<binddn> and B<password>, if not given B<--binddn> and B<--password> options.
-LDAP object deletion is only allowed at the prompt. Your B<binddn> must have write permission to the ldap master server.
-
-=item B<-r>|B<--refresh>
-
-Refresh the stamp attribute of the check attribute with current unix time.
-You will ask for an B<binddn> and B<password>, if not given B<--binddn> and B<--password> options. See also B<--file> option.
-Your B<binddn> must have write permission to the ldap master server.
-
=item B<-c>|B<--cn> I<string>
cn for the initialized object. See also the B<--init> option. (default: replcheck)