#!/bin/sh

# Copyright © RealVNC Ltd.  All rights reserved.

locate_cmd() {
  cmd=$1
  shift
  if command -v $cmd >/dev/null 2>&1; then
    return 0
  else
    for p in $@; do
      if test -x $p/$cmd; then
        echo "Checking for $cmd... [Not in path]"
        return 1
      fi
    done
  fi
  echo "Checking for $cmd... [Not found]"
  return 2
}

#### vars
hasCheckModule=-1
hasSEManage=-1
hasSEModule=-1
hasRestoreCon=-1
hasGetEnforce=-1
hasSEBins=-1

PATH=$PATH:/usr/sbin:/sbin


#### Helpers
getMinModuleVersion() {
  output=`checkmodule -V | sed 's|[^0-9]*||' | sed 's|[^0-9].*||' `
  echo $output
}

getMaxModuleVersion() {
  output=`checkmodule -V | sed 's|[^0-9]*||' | sed 's|[0-9]*[^0-9]*||' `
  echo $output
}


# File formats are to be in the form [ModuleName]-V[ModuleNumber]
# this may be revised later when we find that Centos and Fedora are so different
# note that to make life easy for sort, module numbers should all be 2 chars 
getPPModuleVersion() {
  fileName=$1
  output=`echo $fileName | sed 's|.*-V||' | sed 's|[^0-9].*||' `
  echo $output 
}


#### Do we have selinux binaries?
locate_checkmodule() {
  if [ $hasCheckModule -lt 0 ]; then
    locate_cmd checkmodule
    hasCheckModule=$?
  fi
  return $hasCheckModule
}

locate_SEManage() {
  if [ $hasSEManage -lt 0 ]; then
    locate_cmd semanage
    hasSEManage=$?
  fi
  return $hasSEManage
}

locate_SEModule() {
  if [ $hasSEModule -lt 0 ]; then
    locate_cmd semodule
    hasSEModule=$?
  fi
  return $hasSEModule
}

locate_Restorecon() {
  if [ $hasRestoreCon -lt 0 ]; then
    locate_cmd restorecon
    hasRestoreCon=$?
  fi
  return $hasRestoreCon
}

locate_GetEnforce() {
  if [ $hasGetEnforce -lt 0 ]; then
    locate_cmd getenforce
    hasGetEnforce=$?
  fi
  return $hasGetEnforce
}


#### Work out what SELinux module should work with this system
#### and if that fails to install, it will attempt all other versions
installSEModule() {
  moduleDir=$1
  moduleName=$2

  viableModules=`ls ${moduleDir}/${moduleName}-V*.pp | sort -r`
  maxModVersion=`getMaxModuleVersion`
  minModVersion=`getMinModuleVersion`
  
  for moduleFile in ${viableModules} ; do
    modVersion=`getPPModuleVersion $moduleFile`
    if [ "$modVersion" -le "$maxModVersion" ] && \
       [ "$modVersion" -ge "$minModVersion" ]; then
      echo -n "Installing SELinux policy module $moduleName for SELinux version $modVersion - this may take a while... "
      if semodule -i "${moduleFile}" ; then
        echo "[OK]"
        return 0
      fi
      echo "[ERROR]"
    fi
  done
  
  return 1
}


## this could be made faster - by trying to grep for all of them at once...
isSEModuleGroupInstalled() {
  for module in "$@"; do
    name=`isSEModuleInstalled "${module}"`
    if [ $? -eq 0 ]; then
      echo $name
      return 0
    fi
  done
  return 1
}


## returns 0 if installed
## returns 1 otherwise
## prints out the name of the module that can be uninstalled if it was found
## so something like
## name=`isSEModuleInstalled server`
## if [ $? -eq 0 ]; then ....
isSEModuleInstalled() {
  moduleName=$1
  # On different RHEL/Fedora versions, semodule prints module names in various
  # ways:
  #   * it may or may not have tab-separated columns
  #     ("<module-name><tab><other info>")
  #   * module names may be of the form "realvnc-server-V17" (named with the
  #     SELinux module version) or may be just "realvnc-server"
  foundName=`semodule -l | grep -E "^${moduleName}(-V[0-9][0-9])?(\s|$)" 2>/dev/null`
  if [ $? -eq 0 ]; then
    echo "$foundName" | cut -f1
    return 0
  fi
  
  return 1
}


removeOldSEModules() {
  for module in $oldSEModules; do
    removeSEModule $module
  done
}


