summaryrefslogtreecommitdiffstats
path: root/x11vnc/misc
diff options
context:
space:
mode:
Diffstat (limited to 'x11vnc/misc')
-rw-r--r--x11vnc/misc/Makefile.am2
-rwxr-xr-xx11vnc/misc/connect_switch422
-rwxr-xr-xx11vnc/misc/ultravnc_repeater.pl424
3 files changed, 847 insertions, 1 deletions
diff --git a/x11vnc/misc/Makefile.am b/x11vnc/misc/Makefile.am
index efc5443..f814315 100644
--- a/x11vnc/misc/Makefile.am
+++ b/x11vnc/misc/Makefile.am
@@ -1,3 +1,3 @@
SUBDIRS = turbovnc
DIST_SUBDIRS = turbovnc
-EXTRA_DIST=README blockdpy.c dtVncPopup rx11vnc rx11vnc.pl shm_clear ranfb.pl slide.pl vcinject.pl x11vnc_loop Xdummy
+EXTRA_DIST=README blockdpy.c dtVncPopup rx11vnc rx11vnc.pl shm_clear ranfb.pl slide.pl vcinject.pl x11vnc_loop Xdummy ultravnc_repeater.pl connect_switch
diff --git a/x11vnc/misc/connect_switch b/x11vnc/misc/connect_switch
new file mode 100755
index 0000000..ad6d138
--- /dev/null
+++ b/x11vnc/misc/connect_switch
@@ -0,0 +1,422 @@
+#!/usr/bin/perl
+#
+# Copyright (c) 2006-2009 by Karl J. Runge <runge@karlrunge.com>
+#
+# connect_switch 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.
+#
+# connect_switch 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 connect_switch; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
+# or see <http://www.gnu.org/licenses/>.
+#
+#
+# connect_switch:
+#
+# A kludge script that sits between web clients and a mod_ssl (https)
+# enabled apache webserver.
+#
+# If an incoming web client connection makes a proxy CONNECT request
+# it is handled directly by this script (apache is not involved).
+# Otherwise, all other connections are forwarded to the apache webserver.
+#
+# This can be useful for VNC redirection using an existing https (port
+# 443) webserver, thereby not requiring a 2nd (non-https) port open on
+# the firewall for the CONNECT requests.
+#
+# It does not seem possible (to me) to achieve this entirely within apache
+# because the CONNECT request appears to be forwarded encrypted to
+# the remote host and so the SSL dies immediately.
+#
+# Note: There is no need to use this script for a non-ssl apache webserver
+# port because mod_proxy works fine for doing the switching all inside
+# apache (see ProxyRequests and AllowCONNECT parameters).
+#
+# Apache configuration:
+#
+# The mod_ssl configuration is often in a file named ssl.conf. In the
+# simplest case you change something like this:
+#
+# From:
+#
+# Listen 443
+#
+# <VirtualHost _default_:443>
+# ...
+# </VirtualHost>
+#
+# To:
+#
+# Listen 127.0.0.1:443
+#
+# <VirtualHost _default_:443>
+# ...
+# </VirtualHost>
+#
+# (i.e. just change the Listen directive).
+#
+# If you have mod_ssl listening on a different internal port, you do
+# not need to specify the localhost Listen address.
+#
+# It is probably a good idea to set $listen_host below to the known
+# IP address you want the service to listen on (to avoid localhost where
+# apache is listening).
+
+
+############################################################################
+# The defaults for hosts and ports (you can override them below if needed):
+#
+# Look below for these environment variables that let you set the various
+# parameters without needing to edit this script:
+#
+# CONNECT_SWITCH_LISTEN
+# CONNECT_SWITCH_HTTPD
+# CONNECT_SWITCH_ALLOWED
+# CONNECT_SWITCH_ALLOW_FILE
+# CONNECT_SWITCH_VERBOSE
+# CONNECT_SWITCH_APPLY_VNC_OFFSET
+# CONNECT_SWITCH_VNC_OFFSET
+
+my $hostname = `hostname`;
+chomp $hostname;
+
+my $listen_host = $hostname;
+my $listen_port = 443;
+
+if (exists $ENV{CONNECT_SWITCH_LISTEN}) {
+ # E.g. CONNECT_SWITCH_LISTEN=192.168.0.32:443
+ ($listen_host, $listen_port) = split(/:/, $ENV{CONNECT_SWITCH_LISTEN});
+}
+
+my $httpd_host = 'localhost';
+my $httpd_port = 443;
+
+if (exists $ENV{CONNECT_SWITCH_HTTPD}) {
+ # E.g. CONNECT_SWITCH_HTTPD=127.0.0.1:443
+ ($httpd_host, $httpd_port) = split(/:/, $ENV{CONNECT_SWITCH_HTTPD});
+}
+
+############################################################################
+# You can/should override the host/port settings here:
+#
+#$listen_host = '23.45.67.89'; # set to your interface IP number.
+#$listen_port = 555; # and/or nonstandard port.
+#$httpd_host = 'somehost'; # maybe you redir https to another machine.
+#$httpd_port = 666; # and/or nonstandard port.
+
+# You must set the allowed host:port CONNECT redirection list.
+# Only these host:port pairs will be redirected to.
+#
+my @allowed = qw(
+ machine1:5915
+ machine2:5900
+);
+
+if (exists $ENV{CONNECT_SWITCH_ALLOWED}) {
+ #
+ # E.g. CONNECT_SWITCH_ALLOWED=machine1:5915,machine2:5900
+ #
+ @allowed = split(/,/, $ENV{CONNECT_SWITCH_ALLOWED});
+}
+
+# Or you could also use an external "allow file".
+# They get added to the @allowed list.
+# The file is re-read for each new connection.
+#
+# Format of $allow_file:
+#
+# host1 vncdisp
+# host2 vncdisp
+#
+# where, e.g. vncdisp = 15 => port 5915, say
+#
+# joesbox 15
+# fredsbox 15
+# rupert 1
+
+my $allow_file = '/dist/apache/2.0/conf/vnc.hosts';
+$allow_file = '';
+
+if (exists $ENV{CONNECT_SWITCH_ALLOW_FILE}) {
+ # E.g. CONNECT_SWITCH_ALLOW_FILE=/usr/local/etc/allow.txt
+ $allow_file = $ENV{CONNECT_SWITCH_ALLOW_FILE};
+}
+
+# Set to 1 to re-map to vnc port, e.g. 'hostname 15' to 'hostname 5915'
+# i.e. assume a port 0 <= port < 200 is actually a VNC display
+# and add 5900 to it. Set to 0 to not do the mapping.
+# Note that negative ports, e.g. 'joesbox -22' go directly to -port.
+#
+my $apply_vnc_offset = 1;
+my $vnc_offset = 5900;
+
+if (exists $ENV{CONNECT_SWITCH_APPLY_VNC_OFFSET}) {
+ # E.g. CONNECT_SWITCH_APPLY_VNC_OFFSET=0
+ $apply_vnc_offset = $ENV{CONNECT_SWITCH_APPLY_VNC_OFFSET};
+}
+if (exists $ENV{CONNECT_SWITCH_VNC_OFFSET}) {
+ # E.g. CONNECT_SWITCH_VNC_OFFSET=6000
+ $vnc_offset = $ENV{CONNECT_SWITCH_VNC_OFFSET};
+}
+
+# Set to 1 for more debugging output:
+#
+my $verbose = 0;
+
+if (exists $ENV{CONNECT_SWITCH_VERBOSE}) {
+ # E.g. CONNECT_SWITCH_VERBOSE=1
+ $verbose = $ENV{CONNECT_SWITCH_VERBOSE};
+}
+
+############################################################################
+# No need for any changes below here.
+
+use IO::Socket::INET;
+use strict;
+use warnings;
+
+my $killpid = 1;
+
+setpgrp(0, 0);
+
+my $listen_sock = IO::Socket::INET->new(
+ Listen => 10,
+ LocalAddr => $listen_host,
+ LocalPort => $listen_port,
+ Proto => "tcp"
+);
+
+if (! $listen_sock) {
+ die "connect_switch: $!\n";
+}
+
+my $current_fh1 = '';
+my $current_fh2 = '';
+
+my $conn = 0;
+
+while (1) {
+ $conn++;
+ print STDERR "listening for connection: $conn\n" if $verbose;
+ my ($client, $ip) = $listen_sock->accept();
+ if (! $client) {
+ fsleep(0.5);
+ next;
+ }
+ print STDERR "conn: $conn -- ", $client->peerhost(), "\n" if $verbose;
+
+ my $pid = fork();
+ if (! defined $pid) {
+ die "connect_switch: $!\n";
+ } elsif ($pid) {
+ wait;
+ next;
+ } else {
+ close $listen_sock;
+ if (fork) {
+ exit 0;
+ }
+ setpgrp(0, 0);
+ handle_conn($client);
+ }
+}
+
+exit 0;
+
+sub handle_conn {
+ my $client = shift;
+
+ my $start = time();
+
+ my @allow = @allowed;
+
+ if ($allow_file && -f $allow_file) {
+ if (open(ALLOW, "<$allow_file")) {
+ while (<ALLOW>) {
+ next if /^\s*#/;
+ next if /^\s*$/;
+ chomp;
+ my ($host, $dpy) = split(' ', $_);
+ next if ! defined $host;
+ next if ! defined $dpy;
+ if ($dpy < 0) {
+ $dpy = -$dpy;
+ } elsif ($apply_vnc_offset) {
+ $dpy += $vnc_offset if $dpy < 200;
+ }
+ push @allow, "$host:$dpy";
+ }
+ close(ALLOW);
+ } else {
+ warn "$allow_file: $!\n";
+ }
+ }
+
+ my $str = '';
+ my $N = 0;
+ my $isconn = 1;
+ for (my $i = 0; $i < 7; $i++) {
+ my $b;
+ sysread($client, $b, 1);
+ $str .= $b;
+ $N++;
+ print STDERR "read: '$str'\n" if $verbose;
+ my $cstr = substr('CONNECT', 0, $i+1);
+ if ($str ne $cstr) {
+ $isconn = 0;
+ last;
+ }
+ }
+
+ my $sock = '';
+ if ($isconn) {
+ while ($str !~ /\r\n\r\n/) {
+ my $b;
+ sysread($client, $b, 1);
+ $str .= $b;
+ }
+ print STDERR "read: $str\n" if $verbose;
+
+ my $ok = 0;
+ my $hostport = '';
+ my $http_vers = '1.0';
+ if ($str =~ /^CONNECT\s+(\S+)\s+HTTP\/(\S+)/) {
+ $hostport = $1;
+ $http_vers = $2;
+ foreach my $hp (@allow) {
+ if ($hp eq $hostport) {
+ $ok = 1;
+ last;
+ }
+ }
+ }
+ if (! $ok) {
+ close $client;
+ exit 0;
+ }
+
+ my ($host, $port) = split(/:/, $hostport);
+
+ print STDERR "connecting to: $host:$port\n" if $verbose;
+
+ $sock = IO::Socket::INET->new(
+ PeerAddr => $host,
+ PeerPort => $port,
+ Proto => "tcp"
+ );
+ my $msg;
+ if ($sock) {
+ $msg = "HTTP/$http_vers 200 Connection Established\r\n"
+ . "Proxy-agent: connect_switch v0.2\r\n\r\n";
+ } else {
+ $msg = "HTTP/$http_vers 502 Bad Gateway\r\n"
+ . "Connection: close\r\n\r\n";
+ }
+ syswrite($client, $msg, length($msg));
+ $str = '';
+ } else {
+ print STDERR "connecting to: $httpd_host:$httpd_port\n"
+ if $verbose;
+ $sock = IO::Socket::INET->new(
+ PeerAddr => $httpd_host,
+ PeerPort => $httpd_port,
+ Proto => "tcp"
+ );
+ }
+
+ if (! $sock) {
+ close $client;
+ die "connect_switch: $!\n";
+ }
+
+ $current_fh1 = $client;
+ $current_fh2 = $sock;
+
+ $SIG{TERM} = sub {print STDERR "got sigterm\[$$]\n" if $verbose; close $current_fh1; close $current_fh2; exit 0};
+
+ my $parent = $$;
+ if (my $child = fork()) {
+ xfer($sock, $client, 'S->C');
+ if ($killpid) {
+ fsleep(0.5);
+ kill 'TERM', $child;
+ }
+ } else {
+ if ($str ne '' && $N > 0) {
+ syswrite($sock, $str, $N);
+ }
+ xfer($client, $sock, 'C->S');
+ if ($killpid) {
+ fsleep(0.75);
+ kill 'TERM', $parent;
+ }
+ }
+ if ($verbose) {
+ my $dt = time() - $start;
+ print STDERR "dt\[$$]: $dt\n";
+ }
+ exit 0;
+}
+
+sub xfer {
+ my($in, $out, $lab) = @_;
+ my ($RIN, $WIN, $EIN, $ROUT);
+ $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/;
+ print STDERR "connect_switch\[$lab/$conn/$$]: $!\n";
+ last;
+ } elsif ($len == 0) {
+ print STDERR "connect_switch\[$lab/$conn/$$]: "
+ . "Input is EOF.\n";
+ last;
+ }
+
+ if (0) {
+ # verbose debugging of data:
+ syswrite(STDERR , "\n$lab: ", 6);
+ syswrite(STDERR , $buf, $len);
+ }
+
+ my $offset = 0;
+ my $quit = 0;
+ while ($len) {
+ my $written = syswrite($out, $buf, $len, $offset);
+ if (! defined $written) {
+ print STDERR "connect_switch\[$lab/$conn/$$]: "
+ . "Output is EOF. $!\n";
+ $quit = 1;
+ last;
+ }
+ $len -= $written;
+ $offset += $written;
+ }
+ last if $quit;
+ }
+ close($in);
+ close($out);
+}
+
+sub fsleep {
+ my ($time) = @_;
+ select(undef, undef, undef, $time) if $time;
+}
diff --git a/x11vnc/misc/ultravnc_repeater.pl b/x11vnc/misc/ultravnc_repeater.pl
new file mode 100755
index 0000000..40af575
--- /dev/null
+++ b/x11vnc/misc/ultravnc_repeater.pl
@@ -0,0 +1,424 @@
+#!/usr/bin/env perl
+#
+# Copyright (c) 2009 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 us up to the client and server to manage that
+ and whether to encrypt the session, etc.
+
+usage: ultravnc_repeater.pl [-r] [client_port [server_port]]
+
+Use -r to refuse new server/client connections with an existing
+server/client ID. The default is to close the previous one.
+
+Examples:
+
+ ultravnc_repeater.pl -r
+ ultravnc_repeater.pl 5901
+ ultravnc_repeater.pl 5901 5501
+
+';
+
+use warnings;
+use strict;
+
+use IO::Socket::INET;
+use IO::Select;
+
+my $prog = 'ultravnc_repeater.pl';
+my %ID;
+
+my $refuse = 0;
+my $init_timeout = 3;
+
+if (@ARGV && $ARGV[0] =~ /-h/) {
+ print $usage;
+ exit 0;
+}
+if (@ARGV && $ARGV[0] eq '-r') {
+ $refuse = 1;
+ shift;
+}
+
+my $client_port = shift;
+my $server_port = shift;
+
+$client_port = 5900 unless $client_port;
+$server_port = 5500 unless $server_port;
+
+
+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,
+ Proto => "tcp"
+);
+if (! $client_listen) {
+ cleanup();
+ die "$prog: error: client listen on port $client_port: $!\n";
+}
+
+my $server_listen = IO::Socket::INET->new(
+ Listen => 10,
+ LocalPort => $server_port,
+ Proto => "tcp"
+);
+if (! $server_listen) {
+ cleanup();
+ die "$prog: error: server listen on port $server_port: $!\n";
+}
+
+my $select = new IO::Select();
+if (! select) {
+ cleanup();
+ die "$prog: select $!\n";
+}
+
+$select->add($client_listen);
+$select->add($server_listen);
+
+$SIG{INT} = sub {cleanup(); exit;};
+$SIG{TERM} = sub {cleanup(); exit;};
+
+my $SOCK1 = '';
+my $SOCK2 = '';
+my $CURR = '';
+
+print "watching for connections on ports $server_port/server and $client_port/client\n";
+
+select(STDERR); $| = 1;
+select(STDOUT); $| = 1;
+
+my $alarm_sock = '';
+my $got_alarm = 0;
+sub alarm_handler {
+ print "$prog: got sig alarm.\n";
+ if ($alarm_sock ne '') {
+ close $alarm_sock;
+ }
+ $alarm_sock = '';
+ $got_alarm = 1;
+}
+
+while (my @ready = $select->can_read()) {
+ foreach my $fh (@ready) {
+ if ($fh == $client_listen) {
+ print "new vnc client connecting at ", scalar(localtime), "\n";
+ } elsif ($fh == $server_listen) {
+ print "new vnc server connecting at ", scalar(localtime), "\n";
+ }
+ my $sock = $fh->accept();
+ if (! $sock) {
+ print "$prog: accept $!\n";
+ next;
+ }
+
+ if ($fh == $client_listen) {
+ my $str = "RFB 000.000\n";
+ my $len = length $str;
+ my $n = syswrite($sock, $str, $len, 0);
+ if ($n != $len) {
+ print "$prog: bad $str write: $n != $len $!\n";
+ 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) {
+ print "$prog: read timed out: $!\n";
+ } elsif (! defined $n) {
+ print "$prog: read error: $!\n";
+ } elsif ($repeater_bufsize > 0 && $n != $size) {
+ print "$prog: short read $n != $size $!\n";
+ close $sock;
+ } elsif ($fh == $client_listen) {
+ do_new_client($sock, $buf);
+ } elsif ($fh == $server_listen) {
+ do_new_server($sock, $buf);
+ }
+ }
+}
+
+sub do_new_client {
+ my ($sock, $buf) = @_;
+
+ if ($buf =~ /^ID:(\w+)/) {
+ my $id = $1;
+ if (exists $ID{$id}) {
+ if ($ID{$id}{client}) {
+ print "refusing extra vnc client for ID:$id\n";
+ close $sock;
+ return;
+ if ($refuse) {
+ print "refusing extra vnc client for ID:$id\n";
+ close $sock;
+ return;
+ } else {
+ print "closing and deleting previous vnc client with ID:$id\n";
+ close $ID{$id}{sock};
+
+ print "storing new vnc client with ID:$id\n";
+ $ID{$id}{client} = 1;
+ $ID{$id}{sock} = $sock;
+ }
+ } else {
+ print "hooking up new vnc client with existing vnc server for ID:$id\n";
+ my $sock2 = $ID{$id}{sock};
+ delete $ID{$id};
+ hookup($sock, $sock2, "ID:$id");
+ }
+ } else {
+ print "storing new vnc client with ID:$id\n";
+ $ID{$id}{client} = 1;
+ $ID{$id}{sock} = $sock;
+ }
+ } else {
+ my $str = sprintf("%s", $buf);
+ my $host = '';
+ my $port = '';
+ if ($str =~ /^(.+):(\d+)/) {
+ $host = $1;
+ $port = $2;
+ } else {
+ $host = $str;
+ $port = 5900;
+ }
+ if ($port < 0) {
+ my $pnew = -$port;
+ print "resetting port from $port to $pnew\n";
+ $port = $pnew;
+ } elsif ($port < 200) {
+ my $pnew = $port + 5900;
+ print "resetting port from $port to $pnew\n";
+ $port = $pnew;
+ }
+ print "making vnc client connection directly to vnc server $host:$port\n";
+ my $sock2 = IO::Socket::INET->new(
+ PeerAddr => $host,
+ PeerPort => $port,
+ Proto => "tcp"
+ );
+ if (!$sock2) {
+ print "failed to connect to $host:$port\n";
+ 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}) {
+ if (! $ID{$id}{client}) {
+ if ($refuse) {
+ print "refusing extra vnc server for ID:$id\n";
+ close $sock;
+ return;
+ } else {
+ print "closing and deleting previous vnc server with ID:$id\n";
+ close $ID{$id}{sock};
+
+ print "storing new vnc server with ID:$id\n";
+ $ID{$id}{client} = 0;
+ $ID{$id}{sock} = $sock;
+ }
+ } else {
+ print "hooking up new vnc server with existing vnc client for ID:$id\n";
+ my $sock2 = $ID{$id}{sock};
+ delete $ID{$id};
+ hookup($sock, $sock2, "ID:$id");
+ }
+ } else {
+ print "storing new vnc server with ID:$id\n";
+ $ID{$id}{client} = 0;
+ $ID{$id}{sock} = $sock;
+ }
+ } else {
+ print "invalid ID:NNNNN string for vnc server: $buf\n";
+ close $sock;
+ return;
+ }
+}
+
+sub handler {
+ print STDERR "$prog\[$$/$CURR]: got SIGTERM.\n";
+ close $SOCK1 if $SOCK1;
+ close $SOCK2 if $SOCK2;
+ exit;
+}
+
+sub hookup {
+ my ($sock1, $sock2, $tag) = @_;
+
+ my $worker = fork();
+
+ if (! defined $worker) {
+ print "failed to fork worker: $!\n";
+ 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/;
+ print STDERR "$prog\[$$/$CURR]: $!\n";
+ last;
+ } elsif ($len == 0) {
+ print STDERR "$prog\[$$/$CURR]: Input is EOF.\n";
+ last;
+ }
+ my $offset = 0;
+ my $quit = 0;
+ while ($len) {
+ my $written = syswrite($out, $buf, $len, $offset);
+ if (! defined $written) {
+ print STDERR "$prog\[$$/$CURR]: Output is EOF. $!\n";
+ $quit = 1;
+ last;
+ }
+ $len -= $written;
+ $offset += $written;
+ }
+ last if $quit;
+ }
+ close($out);
+ close($in);
+ print STDERR "$prog\[$$/$CURR]: finished xfer.\n";
+}
+
+sub xfer_both {
+ my ($sock1, $sock2) = @_;
+
+ my $parent = $$;
+
+ my $child = fork();
+
+ if (! defined $child) {
+ print STDERR "$prog\[$$/$CURR] failed to fork: $!\n";
+ return;
+ }
+
+ $SIG{TERM} = "handler";
+ $SIG{INT} = "handler";
+
+ if ($child) {
+ print STDERR "$prog parent[$$/$CURR] 1 -> 2\n";
+ xfer($sock1, $sock2);
+ select(undef, undef, undef, 0.25);
+ if (kill 0, $child) {
+ select(undef, undef, undef, 0.9);
+ if (kill 0, $child) {
+ print STDERR "$prog\[$$/$CURR]: kill TERM child $child\n";
+ kill "TERM", $child;
+ } else {
+ print STDERR "$prog\[$$/$CURR]: child $child gone.\n";
+ }
+ }
+ } else {
+ select(undef, undef, undef, 0.05);
+ print STDERR "$prog child [$$/$CURR] 2 -> 1\n";
+ xfer($sock2, $sock1);
+ select(undef, undef, undef, 0.25);
+ if (kill 0, $parent) {
+ select(undef, undef, undef, 0.8);
+ if (kill 0, $parent) {
+ print STDERR "$prog\[$$/$CURR]: kill TERM parent $parent\n";
+ kill "TERM", $parent;
+ } else {
+ print STDERR "$prog\[$$/$CURR]: parent $parent gone.\n";
+ }
+ }
+ }
+}
+
+sub cleanup {
+ close $client_listen if defined $client_listen;
+ close $server_listen if defined $server_listen;
+ foreach my $id (keys %ID) {
+ close $ID{$id}{sock};
+ }
+}