You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
742 lines
18 KiB
742 lines
18 KiB
#!/usr/bin/env perl
|
|
#
|
|
# Copyright (c) 2009-2010 by Karl J. Runge <runge@karlrunge.com>
|
|
#
|
|
# ultravnc_repeater.pl is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or (at
|
|
# your option) any later version.
|
|
#
|
|
# ultravnc_repeater.pl is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with ultravnc_repeater.pl; if not, write to the Free Software
|
|
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
|
|
# or see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
my $usage = '
|
|
ultravnc_repeater.pl:
|
|
perl script implementing the ultravnc repeater
|
|
proxy protocol.
|
|
|
|
protocol: Listen on one port for vnc clients (default 5900.)
|
|
Listen on one port for vnc servers (default 5500.)
|
|
Read 250 bytes from connecting vnc client or server.
|
|
Accept ID:<string> from clients and servers, connect them
|
|
together once both are present.
|
|
|
|
The string "RFB 000.000\n" is sent to the client (the client
|
|
must understand this means send ID:... or host:port.)
|
|
Also accept <host>:<port> from clients and make the
|
|
connection to the vnc server immediately.
|
|
|
|
Note there is no authentication or security WRT ID names or
|
|
identities; it is up to the client and server to completely
|
|
manage that aspect and whether to encrypt the session, etc.
|
|
|
|
usage: ultravnc_repeater.pl [-r] [client_port [server_port]]
|
|
|
|
Use -r to refuse new server/client connections when there is an existing
|
|
server/client ID. The default is to close the previous one.
|
|
|
|
To write to a log file set the env. var ULTRAVNC_REPEATER_LOGFILE.
|
|
|
|
To run in a loop restarting the server if it exits set the env. var.
|
|
ULTRAVNC_REPEATER_LOOP=1 or ULTRAVNC_REPEATER_LOOP=BG, the latter
|
|
forks into the background. Set ULTRAVNC_REPEATER_PIDFILE to a file
|
|
to store the master pid in.
|
|
|
|
Set ULTRAVNC_REPEATER_NO_RFB=1 to disable sending "RFB 000.000" to
|
|
the client. Then this program acts as general TCP rendezvous tool.
|
|
|
|
Examples:
|
|
|
|
ultravnc_repeater.pl
|
|
ultravnc_repeater.pl -r
|
|
ultravnc_repeater.pl 5901
|
|
ultravnc_repeater.pl 5901 5501
|
|
|
|
env ULTRAVNC_REPEATER_LOOP=BG ULTRAVNC_REPEATER_LOGFILE=/tmp/u.log ultravnc_repeater.pl ...
|
|
|
|
';
|
|
|
|
use strict;
|
|
|
|
# Set up logging:
|
|
#
|
|
if (exists $ENV{ULTRAVNC_REPEATER_LOGFILE}) {
|
|
close STDOUT;
|
|
if (!open(STDOUT, ">>$ENV{ULTRAVNC_REPEATER_LOGFILE}")) {
|
|
die "ultravnc_repeater.pl: $ENV{ULTRAVNC_REPEATER_LOGFILE} $!\n";
|
|
}
|
|
close STDERR;
|
|
open(STDERR, ">&STDOUT");
|
|
}
|
|
select(STDERR); $| = 1;
|
|
select(STDOUT); $| = 1;
|
|
|
|
# interrupt handler:
|
|
#
|
|
my $looppid = '';
|
|
my $pidfile = '';
|
|
#
|
|
sub get_out {
|
|
lprint("$_[0]:\t$$ looppid=$looppid");
|
|
if ($looppid) {
|
|
kill 'TERM', $looppid;
|
|
fsleep(0.2);
|
|
}
|
|
unlink $pidfile if $pidfile;
|
|
cleanup();
|
|
exit 0;
|
|
}
|
|
|
|
sub lprint {
|
|
print STDERR scalar(localtime), ": ", @_, "\n";
|
|
}
|
|
|
|
# These are overridden in actual server thread:
|
|
#
|
|
$SIG{INT} = \&get_out;
|
|
$SIG{TERM} = \&get_out;
|
|
|
|
# pidfile:
|
|
#
|
|
sub open_pidfile {
|
|
if (exists $ENV{ULTRAVNC_REPEATER_PIDFILE}) {
|
|
my $pf = $ENV{ULTRAVNC_REPEATER_PIDFILE};
|
|
if (open(PID, ">$pf")) {
|
|
print PID "$$\n";
|
|
close PID;
|
|
$pidfile = $pf;
|
|
} else {
|
|
lprint("could not open pidfile: $pf - $! - continuing...");
|
|
}
|
|
delete $ENV{ULTRAVNC_REPEATER_PIDFILE};
|
|
}
|
|
}
|
|
|
|
####################################################################
|
|
# Set ULTRAVNC_REPEATER_LOOP=1 to have this script create an outer loop
|
|
# restarting itself if it ever exits. Set ULTRAVNC_REPEATER_LOOP=BG to
|
|
# do this in the background as a daemon.
|
|
|
|
if (exists $ENV{ULTRAVNC_REPEATER_LOOP}) {
|
|
my $csl = $ENV{ULTRAVNC_REPEATER_LOOP};
|
|
if ($csl ne 'BG' && $csl ne '1') {
|
|
die "ultravnc_repeater.pl: invalid ULTRAVNC_REPEATER_LOOP.\n";
|
|
}
|
|
if ($csl eq 'BG') {
|
|
# go into bg as "daemon":
|
|
setpgrp(0, 0);
|
|
my $pid = fork();
|
|
if (! defined $pid) {
|
|
die "ultravnc_repeater.pl: $!\n";
|
|
} elsif ($pid) {
|
|
wait;
|
|
exit 0;
|
|
}
|
|
if (fork) {
|
|
exit 0;
|
|
}
|
|
setpgrp(0, 0);
|
|
close STDIN;
|
|
if (! $ENV{ULTRAVNC_REPEATER_LOGFILE}) {
|
|
close STDOUT;
|
|
close STDERR;
|
|
}
|
|
}
|
|
delete $ENV{ULTRAVNC_REPEATER_LOOP};
|
|
|
|
if (exists $ENV{ULTRAVNC_REPEATER_PIDFILE}) {
|
|
open_pidfile();
|
|
}
|
|
|
|
lprint("ultravnc_repeater.pl: starting service. master-pid=$$");
|
|
while (1) {
|
|
$looppid = fork;
|
|
if (! defined $looppid) {
|
|
sleep 10;
|
|
} elsif ($looppid) {
|
|
wait;
|
|
} else {
|
|
exec $0, @ARGV;
|
|
exit 1;
|
|
}
|
|
lprint("ultravnc_repeater.pl: re-starting service. master-pid=$$");
|
|
sleep 1;
|
|
}
|
|
exit 0;
|
|
}
|
|
if (exists $ENV{ULTRAVNC_REPEATER_PIDFILE}) {
|
|
open_pidfile();
|
|
}
|
|
|
|
# End of background/daemon stuff.
|
|
####################################################################
|
|
|
|
use warnings;
|
|
use IO::Socket::INET;
|
|
use IO::Select;
|
|
|
|
# Test for INET6 support:
|
|
#
|
|
my $have_inet6 = 0;
|
|
eval "use IO::Socket::INET6;";
|
|
$have_inet6 = 1 if $@ eq "";
|
|
print "perl module IO::Socket::INET6 not available: no IPv6 support.\n" if ! $have_inet6;
|
|
|
|
my $prog = 'ultravnc_repeater';
|
|
my %ID;
|
|
|
|
my $refuse = 0;
|
|
my $init_timeout = 5;
|
|
|
|
if (@ARGV && $ARGV[0] =~ /-h/) {
|
|
print $usage;
|
|
exit 0;
|
|
}
|
|
if (@ARGV && $ARGV[0] eq '-r') {
|
|
$refuse = 1;
|
|
lprint("enabling refuse mode (-r).");
|
|
shift;
|
|
}
|
|
|
|
my $client_port = shift;
|
|
my $server_port = shift;
|
|
|
|
$client_port = 5900 unless $client_port;
|
|
$server_port = 5500 unless $server_port;
|
|
|
|
my $uname = `uname`;
|
|
|
|
my $repeater_bufsize = 250;
|
|
$repeater_bufsize = $ENV{BUFSIZE} if exists $ENV{BUFSIZE};
|
|
|
|
my ($RIN, $WIN, $EIN, $ROUT);
|
|
|
|
my $client_listen = IO::Socket::INET->new(
|
|
Listen => 10,
|
|
LocalPort => $client_port,
|
|
ReuseAddr => 1,
|
|
Proto => "tcp"
|
|
);
|
|
my $err1 = $!;
|
|
my $err2 = '';
|
|
$client_listen = '' if ! $client_listen;
|
|
|
|
my $client_listen6 = '';
|
|
if ($have_inet6) {
|
|
eval {$client_listen6 = IO::Socket::INET6->new(
|
|
Listen => 10,
|
|
LocalPort => $client_port,
|
|
ReuseAddr => 1,
|
|
Domain => AF_INET6,
|
|
LocalAddr => "::",
|
|
Proto => "tcp"
|
|
);};
|
|
$err2 = $!;
|
|
}
|
|
if (! $client_listen && ! $client_listen6) {
|
|
cleanup();
|
|
die "$prog: error: client listen on port $client_port: $err1 - $err2\n";
|
|
}
|
|
|
|
my $server_listen = IO::Socket::INET->new(
|
|
Listen => 10,
|
|
LocalPort => $server_port,
|
|
ReuseAddr => 1,
|
|
Proto => "tcp"
|
|
);
|
|
$err1 = $!;
|
|
$err2 = '';
|
|
$server_listen = '' if ! $server_listen;
|
|
|
|
my $server_listen6 = '';
|
|
if ($have_inet6) {
|
|
eval {$server_listen6 = IO::Socket::INET6->new(
|
|
Listen => 10,
|
|
LocalPort => $server_port,
|
|
ReuseAddr => 1,
|
|
Domain => AF_INET6,
|
|
LocalAddr => "::",
|
|
Proto => "tcp"
|
|
);};
|
|
$err2 = $!;
|
|
}
|
|
if (! $server_listen && ! $server_listen6) {
|
|
cleanup();
|
|
die "$prog: error: server listen on port $server_port: $err1 - $err2\n";
|
|
}
|
|
|
|
my $select = new IO::Select();
|
|
if (! $select) {
|
|
cleanup();
|
|
die "$prog: select $!\n";
|
|
}
|
|
|
|
$select->add($client_listen) if $client_listen;
|
|
$select->add($client_listen6) if $client_listen6;
|
|
$select->add($server_listen) if $server_listen;
|
|
$select->add($server_listen6) if $server_listen6;
|
|
|
|
$SIG{INT} = sub {cleanup(); exit;};
|
|
$SIG{TERM} = sub {cleanup(); exit;};
|
|
|
|
my $SOCK1 = '';
|
|
my $SOCK2 = '';
|
|
my $CURR = '';
|
|
|
|
lprint("$prog: starting up. pid: $$");
|
|
lprint("watching for IPv4 connections on $client_port/client.") if $client_listen;
|
|
lprint("watching for IPv4 connections on $server_port/server.") if $server_listen;
|
|
lprint("watching for IPv6 connections on $client_port/client.") if $client_listen6;
|
|
lprint("watching for IPv6 connections on $server_port/server.") if $server_listen6;
|
|
|
|
my $alarm_sock = '';
|
|
my $got_alarm = 0;
|
|
sub alarm_handler {
|
|
lprint("$prog: got sig alarm.");
|
|
if ($alarm_sock ne '') {
|
|
close $alarm_sock;
|
|
}
|
|
$alarm_sock = '';
|
|
$got_alarm = 1;
|
|
}
|
|
|
|
while (my @ready = $select->can_read()) {
|
|
foreach my $fh (@ready) {
|
|
if (($client_listen && $fh == $client_listen) || ($client_listen6 && $fh == $client_listen6)) {
|
|
lprint("new vnc client connecting.");
|
|
} elsif (($server_listen && $fh == $server_listen) || ($server_listen6 && $fh == $server_listen6)) {
|
|
lprint("new vnc server connecting.");
|
|
}
|
|
my $sock = $fh->accept();
|
|
if (! $sock) {
|
|
lprint("$prog: accept $!");
|
|
next;
|
|
}
|
|
|
|
if (($client_listen && $fh == $client_listen) || ($client_listen6 && $fh == $client_listen6)) {
|
|
if (exists $ENV{ULTRAVNC_REPEATER_NO_RFB} && $ENV{ULTRAVNC_REPEATER_NO_RFB}) {
|
|
lprint("ULTRAVNC_REPEATER_NO_RFB: not sending RFB 000.000");
|
|
} else {
|
|
my $str = "RFB 000.000\n";
|
|
my $len = length $str;
|
|
my $n = syswrite($sock, $str, $len, 0);
|
|
if ($n != $len) {
|
|
lprint("$prog: bad $str write: $n != $len $!");
|
|
close $sock;
|
|
}
|
|
}
|
|
}
|
|
|
|
my $buf = '';
|
|
my $size = $repeater_bufsize;
|
|
$size = 1024 unless $size;
|
|
|
|
$SIG{ALRM} = "alarm_handler";
|
|
$alarm_sock = $sock;
|
|
$got_alarm = 0;
|
|
alarm($init_timeout);
|
|
my $n = sysread($sock, $buf, $size);
|
|
alarm(0);
|
|
|
|
if ($got_alarm) {
|
|
lprint("$prog: read timed out: $!");
|
|
} elsif (! defined $n) {
|
|
lprint("$prog: read error: $!");
|
|
} elsif ($repeater_bufsize > 0 && $n != $size) {
|
|
lprint("$prog: short read $n != $size $!");
|
|
close $sock;
|
|
} elsif (($client_listen && $fh == $client_listen) || ($client_listen6 && $fh == $client_listen6)) {
|
|
do_new_client($sock, $buf);
|
|
} elsif (($server_listen && $fh == $server_listen) || ($server_listen6 && $fh == $server_listen6)) {
|
|
do_new_server($sock, $buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub do_new_client {
|
|
my ($sock, $buf) = @_;
|
|
|
|
if ($buf =~ /^ID:(\w+)/) {
|
|
my $id = $1;
|
|
if (exists $ID{$id} && exists $ID{$id}{client} && $ID{$id}{client} eq "0") {
|
|
if (!established($ID{$id}{sock})) {
|
|
lprint("server socket for ID:$id is no longer established, closing it.");
|
|
close $ID{$id}{sock};
|
|
delete $ID{$id};
|
|
} else {
|
|
lprint("server socket for ID:$id is still established.");
|
|
}
|
|
}
|
|
if (exists $ID{$id}) {
|
|
if ($ID{$id}{client}) {
|
|
my $ref = $refuse;
|
|
if ($ref && !established($ID{$id}{sock})) {
|
|
lprint("socket for ID:$id is no longer established, closing it.");
|
|
$ref = 0;
|
|
}
|
|
if ($ref) {
|
|
lprint("refusing extra vnc client for ID:$id.");
|
|
close $sock;
|
|
return;
|
|
} else {
|
|
lprint("closing and deleting previous vnc client with ID:$id.");
|
|
close $ID{$id}{sock};
|
|
|
|
lprint("storing new vnc client with ID:$id.");
|
|
$ID{$id}{client} = 1;
|
|
$ID{$id}{sock} = $sock;
|
|
}
|
|
} else {
|
|
lprint("hooking up new vnc client with existing vnc server for ID:$id.");
|
|
my $sock2 = $ID{$id}{sock};
|
|
delete $ID{$id};
|
|
hookup($sock, $sock2, "ID:$id");
|
|
}
|
|
} else {
|
|
lprint("storing new vnc client with ID:$id.");
|
|
$ID{$id}{client} = 1;
|
|
$ID{$id}{sock} = $sock;
|
|
}
|
|
} else {
|
|
my $str = sprintf("%s", $buf);
|
|
$str =~ s/\s*$//g;
|
|
$str =~ s/\0*$//g;
|
|
my $host = '';
|
|
my $port = '';
|
|
if ($str =~ /^(.+):(\d+)$/) {
|
|
$host = $1;
|
|
$port = $2;
|
|
} else {
|
|
$host = $str;
|
|
$port = 5900;
|
|
}
|
|
if ($port < 0) {
|
|
my $pnew = -$port;
|
|
lprint("resetting port from $port to $pnew.");
|
|
$port = $pnew;
|
|
} elsif ($port < 200) {
|
|
my $pnew = $port + 5900;
|
|
lprint("resetting port from $port to $pnew.");
|
|
$port = $pnew;
|
|
}
|
|
lprint("making vnc client connection directly to vnc server host='$host' port='$port'.");
|
|
my $sock2 = IO::Socket::INET->new(
|
|
PeerAddr => $host,
|
|
PeerPort => $port,
|
|
Proto => "tcp"
|
|
);
|
|
if (! $sock2 && $have_inet6) {
|
|
lprint("IPv4 connect error: $!, trying IPv6 ...");
|
|
eval{$sock2 = IO::Socket::INET6->new(
|
|
PeerAddr => $host,
|
|
PeerPort => $port,
|
|
Proto => "tcp"
|
|
);};
|
|
lprint("IPv6 connect error: $!") if !$sock2;
|
|
} else {
|
|
lprint("IPv4 connect error: $!") if !$sock2;
|
|
}
|
|
if (!$sock2) {
|
|
lprint("failed to connect to $host:$port.");
|
|
close $sock;
|
|
return;
|
|
}
|
|
hookup($sock, $sock2, "$host:$port");
|
|
}
|
|
}
|
|
|
|
sub do_new_server {
|
|
my ($sock, $buf) = @_;
|
|
|
|
if ($buf =~ /^ID:(\w+)/) {
|
|
my $id = $1;
|
|
my $store = 1;
|
|
if (exists $ID{$id} && exists $ID{$id}{client} && $ID{$id}{client} eq "1") {
|
|
if (!established($ID{$id}{sock})) {
|
|
lprint("client socket for ID:$id is no longer established, closing it.");
|
|
close $ID{$id}{sock};
|
|
delete $ID{$id};
|
|
} else {
|
|
lprint("client socket for ID:$id is still established.");
|
|
}
|
|
}
|
|
if (exists $ID{$id}) {
|
|
if (! $ID{$id}{client}) {
|
|
my $ref = $refuse;
|
|
if ($ref && !established($ID{$id}{sock})) {
|
|
lprint("socket for ID:$id is no longer established, closing it.");
|
|
$ref = 0;
|
|
}
|
|
if ($ref) {
|
|
lprint("refusing extra vnc server for ID:$id.");
|
|
close $sock;
|
|
return;
|
|
} else {
|
|
lprint("closing and deleting previous vnc server with ID:$id.");
|
|
close $ID{$id}{sock};
|
|
|
|
lprint("storing new vnc server with ID:$id.");
|
|
$ID{$id}{client} = 0;
|
|
$ID{$id}{sock} = $sock;
|
|
}
|
|
} else {
|
|
lprint("hooking up new vnc server with existing vnc client for ID:$id.");
|
|
my $sock2 = $ID{$id}{sock};
|
|
delete $ID{$id};
|
|
hookup($sock, $sock2, "ID:$id");
|
|
}
|
|
} else {
|
|
lprint("storing new vnc server with ID:$id.");
|
|
$ID{$id}{client} = 0;
|
|
$ID{$id}{sock} = $sock;
|
|
}
|
|
} else {
|
|
lprint("invalid ID:NNNNN string for vnc server: $buf");
|
|
close $sock;
|
|
return;
|
|
}
|
|
}
|
|
|
|
sub established {
|
|
my $fh = shift;
|
|
|
|
return established_linux_proc($fh);
|
|
|
|
# not working:
|
|
my $est = 1;
|
|
my $str = "Z";
|
|
my $res;
|
|
#$res = recv($fh, $str, 1, MSG_PEEK | MSG_DONTWAIT);
|
|
if (defined($res)) {
|
|
lprint("established OK: $! '$str'.");
|
|
$est = 1;
|
|
} else {
|
|
# would check for EAGAIN here to decide ...
|
|
lprint("established err: $! '$str'.");
|
|
$est = 1;
|
|
}
|
|
return $est;
|
|
}
|
|
|
|
|
|
sub established_linux_proc {
|
|
# hack for Linux to see if remote side has gone away:
|
|
my $fh = shift;
|
|
|
|
# if we can't figure things out, we must return true.
|
|
if ($uname !~ /Linux/) {
|
|
return 1;
|
|
}
|
|
|
|
my @proc_net_tcp = ();
|
|
if (-e "/proc/net/tcp") {
|
|
push @proc_net_tcp, "/proc/net/tcp";
|
|
}
|
|
if (-e "/proc/net/tcp6") {
|
|
push @proc_net_tcp, "/proc/net/tcp6";
|
|
}
|
|
if (! @proc_net_tcp) {
|
|
return 1;
|
|
}
|
|
|
|
my $n = fileno($fh);
|
|
if (!defined($n)) {
|
|
return 1;
|
|
}
|
|
|
|
my $proc_fd = "/proc/$$/fd/$n";
|
|
if (! -e $proc_fd) {
|
|
return 1;
|
|
}
|
|
|
|
my $val = readlink($proc_fd);
|
|
if (! defined $val || $val !~ /socket:\[(\d+)\]/) {
|
|
return 1;
|
|
}
|
|
my $num = $1;
|
|
|
|
my $st = '';
|
|
|
|
foreach my $tcp (@proc_net_tcp) {
|
|
if (! open(TCP, "<$tcp")) {
|
|
next;
|
|
}
|
|
while (<TCP>) {
|
|
next if /^\s*[A-z]/;
|
|
chomp;
|
|
# sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
|
|
# 170: 0102000A:170C FE02000A:87FA 01 00000000:00000000 00:00000000 00000000 1001 0 423294766 1 f6fa4100 21 4 4 2 -1
|
|
# 172: 0102000A:170C FE02000A:87FA 08 00000000:00000001 00:00000000 00000000 1001 0 423294766 1 f6fa4100 21 4 4 2 -1
|
|
my @items = split(' ', $_);
|
|
my $state = $items[3];
|
|
my $inode = $items[9];
|
|
if (!defined $state || $state !~ /^\d+$/) {
|
|
next;
|
|
}
|
|
if (!defined $inode || $inode !~ /^\d+$/) {
|
|
next;
|
|
}
|
|
if ($inode == $num) {
|
|
$st = $state;
|
|
last;
|
|
}
|
|
}
|
|
close TCP;
|
|
last if $st ne '';
|
|
}
|
|
|
|
if ($st ne '' && $st != 1) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub handler {
|
|
lprint("\[$$/$CURR] got SIGTERM.");
|
|
close $SOCK1 if $SOCK1;
|
|
close $SOCK2 if $SOCK2;
|
|
exit;
|
|
}
|
|
|
|
sub hookup {
|
|
my ($sock1, $sock2, $tag) = @_;
|
|
|
|
my $worker = fork();
|
|
|
|
if (! defined $worker) {
|
|
lprint("failed to fork worker: $!");
|
|
close $sock1;
|
|
close $sock2;
|
|
return;
|
|
} elsif ($worker) {
|
|
close $sock1;
|
|
close $sock2;
|
|
wait;
|
|
} else {
|
|
cleanup();
|
|
if (fork) {
|
|
exit 0;
|
|
}
|
|
setpgrp(0, 0);
|
|
$SOCK1 = $sock1;
|
|
$SOCK2 = $sock2;
|
|
$CURR = $tag;
|
|
$SIG{TERM} = "handler";
|
|
$SIG{INT} = "handler";
|
|
xfer_both($sock1, $sock2);
|
|
exit 0;
|
|
}
|
|
}
|
|
|
|
sub xfer {
|
|
my ($in, $out) = @_;
|
|
|
|
$RIN = $WIN = $EIN = "";
|
|
$ROUT = "";
|
|
vec($RIN, fileno($in), 1) = 1;
|
|
vec($WIN, fileno($in), 1) = 1;
|
|
$EIN = $RIN | $WIN;
|
|
|
|
my $buf;
|
|
|
|
while (1) {
|
|
my $nf = 0;
|
|
while (! $nf) {
|
|
$nf = select($ROUT=$RIN, undef, undef, undef);
|
|
}
|
|
my $len = sysread($in, $buf, 8192);
|
|
if (! defined($len)) {
|
|
next if $! =~ /^Interrupted/;
|
|
lprint("\[$$/$CURR] $!");
|
|
last;
|
|
} elsif ($len == 0) {
|
|
lprint("\[$$/$CURR] Input is EOF.");
|
|
last;
|
|
}
|
|
my $offset = 0;
|
|
my $quit = 0;
|
|
while ($len) {
|
|
my $written = syswrite($out, $buf, $len, $offset);
|
|
if (! defined $written) {
|
|
lprint("\[$$/$CURR] Output is EOF. $!");
|
|
$quit = 1;
|
|
last;
|
|
}
|
|
$len -= $written;
|
|
$offset += $written;
|
|
}
|
|
last if $quit;
|
|
}
|
|
close($out);
|
|
close($in);
|
|
lprint("\[$$/$CURR] finished xfer.");
|
|
}
|
|
|
|
sub xfer_both {
|
|
my ($sock1, $sock2) = @_;
|
|
|
|
my $parent = $$;
|
|
|
|
my $child = fork();
|
|
|
|
if (! defined $child) {
|
|
lprint("$prog\[$$/$CURR] failed to fork: $!");
|
|
return;
|
|
}
|
|
|
|
$SIG{TERM} = "handler";
|
|
$SIG{INT} = "handler";
|
|
|
|
if ($child) {
|
|
lprint("[$$/$CURR] parent 1 -> 2.");
|
|
xfer($sock1, $sock2);
|
|
select(undef, undef, undef, 0.25);
|
|
if (kill 0, $child) {
|
|
select(undef, undef, undef, 0.9);
|
|
if (kill 0, $child) {
|
|
lprint("\[$$/$CURR] kill TERM child $child");
|
|
kill "TERM", $child;
|
|
} else {
|
|
lprint("\[$$/$CURR] child $child gone.");
|
|
}
|
|
}
|
|
} else {
|
|
select(undef, undef, undef, 0.05);
|
|
lprint("[$$/$CURR] child 2 -> 1.");
|
|
xfer($sock2, $sock1);
|
|
select(undef, undef, undef, 0.25);
|
|
if (kill 0, $parent) {
|
|
select(undef, undef, undef, 0.8);
|
|
if (kill 0, $parent) {
|
|
lprint("\[$$/$CURR] kill TERM parent $parent.");
|
|
kill "TERM", $parent;
|
|
} else {
|
|
lprint("\[$$/$CURR] parent $parent gone.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub fsleep {
|
|
my ($time) = @_;
|
|
select(undef, undef, undef, $time) if $time;
|
|
}
|
|
|
|
sub cleanup {
|
|
close $client_listen if $client_listen;
|
|
close $client_listen6 if $client_listen6;
|
|
close $server_listen if $server_listen;
|
|
close $server_listen6 if $server_listen6;
|
|
foreach my $id (keys %ID) {
|
|
close $ID{$id}{sock};
|
|
}
|
|
}
|