#!/bin/sh

set -x

PATCH_NAME="KSK for Mac 1.9 - Patch B"

KSK_DIR="/Library/Application Support/Kaspersky Lab/SafeKids"
DATA_DIR="${KSK_DIR}/Data"
BINARIES_DIR="$KSK_DIR/Binaries"

UPDATE_CFG_TARGET="${BINARIES_DIR}/config.xml"

APP_TAR="$DATA_DIR/ksk_app.tar.gz"
AGENT_TAR="$DATA_DIR/ksk_agent.tar.gz"
UPDATE_TAR="$DATA_DIR/ksk_update.tar.gz"
UNINSTALLER_TAR="$DATA_DIR/ksk_uninstaller.tar.gz"

APP_PATH="/Applications/Kaspersky Safe Kids for Mac.app"
AGENT_APP_PATH="$KSK_DIR/Applications/Kaspersky Safe Kids Agent.app"
UPDATE_APP_PATH="$KSK_DIR/Applications/Kaspersky Safe Kids Update.app"
UNINSTALLER_APP_PATH="$KSK_DIR/Applications/Kaspersky Uninstaller.app"

BINARIES_TAR="$DATA_DIR/binaries.tar.gz"
CONFIGPDK_TAR="$DATA_DIR/configpdk.tar.gz"
LOC_TAR="$DATA_DIR/loc.tar.gz"
DOC_TAR="$DATA_DIR/doc.tar.gz"
DATA_TAR="$DATA_DIR/data.tar.gz"

LOC_DIR="$KSK_DIR/Loc"
DOC_DIR="$KSK_DIR/Doc"

LAUNCHAGENT_ID="com.kaspersky.safekids.gui"
AGENT_PLIST="$LAUNCHAGENT_ID.plist"
LAUNCHAGENT_DIR="/Library/LaunchAgents"
AGENT_PLIST_BACKUP_DIR="$KSK_DIR"

APP_BIN_NAME="ksk_app"
AGENT_BIN_NAME="ksk_agent"


install_log()
{
    /usr/bin/logger -p 'install.warning' "KasperskySK.patch[$$] $@"
}

log()
{
    echo "`date '+%Y-%m-%d %H:%M:%S'` $@"
    install_log "$@"
}

log "${PATCH_NAME}"

delete_kvs_bases()
{
    log "[KSKMAC1.9a] Deleting KVS bases"
    for db in $(echo ac_file_registry ac_file_meta ac_cert_info ac_stat_history)
    do
        /bin/rm -v "${DATA_DIR}/$db".kvs || true

        for f in "$(/bin/ls "${DATA_DIR}/$db".*.kvw)"
        do
            echo "$f" | /usr/bin/egrep -q -i -e '\.[0-9a-f-]{36}\.kvw$' && /bin/rm -v "$f"
        done
    done
}


extract_non_bundle()
{
    local tgz="$1"
    local dest="$2"

    log "extracting '$tgz' to '$dest' ..."

    [[ ! -f "$tgz" ]] \
        && log "skip extracting $tgz - does not exist" && return 0

    [[ ! -d "$dest" ]] \
        && log "error: skip extracting - '$dest' bundle does not exist" && return 1

    local tmp=$(/usr/bin/mktemp -d)
    /usr/bin/tar -xzvf "$tgz" -C "$tmp"
        [ $? -ne 0 ] && log "error on extracting" && return 1

    /bin/cp -Rfv "$tmp"/ "$dest"
        [ $? -ne 0 ] && log "error on copying" && return 1

    log "extracted successfully"
}

