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