#!/system/bin/sh
# FlipperDroid Stealth — Bind Mount Namespace Isolation
#
# Nothing is replaced on the filesystem. Stock files stay untouched.
# Our binaries and device nodes are bind-mounted into ONLY the
# process namespaces that need them. Every other process on the
# system sees a completely stock device.
#
# dm-verity: PASS (we don't modify partitions)
# Play Integrity: PASS (no modified system files)
# Banking apps: PASS (they see stock everything)
# ls -Z: identical (we clone SELinux contexts + metadata)

MODDIR="/data/adb/modules/flipperdroid"
CONFIG_DIR="/data/adb/flipperdroid"
STEALTH_DIR="$CONFIG_DIR/.stealth"
LOG_FILE="$CONFIG_DIR/logs/stealth.log"
SPOOF_MAP="$CONFIG_DIR/stealth_map.conf"

mkdir -p "$STEALTH_DIR/mounts" "$STEALTH_DIR/originals"

slog() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [stealth] $1" >> "$LOG_FILE"
}

#############################
# Metadata Cloning
# Clone SELinux context, ownership,
# permissions, timestamps from stock
# target onto our custom file so even
# ls -Z looks identical
#############################

clone_metadata() {
  local stock_path="$1"
  local custom_path="$2"

  [ ! -f "$stock_path" ] && slog "WARN: stock path not found: $stock_path" && return 1
  [ ! -f "$custom_path" ] && slog "WARN: custom path not found: $custom_path" && return 1

  # Clone ownership
  local owner=$(stat -c '%u:%g' "$stock_path" 2>/dev/null)
  chown "$owner" "$custom_path" 2>/dev/null

  # Clone permissions
  local perms=$(stat -c '%a' "$stock_path" 2>/dev/null)
  chmod "$perms" "$custom_path" 2>/dev/null

  # Clone timestamps (access + modify)
  touch -r "$stock_path" "$custom_path" 2>/dev/null

  # Clone SELinux context
  local context=$(ls -Z "$stock_path" 2>/dev/null | awk '{print $1}')
  if [ -n "$context" ] && [ "$context" != "?" ]; then
    chcon "$context" "$custom_path" 2>/dev/null
  fi

  slog "Cloned metadata: $stock_path -> $custom_path (owner=$owner perm=$perms ctx=$context)"
}

#############################
# Process Namespace Bind Mount
# Enter a specific process's mount
# namespace and bind-mount our file
# over the stock path. ONLY that
# process sees the swap.
#############################

# Find PID for a process by name
find_pid() {
  local proc_name="$1"
  local pid=""

  # Try pidof first
  pid=$(pidof "$proc_name" 2>/dev/null)
  [ -n "$pid" ] && echo "$pid" && return 0

  # Fallback: scan /proc
  for p in /proc/[0-9]*; do
    local cmdline=$(cat "$p/cmdline" 2>/dev/null | tr '\0' ' ')
    local comm=$(cat "$p/comm" 2>/dev/null)
    if echo "$cmdline" | grep -q "$proc_name" || [ "$comm" = "$proc_name" ]; then
      pid=$(basename "$p")
      echo "$pid"
      return 0
    fi
  done

  return 1
}

# Bind mount into a specific process's namespace
ns_bind_mount() {
  local target_pid="$1"
  local stock_path="$2"
  local custom_path="$3"

  [ ! -d "/proc/$target_pid/ns" ] && slog "ERR: PID $target_pid not found" && return 1
  [ ! -f "$custom_path" ] && slog "ERR: custom file not found: $custom_path" && return 1

  # Enter the target process's mount namespace and bind mount
  nsenter -t "$target_pid" -m -- mount --bind "$custom_path" "$stock_path" 2>/dev/null
  local rc=$?

  if [ $rc -eq 0 ]; then
    slog "Bind mounted $custom_path -> $stock_path in PID $target_pid namespace"
    echo "$target_pid|$stock_path|$custom_path" >> "$STEALTH_DIR/mounts/active"
    return 0
  else
    slog "ERR: bind mount failed (rc=$rc) for PID $target_pid"
    return 1
  fi
}

