#!/usr/bin/perl # ########################################################################## # desktop.cgi: # # This is an example CGI script to provide multi-user web access to # x11vnc desktops. The user desktop sessions run in 'Xvfb' displays # that are created automatically. # # This script should/must be served by an HTTPS (i.e. SSL) webserver, # otherwise the unix and vnc passwords would be sent over the network # unencrypted (see below to disable if you really want to.) # # The Java VNC Viewer applet connections are encrypted by SSL as well. # # You can use this script to provide unix users desktops available on # demand via any Java enabled web browser. One could also use this for # a special-purpose 'single application' service running in a minimal # window manager. # # One example of a special-purpose application would be a scientific # data visualization tool running on a server where the data is housed. # To do this set $x11vnc_extra_opts = '-env FD_PROG=/path/to/app/launcher' # where the program launches your special purpose application. A very # simple example: '-env FD_PROG=/usr/bin/xclock' # # # Depending on where you place this script, the user accesses the service # with the URL: # # https://your.webserver.net/cgi-bin/desktop.cgi # # Then they login with their unix username and password to get their # own desktop session. # # If the user has an existing desktop it is connected to directly, # otherwise a new session is created inside an Xvfb display and then # connected to by VNC. # # It is possible to do port redirection to other machines running SSL # enabled VNC servers (see below.) This script does not start the VNC # servers on the other machines, although with some extra rigging you # should be able to do that as well. # # You can customize the login procedure to whatever you want by modifying # this script, or by using ideas in this script write your own PHP, # (for example), script. # ########################################################################## # Overriding default settings: # # If you want to override any settings in this script and do not # want to edit this script create the assignments in a file named # 'desktop.cgi.conf' in the same directory as desktop.cgi. It will be # sourced after the defaults are set. The format of desktop.cgi.conf # is simply perl statements that make the assignments. # # For example, if you put something like this in desktop.cgi.conf: # # $x11vnc = '/usr/local/bin/x11vnc'; # # that will set the path to the x11vnc binary to that location. Look at # the settings below for the other variables that you can modify, for # example one could set $allowed_users_file. # ########################################################################## # x11vnc: # # You need to install x11vnc or otherwise have it available. It is # REQUIRED that you use x11vnc 0.9.10 or later. It won't work with # earlier versions. See below the $x11vnc parameter that you can set # to the full path to x11vnc. # ########################################################################## # Xvfb: # # Note that the x11vnc -create virtual desktop service used below requires # that you install the 'Xvfb' program. On debian this is currently done # via 'apt-get install xvfb'. # # If you are having trouble getting 'x11vnc -create' to work with this # script (it can be tricky), try it manually and/or see the x11vnc FAQ # links below. # ########################################################################## # Apache httpd: # # You should put this script in, say, a cgi-bin directory. Enable cgi # scripts in your apache (or other httpd) config. For example, we have # these lines (not commented): # # In httpd.conf: # # ScriptAlias /cgi-bin/ "/dist/apache/2.0/cgi-bin/" # # # AllowOverride None # Options None # Order allow,deny # Allow from all # # # and in ssl.conf: # # # SSLOptions +StdEnvVars # # # Do not be confused by the non-standard /dist/apache/2.0 apache # installation location that we happen to use. Yours will be different. # # You can test that you have CGI scripts working properly with the # 'test-cgi' and 'printenv' scripts apache provides. # # Copy this file (desktop.cgi) to /dist/apache/2.0/cgi-bin and then run # 'chmod 755 ...' on it to make it executable. # ########################################################################## # Applet Jar files served by apache: # # You will *also* need to copy the x11vnc classes/ssl/UltraViewerSSL.jar # file to the httpd DocumentRoot to be accessible by: /UltraViewerSSL.jar # in a URL (or change $applet_jar below or the html in $applet_html if # you want to use a different location.) # # This location is relative to the apache DocumentRoot 'htdocs' directory. # For our (non-standard location installation) that meant we copied the # file to: # # /dist/apache/2.0/htdocs/UltraViewerSSL.jar # # (your DocumentRoot directory will be different.) # # The VncViewer.jar (tightvnc) will also work, but you need to change # the $applet_jar below. You can get these jar files from the x11vnc # tarball from: # # http://www.karlrunge.com/x11vnc/#downloading # # This script requires x11vnc 0.9.10 or later. # # Note that the usage mode for this script is a different from regular # 'x11vnc -http ...' usage where x11vnc acts as a mini web server and # serves its own applet jars. We don't use that mode for this script. # Apache (httpd) serves the jars. # # ########################################################################## # Notes and Information: # # Each x11vnc server created for a user login will listen on its own port # (see below for port selection schemes.) Your firewall must let in *ALL* # of these ports (e.g. a port range, see below for the syntax.) # # It is also possible, although not as reliable, to do all of this through # a single port, see the fixed port scheme $find_free_port = 'fixed:5910' # below. This single port mode must be different from apache's port # (usually 443 for https) and must also be allowed in by your firewall. # # Note: The fixed port scheme is DISABLED by default. # # It is also possible to have this script act as a vnc redirector to SSL # enabled VNC servers running on *other* machines inside your firewall # (presumably the users' desktops) See the $enable_port_redirection # setting below. The user provides 'username@host:port' instead of just # 'username' when she logs in. This script doesn't start VNC servers # on those other machines, the servers must be running there already. # (If you want this script to start them you will need to add it # yourself.) It is possible to provide a host:port allow list to limit # which internal machines and ports can be redirected to. This is the # $port_redirection_allowed_hosts parameter. # # Note: The vnc redirector scheme is DISABLED by default. # # Note there are *two* SSL certificates involved that the user may be # asked to inspect: apache's SSL cert and x11vnc's SSL cert. This may # confuse naive users. You may want to use the same cert for both. # # This script provides one example on how to provide the service. You can # customize it to meet your needs, e.g. switch to php, newer cgi modules, # different authentication, SQL database for user authentication, etc, # etc. If you plan to use it in production, please examine all security # aspects of it carefully; read the comments in the script for more info. # # More information and background and troubleshooting: # # http://www.karlrunge.com/x11vnc/faq.html#faq-xvfb # http://www.karlrunge.com/x11vnc/faq.html#faq-ssl-tunnel-viewers # http://www.karlrunge.com/x11vnc/faq.html#faq-ssl-java-viewer-proxy # http://www.karlrunge.com/x11vnc/faq.html#faq-ssl-portal # http://www.karlrunge.com/x11vnc/faq.html#faq-unix-passwords # http://www.karlrunge.com/x11vnc/faq.html#faq-userlogin # # # Please also read the comments below for changing specific settings. # You can modify them in this script or by override file 'desktop.cgi.conf' #------------------------------------------------------------------------- # Copyright (c) 2010 by Karl J. Runge # # desktop.cgi is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or (at # your option) any later version. # # desktop.cgi is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with desktop.cgi; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA # or see . #------------------------------------------------------------------------- use strict; use IO::Socket::INET; # Test for INET6 support: # my $have_inet6 = 0; eval "use IO::Socket::INET6;"; $have_inet6 = 1 if $@ eq ""; ########################################################################## # Path to the x11vnc program: # my $x11vnc = '/usr/bin/x11vnc'; ########################################################################## # You can set some extra x11vnc cmdline options here: # my $x11vnc_extra_opts = ''; ########################################################################## # Override the default x11vnc viewer connection timeout of 75 seconds: # my $x11vnc_timeout = ''; ########################################################################## # TCP Ports: # # Set find_free_port to 1 (or the other modes described below) to # autoselect a free port to use. The default is to use a port based on # the userid number (7000 + uid). # my $find_free_port = 0; # Or specify a port range: # #$find_free_port = '7000-8000'; # # Or indicate to use a kludge to try to do everything through a SINGLE # port. To try to avoid contention on the port, simultaneous instances # of this script attempt to 'take turns' using it the single port. # #$find_free_port = 'fixed:5910'; # This is the starting port for 7000 + uid and also $find_free_port = 1 # autoselection: # my $starting_port = 7000; # Listen on AF_INET6 if IO::Socket::INET6 is available. # my $listen_on_ipv6 = 0; ########################################################################## # Port redirection mode: # # This is to enable port redirection mode: username@host:port. If # username is valid, there will be a port redirection to internal machine # host:port. Presumably there is already an SSL enabled and password # protected VNC server running there. We don't start that VNC server. # (You might be able to figure out a way to do this yourself.) # # See the next setting for an allowed hosts file. The default for port # redirection is off. # my $enable_port_redirection = 0; # A file with allowed port redirections. The empty string '' (the # default) means all host:port redirections would be allowed. # # Format of the file: A list of 'user@host:port' or 'host:port' # entries, one per line. Port ranges, e.g. host:n-m are also accepted. # # Leading and trailing whitespace is trimmed off each line. Blank lines # and comment lines starting with '#' are skipped. A line consisting of # 'ALL' matches everything. If no match can be found or the file cannot # be opened the connection is dropped. # my $port_redirection_allowed_hosts = ''; ########################################################################## # Allowed users: # # To limit which users can use this service, set the following to a file # that contains the allowed user names one per line. Lines starting with # the '#' character are skipped. # my $allowed_users_file = ''; ########################################################################## # Denied users: # # As with $allowed_users_file, but to deny certain users. Applied after # any $allowed_users_file check and overrides the result. # my $denied_users_file = ''; ########################################################################## # trustUrlVncCert applet parameter: # # Set to 0 to have the java applet html set the parameter # trustUrlVncCert=no, i.e. the applet will not automatically accept # an SSL cert already accepted by an HTTPS URL. See $applet_html and # print_applet_html() below for more info. # my $trustUrlVncCert = 1; ########################################################################## # One-time VNC password fifo: # # For extra security against local untrusted users a fifo is used # to copy the one-time VNC password to the user's VNC password file # ~user/x11vnc.pw. If that fifo transfer technique causes problems, # you can set this value to 1 to disable the security feature: # my $disable_vnc_passwd_fifo_safety = 0; ########################################################################## # Comment this out if you don't want PATH modified: # $ENV{PATH} = "/usr/bin:/bin:$ENV{PATH}"; ########################################################################## # For the next two settings, note that most users will be confused that # geometry and session are ignored when they are returning to their # existing desktop session (x11vnc FINDDISPLAY action.) ########################################################################## # Used below if user did not specify preferred geometry and color depth: # my $default_geometry = '1024x768x24'; # Set this to the list of x11vnc -create sessions types to show a session # dropdown for the user to select from. # my $session_types = ''; # # example: #$session_types = 'gnome kde xfce lxde wmaker enlightenment mwm twm failsafe'; ########################################################################## # Set this to 1 to enable user setting a unique tag for each one # of his desktops and so can have multiple ones simultaneously and # select which one he wants. For now we just hack this onto geometry # 1024x768x24:my_2nd_desktop but ultimately there should be a form entry # for it. Search for enable_unique_tags for more info: # my $enable_unique_tags = 0; my $unique_tag = ''; ########################################################################## # String of HTML for the login form: # # Feel free to customize to your taste, _USERNAME_ and _GEOMETRY_ are # expanded to that of the request. # my $login_str = <<"END"; x11vnc web access

