1#!/usr/bin/perl
2#
3##########################################################################
4# desktop.cgi:
5#
6# This is an example CGI script to provide multi-user web access to
7# x11vnc desktops.  The user desktop sessions run in 'Xvfb' displays
8# that are created automatically.
9#
10# This script should/must be served by an HTTPS (i.e. SSL) webserver,
11# otherwise the unix and vnc passwords would be sent over the network
12# unencrypted (see below to disable if you really want to.)
13#
14# The Java VNC Viewer applet connections are encrypted by SSL as well.
15#
16# You can use this script to provide unix users desktops available on
17# demand via any Java enabled web browser.  One could also use this for
18# a special-purpose 'single application' service running in a minimal
19# window manager.
20#
21# One example of a special-purpose application would be a scientific
22# data visualization tool running on a server where the data is housed.
23# To do this set $x11vnc_extra_opts = '-env FD_PROG=/path/to/app/launcher'
24# where the program launches your special purpose application.  A very
25# simple example:  '-env FD_PROG=/usr/bin/xclock'
26# 
27#
28# Depending on where you place this script, the user accesses the service
29# with the URL:
30#
31#     https://your.webserver.net/cgi-bin/desktop.cgi
32#
33# Then they login with their unix username and password to get their
34# own desktop session.
35#
36# If the user has an existing desktop it is connected to directly,
37# otherwise a new session is created inside an Xvfb display and then
38# connected to by VNC.
39#
40# It is possible to do port redirection to other machines running SSL
41# enabled VNC servers (see below.)  This script does not start the VNC
42# servers on the other machines, although with some extra rigging you
43# should be able to do that as well.
44#
45# You can customize the login procedure to whatever you want by modifying
46# this script, or by using ideas in this script write your own PHP,
47# (for example), script.
48#
49##########################################################################
50# Overriding default settings:
51#
52# If you want to override any settings in this script and do not
53# want to edit this script create the assignments in a file named
54# 'desktop.cgi.conf' in the same directory as desktop.cgi.  It will be
55# sourced after the defaults are set.  The format of desktop.cgi.conf
56# is simply perl statements that make the assignments.
57#
58# For example, if you put something like this in desktop.cgi.conf:
59#
60#	$x11vnc = '/usr/local/bin/x11vnc';
61#
62# that will set the path to the x11vnc binary to that location.  Look at
63# the settings below for the other variables that you can modify, for
64# example one could set $allowed_users_file.
65#
66##########################################################################
67# x11vnc:
68#
69# You need to install x11vnc or otherwise have it available.  It is
70# REQUIRED that you use x11vnc 0.9.10 or later.  It won't work with
71# earlier versions.  See below the $x11vnc parameter that you can set
72# to the full path to x11vnc.
73#
74##########################################################################
75# Xvfb:
76#
77# Note that the x11vnc -create virtual desktop service used below requires
78# that you install the 'Xvfb' program.  On debian this is currently done
79# via 'apt-get install xvfb'.
80#
81# If you are having trouble getting 'x11vnc -create' to work with this
82# script (it can be tricky), try it manually and/or see the x11vnc FAQ
83# links below.
84#
85##########################################################################
86# Apache httpd:
87#
88# You should put this script in, say, a cgi-bin directory.  Enable cgi
89# scripts in your apache (or other httpd) config.  For example, we have
90# these lines (not commented):
91#
92# In httpd.conf:
93#
94#	ScriptAlias /cgi-bin/ "/dist/apache/2.0/cgi-bin/"
95#
96#	<Directory "/dist/apache/2.0/cgi-bin">
97#	    AllowOverride None
98#	    Options None     
99#	    Order allow,deny
100#	    Allow from all 
101#	</Directory>
102#
103# and in ssl.conf:
104#
105#	<Directory "/dist/apache/2.0/cgi-bin">
106#	    SSLOptions +StdEnvVars
107#	</Directory>
108#
109# Do not be confused by the non-standard /dist/apache/2.0 apache
110# installation location that we happen to use.  Yours will be different.
111#
112# You can test that you have CGI scripts working properly with the 
113# 'test-cgi' and 'printenv' scripts apache provides.
114#
115# Copy this file (desktop.cgi) to /dist/apache/2.0/cgi-bin and then run
116# 'chmod 755 ...' on it to make it executable.
117#
118##########################################################################
119# Applet Jar files served by apache:
120#
121# You will *also* need to copy the x11vnc classes/ssl/UltraViewerSSL.jar
122# file to the httpd DocumentRoot to be accessible by: /UltraViewerSSL.jar
123# in a URL (or change $applet_jar below or the html in $applet_html if
124# you want to use a different location.)
125#
126# This location is relative to the apache DocumentRoot 'htdocs' directory.
127# For our (non-standard location installation) that meant we copied the
128# file to:
129#
130#    /dist/apache/2.0/htdocs/UltraViewerSSL.jar
131#
132# (your DocumentRoot directory will be different.) 
133#
134# The VncViewer.jar (tightvnc) will also work, but you need to change
135# the $applet_jar below.  You can get these jar files from the x11vnc
136# tarball from:
137#
138#    http://www.karlrunge.com/x11vnc/#downloading
139#
140# This script requires x11vnc 0.9.10 or later.
141#
142# Note that the usage mode for this script is a different from regular
143# 'x11vnc -http ...' usage where x11vnc acts as a mini web server and
144# serves its own applet jars.  We don't use that mode for this script.
145# Apache (httpd) serves the jars.
146#
147#
148##########################################################################
149# Notes and Information:
150#
151# Each x11vnc server created for a user login will listen on its own port
152# (see below for port selection schemes.)  Your firewall must let in *ALL*
153# of these ports (e.g. a port range, see below for the syntax.)
154#
155# It is also possible, although not as reliable, to do all of this through
156# a single port, see the fixed port scheme $find_free_port = 'fixed:5910'
157# below.  This single port mode must be different from apache's port
158# (usually 443 for https) and must also be allowed in by your firewall.
159#
160# Note: The fixed port scheme is DISABLED by default.
161#
162# It is also possible to have this script act as a vnc redirector to SSL
163# enabled VNC servers running on *other* machines inside your firewall
164# (presumably the users' desktops) See the $enable_port_redirection
165# setting below.  The user provides 'username@host:port' instead of just
166# 'username' when she logs in.  This script doesn't start VNC servers
167# on those other machines, the servers must be running there already.
168# (If you want this script to start them you will need to add it
169# yourself.)  It is possible to provide a host:port allow list to limit
170# which internal machines and ports can be redirected to.  This is the
171# $port_redirection_allowed_hosts parameter.
172#
173# Note: The vnc redirector scheme is DISABLED by default.
174#
175# Note there are *two* SSL certificates involved that the user may be
176# asked to inspect: apache's SSL cert and x11vnc's SSL cert.  This may
177# confuse naive users.  You may want to use the same cert for both.
178#
179# This script provides one example on how to provide the service.  You can
180# customize it to meet your needs, e.g. switch to php, newer cgi modules,
181# different authentication, SQL database for user authentication, etc,
182# etc.  If you plan to use it in production, please examine all security
183# aspects of it carefully; read the comments in the script for more info.
184#
185# More information and background and troubleshooting:
186#
187#      http://www.karlrunge.com/x11vnc/faq.html#faq-xvfb
188#      http://www.karlrunge.com/x11vnc/faq.html#faq-ssl-tunnel-viewers
189#      http://www.karlrunge.com/x11vnc/faq.html#faq-ssl-java-viewer-proxy
190#      http://www.karlrunge.com/x11vnc/faq.html#faq-ssl-portal
191#      http://www.karlrunge.com/x11vnc/faq.html#faq-unix-passwords
192#      http://www.karlrunge.com/x11vnc/faq.html#faq-userlogin
193#
194#
195# Please also read the comments below for changing specific settings.
196# You can modify them in this script or by override file 'desktop.cgi.conf'
197
198
199#-------------------------------------------------------------------------
200# Copyright (c) 2010 by Karl J. Runge <runge@karlrunge.com>
201#
202# desktop.cgi is free software; you can redistribute it and/or modify
203# it under the terms of the GNU General Public License as published by
204# the Free Software Foundation; either version 2 of the License, or (at
205# your option) any later version.
206# 
207# desktop.cgi is distributed in the hope that it will be useful,
208# but WITHOUT ANY WARRANTY; without even the implied warranty of
209# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
210# GNU General Public License for more details.
211# 
212# You should have received a copy of the GNU General Public License
213# along with desktop.cgi; if not, write to the Free Software
214# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
215# or see <http://www.gnu.org/licenses/>.
216#-------------------------------------------------------------------------
217
218use strict;
219use IO::Socket::INET;
220
221# Test for INET6 support:
222#
223my $have_inet6 = 0;
224eval "use IO::Socket::INET6;";
225$have_inet6 = 1 if $@ eq "";
226
227##########################################################################
228# Path to the x11vnc program:
229#
230my $x11vnc = '/usr/bin/x11vnc';
231
232
233##########################################################################
234# You can set some extra x11vnc cmdline options here:
235#
236my $x11vnc_extra_opts = '';
237
238
239##########################################################################
240# Override the default x11vnc viewer connection timeout of 75 seconds:
241#
242my $x11vnc_timeout = '';
243
244
245##########################################################################
246# TCP Ports:
247#
248# Set find_free_port to 1 (or the other modes described below) to
249# autoselect a free port to use.  The default is to use a port based on
250# the userid number (7000 + uid).
251#
252my $find_free_port = 0;
253
254# Or specify a port range:
255#
256#$find_free_port = '7000-8000';
257#
258# Or indicate to use a kludge to try to do everything through a SINGLE
259# port.  To try to avoid contention on the port, simultaneous instances
260# of this script attempt to 'take turns' using it the single port.
261#
262#$find_free_port = 'fixed:5910';
263
264# This is the starting port for 7000 + uid and also $find_free_port = 1
265# autoselection:
266#
267my $starting_port = 7000;
268
269# Listen on AF_INET6 if IO::Socket::INET6 is available.
270#
271my $listen_on_ipv6 = 0;
272
273
274##########################################################################
275# Port redirection mode:
276#
277# This is to enable port redirection mode: username@host:port. If
278# username is valid, there will be a port redirection to internal machine
279# host:port.  Presumably there is already an SSL enabled and password
280# protected VNC server running there.  We don't start that VNC server.
281# (You might be able to figure out a way to do this yourself.)
282#
283# See the next setting for an allowed hosts file.  The default for port
284# redirection is off.
285#
286my $enable_port_redirection = 0;
287
288# A file with allowed port redirections.  The empty string '' (the
289# default) means all host:port redirections would be allowed.
290#
291# Format of the file: A list of 'user@host:port' or 'host:port'
292# entries, one per line.  Port ranges, e.g. host:n-m are also accepted.
293#
294# Leading and trailing whitespace is trimmed off each line.  Blank lines
295# and comment lines starting with '#' are skipped.  A line consisting of
296# 'ALL' matches everything.  If no match can be found or the file cannot
297# be opened the connection is dropped.
298#
299my $port_redirection_allowed_hosts = '';
300
301
302##########################################################################
303# Allowed users:
304#
305# To limit which users can use this service, set the following to a file
306# that contains the allowed user names one per line.  Lines starting with
307# the '#' character are skipped.
308#
309my $allowed_users_file = '';
310
311
312##########################################################################
313# Denied users:
314#
315# As with $allowed_users_file, but to deny certain users.  Applied after
316# any $allowed_users_file check and overrides the result.
317#
318my $denied_users_file = '';
319
320
321##########################################################################
322# trustUrlVncCert applet parameter:
323#
324# Set to 0 to have the java applet html set the parameter
325# trustUrlVncCert=no, i.e. the applet will not automatically accept
326# an SSL cert already accepted by an HTTPS URL.  See $applet_html and
327# print_applet_html() below for more info.
328#
329my $trustUrlVncCert = 1;
330
331
332##########################################################################
333# One-time VNC password fifo:
334#
335# For extra security against local untrusted users a fifo is used
336# to copy the one-time VNC password to the user's VNC password file
337# ~user/x11vnc.pw.  If that fifo transfer technique causes problems,
338# you can set this value to 1 to disable the security feature:
339#
340my $disable_vnc_passwd_fifo_safety = 0;
341
342
343##########################################################################
344# Comment this out if you don't want PATH modified:
345#
346$ENV{PATH} = "/usr/bin:/bin:$ENV{PATH}";
347
348
349##########################################################################
350# For the next two settings, note that most users will be confused that
351# geometry and session are ignored when they are returning to their
352# existing desktop session (x11vnc FINDDISPLAY action.)
353
354
355##########################################################################
356# Used below if user did not specify preferred geometry and color depth:
357#
358my $default_geometry = '1024x768x24';
359
360
361# Set this to the list of x11vnc -create sessions types to show a session
362# dropdown for the user to select from.
363#
364my $session_types = '';
365#
366# example:
367#$session_types = 'gnome kde xfce lxde wmaker enlightenment mwm twm failsafe'; 
368
369
370##########################################################################
371# Set this to 1 to enable user setting a unique tag for each one
372# of his desktops and so can have multiple ones simultaneously and
373# select which one he wants.  For now we just hack this onto geometry
374# 1024x768x24:my_2nd_desktop but ultimately there should be a form entry
375# for it.  Search for enable_unique_tags for more info:
376#
377my $enable_unique_tags = 0;
378my $unique_tag = '';
379
380
381##########################################################################
382# String of HTML for the login form:
383#
384# Feel free to customize to your taste, _USERNAME_ and _GEOMETRY_ are
385# expanded to that of the request.
386#
387my $login_str = <<"END";
388<title>x11vnc web access</title>
389<h3>x11vnc web access</h3>
390<form action="$ENV{REQUEST_URI}" method="post">
391 <table border="0">
392  <tr><td colspan=2><h2>Login</h2></td></tr>
393  <tr><td>Username:</td><td>
394  <input type="text" name="username" maxlength="40" value="_USERNAME_">
395  </td></tr>
396  <tr><td>Password:</td><td>
397  <input type="password" name="password" maxlength="50">
398  </td></tr>
399  <tr><td>Geometry:</td><td>
400  <input type="text" name="geometry" maxlength="40" value="_GEOMETRY_">
401  </td></tr>
402  <!-- session -->
403  <tr><td colspan="2" align="right">
404  <input type="submit" name="submit" value="Login">
405  </td></tr>
406 </table>
407</form>
408END
409
410
411##########################################################################
412# String of HTML returned to web browser to launch applet:
413#
414# Feel free to customize to your taste, _UID_, _VNC_PORT_, _WIDTH_,
415# _HEIGHT_, _PASS_, _TRUST_UVC_, _APPLET_JAR_, and _APPLET_CLASS_ are
416# expanded to the appropriate values before sending out to the browser.
417#
418my $applet_html = <<"END";
419<html>
420<TITLE>
421x11vnc desktop (_UID_/_VNC_PORT_)
422</TITLE>
423<APPLET CODE=_APPLET_CLASS_ ARCHIVE=_APPLET_JAR_ WIDTH=_WIDTH_ HEIGHT=_HEIGHT_>
424<param name=PORT value=_VNC_PORT_>
425<param name=VNCSERVERPORT value=_VNC_PORT_>
426<param name=PASSWORD value=_PASS_>
427<param name=trustUrlVncCert value=_TRUST_UVC_>
428<param name="Open New Window" value=yes>
429<param name="Offer Relogin" value=no>
430<param name="ignoreMSLogonCheck" value=yes>
431<param name="delayAuthPanel" value=yes>
432<!-- extra -->
433</APPLET>
434<br>
435<a href="$ENV{REQUEST_URI}">Login page</a><br>
436<a href=http://www.karlrunge.com/x11vnc>x11vnc website</a>
437</html>
438END
439
440
441##########################################################################
442# These java applet strings are expanded into the above $applet_html.
443# Note that $applet_jar is relative to your apache DocumentRoot (htdocs)
444# not the filesystem root. 
445#
446my $applet_jar = '/UltraViewerSSL.jar';
447my $applet_class = 'VncViewer.class';
448
449# These make the applet panel smaller because we use 'Open New Window'
450# anyway (set to 'W' or 'H' to use actual session geometry values):
451#
452my $applet_width  = '400';
453my $applet_height = '300';
454
455# To customize ALL of the HTML printed out you may need to redefine
456# the bye() subtroutine in your desktop.cgi.conf file. 
457
458
459##########################################################################
460# Override any of the above settings by setting them in a file named
461# 'desktop.cgi.conf'.  It is sourced here.
462#
463# You can override any variable set above by supplying perl code
464# in $0.conf that sets it to the desired value.
465#
466# Some examples you could put in $0.conf:
467#
468#   $x11vnc = '/usr/local/bin/x11vnc';
469#   $x11vnc_extra_opts = '-env FD_PROG=/usr/bin/xclock';
470#   $x11vnc_extra_opts = '-ssl /usr/local/etc/dtcgi.pem';
471#   $find_free_port = 'fixed:5999';
472#   $enable_port_redirection = 1;
473#   $allowed_users_file = '/usr/local/etc/dtcgi.allowed';
474#
475if (-f "$0.conf") {
476	eval `cat "$0.conf"`;
477}
478
479
480##########################################################################
481# END OF MAIN USER SETTINGS.
482# Only power users should change anything below.
483##########################################################################
484
485# Print http header reply:
486#
487print STDOUT "Content-Type: text/html\r\n\r\n";
488
489
490# Require HTTPS so that unix and vnc passwords are not sent in clear text
491# (perhaps it is too late...)  Disable HTTPS here at your own risk.
492#
493if ($ENV{HTTPS} !~ /^on$/i) {
494	bye("HTTPS must be used (to encrypt passwords)");
495}
496
497
498# Read URL request:
499#
500my $request;
501if ($ENV{'REQUEST_METHOD'} eq "POST") {
502	read(STDIN, $request, $ENV{'CONTENT_LENGTH'});
503} elsif ($ENV{'REQUEST_METHOD'} eq "GET" ) {
504	$request = $ENV{'QUERY_STRING'};
505} else {
506	$request = $ARGV[0];
507}
508
509my %request = url_decode(split(/[&=]/, $request));
510
511
512# Experiment for FD_TAG x11vnc feature for multiple desktops for a
513# single user:
514#
515# we hide it in geometry:tag for now:
516#
517if ($enable_unique_tags && $request{geometry} =~ /^(.*):(\w+)$/) {
518	$request{geometry} = $1;
519	$unique_tag = $2;
520}
521
522# Check/set geometry and session:
523#
524if (!exists $request{geometry} || $request{geometry} !~ /^[x\d]+$/) {
525	# default geometry and depth:
526	$request{geometry} = $default_geometry;
527}
528if (!exists $request{session} || $request{session} =~ /^\s*$/) {
529	$request{session} = '';
530}
531
532
533# Expand _USERNAME_ and _GEOMETRY_ in the login string HTML:
534#
535$login_str =~ s/_USERNAME_/$request{username}/g;
536$login_str =~ s/_GEOMETRY_/$request{geometry}/g;
537
538
539# Check x11vnc version for installers of this script who do not know
540# how to read and follow instructions:
541#
542my $version = (split(' ', `$x11vnc -version`))[1];
543$version =~ s/\D*$//;
544
545my ($major, $minor, $micro) = split(/\./, $version);
546if ($major !~ /^\d+$/ || $minor !~ /^\d+$/) {
547	bye("The x11vnc program is not installed correctly.");
548}
549$micro = 0 unless $micro;
550my $level  = $major * 100 * 100 + $minor * 100 + $micro;
551my $needed =      0 * 100 * 100 +      9 * 100 +     10;
552if ($level < $needed) {
553	bye("x11vnc version 0.9.10 or later is required. (Found version $version)");
554}
555
556
557# Set up user selected desktop session list, if enabled:
558#
559my %sessions;
560
561if ($session_types ne '') {
562	my $str = "<tr><td>Session:</td><td>\n<select name=session>";
563	$str .= "<option value=none>select</option>";
564
565	foreach my $sess (split(' ', $session_types)) {
566		next if $sess =~ /^\s*$/;
567		next if $sess !~ /^\w+$/; # alphanumeric
568		$sessions{$sess} = 1;
569		$str .= "<option value=$sess>$sess</option>";
570	}
571	$str .= "</select>\n</td></tr>";
572
573	# This forces $request{session} to be a valid one:
574	#
575	if (! exists $sessions{$request{session}}) {
576		$request{session} = 'none';
577	}
578
579	# Insert into login_str:
580	#
581	my $r = $request{session};
582	$str =~ s/option value=\Q$r\E/option selected value=$r/;
583	$login_str =~ s/<!-- session -->/$str/;
584}
585
586
587# If no username or password, show login form:
588#
589if (!$request{username} && !$request{password}) {
590	bye($login_str);
591} elsif (!$request{username}) {
592	bye("No Username.<p>$login_str");
593} elsif (!$request{password}) {
594	bye("No Password.<p>$login_str");
595}
596
597
598# Some shorthand names:
599#
600my $username = $request{username};
601my $password = $request{password};
602my $geometry = $request{geometry};
603my $session  = $request{session};
604
605
606# If port redirection is enabled, split username@host:port
607#
608my $redirect_host = '';
609my $current_fh1 = '';
610my $current_fh2 = '';
611
612if ($enable_port_redirection) {
613	($username, $redirect_host) = split(/@/, $username, 2);
614	if ($redirect_host ne '') {
615		# will exit if the redirection is not allowed:
616		check_redirect_host();
617	}
618}
619
620# If there is an $allowed_users_file, check username against it:
621#
622if ($allowed_users_file ne '') {
623	if (! open(USERS, "<$allowed_users_file")) {
624		bye("Internal Error #0");
625	}
626	my $ok = 0;
627	while (<USERS>) {
628		chomp;
629		$_ =~ s/^\s*//;
630		$_ =~ s/\s*$//;
631		next if /^#/;
632		if ($username eq $_) {
633			$ok = 1;
634		}
635	}
636	close USERS;
637	if (! $ok) {
638		bye("Denied Username.<p>$login_str");
639	}
640}
641
642# If there is a $denied_users_file, check username against it:
643#
644if ($denied_users_file ne '') {
645	if (! open(USERS, "<$denied_users_file")) {
646		bye("Internal Error #0");
647	}
648	my $ok = 1;
649	while (<USERS>) {
650		chomp;
651		$_ =~ s/^\s*//;
652		$_ =~ s/\s*$//;
653		next if /^#/;
654		if ($username eq $_) {
655			$ok = 0;
656		}
657	}
658	close USERS;
659	if (! $ok) {
660		bye("Denied Username.<p>$login_str");
661	}
662}
663
664# Require username to be alphanumeric + '-' + '_':
665# (one may want to add '.' as well)
666#
667if ($username !~ /^\w[-\w]*$/) {
668	bye("Invalid Username.<p>$login_str");
669}
670
671
672# Get the userid number, we may use it as his VNC display port; this
673# also checks if the username exists:
674#
675my $uid = `/usr/bin/id -u '$username'`;
676chomp $uid;
677if ($? != 0 || $uid !~ /^\d+$/) {
678	bye("Invalid Username.<p>$login_str");
679}
680
681
682# Use x11vnc trick to check if the unix password is valid:
683# (requires x11vnc 0.9.10 or later.)
684#
685if (!open(X11VNC, "| $x11vnc -unixpw \%stdin > /dev/null")) {
686	bye("Internal Error #1");
687}
688print X11VNC "$username:$password\n";
689
690if (!close X11VNC) {
691	# x11vnc returns non-zero for invalid username+password:
692	bye("Invalid Password.<p>$login_str");
693}
694
695
696# Initialize random number generator for use below:
697#
698initialize_random();
699
700
701# Set vnc port:
702#
703my $vnc_port = 0;
704my $fixed_port = 0;
705
706if (! $find_free_port) {
707	# Fixed port based on userid (we assume it is free):
708	#
709	$vnc_port = $starting_port + $uid;
710
711} elsif ($find_free_port =~ /^fixed:(\d+)$/) {
712	#
713	# Enable the -loopbg method that tries to share a single port:
714	#
715	$vnc_port = $1;
716	$fixed_port = 1;
717} else {
718	# Autoselect a port, either default range (7000-8000) or a user
719	# supplied range.  (note that $find_free_port will now contain
720	# a socket listening on the found port so that it is held.)
721	#
722	$vnc_port = auto_select_port();
723}
724
725# Check for crazy port value:
726#
727if ($vnc_port > 64000 || $vnc_port < 1) {
728	bye("Internal Error #2 $vnc_port");
729}
730
731
732# If port redirection is enabled and the user selected it via
733# username@host:port, we do that right now and then exit.
734#
735if ($enable_port_redirection && $redirect_host ne '') {
736	port_redir();
737	exit 0;
738}
739
740
741# Make a random, onetime vnc password:
742#
743my $pass = '';
744my $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
745my @abc = split(//, $chars);
746
747for (my $i = 0; $i < 8; $i++) {
748	$pass .= $abc[ rand(scalar(@abc)) ];
749}
750
751# Use x11vnc trick to switch to user and store vnc pass in the passwdfile.
752# Result is $pass is placed in user's $HOME/x11vnc.pw
753#
754# (This is actually difficult to do without untrusted LOCAL users being
755# able to see the pass as well, see copy_password_to_user() for details
756# on how we try to avoid this.)
757#
758copy_password_to_user($pass);
759
760
761# Make a tmp file for x11vnc launcher script:
762#
763my $tmpfile = `/bin/mktemp /tmp/desktop.cgi.XXXXXX`;
764chomp $tmpfile;
765
766# Check if the tmpfile is valid:
767#
768if (! -e $tmpfile || ! -o $tmpfile || -l $tmpfile) {
769	unlink $tmpfile;
770	bye("Internal Error #3");
771}
772if (!chmod 0644, $tmpfile) {
773	unlink $tmpfile;
774	bye("Internal Error #4");
775}
776if (!open(TMP, ">$tmpfile")) {
777	unlink $tmpfile;
778	bye("Internal Error #5");
779}
780
781
782# The x11vnc command.  You adjust it to suit your needs.
783#
784# some ideas:  -env FD_PROG=/usr/bin/gnome-session 
785#              -env FD_SESS=kde
786#              -env FD_TAG=my_2nd_desktop
787#              -ultrafilexfer
788#
789# Note that -timeout will cause it to exit if client does not connect
790# and -sslonly disables VeNCrypt SSL connections.
791
792# Some settings:
793# (change these if you encounter timing problems, etc.)
794#
795my $timeout = 75;
796my $extra = '';
797if ($fixed_port) {
798	# settings for fixed port case:
799	$timeout = 45;
800	$extra .= " -loopbg100,1";
801}
802$timeout = $x11vnc_timeout if $x11vnc_timeout ne '';
803
804if ($session_types ne '') {
805	# settings for session selection case:
806	if (exists $sessions{$session}) {
807		$extra .= " -env FD_SESS='$session'";
808	}
809}
810if ($enable_unique_tags && $unique_tag ne '' && $unique_tag =~ /^\w+$/) {
811	$extra .= " -env FD_TAG='$unique_tag'";
812}
813
814# This md5sum check of the vnc passwd is for extra safety (see
815# copy_password_to_user for details.)
816#
817my $md5sum = '';
818system("type md5sum > /dev/null");
819if ($? == 0) {
820	my $md5 = `/bin/mktemp /tmp/desktop.cgi.XXXXXX`;
821	chomp $md5;
822	# compute md5sum of password:
823	if (-o $md5 && open(MD5, "| md5sum > $md5")) {
824		print MD5 "$pass\n";
825		close MD5;
826		if (open(MD5, "<$md5")) {
827			# read it:
828			my $line = <MD5>;
829			close MD5;
830			my ($s, $t) = split(' ', $line);
831			if (length($s) >= 32 && $s =~ /^\w+$/) {
832				# shell code for user to check he has correct passwd:
833				$md5sum = "if md5sum \$HOME/x11vnc.pw | grep '$s' > /dev/null; then true; else exit 1; fi";
834			}
835		}
836	}
837	unlink $md5;
838}
839
840# Write x11vnc command to the tmp file:
841#
842print TMP <<"END";
843#!/bin/sh
844export PATH=/usr/bin:/bin:\$PATH
845$md5sum
846$x11vnc -sigpipe ignore:HUP -nopw -rfbport $vnc_port \\
847    -passwdfile \$HOME/x11vnc.pw -oa \$HOME/x11vnc.log \\
848    -create -ssl SAVE -sslonly -env FD_GEOM=$geometry \\
849    -timeout $timeout $extra $x11vnc_extra_opts \\
850        >/dev/null 2>/dev/null </dev/null &
851sleep 2
852exit 0
853END
854
855close TMP;
856
857# Now launch x11vnc to switch to user and run the wrapper script:
858# (this requires x11vnc 0.9.10 or later.)
859#
860$ENV{UNIXPW_CMD} = "/bin/sh $tmpfile";
861
862# For the fixed port scheme we try to cooperate via lock file:
863# (disabled by default.)
864#
865my $rmlock = '';
866#
867if ($fixed_port) {
868	# try to grab the fixed port for the next 90 secs removing stale
869	# locks older than 60 secs:
870	#
871	$rmlock = lock_fixed_port(90, 60);
872}
873
874# Start the x11vnc cmd:
875#
876if (!open(X11VNC, "| $x11vnc -unixpw \%stdin > /dev/null")) {
877	unlink $tmpfile;
878	unlink $rmlock if $rmlock;
879	bye("Internal Error #6");
880}
881
882select(X11VNC); $| = 1; select(STDOUT);
883
884# Close any port we held.  There is still a gap of time between now
885# and when when x11vnc in $tmpfile reopens the port after the password
886# authentication.  So another instance of this script could accidentally
887# think it is free...
888# 
889sleep 1;
890close $find_free_port if $find_free_port;
891
892print X11VNC "$username:$password\n";
893close X11VNC;	# note we ignore return value.
894unlink $tmpfile;
895
896if ($rmlock) {
897	# let our x11vnc proceed a bit before removing lock.
898	sleep 2;
899	unlink $rmlock;
900}
901
902# Return html for the java applet to connect to x11vnc.
903#
904print_applet_html();
905
906exit 0;
907
908#################################################################
909# Subroutines:
910
911# print the message to client and exit with success.
912#
913sub bye {
914	my $msg = shift;
915	print STDOUT "<html>$msg</html>\n";
916	exit 0;
917}
918
919# decode %xx to character:
920#
921sub url_decode {
922	foreach (@_) {
923		tr/+/ /;
924		s/%(..)/pack("c",hex($1))/ge;
925	}
926	@_;
927}
928
929# seed random
930#
931sub initialize_random {
932	my $rbytes = '';
933	if (open(RAN, "</dev/urandom")) {
934		read(RAN, $rbytes, 8);
935	} elsif (open(RAN, "</dev/random")) {
936		read(RAN, $rbytes, 8);
937	} else {
938		$rbytes = sprintf("%08d", $$);
939	}
940	close RAN;
941
942	# set seed:
943	#
944	my $seed = join('', unpack("C8", $rbytes));
945	$seed = substr($seed, -9);
946	srand($seed);
947
948	for (my $i = 0; $i < ($$ % 4096); $i++) {
949		# Mix it up even a little bit more.  There should be
950		# over 1 billion possible vnc passwords now.
951		rand();
952	}
953}
954
955# Autoselect a port for vnc.  Note that a socket for the found port
956# is kept open (and stored in $find_free_port) until we call x11vnc at
957# the end.
958#
959sub auto_select_port {
960	my $pmin = $starting_port;	# default range 7000-8000.
961	my $pmax = $starting_port + 1000;
962
963	if ($find_free_port =~ /^(\d+)-(\d+)$/) {
964		# user supplied a range:
965		$pmin = $1;
966		$pmax = $2;
967		if ($pmin > $pmax) {
968			($pmin, $pmax) = ($pmax, $pmin);
969		}
970	} elsif ($find_free_port > 1024) {
971		# user supplied a starting port:
972		$pmin = $find_free_port;
973		$pmax = $pmin + 1000;
974	}
975
976	# Try to add a bit of randomness to the starting port so
977	# simultaneous instances of this script won't be fooled by the gap
978	# of time before x11vnc reopens the port (see near the bottom.)
979	#
980	my $dp = int(rand(1.0) * 0.25 * ($pmax - $pmin));
981	if ($pmin + $dp < $pmax - 20) {
982		$pmin = $pmin + $dp;
983	}
984
985	my $port = 0;
986
987	# Now try to find a free one:
988	#
989	for (my $p = $pmin; $p <= $pmax; $p++) {
990		my $sock = '';
991		if ($have_inet6 && $listen_on_ipv6) {
992			eval {$sock = IO::Socket::INET6->new(
993				Listen    => 1,
994				LocalPort => $p,
995				ReuseAddr => 1,
996				Domain    => AF_INET6,
997				LocalAddr => "::",
998				Proto     => "tcp"
999			);};
1000		} else {
1001			$sock = IO::Socket::INET->new(
1002				Listen	  => 1,
1003				LocalPort => $p,
1004				ReuseAddr => 1,
1005				Proto     => "tcp"
1006			);
1007		}
1008		if ($sock) {
1009			# we will keep this open until we call x11vnc:
1010			$find_free_port = $sock;
1011			$port = $p;
1012			last;
1013		}
1014	}
1015	return $port;
1016}
1017
1018# Since apache typically runs as user 'apache', 'nobody', etc, and not
1019# as root it is tricky for us to copy the pass string to a file owned by
1020# the user without some other untrusted local user being able to learn
1021# the password (e.g. via reading a file or watching ps.)  Note that with
1022# the x11vnc -unixpw trick we unfortunately can't use a pipe because
1023# the user command is run in its own tty.
1024#
1025# The best way would be a sudo action or a special setuid program for
1026# copying.  So consider doing that and thereby simplify this function.
1027#
1028# Short of a special program doing this, we use a fifo so ONLY ONE
1029# process can read the password.  If the untrusted local user reads it,
1030# then the logging-in user's x11vnc won't get it.  The login and x11vnc
1031# will fail, but the untrusted user won't gain access to the logging-in
1032# user's desktop.
1033#
1034# So here we start long, tedious work carefully managing the fifo.
1035#
1036sub copy_password_to_user {
1037
1038	my $pass = shift;
1039	
1040	my $use_fifo = '';
1041
1042	# Find a command to make a fifo:
1043	#
1044	system("type mkfifo > /dev/null");
1045	if ($? == 0) {
1046		$use_fifo = 'mkfifo %s';
1047	} else {
1048		system("type mknod > /dev/null");
1049		if ($? == 0) {
1050			$use_fifo = 'mknod %s p';
1051		}
1052	}
1053
1054	# Create the filename for our fifo:
1055	#
1056	my $fifo = `/bin/mktemp /tmp/desktop.cgi.XXXXXX`;
1057	chomp $fifo;
1058
1059	if (! -e $fifo || ! -o $fifo || -l $fifo) {
1060		unlink $fifo;
1061		bye("Internal Error #7");
1062	}
1063
1064	# disable fifo safety if requested:
1065	#
1066	if ($disable_vnc_passwd_fifo_safety) {
1067		$use_fifo = '';
1068	}
1069
1070	# Make the fifo:
1071	#
1072	if ($use_fifo) {
1073		$use_fifo = sprintf($use_fifo, $fifo);
1074
1075		# there is a small race here:
1076		system("umask 077; rm -f $fifo; $use_fifo; chmod 600 $fifo");
1077
1078		if (!chmod 0600, $fifo) {
1079			# we chmod once more..
1080			unlink $fifo;
1081			bye("Internal Error #8");
1082		}
1083
1084		if (! -o $fifo || ! -p $fifo || -l $fifo)  {
1085			# but we get out if not owned by us anymore:
1086			unlink $fifo;
1087			bye("Internal Error #9");
1088		}
1089	}
1090
1091	# Build cmd for user to read our fifo:
1092	#
1093	my $upw = '$HOME/x11vnc.pw';
1094	$ENV{UNIXPW_CMD} = "touch $upw; chmod 600 $upw; cat $fifo > $upw";
1095
1096	# Start it:
1097	#
1098	if (!open(X11VNC, "| $x11vnc -unixpw \%stdin > /dev/null")) {
1099		unlink $fifo;
1100		bye("Internal Error #10");
1101	}
1102	select(X11VNC); $| = 1; select(STDOUT);
1103
1104	if (! $use_fifo) {
1105		# regular file, we need to write it now.
1106		if (!open(FIFO, ">$fifo")) {
1107			close X11VNC;
1108			unlink $fifo;
1109			bye("Internal Error #11");
1110		}
1111		print FIFO "$pass\n";
1112		close FIFO;
1113	}
1114
1115	# open fifo up for reading.
1116	# (this means the bad guy can read it too.)
1117	#
1118	if (!chmod 0644, $fifo) {
1119		unlink $fifo;
1120		bye("Internal Error #12");
1121	}
1122
1123	# send the user's passwd now:
1124	#
1125	print X11VNC "$username:$password\n";
1126
1127	if ($use_fifo) {
1128		# wait a bit for the cat $fifo to start, reader will block.
1129		sleep 1;
1130		if (!open(FIFO, ">$fifo")) {
1131			close X11VNC;
1132			unlink $fifo;
1133			bye("Internal Error #13");
1134		}
1135		# here it goes:
1136		print FIFO "$pass\n";
1137		close FIFO;
1138	}
1139	close X11VNC;	# note we ignore return value.
1140	fsleep(0.5);
1141	unlink $fifo;
1142
1143	# Done!
1144}
1145
1146# For fixed, single port mode.  Try to open and lock the port before
1147# proceeding.
1148#
1149sub lock_fixed_port {
1150	my ($t_max, $t_age) = @_;
1151
1152	# lock file name:
1153	#
1154	my $lock = '/tmp/desktop.cgi.lock';
1155	my $remove = '';
1156
1157	my $t = 0;
1158	my $sock = '';
1159
1160	while ($t < $t_max) {
1161		if (-e $lock) {
1162			# clean out stale locks if possible:
1163			if (! -l $lock) {
1164				unlink $lock;
1165			} else {
1166				my ($pid, $time) = split(/:/, readlink($lock));
1167				if (! -d "/proc/$pid") {
1168					unlink $lock;
1169				}
1170				if (time() > $time + $t_age) {
1171					unlink $lock;
1172				}
1173			}
1174		}
1175
1176		my $reason = '';
1177
1178		if (-l $lock) {
1179			# someone has locked it.
1180			$reason = 'locked';
1181		} else {
1182			# unlocked, try to listen on port:
1183			if ($have_inet6 && $listen_on_ipv6) {
1184				eval {$sock = IO::Socket::INET6->new(
1185					Listen    => 1,
1186					LocalPort => $vnc_port,
1187					ReuseAddr => 1,
1188					Domain    => AF_INET6,
1189					LocalAddr => "::",
1190					Proto     => "tcp"
1191				);};
1192			} else {
1193				$sock = IO::Socket::INET->new(
1194					Listen	  => 1,
1195					LocalPort => $vnc_port,
1196					ReuseAddr => 1,
1197					Proto     => "tcp"
1198				);
1199			}
1200			if ($sock) {
1201				# we got it, now try to lock:
1202				my $str = "$$:" . time();
1203				if (symlink($str, $lock)) {
1204					$remove = $lock;
1205					$find_free_port = $sock;
1206					last;
1207				}
1208				# wow, we didn't lock it...
1209				$reason = "symlink failed: $!";
1210				close $sock;
1211			} else {
1212				$reason = "listen failed: $!";
1213			}
1214		}
1215		# sleep a bit and then try again:
1216		#
1217		print STDERR "$$ failed to get fixed port $vnc_port for $username at $t ($reason)\n";
1218		$sock = '';
1219		$t += 5;
1220		sleep 5;
1221	}
1222	if (! $sock) {
1223		bye("Failed to lock fixed TCP port. Try again a bit later.<p>$login_str");
1224	}
1225	print STDERR "$$ got fixed port $vnc_port for $username at $t\n";
1226
1227	# Return the file to remove, if any:
1228	#
1229	return $remove;
1230}
1231
1232
1233# Return html for the java applet to connect to x11vnc.
1234#
1235# N.B. Please examine the applet params, e.g. trustUrlVncCert=yes to
1236# see if you agree with them.  See x11vnc classes/ssl/README for all
1237# parameters.
1238#
1239# Note how we do not take extreme care to authenticate the server to
1240# the client applet (but note that trustUrlVncCert=yes is better than
1241# trustAllVncCerts=yes)  One can tighten all of this up at the expense
1242# of extra certificate dialogs (assuming the user bothers to check...)
1243#
1244# This assumes /UltraViewerSSL.jar is at document root; you need to put
1245# it there.
1246#
1247sub print_applet_html {
1248	my ($W, $H, $D) = split(/x/, $geometry);
1249
1250	# make it smaller since we 'Open New Window' below anyway.
1251	if ($applet_width ne 'W') {
1252		$W = $applet_width;
1253	}
1254	if ($applet_height ne 'H') {
1255		$H = $applet_height;
1256	}
1257
1258	my $tUVC = ($trustUrlVncCert ? 'yes' : 'no');
1259
1260	# see $applet_html set in defaults section for more info:
1261	#
1262	my $str = $applet_html;
1263
1264	$str =~ s/_UID_/$uid/g;
1265	$str =~ s/_VNC_PORT_/$vnc_port/g;
1266	$str =~ s/_WIDTH_/$W/g;
1267	$str =~ s/_HEIGHT_/$H/g;
1268	$str =~ s/_PASS_/$pass/g;
1269	$str =~ s/_APPLET_JAR_/$applet_jar/g;
1270	$str =~ s/_APPLET_CLASS_/$applet_class/g;
1271	$str =~ s/_TRUST_UVC_/$tUVC/g;
1272
1273	if ($enable_port_redirection && $redirect_host ne '') {
1274		$str =~ s/name=PASSWORD value=.*>/name=NOT_USED value=yes>/i;
1275		#$str =~ s/<!-- extra -->/<!-- extra -->\n<param name="ignoreProxy" value=yes>/;
1276	}
1277
1278	print $str;
1279}
1280
1281##########################################################################
1282# The following subroutines are for port redirection only, which is
1283# disabled by default ($enable_port_redirection == 0)
1284#
1285sub port_redir {
1286	# To aid in avoiding zombies:
1287	#
1288	setpgrp(0, 0);
1289
1290	# For the fixed port scheme we try to cooperate via lock file:
1291	#
1292	my $rmlock = '';
1293	#
1294	if ($fixed_port) {
1295		# try to grab the fixed port for the next 90 secs removing
1296		# stale locks older than 60 secs:
1297		#
1298		$rmlock = lock_fixed_port(90, 60);
1299
1300	} elsif ($find_free_port eq '0') {
1301		if ($have_inet6 && $listen_on_ipv6) {
1302			eval {$find_free_port = IO::Socket::INET6->new(
1303				Listen    => 1,
1304				LocalPort => $vnc_port,
1305				ReuseAddr => 1,
1306				Domain    => AF_INET6,
1307				LocalAddr => "::",
1308				Proto     => "tcp"
1309			);};
1310		} else {
1311			$find_free_port = IO::Socket::INET->new(
1312				Listen	  => 1,
1313				LocalPort => $vnc_port,
1314				ReuseAddr => 1,
1315				Proto     => "tcp"
1316			);
1317		}
1318	}
1319	# In all cases, at this point $find_free_port is the listening
1320	# socket.
1321
1322	# fork a helper process to do the port redir:
1323	#
1324	# Actually we need to spawn 4(!) of them in case the proxy check
1325	# /check.https.proxy.connection (it is by default) and the other
1326	# test connections.  Spawn one for each expected connection, for
1327	# whatever applet parameter usage mode you set up.
1328	#
1329	for (my $n = 1; $n <= 4; $n++) {
1330		my $pid = fork();
1331		if (! defined $pid) {
1332			bye("Internal Error #14");
1333		} elsif ($pid) {
1334			wait;
1335		} else {
1336			if (fork) {
1337				exit 0;
1338			}
1339			setpgrp(0, 0);
1340			handle_conn();
1341			exit 0;
1342		}
1343	}
1344
1345	# We now close the listening socket:
1346	#
1347	close $find_free_port;
1348	
1349	if ($rmlock) {
1350		# let our process proceed a bit before removing lock.
1351		sleep 1;
1352		unlink $rmlock;
1353	}
1354
1355	# Now send html to the browser so it can connect:
1356	#
1357	print_applet_html();
1358
1359	exit 0;
1360}
1361
1362# This checks the validity of a username@host:port for the port
1363# redirection mode.  Finishes and exits if it is invalid.
1364#
1365sub check_redirect_host {
1366	# First check that the host:port string is valid:
1367	#
1368	if ($redirect_host !~ /^\w[-\w\.]*:\d+$/) {
1369		bye("Invalid Redirect Host:Port.<p>$login_str");
1370	}
1371	# Second, check if the allowed host file permits it:
1372	#
1373	if ($port_redirection_allowed_hosts ne '') {
1374		if (! open(ALLOWED, "<$port_redirection_allowed_hosts")) {
1375			bye("Internal Error #15");
1376		}
1377		my $ok = 0;
1378		while (my $line = <ALLOWED>) {
1379			chomp $line;
1380			# skip blank lines and '#' comments:
1381			next if $line =~ /^\s*$/;
1382			next if $line =~ /^\s*#/;
1383
1384			# trim spaces from ends:
1385			$line =~ s/^\s*//;
1386			$line =~ s/\s*$//;
1387
1388			# collect host:ports in case port range given:
1389			my @items;
1390			if ($line =~ /^(.*):(\d+)-(\d+)$/) {
1391				# port range:
1392				my $host = $1;
1393				my $pmin = $2;
1394				my $pmax = $3;
1395				for (my $p = $pmin; $p <= $pmax; $p++) {
1396					push @items, "$host:$p";
1397				}
1398			} else {
1399				push @items, $line;
1400			}
1401
1402			# now check each item for a match:
1403			foreach my $item (@items) {
1404				if ($item eq 'ALL') {
1405					$ok = 1;
1406					last;
1407				} elsif ($item =~ /@/) {
1408					if ("$username\@$redirect_host" eq $item) {
1409						$ok = 1;
1410						last;
1411					}
1412				} elsif ($redirect_host eq $item) {
1413					$ok = 1;
1414					last;
1415				}
1416			}
1417			# got a match:
1418			last if $ok;
1419		}
1420		close ALLOWED;
1421
1422		if (! $ok) {
1423			bye("Disallowed Redirect Host:Port.<p>$login_str");
1424		}
1425	}
1426}
1427
1428# Much of this code is borrowed from 'connect_switch':
1429#
1430# (it only applies to the vnc redirector $enable_port_redirection mode
1431# which is off by default.)
1432#
1433sub handle_conn {
1434	close STDIN;
1435	close STDOUT;
1436	close STDERR;
1437
1438	$SIG{ALRM} = sub {close $find_free_port; exit 0};
1439
1440	# We only wait 30 secs for the redir case, esp. since
1441	# we need to spawn so many helpers...
1442	#
1443	alarm(30);
1444
1445	my ($client, $ip) = $find_free_port->accept();
1446
1447	alarm(0);
1448
1449	close $find_free_port;
1450
1451	if (!$client) {
1452		exit 1;
1453	}
1454
1455	my $host = '';
1456	my $port = '';
1457	if ($redirect_host =~ /^(.*):(\d+)$/) {
1458		($host, $port) = ($1, $2);
1459	}
1460
1461	my $sock = IO::Socket::INET->new(
1462		PeerAddr => $host,
1463		PeerPort => $port,
1464		Proto => "tcp"
1465	);
1466	if (! $sock && $have_inet6) {
1467		eval {$sock = IO::Socket::INET6->new(
1468			PeerAddr => $host,
1469			PeerPort => $port,
1470			Proto => "tcp"
1471		);};
1472	}
1473
1474	if (! $sock) {
1475		close $client;
1476		exit 1;
1477	}
1478
1479	$current_fh1 = $client;
1480	$current_fh2 = $sock;
1481
1482	$SIG{TERM} = sub {close $current_fh1; close $current_fh2; exit 0};
1483
1484	my $killpid = 1;
1485
1486	my $parent = $$;
1487	if (my $child = fork()) {
1488		xfer($sock, $client, 'S->C');
1489		if ($killpid) {
1490			fsleep(0.5);
1491			kill 'TERM', $child;
1492		}
1493	} else {
1494		xfer($client, $sock, 'C->S');
1495		if ($killpid) {
1496			fsleep(0.75);
1497			kill 'TERM', $parent;
1498		}
1499	}
1500	exit 0;
1501}
1502
1503# This does socket data transfer in one direction.
1504#
1505sub xfer {
1506	my($in, $out, $lab) = @_;
1507	my ($RIN, $WIN, $EIN, $ROUT);
1508	$RIN = $WIN = $EIN = "";
1509	$ROUT = "";
1510	vec($RIN, fileno($in), 1) = 1;
1511	vec($WIN, fileno($in), 1) = 1;
1512	$EIN = $RIN | $WIN;
1513	my $buf;
1514
1515	while (1) {
1516		my $nf = 0;
1517		while (! $nf) {
1518			$nf = select($ROUT=$RIN, undef, undef, undef);
1519		}
1520		my $len = sysread($in, $buf, 8192);
1521		if (! defined($len)) {
1522			next if $! =~ /^Interrupted/;
1523			last;
1524		} elsif ($len == 0) {
1525			last;
1526		}
1527
1528		my $offset = 0;
1529		my $quit = 0;
1530		while ($len) {
1531			my $written = syswrite($out, $buf, $len, $offset);
1532			if (! defined $written) {
1533				$quit = 1;
1534				last;
1535			}
1536			$len -= $written;
1537			$offset += $written;
1538		}
1539		last if $quit;
1540	}
1541	close($in);
1542	close($out);
1543}
1544
1545# Sleep a small amount of time (float)
1546#
1547sub fsleep {
1548	my ($time) = @_;
1549	select(undef, undef, undef, $time) if $time;
1550}
1551