#!/bin/sh
######################################################################
# 
# PowerChute Network Shutdown Patch Script for Linux and Virtual Appliance.
#
# Copyright (c) 2022 Schneider Electric, All Rights Reserved. 
#
######################################################################

######################################################################
# Global Constants
######################################################################
LINUX="Linux"
SOLARIS="Solaris"
HPUX="HP-UX"
AIX="AIX"
VIMA="VIMA"
UNKNOWN="UNKNOWN"
XENSERVER="XenServer"
MACOS="MacOS"
x86_64="x86_64"
GROUP1="group1"
GROUP2="group2"
GROUP3="group3"
ARCH="@ARCH@"

TRUE=0
FALSE=1
STR_YES="YES"
YES=0
NO=1
QUIT=2
INVALID=99
PATH=/sbin:$PATH

######################################################################
# Exit Codes
######################################################################
EXIT_SUCCESS=0
EXIT_USAGE=1
EXIT_NOT_ROOT_USER=4
EXIT_UNSUPPORTED_OS=5
EXIT_UPGRADE_NOT_SUPPORTED=6
EXIT_USER_ABORT=7
EXIT_CONFLICT_PCPLUS=8
EXIT_CONFLICT_PCBE=9
EXIT_CONFLICT_PCS=10
EXIT_CONFLICT_VMWARE=11
EXIT_INVALID_INSTALL_DIR=12
EXIT_INVALID_JAVA_VERSION=13
EXIT_INVALID_LOCALE=14
EXIT_ZIPFILE_MISSING=15
EXIT_SILENT_CONFIG_MISSING=16
EXIT_EULA_NOT_ACCEPTED=17
EXIT_SILENT_INSTALL_JAVA_DIR=20
EXIT_TOOLS_MISSING=21

######################################################################
# Global Variables
######################################################################
UPDATE_INSTALL=$FALSE
OS=$UNKNOWN
SILENT_MODE=$FALSE

SRC_DIR=""
INSTALL_DIR=""
APP_DIR=""
JAVA_DIR=""
ACCEPT_EULA="NO"
REGISTER_NMC="NO"
STARTUP=""
SYSV_STARTUP=""
PCBE_STARTUP=""
PCS_STARTUP=""
PCNS_STARTUP=""
OLD_INSTALL_DIR=""
JRE_FILE=""
SPARC=$FALSE
TR="tr"
SYSTEMCTL=$(which systemctl 2>/dev/null)
CHKCONFIG=$(which chkconfig 2>/dev/null)

######################################################################
#  Functions
######################################################################
# trap keyboard interrupt on the following:
# 1 SIGHUP
# 2 SIGINT
# 3 SIGQUIT
# 6 SIGABRT
trap control_c 1 2 3 6

control_c()
# run if user hits control-c
{
  echo -en "\n*** User Abort Detected! Exiting ***\n"
  Echo "Aborting with error code-$EXIT_USER_ABORT"
  CancelAll $EXIT_USER_ABORT
}

# Waits for user key press.
Pause() {
    OLDCONFIG=`stty -g`
    stty -icanon -echo min 1 time 0
    dd count=1 2>/dev/null
    stty $OLDCONFIG
}

Echo() {
    string="$1"
    echo "$string"
}

IsRootUser() {
    ROOT="root"
    USER=`id -nu`
    
    if [ $USER != $ROOT ]; then
        Echo "Error: $0 must be run with root privileges!"
        Echo "Aborting with error code-$EXIT_NOT_ROOT_USER"
        exit $EXIT_NOT_ROOT_USER
    fi
}

CheckOS() {
    OS=`uname | grep -i Linux`
    if [ ! -z "$OS" ]; then
        # We're a Linux derivative, decide which one.
        if [ -f /etc/vima-release ] || [ -f /etc/vma-release ]; then
            OS=$VIMA
        else 
            grep XenServer /etc/redhat-release > /dev/null 2>&1
            if [ $? -eq 0 ]; then
                OS=$XENSERVER
            else
                OS=$LINUX
            fi
        fi
    else
        OS=`uname | grep -i HP-UX`
        if [ ! -z "$OS" ] 
        then
            OS=$HPUX
        else
            OS=`uname | grep -i AIX`
            if [ ! -z "$OS" ] 
            then
                OS=$AIX
            else
                OS=`uname | grep -i SOLARIS`
                if [ ! -z "$OS" ]
                then
                    OS=$SOLARIS
                    isSparc
                    SPARC=$?
                else 
                    OS=`uname | grep -i SUNOS`
                    if [ ! -z "$OS" ]
                    then
                        OS=$SOLARIS
                        isSparc
                        SPARC=$?                               
                    else
                        OS=`uname | grep -i DARWIN`
                        if [ ! -z "$OS" ]; then
                            OS=$MACOS
                        else
                            Echo "Aborting with error code-$EXIT_UNSUPPORTED_OS"
                            Echo "Error: Unknown OS."
                            exit $EXIT_UNSUPPORTED_OS
                        fi
                    fi
                fi
            fi
        fi
    fi
    Echo "OS=$OS"
    Echo ""
}