x11vnc web access

Login

Username:
Password:
Geometry:
END ########################################################################## # String of HTML returned to web browser to launch applet: # # Feel free to customize to your taste, _UID_, _VNC_PORT_, _WIDTH_, # _HEIGHT_, _PASS_, _TRUST_UVC_, _APPLET_JAR_, and _APPLET_CLASS_ are # expanded to the appropriate values before sending out to the browser. # my $applet_html = <<"END"; x11vnc desktop (_UID_/_VNC_PORT_)
Login page
x11vnc website END ########################################################################## # These java applet strings are expanded into the above $applet_html. # Note that $applet_jar is relative to your apache DocumentRoot (htdocs) # not the filesystem root. # my $applet_jar = '/UltraViewerSSL.jar'; my $applet_class = 'VncViewer.class'; # These make the applet panel smaller because we use 'Open New Window' # anyway (set to 'W' or 'H' to use actual session geometry values): # my $applet_width = '400'; my $applet_height = '300'; # To customize ALL of the HTML printed out you may need to redefine # the bye() subtroutine in your desktop.cgi.conf file. ########################################################################## # Override any of the above settings by setting them in a file named # 'desktop.cgi.conf'. It is sourced here. # # You can override any variable set above by supplying perl code # in $0.conf that sets it to the desired value. # # Some examples you could put in $0.conf: # # $x11vnc = '/usr/local/bin/x11vnc'; # $x11vnc_extra_opts = '-env FD_PROG=/usr/bin/xclock'; # $x11vnc_extra_opts = '-ssl /usr/local/etc/dtcgi.pem'; # $find_free_port = 'fixed:5999'; # $enable_port_redirection = 1; # $allowed_users_file = '/usr/local/etc/dtcgi.allowed'; # if (-f "$0.conf") { eval `cat "$0.conf"`; } ########################################################################## # END OF MAIN USER SETTINGS. # Only power users should change anything below. ########################################################################## # Print http header reply: # print STDOUT "Content-Type: text/html\r\n\r\n"; # Require HTTPS so that unix and vnc passwords are not sent in clear text # (perhaps it is too late...) Disable HTTPS here at your own risk. # if ($ENV{HTTPS} !~ /^on$/i) { bye("HTTPS must be used (to encrypt passwords)"); } # Read URL request: # my $request; if ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN, $request, $ENV{'CONTENT_LENGTH'}); } elsif ($ENV{'REQUEST_METHOD'} eq "GET" ) { $request = $ENV{'QUERY_STRING'}; } else { $request = $ARGV[0]; } my %request = url_decode(split(/[&=]/, $request)); # Experiment for FD_TAG x11vnc feature for multiple desktops for a # single user: # # we hide it in geometry:tag for now: # if ($enable_unique_tags && $request{geometry} =~ /^(.*):(\w+)$/) { $request{geometry} = $1; $unique_tag = $2; } # Check/set geometry and session: # if (!exists $request{geometry} || $request{geometry} !~ /^[x\d]+$/) { # default geometry and depth: $request{geometry} = $default_geometry; } if (!exists $request{session} || $request{session} =~ /^\s*$/) { $request{session} = ''; } # Expand _USERNAME_ and _GEOMETRY_ in the login string HTML: # $login_str =~ s/_USERNAME_/$request{username}/g; $login_str =~ s/_GEOMETRY_/$request{geometry}/g; # Check x11vnc version for installers of this script who do not know # how to read and follow instructions: # my $version = (split(' ', `$x11vnc -version`))[1]; $version =~ s/\D*$//; my ($major, $minor, $micro) = split(/\./, $version); if ($major !~ /^\d+$/ || $minor !~ /^\d+$/) { bye("The x11vnc program is not installed correctly."); } $micro = 0 unless $micro; my $level = $major * 100 * 100 + $minor * 100 + $micro; my $needed = 0 * 100 * 100 + 9 * 100 + 10; if ($level < $needed) { bye("x11vnc version 0.9.10 or later is required. (Found version $version)"); } # Set up user selected desktop session list, if enabled: # my %sessions; if ($session_types ne '') { my $str = "Session:\n\n"; # This forces $request{session} to be a valid one: # if (! exists $sessions{$request{session}}) { $request{session} = 'none'; } # Insert into login_str: # my $r = $request{session}; $str =~ s/option value=\Q$r\E/option selected value=$r/; $login_str =~ s//$str/; } # If no username or password, show login form: # if (!$request{username} && !$request{password}) { bye($login_str); } elsif (!$request{username}) { bye("No Username.

