#!/usr/bin/perl -sw
#
# Created by Steve Voisey (srv) 21 Jan 2011.
# email: [steve.voisey@ericsson.com]
$version = "1.05";

# Some psuedo string constants, excuse for using as globals, makes life easier.
my $VALID_IP_ADDRESS_REGEX = "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+";
my $VALID_HOSTNAME_REGEX   = "[\\w\\d-]+";
my $VALID_PORT_REGEX       = "[0-9]+";
my $PING_SUCCESS_MSG       = "2 received, 0% packet loss";
my $TELNET_SUCCESS_MSG     = "Connected to";
my $WGET_SUCCESS_MSG       = "connected.";
my $SEP = ",";
my $DIVIDE = "=" x 80 . "\n";

# lazy global array
my @IGNORED;

if (defined($help))    { help(); exit;}

unless(defined($ignore))      { $ignore = "no"; }
unless(defined($action))      { $action = "ping"; }
unless(defined($max))         { $max = 1; }
unless(defined($newport))     { $newport = "NONE"; }
unless(defined($defaultport)) { $defaultport = "22"; }
unless(defined($destination)) { $destination = "ip"; }

unless(($destination =~ /ip/) || ($destination =~ /hostname/)) { die "Invalid input parameter destination [$destination]\n"; }

$localHostname = `hostname`;
chomp($localHostname);

$currentDate   = `date`;
chomp($currentDate);

print $DIVIDE;
print "Connectivity report for server [$localHostname] action [$action]\n";
print "                          date [$currentDate]\n\n";

if ( $action =~ m/hop/i )    { hop($max) };
if ( $action =~ m/ping/i )   { ping($destination) };
if ( $action =~ m/telnet/i ) { telnet($destination, $defaultport, $newport) };
if ( $action =~ m/wget/i )   { wget($destination, $defaultport, $newport) };

if ( $ignore =~ m/yes/i )  {
    print "\n";
    print $DIVIDE;
    foreach $element (@IGNORED) { print "ignored: $element\n"; }
}
exit;

##############################################################
# trace
##############################################################

sub hop {
    my $maxHops = $_[0];
    my (@tmp, @local, @remote);
    my ($ip, $message, $command, $traceRoute, $nextHop);

    foreach $line (<>) {
        cleanIp($line, \$ip, \$message); if ( $ip eq "BAD" ) { next; }
        unless ($ip =~ m/$VALID_IP_ADDRESS_REGEX/) { next; }
        $command = "traceroute -n -m $maxHops $ip";  
        $traceRoute = `$command`;
        chomp($traceRoute);      
        if ( $maxHops > 1 ) { 
            print "$traceRoute\n\n"; 
            @tmp = split("\n", $traceRoute);
            if ( @tmp > $maxHops ) { print "WARNING! max hop [$maxHops] reached\n\n"; }
            next; }

        @tmp = split("\n", $traceRoute);
        unless ( $tmp[1] =~ m/\s+($VALID_IP_ADDRESS_REGEX)\s+/) {
            # sometimes fails on first attempt, very weird, so lets try again.
            $traceRoute = `$command`;
            chomp($traceRoute);
            @tmp = split("\n", $traceRoute);
        }
        unless ( $tmp[1] =~ m/\s+($VALID_IP_ADDRESS_REGEX)\s+/) {
            # if its failed twice, then its a real fail.
            print "ERROR: no next hop found for...\n";
            print "command: $command\n";
            print "         $tmp[0]\n";
            print "         $tmp[1]\n";
            $traceRoute = "";
            next;
        }
        ($nextHop) = ($tmp[1] =~ m/\s+($VALID_IP_ADDRESS_REGEX)\s+/);
        if ( $ip eq $nextHop ) {
            push (@local, "${ip}${SEP}${nextHop}${SEP}${message}");
        } else {
            push (@remote, "${ip}${SEP}${nextHop}${SEP}${message}");
        }
    }

    if ( $maxHops > 1 ) { 
        # print "\n\nWHY are we here?\n"; 
        return; }
    
    print "Remote:\n";
    foreach $entry (@remote) {
        chomp($entry);
        @tmp = split("$SEP", $entry);
        printf "%-16s next hop --> %-16s - %-30s\n", $tmp[0], $tmp[1], $tmp[2];
    }

    print "\n\nLocal:\n";
    foreach $entry (@local) {
        @tmp = split("$SEP", $entry);
        printf "%-16s next hop --> %-16s - %-30s\n", $tmp[0], $tmp[1], $tmp[2];
    }
}

##############################################################
# ping
##############################################################

