#!/usr/bin/perl -w ############################## # Nagios Ajax API CGI Script # # Ryan Armstrong 2013 # ############################## # # This script will parse the Nagios status cache files and return # the requested data as Nagios Config, XML or JSON encoded data via HTTP # or STDOUT. # # This script will reparse the source files with each request so please be # mindful of impact to disk IO and CPU load in larger environments. # # For best performance, move your Nagios data to a shared memory location # such as /dev/shm. # # Copy or link this script to /sbin and access it via: # http:///nagios/cgi-bin/ajax.cgi # # For web access, pass arguments as follows: # http:///nagios/cgi-bin/ajax.cgi?=&=... # # For shell/STDOUT, pass arguments as follows: # ./ajax.cgi = =... # # If no arguments are passed, the script will parse and return all status data. # # The location of the Nagios status file can be set below in STATUS_FILE or # as a command argument with: # # status_file= # # Multiple formats are available including plain text (in Nagios config format), # XML and JSON. The default for all web requests is JSON while the default for STDOUT # is plain text. Specify the desired format with: # # format= # # To filter the return data, simply provide the Nagios field name and value for the # desired data. Pseudo field 'type' is provided for filtering object types. # # Example: # - The following example will return all 'servicestatus' objects for 'host1.example.com': # http:///nagios/cgi-bin/ajax.cgi?type=servicestatus&host_name=host1.example.com # # You can also filter which fields are returned for each object by providing a # comma separated list in the 'fields' argument. # # Example: # - The following example will only return the 'host_name', 'service_desciption', # 'current_state' and 'type' fields for all services ('type' is always returned): # http://.../ajax.cgi?type=servicestatus&fields=host_name,service_description,current_state # # For additional performance benefit, you can limit the number of results returned with: # result_limit=n # use strict; use warnings; use CGI; use Data::Dumper; use JSON; use String::Util 'trim'; use Switch; use Time::HiRes 'time'; use Util::Any -all; use XML::Simple; use constant STATUS_FILE => '/usr/local/nagios/var/status.dat'; use constant BUILD_TREE => 0; # Are we in web or shell? my $web_context = defined($ENV{ GATEWAY_INTERFACE }); # Check args for usage request if (!$web_context && defined($ARGV[0]) && any(sub { $ARGV[0] eq $_}, ('-h', '--help', '--usage'))) { my $usage = < EOL print $usage; exit 0; } # Read filters from URL query my %filters = CGI->new()->Vars(); # Get user defined status file path my $status_file = STATUS_FILE; if (defined($filters{ status_file })) { $status_file = $filters{ status_file }; delete $filters{ status_file }; } # Get user defined output format my $format = $web_context ? 'json' : 'text'; if (defined($filters{ format })) { $format = $filters{ format }; delete $filters{ format }; } # Get user defined field list my @fields = (); if (defined($filters{ fields })) { @fields = split(',', $filters{ fields }); push(@fields, 'type'); delete $filters{ fields }; } # Get user defined result limit my $result_limit = 0; if (defined($filters{ result_limit })) { $result_limit = int $filters{ result_limit }; delete $filters{ result_limit }; } # Get user defined hierarchy my $build_tree = BUILD_TREE;; if (defined($filters{ hierarchy })) { switch($filters{ hierarchy }) { case 'tree' { $build_tree = 1; } case 'flat' { $build_tree = 0; } else { my $msg = "ERROR: Unknown hierarchy: '$filters{ hierarchy }'"; if($web_context) { print "Content-type: text/plain\n\n$msg"; } die $msg; } } delete $filters{ hierarchy }; } # HTTP Headers if ($web_context) { switch ($format) { case 'json' { print "Content-type: application/json\n\n"; } case 'xml' { print "Content-type: application/xml\n\n"; } case 'text' { print "Content-type: text/plain\n\n"; } else { my $msg = "ERROR: Unknown format: '$format'"; print "Content-type: text/plain\n\n$msg"; die $msg; } } } # Create results hash my %tree; my @results = (); my %output = ( 'filters' => \%filters, 'fields' => \@fields, 'results' => $build_tree ? \%tree : \@results, 'result_count' => 0, 'line_count' => 0, 'status' => 'OK' ); my %object_def; my $object_type; my $in_def = 0; my $line_count = 0; my $result_count = 0; # Start stopwatch my $time_start = time(); # Open and parse status file if(open my $fh, $status_file) { while( my $line = <$fh>) { $line_count++; unless ($in_def) { # Check for new object definition ($object_type) = ($line =~ m/(\w+)\s\{/); if ($object_type) { # New object definition $in_def = 1; %object_def = ('type' => $object_type); } } else { # Grab key=val pair my ($key, $val) = ($line =~ m/\s+([\w_]+)=(.*)/); if ($key) { $object_def{ $key } = $val; } else { # No match. Might be end of definition if ($line =~ /\s+\}/) { $in_def = 0; # Check filters my $include = 1; foreach my $filter_key (keys %filters) { $include &= (defined($object_def{ $filter_key }) && $object_def{ $filter_key } eq $filters{ $filter_key }); } # Process search result if($include) { $result_count++; # Copy hash my %result = %object_def; # Build hierarchical tree if ($build_tree) { switch($result{ type }) { case 'info' { $tree{ info } = \%result; } case 'programstatus' { $tree{ programstatus } = \%result; } case 'contactstatus' { $tree{ contact }->{ $result{ contact_name } }->{ status } = \%result; } case 'hoststatus' { $tree{ hosts }{ host }->{ $result{ host_name } }->{ status } = \%result; } case 'hostcomment' { $tree{ hosts }{ host }->{ $result{ host_name } }->{ comment }->{ $result{ comment_id } } = \%result; } case 'servicestatus' { $tree{ hosts }{ host }->{ $result{ host_name } }->{ services }{ service }->{ $result{ service_description } }->{ status } = \%result; } case 'servicecomment' { $tree{ hosts }{ host }->{ $result{ host_name } }->{ services }{ service }->{ $result{ service_description } }->{ comments }{ comment }->{ $result{ comment_id } } = \%result; } } } else { push @results, \%result; } # Apply field list if (@fields) { foreach my $field (keys %result) { unless (any(sub { $_ eq $field }, @fields)) { delete $result{ $field }; } } } # Check result limit if ($result_limit && $result_count >= $result_limit) { last; } } } } } } # Close status file close $fh; } else { $output{ status } = 'ERROR'; $output{ error } = "Unable to open status file."; } # Search stats my $time_end = time(); $output{ time } = $time_end - $time_start; $output{ line_count } = $line_count; $output{ result_count } = $result_count; $output{ result_limit } = $result_limit; # Output data switch ($format) { case 'json' { print encode_json(\%output); } case 'xml' { print XMLout(\%output, ( RootName => 'nagios_status', GroupTags => { fields => 'field', results => 'result', hosts => 'host', services => 'service', comments => 'comment' }, ContentKey => 'comment_data', NoIndent => $web_context, NoSort => 1 )); } case 'text' { foreach my $result (@results) { print "$result->{ type } {\n"; foreach my $field (sort keys %{ $result }) { print "\t$field=$result->{ $field }\n"; } print "\t}\n\n"; } if (defined($output{ error })) { die "Error: $output{ error }\n"; } } } # Tidy line break for shell users if (!$web_context && $format ne 'text') { print "\n"; }