#!/bin/sh
#
# Copyright (c) 1999-2024 PaperCut Software International Pty. Ltd.
#
# A script to perform install and uninstall tasks requiring root access.
# For example, installing CUPS backends and init.rc script.
#
# IMPORTANT: This script must be kept cross-platform and work on
# Novell, Linux, and macOS.
#

. "`dirname \"$0\"`/.common"

userid=`id | sed "s/^uid=\([0-9][0-9]*\).*$/\1/"`
if test "${userid}" -ne 0
then
    echo "Error: You must be root to run this program" 1>&2
    exit 1
fi

if [ -z "${APP_NAME}" ]; then
    # If we are here, we've failed to source our .common file
    echo "Error: Unable to source .common file. Does this file exist?" 1>&2
    exit 2
fi

cd "${SCRIPT_HOME}"

###########################################################################
# Common Functions
###########################################################################
update_conf() {
    echo "Updating print provider configuration..."

    provider_conf="${SCRIPT_HOME}/print-provider.conf"
    base_dir="${SCRIPT_HOME}/"

    if (grep -i "BaseDir=" "${provider_conf}" > /dev/null) ; then
        # Contains the BaseDir - update the BaseDir if not set yet
        sed -e "s@#BaseDir=.*\$@BaseDir=${base_dir}@" \
            < "${provider_conf}" > "${provider_conf}.tmp"

        # Preserve ownership and perms on new by copying over original
        cp "${provider_conf}.tmp" "${provider_conf}"
        rm "${provider_conf}.tmp"

    else
        # Does not contain a BaseDir, so upgrading an old install.
        echo "Upgrading print-provider.conf file ..."
        cat >> "${provider_conf}" << EOF

#
# UNIX ONLY: The base directory where the print provider is installed.
# i.e. where the executables, config file, logs, etc are located.
#
# This setting is automatically configured during installation.
#
# Do not change without contacting support.
#
BaseDir=${base_dir}
EOF
    fi

    echo "Creating Print Provider UUID file if needed"
    "${SCRIPT_HOME}/pc-gen-uuid" "${SCRIPT_HOME}"

    # Ensure correct perms on uuid file
    chmod 644 "${SCRIPT_HOME}/print-provider.uuid4"
}

#
# Output the cupsd path on stdout
# If don't find it, return with status 1
# If find it, return with status 0
#
find_cupsd() {
    if which cupsd >/dev/null 2>&1; then
        which cupsd
        return 0
    fi

    #
    # Else search in common locations
    #
    locs="/usr/sbin/cupsd
            /usr/bin/cupsd
            /opt/cup/bin/cupsd
            /usr/local/sbin/cupsd
            /usr/local/bin/cupsd
            /usr/local/cups/bin/cupsd"

    for d in ${locs}; do
        if test -f "${d}"; then
            echo "${d}"
            return 0
        fi
    done
    return 1
}

#
# CUPS does not have a '-v' option so we'll grep the binary for a version number
# XXX: In future, consider using cups-config --api-version or --version
#
is_cups_v12_plus() {
    cupsd=`find_cupsd`
    versionstr=`egrep -ao "CUPS/1\.[2-9]|CUPS/2\." "${cupsd}"`
    test ! -z "${versionstr}"
}

chown_reference() {
    file=$1
    reference=$2

    chown --reference=${reference} "${file}" 2>/dev/null
    if [ $? -ne 0 ]; then
        # do this for Mac
        owner=`stat -f "%u:%g" ${reference}`
        chown ${owner} "${file}" 2>/dev/null
    fi
}

chmod_reference() {
    file=$1
    reference=$2

    chmod --reference=${reference} "${file}" 2>/dev/null
    if [ $? -ne 0 ]; then
        # do this for Mac
        permissions=`stat -f "%A" ${reference}`
        chmod ${permissions} "${file}" 2>/dev/null
    fi
}