$login_str"); } elsif (!$request{password}) { bye("No Password.

$login_str"); } # Some shorthand names: # my $username = $request{username}; my $password = $request{password}; my $geometry = $request{geometry}; my $session = $request{session}; # If port redirection is enabled, split username@host:port # my $redirect_host = ''; my $current_fh1 = ''; my $current_fh2 = ''; if ($enable_port_redirection) { ($username, $redirect_host) = split(/@/, $username, 2); if ($redirect_host ne '') { # will exit if the redirection is not allowed: check_redirect_host(); } } # If there is an $allowed_users_file, check username against it: # if ($allowed_users_file ne '') { if (! open(USERS, "<$allowed_users_file")) { bye("Internal Error #0"); } my $ok = 0; while () { chomp; $_ =~ s/^\s*//; $_ =~ s/\s*$//; next if /^#/; if ($username eq $_) { $ok = 1; } } close USERS; if (! $ok) { bye("Denied Username.

$login_str"); } } # If there is a $denied_users_file, check username against it: # if ($denied_users_file ne '') { if (! open(USERS, "<$denied_users_file")) { bye("Internal Error #0"); } my $ok = 1; while () { chomp; $_ =~ s/^\s*//; $_ =~ s/\s*$//; next if /^#/; if ($username eq $_) { $ok = 0; } } close USERS; if (! $ok) { bye("Denied Username.

