#!/usr/local/bin/perl
#
# TmmRemote
#                               COPYRIGHT (C) 2002, SANO Taku & YMIRLINK Inc.
#                                               email: mikage@ymirlink.co.jp
#
# $Id: tmmremote.pl,v 1.9 2002/04/07 16:00:33 mikage Exp $
#

use strict;

use Fcntl;
use IO::Handle;
use IO::Select;
use IO::Socket::INET;

use Digest::MD5;


$| = 1;

$SIG{PIPE} = sub { print "!!! TMMREMOTE: Broken pipe. !!!\n"; exit; };

my $listfile = $ARGV[0];

if(!$listfile)
  {
    print "usage: tmmremote.pl listfile [scriptfile]\n";
    exit;
  }
if(!open(LIST, $listfile))
  {
    print "!!! can't open listfile. [$listfile] !!!\n";
    exit;
  }

my $scriptfile = $ARGV[1];
my $script;
my $scriptcmd;
if($scriptfile)
  {
    if(!open(SCRIPT, $scriptfile))
      {
	print "!!! can't open scriptfile [$scriptfile] !!!\n";
	exit;
      }
    local($/) = undef;
    $script = <SCRIPT>;
    close(SCRIPT);

    if($script =~ s/^#!([^\r\n]+)\r?\n//)
      {
	$scriptcmd = $1;
      }
    else
      {
	print "!!! can't fild #! line [$scriptfile] !!!\n";
	exit;
      }
  }


my @server;
my $authpass;
my $shortserver;

while(<LIST>)
  {
    next if(m/^\s*#/);
    
    my ($short, $server, $pass) = split(/:/, $_, 3);
    next if(!$pass);
    next if(!$server);
    next if(!$short);
    if(exists $authpass->{$server})
      {
	print "!!! duplicate server. [$server] !!!\n";
	exit;
      }
    
    push(@server, $server);
    $authpass->{$server} = $pass;
    $shortserver->{$server} = $short;
  }

close(LIST);

my $servernum = scalar(@server);
if(!$servernum)
  {
    print "!!! no server in list. [$listfile] !!!\n";
    exit;
  }
print "TmmRemote start... $servernum servers.\n";

my $remotesock = {};
my $remotesockr = {};

foreach my $server (@server)
  {
    print "console> connecting... ($shortserver->{$server}) $server ...";
    my $sock = IO::Socket::INET->new(PeerAddr => $server,
				     PeerPort => '78',
				     Proto    => 'tcp',
				    );
    if(defined($sock))
      {
	print " connected\n";
	$remotesock->{$server} = $sock;
	$remotesockr->{$sock} = $server;

	my $saltmd5;
	sysread($sock, $saltmd5, 32);
	my $myauth = Digest::MD5->new->add($saltmd5)
	  ->add($authpass->{$server})->hexdigest;
	
	syswrite($sock, $myauth, 32);

	my $result;
	sysread($sock, $result, 3);
	if($result eq "ok\n")
	  {
	  }
	elsif($result eq "ng\n")
	  {
	    print "!!! auth failed. [($shortserver->{$server})$server] !!!\n";
	    exit;
	  }
	else
	  {
	    print "!!! auth error. connection error? [($shortserver->{$server})$server] !!!\n";
	    exit;
	  }
      }
    else
      {
	print " error\n";
	print "!!! can't connected: ($shortserver->{$server}) $server !!!\n";
      }
  }

foreach my $fh (*STDIN, *STDOUT, values(%$remotesock))
  {
#    print "set... $fh\n";
    $fh->autoflush(1);
    fcntl($fh, F_SETFL,
	  fcntl($fh, F_GETFL, 0) | O_NONBLOCK);
  }

my $in_prompt;
if($script)
  {
    print "console> all server connected... execute command.\n";
    print "script-cmd> #!$scriptcmd\n";
  }
else
  {
    print "console> all server connected... enter command.\n";
    print "cmd> ";
    $in_prompt = 1;
  }

my $timeout = 0.1;
my $rs = new IO::Select;
my $ws = new IO::Select;

$rs->add(*STDIN);
foreach my $sock (values(%$remotesock))
  {
#    print "add $sock\n";
    $rs->add($sock);
  }


my $outputbuff = {};
my $inputbuff = {};
my $to_exit;

if($scriptcmd)
  {
    foreach my $server (keys %$remotesock)
      {
	$outputbuff->{$remotesock->{$server}} .= '#!' . $scriptcmd . "\n";
	$outputbuff->{$remotesock->{$server}} .= $script . "\x0d";
	$ws->add($remotesock->{$server});
      }
  }

while(1)
  {
#    print STDERR "check read\n";
    my $in_sleep = 1;
    
    my (@rh) = $rs->can_read($timeout);

    foreach my $rh (@rh)
      {
	my $buff;
	my $rsize = sysread($rh, $buff, 65536);
	undef $in_sleep;
#	print "read[$rh] $buff\n";
	
	if(!defined($rsize))
	  {
	    $rs->remove($rh);
	    syswrite(*STDOUT, "!!! $rh: read error closed. !!!\n");
	  }
	elsif($rsize == 0)
	  {
	    $rs->remove($rh);
#	    print "$rh: closed\n";
	    if($rh ne *STDIN)
	      {
		my $server = $remotesockr->{$rh};
		delete $remotesock->{$server};
		delete $remotesockr->{$rh};

		if(! scalar(keys %$remotesock))
		  {
		    &dispstdout("!!! All remote server shutdown. !!!\n");
		    $to_exit = 1;
		  }
	      }
	  }
	else
	  {
	    if ($rh eq *STDIN)
	      {
		foreach my $server (keys %$remotesock)
		  {
		    #		print "add $remotesock->{$server}\n";
		    $outputbuff->{$remotesock->{$server}} .= $buff;
		    $ws->add($remotesock->{$server});
		    if($buff =~ m/\n/)
		      {
			undef $in_prompt;
		      }
		  }
	      }
	    else
	      {
		my $server = $remotesockr->{$rh};
		$inputbuff->{$server} .= $buff;

		while ($inputbuff->{$server} =~ s/^(.*)\n//)
		  {
		    my $line = "($shortserver->{$server}) $1\n";
		    &dispstdout($line);
		  }
	      }
	  }
      }

#    print STDERR "check write\n";
    my (@wh) = $ws->can_write($timeout);

    foreach my $wh (@wh)
      {
	my $wsize = syswrite($wh, $outputbuff->{$wh});
	undef $in_sleep;
#	print "write[$wh] " . length($outputbuff->{$wh}) . "\n";
	
	if(!defined($wsize))
	  {
	    $ws->remove($wh);
	    &dispstdout("!!! $wh: write error closed. !!!\n");
	  }
	else
	  {
	    substr($outputbuff->{$wh}, 0, $wsize) = '';
	    #	print "$wh: $wsize\n";

	    if (length($outputbuff->{$wh}) == 0)
	      {
		$ws->remove($wh);
		# print "$wh: buff empty\n";
		
		if($to_exit)
		  {
		    print "TmmRemote exit...\n";
		    exit;
		  }
	      }
	  }
      }

    if($in_sleep and !$in_prompt)
      {
	syswrite(*STDOUT, "input> ");
	$in_prompt = 1;
      }
  }


sub dispstdout {
  my $line = shift;
  
  if($in_prompt)
    {
      $outputbuff->{*STDOUT} .= "\x0d";
      undef $in_prompt;
    }
  
  $outputbuff->{*STDOUT} .= $line;
  $ws->add(*STDOUT);
}