_update_cups_conf() {
    #
    # We run our programs as root and so ensure that root is
    # part of CUPS' SystemGroup setting.
    # On Ubuntu, we found that since CUPS 1.5, JobPrivateAccess and JobPrivateValues were added
    # which hide all "personal" information in jobs unless you are an admin or the owner of the job.
    # On Ubuntu this prevents us from accessing job-originating-user-name for pre-notification
    # because admin, or "SystemGroup" is set to lpadmin which doesn't include root.
    # On Mac, the "SystemGroup" is set to admin which includes root.
    # Therefore, we need to update /etc/cups/cupsd.conf or /etc/cups/cups-files.conf to include
    # root (e.g. prepend it).
    # Don't worry about what group actually include user root, just make sure the root group
    # is explicitly added if it is not there already. On mac, though that would add root as well as admin.
    #
    # if not Mac (os is Darwin)
    if uname -a | egrep -qv '^Darwin'; then
        cups_changed=false
        # for each of the cups deamon related config files
        for cupsconf in /etc/cups/cupsd.conf /etc/cups/cups-files.conf; do
            # ensure file exists
            [ -e $cupsconf ] || continue
            # if have SystemGroup line in this file
            if egrep -q '^SystemGroup' $cupsconf; then
                # if don't have root on this line
                if ! egrep -q '^SystemGroup.*\s+root\s*' $cupsconf; then
                    # prepend root
                    cups_changed=true
                    sed -i'.pctmp' 's/^SystemGroup/SystemGroup root/' $cupsconf
                fi
            fi
        done
        # get cupsd to reread config
        if $cups_changed; then
            echo "Adding root to SystemGroup and restarting cupsd"
            killall -HUP cupsd >/dev/null 2>&1
        fi
    fi
}


############################################################################
# Test if iprint
############################################################################
is_iprint() {
    test -d "/opt/novell/iprint/bin/"
}

############################################################################
# Version compare functions. Based off code from Stack Overflow.
# Use sort -V to sort by versions.
# Take top one as smallest value
############################################################################
version_less_than_equals() {
    [  "$1" = "$(printf "%s\n%s" "$1" "$2"| sort -V | head -n1)" ]
}
version_less_than() {
    # if equals then false
    [ "$1" = "$2" ] && return 1

    # else just use less than
    version_less_than_equals $1 $2
}

############################################################################
# Install Novell iPrint if found
############################################################################
install_iprint() {
    echo "Installing iPrint print provider..."

    target="/opt/novell/iprint/bin/${CUPS_PREFIX}"

    #
    # Select version of novell-print-provider depending on versions of ipsmd.
    # Microfocus changed api format after iPrint 4.1.3
    # We tested this change and rebuild to iPrint 4.2
    # This has an ipsmd version of 6.21
    #
    iprint_min_ver="6.21.0"
    iprint_ver=$(rpm -q --queryformat '%{VERSION}\n' -f /opt/novell/iprint/bin/ipsmd 2>/dev/null | sort -V 2>/dev/null | head -1)
    if [ -z "$iprint_ver" ] || version_less_than "$iprint_ver" "$iprint_min_ver"; then
        echo "Using old compatible print provider..."
        src="novell-print-provider"
    else
        echo "Using iPrint 4.2 print provider..."
        src="novell-print-provider-iprint42"
    fi

    rm -f "${target}" && cp "${src}" "${target}"
    # Set permissions with a logical default, then try to copy settings
    # based on those on iprint executables.
    #
    # ownership:
    chown root:iprint "${target}"
    chown --reference "/opt/novell/iprint/bin/iprintgw" "${target}" 2>/dev/null
    # permissions:
    chmod 750 "${target}"
    chmod --reference "/opt/novell/iprint/bin/ipsmd" "${target}" 2>/dev/null
}

