ssl_vncviewer 11.7 KB
Newer Older
1 2
#!/bin/sh
#
3 4
# ssl_vncviewer:  wrapper for vncviewer to use an stunnel SSL tunnel
#                 or an SSH tunnel.
5 6
#
# Copyright (c) 2006 by Karl J. Runge <runge@karlrunge.com>
7
#
8 9 10
# You must have stunnel(8) installed on the system and in your PATH
# (however, see the -ssh option below, in which case you will need ssh(1)
# installed)  Note: stunnel is usually installed in an "sbin" subdirectory.
11 12
#
# You should have "x11vnc -ssl ..." or "x11vnc -stunnel ..." 
13 14
# already running as the VNC server on the remote machine.
# (or use stunnel on the server side for any other VNC server)
15
#
16 17
#
# Usage: ssl_vncviewer [cert-args] host:display <vncviewer-args>
18 19 20 21 22
#
# e.g.:  ssl_vncviewer snoopy:0
#        ssl_vncviewer snoopy:0 -encodings "copyrect tight zrle hextile"
#
# [cert-args] can be:
23
#
24 25
#	-verify /path/to/cacert.pem		
#	-mycert /path/to/mycert.pem		
26
#	-proxy  host:port
27 28 29 30 31 32 33
#
# -verify specifies a CA cert PEM file (or a self-signed one) for
#         authenticating the VNC server.
#
# -mycert specifies this client's cert+key PEM file for the VNC server to
#	  authenticate this client. 
#
34 35 36 37
# -proxy  try host:port as a Web proxy to use the CONNECT method
#         to reach the VNC server (e.g. your firewall requires a proxy).
#         For the "double proxy" case use -proxy host1:port1,host2:port2
#
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
#    See http://www.karlrunge.com/x11vnc/#faq-ssl-ca for details on SSL
#    certificates with VNC.
#
# A few other args (not related to SSL and certs):
#
# -ssh    Use ssh instead of stunnel SSL.  ssh(1) must be installed and you
#         must be able to log into the remote machine via ssh.
#
#         In this case "host:display" may be of the form "user@host:display"
#         where "user@host" is used for the ssh login (see ssh(1) manpage).
#
#         If -proxy is supplied it can be of the forms: "gwhost" "gwhost:port"
#         "user@gwhost" or "user@gwhost:port".  "gwhost" is an incoming ssh 
#         gateway machine (the VNC server is not running there), an ssh -L
#         redir is used to "host" in "host:display" from "gwhost". Any "user@"
#         part must be in the -proxy string (not in "host:display").
#
#         Under -proxy use "gwhost:port" if connecting to any ssh port
#         other than the default (22).  (even for the non-gateway case,
#         -proxy must be used to specify a non-standard ssh port)
#
#         Examples:
#
#         ssl_vncviewer -ssh bob@bobs-home.net:0
#         ssl_vncviewer -ssh -sshcmd 'x11vnc -localhost' bob@bobs-home.net:0
#
#         ssl_vncviewer -ssh -proxy fred@mygate.com:2022 mymachine:0
#         ssl_vncviewer -ssh -proxy bob@bobs-home.net:2222 localhost:0
#
# -sshcmd cmd   Run "cmd" via ssh instead of the default "sleep 15"
#               e.g. -sshcmd 'x11vnc -display :0 -localhost -rfbport 5900'
#
70 71 72 73 74 75 76 77 78 79 80
# -sshargs "args"  pass "args" to the ssh process, e.g. -L/-R port redirs.
#
# -sshssl Tunnel the SSL connection thru a SSH connection.  The tunnel as
#         under -ssh is set up and the SSL connection goes thru it.  Use
#         this if you want to have and end-to-end SSL connection but must
#         go thru a SSH gateway host (e.g. not the vnc server).  Or use
#         this if you need to tunnel additional services via -R and -L 
#         (see -sshargs above).
#
#         ssl_vncviewer -sshssl -proxy fred@mygate.com mymachine:0
#
runge's avatar
runge committed
81 82 83 84 85 86 87 88
#
# -alpha  turn on cursor alphablending hack if you are using the
#         enhanced tightvnc vncviewer.
#
# -grab   turn on XGrabServer hack if you are using the enhanced tightvnc
#         vncviewer (e.g. for fullscreen mode in some windowmanagers like
#         fvwm that do not otherwise work in fullscreen mode)
#
89
#
90
# set VNCVIEWERCMD to whatever vncviewer command you want to use.
91
#
runge's avatar
runge committed
92
VNCIPCMD=${VNCVIEWERCMD:-vncip}
93
VNCVIEWERCMD=${VNCVIEWERCMD:-vncviewer}
94
#
95
# Same for STUNNEL, e.g. set it to /path/to/stunnel or stunnel4, etc.
96
#
97

