#!/bin/bash RELEASE_RING="Production" #!/bin/bash # shellcheck disable=2034 # (SC2034: Unused variable) #Functions used by the installation scripts in Linux only. function script_error() { ## Do not put any raw echo statement in this function. Some of the variables take value directly from a command. If the command fails, trap will be executed and thus, the echo from this function will get stored in that variable. local COMMAND_EXIT_CODE=$? local ERROR_LINENO=$1 local ERROR_COMMAND=$2 local ERROR_FUNC=$3 local timestamp timestamp=$(date +%s.%N) # Using [[ ... ]]] instead of [ ... ] for checking equalify of CLEANUP_ON_FAILURE # because it may happen that CLEANUP_ON_FAILURE is undefined and in that case [ ... ] will give an error. if [[ "${CLEANUP_ON_FAILURE:-0}" -eq 1 ]] && [[ "$-" =~ "e" ]]; then CleanUpInstallationFailure local ERR_TYPE="E" ## Fatal Error else local ERR_TYPE="W" ## Non-fatal Warning fi echo -e "${timestamp}\t${correlation_id}\t${bundle_version}\t${installation_stage}\t${ERR_TYPE}:error_code=${COMMAND_EXIT_CODE}, lineno=${ERROR_LINENO}, command='${ERROR_COMMAND}', func='${ERROR_FUNC}'" >> "$INSTALL_SCRIPT_ERROR_FILE" } function script_exit { EXIT_STATUS=$1 ListFailures 2>&1 | logger -t "$LOG_INSTALL_TAG" || true ##Any error in this should be ignored, or else the script may go into infinite loop. } function SetErrTrap { trap 'script_error ${LINENO} "${BASH_COMMAND}" "${FUNCNAME[0]}"' ERR } function SetExitTrap { trap 'script_exit $?' EXIT } function ValidateInstallationPath { local installation_path="$1" # Relative paths are not supported if [[ "$installation_path" != /* ]]; then echo "Error: Relative path - installation_path = $installation_path is not supported." return 1 fi # Extract resolved path using realpath or readlink local resolved_path if command -v realpath >/dev/null 2>&1 && realpath -m / >/dev/null 2>&1; then if ! resolved_path=$(realpath -m "$installation_path" 2>/dev/null); then echo "Error: Failed to resolve given installation_path = $installation_path with realpath -m" return 1 fi elif command -v readlink > /dev/null 2>&1 && readlink -f / >/dev/null 2>&1; then if ! resolved_path=$(readlink -f "$installation_path" 2>/dev/null); then echo "Error: Failed to resolve given installation_path = $installation_path with readlink -f" return 1 fi elif command -v realpath >/dev/null 2>&1; then if ! resolved_path=$(realpath "$installation_path" 2>/dev/null); then echo "Error: Failed to resolve given installation_path = $installation_path with realpath" return 1 fi else echo "Error: Neither realpath -m, readlink -f, nor realpath is available to resolve installation_path" return 1 fi if [ "$resolved_path" = "/" ] || [ "$resolved_path" != "$installation_path" ]; then echo "Error: installation_path = $installation_path is not a valid path." return 1 fi return 0 } function CheckAndSetCustomInstallationPath { local mde_path_json=$MDATP_INSTALLATION_PATH local validate_mde_path_json=$1 local install_path # Check if mde_path.json exists and consists of a valid installation path if [ -e "$mde_path_json" ]; then install_path=$(awk '/"path"/ {print $0}' "$mde_path_json" | grep -o '"path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') if [ -n "$install_path" ]; then if [ "$install_path" != "/" ]; then install_path="${install_path%/}" # Remove trailing slash if present fi # Validate the installation path in case of: # deb -> preinst, prerm scripts to cover installation and upgrade scenarios # rpm -> preinst script to cover installation and upgrade scenarios if [ "$validate_mde_path_json" = "true" ]; then ValidateInstallationPath "$install_path" 2>&1 | LogCustom "$LOG_INSTALL_FILE" "$LOG_INSTALL_TAG" # If the validation fails, exit with error code # Check the exit status of ValidateInstallationPath in the pipe chain if [ "${PIPESTATUS[0]}" -eq 1 ]; then echo "ValidateInstallationPath failed." | LogCustom "$LOG_INSTALL_FILE" "$LOG_INSTALL_TAG" exit 1 fi fi CUSTOM_INSTALL_DIR=$install_path return 0 fi fi CUSTOM_INSTALL_DIR="" } function SetGlobalPrerequisites { MDATP_INSTALLATION_PATH_FOLDER='/etc/opt/microsoft/mdatp' MDATP_INSTALLATION_PATH="$MDATP_INSTALLATION_PATH_FOLDER/mde_path.json" # Absolute path to the file containing custom installation path # Set logging paths LOG_DIR_PARENT="/var/log/microsoft" LOG_DIR="$LOG_DIR_PARENT/mdatp" LOG_INSTALL_TAG="microsoft-mdatp-installer" LOG_UNINSTALL_TAG="microsoft-mdatp-uninstaller" LOG_INSTALL_FILE=$LOG_DIR/install.log LOG_UNINSTALL_FILE=$LOG_DIR/uninstall.log PREINST_SCRIPT_NAME="preinstall" POSTINST_SCRIPT_NAME="postinstall" PRERM_SCRIPT_NAME="pre-remove" POSTRM_SCRIPT_NAME="post-remove" } function SetGlobalCommon { MDATP_SCRIPT="/usr/bin/mdatp" MDATP_ZSH_COMPLETION_DIR_PARENT="/usr/share/zsh" MDATP_ZSH_COMPLETION_FUNCTIONS_DIRS=("$MDATP_ZSH_COMPLETION_DIR_PARENT/vendor-completions" "$MDATP_ZSH_COMPLETION_DIR_PARENT/site-functions") MDATP_BASH_COMPLETION_DIR="/usr/share/bash-completion" MDATP_BASH_COMPLETION_FUNCTIONS_DIR="$MDATP_BASH_COMPLETION_DIR/completions" MDATP_COMPLETION_SCRIPT="$MDATP_BASH_COMPLETION_FUNCTIONS_DIR/mdatp" MDATP_DEFAULT_INSTALL_PARENT_PATH='/opt/microsoft' MDATP_DEFAULT_INSTALL_PATH="$MDATP_DEFAULT_INSTALL_PARENT_PATH/mdatp" MDATP_DEST_DIR="$CUSTOM_INSTALL_DIR/opt/microsoft/mdatp" MDATP_STATE_FOLDER="$CUSTOM_INSTALL_DIR/var/opt/microsoft/mdatp" MDATP_CONFIG_FOLDER="$CUSTOM_INSTALL_DIR/etc/opt/microsoft/mdatp" MDATP_DEST_DIR_PARENT="$MDATP_DEST_DIR/.." MDATP_EXEC_DIR="$MDATP_DEST_DIR/sbin" MDATP_CONF_DIR="$MDATP_DEST_DIR/conf" MDATP_LIB_DIR="$MDATP_DEST_DIR/lib" MDATP_CONF_SCRIPTS_DIR="$MDATP_CONF_DIR/scripts" MDATP_DEFINITIONS_DIR="$MDATP_DEST_DIR/definitions" JSON_WRITER_TOOL="$MDATP_EXEC_DIR/install_helper" MDATP_STATE_PARENT="$MDATP_STATE_FOLDER/.." MDATP_STATE="$MDATP_STATE_FOLDER/wdavstate" MDATP_INSTALLATION_STATE="$MDATP_STATE_FOLDER/mdatp_installation_state" MDATP_DEF_ARCHIVE_DIR_ROOT="$MDATP_STATE_FOLDER/signatures.noindex" OLD_VAR_DEFINITIONS_ROOT="$MDATP_STATE_FOLDER/definitions.noindex" NEW_MDATP_DEFINITIONS_FOLDER="$MDATP_DEST_DIR" NEW_DEFINITIONS_ROOT="$NEW_MDATP_DEFINITIONS_FOLDER/definitions.noindex" if VarIsMountedNoExec; then DEFINITIONS_ROOT_PARENT="$NEW_MDATP_DEFINITIONS_FOLDER" else DEFINITIONS_ROOT_PARENT="$MDATP_STATE_FOLDER" fi DEFINITIONS_ROOT="$DEFINITIONS_ROOT_PARENT/definitions.noindex" DEFINITIONS_TARGET_DIRECTORY="$DEFINITIONS_ROOT/00000000-0000-0000-0000-000000000000" MDATP_CONFIG_PARENT="$MDATP_CONFIG_FOLDER/.." MDATP_MANAGED_CONFIG_FOLDER="$MDATP_CONFIG_FOLDER/managed" MDATP_TEMP_CONFIG_FOLDER="$MDATP_CONFIG_FOLDER/tmp" INSTALL_SCRIPT_ERROR_FILE="$MDATP_TEMP_CONFIG_FOLDER/install_script_errors" MDATP_TEMP_TELEMETRY_FILE="$MDATP_TEMP_CONFIG_FOLDER/temp_telemetry" #Remove if $INSTALL_SCRIPT_ERROR_FILE already exists RemoveInstallScriptErrorFile RPM_SYSTEMD_SERVICE_PREFIX="/usr/lib/systemd/system/" DEB_SYSTEMD_SERVICE_PREFIX="/lib/systemd/system/" RSYSLOG_DIR="/etc/rsyslog.d" RSYSLOG_CONF="$RSYSLOG_DIR/10-mdatp.conf" LOGROTATE_DIR="/etc/logrotate.d" LOGROTATE_CONF="$LOGROTATE_DIR/mdatp" MDATP_CRASH_REPORT_PATH="$MDATP_STATE_FOLDER/crash" # Run job hourly CRON_LOGROTATE_SCRIPT="$MDATP_CONFIG_FOLDER/logrotate.sh" LOG_DIR_OPT="$MDATP_DEST_DIR/log" INSTALL_SCENARIO="Install" UNINSTALL_SCENARIO="Uninstall" REINSTALL_SCENARIO="Reinstall" PURGE_SCENARIO="Purge" UPGRADE_SCENARIO="Upgrade" DAEMON_USER='mdatp' PLATFORM="Linux" SUPPORT_TOOL_DIR="$MDATP_CONF_DIR/mde_tools/" CONFIGURE_SELINUX=0 CLOUD_METADATA_IP="169.254.169.254" correlation_id="" uuid="" CURLTOOL="$MDATP_EXEC_DIR/curltool" export SKIP_SIMPLIFIED=false # FLAGS } function LogPaths { echo "MDE Paths used: MDATP_DEST_DIR=$MDATP_DEST_DIR, MDATP_STATE_FOLDER=$MDATP_STATE_FOLDER, MDATP_CONFIG_FOLDER=$MDATP_CONFIG_FOLDER" } function WaitForTelemetry { local pid="$1" if [[ -z "$pid" ]]; then echo "Error: curl PID not provided." return 1 fi local retry=1 local max_retries=5 # Check if the process is still running while kill -0 "$pid" 2>/dev/null; do echo "Waiting for telemetry process to finish... Attempt:$retry" sleep 1 if [[ $retry -ge $max_retries ]]; then echo "Telemetry took too long. Killing process $pid" kill -9 "$pid" 2>/dev/null wait "$pid" 2>/dev/null return 1 fi retry=$((retry + 1)) done # Capture the exit status of the process wait "$pid" local status=$? if [[ $status -ne 0 ]]; then echo "Telemetry request failed with exit code: $status" return 1 fi return 0 } # shellcheck disable=SC2086 function LogTelemetrySimplifiedConnectivity { # $1: request = body of the request # $2: retry = true/false (whether to retry or not) local request=$1 local retry=$2 # Subject: L = Redmond, ST = Washington, C = US, O = Microsoft Corporation, CN = Microsoft Root Certificate Authority 2011 # Issuer: CN = Microsoft Root Certificate Authority 2011, O = Microsoft Corporation, L = Redmond, ST = Washington, C = US # Serial Number: 3F:8B:C8:B5:FC:9F:B2:96:43:B5:69:D6:6C:42:E1:44 # Validity: Not Before: 3/22/11, 3:05:28 PM PDT; Not After: 3/22/36, 3:13:04 PM PDT # Subject: C = US, ST = Washington, L = Redmond, O = Microsoft Corporation, CN = Microsoft Secure Server CA 2011 # Issuer: CN = Microsoft Secure Server CA 2011 # Serial Number: 33:00:00:02:2f:38:cd:ff:b5:b4:dc:67:0e:00:00:00:00:02:2f # Validity Not Before: Apr 4 19:28:29 2023 GMT; Not After : Apr 4 19:28:29 2024 GMT ca_cert='-----BEGIN CERTIFICATE----- MIIF7TCCA9WgAwIBAgIQP4vItfyfspZDtWnWbELhRDANBgkqhkiG9w0BAQsFADCB iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEw MzIyMjIwNTI4WhcNMzYwMzIyMjIxMzA0WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm aWNhdGUgQXV0aG9yaXR5IDIwMTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK AoICAQCygEGqNThNE3IyaCJNuLLx/9VSvGzH9dJKjDbu0cJcfoyKrq8TKG/Ac+M6 ztAlqFo6be+ouFmrEyNozQwph9FvgFyPRH9dkAFSWKxRxV8qh9zc2AodwQO5e7BW 6KPeZGHCnvjzfLnsDbVU/ky2ZU+I8JxImQxCCwl8MVkXeQZ4KI2JOkwDJb5xalwL 54RgpJki49KvhKSn+9GY7Qyp3pSJ4Q6g3MDOmT3qCFK7VnnkH4S6Hri0xElcTzFL h93dBWcmmYDgcRGjuKVB4qRTufcyKYMME782XgSzS0NHL2vikR7TmE/dQgfI6B0S /Jmpaz6SfsjWaTr8ZL22CZ3K/QwLopt3YEsDlKQwaRLWQi3BQUzK3Kr9j1uDRprZ /LHR47PJf0h6zSTwQY9cdNCssBAgBkm3xy0hyFfj0IbzA2j70M5xwYmZSmQBbP3s MJHPQTySx+W6hh1hhMdfgzlirrSSL0fzC/hV66AfWdC7dJse0Hbm8ukG1xDo+mTe acY1logC8Ea4PyeZb8txiSk190gWAjWP1Xl8TQLPX+uKg09FcYj5qQ1OcunCnAfP SRtOBA5jUYxe2ADBVSy2xuDCZU7JNDn1nLPEfuhhbhNfFcRf2X7tHc7uROzLLoax 7Dj2cO2rXBPB2Q8Nx4CyVe0096yb5MPa50c8prWPMd/FS6/r8QIDAQABo1EwTzAL BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUci06AjGQQ7kU BU7h6qfHMdEjiTQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQELBQADggIB AH9yzw+3xRXbm8BJyiZb/p4T5tPw0tuXX/JLP02zrhmu7deXoKzvqTqjwkGw5biR nhOBJAPmCf0/V0A5ISRW0RAvS0CpNoZLtFNXmvvxfomPEf4YbFGq6O0JlbXlccmh 6Yd1phV/yX43VF50k8XDZ8wNT2uoFwxtCJJ+i92Bqi1wIcM9BhS7vyRep4TXPw8h Ir1LAAbblxzYXtTFC1yHblCk6MM4pPvLLMWSZpuFXst6bJN8gClYW1e1QGm6CHmm ZGIVnYeWRbVmIyADixxzoNOieTPgUFmG2y/lAiXqcyqfABTINseSO+lOAOzYVgm5 M0kS0lQLAausR7aRKX1MtHWAUgHoyoL2n8ysnI8X6i8msKtyrAv+nlEex0NVZ09R s1fWtuzuUrc66U7h14GIvE+OdbtLqPA1qibUZ2dJsnBMO5PcHd94kIZysjik0dyS TclY6ysSXNQ7roxrsIPlAT/4CTL2kzU0Iq/dNw13CYArzUgA8YyZGUcFAenRv9FO 0OYoQzeZpApKCNmacXPSqs0xE2N2oTdvkjgefRI8ZjLny23h/FKJ3crWZgWalmG+ oijHHKOnNlA8OqTfSm7mhzvO6/DggTedEzxSjr25HTTGHdUKaj2YKXCMiSrRq4IQ SB/c9O+lxbtVGjhjhE63bK2VVOxlIhBJF7jAHscPrFRH -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIG2DCCBMCgAwIBAgIKYT+3GAAAAAAABDANBgkqhkiG9w0BAQsFADCBiDELMAkG A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9z b2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTExMDE4MjI1 NTE5WhcNMjYxMDE4MjMwNTE5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv cnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQgU2VjdXJlIFNlcnZlciBDQSAy MDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0AvApKgZgeI25eKq 5fOyFVh1vrTlSfHghPm7DWTvhcGBVbjz5/FtQFU9zotq0YST9XV8W6TUdBDKMvMj 067uz54EWMLZR8vRfABBSHEbAWcXGK/G/nMDfuTvQ5zvAXEqH4EmQ3eYVFdznVUr 8J6OfQYOrBtU8yb3+CMIIoueBh03OP1y0srlY8GaWn2ybbNSqW7prrX8izb5nvr2 HFgbl1alEeW3Utu76fBUv7T/LGy4XSbOoArX35Ptf92s8SxzGtkZN1W63SJ4jqHU mwn4ByIxcbCUruCw5yZEV5CBlxXOYexl4kvxhVIWMvi1eKp+zU3sgyGkqJu+mmoE 4KMczVYYbP1rL0I+4jfycqvQeHNye97sAFjlITCjCDqZ75/D93oWlmW1w4Gv9Dlw Sa/2qfZqADj5tAgZ4Bo1pVZ2Il9q8mmuPq1YRk24VPaJQUQecrG8EidT0sH/ss1Q mB619Lu2woI52awb8jsnhGqwxiYL1zoQ57PbfNNWrFNMC/o7MTd02Fkr+QB5GQZ7 /RwdQtRBDS8FDtVrSSP/z834eoLP2jwt3+jYEgQYuh6Id7iYHxAHu8gFfgsJv2vd 405bsPnHhKY7ykyfW2Ip98eiqJWIcCzlwT88UiNPQJrDMYWDL78p8R1QjyGWB87v 8oDCRH2bYu8vw3eJq0VNUz4CedMCAwEAAaOCAUswggFHMBAGCSsGAQQBgjcVAQQD AgEAMB0GA1UdDgQWBBQ2VollSctbmy88rEIWUE2RuTPXkTAZBgkrBgEEAYI3FAIE DB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNV HSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklo dHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29D ZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEF BQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29D ZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQBByGHB 9VuePpEx8bDGvwkBtJ22kHTXCdumLg2fyOd2NEavB2CJTIGzPNX0EjV1wnOl9U2E jMukXa+/kvYXCFdClXJlBXZ5re7RurguVKNRB6xo6yEM4yWBws0q8sP/z8K9SRia x/CExfkUvGuV5Zbvs0LSU9VKoBLErhJ2UwlWDp3306ZJiFDyiiyXIKK+TnjvBWW3 S6EWiN4xxwhCJHyke56dvGAAXmKX45P8p/5beyXf5FN/S77mPvDbAXlCHG6FbH22 RDD7pTeSk7Kl7iCtP1PVyfQoa1fB+B1qt1YqtieBHKYtn+f00DGDl6gqtqy+G0H1 5IlfVvvaWtNefVWUEH5TV/RKPUAqyL1nn4ThEO792msVgkn8Rh3/RQZ0nEIU7cU5 07PNC4MnkENRkvJEgq5umhUXshn6x0VsmAF7vzepsIikkrw4OOAd5HyXmBouX+84 Zbc1L71/TyH6xIzSbwb5STXq3yAPJarqYKssH0uJ/Lf6XFSQSz6iKE9s5FJlwf2Q HIWCiG7pplXdISh5RbAU5QrM5l/Eu9thNGmfrCY498EpQQgVLkyg9/kMPt5fqwgJ LYOsrDSDYvTJSUKJJbVuskfFszmgsSAbLLGOBG+lMEkc0EbpQFv0rW6624JKhxJK gAlN2992uQVbG+C7IHBfACXH0w76Fq17Ip5xCA== -----END CERTIFICATE-----' local result=0 # Default to success local telemetry_url='https://global.endpoint.security.microsoft.com/x/api/report' local cmd_mktemp local cert_file local curl_cmd cmd_mktemp=$(command -v mktemp) if [[ -z "$cmd_mktemp" ]]; then echo "mktemp not found!" >&2 return 1 fi cert_file="$("$cmd_mktemp" --suffix=".pem")" echo "$ca_cert" > "$cert_file" # $cert_file has permissions: 600, user: root, group: root. # Without chmod cURL command fails because it is unable to read $cert_file chmod +r "$cert_file" trap 'rm -f "$cert_file"' RETURN # Check if curl is available in the system # Use the custom curl tool if available if command -v curl &> /dev/null; then curl_cmd=( curl --cacert "$cert_file" -s -S -X POST -d "$request" --max-time 5 --connect-timeout 1 "$telemetry_url" -o /dev/null -w '%{http_code}') elif [ -x "$CURLTOOL" ]; then curl_cmd=( "$CURLTOOL" -s -X POST -d "$request" --url "$telemetry_url" --max-time 5 --connect-timeout 1) else echo "Error: Neither curl nor $CURLTOOL is available for telemetry submission." >&2 return 1 fi if [ "$retry" = "true" ]; then local retry_count=0 local retries=2 # Retry logic for curl command while [ $retry_count -lt $retries ]; do # Execute the curl command as the mdatp user using runuser or sudo echo "Attempt $((retry_count + 1)) of $retries..." local http_code curl_exit_status if command -v runuser > /dev/null; then http_code=$(runuser -u mdatp -- "${proxy_env[@]}" "${curl_cmd[@]}") else # Fallback to sudo if runuser is not available http_code=$(sudo -u mdatp "${proxy_env[@]}" "${curl_cmd[@]}") fi # Capture the exit status of the curl command curl_exit_status=$? # Check if curl command completed successfully if [ $curl_exit_status -eq 0 ] && [ "$http_code" = "200" ]; then echo "Telemetry submission succeeded with HTTP code $http_code." result=0 break else echo "Telemetry submission failed with HTTP code $http_code. Retrying... ($((retry_count + 1))/$retries)" result=1 retry_count=$((retry_count + 1)) sleep 1 fi done if [ $retry_count -eq $retries ]; then echo "Telemetry submission failed after $retries attempts." result=1 fi else # If retry is not required, run the curl command in the background if command -v runuser > /dev/null; then runuser -u mdatp -- "${proxy_env[@]}" "${curl_cmd[@]}" & else sudo -u mdatp "${proxy_env[@]}" "${curl_cmd[@]}" & #This is a fallback mechanism if runuser is not available. This could still fail if root password is expired. If such issues come later explore setpriv fi # Capture the PID of the curl command and return code of the last command local ret=$? local curl_pid=$! if [ $ret -eq 0 ]; then # if the command was started successfully wait for curl to finish if WaitForTelemetry $curl_pid; then echo "Telemetry submission completed successfully." else echo "Telemetry submission request failed or was aborted." result=1 fi else echo "Failed to start the telemetry submission process." fi fi # End of retry logic # Clean up the cert files created using mktemp if [ -f "$cert_file" ]; then rm -f "$cert_file" fi return $result } # shellcheck disable=SC2086 function LogTelemetryLegacy { # $1: request = body of the request local request=$1 local telemetry_url='https://x.cp.wd.microsoft.com/api/report' if command -v curl &> /dev/null then if command -v runuser > /dev/null; then # shellcheck disable=SC2154 runuser -u mdatp -- "${proxy_env[@]}" curl -s -S -d "$1" -X POST --max-time 5 --connect-timeout 1 "$telemetry_url" & else sudo -u mdatp "${proxy_env[@]}" curl -s -S -d "$1" -X POST --max-time 5 --connect-timeout 1 "$telemetry_url" & #This is a fallback mechanism if runuser is not available. This could still fail if root password is expired. If such issues come later explore setpriv fi WaitForTelemetry $! result=$? if [ $result != 0 ]; then echo "Telemetry submission took more than 5 sec, aborted" else echo "Telemetry submission command not running, either completed sucessfully or failed." fi else if [ -x "$CURLTOOL" ]; then if command -v runuser > /dev/null; then runuser -u mdatp -- "${proxy_env[@]}" $CURLTOOL -s -d "$request" -X POST --max-time 5 --connect-timeout 1 --url "$telemetry_url" & else sudo -u mdatp "${proxy_env[@]}" $CURLTOOL -s -d "$request" -X POST --max-time 5 --connect-timeout 1 --url "$telemetry_url" & #This is a fallback mechanism if runuser is not available. This could still fail if root password is expired. If such issues come later explore setpriv fi WaitForTelemetry $! result=$? if [ $result != 0 ]; then echo "Telemetry submission took more than 5 sec, aborted" else echo "Telemetry Submitted with curl tool" fi else result=$? echo "Telemetry could not be submitted, $result" fi fi } # (SC2154: Variables referenced but not assigned) # shellcheck disable=2154 function LogTelemetry { # $1: severity = I(nformational) | W(arnining) | E(rror) | C(ritical) # $2: code: trace code identifying the operation # $3: text: free form text # $4: timestamp: Unix timestamp in seconds since 1970-01-01 00:00:00 UTC # $5: id: correlation id # $6: version: app version # $7: stage: installation stage # $8: outfile: text file to send to telemetry local severity=$1 local code=$2 local text=$3 local timestamp=$4 local id=$5 local version=$6 local stage=$7 local outfile=$8 #convert text double quotes into single quotes otherwise it will result into parsing issues when we send it to telemetry server text=${text//\"/\'} case $severity in [IWEC]) ;; *) echo "[LogTelemetry] Invalid severity ($severity)" exit 1 ;; esac if [ -z "$code" ]; then echo "[LogTelemetry] Invalid code ($code)" exit 1 fi # Use current timestamp if 'timestamp' argument is not provided if [ -z "$timestamp" ]; then timestamp=$(date +%s.%N) fi # If id is not given, use the corelation id if [ -z "$id" ]; then id=$correlation_id fi # If no argument is given, use the update_version if it is non empty or bundle version if [ -z "$version" ]; then if [ -n "$update_version" ]; then version=$update_version else version=$bundle_version fi fi # Use the installation stage if 'stage' argument is not provided if [ -z "$stage" ]; then stage=$installation_stage fi if ! id "$DAEMON_USER" > /dev/null 2>&1 ; then if [ ! -d "$MDATP_TEMP_CONFIG_FOLDER" ]; then mkdir -p "$MDATP_TEMP_CONFIG_FOLDER" fi echo -e "${severity}\t${code}\t${text}\t${timestamp}\t${id}\t${version}\t${stage}\t${outfile}" >> "$MDATP_TEMP_TELEMETRY_FILE" return fi local outfilecontent= if [ "$outfile" ]; then outfilecontent=", file:"$(python -c "import json; f = open('$outfile', 'r'); print(json.dumps(f.read())); f.close()") fi RetrieveTelemetryId GetOrgId # (SC2016: Variables will not expand in single quotes) # shellcheck disable=2016 request='{ "client": { "appVersion": "'$version'", "hostname": "'$HOSTNAME'", "platform": "'$PLATFORM'", "machineGuid": "'$uuid'", "orgId": "'$org_id'", "releaseRing": "'$RELEASE_RING'", "productGuid":"c65eac3e-401e-4a0c-82e3-f106f693222f" }, "reports":[ { "$type":"installationReport", "timestamp": "'$timestamp'", "correlation_id": "'$id'", "version": "'$version'", "distro": "'$DISTRO' '$DISTRO_VERSION'", "scenario": "'$SCENARIO'", "severity": "'$severity'", "stage": "'$stage'", "code": "'$code'", "text": "'$text'"'$outfilecontent' } ] }' echo "[LogTelemetry] Submitting $request" if [[ "$SKIP_SIMPLIFIED" == "true" ]]; then LogTelemetryLegacy "$request" else if [ "$severity" = "C" ] || [ "$severity" = "E" ]; then # If the severity is critical or error, we need to send telemetry using retries LogTelemetrySimplifiedConnectivity "$request" true else # If the severity is informational, we do not need to retry and the telemetry can be sent in the background LogTelemetrySimplifiedConnectivity "$request" false fi local result=$? if [ $result != 0 ]; then export SKIP_SIMPLIFIED=true echo "Sending telemetry without simplifed connectivity, simplified submission finished with $result" LogTelemetryLegacy "$request" result=$? fi fi echo "[LogTelemetry] result=$result" } # Function : CreateDir # Description : This function creates a directory, log Telemetry and terminate installation on failure of critical directory creation. # Parameters : # param1 : dir_name - name of directory to be created function CreateDir { local dir_name=$1 local exit_status # Create directory mkdir -p "$dir_name" exit_status=$? if [ $exit_status -eq 0 ]; then return 0 else echo "ERROR: Failed to create $dir_name" >&2 LogTelemetry W "${SCENARIO}" "reason='Failed to create $dir_name'" return 1 fi } # Check if /var is mounted with noexec function VarIsMountedNoExec { if mount | grep -q ' on /var ' && mount | grep ' on /var ' | grep -q 'noexec'; then return 0 else return 1 fi } function SetProxyConfig { proxy_env=() # shellcheck disable=SC2154 if [[ -n "${https_proxy}" ]]; then proxy_env=(env "HTTPS_PROXY=${https_proxy}") elif [[ -n "${HTTPS_PROXY}" ]]; then proxy_env=(env "HTTPS_PROXY=${HTTPS_PROXY}") fi echo "set proxy to ${proxy_env[*]}" | LogCustom "$LOG_INSTALL_FILE" "$LOG_INSTALL_TAG" } # Log installation/uninstallation to syslog and a logging file function LogCustom { local FILE=$1 local TAG=$2 local DIR DIR=$( dirname "$FILE") if [ -d "$DIR" ]; then if [ ! -f "$FILE" ]; then touch "$FILE" chmod 0600 "$FILE" # Readable only by root fi tee -a "$FILE" 2>&1 | logger -t "$TAG" else logger -t "$TAG" fi } function GetDistro { if [ -f /etc/os-release ]; then # shellcheck source=/dev/null . "/etc/os-release" cat "/etc/os-release" DISTRO=$ID DISTRO_VERSION=$VERSION_ID DISTRO_TYPE="$ID_LIKE" # Minor version is not available in /etc/os-release on CentOS based systems, Pick up minor version from /etc/centos-release and trim it to total 3 chars # /etc/redhat-release -> /etc/centos-release # /etc/system-release -> /etc/centos-release if [ "$DISTRO" = "centos" ] && [ -f /etc/centos-release ]; then DISTRO_VERSION=$(grep -o "release .*" /etc/centos-release | cut -d ' ' -f2 | cut -c1-3) fi # This part is for RHEL-6 (downlevel) in which os-release file is not available elif [ -f /etc/redhat-release ]; then if [ -f /etc/oracle-release ]; then DISTRO="ol" elif grep -o -i -q "Red\ Hat" /etc/redhat-release ; then DISTRO="rhel" elif grep -o -i -q "Centos" /etc/redhat-release ; then DISTRO="centos" fi DISTRO_VERSION=$(grep -o "release .*" /etc/redhat-release | cut -d ' ' -f2) elif command -v lsb_release > /dev/null; then DISTRO=$(lsb_release -is) DISTRO_VERSION=$(lsb_release -rs) fi if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ] || [ "$DISTRO_TYPE" = "ubuntu" ]; then DISTRO_FAMILY="debian" else if [[ "$DISTRO" = "rhel" || "$DISTRO" = "centos" || "$DISTRO" = "ol" ]]; then DISTRO_FAMILY="redhat" elif [ "$DISTRO" = "fedora" ]; then DISTRO_FAMILY="fedora" elif [ "$DISTRO" = "mariner" ]; then DISTRO_FAMILY="mariner" elif [ "$DISTRO" = "almalinux" ]; then DISTRO_FAMILY="almalinux" elif [ "$DISTRO" = "rocky" ]; then DISTRO_FAMILY="rocky" else DISTRO_FAMILY="sles" fi fi echo "DISTRO: $DISTRO" echo "DISTRO_VERSION: $DISTRO_VERSION" echo "DISTRO_FAMILY: $DISTRO_FAMILY" if [ -z "$DISTRO" ] || [ -z "$DISTRO_VERSION" ] || [ -z "$DISTRO_FAMILY" ]; then echo "Unable to determine Linux distribution" LogTelemetry E UnknownDistro "unable to find distro info DISTRO:${DISTRO}, DISTRO_VERSION:${DISTRO_VERSION}, DISTRO_FAMILY:${DISTRO_FAMILY}" fi } function GenerateUUIDUsingDevRandom { local bytes byte_list pair i if [ ! -r /dev/urandom ]; then echo "Error: /dev/urandom is not readable" >&2 return 1 fi # Read 16 bytes from /dev/urandom and output a continuous 32-character hex string. bytes=$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n') if [ -z "$bytes" ] || [ "$(printf "%s" "$bytes" | wc -c)" -ne 32 ]; then echo "Error: Unable to retrieve 16 bytes of randomness" >&2 return 1 fi byte_list="" i=1 while [ "$i" -le 32 ]; do pair=${bytes:$((i-1)):2} # If pair is not 2 characters, it's an error if [ "$(printf "%s" "$pair" | wc -c)" -ne 2 ]; then echo "Error: Failed to extract byte pair" >&2 return 1 fi byte_list="$byte_list $pair" i=$((i + 2)) done local b6 b8 # Convert space-separated string to positional params IFS=' ' read -r -a byte_array <<< "$byte_list" set -- "${byte_array[@]}" # Version and variant modification b6=$(printf "%02x" $(( 0x$7 & 0x0f | 0x40 ))) b8=$(printf "%02x" $(( 0x$9 & 0x3f | 0x80 ))) printf "%s%s%s%s-%s%s-%s%s-%s%s-%s%s%s%s%s%s\n" \ "$1" "$2" "$3" "$4" \ "$5" "$6" \ "$b6" "$8" \ "$b8" "${10}" \ "${11}" "${12}" "${13}" "${14}" "${15}" "${16}" return 0 } function GenerateUUIDUsingKernelProc { local uuid_generated="" local cmd_status=0 # Generate a UUID using /proc/sys/kernel/random/uuid if [ -r /proc/sys/kernel/random/uuid ]; then uuid_generated=$(cat /proc/sys/kernel/random/uuid) cmd_status=$? if [ $cmd_status -eq 0 ] && [ -n "$uuid_generated" ]; then echo "$uuid_generated" return 0 fi fi echo "Error: Unable to generate UUID using kernel proc" >&2 return 1 } function GenerateUUIDUsingUuidgen { local uuid_generated="" local cmd_status=0 # Generate a UUID using uuidgen if command -v uuidgen > /dev/null 2>&1; then uuid_generated=$(uuidgen 2>/dev/null) cmd_status=$? if [ $cmd_status -eq 0 ] && [ -n "$uuid_generated" ]; then echo "$uuid_generated" return 0 fi fi echo "Error: Unable to generate UUID using uuidgen" >&2 return 1 } function GenerateUUID { local uuid_generated="" local cmd_status=0 # Generate a UUID using uuidgen uuid_generated=$(GenerateUUIDUsingUuidgen) cmd_status=$? if [ $cmd_status -eq 0 ] && [ -n "$uuid_generated" ]; then echo "$uuid_generated" return 0 fi # If uuidgen is not available, use /proc/sys/kernel/random/uuid to generate a UUID # This is a fallback mechanism for systems where uuidgen is not installed # or not available in the PATH. uuid_generated=$(GenerateUUIDUsingKernelProc) cmd_status=$? if [ $cmd_status -eq 0 ] && [ -n "$uuid_generated" ]; then echo "$uuid_generated" return 0 fi # If uuidgen is not available, use /dev/urandom to generate a random UUID uuid_generated=$(GenerateUUIDUsingDevRandom) cmd_status=$? if [ $cmd_status -eq 0 ] && [ -n "$uuid_generated" ]; then echo "$uuid_generated" return 0 else echo "Error: Unable to generate UUID" >&2 return 1 fi } # Function : GenerateCorrelationId # Description : This function retrieves or generates a machine correlation_id. # This id helps to uniquely identify all Telemetry corresponding to particular scenario. function GenerateCorrelationId { local cmd_status local ret_value=0 if [ -z "$correlation_id" ]; then if [ -f "$MDATP_TEMP_CONFIG_FOLDER/mdatp_correlation_id" ]; then correlation_id=$(cat "$MDATP_TEMP_CONFIG_FOLDER/mdatp_correlation_id") cmd_status=$? if [ $cmd_status -ne 0 ]; then echo "ERROR: Could not read from $MDATP_TEMP_CONFIG_FOLDER/mdatp_correlation_id" >&2 ret_value=1 fi else correlation_id=$(GenerateUUID) cmd_status=$? if [ $cmd_status -ne 0 ]; then echo "ERROR: Could not generate a new correlation id" >&2 ret_value=2 else if ! CreateDir "$MDATP_TEMP_CONFIG_FOLDER"; then echo "ERROR: Could not create directory $MDATP_TEMP_CONFIG_FOLDER" >&2 ret_value=3 else echo "$correlation_id" > "$MDATP_TEMP_CONFIG_FOLDER/mdatp_correlation_id" cmd_status=$? if [ $cmd_status -ne 0 ]; then echo "ERROR: Could not write to $MDATP_TEMP_CONFIG_FOLDER/mdatp_correlation_id" >&2 ret_value=4 fi fi fi fi if [ $ret_value -ne 0 ]; then echo "ERROR: GenerateCorrelationId failed with ReasonCode=$ret_value" >&2 LogTelemetry W GenerateCorrelationIdFailed "ReasonCode=$ret_value cmd_status=$cmd_status" return $ret_value fi fi echo "correlation id=$correlation_id" } # Validate if the given string is a valid UUID format: 8-4-4-4-12 function ValidateUUID { local uuid_string=$1 # Check if UUID is empty if [ -z "$uuid_string" ]; then return 1 fi # Check if UUID is zero if [ "$uuid_string" = "00000000-0000-0000-0000-000000000000" ]; then return 1 fi # Check if UUID is in the correct format if [[ $uuid_string =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then return 0 else return 1 fi } # Generate UUID from instance ID function GenerateUUIDFromInstanceId { local input_string=$1 local string128 string128=$(Pad128HexString "$input_string") if [ -z "$string128" ]; then echo "Error: Failed to generate padded string from input '$input_string'" >&2 return 1 fi local uuid_string="${string128:0:8}-${string128:8:4}-${string128:12:4}-${string128:16:4}-${string128:20:12}" echo "$uuid_string" } function Pad128HexString() { local input="$1" # Check for empty input if [ -z "$input" ]; then return 1 fi # Remove all non-alphanumeric characters local clean len clean=$(echo "$input" | tr -cd '0-9a-fA-F') len=${#clean} # check suffix if [ -z "$suffix" ]; then # If suffix is not set, default to '0' suffix="0" fi # Check the length of the cleaned string # If the length is 32, return it as is # If the length is less than 32, pad it with trailing suffix character # If the length is greater than 32, return an error if [[ $len -eq 32 ]]; then echo "$clean" elif [[ $len -lt 32 ]]; then # Pad the cleaned string with the suffix character until it reaches 32 characters while [ ${#clean} -lt 32 ]; do clean="${clean}${suffix}" done echo "${clean:0:32}" # Ensure it is exactly 32 characters long elif [[ $len -gt 32 ]]; then # If the length is greater than 32, truncate it to 32 characters echo "${clean:0:32}" else echo "Error: Input too long after cleanup (${len} characters). Cannot convert to 128-bit hex." >&2 return 1 fi return 0 } # Use instance ID directly if valid UUID, else generate one function ProcessInstanceId { local instance_id=$1 # Change to lowercase to ensure consistency instance_id=$(echo "$instance_id" | tr '[:upper:]' '[:lower:]') # Validate if the instance ID is a valid UUID if ValidateUUID "$instance_id"; then # If the instance ID is already a valid UUID, return it as is echo "$instance_id" else # If not, generate a UUID from the instance ID GenerateUUIDFromInstanceId "$instance_id" fi } function CurlWithRetry() { local path="$1" local header="$2" local retries=3 local delay=0.5 local url="http://${CLOUD_METADATA_IP}${path}" local response status err_cde for ((i=1; i<=retries; i++)); do if [ -n "$header" ]; then response=$(curl --connect-timeout 1 -s -w "%{http_code}" -H "$header" "$url") else response=$(curl --connect-timeout 1 -s -w "%{http_code}" "$url") fi status="${response: -3}" err_cde=$? if [[ $status -ge 200 && $status -lt 300 ]]; then echo "${response:: -3}" # success response (remove HTTP code) return 0 elif [[ $err_cde -eq 7 || $err_cde -eq 28 ]]; then sleep "$delay" else break fi done return 1 } function GetAWSInstanceId() { suffix="a" # Default suffix for AWS # AWS metadata service v2 requires a token for access local aws_token aws_token=$(curl -s --connect-timeout 1 -X PUT "http://${CLOUD_METADATA_IP}/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null) if [ -n "$aws_token" ]; then CurlWithRetry "/latest/meta-data/instance-id" "X-aws-ec2-metadata-token: $aws_token" else CurlWithRetry "/latest/meta-data/instance-id" "" fi } function GetGCPInstanceId() { suffix="c" # Default suffix for GCP CurlWithRetry "/computeMetadata/v1/instance/id" "Metadata-Flavor: Google" | tr -d '\n' # Remove trailing newline } function GetAzureInstanceId() { suffix="e" # Default suffix for Azure [Note: Added for consistency, this will not be used as Azure VM ID is already a valid UUID] local json json=$(CurlWithRetry "/metadata/instance?api-version=2021-02-01" "Metadata: true") echo "$json" | grep -oE '"vmId":"[^"]+"' | cut -d':' -f2 | tr -d '"' } function GetCloudInstanceId() { if ! command -v curl >/dev/null 2>&1; then echo "curl not found, cannot retrieve cloud instance ID" >&2 return 1 fi local instance_id processed_id provider_func for provider_func in GetAzureInstanceId GetAWSInstanceId GetGCPInstanceId; do instance_id=$($provider_func) if [ -n "$instance_id" ]; then processed_id=$(ProcessInstanceId "$instance_id") if ValidateUUID "$processed_id"; then echo "$processed_id" return 0 fi fi # Otherwise try the next provider done echo "Error: Unable to retrieve cloud instance ID" >&2 return 1 } function GetDmidecodeUUID() { if command -v dmidecode >/dev/null 2>&1; then local dmid dmid=$(dmidecode -s system-uuid 2>/dev/null | tr '[:upper:]' '[:lower:]') if [ -n "$dmid" ]; then echo "$dmid" return 0 fi fi return 1 } function GetSysfsUUID() { local uuid_paths=( "/sys/class/dmi/id/product_uuid" "/sys/devices/virtual/dmi/id/product_uuid" ) local path product_uuid for path in "${uuid_paths[@]}"; do if [[ -r "$path" ]]; then product_uuid=$(< "$path") if [[ -n "$product_uuid" ]]; then echo "$product_uuid" | tr '[:upper:]' '[:lower:]' return 0 fi fi done return 1 } # Retrieve hardware ID using various methods function GetDmiUuid { local id # Attempt to retrieve the UUID from sysfs id=$(GetSysfsUUID) if [ -n "$id" ] && ValidateUUID "$id"; then echo "$id" return 0 fi # Fallback to dmidecode (requires sudo) id=$(GetDmidecodeUUID) if [ -n "$id" ] && ValidateUUID "$id"; then echo "$id" return 0 fi # If no valid hardware identifier is found, print an error and exit echo "" return 1 } # Retrieve machine Guid from the MDATP_STATE file # Returns: 0 if successful, 1 otherwise function RetrieveGuidFromStateFile { local cmd_status retrieved_uuid ret_value=0 if [ -e "$MDATP_STATE" ]; then retrieved_uuid=$(grep -o '"machineGuid":".*"' "$MDATP_STATE" | cut -d '"' -f 4) cmd_status=$? if [ $cmd_status -eq 0 ] && ValidateUUID "$retrieved_uuid"; then uuid="$retrieved_uuid" else echo "ERROR: No valid UUID found in $MDATP_STATE" >&2 ret_value=1 fi uuid_type=$(grep -o '"machineUuidType":".*"' "$MDATP_STATE" | cut -d '"' -f 4) cmd_status=$? if [ $cmd_status -ne 0 ]; then echo "ERROR: Could not extract UUID type from $MDATP_STATE" >&2 ret_value=2 fi fi if [ "$ret_value" -ne 0 ]; then LogTelemetry W RetrieveGuidFromStateFileFailed "RetrieveGuidFromStateFile failed with ReasonCode=$ret_value" fi return $ret_value } # Retrieve saved machine Guid from the MDATP_TEMP_CONFIG_FOLDER function RetrieveGuidFromTemp { local retrieved_uuid retreived_uuid_type # Check if the saved UUID and UUID type files exist if [ ! -e "$MDATP_TEMP_CONFIG_FOLDER/mdatp_guid" ] || [ ! -e "$MDATP_TEMP_CONFIG_FOLDER/uuid_type" ]; then echo "No saved UUID or UUID type found in $MDATP_TEMP_CONFIG_FOLDER" >&2 return 1 fi retrieved_uuid=$(cat "$MDATP_TEMP_CONFIG_FOLDER/mdatp_guid" 2>/dev/null) if ! ValidateUUID "$retrieved_uuid"; then echo "Invalid or empty UUID found in $MDATP_TEMP_CONFIG_FOLDER/mdatp_guid" >&2 return 1 fi retreived_uuid_type=$(cat "$MDATP_TEMP_CONFIG_FOLDER/uuid_type" 2>/dev/null) if [ -z "$retreived_uuid_type" ] || [ "$retreived_uuid_type" = "null" ]; then echo "Invalid or missing UUID type in $MDATP_TEMP_CONFIG_FOLDER/uuid_type" >&2 return 1 fi # Set them to global variables uuid="$retrieved_uuid" uuid_type="$retreived_uuid_type" return 0 } # Save UUID and UUID type to the MDATP_TEMP_CONFIG_FOLDER function SaveUUID { local mdatp_guid=$1 local machine_uuid_type=$2 local ret_value=0 # Check if the temp config folder exists, if not create it if ! CreateDir "$MDATP_TEMP_CONFIG_FOLDER"; then echo "ERROR: Could not create directory $MDATP_TEMP_CONFIG_FOLDER" >&2 ret_value=1 elif [ -n "$mdatp_guid" ] && [ -n "$machine_uuid_type" ]; then local cmd_status echo "$mdatp_guid" > "$MDATP_TEMP_CONFIG_FOLDER/mdatp_guid" cmd_status=$? if [ $cmd_status -ne 0 ]; then echo "ERROR: Could not write to $MDATP_TEMP_CONFIG_FOLDER/mdatp_guid" >&2 ret_value=2 fi echo "$machine_uuid_type" > "$MDATP_TEMP_CONFIG_FOLDER/uuid_type" cmd_status=$? if [ $cmd_status -ne 0 ]; then echo "ERROR: Could not write to $MDATP_TEMP_CONFIG_FOLDER/uuid_type" >&2 ret_value=3 fi else echo "ERROR: UUID or UUID type is empty, cannot save to temp config folder" >&2 ret_value=4 fi if [ $ret_value -ne 0 ]; then echo "ERROR: SaveUUID failed with ReasonCode=$ret_value" >&2 LogTelemetry W SaveUUIDFailed "SaveUUID failed with ReasonCode=$ret_value" fi return $ret_value } ####################################### # This function retrieves machine GUID from MDATP_STATE file if it exists. # or generates a machine GUID and machine GUID Type. # Globals: # uuid : variable storing machine GUID # uuid_type : storing machine GUID type[Dmid, Imds, Uuid, Unknown] # MDATP_STATE : path to MDATP_STATE file # MDATP_TEMP_CONFIG_FOLDER : path to MDATP_TEMP_CONFIG_FOLDER # Arguments: # None ####################################### function GetMachineGuid { if RetrieveGuidFromStateFile && [ -n "$uuid" ]; then echo "MachineGuid retrieved from state file: $uuid" echo "MachineGuidType: $uuid_type" elif RetrieveGuidFromTemp && [ -n "$uuid" ]; then echo "MachineGuid retrieved from temp file: $uuid" echo "MachineGuidType: $uuid_type" else # If a valid UUID is not found in the state file or temp file, generate a new UUID local cmd_status uuid_generated # 1. Attempt to retrieve the UUID using dmidecode uuid_generated=$(GetDmiUuid) cmd_status=$? if [ $cmd_status -eq 0 ] && [ -n "$uuid_generated" ]; then uuid_type="Dmid" else echo "ERROR: Failed to retrieve product UUID" >&2 LogTelemetry W PlatformIdNotFound "Valid product uuid not found. cmd_status=$cmd_status" # 2. Fallback to IMDS for cloud instance ID uuid_generated=$(GetCloudInstanceId) cmd_status=$? if [ $cmd_status -eq 0 ] && [ -n "$uuid_generated" ]; then uuid_type="Imds" else echo "ERROR: Failed to retrieve cloud instance ID" >&2 LogTelemetry W CloudIdNotFound "Cloud instance id not found. cmd_status=$cmd_status" # 3. Fallback to random UUID generation if no other method works uuid_generated=$(GenerateUUID) cmd_status=$? if [ $cmd_status -eq 0 ] && [ -n "$uuid_generated" ]; then uuid_type="Uuid" else echo "ERROR: Failed to generate random UUID" >&2 LogTelemetry W GetMachineGuidFailed "FailedToGenerateRandomUUID cmd_status=$cmd_status" return 1 fi fi fi # Set the uuid variable to the generated UUID uuid="$uuid_generated" # Save the generated UUID and its type to the temp config folder if ! SaveUUID "$uuid" "$uuid_type"; then echo "ERROR: Failed to save UUID and UUID type to temp config folder" >&2 return 1 fi echo "Machine Guid: $uuid" echo "Machine Guid Type: $uuid_type" fi return 0 } function GetOrgId { if [ -z "$org_id" ]; then if [ -x "$JSON_WRITER_TOOL" ]; then org_id=$($JSON_WRITER_TOOL get-json --field enterprise.orgId --file "$MDATP_STATE") fi fi } function GetDefinitionPath { if [ -e "$MDATP_STATE" ]; then if [ -x "$JSON_WRITER_TOOL" ]; then definition_path=$($JSON_WRITER_TOOL get-json --field engineCore.databaseRootPath --file "$MDATP_STATE") fi fi if [ -n "$definition_path" ]; then if [ "$definition_path" != "$MDATP_STATE_FOLDER/definitions.noindex" ]; then if [ "$( basename "$definition_path" )" = "definitions.noindex" ]; then DEFINITIONS_TARGET_DIRECTORY="$definition_path/00000000-0000-0000-0000-000000000000" else DEFINITIONS_TARGET_DIRECTORY="$definition_path/definitions.noindex/00000000-0000-0000-0000-000000000000" fi fi fi } function RetrieveTelemetryId { local ret=1 if [ -z "$uuid" ]; then if command -v dmidecode > /dev/null; then uuid=$(dmidecode -s system-uuid) ret=$? fi if [ $ret -ne 0 ] && [ -r "/sys/class/dmi/id/product_uuid" ]; then uuid=$(cat /sys/class/dmi/id/product_uuid) fi echo "Installation id: $uuid" fi } function SetServicePaths { if [ "$DISTRO_PACKAGE" = "deb" ]; then SYSTEMD_SERVICE="$DEB_SYSTEMD_SERVICE_PREFIX/mdatp.service" SYSTEMD_SERVICE_PREFIX=$DEB_SYSTEMD_SERVICE_PREFIX else SYSTEMD_SERVICE="$RPM_SYSTEMD_SERVICE_PREFIX/mdatp.service" SYSTEMD_SERVICE_PREFIX=$RPM_SYSTEMD_SERVICE_PREFIX fi } function UpdateServicePaths { local src_file="$1" local dest_file="$2" local update_path="$3" cp "$src_file" "$dest_file" if [ -n "$update_path" ]; then sed -i "s|/opt/microsoft/mdatp|$update_path|g" "$dest_file" echo "Updating $dest_file from $src_file with custom installation path = $update_path succeeded." fi } function SetSystemdService { set -e local update_path="$1" UpdateServicePaths "$MDATP_CONF_DIR/mdatp.service" "$SYSTEMD_SERVICE" "$update_path" chmod 0644 "$SYSTEMD_SERVICE" set +e } function EnableServiceGeneric { local service="$1" LSYSTEMD_SERVICE="$SYSTEMD_SERVICE_PREFIX/$service" if [ -e "$LSYSTEMD_SERVICE" ]; then if ! systemctl is-enabled --quiet "$service"; then if ! systemctl enable "$service"; then echo "ERROR: Failed to enable the $service" >&2 LogTelemetry E "${service}EnableFailed" "Failed to enable the $service" return 1 else echo "Successfully enabled the $service" >&2 LogTelemetry I "${service}Enable" "Successfully enabled the $service" return 0 fi else LogTelemetry I "${service}Enable" "$service is already enabled" echo "$service is already enabled" >&2 return 0 fi else echo "ERROR: Failed to enable the $service, Reason=$LSYSTEMD_SERVICE does not exist" >&2 LogTelemetry E "${service}EnableFailed" "Reason=$LSYSTEMD_SERVICE does not exist" return 1 fi } function StartServiceGeneric { local service="$1" LSYSTEMD_SERVICE="$SYSTEMD_SERVICE_PREFIX/$service" if [ -e "$LSYSTEMD_SERVICE" ]; then if ! systemctl is-active --quiet "$service"; then if ! systemctl start "$service"; then echo "ERROR: Failed to start the $service" >&2 LogTelemetry E "${service}StartFailed" "Failed to start the $service" return 1 else echo "Successfully started the $service" >&2 LogTelemetry I "${service}Start" "Successfully started the $service" return 0 fi else echo "$service is already running" >&2 LogTelemetry I "${service}Start" "$service is already running" return 0 fi else echo "ERROR: Failed to start the $service, Reason=$LSYSTEMD_SERVICE does not exist" >&2 LogTelemetry E "${service}StartFailed" "Reason=$LSYSTEMD_SERVICE does not exist" return 1 fi } function SetAndStartNetfilterV2Service { echo "Configure: Starting MDE Netfilter socket based activation" >&2 LogTelemetry I "ConfigureNetfilterV2" "Configure: Starting MDE Netfilter V2 socket based activation" local update_path="$1" if [ ! -f "$SYSTEMD_SERVICE_PREFIX/mde_netfilter_v2.socket" ]; then cp "$MDATP_CONF_DIR/mde_netfilter_v2.socket" "$SYSTEMD_SERVICE_PREFIX" chmod 0644 "$SYSTEMD_SERVICE_PREFIX/mde_netfilter_v2.socket" fi if [ ! -f "$SYSTEMD_SERVICE_PREFIX/mde_netfilter_v2.service" ]; then UpdateServicePaths "$MDATP_CONF_DIR/mde_netfilter_v2.service" "$SYSTEMD_SERVICE_PREFIX/mde_netfilter_v2.service" "$update_path" chmod 0644 "$SYSTEMD_SERVICE_PREFIX/mde_netfilter_v2.service" fi systemctl daemon-reload if EnableServiceGeneric "mde_netfilter_v2.socket"; then EnableServiceGeneric "mde_netfilter_v2.service" StartServiceGeneric "mde_netfilter_v2.socket" fi LogTelemetry I "ConfigureNetfilterV2" "Successfully configured MDE Netfilter V2" } function GetCPUArch { if command -v uname &> /dev/null; then CPU_ARCH="$(uname -m)" else CPU_ARCH="Unavailable" echo "Error command uname not found" fi } function SetLibPcre { echo "Creating soft link of libpcre if it doesn't exists" GetCPUArch if [[ ! -f /lib/x86_64-linux-gnu/libpcre.so.1 ]] && [[ -f /lib/x86_64-linux-gnu/libpcre.so.3 ]]; then ln -sf /lib/x86_64-linux-gnu/libpcre.so.3 /lib/x86_64-linux-gnu/libpcre.so.1 fi } function SetLibfuse { echo "Creating soft link for libfuse if it doesn't exists" if [[ ! -f "$MDATP_LIB_DIR/libfuse.so.2" ]] && [[ -f "$MDATP_LIB_DIR/libfuse.so.2.9.2" ]]; then ln -sf "$MDATP_LIB_DIR/libfuse.so.2.9.2" "$MDATP_LIB_DIR/libfuse.so.2" fi } function CreateSoftLink { local name="$1" local source="$2" local target="$3" echo "Creating soft link for $name if it doesn't exists" if [[ -f "$MDATP_LIB_DIR/$source" ]]; then ln -sf "$MDATP_LIB_DIR/$source" "$MDATP_LIB_DIR/$target" fi } function NetFilterDependencies { CreateSoftLink "libmount" "libmount.so.1.1.0" "libmount.so.1" CreateSoftLink "libblkid" "libblkid.so.1.1.0" "libblkid.so.1" CreateSoftLink "libpcre2" "libpcre2-8.so.0.14.0" "libpcre2-8.so.0" CreateSoftLink "libpcre2" "libpcre2-8.so" "libpcre2-8.so.0" CreateSoftLink "libnfnetlink" "libnfnetlink.so.0.2.0" "libnfnetlink.so.0" CreateSoftLink "libnetfilter_queue" "libnetfilter_queue.so.1.5.0" "libnetfilter_queue.so.1" CreateSoftLink "libmnl" "libmnl.so.0.2.0" "libmnl.so.0" CreateSoftLink "libffi" "libffi.so.8.1.0" "libffi.so.8" CreateSoftLink "libglib-2" "libglib-2.0.so.0.7302.0" "libglib-2.0.so.0" } function GetDefaultLibPath { DEFAULT_LIB_PATH="" GetCPUArch if [[ ${CPU_ARCH} = "aarch64" ]]; then if [[ -d /lib/aarch64-linux-gnu ]]; then DEFAULT_LIB_PATH="/lib/aarch64-linux-gnu" elif [[ -d /lib64 ]]; then DEFAULT_LIB_PATH="/lib64" fi fi } function SetLibAtomic { GetDefaultLibPath if [[ "$DEFAULT_LIB_PATH" != "" ]] && [[ ! -f "$DEFAULT_LIB_PATH/libatomic.so.1" ]]; then echo "$DEFAULT_LIB_PATH/libatomic.so.1 -> $MDATP_LIB_DIR/libatomic.so.1" ln -sf "$MDATP_LIB_DIR/libatomic.so.1" "$DEFAULT_LIB_PATH/libatomic.so.1" fi } function UnSetLibAtomic { GetDefaultLibPath if [[ "$DEFAULT_LIB_PATH" != "" ]] && [[ -L "$DEFAULT_LIB_PATH/libatomic.so.1" ]]; then echo "Removing soft link of libatomic if it exists" referred_path=$(readlink "$DEFAULT_LIB_PATH/libatomic.so.1") if [[ "$referred_path" = "$MDATP_LIB_DIR/libatomic.so.1" ]]; then rm "$DEFAULT_LIB_PATH/libatomic.so.1" fi fi } function StartDaemon { if ! systemctl is-active --quiet mdatp; then if [ -e "$SYSTEMD_SERVICE" ]; then echo "Starting wdavdaemon" systemctl daemon-reload set -e systemctl enable mdatp.service systemctl start mdatp set +e LogTelemetry I DaemonInitialized echo "Daemon initialization completed" return 0 else echo "ERROR: Failed to start the mdatp service, Reason=$SYSTEMD_SERVICE does not exist" >&2 LogTelemetry E mdatpStartFailed "Reason=$SYSTEMD_SERVICE does not exist" return 1 fi else echo "wdavdaemon is already active" return 0 fi } function PrintStatus { echo "Daemon status:" systemctl is-active mdatp } function CheckMdatp { # Try to check if the mdatp is running (for 20 times with 3 seconds interval) for i in {1..20}; do # check the return code of mdatp health command if mdatp health --field healthy &> /dev/null; then # mdatp health command is running return 0 fi # mdatp command not found, retrying in 3 seconds... sleep 3 done return 1 } function GetMdatpHealthIssues { # Get mdatp health issues health_issues="$(mdatp health --field health_issues | sed '1{/^ATTENTION/d}')" # Output the health issues and return 0 if successful echo "$health_issues" } function CheckMdatpHealthStatus { # Get mdatp health status health_status="$(mdatp health --field healthy | sed '1{/^ATTENTION/d}')" # Check if mdatp health is true or false # return 0 if health_status is true # return 1 if health_status is false if [ "$health_status" = "true" ]; then return 0 else return 1 fi } function SendMdatpHealthStatus { # Check if mdatp is running and healthy # send telemetry if health is true # if health is false send telemetry with the health issues if ! CheckMdatp; then echo "mdatp command not found, mdatp did not come up after 60 seconds" LogTelemetry W HealthStatus "unknown" return fi if CheckMdatpHealthStatus; then LogTelemetry I HealthStatus "true" else health_issues="$(GetMdatpHealthIssues)" if [ -n "$health_issues" ]; then LogTelemetry I HealthStatus "false : $health_issues" else LogTelemetry I HealthStatus "false : No health issues found" fi fi } function DeleteUser() { if [ -e "$MDATP_INSTALLATION_STATE" ]; then # shellcheck source=/dev/null . "$MDATP_INSTALLATION_STATE" if [ "$DAEMON_USER_PRE_EXISTS" = "1" ]; then echo "Daemon user ($DAEMON_USER) existed prior to installation. Skipping user deletion" LogTelemetry I DeamonUserExists "Daemon user $DAEMON_USER existed prior to installation. Skipping user deletion" return 0 fi fi if id "$DAEMON_USER" > /dev/null 2>&1 ; then echo "Deleting daemon user" if command -v userdel &>/dev/null; then if userdel "$DAEMON_USER"; then # Note that after user is deleted succseefully, don't try to send telemetry # since user mdatp wouldn't exist anymore to invoke curl command echo "Daemon user deleted successfully" else LogTelemetry W UserDeletionFailed "Failed to delete daemon user $?" echo "Failed to delete daemon user" return 1 fi else LogTelemetry W CommandNotFound "userdel command not found" return 1 fi if grep -q "^$DAEMON_USER:" /etc/group ; then echo "Deleting daemon group" if command -v groupdel &>/dev/null; then if groupdel "$DAEMON_USER"; then echo "Daemon group deleted successfully" else echo "Failed to delete daemon group" return 1 fi else echo "groupdel command not found" return 1 fi fi else echo "Daemon user(${DAEMON_USER}) does not exist. Skipping deletion." fi } function GetSELinuxStatus { if command -v sestatus >/dev/null 2>&1; then # Configure regardless of whether SELinux is actively enforcing or not in case it is toggled CONFIGURE_SELINUX=1 else CONFIGURE_SELINUX=0 fi } function StopServiceGeneric { local service="$1" LSYSTEMD_SERVICE="$SYSTEMD_SERVICE_PREFIX/$service" if [ -e "$LSYSTEMD_SERVICE" ]; then local failed=0 if systemctl is-active --quiet "$service"; then if ! systemctl stop "$service"; then echo "ERROR: Failed to stop the $service service" >&2 LogTelemetry E "${service}StopFailed" "Failed to stop the $service service" failed=1 fi fi if systemctl is-enabled --quiet "$service"; then if ! systemctl disable "$service"; then echo "ERROR: Failed to disable the $service service" >&2 LogTelemetry E "${service}DisableFailed" "Failed to disable the $service service" failed=1 fi fi if [ $failed -eq 0 ]; then echo "$service stopped and disabled successfully" >&2 LogTelemetry I "${service}StopDisable" "$service stopped and disabled successfully" fi else echo "WARN: Failed to stop the $service, Reason=$LSYSTEMD_SERVICE does not exist" >&2 LogTelemetry W "${service}StopFailed" "Reason=$LSYSTEMD_SERVICE does not exist" fi } function CleanNetFilterV2Service { rm -f /var/run/mde_netfilter.sock rm -f "$SYSTEMD_SERVICE_PREFIX"/mde_netfilter_v2.* systemctl daemon-reload } function StopNetfilterLegacyService { StopServiceGeneric "mde_netfilter.socket" StopServiceGeneric "mde_netfilter.service" } function StopNetFilterV2Service { StopServiceGeneric "mde_netfilter_v2.socket" StopServiceGeneric "mde_netfilter_v2.service" if [ "$SCENARIO" = "$UNINSTALL_SCENARIO" ]; then CleanNetFilterV2Service fi } function StopService { if systemctl is-active --quiet mdatp; then if [ -e "$SYSTEMD_SERVICE" ]; then systemctl stop mdatp systemctl disable mdatp.service systemctl daemon-reload echo "mdatp stopped" if [ "$SCENARIO" = "$UNINSTALL_SCENARIO" ]; then rm -rf "$SYSTEMD_SERVICE" fi else echo "ERROR: Failed to stop the mdatp service, Reason=$SYSTEMD_SERVICE does not exist" >&2 LogTelemetry E mdatpStopFailed "Reason=$SYSTEMD_SERVICE does not exist" fi fi } function ListFailures { if [ -r "$INSTALL_SCRIPT_ERROR_FILE" ]; then echo "Sending Telemetry for failed commands:" local type local func local raw_text local text local timestamp local id local version local stage while IFS= read -r line do timestamp=$(cut -f1 <<< "$line") id=$(cut -f2 <<< "$line") version=$(cut -f3 <<< "$line") stage=$(cut -f4 <<< "$line") raw_text=$(cut -f5 <<< "$line") type="$(cut -d":" -f1 <<< "$raw_text")" func="$(grep -o "func=.*" <<< "$raw_text" | cut -d"'" -f2)" text="$(cut -d":" -f2 <<< "$raw_text")" LogTelemetry "$type" "${func}Failed" "$text" "$timestamp" "$id" "$version" "$stage" done < "$INSTALL_SCRIPT_ERROR_FILE" echo "List of commands that failed while executing $SCRIPT_NAME and their details:" cat "$INSTALL_SCRIPT_ERROR_FILE" RemoveInstallScriptErrorFile fi if ! [ "$EXIT_STATUS" = "0" ]; then rm -rf "$MDATP_CONFIG_FOLDER" if [ -d "$MDATP_CONFIG_PARENT" ]; then RemoveDirIfNotEmpty "$MDATP_CONFIG_PARENT" fi fi } function RemoveDirIfNotEmpty { rmdir --ignore-fail-on-non-empty "$1" } function RemoveInstallScriptErrorFile { rm -rf "$INSTALL_SCRIPT_ERROR_FILE" } function TestAndRemoveFile { File2Remove="$1" if [ -e "$File2Remove" ]; then rm -f "$File2Remove" fi } # Can be called from "fresh install" and/or "un-install" scenario function CleanUpTempFiles { TestAndRemoveFile "$MDATP_TEMP_CONFIG_FOLDER/mdatp_correlation_id" TestAndRemoveFile "$MDATP_TEMP_CONFIG_FOLDER/mdatp_installation_type" TestAndRemoveFile "$MDATP_TEMP_CONFIG_FOLDER/mdatp_guid" TestAndRemoveFile "$MDATP_TEMP_TELEMETRY_FILE" TestAndRemoveFile "$MDATP_STATE_FOLDER/shutdown_monitor.json" } function GetBundleVersion { local PACKAGE_VERSION=101.25062.0003 bundle_version=$PACKAGE_VERSION #$PACKAGE_VERSION is being set during build in build.sh echo "Package version = $bundle_version" } function SetInstalledProductVersion { # return if it is fresh install if [ "$SCENARIO" = "$INSTALL_SCENARIO" ]; then return fi if [ -r "$MDATP_DEST_DIR/conf/BuildInfo" ]; then # shellcheck source=/dev/null . "$MDATP_DEST_DIR/conf/BuildInfo" installed_version=$PRODUCT_VERSION echo "Installed product version: $installed_version" else echo "File $MDATP_DEST_DIR/conf/BuildInfo not found" LogTelemetry W FileNotExist "File $MDATP_DEST_DIR/conf/BuildInfo not found" fi } function SetInstallationStage { if [ -n "$1" ]; then installation_stage=$1 elif [ -n "$SCRIPT_NAME" ]; then installation_stage="$SCRIPT_NAME" fi } function DefaultDefinitionsDatabase { GetDefinitionPath if [ -e "$DEFINITIONS_TARGET_DIRECTORY" ]; then rm -rf "$DEFINITIONS_TARGET_DIRECTORY" fi # MDATP will be un-healthy if failed to create below directory if ! CreateDir "$DEFINITIONS_TARGET_DIRECTORY"; then echo "ERROR: Failed to update Definition DB" >&2 return 1 fi chmod -R 755 "$DEFINITIONS_ROOT_PARENT" if [ -f "$MDATP_DEFINITIONS_DIR/libmpengine.so" ]; then cp "$MDATP_DEFINITIONS_DIR/libmpengine.so" "$DEFINITIONS_TARGET_DIRECTORY" chmod 444 "$DEFINITIONS_TARGET_DIRECTORY/libmpengine.so" cp "$MDATP_DEFINITIONS_DIR/libmpengine.so.sig" "$DEFINITIONS_TARGET_DIRECTORY" chmod 444 "$DEFINITIONS_TARGET_DIRECTORY/libmpengine.so.sig" if [ -f "$MDATP_DEFINITIONS_DIR/mpavbase.vdm" ] && [ -f "$MDATP_DEFINITIONS_DIR/mpasbase.vdm" ] && [ -f "$MDATP_DEFINITIONS_DIR/mpavdlta.vdm" ] && [ -f "$MDATP_DEFINITIONS_DIR/mpasdlta.vdm" ]; then cp "$MDATP_DEFINITIONS_DIR/mpavbase.vdm" "$DEFINITIONS_TARGET_DIRECTORY" cp "$MDATP_DEFINITIONS_DIR/mpasbase.vdm" "$DEFINITIONS_TARGET_DIRECTORY" cp "$MDATP_DEFINITIONS_DIR/mpavdlta.vdm" "$DEFINITIONS_TARGET_DIRECTORY" cp "$MDATP_DEFINITIONS_DIR/mpasdlta.vdm" "$DEFINITIONS_TARGET_DIRECTORY" # Adjust permissions for all the files copied chmod 444 "$DEFINITIONS_TARGET_DIRECTORY/mpavbase.vdm" "$DEFINITIONS_TARGET_DIRECTORY/mpasbase.vdm" "$DEFINITIONS_TARGET_DIRECTORY/mpavdlta.vdm" "$DEFINITIONS_TARGET_DIRECTORY/mpasdlta.vdm" fi fi } # Function : DeleteCustomDefinitionsRoot # Description : Delete custom definitions directory if it exists. function DeleteCustomDefinitionsRoot { GetDefinitionPath local definitions_root definitions_root=$(dirname "$DEFINITIONS_TARGET_DIRECTORY") # if custom definitions path is set, delete the definitions root if [ "$definitions_root" != "$OLD_VAR_DEFINITIONS_ROOT" ] && [ "$definitions_root" != "$NEW_MDATP_DEFINITIONS_FOLDER" ]; then if [ -d "$definitions_root" ]; then rm -rf "$definitions_root" echo "Deleted residual definitions root $definitions_root" fi fi } # Function : IsDaemonRunning # Description : This function checks if wdavdaemon process is running. It checks for 2 seconds that the pid hasn't changed to conclude wdavdaemon is running successfully. # Parameters : None # Return : 0: Daemon running, 1: Daemon down function IsDaemonRunning { # Variables local pid_wdavdaemon_first local pid_wdavdaemon_second pid_wdavdaemon_first=$(pgrep -P 1 wdavdaemon) if [ -z "$pid_wdavdaemon_first" ]; then sleep 1 pid_wdavdaemon_first=$(pgrep -P 1 wdavdaemon) fi sleep 2 pid_wdavdaemon_second=$(pgrep -P 1 wdavdaemon) if [ -n "$pid_wdavdaemon_first" ] && [ -n "$pid_wdavdaemon_second" ] && [ "$pid_wdavdaemon_first" == "$pid_wdavdaemon_second" ]; then echo "wdavdaemon is running, pid=$pid_wdavdaemon_first" return 0 else echo "wdavdaemon is not running, pid1=$pid_wdavdaemon_first, pid2=$pid_wdavdaemon_second" return 1 fi } # Function : RestoreAllFilePermission # Description : This function restores mde files permissoins # Parameter : None function RestoreAllFilePermission { chmod 755 "$MDATP_STATE_PARENT" || true chmod 600 "$MDATP_STATE_FOLDER/wdavstate" || true chmod 600 "$MDATP_CONFIG_FOLDER/wdavcfg" || true chmod 600 "$MDATP_STATE_FOLDER/wdav_crash_state" || true chmod 600 "$MDATP_STATE_FOLDER/aad_device_id" || true chmod 600 "$MDATP_STATE_FOLDER/wdavsiginfo" || true chmod 600 "$MDATP_STATE_FOLDER/wdavhistory" || true chmod 600 "$MDATP_STATE_FOLDER/wdavengine" || true } function LogMachineInfo { # Check if the required commands are available if command -v uname &> /dev/null; then kernel_version="$(uname -r)" else kernel_version="Unavailable" fi # Get the kernel boot parameters from /proc/cmdline if [[ -f /proc/cmdline ]]; then kernel_cmdline="$(cat /proc/cmdline)" else kernel_cmdline="Unavailable" fi # Get CPU information if command -v arch &> /dev/null; then cpu_architecture="$(arch)" else cpu_architecture="Unavailable" fi if command -v nproc &> /dev/null; then cpu_cores="$(nproc)" else cpu_cores="Unavailable" fi if command -v lscpu &> /dev/null; then cpu_info=$(lscpu) cpu_name="$(echo "$cpu_info" | awk -F ':' '/Model name/ {gsub(/^[ \t]+/,"", $2); print $2}')" threads_per_core=$(echo "$cpu_info" | awk '/Thread\(s\) per core/ {print $4}') else cpu_name="Unavailable" threads_per_core="Unavailable" fi # Calculate total logical cores if [[ "$cpu_cores" =~ ^[0-9]+$ ]] && [[ "$threads_per_core" =~ ^[0-9]+$ ]]; then logical_cores=$((cpu_cores * threads_per_core)) else logical_cores="Calculation error" fi # Get RAM information if command -v free &> /dev/null; then mem_info=$(free -h) free_ram="$(echo "$mem_info" | awk '/^Mem/ {print $4}')" available_ram="$(echo "$mem_info" | awk '/^Mem/ {print $7}')" free_swap="$(echo "$mem_info" | awk '/^Swap/ {print $4}')" else free_ram="Unavailable" available_ram="Unavailable" free_swap="Unavailable" fi # Get Disk information free_disk="Unavailable" if [[ -d / ]]; then free_disk="$(df -h / | awk 'NR==2 {print $4}')" fi # Get locale information if command -v locale &> /dev/null; then locale_info="$(locale | tr '\n' ', ')" else locale_info="Unavailable" fi LogTelemetry I machineInformation "distro='$DISTRO $DISTRO_VERSION', cpu_name='$cpu_name', cpu_architecture='$cpu_architecture', cpu_cores='$cpu_cores', threads_per_core='$threads_per_core', logical_cores='$logical_cores', available_ram='$available_ram', free_ram='$free_ram', free_swap='$free_swap', free_disk='$free_disk', kernel_version='$kernel_version', kernel_cmdline='$kernel_cmdline', locale_info='$locale_info'" } # Function : CheckExecPermission # Description : This function checks if the given directory has execute permission for the mdatp user/others # Parameters : Directory path # Return : 0 if the directory has execute permission for the mdatp user, 1 otherwise function CheckExecPermission { local dir="$1" local user="$DAEMON_USER" local ret_value=0 # Check if directory exists if [ ! -d "$dir" ]; then echo "Directory $dir does not exist" LogTelemetry "W" "LogPath" "Directory $dir does not exist" return 1 fi # Check execute permissions using available tools if command -v runuser > /dev/null; then # Check if mdatp user has execute permission using runuser if ! runuser -u "$user" -- test -x "$dir"; then ret_value=1 fi elif command -v getfacl > /dev/null; then # Check if others have exec permission using getfacl if ! getfacl -c "$dir" | grep -q "other::.*x"; then ret_value=1 fi else # Check if others have execute permission using stat if [ $(( $(stat -c "%a" "$dir") % 2 )) -eq 0 ]; then ret_value=1 fi fi if [ $ret_value -ne 0 ]; then echo "Exec permission missing for $dir directory" LogTelemetry "W" "LogPath" "Exec permission missing for $dir directory" else echo "Exec permission present for $dir directory" fi return $ret_value } # Function : ValidateUpgradePathWithInstalled # Description : This function checks if upgrade path matches with the installed path. # Parameters : Upgrade path # Return : 0 if upgrade path is valid, 1 otherwise function ValidateUpgradePathWithInstalled { echo "Starting upgrade validation" local upgrade_path=$1 local current_installation_path # $MDATP_CONF_DIR/mde_path.json is resolved for current installation path only if previous sym link if valid exists if ! [ -e "$MDATP_CONF_DIR/mde_path.json" ]; then if [ -z "$upgrade_path" ]; then echo "Upgrade and install paths are at default paths, validation completed" return 0 fi LogTelemetry E UpgradePathMismatch "Installed path information missing during upgrade" echo "[Error] mde_path.json should exist from installation in $MDATP_CONF_DIR. Upgrade path changed, thus aborting operation" return 1 else # Extracting the value of "path" using awk and grep since we don't have access to install_helper tool. # This is because we do not know the path of existing install yet. current_installation_path=$(awk '/"path"/ {print $0}' "$MDATP_CONF_DIR/mde_path.json" | grep -o '"path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') if [ "$current_installation_path" != "/" ]; then current_installation_path="${current_installation_path%/}" # Remove trailing slash if present fi if [ "$current_installation_path" != "$upgrade_path" ]; then LogTelemetry E UpgradePathMismatch "Upgrade path mismatch with installed path" echo "Upgrade path $upgrade_path mismatch with installed path $current_installation_path" return 1 else echo "Upgrade path $upgrade_path matches with installed path $current_installation_path, validation complete" fi fi } # Do not remove below empty line otherwise it will result into syntax error because preinst,postinst,prerm and postrm files are concatenated into this file. Removng below empty line will result into something like "}#!/bin/bash" #!/bin/bash ## Executed immediately after preinst in case of INSTALL in all distributions ## Postinst of upgrade package: Last script to execute (after postrm of previously installed package) in case of UPGRADE/REINSTALL in ubuntu and debian distributions ## Postinst of upgrade package: Executed immediately after preinst in case of UPGRADE/REINSTALL in all rpm based distributions # (SC2034: Unused variable) # shellcheck disable=2034 function SetGlobalSpecific { SCRIPT_NAME="$POSTINST_SCRIPT_NAME" if [ -z "$MDATP_CLEANUP" ]; then export MDATP_CLEANUP=1 ##Environmental variable to control cleanup in case of exit due to fatal error fi CLEANUP_ON_FAILURE=1 SetInstallationStage } function GetScenario { if [ -z "$SCENARIO" ]; then if [ -r "$MDATP_TEMP_CONFIG_FOLDER/mdatp_installation_type" ]; then SCENARIO=$(cat "$MDATP_TEMP_CONFIG_FOLDER/mdatp_installation_type") else SCENARIO=$INSTALL_SCENARIO fi fi echo "Scenario= $SCENARIO" } function SetBundleVersion { GetBundleVersion echo "Package is being installed." } function ConfigureTools { rm -f "$MDATP_SCRIPT" ln -sf "$MDATP_DEST_DIR/sbin/wdavdaemonclient" "$MDATP_SCRIPT" if [ -d "$MDATP_BASH_COMPLETION_DIR" ]; then echo "Trying to auto configure bash-completion at ${MDATP_BASH_COMPLETION_FUNCTIONS_DIR}" if [ ! -d "$MDATP_BASH_COMPLETION_FUNCTIONS_DIR" ]; then # Continue with installation even if below directory creation failed CreateDir "$MDATP_BASH_COMPLETION_FUNCTIONS_DIR" fi rm -f "$MDATP_COMPLETION_SCRIPT" ln -sf "$MDATP_DEST_DIR/resources/mdatp_completion.bash" "$MDATP_COMPLETION_SCRIPT" else echo "bash-completion is not installed, auto-complete cannot be configured" fi for functions_dir in "${MDATP_ZSH_COMPLETION_FUNCTIONS_DIRS[@]}" do echo "Trying to auto configure zsh-completion at $functions_dir" if [ -d "$functions_dir" ]; then rm -f "$functions_dir/_mdapt" cp "$MDATP_DEST_DIR/resources/mdatp_completion.zsh" "$functions_dir/_mdapt" else echo "zsh-completion is not installed at $functions_dir, auto-complete cannot be configured" fi done if [ -d "$SUPPORT_TOOL_DIR" ]; then # Configure SupportTool Environment python3 -m pip install --target="$SUPPORT_TOOL_DIR" lxml fi } function CheckDefinitionsDatabase { if [ -d "$MDATP_DEF_ARCHIVE_DIR_ROOT" ]; then rm -rf "$MDATP_DEF_ARCHIVE_DIR_ROOT" fi } function EnableCgroupCPUController { # will be a no-op if already enabled if [[ -f /sys/fs/cgroup/cgroup.subtree_control ]]; then if ! grep -q "cpu " /sys/fs/cgroup/cgroup.subtree_control; then echo "+cpu" >> /sys/fs/cgroup/cgroup.subtree_control fi fi } function RestoreConForMDATPFilesImpl { echo "Setting SELinux labels" if command -v restorecon > /dev/null; then restorecon -Rv "$MDATP_DEST_DIR" else echo "WARNING: restorecon command not found" >&2 LogTelemetry W "CommandNotFound" "RestoreConForMDATPFilesImpl: restorecon command not found" fi } function AddSELinuxPoliciesMdatp { local latest_mdatp_policy="mdatp_service" #whenever any update in mdatp selinux policies, this variable should be updated. local latest_audisp_mdatp_rhel77="audisp_mdatp_rhel77" #Whenever any update in selinux policies for rhel7.7, this variable should be updated. # SELinux systems usually have these global rules for items in /opt: # # /opt/(.*/)?sbin(/.*)? all files system_u:object_r:bin_t:s0 # /opt/(.*/)?lib(/.*)? all files system_u:object_r:lib_t:s0 # # However, there is no guarantee that these rules are set, and the rest of # the files in the mdatp destination dir may need explicit rules. # # bin_t is required for systemd executables. Programs executing in the # bin_t context run with unconstrained privileges. lib_t should be assigned # to shared libraries. # # There is no need to set a policy for the mdatp.service file so it has # the domain systemd_unit_file_t, because there should be a global rule. If # there isn't, then none of the systemd services would work anyway. GetSELinuxStatus # Set CONFIGURE_SELINUX variable based on whether sestatus utility is available or not if [ "$CONFIGURE_SELINUX" -eq 1 ]; then # sestatus utility is available echo "Installing MDATP SELinux policy" if ! semodule -l | grep $latest_mdatp_policy; then semodule -i "$MDATP_DEST_DIR/conf/selinux_policies/out/mdatp_service.pp" || echo "Warning: Failed installing SELinux policy" fi if [[ ! ( "$DISTRO_FAMILY" = "redhat" && ( "$DISTRO_VERSION" =~ 7\.[0-6] || "$DISTRO_VERSION" == 6* ) ) ]] && ! semodule -l | grep $latest_audisp_mdatp_rhel77; then echo "Installing MDATP SELinux policy for RHEL7.7" semodule -i "$MDATP_DEST_DIR/conf/selinux_policies/out/audisp_mdatp_rhel77.pp" || echo "Warning: Failed installing SELinux policy for non-RHEL76 systems" fi if [ -n "$CUSTOM_INSTALL_DIR" ]; then if ! AddSELinuxEquivalence "$MDATP_DEST_DIR" ; then LogTelemetry E "CustomPathInstallation" "AddSELinuxEquivalence failed" fi fi RestoreConForMDATPFilesImpl fi } function AddSELinuxEquivalence { local install_path="$1" if command -v semanage >/dev/null; then # We are modifying equivalence rule even though it was already set. How? if semanage fcontext -a -e "$MDATP_DEFAULT_INSTALL_PATH" "$install_path" ; then echo "Setting SELinux equivalence succeeded" return 0 else echo "Setting SELinux equivalence failed. This could lead to wdavdaemon being terminated due to SELinux policies." LogTelemetry W "AddSELinuxEquivalence" "Setting SELinux equivalence failed" return 1 fi else echo "semanage command not found, cannot set SELinux equivalence. This could lead to wdavdaemon being terminated due to SELinux policies." LogTelemetry W "CommandNotFound" "AddSELinuxEquivalence: semanage command not found" return 2 fi } function JsonWriter { # JsonWriter # $1: file to write to # $2: field to write # $3: type of field # $4: value to write local file=$1 local field=$2 local type=$3 local value=$4 if [ -x "$JSON_WRITER_TOOL" ]; then $JSON_WRITER_TOOL set-json --file "$file" --field "$field" --type "$type" --value "$value" || { echo "ERROR: Failed to write field $field to file $file" >&2 return 1 } else echo "ERROR: $JSON_WRITER_TOOL command not found" >&2 LogTelemetry E "${SCENARIO}Failed" "JsonWriterTool: $JSON_WRITER_TOOL command not found" return 1 fi return 0 } function JsonReader { # JsonReader # $1: file to read from # $2: field to read local file=$1 local field=$2 local value if [ -x "$JSON_WRITER_TOOL" ]; then value=$($JSON_WRITER_TOOL get-json --file "$file" --field "$field") local exit_code=$? if [ $exit_code -ne 0 ]; then echo "ERROR: Failed to read field $field from file $file" >&2 return 1 fi echo "$value" else echo "ERROR: $JSON_WRITER_TOOL command not found" >&2 LogTelemetry E "${SCENARIO}Failed" "JsonTool: $JSON_WRITER_TOOL command not found" return 1 fi return 0 } function GetPlatformIdentifier { # Get the platform identifiers - product uuid and imds instance id platform_dmidecode_uuid=$(GetDmiUuid) cmd_status=$? if [ $cmd_status -ne 0 ]; then echo "Failed to get DMI UUID, setting dmi_id empty in state" >&2 platform_dmidecode_uuid="" fi platform_imds_uuid=$(GetCloudInstanceId) cmd_status=$? if [ $cmd_status -ne 0 ]; then echo "Failed to get IMDS instance ID, setting empty in state" >&2 platform_imds_uuid="" fi } function CheckMachineGuid { # Get the platform identifiers - product uuid and imds instance id GetPlatformIdentifier # Verify UUID if ! ValidateUUID "$uuid"; then echo "Invalid uuid, output: $uuid" if [ -n "$platform_dmidecode_uuid" ]; then uuid="$platform_dmidecode_uuid" uuid_type="Dmid" else if [ -n "$platform_imds_uuid" ]; then uuid="$platform_imds_uuid" uuid_type="Imds" else uuid=$(GenerateUUID) uuid_type="Uuid" fi fi if ! ValidateUUID "$uuid"; then echo "Failed to generate a valid uuid, output: $uuid" >&2 LogTelemetry E "${SCENARIO}Failed" "Failed to generate a valid uuid" return 1 fi echo "Setting uuid to $uuid" fi return 0 } function CreateOrUpdateStateFile { local validate_id="" # Flag to check if identifiers are new local ret_value=0 # Write the following fields to the state file # 1. machineGuid # 2. machineUuidType # 3. releaseRing # 4. validate_id # 5. platform_dmidecode_uuid # 6. platform_imds_uuid CheckMachineGuid # Check if the state file exists, if not create it and write the identifiers if [ ! -e "$MDATP_STATE" ]; then # state file doesnt exist, it is a new installation # this indicates that we are using new identifiers and we should validate them validate_id="true" JsonWriter "$MDATP_STATE" "machineGuid" "string" "$uuid" || { echo "Failed to create $MDATP_STATE file" >&2 ret_value=1 } else machineGuid=$(JsonReader "$MDATP_STATE" "machineGuid") if [ -z "$machineGuid" ] || [ "$machineGuid" = "null" ]; then # state file exists but machineGuid is not set, set the new identifiers bit to true validate_id="true" JsonWriter "$MDATP_STATE" "machineGuid" "string" "$uuid" || { echo "Failed to set machineGuid" >&2 ret_value=2 } fi fi # Check if the state file was created or updated successfully, if not return an error & exit if [ $ret_value -ne 0 ]; then LogTelemetry E "${SCENARIO}Failed" "Failed to create or update $MDATP_STATE file. Returning error code $ret_value" return $ret_value fi # Set the machineUuidType if [ -z "$uuid_type" ] && [ -f "$MDATP_TEMP_CONFIG_FOLDER/uuid_type" ]; then uuid_type=$(cat "$MDATP_TEMP_CONFIG_FOLDER/uuid_type") fi if [ -z "$uuid_type" ]; then uuid_type="Unknown" fi LogTelemetry I "MachineUuidType" "uuid_type=$uuid_type" JsonWriter "$MDATP_STATE" "machineUuidType" "string" "$uuid_type" || { LogTelemetry E "UpdateStateFailed" "Failed to set machineUuidType=$uuid_type in $MDATP_STATE" ret_value=3 } # Set the releaseRing JsonWriter "$MDATP_STATE" "releaseRing" "string" "$RELEASE_RING" || { LogTelemetry E "${SCENARIO}Failed" "Failed to set releaseRing in $MDATP_STATE" ret_value=4 } # Set the validate_id bit if true, else it will not be sets if [ -n "$validate_id" ] && [ "$validate_id" = "true" ]; then JsonWriter "$MDATP_STATE" "validateId" "bool" "$validate_id" || { LogTelemetry E "UpdateStateFailed" "Failed to set validate_id in $MDATP_STATE" ret_value=5 } fi # Set the platform_dmidecode_uuid if [ -n "$platform_dmidecode_uuid" ] && ValidateUUID "$platform_dmidecode_uuid"; then JsonWriter "$MDATP_STATE" "platformDmidecodeUuid" "string" "$platform_dmidecode_uuid" || { LogTelemetry W "UpdateStateFailed" "Failed to set platformDmidecodeUuid in $MDATP_STATE" } fi # Set the platform_imds_uuid if [ -n "$platform_imds_uuid" ] && ValidateUUID "$platform_imds_uuid"; then JsonWriter "$MDATP_STATE" "platformImdsUuid" "string" "$platform_imds_uuid" || { LogTelemetry W "UpdateStateFailed" "Failed to set platformImdsUuid in $MDATP_STATE" } fi echo "Release ring: $RELEASE_RING" echo "UUID: $uuid" echo "UUID type: $uuid_type" echo "New identifiers bit: $validate_id" echo "Platform DMI UUID: $platform_dmidecode_uuid" echo "Platform IMDS UUID: $platform_imds_uuid" # check log directory permissions CheckLogDirPermissions return $ret_value } # Function : SetCrashReportPath # Description : This function creates a directory for Crash. # Parameters : None function SetCrashReportPath { echo "Creating Crash Reports Path: $MDATP_CRASH_REPORT_PATH" CreateDir "$MDATP_CRASH_REPORT_PATH" } function UnconfigureTools { rm -f "$MDATP_SCRIPT" rm -f "$MDATP_COMPLETION_SCRIPT" } function RemoveCreatedFiles { rm -rf "$LOG_DIR" # call before deleting state folder DeleteCustomDefinitionsRoot rm -rf "$MDATP_STATE_FOLDER" rm -rf "$MDATP_MANAGED_CONFIG_FOLDER" if [ -d "$MDATP_STATE_PARENT" ]; then RemoveDirIfNotEmpty "$MDATP_STATE_PARENT" fi if [ -d "$MDATP_CONFIG_PARENT" ]; then RemoveDirIfNotEmpty "$MDATP_CONFIG_PARENT" fi } function CleanUpInstallationFailure { if [ $MDATP_CLEANUP -eq 1 ]; then DeleteUser UnconfigureTools RemoveCreatedFiles fi } # Function : CheckServiceContextUnconfined # Description : This function checks if SELinux (Security-Enhanced Linux) context of MDE services/timers is "unconfined_u" # Parameter : Absolute Service/Timer file path function CheckServiceContextUnconfined() { # Iterate over all provided arguments for path in "$@"; do if [ -e "$path" ]; then context=$(ls -Z "$path") if [[ "$context" =~ "unconfined_u" ]]; then return 1 fi fi done return 0 } # Function : RestoreMDEServiceContext # Description : This function restores the SELinux (Security-Enhanced Linux) security context of systemd service files # Parameter : None function RestoreMDEServiceContext { if ! command -v restorecon > /dev/null; then echo "ERROR: restorecon command not found" >&2 return 1 fi if [ -n "$SYSTEMD_SERVICE" ]; then CheckServiceContextUnconfined "$SYSTEMD_SERVICE" result=$? if [ $result -eq 1 ]; then restorecon -vF "$SYSTEMD_SERVICE" fi else echo "ERROR: Systemd service, var SYSTEMD_SERVICE is empty" >&2 return 1 fi } # Function : ChangeLogDirToOpt # Description : This function checks if log directory parents has required execute permission for mdatp user and updates setLogPathOpt field in state file # Parameter : None # Return : 0 if the log directory is updated successfully, 1 otherwise function ChangeLogDirToOpt { local FIELD local VALUE local DIR_GROUP if [ -x "$JSON_WRITER_TOOL" ]; then FIELD="setLogPathOpt" VALUE="true" # Create log directory if it does not exist, check result and send telemetry if [ ! -d "$LOG_DIR_OPT" ]; then if ! CreateDir "$LOG_DIR_OPT"; then LogTelemetry "W" "LogPath" "Failed to create opt log directory" return 1 else # Set the group and permissions for the log directory DIR_GROUP=$DAEMON_USER chown ":$DIR_GROUP" "$LOG_DIR_OPT" chmod 775 "$LOG_DIR_OPT" LogTelemetry "I" "LogPath" "Log opt directory created successfully" fi fi # Update the state file $JSON_WRITER_TOOL set-json --field "$FIELD" --type bool --file "$MDATP_STATE" --value "$VALUE" || { LogTelemetry "W" "LogPath" "Failed to set log path in state file" return 1 } LogTelemetry "I" "LogPath" "setLogPathOpt value updated to true in state file" else LogTelemetry "W" "LogPath" "JSON writer tool not found. Unable to update log directory" return 1 fi return 0 # success } # Function : CheckLogDirPermissions # Description : This function checks if the log directory has execute permission for the mdatp user and updates setLogPathOpt field in state file # Parameter : None function CheckLogDirPermissions { local FIELD local VALUE if [ -x "$JSON_WRITER_TOOL" ]; then # Check setLogPathOpt field in state file FIELD="setLogPathOpt" VALUE=$($JSON_WRITER_TOOL get-json --field "$FIELD" --file "$MDATP_STATE") # If the value is empty/not set, check the permissions if [ -z "$VALUE" ] || [ "$VALUE" = "null" ]; then # Check the execute permissions for the mdatp user in the log parent directories - /var and /var/log if ! CheckExecPermission "/var" || ! CheckExecPermission "/var/log"; then echo "Missing required permissions for log directory, change log path to opt" # Update the log directory to /opt/microsoft/mdatp/log if ! ChangeLogDirToOpt; then LogTelemetry "W" "LogPath" "Failed to update log path" else LogTelemetry "I" "LogPath" "Log path updated successfully" VALUE="true" fi else LogTelemetry "I" "LogPath" "All required permissions present" fi else LogTelemetry "I" "LogPath" "setLogPathOpt value already set to $VALUE" fi else LogTelemetry "W" "LogPath" "JSON writer tool not found. Unable to check log directory permissions" fi # Telemetry for log path if [ "$VALUE" = "true" ]; then LogTelemetry "I" "LogPath" "$LOG_DIR_OPT" else LogTelemetry "I" "LogPath" "$LOG_DIR" fi } # Check if the target has sufficient disk space function TargetHasSufficientDiskSpace { local old_root=$1 local new_root=$2 if [ -d "$old_root" ]; then # Get the size of old_root old_var_definitions_size=$(du -s "$old_root" | awk '{print $1}') # Check if the size of old_root is less than 50% of the available disk space if [ -n "$old_var_definitions_size" ]; then available_disk_space=$(df -k "$new_root" | awk 'NR==2 {print $4}') LogTelemetry I DefinitionsMigration "available_disk_space=$available_disk_space old_var_definitions_size=$old_var_definitions_size" if [ -n "$available_disk_space" ]; then # Check if the size of old_root is less than 50% of the available disk space if [ "$old_var_definitions_size" -lt "$((available_disk_space / 2))" ]; then echo "Size of old_root is less than 50% of the available disk space" return 0 else echo "Size of old_root is greater than 50% of the available disk space" return 1 fi else echo "Failed to get available disk space" return 1 fi else echo "Failed to get size of old_root" return 1 fi else echo "old_root does not exist" return 1 fi } # Copy definitions from old path to new path # This should be removed once all machines # are migrated to opt directory. function CopyDefinitionsFromVarToOpt { local old_root=$1 local new_root=$2 mkdir -p "$new_root" chmod 755 "$new_root" if [[ -d "$old_root" ]]; then # Only copy folders in which libmpengine.so is present. In some cases the update zip folder is not extracted. for definition_folder in "$old_root"/*; do if [[ -f $definition_folder/libmpengine.so ]]; then definition=$(basename "$definition_folder") echo "Copying $definition to $new_root" mkdir -p "$new_root/$definition" chmod 755 "$new_root/$definition" for file in "libmpengine.so" "libmpengine.so.sig" "mpavbase.vdm" "mpasbase.vdm" "mpavdlta.vdm" "mpasdlta.vdm"; do if [[ -f "$definition_folder/$file" ]]; then cp "$definition_folder/$file" "$new_root/$definition" chmod 444 "$new_root/$definition/$file" fi done fi done fi } # Update the state file with the new definitions path function SetDefinitionsPathInStateFile { local state_file=$1 local active_root=$2 local active_definition=$3 if [ -e "$MDATP_STATE" ]; then if [ -x "$JSON_WRITER_TOOL" ]; then $JSON_WRITER_TOOL set-json --field engineCore.databaseRootPath --type string --file "$state_file" --value "$active_root" $JSON_WRITER_TOOL set-json --field engineCore.activeDatabasePath --type string --file "$state_file" --value "$active_definition" fi fi } # Handles migration of definitions from old path to new path function TryMigrateDefinitionsOutOfVar { local state_file=$1 local old_root=$2 local new_root=$3 if VarIsMountedNoExec; then LogTelemetry I DefinitionsMigration "/var is mounted noexec. Checking if migration is required ..." if [ -x "$JSON_WRITER_TOOL" ] && command -v diff > /dev/null; then definition_path=$($JSON_WRITER_TOOL get-json --field engineCore.databaseRootPath --file "$state_file") #/var to /opt migration if [[ -n "$definition_path" ]]; then if [ "$definition_path" = "$old_root" ]; then if TargetHasSufficientDiskSpace "$old_root" "$(dirname "$new_root")"; then LogTelemetry I DefinitionsMigration "Definitions in old /var path detected. Sufficient space in /opt. Copying definitions ..." CopyDefinitionsFromVarToOpt "$old_root" "$new_root" LogTelemetry I DefinitionsMigration "Old definitions copied from /var to /opt." active_database_path=$($JSON_WRITER_TOOL get-json --field engineCore.activeDatabasePath --file "$state_file") active_definition=$(echo "$active_database_path" | rev | cut -d '/' -f 1 | rev) LogTelemetry I DefinitionsMigration "Older active definition was $active_definition" if [[ -n "$active_definition" && -d "$active_database_path" ]]; then if diff -r "$active_database_path" "$new_root/$active_definition" > /dev/null; then SetDefinitionsPathInStateFile "$state_file" "$new_root" "$new_root/$active_definition" LogTelemetry I DefinitionsMigration "Updated state file. Definitions migrated successfully!" rm -rf "$old_root" LogTelemetry I DefinitionsMigration "Deleted old definitions since migration succeeded." else LogTelemetry E DefinitionsMigration "Active definition $active_definition corrupted during copy. Not updating state file. Deleting new definitions." rm -rf "$new_root" fi else LogTelemetry W DefinitionsMigration "Active database path not found. Not updating state file. Deleting new definitions." rm -rf "$new_root" fi else LogTelemetry W DefinitionsMigration "Definitions in old /var path detected. Not enough disk space on $new_root. Skipping definitions migration." fi elif [ "$definition_path" = "$new_root" ]; then LogTelemetry I DefinitionsMigration "Definitions already migrated to /opt." else LogTelemetry I DefinitionsMigration "Definitions path customized. Skipping definitions migration." fi else LogTelemetry W DefinitionsMigration "Definitions path not configured. Skipping definitions migration." fi else local missing_tools="" if [ -n "$JSON_WRITER_TOOL" ]; then missing_tools="JSON writer tool" fi if ! command -v diff > /dev/null; then if [ -n "$missing_tools" ]; then missing_tools="$missing_tools, diff command" else missing_tools="diff command" fi fi LogTelemetry E DefinitionsMigration "Missing tools: $missing_tools. Skipping definitions migration." fi else LogTelemetry I DefinitionsMigration "/var is not mounted noexec. No migration needed ..." fi } # Do not remove below empty line otherwise it will result into syntax error because preinst,postinst,prerm and postrm files are concatenated into this file. Removng below empty line will result into something like "}#!/bin/bash" #!/bin/bash # shellcheck disable=SC2034 # Disabling since this would be used when scripts are combined into one DISTRO_PACKAGE="deb" function StartAllRaw { LogPaths GenerateCorrelationId SetBundleVersion GetScenario # Check if the scenario is ABORT UPGRADE CheckAndExitIfUpgradeAborted "$1" LogTelemetry I "postinstStarted" "" "$(date +%s.%N)" set -e ConfigureTools GetDistro set +e GetMachineGuid SetServicePaths # We want to execute this cleanup of temporary files whenever this script (postinst) is the last script to execute # That happens in the following scenarios: # 1. Installation and Reinstallation on all the distributions # 2. Upgrade on deb based distributions CleanUpTempFiles AddSELinuxPoliciesMdatp EnableCgroupCPUController CreateOrUpdateStateFile CheckDefinitionsDatabase SetCrashReportPath SetLibfuse NetFilterDependencies if [ "$SCENARIO" = "$INSTALL_SCENARIO" ]; then if [ -n "$CUSTOM_INSTALL_DIR" ] && [ -e "$MDATP_INSTALLATION_PATH" ]; then # We are internally persisting the custom path in /opt/microsoft/mdatp/conf/mde_path.json during install cp "$MDATP_INSTALLATION_PATH" "$MDATP_CONF_DIR/mde_path.json" echo "Persisted $MDATP_CONF_DIR/mde_path.json" fi # Do not overwrite existing service file for upgrade/reinstall in case a proxy is configured. if [ -n "$CUSTOM_INSTALL_DIR" ]; then SetSystemdService "$MDATP_DEST_DIR" else SetSystemdService fi fi if [ "$SCENARIO" = "$UPGRADE_SCENARIO" ]; then TryMigrateDefinitionsOutOfVar "$MDATP_STATE" "$OLD_VAR_DEFINITIONS_ROOT" "$NEW_DEFINITIONS_ROOT" elif [ "$SCENARIO" = "$REINSTALL_SCENARIO" ]; then TryMigrateDefinitionsOutOfVar "$MDATP_STATE" "$OLD_VAR_DEFINITIONS_ROOT" "$NEW_DEFINITIONS_ROOT" else SetDefinitionsPathInStateFile "$MDATP_STATE" "$DEFINITIONS_ROOT" "$DEFINITIONS_TARGET_DIRECTORY" fi RestoreMDEServiceContext SetLibPcre if [ -n "$CUSTOM_INSTALL_DIR" ]; then SetAndStartNetfilterV2Service "$MDATP_DEST_DIR" else SetAndStartNetfilterV2Service fi StartDaemon RestoreAllFilePermission PrintStatus if IsDaemonRunning ; then LogTelemetry C "${SCENARIO}Succeeded" SendMdatpHealthStatus else LogTelemetry W "DaemonDown" fi LogTelemetry I "postinstCompleted" "" "$(date +%s.%N)" } function StartAll { local NOW NOW=$(date +"%Y-%m-%d %H:%M:%S %z") echo "$SCRIPT_NAME begin [$NOW] $PPID" StartAllRaw "$1" "$2" NOW=$(date +"%Y-%m-%d %H:%M:%S %z") echo "$SCRIPT_NAME end [$NOW] $PPID" } function CheckAndExitIfUpgradeAborted { # Check if the scenario is ABORT UPGRADE local scenario_arg="$1" if [ "$scenario_arg" = "abort-upgrade" ]; then echo "Upgrade Aborted" exit 0 fi } ################# BEGIN SCRIPT set +e set -E SetErrTrap SetExitTrap # Setup prerequisites - logging and mde_path.json paths SetGlobalPrerequisites echo "$POSTINST_SCRIPT_NAME" | LogCustom "$LOG_INSTALL_FILE" "$LOG_INSTALL_TAG" echo "Arguments passed to the $POSTINST_SCRIPT_NAME script: $*" | LogCustom "$LOG_INSTALL_FILE" "$LOG_INSTALL_TAG" # Extract Custom Installation Path from mde_path.json CheckAndSetCustomInstallationPath "false" SetGlobalCommon SetGlobalSpecific DefaultDefinitionsDatabase SetProxyConfig StartAll "$1" "$2" 2>&1 | LogCustom "$LOG_INSTALL_FILE" "$LOG_INSTALL_TAG" exit "${PIPESTATUS[0]}"