<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/perl -w
# Copyright 2013 Michael Fladischer
# OpenServices e.U.
# office@openservices.at
#
# Monitor Typo3 instances.
# This check is a rewrite of Michael Schams &lt;michael@schams.net&gt; script
# check_typo3.sh available at http://schams.net/nagios/.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.

use strict;
use lib "/usr/local/nagios/libexec/";

use version;
use Thread::Pool::Simple;
use LWP::UserAgent;
use HTML::TreeBuilder::XPath;
use Time::HiRes qw(gettimeofday tv_interval);
use Log::Message::Simple qw[:STD :CARP];

use Nagios::Plugin;
use Nagios::Plugin::Performance use_die =&gt; 1;

my $nagios = Nagios::Plugin-&gt;new(
    shortname =&gt; "TYPO3",
    version =&gt; "0.1",
    url =&gt; "http://openservices.at/services/infrastructure-monitoring/typo3",
    usage =&gt; "Usage: %s ".
        "[-v|--verbose] ".
        "[-t &lt;timeout&gt;] ".
        "-H &lt;host&gt; ".
        "[-u &lt;uri&gt;] ".
        "[-l &lt;login&gt;] ".
        "[-p &lt;password&gt;] ".
        "[-I &lt;ip&gt;] ".
        "[-i &lt;extension&gt;] ".
        "[--update-action=&lt;update-action&gt;] ".
        "[--conflict-action=&lt;conflict-action&gt;] ".
        "[--deprecationlog-action=&lt;deprecation-action&gt;] ".
        "[-T &lt;threads&gt;] ".
        "[-w &lt;threshold&gt;] ".
        "[-c &lt;threshold&gt;] ",
);

# add valid command line options and build them into your usage/help documentation.
$nagios-&gt;add_arg(
    spec =&gt; 'host|H=s',
    help =&gt; "-H, --host=STRING\n".
        "The host to connect to.",
    required =&gt; 1,
);
$nagios-&gt;add_arg(
    spec =&gt; 'warning|w=s',
    help =&gt; "-w, --warning=INTEGER:INTEGER\n".
        "See http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT for the threshold format.",
    required =&gt; 1,
);
$nagios-&gt;add_arg(
    spec =&gt; 'critical|c=s',
    help =&gt; "-c, --critical=INTEGER:INTEGER\n".
        "See http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT for the threshold format.",
    required =&gt; 1,
);
$nagios-&gt;add_arg(
    spec =&gt; 'login|l=s',
    help =&gt; "-l, --login=STRING\n".
        "Username to login.",
    required =&gt; 0,
    default =&gt; "",
);
$nagios-&gt;add_arg(
    spec =&gt; 'password|p=s',
    help =&gt; "-p, --password=STRING\n".
        "   Password used for authentication.",
    required =&gt; 0,
    default =&gt; "",
);
$nagios-&gt;add_arg(
    spec =&gt; 'uri|u=i',
    help =&gt; "-u, --uri=STRING\n".
        "URI of TYPO3 server's Nagios extension output (default: /index.php?eID=nagios).",
    required =&gt; 0,
    default =&gt; "/index.php?eID=nagios",
);
$nagios-&gt;add_arg(
    spec =&gt; 'ssl|s',
    help =&gt; "-s, --ssl\n".
        "Use SSL (HTTPS) when connecting to TYPO3.",
    required =&gt; 0,
    default =&gt; 0,
);
$nagios-&gt;add_arg(
    spec =&gt; 'ip|I=s',
    help =&gt; "-I, --ip=STRING\n".
        "IPv4 address of the TYPO3 server. If this argument is used, the hostname (argument -H or --hostname) is sent as \"Host:\" in the HTTP header of the request.",
    required =&gt; 0,
);
$nagios-&gt;add_arg(
    spec =&gt; 'ignore|i=s@',
    help =&gt; "-i, --ignore=STRING\n".
        "Names of TYPO3 extensions that should be ignored. Can be used multiple times to ignore more than one extension.",
    required =&gt; 0,
);
$nagios-&gt;add_arg(
    spec =&gt; 'conflict-action=s',
    help =&gt; "--conflict-action=(ignore|warning|critical)\n".
        "   One of the following actions, if a conflict with an extension has been detected (default: warning):\n".
        "       \"ignore\"    do nothing, ignore conflict\n".
        "       \"warning\"   generate a warning condition in Nagios\n".
        "       \"critical\"  generate a critical condition in Nagios",
    required =&gt; 0,
    default =&gt; "warning",
);
$nagios-&gt;add_arg(
    spec =&gt; 'update-action=s',
    help =&gt; "--update-action=(ignore|warning|critical)\n".
        "   One of the following actions, if an update for an extension has been detected (default: warning):\n".
        "       \"ignore\"    do nothing, ignore available updates\n".
        "       \"warning\"   generate a warning condition in Nagios\n".
        "       \"critical\"  generate a critical condition in Nagios",
    required =&gt; 0,
    default =&gt; "warning",
);
$nagios-&gt;add_arg(
    spec =&gt; 'deprecationlog-action=s',
    help =&gt; "--deprecationlog-action=(ignore|warning|critical)\n".
        "   One of the following actions, if an enabled deprecation log has been detected (default: warning):\n".
        "       \"ignore\"    do nothing, ignore enabled deprecation logs\n".
        "       \"warning\"   generate a warning condition in Nagios\n".
        "       \"critical\"  generate a critical condition in Nagios",
    required =&gt; 0,
    default =&gt; "warning",
);
$nagios-&gt;add_arg(
    spec =&gt; 'threads|T=i',
    help =&gt; "-T, --threads=INTEGER\n".
        "The number of threads to use for gathering version information from typo3.org (default: 4).",
    required =&gt; 0,
    default =&gt; 4,
);