98

99 100
PATH=$PATH:/usr/sbin:/usr/local/sbin:/dist/sbin; export PATH

101 102 103 104 105 106 107 108 109
if [ "X$STUNNEL" = "X" ]; then
	type stunnel4 > /dev/null 2>&1
	if [ $? = 0 ]; then
		STUNNEL=stunnel4
	else
		STUNNEL=stunnel
	fi
fi

110
help() {
111
	tail +2 "$0" | sed -e '/^$/ q'
112 113
}

runge's avatar
runge committed
114
gotalpha=""
115
use_ssh=""
116 117 118 119 120 121 122
use_sshssl=""
ssh_sleep=15
ssh_cmd="sleep $ssh_sleep"
if [ "X$SSL_VNCVIEWER_SSH_CMD" != "X" ]; then
	ssh_cmd="$SSL_VNCVIEWER_SSH_CMD"
fi
ssh_args=""
runge's avatar
runge committed
123

124 125 126 127 128 129 130 131
# grab our cmdline options:
while [ "X$1" != "X" ]
do
    case $1 in 
	"-verify")	shift; verify="$1"
                ;;
	"-mycert")	shift; mycert="$1"
                ;;
132 133
	"-proxy")	shift; proxy="$1"
                ;;
134 135
	"-ssh")		use_ssh=1
                ;;
136 137 138
	"-sshssl")	use_ssh=1
			use_sshssl=1
                ;;
139 140
	"-sshcmd")	shift; ssh_cmd="$1"
                ;;
141 142
	"-sshargs")	shift; ssh_args="$1"
                ;;
runge's avatar
runge committed
143 144 145 146
	"-alpha")	gotalpha=1
                ;;
	"-grab")	VNCVIEWER_GRAB_SERVER=1; export VNCVIEWER_GRAB_SERVER
                ;;
147 148
	"-h"*)	help; exit 0
                ;;
149 150
	"--h"*)	help; exit 0
                ;;
151 152 153 154 155 156
	*)	break
                ;;
    esac
    shift
done

runge's avatar
runge committed
157 158 159 160 161
if [ "X$gotalpha" != "X1" ]; then
	NO_ALPHABLEND=1
	export NO_ALPHABLEND
fi

162 163 164
orig="$1"
shift

165
if [ "X$use_ssh" = "X1" -a "X$use_sshssl" = "X" ]; then
166 167 168 169 170 171
	if [ "X$mycert" != "X" -o "X$verify" != "X" ]; then
		echo "-mycert and -verify cannot be used in -ssh mode" 
		exit 1
	fi
fi

172
# play around with host:display port:
runge's avatar
runge committed
173 174 175
if echo "$orig" | grep ':' > /dev/null; then
	:
else
176 177 178 179 180
	orig="$orig:0"
fi

host=`echo "$orig" | awk -F: '{print $1}'`
disp=`echo "$orig" | awk -F: '{print $2}'`
runge's avatar
runge committed
181 182 183
if [ "X$host" = "X" ]; then
	host=localhost
fi
184 185
if [ $disp -lt 200 ]; then
	port=`expr $disp + 5900`
186 187
else
	port=$disp
188 189 190
fi

# try to find an open listening port via netstat(1):
191
inuse=""
192
if uname | grep Linux > /dev/null; then
193
	inuse=`netstat -ant | egrep 'LISTEN|WAIT|ESTABLISH|CLOSE' | awk '{print $4}' | sed 's/^.*://'`
194 195
elif uname | grep SunOS > /dev/null; then
	inuse=`netstat -an -f inet -P tcp | grep LISTEN | awk '{print $1}' | sed 's/^.*\.//'`
196
# add others...
197
fi
198 199 200 201 202 203 204 205

date_sec=`date +%S`

findfree() {
	try0=$1
	try=$try0
	use0=""

206 207
	while [ $try -lt 6000 ]
	do
208 209 210
		if [ "X$inuse" = "X" ]; then
			break
		fi
runge's avatar
runge committed
211 212 213
		if echo "$inuse" | grep -w $try > /dev/null; then
			:
		else
214
			use0=$try
215 216 217 218
			break
		fi
		try=`expr $try + 1`
	done
219 220 221 222 223 224 225 226
	if [ "X$use0" = "X" ]; then
		use0=`expr $date_sec + $try0`
	fi

	echo $use0
}

