#!/bin/bash
#
# jetbackup5-clones-1800-patch.sh
# Detects the installed JetBackup 5 version ONCE and applies the official
# Clones.inc patch for issue #1800:
#   - 5.3.12.1 (any tier) -> jb5-clones-1800-patch-v5.3.12.1.tar
# Any other JetBackup version is rejected and nothing is modified.
#
# Security hardening vs. the original one-liners:
#   * Runs with `set -u -o pipefail` and a restrictive umask
#   * Requires root (the target file is root-owned)                                                                                                                                                                   
#   * Temp dir is mode 0700 (mktemp -d default) and removed on exit via trap
#   * Download forced over HTTPS with certificate verification
#   * Archive entry is extracted to stdout only (-O), so a malicious
#     archive cannot perform path traversal writes
#   * Sanity-checks the extracted file looks like PHP source before install                                                                                                                                           
#   * Timestamped backup of the original file, perms/owner preserved
#   * Patch installed atomically via mv onto the same filesystem
set -u -o pipefail
umask 077

TARGET="/usr/local/jetapps/var/lib/jetbackup5/Core/Destination/Clones/Clones.inc"
PATCH_FILE="Clones.inc"
PATCH_VERSION="5.3.12.1"                                                                                                                                                                                              
ARCHIVE_NAME="jb5-clones-1800-patch-v5.3.12.1.tar"
URL="https://repo.jetlicense.com/patches/jetbackup5/$ARCHIVE_NAME"

log()  { printf '%s\n' "$*"; }
fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }

# --- Preconditions -----------------------------------------------------------

[ "$(id -u)" -eq 0 ] || fail "This script must be run as root."                                                                                                                                                       

BIN="$(command -v jetbackup5 2>/dev/null || true)"
[ -n "$BIN" ] || BIN="/usr/local/jetapps/usr/bin/jetbackup5"
[ -x "$BIN" ]   || fail "JetBackup 5 CLI not found. Patch not applied."
command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 \
    || fail "curl or wget is required but neither is installed. Patch not applied."
[ -f "$TARGET" ] || fail "Target file not found: $TARGET. JetBackup 5 may not be fully installed. Patch not applied."

# --- Detect version and tier (once) ------------------------------------------                                                                                                                                       

OUT="$("$BIN" --version 2>&1)"
JBV="$(printf '%s\n' "$OUT"  | sed -nE 's/.*Base[[:space:]]+([0-9]+(\.[0-9]+){2,3}).*/\1/p')"                                                                                                                         
TIER="$(printf '%s\n' "$OUT" | sed -nE 's/.*Current Tier[[:space:]]+([A-Z]+).*/\1/p')"

log "Detected JetBackup version: ${JBV:-unknown}, tier: ${TIER:-unknown}"

[ "$JBV" = "$PATCH_VERSION" ] \
    || fail "Unsupported JetBackup version: ${JBV:-unknown} ${TIER:-unknown}. This patch (issue #1800) only supports v$PATCH_VERSION."                                                                                

log "Applying JetBackup $JBV (${TIER:-unknown}) Clones patch (issue #1800)..."

# --- Secure temp workspace with guaranteed cleanup ----------------------------

TMP="$(mktemp -d)" || fail "Could not create temporary directory."
trap 'rm -rf -- "$TMP"' EXIT INT TERM

ARCHIVE="$TMP/$ARCHIVE_NAME"                                                                                                                                                                                          

# --- Download (HTTPS only, certs verified) ------------------------------------

# Certificates are always verified. Redirects may only lead to another https://                                                                                                            
# URL, so a downgrade to plain HTTP is impossible with either client.                                                                                                                                                 
download() {
    if command -v curl >/dev/null 2>&1; then
        curl -fsSL --proto '=https' --proto-redir '=https' -o "$2" -- "$1"
    elif wget --help 2>&1 | grep -q -- '--https-only'; then
        wget --https-only --secure-protocol=auto -q -O "$2" -- "$1"
    else
        # wget < 1.15 (CentOS/CloudLinux 7) lacks --https-only; refuse redirects                                                                                                                                      
        # instead, since the hard-coded URL is already https://
        wget --max-redirect=0 --secure-protocol=auto -q -O "$2" -- "$1"
    fi
}

download "$URL" "$ARCHIVE" || fail "Patch download failed. Patch not applied."                                                                                                             
[ -s "$ARCHIVE" ] || fail "Downloaded archive is empty. Patch not applied."                                                                                                                                           

# --- Validate and extract (stdout-only; no path traversal possible) -----------

FILE="$(tar -tf "$ARCHIVE" 2>/dev/null | grep -E "(^|/)${PATCH_FILE}\$" | head -n1)"
[ -n "$FILE" ] || fail "$PATCH_FILE not found inside the archive. Patch not applied."

tar -xf "$ARCHIVE" -O -- "$FILE" > "$TMP/$PATCH_FILE.new" \
    || fail "Archive extraction failed. Patch not applied."
[ -s "$TMP/$PATCH_FILE.new" ] || fail "Extracted $PATCH_FILE is empty. Patch not applied."

# Basic sanity check that this is PHP source, not an error page or junk
head -c 64 "$TMP/$PATCH_FILE.new" | grep -q '<?php' \
    || fail "Extracted file does not look like a PHP source file. Patch not applied."

# --- Backup and atomic install -------------------------------------------------

BACKUP="$TARGET.bak.$(date +%F-%H%M%S)"                                                                                                                                                                               
cp -a -- "$TARGET" "$BACKUP" || fail "Could not create backup. Patch not applied."

# Stage on the same filesystem so mv is atomic, then match the original
# file's permissions and ownership on the staged copy (cp honours the
# restrictive umask, so the reference must be applied AFTER staging)
STAGE="$(dirname -- "$TARGET")/.$PATCH_FILE.new.$$"
cp -f -- "$TMP/$PATCH_FILE.new" "$STAGE" || fail "Could not stage patch file. Patch not applied."
chmod --reference="$TARGET" -- "$STAGE"  || { rm -f -- "$STAGE"; fail "Could not set permissions on patch file. Patch not applied."; }
chown --reference="$TARGET" -- "$STAGE"  || { rm -f -- "$STAGE"; fail "Could not set ownership on patch file. Patch not applied."; }
mv -f -- "$STAGE" "$TARGET"              || { rm -f -- "$STAGE"; fail "Could not install patch file. Patch not applied."; }

log "Patch for issue #1800 applied successfully for JetBackup $JBV on ${TIER:-unknown}."
log "Backup of the original file saved at: $BACKUP"

