#!/usr/bin/perl


## Script written by Noah Guttman and Copyright (C) 2014 Noah Guttman. This script is released and distributed under the terms of the GNU General Public License

#Libraries to use
use warnings;
use strict;
use Getopt::Std;
use Time::HiRes qw(gettimeofday usleep);
use IO::Socket;
use threads;
use threads::shared;
use String::Random;

use vars qw($opt_h $opt_H $opt_P $opt_t $opt_r $opt_d $opt_R $opt_M $opt_s $opt_I $opt_c);

$opt_R ='';
my $baseport;

my @output;
my @line;
my $val;

my $gah :shared;
$gah ="";
my $testerrortotal=0;
my $response_timeout=0;

my $elapsed=1000;
my $debug_output="";
my $exitcode=3;
my $icmp_responsed :shared;
$icmp_responsed = 0;
my $timeout=1000000;
my $retries=4;
my $errors=0;
my $maxfailures =3;
my $localIP;
my $localPort;
my $timer =0;

my $ReturnMin=10000000;
my $ReturnMax=0;
my $ReturnTotal=0;
my $testoktotal=0;
my $ReturnAvg=0;

my $sock;
my $recvthread;
my $optionPacket='';

my $randomstring = new String::Random;

##init();

# Get the options
if ($#ARGV le 0) {
	$opt_h=1;
} else {
	getopts('hdsH:P:t:r:R:M:I:c:');
}


## Display Help
if ($opt_h){
	print "::SIP Options Check Instructions::\n\n";
	print " -h,		Display this help information\n";
	print " -H,		Hostname or IP to check\n";
        print " -P,		Port to check\n";
        print "                  The default is 5060\n";
	print " -s,             Accept any SIP message as valid response\n";
	print " -I,             Attempt to bind to the IP when sending the Options packet\n";
	print "                  If this IP is not present on the server the test will fail with an error \n";
	print " -M,             Custom message to report on failure\n";
	print " -R,             Restart command to use (for use with event handlers)\n";
        print " -t,             Timeout (ms) for each communication attempt.\n";
        print "                  The default is 1000\n";
        print " -r,             Number of OPTIONS packets to send.\n";
        print "                  The default is 4\n";
        print " -c,             Number of bad responses to trigger a critical\n";
        print "                  The default is 3\n";
        print " -d,             Turn on debug mode - prints out packet(s) recived.\n";
        print "Script written by Noah Guttman and Copyright (C) 2014 Noah Guttman.\n";
        print "This script is released and distributed under the terms of the GNU\n";
        print "General Public License.     >>>>    http://www.gnu.org/licenses/\n";
        print "";
        print "This program is free software: you can redistribute it and/or modify\n";
        print "it under the terms of the GNU General Public License as published by\n";
        print "the Free Software Foundation.\n\n";
        print "This program is distributed in the hope that it will be useful,\n";
        print "but WITHOUT ANY WARRANTY; without even the implied warranty of\n";
        print "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n";
        print "GNU General Public License for more details.\n";
        print ">>>>    http://www.gnu.org/licenses/\n";
        exit 0; 
}




##Set custom output if any set

if ($opt_P){
        $baseport=$opt_P;
}else{
        $baseport=5060;
}
if ($opt_t){
        $timeout=$opt_t*1000;
}
if ($opt_r){
        $retries=$opt_r;
}
if ($opt_c){
	$maxfailures = $opt_c;
}