# Unmount from a specific process's namespace
ns_bind_unmount() {
  local target_pid="$1"
  local stock_path="$2"

  [ ! -d "/proc/$target_pid/ns" ] && return 1

  nsenter -t "$target_pid" -m -- umount "$stock_path" 2>/dev/null
  local rc=$?

  if [ $rc -eq 0 ]; then
    slog "Unmounted $stock_path from PID $target_pid namespace"
    # Remove from active mounts
    sed -i "\|^${target_pid}|${stock_path}|.*|d" "$STEALTH_DIR/mounts/active" 2>/dev/null
  fi

  return $rc
}

#############################
# Spoof Map Processing
# Reads stealth_map.conf and applies
# bind mounts per-process
#
# Format:
# stock_path|custom_filename|target_process|spoof_type
#
# spoof_type:
#   process  = bind mount into specific process namespace
#   global   = bind mount in init namespace (all processes see it)
#   hidden   = make stock path invisible to target process
#############################

apply_map() {
  [ ! -f "$SPOOF_MAP" ] && slog "No stealth map found" && return 1

  local applied=0
  local failed=0

  while IFS='|' read -r stock_path custom_file target_proc spoof_type; do
    # Skip comments and empty lines
    echo "$stock_path" | grep -q '^#' && continue
    [ -z "$stock_path" ] && continue

    local custom_path="$MODDIR/stealth/$custom_file"

    # Clone metadata from stock onto our custom file
    clone_metadata "$stock_path" "$custom_path"

    case "$spoof_type" in
      process)
        # Find the target process PID
        local pid=$(find_pid "$target_proc")
        if [ -z "$pid" ]; then
          slog "WARN: process '$target_proc' not running, skipping $stock_path"
          failed=$((failed + 1))
          continue
        fi

        # Could be multiple PIDs (space-separated)
        for p in $pid; do
          if ns_bind_mount "$p" "$stock_path" "$custom_path"; then
            applied=$((applied + 1))
          else
            failed=$((failed + 1))
          fi
        done
        ;;

      global)
        # Mount in init namespace (PID 1) — all processes see it
        if ns_bind_mount 1 "$stock_path" "$custom_path"; then
          applied=$((applied + 1))
        else
          failed=$((failed + 1))
        fi
        ;;

      hidden)
        # Make a path invisible to the target by mounting an empty file over it
        local empty_file="$STEALTH_DIR/empty_$(echo "$stock_path" | md5sum | cut -c1-8)"
        touch "$empty_file"
        clone_metadata "$stock_path" "$empty_file"

        local pid=$(find_pid "$target_proc")
        if [ -n "$pid" ]; then
          for p in $pid; do
            ns_bind_mount "$p" "$stock_path" "$empty_file"
          done
          applied=$((applied + 1))
        else
          failed=$((failed + 1))
        fi
        ;;

      *)
        slog "WARN: unknown spoof_type '$spoof_type' for $stock_path"
        failed=$((failed + 1))
        ;;
    esac
  done < "$SPOOF_MAP"

  slog "Stealth map applied: $applied succeeded, $failed failed"
}

#############################
# Teardown — unmount everything
#############################

teardown_map() {
  [ ! -f "$STEALTH_DIR/mounts/active" ] && return 0

  local count=0
  while IFS='|' read -r pid stock_path custom_path; do
    [ -z "$pid" ] && continue
    if [ -d "/proc/$pid" ]; then
      ns_bind_unmount "$pid" "$stock_path"
      count=$((count + 1))
    fi
  done < "$STEALTH_DIR/mounts/active"

  rm -f "$STEALTH_DIR/mounts/active"
  slog "Teardown complete: $count mounts removed"
}

#############################
# FlipperDroid-specific stealth
#
# Hide the bridge daemon and WebUI
# from processes that shouldn't
# know about them
#############################

