#!/usr/bin/env perl

##############################################################################
# Program to check to make sure milter is running and report back to nrpe
# for nagios. The defalt port here is for DKIM which is commonly found on
# port 8891 and default host is localhost
# Created: 2015-06-18
# Version: 1.0.1
# Author: Alex Schuilenburg
##############################################################################

use English qw( -no_match_vars );
use Getopt::Long;
use Pod::Usage;
use Time::HiRes;
use Net::Milter;
use strict;
use warnings;

my $timeout = 10;
my $warn = $timeout / 3;
my $crit = $timeout / 2;
my $milter_host = '127.0.0.1';
my $milter_port = 8891;
my $VERSION = '1.0.1'; 

Getopt::Long::Configure( 'bundling', 'gnu_compat', );

GetOptions( 'man'           => sub { pod2usage(-verbose  => 2) },
            'host|H=s'      => \$milter_host,
            'port|p=s'      => \$milter_port,
            'timeout|t=i'   => \$timeout,
            'warning|w=s'   => \$warn,
            'critical|c=s'  => \$crit,
            'version|V'     => sub { VersionMessage() },
            'help|h'        => sub { pod2usage(1) },
);

#Make sure milter_port exists and if not give back a nagios error
if (($milter_port !~ /^\d+$/) || ($milter_port < 1) || ($milter_port > 65535)) {
    print "Invalid socket number $milter_port.\n";
    exit 1;
}

if ($warn !~ /^(\d*\.\d+|\d+\.?\d*)$/) {
    printf "Invalid warning parameter %s\n", $warn;
    exit 1;
}
if ($crit !~ /^(\d*\.\d+|\d+\.?\d*)$/) {
    printf "Invalid critical parameter %s\n", $crit;
    exit 1;
}

#Timer operation. Times out after $timeout seconds.
my $start_time = Time::HiRes::time;
eval {
    #Set the alarm and set the timeout
    local $SIG{ALRM} = sub { die "alarm\n" };
    alarm $timeout;

    # Attempt to connect
    my $milter = new Net::Milter;
    if (not $milter->open($milter_host,$milter_port,'tcp')) {
      exit 1;  
    }

    my ($milter_version,$returned_actions_ref,$returned_protocol_ref) = $milter->protocol_negotiation(
      SMFIP_NOBODY    => 1,
      SMFIP_NOEOH     => 1,
      SMFIP_NOCONNECT => 1,
      SMFIF_ADDHDRS   => 1,
      SMFIF_CHGBODY   => 0,
    );

    if (not defined $milter_version) {
      print "Connot negotiate with milter\n";
      exit 1;
    }
    if ($milter_version < 1) {
      print "Expected milter version greater than 1\n";
      exit 1;
    }

    my (@results) = $milter->send_helo('localhost');
    if (($#results != 0) || (${$results[0]}{action} ne 'continue')) {
      print "Expected 'continue' response to HELO\n";
      exit 1;
    }
    $milter->send_quit();

    alarm 0;
};

my $elapsed_time = Time::HiRes::time - $start_time;

#Test return value and exit if eval caught the alarm
if ($EVAL_ERROR) {
    if ( $EVAL_ERROR eq "alarm\n" ) {
        print "Operation timed out after $timeout seconds.\n";
        exit 2;
    }
    else {
        print "An unknown error has occured: $EVAL_ERROR \n";
        exit 3;
    }
}

# Check for critical
if ($elapsed_time > $crit) {
    printf "CRITICAL: Response time is %.3f\n", $elapsed_time;
    exit 1;
}

# Check for warning
if ($elapsed_time > $warn) {
    printf "WARNING: Response time is %.3f\n", $elapsed_time;
    exit 1;
}

#Give Nagios OK
printf "OK - %.3fs response time|time=%.6fs;%.6f;%.6f;;\n",
    $elapsed_time, $elapsed_time, $warn, $crit;
exit 0;


#Version message information displayed in both --version and --help
sub main::VersionMessage {
    print <<"EOF";
This is version $VERSION of check_milter.

Copyright (c) 2015 Alex Schuilenburg (alexs\@ecoscentric.com). 
All rights reserved.

This module is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License. 
See http://www.fsf.org/licensing/licenses/gpl.html

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. 

EOF

    exit 1;
}
__END__

=head1 NAME

check_milter - Checks the status of the Mail filter server

=head1 VERSION

This documentation refers to check_milter version 1.0.1

=head1 USAGE

check_milter.pl

=head1 REQUIRED ARGUMENTS

None

=head1 OPTIONS

 --port      (-p)     Set the port number for the milter server
 --host      (-H)     Set the host name of the milter server
 --timeout   (-t)     Sets the timeout, defaults to 10 seconds.
 --warning=  (-w)     Sets the warning period for the response time
 --critical= (-c)     Sets the critical period for the response time
 --version   (-V)     Display current version and exit
 --help      (-h)     Display help message and exit
 --man                Display man page and exit

=head1 DESCRIPTION
 
This is a Nagios plugin that checks the status of a milter server that has been
configured to listen on a tcp socket. It connects to a running milter server
and performs a HELO check for 'localhost' and expects a 'continue' result.
It will return the appropriate NAGIOS/NRPE response on success/failure.

=head1 DIAGNOSTICS

=head2  IO::Socket::INET: connect: Connection refused

The milter tcp port in not accepting incoming connections.
The defaults may not be the number and host of the server you wish to check.
Change variable $milter_port and/or $milter_host to fix this issue or add the 
-p and/or -h parameters to specify the correct port and/or host respectively.

=head1 CONFIGURATION AND ENVIRONMENT

check_milter must be available on the system being checked.
 
=head1 DEPENDENCIES
 
check_milter depends on the following modules:
    Getopt::Std       Standard Perl 5.8 module
    Time::HiRes       Standard Perl 5.8 module
    Net::Milter       Available from CPAN
    
=head1 INCOMPATIBILITIES

None known yet.

=head1 BUGS AND LIMITATIONS

No known bugs. If you encounter any let me know. 
(alexs@ecoscentric.com)

Currently only tcp sockets are supported. This can easily be extended to
check milter servers on UNIX sockets - I just wrote what my systems use.

=head1 AUTHOR

Alex Schuilenburg (alexs@ecoscentric.com)

=head1 LICENCE AND COPYRIGHT

Copyright (c) 2015 Alex Schuilenburg (alexs@ecoscentric.com)
All rights reserved.

This module is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License. 
See L<http://www.fsf.org/licensing/licenses/gpl.html>.
 
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. 
