#!/bin/bash

# Copyright (c) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2026 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT

# Harden PATH so root-executed unqualified commands resolve only from trusted,
# root-owned system directories (defense-in-depth against a PATH-hijack when this
# library is sourced by a root cPanel hook).
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH

##################################################
# Common fucntions                               #
##################################################
VERSION="0.1beta"

common_path_of_cpanel="/usr/share/lve/modlscapi"
common_current_date=$(date +%Y-%m-%d)
common_tmp_path="$common_path_of_cpanel/tmp"

# --- Telemetry: EA3 source-build usage tracking --------------------------
# The EA3-era source-build path (cmakeSorce, reached only on cPanel via the
# legacy installer and EasyApache make hooks) is believed obsolete now that
# EA4 ships a prebuilt module. To measure whether it still executes anywhere
# before retiring the code, sendSentryEvent emits a best-effort event to the
# mod_lsapi Sentry project on each invocation. The DSN carries a public key
# only (no secret), and every failure is swallowed so telemetry can never
# slow down or break an install/build.
LSAPI_SENTRY_HOST="cl.sentry.cloudlinux.com"
LSAPI_SENTRY_PROJECT="45"
LSAPI_SENTRY_KEY="1acafc22de2e372e975f601583793d13"

function lsapiOwningPackage(){
    # Print "<name>|<version>" of the package that owns this script (so the
    # beacon records which package/version triggered the build -- mod_lsapi on
    # nopanel/Plesk, ea-apache24-mod_lsapi on EA4). "unknown|unknown" on miss.
    local f="$common_path_of_cpanel/include/cpanel-common-lve" out=""
    if command -v rpm >/dev/null 2>&1; then
        out=$(rpm -qf --qf '%{NAME}|%{VERSION}-%{RELEASE}' "$f" 2>/dev/null)
        case "$out" in *"not owned"*|*"no such"*) out="" ;; esac
    fi
    if [ -z "$out" ] && command -v dpkg-query >/dev/null 2>&1; then
        local n; n=$(dpkg -S "$f" 2>/dev/null | cut -d: -f1)
        [ -n "$n" ] && out="${n}|$(dpkg-query -W -f='${Version}' "$n" 2>/dev/null)"
    fi
    [ -n "$out" ] && echo "$out" || echo "unknown|unknown"
}

function sendSentryEvent(){
    # $1 - message, $2 - fingerprint (issue-grouping key), rest - tag=value pairs
    local message="$1" fingerprint="$2"
    shift 2 2>/dev/null || return 0
    command -v curl >/dev/null 2>&1 || return 0
    local tags="\"component\":\"ea3-source-build\"" kv
    for kv in "$@"; do
        tags="${tags},\"${kv%%=*}\":\"${kv#*=}\""
    done
    # NOTE: user.ip_address="{{auto}}" makes Sentry record the server's
    # connecting (egress) IP. An IP is PII -- keep this only if it clears the
    # telemetry/consent policy; drop the "user" object to disable.
    local body
    body=$(printf '{"platform":"other","level":"info","logger":"modlsapi.ea3.source_build","message":"%s","server_name":"%s","user":{"ip_address":"{{auto}}"},"environment":"production","fingerprint":["%s"],"tags":{%s}}' \
        "$message" "$(hostname 2>/dev/null)" "$fingerprint" "$tags")
    curl -sS --connect-timeout 3 -m 5 \
        "https://${LSAPI_SENTRY_HOST}/api/${LSAPI_SENTRY_PROJECT}/store/" \
        -H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=${LSAPI_SENTRY_KEY}, sentry_client=modlsapi-ea3/1.0" \
        -H "Content-Type: application/json" \
        --data "$body" >/dev/null 2>&1 || true
}

function getLogFile(){
    if [ ! -e "$common_path_of_cpanel/logs" ];then
	mkdir -p "$common_path_of_cpanel/logs"
    fi
    current_date_time=$(date +"%Y-%m-%d %k:%M:%S")
    echo "$common_path_of_cpanel/logs/$common_current_date.log"
}

function writeToLog(){
    if [ ! -e "$common_path_of_cpanel/logs" ];then
	mkdir -p "$common_path_of_cpanel/logs"
    fi
    current_date_time=$(date +"%Y-%m-%d %k:%M:%S")
    prg=$(basename "$0")
    echo "[$current_date_time from $prg] $1" >> "$common_path_of_cpanel/logs/$common_current_date.log"
}