sub ping {
    my $destination = $_[0];
    my ($ip, $hostname, $message, $command, $response);
    foreach $line (<>) {
        $response = "";
        if ( $destination =~ /hostname/ ) {
            cleanHost($line, \$hostname, \$message); 
            if ( $hostname eq "BAD" ) { next; }
            #unless ($hostname =~ m/$VALID_HOSTNAME_REGEX/)  { next; }
            $server = $hostname;
        } else {
            cleanIp($line, \$ip, \$message); 
            if ( $ip eq "BAD" ) { next; }
            #unless ($ip =~ m/$VALID_IP_ADDRESS_REGEX/) { next; }
            $server = $ip;
        }
        $command = "ping -c 2 -W 3 $server";
        $response = `$command`;
        chomp($response);
        if ( $response =~ m/$PING_SUCCESS_MSG/ ) {
            printf "success: [%-28s] %-30s - %-30s\n", $server, $command, $message;
       } else {
            printf "failed:  [%-28s] %-30s - %-30s\n", $server, $command, $message;
       }
   }
}

##############################################################
# telnet
##############################################################

sub telnet {
    my $destination = $_[0];
    my $defaultport = $_[1];
    my $newport     = $_[2];
    
    my ($ip, $port, $message, $command, $line, $hostname, $server, $response);
    
    my $tmpFile = "telnet.response.tmp";
    if ( -f "$tmpFile" ) { unlink($tmpFile); }
    foreach $line (<>) {
        $response = "";
        if ( $destination =~ /hostname/ ) {
            cleanHostAndPort($line, \$hostname, \$port, \$message, $defaultport, $newport); 
            if(( $hostname eq "BAD") || ($port eq "BAD")) { next; }
            #unless ($hostname =~ m/$VALID_HOSTNAME_REGEX/)  { next; }
            #unless ($port =~ m/$VALID_PORT_REGEX/)    { next; }
            $server = $hostname;
        } else {
            cleanIpAndPort($line, \$ip, \$port, \$message, $defaultport, $newport); 
            if(( $ip eq "BAD") || ($port eq "BAD")) { next; }
            #unless ($ip =~ m/$VALID_IP_ADDRESS_REGEX/) { next; }
            #unless ($port =~ m/$VALID_PORT_REGEX/)    { next; }
            $server = $ip;
        }
        $command = <<ENDTELNET;
telnet $server $port <<END >${tmpFile} 2>&1
quit
quit
END    
ENDTELNET
        $response = `$command`;
        $cmd = "egrep '$TELNET_SUCCESS_MSG' $tmpFile > /dev/null";
        system("$cmd");
        # Perl weirdness, need to divide return value by 256
        $status = ( $? / 256 );
        
        if ( $status == 0 ) { 
            printf "success: [%-28s] port [%-6s] - %-30s\n", "$server", "$port", $message;
       } else {
            printf "failed:  [%-28s] port [%-6s] - %-30s\n", "$server", "$port", $message;
       }
       if ( -f "$tmpFile" ) { unlink($tmpFile); }
   }
}

##############################################################
# wget
##############################################################

sub wget {
    my $destination = $_[0];
    my $defaultport = $_[1];
    my $newport     = $_[2];
    
    my ($ip, $port, $message, $command, $line, $hostname, $server, $response);
    
    my $tmpOutputFile   = "wget.output.tmp";
    my $tmpResponseFile = "wget.response.tmp";
    if ( -f "$tmpOutputFile" )   { unlink($tmpOutputFile); }
    if ( -f "$tmpResponseFile" ) { unlink($tmpResponseFile); }
    foreach $line (<>) {
        $response = "";
        if ( $destination =~ /hostname/ ) {
            cleanHostAndPort($line, \$hostname, \$port, \$message, $defaultport, $newport); 
            if(( $hostname eq "BAD") || ($port eq "BAD")) { next; }
            #unless ($hostname =~ m/$VALID_HOSTNAME_REGEX/)  { next; }
            #unless ($port =~ m/$VALID_PORT_REGEX/)    { next; }
            $server = $hostname;
        } else {
            cleanIpAndPort($line, \$ip, \$port, \$message, $defaultport, $newport); 
            if(( $ip eq "BAD") || ($port eq "BAD")) { next; }
            #unless ($ip =~ m/$VALID_IP_ADDRESS_REGEX/) { next; }
            #unless ($port =~ m/$VALID_PORT_REGEX/)    { next; }
            $server = $ip;
        }
        $command = "wget ${server}:${port} -O $tmpOutputFile >${tmpResponseFile} 2>&1";
        $response = `$command`;
        $cmd = "egrep '$WGET_SUCCESS_MSG' $tmpResponseFile > /dev/null";
        system("$cmd");
        # Perl weirdness, need to divide return value by 256
        $status = ( $? / 256 );
        
        if ( $status == 0 ) { 
            printf "success: [%-28s] port [%-6s] - %-30s\n", "$server", "$port", $message;
       } else {
            printf "failed:  [%-28s] port [%-6s] - %-30s\n", "$server", "$port", $message;
       }
       if ( -f "$tmpOutputFile" )   { unlink($tmpOutputFile); }
       if ( -f "$tmpResponseFile" ) { unlink($tmpResponseFile); }
   }
}

