The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

=head1 NAME

Nagios::NRPE::Daemon - A Nagios NRPE Daemon

=head1 SYNOPSIS

 use Nagios::NRPE::Daemon;
 use IPC::Cmd qw(can_run run run_forked);

 # create the commandlist we accept
 my $commandlist = 
 };
 my $callback = sub {
   my ($self,$check,@options) = @_;
   my $commandlist = $self->commandlist();
   if ($commandlist->{$check}) {
     my $args = $commandlist->{$check}->{args};
     my $i = 0;
     foreach (@options) {
       $i++;
       $args =~ "s/\$ARG$i\$/$_/";
     }
     my $buffer;
     if (scalar run(command => $commandlist->{$check}->{bin} . " " . $args,
 		    verbose => 0,
		    buffer => \$buffer,
		    timeout => 20)) {
       return $buffer;
     }
   }
 };

 my $daemon = Nagios::NRPE::Daemon->new(
   listen => "127.0.0.1",
   port => "5666",
   pid_dir => '/var/run',
   ssl => 0,
   commandlist => {
     "check_cpu" => { bin => "/usr/lib/nagios/plugin/check_cpu",
                      args => "-w 50 -c 80" }
   },
   callback => $callback
 );

=head1 DESCRIPTION

A simple daemon implementation with the capabillity to add your own callbacks 
and hooks in case you want to build your own NRPE Server.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Andreas Marschke <andreas.marschke@googlemail.com>.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

package Nagios::NRPE::Daemon;

our $VERSION = '0.003';

use 5.010_000;

use strict;
use warnings;

use Data::Dumper;
use Carp;
use IO::Socket;
use IO::Socket::INET;
use Nagios::NRPE::Packet qw(NRPE_PACKET_VERSION_2
			    NRPE_PACKET_RESPONSE
			    MAX_PACKETBUFFER_LENGTH
			    STATE_UNKNOWN
			    STATE_CRITICAL
			    STATE_WARNING
			    STATE_OK);

=over

=item new()

Takes the following options as a hashref:

=item listen:

Listen on this IP Address

=item port:

Port to listen on

=item pid_dir

The pidfile for this daemon

=item ssl

Use ssl (1|0)

=item commandlist

A hashref of the allowed commands on the daemon

=item callback

A sub executed everytime a check should be run. Giving the daemon full control what should happen.

 my $callback = sub {
   my ($self,$check,@options) = @_;
   my $commandlist = $self->commandlist();
   if ($commandlist->{$check}) {
     my $args = $commandlist->{$check}->{args};
     my $i = 0;
     foreach (@options) {
       $i++;
       $args =~ "s/\$ARG$i\$/$_/";
     }
     my $buffer;
     if (scalar run(command => $commandlist->{$check}->{bin} . " " . $args,
 		    verbose => 0,
		    buffer => \$buffer,
		    timeout => 20)) {
       return $buffer;
     }
   }
 };


=back

=cut

sub new {
  my ($class,%hash) = @_;
  my $self = {};

  $self->{listen} = delete $hash{listen} || "0.0.0.0";
  $self->{port} = delete $hash{port} || "5666";
  $self->{pid_dir} = delete $hash{pid_dir} || "/var/run";
  $self->{ssl} = delete $hash{ssl} || 0;
  $self->{commandlist} = delete $hash{commandlist} || {};
  $self->{callback} = delete $hash{callback} || sub{};

  bless $self,$class;
}

=over

=item start

Starts the server and enters the Loop listening for packets

=back

=cut

sub start{
  my $self = shift;
  my $packet = Nagios::NRPE::Packet->new();
  my $callback = $self->{callback};
  my ($socket,$s);

  $socket = $self->create_socket();

  while (1) {
    while(($s = $socket->accept())) {
      my $request;
      $s->recv($request,1036);
      my $unpacked_request = $packet->deassemble($request);
      my $buffer = $unpacked_request->{buffer};
      my ($command,@options) = split /!/,$buffer;

      my $return = $self->{callback}($self,$command,@options);
      print $s $packet->assemble(version =>NRPE_PACKET_VERSION_2,
				 type => NRPE_PACKET_RESPONSE,
				 check => $return
				);

      close($s);
    }
  }
}

=over

=item commandlist

A hashref of elements that are valid commands.
An example for it is:

 "check_cpu" => { bin => "/usr/lib/nagios/plugin/check_cpu",
                  args => "-w 50 -c 80" }

C<args> can contain $ARG1$ elements like normal nrpe.cfg command elements.

=back

=cut

sub commandlist {
  my $self = shift;
  return $self->{commandlist};
}

=over

=item create_socket

A shorthand function returning either an encrypted or unencrypted socket
depending on wether ssl is set to 1 or 0.

=back

=cut

sub create_socket {
  my $self = shift;
  my $socket;

  if ($self->{ssl}) {
    eval {
      # required for new IO::Socket::SSL versions
      require IO::Socket::SSL;
      IO::Socket::SSL->import();
      IO::Socket::SSL::set_ctx_defaults( SSL_verify_mode => 0 );
    };
    $socket = IO::Socket::SSL->new(
      Listen => 5,
      LocalAddr => $self->{host},
      LocalPort => $self->{port},
      Proto    => 'tcp',
      Reuse    => 1,
      SSL_verify_mode => 0x01,
      Type     => SOCK_STREAM)
      or die(IO::Socket::SSL::errstr());
  } else {
    $socket = IO::Socket::INET->new(
      Listen => 5,
      LocalAddr => $self->{host},
      LocalPort => $self->{port},
      Reuse    => 1,
      Proto    => 'tcp',
      Type     => SOCK_STREAM) or die "ERROR: $@ \n";
  }
  return $socket;
}
1;