#!/usr/bin/env bash
set -u
ROOT="$HOME/ForensicTimeValidation"; RUN="$(date -u +%Y%m%dT%H%M%SZ)"
R="$ROOT/$RUN"; W="$R/work"; L="$R/logs"; O="$R/results"
mkdir -p "$W"/{source,copy_default,copy_p,copy_a,rsync_a,rename,tar_extract} "$L" "$O"
exec > >(tee -a "$L/transcript.txt") 2>&1
C='\033[0;36m';G='\033[0;32m';Y='\033[1;33m';X='\033[0m'
banner(){ echo; echo -e "${C}====================================================================${X}";echo -e "${C} $*${X}";echo -e "${C}====================================================================${X}"; }
pass(){ echo -e "${G}[PASS] $*${X}";}; obs(){ echo -e "${Y}[OBSERVED] $*${X}";}; info(){ echo -e "${C}[INFO] $*${X}";}
pause(){ read -r -p "ENTER=run | s=skip | q=quit: " a; case "${a:-}" in s|S)return 1;;q|Q)exit 0;;*)return 0;;esac; }
state(){ stat -c 'Path=%n|Inode=%i|Size=%s|Mode=%A|UID=%u|GID=%g|Access=%x|Modify=%y|Change=%z|Birth=%w' "$1"; }
hash(){ sha256sum "$1"|awk '{print $1}';}
printf 'Test\tClaim\tResult\tEvidence\n' > "$O/claims.tsv"
claim(){ printf '%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" >> "$O/claims.tsv";}

banner "LINUX FORENSIC TIME VALIDATOR"
info "Synthetic workspace: $R"
banner "PRE-FLIGHT"
cat /etc/os-release | tee "$L/os.txt"; uname -a | tee "$L/kernel.txt"
findmnt -T "$W" -o TARGET,SOURCE,FSTYPE,OPTIONS | tee "$L/findmnt.txt"
FSTYPE="$(findmnt -T "$W" -no FSTYPE)"; OPTS="$(findmnt -T "$W" -no OPTIONS)"
info "Filesystem=$FSTYPE"; info "Mount options=$OPTS"
for c in stat sha256sum cp mv tar findmnt;do command -v "$c" >/dev/null||{ echo "Missing $c";exit 1;};done
for c in rsync getfacl setfacl getfattr setfattr;do command -v "$c" >/dev/null||obs "$c missing; related test may skip.";done

banner "01 BASELINE"
pause && {
 F="$W/source/original.txt"; printf 'FORENSIC LINUX TIMESTAMP VALIDATION\n' > "$F"
 touch -m -d '2024-01-02 03:04:05 UTC' "$F"; touch -a -d '2024-01-02 03:14:05 UTC' "$F"
 state "$F"|tee "$L/01_baseline.txt";sha256sum "$F"|tee "$L/01_hash.txt";BASE="$(hash "$F")";pass "Baseline created."
}

banner "02 SHA256SUM READ EFFECT"
pause && {
 state "$F"|tee "$L/02_before.txt"; A1="$(stat -c %x "$F")";sleep 5;sha256sum "$F"|tee "$L/02_hash.txt";state "$F"|tee "$L/02_after.txt";A2="$(stat -c %x "$F")"
 [[ "$(hash "$F")" == "$BASE" ]]&&pass "Content unchanged."
 [[ "$A1" == "$A2" ]]&&{ obs "atime unchanged here.";claim "02" "Read may affect atime" "CONDITIONAL" "No change; $OPTS";}||{ obs "atime changed.";claim "02" "Read may affect atime" "CONFIRMED HERE" "$A1 -> $A2";}
}

banner "03 DEFAULT cp"
pause && {
 D="$W/copy_default/original.txt";sleep 5;cp "$F" "$D";state "$F"|tee "$L/03_source.txt";state "$D"|tee "$L/03_dest.txt";sha256sum "$F" "$D"|tee "$L/03_hashes.txt"
 [[ "$(hash "$F")" == "$(hash "$D")" ]]&&pass "Hashes match."
 claim "03" "Default cp creates new destination object" "OBSERVED" "source inode=$(stat -c %i "$F"); dest inode=$(stat -c %i "$D")"
}

banner "04 cp -p"
pause && {
 D="$W/copy_p/original.txt";sleep 5;cp -p "$F" "$D";state "$F"|tee "$L/04_source.txt";state "$D"|tee "$L/04_dest.txt";sha256sum "$F" "$D"|tee "$L/04_hashes.txt"
 claim "04" "cp -p preserves selected metadata, not filesystem identity" "OBSERVED" "See 04 logs"
}

banner "05 cp -a"
pause && {
 D="$W/copy_a/original.txt";sleep 5;cp -a "$F" "$D";state "$F"|tee "$L/05_source.txt";state "$D"|tee "$L/05_dest.txt";sha256sum "$F" "$D"|tee "$L/05_hashes.txt"
 claim "05" "cp -a is logical preservation, not inode cloning" "OBSERVED" "See 05 logs"
}

banner "06 rsync -a"
if command -v rsync >/dev/null;then pause && {
 D="$W/rsync_a/original.txt";sleep 5;rsync -a "$F" "$D";state "$F"|tee "$L/06_source.txt";state "$D"|tee "$L/06_dest.txt";sha256sum "$F" "$D"|tee "$L/06_hashes.txt"
 claim "06" "rsync -a preserves selected metadata; destination remains new object" "OBSERVED" "See 06 logs"
};fi

banner "07 SAME-FILESYSTEM mv"
pause && {
 B="$W/rename/before.txt";A="$W/rename/after.txt";cp -p "$F" "$B";state "$B"|tee "$L/07_before.txt";BI="$(stat -c %i "$B")";BC="$(stat -c %z "$B")";sleep 5;mv "$B" "$A";state "$A"|tee "$L/07_after.txt";AI="$(stat -c %i "$A")";AC="$(stat -c %z "$A")"
 [[ "$BI" == "$AI" ]]&&pass "Same inode: $AI."||obs "Inode changed."
 [[ "$BC" != "$AC" ]]&&pass "ctime changed."||obs "ctime unchanged at available precision."
 claim "07" "Same-filesystem rename normally retains inode identity" "OBSERVED" "$BI -> $AI"
}

banner "08 chmod METADATA CHANGE"
pause && {
 D="$W/metadata_change.txt";cp -p "$F" "$D";BH="$(hash "$D")";BM="$(stat -c %y "$D")";BC="$(stat -c %z "$D")";state "$D"|tee "$L/08_before.txt";sleep 5;chmod 600 "$D";state "$D"|tee "$L/08_after.txt";AH="$(hash "$D")";AM="$(stat -c %y "$D")";AC="$(stat -c %z "$D")"
 [[ "$BH" == "$AH" ]]&&pass "Hash unchanged.";[[ "$BM" == "$AM" ]]&&pass "mtime unchanged.";[[ "$BC" != "$AC" ]]&&pass "ctime changed."
 claim "08" "Metadata can change without content modification" "OBSERVED" "hashsame=$([[ "$BH" == "$AH" ]]&&echo yes||echo no); mtimesame=$([[ "$BM" == "$AM" ]]&&echo yes||echo no); ctimechanged=$([[ "$BC" != "$AC" ]]&&echo yes||echo no)"
}

banner "09 touch BACKDATING"
pause && {
 D="$W/touch_test.txt";cp -p "$F" "$D";state "$D"|tee "$L/09_before.txt";sleep 5;touch -d '2020-01-01 12:00:00 UTC' "$D";state "$D"|tee "$L/09_after.txt"
 claim "09" "Backdated atime/mtime may coexist with newer ctime" "OBSERVED" "See 09 logs"
}

banner "10 ACL + XATTR"
if command -v setfacl >/dev/null&&command -v setfattr >/dev/null;then pause && {
 D="$W/source/metadata.txt";cp -p "$F" "$D";BH="$(hash "$D")";state "$D"|tee "$L/10_before.txt";sleep 5;setfacl -m "u:$(id -un):rw" "$D";setfattr -n user.forensic_test -v LAB409 "$D";state "$D"|tee "$L/10_after.txt";getfacl -p "$D"|tee "$L/10_acl.txt";getfattr -d -m- "$D"|tee "$L/10_xattr.txt";[[ "$BH" == "$(hash "$D")" ]]&&pass "Content unchanged."
 claim "10" "ACL/xattr changes are metadata activity" "OBSERVED" "See 10 logs"
};fi

banner "11 TAR CONTAINER"
pause && {
 PRE="$(stat -c %x "$F")";state "$F"|tee "$L/11_pre.txt";sha256sum "$W"/source/*.txt > "$L/11_evidence_hashes.txt"
 cd "$W";ARGS=(--numeric-owner --atime-preserve=system);tar --help|grep -q -- --acls&&ARGS+=(--acls);tar --help|grep -q -- --xattrs&&ARGS+=(--xattrs);tar "${ARGS[@]}" -czf evidence_package.tar.gz source ../logs/11_evidence_hashes.txt;cd - >/dev/null
 sha256sum "$W/evidence_package.tar.gz"|tee "$O/evidence_package.sha256";POST="$(stat -c %x "$F")";state "$F"|tee "$L/11_post.txt";obs "Source atime: $PRE -> $POST"
 claim "11" "tar --atime-preserve=system attempts to avoid source atime change" "OBSERVED" "$PRE -> $POST"
}

banner "12 TAR EXTRACTION"
pause && {
 cd "$W";X=();tar --help|grep -q -- --acls&&X+=(--acls);tar --help|grep -q -- --xattrs&&X+=(--xattrs);tar "${X[@]}" -xzf evidence_package.tar.gz -C tar_extract;cd - >/dev/null
 E="$W/tar_extract/source/original.txt";state "$F"|tee "$L/12_source.txt";state "$E"|tee "$L/12_extract.txt";sha256sum "$F" "$E"|tee "$L/12_hashes.txt";[[ "$(hash "$F")" == "$(hash "$E")" ]]&&pass "Extracted content matches."
 claim "12" "Extraction creates destination objects while content can match" "OBSERVED" "See 12 logs"
}

banner "13 OPTIONAL SCP"
if command -v scp >/dev/null;then read -r -p "Second SSH endpoint available? [y/N]: " yn;if [[ "$yn" =~ ^[Yy]$ ]];then read -r -p "Remote user@host: " REMOTE;scp "$F" "$REMOTE:/tmp/scp_default.txt";scp -p "$F" "$REMOTE:/tmp/scp_p.txt";ssh "$REMOTE" "stat /tmp/scp_default.txt;stat /tmp/scp_p.txt;sha256sum /tmp/scp_default.txt /tmp/scp_p.txt"|tee "$L/13_scp.txt";fi;fi

banner "FINAL SUMMARY"
{
 echo "RUN=$RUN";echo "FSTYPE=$FSTYPE";echo "MOUNT_OPTIONS=$OPTS"
 for f in "$F" "$W/copy_default/original.txt" "$W/copy_p/original.txt" "$W/copy_a/original.txt" "$W/rsync_a/original.txt" "$W/rename/after.txt" "$W/metadata_change.txt" "$W/touch_test.txt" "$W/tar_extract/source/original.txt";do [[ -e "$f" ]]||continue;echo;echo "=== $f ===";state "$f";sha256sum "$f";done
}|tee "$O/Linux_Forensic_Time_Final_Summary.txt"
cp "$O/claims.tsv" "$O/Linux_Validated_Claims_Raw.tsv"
tar -czf "$ROOT/Linux_Forensic_Time_Validation_Results_${RUN}.tar.gz" -C "$ROOT" "$RUN"
sha256sum "$ROOT/Linux_Forensic_Time_Validation_Results_${RUN}.tar.gz"|tee "$ROOT/Linux_Forensic_Time_Validation_Results_${RUN}.sha256"
banner "COMPLETE"
pass "Upload: $ROOT/Linux_Forensic_Time_Validation_Results_${RUN}.tar.gz"
info "Do not treat OBSERVED claims as universal until reviewed against filesystem/mount/tool versions."