############################################################################
# Copy in CUPS papercut backend or filter
############################################################################
_copy_cups_provider() {
    _src="$1"
    _dst="$2"

    if [ -f "$_dst" ]; then
        # Upgrade
        #
        # Needed on Mac Catalina to prevent backend being killed off.
        # Cannot change the executable (and its signature) on existing inode.
        # So remove the old one inode first and then create a new inode.
        # Backup original in case the cp fails and we can go back to it.
        #
        echo "Removing existing CUPS backend/filter $_dst prior to copy"
        mv -f "$_dst" "$_dst.orig"
        if [ $? -eq 0 ]; then
            # success
            cp -af "$_src" "$_dst"
            if [ $? -eq 0 ]; then
                # success - remove original backup
                rm -f "$_dst.orig"
            else
                # failure - restore from backup
                echo "Failed to copy and using old backend/filter"
                mv -f "$_dst.orig" "$_dst"
            fi
        else
            # failure
            echo "Failed to backup backend/filter so do NOT upgrade backend/filter"
        fi
    else
        # Fresh install
        cp -af "$_src" "$_dst"
    fi
}

############################################################################
# Install CUPS if found
############################################################################
install_cups() {
    echo "Installing CUPS print provider..."

    if ! find_cupsd >/dev/null 2>&1; then
        echo "Unable to find cupsd - is CUPS installed?"
        exit 1
    fi

    has_cups=

    _update_cups_conf

    #
    # Link provider into backend directory
    #
    backend_locs="/usr/lib/cups/backend
            /usr/libexec/cups/backend
            /usr/local/lib/cups/backend
            /usr/local/cups/lib/cups/backend
            /opt/cups/lib/cups/backend
            /usr/lib64/cups/backend"

    # Select backend executable based on platform
    cups_backend_src="cups-print-provider"
    if [ "$(uname)" = "Darwin" ]; then
        cups_backend_src="pc-backend"
    fi

    for d in ${backend_locs}; do
        if test -d "${d}"; then

            echo "Found CUPS backend directory at ${d}"
            echo "Installing ${APP_NAME} into CUPS backend directory"
            has_cups=y

            # Copy in backend
            cd "${d}"
            _copy_cups_provider "${SCRIPT_HOME}/${cups_backend_src}" "${CUPS_PREFIX}"

            # 1.
            # Set some default permissions.
            # This will be used for versions 1.1 or below and if the following tests fail.
            # Normally, these commands will be overridden below.
            echo "Setting backend permissions"
            chmod 755 "${CUPS_PREFIX}"
            chown root "${CUPS_PREFIX}"

            # 2.
            # Set logical defaults depending on CUPS version
            # For version 1.2 or higher we set up to run papercut backend as root.
            # This is essential if the chmod_reference calls below fail to work.
            # Initially, this was the case for Mac as the chmod_reference was not
            # implemented to work on Mac.
            if is_cups_v12_plus; then
                echo "Found CUPS version 1.2 or higher"
                # Explicitly set backend to run as root
                chmod 700 "${CUPS_PREFIX}"
            fi

            # 3.
            # Set permissions on the backend.  We use LPD or IPP as our
            # reference.  This ensures that our permissions are setup
            # in line with the target distribution (e.g. using setuid?)
            # This is considered the ideal case.
            # It will allow the file mode to be copied from the reference root backend.
            # Under Debian or Ubuntu this allowed copying the setuid bit in the file's mode to be copied.
            # Generally, this will be the code that is executed on Linux and Mac (now).
            refbackend=
            if [ -x "lpd" ]; then
                refbackend=lpd
            fi
            if [ -z "${refbackend}" -a -x "ipp" ]; then
                refbackend=ipp
            fi
            if [ -z "${refbackend}" -a -x "socket" ]; then
                refbackend=socket
            fi
            if [ ! -z "${refbackend}" ]; then
                echo "Using ${refbackend} as reference for ownership and permissions"
                chown_reference "${CUPS_PREFIX}" "${refbackend}"
                chmod_reference "${CUPS_PREFIX}" "${refbackend}"
            fi

        fi
    done

    #
    # Link provider into CUPS filter directory
    #
    filter_locs="/usr/lib/cups/filter
            /usr/libexec/cups/filter
            /usr/local/lib/cups/filter
            /usr/local/cups/lib/cups/filter
            /opt/cups/lib/cups/filter
            /usr/lib64/cups/filter"

    for d in ${filter_locs}; do
        if test -d "${d}"; then

            echo "Found CUPS filter directory at ${d}"
            echo "Installing ${APP_NAME} into CUPS filter directory"
            cd "${d}"
            _copy_cups_provider "${SCRIPT_HOME}/cups-filter-print-provider" "${CUPS_PREFIX}"
            chmod 755 "${CUPS_PREFIX}"
            chown root "${CUPS_PREFIX}"
        fi
    done
}