use=`findfree 5930`
227

228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
if [ $use -ge 5900 ]; then
	N=`expr $use - 5900`
else
	N=$use
fi

if echo "$0" | grep vncip > /dev/null; then
	VNCVIEWERCMD="$VNCIPCMD"
fi

if [ "X$use_ssh" = "X1" ]; then
	ssh_port="22"
	ssh_host="$host"
	vnc_host="localhost"
	ssh=${SSH:-"ssh -x"}
	if [ "X$proxy" != "X" ]; then
		ssh_port=`echo "$proxy" | awk -F: '{print $2}'`
		if [ "X$ssh_port" = "X" ]; then
			ssh_port="22"
		fi
		ssh_host=`echo "$proxy" | awk -F: '{print $1}'`
		vnc_host="$host"
	fi
	echo ""
	echo "Running ssh:"
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
	sz=`echo "$ssh_cmd" | wc -c`
	if [ "$sz" -gt 200 ]; then
		info="..."
	else
		info="$ssh_cmd"
	fi
	if [ "X$SSL_VNCVIEWER_SSH_ONLY" != "X" ]; then
		echo "$ssh -p $ssh_port -t -C $ssh_args $ssh_host \"$info\""
		echo ""
		$ssh -p $ssh_port -t -C $ssh_args $ssh_host "$ssh_cmd"
		exit $?
	elif [ "X$SSL_VNCVIEWER_NO_F" != "X" ]; then
		echo "$ssh -p $ssh_port -C -L ${use}:${vnc_host}:${port} $ssh_args $ssh_host \"$info\""
		echo ""
		$ssh -p $ssh_port -C -L ${use}:${vnc_host}:${port} $ssh_args $ssh_host "$ssh_cmd"
	else
		echo "$ssh -f -p $ssh_port -C -L ${use}:${vnc_host}:${port} $ssh_args $ssh_host \"$info\""
		echo ""
		$ssh -f -p $ssh_port -C -L ${use}:${vnc_host}:${port} $ssh_args $ssh_host "$ssh_cmd"
	fi
273 274 275 276 277 278
	if [ "$?" != "0" ]; then
		echo ""
		echo "ssh to $ssh_host failed."
		exit 1
	fi
	echo ""
279
	if [ "X$ssh_cmd" = "Xsleep $ssh_sleep" ] ; then
280 281 282 283 284 285
		sleep 1
	else
		# let any command get started a bit.
		sleep 5
	fi
	echo ""
286 287 288 289 290
	if [ "X$use_sshssl" = "X" ]; then
		echo "Running viewer:"
		echo $VNCVIEWERCMD "$@" localhost:$N
		echo ""
		$VNCVIEWERCMD "$@" localhost:$N
291

292 293 294 295 296 297 298 299 300 301 302
		exit $?
	else
		use2=`findfree 5960`
		host0=$host
		port0=$port
		host=localhost
		port=$use
		use=$use2
		N=`expr $use - 5900`
		proxy=""
	fi
303 304
fi

305 306 307 308 309 310 311 312 313 314 315 316 317 318
# create the stunnel config file:
if [ "X$verify" != "X" ]; then
	if [ -d $verify ]; then
		verify="CApath = $verify"
	else
		verify="CAfile = $verify"
	fi
	verify="$verify
verify = 2"
fi
if [ "X$mycert" != "X" ]; then
	cert="cert = $mycert"
fi

319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
mytmp() {
	tf=$1
	rm -rf "$tf" || exit 1
	if [ -d "$tf" ]; then
		echo "tmp file $tf still exists as a directory."
		exit 1
	elif [ -L "$tf" ]; then
		echo "tmp file $tf still exists as a symlink."
		exit 1
	elif [ -f "$tf" ]; then
		echo "tmp file $tf still exists."
		exit 1
	fi
	touch "$tf" || exit 1
	chmod 600 "$tf" || exit 1
}

if echo "$RANDOM" | grep '[^0-9]' > /dev/null; then
	RANDOM=`date +%S`
