#!perl

# check_rbl is a Nagios plugin to check if an SMTP server is blacklisted
#
# See  the INSTALL file for installation instructions
#
# Copyright (c) 2007, ETH Zurich.
#
# This module is free software; you can redistribute it and/or modify it
# under the terms of GNU general public license (gpl) version 3.
# See the LICENSE file for details.
#
# RCS information
# enable substitution with:
#   $ svn propset svn:keywords "Id Revision HeadURL Source Date"
#
#   $Id: check_rbl 1085 2009-10-28 07:10:52Z corti $
#   $Revision: 1085 $
#   $HeadURL: https://svn.id.ethz.ch/nagios_plugins/check_rbl/check_rbl $
#   $Date: 2009-10-28 08:10:52 +0100 (Wed, 28 Oct 2009) $

use strict;
use warnings;

use Carp;
use Data::Dumper;
use English '-no_match_vars';
use Nagios::Plugin::Threshold;
use Nagios::Plugin;
use Nagios::Plugin::Getopt;
use Net::DNS;
use Parallel::Iterator qw(iterate);
use Readonly;

Readonly my $DEFAULT_RETRIES => 4;
Readonly my $DEFAULT_WORKERS => 20;

use version;

our $VERSION = '1.1.0';

# IMPORTANT: Nagios plugins could be executed using embedded perl in this case
#            the main routine would be executed as a subroutine and all the
#            declared subroutines would therefore be inner subroutines
#            This will cause all the global lexical variables not to stay shared
#            in the subroutines!
#
# All variables are therefore declared as package variables...
#
use vars qw(
  @BLACKLISTED
  $IP
  $OPTIONS
  $PLUGIN
  $THRESHOLD
);

##############################################################################
# Usage     : my $ip = lookup( $hostname );
# Purpose   : DNS lookup
# Returns   : $ip if found; undef if not found
# Arguments : $hostname : the FQDN to resolve
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub lookup {

    my ($hostname) = @_;

    require Net::DNS;

    my $res = Net::DNS::Resolver->new;

    $res->retry( $OPTIONS->retry() );

    my $query = $res->search($hostname);

    if ($query) {
        foreach my $rr ( $query->answer ) {
            if ( $rr->type eq 'A' ) {
                return $rr->address;
            }
        }
    }
    else {
        return;
    }

    return;    # dead code to make perlcritic happy

}

##############################################################################
# Usage     : verbose("some message string", $optional_verbosity_level);
# Purpose   : write a message if the verbosity level is high enough
# Returns   : n/a
# Arguments : message : message string
#             level   : options verbosity level
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub verbose {

    # arguments
    my $message = shift;
    my $level   = shift;

    if ( !defined $message ) {
        $PLUGIN->nagios_exit( UNKNOWN,
            q{Internal error: not enough parameters for 'verbose'} );
    }

    if ( !defined $level ) {
        $level = 0;
    }

    if ( $level < $OPTIONS->verbose ) {
        if ( !print $message ) {
            $PLUGIN->nagios_exit( UNKNOWN, 'Error: cannot write to STDOUT' );
        }
    }

    return;

}

##############################################################################
# Usage     : check_server( $ip, $server )
# Purpose   : checks if $ip is blacklisted by $server
# Returns   : n/a
# Arguments : $ip     : host IP
#             $server : RBL server
# Throws    : n/a
# Comments  : if blacklisted pushed $server onto @blacklisted
# See also  : n/a
sub check_server {

    my ($server) = @_;

    my $lookup_ip = $IP;

    $lookup_ip =~
s/(\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3})/$4.$3.$2.$1.$server/mxs;

    verbose " -> $lookup_ip\n";

    if ( lookup($lookup_ip) ) {

        return $lookup_ip;

    }

    return 0;

}

##############################################################################
# main
#

################################################################################
# Initialization

$PLUGIN = Nagios::Plugin->new( shortname => 'CHECK_RBL' );

my $time = time;

########################
# Command line arguments

my $usage = <<'EOT';
Usage: check_rbl [--help] [--verbose] [--version] [--timeout t]
                 -H hostname --server servername
                 [--critical n] [--warning n] [--workers n]
EOT

$OPTIONS = Nagios::Plugin::Getopt->new(
    usage   => $usage,
    version => $VERSION,
    url     => 'https://trac.id.ethz.ch/projects/nagios_plugins',
    blurb   => 'Check SMTP blaklisting status',
);

$OPTIONS->arg(
    spec     => 'critical|c=i',
    help     => 'Number of blacklisting servers for a critical warning',
    required => 0,
    default  => 0,
);

$OPTIONS->arg(
    spec     => 'warning|w=i',
    help     => 'Number of blacklisting servers for a warning',
    required => 0,
    default  => 0,
);

$OPTIONS->arg(
    spec     => 'server|s=s@',
    help     => 'RBL server',
    required => 1,
);

$OPTIONS->arg(
    spec     => 'host|H=s',
    help     => 'SMTP server to check',
    required => 1,
);

$OPTIONS->arg(
    spec     => 'retry|r=i',
    help     => 'Number of times to try a DNS query (default is 4) ',
    required => 0,
    default  => $DEFAULT_RETRIES,
);

$OPTIONS->arg(
    spec     => 'workers=i',
    help     => 'Number of parallel checks',
    required => 0,
    default  => $DEFAULT_WORKERS,
);

$OPTIONS->getopts();

###############
# Sanity checks

if ( $OPTIONS->critical < $OPTIONS->warning ) {
    $PLUGIN->nagios_exit( UNKNOWN,
        'critical has to be greater or equal warning' );
}

$IP = $OPTIONS->host;
if ( $IP =~ m/[[:lower:]]/mxs ) {
    $IP = lookup( $OPTIONS->host );
}

if ( !$IP ) {
    $PLUGIN->nagios_exit( UNKNOWN, 'Cannot resolve ' . $OPTIONS->host );
}

alarm $OPTIONS->timeout;

################
# Set the limits

$THRESHOLD = Nagios::Plugin::Threshold->set_thresholds(
    warning  => $OPTIONS->warning,
    critical => $OPTIONS->critical,
);

################################################################################

verbose 'Checking ' . $OPTIONS->host . " ($IP)\n";

my $iter = iterate(
    { workers => $OPTIONS->workers },
    \&check_server,
    sub {
        while ( my $server = pop @{ $OPTIONS->server } ) {
            return $server;
        }
        return;
    }
);

while ( my ( $server, $result ) = $iter->() ) {
    if ($result) {
        push @BLACKLISTED, $server;
    }
}

my $total = scalar @BLACKLISTED;

my $status =
  "BLACKLISTED on $total " . ( ( $total == 1 ) ? 'server' : 'servers' );

if ( $total > 0 ) {
    $status .= " (@BLACKLISTED)";
}

$PLUGIN->add_perfdata(
    label     => 'servers',
    value     => $total,
    uom       => q{},
    threshold => $THRESHOLD,
);

$PLUGIN->add_perfdata(
    label => 'time',
    value => time - $time,
    uom   => q{s},
);

$PLUGIN->nagios_exit( $THRESHOLD->get_status($total), $status );

1;