############################################################################
# Install Event Monitor
############################################################################
install_event_monitor() {
    if ${hassystemd}; then
        # Clean up RC type boot scripts first.
        uninstall_event_monitor_systemv_scripts

        # Clean up old connection monitor stuff on an upgrade
        uninstall_connection_monitor_systemd_services

        install_event_monitor_systemd_services
    else
        install_event_monitor_systemv_scripts
    fi
}

install_event_monitor_systemd_services() {
    echo "Installing SysD service (event monitor)..."
    # Create service definition file (event monitor).
    cat > /etc/systemd/system/pc-event-monitor.service << EOF
[Unit]
Description=${APP_NAME_SHORT} Event Monitor
After=network.target local_fs.target

[Service]
ExecStart=${SCRIPT_HOME}/pc-event-monitor

[Install]
WantedBy=multi-user.target
EOF

    # Enable the new services (required for autostart after system reboot).
    systemctl --no-ask-password -q reenable pc-event-monitor.service

    # Start services if required.
    if $startService; then
        systemctl --no-ask-password restart pc-event-monitor.service
    fi
}

install_event_monitor_systemv_scripts() {
    # Get rid of old "papercutcups" named event monitor if we are overwriting an old install.
    uninstall_old_event_monitor_systemv_scripts

    RC_PREFIX="${APP_NAME_SHORT}-event-monitor"

    #
    # Stop if it's already running
    #
    if [ -e "/etc/init.d/${RC_PREFIX}" ]; then
        /etc/init.d/${RC_PREFIX} stop
    fi

    #
    # Link in the pc-event-monitor rc style start scripts
    #
    hasinitd=
    if [ -d "/etc/init.d" -a ! -e "/etc/init.d/${RC_PREFIX}" ]; then
        echo "Installing SysV style boot scripts (event monitor)..."
        hasinitd=1
    fi

    #
    # Make sure we always overwrite our symlink just in case the user switches
    # architecture.
    #
    if [ -d "/etc/init.d" ]; then
        cd /etc/init.d
        ln -sf "${SCRIPT_HOME}/pc-event-monitor.rc" "${RC_PREFIX}"
    fi

    if [ ! -z "${hasinitd}" ]; then
        # Start in runlevel 3
        if [ -d "/etc/rc3.d" ]; then
            cd /etc/rc3.d
            ln -s "../init.d/${RC_PREFIX}" "S99${RC_PREFIX}"
        fi

        # Start in runlevel 5
        if [ -d "/etc/rc5.d" ]; then
            cd /etc/rc5.d
            ln -s "../init.d/${RC_PREFIX}" "S99${RC_PREFIX}"
        fi

        # Start in runlevel 2
        if [ -d "/etc/rc2.d" ]; then
            cd /etc/rc2.d
            ln -s "../init.d/${RC_PREFIX}" "S99${RC_PREFIX}"
        fi

        # Kill in runlevel 0
        if [ -d "/etc/rc0.d" ]; then
            cd /etc/rc0.d
            ln -s "../init.d/${RC_PREFIX}" "K89${RC_PREFIX}"
        fi

        # Kill in runlevel 1
        if [ -d "/etc/rc1.d" ]; then
            cd /etc/rc1.d
            ln -s "../init.d/${RC_PREFIX}" "K89${RC_PREFIX}"
        fi

        # Kill in runlevel 6
        if [ -d "/etc/rc6.d" ]; then
            cd /etc/rc6.d
            ln -s "../init.d/${RC_PREFIX}" "K89${RC_PREFIX}"
        fi
    fi

    #
    # SuSE's rc*.d dir's are located under /etc/init.d. Activate here
    # if found.
    #
    if [ ! -z "${hasinitd}" -a -d "/etc/init.d/rc5.d" ]; then
        # Start in runlevel 3
        if [ -d "/etc/init.d/rc3.d" ]; then
            cd /etc/init.d/rc3.d
            ln -s "../${RC_PREFIX}" "S99${RC_PREFIX}"
        fi

        # Start in runlevel 5
        if [ -d "/etc/init.d/rc5.d" ]; then
            cd /etc/init.d/rc5.d
            ln -s "../${RC_PREFIX}" "S99${RC_PREFIX}"
        fi

        # Start in runlevel 2
        if [ -d "/etc/init.d/rc2.d" ]; then
            cd /etc/init.d/rc2.d
            ln -s "../${RC_PREFIX}" "S99${RC_PREFIX}"
        fi

        # Kill in runlevel 0
        if [ -d "/etc/init.d/rc0.d" ]; then
            cd /etc/init.d/rc0.d
            ln -s "../${RC_PREFIX}" "K89${RC_PREFIX}"
        fi

        # Kill in runlevel 1
        if [ -d "/etc/init.d/rc1.d" ]; then
            cd /etc/init.d/rc1.d
            ln -s "../${RC_PREFIX}" "K89${RC_PREFIX}"
        fi

        # Kill in runlevel 6
        if [ -d "/etc/init.d/rc6.d" ]; then
            cd /etc/init.d/rc6.d
            ln -s "../${RC_PREFIX}" "K89${RC_PREFIX}"
        fi
    fi

    #
    # Start if we've installed our RC scripts.
    #
    if [ -e "/etc/init.d/${RC_PREFIX}" ]; then
        if $startService; then
            /etc/init.d/${RC_PREFIX} start
        fi
    fi
}

