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.
697 lines
16 KiB
697 lines
16 KiB
#!/usr/bin/perl
|
|
#
|
|
# Copyright (c) 2006-2010 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.
|
|
#
|
|
# It can also be used to redirect ANY protocol, e.g. SSH, not just VNC.
|
|
# See CONNECT_SWITCH_APPLY_VNC_OFFSET=0 to disable VNC 5900 shift.
|
|
#
|
|
# 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).
|
|
#
|
|
|
|
####################################################################
|
|
# NOTE: For more info on configuration settings, read below for
|
|
# all of the CONNECT_SWITCH_* env. var. parameters.
|
|
####################################################################
|
|
|
|
|
|
####################################################################
|
|
# Allow env vars to also be specified on cmdline:
|
|
#
|
|
foreach my $arg (@ARGV) {
|
|
if ($arg =~ /^(CONNECT_SWITCH.*?)=(.*)$/) {
|
|
$ENV{$1} = $2;
|
|
}
|
|
}
|
|
|
|
# Set up logging:
|
|
#
|
|
if (exists $ENV{CONNECT_SWITCH_LOGFILE}) {
|
|
close STDOUT;
|
|
if (!open(STDOUT, ">>$ENV{CONNECT_SWITCH_LOGFILE}")) {
|
|
die "connect_switch: $ENV{CONNECT_SWITCH_LOGFILE} $!\n";
|
|
}
|
|
close STDERR;
|
|
open(STDERR, ">&STDOUT");
|
|
}
|
|
select(STDERR); $| = 1;
|
|
select(STDOUT); $| = 1;
|
|
|
|
# interrupt handler:
|
|
#
|
|
my $looppid = '';
|
|
my $pidfile = '';
|
|
my $listen_sock = ''; # declared here for get_out()
|
|
#
|
|
sub get_out {
|
|
print STDERR "$_[0]:\t$$ looppid=$looppid\n";
|
|
close $listen_sock if $listen_sock;
|
|
if ($looppid) {
|
|
kill 'TERM', $looppid;
|
|
fsleep(0.2);
|
|
}
|
|
unlink $pidfile if $pidfile;
|
|
exit 0;
|
|
}
|
|
$SIG{INT} = \&get_out;
|
|
$SIG{TERM} = \&get_out;
|
|
|
|
# pidfile:
|
|
#
|
|
sub open_pidfile {
|
|
if (exists $ENV{CONNECT_SWITCH_PIDFILE}) {
|
|
my $pf = $ENV{CONNECT_SWITCH_PIDFILE};
|
|
if (open(PID, ">$pf")) {
|
|
print PID "$$\n";
|
|
close PID;
|
|
$pidfile = $pf;
|
|
} else {
|
|
print STDERR "could not open pidfile: $pf - $! - continuing...\n";
|
|
}
|
|
delete $ENV{CONNECT_SWITCH_PIDFILE};
|
|
}
|
|
}
|
|
|
|
####################################################################
|
|
# Set CONNECT_SWITCH_LOOP=1 to have this script create an outer loop
|
|
# restarting itself if it ever exits. Set CONNECT_SWITCH_LOOP=BG to
|
|
# do this in the background as a daemon.
|
|
|
|
if (exists $ENV{CONNECT_SWITCH_LOOP}) {
|
|
my $csl = $ENV{CONNECT_SWITCH_LOOP};
|
|
if ($csl ne 'BG' && $csl ne '1') {
|
|
die "connect_switch: invalid CONNECT_SWITCH_LOOP.\n";
|
|
}
|
|
if ($csl eq 'BG') {
|
|
# go into bg as "daemon":
|
|
setpgrp(0, 0);
|
|
my $pid = fork();
|
|
if (! defined $pid) {
|
|
die "connect_switch: $!\n";
|
|
} elsif ($pid) {
|
|
wait;
|
|
exit 0;
|
|
}
|
|
if (fork) {
|
|
exit 0;
|
|
}
|
|
setpgrp(0, 0);
|
|
close STDIN;
|
|
if (! $ENV{CONNECT_SWITCH_LOGFILE}) {
|
|
close STDOUT;
|
|
close STDERR;
|
|
}
|
|
}
|
|
delete $ENV{CONNECT_SWITCH_LOOP};
|
|
|
|
if (exists $ENV{CONNECT_SWITCH_PIDFILE}) {
|
|
open_pidfile();
|
|
}
|
|
|
|
print STDERR "connect_switch: starting service at ", scalar(localtime), " master-pid=$$\n";
|
|
while (1) {
|
|
$looppid = fork;
|
|
if (! defined $looppid) {
|
|
sleep 10;
|
|
} elsif ($looppid) {
|
|
wait;
|
|
} else {
|
|
exec $0;
|
|
exit 1;
|
|
}
|
|
print STDERR "connect_switch: re-starting service at ", scalar(localtime), " master-pid=$$\n";
|
|
sleep 1;
|
|
}
|
|
exit 0;
|
|
}
|
|
if (exists $ENV{CONNECT_SWITCH_PIDFILE}) {
|
|
open_pidfile();
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# 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
|
|
# CONNECT_SWITCH_LISTEN_IPV6
|
|
# CONNECT_SWITCH_BUFSIZE
|
|
# CONNECT_SWITCH_LOGFILE
|
|
# CONNECT_SWITCH_PIDFILE
|
|
# CONNECT_SWITCH_MAX_CONNECTIONS
|
|
#
|
|
# You can also set these on the cmdline:
|
|
# connect_switch CONNECT_SWITCH_LISTEN=X CONNECT_SWITCH_ALLOW_FILE=Y ...
|
|
#
|
|
|
|
# By default we will use hostname and assume it resolves:
|
|
#
|
|
my $hostname = `hostname`;
|
|
chomp $hostname;
|
|
|
|
my $listen_host = $hostname;
|
|
my $listen_port = 443;
|
|
|
|
# Let user override listening situation, e.g. multihomed:
|
|
#
|
|
if (exists $ENV{CONNECT_SWITCH_LISTEN}) {
|
|
#
|
|
# E.g. CONNECT_SWITCH_LISTEN=192.168.0.32:443
|
|
#
|
|
$listen_host = '';
|
|
$listen_port = '';
|
|
if ($ENV{CONNECT_SWITCH_LISTEN} =~ /^(.*):(\d+)$/) {
|
|
($listen_host, $listen_port) = ($1, $2);
|
|
}
|
|
}
|
|
|
|
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 = '';
|
|
if ($ENV{CONNECT_SWITCH_HTTPD} =~ /^(.*):(\d+)$/) {
|
|
($httpd_host, $httpd_port) = ($1, $2);
|
|
}
|
|
}
|
|
|
|
my $bufsize = 8192;
|
|
if (exists $ENV{CONNECT_SWITCH_BUFSIZE}) {
|
|
#
|
|
# E.g. CONNECT_SWITCH_BUFSIZE=32768
|
|
#
|
|
$bufsize = $ENV{CONNECT_SWITCH_BUFSIZE};
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# 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.
|
|
# Port ranges are allowed too: host:5900-5930.
|
|
# If there is one entry named ALL all connections are allow.
|
|
# You must supply something, default is deny.
|
|
#
|
|
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
|
|
|
|
# For examply, mine is:
|
|
#
|
|
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 or higher for more info output:
|
|
#
|
|
my $verbose = 0;
|
|
|
|
if (exists $ENV{CONNECT_SWITCH_VERBOSE}) {
|
|
#
|
|
# E.g. CONNECT_SWITCH_VERBOSE=1
|
|
#
|
|
$verbose = $ENV{CONNECT_SWITCH_VERBOSE};
|
|
}
|
|
|
|
# zero means loop forever, positive value means exit after handling that
|
|
# many connections.
|
|
#
|
|
my $cmax = 0;
|
|
if (exists $ENV{CONNECT_SWITCH_MAX_CONNECTIONS}) {
|
|
$cmax = $ENV{CONNECT_SWITCH_MAX_CONNECTIONS};
|
|
}
|
|
|
|
|
|
#===========================================================================
|
|
# No need for any changes below here.
|
|
#===========================================================================
|
|
|
|
use IO::Socket::INET;
|
|
use strict;
|
|
use warnings;
|
|
|
|
# Test for INET6 support:
|
|
#
|
|
my $have_inet6 = 0;
|
|
eval "use IO::Socket::INET6;";
|
|
$have_inet6 = 1 if $@ eq "";
|
|
|
|
my $killpid = 1;
|
|
|
|
setpgrp(0, 0);
|
|
|
|
if (exists $ENV{CONNECT_SWITCH_LISTEN_IPV6}) {
|
|
# note we leave out LocalAddr.
|
|
my $cmd = '
|
|
$listen_sock = IO::Socket::INET6->new(
|
|
Listen => 10,
|
|
LocalPort => $listen_port,
|
|
ReuseAddr => 1,
|
|
Domain => AF_INET6,
|
|
Proto => "tcp"
|
|
);
|
|
';
|
|
eval $cmd;
|
|
die "$@\n" if $@;
|
|
} else {
|
|
$listen_sock = IO::Socket::INET->new(
|
|
Listen => 10,
|
|
LocalAddr => $listen_host,
|
|
LocalPort => $listen_port,
|
|
ReuseAddr => 1,
|
|
Proto => "tcp"
|
|
);
|
|
}
|
|
|
|
if (! $listen_sock) {
|
|
die "connect_switch: $!\n";
|
|
}
|
|
|
|
my $current_fh1 = '';
|
|
my $current_fh2 = '';
|
|
|
|
my $conn = 0;
|
|
|
|
while (1) {
|
|
$conn++;
|
|
if ($cmax > 0 && $conn > $cmax) {
|
|
print STDERR "last connection ($cmax)\n" if $verbose;
|
|
last;
|
|
}
|
|
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(), " at ", scalar(localtime), "\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;
|
|
|
|
# read allow file. Note we read it for every connection
|
|
# to allow the admin to modify it w/o restarting us.
|
|
# better way would be to read in parent and check mtime.
|
|
#
|
|
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";
|
|
}
|
|
}
|
|
|
|
# Read the first 7 bytes of connection, see if it is 'CONNECT'
|
|
#
|
|
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 > 1;
|
|
my $cstr = substr('CONNECT', 0, $i+1);
|
|
if ($str ne $cstr) {
|
|
$isconn = 0;
|
|
last;
|
|
}
|
|
}
|
|
|
|
my $sock = '';
|
|
|
|
if ($isconn) {
|
|
# it is CONNECT, read rest of HTTP header:
|
|
#
|
|
while ($str !~ /\r\n\r\n/) {
|
|
my $b;
|
|
sysread($client, $b, 1);
|
|
$str .= $b;
|
|
}
|
|
print STDERR "read: $str\n" if $verbose > 1;
|
|
|
|
# get http version and host:port
|
|
#
|
|
my $ok = 0;
|
|
my $hostport = '';
|
|
my $http_vers = '1.0';
|
|
if ($str =~ /^CONNECT\s+(\S+)\s+HTTP\/(\S+)/) {
|
|
$hostport = $1;
|
|
$http_vers = $2;
|
|
my $h = '';
|
|
my $p = '';
|
|
|
|
if ($hostport =~ /^(.*):(\d+)$/) {
|
|
($h, $p) = ($1, $2);
|
|
}
|
|
if ($p =~ /^\d+$/) {
|
|
# check allowed host list:
|
|
foreach my $hp (@allow) {
|
|
if ($hp eq 'ALL') {
|
|
$ok = 1;
|
|
}
|
|
if ($hp eq $hostport) {
|
|
$ok = 1;
|
|
}
|
|
if ($hp =~ /^(.*):(\d+)-(\d+)$/) {
|
|
my $ahost = $1;
|
|
my $pmin = $2;
|
|
my $pmax = $3;
|
|
if ($h eq $ahost) {
|
|
if ($p >= $pmin && $p <= $pmax) {
|
|
$ok = 1;
|
|
}
|
|
}
|
|
}
|
|
last if $ok;
|
|
}
|
|
}
|
|
}
|
|
|
|
my $msg_1 = "HTTP/$http_vers 200 Connection Established\r\n"
|
|
. "Proxy-agent: connect_switch v0.2\r\n\r\n";
|
|
my $msg_2 = "HTTP/$http_vers 502 Bad Gateway\r\n"
|
|
. "Connection: close\r\n\r\n";
|
|
|
|
if (! $ok) {
|
|
# disallowed. drop with message.
|
|
#
|
|
syswrite($client, $msg_2, length($msg_2));
|
|
close $client;
|
|
exit 0;
|
|
}
|
|
|
|
my $host = '';
|
|
my $port = '';
|
|
|
|
if ($hostport =~ /^(.*):(\d+)$/) {
|
|
($host, $port) = ($1, $2);
|
|
}
|
|
|
|
print STDERR "connecting to: $host:$port\n" if $verbose;
|
|
|
|
$sock = IO::Socket::INET->new(
|
|
PeerAddr => $host,
|
|
PeerPort => $port,
|
|
Proto => "tcp"
|
|
);
|
|
print STDERR "connect to host='$host' port='$port' failed: $!\n" if !$sock;
|
|
if (! $sock && $have_inet6) {
|
|
eval {$sock = IO::Socket::INET6->new(
|
|
PeerAddr => $host,
|
|
PeerPort => $port,
|
|
Proto => "tcp"
|
|
);};
|
|
print STDERR "connect to host='$host' port='$port' failed: $! (ipv6)\n" if !$sock;
|
|
}
|
|
my $msg;
|
|
|
|
# send the connect proxy reply:
|
|
#
|
|
if ($sock) {
|
|
$msg = $msg_1;
|
|
} else {
|
|
$msg = $msg_2;
|
|
}
|
|
syswrite($client, $msg, length($msg));
|
|
$str = '';
|
|
} else {
|
|
# otherwise, redirect to apache for normal https:
|
|
#
|
|
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 && $have_inet6) {
|
|
eval {$sock = IO::Socket::INET6->new(
|
|
PeerAddr => $httpd_host,
|
|
PeerPort => $httpd_port,
|
|
Proto => "tcp"
|
|
);};
|
|
}
|
|
}
|
|
|
|
if (! $sock) {
|
|
close $client;
|
|
die "connect_switch: $!\n";
|
|
}
|
|
|
|
# get ready for xfer phase:
|
|
#
|
|
$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 {
|
|
# write those first bytes if not CONNECT:
|
|
#
|
|
if ($str ne '' && $N > 0) {
|
|
syswrite($sock, $str, $N);
|
|
}
|
|
xfer($client, $sock, 'C->S');
|
|
if ($killpid) {
|
|
fsleep(0.75);
|
|
kill 'TERM', $parent;
|
|
}
|
|
}
|
|
if ($verbose > 1) {
|
|
my $dt = time() - $start;
|
|
print STDERR "duration\[$$]: $dt seconds. ", scalar(localtime), "\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, $bufsize);
|
|
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) {
|
|
# very 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;
|
|
}
|