##############################################################
# cleanIp
##############################################################

sub cleanIp {
    my $line  =  $_[0];
    my $refIp =  $_[1];
    my $refMsg = $_[2];
    
    my ($tmpIp, $tmpMsg);
    $$refIp   = "BAD";
    $$refMsg = "BAD";
    
    chomp($line);
    if ( $line =~ /^#/ )    { push(@IGNORED, $line); return("BAD"); }
    if ( $line =~ /^\s*$/ ) { push(@IGNORED, $line); return("BAD"); }
    
    ( $tmpIp, $tmpMsg ) = $line =~ /^($VALID_IP_ADDRESS_REGEX)[\s:]*(.*?)$/; 
    unless(defined($tmpMsg)) { $tmpMsg = ""; }
    unless(defined($tmpIp)) { push(@IGNORED, $line); return("BAD"); }
    $$refIp = $tmpIp;
    $$refMsg = $tmpMsg;
    return;
}

##############################################################
# cleanHost
##############################################################

sub cleanHost {
    my $line    = $_[0];
    my $refHost = $_[1];
    my $refMsg  = $_[2];
    
    my ($tmpHost, $tmpMsg);
    $$refHost = "BAD";
    $$refMsg  = "BAD";
    
    chomp($line);
    if ( $line =~ /^#/ )    { push(@IGNORED, $line); return("BAD"); }
    if ( $line =~ /^\s*$/ ) { push(@IGNORED, $line); return("BAD"); }
    
    ( $tmpHost, $tmpMsg ) = $line =~ /^($VALID_HOSTNAME_REGEX)[\s:]*(.*?)$/; 
    unless(defined($tmpMsg)) { $tmpMsg = ""; }
    unless(defined($tmpHost)) { push(@IGNORED, $line); return("BAD"); }
    $$refHost = $tmpHost;
    $$refMsg  = $tmpMsg;
    return;
}

##############################################################
# cleanIpAndPort
##############################################################

sub cleanIpAndPort {
    my $line    = $_[0];
    my $refIp   = $_[1];
    my $refPort = $_[2];
    my $refMsg  = $_[3];
    my $defaultport = $_[4];
    my $newport = $_[5];
    
    my ($tmpIp, $tmpPort, $tmpMsg);
    $$refIp   = "BAD";
    $$refPort = "BAD";
    $$refMsg  = "BAD";
    
    chomp($line);
    if ( $line =~ /^#/ )    { push(@IGNORED, $line); return("BAD"); }
    if ( $line =~ /^\s*$/ ) { push(@IGNORED, $line); return("BAD"); }
    
    ( $tmpIp, $tmpPort, $tmpMsg ) = $line =~ /^($VALID_IP_ADDRESS_REGEX)[\s:]($VALID_PORT_REGEX)\s*(.*?)$/; 
    
    unless(defined($tmpPort)) {
        # No port returned, use the default if we have one set.
        if($defaultport =~ /$VALID_PORT_REGEX/ ) {
            $tmpPort = $defaultport;
            cleanIp($line, \$tmpIp, \$tmpMsg);
            $tmpMsg = "<DEFAULT PORT> - " . $tmpMsg;
        } else {
            push(@IGNORED, $line); return("BAD"); 
        }
    }
    unless(defined($tmpMsg)) { $tmpMsg = ""; }
    unless(defined($tmpIp)) { push(@IGNORED, $line); return("BAD"); }   

    # Do we want to ignore the returned port and use a preffered value instead?
    # Yes if newport has been set to a valid value.
    if($newport =~ /$VALID_PORT_REGEX/ ) {
        $tmpPort = $newport;
        $tmpMsg = "<NEW PORT> - " . $tmpMsg;
    }
    
    $$refIp   = $tmpIp;
    $$refPort = $tmpPort;
    $$refMsg  = $tmpMsg;
    return;
}

##############################################################
# cleanHostAndPort
##############################################################

sub cleanHostAndPort {
    my $line    = $_[0];
    my $refHost = $_[1];
    my $refPort = $_[2];
    my $refMsg  = $_[3];
    my $defaultport = $_[4];
    my $newport = $_[5];
    
    my ($tmpHost, $tmpPort, $tmpMsg);
    $$refHost = "BAD";
    $$refPort = "BAD";
    $$refMsg  = "BAD";
    
    chomp($line);
    if ( $line =~ /^#/ )    { push(@IGNORED, $line); return("BAD"); }
    if ( $line =~ /^\s*$/ ) { push(@IGNORED, $line); return("BAD"); }
    
    ( $tmpHost, $tmpPort, $tmpMsg ) = $line =~ /^($VALID_HOSTNAME_REGEX)[\s:]($VALID_PORT_REGEX)\s*(.*?)$/; 
    
    unless(defined($tmpPort)) { push(@IGNORED, $line); return("BAD"); }
    unless(defined($tmpMsg)) { $tmpMsg = ""; }
    unless(defined($tmpHost)) { push(@IGNORED, $line); return("BAD"); }   

    # Do we want to ignore the returned port and use a preffered value instead?
    # Yes if newport has been set to a valid value.
    if($newport =~ /$VALID_PORT_REGEX/ ) {
        $tmpPort = $newport;
        $tmpMsg = "<NEW PORT> - " . $tmpMsg;
    }
    
    $$refHost = $tmpHost;
    $$refPort = $tmpPort;
    $$refMsg  = $tmpMsg;
    return;
}
##################################################################################
##################################################################################

sub help {

    $helpText1 = <<ENDHELPPAGE1;

  File: ipTest.pl    Version: $version
  
  example usage: 
  
        > cat ipFile.list   | ./ipTest.pl -action=ping 
        > cat hostFile.list | ./ipTest.pl -action=ping -destination=hostname
     or
        > cat ipFile.list | ./ipTest.pl -action=hop
     or
        > cat ipPort.list   | ./ipTest.pl -action=telnet
        > cat hostPort.list | ./ipTest.pl -action=ping -destination=hostname
        
    ipFile.list -   all lines starting with a comment '#' are ignored.
                    all blank lines are ignored.
                    all lines that do not start with a valid IP ( well almost ) are ignored.
                    all characters after the first valid IP on a line are ignored.

    hostFile.list - As ipFile.list except lines begin with a hostname instead of an IP address.
     
    ipPort.list   - As ipFile.list except lines begin with IP address [ space or : ] port number.
    
                    Example: 
                             10.10.10.10:80
                             10.10.10.10 80    
                             
    hostPort.list - As ipPort.list except lines begin with a hostname instead of an IP address.
    
    This means the connectivity of all IP's defined in the '/etc/hosts' file
    can easily be verified using the command:                  
   
        > cat /etc/hosts | ./ipTest.pl -action=ping

    or, using defaults
    
        > cat /etc/hosts | ./ipTest.pl
        
        action [ping|hop|telnet|wget]
                default 'ping'
                
                [ping]   Ping each IPIP|hostname passed from STDIN and report on the connectivity status.
                         Format:  ^nnn.nnn.nnn.nnn        ANY OTHER TEXT
                      or Format:  ^hostname               ANY OTHER TEXT
                      
                         success return string: [$PING_SUCCESS_MSG]
                         
                [hop]    Traceroute each IP passed from STDIN and report on the connectivity status.
                         Format:  ^nnn.nnn.nnn.nnn        ANY OTHER TEXT
                       
                [telnet] Telnet each IP|hostname and PORT passed from STDIN and report on the connectivity status.
                         Format:  ^nnn.nnn.nnn.nnn:nnnn   ANY OTHER TEXT
                      or Format:  ^hostname:nnnn          ANY OTHER TEXT
                         
                         success return string: [$TELNET_SUCCESS_MSG]
                         
                [wget]   wget each IP|hostname and PORT passed from STDIN and report on the connectivity status.
                         Format:  ^nnn.nnn.nnn.nnn:nnnn   ANY OTHER TEXT
                      or Format:  ^hostname:nnnn          ANY OTHER TEXT                         
                         
                         success return string: [$WGET_SUCCESS_MSG]
                         
                         where '^' refers to start of line.
                         
        ignore [true|false]
                default 'false'
                
                Print report of all ignored input lines.
                
        max    [integer]
                default 1
               
               max number of hops when action=hop
               If max=1 report on which ip's are on the local network and which ip's are remote.
               
        defaultport [nnnn]
                     default 22
                     
        newport     [nnnn]
                     default none
                     Override the given port with the new value.
                     
        destination [ip|hostname]
                     default ip
                     'telnet|wget|ping' only. 
                     Allow destination to be either a hostname or an ip address.
               
ENDHELPPAGE1
print $helpText1;

}

##############################################################
# end
##############################################################