function writeFileToLog(){
    if [ ! -e "$common_path_of_cpanel/logs" ];then
	mkdir -p $common_path_of_cpanel/logs
    fi
    current_date_time=$(date +"%Y-%m-%d %k:%M:%S")
    prg=$(basename "$0")
    echo "[$current_date_time from $prg] ----------------File Content $1 BEG---------------" >> "$common_path_of_cpanel/logs/$common_current_date.log"
    if [ -e "$1" ];then
	cat "$1" >> "$common_path_of_cpanel/logs/$common_current_date.log"
    fi
    echo "[$current_date_time from $prg] ----------------File Content $1 End---------------" >> "$common_path_of_cpanel/logs/$common_current_date.log"
}

function checkForAppNameSyntax(){
    isApache2_0syntax=$(echo "$1" | grep [,\;@])
    if [ -n "$isApache2_0syntax" ];then
	echo "$1" | cut -d',' -f 3 | tr '[:lower:]' '[:lower:]'
    else
	echo "$1"
    fi
}

function removeEmptyStringsFromFile(){
    filename="$1"
    # Fail-safe: only ever rewrite an existing, plain regular file. Refuse
    # symlinks, directories, and missing paths so a bad/hostile path can never
    # be clobbered (a symlink would otherwise follow through to its target).
    if [ ! -f "$filename" ] || [ -L "$filename" ]; then
        writeToLog "removeEmptyStringsFromFile: refusing non-regular or symlink path: $filename"
        return 1
    fi
    # Write atomically via a temp file in the SAME directory, preserving the
    # original mode and ownership, then rename over the original.
    tmpfile=$(mktemp "$(dirname "$filename")/.rmempty.XXXXXX") || return 1
    if ! sed -e '/^$/d' "$filename" > "$tmpfile"; then
        rm "$tmpfile"
        return 1
    fi
    # The temp file sits in $filename's directory. If that directory were ever
    # caller-writable, the just-created temp file could be unlinked and replaced
    # with a symlink before the chmod/chown below, which (following the link)
    # would chmod/chown an arbitrary target as root. The fixed /scripts/<hook>
    # callers are all in root-owned dirs, but re-verify the temp file is still a
    # plain regular file right before adjusting its attributes so the shared
    # helper stays safe regardless of caller.
    if [ -L "$tmpfile" ] || [ ! -f "$tmpfile" ]; then
        writeToLog "removeEmptyStringsFromFile: temp file is a symlink or not a regular file (possible race): $tmpfile"
        rm "$tmpfile" 2>/dev/null
        return 1
    fi
    # Restore the original mode; if it cannot be preserved, abort and leave
    # $filename untouched rather than rename a default-0600 temp over an
    # executable hook and report success (which would silently break the hook).
    if ! chmod --reference="$filename" "$tmpfile" 2>/dev/null; then
        writeToLog "removeEmptyStringsFromFile: could not preserve mode for $filename; leaving it unchanged"
        rm "$tmpfile"
        return 1
    fi
    # Ownership is best-effort: for the root-owned /scripts/<hook> callers the
    # temp file is already root-owned (no-op), and a non-root context fails
    # harmlessly; the executability-critical attribute is the mode above.
    chown --reference="$filename" "$tmpfile" 2>/dev/null
    if ! mv "$tmpfile" "$filename"; then
        rm "$tmpfile"
        return 1
    fi
}

function deleteAllExcept(){
    #1 - hook
    #2 - tmp name
    #3 - pattern
    if [ ! -e "$common_tmp_path" ]; then
        mkdir -p "$common_tmp_path"
    fi
    if [ -e "$1" ];then
        # scanner false-positive (command_injection): $3 (the sed program) is a root-authored
        # constant passed by in-repo callers; this script runs as root at install/rebuild time,
        # no tenant input.
        cat "$1" | sed -n "$3" > "$common_tmp_path/$2.tmp.$$"
        echo "#!/bin/bash" > "$1"
        cat "$common_tmp_path/$2.tmp.$$" >> "$1"
        rm -f "$common_tmp_path/$2.tmp.$$"
    fi
}

function deleteAllInclude(){
    #1 - hook
    #2 - tmp name
    #3 - pattern
    if [ ! -e "$common_tmp_path" ]; then
        mkdir -p "$common_tmp_path"
    fi
    if [ -e "$1" ];then
        # scanner false-positive (command_injection): $3 (the sed program) is a root-authored
        # constant passed by in-repo callers; this script runs as root at install/rebuild time,
        # no tenant input.
        cat "$1" | sed "$3" > "$common_tmp_path/$2.tmp.$$"
        cat "$common_tmp_path/$2.tmp.$$" > "$1"
        rm -f "$common_tmp_path/$2.tmp.$$"
    fi
}


