ssl_vncviewer 14.8 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
# -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).
runge's avatar
runge committed
36
#
37
#         For the "double proxy" case use -proxy host1:port1,host2:port2
runge's avatar
runge committed
38 39
#         (the first CONNECT is done through host1:port1 to host2:port2
#         and then a 2nd CONNECT to the destination VNC server.)
40
#
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
#    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)
#
runge's avatar
runge committed
62 63 64 65 66 67 68 69
#         A "double ssh" can be specified via a -proxy string with the two
#         hosts separated by a comma:
#
#             [user1@]host1[:port1],[user2@]host2[:port2]
#
#         in which case a ssh to host1 and thru it via a -L redir a 2nd
#         ssh is established to host2.  
#
70 71 72 73 74 75 76 77
#         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
#
runge's avatar
runge committed
78 79
#         ssl_vncviewer -ssh -proxy fred@gw-host,fred@peecee localhost:0
#
80 81 82
# -sshcmd cmd   Run "cmd" via ssh instead of the default "sleep 15"
#               e.g. -sshcmd 'x11vnc -display :0 -localhost -rfbport 5900'
#
83 84 85 86 87 88 89 90 91 92 93
# -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
94 95 96 97 98 99 100 101
#
# -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)
#
102
#
103
# set VNCVIEWERCMD to whatever vncviewer command you want to use.
104
#
runge's avatar
runge committed
105
VNCIPCMD=${VNCVIEWERCMD:-vncip}
106
VNCVIEWERCMD=${VNCVIEWERCMD:-vncviewer}
107
#
108
# Same for STUNNEL, e.g. set it to /path/to/stunnel or stunnel4, etc.
109
#
110 111 112

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

113 114 115 116 117 118 119 120 121
if [ "X$STUNNEL" = "X" ]; then
	type stunnel4 > /dev/null 2>&1
	if [ $? = 0 ]; then
		STUNNEL=stunnel4
	else
		STUNNEL=stunnel
	fi
fi

122
help() {
123
	tail +2 "$0" | sed -e '/^$/ q'
124 125
}

runge's avatar
runge committed
126
gotalpha=""
127
use_ssh=""
128
use_sshssl=""
129
direct_connect=""
130 131 132 133 134 135
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
136

137 138 139 140 141 142 143 144
# grab our cmdline options:
while [ "X$1" != "X" ]
do
    case $1 in 
	"-verify")	shift; verify="$1"
                ;;
	"-mycert")	shift; mycert="$1"
                ;;
145 146
	"-proxy")	shift; proxy="$1"
                ;;
147 148
	"-ssh")		use_ssh=1
                ;;
149 150 151
	"-sshssl")	use_ssh=1
			use_sshssl=1
                ;;
152 153
	"-sshcmd")	shift; ssh_cmd="$1"
                ;;
154 155
	"-sshargs")	shift; ssh_args="$1"
                ;;
runge's avatar
runge committed
156 157 158 159
	"-alpha")	gotalpha=1
                ;;
	"-grab")	VNCVIEWER_GRAB_SERVER=1; export VNCVIEWER_GRAB_SERVER
                ;;
160 161
	"-h"*)	help; exit 0
                ;;
162 163
	"--h"*)	help; exit 0
                ;;
164 165 166 167 168 169
	*)	break
                ;;
    esac
    shift
done

runge's avatar
runge committed
170 171 172 173 174
if [ "X$gotalpha" != "X1" ]; then
	NO_ALPHABLEND=1
	export NO_ALPHABLEND
fi

175 176 177
orig="$1"
shift

178
if [ "X$use_ssh" = "X1" -a "X$use_sshssl" = "X" ]; then
179 180 181 182 183 184
	if [ "X$mycert" != "X" -o "X$verify" != "X" ]; then
		echo "-mycert and -verify cannot be used in -ssh mode" 
		exit 1
	fi
fi

185 186 187 188 189 190 191 192 193
if echo "$orig" | grep '^vnc://' > /dev/null; then
	orig=`echo "$orig" | sed -e 's,vnc://,,'`
	verify=""
	mycert=""
	use_ssh=""
	use_sshssl=""
	direct_connect=1
fi

194
# play around with host:display port:
runge's avatar
runge committed
195 196 197
if echo "$orig" | grep ':' > /dev/null; then
	:
else
198 199 200 201 202
	orig="$orig:0"
fi

host=`echo "$orig" | awk -F: '{print $1}'`
disp=`echo "$orig" | awk -F: '{print $2}'`
runge's avatar
runge committed
203 204 205
if [ "X$host" = "X" ]; then
	host=localhost
