#! /usr/bin/perl
# vim: set sw=2 sts=2 ts=8 syn=perl expandtab:
#
# vncserver - wrapper script to start an X VNC server.
#
# Copyright (C) 2004-2021 Joachim Falk <joachim.falk@gmx.de>
# Please report all errors to Joachim Falk and not to OL.
#
# This file is based on a vncserver script provided by:
#
#  Copyright (C) 2017 Philipp Wolski <philipp.wolski@kisters.de>
#  Copyright (C) 2004 Ola Lundqvist <opal@debian.org>
#  Copyright (C) 2004 Marcus Brinkmann <Marcus.Brinkmann@ruhr-uni-bochum.de>
#  Copyright (C) 2004 Dirk Eddelbuettel <edd@debian.org>
#  Copyright (C) 2002-2003 RealVNC Ltd.
#  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
#  Copyright (C) 1997, 1998 Olivetti & Oracle Research Laboratory
#
# This is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
# USA.

package config;

#########################################################################
#
# readConfigFile reads in a config file and sets variables according to it.
#

sub readConfigFile($$$) {
  my ($options, $desc, $configFile) = @_;

  # Clean config package before loading new config
  foreach my $key (keys %{config::}) {
    delete ${config::}{$key};
  }

  my %tiedNoCase;

  foreach my $optionParseEntry (@{&main::getOptionParseTable($options, $desc)}) {
    my ($flags, $optname, $store) = @{$optionParseEntry};
    next unless $flags & &main::OPT_CFGFILE;

    $optname =~ m/^([^:=]*)/;
    $optname = $1;

#   print STDERR "==> $optname <==\n";
    foreach my $name (split(/\|/, $optname)) {
      $name =~ s{[^a-zA-Z0-9_]}{_}g;
#     print STDERR $name, "\n";
      no strict 'refs';
      tie ${"config::$name"}, 'OptionTie', sub { &{$store}($name, @_) };
      $tiedNoCase{lc($name)} = $name;
    }
  }

# print STDERR "readConfigFile $desc $configFile\n";

  $@ = undef;
  do $configFile;
  if ($@) {
    print STDERR "$main::PROG: Error parsing config file $ConfigFile: $@";
    exit -1;
  }

  foreach my $key (keys %{config::}) {
    if (my $name = $tiedNoCase{lc($key)}) {
      next if $name eq $key;
      print STDERR "$main::PROG: Warning: $key option must be $name in config file $configFile!\n";
      next;
    }
    my $value = ${${config::}{$key}};
    $options->{'vncServerExtraArgs'} = [grep {
	$_->{'name'} ne $key
      } @{$options->{'vncServerExtraArgs'}}];
    if (defined $value) {
      push @{$options->{'vncServerExtraArgs'}}, {
	  name => $key,
	  src  => $desc,
	  args => [ "-$key", $value ]
	};
    }
  }
  return 1;
}

package OptionTie;

use Carp;

sub TIESCALAR {
  my $class  = shift;
  my $option = shift;

  return bless $option, $class;
}

sub STORE {
  my $self = shift;
  unless (eval { &{$self}($_[0]); 1 }) {
    my $errorText = $@;
    $errorText =~ s/ at .* line \d+\.$//;
    chomp $errorText;
    croak $errorText;
  }
  return &{$self}();
}

sub FETCH {
  my $self = shift;
  return &{$self}();
}

package main;

use strict;
use warnings;

use File::Path;
use File::Spec;
use File::Basename qw(dirname basename);
use File::ReadBackwards;
use DirHandle;
use File::stat;
use IO::File;
use Fcntl qw(SEEK_SET SEEK_CUR SEEK_END);
use Socket;
use Time::HiRes qw(usleep);
use Errno qw(:POSIX);
use POSIX ":sys_wait_h";

#
# Set global constants
#

# Get the program name
our $PROG = basename($0);

sub installPackageError {
  my ($package) = @_;
  print STDERR "\tPlease install the $package package, i.e., sudo apt-get install $package.\n";
  exit 1;
}

sub getCommand {
  my ($cmd) = @_;

  our %CMDS;

  return $CMDS{$cmd} if defined $CMDS{$cmd};
  print STDERR "$PROG: Couldn't find \"$cmd\" on your PATH.\n";
  &installPackageError("tigervnc-common") if $cmd eq 'tigervncpasswd';
  &installPackageError("openssl") if $cmd eq 'openssl';
  &installPackageError("x11-utils") if $cmd eq 'xdpyinfo';
  exit 1;
}

#
# Routine to make sure we're operating in a sane environment.
#
sub sanityCheck {
  our %CMDS;

  # Get install base bin dir
  my $binbase = dirname(File::Spec->rel2abs($0));

  #
  # Check we have all the commands we'll need on the path.
  #
  %CMDS = ();
cmd:
  foreach my $cmd ("hostname","xauth","Xtigervnc") {
    foreach my $dir ($binbase, split(/:/,$ENV{PATH})) {
      my $fqcmd = File::Spec->catfile($dir, $cmd);
      if (-x $fqcmd) {
        $CMDS{$cmd} = $fqcmd;
        next cmd;
      }
    }
    print STDERR "$PROG: Couldn't find \"$cmd\" on your PATH.\n";
    exit 1;
  }
  # These commands are optional.
  foreach my $cmd ("tigervncpasswd", "openssl", "xdpyinfo") {
    foreach my $dir ($binbase, split(/:/,$ENV{PATH})) {
      my $fqcmd = File::Spec->catfile($dir, $cmd);
      if (-x $fqcmd) {
        $CMDS{$cmd} = $fqcmd;
      }
    }
  }
  #
  # Check the HOME environment variable is set
  #
  if (!defined($ENV{HOME})) {
    print STDERR "$PROG: The HOME environment variable is not set.\n";
    exit 1;
  }
}

#
# Next, make sure we're operating in a sane environment.
#
&sanityCheck();

# Get the hostname
our ($HOST, $HOSTFQDN);
{
  my $hostname= getCommand("hostname");
  chomp($HOST     = `$hostname`);
  chomp($HOSTFQDN = `$hostname -f`);
}
# Get the username
our $USER;
chomp($USER = `/usr/bin/id -u -n`);

our $SYSTEMCONFIGDIR = "/etc/tigervnc";

sub OPT_PARAM()           { return 1; }
sub OPT_CFGFILE()         { return 2; }
sub OPT_TIGERVNCSERVER()  { return 4; }
sub OPT_XTIGERVNC()       { return 8; }

