#!/bin/sh

PATCH_NAME="Kaspersky for Mac 12.2.1 - Patch B"
PATCH_VER="Patch script v12.2.1b-1"

KAV_DIR="/Library/Application Support/Kaspersky Lab/KAV"
KAV_BINARIES_DIR="$KAV_DIR/Binaries"
KAV_APP_DIR="$KAV_DIR"/Applications
KAV_DATA_DIR="$KAV_DIR/Data"
KAV_APP_PKG="$KAV_DATA_DIR/kav_app.tar.gz"
KAV_AGENT_TAR="$KAV_DATA_DIR/kav_agent.tar.gz"
NEREMOVER_TAR="$KAV_DATA_DIR/ne_remover.tar.gz"
KAV_UPDATE_TAR="$KAV_DATA_DIR/kav_update.tar.gz"
KAV_CONFIGPDK_TAR="$KAV_DATA_DIR/configpdk.tar.gz"
KAV_UNINSTALLER_TAR="$KAV_DATA_DIR/kav_uninstaller.tar.gz"
KAVD_BINARIES_TAR="$KAV_DATA_DIR/binaries.tar.gz"
KAV_AGENT_APP_PATH="$KAV_APP_DIR/Kaspersky Anti-Virus Agent.app"
KAV_UPDATE_APP_PATH="$KAV_APP_DIR/Kaspersky Anti-Virus Update.app"
NEREMOVER_BUNDLE_DIR="$KAV_APP_DIR/Kaspersky Network Configuration Remover.app"
KAV_AGENT_WATCHPATH="$KAV_DIR/kickstart_gui"
LAUNCHAGENT_DIR="/Library/LaunchAgents"
LAUNCHAGENT_KAV_ID="com.kaspersky.kav.gui"
KAV_AGENT_PLIST="$LAUNCHAGENT_KAV_ID.plist"
KAV_AGENT_PLIST_BACKUP_DIR="$KAV_DIR"
KAVAPP_PATH="/Applications/Kaspersky Anti-Virus For Mac.app"
LOC_TGZ="$KAV_DATA_DIR/loc.tar.gz"
LOC_DIR="$KAV_DIR/Loc"
WEBALERTS_ARCHIVE="webalerts.tar.gz"
WEBALERTS_DIR="$KAV_DIR/WebAlerts"
DOC_DIR="$KAV_DIR/Doc"
CONFIG_XML_CHANGES_TMP="$KAV_BINARIES_DIR/kav_config_xml_changes.v2"
UPDATE_CFG_TARGET="$KAV_BINARIES_DIR/config.xml"
KAV_SETTINGS_DB_PATH="$KAV_DATA_DIR/settings.kvdb"
KATA_PROTOCOLLER_CACHE_DIR="$KAV_DATA_DIR/KataProtocoller"
KATA_PROTOCOLLER_CACHE_BUNDLE_DIR="$KAVAPP_PATH/Contents/MacOS/kavd.app/Contents/MacOS"
KILL_KAV_APP_RESULT=1

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

log "${PATCH_NAME}"
log "${PATCH_VER}"

get_environment_key()
{
    /usr/bin/xmllint --xpath "//propertiesmap/key[@name=\"environment\"]/tSTRING[@name=\"$1\"]/text()" "$UPDATE_CFG_TARGET"
}

get_last_patch_deployment()
{
    /usr/bin/xmllint --xpath "//propertiesmap/key[@name=\"Timestamps\"]/tDWORD[@name=\"LastPatchDeployment\"]/text()" "$UPDATE_CFG_TARGET"
}

need_update()
{
    local file="$1"
    local TIMESTAMP=$(/usr/bin/stat -f "%m" "$file")
    log "File '$file' timestamp: $TIMESTAMP, last path deployment timestamp: $LAST_PATCH_DEPLOYMENT_TIMESTAMP"

    if [[ (-e "$file") && ($TIMESTAMP > $LAST_PATCH_DEPLOYMENT_TIMESTAMP) ]]; then
        return 1;
    else
        return 0;
    fi
}

