Playbooks to a new Lilik
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

597 lines
18 KiB

  1. #! /usr/bin/perl -w
  2. #==========================================================================
  3. # Summary
  4. #==========================================================================
  5. # Check OpenLDAP Syncrepl status
  6. #
  7. # To know if a slave and a master are in sync, get the ContextCSN
  8. # and compare them
  9. #
  10. # Copyright (C) 2007 Clement OUDOT
  11. # Copyright (C) 2009 LTB-project.org
  12. #
  13. #==========================================================================
  14. # License: GPLv2+
  15. #==========================================================================
  16. # This program is free software; you can redistribute it and/or
  17. # modify it under the terms of the GNU General Public License
  18. # as published by the Free Software Foundation; either version 2
  19. # of the License, or (at your option) any later version.
  20. #
  21. # This program is distributed in the hope that it will be useful,
  22. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. # GNU General Public License for more details.
  25. #
  26. # GPL License: http://www.gnu.org/licenses/gpl.txt
  27. #==========================================================================
  28. #==========================================================================
  29. # Version
  30. #==========================================================================
  31. my $VERSION = '0.8';
  32. my $TEMPLATE_VERSION = '1.0.0';
  33. #==========================================================================
  34. # Modules
  35. #==========================================================================
  36. use strict;
  37. use lib
  38. qw(/usr/local/nagios/libexec /usr/lib/nagios/plugins /usr/lib64/nagios/plugins);
  39. use utils qw /$TIMEOUT %ERRORS &print_revision &support/;
  40. use Getopt::Long;
  41. &Getopt::Long::config('bundling');
  42. use File::Basename;
  43. use POSIX;
  44. use Net::LDAP;
  45. use Time::Piece;
  46. #==========================================================================
  47. # Options
  48. #==========================================================================
  49. my $progname = basename($0);
  50. my $help;
  51. my $version;
  52. my $verbose = 0;
  53. my $host;
  54. my $warning;
  55. my $critical;
  56. my $logname;
  57. my $authentication;
  58. my $log_file;
  59. my $perf_data;
  60. my $name;
  61. my $port;
  62. my $timeout = $TIMEOUT;
  63. my $regexp;
  64. my $eregexp;
  65. my $exclude;
  66. my $minute = 60;
  67. my $url;
  68. # For SNMP plugins
  69. my $snmp_version;
  70. my $snmp_v1;
  71. my $snmp_v2c;
  72. my $snmp_v3;
  73. my $community;
  74. my $seclevel;
  75. my $authproto;
  76. my $privpasswd;
  77. my $oid;
  78. # For LDAP plugins
  79. my $ldap_binddn;
  80. my $ldap_bindpw;
  81. my $ldap_binduri;
  82. my $slave_ldap_suffix;
  83. my $master_ldap_suffix;
  84. my $ldap_serverid;
  85. my $ldap_singlemaster;
  86. GetOptions(
  87. 'h' => \$help,
  88. 'help' => \$help,
  89. 'V' => \$version,
  90. 'version' => \$version,
  91. 'v+' => \$verbose,
  92. 'verbose+' => \$verbose,
  93. 'H:s' => \$host,
  94. 'host:s' => \$host,
  95. 'w:f' => \$warning,
  96. 'warning:f' => \$warning,
  97. 'c:f' => \$critical,
  98. 'critical:f' => \$critical,
  99. #'l:s'=> \$logname,'logname:s'=> \$logname,
  100. #'a:s'=> \$authentication,
  101. #'authentication:s'=> \$authentication,
  102. #'F:s'=> \$log_file,'log_file:s'=> \$log_file,
  103. 'f' => \$perf_data,
  104. 'perf_data' => \$perf_data,
  105. 'n:s' => \$name,
  106. 'name:s' => \$name,
  107. 'p:i' => \$port,
  108. 'port:i' => \$port,
  109. 't:i' => \$timeout,
  110. 'timeout:i' => \$timeout,
  111. #'r:s'=> \$regexp,'regexp:s'=> \$regexp,
  112. #'e:s'=> \$exclude,'exclude:s'=> \$exclude,
  113. #'m:s'=> \$minute,'minute:s'=> \$minute,
  114. #'u:s'=> \$url,'url:s'=> \$url,
  115. # For SNMP plugins
  116. #'snmp_version:s'=> \$snmp_version,'1'=> \$snmp_v1,
  117. #'2'=> \$snmp_v2c,'3'=> \$snmp_v3,
  118. #'C:s'=> \$community,'community:s'=> \$community,
  119. #'L:s'=> \$seclevel,'seclevel:s'=> \$seclevel,
  120. #'A:s'=> \$authproto,'authproto:s'=> \$authproto,
  121. #'X:s'=> \$privpasswd,'privpasswd:s'=> \$privpasswd,
  122. #'o:s'=> \$oid,'oid:s'=> \$oid,
  123. # For LDAP plugins
  124. 'D:s' => \$ldap_binddn,
  125. 'binddn:s' => \$ldap_binddn,
  126. 'P:s' => \$ldap_bindpw,
  127. 'bindpw:s' => \$ldap_bindpw,
  128. 'U:s' => \$ldap_binduri,
  129. 'binduri:s' => \$ldap_binduri,
  130. 'S:s' => \$slave_ldap_suffix,
  131. 'suffix:s' => \$slave_ldap_suffix,
  132. 'M:s' => \$master_ldap_suffix,
  133. 'mastersuffix:s' => \$master_ldap_suffix,
  134. 'I:s' => \$ldap_serverid,
  135. 'serverid:s' => \$ldap_serverid,
  136. 's' => \$ldap_singlemaster,
  137. 'singlemaster' => \$ldap_singlemaster,
  138. );
  139. # Fix SMNP Version
  140. unless ($snmp_version) {
  141. $snmp_version = "1" if $snmp_v1;
  142. $snmp_version = "2c" if $snmp_v2c;
  143. $snmp_version = "3" if $snmp_v3;
  144. }
  145. #==========================================================================
  146. # Usage
  147. #==========================================================================
  148. sub print_usage {
  149. print "Usage: \n";
  150. print "$progname -H <hostname> [-h] [-v] [-V]\n\n";
  151. print "Use option --help for more information\n\n";
  152. print "$progname comes with ABSOLUTELY NO WARRANTY\n\n";
  153. }
  154. #=========================================================================
  155. # Version
  156. #=========================================================================
  157. if ($version) {
  158. &print_revision( $progname,
  159. "\$Revision: $VERSION (TPL: $TEMPLATE_VERSION)\$" );
  160. exit $ERRORS{'UNKNOWN'};
  161. }
  162. #=========================================================================
  163. # Help
  164. #=========================================================================
  165. if ($help) {
  166. &print_revision( $progname, "\$Revision: $VERSION\$" );
  167. print
  168. "\n\nConnect to master and slave directories,\nsearch for ContextCSN and check if they are in sync.\n\n";
  169. &print_usage;
  170. print "-v, --verbose\n";
  171. print "\tPrint extra debugging information.\n";
  172. print "-V, --version\n";
  173. print "\tPrint version and exit.\n";
  174. print "-h, --help\n";
  175. print "\tPrint this help message and exit.\n";
  176. print "-H, --host=STRING\n";
  177. print
  178. "\tIP or name (FQDN) of the slave directory. You can use URI (ldap://, ldaps://, ldap+tls://)\n";
  179. print "-p, --port=INTEGER\n";
  180. print "\tSlave directory port to connect to.\n";
  181. print "-w, --warning=DOUBLE\n";
  182. print "\t Level to return a warning status.\n";
  183. print "-c, --critical=DOUBLE\n";
  184. print "\tLevel to return a critical status.\n";
  185. #print "-l, --logname=STRING\n";
  186. #print "\tUser id for login.\n";
  187. #print "-a, --authentication=STRING\n";
  188. #print "\tSSH private key file.\n";
  189. #print "-F, --log_file=STRING\n";
  190. #print "\tStatus or log file.\n";
  191. print "-f, --perf_data\n";
  192. print "\tDisplay performance data.\n";
  193. #print "-n, --name=STRING\n";
  194. #print "\tName (database, table, service, ...).\n";
  195. print "-t, --timeout=INTEGER\n";
  196. print "\tSeconds before connection times out (default: $TIMEOUT).\n";
  197. #print "-r, --regexp=STRING\n";
  198. #print "\tCase sensitive regular expression.\n";
  199. #print "-e, --exclude=STRING\n";
  200. #print "\nExclude this regular expression.\n";
  201. #print "-u, --url=STRING\n";
  202. #print "\tURL.\n";
  203. #print "-1, -2, -3, --snmp_version=(1|2c|3)\n";
  204. #print "\tSNMP protocol version.\n";
  205. #print "-C, --community=STRING\n";
  206. #print "\tSNMP community (v1 and v2c).\n";
  207. #print "-L, --seclevel=(noAuthNoPriv|authNoPriv|authPriv))\n";
  208. #print "\tSNMP security level (v3).\n";
  209. #print "-A, --authproto=(MD5|SHA)\n";
  210. #print "\tSNMP authentication protocol (v3).\n";
  211. #print "-X, --privpasswd=STRING\n";
  212. #print "\tSNMP cipher password (v3).\n";
  213. #print "-o, --oid=STRING\n";
  214. #print "\tSNMP OID.\n";
  215. print "-D, --binddn=STRING\n";
  216. print
  217. "\tBind DN to master and slave directories. Bind anonymous if not present.\n";
  218. print "-P, --bindpw=STRING\n";
  219. print
  220. "\tBind passwd to master and slave directories. Need the Bind DN option to work.\n";
  221. print "-U, --binduri=STRING\n";
  222. print
  223. "\tBind URI (ldap://, ldaps://, ldap+tls://) of the master directory. Retrieve this value in cn=monitor if not present.\n";
  224. print "-S, --suffix=STRING\n";
  225. print "-M, --mastersuffix=STRING\n";
  226. print
  227. "\tSuffix of the directories. Retrieve this value in RootDSE if not present.\n";
  228. print "-I, --serverid=STRING\n";
  229. print "\tSID of the syncrepl link\n";
  230. print "-s, --singlemaster\n";
  231. print "\tClassic master-slave. No multi-mastering\n";
  232. print "\n";
  233. &support;
  234. exit $ERRORS{'UNKNOWN'};
  235. }
  236. #=========================================================================
  237. # Functions
  238. #=========================================================================
  239. # DEBUG function
  240. sub verbose {
  241. my $output_code = shift;
  242. my $text = shift;
  243. if ( $verbose >= $output_code ) {
  244. printf "VERBOSE $output_code ===> %s\n", $text;
  245. }
  246. }
  247. # check if -H is used
  248. sub check_host_param {
  249. if ( !defined($host) ) {
  250. printf "UNKNOWN: you have to define a hostname.\n";
  251. &print_usage;
  252. exit $ERRORS{UNKNOWN};
  253. }
  254. }
  255. # check if -w is used
  256. sub check_warning_param {
  257. if ( !defined($warning) ) {
  258. printf "UNKNOWN: you have to define a warning threshold.\n";
  259. &print_usage;
  260. exit $ERRORS{UNKNOWN};
  261. }
  262. }
  263. # check if -c is used
  264. sub check_critical_param {
  265. if ( !defined($critical) ) {
  266. printf "UNKNOWN: you have to define a critical threshold.\n";
  267. &print_usage;
  268. exit $ERRORS{UNKNOWN};
  269. }
  270. }
  271. # Parse CSN
  272. # See http://www.openldap.org/faq/index.cgi?_highlightWords=csn&file=1145
  273. sub parse_csn {
  274. &verbose( '3', "Enter &parse_csn" );
  275. my ($csn) = @_;
  276. my ( $utime, $mtime, $count, $sid, $mod ) =
  277. ( $csn =~ m/(\d{14})\.?(\d{6})?Z#(\w{6})#(\w{2,3})#(\w{6})/g );
  278. &verbose( '2', "Parse $csn into $utime - $count - $sid - $mod" );
  279. &verbose( '3', "Leave &parse_csn" );
  280. return ( $utime, $count, $sid, $mod );
  281. }
  282. # Bind to LDAP server
  283. sub get_ldapconn {
  284. &verbose( '3', "Enter &get_ldapconn" );
  285. my ( $server, $binddn, $bindpw ) = @_;
  286. my ( $useTls, $tlsParam );
  287. # Manage ldap+tls:// URI
  288. if ( $server =~ m{^ldap\+tls://([^/]+)/?\??(.*)$} ) {
  289. $useTls = 1;
  290. $server = $1;
  291. $tlsParam = $2 || "";
  292. }
  293. else {
  294. $useTls = 0;
  295. }
  296. my $ldap = Net::LDAP->new( $server, timeout => $timeout );
  297. return ('1') unless ($ldap);
  298. &verbose( '2', "Connected to $server" );
  299. if ($useTls) {
  300. my %h = split( /[&=]/, $tlsParam );
  301. my $message = $ldap->start_tls(%h);
  302. $message->code
  303. && &verbose( '1', $message->error )
  304. && return ( $message->code, $message->error );
  305. &verbose( '2', "startTLS success on $server" );
  306. }
  307. if ( $binddn && $bindpw ) {
  308. # Bind witch credentials
  309. my $req_bind = $ldap->bind( $binddn, password => $bindpw );
  310. $req_bind->code
  311. && &verbose( '1', $req_bind->error )
  312. && return ( $req_bind->code, $req_bind->error );
  313. &verbose( '2', "Bind with $binddn" );
  314. }
  315. else {
  316. my $req_bind = $ldap->bind();
  317. $req_bind->code
  318. && &verbose( '1', $req_bind->error )
  319. && return ( $req_bind->code, $req_bind->error );
  320. &verbose( '2', "Bind anonymous" );
  321. }
  322. &verbose( '3', "Leave &get_ldapconn" );
  323. return ( '0', $ldap );
  324. }
  325. # Get the master URI from cn=monitor
  326. sub get_masteruri {
  327. &verbose( '3', "Enter &get_masteruri" );
  328. my ($ldapconn) = @_;
  329. my $result;
  330. my $message;
  331. my $entry;
  332. $message = $ldapconn->search(
  333. base => 'cn=monitor',
  334. scope => 'sub',
  335. filter => '(&(namingContexts=*)(MonitorUpdateRef=*))',
  336. attrs => [ 'monitorupdateref', 'namingContexts' ]
  337. );
  338. $message->code
  339. && &verbose( '1', $message->error )
  340. && return ( $message->code, $message->error );
  341. $entry = $message->entry(0);
  342. return ( 1, "No data" ) unless $entry;
  343. &verbose( '2',
  344. "Found Master URI: " . $entry->get_value('monitorupdateref') );
  345. &verbose( '3', "Leave &get_masteruri" );
  346. return ( 0, $entry->get_value('monitorupdateref') );
  347. }
  348. # Get the suffix from RootDSE
  349. sub get_suffix {
  350. &verbose( '3', "Enter &get_suffix" );
  351. # Return the first namingContext of the RootDSE
  352. my ($ldapconn) = @_;
  353. my $result;
  354. my $message;
  355. my $entry;
  356. $message = $ldapconn->search(
  357. base => '',
  358. scope => 'base',
  359. filter => '(objectClass=*)',
  360. attrs => ['namingcontexts']
  361. );
  362. $message->code
  363. && &verbose( '1', $message->error )
  364. && return ( $message->code, $message->error );
  365. $entry = $message->entry(0);
  366. return ( 1, "No data" ) unless $entry;
  367. &verbose( '2', "Found suffix: " . $entry->get_value('namingcontexts') );
  368. &verbose( '3', "Leave &get_suffix" );
  369. return ( 0, $entry->get_value('namingcontexts') );
  370. }
  371. # Get the ContextCSN
  372. sub get_contextcsn {
  373. &verbose( '3', "Enter &get_contextCSN" );
  374. my ( $ldapconn, $base, $serverid ) = @_;
  375. my $result;
  376. my $message;
  377. my $entry;
  378. my $contextcsn;
  379. $message = $ldapconn->search(
  380. base => $base,
  381. scope => 'base',
  382. filter => '(objectclass=*)',
  383. attrs => ['contextCSN']
  384. );
  385. $message->code
  386. && &verbose( '1', $message->error )
  387. && return ( $message->code, $message->error );
  388. $entry = $message->entry(0);
  389. return ( 1, "No data" ) unless $entry;
  390. # Get values
  391. foreach ( $entry->get_value('contextCSN') ) {
  392. &verbose( '2', "Found ContextCSN: " . $_ );
  393. # Keep only ContextCSN with SID
  394. my @csn = &parse_csn($_);
  395. if ( !$ldap_singlemaster ) {
  396. if ( $serverid eq $csn[2] ) {
  397. $contextcsn = $_;
  398. &verbose( '2',
  399. "ContextCSN match with SID $serverid: " . $contextcsn );
  400. last;
  401. }
  402. }
  403. else {
  404. $contextcsn = $_;
  405. &verbose( '2',
  406. "ContextCSN match with SID $serverid: " . $contextcsn );
  407. }
  408. }
  409. unless ($contextcsn) {
  410. &verbose( '2', "Found no ContextCSN with SID $serverid" );
  411. return ( '1', "No data" );
  412. }
  413. &verbose( '3', "Leave &get_contextCSN" );
  414. return ( 0, $contextcsn );
  415. }
  416. #=========================================================================
  417. # Main
  418. #=========================================================================
  419. # Options checks
  420. &check_host_param();
  421. &check_warning_param();
  422. &check_critical_param();
  423. my $errorcode;
  424. # Set SID to 000 if not defined
  425. $ldap_serverid ||= "000";
  426. # Connect to the slave
  427. # If $host is an URI, use it directly
  428. my $slave_uri;
  429. if ( $host =~ m#ldap(\+tls)?(s)?://.*# ) {
  430. $slave_uri = $host;
  431. $slave_uri .= ":$port" if ( $port and $host !~ m#:(\d)+# );
  432. }
  433. else {
  434. $slave_uri = "ldap://$host";
  435. $slave_uri .= ":$port" if $port;
  436. }
  437. my $ldap_slave;
  438. ( $errorcode, $ldap_slave ) =
  439. &get_ldapconn( $slave_uri, $ldap_binddn, $ldap_bindpw );
  440. if ($errorcode) {
  441. print "Can't connect to $slave_uri.\n";
  442. exit $ERRORS{'CRITICAL'};
  443. }
  444. # Get the suffix if not provided
  445. if ( !$slave_ldap_suffix ) {
  446. ( $errorcode, $slave_ldap_suffix ) = &get_suffix($ldap_slave);
  447. if ($errorcode) {
  448. print
  449. "Can't get suffix from $slave_uri. Please provide it with -S option.\n";
  450. exit $ERRORS{'UNKNOWN'};
  451. }
  452. }
  453. # Get the master URI if not provided
  454. my $master_uri = $ldap_binduri;
  455. if ( !$master_uri ) {
  456. ( $errorcode, $master_uri ) = &get_masteruri($ldap_slave);
  457. if ($errorcode) {
  458. print
  459. "Can't get Master URI from $slave_uri. Please provide it with -U option.\n";
  460. exit $ERRORS{'UNKNOWN'};
  461. }
  462. }
  463. # Connect to the master
  464. my $ldap_master;
  465. ( $errorcode, $ldap_master ) =
  466. &get_ldapconn( $master_uri, $ldap_binddn, $ldap_bindpw );
  467. if ($errorcode) {
  468. print "Can't connect to $master_uri.\n";
  469. exit $ERRORS{'CRITICAL'};
  470. }
  471. # Get the suffix if not provided
  472. if ( !$master_ldap_suffix ) {
  473. ( $errorcode, $master_ldap_suffix ) = &get_suffix($ldap_master);
  474. if ($errorcode) {
  475. print
  476. "Can't get suffix from $master_uri. Please provide it with -M option.\n";
  477. exit $ERRORS{'UNKNOWN'};
  478. }
  479. }
  480. # Get the contextCSN
  481. my $slavecsn;
  482. my $mastercsn;
  483. ( $errorcode, $slavecsn ) =
  484. &get_contextcsn( $ldap_slave, $slave_ldap_suffix, $ldap_serverid );
  485. if ($errorcode) {
  486. print
  487. "Can't get Context CSN with SID $ldap_serverid from $slave_uri. Please set SID with -I option.\n";
  488. exit $ERRORS{'UNKNOWN'};
  489. }
  490. ( $errorcode, $mastercsn ) =
  491. &get_contextcsn( $ldap_master, $master_ldap_suffix, $ldap_serverid );
  492. if ($errorcode) {
  493. print
  494. "Can't get Context CSN with SID $ldap_serverid from $master_uri. Please set SID with -I option.\n";
  495. exit $ERRORS{'UNKNOWN'};
  496. }
  497. # Compare the utime in CSN
  498. my @slavecsn_elts = &parse_csn($slavecsn);
  499. my @mastercsn_elts = &parse_csn($mastercsn);
  500. my $time_master = Time::Piece->strptime( $mastercsn_elts[0], "%Y%m%d%H%M%S" );
  501. my $time_slave = Time::Piece->strptime( $slavecsn_elts[0], "%Y%m%d%H%M%S" );
  502. &verbose( '2', "Master times: $time_master" );
  503. &verbose( '2', "Slave times: $time_slave" );
  504. my $utime_master = $time_master->epoch;
  505. my $utime_slave = $time_slave->epoch;
  506. &verbose( '2', "Master timestamp: $utime_master" );
  507. &verbose( '2', "Slave timestamp: $utime_slave" );
  508. my $deltacsn = abs( $utime_master - $utime_slave );
  509. #==========================================================================
  510. # Exit with Nagios codes
  511. #==========================================================================
  512. # Prepare PerfParse data
  513. my $perfparse = " ";
  514. if ($perf_data) {
  515. $perfparse = "|'deltatime'=" . $deltacsn . "s;$warning;$critical";
  516. }
  517. # Test the delta and exit
  518. if ( $deltacsn == 0 ) {
  519. print "OK - directories are in sync (W:$warning - C:$critical)$perfparse";
  520. exit $ERRORS{'OK'};
  521. }
  522. else {
  523. if ( $deltacsn < $warning ) {
  524. print
  525. "OK - directories are not in sync - $deltacsn seconds late (W:$warning - C:$critical)$perfparse";
  526. exit $ERRORS{'OK'};
  527. }
  528. elsif ( $deltacsn > $warning and $deltacsn < $critical ) {
  529. print
  530. "WARNING - directories are not in sync - $deltacsn seconds late (W:$warning - C:$critical)$perfparse";
  531. exit $ERRORS{'WARNING'};
  532. }
  533. else {
  534. print
  535. "CRITICAL - directories are not in sync - $deltacsn seconds late (W:$warning - C:$critical)$perfparse";
  536. exit $ERRORS{'CRITICAL'};
  537. }
  538. }
  539. exit $ERRORS{'UNKNOWN'};