$login_str"); } } # Require username to be alphanumeric + '-' + '_': # (one may want to add '.' as well) # if ($username !~ /^\w[-\w]*$/) { bye("Invalid Username.

$login_str"); } # Get the userid number, we may use it as his VNC display port; this # also checks if the username exists: # my $uid = `/usr/bin/id -u '$username'`; chomp $uid; if ($? != 0 || $uid !~ /^\d+$/) { bye("Invalid Username.

$login_str"); } # Use x11vnc trick to check if the unix password is valid: # (requires x11vnc 0.9.10 or later.) # if (!open(X11VNC, "| $x11vnc -unixpw \%stdin > /dev/null")) { bye("Internal Error #1"); } print X11VNC "$username:$password\n"; if (!close X11VNC) { # x11vnc returns non-zero for invalid username+password: bye("Invalid Password.

$login_str"); } # Initialize random number generator for use below: # initialize_random(); # Set vnc port: # my $vnc_port = 0; my $fixed_port = 0; if (! $find_free_port) { # Fixed port based on userid (we assume it is free): # $vnc_port = $starting_port + $uid; } elsif ($find_free_port =~ /^fixed:(\d+)$/) { # # Enable the -loopbg method that tries to share a single port: # $vnc_port = $1; $fixed_port = 1; } else { # Autoselect a port, either default range (7000-8000) or a user # supplied range. (note that $find_free_port will now contain # a socket listening on the found port so that it is held.) # $vnc_port = auto_select_port(); } # Check for crazy port value: # if ($vnc_port > 64000 || $vnc_port < 1) { bye("Internal Error #2 $vnc_port"); } # If port redirection is enabled and the user selected it via # username@host:port, we do that right now and then exit. # if ($enable_port_redirection && $redirect_host ne '') { port_redir(); exit 0; } # Make a random, onetime vnc password: # my $pass = ''; my $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; my @abc = split(//, $chars); for (my $i = 0; $i < 8; $i++) { $pass .= $abc[ rand(scalar(@abc)) ]; } # Use x11vnc trick to switch to user and store vnc pass in the passwdfile. # Result is $pass is placed in user's $HOME/x11vnc.pw # # (This is actually difficult to do without untrusted LOCAL users being # able to see the pass as well, see copy_password_to_user() for details # on how we try to avoid this.) # copy_password_to_user($pass); # Make a tmp file for x11vnc launcher script: # my $tmpfile = `/bin/mktemp /tmp/desktop.cgi.XXXXXX`; chomp $tmpfile; # Check if the tmpfile is valid: # if (! -e $tmpfile || ! -o $tmpfile || -l $tmpfile) { unlink $tmpfile; bye("Internal Error #3"); } if (!chmod 0644, $tmpfile) { unlink $tmpfile; bye("Internal Error #4"); } if (!open(TMP, ">$tmpfile")) { unlink $tmpfile; bye("Internal Error #5"); } # The x11vnc command. You adjust it to suit your needs. # # some ideas: -env FD_PROG=/usr/bin/gnome-session # -env FD_SESS=kde # -env FD_TAG=my_2nd_desktop # -ultrafilexfer # # Note that -timeout will cause it to exit if client does not connect # and -sslonly disables VeNCrypt SSL connections. # Some settings: # (change these if you encounter timing problems, etc.) # my $timeout = 75; my $extra = ''; if ($fixed_port) { # settings for fixed port case: $timeout = 45; $extra .= " -loopbg100,1"; } $timeout = $x11vnc_timeout if $x11vnc_timeout ne ''; if ($session_types ne '') { # settings for session selection case: if (exists $sessions{$session}) { $extra .= " -env FD_SESS='$session'"; } } if ($enable_unique_tags && $unique_tag ne '' && $unique_tag =~ /^\w+$/) { $extra .= " -env FD_TAG='$unique_tag'"; } # This md5sum check of the vnc passwd is for extra safety (see # copy_password_to_user for details.) # my $md5sum = ''; system("type md5sum > /dev/null"); if ($? == 0) { my $md5 = `/bin/mktemp /tmp/desktop.cgi.XXXXXX`; chomp $md5; # compute md5sum of password: if (-o $md5 && open(MD5, "| md5sum > $md5")) { print MD5 "$pass\n"; close MD5; if (open(MD5, "<$md5")) { # read it: my $line = ; close MD5; my ($s, $t) = split(' ', $line); if (length($s) >= 32 && $s =~ /^\w+$/) { # shell code for user to check he has correct passwd: $md5sum = "if md5sum \$HOME/x11vnc.pw | grep '$s' > /dev/null; then true; else exit 1; fi"; } } } unlink $md5; } # Write x11vnc command to the tmp file: # print TMP <<"END"; #!/bin/sh export PATH=/usr/bin:/bin:\$PATH $md5sum $x11vnc -sigpipe ignore:HUP -nopw -rfbport $vnc_port \\ -passwdfile \$HOME/x11vnc.pw -oa \$HOME/x11vnc.log \\ -create -ssl SAVE -sslonly -env FD_GEOM=$geometry \\ -timeout $timeout $extra $x11vnc_extra_opts \\ >/dev/null 2>/dev/null /dev/null")) { unlink $tmpfile; unlink $rmlock if $rmlock; bye("Internal Error #6"); } select(X11VNC); $| = 1; select(STDOUT); # Close any port we held. There is still a gap of time between now # and when when x11vnc in $tmpfile reopens the port after the password # authentication. So another instance of this script could accidentally # think it is free... # sleep 1; close $find_free_port if $find_free_port; print X11VNC "$username:$password\n"; close X11VNC; # note we ignore return value. unlink $tmpfile; if ($rmlock) { # let our x11vnc proceed a bit before removing lock. sleep 2; unlink $rmlock; } # Return html for the java applet to connect to x11vnc. # print_applet_html(); exit 0; ################################################################# # Subroutines: # print the message to client and exit with success. # sub bye { my $msg = shift; print STDOUT "$msg\n"; exit 0; } # decode %xx to character: # sub url_decode { foreach (@_) { tr/+/ /; s/%(..)/pack("c",hex($1))/ge; } @_; } # seed random # sub initialize_random { my $rbytes = ''; if (open(RAN, " $pmax) { ($pmin, $pmax) = ($pmax, $pmin); } } elsif ($find_free_port > 1024) { # user supplied a starting port: $pmin = $find_free_port; $pmax = $pmin + 1000; } # Try to add a bit of randomness to the starting port so # simultaneous instances of this script won't be fooled by the gap # of time before x11vnc reopens the port (see near the bottom.) # my $dp = int(rand(1.0) * 0.25 * ($pmax - $pmin)); if ($pmin + $dp < $pmax - 20) { $pmin = $pmin + $dp; } my $port = 0; # Now try to find a free one: # for (my $p = $pmin; $p <= $pmax; $p++) { my $sock = ''; if ($have_inet6 && $listen_on_ipv6) { eval {$sock = IO::Socket::INET6->new( Listen => 1, LocalPort => $p, ReuseAddr => 1, Domain => AF_INET6, LocalAddr => "::", Proto => "tcp" );}; } else { $sock = IO::Socket::INET->new( Listen => 1, LocalPort => $p, ReuseAddr => 1, Proto => "tcp" ); } if ($sock) { # we will keep this open until we call x11vnc: $find_free_port = $sock; $port = $p; last; } } return $port; } # Since apache typically runs as user 'apache', 'nobody', etc, and not # as root it is tricky for us to copy the pass string to a file owned by # the user without some other untrusted local user being able to learn # the password (e.g. via reading a file or watching ps.) Note that with # the x11vnc -unixpw trick we unfortunately can't use a pipe because # the user command is run in its own tty. # # The best way would be a sudo action or a special setuid program for # copying. So consider doing that and thereby simplify this function. # # Short of a special program doing this, we use a fifo so ONLY ONE # process can read the password. If the untrusted local user reads it, # then the logging-in user's x11vnc won't get it. The login and x11vnc # will fail, but the untrusted user won't gain access to the logging-in # user's desktop. # # So here we start long, tedious work carefully managing the fifo. # sub copy_password_to_user { my $pass = shift; my $use_fifo = ''; # Find a command to make a fifo: # system("type mkfifo > /dev/null"); if ($? == 0) { $use_fifo = 'mkfifo %s'; } else { system("type mknod > /dev/null"); if ($? == 0) { $use_fifo = 'mknod %s p'; } } # Create the filename for our fifo: # my $fifo = `/bin/mktemp /tmp/desktop.cgi.XXXXXX`; chomp $fifo; if (! -e $fifo || ! -o $fifo || -l $fifo) { unlink $fifo; bye("Internal Error #7"); } # disable fifo safety if requested: # if ($disable_vnc_passwd_fifo_safety) { $use_fifo = ''; } # Make the fifo: # if ($use_fifo) { $use_fifo = sprintf($use_fifo, $fifo); # there is a small race here: system("umask 077; rm -f $fifo; $use_fifo; chmod 600 $fifo"); if (!chmod 0600, $fifo) { # we chmod once more.. unlink $fifo; bye("Internal Error #8"); } if (! -o $fifo || ! -p $fifo || -l $fifo) { # but we get out if not owned by us anymore: unlink $fifo; bye("Internal Error #9"); } } # Build cmd for user to read our fifo: # my $upw = '$HOME/x11vnc.pw'; $ENV{UNIXPW_CMD} = "touch $upw; chmod 600 $upw; cat $fifo > $upw"; # Start it: # if (!open(X11VNC, "| $x11vnc -unixpw \%stdin > /dev/null")) { unlink $fifo; bye("Internal Error #10"); } select(X11VNC); $| = 1; select(STDOUT); if (! $use_fifo) { # regular file, we need to write it now. if (!open(FIFO, ">$fifo")) { close X11VNC; unlink $fifo; bye("Internal Error #11"); } print FIFO "$pass\n"; close FIFO; } # open fifo up for reading. # (this means the bad guy can read it too.) # if (!chmod 0644, $fifo) { unlink $fifo; bye("Internal Error #12"); } # send the user's passwd now: # print X11VNC "$username:$password\n"; if ($use_fifo) { # wait a bit for the cat $fifo to start, reader will block. sleep 1; if (!open(FIFO, ">$fifo")) { close X11VNC; unlink $fifo; bye("Internal Error #13"); } # here it goes: print FIFO "$pass\n"; close FIFO; } close X11VNC; # note we ignore return value. fsleep(0.5); unlink $fifo; # Done! } # For fixed, single port mode. Try to open and lock the port before # proceeding. # sub lock_fixed_port { my ($t_max, $t_age) = @_; # lock file name: # my $lock = '/tmp/desktop.cgi.lock'; my $remove = ''; my $t = 0; my $sock = ''; while ($t < $t_max) { if (-e $lock) { # clean out stale locks if possible: if (! -l $lock) { unlink $lock; } else { my ($pid, $time) = split(/:/, readlink($lock)); if (! -d "/proc/$pid") { unlink $lock; } if (time() > $time + $t_age) { unlink $lock; } } } my $reason = ''; if (-l $lock) { # someone has locked it. $reason = 'locked'; } else { # unlocked, try to listen on port: if ($have_inet6 && $listen_on_ipv6) { eval {$sock = IO::Socket::INET6->new( Listen => 1, LocalPort => $vnc_port, ReuseAddr => 1, Domain => AF_INET6, LocalAddr => "::", Proto => "tcp" );}; } else { $sock = IO::Socket::INET->new( Listen => 1, LocalPort => $vnc_port, ReuseAddr => 1, Proto => "tcp" ); } if ($sock) { # we got it, now try to lock: my $str = "$$:" . time(); if (symlink($str, $lock)) { $remove = $lock; $find_free_port = $sock; last; } # wow, we didn't lock it... $reason = "symlink failed: $!"; close $sock; } else { $reason = "listen failed: $!"; } } # sleep a bit and then try again: # print STDERR "$$ failed to get fixed port $vnc_port for $username at $t ($reason)\n"; $sock = ''; $t += 5; sleep 5; } if (! $sock) { bye("Failed to lock fixed TCP port. Try again a bit later.