# Map strings from arguments to Nagios Plugin codes.
my %codemap = (
    "ignore" =&gt; undef,
    "warning" =&gt; WARNING,
    "critical" =&gt; CRITICAL,
);

# Parse @ARGV and process arguments.
$nagios-&gt;getopts;

# Construct URL to TYPO3 nagios extension page.
my $url = sprintf("http%s://%s%s",
    $nagios-&gt;opts-&gt;get("ssl") ? "s" : "",
    ($nagios-&gt;opts-&gt;get("ip") or $nagios-&gt;opts-&gt;get("host")),
    $nagios-&gt;opts-&gt;get("uri")
);

msg(
    sprintf(
        "Connecting to TYPO3 on %s with user %s",
        $url,
        $nagios-&gt;opts-&gt;get("login")
    ),
    $nagios-&gt;opts-&gt;get('verbose')
);

# Instantiate new LWP user agent for TYPO3 nagios extension page.
my $ua = LWP::UserAgent-&gt;new;
$ua-&gt;default_header("Host" =&gt; $nagios-&gt;opts-&gt;get("host"));
$ua-&gt;timeout($nagios-&gt;opts-&gt;get("timeout"));
$ua-&gt;cookie_jar({});

if ($nagios-&gt;opts-&gt;get("login") and $nagios-&gt;opts-&gt;get("password")) {
    msg(
        sprintf(
            "Setting credentials for realm \"TYPO3 Nagios\": %s",
            $nagios-&gt;opts-&gt;get("login")
        ),
        $nagios-&gt;opts-&gt;get('verbose')
    );
    $ua-&gt;credentials(
        sprintf(
            "%s:%i",
            $nagios-&gt;opts-&gt;get("host"),
            $nagios-&gt;opts-&gt;get("ssl") ? 443 : 80
        ),
        "TYPO3 Nagios",
        $nagios-&gt;opts-&gt;get("login"),
        $nagios-&gt;opts-&gt;get("password")
    );
}

# Retrieve TYPO3 nagios extension page and meassure required time.
my $timer = [gettimeofday];
my $response = $ua-&gt;get($url);
my $elapsed = tv_interval($timer) * 1000;

# Perfdata
$nagios-&gt;add_perfdata(
    label =&gt; "Latency",
    value =&gt; $elapsed,
    threshold =&gt; $nagios-&gt;threshold,
    uom =&gt; 'ms',
);

# See if we got a valid response from the TYPO3 nagios extension.
if ($response-&gt;code != 200) {
    $nagios-&gt;nagios_exit(
        CRITICAL,
        sprintf(
            "TYPO3 returned an HTTP error: %i",
            $response-&gt;code
        )
    );
}

# Hash that will hold the paresd response data.
my %data;