#Check that the thread is responding
for (my $i=0; $i < $retries; $i++){
	if ($opt_I){
                $sock = IO::Socket::INET->new(
                Proto    => 'udp',
                PeerPort => $baseport,
                PeerAddr => $opt_H,
		LocalAddr => $opt_I,
                ) or die "Could not create socket: $!\n";
	}else{
	        $sock = IO::Socket::INET->new(
	        Proto    => 'udp',
	        PeerPort => $baseport,
	        PeerAddr => $opt_H,
	        ) or die "Could not create socket: $!\n";
	}
	$recvthread = threads->new( \&recive_rtpproxy_request);
	usleep(10000);
	$localIP = $sock->sockhost;
	$localPort = $sock->sockport;

	$optionPacket = "OPTIONS sip:test\@$opt_H SIP/2.0\nVia: SIP/2.0/UDP $localIP:$localPort;branch=z9hG4bK." .$randomstring->randpattern("cCncnCCcnCncnCcncC") .";rport;alias\nFrom: sip:nagios\@$localIP:$localPort;tag=" .$randomstring->randpattern("cCncnCc") ."\nTo: sip:test\@$opt_H\nCall-ID: " .$randomstring->randpattern("cCncnCCcnCncnCcncC") ."\@$localIP\nCSeq: 1 OPTIONS\nContact: sip:nagios\@$localIP:$localPort\nContent-Length: 0\nMax-Forwards: 70\nUser-Agent: NoahGuttman 0.5.0\nAccept: text/plain\n";
	if ($opt_d){
		$debug_output = $debug_output."\n------------------------------\nOPTIONS PACKET:$optionPacket\n------------------------------\n";
	}

        my $starttime = gettimeofday;
	$sock->send("$optionPacket") or die "Send error: $!\n";

	#reset timer
	$timer =0;
	while (($timer <= $timeout ) && ($gah !~ /SIP\/2.0 \d\d\d/i)){
		usleep(1000);
		$timer = $timer + 2000;
	}
	#reset timer
	$timer =0;

        $elapsed = (gettimeofday - $starttime)*1000;

	#reject bad time data
        if ($elapsed < 0){
                $elapsed='';
        }

	if ($opt_d){
                $debug_output = $debug_output."\n------------------------------\nRESPONSE PACKET:$gah\n------------------------------\n";
        }

	if ($gah =~ /SIP\/2.0 200/i){
		$recvthread->join();
		if ($ReturnMin > $elapsed){
			$ReturnMin = $elapsed;
		}
		if ($ReturnMax <$elapsed){
			$ReturnMax = $elapsed;
		}
		$ReturnTotal = $ReturnTotal + $elapsed;
		$testoktotal++;
		$ReturnAvg = ($ReturnTotal / $testoktotal);
		if ($opt_d){
			$debug_output = $debug_output."Stats so far : ReturnMin=$ReturnMin"."ms;; ReturnAvg=$ReturnAvg"."ms;; ReturnMax=$ReturnMax"."ms;;ICMP=$icmp_responsed;; Errors=$errors;; Timeouts=$response_timeout;;\n";
		}
	}elsif (($gah =~ /SIP\/2.0/i) && ($opt_s)){
                $recvthread->join();
                if ($ReturnMin > $elapsed){
                        $ReturnMin = $elapsed;
                }
                if ($ReturnMax <$elapsed){
                        $ReturnMax = $elapsed;
                }
                $ReturnTotal = $ReturnTotal + $elapsed;
                $testoktotal++;
                $ReturnAvg = ($ReturnTotal / $testoktotal);
                if ($opt_d){
			$debug_output = $debug_output."Stats so far : ReturnMin=$ReturnMin"."ms;; ReturnAvg=$ReturnAvg"."ms;; ReturnMax=$ReturnMax"."ms;;ICMP=$icmp_responsed;; Errors=$errors;; Timeouts=$response_timeout;;\n";
                }
	}elsif ($icmp_responsed >$testerrortotal){
		$recvthread->join();
		$testerrortotal++
	}elsif ($gah =~ /./){
                $errors++;
		$recvthread->join();
		$testerrortotal++
	}else{
		$recvthread->detach;
		$response_timeout++;
		$testerrortotal++;
	}
	$sock->close;
	$gah='';
	usleep (5000);
}
if ($testerrortotal == 0){
	print ("$opt_R $opt_H $opt_P OK: SIP Component has responded in an average of $ReturnAvg ms.");
	$exitcode = 0;
	if ($opt_d){
        	print ("$debug_output");
        }
	print ("|ReturnMin=$ReturnMin"."ms;; ReturnAvg=$ReturnAvg"."ms;; ReturnMax=$ReturnMax"."ms;; ICMP=$icmp_responsed;; Errors=$errors;; Timeouts=$response_timeout;;\n");
	exit ($exitcode);
}elsif ($testerrortotal < $maxfailures){
        print ("$opt_R $opt_H $opt_P Warning: SIP Component has responded with $testerrortotal errors and in an average of $ReturnAvg ms.");
        $exitcode = 1;
	if ($opt_M){
        	print ("$opt_M");
	}
        if ($opt_d){
                print ("$debug_output");
        }
        print ("|ReturnMin=$ReturnMin"."ms;; ReturnAvg=$ReturnAvg"."ms;; ReturnMax=$ReturnMax"."ms;; ICMP=$icmp_responsed;; Errors=$errors;; Timeouts=$response_timeout;;\n");
        exit ($exitcode);
}
#Catch all for all other cases
print ("$opt_R $opt_H $opt_P CRITICAL: SIP Component has not responded correctly after $testerrortotal attempts.");
$exitcode = 2;
if ($opt_M){
	print ("$opt_M");
}
if ($opt_d){
	print ("$debug_output");
}
print ("|ICMP=$icmp_responsed;; Errors=$errors;; Timeouts=$response_timeout;;\n");
exit ($exitcode);


sub recive_rtpproxy_request{
		$sock->recv($gah,128) or $icmp_responsed++;
		$sock->close;
}
