#!/usr/bin/perl
# embedded perl von nagios deaktivieren
# http://nagios.sourceforge.net/docs/3_0/embeddedperl.html
# nagios: -epn

use 5.014;
use utf8;
use warnings;

use noris::NetSaint;
use Getopt::Long qw(GetOptions);
use HTML::TreeBuilder ();
use IO::Socket::SSL qw(SSL_VERIFY_NONE);
use Object::Destroyer ();
use WWW::Mechanize    ();

GetOptions(
    'absolute!' => \( my $Absolute = 0 ),
    'critical-percent=f' => \my $CriticalPercent,
    'critical-users=i'   => \my $CriticalUsers,
    'debug+'             => \( my $Debug = 0 ),
    'help|?' =>
      sub { exec perldoc => -F => $0 or die "exec('perldoc -F $0'): $!\n" },
    'host=s'            => \my $Host,
    'password=s'        => \my $Password,
    'username=s'        => \my $Username,
    'warning-percent=f' => \my $WarningPercent,
    'warning-users=i'   => \my $WarningUsers,
) or exit 1;

my $NetSaint = noris::NetSaint->new();

die "Parameter -host is needed.\n"     unless $Host;
die "Parameter -username is needed.\n" unless $Username;
die "Parameter -password is needed.\n" unless $Password;

$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} //= 0;

my $mech =
  WWW::Mechanize->new( ssl_opts => { SSL_verify_mode => SSL_VERIFY_NONE } )
  or die;

$mech->get( my $url = "https://$Host/admin/" );

# Login into Admin Site
$mech->submit_form(
    with_fields => {
        'username' => $Username,
        'password' => $Password,
    },
);

my $sign_out = Object::Destroyer->new(
    sub {
        my $link = $mech->find_link( text => 'Sign Out' ) or return;
        $mech->get( $link->url );
        say 'Signed out.' if $Debug;
    }
);

my $output_page = $mech->content;

# If another user is logged in (confirmation is needed)
if ( $output_page =~ m/frmConfirmation/ ) {
    $mech->submit_form(
        form_name => 'frmConfirmation',
        button    => 'btnContinue'
    );
    $output_page = $mech->content;
}

print $output_page if $Debug > 1;

# <table id="table_em-status_10" cellpadding="0" cellspacing="0" border="0" width="100%">
# <tr>
#     <td><span class="cssSmallWhite"><b>Logging Disk:</b></span></td>
#     <td><span class="cssSmallWhite">17% Full</span></td>
#
# </tr>
# <tr>
#     <td colspan="2">&nbsp;</td>
# </tr>                <tr>
#     <td><span class="cssSmallWhite"><b>Max Licensed Users:</b></span></td>
#     <td><span class="cssSmallWhite">125</span></td>
# </tr>                <tr>
#     <td colspan="2">&nbsp;</td>
# </tr>
#                                 <tr>
#     <td><span class="cssSmallWhite"><b>Signed-In Users [Device]:</b></span></td>
#     <td><span class="cssSmallWhite">102</span></td>
# </tr>
# <tr>
#     <td colspan="2">&nbsp;</td>
# </tr>
# <tr>                    <td><span class="cssSmallWhite"><b>Signed-In Users [Default Network]:</b></span></td>                    <td><span class="cssSmallWhite">102</span></td>
# </tr>
# <tr>
#     <td colspan="2">&nbsp;</td>
# </tr>
# <tr>
#     <td><span class="cssSmallWhite"><b>Signed-In Mail Users:</b></span></td>
#     <td><span class="cssSmallWhite">0</span></td>
# </tr>
# <tr>
#     <td colspan="2">&nbsp;</td>
# </tr>                <tr>
#     <td><span class="cssSmallWhite"><b>Concurrent Connections for Authorization only Access:</b></span></td>
#     <td><span class="cssSmallWhite">0</span></td>
# </tr>
# <tr>
#     <td colspan="2">&nbsp;</td>
# </tr>
# <tr>
#     <td><span class="cssSmallWhite"><b>ActiveSync Connections:</b></span></td>
#     <td><span class="cssSmallWhite">0</span></td>
# </tr>
# <tr>
#     <td colspan="2">&nbsp;</td>
# </tr>
# </table>

my $tree = HTML::TreeBuilder->new_from_content($output_page) or die;
my $sentry = Object::Destroyer->new( $tree, 'delete' );

my %status;
{
    my $status_table =
      $tree->look_down( _tag => 'table', id => 'table_em-status_10' )
      or die 'Status table not found in <' . $mech->uri . ">; failed login?\n";
    for my $tr ( $status_table->look_down( _tag => 'tr' ) ) {
        next if $tr->as_trimmed_text !~ /\S/;
        my ( $key, $value ) = my (@tds) = map $_->as_trimmed_text,
          $tr->look_down( _tag => 'td' );
        die @tds
          . ' instead of 2 fields in status table row: '
          . $tr->as_HTML . "\n"
          if @tds != 2;
        die
          "Several values for key $key in status table: $status{$key} vs $key\n"
          if defined $status{$key};
        $status{$key} = $value;
    }
}

if ($Debug) {
    say "$_ $status{$_}" for sort keys %status;
}

defined( my $current_users = $status{'Signed-In Users [Device]:'} )
  or die "Number of currently signed in users not found.\n";
defined( my $max_users = $status{'Max Licensed Users:'} )
  or die "Max Licensed Users not found.\n";
die "Max Licensed Users is zero.\n" unless $max_users;
my $rel_usage = 100 * $current_users / $max_users;

$NetSaint->update(
    defined $CriticalUsers && $current_users >= $CriticalUsers
      || defined $CriticalPercent && $rel_usage >= $CriticalPercent ? 'Critical'
    : defined $WarningUsers       && $current_users >= $WarningUsers
      || defined $WarningPercent  && $rel_usage >= $WarningPercent  ? 'Warning'
    : 'OK',
    sprintf(
        '%d of %d users currently logged in (%.f %%).',
        $current_users, $max_users, $rel_usage
    )
);

__END__

=encoding utf8

=head1 NAME

check_juniper_sslvpn_users - Check Number of Users Logged in

=head1 SYNOPSE

    check_juniper_sslvpn_users -host juniper.example.com \
         -username ADMIN \
         -password *** \
         -warning-users 180 \
         -critical-users 190

=head1 BESCHREIBUNG

Liest die maximal erlaubten User aus, sowie die momentan eingeloggten.

=head1 NOTWENDIGE ARGUMENTE

=over 4

=item -host Hostname

der zu überwachende Host

=item -username USERNAME

=item -password ********

die Logindaten fürs Admin-Frontend

=back

=head1 OPTIONEN

=over 4

=item -warning-users Anzahl

=item -critical-users Anzahl

absolute Anzahl der eingeloggten User, ab der eine Warnung bzw. ein
kritischer Alarm ausgelöst werden soll

=item -warning-percent Prozentwert

=item -critical-percent Prozentwert

Schwellenwerte, gemessen an den "Max Licensed Users", ab denen eine Warnung
bzw. ein kritischer Alarm augelöst werden soll

Beachte: Ist nur bedingt sinnvoll, wenn der Wert "Max Licensed Users" dadurch
schwanken kann, dass sich die Appliance bei Bedarf Lizenzen aus einem Pool holt,
vgl. Ticket 20338389.

=item -help

um (nur) diese Dokumentation anzeigen zu lassen

=back

=head1 ANMERKUNGEN

SNMP würde hier nicht die gewünschten Daten liefern (s. Ticket 15547848),
daher verwenden wir die Web-Schnittstelle.

=cut