############################################################################
# Uninstall iPrint print provider
############################################################################
uninstall_iprint() {
    target="/opt/novell/iprint/bin/${CUPS_PREFIX}"
    if [ -e ${target} ]; then
        rm -f "${target}" >/dev/null 2>&1
        echo
        echo "REMINDER: Remove \"${CUPS_PREFIX}\" from your iPrint Account Autoload command."
        echo
    fi
}

############################################################################
# Uninstall PC event monitor
# Remove init/rc scripts from all possible locations
############################################################################
uninstall_event_monitor() {
    if ${hassystemd}; then
        uninstall_event_monitor_systemd_services
    else
        uninstall_event_monitor_systemv_scripts
    fi
}

uninstall_event_monitor_systemd_services() {
    echo "Uninstalling SysD service (event monitor)..."
    systemctl --no-ask-password stop pc-event-monitor.service
    systemctl --no-ask-password -q disable pc-event-monitor.service
    rm -f /etc/systemd/system/pc-event-monitor.service > /dev/null 2>&1

    uninstall_connection_monitor_systemd_services
}

uninstall_connection_monitor_systemd_services() {
    # for older installations - prior to 16.1 - which had a separate connection monitor
    if [ -e /etc/systemd/system/pc-connection-monitor.service ]; then
        echo "Uninstalling SysD service (connection monitor)..."
        systemctl --no-ask-password stop pc-connection-monitor.service
        systemctl --no-ask-password -q disable pc-connection-monitor.service
        rm -f /etc/systemd/system/pc-connection-monitor.service > /dev/null 2>&1
    fi
}