sub getOptionParseTable($$) {
  my ($options, $desc) = @_;

  my $override = sub($) {
    my ($name, $value) = @_;

    if ($desc eq 'mandatory' && defined $options->{'src'}->{$name} &&
        $value ne $options->{$name}) {
      my $configFile = File::Spec->catfile($SYSTEMCONFIGDIR,
        "vncserver-config-mandatory");
      my $v;
      if (!defined $value) {
        $v = 'undef';
      } elsif (ref($value) eq '') {
        $v = '"'.$value.'"';
      } elsif (ref($value) eq 'ARRAY') {
        $v = '"'.join('", "', @{$value}).'"';
      } else {
        die 'Oops, '.ref($value).' values not supported!';
      }
      if ($options->{'src'}->{$name} eq 'defaults') {
        print STDERR "$PROG: Warning: $configFile is overriding $name from defaults config to be $v!\n";
      } elsif ($options->{'src'}->{$name} eq 'user') {
        print STDERR "$PROG: Warning: $configFile is overriding $name form user config to be $v!\n";
      } elsif ($options->{'src'}->{$name} eq 'cmdline') {
        print STDERR "$PROG: Warning: $configFile is overriding $name from commandline to be $v!\n";
      }
    }
    $options->{$name} = $value;
    $options->{'src'}->{$name} = $desc;
  };

  # Flag field in $optionParseTable
  # 1 => Case insensitive
  # 2 => Config file parameter
  # 4 => Command line option for tigervncserver
  # 8 => Command line option for Xtigervnc

  my @optionParseTable = (
#     [ flag, 'name=type'      => store        ],
      [ 4, 'name=s'            => 'desktopName' ],
      [ 4, 'kill'              => 'kill' ],
      [ 4, 'help|h|?'          => 'help' ],
      [ 4, 'list'              => 'list' ],
      [ 4, 'fg'                => 'fg' ],
      [ 4, 'autokill'          => 'autokill' ],
      [ 4, 'noxstartup'        => sub {
          if (@_ == 2) {
            &{$override}('vncStartup', undef);
          } else {
            return !defined $options->{'vncStartup'};
          }
        } ],
      [ 4, 'xstartup:s'        => sub {
          if (@_ == 2) {
            if ($_[1] eq '') {
              &{$override}('vncStartup', '__AUTO__');
            } else {
              &{$override}('vncStartup', $_[1]);
            }
          } else {
            return $options->{'vncStartup'};
          }
        } ],
      [ 4, 'xdisplaydefaults'  => sub {
          if (@_ == 2) {
            &getXDisplayDefaults($options);
          } else {
            return undef;
          }
        } ],
      [ 6, 'wmDecoration=s'    => 'wmDecoration' ],
      [ 4, 'useold'            => 'useold' ],
      [ 4, 'cleanstale'        => 'cleanstale' ],
      [ 4, 'clean'             => 'clean' ],
      [ 4, 'verbose'           => 'verbose' ],
      [ 4, 'dry-run'           => 'dry-run' ],
      [ 4, 'passwd=s'          => 'vncPasswdFile' ],
      [ 4, 'I-KNOW-THIS-IS-INSECURE' => 'I-KNOW-THIS-IS-INSECURE' ],
      # Config file stuff
      [ 2, 'session=s'         => sub {
          if (@_ == 2) {
            my $session;
            if (defined $_[1] && ref($_[1]) eq '') {
              $session = [split(qr{\s+}, $_[1])];
            } elsif (defined $_[1] && ref($_[1]) eq 'ARRAY') {
              $session = $_[1];
            } elsif (!defined $_[1]) {
              $session = [];
            } else {
              die "Option $_[0] must be set to a string or array reference!";
            }
            &{$override}('session', $session);
          } else {
            return $options->{'session'};
          }
        } ],
      [ 2, 'fontPath=s'        => 'fontPath' ],
      [ 2, 'sslAutoGenCertCommand=s' => 'sslAutoGenCertCommand' ],
      [ 2, 'vncUserDir=s'      => 'vncUserDir' ],
      [ 2, 'vncPasswdFile=s'   => 'vncPasswdFile' ],
      [ 2, 'vncStartup=s'      => 'vncStartup' ],
      [ 2, 'xauthorityFile=s'  => 'xauthorityFile' ],
      [ 2, 'desktopName=s'     => 'desktopName' ],
      [ 2, 'getDefaultFrom=s'  => 'getDefaultFrom' ],
      # Arguments from Xtigervnc (case sensitive)
      [14, 'auth=s'            => 'xauthorityFile' ],
      [14, 'fp=s'              => 'fontPath' ],
      [14, 'geometry=s'        => 'geometry' ],
      [14, 'depth=i'           => 'depth' ],
      [14, 'pixelformat=s'     => sub {
          if (@_ == 2) {
            &{$override}('pixelformat', $_[1]);
            &{$override}('depth', undef);
          } else {
            return $options->{'pixelformat'};
          }
        } ],
      # -inetd is not handled
      [14, 'interface=s'       => sub {
          if (@_ == 2) {
            &{$override}('interface', $_[1]);
            &{$override}('localhost', undef);
          } else {
            return $options->{'interface'};
          }
        } ],
      # Parameters from Xtigervnc (case insensitive)
      [15, 'desktop=s'         => 'desktopName' ],
      [15, 'rfbport=i'         => 'rfbport' ],
      [15, 'UseIPv4:b'         => 'UseIPv4' ],
      [15, 'UseIPv6:b'         => 'UseIPv6' ],
      [15, 'rfbunixpath=s'     => 'rfbunixpath' ],
      [15, 'rfbunixmode=s'     => 'rfbunixmode' ],
      [15, 'rfbwait|ClientWaitTimeMillis=i' => 'rfbwait' ],
      [15, 'rfbauth|PasswordFile=s' => 'vncPasswdFile' ],
      [15, 'AcceptCutText:b'   => 'AcceptCutText' ],
      [15, 'MaxCutText=i'      => 'MaxCutText' ],
      [15, 'SendCutText:b'     => 'SendCutText' ],
      [15, 'SendPrimary:b'     => 'SendPrimary' ],
      [15, 'AcceptPointerEvents:b' => 'AcceptPointerEvents' ],
      [15, 'AcceptKeyEvents:b' => 'AcceptKeyEvents' ],
      [15, 'AcceptSetDesktopSize:b' => 'AcceptSetDesktopSize' ],
      [15, 'DisconnectClients:b' => 'DisconnectClients' ],
      [15, 'NeverShared:b'     => sub {
          if (@_ == 2) {
            if ($_[1] eq '' || $_[1] eq '1') {
              &{$override}('shared', 'never');
            } elsif ($options->{'shared'} eq 'never') {
              &{$override}('shared', undef);
            }
          } elsif (defined $options->{'shared'}) {
            return $options->{'shared'} eq 'never';
          } else {
            return undef;
          }
        } ],
      [15, 'AlwaysShared:b'    => sub {
          if (@_ == 2) {
            if ($_[1] eq '' || $_[1] eq '1') {
              &{$override}('shared', 'always');
            } elsif ($options->{'shared'} eq 'always') {
              &{$override}('shared', undef);
            }
          } elsif (defined $options->{'shared'}) {
            return $options->{'shared'} eq 'always';
          } else {
            return undef;
          }
        } ],
      [15, 'Protocol3.3:b'     => 'Protocol3.3' ],
      [15, 'FrameRate=i'       => 'FrameRate' ],
      [15, 'CompareFB=s'       => 'CompareFB' ],
      [15, 'ZlibLevel=i'       => 'ZlibLevel' ],
      [15, 'ImprovedHextile:b' => 'ImprovedHextile' ],
      [15, 'SecurityTypes=s'   => 'SecurityTypes' ],
      [15, 'Password=s'        => 'Password' ],
      [15, 'PlainUsers=s'      => 'PlainUsers' ],
      [15, 'PAMService|pam_service=s' => 'PAMService' ],
      [15, 'X509Cert=s'        => 'X509Cert' ],
      [15, 'X509Key=s'         => 'X509Key'  ],
      [15, 'GnuTLSPriority=s'  => 'GnuTLSPriority' ],
      [15, 'UseBlacklist:b'    => 'UseBlacklist' ],
      [15, 'BlacklistThreshold=i' => 'BlacklistThreshold' ],
      [15, 'BlacklistTimeout=i' => 'BlacklistTimeout' ],
      [15, 'IdleTimeout=i'     => 'IdleTimeout' ],
      [15, 'MaxDisconnectionTime=i' => 'MaxDisconnectionTime' ],
      [15, 'MaxConnectionTime=i' => 'MaxConnectionTime' ],
      [15, 'MaxIdleTime=i'     => 'MaxIdleTime' ],
      [15, 'QueryConnect:b'    => 'QueryConnect' ],
      [15, 'QueryConnectTimeout=i' => 'QueryConnectTimeout' ],
      [15, 'localhost:b'       => 'localhost' ],
      [15, 'Log=s'             => 'Log' ],
      [15, 'RemapKeys=s'       => 'RemapKeys' ],
      [15, 'AvoidShiftNumLock:b' => 'AvoidShiftNumLock' ],
      [15, 'RawKeyboard:b'     => 'RawKeyboard' ],
      [15, 'AllowOverride=s'   => 'AllowOverride' ],
    );

  my $optionParseTable = [];
  foreach my $optionParseEntry (@optionParseTable) {
    my ($flags, $optname, $store) = @{$optionParseEntry};
    if (@{$optionParseEntry} != 3 ||
        (ref($store) ne '' && ref($store) ne 'CODE')) {
#     print $#{$optionParseEntry}, "\n";
#     print ref($optionParseEntry->[2]), "\n";
      die "Oops, internal error: Wrong optioneParseEntry!";
    }
    my $valueVerifyer = undef;
    if ($optname =~ m/:b$/) {
      $valueVerifyer = sub {
        if ($_[1] eq '' || $_[1] eq '1' || $_[1] eq 'yes' || $_[1] eq 'true') {
          return 1;
        } elsif ($_[1] eq '0' || $_[1] eq 'no' || $_[1] eq 'false') {
          return 0;
        } else {
          die "Option $_[0] can only be set to true or false!";
        }
      }
    } elsif ($optname =~ m/=i$/) {
      $valueVerifyer = sub { $_[1] };
    } elsif ($optname =~ m/[=:]s$/) {
      $valueVerifyer = sub { $_[1] };
    } elsif ($optname =~ m/[=:!+]/) {
      die "Oops, internal error: Can't parse $optname format!";
    } else {
      $valueVerifyer = sub {
        die "Option $_[0] can only be set to 1!" unless $_[1] eq 1;
        $_[1]
      };
    }
    if (ref($store) eq '') {
      push @{$optionParseTable}, [
          $flags,
          $optname,
          sub {
            if (@_ == 2) {
#             print STDERR $_[0], " <= ", $_[1]//"undef","\n";
              &{$override}($store, &{$valueVerifyer}(@_));
              return undef;
            } else {
              my $value = $options->{$store};
#             print STDERR $_[0], " => ", $value//"undef","\n";
              return $value;
            }
          }
        ];
    } else { # ref($store) eq 'CODE'
      push @{$optionParseTable}, [
          $flags,
          $optname,
          sub {
            if (@_ == 2) {
#             print STDERR $_[0], " <= ", $_[1]//"undef","\n";
              &{$valueVerifyer}(@_);
              &{$store}($_[0], &{$valueVerifyer}(@_));
              return undef;
            } else {
              my $value = &{$store}(@_);
#             print STDERR $_[0], " => ", $value//"undef","\n";
              return $value;
            }
          }
        ];
    }
  }

  return $optionParseTable;
}

