#!/usr/bin/env python3
# For authorized testing in lab/CTF environments only
"""Active Directory attack simulation agent using Impacket and ldap3."""

import argparse
import sys
import json
import logging
from datetime import datetime

try:
    from impacket.smbconnection import SMBConnection
except ImportError:
    sys.exit("impacket is required: pip install impacket")

try:
    import ldap3
    from ldap3 import Server, Connection, ALL, SUBTREE
except ImportError:
    sys.exit("ldap3 is required: pip install ldap3")

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)


def ldap_enum_users(dc_ip: str, domain: str, username: str, password: str) -> list:
    """Enumerate domain users via LDAP, returning accounts with SPNs and no preauth."""
    base_dn = ",".join(f"DC={part}" for part in domain.split("."))
    server = Server(dc_ip, get_info=ALL, use_ssl=False)
    conn = Connection(server, user=f"{domain}\\{username}", password=password, auto_bind=True)

    conn.search(
        base_dn,
        "(objectClass=user)",
        search_scope=SUBTREE,
        attributes=[
            "sAMAccountName", "servicePrincipalName", "userAccountControl",
            "memberOf", "adminCount", "pwdLastSet", "lastLogon",
        ],
    )
    users = []
    for entry in conn.entries:
        uac = int(str(entry.userAccountControl)) if entry.userAccountControl else 0
        spn_list = list(entry.servicePrincipalName) if entry.servicePrincipalName else []
        no_preauth = bool(uac & 0x400000)
        users.append({
            "samaccountname": str(entry.sAMAccountName),
            "spns": spn_list,
            "no_preauth": no_preauth,
            "admin_count": str(entry.adminCount) if entry.adminCount else "0",
        })
    conn.unbind()
    logger.info("Enumerated %d domain users via LDAP", len(users))
    return users


def find_kerberoastable(users: list) -> list:
    """Filter users with service principal names set (Kerberoastable)."""
    targets = [u for u in users if u["spns"]]
    logger.info("Found %d Kerberoastable accounts", len(targets))
    return targets


def find_asrep_roastable(users: list) -> list:
    """Filter users with Kerberos pre-authentication disabled."""
    targets = [u for u in users if u["no_preauth"]]
    logger.info("Found %d AS-REP roastable accounts", len(targets))
    return targets


def enum_groups(dc_ip: str, domain: str, username: str, password: str) -> dict:
    """Enumerate high-value group memberships via LDAP."""
    base_dn = ",".join(f"DC={part}" for part in domain.split("."))
    server = Server(dc_ip, get_info=ALL)
    conn = Connection(server, user=f"{domain}\\{username}", password=password, auto_bind=True)

    high_value_groups = [
        "Domain Admins", "Enterprise Admins", "Schema Admins",
        "Backup Operators", "Account Operators",
    ]
    results = {}
    for group_name in high_value_groups:
        conn.search(
            base_dn,
            f"(&(objectClass=group)(cn={group_name}))",
            attributes=["member"],
        )
        members = []
        if conn.entries:
            members = list(conn.entries[0].member) if conn.entries[0].member else []
        results[group_name] = members
        logger.info("Group '%s' has %d members", group_name, len(members))

    conn.unbind()
    return results


def check_smb_signing(target_ip: str) -> bool:
    """Check if SMB signing is required on the target host."""
    try:
        smb = SMBConnection(target_ip, target_ip, sess_port=445, timeout=5)
        smb.negotiateSession()
        signing = smb.isSigningRequired()
        smb.close()
        return signing
    except Exception as exc:
        logger.warning("SMB connect failed on %s: %s", target_ip, exc)
        return True


def generate_report(users: list, groups: dict, dc_ip: str) -> dict:
    """Compile AD assessment findings into a structured report."""
    kerberoastable = find_kerberoastable(users)
    asrep = find_asrep_roastable(users)
    smb_signing = check_smb_signing(dc_ip)

    report = {
        "assessment_date": datetime.utcnow().isoformat(),
        "total_users": len(users),
        "kerberoastable_accounts": [u["samaccountname"] for u in kerberoastable],
        "asrep_roastable_accounts": [u["samaccountname"] for u in asrep],
        "high_value_groups": {g: len(m) for g, m in groups.items()},
        "dc_smb_signing_required": smb_signing,
        "risk_summary": [],
    }
    if kerberoastable:
        report["risk_summary"].append(
            f"CRITICAL: {len(kerberoastable)} accounts are Kerberoastable"
        )
    if asrep:
        report["risk_summary"].append(
            f"HIGH: {len(asrep)} accounts lack Kerberos pre-authentication"
        )
    if not smb_signing:
        report["risk_summary"].append("HIGH: SMB signing not required on DC - relay attacks possible")
    return report


def main():
    parser = argparse.ArgumentParser(description="AD Attack Simulation Agent")
    parser.add_argument("--dc-ip", required=True, help="Domain Controller IP")
    parser.add_argument("--domain", required=True, help="Domain FQDN (e.g., corp.local)")
    parser.add_argument("--username", required=True, help="Low-privilege domain username")
    parser.add_argument("--password", required=True, help="Domain user password")
    parser.add_argument("--output", default="ad_assessment.json", help="Output JSON report path")
    args = parser.parse_args()

    logger.info("Starting AD attack simulation against %s", args.domain)
    users = ldap_enum_users(args.dc_ip, args.domain, args.username, args.password)
    groups = enum_groups(args.dc_ip, args.domain, args.username, args.password)
    report = generate_report(users, groups, args.dc_ip)

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2)
    logger.info("Report saved to %s", args.output)
    print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
