# HG changeset patch # User Matthias Förste # Date 1461065646 -7200 # Node ID 5d59fd79e7f4e59c44468ef7e799c893da1b381d # Parent dcdc1a43d887462b3058e54ac34aae9745dc8be0 vereinfacht und flexibilisiert weil der zu prüfende "cn" nicht immer an der Wurzel des DIT liegen kann diff -r dcdc1a43d887 -r 5d59fd79e7f4 TODO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TODO Tue Apr 19 13:34:06 2016 +0200 @@ -0,0 +1,2 @@ +* cn ist misnomer (kann ja alles mögliche sein) +* doku aktualisieren diff -r dcdc1a43d887 -r 5d59fd79e7f4 check_ldap_repl.pl --- 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 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 to the master server if not exists. See also the B<--cn> option. -You will ask for an B and B, if not given B<--binddn> and B<--password> options. -LDAP object initialisation is only allowed at the prompt. Your B 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 and B, if not given B<--binddn> and B<--password> options. -LDAP object deletion is only allowed at the prompt. Your B 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 and B, if not given B<--binddn> and B<--password> options. See also B<--file> option. -Your B must have write permission to the ldap master server. - =item B<-c>|B<--cn> I cn for the initialized object. See also the B<--init> option. (default: replcheck)