isSparc() {
    rval=$FALSE
    tmp=`uname -a | grep -i SPARC`
    if [ ! -z "$tmp" ] 
    then
        rval=$TRUE
    fi
    return $rval
}

IsPCNSInstalled() {
    upgrade=$FALSE    
    version="Unknown"
    SCRIPT=""
    
    if [ "$OS" = "$MACOS" ]; then
        SCRIPT=/Library/LaunchDaemons/com.apcc.pcns.plist
    else      
        # Check for the SystemD startup script
        if [ -f "$STARTUP" ]; then
            SCRIPT="$STARTUP"
        elif [ -f "$SYSV_STARTUP" ]; then
            SCRIPT="$SYSV_STARTUP"
        fi
    fi
    
    # Echo "SCRIPT=$SCRIPT"
    
    if [ -z "$SCRIPT" ]; then 
        Echo "Could not find PowerChute Network Shutdown installation directory."
        Echo "Aborting with error code-$EXIT_UPGRADE_NOT_SUPPORTED"
        exit $EXIT_UPGRADE_NOT_SUPPORTED
    fi           
}

InitUpdate() {
    # Get PCNS Path
    if [ "$OS" = "$MACOS" ]; then
        OLD_INSTALL_DIR=`defaults read $STARTUP Program`

        # Drop script portion of path...
        OLD_INSTALL_DIR=`dirname $OLD_INSTALL_DIR`

        # Drop group 1 folder portion of path...
        OLD_INSTALL_DIR=`dirname $OLD_INSTALL_DIR`
    else
        if [ -f "$STARTUP" ]; then
            OLD_INSTALL_DIR=`grep '^INSTALL_PATH='  $STARTUP | sed s/INSTALL_PATH=\"// | sed s/\"//`
        elif [ -f "$SYSV_STARTUP" ]; then
            OLD_INSTALL_DIR=`grep '^INSTALL_PATH='  $SYSV_STARTUP | sed s/INSTALL_PATH=\"// | sed s/\"//`
        fi
    fi
    
    Echo "PowerChute Network Shutdown previously installed at: $OLD_INSTALL_DIR"
    INSTALL_DIR=`dirname $OLD_INSTALL_DIR`
        
}

Initialize() {
    Echo "Initializing ..."
    case "$OS" in
    $VIMA)
        Echo "This version of PowerChute Network Shutdown does not support VMWare ESX or ESXi."
        Echo "Please consult www.apc.com for the required version of PowerChute Network Shutdown."
        Echo "Aborting with error code-$EXIT_UNSUPPORTED_OS"
        CancelAll $EXIT_UNSUPPORTED_OS
        ;;
    $XENSERVER)
        STARTUP=/etc/rc.d/init.d/PowerChute
        PCBE_STARTUP=/etc/rc.d/init.d/PBEAgent
        PCS_STARTUP=/etc/rc.d/init.d/pcs
        ;;
    $LINUX)
        if [ -f /etc/vmware-release ] ; then
            Echo "This version of PowerChute Network Shutdown does not support VMWare ESX or ESXi."
            Echo "Please consult www.apc.com for the required version of PowerChute Network Shutdown."
            Echo "Aborting with error code-$EXIT_UNSUPPORTED_OS"
            CancelAll $EXIT_UNSUPPORTED_OS
        fi
        
        STARTUP=/usr/bin/PowerChute
        SYSV_STARTUP=/etc/init.d/PowerChute
        PCBE_STARTUP=/etc/rc.d/init.d/PBEAgent
        PCS_STARTUP=/etc/rc.d/init.d/pcs
        ;;
    $SOLARIS)
        STARTUP=/etc/rc2.d/S99PowerChute
        PCBE_STARTUP=/etc/rc2.d/S99PBEAgent
        TR=/usr/xpg4/bin/tr
        ;;
    $HPUX)
        STARTUP=/sbin/init.d/pcns
        ;;
    $AIX)
        STARTUP=/etc/rc.APCpcns
        ;;
    $MACOS)
        STARTUP=/Library/LaunchDaemons/com.apcc.pcns.plist
        ;;
    esac
}

