#!/usr/bin/env perl
use warnings;  
use strict;  

use AnyEvent;  
use AnyEvent::Impl::Perl;  
use AnyEvent::Socket;  
use AnyEvent::Handle;  
  
use MYDan::API::Agent;
use MYDan::Agent::Query;
use MYDan::Util::OptConf;
use IO::Socket;
use Term::ReadKey;
use POSIX qw( :sys_wait_h );
use Data::UUID;
use IO::Poll qw( POLLIN POLLHUP POLLOUT POLLERR);
use Term::Size;

$| ++;

=head1 SYNOPSIS

 $0 --host host

    get a shell from remote machine

 $0 --host host --listen 9999

 $0 --host host --listen 9999 [--addr 10.10.10.1]\
      [--user user(default `logname`)] [--sudo sudoer]

=cut

$MYDan::Util::OptConf::THIS = 'agent';
my $option = MYDan::Util::OptConf->load();
my %o = $option->get( qw( host=s listen=i addr=s user=s sudo=s ) )->dump();
$option->assert( 'host' );

my $cv = AE::cv;  

my $listen = $o{listen};

unless( $listen )
{
    my $scan = `nc -z localhost 65111-65333`;
    my %open = map{ $_ => 1 }my @open = $scan =~ / (\d+) /g;
    ( $listen ) = grep{ ! $open{$_} }65111 .. 65333 ;
}

my $socket = IO::Socket::INET->new (
    LocalPort => $listen,
    Type      => SOCK_STREAM,
    Reuse     => 1,
    Listen    => 1
) or die "listen $listen: $!\n";

$o{user} = `logname` and chop $o{user}  unless $o{user};

my $uuid = Data::UUID->new->create_str();

my ($cols, $rows) = Term::Size::chars *STDOUT{IO};

my %query = (
    env => +{ TERM => 'linux' },
    code => 'shell',
    argv => [ $o{addr}, $listen, $uuid, $rows, $cols ],
    map{ $_ => $o{$_} }qw( user sudo )
);

my $isc = $o{role} && $o{role} eq 'client' ? 1 : 0;
$query{node} = [ $o{host} ] if $isc;
my $query = MYDan::Agent::Query->dump( \%query );

eval{ $query = MYDan::API::Agent->new()->encryption( $query ) if $isc };
die "encryption fail:$@" if $@;

my $call='';
tcp_connect $o{host}, $o{port}, sub {  
   my ( $fh ) = @_  or die "tcp_connect: $!";  
   my $hdl; $hdl = new AnyEvent::Handle( 
       fh => $fh,
       on_read => sub {
           my $self = shift;
           $self->unshift_read (
               chunk => length $self->{rbuf},
               sub { $call .= $_[1]}
           );
       },
       on_eof => sub{
           undef $hdl;
           $cv->send;  
       }
   );  

   $hdl->push_write($query);  
   $hdl->push_shutdown;
};  

$cv->recv;  

die "call fail:$call\n" 
    unless $call && $call =~ /--- 0\n$/;

my $soc = $socket->accept();
$soc->blocking(0);

my $poll = IO::Poll->new();
$poll->mask( $soc => POLLIN  );
$poll->mask( \*STDIN => POLLIN );

ReadMode(4);

syswrite( $soc, $uuid, 36 );

while ( $poll->handles && $soc ) {
    $poll->poll();
    for my $handle ( $poll->handles( POLLIN ) ) 
    {
        my ( $data, $byte );
        if ( $handle eq $soc )
        {
            if ( $byte = sysread( $soc, $data, 1024 ) ) { syswrite( STDOUT, $data, $byte ); }
            else { $soc->shutdown(2); last; }
        }

        syswrite( $soc, $data, $byte ) 
            if ( $handle eq \*STDIN )
            &&  ( $byte = sysread( STDIN, $data, 1024 ) );
    }
    if( $poll->handles( POLLHUP | POLLERR) )
    {
        $soc->shutdown( 2 );
        last;
    }
}

ReadMode(0);
