#!/bin/sh

PATCH_NAME="Kaspersky for Mac 12.1 - Patch A"
PATCH_VER="Patch script v12.1a-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"
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"
DOC_DIR="$KAV_DIR/Doc"
CONFIG_XML_CHANGES_TMP="$KAV_BINARIES_DIR/kav_config_xml_changes"
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"

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
    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
}

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
    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"
        /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}"

        /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_settings_kvdb() 
{
    if [ -f "$KAV_SETTINGS_DB_PATH" ]
    then
        log "Update services settings..."
        local UPDATE_COMMAND=$(cat << EOF
            INSERT OR REPLACE INTO storage (id, keyhash, keylob, valuelob) VALUES (
                (SELECT id FROM storage WHERE keyhash=708774520),
                708774520,
                cast("avpmain.AutoStartController/factory_settings" as blob),
                '<root unique_id="2625529045"><files item_0000="/Library/LaunchDaemons/com.kaspersky.kav.kavd.plist" item_0001="/Library/LaunchAgents/com.kaspersky.kav.gui.plist" /></root>');
            INSERT OR REPLACE INTO storage (id, keyhash, keylob, valuelob) VALUES (
                (SELECT id FROM storage WHERE keyhash=3400288170),
                3400288170,
                cast("system_interceptors.endpoint_security.ESClient/factory_settings" as blob),
                '<root unique_id="3336568885" />');
EOF
        )
        /usr/bin/sqlite3 "$KAV_SETTINGS_DB_PATH" "${UPDATE_COMMAND}"
        log "Result: " $?
    else
        log "error: services settings file not found ($KAV_SETTINGS_DB_PATH)"
    fi
}

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
}

update_config_xml()
{
    b=$(find_notification_baloon_path "$UPDATE_CFG_TARGET")
    rb=$?
    if (( $rb < 255 )); then
        path=$(printf "settings/NotificationSettings/PerComponent/0001/PerSeverity/0000/PerId/%04d/Set" $rb)
        /usr/bin/touch "$CONFIG_XML_CHANGES_TMP"
        /bin/echo "$path Balloon 0" >> "$CONFIG_XML_CHANGES_TMP"
    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
}


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

    replace_kata_connector_persistent_data

    patch_loc
    patch_kav_app
    patch_kav_agent
    patch_ne_remover
    update_config_xml
    patch_configpdk
    patch_settings_kvdb
    
    log "Enable agents..."
    enable_kav_agents

    /usr/sbin/chown -R root:wheel "$KAV_APP_DIR"
else
    LAST_PATCH_DEPLOYMENT_TIMESTAMP=$(get_last_patch_deployment)

    log "Last path deployment timestamp: $LAST_PATCH_DEPLOYMENT_TIMESTAMP"
fi

delete_old_stuff