CancelAll() {
    cd "$SRC_DIR"
    
    Echo ""
    Echo "Installation cancelled."
    Echo ""
    exit $1
}

verifyTools() {
    Echo "Verifying all necessary tools are available ..."
    ALL_TOOLS=0
    TOOLS="sed"
    for tool in ${TOOLS}; do
        if ! [ -x "$(command -v $tool)" ]; then
            Echo "ERROR: $tool is not available." 1>&2
            ALL_TOOLS=1
        fi
    done

    if [ $ALL_TOOLS -eq 0 ]; then
        Echo "SUCCESS: All necessary tools found."
    else 
        Echo "ERROR: Some necessary tools are not available."
        Echo "Please install the necessary tools and try again."
        Echo "Aborting with error code-$EXIT_TOOLS_MISSING"
        exit $EXIT_TOOLS_MISSING
    fi
}

startService() {
    Echo "Starting service ..."
    
    case "$OS" in       
    $SOLARIS)
        $STARTUP start
        ;;
            
    $MACOS)
        launchctl load -w $STARTUP
        ;;
            
    *)
        if [ -n "$SYSTEMCTL" ]; then
            # PCNS 4.2 on Ubuntu 16.04 has systemctl available but doesn't use it.  As such we need ensure use of systemctl comes last.
            $SYSTEMCTL restart PowerChute
        elif [ -f "$STARTUP" ]; then
            # If we have a startup script, use it...
            $STARTUP start
        elif [ -f "$SYSV_STARTUP" ]; then
            # If we have a SYSV style startup script, use it...
            $SYSV_STARTUP start        
        else
            # Will hopefully, never happen. 
            Echo "WARNING: PowerChute Service did not start as expected. Please start the service manually."
        fi        
        ;;
    esac        
}

stopService() {
    Echo "Stopping service ..."
    # Stop daemon
    case "$OS" in
    $VIMA)
        /etc/rc.d/init.d/PowerChute stop
        ;;
    $XENSERVER)
        /etc/rc.d/init.d/PowerChute stop
        ;;
    $LINUX)
        if [ -n "$SYSTEMCTL" ]; then            
            $SYSTEMCTL stop PowerChute
        elif [ -f /usr/bin/PowerChute ]; then
            /usr/bin/PowerChute stop
        elif [ -f /etc/init.d/PowerChute ]; then
            /etc/init.d/PowerChute stop
        elif [ -f /etc/rc.d/init.d/PowerChute ]; then
            /etc/rc.d/init.d/PowerChute stop
        fi
        ;;
    $SOLARIS)
        /etc/rc2.d/S99PowerChute stop
        ;;
    $HPUX)
        /sbin/init.d/pcns stop
        ;;
    $AIX)
        /etc/rc.APCpcns stop
        ;;
    $MACOS)
        launchctl unload $STARTUP > /dev/null 2>&1
        ;;
    esac
}

copyPcnsJar() {
    # Assumes the pcns jar is in the ../files subdirectory, and is called pcns.jar
    Echo "Copying updated pcns.jar..."
    
    TARGET_JAR=$INSTALL_DIR/PowerChute/group1/comp/pcns.jar
    
    cp ./files/pcns.jar $TARGET_JAR
    
    if [ $? -ne 0 ]; then
        Echo "PowerChute jar file could not be copied."
        Echo "Aborting with error code-$EXIT_UPGRADE_NOT_SUPPORTED"
        exit $EXIT_UPGRADE_NOT_SUPPORTED
    fi
}

removeOldLibs() {
    Echo "Remove existing libraries ..."
    TARGET_DIR=$INSTALL_DIR/PowerChute/group1/lib
    
    rm -f $TARGET_DIR/log4j-api-*.jar && rm -f $TARGET_DIR/log4j-slf4j-*.jar && rm -f $TARGET_DIR/log4j-core-*.jar && rm -f $TARGET_DIR/jetty-*.jar && rm -f $TARGET_DIR/commons-compress-*.jar && rm -f $TARGET_DIR/spring-*.jar
    
    if [ $? -ne 0 ]; then
        Echo "Vulnerable libraries could not be removed."
        Echo "Aborting with error code-$EXIT_UPGRADE_NOT_SUPPORTED"
        exit $EXIT_UPGRADE_NOT_SUPPORTED
    fi
}

