"""Test package that exercises every syscall we trace.

This is a TEST FIXTURE — it performs actions that would be suspicious
in a real package, specifically to verify that our ptrace tracing
captures them correctly.

Each action is clearly labeled and benign (writes to /tmp, connects
to localhost, etc.)
"""

import os
import socket
import subprocess
import sys
import tempfile
from setuptools import setup
from setuptools.command.install import install


class ExerciseSyscalls(install):
    """Custom install that exercises all traced syscalls."""

    def run(self):
        print("[EXERCISE] Starting syscall exerciser...")

        # === SYS_OPENAT (already traced) ===
        print("[EXERCISE] 1. SYS_OPENAT — reading files")
        try:
            with open("/etc/hostname", "r") as f:
                f.read()
        except Exception:
            pass

        # === SYS_WRITE / SYS_PWRITE64 (NEW) ===
        print("[EXERCISE] 2. SYS_WRITE — writing to files")
        with open("/tmp/pip-witness-test-write.txt", "w") as f:
            f.write("This is a test write from pip-witness syscall exerciser\n")
            f.write("Second line of test data\n")

        # Write to a "suspicious" location
        with open("/tmp/.hidden-test-file", "w") as f:
            f.write("hidden file write test\n")

        # === SYS_RENAMEAT2 (NEW) ===
        print("[EXERCISE] 3. SYS_RENAME — renaming files")
        with open("/tmp/pip-witness-innocent.txt", "w") as f:
            f.write("will be renamed")
        os.rename("/tmp/pip-witness-innocent.txt", "/tmp/pip-witness-renamed.txt")

        # === SYS_UNLINKAT (NEW) ===
        print("[EXERCISE] 4. SYS_UNLINK — deleting files")
        with open("/tmp/pip-witness-to-delete.txt", "w") as f:
            f.write("will be deleted")
        os.unlink("/tmp/pip-witness-to-delete.txt")
        # Also delete the renamed file
        os.unlink("/tmp/pip-witness-renamed.txt")

        # === SYS_FCHMODAT (NEW) ===
        print("[EXERCISE] 5. SYS_CHMOD — changing permissions")
        script_path = "/tmp/pip-witness-test-script.sh"
        with open(script_path, "w") as f:
            f.write("#!/bin/sh\necho test\n")
        os.chmod(script_path, 0o755)  # Make executable (+x)
        os.chmod(script_path, 0o644)  # Remove executable

        # === SYS_EXECVE (already traced) ===
        print("[EXERCISE] 6. SYS_EXECVE — spawning processes")
        subprocess.run(["whoami"], capture_output=True)
        subprocess.run(["uname", "-a"], capture_output=True)

        # === SYS_SOCKET + SYS_CONNECT (already traced) ===
        print("[EXERCISE] 7. SYS_SOCKET + SYS_CONNECT — network")
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(1)
            s.connect(("127.0.0.1", 1))  # Will fail but syscall is captured
        except Exception:
            pass
        finally:
            s.close()

        # UDP socket
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.sendto(b"test", ("127.0.0.1", 9999))
            s.close()
        except Exception:
            pass

        # === SYS_MEMFD_CREATE (NEW) ===
        print("[EXERCISE] 8. SYS_MEMFD_CREATE — anonymous memory file")
        try:
            # Python doesn't expose memfd_create directly, use ctypes
            import ctypes
            import ctypes.util
            libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
            MFD_CLOEXEC = 1
            fd = libc.memfd_create(b"test-memfd", MFD_CLOEXEC)
            if fd >= 0:
                os.write(fd, b"memfd test content")
                os.close(fd)
                print("[EXERCISE]   memfd_create succeeded")
            else:
                print("[EXERCISE]   memfd_create not available")
        except Exception as e:
            print(f"[EXERCISE]   memfd_create: {e}")

        # === SYS_CLONE with namespace flags (NEW) ===
        # We can't easily do this from Python without root/capabilities,
        # but we can try unshare which uses CLONE_NEWNS
        print("[EXERCISE] 9. SYS_CLONE — namespace manipulation (will likely fail)")
        try:
            subprocess.run(["unshare", "--mount", "true"],
                         capture_output=True, timeout=2)
        except Exception:
            pass

        # === SYS_MOUNT (NEW) ===
        print("[EXERCISE] 10. SYS_MOUNT — filesystem mount (will fail without CAP_SYS_ADMIN)")
        try:
            import ctypes
            libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
            # Try to mount tmpfs (will fail, but syscall is captured)
            ret = libc.mount(b"none", b"/tmp/pip-witness-mount-test",
                           b"tmpfs", 0, None)
            print(f"[EXERCISE]   mount returned: {ret}")
        except Exception as e:
            print(f"[EXERCISE]   mount: {e}")

        # === SYS_PTRACE (NEW) ===
        print("[EXERCISE] 11. SYS_PTRACE — ptrace call (anti-debug check)")
        try:
            import ctypes
            libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
            PTRACE_TRACEME = 0
            ret = libc.ptrace(PTRACE_TRACEME, 0, None, None)
            print(f"[EXERCISE]   ptrace returned: {ret}")
        except Exception as e:
            print(f"[EXERCISE]   ptrace: {e}")

        # === SYS_DUP2 (NEW — reverse shell indicator) ===
        print("[EXERCISE] 12. SYS_DUP2 — fd redirection")
        try:
            # Create a socket and dup2 it to a high fd (not 0/1/2 to avoid breaking output)
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # dup2 the socket fd to fd 10 (won't trigger our detection since we only flag 0,1,2)
            # But let's also dup2 to stderr (fd 2) briefly to test detection
            saved_stderr = os.dup(2)
            os.dup2(s.fileno(), 2)  # redirect stderr to socket — reverse shell indicator!
            os.dup2(saved_stderr, 2)  # restore immediately
            os.close(saved_stderr)
            s.close()
            print("[EXERCISE]   dup2 to stderr detected")
        except Exception as e:
            print(f"[EXERCISE]   dup2: {e}")

        # === SYS_MPROTECT (NEW — shellcode indicator) ===
        print("[EXERCISE] 13. SYS_MPROTECT — make memory executable")
        try:
            import ctypes
            import mmap
            # Allocate memory and make it executable
            m = mmap.mmap(-1, 4096, prot=mmap.PROT_READ | mmap.PROT_WRITE)
            m.write(b"\x90" * 100)  # NOP sled (harmless)
            # mprotect to add EXEC — this is the suspicious part
            libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
            PROT_READ = 1
            PROT_EXEC = 4
            # Get the address of the mmap'd region
            buf = (ctypes.c_char * 4096).from_buffer(m)
            addr = ctypes.addressof(buf)
            ret = libc.mprotect(addr, 4096, PROT_READ | PROT_EXEC)
            print(f"[EXERCISE]   mprotect(EXEC) returned: {ret}")
            m.close()
        except Exception as e:
            print(f"[EXERCISE]   mprotect: {e}")

        # === SYS_PRCTL (NEW — process hiding) ===
        print("[EXERCISE] 14. SYS_PRCTL — rename process")
        try:
            import ctypes
            libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
            PR_SET_NAME = 15
            libc.prctl(PR_SET_NAME, b"totally-legit", 0, 0, 0)
            print("[EXERCISE]   prctl(PR_SET_NAME) called")
        except Exception as e:
            print(f"[EXERCISE]   prctl: {e}")

        # === SYS_SETSID (NEW — daemonize) ===
        print("[EXERCISE] 15. SYS_SETSID — create new session")
        try:
            import ctypes
            libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
            ret = libc.setsid()
            print(f"[EXERCISE]   setsid returned: {ret}")
        except Exception as e:
            print(f"[EXERCISE]   setsid: {e}")

        # Clean up
        for f in ["/tmp/pip-witness-test-write.txt", "/tmp/.hidden-test-file",
                  "/tmp/pip-witness-test-script.sh"]:
            try:
                os.unlink(f)
            except Exception:
                pass

        print("[EXERCISE] All syscalls exercised!")

        # Do the actual install
        install.run(self)


setup(
    name="syscall-exerciser",
    version="0.0.1",
    description="Test package that exercises all traced syscalls",
    py_modules=["syscall_exerciser_mod"],
    cmdclass={"install": ExerciseSyscalls},
    python_requires=">=3.8",
)
