Home | History | Annotate | Download | only in misc
      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 (at] 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 
    218 use strict;
    219 use IO::Socket::INET;
    220 
    221 # Test for INET6 support:
    222 #
    223 my $have_inet6 = 0;
    224 eval "use IO::Socket::INET6;";
    225 $have_inet6 = 1 if $@ eq "";
    226 
    227 ##########################################################################
    228 # Path to the x11vnc program:
    229 #
    230 my $x11vnc = '/usr/bin/x11vnc';
    231 
    232 
    233 ##########################################################################
    234 # You can set some extra x11vnc cmdline options here:
    235 #
    236 my $x11vnc_extra_opts = '';
    237 
    238 
    239 ##########################################################################
    240 # Override the default x11vnc viewer connection timeout of 75 seconds:
    241 #
    242 my $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 #
    252 my $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 #
    267 my $starting_port = 7000;
    268 
    269 # Listen on AF_INET6 if IO::Socket::INET6 is available.
    270 #
    271 my $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 #
    286 my $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 #
    299 my $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 #
    309 my $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 #
    318 my $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 #
    329 my $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 #
    340 my $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 #
    358 my $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 #
    364 my $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 #
    377 my $enable_unique_tags = 0;
    378 my $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 #
    387 my $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>
    408 END
    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 #
    418 my $applet_html = <<"END";
    419 <html>
    420 <TITLE>
    421 x11vnc 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>
    438 END
    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 #
    446 my $applet_jar = '/UltraViewerSSL.jar';
    447 my $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 #
    452 my $applet_width  = '400';
    453 my $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 #
    475 if (-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 #
    487 print 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 #
    493 if ($ENV{HTTPS} !~ /^on$/i) {
    494 	bye("HTTPS must be used (to encrypt passwords)");
    495 }
    496 
    497 
    498 # Read URL request:
    499 #
    500 my $request;
    501 if ($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 
    509 my %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 #
    517 if ($enable_unique_tags && $request{geometry} =~ /^(.*):(\w+)$/) {
    518 	$request{geometry} = $1;
    519 	$unique_tag = $2;
    520 }
    521 
    522 # Check/set geometry and session:
    523 #
    524 if (!exists $request{geometry} || $request{geometry} !~ /^[x\d]+$/) {
    525 	# default geometry and depth:
    526 	$request{geometry} = $default_geometry;
    527 }
    528 if (!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 #
    542 my $version = (split(' ', `$x11vnc -version`))[1];
    543 $version =~ s/\D*$//;
    544 
    545 my ($major, $minor, $micro) = split(/\./, $version);
    546 if ($major !~ /^\d+$/ || $minor !~ /^\d+$/) {
    547 	bye("The x11vnc program is not installed correctly.");
    548 }
    549 $micro = 0 unless $micro;
    550 my $level  = $major * 100 * 100 + $minor * 100 + $micro;
    551 my $needed =      0 * 100 * 100 +      9 * 100 +     10;
    552 if ($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 #
    559 my %sessions;
    560 
    561 if ($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 #
    589 if (!$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 #
    600 my $username = $request{username};
    601 my $password = $request{password};
    602 my $geometry = $request{geometry};
    603 my $session  = $request{session};
    604 
    605 
    606 # If port redirection is enabled, split username@host:port
    607 #
    608 my $redirect_host = '';
    609 my $current_fh1 = '';
    610 my $current_fh2 = '';
    611 
    612 if ($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 #
    622 if ($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 #
    644 if ($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 #
    667 if ($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 #
    675 my $uid = `/usr/bin/id -u '$username'`;
    676 chomp $uid;
    677 if ($? != 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 #
    685 if (!open(X11VNC, "| $x11vnc -unixpw \%stdin > /dev/null")) {
    686 	bye("Internal Error #1");
    687 }
    688 print X11VNC "$username:$password\n";
    689 
    690 if (!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 #
    698 initialize_random();
    699 
    700 
    701 # Set vnc port:
    702 #
    703 my $vnc_port = 0;
    704 my $fixed_port = 0;
    705 
    706 if (! $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 #
    727 if ($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 #
    735 if ($enable_port_redirection && $redirect_host ne '') {
    736 	port_redir();
    737 	exit 0;
    738 }
    739 
    740 
    741 # Make a random, onetime vnc password:
    742 #
    743 my $pass = '';
    744 my $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    745 my @abc = split(//, $chars);
    746 
    747 for (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 #
    758 copy_password_to_user($pass);
    759 
    760 
    761 # Make a tmp file for x11vnc launcher script:
    762 #
    763 my $tmpfile = `/bin/mktemp /tmp/desktop.cgi.XXXXXX`;
    764 chomp $tmpfile;
    765 
    766 # Check if the tmpfile is valid:
    767 #
    768 if (! -e $tmpfile || ! -o $tmpfile || -l $tmpfile) {
    769 	unlink $tmpfile;
    770 	bye("Internal Error #3");
    771 }
    772 if (!chmod 0644, $tmpfile) {
    773 	unlink $tmpfile;
    774 	bye("Internal Error #4");
    775 }
    776 if (!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 #
    795 my $timeout = 75;
    796 my $extra = '';
    797 if ($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 
    804 if ($session_types ne '') {
    805 	# settings for session selection case:
    806 	if (exists $sessions{$session}) {
    807 		$extra .= " -env FD_SESS='$session'";
    808 	}
    809 }
    810 if ($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 #
    817 my $md5sum = '';
    818 system("type md5sum > /dev/null");
    819 if ($? == 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 #
    842 print TMP <<"END";
    843 #!/bin/sh
    844 export 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 &
    851 sleep 2
    852 exit 0
    853 END
    854 
    855 close 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 #
    865 my $rmlock = '';
    866 #
    867 if ($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 #
    876 if (!open(X11VNC, "| $x11vnc -unixpw \%stdin > /dev/null")) {
    877 	unlink $tmpfile;
    878 	unlink $rmlock if $rmlock;
    879 	bye("Internal Error #6");
    880 }
    881 
    882 select(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 # 
    889 sleep 1;
    890 close $find_free_port if $find_free_port;
    891 
    892 print X11VNC "$username:$password\n";
    893 close X11VNC;	# note we ignore return value.
    894 unlink $tmpfile;
    895 
    896 if ($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 #
    904 print_applet_html();
    905 
    906 exit 0;
    907 
    908 #################################################################
    909 # Subroutines:
    910 
    911 # print the message to client and exit with success.
    912 #
    913 sub bye {
    914 	my $msg = shift;
    915 	print STDOUT "<html>$msg</html>\n";
    916 	exit 0;
    917 }
    918 
    919 # decode %xx to character:
    920 #
    921 sub url_decode {
    922 	foreach (@_) {
    923 		tr/+/ /;
    924 		s/%(..)/pack("c",hex($1))/ge;
    925 	}
    926 	@_;
    927 }
    928 
    929 # seed random
    930 #
    931 sub 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 #
    959 sub 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 #
   1036 sub 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 #
   1149 sub 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 #
   1247 sub 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 #
   1285 sub 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 #
   1365 sub 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 #
   1433 sub 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 #
   1505 sub 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 #
   1547 sub fsleep {
   1548 	my ($time) = @_;
   1549 	select(undef, undef, undef, $time) if $time;
   1550 }
   1551