check_ldap_repl.pl
changeset 11 5d59fd79e7f4
parent 10 dcdc1a43d887
child 12 7202e55a0713
equal deleted inserted replaced
10:dcdc1a43d887 11:5d59fd79e7f4
    17 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18 #
    18 #
    19 #    Matthias Förste <foerste@schlittermann.de>
    19 #    Matthias Förste <foerste@schlittermann.de>
    20 
    20 
    21 use strict;
    21 use strict;
       
    22 use warnings;
       
    23 
       
    24 # required? https://rt.cpan.org/Public/Bug/Display.html?id=95875
       
    25 use threads;
       
    26 
    22 use File::Basename;
    27 use File::Basename;
    23 use Getopt::Long;
    28 use AppConfig;
    24 use Config::IniFiles;
       
    25 use Net::LDAP;
    29 use Net::LDAP;
    26 use IO::Prompt;
       
    27 use File::stat;
    30 use File::stat;
    28 use Pod::Usage;
    31 use Pod::Usage;
    29 use if $ENV{DEBUG} => "Smart::Comments";
    32 use if $ENV{DEBUG} => "Smart::Comments";
    30 
       
    31 sub version($$);
       
    32 sub read_config();
       
    33 sub ldap_object($);
       
    34 sub get_stamp($$);
       
    35 sub compare_results(%);
       
    36 
    33 
    37 my %ERRORS = (
    34 my %ERRORS = (
    38 	OK        => 0,
    35 	OK        => 0,
    39 	WARNING   => 1,
    36 	WARNING   => 1,
    40 	CRITICAL  => 2,
    37 	CRITICAL  => 2,
    42 	DEPENDENT => 4
    39 	DEPENDENT => 4
    43 );
    40 );
    44 
    41 
    45 my $ME      = basename $0;
    42 my $ME      = basename $0;
    46 my $NAME    = "LDAPREPL";
    43 my $NAME    = "LDAPREPL";
    47 my $VERSION = "0.3.2";
    44 my $VERSION = "0.3.3";
    48 
    45 
    49 my $master_default = "ldap://ldap-master:389/";
    46 my $defaults = {
    50 my $slave_default = "ldap://ldap-slave:389/";
    47     'init|i!'       => 0,
    51 my $cn_default = "replcheck";
    48     'delete|d!'     => 0,
    52 
    49     'refresh|r!'    => 1,
    53 my %opt = (
    50     'dn=s'          => undef,
    54 	init	=> 0,
    51     'binddn|D=s'    => undef,
    55 	delete  => 0,
    52     'password=s'    => undef,
    56 	refresh => 0,
    53     'wait|w=i'      => 1,
    57 	cn      => $cn_default,
    54     'config=s'      => '/etc/nagios/ius/plugins/config/check_ldap_repl.cfg',
    58 	wait    => 1,
    55     'provider|p=s'  => 'ldap://provider:389',
    59 	file    => "/etc/nagios/ius/plugins/config/check_ldap_repl.cfg",
    56     'consumer|c=s@' => 'ldap://consumer:389',
    60 	master	=> $master_default,
    57     'help|h!'       => sub { pod2usage(-verbose => 1, -exitval => $ERRORS{OK}) },
    61 	slave	=> $slave_default
    58     'man|m!'        => sub { pod2usage(-verbose => 2, -exitval => $ERRORS{OK}) },
    62 );
    59     'version|V!'    => sub { version($ME, $VERSION); exit $ERRORS{OK}; }
    63 
    60 };
    64 MAIN: {
    61 
    65 	Getopt::Long::Configure('bundling');
    62 my $attr = 'description';
    66 	GetOptions(
    63 
    67 		"i|init"       => \$opt{init},
    64 sub critical { print STDERR "$NAME CRITICAL: ", @_; exit $ERRORS{CRITICAL}; }
    68 		"d|delete"     => \$opt{delete},
    65  $SIG{__DIE__} = sub { print STDERR "$NAME UNKNOWN: ", @_; exit $ERRORS{UNKNOWN}; };
    69 		"r|refresh"    => \$opt{refresh},
    66 
    70 		"b|binddn=s"   => \$opt{binddn},
    67 sub stamp {
    71 		"p|password=s" => \$opt{password},
    68     my ($u, $dn) = @_;
    72 		"c|cn=s"       => \$opt{cn},
    69 
    73 		"w|wait=i"     => \$opt{wait},
    70     my $l = ref $u eq 'Net::LDAP' ? $u : Net::LDAP->new($u, onerror => 'die') or die "$@";
    74 		"M|master=s"   => \$opt{master},
    71     my $r = $l->search(base => $dn, scope => 'base', filter => '(objectClass=*)');
    75 		"S|slave=s"    => \$opt{slave},
    72     die "unexpected result count: ", $r->count unless $r->count == 1;
    76 		"f|file=s"     => \$opt{file},
    73     my @v = $r->entry(0)->get_value($attr);
    77 		"h|help"       => sub { pod2usage( -verbose => 1, -exitval => $ERRORS{OK} ) },
    74     die "unexpected value count [@v]" unless @v == 1;
    78 		"m|man"        => sub { pod2usage( -verbose => 2, -exitval => $ERRORS{OK} ) },
    75     return $v[0];
    79 		"V|version"    => sub { version( $ME, $VERSION ); exit $ERRORS{OK}; }
    76 
    80 	) or pod2usage( -verbose => 1, -exitval => $ERRORS{CRITICAL} );
       
    81 
       
    82 	# init or delete the ldap object
       
    83 	if ($opt{init}) {
       
    84 		ldap_object("init");
       
    85 		print "new object successfully initialized\n";
       
    86 		exit $ERRORS{OK};
       
    87 	} elsif ($opt{delete}) {
       
    88 		ldap_object("delete");
       
    89 		print "object successfully deleted\n";
       
    90 		exit $ERRORS{OK};
       
    91 	}
       
    92 
       
    93 	# refresh our ldap object
       
    94 	ldap_object("refresh") if ($opt{refresh});
       
    95 
       
    96 	my ($master, $slave, $cn) = undef;
       
    97 	my @slaves = ();
       
    98 	my %results = ();
       
    99 
       
   100 	# preparing for the comparison of ldap entries
       
   101     if (($opt{master} ne $master_default) || ($opt{slave} ne $slave_default) || ($opt{cn} ne $cn_default)) {
       
   102         $master = $opt{master};
       
   103         @slaves = split(/,/, $opt{slave});
       
   104         $cn = $opt{cn};
       
   105     } elsif (-r $opt{file}) {
       
   106         (undef, undef, $master, $slave, $cn) = read_config();
       
   107         @slaves = split(/,/, $slave);
       
   108     } else {
       
   109         $master = $opt{master};
       
   110         @slaves = split(/,/, $opt{slave});
       
   111         $cn = $opt{cn};
       
   112     }
       
   113 
       
   114 	# get the values from the ldap
       
   115 	$results{$master}{'master'} = get_stamp($master, $cn);
       
   116 	sleep $opt{wait};
       
   117 	foreach (@slaves) {
       
   118 		$results{$_}{'slave'} = get_stamp($_, $cn);
       
   119 	}
       
   120 
       
   121 	# compare the time stamps and generate the output
       
   122 	compare_results(\%results);
       
   123 }
    77 }
   124 
    78 
   125 sub compare_results(%) {
    79 sub version {
   126 	my @output = ();
       
   127 	my %stamps = ();
       
   128 	my (%results) = %{$_[0]};
       
   129 	for my $server ( keys %results ) {
       
   130 		for my $type ( keys %{ $results{$server} } ) {
       
   131 			$stamps{$results{$server}{$type}} = "";
       
   132 			push @output, "$type: $server = $results{$server}{$type}";
       
   133 		}
       
   134 	}
       
   135 
       
   136 	@output = sort(@output);
       
   137 	if (scalar(keys(%stamps)) != 1 ) {
       
   138 		print "$NAME CRITICAL: server are not in sync @output\n";
       
   139 		exit $ERRORS{CRITICAL};
       
   140 	} else {
       
   141 		print "$NAME OK: servers are in sync @output\n";
       
   142 		exit $ERRORS{OK};
       
   143 	}
       
   144 }
       
   145 
       
   146 sub read_config() {
       
   147 	my ($binddn, $password, $cn, $master, $slave);
       
   148 	my $cfg = new Config::IniFiles( -file => "$opt{file}");
       
   149 	$binddn = $cfg->val('bind', 'dn')?$cfg->val('bind', 'dn'):$opt{binddn};
       
   150 	$password = $cfg->val('bind', 'password')?$cfg->val('bind', 'password'):$opt{password};
       
   151 	$master = $cfg->val('master', 'server')?$cfg->val('master', 'server'):$opt{master};
       
   152 	$slave = $cfg->val('slave', 'server')?$cfg->val('slave', 'server'):$opt{slave};
       
   153 	$cn = $cfg->val('object', 'cn')? $cfg->val('object', 'cn'):$opt{cn};
       
   154 
       
   155 	return ($binddn, $password, $master, $slave, $cn);
       
   156 }
       
   157 
       
   158 sub ldap_object($) {
       
   159 	my $type = shift;
       
   160 	my ($binddn, $password) = undef;
       
   161 
       
   162 	my $master = $opt{master};
       
   163 	my $cn = $opt{cn};
       
   164 
       
   165 	# ldap object init/delete is only allowed at the prompt
       
   166 	if ( ($type eq "init") || ($type eq "delete") ) {
       
   167 		$binddn = prompt('BindDN: ');
       
   168 		$password = prompt('Password: ', -e => '*');
       
   169 		if (($opt{master} ne $master_default) || ($opt{cn} ne $cn_default)) {
       
   170 			$master = $opt{master};
       
   171 			$cn = $opt{cn};
       
   172 		} elsif ( -r $opt{file} ) {
       
   173 			(undef, undef, $master, undef, $cn) = read_config();
       
   174 		}
       
   175 	} else {
       
   176 		if ($opt{binddn} && $opt{password}) {
       
   177 			$binddn = $opt{binddn};
       
   178 			$password = $opt{password};
       
   179 		} elsif ( -r $opt{file} ) {
       
   180 			($binddn, $password, undef, undef, undef) = read_config();
       
   181 		} else {
       
   182 			$binddn = prompt('BindDN: ');
       
   183 			$password = prompt('Password: ', -e => '*');
       
   184 		}
       
   185 
       
   186 		if (($opt{master} ne $master_default) || ($opt{cn} ne $cn_default)) {
       
   187 			$master = $opt{master};
       
   188 			$cn = $opt{cn};
       
   189 		} elsif ( -r $opt{file} ) {
       
   190 			(undef, undef, $master, undef, $cn) = read_config();
       
   191 		}
       
   192 	}
       
   193 	
       
   194 	my $ldap = Net::LDAP->new( $master );
       
   195 
       
   196 	if (!$ldap) {
       
   197 		print "$NAME CRITICAL: [$master] $!\n";
       
   198 		exit $ERRORS{CRITICAL};
       
   199 	}
       
   200 
       
   201 	my $mesg = $ldap->bind("$binddn", password => $password);
       
   202 	if ($mesg->code) {
       
   203 		$ldap->unbind() if ($ldap);
       
   204 		print "$NAME CRITICAL: " . $mesg->error . "\n";
       
   205 		exit $ERRORS{CRITICAL};
       
   206 	}
       
   207 
       
   208 	# get ldap naming context
       
   209 	my $dse = $ldap->root_dse();
       
   210 	my $context = $dse->get_value('namingContexts');
       
   211 
       
   212 	if (! defined $context) {
       
   213 		print "$NAME CRITICAL: can't determine ldap 'naming context'\n";
       
   214 		exit $ERRORS{CRITICAL};
       
   215 	}
       
   216 
       
   217 	if ($mesg->code) {
       
   218 		print "$NAME CRITICAL: " . $mesg->error . "\n";
       
   219 		exit $ERRORS{CRITICAL};
       
   220 	}
       
   221 
       
   222 	# initialize check object
       
   223 	$mesg = $ldap->add(
       
   224 		"cn=$cn,$context",
       
   225 		attr => [
       
   226 			'objectclass' => [ 'top', 'person' ],
       
   227 			'cn' => "$cn",
       
   228 			'sn' => "$cn",
       
   229 			'description' => time()
       
   230 			],
       
   231 	) if ($type eq "init");
       
   232 
       
   233 	# delete check object
       
   234 	$mesg = $ldap->delete("cn=$cn,$context") if ($type eq "delete");
       
   235 
       
   236 	# refresh check object
       
   237 	$mesg = $ldap->modify(
       
   238 		"cn=$cn,$context",
       
   239 		replace => {
       
   240 			description => time()
       
   241 		}
       
   242 	) if ($opt{refresh});
       
   243 
       
   244 	if ($mesg->code && ($type eq "delete" || $type eq "init" || $type eq "refresh")) {
       
   245 		print "$NAME WARNING: [ldapt] " . $mesg->error . "\n";
       
   246 		exit $ERRORS{WARNING};
       
   247 	}
       
   248 
       
   249 	$ldap->unbind() if ($ldap);
       
   250 	return 0;
       
   251 }
       
   252 
       
   253 sub get_stamp($$) {
       
   254 	my ($server, $cn) = @_;
       
   255 
       
   256 	my $ldap = Net::LDAP->new( $server );
       
   257 	if (!$ldap) {
       
   258 		print "$NAME CRITICAL: [$server] $!\n";
       
   259 		exit $ERRORS{CRITICAL};
       
   260 	}
       
   261 
       
   262 	my $mesg = $ldap->bind();
       
   263 
       
   264 	if ($mesg->code) {
       
   265 		$ldap->unbind() if ($ldap);
       
   266 		print "$NAME CRITICAL: " . $mesg->error . "\n";
       
   267 		exit $ERRORS{CRITICAL};
       
   268 	}
       
   269 
       
   270 	# get ldap naming context
       
   271 	my $dse = $ldap->root_dse();
       
   272 	my $context = $dse->get_value('namingContexts');
       
   273 
       
   274 	if (! defined $context) {
       
   275 		print "$NAME CRITICAL: can't determine ldap 'naming context'\n";
       
   276 		exit $ERRORS{CRITICAL};
       
   277 	}
       
   278 
       
   279 	$mesg = $ldap->search(
       
   280 		base => "cn=replcheck,$context",
       
   281 		scope => "base",
       
   282 		filter => "(cn=$cn)",
       
   283 		attr => [ 'description' ]
       
   284 
       
   285 	);
       
   286 
       
   287 	if ($mesg->code) {
       
   288 		$ldap->unbind() if ($ldap);
       
   289 		print "$NAME CRITICAL: " . $mesg->error . "\n";
       
   290 		exit $ERRORS{CRITICAL};
       
   291 	}
       
   292 
       
   293 	if ($mesg->count != 1) {
       
   294 		print "$NAME CRITICAL: \n";
       
   295 		exit $ERRORS{CRITICAL};
       
   296 	}
       
   297 
       
   298 	my $entry = $mesg->entry(0);
       
   299 	return $entry->get_value("description");
       
   300 
       
   301 	$ldap->unbind() if ($ldap);
       
   302 }
       
   303 
       
   304 sub version($$) {
       
   305 	my ( $progname, $version ) = @_;
    80 	my ( $progname, $version ) = @_;
   306 	
    81 	
   307 	print <<_VERSION;
    82 	print <<_VERSION;
   308 $progname version $version
    83 $progname version $version
   309 Copyright (C) 2012 by Christian Arnold and Schlittermann internet & unix support.
    84 Copyright (C) 2012 by Christian Arnold and Schlittermann internet & unix support.
   313 and you are welcome to redistribute it under certain conditions.
    88 and you are welcome to redistribute it under certain conditions.
   314 See the GNU General Public Licence for details.
    89 See the GNU General Public Licence for details.
   315 _VERSION
    90 _VERSION
   316 }
    91 }
   317 
    92 
       
    93 MAIN: {
       
    94 
       
    95     my $c = AppConfig->new( { CASE => 1 },
       
    96         map { $_, { ref $defaults->{$_} eq 'CODE' ? 'ACTION' : 'DEFAULT' => $defaults->{$_} } } keys %{$defaults}
       
    97     ) or die "Can't initialize";
       
    98 
       
    99     my $cf = $c->get('config');
       
   100     # ignore default configuration file if it does not exist
       
   101     $c->file($cf) if -e $cf;
       
   102 
       
   103     # read configuration file if passed on command line
       
   104     $c->getopt(qw(no_ignore_case));
       
   105     $c->file($cf) if $cf ne ($cf = $c->get('config'));
       
   106     # make sure that command line options override any config file options
       
   107     $c->getopt;
       
   108 
       
   109     my %o = $c->varlist('.');
       
   110     my $t = time();
       
   111 
       
   112     my $p = Net::LDAP->new($o{provider}, onerror => 'die' ) or die $@;
       
   113     $p->bind($o{binddn}, password => $o{password});
       
   114     $p->modify($o{dn}, replace => { $attr => $t });
       
   115 
       
   116     my $tp = stamp($p, $o{dn});
       
   117     if ($o{refresh}) {
       
   118         die "Provider update failed for unknown reason\n" unless $tp == $t;
       
   119         sleep $o{wait};
       
   120     }
       
   121     for (@{$o{consumer}}) { critical "'$_' out of sync\n" unless $tp == stamp($_, $o{dn}); }
       
   122 
       
   123     print "$NAME OK: servers are in sync\n";
       
   124     exit $ERRORS{OK};
       
   125 
       
   126 }
       
   127 
   318 __END__
   128 __END__
   319 
   129 
   320 =head1 NAME
   130 =head1 NAME
   321 
   131 
   322 check_ldap_repl - nagios/icinga plugin to check correctly working of ldap replication.
   132 check_ldap_repl - nagios/icinga plugin to check correctly working of ldap replication.
   323 
   133 
   324 =head1 SYNOPSIS
   134 =head1 SYNOPSIS
   325 
   135 
   326 check_ldap_repl [-i|--init]
   136 check_ldap_repl [-c|--cn string]
   327                 [-d|--delete]
       
   328                 [-r|--refresh]
       
   329                 [-c|--cn string]
       
   330                 [-b|--binddn string]
   137                 [-b|--binddn string]
   331                 [-p|--password string]
   138                 [-p|--password string]
   332                 [-f|--file string]
   139                 [-f|--file string]
   333                 [-M|--master string]
   140                 [-M|--master string]
   334                 [-S|--slave string]
   141                 [-S|--slave string]
   339 
   146 
   340 =head1 OPTIONS
   147 =head1 OPTIONS
   341 
   148 
   342 =over
   149 =over
   343 
   150 
   344 =item B<-i>|B<--init>
       
   345 
       
   346 Add the check object cn=replcheck,I<namingContext> to the master server if not exists. See also the B<--cn> option.
       
   347 You will ask for an B<binddn> and B<password>, if not given B<--binddn> and B<--password> options.
       
   348 LDAP object initialisation is only allowed at the prompt. Your B<binddn> must have write permission to the ldap master server.
       
   349 
       
   350 =item B<-d>|B<--delete>
       
   351 
       
   352 Delete the check object from the ldap master server if exists. See also the B<--cn> option.
       
   353 You will ask for an B<binddn> and B<password>, if not given B<--binddn> and B<--password> options.
       
   354 LDAP object deletion is only allowed at the prompt. Your B<binddn> must have write permission to the ldap master server.
       
   355 
       
   356 =item B<-r>|B<--refresh>
       
   357 
       
   358 Refresh the stamp attribute of the check attribute with current unix time.
       
   359 You will ask for an B<binddn> and B<password>, if not given B<--binddn> and B<--password> options. See also B<--file> option.
       
   360 Your B<binddn> must have write permission to the ldap master server.
       
   361 
       
   362 =item B<-c>|B<--cn> I<string>
   151 =item B<-c>|B<--cn> I<string>
   363 
   152 
   364 cn for the initialized object. See also the B<--init> option. (default: replcheck)
   153 cn for the initialized object. See also the B<--init> option. (default: replcheck)
   365 
   154 
   366 =item B<-b>|B<--binddn> I<string>
   155 =item B<-b>|B<--binddn> I<string>