fi
206 207
if [ $disp -lt 200 ]; then
	port=`expr $disp + 5900`
208 209
else
	port=$disp
210 211 212
fi

# try to find an open listening port via netstat(1):
213
inuse=""
214
if uname | grep Linux > /dev/null; then
215
	inuse=`netstat -ant | egrep 'LISTEN|WAIT|ESTABLISH|CLOSE' | awk '{print $4}' | sed 's/^.*://'`
216 217
elif uname | grep SunOS > /dev/null; then
	inuse=`netstat -an -f inet -P tcp | grep LISTEN | awk '{print $1}' | sed 's/^.*\.//'`
218
# add others...
219
fi
220 221 222 223 224 225 226 227

date_sec=`date +%S`

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

228 229
	while [ $try -lt 6000 ]
	do
230 231 232
		if [ "X$inuse" = "X" ]; then
			break
		fi
runge's avatar
runge committed
233 234 235
		if echo "$inuse" | grep -w $try > /dev/null; then
			:
		else
236
			use0=$try
237 238 239 240
			break
		fi
		try=`expr $try + 1`
	done
241 242 243 244 245 246 247 248
	if [ "X$use0" = "X" ]; then
		use0=`expr $date_sec + $try0`
	fi

	echo $use0
}

use=`findfree 5930`
249

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
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"}
runge's avatar
runge committed
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
	if echo "$proxy" | grep "," > /dev/null; then
		proxy1=`echo "$proxy" | awk -F, '{print $1}'`
		proxy2=`echo "$proxy" | awk -F, '{print $2}'`
		# user1@gw1.com:port1,user2@ws2:port2
		ssh_host1=`echo "$proxy1" | awk -F: '{print $1}'`
		ssh_port1=`echo "$proxy1" | awk -F: '{print $2}'`
		if [ "X$ssh_port1" = "X" ]; then
			ssh_port1="22"
		fi
		ssh_host2=`echo "$proxy2" | awk -F: '{print $1}'`
		ssh_user2=`echo "$ssh_host2" | awk -F@ '{print $1}'`
		ssh_host2=`echo "$ssh_host2" | awk -F@ '{print $2}'`
		if [ "X$ssh_host2" = "X" ]; then
			ssh_host2=$ssh_user2
			ssh_user2=""
		else
			ssh_user2="${ssh_user2}@"
		fi
		ssh_port2=`echo "$proxy2" | awk -F: '{print $2}'`
		if [ "X$ssh_port2" = "X" ]; then
			ssh_port2="22"
		fi
		proxport=`findfree 3500`
		echo
		echo "Running 1st ssh proxy:"
290 291
		echo "$ssh -f -x -p $ssh_port1 -t -e none -L $proxport:$ssh_host2:$ssh_port2 $ssh_host1 \"sleep 30\""
		      $ssh -f -x -p $ssh_port1 -t -e none -L $proxport:$ssh_host2:$ssh_port2 $ssh_host1 "sleep 30"
runge's avatar
runge committed
292 293 294 295 296
		ssh_args="$ssh_args -o NoHostAuthenticationForLocalhost=yes"
		sleep 1
		stty sane
		proxy="${ssh_user2}localhost:$proxport"
	fi
297 298 299 300 301 302 303 304 305 306
	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:"
307 308 309 310 311 312
	sz=`echo "$ssh_cmd" | wc -c`
	if [ "$sz" -gt 200 ]; then
		info="..."
	else
		info="$ssh_cmd"
	fi
313 314 315 316 317 318

	C=""
	if [ "X$SSL_VNCVIEWER_USE_C" != "X" ]; then
		C="-C"
	fi
	# the -t option actually speeds up typing response via VNC!!
319
	if [ "X$SSL_VNCVIEWER_SSH_ONLY" != "X" ]; then
320
		echo "$ssh -x -p $ssh_port -t $C $ssh_args $ssh_host \"$info\""
321
		echo ""
322
		$ssh -x -p $ssh_port -t $C $ssh_args $ssh_host "$ssh_cmd"
323 324
		exit $?
	elif [ "X$SSL_VNCVIEWER_NO_F" != "X" ]; then
325
		echo "$ssh -x -p $ssh_port -t $C -L ${use}:${vnc_host}:${port} $ssh_args $ssh_host \"$info\""
326
		echo ""
327
		$ssh -x -p $ssh_port -t $C -L ${use}:${vnc_host}:${port} $ssh_args $ssh_host "$ssh_cmd"
328
	else
329
		echo "$ssh -x -f -p $ssh_port -t $C -L ${use}:${vnc_host}:${port} $ssh_args $ssh_host \"$info\""