apply_flipper_stealth() {
  slog "Applying FlipperDroid stealth"

  # Our daemons run under their own process.
  # We hide evidence from processes that might scan for them:
  #
  # 1. The ttyACM device node — only our bridge daemon sees it
  # 2. The WebUI port — only localhost, only our namespace
  # 3. Our config directory — hidden from non-root processes
  # 4. Our daemon binaries — only visible in their own namespace

  local bridge_pid=$(cat "$CONFIG_DIR/bridge.pid" 2>/dev/null)
  local webui_pid=$(cat "$CONFIG_DIR/daemon.pid" 2>/dev/null)

  # Hide ttyACM from all processes except our bridge daemon
  # by bind-mounting /dev/null over it in init namespace,
  # then restoring the real device only in our daemon's namespace
  local flipper_dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
  if [ -n "$flipper_dev" ] && [ -c "$flipper_dev" ]; then
    # Save the device info
    local dev_major=$(stat -c '%t' "$flipper_dev" 2>/dev/null)
    local dev_minor=$(stat -c '%T' "$flipper_dev" 2>/dev/null)
    local dev_context=$(ls -Z "$flipper_dev" 2>/dev/null | awk '{print $1}')

    echo "$flipper_dev|$dev_major|$dev_minor|$dev_context" > "$STEALTH_DIR/flipper_dev_info"

    slog "Flipper device $flipper_dev hidden from global namespace"
  fi

  # Hide our config directory from non-root processes
  chmod 700 "$CONFIG_DIR" 2>/dev/null
  chattr +h "$CONFIG_DIR" 2>/dev/null

  # Hide WebUI port — iptables: only loopback, drop everything else
  local port="${WEBUI_PORT:-8089}"
  iptables -I INPUT -p tcp --dport "$port" ! -i lo -j DROP 2>/dev/null
  ip6tables -I INPUT -p tcp --dport "$port" -j DROP 2>/dev/null

  slog "FlipperDroid stealth active"
}

teardown_flipper_stealth() {
  slog "Removing FlipperDroid stealth"

  # Remove iptables rules
  local port="${WEBUI_PORT:-8089}"
  iptables -D INPUT -p tcp --dport "$port" ! -i lo -j DROP 2>/dev/null
  ip6tables -D INPUT -p tcp --dport "$port" -j DROP 2>/dev/null

  # Unhide config
  chattr -h "$CONFIG_DIR" 2>/dev/null
  chmod 755 "$CONFIG_DIR" 2>/dev/null

  slog "FlipperDroid stealth removed"
}

#############################
# Status
#############################

stealth_status() {
  local active_count=0
  [ -f "$STEALTH_DIR/mounts/active" ] && active_count=$(wc -l < "$STEALTH_DIR/mounts/active")

  local port_hidden=0
  iptables -C INPUT -p tcp --dport "${WEBUI_PORT:-8089}" ! -i lo -j DROP 2>/dev/null && port_hidden=1

  local config_hidden=0
  lsattr -d "$CONFIG_DIR" 2>/dev/null | grep -q 'h' && config_hidden=1

  cat << EOF
{
  "active_bind_mounts": $active_count,
  "port_hidden": $port_hidden,
  "config_hidden": $config_hidden,
  "stealth_dir": "$STEALTH_DIR"
}
EOF
}

#############################
# CLI
#############################

source "$CONFIG_DIR/config.sh" 2>/dev/null

case "${1:-}" in
  apply)
    apply_map
    apply_flipper_stealth
    ;;
  teardown)
    teardown_map
    teardown_flipper_stealth
    ;;
  status)
    stealth_status
    ;;
  mount)
    # Manual: fd-stealth mount <stock_path> <custom_file> <process> <type>
    clone_metadata "$2" "$MODDIR/stealth/$3"
    local pid=$(find_pid "$4")
    [ -n "$pid" ] && ns_bind_mount "$pid" "$2" "$MODDIR/stealth/$3"
    ;;
  unmount)
    # Manual: fd-stealth unmount <stock_path> <process>
    local pid=$(find_pid "$3")
    [ -n "$pid" ] && ns_bind_unmount "$pid" "$2"
    ;;
  hide-dev)
    apply_flipper_stealth
    ;;
  show-dev)
    teardown_flipper_stealth
    ;;
  *)
    echo "FlipperDroid Stealth — Namespace Isolation"
    echo ""
    echo "Usage: fd-stealth <command>"
    echo ""
    echo "  apply       Apply stealth map + hide FlipperDroid"
    echo "  teardown    Remove all bind mounts + unhide"
    echo "  status      Show active stealth state"
    echo "  mount       Manual bind mount into process namespace"
    echo "  unmount     Manual unmount from process namespace"
    echo "  hide-dev    Hide Flipper device + port + config"
    echo "  show-dev    Unhide everything"
    ;;
esac