# scanner false-positive (command_injection): showBar's numeric arg is a root-supplied
# integer literal from in-repo callers, not attacker-controlled.
function showBar {
 nmb=$(cat "$0" | grep showBar | wc -l)
 let "nmb = $nmb"
 let "prct = $1 * 30 / $nmb"
 let "prct_n = $1 * 100 / $nmb"
 prg=$(basename "$0")
 echo -n "$prg: [" >&2
 for bar in {1..30}
 do
  if [ $bar -le $prct ];then
    echo -n "#" >&2
  else
    echo -n " " >&2
  fi
 done
 echo -ne "] ($prct_n%)\r" >&2
}

function get_command(){
    command=$(which "$1")                                                                                                                                      
    if [ $? != 0 ]; then                                                                                                                                     
        writeToLog "Can't execute command $1..."                                                                                                                   
        exit 1;                                                                                                                                              
    fi                                                                                                                                                       
    echo "$command"                                                                                                                                            
} 

function cmakeSorce(){
 src="$1"
 subdir="$2"
 # $1/$2 are run as root (cmake/make/make install), so treat them as untrusted:
 # require an absolute path to an existing regular file with no traversal/metacharacters,
 # and a relative subdir with no traversal so the build tree stays under $CMAKE_SRC.
 if [ -z "$src" ] || [ "${src#/}" = "$src" ] \
    || [ "$src" != "${src//[\`\$\;\&\|\(\)\<\>\*\?\"\'$'\n']/}" ] \
    || [ "$src" != "${src//\.\.\//}" ] || [ ! -f "$src" ]; then
    writeToLog "cmakeSorce: refusing unsafe archive path '$src'"
    return 1
 fi
 if [ "${subdir#/}" != "$subdir" ] \
    || [ "$subdir" != "${subdir//[\`\$\;\&\|\(\)\<\>\*\?\"\'$'\n']/}" ] \
    || [ "$subdir" != "${subdir//\.\.\//}" ] || [ "$subdir" = ".." ]; then
    writeToLog "cmakeSorce: refusing unsafe subdir '$subdir'"
    return 1
 fi
 CL7=$(uname -r | grep '\.el7')
 PARAMS=""
 if [ -n "$CL7" ]; then
   PARAMS="-DWITH_CRIU:BOOLEAN=TRUE"
 fi
 cur=$(pwd)
 log=$(getLogFile)
 # Usage telemetry: this point is reached once per real EA3 source-build,
 # before cmake is resolved (get_command may exit if absent), so the beacon
 # is recorded even when the build later fails for an environment reason.
 pkg=$(lsapiOwningPackage)
 sendSentryEvent "mod_lsapi EA3 source-build invoked" "modlsapi-ea3-source-build" \
     "caller=$(basename "$0" 2>/dev/null)" "el=$(uname -r)" \
     "package=${pkg%%|*}" "pkg_version=${pkg#*|}"
 iscmake=$(get_command "cmake")                                                                                                                              
 CMAKE_SRC=$common_tmp_path/tmpcmakesrc                                                                                                                                       
 if [ -e "$CMAKE_SRC" ];then
    rm -rf "$CMAKE_SRC"
 fi
 mkdir -p $CMAKE_SRC
 tar -zxvf "$src" -C $CMAKE_SRC >>$log
 (cd "$CMAKE_SRC/$subdir"
            "$iscmake" "$PARAMS" . 1>>$log 2>&1 && make 1>>$log 2>&1 && make install 1>>$log 2>&1)
 makeproc=$?
 find "$CMAKE_SRC" -mindepth 1 -exec rm -rf {} +
 cd "$cur"
 return $makeproc
}

function installModule_lsapi(){                                                                                                                            
 
        pathtosrc="$common_path_of_cpanel/tars/mod_lsapi.tar.gz"                                                                                                               
        cmakeSorce $pathtosrc ""                                                                                                                         
        if [[ $? != 0 ]]; then                                                                                                                           
            writeToLog "Linking error mod_lsapi.tar.gz"                                                                                                     
            exit 1
        else                                                                                                                                             
            writeToLog "Module mod_lsapi compiled... Ok"                                                                                                                                                                                                                                                     
        fi
        if [ ! -e "/usr/local/apache/conf/conf.d" ];then
            mkdir -p /usr/local/apache/conf/conf.d/
        fi
        if [ ! -e "/usr/local/apache/conf/conf.d/lsapi.conf" ];then
            cp -f "$common_path_of_cpanel/confs/lsapi.conf" /usr/local/apache/conf/conf.d/lsapi.conf                                                                                                                                                    
        fi
        return 0                                                                                                                                             
                                                                                                                                                             
}