uninstall_event_monitor_systemv_scripts() {
    RC_PREFIX=$1
    if [ -z "${RC_PREFIX}" ]; then
        RC_PREFIX="${APP_NAME_SHORT}-event-monitor"
        echo "Uninstalling SysV style boot scripts (event monitor)..."
    fi

    # Stop if it's already running
    if [ -e "/etc/init.d/${RC_PREFIX}" ]; then
        /etc/init.d/${RC_PREFIX} stop
    fi
    rm -f /etc/init.d/${RC_PREFIX} >/dev/null 2>&1
    rm -f /etc/rc*.d/*${RC_PREFIX} >/dev/null 2>&1
    rm -f /etc/init.d/rc*.d/*${RC_PREFIX} >/dev/null 2>&1
}

############################################################################
# Uninstall old cups event monitor
# Remove init/rc scripts from all possible locations
# cups-event-monitor --has-become--> pc-event-monitor
############################################################################
uninstall_old_event_monitor_systemv_scripts() {
    # uninstall from /etc/init.d
    uninstall_event_monitor_systemv_scripts ${APP_NAME_SHORT}cups

    # uninstall from ~papercut/*
    OLD_MONITOR=${SCRIPT_HOME}/cups-event-monitor

    # If on OES and we have an old monitor, then warn
    if [ -f "${OLD_MONITOR}" -a -d "/opt/novell/iprint/bin/" ]; then
        echo
        echo "WARNING: If you're running in a Novell OES cluster environment"
        echo "         your cluster resource scripts may need updating. See"
        echo "         the OES Cluster install notes for new script syntax."
        echo
    fi

    [ -f "${OLD_MONITOR}" ] && rm -f "${OLD_MONITOR}"
    [ -f "${OLD_MONITOR}.rc" ] && rm -f "${OLD_MONITOR}.rc"
    [ -f "${OLD_MONITOR}.pid" ] && rm -f "${OLD_MONITOR}.pid"
}

############################################################################
# Uninstall CUPS print provider
############################################################################
uninstall_cups() {
    # Remove backend and filter from all possible target locations
    locs="/usr/lib/cups/filter
            /usr/libexec/cups/filter
            /usr/local/lib/cups/filter
            /usr/local/cups/lib/cups/filter
            /opt/cups/lib/cups/filter
            /usr/lib64/cups/filter
            /usr/lib/cups/backend
            /usr/libexec/cups/backend
            /usr/local/lib/cups/backend
            /usr/local/cups/lib/cups/backend
            /opt/cups/lib/cups/backend
            /usr/lib64/cups/backend"

    hasremoved=
    for d in ${locs}; do
        if [ -e "${d}/${CUPS_PREFIX}" ]; then
            rm -f "${d}/${CUPS_PREFIX}" > /dev/null 2>&1
            hasremoved=y
        fi
    done
    if [ ! -z "${hasremoved}" ]; then
        ./configure-cups remove-all >/dev/null 2>&1
    fi

}

############################################################################
# Do the uninstall potentially of it all
############################################################################
uninstall_all(){
    if $doPrintProvider; then
        if is_iprint; then
            uninstall_iprint
        else
            uninstall_cups
        fi
    fi
    $doEventMonitor && uninstall_event_monitor
}

############################################################################
# Do the install potentially of it all
############################################################################
install_all() {
    $doConfigUpdate && update_conf
    if $doPrintProvider; then
        if is_iprint; then
            install_iprint
        else
            install_cups
        fi
    fi
    $doEventMonitor && install_event_monitor
}

############################################################################
# usage
############################################################################
usage() {
  echo >&2 'Usage: roottasks [options] [install|uninstall] [no-service-start]

Options:
-c   do configuration update
-e   start event monitor
-p   install Print Provider (iPrint or CUPS depending on installation)

Parameters:
install            install modules specified by options
uninstall          uninstall modules specified by options
no-service-start   do not start event monitor immediately after installation
'
   exit 1

}

############################################################################
# Main
############################################################################
doConfigUpdate=false
doEventMonitor=false
doPrintProvider=false
doAll=true

while getopts "cep" c
do
    case $c in
    c)
        doConfigUpdate=true
        doAll=false
        ;;
    e)
        doEventMonitor=true
        doAll=false
        ;;
    p)
        doPrintProvider=true
        doAll=false
        ;;
    *)
        usage
        #NOTREACHED
        ;;
    esac
done
shift `expr $OPTIND - 1`

if $doAll; then
    doConfigUpdate=true
    doEventMonitor=true
    doPrintProvider=true
fi

startService=true
case "${2}" in
    "no-service-start")
        startService=false
        ;;
    *)
        ;;
esac

case "${1}" in
    uninstall)
        uninstall_all
        ;;
    install)
        install_all
        ;;
    *)
        install_all
        ;;
esac
