You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

vncserver 14KB

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