"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecurityScanner = void 0;
const logger_1 = require("../logger");
const child_process_1 = require("child_process");
const net = __importStar(require("net"));
const os = __importStar(require("os"));
// Well-known risky ports
const RISKY_PORTS = {
    21: { service: 'FTP', severity: 'high', desc: 'FTP allows file transfer, often with weak auth' },
    23: { service: 'Telnet', severity: 'critical', desc: 'Telnet transmits data in plaintext' },
    25: { service: 'SMTP', severity: 'medium', desc: 'SMTP may allow open relay' },
    53: { service: 'DNS', severity: 'info', desc: 'DNS server' },
    80: { service: 'HTTP', severity: 'info', desc: 'HTTP web server' },
    110: { service: 'POP3', severity: 'medium', desc: 'POP3 may transmit credentials in plaintext' },
    135: { service: 'RPC', severity: 'high', desc: 'Microsoft RPC endpoint mapper' },
    139: { service: 'NetBIOS', severity: 'high', desc: 'NetBIOS session service' },
    143: { service: 'IMAP', severity: 'medium', desc: 'IMAP may transmit credentials in plaintext' },
    443: { service: 'HTTPS', severity: 'info', desc: 'HTTPS web server' },
    445: { service: 'SMB', severity: 'high', desc: 'SMB file sharing - common attack vector' },
    1433: { service: 'MSSQL', severity: 'high', desc: 'Microsoft SQL Server' },
    1434: { service: 'MSSQL Browser', severity: 'high', desc: 'SQL Server Browser' },
    3306: { service: 'MySQL', severity: 'high', desc: 'MySQL database server' },
    3389: { service: 'RDP', severity: 'medium', desc: 'Remote Desktop Protocol' },
    5432: { service: 'PostgreSQL', severity: 'high', desc: 'PostgreSQL database server' },
    5900: { service: 'VNC', severity: 'high', desc: 'VNC remote desktop' },
    6379: { service: 'Redis', severity: 'critical', desc: 'Redis (often no auth)' },
    8080: { service: 'HTTP Alt', severity: 'info', desc: 'Alternative HTTP port' },
    8443: { service: 'HTTPS Alt', severity: 'info', desc: 'Alternative HTTPS port' },
    27017: { service: 'MongoDB', severity: 'critical', desc: 'MongoDB (often no auth)' },
};
const COMMON_SERVICES = {
    22: 'SSH', 53: 'DNS', 67: 'DHCP', 68: 'DHCP', 69: 'TFTP',
    80: 'HTTP', 88: 'Kerberos', 111: 'RPCBind', 123: 'NTP',
    161: 'SNMP', 162: 'SNMP Trap', 389: 'LDAP', 443: 'HTTPS',
    464: 'Kerberos', 514: 'Syslog', 587: 'SMTP Submission',
    636: 'LDAPS', 993: 'IMAPS', 995: 'POP3S', 1080: 'SOCKS',
    1521: 'Oracle', 2049: 'NFS', 2379: 'etcd', 2380: 'etcd',
    3000: 'Dev Server', 3001: 'Dev Server', 4443: 'HTTPS Alt',
    5000: 'Dev Server', 5432: 'PostgreSQL', 5672: 'AMQP',
    6443: 'Kubernetes', 8000: 'HTTP Alt', 8080: 'HTTP Alt',
    8443: 'HTTPS Alt', 9090: 'Prometheus', 9200: 'Elasticsearch',
    9300: 'Elasticsearch', 27017: 'MongoDB', 27018: 'MongoDB',
};
class SecurityScanner {
    constructor() {
        this.scanning = false;
    }
    /**
     * Check if dependencies are available and install if needed
     */
    async ensureDependencies() {
        // The scanner uses native Node.js net module for port scanning
        // No external dependencies needed for basic scanning
        logger_1.logger.info('Security scanner dependencies verified (using native Node.js net module)');
    }
    /**
     * Parse port range string like "1-1024,3306,3389,8080-8090"
     */
    parsePorts(portRange) {
        const ports = new Set();
        const parts = portRange.split(',').map(s => s.trim());
        for (const part of parts) {
            if (part.includes('-')) {
                const [start, end] = part.split('-').map(Number);
                if (!isNaN(start) && !isNaN(end)) {
                    for (let p = Math.max(1, start); p <= Math.min(65535, end); p++) {
                        ports.add(p);
                    }
                }
            }
            else {
                const p = parseInt(part);
                if (!isNaN(p) && p >= 1 && p <= 65535)
                    ports.add(p);
            }
        }
        return Array.from(ports).sort((a, b) => a - b);
    }
    ipv4ToInt(ip) {
        const parts = ip.split('.').map((p) => parseInt(p, 10));
        if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255))
            return null;
        return (((parts[0] << 24) >>> 0) + (parts[1] << 16) + (parts[2] << 8) + parts[3]) >>> 0;
    }
    intToIpv4(value) {
        return [
            (value >>> 24) & 255,
            (value >>> 16) & 255,
            (value >>> 8) & 255,
            value & 255,
        ].join('.');
    }
    netmaskToPrefix(mask) {
        const parts = mask.split('.').map((p) => parseInt(p, 10));
        if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255))
            return 24;
        const bits = parts.map((n) => n.toString(2).padStart(8, '0')).join('');
        const firstZero = bits.indexOf('0');
        return firstZero >= 0 ? firstZero : 32;
    }
    parseSweepTargets(target) {
        const raw = String(target || '').trim();
        if (!raw || raw.toLowerCase() === 'localhost' || raw.toLowerCase() === 'local')
            return ['127.0.0.1'];
        // CSV list of explicit hosts
        if (raw.includes(',')) {
            return raw
                .split(',')
                .map((h) => h.trim())
                .filter(Boolean)
                .slice(0, 1024);
        }
        // CIDR (IPv4 only)
        if (raw.includes('/')) {
            const [ip, prefixRaw] = raw.split('/');
            const prefix = parseInt(prefixRaw, 10);
            const ipInt = this.ipv4ToInt(ip);
            if (ipInt != null && !Number.isNaN(prefix) && prefix >= 0 && prefix <= 32) {
                const hostBits = 32 - prefix;
                const total = Math.max(1, Math.pow(2, hostBits));
                const mask = prefix === 0 ? 0 : ((0xffffffff << hostBits) >>> 0);
                const network = (ipInt & mask) >>> 0;
                const firstHost = prefix <= 30 ? network + 1 : network;
                const lastHost = prefix <= 30 ? (network + total - 2) : (network + total - 1);
                const maxHosts = 1024;
                const hosts = [];
                for (let current = firstHost; current <= lastHost && hosts.length < maxHosts; current++) {
                    hosts.push(this.intToIpv4(current >>> 0));
                }
                return hosts;
            }
        }
        // A-B IPv4 range
        const range = raw.match(/^(\d{1,3}(?:\.\d{1,3}){3})\s*-\s*(\d{1,3}(?:\.\d{1,3}){3})$/);
        if (range) {
            const start = this.ipv4ToInt(range[1]);
            const end = this.ipv4ToInt(range[2]);
            if (start != null && end != null && end >= start) {
                const hosts = [];
                for (let current = start; current <= end && hosts.length < 1024; current++) {
                    hosts.push(this.intToIpv4(current >>> 0));
                }
                return hosts;
            }
        }
        return [raw];
    }
    pingHost(host, timeoutMs = 1500) {
        return new Promise((resolve) => {
            const isWindows = process.platform === 'win32';
            const args = isWindows
                ? ['-n', '1', '-w', String(Math.max(500, Math.floor(timeoutMs))), host]
                : ['-c', '1', '-W', String(Math.max(1, Math.ceil(timeoutMs / 1000))), host];
            (0, child_process_1.execFile)('ping', args, { timeout: timeoutMs + 3000, maxBuffer: 256 * 1024 }, (error, stdout, stderr) => {
                const combined = `${stdout || ''}\n${stderr || ''}`.trim();
                const latencyMatch = combined.match(/time[=<]?\s*([0-9.]+)\s*ms/i) || combined.match(/Average = (\d+)ms/i);
                const latencyMs = latencyMatch ? Number(latencyMatch[1]) || 0 : 0;
                const alive = !error || /ttl=|bytes from|time[=<]/i.test(combined);
                resolve({
                    host,
                    alive,
                    latencyMs,
                    output: combined.substring(0, 200),
                });
            });
        });
    }
    async runPingSweep(target, options = {}, onProgress) {
        const targets = this.parseSweepTargets(target);
        const concurrency = Math.max(1, Math.min(256, options.concurrency || 64));
        const timeout = options.timeout || 1500;
        const aliveHosts = [];
        const startTime = Date.now();
        let completed = 0;
        let idx = 0;
        const runBatch = async () => {
            const batch = [];
            while (idx < targets.length && batch.length < concurrency) {
                const host = targets[idx++];
                batch.push(this.pingHost(host, timeout).then((result) => {
                    completed++;
                    if (result.alive) {
                        aliveHosts.push({ host: result.host, latencyMs: result.latencyMs, output: result.output });
                    }
                    if (onProgress && (completed % 10 === 0 || completed === targets.length)) {
                        onProgress((completed / Math.max(1, targets.length)) * 100);
                    }
                }));
            }
            await Promise.all(batch);
        };
        while (idx < targets.length) {
            await runBatch();
        }
        return {
            aliveHosts,
            scannedHosts: targets.length,
            scanTime: Date.now() - startTime,
        };
    }
    discoverNetworkSegments() {
        const interfaces = os.networkInterfaces();
        const segments = [];
        for (const [name, addrs] of Object.entries(interfaces)) {
            if (!Array.isArray(addrs) || !name)
                continue;
            const vlanMatch = name.match(/(?:^|[-_.])vlan[-_.]?(\d{1,4})$/i) || name.match(/[._](\d{1,4})$/);
            const vlanId = vlanMatch ? vlanMatch[1] : undefined;
            for (const addr of addrs) {
                if (!addr || addr.family !== 'IPv4' || addr.internal || !addr.address)
                    continue;
                const cidr = addr.cidr || `${addr.address}/${this.netmaskToPrefix(addr.netmask || '255.255.255.0')}`;
                segments.push({
                    interfaceName: name,
                    address: addr.address,
                    cidr,
                    vlanId,
                });
            }
        }
        return segments;
    }
    /**
     * Scan a single TCP port
     */
    scanPort(host, port, timeout = 2000) {
        return new Promise((resolve) => {
            const socket = new net.Socket();
            let banner = '';
            let resolved = false;
            const finish = (open) => {
                if (resolved)
                    return;
                resolved = true;
                socket.destroy();
                resolve({ port, open, banner: banner.trim().substring(0, 200) });
            };
            socket.setTimeout(timeout);
            socket.on('connect', () => {
                // Try to grab a banner
                socket.setTimeout(1500);
                socket.on('data', (data) => {
                    banner = data.toString('utf-8');
                    finish(true);
                });
                // If no data after short wait, still mark as open
                setTimeout(() => finish(true), 1500);
            });
            socket.on('timeout', () => finish(false));
            socket.on('error', () => finish(false));
            try {
                socket.connect(port, host);
            }
            catch {
                finish(false);
            }
        });
    }
    /**
     * Run a port scan against a target
     */
    async runPortScan(target, portRange = '1-1024', options = {}, onProgress) {
        const ports = this.parsePorts(portRange);
        const concurrency = options.concurrency || 100;
        const timeout = options.timeout || 2000;
        const openPorts = [];
        const startTime = Date.now();
        let completed = 0;
        let idx = 0;
        const runBatch = async () => {
            const batch = [];
            while (idx < ports.length && batch.length < concurrency) {
                const port = ports[idx++];
                batch.push(this.scanPort(target, port, timeout).then(result => {
                    completed++;
                    if (result.open) {
                        openPorts.push({ port: result.port, banner: result.banner });
                    }
                    if (onProgress && completed % 50 === 0) {
                        onProgress((completed / ports.length) * 100);
                    }
                }));
            }
            await Promise.all(batch);
        };
        while (idx < ports.length) {
            await runBatch();
        }
        return { openPorts, scanTime: Date.now() - startTime };
    }
    buildSummary(findings, totalHosts, totalPorts, openPorts, scanDuration) {
        return {
            totalHosts,
            totalPorts,
            openPorts,
            criticalFindings: findings.filter((f) => f.severity === 'critical').length,
            highFindings: findings.filter((f) => f.severity === 'high').length,
            mediumFindings: findings.filter((f) => f.severity === 'medium').length,
            lowFindings: findings.filter((f) => f.severity === 'low').length,
            infoFindings: findings.filter((f) => f.severity === 'info').length,
            scanDuration,
        };
    }
    /**
     * Run a full security scan
     */
    async scan(scanId, scanType = 'port', target, portRange = '1-1024', options = {}, onProgress) {
        if (this.scanning) {
            return {
                scanId,
                findings: [],
                summary: { totalHosts: 0, totalPorts: 0, openPorts: 0, criticalFindings: 0, highFindings: 0, mediumFindings: 0, lowFindings: 0, infoFindings: 0, scanDuration: 0 },
                error: 'A scan is already in progress',
            };
        }
        this.scanning = true;
        const startTime = Date.now();
        try {
            await this.ensureDependencies();
            const normalizedType = String(scanType || 'port').toLowerCase();
            logger_1.logger.info(`Starting security scan: type=${normalizedType} target=${target} ports=${portRange}`);
            const findings = [];
            let totalHosts = 1;
            let totalPorts = 0;
            let openPortsCount = 0;
            let scanDuration = 0;
            if (normalizedType === 'ping_sweep') {
                const sweep = await this.runPingSweep(target, options || {}, onProgress);
                totalHosts = sweep.scannedHosts;
                openPortsCount = sweep.aliveHosts.length;
                scanDuration = sweep.scanTime;
                for (const hostResult of sweep.aliveHosts) {
                    findings.push({
                        type: 'host_alive',
                        severity: 'info',
                        host: hostResult.host,
                        protocol: 'icmp',
                        service: 'ICMP',
                        banner: hostResult.output || '',
                        description: `Host ${hostResult.host} responded to ping${hostResult.latencyMs ? ` (${hostResult.latencyMs.toFixed(1)} ms)` : ''}`,
                        recommendation: 'Investigate exposed hosts and continue with targeted port/service scans.',
                    });
                }
            }
            else if (normalizedType === 'vlan') {
                const segments = this.discoverNetworkSegments();
                totalHosts = segments.length;
                scanDuration = Date.now() - startTime;
                onProgress?.(100);
                for (const segment of segments) {
                    const hasVlanId = !!segment.vlanId;
                    findings.push({
                        type: hasVlanId ? 'vlan_detected' : 'network_segment',
                        severity: 'info',
                        host: segment.address,
                        protocol: 'layer3',
                        service: hasVlanId ? `VLAN ${segment.vlanId}` : 'Interface Segment',
                        banner: segment.interfaceName,
                        description: hasVlanId
                            ? `Interface ${segment.interfaceName} appears to be on VLAN ${segment.vlanId} (${segment.cidr})`
                            : `Interface ${segment.interfaceName} network segment detected (${segment.cidr})`,
                        recommendation: 'Review segmentation boundaries, inter-VLAN routing, and ACL policies.',
                    });
                }
            }
            else {
                const ports = this.parsePorts(portRange);
                totalPorts = ports.length;
                const { openPorts, scanTime } = await this.runPortScan(target, portRange, options, onProgress);
                scanDuration = scanTime;
                openPortsCount = openPorts.length;
                for (const { port, banner } of openPorts) {
                    const risky = RISKY_PORTS[port];
                    const serviceName = risky?.service || COMMON_SERVICES[port] || 'Unknown';
                    const severity = risky?.severity || 'info';
                    const desc = risky?.desc || `Port ${port} (${serviceName}) is open`;
                    const recommendation = severity === 'critical' || severity === 'high'
                        ? `Consider closing port ${port} or restricting access with a firewall`
                        : severity === 'medium'
                            ? `Ensure port ${port} (${serviceName}) is properly secured`
                            : `Port ${port} (${serviceName}) detected`;
                    findings.push({
                        type: 'port_open',
                        severity,
                        host: target,
                        port,
                        protocol: 'tcp',
                        service: serviceName,
                        banner,
                        description: desc,
                        recommendation,
                    });
                    if (normalizedType === 'service' && banner) {
                        findings.push({
                            type: 'service_banner',
                            severity: 'info',
                            host: target,
                            port,
                            protocol: 'tcp',
                            service: serviceName,
                            banner,
                            description: `Service banner detected on port ${port}`,
                            recommendation: 'Review exposed service versions and disable unnecessary banner disclosure where possible.',
                        });
                    }
                    if (normalizedType === 'vulnerability' && risky && (risky.severity === 'critical' || risky.severity === 'high')) {
                        findings.push({
                            type: 'potential_exposure',
                            severity: risky.severity,
                            host: target,
                            port,
                            protocol: 'tcp',
                            service: serviceName,
                            banner,
                            description: `${serviceName} on port ${port} is commonly targeted and requires hardening.`,
                            recommendation: 'Restrict access to trusted networks, patch regularly, and enforce strong authentication.',
                        });
                    }
                }
            }
            const summary = this.buildSummary(findings, totalHosts, totalPorts, openPortsCount, scanDuration || (Date.now() - startTime));
            logger_1.logger.info(`Security scan complete: type=${normalizedType} findings=${findings.length}`);
            return { scanId, findings, summary };
        }
        catch (e) {
            logger_1.logger.error(`Security scan error: ${e.message}`);
            return {
                scanId,
                findings: [],
                summary: this.buildSummary([], 0, 0, 0, Date.now() - startTime),
                error: e.message,
            };
        }
        finally {
            this.scanning = false;
        }
    }
}
exports.SecurityScanner = SecurityScanner;
//# sourceMappingURL=security-scanner.js.map