fi

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
pcode() {
	tf=$1
	SSL_VNC_PROXY=$proxy; export SSL_VNC_PROXY
	SSL_VNC_DEST="$host:$port"; export SSL_VNC_DEST
	cod='#!/usr/bin/perl

# A hack to glue stunnel to a Web proxy for client connections.

use IO::Socket::INET;

my ($first, $second) = split(/,/, $ENV{SSL_VNC_PROXY});
my ($proxy_host, $proxy_port) = split(/:/, $first);
my $connect = $ENV{SSL_VNC_DEST};

print STDERR "\nperl script for web proxing:\n";
print STDERR "proxy_host:       $proxy_host\n";
print STDERR "proxy_port:       $proxy_port\n";
print STDERR "proxy_connect:    $connect\n";

my $sock = IO::Socket::INET->new(
	PeerAddr => $proxy_host,
	PeerPort => $proxy_port,
	Proto => "tcp");

if (! $sock) {
	unlink($0);
	die "perl proxy: $!\n";
}

my $con = "";
if ($second ne "") {
	$con = "CONNECT $second HTTP/1.1\r\n";
	$con   .= "Host: $second\r\n\r\n";
} else {
	$con = "CONNECT $connect HTTP/1.1\r\n";
	$con   .= "Host: $connect\r\n\r\n";
}

print STDERR "proxy_request1:\n$con";
print $sock $con;

unlink($0);

my $rep = "";
while ($rep !~ /\r\n\r\n/) {
	my $c = getc($sock);
	print STDERR $c;
	$rep .= $c;
}
if ($rep !~ m,HTTP/.* 200,) {
	die "proxy error: $rep\n";
}

if ($second ne "") {
	$con = "CONNECT $connect HTTP/1.1\r\n";
	$con   .= "Host: $connect\r\n\r\n";
	print STDERR "proxy_request2:\n$con";

	print $sock $con;

	$rep = "";
	while ($rep !~ /\r\n\r\n/) {
		my $c = getc($sock);
		print STDERR $c;
		$rep .= $c;
	}
	if ($rep !~ m,HTTP/.* 200,) {
		die "proxy error: $rep\n";
	}
}

if (fork) {
	print STDERR "parent\[$$]  STDIN -> socket\n\n";
	xfer(STDIN, $sock);
} else {
	print STDERR "child \[$$]  socket -> STDOUT\n\n";
	xfer($sock, STDOUT);
}
exit;

sub xfer {
	my($in, $out) = @_;
	$RIN = $WIN = $EIN = "";
	$ROUT = "";
	vec($RIN, fileno($in), 1) = 1;
	vec($WIN, fileno($in), 1) = 1;
	$EIN = $RIN | $WIN;

	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 "perl proxy\[$$]: $!\n";
			last;
		} elsif ($len == 0) {
			print STDERR "perl proxy\[$$]: 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 "perl proxy\[$$]: Output is EOF. $!\n";
				$quit = 1;
				last;
			}
			$len -= $written;
			$offset += $written;
		}
		last if $quit;
	}
	close($in);
	close($out);
}
'
	echo "$cod" > $tf
	chmod 700 $tf
}

ptmp=""
if [ "X$proxy" != "X" ]; then
466 467 468
	ptmp="/tmp/ssl_vncviewer${RANDOM}.$$.pl"
	mytmp "$ptmp"
	pcode "$ptmp"
469 470 471 472 473 474
	connect="exec = $ptmp"
else
	connect="connect = $host:$port"
fi


475
##debug = 7
476 477 478 479
tmp=/tmp/ssl_vncviewer${RANDOM}.$$
mytmp "$tmp"

cat > "$tmp" <<END
480 481 482
foreground = yes
pid =
client = yes
runge's avatar
runge committed
483
debug = 6
484
$STUNNEL_EXTRA_OPTS
485 486 487 488
$verify
$cert

[vnc_stunnel]
489
accept = localhost:$use
490
$connect
491 492 493 494
END

echo ""
echo "Using this stunnel configuration:"
495
echo ""
496
cat "$tmp" | uniq
497 498 499
echo ""
sleep 1

500
echo ""
501 502
echo "Running: stunnel"
echo "$STUNNEL $tmp"
503
$STUNNEL "$tmp" < /dev/tty > /dev/tty &
504 505 506 507 508 509 510 511 512
pid=$!
echo ""

# pause here to let the user supply a possible passphrase for the
# mycert key:
if [ "X$mycert" != "X" ]; then
	sleep 4
fi
sleep 2
513
rm -f "$tmp"
514

515 516 517
echo ""
echo "Running viewer:"
echo $VNCVIEWERCMD "$@" localhost:$N
518
echo ""
519
$VNCVIEWERCMD "$@" localhost:$N
520 521

kill $pid
522
sleep 1