$login_str"); } print STDERR "$$ got fixed port $vnc_port for $username at $t\n"; # Return the file to remove, if any: # return $remove; } # Return html for the java applet to connect to x11vnc. # # N.B. Please examine the applet params, e.g. trustUrlVncCert=yes to # see if you agree with them. See x11vnc classes/ssl/README for all # parameters. # # Note how we do not take extreme care to authenticate the server to # the client applet (but note that trustUrlVncCert=yes is better than # trustAllVncCerts=yes) One can tighten all of this up at the expense # of extra certificate dialogs (assuming the user bothers to check...) # # This assumes /UltraViewerSSL.jar is at document root; you need to put # it there. # sub print_applet_html { my ($W, $H, $D) = split(/x/, $geometry); # make it smaller since we 'Open New Window' below anyway. if ($applet_width ne 'W') { $W = $applet_width; } if ($applet_height ne 'H') { $H = $applet_height; } my $tUVC = ($trustUrlVncCert ? 'yes' : 'no'); # see $applet_html set in defaults section for more info: # my $str = $applet_html; $str =~ s/_UID_/$uid/g; $str =~ s/_VNC_PORT_/$vnc_port/g; $str =~ s/_WIDTH_/$W/g; $str =~ s/_HEIGHT_/$H/g; $str =~ s/_PASS_/$pass/g; $str =~ s/_APPLET_JAR_/$applet_jar/g; $str =~ s/_APPLET_CLASS_/$applet_class/g; $str =~ s/_TRUST_UVC_/$tUVC/g; if ($enable_port_redirection && $redirect_host ne '') { $str =~ s/name=PASSWORD value=.*>/name=NOT_USED value=yes>/i; #$str =~ s//\n/; } print $str; } ########################################################################## # The following subroutines are for port redirection only, which is # disabled by default ($enable_port_redirection == 0) # sub port_redir { # To aid in avoiding zombies: # setpgrp(0, 0); # For the fixed port scheme we try to cooperate via lock file: # my $rmlock = ''; # if ($fixed_port) { # try to grab the fixed port for the next 90 secs removing # stale locks older than 60 secs: # $rmlock = lock_fixed_port(90, 60); } elsif ($find_free_port eq '0') { if ($have_inet6 && $listen_on_ipv6) { eval {$find_free_port = IO::Socket::INET6->new( Listen => 1, LocalPort => $vnc_port, ReuseAddr => 1, Domain => AF_INET6, LocalAddr => "::", Proto => "tcp" );}; } else { $find_free_port = IO::Socket::INET->new( Listen => 1, LocalPort => $vnc_port, ReuseAddr => 1, Proto => "tcp" ); } } # In all cases, at this point $find_free_port is the listening # socket. # fork a helper process to do the port redir: # # Actually we need to spawn 4(!) of them in case the proxy check # /check.https.proxy.connection (it is by default) and the other # test connections. Spawn one for each expected connection, for # whatever applet parameter usage mode you set up. # for (my $n = 1; $n <= 4; $n++) { my $pid = fork(); if (! defined $pid) { bye("Internal Error #14"); } elsif ($pid) { wait; } else { if (fork) { exit 0; } setpgrp(0, 0); handle_conn(); exit 0; } } # We now close the listening socket: # close $find_free_port; if ($rmlock) { # let our process proceed a bit before removing lock. sleep 1; unlink $rmlock; } # Now send html to the browser so it can connect: # print_applet_html(); exit 0; } # This checks the validity of a username@host:port for the port # redirection mode. Finishes and exits if it is invalid. # sub check_redirect_host { # First check that the host:port string is valid: # if ($redirect_host !~ /^\w[-\w\.]*:\d+$/) { bye("Invalid Redirect Host:Port.

