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.in 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. #!/usr/bin/env perl
  2. #
  3. # Copyright (C) 2015-2019 Pierre Ossman for Cendio AB
  4. # Copyright (C) 2009-2010 D. R. Commander. All Rights Reserved.
  5. # Copyright (C) 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
  6. # Copyright (C) 2002-2003 Constantin Kaplinsky. All Rights Reserved.
  7. # Copyright (C) 2002-2005 RealVNC Ltd.
  8. # Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
  9. #
  10. # This is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # This software is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with this software; if not, write to the Free Software
  22. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
  23. # USA.
  24. #
  25. #
  26. # vncserver - wrapper script to start an X VNC server.
  27. #
  28. # First make sure we're operating in a sane environment.
  29. &SanityCheck();
  30. #
  31. # Global variables. You may want to configure some of these for
  32. # your site
  33. #
  34. $vncUserDir = "$ENV{HOME}/.vnc";
  35. $vncUserConfig = "$vncUserDir/config";
  36. $vncSystemConfigDir = "/etc/tigervnc";
  37. $vncSystemConfigDefaultsFile = "$vncSystemConfigDir/vncserver-config-defaults";
  38. $vncSystemConfigMandatoryFile = "$vncSystemConfigDir/vncserver-config-mandatory";
  39. $xauthorityFile = "$ENV{XAUTHORITY}" || "$ENV{HOME}/.Xauthority";
  40. chop($host = `uname -n`);
  41. if (-d "/etc/X11/fontpath.d") {
  42. $fontPath = "catalogue:/etc/X11/fontpath.d";
  43. }
  44. @fontpaths = ('/usr/share/X11/fonts', '/usr/share/fonts', '/usr/share/fonts/X11/');
  45. if (! -l "/usr/lib/X11") {push(@fontpaths, '/usr/lib/X11/fonts');}
  46. if (! -l "/usr/X11") {push(@fontpaths, '/usr/X11/lib/X11/fonts');}
  47. if (! -l "/usr/X11R6") {push(@fontpaths, '/usr/X11R6/lib/X11/fonts');}
  48. push(@fontpaths, '/usr/share/fonts/default');
  49. @fonttypes = ('misc',
  50. '75dpi',
  51. '100dpi',
  52. 'Speedo',
  53. 'Type1');
  54. foreach $_fpath (@fontpaths) {
  55. foreach $_ftype (@fonttypes) {
  56. if (-f "$_fpath/$_ftype/fonts.dir") {
  57. if (! -l "$_fpath/$_ftype") {
  58. $defFontPath .= "$_fpath/$_ftype,";
  59. }
  60. }
  61. }
  62. }
  63. if ($defFontPath) {
  64. if (substr($defFontPath, -1, 1) == ',') {
  65. chop $defFontPath;
  66. }
  67. }
  68. if ($fontPath eq "") {
  69. $fontPath = $defFontPath;
  70. }
  71. # Find display number.
  72. if ((@ARGV == 1) && ($ARGV[0] =~ /^:(\d+)$/)) {
  73. $displayNumber = $1;
  74. if (!&CheckDisplayNumber($displayNumber)) {
  75. die "A VNC server is already running as :$displayNumber\n";
  76. }
  77. } else {
  78. &Usage();
  79. }
  80. $vncPort = 5900 + $displayNumber;
  81. $desktopName = "$host:$displayNumber ($ENV{USER})";
  82. my %default_opts;
  83. my %config;
  84. # We set some reasonable defaults. Config file settings
  85. # override these where present.
  86. $default_opts{desktop} = $desktopName;
  87. $default_opts{auth} = $xauthorityFile;
  88. $default_opts{rfbwait} = 30000;
  89. $default_opts{rfbauth} = "$vncUserDir/passwd";
  90. $default_opts{rfbport} = $vncPort;
  91. $default_opts{fp} = $fontPath if ($fontPath);
  92. $default_opts{pn} = "";
  93. # Load user-overrideable system defaults
  94. LoadConfig($vncSystemConfigDefaultsFile);
  95. # Then the user's settings
  96. LoadConfig($vncUserConfig);
  97. # And then override anything set above if mandatory settings exist.
  98. # WARNING: "Mandatory" is used loosely here! As the man page says,
  99. # there is nothing stopping someone from EASILY subverting the
  100. # settings in $vncSystemConfigMandatoryFile by simply passing
  101. # CLI args to vncserver, which trump config files! To properly
  102. # hard force policy in a non-subvertible way would require major
  103. # development work that touches Xvnc itself.
  104. LoadConfig($vncSystemConfigMandatoryFile, 1);
  105. #
  106. # Check whether VNC authentication is enabled, and if so, check that
  107. # a VNC password has been created.
  108. #
  109. $securityTypeArgSpecified = 0;
  110. $vncAuthEnabled = 0;
  111. $passwordArgSpecified = 0;
  112. @vncAuthStrings = ("vncauth", "tlsvnc", "x509vnc");
  113. # ...first we check our configuration files' settings
  114. if ($config{'securitytypes'}) {
  115. $securityTypeArgSpecified = 1;
  116. foreach $arg2 (split(',', $config{'securitytypes'})) {
  117. if (grep {$_ eq lc($arg2)} @vncAuthStrings) {
  118. $vncAuthEnabled = 1;
  119. }
  120. }
  121. }
  122. if ($config{'password'} ||
  123. $config{'passwordfile'} ||
  124. $config{'rfbauth'}) {
  125. $passwordArgSpecified = 1;
  126. }
  127. if ((!$securityTypeArgSpecified || $vncAuthEnabled) && !$passwordArgSpecified) {
  128. ($z,$z,$mode) = stat("$vncUserDir/passwd");
  129. if (!(-e "$vncUserDir/passwd") || ($mode & 077)) {
  130. die "VNC authentication enabled, but no password file created.\n";
  131. }
  132. }
  133. #
  134. # Find a desktop session to run
  135. #
  136. my $sessionname;
  137. my %session;
  138. $sessionname = delete $config{'session'};
  139. if ($sessionname) {
  140. %session = LoadXSession($sessionname);
  141. if (!%session) {
  142. warn "Could not load configured desktop session $sessionname\n";
  143. $sessionname = undef;
  144. }
  145. }
  146. if (!$sessionname) {
  147. foreach $file (glob("/usr/share/xsessions/*.desktop")) {
  148. ($name) = $file =~ /^.*\/(.*)[.]desktop$/;
  149. %session = LoadXSession($name);
  150. if (%session) {
  151. $sessionname = $name;
  152. last;
  153. }
  154. }
  155. }
  156. if (!$sessionname) {
  157. die "Could not find a desktop session to run\n";
  158. }
  159. warn "Using desktop session $sessionname\n";
  160. if (!$session{'Exec'}) {
  161. die "No command specified for desktop session\n";
  162. }
  163. $ENV{GDMSESSION} = $sessionname;
  164. $ENV{DESKTOP_SESSION} = $sessionname;
  165. $ENV{XDG_SESSION_DESKTOP} = $sessionname;
  166. if ($session{'DesktopNames'}) {
  167. $ENV{XDG_DESKTOP_NAMES} = $session{'DesktopNames'} =~ s/;/:/gr;
  168. }
  169. # Make an X server cookie and set up the Xauthority file
  170. # mcookie is a part of util-linux, usually only GNU/Linux systems have it.
  171. $cookie = `mcookie`;
  172. # Fallback for non GNU/Linux OS - use /dev/urandom on systems that have it,
  173. # otherwise use perl's random number generator, seeded with the sum
  174. # of the current time, our PID and part of the encrypted form of the password.
  175. if ($cookie eq "" && open(URANDOM, '<', '/dev/urandom')) {
  176. my $randata;
  177. if (sysread(URANDOM, $randata, 16) == 16) {
  178. $cookie = unpack 'h*', $randata;
  179. }
  180. close(URANDOM);
  181. }
  182. if ($cookie eq "") {
  183. srand(time+$$+unpack("L",`cat $vncUserDir/passwd`));
  184. for (1..16) {
  185. $cookie .= sprintf("%02x", int(rand(256)) % 256);
  186. }
  187. }
  188. open(XAUTH, "|xauth -f $xauthorityFile source -");
  189. print XAUTH "add $host:$displayNumber . $cookie\n";
  190. print XAUTH "add $host/unix:$displayNumber . $cookie\n";
  191. close(XAUTH);
  192. $ENV{XAUTHORITY} = $xauthorityFile;
  193. # Now start the X VNC Server
  194. @cmd = ("xinit");
  195. push(@cmd, $Xsession, $session{'Exec'});
  196. push(@cmd, '--');
  197. # We build up our Xvnc command with options
  198. push(@cmd, "@CMAKE_INSTALL_FULL_BINDIR@/Xvnc", ":$displayNumber");
  199. foreach my $k (sort keys %config) {
  200. push(@cmd, "-$k");
  201. push(@cmd, $config{$k}) if $config{$k};
  202. delete $default_opts{$k}; # file options take precedence
  203. }
  204. foreach my $k (sort keys %default_opts) {
  205. push(@cmd, "-$k");
  206. push(@cmd, $default_opts{$k}) if $default_opts{$k};
  207. }
  208. warn "\nNew '$desktopName' desktop is $host:$displayNumber\n\n";
  209. warn "Starting desktop session $sessionname\n";
  210. exec(@cmd);
  211. die "Failed to start session.\n";
  212. ###############################################################################
  213. # Functions
  214. ###############################################################################
  215. #
  216. # Populate the global %config hash with settings from a specified
  217. # vncserver configuration file if it exists
  218. #
  219. # Args: 1. file path
  220. # 2. optional boolean flag to enable warning when a previously
  221. # set configuration setting is being overridden
  222. #
  223. sub LoadConfig {
  224. local ($configFile, $warnoverride) = @_;
  225. local ($toggle) = undef;
  226. if (stat($configFile)) {
  227. if (open(IN, $configFile)) {
  228. while (<IN>) {
  229. next if /^#/;
  230. if (my ($k, $v) = /^\s*(\w+)\s*=\s*(.+)$/) {
  231. $k = lc($k); # must normalize key case
  232. if ($warnoverride && $config{$k}) {
  233. print("Warning: $configFile is overriding previously defined '$k' to be '$v'\n");
  234. }
  235. $config{$k} = $v;
  236. } elsif ($_ =~ m/^\s*(\S+)/) {
  237. # We can't reasonably warn on override of toggles (e.g. AlwaysShared)
  238. # because it would get crazy to do so. We'd have to check if the
  239. # current config file being loaded defined the logical opposite setting
  240. # (NeverShared vs. AlwaysShared, etc etc).
  241. $toggle = lc($1); # must normalize key case
  242. $config{$toggle} = $k;
  243. }
  244. }
  245. close(IN);
  246. }
  247. }
  248. }
  249. #
  250. # Load a session desktop file
  251. #
  252. sub LoadXSession {
  253. local ($name) = @_;
  254. my $file, $found_group, %session;
  255. $file = "/usr/share/xsessions/$name.desktop";
  256. if (!stat($file)) {
  257. warn "Could not find session desktop file $file";
  258. return;
  259. }
  260. if (!open(IN, $file)) {
  261. warn "Could not open session desktop file $file";
  262. return;
  263. }
  264. $found_group = 0;
  265. while (my $line = <IN>) {
  266. next if $line =~ /^#/;
  267. next if $line =~ /^\s*$/;
  268. if (!$found_group) {
  269. next if $line != "[Desktop Entry]";
  270. $found_group = 1;
  271. next;
  272. } else {
  273. last if $line =~ /^\[/;
  274. }
  275. my ($key, $value) = $line =~ /^\s*([]A-Za-z0-9_@\-\[]+)\s*=\s*(.*)$/;
  276. if (!$key) {
  277. warn "Invalid session desktop file $file";
  278. close(IN);
  279. return;
  280. }
  281. $value =~ s/\\s/ /g;
  282. $value =~ s/\\n/\n/g;
  283. $value =~ s/\\t/\t/g;
  284. $value =~ s/\\r/\r/g;
  285. $value =~ s/\\\\/\\/g;
  286. $session{$key} = $value;
  287. }
  288. close(IN);
  289. return %session;
  290. }
  291. #
  292. # CheckDisplayNumber checks if the given display number is available. A
  293. # display number n is taken if something is listening on the VNC server port
  294. # (5900+n) or the X server port (6000+n).
  295. #
  296. sub CheckDisplayNumber
  297. {
  298. local ($n) = @_;
  299. my $x11_lock_path = "/tmp/.X$n-lock";
  300. if (-e $x11_lock_path) {
  301. my($pid) = `cat "$x11_lock_path"` =~ /^\s*(\d+)\s*$/;
  302. if (defined($pid) && kill(0, $pid)) {
  303. # Lock is associated with valid PID.
  304. return 0;
  305. }
  306. }
  307. my $rfb_port = 5900 + $n;
  308. my $x11_port = 6000 + $n;
  309. for my $port ($rfb_port, $x11_port) {
  310. # Bind to port to confirm it is not in use.
  311. socket(S, $AF_INET, $SOCK_STREAM, 0) || die "$prog: socket failed: $!\n";
  312. eval 'setsockopt(S, &SOL_SOCKET, &SO_REUSEADDR, pack("l", 1))';
  313. if (!bind(S, pack('S n x12', $AF_INET, $port))) {
  314. # Port is in use.
  315. close(S);
  316. return 0;
  317. }
  318. close(S);
  319. }
  320. my $x11_unix_domain = "/tmp/.X11-unix/X$n";
  321. if (-e $x11_unix_domain) {
  322. warn "\nWarning: $host:$n is taken because of $x11_unix_domain\n";
  323. warn "Remove this file if there is no X server $host:$n\n";
  324. return 0;
  325. }
  326. return 1;
  327. }
  328. #
  329. # Usage
  330. #
  331. sub Usage
  332. {
  333. die("\nusage: $prog <display>\n\n");
  334. }
  335. # Routine to make sure we're operating in a sane environment.
  336. sub SanityCheck
  337. {
  338. local ($cmd);
  339. # Get the program name
  340. ($prog) = ($0 =~ m|([^/]+)$|);
  341. #
  342. # Check we have all the commands we'll need on the path.
  343. #
  344. cmd:
  345. foreach $cmd ("uname","xauth","xinit") {
  346. for (split(/:/,$ENV{PATH})) {
  347. if (-x "$_/$cmd") {
  348. next cmd;
  349. }
  350. }
  351. die "$prog: couldn't find \"$cmd\" on your PATH.\n";
  352. }
  353. foreach $cmd ("/etc/X11/xinit/Xsession", "/etc/X11/Xsession") {
  354. if (-x "$cmd") {
  355. $Xsession = $cmd;
  356. last;
  357. }
  358. }
  359. if (not defined $Xsession) {
  360. die "$prog: Couldn't find suitable Xsession.\n";
  361. }
  362. if (!defined($ENV{HOME})) {
  363. die "$prog: The HOME environment variable is not set.\n";
  364. }
  365. #
  366. # Find socket constants. 'use Socket' is a perl5-ism, so we wrap it in an
  367. # eval, and if it fails we try 'require "sys/socket.ph"'. If this fails,
  368. # we just guess at the values. If you find perl moaning here, just
  369. # hard-code the values of AF_INET and SOCK_STREAM. You can find these out
  370. # for your platform by looking in /usr/include/sys/socket.h and related
  371. # files.
  372. #
  373. chop($os = `uname`);
  374. chop($osrev = `uname -r`);
  375. eval 'use Socket';
  376. if ($@) {
  377. eval 'require "sys/socket.ph"';
  378. if ($@) {
  379. if (($os eq "SunOS") && ($osrev !~ /^4/)) {
  380. $AF_INET = 2;
  381. $SOCK_STREAM = 2;
  382. } else {
  383. $AF_INET = 2;
  384. $SOCK_STREAM = 1;
  385. }
  386. } else {
  387. $AF_INET = &AF_INET;
  388. $SOCK_STREAM = &SOCK_STREAM;
  389. }
  390. } else {
  391. $AF_INET = &AF_INET;
  392. $SOCK_STREAM = &SOCK_STREAM;
  393. }
  394. }