#!/usr/bin/env perl
use Cwd qw(realpath);
use IO::Socket;
use IPC::Open2;

$SSH = 'ssh -a -x -oFallBackToRsh=no';
$Xeq = "/tmp/netchk.$$";
$Ppath = realpath($0);
chomp($myHost=`hostname`);
$| = 1;

# 1st token is what to do
#
  $Op = shift(@ARGV);
  $Initial = ($Op eq 'ssh' || $Op eq 'tcp');
  $Inter   = ($Op eq 'SSH' || $Op eq 'TCP');
  Usage("Operation (ssh or tcp) not specified.") if !$Op;
  Usage("Operation '$Op' is neither 'ssh' nor 'tcp'.") if !($Initial||$Inter);
  $Op = uc($Op); $doTCP = ($Op eq 'TCP');

# Get port to listen on and validate it
#
  if ($doTCP && $Inter)
     {($Src, $Lport) = split(/:/, shift(@ARGV), 2);
      ValPort('listen', $myHost, $Lport, 1);
     }

# Get the first hop and rest of the hops and construct command line
#
  $Hop = shift(@ARGV); ($Hop, $Port) = split(/:/, $Hop, 2);
  if ($Hop)
     {($Usr, $Hop) = split(/@/, $Hop, 2);
      if (!$Hop) {$Hop = $Usr;}
         else {$Lgn = "-l $Usr";}
     } else {Usage("Target host not specified.") if $Initial;}
  ValPort('tcp', $Hop, $Port) if $doTCP && $Hop;
  while($HPval = shift(@ARGV))
       {($Hval, $Pval) = split(/:/, $HPval, 2);
        if ($doTCP) {ValPort('tcp', $Hval, $Pval);}
           else {$HPval = $Hval;}
        $Rest .= $HPval.' ';
       }
  chop($Rest); $Op .= " $myHost:$Port" if $doTCP;

# Tell user where we are
#
  if ($Inter)
     {if ($doTCP) {Accept($Src, $Lport);}
         else     {print "Reached $myHost via ssh!\n";}
     }

# Send program if we have a hop
#
  if ($Hop)
     {$Cmd = "$SSH $Lgn $Hop 'cat > $Xeq; chmod +x $Xeq; $Xeq $Op $Rest; exit'";
      Usage("Cannot open $Ppath; $!",1) if !open(PGM, $Ppath);
      @Prog = <PGM>; close(PGM);
      $myProg = join("", @Prog);
      print "Moving on to $Hop. . .\n";
      Usage("Unable to launch $CMD; $!",1) if !open2(*README, *WRITEME, $Cmd);
      print WRITEME "$myProg"; close(WRITEME);
      Connect($Hop, $Port) if $doTCP;
      while($Output = <README>) {print $Output;}
     }

unlink($Xeq);
exit(0);

sub Accept {my($Src, $Port) = @_;
  my $sock = new IO::Socket::INET (LocalHost => $myHost, LocalPort => $Port,
                                   Proto => 'tcp', Listen => 1, Reuse => 1,);

# If socket created, tell waiter to connect, and accept the connection
#
  Usage("Could not create socket on $myHost; $!",1) unless $sock;
  print "$myHost is listening on port $Lport for $Src\nOK\n";
  my $new_sock = $sock->accept();
  Usage("Accept failed on $myHost; $!") unless $new_sock;
  $Src = $new_sock->peerhost() if !$Src;
  $new_sock->autoflush(1);
  $new_sock->recv($Ack, 256);
  if ($Ack) {$new_sock->send("$Ack");}
     else {print "$Src connect to $myHost but did not send ack.\n";}
  close($sock); close($new_sock);
}

sub Connect {my($Host, $Port) = @_; my($Output, $Resp, $Msg);

# Wait until dest sends an OK to connect
#
  while(($Output = <README>) && ($Output ne "OK\n")) {print $Output;}
  Usage("Unable to connect to $Host:$Port; lost ssh connection.",1) if !$Output;

# Now connect and tell the acceptor who we are
#
  my $sock = new IO::Socket::INET (PeerAddr => $Host, PeerPort => $Port,
                                   Proto    => 'tcp',);
  Usage("Could not create socket on $myHost; $!",1) unless $sock;
  $sock->autoflush(1);
  $Msg = getppid().".$$";
  $sock->send($Msg);
  $sock->recv($Resp, 256);
  if (!$Resp) {print "Connected to $Host:$Port but no ack received.\n";}
     {if ($Resp eq  $Msg) {print "$myHost can communicate with $Host\n";}
         else {print "Connected to $Host:$Port but message exhange failed.";}
     }
  close($sock);
}

sub ValPort {my($What, $Host, $Port, $Snuff) = @_;
   Usage(ucfirst($What)." port not specified for $Host", $Snuff) if !$Port;
   Usage("'$Port' is an invalid $What port for $Host.", $Snuff)
        if !($Port =~ m/^\d+$/);
}

sub Usage {my($txt, $kll) = @_;
    print "netchk: $txt\n";
    if (!$kll)
       {print "Usage: netchk {ssh|tcp} [<usr>@]<host>:<port> [[<usr>@]<host>:<port> [...]]\n";
        print "       ssh only checks if you can ssh through the nodes and <port> is ignored.\n";
        print "       tcp checks for end-to-end message transitivety   and <port> is required.\n";
       }
    exit(8);
}
