blob: 16fe54a29c48e21fe013c554313962a5b34c84b9 [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001#!/usr/bin/env perl
2#
3# Copyright (C) 2002-2005 RealVNC Ltd.
4# Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
5#
6# This is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This software is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this software; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
19# USA.
20#
21
22#
23# vncserver - wrapper script to start an X VNC server.
24#
25
26#
27# First make sure we're operating in a sane environment.
28#
29
30&SanityCheck();
31
32#
33# Global variables. You may want to configure some of these for your site.
34#
35
36$geometry = "1024x768";
37$depth = 16;
38$vncJavaFiles = (((-d "/usr/share/vnc/classes") && "/usr/share/vnc/classes") ||
39 ((-d "/usr/local/vnc/classes") && "/usr/local/vnc/classes"));
40$vncUserDir = "$ENV{HOME}/.vnc";
41$xauthorityFile = "$ENV{XAUTHORITY}" || "$ENV{HOME}/.Xauthority";
42
43$defaultXStartup
44 = ("#!/bin/sh\n\n".
45 "[ -r \$HOME/.Xresources ] && xrdb \$HOME/.Xresources\n".
46 "xsetroot -solid grey\n".
47 "vncconfig -iconic &\n".
48 "xterm -geometry 80x24+10+10 -ls -title \"\$VNCDESKTOP Desktop\" &\n".
49 "twm &\n");
50
51chop($host = `uname -n`);
52
53
54# Check command line options
55
56&ParseOptions("-geometry",1,"-depth",1,"-pixelformat",1,"-name",1,"-kill",1,
57 "-help",0,"-h",0,"--help",0);
58
59&Usage() if ($opt{'-help'} || $opt{'-h'} || $opt{'--help'});
60
61&Kill() if ($opt{'-kill'});
62
63# Uncomment this line if you want default geometry, depth and pixelformat
64# to match the current X display:
65# &GetXDisplayDefaults();
66
67if ($opt{'-geometry'}) {
68 $geometry = $opt{'-geometry'};
69}
70if ($opt{'-depth'}) {
71 $depth = $opt{'-depth'};
72 $pixelformat = "";
73}
74if ($opt{'-pixelformat'}) {
75 $pixelformat = $opt{'-pixelformat'};
76}
77
78&CheckGeometryAndDepth();
79
80
81# Create the user's vnc directory if necessary.
82
83if (!(-e $vncUserDir)) {
84 if (!mkdir($vncUserDir,0755)) {
85 die "$prog: Could not create $vncUserDir.\n";
86 }
87}
88
89# Make sure the user has a password.
90
91($z,$z,$mode) = stat("$vncUserDir/passwd");
92if (!(-e "$vncUserDir/passwd") || ($mode & 077)) {
93 warn "\nYou will require a password to access your desktops.\n\n";
94 system("vncpasswd -q $vncUserDir/passwd");
95 if (($? >> 8) != 0) {
96 exit 1;
97 }
98}
99
100# Find display number.
101
102if ((@ARGV > 0) && ($ARGV[0] =~ /^:(\d+)$/)) {
103 $displayNumber = $1;
104 shift(@ARGV);
105 if (!&CheckDisplayNumber($displayNumber)) {
106 die "A VNC server is already running as :$displayNumber\n";
107 }
108} elsif ((@ARGV > 0) && ($ARGV[0] !~ /^-/)) {
109 &Usage();
110} else {
111 $displayNumber = &GetDisplayNumber();
112}
113
114$vncPort = 5900 + $displayNumber;
115
116$desktopLog = "$vncUserDir/$host:$displayNumber.log";
117unlink($desktopLog);
118
119# Make an X server cookie - use as the seed the sum of the current time, our
120# PID and part of the encrypted form of the password. Ideally we'd use
121# /dev/urandom, but that's only available on Linux.
122
123srand(time+$$+unpack("L",`cat $vncUserDir/passwd`));
124$cookie = "";
125for (1..16) {
126 $cookie .= sprintf("%02x", int(rand(256)) % 256);
127}
128
129system("xauth -f $xauthorityFile add $host:$displayNumber . $cookie");
130system("xauth -f $xauthorityFile add $host/unix:$displayNumber . $cookie");
131
132if ($opt{'-name'}) {
133 $desktopName = $opt{'-name'};
134} else {
135 $desktopName = "$host:$displayNumber ($ENV{USER})";
136}
137
138# Now start the X VNC Server
139
140$cmd = "Xvnc :$displayNumber";
141$cmd .= " -desktop " . &quotedString($desktopName);
142$cmd .= " -httpd $vncJavaFiles" if ($vncJavaFiles);
143$cmd .= " -auth $xauthorityFile";
144$cmd .= " -geometry $geometry" if ($geometry);
145$cmd .= " -depth $depth" if ($depth);
146$cmd .= " -pixelformat $pixelformat" if ($pixelformat);
147$cmd .= " -rfbwait 30000";
148$cmd .= " -rfbauth $vncUserDir/passwd";
149$cmd .= " -rfbport $vncPort";
150$cmd .= " -pn";
151
152# Add font path and color database stuff here, e.g.:
153#
154# $cmd .= " -fp /usr/lib/X11/fonts/misc/,/usr/lib/X11/fonts/75dpi/";
155# $cmd .= " -co /usr/lib/X11/rgb";
156#
157
158foreach $arg (@ARGV) {
159 $cmd .= " " . &quotedString($arg);
160}
161$cmd .= " >> " . &quotedString($desktopLog) . " 2>&1";
162
163# Run $cmd and record the process ID.
164
165$pidFile = "$vncUserDir/$host:$displayNumber.pid";
166system("$cmd & echo \$! >$pidFile");
167
168# Give Xvnc a chance to start up
169
170sleep(3);
171
172warn "\nNew '$desktopName' desktop is $host:$displayNumber\n\n";
173
174# Create the user's xstartup script if necessary.
175
176if (!(-e "$vncUserDir/xstartup")) {
177 warn "Creating default startup script $vncUserDir/xstartup\n";
178 open(XSTARTUP, ">$vncUserDir/xstartup");
179 print XSTARTUP $defaultXStartup;
180 close(XSTARTUP);
181 chmod 0755, "$vncUserDir/xstartup";
182}
183
184# Run the X startup script.
185
186warn "Starting applications specified in $vncUserDir/xstartup\n";
187warn "Log file is $desktopLog\n\n";
188
189# If the unix domain socket exists then use that (DISPLAY=:n) otherwise use
190# TCP (DISPLAY=host:n)
191
192if (-e "/tmp/.X11-unix/X$displayNumber" ||
193 -e "/usr/spool/sockets/X11/$displayNumber")
194{
195 $ENV{DISPLAY}= ":$displayNumber";
196} else {
197 $ENV{DISPLAY}= "$host:$displayNumber";
198}
199$ENV{VNCDESKTOP}= $desktopName;
200
201system("$vncUserDir/xstartup >> " . &quotedString($desktopLog) . " 2>&1 &");
202
203exit;
204
205
206###############################################################################
207#
208# CheckGeometryAndDepth simply makes sure that the geometry and depth values
209# are sensible.
210#
211
212sub CheckGeometryAndDepth
213{
214 if ($geometry =~ /^(\d+)x(\d+)$/) {
215 $width = $1; $height = $2;
216
217 if (($width<1) || ($height<1)) {
218 die "$prog: geometry $geometry is invalid\n";
219 }
220
221 while (($width % 4)!=0) {
222 $width = $width + 1;
223 }
224
225 while (($height % 2)!=0) {
226 $height = $height + 1;
227 }
228
229 $geometry = "${width}x$height";
230 } else {
231 die "$prog: geometry $geometry is invalid\n";
232 }
233
234 if (($depth < 8) || ($depth > 32)) {
235 die "Depth must be between 8 and 32\n";
236 }
237}
238
239
240#
241# GetDisplayNumber gets the lowest available display number. A display number
242# n is taken if something is listening on the VNC server port (5900+n) or the
243# X server port (6000+n).
244#
245
246sub GetDisplayNumber
247{
248 foreach $n (1..99) {
249 if (&CheckDisplayNumber($n)) {
250 return $n+0; # Bruce Mah's workaround for bug in perl 5.005_02
251 }
252 }
253
254 die "$prog: no free display number on $host.\n";
255}
256
257
258#
259# CheckDisplayNumber checks if the given display number is available. A
260# display number n is taken if something is listening on the VNC server port
261# (5900+n) or the X server port (6000+n).
262#
263
264sub CheckDisplayNumber
265{
266 local ($n) = @_;
267
268 socket(S, $AF_INET, $SOCK_STREAM, 0) || die "$prog: socket failed: $!\n";
269 eval 'setsockopt(S, &SOL_SOCKET, &SO_REUSEADDR, pack("l", 1))';
270 if (!bind(S, pack('S n x12', $AF_INET, 6000 + $n))) {
271 close(S);
272 return 0;
273 }
274 close(S);
275
276 socket(S, $AF_INET, $SOCK_STREAM, 0) || die "$prog: socket failed: $!\n";
277 eval 'setsockopt(S, &SOL_SOCKET, &SO_REUSEADDR, pack("l", 1))';
278 if (!bind(S, pack('S n x12', $AF_INET, 5900 + $n))) {
279 close(S);
280 return 0;
281 }
282 close(S);
283
284 if (-e "/tmp/.X$n-lock") {
285 warn "\nWarning: $host:$n is taken because of /tmp/.X$n-lock\n";
286 warn "Remove this file if there is no X server $host:$n\n";
287 return 0;
288 }
289
290 if (-e "/tmp/.X11-unix/X$n") {
291 warn "\nWarning: $host:$n is taken because of /tmp/.X11-unix/X$n\n";
292 warn "Remove this file if there is no X server $host:$n\n";
293 return 0;
294 }
295
296 if (-e "/usr/spool/sockets/X11/$n") {
297 warn("\nWarning: $host:$n is taken because of ".
298 "/usr/spool/sockets/X11/$n\n");
299 warn "Remove this file if there is no X server $host:$n\n";
300 return 0;
301 }
302
303 return 1;
304}
305
306
307#
308# GetXDisplayDefaults uses xdpyinfo to find out the geometry, depth and pixel
309# format of the current X display being used. If successful, it sets the
310# options as appropriate so that the X VNC server will use the same settings
311# (minus an allowance for window manager decorations on the geometry). Using
312# the same depth and pixel format means that the VNC server won't have to
313# translate pixels when the desktop is being viewed on this X display (for
314# TrueColor displays anyway).
315#
316
317sub GetXDisplayDefaults
318{
319 local (@lines, @matchlines, $width, $height, $defaultVisualId, $i,
320 $red, $green, $blue);
321
322 $wmDecorationWidth = 4; # a guess at typical size for window manager
323 $wmDecorationHeight = 24; # decoration size
324
325 return if (!defined($ENV{DISPLAY}));
326
327 @lines = `xdpyinfo 2>/dev/null`;
328
329 return if ($? != 0);
330
331 @matchlines = grep(/dimensions/, @lines);
332 if (@matchlines) {
333 ($width, $height) = ($matchlines[0] =~ /(\d+)x(\d+) pixels/);
334
335 $width -= $wmDecorationWidth;
336 $height -= $wmDecorationHeight;
337
338 $geometry = "${width}x$height";
339 }
340
341 @matchlines = grep(/default visual id/, @lines);
342 if (@matchlines) {
343 ($defaultVisualId) = ($matchlines[0] =~ /id:\s+(\S+)/);
344
345 for ($i = 0; $i < @lines; $i++) {
346 if ($lines[$i] =~ /^\s*visual id:\s+$defaultVisualId$/) {
347 if (($lines[$i+1] !~ /TrueColor/) ||
348 ($lines[$i+2] !~ /depth/) ||
349 ($lines[$i+4] !~ /red, green, blue masks/))
350 {
351 return;
352 }
353 last;
354 }
355 }
356
357 return if ($i >= @lines);
358
359 ($depth) = ($lines[$i+2] =~ /depth:\s+(\d+)/);
360 ($red,$green,$blue)
361 = ($lines[$i+4]
362 =~ /masks:\s+0x([0-9a-f]+), 0x([0-9a-f]+), 0x([0-9a-f]+)/);
363
364 $red = hex($red);
365 $green = hex($green);
366 $blue = hex($blue);
367
368 if ($red > $blue) {
369 $red = int(log($red) / log(2)) - int(log($green) / log(2));
370 $green = int(log($green) / log(2)) - int(log($blue) / log(2));
371 $blue = int(log($blue) / log(2)) + 1;
372 $pixelformat = "rgb$red$green$blue";
373 } else {
374 $blue = int(log($blue) / log(2)) - int(log($green) / log(2));
375 $green = int(log($green) / log(2)) - int(log($red) / log(2));
376 $red = int(log($red) / log(2)) + 1;
377 $pixelformat = "bgr$blue$green$red";
378 }
379 }
380}
381
382
383#
384# quotedString returns a string which yields the original string when parsed
385# by a shell.
386#
387
388sub quotedString
389{
390 local ($in) = @_;
391
392 $in =~ s/\'/\'\"\'\"\'/g;
393
394 return "'$in'";
395}
396
397
398#
399# removeSlashes turns slashes into underscores for use as a file name.
400#
401
402sub removeSlashes
403{
404 local ($in) = @_;
405
406 $in =~ s|/|_|g;
407
408 return "$in";
409}
410
411
412#
413# Usage
414#
415
416sub Usage
417{
418 die("\nusage: $prog [:<number>] [-name <desktop-name>] [-depth <depth>]\n".
419 " [-geometry <width>x<height>]\n".
420 " [-pixelformat rgbNNN|bgrNNN]\n".
421 " <Xvnc-options>...\n\n".
422 " $prog -kill <X-display>\n\n");
423}
424
425
426#
427# Kill
428#
429
430sub Kill
431{
432 $opt{'-kill'} =~ s/(:\d+)\.\d+$/$1/; # e.g. turn :1.0 into :1
433
434 if ($opt{'-kill'} =~ /^:\d+$/) {
435 $pidFile = "$vncUserDir/$host$opt{'-kill'}.pid";
436 } else {
437 if ($opt{'-kill'} !~ /^$host:/) {
438 die "\nCan't tell if $opt{'-kill'} is on $host\n".
439 "Use -kill :<number> instead\n\n";
440 }
441 $pidFile = "$vncUserDir/$opt{'-kill'}.pid";
442 }
443
444 if (! -r $pidFile) {
445 die "\nCan't find file $pidFile\n".
446 "You'll have to kill the Xvnc process manually\n\n";
447 }
448
449 $SIG{'HUP'} = 'IGNORE';
450 chop($pid = `cat $pidFile`);
451 warn "Killing Xvnc process ID $pid\n";
452 system("kill $pid");
453 unlink $pidFile;
454 exit;
455}
456
457
458#
459# ParseOptions takes a list of possible options and a boolean indicating
460# whether the option has a value following, and sets up an associative array
461# %opt of the values of the options given on the command line. It removes all
462# the arguments it uses from @ARGV and returns them in @optArgs.
463#
464
465sub ParseOptions
466{
467 local (@optval) = @_;
468 local ($opt, @opts, %valFollows, @newargs);
469
470 while (@optval) {
471 $opt = shift(@optval);
472 push(@opts,$opt);
473 $valFollows{$opt} = shift(@optval);
474 }
475
476 @optArgs = ();
477 %opt = ();
478
479 arg: while (defined($arg = shift(@ARGV))) {
480 foreach $opt (@opts) {
481 if ($arg eq $opt) {
482 push(@optArgs, $arg);
483 if ($valFollows{$opt}) {
484 if (@ARGV == 0) {
485 &Usage();
486 }
487 $opt{$opt} = shift(@ARGV);
488 push(@optArgs, $opt{$opt});
489 } else {
490 $opt{$opt} = 1;
491 }
492 next arg;
493 }
494 }
495 push(@newargs,$arg);
496 }
497
498 @ARGV = @newargs;
499}
500
501
502#
503# Routine to make sure we're operating in a sane environment.
504#
505
506sub SanityCheck
507{
508 local ($cmd);
509
510 #
511 # Get the program name
512 #
513
514 ($prog) = ($0 =~ m|([^/]+)$|);
515
516 #
517 # Check we have all the commands we'll need on the path.
518 #
519
520 cmd:
521 foreach $cmd ("uname","xauth","Xvnc","vncpasswd") {
522 for (split(/:/,$ENV{PATH})) {
523 if (-x "$_/$cmd") {
524 next cmd;
525 }
526 }
527 die "$prog: couldn't find \"$cmd\" on your PATH.\n";
528 }
529
530 #
531 # Check the HOME environment variable is set
532 #
533
534 if (!defined($ENV{HOME})) {
535 die "$prog: The HOME environment variable is not set.\n";
536 }
537 chdir($ENV{HOME});
538
539 #
540 # Find socket constants. 'use Socket' is a perl5-ism, so we wrap it in an
541 # eval, and if it fails we try 'require "sys/socket.ph"'. If this fails,
542 # we just guess at the values. If you find perl moaning here, just
543 # hard-code the values of AF_INET and SOCK_STREAM. You can find these out
544 # for your platform by looking in /usr/include/sys/socket.h and related
545 # files.
546 #
547
548 chop($os = `uname`);
549 chop($osrev = `uname -r`);
550
551 eval 'use Socket';
552 if ($@) {
553 eval 'require "sys/socket.ph"';
554 if ($@) {
555 if (($os eq "SunOS") && ($osrev !~ /^4/)) {
556 $AF_INET = 2;
557 $SOCK_STREAM = 2;
558 } else {
559 $AF_INET = 2;
560 $SOCK_STREAM = 1;
561 }
562 } else {
563 $AF_INET = &AF_INET;
564 $SOCK_STREAM = &SOCK_STREAM;
565 }
566 } else {
567 $AF_INET = &AF_INET;
568 $SOCK_STREAM = &SOCK_STREAM;
569 }
570}