extract_full_bundle()
{
    local tgz="$1"
    local dest="$2"

    local bundle_name=$(basename "$dest")


    log "extracting '$tgz' to '$dest' ..."

    [[ ! -f "$tgz" ]] \
        && log "skip extracting $tgz - does not exist" && return 0

    [[ ! -d "$dest" ]] \
        && log "error: skip extracting - '$dest' bundle does not exist" && return 1

    local tmp=$(/usr/bin/mktemp -d)
    /usr/bin/tar -xzvf "$tgz" -C "$tmp"
        [ $? -ne 0 ] && log "error on extracting" && return 1

    # check that proper dir exists
    local new_bundle_path="$tmp/$bundle_name"
    [[ ! -d "$new_bundle_path" ]] \
        && log "error: non exists path '$new_bundle_path' - wrong tgz" && return 1

    local dest_bak="$dest.bak.$RANDOM"

    # % ls -lOd x
    # drwxr-xr-x  2 user staff  uchg 64 Oct 20 21:27 x 
    local has_uchg_flag=$(ls -lOd "$dest" | awk '{print $5}' | grep "uchg")
    if [[ -n "$has_uchg_flag" ]]; then
        /usr/bin/chflags -v nouchg "$dest"
            [ $? -ne 0 ] && log "unable to disable unch flag in $dest" && return 1
    fi
        

    log "backuping to '$dest_bak' ..."

    /bin/mv -v "$dest" "$dest_bak"
        [ $? -ne 0 ] && log "mv old failed" && return 1

    local dest_parent=$(dirname "$dest")
    /bin/mv -v "$new_bundle_path" "$dest_parent"
    if [ $? -ne 0 ]; then
        log "mv to new failed"
        /bin/mv -v "$dest_bak" "$dest"
            [ $? -ne 0 ] && log "error: unable to rollback." && return 1

        if [[ -n "$has_uchg_flag" ]]; then
            /usr/bin/chflags -v uchg "$dest"
                [ $? -ne 0 ] && log "error: unable to restore 'uchg' flag" && return 1
        fi

        return 1
    fi

    if [[ -n "$has_uchg_flag" ]]; then
        /usr/bin/chflags -v uchg "$dest"
            [ $? -ne 0 ] && log "unable to restore 'uchg' flag" && return 1
    fi

    /bin/rm -rf "$dest_bak"
        [ $? -ne 0 ] && log "rm bak failed" && return 1
    
    log "extracted successfully"
}

###### helpers
do_killall()
{
    local app_name="$1"

    local pids=$(/bin/ps -axwo pid,command | /usr/bin/grep -v grep | /usr/bin/grep "Contents/MacOS/$app_name" | /usr/bin/awk '{print $1}')
    if [ "$pids" != "" ] ; then
        local oldIFS="$IFS"
        IFS=$'\n'
        for pid in `echo "$pids"`
        do
            log "Stopping '$app_name' with pid=$pid"
            /bin/kill -s KILL $pid
        done
        IFS="$oldIFS"
    fi
}

kill_apps()
{
    do_killall "$APP_BIN_NAME"
}

kill_agents()
{
    do_killall "$AGENT_BIN_NAME"
}

unload_agents()
{
    log "Disable and stop agents in launchctl ..."
    local records=$(/bin/ps -axwo uid,command | /usr/bin/grep -v grep | /usr/bin/grep loginwindow)
    if [ "$records" != "" ] ; then
        local oldIFS="$IFS"
        IFS=$'\n'
        for rec in `echo "$records"`
        do
            local uid=$(/bin/echo -n "$rec" | /usr/bin/awk '{print $1}')
            local target="gui/$uid/$LAUNCHAGENT_ID"

            # skip for initial 'loginwindow' dialog
            if [ "$uid" == "0" ] ; then
                log "Skip disabling agent when no user is logged on"
                continue
            fi

            /bin/launchctl disable "$target"
            log "Disabling agent for uid $uid finished with rc=$?"

            /bin/launchctl bootout "$target"
            log "bootout agent for uid $uid finished with rc=$?"
        done
        IFS="$oldIFS"
    fi

    # wait for stopping

    # 5 secs
    local counter=20
    while [ $counter -gt 0 ] ; do
        local uids=$(/bin/ps -axwo uid,command | /usr/bin/grep -v grep | /usr/bin/grep "Contents/MacOS/$AGENT_BIN_NAME" | /usr/bin/awk '{print $1}')
        if [ -z "$uids" ] ; then
            log "No agents to stop"
            break
        fi

        log "Wait for stopping agent for uids = $uids ... "
        sleep 0.25
        ((counter-=1))
    done

    [ $counter -gt 0 ] && return 0

    log "Unable to stop all agents."
    return 1
}