sub readConfigFile($$) {
  my ($options, $desc) = @_;
  my ($configFile, $textConfig);

  if ($desc eq 'mandatory') {
    $configFile = File::Spec->catfile($SYSTEMCONFIGDIR,
      "vncserver-config-mandatory");
  } elsif ($desc eq 'defaults') {
    $configFile = File::Spec->catfile($SYSTEMCONFIGDIR,
      "vncserver-config-defaults");
  } elsif ($desc eq 'user') {
    if (-f File::Spec->catfile($options->{'vncUserDir'}, "tigervnc.conf")) {
      # User provided TigerVNC configuration found
      $configFile = File::Spec->catfile($options->{'vncUserDir'}, "tigervnc.conf");
    } elsif (-f File::Spec->catfile($options->{'vncUserDir'}, "vnc.conf")) {
      # This is deprecated rename it to tigervnc.conf
      if (rename
	  File::Spec->catfile($options->{'vncUserDir'}, "vnc.conf")
	, File::Spec->catfile($options->{'vncUserDir'}, "tigervnc.conf")) {
	symlink "tigervnc.conf"
	  , File::Spec->catfile($options->{'vncUserDir'}, "vnc.conf");
	# User provided TigerVNC configuration found
	$configFile = File::Spec->catfile($options->{'vncUserDir'}, "tigervnc.conf");
      } else {
	# User provided TigerVNC configuration found
	$configFile = File::Spec->catfile($options->{'vncUserDir'}, "vnc.conf");
      }
    } elsif (-f File::Spec->catfile($options->{'vncUserDir'}, "config")) {
      # User provided TigerVNC configuration found
      $configFile = File::Spec->catfile($options->{'vncUserDir'}, "config");
      $textConfig = 1;
    }
  } else {
    die "Oops, $desc config file not known!";
  }

  return unless defined $configFile and -f $configFile;

  if ($textConfig) {
    my $configFileFh = IO::File->new($configFile, "r");

    my %lowCase;

    foreach my $optionParseEntry (@{&getOptionParseTable($options, $desc)}) {
      my ($flags, $optname, $store) = @{$optionParseEntry};
      next unless $flags & &OPT_CFGFILE;

      $optname =~ m/^([^:=]*)/;
      $optname = $1;

      foreach my $name (split(/\|/, $optname)) {
	$lowCase{lc($name)} = sub { &{$store}($name, @_) };
      }
    }
    while (my $line = <$configFileFh>) {
      chomp $line; $line =~ s/#.*$//;
      next if $line =~ /^\s*$/;
      if ($line =~ /^\s*(\w+)\s*=\s*(.*)$/) {
	my ($k, $v) = ($1, $2);
	chomp $v;
	if (defined $lowCase{lc($k)}) {
	  &{$lowCase{lc($k)}}($v);
	} else {
	  $options->{'vncServerExtraArgs'} = [grep {
	      $_->{'name'} ne $k
	    } @{$options->{'vncServerExtraArgs'}}];
	  push @{$options->{'vncServerExtraArgs'}}, {
	      name => $k,
	      src  => $desc,
	      args => [ "-$k", $v ]
	    };
	}
      } elsif ($line =~ m/^\s*(\w+)\s*$/) {
	my ($k) = ($1);
	if (defined $lowCase{lc($k)}) {
	  &{$lowCase{lc($k)}}(1);
	} else {
	  $options->{'vncServerExtraArgs'} = [grep {
	      $_->{'name'} ne $k
	    } @{$options->{'vncServerExtraArgs'}}];
	  push @{$options->{'vncServerExtraArgs'}}, {
	      name => $k,
	      src  => $desc,
	      args => [ "-$k" ]
	    };
	}
      } else {
	print STDERR "$PROG: Warning: Can't parse '$line' from config file $configFile!\n";
      }
    }
    $configFileFh->close();
  } else {
    &config::readConfigFile($options, $desc, $configFile);
  }
}

###############################################################################
#
# checkGeometryAndDepth simply makes sure that the geometry and depth values
# are sensible.
#

sub checkGeometryAndDepth {
  my ( $options ) = @_;

  my $wmDecorationWidth;
  my $wmDecorationHeight;

  if ($options->{'wmDecoration'} =~ /^(\d+)x(\d+)$/) {
    ($wmDecorationWidth, $wmDecorationHeight) = ($1,$2);
  } else {
    print STDERR "$PROG: wmDecoration $options->{'wmDecoration'} is invalid\n";
    exit 1;
  }
  if ($options->{'geometry'} =~ /^(\d+)x(\d+)$/) {
    my ( $width, $height ) = ( $1, $2 );
    if ($options->{'usedXDisplayDefaultsGeometry'}) {
      $width  -= $wmDecorationWidth;
      $height -= $wmDecorationHeight;
    }
    if (($width<1) || ($height<1)) {
      print STDERR "$PROG: geometry $options->{'geometry'} is invalid\n";
      exit 1;
    }

    $width  = int(($width +3)/4)*4;
    $height = int(($height+1)/2)*2;

    $options->{'geometry'} = "${width}x${height}";
  } else {
    print STDERR "$PROG: geometry $options->{'geometry'} is invalid\n";
    exit 1;
  }

  if ($options->{'pixelformat'}) {
    unless ($options->{'pixelformat'} =~ m/^(?:rgb|bgr)(\d)(\d)(\d)$/) {
      die 'Internal logic error !';
    }
    if (!defined $options->{'depth'}) {
      $options->{'depth'} = $1+$2+$3;
    } elsif ($options->{'depth'} < $1+$2+$3) {
      print STDERR "$PROG: Depth $options->{'depth'} and pixelformat $options->{'pixelformat'} are inconsistent.\n";
      exit 1;
    }
  }
  if (($options->{'depth'} < 8) || ($options->{'depth'} > 32)) {
    print STDERR "$PROG: Depth must be between 8 and 32.\n";
    exit 1;
  }
}

#
# getXDisplayDefaults uses xdpyinfo to find out the geometry, depth and pixel
# format of the current X display being used.  If successful, it sets the
# options as appropriate so that the X VNC server will use the same settings
# (minus an allowance for window manager decorations on the geometry).  Using
# the same depth and pixel format means that the VNC server won't have to
# translate pixels when the desktop is being viewed on this X display (for
# TrueColor displays anyway).
#

sub getXDisplayDefaults {
  my ( $options ) = @_;

  my (@lines, @matchlines, $defaultVisualId, $i);

  return if !defined($ENV{DISPLAY}) &&
            !defined($options->{'getDefaultFrom'});

  my $xdpyinfo = &getCommand("xdpyinfo");
  if (defined $ENV{DISPLAY}) {
    @lines = `$xdpyinfo 2>/dev/null`;
  } else {
    @lines = `$xdpyinfo $options->{'getDefaultFrom'} 2>/dev/null`;
  }

  return if ($? != 0);

  @matchlines = grep(/dimensions/, @lines);
  if (@matchlines) {
    my ($width, $height) = ($matchlines[0] =~ /(\d+)x(\d+) pixels/);
    $options->{'geometry'} = "${width}x${height}";
    $options->{'usedXDisplayDefaultsGeometry'} = 1;
  }

  @matchlines = grep(/default visual id/, @lines);
  if (@matchlines) {
    ($defaultVisualId) = ($matchlines[0] =~ /id:\s+(\S+)/);

    for ($i = 0; $i < @lines; $i++) {
      if ($lines[$i] =~ /^\s*visual id:\s+$defaultVisualId$/) {
        if (($lines[$i+1] !~ /TrueColor/) ||
            ($lines[$i+2] !~ /depth/) ||
            ($lines[$i+4] !~ /red, green, blue masks/)) {
          return;
        }
        last;
      }
    }

    return if ($i >= @lines);

    ( $options->{'depth'} ) = ($lines[$i+2] =~ /depth:\s+(\d+)/);
    my ($red,$green,$blue)
        = ($lines[$i+4]
           =~ /masks:\s+0x([0-9a-f]+), 0x([0-9a-f]+), 0x([0-9a-f]+)/);

    $red = hex($red);
    $green = hex($green);
    $blue = hex($blue);

    if ($red > $blue) {
      $red = int(log($red) / log(2)) - int(log($green) / log(2));
      $green = int(log($green) / log(2)) - int(log($blue) / log(2));
      $blue = int(log($blue) / log(2)) + 1;
      $options->{'pixelformat'} = "rgb$red$green$blue";
    } else {
      $blue = int(log($blue) / log(2)) - int(log($green) / log(2));
      $green = int(log($green) / log(2)) - int(log($red) / log(2));
      $red = int(log($red) / log(2)) + 1;
      $options->{'pixelformat'} = "bgr$blue$green$red";
    }
  }
}