330
		echo ""
331
		$ssh -x -f -p $ssh_port -t $C -L ${use}:${vnc_host}:${port} $ssh_args $ssh_host "$ssh_cmd"
332
	fi
333 334 335 336 337 338
	if [ "$?" != "0" ]; then
		echo ""
		echo "ssh to $ssh_host failed."
		exit 1
	fi
	echo ""
339
	if [ "X$ssh_cmd" = "Xsleep $ssh_sleep" ] ; then
340 341 342 343 344 345
		sleep 1
	else
		# let any command get started a bit.
		sleep 5
	fi
	echo ""
346 347
	#reset
	stty sane
348 349
	if [ "X$use_sshssl" = "X" ]; then
		echo "Running viewer:"
350
		echo "$VNCVIEWERCMD" "$@" localhost:$N
351
		echo ""
352
		"$VNCVIEWERCMD" "$@" localhost:$N
353

354 355 356 357 358 359 360 361 362 363 364
		exit $?
	else
		use2=`findfree 5960`
		host0=$host
		port0=$port
		host=localhost
		port=$use
		use=$use2
		N=`expr $use - 5900`
		proxy=""
	fi
365 366
fi

367 368 369 370 371 372 373 374 375 376 377 378 379 380
# 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

381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
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

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
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";

421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
my $listen_handle = "";
if ($ENV{SSL_VNC_LISTEN} != "") {
	my $listen_sock = IO::Socket::INET->new(
		Listen    => 2,
		LocalAddr => "localhost",
		LocalPort => $ENV{SSL_VNC_LISTEN},
		Proto     => "tcp");
	if (! $listen_sock) {
		die "perl proxy: $!\n";
	}
	my $ip;
	($listen_handle, $ip) = $listen_sock->accept();
	if (! $listen_handle) {
		die "perl proxy: $!\n";
	}
}

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 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
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";
492 493 494 495 496
	if ($listen_handle) {
		xfer($listen_handle, $sock);
	} else {
		xfer(STDIN, $sock);
	}
497 498
} else {
	print STDERR "child \[$$]  socket -> STDOUT\n\n";
499 500 501 502 503
	if ($listen_handle) {
		xfer($sock, $listen_handle);
	} else {
		xfer($sock, STDOUT);
	}
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
}
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
553 554 555
	ptmp="/tmp/ssl_vncviewer${RANDOM}.$$.pl"
	mytmp "$ptmp"
	pcode "$ptmp"
556 557 558 559 560
	connect="exec = $ptmp"
else
	connect="connect = $host:$port"
fi

561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
if [ "X$direct_connect" != "X" ]; then
	echo ""
	echo "Running viewer for direct connection:"
	echo ""
	echo "** NOTE: THERE WILL BE NO SSL OR SSH ENCRYPTION **"
	echo ""
	if type printf > /dev/null 2>&1; then
		printf  "Are you sure you want to continue? [y]/n "
	else
		echo -n "Are you sure you want to continue? [y]/n "
	fi
	read x
	if [ "X$x" = "Xn" ]; then
		exit 1
	fi
	echo ""
	if [ "X$ptmp" != "X" ]; then
		SSL_VNC_LISTEN=$use
		export SSL_VNC_LISTEN
		$ptmp &
		sleep 2
		host="localhost"
		disp="$N"
	fi
	echo "$VNCVIEWERCMD" "$@" $host:$disp
	echo ""
	"$VNCVIEWERCMD" "$@" $host:$disp
	exit $?
fi
590

591
##debug = 7
592 593 594 595
tmp=/tmp/ssl_vncviewer${RANDOM}.$$
mytmp "$tmp"

cat > "$tmp" <<END
596 597 598
foreground = yes
pid =
client = yes
runge's avatar
runge committed
599
debug = 6
600
$STUNNEL_EXTRA_OPTS
601 602 603 604
$verify
$cert

[vnc_stunnel]
605
accept = localhost:$use
606
$connect
607 608 609 610
END

echo ""
echo "Using this stunnel configuration:"
611
echo ""
612
cat "$tmp" | uniq
613 614 615
echo ""
sleep 1

616
echo ""
617 618
echo "Running: stunnel"
echo "$STUNNEL $tmp"
619
$STUNNEL "$tmp" < /dev/tty > /dev/tty &
620 621 622 623 624 625 626 627 628
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
629
rm -f "$tmp"
630

631 632
echo ""
echo "Running viewer:"
633
echo "$VNCVIEWERCMD" "$@" localhost:$N
634
echo ""
635
"$VNCVIEWERCMD" "$@" localhost:$N
636 637

kill $pid
638
sleep 1