# Parse response content and populate data hash.
foreach (grep { !/^#|^$/ } split /\n/, $response-&gt;content) {
    my @matches = $_ =~ /^(\w+):(.*?)((-version)?-(([\d\.]+)(-dev|-([\d\.]+))?))?$/g;

    # Ignore extensions from arguments.
    next if (grep { /$matches[1]/ } @{$nagios-&gt;opts-&gt;get("ignore") || []});

    if (!exists $data{$matches[0]}) {
        $data{$matches[0]} = {};
    }
    if ($matches[0] =~ /^TYPO3|PHP$/) {
        $data{$matches[0]} = version-&gt;parse($matches[4]);
    } elsif ($matches[0] =~ /^EXT$/) {
        $data{$matches[0]}-&gt;{$matches[1]} = version-&gt;parse($matches[5]);
    } elsif ($matches[0] =~ /^EXTDEPTYPO3$/) {
        $data{$matches[0]}-&gt;{$matches[1]} = {
            "from" =&gt; version-&gt;parse($matches[5]),
            "to" =&gt; version-&gt;parse($matches[7])
        }
    } else {
        $data{$matches[0]} = $matches[1];
    }

}

# Perfdata
$nagios-&gt;add_perfdata(
    label =&gt; "Database tables",
    value =&gt; $data{DBTABLES},
);

# Check for latest version of installed extensions from typo3.org.
sub update {
    my ($extension) = @_;

    # Instantiate new LWP user agent for typo3.org to avoid leaking credentials.
    $ua = LWP::UserAgent-&gt;new;
    $ua-&gt;timeout($nagios-&gt;opts-&gt;get("timeout"));
    $ua-&gt;cookie_jar( {} );
    msg(
        sprintf(
            "Checking for latest version of %s on TYPO3.org",
            $extension
        ),
        $nagios-&gt;opts-&gt;get('verbose')
    );
    my $url = sprintf("http://typo3.org/extensions/repository/view/%s", $extension);
    my $response = $ua-&gt;get($url);
    if ($response-&gt;code != 200) {
        error(
            sprintf(
                "Could not fetch version information from %s: %i",
                $url,
                $response-&gt;code
            )
        );
        next;
    }

    my $tree = HTML::TreeBuilder::XPath-&gt;new;
    $tree-&gt;utf8_mode(1);
    $tree-&gt;parse($response-&gt;content);
    $tree-&gt;eof();
    my $key = $tree-&gt;findvalue('//tr/th[text()="Extension key"]/../td/strong/text()');
    if ($key ne $extension) {
        error(
            sprintf(
                "Could not fetch remote version information for %s from %s",
                $extension,
                $url
            )
        );
        return undef;
    }
    my $new = $tree-&gt;findvalue('//div[@class="download-button"]/a/text()');
    $new =~ s/^Download version //;
    msg(
        sprintf(
            "Version of %s found on typo3.org: %s",
            $extension,
            $new
        ),
        $nagios-&gt;opts-&gt;get('verbose')
    );
    return version-&gt;parse($new);
}

# Instantiate a thread pool which will look up version information on typo.org.
msg(
    sprintf(
        "Starting thread pool with %i threads...",
        $nagios-&gt;opts-&gt;get('threads')
    ),
    $nagios-&gt;opts-&gt;get('verbose')
);
my $pool = Thread::Pool::Simple-&gt;new(
    do =&gt; [\&amp;update],
    min =&gt; $nagios-&gt;opts-&gt;get('threads')
);

# Queue jobs to thread pool by passing the extension key.
# Job IDs are stored for later retrieval of the results.
my %jobs = map { $_ =&gt; $pool-&gt;add(($_)) } keys %{$data{EXT}};

# Wait for thread pool to run out of jobs.
msg("Waiting for thread pool to finish...", $nagios-&gt;opts-&gt;get('verbose'));
$pool-&gt;join();

# Retrieve results from thread pool.
my %results = map { $_ =&gt; $pool-&gt;remove($jobs{$_}) } keys %jobs;

# Filter all updates.
my @updates = grep { $results{$_} &gt; $data{EXT}-&gt;{$_} } grep { defined $results{$_} } keys %results;

# Perfdata
$nagios-&gt;add_perfdata(
    label =&gt; "Updates pending",
    value =&gt; scalar @updates,
);

# Filter all conflicts.
my @conflicts = grep { $data{TYPO3} &lt; $data{EXTDEPTYPO3}-&gt;{$_}-&gt;{from} or $data{TYPO3} &gt; $data{EXTDEPTYPO3}-&gt;{$_}-&gt;{to} } grep { $data{EXTDEPTYPO3}-&gt;{$_}-&gt;{to} &gt; 0 } keys %{$data{EXTDEPTYPO3}};

# Perfdata
$nagios-&gt;add_perfdata(
    label =&gt; "Version conflicts",
    value =&gt; scalar @conflicts,
);

# First status derived from the time elapsed during the initial request.
my $code = $nagios-&gt;check_threshold(check =&gt; $elapsed);
my $message = sprintf("Request finished in %ims", $elapsed);

# Check for deprecation log.
if (defined $codemap{$nagios-&gt;opts-&gt;get('deprecationlog-action')} and $data{DEPRECATIONLOG} eq "enabled") {
    if ($codemap{$nagios-&gt;opts-&gt;get('deprecationlog-action')} &gt; $code) {
        $code = $codemap{$nagios-&gt;opts-&gt;get('deprecationlog-action')};
    }
    $message .= "; Deprecation log enabled!";
}

# Process conflicts.
if (defined $codemap{$nagios-&gt;opts-&gt;get('conflict-action')} and scalar @conflicts &gt; 0) {
    if ($codemap{$nagios-&gt;opts-&gt;get('conflict-action')} &gt; $code) {
        $code = $codemap{$nagios-&gt;opts-&gt;get('conflict-action')};
    }
    $message .= sprintf("; TYPO3 %s conflicts: %s", $data{TYPO3}, join(", ", map { sprintf ("%s[%s-%s]", $_, $data{EXTDEPTYPO3}-&gt;{$_}-&gt;{from}, $data{EXTDEPTYPO3}-&gt;{$_}-&gt;{to}) } @conflicts));
}

# Process updates.
if (defined $codemap{$nagios-&gt;opts-&gt;get('update-action')} and scalar @updates &gt; 0) {
    if ($codemap{$nagios-&gt;opts-&gt;get('update-action')} &gt; $code) {
        $code = $codemap{$nagios-&gt;opts-&gt;get('update-action')};
    }
    $message .= sprintf("; Updates available: %s", join(", ", map { sprintf ("%s[%s-&gt;%s]", $_, $data{EXT}-&gt;{$_}, $results{$_}) } @updates));
}

# Exit with final status and message.
$nagios-&gt;nagios_exit($code, $message);
</pre></body></html>