#!/usr/bin/env bash
#
# Werk 19575 hotfix for Checkmk 2.5.0p6
# =================================================
#
# Symptom:  the GUI / REST API fail to load on distributions whose *system*
#           OpenSSL is compiled without certain algorithms - notably RHEL-family
#           systems, which build OpenSSL without the SM4 cipher (no EVP_sm4_ecb
#           symbol). The site apache maps the system libcrypto.so.3 before our
#           Python crypto extensions, so they resolve against the wrong library.
#
# Fix:      preload the bundled OpenSSL (libssl.so / libcrypto.so) on the apache
#           start and graceful-reload execs in etc/init.d/apache.
#
# This script patches, for the target version:
#   * the skel of every installed   <version>.*   under /omd/versions/...
#       -> .../skel/etc/init.d/apache   (so newly created sites get the fix)
#   * the live etc/init.d/apache of every site running <version>.*
#       -> /omd/sites/<site>/etc/init.d/apache
#
# It is SAFE TO RE-RUN: files that already carry the fix are detected and left
# alone, and files that do not match the expected content are skipped untouched
# (never corrupted). Every modified file is backed up next to the original.
#
# Usage:   sudo ./patch-werk-19575-apache.sh [VERSION]
#          VERSION defaults to 2.5.0p6. Pass another patch level to target it,
#          e.g.  sudo ./patch-werk-19575-apache.sh 2.5.0p6
#
set -euo pipefail

TARGET_VERSION="${1:-2.5.0p6}"
REL="etc/init.d/apache"
BACKUP_SUFFIX=".orig-werk-19575"

if [ "$(id -u)" -ne 0 ]; then
    echo "ERROR: must be run as root (it writes under /omd/versions and /omd/sites)." >&2
    exit 1
fi

# --- the patch (site-relative paths; applied with -p1 -d <basedir>) ----------
PATCH_FILE="$(mktemp)"
trap 'rm -f "$PATCH_FILE"' EXIT
cat >"$PATCH_FILE" <<'PATCH_EOF'
diff --git a/etc/init.d/apache b/etc/init.d/apache
--- a/etc/init.d/apache
+++ b/etc/init.d/apache
@@ -18,6 +18,24 @@ INITLOG_ARGS=""
 PID_FILE="$OMD_ROOT"/tmp/apache/run/apache.pid
 CONFIG_FILE="$OMD_ROOT"/etc/apache/apache.conf

+# Preload our bundled OpenSSL into the (distribution-provided) httpd binary.
+#
+# The site apache runs the distro httpd, whose own dependencies and startup
+# pull in the system libcrypto.so.3 (via libnss_systemd, used for user/group
+# lookups) before mod_wsgi loads our Python. Both libraries share the SONAME
+# "libcrypto.so.3", so the loader reuses the already-mapped system copy for our
+# extensions too - regardless of their RPATH. On distributions that strip
+# algorithms from their OpenSSL (e.g. RHEL drops SM4) this makes our Python
+# crypto extensions fail to resolve symbols such as EVP_sm4_ecb, breaking the
+# GUI/REST API (SUP-29520).
+#
+# Preloading our libssl/libcrypto here makes our OpenSSL win that resolution.
+# It is scoped to this single exec (not exported) so it never leaks into the
+# other system tools this script invokes (ping, kill, killall, php-cgi, ...).
+# We reference the unversioned ".so" symlinks so this keeps working across an
+# OpenSSL major bump (e.g. 3 -> 4) without editing this script.
+APACHE_LD_PRELOAD="$OMD_ROOT/lib/libssl.so $OMD_ROOT/lib/libcrypto.so"
+
 apache_bin() {
     if [ -e /usr/sbin/apache2 ]; then
         echo /usr/sbin/apache2
@@ -124,7 +142,7 @@ apache_wait_start() {
         done
     fi

-    $(apache_bin) -f "$CONFIG_FILE"
+    LD_PRELOAD="$APACHE_LD_PRELOAD" $(apache_bin) -f "$CONFIG_FILE"

     i=0
     while ! pidof_apache >/dev/null 2>&1; do
@@ -192,7 +210,7 @@ case $1 in
         ;;
     reload)
         echo "Reloading apache"
-        $(apache_bin) -f "$CONFIG_FILE" -k graceful
+        LD_PRELOAD="$APACHE_LD_PRELOAD" $(apache_bin) -f "$CONFIG_FILE" -k graceful
         __init_hook $0 $1 post $?
         ;;
     status)
PATCH_EOF

patched=0 already=0 skipped=0 failed=0

# apply_one <basedir> <label>
apply_one() {
    local base="$1" label="$2"
    local target="$base/$REL"

    if [ ! -f "$target" ]; then
        echo "  SKIP  $label: $REL not found"
        skipped=$((skipped + 1))
        return
    fi

    if grep -q 'APACHE_LD_PRELOAD' "$target"; then
        echo "  OK    $label: already patched"
        already=$((already + 1))
        return
    fi

    # --forward: clean three-way decision without ever corrupting the file.
    if ! patch -p1 --forward --dry-run -d "$base" <"$PATCH_FILE" >/dev/null 2>&1; then
        echo "  FAIL  $label: content does not match (edited or different version?) - left untouched"
        failed=$((failed + 1))
        return
    fi

    local owner mode
    owner="$(stat -c '%u:%g' "$target")"
    mode="$(stat -c '%a' "$target")"
    cp -p "$target" "$target$BACKUP_SUFFIX"
    patch -p1 --forward -d "$base" <"$PATCH_FILE" >/dev/null
    chown "$owner" "$target"
    chmod "$mode" "$target"
    echo "  DONE  $label  (backup: $target$BACKUP_SUFFIX)"
    patched=$((patched + 1))
}

echo "== Versions matching ${TARGET_VERSION}.* (skel) =="
shopt -s nullglob
found_version=0
for vdir in /omd/versions/"${TARGET_VERSION}".*; do
    [ -d "$vdir" ] || continue
    found_version=1
    apply_one "$vdir/skel" "version $(basename "$vdir")"
done
[ "$found_version" -eq 0 ] && echo "  (no installed versions match ${TARGET_VERSION}.*)"

echo "== Sites running ${TARGET_VERSION}.* =="
restart_sites=()
found_site=0
# `omd sites` columns: SITE VERSION COMMENTS (with header). The version-shaped
# case pattern ignores the header line and any site on another version.
while read -r site version _rest; do
    case "$version" in
        "${TARGET_VERSION}".*)
            found_site=1
            before=$patched
            apply_one "/omd/sites/$site" "site $site ($version)"
            [ "$patched" -gt "$before" ] && restart_sites+=("$site")
            ;;
    esac
done < <(omd sites)
[ "$found_site" -eq 0 ] && echo "  (no sites running ${TARGET_VERSION}.*)"

echo
echo "Summary: patched=$patched already=$already skipped=$skipped failed=$failed"

if [ "${#restart_sites[@]}" -gt 0 ]; then
    echo
    echo "Restart apache in each patched site for the change to take effect:"
    for s in "${restart_sites[@]}"; do
        echo "  omd restart $s apache"
    done
fi

[ "$failed" -eq 0 ]
