diff options
Diffstat (limited to 'x11vnc/misc/ultravnc_repeater.pl')
-rwxr-xr-x | x11vnc/misc/ultravnc_repeater.pl | 424 |
1 files changed, 424 insertions, 0 deletions
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 <[email protected]> +# +# 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}; + } +} |