#
# Check if tcp port is available
#
sub checkTCPPortUsed {
  my ($port) = @_;
  my $proto  = getprotobyname('tcp');

  socket(S, AF_INET, SOCK_STREAM, $proto) || die "$PROG: socket failed: $!";
  setsockopt(S, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die "$PROG: setsockopt failed: $!";
  if (!bind(S, sockaddr_in($port, INADDR_ANY))) {
    # print "$PROG: bind ($port) failed: $!\n";
    close(S);
    return 1;
  }
  close(S);
  return 0;
}

#
# checkDisplayNumberUsed checks if the given display number is used by vnc.
# A display number n is used if something is listening on the VNC server port
# (5900+n).
#

sub checkDisplayNumberUsed {
  my ($n) = @_;
  return &checkTCPPortUsed( 5900 + $n ) ||
         &checkTCPPortUsed( 6000 + $n );
}

#
# checkDisplayNumberAvailable checks if the given display number is available.
# A display number n is taken if something is listening on the VNC server port
# (5900+n) or the X server port (6000+n).
#

sub checkDisplayNumberAvailable {
  my ($n) = @_;

  return 0 if &checkDisplayNumberUsed($n);

  if (-e "/tmp/.X$n-lock") {
    print "\nWarning: $HOSTFQDN:$n is taken because of /tmp/.X$n-lock\n";
    print "Remove this file if there is no X server $HOSTFQDN:$n\n";
    return 0;
  }

  if (-e "/tmp/.X11-unix/X$n") {
    print "\nWarning: $HOSTFQDN:$n is taken because of /tmp/.X11-unix/X$n\n";
    print "Remove this file if there is no X server $HOSTFQDN:$n\n";
    return 0;
  }
  return 1;
}

#
# getDisplayNumber gets the lowest available display number.  A display number
# n is taken if something is listening on the VNC server port (5900+n) or the
# X server port (6000+n).
#

sub getDisplayNumber {
  foreach my $n (1..99) {
    return $n if &checkDisplayNumberAvailable($n);
  }

  print STDERR "$PROG: no free display number on $HOSTFQDN.\n";
  exit -1;
}

#
# quotedString returns a string which yields the original string when parsed
# by a shell.
#

sub quotedString {
  my ($in) = @_;
  $in =~ s/\'/\'\"\'\"\'/g;
  return "'$in'";
}

sub pidFile {
  my ($options,$usedDisplay) = @_;
  $usedDisplay = $options->{'displayNumber'} unless defined $usedDisplay;
  return "$options->{'vncUserDir'}/$HOSTFQDN:$usedDisplay.pid";
}

sub x509CertFiles {
  my ($options) = @_;
  return (
    "$options->{'vncUserDir'}/${HOSTFQDN}-SrvCert.pem",
    "$options->{'vncUserDir'}/${HOSTFQDN}-SrvKey.pem");
}

sub desktopLog {
  my ($options,$usedDisplay) = @_;
  $usedDisplay = $options->{'displayNumber'} unless defined $usedDisplay;
  return File::Spec->catfile($options->{'vncUserDir'}, "$HOSTFQDN:$usedDisplay.log");
}

sub cleanStale {
  my ($options, $usedDisplay, $stale) = @_;
  my $pidFile  = pidFile($options,$usedDisplay);
  my @x11Locks = ("/tmp/.X$usedDisplay-lock", "/tmp/.X11-unix/X$usedDisplay");

  # vnc pidfile stale
  my $msg = "";
  if (-e $pidFile) {
    unless ($options->{'dry-run'} || unlink($pidFile) || $! == &ENOENT) {
      print STDERR "$PROG: Can't clean stale pidfile '$pidFile': $!\n";
    } elsif ($stale) {
      print "Cleaning stale pidfile '$pidFile'!\n";
    }
  }
  if (!$stale || !&checkDisplayNumberUsed($usedDisplay)) {
    foreach my $entry (grep { -e $_ } @x11Locks) {
      unless ($options->{'dry-run'} || unlink($entry) || $! == &ENOENT) {
        print STDERR "$PROG: Can't clean stale x11 lock '$entry': $!\n";
      } else {
        print "Cleaning stale x11 lock '$entry'!\n";
      }
    }
  }
}

sub runningUserVncservers {
  my ($options) = @_;
  my %runningUserVncservers = ();

  my $d = DirHandle->new($options->{'vncUserDir'});
  if (defined $d) {
    while (defined(my $entry = $d->read)) {
      next unless $entry =~ m/^\Q$HOSTFQDN\E:(\d+)\.pid$/;
      my $usedDisplay = $1;
      my $pidFile     = File::Spec->catfile($options->{'vncUserDir'}, $entry);
      my $pidFileFh   = IO::File->new($pidFile, "r");
      unless (defined $pidFileFh) {
        print STDERR "$PROG: Can't open pid file '$pidFile': $!\n";
        next;
      }
      unless (($pidFileFh->getline()//"") =~ m/^([0-9]+)$/) {
        print STDERR "$PROG: Can't parse pid file '$pidFile'!\n";
        next;
      }
      my $pid   = int($1);
      my $stale = !kill(0, $pid);
      if ($options->{'cleanstale'} && $stale) {
        cleanStale($options, $usedDisplay, 1);
        next;
      }
      my $DISPLAY = -e "/tmp/.X11-unix/X${usedDisplay}"
        ? ":${usedDisplay}"
        : "$HOSTFQDN:${usedDisplay}";
      my $rfbport     = 5900 + $usedDisplay;
      my $logFile     = desktopLog($options, $usedDisplay);
      my $logFileFh   = File::ReadBackwards->new($logFile);

      if (defined $logFileFh) {
        my $line;
        while (defined ($line = $logFileFh->readline)) {
          chomp $line;
          if ($line =~ m/Listening for VNC connections.* port\s+(\d+)/) {
            $rfbport = $1; last;
          }
        }
      }
      # running vnc if !$options->{'cleanstale'}
      $runningUserVncservers{$usedDisplay} = {
          'name'        => "$HOSTFQDN:$usedDisplay",
          'pid'         => $pid,
          'DISPLAY'     => $DISPLAY,
          'usedDisplay' => $usedDisplay,
          'rfbport'     => $rfbport,
          'stale'       => $stale,
        };
    }
    undef $d;
  }
  return \%runningUserVncservers;
}

#
# killXvncServer
#

sub killXvncServer {
  my ($options, $runningUserVncservers, $vncs) = @_;

  $SIG{'CHLD'} = 'IGNORE';
  my $retval = 0;
  foreach my $vnc (@{$vncs}) {
    my $stale = 0;
    my $pid   = $runningUserVncservers->{$vnc}->{'pid'};

    next unless defined $pid;
    print "Killing Xtigervnc process ID $pid...";
    unless ($options->{'dry-run'}) {
      if (kill('TERM', $pid)) {
        my $i = 10;
        for (; $i >= 0; $i = $i-1) {
          last unless kill(0, $pid);
          usleep 100000;
        }
        if ($i >= 0) {
          print " success!\n";
        } else {
          $retval = 1;
          print " which seems to be deadlocked. Using SIGKILL!\n";
          unless (kill('KILL', $pid) || $! == &ESRCH) {
            print STDERR "Can't kill '$pid': $!\n";
            next;
          }
        }
      } elsif ($! == &ESRCH) {
        print " which was already dead\n";
        $stale = 1;
      } else {
        $retval = 1;
        print STDERR "\nCan't kill '$pid': $!\n";
        next;
      }
    }
    &cleanStale($options,$vnc,$stale);

    # If option -clean is given, also remove the logfile
    if (!$options->{'dry-run'} && $options->{'clean'}) {
      my $desktopLog = &desktopLog($options, $vnc);
      unless (unlink($desktopLog) || $! == &ENOENT) {
        $retval = 1;
        print STDERR "Can't remove '$desktopLog': $!\n";
      }
    }
  }
  $SIG{'CHLD'} = 'DEFAULT';
  return $retval;
}

sub listXvncServer {
  my ($fh, $options, $runningUserVncservers, $vncs) = @_;

  print $fh
    "\n".
    "TigerVNC server sessions:\n".
    "\n".
    "X DISPLAY #\tRFB PORT #\tPROCESS ID\n";
  foreach my $vnc (@{$vncs}) {
    next unless defined $runningUserVncservers->{$vnc};
    my $stale = $runningUserVncservers->{$vnc}->{'stale'}
      ? " (stale)" : "";
    my $rfbport = $runningUserVncservers->{$vnc}->{'rfbport'};
    my $pid = $runningUserVncservers->{$vnc}->{'pid'};
    print $fh ":".$vnc."\t\t".$rfbport."\t\t".$pid.$stale."\n";
  }
}

# Make an X server cookie
sub CreateMITCookie {
  my ( $options ) = @_;
  my $displayNumber  = $options->{'displayNumber'};
  my $xauthorityFile = $options->{'xauthorityFile'};
  my $cookie = `mcookie`; # try mcookie

  unless (defined $cookie) {
    # mcookie failed => make an X server cookie the old fashioned way
    srand(time+$$+unpack("L",`cat $options->{'vncPasswdFile'}`));
    $cookie = "";
    for (1..16) {
      $cookie .= sprintf("%02x", int(rand(256)));
    }
  } else {
    chomp $cookie;
  }
  system(getCommand("xauth"), "-f", "$xauthorityFile", "add", "$HOSTFQDN:$displayNumber", ".", "$cookie");
  system(getCommand("xauth"), "-f", "$xauthorityFile", "add", "$HOST/unix:$displayNumber", ".", "$cookie");
}

# Make sure the user has a password.
sub CreateVNCPasswd {
  my ( $options ) = @_;

  # Check whether VNC authentication is enabled, and if so, prompt the user to
  # create a VNC password if they don't already have one.
  return if !$options->{'vncAuthEnabled'} ||
             $options->{'passwordArgSpecified'};
  my $vncPasswdFile = $options->{'vncPasswdFile'};
  my $st = stat($vncPasswdFile);

  if (!defined($st) || ($st->mode & 077)) {
    print "\nYou will require a password to access your desktops.\n\n";
    unless (unlink($vncPasswdFile) || $! == &ENOENT) {
      print STDERR "Can't remove old vnc passwd file '$vncPasswdFile': $!!\n";
      exit 1;
    }
    system(getCommand("tigervncpasswd"), $vncPasswdFile);
    exit 1 if (($? >> 8) != 0);
  }
}

# Make sure the user has a x509 certificate.
sub CreateX509Cert {
  my ( $options ) = @_;

  # Check whether X509 encryption is enabled, and if so, create
  # a self signed certificate if not already present or specified
  # on the command line.
  return if !$options->{'x509CertRequired'} ||
            defined $options->{'X509Cert'} ||
            defined $options->{'X509Key'};
  ($options->{'X509Cert'}, $options->{'X509Key'}) =
    &x509CertFiles($options);

  my $st = stat($options->{'X509Key'});
  if (!defined($st) || ($st->mode & 077) || !-f $options->{'X509Cert'}) {
    print "\nYou will require a certificate to use X509None, X509Vnc, or X509Plain.\n";
    print "I will generate a self signed certificate for you in $options->{'X509Cert'}.\n\n";
    unless (unlink($options->{'X509Cert'}) || $! == &ENOENT) {
      print STDERR "Can't remove old X509Cert file '$options->{'X509Cert'}': $!!\n";
      exit 1;
    }
    unless (unlink($options->{'X509Key'}) || $! == &ENOENT) {
      print STDERR "Can't remove old X509Key file '$options->{'X509Key'}': $!!\n";
      exit 1;
    }
    my $toSSLFh;
    my @CMD = split(/\s+/, $options->{'sslAutoGenCertCommand'});
    $CMD[0] = &getCommand($CMD[0]);
    push @CMD, "-config", "-" unless grep { $_ eq "-config" } @CMD;
    push @CMD, "-out", $options->{'X509Cert'} unless grep { $_ eq "-out" } @CMD;
    push @CMD, "-keyout", $options->{'X509Key'} unless grep { $_ eq "-keyout" } @CMD;
    unless (defined open($toSSLFh, "|-", @CMD)) {
      print STDERR "Can't start openssl pipe: $!!\n";
      exit 1;
    }
    my $configSSLFh;
    unless (defined open($configSSLFh, "<", "$SYSTEMCONFIGDIR/ssleay.cnf")) {
      print STDERR "Can't open openssl configuration template $SYSTEMCONFIGDIR/ssleay.cnf: $!\n";
      exit 1;
    }
    while (my $line = <$configSSLFh>) {
      $line =~ s/\@HostName\@/$HOSTFQDN/;
      print $toSSLFh $line;
    }
    close $configSSLFh;
    close $toSSLFh;
    if ($? != 0) {
      unlink $options->{'X509Cert'};
      unlink $options->{'X509Key'};
      print STDERR "The openssl command ", join(' ', @CMD), " failed: $?\n";
      exit 1;
    }
  }
}

#
# Load a session desktop file
#
sub loadXSession {
  my ($name) = @_;
  my ($file, $found_group, %session);

  $file = "/usr/share/xsessions/$name.desktop";

  if (!-f $file) {
    return;
  }

  my $fh;
  if (!open($fh, $file)) {
    warn "Could not open session desktop file $file: $!";
    return;
  }

  $found_group = 0;
  while (my $line = <$fh>) {
    next if $line =~ /^#/;
    next if $line =~ /^\s*$/;

    chomp $line;

    if (!$found_group) {
      next if $line ne "[Desktop Entry]";
      $found_group = 1;
      next;
    } else {
      last if $line =~ /^\[/;
    }

    my ($key, $value) = $line =~ /^\s*([]A-Za-z0-9_@\-\[]+)\s*=\s*(.*)$/;
    if (!$key) {
      warn "Invalid session desktop file $file";
      close($fh);
      return;
    }

    $value =~ s/\\s/ /g;
    $value =~ s/\\n/\n/g;
    $value =~ s/\\t/\t/g;
    $value =~ s/\\r/\r/g;
    $value =~ s/\\\\/\\/g;

    $session{$key} = $value;
#   print STDERR "$key => $value\n";
  }

  close($fh);

  unless (defined $session{'Exec'}) {
    warn "Invalid session desktop file $file does not contain a command to start the session!";
    close($fh);
    return;
  }

  return $session{'Exec'};
}

# Now start the X VNC Server
sub startXvncServer {
  my ($options) = @_;
  my $vncStartup = $options->{'vncStartup'};
  my $desktopLog = &desktopLog($options);
  my $pidFile    = &pidFile($options);

  unless (defined $options->{'rfbport'}) {
    $options->{'rfbport'} = 5900 + $options->{'displayNumber'};
  }

  # Make sure the user has a password if required.
  &CreateVNCPasswd($options);
  # Make sure the user has a x509 certificate if required.
  &CreateX509Cert($options);
  &CreateMITCookie($options);

  my $pidFileFh  = IO::File->new($pidFile, "w", 0644);
  unless (defined $pidFileFh) {
    print STDERR "$PROG: Can't create pid file '$pidFile': $!\n";
    exit 1;
  }

  my $desktopLogFh = IO::File->new($desktopLog, "a+");
  seek($desktopLogFh, 0, SEEK_END);

  my $xvncServerPid = fork();
  if ($xvncServerPid == 0) {
    # I am the child
    $desktopLogFh->close();
    my @cmd = (getCommand("Xtigervnc"));
    push @cmd, ":".$options->{'displayNumber'};
    foreach my $optionParseEntry (@{&getOptionParseTable($options)}) {
      my ($flags, $optname, $store) = @{$optionParseEntry};
      next unless $flags & &OPT_XTIGERVNC;
      $optname =~ m/^([^:=|]*)/;
      my $name = $1;
      my $value = &{$store}($name);
      if ($optname =~ m/:/) {
        push @cmd, "-$name=$value" if defined $value;
      } elsif ($optname =~ m/=/) {
        push @cmd, "-$name", $value if defined $value;
      } else {
        die "Oops, can't parse $optname format!";
      }
    }
#   push @cmd, '-pn';
    push @cmd, map { @{$_->{'args'}} } @{$options->{'vncServerExtraArgs'}};

    print join(" ",@cmd), "\n" if $options->{'verbose'};
    open(OLDERR, '>&', \*STDERR); # save old STDERR
    open(STDOUT, '>>', $desktopLog);
    open(STDERR, '>>', $desktopLog);
    STDERR->autoflush(1);
    STDOUT->autoflush(1);
    exec {$cmd[0]} (@cmd) or
      print OLDERR "$PROG: Can't exec '".$cmd[0]."': $!\n";
    exit 1;
  } elsif ($xvncServerPid < 0) {
    # Failed to fork
    print STDERR "$PROG: failed to fork: $!\n";
    exit 1;
  }
  $pidFileFh->print($xvncServerPid."\n");
  $pidFileFh->close();

  my $runningUserVncservers = {
      $options->{'displayNumber'} => {
          'name'        => "$HOSTFQDN:".$options->{'displayNumber'},
          'pid'         => $xvncServerPid,
          'usedDisplay' => $options->{'displayNumber'}
        }
    };
  # Wait for Xtigervnc to start up
  {
    my $i = 300;
    for (; $i >= 0; $i = $i-1) {
      last if &checkTCPPortUsed($options->{'rfbport'});
      if ($xvncServerPid == waitpid($xvncServerPid, WNOHANG)) { $i = -2; last; }
      usleep 100000;
    }
    for (; $i >= 0; $i = $i-1) {
      last if -e "/tmp/.X11-unix/X$options->{'displayNumber'}" ||
              &checkTCPPortUsed(6000 + $options->{'displayNumber'});
      if ($xvncServerPid == waitpid($xvncServerPid, WNOHANG)) { $i = -2; last; }
      usleep 100000;
    }
    $i = -2 unless kill(0, $xvncServerPid);
    if ($i < 0) {
      if (kill(0, $xvncServerPid)) {
        &killXvncServer($options, $runningUserVncservers, [$options->{'displayNumber'}]);
      } else {
        &cleanStale($options,$options->{'displayNumber'},0);
      }
      my $header = "=================== tail $desktopLog ===================";
      print STDERR "\n${header}\n";
      while (my $line = <$desktopLogFh>) {
        print STDERR $line;
      }
      print STDERR "\n".("=" x length $header)."\n\n";
      print STDERR "$PROG: ".getCommand("Xtigervnc")." did not start up, please look into '$desktopLog' to determine the reason! $i\n";
      exit -1;
    }
  }
  # If the unix domain socket exists then use that (DISPLAY=:n) otherwise use
  # TCP (DISPLAY=host:n)
  if (-e "/tmp/.X11-unix/X$options->{'displayNumber'}" ) {
    $ENV{DISPLAY}= ":$options->{'displayNumber'}";
  } else {
    $ENV{DISPLAY}= "$HOSTFQDN:$options->{'displayNumber'}";
  }
  $ENV{VNCDESKTOP} = $options->{'desktopName'};
  print "\nNew '$options->{'desktopName'}' desktop at $ENV{DISPLAY} on machine $HOSTFQDN\n\n";

  if (defined $vncStartup) {
    # Run the X startup script.
    print "Starting applications specified in $vncStartup\n";
    print "Log file is $desktopLog\n\n";
  } elsif ($options->{'fg'} || $options->{'autokill'}) {
    # Nothing to start and I should also kill the Xtigervnc server when the
    # Xtigervnc-session terminates. Well, lets do so. What a pointless exercise.
    &killXvncServer($options, $runningUserVncservers, [$options->{'displayNumber'}]);
  }

  my @cmd = ("xtigervncviewer");
  push @cmd, "-SecurityTypes", $options->{'SecurityTypes'};
  push @cmd, "-X509CA", $options->{'X509Cert'} if $options->{'x509CertRequired'};
  push @cmd, "-passwd", $options->{'vncPasswdFile'} if $options->{'vncAuthEnabled'};
  my $connection = $options->{'localhost'} =~ m/^(?:yes|true|1)$/i
    ? ":" : "$HOSTFQDN:";
  if ($options->{'rfbport'} >= 5900 && $options->{'rfbport'} <= 5999) {
    $connection .= $options->{'rfbport'}-5900;
  } else {
    $connection .= $options->{'rfbport'};
  }
  push @cmd, $connection;
  print "Use ".join(" ", @cmd)." to connect to the VNC server.\n\n";

  seek($desktopLogFh, 0, SEEK_END);

  pipe RH, WH or die "Can't open pipe: $!";
  my $childPid = defined $vncStartup
    ? ($options->{'fg'} ? 0 : fork())
    : $$;

  if ($childPid == 0) {
    # I am the child
    $desktopLogFh->close() unless $options->{'fg'};
    my @cmd = ($vncStartup);
    push @cmd, @{$options->{'session'}};
    print join(" ",@cmd), "\n" if $options->{'verbose'};

    open(OLDOUT, '>&', \*STDOUT); # save old STDOUT
    open(OLDERR, '>&', \*STDERR); # save old STDERR
    open(STDOUT, '>>', $desktopLog);
    open(STDERR, '>>', $desktopLog);
    STDERR->autoflush(1);
    STDOUT->autoflush(1);
    OLDERR->autoflush(1);
    OLDOUT->autoflush(1);

    $SIG{'ALRM'} = sub {
        open(OLDERR, '>', '/dev/null') unless $options->{'fg'};
        open(OLDOUT, '>', '/dev/null') unless $options->{'fg'};
        syswrite WH, "OK"; close WH;
        $SIG{'ALRM'} = 'DEFAULT';
      };
    # Wait for three seconds for erros to appear and to propagate to
    # our parent if not in -fg mode.
    alarm 3 unless $options->{'fg'};
    $! = 0;
    if (system {$cmd[0]} (@cmd)) {
      if ($!) {
        alarm 0; # this must not be before the if ($!) condition
        print OLDERR "\n$PROG: Can't start ",
          join(" ", map { &quotedString($_); } @cmd), ": $!!\n";
      } else {
        alarm 0; # this must not be before the if ($!) condition
        print OLDERR "\n$PROG: Failed command ",
          join(" ", map { &quotedString($_); } @cmd), ": $?!\n";
      }
      $SIG{'ALRM'} = 'DEFAULT';
      syswrite WH, "ERR"; close WH;
    } else {
      &{$SIG{'ALRM'}} if ref($SIG{'ALRM'}) eq 'CODE';
    }
    if ($options->{'fg'} || $options->{'autokill'}) {
      if (kill(0, $xvncServerPid)) {
        &killXvncServer($options, $runningUserVncservers, [$options->{'displayNumber'}]);
      } else {
        &cleanStale($options,$options->{'displayNumber'},0);
      }
    }
    exit 0 unless $options->{'fg'};
    open(STDOUT, '>&', \*OLDOUT); # restore STDOUT
    open(STDERR, '>&', \*OLDERR); # restore STDERR
  } elsif ($childPid < 0) {
    # Failed to fork
    print STDERR "$PROG: failed to fork: $!\n";
    exit -1;
  }
  if (defined $vncStartup) {
    # I am the parent
    close WH;
    my $status;
    $status = 'ERR' unless defined sysread RH, $status, 3;
    unless ($status eq 'OK') {
      if (kill(0, $xvncServerPid)) {
        &killXvncServer($options, $runningUserVncservers, [$options->{'displayNumber'}]);
      } else {
        &cleanStale($options,$options->{'displayNumber'},0);
      }
      my $header = "=================== tail $desktopLog ===================";
      print STDERR "\n${header}\n";
      while (my $line = <$desktopLogFh>) {
        print STDERR $line;
      }
      print STDERR "\n".("=" x length $header)."\n\n";
      print STDERR "Starting applications specified in $vncStartup has failed.\n";
      print STDERR "Maybe try something simple first, e.g.,\n";
      print STDERR "\ttigervncserver -xstartup /usr/bin/xterm\n";
      exit -1;
    }
  }
  exit 0;
}

sub parseCmdLine {
  my ($options) = @_;
  my $rc = 1;

  # Seperate session args
  {
    my @newargv;
    my $ref = \@newargv;

    foreach my $entry (@ARGV) {
      if ($entry eq '--') {
        $options->{'session'} = [];
        $ref = $options->{'session'};
      } else {
        push @$ref, $entry;
      }
    }
    @ARGV = @newargv;
  }
  # Command line parsing
  {
    my (%options, %parameters);
    foreach my $optionParseEntry (@{&getOptionParseTable($options, "cmdline")}) {
      my ($flags, $optname, $store) = @{$optionParseEntry};
      next unless $flags & &OPT_TIGERVNCSERVER;
      my $opttype;
      if ($optname =~ m/^(.*)([:=][bis])$/) {
        $optname = $1;
        $opttype = $2;
      } else {
        $opttype = '';
      }
      foreach my $name (split(/\|/, $optname)) {
        if ($flags & &OPT_PARAM) {
          $parameters{lc($name)} = [ $name.$opttype, $store ];
        } else {
          $options{$name} = [ $name.$opttype, $store ];
        }
      }
    }
    my $pendingExtraArg = undef;
    while (@ARGV) {
      my $arg = shift @ARGV;
      my $opt = undef;
      my $par = undef;
      my $val = undef;

      if ($arg =~ /^((?:[^-:@=.0-9][\w.-]+@)?(?:\d+\.\d+\.\d+\.\d+|\[[0-9a-fA-F:.]+\]|[^-:@=.0-9][^=:@]*))?(?::(\d+(?:\.\d+)?|\*))?$/) {
#       print STDERR "==> $arg <==\n";
        my ($host, $nr) = ($1, $2);
        $host =~ s{\[([0-9a-fA-F:.]+)\]$}{$1} if defined $host;
        $nr   =~ s{\.\d+$}{}                  if defined $nr;
        if (!defined $host) {
          $options->{'displayHost'} = $HOSTFQDN;
        } elsif ($host eq "localhost") {
          $options->{'localhost'}   = 'yes';
          $options->{'displayHost'} = $HOSTFQDN;
        } else {
          $options->{'displayHost'} = $host;
        }
        if (defined $nr) {
          $options->{'displayNumber'} = $nr;
        }
        undef $pendingExtraArg;
        next;
      } elsif ($arg =~ m/^(-([a-zA-Z]))=(.*)$/) {
        $arg = $1;
        $opt = $2;
        $val = $3;
      } elsif ($arg =~ m/^-([a-zA-Z])$/) {
        $opt = $1;
      } elsif ($arg =~ m/^(--?([a-zA-Z][^=]+))=(.*)$/) {
        $arg = $1;
        $opt = $par = $2;
        $val = $3;
      } elsif ($arg =~ m/^--?([a-zA-Z][^=]+)$/) {
        $opt = $par = $1;
      } elsif ($arg =~ m/^([a-zA-Z][^=]+)=(.*)$/) {
        $par = $1;
        $val = $2;
      } elsif (defined $pendingExtraArg) {
        push @{$pendingExtraArg->{'args'}}, $arg;
	undef $pendingExtraArg;
        next;
      } else {
        $rc = 0;
        print STDERR "$PROG: Option $arg: Unrecognized!\n\n";
        last;
      }
      my $optInfo = $parameters{lc($par)} // $options{$opt//'undef'};
      $opt = $par;
      if (defined $optInfo) {
        if ($optInfo->[0] =~ m/=/) {
          $val = shift @ARGV unless defined $val;
          unless (defined $val) {
            $rc = 0;
            print STDERR "$PROG: Option $arg: Missing option value!\n\n";
            last;
          }
        } elsif ($optInfo->[0] =~ m/:b$/) {
          if (@ARGV || !defined $val) {
            $val = shift @ARGV;
            unless ($val =~ m/^(0|1|no|yes|false|true)$/) {
              unshift @ARGV, $val;
              undef $val;
            }
          }
        } elsif ($optInfo->[0] =~ m/:s$/) {
          if (@ARGV || !defined $val) {
            $val = shift @ARGV;
            if ($val =~ m/^-/) {
              unshift @ARGV, $val;
              undef $val;
            }
          }
        } else {
          if (defined $val) {
            $rc = 0;
            print STDERR "$PROG: Option $arg: Does not take an option value!\n\n";
            last;
          }
          $val = 1;
        }
        unless (eval { &{$optInfo->[1]}($opt, $val//""); 1 }) {
          my $errorText = $@;
          $errorText =~ s/ at .* line \d+\.$//;
          chomp $errorText;
          print STDERR "$PROG: Option $arg: $errorText\n\n";
          $rc = 0;
          last;
        }
        undef $pendingExtraArg;
      } else {
	$options->{'vncServerExtraArgs'} = [grep {
	    $_->{'name'} ne $opt
	  } @{$options->{'vncServerExtraArgs'}}];
	$pendingExtraArg = {
	    name => $opt,
	    src  => 'cmdline',
	    args => [ $arg ]
	  };
        push @{$options->{'vncServerExtraArgs'}}, $pendingExtraArg;
	undef $pendingExtraArg if defined $val;
      }
    }
  }
  return $rc;
}

#
# usage
#

sub usage {
  my ($err) = @_;

  my $prefix = " " x length("  $PROG ");
  my $msg = "usage:\n".
    "  $PROG -help|-h|-?            This help message. Further help in tigervncserver(1).\n\n".

    "  $PROG [:<number>]            X11 display for VNC server\n".
    $prefix."[-dry-run]             Take no real action\n".
    $prefix."[-verbose]             Be more verbose\n".
    $prefix."[-useold]              Only start VNC server if not already running\n".
    $prefix."[-name <desktop-name>] VNC desktop name\n".
    $prefix."[-depth <depth>]       Desktop bit depth (8|16|24|32)\n".
    $prefix."[-pixelformat          X11 server pixel format\n".
    $prefix."  rgb888|rgb565|rgb332   blue color channel encoded in lower bits\n".
    $prefix." |bgr888|bgr565|bgr233]  red color channel encoded in lower bits\n".
    $prefix."[-geometry <dim>]      Desktop geometry in <width>x<height>\n".
    $prefix."[-xdisplaydefaults]    Get geometry and pixelformat from running X\n".
    $prefix."[-wmDecoration <dim>]  Shrink geometry from xdisplaydefaults by dim\n".
    $prefix."[-localhost yes|no]    Only accept VNC connections from localhost\n".
    $prefix."[-rfbport port]        TCP port to listen for RFB protocol\n".
    $prefix."[-fg]                  No daemonization and\n".
    $prefix."                       kill the VNC server after its X session has terminated\n".
    $prefix."[-autokill]            Kill the VNC server after its X session has terminated\n".
    $prefix."[-noxstartup]          Do not run the Xtigervnc-session script\n".
    $prefix."[-xstartup]            Specify the script to start after launching Xtigervnc\n".
    $prefix."[-fp fontpath]         Colon separated list of font locations\n".
    $prefix."[-cleanstale]          Do not choke on a stale lockfile\n".
    $prefix."[-SecurityTypes]       Comma list of security types to offer (None, VncAuth,\n".
    $prefix."                       Plain, TLSNone, TLSVnc, TLSPlain, X509None, X509Vnc,\n".
    $prefix."                       X509Plain). On default, offer only VncAuth.\n".
    $prefix."[-PlainUsers]          In case of security types Plain, TLSPlain, and X509Plain,\n".
    $prefix."                       this options specifies the list of authorized users.\n".
    $prefix."[-PAMService]          In case of security types Plain, TLSPlain, and X509Plain,\n".
    $prefix."                       this options specifies the service name for PAM password\n".
    $prefix."                       validation (default vnc if present otherwise tigervnc).\n".
    $prefix."[-PasswordFile]        Password file for security types VncAuth, TLSVnc, and X509Vnc.\n".
    $prefix."                       The default password file is ~/.vnc/passwd\n".
    $prefix."[-passwd]              Alias for PasswordFile\n".
    $prefix."[-rfbauth]             Alias for PasswordFile\n".
    $prefix."[-X509Key]             Path to the key of the X509 certificate in PEM format. This\n".
    $prefix."                       is used by the security types X509None, X509Vnc, and X509Plain.\n".
    $prefix."[-X509Cert]            Path to the X509 certificate in PEM format. This is used by\n".
    $prefix."                       the security types X509None, X509Vnc, and X509Plain.\n".
    $prefix."<X11-options ...>      Further options for Xtigervnc(1)\n".
    $prefix."[-- sessiontype]       Specify which X session desktop should be started\n\n".

    "  $PROG -kill                  Kill a VNC server\n".
    $prefix."[:<number>|:*]         VNC server to kill, * for all\n".
    $prefix."[-dry-run]             Take no real action\n".
    $prefix."[-verbose]             Be more verbose\n".
    $prefix."[-clean]               Also clean log files of VNC session\n\n".

    "  $PROG -list                  List VNC server sessions\n".
    $prefix."[:<number>|:*]         VNC server to list, * for all\n".
    $prefix."[-cleanstale]          Do not list stale VNC server sessions\n\n";

  if ($err) {
    print STDERR $msg;
  } else {
    print STDOUT $msg;
  }
  exit($err ? 1 : 0);
}

sub main {
  #
  # Global options.  You may want to configure some of these for your site.
  # Use /etc/vnc.conf and ~/.vnc/vnc.conf for this purpose.
  #
  my $options = {
# Values that are documented in /etc/tigervnc/vncserver-config-*
## Values declared as system values in /etc/tigervnc/vncserver-config-*
      fontPath                  => undef,
      PAMService                =>
        undef, # Use vnc if /etc/pam.d/vnc exists. Otherwise,
               # use our own /etc/pam.d/tigervnc as fallback.
      sslAutoGenCertCommand     =>
        "openssl req -newkey ec:$SYSTEMCONFIGDIR/ecparams.pem -x509 -days 2190 -nodes",
## Values declared as user values in /etc/tigervnc/vncserver-config-*, i.e.,
## values that are intended to be overwritten by ~/.vnc/tigervnc.conf.
      vncUserDir                =>
        File::Spec->catfile($ENV{HOME}, ".vnc"),
      vncPasswdFile             =>
        undef, # later derived from vncUserDir
      vncStartup                =>
        "/etc/X11/Xtigervnc-session",
      xauthorityFile            =>
        $ENV{XAUTHORITY} ||
        File::Spec->catfile($ENV{HOME}, ".Xauthority"),
      desktopName               => undef,
      wmDecoration              =>
        "8x64", # a guess at the typical size for a window manager decoration
      geometry                  => "1280x1024",
      depth                     => 24,
      pixelformat               => undef,
      getDefaultFrom            => undef,
      rfbwait                   => 30000,
      rfbport                   => undef,
      localhost                 => undef,
      SecurityTypes             =>
        undef, # later derived depening on localhost setting
      PlainUsers                =>
        undef, # later derived from /usr/bin/id -u -n
      X509Cert                  =>
        undef, # auto generated if absent and stored in
               # ~/.vnc/${HOSTFQDN}-SrvCert.pem
      X509Key                   =>
        undef, # auto generated if absent and stored in
               # ~/.vnc/${HOSTFQDN}-SrvKey.pem
      session                   => undef,
# Undocumented values
      vncServerExtraArgs        => [],
      cleanstale                => 0,
      clean                     => 0,
      displayNumber             => undef,
      displayHost               => undef,
      src                       => {},
    };

  #
  # Then source in configuration files, first the site wide one and then the
  # user specific one.
  #
  &readConfigFile($options, "defaults");

  if (!(-d $options->{'vncUserDir'})) {
    # Create the user's vnc directory if necessary.
    if (-e $options->{'vncUserDir'}) {
      print STDERR "$PROG: Could not create $options->{'vncUserDir'}, file exists but is not a directory.\n";
      exit 1;
    }
    if (!mkpath ($options->{'vncUserDir'}, 0, 0755)) {
      print STDERR "$PROG: Could not create $options->{'vncUserDir'}.\n";
      exit 1;
    }
  }
  my $vncStartup = $options->{'vncStartup'};
  $options->{'vncStartup'} = '__AUTO__' if defined $vncStartup;

  &readConfigFile($options, "user");

  {
    my $rc = &parseCmdLine($options);
    &usage(!$rc) if (!$rc || $options->{'help'});
  }

  if (defined $options->{'displayHost'}) {
    if (!$options->{'kill'} && !$options->{'list'}) {
      &usage(1) if ($options->{'displayNumber'}||"") eq '*';
    }
  } else {
    $options->{'displayHost'} = $HOSTFQDN;
  }
  if ($options->{'displayHost'} ne $HOST &&
      $options->{'displayHost'} ne $HOSTFQDN) {
    my @cmdPrefix = ("ssh", "$options->{'displayHost'}", "tigervncserver");
    # Get rid of possible user@ in front of displayHost.
    $options->{'displayHost'} =~ s/^[^@]*@//;
    my @cmd;
    push @cmd, "-dry-run" if $options->{'dry-run'};
    if ( $options->{'kill'} ) {
      push @cmd, "-kill";
      push @cmd, ":$options->{'displayNumber'}" if defined $options->{'displayNumber'};
      push @cmd, "-clean" if ($options->{'clean'});
    } elsif ( $options->{'list'} ) {
      push @cmd, "-list";
      push @cmd, ":$options->{'displayNumber'}" if defined $options->{'displayNumber'};
      push @cmd, "-cleanstale" if ($options->{'cleanstale'});
    } else {
      push @cmd, ":$options->{'displayNumber'}" if defined $options->{'displayNumber'};

      my $userOptions = {};
      while (my ($key, $value) = each %{$options}) {
        my $src = $options->{'src'}->{$key} // "undef";
        if ($src eq 'user' || $src eq 'cmdline') {
          $userOptions->{$key} = $value;
        }
      }
      $userOptions->{'vncServerExtraArgs'} = [grep {
	  $_->{'src'} eq 'user' || $_->{'src'} eq 'cmdline';
	} @{$options->{'vncServerExtraArgs'}}];
      foreach my $optionParseEntry (@{&getOptionParseTable($userOptions)}) {
        my ($flags, $optname, $store) = @{$optionParseEntry};
        next unless $flags & &OPT_TIGERVNCSERVER;
        $optname =~ m/^([^:=|]*)/;
        my $name = $1;
        my $value = &{$store}($name);
        if ($optname =~ m/:/) {
          push @cmd, "-$name=$value" if defined $value;
        } elsif ($optname =~ m/=/) {
          push @cmd, "-$name", $value if defined $value;
	} elsif (!($optname =~ m/[:=]/)) {
          push @cmd, "-$name" if defined $value;
        } else {
          die "Oops, can't parse $optname format!";
        }
      }
      push @cmd, map { @{$_->{'args'}} } @{$userOptions->{'vncServerExtraArgs'}};
      if ($#{$userOptions->{'session'}} >= 0) {
        push @cmd, '--';
        push @cmd, @{$userOptions->{'session'}};
      }
    }
    @cmd = (@cmdPrefix, map { &quotedString($_); } @cmd);
    print join(" ",@cmd), "\n" if $options->{'verbose'};
    if (system (@cmd)) {
      print STDERR "\n$PROG: Command '", join(" ", @cmd), "' failed: $?\n";
      exit -1;
    }
    if (!$options->{'kill'} && !$options->{'list'}) {
      # Feedback on how to connect to the remote tigervnc server.
      print "Use xtigervncviewer -via $options->{'displayHost'} :n to connect!\n";
      print "The display number :n is given in the above startup message from tigervncserver.\n";
    }
    exit 0;
  }

  if (($options->{'src'}{'vncPasswdFile'}//"undef") eq "cmdline") {
    $options->{'passwordArgSpecified'} = 1;
  } else {
    $options->{'passwordArgSpecified'} = 0;
  }

  &readConfigFile($options, "mandatory");
  if (defined($options->{'vncStartup'}) && $options->{'vncStartup'} eq '__AUTO__') {
    # vncStartup was not defined by the user configuration in ~/.vnc/vnc.conf.
    if (-f File::Spec->catfile($options->{'vncUserDir'}, "Xtigervnc-session")) {
      # A user provided Xtigervnc-session script exists => use it.
      $options->{'vncStartup'} =
        File::Spec->catfile($options->{'vncUserDir'}, "Xtigervnc-session");
    } elsif (-f File::Spec->catfile($options->{'vncUserDir'}, "Xvnc-session")) {
      # This is deprecated rename it to Xtigervnc-session.
      if (rename
          File::Spec->catfile($options->{'vncUserDir'}, "Xvnc-session")
        , File::Spec->catfile($options->{'vncUserDir'}, "Xtigervnc-session")) {
        symlink "Xtigervnc-session"
          , File::Spec->catfile($options->{'vncUserDir'}, "Xvnc-session");
        # A user provided Xtigervnc-session script exists => use it.
        $options->{'vncStartup'} =
          File::Spec->catfile($options->{'vncUserDir'}, "Xtigervnc-session");
      } else {
        # A user provided Xtigervnc-session script exists => use it.
        $options->{'vncStartup'} =
          File::Spec->catfile($options->{'vncUserDir'}, "Xvnc-session");
      }
    } elsif (-f File::Spec->catfile($options->{'vncUserDir'}, "xstartup")) {
      # A user provided Xtigervnc-session script exists => use it.
      $options->{'vncStartup'} =
        File::Spec->catfile($options->{'vncUserDir'}, "xstartup");
    } else {
      # Use the system configuration for vncStartup.
      $options->{'vncStartup'} = $vncStartup;
    }
  }

  unless (defined $options->{'PlainUsers'}) {
    chomp($options->{'PlainUsers'} = `/usr/bin/id -u -n`);
  }
  unless (defined $options->{'PAMService'}) {
    if (-f '/etc/pam.d/vnc') {
      $options->{'PAMService'} = 'vnc';
    } else {
      # Default vnc service not present. Hence, we fall back to our own tigervnc service.
      $options->{'PAMService'} = 'tigervnc';
    }
  }

  unless (defined $options->{'vncPasswdFile'}) {
    $options->{'vncPasswdFile'} =
      File::Spec->catfile($options->{'vncUserDir'}, "passwd");
  }
  if (defined $options->{'session'} &&
      ref($options->{'session'}) eq '') {
    $options->{'session'} = [split(qr{\s+}, $options->{'session'})];
  } elsif (!defined $options->{'session'} ||
           ref($options->{'session'}) ne 'ARRAY') {
    $options->{'session'} = [];
  }
  if (@{$options->{'session'}} == 1 &&
      !($options->{'session'}->[0] =~ m{/})) {
    my $sn = $options->{'session'}->[0];
    my $sessionCommand = loadXSession($sn);
    if (defined $sessionCommand) {
      $options->{'session'} = [$sessionCommand];
    } else {
      print STDERR "Could not load X session desktop file $sn\n";
    }
  }
  if (defined $options->{'localhost'}) {
    $options->{'localhost'} = $options->{'localhost'} =~ m/^(?:yes|true|1)$/i;
  }
  unless (defined $options->{'SecurityTypes'}) {
    if (!defined($options->{'localhost'}) || $options->{'localhost'}) {
      $options->{'SecurityTypes'} = 'VncAuth';
      $options->{'localhost'}     = 1;
    } else {
      $options->{'SecurityTypes'} = 'VncAuth,TLSVnc';
      $options->{'localhost'}     = 0;
    }
  }
  $options->{'vncAuthEnabled'} = 0;
  $options->{'noneAuthEnabled'} = 0;
  $options->{'plainAuthEnabled'} = 0;
  $options->{'x509CertRequired'} = 0;
  $options->{'haveSSLEncryption'} = 0;
  foreach my $securityType (split(',', $options->{'SecurityTypes'})) {
    $options->{'vncAuthEnabled'} = 1    if $securityType =~ m/^(?:.*vnc|vncauth)$/i;
    $options->{'noneAuthEnabled'} = 1   if $securityType =~ m/none$/i;
    $options->{'plainAuthEnabled'} = 1  if $securityType =~ m/plain$/i;
    $options->{'x509CertRequired'} = 1  if $securityType =~ m/^x509/i;
    $options->{'haveSSLEncryption'} = 1 if $securityType =~ m/^(?:x509|tls)/i;
  }

  if ($options->{'plainAuthEnabled'} &&
      $options->{'PAMService'} eq 'tigervnc' &&
      ! -f '/etc/pam.d/tigervnc') {
    print STDERR "$PROG: The tigervnc PAM servcice required for the security types\n";
    print STDERR "\tPlain, TLSPlain, or X509Plain is not installed.\n";
    &installPackageError("tigervnc-common");
  }

  unless (defined $options->{'localhost'}) {
    # If we have no encrypted VNC connection security types or
    # we have at least one *None security type in there, then
    # we better only server VNC on localhost to be tunneled via
    # ssh.
    $options->{'localhost'} = !$options->{'haveSSLEncryption'}
                           || $options->{'noneAuthEnabled'};
  }
  # PREVENT THE USER FROM EXPOSING A VNC SESSION WITHOUT AUTHENTICATION
  # TO THE WHOLE INTERNET!!!
  if (!$options->{'localhost'} && $options->{'noneAuthEnabled'} &&
      !$options->{'I-KNOW-THIS-IS-INSECURE'}) {
    print STDERR "$PROG: YOU ARE TRYING TO EXPOSE A VNC SERVER WITHOUT ANY\n";
    print STDERR "AUTHENTICATION TO THE WHOLE INTERNET! I AM REFUSING TO COOPERATE!\n\n";
    print STDERR "If you really want to do that, add the --I-KNOW-THIS-IS-INSECURE option!\n";
    exit -1;
  }
  if ($options->{'noneAuthEnabled'} &&
      !$options->{'I-KNOW-THIS-IS-INSECURE'}) {
    print STDERR "Please be aware that you are exposing your VNC server to all users on the\n";
    print STDERR "local machine. These users can access your server without authentication!\n";
  }

  unless ($options->{'vncAuthEnabled'}) {
    delete $options->{'vncPasswdFile'};
  }
  unless ($options->{'plainAuthEnabled'}) {
    delete $options->{'PAMService'};
    delete $options->{'PlainUsers'};
  }
  unless ($options->{'x509CertRequired'}) {
    delete $options->{'X509Cert'};
    delete $options->{'X509Key'};
  }

  my $runningUserVncservers = &runningUserVncservers($options);
  my @vncs = ();
  if (defined $options->{'displayNumber'}) {
    if ($options->{'displayNumber'} eq '*') {
      push @vncs, sort keys %{$runningUserVncservers};
    } else {
      push @vncs, $options->{'displayNumber'};
    }
  } elsif ($options->{'kill'} || $options->{'useold'}) {
    push @vncs, sort grep { !$runningUserVncservers->{$_}->{'stale'} } keys %{$runningUserVncservers};
    if ($#vncs >= 1) {
      print STDERR "$PROG: This is ambiguous. Multiple vncservers are running for this user!\n";
      &listXvncServer(\*STDERR, $options, $runningUserVncservers, \@vncs);
      exit 1;
    } elsif ($#vncs == -1 && $options->{'kill'}) {
      print STDERR "$PROG: No vncserver running for this user!\n";
      exit 1;
    } elsif ($#vncs == -1 && $options->{'useold'}) {
      # Find display number.
      push @vncs, &getDisplayNumber();
    }
  } elsif ($options->{'list'}) {
    push @vncs, sort keys %{$runningUserVncservers};
  } else {
    # Find display number.
    push @vncs, &getDisplayNumber();
  }

  if ($options->{'kill'}) {
    my $err = &killXvncServer($options, $runningUserVncservers, \@vncs);
    exit($err ? 1 : 0);
  } elsif ($options->{'list'}) {
    &listXvncServer(\*STDOUT, $options, $runningUserVncservers, \@vncs);
    exit 0;
  } else {
    $options->{'displayNumber'} = $vncs[0];

    &checkGeometryAndDepth($options);
    my $haveOld =
      $runningUserVncservers->{$options->{'displayNumber'}} &&
      !$runningUserVncservers->{$options->{'displayNumber'}}->{'stale'};
    if (!($options->{'useold'} && $haveOld) &&
        !&checkDisplayNumberAvailable($options->{'displayNumber'})) {
      print STDERR "A VNC/X11 server is already running as :$options->{'displayNumber'} on machine $HOSTFQDN\n";
      exit 1;
    }
    unless (defined $options->{'desktopName'}) {
      $options->{'desktopName'} = "${HOSTFQDN}:$options->{'displayNumber'} ($USER)";
    }
    if ($options->{'useold'} && $haveOld) {
      my $DISPLAY = $runningUserVncservers->{$options->{'displayNumber'}}->{'DISPLAY'};
      print "\nReusing old '$options->{'desktopName'}' desktop at $DISPLAY on machine $HOSTFQDN.\n";
      my $rfbport = $runningUserVncservers->{$options->{'displayNumber'}}->{'rfbport'};
      if ($rfbport != $options->{'displayNumber'} + 5900) {
        my @cmd = ("xtigervncviewer");
        my $connection = ($rfbport >= 5900 && $rfbport <= 5999)
          ? ":".$rfbport-5900 : ":".$rfbport;
        push @cmd, $connection;
        print "Use ".join(" ", @cmd)." to connect to the VNC server.\n";
      }
      print "\n";
    } else {
      if ($runningUserVncservers->{$options->{'displayNumber'}} &&
          $runningUserVncservers->{$options->{'displayNumber'}}->{'stale'}) {
        &cleanStale($options,$options->{'displayNumber'},1);
      }
      &startXvncServer( $options );
    }
  }
}

&main;