disable_agents()
{
    unload_agents && return 0

    # try to stop hung agents through killing
    log "Kill agents and try to unload again"
    kill_agents

    unload_agents
}

enable_agents()
{
    local plist_path="$LAUNCHAGENT_DIR/$AGENT_PLIST"

    log "Restarting agents..."
    local records=$(/bin/ps -axwo uid,command | /usr/bin/grep -v grep | /usr/bin/grep loginwindow)
    if [ "$records" != "" ] ; then
        local oldIFS="$IFS"
        IFS=$'\n'
        for rec in `echo "$records"`
        do
            local uid=$(/bin/echo -n "$rec" | /usr/bin/awk '{print $1}')

            # skip for initial 'loginwindow' dialog
            if [ "$uid" == "0" ] ; then
                log "Skip starting agent when no user is logged on"
                continue
            fi

            /bin/launchctl enable "gui/$uid/$LAUNCHAGENT_ID"
            log "Enabling agent for uid=$uid with rc=$?"

            /bin/launchctl asuser "$uid" /bin/launchctl list | /usr/bin/grep -q "$LAUNCHAGENT_ID"
            if [ $? -eq 0 ] ; then
                log "Starting already loaded agent for uid $uid ..."
                /bin/launchctl asuser "$uid" /bin/launchctl start "$LAUNCHAGENT_ID" \
                    || log "Unable to start agent $LAUNCHAGENT_ID"
            else
                log "Loading agent for uid $uid ..."
                /bin/launchctl bootstrap "gui/$uid" "$plist_path" \
                    || log "Unable to load agent $plist_path"
            fi
        done
        IFS="$oldIFS"
    fi
    log "Kickstarting agent..."
    USER=$(whoami)
    USER_UID=$(id -u "$USER")
    /bin/launchctl enable gui/"$USER_UID"/"$LAUNCHAGENT_ID"
    /bin/launchctl bootstrap gui/"$USER_UID" "$plist_path"
    /bin/launchctl enable gui/"$USER_UID"/"$LAUNCHAGENT_ID"
    /bin/launchctl kickstart gui/"$USER_UID"/"$LAUNCHAGENT_ID"

}

######## patch files
patch_loc()
{
    extract_non_bundle "$LOC_TAR" "$LOC_DIR"
}

patch_doc()
{
    extract_non_bundle "$DOC_TAR" "$DOC_DIR"
}

patch_data()
{
    extract_non_bundle "$DATA_TAR" "$DATA_DIR"
}

patch_configpdk()
{
    extract_non_bundle "$CONFIGPDK_TAR" "$BINARIES_DIR"
}

patch_binaries()
{
    extract_non_bundle "$BINARIES_TAR" "$BINARIES_DIR"
}

###### patch bundles
patch_app()
{
    extract_full_bundle "$APP_TAR" "$APP_PATH"
}

patch_agent()
{
    extract_full_bundle "$AGENT_TAR" "$AGENT_APP_PATH"
}

patch_updater()
{
    extract_full_bundle "$UPDATE_TAR" "$UPDATE_APP_PATH"
}

patch_uninstaller()
{
    extract_full_bundle "$UNINSTALLER_TAR" "$UNINSTALLER_APP_PATH"
}

main()
{
    local IS_REBRANDING=$(/usr/bin/grep \"UseSsoWithAuthCode\" "${UPDATE_CFG_TARGET}" | /usr/bin/sed 's!<[^>]*>!!g;s! !!g' | /usr/bin/tr -d '\r')
    if [[ "${IS_REBRANDING}" -eq "1" ]]; then
        log "rebranded product, skip update"
        return 0
    fi

    log "Disable agents..."
    disable_agents

    log "Stopping apps..."
    kill_apps

    log "patching bundles ..."
    patch_app
    patch_agent
    patch_updater
    patch_uninstaller

    log "patching files ..."
    patch_loc
    patch_doc
    patch_data
    patch_configpdk
    patch_binaries

    delete_kvs_bases

    log "Enable agents..."
    enable_agents
}

main
