#! /usr/bin/perl -w #========================================================================== # Summary #========================================================================== # Check OpenLDAP Syncrepl status # # To know if a slave and a master are in sync, get the ContextCSN # and compare them # # Copyright (C) 2007 Clement OUDOT # Copyright (C) 2009 LTB-project.org # #========================================================================== # License: GPLv2+ #========================================================================== # 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; either version 2 # of the License, or (at your option) any later version. # # 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. # # GPL License: http://www.gnu.org/licenses/gpl.txt #========================================================================== #========================================================================== # Version #========================================================================== my $VERSION = '0.8'; my $TEMPLATE_VERSION = '1.0.0'; #========================================================================== # Modules #========================================================================== use strict; use lib qw(/usr/local/nagios/libexec /usr/lib/nagios/plugins /usr/lib64/nagios/plugins); use utils qw /$TIMEOUT %ERRORS &print_revision &support/; use Getopt::Long; &Getopt::Long::config('bundling'); use File::Basename; use POSIX; use Net::LDAP; use Time::Piece; #========================================================================== # Options #========================================================================== my $progname = basename($0); my $help; my $version; my $verbose = 0; my $host; my $warning; my $critical; my $logname; my $authentication; my $log_file; my $perf_data; my $name; my $port; my $timeout = $TIMEOUT; my $regexp; my $eregexp; my $exclude; my $minute = 60; my $url; # For SNMP plugins my $snmp_version; my $snmp_v1; my $snmp_v2c; my $snmp_v3; my $community; my $seclevel; my $authproto; my $privpasswd; my $oid; # For LDAP plugins my $ldap_binddn; my $ldap_bindpw; my $ldap_binduri; my $slave_ldap_suffix; my $master_ldap_suffix; my $ldap_serverid; my $ldap_singlemaster; GetOptions( 'h' => \$help, 'help' => \$help, 'V' => \$version, 'version' => \$version, 'v+' => \$verbose, 'verbose+' => \$verbose, 'H:s' => \$host, 'host:s' => \$host, 'w:f' => \$warning, 'warning:f' => \$warning, 'c:f' => \$critical, 'critical:f' => \$critical, #'l:s'=> \$logname,'logname:s'=> \$logname, #'a:s'=> \$authentication, #'authentication:s'=> \$authentication, #'F:s'=> \$log_file,'log_file:s'=> \$log_file, 'f' => \$perf_data, 'perf_data' => \$perf_data, 'n:s' => \$name, 'name:s' => \$name, 'p:i' => \$port, 'port:i' => \$port, 't:i' => \$timeout, 'timeout:i' => \$timeout, #'r:s'=> \$regexp,'regexp:s'=> \$regexp, #'e:s'=> \$exclude,'exclude:s'=> \$exclude, #'m:s'=> \$minute,'minute:s'=> \$minute, #'u:s'=> \$url,'url:s'=> \$url, # For SNMP plugins #'snmp_version:s'=> \$snmp_version,'1'=> \$snmp_v1, #'2'=> \$snmp_v2c,'3'=> \$snmp_v3, #'C:s'=> \$community,'community:s'=> \$community, #'L:s'=> \$seclevel,'seclevel:s'=> \$seclevel, #'A:s'=> \$authproto,'authproto:s'=> \$authproto, #'X:s'=> \$privpasswd,'privpasswd:s'=> \$privpasswd, #'o:s'=> \$oid,'oid:s'=> \$oid, # For LDAP plugins 'D:s' => \$ldap_binddn, 'binddn:s' => \$ldap_binddn, 'P:s' => \$ldap_bindpw, 'bindpw:s' => \$ldap_bindpw, 'U:s' => \$ldap_binduri, 'binduri:s' => \$ldap_binduri, 'S:s' => \$slave_ldap_suffix, 'suffix:s' => \$slave_ldap_suffix, 'M:s' => \$master_ldap_suffix, 'mastersuffix:s' => \$master_ldap_suffix, 'I:s' => \$ldap_serverid, 'serverid:s' => \$ldap_serverid, 's' => \$ldap_singlemaster, 'singlemaster' => \$ldap_singlemaster, ); # Fix SMNP Version unless ($snmp_version) { $snmp_version = "1" if $snmp_v1; $snmp_version = "2c" if $snmp_v2c; $snmp_version = "3" if $snmp_v3; } #========================================================================== # Usage #========================================================================== sub print_usage { print "Usage: \n"; print "$progname -H [-h] [-v] [-V]\n\n"; print "Use option --help for more information\n\n"; print "$progname comes with ABSOLUTELY NO WARRANTY\n\n"; } #========================================================================= # Version #========================================================================= if ($version) { &print_revision( $progname, "\$Revision: $VERSION (TPL: $TEMPLATE_VERSION)\$" ); exit $ERRORS{'UNKNOWN'}; } #========================================================================= # Help #========================================================================= if ($help) { &print_revision( $progname, "\$Revision: $VERSION\$" ); print "\n\nConnect to master and slave directories,\nsearch for ContextCSN and check if they are in sync.\n\n"; &print_usage; print "-v, --verbose\n"; print "\tPrint extra debugging information.\n"; print "-V, --version\n"; print "\tPrint version and exit.\n"; print "-h, --help\n"; print "\tPrint this help message and exit.\n"; print "-H, --host=STRING\n"; print "\tIP or name (FQDN) of the slave directory. You can use URI (ldap://, ldaps://, ldap+tls://)\n"; print "-p, --port=INTEGER\n"; print "\tSlave directory port to connect to.\n"; print "-w, --warning=DOUBLE\n"; print "\t Level to return a warning status.\n"; print "-c, --critical=DOUBLE\n"; print "\tLevel to return a critical status.\n"; #print "-l, --logname=STRING\n"; #print "\tUser id for login.\n"; #print "-a, --authentication=STRING\n"; #print "\tSSH private key file.\n"; #print "-F, --log_file=STRING\n"; #print "\tStatus or log file.\n"; print "-f, --perf_data\n"; print "\tDisplay performance data.\n"; #print "-n, --name=STRING\n"; #print "\tName (database, table, service, ...).\n"; print "-t, --timeout=INTEGER\n"; print "\tSeconds before connection times out (default: $TIMEOUT).\n"; #print "-r, --regexp=STRING\n"; #print "\tCase sensitive regular expression.\n"; #print "-e, --exclude=STRING\n"; #print "\nExclude this regular expression.\n"; #print "-u, --url=STRING\n"; #print "\tURL.\n"; #print "-1, -2, -3, --snmp_version=(1|2c|3)\n"; #print "\tSNMP protocol version.\n"; #print "-C, --community=STRING\n"; #print "\tSNMP community (v1 and v2c).\n"; #print "-L, --seclevel=(noAuthNoPriv|authNoPriv|authPriv))\n"; #print "\tSNMP security level (v3).\n"; #print "-A, --authproto=(MD5|SHA)\n"; #print "\tSNMP authentication protocol (v3).\n"; #print "-X, --privpasswd=STRING\n"; #print "\tSNMP cipher password (v3).\n"; #print "-o, --oid=STRING\n"; #print "\tSNMP OID.\n"; print "-D, --binddn=STRING\n"; print "\tBind DN to master and slave directories. Bind anonymous if not present.\n"; print "-P, --bindpw=STRING\n"; print "\tBind passwd to master and slave directories. Need the Bind DN option to work.\n"; print "-U, --binduri=STRING\n"; print "\tBind URI (ldap://, ldaps://, ldap+tls://) of the master directory. Retrieve this value in cn=monitor if not present.\n"; print "-S, --suffix=STRING\n"; print "-M, --mastersuffix=STRING\n"; print "\tSuffix of the directories. Retrieve this value in RootDSE if not present.\n"; print "-I, --serverid=STRING\n"; print "\tSID of the syncrepl link\n"; print "-s, --singlemaster\n"; print "\tClassic master-slave. No multi-mastering\n"; print "\n"; &support; exit $ERRORS{'UNKNOWN'}; } #========================================================================= # Functions #========================================================================= # DEBUG function sub verbose { my $output_code = shift; my $text = shift; if ( $verbose >= $output_code ) { printf "VERBOSE $output_code ===> %s\n", $text; } } # check if -H is used sub check_host_param { if ( !defined($host) ) { printf "UNKNOWN: you have to define a hostname.\n"; &print_usage; exit $ERRORS{UNKNOWN}; } } # check if -w is used sub check_warning_param { if ( !defined($warning) ) { printf "UNKNOWN: you have to define a warning threshold.\n"; &print_usage; exit $ERRORS{UNKNOWN}; } } # check if -c is used sub check_critical_param { if ( !defined($critical) ) { printf "UNKNOWN: you have to define a critical threshold.\n"; &print_usage; exit $ERRORS{UNKNOWN}; } } # Parse CSN # See http://www.openldap.org/faq/index.cgi?_highlightWords=csn&file=1145 sub parse_csn { &verbose( '3', "Enter &parse_csn" ); my ($csn) = @_; my ( $utime, $mtime, $count, $sid, $mod ) = ( $csn =~ m/(\d{14})\.?(\d{6})?Z#(\w{6})#(\w{2,3})#(\w{6})/g ); &verbose( '2', "Parse $csn into $utime - $count - $sid - $mod" ); &verbose( '3', "Leave &parse_csn" ); return ( $utime, $count, $sid, $mod ); } # Bind to LDAP server sub get_ldapconn { &verbose( '3', "Enter &get_ldapconn" ); my ( $server, $binddn, $bindpw ) = @_; my ( $useTls, $tlsParam ); # Manage ldap+tls:// URI if ( $server =~ m{^ldap\+tls://([^/]+)/?\??(.*)$} ) { $useTls = 1; $server = $1; $tlsParam = $2 || ""; } else { $useTls = 0; } my $ldap = Net::LDAP->new( $server, timeout => $timeout ); return ('1') unless ($ldap); &verbose( '2', "Connected to $server" ); if ($useTls) { my %h = split( /[&=]/, $tlsParam ); my $message = $ldap->start_tls(%h); $message->code && &verbose( '1', $message->error ) && return ( $message->code, $message->error ); &verbose( '2', "startTLS success on $server" ); } if ( $binddn && $bindpw ) { # Bind witch credentials my $req_bind = $ldap->bind( $binddn, password => $bindpw ); $req_bind->code && &verbose( '1', $req_bind->error ) && return ( $req_bind->code, $req_bind->error ); &verbose( '2', "Bind with $binddn" ); } else { my $req_bind = $ldap->bind(); $req_bind->code && &verbose( '1', $req_bind->error ) && return ( $req_bind->code, $req_bind->error ); &verbose( '2', "Bind anonymous" ); } &verbose( '3', "Leave &get_ldapconn" ); return ( '0', $ldap ); } # Get the master URI from cn=monitor sub get_masteruri { &verbose( '3', "Enter &get_masteruri" ); my ($ldapconn) = @_; my $result; my $message; my $entry; $message = $ldapconn->search( base => 'cn=monitor', scope => 'sub', filter => '(&(namingContexts=*)(MonitorUpdateRef=*))', attrs => [ 'monitorupdateref', 'namingContexts' ] ); $message->code && &verbose( '1', $message->error ) && return ( $message->code, $message->error ); $entry = $message->entry(0); return ( 1, "No data" ) unless $entry; &verbose( '2', "Found Master URI: " . $entry->get_value('monitorupdateref') ); &verbose( '3', "Leave &get_masteruri" ); return ( 0, $entry->get_value('monitorupdateref') ); } # Get the suffix from RootDSE sub get_suffix { &verbose( '3', "Enter &get_suffix" ); # Return the first namingContext of the RootDSE my ($ldapconn) = @_; my $result; my $message; my $entry; $message = $ldapconn->search( base => '', scope => 'base', filter => '(objectClass=*)', attrs => ['namingcontexts'] ); $message->code && &verbose( '1', $message->error ) && return ( $message->code, $message->error ); $entry = $message->entry(0); return ( 1, "No data" ) unless $entry; &verbose( '2', "Found suffix: " . $entry->get_value('namingcontexts') ); &verbose( '3', "Leave &get_suffix" ); return ( 0, $entry->get_value('namingcontexts') ); } # Get the ContextCSN sub get_contextcsn { &verbose( '3', "Enter &get_contextCSN" ); my ( $ldapconn, $base, $serverid ) = @_; my $result; my $message; my $entry; my $contextcsn; $message = $ldapconn->search( base => $base, scope => 'base', filter => '(objectclass=*)', attrs => ['contextCSN'] ); $message->code && &verbose( '1', $message->error ) && return ( $message->code, $message->error ); $entry = $message->entry(0); return ( 1, "No data" ) unless $entry; # Get values foreach ( $entry->get_value('contextCSN') ) { &verbose( '2', "Found ContextCSN: " . $_ ); # Keep only ContextCSN with SID my @csn = &parse_csn($_); if ( !$ldap_singlemaster ) { if ( $serverid eq $csn[2] ) { $contextcsn = $_; &verbose( '2', "ContextCSN match with SID $serverid: " . $contextcsn ); last; } } else { $contextcsn = $_; &verbose( '2', "ContextCSN match with SID $serverid: " . $contextcsn ); } } unless ($contextcsn) { &verbose( '2', "Found no ContextCSN with SID $serverid" ); return ( '1', "No data" ); } &verbose( '3', "Leave &get_contextCSN" ); return ( 0, $contextcsn ); } #========================================================================= # Main #========================================================================= # Options checks &check_host_param(); &check_warning_param(); &check_critical_param(); my $errorcode; # Set SID to 000 if not defined $ldap_serverid ||= "000"; # Connect to the slave # If $host is an URI, use it directly my $slave_uri; if ( $host =~ m#ldap(\+tls)?(s)?://.*# ) { $slave_uri = $host; $slave_uri .= ":$port" if ( $port and $host !~ m#:(\d)+# ); } else { $slave_uri = "ldap://$host"; $slave_uri .= ":$port" if $port; } my $ldap_slave; ( $errorcode, $ldap_slave ) = &get_ldapconn( $slave_uri, $ldap_binddn, $ldap_bindpw ); if ($errorcode) { print "Can't connect to $slave_uri.\n"; exit $ERRORS{'CRITICAL'}; } # Get the suffix if not provided if ( !$slave_ldap_suffix ) { ( $errorcode, $slave_ldap_suffix ) = &get_suffix($ldap_slave); if ($errorcode) { print "Can't get suffix from $slave_uri. Please provide it with -S option.\n"; exit $ERRORS{'UNKNOWN'}; } } # Get the master URI if not provided my $master_uri = $ldap_binduri; if ( !$master_uri ) { ( $errorcode, $master_uri ) = &get_masteruri($ldap_slave); if ($errorcode) { print "Can't get Master URI from $slave_uri. Please provide it with -U option.\n"; exit $ERRORS{'UNKNOWN'}; } } # Connect to the master my $ldap_master; ( $errorcode, $ldap_master ) = &get_ldapconn( $master_uri, $ldap_binddn, $ldap_bindpw ); if ($errorcode) { print "Can't connect to $master_uri.\n"; exit $ERRORS{'CRITICAL'}; } # Get the suffix if not provided if ( !$master_ldap_suffix ) { ( $errorcode, $master_ldap_suffix ) = &get_suffix($ldap_master); if ($errorcode) { print "Can't get suffix from $master_uri. Please provide it with -M option.\n"; exit $ERRORS{'UNKNOWN'}; } } # Get the contextCSN my $slavecsn; my $mastercsn; ( $errorcode, $slavecsn ) = &get_contextcsn( $ldap_slave, $slave_ldap_suffix, $ldap_serverid ); if ($errorcode) { print "Can't get Context CSN with SID $ldap_serverid from $slave_uri. Please set SID with -I option.\n"; exit $ERRORS{'UNKNOWN'}; } ( $errorcode, $mastercsn ) = &get_contextcsn( $ldap_master, $master_ldap_suffix, $ldap_serverid ); if ($errorcode) { print "Can't get Context CSN with SID $ldap_serverid from $master_uri. Please set SID with -I option.\n"; exit $ERRORS{'UNKNOWN'}; } # Compare the utime in CSN my @slavecsn_elts = &parse_csn($slavecsn); my @mastercsn_elts = &parse_csn($mastercsn); my $time_master = Time::Piece->strptime( $mastercsn_elts[0], "%Y%m%d%H%M%S" ); my $time_slave = Time::Piece->strptime( $slavecsn_elts[0], "%Y%m%d%H%M%S" ); &verbose( '2', "Master times: $time_master" ); &verbose( '2', "Slave times: $time_slave" ); my $utime_master = $time_master->epoch; my $utime_slave = $time_slave->epoch; &verbose( '2', "Master timestamp: $utime_master" ); &verbose( '2', "Slave timestamp: $utime_slave" ); my $deltacsn = abs( $utime_master - $utime_slave ); #========================================================================== # Exit with Nagios codes #========================================================================== # Prepare PerfParse data my $perfparse = " "; if ($perf_data) { $perfparse = "|'deltatime'=" . $deltacsn . "s;$warning;$critical"; } # Test the delta and exit if ( $deltacsn == 0 ) { print "OK - directories are in sync (W:$warning - C:$critical)$perfparse"; exit $ERRORS{'OK'}; } else { if ( $deltacsn < $warning ) { print "OK - directories are not in sync - $deltacsn seconds late (W:$warning - C:$critical)$perfparse"; exit $ERRORS{'OK'}; } elsif ( $deltacsn > $warning and $deltacsn < $critical ) { print "WARNING - directories are not in sync - $deltacsn seconds late (W:$warning - C:$critical)$perfparse"; exit $ERRORS{'WARNING'}; } else { print "CRITICAL - directories are not in sync - $deltacsn seconds late (W:$warning - C:$critical)$perfparse"; exit $ERRORS{'CRITICAL'}; } } exit $ERRORS{'UNKNOWN'};