i am writing a kind of remote shell. Therefore a pseudo-tty is created with IO::Pty on the remote machine. In it the bash is started.
Its output should be sent to the local machine using a tcp-connection.
In the other direction, the local STDIN should be sent to the remote machine, which gives it to the pseudo-tty.
That's my approach (sorry for the length):
client.pl:
----------
#!/usr/bin/perl -w
use strict;
use IO::Socket;
use Term::ReadKey;
my ($host, $port, $handle);
$host = "localhost";
$port = 1234;
$handle = IO::Socket::INET->new (
Proto => "tcp",
PeerAddr => $host,
PeerPort => $port)
or die "Could not connect to $host on Port $port: $!";
$handle->autoflush();
print STDERR "[Connected with $host:$port]\n";
defined (my $child = fork()) or die "Could not fork: $!";
my $char;
ReadMode(4);
if ($child) {
while (defined($char = getc($handle))) {
print STDOUT $char;
}
} else {
while (defined($char = getc(STDIN))) {
print $handle $char;
}
}
#############################
server.pl:
----------
#!/usr/bin/perl -w
use strict;
use IO::Pty;
use IO::Socket;
use POSIX 'setsid';
use Term::ReadKey;
my $server_port = "1234";
my $server_address = "localhost";
my $server = IO::Socket::INET->new(
LocalPort => $server_port,
LocalAddr => $server_address,
Type => SOCK_STREAM,
Reuse => 1,
Listen => 5)
or die "Cannot be a server on $server_port: $@\n";
my $client = $server->accept();
sub do_cmd {
my ($cmd, @args) = @_;
my $pty = IO::Pty->new or die "Could not create pty: $!";
defined (my $child = fork) or die "Could not fork: $!";
return $pty if $child;
POSIX::setsid();
my $tty = $pty->slave;
close $pty;
STDIN->fdopen($tty, "<") or die "STDIN: $!";
STDOUT->fdopen($tty, ">") or die "STDOUT: $!";
STDERR->fdopen(\*STDOUT, ">") or die "STDERR: $!";
close $tty;
$| = 1;
exec $cmd, @args;
die "Could not execute program: $!";
}
my $bash = do_cmd('bash');
defined (my $child = fork()) or die "Could not fork: $!";
my $char;
ReadMode(4);
if ($child) {
while (defined($char = getc($bash))) {
print $client $char;
}
} else {
while (defined($zeichen = getc($client))) {
print $bash $zeichen;
}
}
##########################
For testing you first have to start server.pl and after that client.pl.
As you can (hopefully) see, it doesn't work really good. E.g. you have to press enter to see the first output (like user@hostname:~$) and using the mc you also have to press enter a lot of times when it shouldn't be necessary.
I am happy about any ideas how it could work correctly. Thank you.
Michael Pradel.
[...]
> For testing you first have to start server.pl and after that client.pl.
>
> As you can (hopefully) see, it doesn't work really good. E.g. you have to press enter to see the first output (like user@hostname:~$) and using the mc you also have to press enter a lot of times when it shouldn't be necessary.
>
> I am happy about any ideas how it could work correctly. Thank you.
I think your terminal is probably in canonical mode. This means input
is processed by line, not character, so getc() only begins to read
characters once EOL is received.
2) eyrie:~% perl -wle 'while (1) { print ord getc(STDIN) }'
input
105
110
112
117
116
10
^C
There are ways to turn off canonical mode. Read about stty(1) and the
POSIX module.
There are programs to do this already. I have written one, and it's
available at http://poe.perl.org/?POE_Cookbook/Job_Server
Finally, you're in for a world of hurt if you let anyone with a telnet
client run a shell on your machine. Seriously consider using sshd
instead.
-- Rocco Caputo - tr...@pobox.com - http://poe.perl.org/
I use 'ReadMode(4)' from the module Term::Readkey. This should set the
terminal in 'raw mode'. I also tried 'ReadMode(3)', which means 'cbreak
mode'. I thought both are non-canonical (?).
I also tried it with a 'system("stty -icanon")' instead, but it also
didn't work.
Any other ideas?
> Finally, you're in for a world of hurt if you let anyone with a telnet
> client run a shell on your machine. Seriously consider using sshd
> instead.
Oh yes - i know. That's only for testing.