copyNewLibs() {
    Echo "Add updated libraries ..."
    # Assumes the new libs are in the ../files directory and named 
    TARGET_DIR=$INSTALL_DIR/PowerChute/group1/lib
    
    cp ./files/log4j-*.jar $TARGET_DIR && cp ./files/jetty-*.jar $TARGET_DIR && cp ./files/commons-compress-*.jar $TARGET_DIR && cp ./files/spring-*.jar $TARGET_DIR
         
    if [ $? -ne 0 ]; then
        Echo "Libraries could not be copied to PowerCHute libs folder."
        Echo "Aborting with error code-$EXIT_UPGRADE_NOT_SUPPORTED"
        exit $EXIT_UPGRADE_NOT_SUPPORTED
    fi
}

patchWithBsdSed() {
    # BSD sed doesn't have an in-place option...         
    PATCH_FILE="$PCNS_STARTUP.patch"
    
    # Handle Log4j 2.10.0 to 2.17.1 ...
    sed -e 's/log4j-api-2.10.0/log4j-api-2.17.1/g' $PCNS_STARTUP > $PATCH_FILE
    mv $PATCH_FILE $PCNS_STARTUP
            
    sed -e 's/log4j-core-2.10.0/log4j-core-2.17.1/g' $PCNS_STARTUP > $PATCH_FILE
    mv $PATCH_FILE $PCNS_STARTUP
        
    sed -e 's/log4j-slf4j-impl-2.10.0/log4j-slf4j-impl-2.17.1/g' $PCNS_STARTUP > $PATCH_FILE
    mv $PATCH_FILE $PCNS_STARTUP
    
    # Handle Log4j 2.2 to 2.17.1 ...
    sed -e 's/log4j-api-2.2/log4j-api-2.17.1/g' $PCNS_STARTUP > $PATCH_FILE
    mv $PATCH_FILE $PCNS_STARTUP
            
    sed -e 's/log4j-core-2.2/log4j-core-2.17.1/g' $PCNS_STARTUP > $PATCH_FILE
    mv $PATCH_FILE $PCNS_STARTUP
        
    sed -e 's/log4j-slf4j-impl-2.2/log4j-slf4j-impl-2.17.1/g' $PCNS_STARTUP > $PATCH_FILE
    mv $PATCH_FILE $PCNS_STARTUP
        
    chmod 744 $PCNS_STARTUP
}

patchWithGnuSed() {
    sed -i -e 's/log4j-api-2.2/log4j-api-2.17.1/g' $PCNS_STARTUP
    sed -i -e 's/log4j-core-2.2/log4j-core-2.17.1/g' $PCNS_STARTUP
    sed -i -e 's/log4j-slf4j-impl-2.2/log4j-slf4j-impl-2.17.1/g' $PCNS_STARTUP
}

modifyPowerChuteClasspath() {
    grep log4j-core $PCNS_STARTUP > /dev/null 2>&1
    
     if [ $? -eq 0 ]; then
        Echo "Modifying classpath ..."
    
        case "$OS" in     
        $AIX)
            patchWithBsdSed
            ;;
              
        $SOLARIS)
            patchWithBsdSed
            ;;
            
        $MACOS)
            patchWithBsdSed
            ;;
            
        *)
            # Default/ Linux uses GNU sed...            
            patchWithGnuSed
            ;;
        esac
    
    fi
}

######################################################################
# Main routine
######################################################################
SRC_DIR=`pwd`

# Must be root to run this script...
IsRootUser

# Script supports Linux/ Virtual appliance only...
CheckOS

# Verify we have the necessary tools...
verifyTools

# Detect startup scripts...
Initialize

# Verify that PCNS is installed...
IsPCNSInstalled

# Find the install dir ...
InitUpdate

# Stop the service...
stopService

# Apply patch to startup script
Echo "Applying patch ..."

# Copy new pcns.jar into place
copyPcnsJar

# Remove old log4j libs
removeOldLibs

# Copy new log4j libs into place
copyNewLibs

# Edit the classpath (for non-wildcard classpaths)
# modifyPowerChuteClasspath

startService

Echo "Patch applied to PowerChute."
cd "$SRC_DIR"
exit $EXIT_SUCCESS