$login_str"); } # Second, check if the allowed host file permits it: # if ($port_redirection_allowed_hosts ne '') { if (! open(ALLOWED, "<$port_redirection_allowed_hosts")) { bye("Internal Error #15"); } my $ok = 0; while (my $line = ) { chomp $line; # skip blank lines and '#' comments: next if $line =~ /^\s*$/; next if $line =~ /^\s*#/; # trim spaces from ends: $line =~ s/^\s*//; $line =~ s/\s*$//; # collect host:ports in case port range given: my @items; if ($line =~ /^(.*):(\d+)-(\d+)$/) { # port range: my $host = $1; my $pmin = $2; my $pmax = $3; for (my $p = $pmin; $p <= $pmax; $p++) { push @items, "$host:$p"; } } else { push @items, $line; } # now check each item for a match: foreach my $item (@items) { if ($item eq 'ALL') { $ok = 1; last; } elsif ($item =~ /@/) { if ("$username\@$redirect_host" eq $item) { $ok = 1; last; } } elsif ($redirect_host eq $item) { $ok = 1; last; } } # got a match: last if $ok; } close ALLOWED; if (! $ok) { bye("Disallowed Redirect Host:Port.

$login_str"); } } } # Much of this code is borrowed from 'connect_switch': # # (it only applies to the vnc redirector $enable_port_redirection mode # which is off by default.) # sub handle_conn { close STDIN; close STDOUT; close STDERR; $SIG{ALRM} = sub {close $find_free_port; exit 0}; # We only wait 30 secs for the redir case, esp. since # we need to spawn so many helpers... # alarm(30); my ($client, $ip) = $find_free_port->accept(); alarm(0); close $find_free_port; if (!$client) { exit 1; } my $host = ''; my $port = ''; if ($redirect_host =~ /^(.*):(\d+)$/) { ($host, $port) = ($1, $2); } my $sock = IO::Socket::INET->new( PeerAddr => $host, PeerPort => $port, Proto => "tcp" ); if (! $sock && $have_inet6) { eval {$sock = IO::Socket::INET6->new( PeerAddr => $host, PeerPort => $port, Proto => "tcp" );}; } if (! $sock) { close $client; exit 1; } $current_fh1 = $client; $current_fh2 = $sock; $SIG{TERM} = sub {close $current_fh1; close $current_fh2; exit 0}; my $killpid = 1; my $parent = $$; if (my $child = fork()) { xfer($sock, $client, 'S->C'); if ($killpid) { fsleep(0.5); kill 'TERM', $child; } } else { xfer($client, $sock, 'C->S'); if ($killpid) { fsleep(0.75); kill 'TERM', $parent; } } exit 0; } # This does socket data transfer in one direction. # sub xfer { my($in, $out, $lab) = @_; my ($RIN, $WIN, $EIN, $ROUT); $RIN = $WIN = $EIN = ""; $ROUT = ""; vec($RIN, fileno($in), 1) = 1; vec($WIN, fileno($in), 1) = 1; $EIN = $RIN | $WIN; my $buf; while (1) { my $nf = 0; while (! $nf) { $nf = select($ROUT=$RIN, undef, undef, undef); } my $len = sysread($in, $buf, 8192); if (! defined($len)) { next if $! =~ /^Interrupted/; last; } elsif ($len == 0) { last; } my $offset = 0; my $quit = 0; while ($len) { my $written = syswrite($out, $buf, $len, $offset); if (! defined $written) { $quit = 1; last; } $len -= $written; $offset += $written; } last if $quit; } close($in); close($out); } # Sleep a small amount of time (float) # sub fsleep { my ($time) = @_; select(undef, undef, undef, $time) if $time; }