stop_kav_app()
{
    local pids=$(/usr/bin/pgrep kav_app | /usr/bin/awk '{print $1}')
    if [ "$pids" != "" ] ; then
        local oldIFS="$IFS"
        IFS=$'\n'
        for pid in `/bin/echo "$pids"`
        do
            log "Stopping kav_app with pid=$pid"
            /bin/kill -s KILL $pid
        done
        IFS="$oldIFS"
    fi
}

unload_agents()
{
    log "Disable and stop agents in launchctl ..."
    local records=$(/usr/bin/pgrep loginwindow)
    if [ "$records" != "" ] ; then
        local oldIFS="$IFS"
        IFS=$'\n'
        for rec in `/bin/echo "$records"`
        do
            local uid=$(/bin/ps -o uid= $rec | /usr/bin/awk '{print $1}')
            local target="gui/$uid/$LAUNCHAGENT_KAV_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
    /bin/sleep 1
    local counter=20 # 5 secs
    while [ $counter -gt 0 ] ; do
        local uids=$(/usr/bin/pgrep kav_agent | /usr/bin/awk '{print $1}')
        if [ -z "$uids" ] ; then
            log "No agents to stop"
            break
        fi

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

    [ $counter -gt 0 ] && return 0

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

kill_agents()
{
    # killall agents
    local pids=$(/usr/bin/pgrep kav_agent | /usr/bin/awk '{print $1}')
    if [ "$pids" != "" ] ; then
        local oldIFS="$IFS"
        IFS=$'\n'
        for pid in `/bin/echo "$pids"`
        do
            log "Stopping kav_agent with pid=$pid"
            /bin/kill -s KILL $pid
        done
        IFS="$oldIFS"
    fi
}

kill_kav_app()
{
    # killall kav_app
    local pids=$(/usr/bin/pgrep kav_app | /usr/bin/awk '{print $1}')
    if [ "$pids" != "" ] ; then
        local oldIFS="$IFS"
        IFS=$'\n'
        for pid in `/bin/echo "$pids"`
        do
            log "Stopping kav_app with pid=$pid"
            /bin/kill -s KILL $pid
            KILL_KAV_APP_RESULT=$?
        done
        IFS="$oldIFS"
    fi
}

start_kav_app()
{
    if (( $KILL_KAV_APP_RESULT == 0 )); then
        open "kav-app-product-action://"
    fi
}

disable_kav_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_kav_agents()
{
    local plist_path="$LAUNCHAGENT_DIR/$KAV_AGENT_PLIST"

    log "Restarting agents..."
    local records=$(/usr/bin/pgrep loginwindow)
    if [ "$records" != "" ] ; then
        local oldIFS="$IFS"
        IFS=$'\n'
        for rec in `/bin/echo "$records"`
        do
            local uid=$(/bin/ps -o uid= $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_KAV_ID"
            log "Enabling agent for uid=$uid with rc=$?"

            /bin/launchctl asuser "$uid" /bin/launchctl list | /usr/bin/grep -q "$LAUNCHAGENT_KAV_ID"
            if [ $? -eq 0 ] ; then
                log "Starting already loaded agent for uid $uid ..."
                /bin/launchctl asuser "$uid" /bin/launchctl start "$LAUNCHAGENT_KAV_ID" \
                    || log "Unable to start agent $LAUNCHAGENT_KAV_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..."
    /usr/bin/touch "$KAV_AGENT_WATCHPATH"
}


patch_loc()
{
    LOC_TGZ_TIMESTAMP=$(/usr/bin/stat -f "%m" "$LOC_TGZ")
    log "loc.tar.gz timestamp: $LOC_TGZ_TIMESTAMP"
    if [[ -e "$LOC_TGZ" && ($LOC_TGZ_TIMESTAMP > $LAST_PATCH_DEPLOYMENT_TIMESTAMP) ]];
    then
        LOC_EXTRACTION_TEMP_PATH=$(mktemp -d)

        log "Extracting $LOC_TGZ into $LOC_EXTRACTION_TEMP_PATH"
        /usr/bin/tar -xzvf "$LOC_TGZ" -C "$LOC_EXTRACTION_TEMP_PATH"
        log "Extracted: "
        /bin/ls -la "$LOC_EXTRACTION_TEMP_PATH"

        log "Deploying localizations..."
        if [ -f "$LOC_EXTRACTION_TEMP_PATH/$MATCHED_ARCHIVE" ]; then
            CURRENT_LOC_EXTRACTION_TEMP_PATH=$(mktemp -d)
            log "Extracting application resources from $MATCHED_ARCHIVE to $CURRENT_LOC_EXTRACTION_TEMP_PATH..."
            /usr/bin/tar -xzvf "$LOC_EXTRACTION_TEMP_PATH/$MATCHED_ARCHIVE" -C "$CURRENT_LOC_EXTRACTION_TEMP_PATH"
            /bin/cp -Rfv "$CURRENT_LOC_EXTRACTION_TEMP_PATH"/ "$LOC_DIR"
        else
            log "No matched archive in $LOC_EXTRACTION_TEMP_PATH, do not apply patch for $LOC_DIR"
        fi
        log "Deploying web alerts..."
        if [ -f "$LOC_EXTRACTION_TEMP_PATH/$WEBALERTS_ARCHIVE" ]; then
            CURRENT_LOC_EXTRACTION_TEMP_PATH=$(mktemp -d)
            log "Extracting application resources from $WEBALERTS_ARCHIVE to $CURRENT_LOC_EXTRACTION_TEMP_PATH..."
            /usr/bin/tar -xzvf "$LOC_EXTRACTION_TEMP_PATH/$WEBALERTS_ARCHIVE" -C "$CURRENT_LOC_EXTRACTION_TEMP_PATH"
            /bin/cp -Rfv "$CURRENT_LOC_EXTRACTION_TEMP_PATH"/ "$WEBALERTS_DIR"
        else
            log "No matched archive in $LOC_EXTRACTION_TEMP_PATH, do not apply patch for $WEBALERTS_DIR"
        fi
    else
        log "Skip extracting loc.tar.gz"
    fi
}


patch_kav_app()
{
    local KAV_APP_PKG_TIMESTAMP=$(/usr/bin/stat -f "%m" "$KAV_APP_PKG")
    log "kav app pkg timestamp: $KAV_APP_PKG_TIMESTAMP, last path deployment timestamp: $LAST_PATCH_DEPLOYMENT_TIMESTAMP"

    if [[ (-e "$KAV_APP_PKG") && ($KAV_APP_PKG_TIMESTAMP > $LAST_PATCH_DEPLOYMENT_TIMESTAMP) ]]; then
        log "Deleting current app binary"
        /usr/bin/chflags -R nouchg "$KAVAPP_PATH"
        /bin/rm -v "${KAVAPP_PATH}/Contents/MacOS/kav_app"

        log "Stopping application..."
        stop_kav_app

        log "Deleting current app bundle"
        /bin/rm -rfv "${KAVAPP_PATH}"

        log "Extracting $KAV_APP_PKG into $KAVAPP_PATH"
        /bin/mkdir -vp "${KAVAPP_PATH}"
        /usr/bin/tar --no-same-owner -xzvf "${KAV_APP_PKG}" --strip-components=1 -C "${KAVAPP_PATH}"

        PRODUCT_GROUP_NAME=$(/bin/ls -l "$KAV_DATA_DIR/reports.db" | /usr/bin/awk '{print $4}')
        GROUP_LIST_NAME=$(/usr/bin/dscl . list /Groups | /usr/bin/grep -E "^$PRODUCT_GROUP_NAME$")
        if [[ "$PRODUCT_GROUP_NAME" == "kaspersky"* && "$GROUP_LIST_NAME" == "$PRODUCT_GROUP_NAME" ]]; then
            log "Set $PRODUCT_GROUP_NAME to app bundle"
            /usr/sbin/chown root:$PRODUCT_GROUP_NAME "$KAVAPP_PATH/Contents/MacOS/kavd.app/Contents/MacOS/kavd"
            /usr/sbin/chown root:$PRODUCT_GROUP_NAME "$KAVAPP_PATH/Contents/MacOS/kav_app"
            /bin/chmod g+s "$KAVAPP_PATH/Contents/MacOS/kav_app"
        fi

        /usr/bin/touch "$KAVAPP_PATH"
        /usr/bin/chflags -R uchg "$KAVAPP_PATH"
        /usr/bin/chflags -R nouchg "$KAVAPP_PATH/Contents/MacOS/kavd.app/Contents/Library/SystemExtensions"
    else
        log "Skip extracting ${KAV_APP_PKG}"
    fi
}

patch_kav_agent()
{
    local KAV_AGENT_PKG_TIMESTAMP=$(/usr/bin/stat -f "%m" "$KAV_AGENT_TAR")
    log "kav agent pkg timestamp: $KAV_AGENT_PKG_TIMESTAMP, last path deployment timestamp: $LAST_PATCH_DEPLOYMENT_TIMESTAMP"

    if [[ (-e "$KAV_AGENT_TAR") && ($KAV_AGENT_PKG_TIMESTAMP > $LAST_PATCH_DEPLOYMENT_TIMESTAMP) ]];
    then
        KAV_AGENT_EXTRACTION_TEMP_PATH=$(mktemp -d)

        log "Deleting current agent"
        /bin/rm -rfv "${KAV_AGENT_APP_PATH}"

        log "Extracting agent to $KAV_APP_DIR..."
        /usr/bin/tar --no-same-owner -xzvf "$KAV_AGENT_TAR" -C "${KAV_AGENT_EXTRACTION_TEMP_PATH}"
        /bin/cp -rvf "${KAV_AGENT_EXTRACTION_TEMP_PATH}/Kaspersky Anti-Virus Agent.app" "${KAV_APP_DIR}"
        /bin/rm -rf "${KAV_AGENT_EXTRACTION_TEMP_PATH}"
        /usr/bin/touch "${KAV_AGENT_APP_PATH}"
    else
        log "Skip extracting ${KAV_AGENT_TAR}"
    fi
}

patch_ne_remover()
{
    NEREMOVER_TIMESTAMP=$(/usr/bin/stat -f "%m" "$NEREMOVER_TAR")
    log "network extension configuration remover timestamp: $NEREMOVER_TIMESTAMP, last path deployment timestamp: $LAST_PATCH_DEPLOYMENT_TIMESTAMP"

    if [[ (-e "$NEREMOVER_TAR") && ($NEREMOVER_TIMESTAMP > $LAST_PATCH_DEPLOYMENT_TIMESTAMP) ]];
    then

        local EXTRACTION_TEMP_PATH=$(/usr/bin/mktemp -d)

        log "Extracting extension configuration remover to $EXTRACTION_TEMP_PATH..."
        /usr/bin/tar -xzvf "$NEREMOVER_TAR" -C "$EXTRACTION_TEMP_PATH"

        if [ -f "$EXTRACTION_TEMP_PATH/$MATCHED_ARCHIVE" ]; then
            log "Deleting current network extension configuration remover"
            /bin/rm -rfv "${NEREMOVER_BUNDLE_DIR}" || /usr/bin/true

            log "Extracting extension configuration remover $MATCHED_ARCHIVE to $KAV_APP_DIR..."
            /usr/bin/tar -xzvf "$EXTRACTION_TEMP_PATH/$MATCHED_ARCHIVE" -C "$KAV_APP_DIR"
        else
            log "No matched archive in $EXTRACTION_TEMP_PATH, do not apply patch for $KAV_APP_DIR"
        fi

        log "Remove legecy network extension configuration..."
        remove_legace_ne_configurations
    fi
}


patch_configpdk()
{
    log "Extracting configpdk to $KAV_BINARIES_DIR..."
    /usr/bin/tar --no-same-owner -xzvf "${KAV_CONFIGPDK_TAR}" --strip-components=1 -C "${KAV_BINARIES_DIR}"

}

patch_binaries()
{
    BINARIES_TGZ_TIMESTAMP=$(/usr/bin/stat -f "%m" "$KAVD_BINARIES_TAR")
    log "binaries.tar.gz timestamp: $BINARIES_TGZ_TIMESTAMP"

    if [[ -e "$KAVD_BINARIES_TAR" && ($BINARIES_TGZ_TIMESTAMP > $LAST_PATCH_DEPLOYMENT_TIMESTAMP) ]];
    then
        log "Extracting $KAVD_BINARIES_TAR to $KAV_BINARIES_DIR..."
        /usr/bin/tar -xzvf "$KAVD_BINARIES_TAR" -C "$KAV_BINARIES_DIR" --no-same-owner
    else
        log "Skip extracting binaries.tar.gz"
    fi
}

patch_settings_kvdb()
{
    return
}

remove_legace_ne_configurations()
{
    log "Trying to remove legacy netwotk filter configurations..."
    "${NEREMOVER_BUNDLE_DIR}"/Contents/MacOS/NetworkConfigurationRemover remove filter
    log "Legacy network filter configuration removing operation finished with rc=$?"
    log "Trying to remove legacy transparent proxies configurations..."
    "${NEREMOVER_BUNDLE_DIR}"/Contents/MacOS/NetworkConfigurationRemover remove proxy
    log "Legacy transparent proxies configurations removing operation finished with rc=$?"
}

delete_old_stuff()
{
    log "Deleting personal certificates"
    /bin/rm -rfv /Library/Caches/com.kaspersky.kav/PersonalCertificates/*

    local RMVERBARG="-v"
    local CRASH_FILES_DIR="Library/Logs/DiagnosticReports"
    USERS_DIR="/Users"

    log "Removing kav crash logs from \"/$CRASH_FILES_DIR\"..."
    /bin/rm $RMVERBARG -f "/$CRASH_FILES_DIR/"{Retired,}/kav*
    /bin/rm $RMVERBARG -f "/$CRASH_FILES_DIR/"{Retired,}/kavd*
    /bin/rm $RMVERBARG -f "/$CRASH_FILES_DIR/"{Retired,}/com.kaspersky.kav.sysext*
    for USERHOME in $(/bin/ls "$USERS_DIR")
    do
        log "Removing kav crash logs from \"$USERS_DIR/$USERHOME/$CRASH_FILES_DIR\"..."
        /bin/rm $RMVERBARG -f "$USERS_DIR/$USERHOME/$CRASH_FILES_DIR/"{Retired,}/kav_app*
        /bin/rm $RMVERBARG -f "$USERS_DIR/$USERHOME/$CRASH_FILES_DIR/"{Retired,}/kav_agent*
        /bin/rm $RMVERBARG -f "$USERS_DIR/$USERHOME/$CRASH_FILES_DIR/"{Retired,}/KasperskySecurity*
    done
}

replace_kata_connector_persistent_data()
{
  log "Replace kata connector cache files from bundle"
  set -- "queues"  "reports"  "settings"  "tasks"
  if [ ! -d  "$KATA_PROTOCOLLER_CACHE_DIR" ]
  then
    /bin/mkdir -vp "$KATA_PROTOCOLLER_CACHE_DIR"
  else
    log "Cache already exist in Library"
    return
  fi

  for cache_dir in "$@"; do
    if [ -d "$KATA_PROTOCOLLER_CACHE_BUNDLE_DIR/$cache_dir" ]
    then
      log "Copy directory $cache_dir"
      /bin/cp -Rvf "$KATA_PROTOCOLLER_CACHE_BUNDLE_DIR/$cache_dir" "$KATA_PROTOCOLLER_CACHE_DIR"
    fi
  done
}

find_notification_baloon_path()
{
    for i in {0..250}; do
        ppath=$(printf "//propertiesmap/key[@name=\"settings\"]/key[@name=\"NotificationSettings\"]/key[@name=\"PerComponent\"]/key[@name=\"0001\"]/key[@name=\"PerSeverity\"]/key[@name=\"0000\"]/key[@name=\"PerId\"]/key[@name=\"%04d\"]" $i)
        x=$(/usr/bin/xmllint --xpath "$ppath" --noblanks "$1")
        if (( $? != 0 )); then return 255; fi
        if [[ $x == *"name=\"EventID\">317"* ]]; then
            return $i
        fi
    done
}

add_value_to_update()
{
    local KEY="$1"
    local VALUE="$2"
    local PATTERN="$3" # use '*' to update existing field
    local PATH_TO_NODE="$4" # e.g. "profiles/LocalizationManager/settings"
    local TYPE="$5"


    local SEARCH_RESULT=$(/usr/bin/grep "${PATTERN}" "$UPDATE_CFG_TARGET")
    if [ ! -z "$SEARCH_RESULT" ];
    then
        log "Skip updating ${KEY} value"
    else
        log "Updating in node '${PATH_TO_NODE}'  ${KEY} value to ${VALUE}"
        /usr/bin/touch "$CONFIG_XML_CHANGES_TMP"
        /bin/echo "${PATH_TO_NODE} ${KEY} ${VALUE} ${TYPE}" >> "$CONFIG_XML_CHANGES_TMP"
    fi

    return 0
}

update_config_xml()
{
    b=$(xmllint --xpath "//propertiesmap/key[@name=\"profiles\"]/key[@name=\"Protection\"]/key[@name=\"profiles\"]/key[@name=\"File_Monitoring\"]/key[@name=\"settings\"]/key[@name=\"def\"]/tBOOL[@name=\"TryDeleteContainer\"]/text()" "$UPDATE_CFG_TARGET")
    rb=$?
    if (( $rb == 0 )); then
        add_value_to_update "TryDeleteContainer" "1" "*" "profiles.Protection.profiles.File_Monitoring.settings.def" "tBOOL"
    fi

    if [ -f "${CONFIG_XML_CHANGES_TMP}" ];
    then
        log "Printing $CONFIG_XML_CHANGES_TMP"
        /bin/cat "$CONFIG_XML_CHANGES_TMP"
    else
        log "No ${CONFIG_XML_CHANGES_TMP} file"
    fi
}

drop_obsolete_bins()
{
    log "drop_obsolete_bins() ..."

    pushd "$KAV_BINARIES_DIR"
        [ $? != 0 ] && return 1

        rm -fv "libksnmock_http2server.dylib"
        rm -fv "libksn_mock_metainfo.dylib"
        rm -fv "libconnector_controller_mock.dylib"

    popd
}


if [ "$1" != "OnHotfixInstalled" ]; then
    PRODUCT_TYPE=$(get_environment_key "ProductType")
    log "ProductType:      $PRODUCT_TYPE"
    REGION_TYPE=INT
    log "ProductRegion:    $REGION_TYPE"
    MATCHED_ARCHIVE="${REGION_TYPE}".tar.gz
    log "Matched archive:  $MATCHED_ARCHIVE"
    LAST_PATCH_DEPLOYMENT_TIMESTAMP=$(get_last_patch_deployment)
    log "Last patch deployment timestamp:  $LAST_PATCH_DEPLOYMENT_TIMESTAMP"

    log "Disable agents..."
    disable_kav_agents

    log "Stopping kav_apps..."
    kill_kav_app

    drop_obsolete_bins

    replace_kata_connector_persistent_data

    patch_binaries
    patch_loc
    patch_kav_app
    patch_kav_agent
    patch_ne_remover
    update_config_xml
    patch_configpdk
    patch_settings_kvdb

    /usr/sbin/chown -R root:wheel "$KAV_APP_DIR"

    log "Enable agents..."
    enable_kav_agents
    start_kav_app
else
    LAST_PATCH_DEPLOYMENT_TIMESTAMP=$(get_last_patch_deployment)

    log "Last path deployment timestamp: $LAST_PATCH_DEPLOYMENT_TIMESTAMP"
fi

delete_old_stuff