removeSEModule() {
  moduleName=$1
  if locate_SEModule; then
    modName=`isSEModuleInstalled "${moduleName}"`
    if [ $? -eq 0 ]; then
      echo "Removing existing SELinux policy module $1" 
      semodule -r "$modName"
    fi
  fi
}

# These are the files we edit about with regarding SELinux permssions
# so we have this function to reset the permissions on them if required
restorePermissions() {
  binDir=$1
  if locate_Restorecon; then
    restorecon -R $binDir/vncserver-x11-serviced \
                  $binDir/vncserver-virtuald \
                  $binDir/Xvnc
  fi
}


# We shouldn't attempt to install SELinux modules if it's disabled.
selinuxenabled=-1
isSELinuxEnabled() {
  if [ $selinuxenabled -lt 0 ]; then
    if locate_GetEnforce; then
      enforceStatus=`getenforce`
      echo -n "Testing if SELinux enabled... "
      if test "$enforceStatus" = "Disabled" ; then
        echo "[Disabled]"
        selinuxenabled=1
      else
        echo "[Enabled]"
        selinuxenabled=0
      fi
    else
      selinuxenabled=1
    fi
  fi
  
  return $selinuxenabled
}


# Fedora 18+ messes with the SELinux label of Xvnc because our binary has the
# same path as the open-source Xvnc, so Fedora's xserver_selinux(8) is applied to it.
SELinux_installFedoraCorrection() {
  binDir=$1

  if ! isSELinuxEnabled; then
    return
  fi

  if ls -Z $binDir/Xvnc 2>/dev/null | grep xserver_exec_t >/dev/null 2>&1; then
    if locate_Restorecon && locate_SEManage; then
      semanage fcontext -a -t bin_t $binDir/Xvnc
      restorePermissions $binDir
      echo "Xvnc SELinux permissions repaired."
    else
      echo
      echo "WARNING: semanage (policycoreutils-python) was not found. Before the system"
      echo "         is next relabelled, install it and run the following command:"
      echo "                  semanage fcontext -a -t bin_t $binDir/Xvnc"
      chcon -t bin_t $binDir/Xvnc || true
    fi
  fi
}


SELinux_installModules() {
  dataDir=$1
  binDir=$2
  
  if ! isSELinuxEnabled; then
    return
  fi
  
  if ! locate_checkmodule ; then
    echo "WARNING: Unable to install selinux modules as checkmodule is not in path"
    echo "Please correct and run vncinitconfig -register-SELinux"
    return
  fi
  
  if locate_SEModule ; then
    if ! installSEModule $dataDir realvnc-server ; then
      echo
      echo "WARNING: failed to install selinux module on your system."
      echo "Please run vncinitconfig -register-SELinux once the errors are resolved"
    fi
  
    # For cups - if there are any really old versions of the module, then we can 
    # just do nothing; we don't replace these directly.  We can probably warn the user
    # that we're not trying anything though - incase they actually care
    installSECups=1
    installedItem=`isAncientSEModuleInstalled`
    if [ $? -eq 0 ]; then
      echo
      echo "Outdated SELinux policy module discovered [$installedItem]"
      echo "If you encounter printing issues, remove this policy module and run vncinitconfig -register-SELinux"
      installSECups=0
    elif isOldSEModuleInstalled ; then
      removeOldSEModules
    fi

    if [ $installSECups -eq 1 ] && \
       ! installSEModule $dataDir realvnc-server-cups; then
      echo
      echo "WARNING: failed to install SELinux cups module"
      echo "printing may not work as expected"
    fi

    restorePermissions $binDir
  fi
}


SELinux_removeModules() {
  binDir=$1
  removeSEModule realvnc-server-cups
  removeSEModule realvnc-server
  
  if locate_SEManage; then
    semanage fcontext -d -t bin_t ${bindir}/Xvnc 2>/dev/null || true
  fi
}


## modules that we don't replace
ancientSEModules="cupsvncfc7
              cupsvncfc8
              cupsvncfc9
              cupsvncfedora11"

## modules that we replace
oldSEModules="cupsvncfedora17
               cupsvncrhel5
               cupsvncrhel6"


isAncientSEModuleInstalled() {
  ret=`isSEModuleGroupInstalled $ancientSEModules`
  if [ $? -eq 0 ]; then
    echo $ret
    return 0
  else
    return 1
  fi
}


isOldSEModuleInstalled() {
  ret=`isSEModuleGroupInstalled $oldSEModules`
  if [ $? -eq 0 ]; then
    return 0
  else
    return 1
  fi
}
